circuit
eigenkit.torch.circuit
The PyTorch nn.Module circuit, which is the trainable quantum layer for users.
TorchCircuit stores its trainable weights as an nn.Parameter and passes the
execution to the core Circuit with the PyTorch interface. It can therefore be
used inside any PyTorch model (nn.Sequential or your own nn.Module), and it
trains with a standard PyTorch optimiser. The gradients are computed with the
method that the active backend declares: the backend's own automatic
differentiation, the parameter-shift rule or the adjoint method.
The top-level Circuit(..., interface="torch") function returns this class. It
accepts a list of blocks, a CircuitSpec that is already assembled, or a
CircuitBuilder.
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 |
required |
observables
|
ObservableLike
|
What to measure. |
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'
|
init
|
Tensor | None
|
The initial weights, of shape |
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
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
decimals
|
int
|
The number of significant digits shown for a numeric gate
angle. Default: |
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 |
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
|
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 |
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
|
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
|
True
|
init
|
Tensor | None
|
An optional weight tensor of shape |
None
|
Returns:
| Type | Description |
|---|---|
'TorchCircuit'
|
The new |
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
|
Returns:
| Type | Description |
|---|---|
float
|
The all-zeros probability, a float in |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |