ruvector_attention/topology/
policy.rs

1//! Attention Control Policy
2//!
3//! 3-mode policy for controlling attention based on coherence:
4//! - Stable: full attention width
5//! - Cautious: reduced width, increased sparsity
6//! - Freeze: retrieval only, no updates
7
8use serde::{Deserialize, Serialize};
9
10/// Attention operating mode
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum AttentionMode {
13    /// Full attention, normal updates
14    Stable,
15    /// Reduced attention width, increased sparsity
16    Cautious,
17    /// Retrieval only, no updates, no writes
18    Freeze,
19}
20
21/// Policy configuration
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PolicyConfig {
24    /// Coherence threshold for stable mode (above this = stable)
25    pub stable_threshold: f32,
26    /// Coherence threshold for freeze mode (below this = freeze)
27    pub freeze_threshold: f32,
28    /// Attention width multiplier in cautious mode (0.5 = half width)
29    pub cautious_width_factor: f32,
30    /// Sparsity increase in cautious mode (2.0 = twice as sparse)
31    pub cautious_sparsity_factor: f32,
32    /// How many tokens between coherence updates
33    pub update_period: usize,
34    /// Hysteresis factor to prevent mode oscillation
35    pub hysteresis: f32,
36}
37
38impl Default for PolicyConfig {
39    fn default() -> Self {
40        Self {
41            stable_threshold: 0.7,
42            freeze_threshold: 0.3,
43            cautious_width_factor: 0.5,
44            cautious_sparsity_factor: 2.0,
45            update_period: 4,
46            hysteresis: 0.05,
47        }
48    }
49}
50
51/// Attention control policy
52#[derive(Debug, Clone)]
53pub struct AttentionPolicy {
54    config: PolicyConfig,
55    current_mode: AttentionMode,
56    mode_history: Vec<AttentionMode>,
57}
58
59impl AttentionPolicy {
60    /// Create new policy
61    pub fn new(config: PolicyConfig) -> Self {
62        Self {
63            config,
64            current_mode: AttentionMode::Stable,
65            mode_history: Vec::new(),
66        }
67    }
68
69    /// Determine mode from coherence score
70    pub fn determine_mode(&mut self, coherence: f32) -> AttentionMode {
71        let new_mode = self.compute_mode(coherence);
72
73        // Apply hysteresis to prevent oscillation
74        let mode = self.apply_hysteresis(new_mode, coherence);
75
76        // Record history
77        self.mode_history.push(mode);
78        if self.mode_history.len() > 16 {
79            self.mode_history.remove(0);
80        }
81
82        self.current_mode = mode;
83        mode
84    }
85
86    /// Compute mode without hysteresis
87    fn compute_mode(&self, coherence: f32) -> AttentionMode {
88        if coherence >= self.config.stable_threshold {
89            AttentionMode::Stable
90        } else if coherence <= self.config.freeze_threshold {
91            AttentionMode::Freeze
92        } else {
93            AttentionMode::Cautious
94        }
95    }
96
97    /// Apply hysteresis to mode transitions
98    fn apply_hysteresis(&self, new_mode: AttentionMode, coherence: f32) -> AttentionMode {
99        let h = self.config.hysteresis;
100
101        match (self.current_mode, new_mode) {
102            // Stable -> Cautious: require coherence to drop below threshold - hysteresis
103            (AttentionMode::Stable, AttentionMode::Cautious) => {
104                if coherence < self.config.stable_threshold - h {
105                    AttentionMode::Cautious
106                } else {
107                    AttentionMode::Stable
108                }
109            }
110            // Cautious -> Stable: require coherence to rise above threshold + hysteresis
111            (AttentionMode::Cautious, AttentionMode::Stable) => {
112                if coherence > self.config.stable_threshold + h {
113                    AttentionMode::Stable
114                } else {
115                    AttentionMode::Cautious
116                }
117            }
118            // Cautious -> Freeze: require coherence to drop below threshold - hysteresis
119            (AttentionMode::Cautious, AttentionMode::Freeze) => {
120                if coherence < self.config.freeze_threshold - h {
121                    AttentionMode::Freeze
122                } else {
123                    AttentionMode::Cautious
124                }
125            }
126            // Freeze -> Cautious: require coherence to rise above threshold + hysteresis
127            (AttentionMode::Freeze, AttentionMode::Cautious) => {
128                if coherence > self.config.freeze_threshold + h {
129                    AttentionMode::Cautious
130                } else {
131                    AttentionMode::Freeze
132                }
133            }
134            // Same mode or big jump (Stable <-> Freeze): accept new mode
135            _ => new_mode,
136        }
137    }
138
139    /// Get current mode
140    pub fn current_mode(&self) -> AttentionMode {
141        self.current_mode
142    }
143
144    /// Get attention width for current mode
145    pub fn get_attention_width(&self, base_width: usize) -> usize {
146        match self.current_mode {
147            AttentionMode::Stable => base_width,
148            AttentionMode::Cautious => {
149                ((base_width as f32 * self.config.cautious_width_factor) as usize).max(1)
150            }
151            AttentionMode::Freeze => 0, // No attention updates
152        }
153    }
154
155    /// Get sparsity factor for current mode
156    pub fn get_sparsity_factor(&self) -> f32 {
157        match self.current_mode {
158            AttentionMode::Stable => 1.0,
159            AttentionMode::Cautious => self.config.cautious_sparsity_factor,
160            AttentionMode::Freeze => f32::INFINITY, // Maximum sparsity
161        }
162    }
163
164    /// Check if updates are allowed
165    pub fn allows_updates(&self) -> bool {
166        self.current_mode != AttentionMode::Freeze
167    }
168
169    /// Check if writes are allowed
170    pub fn allows_writes(&self) -> bool {
171        self.current_mode != AttentionMode::Freeze
172    }
173
174    /// Get mode stability (how often mode has been same recently)
175    pub fn mode_stability(&self) -> f32 {
176        if self.mode_history.is_empty() {
177            return 1.0;
178        }
179
180        let current = self.current_mode;
181        let matches = self.mode_history.iter().filter(|&&m| m == current).count();
182        matches as f32 / self.mode_history.len() as f32
183    }
184
185    /// Reset to stable mode
186    pub fn reset(&mut self) {
187        self.current_mode = AttentionMode::Stable;
188        self.mode_history.clear();
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_policy_modes() {
198        let mut policy = AttentionPolicy::new(PolicyConfig::default());
199
200        // High coherence = stable
201        assert_eq!(policy.determine_mode(0.9), AttentionMode::Stable);
202
203        // Medium coherence = cautious
204        assert_eq!(policy.determine_mode(0.5), AttentionMode::Cautious);
205
206        // Low coherence = freeze
207        assert_eq!(policy.determine_mode(0.1), AttentionMode::Freeze);
208    }
209
210    #[test]
211    fn test_attention_width() {
212        let mut policy = AttentionPolicy::new(PolicyConfig::default());
213
214        policy.determine_mode(0.9);
215        assert_eq!(policy.get_attention_width(100), 100);
216
217        policy.determine_mode(0.5);
218        assert_eq!(policy.get_attention_width(100), 50);
219
220        policy.determine_mode(0.1);
221        assert_eq!(policy.get_attention_width(100), 0);
222    }
223
224    #[test]
225    fn test_hysteresis() {
226        let mut policy = AttentionPolicy::new(PolicyConfig {
227            stable_threshold: 0.7,
228            freeze_threshold: 0.3,
229            hysteresis: 0.1,
230            ..Default::default()
231        });
232
233        // Start stable
234        policy.determine_mode(0.8);
235        assert_eq!(policy.current_mode(), AttentionMode::Stable);
236
237        // Drop to 0.65 (below 0.7 but above 0.7 - 0.1 = 0.6)
238        policy.determine_mode(0.65);
239        // Should stay stable due to hysteresis
240        assert_eq!(policy.current_mode(), AttentionMode::Stable);
241
242        // Drop to 0.55 (below 0.6)
243        policy.determine_mode(0.55);
244        assert_eq!(policy.current_mode(), AttentionMode::Cautious);
245    }
246
247    #[test]
248    fn test_update_permissions() {
249        let mut policy = AttentionPolicy::new(PolicyConfig::default());
250
251        policy.determine_mode(0.8);
252        assert!(policy.allows_updates());
253        assert!(policy.allows_writes());
254
255        policy.determine_mode(0.1);
256        assert!(!policy.allows_updates());
257        assert!(!policy.allows_writes());
258    }
259}