Build Operators¶
Physical operators in quantum many-body systems must respect symmetries and charge conservation. This page demonstrates how to construct common operators—identity, isometry, and ladder operators—as symmetry-aware tensors.
Key operators:
- Identity: Diagonal operators mapping each state to itself
- Isometry: Fusion tensors that combine multiple indices
- Number operator: Diagonal operator returning charge values
- Ladder operators: Creation and annihilation with auxiliary indices for charge conservation
- Spin operators: \(S_z\) (diagonal z-component)
Operators with auxiliary indices (like creation/annihilation or spin raising/lowering) need an extra index to ensure the total charge is conserved. These auxiliary indices have specific charges that balance the charge transfer between physical states.
Identity Operator¶
group = U1Group()
idx = Index(Direction.OUT, group, sectors=(Sector(0, 2), Sector(1, 1)))
# Create identity operator
I = identity(idx, itags=("i", "j"))
print(f"Identity has {len(I.data)} blocks\n{I}")
Fusion Isometry¶
# Fuse two indices into one
idx1 = Index(Direction.OUT, group, sectors=(Sector(0, 2), Sector(1, 1)))
idx2 = Index(Direction.OUT, group, sectors=(Sector(0, 1), Sector(1, 2)))
# Create fusion tensor
iso = isometry(idx1, idx2, itags=("i", "j", "ij"))
print(f"Isometry indices: {iso.itags}")
print(f"Fused index dim: {iso.indices[2].dim}\n{iso}")
Isometry indices: ('i', 'j', 'ij')
Fused index dim: 9
info: 3x { 4 x 1 } having 'A', Tensor, { i*, j*, ij }
data: 3-D float64 (264 B) 3 x 3 x 9 => 3 x 3 x 9 @ norm = 3
1. 2x1x2 | 1x1x1 [ 0 ; 0 ; 0 ] 32 B
2. 2x2x5 | 1x1x1 [ 0 ; 1 ; 1 ] 160 B
3. 1x1x5 | 1x1x1 [ 1 ; 0 ; 1 ] 40 B
4. 1x2x2 | 1x1x1 [ 1 ; 1 ; 2 ] 32 B
Number Operator¶
# Diagonal operator that returns the charge value
idx_num = Index(Direction.OUT, group, sectors=(Sector(0, 1), Sector(1, 1), Sector(2, 1)))
# Manually construct number operator
N_data = {}
for sector in idx_num.sectors:
charge = sector.charge
dim = sector.dim
# Diagonal matrix with charge values
N_data[(charge, charge)] = torch.eye(dim) * charge
N = Tensor(indices=(idx_num, idx_num.flip()), itags=("out", "in"), data=N_data)
print(f"Number operator:\n{N}")
Ladder Operators¶
# Creation and annihilation operators for bosons
# a†|n⟩ = √(n+1)|n+1⟩
# a|n⟩ = √n|n-1⟩
# Need auxiliary indices to conserve total charge
n_max = 3
idx_ladder = Index(Direction.OUT, group, sectors=tuple(Sector(n, 1) for n in range(n_max + 1)))
# Auxiliary index with charge +1 for creation
idx_aux_plus = Index(Direction.OUT, group, sectors=(Sector(1, 1),))
# Creation operator a†: (out, in, aux+1)
a_dag_data = {}
for n in range(n_max):
# Connects |n⟩ to |n+1⟩, charge conserved: (n+1) + (-n) + (-1) = 0
a_dag_data[(n + 1, n, 1)] = torch.tensor([[[torch.sqrt(torch.tensor(float(n + 1)))]]])
a_dag = Tensor(
indices=(idx_ladder, idx_ladder.flip(), idx_aux_plus.flip()),
itags=("out", "in", "aux"),
data=a_dag_data
)
print(f"Creation operator has {len(a_dag.data)} blocks\n{a_dag}\n")
# Auxiliary index with charge -1 for annihilation
idx_aux_minus = Index(Direction.OUT, group, sectors=(Sector(-1, 1),))
# Annihilation operator a: (out, in, aux-1)
a_data = {}
for n in range(1, n_max + 1):
# Connects |n⟩ to |n-1⟩, charge conserved: (n-1) + (-n) + (1) = 0
a_data[(n - 1, n, -1)] = torch.tensor([[[torch.sqrt(torch.tensor(float(n)))]]])
a = Tensor(
indices=(idx_ladder, idx_ladder.flip(), idx_aux_minus.flip()),
itags=("out", "in", "aux"),
data=a_data
)
print(f"Annihilation operator has {len(a.data)} blocks\n{a}")
Creation operator has 3 blocks
info: 3x { 3 x 1 } having 'A', Tensor, { out*, in, aux }
data: 3-D float64 (12 B) 3 x 3 x 1 => 3 x 3 x 1 @ norm = 2.44949
1. 1x1x1 | 1x1x1 [ 1 ; 0 ; 1 ] 1.
2. 1x1x1 | 1x1x1 [ 2 ; 1 ; 1 ] 1.414
3. 1x1x1 | 1x1x1 [ 3 ; 2 ; 1 ] 1.732
Annihilation operator has 3 blocks
info: 3x { 3 x 1 } having 'A', Tensor, { out*, in, aux }
data: 3-D float64 (12 B) 3 x 3 x 1 => 3 x 3 x 1 @ norm = 2.44949
1. 1x1x1 | 1x1x1 [ 0 ; 1 ; -1 ] 1.
2. 1x1x1 | 1x1x1 [ 1 ; 2 ; -1 ] 1.414
3. 1x1x1 | 1x1x1 [ 2 ; 3 ; -1 ] 1.732
Spin Operators¶
# Sz operator for spin-1/2
# |↓⟩ has Sz = -1/2, |↑⟩ has Sz = +1/2
# Using units where 2*Sz is an integer
idx_spin = Index(Direction.OUT, group, sectors=(Sector(-1, 1), Sector(1, 1)))
# Sz is diagonal
Sz_data = {
(-1, -1): torch.tensor([[-0.5]]), # |↓⟩
(1, 1): torch.tensor([[0.5]]), # |↑⟩
}
Sz = Tensor(indices=(idx_spin, idx_spin.flip()), itags=("out", "in"), data=Sz_data)
print(f"Sz operator:\n{Sz}")
See Also¶
- API Reference: identity
- API Reference: isometry
- Next: Load Space