Skip to content

models.feed_systems

Feed-system models for liquid-fed rocket engines. The package describes how propellant is delivered from the tanks to the combustion chamber, and is organised around three concerns:

Concern Where it lives What it contains
Contract feed_systems.base The abstract FeedSystem interface every cycle must satisfy.
Cycle implementations feed_systems.cycles One module per cycle topology (pressure-fed today; electric-pump, gas-generator, expander, staged-combustion to follow).
Shared component descriptions feed_systems.components Static descriptions of pumps, turbines, gas generators, regenerative jackets, plus a tabulated-curve helper that cycle implementations consume.
Tank thermodynamics feed_systems.tank Two-phase tank model backed by CoolProp.
Propellant lines feed_systems.lines The PropellantLine a feed system delivers — its name, its role, and the tank it draws from — and the LineState the integrator carries for it.

The FeedSystem contract

A feed system delivers one PropellantLine per propellant, keyed by line name, and every cycle implementation extends FeedSystem to supply the pressure that reaches the injector on every line:

  • get_inlet_pressures(line_states) -> dict[str, float]

From those, the base class assembles the state every line is fed with:

  • get_inlet_states(line_states) -> dict[str, FluidState]

Both take the LineState of every line — the fluid mass and the internal energy the integrator carries beside it — keyed by line name.

The default inlet state is the tank fluid at the tank temperature and density, at the pressure that survives the path to the injector. A cycle that heats or pressurizes a propellant on the way — a regenerative jacket, a pump — overrides get_inlet_states to say so.

machwave.simulation.biliquid.states.BiliquidEngineState.run_timestep composes the two: it reads every inlet state once per integration step, then calls Injector with them at each chamber pressure the solver tries.

Public surface

Importing from machwave.models.feed_systems exposes the abstract base, every concrete cycle, and the components sub-package:

from machwave.models.feed_systems import (
    FeedSystem,
    StackedTankPressureFedFeedSystem,
    components,
)

pump_spec = components.PumpSpec(
    name="oxidizer pump",
    isentropic_efficiency=0.7,
    pressure_rise=5.0e6,
    volumetric_flow_design=2.0e-3,
    shaft_speed_design=3000.0,
)

The top-level machwave package additionally re-exports feed_systems as a shortcut, so from machwave import feed_systems and feed_systems.StackedTankPressureFedFeedSystem work identically.

machwave.models.feed_systems

FeedSystem

Bases: ABC

Abstract base class for the feed system of a rocket engine.

The system delivers one PropellantLine per propellant, keyed by line name, and solves every line together: a cycle couples its lines physically, as the stacked-tank piston ties the fuel pressure to the oxidizer ullage pressure.

