Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hamiltonian simulation

Hamiltonian simulation builds a circuit for exp(−iHt), with H = Σ_γ h_γ P_γ a sum of Pauli strings. QSO does this two ways: product formulas, which come with rigorous error bounds, and qDRIFT, a randomised method. Both use Circuit.pauli_rotation, which appends exp(−iθP/2) for any Pauli string P. One-qubit strings and XX, YY, ZZ become a single gate. Longer strings become a basis change, a CNOT ladder and one rz.

Product formulas

trotter_circuit(h, n_qubits, t, steps, order) splits the time into steps steps of length τ = t/steps:

orderone step
1 (Lie–Trotter)exp(−iτ h_γ P_γ) for γ = 1, …, Γ
2 (Strang)half steps for γ = 1, …, Γ and back again
2k ≥ 4 (Suzuki)S₂ₖ(τ) = S₂ₖ₋₂(uτ)² S₂ₖ₋₂((1 − 4u)τ) S₂ₖ₋₂(uτ)², u = 1/(4 − 4^{1/(2k−1)})

trotter_error_bound returns an upper bound on the spectral-norm distance between the circuit and exp(−iHt). The commutators in the bound are computed exactly in the Pauli algebra:

  • order 1: τ²/2 · Σ_γ ‖[Σ_{γ'>γ} H_γ', H_γ]‖ (Childs, Su, Tran, Wiebe and Zhu, Phys. Rev. X 11, 011020 (2021), Proposition 15);
  • order 2: the nested-commutator bound of their Proposition 16;
  • order 4 and higher: the smaller of a stage-by-stage second-order bound and a Taylor-remainder bound.

Each bound is multiplied by the number of steps.

import numpy as np
import metatron_qso as q

h = q.heisenberg_xxz(4, 1.0, 0.5)
w, v = np.linalg.eigh(h.to_matrix(4))
exact = (v * np.exp(-1j * w)) @ v.conj().T          # exp(-iH), t = 1

def unitary(c):
    cols = []
    for k in range(16):
        prep = q.Circuit(4)
        for b in range(4):
            if k >> b & 1:
                prep.x(b)
        cols.append(q.simulate(prep.append(c)).amplitudes)
    return np.array(cols).T

for order in (1, 2, 4):
    c = q.trotter_circuit(h, 4, 1.0, steps=10, order=order)
    error = np.linalg.norm(unitary(c) - exact, 2)
    bound = q.trotter_error_bound(h, 1.0, steps=10, order=order)
    assert error <= bound
    print(order, len(c), f"{error:.2e} <= {bound:.2e}")

The identity term of H only adds a global phase, so circuits leave it out. In the tests (Heisenberg, Ising and random four-qubit Hamiltonians), the first- and second-order bounds are 3.5 to 13 times the actual error. The bound for order 4 and higher is valid but loose: it shrinks only as r⁻² with the number of steps r until the steps are short, while the actual error shrinks as r⁻⁴. Doubling the steps divides the actual error by 2^order, and the tests check this.

qDRIFT

qdrift_circuit(h, n_qubits, t, samples, seed) draws N = samples terms at random, with probability |h_γ|/λ (λ = Σ|h_γ|). Each drawn term is applied for time λt/N (Campbell, Phys. Rev. Lett. 123, 070503 (2019)). The average over seeds is a channel within diamond distance qdrift_error_bound = 2λ²t²/N · e^{2λt/N} of the exact evolution. The tests compute that channel exactly. Its error falls as 1/N and stays below the bound.

import metatron_qso as q

h = q.PauliSum("0.8 X0 X1 + 0.5 Z1 + 0.3 Y1 Y2 - 0.6 Z0 Z2")
c = q.qdrift_circuit(h, 3, 1.0, samples=200, seed=7)
print(len(c), q.qdrift_error_bound(h, 1.0, 200))

qDRIFT pays off when H has many small terms, because its cost depends on λ and not on the number of terms.

On the command line

$ qso trotter --hamiltonian "Z0 Z1 + 0.5 X0 + 0.5 X1" --time 1 --steps 10 --order 2
order 2, 10 steps, t = 1: 41 gates, depth 31, error bound 5.000e-3, largest column error 1.938e-3 (a lower bound on ‖U − exp(−iHt)‖)

--output FILE writes the circuit as OpenQASM 2.0.