1use crate::{
2 finite_vec, nonnegative, positive, ControlInput, Error, Guidance, GuidanceContext, MotorPhase,
3 Result, 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
63#[derive(Debug, Clone, Copy)]
65pub struct IntegratorSettings {
66 pub max_step_s: f64,
68 pub max_substeps: usize,
70 pub max_steering_accel_mps2: f64,
73}
74
75impl Default for IntegratorSettings {
76 fn default() -> Self {
77 Self {
78 max_step_s: 0.01,
79 max_substeps: 100_000,
80 max_steering_accel_mps2: 0.0,
81 }
82 }
83}
84
85impl IntegratorSettings {
86 fn validate(&self) -> Result<()> {
87 positive(self.max_step_s, "max_step_s")?;
88 nonnegative(self.max_steering_accel_mps2, "max_steering_accel_mps2")?;
89 if self.max_substeps == 0 {
90 return Err(Error::invalid("max_substeps", "must be nonzero"));
91 }
92 Ok(())
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq)]
98pub struct StepResult {
99 pub state: FlightState,
101 pub rocket: RocketSample,
103 pub substeps: usize,
105}
106
107#[derive(Debug, Clone)]
113pub struct Simulator<E> {
114 rocket: Rocket,
115 forces: E,
116 settings: IntegratorSettings,
117}
118
119impl<E: ForceModel> Simulator<E> {
120 pub fn new(rocket: Rocket, forces: E, settings: IntegratorSettings) -> Result<Self> {
122 settings.validate()?;
123 Ok(Self {
124 rocket,
125 forces,
126 settings,
127 })
128 }
129
130 pub fn rocket(&self) -> &Rocket {
132 &self.rocket
133 }
134 pub fn forces(&self) -> &E {
136 &self.forces
137 }
138 pub fn forces_mut(&mut self) -> &mut E {
140 &mut self.forces
141 }
142 pub fn settings(&self) -> IntegratorSettings {
144 self.settings
145 }
146
147 fn end_time(&self, state: &FlightState, duration_s: f64) -> Result<f64> {
148 state.validate()?;
149 positive(duration_s, "duration_s")?;
150 let end = state.time_s + duration_s;
151 if !end.is_finite() || end <= state.time_s {
152 return Err(Error::invalid(
153 "duration_s",
154 "must advance time to a finite, distinguishable value",
155 ));
156 }
157 if duration_s > self.settings.max_step_s * self.settings.max_substeps as f64 {
158 return Err(Error::StepLimitExceeded {
159 limit: self.settings.max_substeps,
160 });
161 }
162 Ok(end)
163 }
164
165 pub fn step(
168 &self,
169 state: &FlightState,
170 duration_s: f64,
171 control: ControlInput,
172 ) -> Result<StepResult> {
173 let end = self.end_time(state, duration_s)?;
174 let (axis, steering) = self.resolve_control(control)?;
175 let mut current = *state;
176 let mut substeps = 0;
177 let mut grid_index = 1usize;
178 while current.time_s < end {
179 if substeps >= self.settings.max_substeps {
180 return Err(Error::StepLimitExceeded {
181 limit: self.settings.max_substeps,
182 });
183 }
184 let grid_boundary =
188 (state.time_s + grid_index as f64 * self.settings.max_step_s).min(end);
189 let mut boundary = grid_boundary;
190 if let Some(event) = self
191 .rocket
192 .motor()
193 .and_then(|m| m.next_event_after(current.time_s))
194 {
195 boundary = boundary.min(event);
196 }
197 if boundary <= current.time_s {
198 return Err(Error::NumericalFailure(
199 "internal timestep cannot advance time",
200 ));
201 }
202 current = self.rk4(current, boundary, axis, steering)?;
203 substeps += 1;
204 if boundary == grid_boundary {
205 grid_index += 1;
206 }
207 }
208 Ok(StepResult {
209 state: current,
210 rocket: self.rocket.sample_unchecked(current.time_s),
211 substeps,
212 })
213 }
214
215 pub fn step_guided<G: Guidance + ?Sized>(
219 &self,
220 state: &FlightState,
221 duration_s: f64,
222 guidance: &mut G,
223 ) -> Result<StepResult> {
224 self.end_time(state, duration_s)?;
225 let command = guidance.command(GuidanceContext {
226 state,
227 rocket: self.rocket.sample_unchecked(state.time_s),
228 step_duration_s: duration_s,
229 max_steering_accel_mps2: self.settings.max_steering_accel_mps2,
230 })?;
231 self.step(state, duration_s, command)
232 }
233
234 fn resolve_control(&self, control: ControlInput) -> Result<(Vec3, Vec3)> {
235 if !finite_vec(&control.thrust_direction)
236 || !finite_vec(&control.steering_acceleration_mps2)
237 {
238 return Err(Error::invalid("control", "all components must be finite"));
239 }
240 let scale = control.thrust_direction.amax();
242 if scale == 0.0 {
243 return Err(Error::invalid("thrust_direction", "must be nonzero"));
244 }
245 let scaled = control.thrust_direction / scale;
246 let axis = scaled / scaled.norm();
247 let scale = control.steering_acceleration_mps2.amax();
248 let steering = if scale == 0.0 || self.settings.max_steering_accel_mps2 == 0.0 {
249 Vec3::zeros()
250 } else {
251 let scaled = control.steering_acceleration_mps2 / scale;
252 let norm = scaled.norm();
253 if scale > self.settings.max_steering_accel_mps2 / norm {
254 scaled * (self.settings.max_steering_accel_mps2 / norm)
255 } else {
256 control.steering_acceleration_mps2
257 }
258 };
259 Ok((axis, steering))
260 }
261
262 fn derivative(
263 &self,
264 state: FlightState,
265 axis: Vec3,
266 steering: Vec3,
267 burning: bool,
268 ) -> Result<(Vec3, Vec3)> {
269 state
270 .validate()
271 .map_err(|_| Error::NumericalFailure("non-finite intermediate state"))?;
272 let mass = self.rocket.sample_unchecked(state.time_s).mass_kg;
273 let thrust = self
274 .rocket
275 .motor()
276 .map_or(0.0, |m| m.interval_thrust(state.time_s, burning));
277 let acceleration =
278 self.forces.acceleration(&state, mass)? + axis * (thrust / mass) + steering;
279 if !finite_vec(&acceleration) {
280 return Err(Error::NumericalFailure("non-finite acceleration"));
281 }
282 Ok((state.velocity_mps, acceleration))
283 }
284
285 fn rk4(&self, start: FlightState, end: f64, axis: Vec3, steering: Vec3) -> Result<FlightState> {
286 let dt = end - start.time_s;
287 let burning = self.rocket.sample_unchecked(start.time_s).motor.phase == MotorPhase::Burning;
288 let stage = |derivative: (Vec3, Vec3), delta: f64, time_s: f64| FlightState {
289 time_s,
290 position_m: start.position_m + derivative.0 * delta,
291 velocity_mps: start.velocity_mps + derivative.1 * delta,
292 };
293 let midpoint = start.time_s + dt * 0.5;
294 let k1 = self.derivative(start, axis, steering, burning)?;
295 let k2 = self.derivative(stage(k1, dt * 0.5, midpoint), axis, steering, burning)?;
296 let k3 = self.derivative(stage(k2, dt * 0.5, midpoint), axis, steering, burning)?;
297 let k4 = self.derivative(stage(k3, dt, end), axis, steering, burning)?;
298 let result = FlightState {
299 time_s: end,
300 position_m: start.position_m + (k1.0 + k2.0 * 2.0 + k3.0 * 2.0 + k4.0) * (dt / 6.0),
301 velocity_mps: start.velocity_mps + (k1.1 + k2.1 * 2.0 + k3.1 * 2.0 + k4.1) * (dt / 6.0),
302 };
303 result
304 .validate()
305 .map_err(|_| Error::NumericalFailure("non-finite output state"))?;
306 Ok(result)
307 }
308}