Qx.Qubit (Qx - Quantum Computing Simulator v0.5.0)

View Source

Simplified API for single-qubit quantum operations.

This module provides a beginner-friendly interface for working with individual qubits. Under the hood, it uses Qx.Register with num_qubits=1, providing a clean abstraction for single-qubit operations without exposing multi-qubit complexity.

Calculation Mode

Calculation mode allows you to work with qubits directly, applying gates in real-time and seeing results immediately. This is different from circuit mode (using Qx.QuantumCircuit) where you build a circuit first and then execute it.

In calculation mode:

  • Gates are applied immediately to the quantum state
  • You can inspect the state at any point using state_vector/1 or measure_probabilities/1
  • Results are available instantly without needing to run a simulation
  • Perfect for learning, exploration, and debugging

Example Workflows

# Basic gate application
q = Qx.Qubit.new()
  |> Qx.Qubit.h()
  |> Qx.Qubit.state_vector()
# Returns superposition state

# Check probabilities at each step
q = Qx.Qubit.new()
Qx.Qubit.measure_probabilities(q)  # [1.0, 0.0] - definitely |0⟩

q = Qx.Qubit.x(q)
Qx.Qubit.measure_probabilities(q)  # [0.0, 1.0] - definitely |1⟩

q = Qx.Qubit.h(q)
Qx.Qubit.measure_probabilities(q)  # [0.5, 0.5] - equal superposition

# Create and manipulate custom states
q = Qx.Qubit.new(0.6, 0.8)  # Custom amplitudes (auto-normalized)
  |> Qx.Qubit.rz(:math.pi() / 4)
  |> Qx.Qubit.rx(:math.pi() / 2)

Available Gates

Single-Qubit Gates (no parameters):

  • h/1 - Hadamard gate (creates superposition)
  • x/1 - Pauli-X gate (bit flip)
  • y/1 - Pauli-Y gate (bit and phase flip)
  • z/1 - Pauli-Z gate (phase flip)
  • s/1 - S gate (π/2 phase)
  • t/1 - T gate (π/4 phase)

Parameterized Gates:

  • rx/2 - Rotation around X-axis
  • ry/2 - Rotation around Y-axis
  • rz/2 - Rotation around Z-axis
  • phase/2 - Arbitrary phase gate

Pipeline Patterns

The Qx.Qubit module supports both transformation pipelines and terminal operations:

Transformation Pipelines (return qubits):

Gates and state manipulations that return qubits for chaining:

Qx.Qubit.new()
|> Qx.Qubit.h()           # Hadamard gate
|> Qx.Qubit.x()           # Pauli-X gate
|> Qx.Qubit.ry(angle)     # Rotation

Debugging Within Pipelines:

Use tap_state/2 to inspect state without breaking the chain:

Qx.Qubit.new()
|> Qx.Qubit.h()
|> Qx.Qubit.tap_state(label: "After H")  # Side effect: prints state
|> Qx.Qubit.x()                          # Returns qubit, pipeline continues

Terminal Operations (return data):

Functions that extract information and end the pipeline:

state_map = qubit |> Qx.Qubit.h() |> Qx.Qubit.show_state()         # Returns map
prob_tensor = qubit |> Qx.Qubit.measure_probabilities()            # Returns Nx.Tensor
bloch = qubit |> Qx.Qubit.draw_bloch()                              # Returns VegaLite struct (default) or SVG string
is_valid = qubit |> Qx.Qubit.valid?()                              # Returns boolean

Choose the right pattern for your use case!

Architecture Note

This module is implemented as a thin wrapper around Qx.Register. All gate operations delegate to the Register module, ensuring consistency and reducing code duplication. This design provides:

  • Single source of truth for gate implementations
  • Better performance (Register is highly optimized)
  • Easier maintenance
  • Seamless transition when scaling to multi-qubit operations

See Also

Summary

Functions

Gets the amplitude for the |0⟩ state.

Gets the amplitude for the |1⟩ state.

Draws the qubit state on a Bloch sphere.

Creates a qubit from angle (simplified Bloch sphere).

Creates a qubit from a computational basis state.

Creates a qubit from Bloch sphere coordinates.

Applies a Hadamard gate to a qubit in calculation mode.

Measures a qubit and returns the probability of measuring |0⟩ and |1⟩.

