Qx.Simulation (Qx - Quantum Computing Simulator v0.10.0)

View Source

Simulation engine for executing quantum circuits with full complex number support.

This module runs circuit instructions and applies quantum gates to evolve the quantum state, then provides measurement probabilities and classical bit results. All quantum states and operations properly support complex number arithmetic.

Summary

Functions

Gets the probability distribution for all computational basis states.

Executes a quantum circuit without measurements, returning only the final state.

Executes a quantum circuit and returns the simulation results.

Returns a lazy stream of %Qx.Step{} structs, one per executed operation.

Types

simulation_result()

@type simulation_result() :: Qx.SimulationResult.t()

Functions

get_probabilities(circuit, options \\ [])

@spec get_probabilities(
  Qx.QuantumCircuit.t(),
  keyword()
) :: Nx.Tensor.t()

Gets the probability distribution for all computational basis states.

Note: This function only works for circuits without measurements or conditionals. For circuits with measurements, use run/2 and access the probabilities from the result.

Parameters

  • circuit - The quantum circuit
  • options - Optional keyword list (default: [])

Options

  • :backend - Nx backend to use for computation (default: current Nx default)

Examples

iex> qc = Qx.QuantumCircuit.new(1, 0) |> Qx.Operations.h(0)
iex> probs = Qx.Simulation.get_probabilities(qc)
iex> Nx.shape(probs)
{2}

Raises

get_state(circuit, options \\ [])

@spec get_state(
  Qx.QuantumCircuit.t(),
  keyword()
) :: Nx.Tensor.t()

Executes a quantum circuit without measurements, returning only the final state.

Note: This function only works for circuits without measurements or conditionals. For circuits with measurements, use run/2 instead to get the simulation result.

Parameters

  • circuit - The quantum circuit to execute
  • options - Optional keyword list (default: [])

Options

  • :backend - Nx backend to use for computation (default: current Nx default)

Examples

iex> qc = Qx.QuantumCircuit.new(1, 0) |> Qx.Operations.h(0)
iex> state = Qx.Simulation.get_state(qc)
iex> Nx.shape(state)
{2}

Raises

run(circuit, options \\ [])

Executes a quantum circuit and returns the simulation results.

Parameters

  • circuit - The quantum circuit to execute
  • options - Optional keyword list (default: [])

Options

  • :shots - Number of measurement shots (default: 1024)

  • :backend - Nx backend to use for computation (default: current Nx default)

  • :renormalize - Counter unitary float drift by renormalizing the statevector (default: false). Accepts:

    • false — no renormalization (current behaviour, zero cost)
    • true — renormalize once at measurement-time (before probabilities are computed)
    • positive integer N — renormalize every N gates and at measurement-time Any other value raises Qx.OptionError.

    Note: states are :c64 (complex float32, ε≈1.2e-7). Even immediately after renormalization the total probability deviates from 1.0 by ~1e-7, so renormalization bounds drift but does not eliminate it; do not expect sub-1e-7 accuracy in this backend.

Backend Selection

You can specify which Nx backend to use for this simulation:

# Use EXLA for GPU/CPU acceleration. EXLA is not a Qx dependency; add it
# to your own project first (see the README's "Performance &
# Acceleration" section), then pass its backend here.
Qx.Simulation.run(circuit, backend: EXLA.Backend)

# Force binary backend (no acceleration)
Qx.Simulation.run(circuit, backend: Nx.BinaryBackend)

# Use default backend
Qx.Simulation.run(circuit)

Examples

iex> qc = Qx.QuantumCircuit.new(2, 2)
iex> qc = qc |> Qx.Operations.h(0) |> Qx.Operations.cx(0, 1)
iex> result = Qx.Simulation.run(qc)
iex> is_map(result)
true

# With custom shots
iex> result = Qx.Simulation.run(qc, shots: 2048)
iex> result.shots
2048

# Renormalize every 8 gates to counter long-circuit float drift
iex> result = Qx.Simulation.run(qc, renormalize: 8)
iex> is_map(result)
true

steps(circuit, options \\ [])

@spec steps(
  Qx.QuantumCircuit.t(),
  keyword()
) :: Enumerable.t()

Returns a lazy stream of %Qx.Step{} structs, one per executed operation.

Unlike get_state/2, this works on circuits with mid-circuit measurement and c_if: measurements collapse the state and record their outcome in classical_bits, and each gate inside a taken c_if block yields its own step. A block that doesn't run yields a single step flagged :not_taken.

A circuit with measurements is stochastic, so each materialisation of the stream samples one fresh trajectory. Pass seed: to reproduce a trajectory. Seeding never touches the caller's process :rand state.

Options

  • :seed - integer seed for the trajectory's random measurement outcomes (default: fresh entropy per materialisation)
  • :backend - Nx backend to use for computation, same pass-through as run/2 (default: current Nx default)
  • :renormalize - same contract as run/2; the every-n cadence counts gates inside c_if blocks exactly like run/2 does (default: false)

Examples

iex> qc = Qx.create_circuit(2) |> Qx.h(0) |> Qx.cx(0, 1)
iex> steps = Qx.Simulation.steps(qc) |> Enum.to_list()
iex> length(steps)
2
iex> Enum.map(steps, & &1.operation)
[{:h, [0], []}, {:cx, [0, 1], []}]

Raises

Materialising the stream raises the same typed errors run/2 raises: Qx.GateError on an unsupported instruction, Qx.OptionError on a bad :renormalize value.