Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "TensorKitTensors"
uuid = "41b62e7d-e9d1-4e23-942c-79a97adf954b"
version = "0.3.0"
version = "0.3.1"
authors = ["QuantumKitHub"]

[workspace]
Expand Down
24 changes: 17 additions & 7 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@ Pages = Main.operatorpages
## Symmetric operators through basis transformations

Each operator module defines its operators only once, in a non-symmetric reference basis.
The symmetric versions are generated automatically by rotating the reference operator with a
documented unitary basis transformation and projecting the result onto the symmetric tensor
structure. The basis transformations are exposed through each module's `basis_transform`
function (e.g. the Hadamard matrix that maps the ``S^z`` basis onto the ``ℤ₂`` spin-flip
eigenbasis for `SpinOperators`), and the projection is available as [`symmetrize`](@ref) for
symmetrizing custom operators. Operators that are incompatible with a given symmetry throw
an `ArgumentError`.
The symmetric versions are generated automatically by rotating the reference operator with a documented unitary basis transformation and projecting the result onto the symmetric tensor structure.
The basis transformations are exposed through each module's `basis_transform` function, and each module exports `symmetrize_operator` to apply the appropriate transformation and symmetry projection to an operator on its desymmetrized form.
Operators that are incompatible with a given symmetry throw an `ArgumentError`.

### Custom operators

For each operator module, one can define a custom operator in `TensorMap` format in the non-symmetric reference basis, and then impose the symmetries with that module's `symmetrize_operator` function:

```julia
using TensorKit
using TensorKitTensors.SpinOperators
V = spin_space(Trivial)
O = TensorMap([1.0 0.0; 0.0 -1.0], V ← V)
Z = symmetrize_operator(O, U1Irrep)
```

For `TJOperators`, construct the input on `tj_space(Trivial, Trivial; slave_fermion)` in the representation selected by the same `slave_fermion` keyword passed to `symmetrize_operator`.
Comment thread
Yue-Zhengyuan marked this conversation as resolved.
Outdated

