nyx_space/propagators/rk_methods/
mod.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19mod rk;
20use std::str::FromStr;
21
22use serde::Deserialize;
23use serde::Serialize;
24
25use crate::io::ConfigError;
26
27use self::rk::*;
28mod dormand;
29use self::dormand::*;
30mod verner;
31use self::verner::*;
32
33use super::PropagationError;
34
35/// The `RK` trait defines a Runge Kutta integrator.
36#[allow(clippy::upper_case_acronyms)]
37trait RK
38where
39    Self: Sized,
40{
41    /// Returns the order of this integrator (as u8 because there probably isn't an order greater than 255).
42    /// The order is used for the adaptive step size only to compute the error between estimates.
43    const ORDER: u8;
44
45    /// Returns the stages of this integrator (as usize because it's used as indexing)
46    const STAGES: usize;
47
48    /// Returns a pointer to a list of f64 corresponding to the A coefficients of the Butcher table for that RK.
49    /// This module only supports *implicit* integrators, and as such, `Self.a_coeffs().len()` must be of
50    /// size (order+1)*(order)/2.
51    /// *Warning:* this RK trait supposes that the implementation is consistent, i.e. c_i = \sum_j a_{ij}.
52    const A_COEFFS: &'static [f64];
53    /// Returns a pointer to a list of f64 corresponding to the b_i and b^*_i coefficients of the
54    /// Butcher table for that RK. `Self.a_coeffs().len()` must be of size (order+1)*2.
55    const B_COEFFS: &'static [f64];
56}
57
58/// Enum of supported integration methods, all of which are part of the Runge Kutta family of ordinary differential equation (ODE) solvers.
59/// Nomenclature: X-Y means that this is an X order solver with a Y order error correction step.
60#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
61pub enum IntegratorMethod {
62    /// Runge Kutta 8-9 is the recommended integrator for most application.
63    RungeKutta89,
64    /// `Dormand78` is a [Dormand-Prince integrator](https://en.wikipedia.org/wiki/Dormand%E2%80%93Prince_method). Coefficients taken from GMAT `src/base/propagator/PrinceDormand78.cpp`.
65    DormandPrince78,
66    /// `Dormand45` is a [Dormand-Prince integrator](https://en.wikipedia.org/wiki/Dormand%E2%80%93Prince_method).
67    DormandPrince45,
68    /// Runge Kutta 4 is a fixed step solver.
69    RungeKutta4,
70    /// Runge Kutta 4-5 [Cash Karp integrator](https://en.wikipedia.org/wiki/Cash%E2%80%93Karp_method).
71    CashKarp45,
72    /// Verner56 is an RK Verner integrator of order 5-6. Coefficients taken from [here (PDF)](http://people.math.sfu.ca/~jverner/classify.1992.ps).
73    Verner56,
74}
75
76impl IntegratorMethod {
77    /// Returns the order of this integrator (as u8 because there probably isn't an order greater than 255).
78    /// The order is used for the adaptive step size only to compute the error between estimates.
79    pub const fn order(self) -> u8 {
80        match self {
81            Self::RungeKutta89 => RK89::ORDER,
82            Self::DormandPrince78 => Dormand78::ORDER,
83            Self::DormandPrince45 => Dormand45::ORDER,
84            Self::RungeKutta4 => RK4Fixed::ORDER,
85            Self::CashKarp45 => CashKarp45::ORDER,
86            Self::Verner56 => Verner56::ORDER,
87        }
88    }
89
90    /// Returns the stages of this integrator, i.e. how many times the derivatives will be called
91    pub const fn stages(self) -> usize {
92        match self {
93            Self::RungeKutta89 => RK89::STAGES,
94            Self::DormandPrince78 => Dormand78::STAGES,
95            Self::DormandPrince45 => Dormand45::STAGES,
96            Self::RungeKutta4 => RK4Fixed::STAGES,
97            Self::CashKarp45 => CashKarp45::STAGES,
98            Self::Verner56 => Verner56::STAGES,
99        }
100    }
101
102    /// Returns a pointer to a list of f64 corresponding to the A coefficients of the Butcher table for that RK.
103    /// This module only supports *implicit* integrators, and as such, `Self.a_coeffs().len()` must be of
104    /// size (order+1)*(order)/2.
105    /// *Warning:* this RK trait supposes that the implementation is consistent, i.e. c_i = \sum_j a_{ij}.
106    pub const fn a_coeffs(self) -> &'static [f64] {
107        match self {
108            Self::RungeKutta89 => RK89::A_COEFFS,
109            Self::DormandPrince78 => Dormand78::A_COEFFS,
110            Self::DormandPrince45 => Dormand45::A_COEFFS,
111            Self::RungeKutta4 => RK4Fixed::A_COEFFS,
112            Self::CashKarp45 => CashKarp45::A_COEFFS,
113            Self::Verner56 => Verner56::A_COEFFS,
114        }
115    }
116    /// Returns a pointer to a list of f64 corresponding to the b_i and b^*_i coefficients of the
117    /// Butcher table for that RK. `Self.a_coeffs().len()` must be of size (order+1)*2.
118    pub const fn b_coeffs(self) -> &'static [f64] {
119        match self {
120            Self::RungeKutta89 => RK89::B_COEFFS,
121            Self::DormandPrince78 => Dormand78::B_COEFFS,
122            Self::DormandPrince45 => Dormand45::B_COEFFS,
123            Self::RungeKutta4 => RK4Fixed::B_COEFFS,
124            Self::CashKarp45 => CashKarp45::B_COEFFS,
125            Self::Verner56 => Verner56::B_COEFFS,
126        }
127    }
128}
129
130impl Default for IntegratorMethod {
131    fn default() -> Self {
132        Self::RungeKutta89
133    }
134}
135
136impl FromStr for IntegratorMethod {
137    type Err = PropagationError;
138
139    fn from_str(s: &str) -> Result<Self, Self::Err> {
140        match s.to_lowercase().as_str() {
141            "rungekutta89" => Ok(Self::RungeKutta89),
142            "dormandprince78" => Ok(Self::DormandPrince78),
143            "dormandprince45" => Ok(Self::DormandPrince45),
144            "rungekutta4" => Ok(Self::RungeKutta4),
145            "cashkarp45" => Ok(Self::CashKarp45),
146            "verner56" => Ok(Self::Verner56),
147            _ => {
148                let valid = [
149                    "RungeKutta89",
150                    "DormandPrince78",
151                    "DormandPrince45",
152                    "RungeKutta4",
153                    "CashKarp45",
154                    "Verner56",
155                ];
156                let valid_msg = valid.join(",");
157                Err(PropagationError::PropConfigError {
158                    source: ConfigError::InvalidConfig {
159                        msg: format!("unknow integration method `{s}`, must be one of {valid_msg}"),
160                    },
161                })
162            }
163        }
164    }
165}
166
167#[cfg(test)]
168mod ut_propagator {
169    use std::str::FromStr;
170
171    use super::IntegratorMethod;
172
173    #[test]
174    fn from_str_ok() {
175        let valid = [
176            "RungeKutta89",
177            "DormandPrince78",
178            "DormandPrince45",
179            "RungeKutta4",
180            "CashKarp45",
181            "Verner56",
182        ];
183        for method in valid {
184            assert!(IntegratorMethod::from_str(method.to_uppercase().as_str()).is_ok());
185        }
186        assert!(IntegratorMethod::from_str("blah").is_err());
187    }
188}