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, Serialize};
23use serde_dhall::StaticType;
24
25use crate::io::ConfigError;
26
27use self::rk::*;
28mod dormand;
29use self::dormand::*;
30mod verner;
31use self::verner::*;
32
33#[cfg(feature = "python")]
34use pyo3::prelude::*;
35
36use super::PropagationError;
37
38/// The `RK` trait defines a Runge Kutta integrator.
39#[allow(clippy::upper_case_acronyms)]
40trait RK
41where
42 Self: Sized,
43{
44 /// Returns the order of this integrator (as u8 because there probably isn't an order greater than 255).
45 /// The order is used for the adaptive step size only to compute the error between estimates.
46 const ORDER: u8;
47
48 /// Returns the stages of this integrator (as usize because it's used as indexing)
49 const STAGES: usize;
50
51 /// Returns a pointer to a list of f64 corresponding to the A coefficients of the Butcher table for that RK.
52 /// This module only supports *implicit* integrators, and as such, `Self.a_coeffs().len()` must be of
53 /// size (order+1)*(order)/2.
54 /// *Warning:* this RK trait supposes that the implementation is consistent, i.e. c_i = \sum_j a_{ij}.
55 const A_COEFFS: &'static [f64];
56 /// Returns a pointer to a list of f64 corresponding to the b_i and b^*_i coefficients of the
57 /// Butcher table for that RK. `Self.a_coeffs().len()` must be of size (order+1)*2.
58 const B_COEFFS: &'static [f64];
59}
60
61/// Enum of supported integration methods, all of which are part of the Runge Kutta family of ordinary differential equation (ODE) solvers.
62/// Nomenclature: X-Y means that this is an X order solver with a Y order error correction step.
63#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, StaticType)]
64#[cfg_attr(feature = "python", pyclass(from_py_object))]
65pub enum IntegratorMethod {
66 /// Runge Kutta 8-9 is the recommended integrator for most application.
67 #[default]
68 RungeKutta89,
69 /// `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`.
70 DormandPrince78,
71 /// `Dormand45` is a [Dormand-Prince integrator](https://en.wikipedia.org/wiki/Dormand%E2%80%93Prince_method).
72 DormandPrince45,
73 /// Runge Kutta 4 is a fixed step solver.
74 RungeKutta4,
75 /// Runge Kutta 4-5 [Cash Karp integrator](https://en.wikipedia.org/wiki/Cash%E2%80%93Karp_method).
76 CashKarp45,
77 /// Verner56 is an RK Verner integrator of order 5-6. Coefficients taken from [here (PDF)](http://people.math.sfu.ca/~jverner/classify.1992.ps).
78 Verner56,
79}
80
81impl IntegratorMethod {
82 /// Returns the order of this integrator (as u8 because there probably isn't an order greater than 255).
83 /// The order is used for the adaptive step size only to compute the error between estimates.
84 pub const fn order(self) -> u8 {
85 match self {
86 Self::RungeKutta89 => RK89::ORDER,
87 Self::DormandPrince78 => Dormand78::ORDER,
88 Self::DormandPrince45 => Dormand45::ORDER,
89 Self::RungeKutta4 => RK4Fixed::ORDER,
90 Self::CashKarp45 => CashKarp45::ORDER,
91 Self::Verner56 => Verner56::ORDER,
92 }
93 }
94
95 /// Returns the stages of this integrator, i.e. how many times the derivatives will be called
96 pub const fn stages(self) -> usize {
97 match self {
98 Self::RungeKutta89 => RK89::STAGES,
99 Self::DormandPrince78 => Dormand78::STAGES,
100 Self::DormandPrince45 => Dormand45::STAGES,
101 Self::RungeKutta4 => RK4Fixed::STAGES,
102 Self::CashKarp45 => CashKarp45::STAGES,
103 Self::Verner56 => Verner56::STAGES,
104 }
105 }
106
107 /// Returns a pointer to a list of f64 corresponding to the A coefficients of the Butcher table for that RK.
108 /// This module only supports *implicit* integrators, and as such, `Self.a_coeffs().len()` must be of
109 /// size (order+1)*(order)/2.
110 /// *Warning:* this RK trait supposes that the implementation is consistent, i.e. c_i = \sum_j a_{ij}.
111 pub const fn a_coeffs(self) -> &'static [f64] {
112 match self {
113 Self::RungeKutta89 => RK89::A_COEFFS,
114 Self::DormandPrince78 => Dormand78::A_COEFFS,
115 Self::DormandPrince45 => Dormand45::A_COEFFS,
116 Self::RungeKutta4 => RK4Fixed::A_COEFFS,
117 Self::CashKarp45 => CashKarp45::A_COEFFS,
118 Self::Verner56 => Verner56::A_COEFFS,
119 }
120 }
121 /// Returns a pointer to a list of f64 corresponding to the b_i and b^*_i coefficients of the
122 /// Butcher table for that RK. `Self.a_coeffs().len()` must be of size (order+1)*2.
123 pub const fn b_coeffs(self) -> &'static [f64] {
124 match self {
125 Self::RungeKutta89 => RK89::B_COEFFS,
126 Self::DormandPrince78 => Dormand78::B_COEFFS,
127 Self::DormandPrince45 => Dormand45::B_COEFFS,
128 Self::RungeKutta4 => RK4Fixed::B_COEFFS,
129 Self::CashKarp45 => CashKarp45::B_COEFFS,
130 Self::Verner56 => Verner56::B_COEFFS,
131 }
132 }
133}
134
135impl FromStr for IntegratorMethod {
136 type Err = PropagationError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 match s.to_lowercase().as_str() {
140 "rungekutta89" => Ok(Self::RungeKutta89),
141 "dormandprince78" => Ok(Self::DormandPrince78),
142 "dormandprince45" => Ok(Self::DormandPrince45),
143 "rungekutta4" => Ok(Self::RungeKutta4),
144 "cashkarp45" => Ok(Self::CashKarp45),
145 "verner56" => Ok(Self::Verner56),
146 _ => {
147 let valid = [
148 "RungeKutta89",
149 "DormandPrince78",
150 "DormandPrince45",
151 "RungeKutta4",
152 "CashKarp45",
153 "Verner56",
154 ];
155 let valid_msg = valid.join(",");
156 Err(PropagationError::PropConfigError {
157 source: ConfigError::InvalidConfig {
158 msg: format!("unknow integration method `{s}`, must be one of {valid_msg}"),
159 },
160 })
161 }
162 }
163 }
164}
165
166#[cfg(test)]
167mod ut_propagator {
168 use std::str::FromStr;
169
170 use super::IntegratorMethod;
171
172 #[test]
173 fn from_str_ok() {
174 let valid = [
175 "RungeKutta89",
176 "DormandPrince78",
177 "DormandPrince45",
178 "RungeKutta4",
179 "CashKarp45",
180 "Verner56",
181 ];
182 for method in valid {
183 assert!(IntegratorMethod::from_str(method.to_uppercase().as_str()).is_ok());
184 }
185 assert!(IntegratorMethod::from_str("blah").is_err());
186 }
187}