Skip to content

Commit 044ea98

Browse files
committed
adding sensing codes
1 parent 64e2366 commit 044ea98

File tree

3 files changed

+359
-0
lines changed

3 files changed

+359
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from matplotlib.widgets import Slider
4+
from scipy.optimize import curve_fit
5+
import matplotlib.animation as animation
6+
7+
# -------------------- PARAMETERS --------------------
8+
n_qubits = 3
9+
gamma = 1.0
10+
B0_true = 0.8
11+
omega_true = 1.0
12+
phi0_true = 0.0
13+
T2 = 3.0
14+
shots = 100
15+
sigma_noise = 0.05
16+
times = np.linspace(0.1, 5, 100)
17+
18+
# -------------------- FUNCTIONS --------------------
19+
def B_field(t, B0, omega):
20+
return B0 * np.sin(omega * t)
21+
22+
def ideal_expectation(t, B0, omega, phi0):
23+
phase = gamma * n_qubits * B0 * np.sin(omega * t) * t + phi0
24+
return np.cos(phase)
25+
26+
def decohered_expectation(t, B0, omega, phi0, T2):
27+
return ideal_expectation(t, B0, omega, phi0) * np.exp(-t / T2)
28+
29+
def noisy_measurement(expectation, shots, sigma):
30+
p = (1 + expectation) / 2
31+
counts = np.random.binomial(shots, p)
32+
mean_measurement = 2 * (counts / shots) - 1
33+
return mean_measurement + np.random.normal(0, sigma)
34+
35+
def fit_model(t, B0, omega, phi0):
36+
phase = gamma * n_qubits * B0 * np.sin(omega * t) * t + phi0
37+
return np.cos(phase) * np.exp(-t / T2)
38+
39+
# -------------------- SIMULATE DATA --------------------
40+
true_expectations = []
41+
measured_expectations = []
42+
for t in times:
43+
exp_t = decohered_expectation(t, B0_true, omega_true, phi0_true, T2)
44+
meas_t = noisy_measurement(exp_t, shots, sigma_noise)
45+
true_expectations.append(exp_t)
46+
measured_expectations.append(meas_t)
47+
48+
# -------------------- CURVE FIT (MAP ESTIMATE) --------------------
49+
initial_guess = [0.5, 1.2, 0.1]
50+
params_opt, _ = curve_fit(fit_model, times, measured_expectations, p0=initial_guess)
51+
B0_map, omega_map, phi0_map = params_opt
52+
53+
# -------------------- INTERACTIVE FIT VIEWER --------------------
54+
fig, ax = plt.subplots(figsize=(10, 5))
55+
plt.subplots_adjust(bottom=0.3)
56+
ax.scatter(times, measured_expectations, label='Noisy Measurements', color='red', s=10)
57+
ax.plot(times, true_expectations, label='True Signal', linewidth=2, alpha=0.5)
58+
model_line, = ax.plot(times, fit_model(times, B0_map, omega_map, phi0_map),
59+
label='Model Fit', color='green', linewidth=2)
60+
ax.set_title("Interactive Quantum Sensor Fit")
61+
ax.set_xlabel("Time [s]")
62+
ax.set_ylabel(r"$\langle X^{\otimes n} \rangle$")
63+
ax.legend()
64+
ax.grid(True)
65+
66+
# Sliders
67+
axcolor = 'lightgoldenrodyellow'
68+
ax_B0 = plt.axes([0.15, 0.2, 0.7, 0.03], facecolor=axcolor)
69+
ax_omega = plt.axes([0.15, 0.15, 0.7, 0.03], facecolor=axcolor)
70+
ax_phi0 = plt.axes([0.15, 0.1, 0.7, 0.03], facecolor=axcolor)
71+
72+
slider_B0 = Slider(ax_B0, 'B₀', 0.3, 1.2, valinit=B0_map)
73+
slider_omega = Slider(ax_omega, 'ω', 0.5, 1.5, valinit=omega_map)
74+
slider_phi0 = Slider(ax_phi0, 'φ₀', -np.pi, np.pi, valinit=phi0_map)
75+
76+
def update(val):
77+
B0 = slider_B0.val
78+
omega = slider_omega.val
79+
phi0 = slider_phi0.val
80+
model_line.set_ydata(fit_model(times, B0, omega, phi0))
81+
fig.canvas.draw_idle()
82+
83+
slider_B0.on_changed(update)
84+
slider_omega.on_changed(update)
85+
slider_phi0.on_changed(update)
86+
87+
plt.show()
88+
89+
# -------------------- TRUE PHASE ANIMATION --------------------
90+
true_phase = gamma * n_qubits * B_field(times, B0_true, omega_true) * times + phi0_true
91+
wrapped_phase = np.mod(true_phase + np.pi, 2 * np.pi) - np.pi # wrap to [-π, π]
92+
93+
fig_phase, ax_phase = plt.subplots(figsize=(8, 4))
94+
ax_phase.set_title("True Quantum Phase Evolution")
95+
ax_phase.set_xlabel("Time [s]")
96+
ax_phase.set_ylabel("Phase [rad]")
97+
ax_phase.set_ylim([-np.pi, np.pi])
98+
ax_phase.grid(True)
99+
100+
(line_phase,) = ax_phase.plot([], [], lw=2, label='True Phase')
101+
(time_dot,) = ax_phase.plot([], [], 'ro')
102+
ax_phase.legend()
103+
104+
def init():
105+
line_phase.set_data([], [])
106+
time_dot.set_data([], [])
107+
return line_phase, time_dot
108+
109+
def animate(i):
110+
t = times[:i]
111+
phi = wrapped_phase[:i]
112+
line_phase.set_data(t, phi)
113+
if i > 0:
114+
time_dot.set_data(t[-1], phi[-1])
115+
return line_phase, time_dot
116+
117+
ani = animation.FuncAnimation(
118+
fig_phase, animate, frames=len(times), init_func=init,
119+
blit=True, interval=100, repeat=False
120+
)
121+
122+
plt.show()
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
# -------------------- Prior Parameter Grid --------------------
5+
B0_vals = np.linspace(0.5, 1.1, 60)
6+
omega_vals = np.linspace(0.7, 1.3, 60)
7+
phi0_vals = np.linspace(-np.pi, np.pi, 60)
8+
9+
# -------------------- Simulation Parameters --------------------
10+
n_qubits = 3
11+
gamma = 1.0
12+
T2 = 3.0
13+
sigma_noise = 0.05
14+
shots = 100
15+
times = np.linspace(0.1, 5, 100)
16+
17+
# True values of the magnetic field
18+
B0_true = 0.8
19+
omega_true = 1.0
20+
phi0_true = 0.0
21+
22+
# -------------------- Field and Measurement Functions --------------------
23+
def B_field(t, B0, omega):
24+
return B0 * np.sin(omega * t)
25+
26+
def ideal_expectation(t, B0, omega, phi0):
27+
phase = gamma * n_qubits * B0 * np.sin(omega * t) * t + phi0
28+
return np.cos(phase)
29+
30+
def decohered_expectation(t, B0, omega, phi0, T2):
31+
return ideal_expectation(t, B0, omega, phi0) * np.exp(-t / T2)
32+
33+
def noisy_measurement(expectation, shots, sigma):
34+
p = (1 + expectation) / 2
35+
counts = np.random.binomial(shots, p)
36+
mean = 2 * (counts / shots) - 1
37+
return mean + np.random.normal(0, sigma)
38+
39+
# -------------------- Generate Simulated Measurements --------------------
40+
measured_expectations = []
41+
for t in times:
42+
exp_t = decohered_expectation(t, B0_true, omega_true, phi0_true, T2)
43+
meas_t = noisy_measurement(exp_t, shots, sigma_noise)
44+
measured_expectations.append(meas_t)
45+
measured_expectations = np.array(measured_expectations)
46+
47+
# -------------------- Compute Likelihood Grid --------------------
48+
posterior = np.zeros((len(B0_vals), len(omega_vals), len(phi0_vals)))
49+
50+
for i, B0 in enumerate(B0_vals):
51+
for j, omega in enumerate(omega_vals):
52+
for k, phi0 in enumerate(phi0_vals):
53+
preds = decohered_expectation(times, B0, omega, phi0, T2)
54+
log_likelihood = -np.sum((measured_expectations - preds)**2) / (2 * sigma_noise**2)
55+
posterior[i, j, k] = np.exp(log_likelihood)
56+
57+
# Normalize the posterior
58+
posterior /= np.sum(posterior)
59+
60+
# -------------------- Marginals and MAP Estimate --------------------
61+
posterior_B0 = np.sum(posterior, axis=(1, 2))
62+
posterior_omega = np.sum(posterior, axis=(0, 2))
63+
posterior_phi0 = np.sum(posterior, axis=(0, 1))
64+
65+
i_max, j_max, k_max = np.unravel_index(np.argmax(posterior), posterior.shape)
66+
B0_map = B0_vals[i_max]
67+
omega_map = omega_vals[j_max]
68+
phi0_map = phi0_vals[k_max]
69+
70+
print(f"MAP Estimates: B0 = {B0_map:.3f}, omega = {omega_map:.3f}, phi0 = {phi0_map:.3f}")
71+
72+
# -------------------- Posterior Marginal Plots --------------------
73+
fig, axs = plt.subplots(3, 1, figsize=(8, 8))
74+
75+
axs[0].plot(B0_vals, posterior_B0, label='Posterior p(B₀)', color='navy')
76+
axs[0].axvline(B0_true, color='green', linestyle='--', label='True B₀')
77+
axs[0].axvline(B0_map, color='red', linestyle=':', label='MAP B₀')
78+
axs[0].legend()
79+
axs[0].set_title("Posterior Distribution for B₀")
80+
81+
axs[1].plot(omega_vals, posterior_omega, label='Posterior p(ω)', color='purple')
82+
axs[1].axvline(omega_true, color='green', linestyle='--', label='True ω')
83+
axs[1].axvline(omega_map, color='red', linestyle=':', label='MAP ω')
84+
axs[1].legend()
85+
axs[1].set_title("Posterior Distribution for ω")
86+
87+
axs[2].plot(phi0_vals, posterior_phi0, label='Posterior p(φ₀)', color='darkorange')
88+
axs[2].axvline(phi0_true, color='green', linestyle='--', label='True φ₀')
89+
axs[2].axvline(phi0_map, color='red', linestyle=':', label='MAP φ₀')
90+
axs[2].legend()
91+
axs[2].set_title("Posterior Distribution for φ₀")
92+
93+
plt.tight_layout()
94+
plt.show()
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
class QuantumSensor:
5+
def __init__(self, n_qubits=3, gamma=1.0, T2=3.0, sigma_noise=0.05, shots=100):
6+
self.n_qubits = n_qubits
7+
self.gamma = gamma
8+
self.T2 = T2
9+
self.sigma_noise = sigma_noise
10+
self.shots = shots
11+
12+
self.times = np.linspace(0.1, 5, 100)
13+
14+
# True field parameters
15+
self.B0_true = 0.8
16+
self.omega_true = 1.0
17+
self.phi0_true = 0.0
18+
19+
# Bayesian grid
20+
self.B0_vals = np.linspace(0.5, 1.1, 60)
21+
self.omega_vals = np.linspace(0.7, 1.3, 60)
22+
self.phi0_vals = np.linspace(-np.pi, np.pi, 60)
23+
24+
self.posterior = None
25+
self.posterior_B0 = None
26+
self.posterior_omega = None
27+
self.posterior_phi0 = None
28+
self.map_estimates = None
29+
30+
def B_field(self, t, B0, omega):
31+
return B0 * np.sin(omega * t)
32+
33+
def ideal_expectation(self, t, B0, omega, phi0):
34+
phase = self.gamma * self.n_qubits * self.B_field(t, B0, omega) * t + phi0
35+
return np.cos(phase)
36+
37+
def decohered_expectation(self, t, B0, omega, phi0):
38+
return self.ideal_expectation(t, B0, omega, phi0) * np.exp(-t / self.T2)
39+
40+
def noisy_measurement(self, expectation):
41+
p = (1 + expectation) / 2
42+
counts = np.random.binomial(self.shots, p)
43+
mean = 2 * (counts / self.shots) - 1
44+
return mean + np.random.normal(0, self.sigma_noise)
45+
46+
def generate_measurements(self):
47+
self.measurements = []
48+
for t in self.times:
49+
exp_val = self.decohered_expectation(t, self.B0_true, self.omega_true, self.phi0_true)
50+
meas = self.noisy_measurement(exp_val)
51+
self.measurements.append(meas)
52+
self.measurements = np.array(self.measurements)
53+
54+
def compute_posterior(self):
55+
B0_vals, omega_vals, phi0_vals = self.B0_vals, self.omega_vals, self.phi0_vals
56+
posterior = np.zeros((len(B0_vals), len(omega_vals), len(phi0_vals)))
57+
58+
for i, B0 in enumerate(B0_vals):
59+
for j, omega in enumerate(omega_vals):
60+
for k, phi0 in enumerate(phi0_vals):
61+
preds = self.decohered_expectation(self.times, B0, omega, phi0)
62+
log_like = -np.sum((self.measurements - preds)**2) / (2 * self.sigma_noise**2)
63+
posterior[i, j, k] = np.exp(log_like)
64+
65+
posterior /= np.sum(posterior)
66+
self.posterior = posterior
67+
68+
self.posterior_B0 = np.sum(posterior, axis=(1, 2))
69+
self.posterior_omega = np.sum(posterior, axis=(0, 2))
70+
self.posterior_phi0 = np.sum(posterior, axis=(0, 1))
71+
72+
i_max, j_max, k_max = np.unravel_index(np.argmax(posterior), posterior.shape)
73+
self.map_estimates = (
74+
B0_vals[i_max],
75+
omega_vals[j_max],
76+
phi0_vals[k_max]
77+
)
78+
79+
def print_results(self):
80+
B0_map, omega_map, phi0_map = self.map_estimates
81+
print("MAP Estimates:")
82+
print(f" B₀ = {B0_map:.3f} (true = {self.B0_true})")
83+
print(f" ω = {omega_map:.3f} (true = {self.omega_true})")
84+
print(f" φ₀ = {phi0_map:.3f} (true = {self.phi0_true})")
85+
86+
def plot_posteriors(self):
87+
fig, axs = plt.subplots(3, 1, figsize=(8, 8))
88+
axs[0].plot(self.B0_vals, self.posterior_B0, label='Posterior p(B₀)', color='navy')
89+
axs[0].axvline(self.B0_true, color='green', linestyle='--', label='True B₀')
90+
axs[0].axvline(self.map_estimates[0], color='red', linestyle=':', label='MAP B₀')
91+
axs[0].legend()
92+
axs[0].set_title("Posterior Distribution for B₀")
93+
94+
axs[1].plot(self.omega_vals, self.posterior_omega, label='Posterior p(ω)', color='purple')
95+
axs[1].axvline(self.omega_true, color='green', linestyle='--', label='True ω')
96+
axs[1].axvline(self.map_estimates[1], color='red', linestyle=':', label='MAP ω')
97+
axs[1].legend()
98+
axs[1].set_title("Posterior Distribution for ω")
99+
100+
axs[2].plot(self.phi0_vals, self.posterior_phi0, label='Posterior p(φ₀)', color='darkorange')
101+
axs[2].axvline(self.phi0_true, color='green', linestyle='--', label='True φ₀')
102+
axs[2].axvline(self.map_estimates[2], color='red', linestyle=':', label='MAP φ₀')
103+
axs[2].legend()
104+
axs[2].set_title("Posterior Distribution for φ₀")
105+
106+
plt.tight_layout()
107+
plt.show()
108+
109+
def estimate_fidelity(self):
110+
def evolved_state_vector(B0, omega, phi0, t):
111+
phase = self.gamma * self.n_qubits * B0 * np.sin(omega * t) * t + phi0
112+
return np.array([np.cos(phase), np.sin(phase)])
113+
114+
fidelities = []
115+
for t in self.times:
116+
true_state = evolved_state_vector(self.B0_true, self.omega_true, self.phi0_true, t)
117+
est_state = evolved_state_vector(*self.map_estimates, t)
118+
fid = np.abs(np.dot(true_state, est_state))**2
119+
fidelities.append(fid)
120+
121+
avg_fidelity = np.mean(fidelities)
122+
print(f"Average Fidelity between true and estimated quantum state: {avg_fidelity:.4f}")
123+
124+
# Optional: plot fidelity over time
125+
plt.figure(figsize=(7, 3))
126+
plt.plot(self.times, fidelities, label="Fidelity over time", color="teal")
127+
plt.xlabel("Time")
128+
plt.ylabel("Fidelity")
129+
plt.title("Quantum State Fidelity vs Time")
130+
plt.ylim(0, 1.05)
131+
plt.grid(True)
132+
plt.legend()
133+
plt.tight_layout()
134+
plt.show()
135+
136+
137+
# -------------------- Run the Sensor Simulation --------------------
138+
sensor = QuantumSensor()
139+
sensor.generate_measurements()
140+
sensor.compute_posterior()
141+
sensor.print_results()
142+
sensor.plot_posteriors()
143+
sensor.estimate_fidelity()

0 commit comments

Comments
 (0)