Spaces:
Running
Running
File size: 8,123 Bytes
227c43a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
"""
High-Fidelity Quantum Phase Estimation Simulation using Google Cirq with Detailed Math Logging.
Quantum Phase Estimation (QPE) is a quantum algorithm used to estimate the eigenphase of an
eigenvector of a unitary operator. It is a critical subroutine in many quantum algorithms including
Shor's algorithm and HHL algorithm for linear systems.
"""
import cirq
import numpy as np
import math
from cirq.contrib.svg import circuit_to_svg
def inverse_qft_circuit(qubits):
"""
Constructs a circuit that performs the inverse Quantum Fourier Transform (QFT†).
Args:
qubits: List of qubits
Returns:
A cirq.Circuit implementing the inverse QFT
"""
n = len(qubits)
circuit = cirq.Circuit()
# Process qubits in reverse order for inverse QFT
for i in range(n-1, -1, -1):
# Apply H gate
circuit.append(cirq.H(qubits[i]))
# Apply controlled phase rotations with conjugated phases
for j in range(i):
k = i - j
# Phase rotation by -2π/2^k
circuit.append(cirq.CZPowGate(exponent=-1/(2**(k))).on(qubits[j], qubits[i]))
return circuit
def controlled_unitary(control, target, phase_angle):
"""
Creates a controlled unitary operation for a given phase angle.
Args:
control: Control qubit
target: Target qubit
phase_angle: Phase angle (in radians)
Returns:
A cirq.Circuit implementing the controlled unitary
"""
# For simplicity, we use controlled phase rotations
# In a real QPE, this would be a controlled version of the unitary operator
return cirq.Circuit([
cirq.CZPowGate(exponent=phase_angle/(2*math.pi)).on(control, target)
])
def phase_estimation_circuit(phase_qubits, target_qubit, phase_angle, precision_bits):
"""
Constructs a circuit that performs Quantum Phase Estimation.
Args:
phase_qubits: Qubits used for phase estimation
target_qubit: Qubit that is an eigenstate of the unitary operator
phase_angle: True phase angle to estimate (in radians)
precision_bits: Number of bits of precision
Returns:
A cirq.Circuit implementing QPE
"""
n = len(phase_qubits)
circuit = cirq.Circuit()
# Step 1: Initialize target qubit to the eigenstate of the unitary
# For a phase gate, |1⟩ is an eigenstate
circuit.append(cirq.X(target_qubit))
# Step 2: Apply Hadamard gates to create superposition of phase qubits
circuit.append(cirq.H.on_each(*phase_qubits))
# Step 3: Apply controlled unitary operations
for i, qubit in enumerate(phase_qubits):
# Apply U^(2^i) controlled by the i-th qubit
power = 2**(n-i-1) # Powers decrease from most significant to least
for _ in range(power):
circuit.append(controlled_unitary(qubit, target_qubit, phase_angle))
# Step 4: Apply inverse QFT to the phase register
circuit.append(inverse_qft_circuit(phase_qubits))
return circuit
def add_noise(circuit, noise_prob):
"""
Adds realistic quantum noise to the circuit.
Args:
circuit: A Cirq circuit
noise_prob: Probability of depolarizing noise
Returns:
A circuit with added noise operations
"""
if noise_prob <= 0:
return circuit
noisy_ops = []
for op in circuit.all_operations():
noisy_ops.append(op)
for q in op.qubits:
noisy_ops.append(cirq.DepolarizingChannel(noise_prob).on(q))
return cirq.Circuit(noisy_ops)
def binary_to_phase(binary_str):
"""
Converts a binary string to a phase between 0 and 1.
Args:
binary_str: A string of '0's and '1's
Returns:
A float representing the phase
"""
if not binary_str:
return 0.0
return sum(int(bit) * 2**(-i-1) for i, bit in enumerate(binary_str))
def phase_to_binary(phase, precision_bits):
"""
Converts a phase between 0 and 1 to a binary string.
Args:
phase: A float between 0 and 1
precision_bits: Number of binary digits to include
Returns:
A binary string representation
"""
binary = ""
for i in range(precision_bits):
phase *= 2
bit = int(phase)
binary += str(bit)
phase -= bit
return binary
def run_phase_estimation(precision_bits=3, target_phase=0.125, noise_prob=0.0):
"""
Runs the Quantum Phase Estimation algorithm.
Args:
precision_bits: Number of bits of precision
target_phase: True phase to estimate (between 0 and 1)
noise_prob: Probability of depolarizing noise
Returns:
Dictionary with QPE results and visualization
"""
log = []
log.append("=== Quantum Phase Estimation Simulation ===")
# Validate target phase
target_phase = max(0.0, min(1.0, target_phase))
phase_angle = 2 * math.pi * target_phase
log.append(f"Target phase: {target_phase} (fraction of 2π)")
log.append(f"Phase angle: {phase_angle} radians")
# Create qubits
phase_qubits = [cirq.NamedQubit(f'p{i}') for i in range(precision_bits)]
target_qubit = cirq.NamedQubit('t')
# Create circuit
circuit = phase_estimation_circuit(phase_qubits, target_qubit, phase_angle, precision_bits)
log.append(f"Created QPE circuit with {precision_bits} precision qubits")
# Add noise if specified
if noise_prob > 0:
circuit = add_noise(circuit, noise_prob)
log.append(f"Added noise with probability {noise_prob}")
# Add measurements
measure_circuit = cirq.Circuit()
measure_circuit.append(cirq.measure(*phase_qubits, key='phase'))
# Combine circuits
full_circuit = circuit + measure_circuit
# Run the circuit
simulator = cirq.Simulator()
result = simulator.run(full_circuit, repetitions=1)
# Process the measurement results
measurements = result.measurements['phase'][0]
measured_state = ''.join([str(bit) for bit in measurements])
# Convert binary result to phase
estimated_phase = binary_to_phase(measured_state)
phase_error = abs(estimated_phase - target_phase)
log.append(f"Measured state: |{measured_state}⟩")
log.append(f"Estimated phase: {estimated_phase}")
log.append(f"Absolute error: {phase_error}")
# Generate circuit SVG for visualization
circuit_svg = circuit_to_svg(full_circuit)
# Theoretical analysis
expected_accuracy = 1 / (2**precision_bits)
log.append(f"\nTheoretical Notes:")
log.append(f"- With {precision_bits} qubits, we expect accuracy of approximately {expected_accuracy}")
log.append(f"- Theoretical best binary approximation with {precision_bits} bits: {phase_to_binary(target_phase, precision_bits)}")
log.append(f"- Corresponding phase: {binary_to_phase(phase_to_binary(target_phase, precision_bits))}")
if noise_prob > 0:
log.append(f"- Noise will reduce accuracy, with more effect on higher-precision bits")
# Return results
return {
"precision_bits": precision_bits,
"target_phase": target_phase,
"measured_state": measured_state,
"estimated_phase": float(estimated_phase),
"phase_error": float(phase_error),
"theoretical_accuracy": float(expected_accuracy),
"noise_prob": noise_prob,
"circuit_svg": circuit_svg,
"log": "\n".join(log)
}
if __name__ == '__main__':
# Run QPE examples
qpe_simple = run_phase_estimation(3, 0.125) # 1/8 = 0.001 in binary
qpe_complex = run_phase_estimation(5, 0.3) # 0.3 is not precisely representable in binary
qpe_noisy = run_phase_estimation(4, 0.25, 0.01) # 1/4 = 0.01 in binary, with noise |