time_func/
lib.rs

1#![feature(is_sorted)]
2#![feature(let_chains)]
3use chrono::{DateTime, Duration, Utc};
4use plotters::prelude::*;
5use serde::{Deserialize, Serialize};
6use std::{error::Error, ops::{Mul, Div, Add, Sub}};
7
8/// A vector of data points that we can use to analyse time series data.
9/// Each data point is valid from the previous timestamp until its current timestamp.
10/// If it is the first point of data then it is only valid for a total time of 0.
11/**
12```text
13                .B
14          |    /\    |
15          |   /  \   |
16          |  /    \  |
17          | /      \ |
18         A./        \.C
19__________|__________|_________
20Invalid   |  Valid   |  Invalid
21_______________________________
22          .     .    .
23          A     B    C
24```
25*/
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27pub struct TimeFunc(pub Vec<(DateTime<Utc>, f64)>);
28
29impl From<Vec<(DateTime<Utc>, f64)>> for TimeFunc {
30    fn from(input: Vec<(DateTime<Utc>, f64)>) -> Self { TimeFunc(input) }
31}
32
33fn intersection(lhs: TimeFunc, rhs: TimeFunc, function: fn(f64, f64) -> f64 ) -> TimeFunc {
34    let lhs_domain = lhs.get_domain();
35    let rhs_domain = rhs.get_domain();
36
37    let output_start_time = DateTime::max(lhs_domain.start, rhs_domain.start);
38
39    let lhs_start_index = lhs.get_index_safe(&output_start_time);
40    let rhs_start_index = rhs.get_index_safe(&output_start_time);
41
42    let mut lhs_index = lhs_start_index;
43    let mut rhs_index = rhs_start_index;
44
45    let mut output = TimeFunc::new();
46
47    loop {
48        let lhs_tuple = lhs.0.get(lhs_index);
49        let rhs_tuple = rhs.0.get(rhs_index);
50
51        if let Some(lhs_tuple) = lhs_tuple 
52        && let Some(rhs_tuple) = rhs_tuple {
53            if lhs_tuple.0 < rhs_tuple.0 {
54                let val = function(lhs_tuple.1, rhs.get_value_interpolated(&lhs_tuple.0));
55                output.push((lhs_tuple.0, val)).unwrap();
56                lhs_index += 1;
57            } else if lhs_tuple.0 > rhs_tuple.0 {
58                let val = function(lhs.get_value_interpolated(&rhs_tuple.0), rhs_tuple.1);
59                output.push((rhs_tuple.0, val)).unwrap();
60                rhs_index += 1;
61            } else {  //lhs_tuple.0 == rhs_tuple.0
62                let val = function(lhs_tuple.1, rhs_tuple.1);
63                output.push((lhs_tuple.0, val)).unwrap();
64                lhs_index += 1;
65                rhs_index += 1;
66            }
67        } else { return output; }
68    }
69}
70
71impl Mul for TimeFunc {
72    type Output = Self;
73
74    fn mul(self, rhs: Self) -> Self::Output {
75        intersection(self, rhs, |x, y| {x*y})
76    }
77}
78
79impl Div for TimeFunc {
80    type Output = Self;
81
82    fn div(self, rhs: Self) -> Self::Output {
83        intersection(self, rhs, |x, y| {x/y})
84    }
85}
86
87impl Add for TimeFunc {
88    type Output = Self;
89
90    fn add(self, rhs: Self) -> Self::Output {
91        intersection(self, rhs, |x, y| {x+y})
92    }
93}
94
95impl Sub for TimeFunc {
96    type Output = Self;
97
98    fn sub(self, rhs: Self) -> Self::Output {
99        intersection(self, rhs, |x, y| {x-y})
100    }
101}
102
103impl TimeFunc {
104    /// Creates an empty TimeFunc
105    pub fn new() -> Self { TimeFunc(vec![]) }
106
107    /// Gets the average of the function from time, to time - duration
108    /// This function isn't perfects acurate as it uses the closest available indeces, and does not interpolate
109    pub fn get_moving_average(&self, time: DateTime<Utc>, duration: Duration) -> f64 {
110        let start_index = self.get_index_safe(&(time - duration));
111        //if start_index == 0 { start_index = 1 }
112        let end_index = self.get_index_safe(&time);
113        if start_index == end_index {
114            return self.0[end_index].1;
115        }
116        if end_index == 0 {
117            return self.0[0].1;
118        }
119        let mut sum = 0.0;
120        let mut total_duration = Duration::seconds(0);
121        for i in start_index..end_index {
122            let this = self.0[i];
123            let next = self.0[i + 1];
124            let duration = next.0 - this.0;
125            total_duration = total_duration + duration;
126            sum += this.1 * duration.num_seconds() as f64;
127        }
128        sum / total_duration.num_seconds() as f64
129    }
130
131    /// Gets the average of the function from time, to time - duration
132    /// This function interpolates the function and uses exact positions
133    pub fn get_integral_interpolated(&self, time: DateTime<Utc>, duration: Duration) -> f64 {
134        let start_time = time - duration;
135        let start_point = (start_time, self.get_value_interpolated(&start_time));
136        let end_point = (time, self.get_value_interpolated(&time));
137        let start_index = self.get_index_above(&start_time);
138        let end_index = self.get_index_below(&time);
139        let mut current_index = start_index;
140        let mut integral = 0.0;
141        integral += get_integral(start_point, self.0[start_index]);
142        while current_index < end_index {
143            integral += get_integral(self.0[current_index], self.0[current_index + 1]);
144            current_index += 1;
145        }
146        integral += get_integral(self.0[end_index], end_point);
147        integral
148    }
149
150    /// Gets the linear interpolated average of the function
151    pub fn get_average_interpolated(&self, time: DateTime<Utc>, duration: Duration) -> f64 {
152        let integral = self.get_integral_interpolated(time, duration);
153        integral / duration.num_seconds() as f64
154    }
155
156    /// Get's the normalized RMS
157    /// Does not use interpolation as of yet
158    pub fn get_rms(&self, duration: Duration, time: DateTime<Utc>) -> f64 {
159        let average = self.get_moving_average(time, duration);
160        let end_index = self.get_index_safe(&time);
161        let mut start_index = self.get_index_safe(&(time - duration));
162        if end_index < 2 {
163            return 0.0;
164        }
165        if end_index == start_index {
166            start_index = end_index - 1;
167        }
168        let mut total_duration = Duration::seconds(0);
169        let mut sum = 0.0;
170        for i in start_index..end_index {
171            let this = self.0[i];
172            let next = self.0[i + 1];
173            let duration = next.0 - this.0;
174            let relative_square = ((this.1 - average) / average).powf(2.0);
175            sum += relative_square * duration.num_seconds() as f64;
176            total_duration = total_duration + duration;
177        }
178        (sum / total_duration.num_seconds() as f64).sqrt()
179    }
180
181    pub fn get_rms_timefunc(&self, duration: Duration) -> Self {
182        let mut timefunc = TimeFunc::new();
183        for i in 1..self.0.len() {
184            let time = self.0[i].0;
185            timefunc.push((time, self.get_rms(duration, time))).unwrap();
186        }
187        timefunc
188    }
189
190    pub fn get_inflation(&self, duration: Duration, time: DateTime<Utc>) -> f64 {
191        let end_index = self.get_index_safe(&time);
192        if end_index == 0 {
193            return 0.0;
194        }
195        let mut start_index = self.get_index_safe(&(time - duration));
196        if end_index == start_index {
197            start_index = end_index - 1
198        }
199        let end = self.0[end_index];
200        let start = self.0[start_index];
201        let duration = end.0 - start.0;
202        ((start.1 / end.1) - 1.0) * (365 * 24 * 60 * 60) as f64 / duration.num_seconds() as f64
203    }
204
205    pub fn get_inflation_interpolated(&self, duration: Duration, time: DateTime<Utc>) -> f64 {
206        let start = self.get_value_interpolated(&(time - duration));
207        let end = self.get_value_interpolated(&time);
208        ((start / end) - 1.0) * (365 * 24 * 60 * 60) as f64 / duration.num_seconds() as f64
209    }
210
211    pub fn get_inflation_timefunc(&self, duration: Duration) -> Self {
212        let mut timefunc = TimeFunc::new();
213        for i in 1..self.0.len() {
214            let time = self.0[i].0;
215            timefunc
216                .push((time, self.get_inflation(duration, time)))
217                .unwrap();
218        }
219        timefunc
220    }
221
222    pub fn get_inflation_interpolated_timefunc(&self, duration: Duration) -> Self {
223        let mut timefunc = TimeFunc::new();
224        for i in 1..self.0.len() {
225            let time = self.0[i].0;
226            timefunc
227                .push((time, self.get_inflation_interpolated(duration, time)))
228                .unwrap();
229        }
230        timefunc
231    }
232
233    pub fn get_index(&self, time: &DateTime<Utc>) -> Result<usize, usize> {
234        self.0.binary_search_by(|probe| probe.0.cmp(time))
235    }
236
237    pub fn get_index_safe(&self, time: &DateTime<Utc>) -> usize {
238        match self.get_index(time) {
239            Ok(index) => index,
240            Err(index) => {
241                let len = self.0.len();
242                if index == len {
243                    len - 1
244                } else {
245                    index
246                }
247            }
248        }
249    }
250
251    pub fn get_index_above(&self, time: &DateTime<Utc>) -> usize {
252        match self.get_index(time) {
253            Ok(index) => index + 1,
254            Err(index) => index,
255        }
256    }
257
258    pub fn get_index_below(&self, time: &DateTime<Utc>) -> usize {
259        match self.get_index(time) {
260            Ok(index) => index - 1,
261            Err(index) => index - 1,
262        }
263    }
264
265    /// Returns the fractional index of a specific time
266    pub fn get_fractional_index(&self, time: &DateTime<Utc>) -> Result<f64, Box<dyn Error>> {
267        match self.get_index(time) {
268            Ok(index) => Ok(index as f64),
269            Err(index) => {
270                let len = self.0.len();
271                if index == len || index == 0 {
272                    // point falls out of range
273                    return Err("Index is out of bounds".into())
274                } else {
275                    let lower_time = self.0[index - 1].0;
276                    let upper_time = self.0[index].0;
277                    let time_step = upper_time - lower_time;
278                    let difference = *time - lower_time;
279                    let fractional =
280                        difference.num_seconds() as f64 / time_step.num_seconds() as f64;
281                    let fractional_index = (index - 1) as f64 + fractional;
282                    Ok(fractional_index)
283                }
284            }
285        }
286    }
287
288    pub fn get_value(&self, time: DateTime<Utc>) -> f64 {
289        let result = self.get_index(&time);
290        match result {
291            Ok(index) => self.0[index].1,
292            Err(index) => {
293                let len = self.0.len();
294                if index == len {
295                    self.0.last().unwrap().1
296                } else {
297                    self.0[index].1
298                }
299            }
300        }
301    }
302
303    pub fn get_value_interpolated(&self, time: &DateTime<Utc>) -> f64 {
304        let result = self.get_index(&time);
305        match result {
306            Ok(index) => self.0[index].1,
307            // if value is not hit, the returned index is higher than the true value
308            Err(index) => {
309                if index == 0 {
310                    return self.0[0].1;
311                }
312                let len = self.0.len();
313                if index == len {
314                    return self.0.last().unwrap().1;
315                }
316                let upper_index = index;
317                let lower_index = index - 1;
318                let upper_tuple = self.0[upper_index];
319                let lower_tuple = self.0[lower_index];
320                let slope = (upper_tuple.1 - lower_tuple.1)
321                    / (upper_tuple.0 - lower_tuple.0).num_seconds() as f64;
322                slope * (time.to_owned() - lower_tuple.0).num_seconds() as f64 + lower_tuple.1
323            }
324        }
325    }
326
327    fn get_range(&self) -> std::ops::Range<f64> {
328        let mut max = self.0[0].1;
329        let mut min = self.0[0].1;
330        for i in 1..self.0.len() {
331            let val = self.0[i].1;
332            if val > max {
333                max = val
334            } else if val < min {
335                min = val
336            }
337        }
338        min..max
339    }
340
341    fn get_domain(&self) -> std::ops::Range<DateTime<Utc>> {
342        let first = self.0[0].0;
343        let last = self.0.last().unwrap().0;
344        first..last
345    }
346
347    pub fn push(&mut self, point: (DateTime<Utc>, f64)) -> Result<(), Box<dyn Error>> {
348        match self.0.last() {
349            Some(last) => {
350                if last.0 >= point.0 {
351                    return Err("Attempting to add point to TimeFunc that is out of order.".into());
352                }
353            }
354            None => {}
355        }
356        self.0.push(point);
357        Ok(())
358    }
359
360    /// Verifies that the timefunc is valid.
361    /// Checks to ensure the list is sorted and deduped.
362    pub fn verify(&self) -> Result<(), Box<dyn Error>> {
363        if self.0.is_sorted_by(|a, b| Some(a.0.cmp(&b.0))) && self.is_deduped() {
364            Ok(())
365        } else {
366            Err("TimeFunc is invalid.".into())
367        }
368    }
369
370    /// Executed dedup
371    /// Assumes vec is sorted.
372    pub fn dedup(&mut self) { self.0.dedup_by(|a, b| a.0.eq(&b.0)); }
373
374    /// Verifies that the function is deduped
375    /// Assumes vec is sorted is sorted.
376    pub fn is_deduped(&self) -> bool {
377        for i in 1..self.0.len() {
378            if self.0[i].0 == self.0[i - 1].0 {
379                return false;
380            }
381        }
382        true
383    }
384
385    /// Fixes the TimeFunc by sorting it.
386    pub fn repair(&mut self) {
387        self.0.sort_unstable_by(|a, b| a.0.cmp(&b.0));
388        self.dedup();
389    }
390
391    pub fn draw(&self, title: String) -> Result<(), Box<dyn std::error::Error>> {
392        let filename = format!("images/{}.png", title);
393        let style = ("sans-serif", 25).into_font();
394        let root = BitMapBackend::new(&filename, (1200, 800)).into_drawing_area();
395        root.fill(&WHITE)?;
396
397        let mut chart = ChartBuilder::on(&root)
398            .caption(title, ("sans-serif", 50).into_font())
399            .margin(10)
400            .x_label_area_size(60)
401            .y_label_area_size(80)
402            .right_y_label_area_size(80)
403            .build_cartesian_2d(self.get_domain().yearly(), self.get_range())?;
404
405        chart
406            .configure_mesh()
407            .x_label_style(style.clone())
408            .y_label_style(style)
409            .draw()?;
410
411        chart.draw_series(LineSeries::new(self.0.clone(), &BLUE))?;
412
413        Ok(())
414    }
415}
416
417/// Gets the area under the interpolated curve
418/// Assumes points are ordered properly
419fn get_integral(a: (DateTime<Utc>, f64), b: (DateTime<Utc>, f64)) -> f64 {
420    let duration = b.0 - a.0;
421    let average = get_average(b.1, a.1);
422    duration.num_seconds() as f64 * average
423}
424
425/// Simply gets the average of two f64s
426fn get_average(a: f64, b: f64) -> f64 { (a + b) / 2.0 }
427
428#[cfg(test)]
429mod tests {
430
431    use super::*;
432
433    #[test]
434    fn test_get_moving_average() {
435        let mut time_func = TimeFunc::new();
436        let start_time = Utc::now();
437        let time_step = Duration::seconds(60);
438        time_func.push((start_time, 1.0)).unwrap();
439        time_func.push((start_time + time_step, 1.0)).unwrap();
440        time_func.push((start_time + time_step * 2, 1.0)).unwrap();
441
442        let av = time_func.get_moving_average(start_time + time_step * 2, time_step * 2);
443        assert_eq!(av, 1.0)
444    }
445
446    #[test]
447    fn test_get_fractional_index() {
448        let mut time_func = TimeFunc::new();
449        let start_time = Utc::now();
450        let time_step = Duration::seconds(60);
451        time_func.push((start_time, 1.0)).unwrap();
452        time_func.push((start_time + time_step, 1.0)).unwrap();
453        time_func.push((start_time + time_step * 2, 1.0)).unwrap();
454
455        let fractional_index = time_func
456            .get_fractional_index(&(start_time + time_step / 2))
457            .unwrap();
458        assert_eq!(fractional_index, 0.5);
459        let fractional_index = time_func.get_fractional_index(&(start_time - time_step / 2));
460        assert!(fractional_index.is_err());
461    }
462
463    #[test]
464    fn test_get_value_interpolated() {
465        let mut time_func = TimeFunc::new();
466        let start_time = Utc::now();
467        let time_step = Duration::seconds(60);
468        time_func.push((start_time, 1.0)).unwrap();
469        time_func.push((start_time + time_step, 2.0)).unwrap();
470        time_func.push((start_time + time_step * 2, 1.0)).unwrap();
471
472        let value = time_func.get_value_interpolated(&(start_time + time_step / 2));
473        assert_eq!(value, 1.5);
474    }
475
476    #[test]
477    fn test_get_integral_interpolated() {
478        let mut time_func = TimeFunc::new();
479        let start_time = Utc::now();
480        let time_step = Duration::seconds(60);
481        time_func.push((start_time, 1.0)).unwrap();
482        time_func.push((start_time + time_step, 2.0)).unwrap();
483        time_func.push((start_time + time_step * 2, 1.0)).unwrap();
484
485        let integral =
486            time_func.get_integral_interpolated(start_time + time_step * 2, time_step * 2);
487        assert_eq!(integral, 1.5 * 60.0 + 1.5 * 60.0);
488    }
489}