Source code in machwave/models/feed_systems/base.py
class FeedSystem(ABC):
    """
    Abstract base class for the feed system of a rocket engine.

    The system delivers one `PropellantLine` per propellant, keyed by line
    name, and solves every line together: a cycle couples its lines
    physically, as the stacked-tank piston ties the fuel pressure to the
    oxidizer ullage pressure.
    """

    def __init__(self, lines: Sequence[line_models.PropellantLine]):
        """
        Initialize the feed system with the lines it delivers.

        Args:
            lines: Propellant lines the system feeds, one per propellant.

        Raises:
            ValueError: If no line was given, or if two lines share a name.
        """
        self.lines: dict[str, line_models.PropellantLine] = {
            line.name: line for line in lines
        }

        if not lines:
            raise ValueError("lines must hold at least one propellant line")
        if len(self.lines) != len(lines):
            raise ValueError(
                f"line names must be unique, got {[line.name for line in lines]}"
            )

    def get_initial_propellant_mass(self) -> float:
        """Compute and return the initial propellant mass in the system [kg]."""
        return sum(line.tank.initial_fluid_mass for line in self.lines.values())

    def get_lines_with_role(
        self, role: propellant_components.ComponentRole
    ) -> tuple[line_models.PropellantLine, ...]:
        """Return every line carrying the given role, in the order they were given."""
        return tuple(line for line in self.lines.values() if line.role == role)

    def get_inlet_states(
        self, line_states: Mapping[str, line_models.LineState]
    ) -> dict[str, fluid_state_models.FluidState]:
        """
        Return the state delivered to the injector inlet on every line.

        Each state is the tank fluid at the tank temperature and density, at
        the pressure that survives the path to the injector. A cycle that heats
        or works on a propellant on the way — a regenerative jacket, a pump —
        overrides this to say so.

        Args:
            line_states: Fluid mass and internal energy of every line, keyed by
                line name.

        Returns:
            Injector inlet state of every line, keyed by line name.

        Raises:
            ValueError: If any line of the system has no state.
        """
        missing = [name for name in self.lines if name not in line_states]
        if missing:
            raise ValueError(f"no line state was given for {missing}")

        inlet_pressures = self.get_inlet_pressures(line_states)

        inlet_states = {}
        for name, line in self.lines.items():
            line_state = line_states[name]
            inlet_states[name] = fluid_state_models.FluidState(
                fluid_name=line.tank.fluid_name,
                pressure=inlet_pressures[name],
                temperature=line.tank.get_temperature(
                    line_state.fluid_mass, line_state.internal_energy
                ),
                density=line.tank.get_density(
                    line_state.fluid_mass, line_state.internal_energy
                ),
            )
        return inlet_states

    @abstractmethod
    def get_inlet_pressures(
        self, line_states: Mapping[str, line_models.LineState]
    ) -> dict[str, float]:
        """
        Compute the pressure delivered to the injector inlet on every line [Pa].

        Args:
            line_states: Fluid mass and internal energy of every line, keyed by
                line name.

        Returns:
            Injector inlet pressure of every line [Pa], keyed by line name.
        """
        pass

__init__(lines)

Initialize the feed system with the lines it delivers.

Parameters:

Name Type Description Default
lines Sequence[PropellantLine]

Propellant lines the system feeds, one per propellant.

required

Raises:

Type Description
ValueError

If no line was given, or if two lines share a name.

Source code in machwave/models/feed_systems/base.py
def __init__(self, lines: Sequence[line_models.PropellantLine]):
    """
    Initialize the feed system with the lines it delivers.

    Args:
        lines: Propellant lines the system feeds, one per propellant.

    Raises:
        ValueError: If no line was given, or if two lines share a name.
    """
    self.lines: dict[str, line_models.PropellantLine] = {
        line.name: line for line in lines
    }

    if not lines:
        raise ValueError("lines must hold at least one propellant line")
    if len(self.lines) != len(lines):
        raise ValueError(
            f"line names must be unique, got {[line.name for line in lines]}"
        )

get_initial_propellant_mass()

Compute and return the initial propellant mass in the system [kg].

Source code in machwave/models/feed_systems/base.py
def get_initial_propellant_mass(self) -> float:
    """Compute and return the initial propellant mass in the system [kg]."""
    return sum(line.tank.initial_fluid_mass for line in self.lines.values())

get_inlet_pressures(line_states) abstractmethod

Compute the pressure delivered to the injector inlet on every line [Pa].

Parameters:

Name Type Description Default
line_states Mapping[str, LineState]

Fluid mass and internal energy of every line, keyed by line name.

required

Returns:

Type Description
dict[str, float]

Injector inlet pressure of every line [Pa], keyed by line name.

Source code in machwave/models/feed_systems/base.py
@abstractmethod
def get_inlet_pressures(
    self, line_states: Mapping[str, line_models.LineState]
) -> dict[str, float]:
    """
    Compute the pressure delivered to the injector inlet on every line [Pa].

    Args:
        line_states: Fluid mass and internal energy of every line, keyed by
            line name.

    Returns:
        Injector inlet pressure of every line [Pa], keyed by line name.
    """
    pass

get_inlet_states(line_states)

Return the state delivered to the injector inlet on every line.

Each state is the tank fluid at the tank temperature and density, at the pressure that survives the path to the injector. A cycle that heats or works on a propellant on the way — a regenerative jacket, a pump — overrides this to say so.

Parameters:

Name Type Description Default
line_states Mapping[str, LineState]

Fluid mass and internal energy of every line, keyed by line name.

required

Returns:

Type Description
dict[str, FluidState]

Injector inlet state of every line, keyed by line name.

Raises:

Type Description
ValueError

If any line of the system has no state.

Source code in machwave/models/feed_systems/base.py
def get_inlet_states(
    self, line_states: Mapping[str, line_models.LineState]
) -> dict[str, fluid_state_models.FluidState]:
    """
    Return the state delivered to the injector inlet on every line.

    Each state is the tank fluid at the tank temperature and density, at
    the pressure that survives the path to the injector. A cycle that heats
    or works on a propellant on the way — a regenerative jacket, a pump —
    overrides this to say so.

    Args:
        line_states: Fluid mass and internal energy of every line, keyed by
            line name.

    Returns:
        Injector inlet state of every line, keyed by line name.

    Raises:
        ValueError: If any line of the system has no state.
    """
    missing = [name for name in self.lines if name not in line_states]
    if missing:
        raise ValueError(f"no line state was given for {missing}")

    inlet_pressures = self.get_inlet_pressures(line_states)

    inlet_states = {}
    for name, line in self.lines.items():
        line_state = line_states[name]
        inlet_states[name] = fluid_state_models.FluidState(
            fluid_name=line.tank.fluid_name,
            pressure=inlet_pressures[name],
            temperature=line.tank.get_temperature(
                line_state.fluid_mass, line_state.internal_energy
            ),
            density=line.tank.get_density(
                line_state.fluid_mass, line_state.internal_energy
            ),
        )
    return inlet_states

get_lines_with_role(role)

Return every line carrying the given role, in the order they were given.

Source code in machwave/models/feed_systems/base.py
def get_lines_with_role(
    self, role: propellant_components.ComponentRole
) -> tuple[line_models.PropellantLine, ...]:
    """Return every line carrying the given role, in the order they were given."""
    return tuple(line for line in self.lines.values() if line.role == role)

LineState dataclass

The propellant a line still holds at one point in the integration.

Attributes:

Name Type Description
fluid_mass float

Mass of fluid left in the tank [kg].

internal_energy float | None

Internal energy of that fluid [J]. None for an isothermal tank, which runs no energy balance.

Source code in machwave/models/feed_systems/lines.py
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
class LineState:
    """
    The propellant a line still holds at one point in the integration.

    Attributes:
        fluid_mass: Mass of fluid left in the tank [kg].
        internal_energy: Internal energy of that fluid [J]. None for an
            isothermal tank, which runs no energy balance.
    """

    fluid_mass: float
    internal_energy: float | None = None

    def __post_init__(self) -> None:
        if self.fluid_mass < 0.0:
            raise ValueError(f"fluid_mass must be non-negative, got {self.fluid_mass}")

PropellantLine dataclass

One propellant on its way from a tank to the injector.

Attributes:

Name Type Description
name str

Line name, which keys the line across the feed system, the injector and the simulation state.

role ComponentRole

Whether the line carries an oxidizer, a fuel, or an additive such as a triliquid diluent or a coolant.

tank Tank

Tank the line draws from.

Source code in machwave/models/feed_systems/lines.py
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
class PropellantLine:
    """
    One propellant on its way from a tank to the injector.

    Attributes:
        name: Line name, which keys the line across the feed system, the
            injector and the simulation state.
        role: Whether the line carries an oxidizer, a fuel, or an additive such
            as a triliquid diluent or a coolant.
        tank: Tank the line draws from.
    """

    name: str
    role: propellant_components.ComponentRole
    tank: tank_models.Tank

    def __post_init__(self) -> None:
        if not self.name:
            raise ValueError("name must be a non-empty string")

        object.__setattr__(self, "role", propellant_components.ComponentRole(self.role))

    @property
    def initial_state(self) -> LineState:
        """The line state the integrator starts from, as the tank was loaded."""
        return LineState(
            fluid_mass=self.tank.initial_fluid_mass,
            internal_energy=(
                None if self.tank.isothermal else self.tank.initial_internal_energy
            ),
        )

