qarp.engines

Execution engines. Public depth: flat. Submodules are private.

class qarp.engines.CudaqEngine(*, backend: str = 'statevector', precision: str = 'fp64', max_bond_dim: int = 0, target: str | None = None, n_shots: int | Shots = 10000, seed: int | None = None)[source]

Bases: Engine

Pure-Python engine wrapping the C++ Transpiler + qx.CudaqSimulator.

provides_amplitudes = True (base default) — noiseless simulator; the device→host statevector transfer is separately capped by _check_statevector_host.

Gradients: "default" resolves to the batched parameter shift, which stays on-device for StateVector expectation values over a QubitOperator (batch_expectation); other targets pull the host statevector per point. "adjoint" is refused — the adjoint is a CPU kernel and this engine never constructs a CPU simulator behind the caller’s back (a GPU adjoint is post-release work); use QarpEngine for it.

Parameters:
  • backend – One of statevector (single GPU, default), statevector-mgpu (multi-GPU + MPI), tensornet, tensornet-mps.

  • precision – fp64 (default) or fp32.

  • max_bond_dim – Bond-dimension cap for tensornet-mps (ignored otherwise).

  • target – Optional NVQIR backend name passed verbatim to the simulator (CUDAQ_DEFAULT_SIMULATOR), overriding backend/precision. Use "qpp" to run on CPU (e.g. for tests/CI without a GPU) or to reach a backend not covered by the backend enum.

  • n_shots – Default shot count used when a primitive has n_shots=None; qarp.EXACT makes exact readout the engine-wide default.

  • seed – Optional RNG seed (passed to the simulator). EXACT readouts involve no RNG and are seed-independent.

gradient_methods: frozenset[str] = frozenset({'default', 'finite-diff', 'parameter-shift', 'spsa'})
class qarp.engines.Engine[source]

Bases: ABC

Common surface implemented by all concrete engines.

Concrete engines own a transpiler + simulator, plan or compile each primitive’s circuits once on build(), then dispatch one or many parameter sets through run() / batch_run() keeping the per-shot loop in C++ (no Python round-trip per shot).

batch_run(primitives: list[Runnable], param_sets: Sequence[Mapping], n_shots: int | Shots | None = None, rebuild: bool = True) → list[list[float | complex | SamplingDistribution]][source]

Sweep one set of primitives over multiple parameter dicts.

Template: _batch_setup then the engine’s _sweep.

Parameters:
  • primitives – Primitive instances to evaluate.

  • param_sets – One dict per evaluation point.

  • n_shots – Override engine-default shot count for this sweep.

  • rebuild – If False, reuse a primitive’s circuits when a prior build() compiled them under this engine’s settings.

Returns:

results_by_set[set_idx][prim_idx] = scalar.

build(primitives: Sequence[Runnable], params: Mapping = {}, rebuild: bool = True) → None[source]

Compile all primitives (template method).

Per primitive: prim.build() (derives its circuits, builds nothing) → _validate_primitive → per circuit: _plan_unbuilt, else build → flatten → substitute → _validate_flat_commands → _warn_uninitialised_conditions → _compile_one → _post_compile_check → _plan_built, into one CompiledCircuit each on prim.compiled. rebuild=False reuses a primitive’s circuits when all were compiled under this engine’s _compile_stamp(), re-validating it — capability-relevant state may have changed — and compiles it afresh otherwise.

gradient_methods: frozenset[str] = frozenset({})
kernels(primitive: Runnable) → list[list[str]][source]

The kernels run() and batch_run() execute for each circuit of primitive, as Block.kernels() lists them for one block: one list per circuit, in order, ["gates"] where its gate stream runs (§14 Structured execution). primitive must be compiled under this engine’s settings; one with no circuits has no kernels.

property provides_amplitudes: bool
resolve_gradient_method(prim: Runnable, method: str) → str[source]

The concrete method run_gradient(method=...) would use for prim.

resource_modeler() → ResourceModeler | None[source]

Modeler for qarp.resources.ResourceEstimator, or None.

Engines pricing gates beyond raw counting (e.g. a digital-Rz T-cost) override this. No in-tree engine does: the base None is the only return, and the hook is the seam a modeling engine re-attaches to.

run(params: Mapping = {}) → list[float | complex | SamplingDistribution][source]

Simulate all built primitives and return their scalar results.

Template method: re-validates every primitive per call (noise toggles and initial_state mutations must not slip through), substitutes parameters, and defers the engine-specific simulation to _dispatch_one.

