Qx.Simulation (Qx - Quantum Computing Simulator v0.10.0)
View SourceSimulation 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
@type simulation_result() :: Qx.SimulationResult.t()
Functions
@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 circuitoptions- 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
Qx.MeasurementError- If circuit contains measurements or conditionals
@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 executeoptions- 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
Qx.MeasurementError- If circuit contains measurements or conditionals
@spec run( Qx.QuantumCircuit.t(), keyword() ) :: simulation_result()
Executes a quantum circuit and returns the simulation results.
Parameters
circuit- The quantum circuit to executeoptions- 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 everyNgates and at measurement-time Any other value raisesQx.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
@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 asrun/2(default: current Nx default):renormalize- same contract asrun/2; the every-ncadence counts gates insidec_ifblocks exactly likerun/2does (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.