Fermion Gaussian State (FGS) Simulator¶

This tutorial demonstrates how to use the Fermion Gaussian State (FGS) simulator implemented in tensorcircuit-ng. The FGS simulator allows for efficient simulation of non-interacting fermionic systems, which is particularly useful for studying free fermions on lattices.

Introduction¶

The FGS simulator efficiently handles systems governed by quadratic Hamiltonians of the form:

\[H = \sum_{ij} H_{ij} c_i^\dagger c_j + \frac{1}{2} \sum_{ij} (H_{ij}^{(2)} c_i^\dagger c_j^\dagger + h.c.)\]

Instead of working with the full \(2^N\)-dimensional Hilbert space, the FGS simulator uses the correlation matrix formalism which scales polynomially with system size.

Setup¶

[1]:
import numpy as np
import tensorcircuit as tc

# Set the backend (using numpy for this tutorial)
tc.set_backend("numpy")
tc.set_dtype("complex128")
[1]:
('complex128', 'float64')

Creating an FGS Simulator Instance¶

We can initialize an FGS simulator in several ways:

  1. By specifying occupied sites in a product state

  2. From the groudn state of a given Hamiltonian

  3. Directly with the alpha matrix

[2]:
# Method 1: Initialize with occupied sites
# Create a 4-site system with sites 0 and 2 occupied
sim1 = tc.FGSSimulator(L=4, filled=[0, 2])
print("Initialized FGS with filled sites [0, 2]")

# Method 2: Initialize from a Hamiltonian (ground state)
# Create a simple hopping Hamiltonian
L = 4
hc = np.zeros([2 * L, 2 * L])
# Add hopping terms between neighboring sites
for i in range(L - 1):
    # chi * (c_i^\dagger c_j + h.c.)
    hc[i, i + 1] = 1.0
    hc[i + L + 1, i + L] = -1.0

sim2 = tc.FGSSimulator(L=4, hc=hc)
print("Initialized FGS from Hamiltonian ground states")

# Check the alpha matrix of the first simulator
alpha = sim1.get_alpha()
print(f"Alpha matrix shape: {alpha.shape}")
Initialized FGS with filled sites [0, 2]
Initialized FGS from Hamiltonian ground states
Alpha matrix shape: (8, 4)

Single-particle Green’s Functions¶

We can compute correlation functions, which are related to single-particle Green’s functions:

\[C_{ij} = \langle c_i^\dagger c_j \rangle\]
[3]:
# Get the correlation matrix
cmatrix = sim1.get_cmatrix()
print(f"Correlation matrix shape: {cmatrix.shape}")

# Check occupation numbers (diagonal elements)
print("Occupation numbers:")
for i in range(sim1.L):
    print(f"Site {i}: {1-cmatrix[i, i].real:.3f}")

# Compute off-diagonal correlations
print("\nSelected off-diagonal correlations:")
print(f"<c_0 c_0^†> = {sim1.expectation_2body(0, 0):.3f}")
print(f"<c_0 c_1^†> = {sim1.expectation_2body(0, 1):.3f}")
print(f"<c_2 c_3^†> = {sim1.expectation_2body(2, 3):.3f}")
Correlation matrix shape: (8, 8)
Occupation numbers:
Site 0: 1.000
Site 1: 0.000
Site 2: 1.000
Site 3: 0.000

Selected off-diagonal correlations:
<c_0 c_0^†> = 0.000+0.000j
<c_0 c_1^†> = 0.000+0.000j
<c_2 c_3^†> = 0.000+0.000j

Time Evolution¶

The FGS simulator supports evolution under quadratic Hamiltonians:

  1. Hopping terms: \(\chi c_i^\dagger c_j + h.c.\)

  2. Chemical potential terms: \(\chi c_i^\dagger c_i\)

  3. Superconducting pairing terms: \(\chi c_i^\dagger c_j^\dagger + h.c.\)

Let’s demonstrate hopping:

[4]:
# Create a new simulator
sim = tc.FGSSimulator(L=4, filled=[0])

print("Initial state:")
cmatrix_init = sim.get_cmatrix()
for i in range(sim.L):
    print(f"Site {i} occupation: {1-cmatrix_init[i, i].real:.3f}")

# Apply hopping between sites 0 and 1 with strength 1.0 for time π/2
# This should transfer the fermion from site 0 to site 1
sim.evol_hp(0, 1, np.pi)

print("\nAfter hopping evolution:")
cmatrix_final = sim.get_cmatrix()
for i in range(sim.L):
    print(f"Site {i} occupation: {1-cmatrix_final[i, i].real:.3f}")
Initial state:
Site 0 occupation: 1.000
Site 1 occupation: 0.000
Site 2 occupation: 0.000
Site 3 occupation: 0.000

After hopping evolution:
Site 0 occupation: 0.000
Site 1 occupation: 1.000
Site 2 occupation: 0.000
Site 3 occupation: 0.000

Entanglement Measures¶

The FGS simulator can efficiently compute entanglement measures like von Neumann entropy and Renyi entropy:

[5]:
# Create a simple entangled state
sim_ent = tc.FGSSimulator(L=4, filled=[0, 2])
# Apply a hopping that creates entanglement
sim_ent.evol_hp(0, 1, np.pi / 4)

# Compute entanglement entropy for different subsystems
# Tracing out sites [2, 3] means we look at the entanglement of sites [0, 1]
entropy_01 = sim_ent.entropy([2, 3])
print(f"Entanglement entropy of sites [0,1]: {entropy_01.real:.6f}")

