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)]
40#[cfg_attr(feature = "python", pyclass(from_py_object))]
41#[builder(doc)]
42pub struct IntegratorOptions {
43 #[builder(default_code = "60.0 * Unit::Second")]
44 pub init_step: Duration,
45 #[builder(default_code = "0.001 * Unit::Second")]
46 pub min_step: Duration,
47 #[builder(default_code = "2700.0 * Unit::Second")]
48 pub max_step: Duration,
49 #[builder(default = 1e-12)]
50 pub tolerance: f64,
51 #[builder(default = 50)]
52 pub attempts: u8,
53 #[builder(default = false)]
54 pub fixed_step: bool,
55 #[builder(default)]
56 pub error_ctrl: ErrorControl,
57 #[builder(default, setter(strip_option))]
60 pub integration_frame: Option<Frame>,
61}
62
63impl IntegratorOptions {
64 pub fn with_adaptive_step(
67 min_step: Duration,
68 max_step: Duration,
69 tolerance: f64,
70 error_ctrl: ErrorControl,
71 ) -> Self {
72 IntegratorOptions {
73 init_step: max_step,
74 min_step,
75 max_step,
76 tolerance,
77 attempts: 50,
78 fixed_step: false,
79 error_ctrl,
80 integration_frame: None,
81 }
82 }
83
84 pub fn with_adaptive_step_s(
85 min_step: f64,
86 max_step: f64,
87 tolerance: f64,
88 error_ctrl: ErrorControl,
89 ) -> Self {
90 Self::with_adaptive_step(
91 min_step * Unit::Second,
92 max_step * Unit::Second,
93 tolerance,
94 error_ctrl,
95 )
96 }
97
98 pub fn with_fixed_step(step: Duration) -> Self {
101 IntegratorOptions {
102 init_step: step,
103 min_step: step,
104 max_step: step,
105 tolerance: 0.0,
106 fixed_step: true,
107 attempts: 0,
108 error_ctrl: ErrorControl::RSSCartesianStep,
109 integration_frame: None,
110 }
111 }
112
113 pub fn with_fixed_step_s(step: f64) -> Self {
114 Self::with_fixed_step(step * Unit::Second)
115 }
116
117 #[allow(clippy::field_reassign_with_default)]
119 pub fn with_tolerance(tolerance: f64) -> Self {
120 let mut opts = Self::default();
121 opts.tolerance = tolerance;
122 opts
123 }
124
125 #[allow(clippy::field_reassign_with_default)]
127 pub fn with_max_step(max_step: Duration) -> Self {
128 let mut opts = Self::default();
129 opts.set_max_step(max_step);
130 opts
131 }
132}
133
134#[cfg_attr(feature = "python", pymethods)]
135impl IntegratorOptions {
136 pub fn info(&self) -> String {
138 format!("{self}")
139 }
140
141 pub fn set_max_step(&mut self, max_step: Duration) {
143 if self.init_step > max_step {
144 self.init_step = max_step;
145 }
146 self.max_step = max_step;
147 }
148
149 pub fn set_min_step(&mut self, min_step: Duration) {
151 if self.init_step < min_step {
152 self.init_step = min_step;
153 }
154 self.min_step = min_step;
155 }
156}
157
158impl fmt::Display for IntegratorOptions {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 if self.fixed_step {
161 write!(f, "fixed step: {:e}", self.min_step,)
162 } else {
163 write!(
164 f,
165 "min_step: {:e}, max_step: {:e}, tol: {:e}, attempts: {}",
166 self.min_step, self.max_step, self.tolerance, self.attempts,
167 )
168 }
169 }
170}
171
172impl Default for IntegratorOptions {
173 fn default() -> IntegratorOptions {
175 IntegratorOptions {
176 init_step: 60.0 * Unit::Second,
177 min_step: 0.001 * Unit::Second,
178 max_step: 2700.0 * Unit::Second,
179 tolerance: 1e-12,
180 attempts: 50,
181 fixed_step: false,
182 error_ctrl: ErrorControl::RSSCartesianStep,
183 integration_frame: None,
184 }
185 }
186}
187
188impl StaticType for IntegratorOptions {
189 fn static_type() -> SimpleType {
190 let mut fields = HashMap::new();
191
192 fields.insert("init_step".to_string(), SimpleType::Text);
194 fields.insert("min_step".to_string(), SimpleType::Text);
195 fields.insert("max_step".to_string(), SimpleType::Text);
196
197 fields.insert("tolerance".to_string(), SimpleType::Double);
199 fields.insert("attempts".to_string(), SimpleType::Natural);
200 fields.insert("fixed_step".to_string(), SimpleType::Bool);
201
202 fields.insert("error_ctrl".to_string(), ErrorControl::static_type());
205
206 fields.insert(
208 "integration_frame".to_string(),
209 SimpleType::Optional(Box::new(Frame::static_type())),
210 );
211
212 SimpleType::Record(fields)
213 }
214}
215#[cfg(test)]
216mod ut_integr_opts {
217 use hifitime::Unit;
218
219 use crate::propagators::{ErrorControl, IntegratorOptions};
220
221 #[test]
222 fn test_options() {
223 let opts = IntegratorOptions::with_fixed_step_s(1e-1);
224 assert_eq!(opts.min_step, 1e-1 * Unit::Second);
225 assert_eq!(opts.max_step, 1e-1 * Unit::Second);
226 assert!(opts.tolerance.abs() < f64::EPSILON);
227 assert!(opts.fixed_step);
228
229 let opts =
230 IntegratorOptions::with_adaptive_step_s(1e-2, 10.0, 1e-12, ErrorControl::RSSStep);
231 assert_eq!(opts.min_step, 1e-2 * Unit::Second);
232 assert_eq!(opts.max_step, 10.0 * Unit::Second);
233 assert!((opts.tolerance - 1e-12).abs() < f64::EPSILON);
234 assert!(!opts.fixed_step);
235
236 let opts: IntegratorOptions = Default::default();
237 assert_eq!(opts.init_step, 60.0 * Unit::Second);
238 assert_eq!(opts.min_step, 0.001 * Unit::Second);
239 assert_eq!(opts.max_step, 2700.0 * Unit::Second);
240 assert!((opts.tolerance - 1e-12).abs() < f64::EPSILON);
241 assert_eq!(opts.attempts, 50);
242 assert!(!opts.fixed_step);
243
244 let opts = IntegratorOptions::with_max_step(1.0 * Unit::Second);
245 assert_eq!(opts.init_step, 1.0 * Unit::Second);
246 assert_eq!(opts.min_step, 0.001 * Unit::Second);
247 assert_eq!(opts.max_step, 1.0 * Unit::Second);
248 assert!((opts.tolerance - 1e-12).abs() < f64::EPSILON);
249 assert_eq!(opts.attempts, 50);
250 assert!(!opts.fixed_step);
251 }
252
253 #[test]
254 fn test_serde() {
255 let opts = IntegratorOptions::default();
256 let serialized = toml::to_string(&opts).unwrap();
257 println!("{serialized}");
258 let deserd: IntegratorOptions = toml::from_str(&serialized).unwrap();
259 assert_eq!(deserd, opts);
260 }
261}