use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
use crate::{
enums::Continuous,
linear_system::{
solver::{Order, Radau, RadauConst, Rk, RkConst, Rkf45, Rkf45Const},
Equilibrium, SsGen,
},
matrix::{lup, MatrixMul},
units::Seconds,
Abs, Max, NumCast, One, Pow, RelativeEq, Zero,
};
pub type Ss<T> = SsGen<T, Continuous>;
impl<T> Ss<T>
where
T: Abs
+ Add<Output = T>
+ Clone
+ Div<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ PartialOrd
+ Sub<Output = T>
+ Zero,
{
#[must_use]
pub fn equilibrium(&self, u: &[T]) -> Option<Equilibrium<T>> {
if u.len() != self.dim.inputs() {
return None;
}
let bu = -self.b.mmul(u);
let lu = lup(self.a.clone());
let x = lu?.solve(&bu);
let y = self.c.mmul(&x) + self.d.mmul(u);
Some(Equilibrium::new(
x.iter().cloned().collect(),
y.iter().cloned().collect(),
))
}
}
impl Ss<f64> {
#[must_use]
pub fn is_stable(&self) -> bool {
self.poles().iter().all(|p| p.re.is_sign_negative())
}
}
impl<T> Ss<T>
where
T: Abs
+ Add<Output = T>
+ AddAssign
+ Clone
+ Div<Output = T>
+ Max
+ Mul<Output = T>
+ NumCast
+ PartialOrd
+ Pow<T>
+ RkConst
+ Rkf45Const
+ Sub<Output = T>
+ Zero,
{
pub fn rk2<F>(&self, u: F, x0: &[T], h: Seconds<T>, n: usize) -> Rk<T, F>
where
F: Fn(Seconds<T>) -> Vec<T>,
{
Rk::new(self, u, x0, h, n, Order::Rk2)
}
pub fn rk4<F>(&self, u: F, x0: &[T], h: Seconds<T>, n: usize) -> Rk<T, F>
where
F: Fn(Seconds<T>) -> Vec<T>,
{
Rk::new(self, u, x0, h, n, Order::Rk4)
}
pub fn rkf45<F>(&self, u: F, x0: &[T], h: Seconds<T>, limit: Seconds<T>, tol: T) -> Rkf45<T, F>
where
F: Fn(Seconds<T>) -> Vec<T>,
{
Rkf45::new(self, u, x0, h, limit, tol)
}
}
impl<T> Ss<T>
where
T: Abs
+ Add<Output = T>
+ AddAssign
+ Clone
+ Div<Output = T>
+ Mul<Output = T>
+ NumCast
+ One
+ PartialOrd
+ RadauConst
+ RelativeEq
+ Sub<Output = T>
+ Zero,
{
pub fn radau<F>(&self, u: F, x0: &[T], h: Seconds<T>, n: usize, tol: T) -> Option<Radau<T, F>>
where
F: Fn(Seconds<T>) -> Vec<T>,
{
Radau::new(self, u, x0, h, n, tol)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::many_single_char_names)]
#[test]
fn equilibrium() {
let a = [-1.0_f64, 1., -1., 0.25];
let b = [1., 0.25];
let c = [0., 1., -1., 1.];
let d = [0., 1.];
let sys = Ss::new_from_slice(2, 1, 2, &a, &b, &c, &d);
let u = 0.0;
let eq = sys.equilibrium(&[u]).unwrap();
assert_eq!((0., 0.), (eq.x()[0], eq.y()[0]));
assert!(!format!("{}", eq).is_empty());
assert!(sys.equilibrium(&[0., 0., 0.]).is_none());
}
#[test]
fn stability() {
let eig1 = -2.;
let eig2 = -7.;
let sys = Ss::new_from_slice(
2,
1,
1,
&[eig1, 0., 3., eig2],
&[1., 3.],
&[-1., 0.5],
&[0.1],
);
assert!(sys.is_stable());
}
#[test]
fn new_rk2() {
let a = [-1., 1., -1., 0.25];
let b = [1., 0.25];
let c = [0., 1.];
let d = [0.];
let sys = Ss::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let iter = sys.rk2(|_| vec![1.], &[0., 0.], Seconds(0.1), 30);
assert_eq!(31, iter.count());
}
#[test]
fn new_rk4() {
let a = [-1., 1., -1., 0.25];
let b = [1., 0.25];
let c = [0., 1.];
let d = [0.];
let sys = Ss::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let iter = sys.rk4(|_| vec![1.], &[0., 0.], Seconds(0.1), 30);
assert_eq!(31, iter.count());
}
#[test]
fn new_rkf45() {
let a = [-1., 1., -1., 0.25];
let b = [1., 0.25];
let c = [0., 1.];
let d = [0.];
let sys = Ss::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let iter = sys.rkf45(|_| vec![1.], &[0., 0.], Seconds(0.1), Seconds(2.), 1e-5);
assert_relative_eq!(2., iter.last().unwrap().time().0, max_relative = 0.01);
}
#[test]
fn new_radau() {
let a = [-1., 1., -1., 0.25];
let b = [1., 0.25];
let c = [0., 1.];
let d = [0.];
let sys = Ss::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let iter = sys
.radau(|_| vec![1.], &[0., 0.], Seconds(0.1), 30, 1e-5)
.unwrap();
assert_eq!(31, iter.count());
}
}