# Import libraries
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import Aer
from qiskit.visualization import plot_histogram, circuit_drawer
from qiskit.quantum_info import Statevector, partial_trace, entropy
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
print("✅ Qiskit imported successfully!")Why This Post Exists
This is the prerequisite for understanding Stage Zero: Programming Quantum Reality Through Relations, Not Gates.
If you’re coming from classical programming and want to understand quantum computing—not just run circuits, but actually understand what’s happening—this guide is for you.
We’ll cover the physics that makes quantum computing possible:
- Quantum States - What |0⟩, |1⟩, and superposition actually mean
- Measurement & Basis - Why “measuring” depends on what question you ask
- Entanglement - The fundamental resource of quantum computing
- Correlations - Why entangled systems show different correlations in different bases
- Entanglement Entropy - How to quantify quantum correlation
- Basis Transformations - The mathematics behind multi-basis measurements
By the end, you’ll understand: - Why quantum computing is different from probabilistic classical computing - Why entanglement is a relation between systems, not a property of systems - Why measurement basis matters (the foundation of QPL’s QuantumQuestion abstraction) - The physics that QPL Stage Zero implements
Let’s start from first principles.
Prerequisites
pip install qiskit qiskit-aer matplotlib numpyPart 1: Quantum States - The Foundation
Classical Bits vs Qubits
A classical bit is either 0 or 1. Period.
A qubit is a quantum system that, when measured, gives 0 or 1—but before measurement, it exists in a superposition of both states.
The Mathematical Representation
We write quantum states using Dirac notation (bra-ket notation):
\[|0\rangle = \begin{pmatrix} 1 \\ 0 \end{pmatrix}, \quad |1\rangle = \begin{pmatrix} 0 \\ 1 \end{pmatrix}\]
These are basis states (like unit vectors in 2D space).
A general qubit state is:
\[|\psi\rangle = \alpha|0\rangle + \beta|1\rangle = \begin{pmatrix} \alpha \\ \beta \end{pmatrix}\]
where \(\alpha, \beta\) are complex numbers satisfying \(|\alpha|^2 + |\beta|^2 = 1\) (normalization).
Why Complex Numbers?
Classical probability uses real numbers: P(heads) = 0.5, P(tails) = 0.5.
Quantum mechanics uses probability amplitudes (complex numbers). The probability of measuring a state is:
\[P(0) = |\alpha|^2, \quad P(1) = |\beta|^2\]
The complex phase matters for interference—the key to quantum algorithms.
Let’s see this in code:
# Create a qubit in state |0⟩
state_0 = Statevector([1, 0])
print("State |0⟩:")
print(state_0)
print(f"Probabilities: P(0) = {abs(state_0[0])**2}, P(1) = {abs(state_0[1])**2}")
print("\n" + "="*50 + "\n")
# Create a qubit in state |1⟩
state_1 = Statevector([0, 1])
print("State |1⟩:")
print(state_1)
print(f"Probabilities: P(0) = {abs(state_1[0])**2}, P(1) = {abs(state_1[1])**2}")
print("\n" + "="*50 + "\n")
# Create a superposition: (|0⟩ + |1⟩)/√2
state_plus = Statevector([1/np.sqrt(2), 1/np.sqrt(2)])
print("State |+⟩ = (|0⟩ + |1⟩)/√2:")
print(state_plus)
print(f"Probabilities: P(0) = {abs(state_plus[0])**2:.3f}, P(1) = {abs(state_plus[1])**2:.3f}")Key insight: The state \(|+\rangle\) is NOT “50% chance of being 0, 50% chance of being 1.” It’s both simultaneously until measured. The probabilities only emerge upon measurement.
Part 2: Superposition - Both At Once
The Hadamard Gate: Creating Superposition
The Hadamard gate (H) transforms basis states into superposition:
\[H|0\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}} = |+\rangle\] \[H|1\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}} = |-\rangle\]
Mathematically, H is a matrix:
\[H = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}\]
Applying H to \(|0\rangle\):
\[H|0\rangle = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \end{pmatrix} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 \\ 1 \end{pmatrix} = |+\rangle\]
# Create a circuit with Hadamard gate
qc = QuantumCircuit(1, 1)
qc.h(0) # Apply Hadamard to qubit 0
qc.measure(0, 0)
# Draw the circuit
print("Circuit:")
display(qc.draw('mpl'))
# Simulate
simulator = Aer.get_backend('qasm_simulator')
job = simulator.run(qc, shots=1000)
counts = job.result().get_counts()
print("\nMeasurement results after H gate:")
print(counts)
plot_histogram(counts)Result: Approximately 50% |0⟩ and 50% |1⟩.
But here’s the crucial difference from classical randomness:
- Classical coin flip: The coin is either heads or tails before you look. You just don’t know which.
- Quantum superposition: The qubit is actually both |0⟩ and |1⟩ until measured. This is verified by interference experiments.
Part 3: Measurement - Asking Questions, Not Reading Answers
Measurement Collapses the State
When you measure a qubit in superposition:
- Before measurement: \(|\psi\rangle = \alpha|0\rangle + \beta|1\rangle\) (superposition)
- Measurement: You get outcome 0 with probability \(|\alpha|^2\), or outcome 1 with probability \(|\beta|^2\)
- After measurement: The state collapses to \(|0\rangle\) or \(|1\rangle\) (superposition destroyed)
This is irreversible. You can’t “un-measure” a qubit.
The Z Basis (Computational Basis)
When we say “measure a qubit,” we usually mean measure in the Z basis (also called computational basis):
- Eigenstates: \(|0\rangle\) (eigenvalue +1) and \(|1\rangle\) (eigenvalue -1)
- Measurement operator: \(Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}\)
But this is just one choice of basis. We could measure in other bases…
Part 4: Measurement Basis - Context Matters
The X Basis (Hadamard Basis)
We can also measure in the X basis:
- Eigenstates: \(|+\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}\) and \(|-\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}}\)
- Measurement operator: \(X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}\)
This is a completely different question to ask the qubit!
Same State, Different Measurements
Consider the state \(|0\rangle\):
Measured in Z basis: - \(|0\rangle\) is an eigenstate of Z - Result: 100% chance of outcome 0
Measured in X basis: - \(|0\rangle = \frac{|+\rangle + |-\rangle}{\sqrt{2}}\) (superposition in X basis!) - Result: 50% chance of + outcome, 50% chance of - outcome
The measurement outcome depends on which basis you choose. This is why QPL has QuantumQuestion with explicit basis—the question you ask determines the answer you get.
# Measure |0⟩ in Z basis (default)
qc_z = QuantumCircuit(1, 1)
# Qubit starts in |0⟩
qc_z.measure(0, 0)
print("Measuring |0⟩ in Z basis:")
job_z = simulator.run(qc_z, shots=1000)
counts_z = job_z.result().get_counts()
print(counts_z, "← 100% outcome 0")
print("\n" + "="*50 + "\n")
# Measure |0⟩ in X basis (apply H before measurement to rotate basis)
qc_x = QuantumCircuit(1, 1)
# Qubit starts in |0⟩
qc_x.h(0) # Rotate from Z basis to X basis
qc_x.measure(0, 0)
print("Measuring |0⟩ in X basis:")
job_x = simulator.run(qc_x, shots=1000)
counts_x = job_x.result().get_counts()
print(counts_x, "← ~50% outcome 0, ~50% outcome 1")
# Visualize both
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
plot_histogram(counts_z, ax=ax1, title="Z Basis Measurement of |0⟩")
plot_histogram(counts_x, ax=ax2, title="X Basis Measurement of |0⟩")
plt.tight_layout()
plt.show()Key insight: The state doesn’t change, but the measurement basis does. This is contextuality—a fundamental feature of quantum mechanics that QPL makes explicit.
Part 5: Entanglement - Relations Between Systems
What is Entanglement?
Entanglement is when the quantum state of a multi-particle system cannot be factored into independent single-particle states.
Separable (not entangled): \[|\psi\rangle = |0\rangle_A \otimes |0\rangle_B = |00\rangle\] Alice has \(|0\rangle\), Bob has \(|0\rangle\). These are independent.
Entangled (Bell state): \[|\Phi^+\rangle = \frac{|00\rangle + |11\rangle}{\sqrt{2}}\] You cannot write this as \(|\psi\rangle_A \otimes |\phi\rangle_B\). Alice and Bob’s states are fundamentally correlated.
Creating a Bell State
The standard recipe: 1. Start with \(|00\rangle\) 2. Apply Hadamard to first qubit: \(\frac{|0\rangle + |1\rangle}{\sqrt{2}} \otimes |0\rangle = \frac{|00\rangle + |10\rangle}{\sqrt{2}}\) 3. Apply CNOT (controlled-NOT): \(\frac{|00\rangle + |11\rangle}{\sqrt{2}} = |\Phi^+\rangle\)
# Create Bell state circuit
qc_bell = QuantumCircuit(2, 2)
qc_bell.h(0) # Hadamard on qubit 0
qc_bell.cx(0, 1) # CNOT from qubit 0 to 1
qc_bell.measure([0, 1], [0, 1])
print("Bell State Circuit:")
display(qc_bell.draw('mpl'))
# Simulate
job_bell = simulator.run(qc_bell, shots=1000)
counts_bell = job_bell.result().get_counts()
print("\nMeasurement results:")
print(counts_bell)
plot_histogram(counts_bell)Result: ~50% |00⟩ and ~50% |11⟩. Never |01⟩ or |10⟩.
Why This is Weird
Before measurement: - Alice’s qubit is not “definitely 0” or “definitely 1” - Bob’s qubit is not “definitely 0” or “definitely 1” - But they are perfectly correlated
When Alice measures her qubit: - If she gets 0, Bob will definitely get 0 - If she gets 1, Bob will definitely get 1
This correlation exists regardless of how far apart they are. This is quantum entanglement.
In QPL, this is why entanglement is a QuantumRelation—it’s fundamentally about the relationship between systems, not properties of individual systems.
Part 6: Measuring Entangled Systems - When Basis Matters
Same Basis: Perfect Correlation
If Alice and Bob both measure in Z basis on a Bell state: - 100% correlation (always get same result)
We just saw this above.
# Create Bell state and measure both in Z basis
qc_zz = QuantumCircuit(2, 2)
qc_zz.h(0)
qc_zz.cx(0, 1)
# Measure both in Z basis (default)
qc_zz.measure([0, 1], [0, 1])
job_zz = simulator.run(qc_zz, shots=1000)
counts_zz = job_zz.result().get_counts()
print("Bell state, both measured in Z basis:")
print(counts_zz)
# Compute correlation
same_results = counts_zz.get('00', 0) + counts_zz.get('11', 0)
correlation_zz = same_results / 1000
print(f"\nCorrelation (same outcome rate): {correlation_zz*100:.1f}%")Different Basis: Random Correlation
If Alice measures in Z basis and Bob measures in X basis: - ~50% correlation (random, uncorrelated)
This is critical for understanding QPL Stage Zero! The bug that was fixed was exactly this: cross-basis measurements were showing 100% correlation when they should show 50%.
# Create Bell state, Alice measures Z, Bob measures X
qc_zx = QuantumCircuit(2, 2)
qc_zx.h(0)
qc_zx.cx(0, 1)
# Alice measures in Z basis (default)
qc_zx.measure(0, 0)
# Bob measures in X basis (apply H first to rotate basis)
qc_zx.h(1)
qc_zx.measure(1, 1)
job_zx = simulator.run(qc_zx, shots=1000)
counts_zx = job_zx.result().get_counts()
print("Bell state, Alice: Z basis, Bob: X basis:")
print(counts_zx)
# Compute correlation
same_results = counts_zx.get('00', 0) + counts_zx.get('11', 0)
correlation_zx = same_results / 1000
print(f"\nCorrelation (same outcome rate): {correlation_zx*100:.1f}%")
# Compare both
print("\n" + "="*50)
print(f"Z-Z correlation: {correlation_zz*100:.1f}% (perfect correlation)")
print(f"Z-X correlation: {correlation_zx*100:.1f}% (random, uncorrelated)")
print("="*50)This is the physics that QPL Stage Zero implements correctly.
The measurement basis changes the physics. This is why QuantumQuestion in QPL has an explicit basis parameter—because quantum mechanics is contextual.
Part 7: Entanglement Entropy - Quantifying Correlation
How Do We Measure “How Entangled” Two Systems Are?
For a pure bipartite state (2 subsystems), we use entanglement entropy (also called von Neumann entropy).
The Math: Schmidt Decomposition
Any 2-qubit pure state can be written as:
\[|\psi\rangle = \sum_i \lambda_i |i\rangle_A \otimes |i\rangle_B\]
where \(\lambda_i \geq 0\) are the Schmidt coefficients (singular values).
The entanglement entropy is:
\[S = -\sum_i \lambda_i^2 \log_2(\lambda_i^2)\]
Interpretation: - \(S = 0\): No entanglement (separable state) - \(S = 1\): Maximal entanglement (Bell state) - \(0 < S < 1\): Partial entanglement
Examples
Separable state: \(|00\rangle\) - Schmidt decomposition: \(\lambda_1 = 1, \lambda_2 = 0\) - \(S = -(1^2 \log_2(1^2) + 0) = 0\)
Bell state: \(\frac{|00\rangle + |11\rangle}{\sqrt{2}}\) - Schmidt decomposition: \(\lambda_1 = \lambda_2 = \frac{1}{\sqrt{2}}\) - \(S = -(\frac{1}{2}\log_2(\frac{1}{2}) + \frac{1}{2}\log_2(\frac{1}{2})) = 1\)
Let’s compute this in Qiskit:
# Separable state: |00⟩
separable_state = Statevector([1, 0, 0, 0]) # |00⟩
rho_A_sep = partial_trace(separable_state, [1]) # Trace out qubit 1
S_sep = entropy(rho_A_sep, base=2)
print("Separable state |00⟩:")
print(f"Entanglement entropy: {S_sep:.6f}")
print("\n" + "="*50 + "\n")
# Bell state: (|00⟩ + |11⟩)/√2
bell_state = Statevector([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)]) # |Φ+⟩
rho_A_bell = partial_trace(bell_state, [1]) # Trace out qubit 1
S_bell = entropy(rho_A_bell, base=2)
print("Bell state (|00⟩ + |11⟩)/√2:")
print(f"Entanglement entropy: {S_bell:.6f}")
print("\n" + "="*50 + "\n")
# Partially entangled state: 0.9|00⟩ + 0.436|11⟩
partial_state = Statevector([0.9, 0, 0, 0.436])
partial_state = partial_state / np.linalg.norm(partial_state.data) # Normalize
rho_A_partial = partial_trace(partial_state, [1])
S_partial = entropy(rho_A_partial, base=2)
print("Partially entangled state:")
print(f"Entanglement entropy: {S_partial:.6f}")
print("\n" + "="*50)
print("Summary:")
print(f" Separable: S = {S_sep:.3f} (no entanglement)")
print(f" Partial: S = {S_partial:.3f} (some entanglement)")
print(f" Maximal: S = {S_bell:.3f} (Bell state)")
print("="*50)This is the math that QPL Stage Zero computes automatically.
Every QuantumRelation object in QPL has an entanglement_entropy field that’s computed via Schmidt decomposition. This lets you see the entanglement structure of your quantum program.
Part 8: Basis Transformations - The Key to Multi-Basis Measurement
The Problem
Quantum circuits naturally measure in the Z (computational) basis. But what if we want to measure in the X basis? Or an arbitrary basis at angle \(\theta\)?
The Solution: Unitary Rotation
To measure in basis defined by unitary \(U\), we: 1. Apply \(U^\dagger\) (conjugate transpose) to rotate state to computational basis 2. Measure in computational basis 3. Interpret result as measurement in original basis
Example: X basis measurement
The X basis has eigenstates \(|+\rangle\) and \(|-\rangle\). To measure in X basis: - Apply \(H\) (Hadamard) before measurement - \(H\) is its own inverse: \(H^\dagger = H\) - \(H\) rotates X basis to Z basis
The Mathematics
For a state \(|\psi\rangle\) and measurement basis \(U\):
\[|\psi'\rangle = U^\dagger |\psi\rangle\]
Then measure \(|\psi'\rangle\) in computational basis.
This is exactly what QPL Stage Zero implements in measurement.py:
def compute_subsystem_probabilities(state, basis, subsystem_idx, num_qubits):
state_matrix = state.reshape(2, 2)
# Apply U† to transform to measurement basis
if not np.allclose(basis, np.eye(2)):
if subsystem_idx == 0:
state_matrix = basis.T.conj() @ state_matrix
else:
state_matrix = state_matrix @ basis.T.conj()
# Compute probabilities in transformed basis
# ...Let’s verify this works:
# Define X basis matrix
H_matrix = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
# State |0⟩
state_0 = np.array([1, 0])
# Transform to X basis: H† |0⟩
state_in_x_basis = H_matrix.T.conj() @ state_0
print("State |0⟩ transformed to X basis:")
print(state_in_x_basis)
print(f"\nThis is |+⟩ = (|0⟩ + |1⟩)/√2")
print(f"\nProbabilities when measured in computational basis:")
print(f"P(0) = {abs(state_in_x_basis[0])**2:.3f}")
print(f"P(1) = {abs(state_in_x_basis[1])**2:.3f}")
print("\n→ This means 50% + outcome, 50% - outcome in X basis")This is textbook quantum mechanics (Nielsen & Chuang, Chapter 2), but it’s the foundation of QPL’s multi-basis measurement system.
Part 9: Why This Matters for QPL
Everything we’ve covered is implemented in QPL Stage Zero:
1. Quantum States → QuantumRelation
bell_pair = program.entangle(alice, bob)
# bell_pair.state is the 4D state vector2. Superposition → First-Class
# Superposition is natural in QPL
system = program.create_system() # |0⟩
# In Stage 1+: apply_hadamard(system) # |+⟩3. Measurement Basis → QuantumQuestion
question_z = create_question(QuestionType.SPIN_Z) # Z basis
question_x = create_question(QuestionType.SPIN_X) # X basis4. Contextual Measurement → ask(relation, question, perspective)
result = program.ask(bell_pair, question_z, perspective="alice")
# Explicitly: who asks, what they ask, what they ask it of5. Entanglement Tracking → Automatic
print(bell_pair.entanglement_entropy) # → 1.0 (maximal)6. Basis Transformation → Built-In
# QPL automatically applies U† when you specify basis in QuantumQuestion
# You don't write the matrix multiplication—it's in the semantics7. Cross-Basis Correlations → Correct Physics
# After the bug fix:
alice_z = ask(bell_pair, SPIN_Z, subsystem=0, perspective="alice")
bob_x = ask(remaining, SPIN_X, subsystem=1, perspective="bob")
# Correlation: ~50% (correct!)Summary: The Theory You Need
To understand QPL Stage Zero, you need to understand:
✅ Quantum states are superpositions of basis states, not classical probabilities
✅ Measurement collapses superposition and is contextual (basis-dependent)
✅ Entanglement is a relation between systems, not a property of systems
✅ Same-basis measurements of entangled systems show 100% correlation
✅ Different-basis measurements of entangled systems show ~50% correlation
✅ Entanglement entropy quantifies correlation via Schmidt decomposition
✅ Basis transformations (\(U^\dagger\)) enable measurement in arbitrary bases
With this foundation, you can now understand why QPL’s abstractions—QuantumRelation, QuantumQuestion, Perspective—match the physics of quantum mechanics better than gate-based frameworks.
Next: Read Stage Zero
Now you’re ready: Stage Zero: Programming Quantum Reality Through Relations, Not Gates
You’ll see how all this theory translates into a working quantum programming language where: - Relations are first-class citizens - Questions (measurements) are explicit and contextual - Entanglement entropy is tracked automatically - The code reads like physics, not circuit design
Welcome to relations-first quantum programming.