Skip to content

core

eigenkit.core

The internal circuit layer of eigenkit.

This package re-exports the classes and functions that users need, and it registers the default backends when it is imported.

The entry points are:

  • Circuit creates a circuit that can be run.
  • CircuitBuilder builds a circuit one gate at a time.
  • AngleEncoding, BasicVariational, StrongVariational, Reuploading, CustomBlock and assemble combine blocks into a circuit.
  • X, Y, Z, all_z, from_string and Observable build observables.
  • PennyLaneConfig, QiskitConfig and the other configurations hold the settings of each backend.
  • register gives a configuration a name, so that it can be used again.
  • BaseQuantumSolver is the base class of every solver that computes its result in a single call.

The top-level provider= argument accepts a registered name, a configuration dataclass, or a backend instance.

AmplitudeEncoding dataclass

Encode a classical vector as the amplitudes of a quantum state.

The vector is padded with zeros to the next power of two. With normalize=True, the default, it is also rescaled to norm 1. The number of qubits follows from the length of the vector: it is max(1, ceil(log2(len(amplitudes)))).

The block adds a single AmplitudeEncoding gate. The data field of the gate holds the prepared amplitudes as a flat tuple, and each backend loads them with its own state preparation method.

Parameters:

Name Type Description Default
amplitudes object

The vector to load, as an array-like. Its length sets the number of qubits, and it is padded with zeros to the next power of two. A ValueError is raised if the vector is empty, if all its values are zero, or if it holds a not-a-number (NaN) or infinite value.

required
normalize bool

If True, the vector is rescaled to norm 1 before it is loaded. Pass False when the vector is already a state. The block then stores it unchanged and checks it: a ValueError is raised if its norm differs from 1 by more than 1e-10. This check exists because, without it, the same mistake would be caught or not depending on the backend: the NumPy simulator rescales the vector without a warning, and PennyLane refuses it. The same circuit would then behave differently on different backends. Default: True.

True
Example
from eigenkit.core.blocks import AmplitudeEncoding, assemble

spec = assemble([AmplitudeEncoding([0.6, 0.8], normalize=False)])

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
'IRFragment'

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'AmplitudeEncoding'

The rebuilt AmplitudeEncoding.

required_qubits()

Return the number of qubits that the padded vector needs.

It is ceil(log2(len(amplitudes))), and at least 1.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

AngleEncoding dataclass

Encode in_features data values as single-qubit rotations.

Feature i sets the angle of one rotation on qubit i, so the block uses the qubits 0 to in_features - 1. This matters only when the block is combined with others. A circuit has as many qubits as its widest block, and when another block in the list needs more qubits, this block leaves the extra qubits unchanged.

The block takes in_features inputs and no weights.

Parameters:

Name Type Description Default
in_features int

The number of features to encode, one per qubit. It must be positive, and a ValueError is raised otherwise.

required
rotation GateName

The rotation gate: "RX", "RY" or "RZ". Default: "RY".

'RY'
scale float

A constant factor applied to every input angle. It is useful when the data is normalised to [0, 1] and the circuit needs angles in [0, 2π]. It must be finite. Default: 1.0.

1.0

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

The qubits with an index of in_features or more are left unchanged.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
IRFragment

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'AngleEncoding'

The rebuilt AngleEncoding.

required_qubits()

Return in_features, because the block uses one qubit per feature.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

BaseQuantumSolver

Bases: ABC

Base class for quantum solvers that compute a result in a single call.

Unlike BaseQuantumEstimator, a solver has no training phase and no fitted state, and it does not follow the scikit-learn estimator interface. It receives a mathematical problem directly in solve() and returns a structured result object.

Four classes inherit from it: HHL, IQAE, QAOA and VQE. PortfolioRiskEstimator does not, by design. It is a scikit-learn-style estimator with fit(), estimate() and var_at(), and it has no solve() method. As a result, isinstance(x, BaseQuantumSolver) tells you whether an object can be given a problem directly, and it returns False for the estimator.

Each subclass sets its own provider default in its signature and passes it to this class. All of them use "numpy". Two of the modes of IQAE, a callable oracle and shot_mode="provider", need provider="pennylane".

Parameters:

Name Type Description Default
provider ProviderSpec

The backend to use: a registered name ("numpy", "pennylane", "qiskit" and so on), a config instance, or a backend object. The default is "numpy", the built-in simulator that the core installation includes.

'numpy'
Example
from eigenkit.core import BaseQuantumSolver
from eigenkit.optimization import QAOA

isinstance(QAOA(), BaseQuantumSolver)   # True

solve(*args, **kwargs) abstractmethod

Compute and return the solution.

Each subclass declares the signature that its problem needs. HHL takes a matrix and a right-hand side, IQAE takes an oracle, QAOA takes a problem, and VQE takes a Hamiltonian and the number of qubits.

BasicVariational dataclass

depth trainable layers of single-qubit rotations and entangling gates.

Each layer applies one trainable rotation to every qubit, then a controlled-NOT (CNOT) gate from each qubit to the next one. The block takes depth * n_qubits weights and no inputs.

