use std::rc::Rc;
use crate::{
op::{unit::UnitCallable, ConstantOp},
scalar::Scalar,
LinearOp, Matrix, NonLinearOp, Vector,
};
use serde::Serialize;
#[derive(Clone, Debug, Serialize)]
pub struct OdeEquationsStatistics {
pub number_of_rhs_evals: usize,
pub number_of_jac_mul_evals: usize,
pub number_of_mass_evals: usize,
pub number_of_mass_matrix_evals: usize,
pub number_of_jacobian_matrix_evals: usize,
}
impl Default for OdeEquationsStatistics {
fn default() -> Self {
Self::new()
}
}
impl OdeEquationsStatistics {
pub fn new() -> Self {
Self {
number_of_rhs_evals: 0,
number_of_jac_mul_evals: 0,
number_of_mass_evals: 0,
number_of_mass_matrix_evals: 0,
number_of_jacobian_matrix_evals: 0,
}
}
}
pub trait OdeEquations {
type T: Scalar;
type V: Vector<T = Self::T>;
type M: Matrix<T = Self::T, V = Self::V>;
type Mass: LinearOp<M = Self::M, V = Self::V, T = Self::T>;
type Rhs: NonLinearOp<M = Self::M, V = Self::V, T = Self::T>;
type Root: NonLinearOp<M = Self::M, V = Self::V, T = Self::T>;
type Init: ConstantOp<M = Self::M, V = Self::V, T = Self::T>;
fn set_params(&mut self, p: Self::V);
fn rhs(&self) -> &Rc<Self::Rhs>;
fn mass(&self) -> Option<&Rc<Self::Mass>>;
fn root(&self) -> Option<&Rc<Self::Root>> {
None
}
fn init(&self) -> &Rc<Self::Init>;
}
pub struct OdeSolverEquations<M, Rhs, Init, Mass = UnitCallable<M>, Root = UnitCallable<M>>
where
M: Matrix,
Rhs: NonLinearOp<M = M, V = M::V, T = M::T>,
Mass: LinearOp<M = M, V = M::V, T = M::T>,
Root: NonLinearOp<M = M, V = M::V, T = M::T>,
Init: ConstantOp<M = M, V = M::V, T = M::T>,
{
rhs: Rc<Rhs>,
mass: Option<Rc<Mass>>,
root: Option<Rc<Root>>,
init: Rc<Init>,
p: Rc<M::V>,
}
impl<M, Rhs, Init, Mass, Root> OdeSolverEquations<M, Rhs, Init, Mass, Root>
where
M: Matrix,
Rhs: NonLinearOp<M = M, V = M::V, T = M::T>,
Mass: LinearOp<M = M, V = M::V, T = M::T>,
Root: NonLinearOp<M = M, V = M::V, T = M::T>,
Init: ConstantOp<M = M, V = M::V, T = M::T>,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
rhs: Rc<Rhs>,
mass: Option<Rc<Mass>>,
root: Option<Rc<Root>>,
init: Rc<Init>,
p: Rc<M::V>,
) -> Self {
Self {
rhs,
mass,
root,
init,
p,
}
}
}
impl<M, Rhs, Init, Mass, Root> OdeEquations for OdeSolverEquations<M, Rhs, Init, Mass, Root>
where
M: Matrix,
Rhs: NonLinearOp<M = M, V = M::V, T = M::T>,
Mass: LinearOp<M = M, V = M::V, T = M::T>,
Root: NonLinearOp<M = M, V = M::V, T = M::T>,
Init: ConstantOp<M = M, V = M::V, T = M::T>,
{
type T = M::T;
type V = M::V;
type M = M;
type Rhs = Rhs;
type Mass = Mass;
type Root = Root;
type Init = Init;
fn rhs(&self) -> &Rc<Self::Rhs> {
&self.rhs
}
fn mass(&self) -> Option<&Rc<Self::Mass>> {
self.mass.as_ref()
}
fn root(&self) -> Option<&Rc<Self::Root>> {
self.root.as_ref()
}
fn init(&self) -> &Rc<Self::Init> {
&self.init
}
fn set_params(&mut self, p: Self::V) {
self.p = Rc::new(p);
Rc::<Rhs>::get_mut(&mut self.rhs)
.unwrap()
.set_params(self.p.clone());
if let Some(m) = self.mass.as_mut() {
Rc::<Mass>::get_mut(m).unwrap().set_params(self.p.clone());
}
if let Some(r) = self.root.as_mut() {
Rc::<Root>::get_mut(r).unwrap().set_params(self.p.clone())
}
}
}
#[cfg(test)]
mod tests {
use nalgebra::DVector;
use crate::ode_solver::equations::OdeEquations;
use crate::ode_solver::test_models::exponential_decay::exponential_decay_problem;
use crate::ode_solver::test_models::exponential_decay_with_algebraic::exponential_decay_with_algebraic_problem;
use crate::vector::Vector;
use crate::LinearOp;
use crate::NonLinearOp;
type Mcpu = nalgebra::DMatrix<f64>;
type Vcpu = nalgebra::DVector<f64>;
#[test]
fn ode_equation_test() {
let (problem, _soln) = exponential_decay_problem::<Mcpu>(false);
let y = DVector::from_vec(vec![1.0, 1.0]);
let rhs_y = problem.eqn.rhs().call(&y, 0.0);
let expect_rhs_y = DVector::from_vec(vec![-0.1, -0.1]);
rhs_y.assert_eq_st(&expect_rhs_y, 1e-10);
let jac_rhs_y = problem.eqn.rhs().jac_mul(&y, 0.0, &y);
let expect_jac_rhs_y = Vcpu::from_vec(vec![-0.1, -0.1]);
jac_rhs_y.assert_eq_st(&expect_jac_rhs_y, 1e-10);
assert!(problem.eqn.mass().is_none());
let jac = problem.eqn.rhs().jacobian(&y, 0.0);
assert_eq!(jac[(0, 0)], -0.1);
assert_eq!(jac[(1, 1)], -0.1);
assert_eq!(jac[(0, 1)], 0.0);
assert_eq!(jac[(1, 0)], 0.0);
}
#[test]
fn ode_with_mass_test() {
let (problem, _soln) = exponential_decay_with_algebraic_problem::<Mcpu>(false);
let y = DVector::from_vec(vec![1.0, 1.0, 1.0]);
let rhs_y = problem.eqn.rhs().call(&y, 0.0);
let expect_rhs_y = DVector::from_vec(vec![-0.1, -0.1, 0.0]);
rhs_y.assert_eq_st(&expect_rhs_y, 1e-10);
let jac_rhs_y = problem.eqn.rhs().jac_mul(&y, 0.0, &y);
let expect_jac_rhs_y = Vcpu::from_vec(vec![-0.1, -0.1, 0.0]);
jac_rhs_y.assert_eq_st(&expect_jac_rhs_y, 1e-10);
let mass = problem.eqn.mass().unwrap().matrix(0.0);
assert_eq!(mass[(0, 0)], 1.);
assert_eq!(mass[(1, 1)], 1.);
assert_eq!(mass[(2, 2)], 0.);
assert_eq!(mass[(0, 1)], 0.);
assert_eq!(mass[(1, 0)], 0.);
assert_eq!(mass[(0, 2)], 0.);
assert_eq!(mass[(2, 0)], 0.);
assert_eq!(mass[(1, 2)], 0.);
assert_eq!(mass[(2, 1)], 0.);
let jac = problem.eqn.rhs().jacobian(&y, 0.0);
assert_eq!(jac[(0, 0)], -0.1);
assert_eq!(jac[(1, 1)], -0.1);
assert_eq!(jac[(2, 2)], 1.0);
assert_eq!(jac[(0, 1)], 0.0);
assert_eq!(jac[(1, 0)], 0.0);
assert_eq!(jac[(0, 2)], 0.0);
assert_eq!(jac[(2, 0)], 0.0);
assert_eq!(jac[(1, 2)], 0.0);
assert_eq!(jac[(2, 1)], -1.0);
}
}