1"""The time-varying solve's wall, geometry and bookkeeping (TH-6, TH-8, TH-9, CN-7, TH-12, TH-2).
3What each test guards, and what the code did before:
4 * the nozzle exit stayed put only on paper: it grew at the chamber liner's rate even with
5 ``nozzle_ablative: false`` (eps rose 10 % over the burn);
6 * t = 0 rebuilt the chamber as a full-bore cylinder of cg.length (L* 1.08 for a 1.00 design);
7 * thrust used the post-step throat while Pc was solved on the pre-step one;
8 * the graphite surface was the caller's 2000 K guess and the insert conducted steadily into a
9 300 K sink, so a 3 mm and a 12 mm insert burned identically;
10 * the state carried a 2 GW/m^2 "nozzle heat flux", an 86,000 K nozzle wall and 3e7 K case.
20sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
25ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26CFG = os.path.join(ROOT,
"configs",
"ethalox_6500N.yaml")
27PSI = 6894.757293168361
31@pytest.fixture(autouse=True)
33 monkeypatch.setenv(
"ED_ACCEL",
"off")
37 cfg = load_config(CFG)
38 if mutate
is not None:
40 t = np.asarray(times, dtype=float)
41 P = np.full_like(t, P_TANK)
42 return cfg,
PintleEngineRunner(copy.deepcopy(cfg)).evaluate_arrays_with_time(t, P, P, use_coupled_solver=
True)
45@pytest.fixture(scope="module")
47 os.environ[
"ED_ACCEL"] =
"off"
48 return _series(np.linspace(0.0, 2.0, 9))
53 assert cfg.ablative_cooling.nozzle_ablative
is False
54 A_exit = np.asarray(tv[
"A_exit"])
55 assert np.all(A_exit == A_exit[0])
56 assert np.all(np.asarray(tv[
"recession_exit"]) == 0.0)
58 assert tv[
"eps"][-1] < tv[
"eps"][0]
63 cfg.ablative_cooling.nozzle_ablative =
True
64 _, tv =
_series([0.0, 0.5, 1.0], m)
65 assert tv[
"A_exit"][-1] > tv[
"A_exit"][0]
70 cg = cfg.chamber_geometry
71 assert tv[
"V_chamber"][0] == pytest.approx(cg.volume, rel=1e-12)
72 assert tv[
"Lstar"][0] == pytest.approx(cg.volume / cg.A_throat, rel=1e-12)
78 c2 = copy.deepcopy(cfg)
79 cg = c2.chamber_geometry
80 cg.A_throat = float(tv[
"A_throat"][i])
81 cg.A_exit = float(tv[
"A_exit"][i])
82 cg.expansion_ratio = cg.A_exit / cg.A_throat
83 cg.volume = float(tv[
"V_chamber"][i])
84 cg.Lstar = cg.volume / cg.A_throat
85 cg.chamber_diameter = float(tv[
"D_chamber"][i])
87 assert float(tv[
"Pc"][i]) == pytest.approx(float(steady[
"Pc"]), rel=1e-6)
88 assert float(tv[
"F"][i]) == pytest.approx(float(steady[
"F"]), rel=1e-6)
94 cfg = load_config(CFG)
95 r = R(copy.deepcopy(cfg))
96 s = TimeVaryingCoupledSolver(r.config, r.cea_cache)
97 t = np.array([0.0, 0.25])
98 s.solve_time_series(t, np.full(2, P_TANK), np.full(2, P_TANK))
99 out = s.get_results_dict()
100 for i, d
in enumerate(out[
"diagnostics"]):
101 assert out[
"eta_cstar"][i] == pytest.approx(d[
"eta_cstar"], rel=1e-12)
102 assert out[
"cstar_ideal"][i] == pytest.approx(d[
"cstar_ideal"], rel=1e-12)
103 assert 0.85 < out[
"eta_cstar"][0] < 1.0
and out[
"eta_cstar"][0] != 0.85
105 assert 1690.0 < out[
"cstar_ideal"][0] < 1760.0
111 cfg.graphite_insert.initial_thickness = t
113 _, thin =
_series([0.0, 1.0, 2.0], thick(0.003))
114 _, fat =
_series([0.0, 1.0, 2.0], thick(0.012))
115 assert thin[
"T_graphite_surface"][-1] > fat[
"T_graphite_surface"][-1] + 50.0
116 assert thin[
"T_graphite_back"][-1] > fat[
"T_graphite_back"][-1] + 300.0
117 assert thin[
"recession_graphite"][-1] > fat[
"recession_graphite"][-1]
121 """The cold liner first has to heat to its ablation temperature -- semi-infinite onset
122 t = (pi/4) k rho c (T_abl - T0)^2 / q^2, a few tenths of a second here -- so the first
123 0.25 s recedes far less than steady ablation would; by 2 s it ablates at the Landau rate."""
125 abl = cfg.ablative_cooling
126 q = float(tv[
"q_conv_chamber"][-1] + tv[
"q_rad_chamber"][-1])
127 t_on = math.pi / 4 * abl.thermal_conductivity * abl.material_density * abl.specific_heat * (
128 abl.ablation_surface_temperature - abl.ambient_temperature) ** 2 / q ** 2
129 assert 0.05 < t_on < 1.0
130 assert tv[
"recession_chamber"][1] < 0.3 * tv[
"ablative_recession_rate"][-1] * 0.25
131 assert tv[
"T_liner_surface"][-1] == pytest.approx(abl.ablation_surface_temperature, abs=1e-6)
133 qs = tv[
"diagnostics"][-1][
"cooling"][
"ablative"][
"recession_rate_quasi_steady"]
134 assert tv[
"ablative_recession_rate"][-1] == pytest.approx(qs, rel=0.1)
135 assert 0 < tv[
"char_depth_chamber"][-1] < abl.initial_thickness
140 for k, v
in tv.items():
141 if k.startswith(
"T_")
and k
not in (
"T_exit",):
142 a = np.asarray(v, dtype=float)
143 a = a[np.isfinite(a)]
144 assert np.all((a >= 200.0) & (a <= 4000.0)), k
145 assert not any(k.startswith(
"nozzle_")
for k
in tv)
146 assert not any(
"stainless" in k
for k
in tv)
147 assert np.all(np.asarray(tv[
"heat_flux_throat"]) < 1e8)
153 cfg = load_config(CFG)
154 r = R(copy.deepcopy(cfg))
155 s = TimeVaryingCoupledSolver(r.config, r.cea_cache)
156 t = np.array([0.0, 1.0])
157 s.solve_time_series(t, np.full(2, P_TANK), np.full(2, P_TANK))
158 back_at_burnout = s.get_results_dict()[
"T_graphite_back"][-1]
159 soak = s.soak_back(30.0)
160 assert soak[
"throat"][
"T_back_peak"] >= back_at_burnout
164 """The non-coupled path's helper: a micron of recession must not jump the volume to a
165 full-bore cylinder of cg.length (+8 % on this chamber)."""
167 cg = load_config(CFG).chamber_geometry
168 Dt = math.sqrt(4 * cg.A_throat / math.pi)
169 V, At, Dc, Dt_new, _ = update_chamber_geometry_from_ablation(
170 cg.volume, cg.A_throat, cg.chamber_diameter, Dt, cg.length, 1e-6, recession_thickness_throat=0.0)
171 shell = math.pi * ((cg.chamber_diameter / 2 + 1e-6) ** 2 - (cg.chamber_diameter / 2) ** 2) * cg.length
172 assert V == pytest.approx(cg.volume + shell, rel=1e-12)
test_legacy_geometry_helper_grows_from_the_declared_volume()
test_soak_back_after_shutdown()
_series(times, mutate=None)
test_thrust_is_computed_on_the_geometry_pc_was_solved_on(burn)
test_t0_geometry_is_the_design(burn)
_python_physics(monkeypatch)
test_liner_waits_for_its_ablation_temperature(burn)
test_exit_does_not_recede_on_a_non_ablative_nozzle(burn)
test_solver_reports_the_solved_efficiency()
test_a_thinner_insert_runs_hotter_and_recedes_faster()
test_state_is_physical(burn)
test_exit_recedes_when_the_nozzle_is_ablative()