Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.10.1] - 2026-07-04

### Fixed

- README corrected against the released 0.10 surface (docs-only
  release; no code changes). The install snippets were broken outright
  (`{:qx_sim, "~> 0.8.0"}` is the wrong app atom; Livebook cells
  pinned `~> 0.6.0`), the Bell quick-start showed counts a
  measurement-free circuit can never produce, the GHZ example showed
  pre-0.10 list-shaped counts keys, and the `from_qasm_function/1`
  recipe predated the 0.9 full-module source shape. Also: nonexistent
  `RemoteError` replaced with `MissingDependencyError`, Nx floor
  corrected to 0.12, VegaLite documented as optional, and the stale
  QxServer naming replaced with `Qx.Hardware` + Qx Portal. Every
  corrected example was executed against 0.10.0 before landing.

## [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-2 `Qx.Draw.circuit/2`). Returns a
  `Qx.Draw.Image`.
- `Qx.Draw.Image` and `Qx.Draw.StateTable`: artifact structs returned
  by `draw_bloch`/`draw_circuit` and `draw_state`. Plain data with one
  static shape everywhere — `Inspect` for IEx, raw SVG/text/markdown/
  HTML fields for standalone use, and rich Livebook rendering via
  `Kino.Render`.
- `Kino.Render` implementations (active only when Livebook provides
  the optional `kino` dependency) for `Qx.QuantumCircuit` (a cell
  returning a circuit renders its diagram), `Qx.SimulationResult`
  (counts table), `Qx.Step` (state table), `Qx.Draw.Image`, and
  `Qx.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/2` return `VegaLite.t()` only;
  `draw_bloch/2` returns `Qx.Draw.Image`; `draw_state/2` returns
  `Qx.Draw.StateTable`. The `:format` option is gone everywhere, and
  `draw_state` no longer sniffs for Kino at runtime (the same call
  returned `%Kino.Markdown{}` in Livebook and a string in a release).
- **Breaking:** tier-2 `Qx.Draw` names now follow the facade
  (`Qx.draw_X` delegates to `Qx.Draw.X`): `plot_counts` is now
  `counts`, `bloch_sphere` is now `bloch`.
- `vega_lite` is now an optional dependency: only the three
  VegaLite-returning chart functions need it, and they raise
  `Qx.MissingDependencyError` when 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/1` when you want to
  see the state evolve. The "Which `h` am 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 new
  `Qx.draw_circuit/2`) returns a `Qx.Draw.Image` instead of a raw SVG
  string. Where 0.8.x docs said `File.write!(path, svg)`, write
  `image.svg`.

### Deprecated

- `Qx.Qubit` and `Qx.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: :svg` on
  `draw`/`draw_counts`/`draw_histogram`): a usage inventory found no
  consumer anywhere. SVG remains the artifact for Bloch spheres and
  circuit diagrams via `Qx.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,2` and `%Qx.Step{}`: a lazy stream of per-operation steps for
  walking through a circuit's execution. Works on circuits with mid-circuit
  measurement and `c_if` (one stochastic trajectory per materialisation;
  `seed:` reproduces a trajectory without touching the caller's process
  `:rand` state). 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`: the `Qx.Register.show_state/1` display map (Dirac string,
  amplitudes, probabilities) for any step, plus a readable single-line
  `Inspect` rendering.
- `Qx.tap_state/2` and `Qx.tap_probabilities/2` now share the stepper's
  execution path internally. No behaviour change: same values, same
  `Qx.MeasurementError` contract on measured/conditional prefixes.
- `Qx.StateInit.bell_state/0,1,2` and `Qx.StateInit.ghz_state/1,2`, the
  deprecated state-vector aliases: use `bell_state_vector/0,1,2` and
  `ghz_state_vector/1,2`. The circuit-returning `Qx.bell_state/0,1` and
  `Qx.ghz_state/0` facades are unaffected. Deprecated through 0.8.x;
  window closed.
