Skip to content

Commit 182e634

Browse files
committed
added more codes
1 parent 044ea98 commit 182e634

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from matplotlib.animation import FuncAnimation
4+
5+
class QuantumSensorAnimated:
6+
def __init__(self, n_qubits=2, gamma=1.0, T2=5.0, shots=100, sigma_noise=0.02):
7+
self.n_qubits = n_qubits
8+
self.gamma = gamma
9+
self.T2 = T2
10+
self.shots = shots
11+
self.sigma_noise = sigma_noise
12+
13+
# Time points for simulation
14+
self.times = np.linspace(0.1, 5, 100)
15+
16+
# True field parameters
17+
self.B0_true = 1.0
18+
self.omega_true = 1.0
19+
self.phi0_true = 0.0
20+
def B_field(self, t):
21+
"""Time-dependent magnetic field."""
22+
return self.B0_true * np.sin(self.omega_true * t)
23+
24+
def phase(self, t):
25+
"""Total accumulated phase for entangled qubits."""
26+
return self.n_qubits * self.gamma * self.B_field(t) * t + self.phi0_true
27+
28+
def animate_true_phase(self):
29+
"""Create an animation of phase evolution over time."""
30+
fig, ax = plt.subplots()
31+
ax.set_xlim(self.times[0], self.times[-1])
32+
ax.set_ylim(-10, 10)
33+
ax.set_xlabel("Time")
34+
ax.set_ylabel("Phase (radians)")
35+
ax.set_title(f"Quantum Phase Accumulation for {self.n_qubits} Qubits")
36+
37+
line, = ax.plot([], [], lw=2, color='blue')
38+
phase_vals = [self.phase(t) for t in self.times]
39+
40+
def init():
41+
line.set_data([], [])
42+
return line,
43+
44+
def update(frame):
45+
t_vals = self.times[:frame]
46+
y_vals = phase_vals[:frame]
47+
line.set_data(t_vals, y_vals)
48+
return line,
49+
50+
ani = FuncAnimation(fig, update, frames=len(self.times),
51+
init_func=init, blit=True, repeat=False)
52+
plt.show()
53+
54+
55+
# ----------------------- Run Example -----------------------
56+
57+
sensor = QuantumSensorAnimated(n_qubits=4) # use any number of entangled qubits
58+
sensor.animate_true_phase()
59+
60+
"""
61+
Notes:
62+
63+
You can change n_qubits=4 to any integer for different entanglement levels.
64+
65+
The phase scales linearly with the number of entangled qubits — as expected in quantum metrology.
66+
67+
The animation runs in-place in a Jupyter notebook or can be saved using ani.save(...).
68+
"""
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""
2+
Models a time-dependent magnetic field
3+
Uses Bell-state entangled qubits
4+
Simulates decoherence
5+
Computes density matrices for mixed states
6+
Calculates expectation values
7+
Estimates field parameters using Bayesian inference
8+
Visualizes fidelity and posterior distributions
9+
"""
10+
11+
import numpy as np
12+
import matplotlib.pyplot as plt
13+
from numpy.linalg import eigh
14+
15+
class QuantumSensorBayesian:
16+
def __init__(self, n_qubits=2, gamma=1.0, T2=5.0, shots=100, sigma_noise=0.02):
17+
self.n_qubits = n_qubits
18+
self.gamma = gamma
19+
self.T2 = T2
20+
self.shots = shots
21+
self.sigma_noise = sigma_noise
22+
23+
self.times = np.linspace(0.1, 5, 100)
24+
self.B0_true = 1.0
25+
self.omega_true = 1.0
26+
self.phi0_true = 0.0
27+
28+
def B_field(self, t, B0, omega):
29+
return B0 * np.sin(omega * t)
30+
31+
def phase(self, t, B0, omega, phi0):
32+
return self.n_qubits * self.gamma * self.B_field(t, B0, omega) * t + phi0
33+
34+
def bell_density_matrix(self, t, B0, omega, phi0):
35+
theta = self.phase(t, B0, omega, phi0)
36+
decay = np.exp(-t / self.T2)
37+
rho = np.zeros((4, 4), dtype=complex)
38+
rho[0, 0] = rho[3, 3] = 0.5
39+
rho[0, 3] = 0.5 * decay * np.exp(-1j * theta)
40+
rho[3, 0] = 0.5 * decay * np.exp(1j * theta)
41+
return rho
42+
43+
def observable_ZZ(self):
44+
return np.diag([1, -1, -1, 1])
45+
46+
def expectation(self, rho):
47+
ZZ = self.observable_ZZ()
48+
return np.real(np.trace(rho @ ZZ))
49+
50+
def simulate_measurement(self, exp_val):
51+
p = (1 + exp_val) / 2
52+
counts = np.random.binomial(self.shots, p)
53+
return 2 * (counts / self.shots) - 1 + np.random.normal(0, self.sigma_noise)
54+
55+
def generate_data(self):
56+
self.measurements = []
57+
for t in self.times:
58+
rho = self.bell_density_matrix(t, self.B0_true, self.omega_true, self.phi0_true)
59+
exp_val = self.expectation(rho)
60+
meas = self.simulate_measurement(exp_val)
61+
self.measurements.append(meas)
62+
self.measurements = np.array(self.measurements)
63+
64+
def sqrtm(self, matrix):
65+
eigvals, eigvecs = eigh(matrix)
66+
sqrt_vals = np.sqrt(np.maximum(eigvals, 0))
67+
return eigvecs @ np.diag(sqrt_vals) @ eigvecs.T.conj()
68+
69+
def fidelity(self, rho_true, rho_est):
70+
sqrt_rho = self.sqrtm(rho_true)
71+
inter = sqrt_rho @ rho_est @ sqrt_rho
72+
sqrt_inter = self.sqrtm(inter)
73+
return np.real(np.trace(sqrt_inter)) ** 2
74+
75+
def compute_fidelity_curve(self, B0_est, omega_est, phi0_est):
76+
fids = []
77+
for t in self.times:
78+
r_true = self.bell_density_matrix(t, self.B0_true, self.omega_true, self.phi0_true)
79+
r_est = self.bell_density_matrix(t, B0_est, omega_est, phi0_est)
80+
fids.append(self.fidelity(r_true, r_est))
81+
return np.array(fids)
82+
83+
def plot_fidelity_vs_data(self, B0, omega, phi0):
84+
preds = []
85+
for t in self.times:
86+
rho = self.bell_density_matrix(t, B0, omega, phi0)
87+
preds.append(self.expectation(rho))
88+
preds = np.array(preds)
89+
fids = self.compute_fidelity_curve(B0, omega, phi0)
90+
91+
plt.figure(figsize=(12, 4))
92+
93+
plt.subplot(1, 2, 1)
94+
plt.plot(self.times, self.measurements, label='Data', alpha=0.7)
95+
plt.plot(self.times, preds, label='Model Prediction', color='crimson')
96+
plt.xlabel("Time")
97+
plt.ylabel("⟨Z⊗Z⟩")
98+
plt.title("Signal Comparison")
99+
plt.legend()
100+
101+
plt.subplot(1, 2, 2)
102+
plt.plot(self.times, fids, color='seagreen')
103+
plt.ylim(0, 1.05)
104+
plt.xlabel("Time")
105+
plt.ylabel("Fidelity")
106+
plt.title("Fidelity of Bell State Over Time")
107+
108+
plt.tight_layout()
109+
plt.show()
110+
111+
def bayesian_posterior(self, B0_range, omega_range, phi0_range):
112+
B_vals = np.linspace(*B0_range)
113+
omega_vals = np.linspace(*omega_range)
114+
phi_vals = np.linspace(*phi0_range)
115+
posterior = np.zeros((len(B_vals), len(omega_vals), len(phi_vals)))
116+
117+
for i, B0 in enumerate(B_vals):
118+
for j, omega in enumerate(omega_vals):
119+
for k, phi0 in enumerate(phi_vals):
120+
likelihood = 0
121+
for t, meas in zip(self.times, self.measurements):
122+
rho = self.bell_density_matrix(t, B0, omega, phi0)
123+
pred = self.expectation(rho)
124+
likelihood += -((meas - pred) ** 2) / (2 * self.sigma_noise ** 2)
125+
posterior[i, j, k] = np.exp(likelihood)
126+
127+
posterior /= np.sum(posterior)
128+
self.posterior = posterior
129+
self.param_grids = (B_vals, omega_vals, phi_vals)
130+
131+
def plot_posterior_slices(self):
132+
B_vals, omega_vals, phi_vals = self.param_grids
133+
B_slice = np.argmax(np.sum(np.sum(self.posterior, axis=2), axis=1))
134+
omega_slice = np.argmax(np.sum(np.sum(self.posterior, axis=2), axis=0))
135+
phi_slice = np.argmax(np.sum(np.sum(self.posterior, axis=0), axis=0))
136+
137+
fig, axs = plt.subplots(1, 3, figsize=(15, 4))
138+
axs[0].imshow(self.posterior[:, :, phi_slice], aspect='auto',
139+
extent=[omega_vals[0], omega_vals[-1], B_vals[0], B_vals[-1]], origin='lower')
140+
axs[0].set_title(f"Posterior Slice at ϕ₀ = {phi_vals[phi_slice]:.2f}")
141+
axs[0].set_xlabel("ω"), axs[0].set_ylabel("B₀")
142+
143+
axs[1].imshow(self.posterior[:, omega_slice, :], aspect='auto',
144+
extent=[phi_vals[0], phi_vals[-1], B_vals[0], B_vals[-1]], origin='lower')
145+
axs[1].set_title(f"Posterior Slice at ω = {omega_vals[omega_slice]:.2f}")
146+
axs[1].set_xlabel("ϕ₀"), axs[1].set_ylabel("B₀")
147+
148+
axs[2].imshow(self.posterior[B_slice, :, :], aspect='auto',
149+
extent=[phi_vals[0], phi_vals[-1], omega_vals[0], omega_vals[-1]], origin='lower')
150+
axs[2].set_title(f"Posterior Slice at B₀ = {B_vals[B_slice]:.2f}")
151+
axs[2].set_xlabel("ϕ₀"), axs[2].set_ylabel("ω")
152+
153+
plt.tight_layout()
154+
plt.show()
155+
156+
157+
sensor = QuantumSensorBayesian()
158+
sensor.generate_data()
159+
sensor.plot_fidelity_vs_data(B0=1.0, omega=1.0, phi0=0.0)
160+
161+
# Posterior estimation
162+
sensor.bayesian_posterior(
163+
B0_range=(0.8, 1.2, 20),
164+
omega_range=(0.8, 1.2, 20),
165+
phi0_range=(-np.pi, np.pi, 20)
166+
)
167+
sensor.plot_posterior_slices()

0 commit comments

Comments
 (0)