use std::sync::Arc;
use lox_core::math::series::{InterpolationType, Series, SeriesError};
use crate::deltas::TimeDelta;
use crate::time::Time;
use crate::time_scales::TimeScale;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TimeSeries<T: TimeScale> {
epoch: Time<T>,
series: Series,
}
impl<T: TimeScale + Copy> TimeSeries<T> {
pub fn new(epoch: Time<T>, series: Series) -> Self {
Self { epoch, series }
}
pub fn try_new(
epoch: Time<T>,
x: impl Into<Arc<[f64]>>,
y: impl Into<Arc<[f64]>>,
interpolation: InterpolationType,
) -> Result<Self, SeriesError> {
let series = Series::try_new(x, y, interpolation)?;
Ok(Self { epoch, series })
}
pub fn interpolate(&self, time: Time<T>) -> f64 {
let dt = time - self.epoch;
self.series.interpolate(delta_to_f64(dt))
}
pub fn epoch(&self) -> Time<T> {
self.epoch
}
pub fn series(&self) -> &Series {
&self.series
}
pub fn times(&self) -> Vec<Time<T>> {
self.series
.x()
.iter()
.map(|&x| self.epoch + TimeDelta::from_seconds_f64(x))
.collect()
}
pub fn values(&self) -> &[f64] {
self.series.y()
}
pub fn iter(&self) -> impl Iterator<Item = (Time<T>, f64)> + '_ {
self.series
.x()
.iter()
.zip(self.series.y().iter())
.map(|(&x, &y)| (self.epoch + TimeDelta::from_seconds_f64(x), y))
}
pub fn first(&self) -> (Time<T>, f64) {
let (x, y) = self.series.first();
(self.epoch + TimeDelta::from_seconds_f64(x), y)
}
pub fn last(&self) -> (Time<T>, f64) {
let (x, y) = self.series.last();
(self.epoch + TimeDelta::from_seconds_f64(x), y)
}
}
fn delta_to_f64(delta: TimeDelta) -> f64 {
delta.to_seconds().to_f64()
}
#[cfg(test)]
mod tests {
use lox_core::math::series::{InterpolationType, SeriesError};
use crate::time::Time;
use crate::time_scales::Tai;
use super::*;
fn epoch() -> Time<Tai> {
Time::builder_with_scale(Tai)
.with_ymd(2024, 1, 1)
.with_hms(0, 0, 0.0)
.build()
.unwrap()
}
#[test]
fn test_new_and_interpolate() {
let ep = epoch();
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y = vec![0.0, 1.0, 4.0, 9.0, 16.0];
let ts = TimeSeries::try_new(ep, x, y, InterpolationType::Linear).unwrap();
let t = ep + TimeDelta::from_seconds_f64(1.5);
let val = ts.interpolate(t);
assert!((val - 2.5).abs() < 1e-12);
}
#[test]
fn test_epoch_and_series() {
let ep = epoch();
let x = vec![0.0, 1.0, 2.0];
let y = vec![10.0, 20.0, 30.0];
let ts = TimeSeries::try_new(ep, x, y, InterpolationType::Linear).unwrap();
assert_eq!(ts.epoch(), ep);
assert_eq!(ts.series().x(), &[0.0, 1.0, 2.0]);
assert_eq!(ts.values(), &[10.0, 20.0, 30.0]);
}
#[test]
fn test_times() {
let ep = epoch();
let x = vec![0.0, 60.0, 120.0];
let y = vec![1.0, 2.0, 3.0];
let ts = TimeSeries::try_new(ep, x, y, InterpolationType::Linear).unwrap();
let times = ts.times();
assert_eq!(times.len(), 3);
assert_eq!(times[0], ep);
assert_eq!(times[1], ep + TimeDelta::from_seconds_f64(60.0));
assert_eq!(times[2], ep + TimeDelta::from_seconds_f64(120.0));
}
#[test]
fn test_iter() {
let ep = epoch();
let x = vec![0.0, 1.0];
let y = vec![5.0, 10.0];
let ts = TimeSeries::try_new(ep, x, y, InterpolationType::Linear).unwrap();
let pairs: Vec<_> = ts.iter().collect();
assert_eq!(pairs.len(), 2);
assert_eq!(pairs[0], (ep, 5.0));
assert_eq!(pairs[1], (ep + TimeDelta::from_seconds_f64(1.0), 10.0));
}
#[test]
fn test_first_last() {
let ep = epoch();
let x = vec![0.0, 100.0, 200.0];
let y = vec![1.0, 2.0, 3.0];
let ts = TimeSeries::try_new(ep, x, y, InterpolationType::Linear).unwrap();
let (ft, fv) = ts.first();
assert_eq!(ft, ep);
assert_eq!(fv, 1.0);
let (lt, lv) = ts.last();
assert_eq!(lt, ep + TimeDelta::from_seconds_f64(200.0));
assert_eq!(lv, 3.0);
}
#[test]
fn test_try_new_insufficient_points() {
let ep = epoch();
let result = TimeSeries::try_new(ep, vec![1.0], vec![1.0], InterpolationType::Linear);
assert_eq!(result.unwrap_err(), SeriesError::InsufficientPoints(1));
}
#[test]
fn test_try_new_dimension_mismatch() {
let ep = epoch();
let result = TimeSeries::try_new(ep, vec![0.0, 1.0], vec![1.0], InterpolationType::Linear);
assert_eq!(result.unwrap_err(), SeriesError::DimensionMismatch(2, 1));
}
#[test]
fn test_try_new_non_monotonic() {
let ep = epoch();
let result = TimeSeries::try_new(
ep,
vec![0.0, 2.0, 1.0],
vec![1.0, 2.0, 3.0],
InterpolationType::Linear,
);
assert_eq!(result.unwrap_err(), SeriesError::NonMonotonic);
}
}