initial_state property

The line state the integrator starts from, as the tank was loaded.

SingleLinePressureFedFeedSystem

Bases: FeedSystem

Pressure-fed feed system delivering a single propellant line.

The tank pressurizes itself: a self-pressurized propellant rides its own vapor pressure while liquid remains, and blows down on the real-gas equation of state once none does. This is the one-line case of the feed system contract, which an oxidizer-only hybrid feed and a monoliquid engine both run on.

Source code in machwave/models/feed_systems/cycles/single_line_pressure_fed.py
class SingleLinePressureFedFeedSystem(feed_system_base.FeedSystem):
    """
    Pressure-fed feed system delivering a single propellant line.

    The tank pressurizes itself: a self-pressurized propellant rides its own
    vapor pressure while liquid remains, and blows down on the real-gas
    equation of state once none does. This is the one-line case of the feed
    system contract, which an oxidizer-only hybrid feed and a monoliquid engine
    both run on.
    """

    def __init__(
        self,
        line: line_models.PropellantLine,
        line_loss: float = 0.0,
    ):
        """
        Initialize the SingleLinePressureFedFeedSystem.

        Args:
            line: The propellant line the system feeds.
            line_loss: Pressure loss along the feedline [Pa], stated for the
                design flow rather than computed from it.

        Raises:
            ValueError: If the pressure loss is negative.
        """
        super().__init__([line])

        self.line_loss = line_loss

        if line_loss < 0.0:
            raise ValueError(f"line_loss must be non-negative, got {line_loss}")

    @classmethod
    def from_oxidizer_tank(
        cls,
        *,
        oxidizer_tank: tank.Tank,
        line_loss: float = 0.0,
    ) -> "SingleLinePressureFedFeedSystem":
        """
        Build the oxidizer-only system a hybrid motor runs on.

        Args:
            oxidizer_tank: Tank the oxidizer line draws from.
            line_loss: Pressure loss along the feedline [Pa].
        """
        return cls(
            line=line_models.PropellantLine(
                name="oxidizer",
                role=propellant_components.ComponentRole.OXIDIZER,
                tank=oxidizer_tank,
            ),
            line_loss=line_loss,
        )

    @property
    def line(self) -> line_models.PropellantLine:
        """The one line the system feeds."""
        return next(iter(self.lines.values()))

    def get_inlet_pressures(
        self, line_states: Mapping[str, line_models.LineState]
    ) -> dict[str, float]:
        """
        Return the tank pressure less the feedline loss [Pa], keyed by line name.

        Args:
            line_states: Fluid mass and internal energy of the line, keyed by
                line name.
        """
        line_state = line_states[self.line.name]

        return {
            self.line.name: self.line.tank.get_pressure(
                line_state.fluid_mass, line_state.internal_energy
            )
            - self.line_loss
        }

line property

The one line the system feeds.

__init__(line, line_loss=0.0)

Initialize the SingleLinePressureFedFeedSystem.

Parameters:

Name Type Description Default
line PropellantLine

The propellant line the system feeds.

required
line_loss float

Pressure loss along the feedline [Pa], stated for the design flow rather than computed from it.

0.0

Raises:

Type Description
ValueError

If the pressure loss is negative.

Source code in machwave/models/feed_systems/cycles/single_line_pressure_fed.py
def __init__(
    self,
    line: line_models.PropellantLine,
    line_loss: float = 0.0,
):
    """
    Initialize the SingleLinePressureFedFeedSystem.

    Args:
        line: The propellant line the system feeds.
        line_loss: Pressure loss along the feedline [Pa], stated for the
            design flow rather than computed from it.

    Raises:
        ValueError: If the pressure loss is negative.
    """
    super().__init__([line])

    self.line_loss = line_loss

    if line_loss < 0.0:
        raise ValueError(f"line_loss must be non-negative, got {line_loss}")

