STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
feedtwin.session.core.Session Class Reference
Collaboration diagram for feedtwin.session.core.Session:
[legend]

Public Member Functions

None __init__ (self, Model model, StateMachine machine, Binding binding, *, str state="Idle", Setup|None setup=None, Hookup|None hookup=None)
frozenset[str] ground (self)
dict[str, float] fire_loads (self)
list[str] short_loads (self)
tuple[str,...] vehicle_tanks (self)
dict[str, str] builtin (self)
None prime (self, *, float fill_fraction=0.95, float tank_psi=500.0, float copv_psi=0.0, str state="Ready", float hold_s=PAD_HOLD_S, Mapping[str, float]|None loads=None)
None apply_thermal (self)
None command_state (self, str state)
list[str] operator_held (self)
None set_valve (self, str drawing_id, bool is_open)
None release (self, str drawing_id="")
list[str] skip_chilldown (self, str tank_id="")
dict[str, float] signals (self, float dt=0.0)
dict[str, float] peek_signals (self, *, bool loaded_dome=False)
float inventory (self)
None reset_balance (self)
Sample step (self, float dt)
bool lit_this_state (self)

Public Attributes

 id = uuid.uuid4().hex[:12]
 model = model
 machine = machine
 binding = binding
 hookup = hookup
tuple knobs
str state = state if state in machine.states else machine.states[0]
 gse_ignored = bool(model.meta.get("vehicle_only"))
 setup = setup or Setup()
dict forced = {}
str|None tripped = None
Trip|None trip = None
str|None burnout = None
float t = 0.0
 wall = time.monotonic()
Deque[Sample] history = deque(maxlen=HISTORY)
Deque[SolverRecord] solver_log = deque(maxlen=HISTORY)
 watcher = Watcher()
list assumptions = []
dict tanks = {}
dict bottles = {}
 operator_held
 ground
dict walls = {}
dict wall_temperature = {}
dict arriving_enthalpy = {}

Protected Member Functions

frozenset[str] _find_drawn_fills (self)
list[PidNode] _cut_dome_regulators (self)
dict[str, frozenset[str]] _find_cut_dome_ports (self)
dict[str, DomeLine] _find_cut_dome_lines (self)
None _build_vessels (self)
float|None _ullage_wall_T0 (self, str tank_id, TankSim sim)
float _loader_supply (self, object loader)
Callable[[], GasFilm]|None _film_estimator (self, Fluid gas, object node, CylindricalTank geometry)
dict[str, Any] _tank_wall_defaults (self)
dict[str, Any] _bottle_wall_defaults (self)
float _gated_dome (self, DomeLine|None line, float live, Mapping[str, float] commanded)
float _through_loader (self, DomeLoader loader, float psig_set)
float _slew (self, str drawing_id, float target, float dt)
float _travel_time (self, str drawing_id)
frozenset[str] _through_outlets (self)
frozenset[str] _dry_branches (self)
bool _firing (self)
None _rest_ground (self, Mapping[str, float] signals)
tuple[frozenset[str], frozenset[str]] _unreachable_ground (self, frozenset[str] shut)
float _coupling_timescale (self, Mapping[str, float]|None signals=None, float dt=0.0, bool press=True)
dict[str, float] _regulator_slopes (self, frozenset[str] shut)
float _press_path_timescale (self, TankSim sim, float capacitance, Mapping[str, float]|None signals, float dt=0.0)
dict[str, float] _vessel_pressures (self)
dict[str, tuple[float, float]] _ullage_storage (self, float dt, frozenset[str] isolated)
None _close_outlet (self, TankSim sim, float dt, set[str] cut, dict[str, tuple[float, float]] out)
SteadyResult _advance_once (self, Network net, Mapping[str, float] signals, float dt, int substeps=SUBSTEPS)
float _dry_cut (self, Mapping[str, float] flows, float dt)
None _note_solve (self, SteadyResult result)
frozenset[str] _boundary_nodes (self)
tuple[float, float, float] _ledger_totals (self)
None _record_solver (self, int couplings)
dict[str, float] _relief_signals (self, Network net, Mapping[str, float] signals, Mapping[str, float]|None pressures=None)
None _accept (self, SteadyResult result)
SteadyResult _solve (self, Network net, Mapping[str, float] signals, frozenset[str] isolate, Mapping[str, tuple[float, float]]|None storage)
Mapping[str, float] _held (self, SteadyResult result)
SteadyResult _close_chamber (self, Network net, Mapping[str, float] signals, frozenset[str] dry, SteadyResult result, Mapping[str, tuple[float, float]]|None storage=None)
None _move_vessels (self, Mapping[str, float] flows, Mapping[str, float] pressures, float dt, int substeps=SUBSTEPS)
dict[str, str] _find_fill_stops (self)
bool _loading (self, TankSim sim)
None _stop_full_loads (self)
tuple[float, float, float] _supply_press (self, TankSim sim, float dt=0.0)
float|None _arriving_liquid_T (self, TankSim sim, Mapping[str, float] flows)
frozenset[str] _sealed (self, frozenset[str] isolated)
bool _venting (self, str node, Mapping[str, float] flows)
float _vent_fraction (self, str node, Mapping[str, float] flows)
None _apply_vessel_pressures (self)
dict[str, str] _find_fill_stubs (self)
list[str] _leaking_notes (self)
tuple[float, float] _split_at (self, str node, Mapping[str, float] flows)
tuple[float, float] _outlet_split (self, TankSim sim, Mapping[str, float] flows, frozenset[str]|None through=None)
float _net_into (self, str node, Mapping[str, float] flows)
Sample _integrate (self, float dt)
float _supply_to (self, TankSim sim, Mapping[str, float] pressures)
float _pressurant_enthalpy (self, TankSim sim)
float _bottle_inflow_enthalpy (self, BottleSim bottle)
BottleSim|None _bottle_for (self, TankSim sim)
bool _fills (self, TankSim sim)
None _build_line_walls (self)
float _off_the_dew_line (self, Fluid fluid, float pressure, float enthalpy, float landed, str node_id)
None _propagate_temperatures (self, Mapping[str, float] pressures, Mapping[str, float] flows, float dt=0.0)
None _chamber (self, Mapping[str, float] flows)
None _burnout_check (self)
list[str] _fire_notes (self)
None _check_limits (self)
list[tuple[str, Any]] _reliefs_on (self, str vessel)
list[str] _notes (self)

Protected Attributes

