1use crate::error::Error;
2use crate::linalg;
3use crate::name;
4use crate::name::Name;
5use crate::params::Params;
6
7#[derive(Clone, Debug)]
9pub struct Complexity {
10 pub name: Name,
12
13 pub notation: &'static str,
15
16 pub params: Params,
18
19 pub rank: u32,
21}
22
23fn get_function(name: Name, params: Params) -> Result<Box<dyn Fn(f64) -> f64>, Error> {
25 if let (Some(a), Some(b)) = match name {
26 Name::Polynomial => (params.gain, params.power),
27 Name::Exponential => (params.gain, params.base),
28 _other => (params.gain, params.offset),
29 } {
30 let f: Box<dyn Fn(f64) -> f64> = match name {
31 Name::Constant => Box::new(move |_x| b),
32 Name::Logarithmic => Box::new(move |x| a * x.ln() + b),
33 Name::Linear => Box::new(move |x| a * x + b),
34 Name::Linearithmic => Box::new(move |x| a * x * x.ln() + b),
35 Name::Quadratic => Box::new(move |x| a * x.powi(2) + b),
36 Name::Cubic => Box::new(move |x| a * x.powi(3) + b),
37 Name::Polynomial => Box::new(move |x| a * x.powf(b)),
38 Name::Exponential => Box::new(move |x| a * b.powf(x)),
39 };
40 Ok(f)
41 } else {
42 Err(Error::MissingFunctionCoeffsError)
43 }
44}
45
46#[allow(dead_code)]
48fn compute_f(name: Name, params: Params, x: Vec<f64>) -> Result<Vec<f64>, Error> {
49 let f = get_function(name, params)?;
50 let y = x.into_iter().map(f).collect();
51 Ok(y)
52}
53
54pub struct ComplexityBuilder {
55 name: Name,
56 params: Option<Params>,
57}
58
59impl ComplexityBuilder {
60 pub fn new(name: Name) -> Self {
61 Self { name, params: None }
62 }
63
64 #[allow(dead_code)] pub fn power(&mut self, x: f64) -> &mut Self {
66 self.params = Some(Params::new().power(x).build());
67 self
68 }
69
70 pub fn build(&self) -> Complexity {
71 let mut params = Params::new().build();
72 if let Some(p) = &self.params {
73 params = p.clone();
74 }
75 let rank = rank(self.name, params.clone());
76 Complexity {
77 name: self.name,
78 notation: name::notation(self.name),
79 params,
80 rank,
81 }
82 }
83}
84
85fn linearize(name: Name, x: f64, y: f64) -> (f64, f64) {
87 match name {
88 Name::Constant => (0.0, y),
89 Name::Logarithmic => (x.ln(), y),
90 Name::Linear => (x, y),
91 Name::Linearithmic => (x * x.ln(), y),
92 Name::Quadratic => (x.powi(2), y),
93 Name::Cubic => (x.powi(3), y),
94 Name::Polynomial => (x.ln(), y.ln()),
95 Name::Exponential => (x, y.ln()),
96 }
97}
98
99fn delinearize(name: Name, gain: f64, offset: f64) -> Params {
101 let (a, b) = match name {
103 Name::Polynomial => (offset.exp(), gain),
104 Name::Exponential => (offset.exp(), gain.exp()),
105 _other => (gain, offset),
106 };
107 match name {
109 Name::Polynomial => Params::new().gain(a).power(b).build(),
110 Name::Exponential => Params::new().gain(a).base(b).build(),
111 _other => Params::new().gain(a).offset(b).build(),
112 }
113}
114
115fn calculate_residuals(name: Name, params: Params, data: Vec<(f64, f64)>) -> Result<f64, Error> {
116 let f = get_function(name, params)?;
117 let residuals = data.into_iter().map(|(x, y)| (y - f(x)).abs()).sum();
118
119 Ok(residuals)
120}
121
122fn rank(name: Name, params: Params) -> u32 {
123 match name {
135 Name::Constant => 0,
136 Name::Logarithmic => 130,
137 Name::Linear => 1_000,
138 Name::Linearithmic => 1_130,
139 Name::Quadratic => 2_000,
140 Name::Cubic => 3_000,
141 Name::Polynomial => match params.power {
142 Some(power) => std::cmp::min((1_000.0 * power) as u32, 1_000_000),
143 None => panic!("Polynomial is missing its power parameter"),
144 },
145 Name::Exponential => 1_000_000,
146 }
147}
148
149pub fn fit(name: Name, data: Vec<(f64, f64)>) -> Result<Complexity, Error> {
151 let linearized = data
152 .clone()
153 .into_iter()
154 .map(|(x, y)| linearize(name, x, y))
155 .collect();
156
157 let (gain, offset, _residuals) = linalg::fit_line(linearized)?;
158 let params = delinearize(name, gain, offset);
159 let residuals = calculate_residuals(name, params.clone(), data)?;
161 let params = Params {
162 residuals: Some(residuals),
163 ..params
164 };
165 let rank = rank(name, params.clone());
166
167 Ok(Complexity {
168 name,
169 notation: name::notation(name),
170 params,
171 rank,
172 })
173}
174
175pub fn complexity(string: &str) -> Result<Complexity, Error> {
190 let name: Name = string.try_into()?;
191 Ok(crate::complexity::ComplexityBuilder::new(name).build())
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 fn constant() -> Complexity {
199 ComplexityBuilder::new(Name::Constant).build()
200 }
201
202 fn logarithmic() -> Complexity {
203 ComplexityBuilder::new(Name::Logarithmic).build()
204 }
205
206 fn linear() -> Complexity {
207 ComplexityBuilder::new(Name::Linear).build()
208 }
209
210 fn linearithmic() -> Complexity {
211 ComplexityBuilder::new(Name::Linearithmic).build()
212 }
213
214 fn quadratic() -> Complexity {
215 ComplexityBuilder::new(Name::Quadratic).build()
216 }
217
218 fn cubic() -> Complexity {
219 ComplexityBuilder::new(Name::Cubic).build()
220 }
221
222 fn exponential() -> Complexity {
223 ComplexityBuilder::new(Name::Exponential).build()
224 }
225
226 fn polynomial(power: f64) -> Complexity {
227 ComplexityBuilder::new(Name::Polynomial)
228 .power(power)
229 .build()
230 }
231
232 #[test]
233 fn test_complecity_rank() {
234 assert!(constant().rank < logarithmic().rank);
236 assert!(logarithmic().rank < polynomial(0.5).rank);
237 assert!(polynomial(0.5).rank < linear().rank);
238
239 assert!(linear().rank < linearithmic().rank);
241 assert!(linearithmic().rank < polynomial(1.5).rank);
242 assert!(polynomial(1.5).rank < quadratic().rank);
243
244 assert!(quadratic().rank < polynomial(2.5).rank);
246 assert!(polynomial(2.5).rank < cubic().rank);
247
248 assert!(cubic().rank < polynomial(3.5).rank);
250 assert!(polynomial(3.5).rank < exponential().rank);
251 }
252}