Constrained Hilbert spaces and conserved quantum numbers
The basis states of a Hilbert space can be restricted to a subset of states and organized into sectors with different quantum numbers.
You can apply a constraint either when constructing a space, hilbert_space(f, labels, constraint), when doing tensor products tensor_product((H1, H2, ...); constraint), or afterwards with constrain_space(H, constraint).
We define two common types of constraints for conserved quantum numbers:
- NumberConservation — Conserves a (possibly weighted) particle number
- ParityConservation — Conserves parity
As well as some types to help with constructing custom constraints:
- FilterConstraint — Flexible filtering using a boolean test on states (iterates over all states)
- SectorConstraint — Groups states into sectors using a custom rule (iterates over all states)
- AdditiveConstraint — Generalization of number conservation, which can be used for more complicated additive quantum numbers (can be used by iterating over all states, or by branch pruning)
- BranchConstraint — Efficient generation of states using branch pruning
One can also combine constraints by taking products.
NumberConservation and ParityConservation
NumberConservation(total=missing, subspaces=missing, weights=missing): Conserves a (possibly weighted) particle number.totalcan be a single integer or a collection of allowed values.subspacescan restrict the count to selected subspaces (or modes).weightslets each selected subspace contribute with a different integer weight.
NumberConservation(),NumberConservation(2),NumberConservation(0:1, Hup),NumberConservation(-1:1, [Hl, Hr], [1, -1]).
using FermionicHilbertSpaces
@fermions f
Hf = hilbert_space(f, 1:3, NumberConservation(2)) # Single sector with 2 particles3-dimensional SectorHilbertSpace
Parent: (f[1, 2, 3])
Sectors: [2 (3-dim)]@boson b
Hb = hilbert_space(b, 10)
H = tensor_product(Hf, Hb; constraint = NumberConservation(0:1, [Hf,Hb] , [1,-1])) # the number of fermions in Hf minus the number of bosons in Hb must be 0 or 1
basisstates(H)6-element Vector{FermionicHilbertSpaces.ProductState{Tuple{FockNumber{UInt64}, FermionicHilbertSpaces.BosonicState}}}:
|11⟩⨯|2⟩
|101⟩⨯|2⟩
|011⟩⨯|2⟩
|11⟩⨯|1⟩
|101⟩⨯|1⟩
|011⟩⨯|1⟩ParityConservation(parities=[-1, 1], subspaces=missing): Conserves fermionic parity.- Allowed parities are
-1(odd) and1(even). ParityConservation()keeps both sectors (odd first, then even).ParityConservation([1])orParityConservation(1)keeps only even parity.subspaceslets you enforce parity on a specific part of a tensor-product space.
- Allowed parities are
constrain_space(H, ParityConservation(1, [Hb])) # keep only even parity of the bosonic part3-dimensional SectorHilbertSpace
Parent: SectorHilbertSpace(ConstrainedSpace(ProductSpace(80-dim, 2 factors), 30-dim), 6-dim)
Sectors: [1 (3-dim)]FilterConstraint and SectorConstraint
FilterConstraint(...): A flexible filtering constraint.FilterConstraint(reducer)appliesreducer(state)and keeps states where it returnstrue.FilterConstraint(subspaces, subspace_functions, reducer)first applies the subspace_functions to the states in each subspace, then passes them to thereducerfunction.
Use this for custom selection rules that are easiest to express as a boolean test on complete states.
SectorConstraint(...): LikeFilterConstraint, but for grouping states into sectors.- The reducer should return a sector label/key (or
missingto discard a state). - States with the same returned key are collected into the same sector.
Use this when you want an explicit block structure from a custom rule.
- The reducer should return a sector label/key (or
@bosons b
H = hilbert_space(b, 1:2, 3)
using FermionicHilbertSpaces: FilterConstraint, SectorConstraint, particle_number
constraint = FilterConstraint(issorted, particle_number, factors(H))
basisstates(constrain_space(H, constraint)) # keep only states with sorted particle numbers6-element Vector{FermionicHilbertSpaces.ProductState{Tuple{FermionicHilbertSpaces.BosonicState, FermionicHilbertSpaces.BosonicState}}}:
|0⟩⨯|0⟩
|0⟩⨯|1⟩
|1⟩⨯|1⟩
|0⟩⨯|2⟩
|1⟩⨯|2⟩
|2⟩⨯|2⟩constraint = SectorConstraint(numbers -> issorted(numbers) ? first(numbers) : missing, particle_number, factors(H)) # keep only states with sorted particle numbers, organize them into sectors according to the number of particles in the first mode
constrain_space(H, constraint).qn_to_statesOrderedCollections.OrderedDict{Int64, Vector{FermionicHilbertSpaces.ProductState{Tuple{FermionicHilbertSpaces.BosonicState, FermionicHilbertSpaces.BosonicState}}}} with 3 entries:
0 => [|0⟩⨯|0⟩, |0⟩⨯|1⟩, |0⟩⨯|2⟩]
1 => [|1⟩⨯|1⟩, |1⟩⨯|2⟩]
2 => [|2⟩⨯|2⟩]Product
Constraints can be combined by multiplying them:
Hs = [hilbert_space(f,2k-1:2k) for k in 1:3]
constraint = prod(NumberConservation(0:1, H) for H in Hs) * NumberConservation(2) # each mode can be occupied by at most one fermion, and the total number of fermions must be 2
H = tensor_product(Hs; constraint)12-dimensional SectorHilbertSpace
Parent: (f[1, 2, 3, 4, 5, 6])
Sectors: [[0, 1, 1, 2] (4-dim), [1, 0, 1, 2] (4-dim), [1, 1, 0, 2] (4-dim)]BranchConstraint
BranchConstraint(f): For efficient constrained generation using branch pruning.f(partial_state, depth, spaces) -> Boolis evaluated during state construction.- Returning
falseprunes the branch immediately.
Examples
Spin
This package does not know anything about spin, but one can treat spin just as an extra label as follows:
using FermionicHilbertSpaces
@fermions f
Hup = hilbert_space(f, [(i, :↑) for i in 1:4])
Hdn = hilbert_space(f, [(i, :↓) for i in 1:4])
H = tensor_product(Hup, Hdn)256-dimensional FermionicSpace
Modes: (f[(1, :↑), (2, :↑), (3, :↑), (4, :↑), (1, :↓), (2, :↓), (3, :↓), (4, :↓)])If spin is conserved, one can use
Hsectors = constrain_space(H, NumberConservation(Hup)*NumberConservation(Hdn))256-dimensional SectorHilbertSpace
Parent: (f[(1, :↑), (2, :↑), ..., (3, :↓), (4, :↓)])
Sectors: 25 total [[0, 0] (1-dim), ..., [4, 4] (1-dim)]to sort states according to the number of fermions with spin up and down. However, this package can't help to sort states into sectors with different total angular momentum, because that requires taking superpositions of different fock states.
To pick out the sector with 2 fermions with spin up and 0 fermions with spin down, one can extract it from the hilbert space defined above using sector as
sector([2,0], Hsectors)6-dimensional ConstrainedSpace
Parent: (f[(1, :↑), (2, :↑), ..., (3, :↓), (4, :↓)])or more efficiently by only constructing that specific sector in the first place
constrain_space(H, NumberConservation(2, Hup)*NumberConservation(0, Hdn))6-dimensional SectorHilbertSpace
Parent: (f[(1, :↑), (2, :↑), ..., (3, :↓), (4, :↓)])
Sectors: [[2, 0] (6-dim)]Hubbard model
For N fermions, the full hilbert space is exponentially large in N. However, due to conservation laws, we may be interested in only a small subspace. If you use a quantum number which consists of only products of number conservations, this package attempts to find the subspaces without enumerating the full hilbert space.
As an example, consider the Hubbard model on N sites
\[H = -t \sum_{i,\sigma} (c_{i,\sigma}^\dagger c_{i+1,\sigma} + \mathrm{h.c}) + U \sum_i n_{i,↑} n_{i,↓}.\]
which conserves the number of spin up and spin down fermions separately. Let's define a function to get the hamiltonian with symbolic fermions
using FermionicHilbertSpaces
function hubbard_hamiltonian(c, N, t, U)
spins = (:↑,:↓)
sum(-t * c[i,σ]' * c[i+1,σ] + hc for σ in spins for i in 1:N-1) + sum(U * c[i,:↑]'c[i,:↑] * c[i,:↓]'c[i,:↓] for i in 1:N)
endhubbard_hamiltonian (generic function with 1 method)Let's find the matrix representation of the hamiltonian in the sector with N_up spin up fermions and N_down spin down fermions. To find this subspace we do
@fermions f
N = 20
Nup = 2
Ndn = 1
Hup = hilbert_space(f, [(i, :↑) for i in 1:N], NumberConservation(Nup))
Hdn = hilbert_space(f, [(i, :↓) for i in 1:N], NumberConservation(Ndn))
H = tensor_product(Hup, Hdn)3800-dimensional SectorHilbertSpace
Parent: ConstrainedSpace((f[(1, :↑), (2, :↑), ..., (19, :↓), (20, :↓)]), 3800-dim)
Sectors: [[2, 1] (3800-dim)]The full hilbert space is of size 4^20 ≈ 10^12, but the sector with 2 spin up and 1 spin down fermion is only of size 3800 and is generated without constructing the full hilbert space. Finally, we can get the matrix representation of the hamiltonian in this sector as
symham = hubbard_hamiltonian(f, N, 1.0, 4.0)
ham = matrix_representation(symham, H)3800×3800 SparseArrays.SparseMatrixCSC{Float64, Int64} with 21280 stored entries:
⎡⠻⣦⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢮⠻⣦⎦No double occupation
When the onsite Coulomb interaction is very strong, there is a large energy penalty for double occupation of a site. In that case, we can restrict the Hilbert space to not allow double occupation of any site. Consider the site k, which has two labels (k, :↑) and (k, :↓). We can use number_conservation(0:1, label -> label[1] == k) which says that the sum of occupation numbers of all labels where the first element of the label equals k is contained in the set 0:1. To impose this for all sites, we take the product over all sites.
no_double_occ = prod(NumberConservation(0:1, [f[(k, :↑)], f[(k, :↓)]]) for k in 1:N)
H_ndo = constrain_space(H, no_double_occ)3420-dimensional SectorHilbertSpace
Parent: SectorHilbertSpace(ConstrainedSpace((f[(1, :↑), (2, :↑), ..., (19, :↓), (20, :↓)]), 3800-dim), 3800-dim)
Sectors: 1140 total [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1] (3-dim), ..., [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (3-dim)]This quantum number is a product of number conservations, so the sector is constructed without enumerating the full Hilbert space.
The matrix representation of the hamiltonian in this sector can be constructed as before, but now we need to specify projection = true as the symbolic hamiltonian maps states in the subspace to states outside the subspace. The keyword projection = true says to ignore those terms.
symham = hubbard_hamiltonian(f, N, 1, 0)
ham_ndo = matrix_representation(symham, H_ndo; projection = true)3420×3420 SparseArrays.SparseMatrixCSC{Int64, Int64} with 17442 stored entries:
⎡⢻⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⠀⠙⢿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠻⡿⣯⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠙⢮⡻⣮⡳⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠙⢮⠻⣦⡙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠈⠳⣌⠻⣦⡉⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢧⡈⠻⣦⡈⠓⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠈⠻⣦⡀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠈⠻⣦⡀⠉⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⡄⠈⠻⣦⡀⠈⠳⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠈⠻⣦⡀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠀⠈⠻⣦⡀⠀⠙⢦⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠈⠻⣦⡀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠈⠻⣦⡄⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⢦⡀⠀⠉⠻⣦⡀⠀⠈⠓⢦⡀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠈⠻⣦⡀⠀⠀⠙⢦⡀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⠀⠀⠈⢿⣷⡀⠀⠀⠙⢦⡀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠈⠻⣦⡀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠈⠻⣦⣄⠀⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠀⠀⠀⠙⢿⣷⎦Fractionalized hilbert space with BranchConstraint
The $t-J_z$ model is
\[H = -t \sum_{i,\sigma} (c_{i,\sigma}^\dagger c_{i+1,\sigma} + \mathrm{h.c}) + J_z \sum_{i} S^z_i S^z_{i+1},\]
where $S^z_i = n_{i,↑} - n_{i,↓}$ and double occupation is forbidden. This model features a fractionalized hilbert space where the Hilbert space splits into exponentially many dynamically disconnected sectors, see [1910.06341]. We implement the hamiltonian as
function tjz(c, N,t,Jz)
spins = (:↑,:↓)
Sz(i) = c[i,:↑]'c[i,:↑] - c[i,:↓]'c[i,:↓]
-t*sum(c[i,σ]'c[i+1,σ] + hc for σ in spins for i in 1:N-1) + Jz*sum(Sz(i)Sz(i+1) for i in 1:N-1)
endtjz (generic function with 1 method)To construct the hilbert space in a specific sector, we need to go beyond simple particle number constraints. Each sector is defined by a spin ordering, e.g. '[:↑, :↑, :↓, :↑, :↓]'. Each state in this sector has three spin up electrons occupied, two spin down electrons occupied, and they come in that specific order with possible holes between them.
This package can help construct these small sectors while avoiding the exponentially large hilbert space, by
function valid_state(partials, depth::Int, spaces, expected_order)
# partials is a list of 2N focknumbers, but only the ones up to 'depth' are assigned.
# spaces is a list of 2N fermionic modes
exc_count = 0
prev_site = 0
for n in 1:depth
if count_ones(partials[n]) == 1
exc_count += 1
site, spin = only(spaces[n].modes).label
exc_count > length(expected_order) && return false # too many excitations
site == prev_site && return false # two excitations on the same site (this uses the fact that we ordered the spaces by site)
prev_site = site
expected_order[exc_count] == spin || return false # check if the spin of the excitation matches the expected order
end
end
depth == length(spaces) && return exc_count == length(expected_order)
return true
end
using FermionicHilbertSpaces: BranchConstraint
spin_order_constraint(order) = BranchConstraint((p,d,s) -> valid_state(p,d,s,order))spin_order_constraint (generic function with 1 method)N = 16
labels = [(i, s) for i in 1:N for s in (:↑,:↓)]
Hfull = hilbert_space(f, labels)4294967296-dimensional FermionicSpace
Modes: 32 total (f[(1, :↑), (1, :↓), (2, :↑), ..., (15, :↓), (16, :↑), (16, :↓)])This space is too large to deal with directly, but we can constrain it to the sector with spin order '[:↑, :↑, :↓, :↑, :↓]' as
Hfrac = hilbert_space(f, labels, spin_order_constraint([:↑, :↑, :↓, :↑, :↓]))4368-dimensional ConstrainedSpace
Parent: (f[(1, :↑), (1, :↓), ..., (16, :↑), (16, :↓)])which has only dimension 4368. We can then construct hamiltonian in this sector by
symham = tjz(f, N, 1, 1/4)
ham = matrix_representation(symham, Hfrac; projection = true)4368×4368 SparseArrays.SparseMatrixCSC{Float64, Int64} with 33606 stored entries:
⎡⢻⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⠀⠻⣿⣿⡶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠘⢯⡻⣮⣅⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⢥⡙⠿⣧⡈⠳⠄⢠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠙⢦⡈⢿⣷⣤⡀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⣁⠀⠻⠿⣧⡀⠀⠙⢦⡀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠈⢻⣶⣄⡀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠹⣿⣿⣦⡀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠈⠻⡿⣯⡀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠈⠻⣦⣄⠀⠀⠀⠉⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠙⣿⣿⣦⡀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠈⠻⣿⣿⡲⣄⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠃⠀⠀⠀⠘⢮⡻⣮⡓⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠀⠀⠀⠀⠙⠈⠻⣦⡀⠀⠀⠀⠀⠀⠀⠙⠆⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠈⢿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠻⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠈⢻⡻⣮⡳⣄⣀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠙⢮⠻⣦⡈⠳⣄⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠘⢦⡈⠻⣦⡈⠁⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠆⠈⢿⣷⎦We can use subregion to find the hilbert space of a subsystem, taking into account the constraint. The full hilbert space of the left half of the system is
Hsub_full = hilbert_space(f, [(i, s) for i in 1:div(N,2) for s in (:↑,:↓)])65536-dimensional FermionicSpace
Modes: 16 total (f[(1, :↑), (1, :↓), (2, :↑), ..., (7, :↓), (8, :↑), (8, :↓)])but taking into account the constraint, we have
Hsub_frac = subregion(Hsub_full, Hfrac)219-dimensional ConstrainedSpace
Parent: (f[(1, :↑), (1, :↓), ..., (8, :↑), (8, :↓)])and now one can do partial traces partial_trace(mat, Hfrac => Hsub_frac).