STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_forward_report.py
Go to the documentation of this file.
1"""Forward mode's report (engine/pipeline/forward_report.py): each quantity once, graded here."""
2import copy
3
4import pytest
5
6PSI = 6894.757
7CFG = "tests/fixtures/ethalox_6500N_doublet_cad_2026-09-28.yaml"
8
9
10@pytest.fixture(scope="module")
11def rep():
12 from engine.pipeline.io import load_config
13 from engine.core.runner import PintleEngineRunner
14 from engine.pipeline.forward_report import forward_report
15 cfg = load_config(CFG)
16 P = cfg.lox_tank.initial_pressure_psi * PSI
17 r = PintleEngineRunner(copy.deepcopy(cfg)).evaluate(P, P, silent=True, rich_stability=True)
18 return r, forward_report(cfg, r)
19
20
22 """A key that appears in more than one place (Pc in the headline and Chamber, theta_c in
23 Chamber and Stability) carries one value everywhere."""
24 _, R = rep
25 seen = {}
26 for q in R["headline"] + [q for s in R["sections"] for q in s["quantities"]]:
27 if q["key"] in seen:
28 assert q["value"] == pytest.approx(seen[q["key"]], rel=1e-12), q["key"]
29 seen[q["key"]] = q["value"]
30 for s in R["sections"]:
31 keys = [q["key"] for q in s["quantities"]]
32 assert len(keys) == len(set(keys)), s["key"]
33 assert set(s["summary"]) <= set(keys)
34
35
37 r, R = rep
38 h = {q["key"]: q["value"] for q in R["headline"]}
39 assert h["F"] == pytest.approx(r["F"] / 1000)
40 assert h["Pc"] == pytest.approx(r["Pc"] / PSI)
41 assert h["OF"] == pytest.approx(r["MR"])
42
43
45 r, R = rep
46 v = {x["key"]: x for x in R["verdicts"]}
47 assert v["chug"]["value"] == pytest.approx(r["stability_rich"]["chug"]["margin"])
48 assert v["chug"]["status"] == "ok" # 1.42 against 1.05
49 assert v["stiffness"]["status"] == "ok" # 33.7 % against 15 %
50 assert all(x["status"] in ("ok", "warn", "bad", "unknown") for x in R["verdicts"])
51
52
54 from engine.pipeline.forward_report import CALIBRATION
55 _, R = rep
56 for q in R["headline"] + R["verdicts"] + [q for s in R["sections"] for q in s["quantities"]]:
57 assert set(q["assumed"]) <= set(CALIBRATION), q["key"]
58 assert set(q["assumed"]) <= set(R["calibration"]), q["key"]
59
60
62 r, R = rep
63 ch = next(s for s in R["sections"] if s["key"] == "chamber")
64 th = next(q for q in ch["quantities"] if q["key"] == "theta_c")
65 assert th["value"] == pytest.approx(r["chamber_intrinsics"]["residence_time"] * 1000)
66 # shown once: the chug model's own theta_c (nozzle-stagnation form, 0.4 % lower) is not a second row
67 keys = [q["key"] for s in R["sections"] for q in s["quantities"]]
68 assert keys.count("theta_c") == 1
69
70
72 """F = Cf · Pc_ns · At and c* = Pc_ns · At / ṁ from the numbers the report shows.
73
74 The bases said Pc where the nozzle uses Pc_ns = Pc / κ, and Pc_ns was not on the page: on the
75 LE4 (3) stand Cf · Pc · At came to 6898 N against the 6870 N shown (+0.41 %), and Pc · At / ṁ to
76 1574 m/s against c* 1568. Nothing from the page closed.
77 """
78 _, R = rep
79 q = {x["key"]: x for s in R["sections"] for x in s["quantities"]}
80 h = {x["key"]: x for x in R["headline"]}
81 pc_ns = q["Pc_ns"]["value"] * PSI
82 At = q["At"]["value"] * 1e-6
83 assert "Pc_ns" in h["F"]["basis"] and "Pc_ns" in q["Cf"]["basis"] and "Pc_ns" in q["cstar"]["basis"]
84 assert h["F"]["value"] * 1000.0 == pytest.approx(q["Cf"]["value"] * pc_ns * At, rel=2e-4)
85 assert q["cstar"]["value"] == pytest.approx(pc_ns * At / h["mdot"]["value"], rel=2e-4)
86 assert q["Pc"]["value"] / q["Pc_ns"]["value"] == pytest.approx(q["kappa"]["value"], rel=1e-9)
87 assert q["kappa"]["value"] > 1.0, "no Rayleigh loss: the case no longer tells Pc from Pc_ns"
test_thrust_and_cstar_close_by_hand_from_the_page(rep)
test_verdicts_are_graded_against_their_thresholds(rep)