Creates a qubit in the |-⟩ state.

Creates a qubit in the |0⟩ state.

Creates a qubit with custom amplitudes.

Creates a qubit in the |1⟩ state.

Applies an arbitrary phase gate.

Creates a qubit in the |+⟩ state (equal superposition).

Creates a random qubit state with uniformly distributed amplitudes.

Applies a rotation around the X-axis.

Applies a rotation around the Y-axis.

Applies a rotation around the Z-axis.

Applies an S gate (π/2 phase) to a qubit.

Returns a map containing formatted state information.

Returns the state vector of the qubit.

Applies a T gate (π/4 phase) to a qubit.

Inspects the qubit state and returns the original qubit (pipeline-friendly).

Checks if a given state vector represents a valid qubit.

Applies a Pauli-X gate (bit flip) to a qubit.

Applies a Pauli-Y gate to a qubit.

Applies a Pauli-Z gate (phase flip) to a qubit.

Types

t()

@type t() :: Nx.Tensor.t()

Functions

alpha(qubit)

Gets the amplitude for the |0⟩ state.

Examples

iex> qubit = Qx.Qubit.new(0.6, 0.8)
iex> alpha = Qx.Qubit.alpha(qubit)
iex> abs(Complex.abs(alpha) - 0.6) < 0.01
true

Pipeline Usage

⚠️ This is a terminal operation that returns a Complex number, not a qubit. For pipeline-friendly state inspection, use tap_state/2.

beta(qubit)

Gets the amplitude for the |1⟩ state.

Examples

iex> qubit = Qx.Qubit.new(0.6, 0.8)
iex> beta = Qx.Qubit.beta(qubit)
iex> abs(Complex.abs(beta) - 0.8) < 0.01
true

Pipeline Usage

⚠️ This is a terminal operation that returns a Complex number, not a qubit. For pipeline-friendly state inspection, use tap_state/2.

draw_bloch(qubit, options \\ [])

Draws the qubit state on a Bloch sphere.

This function visualizes the qubit's state on the Bloch sphere, showing the state vector, axes, and key basis states.

Parameters

  • qubit - The qubit state to visualize
  • options - Optional visualization parameters
    • :format - Output format (:svg, :vega_lite) (default: :vega_lite)
    • :title - Title of the plot (default: "Bloch Sphere")
    • :size - Size of the visualization in pixels (default: 400)

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.h()
iex> result = Qx.Qubit.draw_bloch(q)
iex> is_struct(result) or is_binary(result)
true

iex> q = Qx.Qubit.new() |> Qx.Qubit.h()
iex> svg = Qx.Qubit.draw_bloch(q, format: :svg)
iex> is_binary(svg)
true

Pipeline Usage

⚠️ This is a terminal operation that returns an SVG string or VegaLite struct, not a qubit. For pipeline-friendly state inspection, use tap_state/2.

from_angle(theta)

@spec from_angle(number()) :: t()

Creates a qubit from angle (simplified Bloch sphere).

This is a simplified version of from_bloch/2 where phi=0. Useful for creating states along the X-Z plane of the Bloch sphere.

Parameters

  • theta - Polar angle in radians (0 to π)

Examples

# Create superposition |+⟩
iex> q = Qx.Qubit.from_angle(:math.pi() / 2)
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

from_basis(basis)

@spec from_basis(0 | 1) :: t()

Creates a qubit from a computational basis state.

Parameters

  • basis - 0 for |0⟩ or 1 for |1⟩

Examples

iex> q = Qx.Qubit.from_basis(0)
iex> Qx.Qubit.alpha(q) |> Complex.abs()
1.0

iex> q = Qx.Qubit.from_basis(1)
iex> Qx.Qubit.beta(q) |> Complex.abs()
1.0

from_bloch(theta, phi)

@spec from_bloch(number(), number()) :: t()

Creates a qubit from Bloch sphere coordinates.

The Bloch sphere is a geometrical representation of a qubit's quantum state. Any pure qubit state can be written as: |ψ⟩ = cos(θ/2)|0⟩ + e^(iφ)sin(θ/2)|1⟩

Parameters

  • theta - Polar angle in radians (0 to π)
  • phi - Azimuthal angle in radians (0 to 2π)

Examples

