STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_layerx_operating_point.py
Go to the documentation of this file.
1"""A set-point, optimiser, uncertainty or Hardware candidate burns at the lockup and fill it asks for.
2
3The rail writes the dome dial (``dome_psia``) and the absolute bottle fill (``copv_pressure_psia``)
4as soon as a person turns either (2026-10-07), and ``prepare`` reads both first: the dome over a
5tank pressure, the absolute fill over a gauge one. Every tool that moves the lockup or the fill
6wrote only ``tank_pressure_psia`` / ``copv_pressure_psig`` on top of the rail's settings, so with
7either knob turned every candidate burned the rail's point: the set point's secant saw a zero slope
8("Mean thrust does not rise with lockup"), the tornado showed no swing for the lockup and the fill,
9and "Put the settings on the rail" merged a patch the rail then ignored (LE4 (3), the rail at dome
10560 psia / fill 3600 psia: candidates at 600 / 500 psia both burned 557.3 psia, 3585.3 psig).
11"""
12
13from __future__ import annotations
14
15from pathlib import Path
16from types import SimpleNamespace
17
18import pytest
19
20pytest.importorskip("feedtwin", reason="lib/feedtwin is not installed")
21
22from engine.layerx import DrawingStore, LayerXSettings, prepare # noqa: E402
23from engine.layerx import diff # noqa: E402
24from engine.layerx import optimize as opt # noqa: E402
25from engine.layerx import setpoint as sp # noqa: E402
26from engine.layerx import uncertainty as unc # noqa: E402
27from engine.layerx.prepare import at_operating_point # noqa: E402
28from engine.layerx.sources import shipped_drawings_dir # noqa: E402
29from engine.pipeline.io import load_config # noqa: E402
30
31GN2 = "copv_study_gn2"
32CFG = Path(__file__).resolve().parent / "fixtures" / "ethalox_6500N_doublet_cad_2026-09-28.yaml"
33needs_drawing = pytest.mark.skipif(not (shipped_drawings_dir() / f"{GN2}.json").is_file(),
34 reason="feed-twin's shipped drawings are not next to this checkout")
35
36
37@pytest.fixture(scope="module")
38def config():
39 return load_config(str(CFG))
40
41
42@pytest.fixture(scope="module")
43def drawing():
44 return {d.name: d for d in DrawingStore(None).list()}[GN2]
45
46
47def _rail(drawing):
48 """The rail after a person turned the dome dial and the bottle knob."""
49 return LayerXSettings(drawing_id=drawing.id, replay=False, engine_model="native", dome_psia=560.0,
50 copv_pressure_psia=3600.0)
51
52
53@needs_drawing
55 rail = prepare(config, None, drawing, _rail(drawing))
56 assert rail.ok and rail.derived["dome_source"] == "set on the rail"
57 for lockup, fill in ((600.0, 3000.0), (500.0, 4200.0)):
58 p = prepare(config, None, drawing, at_operating_point(_rail(drawing), lockup_psia=lockup, fill_psig=fill))
59 assert p.derived["target_lockup_psia"] == pytest.approx(lockup, abs=0.01)
60 assert p.derived["copv_psig"] == pytest.approx(fill)
61 assert p.derived["dome_source"] == "solved for the rail's tank pressure"
62 # Only what the candidate sets is cleared: a lockup alone keeps the rail's fill.
63 only = at_operating_point(_rail(drawing), lockup_psia=600.0)
64 assert only.dome_psia is None and only.copv_pressure_psia == 3600.0
65
66
68 variables = [opt.Variable("lockup_psia", "Tank pressure", "psia", 500.0, 700.0, 600.0),
69 opt.Variable("copv_psig", "Bottle fill", "psig", 3000.0, 4500.0, 4000.0)]
70 st, _ = opt._candidate(LayerXSettings(drawing_id="d", dome_psia=560.0, copv_pressure_psia=3600.0), variables,
71 {"lockup_psia": 610.0, "copv_psig": 3100.0}, None, "impulse", 600.0)
72 assert (st.tank_pressure_psia, st.dome_psia, st.copv_pressure_psig, st.copv_pressure_psia) == (610.0, None, 3100.0, None)
73
74
76 seen = []
77
78 def fake_prepare(config, runner, drawing, settings, overrides=None):
79 seen.append(settings)
80 return SimpleNamespace(ok=False, derived={}, checks=[SimpleNamespace(status="fail", label="stub", detail="")])
81
82 import sys
83
84 # ``engine.layerx.prepare`` the attribute is the function; the module is in sys.modules.
85 monkeypatch.setattr(sys.modules["engine.layerx.prepare"], "prepare", fake_prepare)
86 out = sp.burn_point({"config": None, "drawing": None, "overrides": [], "lockup_psia": 600.0, "fill_psig": 3000.0,
87 "settings": LayerXSettings(drawing_id="d", dome_psia=560.0, copv_pressure_psia=3600.0)})
88 assert out["ok"] is False and len(seen) == 1
89 s = seen[0]
90 assert (s.tank_pressure_psia, s.dome_psia, s.copv_pressure_psig, s.copv_pressure_psia) == (600.0, None, 3000.0, None)
91
92
93@needs_drawing
95 from dataclasses import replace
96
97 nominal = prepare(config, None, drawing, _rail(drawing))
98 found, _ = unc.factors(nominal, config)
99 by = {f.key: f for f in found}
100 lockup, fill = nominal.derived["target_lockup_psia"], nominal.derived["copv_psig"]
101 for case in by["op.lockup"].cases + by["op.fill"].cases:
102 p = prepare(config, None, drawing, replace(_rail(drawing), **case.settings))
103 moved = (p.derived["target_lockup_psia"] - lockup) if case.factor == "op.lockup" else (p.derived["copv_psig"] - fill)
104 assert abs(moved) > 4.0, (case.factor, case.side, moved)
105
106
108 cl = {"changes": [{"target": "op:lockup_psia", "after": 596.5}, {"target": "op:copv_psig", "after": 3200.0},
109 {"target": "op:dome_psig", "after": 540.0}]}
110 assert diff.settings_patch(cl) == {"tank_pressure_psia": 596.5, "dome_psia": None,
111 "copv_pressure_psig": 3200.0, "copv_pressure_psia": None}
test_a_candidate_burns_its_own_lockup_and_fill(config, drawing)
test_the_uncertainty_lockup_and_fill_cases_move_the_burn(config, drawing)