tenet.models¶
The standard local sites and the named Hamiltonians on them. See the guide, Building a Hamiltonian.
| function | what it builds |
|---|---|
heisenberg |
the XXX chain, U1 or SU2 |
xxz |
the anisotropic chain, U1 |
transverse_field_ising |
the Ising chain in a field, Z2 |
hubbard |
the spinful Hubbard chain, fZ2 |
spinless_tv |
spinless fermions with nearest-neighbour repulsion, fZ2 |
sun_heisenberg |
the SU(\(N\)) fundamental exchange chain |
sun_exchange |
that chain's bond term, on its own |
tenet.models ¶
tenet.models: the standard sites and the named Hamiltonians on them.
An optional layer above the core, imported explicitly (from tenet.models import
spin_half) and re-exported nowhere: tenet.network never imports it, which is
what keeps the driver layer from deciding anything about what a caller's operators
mean. tests/network/test_hygiene.py enforces
that edge.
The sites are spin_half,
spinless_fermion,
spinful_fermion and
hard_core_boson, each returning a
Site -- a physical GradedSpace, a name-to-operator mapping in
the shape MPO.from_arrays calls ops, and the dense
matrices behind them.
The named Hamiltonians are one function per model, each returning an MPO over an open chain and taking the symmetry as a parameter: heisenberg, xxz, transverse_field_ising, hubbard, spinless_tv and sun_heisenberg (whose bond term, sun_exchange, is public too, since measuring it is how a bulk energy density is read). There is no lattice geometry and no parameter sweep: what a function does not name -- a next-nearest-neighbour coupling, a ladder, a chemical potential -- stays the caller's term list, and every one of these models is a short one.
Examples:
>>> from tenet.models import heisenberg, spin_half
>>> from tenet.network import MPO
>>> heisenberg(4).to_dense().shape # the chain in one call
(16, 16)
>>> site = spin_half() # or the site, and the terms are yours
>>> n = 4
>>> blocks = [
... ("Sz Sz", [(i, i + 1) for i in range(n - 1)], [1.0] * (n - 1)),
... ("S+ S-", [(i, i + 1) for i in range(n - 1)], [0.5] * (n - 1)),
... ("S- S+", [(i, i + 1) for i in range(n - 1)], [0.5] * (n - 1)),
... ]
>>> h = MPO.from_arrays(n, site.ops, blocks)
>>> h.to_dense().shape
(16, 16)
The build carries no keyword: a builder hands back the site tensors, and a
finite-range model's MPO bond is narrow enough that
Env.heff2's prepared, symbolic path costs more per sweep than
it returns, so the sites are what the sweep should run on. An ab initio Hamiltonian
writes symbolic=True and keeps the description.
Site
dataclass
¶
Site(
phys: GradedSpace,
ops: Mapping[str, SymmetricTensor],
matrices: Mapping[str, ndarray],
)
One lattice site: its physical space, its term operators, and their matrices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
phys
|
GradedSpace
|
The physical space. Every operator in |
required |
ops
|
Mapping[str, SymmetricTensor]
|
Name to term operator, in whichever of
local_op's two forms the grading admits: rank 3 with a
charge leg where the symmetry is Abelian, rank 2k invariant where it is not
( |
required |
matrices
|
Mapping[str, ndarray]
|
The dense matrix behind each operator, under the same key, plus any matrix
the site knows whose grading admits no entry in |
required |
Examples:
>>> from tenet.models import spin_half
>>> site = spin_half()
>>> site.phys.dim
2
>>> sorted(site.ops)
['S+', 'S-', 'Sz']
>>> site.ops["S+"].ndim # the charge-leg form
3
>>> sorted(site.matrices) # S.S has no rank-3 form, so it is a matrix here
['S+', 'S-', 'S.S', 'Sz']
heisenberg ¶
heisenberg(
n: int,
symmetry: _DualFusionRules = U1,
*,
J: float = 1.0,
spin: float = 0.5,
symbolic: bool = False,
) -> MPO
The XXX chain \(H = J \sum_i \vec{S}_i \cdot \vec{S}_{i+1}\), open boundaries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Chain length. |
required |
symmetry
|
FusionRules
|
|
U1
|
J
|
float
|
The coupling. Positive is antiferromagnetic. Default |
1.0
|
spin
|
float
|
The site spin. Only |
0.5
|
symbolic
|
bool
|
Passed to MPO.from_terms. Default |
False
|
Returns:
| Type | Description |
|---|---|
MPO
|
The Hamiltonian on |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
hubbard ¶
hubbard(
n: int,
*,
t: float = 1.0,
U: float = 4.0,
symbolic: bool = False,
) -> MPO
\(H = -t \sum_{i\sigma} (c^\dagger_{i\sigma} c_{i+1\sigma} + h.c.) + U \sum_i n_{i\uparrow} n_{i\downarrow}\).
On spinful_fermion, graded by fZ2: there is no
Jordan-Wigner operator in the terms, because the string is the braiding an odd MPO
bond pays when it crosses a physical line. Both hopping directions are written out --
\(c^\dagger_i c_{i+1}\) and \(c^\dagger_{i+1} c_i\), spin by spin, up before down -- and
the on-site repulsion uses the site's pre-multiplied n_up n_dn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Chain length. |
required |
t
|
float
|
The hopping. It enters as \(-t\), so |
1.0
|
U
|
float
|
The on-site repulsion, positive for repulsive. Default |
4.0
|
symbolic
|
bool
|
Passed to MPO.from_terms. Default |
False
|
Returns:
| Type | Description |
|---|---|
MPO
|
The Hamiltonian on |
Examples:
spinless_tv ¶
spinless_tv(
n: int,
*,
t: float = 1.0,
V: float = 1.0,
symbolic: bool = False,
) -> MPO
\(H = -t \sum_i (c^\dagger_i c_{i+1} + h.c.) + V \sum_i n_i n_{i+1}\).
On spinless_fermion, graded by fZ2, with the
same convention as hubbard: both hopping directions written
out, and no Jordan-Wigner operator in the terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Chain length. |
required |
t
|
float
|
The hopping, entering as \(-t\). Default |
1.0
|
V
|
float
|
The nearest-neighbour repulsion, positive for repulsive. Default |
1.0
|
symbolic
|
bool
|
Passed to MPO.from_terms. Default |
False
|
Returns:
| Type | Description |
|---|---|
MPO
|
The Hamiltonian on |
Examples:
sun_exchange ¶
sun_exchange(N: int) -> SymmetricTensor
The two-site exchange \(P\) on a pair of SU(\(N\)) fundamentals, as a rank-4 operator.
\(\mathbf{N} \otimes \mathbf{N}\) is the symmetric part (Dynkin label
\((2, 0, \ldots)\)) plus the antisymmetric one (\((0, 1, 0, \ldots)\), the singlet at
\(N = 2\)), and \(P\) is \(+1\) on the first and \(-1\) on the second. That is one block per
coupled sector, so the operator is written with
SymmetricTensor.from_blocks and no
Clebsch-Gordan array is spelled out. The legs are two OUT (ket) then two IN
(bra), the ordering MPO.from_terms and
expectation_2site both read a term through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
N
|
int
|
The number of colours, |
required |
Returns:
| Type | Description |
|---|---|
SymmetricTensor
|
\(P\), on |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
sun_heisenberg ¶
sun_heisenberg(
n: int,
N: int,
*,
J: float = 1.0,
symbolic: bool = False,
) -> MPO
\(H = J \sum_i P_{i,i+1}\) on a chain of SU(\(N\)) fundamentals, open boundaries.
\(P\) is sun_exchange, the permutation of two neighbouring fundamentals, and each bond is one invariant two-site term. At \(N = 2\) it is the spin-1/2 chain up to a constant, \(P = 2\,\vec{S}\cdot\vec{S} + \frac{1}{2}\) per bond.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Chain length. |
required |
N
|
int
|
The number of colours, |
required |
J
|
float
|
The coupling. Positive is antiferromagnetic. Default |
1.0
|
symbolic
|
bool
|
Passed to MPO.from_terms. Default |
False
|
Returns:
| Type | Description |
|---|---|
MPO
|
The Hamiltonian on |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
transverse_field_ising ¶
transverse_field_ising(
n: int,
*,
J: float = 1.0,
g: float = 1.0,
symbolic: bool = False,
) -> MPO
\(H = -J\left(\sum_i \sigma^z_i \sigma^z_{i+1} + g \sum_i \sigma^x_i\right)\), on Z2.
The Pauli matrices, not the spin operators: the critical point is \(g = 1\). The site is graded by the Z2 spin flip, so the physical basis is the \(\sigma^x\) eigenbasis \((\lvert +\rangle, \lvert -\rangle)\) in charge order -- \(\sigma^x\) is the diagonal even operator there and \(\sigma^z\) the off-diagonal odd one, and it is \(\sigma^z\) that the grading forbids alone and admits in pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Chain length. |
required |
J
|
float
|
The coupling, multiplying both terms. Default |
1.0
|
g
|
float
|
The transverse field, in units of |
1.0
|
symbolic
|
bool
|
Passed to MPO.from_terms. Default |
False
|
Returns:
| Type | Description |
|---|---|
MPO
|
The Hamiltonian on |
Examples:
xxz ¶
xxz(
n: int,
*,
Delta: float = 1.0,
J: float = 1.0,
spin: float = 0.5,
symbolic: bool = False,
) -> MPO
\(H = J \sum_i S^x_i S^x_{i+1} + S^y_i S^y_{i+1} + \Delta S^z_i S^z_{i+1}\), on U(1).
The transverse half is written as \(\frac{1}{2}(S^+_i S^-_{i+1} + S^-_i S^+_{i+1})\),
which is what the U(1) grading (charge \(2S^z\)) has operators for: \(S^\pm\) carry charge
\(\mp 2\) each and enter paired across a bond. Delta = 1 is
heisenberg under the same grading, term for term.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Chain length. |
required |
Delta
|
float
|
The Ising anisotropy of the \(S^z S^z\) term. Default |
1.0
|
J
|
float
|
The overall coupling. Positive is antiferromagnetic. Default |
1.0
|
spin
|
float
|
The site spin. Only |
0.5
|
symbolic
|
bool
|
Passed to MPO.from_terms. Default |
False
|
Returns:
| Type | Description |
|---|---|
MPO
|
The Hamiltonian on |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
hard_core_boson ¶
The hard-core boson site, {|0>, |1>}, graded by U1 (the number) or ungraded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symmetry
|
FusionRules
|
|
U1
|
Returns:
| Type | Description |
|---|---|
Site
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from tenet.models import hard_core_boson
>>> from tenet.symmetry import Trivial
>>> sorted(hard_core_boson().ops)
['b', 'b+', 'n']
>>> hard_core_boson(Trivial).phys.dim
2
Notes
The matrices are the spin-1/2 ladder in disguise (b = S^-, n = S^z + 1/2);
the site exists separately because the grading is the difference that matters at a
call site -- U1 here counts particles from 0, where spin_half counts
2 S^z from -1, and the two are not interchangeable in a term list. Under
Trivial nothing is conserved, so every operator still arrives rank 3 on a
D=1 trivial leg and MPO.from_terms works unchanged.
spin_half ¶
The spin-1/2 site, graded by U1 (2 S^z) or by SU2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symmetry
|
FusionRules
|
|
U1
|
Returns:
| Type | Description |
|---|---|
Site
|
Under |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from tenet.models import spin_half
>>> from tenet.symmetry import SU2
>>> sorted(spin_half().ops)
['S+', 'S-', 'Sz']
>>> sorted(spin_half(SU2).ops)
['S.S']
>>> spin_half(SU2).ops["S.S"].ndim # rank 2k, k = 2: one whole term
4
Notes
Under SU(2) the set is {S.S} and that is the whole answer. S+ is not an
SU(2) operator and it is not omitted by preference: the charge-leg form needs a
D=1 sector on the emitted leg, and the only leg a spin-1 tensor operator could
emit onto is the j=1 multiplet, whose dense dimension is 3 -- so
local_op(sz, phys=phys, charge=SU2Sector(2)) raises on the shape, and no
irreducible tensor operator of nonzero rank exists in this API to hand back. What
exists is the invariant k-site form, and S.S is it: one whole Heisenberg
bond term, whose coupling lives inside its own blocks, which
MPO.from_terms splits with an SVD. The same object
is invariant under U1 too, where S.S is what
expectation_2site measures a bond energy with;
it sits in matrices there rather than in ops, because a U1 term list is
written from the rank-3 three and a rank-4 entry in ops would be one
from_arrays refuses.
spinful_fermion ¶
The spinful fermion site, graded by fZ2 or by fZ2 x U1 x SU2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symmetry
|
FusionRules
|
|
fZ2
|
Returns:
| Type | Description |
|---|---|
Site
|
Under |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from tenet.models import spinful_fermion
>>> from tenet.symmetry import SU2, U1, ProductProvider, fZ2
>>> site = spinful_fermion()
>>> site.phys.dim
4
>>> site.matrices["n"].diagonal().tolist()
[0.0, 2.0, 1.0, 1.0]
>>> su2 = spinful_fermion(ProductProvider((fZ2, U1, SU2)))
>>> su2.phys.dim, su2.phys.reduced_dim # four states, three multiplets
(4, 3)
>>> sorted(su2.ops)
['S.S', 'hop', 'n', 'n_up n_dn']
>>> su2.ops["hop"].ndim # rank 2k, k = 2: one whole bond
4
Notes
The basis is graded -- the even sector {|0>, |ud>} before the odd
{|u>, |d>} -- because a dense array over a GradedSpace is laid out sector by
sector in canonical order. Modes run up before down, |ud> = c+_up c+_dn |0>, so
c_up carries no intra-site sign and c_dn pays the Jordan-Wigner Z on the
up mode (c_dn |ud> = -|u>). Inter-site strings are the braiding's business, not
a matrix's, and this convention is pinned against a dense oracle.
fZ2 x U1 x SU2 splits the same four states into (even, q=0, j=0),
(even, q=2, j=0) and the doublet (odd, q=1, j=1/2), in that canonical order,
so it is the same even-before-odd dense basis and every matrix above is still the
matrix of the same operator. What changes is which of them is a tensor: the set is
{n, n_up n_dn, hop, S.S} and that is the whole answer. c_up and its five
relatives are not omitted by preference -- none of them is an SU(2)-invariant
tensor, so the invariant form refuses them, and the charge-leg form cannot carry
them either, for the reason spin_half has no S+: that
form puts the emitted sector on a D=1 dense leg, and the sector c_up emits
is the j = 1/2 doublet, of dense dimension 2. n_up and n_dn are
invariant under neither, individually; only their sum and their product are.
What is left is enough for the Hubbard model, because the pieces that are not
invariant one-site operators are invariant bond operators: hop is the whole
hopping term sum_sigma (c+_{i,sigma} c_{j,sigma} + c+_{j,sigma} c_{i,sigma}) on
one bond, the analogue of spin_half(SU2)'s S.S, and
MPO.from_terms splits it across that bond with an
SVD. n and n_up n_dn arrive in the invariant one-site form, rank 2, which
is both what expectation_1site measures with and
what a one-site term is: a U term goes into a term list on its own site, and
the charge-leg form is not needed for it.
spinless_fermion ¶
spinless_fermion() -> Site
The spinless fermion site on fZ2: {|0>, |1>}, even sector first.
Returns:
| Type | Description |
|---|---|
Site
|
|
Examples:
>>> from tenet.models import spinless_fermion
>>> site = spinless_fermion()
>>> sorted(site.ops)
['c', 'c+', 'n']
>>> site.matrices["n"].diagonal().tolist()
[0.0, 1.0]
Notes
There is no JW operator to ship and no place to put one: the Jordan-Wigner
string is the fZ2 braiding an odd MPO bond pays when it crosses a physical
line, so a term list over these operators is already the fermionic Hamiltonian.