Skip to content

Commit 3558dae

Browse files
add pauli propogation
1 parent 4f73ec2 commit 3558dae

8 files changed

Lines changed: 896 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
- Add multi controller jax support for distrubuted contraction.
1818

19+
- Add `pauli_propagation` function for efficient approximate simulation of Pauli operators in Heisenberg picture.
20+
1921
### Fixed
2022

2123
- Fix the breaking logic change in jax from dlpack API, dlcapsule -> tensor.

docs/source/api/pauliprop.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
tensorcircuit.pauliprop
2+
================================================================================
3+
.. automodule:: tensorcircuit.pauliprop
4+
:members:
5+
:undoc-members:
6+
:show-inheritance:
7+
:inherited-members:

docs/source/modules.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ tensorcircuit
2121
./api/mps_base.rst
2222
./api/mpscircuit.rst
2323
./api/noisemodel.rst
24+
./api/pauliprop.rst
2425
./api/quantum.rst
2526
./api/quditcircuit.rst
2627
./api/quditgates.rst

examples/pauli_propagation_vqe.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
TFIM VQE with Pauli Propagation
3+
================================
4+
5+
This example demonstrates how to use the Pauli Propagation module to find the
6+
ground state energy of a Transverse Field Ising Model (TFIM) Hamiltonian.
7+
8+
Pauli Propagation tracks the evolution of Pauli strings in the Heisenberg picture,
9+
which is particularly efficient for circuits with limited qubit-interaction locality.
10+
"""
11+
12+
import time
13+
import numpy as np
14+
import jax.numpy as jnp
15+
import optax
16+
import tensorcircuit as tc
17+
18+
# Use JAX backend for automatic differentiation and efficient scanning
19+
tc.set_backend("jax")
20+
21+
22+
def example_tfim_vqe():
23+
# 1. Configuration
24+
N = 12 # Number of qubits
25+
layers = 4 # Number of circuit layers
26+
k = 3 # Local Pauli tracking limit (Pauli string length <= k)
27+
g = 1.0 # Transverse field strength
28+
29+
print(f"TFIM VQE with Pauli Propagation: N={N}, layers={layers}, k={k}, g={g}")
30+
31+
# 2. Define Hamiltonian: H = \sum Z_i Z_{i+1} + g \sum X_i
32+
# We use tc-internal format for efficiency in the engine:
33+
# 0 -> I, 1 -> X, 2 -> Y, 3 -> Z
34+
num_terms = (N - 1) + N
35+
ham_structures = np.zeros((num_terms, N), dtype=int)
36+
ham_weights = np.zeros(num_terms, dtype=np.float32)
37+
38+
idx = 0
39+
# Nearest-neighbor ZZ terms
40+
for i in range(N - 1):
41+
ham_structures[idx, i] = 3 # Z
42+
ham_structures[idx, i + 1] = 3 # Z
43+
ham_weights[idx] = 1.0
44+
idx += 1
45+
46+
# Transverse field X terms
47+
for i in range(N):
48+
ham_structures[idx, i] = 1 # X
49+
ham_weights[idx] = g
50+
idx += 1
51+
52+
# 3. Parameter Initialization
53+
# Each layer has (N-1) Rzz parameters and N Rx parameters
54+
params_per_layer = (N - 1) + N
55+
total_params = layers * params_per_layer
56+
# Using small random initialization
57+
params = np.random.normal(scale=0.1, size=total_params).astype(np.float32)
58+
params = jnp.array(params)
59+
60+
# 4. Define Loss Function
61+
pp_engine = tc.pauliprop.PauliPropagationEngine(N, k)
62+
63+
def loss_fn(p_flat):
64+
# Reshape parameters for efficient scanning over layers
65+
p_layers = jnp.reshape(p_flat, (layers, params_per_layer))
66+
67+
# Define how one layer of the circuit is constructed
68+
def layer_fn(c, p_l):
69+
p_rzz = p_l[: N - 1]
70+
p_rx = p_l[N - 1 :]
71+
72+
# 1. Apply Rx gates
73+
for i in range(N):
74+
c.rx(i, theta=p_rx[i])
75+
76+
# 2. Apply Rzz gates
77+
# Heisenberg ordering is handled automatically by pp_engine
78+
# Even-indexed pairs
79+
for i in range(0, N - 1, 2):
80+
c.rzz(i, i + 1, theta=p_rzz[i // 2])
81+
# Odd-indexed pairs
82+
num_even = len(range(0, N - 1, 2))
83+
for i in range(1, N - 1, 2):
84+
c.rzz(i, i + 1, theta=p_rzz[num_even + (i - 1) // 2])
85+
86+
# Compute expectation by propagating the Hamiltonian back through layers
87+
energy = pp_engine.compute_expectation_scan(
88+
ham_structures, ham_weights, layer_fn, p_layers
89+
)
90+
return jnp.real(energy)
91+
92+
# 5. Optimization Loop
93+
optimizer = optax.adam(learning_rate=0.02)
94+
opt_state = optimizer.init(params)
95+
96+
# JIT compile the value and gradient function for maximum performance
97+
loss_val_grad_fn = tc.backend.jit(tc.backend.value_and_grad(loss_fn))
98+
99+
print("\nStarting Optimization Loop...")
100+
t_start = time.time()
101+
for i in range(201):
102+
# Compute value and gradients
103+
val, grads = loss_val_grad_fn(params)
104+
105+
# Apply updates
106+
updates, opt_state = optimizer.update(grads, opt_state)
107+
params = optax.apply_updates(params, updates)
108+
109+
if i % 10 == 0:
110+
# Sync JAX if measuring time inside the loop is needed
111+
# val.block_until_ready()
112+
print(f"Step {i:3d}: Energy = {val:.6f}")
113+
114+
duration = time.time() - t_start
115+
print(f"\nOptimization finished in {duration:.2f}s")
116+
print(f"Final VQE Energy: {val:.6f}")
117+
118+
119+
if __name__ == "__main__":
120+
example_tfim_vqe()

llm_experience.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,63 @@ This document records specific technical protocols, lessons learned, and advance
7878
1. **JAX Vmap in TensorCircuit Wrapper**:
7979
* When using `tc.backend.vmap` (alias `K.vmap`) with the JAX backend, do **not** use JAX-native arguments like `in_axes`. The TC wrapper unifies behavior and exposes `vectorized_argnums` (like TensorFlow) instead.
8080
* **Protocol**: Always use `vectorized_argnums=(0, 1, ...)` to specify batched arguments, regardless of the backend (JAX/TF/Torch). Passing `in_axes` will raise a `TypeError` because the wrapper function definition doesn't accept it.
81+
82+
## Backend Quirks
83+
84+
1. **Missing Backend APIs**:
85+
* The `tc.backend` (JAX) interface may lack some convenient NumPy/TensorFlow methods widely used in other backends.
86+
* **Missing**: `K.zeros_like`, `K.ones_like`, `K.any`, `K.minimum`, `K.gather`, `K.outer`.
87+
* **Workarounds**:
88+
* `zeros_like(x)` -> `K.zeros(x.shape, dtype=x.dtype)`
89+
* `probs > 0` (any) -> `K.sum(probs) > 0`
90+
* `minimum(a,b)` -> `K.where(a < b, a, b)`
91+
* `gather(x, idx)` -> `x[idx]`
92+
* `outer(a, b)` -> `a[:, None] * b[None, :]` (Broadcasting)
93+
94+
2. **JIT Buffer Management**:
95+
* Updating a fixed-size buffer with a dynamic number of new terms in JAX JIT is challenging due to static shape requirements.
96+
* **Pitfall**: `jax.lax.dynamic_update_slice` requires static slice shapes and can behave unexpectedly if logic implies dynamic sizes.
97+
* **Protocol**: Use **masked updates** (`K.where`). Create a mask for valid insertion indices and map source indices to destination indices using modular arithmetic or standard indexing, masked by the valid region. This maintains static graph shapes.
98+
99+
## Pauli Propagation & Operator Evolution (Heisenberg Picture)
100+
101+
1. **Heisenberg Picture Reverse Order**:
102+
* Pauli Propagation evolves the *observable* rather than the state.
103+
* **Protocol**: Circuit operations must be applied in **reversed** chronological order (from the measurement gate back to the initial layer). This corresponds to applying the adjoint gate to the operator: $O \to U^\dagger O U$.
104+
105+
2. **JAX Tracer Accumulation**:
106+
* When initializing an operator state from a Hamiltonian where weights are JAX Tracers (e.g., in VQE optimization), direct indexing and assignment (`state[idx] = w`) will fail with `TracerArrayConversionError`.
107+
* **Protocol**: Use the `at[].add()` or `at[].set()` functional update syntax for compatibility with JIT and AD.
108+
```python
109+
state = state.at[target_idx, flat_idx].add(w)
110+
```
111+
112+
3. **Real-Valued Expectations for Gradients**:
113+
* JAX gradients of loss functions often require the output to be a real-valued scalar. Even if the physics dictates a real expectation value, numerical complex types (even with zero imaginary part) can trigger `TypeError`.
114+
* **Protocol**: Always explicitly take the real part using `K.real()` or `.real` before returning the expectation value from a loss function or engine method.
115+
116+
117+
## Module Integration Protocols
118+
119+
1. **Exporting and Aliasing**:
120+
* Export new modules in `tensorcircuit/__init__.py`.
121+
* Provide both the module export (e.g. `from . import pauliprop`) and the primary helper function (e.g. `from .pauliprop import pauli_propagation`).
122+
* Use concise aliases for frequently used functions (e.g., `PauliProp = pauli_propagation`).
123+
124+
2. **Backwards Compatibility in Helpers**:
125+
* High-level wrapper functions should support multiple input formats (e.g., both raw arrays and convenient list-of-tuples for observables) to be user-friendly while maintaining internal efficiency.
126+
127+
3. **Standardized Testing Patterns**:
128+
* **Backend Isolation**: Avoid global `tc.set_backend()` in test files. Use the standard fixtures (`npb`, `tfb`, `jaxb`) from `conftest.py` as function arguments.
129+
* **Test Levels**:
130+
* **Unit**: Test initialization, state mapping, and single-gate kernels.
131+
* **Correctness**: Compare results against `tc.Circuit.expectation` or `expectation_ps` for small $N$.
132+
* **AD/Gradients**: Verify that `jax.grad` (or backend equivalent) works on the module's primary interfaces.
133+
* **Scanning**: Verify that loop-optimization interfaces (e.g. `compute_expectation_scan`) match manual application results.
134+
135+
4. **Documentation & Linting**:
136+
* Achieve **10/10 pylint score** and pass `mypy` before finalizing a module.
137+
* Follow Google-style docstrings with reStructuredText markers. This is critical for automated documentation generation.
138+
139+
5. **User Verification (Walkthroughs)**:
140+
* Always provide a production-ready example in `examples/` (e.g., `pauli_propagation_vqe.py`) that showcases a real-world use case (optimization, dynamics, etc.) and demonstrates performance features like JAX JIT and Scanning.

tensorcircuit/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
from . import cloud
5656
from . import fgs
5757
from .fgs import FGSSimulator
58+
from . import pauliprop
59+
from .pauliprop import pauli_propagation
5860
from . import timeevol
5961

6062
FGSCircuit = FGSSimulator

0 commit comments

Comments
 (0)