Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ITensors] Fix rrule for applying quantum circuit to non-hermitian MPO with apply_dag=true #1344

Merged
merged 5 commits into from
Apr 3, 2024

Conversation

ntausend
Copy link
Contributor

@ntausend ntausend commented Feb 27, 2024

Description

This Pull request fixes the rrule for applying a quantum circuit to an MPO which is not hermitian with the keyword apply_dag = true.
Instead of assuming the MPO to be hermitian, I added an if statement in Line 122 of src/src/ITensorChainRules/mps/abstractmps.jl, checking if the MPO is hermitian. In case the MPO is hermitian, the old behavior is applied, in case
the MPO is not hermitian, I perform the correct pullback, which involves two contributions, one from the normal application of the gate, and one coming from the daggered application of the gate.

Fixes #1314

For demonstration, I used a (modified) version of the code snipped provided in the Issue. In the beginning one can choose using Complex or Floats, QN conserved states or dense states. In all cases the output is the expected one.

Code snipped

using ITensors
using Zygote: pullback
using Random: seed!

seed!(1234)
N = 2
# Field type
elT = ComplexF64
# explicitly make the MPO hermitian
hermitice = false
# using QN number conservation
qn_version = false

# dagger of mpo
dagger(A::MPO) = swapprime(dag(A), 0 => 1)
# hermitian part
hermitianpart(A::MPO) = 1/2 * (A + dagger(A))
# printing only 3 digits
round_mat(M) = map(m -> round(m, digits = 3), M)

if qn_version
	qvals = [QN("Parity", 0, 2) => N÷2,
			 QN("Parity", 1, 2) => N÷2]
	idx = Index(qvals)
else
	idx = Index(N)
end

# printing only 3 digits
round_mat(M) = map(m -> round(m, digits = 3), M)

# generate the test MPO
A = MPO(randomITensor(elT, prime(idx), dag(idx)), [idx])
hermitice && (A = hermitianpart(A))

# ITensor version
Ait = contract(A)
# fix the order to compare to numerical results
inds_order = inds(Ait)
# matrix version
Am = matrix(Ait)

# apply itensor
G = randomITensor(elT, prime(idx), dag(idx))
inds_order_G = inds(G)
# matrix version
Gm = matrix(G)

# generate a dual vector MPO
M = MPO(randomITensor(elT, prime(idx), dag(idx)), [idx])
hermitice && (M = hermitianpart(M))
# ITensor version
Mit = contract(M)
# matrix version
Mm = matrix(Mit)

# apply function without dagger apply
f(G) = apply([G], A)
fit(G) = apply([G], Ait)
fm(G) = G * Am

# compute the actions
fG   = matrix(permute(contract(f(G)), inds_order))
fGit = matrix(permute(fit(G), inds_order))
fGm  = fm(Gm) 

# should all result in the same matrix
println("Results from applying gate to MPO/ITensor/Matrix")
println("Are all versions of computing the contraction results in the same result? $((fG  fGit) && (fGit  fGm))")

# now we calculate the pullback of all these versions onto a test dual vetor
∇fG   = matrix(permute(pullback(f, G)[2](M)[1], inds_order_G))
∇fGit = matrix(permute(pullback(fit, G)[2](Mit)[1], inds_order_G))
∇fGm  =        pullback(fm, Gm)[2](Mm)[1]

println("Is the ITensor pullback equal to the matrix pullback? $(∇fGit  ∇fGm)")
println("Is the MPO pullback equal to the matrix pullback? $(∇fG  ∇fGm)")


# apply function with dagger apply
g(G) = apply([G], A; apply_dag = true)
git(G) = apply([G], Ait; apply_dag = true)
gm(G) = G * Am * G'

# compute the actions
gG   = matrix(permute(contract(g(G)), inds_order))
gGit = matrix(permute(git(G), inds_order))
gGm  = gm(Gm) 

# should all result in the same matrix
println("Results from applying gate to MPO/ITensor/Matrix")
println("Are all versions of computing the contraction results in the same result? $((gG  gGit) && (gGit  gGm))")

# now we calculate the pullback of all these versions onto a test dual vetor
∇gG   = matrix(permute(pullback(g, G)[2](M)[1], inds_order_G))
∇gGit = matrix(permute(pullback(git, G)[2](Mit)[1], inds_order_G))
∇gGm  =        pullback(gm, Gm)[2](Mm)[1]

println("Is the ITensor pullback equal to the matrix pullback? $(∇gGit  ∇gGm)")
println("Is the MPO pullback equal to the matrix pullback? $(∇gG  ∇gGm)")
println(∇gGit)
println(∇gG)

Minimal demonstration of previous behavior

The output before the fix was:

