Skip to main content

Overview

The JointHilbertState class represents the quantum state of n qubits in the full joint Hilbert space (dimension 2^n). Unlike “statevector” approaches that store n separate qubit states, this representation stores the complete set of 2^n complex amplitudes, enabling true entanglement. Key Features:
  • Complete 2^n amplitude representation
  • Each amplitude is a spatial wavefunction (2D grid)
  • Supports superposition, entanglement, and multi-qubit coherence
  • Non-destructive Born-rule measurements
  • Marginal probabilities and Bloch vector calculations
Location: quantum_computer.py:277-386

State Tensor Structure

The state is stored as a tensor with shape (2^n, 2, G, G):
  • Dimension 0 (size 2^n): Computational basis index k ∈ {0, …, 2^n - 1}
    • Bit j of k represents the state of qubit j (MSB = qubit 0)
    • Example: For n=3, k=5 (binary 101) represents |101⟩
  • Dimension 1 (size 2): Real and imaginary channels
    • Channel 0: real part of amplitude
    • Channel 1: imaginary part of amplitude
  • Dimensions 2, 3 (size G × G): Spatial wavefunction grid
    • G = grid_size (typically 16 or 32)
    • Each amplitude is a 2D spatial field

Born Probability

The probability of measuring basis state |k⟩ is:
P(k) = ∫∫ |α_k(x,y)|² dx dy
     = Σ_{x,y} (α_k_real² + α_k_imag²)
normalized so that Σ_k P(k) = 1.

Constructor

JointHilbertState(amplitudes: torch.Tensor, n_qubits: int)
Create a joint Hilbert state from amplitude tensor.
amplitudes
torch.Tensor
required
Amplitude tensor of shape (2^n_qubits, 2, G, G). The tensor must be on the correct device (CPU/CUDA) and have the expected dimensions.
n_qubits
int
required
Number of qubits. Must satisfy amplitudes.shape[0] == 2^n_qubits.

Example

import torch
from quantum_computer import JointHilbertState

n_qubits = 2
G = 16

# Create amplitude tensor (2^2=4 basis states, 2 channels, 16×16 grid)
amplitudes = torch.zeros(4, 2, G, G)
amplitudes[0, 0] = 1.0  # |00⟩ with amplitude 1 (real part)

state = JointHilbertState(amplitudes, n_qubits)
print(f"State dimension: {state.dim}")  # 4
print(f"Grid size: {state.G}")          # 16
Direct construction is rare. Use JointStateFactory to create initial states or let QuantumComputer.run() manage state evolution.

Attributes

amplitudes

amplitudes: torch.Tensor
The amplitude tensor of shape (2^n, 2, G, G).

n_qubits

n_qubits: int
Number of qubits in the register.

dim

dim: int
Hilbert space dimension (2^n_qubits).

device

device: torch.device
Device where amplitudes are stored (CPU or CUDA).

G

G: int
Spatial grid size (amplitudes.shape[-1]).

Methods

normalize_

normalize_() -> None
In-place normalization ensuring Σ_k P(k) = 1.
state.normalize_()

probabilities

probabilities() -> torch.Tensor
Return Born probabilities P(k) for each basis state.
probs
torch.Tensor
Tensor of shape (2^n,) containing probabilities for basis states k=0, 1, …, 2^n - 1.

Example

probs = state.probabilities()
print(probs)  # tensor([0.5, 0.0, 0.0, 0.5])  # Bell state |00⟩ + |11⟩

marginal_probability_one

marginal_probability_one(qubit: int) -> float
Compute marginal Born probability P(qubit_j = |1⟩). Sums P(k) over all basis states k where bit j equals 1.
qubit
int
required
Qubit index (0-indexed)
p1
float
Probability that qubit j measures |1⟩, clamped to [0, 1]

Example

# Bell state: equal superposition of |00⟩ and |11⟩
p1_q0 = state.marginal_probability_one(0)  # 0.5
p1_q1 = state.marginal_probability_one(1)  # 0.5

most_probable_basis_state

most_probable_basis_state() -> int
Return the basis state index k with the highest probability.
k
int
Basis state index (0 to 2^n - 1) with maximum P(k)