Parameters:

Name Type Description Default
depth int

The number of layers. It must be positive, and a ValueError is raised otherwise.

required
rotation GateName

The rotation gate of the trainable layer: "RX", "RY" or "RZ". Default: "RY".

'RY'

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

The layers act on every qubit of the circuit.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
IRFragment

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'BasicVariational'

The rebuilt BasicVariational.

required_qubits()

Return 1, because the block extends to all the qubits of the circuit.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

Circuit

A compiled circuit, ready to evaluate.

It holds a CircuitSpec, the observables to measure and a compiled backend handle. The circuit is compiled once, when the instance is created, and every call reuses the compiled handle. The instance does not store weights: you pass the weights at each call.

This is the NumPy interface. The top-level Circuit(...) function of the package returns this class for interface="numpy" and a TorchCircuit for interface="torch".

Parameters:

Name Type Description Default
spec CircuitSpec

The circuit to run, usually built with assemble(blocks).

required
observables ObservableLike

What to measure. None measures Z on every qubit and returns one value per qubit. A single Observable or PauliExpr returns a single value, and a list of them returns one value for each. Default: None.

None
provider ProviderSpec

The backend that runs the circuit. It can be a registered name ("numpy", "pennylane" and others), a configuration instance, or a provider object. Default: "numpy".

'numpy'
interface Interface

How a call is evaluated. With "numpy", a call runs the NumPy path and returns NumPy values. With "torch", each call is passed to the registered PyTorch interface, and TorchCircuit builds its core circuit in this way. The top-level Circuit function always passes "numpy". Default: "numpy".

'numpy'
Example
import numpy as np
from eigenkit import Circuit, Encoding, Variational, all_z

circuit = Circuit(
    blocks=[Encoding(in_features=2), Variational(depth=1)],
    observables=all_z(2),
)

# The circuit stores no weights, so you pass them at every call.
weights = np.zeros(circuit.spec.n_weights)
values = circuit(weights, np.array([0.1, 0.2]))

# Gradients, without PyTorch.
grad = circuit.jacobian(weights, np.array([0.1, 0.2]))

draw(*, decimals=3)

Return a text drawing of the circuit, over several lines.

The drawing does not depend on the provider: it is built from the CircuitSpec, not from the compiled backend object. See eigenkit.core.draw for the details of the layout.

Parameters:

Name Type Description Default
decimals int

The number of significant digits shown for a numeric gate angle. Default: 3.

3

Returns:

Type Description
str

The drawing, as a single string that contains newlines.

draw_mpl(**kwargs)

Draw the circuit as a matplotlib figure, and return (fig, ax).

Gates are drawn as labelled boxes and controls as filled dots, and each measured wire (from self.observables) ends with a measurement symbol. As with draw(), the picture is built from the CircuitSpec and the observables, not from the compiled backend, so it does not depend on the provider.

Pass style="eigen" for the brand colours (each gate is coloured by its kind: rotations, H and block gates, and measurements), or style="roles" to colour the outline of each gate by its role (encoding, trainable or fixed). The default is black on white. Each style has a "-dark" variant (for example "eigen-dark") for dark backgrounds, and "eigen-universal" is a single image that works on both.

matplotlib is an optional dependency. Install it with eigenkit[viz].

Parameters:

Name Type Description Default
**kwargs Any

Arguments passed to draw_mpl(): decimals, show_params, show_measurements, fontsize, style, legend and ax.

{}

Returns:

Name Type Description
fig_ax tuple

The matplotlib (fig, ax) pair.

dry_run_info()

Describe the prepared circuit in a dictionary, without submitting it.

It is available only on backends whose CompiledCircuit provides a dry_run_info method. Currently, these are Braket (on its local simulator as well as on cloud devices), Qiskit Runtime and IQM. It is useful to check the size of the circuit, the gate counts after transpilation, the number of shots and the number of circuits that each call runs, before you submit to a cloud simulator or a quantum processing unit (QPU). It does not estimate a price.

jacobian(weights, inputs=None, *, input_grad=False)

Return the gradient of the expectation values with respect to the weights.

It returns d<O>/d(weights), of shape (n_weights,) for a single observable or (n_observables, n_weights) for a list of observables, like the output of __call__(). Pass a single 1-D inputs sample. For a batch, call it once for each row.

The NumPy interface can compute every gradient without PyTorch. This method uses the same sequence of gradient methods as the variational optimisation tools and the PyTorch interface. It uses the backend's own differentiation when the backend has one (the analytic adjoint method of the NumPy simulator, the differentiation of PennyLane, and so on), and otherwise the parameter-shift rule, which works on any backend.

With input_grad=True, it returns (weight_jacobian, input_jacobian), where input_jacobian is d<O>/d(inputs), with the same leading shape.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray | None

One 1-D sample, of shape (n_inputs,). This method takes a single sample, so for a batch, call it once for each row. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None.

None
input_grad bool

If True, also return the jacobian with respect to the inputs. Default: False.

