Quickstart¶
This page walks through one complete propagation, involving a Qiskit circuit, a Pauli observable, a truncation pipeline, and an expectation value.
1. Build a circuit¶
propaq consumes Qiskit (and optionally Cirq) circuits. Convert one into the representation matching the basis you want to propagate in:
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit.library import XXPlusYYGate, RZGate, SwapGate
from propaq.circuits import MajoranaCircuit
qc = QuantumCircuit(4)
qc.append(XXPlusYYGate(np.pi / 4, 0.0), [0, 1])
qc.append(RZGate(np.pi / 3), [2])
qc.append(SwapGate(), [2, 3])
circuit = MajoranaCircuit.from_qiskit(qc, n_modes=2 * qc.num_qubits)
Gates outside propaq's native basis are transpiled automatically. If a gate you use often is being decomposed into many native rotations, register a fast path for it. See Circuits and gates.
2. Choose an observable¶
An observable is a term sum, i.e. a weighted sum of Pauli strings or Majorana
monomials. The easiest way in is from a Qiskit SparsePauliOp:
3. Pick a truncation pipeline¶
A propagator takes a list of truncators that run together after each gate.
from propaq.truncation import WeightTruncator, CoefficientTruncator, TermBudget
truncation = [
WeightTruncator(weight=10), # drop terms of Pauli weight > 10
CoefficientTruncator(coefficient=1e-5), # drop |coeff| < 1e-5
TermBudget(min_terms=1_000_000), # suppress the above below 1M live terms
]
The truncation guide explains what each truncator costs and when it fires.
4. Propagate¶
from propaq.noise import UniformNoiseModel
from propaq.propagators import MajoranaPropagator
prop = MajoranaPropagator(
noise=UniformNoiseModel(damping=0.001),
truncation=truncation,
)
result = prop.expectation_value(mts, circuit, initial_state=0)
print("expectation value:", result.expectation_value)
print("terms at each gate:", result.n_terms) # per-gate trace, not a total
print("final term count: ", result.n_terms[-1])
print("below cutoff: ", result.terms_below_cutoff)
from propaq.noise import UniformNoiseModel
from propaq.propagators import PauliPropagator
prop = PauliPropagator(
noise=UniformNoiseModel(damping=0.001),
truncation=truncation,
)
result = prop.expectation_value(pts, circuit, initial_state=0)
print("expectation value:", result.expectation_value)
print("terms at each gate:", result.n_terms) # per-gate trace, not a total
print("final term count: ", result.n_terms[-1])
print("below cutoff: ", result.terms_below_cutoff)
n_terms is a list, not a number
PropagationResult.n_terms is a
list[int]. It represents the live term count after each gate, so you can see where
branching took off. Use n_terms[-1] for the final count.
expectation_value
back-propagates the observable through the circuit and contracts the result
against the computational-basis state given by initial_state, represented as a bitstring. It returns a
PropagationResult.
If you want the propagated operator itself rather than a number, use
propagate, which returns
the evolved MajoranaTermSum/PauliTermSum:
Both methods accept filename=, which writes the final terms to a
gzip-compressed binary file you can reload lazily later. See
Streaming and I/O.
Where to go next¶
-
Understand the model
-
Keep the term count bounded
-
Sweep circuit parameters cheaply
-
See it all working end to end