36def _x(eps=EPS, lstar=LSTAR, ang=(43.0, 46.0), n=24, P=583.1):
37 return np.array([AT, lstar, eps, BORE + WALL, float(n), 2.4e-3, ang[0], 0.0126,
38 2.2e-3, ang[1], 0.0152, P, P], dtype=float)
79 """Frustum-plus-arc chamber, by hand (H&H ch. 4 geometry), independent of the module."""
80 Rt = math.sqrt(AT / math.pi)
82 r_tan = Rt * (1.0 + 1.5 * (1.0 - math.cos(theta)))
83 L_cone = (Rc - r_tan) / math.tan(theta)
85 V_cone = math.pi * L_cone / 3.0 * (Rc ** 2 + Rc * r_tan + r_tan ** 2)
86 t = np.linspace(-(math.pi / 2 + theta), -math.pi / 2, 20001)
87 xa = 1.5 * Rt * np.cos(t)
88 ya = 1.5 * Rt * np.sin(t) + 2.5 * Rt
89 V_arc = float(np.sum(np.pi * 0.5 * (ya[1:] ** 2 + ya[:-1] ** 2) * np.diff(xa)))
90 L_cyl = (LSTAR * AT - V_cone - V_arc) / (math.pi * Rc ** 2)
91 L_arc = 1.5 * Rt * math.sin(theta)
92 return L_cyl, L_cone, L_arc
96 """CN-3: the mass term uses the chamber length _layer1_apply_chamber_geometry_to_config
97 writes. The worker used a cone to R_t (140.2 mm here) against 130.5 mm built."""
99 cfg = load_config(str(ROOT /
"configs" /
"ethalox_6500N.yaml"))
100 L1._layer1_apply_chamber_geometry_to_config(
101 cfg, A_throat=AT, Lstar=LSTAR, expansion_ratio=EPS, D_chamber_outer=BORE + WALL,
102 max_nozzle_exit=0.2032, wall_thickness_m=WALL)
103 assert cfg.chamber_geometry.length == pytest.approx(L_cyl + L_cone, abs=2e-5)
105 c_on =
_constants(layer1_W_MASS=3000.0, layer1_chamber_wall_density_kg_m3=3400.0,
106 layer1_chamber_mass_ref_kg=5.0, layer1_target_Pc_pa=PC, layer1_W_PC=0.0)
107 c_off = dict(c_on, layer1_W_MASS=0.0)
108 d = (L1._compute_objective_value(
_result(),
_x(),
_req(), c_on)
109 - L1._compute_objective_value(
_result(),
_x(),
_req(), c_off))
110 m = L1._layer1_chamber_mass_kg(math.pi * BORE ** 2 / 4, L_cyl + L_cone, WALL, 3400.0, Pc_pa=PC)
111 assert d == pytest.approx(3000.0 * (m / 5.0) ** 2, rel=1e-3)
115 """CN-9: face-to-exit length of the drawn engine is 244.1 mm here (CN-1 contour
116 integration); the estimate used 214-224 mm and let a 240 mm limit through."""
119 xs, _ = rao(AT, AT * EPS, bell_percent=0.8, do_plot=
False, method=
"top")[:2]
120 L_true = L_cyl + L_cone + L_arc + float(np.max(xs))
121 assert L_true == pytest.approx(0.2441, abs=5e-4)
123 over = L1._compute_objective_value(
_result(),
_x(),
_req(max_engine_length=L_true - 0.004), c)
124 under = L1._compute_objective_value(
_result(),
_x(),
_req(max_engine_length=L_true + 0.004), c)
125 assert over >= 1e6 > under
128@pytest.mark.parametrize("eps", [3.0, 5.6, 20.0])
150 """L1-09: the free band was [1/1.05, 1.05] whatever impinging_momentum_R_min/max said."""
152 c =
_constants(W_MOM=75.0, impinging_momentum_R_min=0.98, impinging_momentum_R_max=1.02,
153 layer1_momentum_band_width=0.05)
154 c0 = dict(c, W_MOM=0.0)
155 res =
_result(diagnostics={
"momentum_ratio_R": 1.04})
156 assert (L1._compute_objective_value(res,
_x(),
_req(), c)
157 > L1._compute_objective_value(res,
_x(),
_req(), c0))
158 res_in =
_result(diagnostics={
"momentum_ratio_R": 1.01})
159 assert (L1._compute_objective_value(res_in,
_x(),
_req(), c)
160 == L1._compute_objective_value(res_in,
_x(),
_req(), c0))
188 """INJ-7: axial standoff 4 d_avg at 60/60 deg is 8 d along each jet (> 7 d)."""
191 tan_sum = 2.0 * math.tan(math.radians(60.0))
192 s_F = s_O + 2.0 * math.pi * 4.0 * d * tan_sum / n
193 ld = L1._impinging_free_jet_ld(n_elements=n, spacing_O_m=s_O, spacing_F_m=s_F,
194 d_jet_O_m=d, d_jet_F_m=d, angle_O_deg=60.0, angle_F_deg=60.0)
195 assert ld == pytest.approx(8.0, rel=1e-9)
196 x =
_x(ang=(60.0, 60.0), n=int(n))
197 x[5], x[7], x[8], x[10] = d, s_O, d, s_F
201 x45[6] = x45[9] = 40.0
202 tan40 = 2.0 * math.tan(math.radians(40.0))
203 x45[10] = s_O + 2.0 * math.pi * 4.0 * d * tan40 / n
208 """L1-11: after 200 consecutive failures the inline objective returned 1e5, which
209 counts as feasible and beats every infeasible-but-evaluable design."""
210 from unittest.mock
import MagicMock, patch
211 real_cfg = load_config(str(ROOT /
"configs" /
"impinging_smoke.yaml"))
212 engine_mock = MagicMock()
213 engine_mock.evaluate.side_effect = ValueError(
"Supply < Demand at all Pc")
214 x13 = [0.002, 1.0, 8.0, 0.14, 12.0, 0.002, 45.0, 0.012, 0.0022, 45.0, 0.011, 550.0, 650.0]
216 def _dc(obj, memo=None):
217 return real_cfg
if obj
is real_cfg
else copy.deepcopy(obj)
219 with patch(
"engine.optimizer.layers.layer1_static_optimization.copy.deepcopy", side_effect=_dc), \
220 patch(
"engine.optimizer.layers.layer1_static_optimization.PintleEngineRunner",
221 return_value=engine_mock), \
222 patch(
"engine.optimizer.layers.layer1_static_optimization.cma.CMAEvolutionStrategy")
as mcma, \
223 patch(
"engine.optimizer.layers.layer1_static_optimization.minimize")
as mmin, \
224 patch(
"engine.optimizer.layers.layer1_static_optimization.ProcessPoolExecutor"):
225 mcma.return_value.stop.return_value =
True
226 mcma.return_value.result.xbest = x13
227 mcma.return_value.result.fbest = 0.1
228 lb = MagicMock(); lb.x = np.array(x13); lb.fun = 0.05; lb.success =
True
229 mmin.return_value = lb
230 req = real_cfg.design_requirements.model_dump()
232 L1.run_layer1_optimization(
233 config_obj=real_cfg, runner=MagicMock(), requirements=req, target_burn_time=10.0,
234 tolerances={
"thrust": 0.1}, pressure_config={
"mode":
"optimizer_controlled"},
235 layer1_max_iterations=1, layer1_cma_restarts=1)
238 assert mmin.call_args
is not None
239 objective = mmin.call_args[0][0]
240 vals = [objective(np.array(x13, dtype=float))
for _
in range(205)]
241 assert min(vals) >= 1e6
245 """The face-closure thickness in the chamber-mass proxy used t = a sqrt(0.3 p / sigma),
246 37 % thinner than Roark's fixed-edge plate, sigma_max = 3 p a^2 / (4 t^2)."""
247 a, p, sig, rho = 0.0635, 3.0e6, 205e6, 3400.0
248 t = a * math.sqrt(3.0 * p / (4.0 * sig))
249 A_c = math.pi * (2 * a) ** 2 / 4
250 m_barrel_only = L1._layer1_chamber_mass_kg(A_c, 0.1, WALL, rho, Pc_pa=0.0)
251 m = L1._layer1_chamber_mass_kg(A_c, 0.1, WALL, rho, Pc_pa=p)
252 assert m - m_barrel_only == pytest.approx(math.pi * a * a * t * rho, rel=1e-12)