Skip to content

Commit cf1e46d

Browse files
committed
codes
1 parent bffbabc commit cf1e46d

File tree

4 files changed

+382
-0
lines changed

4 files changed

+382
-0
lines changed

doc/src/FuturePlans/programs/pcodes/qft3.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,60 @@ def print_amplitudes(self, title="Quantum State"):
109109

110110

111111

112+
def plot_amplitudes(self, title="State Vector Amplitudes", save_path=None):
113+
labels = [format(i, f'0{self.n_qubits}b') for i in range(self.N)]
114+
reals = [self.state[i].real for i in range(self.N)]
115+
imags = [self.state[i].imag for i in range(self.N)]
116+
117+
x = np.arange(self.N)
118+
width = 0.35
119+
120+
fig, ax = plt.subplots()
121+
ax.bar(x - width/2, reals, width, label='Real')
122+
ax.bar(x + width/2, imags, width, label='Imaginary')
123+
ax.set_xticks(x)
124+
ax.set_xticklabels(labels, rotation=45)
125+
ax.set_ylabel('Amplitude')
126+
ax.set_title(title)
127+
ax.legend()
128+
plt.tight_layout()
129+
130+
if save_path:
131+
plt.savefig(save_path)
132+
print(f"Plot saved to: {save_path}")
133+
else:
134+
plt.show()
135+
136+
def plot_probabilities(self, shots=1024, title="Measurement Probabilities", save_path=None):
137+
results = self.measure(shots=shots)
138+
bitstrings = sorted(results.keys())
139+
counts = [results[b] for b in bitstrings]
140+
141+
fig, ax = plt.subplots()
142+
ax.bar(bitstrings, counts)
143+
ax.set_xlabel("Bitstring")
144+
ax.set_ylabel("Counts")
145+
ax.set_title(title)
146+
ax.set_xticklabels(bitstrings, rotation=45)
147+
plt.tight_layout()
148+
149+
if save_path:
150+
plt.savefig(save_path)
151+
print(f"Histogram saved to: {save_path}")
152+
else:
153+
plt.show()
154+
155+
qft = QuantumFourierTransform(3)
156+
qft.initialize_basis_state(5)
157+
158+
qft.apply_qft()
159+
160+
# Save amplitude plot
161+
qft.plot_amplitudes("QFT Amplitudes", save_path="qft_amplitudes.png")
162+
163+
# Save measurement histogram
164+
qft.plot_probabilities(shots=1024, title="QFT Output Distribution", save_path="qft_histogram.png")
165+
112166
"""
113167
Quick Tips on Usage
114168
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from matplotlib.animation import FuncAnimation
4+
import os
5+
6+
class QuantumFourierTransform:
7+
def __init__(self, n_qubits):
8+
self.n_qubits = n_qubits
9+
self.N = 2 ** n_qubits
10+
self.state = np.zeros(self.N, dtype=complex)
11+
12+
def initialize_basis_state(self, index):
13+
"""Set state to basis state |index⟩"""
14+
self.state = np.zeros(self.N, dtype=complex)
15+
self.state[index] = 1.0
16+
17+
def initialize_custom_state(self, state_vector):
18+
"""Set state to custom normalized state vector"""
19+
assert len(state_vector) == self.N, "State vector length mismatch."
20+
norm = np.linalg.norm(state_vector)
21+
assert np.isclose(norm, 1.0), "State vector must be normalized."
22+
self.state = np.array(state_vector, dtype=complex)
23+
24+
def initialize_superposition(self):
25+
"""Create |+⟩^n superposition state"""
26+
self.state = np.ones(self.N, dtype=complex) / np.sqrt(self.N)
27+
def initialize_bell_pair(self):
28+
"""2-qubit Bell state: (|00⟩ + |11⟩)/√2"""
29+
if self.n_qubits != 2:
30+
raise ValueError("Bell pair requires exactly 2 qubits.")
31+
self.state = np.zeros(self.N, dtype=complex)
32+
self.state[0] = 1 / np.sqrt(2)
33+
self.state[3] = 1 / np.sqrt(2)
34+
35+
def initialize_ghz_state(self):
36+
"""n-qubit GHZ state: (|00...0⟩ + |11...1⟩)/√2"""
37+
self.state = np.zeros(self.N, dtype=complex)
38+
self.state[0] = 1 / np.sqrt(2)
39+
self.state[-1] = 1 / np.sqrt(2)
40+
41+
def hadamard(self):
42+
return np.array([[1, 1], [1, -1]]) / np.sqrt(2)
43+
44+
def apply_single_qubit_gate(self, gate, target):
45+
I = np.eye(2)
46+
ops = [I] * self.n_qubits
47+
ops[target] = gate
48+
U = ops[0]
49+
for op in ops[1:]:
50+
U = np.kron(U, op)
51+
self.state = U @ self.state
52+
def apply_controlled_phase(self, control, target, theta):
53+
U = np.eye(self.N, dtype=complex)
54+
for i in range(self.N):
55+
b = format(i, f'0{self.n_qubits}b')
56+
if b[self.n_qubits - 1 - control] == '1' and b[self.n_qubits - 1 - target] == '1':
57+
U[i, i] *= np.exp(1j * theta)
58+
self.state = U @ self.state
59+
60+
def swap_registers(self):
61+
perm = [int(format(i, f'0{self.n_qubits}b')[::-1], 2) for i in range(self.N)]
62+
self.state = self.state[perm]
63+
def apply_qft(self, inverse=False):
64+
for target in range(self.n_qubits):
65+
idx = self.n_qubits - 1 - target
66+
for offset in range(1, self.n_qubits - target):
67+
control = self.n_qubits - 1 - (target + offset)
68+
angle = np.pi / (2 ** offset)
69+
if inverse:
70+
angle *= -1
71+
self.apply_controlled_phase(control, idx, angle)
72+
self.apply_single_qubit_gate(self.hadamard(), idx)
73+
self.swap_registers()
74+
def measure(self, shots=1024):
75+
probs = np.abs(self.state) ** 2
76+
outcomes = np.random.choice(self.N, size=shots, p=probs)
77+
counts = {}
78+
for o in outcomes:
79+
bitstring = format(o, f'0{self.n_qubits}b')
80+
counts[bitstring] = counts.get(bitstring, 0) + 1
81+
return counts
82+
83+
def print_amplitudes(self, title="Quantum State"):
84+
print(f"\n{title}:")
85+
for i, amp in enumerate(self.state):
86+
print(f"|{i:0{self.n_qubits}b}>: {amp:.4f}")
87+
88+
89+
# Inside the class
90+
def reset_animation_log(self):
91+
self.animation_frames = []
92+
93+
def record_probability_frame(self):
94+
"""Store current probabilities for animation."""
95+
probs = np.abs(self.state) ** 2
96+
self.animation_frames.append(probs.copy())
97+
98+
def apply_qft_with_recording(self, inverse=False):
99+
self.reset_animation_log()
100+
self.record_probability_frame() # Record initial state
101+
102+
for target in range(self.n_qubits):
103+
idx = self.n_qubits - 1 - target
104+
for offset in range(1, self.n_qubits - target):
105+
control = self.n_qubits - 1 - (target + offset)
106+
angle = np.pi / (2 ** offset)
107+
if inverse:
108+
angle *= -1
109+
self.apply_controlled_phase(control, idx, angle)
110+
self.record_probability_frame()
111+
self.apply_single_qubit_gate(self.hadamard(), idx)
112+
self.record_probability_frame()
113+
114+
self.swap_registers()
115+
self.record_probability_frame()
116+
117+
def animate_probability_evolution(self, save_path="qft_animation.gif", interval=600):
118+
labels = [format(i, f'0{self.n_qubits}b') for i in range(self.N)]
119+
fig, ax = plt.subplots()
120+
bar = ax.bar(labels, [0]*self.N)
121+
ax.set_ylim(0, 1)
122+
ax.set_ylabel("Probability")
123+
ax.set_title("QFT Probability Evolution")
124+
125+
def update(frame_probs):
126+
for rect, prob in zip(bar, frame_probs):
127+
rect.set_height(prob)
128+
return bar
129+
130+
ani = FuncAnimation(fig, update, frames=self.animation_frames,
131+
interval=interval, blit=False, repeat=False)
132+
133+
if save_path.endswith(".gif"):
134+
ani.save(save_path, writer='pillow')
135+
elif save_path.endswith(".mp4"):
136+
ani.save(save_path, writer='ffmpeg')
137+
else:
138+
raise ValueError("Unsupported format. Use .gif or .mp4")
139+
140+
print(f"Animation saved to {save_path}")
141+
142+
# ---------------------------
143+
# Example usage
144+
# ---------------------------
145+
if __name__ == "__main__":
146+
qft = QuantumFourierTransform(n_qubits=3)
147+
qft.initialize_basis_state(5)
148+
149+
# Apply QFT with intermediate state recording
150+
qft.apply_qft_with_recording()
151+
152+
# Animate the probability distribution over steps
153+
qft.animate_probability_evolution(save_path="qft_probs.gif", interval=800)
154+
155+
156+
157+
"""
158+
Quick Tips on Usage
159+
160+
161+
Superposition inputs (e.g. Hadamards to create |+\rangle^{\otimes n})
162+
Custom state initialization (e.g. any normalized complex vector)
163+
Entangled state preparation (e.g. Bell states, GHZ states, etc.)
164+
165+
166+
167+
initialize_superposition() gives |+\rangle^{\otimes n}
168+
initialize_bell_pair() is for 2-qubit entangled states
169+
initialize_ghz_state() creates a GHZ state
170+
initialize_custom_state(vec) lets you pass any normalized state
171+
"""
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from matplotlib.animation import FuncAnimation
4+
import os
5+
6+
class QuantumFourierTransform:
7+
def __init__(self, n_qubits):
8+
self.n_qubits = n_qubits
9+
self.N = 2 ** n_qubits
10+
self.state = np.zeros(self.N, dtype=complex)
11+
12+
def initialize_basis_state(self, index):
13+
"""Set state to basis state |index⟩"""
14+
self.state = np.zeros(self.N, dtype=complex)
15+
self.state[index] = 1.0
16+
17+
def initialize_custom_state(self, state_vector):
18+
"""Set state to custom normalized state vector"""
19+
assert len(state_vector) == self.N, "State vector length mismatch."
20+
norm = np.linalg.norm(state_vector)
21+
assert np.isclose(norm, 1.0), "State vector must be normalized."
22+
self.state = np.array(state_vector, dtype=complex)
23+
24+
def initialize_superposition(self):
25+
"""Create |+⟩^n superposition state"""
26+
self.state = np.ones(self.N, dtype=complex) / np.sqrt(self.N)
27+
def initialize_bell_pair(self):
28+
"""2-qubit Bell state: (|00⟩ + |11⟩)/√2"""
29+
if self.n_qubits != 2:
30+
raise ValueError("Bell pair requires exactly 2 qubits.")
31+
self.state = np.zeros(self.N, dtype=complex)
32+
self.state[0] = 1 / np.sqrt(2)
33+
self.state[3] = 1 / np.sqrt(2)
34+
35+
def initialize_ghz_state(self):
36+
"""n-qubit GHZ state: (|00...0⟩ + |11...1⟩)/√2"""
37+
self.state = np.zeros(self.N, dtype=complex)
38+
self.state[0] = 1 / np.sqrt(2)
39+
self.state[-1] = 1 / np.sqrt(2)
40+
41+
def hadamard(self):
42+
return np.array([[1, 1], [1, -1]]) / np.sqrt(2)
43+
44+
def apply_single_qubit_gate(self, gate, target):
45+
I = np.eye(2)
46+
ops = [I] * self.n_qubits
47+
ops[target] = gate
48+
U = ops[0]
49+
for op in ops[1:]:
50+
U = np.kron(U, op)
51+
self.state = U @ self.state
52+
def apply_controlled_phase(self, control, target, theta):
53+
U = np.eye(self.N, dtype=complex)
54+
for i in range(self.N):
55+
b = format(i, f'0{self.n_qubits}b')
56+
if b[self.n_qubits - 1 - control] == '1' and b[self.n_qubits - 1 - target] == '1':
57+
U[i, i] *= np.exp(1j * theta)
58+
self.state = U @ self.state
59+
60+
def swap_registers(self):
61+
perm = [int(format(i, f'0{self.n_qubits}b')[::-1], 2) for i in range(self.N)]
62+
self.state = self.state[perm]
63+
def apply_qft(self, inverse=False):
64+
for target in range(self.n_qubits):
65+
idx = self.n_qubits - 1 - target
66+
for offset in range(1, self.n_qubits - target):
67+
control = self.n_qubits - 1 - (target + offset)
68+
angle = np.pi / (2 ** offset)
69+
if inverse:
70+
angle *= -1
71+
self.apply_controlled_phase(control, idx, angle)
72+
self.apply_single_qubit_gate(self.hadamard(), idx)
73+
self.swap_registers()
74+
def measure(self, shots=1024):
75+
probs = np.abs(self.state) ** 2
76+
outcomes = np.random.choice(self.N, size=shots, p=probs)
77+
counts = {}
78+
for o in outcomes:
79+
bitstring = format(o, f'0{self.n_qubits}b')
80+
counts[bitstring] = counts.get(bitstring, 0) + 1
81+
return counts
82+
83+
def print_amplitudes(self, title="Quantum State"):
84+
print(f"\n{title}:")
85+
for i, amp in enumerate(self.state):
86+
print(f"|{i:0{self.n_qubits}b}>: {amp:.4f}")
87+
88+
def reset_animation_log(self):
89+
self.animation_frames = []
90+
self.animation_labels = []
91+
92+
def record_probability_frame(self, label=""):
93+
probs = np.abs(self.state) ** 2
94+
self.animation_frames.append(probs.copy())
95+
self.animation_labels.append(label)
96+
97+
def apply_qft_with_recording(self, inverse=False):
98+
self.reset_animation_log()
99+
self.record_probability_frame("Initial State")
100+
101+
for target in range(self.n_qubits):
102+
idx = self.n_qubits - 1 - target
103+
for offset in range(1, self.n_qubits - target):
104+
control = self.n_qubits - 1 - (target + offset)
105+
angle = np.pi / (2 ** offset)
106+
if inverse:
107+
angle *= -1
108+
label = f"CP({angle:.2f}) from q{control} to q{idx}"
109+
self.apply_controlled_phase(control, idx, angle)
110+
self.record_probability_frame(label)
111+
self.apply_single_qubit_gate(self.hadamard(), idx)
112+
self.record_probability_frame(f"H on q{idx}")
113+
114+
self.swap_registers()
115+
self.record_probability_frame("Final Swap")
116+
117+
def animate_probability_evolution(self, save_path="qft_probs.gif", interval=600):
118+
labels = [format(i, f'0{self.n_qubits}b') for i in range(self.N)]
119+
fig, ax = plt.subplots()
120+
bar = ax.bar(labels, [0]*self.N)
121+
ax.set_ylim(0, 1)
122+
ax.set_ylabel("Probability")
123+
124+
step_label = ax.text(0.5, 1.02, "", ha="center", va="bottom", transform=ax.transAxes, fontsize=12)
125+
126+
def update(i):
127+
probs = self.animation_frames[i]
128+
for rect, prob in zip(bar, probs):
129+
rect.set_height(prob)
130+
step_label.set_text(self.animation_labels[i])
131+
return bar
132+
133+
ani = FuncAnimation(fig, update, frames=len(self.animation_frames),
134+
interval=interval, blit=False, repeat=False)
135+
136+
if save_path.endswith(".gif"):
137+
ani.save(save_path, writer='pillow')
138+
elif save_path.endswith(".mp4"):
139+
ani.save(save_path, writer='ffmpeg')
140+
else:
141+
raise ValueError("Unsupported format. Use .gif or .mp4")
142+
143+
print(f"Animation saved to {save_path}")
144+
145+
146+
147+
148+
# ---------------------------
149+
# Example usage
150+
# ---------------------------
151+
if __name__ == "__main__":
152+
qft = QuantumFourierTransform(3)
153+
qft.initialize_basis_state(5)
154+
qft.apply_qft_with_recording()
155+
qft.animate_probability_evolution("qft_with_labels.gif", interval=800)
156+
157+
26.5 KB
Loading

0 commit comments

Comments
 (0)