run_gradient(params: Mapping, method: str = 'default', options: Mapping | None = None) → list[ndarray][source]

Per-primitive gradients — one array per built primitive.

Contract (docs/contracts/qarp_conventions.md §17):

  • result[i] has shape (len(params),) with columns in the insertion order of params — for every method.

  • dtype is float64, or complex128 when the primitive’s value is complex by target (TRANSITION_AMPLITUDE, an expectation value over a qx.Block operator, a complex HadamardTest).

  • The differentiated objective per target: EXPECTATION_VALUE and TRANSITION_AMPLITUDE — the value run() returns (Re and Im separately); a StateVector OVERLAP — |⟨bra|ket⟩|² although run() returns the amplitude (gradient_kind == "squared_overlap"); every other primitive — the value run() returns.

method: "default" (this engine’s policy — adjoint where eligible on QarpEngine, parameter shift otherwise; may change between releases), "adjoint", "parameter-shift", "finite-diff" (options: fd_eps, fd_order 1|2), "spsa" (options: spsa_c0, num_spsa, spsa_seed). An unknown name is a ValueError; a method this engine does not declare in gradient_methods is a CapabilityError.

supports_initial_state: bool = False
class qarp.engines.QarpEngine(device: Device | None = None, *, n_qubits: int | None = None, architecture: Architecture | None = None, noise_model=None, gate_set: GateSet | None = None, directedness: bool = False, n_shots: int | Shots = 10000, seed: int | None = None, structured: bool | None = None, optimization_level: int | None = None)[source]

Bases: Engine

Pure Python engine wrapping C++ Transpiler + QarpSimulator.

Pipeline:
build(primitives) — reads each primitive’s circuits; a declared

composition is planned unbuilt, otherwise the circuit is built, flattened, run through the device-aware compilation pipeline (qx.compile_for_device: check_fits → rebase → route → re-rebase, whichever stages the device configures) and then planned from its flat stream.

run(params) — dispatches per primitive on consumes:

AMPLITUDES → primitive.run_from_amplitudes; COUNTS → csim sampling (an ExactResult under n_shots=qarp.EXACT), reindexed back to logical-qubit order when the device routed, then primitive.run(results).

batch_run(…) — sweeps one set of primitives over multiple

parameter dicts, each evaluated as run evaluates it.

Parameters:
  • device – A pre-built qx.Device. Mutually exclusive with field-style kwargs.

  • n_qubits/architecture/noise_model/gate_set/directedness – Field shorthand — assembles an effective Device internally (provided in lieu of device=…).

  • n_shots – Default shot count used when a primitive has n_shots=None. qarp.EXACT makes exact readout the engine-wide default.

  • seed – Optional RNG seed (passed to QarpSimulator). EXACT readouts involve no RNG and are seed-independent / bit-reproducible.

  • structured – Run sampling primitives’ block structure as typed kernels where cheaper than their gates (§14 Structured execution); None follows QARP_STRUCTURED (default on). Engines with a device always run the gate path.

gradient_methods: frozenset[str] = frozenset({'adjoint', 'default', 'finite-diff', 'parameter-shift', 'spsa'})
property noise_model

The device’s noise model (C++ qx.NoiseModel), or None.

qx.Device copies the model at construction, so toggling the object originally passed in has no effect afterwards — toggle this one instead: engine.noise_model.enabled = False (the disable-noise-for-gradients workflow).

property provides_amplitudes: bool

a disabled noise model restores amplitude capability.

Type:

Dynamic

supports_initial_state: bool = True
class qarp.engines.Runnable(*args, **kwargs)[source]

Bases: Protocol

Members engines actually touch on an executable item, grouped by what they describe: the quantity, the circuits, the estimator. A new member joins one group.

Target and Consumes are importable: they live in qarp._types, below both layers, and engines compare them by identity. operator stays Any: QubitOperator lives above this layer, so engines duck-type it — importing it here would recreate the engines⇄algorithms cycle this protocol removes.

build() → Runnable[source]
compiled: list[CompiledCircuit]
consumes: Consumes
gradient_kind: str
initial_state: Any
n_shots: int | Shots | None
operator: Any
run(results: list) → Any[source]
run_from_amplitudes(compiled_circuits: list, simulator=None) → Any[source]
sub_blocks: list
supported_targets: frozenset
supports_backprop_gradient: bool
supports_exact: bool
target: Target