Circuits and parameters
Applicable version · ArcQML 0.1.0
On this page
Circuit structure
Circuit stores num_qubits, an execution-ordered list of Operation objects, and a Parameter list. add_gate wraps a Gate and qubit list into an Operation. Only when the simulator is called does the interface validate and bind parameters, after which ArcQML Runtime applies the Gate to the current quantum state.
Circuit::depth() computes parallel depth from qubit resources: each Operation receives one plus the maximum current layer of its qubits, and writes that layer back to all participating qubits.
Built-in gates
| Category | Rust Circuit methods |
Parameter behavior |
|---|---|---|
| Fixed single-qubit | i, x, y, z, h, s, sdg, t, tdg, sx, sxdg |
No trainable parameters. |
| Parameterized single-qubit | rx, ry, rz, phase, u1, u2, u3 |
Create new parameters by default; some provide _fixed or _param variants. |
| Controlled | cnot/cx, cy, cz, ch, cs, ct, cp, crx, cry, crz |
The first two qubit arguments are control and target, respectively. |
| Two-/multi-qubit | swap, iswap, dcx, ecr, rxx, ryy, rzz, rzx, fsim, toffoli, cswap, mcx |
Rotation gates and fSim are trainable; the rest are fixed. |
| Extensions | Gate::custom_unitary(name, arity, matrix) |
Accepts a row-major C64 matrix; dimensions and unitarity are checked at construction. |
Python Circuit currently exposes h, x, y, z, rx, ry, rz, phase, u3, and cnot. Its gate set is smaller than the Rust Circuit set; use Rust for other controlled gates, multi-qubit gates, or custom unitaries.
Three ways to add gate parameters
| Method | Example | Parameter-table behavior | Suitable use |
|---|---|---|---|
| Automatic registration | circuit.ry(0.3, 0) |
Creates an F64 scalar Parameter with a stable automatic name |
Ordinary trainable gates. |
| Fixed parameter | circuit.ry_fixed(0.3, 0) |
Uses Gateparam::Fixed; registers no Parameter |
Encoding constants and nontrainable layers. |
| Explicit sharing | id = add_parameter(...); ry_param(id, 0) |
Multiple gates reference one ParameterId |
Symmetric ansatzes and shared weights. |
Automatic gate-parameter names combine the gate, role, qubit, and parameter-table index, such as ry_q0_theta_0. Manual add_parameter_tensor calls default to parameter_<index>. Names are part of checkpoint matching, so changing model-construction order or gate naming rules affects compatibility for persistently saved models.
Shared parameters and gradient accumulation
use arcqml::prelude::*;
let mut circuit = Circuit::new(1)?;
let theta = circuit.add_parameter(0.3)?;
circuit.ry_param(theta, 0usize)?;
circuit.rz_param(theta, 0usize)?;
// During backpropagation, add contributions to the same theta from both operations.
Each parameterized Operation stores only a ParameterId. Before execution, the interface reads current Parameter Tensor values from Circuit and passes them to Runtime. Optimizer updates therefore require no circuit reconstruction: the next run uses the new angles automatically. Adjoint backpropagation also accumulates contributions from multiple gates by ParameterId, implementing shared-parameter gradient accumulation.
Validation, copying, and concatenation
validate() checks that the qubit count is nonzero, Operation qubits are valid, Gate parameter references belong to the current table, and parameter names are nonempty and unique. Circuit::clone() calls deep_clone(), so the copied Circuit shares neither parameter values nor gradients with the original.
append(right) consumes the right circuit, appends its operations, and remaps unbound parameters. append_with_bindings(right, bindings) explicitly binds selected right-side ParameterId values to existing left-side parameters. Both circuits must have the same qubit count, and successful concatenation clears parameter gradients in the result.
Current limits on parameter expressions
Parameterized gates currently store only a fixed scalar or a single ParameterId in each parameter slot, not expressions involving multiple parameters. Expressions such as -x and x + y cannot be supplied as gate parameters directly. Multiple gates may share a ParameterId, but arithmetic relationships between parameters cannot be declared inside Circuit.
For parameter expressions, currently compute the value outside the circuit and insert it as a fixed or independent parameter. This does not automatically preserve dependencies on the original parameters: registering the result of x + y as a new Parameter yields a gradient only for that new parameter, without automatically applying the chain rule to x and y. Recompute and update the value before each training forward pass.
The current version does not support registering a nonleaf Tensor expression directly as a gate parameter; such a call may also panic. Training the original expression variables requires a caller-provided parameter-mapping VJP / chain-rule gradient. Rewriting values each iteration does not create dependencies automatically. Prefer single ParameterId values and parameter sharing for ordinary training. Connecting quantum outputs to classical differentiable losses is supported.