- `Qx.Math.basis_state/2`, the f32 shim deprecated in 0.8.x: use
  `Qx.StateInit.basis_state/3` (c64, matching the quantum-state surface).
- `Qx.histogram/1,2`: use `Qx.draw_histogram/1,2`, in place since one
  minor before this release.

### Fixed

- **`SimulationResult.counts` keys 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 like
  `Qx.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 labels
  `Qx.draw_counts/2` renders, so charts are unchanged. **Behaviour change:** code that
  pattern-matches list keys out of `result.counts` must switch to
  strings (`counts[[1, 0]]` → `counts["10"]`). `result.classical_bits`
  still holds per-shot bit lists, as documented.
- Circuits containing a barrier no longer raise
  `Qx.GateError: Unsupported gate: :barrier` when 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, so `run/2`, `get_state/2`, and `steps/2` all
  raised. Barriers are now a no-op at any arity and do not advance the
  `renormalize: n` gate counter; `Qx.steps/2` still yields a step for
  them with the state unchanged.

- `Qx.tap_state/2` and `Qx.tap_probabilities/2` (and their `Qx.Operations`
  counterparts) 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 raise
  `Qx.MeasurementError` when the circuit so far contains measurements or
  conditionals, matching `Qx.Simulation.get_state/2`; tap before the first
  `measure/3` or `c_if/4` in 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 `/results` receive timeout to 60 s for multi-MB Sampler V2 results.
  `Qx.Hardware.Portal` GETs 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), and `req ~> 0.6`
  (was `~> 0.5`). No Qx API or numerical changes; Nx 0.12 renders float32
  tensors at native precision, so two `Qx.Math` doctests were updated to match.

### Security

- `Qx.Hardware.Ibm` and `Qx.Hardware.Portal` HTTP 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.Config` now rejects plaintext `http://` URLs to non-loopback
  hosts for `portal_url`, `base_url`, and `iam_url`. These carry the portal
  bearer token and route IBM IAM token exchange, so a remote `http` URL 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 remote `http` URL now raises
  `Qx.Hardware.ConfigError` instead 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 raises `Qx.QasmParseError`.

- `Qx.Export.OpenQASM.from_qasm_function/1` now wraps the generated code in a
  unique `Qx.Generated.<Name>_<hash>` module instead of returning a bare `def`,
  so a downstream `Code.compile_string/1` can no longer inject an
  attacker-named helper into the caller's module. **Behaviour change:** the
  returned `source` is a `defmodule`, and the result map gains a `:module` key.

## [0.8.1] - 2026-06-27

### Added

- `Qx.draw_histogram/2`, replacing `Qx.histogram/2`. The new name
  matches the rest of the `Qx.draw*` family (`Qx.draw/2`,
  `Qx.draw_counts/2`, `Qx.draw_bloch/2`, `Qx.draw_state/2`).

- `Qx.StateInit.bell_state_vector/2` and `Qx.StateInit.ghz_state_vector/2`,
  the canonically-named state-vector constructors. The `_vector` suffix
  names the return type, disambiguating these from the circuit-returning
  `Qx.bell_state/1` and `Qx.ghz_state/0`.

### Deprecated

- `Qx.StateInit.bell_state/2` and `Qx.StateInit.ghz_state/2` in 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/1` and `Qx.Patterns.ghz_state_circuit/1` are
  now `@doc false`. The documented entry points for these circuits are the
  `Qx.bell_state/1` and `Qx.ghz_state/1` facades, 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`, and
  `Qx.Draw` (alongside the previously-declared `Qx`, `Qx.QuantumCircuit`,
  `Qx.Operations`, `Qx.Simulation`, `Qx.SimulationResult`, `Qx.Behaviours.*`).
  Internal helpers no longer appear in the published docs — `Qx.Validation`,
  the `Qx.Draw.SVG.*` and `Qx.Export.OpenQASM.*` sub-modules, and
  `Qx.Hardware.Ibm` / `Qx.Hardware.Portal` are now `@moduledoc false`. Every
  function stays callable; the typed `Qx.*Error` exceptions remain part of the
  public contract.

