use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReturnCode {
Success,
MaxIters,
Unstable,
Terminated,
Failure,
}
impl ReturnCode {
pub fn is_success(self) -> bool {
matches!(self, ReturnCode::Success)
}
pub fn name(self) -> &'static str {
match self {
ReturnCode::Success => "Success",
ReturnCode::MaxIters => "MaxIters",
ReturnCode::Unstable => "Unstable",
ReturnCode::Terminated => "Terminated",
ReturnCode::Failure => "Failure",
}
}
}
impl std::fmt::Display for ReturnCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
pub const DEFAULT_RELTOL: f64 = 1e-4;
pub const DEFAULT_ABSTOL: f64 = 1e-6;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Alg {
Bdf,
Sdirk,
Erk,
}
#[cfg(feature = "solve")]
pub(crate) type RawTrajectory = (Vec<f64>, Vec<Vec<f64>>, ReturnCode);
impl Alg {
pub fn from_name(name: &str) -> Option<Self> {
match name.trim().to_ascii_lowercase().as_str() {
"bdf" => Some(Self::Bdf),
"sdirk" => Some(Self::Sdirk),
"erk" => Some(Self::Erk),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Bdf => "bdf",
Self::Sdirk => "sdirk",
Self::Erk => "erk",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Progress<'a> {
pub t0: f64,
pub t: f64,
pub t_end: f64,
pub step: usize,
pub maxiters: Option<usize>,
pub u: &'a [f64],
}
impl Progress<'_> {
pub fn fraction(&self) -> f64 {
let span = self.t_end - self.t0;
if !span.is_finite() || span <= 0.0 {
return 0.0;
}
((self.t - self.t0) / span).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Flow {
Continue,
Cancel,
}
#[cfg(target_arch = "wasm32")]
pub type ProgressFn = std::sync::Arc<dyn for<'p> Fn(&Progress<'p>) -> Flow>;
#[cfg(not(target_arch = "wasm32"))]
pub type ProgressFn = std::sync::Arc<dyn for<'p> Fn(&Progress<'p>) -> Flow + Send + Sync>;
#[derive(Clone)]
pub struct SolveOptions {
pub alg: Alg,
pub abstol: Option<f64>,
pub reltol: Option<f64>,
pub maxiters: Option<usize>,
pub saveat: Option<Vec<f64>>,
pub callback: Option<crate::problem::CallbackSet>,
pub progress: Option<ProgressFn>,
pub output_observed: Vec<String>,
}
impl std::fmt::Debug for SolveOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SolveOptions")
.field("alg", &self.alg)
.field("abstol", &self.abstol)
.field("reltol", &self.reltol)
.field("maxiters", &self.maxiters)
.field("saveat", &self.saveat)
.field("callback", &self.callback)
.field(
"progress",
&self
.progress
.as_ref()
.map(|_| "<observer>")
.unwrap_or("None"),
)
.field("output_observed", &self.output_observed)
.finish()
}
}
impl Default for SolveOptions {
fn default() -> Self {
Self {
alg: Alg::Bdf,
abstol: None,
reltol: None,
maxiters: None,
saveat: None,
callback: None,
progress: None,
output_observed: Vec::new(),
}
}
}
impl SolveOptions {
pub fn abstol_or_default(&self) -> f64 {
self.abstol.unwrap_or(DEFAULT_ABSTOL)
}
pub fn reltol_or_default(&self) -> f64 {
self.reltol.unwrap_or(DEFAULT_RELTOL)
}
pub fn sample_evenly(&mut self, t0: f64, t_end: f64, n: usize) {
let n = n.max(2);
let span = t_end - t0;
self.saveat = Some(
(0..n)
.map(|i| t0 + span * (i as f64) / ((n - 1) as f64))
.collect(),
);
}
}
#[derive(Debug, Clone)]
pub struct Solution {
pub time: Vec<f64>,
pub state: Vec<Vec<f64>>,
pub state_variable_names: Vec<String>,
pub retcode: ReturnCode,
pub metadata: SolutionMetadata,
}
impl Solution {
pub fn get(&self, name: &str) -> Option<&[f64]> {
self.index_of(name).map(|i| self.state[i].as_slice())
}
pub fn index_of(&self, name: &str) -> Option<usize> {
if let Some(i) = self.state_variable_names.iter().position(|n| n == name) {
return Some(i);
}
if let Some(survivor) = self.metadata.merged_variable_renames.get(name)
&& let Some(i) = self.state_variable_names.iter().position(|n| n == survivor)
{
return Some(i);
}
if name.contains('.') {
let ns = self.metadata.namespace.as_deref()?;
let rest = name.strip_prefix(ns)?.strip_prefix('.')?;
return self.state_variable_names.iter().position(|n| n == rest);
}
let mut hit = None;
for (i, n) in self.state_variable_names.iter().enumerate() {
let matches = n.contains('.') && n.rsplit('.').next() == Some(name);
if matches {
if hit.is_some() {
return None; }
hit = Some(i);
}
}
hit
}
pub fn variable(&self, name: &str) -> Result<&[f64], SimulateError> {
self.get(name)
.ok_or_else(|| SimulateError::InvalidParameter {
name: name.to_string(),
})
}
pub fn at(&self, name: &str, k: usize) -> Option<f64> {
self.get(name).and_then(|row| row.get(k).copied())
}
pub fn final_value(&self, name: &str) -> Option<f64> {
self.get(name).and_then(|row| row.last().copied())
}
pub fn variable_names(&self) -> &[String] {
&self.state_variable_names
}
}
impl std::ops::Index<&str> for Solution {
type Output = [f64];
fn index(&self, name: &str) -> &[f64] {
self.get(name).unwrap_or_else(|| {
panic!(
"no state variable '{name}' in this solution; have {:?}",
self.state_variable_names
)
})
}
}
#[derive(Debug, Clone, Default)]
pub struct SolutionMetadata {
pub alg: String,
pub n_rhs_calls: usize,
pub n_jacobian_calls: usize,
pub n_accepted_steps: usize,
pub n_rejected_steps: usize,
pub tape_fallbacks: Vec<(String, String)>,
pub merged_variable_renames: HashMap<String, String>,
pub namespace: Option<String>,
}