use serde::{Deserialize, Serialize};
use super::{CalcInputName, FieldName, SequenceLookup, StateName};
#[derive(Debug, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Timeout {
Transition(StateName),
#[default]
Loop,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ThreshOp {
Gt { by: f64 },
Lt { by: f64 },
Approx { atol: f64 },
}
impl Default for ThreshOp {
fn default() -> Self {
Self::Gt { by: 0.0 }
}
}
impl ThreshOp {
pub fn eval(&self, v: f64, thresh: f64) -> bool {
assert!(
!v.is_nan() && !thresh.is_nan(),
"Unable to assess transition criteria involving NaN values."
);
match self {
ThreshOp::Gt { by } => v > thresh + by,
ThreshOp::Lt { by } => v < thresh - by,
ThreshOp::Approx { atol } => (v - thresh).abs() < *atol,
}
}
pub fn try_parse(op: (&str, f64)) -> Result<Self, String> {
let param = op.1;
let op = op.0.trim().to_ascii_lowercase();
if param.is_nan() {
return Err("Threshold op parameter must not be a NaN value".to_string());
}
match op.as_str() {
"gt" | ">" => Ok(Self::Gt { by: param }),
"lt" | "<" => Ok(Self::Lt { by: param }),
"approx" | "~" | "~=" => Ok(Self::Approx { atol: param }),
_ => Err(format!(
"Invalid threshold op `{op}`. Expected `gt`, `lt`, or `approx`"
)),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[non_exhaustive]
pub enum Transition {
ConstantThresh(CalcInputName, ThreshOp, f64),
ChannelThresh(CalcInputName, ThreshOp, CalcInputName),
LookupThresh(CalcInputName, ThreshOp, SequenceLookup),
}
impl Transition {
pub fn get_input_names(&self) -> Vec<FieldName> {
let mut names = Vec::new();
match self {
Self::ConstantThresh(name, _, _) => names.push(name.clone()),
Self::ChannelThresh(first, _, second) => {
names.extend_from_slice(&[first.clone(), second.clone()])
}
Self::LookupThresh(name, _, _) => names.push(name.clone()),
};
names
}
}