Skip to main content

ruvector_consciousness/
collapse.rs

1//! Quantum-inspired consciousness collapse.
2//!
3//! Models the partition search as a quantum-inspired process:
4//! each bipartition exists in superposition with an amplitude
5//! proportional to 1/sqrt(information_loss). Grover-like iterations
6//! amplify the MIP, then "measurement" collapses to it.
7//!
8//! This provides a sublinear approximation for finding the minimum
9//! information partition without exhaustive search.
10
11use crate::arena::PhiArena;
12use crate::error::ConsciousnessError;
13use crate::traits::ConsciousnessCollapse;
14use crate::types::{Bipartition, PhiAlgorithm, PhiResult, TransitionMatrix};
15
16use rand::rngs::StdRng;
17use rand::{Rng, SeedableRng};
18use std::f64::consts::PI;
19use std::time::Instant;
20
21/// Quantum-inspired partition collapse engine.
22///
23/// Uses amplitude-based search inspired by Grover's algorithm:
24/// 1. Initialize uniform amplitudes over sampled partitions
25/// 2. Oracle: phase-rotate proportional to information loss (low loss = high rotation)
26/// 3. Diffusion: inversion about the mean amplitude
27/// 4. Collapse: sample from |amplitude|² distribution
28///
29/// Achieves approximately √N speedup over exhaustive search.
30pub struct QuantumCollapseEngine {
31    /// Number of partitions to hold in the "register".
32    register_size: usize,
33}
34
35impl QuantumCollapseEngine {
36    pub fn new(register_size: usize) -> Self {
37        Self { register_size }
38    }
39}
40
41impl Default for QuantumCollapseEngine {
42    fn default() -> Self {
43        Self {
44            register_size: 256,
45        }
46    }
47}
48
49impl ConsciousnessCollapse for QuantumCollapseEngine {
50    fn collapse_to_mip(
51        &self,
52        tpm: &TransitionMatrix,
53        iterations: usize,
54        seed: u64,
55    ) -> Result<PhiResult, ConsciousnessError> {
56        let n = tpm.n;
57        let start = Instant::now();
58        let arena = PhiArena::with_capacity(n * n * 16);
59
60        let mut rng = StdRng::seed_from_u64(seed);
61        let total_partitions = (1u64 << n) - 2;
62
63        // Sample partitions into the register.
64        let reg_size = self.register_size.min(total_partitions as usize);
65        let mut partitions: Vec<Bipartition> = Vec::with_capacity(reg_size);
66        let mut seen = std::collections::HashSet::new();
67
68        while partitions.len() < reg_size {
69            let mask = loop {
70                let m = rng.gen::<u64>() & ((1u64 << n) - 1);
71                if m != 0 && m != (1u64 << n) - 1 && !seen.contains(&m) {
72                    break m;
73                }
74            };
75            seen.insert(mask);
76            partitions.push(Bipartition { mask, n });
77        }
78
79        // Compute information loss for each partition.
80        let losses: Vec<f64> = partitions
81            .iter()
82            .map(|p| {
83                let loss = super::phi::partition_information_loss_pub(tpm, 0, p, &arena);
84                arena.reset();
85                loss
86            })
87            .collect();
88
89        // Initialize amplitudes: uniform superposition.
90        let inv_sqrt = 1.0 / (reg_size as f64).sqrt();
91        let mut amplitudes: Vec<f64> = vec![inv_sqrt; reg_size];
92
93        // Grover-like iterations.
94        let optimal_iters = iterations.min(((reg_size as f64).sqrt() * PI / 4.0) as usize);
95
96        for _ in 0..optimal_iters {
97            // Oracle: phase-rotate based on information loss.
98            // Low loss = high phase kick (we want to amplify the minimum).
99            let max_loss = losses.iter().copied().fold(f64::MIN, f64::max);
100            if max_loss < 1e-15 {
101                break;
102            }
103            let inv_max = 1.0 / max_loss;
104
105            for i in 0..reg_size {
106                let relevance = 1.0 - (losses[i] * inv_max);
107                let phase = PI * relevance;
108                amplitudes[i] *= phase.cos();
109            }
110
111            // Diffusion: inversion about the mean.
112            let mean: f64 = amplitudes.iter().sum::<f64>() / reg_size as f64;
113            for amp in &mut amplitudes {
114                *amp = 2.0 * mean - *amp;
115            }
116        }
117
118        // Collapse: sample from |amplitude|² distribution.
119        let probs: Vec<f64> = amplitudes.iter().map(|a| a * a).collect();
120        let total_prob: f64 = probs.iter().sum();
121
122        let best_idx = if total_prob > 1e-15 {
123            // Weighted sampling.
124            let r: f64 = rng.gen::<f64>() * total_prob;
125            let mut cumsum = 0.0;
126            let mut selected = 0;
127            for (i, &p) in probs.iter().enumerate() {
128                cumsum += p;
129                if cumsum >= r {
130                    selected = i;
131                    break;
132                }
133            }
134            selected
135        } else {
136            // Fallback: pick the one with minimum loss.
137            losses
138                .iter()
139                .enumerate()
140                .min_by(|a, b| a.1.partial_cmp(b.1).unwrap())
141                .map(|(i, _)| i)
142                .unwrap_or(0)
143        };
144
145        Ok(PhiResult {
146            phi: losses[best_idx],
147            mip: partitions[best_idx].clone(),
148            partitions_evaluated: reg_size as u64,
149            total_partitions,
150            algorithm: PhiAlgorithm::Collapse,
151            elapsed: start.elapsed(),
152            convergence: vec![losses[best_idx]],
153        })
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    fn simple_tpm() -> TransitionMatrix {
162        #[rustfmt::skip]
163        let data = vec![
164            0.5, 0.25, 0.25, 0.0,
165            0.5, 0.25, 0.25, 0.0,
166            0.5, 0.25, 0.25, 0.0,
167            0.0, 0.0,  0.0,  1.0,
168        ];
169        TransitionMatrix::new(4, data)
170    }
171
172    #[test]
173    fn collapse_finds_partition() {
174        let tpm = simple_tpm();
175        let engine = QuantumCollapseEngine::new(32);
176        let result = engine.collapse_to_mip(&tpm, 10, 42).unwrap();
177        assert!(result.phi >= 0.0);
178        assert!(result.mip.is_valid());
179    }
180
181    #[test]
182    fn collapse_deterministic_with_seed() {
183        let tpm = simple_tpm();
184        let engine = QuantumCollapseEngine::new(32);
185        let r1 = engine.collapse_to_mip(&tpm, 10, 42).unwrap();
186        let r2 = engine.collapse_to_mip(&tpm, 10, 42).unwrap();
187        assert_eq!(r1.mip, r2.mip);
188    }
189}