What's Changed
Added
-
Full calc-mode gate parity on Qx.Register
(plan: api-cleanup-phase-b). Eight gates that previously existed
only in circuit mode are now available onQx.Registeras direct
state-vector evolutions:Qx.Register.cy/3— controlled-YQx.Register.crx/4,cry/4,crz/4— controlled rotationsQx.Register.cp/4— controlled-phaseQx.Register.swap/3,iswap/3— two-qubit swapsQx.Register.cswap/4— Fredkin (controlled-SWAP)Qx.Register.u/5— general single-qubit unitary
All five controlled-target gates (
cy,crx,cry,crz,cp)
share anapply_controlled_target/4internal helper that lifts a
2×2 gate matrix into the full controlled two-qubit unitary via the
internal Qx.Gates.controlled_gate factory and applies it to
register.state. -
Basis-explicit measurement on Qx.Qubit
(plan: api-cleanup-phase-b).Qx.Qubit.measure_x/1,measure_y/1,
andmeasure_z/1return the probability distribution in the X, Y,
and Z bases respectively (the Z form is an alias of the existing
measure_probabilities/1for symmetry). Maps directly to QAALMx,
My,Mz. Implementation reuses the existing single-qubit gate
pipeline (Hfor X-basis,Sdg ; Hfor Y-basis). -
Named circuit recipes consolidated under Qx.Patterns
(plan: api-cleanup-phase-b). New helpers
Qx.Patterns.bell_state_circuit/1,ghz_state_circuit/1, and
superposition_circuit/1. The top-levelQx.bell_state,
Qx.ghz_state, andQx.superpositionnow delegate to these
helpers (no break — old call sites continue to work). Two of the
three got new optional arguments:Qx.ghz_state(num_qubits \\ 3)
andQx.superposition(num_qubits \\ 1)— previously hardcoded to
3 and 1 qubits respectively.
Changed
-
Qx.Behaviours.QuantumState now has callbacks (and an
implementor). Previously a dead behaviour that no module
implemented —Qx.Registernow declares@behaviour Qx.Behaviours.QuantumStateand provides every required callback.
The callback list grew to cover the full Phase B gate surface
(sdg,u,cy,swap,iswap,cp,crx,cry,crz,
ccx,cswap); the new callbacks are listed under
@optional_callbacksso future implementors with smaller surfaces
(e.g. a 2-qubit-only subset) don't need to provide them. Going
forward, adding a gate to the multi-qubit calc-mode surface is
compile-time-enforced to update both the behaviour andQx.Register.Single-qubit
Qx.Qubitis not an implementor: its functions
take(state)rather than(state, qubit_index), so the
signature is structurally incompatible. Unifying both paradigms
under one behaviour is a v1.0 redesign and is documented in the
behaviour's@moduledoc. -
Qx.Validation.validate_gate_name!/1 removed. It was dead code
(called only from its own tests) with a stale known-gates list
(missing CY/CRx/CRy/CRz/CP added in qaal-parity). The
corresponding test block is removed fromvalidation_test.exs. -
Qx.Validation removed from
mix.exsgroups_for_modules.
After Phase A's@doc falsesweep, onlyvalid_qubit?/2and
valid_register?/2remain visible — too thin to warrant a top-level
group. The module page itself is still reachable; the renamed
"Utilities" group listsQx.MathandQx.StateInit. -
Internal-only functions hidden from documentation
(plan: api-cleanup-phase-a). A public-API audit found that several
modules labelled "Low-Level Operations" or "Validation & Utilities"
inmix.exswere exportingdeffunctions that are used only inside
the library. The following are now@moduledoc falseor
per-function@doc false:- Qx.Gates, Qx.Calc, Qx.CalcFast, Qx.Format, Qx.ResultBuilder —
entire modules tagged@moduledoc false. Removed frommix.exs
groups_for_modules. Functions remain callable for advanced users;
they just no longer appear in ExDoc or IDE auto-complete. - Qx.QuantumCircuit.add_gate, add_two_qubit_gate,
add_three_qubit_gate, add_measurement —@doc false. The
user-facing API isQx.h(qc, 0)etc.; these are internal helpers. - Qx.Validation
validate_*!family (10 functions) —@doc false.
Internal Iron Law #7 contracts. The user-facing predicates
Qx.Validation.valid_qubit?/2andvalid_register?/2remain
public. - Qx.Math.complex_to_tensor, tensor_to_complex, complex_matrix —
@doc false. The rest ofQx.Math(complex/2,identity/1,
unitary?/1,probabilities/1) stays public.
No call site breaks. Existing tests pass unchanged.
- Qx.Gates, Qx.Calc, Qx.CalcFast, Qx.Format, Qx.ResultBuilder —
-
Qx.Qubit.draw_bloch/2converted from adefwrapper to a
defdelegate. Behaviour unchanged. -
Qx.Error@moduledocrewritten to be accurate. The previous
text described it as a "base exception" Qx users could rescue to
catch any Qx error. Elixir exceptions do not inherit, so
rescue Qx.Errorcatches nothing today — the docstring now says
so explicitly and lists every typed exception users actually need
to rescue.
Deprecated
- Qx.Math.basis_state/2 is deprecated — use
Qx.StateInit.basis_state/3instead. The two functions returned
different types (Math was f32, StateInit is c64), and the StateInit
form is the canonical one. The deprecated function emits a
compile-time warning and is hidden from ExDoc; it will be removed
in v1.0.
Fixed
- Qx.QuantumCircuit.new now enforces the documented 1..20-qubit cap
at both bounds (plan: api-cleanup-phase-a, finding D3). Previously:
new(25)silently created an over-cap circuit (upper bound unchecked
on this path);new(0)raisedFunctionClauseErrorvia a guard
(lower bound untyped). Both paths now raiseQx.QubitCountError
consistently — the function calls the internalvalidate_num_qubits!
validator on every input. Closes Iron Law #7 holes at both bounds,
surfaced by.claude/plans/public-api-audit/plan.md.
BREAKING
-
Typed errors at public API boundaries (Iron Law #7). Out-of-range
qubit indices, duplicate qubit indices, classical-bit OOR, invalid
conditional values, and unsupported gates now raise the matching
Qx.*Errorexception instead ofFunctionClauseError,ArgumentError,
orRuntimeError. Resolves C1/C2/C3 of
.claude/audit/reports/arch-review.md.Qx.QuantumCircuit.add_gate/4,add_two_qubit_gate/5,
add_three_qubit_gate/6,add_measurement/3now raise
Qx.QubitIndexErrorfor out-of-range or duplicate qubits, and
Qx.ClassicalBitErrorfor out-of-range classical-bit indices.Qx.Operations.barrier/2now raisesQx.QubitIndexError.Qx.Operations.c_if/4now raisesQx.ClassicalBitError
(out-of-range bit) andQx.ConditionalError(invalid value,
non-functiongate_fn, nested conditional).Qx.Simulation.run/2(and theQx.run/2delegate) now raises
Qx.GateError, {:unsupported_gate, gate_name}instead of
RuntimeErrorfor unsupported gate names at any arity.Qx.QuantumCircuit.set_state/2now raisesQx.StateShapeError
instead ofArgumentErroron size or rank mismatch.QxandQx.Operationsdocstring## Raisessections updated
accordingly.
Migration: rescue clauses matching
FunctionClauseError,
ArgumentError, orRuntimeErrorat these public call sites must
be updated to the matchingQx.*Errorexception. The sametry
block can rescueQx.Errorto catch any Qx-raised exception.Known deferred (not fixed in 0.8.0):
Qx.Validation
(validate_qubits_different!,validate_state_shape!,
validate_parameter!) still raises bareArgumentError;Qx.Qubit
andQx.Registerstill raiseArgumentErrorfrom public functions;
Qx.Operations.u/5still firesFunctionClauseErrorfor OOR qubit
(its own bounds guard). These map to arch-review findings H1, M3,
M4, M5 and are scheduled for a follow-on Iron Law #7 sweep.
Added
-
Qx.StateShapeError— new exception type raised by
Qx.QuantumCircuit.set_state/2when the supplied state vector's
shape doesn't match{2^num_qubits}. Carries:actualand
:expectedsize fields. -
Qx.QubitIndexError{:duplicate, qubits}constructor. New
exception/1clause to raise on distinct-indices violations (e.g.
CNOT withcontrol == target, Toffoli with repeated qubits).
Message:"Qubit indices must be distinct, got: [...]". -
Controlled rotations:
Qx.cy/3,Qx.crx/4,Qx.cry/4,Qx.crz/4
(plan: qaal-parity). Standard controlled-Pauli-Y and controlled
rotation gates, mapping directly to QAALCY/CRx/CRy/CRzand
OpenQASM 3cy/crx/cry/crz. Simulation handlers reuse the
existing two-qubitcontrolled_gate/4contraction. The OpenQASM
importer (Qx.Export.OpenQASM.from_qasm/1) now also recognises these
gates — previously they were in the unsupported-stdgates set. -
Basis-explicit measurement:
Qx.measure_x/3,Qx.measure_y/3,
Qx.measure_z/3(plan: qaal-parity). Match QAALMx/My/Mz
classical-outcome semantics:measure_xlowers toH ; Mz,
measure_ylowers toSdg ; H ; Mz,measure_zis an alias of
measure/3for symmetry. Note: Qx's simulator samples in the
computational basis at end-of-circuit, so the post-measurement
quantum state stays Z-basis-aligned (not rotated back into the
X-/Y-basis eigenstate) — the classical outcome is what tutorials
care about and matches QAAL. -
Qx.Patternssub-register overload (/2arity)
(plan: qaal-parity).h_all/2,x_all/2,y_all/2,z_all/2,
measure_all/2,barrier_all/2accept a list orRangeof qubit
indices in addition to the existing whole-circuit/1form. Lets
tutorials operate on a sub-register without re-deriving qubit ranges
by hand:Qx.h_all(qc, 0..2),Qx.measure_all(qc, [0, 2]). Empty
list/range is a no-op. -
Qx.Patterns— composite circuit-building helpers. New module
providing seven thin wrappers overQx.Operationsfor the recurring
"apply to every qubit" / "CNOT chain" motifs that appear in tutorials
(Grover diffuser, Bernstein-Vazirani oracle, GHZ preparation):Qx.Patterns.h_all/1,x_all/1,y_all/1,z_all/1— apply the
single-qubit gate to every qubit in the circuit.Qx.Patterns.measure_all/1— measure qubitiinto classical bit
ifor all qubits. RaisesQx.ClassicalBitErrorif
num_classical_bits < num_qubits(caller owns circuit shape — no
auto-grow).Qx.Patterns.barrier_all/1— single barrier across every qubit.Qx.Patterns.cx_chain/2— linear CNOT cascade
(cx(q0,q1) → cx(q1,q2) → …) along the supplied qubit list;
[]and[q]are deliberate no-ops.
All seven are also exposed at the top level (
Qx.h_all/1,
Qx.measure_all/1, …) viadefdelegate. Purely additive — no
breaking change. Out-of-range qubit indices propagate the existing
typedQx.QubitIndexErrorinherited from
Qx.QuantumCircuit.add_*/Qx.Validation. -
Configurable statevector renormalization + dev/test norm-drift
guard inQx.Simulation.run/2/Qx.run/2(qx-53v). New
:renormalizeoption (defaultfalse— fully backwards compatible,
zero cost when off):truerenormalizes at measurement-time; a
positive integerNrenormalizes everyNgates and at
measurement-time; any other value raises the new typed
Qx.OptionError. A compile-time-gated assertion
(Application.compile_env(:qx, :assert_norm, false), on in:test,
off in:prod/:dev) fails fast if a circuit's total probability
drifts beyond1.0e-6. Note: states are:c64(float32), so the
practical norm-accuracy floor is ~1e-7; renormalization bounds drift
rather than eliminating it.
Installation
Add to your mix.exs:
def deps do
[
{:qx_sim, "~> 0.8.0"}
]
end