Skip to main content

ruvector_temporal_tensor/
tier_policy.rs

1//! Tier policy for access-pattern-driven bit-width selection.
2//!
3//! Score = `access_count * 1024 / (now_ts - last_access_ts + 1)`
4//!
5//! | Tier | Condition | Bits |
6//! |------|-----------|------|
7//! | Hot  | score >= hot_min_score | 8 |
8//! | Warm | score >= warm_min_score | warm_bits (7 or 5) |
9//! | Cold | otherwise | 3 |
10
11#[derive(Clone, Copy, Debug)]
12pub struct TierPolicy {
13    pub hot_min_score: u32,
14    pub warm_min_score: u32,
15    pub warm_bits: u8,
16    /// Drift tolerance as Q8 fixed-point. 26 means ~10.2% (26/256).
17    pub drift_pct_q8: u32,
18    pub group_len: u32,
19}
20
21impl Default for TierPolicy {
22    fn default() -> Self {
23        Self {
24            hot_min_score: 512,
25            warm_min_score: 64,
26            warm_bits: 7,
27            drift_pct_q8: 26,
28            group_len: 64,
29        }
30    }
31}
32
33impl TierPolicy {
34    /// Select bit width based on access pattern.
35    pub fn select_bits(&self, access_count: u32, last_access_ts: u32, now_ts: u32) -> u8 {
36        let age = now_ts.wrapping_sub(last_access_ts).wrapping_add(1);
37        let score = access_count.saturating_mul(1024).wrapping_div(age);
38
39        if score >= self.hot_min_score {
40            8
41        } else if score >= self.warm_min_score {
42            self.warm_bits
43        } else {
44            3
45        }
46    }
47
48    /// Compute the drift factor as 1.0 + drift_pct_q8/256.
49    pub fn drift_factor(&self) -> f32 {
50        1.0 + (self.drift_pct_q8 as f32) / 256.0
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn test_default_policy() {
60        let p = TierPolicy::default();
61        assert_eq!(p.hot_min_score, 512);
62        assert_eq!(p.warm_min_score, 64);
63        assert_eq!(p.warm_bits, 7);
64        assert_eq!(p.drift_pct_q8, 26);
65        assert_eq!(p.group_len, 64);
66    }
67
68    #[test]
69    fn test_tier_selection_hot() {
70        let p = TierPolicy::default();
71        // 100 accesses, age=10 -> score = 100*1024/10 = 10240 >= 512
72        assert_eq!(p.select_bits(100, 0, 9), 8);
73    }
74
75    #[test]
76    fn test_tier_selection_warm() {
77        let p = TierPolicy::default();
78        // 10 accesses, age=100 -> score = 10*1024/100 = 102 >= 64, < 512
79        assert_eq!(p.select_bits(10, 0, 99), 7);
80    }
81
82    #[test]
83    fn test_tier_selection_cold() {
84        let p = TierPolicy::default();
85        // 1 access, age=1000 -> score = 1024/1000 = 1 < 64
86        assert_eq!(p.select_bits(1, 0, 999), 3);
87    }
88
89    #[test]
90    fn test_drift_factor() {
91        let p = TierPolicy::default();
92        let df = p.drift_factor();
93        assert!((df - 1.1015625).abs() < 1e-6);
94    }
95
96    #[test]
97    fn test_warm_bits_5() {
98        let p = TierPolicy {
99            warm_bits: 5,
100            ..Default::default()
101        };
102        assert_eq!(p.select_bits(10, 0, 99), 5);
103    }
104}