@@ -10,7 +10,6 @@ DATE: April 2, 2025
1010o Discrete Fourier transforms (DFTs, reminder from last week) ) and the fast Fourier Transform (FFT) (see URL:"https://en.wikipedia.org/wiki/Fast_Fourier_transform")
1111o Quantum Fourier transforms (QFTs), reminder from last week
1212o Setting up circuits for QFTs
13- o Quantum phase estimation algorithm (QPE)
1413o Reading recommendation Hundt, Quantum Computing for Programmers, sections 6.1-6.4 on QFTs and QPE.
1514#o "Video of lecture TBA":"https://youtu.be/"
1615o "Whiteboard notes":"https://github.com/CompPhysics/QuantumComputingMachineLearning/blob/gh-pages/doc/HandWrittenNotes/2025/NotesApril2.pdf"
@@ -816,323 +815,3 @@ print("Probabilities after performing QFT:")
816815print(probabilities)
817816!ec
818817
819-
820- !split
821- ===== Introduction to Quantum Phase Estimation (QPE) =====
822- The QPE is a fundamental quantum algorithm used to estimate the phase $\phi$ in eigenvalue equations of unitary operators.
823- Given a unitary operator $U$ and an eigenvector $| \psi \rangle$ such that:
824- !bt
825- \[
826- U | \psi \rangle = \exp{2\pi i \phi} | \psi \rangle
827- \]
828- !et
829- the goal of QPE is to estimate $\phi$.
830-
831- The QPE provides an estimate of $\phi$ with high probability. It is a
832- crucial subroutine for algorithms like
833- o Shor's factoring algorithm and
834- o quantum many-body simulations.
835-
836- !split
837- ===== Mathematical Background =====
838-
839- The goal of QPE is to estimate the phase $\phi \in [0,1)$ where:
840- !bt
841- \[
842- U\vert \psi\rangle = \exp{2\pi i \phi} \vert\psi\rangle.
843- \]
844- !et
845-
846- The Quantum Fourier Transform on $n$ qubits is defined as (see above):
847- !bt
848- \[
849- \vert x\rangle \rightarrow \frac{1}{\sqrt{2^n}}\sum_{y=0}^{2^n-1} e^{2\pi i xy / 2^n}\vert y\rangle.
850- \]
851- !et
852- The QFT is essential for extracting phase information in QPE.
853-
854- !split
855- ===== Quantum Circuit and Working =====
856-
857- The quantum circuit for QPE consists of two quantum registers:
858- o The first register with $n$ qubits is initialized to $\vert 0\rangle^{\otimes n}$.
859- o second register is initialized to the eigenstate $\vert\psi\rangle$ of $U$.
860-
861- Figure to be added
862-
863- !split
864- ===== Derivation of the Algorithm, Step 1: Initialization =====
865- !bblock
866- The system starts in the state:
867- !bt
868- \[
869- \vert 0 \rangle ^{\otimes n} \otimes \vert\psi\rangle .
870- \]
871- !et
872- Applying Hadamard gates to the first register creates a superposition:
873- !bt
874- \[
875- \frac{1}{2^{n/2}}\sum_{k=0}^{2^n -1} \vert k \rangle \otimes \vert\psi\rangle .
876- \]
877- !et
878- !eblock
879- !split
880- ===== Step 2: Controlled Unitary Operations =====
881- Controlled-$U^{2^j}$ gates apply the operation $U$ with exponential powers:
882- !bblock
883- !bt
884- \[
885- \frac{1}{2^{n/2}}\sum_{k=0}^{2^n -1} \vert k \rangle \otimes U^k\vert\psi\rangle .
886- \]
887- !et
888- For eigenstate $\vert\psi\rangle $, this becomes:
889- !bt
890- \[
891- \frac{1}{2^{n/2}}\sum_{k=0}^{2^n -1} e^{2\pi i \phi k}\vert k \rangle \otimes \vert\psi\rangle .
892- \]
893- !et
894- !eblock
895-
896- !split
897- ===== Step 3: Quantum Fourier Transform (QFT) =====
898-
899- !bblock
900- Applying QFT to the first register transforms the state to:
901- !bt
902- \[
903- \sum_{k=0}^{2^n -1} c_k \vert k \rangle ,
904- \]
905- !et
906- where coefficients $c_k$ are peaked near $k \approx 2^n \phi$.
907-
908- !eblock
909- !split
910- ===== Step 4: Measurement =====
911- !bblock
912- Measuring the first register gives an $n$-bit estimate of $\phi$ with high probability.
913- !eblock
914-
915- !split
916- ===== Simple code which implements the QPE =====
917- !bc pycod
918- import numpy as np
919- def hadamard(n):
920- # Creates an n-qubit Hadamard gate as a matrix.
921- H = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
922- H_n = H
923- for _ in range(n - 1):
924- H_n = np.kron(H_n, H) # Tensor product to expand Hadamard gate
925- return H_n
926-
927- def qft(n):
928- # Creates an n-qubit Quantum Fourier Transform (QFT) matrix.
929- N = 2**n
930- omega = np.exp(2j * np.pi / N)
931- qft_matrix = np.array([[omega**(i * j) for j in range(N)] for i in range(N)]) / np.sqrt(N)
932- return qft_matrix
933-
934- def inverse_qft(n):
935- # Creates an n-qubit inverse Quantum Fourier Transform (QFT†) matrix.
936- return np.conj(qft(n)).T # Hermitian transpose of QFT
937-
938- def controlled_unitary(U, control, target, n):
939- # Creates an n-qubit controlled unitary matrix
940- I = np.eye(2**n) # Identity matrix
941- CU = np.copy(I)
942- for i in range(2**n):
943- if (i >> control) & 1: # Check if control qubit is |1⟩
944- CU[i, :] = np.kron(np.eye(2**target), U).dot(I[i, :])
945- return CU
946-
947- def apply_gate(state, gate):
948- # Applies a gate (matrix) to a quantum state (vector).
949- return gate @ state
950-
951- def measure(state):
952- # Simulates measurement by computing probability distribution
953- probabilities = np.abs(state) ** 2
954- return np.argmax(probabilities) # Return the most probable outcome
955-
956- def qpe(unitary, phi, num_counting_qubits):
957- # Simulates the Quantum Phase Estimation algorithm.
958- n = num_counting_qubits
959- total_qubits = n + 1
960- dim = 2**total_qubits
961-
962- # Step 1: Initialize state |0...0⟩ tensor product with psi
963- state = np.zeros(dim, dtype=complex)
964- state[0] = 1 # |00...0⟩
965-
966- # Step 2: Apply Hadamard to counting qubits
967- H_n = hadamard(n)
968- state = apply_gate(state.reshape(2**n, 2), H_n).reshape(dim)
969-
970- # Step 3: Apply controlled-U^2^j operations
971- for j in range(n):
972- power = 2**j
973- U_power = np.linalg.matrix_power(unitary, power)
974- CU = controlled_unitary(U_power, j, n, total_qubits)
975- state = apply_gate(state, CU)
976-
977- # Step 4: Apply inverse QFT
978- IQFT = inverse_qft(n)
979- state = apply_gate(state.reshape(2**n, 2), IQFT).reshape(dim)
980-
981- # Step 5: Measure and return estimated phase
982- measurement_result = measure(state)
983- return measurement_result / (2**n) # Convert binary to decimal
984-
985- # Define the unitary U with phase phi = 1/3
986- phi = 1/3
987- U = np.array([[1, 0], [0, np.exp(2j * np.pi * phi)]]) # Phase gate
988-
989- # Run QPE
990- num_counting_qubits = 3 # More qubits give higher precision
991- estimated_phi = qpe(U, phi, num_counting_qubits)
992- print(f"Estimated phase: {estimated_phi}")
993- print(f"Actual phase: {phi}")
994- !ec
995-
996- !split
997- ===== Code which implements the QPE using Qiskit =====
998- !bc pycod
999- from qiskit import QuantumCircuit, Aer, transpile, assemble, execute
1000- from qiskit.visualization import plot_histogram
1001- import numpy as np
1002-
1003- def qpe(unitary, num_counting_qubits):
1004- """
1005- Quantum Phase Estimation Algorithm.
1006- Args:
1007- unitary (QuantumCircuit): The unitary operator whose phase we estimate.
1008- num_counting_qubits (int): Number of qubits in the counting register.
1009-
1010- Returns:
1011- QuantumCircuit: QPE quantum circuit
1012- """
1013- n = num_counting_qubits
1014- qc = QuantumCircuit(n + 1, n) # n counting qubits + 1 eigenstate qubit
1015- # Step 1: Apply Hadamard to counting qubits
1016- for qubit in range(n):
1017- qc.h(qubit)
1018- # Step 2: Apply controlled-U^2^j operations
1019- for j in range(n):
1020- power = 2**j
1021- controlled_U = unitary.control(1).power(power)
1022- qc.append(controlled_U, [j] + [n]) # Control: j, Target: n
1023- # Step 3: Apply inverse QFT
1024- qc.append(qft_dagger(n), range(n))
1025- # Step 4: Measure counting qubits
1026- qc.measure(range(n), range(n))
1027- return qc
1028-
1029- def qft_dagger(n):
1030- """Creates an inverse Quantum Fourier Transform (QFT†) circuit."""
1031- qc = QuantumCircuit(n)
1032- for qubit in range(n//2):
1033- qc.swap(qubit, n-qubit-1)
1034- for j in range(n):
1035- for m in range(j):
1036- qc.cp(-np.pi / (2**(j-m)), m, j)
1037- qc.h(j)
1038- return qc
1039- # Define the unitary U with phase phi = 1/3
1040- phi = 1/3
1041- U = QuantumCircuit(1)
1042- U.p(2 * np.pi * phi, 0) # Phase gate
1043- # Number of counting qubits
1044- num_counting_qubits = 3
1045- # Generate QPE circuit
1046- qpe_circuit = qpe(U, num_counting_qubits)
1047- # Simulate the circuit
1048- simulator = Aer.get_backend('qasm_simulator')
1049- compiled_circuit = transpile(qpe_circuit, simulator)
1050- qobj = assemble(compiled_circuit)
1051- result = simulator.run(qobj).result()
1052- # Get measurement results
1053- counts = result.get_counts()
1054- # Plot histogram of results
1055- plot_histogram(counts)
1056- !ec
1057-
1058-
1059- !split
1060- ===== Code which implements the QPE using PennyLane =====
1061-
1062- !bc pycod
1063- import pennylane as qml
1064- import numpy as np
1065- def qft(n):
1066- # Applies the inverse Quantum Fourier Transform (QFT†) circuit
1067- for i in range(n):
1068- qml.Hadamard(wires=i)
1069- for j in range(i):
1070- qml.CPhase(-np.pi / (2 ** (i - j)), wires=[j, i])
1071- for i in range(n // 2):
1072- qml.SWAP(wires=[i, n - i - 1])
1073-
1074- def controlled_unitary(U, control, target):
1075- # Applies a controlled-unitary operation
1076- qml.ctrl(U, control=control)(wires=target)
1077-
1078-
1079- def qpe(phi, num_counting_qubits):
1080- # Quantum Phase Estimation circuit in PennyLane.
1081- total_qubits = num_counting_qubits + 1
1082- dev = qml.device("default.qubit", wires=total_qubits, shots=1000)
1083- @qml.qnode(dev)
1084- def circuit():
1085- # Initialize the counting register in |0> and eigenstate register in |1>
1086- qml.PauliX(wires=num_counting_qubits) # Set last qubit to |1>
1087- # Apply Hadamard to counting qubits
1088- for qubit in range(num_counting_qubits):
1089- qml.Hadamard(wires=qubit)
1090- # Apply controlled-U^2^j operations
1091- for j in range(num_counting_qubits):
1092- power = 2**j
1093- U = qml.RZ(2 * np.pi * phi, wires=num_counting_qubits) # Phase shift
1094- controlled_unitary(U, control=j, target=num_counting_qubits)
1095- # Apply inverse QFT
1096- qft(num_counting_qubits)
1097- # Measure counting qubits
1098- return qml.sample(wires=range(num_counting_qubits))
1099- # Run the circuit
1100- samples = circuit()
1101- # Convert measurement results to decimal phase estimate
1102- binary_result = "".join(map(str, samples[0])) # Take the first sample
1103- estimated_phi = int(binary_result, 2) / (2 ** num_counting_qubits)
1104- return estimated_phi
1105- # Define the phase phi
1106- phi = 1/3
1107- # Number of counting qubits (higher gives better precision)
1108- num_counting_qubits = 3
1109- # Run QPE
1110- estimated_phi = qpe(phi, num_counting_qubits)
1111- print(f"Estimated phase: {estimated_phi}")
1112- print(f"Actual phase: {phi}")
1113- !ec
1114-
1115- !split
1116- ===== Applications of QPE =====
1117-
1118- The QPE is a foundational algorithm with several key applications:
1119- o \textbf{Factoring and Cryptography:} Integral to Shor's algorithm for integer factoring.
1120- o \textbf{Quantum Simulations:} Estimating eigenvalues of Hamiltonians in many-body physics.
1121- o \textbf{Amplitude Estimation:} Enhances algorithms like Grover's search.
1122-
1123- !split
1124- ===== Challenges and Practical Considerations =====
1125-
1126- o \textbf{Qubit Requirements:} Requires a large number of qubits for high precision.
1127- o \textbf{Gate Depth:} Controlled-$U^{2^j}$ operations increase gate complexity.
1128- o \textbf{Noise Sensitivity:} Errors in QFT or controlled gates impact accuracy.
1129-
1130- It is a powerful quantum algorithm for extracting phase information of
1131- unitary operators. It forms the foundation for many advanced quantum
1132- algorithms and demonstrates the power of quantum Fourier transforms in
1133- computational tasks. For many-body simulations it is however not as efficient as the VQE algorithm.
1134-
1135-
1136-
1137-
1138-
0 commit comments