ruvix_vecgraph/
coherence.rs1use ruvix_types::CoherenceMeta;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(C)]
22pub struct CoherenceConfig {
23 pub min_coherence_threshold: u16,
26
27 pub enable_scheduler_hints: bool,
29
30 pub track_deltas: bool,
32
33 pub decay_rate: u16,
36
37 pub initial_coherence: u16,
39}
40
41impl Default for CoherenceConfig {
42 fn default() -> Self {
43 Self {
44 min_coherence_threshold: 5000, enable_scheduler_hints: true,
46 track_deltas: false,
47 decay_rate: 0,
48 initial_coherence: 10000, }
50 }
51}
52
53impl CoherenceConfig {
54 #[inline]
56 #[must_use]
57 pub const fn new() -> Self {
58 Self {
59 min_coherence_threshold: 5000,
60 enable_scheduler_hints: true,
61 track_deltas: false,
62 decay_rate: 0,
63 initial_coherence: 10000,
64 }
65 }
66
67 #[inline]
69 #[must_use]
70 pub const fn with_min_threshold(mut self, threshold: f32) -> Self {
71 self.min_coherence_threshold = (threshold.clamp(0.0, 1.0) * 10000.0) as u16;
72 self
73 }
74
75 #[inline]
77 #[must_use]
78 pub const fn with_scheduler_hints(mut self, enabled: bool) -> Self {
79 self.enable_scheduler_hints = enabled;
80 self
81 }
82
83 #[inline]
85 #[must_use]
86 pub const fn with_delta_tracking(mut self, enabled: bool) -> Self {
87 self.track_deltas = enabled;
88 self
89 }
90
91 #[inline]
93 #[must_use]
94 pub const fn with_decay_rate(mut self, rate: f32) -> Self {
95 self.decay_rate = (rate.clamp(0.0, 1.0) * 10000.0) as u16;
96 self
97 }
98
99 #[inline]
101 #[must_use]
102 pub fn min_threshold_f32(&self) -> f32 {
103 self.min_coherence_threshold as f32 / 10000.0
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct CoherenceTracker {
110 config: CoherenceConfig,
112
113 current_epoch: u64,
115
116 average_coherence: u16,
118
119 entry_count: u32,
121
122 coherence_sum: u64,
124
125 low_coherence_mutations: u32,
127}
128
129impl CoherenceTracker {
130 #[inline]
132 #[must_use]
133 pub const fn new(config: CoherenceConfig) -> Self {
134 Self {
135 config,
136 current_epoch: 0,
137 average_coherence: 10000,
138 entry_count: 0,
139 coherence_sum: 0,
140 low_coherence_mutations: 0,
141 }
142 }
143
144 #[inline]
146 #[must_use]
147 pub const fn current_epoch(&self) -> u64 {
148 self.current_epoch
149 }
150
151 #[inline]
153 pub fn advance_epoch(&mut self) -> u64 {
154 self.current_epoch = self.current_epoch.wrapping_add(1);
155 self.current_epoch
156 }
157
158 #[inline]
160 #[must_use]
161 pub const fn config(&self) -> &CoherenceConfig {
162 &self.config
163 }
164
165 #[inline]
170 #[must_use]
171 pub fn create_initial_meta(&mut self, proof_attestation_hash: [u8; 32]) -> CoherenceMeta {
172 CoherenceMeta::new(
173 self.config.initial_coherence,
174 self.advance_epoch(),
175 proof_attestation_hash,
176 )
177 }
178
179 pub fn on_entry_added(&mut self, coherence_score: u16) {
181 self.entry_count = self.entry_count.saturating_add(1);
182 self.coherence_sum = self.coherence_sum.saturating_add(coherence_score as u64);
183 self.update_average();
184 }
185
186 pub fn on_entry_removed(&mut self, coherence_score: u16) {
188 self.entry_count = self.entry_count.saturating_sub(1);
189 self.coherence_sum = self.coherence_sum.saturating_sub(coherence_score as u64);
190 self.update_average();
191 }
192
193 pub fn on_entry_mutated(
197 &mut self,
198 old_meta: &CoherenceMeta,
199 new_coherence: u16,
200 proof_attestation_hash: [u8; 32],
201 ) -> CoherenceMeta {
202 self.coherence_sum = self
204 .coherence_sum
205 .saturating_sub(old_meta.coherence_score as u64)
206 .saturating_add(new_coherence as u64);
207
208 self.update_average();
209
210 if new_coherence < self.config.min_coherence_threshold {
212 self.low_coherence_mutations = self.low_coherence_mutations.saturating_add(1);
213 }
214
215 CoherenceMeta::new(new_coherence, self.advance_epoch(), proof_attestation_hash)
217 }
218
219 #[inline]
221 #[must_use]
222 pub fn would_violate_threshold(&self, proposed_coherence: u16) -> bool {
223 proposed_coherence < self.config.min_coherence_threshold
224 }
225
226 #[inline]
228 #[must_use]
229 pub fn average_coherence_f32(&self) -> f32 {
230 self.average_coherence as f32 / 10000.0
231 }
232
233 #[inline]
235 #[must_use]
236 pub const fn entry_count(&self) -> u32 {
237 self.entry_count
238 }
239
240 #[inline]
242 #[must_use]
243 pub const fn low_coherence_mutations(&self) -> u32 {
244 self.low_coherence_mutations
245 }
246
247 fn update_average(&mut self) {
249 if self.entry_count > 0 {
250 self.average_coherence = (self.coherence_sum / self.entry_count as u64) as u16;
251 } else {
252 self.average_coherence = self.config.initial_coherence;
253 }
254 }
255
256 pub fn apply_decay(&mut self, epochs_elapsed: u64) {
260 if self.config.decay_rate == 0 || epochs_elapsed == 0 {
261 return;
262 }
263
264 let decay_amount =
267 ((self.config.decay_rate as u64) * epochs_elapsed).min(self.average_coherence as u64);
268
269 self.average_coherence = self.average_coherence.saturating_sub(decay_amount as u16);
270
271 if self.entry_count > 0 {
273 let sum_decay = decay_amount * self.entry_count as u64;
274 self.coherence_sum = self.coherence_sum.saturating_sub(sum_decay);
275 }
276 }
277}
278
279impl Default for CoherenceTracker {
280 fn default() -> Self {
281 Self::new(CoherenceConfig::default())
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_coherence_config_defaults() {
291 let config = CoherenceConfig::default();
292 assert_eq!(config.initial_coherence, 10000);
293 assert_eq!(config.min_coherence_threshold, 5000);
294 assert!(config.enable_scheduler_hints);
295 }
296
297 #[test]
298 fn test_coherence_config_builder() {
299 let config = CoherenceConfig::new()
300 .with_min_threshold(0.7)
301 .with_decay_rate(0.01)
302 .with_delta_tracking(true);
303
304 assert_eq!(config.min_coherence_threshold, 7000);
305 assert_eq!(config.decay_rate, 100);
306 assert!(config.track_deltas);
307 }
308
309 #[test]
310 fn test_coherence_tracker_epoch() {
311 let mut tracker = CoherenceTracker::default();
312
313 assert_eq!(tracker.current_epoch(), 0);
314
315 let epoch1 = tracker.advance_epoch();
316 assert_eq!(epoch1, 1);
317
318 let epoch2 = tracker.advance_epoch();
319 assert_eq!(epoch2, 2);
320 }
321
322 #[test]
323 fn test_coherence_tracker_average() {
324 let mut tracker = CoherenceTracker::default();
325
326 tracker.on_entry_added(10000); tracker.on_entry_added(8000); tracker.on_entry_added(6000); assert_eq!(tracker.entry_count(), 3);
333 assert!((tracker.average_coherence_f32() - 0.8).abs() < 0.01);
334 }
335
336 #[test]
337 fn test_coherence_tracker_mutation() {
338 let mut tracker = CoherenceTracker::default();
339
340 let initial_meta = tracker.create_initial_meta([0u8; 32]);
341 tracker.on_entry_added(initial_meta.coherence_score);
342
343 assert_eq!(initial_meta.coherence_score, 10000);
344 assert_eq!(initial_meta.mutation_epoch, 1); let new_meta = tracker.on_entry_mutated(&initial_meta, 9000, [1u8; 32]);
347
348 assert_eq!(new_meta.coherence_score, 9000);
349 assert_eq!(new_meta.mutation_epoch, 2); assert_eq!(new_meta.proof_attestation_hash, [1u8; 32]);
351 }
352
353 #[test]
354 fn test_coherence_threshold_violation() {
355 let tracker = CoherenceTracker::new(CoherenceConfig::new().with_min_threshold(0.5));
356
357 assert!(!tracker.would_violate_threshold(6000)); assert!(tracker.would_violate_threshold(4000)); }
360
361 #[test]
362 fn test_coherence_decay() {
363 let config = CoherenceConfig::new().with_decay_rate(0.1); let mut tracker = CoherenceTracker::new(config);
365
366 tracker.on_entry_added(10000);
367 assert_eq!(tracker.average_coherence, 10000);
368
369 tracker.apply_decay(1);
370 assert_eq!(tracker.average_coherence, 9000);
372 }
373}