18 time_array: np.ndarray,
23 config: PintleEngineConfig,
24 copv_volume_m3: float,
29 Calculate COPV pressure curve using polytropic blowdown model.
31 Uses the same method as the COPV tab with user-specified temperatures (260K).
36 psi_to_Pa = 6894.757293168
39 "mdot_O (kg/s)": mdot_O,
40 "mdot_F (kg/s)": mdot_F,
41 "P_tank_O (psi)": P_tank_O / psi_to_Pa,
42 "P_tank_F (psi)": P_tank_F / psi_to_Pa,
45 copv_results = size_or_check_copv_for_polytropic_N2(
52 copv_volume_m3=copv_volume_m3,
53 branch_temperatures_K={
62 "copv_pressure_Pa": copv_results.get(
"PH_trace_Pa", np.zeros_like(time_array)),
63 "copv_pressure_psi": copv_results.get(
"PH_trace_Pa", np.zeros_like(time_array)) / psi_to_Pa,
64 "initial_pressure_Pa": copv_results.get(
"P0_Pa", 0),
65 "initial_mass_kg": copv_results.get(
"m0_kg", 0),
66 "total_delivered_kg": copv_results.get(
"total_delivered_mass_kg", 0),
67 "min_margin_psi": copv_results.get(
"min_margin_Pa", 0) / psi_to_Pa,
69 except Exception
as e:
71 P_max =
max(np.max(P_tank_O), np.max(P_tank_F))
72 copv_P0 = P_max * 1.15
74 mdot_total = mdot_O + mdot_F
75 mass_consumed = np.cumsum(mdot_total * np.gradient(time_array))
76 copv_pressure = copv_P0 * (1 - 0.3 * mass_consumed / (mass_consumed[-1] + 0.001))
82 "copv_pressure_Pa": copv_pressure,
83 "copv_pressure_psi": copv_pressure / 6894.757293168,
84 "initial_pressure_Pa": copv_P0,
85 "initial_mass_kg": 0.5,
86 "total_delivered_kg": 0.3,
87 "min_margin_psi": 50.0,
92 config: PintleEngineConfig,
93 pressure_curves: Dict[str, np.ndarray],
96 """Run flight simulation on the optimized engine.
98 Uses the existing setup_flight function from flight_sim.py.
101 config: Engine configuration (will be updated with burn_time)
102 pressure_curves: Dict with 'time', 'thrust', 'mdot_O', 'mdot_F' arrays
103 burn_time: Actual burn time to use (may be truncated from original)
106 Dict with flight simulation results
110 from scipy.interpolate
import interp1d
114 config_copy = copy.deepcopy(config)
119 if hasattr(burn_time,
'__call__'):
121 burn_time_float = float(burn_time(0))
123 burn_time_float = float(burn_time)
124 if hasattr(config_copy,
'thrust')
and hasattr(config_copy.thrust,
'burn_time'):
125 config_copy.thrust.burn_time = burn_time_float
129 time_raw = pressure_curves[
"time"]
130 thrust_raw = pressure_curves[
"thrust"]
131 mdot_O_raw = pressure_curves[
"mdot_O"]
132 mdot_F_raw = pressure_curves[
"mdot_F"]
135 def to_array(val, time_samples):
136 """Convert a value (array, callable, or interp1d) to a numpy array."""
138 return np.array([], dtype=float)
139 elif hasattr(val,
'__call__'):
141 return np.asarray([float(val(t))
for t
in time_samples], dtype=float)
143 return np.asarray(val, dtype=float)
146 if hasattr(time_raw,
'__call__'):
148 time_samples = np.linspace(0, burn_time_float,
max(100, int(burn_time_float * 100)))
149 time_array = time_samples
151 time_array = np.asarray(time_raw, dtype=float)
152 time_samples = time_array
155 thrust_array = to_array(thrust_raw, time_samples)
156 mdot_O_array = to_array(mdot_O_raw, time_samples)
157 mdot_F_array = to_array(mdot_F_raw, time_samples)
160 time_array = time_array.flatten()
161 thrust_array = thrust_array.flatten()
162 mdot_O_array = mdot_O_array.flatten()
163 mdot_F_array = mdot_F_array.flatten()
166 min_len =
min(len(time_array), len(thrust_array), len(mdot_O_array), len(mdot_F_array))
168 raise ValueError(
"Pressure curves arrays are empty")
170 time_array = time_array[:min_len]
171 thrust_array = thrust_array[:min_len]
172 mdot_O_array = mdot_O_array[:min_len]
173 mdot_F_array = mdot_F_array[:min_len]
177 thrust_func = interp1d(time_array, thrust_array, kind=
'linear', fill_value=0, bounds_error=
False)
178 mdot_O_func = interp1d(time_array, mdot_O_array, kind=
'linear', fill_value=0, bounds_error=
False)
179 mdot_F_func = interp1d(time_array, mdot_F_array, kind=
'linear', fill_value=0, bounds_error=
False)
183 result = setup_flight(config_copy, thrust_func, mdot_O_func, mdot_F_func, plot_results=
False)
185 apogee = result.get(
"apogee", 0)
186 max_velocity = result.get(
"max_velocity", 0)
191 max_thrust = np.max(thrust_array)
if len(thrust_array) > 0
else 0
192 initial_thrust = thrust_array[0]
if len(thrust_array) > 0
else 0
195 lox_mass = getattr(config_copy.lox_tank,
'mass', 0)
if hasattr(config_copy,
'lox_tank')
else 0
196 fuel_mass = getattr(config_copy.fuel_tank,
'mass', 0)
if hasattr(config_copy,
'fuel_tank')
else 0
199 f
"Apogee is suspiciously low ({apogee:.1f}m). "
200 f
"Diagnostics: max_thrust={max_thrust:.1f}N, initial_thrust={initial_thrust:.1f}N, "
201 f
"lox_mass={lox_mass:.2f}kg, fuel_mass={fuel_mass:.2f}kg, burn_time={burn_time_float:.2f}s"
208 "max_velocity": max_velocity,
209 "flight_time": result.get(
"flight_time", 0),
210 "flight_obj": result.get(
"flight",
None),
216 "max_velocity": max_velocity,
217 "flight_time": result.get(
"flight_time", 0),
218 "flight_obj": result.get(
"flight",
None),
219 "truncation_info": result.get(
"truncation_info", {}),
221 except ValueError
as e:
224 if "must be within the domain of the Function" in error_str:
229 "error":
"Tank simulation error: propellant mass may exceed tank capacity. Try reducing LOX or fuel mass slightly.",
240 except Exception
as e: