wickra_core/indicators/
mcclellan_summation_index.rs1use crate::cross_section::CrossSection;
4use crate::indicators::mcclellan_oscillator::McClellanOscillator;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone, Default)]
42pub struct McClellanSummationIndex {
43 oscillator: McClellanOscillator,
44 sum: f64,
45 has_emitted: bool,
46}
47
48impl McClellanSummationIndex {
49 #[must_use]
51 pub fn new() -> Self {
52 Self {
53 oscillator: McClellanOscillator::new(),
54 sum: 0.0,
55 has_emitted: false,
56 }
57 }
58}
59
60impl Indicator for McClellanSummationIndex {
61 type Input = CrossSection;
62 type Output = f64;
63
64 #[inline]
65 fn update(&mut self, section: CrossSection) -> Option<f64> {
66 let oscillator = self.oscillator.step(§ion);
67 self.sum += oscillator;
68 self.has_emitted = true;
69 Some(self.sum)
70 }
71
72 fn reset(&mut self) {
73 self.oscillator.reset();
74 self.sum = 0.0;
75 self.has_emitted = false;
76 }
77
78 #[inline]
79 fn warmup_period(&self) -> usize {
80 1
81 }
82
83 #[inline]
84 fn is_ready(&self) -> bool {
85 self.has_emitted
86 }
87
88 #[inline]
89 fn name(&self) -> &'static str {
90 "McClellanSummationIndex"
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::cross_section::Member;
98 use crate::traits::BatchExt;
99
100 fn section(up: usize, down: usize) -> CrossSection {
101 let mut members = Vec::new();
102 for _ in 0..up {
103 members.push(Member::new(1.0, 10.0, false, false));
104 }
105 for _ in 0..down {
106 members.push(Member::new(-1.0, 10.0, false, false));
107 }
108 members.push(Member::new(0.0, 10.0, false, false));
109 CrossSection::new(members, 0).unwrap()
110 }
111
112 #[test]
113 fn accessors_and_metadata() {
114 let msi = McClellanSummationIndex::new();
115 assert_eq!(msi.name(), "McClellanSummationIndex");
116 assert_eq!(msi.warmup_period(), 1);
117 assert!(!msi.is_ready());
118 }
119
120 #[test]
121 fn first_tick_starts_at_zero() {
122 let mut msi = McClellanSummationIndex::new();
123 assert_eq!(msi.update(section(3, 1)), Some(0.0));
124 assert!(msi.is_ready());
125 }
126
127 #[test]
128 fn accumulates_the_oscillator() {
129 let mut msi = McClellanSummationIndex::new();
130 assert_eq!(msi.update(section(3, 1)), Some(0.0)); let value = msi.update(section(1, 3)).unwrap();
133 assert!((value - (-50.0)).abs() < 1e-9);
134 let value = msi.update(section(2, 2)).unwrap();
136 assert!((value - (-117.5)).abs() < 1e-9);
137 }
138
139 #[test]
140 fn reset_clears_state() {
141 let mut msi = McClellanSummationIndex::new();
142 msi.update(section(3, 1));
143 msi.update(section(1, 3));
144 assert!(msi.is_ready());
145 msi.reset();
146 assert!(!msi.is_ready());
147 assert_eq!(msi.update(section(1, 3)), Some(0.0));
149 }
150
151 #[test]
152 fn batch_equals_streaming() {
153 let sections = vec![section(3, 1), section(1, 3), section(2, 2)];
154 let mut a = McClellanSummationIndex::new();
155 let mut b = McClellanSummationIndex::new();
156 assert_eq!(
157 a.batch(§ions),
158 sections
159 .iter()
160 .map(|s| b.update(s.clone()))
161 .collect::<Vec<_>>()
162 );
163 }
164}