This page documents the public lindblad::Estimator primitive.
- Header:
include/lindblad/primitives.hpp - Namespace:
lindblad
Estimator computes expectation values of a SparsePauliOp against a circuit.
It supports three execution modes — exact statevector, exact density-matrix
(for noisy circuits), and per-Pauli-term shot sampling — selected automatically
from Options. It also supports a cached transpilation path for repeated
parameter sweeps.
Fields and defaults:
shots = 0: shot budget per non-identity Pauli term in the observable.shots == 0: exact analytic expectation (zero variance, reproducible). THROWS for circuits containing measurement or classically-conditioned instructions (frozen in R.1.12): the exact expectation of a stochastic trajectory is undefined; estimate such circuits from counts withshots > 0.shots > 0: real shot-noise sampling — each non-identity term is rotated into the Z measurement basis and measuredshotstimes. Variance scales as1/√shotsper term.- Changed in R.1.10.7:
shots > 0now performs real sampling. Prior releases usedshotsas a hidden DM-backend toggle and returned exact values with zero variance regardless ofshots. - Pauli strings follow the project LSB-first convention (
pauli[q]acts on qubit q); the exact and sampling paths agree on it (frozen in R.1.12).
seed = 0: RNG seed forwarded to the simulator. Each measurement group gets a decorrelated derived seed so the shot streams across groups are independent.noise_model: when non-ideal (!is_ideal()), the sampling/exact path usesDensityMatrixSimulatorso Kraus channels are applied. OtherwiseStatevectorSimulatoris used.optimization_level = 0: enables transpilation and caching when > 0.group_pauli_terms = true(added in R.1.13, audit F-10): withshots > 0, group qubit-wise-commuting observable terms so they share a single measurement run. All Z/I-only terms measure together in the plain Z basis, and the remaining terms group by qubit-wise commutativity (one basis rotation per group). For a T-term observable this is up to a T-fold reduction in simulations, at identical accuracy and statistics. Setfalseto restore the pre-R.1.13 one-run-per-term sampling.- Seed note: grouping consumes the RNG stream in a different order, so a
given
seedyields statistically-equivalent but not byte-identical counts versus the ungrouped path (and versus R.1.12). Seeds reproduce within a version, not across versions. Usegroup_pauli_terms = falseonly when you need the old byte-for-byte seeded stream. The flag has no effect whenshots == 0(the exact path does not sample).
- Seed note: grouping consumes the RNG stream in a different order, so a
given
Clears the internal transpilation cache. This is useful when you change transpilation settings or need to release cached circuits.
Signature:
std::vector<double> run_batch(
const QuantumCircuit& circuit,
const SparsePauliOp& observable,
const std::vector<std::vector<double>>& parameter_values
);Behavior:
- Evaluates each parameter vector with
run_single - Parallelized with OpenMP when enabled
- Returns one expectation value per parameter vector
Signature:
double run_single(
const QuantumCircuit& circuit,
const SparsePauliOp& observable,
const std::vector<double>& parameters = {}
);Behavior (verified against the implementation):
- When
options.optimization_level > 0, transpiles the unbound circuit and caches the result using a structure key (gate types, qubit indices, andn_clbits) - Transpilation runs outside the cache mutex (double-checked locking); insertion is guarded
- On cache hit, reuses the cached unbound circuit
- Binds parameters by name using the transpiled circuit parameter names when available, otherwise the original circuit parameter names
- Binds up to
min(parameters.size(), names.size())parameters - Three execution modes, selected from
Options:-
shots > 0— real sampling: decomposes the observable into Pauli terms; for each non-identity term, rotates the prepared circuit into that term's Z measurement basis (H for X, S†H for Y, identity for Z), runsoptions.shotsmeasurements throughStatevectorSimulator(orDensityMatrixSimulatorwhen the noise model is non-ideal), and accumulates a parity-weighted estimate. Identity terms contribute their coefficient analytically (no sampling). Variance scales as1/√shotsper term. -
shots == 0, noise non-ideal: runsDensityMatrixSimulatorwith zero shots and reads the exact expectation off the final mixed state viaexpectation_value_sparse(observable). -
shots == 0, ideal: runsStatevectorSimulator::eval_expectation(circuit, observable)which evaluates the observable in-place, avoiding a$O(2^n)$ allocation for the final state vector.
-
- Throws
std::runtime_errorif simulation fails on any path
Preconditions:
- The circuit and observable must target the same number of qubits
- Parameter ordering must match the circuit parameter name order
Signature:
std::vector<double> gradient(
const QuantumCircuit& circuit,
const SparsePauliOp& observable,
const std::vector<double>& parameters
);Behavior:
- Uses the parameter-shift rule with a shift of
pi/2 - Builds
2 * parameters.size()shifted vectors - Evaluates them with a single
run_batchcall - Returns a gradient vector of the same length as
parameters
#include "lindblad/primitives.hpp"
using namespace lindblad;
int main() {
QuantumCircuit circuit(2);
circuit.h(0).cx(0, 1);
SparsePauliOp observable({PauliString("ZZ", Complex128(1.0, 0.0))});
Estimator estimator;
estimator.options.optimization_level = 1;
double value = estimator.run_single(circuit, observable);
return (value > -2.0) ? 0 : 1;
}