Skip to content

U(1) Symmetry Examples

U(1) symmetry represents continuous conservation laws such as particle number, total magnetization, or electric charge. In Nicole, U(1) charges are integers that must sum to zero (neutral) across all tensor indices.

Common applications:

  • Particle number conservation: Charges label the number of particles in each state
  • Spin magnetization: Charges represent \(2 \times S_z\) (doubled to keep integers)
  • Electric charge: Charges quantify the total charge in units of elementary charge

U(1) tensors automatically enforce charge conservation: a tensor block connecting states with charges \((q_1, q_2, \ldots)\) exists only if the total charge \(\sum_i d_i \cdot q_i = 0\), where \(d_i\) is the direction sign (\(+1\) for OUT, \(-1\) for IN).

Basic U(1) Tensor

# U(1) group for particle number conservation
group = U1Group()

# Create index with various charges
idx = Index(
    Direction.OUT,
    group,
    sectors=(
        Sector(charge=0, dim=2),   # vacuum-like states
        Sector(charge=1, dim=1),   # one particle
        Sector(charge=2, dim=1),   # two particles
    )
)

T = Tensor.random([idx, idx.flip()], itags=["i", "j"], seed=42)
print(T)
  info:  2x { 3 x 1 }  having 'A',   Tensor,  { i*, j }
  data:  2-D float64 (48 B)    4 x 4 => 4 x 4  @ norm = 1.23835

     1.  2x2     |  1x1     [ 0 ; 0 ]    32 B      
     2.  1x1     |  1x1     [ 1 ; 1 ]   -1.123     
     3.  1x1     |  1x1     [ 2 ; 2 ]  -0.1863     

Charge Operations

# U(1) operations
print(f"Group name: {group.name}")
print(f"Neutral element: {group.neutral}\n")

# Fusion (addition for U1)
charge1, charge2 = 2, 3
fused = group.fuse_unique(charge1, charge2)
print(f"{charge1} + {charge2} = {fused}\n")

# Dual (negation)
dual_charge = group.dual(5)
print(f"Dual of 5: {dual_charge}\n")

# Multiple fusion
result = group.fuse_unique(1, 2, -1, 3)
print(f"1 + 2 + (-1) + 3 = {result}")
Group name: U1
Neutral element: 0

2 + 3 = 5

Dual of 5: -5

1 + 2 + (-1) + 3 = 5

Charge Conservation Verification

# Only blocks with total charge = 0 exist
T_check = Tensor.random([idx, idx.flip(), idx], itags=["a", "b", "c"], seed=7)

print("Checking charge conservation:")
for key in T_check.data.keys():
    # key = (charge_a, charge_b, charge_c)
    # a is OUT, b is IN, c is OUT
    total = key[0] - key[1] + key[2]  # OUT - IN + OUT
    conserved = (total == 0)
    print(f"Block {key}: total = {total}, conserved = {conserved}")
Checking charge conservation:
Block (0, 0, 0): total = 0, conserved = True
Block (0, 1, 1): total = 0, conserved = True
Block (0, 2, 2): total = 0, conserved = True
Block (1, 1, 0): total = 0, conserved = True
Block (1, 2, 1): total = 0, conserved = True
Block (2, 2, 0): total = 0, conserved = True

Particle Number States

# System with 0, 1, or 2 particles
idx_fock = Index(
    Direction.OUT,
    group,
    sectors=(
        Sector(charge=0, dim=1),  # |0⟩ (vacuum)
        Sector(charge=1, dim=2),  # |1⟩ (two different single-particle states)
        Sector(charge=2, dim=1),  # |2⟩ (two particles)
    )
)

# State represented as density matrix
psi = Tensor.random([idx_fock, idx_fock.flip()], itags=["bra", "ket"], seed=11)
print(f"State has {len(psi.data)} charge sectors")

# Operator that conserves particle number
H = Tensor.random([idx_fock, idx_fock.flip()], itags=["out", "in"], seed=22)
print(f"Hamiltonian has {len(H.data)} blocks\n")
print(f"H = \n{H}")
State has 3 charge sectors
Hamiltonian has 3 blocks

H = 

  info:  2x { 3 x 1 }  having 'A',   Tensor,  { out*, in }
  data:  2-D float64 (48 B)    4 x 4 => 4 x 4  @ norm = 3.63515

     1.  1x1     |  1x1     [ 0 ; 0 ]     1.03     
     2.  2x2     |  1x1     [ 1 ; 1 ]    32 B      
     3.  1x1     |  1x1     [ 2 ; 2 ]  -0.7659     

See Also