```@docs
symmetrize
Expand Down
18 changes: 14 additions & 4 deletions src/bosonoperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using TensorKit
using LinearAlgebra: I
import ..TensorKitTensors: symmetrize, desymmetrize, @operator

export boson_space, basis_transform
export boson_space, basis_transform, symmetrize_operator
export b_plus, b_min, b_num
export b_plus_b_plus, b_plus_b_min, b_min_b_plus, b_min_b_min
export b_hopping
Expand Down Expand Up @@ -58,9 +58,19 @@ function basis_transform(symmetry::Type{<:Sector}; cutoff::Integer)
return TensorMap(Matrix{Int}(I, cutoff + 1, cutoff + 1), V ← boson_space(Trivial; cutoff))
end

# Symmetrize a boson operator through its basis transformation
_symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; kwargs...) =
symmetrize(O, basis_transform(symmetry; kwargs...), boson_space(symmetry; kwargs...))
"""
symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; cutoff::Integer, tol=nothing)

Symmetrize a bosonic operator defined on `boson_space(Trivial; cutoff)` through the basis transformation for `symmetry`.
"""
function symmetrize_operator(
O::AbstractTensorMap, symmetry::Type{<:Sector}; cutoff::Integer,
tol = nothing
Comment thread
Yue-Zhengyuan marked this conversation as resolved.
)
return symmetrize(
O, basis_transform(symmetry; cutoff), boson_space(symmetry; cutoff); tol
)
end

# Single-site operators
# ---------------------
Expand Down
16 changes: 12 additions & 4 deletions src/fermionoperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using TensorKit
using LinearAlgebra: I
import ..TensorKitTensors: symmetrize, desymmetrize, @operator

export fermion_space, basis_transform
export fermion_space, basis_transform, symmetrize_operator
export f_num
export f_plus_f_min, f_min_f_plus, f_plus_f_plus, f_min_f_min
export f_hopping
Expand Down Expand Up @@ -55,9 +55,17 @@ function basis_transform(symmetry::Type{<:Sector})
return TensorMap(Matrix{Int}(I, 2, 2), V ← desymmetrize(fermion_space(Trivial)))
end

# Symmetrize a fermion operator through its basis transformation
_symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}) =
symmetrize(O, basis_transform(symmetry), fermion_space(symmetry))
"""
symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; tol=nothing)

Symmetrize a spinless-fermion operator defined on `fermion_space(Trivial)` through the basis transformation for `symmetry`.
The input space must retain the mandatory fermion-parity grading.
"""
function symmetrize_operator(
O::AbstractTensorMap, symmetry::Type{<:Sector}; tol = nothing
Comment thread
Yue-Zhengyuan marked this conversation as resolved.
)
return symmetrize(O, basis_transform(symmetry), fermion_space(symmetry); tol)
end

# Single-site operators
# ---------------------
Expand Down
26 changes: 13 additions & 13 deletions src/hubbardoperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module HubbardOperators
using TensorKit
import ..TensorKitTensors: symmetrize, desymmetrize, @operator

export hubbard_space, basis_transform
export hubbard_space, basis_transform, symmetrize_operator
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
Expand Down Expand Up @@ -162,33 +162,33 @@ end
# exact (Gaussian) integer entries promote to any scalar type without loss of precision
const _PARTICLE_GAUGE = Complex{Int}[1 0 0 0; 0 -1 0 0; 0 0 im 0; 0 0 0 im]

# Symmetrize a Hubbard operator, inserting the staggered gauge for `SU2Irrep` particle symmetry
# If the staggered gauge commutes we don't incorporate it to retain the option for real tensors.
function _symmetrize_hubbard(
"""
symmetrize_operator(O::AbstractTensorMap, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; tol=nothing)

Symmetrize a Hubbard operator defined on `hubbard_space(Trivial, Trivial)` through the basis transformation for the requested particle and spin symmetries.
The staggered gauge required by `SU2Irrep` particle symmetry is applied automatically.
"""
function symmetrize_operator(
O::AbstractTensorMap, particle_symmetry::Type{<:Sector},
spin_symmetry::Type{<:Sector}
spin_symmetry::Type{<:Sector}; tol = nothing
)
U = basis_transform(particle_symmetry, spin_symmetry)
V = hubbard_space(particle_symmetry, spin_symmetry)
particle_symmetry === SU2Irrep || return symmetrize(O, U, V)
particle_symmetry === SU2Irrep || return symmetrize(O, U, V; tol)

Vref = domain(U)[1]
Gs = ntuple(k -> TensorMap(_PARTICLE_GAUGE^(k - 1), Vref ← Vref), numout(O))
W = reduce(⊗, Gs)
Od = desymmetrize(O)

# check if commutes with the staggered gauge
W * Od ≈ Od * W && return symmetrize(O, U, V)
# Do not incorporate the staggered gauge when it commutes, retaining real tensors.
W * Od ≈ Od * W && return symmetrize(O, U, V; tol)

scalartype(O) <: Real &&
throw(ArgumentError("operator with `SU2Irrep` particle symmetry that does not commute with the staggered gauge requires a complex scalar type"))
return symmetrize(O, map(g -> U * g, Gs), V)
return symmetrize(O, map(g -> U * g, Gs), V; tol)
end

# Symmetrize a Hubbard operator through its (staggered-gauge) basis transformation
_symmetrize_operator(O::AbstractTensorMap, P::Type{<:Sector}, S::Type{<:Sector}) =
_symmetrize_hubbard(O, P, S)

function n_site_operator(::Val{N}, elt::Type{<:Number}) where {N}
V = hubbard_space(Trivial, Trivial)
return zeros(elt, V^N ← V^N)
Expand Down
17 changes: 12 additions & 5 deletions src/quantumgates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using LinearAlgebra: I
using RationalRoots: signedroot
import ..TensorKitTensors: symmetrize, desymmetrize, @operator

export qubit_space, basis_transform, controlled
export qubit_space, basis_transform, symmetrize_operator, controlled
export pauli_x, pauli_y, pauli_z, proj_0, proj_1, hadamard, s_gate, t_gate
export phase_shift, rotation_x, rotation_y, rotation_z, u3
export cnot, cy, cz, cphase, ch, cs, swap, iswap, dcx, ecr
Expand Down Expand Up @@ -56,10 +56,17 @@ function basis_transform(symmetry::Type{<:Sector})
return TensorMap(Matrix{Int}(I, 2, 2), V ← qubit_space(Trivial))
end

# Symmetrize a gate through its basis transformation. The keywords of the parametrized gates
# are accepted and discarded: `qubit_space` and `basis_transform` do not depend on them.
_symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; kwargs...) =
symmetrize(O, basis_transform(symmetry), qubit_space(symmetry))
"""
symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; tol=nothing, kwargs...)

Symmetrize a quantum gate defined on `qubit_space(Trivial)` through the basis transformation for `symmetry`.
Additional keywords used by parametrized gates are accepted but do not affect the transformation.
"""
function symmetrize_operator(
O::AbstractTensorMap, symmetry::Type{<:Sector}; tol = nothing, kwargs...
)
return symmetrize(O, basis_transform(symmetry), qubit_space(symmetry); tol)
end

# Gate combinators
# ----------------
Expand Down
16 changes: 12 additions & 4 deletions src/spinoperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using LinearAlgebra: I
using RationalRoots: signedroot
import ..TensorKitTensors: symmetrize, desymmetrize, @operator

export spin_space, basis_transform, casimir
export spin_space, basis_transform, symmetrize_operator, casimir
export S_x, S_y, S_z, S_plus, S_min
export S_x_S_x, S_y_S_y, S_z_S_z, S_plus_S_min, S_min_S_plus, S_exchange
export σˣ, σʸ, σᶻ, σ⁺, σ⁻
Expand Down Expand Up @@ -85,9 +85,17 @@ function basis_transform(::Type{SU2Irrep}; spin = 1 // 2)
return TensorMap(Matrix{Int}(I, d, d), desymmetrize(spin_space(SU2Irrep; spin)) ← V)
end

# Symmetrize a spin operator through its basis transformation
_symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; kwargs...) =
symmetrize(O, basis_transform(symmetry; kwargs...), spin_space(symmetry; kwargs...))
"""
symmetrize_operator(O::AbstractTensorMap, symmetry::Type{<:Sector}; spin=1 // 2, tol=nothing)

Symmetrize a spin operator defined on `spin_space(Trivial; spin)` through the basis transformation for `symmetry`.
"""
function symmetrize_operator(
O::AbstractTensorMap, symmetry::Type{<:Sector}; spin = 1 // 2, tol = nothing
)
V = spin_space(symmetry; spin)
return symmetrize(O, basis_transform(symmetry; spin), V; tol)
end

# Pauli matrices
# --------------
Expand Down
45 changes: 20 additions & 25 deletions src/tjoperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using TensorKit
import ..HubbardOperators
import ..TensorKitTensors: symmetrize, desymmetrize, fuse_local_operators, @operator

export tj_space, basis_transform, tj_projector
export tj_space, basis_transform, symmetrize_operator, tj_projector
export transform_slave_fermion
# the operator names and their aliases are exported by the generation loop at the bottom of
# this file, from the `_OPERATORS` registry
Expand Down Expand Up @@ -153,16 +153,19 @@ function _state_indices(c, particle_symmetry::Type{<:Sector}, spin_symmetry::Typ
end
end

# Symmetrize a t-J operator through its basis transformation, in the requested basis. Note
# that `slave_fermion` is forwarded from the reference operator, which is already expressed
# in that basis (see `_maybe_slave_fermion`), such that only a permutation remains here.
function _symmetrize_operator(
"""
symmetrize_operator(O::AbstractTensorMap, particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:Sector}; slave_fermion::Bool=false, tol=nothing)

Symmetrize a t-J operator defined on `tj_space(Trivial, Trivial; slave_fermion)` through the basis transformation for the requested particle and spin symmetries.
The input must already use the normal or slave-fermion representation selected by `slave_fermion`.
Comment thread
Yue-Zhengyuan marked this conversation as resolved.
"""
function symmetrize_operator(
O::AbstractTensorMap, particle_symmetry::Type{<:Sector},
spin_symmetry::Type{<:Sector}; kwargs...
spin_symmetry::Type{<:Sector}; slave_fermion::Bool = false, tol = nothing
)
return symmetrize(
O, basis_transform(particle_symmetry, spin_symmetry; kwargs...),
tj_space(particle_symmetry, spin_symmetry; kwargs...)
O, basis_transform(particle_symmetry, spin_symmetry; slave_fermion),
tj_space(particle_symmetry, spin_symmetry; slave_fermion); tol
)
end

Expand Down Expand Up @@ -206,16 +209,13 @@ function tj_projector(particle_symmetry::Type{<:Sector}, spin_symmetry::Type{<:S
return proj
end

# Project a Hubbard reference operator onto the t-J space, and express it in the requested
# basis. The projector is parity-even, so it distributes over `⊗` and projecting the reference
# operator is equivalent to projecting the symmetric one. Since it has integer entries, the
# scalar type of the Hubbard operator is preserved.
function _project_hubbard(O::AbstractTensorMap, slave_fermion::Bool)
Pⁿ = _reference_projector(Val(numout(O)))
return _maybe_slave_fermion(Pⁿ * O * Pⁿ', slave_fermion)
end
function _reference_projector(::Val{N}) where {N}
return reduce(⊗, ntuple(Returns(tj_projector(Trivial, Trivial)), Val(N)))
"Project a Hubbard reference operator onto the normal t-J space."
function _project_hubbard(O::AbstractTensorMap)
# The projector is parity-even, so it distributes over `⊗`
n = numout(O)
Pⁿ = reduce(⊗, ntuple(Returns(tj_projector(Trivial, Trivial)), Val(n)))
# integer entries in `Pⁿ` preserve the scalar type of `O`
return Pⁿ * O * Pⁿ'
end

# Slave-fermion basis
Expand Down Expand Up @@ -256,12 +256,6 @@ function transform_slave_fermion(V::ElementarySpace)
return fuse(V, V_aux)
end

# Express a reference operator in the requested basis. Reference operators are always obtained
# in the plain t-J basis, and transformed only once, at the very end: the slave-fermion
# transformation does not distribute over `⊗`.
_maybe_slave_fermion(O::AbstractTensorMap, slave_fermion::Bool) =
slave_fermion ? transform_slave_fermion(O) : O

# Operators
# ---------

Expand Down Expand Up @@ -345,7 +339,8 @@ for (name, alias) in [
elt::Type{<:Number}, ::Type{Trivial}, ::Type{Trivial};
slave_fermion::Bool = false
)
return _project_hubbard(HubbardOperators.$name(elt, Trivial, Trivial), slave_fermion)
O = _project_hubbard(HubbardOperators.$name(elt, Trivial, Trivial))
return slave_fermion ? transform_slave_fermion(O) : O
end
end

Expand Down
8 changes: 4 additions & 4 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,15 @@ function _operator_defs(alias, def, source)

# symmetric terminal:
# `op(elt::<c>, s₁::Type{<:Sector}, …; kwargs...) =
# _symmetrize_operator(op(elt, Trivial, …; kwargs...), s₁, …; kwargs...)`
# symmetrize_operator(op(elt, Trivial, …; kwargs...), s₁, …; kwargs...)`
tname = gensym(:elt)
tsyms = [gensym(:S) for _ in 1:N]
tsig = call_expr(
fname, Expr(:(::), tname, eltconstraint),
(Expr(:(::), tsyms[i], :(Type{<:Sector})) for i in 1:N)...
)
refcall = call_expr(fname, tname, ntuple(Returns(:Trivial), N)...)
hookcall = call_expr(:_symmetrize_operator, refcall, tsyms...)
hookcall = call_expr(:symmetrize_operator, refcall, tsyms...)
method_terminal = method_def(tsig, hookcall)

(method_allsyms, method_elt, method_terminal)
Expand Down Expand Up @@ -268,11 +268,11 @@ defaulting the unspecified symmetries of a multi-symmetry operator.

If the reference method takes no symmetry arguments, it is itself the terminal: only the
`op()` element-type default is generated (the symmetry-first, elt-only, and symmetric-terminal
methods, including the `_symmetrize_operator` hook, are omitted).
methods, including the `symmetrize_operator` hook, are omitted).

Otherwise, the last generated method, the *symmetric terminal*, delegates to the module-local
hook
`_symmetrize_operator(op(elt, Trivial, …; kwargs...), symmetry₁, …; kwargs...)`, which each
`symmetrize_operator(op(elt, Trivial, …; kwargs...), symmetry₁, …; kwargs...)`, which each
operator module defines once (typically wrapping [`symmetrize`](@ref) with the module's
`basis_transform` and local space). Every generated method simply accepts `kwargs...` and
forwards them verbatim to both the reference call and the hook; the wrapped reference method
Expand Down
Loading