Example

k = state.most_probable_basis_state()
bitstring = format(k, f"0{state.n_qubits}b")
print(f"Most probable: |{bitstring}⟩")  # |00⟩

bloch_vector

bloch_vector(qubit: int) -> Tuple[float, float, float]
Compute the reduced Bloch vector (bx, by, bz) for qubit j via partial trace. The reduced density matrix for qubit j is:
ρ_j[0,0] = P(qubit=0)
ρ_j[1,1] = P(qubit=1)
ρ_j[0,1] = Σ_{pairs} α_{k0}* α_{k1}  (off-diagonal coherence)
Bloch vector components:
bx = 2 Re(ρ_j[0,1])
by = -2 Im(ρ_j[0,1])
bz = P(0) - P(1)
Normalized to unit length if |b| > 1.
qubit
int
required
Qubit index (0-indexed)
bloch
Tuple[float, float, float]
Bloch vector (bx, by, bz) with |b| ≤ 1

Example

bx, by, bz = state.bloch_vector(0)
print(f"Bloch vector: ({bx:+.3f}, {by:+.3f}, {bz:+.3f})")
# Pure |0⟩: (0.000, 0.000, +1.000)
# Pure |1⟩: (0.000, 0.000, -1.000)
# Equal superposition: (varies, varies, 0.000)

clone

clone() -> JointHilbertState
Return a deep copy of the state.
copy
JointHilbertState
New JointHilbertState with independent amplitude tensor
state_copy = state.clone()
# Modifications to state_copy don't affect state

Factory: JointStateFactory

The JointStateFactory class provides convenient methods to create initial states.

Constructor

JointStateFactory(config: SimulatorConfig)
config
SimulatorConfig
required
Simulator configuration (grid size, device, etc.)

all_zeros

all_zeros(n_qubits: int) -> JointHilbertState
Initialize register in |00…0⟩.
n_qubits
int
required
Number of qubits
state
JointHilbertState
Normalized state with P(|0…0⟩) = 1

Example

from quantum_computer import SimulatorConfig, JointStateFactory

config = SimulatorConfig(grid_size=16, device="cuda")
factory = JointStateFactory(config)

state = factory.all_zeros(3)
print(state.probabilities())  # tensor([1., 0., 0., 0., 0., 0., 0., 0.])

basis_state

basis_state(n_qubits: int, k: int) -> JointHilbertState
Initialize register in computational basis state |k⟩.
n_qubits
int
required
Number of qubits
k
int
required
Basis state index (0 to 2^n_qubits - 1)
state
JointHilbertState
Normalized state with P(|k⟩) = 1

Example

# Create |101⟩ (k=5 for 3 qubits)
state = factory.basis_state(3, 5)
print(state.probabilities())  # tensor([0., 0., 0., 0., 0., 1., 0., 0.])

from_bitstring

from_bitstring(bitstring: str) -> JointHilbertState
Initialize in basis state given by binary string.
bitstring
str
required
Binary string (e.g., “101”)
state
JointHilbertState
Normalized state corresponding to bitstring

Example

state = factory.from_bitstring("101")
print(state.n_qubits)  # 3
print(state.probabilities()[5])  # 1.0 (k=5 for "101")

Complete Examples

Accessing Amplitude Data

from quantum_computer import QuantumComputer, QuantumCircuit

qc = QuantumComputer()

# Build Bell state circuit
circuit = QuantumCircuit(2)
circuit.h(0).cnot(0, 1)

# Get state via run_with_state_snapshots
final, _ = qc.run_with_state_snapshots(circuit, snapshot_after=[])

# Access underlying state (not exposed in public API)
# In practice, use MeasurementResult methods instead

Measuring Entanglement

import math

def is_entangled(state: JointHilbertState, tol: float = 0.01) -> bool:
    """Check if state is entangled by measuring correlation."""
    probs = state.probabilities().cpu().numpy()
    n = state.n_qubits
    
    # For Bell state: P(|00⟩) and P(|11⟩) should be ~0.5 each
    # For product state: probabilities factorize
    
    # Simple heuristic: max probability < 0.9 indicates superposition
    max_prob = probs.max()
    return max_prob < (1.0 - tol)

