Skip to content

blocks

eigenkit.core.blocks

Blocks: reusable parts of a circuit that do not depend on the provider.

Given the total number of qubits of the circuit, a block builds its gates and states how many weights and inputs it uses. Blocks are combined with assemble:

from eigenkit.core.blocks import AngleEncoding, BasicVariational, assemble

spec = assemble([AngleEncoding(in_features=3), BasicVariational(depth=2)])

assemble goes through the blocks in order and chooses the total number of qubits. It shifts the local WeightRef and InputRef indices of each block so that they point to the right place in the weight and input vectors of the whole circuit, and it returns a single CircuitSpec. Blocks do not depend on a provider or on PyTorch, and most of them do not use NumPy. Only the blocks that check or compute a matrix (QubitUnitary, ControlledUnitaryBlock and QPEBlock) use NumPy. The blocks use CircuitBuilder internally, because it makes their code easier to read.

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.

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.

Block

Bases: Protocol

The interface that every circuit block follows.

A block is a reusable part of a circuit, such as an encoding, a variational layer or a fixed subroutine. assemble asks each block in turn how many qubits it needs, gives the circuit as many qubits as the widest block needs, and then asks every block to build its gates for that number of qubits. Any object with these two methods works as a block. It does not need to inherit from this class.

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.

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 the minimum number of qubits that this block needs.

ControlledUnitaryBlock dataclass

Apply a unitary matrix U to target_wires, controlled by control_wires.

Parameters:

Name Type Description Default
unitary object

A 2^n_target × 2^n_target matrix, as an array-like, where n_target is the number of target qubits.

required
control_wires tuple[int, ...]

The qubits that control the gate.

required
target_wires tuple[int, ...]

The qubits that the matrix acts on.

required
control_values tuple[int, ...] | None

The bit value that activates each control, one for each entry of control_wires. Default: 1 for every control.

None
Example
import numpy as np
import scipy.linalg
from eigenkit.core.blocks import ControlledUnitaryBlock, assemble

A = np.diag([1.0, 2.0, 3.0, 4.0])  # a Hermitian matrix on two qubits
t = 0.5
U = scipy.linalg.expm(1j * A * t)
block = ControlledUnitaryBlock(U, control_wires=(0,), target_wires=(1, 2))
spec = assemble([block])

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
'ControlledUnitaryBlock'

The rebuilt ControlledUnitaryBlock.

required_qubits()

Return one more than the highest control or 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.

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.

HamiltonianQPEBlock dataclass

Quantum phase estimation (QPE) of e^{iAt}, built with Trotter products.

This block replaces QPEBlock in the clock register of the Harrow-Hassidim-Lloyd (HHL) solver with mode="honest", where the circuit is built as hardware would run it. It turns the Hermitian matrix A = Σ_k c_k P_k into elementary gates with Trotter products. It does not exponentiate a dense oracle and raise it to powers with matrix_power: both of these cost O(N³) for an N × N matrix, and neither gives a circuit of elementary gates. The controlled evolution controlled-e^{iA·2^m·t} that a clock qubit needs is a Trotter product with a total time of 2^m·t, and never a matrix power. The step size t/steps is fixed, so the power 2^m uses steps·2^m steps.

Only the single RZ of each Trotter term is controlled. The controlled RZ is built from RZ and controlled-NOT (CNOT) gates, as CRZ = RZ·CNOT·RZ·CNOT. The change of basis and the ladder gates cancel out when the control is |0⟩. The identity term of A becomes a PhaseShift on the clock qubit, which is a controlled global phase. As a result, the estimated phase reflects the full eigenvalue and not only its traceless part, which matches what QPEBlock gives from expm(1j·A·t).

The qubits are laid out as follows, with indices local to the block:

[clock(0 … n_clock-1) | state(n_clock … n_clock+n_state-1)]

Clock qubit k controls e^{iA·2^(n_clock-1-k)·t}. This is the big-endian convention of QPE, the same as in QPEBlock. The caller maps these local indices to the qubits of the full circuit, for example to skip the HHL ancilla qubit.

Parameters:

Name Type Description Default
pauli_terms tuple[tuple[float, str], ...]

The (coeff, pauli_string) terms of the Hermitian matrix A, for example from pauli_decompose. Every string has length n_state.

required
n_clock int

The number of clock qubits, which sets the resolution of QPE.

required
time float

The base evolution time t.

required
steps int

The number of Trotter steps for the base time t. Default: 1.

1
order int

The Trotter order, 1 or 2. Default: 1.

1
inverse bool

If True, the block builds the inverse QPE, which undoes the phase estimation. Default: False.

False
Example
import numpy as np
from eigenkit.algorithms._hamiltonian_sim import pauli_decompose
from eigenkit.core.blocks import HamiltonianQPEBlock, assemble

A = np.array([[1.0, 0.5], [0.5, -1.0]])  # a Hermitian matrix on one qubit
terms = pauli_decompose(A)
spec = assemble([HamiltonianQPEBlock(terms, n_clock=4, time=1.0, steps=8)])

build(n_qubits)

Build the phase estimation, with Trotter products, for n_qubits qubits.

The qubit indices are local to the block, and the caller maps them to the qubits of the full 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. It holds elementary gates only, and no weights.

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
'HamiltonianQPEBlock'

The rebuilt HamiltonianQPEBlock.

required_qubits()

Return n_clock plus the number of state qubits.

The clock qubits come first, then the state qubits.

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.

IRFragment dataclass

The gates that one block adds to a circuit.

The WeightRef and InputRef indices of a fragment are local to it, and they start at 0. When the blocks are combined into a circuit, assemble shifts them so that they point to the right place in the weight and input vectors of the whole circuit.