dict _positions = {}
dict _dome_held = {}
dict _dome_readings = {}
bool _dome_primed = False
dict _travel = {}
Sample|None _shown = None
float|None _lit_at = None
float _state_since = 0.0
dict _tick = {}
frozenset[str]|None _boundary = None
float _crossed_in = 0.0
float _crossed_out = 0.0
float|None _inventory0 = None
tuple _ledger0 = (0.0, 0.0, 0.0)
float _guard_J0 = 0.0
dict _last_flows = {}
dict _last_pressures = {}
dict _guess = {}
float _last_change = 0.0
float _last_dt = 1.0
frozenset[str] _last_isolated = frozenset()
tuple _resting
dict[str, float]|None _fire_loads = None
tuple[ _resting_memo
tuple _regulator_memo = (None, {})
 _chamber_guess = AMBIENT
ChamberResult|None _last_chamber = None
str _chamber_node = ""
 _design_chamber = AMBIENT
dict _trapped = {}
dict[str, str] _fill_stubs = self._find_fill_stubs()
frozenset[str] _fill_hoses
frozenset[str] _drawn_fill = self._find_drawn_fills()
tuple _dome_ports
tuple _cut_dome_lines
frozenset[str] _liquid_fed
dict _fill_lines = {}
dict[str, str] _fill_stops = self._find_fill_stops()
set[str] _fill_stopped = set()
dict _sealed_cache = {}
dict _fill_crew = {}
list _reliefs
dict _relief_open = {}
dict _relief_lift = {}

Detailed Description

A stand, live. Built once, ticked forever.

The state machine commands valves, the valves decide what is connected, the
network decides the flows and the vessels decide what that does to their
contents. Nothing here scripts a pressure curve; every number on screen is
the consequence of the previous tick.

Definition at line 2269 of file core.py.

Constructor & Destructor Documentation

◆ __init__()

None feedtwin.session.core.Session.__init__ ( self,
Model model,
StateMachine machine,
Binding binding,
* ,
str state = "Idle",
Setup | None setup = None,
Hookup | None hookup = None )

Definition at line 2278 of file core.py.

Member Function Documentation

◆ _accept()

None feedtwin.session.core.Session._accept ( self,
SteadyResult result )
protected
Keep a converged solve as the next one's starting point.

Definition at line 4239 of file core.py.

References _guess, _last_flows, and _last_pressures.

Referenced by _advance_once(), and _close_chamber().

◆ _advance_once()

SteadyResult feedtwin.session.core.Session._advance_once ( self,
Network net,
Mapping[str, float] signals,
float dt,
int substeps = SUBSTEPS )
protected

◆ _apply_vessel_pressures()

None feedtwin.session.core.Session._apply_vessel_pressures ( self)
protected

◆ _arriving_liquid_T()

float | None feedtwin.session.core.Session._arriving_liquid_T ( self,
TankSim sim,
Mapping[str, float] flows )
protected
Mass-weighted temperature of what arrives at a tank's outlet [K].

Definition at line 4743 of file core.py.

References model.

Referenced by _move_vessels().

◆ _bottle_for()

BottleSim | None feedtwin.session.core.Session._bottle_for ( self,
TankSim sim )
protected
The bottle feeding this ullage, if one is reachable.

Matched on species rather than walked: a stand has one pressurant, and a
walk would have to cross the regulator's dome branch to find it.

Definition at line 5328 of file core.py.

References feedtwin.session.burn.Probes.bottles, bottles, anonymous_namespace{idle_e2e.cpp}::World.ground, and ground.

Referenced by _pressurant_enthalpy().

◆ _bottle_inflow_enthalpy()

float feedtwin.session.core.Session._bottle_inflow_enthalpy ( self,
BottleSim bottle )
protected
Specific enthalpy of gas the drawing delivers into a bottle [J/kg]:
the walk's arrival at its node, else cart gas at the fill temperature.

Definition at line 5316 of file core.py.

References arriving_enthalpy, max, anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _move_vessels().

◆ _bottle_wall_defaults()

dict[str, Any] feedtwin.session.core.Session._bottle_wall_defaults ( self)
protected

Definition at line 3104 of file core.py.

References anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _build_vessels().

◆ _boundary_nodes()

frozenset[str] feedtwin.session.core.Session._boundary_nodes ( self)
protected

◆ _build_line_walls()

None feedtwin.session.core.Session._build_line_walls ( self)
protected
One lumped wall per line, started at the temperature its line holds.

This is the whole of the "soak" question, answered without a soak model.
A wall left to sit converges on the temperature of the fluid inside it,
so that is where it starts -- and it falls out per line for free:

* a line full of liquid oxygen has been sitting near 90 K, so its
  fittings are cold and have little heat to give;
* the ullage side above that tank holds cold vapour, cooler than the room
  and nowhere near the liquid;
* a pressurant line that has only ever seen ambient gas is at ambient,
  and is the one with real heat in it.

Three very different wall temperatures, none of them assumed. Tracking
how a stand drifts while it sits would need an ambient boundary, an
insulation state and an hours-long clock on every line, and would land
in the same place this does.

Definition at line 5359 of file core.py.

◆ _build_vessels()

None feedtwin.session.core.Session._build_vessels ( self)
protected
Turn the drawing's tanks and bottles into integrable vessels.

Tanks start **empty and at atmosphere** whatever the drawing's pressure
field says. That field is a design pressure, not a starting condition,
and starting a simulator at the answer is the thing that made the last
one useless.

Definition at line 2719 of file core.py.

References feedtwin.session.core._bottle_geometry(), _bottle_wall_defaults(), _film_estimator(), feedtwin.session.core._not_a_liquid_tank(), feedtwin.session.core._skin_conductance(), feedtwin.session.core._tank_geometry(), _tank_wall_defaults(), feedtwin.session.core._trip_limit(), feedtwin.session.core._vessel_wall(), assumptions, feedtwin.session.burn.Probes.bottles, bottles, anonymous_namespace{idle_e2e.cpp}::World.ground, ground, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

◆ _burnout_check()

None feedtwin.session.core.Session._burnout_check ( self)
protected
Burnout: a tank ran dry during Fire, so the sequence goes to Vent.

The stand's fire is timed and ends in Vent; the twin's ends when the
propellant does. Without this the mains stayed open on empty tanks and
both sat at lockup with the regulator holding them there, which the
operator rightly read as "nothing vented". Vent is what the table
says it is: both tank vents, both press valves, the manifold vent.

Definition at line 5710 of file core.py.

References _state_since, burnout, command_state(), lit_this_state(), machine, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, anonymous_namespace{idle_e2e.cpp}::Beat.state, anonymous_namespace{idle_e2e.cpp}::World.state, battery_t.state, feedtwin.session.core.BottleSim.state, state, feedtwin.session.core.TankSim.state, gnss_core_t.state, gnss_frame_parser_t.state, SensorHotfire::CoreState.state, Valve.state, world_t.state, anonymous_namespace{idle_e2e.cpp}::Beat.t, anonymous_namespace{idle_e2e.cpp}::World::Ground.t, feedtwin.model.component.EvalContext.t, feedtwin.session.burn.BurnTrace.t, t, gui.App.t, lc_calibration_gui.LCPlotWindow.t, rec_t.t, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, tanks, and vehicle_tanks().

Referenced by _integrate().

◆ _chamber()

None feedtwin.session.core.Session._chamber ( self,
Mapping[str, float] flows )
protected
The chamber boundary when there is no engine model on the drawing.

Definition at line 5658 of file core.py.

References _chamber_node, _design_chamber, max, min, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, tanks, and vehicle_tanks().

Referenced by _close_chamber().

◆ _check_limits()

None feedtwin.session.core.Session._check_limits ( self)
protected
Trip the stand if a vessel is over the pressure the drawing rates it for.

A hand-loaded regulator turned too far, a shut LOX tank left to boil,
a fill with the vent closed: the model would happily carry a tank to
the propellant's critical pressure and beyond. A real one lets go.
Above its MAWP the stand stops, says which vessel and how far, and
asks to be reset -- the operator learns what they did, and nothing
pretends to run on past a failure.

Definition at line 5800 of file core.py.

References _reliefs_on(), feedtwin.session.burn.Probes.bottles, bottles, max, model, anonymous_namespace{idle_e2e.cpp}::Beat.t, anonymous_namespace{idle_e2e.cpp}::World::Ground.t, feedtwin.model.component.EvalContext.t, feedtwin.session.burn.BurnTrace.t, t, gui.App.t, lc_calibration_gui.LCPlotWindow.t, rec_t.t, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, tanks, trip, feedtwin.session.burn.BurnEnd.tripped, and tripped.

Referenced by _integrate().

◆ _close_chamber()

SteadyResult feedtwin.session.core.Session._close_chamber ( self,
Network net,
Mapping[str, float] signals,
frozenset[str] dry,
SteadyResult result,
Mapping[str, tuple[float, float]] | None storage = None )
protected
Find the chamber pressure the network and the engine agree on.

With the chamber pinned at ``p`` the network delivers some flow; the
engine turns that flow into a chamber pressure ``g(p)`` through c*
and the throat. The two agree where ``g(p) = p``. ``g`` falls as ``p``
rises (a higher chamber takes injector drop away), it is above
ambient at ambient whenever propellant is arriving, and it is ambient
at the feed pressure where nothing flows -- so a root is bracketed
and regula falsi with a bisection guard finds it in a handful of
solves. A step where the previous answer still holds costs nothing
extra: the first solve is the one already made.

Sets ``_chamber_guess``, the chamber node, and ``_last_chamber``.

Definition at line 4338 of file core.py.

References _accept(), _chamber(), _chamber_guess, _last_chamber, _last_flows, _note_solve(), _solve(), _tick, max, min, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _advance_once().

◆ _close_outlet()

None feedtwin.session.core.Session._close_outlet ( self,
TankSim sim,
float dt,
set[str] cut,
dict[str, tuple[float, float]] out )
protected
Close a shut-in tank's outlet over the step, as its ullage is closed.

With the vent and the press line shut the ullage holds a fixed mass, so
every gram of liquid that leaves expands it: the outlet is as stiff as
that ullage. Held at its start-of-step pressure it is an explicit
coupling -- a full LE4 fuel tank drained through a quarter-psi head
swung its drain 30 g/s each way, step after step, and leaked 2 g/s
where it should have stopped. Closed here (``solve_steady(storage=)``):
where the outlet lands with no liquid exchanged, and how far one probe's
worth of draining moves it -- both by trial steps on a copy.

Only for a tank with liquid in it and a live line at its outlet; a tank
whose outlet is a junction (:meth:`_through_outlets`) has none of its own.

Definition at line 3953 of file core.py.

References _through_outlets(), max, and model.

Referenced by _ullage_storage().

◆ _coupling_timescale()

float feedtwin.session.core.Session._coupling_timescale ( self,
Mapping[str, float] | None signals = None,
float dt = 0.0,
bool press = True )
protected
Shortest ullage RC time constant on the stand [s], or 0.

``C = V rho / p`` is the isothermal gas capacitance of an ullage -- how
much mass it takes to raise its pressure by a pascal. Two resistances
are paired with it, and the shorter product is what the coupling step
has to resolve.

The regulator's slope, ``flow_droop / rated_flow``: how many pascals
its outlet gives up per kg/s drawn. That is the regulator-ullage loop.

And the ullage's own press path (:meth:`_press_path_timescale`): what
the flow into the tank does when the *tank* moves, with the outlet node
next door held. Two tanks on one press manifold trade gas through
nothing but their solenoids, and on helium that is a few milliseconds
where the regulator's is tens. Stepped past it, each solve sent gas the
wrong way through a solenoid for a whole coupling step: the tank fell
15-30 psi in a quarter of a hundredth of a second, the other dumped
into it, and every LE4 helium burn plotted a 0.5-0.8 s sawtooth that
cost 170 N of mean thrust (2026-10-05).

Definition at line 3622 of file core.py.

References _dry_branches(), _press_path_timescale(), _regulator_slopes(), _resting, min, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _integrate().

◆ _cut_dome_regulators()

list[PidNode] feedtwin.session.core.Session._cut_dome_regulators ( self)
protected
The dome-loaded regulators whose loader is not on the drawing: it
was on the cart and went with it (:attr:`Setup.ignore_gse`).

Definition at line 2598 of file core.py.

References model.

Referenced by _find_cut_dome_lines(), and _find_cut_dome_ports().

◆ _dry_branches()

frozenset[str] feedtwin.session.core.Session._dry_branches ( self)
protected
Feed branches leaving a tank with nothing left in it.

Definition at line 3499 of file core.py.

References _fill_lines, _loading(), _through_outlets(), model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _advance_once(), _coupling_timescale(), _press_path_timescale(), and _rest_ground().

◆ _dry_cut()

float feedtwin.session.core.Session._dry_cut ( self,
Mapping[str, float] flows,
float dt )
protected
How much of a ``dt`` step the tanks can feed these ``flows`` [s].

``dt`` unless a tank still feeding would be drained before the step
ends; then the time at which the first of them is. Only a tank above
:data:`DRY_MASS` counts: one below it is already isolated
(:meth:`_dry_branches`), so the remainder of a cut step cannot cut
again on the same tank.

Definition at line 4101 of file core.py.

References _liquid_fed, _split_at(), feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _advance_once().

◆ _fills()

bool feedtwin.session.core.Session._fills ( self,
TankSim sim )
protected
Whether the current state is filling this tank. Never a cart's tank:
that is where a load comes from, pre-loaded.

Matched on the state's name against the tank's fluid, because fill comes
from a tanker that is not on the drawing -- see FILL_RATE. The rule is
:func:`~feedtwin.session.statemachine.fills`, which an edited table is
checked against.

Definition at line 5344 of file core.py.

Referenced by _integrate().

◆ _film_estimator()

Callable[[], GasFilm] | None feedtwin.session.core.Session._film_estimator ( self,
Fluid gas,
object node,
CylindricalTank geometry )
protected
A thunk for the still-gas film of one vessel, or None when the
setup says to keep the per-litre default.

Definition at line 3072 of file core.py.

References anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _build_vessels().

◆ _find_cut_dome_lines()

dict[str, DomeLine] feedtwin.session.core.Session._find_cut_dome_lines ( self)
protected
Each cut dome line (:meth:`_find_cut_dome_ports`) with a valve drawn
on the vehicle's half of it, as a :class:`DomeLine` whose loader is the
capped disconnect the cart's dome supply came in through.

The dome line is loading gas off the cart, and like the cart's vents
it stays plugged into the rocket while the stand is on it: rocket only,
the dome comes in at the knob through that disconnect, and the
rocket's own dome valve decides whether it reaches the regulator.
:meth:`_gated_dome` walks it as it walks a drawn line, so a valve no
state-table row drives passes and the dome follows the knob exactly as
before; one a row drives loads the dome only in the states that open
it, and holds what was shut in otherwise. Walked from the ``dome``
handle through junctions, valves and instruments to one disconnect;
a line that reaches none, or more than one, is left as it was.

Definition at line 2652 of file core.py.

References _cut_dome_regulators(), and model.

◆ _find_cut_dome_ports()

dict[str, frozenset[str]] feedtwin.session.core.Session._find_cut_dome_ports ( self)
protected
The network nodes a dome-loaded regulator's dome line reaches, for
each one with no loader left on the drawing: its loader was on the cart
and went with it (:attr:`Setup.ignore_gse`). The line is no plumbing,
so the solve leaves it at atmosphere; its transducer reads the dome the
knob sets. Walked from the ``dome`` handle through junctions and
instruments, stopping at any valve, regulator, disconnect or vessel.

Definition at line 2611 of file core.py.

References _cut_dome_regulators(), and model.

◆ _find_drawn_fills()

frozenset[str] feedtwin.session.core.Session._find_drawn_fills ( self)
protected
The vessels the drawing fills (:func:`drawn_fills`), each said: its
built-in fill steps aside.

Definition at line 2575 of file core.py.

References assumptions, feedtwin.session.core.drawn_fills(), and model.

◆ _find_fill_stops()

dict[str, str] feedtwin.session.core.Session._find_fill_stops ( self)
protected
For each tank a cart tank loads: the first valve on the transfer line,
from the cart's side. Hand or actuated; none if the line has no valve.

Definition at line 4588 of file core.py.

References _fill_lines, path.append(), and model.

◆ _find_fill_stubs()

dict[str, str] feedtwin.session.core.Session._find_fill_stubs ( self)
protected
Free boundary node of each fill valve -> the vessel node it feeds.

Definition at line 4879 of file core.py.

References binding, feedtwin.session.burn.Probes.bottles, bottles, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

◆ _fire_notes()

list[str] feedtwin.session.core.Session._fire_notes ( self)
protected
What Fire is actually doing, and what an abort's row leaves open.

Fire with nothing lit is a cold flow: tanks too low to start the
engine push propellant through it at a few psi and no thrust, and the
console called that "Burning" for as long as anyone let it (D-10).
An abort the stand's table writes with a main open keeps feeding the
engine; the twin does what the DAQ would (NEEDS-REPAIR.md), and says
so where the operator is looking, with where to fix it (D-18).

Definition at line 5761 of file core.py.

References _state_since, lit_this_state(), machine, model, anonymous_namespace{idle_e2e.cpp}::Beat.state, anonymous_namespace{idle_e2e.cpp}::World.state, battery_t.state, feedtwin.session.core.BottleSim.state, state, feedtwin.session.core.TankSim.state, gnss_core_t.state, gnss_frame_parser_t.state, SensorHotfire::CoreState.state, Valve.state, world_t.state, anonymous_namespace{idle_e2e.cpp}::Beat.t, anonymous_namespace{idle_e2e.cpp}::World::Ground.t, feedtwin.model.component.EvalContext.t, feedtwin.session.burn.BurnTrace.t, t, gui.App.t, lc_calibration_gui.LCPlotWindow.t, rec_t.t, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, tanks, and vehicle_tanks().

Referenced by _notes().

◆ _firing()

bool feedtwin.session.core.Session._firing ( self)
protected
Propellant reached the engine on the last solve.

Definition at line 3529 of file core.py.

References _last_flows, and model.

Referenced by _leaking_notes(), and _rest_ground().

◆ _gated_dome()

float feedtwin.session.core.Session._gated_dome ( self,
DomeLine | None line,
float live,
Mapping[str, float] commanded )
protected
The dome a gated dome line gives: its loader's outlet while the line
is open between them, atmosphere once an open vent reaches the port
side, and otherwise what was shut in [Pa abs].

Walked from the dome port each tick, through what is open. A valve no
actuator drives passes (nothing on the stand shuts it); a vent valve
no actuator drives, and a hand valve nobody has opened, rest shut.

Definition at line 3356 of file core.py.

References _dome_held, _dome_primed, _dome_readings, forced, and model.

Referenced by signals().

◆ _held()

Mapping[str, float] feedtwin.session.core.Session._held ( self,
SteadyResult result )
protected
The node pressures a step acts on and shows.

The solve's own when it converged. A failed solve's iterate is not a
pressure field either, so every node it was solving for stays where the
last converged solve left it; only the boundaries -- vessels,
atmosphere, the chamber -- are taken as they stand, because the session
set those itself. Before any solve has converged there is nothing to
hold and the iterate is all there is.

Definition at line 4318 of file core.py.

References _last_pressures, and model.

Referenced by _advance_once(), and _integrate().

◆ _integrate()

Sample feedtwin.session.core.Session._integrate ( self,
float dt )
protected

◆ _leaking_notes()

list[str] feedtwin.session.core.Session._leaking_notes ( self)
protected
Where a vehicle tank's propellant is going, when it is not the engine.

On LE4 the cart's FD-ROT-G -- a dump nothing in the state table
commands, resting open as drawn -- emptied the flight fuel tank through
its fill line in about ten seconds after T-0, and nothing on screen said
where the fuel went (2026-10-09). This names the way out: each branch
carrying that propellant across the stand's boundary, and whether the
state table drives it. Read off the last solve; changes nothing.

Definition at line 4922 of file core.py.

References _boundary_nodes(), _firing(), _last_flows, _split_at(), binding, anonymous_namespace{idle_e2e.cpp}::World.ground, ground, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _notes().

◆ _ledger_totals()

tuple[float, float, float] feedtwin.session.core.Session._ledger_totals ( self)
protected
``(added, removed, guards)`` the vessels booked themselves [kg].

Definition at line 4150 of file core.py.

References feedtwin.session.burn.Probes.bottles, bottles, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _record_solver(), and reset_balance().

◆ _loader_supply()

float feedtwin.session.core.Session._loader_supply ( self,
object loader )
protected
Pressure feeding a dome control regulator [Pa].

The bottle it hangs off, read from state rather than from the last
solve: the dome has to be known *before* the network is solved, and a
stale node pressure from the previous tick would put the dome one step
behind the supply it is supposed to track.

Definition at line 3058 of file core.py.

References feedtwin.session.burn.Probes.bottles, bottles, anonymous_namespace{idle_e2e.cpp}::World.ground, and ground.

Referenced by _through_loader().

◆ _loading()

◆ _move_vessels()

None feedtwin.session.core.Session._move_vessels ( self,
Mapping[str, float] flows,
Mapping[str, float] pressures,
float dt,
int substeps = SUBSTEPS )
protected
Integrate every vessel over ``dt`` with the flows it is given.

The ground at rest is left where it is (:attr:`Setup.ground_rests`):
what :meth:`_rest_ground` took out of the solve, and any cart vessel
these flows do not touch and no built-in press is acting on.

Definition at line 4430 of file core.py.

References _arriving_liquid_T(), _bottle_inflow_enthalpy(), _drawn_fill, _last_isolated, _liquid_fed, _net_into(), _outlet_split(), _pressurant_enthalpy(), _resting, _sealed(), _split_at(), _supply_press(), _supply_to(), _through_outlets(), _vent_fraction(), feedtwin.session.burn.Probes.bottles, bottles, anonymous_namespace{idle_e2e.cpp}::World.ground, ground, min, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _advance_once().

◆ _net_into()

float feedtwin.session.core.Session._net_into ( self,
str node,
Mapping[str, float] flows )
protected

Definition at line 5009 of file core.py.

References _split_at().

Referenced by _move_vessels().

◆ _note_solve()

None feedtwin.session.core.Session._note_solve ( self,
SteadyResult result )
protected
Count one network solve into this tick's solver record.

Definition at line 4122 of file core.py.

References _tick, and max.

Referenced by _advance_once(), and _close_chamber().

◆ _notes()

◆ _off_the_dew_line()

float feedtwin.session.core.Session._off_the_dew_line ( self,
Fluid fluid,
float pressure,
float enthalpy,
float landed,
str node_id )
protected
A gas node the walk landed inside the two-phase dome, held as
saturated vapour :data:`_DEW_MARGIN_K` above its dew point.

Venting cold pressurant expands it down its own saturation line: GN2
off a chilled LOX tank, throttled to an atmosphere, landed at 77.87 K
and 107.6 kPa -- nitrogen's boiling point there -- and every solve
after it failed on its first property call (the state is on the line,
so liquid or vapour is not said). The lines carry one phase, so a
mostly-vapour landing (quality at least a half) is taken as the
vapour it nearly is; the fog it would really carry is not modelled,
and the notes say so once. A mostly-liquid landing -- a flashing
liquid -- is left where it was.

Definition at line 5424 of file core.py.

References assumptions, and max.

Referenced by _propagate_temperatures().

◆ _outlet_split()

tuple[float, float] feedtwin.session.core.Session._outlet_split ( self,
TankSim sim,
Mapping[str, float] flows,
frozenset[str] | None through = None )
protected
Liquid arriving at and leaving a tank's outlet [kg/s]: nothing for a
dry tank whose outlet is a junction (:meth:`_through_outlets`), whose
flows pass the tank by.

Definition at line 4996 of file core.py.

References _split_at(), and _through_outlets().

Referenced by _move_vessels(), _press_path_timescale(), and _ullage_storage().

◆ _press_path_timescale()

float feedtwin.session.core.Session._press_path_timescale ( self,
TankSim sim,
float capacitance,
Mapping[str, float] | None signals,
float dt = 0.0 )
protected
``C / G`` for one ullage while its tank is delivering liquid [s].

``G`` is the ullage's admittance: the sum over the gas branches on it of
``1 / (d dp / d mdot)``, each slope taken at the flow the last solve put
through it -- or at the flow the ullage needs to replace the liquid
leaving it, if that is more. Counting only the branch on the tank and
not what lies beyond it overstates ``G``, so it errs toward more steps.

Only while liquid is leaving. A tank at rest has no drain to outrun its
press path, and at zero flow a quadratic loss has zero slope: the
constant goes to zero and would spend the step limit on a pad hold that
a few grams of slosh cannot disturb.

And only while the tank rides its supply -- within ``supply_band`` of
the node feeding it, the band in which :meth:`TankSim.advance` clamps
the inflow. That is where a step's drain carries the tank across its
supply and the next solve sends its gas back out. A tank sitting
clearly under its supply cannot be carried across, and gets nothing
from the finer steps: the shipped GN2 stand burns 6-8 psi under its
manifold and traced identically either way, at twice the cost.

And only while the drain is enough to matter: over the tick, more
than :data:`PRESS_PATH_DRAIN_FRACTION` of that band. Below it the
drain cannot carry the tank across its supply, and what is left is a
nearly empty tank at an abort, with a trickle out and a trickle in --
whose zero-flow slope asked for 0.3 ms steps and stalled the panel.

Infinity when there is nothing to measure.

Definition at line 3732 of file core.py.

References _dry_branches(), _guess, _last_flows, _outlet_split(), max, model, anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _coupling_timescale().

◆ _pressurant_enthalpy()

float feedtwin.session.core.Session._pressurant_enthalpy ( self,
TankSim sim )
protected
Enthalpy of the gas arriving at this ullage [J/kg].

**A regulator is a throttle, so enthalpy is what carries across it**, not
temperature and not entropy. The gas that reaches the tank has the
specific enthalpy it had in the bottle; its *temperature* is then
whatever that enthalpy corresponds to at the delivery pressure, and that
is a real-gas question with an answer that reverses between fluids.

Nitrogen at room temperature sits above its Joule-Thomson inversion
curve, so throttling from 4500 psi to 500 cools it about thirty kelvin.
Helium's inversion temperature is around forty-five kelvin, far below
anything on a pad, so the same expansion *warms* it about seventeen. A
tank being pressed with helium therefore receives gas nearly fifty
kelvin warmer than the same tank on nitrogen -- which is most of why the
two behave so differently out of a small bottle.

Assuming a fixed delivery temperature, as this used to, erases that
entirely: both gases arrive at 293 K and the only thing left telling
them apart is molar mass.

Definition at line 5259 of file core.py.

References _bottle_for(), arriving_enthalpy, anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _move_vessels(), and _ullage_storage().

◆ _propagate_temperatures()

None feedtwin.session.core.Session._propagate_temperatures ( self,
Mapping[str, float] pressures,
Mapping[str, float] flows,
float dt = 0.0 )
protected
Carry enthalpy along the flow path, so every node has a real temperature.

Node temperatures were set once at build time and never written again.
The vessel models track cooling carefully -- a LOX ullage at 268.7 K
where the drawing said 293.15 -- and the network then metered gas out of
that node at the temperature it was born with. A 24.5 K error is about
9% on density and 4.5% on the flow through it, and it grew through a
burn, so it was a drifting error rather than a constant one.

**Enthalpy, not temperature, is what propagates.** Every component here
is adiabatic, and an adiabatic component with no shaft work conserves
enthalpy -- which is exactly the argument this codebase already makes for
the regulator, and the reason nitrogen cools ~30 K through it while
helium warms ~17 K. Carrying ``h`` and re-solving ``T(h, p)`` at each
node's own pressure therefore gets Joule-Thomson right at *every*
throttle for free, rather than special-casing the one we thought about.

What it does not model: heat into the line walls, and viscous heating.
Both are small against a five-second burn and neither is free to add --
a wall needs its own thermal state per line.

Lagged by one tick, deliberately. This runs *after* the solve and
annotates the network, so it cannot perturb the Newton iteration; the
next tick's solve picks up the new temperatures. Same arrangement as the
chamber boundary, and for the same reason.

Definition at line 5464 of file core.py.

References _off_the_dew_line(), arriving_enthalpy, feedtwin.session.burn.Probes.bottles, bottles, max, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, tanks, wall_temperature, and walls.

Referenced by _advance_once().

◆ _record_solver()

◆ _regulator_slopes()

dict[str, float] feedtwin.session.core.Session._regulator_slopes ( self,
frozenset[str] shut )
protected
Each ullage's stiffest regulator slope, ``flow_droop / rated_flow``
[Pa/(kg/s)], over the regulators it is joined to.

Walked from the ullage across every branch not ``shut``, never on
through another boundary (a vessel, the sky). A regulator the tank
has no open path to is no loop with it: every regulator on the stand
used to be paired with every ullage, so a LOX tank topped in Ox Fill,
its press valve shut, was stepped at the cart's fuel regulator's time
constant -- three solves every 20 ms for a loop that did not exist.
Remembered per shut set and boundaries, as ``Network.dead_ends`` is.

Definition at line 3674 of file core.py.

References _regulator_memo, min, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _coupling_timescale().

◆ _relief_signals()

dict[str, float] feedtwin.session.core.Session._relief_signals ( self,
Network net,
Mapping[str, float] signals,
Mapping[str, float] | None pressures = None )
protected
Each relief valve's lift for this coupling step, as signals.

From the pressure across it: ``pressures`` (a solve made at this step's
vessel pressures) when given; otherwise a vessel boundary's own pressure
where the valve's end is one, and the last converged solve's value at
any other node. A node never yet solved leaves the valve where it was.
See :mod:`feedtwin.comps.relief`.

Definition at line 4203 of file core.py.

References _guess, _relief_lift, _relief_open, and _reliefs.

Referenced by _advance_once().

◆ _reliefs_on()

list[tuple[str, Any]] feedtwin.session.core.Session._reliefs_on ( self,
str vessel )
protected
The relief branches drawn on ``vessel``'s gas side: reached from its
gas node without crossing a regulator, another vessel or another relief.
A COPV's relief does not protect the tanks behind its regulator, nor one
tank's relief the tank across the press manifold.

Definition at line 5851 of file core.py.

References _reliefs, feedtwin.session.burn.Probes.bottles, bottles, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _check_limits(), and _notes().

◆ _rest_ground()

None feedtwin.session.core.Session._rest_ground ( self,
Mapping[str, float] signals )
protected
Decide which ground support rests this step (``self._resting``).

Nothing rests here unless the engine is burning, the drawing has
ground support, and :attr:`Setup.ground_rests` is on. (A cart vessel
with nothing flowing rests at any time: :meth:`_move_vessels`.)

Definition at line 3537 of file core.py.

References _dry_branches(), _firing(), _resting, _resting_memo, _unreachable_ground(), anonymous_namespace{idle_e2e.cpp}::World.ground, ground, model, anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _integrate().

◆ _sealed()

frozenset[str] feedtwin.session.core.Session._sealed ( self,
frozenset[str] isolated )
protected
Tanks whose ullage has no open gas path to the air.

Walked from the ullage through every branch the solve did not take out
(``isolated``: shut valves, dry outlets) to a fixed-pressure boundary
that is not a vessel -- a vent, an open-ended valve. A vessel stops the
walk: another tank or bottle is not the air, and the liquid below an
ullage is not a gas path. With the vent shut and the press line closed,
a tank is sealed (:attr:`TankSim.sealed`). Remembered per shut set.

Definition at line 4761 of file core.py.

References _sealed_cache, feedtwin.session.burn.Probes.bottles, bottles, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _move_vessels().

◆ _slew()

float feedtwin.session.core.Session._slew ( self,
str drawing_id,
float target,
float dt )
protected
Move one actuator toward ``target`` at its own travel rate.

Definition at line 3437 of file core.py.

References _positions, and _travel_time().

Referenced by signals().

◆ _solve()

SteadyResult feedtwin.session.core.Session._solve ( self,
Network net,
Mapping[str, float] signals,
frozenset[str] isolate,
Mapping[str, tuple[float, float]] | None storage )
protected
The network at this coupling step, warm-started from the last answer.

A solve with the ullages closed (``storage``) that fails is tried once
more, from where the same network lands with them held: that solve is
easy, and it puts Newton beside the answer. A failed solve holds the
stand (:meth:`_held`), so the next step asks the same question and
fails the same way, for good. A Fire from unpressed tanks did exactly
that -- frozen with the chamber at 0 psig, where 384 iterations of the
same solve converged and 9 + 4 by this route. Only a step that failed
takes it, so every step that converges is the step it was.

Definition at line 4259 of file core.py.

References _guess, anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _advance_once(), and _close_chamber().

◆ _split_at()

tuple[float, float] feedtwin.session.core.Session._split_at ( self,
str node,
Mapping[str, float] flows )
protected
Mass arriving at and leaving a node [kg/s], kept apart.

A tank being filled has pressurant coming in one port and vent gas going
out another at the same time. Netting them loses the energy difference,
and the sign of the net says nothing about either.

Definition at line 4973 of file core.py.

References model.

Referenced by _dry_cut(), _integrate(), _leaking_notes(), _move_vessels(), _net_into(), _outlet_split(), and _venting().

◆ _stop_full_loads()

None feedtwin.session.core.Session._stop_full_loads ( self)
protected
The crew's hand on a drawn load's transfer valve.

Shut until the tank's fill state ("Fuel Fill") is selected, opened for
the load, shut again once the tank holds it -- as the built-in load
stops at its full fraction. A transfer valve left at rest is open (it is
plumbed both sides), and a cart tank above the flight tank then siphons
into it before anyone has started the fill. Acts only when what the
crew would do changes, so a hand on the P&ID in between is kept.

Definition at line 4650 of file core.py.

References _fill_crew, _fill_stopped, _fill_stops, _loading(), assumptions, fire_loads(), forced, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _integrate().

◆ _supply_press()

tuple[float, float, float] feedtwin.session.core.Session._supply_press ( self,
TankSim sim,
float dt = 0.0 )
protected
The cart's press on a ground supply tank whose press line is not
drawn: ``(rate [kg/s], target [Pa], arriving enthalpy [J/kg])``.

A fuel transfer tank is pressed to the pressure its drawing gives while
the table holds its press actuator ("Fuel Fill Press") open. When a
valve on the drawing is bound to that actuator, the network presses it
and this is zero; so is it for every tank that is not the cart's.

Definition at line 4685 of file core.py.

References assumptions, binding, machine, max, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, anonymous_namespace{idle_e2e.cpp}::Beat.state, anonymous_namespace{idle_e2e.cpp}::World.state, battery_t.state, feedtwin.session.core.BottleSim.state, state, feedtwin.session.core.TankSim.state, gnss_core_t.state, gnss_frame_parser_t.state, SensorHotfire::CoreState.state, Valve.state, and world_t.state.

Referenced by _move_vessels().

◆ _supply_to()

float feedtwin.session.core.Session._supply_to ( self,
TankSim sim,
Mapping[str, float] pressures )
protected
Pressure of whatever is feeding this ullage [Pa], or zero.

The highest upstream node across a branch carrying gas *into* the
ullage. Zero when nothing is, which leaves the vessel unclamped.

Definition at line 5243 of file core.py.

References max, and model.

Referenced by _move_vessels().

◆ _tank_wall_defaults()

dict[str, Any] feedtwin.session.core.Session._tank_wall_defaults ( self)
protected

Definition at line 3096 of file core.py.

References anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _build_vessels().

◆ _through_loader()

float feedtwin.session.core.Session._through_loader ( self,
DomeLoader loader,
float psig_set )
protected
The dome a control regulator set to ``psig_set`` gives, from its
supply now [Pa].

Definition at line 3423 of file core.py.

References _loader_supply(), and model.

Referenced by signals().

◆ _through_outlets()

frozenset[str] feedtwin.session.core.Session._through_outlets ( self)
protected
Dry tanks whose outlet other lines pass through, by tank id.

An unsized tee on a tank's outlet line is joined into the outlet node
(an unsized line is a direct connection), so on LE4 (6) the cart's
transfer tank outlet *is* the tee between the fill line and the dump.
Cutting every branch on a dry tank's outlet (:meth:`_dry_branches`) cut
the fill line from the dump with it: Eth-Tank sat at 45 psig with the
dump open and nothing solved. A dry tank's outlet with two or more lines
on it -- and not being loaded through them -- is a junction instead: no
pressure of its own (:meth:`_apply_vessel_pressures`), nothing taken
from or given to the empty tank, flow passing between the lines.

Definition at line 3471 of file core.py.

References _loading(), model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _apply_vessel_pressures(), _close_outlet(), _dry_branches(), _move_vessels(), and _outlet_split().

◆ _travel_time()

float feedtwin.session.core.Session._travel_time ( self,
str drawing_id )
protected
Shut-to-open time for one actuator [s], off the drawing.

Definition at line 3452 of file core.py.

References _travel, model, anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by _slew().

◆ _ullage_storage()

dict[str, tuple[float, float]] feedtwin.session.core.Session._ullage_storage ( self,
float dt,
frozenset[str] isolated )
protected
Each ullage's closure over a ``dt`` coupling step, for the solve.

``{ullage node: (C / dt, reference)}`` (``solve_steady(storage=...)``):
where the vessel lands with no gas exchanged -- liquid still leaving at
the last solve's rate, the ullage collapsing, a load still pouring --
and how many kg/s over the step it takes to move it a pascal from
there. Both read off the vessel itself, by trial steps on a copy, so
whatever it models (charge heating, collapse, vapour) is in the slope
the network sees.

Without this the solve holds each tank at its start-of-step pressure,
an explicit coupling of a tiny capacitance behind a huge conductance.
LE4's 0.41 L ullages behind Cv 4 press solenoids flip-flopped through
their shared manifold every coupling step: one tank 5 psi high dumping
40 g/s, the other taking 100 g/s and refusing nearly all of it at its
supply clip, the regulator drooping at a phantom 60 g/s and both tanks
parked 25 psi under lockup for as long as the valves stayed open.

The slope taken is the stiffer of gas in (at the arriving enthalpy)
and gas out: over-stating the vessel's stiffness only damps the
closure, under-stating it hands back some of the explicit coupling.
A tank cut off by shut valves gets none and stays a fixed boundary.

Except an ullage draining to the sky (:meth:`_venting`), which is
closed on its gas-out slope alone. There the flow is one way and
steady, so the slope is not a damping: the node lands at
``reference - q dt / slope`` while the vessel lands where its own
slope puts it, and the stiffer one opens a gap between them that
grows with the step. Over boiling LOX the two differ eightfold --
warm gas arriving compresses the ullage, gas leaving is replaced by
flash boil-off -- and a topped tank on its vent sat 4 psi above the
node its vent flowed from, at 7.3 psig where finer coupling found
3.4. A vent ends at a fixed pressure, so there is no second vessel
across it to flip-flop with.

Definition at line 3859 of file core.py.

References _close_outlet(), _last_flows, _liquid_fed, _outlet_split(), _pressurant_enthalpy(), _venting(), max, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _advance_once().

◆ _ullage_wall_T0()

float | None feedtwin.session.core.Session._ullage_wall_T0 ( self,
str tank_id,
TankSim sim )
protected
The upper-wall T-0 temperature the setup asks of this tank [K], or
None for the previous behaviour (the pressurant's temperature).

Definition at line 3047 of file core.py.

References anonymous_namespace{idle_e2e.cpp}::World.setup, and setup.

Referenced by prime().

◆ _unreachable_ground()

tuple[frozenset[str], frozenset[str]] feedtwin.session.core.Session._unreachable_ground ( self,
frozenset[str] shut )
protected
Ground branches and vessels with no open path to the vehicle.

Walked from the vehicle's vessels and the engine across every branch
not ``shut``. A tank is one place, so arriving at either of its nodes
reaches both; any other fixed node -- atmosphere -- ends the walk, or
every vent would join the cart to the vehicle through the sky.

Definition at line 3556 of file core.py.

References feedtwin.session.burn.Probes.bottles, bottles, anonymous_namespace{idle_e2e.cpp}::World.ground, ground, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _rest_ground().

◆ _vent_fraction()

float feedtwin.session.core.Session._vent_fraction ( self,
str node,
Mapping[str, float] flows )
protected
Share of the gas leaving ``node`` that ends at a vent, 0..1.

Follow the flow out of the node through the network, splitting at
each junction in proportion to what leaves it, until it reaches a
fixed-pressure boundary that is not a vessel (a vent to atmosphere:
counted), another vessel (not counted -- it will be back), or a dead
end (not counted). Species do not exist in the network, so this is
the only way a tank can know whether the vapour it let go is gone.

Definition at line 4814 of file core.py.

References feedtwin.session.burn.Probes.bottles, bottles, min, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _integrate(), _move_vessels(), and _venting().

◆ _venting()

bool feedtwin.session.core.Session._venting ( self,
str node,
Mapping[str, float] flows )
protected
More of the gas at ``node`` leaves for the sky than arrives or is
sent on to another vessel.

Definition at line 4805 of file core.py.

References _split_at(), and _vent_fraction().

Referenced by _ullage_storage().

◆ _vessel_pressures()

dict[str, float] feedtwin.session.core.Session._vessel_pressures ( self)
protected

◆ apply_thermal()

None feedtwin.session.core.Session.apply_thermal ( self)
Push the thermal knobs in ``setup`` onto vessels already built.

The tanks were constructed with whatever the setup said at the time;
the console changes it while the stand runs, and a knob that only
took effect on the next session was a knob that did not work.

Definition at line 3112 of file core.py.

References feedtwin.session.core._skin_conductance(), anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

◆ builtin()

dict[str, str] feedtwin.session.core.Session.builtin ( self)
The table's rows this stand acts on by name with no valve wired
(:func:`builtin_rows`), on the fills it found.

Definition at line 2593 of file core.py.

References _drawn_fill, feedtwin.session.core.builtin_rows(), machine, and model.

◆ command_state()

◆ fire_loads()

dict[str, float] feedtwin.session.core.Session.fire_loads ( self)
Propellant a fire is loaded with, per vehicle tank [kg].

The engine's ``fire_load`` (its config's ``lox_tank.mass`` and
``fuel_tank.mass``) on the tank of that side. What the vehicle carries
is fixed by the competition, not by the size of the tank drawn, so the
pad loads this and T-0 starts with it. Empty without an engine that
states one: then loads fill the tank to its full fraction.

Definition at line 2524 of file core.py.

References _fire_loads, model, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, tanks, and vehicle_tanks().

Referenced by _integrate(), _stop_full_loads(), and short_loads().

◆ ground()

frozenset[str] feedtwin.session.core.Session.ground ( self)
Drawing ids of the ground support: everything off the vehicle
(:func:`feedtwin.pid.roles.vehicle_ids`). Empty for a drawing that is
one piece.

Definition at line 2518 of file core.py.

References feedtwin.session.core._ground(), and model.

◆ inventory()

float feedtwin.session.core.Session.inventory ( self)
Fluid held in every vessel: liquid, ullage gas, vapour, bottles [kg].

Definition at line 4142 of file core.py.

References feedtwin.session.burn.Probes.bottles, bottles, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _record_solver(), and reset_balance().

◆ lit_this_state()

bool feedtwin.session.core.Session.lit_this_state ( self)
Whether the engine has made thrust since the present state began.

Definition at line 5757 of file core.py.

References _lit_at, and _state_since.

Referenced by _burnout_check(), and _fire_notes().

◆ operator_held()

list[str] feedtwin.session.core.Session.operator_held ( self)
Valves a person has taken from the state table, by drawing id.

:attr:`forced` holds those, and also the twin's own crew on a drawn
load -- the transfer valve it shuts at the start and opens for the
load. Shown as the operator's "1 held" on a stand nobody had touched,
that read as a bug (the operator, 2026-10-09). A crew valve a person
has since turned the other way is the person's again.

Definition at line 3175 of file core.py.

References _fill_crew, and forced.

◆ peek_signals()

dict[str, float] feedtwin.session.core.Session.peek_signals ( self,
* ,
bool loaded_dome = False )
What the components would read now, moving nothing.

:meth:`signals` is the step's: it slews each valve toward its command
(a zero ``dt`` snaps it there), spends the T-0 dome prime, and records
what each dome line holds. A readout must do none of that -- the
lockup under the dome knob, asked for on every console tick, snapped
every valve to its command mid-travel, so the console's mains opened
in one tick whatever their travel time.

``loaded_dome``: read each dome line as open to its loader, the dome
the knob sets -- not what a shut line holds (atmosphere, in Idle).

Definition at line 3328 of file core.py.

References _dome_held, _dome_primed, _dome_readings, _positions, feedtwin.comps.base.FlowConditions.signals, feedtwin.model.component.EvalContext.signals, feedtwin.session.core.Sample.signals, and signals().

◆ prime()

None feedtwin.session.core.Session.prime ( self,
* ,
float fill_fraction = 0.95,
float tank_psi = 500.0,
float copv_psi = 0.0,
str state = "Ready",
float hold_s = PAD_HOLD_S,
Mapping[str, float] | None loads = None )
Put the stand where it is at T-0, without rehearsing the pad.

``hold_s`` is how long the tanks have been loaded [s]. It sets two
things the thermal models are exquisitely sensitive to and that
``initial_state`` cannot know: the interface contact time, whose
collapse flux goes as ``1/sqrt(t)`` and is fifty kilowatts at a
millisecond; and the wall temperature, which on a cryogenic tank has
chilled to its contents by the time anybody fires. Built as if freshly
filled -- wall at room temperature, interface a moment old -- a LOX
tank with collapse and chilldown enabled collapsed from 550 to 411 psig
during the study's own settle and read as a violent drop and recovery
at ignition. That was the initial condition, not the burn.

Loading and pressing through the sequence is the honest way to get here
and is what the console does. It is the wrong way to *study* a burn,
because pressing the tanks draws on the same bottle the study is about:
an under-sized COPV then fails for two reasons at once and the trace
cannot tell you which.

This sets the initial condition directly -- tanks loaded to
``fill_fraction`` and sitting at ``tank_psi``, bottle at ``copv_psi`` --
so the burn starts from a stated, reproducible state and the only thing
being asked is whether the bottle can hold the tanks up.

``loads`` gives the propellant mass [kg] for the tanks it names, in
place of ``fill_fraction`` -- for a vehicle whose load is fixed by mass
(a competition rule, a measured fill) rather than by a level. A load
that would not fit the tank is refused, not clipped.

Definition at line 2931 of file core.py.

References _dome_primed, _guess, _last_flows, _last_isolated, _last_pressures, _state_since, _ullage_wall_T0(), assumptions, feedtwin.session.burn.Probes.bottles, bottles, anonymous_namespace{idle_e2e.cpp}::World.ground, ground, machine, max, reset_balance(), anonymous_namespace{idle_e2e.cpp}::Beat.state, anonymous_namespace{idle_e2e.cpp}::World.state, battery_t.state, feedtwin.session.core.BottleSim.state, state, feedtwin.session.core.TankSim.state, gnss_core_t.state, gnss_frame_parser_t.state, SensorHotfire::CoreState.state, Valve.state, world_t.state, anonymous_namespace{idle_e2e.cpp}::Beat.t, anonymous_namespace{idle_e2e.cpp}::World::Ground.t, feedtwin.model.component.EvalContext.t, feedtwin.session.burn.BurnTrace.t, t, gui.App.t, lc_calibration_gui.LCPlotWindow.t, rec_t.t, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

◆ release()

None feedtwin.session.core.Session.release ( self,
str drawing_id = "" )
Hand a valve back to the state table; with no id, every valve a
person took (:attr:`operator_held`) -- not the crew's on a drawn load,
which would open the transfer line out of turn.

Definition at line 3194 of file core.py.

References forced.

◆ reset_balance()

None feedtwin.session.core.Session.reset_balance ( self)
Start the mass balance from the inventory as it stands: after the
stand is put somewhere directly (prime, a restored frame) rather than
reached through its boundary.

Definition at line 4159 of file core.py.

References _crossed_in, _crossed_out, _guard_J0, _inventory0, _ledger0, _ledger_totals(), inventory(), feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _record_solver(), and prime().

◆ set_valve()

None feedtwin.session.core.Session.set_valve ( self,
str drawing_id,
bool is_open )

Definition at line 3191 of file core.py.

References forced.

◆ short_loads()

list[str] feedtwin.session.core.Session.short_loads ( self)
Each vehicle tank too small for the engine's fire load, said.

A load stops at the tank's full fraction (``TankSim._wanted``), so a
drawn tank that cannot hold its fire load is loaded short, silently,
and every burn on the stand is that much shorter than the engine was
designed for. Empty when every tank holds its load.

Definition at line 2543 of file core.py.

References fire_loads(), anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

◆ signals()

dict[str, float] feedtwin.session.core.Session.signals ( self,
float dt = 0.0 )
What the components read this tick: the state, then hand overrides.

Valve positions *slew* toward their commanded value rather than
snapping to it. A main valve that goes shut-to-open in zero time is a
step input into an ullage of half a litre, and the tank pressure dip it
produces is a property of the step, not of the stand: the drop comes
out several times deeper than the pad shows, and it arrives before the
chamber has lit, so the injector sees full tank pressure against
ambient and pulls flow no real start ever pulls. Each valve carries a
`travel_time`; this is the one place it can be honoured, because the
state machine deals in commanded positions and knows nothing of time.

Definition at line 3236 of file core.py.

References _cut_dome_lines, _dome_ports, _dome_primed, _dome_readings, _gated_dome(), _slew(), _through_loader(), binding, forced, hookup, knobs, machine, model, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, anonymous_namespace{idle_e2e.cpp}::Beat.state, anonymous_namespace{idle_e2e.cpp}::World.state, battery_t.state, feedtwin.session.core.BottleSim.state, state, feedtwin.session.core.TankSim.state, gnss_core_t.state, gnss_frame_parser_t.state, SensorHotfire::CoreState.state, Valve.state, and world_t.state.

Referenced by _integrate(), and peek_signals().

◆ skip_chilldown()

list[str] feedtwin.session.core.Session.skip_chilldown ( self,
str tank_id = "" )
Chill a cryogen tank's wall now rather than wait for the load to.

A load spends minutes chilling the metal before anything collects --
~5 on LE4 at the calibrated dewar valve, ~10 on the stand -- and nobody
rehearsing waits that out (the team, 2026-10-08). Each vehicle tank, or
the one named, gets :meth:`TankSim.skip_chill`; the cart's own vessels
are supplies and are left alone. The assumptions say so, so a burn's
record shows its pad was cut short. Returns the labels of the tanks it
chilled: none when every wall is already cold.

Definition at line 3204 of file core.py.

References assumptions, anonymous_namespace{idle_e2e.cpp}::World.ground, ground, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

◆ step()

Sample feedtwin.session.core.Session.step ( self,
float dt )
What the operator sees ``dt`` seconds later.

Integrated now, Fire included, as a run of :attr:`Setup.live_step`
steps -- the Study's grid, with nothing folded to save wall clock. A
stand too stiff for real time runs in slow motion rather than on a
coarser scheme (docs/PHYSICS-BENCHMARK.md 3.10). A tripped stand holds
the frame it tripped on.

Definition at line 5013 of file core.py.

References _integrate(), _shown, max, min, anonymous_namespace{idle_e2e.cpp}::World.setup, setup, feedtwin.session.burn.BurnEnd.tripped, and tripped.

Referenced by engine.pipeline.thermal.wall_conduction.WallModel.advance(), feedtwin.vessels.volume.GasVolume.blowdown(), feedtwin.vessels.tank.Tank.pressurant_for_expulsion(), and engine.pipeline.thermal.wall_conduction.WallModel.soak().

◆ vehicle_tanks()

tuple[str, ...] feedtwin.session.core.Session.vehicle_tanks ( self)
The tanks the engine burns from: every tank, less the ground's.

Definition at line 2570 of file core.py.

References anonymous_namespace{idle_e2e.cpp}::World.ground, ground, feedtwin.session.burn.BurnPlan.tanks, feedtwin.session.core.Sample.tanks, and tanks.

Referenced by _burnout_check(), _chamber(), _fire_notes(), and fire_loads().

Member Data Documentation

◆ _boundary

frozenset[str] | None feedtwin.session.core.Session._boundary = None
protected

Definition at line 2343 of file core.py.

Referenced by _boundary_nodes().

◆ _chamber_guess

feedtwin.session.core.Session._chamber_guess = AMBIENT
protected

Definition at line 2383 of file core.py.

Referenced by _close_chamber().

◆ _chamber_node

str feedtwin.session.core.Session._chamber_node = ""
protected

Definition at line 2405 of file core.py.

Referenced by _chamber().

◆ _crossed_in

float feedtwin.session.core.Session._crossed_in = 0.0
protected

Definition at line 2344 of file core.py.

Referenced by _advance_once(), _record_solver(), and reset_balance().

◆ _crossed_out

float feedtwin.session.core.Session._crossed_out = 0.0
protected

Definition at line 2345 of file core.py.

Referenced by _advance_once(), _record_solver(), and reset_balance().

◆ _cut_dome_lines

tuple feedtwin.session.core.Session._cut_dome_lines
protected
Initial value:
= (
self._find_cut_dome_lines() if self.gse_ignored else {}
)

Definition at line 2463 of file core.py.

Referenced by signals().

◆ _design_chamber

feedtwin.session.core.Session._design_chamber = AMBIENT
protected

Definition at line 2406 of file core.py.

Referenced by _chamber().

◆ _dome_held

dict feedtwin.session.core.Session._dome_held = {}
protected

Definition at line 2312 of file core.py.

Referenced by _gated_dome(), and peek_signals().

◆ _dome_ports

tuple feedtwin.session.core.Session._dome_ports
protected
Initial value:
= (
self._find_cut_dome_ports() if self.gse_ignored else {}
)

Definition at line 2457 of file core.py.

Referenced by signals().

◆ _dome_primed

bool feedtwin.session.core.Session._dome_primed = False
protected

Definition at line 2317 of file core.py.

Referenced by _gated_dome(), peek_signals(), prime(), and signals().

◆ _dome_readings

feedtwin.session.core.Session._dome_readings = {}
protected

Definition at line 2315 of file core.py.

Referenced by _gated_dome(), _integrate(), peek_signals(), and signals().

◆ _drawn_fill

feedtwin.session.core.Session._drawn_fill = self._find_drawn_fills()
protected

Definition at line 2453 of file core.py.

Referenced by _integrate(), _move_vessels(), _notes(), and builtin().

◆ _fill_crew

dict feedtwin.session.core.Session._fill_crew = {}
protected

Definition at line 2497 of file core.py.

Referenced by _stop_full_loads(), and operator_held().

◆ _fill_hoses

frozenset[str] feedtwin.session.core.Session._fill_hoses
protected
Initial value:
= frozenset(
branch_id
for branch_id, branch in net.branches.items()
if branch.upstream in self._fill_stubs
or branch.downstream in self._fill_stubs
)

Definition at line 2442 of file core.py.

Referenced by _advance_once().

◆ _fill_lines

dict feedtwin.session.core.Session._fill_lines = {}
protected

Definition at line 2491 of file core.py.

Referenced by _dry_branches(), and _find_fill_stops().

◆ _fill_stopped

set[str] feedtwin.session.core.Session._fill_stopped = set()
protected

Definition at line 2493 of file core.py.

Referenced by _stop_full_loads().

◆ _fill_stops

dict[str, str] feedtwin.session.core.Session._fill_stops = self._find_fill_stops()
protected

Definition at line 2492 of file core.py.

Referenced by _stop_full_loads().

◆ _fill_stubs

dict[str, str] feedtwin.session.core.Session._fill_stubs = self._find_fill_stubs()
protected

Definition at line 2434 of file core.py.

Referenced by _apply_vessel_pressures().

◆ _fire_loads

dict[str, float] | None feedtwin.session.core.Session._fire_loads = None
protected

Definition at line 2376 of file core.py.

Referenced by fire_loads().

◆ _guard_J0

float feedtwin.session.core.Session._guard_J0 = 0.0
protected

Definition at line 2348 of file core.py.

Referenced by reset_balance().

◆ _guess

dict feedtwin.session.core.Session._guess = {}
protected

Definition at line 2359 of file core.py.

Referenced by _accept(), _advance_once(), _press_path_timescale(), _relief_signals(), _solve(), and prime().

◆ _inventory0

float | None feedtwin.session.core.Session._inventory0 = None
protected

Definition at line 2346 of file core.py.

Referenced by _record_solver(), and reset_balance().

◆ _last_chamber

ChamberResult | None feedtwin.session.core.Session._last_chamber = None
protected

Definition at line 2384 of file core.py.

Referenced by _close_chamber(), and _integrate().

◆ _last_change

float feedtwin.session.core.Session._last_change = 0.0
protected

Definition at line 2363 of file core.py.

Referenced by _integrate().

◆ _last_dt

float feedtwin.session.core.Session._last_dt = 1.0
protected

Definition at line 2364 of file core.py.

Referenced by _integrate().

◆ _last_flows

feedtwin.session.core.Session._last_flows = {}
protected

◆ _last_isolated

frozenset[str] feedtwin.session.core.Session._last_isolated = frozenset()
protected

Definition at line 2368 of file core.py.

Referenced by _advance_once(), _move_vessels(), and prime().

◆ _last_pressures

dict feedtwin.session.core.Session._last_pressures = {}
protected

Definition at line 2353 of file core.py.

Referenced by _accept(), _held(), and prime().

◆ _ledger0

tuple feedtwin.session.core.Session._ledger0 = (0.0, 0.0, 0.0)
protected

Definition at line 2347 of file core.py.

Referenced by _record_solver(), and reset_balance().

◆ _liquid_fed

frozenset[str] feedtwin.session.core.Session._liquid_fed
protected
Initial value:
= frozenset(
t for fed in self.model.built.supplies.values() for t in fed
) | frozenset(self.model.built.supplies)

Definition at line 2483 of file core.py.

Referenced by _dry_cut(), _move_vessels(), and _ullage_storage().

◆ _lit_at

float | None feedtwin.session.core.Session._lit_at = None
protected

Definition at line 2332 of file core.py.

Referenced by _integrate(), and lit_this_state().

◆ _positions

dict feedtwin.session.core.Session._positions = {}
protected

Definition at line 2309 of file core.py.

Referenced by _slew(), and peek_signals().

◆ _regulator_memo

tuple feedtwin.session.core.Session._regulator_memo = (None, {})
protected

Definition at line 2382 of file core.py.

Referenced by _regulator_slopes().

◆ _relief_lift

dict feedtwin.session.core.Session._relief_lift = {}
protected

Definition at line 2513 of file core.py.

Referenced by _integrate(), and _relief_signals().

◆ _relief_open

dict feedtwin.session.core.Session._relief_open = {}
protected

Definition at line 2512 of file core.py.

Referenced by _advance_once(), and _relief_signals().

◆ _reliefs

list feedtwin.session.core.Session._reliefs
protected
Initial value:
= [
(branch_id, branch)
for branch_id, branch in self.model.built.network.branches.items()
if getattr(branch.component, "type", "") == "relief_valve"
]

Definition at line 2507 of file core.py.

Referenced by _advance_once(), _relief_signals(), and _reliefs_on().

◆ _resting

tuple feedtwin.session.core.Session._resting
protected
Initial value:
= (
frozenset(),
frozenset(),
)

Definition at line 2372 of file core.py.

Referenced by _advance_once(), _coupling_timescale(), _integrate(), _move_vessels(), and _rest_ground().

◆ _resting_memo

tuple[ feedtwin.session.core.Session._resting_memo
protected

Definition at line 2377 of file core.py.

Referenced by _rest_ground().

◆ _sealed_cache

dict feedtwin.session.core.Session._sealed_cache = {}
protected

Definition at line 2494 of file core.py.

Referenced by _sealed().

◆ _shown

Sample | None feedtwin.session.core.Session._shown = None
protected

Definition at line 2321 of file core.py.

Referenced by step().

◆ _state_since

float feedtwin.session.core.Session._state_since = 0.0
protected

Definition at line 2334 of file core.py.

Referenced by _burnout_check(), _fire_notes(), command_state(), lit_this_state(), and prime().

◆ _tick

dict feedtwin.session.core.Session._tick = {}
protected

Definition at line 2342 of file core.py.

Referenced by _close_chamber(), _integrate(), _note_solve(), and _record_solver().

◆ _trapped

dict feedtwin.session.core.Session._trapped = {}
protected

Definition at line 2429 of file core.py.

Referenced by _integrate().

◆ _travel

dict feedtwin.session.core.Session._travel = {}
protected

Definition at line 2318 of file core.py.

Referenced by _travel_time().

◆ arriving_enthalpy

dict feedtwin.session.core.Session.arriving_enthalpy = {}

◆ assumptions

list feedtwin.session.core.Session.assumptions = []

◆ binding

feedtwin.session.core.Session.binding = binding

Definition at line 2291 of file core.py.

Referenced by _find_fill_stubs(), _leaking_notes(), _supply_press(), command_state(), and signals().

◆ bottles

◆ burnout

feedtwin.session.core.Session.burnout = None

Definition at line 2328 of file core.py.

Referenced by _burnout_check(), and _notes().

◆ forced

dict feedtwin.session.core.Session.forced = {}

◆ ground

◆ gse_ignored

feedtwin.session.core.Session.gse_ignored = bool(model.meta.get("vehicle_only"))

Definition at line 2303 of file core.py.

◆ history

Deque[Sample] feedtwin.session.core.Session.history = deque(maxlen=HISTORY)

◆ hookup

feedtwin.session.core.Session.hookup = hookup

Definition at line 2294 of file core.py.

Referenced by signals().

◆ id

◆ knobs

tuple feedtwin.session.core.Session.knobs
Initial value:
= (
{k.id: k.psig for k in hookup.knobs} if hookup is not None else {}
)

Definition at line 2297 of file core.py.

Referenced by feedtwin.session.hookup.Hookup.knob_for(), signals(), and feedtwin.session.hookup.Hookup.to_dict().

◆ machine

feedtwin.session.core.Session.machine = machine

◆ model

◆ operator_held

feedtwin.session.core.Session.operator_held

Definition at line 3201 of file core.py.

◆ setup

◆ solver_log

Deque[SolverRecord] feedtwin.session.core.Session.solver_log = deque(maxlen=HISTORY)

Definition at line 2339 of file core.py.

Referenced by _record_solver().

◆ state

◆ t

◆ tanks

◆ trip

Trip | None feedtwin.session.core.Session.trip = None

Definition at line 2326 of file core.py.

Referenced by _check_limits().

◆ tripped

str | None feedtwin.session.core.Session.tripped = None

Definition at line 2324 of file core.py.

Referenced by _check_limits(), and step().

◆ wall

feedtwin.session.core.Session.wall = time.monotonic()

Definition at line 2336 of file core.py.

◆ wall_temperature

dict feedtwin.session.core.Session.wall_temperature = {}

Definition at line 5379 of file core.py.

Referenced by _propagate_temperatures().

◆ walls

dict feedtwin.session.core.Session.walls = {}

Definition at line 5378 of file core.py.

Referenced by _propagate_temperatures().

◆ watcher

feedtwin.session.core.Session.watcher = Watcher()

Definition at line 2341 of file core.py.

Referenced by _record_solver().


The documentation for this class was generated from the following file:
  • lib/feedtwin/feedtwin/session/core.py