mimiqcircuits.backends.backend¶
Abstract base classes for simulator backends.
This module defines the contract every simulator must satisfy:
Backend— the common interface, advertising identity, capabilities, limits, topology, and anexecute()entry point.LocalBackend— for in-process simulators. Addsbuild_state(),compile(),evolve(), and a defaultexecute()loop built on top of them.RemoteBackend— for cloud or submit/poll execution. Addssubmit()and provides a defaultexecute()that waits on the returned job.
See Implementing a Custom Backend for a step-by-step guide.
Classes
|
Abstract base class for any simulator backend. |
Base class for simulators that run in the local process. |
|
|
Per-qubit present/lost register, a peer to the quantum, classical and complex registers of a |
|
Four independent RNG streams, one per simulation stage. |
Base class for simulators that run on a remote service. |
|
|
Composite simulation state held by a backend. |
- class mimiqcircuits.backends.backend.RNGs(shot, noise, trajectory, pass_)[source]¶
Bases:
objectFour independent RNG streams, one per simulation stage.
Keeping the streams separate means seeding one stage (for example sampling) does not affect the determinism of another (for example noise selection), even when the same execution touches both.
- shot¶
Sampling shots from the final state.
- Type:
- noise¶
Kraus and mixed-unitary noise sampling.
- Type:
- trajectory¶
Monte Carlo trajectory selection.
- Type:
- pass_¶
Randomised compilation passes (e.g. simulated-annealing reorderers).
- Type:
- Construct from a single int seed via :py:meth:`from_seed`.
- static from_seed(master)[source]¶
Derive four streams from a single
masterseed.XOR-tags the seed bits per stream — cheap, deterministic, and stable across Python versions, which matters because the downstream backends must reproduce results bit-for-bit from the same input.
- static from_generator(gen)[source]¶
Draw a master seed from
genand forward tofrom_seed().
- __init__(shot, noise, trajectory, pass_)¶
- class mimiqcircuits.backends.backend.State[source]¶
Bases:
ABCComposite simulation state held by a backend.
A state bundles three registers:
the quantum register (whatever representation the backend uses; state vector, MPS, tensor network, …);
the classical-bit register written by measurements and
IfStatementoutcomes;the complex-valued register written by non-destructive observations (
Amplitude,ExpectationValue,BondDim, …).
Required surface for every subclass:
num_qubits,num_bits,num_zvars— register sizes.amplitude(),sample()— observation primitives.classical_bits,complex_values— register accessors.
Optional surface:
expectation()— backend overrides when it can compute⟨ψ|O|ψ⟩non-destructively (gated by the"expectation_state"capability).reset()— backend overrides when it can rebuild itself in place to the zero state.
- abstract sample(nsamples, rng=None, *, seed=None)[source]¶
Sample
nsamplesmeasurement outcomes from the state.Exactly one source of randomness is consumed:
rng(positional or keyword): arandom.Randomwhosegetrandbits(63)produces the seed forwarded to the simulator’s PRNG.seed(keyword): an int that seeds the simulator’s default PRNG directly.neither: the simulator draws fresh cryptographic entropy.
Passing both
rngandseedmust raiseTypeError. The two arguments are mutually exclusive.
- expectation(op, *qubits)[source]¶
Compute
⟨ψ|op|ψ⟩on this state.Most backends implement expectation on the Backend rather than the State (so they can route through the simulator’s compile / evolve machinery). State-level expectation is provided as a convenience hook for backends whose quantum register supports it directly.
- abstract property classical_bits¶
- abstract property complex_values¶
- get_loss_state()[source]¶
Return the per-qubit loss register (see
LossState).Backends that opt into the shared runtime-loss driver (
LocalBackend.uses_loss_driver()) embed one and clear it inreset().
- class mimiqcircuits.backends.backend.LossState(n_or_flags)[source]¶
Bases:
objectPer-qubit present/lost register, a peer to the quantum, classical and complex registers of a
State. EntryqisTruewhen qubitqhas leaked out of the system.The register holds no loss policy: only the shared runtime-loss driver (
LocalBackend.evolve_with_loss()) mutates it.
- class mimiqcircuits.backends.backend.Backend[source]¶
Bases:
ABCAbstract base class for any simulator backend.
A concrete backend must:
advertise its feature set via
capabilities(),limits(),topology();implement
execute()(or subclassLocalBackend/RemoteBackendand inherit a sensible default).
Pick the right base for your simulator:
LocalBackend— the simulator runs in-process and you can hand it aStateit mutates with each instruction.RemoteBackend— the simulator runs elsewhere (cloud service, queued executor) and your wrapper submits jobs and polls for results.
See Implementing a Custom Backend.
- can_handle(circuit)[source]¶
Default admission check against
limits()and the backend’s advertisedcapabilities().Backends with richer admission criteria (bond-dimension estimates, hardware connectivity, gate-set whitelists) should override.
Rejects (returns
Inadmissible) when:the circuit exceeds one of
max_qubits/max_classical_bits/max_zvarsdeclared bylimits();the circuit contains a loss-bearing operation (
Loss) but the backend does not advertise the"loss"capability.
Returns
Admissibleotherwise.
- stochastic_kind(op_or_instruction)[source]¶
Classify how this backend resolves
op_or_instruction—StochasticKind.Deterministic,StochasticKind.TrajectorySampleable, orStochasticKind.RuntimeOnly.The default delegates to
default_stochastic_kind()(mix-unitary Kraus is TS; non-mix-unitary Kraus, mid-circuitMeasure, andLossare RT; everything else Deterministic). Backends that handle a specific op type differently override this method.Backend-dependent: a
MixedUnitaryisTrajectorySampleablefor an MPS-style sampler but could beRuntimeOnlyfor a backend that resolves it state-dependently. The classification lives on the backend, not on the op.
- default_passes()[source]¶
The pipeline this backend wants applied to a circuit before evolution. Empty by default — backends override.
This is the whole of a backend’s involvement in circuit preparation: it declares the passes it expects, and
execute()adjusts that declaration to match what the caller asked for. A backend never sees the flags; when one asks for a pass, the backend builds it throughfuse_pass(),reorder_pass(),canonical_decompose_pass()orremove_swaps_pass().
- fuse_pass(*, threshold=None)[source]¶
The pass
execute(fuse=True)orfuse_threshold=ninstalls.Defaults to
FusePassgated onthresholdqubits (Nonemeans 0).
- reorder_pass(method=None, *, seed=None)[source]¶
The pass
execute(reorderqubits=...)installs.methodnames the requested search, or isNoneforreorderqubits=True, which leaves the choice to the backend. There is no generic local reorder pass, so the default raisesNotImplementedError.
- canonical_decompose_pass()[source]¶
The pass
execute(canonicaldecompose=True)installs. Defaults toCanonicalDecomposePass.
- remove_swaps_pass()[source]¶
The pass
execute(remove_swaps=True)installs.There is no generic local remove-swaps pass, so the default raises
NotImplementedError.
- simplify_pass()[source]¶
The pass
execute(simplify=True)installs. Defaults toSimplifyPasswithDEFAULT_SIMPLIFY_PASSES.
- abstract execute(circuit, *, nsamples=1000, seed=None, rng=None, passes=None, fuse=None, canonicaldecompose=None, reorderqubits=None, remove_swaps=None, simplify=None, reorderqubits_seed=None, callback=None, param_grid=None, strict_pass_order=True)[source]¶
Run
circuiton this backend and returnQCSResults.Pass
circuitas a singleCircuitor a list of circuits — a list returns a list of results in the same shape. On a local backendcircuitcan also be aSweepExperiment, which runs the circuit once per point of its table and returns one result per point.seedandrngare mutually exclusive sources of randomness; pass at most one. With neither, the backend draws fresh entropy. To request specific amplitudes, pushAmplitudeinstructions intocircuitand read the resultingresults.zstates.The flags —
fuse,canonicaldecompose,reorderqubits,remove_swaps,simplify— adjust the backend’sdefault_passes()rather than replacing them. Each speaks about one kind of pass:Noneleaves that pass as the backend declared it,Falsedrops it, and any other value replaces it with the one the flag describes — or adds that one, in the position it belongs, when the backend declared none of that kind. Everything the flags say nothing about stands.So
fuse=Falseis “your pipeline, without the fusion”. Local backends also takefuse_threshold:fuse_threshold=18is “your pipeline, fusing from 18 qubits up”. To prepare a circuit with nothing at all, set every flag toFalse— a pass no flag names (container flattening, say) survives even that. A flag of the wrong type, such asfuse=0, raisesTypeError.simplify=Truesimplifies the circuit before evolution with the backend’ssimplify_pass(), keeping its unitary exactly: inverse pairs cancel, rotations merge, and one- and two-qubit blocks are rewritten with fewer gates. It is off unless asked for.The flags have a fixed order among themselves — remove swaps, simplify, canonical decompose, fuse, reorder — which is where a flag puts its pass when the backend declared none of that kind: before the first pass that runs later than it does, and after any pass no flag names.
passesare appended after that, as given; nothing is deduplicated, so an entry of the same kind as a pass already there simply runs too.reorderqubits_seedconfigures reordering rather than requesting it.param_gridruns a parameter sweep on a local backend: a list of dicts, each mapping the circuit’s symbols (or their names) to values, such as[{theta: 0.1}, {theta: 0.2}]. Every dict is substituted intocircuitwithevaluate()and the result runs as its own execution, with the other arguments unchanged. The call then returns a list of results, one per dict and in the same order (with a list of circuits, one such list per circuit). Each point gets its own seed, derived fromseedorrng, so the points are statistically independent and a sweep repeated with the sameseedgives the same results. Every dict must bind all the free symbols of the circuit, or that point raises an error. An empty list runs the circuit once, as if it was not given. Remote backends raiseNotImplementedError.
- expectation(state, op, *qubits)[source]¶
Compute
⟨ψ|op|ψ⟩onstatenon-destructively.qubitsis the list of qubit indicesopacts on (0-based). Backends override when they advertise the"expectation_state"capability; the default raisesNotImplementedErrorso undeclared backends fail loudly rather than silently degrading.
- class mimiqcircuits.backends.backend.LocalBackend[source]¶
Bases:
BackendBase class for simulators that run in the local process.
Subclasses implement a handful of primitives and inherit a full
execute()driver. Once the hooks are wired up, routing (sampling-vs-trajectory), the final-block projection circuit, loss-sampling pre-pass, and amplitude lookups all work without any per-backend duplication.Required hooks:
build_state()— allocate a fresh zero state.compile()— turn aCircuitinto aCompiledCircuit(a backend-specific lowered form).evolve()— apply the compiled circuit to a state and return the mutated state plus a typedFidelity.
Optional hooks (sensible defaults provided):
prepare_trajectory()— refresh the compiled artifact once per Monte Carlo trajectory (default: identity).recompile_per_trajectory()— predicate; default returnsTrueiff the circuit contains a mixed-unitary Kraus channel.bind()— substitute parameters into a parametric compile artifact (default: re-compile after substitution).
- prepare_trajectory(compiled, rng)[source]¶
Refresh
compiledfor one Monte Carlo trajectory.Override when the compiled artifact contains stochastic elements (sampled mixed-unitary channels, sampled Kraus branches, …) that must be redrawn per trajectory. The default leaves
compiledunchanged, which is correct for fully deterministic compilation.
- recompile_per_trajectory(circuit)[source]¶
Return
Trueiffcompile()should be re-run for every trajectory. The default fires on any mixed-unitarykrauschannel: those backends sample a branch at compile time, so the compiled artifact has to be regenerated per trajectory to expose a fresh sample. Backends that own their per-trajectory sampling internally (insideprepare_trajectory()orevolve()) should override to returnFalse.
- bind(compiled, params)[source]¶
Substitute
paramsinto a parametric compiled circuit.The default implementation substitutes the symbols in the source circuit and re-runs
compile(). This is correct but pays the full compile cost at every parameter point. Override when your backend can re-bind a pre-compiled artifact in-place (slot maps, pre-baked gate templates, …).
- optimize(experiment, initparams=None, *, return_history=False, **kwargs)[source]¶
Minimize a cost read from a parametric circuit by running it on this backend.
At every step the optimizer proposes values for the circuit’s symbols, the circuit is evaluated with them and run with
execute(), and the cost is the real part of the mean of the z-registerzregisterover the samples. Push the quantity to minimize into that register, for example withpush_expval().Call it with an
OptimizationExperiment, a list of them, or a circuit and a dict of initial values for all its symbols plus the experiment’s fields as keywords:optimizer:"BFGS","LBFGS","CG","NELDERMEAD","NEWTON","COBYLA"(default) or"CMAES". All but"CMAES"come fromscipy.optimize.minimize();"CMAES"needs thecmaesextra (pip install 'mimiqcircuits[cmaes]').maxiters: iteration limit, orNonefor the optimizer’s own.zregister: index of the z-register holding the cost (default 0).label: a name for the experiment.
With an experiment these come from it, and passing them again is an error.
return_history=Truealso keeps every evaluated point. Every other keyword goes toexecute()on each evaluation:nsamples,seed,fuse,passesand so on. With a fixedseedevery evaluation draws the same random numbers, so a noisy cost does not jump between otherwise equal points.Returns an
OptimizationResultswith the lowest-costOptimizationRunand, withreturn_history=True, every evaluated run in order. A list of experiments returns a list of results in the same order.Raises
ValueErrorfor an unknown optimizer or azregisterthe circuit does not have.Example
from symengine import symbols import mimiqcircuits as mc theta = symbols("theta") c = mc.Circuit().push(mc.GateRY(theta), 0) # The cost is <Z> on qubit 0, written to z-register 0. c.push(mc.ExpectationValue(mc.GateZ()), 0, 0) res = backend.optimize(c, {theta: 0.1}, optimizer="NELDERMEAD", nsamples=1, seed=1) res.get_best().cost # close to -1, reached at theta = pi
- compile_progress(circuit, progress)[source]¶
Compile
circuitwhile optionally emitting a compression stage. The default ignoresprogressand defers tocompile(); MPS-class backends whose compilation fuses gates into tensors override this to open a “compression” stage and step it per fused gate. Keeping the hook separate fromcompile()leaves the required signature untouched.
- uses_loss_driver()[source]¶
Whether
execute()resolves runtimeLossyOperatorloss throughevolve_with_loss(). Opt in by returningTrueand implementingapply_instruction()andsample_kraus().
- apply_instruction(state, inst, *, rng=None)[source]¶
Apply a single (loss-free) instruction to
stateand return the fidelity factor it contributes (1.0for an exact application). The per-step primitive the runtime-loss driver calls; no loss logic.
- sample_kraus(state, channel, targets, *, rng=None)[source]¶
Sample one branch of a Kraus
channelagainststate, apply it, and return(fidelity, fired_operator)wherefired_operatoris the original branch (tag preserving, so aLossyOperatorarrives intact). The driver maps the fired operator back to the qubits that leaked.
- apply_segment(state, insts, *, rng=None)[source]¶
Apply a contiguous run of loss-free instructions and return the product of their fidelity factors.
The runtime-loss driver hands this the maximal run of instructions between two loss events; every instruction acts only on present qubits, so a compressing or fusing backend (MPS/MPO) may override this to compress the whole run at once — that all-present guarantee is the contract it relies on. The default applies one instruction at a time via
apply_instruction(), which suits a non-compressing backend.
- evolve_with_loss(state, circuit, lossmodel, *, rng=None, callback=None, stopped=None)[source]¶
Evolve
circuitwhile resolving qubit loss at runtime.Loss rides in the state’s
LossStateregister and is interpreted exactly as the offlinelower_losses()does, so the two agree shot for shot. The circuit is walked as alternating [all-present run, loss event]: each run is handed toapply_segment()(which a compressing backend fuses) before the event after it is resolved, so a lost-qubit op can never enter a fused run. Returns(state, fidelity)wherefidelityis the product of the per-step fidelity factors.
- execute(circuit, *, nsamples=1000, seed=None, rng=None, passes=None, fuse=None, fuse_threshold=None, canonicaldecompose=None, reorderqubits=None, remove_swaps=None, simplify=None, reorderqubits_seed=None, callback=None, param_grid=None, strict_pass_order=True, stopped=None, num_qubits=None, progress=False)[source]¶
Run
circuiton this backend and returnQCSResults.Pass
circuitas a singleCircuitor a list of circuits — a list returns a list of results in the same shape. On a local backendcircuitcan also be aSweepExperiment, which runs the circuit once per point of its table and returns one result per point.seedandrngare mutually exclusive sources of randomness; pass at most one. With neither, the backend draws fresh entropy. To request specific amplitudes, pushAmplitudeinstructions intocircuitand read the resultingresults.zstates.The flags —
fuse,canonicaldecompose,reorderqubits,remove_swaps,simplify— adjust the backend’sdefault_passes()rather than replacing them. Each speaks about one kind of pass:Noneleaves that pass as the backend declared it,Falsedrops it, and any other value replaces it with the one the flag describes — or adds that one, in the position it belongs, when the backend declared none of that kind. Everything the flags say nothing about stands.So
fuse=Falseis “your pipeline, without the fusion”. Local backends also takefuse_threshold:fuse_threshold=18is “your pipeline, fusing from 18 qubits up”. To prepare a circuit with nothing at all, set every flag toFalse— a pass no flag names (container flattening, say) survives even that. A flag of the wrong type, such asfuse=0, raisesTypeError.simplify=Truesimplifies the circuit before evolution with the backend’ssimplify_pass(), keeping its unitary exactly: inverse pairs cancel, rotations merge, and one- and two-qubit blocks are rewritten with fewer gates. It is off unless asked for.The flags have a fixed order among themselves — remove swaps, simplify, canonical decompose, fuse, reorder — which is where a flag puts its pass when the backend declared none of that kind: before the first pass that runs later than it does, and after any pass no flag names.
passesare appended after that, as given; nothing is deduplicated, so an entry of the same kind as a pass already there simply runs too.reorderqubits_seedconfigures reordering rather than requesting it.param_gridruns a parameter sweep on a local backend: a list of dicts, each mapping the circuit’s symbols (or their names) to values, such as[{theta: 0.1}, {theta: 0.2}]. Every dict is substituted intocircuitwithevaluate()and the result runs as its own execution, with the other arguments unchanged. The call then returns a list of results, one per dict and in the same order (with a list of circuits, one such list per circuit). Each point gets its own seed, derived fromseedorrng, so the points are statistically independent and a sweep repeated with the sameseedgives the same results. Every dict must bind all the free symbols of the circuit, or that point raises an error. An empty list runs the circuit once, as if it was not given. Remote backends raiseNotImplementedError.
- class mimiqcircuits.backends.backend.RemoteBackend[source]¶
Bases:
BackendBase class for simulators that run on a remote service.
Subclasses implement
submit(), which dispatches the request and returns a job handle. The inheritedexecute()callssubmit(), then blocks on the returnedjob.wait().- abstract submit(circuits, nsamples, **kwargs)[source]¶
Send the request and return a job handle.
The job handle must expose a
wait()method that blocks until results are available and returnsmimiqcircuits.QCSResults(or a list whencircuitswas a list).
- execute(circuit, *, nsamples=1000, seed=None, rng=None, passes=None, callback=None, param_grid=None, strict_pass_order=True)[source]¶
Run
circuiton this backend and returnQCSResults.Pass
circuitas a singleCircuitor a list of circuits — a list returns a list of results in the same shape. On a local backendcircuitcan also be aSweepExperiment, which runs the circuit once per point of its table and returns one result per point.seedandrngare mutually exclusive sources of randomness; pass at most one. With neither, the backend draws fresh entropy. To request specific amplitudes, pushAmplitudeinstructions intocircuitand read the resultingresults.zstates.The flags —
fuse,canonicaldecompose,reorderqubits,remove_swaps,simplify— adjust the backend’sdefault_passes()rather than replacing them. Each speaks about one kind of pass:Noneleaves that pass as the backend declared it,Falsedrops it, and any other value replaces it with the one the flag describes — or adds that one, in the position it belongs, when the backend declared none of that kind. Everything the flags say nothing about stands.So
fuse=Falseis “your pipeline, without the fusion”. Local backends also takefuse_threshold:fuse_threshold=18is “your pipeline, fusing from 18 qubits up”. To prepare a circuit with nothing at all, set every flag toFalse— a pass no flag names (container flattening, say) survives even that. A flag of the wrong type, such asfuse=0, raisesTypeError.simplify=Truesimplifies the circuit before evolution with the backend’ssimplify_pass(), keeping its unitary exactly: inverse pairs cancel, rotations merge, and one- and two-qubit blocks are rewritten with fewer gates. It is off unless asked for.The flags have a fixed order among themselves — remove swaps, simplify, canonical decompose, fuse, reorder — which is where a flag puts its pass when the backend declared none of that kind: before the first pass that runs later than it does, and after any pass no flag names.
passesare appended after that, as given; nothing is deduplicated, so an entry of the same kind as a pass already there simply runs too.reorderqubits_seedconfigures reordering rather than requesting it.param_gridruns a parameter sweep on a local backend: a list of dicts, each mapping the circuit’s symbols (or their names) to values, such as[{theta: 0.1}, {theta: 0.2}]. Every dict is substituted intocircuitwithevaluate()and the result runs as its own execution, with the other arguments unchanged. The call then returns a list of results, one per dict and in the same order (with a list of circuits, one such list per circuit). Each point gets its own seed, derived fromseedorrng, so the points are statistically independent and a sweep repeated with the sameseedgives the same results. Every dict must bind all the free symbols of the circuit, or that point raises an error. An empty list runs the circuit once, as if it was not given. Remote backends raiseNotImplementedError.