From 83c0c0f1825a87700e109dfb56cd5f714f7464ac Mon Sep 17 00:00:00 2001 From: Miles Date: Thu, 24 Jul 2025 19:03:35 -0400 Subject: [PATCH 1/4] Fix exp for fermions and test --- src/tensor_operations/matrix_algebra.jl | 37 +++++++++++++++++++------ test/base/test_fermions.jl | 18 +++++++++++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/tensor_operations/matrix_algebra.jl b/src/tensor_operations/matrix_algebra.jl index 4b125503b5..0b8c80f8db 100644 --- a/src/tensor_operations/matrix_algebra.jl +++ b/src/tensor_operations/matrix_algebra.jl @@ -57,27 +57,46 @@ function exp(A::ITensor, Linds, Rinds; ishermitian=false) # Ensure the indices have the correct directions, # QNs, etc. - # First grab the indices in A, then permute them - # correctly. + + # Permute Lis, Ris to be in same order as on A Lis = permute(commoninds(A, Lis), Lis) Ris = permute(commoninds(A, Ris), Ris) for (l, r) in zip(Lis, Ris) - if space(l) != space(r) - error("In exp, indices must come in pairs with equal spaces.") + dir_check = hasqns(A) ? dir(l) == dir(dag(r)) : true + if !(dir_check && noprime(l) == noprime(r)) + dir_msg = hasqns(A) ? " and opposite directions" : "" + error("In exp, indices must come in pairs with equal spaces$dir_msg.") end - if hasqns(A) - if dir(l) == dir(r) - error("In exp, indices must come in pairs with opposite directions") - end + end + + # + auto_fermion_enabled = using_auto_fermion() + if auto_fermion_enabled + if all(j->dir(j)==Out, Lis) + ordered_inds = [Lis..., reverse(Ris)...] + elseif all(j->dir(j)==In, Lis) + ordered_inds = [reverse(Ris)..., Lis...] + else + error("For fermionic exp, Linds must have same direction, dir.(Linds)=", dir.(Linds)) end + A = permute(A, ordered_inds) + disable_auto_fermion() end CL = combiner(Lis...; dir=Out) CR = combiner(Ris...; dir=In) AC = (A * CR) * CL expAT = ishermitian ? exp(Hermitian(tensor(AC))) : exp(tensor(AC)) - return (itensor(expAT) * dag(CR)) * dag(CL) + expA = (itensor(expAT) * dag(CR)) * dag(CL) + + # + if auto_fermion_enabled + expA = permute(expA, ordered_inds) + enable_auto_fermion() + end + + return expA end function exp(A::ITensor; kwargs...) diff --git a/test/base/test_fermions.jl b/test/base/test_fermions.jl index c70603a283..ea950500d2 100644 --- a/test/base/test_fermions.jl +++ b/test/base/test_fermions.jl @@ -1,6 +1,6 @@ using ITensors, Test import ITensors: Out, In -using ITensors.SiteTypes: op, siteinds +using ITensors.SiteTypes: op, siteind, siteinds @testset "Fermions" begin ITensors.enable_auto_fermion() @@ -778,5 +778,21 @@ using ITensors.SiteTypes: op, siteinds @test norm(a * u - u' * d) ≈ 0 atol = √(eps(real(eltype(a)))) end + @testset "Fermion exp Tests" begin + s = siteinds("Fermion", 2; conserve_qns=true) + + # Matrix test + id_tensor = op("I", s[1]) + @test id_tensor ≈ exp(0.0 * id_tensor) + + # Tensor test + id_tensor = op("I", s[1]) * op("I", s[2]) + @test id_tensor ≈ exp(0.0 * id_tensor) + + # Permute and test again + id_tensor = permute(id_tensor, s[2], s[1], s[2]', s[1]') + @test id_tensor ≈ exp(0.0 * id_tensor) + end + ITensors.disable_auto_fermion() end From 3363f2600a5107190f5592820b559ad1755e7632 Mon Sep 17 00:00:00 2001 From: Miles Date: Fri, 25 Jul 2025 11:11:34 -0400 Subject: [PATCH 2/4] Code simplification --- src/tensor_operations/matrix_algebra.jl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/tensor_operations/matrix_algebra.jl b/src/tensor_operations/matrix_algebra.jl index 0b8c80f8db..162564dded 100644 --- a/src/tensor_operations/matrix_algebra.jl +++ b/src/tensor_operations/matrix_algebra.jl @@ -50,18 +50,12 @@ function exp(A::ITensor, Linds, Rinds; ishermitian=false) N != NL + NR && error("Number of left and right indices must add up to total number of indices") - # Linds, Rinds may not have the correct directions - # TODO: does the need a conversion? - Lis = Linds - Ris = Rinds + # Permute Lis, Ris to be in same order as on A + Lis = permute(commoninds(A, Linds), Linds) + Ris = permute(commoninds(A, Rinds), Rinds) # Ensure the indices have the correct directions, # QNs, etc. - - # Permute Lis, Ris to be in same order as on A - Lis = permute(commoninds(A, Lis), Lis) - Ris = permute(commoninds(A, Ris), Ris) - for (l, r) in zip(Lis, Ris) dir_check = hasqns(A) ? dir(l) == dir(dag(r)) : true if !(dir_check && noprime(l) == noprime(r)) From f1c0378412039a215bdbb168cac273d96782f321 Mon Sep 17 00:00:00 2001 From: Miles Date: Fri, 25 Jul 2025 11:59:26 -0400 Subject: [PATCH 3/4] Restore previous index testing in exp --- src/tensor_operations/matrix_algebra.jl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/tensor_operations/matrix_algebra.jl b/src/tensor_operations/matrix_algebra.jl index 162564dded..bd856523ca 100644 --- a/src/tensor_operations/matrix_algebra.jl +++ b/src/tensor_operations/matrix_algebra.jl @@ -43,24 +43,23 @@ function exp(A::ITensor, Linds, Rinds; ishermitian=false) end end - N = ndims(A) NL = length(Linds) NR = length(Rinds) NL != NR && error("Must have equal number of left and right indices") - N != NL + NR && + ndims(A) != NL + NR && error("Number of left and right indices must add up to total number of indices") # Permute Lis, Ris to be in same order as on A Lis = permute(commoninds(A, Linds), Linds) Ris = permute(commoninds(A, Rinds), Rinds) - # Ensure the indices have the correct directions, - # QNs, etc. + # Ensure indices have correct directions, QNs, etc. for (l, r) in zip(Lis, Ris) - dir_check = hasqns(A) ? dir(l) == dir(dag(r)) : true - if !(dir_check && noprime(l) == noprime(r)) - dir_msg = hasqns(A) ? " and opposite directions" : "" - error("In exp, indices must come in pairs with equal spaces$dir_msg.") + if space(l) != space(r) + error("In exp, indices must come in pairs with equal spaces.") + end + if hasqns(A) && dir(l) == dir(r) + error("In exp, indices must come in pairs with opposite directions") end end From 09371e782fc6c2db830ddfbf73734d1088aa9ba2 Mon Sep 17 00:00:00 2001 From: Miles Date: Fri, 1 Aug 2025 11:23:55 -0400 Subject: [PATCH 4/4] Update exp code to take input index ordering literally --- src/tensor_operations/matrix_algebra.jl | 36 ++++++++++++++++--------- test/base/test_fermions.jl | 9 +++++++ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/tensor_operations/matrix_algebra.jl b/src/tensor_operations/matrix_algebra.jl index bd856523ca..4099c783c5 100644 --- a/src/tensor_operations/matrix_algebra.jl +++ b/src/tensor_operations/matrix_algebra.jl @@ -49,12 +49,13 @@ function exp(A::ITensor, Linds, Rinds; ishermitian=false) ndims(A) != NL + NR && error("Number of left and right indices must add up to total number of indices") - # Permute Lis, Ris to be in same order as on A - Lis = permute(commoninds(A, Linds), Linds) - Ris = permute(commoninds(A, Rinds), Rinds) + # Replace Linds and Rinds with index sets having + # same id's but arrow directions as on A + Linds = permute(commoninds(A, Linds), Linds) + Rinds = permute(commoninds(A, Rinds), Rinds) # Ensure indices have correct directions, QNs, etc. - for (l, r) in zip(Lis, Ris) + for (l, r) in zip(Linds, Rinds) if space(l) != space(r) error("In exp, indices must come in pairs with equal spaces.") end @@ -66,25 +67,36 @@ function exp(A::ITensor, Linds, Rinds; ishermitian=false) # auto_fermion_enabled = using_auto_fermion() if auto_fermion_enabled - if all(j->dir(j)==Out, Lis) - ordered_inds = [Lis..., reverse(Ris)...] - elseif all(j->dir(j)==In, Lis) - ordered_inds = [reverse(Ris)..., Lis...] + # If fermionic, bring indices into i',j',..,dag(j),dag(i) + # ordering with Out indices coming before In indices + # Resulting tensor acts like a normal matrix (no extra signs + # when taking powers A^n) + if all(j->dir(j)==Out, Linds) && all(j->dir(j)==In, Rinds) + ordered_inds = [Linds..., reverse(Rinds)...] + elseif all(j->dir(j)==Out, Rinds) && all(j->dir(j)==In, Linds) + ordered_inds = [Rinds..., reverse(Linds)...] else - error("For fermionic exp, Linds must have same direction, dir.(Linds)=", dir.(Linds)) + error( + "For fermionic exp, Linds and Rinds must have same directions within each set. Got dir.(Linds)=", + dir.(Linds), + ", dir.(Rinds)=", + dir.(Rinds), + ) end A = permute(A, ordered_inds) + # A^n now sign free, ok to temporarily disable fermion system disable_auto_fermion() end - CL = combiner(Lis...; dir=Out) - CR = combiner(Ris...; dir=In) + CL = combiner(Linds...; dir=Out) + CR = combiner(Rinds...; dir=In) AC = (A * CR) * CL expAT = ishermitian ? exp(Hermitian(tensor(AC))) : exp(tensor(AC)) expA = (itensor(expAT) * dag(CR)) * dag(CL) # if auto_fermion_enabled + # Ensure expA indices in "matrix" form before re-enabling fermion system expA = permute(expA, ordered_inds) enable_auto_fermion() end @@ -94,7 +106,7 @@ end function exp(A::ITensor; kwargs...) Ris = filterinds(A; plev=0) - Lis = Ris' + Lis = dag.(prime.(Ris)) return exp(A, Lis, Ris; kwargs...) end diff --git a/test/base/test_fermions.jl b/test/base/test_fermions.jl index ea950500d2..b68d273663 100644 --- a/test/base/test_fermions.jl +++ b/test/base/test_fermions.jl @@ -792,6 +792,15 @@ using ITensors.SiteTypes: op, siteind, siteinds # Permute and test again id_tensor = permute(id_tensor, s[2], s[1], s[2]', s[1]') @test id_tensor ≈ exp(0.0 * id_tensor) + + # Explicitly passing indices in different, valid orders + @test id_tensor ≈ exp(0.0 * id_tensor, (s[1]', s[2]'), (dag(s[1]), dag(s[2]))) + @test id_tensor ≈ exp(0.0 * id_tensor, (s[2]', s[1]'), (dag(s[2]), dag(s[1]))) + @test id_tensor ≈ exp(0.0 * id_tensor, (dag(s[1]), dag(s[2])), (s[1]', s[2]')) + @test id_tensor ≈ exp(0.0 * id_tensor, (dag(s[2]), dag(s[1])), (s[2]', s[1]')) + + # Check wrong index ordering fails (i.e. we are actually paying attention to it) + @test norm(id_tensor - exp(0.0 * id_tensor, (dag(s[1]), dag(s[2])), (s[2]', s[1]'))) > 1 end ITensors.disable_auto_fermion()