False

Returns:

Name Type Description
jacobian ndarray | tuple[ndarray, ndarray]

The weight jacobian, or (weight_jacobian, input_jacobian) when input_grad is True.

Raises:

Type Description
RuntimeError

On a hardware backend whose configuration leaves enable_gradients off (gradient_method="none").

probabilities(weights, inputs=None)

Return the measurement distribution over all 2**n_qubits basis states.

The basis states are ordered with qubit 0 as the most significant bit. The distribution is used to decode samples. For example, the quantum approximate optimisation algorithm (QAOA) reads the most probable bitstrings from the optimised state.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray | None

One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None.

None

Returns:

Type Description
ndarray

The probabilities of all basis states, of shape (2**n_qubits,).

Raises:

Type Description
NotImplementedError

On a backend that has neither a probabilities() method nor a state vector to compute the probabilities from.

resources(**kwargs)

Report the resources that the circuit uses once transpiled for its target.

It reports the native gate counts, the number of two-qubit gates and the two-qubit depth, the physical qubits chosen by the layout, the number of two-qubit gates added by routing (compared with the same circuit on the native gates with all-to-all connectivity), and the success probability predicted by the error rates of the target. Nothing is submitted.

Parameters:

Name Type Description Default
**kwargs Any

Arguments passed to the backend, for example measured=False on Qiskit Runtime for the bare estimator circuit.

{}

Returns:

Type Description
dict

A dictionary. See QiskitRuntimeCompiled.resources() for its keys.

Raises:

Type Description
NotImplementedError

On backends that do not transpile for a device, which includes every simulator. To inspect a device locally, use Qiskit Runtime or IQM with a fake backend_instance.

sample(weights, inputs=None)

Return the raw measurement histogram, which maps each bitstring to a count.

It is what a sampling backend measured, before any averaging. These are the counts behind probabilities(), and it is the only form that stays usable when the circuit has too many qubits for a dense distribution. In each bitstring, qubit 0 is the most significant bit, so "100" means that qubit 0 was measured in state 1.

Only sampling backends can return a histogram, because it needs a finite number of shots. Exact statevector simulators raise an error instead of making up a histogram. Use probabilities() with those.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray | None

One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None.

None

Returns:

Name Type Description
counts dict[str, int]

The number of shots for each measured bitstring. The counts add up to the number of shots set for the backend.

Raises:

Type Description
NotImplementedError

On backends that do not sample. This includes every exact simulator, which has no shots to report.

statevector(weights, inputs=None, initial_state=None)

Return the full complex statevector after the circuit runs.

It is a flat array of length 2**n_qubits, ordered with qubit 0 as the most significant bit. A 2-D inputs batch returns an array of shape (B, 2**n_qubits). With initial_state, a normalised flat statevector in the same order, the evolution starts from that state instead of |0…0⟩. Reading a state out and passing it back in this way is the basic operation behind the state carried over in quantum reservoirs and behind amplitude encoding.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray | None

A 1-D sample, or a 2-D batch. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None.

None
initial_state ndarray | None

The state to start the evolution from, instead of |0…0⟩. It is a normalised flat statevector in the same order. Default: None.

None

Returns:

Type Description
ndarray

The statevector, of shape (2**n_qubits,), or (B, 2**n_qubits) for a batch.

Raises:

Type Description
NotImplementedError

On backends without access to the statevector, which is the case for most quantum processing unit (QPU) and cloud backends.

submit(weights, inputs=None)

Submit a job without waiting for it, and return a JobHandle.

handle.result() returns the expectation value of each observable.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray | None

One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None.

None

Returns:

Name Type Description
handle JobHandle

A handle for the submitted job.

Raises:

Type Description
NotImplementedError

If the CompiledCircuit of the backend does not provide submit. Currently, Braket, Qiskit Runtime and IQM provide it.

transpiled_circuit(**kwargs)

Return the qiskit.QuantumCircuit that a hardware backend submits.

The circuit is transpiled for the target of the backend. Use it to draw the circuit (.draw("mpl")) or to inspect it.

Raises:

Type Description
NotImplementedError

On backends that do not transpile for a device.

zero_state_probabilities(weights, inputs)

Compute zero_state_probability() for every row of a batch of inputs.

inputs has shape (B, n_inputs), and the result is an array of shape (B,) of all-zeros probabilities. Backends that provide a batched zero_state_probabilities (such as the NumPy simulator) compute the whole batch at once. The other backends call the single-sample method once for each row. This is what lets QuantumKernel build a fidelity matrix without a Python loop over pairs of samples.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray

A batch, of shape (B, n_inputs).

required

Returns:

Type Description
ndarray

One all-zeros probability for each row, as an array of shape (B,).

zero_state_probability(weights, inputs=None)

Return the probability of measuring the all-zeros state after the circuit.

