Back to Blog
5 min read

Quantum Computing Basics: Understanding Qubits and Gates

Before diving deep into quantum programming, understanding the fundamental concepts is essential. This post covers qubits, quantum gates, and the mathematics behind quantum computing.

Classical vs Quantum Bits

Classical computers use bits that are either 0 or 1. Quantum computers use qubits that can exist in superposition - a combination of both states simultaneously.

# Classical bit representation
classical_bit = 0  # or 1

# Quantum state representation (using numpy)
import numpy as np

# |0⟩ state vector
state_zero = np.array([1, 0])

# |1⟩ state vector
state_one = np.array([0, 1])

# Superposition state: |+⟩ = (|0⟩ + |1⟩) / √2
state_plus = np.array([1/np.sqrt(2), 1/np.sqrt(2)])

# Probability of measuring |0⟩
prob_zero = np.abs(state_plus[0])**2  # 0.5

# Probability of measuring |1⟩
prob_one = np.abs(state_plus[1])**2   # 0.5

Quantum Gates as Matrices

Quantum gates are represented as unitary matrices:

import numpy as np

# Pauli-X gate (quantum NOT)
X = np.array([[0, 1],
              [1, 0]])

# Pauli-Y gate
Y = np.array([[0, -1j],
              [1j, 0]])

# Pauli-Z gate
Z = np.array([[1, 0],
              [0, -1]])

# Hadamard gate (creates superposition)
H = np.array([[1, 1],
              [1, -1]]) / np.sqrt(2)

# Phase gate
S = np.array([[1, 0],
              [0, 1j]])

# T gate (π/8 gate)
T = np.array([[1, 0],
              [0, np.exp(1j * np.pi / 4)]])

# Apply gate to qubit state
def apply_gate(gate, state):
    return np.dot(gate, state)

# Example: Apply Hadamard to |0⟩
state_zero = np.array([1, 0])
after_hadamard = apply_gate(H, state_zero)
print(f"After Hadamard: {after_hadamard}")
# Output: [0.707, 0.707] - superposition!

Multi-Qubit Systems

import numpy as np
from functools import reduce

# Tensor product for multi-qubit states
def tensor_product(a, b):
    return np.kron(a, b)

# Two-qubit state |00⟩
state_00 = tensor_product(np.array([1, 0]), np.array([1, 0]))
# Result: [1, 0, 0, 0]

# Two-qubit state |01⟩
state_01 = tensor_product(np.array([1, 0]), np.array([0, 1]))
# Result: [0, 1, 0, 0]

# CNOT gate (Controlled-NOT)
CNOT = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 0, 1],
    [0, 0, 1, 0]
])

# Create Bell state |Φ+⟩ = (|00⟩ + |11⟩) / √2
def create_bell_state():
    # Start with |00⟩
    state = np.array([1, 0, 0, 0])

    # Apply Hadamard to first qubit
    H_I = tensor_product(H, np.eye(2))  # H ⊗ I
    state = np.dot(H_I, state)

    # Apply CNOT
    state = np.dot(CNOT, state)

    return state

bell_state = create_bell_state()
print(f"Bell state: {bell_state}")
# Output: [0.707, 0, 0, 0.707] representing (|00⟩ + |11⟩)/√2

Quantum Circuit Simulation

Build a simple quantum simulator:

class QuantumSimulator:
    def __init__(self, num_qubits):
        self.num_qubits = num_qubits
        self.state = np.zeros(2**num_qubits, dtype=complex)
        self.state[0] = 1  # Initialize to |0...0⟩

    def apply_single_qubit_gate(self, gate, target_qubit):
        """Apply a single-qubit gate to the specified qubit."""
        n = self.num_qubits

        # Build the full operator using tensor products
        if target_qubit == 0:
            full_gate = gate
        else:
            full_gate = np.eye(2)

        for i in range(1, n):
            if i == target_qubit:
                full_gate = np.kron(full_gate, gate)
            else:
                full_gate = np.kron(full_gate, np.eye(2))

        self.state = np.dot(full_gate, self.state)

    def apply_cnot(self, control, target):
        """Apply CNOT gate."""
        n = self.num_qubits
        dim = 2**n
        cnot_matrix = np.zeros((dim, dim), dtype=complex)

        for i in range(dim):
            bits = [(i >> j) & 1 for j in range(n)]
            if bits[control] == 1:
                bits[target] ^= 1
            j = sum(b << k for k, b in enumerate(bits))
            cnot_matrix[j, i] = 1

        self.state = np.dot(cnot_matrix, self.state)

    def measure(self):
        """Perform measurement, return result and collapse state."""
        probabilities = np.abs(self.state)**2
        result = np.random.choice(len(self.state), p=probabilities)

        # Collapse to measured state
        self.state = np.zeros_like(self.state)
        self.state[result] = 1

        return format(result, f'0{self.num_qubits}b')

    def measure_many(self, shots=1000):
        """Run circuit multiple times and return measurement statistics."""
        original_state = self.state.copy()
        results = {}

        for _ in range(shots):
            self.state = original_state.copy()
            outcome = self.measure()
            results[outcome] = results.get(outcome, 0) + 1

        return results


