oxihuman_core/
decay_counter.rs1#![allow(dead_code)]
4
5use std::f32::consts::E;
6
7#[allow(dead_code)]
9#[derive(Debug, Clone, Copy)]
10pub struct DecayCounter {
11 value: f32,
12 half_life: f32,
13 last_update: f32,
14}
15
16#[allow(dead_code)]
17impl DecayCounter {
18 pub fn new(half_life: f32) -> Self {
19 Self {
20 value: 0.0,
21 half_life: half_life.max(f32::EPSILON),
22 last_update: 0.0,
23 }
24 }
25
26 pub fn with_initial(value: f32, half_life: f32) -> Self {
27 Self {
28 value,
29 half_life: half_life.max(f32::EPSILON),
30 last_update: 0.0,
31 }
32 }
33
34 pub fn increment(&mut self, amount: f32) {
35 self.value += amount;
36 }
37
38 pub fn update(&mut self, current_time: f32) {
39 let dt = current_time - self.last_update;
40 if dt > 0.0 {
41 let decay_rate = (2.0_f32).ln() / self.half_life;
42 self.value *= (-decay_rate * dt).exp();
43 self.last_update = current_time;
44 }
45 }
46
47 pub fn value(&self) -> f32 {
48 self.value
49 }
50
51 pub fn value_at(&self, time: f32) -> f32 {
52 let dt = time - self.last_update;
53 if dt <= 0.0 {
54 return self.value;
55 }
56 let decay_rate = (2.0_f32).ln() / self.half_life;
57 self.value * (-decay_rate * dt).exp()
58 }
59
60 pub fn half_life(&self) -> f32 {
61 self.half_life
62 }
63
64 pub fn set_half_life(&mut self, hl: f32) {
65 self.half_life = hl.max(f32::EPSILON);
66 }
67
68 pub fn reset(&mut self) {
69 self.value = 0.0;
70 self.last_update = 0.0;
71 }
72
73 pub fn is_negligible(&self, threshold: f32) -> bool {
74 self.value.abs() < threshold
75 }
76
77 pub fn time_to_reach(&self, target: f32) -> Option<f32> {
78 if self.value <= 0.0 || target <= 0.0 || target >= self.value {
79 return None;
80 }
81 let decay_rate = (2.0_f32).ln() / self.half_life;
82 Some((self.value / target).ln() / decay_rate)
83 }
84
85 pub fn decay_constant(&self) -> f32 {
87 let _ = E; (2.0_f32).ln() / self.half_life
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_new() {
98 let dc = DecayCounter::new(1.0);
99 assert!((dc.value() - 0.0).abs() < f32::EPSILON);
100 }
101
102 #[test]
103 fn test_increment() {
104 let mut dc = DecayCounter::new(1.0);
105 dc.increment(10.0);
106 assert!((dc.value() - 10.0).abs() < f32::EPSILON);
107 }
108
109 #[test]
110 fn test_decay_one_half_life() {
111 let mut dc = DecayCounter::with_initial(100.0, 1.0);
112 dc.update(1.0);
113 assert!((dc.value() - 50.0).abs() < 0.5);
114 }
115
116 #[test]
117 fn test_value_at() {
118 let dc = DecayCounter::with_initial(100.0, 1.0);
119 let v = dc.value_at(1.0);
120 assert!((v - 50.0).abs() < 0.5);
121 }
122
123 #[test]
124 fn test_half_life_getter() {
125 let dc = DecayCounter::new(2.5);
126 assert!((dc.half_life() - 2.5).abs() < f32::EPSILON);
127 }
128
129 #[test]
130 fn test_set_half_life() {
131 let mut dc = DecayCounter::new(1.0);
132 dc.set_half_life(5.0);
133 assert!((dc.half_life() - 5.0).abs() < f32::EPSILON);
134 }
135
136 #[test]
137 fn test_reset() {
138 let mut dc = DecayCounter::with_initial(50.0, 1.0);
139 dc.reset();
140 assert!((dc.value() - 0.0).abs() < f32::EPSILON);
141 }
142
143 #[test]
144 fn test_is_negligible() {
145 let dc = DecayCounter::with_initial(0.001, 1.0);
146 assert!(dc.is_negligible(0.01));
147 assert!(!dc.is_negligible(0.0001));
148 }
149
150 #[test]
151 fn test_time_to_reach() {
152 let dc = DecayCounter::with_initial(100.0, 1.0);
153 let t = dc.time_to_reach(50.0).expect("should succeed");
154 assert!((t - 1.0).abs() < 0.1);
155 }
156
157 #[test]
158 fn test_decay_constant() {
159 let dc = DecayCounter::new(1.0);
160 let lambda = dc.decay_constant();
161 assert!((lambda - (2.0_f32).ln()).abs() < 1e-5);
162 }
163}