#![allow(clippy::many_single_char_names)]
#![allow(clippy::too_many_arguments)]
use crate::error::OdeError;
use crate::ode::coeff::{CoefficientMap, CoefficientPoint};
use crate::ode::options::{AdaptiveOptions, OdeOptionMap, Points, StepTimeout};
use crate::ode::rosenbrock::RosenbrockCoeffs;
use crate::ode::runge_kutta::{ButcherTableau, WeightType, Weights};
use crate::ode::solution::OdeSolution;
use crate::ode::types::{OdeType, PNorm};
use crate::ode::Ode;
use alga::general::RealField;
use na::{allocator::Allocator, DMatrix, DVector, DefaultAllocator, Dim, U1, U2};
use num_traits::{abs, signum};
use std::fmt;
use std::ops::{Add, Mul};
#[derive(Debug, Clone)]
pub struct OdeProblem<F, Y>
where
F: Fn(f64, &Y) -> Y,
Y: OdeType,
{
f: F,
y0: Y,
tspan: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct OdeBuilder<F, Y>
where
F: Fn(f64, &Y) -> Y,
Y: OdeType,
{
f: Option<F>,
y0: Option<Y>,
tspan: Option<Vec<f64>>,
}
impl<F, Y> OdeBuilder<F, Y>
where
F: Fn(f64, &Y) -> Y,
Y: OdeType,
{
pub fn fun(mut self, f: F) -> Self {
self.f = Some(f);
self
}
pub fn init<T: Into<Y>>(mut self, y0: T) -> Self {
self.y0 = Some(y0.into());
self
}
pub fn tspan(mut self, tspan: Vec<f64>) -> Self {
self.tspan = Some(tspan);
self
}
pub fn tspan_linspace(mut self, from: f64, to: f64, n: usize) -> Self {
self.tspan = Some(itertools_num::linspace(from, to, n).collect());
self
}
pub fn build(self) -> Result<OdeProblem<F, Y>, OdeError> {
let f = self
.f
.ok_or_else(|| OdeError::uninitialized("Required problem must be initialized"))?;
let y0 = self
.y0
.ok_or_else(|| OdeError::uninitialized("Initial starting point must be initialized"))?;
let tspan = self
.tspan
.ok_or_else(|| OdeError::uninitialized("Time span must be initialized"))?;
Ok(OdeProblem { f, y0, tspan })
}
}
impl<F, Y> Default for OdeBuilder<F, Y>
where
F: Fn(f64, &Y) -> Y,
Y: OdeType,
{
fn default() -> Self {
Self {
f: None,
y0: None,
tspan: None,
}
}
}
impl<F, Y, T> OdeProblem<F, Y>
where
F: Fn(f64, &Y) -> Y,
T: RealField + Add<f64, Output = T> + Mul<f64, Output = T> + Into<f64>,
Y: OdeType<Item = T>,
{
pub fn builder() -> OdeBuilder<F, Y> {
OdeBuilder::default()
}
pub fn solve(self, ode: Ode, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
match ode {
Ode::Feuler => Ok(self.feuler()),
Ode::Heun => Ok(self.heun()),
Ode::Midpoint => Ok(self.midpoint()),
Ode::Ode23 => self.ode23(opts),
Ode::Ode23s => self.ode23s(opts),
Ode::Ode4 => Ok(self.ode4()),
Ode::Ode45 => self.ode45(opts),
Ode::Ode45fe => self.ode45_fe(opts),
Ode::Ode4skr => self.ode4s_kr(),
Ode::Ode4ss => self.ode4s_s(),
Ode::Ode78 => self.ode78(opts),
}
}
pub fn feuler(self) -> OdeSolution<f64, Y> {
self.oderk_fixed(&ButcherTableau::feuler())
}
pub fn heun(self) -> OdeSolution<f64, Y> {
self.oderk_fixed(&ButcherTableau::heun())
}
pub fn midpoint(self) -> OdeSolution<f64, Y> {
self.oderk_fixed(&ButcherTableau::midpoint())
}
pub fn ode21(&self, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderk_adapt(&ButcherTableau::rk21(), opts)
}
pub fn ode23(&self, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderk_adapt(&ButcherTableau::rk23(), opts)
}
pub fn ode4(self) -> OdeSolution<f64, Y> {
self.oderk_fixed(&ButcherTableau::rk4())
}
pub fn ode45(&self, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
self.ode45_dp(opts)
}
pub fn ode45_dp(&self, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderk_adapt(&ButcherTableau::dopri5(), opts)
}
pub fn ode45_fe(&self, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderk_adapt(&ButcherTableau::rk45(), opts)
}
pub fn ode78(&self, opts: OdeOptionMap) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderk_adapt(&ButcherTableau::feh78(), opts)
}
fn oderk_adapt<S: Dim, Ops: Into<AdaptiveOptions>>(
&self,
btab: &ButcherTableau<S>,
opts: Ops,
) -> Result<OdeSolution<f64, Y>, OdeError>
where
DefaultAllocator: Allocator<f64, U1, S>
+ Allocator<f64, S, U2>
+ Allocator<f64, S, S>
+ Allocator<f64, S>,
{
if !btab.is_adaptive() {
return Err(OdeError::InvalidButcherTableauWeightType {
expected: WeightType::Adaptive,
found: WeightType::Explicit,
});
}
if self.tspan.is_empty() {
return Ok(OdeSolution::default());
}
let mut t = self.tspan[0];
let tend = self.tspan[self.tspan.len() - 1];
let opts = opts.into();
let minstep = opts
.minstep
.map_or_else(|| abs(tend - t) / 1e18, |step| step.0);
let maxstep = opts
.maxstep
.map_or_else(|| abs(tend - t) / 2.5, |step| step.0);
let reltol = opts.reltol.0;
let abstol = opts.abstol.0;
let init = self.hinit(&self.y0, t, tend, btab.symbol.order().min(), reltol, abstol)?;
let mut dt = if opts.initstep.0 != 0. {
if (signum(opts.initstep.0) - init.tdir).abs() < std::f64::EPSILON {
opts.initstep.0
} else {
return Err(OdeError::InvalidInitstep);
}
} else {
init.h
};
let mut timeout = 0usize;
let order = btab.symbol.order().min();
let mut diagnostics = Diagnostics::default();
let norm = opts.norm.0;
let mut last_step = (t + dt - tend).abs() <= std::f64::EPSILON;
let mut tspan: Vec<f64> = Vec::with_capacity(self.tspan.len());
tspan.push(t);
let mut ys = Vec::with_capacity(self.tspan.len());
ys.push(self.y0.clone());
let mut coeff = CoefficientPoint::new(init.f0.clone(), self.y0.clone());
let mut iter_fixed = 1usize;
loop {
let coeffs = self.calc_coefficients(btab, t, coeff.clone(), dt);
let y = ys[ys.len() - 1].clone();
let (ytrial, yerr) = self.embedded_step(&y, &coeffs, t, dt, btab)?;
let step = self.stepsize_hw92(
dt, init.tdir, &y, &ytrial, yerr, order, timeout, abstol, reltol, maxstep,
);
timeout = step.timeout_ctn;
if step.err < 1. {
diagnostics.accepted_steps += 1;
let f0 = &coeffs[0].k;
let f1 = if btab.is_first_same_as_last() {
coeffs[btab.nstages() - 1].k.clone()
} else {
(self.f)(t + dt, &ytrial)
};
if Points::Specified == opts.points {
while iter_fixed < self.tspan.len()
&& (init.tdir * self.tspan[iter_fixed] < init.tdir * (t + dt) || last_step)
{
let yout = self.hermite_interp(
self.tspan[iter_fixed],
t,
dt,
&y,
&ytrial,
f0,
&f1,
);
ys.push(yout);
tspan.push(self.tspan[iter_fixed]);
iter_fixed += 1;
}
} else {
while iter_fixed < self.tspan.len()
&& init.tdir * t < init.tdir * self.tspan[iter_fixed]
&& init.tdir * self.tspan[iter_fixed] < init.tdir * (t + dt)
{
let yout = self.hermite_interp(
self.tspan[iter_fixed],
t,
dt,
&y,
&ytrial,
f0,
&f1,
);
ys.push(yout);
tspan.push(self.tspan[iter_fixed]);
iter_fixed += 1;
}
ys.push(ytrial.clone());
tspan.push(t + dt);
}
coeff = CoefficientPoint::new(f1, ytrial);
if last_step {
break;
}
t += dt;
dt = step.dt;
if init.tdir * (t + dt + dt / 100.) >= init.tdir * tend {
dt = tend - t;
last_step = true;
}
} else if step.dt.abs() < minstep {
break;
} else {
diagnostics.rejected_steps += 1;
last_step = false;
dt = step.dt;
timeout = *StepTimeout::default();
}
}
Ok(OdeSolution {
yout: ys,
tout: tspan,
})
}
fn oderk_fixed<S: Dim>(self, btab: &ButcherTableau<S>) -> OdeSolution<f64, Y>
where
DefaultAllocator: Allocator<f64, U1, S>
+ Allocator<f64, S, U2>
+ Allocator<f64, S, S>
+ Allocator<f64, S>,
{
let mut ys = Vec::with_capacity(self.tspan.len());
ys.push(self.y0.clone());
let dof = self.y0.dof();
for i in 0..self.tspan.len() - 1 {
let dt = self.tspan[i + 1] - self.tspan[i];
let mut yi = ys[i].clone();
let b = btab.b.as_slice();
for (s, k) in self
.calc_coefficients(
btab,
self.tspan[i],
CoefficientPoint::new((self.f)(self.tspan[i], &yi), yi.clone()),
dt,
)
.ks()
.enumerate()
{
for d in 0..dof {
*yi.get_mut(d) += k.get(d) * b[s] * dt;
}
}
ys.push(yi);
}
OdeSolution {
tout: self.tspan,
yout: ys,
}
}
pub fn ode23s<Ops: Into<AdaptiveOptions>>(
&self,
opts: Ops,
) -> Result<OdeSolution<f64, Y>, OdeError> {
if self.tspan.is_empty() {
return Ok(OdeSolution::default());
}
let mut t = self.tspan[0];
let tfinal = self.tspan[self.tspan.len() - 1];
let opts = opts.into();
let reltol = opts.reltol.0;
let abstol = opts.abstol.0;
let minstep = opts
.minstep
.map_or_else(|| (tfinal - t).abs() / 1e18, |step| step.0);
let maxstep = opts
.maxstep
.map_or_else(|| abs(tfinal - t) / 2.5, |step| step.0);
let two_sqrt = 2f64.sqrt();
let d = 1. / (2. + two_sqrt);
let e32 = 6. + two_sqrt;
let init = if opts.initstep.0 == 0. {
self.hinit(&self.y0, t, tfinal, 3, reltol, abstol)?
} else {
InitialHint {
h: opts.initstep.0,
tdir: (tfinal - t).signum(),
f0: (self.f)(t, &self.y0),
}
};
let mut h = init.tdir * init.h.abs().min(maxstep);
let mut tout: Vec<f64> = Vec::with_capacity(self.tspan.len());
tout.push(t);
let mut yout = Vec::with_capacity(self.tspan.len());
yout.push(self.y0.clone());
let mut jac = self.fdjacobian(t, &self.y0);
let (m, n) = jac.shape();
let identity = DMatrix::<T>::identity(m, n);
let mut y = self.y0.clone();
let mut f0 = DVector::from_iterator(y.dof(), init.f0.ode_iter());
while (t - tfinal).abs() > 0. && minstep < h.abs() {
if (t - tfinal).abs() < h.abs() {
h = tfinal - t;
}
let mut w = &identity - &jac * (T::one() * (h * d));
if jac.len() != 1 {
w = w.lu().lu_internal().clone();
};
let mut fdt = DVector::from_iterator(y.dof(), (self.f)(t + h / 100., &y).ode_iter());
for i in 0..fdt.dof() {
let fdti = fdt[i] - f0[i];
fdt[i] = fdti * ((h * d) / (h / 100.));
}
let w_inv = w.try_inverse().ok_or(OdeError::InvalidMatrix)?;
let k1 = &w_inv * (&f0 + &fdt);
let mut f1y = y.clone();
for i in 0..y.dof() {
*f1y.get_mut(i) += k1[i] * 0.5 * h;
}
let f1 = DVector::from_iterator(y.dof(), (self.f)(t + 0.5 * h, &f1y).ode_iter());
let k2 = &w_inv * (&f1 - &k1) + &k1;
let mut ynew = y.clone();
for i in 0..ynew.dof() {
*ynew.get_mut(i) += k2[i] * h;
}
let f2 = DVector::from_iterator(y.dof(), (self.f)(t + h, &ynew).ode_iter());
let k3 = &w_inv
* (&f2 - ((&k2 - &f1) * (T::one() * e32)) - ((&k1 - &f0) * (T::one() * 2.)) + &fdt);
let kerr = &k1 - (&k2 * (T::one() * 2.)) + &k3;
let mut etmp = y.clone();
for i in 0..etmp.dof() {
etmp.insert(i, kerr[i]);
}
let err = etmp.pnorm(PNorm::default()) * (h.abs() / 6.);
let delta = (y.pnorm(PNorm::default()).max(ynew.pnorm(PNorm::default())) * reltol)
.max(T::one() * abstol);
if err <= delta {
for toi in &self.tspan {
if *toi > t && *toi <= t + h {
let s = (*toi - t) / h;
tout.push(*toi);
let ktmp = &k1 * (T::one() * (s * (1. - s) / (1. - 2. * d)))
+ &k2 * (T::one() * (s * (s - 2. * d) / (1. - 2. * d)));
let mut ytmp = y.clone();
for i in 0..ytmp.dof() {
*ytmp.get_mut(i) += ktmp[i] * h;
}
yout.push(ytmp);
}
}
if Points::All == opts.points
&& (tout[tout.len() - 1] - (t + h)).abs() > std::f64::EPSILON
{
tout.push(t + h);
yout.push(ynew.clone());
}
t += h;
y = ynew;
f0 = f2;
jac = self.fdjacobian(t, &y);
}
let r: f64 = (delta / err).into();
h = maxstep.min(r.powf(1. / 3.) * h.abs() * 0.8) * init.tdir;
}
Ok(OdeSolution { yout, tout })
}
pub fn oderosenbrock<S: Dim>(
&self,
coeffs: RosenbrockCoeffs<S>,
) -> Result<OdeSolution<f64, Y>, OdeError>
where
DefaultAllocator: Allocator<f64, S, S> + Allocator<f64, S>,
{
if self.tspan.is_empty() {
return Ok(OdeSolution::default());
}
let h = diff(&self.tspan);
let mut x = Vec::with_capacity(self.tspan.len());
x.push(self.y0.clone());
let identity = DMatrix::<T>::identity(self.y0.dof(), self.y0.dof());
for (solstep, hs) in h.iter().enumerate() {
let ts = self.tspan[solstep];
let xs = x[solstep].clone();
let dfdx = self.fdjacobian(ts, &xs);
let (m, n) = dfdx.shape();
let v = DMatrix::from_diagonal_element(m, n, T::one() * (1. / (coeffs.gamma * hs)));
let jac = v - dfdx;
let jac_inv = jac.try_inverse().ok_or(OdeError::InvalidMatrix)?;
let mut g = Vec::with_capacity(coeffs.a.nrows());
let yg =
DVector::from_iterator(xs.dof(), (self.f)(ts + coeffs.b[0] * hs, &xs).ode_iter());
let jac_yg = &jac_inv * &yg;
let mut g1 = xs.clone();
for i in 0..g1.dof() {
g1.insert(i, jac_yg[i]);
}
g.push(g1);
let mut next_x = x[x.len() - 1].clone();
let g1 = &g[0];
for i in 0..next_x.dof() {
*next_x.get_mut(i) += (g1.get(i) * coeffs.b[0]);
}
for i in 1..coeffs.a.nrows() {
let mut dx = next_x.clone();
dx.set_zero();
let mut df = dx.clone();
for (j, gj) in g.iter().enumerate().take(i - 1) {
for d in 0..dx.dof() {
*dx.get_mut(d) += gj.get(d) * coeffs.a[(i, j)];
*df.get_mut(d) += gj.get(d) * coeffs.c[(i, j)];
}
}
let next_gvec = &jac_inv
* DVector::from_iterator(
xs.dof(),
(self.f)(ts + coeffs.b[i] * hs, &xs.clone().sum(&dx)).ode_iter(),
)
+ DVector::from_iterator(xs.dof(), df.ode_iter().map(|x| x * (1. / hs)));
let mut next_g = xs.clone();
for d in 0..next_g.dof() {
next_g.insert(d, next_gvec[d]);
*next_x.get_mut(d) += next_gvec[d] * coeffs.b[i];
}
g.push(next_g);
}
x.push(next_x);
}
Ok(OdeSolution { yout: x, tout: h })
}
pub fn ode4s_kr(&self) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderosenbrock(RosenbrockCoeffs::kr4())
}
pub fn ode4s_s(&self) -> Result<OdeSolution<f64, Y>, OdeError> {
self.oderosenbrock(RosenbrockCoeffs::s4())
}
fn calc_error<S: Dim>(
&self,
coeffs: &CoefficientMap<Y>,
btab: &ButcherTableau<S>,
dt: f64,
) -> Result<Y, OdeError>
where
DefaultAllocator: Allocator<f64, U1, S>
+ Allocator<f64, S, U2>
+ Allocator<f64, S, S>
+ Allocator<f64, S>,
{
assert_eq!(btab.nstages(), coeffs.len());
let mut err = coeffs[0].k.clone();
err.set_zero();
if let Weights::Adaptive(b) = &btab.b {
for (s, k) in coeffs.ks().enumerate() {
for d in 0..err.dof() {
let weight_err = b[(s, 0)] - b[(s, 1)];
*err.get_mut(d) += k.get(d) * weight_err;
}
}
for d in 0..err.dof() {
err.insert(d, err.get(d) * dt);
}
Ok(err)
} else {
Err(OdeError::InvalidButcherTableauWeightType {
expected: WeightType::Adaptive,
found: WeightType::Explicit,
})
}
}
pub fn calc_coefficients<S: Dim>(
&self,
btab: &ButcherTableau<S>,
t: f64,
init: CoefficientPoint<Y>,
dt: f64,
) -> CoefficientMap<Y>
where
DefaultAllocator: Allocator<f64, U1, S>
+ Allocator<f64, S, U2>
+ Allocator<f64, S, S>
+ Allocator<f64, S>,
{
let mut coeffs = CoefficientMap::with_capacity(btab.nstages());
coeffs.push(init);
for row in 1..btab.nstages() {
let mut yi = coeffs[0].y.clone();
for (col, k) in coeffs.ks().enumerate() {
for d in 0..yi.dof() {
*yi.get_mut(d) += k.get(d) * (btab.a[(row, col)] * dt);
}
}
let tn = t + btab.c[row] * dt;
coeffs.push(CoefficientPoint::new((self.f)(tn, &yi), yi));
}
coeffs
}
fn embedded_step<S: Dim>(
&self,
yn: &Y,
coeffs: &CoefficientMap<Y>,
_t: f64,
dt: f64,
btab: &ButcherTableau<S>,
) -> Result<(Y, Y), OdeError>
where
DefaultAllocator: Allocator<f64, U1, S>
+ Allocator<f64, S, U2>
+ Allocator<f64, S, S>
+ Allocator<f64, S>,
{
let mut ytrial = yn.clone();
ytrial.set_zero();
let mut yerr = ytrial.clone();
if let Weights::Adaptive(b) = &btab.b {
for (s, k) in coeffs.ks().enumerate() {
for d in 0..yn.dof() {
*ytrial.get_mut(d) += k.get(d) * b[(s, 0)];
*yerr.get_mut(d) += k.get(d) * b[(s, 1)];
}
}
for d in 0..yn.dof() {
yerr.insert(d, (ytrial.get(d) - yerr.get(d)) * dt);
ytrial.insert(d, yn.get(d) + (ytrial.get(d) * dt));
}
Ok((ytrial, yerr))
} else {
Err(OdeError::InvalidButcherTableauWeightType {
expected: WeightType::Adaptive,
found: WeightType::Explicit,
})
}
}
fn hermite_interp(&self, tquery: f64, t: f64, dt: f64, y0: &Y, y1: &Y, f0: &Y, f1: &Y) -> Y {
let mut y = y0.clone();
let theta = (tquery - t) / dt;
for i in 0..y0.dof() {
let val = (y0.get(i) * (1. - theta) + y1.get(i) * theta)
+ ((y1.get(i) - y0.get(i)) * (1. - 2. * theta)
+ f0.get(i) * (theta - 1.) * dt
+ f1.get(i) * theta * dt)
* theta
* (theta - 1.);
y.insert(i, val);
}
y
}
fn stepsize_hw92(
&self,
dt: f64,
tdir: f64,
x0: &Y,
xtrial: &Y,
mut xerr: Y,
order: usize,
mut timeout: usize,
abstol: f64,
reltol: f64,
maxstep: f64,
) -> StepHW92 {
let fac = 0.8;
let _facmax = 5.;
let facmin = 0.2;
for d in 0..x0.dof() {
if xtrial.get(d).into().is_nan() {
return StepHW92 {
err: 10.,
dt: facmin * dt,
timeout_ctn: *StepTimeout::default(),
};
}
*xerr.get_mut(d) /= x0.get(d).norm1().max(xtrial.get(d).norm1()) * reltol + abstol;
}
let err = xerr.pnorm(PNorm::default()).into();
let pow = 1. / (order + 1) as f64;
let mut new_dt = maxstep.min(facmin.max(err.powi(-1).powf(pow) * fac) * tdir * dt);
if timeout > 0 {
new_dt = new_dt.min(dt);
timeout -= 1;
}
StepHW92 {
err,
dt: tdir * new_dt,
timeout_ctn: timeout,
}
}
fn hinit(
&self,
x0: &Y,
t0: f64,
tend: f64,
order: usize,
reltol: f64,
abstol: f64,
) -> Result<InitialHint<Y>, OdeError> {
let tdir = signum(tend - t0);
if tdir == 0. {
return Err(OdeError::ZeroTimeSpan);
}
let norm = x0.pnorm(PNorm::InfPos);
let one = Y::Item::one();
let tau = (norm * reltol).max(one * abstol);
let d0 = norm / tau;
let f0 = (self.f)(t0, x0);
let d1 = f0.pnorm(PNorm::InfPos) / tau;
let h0: f64 = if d0 < one * 1e-5 || d1 < one * 1e-5 {
1.0e-6
} else {
0.01 * (d0 / d1).into()
};
let mut x1 = x0.clone();
for d in 0..x1.dof() {
*x1.get_mut(d) += f0.get(d) * h0 * tdir;
}
let mut f1_0 = (self.f)(t0 + tdir * h0, &x1);
for d in 0..f1_0.dof() {
*f1_0.get_mut(d) -= f0.get(d);
}
let d2 = f1_0.pnorm(PNorm::InfPos) / (tau * h0);
let h1: f64 = if d1.max(d2) < one * 1e-15f64 {
1.0e-6f64.max(1.0e-3f64 * h0)
} else {
let pow = -(2. + d1.max(d2).log10().into()) / ((order + 1) as f64);
10f64.powf(pow)
};
let h = tdir * h1.min(100. * h0).min(tdir * (tend - t0));
Ok(InitialHint { h, tdir, f0 })
}
pub fn fdjacobian(&self, t: f64, x: &Y) -> DMatrix<T> {
let ftx = (self.f)(t, x);
let lx = ftx.dof();
let mut dfdx = DMatrix::<T>::zeros(lx, lx);
for n in 0..lx {
let mut xj = x.get(n);
if xj == T::zero() {
xj += T::one();
}
let dxj = xj * 0.01;
let mut tmp = x.clone();
*tmp.get_mut(n) += dxj;
let yj = (self.f)(t, &tmp);
for m in 0..lx {
let mut yi = yj.get(m);
yi -= ftx.get(m);
yi /= dxj;
dfdx[(m, n)] = yi;
}
}
dfdx
}
}
#[inline]
pub fn diff<R: RealField>(a: &[R]) -> Vec<R> {
a.iter()
.skip(1)
.enumerate()
.map(|(i, r)| *r - a[i])
.collect()
}
#[derive(Debug)]
pub struct InitialHint<Y> {
h: f64,
tdir: f64,
f0: Y,
}
#[derive(Debug)]
pub struct StepHW92 {
err: f64,
dt: f64,
timeout_ctn: usize,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Diagnostics {
pub num_eval: u32,
pub accepted_steps: u32,
pub rejected_steps: u32,
}
impl fmt::Display for Diagnostics {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Number of function evaluations: {}", self.num_eval)?;
writeln!(f, "Number of accepted steps: {}", self.accepted_steps)?;
write!(f, "Number of rejected steps: {}", self.rejected_steps)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ode::options::OdeOp;
use std::fs::OpenOptions;
use std::io::Write;
const DT: f64 = 0.001;
const TF: f64 = 100.0;
const Y0: [f64; 3] = [0.1, 0.0, 0.0];
const SIGMA: f64 = 10.0;
const RHO: f64 = 28.0;
const BET: f64 = 8.0 / 3.0;
fn lorenz_attractor(_t: f64, v: &Vec<f64>) -> Vec<f64> {
let (x, y, z) = (v[0], v[1], v[2]);
let dx_dt = SIGMA * (y - x);
let dy_dt = x * (RHO - z) - y;
let dz_dt = x * y - BET * z;
vec![dx_dt, dy_dt, dz_dt]
}
fn lorenz_problem() -> OdeProblem<impl Fn(f64, &Vec<f64>) -> Vec<f64>, Vec<f64>> {
OdeProblem::builder()
.tspan_linspace(0., TF, 100_001)
.fun(lorenz_attractor)
.init(vec![0.1, 0., 0.])
.build()
.unwrap()
}
#[test]
fn diff_test() {
let a = vec![2., 6., 4., 16.];
assert_eq!(vec![4.0, -2.0, 12.0], diff(&a));
let v: Vec<f64> = Vec::new();
assert_eq!(v, diff(&v));
}
#[test]
fn ode45_test() {
let mut ops = OdeOptionMap::default();
ops.insert(Points::option_name(), Points::Specified.into());
let _solution = lorenz_problem().ode45(Default::default()).unwrap();
}
#[test]
fn fdjacobian_test() {
let problem = lorenz_problem();
let x = Y0.to_vec();
let jac = problem.fdjacobian(0.0, &x);
assert_eq!((3, 3), jac.shape());
}
}