wifi-densepose/vendor/ruvector/crates/ruqu-exotic/src/quantum_decay.rs

424 lines
15 KiB
Rust

//! Quantum Decay -- Embeddings decohere instead of being deleted
//!
//! Treats f64 embedding vectors as quantum state amplitudes. Applies quantum
//! noise channels (dephasing, amplitude damping) over time instead of TTL
//! deletion. Cold vectors lose phase fidelity before magnitude, and similarity
//! degrades smoothly rather than disappearing at a hard deadline.
//!
//! # Model
//!
//! Two physical noise channels are applied each time [`QuantumEmbedding::decohere`]
//! is called:
//!
//! 1. **Dephasing (T2)** -- random Rz-like phase kicks on every amplitude.
//! Magnitudes are preserved but phase coherence is scrambled.
//! 2. **Amplitude damping (T1)** -- amplitudes decay toward the |0> ground
//! state, modelling energy dissipation. Probability leaked from excited
//! states is transferred to the ground state.
//!
//! The `noise_rate` parameter controls how aggressively both channels act per
//! unit of abstract time `dt`.
use ruqu_core::state::QuantumState;
use ruqu_core::types::Complex;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use std::f64::consts::PI;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// Compute the minimum number of qubits needed to hold `len` amplitudes.
/// Always returns at least 1 (QuantumState requires num_qubits >= 1).
fn required_qubits(len: usize) -> u32 {
if len <= 2 {
return 1;
}
let mut n = 1u32;
while (1usize << n) < len {
n += 1;
}
n
}
// ---------------------------------------------------------------------------
// QuantumEmbedding
// ---------------------------------------------------------------------------
/// A vector embedding treated as a quantum state that decoheres over time.
///
/// Classical f64 values are normalised and zero-padded to the next power of two,
/// then stored as complex amplitudes of a [`QuantumState`]. Decoherence is
/// modelled by applying stochastic phase and amplitude noise, causing the
/// fidelity with the original state to decay smoothly.
pub struct QuantumEmbedding {
/// Embedding encoded as quantum amplitudes.
state: QuantumState,
/// Snapshot of amplitudes at creation for fidelity tracking.
original_state: Vec<Complex>,
/// Dimensionality of the original embedding before power-of-2 padding.
original_dim: usize,
/// Abstract time units elapsed since creation.
age: f64,
/// Decoherence rate per time unit.
noise_rate: f64,
}
impl QuantumEmbedding {
/// Create from a classical f64 embedding vector.
///
/// The embedding is L2-normalised and encoded as purely-real quantum
/// amplitudes. If the length is not a power of two, the vector is
/// zero-padded. An empty or all-zero embedding is mapped to the |0>
/// computational basis state.
pub fn from_embedding(embedding: &[f64], noise_rate: f64) -> Self {
let original_dim = embedding.len().max(1);
let num_qubits = required_qubits(original_dim);
let padded_len = 1usize << num_qubits;
// L2 normalisation factor
let norm_sq: f64 = embedding.iter().map(|x| x * x).sum();
let inv_norm = if norm_sq > 0.0 {
1.0 / norm_sq.sqrt()
} else {
0.0
};
// Build zero-padded amplitude vector
let mut amps = vec![Complex::ZERO; padded_len];
for (i, &val) in embedding.iter().enumerate() {
amps[i] = Complex::new(val * inv_norm, 0.0);
}
// Degenerate case: put all probability in |0>
if inv_norm == 0.0 {
amps[0] = Complex::ONE;
}
let original_state = amps.clone();
let state = QuantumState::from_amplitudes(amps, num_qubits)
.expect("padded amplitude vector length must equal 2^num_qubits");
Self {
state,
original_state,
original_dim,
age: 0.0,
noise_rate,
}
}
/// Apply decoherence for `dt` time units.
///
/// Two noise channels act in sequence:
///
/// 1. **Dephasing** -- every amplitude is multiplied by e^{i*theta} where
/// theta is drawn uniformly from `[-pi * noise_rate * dt, pi * noise_rate * dt]`.
/// This scrambles phase coherence while exactly preserving per-amplitude
/// probabilities.
///
/// 2. **Amplitude damping** -- each non-ground-state amplitude is scaled by
/// sqrt(1 - gamma) where gamma = 1 - e^{-noise_rate * dt}. The probability
/// leaked from excited states is added to the |0> ground state, then the
/// whole vector is renormalised.
///
/// The `seed` controls the pseudo-random number generator for
/// reproducibility.
pub fn decohere(&mut self, dt: f64, seed: u64) {
let mut rng = StdRng::seed_from_u64(seed);
// Damping parameter gamma in [0, 1), approaches 1 for large dt * rate
let gamma = 1.0 - (-self.noise_rate * dt).exp();
// Phase noise scale in [0, inf)
let phase_scale = self.noise_rate * dt;
let amps = self.state.amplitudes_mut();
let n = amps.len();
// ------ Phase noise (dephasing) ------
for amp in amps.iter_mut().take(n) {
let angle = (rng.gen::<f64>() - 0.5) * 2.0 * PI * phase_scale;
let phase_kick = Complex::from_polar(1.0, angle);
*amp = *amp * phase_kick;
}
// ------ Amplitude damping toward |0> ------
let decay_factor = (1.0 - gamma).sqrt();
let mut leaked_probability = 0.0;
for amp in amps.iter_mut().skip(1) {
let prob_before = amp.norm_sq();
*amp = *amp * decay_factor;
leaked_probability += prob_before - amp.norm_sq();
}
// Transfer leaked probability into the ground state
let p0 = amps[0].norm_sq();
let new_p0 = p0 + leaked_probability;
if new_p0 > 0.0 && p0 > 0.0 {
amps[0] = amps[0] * (new_p0 / p0).sqrt();
} else if new_p0 > 0.0 {
amps[0] = Complex::new(new_p0.sqrt(), 0.0);
}
// Correct any accumulated numerical drift
self.state.normalize();
self.age += dt;
}
/// Fidelity with the original state: |<original|current>|^2 in [0, 1].
///
/// Returns 1.0 for a freshly created embedding (perfect memory) and
/// decays toward 0.0 as the state decoheres (completely forgotten).
pub fn fidelity(&self) -> f64 {
let current = self.state.state_vector();
let mut inner = Complex::ZERO;
for (orig, cur) in self.original_state.iter().zip(current.iter()) {
inner = inner + orig.conj() * *cur;
}
inner.norm_sq()
}
/// Current age of this embedding in abstract time units.
pub fn age(&self) -> f64 {
self.age
}
/// Quantum-aware similarity: |<self|other>|^2 as a complex inner product.
///
/// Unlike cosine similarity, this captures phase relationships. Two
/// embeddings that have decohered along different random trajectories will
/// show reduced similarity even if their probability distributions are
/// similar, because their phases no longer align.
pub fn quantum_similarity(&self, other: &QuantumEmbedding) -> f64 {
let sv1 = self.state.state_vector();
let sv2 = other.state.state_vector();
let len = sv1.len().min(sv2.len());
let mut inner = Complex::ZERO;
for i in 0..len {
inner = inner + sv1[i].conj() * sv2[i];
}
inner.norm_sq()
}
/// Extract back to a classical f64 vector.
///
/// Returns the real part of each amplitude, truncated to the original
/// embedding dimension. This is lossy when the state has decohered:
/// dephasing moves energy into imaginary components that are discarded,
/// and amplitude damping shifts probability toward |0>.
pub fn to_embedding(&self) -> Vec<f64> {
self.state
.state_vector()
.iter()
.take(self.original_dim)
.map(|c| c.re)
.collect()
}
/// Check if the embedding has decohered below a fidelity threshold.
///
/// Returns `true` when the state still retains at least `threshold`
/// fidelity with its original value.
pub fn is_coherent(&self, threshold: f64) -> bool {
self.fidelity() >= threshold
}
}
// ---------------------------------------------------------------------------
// Batch operations
// ---------------------------------------------------------------------------
/// Apply decoherence to a batch of embeddings, returning indices of those
/// still coherent.
///
/// Each embedding is decohered by `dt` time units using a unique seed derived
/// from the base `seed` and the embedding's index. Embeddings whose fidelity
/// drops below `threshold` are considered forgotten; the returned vector
/// contains the indices of embeddings that remain coherent.
pub fn decohere_batch(
embeddings: &mut [QuantumEmbedding],
dt: f64,
threshold: f64,
seed: u64,
) -> Vec<usize> {
let mut coherent = Vec::new();
for (i, emb) in embeddings.iter_mut().enumerate() {
// Derive a per-embedding seed to avoid correlated noise
let emb_seed = seed
.wrapping_add(i as u64)
.wrapping_mul(6_364_136_223_846_793_005);
emb.decohere(dt, emb_seed);
if emb.is_coherent(threshold) {
coherent.push(i);
}
}
coherent
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
/// Helper: create a simple embedding of the given dimension.
fn sample_embedding(dim: usize) -> Vec<f64> {
(0..dim).map(|i| (i as f64 + 1.0)).collect()
}
#[test]
fn from_embedding_creates_normalised_state() {
let emb = QuantumEmbedding::from_embedding(&[3.0, 4.0], 0.1);
let sv = emb.state.state_vector();
let norm_sq: f64 = sv.iter().map(|c| c.norm_sq()).sum();
assert!((norm_sq - 1.0).abs() < 1e-10, "state should be normalised");
}
#[test]
fn from_embedding_pads_to_power_of_two() {
let emb = QuantumEmbedding::from_embedding(&[1.0, 2.0, 3.0], 0.1);
// 3 elements -> 4 (2 qubits)
assert_eq!(emb.state.state_vector().len(), 4);
assert_eq!(emb.state.num_qubits(), 2);
}
#[test]
fn fresh_embedding_has_unit_fidelity() {
let emb = QuantumEmbedding::from_embedding(&[1.0, 0.0, 0.0, 0.0], 0.1);
assert!((emb.fidelity() - 1.0).abs() < 1e-10);
}
#[test]
fn decoherence_reduces_fidelity() {
let mut emb = QuantumEmbedding::from_embedding(&sample_embedding(4), 0.5);
let f_before = emb.fidelity();
emb.decohere(1.0, 42);
let f_after = emb.fidelity();
assert!(
f_after < f_before,
"fidelity should decrease: before={f_before}, after={f_after}"
);
}
#[test]
fn decoherence_advances_age() {
let mut emb = QuantumEmbedding::from_embedding(&[1.0, 2.0], 0.1);
assert!((emb.age() - 0.0).abs() < 1e-15);
emb.decohere(0.5, 1);
assert!((emb.age() - 0.5).abs() < 1e-15);
emb.decohere(1.5, 2);
assert!((emb.age() - 2.0).abs() < 1e-15);
}
#[test]
fn heavy_decoherence_destroys_fidelity() {
let mut emb = QuantumEmbedding::from_embedding(&sample_embedding(8), 2.0);
for i in 0..20 {
emb.decohere(1.0, 100 + i);
}
assert!(
emb.fidelity() < 0.3,
"heavy decoherence should destroy fidelity: {}",
emb.fidelity()
);
}
#[test]
fn quantum_similarity_is_symmetric() {
let a = QuantumEmbedding::from_embedding(&[1.0, 0.0, 0.0, 0.0], 0.1);
let b = QuantumEmbedding::from_embedding(&[0.0, 1.0, 0.0, 0.0], 0.1);
let sim_ab = a.quantum_similarity(&b);
let sim_ba = b.quantum_similarity(&a);
assert!(
(sim_ab - sim_ba).abs() < 1e-10,
"similarity should be symmetric"
);
}
#[test]
fn identical_embeddings_have_similarity_one() {
let a = QuantumEmbedding::from_embedding(&[1.0, 2.0, 3.0, 4.0], 0.1);
let b = QuantumEmbedding::from_embedding(&[1.0, 2.0, 3.0, 4.0], 0.1);
assert!(
(a.quantum_similarity(&b) - 1.0).abs() < 1e-10,
"identical embeddings should have similarity 1.0"
);
}
#[test]
fn to_embedding_round_trips_without_decoherence() {
let original = vec![3.0, 4.0];
let emb = QuantumEmbedding::from_embedding(&original, 0.1);
let recovered = emb.to_embedding();
assert_eq!(recovered.len(), original.len());
// Should be the normalised version of the original
let norm = (3.0f64 * 3.0 + 4.0 * 4.0).sqrt();
assert!((recovered[0] - 3.0 / norm).abs() < 1e-10);
assert!((recovered[1] - 4.0 / norm).abs() < 1e-10);
}
#[test]
fn is_coherent_respects_threshold() {
let mut emb = QuantumEmbedding::from_embedding(&sample_embedding(4), 1.0);
assert!(emb.is_coherent(0.9));
// Decohere heavily
for i in 0..10 {
emb.decohere(1.0, 200 + i);
}
assert!(!emb.is_coherent(0.99));
}
#[test]
fn decohere_batch_filters_correctly() {
let mut batch: Vec<QuantumEmbedding> = (0..5)
.map(|i| {
QuantumEmbedding::from_embedding(
&sample_embedding(4),
// Higher noise rate for later embeddings
0.1 * (i as f64 + 1.0),
)
})
.collect();
let coherent = decohere_batch(&mut batch, 1.0, 0.3, 999);
// Embeddings with lower noise rates should remain coherent longer
// At least the lowest-noise-rate embedding should survive
assert!(
!coherent.is_empty(),
"at least some embeddings should remain coherent with mild decoherence"
);
// The first embedding (lowest noise) should be the most likely to survive
if !coherent.is_empty() {
assert_eq!(coherent[0], 0, "lowest-noise embedding should survive");
}
}
#[test]
fn empty_embedding_handled() {
let emb = QuantumEmbedding::from_embedding(&[], 0.1);
assert!((emb.fidelity() - 1.0).abs() < 1e-10);
let recovered = emb.to_embedding();
// original_dim is max(0, 1) = 1
assert_eq!(recovered.len(), 1);
}
#[test]
fn zero_noise_rate_preserves_fidelity() {
let mut emb = QuantumEmbedding::from_embedding(&sample_embedding(4), 0.0);
emb.decohere(10.0, 42);
// With noise_rate=0, gamma=0 and phase_scale=0, so no change
assert!(
(emb.fidelity() - 1.0).abs() < 1e-10,
"zero noise rate should preserve fidelity perfectly"
);
}
}