Changelog
View SourceAll notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
[0.10.0] - 2026-07-04
The "Step-Through API & Public-API Streamlining" minor: circuit mode becomes inspectable one operation at a time, and the public surface is cleaned up around it, including a clean-break rework of the Draw layer decided while v0.10 was still unpublished.
Added
Qx.draw_circuit/2: facade delegate for circuit diagrams (the README no longer needs tier-2Qx.Draw.circuit/2). Returns aQx.Draw.Image.Qx.Draw.ImageandQx.Draw.StateTable: artifact structs returned bydraw_bloch/draw_circuitanddraw_state. Plain data with one static shape everywhere —Inspectfor IEx, raw SVG/text/markdown/ HTML fields for standalone use, and rich Livebook rendering viaKino.Render.Kino.Renderimplementations (active only when Livebook provides the optionalkinodependency) forQx.QuantumCircuit(a cell returning a circuit renders its diagram),Qx.SimulationResult(counts table),Qx.Step(state table),Qx.Draw.Image, andQx.Draw.StateTable.Qx.MissingDependencyError: typed error naming the exact deps line to add when an optional dependency is required.
Changed
- Breaking (pre-release, v0.10 never shipped): every draw function
now returns one static type in every environment.
Qx.draw/2,draw_counts/2,draw_histogram/2returnVegaLite.t()only;draw_bloch/2returnsQx.Draw.Image;draw_state/2returnsQx.Draw.StateTable. The:formatoption is gone everywhere, anddraw_stateno longer sniffs for Kino at runtime (the same call returned%Kino.Markdown{}in Livebook and a string in a release). - Breaking: tier-2
Qx.Drawnames now follow the facade (Qx.draw_Xdelegates toQx.Draw.X):plot_countsis nowcounts,bloch_sphereis nowbloch. vega_liteis now an optional dependency: only the three VegaLite-returning chart functions need it, and they raiseQx.MissingDependencyErrorwhen it's absent.- The docs and README now teach one path: build a circuit, run it, and
step through it with
Qx.steps/2/Qx.Step.show/1when you want to see the state evolve. The "Whichham I calling?" grid, the two-modes framing, and the calc-mode README sections are gone; a short migration note in the README maps old calc-mode pipelines onto the stepper. - Breaking (pre-release):
Qx.Draw.circuit/2(and the newQx.draw_circuit/2) returns aQx.Draw.Imageinstead of a raw SVG string. Where 0.8.x docs saidFile.write!(path, svg), writeimage.svg.
Deprecated
Qx.QubitandQx.Register(calc mode) are demoted to an internal engine: hidden from the generated docs, dropped from the declared public surface, and carrying no stability guarantee from here on. Both modules still compile, run, and pass their tests, so existing notebooks keep working. Removal or restructuring is deferred to v1.0.
Removed
The hand-rolled SVG chart renderer (
format: :svgondraw/draw_counts/draw_histogram): a usage inventory found no consumer anywhere. SVG remains the artifact for Bloch spheres and circuit diagrams viaQx.Draw.Image.The VegaLite Bloch-sphere projection: the SVG Bloch renderer is the single Bloch path (its visual upgrade is scheduled for v0.11).
Qx.steps/1,2and%Qx.Step{}: a lazy stream of per-operation steps for walking through a circuit's execution. Works on circuits with mid-circuit measurement andc_if(one stochastic trajectory per materialisation;seed:reproduces a trajectory without touching the caller's process:randstate). Each step carries the operation, the statevector after it, its probabilities, the classical bits so far, and a taken/not-taken flag on conditional steps.Qx.Step.show/1: theQx.Register.show_state/1display map (Dirac string, amplitudes, probabilities) for any step, plus a readable single-lineInspectrendering.Qx.tap_state/2andQx.tap_probabilities/2now share the stepper's execution path internally. No behaviour change: same values, sameQx.MeasurementErrorcontract on measured/conditional prefixes.Qx.StateInit.bell_state/0,1,2andQx.StateInit.ghz_state/1,2, the deprecated state-vector aliases: usebell_state_vector/0,1,2andghz_state_vector/1,2. The circuit-returningQx.bell_state/0,1andQx.ghz_state/0facades are unaffected. Deprecated through 0.8.x; window closed.Qx.Math.basis_state/2, the f32 shim deprecated in 0.8.x: useQx.StateInit.basis_state/3(c64, matching the quantum-state surface).Qx.histogram/1,2: useQx.draw_histogram/1,2, in place since one minor before this release.
Fixed
SimulationResult.countskeys are now the binary strings the docs always promised (%{"10" => 507}), instead of the bit-lists (%{[1, 0] => 507}) the simulator actually emitted since its first release. The type, every doc, and every doctest specified string keys; the engine never produced them, and helpers likeQx.SimulationResult.probability(result, "0")could never match a real result. Hardware (QPU) results already used string keys, so the two run paths now share one contract. Keys join each shot's classical bits in measurement order — identical to the labelsQx.draw_counts/2renders, so charts are unchanged. Behaviour change: code that pattern-matches list keys out ofresult.countsmust switch to strings (counts[[1, 0]]→counts["10"]).result.classical_bitsstill holds per-shot bit lists, as documented.Circuits containing a barrier no longer raise
Qx.GateError: Unsupported gate: :barrierwhen executed. Every barrier producer (Qx.barrier/2,Qx.barrier_all/1,2, OpenQASM import) stores the instruction with the spanned qubit list, but the simulation engine only treated the 0-qubit shape (which nothing produces) as a no-op, sorun/2,get_state/2, andsteps/2all raised. Barriers are now a no-op at any arity and do not advance therenormalize: ngate counter;Qx.steps/2still yields a step for them with the state unchanged.Qx.tap_state/2andQx.tap_probabilities/2(and theirQx.Operationscounterparts) now execute the circuit's instructions before invoking the inspection function. Previously they read the circuit's stored initial state, so the tap always saw|0...0⟩regardless of the gates applied before it, contradicting their documentation. Both taps now raiseQx.MeasurementErrorwhen the circuit so far contains measurements or conditionals, matchingQx.Simulation.get_state/2; tap before the firstmeasure/3orc_if/4in the pipeline.
0.9.0 - 2026-07-01
Changed
The IBM Quantum client now retries transient GET failures (
retry: :safe_transient) instead of failing on the first blip, and raises the/resultsreceive timeout to 60 s for multi-MB Sampler V2 results.Qx.Hardware.PortalGETs retry transiently too; POST requests (IAM exchange, job submission, transpile) are never auto-replayed.Raised minimum runtime dependency versions:
nx ~> 0.12(was~> 0.10),complex ~> 0.7(was~> 0.6, now required by nx 0.12), andreq ~> 0.6(was~> 0.5). No Qx API or numerical changes; Nx 0.12 renders float32 tensors at native precision, so twoQx.Mathdoctests were updated to match.
Security
Qx.Hardware.IbmandQx.Hardware.PortalHTTP error tuples ({:error, {:http, status, body}}) no longer echo the full decoded response body, which could carry echoed request context. The body is reduced to a recognised error message or a generic marker, bounded to ~256 characters. Behaviour change: the third element is now a bounded string rather than the raw response map/body.Qx.Hardware.Confignow rejects plaintexthttp://URLs to non-loopback hosts forportal_url,base_url, andiam_url. These carry the portal bearer token and route IBM IAM token exchange, so a remotehttpURL would expose them in cleartext.http://stays valid for loopback hosts (localhost,127.0.0.1,::1) so local mocks and dev are unaffected. Behaviour change: a config with a remotehttpURL now raisesQx.Hardware.ConfigErrorinstead of being accepted.The OpenQASM importer now caps parenthesis nesting depth (64) before parsing. The expression grammar recurses one frame per
(, so a((((…))))chain within the 1 MB source cap could exhaust the stack (:enomem); deeply nested input now raisesQx.QasmParseError.Qx.Export.OpenQASM.from_qasm_function/1now wraps the generated code in a uniqueQx.Generated.<Name>_<hash>module instead of returning a baredef, so a downstreamCode.compile_string/1can no longer inject an attacker-named helper into the caller's module. Behaviour change: the returnedsourceis adefmodule, and the result map gains a:modulekey.
0.8.1 - 2026-06-27
Added
Qx.draw_histogram/2, replacingQx.histogram/2. The new name matches the rest of theQx.draw*family (Qx.draw/2,Qx.draw_counts/2,Qx.draw_bloch/2,Qx.draw_state/2).Qx.StateInit.bell_state_vector/2andQx.StateInit.ghz_state_vector/2, the canonically-named state-vector constructors. The_vectorsuffix names the return type, disambiguating these from the circuit-returningQx.bell_state/1andQx.ghz_state/0.
Deprecated
Qx.StateInit.bell_state/2andQx.StateInit.ghz_state/2in favour of the_vector-suffixed names above. Both keep working through 0.8.x — they delegate verbatim to the new functions — and are scheduled for removal in v0.9.
Changed
Qx.Patterns.bell_state_circuit/1andQx.Patterns.ghz_state_circuit/1are now@doc false. The documented entry points for these circuits are theQx.bell_state/1andQx.ghz_state/1facades, which delegate to them; the Patterns functions remain callable (no API change), they just no longer appear as separate HexDocs pages.Public API surface is now declared explicitly (no API change). Iron Law #6 now guards the modules README and the tutorials actually treat as primary surface:
Qx.Qubit,Qx.Register,Qx.StateInit,Qx.Patterns,Qx.Math,Qx.Hardware,Qx.Hardware.Config,Qx.Export.OpenQASM, andQx.Draw(alongside the previously-declaredQx,Qx.QuantumCircuit,Qx.Operations,Qx.Simulation,Qx.SimulationResult,Qx.Behaviours.*). Internal helpers no longer appear in the published docs —Qx.Validation, theQx.Draw.SVG.*andQx.Export.OpenQASM.*sub-modules, andQx.Hardware.Ibm/Qx.Hardware.Portalare now@moduledoc false. Every function stays callable; the typedQx.*Errorexceptions remain part of the public contract.Typed errors for the last raw
ArgumentErrors inQx.Validation(plan: iron-law-7-followon). Completes the Iron Law #7 pass begun in 0.8.0.Qx.Validationnow raises typed exceptions instead ofArgumentError:- a non-numeric gate parameter raises the new
Qx.ParameterError(carries the offending:value); - duplicate qubit indices raise
Qx.QubitIndexError; - a state-vector shape mismatch raises
Qx.StateShapeError.
Observable on the public rotation/phase gates
Qx.rx/3,ry/3,rz/3,u/5,cp/4,crx/4,cry/4,crz/4(and theirQx.Operationsequivalents): a non-numeric angle now raisesQx.ParameterErrorrather thanArgumentError. Code rescuing the oldArgumentErrormust be updated.- a non-numeric gate parameter raises the new
Typed errors across the rest of the public surface (plan: iron-law-7-sweep). Clears the remaining raw
ArgumentErrors and one strayFunctionClauseErrorfrom the public API, finishing the Iron Law #7 work begun in 0.8.0. Two new exceptions:Qx.RegisterError— register construction input (an empty list, a malformed qubit, or a renderer handed a non-register); carries a:reason.Qx.BasisError— a computational basis value that is not 0 or 1; carries the offending:value.
Retyped surfaces — code rescuing the old
ArgumentError/FunctionClauseErrormust be updated:Qx.Register.new/1andQx.Register.from_basis_states/1→Qx.RegisterError/Qx.BasisError;- the register two-qubit distinctness gates (
cx,cz,cy,ccx,swap,iswap,cswap, and the controlled-target gates) →Qx.QubitIndexError; Qx.Qubit.from_basis/1→Qx.BasisError;Qx.Draw.*plots andQx.Draw.Tables.render/2with an invalid:format→Qx.OptionError; a non-register input →Qx.RegisterError;Qx.Export.OpenQASM.to_qasm/2with an invalid:version→Qx.OptionError;Qx.Draw.circuit/2on a malformed circuit →Qx.QubitCountError/Qx.GateError/Qx.QubitIndexError/Qx.ClassicalBitError;Qx.u/5with an out-of-range qubit now raisesQx.QubitIndexErrorinstead ofFunctionClauseError, matchingrx/ry/rz/cp.
Deprecated
Qx.histogram/2is deprecated. UseQx.draw_histogram/2instead. Emits a compile-time warning and is hidden from ExDoc. Scheduled for removal in v1.0.
0.8.0 - 2026-05-22
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 on
Qx.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 toregister.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 existingmeasure_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, andsuperposition_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 from
validation_test.exs.Qx.Validation removed from
mix.exsgroups_for_modules. After Phase A's@doc falsesweep, onlyvalid_qubit?/2andvalid_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" in
mix.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.exsgroups_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 predicatesQx.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:
entire modules tagged
Qx.Qubit.draw_bloch/2converted from adefwrapper to adefdelegate. 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, sorescue 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.QubitCountErrorconsistently: 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 raiseQx.QubitIndexErrorfor out-of-range or duplicate qubits, andQx.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 raisesQx.GateError, {:unsupported_gate, gate_name}instead ofRuntimeErrorfor unsupported gate names at any arity.Qx.QuantumCircuit.set_state/2now raisesQx.StateShapeErrorinstead 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 sametryblock 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.QubitandQx.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 byQx.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. Newexception/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/Mzclassical-outcome semantics:measure_xlowers toH ; Mz,measure_ylowers toSdg ; H ; Mz,measure_zis an alias ofmeasure/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 bitifor all qubits. RaisesQx.ClassicalBitErrorifnum_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 fromQx.QuantumCircuit.add_*/Qx.Validation.Configurable statevector renormalization + dev/test norm-drift guard in
Qx.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 typedQx.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.
0.7.1 - 2026-05-16
Fixed
Qx.Hardware.connect/2now supports discovery before a backend is chosen. It previously hard-validatedbackend ∈ backends_listand returned aQx.Hardware.ConfigErroron a blank backend, which broke the connect-then-pick flow (e.g. a UI populating a backend dropdown from the connect result). Blank (nil/""/whitespace)backendnow skips the membership check and returns the populated config; a set backend is still validated (catches typos early).run/3,run!/3, andsubmit_qasm/3now reject a blank backend up front with a clearConfigErrorinstead of failing deep in an IBM call.
Security
Qx.Hardware.Configno longer leaks credentials viainspect/1(qx-o9h). Added@derive {Inspect, except: [...]}so:portal_token,:ibm_api_key,:ibm_crn, and:access_tokenare redacted in all inspect output (Logger, BEAM crash reports, error tuples embedding the struct). Non-secret fields remain visible.
0.7.0 - 2026-05-15
BREAKING
Removed
Qx.Remote,Qx.Remote.Config, andQx.RemoteError. The qx_server-based hardware path has been replaced by direct-to-IBM execution viaQx.Hardware. There is no shim; the credential shape and call sites are different.Migration:
# before (0.6.x) config = Qx.Remote.Config.new!(url: "...", api_key: "...") {:ok, result} = Qx.Remote.run(circuit, config, backend: "ibm_brisbane", shots: 4096) # after (0.7.x) config = Qx.Hardware.Config.new!( portal_url: "https://api.qxquantum.com", portal_token: "<qxportal token>", ibm_api_key: "<ibm cloud api key>", ibm_crn: "<ibm quantum service crn>", ibm_region: "us-east", backend: "ibm_brisbane", shots: 4096 ) {:ok, result} = Qx.Hardware.run(circuit, config)The new
Qx.Hardware.Config.from_env!/1readsQX_PORTAL_URL,QX_PORTAL_TOKEN,QX_IBM_API_KEY,QX_IBM_CRN,QX_IBM_REGION,QX_IBM_BACKENDfor a one-liner setup.
Added
Qx.Hardware: public namespace owning the full direct-to-IBM execution pipeline (IAM exchange → qxportal transpile → IBM submit → poll → result-build).Qx.Hardware.Config: credential + execution-preference struct (portal_url,portal_token,ibm_api_key,ibm_crn,ibm_region,backend,optimization_level,shots). Validates region against the IBM allowlist, optimization_level0..3, shots1..100_000, portal URL scheme.Qx.Hardware.Ibm: Req-based client for IBM Quantum (Qiskit Runtime REST API). IAM exchange + 401-refresh-retry, backends list, backend configuration, Sampler V2 submission, poll loop with Pascal-Case status allowlist, sample-aggregation to counts, best-effort cancel.Qx.Hardware.Portal: Req-based client for the qxportal/api/v1/meand/api/v1/transpileendpoints. Atomize allowlist for response keys.Qx.Hardware.NoMeasurementsError: raised when a circuit submitted to hardware has nomeasure/2instructions.Qx.Hardware.ConfigError: typed validation error forQx.Hardware.Config.- Privacy invariant: two independent HTTP clients; the portal token never reaches IBM, and the IBM API key/CRN never reach the portal.
- Status callback: pipeline progress events (
{:portal, :transpiling},{:ibm, :polling, status}, …). All atoms literal (Iron Law #1: noString.to_atomon caller input).
Removed
Qx.Remote,Qx.Remote.Config,Qx.RemoteError(see BREAKING above).examples/remote/: superseded byexamples/hardware/run_on_ibm.exs.
Dependencies
- New test-only dep:
{:bypass, "~> 2.1"}(HTTP stubbing for portal + IBM tests). - Explicit pin:
{:jason, "~> 1.4"}(previously transitive via Req/Plug).
0.6.0 - 2026-05-04
Added
- OpenQASM 3.0 import:
Qx.Export.OpenQASM.from_qasm/1andfrom_qasm!/1parse OpenQASM 3 source produced by Qx itself, by Qiskit, or by IBM Quantum and return a%Qx.QuantumCircuit{}. Round-trips withto_qasm/1(statevectors match within 1e-10). - Gate definition codegen:
Qx.Export.OpenQASM.from_qasm_function/1(and the bang variant) parses agate name(p1, …) a, b { … }definition and returns%{name, arity, source}, wheresourceis an Elixirdef …string that compiles viaCode.compile_string/1. Function signature:(circuit, params…, qubits…). - Supported gate set on import: direct mappings for
h, x, y, z, s, sdg, t, rx, ry, rz, p, phase, u, u3, cx, CX, cz, swap, iswap, cp, cphase, ccx, cswap. Decompositions fortdg → phase(-π/4),sx → u(π/2, -π/2, π/2),u1(λ) → phase(λ),u2(φ, λ) → u(π/2, φ, λ).idis dropped. - Typed import errors:
Qx.QasmParseError(line/column/snippet) andQx.QasmUnsupportedError(feature/line/hint) for grammar failures and out-of-scope features respectively. Qx.cp/4: controlled-phase gate applying e^(i·θ) to the |11⟩ basis state, required for QFT and QPE circuitsQx.swap/3: SWAP gate exchanging the quantum states of two qubits; includes circuit diagram rendering (× symbols connected by a line) and OpenQASM 3 exportQx.iswap/3: iSWAP gate exchanging qubit states with an i phase factor on the swapped components; native to superconducting hardware; includes circuit diagram rendering (labelled iSW boxes) and OpenQASM 3 exportQx.u/5: general single-qubit unitary gate U(θ,φ,λ) per IBM/OpenQASM 3 convention; subsumes X, Y, Z, H, RX, RY, RZ as special cases; includes circuit diagram rendering and OpenQASM 3 exportQx.cswap/4: Fredkin (controlled-SWAP) gate; swaps two target qubits when the control is |1⟩; universal reversible gate used in quantum multiplexers and arithmetic circuits; includes circuit diagram rendering and OpenQASM 3 export
Not supported on import (raises Qx.QasmUnsupportedError)
- Multi-register programs (Qx models a single quantum + single classical register)
elsebranches onif(refactor as twoifstatements)- Gate modifiers
inv @,pow(N) @,ctrl @,negctrl @ def,for,while,switch, classical types beyondbit,defcal,let,pragma,extern,box,delay,reset- stdgates
cy,ch,crx,cry,crz,cu(no Qx equivalent yet) - Qiskit-extension gates
rxx,ryy,rzz,rzx(not instdgates.inc) - Discarded
measure q[i];(Qx requires a classical bit target) - Complex boolean conditions (
&&,||)
Dependencies
- New runtime dependency:
nimble_parsec ~> 1.4(compile-time parser generator)
0.5.2 - 2026-04-11
Added
- Bell State Extensions -
Qx.bell_state/2now supports all four Bell states:phi_plus(default),phi_minus,psi_plus, andpsi_minus
Fixed
- Circuit diagram: measurement arrowhead now terminates at the classical register double line instead of extending 8 px past it
- Bloch sphere rendering: improved wireframe contrast, white halos on axis and state labels, and equatorial projection indicator for clearer visualization
0.5.1 - 2026-03-07
Added
- S-dagger (Sdg) Gate - New
sdggate implementing the S† operation (-π/2 phase rotation on |1⟩)Qx.sdg/2for Circuit Mode: adds an sdg gate to a quantum circuitQx.Operations.sdg/2for direct operations APIQx.Qubit.sdg/1for Calculation Mode on single qubitsQx.Register.sdg/2for Calculation Mode on multi-qubit registers- Full simulation support in
Qx.Simulation(mapped toQx.Gates.s_dagger/0) - OpenQASM 3.0 export support (
sdg q[0];) - Validation support for the
:sdggate atom - Full test coverage including matrix correctness, S·S† = I identity verification, and circuit export
- New LiveBook Tutorials - Expanded tutorial collection at
examples/tutorials/quantum_state_and_qubit.livemd- Introduction to quantum states and single-qubit operationsquantum_measurement.livemd- Quantum measurement concepts and examplessystems_of_qubits_and_entanglement.livemd- Multi-qubit systems and entanglementquantum_algorithms.livemd- Common quantum algorithms with Qx
0.5.0 - 2026-02-17
Added
- Remote Execution via QxServer - Run quantum circuits on real hardware through QxServer
- New
Qx.Remotemodule for submitting circuits, polling job status, and retrieving results - New
Qx.Remote.Configfor configuring QxServer connection (URL, API key, timeout) - New
Qx.ResultBuilderfor constructingQx.SimulationResultstructs from hardware counts data - New
Qx.RemoteErrorexception type for remote execution errors - Example script at
examples/remote/run_on_hardware.exs
- New
- Quantum Operations Tutorial - New comprehensive LiveBook tutorial covering quantum gates, Bloch sphere, and two-qubit operations at
examples/tutorials/quantum_operations_tutorial.livemd
Changed
- README Restructure - Major reorganization for better new-user experience
- Moved Performance & Acceleration section (~380 lines) from between Installation and Quick Start to the end
- Removed API Reference section (duplicated by hexdocs) and Module Structure section
- Added "Understanding the Two Modes" orientation section with comparison table
- Added consolidated Visualization section
- Added links to hexdocs and LiveBook guides
- Reduced README from 1,308 to ~750 lines
Fixed
Qx.Qubit.draw_bloch/2now correctly defaults to:vega_liteformat (was ignoring the default and using:svg)- Draw functions (
Qx.Draw.plot_counts/2,Qx.Draw.plot/2) now correctly handleSimulationResultstructs from hardware backends where counts keys are binary strings - OpenQASM export formatting improvements
0.4.0 - 2025-12-23
Added
- OpenQASM 3.0 Export - Export quantum circuits to OpenQASM format for real quantum hardware execution
- Full support for OpenQASM 3.0 syntax including conditionals
- Export via
Qx.Export.OpenQASM.to_qasm/2with customizable options - Supports all quantum gates, measurements, barriers, and conditional operations
- Enables seamless integration with IBM Quantum, Rigetti, and other quantum hardware platforms
- Error Handling Documentation (qx-gd5) - Comprehensive CONTRIBUTING.md with error handling philosophy and best practices
- Detailed guidelines for error types, messages, and recovery strategies
- Error handling patterns for library developers
- Examples of proper error propagation and context enrichment
- Documentation of all custom error types and their use cases
- Test Coverage Integration (qx-xbf) - Complete test coverage metrics and CI/CD integration
- Added ExCoveralls dependency for code coverage reporting
- Achieved 66.4% test coverage across the codebase
- Integrated coverage reporting into CI/CD pipeline
- Configured multiple coverage output formats (HTML, JSON, Cobertura)
- Added GitHub Actions integration for coverage tracking
Changed
- Predicate Function Conventions (qx-7iw) - Enhanced predicate naming and specifications
- Added
@spectype specifications to all predicate functions - Improved naming conventions for boolean-returning functions
- Enhanced documentation for predicate function usage patterns
- Better consistency across module APIs
- Added
- Module Documentation (qx-sdb) - Verified comprehensive module documentation
- Confirmed all modules have proper
@moduledocdocumentation - Ensured consistent documentation style across the codebase
- Enhanced module-level descriptions and usage examples
- Confirmed all modules have proper
Fixed
- Credo strict mode compliance in OpenQASM export module
- Refactored complex pattern matching to reduce cyclomatic complexity
- Used
Enum.map_join/3for better performance - Added inline Credo exception for legitimate gate mapping complexity
Improved
- Development workflow with better error handling guidelines
- Code quality with comprehensive type specifications
- Test coverage visibility and tracking
- Documentation completeness and consistency
- Hardware integration capabilities via OpenQASM export
0.3.0 - 2025-12-21
Added
- Runtime Backend Selection - Major new feature allowing backend specification at runtime without compile-time configuration
- Added
:backendoption toQx.run/2,Qx.get_state/2, andQx.get_probabilities/2 - Users can now specify different backends for different circuits:
Qx.run(circuit, backend: EXLA.Backend) - Supports all Nx backends including EXLA (CPU/CUDA/ROCm) and EMLX (Apple Silicon GPU)
- Combines with other options:
Qx.run(circuit, backend: EXLA.Backend, shots: 2048) - Maintains full backward compatibility with existing code
- Implemented using
Nx.with_default_backend/2for proper scoped execution - Comprehensive documentation added to README.md with usage examples and best practices
- Added
Changed
- Draw Module Refactoring - Reorganized visualization code for better maintainability and clarity
- Split large 1,531-line
Qx.Drawmodule into 5 focused sub-modules:Qx.Draw.VegaLite- VegaLite chart generation for LiveBook (178 lines)Qx.Draw.SVG.Charts- SVG histogram and bar charts (199 lines)Qx.Draw.SVG.Bloch- Bloch sphere visualization with 3D projection (267 lines)Qx.Draw.SVG.Circuit- Quantum circuit diagrams with IEEE notation (596 lines)Qx.Draw.Tables- State table formatting with Kino support (196 lines)
Qx.Drawnow serves as a clean API facade, delegating to specialized sub-modules- Maintains 100% backward compatibility - no API changes required
- Improved code organization following single responsibility principle
- Better separation of concerns between rendering formats and visualization types
- All 557 tests continue to pass
- Split large 1,531-line
Fixed
- Fixed Nx backend configuration anti-pattern where library imposed compile-time backend choices on users
- Eliminated warnings about undefined
Nx.default_backend/2function
0.2.5 - 2025-12-16
Fixed
- More fixes to the pipeline
0.2.4 - 2025-12-16
Fixed
- More automation of the release and build process
- pipeline fixes
0.2.3 - 2025-12-14
Fixed
- Simplified application configuration to resolve Nx.Defn compilation issues
- Removed unnecessary application dependencies that were causing compile-time conflicts
0.2.2 - 2025-12-14
Fixed
- Added
nxandcomplexto extra_applications in mix.exs to fix compilation errors when using the Hex package - Ensures dependencies are loaded before qx_sim compiles
Changed
- Published to Hex.pm as
qx_sim(package name "qx" was already taken) - Updated installation instructions to use Hex.pm syntax
- Added Hex.pm badges to README
0.2.1 - 2025-11-26
Changed
- Improved readability of Bloch sphere labels
- Refactored code and tidied up documentation
- Cleaned up old test files
- Updated README.md
Fixed
- Fixed CNOT gate error
- Fixed dependencies
- Fixed
mix.exsconfiguration
0.2.0 - 2025-11-01
Added
Core Quantum Computing Features
- Full quantum circuit API with chainable operations via
Qxmodule - Support for 20+ quantum gates including:
- Single-qubit gates: H, X, Y, Z, S, T, Sdg, Tdg
- Parametric rotation gates: RX, RY, RZ with arbitrary angles
- Two-qubit gates: CNOT (CX), CZ (Controlled-Z), SWAP
- Multi-qubit support up to 20 qubits
- Measurement operations with classical bit storage and reset capabilities
- Conditional operations based on classical measurement results
- Statevector simulation using Nx tensors with Complex64 format
- Direct state access via
Qx.get_state/1
Visualization
- Circuit diagram generation with
Qx.Draw.circuit/2for publication-quality SVG output - State visualization using VegaLite: bar charts, probability distributions, Bloch sphere
- SVG export capability for all visualization types
- Example visualization scripts in
examples/directory includingcircuit_visualization_example.exs
Performance & Acceleration
- EXLA backend integration for CPU acceleration (~100x speedup vs Binary)
- EMLX backend support for Apple Silicon GPU acceleration (M1/M2/M3/M4)
- Automatic backend detection and configuration
- JIT compilation support via Nx.Defn
Benchmarking Suite
- Professional benchmarking infrastructure using Benchee
- GHZ state scaling benchmarks (5, 10, 15, 20 qubits)
- Backend comparison benchmarks (Binary, EXLA CPU, EMLX GPU, EXLA CUDA/ROCm)
- HTML report generation with interactive graphs
- Statistical analysis with warmup, iterations, and memory profiling
- Safe GPU backend detection with graceful fallback
Documentation & Examples
- Comprehensive API documentation with examples
- Example files demonstrating:
- Basic quantum circuit operations
- Complex number handling
- Bell state creation
- Quantum teleportation protocol
- Conditional circuit operations
- Grover's search algorithm
- Circuit visualization techniques
- Performance benchmarking guide
- Backend configuration documentation
Error Handling
- Structured error types for better debugging:
Qx.QubitIndexError- Invalid qubit indicesQx.StateNormalizationError- State vector normalization issuesQx.MeasurementError- Measurement failuresQx.ConditionalError- Conditional operation errorsQx.ClassicalBitError- Classical bit access errorsQx.GateError- Gate application failuresQx.QubitCountError- Invalid qubit count specifications
Changed
- Updated state representation to use
:c64(Complex64) tensor format for improved performance - Migrated from Torchx to EMLX for Apple Silicon GPU acceleration (pure Elixir, no Python)
- Enhanced error messages with context and suggestions
- Improved documentation structure with module grouping
- Updated examples to work with latest Complex number API
Performance
- ~100x speedup with EXLA CPU backend compared to Binary backend
- Additional 2-10x speedup with GPU acceleration (hardware dependent)
- Efficient statevector manipulation with direct tensor operations
- Optimized gate application avoiding unnecessary matrix construction
Fixed
- Complex number handling in example files for
:c64format - CZ gate now properly exposed in main
Qxmodule API - Backend detection error handling for unavailable GPU platforms
- Output directory creation in visualization examples
- Grover's algorithm now uses proper CZ gates instead of H-CX-H decomposition
Developer Experience
- Added
:usage_rulesdependency for better development ergonomics - Comprehensive test suite with 549 passing tests
- Modular architecture separating concerns (Circuit, Operations, Simulation, etc.)
- Clean API design following Elixir conventions
0.1.0 - 2024-10-05
Added
- Initial release
- Basic quantum circuit functionality
- Core gate operations
- Statevector simulation
- Nx backend integration