1use crate::{
2 finite_vec, nonnegative, positive, ControlInput, Error, Guidance, GuidanceContext, Result,
3 Rocket, RocketSample, Vec3,
4};
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct FlightState {
10 pub time_s: f64,
12 pub position_m: Vec3,
14 pub velocity_mps: Vec3,
16}
17
18impl FlightState {
19 pub fn validate(&self) -> Result<()> {
21 nonnegative(self.time_s, "state.time_s")?;
22 if !finite_vec(&self.position_m) || !finite_vec(&self.velocity_mps) {
23 return Err(Error::invalid("state vectors", "must be finite"));
24 }
25 Ok(())
26 }
27}
28
29pub trait ForceModel {
34 fn acceleration(&self, state: &FlightState, mass_kg: f64) -> Result<Vec3>;
36
37 fn acceleration_with_axis(
42 &self,
43 state: &FlightState,
44 mass_kg: f64,
45 _body_axis: Vec3,
46 ) -> Result<Vec3> {
47 self.acceleration(state, mass_kg)
48 }
49}
50
51impl<F> ForceModel for F
52where
53 F: Fn(&FlightState, f64) -> Result<Vec3>,
54{
55 fn acceleration(&self, state: &FlightState, mass_kg: f64) -> Result<Vec3> {
56 self(state, mass_kg)
57 }
58}
59
60#[derive(Debug, Clone, Copy)]
62pub struct UniformGravity(pub Vec3);
63
64impl Default for UniformGravity {
65 fn default() -> Self {
66 Self(Vec3::new(0.0, -9.80665, 0.0))
67 }
68}
69
70impl ForceModel for UniformGravity {
71 fn acceleration(&self, _state: &FlightState, _mass_kg: f64) -> Result<Vec3> {
72 Ok(self.0)
73 }
74}
75
76pub const EARTH_MU_M3_S2: f64 = 3.986_004_418e14;
78
79#[derive(Debug, Clone, Copy)]
88pub struct CentralGravity {
89 mu_m3_s2: f64,
90 center_m: Vec3,
91}
92
93impl CentralGravity {
94 pub fn new(mu_m3_s2: f64, center_m: Vec3) -> Result<Self> {
96 positive(mu_m3_s2, "mu_m3_s2")?;
97 if !finite_vec(¢er_m) {
98 return Err(Error::invalid("center_m", "must be finite"));
99 }
100 Ok(Self { mu_m3_s2, center_m })
101 }
102
103 pub fn earth(center_m: Vec3) -> Result<Self> {
105 Self::new(EARTH_MU_M3_S2, center_m)
106 }
107
108 pub fn mu_m3_s2(&self) -> f64 {
110 self.mu_m3_s2
111 }
112 pub fn center_m(&self) -> Vec3 {
114 self.center_m
115 }
116}
117
118impl ForceModel for CentralGravity {
119 fn acceleration(&self, state: &FlightState, _mass_kg: f64) -> Result<Vec3> {
120 let offset = state.position_m - self.center_m;
121 let distance_squared = offset.norm_squared();
122 if !distance_squared.is_finite() || distance_squared == 0.0 {
123 return Err(Error::NumericalFailure(
124 "central gravity is singular at its center",
125 ));
126 }
127 let acceleration = offset * (-self.mu_m3_s2 / (distance_squared * distance_squared.sqrt()));
128 if !finite_vec(&acceleration) {
129 return Err(Error::NumericalFailure("non-finite central gravity"));
130 }
131 Ok(acceleration)
132 }
133}
134
135#[derive(Debug, Clone)]
143pub struct AltitudeSwitch<Below, Above> {
144 below: Below,
145 above: Above,
146 cutoff_altitude_m: f64,
147}
148
149impl<Below, Above> AltitudeSwitch<Below, Above> {
150 pub fn new(below: Below, above: Above, cutoff_altitude_m: f64) -> Result<Self> {
152 if !cutoff_altitude_m.is_finite() {
153 return Err(Error::invalid("cutoff_altitude_m", "must be finite"));
154 }
155 Ok(Self {
156 below,
157 above,
158 cutoff_altitude_m,
159 })
160 }
161 pub fn below(&self) -> &Below {
163 &self.below
164 }
165 pub fn above(&self) -> &Above {
167 &self.above
168 }
169 pub fn cutoff_altitude_m(&self) -> f64 {
171 self.cutoff_altitude_m
172 }
173}
174
175impl<Below: ForceModel, Above: ForceModel> ForceModel for AltitudeSwitch<Below, Above> {
176 fn acceleration(&self, state: &FlightState, mass_kg: f64) -> Result<Vec3> {
177 if state.position_m.y <= self.cutoff_altitude_m {
178 self.below.acceleration(state, mass_kg)
179 } else {
180 self.above.acceleration(state, mass_kg)
181 }
182 }
183}
184
185#[derive(Debug, Clone, Copy)]
187pub struct IntegratorSettings {
188 pub max_step_s: f64,
190 pub max_substeps: usize,
192 pub max_steering_accel_mps2: f64,
195}
196
197impl Default for IntegratorSettings {
198 fn default() -> Self {
199 Self {
200 max_step_s: 0.01,
201 max_substeps: 100_000,
202 max_steering_accel_mps2: 0.0,
203 }
204 }
205}
206
207impl IntegratorSettings {
208 fn validate(&self) -> Result<()> {
209 positive(self.max_step_s, "max_step_s")?;
210 nonnegative(self.max_steering_accel_mps2, "max_steering_accel_mps2")?;
211 if self.max_substeps == 0 {
212 return Err(Error::invalid("max_substeps", "must be nonzero"));
213 }
214 Ok(())
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq)]
220pub struct StepResult {
221 pub state: FlightState,
223 pub rocket: RocketSample,
225 pub substeps: usize,
227}
228
229#[derive(Debug, Clone)]
235pub struct Simulator<E> {
236 rocket: Rocket,
237 forces: E,
238 settings: IntegratorSettings,
239}
240
241impl<E: ForceModel> Simulator<E> {
242 pub fn new(rocket: Rocket, forces: E, settings: IntegratorSettings) -> Result<Self> {
244 settings.validate()?;
245 Ok(Self {
246 rocket,
247 forces,
248 settings,
249 })
250 }
251
252 pub fn rocket(&self) -> &Rocket {
254 &self.rocket
255 }
256 pub fn forces(&self) -> &E {
258 &self.forces
259 }
260 pub fn forces_mut(&mut self) -> &mut E {
262 &mut self.forces
263 }
264 pub fn settings(&self) -> IntegratorSettings {
266 self.settings
267 }
268
269 fn end_time(&self, state: &FlightState, duration_s: f64) -> Result<f64> {
270 state.validate()?;
271 positive(duration_s, "duration_s")?;
272 let end = state.time_s + duration_s;
273 if !end.is_finite() || end <= state.time_s {
274 return Err(Error::invalid(
275 "duration_s",
276 "must advance time to a finite, distinguishable value",
277 ));
278 }
279 if duration_s > self.settings.max_step_s * self.settings.max_substeps as f64 {
280 return Err(Error::StepLimitExceeded {
281 limit: self.settings.max_substeps,
282 });
283 }
284 Ok(end)
285 }
286
287 pub fn step(
290 &self,
291 state: &FlightState,
292 duration_s: f64,
293 control: ControlInput,
294 ) -> Result<StepResult> {
295 let end = self.end_time(state, duration_s)?;
296 let (axis, steering) = self.resolve_control(control)?;
297 let mut current = *state;
298 let mut substeps = 0;
299 let mut grid_index = 1usize;
300 while current.time_s < end {
301 if substeps >= self.settings.max_substeps {
302 return Err(Error::StepLimitExceeded {
303 limit: self.settings.max_substeps,
304 });
305 }
306 let grid_boundary =
310 (state.time_s + grid_index as f64 * self.settings.max_step_s).min(end);
311 let mut boundary = grid_boundary;
312 if let Some(event) = self.rocket.next_event_after(current.time_s) {
313 boundary = boundary.min(event);
314 }
315 if boundary <= current.time_s {
316 return Err(Error::NumericalFailure(
317 "internal timestep cannot advance time",
318 ));
319 }
320 current = self.rk4(current, boundary, axis, steering)?;
321 substeps += 1;
322 if boundary == grid_boundary {
323 grid_index += 1;
324 }
325 }
326 Ok(StepResult {
327 state: current,
328 rocket: self.rocket.sample_unchecked(current.time_s),
329 substeps,
330 })
331 }
332
333 pub fn step_guided<G: Guidance + ?Sized>(
337 &self,
338 state: &FlightState,
339 duration_s: f64,
340 guidance: &mut G,
341 ) -> Result<StepResult> {
342 self.end_time(state, duration_s)?;
343 let command = guidance.command(GuidanceContext {
344 state,
345 rocket: self.rocket.sample_unchecked(state.time_s),
346 step_duration_s: duration_s,
347 max_steering_accel_mps2: self.settings.max_steering_accel_mps2,
348 })?;
349 self.step(state, duration_s, command)
350 }
351
352 fn resolve_control(&self, control: ControlInput) -> Result<(Vec3, Vec3)> {
353 if !finite_vec(&control.thrust_direction)
354 || !finite_vec(&control.steering_acceleration_mps2)
355 {
356 return Err(Error::invalid("control", "all components must be finite"));
357 }
358 let scale = control.thrust_direction.amax();
360 if scale == 0.0 {
361 return Err(Error::invalid("thrust_direction", "must be nonzero"));
362 }
363 let scaled = control.thrust_direction / scale;
364 let axis = scaled / scaled.norm();
365 let scale = control.steering_acceleration_mps2.amax();
366 let steering = if scale == 0.0 || self.settings.max_steering_accel_mps2 == 0.0 {
367 Vec3::zeros()
368 } else {
369 let scaled = control.steering_acceleration_mps2 / scale;
370 let norm = scaled.norm();
371 if scale > self.settings.max_steering_accel_mps2 / norm {
372 scaled * (self.settings.max_steering_accel_mps2 / norm)
373 } else {
374 control.steering_acceleration_mps2
375 }
376 };
377 Ok((axis, steering))
378 }
379
380 fn derivative(
381 &self,
382 state: FlightState,
383 axis: Vec3,
384 steering: Vec3,
385 stages: &[crate::motor::IntervalStage],
386 ) -> Result<(Vec3, Vec3)> {
387 state
388 .validate()
389 .map_err(|_| Error::NumericalFailure("non-finite intermediate state"))?;
390 let mass = self.rocket.interval_mass(state.time_s, stages);
391 let thrust = self.rocket.interval_thrust(state.time_s, stages);
392 let acceleration = self.forces.acceleration_with_axis(&state, mass, axis)?
393 + axis * (thrust / mass)
394 + steering;
395 if !finite_vec(&acceleration) {
396 return Err(Error::NumericalFailure("non-finite acceleration"));
397 }
398 Ok((state.velocity_mps, acceleration))
399 }
400
401 fn rk4(&self, start: FlightState, end: f64, axis: Vec3, steering: Vec3) -> Result<FlightState> {
402 let dt = end - start.time_s;
403 let stages = self.rocket.interval_stages(start.time_s);
404 let stage = |derivative: (Vec3, Vec3), delta: f64, time_s: f64| FlightState {
405 time_s,
406 position_m: start.position_m + derivative.0 * delta,
407 velocity_mps: start.velocity_mps + derivative.1 * delta,
408 };
409 let midpoint = start.time_s + dt * 0.5;
410 let k1 = self.derivative(start, axis, steering, &stages)?;
411 let k2 = self.derivative(stage(k1, dt * 0.5, midpoint), axis, steering, &stages)?;
412 let k3 = self.derivative(stage(k2, dt * 0.5, midpoint), axis, steering, &stages)?;
413 let k4 = self.derivative(stage(k3, dt, end), axis, steering, &stages)?;
414 let result = FlightState {
415 time_s: end,
416 position_m: start.position_m + (k1.0 + k2.0 * 2.0 + k3.0 * 2.0 + k4.0) * (dt / 6.0),
417 velocity_mps: start.velocity_mps + (k1.1 + k2.1 * 2.0 + k3.1 * 2.0 + k4.1) * (dt / 6.0),
418 };
419 result
420 .validate()
421 .map_err(|_| Error::NumericalFailure("non-finite output state"))?;
422 Ok(result)
423 }
424}