use interpn::one_dim::{Interp1D, RectilinearGrid1D};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Serialize, Deserialize)]
pub enum InterpMethod {
Linear,
#[default]
Left,
Right,
Nearest,
}
impl InterpMethod {
pub fn try_parse(s: &str) -> Result<Self, String> {
let lower = s.to_lowercase();
let normalized = lower.trim();
match normalized {
"linear" => Ok(Self::Linear),
"left" => Ok(Self::Left),
"right" => Ok(Self::Right),
"nearest" => Ok(Self::Nearest),
_ => Err(format!("Unable to process method: `{s}`")),
}
}
pub fn to_str(&self) -> &'static str {
match self {
InterpMethod::Linear => "linear",
InterpMethod::Left => "left",
InterpMethod::Right => "right",
InterpMethod::Nearest => "nearest",
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SequenceLookup {
pub(super) method: InterpMethod,
pub(super) time_s: Vec<f64>,
pub(super) vals: Vec<f64>,
}
impl SequenceLookup {
pub fn new(method: InterpMethod, time_s: Vec<f64>, vals: Vec<f64>) -> Result<Self, String> {
let seq = Self {
method,
time_s,
vals,
};
match seq.validate() {
Ok(()) => Ok(seq),
Err(x) => Err(x),
}
}
pub fn validate(&self) -> Result<(), String> {
if !self.time_s.is_sorted() {
return Err("Sequence time entries must be monotonically increasing".to_owned());
}
let first_time = self
.time_s
.first()
.ok_or_else(|| "Empty sequence".to_string())?;
match self.eval_checked(*first_time) {
Ok(_) => Ok(()),
Err(x) => Err(x),
}?;
let last_time = self
.time_s
.last()
.ok_or_else(|| "Empty sequence".to_string())?;
match self.eval_checked(*last_time) {
Ok(_) => Ok(()),
Err(x) => Err(x),
}?;
Ok(())
}
pub fn eval_checked(&self, sequence_time_s: f64) -> Result<f64, String> {
let grid = RectilinearGrid1D::new(&self.time_s, &self.vals).map_err(|e| e.to_owned())?;
match self.method {
InterpMethod::Linear => interpn::LinearHoldLast1D::new(grid)
.eval_one(sequence_time_s)
.map_err(|e| e.to_owned()),
InterpMethod::Left => interpn::Left1D::new(grid)
.eval_one(sequence_time_s)
.map_err(|e| e.to_owned()),
InterpMethod::Right => interpn::Right1D::new(grid)
.eval_one(sequence_time_s)
.map_err(|e| e.to_owned()),
InterpMethod::Nearest => interpn::Nearest1D::new(grid)
.eval_one(sequence_time_s)
.map_err(|e| e.to_owned()),
}
}
pub fn eval(&self, sequence_time_s: f64) -> f64 {
self.eval_checked(sequence_time_s).unwrap()
}
}