Basic Quantum Computing Tutorial¶
Welcome to the world of quantum computing! This tutorial will teach you the fundamentals of quantum computing using SuperQuantX. No prior quantum knowledge required - we'll build everything from the ground up.
🎯 What You'll Learn¶
By the end of this tutorial, you'll understand:
- Quantum bits (qubits) and how they differ from classical bits
- Quantum gates and how to manipulate quantum states
- Quantum superposition and entanglement
- Quantum measurement and probability
- Basic quantum algorithms like Deutsch-Jozsa and Grover's
🚀 Prerequisites¶
- Basic Python knowledge
- High school mathematics (vectors, complex numbers helpful but not required)
- SuperQuantX installed (
pip install superquantx
)
📚 Chapter 1: Classical vs Quantum Bits¶
Classical Bits¶
Classical computers use bits that are either 0 or 1:
# Classical bits
classical_bit_1 = 0
classical_bit_2 = 1
print(f"Classical bits: {classical_bit_1}, {classical_bit_2}")
Quantum Bits (Qubits)¶
Quantum computers use qubits that can be in superposition - both 0 and 1 simultaneously!
import superquantx as sqx
import numpy as np
# Create a quantum backend
backend = sqx.get_backend('simulator')
# Create a circuit with 1 qubit
circuit = backend.create_circuit(1)
# Initially, the qubit is in state |0⟩
print("Initial state: |0⟩ - definitely 0")
# Let's measure it
circuit.measure_all()
result = backend.run(circuit, shots=1000)
print(f"Measurements of |0⟩: {result.get_counts()}")
Expected output: All measurements give '0'.
Creating Superposition¶
The magic happens with the Hadamard gate (H gate):
# Create a new circuit
circuit = backend.create_circuit(1)
# Apply Hadamard gate to create superposition
circuit.h(0) # H gate on qubit 0
# Measure the qubit
circuit.measure_all()
# Run many times to see the probability
result = backend.run(circuit, shots=1000)
print(f"Measurements after H gate: {result.get_counts()}")
Expected output: Roughly 50% '0' and 50% '1' - the qubit is in superposition!
What happened? The H gate put the qubit in state |+⟩ = (|0⟩ + |1⟩)/√2, which has equal probability of measuring 0 or 1.
📚 Chapter 2: Quantum Gates¶
Quantum gates are operations that manipulate qubits. Let's explore the basic gates:
Single-Qubit Gates¶
Pauli Gates¶
def demonstrate_pauli_gates():
"""Show the effects of X, Y, Z gates."""
gates = {
'X': lambda c, q: c.x(q), # Bit flip: |0⟩↔|1⟩
'Y': lambda c, q: c.y(q), # Bit and phase flip
'Z': lambda c, q: c.z(q), # Phase flip: |1⟩→-|1⟩
}
for gate_name, gate_func in gates.items():
print(f"\n--- Testing {gate_name} Gate ---")
# Test on |0⟩
circuit = backend.create_circuit(1)
gate_func(circuit, 0)
circuit.measure_all()
result = backend.run(circuit, shots=1000)
print(f"{gate_name}|0⟩ → {result.get_counts()}")
# Test on |1⟩ (first apply X to get |1⟩)
circuit = backend.create_circuit(1)
circuit.x(0) # Prepare |1⟩
gate_func(circuit, 0)
circuit.measure_all()
result = backend.run(circuit, shots=1000)
print(f"{gate_name}|1⟩ → {result.get_counts()}")
demonstrate_pauli_gates()
Rotation Gates¶
def demonstrate_rotation_gates():
"""Show rotation gates that create arbitrary superpositions."""
import matplotlib.pyplot as plt
angles = np.linspace(0, 2*np.pi, 8)
probabilities = []
for angle in angles:
circuit = backend.create_circuit(1)
# RY rotation creates cos(θ/2)|0⟩ + sin(θ/2)|1⟩
circuit.ry(angle, 0)
circuit.measure_all()
result = backend.run(circuit, shots=1000)
counts = result.get_counts()
prob_1 = counts.get('1', 0) / 1000
probabilities.append(prob_1)
print(f"RY({angle:.2f}) → P(|1⟩) = {prob_1:.3f}")
# Plot the results
plt.figure(figsize=(10, 6))
plt.plot(angles, probabilities, 'bo-')
plt.plot(angles, np.sin(angles/2)**2, 'r--', label='Theoretical')
plt.xlabel('Rotation Angle (radians)')
plt.ylabel('Probability of measuring |1⟩')
plt.title('RY Gate: Creating Arbitrary Superpositions')
plt.legend()
plt.grid(True)
plt.show()
demonstrate_rotation_gates()
Two-Qubit Gates¶
CNOT Gate (Controlled-X)¶
The CNOT gate creates entanglement between qubits:
def demonstrate_cnot_gate():
"""Show how CNOT gate creates entanglement."""
print("CNOT Gate Truth Table:")
print("Control | Target | → | Control | Target")
print(" 0 | 0 | → | 0 | 0 ")
print(" 0 | 1 | → | 0 | 1 ")
print(" 1 | 0 | → | 1 | 1 ") # Target flips
print(" 1 | 1 | → | 1 | 0 ") # Target flips
# Test all input combinations
inputs = [('00', []), ('01', [1]), ('10', [0]), ('11', [0, 1])]
for state_name, prep_gates in inputs:
circuit = backend.create_circuit(2)
# Prepare input state
for qubit in prep_gates:
circuit.x(qubit)
# Apply CNOT (control=0, target=1)
circuit.cx(0, 1)
# Measure
circuit.measure_all()
result = backend.run(circuit, shots=1000)
print(f"Input |{state_name}⟩ → {result.get_counts()}")
demonstrate_cnot_gate()
Creating Bell States¶
Bell states are maximally entangled two-qubit states:
def create_bell_states():
"""Create and measure all four Bell states."""
bell_states = {
'|Φ+⟩': lambda c: [c.h(0), c.cx(0, 1)],
'|Φ-⟩': lambda c: [c.h(0), c.z(0), c.cx(0, 1)],
'|Ψ+⟩': lambda c: [c.h(0), c.cx(0, 1), c.x(1)],
'|Ψ-⟩': lambda c: [c.h(0), c.z(0), c.cx(0, 1), c.x(1)],
}
for name, preparation in bell_states.items():
circuit = backend.create_circuit(2)
# Prepare Bell state
for gate_op in preparation(circuit):
pass # Gates are applied by the lambda
circuit.measure_all()
result = backend.run(circuit, shots=1000)
print(f"Bell state {name}: {result.get_counts()}")
print(f" Entanglement: Only '00' and '11' appear!\n")
create_bell_states()
📚 Chapter 3: Understanding Superposition¶
Let's explore superposition with interactive examples:
Superposition Visualization¶
def visualize_superposition():
"""Visualize how superposition probabilities change with angle."""
import matplotlib.pyplot as plt
# Create a range of angles
angles = np.linspace(0, np.pi, 20)
prob_0_list = []
prob_1_list = []
for theta in angles:
# Create state cos(θ/2)|0⟩ + sin(θ/2)|1⟩
circuit = backend.create_circuit(1)
circuit.ry(theta, 0)
circuit.measure_all()
result = backend.run(circuit, shots=1000)
counts = result.get_counts()
prob_0 = counts.get('0', 0) / 1000
prob_1 = counts.get('1', 0) / 1000
prob_0_list.append(prob_0)
prob_1_list.append(prob_1)
# Plot results
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
# Probability plot
ax1.plot(angles, prob_0_list, 'bo-', label='P(|0⟩)')
ax1.plot(angles, prob_1_list, 'ro-', label='P(|1⟩)')
ax1.plot(angles, np.cos(angles/2)**2, 'b--', alpha=0.5, label='Theory P(|0⟩)')
ax1.plot(angles, np.sin(angles/2)**2, 'r--', alpha=0.5, label='Theory P(|1⟩)')
ax1.set_xlabel('Angle θ (radians)')
ax1.set_ylabel('Probability')
ax1.set_title('Superposition: Probability vs Angle')
ax1.legend()
ax1.grid(True)
# Bloch sphere representation (simplified 2D)
ax2.set_xlim(-1.1, 1.1)
ax2.set_ylim(-1.1, 1.1)
for i, theta in enumerate(angles[::2]): # Show every other point
x = np.sin(theta)
z = np.cos(theta)
ax2.arrow(0, 0, x, z, head_width=0.05, head_length=0.05,
fc='blue', ec='blue', alpha=0.6)
ax2.set_xlabel('X (Superposition axis)')
ax2.set_ylabel('Z (Computational basis)')
ax2.set_title('Bloch Sphere (2D projection)')
ax2.grid(True)
ax2.set_aspect('equal')
plt.tight_layout()
plt.show()
visualize_superposition()
Interference Effects¶
Quantum interference is key to quantum algorithms:
def demonstrate_interference():
"""Show constructive and destructive interference."""
print("Quantum Interference Demo")
print("=" * 30)
# Circuit 1: Two H gates (interference)
circuit1 = backend.create_circuit(1)
circuit1.h(0) # Create superposition
circuit1.h(0) # Apply H again
circuit1.measure_all()
result1 = backend.run(circuit1, shots=1000)
print(f"H-H sequence: {result1.get_counts()}")
print("Result: Back to |0⟩ (constructive interference)")
# Circuit 2: H-Z-H sequence
circuit2 = backend.create_circuit(1)
circuit2.h(0) # Create superposition
circuit2.z(0) # Phase flip
circuit2.h(0) # Apply H again
circuit2.measure_all()
result2 = backend.run(circuit2, shots=1000)
print(f"H-Z-H sequence: {result2.get_counts()}")
print("Result: Always |1⟩ (destructive interference)")
# Circuit 3: Ramsey interferometry
print(f"\nRamsey Interferometry (varying phase):")
phases = np.linspace(0, 2*np.pi, 8)
for phase in phases:
circuit = backend.create_circuit(1)
circuit.h(0) # First superposition
circuit.rz(phase, 0) # Add phase
circuit.h(0) # Second superposition (interference)
circuit.measure_all()
result = backend.run(circuit, shots=1000)
prob_1 = result.get_counts().get('1', 0) / 1000
print(f"Phase {phase:.2f}: P(|1⟩) = {prob_1:.3f}")
demonstrate_interference()
📚 Chapter 4: Quantum Entanglement¶
Entanglement is perhaps the most "quantum" feature of quantum mechanics:
Understanding Entanglement¶
def understand_entanglement():
"""Compare separable vs entangled states."""
print("Separable vs Entangled States")
print("=" * 35)
# Separable state: |0⟩|+⟩ = |0⟩ ⊗ (|0⟩ + |1⟩)/√2
print("1. Separable state |0⟩|+⟩:")
circuit_sep = backend.create_circuit(2)
circuit_sep.h(1) # Only second qubit in superposition
circuit_sep.measure_all()
result_sep = backend.run(circuit_sep, shots=1000)
print(f" Measurements: {result_sep.get_counts()}")
print(" Analysis: '00' and '01' appear - first qubit always 0")
# Entangled state: (|00⟩ + |11⟩)/√2
print(f"\n2. Entangled Bell state |Φ+⟩:")
circuit_ent = backend.create_circuit(2)
circuit_ent.h(0) # Create superposition
circuit_ent.cx(0, 1) # Create entanglement
circuit_ent.measure_all()
result_ent = backend.run(circuit_ent, shots=1000)
print(f" Measurements: {result_ent.get_counts()}")
print(" Analysis: Only '00' and '11' appear - qubits are correlated!")
understand_entanglement()
Quantum Teleportation¶
Let's implement the famous quantum teleportation protocol:
def quantum_teleportation_demo():
"""Demonstrate quantum teleportation protocol."""
print("Quantum Teleportation Protocol")
print("=" * 35)
# Step 1: Prepare unknown state to teleport
# We'll teleport the state α|0⟩ + β|1⟩ where α = cos(π/6), β = sin(π/6)
angle = np.pi/6
print(f"Step 1: Prepare state to teleport")
print(f" |ψ⟩ = cos({angle:.2f})|0⟩ + sin({angle:.2f})|1⟩")
# Complete teleportation circuit
circuit = backend.create_circuit(3) # 3 qubits: message, ancilla, target
# Prepare the state to teleport on qubit 0
circuit.ry(2*angle, 0)
# Step 2: Create Bell pair between qubits 1 and 2
circuit.h(1)
circuit.cx(1, 2)
print("Step 2: Create Bell pair between ancilla and target")
# Step 3: Bell measurement on qubits 0 and 1
circuit.cx(0, 1)
circuit.h(0)
# Measure qubits 0 and 1 (in real quantum computer)
# For simulation, we'll show the conditional operations
print("Step 3: Bell measurement and conditional corrections")
# Conditional corrections based on measurement results
# In practice, these would be applied based on classical measurement results
# For demonstration, let's show the final state statistics
circuit.measure_all()
result = backend.run(circuit, shots=1000)
print(f"Final measurements: {result.get_counts()}")
print("Note: In real teleportation, qubit 2 would have the original state")
# Verify by measuring original state directly
verify_circuit = backend.create_circuit(1)
verify_circuit.ry(2*angle, 0)
verify_circuit.measure_all()
verify_result = backend.run(verify_circuit, shots=1000)
print(f"Original state measurement: {verify_result.get_counts()}")
quantum_teleportation_demo()
📚 Chapter 5: Your First Quantum Algorithm¶
Let's implement the Deutsch-Jozsa algorithm - it's simple but shows quantum advantage!
The Problem¶
Given a function f that takes n bits and outputs 1 bit, determine if f is: - Constant: f(x) = 0 for all x, OR f(x) = 1 for all x - Balanced: f(x) = 0 for exactly half the inputs, f(x) = 1 for the other half
Classical solution: Need to check up to 2^(n-1) + 1 inputs. Quantum solution: Need only 1 quantum query!
Implementation¶
def deutsch_jozsa_algorithm():
"""Implement the Deutsch-Jozsa algorithm."""
print("Deutsch-Jozsa Algorithm")
print("=" * 25)
def create_oracle(function_type, n_qubits):
"""Create quantum oracle for different function types."""
def oracle(circuit):
if function_type == "constant_0":
# f(x) = 0 for all x: do nothing
pass
elif function_type == "constant_1":
# f(x) = 1 for all x: flip ancilla
circuit.x(n_qubits) # Flip ancilla qubit
elif function_type == "balanced_first_bit":
# f(x) = first bit of x
circuit.cx(0, n_qubits) # Copy first bit to ancilla
elif function_type == "balanced_parity":
# f(x) = parity of x
for i in range(n_qubits):
circuit.cx(i, n_qubits)
return oracle
def deutsch_jozsa(oracle, n_qubits):
"""Run Deutsch-Jozsa algorithm."""
circuit = backend.create_circuit(n_qubits + 1) # +1 for ancilla
# Step 1: Initialize ancilla in |1⟩
circuit.x(n_qubits)
# Step 2: Create superposition on input qubits, |-> on ancilla
for i in range(n_qubits + 1):
circuit.h(i)
# Step 3: Apply oracle
oracle(circuit)
# Step 4: Apply Hadamard to input qubits
for i in range(n_qubits):
circuit.h(i)
# Step 5: Measure input qubits
for i in range(n_qubits):
circuit.measure(i, i)
return circuit
# Test different function types
n = 3 # 3-bit functions
function_types = [
("constant_0", "Constant f(x)=0"),
("constant_1", "Constant f(x)=1"),
("balanced_first_bit", "Balanced f(x)=x₀"),
("balanced_parity", "Balanced f(x)=x₀⊕x₁⊕x₂")
]
for func_type, description in function_types:
print(f"\nTesting {description}:")
oracle = create_oracle(func_type, n)
circuit = deutsch_jozsa(oracle, n)
result = backend.run(circuit, shots=1000)
counts = result.get_counts()
# Check if all measurements are "000"
all_zeros = counts.get("000", 0)
total_measurements = sum(counts.values())
if all_zeros == total_measurements:
print(f" Result: CONSTANT (all measurements = 000)")
else:
print(f" Result: BALANCED (mixed measurements)")
print(f" Measurements: {counts}")
deutsch_jozsa_algorithm()
Grover's Search Algorithm¶
Let's implement a simplified version of Grover's algorithm:
def grovers_search_demo():
"""Demonstrate Grover's search algorithm."""
print("Grover's Search Algorithm")
print("=" * 28)
def create_oracle(target_item, n_qubits):
"""Create oracle that marks the target item."""
def oracle(circuit):
# Convert target to binary
target_binary = format(target_item, f'0{n_qubits}b')
# Flip qubits that should be 0 in the target
for i, bit in enumerate(target_binary):
if bit == '0':
circuit.x(i)
# Multi-controlled Z gate (marks target with -1 phase)
if n_qubits == 2:
circuit.cz(0, 1)
elif n_qubits == 3:
# For 3 qubits, use CCZ gate (or decompose)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cz(0, 2)
circuit.cx(1, 2)
circuit.cx(0, 1)
# Flip back the qubits
for i, bit in enumerate(target_binary):
if bit == '0':
circuit.x(i)
return oracle
def diffusion_operator(circuit, n_qubits):
"""Apply the diffusion operator (inversion about average)."""
# H gates
for i in range(n_qubits):
circuit.h(i)
# Flip all qubits
for i in range(n_qubits):
circuit.x(i)
# Multi-controlled Z gate
if n_qubits == 2:
circuit.cz(0, 1)
elif n_qubits == 3:
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cz(0, 2)
circuit.cx(1, 2)
circuit.cx(0, 1)
# Flip back
for i in range(n_qubits):
circuit.x(i)
# H gates
for i in range(n_qubits):
circuit.h(i)
def grovers_algorithm(target, n_qubits):
"""Run Grover's algorithm to find target item."""
circuit = backend.create_circuit(n_qubits)
# Step 1: Create uniform superposition
for i in range(n_qubits):
circuit.h(i)
# Step 2: Calculate optimal number of iterations
N = 2**n_qubits
optimal_iterations = int(np.pi/4 * np.sqrt(N))
print(f" Optimal iterations: {optimal_iterations}")
# Step 3: Grover iterations
oracle = create_oracle(target, n_qubits)
for iteration in range(optimal_iterations):
# Apply oracle
oracle(circuit)
# Apply diffusion operator
diffusion_operator(circuit, n_qubits)
circuit.measure_all()
return circuit
# Test Grover's algorithm
n_qubits = 3 # Search in 2^3 = 8 items
target_item = 5 # Search for item 5 (binary: 101)
print(f"Searching for item {target_item} in {2**n_qubits} items:")
print(f"Target in binary: {format(target_item, f'0{n_qubits}b')}")
circuit = grovers_algorithm(target_item, n_qubits)
result = backend.run(circuit, shots=1000)
counts = result.get_counts()
print(f" Search results: {counts}")
# Check success probability
target_binary = format(target_item, f'0{n_qubits}b')
success_count = counts.get(target_binary, 0)
success_probability = success_count / 1000
print(f" Success probability: {success_probability:.3f}")
print(f" Classical random search: {1/8:.3f}")
if success_probability > 0.5:
print(" ✅ Quantum speedup achieved!")
else:
print(" ❌ Algorithm needs tuning")
grovers_search_demo()
📚 Chapter 6: Putting It All Together¶
Let's create a complete quantum application that combines multiple concepts:
Quantum Random Number Generator¶
def quantum_random_number_generator():
"""Create a true quantum random number generator."""
print("Quantum Random Number Generator")
print("=" * 35)
def generate_random_number(num_bits):
"""Generate an n-bit random number using quantum superposition."""
circuit = backend.create_circuit(num_bits)
# Put all qubits in superposition
for i in range(num_bits):
circuit.h(i)
# Measure all qubits
circuit.measure_all()
# Run once to get a single random number
result = backend.run(circuit, shots=1)
measurement = list(result.get_counts().keys())[0]
# Convert binary string to integer
random_number = int(measurement, 2)
return random_number, measurement
# Generate several random numbers
print("Generating 4-bit random numbers:")
for i in range(10):
number, binary = generate_random_number(4)
print(f" Run {i+1:2d}: {binary} = {number:2d}")
# Statistical test
print(f"\nStatistical test (1000 numbers):")
numbers = []
for _ in range(1000):
number, _ = generate_random_number(4)
numbers.append(number)
# Calculate statistics
mean = np.mean(numbers)
std = np.std(numbers)
expected_mean = 7.5 # For 4-bit numbers (0-15)
expected_std = np.sqrt((16**2 - 1) / 12) # Uniform distribution
print(f" Mean: {mean:.2f} (expected: {expected_mean:.2f})")
print(f" Std: {std:.2f} (expected: {expected_std:.2f})")
# Histogram
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.hist(numbers, bins=16, range=(-0.5, 15.5), alpha=0.7, edgecolor='black')
plt.axhline(y=1000/16, color='red', linestyle='--', label='Expected (uniform)')
plt.xlabel('Random Number')
plt.ylabel('Frequency')
plt.title('Quantum Random Number Distribution')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
quantum_random_number_generator()
Quantum Coin Flipping Game¶
def quantum_coin_game():
"""Interactive quantum coin flipping game."""
print("Quantum Coin Flipping Game")
print("=" * 30)
print("Rules:")
print("1. We'll flip a quantum coin (H gate)")
print("2. You guess Heads (H) or Tails (T)")
print("3. |0⟩ = Heads, |1⟩ = Tails")
print("4. Let's see how well you do!")
print()
wins = 0
total_games = 5
for game in range(total_games):
print(f"Game {game + 1}/{total_games}")
# Get player guess (in real implementation, use input())
# For demo, we'll simulate random guesses
import random
guesses = ['H', 'T']
player_guess = random.choice(guesses)
print(f"Your guess: {player_guess}")
# Flip quantum coin
circuit = backend.create_circuit(1)
circuit.h(0) # Fair quantum coin
circuit.measure_all()
result = backend.run(circuit, shots=1)
outcome = list(result.get_counts().keys())[0]
if outcome == '0':
coin_result = 'H'
print("Quantum coin: Heads (|0⟩)")
else:
coin_result = 'T'
print("Quantum coin: Tails (|1⟩)")
if player_guess == coin_result:
print("✅ You win!")
wins += 1
else:
print("❌ You lose!")
print()
print(f"Final Score: {wins}/{total_games} ({wins/total_games*100:.1f}%)")
if wins/total_games > 0.6:
print("🎉 Great job! You might be quantum intuitive!")
elif wins/total_games < 0.4:
print("🤔 The quantum realm is tricky!")
else:
print("📊 Right around random chance - that's expected!")
quantum_coin_game()
🎯 What's Next?¶
Congratulations! You've learned the fundamentals of quantum computing:
✅ Qubits and superposition
✅ Quantum gates and circuits
✅ Entanglement and measurement
✅ Basic quantum algorithms
✅ Hands-on quantum programming
Continue Your Quantum Journey¶
- Quantum Machine Learning Tutorial - Apply quantum computing to machine learning
- Quantum Neural Networks - Build deep quantum neural networks
- Advanced Algorithms - Explore more sophisticated quantum algorithms
- Multi-Backend Usage - Compare different quantum frameworks
Additional Resources¶
- SuperQuantX Documentation - Complete SuperQuantX guide
- API Reference - Detailed function documentation
- Quantum Computing Textbooks:
- "Quantum Computation and Quantum Information" by Nielsen & Chuang
- "Programming Quantum Computers" by Johnston, Harrigan & Gimeno-Segovia
Practice Problems¶
Try these exercises to reinforce your learning:
- Create a 3-qubit GHZ state: |000⟩ + |111⟩
- Implement quantum random walk on a line
- Build a quantum addition circuit for 2-bit numbers
- Design a quantum error correction code (simple 3-qubit)
- Create a quantum version of your favorite classical algorithm
🤝 Need Help?¶
- FAQ - Common quantum computing questions
- Troubleshooting - Solve common issues
- GitHub Issues - Ask questions
- Community - Join quantum computing forums and discussions
Welcome to the quantum computing community! The quantum future is bright, and you're now part of it. 🌟
Keep Experimenting
Quantum computing is best learned by doing. Keep building circuits, trying new gates, and exploring quantum phenomena. Every experiment teaches you something new about the quantum world!
Remember
SuperQuantX is research software. Use it for learning and experimentation, not for production applications. Real quantum computers have additional challenges like noise and limited connectivity.
Next Steps
Once you're comfortable with these basics, dive into specific applications like quantum machine learning, optimization, or cryptography. The quantum computing landscape is vast and full of opportunities!