|
| 1 | +import numpy as np |
| 2 | +import matplotlib.pyplot as plt |
| 3 | +from scipy.linalg import expm |
| 4 | + |
| 5 | +class QuantumSensor: |
| 6 | + def __init__(self, shots=1000, time_steps=100): |
| 7 | + self.shots = shots |
| 8 | + self.time_steps = time_steps |
| 9 | + self.state = self.create_bell_state() |
| 10 | + self.measurement_results = np.zeros(shots) |
| 11 | + self.time_points = np.linspace(0, 10, self.time_steps) |
| 12 | + # Original magnetic field B is still a 3x100 array representing Bx, By, Bz over time |
| 13 | + self.B = np.array([np.cos(self.time_points), np.sin(self.time_points), np.zeros_like(self.time_points)]) |
| 14 | + self.estimated_Bx = None |
| 15 | + self.estimated_By = None |
| 16 | + self.estimated_Bz = None |
| 17 | + |
| 18 | + @staticmethod |
| 19 | + def create_bell_state(): |
| 20 | + """Create an entangled Bell state.""" |
| 21 | + # Returns a 4x1 column vector |
| 22 | + return (1/np.sqrt(2)) * (np.kron(np.array([[1], [0]]), np.array([[1], [0]])) + |
| 23 | + np.kron(np.array([[0], [1]]), np.array([[0], [1]]))) |
| 24 | + |
| 25 | + @staticmethod |
| 26 | + def single_qubit_hamiltonian(B): |
| 27 | + """Create the Hamiltonian for a single qubit in a magnetic field B.""" |
| 28 | + ħ = 1 # Planck's constant |
| 29 | + X = np.array([[0, 1], [1, 0]], dtype=complex) |
| 30 | + Y = np.array([[0, -1j], [1j, 0]], dtype=complex) |
| 31 | + Z = np.array([[1, 0], [0, -1]], dtype=complex) |
| 32 | + |
| 33 | + return -ħ * (B[0] * X + B[1] * Y + B[2] * Z) |
| 34 | + |
| 35 | + @staticmethod |
| 36 | + def two_qubit_hamiltonian(B): |
| 37 | + """Create the Hamiltonian for a two-qubit system.""" |
| 38 | + # Identity matrix for a single qubit |
| 39 | + I = np.array([[1, 0], [0, 1]], dtype=complex) |
| 40 | + # Single qubit Hamiltonian |
| 41 | + H_single = QuantumSensor.single_qubit_hamiltonian(B) |
| 42 | + |
| 43 | + # Total Hamiltonian is the sum of the Hamiltonians on each qubit (assuming no interaction) |
| 44 | + # H_total = H_single (on qubit 1) + H_single (on qubit 2) |
| 45 | + # In tensor product notation: H_total = H_single \otimes I + I \otimes H_single |
| 46 | + return np.kron(H_single, I) + np.kron(I, H_single) |
| 47 | + |
| 48 | + |
| 49 | + @staticmethod |
| 50 | + def evolve_state(state, H, time): |
| 51 | + """Evolve the state using the unitary operator.""" |
| 52 | + # H should now be a 4x4 matrix |
| 53 | + U = expm(-1j * H * time) |
| 54 | + # state is a 4x1 vector |
| 55 | + # U @ state will result in a 4x1 vector |
| 56 | + return U @ state |
| 57 | + |
| 58 | + def measure(self, state): |
| 59 | + """Measure the state in the standard basis.""" |
| 60 | + probabilities = np.abs(state.flatten())**2 |
| 61 | + # Ensure probabilities sum to 1 to avoid issues with floating point inaccuracies |
| 62 | + probabilities /= np.sum(probabilities) |
| 63 | + outcome = np.random.choice(range(len(probabilities)), p=probabilities) |
| 64 | + return outcome, state.flatten()[outcome] |
| 65 | + |
| 66 | + |
| 67 | + def simulate(self): |
| 68 | + """Run the simulation for a given number of shots.""" |
| 69 | + for shot in range(self.shots): |
| 70 | + evolved_state = self.state |
| 71 | + for t in range(self.time_steps): |
| 72 | + # Use the two-qubit Hamiltonian |
| 73 | + H = self.two_qubit_hamiltonian(self.B[:, t]) |
| 74 | + # Evolve the 4-dimensional state with the 4x4 Hamiltonian |
| 75 | + evolved_state = self.evolve_state(evolved_state, H, 0.1) |
| 76 | + |
| 77 | + # Measure the final state |
| 78 | + outcome, _ = self.measure(evolved_state) |
| 79 | + self.measurement_results[shot] = outcome |
| 80 | + |
| 81 | + def estimate_magnetic_field(self): |
| 82 | + """Estimate the magnetic field from measurement outcomes.""" |
| 83 | + |
| 84 | + # Take the average of the measurement results to infer the magnetic field |
| 85 | + n_0 = np.sum(self.measurement_results == 0) # Number of outcomes corresponding to |00> |
| 86 | + n_1 = np.sum(self.measurement_results == 1) # Number of outcomes corresponding to |01> |
| 87 | + n_2 = np.sum(self.measurement_results == 2) # Number of outcomes corresponding to |10> |
| 88 | + n_3 = np.sum(self.measurement_results == 3) # Number of outcomes corresponding to |11> |
| 89 | + |
| 90 | + counts = np.array([n_0, n_1, n_2, n_3]) |
| 91 | + probabilities = counts / self.shots |
| 92 | + |
| 93 | + # Using probabilities to estimate the components of the magnetic field |
| 94 | + # These simplified estimations are likely based on the expected probabilities |
| 95 | + # of measuring |01> and |10> states for a Bell state evolving under Bx and By fields. |
| 96 | + # A more rigorous estimation might involve a maximum likelihood estimation or similar. |
| 97 | + self.estimated_Bx = 2 * (probabilities[1] - probabilities[2]) # Simplified estimation |
| 98 | + self.estimated_By = 2 * (probabilities[3] - probabilities[0]) # Simplified estimation |
| 99 | + self.estimated_Bz = 0 # Assuming Bz doesn't cause |00> or |11> to change in this simplified model |
| 100 | + |
| 101 | + |
| 102 | + def plot_results(self): |
| 103 | + """Plot the histogram of measurement outcomes.""" |
| 104 | + plt.figure(figsize=(10, 6)) |
| 105 | + # Use align='left' and add 0.5 to bins for proper alignment with xticks |
| 106 | + plt.hist(self.measurement_results, bins=np.arange(5) - 0.5, density=True, alpha=0.7, color='blue', edgecolor='black', align='left') |
| 107 | + plt.xticks(range(4)) |
| 108 | + plt.xlabel('Measurement Outcome') |
| 109 | + plt.ylabel('Probability') |
| 110 | + plt.title(f'Histogram of Measurement Outcomes over {self.shots} Shots') |
| 111 | + plt.grid() |
| 112 | + plt.show() |
| 113 | + |
| 114 | + def plot_estimated_field(self): |
| 115 | + """Plot the estimated magnetic field against the actual magnetic field.""" |
| 116 | + plt.figure(figsize=(10, 6)) |
| 117 | + |
| 118 | + plt.subplot(3, 1, 1) |
| 119 | + plt.plot(self.time_points, self.B[0], 'r-', label='Actual Bx') |
| 120 | + # Estimated Bx is a single value, plot as a horizontal line |
| 121 | + plt.axhline(self.estimated_Bx, color='blue', linestyle='--', label=f'Estimated Bx = {self.estimated_Bx:.2f}') |
| 122 | + plt.title('Estimated vs Actual Magnetic Field') |
| 123 | + plt.ylabel('Bx') |
| 124 | + plt.legend() |
| 125 | + plt.grid() |
| 126 | + |
| 127 | + plt.subplot(3, 1, 2) |
| 128 | + plt.plot(self.time_points, self.B[1], 'g-', label='Actual By') |
| 129 | + # Estimated By is a single value, plot as a horizontal line |
| 130 | + plt.axhline(self.estimated_By, color='blue', linestyle='--', label=f'Estimated By = {self.estimated_By:.2f}') |
| 131 | + plt.ylabel('By') |
| 132 | + plt.legend() |
| 133 | + plt.grid() |
| 134 | + |
| 135 | + plt.subplot(3, 1, 3) |
| 136 | + # Estimated Bz is fixed at 0 in this model, plot as a horizontal line |
| 137 | + plt.axhline(self.estimated_Bz, color='blue', linestyle='--', label='Estimated Bz = 0') |
| 138 | + plt.ylabel('Bz') |
| 139 | + plt.xlabel('Time') |
| 140 | + plt.legend() |
| 141 | + plt.grid() |
| 142 | + |
| 143 | + plt.tight_layout() |
| 144 | + plt.show() |
| 145 | + |
| 146 | +# Main execution |
| 147 | +if __name__ == "__main__": |
| 148 | + sensor = QuantumSensor(shots=1000, time_steps=100) |
| 149 | + sensor.simulate() |
| 150 | + sensor.plot_results() |
| 151 | + sensor.estimate_magnetic_field() |
| 152 | + |
| 153 | + estimated_Bx, estimated_By, estimated_Bz = sensor.estimated_Bx, sensor.estimated_By, sensor.estimated_Bz |
| 154 | + print(f"Estimated Magnetic Field: Bx = {estimated_Bx:.2f}, By = {estimated_By:.2f}, Bz = {estimated_Bz:.2f}") |
| 155 | + |
| 156 | + sensor.plot_estimated_field() |
| 157 | + |
0 commit comments