47 design_requirements: Dict[str, Any],
48 constraints: Dict[str, Any],
49 max_iterations: int = 10,
50 convergence_tolerance: float = 0.01,
51 use_time_varying: bool =
True,
54 Optimize pintle and chamber geometry with full coupling.
58 design_requirements : dict
59 - target_thrust: float [N]
60 - target_burn_time: float [s]
61 - target_stability_margin: float
62 - P_tank_O: float [Pa]
63 - P_tank_F: float [Pa]
64 - target_Isp: Optional[float] [s]
67 - max_chamber_length: float [m]
68 - max_chamber_diameter: float [m]
69 - min_Lstar: float [m]
70 - max_Lstar: float [m]
71 - min_expansion_ratio: float
72 - max_expansion_ratio: float
73 - max_engine_weight: Optional[float] [kg]
74 - max_vehicle_length: Optional[float] [m]
75 - max_vehicle_diameter: Optional[float] [m]
78 Maximum number of coupling iterations
80 convergence_tolerance : float
81 Relative change tolerance for convergence (e.g., 0.01 = 1%)
83 use_time_varying : bool
84 If True, optimize chamber across entire burn time (accounts for ablative recession)
85 If False, optimize at t=0 only
90 - optimized_config: PintleEngineConfig
91 - iteration_history: List of iteration results
92 - convergence_info: Dict with convergence status
93 - performance: Final performance metrics
96 iteration_history = []
98 P_tank_O = design_requirements[
"P_tank_O"]
99 P_tank_F = design_requirements[
"P_tank_F"]
100 target_thrust = design_requirements[
"target_thrust"]
101 target_burn_time = design_requirements.get(
"target_burn_time", 10.0)
104 prev_pintle_params =
None
105 prev_chamber_params =
None
108 last_time_varying_results =
None
109 last_time_varying_summary =
None
111 for iteration
in range(max_iterations):
113 "iteration": iteration,
114 "pintle_config":
None,
115 "chamber_config":
None,
123 target_thrust=target_thrust,
124 target_isp=design_requirements.get(
"target_Isp",
None),
125 target_mr=current_config.combustion.MR
if hasattr(current_config.combustion,
'MR')
else 2.5,
128 constraints=constraints,
132 current_config = pintle_results[
"optimized_config"]
134 iteration_result[
"pintle_performance"] = pintle_results[
"performance"]
136 except Exception
as e:
138 warnings.warn(f
"Pintle optimization failed at iteration {iteration}: {e}")
145 chamber_design_reqs = {
146 "target_thrust": target_thrust,
147 "target_burn_time": target_burn_time,
148 "target_stability_margin": design_requirements.get(
"target_stability_margin", 1.2),
149 "P_tank_O": P_tank_O,
150 "P_tank_F": P_tank_F,
151 "target_Isp": design_requirements.get(
"target_Isp",
None),
175 current_config = chamber_results[
"optimized_config"]
177 iteration_result[
"chamber_performance"] = chamber_results[
"performance"]
180 if "time_varying_results" in chamber_results:
181 last_time_varying_results = chamber_results[
"time_varying_results"]
182 if "performance" in chamber_results
and "time_varying" in chamber_results[
"performance"]:
183 last_time_varying_summary = chamber_results[
"performance"][
"time_varying"]
185 except Exception
as e:
187 warnings.warn(f
"Chamber optimization failed at iteration {iteration}: {e}")
194 final_performance = runner.evaluate(P_tank_O, P_tank_F)
197 if iteration_result.get(
"chamber_performance"):
198 merged_performance = dict(iteration_result[
"chamber_performance"])
200 for key, value
in final_performance.items():
201 if key !=
"time_varying":
202 merged_performance[key] = value
203 iteration_result[
"performance"] = merged_performance
205 iteration_result[
"performance"] = final_performance
206 except Exception
as e:
208 warnings.warn(f
"Performance evaluation failed at iteration {iteration}: {e}")
214 if prev_pintle_params
is not None and prev_chamber_params
is not None:
218 max_change =
max(pintle_change, chamber_change)
220 iteration_result[
"convergence"] = {
221 "pintle_change": pintle_change,
222 "chamber_change": chamber_change,
223 "max_change": max_change,
224 "converged": max_change < convergence_tolerance,
227 if max_change < convergence_tolerance:
229 iteration_result[
"converged"] =
True
230 iteration_history.append(iteration_result)
233 iteration_result[
"convergence"] = {
234 "pintle_change": np.inf,
235 "chamber_change": np.inf,
236 "max_change": np.inf,
240 prev_pintle_params = current_pintle
241 prev_chamber_params = current_chamber
242 iteration_history.append(iteration_result)
246 final_performance = runner.evaluate(P_tank_O, P_tank_F)
249 if last_time_varying_summary
is not None:
250 final_performance[
"time_varying"] = last_time_varying_summary
253 "optimized_config": current_config,
254 "iteration_history": iteration_history,
255 "convergence_info": {
256 "converged": iteration_history[-1][
"convergence"][
"converged"]
if iteration_history
else False,
257 "iterations": len(iteration_history),
258 "final_change": iteration_history[-1][
"convergence"][
"max_change"]
if iteration_history
else np.inf,
260 "performance": final_performance,
261 "design_requirements": design_requirements,
262 "constraints": constraints,
266 if last_time_varying_results
is not None:
267 result[
"time_varying_results"] = last_time_varying_results
273 config: PintleEngineConfig,
274 design_requirements: Dict[str, Any],
275 constraints: Dict[str, Any],
281 Optimize chamber geometry across entire burn time.
284 - Ablative recession (chamber volume grows, throat area grows)
285 - Graphite recession (throat area may grow if graphite erodes)
286 - Time-varying Cf (thrust coefficient changes with geometry)
287 - Time-varying efficiency (L* changes affect combustion efficiency)
289 Objective: Minimize error in average thrust over burn time
291 target_thrust = design_requirements[
"target_thrust"]
292 target_stability_margin = design_requirements.get(
"target_stability_margin", 1.2)
296 initial_guess = self.
chamber_optimizer._generate_initial_guess(target_thrust, constraints)
298 initial_guess[
"A_throat"],
299 initial_guess[
"A_exit"],
300 initial_guess[
"Lstar"],
301 initial_guess.get(
"chamber_diameter", 0.1),
310 def objective_time_varying(x: np.ndarray) -> float:
311 """Minimize error in average thrust over entire burn time."""
321 time_array = np.linspace(0.0, burn_time, n_time_points)
322 P_tank_O_array = np.full_like(time_array, P_tank_O)
323 P_tank_F_array = np.full_like(time_array, P_tank_F)
326 results_array = runner.evaluate_arrays_with_time(
330 track_ablative_geometry=
True,
331 use_coupled_solver=
True,
335 thrust_array = results_array[
"F"]
336 avg_thrust = np.mean(thrust_array)
339 thrust_error = abs(avg_thrust - target_thrust) / target_thrust
342 stability_margins = results_array.get(
"chugging_stability_margin", np.array([1.0]))
343 min_stability = np.min(stability_margins)
344 stability_error =
max(0.0, target_stability_margin - min_stability) / target_stability_margin
347 thrust_std = np.std(thrust_array)
348 thrust_variation_penalty = (thrust_std / avg_thrust)
if avg_thrust > 0
else 1.0
352 10.0 * thrust_error +
353 5.0 * stability_error +
354 2.0 * thrust_variation_penalty
360 "avg_thrust": avg_thrust,
361 "thrust_error": thrust_error,
362 "stability_error": stability_error,
363 "thrust_variation": thrust_std,
364 "objective": objective_value,
367 return objective_value
369 except Exception
as e:
371 warnings.warn(f
"Time-varying objective evaluation failed: {e}")
376 constraints, design_requirements, P_tank_O, P_tank_F
381 objective_time_varying,
385 constraints=constraints_list,
386 options={
"maxiter": 100,
"ftol": 1e-6},
390 optimized_config = self.
chamber_optimizer._update_config_from_x(result.x, config)
394 time_array = np.linspace(0.0, burn_time, 100)
395 P_tank_O_array = np.full_like(time_array, P_tank_O)
396 P_tank_F_array = np.full_like(time_array, P_tank_F)
398 final_results_array = optimized_runner.evaluate_arrays_with_time(
402 track_ablative_geometry=
True,
403 use_coupled_solver=
True,
407 final_results = optimized_runner.evaluate(P_tank_O, P_tank_F)
410 final_results[
"time_varying"] = {
411 "avg_thrust": float(np.mean(final_results_array[
"F"])),
412 "min_thrust": float(np.min(final_results_array[
"F"])),
413 "max_thrust": float(np.max(final_results_array[
"F"])),
414 "thrust_std": float(np.std(final_results_array[
"F"])),
415 "avg_isp": float(np.mean(final_results_array[
"Isp"])),
416 "min_stability_margin": float(np.min(final_results_array.get(
"chugging_stability_margin", [1.0]))),
417 "max_recession_chamber": float(np.max(final_results_array.get(
"recession_chamber", [0.0]))),
418 "max_recession_throat": float(np.max(final_results_array.get(
"recession_throat", [0.0]))),
422 "optimized_config": optimized_config,
423 "optimization_result": result,
424 "performance": final_results,
425 "convergence_history": history,
426 "time_varying_results": final_results_array,
430 """Extract pintle geometry parameters for convergence checking."""
432 if hasattr(config,
'injector')
and config.injector.type ==
"pintle":
433 geometry = config.injector.geometry
434 if hasattr(geometry,
'fuel'):
435 params[
"d_pintle_tip"] = geometry.fuel.d_pintle_tip
436 params[
"h_gap"] = geometry.fuel.h_gap
437 if hasattr(geometry,
'lox'):
438 params[
"n_orifices"] = float(geometry.lox.n_orifices)
439 params[
"d_orifice"] = geometry.lox.d_orifice
440 params[
"theta_orifice"] = geometry.lox.theta_orifice
455 self, params_prev: Dict[str, float], params_current: Dict[str, float]
457 """Calculate maximum relative change between parameter sets."""
459 for key
in set(params_prev.keys()) | set(params_current.keys()):
460 if key
in params_prev
and key
in params_current:
461 val_prev = params_prev[key]
462 val_current = params_current[key]
463 if abs(val_prev) > 1e-10:
464 rel_change = abs(val_current - val_prev) / abs(val_prev)
465 max_change =
max(max_change, rel_change)