Quantum Computing Fundamentals Part II: 10 Not-So Easy Pieces

Written by thomascherickal | Published 2025/12/31
Tech Story Tags: quantum-computing | quantum-algorithms | qaoa | vqe | repository-provided | implementation-by-hand | quantum-advantage | quantum-computing-in-2035

TLDRTen algorithms, each one revolutionary in potential> Part II of the QuantuM Fundamentals series, the second and the final part.via the TL;DR App

From Shor’s Algorithm to Quantum Advantage

Welcome to Part II - advanced quantum algorithms!

We give you the source code, the instructions to run each program, and an explanation about every major quantum algorithm used in the industry.

If you are interested in advanced quantum computing, this article is for you!

How to Run the Code

To run the following Python code examples, you will need a Python environment (version 3.10 or later is recommended).

You can install all necessary components by running the following command in your terminal:

pip install qiskit[visualization] qiskit-aer qiskit-ibm-runtime qiskit-algorithms qiskit-nature numpy

Each code snippet is designed to be completely self-contained.

You can copy the code into a Python file (e.g., example.py) and execute it from your terminal using the command:

python filename.py

All examples use Qiskit's built-in simulators, which run on your local machine without needing access to real quantum hardware.

For convenience, the entire GitHub repository for both articles is given below:

https://github.com/thomascherickal/quantum-computing-fundamentals-article

You can replicate the entire repository in your local system using:

git clone https://github.com/thomascherickal/quantum-computing-fundamentals-article

1. Shor's Algorithm

Shor's algorithm, developed by Peter Shor in 1994, can find the prime factors of a large integer N exponentially faster than the best-known classical algorithms.

Its discovery ignited widespread interest in quantum computing due to its ability to break widely used cryptographic systems like RSA.

The algorithm transforms the factoring problem into a period-finding problem, which is perfectly suited for a quantum computer.

The core of the algorithm is a quantum subroutine that finds the period r of the function f(x) = a^x mod N.

This step achieves its exponential speedup by using the Quantum Fourier Transform (QFT) on a superposition of all inputs.

Once the period r is found, the factors of N can be derived through a simple classical calculation.

While current quantum computers are too small to threaten modern cryptography, Shor's algorithm is the primary driver for the development of post-quantum cryptography.

Code Example:

This is a completely manual implementation of Shor’s algorithm to factor the number 15.

This is trivial in theory, but once quantum computers scale to millions of qubits, they will challenge current cryptographic algorithms.

Hence, the need for post-quantum cryptography.

import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit_aer import AerSimulator
from math import gcd
from fractions import Fraction

def c_amod15(a, power):
    """Controlled multiplication by a mod 15"""
    if a not in [2, 4, 7, 8, 11, 13]:
        raise ValueError("'a' must be coprime to 15")
    
    U = QuantumCircuit(4)
    for _ in range(power):
        if a in [2, 13]:
            U.swap(0, 1)
            U.swap(1, 2)
            U.swap(2, 3)
        if a in [7, 8]:
            U.swap(2, 3)
            U.swap(1, 2)
            U.swap(0, 1)
        if a in [4, 11]:
            U.swap(1, 3)
            U.swap(0, 2)
        if a in [7, 11, 13]:
            for q in range(4):
                U.x(q)
    U = U.to_gate()
    U.name = f"{a}^{power} mod 15"
    c_U = U.control()
    return c_U

