Skip to main content

proof_engine/quantum/
measurement.rs

1use super::schrodinger::{Complex, WaveFunction1D, SchrodingerSolver1D};
2use super::wavefunction::DensityMatrix;
3
4/// A measurement basis: set of eigenstates and corresponding eigenvalues.
5#[derive(Clone, Debug)]
6pub struct MeasurementBasis {
7    pub eigenstates: Vec<Vec<Complex>>,
8    pub eigenvalues: Vec<f64>,
9}
10
11impl MeasurementBasis {
12    pub fn new(eigenstates: Vec<Vec<Complex>>, eigenvalues: Vec<f64>) -> Self {
13        Self { eigenstates, eigenvalues }
14    }
15
16    /// Position basis: delta functions at each grid point.
17    pub fn position_basis(n: usize) -> Self {
18        let mut eigenstates = Vec::with_capacity(n);
19        let eigenvalues: Vec<f64> = (0..n).map(|i| i as f64).collect();
20        for i in 0..n {
21            let mut state = vec![Complex::zero(); n];
22            state[i] = Complex::one();
23            eigenstates.push(state);
24        }
25        Self { eigenstates, eigenvalues }
26    }
27}
28
29/// Perform a projective measurement in the given basis.
30/// Returns (outcome index, collapsed state).
31pub fn measure(psi: &[Complex], basis: &MeasurementBasis, rng_val: f64) -> (usize, Vec<Complex>) {
32    let probabilities: Vec<f64> = basis
33        .eigenstates
34        .iter()
35        .map(|eigenstate| {
36            let overlap = inner_product(eigenstate, psi);
37            overlap.norm_sq()
38        })
39        .collect();
40
41    let total: f64 = probabilities.iter().sum();
42    let mut cumulative = 0.0;
43    let mut outcome = 0;
44
45    for (i, &p) in probabilities.iter().enumerate() {
46        cumulative += p / total;
47        if rng_val < cumulative {
48            outcome = i;
49            break;
50        }
51        if i == probabilities.len() - 1 {
52            outcome = i;
53        }
54    }
55
56    // Collapse to the eigenstate
57    let collapsed = basis.eigenstates[outcome].clone();
58    (outcome, collapsed)
59}
60
61/// Projective measurement with a single projector.
62/// Returns (probability, post-measurement state).
63pub fn projective_measurement(psi: &[Complex], projector: &[Vec<Complex>]) -> (f64, Vec<Complex>) {
64    let n = psi.len();
65    // Apply projector: P|psi>
66    let mut projected = vec![Complex::zero(); n];
67    for i in 0..n {
68        for j in 0..n {
69            projected[i] += projector[i][j] * psi[j];
70        }
71    }
72
73    let probability: f64 = projected.iter().map(|c| c.norm_sq()).sum();
74
75    // Normalize
76    if probability > 1e-30 {
77        let norm = probability.sqrt();
78        for c in &mut projected {
79            *c = *c / norm;
80        }
81    }
82
83    (probability, projected)
84}
85
86/// Inner product <a|b>.
87fn inner_product(a: &[Complex], b: &[Complex]) -> Complex {
88    a.iter()
89        .zip(b.iter())
90        .map(|(ai, bi)| ai.conj() * *bi)
91        .fold(Complex::zero(), |acc, x| acc + x)
92}
93
94/// Apply decoherence to a density matrix: off-diagonal elements decay.
95/// Models Lindblad-type decoherence: rho_ij *= exp(-rate * dt) for i != j.
96pub fn decoherence(rho: &mut DensityMatrix, rate: f64, dt: f64) {
97    let n = rho.dim();
98    let decay = (-rate * dt).exp();
99    for i in 0..n {
100        for j in 0..n {
101            if i != j {
102                rho.rho[i][j] = rho.rho[i][j] * decay;
103            }
104        }
105    }
106}
107
108/// Quantum Zeno effect: frequent measurements freeze evolution.
109/// Simulates time evolution with periodic measurements and returns
110/// the probability of remaining in the initial state at each measurement.
111pub fn quantum_zeno_effect(
112    psi_initial: &[Complex],
113    potential: &[f64],
114    measurement_interval: f64,
115    total_time: f64,
116    dx: f64,
117    mass: f64,
118    hbar: f64,
119) -> Vec<f64> {
120    let n = psi_initial.len();
121    let dt = measurement_interval / 10.0; // sub-steps per measurement
122    let n_measurements = (total_time / measurement_interval).ceil() as usize;
123    let steps_per_measurement = 10;
124
125    let mut wf = WaveFunction1D::new(psi_initial.to_vec(), dx, 0.0);
126    let mut solver = SchrodingerSolver1D::new(wf, potential.to_vec(), mass, hbar, dt);
127
128    let mut survival_probs = Vec::with_capacity(n_measurements);
129
130    for _ in 0..n_measurements {
131        // Evolve
132        for _ in 0..steps_per_measurement {
133            solver.step();
134        }
135
136        // Measure: probability of still being in initial state
137        let overlap = inner_product(psi_initial, &solver.psi.psi);
138        let prob = overlap.norm_sq();
139        survival_probs.push(prob);
140
141        // Collapse back to initial state with probability `prob`
142        // (simulate "yes, still in initial state" outcome)
143        if prob > 0.5 {
144            solver.psi.psi = psi_initial.to_vec();
145        }
146    }
147
148    survival_probs
149}
150
151/// Weak measurement: disturbs the state minimally.
152/// Returns (weak value, post-measurement state).
153pub fn weak_measurement(
154    psi: &[Complex],
155    observable: &[Vec<Complex>],
156    strength: f64,
157) -> (f64, Vec<Complex>) {
158    let n = psi.len();
159
160    // Apply observable: A|psi>
161    let mut a_psi = vec![Complex::zero(); n];
162    for i in 0..n {
163        for j in 0..n {
164            a_psi[i] += observable[i][j] * psi[j];
165        }
166    }
167
168    // Expectation value <A>
169    let exp_a = inner_product(psi, &a_psi);
170
171    // Weak measurement: state is slightly shifted toward eigenstate
172    // |psi'> ~ |psi> + strength * (A - <A>)|psi>
173    let mut result = vec![Complex::zero(); n];
174    for i in 0..n {
175        result[i] = psi[i] + (a_psi[i] - psi[i] * exp_a) * strength;
176    }
177
178    // Normalize
179    let norm_sq: f64 = result.iter().map(|c| c.norm_sq()).sum();
180    let norm = norm_sq.sqrt();
181    if norm > 1e-30 {
182        for c in &mut result {
183            *c = *c / norm;
184        }
185    }
186
187    (exp_a.re, result)
188}
189
190/// Renderer for measurement collapse visualization.
191pub struct MeasurementRenderer {
192    pub width: usize,
193}
194
195impl MeasurementRenderer {
196    pub fn new(width: usize) -> Self {
197        Self { width }
198    }
199
200    /// Render collapse: transition from spread wave to localized spike.
201    /// `collapse_progress` goes from 0 (coherent) to 1 (collapsed).
202    pub fn render(
203        &self,
204        psi: &[Complex],
205        collapse_point: usize,
206        collapse_progress: f64,
207    ) -> Vec<(char, f64, f64, f64)> {
208        let n = psi.len();
209        let mut result = Vec::with_capacity(self.width);
210
211        for i in 0..self.width {
212            let idx = (i * n) / self.width.max(1);
213            let idx = idx.min(n.saturating_sub(1));
214
215            let original_prob = if n > 0 { psi[idx].norm_sq() } else { 0.0 };
216
217            // Interpolate between original distribution and delta function
218            let collapse_idx = (collapse_point * self.width) / n.max(1);
219            let collapsed_prob = if i == collapse_idx.min(self.width - 1) { 1.0 } else { 0.0 };
220
221            let prob = (1.0 - collapse_progress) * original_prob * 10.0 + collapse_progress * collapsed_prob;
222            let brightness = prob.min(1.0);
223
224            let ch = if collapse_progress > 0.8 && i == collapse_idx.min(self.width - 1) {
225                '!'  // flash
226            } else if brightness > 0.5 {
227                '#'
228            } else if brightness > 0.2 {
229                '*'
230            } else if brightness > 0.05 {
231                '.'
232            } else {
233                ' '
234            };
235
236            // Color: blue for coherent, yellow for collapse flash
237            let r = collapse_progress * brightness;
238            let g = collapse_progress * brightness;
239            let b = (1.0 - collapse_progress) * brightness;
240
241            result.push((ch, r, g, b));
242        }
243        result
244    }
245}
246
247/// Born rule utilities.
248pub struct BornRule;
249
250impl BornRule {
251    /// Compute probabilities from amplitudes.
252    pub fn probabilities(amplitudes: &[Complex]) -> Vec<f64> {
253        amplitudes.iter().map(|c| c.norm_sq()).collect()
254    }
255
256    /// Verify normalization: sum of probabilities should be 1.
257    pub fn is_normalized(amplitudes: &[Complex], tolerance: f64) -> bool {
258        let sum: f64 = amplitudes.iter().map(|c| c.norm_sq()).sum();
259        (sum - 1.0).abs() < tolerance
260    }
261
262    /// Sample an outcome from probability distribution.
263    pub fn sample(amplitudes: &[Complex], rng_val: f64) -> usize {
264        let probs = Self::probabilities(amplitudes);
265        let total: f64 = probs.iter().sum();
266        let mut cumulative = 0.0;
267        for (i, &p) in probs.iter().enumerate() {
268            cumulative += p / total;
269            if rng_val < cumulative {
270                return i;
271            }
272        }
273        amplitudes.len() - 1
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_born_rule_probabilities() {
283        let amps = vec![
284            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
285            Complex::new(0.0, 1.0 / 2.0_f64.sqrt()),
286        ];
287        let probs = BornRule::probabilities(&amps);
288        assert!((probs[0] - 0.5).abs() < 1e-10);
289        assert!((probs[1] - 0.5).abs() < 1e-10);
290    }
291
292    #[test]
293    fn test_born_rule_normalized() {
294        let amps = vec![
295            Complex::new(0.6, 0.0),
296            Complex::new(0.0, 0.8),
297        ];
298        assert!(BornRule::is_normalized(&amps, 1e-10));
299    }
300
301    #[test]
302    fn test_measurement_collapses() {
303        let psi = vec![
304            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
305            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
306        ];
307        let basis = MeasurementBasis::new(
308            vec![
309                vec![Complex::one(), Complex::zero()],
310                vec![Complex::zero(), Complex::one()],
311            ],
312            vec![0.0, 1.0],
313        );
314
315        let (outcome, collapsed) = measure(&psi, &basis, 0.3);
316        assert!(outcome == 0 || outcome == 1);
317        // Collapsed state should be an eigenstate
318        let norm: f64 = collapsed.iter().map(|c| c.norm_sq()).sum();
319        assert!((norm - 1.0).abs() < 1e-10);
320    }
321
322    #[test]
323    fn test_projective_measurement() {
324        let psi = vec![
325            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
326            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
327        ];
328        // Projector onto |0>
329        let projector = vec![
330            vec![Complex::one(), Complex::zero()],
331            vec![Complex::zero(), Complex::zero()],
332        ];
333        let (prob, state) = projective_measurement(&psi, &projector);
334        assert!((prob - 0.5).abs() < 1e-10, "Projection prob: {}", prob);
335        assert!((state[0].norm() - 1.0).abs() < 1e-10);
336        assert!(state[1].norm() < 1e-10);
337    }
338
339    #[test]
340    fn test_decoherence() {
341        let psi = vec![
342            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
343            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
344        ];
345        let mut dm = DensityMatrix::from_pure_state(&psi);
346        assert!((dm.purity() - 1.0).abs() < 1e-10);
347
348        // Apply strong decoherence
349        for _ in 0..100 {
350            decoherence(&mut dm, 10.0, 0.1);
351        }
352
353        // Should be approximately diagonal (maximally mixed for equal amplitudes)
354        assert!(dm.rho[0][1].norm() < 0.01, "Off-diagonal: {:?}", dm.rho[0][1]);
355        assert!(dm.rho[1][0].norm() < 0.01);
356        // Diagonal should be preserved
357        assert!((dm.rho[0][0].re - 0.5).abs() < 0.01);
358        assert!((dm.rho[1][1].re - 0.5).abs() < 0.01);
359    }
360
361    #[test]
362    fn test_quantum_zeno_effect() {
363        // Particle in a box, initially in ground state
364        let n = 64;
365        let dx = 0.1;
366        let potential = vec![0.0; n];
367        let sigma = 1.0;
368        let psi: Vec<Complex> = (0..n)
369            .map(|i| {
370                let x = -3.2 + i as f64 * dx;
371                Complex::new((-x * x / (2.0 * sigma * sigma)).exp(), 0.0)
372            })
373            .collect();
374        let mut psi_norm = psi.clone();
375        let norm_sq: f64 = psi_norm.iter().map(|c| c.norm_sq()).sum::<f64>() * dx;
376        let norm = norm_sq.sqrt();
377        for c in &mut psi_norm {
378            *c = *c / norm;
379        }
380
381        // Frequent measurements
382        let probs_frequent = quantum_zeno_effect(&psi_norm, &potential, 0.01, 0.1, dx, 1.0, 1.0);
383
384        // Infrequent measurements
385        let probs_infrequent = quantum_zeno_effect(&psi_norm, &potential, 0.05, 0.1, dx, 1.0, 1.0);
386
387        // Frequent measurements should maintain higher survival probability
388        if !probs_frequent.is_empty() && !probs_infrequent.is_empty() {
389            let avg_frequent: f64 = probs_frequent.iter().sum::<f64>() / probs_frequent.len() as f64;
390            let avg_infrequent: f64 = probs_infrequent.iter().sum::<f64>() / probs_infrequent.len() as f64;
391            assert!(
392                avg_frequent >= avg_infrequent - 0.2,
393                "Zeno: frequent={}, infrequent={}",
394                avg_frequent,
395                avg_infrequent
396            );
397        }
398    }
399
400    #[test]
401    fn test_weak_measurement() {
402        let psi = vec![
403            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
404            Complex::new(1.0 / 2.0_f64.sqrt(), 0.0),
405        ];
406        // Pauli Z as observable
407        let obs = vec![
408            vec![Complex::one(), Complex::zero()],
409            vec![Complex::zero(), Complex::new(-1.0, 0.0)],
410        ];
411        let (weak_val, post_state) = weak_measurement(&psi, &obs, 0.01);
412        // For equal superposition, <Z> = 0
413        assert!(weak_val.abs() < 1e-10, "Weak value: {}", weak_val);
414        // State should be barely changed
415        let overlap = inner_product(&psi, &post_state).norm_sq();
416        assert!(overlap > 0.99, "Overlap: {}", overlap);
417    }
418
419    #[test]
420    fn test_measurement_renderer() {
421        let psi: Vec<Complex> = (0..64)
422            .map(|i| {
423                let x = -3.2 + i as f64 * 0.1;
424                Complex::new((-x * x / 2.0).exp(), 0.0)
425            })
426            .collect();
427        let renderer = MeasurementRenderer::new(30);
428        let before = renderer.render(&psi, 32, 0.0);
429        let after = renderer.render(&psi, 32, 1.0);
430        assert_eq!(before.len(), 30);
431        assert_eq!(after.len(), 30);
432        // After collapse, should have a flash character
433        let flashes: usize = after.iter().filter(|&&(c, _, _, _)| c == '!').count();
434        assert!(flashes > 0);
435    }
436
437    #[test]
438    fn test_born_rule_sample() {
439        let amps = vec![
440            Complex::new(1.0, 0.0),
441            Complex::zero(),
442        ];
443        // Should always sample 0
444        assert_eq!(BornRule::sample(&amps, 0.5), 0);
445        assert_eq!(BornRule::sample(&amps, 0.99), 0);
446    }
447}