# Tracing out sites [1, 2, 3] means we look at the entanglement of site [0]
entropy_0 = sim_ent.entropy([1, 2, 3])
print(f"Entanglement entropy of site [0]: {entropy_0.real:.6f}")

# Compute Renyi entropy (n=2) for the same subsystems
renyi_01 = sim_ent.renyi_entropy(2, [2, 3])
print(f"Renyi-2 entropy of sites [0,1]: {renyi_01.real:.6f}")

renyi_0 = sim_ent.renyi_entropy(2, [1, 2, 3])
print(f"Renyi-2 entropy of site [0]: {renyi_0.real:.6f}")
Entanglement entropy of sites [0,1]: -0.000000
Entanglement entropy of site [0]: 0.416496
Renyi-2 entropy of sites [0,1]: -0.000000
Renyi-2 entropy of site [0]: 0.287682

Measurements and Post-selection¶

The FGS simulator supports both projective measurements and post-selection:

[10]:
# Create a superposition state
sim_meas = tc.FGSSimulator(L=2, filled=[0])
# Put site 0 in an equal superposition of occupied and unoccupied
# This is a simplified example - in practice, creating such states requires specific evolutions
sim_meas.evol_hp(0, 1, np.pi / 4)

print("Before measurement:")
cmatrix = sim_meas.get_cmatrix()
for i in range(sim_meas.L):
    print(f"Site {i} occupation probability: {1-cmatrix[i, i].real:.3f}")

# Simulate a measurement on site 0 with a random outcome
# In practice, you would use a random number generator
# Here we manually specify the outcome for reproducibility
outcome = sim_meas.cond_measure(0, status=0.1)  # Should likely result in 0 (unoccupied)
print(f"\nMeasurement outcome for site 0: {outcome}")

print("After measurement:")
cmatrix_post = sim_meas.get_cmatrix()
for i in range(sim_meas.L):
    print(f"Site {i} occupation probability: {1-cmatrix_post[i, i].real:.3f}")

# Demonstrate post-selection (conditioning on a specific outcome)
sim_post = tc.FGSSimulator(L=2, filled=[0])
sim_post.evol_hp(0, 1, np.pi / 4)

print("\nBefore post-selection:")
print(f"Site 0 occupation: {1-sim_post.get_cmatrix()[0, 0].real:.3f}")

# Post-select on site 0 being occupied (keep=1)
sim_post.post_select(0, keep=1)
print("After post-selecting site 0 as occupied:")
print(f"Site 0 occupation: {1-sim_post.get_cmatrix()[0, 0].real:.3f}")
Before measurement:
Site 0 occupation probability: 0.854
Site 1 occupation probability: 0.146

Measurement outcome for site 0: 0.0
After measurement:
Site 0 occupation probability: 0.000
Site 1 occupation probability: 1.000

Before post-selection:
Site 0 occupation: 0.854
After post-selecting site 0 as occupied:
Site 0 occupation: 1.000

Advanced Example: Kitaev Chain¶

Let’s simulate a simple Kitaev chain, which includes both hopping and pairing terms:

[11]:
def kitaev_chain(L, mu, Delta, J):
    """
    Create the Hamiltonian matrix for a Kitaev chain

    H = -J Σ (c_i^† c_{i+1} + h.c.) - μ Σ c_i^† c_i + Δ Σ (c_i c_{i+1} + h.c.)
    """
    hc = np.zeros([2 * L, 2 * L], dtype=complex)

    # Chemical potential term
    for i in range(L):
        hc[i, i] = -mu
        hc[i + L, i + L] = mu

    # Hopping terms
    for i in range(L - 1):
        hc[i, i + 1] = -J
        hc[i + L + 1, i + L] = J

    # Pairing terms
    for i in range(L - 1):
        hc[i, i + 1 + L] = Delta
        hc[i + 1, i + L] = -Delta
        hc[i + L + 1, i] = Delta
        hc[i + L, i + 1] = -Delta

    return hc


# Parameters for the Kitaev chain
L = 6
mu = 1.0  # Chemical potential
Delta = 0.5  # Pairing amplitude
J = 1.0  # Hopping amplitude

# Create the Hamiltonian
kitaev_hc = kitaev_chain(L, mu, Delta, J)

# Initialize the ground state of the Kitaev chain
sim_kitaev = tc.FGSSimulator(L=L, hc=kitaev_hc)

print("Kitaev chain ground state:")
cmatrix = sim_kitaev.get_cmatrix()
print("Site occupation numbers:")
for i in range(L):
    print(f"  Site {i}: {1-cmatrix[i, i].real:.4f}")

# Calculate entanglement entropy for half the chain
half_chain_entropy = sim_kitaev.entropy(list(range(L // 2, L)))
print(f"\nEntanglement entropy of half the chain: {half_chain_entropy.real:.6f}")
Kitaev chain ground state:
Site occupation numbers:
  Site 0: 0.8166
  Site 1: 0.8837
  Site 2: 0.8746
  Site 3: 0.8746
  Site 4: 0.8837
  Site 5: 0.8166

Entanglement entropy of half the chain: 0.349326

Conclusion¶

This tutorial demonstrated the main features of the FGS simulator:

  1. Initialization methods

  2. Computation of correlation functions

  3. Time evolution under quadratic Hamiltonians

  4. Entanglement measures

  5. Measurements and post-selection

  6. Application to a physical model (Kitaev chain)

The FGS simulator provides an efficient way to study non-interacting fermionic systems, avoiding the exponential cost of full Hilbert space simulation while still capturing important physical properties like entanglement and correlation functions.