Skip to content

torch

eigenkit.torch

The PyTorch package: the PyTorch interface and the nn.Module circuit.

Importing this package registers the "torch" differentiation interface with the core, so that Circuit(..., interface="torch") works, and it makes the nn.Module circuit, TorchCircuit, available. This package is an optional layer on top of the NumPy core, and everything in it needs PyTorch.

TorchCircuit

Bases: Module

A quantum layer that stores trainable weights and runs on any registered backend.

It runs through the PyTorch interface, so that PyTorch can compute the gradients of its output.

Parameters:

Name Type Description Default
blocks _BlocksOrSpec

The circuit. It can be a list of blocks to assemble, a CircuitSpec that is already assembled, or a CircuitBuilder. A builder is built when the layer is created, so gates added to it afterwards are not part of this layer.

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, a configuration instance, or a provider object. Any backend works. A backend that computes gradients itself avoids the fallback to the parameter-shift rule. Default: "numpy".

'numpy'
init Tensor | None

The initial weights, of shape (n_weights,). They are random when init is not given. Default: None.

None

n_qubits property

The number of qubits of the circuit in spec.

out_features property

The number of observables, or 1 for a single observable.

provider property

The backend that this circuit was compiled for.

It is the same as Circuit.provider, which turns the provider= argument (a registered name, a configuration or a backend object) into one Provider. The registered name is provider.name. To run the same circuit on another backend, use with_provider(), which returns a new instance and leaves this one unchanged.

spec property

The circuit that this layer runs.

It is the CircuitSpec assembled from blocks, or the one that was passed in already assembled.

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, so it is the same whatever the active backend is. Print the returned string directly.

Example
from eigenkit import Circuit, Encoding, Variational

blocks = [Encoding(in_features=2), Variational(depth=1)]
circuit = Circuit(blocks=blocks, interface="torch")
print(circuit.draw())
# q_0: ──RY(x_0)──RY(w_0)──●──
#                          │
# q_1: ──RY(x_1)──RY(w_1)──X──

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).

The picture does not depend on the provider, because it is built from the CircuitSpec and the observables. matplotlib is an optional dependency. Install it with eigenkit[viz]. The keyword arguments are passed to draw_mpl().

dry_run_info()

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

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. It is available only on the backends that provide it. Currently, these are Braket (on its local simulator as well as on cloud devices), Qiskit Runtime and IQM. Other backends raise a NotImplementedError.

forward(x=None)

Run the layer.

Parameters:

Name Type Description Default
x Tensor

The input batch, of shape (batch, in_features).

None

Returns:

Type Description
Tensor

The output of the layer for that batch.

reset_parameters()

Draw new circuit weights from the current PyTorch random number generator.

Every nn.Module that PyTorch provides defines this method. The estimators call model.apply(reset_parameters) after setting the seed, so that random_state controls the whole initialisation. Without this method, the estimators would skip the layer: two fits with different values of random_state would give different classical weights but exactly the same quantum weights, and a user who restarts with new seeds to escape a barren plateau would change only the part of the model that is not the circuit.

sample(x=None)

Return the raw measurement histogram for the current weights and input.

The histogram maps each bitstring to its number of shots. In each bitstring, qubit 0 is the most significant bit.

It is the second step of the workflow in which you train on a simulator and then run on hardware: you train the layer, and then you read what the device measured. It takes one sample, and its result is not differentiable, because the counts are integers that PyTorch does not record for automatic differentiation.

Parameters:

Name Type Description Default
x Tensor | None

The input features of one sample, as a 1-D tensor. 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.

Raises:

Type Description
NotImplementedError

On backends that do not sample.

ValueError

If x is 2-D.

submit(x=None)

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

Only the backends that provide submission support it (Braket, Qiskit Runtime and IQM).

Parameters:

Name Type Description Default
x Tensor | None

One 1-D input 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.

with_provider(provider, *, copy_weights=True, init=None)

Return a new TorchCircuit for another provider.

The new circuit has the same spec and the same observables argument as this instance, and it is compiled for the given provider.

provider accepts the same forms as in __init__(): a registered name (a str), a configuration object with a make_backend() method (for example PennyLaneConfig), or a backend instance.

The gradients follow the gradient_method of the new backend. The forward values should match across simulators up to numerical tolerance, but not exactly.

Parameters:

Name Type Description Default
provider ProviderSpec

The backend for the new circuit.

required
copy_weights bool

If True, the new circuit starts from a copy of self.weights. It is ignored when init is given. Default: True.

True
init Tensor | None

An optional weight tensor of shape (spec.n_weights,). When it is given, it takes precedence over copy_weights. Default: None.

None

Returns:

Type Description
'TorchCircuit'

The new TorchCircuit.

zero_state_probability(x=None)

Return the probability of measuring the all-zeros state.

It uses the current weights and the given input. It is useful for fidelity kernels and swap tests.

It takes one sample, and its result is not differentiable. The core reads the amplitude through the backend's access to the state and returns a plain float, so PyTorch does not record it for automatic differentiation. A batch raises an error, because a single number cannot hold a batch axis. For a batch, call this method once for each row, or use Circuit.zero_state_probabilities.

Parameters:

Name Type Description Default
x Tensor | None

The input features of one sample, as a 1-D tensor. 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 all-zeros probability, a float in [0, 1].

Raises:

Type Description
ValueError

If x is 2-D.