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 fn update(&mut self, section: CrossSection) -> Option<f64> {
65 let oscillator = self.oscillator.step(§ion);
66 self.sum += oscillator;
67 self.has_emitted = true;
68 Some(self.sum)
69 }
70
71 fn reset(&mut self) {
72 self.oscillator.reset();
73 self.sum = 0.0;
74 self.has_emitted = false;
75 }
76
77 fn warmup_period(&self) -> usize {
78 1
79 }
80
81 fn is_ready(&self) -> bool {
82 self.has_emitted
83 }
84
85 fn name(&self) -> &'static str {
86 "McClellanSummationIndex"
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::cross_section::Member;
94 use crate::traits::BatchExt;
95
96 fn section(up: usize, down: usize) -> CrossSection {
97 let mut members = Vec::new();
98 for _ in 0..up {
99 members.push(Member::new(1.0, 10.0, false, false));
100 }
101 for _ in 0..down {
102 members.push(Member::new(-1.0, 10.0, false, false));
103 }
104 members.push(Member::new(0.0, 10.0, false, false));
105 CrossSection::new(members, 0).unwrap()
106 }
107
108 #[test]
109 fn accessors_and_metadata() {
110 let msi = McClellanSummationIndex::new();
111 assert_eq!(msi.name(), "McClellanSummationIndex");
112 assert_eq!(msi.warmup_period(), 1);
113 assert!(!msi.is_ready());
114 }
115
116 #[test]
117 fn first_tick_starts_at_zero() {
118 let mut msi = McClellanSummationIndex::new();
119 assert_eq!(msi.update(section(3, 1)), Some(0.0));
120 assert!(msi.is_ready());
121 }
122
123 #[test]
124 fn accumulates_the_oscillator() {
125 let mut msi = McClellanSummationIndex::new();
126 assert_eq!(msi.update(section(3, 1)), Some(0.0)); let value = msi.update(section(1, 3)).unwrap();
129 assert!((value - (-50.0)).abs() < 1e-9);
130 let value = msi.update(section(2, 2)).unwrap();
132 assert!((value - (-117.5)).abs() < 1e-9);
133 }
134
135 #[test]
136 fn reset_clears_state() {
137 let mut msi = McClellanSummationIndex::new();
138 msi.update(section(3, 1));
139 msi.update(section(1, 3));
140 assert!(msi.is_ready());
141 msi.reset();
142 assert!(!msi.is_ready());
143 assert_eq!(msi.update(section(1, 3)), Some(0.0));
145 }
146
147 #[test]
148 fn batch_equals_streaming() {
149 let sections = vec![section(3, 1), section(1, 3), section(2, 2)];
150 let mut a = McClellanSummationIndex::new();
151 let mut b = McClellanSummationIndex::new();
152 assert_eq!(
153 a.batch(§ions),
154 sections
155 .iter()
156 .map(|s| b.update(s.clone()))
157 .collect::<Vec<_>>()
158 );
159 }
160}