STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_ignore_gse.py
Go to the documentation of this file.
1"""Ignoring the drawn GSE (``Setup.ignore_gse``, ``assemble_model(vehicle_only=)``).
2
3The twin misreads a complicated cart (the operator, 2026-10-08), and the burn
4only needs the rocket. With the option on, everything off the vehicle is cut
5before the build and the stand fills the way a drawing of the rocket alone
6does: the built-in charge, the built-in loads, the dome knob on the tank
7regulator itself. Each check is against what the drawn cart would have done
8instead, so it fails if the cart were still there.
9"""
10
11from __future__ import annotations
12
13import json
14from dataclasses import replace
15from pathlib import Path
16from typing import Any
17
18import pytest
19
20from feedtwin.pid import read_diagram
21from feedtwin.pid.roles import ground_ids, unpaired_vents, vehicle_only, vent_branches
22from feedtwin.session import Setup, assemble_model, load_machine
23from feedtwin.session.statemachine import machine_from_dict
24from feedtwin.session.burn import open_session
25from feedtwin.session.gauge import from_psig, psig
26from feedtwin.session.hookup import DOME, Hookup, on_vehicle, suggest
27
28HERE = Path(__file__).resolve().parent
29DRAWING = HERE / "fixtures" / "le4_rocket_and_gse.json"
30STAR = HERE.parents[2]
31STAND = STAR / "feed-twin" / "backend" / "diagrams" / "ethalox_stand.json"
32TABLES = STAR / "feed-twin" / "backend" / "statemachines"
33
34needs_tables = pytest.mark.skipif(not TABLES.is_dir(), reason="no state machine tables")
35
36
37def _payload() -> Any:
38 return json.loads(DRAWING.read_text())
39
40
41def _ids() -> dict[str, str]:
42 return {n["data"]["label"]: n["id"] for n in _payload()["nodes"]}
43
44
45def _model(cut: bool, payload: Any | None = None) -> Any:
46 return assemble_model(
47 read_diagram(payload or _payload(), name="LE4 (6)"),
48 diagram_id="le4",
49 vehicle_only=cut,
50 )
51
52
53def _session(cut: bool) -> Any:
54 whole = _model(False)
55 model = _model(cut)
56 hookup = suggest(whole, 500.0, 4500.0)
57 if cut:
58 hookup = on_vehicle(hookup, model)
59 return open_session(model, load_machine(tables=TABLES), hookup=hookup)
60
61
63 """Everything off the vehicle goes but the cart's vent line, which stays
64 plugged into the rocket until launch; every other coupling is capped."""
65 ids = _ids()
66 diagram = read_diagram(_payload(), name="LE4 (6)")
67 ground = ground_ids(diagram)
68 vents = vent_branches(diagram)
69 assert vents == {ids[x] for x in ("FV-QD-A", "FV-SOL", "FV-MAN", "junc_o5qqb5_95")}
70 cut, gone = vehicle_only(diagram)
71 kept = {n.id for n in cut.nodes}
72 assert ground and ground & kept == vents
73 assert ground_ids(cut) == vents, "the rocket and its vent line"
74 assert set(gone) == {"LOX-DW-350 PSI", "6K-GN2", "2K-GN2", "Fuel Transfer Tank"}
75 cart = ground - vents
76 assert all(e.source not in cart and e.target not in cart for e in cut.edges)
77 mates = {
78 n.label: n.options.get("pairedWith", "") for n in cut.nodes if n.type == "QD"
79 }
80 assert mates["FV-QD-B"] == ids["FV-QD-A"], "the vent stays coupled"
81 assert not any(
82 m in cart for m in mates.values()
83 ), "no other half paired to the cart"
84 assert ids["COPV"] in kept
85
86 model = _model(True)
87 assert model.meta["vehicle_only"] is True
88 assert set(model.meta["ground_cut"]) == set(gone)
89 # The one coupling left mated is the vent's; nothing on the cart supplies.
90 assert model.built.mated == ((ids["FV-QD-B"], ids["FV-QD-A"]),)
91 assert not model.built.supplies
92
93
95 """On, against a drawing with nothing off the vehicle: the same object."""
96 diagram = read_diagram(_payload(), name="LE4 (6)")
97 cut, _ = vehicle_only(diagram)
98 again, gone = vehicle_only(cut)
99 assert again is cut and gone == ()
100
101
102@pytest.mark.skipif(
103 not (STAND.exists() and TABLES.is_dir()), reason="stand drawing or tables absent"
104)
106 """The shipped stand has no GSE drawn: built with the option on it runs the
107 same pad, number for number, as with it off."""
108
109 def run(cut: bool) -> Any:
110 model = assemble_model(
111 read_diagram(json.loads(STAND.read_text()), name="s"),
112 diagram_id="s",
113 vehicle_only=cut,
114 )
115 session = open_session(
116 model, load_machine(tables=TABLES), setup=Setup(ignore_gse=cut)
117 )
118 for state, steps in (("GN2 High Press", 10), ("Fuel Fill", 10), ("Ready", 10)):
119 session.state = state
120 for _ in range(steps):
121 session.step(0.5)
122 return session
123
124 on, off = run(True), run(False)
125 assert on.setup.ignore_gse and not off.setup.ignore_gse
126 assert on.model.diagram.nodes == off.model.diagram.nodes
127 for a, b in zip(on.history, off.history, strict=True):
128 assert a.pressures == b.pressures
129 assert a.signals == b.signals
130
131
132@needs_tables
134 """GN2 High Press charges the COPV to the COPV fill setting by the built-in
135 charge, Fuel Fill pours the built-in load, and the dome knob sets DPR_HP's
136 dome itself: the tanks lock up at it with no GN2 Low Press. With the cart
137 simulated, the COPV fills through PR-1, the fuel through the transfer tank,
138 and the dome stays unloaded until GN2 Low Press -- the tanks hold the spring
139 bias less the supply effect, which on a charged COPV is shut."""
140 ids = _ids()
141 for cut in (True, False):
142 session = _session(cut)
143 session.setup = replace(session.setup, copv_target_psi=4000.0, dome_psi=450.0)
144 copv = session.bottles[ids["COPV"]]
145 fuel = session.tanks[ids["Eth-Tank"]]
146 assert session.setup.ignore_gse is cut
147
148 session.state = "GN2 High Press"
149 for _ in range(40):
150 session.step(0.5)
151 assert psig(copv.pressure) == pytest.approx(4000.0, abs=40.0)
152 assert (ids["COPV"] in session._drawn_fill) is not cut
153
154 session.state = "Fuel Fill"
155 for _ in range(60):
156 session.step(0.5)
157 assert fuel.state.liquid_mass == pytest.approx(fuel._wanted(), rel=0.02)
158 assert (ids["Eth-Tank"] in session._drawn_fill) is not cut
159
160 session.state = "Fuel Press"
161 for _ in range(30):
162 session.step(0.5)
163 if not cut:
164 assert psig(fuel.pressure) < 5.0, "the drawn dome is not loaded yet"
165 continue
166 lockup = 450.0 + 50.0 - 14.7e-3 * psig(copv.pressure)
167 assert psig(fuel.pressure) == pytest.approx(lockup, abs=8.0)
168 dome_pt = next(i for i in session.model.built.instruments if i.tag == "DP-PT-R")
169 assert psig(session.history[-1].pressures[dome_pt.node]) == pytest.approx(450.0)
170 assert any("drawn GSE is ignored" in a for a in session.assumptions)
171
172
173@needs_tables
175 """The built-in charge and the network's draw land in one step: dm/dt =
176 charge - draw. The charge branch returned before the draw was applied, so a
177 press valve opened during GN2 High Press filled the LOX ullage from gas the
178 COPV never lost -- 290 g of nitrogen from nothing on LE4 (3), booked by no
179 vessel and opened by the watcher as "Mass not accounted for"."""
180 ids = _ids()
181 session = _session(True)
182 copv = session.bottles[ids["COPV"]]
183 lox = session.tanks[ids["LOX-Tank"]]
184 session.state = "GN2 High Press"
185 for _ in range(4):
186 session.step(0.5)
187 assert copv.filling
188 session.set_valve(session.binding.to_symbol["LOX Press"], True)
189 before = lox.state.ullage.mass
190 for _ in range(8):
191 session.step(0.5)
192 assert copv.filling, "still charging: the draw happened during the charge"
193 assert lox.state.ullage.mass - before > 0.2, "the press valve drew gas"
194 last = session.solver_log[-1]
195 assert abs(last.mass_error_kg - last.guard_kg) < 1e-6
196
197
198@needs_tables
200 """GN2 High Press from empty: the COPV climbs, and the console said it was
201 "down to 107 psia, 3% of what it was filled to" on every tick of the first
202 quarter of the charge. A bottle is down only from what it has held."""
203 ids = _ids()
204 session = _session(True)
205 session.setup = replace(session.setup, copv_target_psi=4000.0)
206 copv = session.bottles[ids["COPV"]]
207 session.state = "GN2 High Press"
208 for _ in range(12): # 3 s: past 2 atm, short of a quarter of the target
209 notes = session.step(0.25).notes
210 assert not [n for n in notes if "down to" in n], notes
211 assert copv.charged and psig(copv.pressure) < 0.25 * 4000.0 + 200.0
212 for _ in range(40): # to the target
213 session.step(0.5)
214 assert psig(copv.pressure) == pytest.approx(4000.0, abs=40.0)
215 # Blown down to a tenth of it, it is down to something, and says from what.
216 session.state = "Press Standby"
217 copv.state = copv.volume.initial_state(
218 pressure=from_psig(400.0), temperature=copv.state.wall_temperature
219 )
220 (low,) = [n for n in session.step(0.02).notes if "down to" in n]
221 assert low.startswith("COPV is down to 415 psia, 10% of the 4,01"), low
222 # Dumped and charged again, part way, then paused: it has only gained
223 # since the dump. What it held before is not what this charge filled it
224 # to, and "down to 8xx psia, 2x% of the 4,01x psia" read as a leak.
225 copv.state = copv.volume.initial_state(
226 pressure=from_psig(0.0), temperature=copv.state.wall_temperature
227 )
228 session.state = "GN2 High Press"
229 for _ in range(8): # 2 s: a few hundred psi, short of a quarter
230 session.step(0.25)
231 assert 2.0 * 14.7 < psig(copv.pressure) < 0.25 * 4000.0
232 session.state = "Press Standby"
233 for _ in range(20): # it cools from the charge and sags
234 notes = session.step(0.25).notes
235 assert not [n for n in notes if "down to" in n], notes
236
237
238@needs_tables
240 """Emergency Abort opens the built-in COPV dump. On a bottle already at the
241 air it read 14.4 psia: the dump kept taking a ninth of the gas a second, the
242 floor put it back at the wall's density with none of its energy, and every
243 step the gas cooled and the bottle sank under atmosphere. A bottle open to
244 the air reads the air, and nothing flows out of it."""
245 ids = _ids()
246 session = _session(True)
247 copv = session.bottles[ids["COPV"]]
248 session.state = "Emergency Abort"
249 session.step(0.02)
250 assert copv.venting, "the abort row opens the built-in dump"
251 held = copv.state.mass
252 for _ in range(40): # 2 s
253 session.step(0.05)
254 assert psig(copv.pressure) == pytest.approx(0.0, abs=0.02)
255 assert copv.volume.temperature(copv.state) == pytest.approx(293.0, abs=1.0)
256 assert copv.state.mass == pytest.approx(held, rel=1e-3)
257
258
259@needs_tables
261 """The floor itself, apart from the dump: a bottle at atmosphere that a
262 step draws 5 % of its gas from is put back at an atmosphere, mass and
263 energy together. Floored at the wall's density with the energy left as it
264 was, it kept the mass and lost the heat that left with it, and read a psi
265 under the air after one step (the dump test above passes on the old floor
266 once the dump stops at the air: this is the floor's own check)."""
267 ids = _ids()
268 copv = _session(True).bottles[ids["COPV"]]
269 assert psig(copv.pressure) == pytest.approx(0.0, abs=1e-3), "starts at the air"
270 held = copv.state.mass
271 for _ in range(3):
272 copv.advance(0.02, mdot_out=0.05 * held / 0.02)
273 assert psig(copv.pressure) == pytest.approx(0.0, abs=1e-3)
274 # The floor books what it put back.
275 assert copv.fixed_kg == pytest.approx(copv.state.mass - held + 0.15 * held)
276
277
278@needs_tables
280 """T-0 sets the bottle; it does not reach it. A bottle charged to 4,000
281 psig and then primed at 400 psig (a Study case, a second T-0 at a lower
282 charge) kept the 4,000 as the most it had held, and the console read
283 "down to 415 psia, 10% of the 4,015 psia it was filled to" of gas nobody had
284 taken out of it."""
285 ids = _ids()
286 session = _session(True)
287 copv = session.bottles[ids["COPV"]]
288 session.prime(fill_fraction=0.5, tank_psi=500.0, copv_psi=4000.0, state="Ready")
289 session.step(0.02)
290 assert copv.peak == pytest.approx(from_psig(4000.0), rel=1e-5)
291 session.prime(fill_fraction=0.5, tank_psi=300.0, copv_psi=400.0, state="Ready")
292 assert copv.pressure < 0.25 * copv.target, "low enough for the note"
293 notes = session.step(0.02).notes
294 assert not [n for n in notes if "down to" in n], notes
295 # Nor once a burn has drawn a little of it: a bottle that never held a
296 # quarter of its target is not "down to" anything a psi under its prime
297 # ("down to 414 psia, 100% of the 414.7 psia it was filled to").
298 copv.state = copv.volume.initial_state(
299 pressure=from_psig(399.0), temperature=copv.state.wall_temperature
300 )
301 assert copv.pressure < copv.peak
302 notes = session.step(0.02).notes
303 assert not [n for n in notes if "down to" in n], notes
304
305
306@needs_tables
308 """The built-in load raised the liquid and shrank the ullage with no p dV
309 work on the gas; the vent then took the displaced gas at its enthalpy, and
310 what stayed behind lost p v per kilogram vented: the fuel ullage cooled
311 293 -> 285 K during a 15 s pour onto a 293 K tank at atmosphere. By hand,
312 an adiabatic displacement at constant pressure leaves the gas where it
313 was -- the liquid's work on the gas pays the flow work leaving."""
314 ids = _ids()
315 session = _session(True)
316 fuel = session.tanks[ids["Eth-Tank"]]
317 session.state = "Fuel Fill"
318 coldest = 293.15
319 for _ in range(36): # 18 s: the whole pour
320 session.step(0.5)
321 coldest = min(coldest, fuel.tank.gas_temperature(fuel.state))
322 assert fuel.state.liquid_mass > 3.0, "poured"
323 assert psig(fuel.pressure) == pytest.approx(0.0, abs=0.1), "on its vent"
324 assert coldest > 292.0, f"the displaced ullage cooled to {coldest:.2f} K"
325
326
327@needs_tables
329 """A LOX load on its vent, chilled: the nitrogen ullage sits between the
330 90 K liquid and the 92 K wall, gas leaves through the vent, and no guard
331 books anything. Each time the tank dipped a hundredth of a psi under the
332 air, the floor restated the ullage's energy at a whole atmosphere of
333 nitrogen while it shared the tank with oxygen vapour -- colder, by
334 nitrogen's departure from ideal near its dew line -- and the ratchet took
335 the ullage to 77 K, drew ~1 g/s of "air" in through a vent blowing out, and
336 booked hundreds of grams as clamps: the Solver tab failed three of five
337 checks after every pad run (LE4 (3): 77.4 K, 0.70 kg drawn, -0.60 kg)."""
338 ids = _ids()
339 session = _session(True)
340 lox = session.tanks[ids["LOX-Tank"]]
341 session.state = "Ox Fill"
342 session.step(0.25)
343 assert session.skip_chilldown() == ["LOX-Tank"]
344 coldest = float("inf")
345 for _ in range(200): # 50 s of the load
346 session.step(0.25)
347 state = lox.state
348 surfaces = min(
349 state.ullage.wall_temperature,
350 state.surface_temperature or state.liquid_temperature,
351 )
352 coldest = min(coldest, lox.tank.gas_temperature(state) - surfaces)
353 assert lox.state.liquid_mass > 0.5, "loading"
354 assert coldest > -0.5, f"the ullage went {-coldest:.1f} K under its surfaces"
355 assert abs(lox.fixed_kg) < 1e-4, lox.fixed_kg
356 assert lox.vent_drawn_kg < 1e-3, "air drawn in through a vent blowing out"
357 last = session.solver_log[-1]
358 assert abs(last.guard_kg) < 1e-3
359
360
361@needs_tables
363 bound = _session(True).binding.to_symbol
364 assert "GSE High Press Control" not in bound, "the built-in charge's command"
365 assert "GSE Med Press Control" not in bound
366 assert {"Fuel Press", "LOX Press", "Fuel Main", "LOX Main"} <= set(bound)
367
368
370 ids = _ids()
371 whole, model = _model(False), _model(True)
372 saved = Hookup(
373 valves={
374 "Fuel Main": ids["FM-R"],
375 "GSE High Press Control": ids["HPC_SOL"],
376 "LOX Dump": "",
377 },
378 knobs=suggest(whole, 500.0, 4500.0).knobs,
379 )
380 cut = on_vehicle(saved, model)
381 assert cut.valves == {"Fuel Main": ids["FM-R"], "LOX Dump": ""}
382 assert [(k.id, k.regulators) for k in cut.knobs] == [(DOME, (ids["DPR_HP"],))]
383
384
385@needs_tables
387 machine = load_machine(tables=TABLES)
388 whole = open_session(_model(False), machine, setup=Setup(ignore_gse=True))
389 assert whole.setup.ignore_gse is False and not whole.gse_ignored
390 cut = open_session(_model(True), machine, setup=Setup())
391 assert cut.setup.ignore_gse is True and cut.gse_ignored
392
393
394@needs_tables
396 """The cart's vents are a small part of the GSE that stays plugged into the
397 rocket until the last moment before launch (the team, 2026-10-10). Rocket
398 only, the fuel tank's vent coupling stays mated to the cart's vent line,
399 Fuel Vent drives the cart's FV-SOL -- not a stand-in on the capped
400 coupling -- and venting through it empties the pressed tank."""
401 ids = _ids()
402 model = _model(True)
403 assert ids["FV-QD-B"] not in model.meta["capped"]
404 session = _session(True)
405 assert session.binding.to_symbol["Fuel Vent"] == ids["FV-SOL"]
406 fuel = session.tanks[ids["Eth-Tank"]]
407 for state, steps in (("GN2 High Press", 30), ("Fuel Fill", 40), ("Fuel Press", 20)):
408 session.state = state
409 for _ in range(steps):
410 session.step(0.5)
411 pressed = psig(fuel.pressure)
412 assert pressed > 400.0
413 session.state = "Fuel Vent"
414 for _ in range(20):
415 session.step(0.5)
416 assert psig(fuel.pressure) < 0.1 * pressed, "it vents through the cart's line"
417
418
420 """LE4 (6) draws the LOX vent's halves -- the rocket's QD-OV-B and the
421 cart's OV-QD-A in front of OV-MOT -- paired with nothing. The twin does
422 not guess the pair; it says so. Paired, the warning goes and the LOX vent
423 line stays plugged into the rocket only like the fuel's."""
424 payload = _payload()
425 ids = _ids()
426 warned = unpaired_vents(read_diagram(payload, name="LE4 (6)"))
427 assert len(warned) == 1 and "QD-OV-B" in warned[0] and "OV-MOT" in warned[0]
428 assert warned[0] in _model(False).report.warnings
429 for node in payload["nodes"]:
430 label = node["data"]["label"]
431 if label in ("QD-OV-B", "OV-QD-A"):
432 other = "OV-QD-A" if label == "QD-OV-B" else "QD-OV-B"
433 node["data"].setdefault("options", {})["pairedWith"] = ids[other]
434 paired = read_diagram(payload, name="LE4 (6) paired")
435 assert unpaired_vents(paired) == []
436 assert {ids["OV-QD-A"], ids["OV-MOT"]} <= vent_branches(paired)
437 cut = _model(True, payload)
438 assert ids["QD-OV-B"] not in cut.meta["capped"]
439
440
442 """``gas_swap`` replaces the pressurant wherever it is declared: the
443 network's bottles, press lines and the ullages they fill hold helium; the
444 tanks keep their propellants. Without it, the drawing's nitrogen."""
445 from feedtwin.session.model import swap_gases
446
447 ids = _ids()
448 diagram = read_diagram(_payload(), name="LE4 (6)")
449 swapped = swap_gases(diagram, {"nitrogen": "helium"})
450 by_id = {n.id: n for n in swapped.nodes}
451 assert by_id[ids["COPV"]].fluid == "helium"
452 assert by_id[ids["Eth-Tank"]].fluid == next(
453 n.fluid for n in diagram.nodes if n.id == ids["Eth-Tank"]
454 )
455 assert not any(n.fluid == "nitrogen" for n in swapped.nodes)
456 for gas in ("nitrogen", "helium"):
457 model = assemble_model(
458 read_diagram(_payload(), name="LE4 (6)"),
459 diagram_id="le4",
460 gas_swap=None if gas == "nitrogen" else {"nitrogen": gas},
461 )
462 nodes = model.built.network.nodes
463 assert nodes[ids["COPV"]].fluid == gas
464 # The ullages it presses (a tank's node is its ullage; ``.out`` its
465 # liquid), and the tanks still hold their propellants.
466 for tank in ("Eth-Tank", "LOX-Tank"):
467 assert nodes[ids[tank]].fluid == gas
468 assert nodes[f"{ids[tank]}.out"].fluid in ("ethanol", "oxygen")
469
470
472 """The shipped table with a "Dome Control" row open only in a "Dome Press"
473 state, reached from Press Standby -- a stand's own table (the operator,
474 2026-11-01)."""
475 table = load_machine(tables=TABLES).to_dict()
476 table["states"].append({"name": "Dome Press", "row": 2, "col": 5, "abort": False})
477 table["actuators"].append("Dome Control")
478 table["open"]["Dome Press"] = ["Dome Control"]
479 standby = table["allowed"]["Press Standby"]
480 standby.append("Dome Press")
481 aborts = [s["name"] for s in table["states"] if s["abort"]]
482 table["allowed"]["Dome Press"] = ["Press Standby", "Dome Press"] + [
483 a for a in aborts if a in standby
484 ]
485 return machine_from_dict(table)
486
487
489 """Rocket only, DR-CTRL-R sits on DPR_HP's dome line between the dome port
490 and the capped DR-QD-B. The loader went with the cart, so the dome line was
491 not found, and the solenoid was reported as a vent to atmosphere with its
492 dome side -- where DR-PT-R reads -- pinned there. The cart's vent line
493 stays a vent."""
494 ids = _ids()
495 cut = _model(True).built
496 assert not [w for w in cut.warnings if "venting" in w and "DR-CTRL-R" in w]
497 branch = cut.network.branches[ids["DR-CTRL-R"]]
498 for end in (branch.upstream, branch.downstream):
499 assert cut.network.nodes[end].pressure is None, end
500 assert [w for w in cut.warnings if "venting" in w and "FV-SOL" in w]
501
502
503def _dome(session: Any) -> float:
504 """The dome the rocket's regulator reads now [psig]."""
505 signal = session.model.built.actuators[_ids()["DPR_HP"]]
506 return psig(session.peek_signals()[signal])
507
508
509@needs_tables
511 """Rocket only, the dome comes in through the capped DR-QD-B and the
512 rocket's DR-CTRL-R gates it -- but no row of the shipped table drives
513 DR-CTRL-R, so it passes and the stand runs bit for bit as it did with no
514 gate at all."""
515 gated = _session(True)
516 assert set(gated._cut_dome_lines) == {_ids()["DPR_HP"]}
517 plain = _session(True)
518 plain._cut_dome_lines = {}
519 for session in (gated, plain):
520 for state, steps in (("Idle", 4), ("GN2 High Press", 20), ("Fuel Press", 20)):
521 session.state = state
522 for _ in range(steps):
523 session.step(0.5)
524 fuel = _ids()["Eth-Tank"]
525 assert _dome(gated) == pytest.approx(500.0)
526 assert gated.tanks[fuel].pressure == plain.tanks[fuel].pressure
527 assert gated.tanks[fuel].pressure > from_psig(400.0)
528
529
530@needs_tables
532 """With DR-CTRL-R on a "Dome Control" row open only in "Dome Press", the
533 rocket's dome sits at atmosphere until Dome Press opens the line to the
534 cart's dome supply (the knob), and holds what it took once the line shuts:
535 a tank pressed before it presses nothing, one pressed after reaches
536 lockup."""
537 ids = _ids()
538 whole = _model(False)
539 model = _model(True)
540 hookup = replace(
541 suggest(whole, 500.0, 4500.0), valves={"Dome Control": ids["DR-CTRL-R"]}
542 )
543 session = open_session(
544 model, _dome_press_machine(), hookup=on_vehicle(hookup, model)
545 )
546 assert session.binding.to_symbol["Dome Control"] == ids["DR-CTRL-R"]
547 fuel = session.tanks[ids["Eth-Tank"]]
548 for state, steps in (("Idle", 4), ("GN2 High Press", 20), ("Fuel Press", 20)):
549 session.state = state
550 for _ in range(steps):
551 session.step(0.5)
552 assert _dome(session) == pytest.approx(0.0, abs=1e-9)
553 assert psig(fuel.pressure) < 100.0, "an unloaded regulator is shut"
554 session.state = "Dome Press"
555 session.step(0.5)
556 assert _dome(session) == pytest.approx(500.0)
557 session.state = "Press Standby"
558 session.step(0.5)
559 assert _dome(session) == pytest.approx(500.0), "shut in, it holds"
560 session.state = "Fuel Press"
561 for _ in range(20):
562 session.step(0.5)
563 assert psig(fuel.pressure) > 400.0
#define min(a, b)
Definition Arduino.h:42
Definition run.py:1
Any _model(bool cut, Any|None payload=None)
Any _session(bool cut)
None test_a_vented_lox_load_keeps_its_ullage_over_the_liquid()
None test_a_bottle_at_the_air_drawn_on_reads_the_air()
dict[str, str] _ids()
None test_a_primed_bottle_is_down_from_its_prime()
None test_on_a_one_page_drawing_it_changes_nothing()
None test_a_dome_press_state_loads_the_dome_and_it_holds()
None test_a_bottle_charging_is_not_down_to_anything()
None test_a_poured_load_displaces_the_ullage_without_cooling_it()
None test_an_undriven_dome_valve_leaves_the_rocket_only_dome_as_it_was()
None test_a_bottle_dumped_at_atmosphere_stays_at_atmosphere()
None test_the_cart_is_cut_and_its_disconnects_capped()
None test_the_cart_actuators_drive_nothing()
None test_a_gas_swap_puts_helium_in_the_bottles_lines_and_ullages()
None test_the_cart_vent_stays_plugged_in_and_vents_the_rocket()
float _dome(Any session)
None test_a_drawing_of_the_rocket_alone_is_not_touched()
None test_the_rocket_fills_at_the_settings_not_through_the_cart()
None test_the_rockets_dome_valve_is_no_vent_with_the_cart_cut()
None test_the_setup_says_what_the_stand_was_built_from()
None test_a_bottle_being_charged_gives_what_the_network_draws()
None test_a_vent_drawn_unpaired_is_said_and_once_paired_it_stays()
None test_the_hookup_keeps_the_vehicle_and_drops_the_cart()