It is useful for quantities based on fidelity: quantum kernels built from inversion circuits, swap tests and the Bures distance. Each backend computes it in its own way. PennyLane returns qml.probs(...)[0], the Qiskit and NumPy simulators return the squared amplitude of the all-zeros basis state, Braket uses its own probability result, and IQM and Qiskit Runtime estimate it from shots. This method does not go through the Observable path, because the value is not the expectation value of a Pauli operator.

Parameters:

Name Type Description Default
weights ndarray

The weight vector, of shape (n_weights,).

required
inputs ndarray | None

One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None.

None

Returns:

Type Description
float

The probability, a float in [0, 1].

Raises:

Type Description
NotImplementedError

If the active backend does not provide zero_state_probability.

CircuitBuilder

Build a CircuitSpec one gate at a time.

Example
qc = CircuitBuilder(n_qubits=3)
weights = qc.add_weights(6)
inputs = qc.add_inputs(3)
for qubit in range(3):
    qc.ry(inputs[qubit], qubit=qubit)
for depth in range(2):
    for qubit in range(3):
        qc.ry(weights[depth * 3 + qubit], qubit=qubit)
    for qubit in range(2):
        qc.cnot(qubit, qubit + 1)
spec = qc.build()

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being built. Every qubit index that a gate uses must be between 0 and n_qubits - 1.

required

Raises:

Type Description
ValueError

If n_qubits is not positive.

n_inputs property

The number of input features that add_inputs() has reserved so far.

The built CircuitSpec has the same number.

n_qubits property

The number of qubits of the circuit, as given to the constructor.

Every qubit index that a gate uses must be smaller than this number, and the number never changes.

n_weights property

The number of trainable weights that add_weights() has reserved so far.

The built CircuitSpec has the same number.

add_inputs(count)

Reserve count input features and return references to them.

This is the counterpart of add_weights() for data instead of trainable parameters.

Parameters:

Name Type Description Default
count int

The number of input features to reserve.

required

Returns:

Type Description
list[InputRef]

One InputRef for each reserved feature, in order.

add_weights(count)

Reserve count trainable weights and return references to them.

Pass a returned WeightRef wherever a gate takes an angle. The indices are local to this builder and start at 0. When the block is combined with others, assemble shifts them to their positions in the whole circuit.

Parameters:

Name Type Description Default
count int

The number of weights to reserve.

required

Returns:

Type Description
list[WeightRef]

One WeightRef for each reserved weight, in order.

build()

Return the current state of the builder as an immutable CircuitSpec.

cnot(control, target)

Add a controlled-NOT (CNOT) gate.

Parameters:

Name Type Description Default
control int

The qubit that controls the flip.

required
target int

The qubit that is flipped.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

Raises:

Type Description
ValueError

If control and target are the same qubit.

cphase(angle, control, target)

Add a controlled phase shift gate.

The gate is symmetric: the control and the target play the same role.

Parameters:

Name Type Description Default
angle ParameterLike

The phase. It can be a number or a symbolic reference.

required
control int

One of the two qubits.

required
target int

The other qubit. The gate acts in the same way if the two qubits are exchanged.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

cry(angle, control, target)

Add a controlled RY gate.

Parameters:

Name Type Description Default
angle ParameterLike

The rotation angle. It can be a number or a symbolic reference.

required
control int

The qubit that controls the rotation.

required
target int

The qubit that is rotated.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

h(qubit)

Add a Hadamard gate.

Parameters:

Name Type Description Default
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

mcx(*qubits)

Add a multi-controlled X gate.

All the qubits except the last one are controls.

Parameters:

Name Type Description Default
*qubits int

Two or more distinct qubits. The controls come first, and the target is the last one.

()

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

Raises:

Type Description
ValueError

If fewer than two qubits are given, or if a qubit is repeated.

mcz(*qubits)

Add a multi-controlled Z gate.

All the qubits except the last one are controls.

Parameters:

Name Type Description Default
*qubits int

Two or more distinct qubits. The controls come first, and the target is the last one.

()

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

Raises:

Type Description
ValueError

If fewer than two qubits are given, or if a qubit is repeated.

pauli_exponential(paulis, angle, coefficient=1.0)

Add the exponential of a product of Pauli operators.

The gate is exp(-i * coefficient * angle * P), where P is the product of paulis. Each element of paulis is a pair (op, qubit), where op is "X", "Y" or "Z", and the qubits are distinct.

This is the Trotter building block that the quantum approximate optimisation algorithm (QAOA) needs for its cost and mixer layers, and that the unitary coupled-cluster singles and doubles (UCCSD) ansatz needs for its excitation operators. It is built from the core gates only. First, H gates (for X) and RX(π/2) gates (for Y) change each factor to the Z basis. Then a ladder of controlled-NOT (CNOT) gates accumulates the parity on the last qubit, and a central RZ(2 * coefficient * angle) is applied (because RZ(φ) = exp(-i φ Z / 2)). Finally, the inverse of the first steps is applied.

Parameters:

Name Type Description Default
paulis Sequence[tuple[str, int]]

The Pauli product, as (operator, qubit) pairs, where the operator is "X", "Y" or "Z" and the qubits are distinct. An empty sequence is the identity, and nothing is added.