Results from applying gate to MPO/ITensor/Matrix
Are all versions of computing the contraction results in the same result? true
Is the ITensor pullback equal to the matrix pullback? true
Is the MPO pullback equal to the matrix pullback? true
Results from applying gate to MPO/ITensor/Matrix
Are all versions of computing the contraction results in the same result? true
Is the ITensor pullback equal to the matrix pullback? true
Is the MPO pullback equal to the matrix pullback? false
[-0.1423413716309932 -0.3131385545124809; -1.5979554418491506 -0.19790363455598498]
[0.7198139139346542 -1.652092462241267; 1.9081148486222423 -4.176254592261031]

with the last test failing, indicating the two pullbacks to be different.

Minimal demonstration of new behavior

After the fix one gets the new output:

Results from applying gate to MPO/ITensor/Matrix
Are all versions of computing the contraction results in the same result? true
Is the ITensor pullback equal to the matrix pullback? true
Is the MPO pullback equal to the matrix pullback? true
Results from applying gate to MPO/ITensor/Matrix
Are all versions of computing the contraction results in the same result? true
Is the ITensor pullback equal to the matrix pullback? true
Is the MPO pullback equal to the matrix pullback? true
[-0.1423413716309932 -0.3131385545124809; -1.5979554418491506 -0.19790363455598498]
[-0.1423413716309932 -0.3131385545124809; -1.5979554418491506 -0.19790363455598498]

With all test returning the desired result.

How Has This Been Tested?

I tested the behavior using the snipped in the above section. I especially run the script with all possible combinations,
I.e. the field being Floats or Complex, hermitian or not hermitian and using QN numbers or not. All tests were successful.

I did not implemented the code snipped as a test in the test folder, but it could be done if wanted.

Checklist:

  • My code follows the style guidelines of this project. Please run using JuliaFormatter; format(".") in the base directory of the repository (~/.julia/dev/ITensors) to format your code according to our style guidelines.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that verify the behavior of the changes I made.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • Any dependent changes have been merged and published in downstream modules.

@ntausend ntausend changed the title Fix rrule for applying quantum Cricuit to non-hermitian MPO with apply_dag=true, Fixes #1314 Fix rrule for applying quantum Cricuit to non-hermitian MPO with apply_dag=true Feb 27, 2024
@mtfishman
Copy link
Member

[test ITensors mps]

@mtfishman mtfishman changed the title Fix rrule for applying quantum Cricuit to non-hermitian MPO with apply_dag=true [ITensors] Fix rrule for applying quantum circuit to non-hermitian MPO with apply_dag=true Feb 27, 2024
Copy link
Contributor

Run ITensors mps tests from comment trigger: failed ❌
https://github.com/ITensor/ITensors.jl/actions/runs/8070505033

1 similar comment
Copy link
Contributor

Run ITensors mps tests from comment trigger: failed ❌
https://github.com/ITensor/ITensors.jl/actions/runs/8070505033

@mtfishman
Copy link
Member

I've already made a test for this case here: https://github.com/ITensor/ITensors.jl/blob/v0.3.57/test/ITensorLegacyMPS/ITensorChainRules/test_chainrules.jl#L172-L181, where I marked that it should throw an error but we can repurpose it now that it is working.

@ntausend
Copy link
Contributor Author

Ah ok, was not aware of that test. Thanks!

@ntausend
Copy link
Contributor Author

ntausend commented Apr 3, 2024

Hey, I was wondering if the PR will be included in the near future. Should I update the test in my fork and update the merge request?

@mtfishman
Copy link
Member

Yes, please update the test to test out this new functionality.

@mtfishman
Copy link
Member

[test ITensorMPS][test ITensorGaussianMPS]

Copy link
Contributor

github-actions bot commented Apr 3, 2024

Run ITensorGaussianMPS tests from comment trigger: succeeded ✅
https://github.com/ITensor/ITensors.jl/actions/runs/8542155592

1 similar comment
Copy link
Contributor

github-actions bot commented Apr 3, 2024

Run ITensorGaussianMPS tests from comment trigger: succeeded ✅
https://github.com/ITensor/ITensors.jl/actions/runs/8542155592

Copy link
Contributor

github-actions bot commented Apr 3, 2024

Run ITensorMPS tests from comment trigger: succeeded ✅
https://github.com/ITensor/ITensors.jl/actions/runs/8542155586

1 similar comment
Copy link
Contributor

github-actions bot commented Apr 3, 2024

Run ITensorMPS tests from comment trigger: succeeded ✅
https://github.com/ITensor/ITensors.jl/actions/runs/8542155586

@mtfishman
Copy link
Member

Looks good, thanks!

@mtfishman mtfishman merged commit 8cb097f into ITensor:main Apr 3, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ITensors][BUG] Derivative of gate application of non-Hermitian MPOs with apply_dag=true
2 participants