# Examples Three runnable, end-to-end examples, each lifted from real experiments/tests already in the repository rather than written fresh for this page: - [Density-matrix ZNE healing](#density-matrix-zne-healing) — from [`experiments/matrix_healing_zne.py`](https://github.com/tatopenn-cell/Dense-Evolution/blob/main/experiments/matrix_healing_zne.py), the exact script `zne_density_matrix`'s docstring reports measured numbers from. - [MPS for low-entanglement circuits](#mps-for-low-entanglement-circuits) — from `test_mps.py::test_run_circuit_jit_ghz_chain`. - [Differentiable VQE](#differentiable-vqe) — from `test_autodiff.py::TestCircuitToEnergyFn`. ## Density-matrix ZNE healing Zero-noise extrapolation on full density matrices: run a circuit at several *scaled* noise levels, extrapolate back to the zero-noise limit, then project the (generally unphysical) extrapolated result back onto the physical cone of valid density matrices ([Smolin-Gambetta-Smith 2012](https://arxiv.org/abs/1106.5458)). The ideal state (`rho_ideal`) is built once here purely to *grade* the result at the end. It is never fed into the noise ensemble, the extrapolation, or the physical-projection step — feeding it back in anywhere but the final fidelity check would be oracle access, not error mitigation. ```python import numpy as np import jax.numpy as jnp import dense_evolution as de from dense_evolution.registry import NoiseModel from dense_evolution.mitigation import uhlmann_fidelity, zne_density_matrix N_QUBITS = 2 BASE_P = 0.05 SCALES = (1.0, 2.0, 3.0) K_TRAJECTORIES = 200 def bell_state_sv(): sim = de.DenseSVSimulator(N_QUBITS) sim.run_circuit([("h", 0), ("cx", 0, 1)]) return np.asarray(sim.get_statevector()) def noisy_density_matrix(ideal_sv, p, k, rng): dim = len(ideal_sv) rho = np.zeros((dim, dim), dtype=np.complex128) for _ in range(k): sv_noisy = NoiseModel.apply_to_sv(ideal_sv.copy(), N_QUBITS, 'depolarizing', p, rng=rng) rho += np.outer(sv_noisy, sv_noisy.conj()) rho /= k return jnp.asarray(rho, dtype=jnp.complex128) ideal_sv = bell_state_sv() rho_ideal = jnp.asarray(np.outer(ideal_sv, ideal_sv.conj()), dtype=jnp.complex128) rng = np.random.default_rng(0) rho_at_scales = jnp.stack([ noisy_density_matrix(ideal_sv, BASE_P * scale, K_TRAJECTORIES, rng) for scale in SCALES ]) raw_fidelity = uhlmann_fidelity(rho_at_scales[0], rho_ideal) # base-scale, uncorrected corrected = zne_density_matrix(rho_at_scales, SCALES) # Richardson + physical projection corrected_fidelity = uhlmann_fidelity(corrected, rho_ideal) # grading only print(f"raw fidelity: {raw_fidelity:.4f}") print(f"corrected fidelity: {corrected_fidelity:.4f}") ``` Measured on this exact script (5 seeds, 400 trajectories each, 2–5 qubits, 5 noise channels): 96/100 runs improve fidelity, mean delta +0.12. See the [Changelog](changelog.md) and [`dense_evolution.mitigation`](api/mitigation.md) docstrings for the full sweep and the honest negative results (predictive-healing coefficient perturbation was tested and rejected for the matrix case — negligible effect even amplified 100x). Run it yourself: ```bash python experiments/matrix_healing_zne.py ``` ## MPS for low-entanglement circuits `MPSSimulator` keeps a bounded bond dimension (`max_bond`), trading exactness for circuits that stay low-entanglement (GHZ chains, shallow QAOA layers, most NISQ ansätze) at qubit counts a dense statevector could never hold. `run_circuit_jit` fuses the whole circuit into a single `jax.lax.scan`. ```python import numpy as np from dense_evolution import MPSSimulator n = 6 ops = [["h", 0]] + [["cx", q, q + 1] for q in range(n - 1)] # GHZ chain mps = MPSSimulator(n_qubits=n, max_bond=8) mps.run_circuit_jit(ops) prob = np.abs(np.asarray(mps.contract_to_statevector())) ** 2 print(prob[0], prob[2 ** n - 1]) # both ~0.5, everything else ~0 -- the GHZ signature ``` See [`dense_evolution.mps`](api/mps.md) for the truncated-SVD mechanics and the `jax.lax.scan` fusion this relies on. ## Differentiable VQE `circuit_to_energy_fn` turns a parsed circuit into a `(theta, hamiltonian) -> (energy, statevector)` function that is differentiable end-to-end with `jax.grad`/`jax.value_and_grad` — no manual parameter-shift rule needed. This runs a short Adam loop against a random diagonal Hamiltonian. ```python import numpy as np import jax import jax.numpy as jnp import dense_evolution as de VQE_QASM = ( 'OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; creg c[2]; ' 'ry(0.5) q[0]; rx(0.5) q[1]; cx q[0],q[1]; rz(0.2) q[1]; cx q[0],q[1]; ' 'ry(0.5) q[0]; rx(0.5) q[1]; measure q -> c;' ) def random_hamiltonian(n_qubits, seed=7): rng = np.random.default_rng(seed) values = np.sort(rng.uniform(-2.5, 2.5, 2 ** n_qubits)) return jnp.diag(jnp.array(values, dtype=jnp.float64)) circ = de.QASMParser().parse(VQE_QASM) energy_fn, n_params = de.circuit_to_energy_fn(circ, circ.n_qubits) h_matrix = random_hamiltonian(circ.n_qubits) rng = np.random.default_rng(3) theta = rng.uniform(-np.pi, np.pi, n_params) energy_and_grad = jax.jit(jax.value_and_grad(energy_fn, argnums=0, has_aux=True)) m, v = np.zeros(n_params), np.zeros(n_params) lr, beta1, beta2 = 0.1, 0.9, 0.999 for epoch in range(1, 31): (energy, _), grad = energy_and_grad(jnp.asarray(theta), h_matrix) grad = np.asarray(grad) m = beta1 * m + (1 - beta1) * grad v = beta2 * v + (1 - beta2) * (grad ** 2) m_hat, v_hat = m / (1 - beta1 ** epoch), v / (1 - beta2 ** epoch) theta = theta - lr * m_hat / (np.sqrt(v_hat) + 1e-8) print(f"final energy: {float(energy):.4f}") print(f"ground state: {float(jnp.min(jnp.diag(h_matrix))):.4f}") ``` `circuit_to_energy_fn` also accepts an optional `noise=` (`dense_evolution.NoiseSpec`, a JAX pytree) to trace noisy VQE runs natively — composable with `jax.jit`, `jax.grad`, and `jax.vmap` over noise-key batches. See [`dense_evolution.autodiff`](api/autodiff.md).