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
38impl<F> ForceModel for F
39where
40 F: Fn(&FlightState, f64) -> Result<Vec3>,
41{
42 fn acceleration(&self, state: &FlightState, mass_kg: f64) -> Result<Vec3> {
43 self(state, mass_kg)
44 }
45}
46
47#[derive(Debug, Clone, Copy)]
49pub struct UniformGravity(pub Vec3);
50
51impl Default for UniformGravity {
52 fn default() -> Self {
53 Self(Vec3::new(0.0, -9.80665, 0.0))
54 }
55}
56
57impl ForceModel for UniformGravity {
58 fn acceleration(&self, _state: &FlightState, _mass_kg: f64) -> Result<Vec3> {
59 Ok(self.0)
60 }
61}
62
63pub const EARTH_MU_M3_S2: f64 = 3.986_004_418e14;
65
66#[derive(Debug, Clone, Copy)]
75pub struct CentralGravity {
76 mu_m3_s2: f64,
77 center_m: Vec3,
78}
79
80impl CentralGravity {
81 pub fn new(mu_m3_s2: f64, center_m: Vec3) -> Result<Self> {
83 positive(mu_m3_s2, "mu_m3_s2")?;
84 if !finite_vec(¢er_m) {
85 return Err(Error::invalid("center_m", "must be finite"));
86 }
87 Ok(Self { mu_m3_s2, center_m })
88 }
89
90 pub fn earth(center_m: Vec3) -> Result<Self> {
92 Self::new(EARTH_MU_M3_S2, center_m)
93 }
94
95 pub fn mu_m3_s2(&self) -> f64 {
97 self.mu_m3_s2
98 }
99 pub fn center_m(&self) -> Vec3 {
101 self.center_m
102 }
103}
104
105impl ForceModel for CentralGravity {
106 fn acceleration(&self, state: &FlightState, _mass_kg: f64) -> Result<Vec3> {
107 let offset = state.position_m - self.center_m;
108 let distance_squared = offset.norm_squared();
109 if !distance_squared.is_finite() || distance_squared == 0.0 {
110 return Err(Error::NumericalFailure(
111 "central gravity is singular at its center",
112 ));
113 }
114 let acceleration = offset * (-self.mu_m3_s2 / (distance_squared * distance_squared.sqrt()));
115 if !finite_vec(&acceleration) {
116 return Err(Error::NumericalFailure("non-finite central gravity"));
117 }
118 Ok(acceleration)
119 }
120}
121
122#[derive(Debug, Clone)]
130pub struct AltitudeSwitch<Below, Above> {
131 below: Below,
132 above: Above,
133 cutoff_altitude_m: f64,
134}
135
136impl<Below, Above> AltitudeSwitch<Below, Above> {
137 pub fn new(below: Below, above: Above, cutoff_altitude_m: f64) -> Result<Self> {
139 if !cutoff_altitude_m.is_finite() {
140 return Err(Error::invalid("cutoff_altitude_m", "must be finite"));
141 }
142 Ok(Self {
143 below,
144 above,
145 cutoff_altitude_m,
146 })
147 }
148 pub fn below(&self) -> &Below {
150 &self.below
151 }
152 pub fn above(&self) -> &Above {
154 &self.above
155 }
156 pub fn cutoff_altitude_m(&self) -> f64 {
158 self.cutoff_altitude_m
159 }
160}
161
162impl<Below: ForceModel, Above: ForceModel> ForceModel for AltitudeSwitch<Below, Above> {
163 fn acceleration(&self, state: &FlightState, mass_kg: f64) -> Result<Vec3> {
164 if state.position_m.y <= self.cutoff_altitude_m {
165 self.below.acceleration(state, mass_kg)
166 } else {
167 self.above.acceleration(state, mass_kg)
168 }
169 }
170}
171
172#[derive(Debug, Clone, Copy)]
174pub struct IntegratorSettings {
175 pub max_step_s: f64,
177 pub max_substeps: usize,
179 pub max_steering_accel_mps2: f64,
182}
183
184impl Default for IntegratorSettings {
185 fn default() -> Self {
186 Self {
187 max_step_s: 0.01,
188 max_substeps: 100_000,
189 max_steering_accel_mps2: 0.0,
190 }
191 }
192}
193
194impl IntegratorSettings {
195 fn validate(&self) -> Result<()> {
196 positive(self.max_step_s, "max_step_s")?;
197 nonnegative(self.max_steering_accel_mps2, "max_steering_accel_mps2")?;
198 if self.max_substeps == 0 {
199 return Err(Error::invalid("max_substeps", "must be nonzero"));
200 }
201 Ok(())
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq)]
207pub struct StepResult {
208 pub state: FlightState,
210 pub rocket: RocketSample,
212 pub substeps: usize,
214}
215
216#[derive(Debug, Clone)]
222pub struct Simulator<E> {
223 rocket: Rocket,
224 forces: E,
225 settings: IntegratorSettings,
226}
227
228impl<E: ForceModel> Simulator<E> {
229 pub fn new(rocket: Rocket, forces: E, settings: IntegratorSettings) -> Result<Self> {
231 settings.validate()?;
232 Ok(Self {
233 rocket,
234 forces,
235 settings,
236 })
237 }
238
239 pub fn rocket(&self) -> &Rocket {
241 &self.rocket
242 }
243 pub fn forces(&self) -> &E {
245 &self.forces
246 }
247 pub fn forces_mut(&mut self) -> &mut E {
249 &mut self.forces
250 }
251 pub fn settings(&self) -> IntegratorSettings {
253 self.settings
254 }
255
256 fn end_time(&self, state: &FlightState, duration_s: f64) -> Result<f64> {
257 state.validate()?;
258 positive(duration_s, "duration_s")?;
259 let end = state.time_s + duration_s;
260 if !end.is_finite() || end <= state.time_s {
261 return Err(Error::invalid(
262 "duration_s",
263 "must advance time to a finite, distinguishable value",
264 ));
265 }
266 if duration_s > self.settings.max_step_s * self.settings.max_substeps as f64 {
267 return Err(Error::StepLimitExceeded {
268 limit: self.settings.max_substeps,
269 });
270 }
271 Ok(end)
272 }
273
274 pub fn step(
277 &self,
278 state: &FlightState,
279 duration_s: f64,
280 control: ControlInput,
281 ) -> Result<StepResult> {
282 let end = self.end_time(state, duration_s)?;
283 let (axis, steering) = self.resolve_control(control)?;
284 let mut current = *state;
285 let mut substeps = 0;
286 let mut grid_index = 1usize;
287 while current.time_s < end {
288 if substeps >= self.settings.max_substeps {
289 return Err(Error::StepLimitExceeded {
290 limit: self.settings.max_substeps,
291 });
292 }
293 let grid_boundary =
297 (state.time_s + grid_index as f64 * self.settings.max_step_s).min(end);
298 let mut boundary = grid_boundary;
299 if let Some(event) = self.rocket.next_event_after(current.time_s) {
300 boundary = boundary.min(event);
301 }
302 if boundary <= current.time_s {
303 return Err(Error::NumericalFailure(
304 "internal timestep cannot advance time",
305 ));
306 }
307 current = self.rk4(current, boundary, axis, steering)?;
308 substeps += 1;
309 if boundary == grid_boundary {
310 grid_index += 1;
311 }
312 }
313 Ok(StepResult {
314 state: current,
315 rocket: self.rocket.sample_unchecked(current.time_s),
316 substeps,
317 })
318 }
319
320 pub fn step_guided<G: Guidance + ?Sized>(
324 &self,
325 state: &FlightState,
326 duration_s: f64,
327 guidance: &mut G,
328 ) -> Result<StepResult> {
329 self.end_time(state, duration_s)?;
330 let command = guidance.command(GuidanceContext {
331 state,
332 rocket: self.rocket.sample_unchecked(state.time_s),
333 step_duration_s: duration_s,
334 max_steering_accel_mps2: self.settings.max_steering_accel_mps2,
335 })?;
336 self.step(state, duration_s, command)
337 }
338
339 fn resolve_control(&self, control: ControlInput) -> Result<(Vec3, Vec3)> {
340 if !finite_vec(&control.thrust_direction)
341 || !finite_vec(&control.steering_acceleration_mps2)
342 {
343 return Err(Error::invalid("control", "all components must be finite"));
344 }
345 let scale = control.thrust_direction.amax();
347 if scale == 0.0 {
348 return Err(Error::invalid("thrust_direction", "must be nonzero"));
349 }
350 let scaled = control.thrust_direction / scale;
351 let axis = scaled / scaled.norm();
352 let scale = control.steering_acceleration_mps2.amax();
353 let steering = if scale == 0.0 || self.settings.max_steering_accel_mps2 == 0.0 {
354 Vec3::zeros()
355 } else {
356 let scaled = control.steering_acceleration_mps2 / scale;
357 let norm = scaled.norm();
358 if scale > self.settings.max_steering_accel_mps2 / norm {
359 scaled * (self.settings.max_steering_accel_mps2 / norm)
360 } else {
361 control.steering_acceleration_mps2
362 }
363 };
364 Ok((axis, steering))
365 }
366
367 fn derivative(
368 &self,
369 state: FlightState,
370 axis: Vec3,
371 steering: Vec3,
372 stages: &[crate::motor::IntervalStage],
373 ) -> Result<(Vec3, Vec3)> {
374 state
375 .validate()
376 .map_err(|_| Error::NumericalFailure("non-finite intermediate state"))?;
377 let mass = self.rocket.interval_mass(state.time_s, stages);
378 let thrust = self.rocket.interval_thrust(state.time_s, stages);
379 let acceleration =
380 self.forces.acceleration(&state, mass)? + axis * (thrust / mass) + steering;
381 if !finite_vec(&acceleration) {
382 return Err(Error::NumericalFailure("non-finite acceleration"));
383 }
384 Ok((state.velocity_mps, acceleration))
385 }
386
387 fn rk4(&self, start: FlightState, end: f64, axis: Vec3, steering: Vec3) -> Result<FlightState> {
388 let dt = end - start.time_s;
389 let stages = self.rocket.interval_stages(start.time_s);
390 let stage = |derivative: (Vec3, Vec3), delta: f64, time_s: f64| FlightState {
391 time_s,
392 position_m: start.position_m + derivative.0 * delta,
393 velocity_mps: start.velocity_mps + derivative.1 * delta,
394 };
395 let midpoint = start.time_s + dt * 0.5;
396 let k1 = self.derivative(start, axis, steering, &stages)?;
397 let k2 = self.derivative(stage(k1, dt * 0.5, midpoint), axis, steering, &stages)?;
398 let k3 = self.derivative(stage(k2, dt * 0.5, midpoint), axis, steering, &stages)?;
399 let k4 = self.derivative(stage(k3, dt, end), axis, steering, &stages)?;
400 let result = FlightState {
401 time_s: end,
402 position_m: start.position_m + (k1.0 + k2.0 * 2.0 + k3.0 * 2.0 + k4.0) * (dt / 6.0),
403 velocity_mps: start.velocity_mps + (k1.1 + k2.1 * 2.0 + k3.1 * 2.0 + k4.1) * (dt / 6.0),
404 };
405 result
406 .validate()
407 .map_err(|_| Error::NumericalFailure("non-finite output state"))?;
408 Ok(result)
409 }
410}