Files
wifi-densepose/vendor/ruvector/crates/prime-radiant/tests/chaos_tests.rs

773 lines
22 KiB
Rust

//! Chaos Tests for Coherence Engine
//!
//! Tests system behavior under adversarial and random conditions:
//! - Random energy spikes
//! - Throttling behavior under load
//! - Recovery from extreme states
//! - Concurrent modifications
//! - Edge case handling
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
// ============================================================================
// TEST INFRASTRUCTURE
// ============================================================================
/// Coherence gate with throttling
#[derive(Clone)]
struct ThrottledGate {
green_threshold: f32,
amber_threshold: f32,
red_threshold: f32,
current_throttle: f32, // 0.0 = no throttle, 1.0 = max throttle
blocked_count: u64,
throttled_count: u64,
allowed_count: u64,
}
impl ThrottledGate {
fn new(green: f32, amber: f32, red: f32) -> Self {
Self {
green_threshold: green,
amber_threshold: amber,
red_threshold: red,
current_throttle: 0.0,
blocked_count: 0,
throttled_count: 0,
allowed_count: 0,
}
}
fn decide(&mut self, energy: f32) -> Decision {
if energy < self.green_threshold {
self.current_throttle = (self.current_throttle - 0.1).max(0.0);
self.allowed_count += 1;
Decision::Allow
} else if energy < self.amber_threshold {
let throttle_factor =
(energy - self.green_threshold) / (self.amber_threshold - self.green_threshold);
self.current_throttle = (self.current_throttle + throttle_factor * 0.1).min(1.0);
self.throttled_count += 1;
Decision::Throttle {
factor: throttle_factor,
}
} else {
self.current_throttle = 1.0;
self.blocked_count += 1;
Decision::Block
}
}
fn should_process(&self, rng: &mut impl Rng) -> bool {
if self.current_throttle <= 0.0 {
true
} else {
rng.gen::<f32>() > self.current_throttle
}
}
fn stats(&self) -> GateStats {
let total = self.allowed_count + self.throttled_count + self.blocked_count;
GateStats {
total_decisions: total,
allowed: self.allowed_count,
throttled: self.throttled_count,
blocked: self.blocked_count,
allow_rate: if total > 0 {
self.allowed_count as f64 / total as f64
} else {
1.0
},
block_rate: if total > 0 {
self.blocked_count as f64 / total as f64
} else {
0.0
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum Decision {
Allow,
Throttle { factor: f32 },
Block,
}
#[derive(Debug)]
struct GateStats {
total_decisions: u64,
allowed: u64,
throttled: u64,
blocked: u64,
allow_rate: f64,
block_rate: f64,
}
/// Simple coherence state for chaos testing
struct ChaosState {
nodes: HashMap<u64, Vec<f32>>,
edges: HashMap<(u64, u64), f32>,
operation_count: AtomicU64,
}
impl ChaosState {
fn new() -> Self {
Self {
nodes: HashMap::new(),
edges: HashMap::new(),
operation_count: AtomicU64::new(0),
}
}
fn add_node(&mut self, id: u64, state: Vec<f32>) {
self.nodes.insert(id, state);
self.operation_count.fetch_add(1, Ordering::Relaxed);
}
fn add_edge(&mut self, src: u64, tgt: u64, weight: f32) {
if self.nodes.contains_key(&src) && self.nodes.contains_key(&tgt) {
self.edges.insert((src, tgt), weight);
self.operation_count.fetch_add(1, Ordering::Relaxed);
}
}
fn compute_energy(&self) -> f32 {
let mut total = 0.0;
for ((src, tgt), weight) in &self.edges {
if let (Some(s), Some(t)) = (self.nodes.get(src), self.nodes.get(tgt)) {
let dim = s.len().min(t.len());
let residual: f32 = s
.iter()
.take(dim)
.zip(t.iter().take(dim))
.map(|(a, b)| (a - b).powi(2))
.sum();
total += weight * residual;
}
}
total
}
fn perturb_node(&mut self, id: u64, rng: &mut impl Rng) {
if let Some(state) = self.nodes.get_mut(&id) {
for val in state.iter_mut() {
*val += rng.gen_range(-0.1..0.1);
}
self.operation_count.fetch_add(1, Ordering::Relaxed);
}
}
}
// ============================================================================
// CHAOS: RANDOM ENERGY SPIKES
// ============================================================================
#[test]
fn test_random_energy_spikes() {
let mut rng = ChaCha8Rng::seed_from_u64(42);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
let mut energies = Vec::new();
let mut decisions = Vec::new();
// Generate random energy values with occasional spikes
for _ in 0..1000 {
let base = rng.gen_range(0.0..0.2);
let spike = if rng.gen_bool(0.1) {
rng.gen_range(0.0..2.0) // 10% chance of spike
} else {
0.0
};
let energy = base + spike;
energies.push(energy);
decisions.push(gate.decide(energy));
}
let stats = gate.stats();
// Verify system handled spikes appropriately
// With 10% spike rate and spikes going up to 2.0 (well above amber threshold),
// we expect a mix of decisions
assert!(stats.blocked > 0, "Should have blocked some spikes");
assert!(
stats.allowed > 0,
"Should have allowed low-energy operations"
);
// Allow rate depends on threshold settings - with spikes going to amber/red zone,
// we expect at least some operations to be allowed (the 90% non-spike operations)
assert!(
stats.allow_rate > 0.3,
"Should have allowed at least 30% of operations (got {})",
stats.allow_rate
);
}
#[test]
fn test_sustained_spike_triggers_persistent_block() {
let mut rng = ChaCha8Rng::seed_from_u64(123);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Normal operations
for _ in 0..50 {
let energy = rng.gen_range(0.0..0.1);
gate.decide(energy);
}
assert!(
gate.current_throttle < 0.1,
"Should have low throttle initially"
);
// Sustained high energy
for _ in 0..20 {
gate.decide(0.8);
}
assert!(
gate.current_throttle > 0.5,
"Should have high throttle after sustained spikes"
);
// Verify recovery is gradual
let throttle_before = gate.current_throttle;
for _ in 0..10 {
gate.decide(0.05);
}
assert!(
gate.current_throttle < throttle_before,
"Throttle should decrease after normal operations"
);
}
#[test]
fn test_spike_patterns() {
let mut rng = ChaCha8Rng::seed_from_u64(456);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Pattern 1: Regular low-high oscillation
for i in 0..100 {
let energy = if i % 2 == 0 { 0.05 } else { 0.8 };
gate.decide(energy);
}
let stats1 = gate.stats();
// Reset
gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Pattern 2: Bursts
for burst in 0..10 {
// Low energy burst
for _ in 0..8 {
gate.decide(0.05);
}
// High energy burst
for _ in 0..2 {
gate.decide(0.9);
}
}
let stats2 = gate.stats();
// Both patterns have the same 20% high-energy ratio but different distributions.
// Pattern 1: random distribution across iterations
// Pattern 2: burst pattern (8 low, 2 high per burst)
// The key invariant is that both should have some blocks from the high-energy operations
assert!(stats1.blocked > 0, "Pattern 1 should have blocks");
assert!(stats2.blocked > 0, "Pattern 2 should have blocks");
// Both should process 100 operations
assert_eq!(stats1.total_decisions, 100);
assert_eq!(stats2.total_decisions, 100);
}
// ============================================================================
// CHAOS: THROTTLING UNDER LOAD
// ============================================================================
#[test]
fn test_throttling_fairness() {
let mut rng = ChaCha8Rng::seed_from_u64(789);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Put gate into throttled state
for _ in 0..10 {
gate.decide(0.3);
}
// Count how many requests get through
let mut processed = 0;
let mut total = 0;
for _ in 0..1000 {
total += 1;
if gate.should_process(&mut rng) {
processed += 1;
}
}
let process_rate = processed as f64 / total as f64;
// Should be roughly inverse of throttle
let expected_rate = 1.0 - gate.current_throttle as f64;
assert!(
(process_rate - expected_rate).abs() < 0.1,
"Process rate {} should be close to expected {}",
process_rate,
expected_rate
);
}
#[test]
fn test_throttling_response_time() {
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Measure response time under different throttle states
let measure_response = |gate: &mut ThrottledGate, energy: f32| {
let start = Instant::now();
for _ in 0..100 {
gate.decide(energy);
}
start.elapsed()
};
let low_energy_time = measure_response(&mut gate, 0.05);
gate = ThrottledGate::new(0.1, 0.5, 1.0);
let high_energy_time = measure_response(&mut gate, 0.8);
// Decision time should be similar regardless of energy level
let ratio = high_energy_time.as_nanos() as f64 / low_energy_time.as_nanos() as f64;
assert!(
ratio < 10.0,
"High energy decisions shouldn't be much slower (ratio: {})",
ratio
);
}
#[test]
fn test_progressive_throttling() {
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
let mut throttle_history = Vec::new();
// Gradually increase energy
for i in 0..100 {
let energy = i as f32 / 100.0; // 0.0 to 1.0
gate.decide(energy);
throttle_history.push(gate.current_throttle);
}
// Throttle should generally increase
let increasing_segments = throttle_history
.windows(10)
.filter(|w| w.last() > w.first())
.count();
assert!(
increasing_segments > 5,
"Throttle should generally increase with energy"
);
}
// ============================================================================
// CHAOS: CONCURRENT MODIFICATIONS
// ============================================================================
#[test]
fn test_concurrent_state_modifications() {
let state = Arc::new(Mutex::new(ChaosState::new()));
// Initialize some nodes
{
let mut s = state.lock().unwrap();
for i in 0..100 {
s.add_node(i, vec![i as f32 / 100.0; 4]);
}
for i in 0..99 {
s.add_edge(i, i + 1, 1.0);
}
}
// Spawn threads that concurrently modify state
let handles: Vec<_> = (0..4)
.map(|thread_id| {
let state = Arc::clone(&state);
thread::spawn(move || {
let mut rng = ChaCha8Rng::seed_from_u64(thread_id);
for _ in 0..100 {
let mut s = state.lock().unwrap();
let node_id = rng.gen_range(0..100);
s.perturb_node(node_id, &mut rng);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
// State should still be valid
let s = state.lock().unwrap();
assert_eq!(s.nodes.len(), 100);
assert_eq!(s.edges.len(), 99);
// Energy should be computable
let energy = s.compute_energy();
assert!(energy.is_finite(), "Energy should be finite");
}
#[test]
fn test_concurrent_energy_computation() {
let state = Arc::new(Mutex::new(ChaosState::new()));
// Initialize
{
let mut s = state.lock().unwrap();
for i in 0..50 {
s.add_node(i, vec![i as f32 / 50.0; 4]);
}
for i in 0..49 {
s.add_edge(i, i + 1, 1.0);
}
}
// Concurrent energy computations
let handles: Vec<_> = (0..8)
.map(|_| {
let state = Arc::clone(&state);
thread::spawn(move || {
let s = state.lock().unwrap();
s.compute_energy()
})
})
.collect();
let energies: Vec<f32> = handles.into_iter().map(|h| h.join().unwrap()).collect();
// All computations should give the same result
let first = energies[0];
for e in &energies {
assert!(
(e - first).abs() < 1e-6,
"Concurrent computations should give same result"
);
}
}
// ============================================================================
// CHAOS: EXTREME VALUES
// ============================================================================
#[test]
fn test_extreme_energy_values() {
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Very small energy
let decision = gate.decide(1e-10);
assert_eq!(decision, Decision::Allow);
// Very large energy
let decision = gate.decide(1e10);
assert_eq!(decision, Decision::Block);
// Zero
let decision = gate.decide(0.0);
assert_eq!(decision, Decision::Allow);
// Negative (should still work, though unusual)
let decision = gate.decide(-0.1);
assert_eq!(decision, Decision::Allow); // Less than green threshold
}
#[test]
fn test_extreme_state_values() {
let mut state = ChaosState::new();
// Very large state values
state.add_node(1, vec![1e10, -1e10, 1e10, -1e10]);
state.add_node(2, vec![-1e10, 1e10, -1e10, 1e10]);
state.add_edge(1, 2, 1.0);
let energy = state.compute_energy();
assert!(energy.is_finite(), "Energy should handle large values");
assert!(energy > 0.0, "Energy should be positive");
}
#[test]
fn test_many_small_perturbations() {
let mut rng = ChaCha8Rng::seed_from_u64(999);
let mut state = ChaosState::new();
// Create a stable baseline
state.add_node(1, vec![0.5, 0.5, 0.5, 0.5]);
state.add_node(2, vec![0.5, 0.5, 0.5, 0.5]);
state.add_edge(1, 2, 1.0);
let initial_energy = state.compute_energy();
// Many small perturbations
for _ in 0..1000 {
state.perturb_node(1, &mut rng);
state.perturb_node(2, &mut rng);
}
let final_energy = state.compute_energy();
// Energy should still be reasonable (not exploded)
assert!(final_energy.is_finite());
// Random walk should increase variance
assert!(final_energy > initial_energy * 0.1 || final_energy < initial_energy * 10.0);
}
// ============================================================================
// CHAOS: RECOVERY SCENARIOS
// ============================================================================
#[test]
fn test_recovery_from_blocked_state() {
let mut rng = ChaCha8Rng::seed_from_u64(111);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Drive into blocked state
for _ in 0..20 {
gate.decide(0.9);
}
assert!(
gate.current_throttle > 0.9,
"Should be in high throttle state"
);
// Recover with low energy
let mut recovery_steps = 0;
while gate.current_throttle > 0.1 && recovery_steps < 200 {
gate.decide(0.05);
recovery_steps += 1;
}
assert!(
recovery_steps < 200,
"Should recover within reasonable time"
);
assert!(
gate.current_throttle < 0.2,
"Should have low throttle after recovery"
);
}
#[test]
fn test_oscillation_dampening() {
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
// Oscillate between extremes
let mut throttle_variance = Vec::new();
for cycle in 0..10 {
// High phase
for _ in 0..5 {
gate.decide(0.8);
}
// Low phase
for _ in 0..5 {
gate.decide(0.05);
}
throttle_variance.push(gate.current_throttle);
}
// Throttle should not oscillate wildly
let max = throttle_variance
.iter()
.cloned()
.fold(f32::NEG_INFINITY, f32::max);
let min = throttle_variance
.iter()
.cloned()
.fold(f32::INFINITY, f32::min);
// Should settle to some stable-ish range
// (This is a soft check - exact behavior depends on parameters)
assert!(max - min < 1.0, "Throttle oscillation should be bounded");
}
// ============================================================================
// CHAOS: RANDOM GRAPH MODIFICATIONS
// ============================================================================
#[test]
fn test_random_graph_operations() {
let mut rng = ChaCha8Rng::seed_from_u64(222);
let mut state = ChaosState::new();
// Random operations
for _ in 0..1000 {
let op = rng.gen_range(0..3);
match op {
0 => {
// Add node
let id = rng.gen_range(0..100);
let dim = rng.gen_range(2..8);
let values: Vec<f32> = (0..dim).map(|_| rng.gen_range(-1.0..1.0)).collect();
state.add_node(id, values);
}
1 => {
// Add edge
let src = rng.gen_range(0..100);
let tgt = rng.gen_range(0..100);
if src != tgt {
let weight = rng.gen_range(0.1..2.0);
state.add_edge(src, tgt, weight);
}
}
2 => {
// Perturb existing node
let id = rng.gen_range(0..100);
state.perturb_node(id, &mut rng);
}
_ => {}
}
}
// State should be valid
assert!(state.nodes.len() <= 100);
let energy = state.compute_energy();
assert!(energy.is_finite());
}
// ============================================================================
// CHAOS: STRESS TESTS
// ============================================================================
#[test]
fn test_rapid_fire_decisions() {
let mut rng = ChaCha8Rng::seed_from_u64(333);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
let start = Instant::now();
let mut count = 0;
while start.elapsed() < Duration::from_millis(100) {
let energy = rng.gen_range(0.0..0.6);
gate.decide(energy);
count += 1;
}
assert!(count > 1000, "Should process many decisions quickly");
let stats = gate.stats();
assert_eq!(stats.total_decisions, count);
}
#[test]
fn test_memory_stability() {
let mut rng = ChaCha8Rng::seed_from_u64(444);
let mut state = ChaosState::new();
// Many cycles of add/modify
for cycle in 0..100 {
// Add phase
for i in 0..10 {
let id = cycle * 10 + i;
state.add_node(id, vec![rng.gen::<f32>(); 4]);
}
// Modify phase
for _ in 0..50 {
let id = rng.gen_range(0..(cycle + 1) * 10);
state.perturb_node(id, &mut rng);
}
// Energy check
let energy = state.compute_energy();
assert!(
energy.is_finite(),
"Energy should be finite at cycle {}",
cycle
);
}
assert!(state.nodes.len() > 0);
}
// ============================================================================
// CHAOS: DETERMINISTIC CHAOS (SEEDED RANDOM)
// ============================================================================
#[test]
fn test_seeded_chaos_reproducible() {
fn run_chaos(seed: u64) -> (f32, u64, u64, u64) {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let mut gate = ThrottledGate::new(0.1, 0.5, 1.0);
let mut state = ChaosState::new();
// Add nodes with random states
for i in 0..100 {
state.add_node(i, vec![rng.gen::<f32>(); 4]);
}
// Add edges to create energy (without edges, compute_energy is always 0)
for i in 0..50 {
let src = rng.gen_range(0..100);
let tgt = rng.gen_range(0..100);
if src != tgt {
state.add_edge(src, tgt, rng.gen_range(0.1..1.0));
}
}
for _ in 0..500 {
let energy = state.compute_energy();
gate.decide(energy / 100.0);
let node_id = rng.gen_range(0..100);
state.perturb_node(node_id, &mut rng);
}
let stats = gate.stats();
(
state.compute_energy(),
stats.allowed,
stats.throttled,
stats.blocked,
)
}
let result1 = run_chaos(12345);
let result2 = run_chaos(12345);
// Same seed should produce same results (using approximate comparison for floats
// due to potential floating point ordering differences)
assert!(
(result1.0 - result2.0).abs() < 0.01,
"Same seed should produce same energy: {} vs {}",
result1.0,
result2.0
);
assert_eq!(
result1.1, result2.1,
"Same seed should produce same allowed count"
);
assert_eq!(
result1.2, result2.2,
"Same seed should produce same throttled count"
);
assert_eq!(
result1.3, result2.3,
"Same seed should produce same blocked count"
);
// Use very different seeds to ensure different random sequences
let result3 = run_chaos(99999);
// At minimum, the final energy should differ between different seeds
assert!(
(result1.0 - result3.0).abs() > 0.001 || result1.1 != result3.1 || result1.2 != result3.2,
"Different seeds should produce different results: seed1={:?}, seed2={:?}",
result1,
result3
);
}