required
angle ParameterLike

The rotation angle. It can be a number or a symbolic reference.

required
coefficient float

A number that multiplies the angle. This is where the weight of a Hamiltonian term goes. Default: 1.0.

1.0

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

phase_shift(angle, qubit)

Add a phase shift gate R(φ).

The gate multiplies |1⟩ by e^(iφ) and leaves |0⟩ unchanged.

Parameters:

Name Type Description Default
angle ParameterLike

The phase φ. It can be a number or a symbolic reference.

required
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

rx(angle, qubit)

Add a rotation about the X axis.

Parameters:

Name Type Description Default
angle ParameterLike

The rotation angle. It can be a number, or a WeightRef, an InputRef or an expression, which is evaluated when the circuit is called.

required
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

ry(angle, qubit)

Add a rotation about the Y axis.

Parameters:

Name Type Description Default
angle ParameterLike

The rotation angle. It can be a number, or a WeightRef, an InputRef or an expression.

required
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

rz(angle, qubit)

Add a rotation about the Z axis.

Parameters:

Name Type Description Default
angle ParameterLike

The rotation angle. It can be a number, or a WeightRef, an InputRef or an expression.

required
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

s(qubit)

Add an S gate, which is a quarter turn about the Z axis.

Its inverse is sdg().

Parameters:

Name Type Description Default
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

sdg(qubit)

Add an S-dagger gate (Sdg), which is the inverse of s().

Parameters:

Name Type Description Default
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

swap(qubit0, qubit1)

Add a SWAP gate, which exchanges the states of two qubits.

Parameters:

Name Type Description Default
qubit0 int

The first qubit.

required
qubit1 int

The second qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

to_block()

Wrap the current state of the builder in a CustomBlock.

This is useful to build a part of a circuit one gate at a time, and then to combine it with other blocks. When the blocks are assembled, the indices of the WeightRef and InputRef objects of this block are shifted to their positions in the whole circuit.

x(qubit)

Add a Pauli X gate, which is a bit flip.

Parameters:

Name Type Description Default
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

z(qubit)

Add a Pauli Z gate, which is a phase flip.

Parameters:

Name Type Description Default
qubit int

The target qubit.

required

Returns:

Type Description
'CircuitBuilder'

This builder, so that calls can be chained.

CircuitSpec dataclass

A whole circuit as data.

It holds the list of gates, the number of qubits, and the lengths of the weight and input vectors that the gates refer to. Every provider compiles this object, and assemble builds it from a list of blocks. It holds no weights and no observables: the weights are passed at each call, and the observables are set by the Circuit that wraps the spec.

Attributes:

Name Type Description
n_qubits int

The number of qubits of the circuit.

n_weights int

The length of the weight vector that the gates refer to through WeightRef.

n_inputs int

The length of the input vector that the gates refer to through InputRef.

gates tuple[Gate, ...]

The gates, in the order in which they are applied.

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.

CustomBlock dataclass

A block made from the gates of a CircuitBuilder.

CircuitBuilder.to_block() creates it. It is useful when you want to write a part of a circuit gate by gate and combine it with the standard blocks.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits that the builder was given, which is also the number of qubits that this block needs. It must be positive.

required
gates tuple[Gate, ...]

The gates that the builder added, in the order in which they are applied.

required
n_weights int

The number of trainable weights that these gates use. It must not be negative.

required
n_inputs int

The number of input features that these gates use. It must not be negative.

required

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
IRFragment

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

required_qubits()

Return n_qubits, the number of qubits that the builder was given.

Grover dataclass

The Grover operator Q = A · S₀ · A† · S_ψ₁ used for amplitude estimation.

This is the operator that iterative quantum amplitude estimation (IQAE) uses. The block builds it from a state-preparation oracle A, given as a CircuitSpec, and from these standard parts:

  • S_ψ₁ flips the phase of the ancilla qubit, which is the last qubit. It is the gate Z(n-1).
  • A† is the adjoint of the oracle. It uses the same weights and inputs as A.
  • S₀ is the reflection about the all-zero state: X^⊗n → MCZ → X^⊗n, where MCZ is a multi-controlled Z gate.
  • A is the oracle.

The gates are applied to the state in this order, from left to right:

Z(ancilla) → A† → S₀ → A

A and A† share their weights and inputs, so fragment.n_weights == a_circuit.n_weights: the count is not doubled.

Parameters:

Name Type Description Default
a_circuit CircuitSpec

The state-preparation oracle A, as a CircuitSpec with at least one qubit. Its last qubit is used as the ancilla.

required
Example
from eigenkit.core.blocks import Grover, assemble
from eigenkit.core.builder import CircuitBuilder

qb = CircuitBuilder(n_qubits=2)
qb.h(0).cnot(0, 1)
spec = assemble([Grover(a_circuit=qb.build())])

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

The oracle A and its adjoint A† share their weights and inputs, so these are not counted twice.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
'IRFragment'

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'Grover'

The rebuilt Grover.

required_qubits()

Return the number of qubits of the oracle.