# Create |0⟩ state (north pole)
iex> q = Qx.Qubit.from_bloch(0, 0)
iex> Qx.Qubit.alpha(q) |> Complex.abs() |> Float.round(3)
1.0

# Create |1⟩ state (south pole)
iex> q = Qx.Qubit.from_bloch(:math.pi(), 0)
iex> Qx.Qubit.beta(q) |> Complex.abs() |> Float.round(3)
1.0

# Create |+⟩ state (equator, x-axis)
iex> q = Qx.Qubit.from_bloch(:math.pi() / 2, 0)
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

h(qubit)

Applies a Hadamard gate to a qubit in calculation mode.

The Hadamard gate creates superposition by transforming:

  • |0⟩ → (|0⟩ + |1⟩)/√2
  • |1⟩ → (|0⟩ - |1⟩)/√2

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.h()
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

measure_probabilities(qubit)

Measures a qubit and returns the probability of measuring |0⟩ and |1⟩.

Examples

iex> qubit = Qx.Qubit.plus()
iex> probs = Qx.Qubit.measure_probabilities(qubit)
iex> Nx.shape(probs)
{2}

Pipeline Usage

⚠️ This is a terminal operation that returns an Nx.Tensor, not a qubit. For pipeline-friendly state inspection, use tap_state/2.

minus()

Creates a qubit in the |-⟩ state.

The |-⟩ state is (|0⟩ - |1⟩)/√2.

Examples

iex> q = Qx.Qubit.minus()
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

new()

Creates a qubit in the |0⟩ state.

The |0⟩ state is represented as the state vector [1, 0].

Examples

iex> Qx.Qubit.new()
#Nx.Tensor<
  c64[2]
  [1.0+0.0i, 0.0+0.0i]
>

new(alpha, beta)

Creates a qubit with custom amplitudes.

The qubit state is automatically normalized to ensure |α|² + |β|² = 1.

Parameters

  • alpha - Amplitude for |0⟩ state (real number or Complex)
  • beta - Amplitude for |1⟩ state (real number or Complex)

Examples

iex> q = Qx.Qubit.new(0.6, 0.8)
iex> Qx.Qubit.valid?(q)
true

iex> q = Qx.Qubit.new(1, 1)  # Auto-normalized to (|0⟩ + |1⟩)/√2
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

one()

Creates a qubit in the |1⟩ state.

Examples

iex> Qx.Qubit.one()
#Nx.Tensor<
  c64[2]
  [0.0+0.0i, 1.0+0.0i]
>

phase(qubit, phi)

Applies an arbitrary phase gate.

Parameters

  • qubit - The qubit state
  • phi - Phase angle in radians

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.phase(:math.pi() / 4)
iex> Qx.Qubit.valid?(q)
true

plus()

Creates a qubit in the |+⟩ state (equal superposition).

The |+⟩ state is (|0⟩ + |1⟩)/√2.

Examples

iex> q = Qx.Qubit.plus()
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

random()

Creates a random qubit state with uniformly distributed amplitudes.

The state is automatically normalized to ensure it represents a valid qubit.

Examples

iex> random_qubit = Qx.Qubit.random()
iex> Qx.Qubit.valid?(random_qubit)
true

rx(qubit, theta)

Applies a rotation around the X-axis.

Parameters

  • qubit - The qubit state
  • theta - Rotation angle in radians

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.rx(:math.pi())
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 1) - 1.0) < 0.01
true

ry(qubit, theta)

Applies a rotation around the Y-axis.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.ry(:math.pi() / 2)
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> abs(Enum.at(probs, 0) - 0.5) < 0.01
true

rz(qubit, theta)

Applies a rotation around the Z-axis.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.h() |> Qx.Qubit.rz(:math.pi() / 4)
iex> Qx.Qubit.valid?(q)
true

s(qubit)

Applies an S gate (π/2 phase) to a qubit.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.s()
iex> Qx.Qubit.valid?(q)
true

show_state(qubit)

Returns a map containing formatted state information.

This function returns a map with human-readable representations of the qubit state, including Dirac notation, complex amplitudes, and measurement probabilities.

Returns

A map with keys:

  • :state - Dirac notation string (e.g., "0.707|0⟩ + 0.707|1⟩")
  • :amplitudes - List of tuples with basis states and complex amplitudes
  • :probabilities - List of tuples with basis states and measurement probabilities

