1. Problem Definition: Prime Factorization
The problem to be solved is the factorization of a large semi-prime number N into its prime factors. In this experiment, we factor N = 143, which is the product of two prime numbers:
N = 11 * 13.
2. Introduction to Shor’s Algorithm
Shor's algorithm finds the prime factors of an integer N by reducing the problem to finding the order r of a randomly chosen number a (such that 1 < a < N and a is coprime to N). The order r is the smallest integer such that:
a^r ≡ 1 (mod N).
Once the period r is found, we use it to compute the potential factors of N:
Factors of N = gcd(a^(r/2) −1, N) and gcd(a^(r/2) +1, N).
3. Classical Preprocessing
Before we can factor N using quantum computing, we choose a number a such that:
1 < a < N and gcd(a, N) = 1.
In this experiment, we choose a = 2, which is coprime to N = 143.
4. Quantum Circuit Construction
We construct a quantum circuit using two sets of qubits:
Counting qubits that are used for quantum phase estimation.
Qubits representing the modular exponentiation result that are used to hold the modular exponentiation values.
For N= 143, we use 8 counting qubits and log_2(N) = 8 additional qubits.
Hadamard Transform
Apply Hadamard gates H to the counting qubits to create an equal superposition over all possible states:
H∣0⟩ = 1/sqrt(2) * (∣0⟩ + ∣1⟩),
so that the state of the system becomes:
1/(sqrt(2^n)) * (2^n) − 1 ∑ x = 0 ∣x⟩.
Modular Exponentiation
Apply the modular exponentiation operation, which raises a = 2 to successive powers, modulo N = 143:
a^k mod N.
This operation is controlled by the counting qubits and updates the modular exponentiation qubits.
Inverse Quantum Fourier Transform (QFT)
After applying modular exponentiation, we use the Inverse Quantum Fourier Transform (QFT) to extract the period r. The QFT transforms the basis states back into the computational basis, measuring the period in the counting qubits.
The QFT for n qubits is defined by the transformation:
QFT ∣x⟩ = 1/(sqrt(2^n)) * (2^n) − 1 ∑ y = 0 (e^(2πixy/2^n) ∣y⟩)
This operation gives the periodicity information in the final state.
Measurement
Measure the state of the counting qubits to get a bitstring representing the outcome of the quantum phase estimation.
6. Saving and Plotting Results
The quantum measurement data is stored in a JSON, including the bitstring outcomes and their associated probabilities. A histogram of the results is also plotted.
Code:
# imports
import numpy as np
import json
import matplotlib.pyplot as plt
from fractions import Fraction
import logging
from qiskit import QuantumCircuit, transpile
from qiskit.circuit.library import QFT
from qiskit.visualization import plot_histogram
from qiskit_ibm_runtime import QiskitRuntimeService, Session, SamplerV2
# Load IBMQ Account
service = QiskitRuntimeService(
channel='ibm_quantum',
instance='ibm-q/open/main',
token='YOUR_IBMQ_KEY_O-`'
)
# Set up ibm_osaka backend
backend_name = 'ibm_brisbane'
backend = service.backend(backend_name)
# Logging setup
logging.basicConfig(level=logging. INFO)
logger = logging.getLogger(__name__)
# Large semi-prime number to factor using Shor's algorithm
N = 143 # 143 = 11 * 13
a = 2 # A coprime integer to N
# Function to create the modular exponentiation circuit
def c_amod15(a, power, n):
"""Controlled multiplication by a mod N."""
U = QuantumCircuit(n)
for iteration in range(power):
for qubit in range(n):
U. cx(qubit, (qubit + 1) % n)
U = U. to_gate()
U. name = f"{a}^{power} mod {N}"
c_U = U.control()
return c_U
# Create the quantum circuit for Shor's algorithm
def create_shor_circuit(N, a):
"""Creates a quantum circuit for Shor's algorithm to factor N."""
n_count = 8 # Number of counting qubits
n_qubits = int(np.ceil(np.log2(N))) # Minimum number of qubits to represent N
qc = QuantumCircuit(n_count + n_qubits, n_count)
# Apply Hadamard gates to counting qubits
for q in range(n_count):
qc.h(q)
# Initialize the qubits representing the number 1
qc.x(n_count + n_qubits - 1)
# Apply controlled modular exponentiation gates
for q in range(n_count):
exponent = 2 ** q
c_U = c_amod15(a, exponent, n_qubits)
qc.append(c_U, [q] + [i + n_count for i in range(n_qubits)])
# Apply inverse QFT to counting qubits
qc.append(QFT(n_count, inverse=True, do_swaps=False).to_gate(), range(n_count))
# Measure counting qubits
qc.measure(range(n_count), range(n_count))
return qc
# Generate the Shor's algorithm circuit for N
qc_shor = create_shor_circuit(N, a)
# Transpile the circuit
transpiled_qc = transpile(qc_shor, backend=backend, optimization_level=3)
# Initialize session and sampler
with Session(service=service, backend=backend) as session:
sampler = SamplerV2(session=session)
# Run the circuit
logger. info("Running Shor's algorithm on ibm_osaka backend")
job = sampler. run([transpiled_qc], shots=8192)
# Wait for the job to complete
job.wait_for_final_state()
# Retrieve the job result after the job has completed
job_result = job.result()
# Retrieve the classical register name
classical_register = qc_shor.cregs[0].name
# Extract counts for the first (and only) result
counts = job_result[0].data[classical_register].get_counts()
# Log the results
logger. info(f"Resulting counts: {counts}")
# Analyze the measurement results to determine the period (r) of modular exponentiation
def analyze_shor_results(counts, n_count):
"""Analyzes the results from Shor's algorithm to extract the period r."""
# Get the most frequent measurement outcomes
measured_phases = []
for output in counts:
decimal = int(output, 2)
phase = decimal / (2 ** n_count)
measured_phases.append(phase)
# Find the best approximation for each measured phase
fractions = [Fraction(phase).limit_denominator(N) for phase in measured_phases]
rs = [frac.denominator for frac in fractions]
# Filter out trivial periods
r_candidates = [r for r in rs if r > 1 and r % 2 == 0]
if not r_candidates:
logger.warning("No valid periods found.")
return None
# Return the smallest valid period
r = min(r_candidates)
return r
# Extract period r from the results
n_count = 8 # Same as the number of counting qubits used
r = analyze_shor_results(counts, n_count)
# Use the period r to compute the factors of N
def compute_factors(N, a, r):
"""Computes the potential factors of N using the period r."""
if r is None:
return None
# Check if r is even and compute factors
if r % 2 != 0:
logger.warning("Period r is not even. Cannot compute factors.")
return None
# Compute the possible factors
plus_factor = np.gcd(a ** (r // 2) + 1, N)
minus_factor = np.gcd(a ** (r // 2) - 1, N)
# Check for non-trivial factors
if plus_factor not in [1, N]:
return plus_factor, N // plus_factor
elif minus_factor not in [1, N]:
return minus_factor, N // minus_factor
else:
logger.warning("Non-trivial factors not found.")
return None
factors = compute_factors(N, a, r)
if factors:
logger. info(f"Factors of {N} are {factors[0]} and {factors[1]}")
else:
logger.warning("No non-trivial factors found.")
# Save the results to JSON
results_data = {
'counts': counts,
'N': N,
'a': a,
'r': r if r else "No valid period found",
'factors': factors if factors else "No non-trivial factors found"
}
file_path = '/Users/Documents/ShorsRiemannResult_0.json'
with open(file_path, 'w') as file:
json.dump(results_data, file, indent=4)
# Plot the histogram
plot_histogram(counts)
plt. show()
logger. info("Experiment complete.")
# End.
////////////////////////////////////////////////////////////////////
Code for all visualizations from Run Data
# imports
import matplotlib.pyplot as plt
import json
from collections import Counter
# Load the JSON
with open('/Users/Documents/ShorsRiemannResult_0.json') as f:
data = json.load(f)
counts = data['counts']
# Convert counts data to a list of tuples for sorting
sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
# Plot Top 10 Frequent Measurement Outcomes
top_counts = sorted_counts[:10] # Top 10 most frequent outcomes
bitstrings = [x[0] for x in top_counts]
frequencies = [x[1] for x in top_counts]
plt.figure(figsize=(10, 6))
plt. bar(bitstrings, frequencies)
plt.title('Top 10 Most Frequent Measurement Outcomes in Shor’s Algorithm')
plt.xlabel('Bitstring (Measurement Outcome)')
plt.ylabel('Frequency')
plt.xticks(rotation=45)
plt.tight_layout()
plt. show()
# Plot Full Distribution of Measurement Counts
all_bitstrings = [x[0] for x in sorted_counts]
all_frequencies = [x[1] for x in sorted_counts]
plt.figure(figsize=(14, 8))
plt. bar(all_bitstrings, all_frequencies)
plt.title('Distribution of Measurement Outcomes in Shor’s Algorithm')
plt.xlabel('Bitstring (Measurement Outcome)')
plt.ylabel('Frequency')
plt.xticks(rotation=90)
plt.tight_layout()
plt. show()
//////////////////////////////////////////////////////
# imports
import seaborn as sns
import matplotlib.pyplot as plt
import json
import numpy as np
from sklearn.cluster import KMeans
# Load the JSON
file_path = '/Users/Documents/ShorsRiemannResult_0.json'
with open(file_path) as f:
data = json.load(f)
counts = data['counts']
# Convert counts data to a list of tuples for sorting
sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
bitstrings = [x[0] for x in sorted_counts]
frequencies = [x[1] for x in sorted_counts]
# Heatmap of Bitstring Frequencies
plt.figure(figsize=(14, 6))
data_heatmap = np.array(frequencies).reshape(1, len(frequencies))
sns.heatmap(data_heatmap, cmap="Blues", annot=False, cbar=True, xticklabels=bitstrings, yticklabels=False)
plt.title('Heatmap of Bitstring Frequencies in Shor\'s Algorithm')
plt.xticks(rotation=90)
plt.tight_layout()
plt. show()
# Phase Distribution Plot
# Convert bitstrings to decimals
decimals = [int(bs, 2) for bs in bitstrings]
phases = [d / 256 for d in decimals]
# Harmonic Frequency Analysis
# Compute harmonic frequencies (multiples of 1/r)
r = 4
harmonics = [m/r for m in range(1, 6)]
# Overlay these harmonics on the phase distribution
plt.figure(figsize=(10, 6))
plt.hist(phases, bins=50, color='green', alpha=0.7, label='Phases')
for harmonic in harmonics:
plt.axvline(harmonic, color='red', linestyle='--', label=f'Harmonic {harmonic:.2f}')
plt.title('Harmonic Frequency Analysis of Shor\'s Algorithm')
plt.xlabel('Phase')
plt.ylabel('Frequency')
plt.legend()
plt. show()
# Clustering of Similar Bitstrings
# Convert bitstrings to vectors of 0s and 1s
bit_vectors = np.array([[int(bit) for bit in bitstring] for bitstring in bitstrings])
# Perform clustering (e.g., 4 clusters)
kmeans = KMeans(n_clusters=4)
kmeans. fit(bit_vectors)
labels = kmeans.labels_
# Plot the clustering results
plt.figure(figsize=(10, 6))
plt.scatter(range(len(bitstrings)), frequencies, c=labels, cmap='viridis')
plt.title('Clustering of Bitstring Frequencies in Shor\'s Algorithm')
plt.xlabel('Bitstring Index')
plt.ylabel('Frequency')
plt.tight_layout()
plt. show()
# Error Distribution Plot
ideal_phases = np.linspace(0, 1, len(phases))
errors = np.abs(np.array(phases) - ideal_phases)
plt.figure(figsize=(10, 6))
plt.hist(errors, bins=50, color='red')
plt.title('Error Distribution between Detected Phases and Ideal Phases')
plt.xlabel('Error')
plt.ylabel('Frequency')
plt.tight_layout()
plt. show()
# Phase vs. Bitstring Index Plot
plt.figure(figsize=(10, 6))
plt.scatter(range(len(bitstrings)), phases, color='purple')
plt.title('Phase vs Bitstring Index')
plt.xlabel('Bitstring Index')
plt.ylabel('Phase')
plt.tight_layout()
plt. show()
///////////////////////////////////////////////////////
# imports
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy.fftpack import fft
import json
# Load the JSON
file_path = '/Users/Documents/ShorsRiemannResult_0.json'
with open(file_path) as f:
data = json.load(f)
counts = data['counts']
# Convert counts data to a list of tuples for sorting
sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
bitstrings = [x[0] for x in sorted_counts]
frequencies = [x[1] for x in sorted_counts]
# Convert bitstrings to decimals and calculate phases
decimals = [int(bs, 2) for bs in bitstrings]
phases = [d / 256 for d in decimals]
# Noise Analysis and Error Patterns
# Calculate errors between phases and ideal phases
ideal_phases = np.linspace(0, 1, len(phases))
errors = np.abs(np.array(phases) - ideal_phases)
# Boxplot of Errors Grouped by Bitstring Index
plt.figure(figsize=(10, 6))
sns.boxplot(x=np.arange(len(errors)), y=errors)
plt.title('Error Grouped by Bitstring Index')
plt.xlabel('Bitstring Index')
plt.ylabel('Error')
plt.xticks(rotation=45)
plt.tight_layout()
plt. show()
# Phase Differences Heatmap
# Calculate pairwise phase differences
phase_diff_matrix = np.abs(np.subtract.outer(phases, phases))
plt.figure(figsize=(10, 6))
sns.heatmap(phase_diff_matrix, cmap="coolwarm", annot=False)
plt.title('Phase Differences Heatmap')
plt.xlabel('Phase Index')
plt.ylabel('Phase Index')
plt.tight_layout()
plt. show()
# Cross-Bitstring Correlation
# Convert bitstrings to vectors of 0s and 1s
bit_vectors = np.array([[int(bit) for bit in bitstring] for bitstring in bitstrings])
# Cross-correlation matrix for bitstrings
bit_corr_matrix = np.corrcoef(bit_vectors)
plt.figure(figsize=(10, 6))
sns.heatmap(bit_corr_matrix, cmap="coolwarm", annot=False)
plt.title('Cross-Bitstring Correlation Heatmap')
plt.tight_layout()
plt. show()
# Eigenvalue Distribution (Quantum Phase Estimation)
# Histogram of estimated eigenvalues derived from phases.
eigenvalues = [np.exp(2j * np.pi * phase) for phase in phases]
# Plot the real part of eigenvalues
plt.figure(figsize=(10, 6))
plt.hist([np.real(ev) for ev in eigenvalues], bins=50, color='green')
plt.title('Eigenvalue Distribution from Quantum Phase Estimation')
plt.xlabel('Real Part of Eigenvalue')
plt.ylabel('Frequency')
plt.tight_layout()
plt. show()
# Clustering of Phases
phase_data = np.array(phases).reshape(-1, 1)
# Apply k-means clustering with 3 clusters
kmeans = KMeans(n_clusters=3)
kmeans. fit(phase_data)
labels = kmeans.labels_
# Scatter plot of phases colored by cluster
plt.figure(figsize=(10, 6))
plt.scatter(np.arange(len(phases)), phases, c=labels, cmap='viridis')
plt.title('Clustering of Phases in Shor\'s Algorithm')
plt.xlabel('Index')
plt.ylabel('Phase')
plt.tight_layout()
plt. show()
# End.