- **Typed errors for the last raw `ArgumentError`s in `Qx.Validation`
  (plan: iron-law-7-followon).** Completes the Iron Law #7 pass begun
  in 0.8.0. `Qx.Validation` now raises typed exceptions instead of
  `ArgumentError`:

    - 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 their
  `Qx.Operations` equivalents): a non-numeric angle now raises
  `Qx.ParameterError` rather than `ArgumentError`. Code rescuing the
  old `ArgumentError` must be updated.

- **Typed errors across the rest of the public surface
  (plan: iron-law-7-sweep).** Clears the remaining raw `ArgumentError`s
  and one stray `FunctionClauseError` from 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` /
  `FunctionClauseError` must be updated:

    - `Qx.Register.new/1` and `Qx.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 and `Qx.Draw.Tables.render/2` with an invalid
      `:format` → `Qx.OptionError`; a non-register input →
      `Qx.RegisterError`;
    - `Qx.Export.OpenQASM.to_qasm/2` with an invalid `:version` →
      `Qx.OptionError`;
    - `Qx.Draw.circuit/2` on a malformed circuit →
      `Qx.QubitCountError` / `Qx.GateError` / `Qx.QubitIndexError` /
      `Qx.ClassicalBitError`;
    - `Qx.u/5` with an out-of-range qubit now raises
      `Qx.QubitIndexError` instead of `FunctionClauseError`, matching
      `rx`/`ry`/`rz`/`cp`.

### Deprecated

