Skip to content

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 use e^{-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, backprop returns d⟨O⟩/dφ, and the chain rule through φ gives d⟨O⟩/d(weights) with the sign handled automatically (see jacobian).
  • Bit order. Qulacs stores the amplitudes with qubit 0 as the least significant bit (as Qiskit does), while the library uses qubit 0 as 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 parametric RZ. The two are equal up to a global phase that cannot be observed.
  • CRY(θ) becomes RY(θ/2) CX RY(-θ/2) CX, where CX is a controlled-NOT gate. This decomposition is exact.
  • CPHASE(θ) becomes P(θ/2)_c CX P(-θ/2)_t CX P(θ/2)_t, with each P as an RZ slot. 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, which uses QulacsConfig().

None
name str

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

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

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.

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 None when need_input_grad is False.

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

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 |0…0⟩. Default: None.

None

Returns:

Type Description
ndarray

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

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 [0, 1].

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