Skip to main content

experiments/
experiments.rs

1// One-off experiment runner
2use constraint_crdt::*;
3
4fn main() {
5    println!("╔══════════════════════════════════════════════════╗");
6    println!("║  CONSTRAINT-CRDT NOVEL EXPERIMENTS              ║");
7    println!("╚══════════════════════════════════════════════════╝\n");
8
9    experiment_1_bloom();
10    experiment_2_geometric();
11    experiment_3_decay();
12    experiment_4_sketch();
13}
14
15fn experiment_1_bloom() {
16    println!("=== Experiment 1: Bloom Filter CRDT ===\n");
17
18    for &n in &[1_000, 10_000, 100_000] {
19        let mut bf = bloom::BloomCRDT::new(n, 0.01);
20        let items: Vec<String> = (0..n).map(|i| format!("constraint_{}", i)).collect();
21        for item in &items { bf.insert(item); }
22
23        // Measure false positive rate
24        let mut fp = 0;
25        let trials = 100_000;
26        for i in n..n+trials {
27            if bf.contains(&format!("constraint_{}", i)) { fp += 1; }
28        }
29        let measured_fpr = fp as f64 / trials as f64;
30
31        // Space comparison
32        let bloom_bytes = bf.wire_size();
33        let exact_bytes = n * 32; // ~32 bytes per constraint ID
34        let compression = exact_bytes as f64 / bloom_bytes as f64;
35
36        println!("  n={:>6}: FPR={:.4} (target 0.01), space={:>6} bytes ({:.1}x compression), fill={:.2}%",
37            n, measured_fpr, bloom_bytes, compression, bf.fill_ratio() * 100.0);
38    }
39
40    // Merge two Bloom filters
41    let mut a = bloom::BloomCRDT::new(1000, 0.01);
42    let mut b = bloom::BloomCRDT::new(1000, 0.01);
43    for i in 0..500 { a.insert(&format!("a_{}", i)); }
44    for i in 500..1000 { b.insert(&format!("b_{}", i)); }
45    let merged = a.merged(&b);
46    let mut all_found = true;
47    for i in 0..500 { if !merged.contains(&format!("a_{}", i)) { all_found = false; } }
48    for i in 500..1000 { if !merged.contains(&format!("b_{}", i)) { all_found = false; } }
49    println!("\n  Merge test: all items found after merge = {}", all_found);
50    println!();
51}
52
53fn experiment_2_geometric() {
54    println!("=== Experiment 2: Eisenstein-Geometric Gossip ===\n");
55
56    for &n in &[4, 8, 16, 32] {
57        let max_rounds = match n {
58            4 => 20, 8 => 40, 16 => 80, 32 => 150, _ => 200,
59        };
60        let result = geometric::run_experiment(n, 42, max_rounds);
61        
62        let rr = result.random_convergence_rounds.unwrap_or(0);
63        let gr = result.geometric_convergence_rounds.unwrap_or(0);
64        let speedup = if gr > 0 { rr as f64 / gr as f64 } else { 0.0 };
65        
66        println!("  n={:>2}: random={:>3} rounds ({:>5} msgs), geometric={:>3} rounds ({:>5} msgs), speedup={:.2}x",
67            n, rr, result.random_messages, gr, result.geometric_messages, speedup);
68    }
69    println!();
70}
71
72fn experiment_3_decay() {
73    println!("=== Experiment 3: Time-Decay Constraint CRDT ===\n");
74
75    let ns = 1_000_000_000u64;
76    
77    // Simulate a fleet node over 1 hour
78    let half_life = 300.0; // 5 minutes
79    let mut state = decay::DecayConstraintState::new("forgemaster", half_life);
80    
81    // Steady state: 100 satisfied per minute, 2 violations per minute
82    for t in 0..60 {
83        let time = t as u64 * 60 * ns;
84        state.record_satisfied(100.0, time);
85        state.record_violations(2.0, time);
86    }
87    
88    println!("  After 60 min steady state:");
89    println!("    Satisfaction rate at t=60: {:.1}%", state.satisfaction_rate(60 * 60 * ns) * 100.0);
90    
91    // Burst of violations at minute 55
92    let mut state2 = decay::DecayConstraintState::new("oracle1", half_life);
93    for t in 0..55 {
94        let time = t as u64 * 60 * ns;
95        state2.record_satisfied(100.0, time);
96        state2.record_violations(2.0, time);
97    }
98    // Burst: 50 violations at minute 55
99    state2.record_violations(50.0, 55 * 60 * ns);
100    state2.record_satisfied(100.0, 55 * 60 * ns);
101    for t in 56..60 {
102        let time = t as u64 * 60 * ns;
103        state2.record_satisfied(100.0, time);
104        state2.record_violations(2.0, time);
105    }
106    
107    println!("\n  After burst (50 violations at min 55):");
108    println!("    Satisfaction rate at t=60: {:.1}%", state2.satisfaction_rate(60 * 60 * ns) * 100.0);
109    println!("    Violation weight at t=60: {:.1}", state2.violation_weight(60 * 60 * ns));
110    
111    // Half-life comparison
112    println!("\n  Half-life sensitivity:");
113    for &hl in &[30.0, 60.0, 300.0, 3600.0] {
114        let mut s = decay::DecayConstraintState::new("test", hl);
115        for t in 0..60 {
116            s.record_violations(5.0, t as u64 * 60 * ns);
117        }
118        let weight = s.violation_weight(60 * 60 * ns);
119        println!("    half_life={:>4.0}s: violation_weight={:.1}", hl, weight);
120    }
121    println!();
122}
123
124fn experiment_4_sketch() {
125    println!("=== Experiment 4: Count-Min Sketch CRDT ===\n");
126
127    // Heavy-hitter detection: which constraints are violated most?
128    let mut sketch = sketch::SketchCRDT::new(0.001, 0.01);
129    
130    // Simulate 1M constraint checks
131    let n = 1_000_000;
132    let mut exact: std::collections::HashMap<String, u64> = std::collections::HashMap::new();
133    
134    for i in 0..n {
135        // Zipfian-ish distribution: a few constraints violated often
136        let constraint = if i % 3 == 0 { "bounds_check" }
137            else if i % 5 == 0 { "norm_check" }
138            else if i % 7 == 0 { "holonomy" }
139            else if i % 100 == 0 { "rare_violation" }
140            else { "ok" };
141        
142        if constraint != "ok" {
143            sketch.record(constraint, 1);
144            *exact.entry(constraint.to_string()).or_insert(0) += 1;
145        }
146    }
147    
148    println!("  Heavy-hitter detection (1M checks):");
149    println!("  {:>20} {:>10} {:>10} {:>10}", "Constraint", "Exact", "Estimated", "Error%");
150    
151    let mut items: Vec<_> = exact.iter().collect();
152    items.sort_by_key(|(_, &v)| std::cmp::Reverse(v));
153    
154    for (name, &true_count) in &items {
155        let est = sketch.estimate(name);
156        let error = if true_count > 0 { (est as f64 - true_count as f64) / true_count as f64 * 100.0 } else { 0.0 };
157        println!("  {:>20} {:>10} {:>10} {:>9.1}%", name, true_count, est, error);
158    }
159    
160    println!("\n  Space: sketch = {} bytes, exact hash map ≈ {} bytes",
161        sketch.space_bytes(),
162        items.len() * 40); // rough estimate
163    
164    // Merge test
165    let mut a = sketch::SketchCRDT::new(0.001, 0.01);
166    let mut b = sketch::SketchCRDT::new(0.001, 0.01);
167    for _ in 0..1000 { a.record("bounds_check", 1); }
168    for _ in 0..500 { b.record("norm_check", 1); }
169    let merged = a.merged(&b);
170    println!("\n  After merge: bounds_check ≥ {}, norm_check ≥ {}",
171        merged.estimate("bounds_check"), merged.estimate("norm_check"));
172    println!();
173}