|
| 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() |
0 commit comments