from_oxidizer_tank(*, oxidizer_tank, line_loss=0.0) classmethod

Build the oxidizer-only system a hybrid motor runs on.

Parameters:

Name Type Description Default
oxidizer_tank Tank

Tank the oxidizer line draws from.

required
line_loss float

Pressure loss along the feedline [Pa].

0.0
Source code in machwave/models/feed_systems/cycles/single_line_pressure_fed.py
@classmethod
def from_oxidizer_tank(
    cls,
    *,
    oxidizer_tank: tank.Tank,
    line_loss: float = 0.0,
) -> "SingleLinePressureFedFeedSystem":
    """
    Build the oxidizer-only system a hybrid motor runs on.

    Args:
        oxidizer_tank: Tank the oxidizer line draws from.
        line_loss: Pressure loss along the feedline [Pa].
    """
    return cls(
        line=line_models.PropellantLine(
            name="oxidizer",
            role=propellant_components.ComponentRole.OXIDIZER,
            tank=oxidizer_tank,
        ),
        line_loss=line_loss,
    )

get_inlet_pressures(line_states)

Return the tank pressure less the feedline loss [Pa], keyed by line name.

Parameters:

Name Type Description Default
line_states Mapping[str, LineState]

Fluid mass and internal energy of the line, keyed by line name.

required
Source code in machwave/models/feed_systems/cycles/single_line_pressure_fed.py
def get_inlet_pressures(
    self, line_states: Mapping[str, line_models.LineState]
) -> dict[str, float]:
    """
    Return the tank pressure less the feedline loss [Pa], keyed by line name.

    Args:
        line_states: Fluid mass and internal energy of the line, keyed by
            line name.
    """
    line_state = line_states[self.line.name]

    return {
        self.line.name: self.line.tank.get_pressure(
            line_state.fluid_mass, line_state.internal_energy
        )
        - self.line_loss
    }

StackedTankPressureFedFeedSystem

Bases: FeedSystem

Pressure-fed feed system whose propellant tanks are stacked vertically.