The operator adds no qubits of its own.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

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.

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 for this configuration.

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, which uses NumpyConfig().

None
name str

The registry name, used in error messages. Default: "numpy".

'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 NumpyCompiled object.

Observable dataclass

A Hermitian operator to measure, written as a sum of Pauli terms.

You can build one with the z, x and y methods for a single Pauli operator on one qubit, with from_string for a whole sum, or with the X, Y and Z functions of eigenkit.core.observables, which support arithmetic.

Attributes:

Name Type Description
terms tuple[PauliTerm, ...]

The Pauli terms of the sum. It can be empty: jordan_wigner returns an empty observable, together with an offset, when the operator reduces to a multiple of the identity. However, Circuit refuses to measure an empty observable, because there is nothing to measure.

from_string(text) classmethod

Parse a sum of Pauli terms written as a string.

Examples of such strings are "0.5 Z0 X1 + 0.3 Y2" and "-Z0 + Z1".

The grammar is as follows. The terms are joined by + or -. Each term starts with an optional numeric coefficient, in plain or scientific notation (1e-3 is accepted). The coefficient is followed by an optional * and by one or more [XYZ]<qubit> operators on distinct qubits. Any amount of whitespace is accepted, including none.

Every part of the string must be a valid term, and every term must contain Pauli operators. A constant term ("0.5 + Z0", "0.5 I") raises an error instead of being dropped from the sum: an Observable holds only Pauli terms, and the constant must be kept next to it as an offset.

Parameters:

Name Type Description Default
text str

The string to parse.

required

Returns:

Type Description
'Observable'

The parsed observable.

Raises:

Type Description
ValueError

If text is empty, if it contains a constant term, if a part of it is not a valid term, or if a term uses the same qubit twice.

x(qubit) classmethod

Return the observable X on the single qubit qubit.

Parameters:

Name Type Description Default
qubit int

The index of the qubit.

required

y(qubit) classmethod

Return the observable Y on the single qubit qubit.

Parameters:

Name Type Description Default
qubit int

The index of the qubit.

required

z(qubit) classmethod

Return the observable Z on the single qubit qubit.

Parameters:

Name Type Description Default
qubit int

The index of the qubit.

required

QubitUnitary dataclass

Apply any unitary matrix to the given qubits.

The matrix is stored in the data field of the gate as a nested tuple, so that the Gate stays hashable.

Parameters:

Name Type Description Default
unitary object

A 2^n × 2^n matrix, as an array-like, where n is len(wires). It is checked to be unitary when the block is created.

required
wires tuple[int, ...]

The target qubits, in the order that the matrix expects.

required
Example
import numpy as np
from eigenkit.core.blocks import QubitUnitary, assemble

U = np.array([[0, 1], [1, 0]])  # the Pauli X matrix
spec = assemble([QubitUnitary(U, wires=(0,))])

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
'IRFragment'

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'QubitUnitary'

The rebuilt QubitUnitary.

required_qubits()

Return max(wires) + 1, one more than the highest target qubit.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

Reuploading dataclass

Data reuploading: the input is encoded several times, between trainable layers.

The block repeats an encoding of the input followed by a variational layer, depth times. Each encoding can have a trainable scale for each feature and repetition.

The arguments give two patterns:

  1. Standard reuploading (the default; Pérez-Salinas et al. 2020). The same input is encoded depth times, with new variational weights between the encodings.
  2. Trainable feature scaling (trainable_scale=True; Schuld et al. 2021). Each encoding rotation takes the angle λ_{d,i} * x_i, where each pair of repetition d and feature i has its own trainable λ_{d,i}.

Parameters:

Name Type Description Default
in_features int

The total number of input features. It must be positive.

required
depth int

The number of times the whole sequence is repeated. It must be positive.

required
encoding_rotation GateName

The rotation gate of the encoding: "RX", "RY" or "RZ". Default: "RY".

'RY'
variational_rotation GateName

The rotation gate of the variational layer: "RX", "RY" or "RZ". It is used only when variational_type="basic". Default: "RY".

'RY'
variational_type str

"basic", for one rotation per qubit followed by a ladder of controlled-NOT (CNOT) gates, or "strong", for RZ, RY and RZ rotations on every qubit followed by a ladder of CNOT gates. Default: "basic".

'basic'
trainable_scale bool

If True, each encoding rotation gets a trainable scale for each repetition and each feature. The CUDA Quantum backend does not support it, because it allows only limited arithmetic on parameters. Default: False.

False
Weight count
  • With variational_type="basic" and no trainable scale, the block has depth * in_features weights.
  • With variational_type="strong" and no trainable scale, it has 3 * depth * in_features weights.
  • trainable_scale=True adds depth * in_features weights.

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
IRFragment

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'Reuploading'

The rebuilt Reuploading.

required_qubits()

Return in_features: one qubit for each feature.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

S0 dataclass

Reflection about the all-zero state |0…0⟩, built as X^⊗n → MCZ → X^⊗n.

