provider
eigenkit.core.provider
The provider protocol, its registry, and the parameter-shift fallback.
A provider compiles a circuit separately from running it, which matters for
speed. Building a qml.device and a QNode (PennyLane), or a parametrised
QuantumCircuit and a SparsePauliOp (Qiskit), takes far longer than one
execution. Doing that work once per Circuit and reusing the compiled handle for
every forward pass is what makes repeated evaluations fast.
The run and run_torch methods of each backend accept batched arrays
directly. The first axis is the batch, and weights and inputs can each be 1-D
(one sample) or 2-D (a batch). Backends that can run a batch at once override
run to submit one job. The others call batch_call and pay for a loop in
Python, but the loop is inside the backend and not in the Circuit that calls
it. The Capabilities.supports_batching flag only gives information, and
nothing chooses a path based on it: Circuit always passes the batch on
unchanged.
Example
Capabilities
dataclass
The description that a provider gives of what it supports.
It is used to check a circuit before compilation, and to choose between code paths at run time.
Attributes:
| Name | Type | Description |
|---|---|---|
supported_gates |
frozenset[str]
|
The names of the gates that the backend can run. A circuit that uses any other gate is rejected before compilation, with a message that names that gate. |
supports_batching |
bool
|
Whether the backend evaluates a 2-D |
supports_shots |
bool
|
Whether the backend can sample, instead of returning exact expectation values. |
max_qubits |
int | None
|
The maximum number of qubits of the hardware. |
supported_adjoint_gates |
frozenset[str] | None
|
The gates whose adjoint the backend can also run.
|
supports_oracle_matrix |
bool
|
Whether the dense unitary of a callable oracle can be extracted. Iterative quantum amplitude estimation (IQAE) uses this to accept oracles that are PennyLane callables. |
supports_provider_shots |
bool
|
Whether the backend can sample an ancilla qubit
with its own shots, which is what IQAE needs for
|
CompiledCircuit
Bases: Protocol
A handle to a circuit that a backend has prepared, cheap to call many times.
run accepts weights and inputs that are each either 1-D (one sample) or
2-D (a batch along the first axis). The output has shape (n_obs,) when both
are 1-D, and (B, n_obs) otherwise. Each backend decides whether to evaluate
the batch at once or in a loop (see batch_call).
Implementations can also provide run_torch(...), for backends that support
PyTorch automatic differentiation themselves. When the gradient_method of
the backend is "native", the PyTorch interface detects this method with
hasattr and uses it instead of the parameter-shift fallback. The same shape
rule applies.
run(weights, inputs)
Evaluate the circuit and return one expectation value for each observable.
See the class description for the shapes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray | Sequence[float]
|
The weight vector, in any container that can be indexed: a NumPy array, a PyTorch tensor or a list. |
required |
inputs
|
ndarray | Sequence[float]
|
The input vector, in the same
kinds of container as |
required |
CostWarning
Bases: UserWarning
Warning issued before a submission to a paid quantum backend.
A paid backend is a quantum processing unit (QPU) or a cloud simulator that charges for use. The first compilation for such a device issues this warning, so that costs cannot build up without the user noticing.
JobHandle
Bases: Protocol
A handle to a job running on a quantum processing unit (QPU) or in the cloud.
compiled.submit(...) returns it without waiting for the job. Call .result()
to wait for the job and combine its results.
result()
Wait until the job finishes, then return its expectation values.
The shape follows the same rule as
CompiledCircuit.run():
(n_obs,) for a single sample, and (B, n_obs) for a batch.
status()
Return the state of the job as the provider reports it, without waiting.
The strings are the provider's own. For example, Braket returns
"CREATED", "QUEUED", "RUNNING", "COMPLETED", "FAILED" or
"CANCELLED". Compare them within one provider, not across providers.
Provider
Bases: Protocol
The protocol that every backend follows.
A provider turns a CircuitSpec into something that can run. Anything with
these members can be passed as provider=: the built-in backends, the backend
built by a configuration object, or your own class. To select it by a string
instead, register it under a short name with register.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
A short name, used in error messages and in |
gradient_method |
GradientMethod
|
How the gradient code should differentiate this backend:
|
capabilities
property
Return what this backend supports, as a Capabilities object.
It covers the supported gates, shots, batching and the other flags.
compile(circuit, observables)
Prepare circuit so that observables can be evaluated many times.
A Circuit calls it once, and reuses the returned handle for every call,
so the expensive work belongs here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
circuit
|
CircuitSpec
|
The circuit to prepare. |
required |
observables
|
Sequence[Observable]
|
The observables to measure. |
required |
batch_call(run_one, weights, inputs)
Call run_one(w_i, x_i) for each sample along the first (batch) axis.
It is meant for backends that cannot run a batch at once. It holds the loop
over the samples, and if one sample fails, it raises a RuntimeError that
gives the index of that sample, so the caller can find which sample failed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_one
|
Callable
|
A function that evaluates one sample. |
required |
weights
|
ndarray | Sequence[float]
|
The weights, 1-D for one sample or
2-D for a batch. They must be a NumPy array or a PyTorch tensor,
because the batch is detected from |
required |
inputs
|
ndarray | Sequence[float]
|
The inputs, 1-D or 2-D, in the same
kinds of container as |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The results of the samples, stacked. |
get(name)
Return the provider registered under name.
A backend registered with register_lazy imports its package at the first
lookup, and the backend is kept for later lookups.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
A registered short name, such as |
required |
Returns:
| Type | Description |
|---|---|
Provider
|
The provider instance. |
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
KeyError
|
If nothing is registered under |
load_failure_hint(exc)
Name the fix for a package that is installed but fails to load, if known.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc
|
ImportError
|
The |
required |
Returns:
| Type | Description |
|---|---|
str
|
A parenthesised hint to append to an error message, or |
parameter_shift_input_jacobian(compiled, weights, inputs, four_term_indices=None)
Return the jacobian with respect to the inputs, by shifting each input.
Each entry is d<O_k>/dx_j, computed by shifting inputs[j] directly by
±π/2 (and also by ±3π/2 for an input listed in four_term_indices). The
result is correct only when the angle of every gate that depends on an input
is exactly InputRef(j), not wrapped in Scale, Multiply, Sum or
Negate, and when each InputRef(j) feeds a single gate. When that is not
the case, use the chain-rule path instead.
This simple path does not use shift_gate_angle (which wraps the angle in a
Sum), so it works on backends that do not support symbolic Sum
expressions, such as CUDA Quantum.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compiled
|
CompiledCircuit
|
The prepared circuit to differentiate. |
required |
weights
|
ndarray | Sequence[float]
|
The weight vector, in any
container that |
required |
inputs
|
ndarray | Sequence[float]
|
The input vector, as a NumPy array
or a list. A PyTorch tensor is not accepted, because the vector is
shifted on a copy made with |
required |
four_term_indices
|
frozenset[int] | None
|
The inputs that feed a gate
with several frequencies, as returned by
|
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The jacobian, of shape |
parameter_shift_jacobian(compiled, weights, inputs, four_term_indices=None)
Return the jacobian with respect to the weights, by the parameter-shift rule.
Each entry is d<O_k>/dθ_i, computed with the parameter-shift rule. For a
weight whose gates have a single frequency, the rule takes two evaluations,
(f(θ + π/2) - f(θ - π/2)) / 2. For a weight listed in four_term_indices,
it uses the four-term rule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compiled
|
CompiledCircuit
|
The prepared circuit to differentiate. |
required |
weights
|
ndarray | Sequence[float]
|
The weight vector, as a NumPy
array or a list. A PyTorch tensor is not accepted, because the vector
is shifted on a copy made with |
required |
inputs
|
ndarray | Sequence[float]
|
The input vector, in any container
that |
required |
four_term_indices
|
frozenset[int] | None
|
The weights that feed a gate
with several frequencies, as returned by
|
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The jacobian, of shape |
register(name, provider_or_config)
Register a backend, or a configuration that can build one, under name.
Later calls to Circuit(..., provider=name) then use that backend. If a
configuration is passed, its make_backend() method is called to build the
backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name to register. |
required |
provider_or_config
|
Provider | object
|
A backend instance, or a
configuration with a |
required |
register_lazy(name, loader)
Register a backend under name without importing its package yet.
loader is called the first time the name is looked up, and its result is
kept for later lookups. The optional backends are registered in this way, so
that naming them imports nothing.
It replaces whatever name currently refers to, as register does. If the
registration were skipped when the name was already taken, a caller could not
replace a built-in backend with a lazy one, and would get no sign that the
loader had been ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name to register. |
required |
loader
|
Callable[[], Provider]
|
A function with no arguments that returns the backend. It is called the first time the name is looked up. |
required |
registered_names()
Return every provider name that get accepts, including the lazy ones.
resolve_provider(spec)
Turn a provider (a name, a configuration or a backend) into a backend instance.
A string is looked up in the registry. Any object with a make_backend()
method is treated as a configuration. A backend is used as it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
str | object | Provider
|
A registered name, a configuration with a
|
required |
Returns:
| Type | Description |
|---|---|
Provider
|
The backend instance. |
validate_circuit(circuit, capabilities)
Raise a ValueError if the circuit cannot run on the provider.
The message says clearly why: the circuit uses a gate, or the adjoint of a gate, that the provider does not support, or it needs more qubits than the provider has.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
circuit
|
CircuitSpec
|
The circuit to check. |
required |
capabilities
|
Capabilities
|
What the provider says it supports. |
required |
validate_initial_state(initial_state, n_qubits, *, batch=None)
Check and flatten a statevector that the user gives as a starting state.
It returns a contiguous complex128 array in the flat order of the library,
where qubit 0 is the most significant bit. The shape is (2**n,) when
batch is None, and (batch, 2**n) otherwise. A 1-D state passed with a
batch is repeated for every row of the batch. It raises a ValueError if
the length is wrong or the state is not normalised. A unitary evolution keeps
the norm, so a state that is not normalised is almost always a mistake of the
caller. Each backend reshapes the returned flat array into its own state
layout. Doing the checks here, in one place, keeps the error messages the same
for the NumPy, PennyLane and Qiskit backends.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_state
|
ndarray
|
The statevector to start from. |
required |
n_qubits
|
int
|
The number of qubits of the circuit. |
required |
batch
|
int | None
|
The batch size that the state must match, or |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The flattened statevector, after the checks. |