- `Qx.histogram/2` is deprecated. Use `Qx.draw_histogram/2` instead.
  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.Register` as direct
  state-vector evolutions:

    - `Qx.Register.cy/3`: controlled-Y
    - `Qx.Register.crx/4`, `cry/4`, `crz/4`: controlled rotations
    - `Qx.Register.cp/4`: controlled-phase
    - `Qx.Register.swap/3`, `iswap/3`: two-qubit swaps
    - `Qx.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 an `apply_controlled_target/4` internal 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`,
  and `measure_z/1` return the probability distribution in the X, Y,
  and Z bases respectively (the Z form is an alias of the existing
  `measure_probabilities/1` for symmetry). Maps directly to QAAL `Mx`,
  `My`, `Mz`. Implementation reuses the existing single-qubit gate
  pipeline (`H` for X-basis, `Sdg ; H` for 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-level `Qx.bell_state`,
  `Qx.ghz_state`, and `Qx.superposition` now 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)`
  and `Qx.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.Register` now declares `@behaviour
  Qx.Behaviours.QuantumState` and 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_callbacks` so 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 and `Qx.Register`.

  Single-qubit `Qx.Qubit` is **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.exs` `groups_for_modules`.**
  After Phase A's `@doc false` sweep, only `valid_qubit?/2` and
  `valid_register?/2` remain visible: too thin to warrant a top-level
  group. The module page itself is still reachable; the renamed
  "Utilities" group lists `Qx.Math` and `Qx.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.exs` were exporting `def` functions that are used only inside
  the library. The following are now `@moduledoc false` or
  per-function `@doc false`:

  - **Qx.Gates, Qx.Calc, Qx.CalcFast, Qx.Format, Qx.ResultBuilder**:
    entire modules tagged `@moduledoc false`. Removed from `mix.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 is `Qx.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?/2` and `valid_register?/2` remain
    public.
  - **Qx.Math.complex_to_tensor, tensor_to_complex, complex_matrix**:
    `@doc false`. The rest of `Qx.Math` (`complex/2`, `identity/1`,
    `unitary?/1`, `probabilities/1`) stays public.

  No call site breaks. Existing tests pass unchanged.

- **`Qx.Qubit.draw_bloch/2`** converted from a `def` wrapper to a
  `defdelegate`. Behaviour unchanged.

- **`Qx.Error` `@moduledoc` rewritten 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.Error` catches *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/3` instead. 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)` raised `FunctionClauseError` via a guard
  (lower bound untyped). Both paths now raise `Qx.QubitCountError`
  consistently: the function calls the internal `validate_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.*Error` exception instead of `FunctionClauseError`, `ArgumentError`,
  or `RuntimeError`. 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/3` now raise
    `Qx.QubitIndexError` for out-of-range or duplicate qubits, and
    `Qx.ClassicalBitError` for out-of-range classical-bit indices.
  - `Qx.Operations.barrier/2` now raises `Qx.QubitIndexError`.
  - `Qx.Operations.c_if/4` now raises `Qx.ClassicalBitError`
    (out-of-range bit) and `Qx.ConditionalError` (invalid value,
    non-function `gate_fn`, nested conditional).
  - `Qx.Simulation.run/2` (and the `Qx.run/2` delegate) now raises
    `Qx.GateError, {:unsupported_gate, gate_name}` instead of
    `RuntimeError` for unsupported gate names at any arity.
  - `Qx.QuantumCircuit.set_state/2` now raises `Qx.StateShapeError`
    instead of `ArgumentError` on size or rank mismatch.
  - `Qx` and `Qx.Operations` docstring `## Raises` sections updated
    accordingly.

  Migration: rescue clauses matching `FunctionClauseError`,
  `ArgumentError`, or `RuntimeError` at these public call sites must
  be updated to the matching `Qx.*Error` exception. The same `try`
  block can rescue `Qx.Error` to 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 bare `ArgumentError`; `Qx.Qubit`
  and `Qx.Register` still raise `ArgumentError` from public functions;
  `Qx.Operations.u/5` still fires `FunctionClauseError` for 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/2` when the supplied state vector's
  shape doesn't match `{2^num_qubits}`. Carries `:actual` and
  `:expected` size fields.

- **`Qx.QubitIndexError` `{:duplicate, qubits}` constructor.** New
  `exception/1` clause to raise on distinct-indices violations (e.g.
  CNOT with `control == 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 QAAL `CY`/`CRx`/`CRy`/`CRz` and
  OpenQASM 3 `cy`/`crx`/`cry`/`crz`. Simulation handlers reuse the
  existing two-qubit `controlled_gate/4` contraction. 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 QAAL `Mx`/`My`/`Mz`
  classical-outcome semantics: `measure_x` lowers to `H ; Mz`,
  `measure_y` lowers to `Sdg ; H ; Mz`, `measure_z` is an alias of
  `measure/3` for 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.Patterns` sub-register overload (`/2` arity)
  (plan: qaal-parity).** `h_all/2`, `x_all/2`, `y_all/2`, `z_all/2`,
  `measure_all/2`, `barrier_all/2` accept a list or `Range` of qubit
  indices in addition to the existing whole-circuit `/1` form. 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 over `Qx.Operations` for 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 qubit `i` into classical bit
    `i` for all qubits. Raises `Qx.ClassicalBitError` if
    `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`, …) via `defdelegate`. Purely additive: no
  breaking change. Out-of-range qubit indices propagate the existing
  typed `Qx.QubitIndexError` inherited from
  `Qx.QuantumCircuit.add_*` / `Qx.Validation`.

- **Configurable statevector renormalization + dev/test norm-drift
  guard in `Qx.Simulation.run/2` / `Qx.run/2` (qx-53v).** New
  `:renormalize` option (default `false`: fully backwards compatible,
  zero cost when off): `true` renormalizes at measurement-time; a
  positive integer `N` renormalizes every `N` gates 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 beyond `1.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/2` now supports discovery before a backend is
  chosen.** It previously hard-validated `backend ∈ backends_list` and
  returned a `Qx.Hardware.ConfigError` on 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) `backend` now
  skips the membership check and returns the populated config; a
  *set* backend is still validated (catches typos early). `run/3`,
  `run!/3`, and `submit_qasm/3` now reject a blank backend up front
  with a clear `ConfigError` instead of failing deep in an IBM call.

### Security

- **`Qx.Hardware.Config` no longer leaks credentials via `inspect/1`**
  (qx-o9h). Added `@derive {Inspect, except: [...]}` so `:portal_token`,
  `:ibm_api_key`, `:ibm_crn`, and `:access_token` are 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`, and `Qx.RemoteError`.** The qx_server-based hardware path has been replaced by direct-to-IBM execution via `Qx.Hardware`. There is no shim; the credential shape and call sites are different.

  Migration:

  ```elixir
  # 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!/1` reads `QX_PORTAL_URL`, `QX_PORTAL_TOKEN`, `QX_IBM_API_KEY`, `QX_IBM_CRN`, `QX_IBM_REGION`, `QX_IBM_BACKEND` for 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_level `0..3`, shots `1..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/me` and `/api/v1/transpile` endpoints. Atomize allowlist for response keys.
