Skip to main content

uncertain_rs/
cache.rs

1#![allow(
2    clippy::cast_precision_loss,
3    clippy::cast_possible_truncation,
4    clippy::cast_sign_loss,
5    clippy::float_cmp
6)]
7
8use std::collections::HashMap;
9use std::hash::Hash;
10use std::sync::atomic::AtomicUsize;
11use std::sync::{Arc, RwLock};
12use std::time::{Duration, Instant};
13
14/// Global cache managers
15static STATS_CACHE: std::sync::LazyLock<StatisticsCache> =
16    std::sync::LazyLock::new(StatisticsCache::new);
17static DIST_CACHE: std::sync::LazyLock<DistributionCache> =
18    std::sync::LazyLock::new(DistributionCache::new);
19
20/// Thread-safe cache with TTL (time-to-live) support for expensive computations
21pub struct TtlCache<K, V> {
22    data: Arc<RwLock<HashMap<K, CacheEntry<V>>>>,
23    ttl: Duration,
24    hit_count: Arc<AtomicUsize>,
25    miss_count: Arc<AtomicUsize>,
26}
27
28struct CacheEntry<V> {
29    value: V,
30    created_at: Instant,
31}
32
33impl<K, V> TtlCache<K, V>
34where
35    K: Hash + Eq + Clone,
36    V: Clone,
37{
38    /// Create a new TTL cache with specified time-to-live duration
39    #[must_use]
40    pub fn new(ttl: Duration) -> Self {
41        Self {
42            data: Arc::new(RwLock::new(HashMap::new())),
43            ttl,
44            hit_count: Arc::new(AtomicUsize::new(0)),
45            miss_count: Arc::new(AtomicUsize::new(0)),
46        }
47    }
48
49    /// Get a value from cache if it exists and hasn't expired
50    pub fn get(&self, key: &K) -> Option<V> {
51        let cache = self.data.read().ok()?;
52        let entry = cache.get(key)?;
53
54        if entry.created_at.elapsed() < self.ttl {
55            self.hit_count
56                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
57            Some(entry.value.clone())
58        } else {
59            self.miss_count
60                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
61            None
62        }
63    }
64
65    /// Insert a value into the cache
66    pub fn insert(&self, key: K, value: V) {
67        if let Ok(mut cache) = self.data.write() {
68            cache.insert(
69                key,
70                CacheEntry {
71                    value,
72                    created_at: Instant::now(),
73                },
74            );
75        }
76    }
77
78    /// Get or compute a value, caching the result
79    pub fn get_or_compute<F>(&self, key: K, compute_fn: F) -> V
80    where
81        F: FnOnce() -> V,
82    {
83        if let Some(cached) = self.get(&key) {
84            return cached;
85        }
86
87        self.miss_count
88            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
89        let value = compute_fn();
90        self.insert(key, value.clone());
91        value
92    }
93
94    /// Clear expired entries from the cache
95    pub fn cleanup_expired(&self) {
96        if let Ok(mut cache) = self.data.write() {
97            cache.retain(|_, entry| entry.created_at.elapsed() < self.ttl);
98        }
99    }
100
101    /// Clear all entries from the cache
102    pub fn clear(&self) {
103        if let Ok(mut cache) = self.data.write() {
104            cache.clear();
105        }
106    }
107
108    /// Get the number of entries in the cache
109    #[must_use]
110    pub fn len(&self) -> usize {
111        self.data.read().map_or(0, |cache| cache.len())
112    }
113
114    /// Check if the cache is empty
115    #[must_use]
116    pub fn is_empty(&self) -> bool {
117        self.len() == 0
118    }
119
120    /// Get cache hit rate as a percentage
121    #[must_use]
122    pub fn hit_rate(&self) -> f64 {
123        let hits = self.hit_count.load(std::sync::atomic::Ordering::Relaxed);
124        let misses = self.miss_count.load(std::sync::atomic::Ordering::Relaxed);
125        let total = hits + misses;
126        if total == 0 {
127            0.0
128        } else {
129            (hits as f64 / total as f64) * 100.0
130        }
131    }
132
133    /// Get cache statistics
134    #[must_use]
135    pub fn cache_stats(&self) -> CacheStats {
136        let hits = self.hit_count.load(std::sync::atomic::Ordering::Relaxed);
137        let misses = self.miss_count.load(std::sync::atomic::Ordering::Relaxed);
138        CacheStats { hits, misses }
139    }
140
141    /// Reset cache statistics
142    pub fn reset_stats(&self) {
143        self.hit_count
144            .store(0, std::sync::atomic::Ordering::Relaxed);
145        self.miss_count
146            .store(0, std::sync::atomic::Ordering::Relaxed);
147    }
148}
149
150impl<K, V> Default for TtlCache<K, V>
151where
152    K: Hash + Eq + Clone,
153    V: Clone,
154{
155    fn default() -> Self {
156        Self::new(Duration::from_secs(300)) // 5 minutes default TTL
157    }
158}
159
160/// Cache for statistical computations
161pub struct StatisticsCache {
162    expected_value: TtlCache<(uuid::Uuid, usize), f64>,
163    variance: TtlCache<(uuid::Uuid, usize), f64>,
164    std_dev: TtlCache<(uuid::Uuid, usize), f64>,
165    skewness: TtlCache<(uuid::Uuid, usize), f64>,
166    kurtosis: TtlCache<(uuid::Uuid, usize), f64>,
167    confidence_intervals: TtlCache<(uuid::Uuid, usize, u64), (f64, f64)>, // confidence as f64 * 1000 for key
168    cdf: TtlCache<(uuid::Uuid, usize, u64), f64>, // value as f64 * 1000 for key
169    quantiles: TtlCache<(uuid::Uuid, usize, u64), f64>, // q as f64 * 1000 for key
170}
171
172impl StatisticsCache {
173    /// Create a new statistics cache with default TTL
174    #[must_use]
175    pub fn new() -> Self {
176        let ttl = Duration::from_secs(300); // 5 minutes
177        Self {
178            expected_value: TtlCache::new(ttl),
179            variance: TtlCache::new(ttl),
180            std_dev: TtlCache::new(ttl),
181            skewness: TtlCache::new(ttl),
182            kurtosis: TtlCache::new(ttl),
183            confidence_intervals: TtlCache::new(ttl),
184            cdf: TtlCache::new(ttl),
185            quantiles: TtlCache::new(ttl),
186        }
187    }
188
189    /// Cache expected value computation
190    pub fn get_or_compute_expected_value<F>(
191        &self,
192        id: uuid::Uuid,
193        sample_count: usize,
194        compute: F,
195    ) -> f64
196    where
197        F: FnOnce() -> f64,
198    {
199        self.expected_value
200            .get_or_compute((id, sample_count), compute)
201    }
202
203    /// Cache variance computation
204    pub fn get_or_compute_variance<F>(&self, id: uuid::Uuid, sample_count: usize, compute: F) -> f64
205    where
206        F: FnOnce() -> f64,
207    {
208        self.variance.get_or_compute((id, sample_count), compute)
209    }
210
211    /// Cache standard deviation computation
212    pub fn get_or_compute_std_dev<F>(&self, id: uuid::Uuid, sample_count: usize, compute: F) -> f64
213    where
214        F: FnOnce() -> f64,
215    {
216        self.std_dev.get_or_compute((id, sample_count), compute)
217    }
218
219    /// Cache skewness computation
220    pub fn get_or_compute_skewness<F>(&self, id: uuid::Uuid, sample_count: usize, compute: F) -> f64
221    where
222        F: FnOnce() -> f64,
223    {
224        self.skewness.get_or_compute((id, sample_count), compute)
225    }
226
227    /// Cache kurtosis computation
228    pub fn get_or_compute_kurtosis<F>(&self, id: uuid::Uuid, sample_count: usize, compute: F) -> f64
229    where
230        F: FnOnce() -> f64,
231    {
232        self.kurtosis.get_or_compute((id, sample_count), compute)
233    }
234
235    /// Cache confidence interval computation
236    pub fn get_or_compute_confidence_interval<F>(
237        &self,
238        id: uuid::Uuid,
239        sample_count: usize,
240        confidence: f64,
241        compute: F,
242    ) -> (f64, f64)
243    where
244        F: FnOnce() -> (f64, f64),
245    {
246        let confidence_key = Self::quantize_float(confidence, 0.001);
247        self.confidence_intervals
248            .get_or_compute((id, sample_count, confidence_key), compute)
249    }
250
251    /// Cache CDF computation
252    pub fn get_or_compute_cdf<F>(
253        &self,
254        id: uuid::Uuid,
255        sample_count: usize,
256        value: f64,
257        compute: F,
258    ) -> f64
259    where
260        F: FnOnce() -> f64,
261    {
262        let value_key = Self::quantize_float(value, 0.001);
263        self.cdf
264            .get_or_compute((id, sample_count, value_key), compute)
265    }
266
267    /// Cache quantile computation
268    pub fn get_or_compute_quantile<F>(
269        &self,
270        id: uuid::Uuid,
271        sample_count: usize,
272        q: f64,
273        compute: F,
274    ) -> f64
275    where
276        F: FnOnce() -> f64,
277    {
278        let q_key = Self::quantize_float(q, 0.001);
279        self.quantiles
280            .get_or_compute((id, sample_count, q_key), compute)
281    }
282
283    /// Clear all statistical caches
284    pub fn clear_all(&self) {
285        self.expected_value.clear();
286        self.variance.clear();
287        self.std_dev.clear();
288        self.skewness.clear();
289        self.kurtosis.clear();
290        self.confidence_intervals.clear();
291        self.cdf.clear();
292        self.quantiles.clear();
293    }
294
295    /// Clean up expired entries in all caches
296    pub fn cleanup_all_expired(&self) {
297        self.expected_value.cleanup_expired();
298        self.variance.cleanup_expired();
299        self.std_dev.cleanup_expired();
300        self.skewness.cleanup_expired();
301        self.kurtosis.cleanup_expired();
302        self.confidence_intervals.cleanup_expired();
303        self.cdf.cleanup_expired();
304        self.quantiles.cleanup_expired();
305    }
306
307    /// Quantize a floating-point value to improve cache hit rates
308    /// by rounding to a specified precision
309    fn quantize_float(value: f64, precision: f64) -> u64 {
310        (value / precision).round() as u64
311    }
312
313    /// Get overall cache statistics across all caches
314    #[must_use]
315    pub fn overall_stats(&self) -> CacheStats {
316        let mut total_hits = 0;
317        let mut total_misses = 0;
318
319        let stats = [
320            self.expected_value.cache_stats(),
321            self.variance.cache_stats(),
322            self.std_dev.cache_stats(),
323            self.skewness.cache_stats(),
324            self.kurtosis.cache_stats(),
325            self.confidence_intervals.cache_stats(),
326            self.cdf.cache_stats(),
327            self.quantiles.cache_stats(),
328        ];
329
330        for stat in &stats {
331            total_hits += stat.hits;
332            total_misses += stat.misses;
333        }
334
335        CacheStats {
336            hits: total_hits,
337            misses: total_misses,
338        }
339    }
340}
341
342impl Default for StatisticsCache {
343    fn default() -> Self {
344        Self::new()
345    }
346}
347
348/// Cache for distribution sampling operations
349pub struct DistributionCache {
350    samples: TtlCache<(uuid::Uuid, usize), Vec<f64>>,
351    pdf_kde: TtlCache<(uuid::Uuid, usize, u64, u64), f64>, // x and bandwidth as keys
352}
353
354impl DistributionCache {
355    /// Create a new distribution cache
356    #[must_use]
357    pub fn new() -> Self {
358        let ttl = Duration::from_secs(300); // 5 minutes
359        Self {
360            samples: TtlCache::new(ttl),
361            pdf_kde: TtlCache::new(ttl),
362        }
363    }
364
365    /// Cache samples for reuse
366    pub fn get_or_compute_samples<F>(
367        &self,
368        id: uuid::Uuid,
369        sample_count: usize,
370        compute: F,
371    ) -> Vec<f64>
372    where
373        F: FnOnce() -> Vec<f64>,
374    {
375        self.samples.get_or_compute((id, sample_count), compute)
376    }
377
378    /// Cache PDF KDE computation
379    pub fn get_or_compute_pdf_kde<F>(
380        &self,
381        id: uuid::Uuid,
382        sample_count: usize,
383        x: f64,
384        bandwidth: f64,
385        compute: F,
386    ) -> f64
387    where
388        F: FnOnce() -> f64,
389    {
390        let x_key = StatisticsCache::quantize_float(x, 0.001);
391        let bandwidth_key = StatisticsCache::quantize_float(bandwidth, 0.0001);
392        self.pdf_kde
393            .get_or_compute((id, sample_count, x_key, bandwidth_key), compute)
394    }
395
396    /// Clear all caches
397    pub fn clear_all(&self) {
398        self.samples.clear();
399        self.pdf_kde.clear();
400    }
401
402    /// Clean up expired entries
403    pub fn cleanup_all_expired(&self) {
404        self.samples.cleanup_expired();
405        self.pdf_kde.cleanup_expired();
406    }
407
408    /// Get overall cache statistics across all distribution caches
409    #[must_use]
410    pub fn overall_stats(&self) -> CacheStats {
411        let samples_stats = self.samples.cache_stats();
412        let pdf_stats = self.pdf_kde.cache_stats();
413
414        CacheStats {
415            hits: samples_stats.hits + pdf_stats.hits,
416            misses: samples_stats.misses + pdf_stats.misses,
417        }
418    }
419}
420
421impl Default for DistributionCache {
422    fn default() -> Self {
423        Self::new()
424    }
425}
426
427/// Cache performance statistics
428#[derive(Debug, Clone, Copy)]
429pub struct CacheStats {
430    pub hits: usize,
431    pub misses: usize,
432}
433
434impl CacheStats {
435    #[must_use]
436    pub fn hit_rate(&self) -> f64 {
437        let total = self.hits + self.misses;
438        if total == 0 {
439            0.0
440        } else {
441            (self.hits as f64 / total as f64) * 100.0
442        }
443    }
444}
445
446/// Get the global statistics cache
447#[must_use]
448pub fn stats_cache() -> &'static StatisticsCache {
449    &STATS_CACHE
450}
451
452/// Get the global distribution cache
453#[must_use]
454pub fn dist_cache() -> &'static DistributionCache {
455    &DIST_CACHE
456}
457
458/// Cleanup expired entries in all global caches
459pub fn cleanup_global_caches() {
460    STATS_CACHE.cleanup_all_expired();
461    DIST_CACHE.cleanup_all_expired();
462}
463
464/// Clear all global caches
465pub fn clear_global_caches() {
466    STATS_CACHE.clear_all();
467    DIST_CACHE.clear_all();
468}
469
470/// Get global cache statistics
471#[must_use]
472pub fn global_cache_stats() -> (CacheStats, CacheStats) {
473    (STATS_CACHE.overall_stats(), DIST_CACHE.overall_stats())
474}
475
476/// Print a comprehensive cache performance report
477pub fn print_cache_report() {
478    let (stats_stats, dist_stats) = global_cache_stats();
479
480    println!("=== Cache Performance Report ===");
481    println!("\nStatistics Cache:");
482    println!("  Hits: {}", stats_stats.hits);
483    println!("  Misses: {}", stats_stats.misses);
484    println!("  Hit Rate: {:.2}%", stats_stats.hit_rate());
485
486    println!("\nDistribution Cache:");
487    println!("  Hits: {}", dist_stats.hits);
488    println!("  Misses: {}", dist_stats.misses);
489    println!("  Hit Rate: {:.2}%", dist_stats.hit_rate());
490
491    let total_hits = stats_stats.hits + dist_stats.hits;
492    let total_misses = stats_stats.misses + dist_stats.misses;
493    let total_requests = total_hits + total_misses;
494
495    if total_requests > 0 {
496        let overall_hit_rate = (total_hits as f64 / total_requests as f64) * 100.0;
497        println!("\nOverall:");
498        println!("  Total Requests: {total_requests}");
499        println!("  Overall Hit Rate: {overall_hit_rate:.2}%");
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506    use std::thread;
507    use std::time::Duration;
508
509    #[test]
510    fn test_ttl_cache_basic() {
511        let cache = TtlCache::new(Duration::from_millis(100));
512
513        cache.insert("key1", "value1");
514        assert_eq!(cache.get(&"key1"), Some("value1"));
515
516        thread::sleep(Duration::from_millis(150));
517        assert_eq!(cache.get(&"key1"), None);
518    }
519
520    #[test]
521    fn test_ttl_cache_get_or_compute() {
522        let cache = TtlCache::new(Duration::from_secs(1));
523        let mut call_count = 0;
524
525        let result = cache.get_or_compute("test", || {
526            call_count += 1;
527            42
528        });
529        assert_eq!(result, 42);
530        assert_eq!(call_count, 1);
531
532        let result2 = cache.get_or_compute("test", || {
533            call_count += 1;
534            99
535        });
536        assert_eq!(result2, 42);
537        assert_eq!(call_count, 1);
538    }
539
540    #[test]
541    fn test_statistics_cache() {
542        let cache = StatisticsCache::new();
543        let test_id = uuid::Uuid::new_v4();
544
545        let result = cache.get_or_compute_expected_value(test_id, 1000, || 42.0);
546        assert_eq!(result, 42.0);
547
548        let result2 = cache.get_or_compute_expected_value(test_id, 1000, || 99.0);
549        assert_eq!(result2, 42.0);
550    }
551
552    #[test]
553    fn test_distribution_cache() {
554        let cache = DistributionCache::new();
555        let test_id = uuid::Uuid::new_v4();
556
557        let result = cache.get_or_compute_samples(test_id, 100, || vec![1.0, 2.0, 3.0]);
558        assert_eq!(result, vec![1.0, 2.0, 3.0]);
559
560        let result2 = cache.get_or_compute_samples(test_id, 100, || vec![4.0, 5.0, 6.0]);
561        assert_eq!(result2, vec![1.0, 2.0, 3.0]);
562    }
563
564    #[test]
565    fn test_cache_cleanup() {
566        let cache = TtlCache::new(Duration::from_millis(50));
567
568        cache.insert("key1", "value1");
569        cache.insert("key2", "value2");
570        assert_eq!(cache.len(), 2);
571
572        thread::sleep(Duration::from_millis(100));
573        cache.cleanup_expired();
574        assert_eq!(cache.len(), 0);
575    }
576
577    #[test]
578    fn test_cache_clear() {
579        let cache = TtlCache::new(Duration::from_secs(1));
580
581        cache.insert("key1", "value1");
582        cache.insert("key2", "value2");
583        assert_eq!(cache.len(), 2);
584
585        cache.clear();
586        assert_eq!(cache.len(), 0);
587        assert!(cache.is_empty());
588    }
589
590    #[test]
591    fn test_ttl_cache_default() {
592        let cache = TtlCache::<String, i32>::default();
593
594        cache.insert("test".to_string(), 42);
595        assert_eq!(cache.get(&"test".to_string()), Some(42));
596        assert!(!cache.is_empty());
597    }
598
599    #[test]
600    fn test_ttl_cache_is_empty() {
601        let cache = TtlCache::new(Duration::from_secs(1));
602
603        assert!(cache.is_empty());
604        assert_eq!(cache.len(), 0);
605
606        cache.insert("key", "value");
607        assert!(!cache.is_empty());
608        assert_eq!(cache.len(), 1);
609    }
610
611    #[test]
612    fn test_ttl_cache_concurrent_access() {
613        use std::sync::Arc;
614
615        let cache = Arc::new(TtlCache::new(Duration::from_secs(1)));
616        let mut handles = vec![];
617
618        for i in 0..10 {
619            let cache_clone = Arc::clone(&cache);
620            let handle = thread::spawn(move || {
621                cache_clone.insert(format!("key{i}"), i);
622                cache_clone.get(&format!("key{i}"))
623            });
624            handles.push(handle);
625        }
626
627        for handle in handles {
628            let result = handle.join().unwrap();
629            assert!(result.is_some());
630        }
631
632        assert_eq!(cache.len(), 10);
633    }
634
635    #[test]
636    fn test_statistics_cache_all_methods() {
637        let cache = StatisticsCache::new();
638        let test_id = uuid::Uuid::new_v4();
639
640        let variance = cache.get_or_compute_variance(test_id, 1000, || 25.0);
641        assert_eq!(variance, 25.0);
642        let variance2 = cache.get_or_compute_variance(test_id, 1000, || 50.0);
643        assert_eq!(variance2, 25.0); // Should use cached value
644
645        let std_dev = cache.get_or_compute_std_dev(test_id, 1000, || 5.0);
646        assert_eq!(std_dev, 5.0);
647
648        let skewness = cache.get_or_compute_skewness(test_id, 1000, || 0.5);
649        assert_eq!(skewness, 0.5);
650
651        let kurtosis = cache.get_or_compute_kurtosis(test_id, 1000, || 3.0);
652        assert_eq!(kurtosis, 3.0);
653
654        let ci = cache.get_or_compute_confidence_interval(test_id, 1000, 0.95, || (1.0, 2.0));
655        assert_eq!(ci, (1.0, 2.0));
656        let ci2 = cache.get_or_compute_confidence_interval(test_id, 1000, 0.95, || (3.0, 4.0));
657        assert_eq!(ci2, (1.0, 2.0)); // Should use cached value
658
659        let cdf = cache.get_or_compute_cdf(test_id, 1000, 1.5, || 0.75);
660        assert_eq!(cdf, 0.75);
661        let cdf2 = cache.get_or_compute_cdf(test_id, 1000, 1.5, || 0.85);
662        assert_eq!(cdf2, 0.75); // Should use cached value
663
664        let quantile = cache.get_or_compute_quantile(test_id, 1000, 0.5, || 1.0);
665        assert_eq!(quantile, 1.0);
666        let quantile2 = cache.get_or_compute_quantile(test_id, 1000, 0.5, || 2.0);
667        assert_eq!(quantile2, 1.0); // Should use cached value
668    }
669
670    #[test]
671    fn test_statistics_cache_clear_and_cleanup() {
672        let cache = StatisticsCache::new();
673        let test_id = uuid::Uuid::new_v4();
674
675        cache.get_or_compute_expected_value(test_id, 1000, || 42.0);
676        cache.get_or_compute_variance(test_id, 1000, || 25.0);
677        cache.get_or_compute_confidence_interval(test_id, 1000, 0.95, || (1.0, 2.0));
678
679        cache.clear_all();
680
681        let result = cache.get_or_compute_expected_value(test_id, 1000, || 99.0);
682        assert_eq!(result, 99.0);
683
684        cache.cleanup_all_expired();
685        let result2 = cache.get_or_compute_expected_value(test_id, 1000, || 88.0);
686        assert_eq!(result2, 99.0); // Should still be cached
687    }
688
689    #[test]
690    fn test_statistics_cache_default() {
691        let cache = StatisticsCache::default();
692        let test_id = uuid::Uuid::new_v4();
693
694        let result = cache.get_or_compute_expected_value(test_id, 1000, || 42.0);
695        assert_eq!(result, 42.0);
696    }
697
698    #[test]
699    fn test_distribution_cache_all_methods() {
700        let cache = DistributionCache::new();
701        let test_id = uuid::Uuid::new_v4();
702
703        let samples = cache.get_or_compute_samples(test_id, 100, || vec![1.0, 2.0, 3.0]);
704        assert_eq!(samples, vec![1.0, 2.0, 3.0]);
705
706        let pdf = cache.get_or_compute_pdf_kde(test_id, 100, 1.5, 0.1, || 0.25);
707        assert_eq!(pdf, 0.25);
708        let pdf2 = cache.get_or_compute_pdf_kde(test_id, 100, 1.5, 0.1, || 0.50);
709        assert_eq!(pdf2, 0.25);
710
711        let pdf3 = cache.get_or_compute_pdf_kde(test_id, 100, 1.6, 0.1, || 0.30);
712        assert_eq!(pdf3, 0.30);
713
714        let pdf4 = cache.get_or_compute_pdf_kde(test_id, 100, 1.5, 0.2, || 0.35);
715        assert_eq!(pdf4, 0.35);
716    }
717
718    #[test]
719    fn test_distribution_cache_clear_and_cleanup() {
720        let cache = DistributionCache::new();
721        let test_id = uuid::Uuid::new_v4();
722
723        cache.get_or_compute_samples(test_id, 100, || vec![1.0, 2.0]);
724        cache.get_or_compute_pdf_kde(test_id, 100, 1.5, 0.1, || 0.25);
725
726        cache.clear_all();
727
728        let samples = cache.get_or_compute_samples(test_id, 100, || vec![3.0, 4.0]);
729        assert_eq!(samples, vec![3.0, 4.0]);
730
731        let pdf = cache.get_or_compute_pdf_kde(test_id, 100, 1.5, 0.1, || 0.50);
732        assert_eq!(pdf, 0.50);
733
734        cache.cleanup_all_expired();
735        let samples2 = cache.get_or_compute_samples(test_id, 100, || vec![5.0, 6.0]);
736        assert_eq!(samples2, vec![3.0, 4.0]);
737    }
738
739    #[test]
740    fn test_distribution_cache_default() {
741        let cache = DistributionCache::default();
742        let test_id = uuid::Uuid::new_v4();
743
744        let samples = cache.get_or_compute_samples(test_id, 100, || vec![1.0, 2.0]);
745        assert_eq!(samples, vec![1.0, 2.0]);
746    }
747
748    #[test]
749    fn test_global_cache_functions() {
750        let stats = stats_cache();
751        let dist = dist_cache();
752
753        let test_id = uuid::Uuid::new_v4();
754
755        let result = stats.get_or_compute_expected_value(test_id, 1000, || 42.0);
756        assert_eq!(result, 42.0);
757
758        let samples = dist.get_or_compute_samples(test_id, 100, || vec![1.0, 2.0]);
759        assert_eq!(samples, vec![1.0, 2.0]);
760
761        cleanup_global_caches();
762
763        let result2 = stats.get_or_compute_expected_value(test_id, 1000, || 99.0);
764        assert_eq!(result2, 42.0);
765
766        clear_global_caches();
767
768        let result3 = stats.get_or_compute_expected_value(test_id, 1000, || 99.0);
769        assert_eq!(result3, 99.0);
770
771        let samples2 = dist.get_or_compute_samples(test_id, 100, || vec![3.0, 4.0]);
772        assert_eq!(samples2, vec![3.0, 4.0]);
773    }
774
775    #[test]
776    fn test_cache_key_precision_handling() {
777        let cache = StatisticsCache::new();
778        let test_id = uuid::Uuid::new_v4();
779
780        let ci1 = cache.get_or_compute_confidence_interval(test_id, 1000, 0.95, || (1.0, 2.0));
781        let ci2 = cache.get_or_compute_confidence_interval(test_id, 1000, 0.96, || (3.0, 4.0));
782
783        assert_eq!(ci1, (1.0, 2.0));
784        assert_eq!(ci2, (3.0, 4.0));
785
786        let ci3 = cache.get_or_compute_confidence_interval(test_id, 1000, 0.95, || (5.0, 6.0));
787        assert_eq!(ci3, (1.0, 2.0));
788    }
789}