1use crate::Error;
2
3pub struct MultiParams {
5 min_size: usize,
6 degree: i32,
7 beta: Option<f64>,
8 percent: Option<f64>,
9}
10
11pub fn multi() -> MultiParams {
13 MultiParams {
14 min_size: 30,
15 degree: 1,
16 beta: None,
17 percent: None,
18 }
19}
20
21impl MultiParams {
22 pub fn min_size(&mut self, value: usize) -> &mut Self {
24 self.min_size = value;
25 self
26 }
27
28 pub fn degree(&mut self, value: i32) -> &mut Self {
30 self.degree = value;
31 self
32 }
33
34 pub fn beta<T>(&mut self, value: T) -> &mut Self
36 where
37 T: Into<Option<f64>>,
38 {
39 self.beta = value.into();
40 self
41 }
42
43 pub fn percent<T>(&mut self, value: T) -> &mut Self
45 where
46 T: Into<Option<f64>>,
47 {
48 self.percent = value.into();
49 self
50 }
51
52 pub fn fit(&self, z: &[f64]) -> Result<Vec<usize>, Error> {
54 if self.min_size < 2 {
55 return Err(Error::Parameter("min_size must be at least 2".to_string()));
56 }
57 if self.beta.is_some() && self.percent.is_some() {
58 return Err(Error::Parameter(
59 "beta and percent cannot be passed together".to_string(),
60 ));
61 }
62 if self.degree < 0 || self.degree > 2 {
63 return Err(Error::Parameter("degree must be 0, 1, or 2".to_string()));
64 }
65
66 if z.len() < self.min_size {
67 return Ok(Vec::new());
68 }
69
70 let min = z.iter().min_by(|i, j| i.partial_cmp(j).unwrap()).unwrap();
72 let max = z.iter().max_by(|i, j| i.partial_cmp(j).unwrap()).unwrap();
73 let denom = max - min;
74 if denom == 0.0 {
75 return Ok(Vec::new());
76 }
77 let zcounts: Vec<f64> = z.iter().map(|x| (x - min) / denom).collect();
78
79 if self.percent.is_some() {
80 Ok(crate::edm_multi::edm_percent(
81 &zcounts,
82 self.min_size,
83 self.percent.unwrap(),
84 self.degree,
85 ))
86 } else {
87 Ok(crate::edm_multi::edm_multi(
88 &zcounts,
89 self.min_size,
90 self.beta.unwrap_or(0.008),
91 self.degree,
92 ))
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use crate::Error;
100
101 #[rustfmt::skip]
102 fn generate_series() -> Vec<f64> {
103 vec![
104 3.0, 1.0, 2.0, 3.0, 2.0, 1.0, 1.0, 2.0, 2.0, 3.0,
105 6.0, 4.0, 4.0, 5.0, 6.0, 4.0, 4.0, 4.0, 6.0, 5.0,
106 9.0, 8.0, 7.0, 9.0, 8.0, 9.0, 9.0, 9.0, 7.0, 9.0
107 ]
108 }
109
110 #[test]
111 fn test_multi() {
112 let series = generate_series();
113 let breakouts = crate::multi().min_size(5).fit(&series).unwrap();
114 assert_eq!(vec![10, 15, 20], breakouts);
115 }
116
117 #[test]
118 fn test_percent() {
119 let series = generate_series();
120 let breakouts = crate::multi()
121 .min_size(5)
122 .percent(0.5)
123 .fit(&series)
124 .unwrap();
125 assert_eq!(vec![8, 19], breakouts);
126 }
127
128 #[test]
129 fn test_empty() {
130 let series = Vec::new();
131 let breakouts = crate::multi().fit(&series).unwrap();
132 assert!(breakouts.is_empty());
133 }
134
135 #[test]
136 fn test_constant() {
137 let series = vec![1.0; 100];
138 let breakouts = crate::multi().fit(&series).unwrap();
139 assert!(breakouts.is_empty());
140 }
141
142 #[test]
143 fn test_almost_constant() {
144 let mut series = vec![1.0; 100];
145 series[50] = 2.0;
146 let breakouts = crate::multi().fit(&series).unwrap();
147 assert!(breakouts.is_empty());
148 }
149
150 #[test]
151 #[rustfmt::skip]
152 fn test_simple() {
153 let series = vec![
154 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
155 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
156 ];
157 let breakouts = crate::multi().min_size(5).fit(&series).unwrap();
158 assert_eq!(vec![10], breakouts);
159 }
160
161 #[test]
162 fn test_bad_min_size() {
163 let series = Vec::new();
164 let result = crate::multi().min_size(1).fit(&series);
165 assert_eq!(
166 result.unwrap_err(),
167 Error::Parameter("min_size must be at least 2".to_string())
168 );
169 }
170
171 #[test]
172 fn test_beta_percent() {
173 let series = Vec::new();
174 let result = crate::multi().beta(0.008).percent(0.5).fit(&series);
175 assert_eq!(
176 result.unwrap_err(),
177 Error::Parameter("beta and percent cannot be passed together".to_string())
178 );
179 }
180
181 #[test]
182 fn test_bad_degree() {
183 let series = Vec::new();
184 let result = crate::multi().degree(3).fit(&series);
185 assert_eq!(
186 result.unwrap_err(),
187 Error::Parameter("degree must be 0, 1, or 2".to_string())
188 );
189 }
190}