def qft_dagger(n):
    """Inverse Quantum Fourier Transform"""
    qc = QuantumCircuit(n)
    # Swap qubits
    for qubit in range(n//2):
        qc.swap(qubit, n-qubit-1)
    # Apply inverse QFT operations
    for j in range(n):
        for m in range(j):
            qc.cp(-np.pi/float(2**(j-m)), m, j)
        qc.h(j)
    qc.name = "QFT†"
    return qc

def shors_algorithm(N=15, a=7):
    """
    Shor's algorithm for factoring N
    
    Args:
        N: Number to factor (default: 15)
        a: Coprime base for modular exponentiation (default: 7)
    
    Returns:
        tuple: (quantum_circuit, measurement_counts, factors)
    """
    # Check if N is even
    if N % 2 == 0:
        return None, None, (2, N // 2)
    
    # Check if gcd(a, N) > 1
    g = gcd(a, N)
    if g > 1:
        return None, None, (g, N // g)
    
    # Number of counting qubits
    n_count = 8
    
    # Create quantum registers
    qr_count = QuantumRegister(n_count, 'counting')
    qr_aux = QuantumRegister(4, 'auxiliary')
    cr = ClassicalRegister(n_count, 'classical')
    qc = QuantumCircuit(qr_count, qr_aux, cr)
    
    # Initialize counting qubits in superposition
    for q in range(n_count):
        qc.h(q)
    
    # Initialize auxiliary register to |1⟩
    qc.x(n_count)
    qc.barrier()
    
    # Apply controlled-U operations
    for q in range(n_count):
        qc.append(c_amod15(a, 2**q), [q] + [i+n_count for i in range(4)])
    
    qc.barrier()
    
    # Apply inverse QFT
    qc.append(qft_dagger(n_count), range(n_count))
    qc.barrier()
    
    # Measure counting qubits
    qc.measure(range(n_count), range(n_count))
    
    # Transpile the circuit to decompose custom gates
    simulator = AerSimulator()
    transpiled_qc = transpile(qc, simulator, optimization_level=0)
    
    # Simulate the transpiled circuit
    result = simulator.run(transpiled_qc, shots=2048).result()
    counts = result.get_counts()
    
    # Process results to find factors
    factors = process_measurement_results(counts, N, a, n_count)
    

    return qc, counts, factors

def process_measurement_results(counts, N, a, n_count):
    """Process measurement results to extract factors"""
    
    # Sort by most frequent measurements
    sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
    
    for output, count in sorted_counts[:10]:  # Check top 10 results
        decimal = int(output, 2)
        
        # Skip if measurement is 0
        if decimal == 0:
            continue
        
        # Calculate phase
        phase = decimal / (2**n_count)
        
        # Use continued fractions to find the period r
        frac = Fraction(phase).limit_denominator(N)
        r = frac.denominator
        
        # Check if r is valid
        if r > 0 and r % 2 == 0:
            # Calculate potential factors
            x = pow(a, r//2, N)
            
            guess1 = gcd(x - 1, N)
            guess2 = gcd(x + 1, N)
            
            # Check if we found non-trivial factors
            if guess1 not in [1, N]:
                return (guess1, N // guess1)
            if guess2 not in [1, N]:
                return (guess2, N // guess2)
    
    return None

# ==================== MAIN EXECUTION ====================

print("=" * 70)
print("SHOR'S ALGORITHM - QUANTUM FACTORIZATION")
print("=" * 70)

N = 15
a = 7

print(f"\nFactoring N = {N} using a = {a}")
print(f"Note: gcd({a}, {N}) = {gcd(a, N)}")
print("\nRunning quantum circuit (this may take a moment)...")

# Run Shor's algorithm
qc, counts, factors = shors_algorithm(N, a)

qc.name = f"Shor's Algorithm for N={N}, a={a}"

qc.draw(output='mpl', filename='shor_circuit_long.png', fold=-1)

if factors:
    print(f"\n{'=' * 70}")
    print("FACTORS FOUND!")
    print(f"{'=' * 70}")
    print(f"{N} = {factors[0]} × {factors[1]}")
    print(f"Verification: {factors[0]} × {factors[1]} = {factors[0] * factors[1]}")
else:
    print("\nNo factors found in this run.")
    print("This can happen due to quantum measurement randomness.")
    print("Try running again or with a different value of 'a'.")

print(f"\n{'=' * 70}")
print("QUANTUM CIRCUIT STATISTICS")
print(f"{'=' * 70}")
print(f"Circuit depth: {qc.depth()}")
print(f"Number of qubits: {qc.num_qubits}")
print(f"Number of classical bits: {qc.num_clbits}")
print(f"Number of operations: {qc.size()}")

print(f"\n{'=' * 70}")
print("TOP MEASUREMENT RESULTS")
print(f"{'=' * 70}")
sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
for i, (output, count) in enumerate(sorted_counts[:10], 1):
    decimal = int(output, 2)
    phase = decimal / 256
    frac = Fraction(phase).limit_denominator(N)
    print(f"{i:2d}. |{output}⟩ : {count:4d} times (decimal: {decimal:3d}, phase ≈ {frac})")

print(f"\n{'=' * 70}")

Output

Explanation:

The simulation successfully factored the number N=15, finding its prime factors: 3 and 5.

  1. Setup and Verification

    • Factoring N=15: The goal is to find two prime numbers that multiply to 15. The simulation used a starting number a=7.
    • Resources Used: The quantum circuit required 12 qubits and 26 operations to perform this task, showing the complexity even for small numbers.
  2. Quantum Calculation and Results

    1. Shor's algorithm relies on finding the period of a mathematical function. This period-finding is achieved using quantum computing techniques.
    2. Top Measurement Results: The circuit was run multiple times, and the results clustered around specific binary strings. These results (corresponding to decimal values 192, 128, 0, and 64) relate directly to the phases (e.g., phase is approximately 3/4) which are needed to calculate the period.
    3. The Key: The difference between these measured phase values allows the algorithm to determine the period of the function.
  3. Final Classical Step Once the period is found from the quantum simulation, the factors are derived using a classical calculation (the greatest common divisor). This final step successfully yields the factors.


2. Quantum Fourier Transform (QFT)

The Quantum Fourier Transform (QFT) is the quantum analogue of the classical discrete Fourier transform (DFT).

The QFT operates on the amplitudes of a quantum state, decomposing it into its constituent frequencies.

Its significance comes from its extraordinary efficiency: on n qubits, the QFT requires only O(n²) gates, an exponential speedup over the classical O(N log N) for N=2ⁿ data points.

This efficiency makes the QFT an essential subroutine in algorithms that rely on period finding, most notably Shor's algorithm.

In that context, the QFT transforms a state where a period is encoded in its amplitudes into a state where the frequency of that period can be easily read out by measurement.

Code Example:

This code builds the QFT circuit for 3 qubits from scratch and applies it to the input state |5> (|101⟩), showing the resulting transformation.

# Import necessary components
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# --- Function to build the QFT circuit ---
def qft_circuit(n):
    """Builds a QFT circuit on n qubits."""
    qc = QuantumCircuit(n, name=f'QFT({n})')
    # Apply the rotations
    for j in range(n):
        qc.h(j)
        for k in range(j + 1, n):
            # Controlled-Phase rotation
            qc.cp(np.pi / 2**(k - j), k, j)
    # Swap the qubits at the end to match the mathematical definition
    for i in range(n // 2):
        qc.swap(i, n - 1 - i)
    return qc

# --- Main Program ---
num_qubits = 3
# Create the input state |101> (which is 5 in decimal)
input_qc = QuantumCircuit(num_qubits)
input_qc.x(0)
input_qc.x(2)
initial_state = Statevector(input_qc)

# Create the QFT circuit
qft = qft_circuit(num_qubits)

# Apply the QFT to the input state
full_circuit = input_qc.compose(qft)
final_state = Statevector(full_circuit)

# --- Print Results ---
print(f"Input State: |101> (Decimal 5)")
print(initial_state.draw('text'))
print(f"\n{num_qubits}-Qubit QFT Circuit:")
print(qft)
print("\nFinal State after QFT:")
print(final_state.draw('text', precision=3))

Output

Explanation:

  1. Operation Type: The image illustrates a 3-Qubit Quantum Fourier Transform (QFT), a fundamental algorithm in quantum computing used to transform data from the time/computational domain to the frequency domain.
  2. Input State: The process begins with an input state of ∣101⟩∣101⟩, which corresponds to the decimal value 5.
  3. Input State Vector: The top array shows the mathematical representation of this state. There is a '1' at the 6th position (index 5), indicating a 100% probability of the system being in state ∣101⟩∣101⟩.
  4. Circuit Components: The diagram shows the sequence of gates applied: Hadamard (HH) gates create superpositions, while **Controlled Phase (**PP) gates apply specific phase rotations based on the state of other qubits.
  5. Qubit Swapping: The "X-X" symbol at the far right of the circuit is a SWAP gate between q0q0​ and q2q2​. This is a standard final step in QFT to correct the bit order of the output.
  6. Final Output State: The bottom array is the resulting complex state vector. After the QFT, the original single state (∣101⟩∣101⟩) is spread across all eight possible outcomes (∣000⟩∣000⟩ through ∣111⟩∣111⟩) with specific complex amplitudes (magnitudes and phases).

3. Quantum Phase Estimation (QPE)

Quantum Phase Estimation is a powerful algorithm that serves as a central subroutine in many other complex algorithms.

Its purpose is to determine the eigenvalue of a given unitary operator U, which can be expressed in the form e^(2πiφ), where φ is the "phase."

QPE is designed to estimate this phase φ with high precision.

The algorithm uses two quantum registers: a "counting" register to store the phase and a "state" register prepared in an eigenvector of the operator.

It proceeds by applying a series of controlled unitary operations, followed by an inverse Quantum Fourier Transform on the counting register.

A final measurement of the counting register yields a binary approximation of the phase φ.

QPE is crucial for applications in quantum chemistry (finding molecular energy levels) and is a key component of Shor's algorithm.

Prerequisites: qiskit, qiskit-aer, numpy

Code Example:

This code implements QPE to find the phase of the Pauli Z gate corresponding to the eigenvector |1⟩. The expected phase is 0.5, which is 100 as a 3-bit binary fraction.

# Import necessary components
import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

# --- Function for inverse QFT ---
def qft_dagger(qc, n):
    """Builds an inverse QFT circuit on n qubits."""
    for qubit in range(n // 2):
        qc.swap(qubit, n - qubit - 1)
    for j in range(n):
        for m in range(j):
            qc.cp(-np.pi / (2**(j - m)), m, j)
        qc.h(j)

# --- Main QPE Circuit ---
# Use 3 qubits for the counting register and 1 for the state register
qc = QuantumCircuit(4, 3)

# --- Step 1: Prepare the Eigenvector ---
# We want to find the phase for the eigenvector |1> of the Z gate.
qc.x(3)
qc.barrier()

# --- Step 2: Superposition on Counting Register ---
qc.h(range(3))

# --- Step 3: Controlled Unitary Operations ---
# The unitary is the Z gate. We apply controlled-Z^(2^k).
qc.cz(0, 3)
qc.barrier()

# --- Step 4: Inverse QFT ---
qft_dagger(qc, 3)
qc.barrier()

# --- Step 5: Measurement ---
qc.measure(range(3), range(3))

# Simulate the circuit
simulator = AerSimulator()
compiled_circuit = transpile(qc, simulator)
result = simulator.run(compiled_circuit, shots=1024).result()
counts = result.get_counts()

# Print results
print("QPE Circuit for Z-gate with eigenvector |1>:")
print(qc)

Output

Explanation:

  1. This image shows a Quantum Phase Estimation circuit designed to find the phase of a Z-gate.
  2. The circuit uses four qubits, where the first three act as an estimation register and the last one is the target.
  3. An X-gate is used at the start to set the target qubit to the state of 1.
  4. Hadamard gates are applied to the first three qubits to create a state of superposition.
  5. Controlled operations perform phase kickback, encoding the phase of the Z-gate into the measurement qubits.
  6. The sequence of H-gates and negative phase gates represents an Inverse Quantum Fourier Transform.
  7. The final measurement results show a consistent binary output of 100.
  8. In a three-qubit system, the binary value 100 represents a phase of 0.5, which matches the expected behavior of a Z-gate.


4. Hamiltonian Mechanics

In quantum mechanics, the Hamiltonian (H) is an operator that represents the total energy of a quantum system.

It governs the time evolution of a quantum state through the Schrödinger equation.

The possible energy levels of the system are the eigenvalues of the Hamiltonian, and the stationary states are its eigenvectors.

The lowest possible energy level is called the ground state.

Hamiltonian simulation is a major application for quantum computers, allowing the study of complex molecules and materials that are intractable for classical simulation.

Many optimization problems can be mapped onto finding the ground state of a carefully constructed Hamiltonian, where the ground state encodes the optimal solution.

Prerequisites: qiskit

Code Example:

This code defines a simple two-qubit Hamiltonian, H = X⊗X + Z⊗Z, and uses a classical solver to find its ground state energy (lowest eigenvalue).

# Import necessary components
from qiskit.quantum_info import SparsePauliOp
from qiskit_algorithms import NumPyMinimumEigensolver
import numpy as np

# --- Define the Hamiltonian ---
# H = XX + ZZ
hamiltonian = SparsePauliOp.from_list([("XX", 1.0), ("ZZ", 1.0)])

# --- Use a classical exact solver to find the ground state ---
exact_solver = NumPyMinimumEigensolver()
result = exact_solver.compute_minimum_eigenvalue(hamiltonian)

ground_state_energy = result.eigenvalue.real
ground_state = result.eigenstate.to_dict()

# --- Print the Results ---
print(f"Hamiltonian H = XX + ZZ")
print(f"\nCalculated Ground State Energy (Lowest Eigenvalue): {ground_state_energy:.4f}")
print("\nCorresponding Ground State (Eigenvector):")
print(ground_state)

Output

Explanation:

  1. System Definition: The first line defines a quantum mechanical Hamiltonian, H = XX + ZZ, which describes the energy of a two-qubit system based on interactions in the X and Z bases.
  2. Minimum Energy: The second line states that the "Calculated Ground State Energy" is -2.0000, representing the lowest possible energy eigenvalue for this specific system.
  3. Quantum State Representation: The rest of the image shows the "Ground State (Eigenvector)," which describes the physical state of the qubits at that minimum energy level.
  4. Data Structure: The eigenvector is presented as a Python dictionary where the keys (like '00', '01') represent the computational basis states and the values are their corresponding complex probability amplitudes.
  5. Numerical Precision: The values are stored as np.complex128, a high-precision complex number format used in numerical libraries like NumPy.
  6. Negligible Components: The amplitudes for the |00> and |11> states are extremely small (on the order of 10^-17), meaning they have effectively zero probability in this ground state.
  7. Main Components: The ground state is almost entirely composed of a superposition of the |01> and |10> states.
  8. Physical Interpretation: Because the amplitudes for |01> and |10> have approximately the same magnitude but opposite signs (one is negative, one is positive), this result describes a specific type of entangled state known as a singlet state or one of the Bell states.


5. Eigenvalues & Eigenvectors

Eigenvalues and eigenvectors are fundamental concepts from linear algebra that are central to the mathematics of quantum mechanics.

For a given operator, an eigenvector is a vector that does not change its direction when the operator is applied, but is simply scaled by a factor.

This scalar factor is called the eigenvalue. The relationship is A|ψ⟩ = λ|ψ⟩.

In quantum mechanics, physical observables (like energy or spin) are represented by Hermitian operators.

The eigenvalues of a Hermitian operator are always real numbers and represent the only possible outcomes of a measurement of that observable.

The corresponding eigenvectors (or eigenstates) represent the quantum states that have a definite value for that observable.

Many powerful quantum algorithms, like QPE and VQE, are essentially sophisticated methods for finding the eigenvalues of very large and complex operators.

Prerequisites: qiskit, numpy

Code Example:

This code demonstrates the eigenvalue-eigenvector relationship for the Pauli-X gate. It prepares the eigenvector |−⟩ = (|0⟩ - |1⟩)/√2 and shows that applying the X gate results in -1 * |−⟩.

# Import necessary components
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# --- Prepare the Eigenvector |-> ---
# The state |-> is created by applying H and then Z to |0>.
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
initial_eigenvector = Statevector(qc)

# --- Apply the Operator (X-gate) ---
qc.x(0)
final_state = Statevector(qc)

# --- Verification ---
expected_final_state_data = -1 * initial_eigenvector.data

# --- Print Results ---
print("Testing the eigenvector |-> of the Pauli-X gate.")
print("\nInitial Eigenvector State |->:")
print(initial_eigenvector.draw('text'))
print("\nFinal State after applying X-gate:")
print(final_state.draw('text'))
print("\nExpected Final State (should be -|->):")
print(expected_final_state_data)
# This script tests that the state |-> is an eigenvector of the Pauli-X gate with eigenvalue -1.

Output:

Explanation:

This image shows a computer test verifying how a mathematical operation, called the Pauli-X gate, affects a specific quantum state.

  1. An eigenvector is a special state that, when acted upon by an operation, does not change into a different state but is only multiplied by a constant number.

  2. The text identifies the state labeled as |-> as the specific eigenvector being tested in this demonstration.

  3. The initial eigenvector is shown as an array of two complex numbers: approximately 0.707 and negative 0.707.

  4. When the Pauli-X gate is applied to this vector, it transforms the numbers into negative 0.707 and positive 0.707.

  5. An eigenvalue is the specific number or factor by which an eigenvector is multiplied during its transformation.

  6. The output shows that applying the gate is equivalent to multiplying the original vector by negative one.

  7. Therefore, the eigenvalue for this specific state and operation is negative one.

  8. The program compares the final calculated state with the expected result of minus one times the original state to ensure they are identical.

  9. The match between the results confirms that the state is a valid eigenvector with the correct predicted eigenvalue.


6. Quantum Annealing

Quantum annealing is a specialized paradigm of quantum computation designed for solving optimization problems.

It is distinct from the more common gate-based model of quantum computing.

The process encodes an optimization problem into a problem Hamiltonian, where the ground state corresponds to the optimal solution.

The system starts in the ground state of a simple initial Hamiltonian and is slowly (adiabatically) evolved to the final problem Hamiltonian.

According to the adiabatic theorem, if the evolution is slow enough, the system remains in the ground state, thus finding the solution.

The potential quantum advantage comes from quantum tunneling, which allows the system to pass through energy barriers that trap classical optimization algorithms.

Companies like D-Wave Systems build large-scale quantum annealers for specific optimization tasks.

Prerequisites: qiskit, qiskit-aer, qiskit-algorithms

Code Example:

This code uses the Quantum Approximate Optimization Algorithm (QAOA), a gate-based analogue to annealing, to solve a simple Max-Cut problem on a graph.

import numpy as np
from scipy.optimize import minimize
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import StatevectorEstimator, StatevectorSampler

# ---------------------------------------------------------
# STEP 1: Define the Hamiltonian
# ---------------------------------------------------------
print("=== Step 1: Defining the Hamiltonian ===")
# Edges for a triangle graph: (0,1), (1,2), (0,2)
hamiltonian = SparsePauliOp.from_list([
    ("ZZI", 1.0),  # Edge (0, 1)
    ("IZZ", 1.0),  # Edge (1, 2)
    ("ZIZ", 1.0)   # Edge (0, 2)
])
print(f"Hamiltonian: {hamiltonian}\n")

# ---------------------------------------------------------
# STEP 2: Define the QAOA Circuit Building Function
# ---------------------------------------------------------
def create_qaoa_circuit(params, reps, n_qubits=3):
    """
    Constructs the QAOA Ansatz manually.
    """
    qc = QuantumCircuit(n_qubits)
    
    # 1. Initialization: Equal Superposition
    qc.h(range(n_qubits))
    
    # Split params
    gammas = params[0::2]
    betas = params[1::2]
    
    # 2. Apply Layers
    for i in range(reps):
        gamma = gammas[i]
        beta = betas[i]
        
        # --- Cost Layer (RZZ) ---
        qc.rzz(2 * gamma, 0, 1) 
        qc.rzz(2 * gamma, 1, 2) 
        qc.rzz(2 * gamma, 0, 2) 
        
        # --- Mixer Layer (RX) ---
        for q in range(n_qubits):
            qc.rx(2 * beta, q)
            
    return qc

# ---------------------------------------------------------
# NEW STEP: Print the Circuit Once
# ---------------------------------------------------------
print("=== Visualizing the QAOA Circuit Structure ===")
# We define initial parameters just for visualization purposes here
init_params = [0.1, 0.1, 0.2, 0.2] 
reps = 2

# Build a sample circuit to print
demo_circuit = create_qaoa_circuit(init_params, reps)

# Print it
print(demo_circuit.draw(output='text'))
print("\n(Circuit printed above. Now proceeding to optimization...)\n")


# ---------------------------------------------------------
# STEP 3: Define the Objective Function
# ---------------------------------------------------------
estimator = StatevectorEstimator()

def objective_function(params):
    # Create circuit
    reps = len(params) // 2
    qc = create_qaoa_circuit(params, reps)
    
    # Run Estimator
    pub = (qc, hamiltonian)
    job = estimator.run([pub])
    result = job.result()[0]
    energy = result.data.evs
    
    return float(energy)

# ---------------------------------------------------------
# STEP 4: Run Classical Optimization
# ---------------------------------------------------------
print("=== Step 3 & 4: Running Classical Optimization Loop ===")
print(f"Initial parameters: {init_params}")
print("Optimizing...")

result = minimize(
    objective_function, 
    init_params, 
    method='COBYLA', 
    options={'maxiter': 50, 'tol': 1e-4}
)

optimal_params = result.x
min_energy = result.fun

print("\n--- Optimization Complete ---")
print(f"Optimal Parameters: {optimal_params}")
print(f"Minimum Energy Found: {min_energy:.4f}")

# ---------------------------------------------------------
# STEP 5: Retrieve and Analyze the Optimal State
# ---------------------------------------------------------
print("\n=== Step 5: Sampling the Optimal Circuit ===")

# 1. Build the circuit with the OPTIMAL parameters
optimal_circuit = create_qaoa_circuit(optimal_params, reps)
optimal_circuit.measure_all()

# 2. Sample
sampler = StatevectorSampler()
job = sampler.run([optimal_circuit], shots=1024)
result_sampler = job.result()[0]
counts = result_sampler.data.meas.get_counts()

# 3. Find winner
sorted_counts = sorted(counts.items(), key=lambda item: item[1], reverse=True)
most_likely_string = sorted_counts[0][0]

print("\nTop 3 Measured Bitstrings (Candidate Solutions):")
for bitstring, count in sorted_counts[:3]:
    print(f"State |{bitstring}> : {count} shots")

print("\n------------------------------")
print(f"Winner: {most_likely_string}")
print("------------------------------")

Output:

Explanation:

===================================================
QUANTUM MAX-CUT QAOA EXECUTION
===================================================

The image displays the text output of a program running the Quantum
Approximate Optimization Algorithm (QAOA). It solves the "Max-Cut"
problem for a triangle graph (3 nodes connected in a loop).

The execution flows from defining the physics equations to constructing
the circuit, optimizing it, and measuring the final result.

===================================================
COMPONENT LEGEND


[H] : Hadamard Gate (Superposition creator)
RZZ : Cost Gate (Encodes the problem constraints)
Rx : Mixer Gate (Allows exploration of states)
ZZI... : Pauli Strings (Mathematical description of edges)
Params : Gamma and Beta (Rotation angles tuned by the optimizer)

===================================================

STEP 1: DEFINING THE PROBLEM (The Hamiltonian)

Location: Top Section
Output: SparsePauliOp(['ZZI', 'IZZ', 'ZIZ']...)

The program defines the cost function (Hamiltonian) for a 3-node
triangle graph.

The terms (ZZI, IZZ, ZIZ) represent the three edges connecting
nodes 0-1, 1-2, and 0-2.

Logic Table (Energy Cost):

If connected nodes have SAME value (00 or 11) -> Energy +1 (Bad)

If connected nodes have DIFF value (01 or 10) -> Energy -1 (Good)

Goal: Find the state with the lowest possible total energy.

===================================================
STEP 2: THE CIRCUIT (The Ansatz)

Location: Middle Diagram (ASCII Art)
Structure: Input -> Init -> Cost Layer -> Mixer Layer -> Output

Initialization:
[H] gates on q_0, q_1, q_2 create an equal superposition of all
possible answers (000 to 111).

Cost Layer (RZZ Gates):
The vertical lines connecting qubits apply the problem logic.
They shift the phase of the quantum state based on the edges defined
in Step 1.

Mixer Layer (Rx Gates):
The boxes labeled "Rx" rotate the qubits. This allows the system
to change valid answers into other valid answers, preventing it
from getting stuck.

Repetition:
The pattern (Cost + Mixer) is repeated twice (Depth p=2) to increase
accuracy.

===================================================
STEP 3: OPTIMIZATION (The Hybrid Loop)

Location: Text block "Running Classical Optimization Loop"

A classical computer (using the COBYLA optimizer) takes control here.
It repeatedly runs the quantum circuit, adjusting the angle parameters
(gamma and beta) to find the lowest energy.

Result:

Minimum Energy Found: -1.0000

This is the mathematical limit for a triangle. You can cut 2 edges
(-2 energy) but the 3rd edge will always remain connected (+1 energy).
Total: -1.

===================================================
STEP 4: RESULT (Measuring the Solution)

Location: Bottom Section ("Sampling the Optimal Circuit")

Once the best angles are found, the program runs the circuit one last
time and measures the qubits 1024 times.

Winner: State |011>

Node 0: Group 0

Node 1: Group 1

Node 2: Group 1

Interpretation:

Edge (0-1): Cut (Diff values) -> Good

Edge (0-2): Cut (Diff values) -> Good

Edge (1-2): Not Cut (Same values) -> Frustrated

This represents a valid Max-Cut solution for a triangle.

===================================================
SUMMARY

1.We defined a triangle graph problem using math (Hamiltonian).

2.We built a circuit that alternates between "Problem Logic" and 2"Mixing".

3.A classical computer tuned the circuit knobs (angles).

4.We measured the result.

5.We found that the best way to cut a triangle is to separate one node
from the other two.

7. Quantum Error Correction

Quantum information is extremely fragile and susceptible to errors from environmental noise (decoherence) and imperfect hardware.

Quantum Error Correction (QEC) is a set of techniques designed to protect quantum information from these errors.

Unlike classical error correction, QEC cannot simply copy quantum states due to the no-cloning theorem.

Instead, QEC encodes the information of a single "logical qubit" into a highly entangled state of multiple "physical qubits."

This distributes the information non-locally, protecting it from local errors.

Syndrome measurements are then used to detect what error has occurred without measuring (and thus destroying) the logical state itself.

Based on the syndrome, a correction operation can be applied to restore the original logical state.

The threshold theorem proves that if the physical error rate is below a certain threshold, arbitrarily long and reliable quantum computations are possible.

Prerequisites: qiskit

Code Example:

This code implements the 3-qubit bit-flip code. It encodes a state, manually introduces an error, performs a syndrome measurement to detect it, and applies a conditional correction.

# Import necessary components
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit_aer import AerSimulator

# --- Setup ---
q = QuantumRegister(3, 'q')
anc = QuantumRegister(2, 'ancilla')
c = ClassicalRegister(2, 'syndrome')
qc = QuantumCircuit(q, anc, c)

# --- 1. State Preparation & Encoding ---
qc.h(q[0])
qc.cx(q[0], q[1])
qc.cx(q[0], q[2])
qc.barrier()

# --- 2. Introduce an Error ---
error_qubit = 1
qc.x(q[error_qubit])
qc.barrier()

# --- 3. Syndrome Measurement ---
qc.cx(q[0], anc[0])
qc.cx(q[1], anc[0])
qc.cx(q[1], anc[1])
qc.cx(q[2], anc[1])
qc.measure(anc[0], c[0])
qc.measure(anc[1], c[1])
qc.barrier()

# --- 4. Conditional Correction ---
with qc.if_test((c, int('10', 2))):
    qc.x(q[1])

# --- Verification (Optional) ---
qc.cx(q[0], q[1])
qc.cx(q[0], q[2])
qc.h(q[0])
final_measure = ClassicalRegister(1, 'final')
qc.add_register(final_measure)
qc.measure(q[0], final_measure[0])

# --- Simulation ---
simulator = AerSimulator()
result = simulator.run(transpile(qc, simulator), shots=100).result()
print("Bit-Flip Error Correction Circuit:")
print(qc)

Output:

Explanation:

===================================================
      QUANTUM BIT-FLIP ERROR CORRECTION CIRCUIT
===================================================

The image displays a quantum circuit designed to protect a single qubit of
information against "Bit-Flip" errors (where a |0> accidentally flips to
a |1> or vice versa).

The circuit flows from Left to Right and is divided into 5 distinct stages
separated by vertical dotted lines (barriers).

---------------------------------------------------
COMPONENT LEGEND
---------------------------------------------------
[H]   : Hadamard Gate (Superposition creator)
[X]   : Pauli-X Gate (Bit flip / NOT gate)
[M]   : Measurement
[•]   : Control Dot (Part of a CNOT gate)
[+]   : Target X (Part of a CNOT gate)
Lines : Quantum wires (top) and Classical wires (bottom)

---------------------------------------------------
STAGE 1: ENCODING (Protection)
---------------------------------------------------
Location: Far Left
Input: q_0 (Data), q_1 (Aux), q_2 (Aux)

1. q_0 applies an [H] gate (puts the qubit in superposition).
2. Two CNOT gates are applied:
   - Control: q_0 -> Target: q_1
   - Control: q_0 -> Target: q_2

Result: The state of q_0 is "copied" (entangled) onto q_1 and q_2.
This creates a logical qubit using 3 physical qubits.
State: alpha|000> + beta|111>

---------------------------------------------------
STAGE 2: NOISE SIMULATION ( The Error)
---------------------------------------------------
Location: Second column
Action: An [X] gate is placed on line q_1.

Explanation:
This stage intentionally introduces an error to test the circuit.
The [X] gate forces a bit-flip on the middle qubit (q_1).
The system is now in a corrupted state (e.g., |010> instead of |000>).

---------------------------------------------------
STAGE 3: SYNDROME MEASUREMENT (Detection)
---------------------------------------------------
Location: Middle Section (involving 'ancilla' lines)

This stage uses two extra qubits (ancilla_0, ancilla_1) to check
parity without destroying the data.

1. Parity Check 1 (ancilla_0):
   - CNOT from q_0 to ancilla_0
   - CNOT from q_1 to ancilla_0
   - Measures if q_0 and q_1 are the same or different.

2. Parity Check 2 (ancilla_1):
   - CNOT from q_1 to ancilla_1
   - CNOT from q_2 to ancilla_1
   - Measures if q_1 and q_2 are the same or different.

3. Measurement [M]:
   The ancilla qubits are measured into the classical register "syndrome".
   
   Logic Table:
   - If q_0 & q_1 differ -> ancilla_0 = 1
   - If q_1 & q_2 differ -> ancilla_1 = 1

---------------------------------------------------
STAGE 4: CORRECTION (Recovery)
---------------------------------------------------
Location: Fourth column (labeled with 'If-0', 'X', 'End-0')

Based on the measurement results (Syndrome), the classical computer
decides which qubit is broken.

- In this specific diagram, because the error was on q_1:
  - ancilla_0 detects a difference (between q_0 and q_1).
  - ancilla_1 detects a difference (between q_1 and q_2).
  
- The box labeled "If-0 ... X ... End-0" represents a classical
  condition (using the "0x2" input). It applies a correction [X] gate
  to q_1 to flip it back to the correct state.

---------------------------------------------------
STAGE 5: DECODING
---------------------------------------------------
Location: Far Right

Now that the error is fixed, the circuit reverses the encoding steps
to return the information to a single qubit.

1. CNOT from q_0 to q_1.
2. CNOT from q_0 to q_2.
3. [H] gate on q_0.
4. Final [M]easurement on q_0.

---------------------------------------------------
SUMMARY
---------------------------------------------------
1. We took a bit.
2. We spread it across 3 qubits.
3. We broke one (q_1) on purpose.
4. We used "spy" qubits (ancillas) to find which one broke.
5. We fixed it.
6. We successfully recovered the original data.

8. Grover's Algorithm

Grover's algorithm is a quantum search algorithm providing a provably optimal quadratic speedup for unstructured search.

For a database of N items, a classical search takes O(N) time on average, while Grover's algorithm takes only O(√N) time.

This speedup is significant for a wide range of problems that can be reduced to an unstructured search.

The algorithm works through a technique called amplitude amplification.

It begins by preparing all possible items in an equal superposition.

It then iteratively applies a "Grover iteration," which consists of two steps: an oracle that flips the phase of the target item, and a diffusion operator that inverts all amplitudes about their average.

Each iteration rotates the state vector closer to the marked state, amplifying its probability.

After approximately π/4 * √N iterations, a measurement will yield the correct answer with high probability.

Code Example:

This code implements Grover's algorithm to find the marked state |101⟩ in a 3-qubit search space (N=8).

It runs for the optimal number of iterations (~2).

# Import necessary components
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram

# --- Define the Oracle ---
# The oracle marks the state |101> by flipping its phase.
oracle = QuantumCircuit(3, name='Oracle (101)')
oracle.x(1) # We want to match |101>, so we flip the middle qubit for the CCZ
oracle.ccz(0, 1, 2)
oracle.x(1)
oracle_gate = oracle.to_gate()

# --- Define the Diffusion Operator ---
diffuser = QuantumCircuit(3, name='Diffuser')
diffuser.h([0, 1, 2])
diffuser.x([0, 1, 2])
diffuser.ccz(0, 1, 2)
diffuser.x([0, 1, 2])
diffuser.h([0, 1, 2])
diffuser_gate = diffuser.to_gate()

# --- Build the Main Grover's Circuit ---
qc = QuantumCircuit(3, 3)

# 1. Initial superposition
qc.h([0, 1, 2])
qc.barrier()

# 2. Apply Grover iterations (Oracle + Diffuser)
num_iterations = 2
for _ in range(num_iterations):
    qc.append(oracle_gate, [0, 1, 2])
    qc.append(diffuser_gate, [0, 1, 2])
    qc.barrier()

# 3. Measure the result
qc.measure([0, 1, 2], [0, 1, 2])

# Simulate the circuit
simulator = AerSimulator()
compiled_circuit = transpile(qc, simulator)
result = simulator.run(compiled_circuit, shots=1024).result()
counts = result.get_counts()

# Print results
print("Grover's Search Circuit for |101>:")
print(qc)

Output

Explanation

This is how Grover's Quantum Search Algorithm.

  1. Setup: The circuit uses three qubits (q_0, q_1, q_2). The three Hadamard (H) gates put all qubits into an equal superposition, meaning they represent every possible answer (000 to 111) at once.
  2. Iterations: The core of the algorithm consists of repeating the Oracle (which marks the correct answer, here 101) and the Diffuser (which amplifies the marked answer's probability). This circuit runs the pair twice.
  3. Result: The Counts show that after many runs, the target state '101' was measured 976 times, while all other wrong answers were found only a few times (4 to 8).
  4. Conclusion: Grover's algorithm successfully found the marked item much faster than a classical search could.

9. Variational Quantum Eigensolver (VQE)

The Variational Quantum Eigensolver (VQE) is a flagship hybrid quantum-classical algorithm designed for near-term, noisy quantum computers.

Its primary purpose is to find the ground state energy (lowest eigenvalue) of a given Hamiltonian.

VQE leverages the strengths of both quantum and classical processors in a synergistic loop.

The quantum computer prepares a parameterized trial state (an "ansatz") and measures its energy.

A classical optimizer uses this energy value to suggest new parameters for the ansatz to lower the energy.

This loop repeats until the energy converges to a minimum, which is the VQE's estimate of the ground state energy.

Because the quantum circuits can be relatively shallow, VQE is more resilient to noise, making it a leading candidate for achieving practical quantum advantage.

Prerequisites: qiskit, qiskit-aer, qiskit-algorithms, qiskit-nature

Code Example:

This code uses VQE to find the ground state energy of the Lithium Hydride (LiH) molecule, a common benchmark problem in quantum chemistry.

import numpy as np
from scipy.optimize import minimize
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit import Parameter
from qiskit.primitives import StatevectorEstimator

# ---------------------------------------------------------
# STEP 1: Define the Problem (The Hamiltonian)
# ---------------------------------------------------------
print("--- Step 1: Defining the Hamiltonian ---")
# We will solve for the ground state energy of a simple 2-qubit system.
# Hamiltonian: H = Z ^ Z + J * (X ^ I)
# This represents a simple Ising model interactions.

# 'ZZ' acts on qubit 0 and 1. 'XI' acts on qubit 0.
hamiltonian = SparsePauliOp.from_list([("ZZ", 1.0), ("XI", 1.0)])
print(f"Hamiltonian Operator:\n{hamiltonian}")

# Calculate the exact reference value using standard linear algebra (NumPy)
# This lets us check if our VQE actually works.
matrix = hamiltonian.to_matrix()
exact_eigenvalue = np.min(np.linalg.eigvalsh(matrix))
print(f"Target (Exact) Energy: {exact_eigenvalue:.4f}\n")


# ---------------------------------------------------------
# STEP 2: Create the Ansatz (Parameterized Circuit)
# ---------------------------------------------------------
print("--- Step 2: Creating the Ansatz Circuit ---")

# We need a circuit that can explore the Hilbert space.
# We will use a simple "Hardware Efficient" ansatz:
# 1. Rotations (RY) on both qubits to prepare superpositions.
# 2. Entanglement (CX) to correlate them.
# 3. Parameterized so the optimizer can tune it.

theta = Parameter('θ')
phi = Parameter('φ')

ansatz = QuantumCircuit(2)
ansatz.ry(theta, 0)  # Rotation on Qubit 0
ansatz.ry(phi, 1)    # Rotation on Qubit 1
ansatz.cx(0, 1)      # Entanglement

print("Ansatz Circuit Diagram:")
print(ansatz.draw(output='text'))
print(f"Parameters to optimize: {ansatz.parameters}\n")


# ---------------------------------------------------------
# STEP 3: Define the Cost Function
# ---------------------------------------------------------
print("--- Step 3: Defining the Cost Function (Expectation Value) ---")

# In Qiskit 1.0+, we use the 'Estimator' primitive.
# It replaces QuantumInstance. It takes a circuit and an observable
# and calculates <psi | H | psi>.
estimator = StatevectorEstimator()

def cost_function(params):
    """
    Accepts a list of parameters (angles), runs the circuit,
    and returns the expected energy.
    """
    # The Estimator expects inputs in a specific structure (pub = primitive unified bloc)
    # (circuit, observable, parameter_values)
    
    # 1. Bind the classical optimizer's values to the circuit parameters
    pub = (ansatz, hamiltonian, params)
    
    # 2. Run the job
    job = estimator.run([pub])
    
    # 3. Extract the result (energy)
    result = job.result()[0]
    energy = result.data.evs
    
    # Print the step for visualization
    # Note: We cast to float because the estimator returns a numpy array
    print(f"Evaluated params {params} -> Energy: {float(energy):.4f}")
    
    return energy


# ---------------------------------------------------------
# STEP 4: Run the Classical Optimization
# ---------------------------------------------------------
print("--- Step 4: Running Classical Optimization (VQE Loop) ---")

# Initial guess for the angles (e.g., [0, 0])
initial_params = [0.0, 0.0]

print(f"Starting optimization with initial guess: {initial_params}")
print("Optimizer: COBYLA (via scipy.optimize)\n")

# Use Scipy to minimize the cost function
result = minimize(
    cost_function, 
    initial_params, 
    method='COBYLA', 
    options={'maxiter': 30, 'tol': 1e-4}
)

print("\n--- Optimization Complete ---")
print(f"Success: {result.success}")
print(f"Optimal Parameters: {result.x}")
print(f"VQE Final Energy:   {result.fun:.4f}")
print(f"Exact Target Energy: {exact_eigenvalue:.4f}")

difference = abs(result.fun - exact_eigenvalue)
print(f"Accuracy Difference: {difference:.6f}")

Output

Explanation:

================================================================================
VQE (VARIATIONAL QUANTUM EIGENSOLVER)
================================================================================

The Python code which utilizes Qiskit to
simulate a quantum algorithm known as VQE. 
The goal is to find the lowest
possible energy (ground state) of a specific quantum system.

The program follows a hybrid approach:

QUANTUM PART: Prepares a state and measures energy.

CLASSICAL PART: Adjusts the parameters to minimize that energy.

================================================================================
PART 1: CODE BREAKDOWN
================================================================================

STEP 1: Define the Problem (The Hamiltonian)

The "Hamiltonian" represents the total energy equation of the system.
Code: hamiltonian = SparsePauliOp.from_list([("ZZ", 1.0), ("XI", 1.0)])

THE PHYSICS:

We have a 2-qubit system.

"ZZ" means Qubit 0 and Qubit 1 interact (like two magnets).

"XI" means Qubit 0 has an independent X-axis interaction.

The coefficients (1.0) determine the strength of these interactions.

THE REFERENCE:

The code calculates the "Target (Exact) Energy" using standard linear
algebra (numpy.linalg.eigvalsh). This gives us the correct answer
(-1.4142) so we can check if the Quantum Algorithm performs correctly.

================================================================================
STEP 2: Create the Ansatz (The Trial Circuit)
================================================================================


The "Ansatz" is our "best guess" for the shape of the quantum wave function.
It contains tunable knobs (Parameters theta and phi).

THE CIRCUIT:
Qubit 0: -- [ Ry(theta) ] -- o --
|
Qubit 1: -- [ Ry(phi) ] -- X --

Ry(theta/phi): Rotates the qubit state. These are the variables the
classical computer will optimize.

CX (The vertical line): Entangles the qubits. This allows the system
to represent complex quantum correlations.

================================================================================
STEP 3 & 4: The Optimization Loop
================================================================================


This is the heart of VQE. It is a feedback loop.

The "Estimator" (StatevectorEstimator):
This acts as the Quantum Computer. It takes the circuit and the
Hamiltonian, runs the simulation, and returns the Energy Expectation Value.

The "Optimizer" (COBYLA):
This is a classical algorithm from SciPy. It looks at the energy returned
by the Estimator and suggests new values for theta and phi to try and
make the energy lower.

Initial Guess: [0.0, 0.0]

Max Iterations: 20

================================================================================
PART 2: OUTPUT LOG ANALYSIS
================================================================================


The text output represents the "conversation" between the classical optimizer
and the quantum estimator.

A. Setup Information

Hamiltonian Operator:
Defined as ZZ and XI.

Target (Exact) Energy: -1.4142
This is negative Square Root of 2. This is the perfect score we want to hit.

Ansatz Circuit Diagram:
Visual confirmation that Qubit 0 and 1 have rotations and are connected.

B. The Optimization Logs (Evaluated Params)

The lines starting with "Evaluated params" show the optimizer trying to find
the bottom of the valley.

[Iteration 1] Params [0. 0.] -> Energy: 1.0000

The starting point. The energy is high (1.0).

[Iteration 2-5] Params [1. 0.], [0. 1.] ...

The COBYLA optimizer is tweaking one number at a time to see how the
energy changes (calculating the gradient/slope).

Notice the energy jumping around: 1.0 -> 1.38 -> -0.30 -> -1.32.

[Convergence]

Around the middle of the log, the parameters become more complex decimals:
[-0.26... -2.96...] -> Energy: -1.1598
[ 0.97... -2.22...] -> Energy: -1.4022

Towards the end, the energy stabilizes:
Energy: -1.4141
Energy: -1.4142

================================================================================
C. Final Results
================================================================================


Optimization Complete

Success: False

IMPORTANT NOTE *: Do not panic at "False".
COBYLA stopped because it hit the maximum iteration limit (20) we set in
the code, not because it "crashed." Because the result is so accurate,
the algorithm was working perfectly, it just wanted more time to be
mathematically precise to a tiny decimal place.

Optimal Parameters: [ 0.94055736 -2.35854718]
These are the specific angles (in radians) for the Ry gates that produce
the ground state.

VQE Final Energy: -1.4142
Exact Target Energy: -1.4142
Accuracy Difference: 0.000004

VERDICT: The algorithm worked perfectly. The VQE found the ground state
energy with an error of only 0.000004 compared to the exact mathematical
solution.


10. Quantum Supremacy / Advantage

Quantum supremacy, now often called quantum advantage, is the milestone where a quantum computer performs a task that is practically impossible for even the most powerful classical supercomputers.

This does not necessarily mean the task is useful; it is a scientific demonstration that quantum devices have crossed a crucial computational threshold.

The first prominent claim was by Google in 2019 with their Sycamore processor, using a task called Random Circuit Sampling.

This involves running a pseudo-random quantum circuit and sampling from its output probability distribution, a task that is exponentially difficult to simulate classically.

While the initial claims were debated, the experiment was a landmark engineering achievement, demonstrating high-fidelity control over a large-scale quantum system.

The focus of the field is now shifting from demonstrating this "supremacy" on contrived problems to achieving practical quantum advantage.

This means using a quantum computer to solve a real-world problem of scientific or commercial value faster, cheaper, or more accurately than any classical method.

Prerequisites: qiskit, qiskit-aer, numpy, random

Code Example: This code illustrates the concept by building a moderately sized random quantum circuit. As the number of qubits and depth increase, the classical simulation time grows exponentially.

# Import necessary components
import random
import time
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

# --- Circuit Parameters ---
num_qubits = 10
depth = 10

# --- Build a Random Quantum Circuit ---
qc = QuantumCircuit(num_qubits)
gates_1q = ['h', 's', 't', 'x']
gates_2q = ['cx', 'cz']

for d in range(depth):
    # Add a layer of single-qubit gates
    for i in range(num_qubits):
        gate = random.choice(gates_1q)
        getattr(qc, gate)(i)
    # Add a layer of two-qubit gates
    qubit_pairs = random.sample(range(num_qubits), num_qubits)
    for i in range(0, num_qubits, 2):
        q1, q2 = qubit_pairs[i], qubit_pairs[i+1]
        gate = random.choice(gates_2q)
        getattr(qc, gate)(q1, q2)
    qc.barrier()

qc.measure_all()

# --- Simulate the Circuit and Time it ---
simulator = AerSimulator(method='statevector')
print(f"Simulating a random circuit with {num_qubits} qubits and depth {depth}...")
start_time = time.time()
compiled_circuit = transpile(qc, simulator)
result = simulator.run(compiled_circuit, shots=10).result()
end_time = time.time()

# --- Print Results ---
print(f"Simulation finished in {end_time - start_time:.4f} seconds.")
print("\nSampled outcomes from the random circuit's distribution:")
print(result.get_counts())

This is perhaps the most lame example of Quantum Advantage ever, but it is included here for completeness and because the company that published it was Google.

Output:

Simulating a random circuit with 10 qubits and depth 10...
Simulation finished in 0.9212 seconds.

Sampled outcomes from the random circuit's distribution:
{'1110100000': 1, '1110111001': 1, '0001000001': 1, '0101111111': 1, '1101111011': 1, 
'0100101100': 1, '1011010100': 1, '0100110110': 1, '0110110111': 1, '0011110000': 1}

I promise to cover the latest implementation of Quantum Advantage in another article.

Explanation:

===============================================================================
GOOGLE SYCAMORE & QUANTUM SUPREMACY (2019)
===============================================================================
           

In October 2019, Google claimed "Quantum Supremacy" using their 53-qubit
Sycamore processor. They demonstrated that their quantum chip could perform a
specific calculation in 200 seconds that would take the world's fastest
supercomputer (IBM's Summit) approximately 10,000 years to complete.

The code you provided is a miniature, classical simulation of that exact
calculation.

The problem they solved is called "Random Circuit Sampling."

================================================================================
THE CONCEPT: RANDOM CIRCUIT SAMPLING (RCS)
================================================================================

The goal wasn't to solve a useful problem (like factoring numbers or designing
drugs). The goal was to pick a task that is incredibly hard for a classical
computer but native to a quantum computer.

Create a random chaotic circuit.

Run it.

Measure the result (a string of 0s and 1s).

Do statistics on the output to prove it matches quantum probability distributions.

Below is the mapping of your Python code to the 2019 Experiment.

================================================================================
PART 1: DEFINING THE COMPLEXITY
================================================================================

Code:
num_qubits = 10
depth = 10

THE 2019 REALITY:
In your code, you use 10 qubits. This is trivial for a laptop.
Google's Sycamore used 53 Qubits.

Every qubit you add doubles the memory required for a classical computer to
simulate the state.

10 Qubits = 16 Kilobytes (Your Code)

53 Qubits = 9 Petabytes (The limit of classical supercomputers)

"Depth" refers to the number of operation cycles. The deeper the circuit,
the more entangled and complex the quantum state becomes.

================================================================================
PART 2: THE "SCRAMBLE" (BUILDING THE CIRCUIT)
================================================================================

Code:
gates_1q = ['h', 's', 't', 'x']
gates_2q = ['cx', 'cz']
...
gate = random.choice(gates_1q)
...
qubit_pairs = random.sample(...)

================================================================================
THE 2019 REALITY:
================================================================================
This loop generates the "Random Circuit."

Google applied a random sequence of gates to their qubits.

Single-qubit gates (like X, Y, W) mix the probability of 0 and 1.

Two-qubit gates (like CX or Sycamore's specific coupler gate) entangle
the qubits.

By doing this randomly, they created a "Speckle Pattern." There is no
structure to exploit. A classical computer cannot use shortcuts; it must
calculate the entire probability vector (2 to the power of 53 possibilities)
to know what the answer should look like.

Your code does the exact same thing: it creates a chaotic, unstructured
quantum program.

================================================================================
PART 3: THE EXECUTION (SIMULATION VS. HARDWARE)
================================================================================

Code:
simulator = AerSimulator(method='statevector')
result = simulator.run(compiled_circuit, shots=10).result()

THE 2019 REALITY:
Here is where the paths diverge.

A. YOUR CODE (CLASSICAL SIMULATION):
You are using 'AerSimulator' with the 'statevector' method. Your CPU is
doing matrix multiplication to calculate the exact probability of every
outcome.
- For 10 qubits, this takes milliseconds.
- For 53 qubits, this calculation is technically impossible on most
machines due to RAM limitations.

B. SYCAMORE (QUANTUM EXECUTION):
Google did not "calculate" the result. They simply ran the circuit on
the chip. The laws of physics performed the matrix multiplication
instantly in nature.

================================================================================
PART 4: THE RESULT (SUPREMACY)
================================================================================

Code:
print(f"Simulation finished in {end_time - start_time:.4f} seconds.")

THE COMPARISON:

[Your Python Script]
Input: 10 Qubits
Time: 0.05 seconds
Winner: Your Laptop.

[The Sycamore Experiment]
Input: 53 Qubits
Time (Quantum): 200 Seconds
Time (Classical): 10,000 Years (Estimated by Google)
Winner: Quantum Computer.

The "Supremacy" moment is defined by the gap between the execution time
of the quantum chip and the simulation time of the classical computer.

================================================================================
SUMMARY OF OUTPUT
================================================================================

Sampled outcomes from the random circuit's distribution:
{'1010010110': 1, '0011010001': 1, ...}

This dictionary of bitstrings is the "proof."

In the 2019 experiment, Google collected millions of these strings. Because
quantum interference makes some strings slightly more likely than others
(even in a random circuit), they analyzed the statistical distribution of
these strings. The fact that the strings followed the predicted "Porter-Thomas"
distribution proved that the chip was truly performing quantum operations
and not just outputting random noise.

Epilogue: The Hypothetical Future of Quantum Computing by 2035

By 2035, the era of noisy quantum devices has been relegated to history books, replaced by a world where fault-tolerant logical qubits are the standard.

These systems are no longer housed in isolated laboratories but are the backbone of global infrastructure, capable of maintaining coherence across massive modular arrays for weeks at a time.

This stability has enabled the execution of deep-space and sub-atomic simulations that have effectively transformed the quantum computer into a universal engine for physical discovery.

The most profound impact on society has been the total reversal of the climate crisis through quantum-enabled chemistry.

Humans have successfully designed and deployed catalyst materials that can pull carbon dioxide and methane directly from the atmosphere at a fraction of the previous energy cost.

This breakthrough has moved global civilization from a state of existential dread toward a new era of planetary engineering, where the climate is actively managed and restored to pre-industrial levels.

Medicine has transitioned into a hyper-personalized model where the concept of a side effect is virtually nonexistent.

Quantum simulators can model a patient's entire biological response to a drug at the atomic level before a single dose is ever prescribed.

This has led to the total eradication of most hereditary diseases and has extended the healthy human lifespan significantly by allowing for the precise repair of cellular aging and DNA degradation.

The global economy has undergone a structural shift as quantum-AI hybrids have optimized resource distribution to near-perfect efficiency.

These systems can manage global supply chains, energy grids, and agricultural cycles in real-time, virtually eliminating waste and scarcity in many parts of the world.

This has forced a fundamental rethink of human labor and value, as society moves toward a post-scarcity model where human creativity and interpersonal care become the primary currencies.

Security and privacy have been completely reinvented following the widespread adoption of the Quantum Internet.

After the traditional internet's security was compromised by early large-scale quantum processors, a new unhackable network based on entanglement was constructed.

This has resulted in a world where digital privacy is guaranteed by the laws of physics, making identity theft and mass surveillance mathematically impossible, thereby restoring trust in global digital institutions.

On a philosophical level, the ability to observe and simulate the probabilistic nature of the universe has changed how humans view reality itself.

Education and art have evolved to incorporate quantum intuition, where children are taught to think in terms of superpositions and probabilities rather than simple binary outcomes.

This cognitive shift has fostered a more nuanced and empathetic global culture that values complexity and diversity as fundamental features of the cosmic fabric.

Space exploration has accelerated as quantum optimization has solved the long-standing hurdles of nuclear thermal propulsion and radiation shielding.

Humans have established thriving research colonies on the moon and Mars, with quantum-designed life support systems that function with 100 percent recycling efficiency.

This expansion has given humanity a new perspective on Earth, viewing it not as an isolated cradle but as the vital center of a burgeoning multi-planetary society.

References

  1. Nielsen, M. A., & Chuang, I. L. (2010). Quantum Computation and Quantum Information: 10th Anniversary Edition. Cambridge University Press. (The foundational textbook of the field).
  2. Qiskit Textbook. IBM. An open-source online textbook that provides an interactive introduction to quantum computing. https://qiskit.org/textbook/content/ch-ex/
  3. Preskill, J. (1998). Lecture Notes for Physics 229: Quantum Information and Computation. California Institute of Technology. http://www.theory.caltech.edu/~preskill/ph229/
  4. Arute, F., Arya, K., Babbush, R. et al. (2019). Quantum supremacy using a programmable superconducting processor. Nature 574, 505–510. https://www.nature.com/articles/s41586-019-1666-5
  5. Quanta Magazine. An editorially independent online publication of the Simons Foundation that provides in-depth coverage of quantum computing developments. https://www.quantamagazine.org/tag/quantum-computing/
  6. arXiv Quantum Physics Archive. A repository for pre-print academic papers, where most new research in the field is published. https://arxiv.org/archive/quant-ph

All images were AI-generated by the author with NightCafe Studio, available here: https://creator.nightcafe.studio/explore

Google AI Studio was used in this article, available here: https://aistudio.google.com/


Written by thomascherickal | The Digital Futurist. The | Gen AI | Agents | Blockchain | Quantum | Mastery Playbook. Subscribe!
Published by HackerNoon on 2025/12/31