57) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
58 """Return curve arrays truncated to burn_time_s (interpolated endpoint)."""
59 times = np.asarray(times, dtype=float)
60 thrust = np.asarray(thrust, dtype=float)
61 mdot_O = np.asarray(mdot_O, dtype=float)
62 mdot_F = np.asarray(mdot_F, dtype=float)
65 raise ValueError(
"Time-series must contain at least two points")
69 t_end = float(np.clip(burn_time_s, times[0], times[-1]))
71 t_end = float(times[1])
73 mask = times <= t_end + 1e-12
75 thrust_out = thrust[mask]
76 mdot_O_out = mdot_O[mask]
77 mdot_F_out = mdot_F[mask]
79 if t_out[-1] < t_end - 1e-9:
80 t_out = np.append(t_out, t_end)
81 thrust_out = np.append(thrust_out, np.interp(t_end, times, thrust))
82 mdot_O_out = np.append(mdot_O_out, np.interp(t_end, times, mdot_O))
83 mdot_F_out = np.append(mdot_F_out, np.interp(t_end, times, mdot_F))
85 return t_out, thrust_out, mdot_O_out, mdot_F_out
111 target_apogee_m: float,
112 apogee_tolerance_m: float,
113 simulate_at_burn_time: Callable[[float], BurnTimeEval],
114 min_burn_time_s: Optional[float] =
None,
115 max_burn_time_s: Optional[float] =
None,
116 time_resolution_s: float = 0.05,
117 max_iterations: int = 24,
118) -> AltitudeOptimizeResult:
119 """Binary-search the shortest burn time that reaches target_apogee_m."""
120 times_norm = np.asarray(times, dtype=float)
121 times_norm = times_norm - times_norm[0]
122 t_max_curve = float(times_norm[-1])
124 t_lo = float(min_burn_time_s)
if min_burn_time_s
is not None else max(0.05, float(times_norm[1] - times_norm[0]))
125 t_hi = float(max_burn_time_s)
if max_burn_time_s
is not None else t_max_curve
126 t_lo =
max(t_lo, 0.05)
127 t_hi =
min(t_hi, t_max_curve)
129 t_lo =
max(0.05, t_hi * 0.1)
131 evaluations: List[BurnTimeEval] = []
134 def _eval(t: float) -> BurnTimeEval:
137 result = simulate_at_burn_time(t)
138 evaluations.append(result)
141 full_eval = _eval(t_hi)
142 if not full_eval.success:
145 target_apogee_m=target_apogee_m,
146 apogee_tolerance_m=apogee_tolerance_m,
147 optimal_burn_time_s=0.0,
150 achieved_apogee_m=0.0,
151 apogee_error_m=target_apogee_m,
152 total_impulse_Ns=0.0,
153 simulations_run=sim_count,
154 infeasible_reason=full_eval.error
or "Flight simulation failed at maximum burn time",
155 evaluations=evaluations,
158 target_min = target_apogee_m - apogee_tolerance_m
159 if full_eval.apogee_m < target_min:
161 times, thrust, mdot_O, mdot_F, t_hi
165 target_apogee_m=target_apogee_m,
166 apogee_tolerance_m=apogee_tolerance_m,
167 optimal_burn_time_s=t_hi,
168 optimal_lox_kg=lox_full,
169 optimal_fuel_kg=fuel_full,
170 achieved_apogee_m=full_eval.apogee_m,
171 apogee_error_m=target_apogee_m - full_eval.apogee_m,
172 total_impulse_Ns=impulse_full,
173 simulations_run=sim_count,
175 f
"Full time-series burn ({t_hi:.2f}s) reaches only {full_eval.apogee_m:.0f} m "
176 f
"(target {target_apogee_m:.0f} m). Re-run time-series with higher thrust or longer burn."
178 evaluations=evaluations,
181 lo_eval = _eval(t_lo)
182 if lo_eval.success
and lo_eval.apogee_m >= target_min:
188 for _
in range(max_iterations):
189 if hi - lo <= time_resolution_s:
191 mid = 0.5 * (lo + hi)
192 mid_eval = _eval(mid)
193 if not mid_eval.success:
196 if mid_eval.apogee_m >= target_min:
203 e
for e
in evaluations
204 if e.success
and e.apogee_m >= target_min
207 best =
min(feasible, key=
lambda e: e.fuel_required_kg)
210 times, thrust, mdot_O, mdot_F, best.burn_time_s
215 target_apogee_m=target_apogee_m,
216 apogee_tolerance_m=apogee_tolerance_m,
217 optimal_burn_time_s=best.burn_time_s,
218 optimal_lox_kg=lox_opt,
219 optimal_fuel_kg=fuel_opt,
220 achieved_apogee_m=best.apogee_m,
221 apogee_error_m=best.apogee_m - target_apogee_m,
222 total_impulse_Ns=impulse_opt,
223 simulations_run=sim_count,
224 evaluations=evaluations,
AltitudeOptimizeResult optimize_minimum_fuel_burn_time(*, np.ndarray times, np.ndarray thrust, np.ndarray mdot_O, np.ndarray mdot_F, float target_apogee_m, float apogee_tolerance_m, Callable[[float], BurnTimeEval] simulate_at_burn_time, Optional[float] min_burn_time_s=None, Optional[float] max_burn_time_s=None, float time_resolution_s=0.05, int max_iterations=24)