use std::ops::{Add, Div, Mul, Sub};
use crate::detector::channel::Channel;
use crate::types::series::{Series, SeriesBuilder};
use astronomy::time::Time;
use astronomy::units::{HERTZ, Quantity, QuantityError, SECOND, Unit, UnitProduct};
use ndarray::{Array1, array};
#[derive(Debug, Clone, PartialEq)]
pub struct TimeSeriesBase {
series_data: Series,
}
pub struct TimeSeriesBaseBuilder {
value: Option<Array1<f64>>,
unit: Option<Unit>,
name: Option<String>,
channel: Option<Channel>,
t0: Option<Time>, dt: Option<Quantity>, sample_rate: Option<Quantity>, times: Option<Quantity>,
_raw_t0_float: Option<f64>, }
impl TimeSeriesBaseBuilder {
pub fn new() -> Self {
TimeSeriesBaseBuilder {
value: None,
unit: None,
name: None,
channel: None,
t0: None,
dt: None,
sample_rate: None,
times: None,
_raw_t0_float: None, }
}
pub fn value(mut self, value: Array1<f64>) -> Self {
self.value = Some(value);
self
}
pub fn unit(mut self, unit: Unit) -> Self {
self.unit = Some(unit);
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn channel(mut self, channel: Channel) -> Self {
self.channel = Some(channel);
self
}
pub fn epoch(mut self, epoch: Time) -> Self {
self.t0 = Some(epoch);
self
}
pub fn t0(mut self, t0: f64) -> Self {
self._raw_t0_float = Some(t0);
self
}
pub fn dt(mut self, dt: Quantity) -> Self {
self.dt = Some(dt);
self
}
pub fn sample_rate(mut self, sample_rate: Quantity) -> Self {
self.sample_rate = Some(sample_rate);
self
}
pub fn times(mut self, times: Quantity) -> Self {
self.times = Some(times);
self
}
pub fn build(self) -> Result<TimeSeriesBase, QuantityError> {
let value = self.value.ok_or_else(|| {
QuantityError::InvalidQuantity("Value is required to build TimeSeriesBase".to_string())
})?;
let mut series_builder = SeriesBuilder::new().value(value).unit(
self.unit
.unwrap_or_else(|| Unit::new("", 1.0, UnitProduct::zero())),
);
if let Some(name) = self.name {
series_builder = series_builder.name(name);
}
if let Some(channel) = self.channel {
series_builder = series_builder.channel(channel);
}
if let Some(times_quantity) = self.times {
series_builder = series_builder.xindex(times_quantity);
} else {
let resolved_t0_quantity = if let Some(epoch_time) = self.t0 {
Some(Quantity::new(
array![epoch_time.as_gps_seconds_f64()],
SECOND,
))
} else {
self._raw_t0_float
.map(|raw_t0| Quantity::new(array![raw_t0], SECOND))
};
if let Some(t0_quantity) = resolved_t0_quantity {
series_builder = series_builder.x0(t0_quantity);
}
let resolved_dt_quantity = if let Some(dt_quantity) = self.dt {
Some(dt_quantity) } else if let Some(sample_rate_quantity) = self.sample_rate {
if sample_rate_quantity.value.len() != 1 {
return Err(QuantityError::InvalidQuantity(
"Sample rate must be a scalar quantity.".to_string(),
));
}
let unit_s = SECOND;
let one_quantity =
Quantity::new(array![1.0], Unit::new("", 1.0, UnitProduct::zero()));
let dt_converted = (one_quantity / sample_rate_quantity)?.to(&unit_s)?;
Some(dt_converted)
} else {
None
};
if let Some(dt_quantity) = resolved_dt_quantity {
series_builder = series_builder.dx(dt_quantity);
}
}
let series_data = series_builder.build()?;
Ok(TimeSeriesBase::new_internal(series_data))
}
}
impl Default for TimeSeriesBaseBuilder {
fn default() -> Self {
Self::new()
}
}
impl TimeSeriesBase {
fn new_internal(series_data: Series) -> Self {
TimeSeriesBase { series_data }
}
pub fn value(&self) -> &Array1<f64> {
self.series_data.value()
}
pub fn unit(&self) -> &Unit {
self.series_data.unit()
}
pub fn get_name(&self) -> Option<&str> {
self.series_data.get_name()
}
pub fn get_channel(&self) -> Option<&Channel> {
self.series_data.get_channel()
}
pub fn get_t0(&self) -> Option<&Quantity> {
self.series_data.get_x0()
}
pub fn get_dt(&self) -> Option<&Quantity> {
self.series_data.get_dx()
}
pub fn get_times(&self) -> Option<&Quantity> {
self.series_data.get_xindex()
}
pub fn get_epoch(&self) -> Option<Time> {
self.get_t0()
.map(|t0_quantity| Time::from_gps_seconds(t0_quantity.value[0]))
}
pub fn get_sample_rate(&self) -> Option<Quantity> {
self.get_dt().map(|dt_quantity| {
let one_quantity = Quantity::new(array![1.0], Unit::new("", 1.0, UnitProduct::zero()));
(one_quantity / dt_quantity.clone())
.expect("Failed to divide Quantity for sample rate.")
.to(&HERTZ)
.expect("Failed to convert Quantity to Hertz.")
})
}
pub fn duration(&self) -> Option<Quantity> {
self.get_times().map(|times_quantity| {
let values = ×_quantity.value;
if values.is_empty() {
Quantity::new(array![0.0], times_quantity.unit.clone())
} else {
let start_time = values[0];
let end_time = values[values.len() - 1];
let duration_value = end_time - start_time;
Quantity::new(array![duration_value], times_quantity.unit.clone())
}
})
}
}
impl Add for TimeSeriesBase {
type Output = Result<Self, QuantityError>;
fn add(self, rhs: Self) -> Self::Output {
let result_series = self.series_data.add(rhs.series_data)?;
Ok(TimeSeriesBase::new_internal(result_series))
}
}
impl Div for TimeSeriesBase {
type Output = Result<Self, QuantityError>;
fn div(self, rhs: Self) -> Self::Output {
let result_series = self.series_data.div(rhs.series_data)?;
Ok(TimeSeriesBase::new_internal(result_series))
}
}
impl Mul for TimeSeriesBase {
type Output = Result<Self, QuantityError>;
fn mul(self, rhs: Self) -> Self::Output {
let result_series = self.series_data.mul(rhs.series_data)?;
Ok(TimeSeriesBase::new_internal(result_series))
}
}
impl Sub for TimeSeriesBase {
type Output = Result<Self, QuantityError>;
fn sub(self, rhs: Self) -> Self::Output {
let result_series = self.series_data.sub(rhs.series_data)?;
Ok(TimeSeriesBase::new_internal(result_series))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::detector::channel::Channel;
use astronomy::units::{HERTZ, JOULE, METRE, SECOND};
use ndarray::array;
#[test]
fn test_timeseriesbase_creation_t0_dt() {
let t0_time = Time::from_gps_seconds(1126259446.0);
let dt_quantity = Quantity::new(array![0.000244140625], SECOND.clone());
let channel = Channel::new("H1:GW-STRAIN", None, None, None, None, None, None).unwrap();
let ts = TimeSeriesBaseBuilder::new()
.value(array![1.0, 2.0, 3.0, 4.0])
.unit(METRE.clone())
.epoch(t0_time) .dt(dt_quantity.clone())
.name("Strain Data".to_string())
.channel(channel)
.build()
.unwrap();
assert_eq!(ts.value(), &array![1.0, 2.0, 3.0, 4.0]);
assert_eq!(ts.unit(), &METRE);
assert_eq!(ts.get_name(), Some("Strain Data"));
assert_eq!(ts.get_channel().unwrap().get_name(), "H1:GW-STRAIN");
assert_eq!(
ts.get_t0().unwrap().value,
&array![t0_time.as_gps_seconds_f64()]
);
assert_eq!(ts.get_t0().unwrap().unit, SECOND);
assert_eq!(ts.get_dt().unwrap(), &dt_quantity);
assert_eq!(
ts.get_times().unwrap().value,
&array![
t0_time.as_gps_seconds_f64(),
t0_time.as_gps_seconds_f64() + dt_quantity.value[0],
t0_time.as_gps_seconds_f64() + 2.0 * dt_quantity.value[0],
t0_time.as_gps_seconds_f64() + 3.0 * dt_quantity.value[0],
]
);
assert_eq!(ts.get_times().unwrap().unit, SECOND);
assert_eq!(ts.get_epoch().unwrap(), t0_time);
assert_eq!(
ts.get_sample_rate().unwrap().value[0],
1.0 / dt_quantity.value[0]
);
assert_eq!(ts.get_sample_rate().unwrap().unit, HERTZ);
println!("TimeSeriesBase (t0, dt): {:?}", ts);
}
#[test]
fn test_timeseriesbase_creation_t0_sample_rate() {
let raw_t0 = 123456789.0;
let sr_quantity = Quantity::new(array![4096.0], HERTZ.clone());
let ts = TimeSeriesBaseBuilder::new()
.value(array![1.0, 2.0, 3.0])
.unit(JOULE.clone())
.t0(raw_t0) .sample_rate(sr_quantity.clone())
.name("Energy Reading".to_string())
.build()
.unwrap();
assert_eq!(ts.get_t0().unwrap().value, &array![raw_t0]);
assert_eq!(ts.get_t0().unwrap().unit, SECOND);
assert_eq!(ts.get_sample_rate().unwrap(), sr_quantity); assert_eq!(ts.get_dt().unwrap().value[0], 1.0 / 4096.0); assert_eq!(ts.get_dt().unwrap().unit, SECOND);
assert_eq!(ts.get_epoch().unwrap(), Time::from_gps_seconds(raw_t0));
println!("TimeSeriesBase (t0, sample_rate): {:?}", ts);
}
#[test]
fn test_timeseriesbase_creation_times() {
let explicit_times = Quantity::new(array![100.0, 101.0, 102.0], SECOND.clone());
let ts = TimeSeriesBaseBuilder::new()
.value(array![10.0, 11.0, 12.0])
.unit(METRE.clone())
.times(explicit_times.clone())
.name("Known Times".to_string())
.build()
.unwrap();
assert_eq!(ts.get_times().unwrap(), &explicit_times);
assert_eq!(ts.get_t0(), None); assert_eq!(ts.get_dt(), None);
assert_eq!(ts.get_epoch(), None); assert!(ts.get_sample_rate().is_none());
println!("TimeSeriesBase (times): {:?}", ts);
}
#[test]
fn test_timeseriesbase_arithmetic_propagation() {
let t0_time = Time::from_gps_seconds(100.0);
let dt_quantity = Quantity::new(array![0.1], SECOND.clone());
let ts1 = TimeSeriesBaseBuilder::new()
.value(array![1.0, 2.0])
.unit(METRE.clone())
.epoch(t0_time)
.dt(dt_quantity.clone())
.name("TS1".to_string())
.build()
.unwrap();
let ts2 = TimeSeriesBaseBuilder::new()
.value(array![5.0, 6.0])
.unit(METRE.clone())
.epoch(Time::from_gps_seconds(100.0)) .dt(dt_quantity.clone())
.name("TS2".to_string())
.build()
.unwrap();
let sum_ts = (ts1.clone() + ts2).unwrap();
assert_eq!(sum_ts.value(), &array![6.0, 8.0]);
assert_eq!(sum_ts.unit(), &METRE);
assert_eq!(sum_ts.get_name(), ts1.get_name()); assert_eq!(sum_ts.get_epoch(), ts1.get_epoch()); assert_eq!(sum_ts.get_dt().unwrap(), ts1.get_dt().unwrap()); }
}