# Example
qc = QuantumComputer()

# Product state |01⟩
c1 = QuantumCircuit(2)
c1.x(1)
r1 = qc.run(c1)
print(is_entangled(r1))  # False (not accessible via public API)

# Bell state
c2 = QuantumCircuit(2)
c2.h(0).cnot(0, 1)
r2 = qc.run(c2)
print(is_entangled(r2))  # True

Bloch Sphere Visualization

def print_bloch_vectors(state: JointHilbertState) -> None:
    """Print Bloch vectors for all qubits."""
    print(f"Bloch vectors for {state.n_qubits}-qubit state:")
    for i in range(state.n_qubits):
        bx, by, bz = state.bloch_vector(i)
        magnitude = math.sqrt(bx**2 + by**2 + bz**2)
        print(f"  q{i}: ({bx:+.3f}, {by:+.3f}, {bz:+.3f})  |b|={magnitude:.3f}")

# Example: GHZ state
qc = QuantumComputer()
circuit = QuantumCircuit(3)
circuit.h(0).cnot(0, 1).cnot(1, 2)

result = qc.run(circuit)
# Use result.bloch_vectors instead of direct state access
for i in range(3):
    bx, by, bz = result.bloch_vectors[i]
    print(f"q{i}: Bloch=({bx:+.3f}, {by:+.3f}, {bz:+.3f})")

Marginal Probability Distribution

def marginal_distribution(state: JointHilbertState, qubit: int) -> dict:
    """Compute marginal distribution for a single qubit."""
    p0 = 1.0 - state.marginal_probability_one(qubit)
    p1 = state.marginal_probability_one(qubit)
    return {"0": p0, "1": p1}

# Example
qc = QuantumComputer()
circuit = QuantumCircuit(2)
circuit.h(0)  # Only qubit 0 in superposition

result = qc.run(circuit)
# Access via MeasurementResult API
print(f"q0 marginal: P(0)={1 - result.marginal_p1[0]:.3f}, P(1)={result.marginal_p1[0]:.3f}")
print(f"q1 marginal: P(0)={1 - result.marginal_p1[1]:.3f}, P(1)={result.marginal_p1[1]:.3f}")
# Expected: q0 is 50/50, q1 is 100% |0⟩

Understanding Entanglement

The joint Hilbert space representation is the only correct way to represent entangled states:

Product State (Not Entangled)

# |0⟩ ⊗ |1⟩ = |01⟩
# Only amplitude[1] is non-zero (k=1 = binary 01)
state = factory.from_bitstring("01")
print(state.probabilities())  # [0, 1, 0, 0]

Bell State (Maximally Entangled)

# (|00⟩ + |11⟩) / √2
# Amplitudes[0] and amplitudes[3] are non-zero
# CANNOT be written as q0 ⊗ q1
circuit = QuantumCircuit(2)
circuit.h(0).cnot(0, 1)
result = qc.run(circuit)
print(result.full_distribution)  # {"00": ~0.5, "11": ~0.5}

GHZ State (Multi-qubit Entanglement)

# (|000⟩ + |111⟩) / √2
# Only amplitudes[0] and amplitudes[7] are non-zero
circuit = QuantumCircuit(3)
circuit.h(0).cnot(0, 1).cnot(1, 2)
result = qc.run(circuit)
print(result.full_distribution)  # {"000": ~0.5, "111": ~0.5}

Bit Ordering Convention

Important: Qubit 0 is the most significant bit (MSB) of the basis index k. For a 3-qubit state:
  • k = 0 (binary 000) = |q0=0, q1=0, q2=0⟩
  • k = 1 (binary 001) = |q0=0, q1=0, q2=1⟩
  • k = 5 (binary 101) = |q0=1, q1=0, q2=1⟩
  • k = 7 (binary 111) = |q0=1, q1=1, q2=1⟩
def basis_to_qubits(k: int, n_qubits: int) -> str:
    """Convert basis index k to qubit string."""
    return format(k, f"0{n_qubits}b")

print(basis_to_qubits(5, 3))  # "101" = |q0=1, q1=0, q2=1⟩

See Also

Build docs developers (and LLMs) love