eom/
traits.rs

1use ndarray::*;
2use ndarray_linalg::*;
3use num_traits::Float;
4
5#[cfg(doc)]
6use crate::{explicit::*, ode::*, semi_implicit::*};
7
8#[cfg_attr(doc, katexit::katexit)]
9/// Model space, the linear space where the system state is represented.
10///
11/// For an ODE in a form $dx/dt = f(x)$, the linear space where $x$ belongs to
12/// is the model space.
13/// It is usually $\mathbb{R}^n$ or $\mathbb{C}^n$,
14/// but this crate allows it in multi-dimensional e.g. $\mathbb{C}^{N_x \times N_y}$
15/// to support spectral methods for PDE whose state space is Fourier coefficients.
16///
17pub trait ModelSpec: Clone {
18    type Scalar: Scalar;
19    type Dim: Dimension;
20    /// Number of scalars to describe the system state.
21    fn model_size(&self) -> <Self::Dim as Dimension>::Pattern;
22}
23
24/// Interface for set/get time step for integration
25pub trait TimeStep {
26    type Time: Scalar + Float;
27    fn get_dt(&self) -> Self::Time;
28    fn set_dt(&mut self, dt: Self::Time);
29}
30
31#[cfg_attr(doc, katexit::katexit)]
32/// Abstraction for implementing explicit schemes
33///
34/// Consider equation of motion of an autonomous system
35/// described as an initial value problem of ODE:
36/// $$
37/// \frac{dx}{dt} = f(x),\space x(0) = x_0
38/// $$
39/// where $x = x(t)$ describes the system state specified by [ModelSpec] trait at a time $t$.
40/// This trait specifies $f$ itself, i.e. this trait will be implemented for structs corresponds to
41/// equations like [Lorenz63],
42/// and used while implementing integrate algorithms like [Euler],
43/// [Heun], and [RK4] algorithms.
44/// Since these algorithms are independent from the detail of $f$,
45/// this trait abstracts this part.
46///
47pub trait Explicit: ModelSpec {
48    /// Evaluate $f(x)$ for a given state $x$
49    ///
50    /// This requires `&mut self` because evaluating $f$ may require
51    /// additional internal memory allocated in `Self`.
52    ///
53    fn rhs<'a, S>(&mut self, x: &'a mut ArrayBase<S, Self::Dim>) -> &'a mut ArrayBase<S, Self::Dim>
54    where
55        S: DataMut<Elem = Self::Scalar>;
56}
57
58#[cfg_attr(doc, katexit::katexit)]
59/// Abstraction for implementing semi-implicit schemes for stiff equations
60///
61/// Consider equation of motion of a stiff autonomous system
62/// described as an initial value problem of ODE:
63/// $$
64/// \frac{dx}{dt} = Ax + f(x),\space x(0) = x_0
65/// $$
66/// where $x = x(t)$ describes the system state specified by [ModelSpec] trait.
67/// We split the right hand side of the equation
68/// as the linear part $Ax$ to be stiff and the nonlinear part $f(x)$ not to be stiff.
69/// In addition, we assume $A$ is diagonalizable,
70/// and $x$ is selected to make $A$ diagonal.
71/// Similar to [Explicit], this trait abstracts the pair $(A, f)$ to implement
72/// semi-implicit schemes like [DiagRK4].
73///
74/// Stiff equations and semi-implicit schemes
75/// -------------------------------------------
76/// The stiffness causes numerical instabilities.
77/// For example, consider solving one-dimensional ODE $dx/dt = -\lambda x$
78/// with large $\lambda$ using explicit Euler scheme.
79/// Apparently, the solution is $x(t) = x(0)e^{-\lambda t}$,
80/// which converges to $0$ very quickly.
81/// However, to capture this process using explicit scheme like Euler scheme,
82/// we need as small $\Delta t$ as $\lambda^{-1}$.
83/// Such small $\Delta t$ is usually unacceptable,
84/// and implicit schemes are used for stiff equations,
85/// but full implicit schemes require solving fixed point problem like
86/// $1 + \lambda f(x) = 0$, which makes another instabilities.
87/// Semi-implicit schemes has been introduced to resolve this situation,
88/// i.e. use implicit scheme only for stiff linear part $Ax$
89/// and use explicit schemes on non-stiff part $f(x)$.
90///
91pub trait SemiImplicit: ModelSpec {
92    /// Non-stiff part $f(x)$
93    fn nlin<'a, S>(
94        &mut self,
95        x: &'a mut ArrayBase<S, Self::Dim>,
96    ) -> &'a mut ArrayBase<S, Self::Dim>
97    where
98        S: DataMut<Elem = Self::Scalar>;
99    /// Diagonal elements of stiff linear part of $A$
100    fn diag(&self) -> Array<Self::Scalar, Self::Dim>;
101}
102
103/// Time-evolution operator
104pub trait TimeEvolution: ModelSpec + TimeStep {
105    /// calculate next step
106    fn iterate<'a, S>(
107        &mut self,
108        x: &'a mut ArrayBase<S, Self::Dim>,
109    ) -> &'a mut ArrayBase<S, Self::Dim>
110    where
111        S: DataMut<Elem = Self::Scalar>;
112
113    /// calculate n-step
114    fn iterate_n<'a, S>(
115        &mut self,
116        a: &'a mut ArrayBase<S, Self::Dim>,
117        n: usize,
118    ) -> &'a mut ArrayBase<S, Self::Dim>
119    where
120        S: DataMut<Elem = Self::Scalar>,
121    {
122        for _ in 0..n {
123            self.iterate(a);
124        }
125        a
126    }
127}
128
129/// Time evolution schemes
130pub trait Scheme: TimeEvolution {
131    type Core: ModelSpec<Scalar = Self::Scalar, Dim = Self::Dim>;
132    /// Initialize with a core implementation
133    fn new(f: Self::Core, dt: Self::Time) -> Self;
134    /// Get immutable core
135    fn core(&self) -> &Self::Core;
136    /// Get mutable core
137    fn core_mut(&mut self) -> &mut Self::Core;
138}