21 """Optional forward-mode knobs for rich stability re-evaluation."""
22 eta_inj_O: float |
None = Field(default=
None, gt=0, le=0.6, description=
"Oxidizer ΔP_inj/Pc")
23 smd_um: float |
None = Field(default=
None, gt=0, le=400, description=
"Oxidizer spray SMD [µm]")
24 smd_F_um: float |
None = Field(default=
None, gt=0, le=400, description=
"Fuel spray SMD [µm]")
25 eta_inj_F: float |
None = Field(default=
None, gt=0, le=0.6, description=
"Fuel ΔP_inj/Pc")
26 n_interaction: float |
None = Field(default=
None, gt=0, le=2, description=
"Combustion interaction index n")
27 chi_acoustic: float |
None = Field(default=
None, gt=0, le=1, description=
"Acoustic sensitive-fraction χ")
28 time_lag_model: Literal[
"leonardi_dtl",
"d2_law"] |
None = Field(
30 description=
"Conversion-lag model for the chug loop. Overrides stability.time_lag_model for this run only.",
32 convection_model: Literal[
"none",
"leonardi_eq8",
"ranz_marshall"] |
None = Field(
33 default=
None, description=
"Convective speed-up applied to the droplet lifetime.",
35 mixing_lag_fraction: float |
None = Field(
36 default=
None, ge=0, le=3,
37 description=
"Mixing lag as a fraction of the rate-limiting vaporization lag.",
42 """Request body for forward evaluation."""
43 lox_pressure_psi: float = Field(..., gt=0, description=
"LOX tank pressure in psi")
44 fuel_pressure_psi: float = Field(..., gt=0, description=
"Fuel tank pressure in psi")
45 stability_overrides: StabilityOverrides |
None = Field(
46 default=
None, description=
"Optional stability sensitivity overrides (forward mode sliders)",
51 """Recursively convert numpy types to Python native types."""
52 if isinstance(obj, dict):
54 elif isinstance(obj, (list, tuple)):
56 elif isinstance(obj, np.ndarray):
58 elif isinstance(obj, np.floating):
65 return v
if math.isfinite(v)
else None
66 elif isinstance(obj, np.integer):
68 elif isinstance(obj, float):
69 return obj
if math.isfinite(obj)
else None
70 elif isinstance(obj, np.bool_):
90def _evaluate(request: EvaluateRequest, session: UserSession) -> dict:
93 config, runner = session.app_state.snapshot()
94 if config
is None or runner
is None:
97 detail=
"No config loaded. Upload a config file first."
101 P_tank_O = request.lox_pressure_psi * PSI_TO_PA
102 P_tank_F = request.fuel_pressure_psi * PSI_TO_PA
107 if request.stability_overrides
is not None:
109 k: v
for k, v
in request.stability_overrides.model_dump().items()
if v
is not None
111 results = runner.evaluate(
112 P_tank_O, P_tank_F, debug=
True, rich_stability=
True, stability_overrides=overrides,
119 design_warning = design_staleness(config)
125 hc = handcheck(config, results, P_tank_O, P_tank_F)
126 except Exception
as e:
127 hc = {
"rows": [],
"flags": [],
"error": f
"{type(e).__name__}: {e}"}
128 report = forward_report(config, results, handcheck_rows=hc)
136 "lox_pressure_psi": request.lox_pressure_psi,
137 "fuel_pressure_psi": request.fuel_pressure_psi,
138 "ambient_pressure_pa": results.get(
"P_ambient", 101325.0),
139 "elevation_m": results.get(
"elevation", 0.0),
141 "design_warning": design_warning,
146 except Exception
as e:
149 detail=f
"Evaluation failed: {str(e)}"