1use serde_dhall::{SimpleType, StaticType};
20use std::collections::HashMap;
21use std::fmt;
22
23use crate::time::{Duration, Unit};
24
25use super::ErrorControl;
26use anise::frames::Frame;
27use serde::{Deserialize, Serialize};
28use typed_builder::TypedBuilder;
29
30#[cfg(feature = "python")]
31use pyo3::prelude::*;
32
33#[derive(Clone, Copy, Debug, TypedBuilder, Serialize, Deserialize, PartialEq)]
44#[cfg_attr(feature = "python", pyclass(from_py_object))]
45#[builder(doc)]
46pub struct IntegratorOptions {
47 #[builder(default_code = "60.0 * Unit::Second")]
48 pub init_step: Duration,
49 #[builder(default_code = "0.001 * Unit::Second")]
50 pub min_step: Duration,
51 #[builder(default_code = "2700.0 * Unit::Second")]
52 pub max_step: Duration,
53 #[builder(default = 1e-12)]
54 pub tolerance: f64,
55 #[builder(default = 50)]
56 pub attempts: u8,
57 #[builder(default = false)]
58 pub fixed_step: bool,
59 #[builder(default)]
60 pub error_ctrl: ErrorControl,
61 #[builder(default, setter(strip_option))]
64 pub integration_frame: Option<Frame>,
65}
66
67impl IntegratorOptions {
68 pub fn with_adaptive_step(
71 min_step: Duration,
72 max_step: Duration,
73 tolerance: f64,
74 error_ctrl: ErrorControl,
75 ) -> Self {
76 IntegratorOptions {
77 init_step: max_step,
78 min_step,
79 max_step,
80 tolerance,
81 attempts: 50,
82 fixed_step: false,
83 error_ctrl,
84 integration_frame: None,
85 }
86 }
87
88 pub fn with_adaptive_step_s(
89 min_step: f64,
90 max_step: f64,
91 tolerance: f64,
92 error_ctrl: ErrorControl,
93 ) -> Self {
94 Self::with_adaptive_step(
95 min_step * Unit::Second,
96 max_step * Unit::Second,
97 tolerance,
98 error_ctrl,
99 )
100 }
101
102 pub fn with_fixed_step(step: Duration) -> Self {
105 IntegratorOptions {
106 init_step: step,
107 min_step: step,
108 max_step: step,
109 tolerance: 0.0,
110 fixed_step: true,
111 attempts: 0,
112 error_ctrl: ErrorControl::RSSCartesianStep,
113 integration_frame: None,
114 }
115 }
116
117 pub fn with_fixed_step_s(step: f64) -> Self {
118 Self::with_fixed_step(step * Unit::Second)
119 }
120
121 #[allow(clippy::field_reassign_with_default)]
123 pub fn with_tolerance(tolerance: f64) -> Self {
124 let mut opts = Self::default();
125 opts.tolerance = tolerance;
126 opts
127 }
128
129 #[allow(clippy::field_reassign_with_default)]
131 pub fn with_max_step(max_step: Duration) -> Self {
132 let mut opts = Self::default();
133 opts.set_max_step(max_step);
134 opts
135 }
136}
137
138#[cfg_attr(feature = "python", pymethods)]
139impl IntegratorOptions {
140 pub fn info(&self) -> String {
143 format!("{self}")
144 }
145
146 pub fn set_max_step(&mut self, max_step: Duration) {
151 if self.init_step > max_step {
152 self.init_step = max_step;
153 }
154 self.max_step = max_step;
155 }
156
157 pub fn set_min_step(&mut self, min_step: Duration) {
162 if self.init_step < min_step {
163 self.init_step = min_step;
164 }
165 self.min_step = min_step;
166 }
167}
168
169impl fmt::Display for IntegratorOptions {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 if self.fixed_step {
172 write!(f, "fixed step: {:e}", self.min_step)
173 } else {
174 write!(
175 f,
176 "min_step: {:e}, max_step: {:e}, tol: {:e}, attempts: {}",
177 self.min_step, self.max_step, self.tolerance, self.attempts,
178 )
179 }
180 }
181}
182
183impl Default for IntegratorOptions {
184 fn default() -> IntegratorOptions {
186 IntegratorOptions {
187 init_step: 60.0 * Unit::Second,
188 min_step: 0.001 * Unit::Second,
189 max_step: 2700.0 * Unit::Second,
190 tolerance: 1e-12,
191 attempts: 50,
192 fixed_step: false,
193 error_ctrl: ErrorControl::RSSCartesianStep,
194 integration_frame: None,
195 }
196 }
197}
198
199impl StaticType for IntegratorOptions {
200 fn static_type() -> SimpleType {
201 let mut fields = HashMap::new();
202
203 fields.insert("init_step".to_string(), SimpleType::Text);
205 fields.insert("min_step".to_string(), SimpleType::Text);
206 fields.insert("max_step".to_string(), SimpleType::Text);
207
208 fields.insert("tolerance".to_string(), SimpleType::Double);
210 fields.insert("attempts".to_string(), SimpleType::Natural);
211 fields.insert("fixed_step".to_string(), SimpleType::Bool);
212
213 fields.insert("error_ctrl".to_string(), ErrorControl::static_type());
216
217 fields.insert(
219 "integration_frame".to_string(),
220 SimpleType::Optional(Box::new(Frame::static_type())),
221 );
222
223 SimpleType::Record(fields)
224 }
225}
226#[cfg(test)]
227mod ut_integr_opts {
228 use hifitime::Unit;
229
230 use crate::propagators::{ErrorControl, IntegratorOptions};
231
232 #[test]
233 fn test_options() {
234 let opts = IntegratorOptions::with_fixed_step_s(1e-1);
235 assert_eq!(opts.min_step, 1e-1 * Unit::Second);
236 assert_eq!(opts.max_step, 1e-1 * Unit::Second);
237 assert!(opts.tolerance.abs() < f64::EPSILON);
238 assert!(opts.fixed_step);
239
240 let opts =
241 IntegratorOptions::with_adaptive_step_s(1e-2, 10.0, 1e-12, ErrorControl::RSSStep);
242 assert_eq!(opts.min_step, 1e-2 * Unit::Second);
243 assert_eq!(opts.max_step, 10.0 * Unit::Second);
244 assert!((opts.tolerance - 1e-12).abs() < f64::EPSILON);
245 assert!(!opts.fixed_step);
246
247 let opts: IntegratorOptions = Default::default();
248 assert_eq!(opts.init_step, 60.0 * Unit::Second);
249 assert_eq!(opts.min_step, 0.001 * Unit::Second);
250 assert_eq!(opts.max_step, 2700.0 * Unit::Second);
251 assert!((opts.tolerance - 1e-12).abs() < f64::EPSILON);
252 assert_eq!(opts.attempts, 50);
253 assert!(!opts.fixed_step);
254
255 let opts = IntegratorOptions::with_max_step(1.0 * Unit::Second);
256 assert_eq!(opts.init_step, 1.0 * Unit::Second);
257 assert_eq!(opts.min_step, 0.001 * Unit::Second);
258 assert_eq!(opts.max_step, 1.0 * Unit::Second);
259 assert!((opts.tolerance - 1e-12).abs() < f64::EPSILON);
260 assert_eq!(opts.attempts, 50);
261 assert!(!opts.fixed_step);
262 }
263
264 #[test]
265 fn test_serde() {
266 let opts = IntegratorOptions::default();
267 let serialized = toml::to_string(&opts).unwrap();
268 println!("{serialized}");
269 let deserd: IntegratorOptions = toml::from_str(&serialized).unwrap();
270 assert_eq!(deserd, opts);
271 }
272}