- **`Qx.Hardware.NoMeasurementsError`**: raised when a circuit submitted to hardware has no `measure/2` instructions.
- **`Qx.Hardware.ConfigError`**: typed validation error for `Qx.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: no `String.to_atom` on caller input).

### Removed

- `Qx.Remote`, `Qx.Remote.Config`, `Qx.RemoteError` (see BREAKING above).
- `examples/remote/`: superseded by `examples/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/1` and `from_qasm!/1` parse OpenQASM 3 source produced by Qx itself, by Qiskit, or by IBM Quantum and return a `%Qx.QuantumCircuit{}`. Round-trips with `to_qasm/1` (statevectors match within 1e-10).
- **Gate definition codegen**: `Qx.Export.OpenQASM.from_qasm_function/1` (and the bang variant) parses a `gate name(p1, …) a, b { … }` definition and returns `%{name, arity, source}`, where `source` is an Elixir `def …` string that compiles via `Code.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 for `tdg → phase(-π/4)`, `sx → u(π/2, -π/2, π/2)`, `u1(λ) → phase(λ)`, `u2(φ, λ) → u(π/2, φ, λ)`. `id` is dropped.
- **Typed import errors**: `Qx.QasmParseError` (line/column/snippet) and `Qx.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 circuits
- `Qx.swap/3`: SWAP gate exchanging the quantum states of two qubits; includes circuit diagram rendering (× symbols connected by a line) and OpenQASM 3 export
- `Qx.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 export
- `Qx.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 export
- `Qx.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)
- `else` branches on `if` (refactor as two `if` statements)
- Gate modifiers `inv @`, `pow(N) @`, `ctrl @`, `negctrl @`
- `def`, `for`, `while`, `switch`, classical types beyond `bit`, `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 in `stdgates.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/2` now supports all four Bell states: `phi_plus` (default), `phi_minus`, `psi_plus`, and `psi_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 `sdg` gate implementing the S† operation (-π/2 phase rotation on |1⟩)
  - `Qx.sdg/2` for Circuit Mode: adds an sdg gate to a quantum circuit
  - `Qx.Operations.sdg/2` for direct operations API
  - `Qx.Qubit.sdg/1` for Calculation Mode on single qubits
  - `Qx.Register.sdg/2` for Calculation Mode on multi-qubit registers
  - Full simulation support in `Qx.Simulation` (mapped to `Qx.Gates.s_dagger/0`)
  - OpenQASM 3.0 export support (`sdg q[0];`)
  - Validation support for the `:sdg` gate 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 operations
  - `quantum_measurement.livemd` - Quantum measurement concepts and examples
  - `systems_of_qubits_and_entanglement.livemd` - Multi-qubit systems and entanglement
  - `quantum_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.Remote` module for submitting circuits, polling job status, and retrieving results
  - New `Qx.Remote.Config` for configuring QxServer connection (URL, API key, timeout)
  - New `Qx.ResultBuilder` for constructing `Qx.SimulationResult` structs from hardware counts data
  - New `Qx.RemoteError` exception type for remote execution errors
  - Example script at `examples/remote/run_on_hardware.exs`
