Skip to content

Commit 7d137d5

Browse files
committed
added vqe codes
1 parent a1c6568 commit 7d137d5

File tree

2 files changed

+298
-0
lines changed

2 files changed

+298
-0
lines changed

doc/Programs/QPEcodes/Qpecode.txt

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
2+
Quantum Phase Estimation (QPE) Simulation in Python
3+
4+
5+
Quantum Phase Estimation (QPE) is a quantum algorithm that estimates the phase (eigenvalue) of an eigenvector of a given unitary operator . In the context of finding eigenvalues of a Hermitian Hamiltonian H, we leverage the fact that H can be associated with a unitary U = e^{2\pi i H}. If |\psi\rangle is an eigenvector of H with eigenvalue \lambda, then U shares the same eigenvector and has eigenvalue e^{2\pi i \lambda} . By performing QPE on U, we can obtain \lambda (mod 1) as the measured phase. The algorithm uses two quantum registers: a counting register of t qubits (for accumulating phase information) and a target register that holds the state on which U acts . Below is a step-by-step overview and a Python simulation.
6+
7+
8+
Algorithm Overview
9+
10+
11+
State Preparation: Initialize the counting register of t qubits to |0\dots0\rangle and the target register to some state |\psi\rangle. If |\psi\rangle is an eigenstate of H, QPE will deterministically find its eigenvalue; if not, |\psi\rangle can be expressed as a superposition of eigenstates , and the algorithm’s outcome will be probabilistic. Apply Hadamard gates on all counting qubits to create an equal superposition of all 2^t basis states . After this step, the state is:
\frac{1}{\sqrt{2^t}} \sum_{k=0}^{2^t-1} |k\rangle_{\text{count}} \otimes |\psi\rangle_{\text{target}}.
12+
Controlled Unitary Operations (Phase Kickback): Implement the unitary U = e^{2\pi i H} controlled on each counting qubit. For each counting qubit (say the j-th qubit, representing the 2^j place in the binary expansion), apply a controlled operation that applies U^{2^j} to the target if and only if that qubit is |1\rangle . These controlled operations introduce a phase kickback onto the counting register. If the target state is an eigenvector with H|\psi\rangle = \lambda|\psi\rangle, then after this step the state is:
\frac{1}{\sqrt{2^t}} \sum_{k=0}^{2^t-1} e^{2\pi i \lambda k} |k\rangle_{\text{count}} \otimes |\psi\rangle_{\text{target}},
where each basis state |k\rangle in the counting register has picked up a phase e^{2\pi i \lambda k} .
13+
Inverse Quantum Fourier Transform (QFT): Apply the inverse QFT on the counting register to convert the phase information into a binary-encoded eigenvalue . The inverse QFT transforms the state such that the counting register collapses (in amplitude) onto a state approximately equal to the binary fraction representation of \lambda. For example, if \lambda = 0.625 (which is 0.101 in binary), the counting qubits (with sufficient precision) will end up in a state close to |101\rangle. In general, after an ideal inverse QFT, the counting register state becomes |\tilde{\lambda}\rangle_{\text{count}}, where \tilde{\lambda} is the binary approximation of \lambda.
14+
Measurement: Measure the counting register in the computational basis. The measurement yields a bitstring x_1x_2\dots x_t. This binary outcome corresponds to an estimated phase \theta \approx x_1/2 + x_2/2^2 + \cdots + x_t/2^t . Under the correspondence U = e^{2\pi i H}, this phase \theta is an estimate of the eigenvalue \lambda of the Hamiltonian (assuming \lambda was scaled to [0,1)). Increasing the number of counting qubits t improves the precision of this estimate (error on the order of 1/2^t per QPE run) . If the initial state was a superposition of eigenstates, the measurement will collapse the target register onto one of the eigenstates, and the outcome will be one of the corresponding eigenvalues with probability equal to the weight of that eigenstate in the initial superposition .
15+
16+
17+
18+
Python Implementation
19+
20+
21+
Below is a Python script that simulates the QPE algorithm using NumPy only. It accepts a Hermitian matrix (Hamiltonian) as input, along with a chosen number of counting qubits for precision. The simulation explicitly constructs the unitary U = e^{2\pi i H} via diagonalization (since H is Hermitian, we can exponentiate its eigenvalues ). It then applies the Hadamard gates, the controlled-U operations, and the inverse QFT on a statevector that represents both registers. Finally, it prints out the estimated eigenvalues (phases) and can also indicate the corresponding eigenstate (particularly if the initial state is one of the eigenvectors). The code is heavily commented to explain each step:
22+
import numpy as np
23+
24+
def quantum_phase_estimation(H, num_counting_qubits, initial_state=None):
25+
"""
26+
Simulate the Quantum Phase Estimation algorithm to find eigenvalues of a Hermitian matrix H.
27+
28+
Parameters:
29+
H (numpy.ndarray): Hermitian matrix (dimension N x N, typically N=2^n for n qubits in the target system).
30+
num_counting_qubits (int): Number of qubits in the counting register (controls precision of estimation).
31+
initial_state (numpy.ndarray or None): State vector for the target register (length N).
32+
If None, a random state is chosen. For deterministic results, provide an eigenvector of H.
33+
34+
Returns:
35+
list of (float, float): A list of tuples where each tuple is (estimated_eigenvalue, probability).
36+
If an eigenstate was provided as initial_state, one dominant eigenvalue with probability ~1 should appear.
37+
"""
38+
# Ensure H is a numpy array and Hermitian
39+
H = np.array(H, dtype=complex)
40+
if not np.allclose(H, H.conj().T):
41+
raise ValueError("Input matrix H must be Hermitian.")
42+
N = H.shape[0] # Dimension of the target register's state space
43+
44+
# 1. Compute the unitary U = exp(2π i H).
45+
# Because H is Hermitian, it has a spectral decomposition H = V diag(eigvals) V^†.
46+
# We exponentiate the eigenvalues to get U's eigenvalues exp(2π i * eigval).
47+
eigvals, eigvecs = np.linalg.eigh(H) # eigvals are real
48+
U = (eigvecs * np.exp(2j * np.pi * eigvals)) @ eigvecs.conj().T # construct U = V * diag(exp(2πi λ)) * V^†
49+
# U is unitary and U|ψ_i> = exp(2π i λ_i)|ψ_i> for each eigenpair (λ_i, |ψ_i>) [oai_citation:12‡raw.githubusercontent.com](https://raw.githubusercontent.com/blueqat/blueqat-tutorials/refs/heads/master/tutorial/3_ftqc/02_pea2.ipynb#:~:text=%5C%5Clambda_j%5En%7D%7Bn%21%7D%5C%5Cleft%7C%5C%5Cpsi_j%5C%5Cright%5C%5Crangle%5C%5Cnonumber%5C%5C%5C%5C%5Cn,metadata).
50+
51+
# 2. Prepare the initial state of the whole system (counting + target).
52+
M = 2 ** num_counting_qubits # size of counting register state space
53+
# Counting register starts in |0...0>, target in given initial_state or random state.
54+
if initial_state is None:
55+
# If no initial state provided, choose a random state on the target (and normalize it).
56+
psi = np.random.rand(N) + 1j * np.random.rand(N)
57+
psi /= np.linalg.norm(psi)
58+
else:
59+
psi = np.array(initial_state, dtype=complex)
60+
psi /= np.linalg.norm(psi) # normalize the initial state
61+
# Full initial state = |0...0>_counting ⊗ |psi>_target.
62+
full_state = np.zeros((M * N,), dtype=complex)
63+
# The counting register |0...0> corresponds to index 0, so place psi in that block of the state vector.
64+
full_state[0:N] = psi
65+
66+
# 3. Apply Hadamard gates to all counting qubits.
67+
# A Hadamard on a counting qubit transforms |0> -> (|0>+|1>)/√2 and |1> -> (|0>-|1>)/√2.
68+
# We loop over each counting qubit to apply the tensor product of H with identity on others.
69+
for q in range(num_counting_qubits):
70+
# The qubit q has a bit-value that flips every 2^q indices in the counting register.
71+
# We iterate over the state vector and apply H to the amplitudes where qubit q is 0 and 1.
72+
step = 2 ** q # half period for this qubit's flipping
73+
period = 2 ** (q + 1) # full period for this qubit (0 then 1)
74+
half_block = step * N # number of consecutive entries for which qubit q is fixed to 0 or 1, in the flat state vector
75+
new_state = full_state.copy()
76+
# Traverse the state in chunks where qubit q is constant
77+
for start in range(0, M * N, period * N):
78+
# In each period, first half_block entries have qubit q = 0, next half_block have qubit q = 1
79+
# Apply H: new_0 = (old_0 + old_1)/√2, new_1 = (old_0 - old_1)/√2
80+
end0 = start + half_block
81+
end1 = start + 2 * half_block
82+
psi0 = full_state[start:end0] # segment where q is |0>
83+
psi1 = full_state[end0:end1] # segment where q is |1>
84+
# Superpose the segments (note: psi0 and psi1 are of length half_block = step*N)
85+
new_state[start:end0] = (psi0 + psi1) / np.sqrt(2)
86+
new_state[end0:end1] = (psi0 - psi1) / np.sqrt(2)
87+
full_state = new_state
88+
# After this loop, the counting register is in an equal superposition of all 2^t states [oai_citation:13‡en.wikipedia.org](https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm#:~:text=state%3AImage%3A%20%7B%5Cdisplaystyle%20%7C%5CPsi%20_%7B1%7D%5Crangle%20%3D%28H,1%7D%7Cj_%7B%5Cell).
89+
90+
# 4. Apply controlled-U^{2^j} operations for each counting qubit j.
91+
# For each counting qubit, if that qubit is |1>, we apply U^(2^j) to the target register state.
92+
for q in range(num_counting_qubits):
93+
# Compute U^(2^q) efficiently using eigen-decomposition (square the phase).
94+
U_power = (eigvecs * np.exp(2j * np.pi * eigvals * (2 ** q))) @ eigvecs.conj().T
95+
# Now apply this controlled operation: loop over each basis state of counting register.
96+
new_state = full_state.copy()
97+
for idx in range(M):
98+
# Check if the q-th bit of idx is 1 (in binary representation)
99+
if (idx >> q) & 1: # if the q-th bit of idx is 1
100+
# The portion of the state corresponding to counting index 'idx' is from idx*N to idx*N+N-1.
101+
start = idx * N
102+
end = start + N
103+
# Apply U_power to the target portion of the state vector for this counting state
104+
new_state[start:end] = U_power.dot(full_state[start:end])
105+
full_state = new_state
106+
# After this, the phase information is encoded in the amplitudes of the counting register states [oai_citation:14‡en.wikipedia.org](https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm#:~:text=,2%5Cpi%20ik%5Ctheta%20%7D%7C%5Cpsi%20%5Crangle).
107+
108+
# 5. Apply the inverse Quantum Fourier Transform (QFT) on the counting register.
109+
# We will construct the inverse QFT matrix on t qubits of size MxM and apply it to the counting part of full_state.
110+
M = 2 ** num_counting_qubits
111+
# Reshape state to (M, N) for convenience (each row = amplitude for a counting basis state, of length N for target).
112+
full_state = full_state.reshape(M, N)
113+
# Construct inverse QFT matrix F_dagger of size M x M (M = 2^t).
114+
F_dagger = np.zeros((M, M), dtype=complex)
115+
omega = np.exp(-2j * np.pi / M) # primitive Mth root of unity (for inverse, use e^{-2πi/M})
116+
for x in range(M):
117+
for y in range(M):
118+
# Inverse QFT: |y> -> (1/√M) * Σ_x exp(-2π i * x * y / M) |x>
119+
F_dagger[x, y] = omega ** (x * y)
120+
F_dagger /= np.sqrt(M)
121+
# Apply the inverse QFT on the counting register (matrix multiply on the first index of full_state).
122+
full_state = F_dagger.dot(full_state)
123+
# Reshape back to flat state vector (optional).
124+
full_state = full_state.reshape(M * N)
125+
126+
# 6. Measure the counting register (simulation by extracting probabilities).
127+
# Calculate the probability of each outcome k (0 <= k < 2^t) by summing probabilities of target states.
128+
probabilities = []
129+
for k in range(M):
130+
# Probability = sum of |amplitude|^2 of state where counting register = k
131+
start = k * N
132+
end = start + N
133+
prob = np.linalg.norm(full_state[start:end])**2
134+
if prob > 1e-12: # consider only significant probabilities
135+
probabilities.append((k, prob))
136+
# Sort outcomes by probability descending
137+
probabilities.sort(key=lambda x: x[1], reverse=True)
138+
# Convert outcome index to eigenvalue estimate (fraction of 2^t)
139+
results = []
140+
for (k, prob) in probabilities:
141+
phase_est = k / (2 ** num_counting_qubits) # in [0, 1)
142+
results.append((phase_est, prob))
143+
return results
144+
145+
# Example usage:
146+
if __name__ == "__main__":
147+
# Define a 2x2 Hermitian matrix (Hamiltonian). For example:
148+
H = np.array([[0.25, 0.0],
149+
[0.0, 0.5]]) # eigenvalues 0.25 and 0.5
150+
# Run QPE with 3 counting qubits (3 bits of precision).
151+
results = quantum_phase_estimation(H, num_counting_qubits=3)
152+
print("Estimated eigenvalues (as fractions of 1) and their probabilities:")
153+
for phase, prob in results:
154+
print(f" Eigenvalue ≈ {phase:.3f} (fraction {phase} of 1) with probability {prob:.2f}")
155+
How it works: The function quantum_phase_estimation returns a list of estimated eigenvalues (in fractional form between 0 and 1) along with their probabilities. If the initial_state provided is an eigenvector of H, the algorithm will output that eigenvalue with near 100% probability. If the initial state is a superposition, multiple eigenvalues may appear in the output distribution (each corresponding to an eigencomponent of the initial state) . In the example above, we used a 2×2 Hamiltonian with eigenvalues 0.25 and 0.5. Running the script (which uses a random initial state by default) might output:
156+
Estimated eigenvalues (as fractions of 1) and their probabilities:
157+
Eigenvalue ≈ 0.250 (fraction 0.25 of 1) with probability 0.50
158+
Eigenvalue ≈ 0.500 (fraction 0.5 of 1) with probability 0.50
159+
This indicates that the algorithm found two eigenvalues (0.25 and 0.5) with equal probability, as expected for a random initial state that had equal overlap on both eigenvectors. If we instead set initial_state to one of the eigenvectors of H (for instance, [1, 0] for eigenvalue 0.25), the output would concentrate on that single eigenvalue (probability ~1.0 for 0.25, 0 for others).
160+
161+
How to run the code: Copy the code into a Python file (e.g., qpe_simulation.py) and ensure you have NumPy installed. You can then run the script directly to see the example, or import the quantum_phase_estimation function in another program. To use the function, prepare a Hermitian matrix H as a NumPy array, decide on the number of counting qubits t for the desired precision (each qubit adds one binary digit of precision ), and call:
162+
results = quantum_phase_estimation(H, t, initial_state=your_state_vector)
163+
The results will be a list of (eigenvalue_estimate, probability) pairs. You can interpret the eigenvalue_estimate as the fraction of the unit circle (i.e., if eigenvalue_estimate = 0.25, it corresponds to a phase 2\pi \times 0.25 = \pi/2). If your Hamiltonian’s eigenvalues are outside [0,1), you may need to scale or shift H before applying QPE (since QPE inherently returns the phase modulo 1). Each additional counting qubit doubles the phase resolution of the estimate.
164+
165+
This approach uses direct state-vector simulation, so the computational cost grows exponentially with the number of qubits. Nevertheless, it serves as a clear demonstration of the QPE algorithm’s mechanics: from preparing superposition states and applying controlled unitaries, to using the inverse QFT to read out eigenvalues of a Hermitian operator using purely classical computation with NumPy. The inline comments in the code provide further guidance on each step of the simulation.

0 commit comments

Comments
 (0)