Pipeline Usage

⚠️ Note: This function returns display data (a map) and ends the pipeline.

To inspect state while continuing a pipeline, use tap_state/2:

qubit
|> h()
|> tap_state(label: "After H gate")  # Shows state, returns qubit
|> x()                                # Pipeline continues
|> tap_state(label: "After X gate")

To get the display map at the end of a pipeline:

state_info =
  qubit
  |> h()
  |> x()
  |> show_state()  # Terminal operation, returns the map

IO.puts(state_info.state)

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.h()
iex> info = Qx.Qubit.show_state(q)
iex> is_map(info)
true

iex> q = Qx.Qubit.plus()
iex> info = Qx.Qubit.show_state(q)
iex> info.state =~ "|0⟩"
true

See also: tap_state/2 for pipeline-friendly state inspection.

state_vector(qubit)

Returns the state vector of the qubit.

Examples

iex> q = Qx.Qubit.new()
iex> Qx.Qubit.state_vector(q)
#Nx.Tensor<
  c64[2]
  [1.0+0.0i, 0.0+0.0i]
>

t(qubit)

Applies a T gate (π/4 phase) to a qubit.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.t()
iex> Qx.Qubit.valid?(q)
true

tap_state(qubit, opts \\ [])

Inspects the qubit state and returns the original qubit (pipeline-friendly).

This function is designed for debugging within pipelines. It displays the state as a side effect but returns the qubit unchanged, allowing the pipeline to continue.

Options

  • :label - Custom label for the output (default: "Qubit State")
  • :verbose - Show detailed probabilities (default: false)

vs. show_state/1

Examples

Basic debugging in a pipeline:

result =
  Qx.Qubit.new()
  |> Qx.Qubit.h()
  |> Qx.Qubit.tap_state(label: "After Hadamard")
  |> Qx.Qubit.x()
  |> Qx.Qubit.tap_state(label: "After X gate")
  |> Qx.Qubit.y()

Verbose mode to see detailed probabilities:

qubit
|> Qx.Qubit.rx(:math.pi() / 4)
|> Qx.Qubit.tap_state(label: "After rotation", verbose: true)

Multiple inspection points throughout a pipeline:

qubit
|> Qx.Qubit.tap_state(label: "Initial state")
|> Qx.Qubit.h()
|> Qx.Qubit.tap_state(label: "Superposition")
|> Qx.Qubit.rz(:math.pi() / 2)
|> Qx.Qubit.tap_state(label: "After phase rotation")

Doctest example:

iex> q = Qx.Qubit.new()
...>   |> Qx.Qubit.h()
...>   |> Qx.Qubit.tap_state(label: "After Hadamard")
...>   |> Qx.Qubit.x()
iex> Qx.Qubit.valid?(q)
true

See also: show_state/1 for getting the state as a map (terminal operation).

valid?(state)

@spec valid?(Nx.Tensor.t()) :: boolean()

Checks if a given state vector represents a valid qubit.

A valid qubit must:

  1. Have exactly 2 complex components (shape {2})
  2. Be normalized (|α|² + |β|² = 1)

Examples

iex> valid_qubit = Qx.Qubit.new(0.6, 0.8)
iex> Qx.Qubit.valid?(valid_qubit)
true

iex> invalid_qubit = Nx.tensor([Complex.new(1.0, 0.0), Complex.new(1.0, 0.0)], type: :c64)
iex> Qx.Qubit.valid?(invalid_qubit)
false

Pipeline Usage

⚠️ This is a terminal operation that returns a boolean, not a qubit. For pipeline-friendly state inspection, use tap_state/2.

x(qubit)

Applies a Pauli-X gate (bit flip) to a qubit.

Transforms |0⟩ ↔ |1⟩.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.x()
iex> probs = Qx.Qubit.measure_probabilities(q) |> Nx.to_flat_list()
iex> Enum.at(probs, 1)
1.0

y(qubit)

Applies a Pauli-Y gate to a qubit.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.y()
iex> Qx.Qubit.valid?(q)
true

z(qubit)

Applies a Pauli-Z gate (phase flip) to a qubit.

Examples

iex> q = Qx.Qubit.new() |> Qx.Qubit.z()
iex> Qx.Qubit.valid?(q)
true