The tank at the top of the stack pressurizes every tank below it through a piston, so one tank sets the pressure the whole system runs on.

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
class StackedTankPressureFedFeedSystem(feed_system_base.FeedSystem):
    """
    Pressure-fed feed system whose propellant tanks are stacked vertically.

    The tank at the top of the stack pressurizes every tank below it through a
    piston, so one tank sets the pressure the whole system runs on.
    """

    def __init__(
        self,
        lines: Sequence[line_models.PropellantLine],
        pressurizing_line: str,
        piston_loss: float = 0.0,
        line_losses: Mapping[str, float] | None = None,
    ):
        """
        Initialize the StackedTankPressureFedFeedSystem.

        Args:
            lines: Propellant lines the system feeds, one per propellant.
            pressurizing_line: Name of the line at the top of the stack, whose
                tank pressure drives every line.
            piston_loss: Pressure loss across the piston [Pa], taken by every
                line below the top of the stack.
            line_losses: Pressure loss along each feedline [Pa], keyed by line
                name and stated for the design flow rather than computed from
                it. A line left out takes no loss.

        Raises:
            ValueError: If the pressurizing line is not one of the lines, if a
                loss is keyed by an unknown line, or if any pressure loss is
                negative.
        """
        super().__init__(lines)

        self.pressurizing_line = pressurizing_line
        self.piston_loss = piston_loss
        self.line_losses = {name: 0.0 for name in self.lines} | dict(line_losses or {})

        self._validate_pressure_losses()

    @classmethod
    def from_oxidizer_and_fuel(
        cls,
        *,
        oxidizer_tank: tank.Tank,
        fuel_tank: tank.Tank,
        piston_loss: float = 0.0,
        oxidizer_line_loss: float = 0.0,
        fuel_line_loss: float = 0.0,
    ) -> "StackedTankPressureFedFeedSystem":
        """
        Build the two-line system a biliquid engine runs on.

        The oxidizer sits at the top of the stack and pressurizes the fuel
        through the piston. The lines are named "oxidizer" and "fuel", which is
        how the injector and the simulation state key them.

        Args:
            oxidizer_tank: Tank the oxidizer line draws from.
            fuel_tank: Tank the fuel line draws from.
            piston_loss: Pressure loss across the piston [Pa].
            oxidizer_line_loss: Pressure loss along the oxidizer feedline [Pa].
            fuel_line_loss: Pressure loss along the fuel feedline [Pa].
        """
        return cls(
            lines=[
                line_models.PropellantLine(
                    name="oxidizer",
                    role=propellant_components.ComponentRole.OXIDIZER,
                    tank=oxidizer_tank,
                ),
                line_models.PropellantLine(
                    name="fuel",
                    role=propellant_components.ComponentRole.FUEL,
                    tank=fuel_tank,
                ),
            ],
            pressurizing_line="oxidizer",
            piston_loss=piston_loss,
            line_losses={"oxidizer": oxidizer_line_loss, "fuel": fuel_line_loss},
        )

    def _validate_pressure_losses(self) -> None:
        if self.pressurizing_line not in self.lines:
            raise ValueError(
                f"pressurizing_line {self.pressurizing_line!r} is not one of the "
                f"lines, got {list(self.lines)}"
            )

        unknown = [name for name in self.line_losses if name not in self.lines]
        if unknown:
            raise ValueError(f"line_losses names {unknown} are not lines of the system")

        if self.piston_loss < 0.0:
            raise ValueError(
                f"piston_loss must be non-negative, got {self.piston_loss}"
            )
        for name, loss in self.line_losses.items():
            if loss < 0.0:
                raise ValueError(
                    f"line_losses[{name!r}] must be non-negative, got {loss}"
                )

    def get_inlet_pressures(
        self, line_states: Mapping[str, line_models.LineState]
    ) -> dict[str, float]:
        """
        Return the pressure delivered to the injector on every line [Pa].

        The line at the top of the stack loses only what its own feedline
        takes; every line below the piston loses the piston pressure loss too.

        Args:
            line_states: Fluid mass and internal energy of every line, keyed by
                line name.

        Returns:
            Injector inlet pressure of every line [Pa], keyed by line name.
        """
        pressurizing_state = line_states[self.pressurizing_line]
        stack_pressure = self.lines[self.pressurizing_line].tank.get_pressure(
            pressurizing_state.fluid_mass, pressurizing_state.internal_energy
        )

        return {
            name: stack_pressure
            - (0.0 if name == self.pressurizing_line else self.piston_loss)
            - self.line_losses[name]
            for name in self.lines
        }

__init__(lines, pressurizing_line, piston_loss=0.0, line_losses=None)

Initialize the StackedTankPressureFedFeedSystem.

Parameters:

Name Type Description Default
lines Sequence[PropellantLine]

Propellant lines the system feeds, one per propellant.

required
pressurizing_line str

Name of the line at the top of the stack, whose tank pressure drives every line.

required
piston_loss float

Pressure loss across the piston [Pa], taken by every line below the top of the stack.

0.0
line_losses Mapping[str, float] | None

Pressure loss along each feedline [Pa], keyed by line name and stated for the design flow rather than computed from it. A line left out takes no loss.

None

Raises:

Type Description
ValueError

If the pressurizing line is not one of the lines, if a loss is keyed by an unknown line, or if any pressure loss is negative.

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def __init__(
    self,
    lines: Sequence[line_models.PropellantLine],
    pressurizing_line: str,
    piston_loss: float = 0.0,
    line_losses: Mapping[str, float] | None = None,
):
    """
    Initialize the StackedTankPressureFedFeedSystem.

    Args:
        lines: Propellant lines the system feeds, one per propellant.
        pressurizing_line: Name of the line at the top of the stack, whose
            tank pressure drives every line.
        piston_loss: Pressure loss across the piston [Pa], taken by every
            line below the top of the stack.
        line_losses: Pressure loss along each feedline [Pa], keyed by line
            name and stated for the design flow rather than computed from
            it. A line left out takes no loss.

    Raises:
        ValueError: If the pressurizing line is not one of the lines, if a
            loss is keyed by an unknown line, or if any pressure loss is
            negative.
    """
    super().__init__(lines)

    self.pressurizing_line = pressurizing_line
    self.piston_loss = piston_loss
    self.line_losses = {name: 0.0 for name in self.lines} | dict(line_losses or {})

    self._validate_pressure_losses()