- **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/2` now correctly defaults to `:vega_lite` format (was ignoring the default and using `:svg`)
- Draw functions (`Qx.Draw.plot_counts/2`, `Qx.Draw.plot/2`) now correctly handle `SimulationResult` structs 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/2` with 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 `@spec` type specifications to all predicate functions
  - Improved naming conventions for boolean-returning functions
  - Enhanced documentation for predicate function usage patterns
  - Better consistency across module APIs
- **Module Documentation (qx-sdb)** - Verified comprehensive module documentation
  - Confirmed all modules have proper `@moduledoc` documentation
  - Ensured consistent documentation style across the codebase
  - Enhanced module-level descriptions and usage examples

### Fixed
- Credo strict mode compliance in OpenQASM export module
  - Refactored complex pattern matching to reduce cyclomatic complexity
  - Used `Enum.map_join/3` for 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 `:backend` option to `Qx.run/2`, `Qx.get_state/2`, and `Qx.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/2` for proper scoped execution
  - Comprehensive documentation added to README.md with usage examples and best practices

### Changed
- **Draw Module Refactoring** - Reorganized visualization code for better maintainability and clarity
  - Split large 1,531-line `Qx.Draw` module 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.Draw` now 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

### Fixed
- Fixed Nx backend configuration anti-pattern where library imposed compile-time backend choices on users
- Eliminated warnings about undefined `Nx.default_backend/2` function

## [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 `nx` and `complex` to 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.exs` configuration

## [0.2.0] - 2025-11-01

### Added

#### Core Quantum Computing Features
- Full quantum circuit API with chainable operations via `Qx` module
- 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/2` for 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 including `circuit_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 indices
  - `Qx.StateNormalizationError` - State vector normalization issues
  - `Qx.MeasurementError` - Measurement failures
  - `Qx.ConditionalError` - Conditional operation errors
  - `Qx.ClassicalBitError` - Classical bit access errors
  - `Qx.GateError` - Gate application failures
  - `Qx.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 `:c64` format
- CZ gate now properly exposed in main `Qx` module 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_rules` dependency 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

---

[Unreleased]: https://github.com/richarc/qx/compare/v0.9.0...HEAD
[0.9.0]: https://github.com/richarc/qx/releases/tag/v0.9.0
[0.8.1]: https://github.com/richarc/qx/releases/tag/v0.8.1
[0.8.0]: https://github.com/richarc/qx/releases/tag/v0.8.0
[0.7.1]: https://github.com/richarc/qx/releases/tag/v0.7.1
[0.7.0]: https://github.com/richarc/qx/releases/tag/v0.7.0
[0.6.0]: https://github.com/richarc/qx/releases/tag/v0.6.0
[0.5.2]: https://github.com/richarc/qx/releases/tag/v0.5.2
[0.5.1]: https://github.com/richarc/qx/releases/tag/v0.5.1
[0.5.0]: https://github.com/richarc/qx/releases/tag/v0.5.0
[0.4.0]: https://github.com/richarc/qx/releases/tag/v0.4.0
[0.3.0]: https://github.com/richarc/qx/releases/tag/v0.3.0
[0.2.5]: https://github.com/richarc/qx/releases/tag/v0.2.5
[0.2.4]: https://github.com/richarc/qx/releases/tag/v0.2.4
[0.2.3]: https://github.com/richarc/qx/releases/tag/v0.2.3
[0.2.2]: https://github.com/richarc/qx/releases/tag/v0.2.2
[0.2.1]: https://github.com/richarc/qx/releases/tag/v0.2.1
[0.2.0]: https://github.com/richarc/qx/releases/tag/v0.2.0
[0.1.0]: https://github.com/richarc/qx/releases/tag/v0.1.0