The block applies X to every qubit, then a multi-controlled Z gate (MCZ) on all of them, then X to every qubit again. It acts on the qubits 0 to n_qubits - 1, which are all the qubits of the circuit unless another block in the list needs more. This reflection is the S₀ part of the Grover operator used by iterative quantum amplitude estimation (IQAE). The Grover block adds the same gates itself.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the reflection. Every one of them takes part. It must be positive.

required
Example
from eigenkit.core.blocks import S0, assemble

spec = assemble([S0(n_qubits=3)])

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
'IRFragment'

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'S0'

The rebuilt S0.

required_qubits()

Return n_qubits, the number of qubits that the reflection acts on.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

StrongVariational dataclass

depth trainable layers of RZ, RY and RZ rotations and entangling gates.

Each layer applies the rotations RZ, RY and RZ to every qubit, then a controlled-NOT (CNOT) gate from each qubit to the next one. The block is modelled on PennyLane's StronglyEntanglingLayers.

The gates are added one layer at a time: all the RZ rotations, then all the RY rotations, then all the RZ rotations, then the CNOT gates. They are not added as three rotations per qubit. The result is the same, because rotations on different qubits commute. The layered order lets the backends that support it apply each layer of rotations in one fast call, in place of 3 * n_qubits calls on single qubits.

The block takes 3 * depth * n_qubits weights and no inputs.

Parameters:

Name Type Description Default
depth int

The number of layers. It must be positive, and a ValueError is raised otherwise.

required

build(n_qubits)

Build the gates of this block for a circuit with n_qubits qubits.

The layers act on every qubit of the circuit.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list.

required

Returns:

Type Description
IRFragment

The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit.

from_dict(d) classmethod

Rebuild a block from the output of to_dict().

Parameters:

Name Type Description Default
d dict

A dictionary returned by to_dict().

required

Returns:

Type Description
'StrongVariational'

The rebuilt StrongVariational.

required_qubits()

Return 1, because the block extends to all the qubits of the circuit.

to_dict()

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the type key selects the block class whose from_dict() rebuilds the block.

Returns:

Type Description
dict

The fields of the block, and its type.

VariationalResult dataclass

Bases: ValueEquality

Outcome of minimize_expectation().

The result is read-only. Assigning to a field raises FrozenInstanceError, and writing into an array field raises ValueError. This prevents a mistyped = (instead of ==) or an in-place edit from leaving a result whose scalar fields describe a different run from the array it holds. To get a writable copy of the array, use np.array(result.field).

Results compare by value, including their arrays. They are not hashable, because a result is a collection of numbers and not a key, and a correct hash of a float array is expensive to compute.

Attributes:

Name Type Description
optimal_value float

The lowest expectation value reached. When the observable is a Hamiltonian, it is the estimated ground-state energy.

optimal_parameters ndarray

The weights that produced this value, of shape (n_weights,).

n_iterations int

The number of iterations reported by the SciPy optimiser. When there is no such count (a custom callable, "spsa", or a SciPy method that does not report one), it is the number of cost evaluations, as in n_evaluations.

n_evaluations int

The number of circuit evaluations that the optimiser made. With a SciPy method it is usually higher than n_iterations, because a line search costs several evaluations per step. Evaluations made only for bookkeeping are not counted, so a custom optimiser that never called the cost function reports 0.

success bool

Whether the run converged. A SciPy optimiser reports it. A custom callable reports it only if it returns (parameters, converged), and otherwise it is inferred from whether the final cost is lower than the starting cost. A False with a plausible energy usually means that the maximum number of iterations was reached.

message str

The termination message of the optimiser. For a custom callable that reported nothing, it gives the cost before and after the run.

history tuple[float, ...]

The expectation value at every evaluation, in order. Plot it to see whether the run converged or stalled. It is a tuple, so nothing can be appended to it after the run.

__post_init__()

Make the array field read-only.

Freezing the dataclass prevents a field from being reassigned, but it does not prevent writing into the array that the field holds, and a result that has been written into still reports the scalar values of the original run. The result therefore keeps its own copy, so the array it was built from can still be changed by the caller. To get a writable copy, use np.array(result.optimal_parameters).

X(qubit)

Return Pauli X on qubit, as an expression that can be added and scaled.

Parameters:

Name Type Description Default
qubit int

The index of the qubit.

required

Returns:

Type Description
PauliExpr

The expression.

Y(qubit)

Return Pauli Y on qubit, as an expression that can be added and scaled.

Parameters:

Name Type Description Default
qubit int

The index of the qubit.

required

Returns:

Type Description
PauliExpr

The expression.

Z(qubit)

Return Pauli Z on qubit, as an expression that can be added and scaled.

Parameters:

Name Type Description Default
qubit int

The index of the qubit.

required

Returns:

Type Description
PauliExpr

The expression.

__getattr__(name)

Return the objects that are re-exported only when they are first accessed.

These are the backend classes and configurations. Because of this, importing core does not import any framework until one is needed.

adjoint(circuit)

Reverse the order of the gates and invert each gate.

