numpy_backend
eigenkit.core.backends.numpy_backend
The reference NumPy statevector simulator, which is the default backend.
This backend uses only NumPy. It needs neither PyTorch nor an external quantum software development kit. It exists for two reasons:
- It works out of the box. The library runs (
Circuit(provider="numpy")) without installing a large simulator or one that needs a licence. This backend is always available. - It is the reference for correctness. Every other backend is tested against it (and against PennyLane) for equal results, so it also serves as the reference that the others are checked against.
It is written to be easy to read, not tuned for speed. For speed, install one of
the optional backends. It does use two simple NumPy optimisations. A batch of
inputs evolves in one vectorised pass, with a leading batch axis on the state
tensor, instead of a Python loop over the samples. A controlled-NOT (CNOT) gate is
applied by swapping amplitudes (np.flip on the subspace where the control is
1) instead of by contracting a 4x4 matrix.
State representation
The state is a complex128 tensor of shape (2,) * n_qubits, and qubit q is
on axis q. In C order, this makes qubit 0 the most significant bit, as in
PennyLane, where wire 0 is the most significant bit. As a result,
state.reshape(-1)[0] is the amplitude of |0…0⟩, and the computed values agree
with PennyLane to machine precision.
Gradients
The backend declares gradient_method = "adjoint" and implements
NumpyCompiled.jacobian().
This method computes analytic gradients in reverse mode, with one forward pass
and one backward pass, so the number of passes is O(1) in the number of
parameters. The gradient code of the core package uses it in preference to the
parameter-shift rule, which is the fallback, and it differentiates a 2-D inputs
batch in one batched pass. The gradients agree with the parameter-shift rule and
with PennyLane to machine precision.
Example
from eigenkit import Circuit, Encoding, Variational, all_z
blocks = [Encoding(in_features=4), Variational(depth=2)]
# by registered name
circuit = Circuit(blocks=blocks, observables=all_z(4), provider="numpy")
# or with the config, when a device setting matters
from eigenkit.configs import NumpyConfig
circuit = Circuit(
blocks=blocks,
observables=all_z(4),
provider=NumpyConfig(),
)
This backend is always available. It is the default, and it needs no extra.
NumpyCompiled
Bases: CompiledCircuit
A circuit prepared for the reference NumPy simulator.
NumpySimulator.compile()
builds it, and it is cheap to call many times. run() evaluates the
observables, and jacobian() computes their analytic gradient.
statevector(), probabilities() and zero_state_probability() read the
state directly, which the Harrow-Hassidim-Lloyd (HHL) linear solver needs.
__init__(circuit, observables, config)
Prepare the circuit for repeated evaluation.
NumpySimulator.compile() builds this object, so there is no reason to
build one directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
circuit
|
CircuitSpec
|
The circuit to prepare. |
required |
observables
|
Sequence[Observable]
|
The observables to measure. |
required |
config
|
NumpyConfig
|
The device settings. |
required |
jacobian(weights, inputs, *, need_input_grad=False)
Compute the gradient with this backend's own method, the analytic adjoint.
The gradient code of the core package looks for a jacobian method. When
a backend has one, it is used in preference to the parameter-shift rule,
which works on any backend and is the fallback. Here it is the adjoint
method in reverse mode: one forward pass and one backward pass, so the
number of passes is O(1) in the number of parameters.
For a single sample (1-D inputs), the two jacobians have shapes
(n_obs, n_weights) and (n_obs, n_inputs). For a 2-D inputs batch,
they have a leading batch axis ((B, n_obs, n_weights) and
(B, n_obs, n_inputs)), and the whole batch is differentiated in one
pass. This is what makes the training of a hybrid quantum neural network
with PyTorch fast on this backend. The gradients with respect to the gate
angles are mapped onto the weights and inputs with the same symbolic
chain-rule factors as the parameter-shift code, so angles built with
Scale, Multiply, Sum or Negate are also differentiated correctly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features: 1-D for one sample, or 2-D with a leading batch axis. |
required |
need_input_grad
|
bool
|
If True, also compute the jacobian with
respect to the inputs. Default: |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
weight_jacobian |
ndarray
|
The jacobian with respect to the weights. |
input_jacobian |
ndarray | None
|
The jacobian with respect to the inputs, or |
probabilities(weights, inputs)
Return the measurement probabilities of all 2**n_qubits basis states.
Qubit 0 is the most significant bit, as in PennyLane's qml.probs. The
probabilities are used to decode samples from the prepared state, for
example the bitstrings of the Quantum Approximate Optimization Algorithm
(QAOA).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features of one sample (1-D). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The probabilities, of shape |
run(weights, inputs)
Evaluate the circuit and return one expectation value per observable.
It follows the CompiledCircuit contract: the result has shape (n_obs,)
when weights and inputs are both 1-D, and (B, n_obs) otherwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector: 1-D for one sample, or 2-D with a leading batch axis. |
required |
inputs
|
ndarray
|
The input features, with the same 1-D or 2-D
shape convention as |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The expectation values, one for each observable. |
statevector(weights, inputs, initial_state=None)
Return the full complex statevector after the circuit has run.
The result is a flat array of 2**n_qubits amplitudes, with qubit 0 as
the most significant bit (the same order as probabilities()).
initial_state, a normalised flat vector in the same order, makes the
evolution start from that state instead of |0…0⟩. Reading a state out
and passing it back in this way is what quantum reservoir carry-over and
amplitude encoding are built on. A 2-D inputs batch evolves in one pass
and returns an array of shape (B, 2**n_qubits). initial_state can then
be one state of shape (2**n,), shared by all samples, or a stack of
shape (B, 2**n) with one state for each sample.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features: 1-D for one sample, or 2-D with a leading batch axis. |
required |
initial_state
|
ndarray | None
|
The state to start from, instead
of |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The statevector, of shape |
zero_state_probabilities(weights, inputs)
Return the probability of |0…0⟩ for every input of a batch, vectorised.
inputs has shape (B, n_inputs), and the result has shape (B,). The
whole batch evolves in one pass. The quantum kernel uses this method to
build a fidelity matrix without a Python loop over pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The batch of inputs, of shape |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The probabilities, one for each row, of shape |
zero_state_probability(weights, inputs)
Return the probability of measuring the state |0…0⟩.
Fidelity kernels and swap tests are built on it. It does not go through
Observable, because it is not the expectation value of a Pauli operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features of one sample (1-D). |
required |
Returns:
| Type | Description |
|---|---|
float
|
The probability, a float in |
NumpyConfig
dataclass
Configuration for the reference NumPy simulator.
It has no settings yet. It exists so that Circuit(provider=NumpyConfig())
works in the same way as with the configurations of the other backends.
make_backend()
Build the backend that this configuration describes.
The provider layer calls it when a configuration is passed as provider=.
There is no reason to call it directly.
Returns:
| Type | Description |
|---|---|
'NumpySimulator'
|
The |
NumpySimulator
The provider for the reference NumPy statevector simulator.
It is registered automatically as "numpy". It uses only NumPy and no
optional dependency, so it is always available.
It declares gradient_method = "adjoint", so the gradient code of the core
package uses
NumpyCompiled.jacobian()
(analytic, in reverse mode, with one forward pass and one backward pass
whatever the number of parameters) instead of the parameter-shift rule, whose
cost is O(n_params).
capabilities
property
What this backend supports.
It runs every gate of the library, evaluates a batch of inputs in one
call, returns exact expectation values only (it does not sample shots),
and has no limit on the number of qubits. See
Capabilities.
__init__(config=None, name='numpy')
Build the backend from a device configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
NumpyConfig
|
The device settings. Default: |
None
|
name
|
str
|
The registry name, used in error messages. Default:
|
'numpy'
|
compile(circuit, observables)
Prepare circuit for repeated evaluation of observables.
A Circuit calls it once and reuses the result 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 |
Returns:
| Type | Description |
|---|---|
NumpyCompiled
|
The prepared circuit, as a |