1use serde::{Deserialize, Serialize};
2
3pub struct ExpAvg {
8 alpha: f32, avg: Option<f32>, }
11
12impl ExpAvg {
13 pub fn new(alpha: f32) -> Self {
15 assert!(
16 (0.0..=1.0).contains(&alpha),
17 "Alpha must be in the range (0,1]"
18 );
19 Self { alpha, avg: None }
20 }
21
22 pub fn update(&mut self, sample: f32) -> f32 {
24 self.avg = Some(match self.avg {
25 Some(current_avg) => self.alpha * sample + (1.0 - self.alpha) * current_avg,
26 None => sample, });
28 self.avg.unwrap()
29 }
30
31 pub fn get(&self) -> Option<f32> {
33 self.avg
34 }
35}
36
37fn cosine_decay(current_timestep: usize, end_timestep: usize, start_val: f32, end_val: f32) -> f32 {
38 if current_timestep >= end_timestep {
39 return end_val;
40 }
41
42 use std::f32::consts::PI;
43 let decay_ratio = (current_timestep as f32) / (end_timestep as f32);
44 let cosine_decay = 0.5 * (1.0 + (PI * decay_ratio).cos());
45 end_val + (start_val - end_val) * cosine_decay
46}
47
48#[derive(Clone, Serialize, Deserialize, Debug)]
50pub enum Decay {
51 None(f32),
53 Linear { start: f32, decay: f32 },
55 Cosine {
58 start: f32,
59 end: f32,
60 num_steps: usize,
61 },
62}
63
64impl Decay {
65 pub fn start_value(&self) -> f32 {
67 use Decay::*;
68 match self {
69 None(v) => *v,
70 Linear { start, .. } => *start,
71 Cosine { start, .. } => *start,
72 }
73 }
74
75 pub fn at_timestep(&self, timestep: usize) -> f32 {
77 use Decay::*;
78 match self {
79 None(v) => *v,
80 Linear { start, decay } => {
81 let decayed = *start - (*decay * timestep as f32);
82 if decayed < *decay {
83 *decay
84 } else {
85 decayed
86 }
87 }
88 Cosine {
89 start,
90 end,
91 num_steps,
92 } => cosine_decay(timestep, *num_steps, *start, *end),
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_decay_linear() {
103 let decay = Decay::Linear {
104 start: 2.0,
105 decay: 0.25,
106 };
107 assert_eq!(decay.at_timestep(0), 2.0);
108 assert_eq!(decay.at_timestep(1), 1.75);
109 assert_eq!(decay.at_timestep(999999), 0.25);
110 }
111
112 #[test]
113 fn test_decay_cosine() {
114 let decay = Decay::Cosine {
115 start: 2.0,
116 end: 1.0,
117 num_steps: 1000,
118 };
119 assert_eq!(decay.at_timestep(0), 2.0);
120
121 let mid = decay.at_timestep(500);
122 assert!(mid > 1.49);
123 assert!(mid < 1.51);
124
125 assert_eq!(decay.at_timestep(999999), 1.0);
126 }
127}