The transform does not depend on the provider. It returns another CircuitSpec that any backend can compile, so the transform does not have to be written again for each backend.

Each type of gate is inverted as follows:

  • Rotations with parameters (RX, RY, RZ, PhaseShift, CRY, CPHASE): the angle parameters are negated.
  • Gates without parameters (H, X, Z, CNOT, SWAP, MCZ, MCX): they are copied unchanged, because applying them twice gives the identity. S and Sdg are not their own inverse, so each one is replaced by the other.
  • QubitUnitary(U): the stored matrix is replaced by its conjugate transpose U†. It is computed without NumPy, so that the intermediate representation stays pure Python.
  • ControlledQubitUnitary(U): the target unitary (data[0]) is replaced by its conjugate transpose, and the control values (data[1]) are kept unchanged.
  • AmplitudeEncoding: the transform flips gate.adjoint and leaves the inversion to the backend. PennyLane, for example, applies the conjugate transpose of the state-preparation unitary. The Qiskit backend does not support this adjoint: Circuit rejects such a circuit with a ValueError, and the backend itself raises NotImplementedError.

Parameters:

Name Type Description Default
circuit CircuitSpec

The circuit to invert.

required

Returns:

Type Description
CircuitSpec

The inverted circuit, with the gates in reverse order.

all_z(n_qubits)

Return one Z observable for each qubit.

This is useful as a default measurement.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the circuit.

required

Returns:

Type Description
list[Observable]

One Observable for each qubit.

assemble(blocks)

Combine blocks into a single CircuitSpec.

A CircuitBuilder can take the place of a block. It is replaced by the result of its to_block(), so a part of a circuit written gate by gate can go directly in the list.

The blocks are combined as follows:

  • n_qubits is the largest required_qubits() of the blocks.
  • The weights and the inputs of the blocks are placed one after the other, in the order of the blocks.
  • The local WeightRef and InputRef indices of each block are shifted by the number of weights and inputs of the blocks before it.

Parameters:

Name Type Description Default
blocks Iterable[Block | CircuitBuilder]

The blocks to combine, in the order in which they are applied. Any of them can be a CircuitBuilder, which is taken as it is when assemble is called.

required

Returns:

Type Description
CircuitSpec

One CircuitSpec that contains all the blocks.

Raises:

Type Description
ValueError

If blocks is empty.

Example
from eigenkit import Encoding, Variational
from eigenkit.core import assemble

spec = assemble([Encoding(in_features=4), Variational(depth=2)])
print(spec.n_qubits, spec.n_weights, spec.n_inputs)  # 4 8 4

from_string(text)

Parse an observable from a string.

This function is a shortcut for Observable.from_string.

Parameters:

Name Type Description Default
text str

The string to parse.

required

Returns:

Type Description
Observable

The parsed observable.

minimize_expectation(circuit, *, initial_parameters=None, optimizer='L-BFGS-B', max_iterations=None, tol=None, random_state=42, callback=None)

Minimise <H>(θ) for a circuit whose parameters are all variational.

Parameters:

Name Type Description Default
circuit Circuit

A core NumPy Circuit whose single observable is the Hamiltonian H and whose spec.n_inputs == 0 (every parameter is a variational weight). It works with any provider.

required
initial_parameters ndarray | None

The starting θ, of shape (n_weights,). When it is not given, it is drawn at random with the seed random_state.

None
optimizer Optimizer

The name of a scipy.optimize.minimize method (the default is "L-BFGS-B"), "spsa" for spsa(), or a callable optimizer(cost, theta0, gradient) -> parameters for a custom or framework optimiser. SPSA needs no gradient: each iteration evaluates the circuit twice for the gradient estimate and once more to record the history. A callable can instead return (parameters, converged) to report convergence itself. Otherwise, success is inferred from whether the final cost is lower than the starting cost, because a vector alone says nothing about convergence.

'L-BFGS-B'
max_iterations int | None

The maximum number of iterations. It is passed to SciPy as options={"maxiter": ...}, and to spsa() as maxiter, which is 100 when max_iterations is None.

None
tol float | None

The tolerance, passed to scipy.optimize.minimize.

None
random_state int | None

The seed for the random starting point, which also seeds the perturbations of "spsa". None draws an unseeded starting point.

42
callback Callable[[ndarray], None] | None

An optional function called after every iteration with the current parameters. It is passed to SciPy, and "spsa" also calls it.

None

Returns:

Type Description
VariationalResult

A VariationalResult with the optimal value, the parameters, and the history of the cost at every evaluation.

Example
from eigenkit import Circuit, Variational
from eigenkit.core.ir import Observable
from eigenkit.core.variational import minimize_expectation

# One qubit, one trainable rotation, H = Z. The minimum is -1.
circuit = Circuit(
    blocks=[Variational(depth=1)],
    observables=Observable.from_string("Z0"),
    provider="numpy",
)

result = minimize_expectation(circuit, random_state=42)

print(round(result.optimal_value, 6))        # -1.0
print(result.success, result.n_iterations)   # True 4

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 make_backend() method.

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.