qulacs_backend
eigenkit.core.backends.qulacs_backend
The Qulacs backend.
Qulacs is an optional dependency (pip install eigenkit[qulacs]). The package is
imported only when it is needed, so the rest of the library works without it,
and compile() raises a clear ImportError if it is missing.
It declares gradient_method = "adjoint". Qulacs provides an analytic
reverse-mode gradient (ParametricQuantumCircuit.backprop), so, as with the
NumPy reference simulator, the compiled circuit has a jacobian method, and the
gradient code of the core package prefers it to the parameter-shift rule, whose
cost is O(n_params).
Compile and run
compile() builds one ParametricQuantumCircuit and the Qulacs Observable
objects once. Every rotation whose angle depends on the weights or inputs becomes
a parametric slot. run() sets the slots with set_parameter, without
rebuilding the circuit at each call, and jacobian() reads the gradients of all
slots from one backprop pass for each observable.
Conventions matched with the NumPy and PennyLane reference
- Rotation sign. Qulacs rotations are
e^{+iθP/2}, while the library and PennyLane usee^{-iθP/2}. Every rotation angle is negated when it is passed to Qulacs: the Qulacs parameter of a slot isφ = -(coeff · angle). As a result,backpropreturnsd⟨O⟩/dφ, and the chain rule throughφgivesd⟨O⟩/d(weights)with the sign handled automatically (seejacobian). - Bit order. Qulacs stores the amplitudes with qubit
0as the least significant bit (as Qiskit does), while the library uses qubit0as the most significant bit. The statevector input and output, and the matrices of multi-qubit gates, are bit-reversed to match the two. The Pauli observables need no reindexing, because the qubit labels match: only the storage order of the amplitudes differs.
Gate coverage
add_parametric_R{X,Y,Z} covers the trainable rotations directly. The gates that
have no parametric primitive in Qulacs are decomposed into these rotations, so
that they stay differentiable:
PhaseShift(θ)becomes a parametricRZ. The two are equal up to a global phase that cannot be observed.CRY(θ)becomesRY(θ/2) CX RY(-θ/2) CX, whereCXis a controlled-NOT gate. This decomposition is exact.CPHASE(θ)becomesP(θ/2)_c CX P(-θ/2)_t CX P(θ/2)_t, with eachPas anRZslot. This decomposition is exact up to a global phase that cannot be observed.
PhaseShift, CPHASE and CRY with a constant angle, and the non-parametric
gates (H, X, Z, S, Sdg, CNOT, SWAP, MCX, MCZ, QubitUnitary,
ControlledQubitUnitary, AmplitudeEncoding), are added once as exact Qulacs
gates. backprop accepts these fixed gates, so native adjoint gradients are
available for every circuit.
Example
from eigenkit import Circuit, Encoding, Variational, all_z
blocks = [Encoding(in_features=4), Variational(depth=2)]
# by registered name
circuit = Circuit(blocks=blocks, observables=all_z(4), provider="qulacs")
# or with the config, when a device setting matters
from eigenkit.configs import QulacsConfig
circuit = Circuit(
blocks=blocks,
observables=all_z(4),
provider=QulacsConfig(),
)
Gradients are computed natively in reverse mode, with backprop. This backend
needs the qulacs extra.
QulacsBackend
The provider for the Qulacs statevector simulator.
It is optional: it is registered as "qulacs" when the package is installed,
and the package is imported only on first use. It declares
gradient_method = "adjoint", so the gradient code of the core package uses
QulacsCompiled.jacobian()
(analytic, in reverse mode, through Qulacs backprop) instead of the
parameter-shift rule, whose cost is O(n_params).
capabilities
property
What this backend supports.
It runs every gate, returns exact expectation values only, does not
evaluate a batch in one call, and has no limit on the number of qubits.
The adjoint of AmplitudeEncoding is excluded, because a state
preparation cannot be inverted as a Qulacs gate. See
Capabilities.
__init__(config=None, name='qulacs')
Build the backend from a device configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
QulacsConfig
|
The device settings. Default: |
None
|
name
|
str
|
The registry name, used in error messages. Default:
|
'qulacs'
|
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 |
|---|---|
QulacsCompiled
|
The prepared circuit, as a |
QulacsCompiled
Bases: CompiledCircuit
A compiled circuit: one ParametricQuantumCircuit, reused across calls.
_slots[k] = (expr, coeff) records that the parametric Qulacs gate k
implements the library angle coeff · expr. Its Qulacs parameter is set to
-(coeff · resolve(expr)), because Qulacs uses the opposite rotation sign.
__init__(circuit, observables, config)
Prepare the circuit for repeated evaluation.
QulacsBackend.compile() builds this object, so there is no reason to
build one directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
circuit
|
CircuitSpec
|
The circuit to prepare. |
required |
observables
|
Sequence[Observable]
|
The observables to measure. |
required |
config
|
QulacsConfig
|
The device settings. |
required |
jacobian(weights, inputs, *, need_input_grad=False)
Compute the gradient with the native Qulacs method, the analytic adjoint.
The gradient code of the core package looks for a jacobian method. When
a backend has one, it is used in preference to the parameter-shift rule,
which works on any backend and is the fallback. Here the gradient comes
from backprop: one pass for each observable gives d⟨O⟩/dφ for every
parametric slot. These gradients are mapped onto the weights (and the
inputs) with the same symbolic chain-rule factors as the parameter-shift
code, so angles built with Scale, Multiply, Sum or Negate (for
example in Reuploading(trainable_scale=True)) are also handled.
For a single sample, the two jacobians have shapes (n_obs, n_weights)
and (n_obs, n_inputs). For a 2-D inputs batch, they have a leading
batch axis, and the batch is evaluated in a loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features: 1-D for one sample, or 2-D with a leading batch axis. |
required |
need_input_grad
|
bool
|
If True, also compute the jacobian with
respect to the inputs. Default: |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
weight_jacobian |
ndarray
|
The jacobian with respect to the weights. |
input_jacobian |
ndarray | None
|
The jacobian with respect to the inputs, or |
run(weights, inputs)
Evaluate the circuit and return one expectation value per observable.
It follows the CompiledCircuit contract: the result has shape (n_obs,)
when weights and inputs are both 1-D, and (B, n_obs) otherwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector: 1-D for one sample, or 2-D with a leading batch axis. |
required |
inputs
|
ndarray
|
The input features, with the same 1-D or 2-D
shape convention as |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The expectation values, one for each observable. |
statevector(weights, inputs, initial_state=None)
Return the full complex statevector after the circuit has run.
Qubit 0 is the most significant bit. initial_state, in the same
order, is the starting state instead of |0…0⟩. A 2-D inputs batch is
evaluated in a loop and returns an array of shape (B, 2**n).
initial_state can then be one state of shape (2**n,), shared by all
samples, or a stack of shape (B, 2**n) with one state for each sample.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features: 1-D for one sample, or 2-D with a leading batch axis. |
required |
initial_state
|
ndarray | None
|
The state to start from, instead
of |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The statevector, of shape |
zero_state_probability(weights, inputs)
Return |⟨0…0|ψ⟩|², the squared amplitude of |0…0⟩.
The basis state 0 is |0…0⟩ whether qubit 0 is the least or the most
significant bit, so no reindexing is needed. The fidelity quantum kernel
is built on this method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ndarray
|
The parameter vector (1-D). |
required |
inputs
|
ndarray
|
The input features of one sample (1-D). |
required |
Returns:
| Type | Description |
|---|---|
float
|
The probability, a float in |
QulacsConfig
dataclass
Device configuration for the Qulacs backend.
The Qulacs statevector simulator for the central processing unit (CPU) needs
no settings, so this configuration is currently only a marker. It makes
Circuit(provider=QulacsConfig()) select this backend, and it leaves room for
future fields, for example a state on a graphics processing unit (GPU).
Pass it to Circuit(provider=QulacsConfig()), or pass it to register() under
a name to reuse it.
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 |
|---|---|
'QulacsBackend'
|
The |