""" 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