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