Are Jordan Wigner strings handled in apply?

Hi,

Just a quick question on fermionic/electronic sitetypes and where the JW strings are handled automatically. Looking at Fermion site-type operator `c`: `inner()` indicates `c` on different sites commute - #3 by christopherdavidwhit and Proper way to construct fermion gates, it’s unclear if Jordan Wigner strings are handled automatically in apply.

EDIT:
From some testing, it seems to depend on whether QN conservation is used. Here’s a minimal example showing this, where I create a state of two maximally entangled pairs. I will denote fermionic creation operators with c’s, bosonic creation operators as a’s, and pauli Z operators as F’s.

\ket{\psi} =\frac{1}{2}(\hat{c}^{\dag}_{2}+\hat{c}^{\dag}_{4})(\hat{c}^{\dag}_{1}+\hat{c}^{\dag}_{3})\ket{\textrm{vac}}, \\ =\frac{1}{2} (F_{1}\hat{a}^{\dag}_{2}+F_{1}F_{2}F_{3}\hat{a}^{\dag}_{4})(\hat{a}^{\dag}_{1}+F_{1}F_{2}\hat{a}^{\dag}_{3})\ket{\textrm{vac}}, \\ = \frac{1}{2}(F_{1}\hat{a}^{\dag}_{2}\hat{a}^{\dag}_{1}+\hat{a}^{\dag}_{2}\hat{a}^{\dag}_{3}+F_{1}\hat{a}^{\dag}_{4}\hat{a}^{\dag}_{1}+F_{3}\hat{a}^{\dag}_{4}\hat{a}^{\dag}_{3})\ket{\textrm{vac}} ,\\ = \frac{1}{2}(-\ket{1100}+\ket{0110}-\ket{1001}-\ket{0011}).

If the operators are applied in the opposite order, the answer should be

\ket{\psi} =\frac{1}{2} (\hat{a}^{\dag}_{1}+F_{1}F_{2}\hat{a}^{\dag}_{3})(F_{1}\hat{a}^{\dag}_{2}+F_{1}F_{2}F_{3}\hat{a}^{\dag}_{4})\ket{\textrm{vac}}, \\ = \frac{1}{2}(\ket{1100}-\ket{0110}+\ket{1001}+\ket{0011}),

i.e. minus the answer of the opposite ordering.

Here is the code to test whether the F factors are included.

N = 4
s = siteinds("Fermion",N,conserve_qns = true)
cdag = [op(s,"Cdag",i) for i in 1:N]
Id = [op(s,"Id",i) for i in 1:N]
psi = MPS(ComplexF64,s,"0")
gate1 = (cdag[1]*Id[3] + Id[1]*cdag[3])/sqrt(2)
gate2 = (cdag[2]*Id[4] + Id[2]*cdag[4])/sqrt(2)
psi = apply(gate1,psi)
psi = apply(gate2,psi)
@show(psi[1]*psi[2]*psi[3]*psi[4])

If conserve_qns = false, the result is the state with no minus signs (i.e the c operators are treated bosonically) either way the operators are applied. If conserve_qns=true, the answer is strange. If gate1 is applied first and then gate2, the answer is

\ket{\psi}= \frac{1}{2}(\ket{1100}+\ket{0110}+\ket{1001}-\ket{0011}),

which is wrong. If gate2 is applied first and then gate1, the answer is

\ket{\psi}= \frac{1}{2}(\ket{1100}-\ket{0110}+\ket{1001}+\ket{0011}),

which is as expected. If you could help clear up the confusion for me that would be great!

I think product states can sometimes be tricky to look at. I will say that apply is using swap gates to move sites to be neighbors for two site gates, and so this can sometimes get the signs right.

But here is my explanatory example:

julia> using ITensors,ITensorMPS
       s = siteinds("Electron",10; conserve_qns=true);
       state = [isodd(n) ? "Up" : "Dn" for n in 1:length(s)]
       psi0 = random_mps(s, state; linkdims=10);

julia> correlation_matrix(psi0,"Sz","Sz")[2,5]
-0.008653137186117061

julia> inner(psi0,apply(ops([("Sz",2),("Sz",5)],s),psi0))
-0.008653137186117061

julia> correlation_matrix(psi0,"Ntot * Sz","Nup")[2,5] # these don't commute
-0.007789870264149597

julia> inner(psi0,apply(ops([("Ntot * Sz",2),("Nup",5)],s),psi0))
-0.0077898702641496205

Things without JW strings work just fine, however let’s do some hops:

julia> os = OpSum()
       os += "Cdagup",2,"Cup",5
       O = MPO(os,s);

julia> inner(psi0',O,psi0) # this takes care of strings
0.01640983389785411

julia> correlation_matrix(psi0,"Cdagup","Cup")[2,5] # also takes care of strings
0.01640983389785412

julia> inner(psi0,apply(ops([("Cdagup",2),("Cup",5)],s),psi0)) #!! Don't do this
0.0583564218628752

where we see the strings are not being handled correctly, and if we insert them

julia> inner(psi0,apply(ops([("Cdagup * F",2),("F",3),("F",4),("Cup",5)],s),psi0))
0.016409833897854137

it checks out up to some numerical noise.


Using odd number of fermion strings will not be handled automatically anywhere

examples:
julia>
       os = OpSum()
       os += "Cdagup",2
       O = MPO(os,s);
ERROR: Parity-odd fermionic terms not yet supported by OpSum to MPO conversion
# ...

and

julia> left = apply(op("Cup",s[2]),psi0)
       right = apply(op("Cup",s[5]),psi0)
       inner(left,right)
0.05835642186287519

and with strings

julia> left = apply(ops([("F",1),("Cup",2)],s),psi0)
       right = apply(ops([("F",1),("F",2),("F",3),("F",4),("Cup",5)],s),psi0)
       inner(left,right)
0.016409833897854116

Note that ITensors is being rewritten and future versions may not have this issue

1 Like

To show how apply can get the signs right, lets say you have a two site gate:

julia> O = op("Cdagup * F",s[2])*op("Cup",s[5]); # this is a ord=4 ITensor

julia> inner(psi0,apply(O,psi0))
0.01640983389785411

Here because the internals are using swap gates we didn’t have to insert more F strings, but we did have to include an F with the Cdagup

Thankyou for the response!