From 88b439d4bf21205f1db457a780a59d7f258391b0 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 2 Oct 2025 10:16:48 +0800 Subject: [PATCH 01/22] Get t-J operators from Hubbard --- src/TensorKitTensors.jl | 5 +- src/hubbardoperators.jl | 8 +- src/tjoperators.jl | 1020 ++++----------------------------------- src/utils.jl | 69 +++ test/runtests.jl | 8 +- 5 files changed, 172 insertions(+), 938 deletions(-) create mode 100644 src/utils.jl diff --git a/src/TensorKitTensors.jl b/src/TensorKitTensors.jl index 8787842..ca674e0 100644 --- a/src/TensorKitTensors.jl +++ b/src/TensorKitTensors.jl @@ -6,10 +6,13 @@ export FermionOperators export TJOperators export HubbardOperators +using TensorKit + +include("utils.jl") include("spinoperators.jl") include("bosonoperators.jl") include("fermionoperators.jl") -include("tjoperators.jl") include("hubbardoperators.jl") +include("tjoperators.jl") end diff --git a/src/hubbardoperators.jl b/src/hubbardoperators.jl index 3954b9d..7722917 100644 --- a/src/hubbardoperators.jl +++ b/src/hubbardoperators.jl @@ -72,11 +72,11 @@ end # Single-site operators # --------------------- function single_site_operator( - T, particle_symmetry::Type{<:Sector}, + elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector} ) V = hubbard_space(particle_symmetry, spin_symmetry) - return zeros(T, V ← V) + return zeros(elt, V ← V) end @doc """ @@ -132,8 +132,8 @@ end const nꜛ = u_num @doc """ - d_num([particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) - nꜜ([particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) + d_num([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) + nꜜ([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) Return the one-body operator that counts the number of spin-down particles. """ d_num diff --git a/src/tjoperators.jl b/src/tjoperators.jl index e5f2482..328fb4a 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -1,8 +1,11 @@ module TJOperators +using LinearAlgebra using TensorKit +import ..HubbardOperators as Hub +import ..TensorKitTensors: _fuse_ids -export tj_space +export tj_space, tj_projector export e_num, u_num, d_num, h_num export S_x, S_y, S_z, S_plus, S_min export u_plus_u_min, d_plus_d_min @@ -82,173 +85,70 @@ function tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false end end -# Single-site operators -# --------------------- -function single_site_operator( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false - ) - V = tj_space(particle_symmetry, spin_symmetry; slave_fermion) - return zeros(elt, V ← V) -end - -@doc """ - u_num(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - nꜛ(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the one-body operator that counts the number of spin-up electrons. -""" u_num -function u_num(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_num(ComplexF64, P, S; slave_fermion) -end -function u_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; - slave_fermion::Bool = false - ) - t = single_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b), dual(I(b)))][1, 1] = 1 - return t -end -function u_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1 // 2), dual(I(b, 1 // 2)))][1, 1] = 1 - return t -end -function u_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_num` is not symmetric under `SU2Irrep` spin symmetry")) -end -function u_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, U1Irrep, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1), dual(I(b, 1)))][1, 1] = 1 - return t -end -function u_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, U1Irrep, U1Irrep; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1, 1 // 2), dual(I(b, 1, 1 // 2)))] .= 1 - return t -end -function u_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_num` is not symmetric under `SU2Irrep` spin symmetry")) -end -const nꜛ = u_num - -@doc """ - d_num(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - nꜜ(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) +""" + slave_fermion_auxiliary_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Return the one-body operator that counts the number of spin-down electrons. -""" d_num -function d_num(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_num(ComplexF64, P, S; slave_fermion) -end -function d_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b), dual(I(b)))][2, 2] = 1 - return t +Return the auxiliary space to add a fermion-Z2 charge +to the t-J space and switch to the slave fermion basis. +""" +function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{Trivial}) + return Vect[FermionParity](1 => 1) end -function d_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, -1 // 2), dual(I(b, -1 // 2)))][1, 1] = 1 - return t +function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{U1Irrep}) + return Vect[FermionParity ⊠ U1Irrep]((1, 0) => 1) end -function d_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_num` is not symmetric under `SU2Irrep` spin symmetry")) +function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{SU2Irrep}) + return Vect[FermionParity ⊠ SU2Irrep]((1, 0) => 1) end -function d_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, U1Irrep, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1), dual(I(b, 1)))][2, 2] = 1 - return t +function slave_fermion_auxiliary_space(::Type{U1Irrep}, ::Type{Trivial}) + return Vect[FermionParity ⊠ U1Irrep]((1, 0) => 1) end -function d_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, U1Irrep, U1Irrep; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1, -1 // 2), dual(I(b, 1, -1 // 2)))] .= 1 - return t +function slave_fermion_auxiliary_space(::Type{U1Irrep}, ::Type{U1Irrep}) + return Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]((1, 0, 0) => 1) end -function d_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_num` is not symmetric under `SU2Irrep` spin symmetry")) +function slave_fermion_auxiliary_space(::Type{U1Irrep}, ::Type{SU2Irrep}) + return Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((1, 0, 0) => 1) end -const nꜜ = d_num -@doc """ - e_num(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - n(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) +""" + tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Return the one-body operator that counts the number of particles. -""" e_num -function e_num(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return e_num(ComplexF64, P, S; slave_fermion) -end -function e_num( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return u_num(elt, particle_symmetry, spin_symmetry; slave_fermion) + - d_num(elt, particle_symmetry, spin_symmetry; slave_fermion) -end -function e_num( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, Trivial, SU2Irrep; slave_fermion) - I = sectortype(t) - if slave_fermion - block(t, I(0, 1 // 2))[1, 1] = 1 - else - block(t, I(1, 1 // 2))[1, 1] = 1 +Projection operator from Hubbard space to t-J space (under usual basis, i.e. `slave_fermion = false`). The scalartype is `Int`. +""" +function tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) + Vhub = Hub.hubbard_space(particle_symmetry, spin_symmetry) + VtJ = tj_space(particle_symmetry, spin_symmetry) + proj = zeros(Int, Vhub → VtJ) + for (f1, f2) in fusiontrees(proj) + proj[f1, f2][diagind(proj[f1, f2])] .= 1 end - return t + return proj end -function e_num( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, U1Irrep, SU2Irrep; slave_fermion) - I = sectortype(t) - if slave_fermion - block(t, I(0, 1, 1 // 2))[1, 1] = 1 - else - block(t, I(1, 1, 1 // 2))[1, 1] = 1 + +# Single-site operators +for opname in ( + :e_num, :u_num, :d_num, + :S_x, :S_y, :S_z, :S_plus, :S_min, + :n, :nꜛ, :nꜜ, + :Sˣ, :Sʸ, :Sᶻ, :S⁺, :S⁻, + ) + @eval begin + function ($opname)(args...; slave_fermion::Bool = false) + psymm, ssymm = args[end - 1], args[end] + if psymm == SU2Irrep + error("t-J model doesn't have SU(2) particle symmetry.") + end + opHub = Hub.$opname(args...) + proj = tj_projector(psymm, ssymm) + optJ = proj * opHub * proj' + if slave_fermion + Vaux = slave_fermion_auxiliary_space(psymm, ssymm) + optJ = _fuse_ids(optJ, (Vaux,)) + end + return optJ + end end - return t end -const n = e_num @doc """ h_num(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) @@ -261,786 +161,48 @@ function h_num(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false end function h_num( elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false + spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false ) - iden = TensorKit.id(tj_space(particle_symmetry, spin_symmetry; slave_fermion)) + iden = TensorKit.id(elt, tj_space(particle_symmetry, spin_symmetry; slave_fermion)) return iden - e_num(elt, particle_symmetry, spin_symmetry; slave_fermion) end const nʰ = h_num -@doc """ - S_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - S⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - -Return the spin-plus operator (only defined for `Trivial` spin symmetry). -""" S_plus -function S_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return S_plus(ComplexF64, P, S; slave_fermion) -end -function S_plus( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; - slave_fermion::Bool = false - ) - t = single_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b), dual(I(b)))][1, 2] = 1.0 - return t -end -function S_plus( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = single_site_operator(elt, U1Irrep, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1), dual(I(b, 1)))][1, 2] = 1.0 - return t -end -function S_plus( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`S_plus`, `S_min` are not symmetric under `U1Irrep` spin symmetry")) -end -function S_plus( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`S_plus`, `S_min` are not symmetric under `SU2Irrep` spin symmetry")) -end -const S⁺ = S_plus - -@doc """ - S_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - S⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - -Return the spin-minus operator (only defined for `Trivial` spin symmetry). -""" S_min -function S_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return S_min(ComplexF64, P, S; slave_fermion) -end -function S_min( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return copy(adjoint(S_plus(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const S⁻ = S_min - -@doc """ - S_x(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - Sˣ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - -Return the one-body spin-1/2 x-operator on the electrons (only defined for `Trivial` spin symmetry). -""" S_x -function S_x( - P::Type{<:Sector} = Trivial, S::Type{<:Sector} = Trivial; slave_fermion::Bool = false - ) - return S_x(ComplexF64, P, S; slave_fermion) -end -function S_x( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return ( - S_plus(elt, particle_symmetry, spin_symmetry; slave_fermion) + - S_min(elt, particle_symmetry, spin_symmetry; slave_fermion) - ) / 2 -end -const Sˣ = S_x - -@doc """ - S_y(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - Sʸ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - -Return the one-body spin-1/2 y-operator on the electrons (only defined for `Trivial` spin symmetry). -""" S_y -function S_y( - P::Type{<:Sector} = Trivial, S::Type{<:Sector} = Trivial; slave_fermion::Bool = false - ) - return S_y(ComplexF64, P, S; slave_fermion) -end -function S_y( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return ( - S_plus(elt, particle_symmetry, spin_symmetry; slave_fermion) - - S_min(elt, particle_symmetry, spin_symmetry; slave_fermion) - ) / (2im) -end -const Sʸ = S_y - -@doc """ - S_z(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - Sᶻ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - -Return the one-body spin-1/2 z-operator on the electrons. -""" S_z -function S_z( - P::Type{<:Sector} = Trivial, S::Type{<:Sector} = Trivial; slave_fermion::Bool = false - ) - return S_z(ComplexF64, P, S; slave_fermion) -end -function S_z( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return ( - u_num(elt, particle_symmetry, spin_symmetry; slave_fermion) - - d_num(elt, particle_symmetry, spin_symmetry; slave_fermion) - ) / 2 -end -const Sᶻ = S_z - -# Two site operators -# ------------------ -function two_site_operator( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false - ) - V = tj_space(particle_symmetry, spin_symmetry; slave_fermion) - return zeros(elt, V ⊗ V ← V ⊗ V) -end - -@doc """ - u_plus_u_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - u⁺u⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e†_{1,↑}, e_{2,↑}`` that creates a spin-up electron at the first site and annihilates a spin-up electron at the second. -The only nonzero matrix element corresponds to `|↑0⟩ <-- |0↑⟩`. -""" u_plus_u_min -function u_plus_u_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_plus_u_min(ComplexF64, P, S; slave_fermion) -end -function u_plus_u_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - #= The extra minus sign in slave-fermion basis: - c†_{1,↑} c_{2,↑} |0↑⟩ - = h_1 b†_{1,↑} h†_2 b_{2,↑} h†_1 b†_{2,↑}|vac⟩ - = -b†_{1,↑} h†_2 h_1 h†_1 b_{2,↑} b†_{2,↑}|vac⟩ - = -b†_{1,↑} h†_2 |vac⟩ - = -|↑0⟩ - =# - t[(I(b), I(h), dual(I(h)), dual(I(b)))][1, 1, 1, 1] = sgn * 1 - return t -end -function u_plus_u_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b, 1 // 2), I(h, 0), dual(I(h, 0)), dual(I(b, 1 // 2)))] .= sgn * 1 - return t -end -function u_plus_u_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_plus_u_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -function u_plus_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b, 1), I(h, 0), dual(I(h, 0)), dual(I(b, 1)))][1, 1, 1, 1] = sgn * 1 - return t -end -function u_plus_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, U1Irrep; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b, 1, 1 // 2), I(h, 0, 0), dual(I(h, 0, 0)), dual(I(b, 1, 1 // 2)))] .= sgn * 1 - return t -end -function u_plus_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_plus_u_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -const u⁺u⁻ = u_plus_u_min - -@doc """ - d_plus_d_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - d⁺d⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e†_{1,↓}, e_{2,↓}`` that creates a spin-down electron at the first site and annihilates a spin-down electron at the second. -The only nonzero matrix element corresponds to `|↓0⟩ <-- |0↓⟩`. -""" d_plus_d_min -function d_plus_d_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_plus_d_min(ComplexF64, P, S; slave_fermion) -end -function d_plus_d_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; - slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b), I(h), dual(I(h)), dual(I(b)))][2, 1, 1, 2] = sgn * 1 - return t -end -function d_plus_d_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt::Type{<:Number}, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b, -1 // 2), I(h, 0), dual(I(h, 0)), dual(I(b, -1 // 2)))] .= sgn * 1 - return t -end -function d_plus_d_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_plus_d_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -function d_plus_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b, 1), I(h, 0), dual(I(h, 0)), dual(I(b, 1)))][2, 1, 1, 2] = sgn * 1 - return t -end -function d_plus_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, U1Irrep; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(b, 1, -1 // 2), I(h, 0, 0), dual(I(h, 0, 0)), dual(I(b, 1, -1 // 2)))] .= sgn * 1 - return t -end -function d_plus_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_plus_d_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -const d⁺d⁻ = d_plus_d_min - -@doc """ - u_min_u_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - u⁻u⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e_{1,↑}, e†_{2,↑}`` that annihilates a spin-up electron at the first site and creates a spin-up electron at the second. -The only nonzero matrix element corresponds to `|0↑⟩ <-- |↑0⟩`. -""" u_min_u_plus -function u_min_u_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_min_u_plus(ComplexF64, P, S; slave_fermion) -end -function u_min_u_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(u_plus_u_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const u⁻u⁺ = u_min_u_plus - -@doc """ - d_min_d_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - d⁻d⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e_{1,↓}, e†_{2,↓}`` that annihilates a spin-down electron at the first site and creates a spin-down electron at the second. -The only nonzero matrix element corresponds to `|0↓⟩ <-- |↓0⟩`. -""" d_min_d_plus -function d_min_d_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_min_d_plus(ComplexF64, P, S; slave_fermion) -end -function d_min_d_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(d_plus_d_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const d⁻d⁺ = d_min_d_plus - -@doc """ - u_min_d_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - u⁻d⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e_{1,↑} e_{2,↓}`` that annihilates a spin-up particle at the first site and a spin-down particle at the second site. -The only nonzero matrix element corresponds to `|0,0⟩ <-- |↑,↓⟩`. -""" u_min_d_min -function u_min_d_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_min_d_min(ComplexF64, P, S; slave_fermion) -end -function u_min_d_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(h), I(h), dual(I(b)), dual(I(b)))][1, 1, 1, 2] = -sgn * 1 - return t -end -function u_min_d_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(h, 0), I(h, 0), dual(I(b, 1 // 2)), dual(I(b, -1 // 2)))] .= -sgn * 1 - return t -end -function u_min_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{<:Sector}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_d_min` is not symmetric under `U1Irrep` particle symmetry")) -end -function u_min_d_min( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_d_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -function u_min_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_d_min` is not symmetric under `U1Irrep` particle symmetry or under `SU2Irrep` spin symmetry")) -end -const u⁻d⁻ = u_min_d_min - -@doc """ - u_plus_d_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - u⁺d⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e†_{1,↑} e†_{2,↓}`` that annihilates a spin-up particle at the first site and a spin-down particle at the second site. -""" u_plus_d_plus -function u_plus_d_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_plus_d_plus(ComplexF64, P, S; slave_fermion) -end -function u_plus_d_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(u_min_d_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const u⁺d⁺ = u_plus_d_plus - -@doc """ - d_min_u_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - d⁻u⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e_{1,↓} e_{2,↑}`` that annihilates a spin-down particle at the first site and a spin-up particle at the second site. -The only nonzero matrix element corresponds to `|0,0⟩ <-- |↓,↑⟩`. -""" d_min_u_min -function d_min_u_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_min_u_min(ComplexF64, P, S; slave_fermion) -end -function d_min_u_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(h), I(h), dual(I(b)), dual(I(b)))][1, 1, 2, 1] = -sgn * 1 - return t -end -function d_min_u_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(h, 0), I(h, 0), dual(I(b, -1 // 2)), dual(I(b, 1 // 2)))] .= -sgn * 1 - return t -end -function d_min_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{<:Sector}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_u_min` is not symmetric under `U1Irrep` particle symmetry")) -end -function d_min_u_min( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_u_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -function d_min_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_u_min` is not symmetric under `U1Irrep` particle symmetry or under `SU2Irrep` particle symmetry")) -end -const d⁻u⁻ = d_min_u_min - -@doc """ - d_plus_u_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - d⁺u⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e†_{1,↓} e†_{2,↑}`` that annihilates a spin-down particle at the first site and a spin-up particle at the second site. -""" d_plus_u_plus -function d_plus_u_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_plus_u_plus(ComplexF64, P, S; slave_fermion) -end -function d_plus_u_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(d_min_u_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const d⁺u⁺ = d_plus_u_plus - -@doc """ - u_min_u_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - u⁻u⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e_{1,↑} e_{2,↑}`` that annihilates a spin-up particle at both sites. -The only nonzero matrix element corresponds to `|0,0⟩ <-- |↑,↑⟩`. -""" u_min_u_min -function u_min_u_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_min_u_min(ComplexF64, P, S; slave_fermion) -end -function u_min_u_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(h), I(h), dual(I(b)), dual(I(b)))][1, 1, 1, 1] = -sgn * 1 - return t -end -function u_min_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{<:Sector}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_u_min` is not symmetric under `U1Irrep` particle symmetry")) -end -function u_min_u_min( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_u_min` is not symmetric under `U1Irrep` spin symmetry")) -end -function u_min_u_min( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_u_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -function u_min_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_u_min` is not symmetric under `U1Irrep` particle symmetry or under `U1Irrep` particle symmetry")) -end -function u_min_u_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`u_min_u_min` is not symmetric under `U1Irrep` particle symmetry or under `SU2Irrep` particle symmetry")) -end -const u⁻u⁻ = u_min_u_min - -@doc """ - u_plus_u_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - u⁺u⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e†_{1,↑} e†_{2,↑}`` that annihilates a spin-up particle at both sites. -""" u_plus_u_plus -function u_plus_u_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return u_plus_u_plus(ComplexF64, P, S; slave_fermion) -end -function u_plus_u_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(u_min_u_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const u⁺u⁺ = u_plus_u_plus - -@doc """ - d_min_d_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - d⁻d⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e_{1,↓} e_{2,↓}`` that annihilates a spin-down particle at both sites. -The only nonzero matrix element corresponds to `|0,0⟩ <-- |↓,↓⟩`. -""" d_min_d_min -function d_min_d_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_min_d_min(ComplexF64, P, S; slave_fermion) -end -function d_min_d_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - (h, b, sgn) = slave_fermion ? (1, 0, -1) : (0, 1, 1) - t[(I(h), I(h), dual(I(b)), dual(I(b)))][1, 1, 2, 2] = -sgn * 1 - return t -end -function d_min_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{<:Sector}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_d_min` is not symmetric under `U1Irrep` particle symmetry")) -end -function d_min_d_min( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_d_min` is not symmetric under `U1Irrep` spin symmetry")) -end -function d_min_d_min( - elt::Type{<:Number}, ::Type{<:Sector}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_d_min` is not symmetric under `SU2Irrep` spin symmetry")) -end -function d_min_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_d_min` is not symmetric under `U1Irrep` particle symmetry or under `U1Irrep` particle symmetry")) -end -function d_min_d_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - throw(ArgumentError("`d_min_d_min` is not symmetric under `U1Irrep` particle symmetry or under `SU2Irrep` particle symmetry")) -end -const d⁻d⁻ = d_min_d_min - -@doc """ - d_plus_d_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - d⁺d⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator ``e†_{1,↓} e†_{2,↓}`` that annihilates a spin-down particle at both sites. -""" d_plus_d_plus -function d_plus_d_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return d_plus_d_plus(ComplexF64, P, S; slave_fermion) -end -function d_plus_d_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(d_min_d_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const d⁺d⁺ = d_plus_d_plus - -@doc """ - e_plus_e_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - e⁺e⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator that creates a particle at the first site and annihilates a particle at the second. -This is the sum of `u_plus_u_min` and `d_plus_d_min`. -""" e_plus_e_min -function e_plus_e_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return e_plus_e_min(ComplexF64, P, S; slave_fermion) -end -function e_plus_e_min( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return u_plus_u_min(elt, particle_symmetry, spin_symmetry; slave_fermion) + - d_plus_d_min(elt, particle_symmetry, spin_symmetry; slave_fermion) -end -function e_plus_e_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; - slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, SU2Irrep; slave_fermion) - I = sectortype(t) - (h, b) = slave_fermion ? (1, 0) : (0, 1) - f1 = only(fusiontrees((I(h, 0), I(b, 1 // 2)), I(1, 1 // 2))) - f2 = only(fusiontrees((I(b, 1 // 2), I(h, 0)), I(1, 1 // 2))) - t[f1, f2][1, 1, 1, 1] = 1 - return t -end -function e_plus_e_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, SU2Irrep; slave_fermion) - I = sectortype(t) - (h, b) = slave_fermion ? (1, 0) : (0, 1) - f1 = only(fusiontrees((I(h, 0, 0), I(b, 1, 1 // 2)), I(1, 1, 1 // 2))) - f2 = only(fusiontrees((I(b, 1, 1 // 2), I(h, 0, 0)), I(1, 1, 1 // 2))) - t[f1, f2][1, 1, 1, 1] = 1 - return t -end - -const e⁺e⁻ = e_plus_e_min - -@doc """ - e_min_e_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - e⁻e⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator that annihilates a particle at the first site and creates a particle at the second. -This is the sum of `u_min_u_plus` and `d_min_d_plus`. -""" e_min_e_plus -function e_min_e_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return e_min_e_plus(ComplexF64, P, S; slave_fermion) -end -function e_min_e_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return -copy(adjoint(e_plus_e_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const e⁻e⁺ = e_min_e_plus - -@doc """ - singlet_plus(elt, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - singlet⁺(elt, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body singlet operator ``(e^†_{1,↑} e^†_{2,↓} - e^†_{1,↓} e^†_{2,↑}) / sqrt(2)``, -which creates the singlet state when acting on vaccum. -""" singlet_plus -function singlet_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return singlet_plus(ComplexF64, P, S; slave_fermion) -end -function singlet_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false - ) - return ( - u_plus_d_plus(elt, particle_symmetry, spin_symmetry; slave_fermion) - - d_plus_u_plus(elt, particle_symmetry, spin_symmetry; slave_fermion) - ) / sqrt(2) -end -const singlet⁺ = singlet_plus - -@doc """ - singlet_min(elt, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - singlet⁻(elt, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the adjoint of `singlet_plus` operator, which is -``(-e_{1,↑} e_{2,↓} + e_{1,↓} e_{2,↑}) / sqrt(2)`` -""" singlet_min -function singlet_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return singlet_min(ComplexF64, P, S; slave_fermion) -end -function singlet_min( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false - ) - return copy(adjoint(singlet_plus(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const singlet⁻ = singlet_min - -@doc """ - e_hopping([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false) - e_hop([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false) - -Return the two-body operator that describes a particle that hops between the first and the second site. -""" e_hopping -function e_hopping(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return e_hopping(ComplexF64, P, S; slave_fermion) -end -function e_hopping( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return e_plus_e_min(elt, particle_symmetry, spin_symmetry; slave_fermion) - - e_min_e_plus(elt, particle_symmetry, spin_symmetry; slave_fermion) -end -const e_hop = e_hopping - -@doc """ - S_plus_S_min(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - S⁺S⁻(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator S⁺S⁻. -The only nonzero matrix element corresponds to `|↑,↓⟩ <-- |↓,↑⟩`. -""" S_plus_S_min -function S_plus_S_min(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return S_plus_S_min(ComplexF64, P, S; slave_fermion) -end -function S_plus_S_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b), I(b), dual(I(b)), dual(I(b)))][1, 2, 2, 1] = 1 - return t -end -function S_plus_S_min( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, U1Irrep; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1 // 2), I(b, -1 // 2), dual(I(b, -1 // 2)), dual(I(b, 1 // 2)))] .= 1 - return t -end -function S_plus_S_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, Trivial; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1), I(b, 1), dual(I(b, 1)), dual(I(b, 1)))][1, 2, 2, 1] = 1 - return t -end -function S_plus_S_min( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, U1Irrep; slave_fermion) - I = sectortype(t) - b = slave_fermion ? 0 : 1 - t[(I(b, 1, 1 // 2), I(b, 1, -1 // 2), dual(I(b, 1, -1 // 2)), dual(I(b, 1, 1 // 2)))] .= 1 - return t -end -const S⁺S⁻ = S_plus_S_min - -@doc """ - S_min_S_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - S⁻S⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the two-body operator S⁻S⁺. -The only nonzero matrix element corresponds to `|↓,↑⟩ <-- |↑,↓⟩`. -""" S_min_S_plus -function S_min_S_plus(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return S_min_S_plus(ComplexF64, P, S; slave_fermion) -end -function S_min_S_plus( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - return copy(adjoint(S_plus_S_min(elt, particle_symmetry, spin_symmetry; slave_fermion))) -end -const S⁻S⁺ = S_min_S_plus - -@doc """ - S_exchange(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) - -Return the spin exchange operator S⋅S. -""" S_exchange -function S_exchange(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return S_exchange(ComplexF64, P, S; slave_fermion) -end -function S_exchange( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false - ) - Sz = S_z(elt, particle_symmetry, spin_symmetry; slave_fermion) - return (1 / 2) * ( - S_plus_S_min(elt, particle_symmetry, spin_symmetry; slave_fermion) + - S_min_S_plus(elt, particle_symmetry, spin_symmetry; slave_fermion) - ) + Sz ⊗ Sz -end -function S_exchange( - elt::Type{<:Number}, ::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, Trivial, SU2Irrep; slave_fermion) - for (s, f) in fusiontrees(t) - l3 = f.uncoupled[1][2].j - l4 = f.uncoupled[2][2].j - k = f.coupled[2].j - t[s, f] .= (k * (k + 1) - l3 * (l3 + 1) - l4 * (l4 + 1)) / 2 - end - return t -end -function S_exchange( - elt::Type{<:Number}, ::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false - ) - t = two_site_operator(elt, U1Irrep, SU2Irrep; slave_fermion) - for (s, f) in fusiontrees(t) - l3 = f.uncoupled[1][3].j - l4 = f.uncoupled[2][3].j - k = f.coupled[3].j - t[s, f] .= (k * (k + 1) - l3 * (l3 + 1) - l4 * (l4 + 1)) / 2 +# Two-site operators +for opname in ( + :u_plus_u_min, :d_plus_d_min, + :u_min_u_plus, :d_min_d_plus, + :u_min_d_min, :d_min_u_min, + :u_plus_d_plus, :d_plus_u_plus, + :u_min_u_min, :d_min_d_min, + :u_plus_u_plus, :d_plus_d_plus, + :e_plus_e_min, :e_min_e_plus, :e_hopping, + :singlet_plus, :singlet_min, + :S_plus_S_min, :S_min_S_plus, :S_exchange, + :u⁺u⁻, :d⁺d⁻, :u⁻u⁺, :d⁻d⁺, + :u⁻d⁻, :d⁻u⁻, :u⁺d⁺, :d⁺u⁺, + :u⁻u⁻, :u⁺u⁺, :d⁻d⁻, :d⁺d⁺, + :e⁺e⁻, :e⁻e⁺, :e_hop, + :singlet⁺, :singlet⁻, + :S⁻S⁺, :S⁺S⁻, + ) + @eval begin + function ($opname)(args...; slave_fermion::Bool = false) + psymm, ssymm = args[end - 1], args[end] + if psymm == SU2Irrep + error("t-J model doesn't have SU(2) particle symmetry.") + end + opHub = Hub.$opname(args...) + proj = tj_projector(psymm, ssymm) + proj = proj ⊗ proj + optJ = proj * opHub * proj' + if slave_fermion + Vaux = slave_fermion_auxiliary_space(psymm, ssymm) + optJ = _fuse_ids(optJ, (Vaux, Vaux)) + end + return optJ + end end - return t end end diff --git a/src/utils.jl b/src/utils.jl new file mode 100644 index 0000000..69cf671 --- /dev/null +++ b/src/utils.jl @@ -0,0 +1,69 @@ +# ---- copied from MPSKit, PEPSKit ---- + +_totuple(t) = t isa Tuple ? t : Tuple(t) + +""" + tensorexpr(name, ind_out, [ind_in]) + +Generates expressions for use within `@tensor` environments +of the form `name[ind_out...; ind_in]`. +""" +tensorexpr(name, inds) = Expr(:ref, name, _totuple(inds)...) +function tensorexpr(name, indout, indin) + return Expr( + :typed_vcat, name, Expr(:row, _totuple(indout)...), Expr(:row, _totuple(indin)...) + ) +end + +""" + twistdual(t::AbstractTensorMap, i) + twistdual!(t::AbstractTensorMap, i) + +Twist the i-th leg of a tensor `t` if it represents a dual space. +""" +function twistdual!(t::AbstractTensorMap, i::Int) + isdual(space(t, i)) || return t + return twist!(t, i) +end +function twistdual!(t::AbstractTensorMap, is) + is′ = filter(i -> isdual(space(t, i)), is) + return twist!(t, is′) +end +twistdual(t::AbstractTensorMap, is) = twistdual!(copy(t), is) + + +@generated function _fuse_isomorphisms( + op::AbstractTensorMap{<:Any, S, N, N}, fs::Vector{<:AbstractTensorMap{<:Any, S, 1, 2}} + ) where {S, N} + op_out_e = tensorexpr(:op_out, -(1:N), -((1:N) .+ N)) + op_e = tensorexpr(:op, 1:3:(3 * N), 2:3:(3 * N)) + f_es = map(1:N) do i + j = 3 * (i - 1) + 1 + return tensorexpr(:(fs[$i]), -i, (j, j + 2)) + end + f_dag_es = map(1:N) do i + j = 3 * (i - 1) + 1 + return tensorexpr(:(twistdual(fs[$i]', 1:2)), (j + 1, j + 2), -(N + i)) + end + multiplication_ex = Expr( + :call, :*, op_e, f_es..., f_dag_es... + ) + return macroexpand(@__MODULE__, :(return @tensor $op_out_e := $multiplication_ex)) +end + +""" + _fuse_ids(op::AbstractTensorMap{T, S, N, N}, [Ps::NTuple{N, S}]) where {T, S, N} + +Fuse identities on auxiliary physical spaces `Ps` into a given operator `op`. +When `Ps` is not specified, it defaults to the domain spaces of `op`. +""" +function _fuse_ids(op::AbstractTensorMap{T, S, N, N}, Ps::NTuple{N, S}) where {T, S, N} + # make isomorphisms + fs = map(1:N) do i + return isomorphism(fuse(space(op, i), Ps[i]), space(op, i) ⊗ Ps[i]) + end + # and fuse them into the operator + return _fuse_isomorphisms(op, fs) +end + +# ---- diff --git a/test/runtests.jl b/test/runtests.jl index 0c1c02d..6dc7d9d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,14 +12,14 @@ end include("fermionoperators.jl") end -@time @safetestset "tj operators" begin - include("tjoperators.jl") -end - @time @safetestset "Hubbard operators" begin include("hubbardoperators.jl") end +@time @safetestset "tj operators" begin + include("tjoperators.jl") +end + @time @safetestset "Aqua" begin include("aqua.jl") end From 5419fa44989fe4127886bb2447d5b51e50df5308 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 2 Oct 2025 10:21:34 +0800 Subject: [PATCH 02/22] Update FormatCheck workflow configuration --- .github/workflows/FormatCheck.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 40aaca1..383c502 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -1,17 +1,15 @@ -name: FormatCheck +name: 'Format' on: - push: - branches: - - 'main' - - 'master' - tags: '*' - pull_request: - branches: - - 'main' - - 'master' + pull_request_target: + paths: ['**/*.jl'] + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + actions: write + pull-requests: write jobs: formatcheck: - name: "Format Check" uses: "QuantumKitHub/QuantumKitHubActions/.github/workflows/FormatCheck.yml@main" From e32f4f6154d591241ce6328f92bddd975da0bf7e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 2 Oct 2025 10:33:32 +0800 Subject: [PATCH 03/22] Cover tJ.e_hopping in tests --- test/tjoperators.jl | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/test/tjoperators.jl b/test/tjoperators.jl index 20dfab4..f457657 100644 --- a/test/tjoperators.jl +++ b/test/tjoperators.jl @@ -255,10 +255,7 @@ end function tjhamiltonian(particle_symmetry, spin_symmetry; t, J, mu, L, slave_fermion) num = e_num(particle_symmetry, spin_symmetry; slave_fermion) - hop_heis = (-t) * ( - e_plus_e_min(particle_symmetry, spin_symmetry; slave_fermion) - - e_min_e_plus(particle_symmetry, spin_symmetry; slave_fermion) - ) + J * (S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - (1 / 4) * (num ⊗ num)) + hop_heis = (-t) * e_hopping(particle_symmetry, spin_symmetry; slave_fermion) + J * (S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - (1 / 4) * (num ⊗ num)) chemical_potential = (-mu) * num I = id(tj_space(particle_symmetry, spin_symmetry; slave_fermion)) H = sum(1:(L - 1)) do i @@ -316,13 +313,8 @@ end if (particle_symmetry, spin_symmetry) in implemented_symmetries t, J = rand(rng, 2) num = e_num(particle_symmetry, spin_symmetry; slave_fermion) - H = (-t) * - ( - e_plus_e_min(particle_symmetry, spin_symmetry; slave_fermion) - - e_min_e_plus(particle_symmetry, spin_symmetry; slave_fermion) - ) + - J * - ( + H = (-t) * e_hopping(particle_symmetry, spin_symmetry; slave_fermion) + + J * ( S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - (1 / 4) * (num ⊗ num) ) From 4013e9d1ffa6d526a546da75f614fa9a60097445 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 2 Oct 2025 17:23:35 +0800 Subject: [PATCH 04/22] Improve error handling --- src/tjoperators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 328fb4a..c4284aa 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -136,7 +136,7 @@ for opname in ( function ($opname)(args...; slave_fermion::Bool = false) psymm, ssymm = args[end - 1], args[end] if psymm == SU2Irrep - error("t-J model doesn't have SU(2) particle symmetry.") + throw(ArgumentError("t-J model does not have SU(2) particle symmetry.")) end opHub = Hub.$opname(args...) proj = tj_projector(psymm, ssymm) @@ -190,7 +190,7 @@ for opname in ( function ($opname)(args...; slave_fermion::Bool = false) psymm, ssymm = args[end - 1], args[end] if psymm == SU2Irrep - error("t-J model doesn't have SU(2) particle symmetry.") + throw(ArgumentError("t-J model does not have SU(2) particle symmetry.")) end opHub = Hub.$opname(args...) proj = tj_projector(psymm, ssymm) From 994041ecd0967a9e63b7955ff7f92db29e40f902 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 09:03:50 -0400 Subject: [PATCH 05/22] slight reorganization to deduplicate and add back docstrings --- src/tjoperators.jl | 107 ++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index c4284aa..907c4dc 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -2,7 +2,7 @@ module TJOperators using LinearAlgebra using TensorKit -import ..HubbardOperators as Hub +using ..HubbardOperators: HubbardOperators, hubbard_space import ..TensorKitTensors: _fuse_ids export tj_space, tj_projector @@ -116,37 +116,63 @@ end Projection operator from Hubbard space to t-J space (under usual basis, i.e. `slave_fermion = false`). The scalartype is `Int`. """ function tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) - Vhub = Hub.hubbard_space(particle_symmetry, spin_symmetry) + Vhub = hubbard_space(particle_symmetry, spin_symmetry) VtJ = tj_space(particle_symmetry, spin_symmetry) proj = zeros(Int, Vhub → VtJ) + for (f1, f2) in fusiontrees(proj) proj[f1, f2][diagind(proj[f1, f2])] .= 1 end return proj end -# Single-site operators -for opname in ( - :e_num, :u_num, :d_num, - :S_x, :S_y, :S_z, :S_plus, :S_min, - :n, :nꜛ, :nꜜ, - :Sˣ, :Sʸ, :Sᶻ, :S⁺, :S⁻, +for (opname, alias) in zip( + ( + :e_num, :u_num, :d_num, + :S_x, :S_y, :S_z, :S_plus, :S_min, + :u_plus_u_min, :d_plus_d_min, :u_min_u_plus, :d_min_d_plus, + :u_min_d_min, :d_min_u_min, :u_plus_d_plus, :d_plus_u_plus, + :u_min_u_min, :d_min_d_min, :u_plus_u_plus, :d_plus_d_plus, + :e_plus_e_min, :e_min_e_plus, :e_hopping, + :singlet_plus, :singlet_min, + :S_plus_S_min, :S_min_S_plus, :S_exchange, + ), ( + :n, :nꜛ, :nꜜ, + :Sˣ, :Sʸ, :Sᶻ, :S⁺, :S⁻, + :u⁺u⁻, :d⁺d⁻, :u⁻u⁺, :d⁻d⁺, + :u⁻d⁻, :d⁻u⁻, :u⁺d⁺, :d⁺u⁺, + :u⁻u⁻, :u⁺u⁺, :d⁻d⁻, :d⁺d⁺, + :e⁺e⁻, :e⁻e⁺, :e_hop, + :singlet⁺, :singlet⁻, + :S⁻S⁺, :S⁺S⁻, nothing, + ) ) + # copy over the docstrings @eval begin - function ($opname)(args...; slave_fermion::Bool = false) - psymm, ssymm = args[end - 1], args[end] - if psymm == SU2Irrep - throw(ArgumentError("t-J model does not have SU(2) particle symmetry.")) - end - opHub = Hub.$opname(args...) - proj = tj_projector(psymm, ssymm) - optJ = proj * opHub * proj' - if slave_fermion - Vaux = slave_fermion_auxiliary_space(psymm, ssymm) - optJ = _fuse_ids(optJ, (Vaux,)) - end - return optJ - end + @doc (@doc HubbardOperators.$opname) $opname + end + + # apply projector on Hubbard operator + @eval function $opname( + elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; + slave_fermion::Bool = false + ) + particle_symmetry == SU2Irrep && + throw(ArgumentError("t-J model does not have ``SU(2)`` particle symmetry.")) + op_H = HubbardOperators.$opname(elt, particle_symmetry, spin_symmetry) + proj = tj_projector(particle_symmetry, spin_symmetry) + N = numin(op_H) + (N > 1) && (proj = reduce(⊗, ntuple(Returns(proj), N))) + op_tJ = proj * op_H * proj' + slave_fermion || return op_tJ + + Vaux = slave_fermion_auxiliary_space(particle_symmetry, spin_symmetry) + return _fuse_ids(op_tJ, ntuple(Returns(Vaux), N)) + end + + # define alias + isnothing(alias) || @eval begin + const $alias = $opname end end @@ -168,41 +194,4 @@ function h_num( end const nʰ = h_num -# Two-site operators -for opname in ( - :u_plus_u_min, :d_plus_d_min, - :u_min_u_plus, :d_min_d_plus, - :u_min_d_min, :d_min_u_min, - :u_plus_d_plus, :d_plus_u_plus, - :u_min_u_min, :d_min_d_min, - :u_plus_u_plus, :d_plus_d_plus, - :e_plus_e_min, :e_min_e_plus, :e_hopping, - :singlet_plus, :singlet_min, - :S_plus_S_min, :S_min_S_plus, :S_exchange, - :u⁺u⁻, :d⁺d⁻, :u⁻u⁺, :d⁻d⁺, - :u⁻d⁻, :d⁻u⁻, :u⁺d⁺, :d⁺u⁺, - :u⁻u⁻, :u⁺u⁺, :d⁻d⁻, :d⁺d⁺, - :e⁺e⁻, :e⁻e⁺, :e_hop, - :singlet⁺, :singlet⁻, - :S⁻S⁺, :S⁺S⁻, - ) - @eval begin - function ($opname)(args...; slave_fermion::Bool = false) - psymm, ssymm = args[end - 1], args[end] - if psymm == SU2Irrep - throw(ArgumentError("t-J model does not have SU(2) particle symmetry.")) - end - opHub = Hub.$opname(args...) - proj = tj_projector(psymm, ssymm) - proj = proj ⊗ proj - optJ = proj * opHub * proj' - if slave_fermion - Vaux = slave_fermion_auxiliary_space(psymm, ssymm) - optJ = _fuse_ids(optJ, (Vaux, Vaux)) - end - return optJ - end - end -end - end From a67f70eae50a6d19889eace7d93ca8e5c53a34d9 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 09:20:39 -0400 Subject: [PATCH 06/22] add `h_num` to Hubbard, remove from TJ --- src/hubbardoperators.jl | 15 +++++++++++++-- src/tjoperators.jl | 22 ++-------------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/hubbardoperators.jl b/src/hubbardoperators.jl index 7722917..5b42519 100644 --- a/src/hubbardoperators.jl +++ b/src/hubbardoperators.jl @@ -3,7 +3,7 @@ module HubbardOperators using TensorKit export hubbard_space -export e_num, u_num, d_num, ud_num, half_ud_num +export e_num, u_num, d_num, ud_num, half_ud_num, h_num export S_x, S_y, S_z, S_plus, S_min export u_plus_u_min, d_plus_d_min export u_min_u_plus, d_min_d_plus @@ -15,7 +15,7 @@ export e_plus_e_min, e_min_e_plus, e_hopping export singlet_plus, singlet_min export S_plus_S_min, S_min_S_plus, S_exchange -export n, nꜛ, nꜜ, nꜛꜜ +export n, nꜛ, nꜜ, nꜛꜜ, nʰ export Sˣ, Sʸ, Sᶻ, S⁺, S⁻ export u⁺u⁻, d⁺d⁻, u⁻u⁺, d⁻d⁺ export u⁻d⁻, d⁻u⁻, u⁺d⁺, d⁺u⁺ @@ -262,6 +262,17 @@ function half_ud_num(elt::Type{<:Number}, ::Type{SU2Irrep}, ::Type{SU2Irrep}) return t end +@doc """ + h_num([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) + nʰ([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) + +Return the one-body operator that counts the number of holes, i.e. the number of non-occupied sites. +""" h_num +h_num(P::Type{<:Sector}, S::Type{<:Sector}) = h_num(ComplexF64, P, S) +h_num(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) = + id(elt, hubbard_space(particle_symmetry, spin_symmetry)) - e_num(elt, particle_symmetry, spin_symmetry) +const nʰ = h_num + @doc """ S_plus(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) S⁺(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 907c4dc..cd7317a 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -128,7 +128,7 @@ end for (opname, alias) in zip( ( - :e_num, :u_num, :d_num, + :e_num, :u_num, :d_num, :h_num, :S_x, :S_y, :S_z, :S_plus, :S_min, :u_plus_u_min, :d_plus_d_min, :u_min_u_plus, :d_min_d_plus, :u_min_d_min, :d_min_u_min, :u_plus_d_plus, :d_plus_u_plus, @@ -137,7 +137,7 @@ for (opname, alias) in zip( :singlet_plus, :singlet_min, :S_plus_S_min, :S_min_S_plus, :S_exchange, ), ( - :n, :nꜛ, :nꜜ, + :n, :nꜛ, :nꜜ, :nʰ, :Sˣ, :Sʸ, :Sᶻ, :S⁺, :S⁻, :u⁺u⁻, :d⁺d⁻, :u⁻u⁺, :d⁻d⁺, :u⁻d⁻, :d⁻u⁻, :u⁺d⁺, :d⁺u⁺, @@ -176,22 +176,4 @@ for (opname, alias) in zip( end end -@doc """ - h_num(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - nʰ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false) - -Return the one-body operator that counts the number of holes. -""" h_num -function h_num(P::Type{<:Sector}, S::Type{<:Sector}; slave_fermion::Bool = false) - return h_num(ComplexF64, P, S; slave_fermion) -end -function h_num( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, - spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false - ) - iden = TensorKit.id(elt, tj_space(particle_symmetry, spin_symmetry; slave_fermion)) - return iden - e_num(elt, particle_symmetry, spin_symmetry; slave_fermion) -end -const nʰ = h_num - end From 2371cb302d8521981f42e3da6d7a9f693f7dd331 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 09:32:09 -0400 Subject: [PATCH 07/22] some more reorganization --- src/tjoperators.jl | 73 ++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index cd7317a..dd00cc9 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -27,7 +27,7 @@ export e⁺e⁻, e⁻e⁺, e_hop export singlet⁺, singlet⁻ export S⁻S⁺, S⁺S⁻ -""" +@doc """ tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) Return the local hilbert space for a t-J-type model with the given particle and spin symmetries. @@ -41,56 +41,32 @@ Setting `slave_fermion = true` switches to the slave-fermion basis. |0⟩ = |vac⟩ (vacuum), |↑⟩ = (c↑)†|vac⟩, |↓⟩ = (c↓)†|vac⟩ - basis states for `slave_fermion = true`: (c_σ = h† b_σ; holon h is fermionic, spinon b_σ is bosonic): |0⟩ = h†|vac⟩, |↑⟩ = (b↑)†|vac⟩, |↓⟩ = (b↓)†|vac⟩ -""" -function tj_space( - ::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial; slave_fermion::Bool = false - ) - return slave_fermion ? Vect[FermionParity](0 => 2, 1 => 1) : - Vect[FermionParity](0 => 1, 1 => 2) -end -function tj_space(::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false) - return if slave_fermion - Vect[FermionParity ⊠ U1Irrep]((1, 0) => 1, (0, 1 // 2) => 1, (0, -1 // 2) => 1) - else - Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1 // 2) => 1, (1, -1 // 2) => 1) - end -end -function tj_space(::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false) - return slave_fermion ? Vect[FermionParity ⊠ SU2Irrep]((1, 0) => 1, (0, 1 // 2) => 1) : - Vect[FermionParity ⊠ SU2Irrep]((0, 0) => 1, (1, 1 // 2) => 1) -end -function tj_space(::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false) - return if slave_fermion - Vect[FermionParity ⊠ U1Irrep]((1, 0) => 1, (0, 1) => 2) - else - Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2) - end -end -function tj_space(::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false) - return if slave_fermion - Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]( - (1, 0, 0) => 1, (0, 1, 1 // 2) => 1, (0, 1, -1 // 2) => 1 - ) - else - Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]( - (0, 0, 0) => 1, (1, 1, 1 // 2) => 1, (1, 1, -1 // 2) => 1 - ) - end -end -function tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false) - return if slave_fermion - Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((1, 0, 0) => 1, (0, 1, 1 // 2) => 1) - else - Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1) - end -end - -""" +""" tj_space +tj_space(::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial; slave_fermion::Bool = false) = + Vect[FermionParity](0 ⊻ slave_fermion => 1, 1 ⊻ slave_fermion => 2) +tj_space(::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false) = + Vect[FermionParity ⊠ U1Irrep]( + (0 ⊻ slave_fermion, 0) => 1, (1 ⊻ slave_fermion, 1 // 2) => 1, (1 ⊻ slave_fermion, -1 // 2) => 1 +) +tj_space(::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false) = + Vect[FermionParity ⊠ SU2Irrep]((0 ⊻ slave_fermion, 0) => 1, (1 ⊻ slave_fermion, 1 // 2) => 1) +tj_space(::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false) = + Vect[FermionParity ⊠ U1Irrep]((0 ⊻ slave_fermion, 0) => 1, (1 ⊻ slave_fermion, 1) => 2) +tj_space(::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false) = + Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]( + (0 ⊻ slave_fermion, 0, 0) => 1, (1 ⊻ slave_fermion, 1, 1 // 2) => 1, (1 ⊻ slave_fermion, 1, -1 // 2) => 1 +) +tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false) = + Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]( + (0 ⊻ slave_fermion, 0, 0) => 1, (1 ⊻ slave_fermion, 1, 1 // 2) => 1 +) + +@doc """ slave_fermion_auxiliary_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) Return the auxiliary space to add a fermion-Z2 charge to the t-J space and switch to the slave fermion basis. -""" +""" slave_fermion_auxiliary_space function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{Trivial}) return Vect[FermionParity](1 => 1) end @@ -113,7 +89,8 @@ end """ tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Projection operator from Hubbard space to t-J space (under usual basis, i.e. `slave_fermion = false`). The scalartype is `Int`. +Projection operator from Hubbard space to t-J space (under usual basis, i.e. `slave_fermion = false`). +The scalartype is `Int` to avoid floating point errors. """ function tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) Vhub = hubbard_space(particle_symmetry, spin_symmetry) From 2098f2bfe330dd1973fc0a59fa84566dc98908b8 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 10:49:48 -0400 Subject: [PATCH 08/22] rework slave_fermion --- src/tjoperators.jl | 114 +++++++++++++++++++++------------------------ src/utils.jl | 83 +++++++++------------------------ 2 files changed, 73 insertions(+), 124 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index dd00cc9..833a5ec 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -3,7 +3,7 @@ module TJOperators using LinearAlgebra using TensorKit using ..HubbardOperators: HubbardOperators, hubbard_space -import ..TensorKitTensors: _fuse_ids +import ..TensorKitTensors: fuse_local_operators export tj_space, tj_projector export e_num, u_num, d_num, h_num @@ -27,69 +27,35 @@ export e⁺e⁻, e⁻e⁺, e_hop export singlet⁺, singlet⁻ export S⁻S⁺, S⁺S⁻ +export transform_slave_fermion + @doc """ - tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) + tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) Return the local hilbert space for a t-J-type model with the given particle and spin symmetries. -The possible symmetries are -- Particle number: `Trivial`, `U1Irrep`; -- Spin: `Trivial`, `U1Irrep`, `SU2Irrep`. - -Setting `slave_fermion = true` switches to the slave-fermion basis. +The basis consists of ``|0⟩``, ``|↑⟩ = u⁺|0⟩`` and ``|↓⟩ = d⁺|0⟩``. -- basis states for `slave_fermion = false`: - |0⟩ = |vac⟩ (vacuum), |↑⟩ = (c↑)†|vac⟩, |↓⟩ = (c↓)†|vac⟩ -- basis states for `slave_fermion = true`: (c_σ = h† b_σ; holon h is fermionic, spinon b_σ is bosonic): - |0⟩ = h†|vac⟩, |↑⟩ = (b↑)†|vac⟩, |↓⟩ = (b↓)†|vac⟩ +The possible symmetries are: +- Particle number : `Trivial`, `U1Irrep` +- Spin : `Trivial`, `U1Irrep`, `SU2Irrep`. """ tj_space -tj_space(::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial; slave_fermion::Bool = false) = - Vect[FermionParity](0 ⊻ slave_fermion => 1, 1 ⊻ slave_fermion => 2) -tj_space(::Type{Trivial}, ::Type{U1Irrep}; slave_fermion::Bool = false) = - Vect[FermionParity ⊠ U1Irrep]( - (0 ⊻ slave_fermion, 0) => 1, (1 ⊻ slave_fermion, 1 // 2) => 1, (1 ⊻ slave_fermion, -1 // 2) => 1 -) -tj_space(::Type{Trivial}, ::Type{SU2Irrep}; slave_fermion::Bool = false) = - Vect[FermionParity ⊠ SU2Irrep]((0 ⊻ slave_fermion, 0) => 1, (1 ⊻ slave_fermion, 1 // 2) => 1) -tj_space(::Type{U1Irrep}, ::Type{Trivial}; slave_fermion::Bool = false) = - Vect[FermionParity ⊠ U1Irrep]((0 ⊻ slave_fermion, 0) => 1, (1 ⊻ slave_fermion, 1) => 2) -tj_space(::Type{U1Irrep}, ::Type{U1Irrep}; slave_fermion::Bool = false) = - Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]( - (0 ⊻ slave_fermion, 0, 0) => 1, (1 ⊻ slave_fermion, 1, 1 // 2) => 1, (1 ⊻ slave_fermion, 1, -1 // 2) => 1 -) -tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}; slave_fermion::Bool = false) = - Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]( - (0 ⊻ slave_fermion, 0, 0) => 1, (1 ⊻ slave_fermion, 1, 1 // 2) => 1 -) - -@doc """ - slave_fermion_auxiliary_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) - -Return the auxiliary space to add a fermion-Z2 charge -to the t-J space and switch to the slave fermion basis. -""" slave_fermion_auxiliary_space -function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{Trivial}) - return Vect[FermionParity](1 => 1) -end -function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{U1Irrep}) - return Vect[FermionParity ⊠ U1Irrep]((1, 0) => 1) -end -function slave_fermion_auxiliary_space(::Type{Trivial}, ::Type{SU2Irrep}) - return Vect[FermionParity ⊠ SU2Irrep]((1, 0) => 1) -end -function slave_fermion_auxiliary_space(::Type{U1Irrep}, ::Type{Trivial}) - return Vect[FermionParity ⊠ U1Irrep]((1, 0) => 1) -end -function slave_fermion_auxiliary_space(::Type{U1Irrep}, ::Type{U1Irrep}) - return Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]((1, 0, 0) => 1) -end -function slave_fermion_auxiliary_space(::Type{U1Irrep}, ::Type{SU2Irrep}) - return Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((1, 0, 0) => 1) -end +tj_space(::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial) = + Vect[FermionParity](0 => 1, 1 => 2) +tj_space(::Type{Trivial}, ::Type{U1Irrep}) = + Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1 // 2) => 1, (1, -1 // 2) => 1) +tj_space(::Type{Trivial}, ::Type{SU2Irrep}) = + Vect[FermionParity ⊠ SU2Irrep]((0, 0) => 1, (1, 1 // 2) => 1) +tj_space(::Type{U1Irrep}, ::Type{Trivial}) = + Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2) +tj_space(::Type{U1Irrep}, ::Type{U1Irrep}) = + Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1, (1, 1, -1 // 2) => 1) +tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}) = + Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1) """ tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Projection operator from Hubbard space to t-J space (under usual basis, i.e. `slave_fermion = false`). +Projection operator from Hubbard space to t-J space. The scalartype is `Int` to avoid floating point errors. """ function tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) @@ -131,8 +97,7 @@ for (opname, alias) in zip( # apply projector on Hubbard operator @eval function $opname( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; - slave_fermion::Bool = false + elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector} ) particle_symmetry == SU2Irrep && throw(ArgumentError("t-J model does not have ``SU(2)`` particle symmetry.")) @@ -140,11 +105,7 @@ for (opname, alias) in zip( proj = tj_projector(particle_symmetry, spin_symmetry) N = numin(op_H) (N > 1) && (proj = reduce(⊗, ntuple(Returns(proj), N))) - op_tJ = proj * op_H * proj' - slave_fermion || return op_tJ - - Vaux = slave_fermion_auxiliary_space(particle_symmetry, spin_symmetry) - return _fuse_ids(op_tJ, ntuple(Returns(Vaux), N)) + return proj * op_H * proj' end # define alias @@ -153,4 +114,33 @@ for (opname, alias) in zip( end end +slave_fermion_auxiliary_charge(::Type{FermionParity}) = FermionParity(1) +slave_fermion_auxiliary_charge(::Type{ProductSector{T}}) where {T} = + mapreduce(⊠, fieldtypes(T)) do I + I === FermionParity ? FermionParity(1) : one(I) +end + +""" + transform_slave_fermion(O::AbstractTensorMap) + +Transform the given operator to the slave-fermion basis. This is a local basis transformation +that transforms the vacuum in order to map the basis states as follows + +| tJ basis | slave-fermion | +| -------- | ------------- | +| |0⟩ | h⁺|0′⟩ | +| u⁺|0⟩ | bꜛ⁺|0′⟩ | +| d⁺|0⟩ | bꜜ⁺|0′⟩ | + +where now the holon operators (``h``) are fermionic, and the spinon operators (``bꜛ, bꜜ``) are bosonic. +""" +function transform_slave_fermion(O::AbstractTensorMap) + (N = numin(O)) == numout(O) || throw(ArgumentError("not a valid operator")) + aux_charge = slave_fermion_auxiliary_charge(sectortype(O)) + aux_space = spacetype(O)(aux_charge => 1) + aux_operator = id(Int, aux_space^N) + + return fuse_local_operators(O, aux_operator) +end + end diff --git a/src/utils.jl b/src/utils.jl index 69cf671..f65eb8a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,69 +1,28 @@ -# ---- copied from MPSKit, PEPSKit ---- - -_totuple(t) = t isa Tuple ? t : Tuple(t) - -""" - tensorexpr(name, ind_out, [ind_in]) - -Generates expressions for use within `@tensor` environments -of the form `name[ind_out...; ind_in]`. -""" -tensorexpr(name, inds) = Expr(:ref, name, _totuple(inds)...) -function tensorexpr(name, indout, indin) - return Expr( - :typed_vcat, name, Expr(:row, _totuple(indout)...), Expr(:row, _totuple(indin)...) - ) -end - """ - twistdual(t::AbstractTensorMap, i) - twistdual!(t::AbstractTensorMap, i) + fuse_local_operators(O₁, O₂) -Twist the i-th leg of a tensor `t` if it represents a dual space. +Given two ``n``-body operators, acting on ``ℋ₁ = V₁ ⊗ ⋯ ⊗ Vₙ`` and ``ℋ₂ = W₁ ⊗ ⋯ ⊗ Wₙ``, +return the operator acting on the fused local spaces, i.e. on ``ℋ = fuse(V₁ ⊗ W₁) ⊗ ⋯ ⊗ fuse(Vₙ ⊗ Wₙ)``. """ -function twistdual!(t::AbstractTensorMap, i::Int) - isdual(space(t, i)) || return t - return twist!(t, i) -end -function twistdual!(t::AbstractTensorMap, is) - is′ = filter(i -> isdual(space(t, i)), is) - return twist!(t, is′) -end -twistdual(t::AbstractTensorMap, is) = twistdual!(copy(t), is) - - -@generated function _fuse_isomorphisms( - op::AbstractTensorMap{<:Any, S, N, N}, fs::Vector{<:AbstractTensorMap{<:Any, S, 1, 2}} - ) where {S, N} - op_out_e = tensorexpr(:op_out, -(1:N), -((1:N) .+ N)) - op_e = tensorexpr(:op, 1:3:(3 * N), 2:3:(3 * N)) - f_es = map(1:N) do i - j = 3 * (i - 1) + 1 - return tensorexpr(:(fs[$i]), -i, (j, j + 2)) - end - f_dag_es = map(1:N) do i - j = 3 * (i - 1) + 1 - return tensorexpr(:(twistdual(fs[$i]', 1:2)), (j + 1, j + 2), -(N + i)) +function fuse_local_operators(O₁::AbstractTensorMap, O₂::AbstractTensorMap) + spacetype(O₁) == spacetype(O₂) || + throw(ArgumentError("operators have incompatible space types")) + (N = numout(O₁)) == numin(O₁) == numout(O₂) == numin(O₂) || + throw(ArgumentError("operators have incompatible number of indices")) + + fuser = mapreduce(⊗, 1:N) do i + Vᵢ = space(O₁, i) + Wᵢ = space(O₂, i) + VWᵢ = fuse(Vᵢ, Wᵢ) + return isomorphism(VWᵢ ← Vᵢ ⊗ Wᵢ) end - multiplication_ex = Expr( - :call, :*, op_e, f_es..., f_dag_es... - ) - return macroexpand(@__MODULE__, :(return @tensor $op_out_e := $multiplication_ex)) -end -""" - _fuse_ids(op::AbstractTensorMap{T, S, N, N}, [Ps::NTuple{N, S}]) where {T, S, N} + O₁₂ = permute( + O₁ ⊗ O₂, ( + ntuple(i -> iseven(i) ? N + (i ÷ 2) : (i + 1) ÷ 2, 2N), + ntuple(i -> iseven(i) ? 3N + (i ÷ 2) : 2N + (i + 1) ÷ 2, 2N), + ) + ) -Fuse identities on auxiliary physical spaces `Ps` into a given operator `op`. -When `Ps` is not specified, it defaults to the domain spaces of `op`. -""" -function _fuse_ids(op::AbstractTensorMap{T, S, N, N}, Ps::NTuple{N, S}) where {T, S, N} - # make isomorphisms - fs = map(1:N) do i - return isomorphism(fuse(space(op, i), Ps[i]), space(op, i) ⊗ Ps[i]) - end - # and fuse them into the operator - return _fuse_isomorphisms(op, fs) + return fuser * O₁₂ * fuser' end - -# ---- From 6d2545aa0188c8981ee7e302c9e47057d88f6ebb Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 10:49:54 -0400 Subject: [PATCH 09/22] fill in default arguments --- src/tjoperators.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 833a5ec..8ba51c2 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -95,6 +95,11 @@ for (opname, alias) in zip( @doc (@doc HubbardOperators.$opname) $opname end + # default arguments + @eval $opname(particle_symmetry::Type{<:Sector} = Trivial, spin_symmetry::Type{<:Sector} = Trivial) = + $opname(ComplexF64, particle_symmetry, spin_symmetry) + @eval $opname(elt::Type{<:Number}) = $opname(elt, Trivial, Trivial) + # apply projector on Hubbard operator @eval function $opname( elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector} From ae830abbc02d8e5462b10e82f8bdbab61ea891e9 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 11:27:59 -0400 Subject: [PATCH 10/22] update tests --- test/tjoperators.jl | 442 +++++++++++++++++++++----------------------- 1 file changed, 208 insertions(+), 234 deletions(-) diff --git a/test/tjoperators.jl b/test/tjoperators.jl index f457657..bc1a33d 100644 --- a/test/tjoperators.jl +++ b/test/tjoperators.jl @@ -12,252 +12,226 @@ implemented_symmetries = [ ] @testset "Compare symmetric with trivial tensors" begin - for slave_fermion in (false, true) - for particle_symmetry in [Trivial, U1Irrep], - spin_symmetry in [Trivial, U1Irrep, SU2Irrep] + for particle_symmetry in [Trivial, U1Irrep], + spin_symmetry in [Trivial, U1Irrep, SU2Irrep] - if (particle_symmetry, spin_symmetry) in implemented_symmetries - space = tj_space(particle_symmetry, spin_symmetry; slave_fermion) + if (particle_symmetry, spin_symmetry) in implemented_symmetries + space = tj_space(particle_symmetry, spin_symmetry) - O = e_plus_e_min( - ComplexF64, particle_symmetry, spin_symmetry; - slave_fermion - ) - O_triv = e_plus_e_min(ComplexF64, Trivial, Trivial; slave_fermion) - test_operator(O, O_triv) + O = e_plus_e_min(ComplexF64, particle_symmetry, spin_symmetry) + O_triv = e_plus_e_min() + test_operator(O, O_triv) + + O_sf = transform_slave_fermion(O) + @test norm(O_sf) ≈ norm(O) + @test transform_slave_fermion(O_sf) ≈ O + + O_sf_triv = transform_slave_fermion(O_triv) + test_operator(O_sf, O_sf_triv) + + O = e_num(ComplexF64, particle_symmetry, spin_symmetry) + O_triv = e_num() + test_operator(O, O_triv) + + O_sf = transform_slave_fermion(O) + @test norm(O_sf) ≈ norm(O) + @test transform_slave_fermion(O_sf) ≈ O + + O_sf_triv = transform_slave_fermion(O_triv) + test_operator(O_sf, O_sf_triv) + + O = S_exchange(ComplexF64, particle_symmetry, spin_symmetry) + O_triv = S_exchange() + test_operator(O, O_triv) - O = e_num(ComplexF64, particle_symmetry, spin_symmetry; slave_fermion) - O_triv = e_num(ComplexF64, Trivial, Trivial; slave_fermion) - test_operator(O, O_triv) + O_sf = transform_slave_fermion(O) + @test norm(O_sf) ≈ norm(O) + @test transform_slave_fermion(O_sf) ≈ O - O = S_exchange(ComplexF64, particle_symmetry, spin_symmetry; slave_fermion) - O_triv = S_exchange(ComplexF64, Trivial, Trivial; slave_fermion) - test_operator(O, O_triv) + O_sf_triv = transform_slave_fermion(O_triv) + test_operator(O_sf, O_sf_triv) + else + @test_broken e_plus_e_min( + ComplexF64, particle_symmetry, spin_symmetry + ) + @test_broken e_num( + ComplexF64, particle_symmetry, spin_symmetry + ) + @test_broken S_exchange( + ComplexF64, particle_symmetry, spin_symmetry + ) + end + end +end + +@testset "basic properties" begin + for particle_symmetry in [Trivial, U1Irrep], + spin_symmetry in [Trivial, U1Irrep, SU2Irrep] + + if (particle_symmetry, spin_symmetry) in implemented_symmetries + # test hopping operator + epem = e_plus_e_min(particle_symmetry, spin_symmetry) + emep = e_min_e_plus(particle_symmetry, spin_symmetry) + @test epem' ≈ -emep ≈ swap_2sites(epem) + @test transform_slave_fermion(epem)' ≈ -transform_slave_fermion(emep) ≈ + swap_2sites(transform_slave_fermion(epem)) + if spin_symmetry !== SU2Irrep + dpdm = d_plus_d_min(particle_symmetry, spin_symmetry) + dmdp = d_min_d_plus(particle_symmetry, spin_symmetry) + @test dpdm' ≈ -dmdp ≈ swap_2sites(dpdm) + upum = u_plus_u_min(particle_symmetry, spin_symmetry) + umup = u_min_u_plus(particle_symmetry, spin_symmetry) + @test upum' ≈ -umup ≈ swap_2sites(upum) else - @test_broken e_plus_e_min( - ComplexF64, particle_symmetry, spin_symmetry; - slave_fermion + @test_throws ArgumentError d_plus_d_min( + particle_symmetry, spin_symmetry + ) + @test_throws ArgumentError d_min_d_plus( + particle_symmetry, spin_symmetry ) - @test_broken e_num( - ComplexF64, particle_symmetry, spin_symmetry; - slave_fermion + @test_throws ArgumentError u_plus_u_min( + particle_symmetry, spin_symmetry ) - @test_broken S_exchange( - ComplexF64, particle_symmetry, spin_symmetry; - slave_fermion + @test_throws ArgumentError u_min_u_plus( + particle_symmetry, spin_symmetry ) end - end - end -end -@testset "basic properties" begin - for slave_fermion in (false, true) - for particle_symmetry in [Trivial, U1Irrep], - spin_symmetry in [Trivial, U1Irrep, SU2Irrep] + # test number operator + if spin_symmetry !== SU2Irrep + pspace = tj_space(particle_symmetry, spin_symmetry) + @test e_num(particle_symmetry, spin_symmetry) ≈ + u_num(particle_symmetry, spin_symmetry) + + d_num(particle_symmetry, spin_symmetry) + @test u_num(particle_symmetry, spin_symmetry) * + d_num(particle_symmetry, spin_symmetry) ≈ + d_num(particle_symmetry, spin_symmetry) * + u_num(particle_symmetry, spin_symmetry) ≈ + zeros(pspace ← pspace) + @test TensorKit.id(pspace) ≈ + h_num(particle_symmetry, spin_symmetry) + + e_num(particle_symmetry, spin_symmetry) + else + @test_throws ArgumentError u_num(particle_symmetry, spin_symmetry) + @test_throws ArgumentError d_num(particle_symmetry, spin_symmetry) + end - if (particle_symmetry, spin_symmetry) in implemented_symmetries - # test hopping operator - epem = e_plus_e_min(particle_symmetry, spin_symmetry; slave_fermion) - emep = e_min_e_plus(particle_symmetry, spin_symmetry; slave_fermion) - @test epem' ≈ -emep ≈ swap_2sites(epem) - if spin_symmetry !== SU2Irrep - dpdm = d_plus_d_min(particle_symmetry, spin_symmetry) - dmdp = d_min_d_plus(particle_symmetry, spin_symmetry) - @test dpdm' ≈ -dmdp ≈ swap_2sites(dpdm) - upum = u_plus_u_min(particle_symmetry, spin_symmetry) - umup = u_min_u_plus(particle_symmetry, spin_symmetry) - @test upum' ≈ -umup ≈ swap_2sites(upum) - else - @test_throws ArgumentError d_plus_d_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError d_min_d_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError u_plus_u_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError u_min_u_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) - end + # test singlet operators + if particle_symmetry == Trivial && spin_symmetry !== SU2Irrep + singm = singlet_min(particle_symmetry, spin_symmetry) + umdm = u_min_d_min(particle_symmetry, spin_symmetry) + dmum = d_min_u_min(particle_symmetry, spin_symmetry) + @test swap_2sites(umdm) ≈ -dmum + @test swap_2sites(transform_slave_fermion(umdm)) ≈ -transform_slave_fermion(dmum) + @test swap_2sites(singm) ≈ singm + @test swap_2sites(transform_slave_fermion(singm)) ≈ transform_slave_fermion(singm) + @test singm ≈ (-umdm + dmum) / sqrt(2) + updp = u_plus_d_plus(particle_symmetry, spin_symmetry) + dpup = d_plus_u_plus(particle_symmetry, spin_symmetry) + @test swap_2sites(updp) ≈ -dpup + @test swap_2sites(transform_slave_fermion(updp)) ≈ -transform_slave_fermion(dpup) - # test number operator - if spin_symmetry !== SU2Irrep - pspace = tj_space(particle_symmetry, spin_symmetry; slave_fermion) - @test e_num(particle_symmetry, spin_symmetry; slave_fermion) ≈ - u_num(particle_symmetry, spin_symmetry; slave_fermion) + - d_num(particle_symmetry, spin_symmetry; slave_fermion) - @test u_num(particle_symmetry, spin_symmetry; slave_fermion) * - d_num(particle_symmetry, spin_symmetry; slave_fermion) ≈ - d_num(particle_symmetry, spin_symmetry; slave_fermion) * - u_num(particle_symmetry, spin_symmetry; slave_fermion) ≈ - zeros(pspace ← pspace) - @test TensorKit.id(pspace) ≈ - h_num(particle_symmetry, spin_symmetry; slave_fermion) + - e_num(particle_symmetry, spin_symmetry; slave_fermion) - else - @test_throws ArgumentError u_num( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError d_num( - particle_symmetry, spin_symmetry; - slave_fermion - ) + else + @test_throws ArgumentError singlet_plus(particle_symmetry, spin_symmetry) + @test_throws ArgumentError singlet_min(particle_symmetry, spin_symmetry) + @test_throws ArgumentError u_min_d_min(particle_symmetry, spin_symmetry) + @test_throws ArgumentError d_min_u_min(particle_symmetry, spin_symmetry) + @test_throws ArgumentError u_plus_d_plus(particle_symmetry, spin_symmetry) + @test_throws ArgumentError d_plus_u_plus(particle_symmetry, spin_symmetry) + end + + # test triplet operators + if particle_symmetry == Trivial && spin_symmetry == Trivial + umum = u_min_u_min(particle_symmetry, spin_symmetry) + dmdm = d_min_d_min(particle_symmetry, spin_symmetry) + upup = u_plus_u_plus(particle_symmetry, spin_symmetry) + dpdp = d_plus_d_plus(particle_symmetry, spin_symmetry) + for O in (umum, dmdm, upup, dpdp) + @test swap_2sites(O) ≈ -O + @test swap_2sites(transform_slave_fermion(O)) ≈ -transform_slave_fermion(O) end + else + @test_throws ArgumentError u_min_u_min(particle_symmetry, spin_symmetry) + @test_throws ArgumentError d_min_d_min(particle_symmetry, spin_symmetry) + @test_throws ArgumentError u_plus_u_plus(particle_symmetry, spin_symmetry) + @test_throws ArgumentError d_plus_d_plus(particle_symmetry, spin_symmetry) + end - # test singlet operators - if particle_symmetry == Trivial && spin_symmetry !== SU2Irrep - singm = singlet_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - umdm = u_min_d_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - dmum = d_min_u_min(particle_symmetry, spin_symmetry; slave_fermion) - @test swap_2sites(umdm) ≈ -dmum - @test swap_2sites(singm) ≈ singm - @test singm ≈ (-umdm + dmum) / sqrt(2) - updp = u_plus_d_plus(particle_symmetry, spin_symmetry; slave_fermion) - dpup = d_plus_u_plus(particle_symmetry, spin_symmetry; slave_fermion) - @test swap_2sites(updp) ≈ -dpup - else - @test_throws ArgumentError singlet_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError singlet_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError u_min_d_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError d_min_u_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError u_plus_d_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError d_plus_u_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) + # test spin operator + if spin_symmetry == Trivial + ε = zeros(ComplexF64, 3, 3, 3) + for i in 1:3 + ε[mod1(i, 3), mod1(i + 1, 3), mod1(i + 2, 3)] = 1 + ε[mod1(i, 3), mod1(i - 1, 3), mod1(i - 2, 3)] = -1 end + Svec = [ + S_x(particle_symmetry, spin_symmetry), + S_y(particle_symmetry, spin_symmetry), + S_z(particle_symmetry, spin_symmetry), + ] + Svec_sf = map(transform_slave_fermion, Svec) - # test triplet operators - if particle_symmetry == Trivial && spin_symmetry == Trivial - umum = u_min_u_min(particle_symmetry, spin_symmetry; slave_fermion) - dmdm = d_min_d_min(particle_symmetry, spin_symmetry; slave_fermion) - upup = u_plus_u_plus(particle_symmetry, spin_symmetry; slave_fermion) - dpdp = d_plus_d_plus(particle_symmetry, spin_symmetry; slave_fermion) - @test swap_2sites(umum) ≈ -umum - @test swap_2sites(dmdm) ≈ -dmdm - @test swap_2sites(upup) ≈ -upup - @test swap_2sites(dpdp) ≈ -dpdp - else - @test_throws ArgumentError u_min_u_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError d_min_d_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError u_plus_u_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError d_plus_d_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) + # Hermiticity + for s in Svec + @test s' ≈ s + end + for s in Svec_sf + @test s' ≈ s end - # test spin operator - if spin_symmetry == Trivial - ε = zeros(ComplexF64, 3, 3, 3) - for i in 1:3 - ε[mod1(i, 3), mod1(i + 1, 3), mod1(i + 2, 3)] = 1 - ε[mod1(i, 3), mod1(i - 1, 3), mod1(i - 2, 3)] = -1 - end - Svec = [ - S_x(particle_symmetry, spin_symmetry; slave_fermion), - S_y(particle_symmetry, spin_symmetry; slave_fermion), - S_z(particle_symmetry, spin_symmetry; slave_fermion), - ] - # Hermiticity - for s in Svec - @test s' ≈ s - end - # operators should be normalized - S = 1 / 2 - @test sum(tr(Svec[i]^2) for i in 1:3) / (2S + 1) ≈ S * (S + 1) - # test S_plus and S_min - @test S_plus_S_min(particle_symmetry, spin_symmetry; slave_fermion) ≈ - S_plus(particle_symmetry, spin_symmetry; slave_fermion) ⊗ - S_min(particle_symmetry, spin_symmetry; slave_fermion) - @test S_min_S_plus(particle_symmetry, spin_symmetry; slave_fermion) ≈ - S_min(particle_symmetry, spin_symmetry; slave_fermion) ⊗ - S_plus(particle_symmetry, spin_symmetry; slave_fermion) - # commutation relations - for i in 1:3, j in 1:3 - @test Svec[i] * Svec[j] - Svec[j] * Svec[i] ≈ - sum(im * ε[i, j, k] * Svec[k] for k in 1:3) - end - else - @test_throws ArgumentError S_plus( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError S_min( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError S_x( - particle_symmetry, spin_symmetry; - slave_fermion - ) - @test_throws ArgumentError S_y( - particle_symmetry, spin_symmetry; - slave_fermion - ) - if spin_symmetry != U1Irrep - @test_throws ArgumentError S_z( - particle_symmetry, spin_symmetry; - slave_fermion - ) - end + # operators should be normalized + S = 1 / 2 + @test sum(tr(Svec[i]^2) for i in 1:3) / (2S + 1) ≈ S * (S + 1) + @test sum(tr(Svec_sf[i]^2) for i in 1:3) / (2S + 1) ≈ S * (S + 1) + + # test S_plus and S_min + Sp = S_plus(particle_symmetry, spin_symmetry) + Sm = S_min(particle_symmetry, spin_symmetry) + Spm = S_plus_S_min(particle_symmetry, spin_symmetry) + Smp = S_min_S_plus(particle_symmetry, spin_symmetry) + @test Spm ≈ Sp ⊗ Sm + @test transform_slave_fermion(Spm) ≈ transform_slave_fermion(Sp) ⊗ transform_slave_fermion(Sm) + @test Smp ≈ Sm ⊗ Sp + @test transform_slave_fermion(Smp) ≈ transform_slave_fermion(Sm) ⊗ transform_slave_fermion(Sp) + + # commutation relations + for i in 1:3, j in 1:3 + @test Svec[i] * Svec[j] - Svec[j] * Svec[i] ≈ + sum(im * ε[i, j, k] * Svec[k] for k in 1:3) + end + for i in 1:3, j in 1:3 + @test Svec_sf[i] * Svec_sf[j] - Svec_sf[j] * Svec_sf[i] ≈ + sum(im * ε[i, j, k] * Svec_sf[k] for k in 1:3) end else - @test_broken d_plus_d_min(particle_symmetry, spin_symmetry; slave_fermion) - @test_broken d_min_d_plus(particle_symmetry, spin_symmetry; slave_fermion) - @test_broken u_plus_u_min(particle_symmetry, spin_symmetry; slave_fermion) - @test_broken u_min_u_plus(particle_symmetry, spin_symmetry; slave_fermion) - @test_broken e_num(particle_symmetry, spin_symmetry; slave_fermion) - @test_broken u_num(particle_symmetry, spin_symmetry; slave_fermion) - @test_broken d_num(particle_symmetry, spin_symmetry; slave_fermion) + @test_throws ArgumentError S_plus(particle_symmetry, spin_symmetry) + @test_throws ArgumentError S_min(particle_symmetry, spin_symmetry) + @test_throws ArgumentError S_x(particle_symmetry, spin_symmetry) + @test_throws ArgumentError S_y(particle_symmetry, spin_symmetry) + spin_symmetry == U1Irrep || + @test_throws ArgumentError S_z(particle_symmetry, spin_symmetry) end + else + @test_broken d_plus_d_min(particle_symmetry, spin_symmetry) + @test_broken d_min_d_plus(particle_symmetry, spin_symmetry) + @test_broken u_plus_u_min(particle_symmetry, spin_symmetry) + @test_broken u_min_u_plus(particle_symmetry, spin_symmetry) + @test_broken e_num(particle_symmetry, spin_symmetry) + @test_broken u_num(particle_symmetry, spin_symmetry) + @test_broken d_num(particle_symmetry, spin_symmetry) end end end function tjhamiltonian(particle_symmetry, spin_symmetry; t, J, mu, L, slave_fermion) - num = e_num(particle_symmetry, spin_symmetry; slave_fermion) - hop_heis = (-t) * e_hopping(particle_symmetry, spin_symmetry; slave_fermion) + J * (S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - (1 / 4) * (num ⊗ num)) + num = e_num(particle_symmetry, spin_symmetry) + hop_heis = (-t) * e_hopping(particle_symmetry, spin_symmetry) + J * (S_exchange(particle_symmetry, spin_symmetry) - (1 / 4) * (num ⊗ num)) chemical_potential = (-mu) * num - I = id(tj_space(particle_symmetry, spin_symmetry; slave_fermion)) + I = id(tj_space(particle_symmetry, spin_symmetry)) + + if slave_fermion + hop_heis, chemical_potential, I = transform_slave_fermion.((hop_heis, chemical_potential, I)) + end H = sum(1:(L - 1)) do i return reduce(⊗, insert!(collect(Any, fill(I, L - 2)), i, hop_heis)) end + sum(1:L) do i @@ -309,22 +283,22 @@ end for particle_symmetry in [Trivial, U1Irrep], spin_symmetry in [Trivial, U1Irrep, SU2Irrep] - for slave_fermion in (false, true) - if (particle_symmetry, spin_symmetry) in implemented_symmetries - t, J = rand(rng, 2) - num = e_num(particle_symmetry, spin_symmetry; slave_fermion) - H = (-t) * e_hopping(particle_symmetry, spin_symmetry; slave_fermion) + - J * ( - S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - - (1 / 4) * (num ⊗ num) - ) + if (particle_symmetry, spin_symmetry) in implemented_symmetries + t, J = rand(rng, 2) + num = e_num(particle_symmetry, spin_symmetry) + H = (-t) * e_hopping(particle_symmetry, spin_symmetry) + + J * ( + S_exchange(particle_symmetry, spin_symmetry) - + (1 / 4) * (num ⊗ num) + ) - true_eigenvals = sort( - vcat([-J], repeat([-t], 2), repeat([t], 2), repeat([0.0], 4)) - ) - eigenvals = expanded_eigenvalues(H; L) - @test eigenvals ≈ true_eigenvals - end + true_eigenvals = sort( + vcat([-J], repeat([-t], 2), repeat([t], 2), repeat([0.0], 4)) + ) + eigenvals = expanded_eigenvalues(H; L) + @test eigenvals ≈ true_eigenvals + eigenvals = expanded_eigenvalues(transform_slave_fermion(H); L) + @test eigenvals ≈ true_eigenvals end end end From d9e1b2ebfacf16d049ecb48c2e36d6c7d9d99be6 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sat, 18 Oct 2025 11:39:02 -0400 Subject: [PATCH 11/22] fix docs --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index b83133d..e0b8f51 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,6 +20,7 @@ makedocs(; assets = String[], ), pages = ["Home" => "index.md", "Operators" => operatorpages], + checkdocs = :public ) deploydocs(; From a75697b80f9260b2737d4c175957a4e4dca6b74b Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 19 Oct 2025 14:08:06 +0800 Subject: [PATCH 12/22] Restore slave_fermion option --- src/hubbardoperators.jl | 6 +- src/tjoperators.jl | 26 ++- test/tjoperators.jl | 450 +++++++++++++++++++++------------------- 3 files changed, 263 insertions(+), 219 deletions(-) diff --git a/src/hubbardoperators.jl b/src/hubbardoperators.jl index 5b42519..724a766 100644 --- a/src/hubbardoperators.jl +++ b/src/hubbardoperators.jl @@ -343,7 +343,7 @@ const Sˣ = S_x S_y(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) Sʸ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Return the one-body spin-1/2 y-operator on the electrons (only defined for `Trivial` symmetry). +Return the one-body spin-1/2 y-operator on the electrons (only defined for `Trivial` symmetry). """ S_y function S_y(P::Type{<:Sector} = Trivial, S::Type{<:Sector} = Trivial) return S_y(ComplexF64, P, S) @@ -363,7 +363,7 @@ const Sʸ = S_y S_z(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) Sᶻ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Return the one-body spin-1/2 z-operator on the electrons. +Return the one-body spin-1/2 z-operator on the electrons. """ S_z function S_z(P::Type{<:Sector} = Trivial, S::Type{<:Sector} = Trivial) return S_z(ComplexF64, P, S) @@ -611,7 +611,7 @@ const e⁻e⁺ = e_min_e_plus e_hop([elt::Type{<:Number}], [particle_symmetry::Type{<:Sector}], [spin_symmetry::Type{<:Sector}]) Return the two-body operator that describes a particle that hops between the first and the second site. -""" e_hop +""" e_hopping e_hopping(P::Type{<:Sector}, S::Type{<:Sector}) = e_hopping(ComplexF64, P, S) function e_hopping( elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 8ba51c2..a42c5cb 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -2,7 +2,7 @@ module TJOperators using LinearAlgebra using TensorKit -using ..HubbardOperators: HubbardOperators, hubbard_space +import ..HubbardOperators import ..TensorKitTensors: fuse_local_operators export tj_space, tj_projector @@ -59,7 +59,7 @@ Projection operator from Hubbard space to t-J space. The scalartype is `Int` to avoid floating point errors. """ function tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) - Vhub = hubbard_space(particle_symmetry, spin_symmetry) + Vhub = HubbardOperators.hubbard_space(particle_symmetry, spin_symmetry) VtJ = tj_space(particle_symmetry, spin_symmetry) proj = zeros(Int, Vhub → VtJ) @@ -92,17 +92,23 @@ for (opname, alias) in zip( ) # copy over the docstrings @eval begin - @doc (@doc HubbardOperators.$opname) $opname + hub_doc = (@doc HubbardOperators.$opname).text[1] + tJ_doc = replace(hub_doc, "[spin_symmetry::Type{<:Sector}])" => "[spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false)") * "Use `slave_fermion = true` to switch to the slave-fermion basis.\n" + @doc (tJ_doc) $opname end # default arguments - @eval $opname(particle_symmetry::Type{<:Sector} = Trivial, spin_symmetry::Type{<:Sector} = Trivial) = - $opname(ComplexF64, particle_symmetry, spin_symmetry) - @eval $opname(elt::Type{<:Number}) = $opname(elt, Trivial, Trivial) + @eval $opname( + particle_symmetry::Type{<:Sector} = Trivial, spin_symmetry::Type{<:Sector} = Trivial; + slave_fermion::Bool = false + ) = $opname(ComplexF64, particle_symmetry, spin_symmetry; slave_fermion) + @eval $opname(elt::Type{<:Number}; slave_fermion::Bool = false) = + $opname(elt, Trivial, Trivial; slave_fermion) # apply projector on Hubbard operator @eval function $opname( - elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector} + elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; + slave_fermion::Bool = false ) particle_symmetry == SU2Irrep && throw(ArgumentError("t-J model does not have ``SU(2)`` particle symmetry.")) @@ -110,7 +116,11 @@ for (opname, alias) in zip( proj = tj_projector(particle_symmetry, spin_symmetry) N = numin(op_H) (N > 1) && (proj = reduce(⊗, ntuple(Returns(proj), N))) - return proj * op_H * proj' + op = proj * op_H * proj' + if slave_fermion + op = transform_slave_fermion(op) + end + return op end # define alias diff --git a/test/tjoperators.jl b/test/tjoperators.jl index bc1a33d..20dfab4 100644 --- a/test/tjoperators.jl +++ b/test/tjoperators.jl @@ -12,226 +12,255 @@ implemented_symmetries = [ ] @testset "Compare symmetric with trivial tensors" begin - for particle_symmetry in [Trivial, U1Irrep], - spin_symmetry in [Trivial, U1Irrep, SU2Irrep] - - if (particle_symmetry, spin_symmetry) in implemented_symmetries - space = tj_space(particle_symmetry, spin_symmetry) - - O = e_plus_e_min(ComplexF64, particle_symmetry, spin_symmetry) - O_triv = e_plus_e_min() - test_operator(O, O_triv) - - O_sf = transform_slave_fermion(O) - @test norm(O_sf) ≈ norm(O) - @test transform_slave_fermion(O_sf) ≈ O - - O_sf_triv = transform_slave_fermion(O_triv) - test_operator(O_sf, O_sf_triv) - - O = e_num(ComplexF64, particle_symmetry, spin_symmetry) - O_triv = e_num() - test_operator(O, O_triv) - - O_sf = transform_slave_fermion(O) - @test norm(O_sf) ≈ norm(O) - @test transform_slave_fermion(O_sf) ≈ O - - O_sf_triv = transform_slave_fermion(O_triv) - test_operator(O_sf, O_sf_triv) - - O = S_exchange(ComplexF64, particle_symmetry, spin_symmetry) - O_triv = S_exchange() - test_operator(O, O_triv) + for slave_fermion in (false, true) + for particle_symmetry in [Trivial, U1Irrep], + spin_symmetry in [Trivial, U1Irrep, SU2Irrep] - O_sf = transform_slave_fermion(O) - @test norm(O_sf) ≈ norm(O) - @test transform_slave_fermion(O_sf) ≈ O + if (particle_symmetry, spin_symmetry) in implemented_symmetries + space = tj_space(particle_symmetry, spin_symmetry; slave_fermion) - O_sf_triv = transform_slave_fermion(O_triv) - test_operator(O_sf, O_sf_triv) - else - @test_broken e_plus_e_min( - ComplexF64, particle_symmetry, spin_symmetry - ) - @test_broken e_num( - ComplexF64, particle_symmetry, spin_symmetry - ) - @test_broken S_exchange( - ComplexF64, particle_symmetry, spin_symmetry - ) - end - end -end + O = e_plus_e_min( + ComplexF64, particle_symmetry, spin_symmetry; + slave_fermion + ) + O_triv = e_plus_e_min(ComplexF64, Trivial, Trivial; slave_fermion) + test_operator(O, O_triv) -@testset "basic properties" begin - for particle_symmetry in [Trivial, U1Irrep], - spin_symmetry in [Trivial, U1Irrep, SU2Irrep] + O = e_num(ComplexF64, particle_symmetry, spin_symmetry; slave_fermion) + O_triv = e_num(ComplexF64, Trivial, Trivial; slave_fermion) + test_operator(O, O_triv) - if (particle_symmetry, spin_symmetry) in implemented_symmetries - # test hopping operator - epem = e_plus_e_min(particle_symmetry, spin_symmetry) - emep = e_min_e_plus(particle_symmetry, spin_symmetry) - @test epem' ≈ -emep ≈ swap_2sites(epem) - @test transform_slave_fermion(epem)' ≈ -transform_slave_fermion(emep) ≈ - swap_2sites(transform_slave_fermion(epem)) - if spin_symmetry !== SU2Irrep - dpdm = d_plus_d_min(particle_symmetry, spin_symmetry) - dmdp = d_min_d_plus(particle_symmetry, spin_symmetry) - @test dpdm' ≈ -dmdp ≈ swap_2sites(dpdm) - upum = u_plus_u_min(particle_symmetry, spin_symmetry) - umup = u_min_u_plus(particle_symmetry, spin_symmetry) - @test upum' ≈ -umup ≈ swap_2sites(upum) + O = S_exchange(ComplexF64, particle_symmetry, spin_symmetry; slave_fermion) + O_triv = S_exchange(ComplexF64, Trivial, Trivial; slave_fermion) + test_operator(O, O_triv) else - @test_throws ArgumentError d_plus_d_min( - particle_symmetry, spin_symmetry - ) - @test_throws ArgumentError d_min_d_plus( - particle_symmetry, spin_symmetry + @test_broken e_plus_e_min( + ComplexF64, particle_symmetry, spin_symmetry; + slave_fermion ) - @test_throws ArgumentError u_plus_u_min( - particle_symmetry, spin_symmetry + @test_broken e_num( + ComplexF64, particle_symmetry, spin_symmetry; + slave_fermion ) - @test_throws ArgumentError u_min_u_plus( - particle_symmetry, spin_symmetry + @test_broken S_exchange( + ComplexF64, particle_symmetry, spin_symmetry; + slave_fermion ) end + end + end +end - # test number operator - if spin_symmetry !== SU2Irrep - pspace = tj_space(particle_symmetry, spin_symmetry) - @test e_num(particle_symmetry, spin_symmetry) ≈ - u_num(particle_symmetry, spin_symmetry) + - d_num(particle_symmetry, spin_symmetry) - @test u_num(particle_symmetry, spin_symmetry) * - d_num(particle_symmetry, spin_symmetry) ≈ - d_num(particle_symmetry, spin_symmetry) * - u_num(particle_symmetry, spin_symmetry) ≈ - zeros(pspace ← pspace) - @test TensorKit.id(pspace) ≈ - h_num(particle_symmetry, spin_symmetry) + - e_num(particle_symmetry, spin_symmetry) - else - @test_throws ArgumentError u_num(particle_symmetry, spin_symmetry) - @test_throws ArgumentError d_num(particle_symmetry, spin_symmetry) - end - - # test singlet operators - if particle_symmetry == Trivial && spin_symmetry !== SU2Irrep - singm = singlet_min(particle_symmetry, spin_symmetry) - umdm = u_min_d_min(particle_symmetry, spin_symmetry) - dmum = d_min_u_min(particle_symmetry, spin_symmetry) - @test swap_2sites(umdm) ≈ -dmum - @test swap_2sites(transform_slave_fermion(umdm)) ≈ -transform_slave_fermion(dmum) - @test swap_2sites(singm) ≈ singm - @test swap_2sites(transform_slave_fermion(singm)) ≈ transform_slave_fermion(singm) - @test singm ≈ (-umdm + dmum) / sqrt(2) - updp = u_plus_d_plus(particle_symmetry, spin_symmetry) - dpup = d_plus_u_plus(particle_symmetry, spin_symmetry) - @test swap_2sites(updp) ≈ -dpup - @test swap_2sites(transform_slave_fermion(updp)) ≈ -transform_slave_fermion(dpup) - - else - @test_throws ArgumentError singlet_plus(particle_symmetry, spin_symmetry) - @test_throws ArgumentError singlet_min(particle_symmetry, spin_symmetry) - @test_throws ArgumentError u_min_d_min(particle_symmetry, spin_symmetry) - @test_throws ArgumentError d_min_u_min(particle_symmetry, spin_symmetry) - @test_throws ArgumentError u_plus_d_plus(particle_symmetry, spin_symmetry) - @test_throws ArgumentError d_plus_u_plus(particle_symmetry, spin_symmetry) - end +@testset "basic properties" begin + for slave_fermion in (false, true) + for particle_symmetry in [Trivial, U1Irrep], + spin_symmetry in [Trivial, U1Irrep, SU2Irrep] - # test triplet operators - if particle_symmetry == Trivial && spin_symmetry == Trivial - umum = u_min_u_min(particle_symmetry, spin_symmetry) - dmdm = d_min_d_min(particle_symmetry, spin_symmetry) - upup = u_plus_u_plus(particle_symmetry, spin_symmetry) - dpdp = d_plus_d_plus(particle_symmetry, spin_symmetry) - for O in (umum, dmdm, upup, dpdp) - @test swap_2sites(O) ≈ -O - @test swap_2sites(transform_slave_fermion(O)) ≈ -transform_slave_fermion(O) + if (particle_symmetry, spin_symmetry) in implemented_symmetries + # test hopping operator + epem = e_plus_e_min(particle_symmetry, spin_symmetry; slave_fermion) + emep = e_min_e_plus(particle_symmetry, spin_symmetry; slave_fermion) + @test epem' ≈ -emep ≈ swap_2sites(epem) + if spin_symmetry !== SU2Irrep + dpdm = d_plus_d_min(particle_symmetry, spin_symmetry) + dmdp = d_min_d_plus(particle_symmetry, spin_symmetry) + @test dpdm' ≈ -dmdp ≈ swap_2sites(dpdm) + upum = u_plus_u_min(particle_symmetry, spin_symmetry) + umup = u_min_u_plus(particle_symmetry, spin_symmetry) + @test upum' ≈ -umup ≈ swap_2sites(upum) + else + @test_throws ArgumentError d_plus_d_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError d_min_d_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError u_plus_u_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError u_min_u_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) end - else - @test_throws ArgumentError u_min_u_min(particle_symmetry, spin_symmetry) - @test_throws ArgumentError d_min_d_min(particle_symmetry, spin_symmetry) - @test_throws ArgumentError u_plus_u_plus(particle_symmetry, spin_symmetry) - @test_throws ArgumentError d_plus_d_plus(particle_symmetry, spin_symmetry) - end - # test spin operator - if spin_symmetry == Trivial - ε = zeros(ComplexF64, 3, 3, 3) - for i in 1:3 - ε[mod1(i, 3), mod1(i + 1, 3), mod1(i + 2, 3)] = 1 - ε[mod1(i, 3), mod1(i - 1, 3), mod1(i - 2, 3)] = -1 + # test number operator + if spin_symmetry !== SU2Irrep + pspace = tj_space(particle_symmetry, spin_symmetry; slave_fermion) + @test e_num(particle_symmetry, spin_symmetry; slave_fermion) ≈ + u_num(particle_symmetry, spin_symmetry; slave_fermion) + + d_num(particle_symmetry, spin_symmetry; slave_fermion) + @test u_num(particle_symmetry, spin_symmetry; slave_fermion) * + d_num(particle_symmetry, spin_symmetry; slave_fermion) ≈ + d_num(particle_symmetry, spin_symmetry; slave_fermion) * + u_num(particle_symmetry, spin_symmetry; slave_fermion) ≈ + zeros(pspace ← pspace) + @test TensorKit.id(pspace) ≈ + h_num(particle_symmetry, spin_symmetry; slave_fermion) + + e_num(particle_symmetry, spin_symmetry; slave_fermion) + else + @test_throws ArgumentError u_num( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError d_num( + particle_symmetry, spin_symmetry; + slave_fermion + ) end - Svec = [ - S_x(particle_symmetry, spin_symmetry), - S_y(particle_symmetry, spin_symmetry), - S_z(particle_symmetry, spin_symmetry), - ] - Svec_sf = map(transform_slave_fermion, Svec) - # Hermiticity - for s in Svec - @test s' ≈ s - end - for s in Svec_sf - @test s' ≈ s + # test singlet operators + if particle_symmetry == Trivial && spin_symmetry !== SU2Irrep + singm = singlet_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + umdm = u_min_d_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + dmum = d_min_u_min(particle_symmetry, spin_symmetry; slave_fermion) + @test swap_2sites(umdm) ≈ -dmum + @test swap_2sites(singm) ≈ singm + @test singm ≈ (-umdm + dmum) / sqrt(2) + updp = u_plus_d_plus(particle_symmetry, spin_symmetry; slave_fermion) + dpup = d_plus_u_plus(particle_symmetry, spin_symmetry; slave_fermion) + @test swap_2sites(updp) ≈ -dpup + else + @test_throws ArgumentError singlet_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError singlet_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError u_min_d_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError d_min_u_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError u_plus_d_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError d_plus_u_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) end - # operators should be normalized - S = 1 / 2 - @test sum(tr(Svec[i]^2) for i in 1:3) / (2S + 1) ≈ S * (S + 1) - @test sum(tr(Svec_sf[i]^2) for i in 1:3) / (2S + 1) ≈ S * (S + 1) - - # test S_plus and S_min - Sp = S_plus(particle_symmetry, spin_symmetry) - Sm = S_min(particle_symmetry, spin_symmetry) - Spm = S_plus_S_min(particle_symmetry, spin_symmetry) - Smp = S_min_S_plus(particle_symmetry, spin_symmetry) - @test Spm ≈ Sp ⊗ Sm - @test transform_slave_fermion(Spm) ≈ transform_slave_fermion(Sp) ⊗ transform_slave_fermion(Sm) - @test Smp ≈ Sm ⊗ Sp - @test transform_slave_fermion(Smp) ≈ transform_slave_fermion(Sm) ⊗ transform_slave_fermion(Sp) - - # commutation relations - for i in 1:3, j in 1:3 - @test Svec[i] * Svec[j] - Svec[j] * Svec[i] ≈ - sum(im * ε[i, j, k] * Svec[k] for k in 1:3) + # test triplet operators + if particle_symmetry == Trivial && spin_symmetry == Trivial + umum = u_min_u_min(particle_symmetry, spin_symmetry; slave_fermion) + dmdm = d_min_d_min(particle_symmetry, spin_symmetry; slave_fermion) + upup = u_plus_u_plus(particle_symmetry, spin_symmetry; slave_fermion) + dpdp = d_plus_d_plus(particle_symmetry, spin_symmetry; slave_fermion) + @test swap_2sites(umum) ≈ -umum + @test swap_2sites(dmdm) ≈ -dmdm + @test swap_2sites(upup) ≈ -upup + @test swap_2sites(dpdp) ≈ -dpdp + else + @test_throws ArgumentError u_min_u_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError d_min_d_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError u_plus_u_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError d_plus_d_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) end - for i in 1:3, j in 1:3 - @test Svec_sf[i] * Svec_sf[j] - Svec_sf[j] * Svec_sf[i] ≈ - sum(im * ε[i, j, k] * Svec_sf[k] for k in 1:3) + + # test spin operator + if spin_symmetry == Trivial + ε = zeros(ComplexF64, 3, 3, 3) + for i in 1:3 + ε[mod1(i, 3), mod1(i + 1, 3), mod1(i + 2, 3)] = 1 + ε[mod1(i, 3), mod1(i - 1, 3), mod1(i - 2, 3)] = -1 + end + Svec = [ + S_x(particle_symmetry, spin_symmetry; slave_fermion), + S_y(particle_symmetry, spin_symmetry; slave_fermion), + S_z(particle_symmetry, spin_symmetry; slave_fermion), + ] + # Hermiticity + for s in Svec + @test s' ≈ s + end + # operators should be normalized + S = 1 / 2 + @test sum(tr(Svec[i]^2) for i in 1:3) / (2S + 1) ≈ S * (S + 1) + # test S_plus and S_min + @test S_plus_S_min(particle_symmetry, spin_symmetry; slave_fermion) ≈ + S_plus(particle_symmetry, spin_symmetry; slave_fermion) ⊗ + S_min(particle_symmetry, spin_symmetry; slave_fermion) + @test S_min_S_plus(particle_symmetry, spin_symmetry; slave_fermion) ≈ + S_min(particle_symmetry, spin_symmetry; slave_fermion) ⊗ + S_plus(particle_symmetry, spin_symmetry; slave_fermion) + # commutation relations + for i in 1:3, j in 1:3 + @test Svec[i] * Svec[j] - Svec[j] * Svec[i] ≈ + sum(im * ε[i, j, k] * Svec[k] for k in 1:3) + end + else + @test_throws ArgumentError S_plus( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError S_min( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError S_x( + particle_symmetry, spin_symmetry; + slave_fermion + ) + @test_throws ArgumentError S_y( + particle_symmetry, spin_symmetry; + slave_fermion + ) + if spin_symmetry != U1Irrep + @test_throws ArgumentError S_z( + particle_symmetry, spin_symmetry; + slave_fermion + ) + end end else - @test_throws ArgumentError S_plus(particle_symmetry, spin_symmetry) - @test_throws ArgumentError S_min(particle_symmetry, spin_symmetry) - @test_throws ArgumentError S_x(particle_symmetry, spin_symmetry) - @test_throws ArgumentError S_y(particle_symmetry, spin_symmetry) - spin_symmetry == U1Irrep || - @test_throws ArgumentError S_z(particle_symmetry, spin_symmetry) + @test_broken d_plus_d_min(particle_symmetry, spin_symmetry; slave_fermion) + @test_broken d_min_d_plus(particle_symmetry, spin_symmetry; slave_fermion) + @test_broken u_plus_u_min(particle_symmetry, spin_symmetry; slave_fermion) + @test_broken u_min_u_plus(particle_symmetry, spin_symmetry; slave_fermion) + @test_broken e_num(particle_symmetry, spin_symmetry; slave_fermion) + @test_broken u_num(particle_symmetry, spin_symmetry; slave_fermion) + @test_broken d_num(particle_symmetry, spin_symmetry; slave_fermion) end - else - @test_broken d_plus_d_min(particle_symmetry, spin_symmetry) - @test_broken d_min_d_plus(particle_symmetry, spin_symmetry) - @test_broken u_plus_u_min(particle_symmetry, spin_symmetry) - @test_broken u_min_u_plus(particle_symmetry, spin_symmetry) - @test_broken e_num(particle_symmetry, spin_symmetry) - @test_broken u_num(particle_symmetry, spin_symmetry) - @test_broken d_num(particle_symmetry, spin_symmetry) end end end function tjhamiltonian(particle_symmetry, spin_symmetry; t, J, mu, L, slave_fermion) - num = e_num(particle_symmetry, spin_symmetry) - hop_heis = (-t) * e_hopping(particle_symmetry, spin_symmetry) + J * (S_exchange(particle_symmetry, spin_symmetry) - (1 / 4) * (num ⊗ num)) + num = e_num(particle_symmetry, spin_symmetry; slave_fermion) + hop_heis = (-t) * ( + e_plus_e_min(particle_symmetry, spin_symmetry; slave_fermion) - + e_min_e_plus(particle_symmetry, spin_symmetry; slave_fermion) + ) + J * (S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - (1 / 4) * (num ⊗ num)) chemical_potential = (-mu) * num - I = id(tj_space(particle_symmetry, spin_symmetry)) - - if slave_fermion - hop_heis, chemical_potential, I = transform_slave_fermion.((hop_heis, chemical_potential, I)) - end + I = id(tj_space(particle_symmetry, spin_symmetry; slave_fermion)) H = sum(1:(L - 1)) do i return reduce(⊗, insert!(collect(Any, fill(I, L - 2)), i, hop_heis)) end + sum(1:L) do i @@ -283,22 +312,27 @@ end for particle_symmetry in [Trivial, U1Irrep], spin_symmetry in [Trivial, U1Irrep, SU2Irrep] - if (particle_symmetry, spin_symmetry) in implemented_symmetries - t, J = rand(rng, 2) - num = e_num(particle_symmetry, spin_symmetry) - H = (-t) * e_hopping(particle_symmetry, spin_symmetry) + - J * ( - S_exchange(particle_symmetry, spin_symmetry) - - (1 / 4) * (num ⊗ num) - ) + for slave_fermion in (false, true) + if (particle_symmetry, spin_symmetry) in implemented_symmetries + t, J = rand(rng, 2) + num = e_num(particle_symmetry, spin_symmetry; slave_fermion) + H = (-t) * + ( + e_plus_e_min(particle_symmetry, spin_symmetry; slave_fermion) - + e_min_e_plus(particle_symmetry, spin_symmetry; slave_fermion) + ) + + J * + ( + S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - + (1 / 4) * (num ⊗ num) + ) - true_eigenvals = sort( - vcat([-J], repeat([-t], 2), repeat([t], 2), repeat([0.0], 4)) - ) - eigenvals = expanded_eigenvalues(H; L) - @test eigenvals ≈ true_eigenvals - eigenvals = expanded_eigenvalues(transform_slave_fermion(H); L) - @test eigenvals ≈ true_eigenvals + true_eigenvals = sort( + vcat([-J], repeat([-t], 2), repeat([t], 2), repeat([0.0], 4)) + ) + eigenvals = expanded_eigenvalues(H; L) + @test eigenvals ≈ true_eigenvals + end end end end From 2abd17b951d0b64b4510bb027cd77adddac7f9b4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 19 Oct 2025 15:16:58 +0800 Subject: [PATCH 13/22] Fix compatibility with Julia 1.10 --- src/tjoperators.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index a42c5cb..4eb4d7e 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -92,8 +92,13 @@ for (opname, alias) in zip( ) # copy over the docstrings @eval begin - hub_doc = (@doc HubbardOperators.$opname).text[1] - tJ_doc = replace(hub_doc, "[spin_symmetry::Type{<:Sector}])" => "[spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false)") * "Use `slave_fermion = true` to switch to the slave-fermion basis.\n" + hub_doc = (@doc HubbardOperators.$opname) + tJ_doc = if hub_doc isa Base.Docs.DocStr + hub_doc.text[1] + else + string(hub_doc) + end + tJ_doc = replace(tJ_doc, "[spin_symmetry::Type{<:Sector}])" => "[spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false)") * "Use `slave_fermion = true` to switch to the slave-fermion basis.\n" @doc (tJ_doc) $opname end From 21a2b01f594acb628d556a182bed69c458410a94 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 19 Oct 2025 15:33:32 +0800 Subject: [PATCH 14/22] Fix tj_space --- src/tjoperators.jl | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 4eb4d7e..62daaca 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -30,26 +30,41 @@ export S⁻S⁺, S⁺S⁻ export transform_slave_fermion @doc """ - tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) + tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) Return the local hilbert space for a t-J-type model with the given particle and spin symmetries. The basis consists of ``|0⟩``, ``|↑⟩ = u⁺|0⟩`` and ``|↓⟩ = d⁺|0⟩``. +When `slave_fermion` is `true`, the basis consists of ``h⁺|0⟩``, ``bꜛ⁺|0⟩`` and ``bꜜ⁺|0⟩``, +where ``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. The possible symmetries are: - Particle number : `Trivial`, `U1Irrep` - Spin : `Trivial`, `U1Irrep`, `SU2Irrep`. """ tj_space -tj_space(::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial) = +function tj_space( + particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; + slave_fermion::Bool = false + ) + V = _tj_space(particle_symmetry, spin_symmetry) + if slave_fermion + charge = slave_fermion_auxiliary_charge(sectortype(V)) + V_aux = spacetype(V)(charge => 1) + V = fuse(V, V_aux) + end + return V +end + +_tj_space(::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial) = Vect[FermionParity](0 => 1, 1 => 2) -tj_space(::Type{Trivial}, ::Type{U1Irrep}) = +_tj_space(::Type{Trivial}, ::Type{U1Irrep}) = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1 // 2) => 1, (1, -1 // 2) => 1) -tj_space(::Type{Trivial}, ::Type{SU2Irrep}) = +_tj_space(::Type{Trivial}, ::Type{SU2Irrep}) = Vect[FermionParity ⊠ SU2Irrep]((0, 0) => 1, (1, 1 // 2) => 1) -tj_space(::Type{U1Irrep}, ::Type{Trivial}) = +_tj_space(::Type{U1Irrep}, ::Type{Trivial}) = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2) -tj_space(::Type{U1Irrep}, ::Type{U1Irrep}) = +_tj_space(::Type{U1Irrep}, ::Type{U1Irrep}) = Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1, (1, 1, -1 // 2) => 1) -tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}) = +_tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}) = Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1) """ @@ -96,6 +111,7 @@ for (opname, alias) in zip( tJ_doc = if hub_doc isa Base.Docs.DocStr hub_doc.text[1] else + # compatibility with Julia 1.10 (hub_doc is a Markdown.MD object) string(hub_doc) end tJ_doc = replace(tJ_doc, "[spin_symmetry::Type{<:Sector}])" => "[spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false)") * "Use `slave_fermion = true` to switch to the slave-fermion basis.\n" From 0e864b87f519ff47e6276d08d1e6027989708715 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 19 Oct 2025 15:47:03 +0800 Subject: [PATCH 15/22] Use Int in fuser --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index f65eb8a..3d03c52 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -14,7 +14,7 @@ function fuse_local_operators(O₁::AbstractTensorMap, O₂::AbstractTensorMap) Vᵢ = space(O₁, i) Wᵢ = space(O₂, i) VWᵢ = fuse(Vᵢ, Wᵢ) - return isomorphism(VWᵢ ← Vᵢ ⊗ Wᵢ) + return isomorphism(Int, VWᵢ ← Vᵢ ⊗ Wᵢ) end O₁₂ = permute( From 397dc2c677865d95c96bdcecf36ff3f4ce95dee3 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 19 Oct 2025 15:48:26 +0800 Subject: [PATCH 16/22] Update docstring --- src/tjoperators.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 62daaca..1327786 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -159,16 +159,15 @@ end """ transform_slave_fermion(O::AbstractTensorMap) -Transform the given operator to the slave-fermion basis. This is a local basis transformation -that transforms the vacuum in order to map the basis states as follows +Transform the given operator to the slave-fermion basis, which is related to the usual t-J basis by | tJ basis | slave-fermion | | -------- | ------------- | -| |0⟩ | h⁺|0′⟩ | -| u⁺|0⟩ | bꜛ⁺|0′⟩ | -| d⁺|0⟩ | bꜜ⁺|0′⟩ | +| |0⟩ | h⁺|0⟩ | +| u⁺|0⟩ | bꜛ⁺|0⟩ | +| d⁺|0⟩ | bꜜ⁺|0⟩ | -where now the holon operators (``h``) are fermionic, and the spinon operators (``bꜛ, bꜜ``) are bosonic. +where ``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. """ function transform_slave_fermion(O::AbstractTensorMap) (N = numin(O)) == numout(O) || throw(ArgumentError("not a valid operator")) From b7477201417d231148de47658763f00b3c5fe67b Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 19 Oct 2025 16:08:31 +0800 Subject: [PATCH 17/22] Undo setting `Int` for fusers --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 3d03c52..f65eb8a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -14,7 +14,7 @@ function fuse_local_operators(O₁::AbstractTensorMap, O₂::AbstractTensorMap) Vᵢ = space(O₁, i) Wᵢ = space(O₂, i) VWᵢ = fuse(Vᵢ, Wᵢ) - return isomorphism(Int, VWᵢ ← Vᵢ ⊗ Wᵢ) + return isomorphism(VWᵢ ← Vᵢ ⊗ Wᵢ) end O₁₂ = permute( From b28ee14143cc44a8c96d276784cb5f4bb6d03df6 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 20 Oct 2025 08:18:48 -0400 Subject: [PATCH 18/22] small reorganization --- src/tjoperators.jl | 58 ++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 1327786..4430a91 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -45,27 +45,32 @@ function tj_space( particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false ) - V = _tj_space(particle_symmetry, spin_symmetry) - if slave_fermion - charge = slave_fermion_auxiliary_charge(sectortype(V)) - V_aux = spacetype(V)(charge => 1) - V = fuse(V, V_aux) + V = if particle_symmetry === Trivial + if spin_symmetry === Trivial + Vect[FermionParity](0 => 1, 1 => 2) + elseif spin_symmetry === U1Irrep + Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1 // 2) => 1, (1, -1 // 2) => 1) + elseif spin_symmetry === SU2Irrep + Vect[FermionParity ⊠ SU2Irrep]((0, 0) => 1, (1, 1 // 2) => 1) + else + throw(ArgumentError("Invalid symmetry")) + end + elseif particle_symmetry === U1Irrep + if spin_symmetry === Trivial + Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2) + elseif spin_symmetry === U1Irrep + Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1, (1, 1, -1 // 2) => 1) + elseif spin_symmetry === SU2Irrep + Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1) + else + throw(ArgumentError("Invalid symmetry")) + end + else + throw(ArgumentError("Invalid symmetry")) end - return V -end -_tj_space(::Type{Trivial} = Trivial, ::Type{Trivial} = Trivial) = - Vect[FermionParity](0 => 1, 1 => 2) -_tj_space(::Type{Trivial}, ::Type{U1Irrep}) = - Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1 // 2) => 1, (1, -1 // 2) => 1) -_tj_space(::Type{Trivial}, ::Type{SU2Irrep}) = - Vect[FermionParity ⊠ SU2Irrep]((0, 0) => 1, (1, 1 // 2) => 1) -_tj_space(::Type{U1Irrep}, ::Type{Trivial}) = - Vect[FermionParity ⊠ U1Irrep]((0, 0) => 1, (1, 1) => 2) -_tj_space(::Type{U1Irrep}, ::Type{U1Irrep}) = - Vect[FermionParity ⊠ U1Irrep ⊠ U1Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1, (1, 1, -1 // 2) => 1) -_tj_space(::Type{U1Irrep}, ::Type{SU2Irrep}) = - Vect[FermionParity ⊠ U1Irrep ⊠ SU2Irrep]((0, 0, 0) => 1, (1, 1, 1 // 2) => 1) + return slave_fermion ? transform_slave_fermion(V) : V +end """ tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) @@ -138,10 +143,7 @@ for (opname, alias) in zip( N = numin(op_H) (N > 1) && (proj = reduce(⊗, ntuple(Returns(proj), N))) op = proj * op_H * proj' - if slave_fermion - op = transform_slave_fermion(op) - end - return op + return slave_fermion ? transform_slave_fermion(op) : op end # define alias @@ -156,8 +158,9 @@ slave_fermion_auxiliary_charge(::Type{ProductSector{T}}) where {T} = I === FermionParity ? FermionParity(1) : one(I) end -""" +@doc """ transform_slave_fermion(O::AbstractTensorMap) + transform_slave_fermion(V::ElementarySpace) Transform the given operator to the slave-fermion basis, which is related to the usual t-J basis by @@ -168,7 +171,7 @@ Transform the given operator to the slave-fermion basis, which is related to the | d⁺|0⟩ | bꜜ⁺|0⟩ | where ``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. -""" +""" transform_slave_fermion function transform_slave_fermion(O::AbstractTensorMap) (N = numin(O)) == numout(O) || throw(ArgumentError("not a valid operator")) aux_charge = slave_fermion_auxiliary_charge(sectortype(O)) @@ -177,5 +180,10 @@ function transform_slave_fermion(O::AbstractTensorMap) return fuse_local_operators(O, aux_operator) end +function transform_slave_fermion(V::ElementarySpace) + charge = slave_fermion_auxiliary_charge(sectortype(V)) + V_aux = spacetype(V)(charge => 1) + return fuse(V, V_aux) +end end From 6c5e2d673d0d5f1625cfadfc1212b4cd0c248ec3 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 20 Oct 2025 08:24:56 -0400 Subject: [PATCH 19/22] docstring improvements --- src/tjoperators.jl | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 4430a91..2c0c318 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -29,13 +29,24 @@ export S⁻S⁺, S⁺S⁻ export transform_slave_fermion +const _docs_basis_table = """ +| label | tJ basis | slave-fermion | +| ----- | -------- | ------------- | +| |0⟩ | |0⟩ | h⁺|0′⟩ | +| |↑⟩ | u⁺|0⟩ | bꜛ⁺|0′⟩ | +| |↓⟩ | d⁺|0⟩ | bꜜ⁺|0′⟩ | +""" + @doc """ tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) Return the local hilbert space for a t-J-type model with the given particle and spin symmetries. -The basis consists of ``|0⟩``, ``|↑⟩ = u⁺|0⟩`` and ``|↓⟩ = d⁺|0⟩``. -When `slave_fermion` is `true`, the basis consists of ``h⁺|0⟩``, ``bꜛ⁺|0⟩`` and ``bꜜ⁺|0⟩``, -where ``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. +The basis consists of the following vectors: + +$_docs_basis_table + +where `u⁺` and `d⁺` denote fermionic spin-up and spin-down creation operators, or when `slave_fermion = true`, +``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. The possible symmetries are: - Particle number : `Trivial`, `U1Irrep` @@ -164,11 +175,7 @@ end Transform the given operator to the slave-fermion basis, which is related to the usual t-J basis by -| tJ basis | slave-fermion | -| -------- | ------------- | -| |0⟩ | h⁺|0⟩ | -| u⁺|0⟩ | bꜛ⁺|0⟩ | -| d⁺|0⟩ | bꜜ⁺|0⟩ | +$_docs_basis_table where ``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. """ transform_slave_fermion From 23d0ac9ea79bf35e7dd899d840e94ee7a28b409d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 20 Oct 2025 10:29:58 -0400 Subject: [PATCH 20/22] change table again --- src/tjoperators.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 2c0c318..60769c9 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -30,11 +30,11 @@ export S⁻S⁺, S⁺S⁻ export transform_slave_fermion const _docs_basis_table = """ -| label | tJ basis | slave-fermion | -| ----- | -------- | ------------- | -| |0⟩ | |0⟩ | h⁺|0′⟩ | -| |↑⟩ | u⁺|0⟩ | bꜛ⁺|0′⟩ | -| |↓⟩ | d⁺|0⟩ | bꜜ⁺|0′⟩ | +| tJ basis | slave-fermion | +| -------- | ------------- | +| |0⟩ | h⁺|0⟩ | +| u⁺|0⟩ | bꜛ⁺|0⟩ | +| d⁺|0⟩ | bꜜ⁺|0⟩ | """ @doc """ From 00b0b258d683a3c10f8ecca7a908aec2eb75e182 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 20 Oct 2025 14:11:41 -0400 Subject: [PATCH 21/22] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ae81ee9..9edb05c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "TensorKitTensors" uuid = "41b62e7d-e9d1-4e23-942c-79a97adf954b" authors = ["QuantumKitHub"] -version = "0.2.1" +version = "0.2.2" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 7964a4615f1d70bcc80dd3897ea0c6f5c9324a7f Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 21 Oct 2025 08:45:55 +0800 Subject: [PATCH 22/22] More docstring improvements --- src/hubbardoperators.jl | 2 +- src/tjoperators.jl | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/hubbardoperators.jl b/src/hubbardoperators.jl index 724a766..389ce73 100644 --- a/src/hubbardoperators.jl +++ b/src/hubbardoperators.jl @@ -323,7 +323,7 @@ const S⁻ = S_min S_x(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) Sˣ(elt::Type{<:Number}, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}) -Return the one-body spin-1/2 x-operator on the electrons (only defined for `Trivial` symmetry). . +Return the one-body spin-1/2 x-operator on the electrons (only defined for `Trivial` symmetry). """ S_x function S_x(P::Type{<:Sector} = Trivial, S::Type{<:Sector} = Trivial) return S_x(ComplexF64, P, S) diff --git a/src/tjoperators.jl b/src/tjoperators.jl index 60769c9..ef2c06a 100644 --- a/src/tjoperators.jl +++ b/src/tjoperators.jl @@ -30,23 +30,26 @@ export S⁻S⁺, S⁺S⁻ export transform_slave_fermion const _docs_basis_table = """ -| tJ basis | slave-fermion | -| -------- | ------------- | -| |0⟩ | h⁺|0⟩ | -| u⁺|0⟩ | bꜛ⁺|0⟩ | -| d⁺|0⟩ | bꜜ⁺|0⟩ | +``` +| label | tJ basis | slave-fermion | +| ----- | -------- | ------------- | +| |0⟩ | |∅⟩ | h⁺|∅⟩ | +| |↑⟩ | u⁺|∅⟩ | bꜛ⁺|∅⟩ | +| |↓⟩ | d⁺|∅⟩ | bꜜ⁺|∅⟩ | +``` """ @doc """ tj_space(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false) Return the local hilbert space for a t-J-type model with the given particle and spin symmetries. -The basis consists of the following vectors: +The basis consists of the following states: $_docs_basis_table -where `u⁺` and `d⁺` denote fermionic spin-up and spin-down creation operators, or when `slave_fermion = true`, -``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. +- `|∅⟩` is the vacuum state; +- `u` and `d` denote fermionic spin-up and spin-down operators; +- in the slave-fermion representation, ``h`` is the fermionic holon operator, and ``bꜛ``, ``bꜜ`` are bosonic spinon operators. The possible symmetries are: - Particle number : `Trivial`, `U1Irrep` @@ -130,7 +133,12 @@ for (opname, alias) in zip( # compatibility with Julia 1.10 (hub_doc is a Markdown.MD object) string(hub_doc) end - tJ_doc = replace(tJ_doc, "[spin_symmetry::Type{<:Sector}])" => "[spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false)") * "Use `slave_fermion = true` to switch to the slave-fermion basis.\n" + tJ_doc = if occursin("[spin_symmetry::Type{<:Sector}])", tJ_doc) + replace(tJ_doc, "[spin_symmetry::Type{<:Sector}])" => "[spin_symmetry::Type{<:Sector}]; slave_fermion::Bool = false)") + else + replace(tJ_doc, "spin_symmetry::Type{<:Sector})" => "spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false)") + end + tJ_doc = tJ_doc * "Use `slave_fermion = true` to switch to the slave-fermion basis.\n" @doc (tJ_doc) $opname end