from_oxidizer_and_fuel(*, oxidizer_tank, fuel_tank, piston_loss=0.0, oxidizer_line_loss=0.0, fuel_line_loss=0.0) classmethod

Build the two-line system a biliquid engine runs on.

The oxidizer sits at the top of the stack and pressurizes the fuel through the piston. The lines are named "oxidizer" and "fuel", which is how the injector and the simulation state key them.

Parameters:

Name Type Description Default
oxidizer_tank Tank

Tank the oxidizer line draws from.

required
fuel_tank Tank

Tank the fuel line draws from.

required
piston_loss float

Pressure loss across the piston [Pa].

0.0
oxidizer_line_loss float

Pressure loss along the oxidizer feedline [Pa].

0.0
fuel_line_loss float

Pressure loss along the fuel feedline [Pa].

0.0
Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
@classmethod
def from_oxidizer_and_fuel(
    cls,
    *,
    oxidizer_tank: tank.Tank,
    fuel_tank: tank.Tank,
    piston_loss: float = 0.0,
    oxidizer_line_loss: float = 0.0,
    fuel_line_loss: float = 0.0,
) -> "StackedTankPressureFedFeedSystem":
    """
    Build the two-line system a biliquid engine runs on.

    The oxidizer sits at the top of the stack and pressurizes the fuel
    through the piston. The lines are named "oxidizer" and "fuel", which is
    how the injector and the simulation state key them.

    Args:
        oxidizer_tank: Tank the oxidizer line draws from.
        fuel_tank: Tank the fuel line draws from.
        piston_loss: Pressure loss across the piston [Pa].
        oxidizer_line_loss: Pressure loss along the oxidizer feedline [Pa].
        fuel_line_loss: Pressure loss along the fuel feedline [Pa].
    """
    return cls(
        lines=[
            line_models.PropellantLine(
                name="oxidizer",
                role=propellant_components.ComponentRole.OXIDIZER,
                tank=oxidizer_tank,
            ),
            line_models.PropellantLine(
                name="fuel",
                role=propellant_components.ComponentRole.FUEL,
                tank=fuel_tank,
            ),
        ],
        pressurizing_line="oxidizer",
        piston_loss=piston_loss,
        line_losses={"oxidizer": oxidizer_line_loss, "fuel": fuel_line_loss},
    )

get_inlet_pressures(line_states)

Return the pressure delivered to the injector on every line [Pa].

The line at the top of the stack loses only what its own feedline takes; every line below the piston loses the piston pressure loss too.

Parameters:

Name Type Description Default
line_states Mapping[str, LineState]

Fluid mass and internal energy of every line, keyed by line name.

required

Returns:

Type Description
dict[str, float]

Injector inlet pressure of every line [Pa], keyed by line name.

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def get_inlet_pressures(
    self, line_states: Mapping[str, line_models.LineState]
) -> dict[str, float]:
    """
    Return the pressure delivered to the injector on every line [Pa].

    The line at the top of the stack loses only what its own feedline
    takes; every line below the piston loses the piston pressure loss too.

    Args:
        line_states: Fluid mass and internal energy of every line, keyed by
            line name.

    Returns:
        Injector inlet pressure of every line [Pa], keyed by line name.
    """
    pressurizing_state = line_states[self.pressurizing_line]
    stack_pressure = self.lines[self.pressurizing_line].tank.get_pressure(
        pressurizing_state.fluid_mass, pressurizing_state.internal_energy
    )

    return {
        name: stack_pressure
        - (0.0 if name == self.pressurizing_line else self.piston_loss)
        - self.line_losses[name]
        for name in self.lines
    }