Attributes:

Name Type Description
gates tuple[Gate, ...]

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

n_weights int

The number of trainable weights that the block uses.

n_inputs int

The number of input features that the block uses.

QFTBlock dataclass

Quantum Fourier transform (QFT) on n_qubits qubits.

The transform is built from H, CPHASE and SWAP gates only, which are all gates that the intermediate representation (IR) of the circuit defines.

Parameters:

Name Type Description Default
n_qubits int

The number of qubits of the transform. It must be positive.

required
inverse bool

If True, the block builds the inverse QFT, which quantum phase estimation (QPE) applies as its last step. Default: False.

False
Example
from eigenkit.core.blocks import AmplitudeEncoding, QFTBlock, assemble

b = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]  # eight values for three qubits
spec = assemble([AmplitudeEncoding(b), QFTBlock(n_qubits=3, inverse=True)])

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
'QFTBlock'

The rebuilt QFTBlock.

required_qubits()

Return n_qubits, the number of qubits that the transform 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.

QPEBlock dataclass

Quantum phase estimation (QPE) of a unitary oracle.

The block applies H to every clock qubit, then the controlled powers U^{2^k} of the oracle, then the inverse quantum Fourier transform (QFT†): H^⊗n_clock → controlled-U^{2^k} → QFT†. Set inverse=True for the adjoint: QFT → controlled-U†^{2^k} → H^⊗n_clock.

The oracle must be a CircuitSpec whose unitary can be read as a dense matrix. In practice, this means a circuit made of a single QubitUnitary gate. Parametrised oracles made of several gates are not supported. To use an AmplitudeEncoding or a ControlledUnitaryBlock as an oracle, first compute its unitary matrix and wrap it in a QubitUnitary.

The qubits are laid out as follows, with indices local to the block:

[clock(0 … n_clock-1) | state(n_clock … n_clock+n_state-1)]

The caller is responsible for mapping these local indices to the qubits of the full circuit, for example to skip the ancilla qubit of the Harrow-Hassidim-Lloyd (HHL) algorithm.

Parameters:

Name Type Description Default
oracle_circuit CircuitSpec

A CircuitSpec whose single QubitUnitary gate defines the unitary U that acts on the state qubits. The single-gate requirement is checked when the block is built, and the type when the block is created.

required
n_clock int

The number of clock qubits, which sets the resolution of QPE. It must be at least 1.

required
inverse bool

If True, the block builds the inverse QPE, which undoes the phase estimation. Default: False.

False
Example
import numpy as np
import scipy.linalg
from eigenkit.core.blocks import QPEBlock, QubitUnitary, assemble

A = np.array([[2, 0], [0, 1]], dtype=float)
U = scipy.linalg.expm(1j * A * np.pi)
oracle = assemble([QubitUnitary(U, wires=(0,))])
spec = assemble([QPEBlock(oracle, n_clock=3)])

build(n_qubits)

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

The qubit indices are local to the block, and the caller maps them to the qubits of the full 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
'QPEBlock'

The rebuilt QPEBlock.

required_qubits()

Return n_clock plus the number of qubits of the oracle.

The clock qubits come first, then the state qubits.

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.

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.

TrotterBlock dataclass

First-order or second-order Trotter approximation of e^{iAt}.

The block builds the time evolution under a Hermitian matrix A = Σ_k c_k P_k, given as (coeff, pauli_string) terms (for example from pauli_decompose()). It uses elementary gates only (H, RX, controlled-NOT (CNOT) and RZ), and no dense QubitUnitary. It replaces applying scipy.linalg.expm(1j*A*t) as a single opaque unitary, and it builds the evolution as hardware would run it.

Each term e^{i·c·P·τ} is built as follows. Every factor of P that is not Z is rotated into the eigenbasis of Z (with H for X, and RX(+π/2) for Y). A ladder of CNOT gates collects the parity of the active qubits on the last one, a single RZ(-2cτ) is applied there, and then the ladder and the change of basis are undone. With the convention RZ(θ) = e^{-iθZ/2} of the library, this gives e^{+iAt} up to a global phase, which matches the sign of the reference Harrow-Hassidim-Lloyd (HHL) solver. The identity term is only a global phase, so it is skipped: it has no observable effect here, and it becomes a phase on the clock qubit in the controlled version used for quantum phase estimation (QPE).

Parameters:

Name Type Description Default
pauli_terms tuple[tuple[float, str], ...]

A sequence of (coeff, pauli_string) pairs. All the strings have the same length n, which is the number of qubits. Each coeff must be real up to numerical tolerance, because A is Hermitian.

required
time float

The evolution time t.

required
steps int

The number of Trotter steps. The approximation error decreases as steps increases. Default: 1.

1
order int

1 (Lie-Trotter) or 2 (symmetric Strang splitting, which has a smaller error for the same steps). Default: 1.

1
Example
import numpy as np
from eigenkit.algorithms._hamiltonian_sim import pauli_decompose
from eigenkit.core.blocks import TrotterBlock, assemble

A = np.array([[1.0, 0.5], [0.5, -1.0]])  # a Hermitian matrix on one qubit
terms = pauli_decompose(A)
spec = assemble([TrotterBlock(terms, time=1.0, steps=20, order=2)])

build(n_qubits)

Build the Trotter product for a circuit with n_qubits qubits.

The qubit indices are local to the block, and the caller maps them to the qubits of the full 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. It holds elementary gates only, and no weights.

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
'TrotterBlock'

The rebuilt TrotterBlock.

required_qubits()

Return the length of the Pauli strings, one qubit for each factor.

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.

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