# Example: Create and measure Bell state
sim = QuantumSimulator(2)
sim.apply_single_qubit_gate(H, 0)  # Hadamard on qubit 0
sim.apply_cnot(0, 1)               # CNOT with control=0, target=1

# Measure multiple times
results = sim.measure_many(1000)
print("Bell state measurements:")
for outcome, count in sorted(results.items()):
    print(f"  |{outcome}⟩: {count/10:.1f}%")
# Expected: ~50% |00⟩, ~50% |11⟩

Bloch Sphere Visualization

Visualize qubit states:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np

def state_to_bloch(state):
    """Convert quantum state to Bloch sphere coordinates."""
    # state = [α, β] where |ψ⟩ = α|0⟩ + β|1⟩
    alpha, beta = state

    # Calculate Bloch sphere angles
    theta = 2 * np.arccos(np.abs(alpha))
    phi = np.angle(beta) - np.angle(alpha)

    # Convert to Cartesian coordinates
    x = np.sin(theta) * np.cos(phi)
    y = np.sin(theta) * np.sin(phi)
    z = np.cos(theta)

    return x, y, z

def plot_bloch_sphere(states, labels=None):
    """Plot states on Bloch sphere."""
    fig = plt.figure(figsize=(10, 10))
    ax = fig.add_subplot(111, projection='3d')

    # Draw sphere
    u = np.linspace(0, 2 * np.pi, 100)
    v = np.linspace(0, np.pi, 50)
    x = np.outer(np.cos(u), np.sin(v))
    y = np.outer(np.sin(u), np.sin(v))
    z = np.outer(np.ones(np.size(u)), np.cos(v))
    ax.plot_surface(x, y, z, alpha=0.1, color='blue')

    # Draw axes
    ax.quiver(0, 0, 0, 1.5, 0, 0, color='r', arrow_length_ratio=0.1)
    ax.quiver(0, 0, 0, 0, 1.5, 0, color='g', arrow_length_ratio=0.1)
    ax.quiver(0, 0, 0, 0, 0, 1.5, color='b', arrow_length_ratio=0.1)

    # Plot states
    colors = plt.cm.rainbow(np.linspace(0, 1, len(states)))
    for i, state in enumerate(states):
        x, y, z = state_to_bloch(state)
        label = labels[i] if labels else f'State {i}'
        ax.scatter([x], [y], [z], color=colors[i], s=100, label=label)
        ax.quiver(0, 0, 0, x, y, z, color=colors[i], arrow_length_ratio=0.1)

    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.legend()
    plt.title('Bloch Sphere Representation')
    plt.show()

# Example states
states = [
    np.array([1, 0]),                      # |0⟩
    np.array([0, 1]),                      # |1⟩
    np.array([1, 1]) / np.sqrt(2),         # |+⟩
    np.array([1, -1]) / np.sqrt(2),        # |-⟩
    np.array([1, 1j]) / np.sqrt(2),        # |i⟩
]
labels = ['|0⟩', '|1⟩', '|+⟩', '|-⟩', '|i⟩']

plot_bloch_sphere(states, labels)

Summary

Quantum computing fundamentals:

  • Qubits exist in superposition states
  • Quantum gates are unitary matrices
  • Multi-qubit systems use tensor products
  • Measurement collapses superposition
  • Entanglement creates correlated states

These concepts form the foundation for quantum algorithms.


References:

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.