Skip to content

models.feed_systems.cycles

Concrete feed-system cycle implementations. A cycle is the topology that determines how propellant is pressurised on its way from the tanks to the injector — pressure stored in the tanks themselves, electrically driven pumps, turbine-driven pumps powered by a small bleed-off combustor, and so on. Each module in this sub-package implements one topology and conforms to the FeedSystem contract so the simulation loop can treat them interchangeably.

Available cycles

  • StackedTankPressureFedFeedSystem — Pressure-fed engine with the oxidizer tank stacked directly above a piston-separated fuel tank. Tank pressure provides both propellants' driving head.

Additional cycles are under active development as separate work items: electric-pump, gas-generator, expander, and staged-combustion. When they land, they will reuse the dataclasses in feed_systems.components to describe their pumps, turbines, gas generators, and regenerative jackets.

How cycles compose with the rest of the engine

┌──────────────┐  ┌──────────────────────┐  ┌──────────┐  ┌──────────┐
│ tank.Tank    │──│ feed_systems.cycles  │──│ injector │──│ chamber  │
│ (two-phase)  │  │  (this sub-package)  │  │          │  │          │
└──────────────┘  └──────────────────────┘  └──────────┘  └──────────┘
                       ▲           ▲
                       │           │
                  feed_systems.components
                  (pump/turbine/gas-generator
                   /regenerative-jacket specs)

The cycle reads tank state via the Tank instances it was constructed with, and combines that state with any component specs it owns to evaluate mass flow through the injector. The simulation step lives in BiliquidEngineState.run_timestep, which calls get_mass_flow_ox and get_mass_flow_fuel once per integration step with the current chamber_pressure and the injector geometry from the BipropellantInjector.


StackedTankPressureFedFeedSystem

A bipropellant pressure-fed engine in which the oxidizer and fuel tanks are arranged as a single vertical stack separated by a piston. The oxidizer tank pressure pressurises both propellants — directly for the oxidizer, and indirectly for the fuel through the piston. Pressure loss across the piston is captured by piston_loss.

Construction

from machwave.models.feed_systems import StackedTankPressureFedFeedSystem
from machwave.models.feed_systems.tank import Tank

feed_system = StackedTankPressureFedFeedSystem(
    oxidizer_line_diameter=0.010,    # m
    oxidizer_line_length=0.5,        # m
    fuel_line_diameter=0.008,        # m
    fuel_line_length=0.5,            # m
    fuel_tank=Tank("Ethanol", volume=0.008, temperature=298.0, initial_fluid_mass=3.0),
    oxidizer_tank=Tank("N2O",   volume=0.010, temperature=298.0, initial_fluid_mass=5.0),
    piston_loss=0.0,                  # Pa
)

Mass-flow model. The feed system supplies the upstream pressure for each side and delegates the orifice dispatch to the injector. Upstream pressure is the oxidizer tank pressure for the oxidizer branch and oxidizer_tank_pressure - piston_loss for the fuel branch; downstream pressure is chamber_pressure. The injector picks SPI or HEM per side from its MassFlowModel:

  • SPI (single-phase incompressible) — get_mass_flow_orifice in machwave.core.incompressible_flow:

    \[ \dot{m} = C_d \cdot A \cdot \sqrt{2 \rho \left(P_\text{up} - P_\text{down}\right)} \]

    with \(\rho\) the saturated-liquid density returned by Tank.get_density.

  • HEM (homogeneous-equilibrium two-phase) — get_homogeneous_equilibrium_mass_flux in machwave.core.two_phase_flow, required for self-pressurized propellants such as nitrous oxide where the upstream saturated liquid flashes across the orifice and the flow can choke on the two-phase sound speed. The injector multiplies the returned mass flux by \(C_d \cdot A\).

Line geometry (oxidizer_line_diameter, oxidizer_line_length, fuel_line_diameter, fuel_line_length) is currently stored on the instance for downstream issues that will add feedline pressure drop, but is not consumed by the mass-flow model itself yet.


machwave.models.feed_systems.cycles

Concrete feed-system cycle implementations.

Each module in this package implements one cycle topology (pressure-fed, electric-pump, gas-generator, expander, staged combustion, ...). Cycles consume the shared component specifications in machwave.models.feed_systems.components and conform to the machwave.models.feed_systems.base.FeedSystem contract.

StackedTankPressureFedFeedSystem

Bases: FeedSystem

Represents a bipropellant biliquid rocket engine feed system with stacked tanks.

A stacked tank system is a type of pressure-fed system where the oxidizer and fuel tanks are arranged in a vertical stack. The tanks are separated by a piston and the fuel is pressurized by the oxidizer tank.

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
class StackedTankPressureFedFeedSystem(feed_system_base.FeedSystem):
    """
    Represents a bipropellant biliquid rocket engine feed system with stacked tanks.

    A stacked tank system is a type of pressure-fed system where the oxidizer and fuel
    tanks are arranged in a vertical stack. The tanks are separated by a piston and the
    fuel is pressurized by the oxidizer tank.
    """

    def __init__(
        self,
        oxidizer_line_diameter: float,
        oxidizer_line_length: float,
        fuel_line_diameter: float,
        fuel_line_length: float,
        fuel_tank: tank.Tank,
        oxidizer_tank: tank.Tank,
        piston_loss: float = 0.0,
    ):
        """
        Initialize the StackedTankPressureFedFeedSystem.

        Args:
            oxidizer_line_diameter: Diameter of the oxidizer feedline [m].
            oxidizer_line_length: Length of the oxidizer feedline [m].
            fuel_line_diameter: Diameter of the fuel feedline [m].
            fuel_line_length: Length of the fuel feedline [m].
            fuel_tank: An instance representing the fuel tank.
            oxidizer_tank: An instance representing the oxidizer tank.
            piston_loss: Pressure loss across the piston [Pa]. Default is 0.0.
        """
        super().__init__(fuel_tank, oxidizer_tank)

        self.oxidizer_line_diameter = oxidizer_line_diameter
        self.oxidizer_line_length = oxidizer_line_length
        self.fuel_line_diameter = fuel_line_diameter
        self.fuel_line_length = fuel_line_length

        self.piston_loss = piston_loss

        self.fuel_tank = fuel_tank
        self.oxidizer_tank = oxidizer_tank

    def get_mass_flow_ox(
        self,
        chamber_pressure: float,
        *,
        injector: injector_models.BipropellantInjector,
        oxidizer_mass: float,
    ) -> float:
        """
        Compute the current oxidizer mass flow rate by delegating to the injector.

        Args:
            chamber_pressure: Chamber pressure [Pa].
            injector: Bipropellant injector handling the orifice dispatch.
            oxidizer_mass: Current oxidizer mass in the tank [kg].

        Returns:
            Oxidizer mass flow rate [kg/s].
        """
        return injector.get_mass_flow_ox(
            tank=self.oxidizer_tank,
            pressure_upstream=self.get_oxidizer_tank_pressure(
                oxidizer_mass=oxidizer_mass
            ),
            chamber_pressure=chamber_pressure,
            fluid_mass=oxidizer_mass,
        )

    def get_mass_flow_fuel(
        self,
        chamber_pressure: float,
        *,
        injector: injector_models.BipropellantInjector,
        fuel_mass: float,
        oxidizer_mass: float,
    ) -> float:
        """
        Compute the current fuel mass flow rate by delegating to the injector.

        The upstream pressure is the oxidizer tank pressure minus the piston
        loss, since this models a stacked tank pressurized through the piston.

        Args:
            chamber_pressure: Chamber pressure [Pa].
            injector: Bipropellant injector handling the orifice dispatch.
            fuel_mass: Current fuel mass in the tank [kg].
            oxidizer_mass: Current oxidizer mass in the tank [kg].

        Returns:
            Fuel mass flow rate [kg/s].
        """
        return injector.get_mass_flow_fuel(
            tank=self.fuel_tank,
            pressure_upstream=self.get_fuel_tank_pressure(
                oxidizer_mass=oxidizer_mass, fuel_mass=fuel_mass
            ),
            chamber_pressure=chamber_pressure,
            fluid_mass=fuel_mass,
        )

    def get_oxidizer_tank_pressure(self, *, oxidizer_mass: float) -> float:
        """Returns the tank pressure [Pa]."""
        return self.oxidizer_tank.get_pressure(oxidizer_mass)

    def get_fuel_tank_pressure(
        self, *, oxidizer_mass: float, fuel_mass: float
    ) -> float:
        """
        Returns the fuel-side upstream pressure [Pa].

        In a stacked-tank system the fuel is pressurized by the oxidizer
        through the piston, so the fuel-side pressure is the oxidizer tank
        pressure minus the piston pressure loss; ``fuel_mass`` is unused here.
        """
        return (
            self.get_oxidizer_tank_pressure(oxidizer_mass=oxidizer_mass)
            - self.piston_loss
        )

__init__(oxidizer_line_diameter, oxidizer_line_length, fuel_line_diameter, fuel_line_length, fuel_tank, oxidizer_tank, piston_loss=0.0)

Initialize the StackedTankPressureFedFeedSystem.

Parameters:

Name Type Description Default
oxidizer_line_diameter float

Diameter of the oxidizer feedline [m].

required
oxidizer_line_length float

Length of the oxidizer feedline [m].

required
fuel_line_diameter float

Diameter of the fuel feedline [m].

required
fuel_line_length float

Length of the fuel feedline [m].

required
fuel_tank Tank

An instance representing the fuel tank.

required
oxidizer_tank Tank

An instance representing the oxidizer tank.

required
piston_loss float

Pressure loss across the piston [Pa]. Default is 0.0.

0.0
Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def __init__(
    self,
    oxidizer_line_diameter: float,
    oxidizer_line_length: float,
    fuel_line_diameter: float,
    fuel_line_length: float,
    fuel_tank: tank.Tank,
    oxidizer_tank: tank.Tank,
    piston_loss: float = 0.0,
):
    """
    Initialize the StackedTankPressureFedFeedSystem.

    Args:
        oxidizer_line_diameter: Diameter of the oxidizer feedline [m].
        oxidizer_line_length: Length of the oxidizer feedline [m].
        fuel_line_diameter: Diameter of the fuel feedline [m].
        fuel_line_length: Length of the fuel feedline [m].
        fuel_tank: An instance representing the fuel tank.
        oxidizer_tank: An instance representing the oxidizer tank.
        piston_loss: Pressure loss across the piston [Pa]. Default is 0.0.
    """
    super().__init__(fuel_tank, oxidizer_tank)

    self.oxidizer_line_diameter = oxidizer_line_diameter
    self.oxidizer_line_length = oxidizer_line_length
    self.fuel_line_diameter = fuel_line_diameter
    self.fuel_line_length = fuel_line_length

    self.piston_loss = piston_loss

    self.fuel_tank = fuel_tank
    self.oxidizer_tank = oxidizer_tank

get_fuel_tank_pressure(*, oxidizer_mass, fuel_mass)

Returns the fuel-side upstream pressure [Pa].

In a stacked-tank system the fuel is pressurized by the oxidizer through the piston, so the fuel-side pressure is the oxidizer tank pressure minus the piston pressure loss; fuel_mass is unused here.

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def get_fuel_tank_pressure(
    self, *, oxidizer_mass: float, fuel_mass: float
) -> float:
    """
    Returns the fuel-side upstream pressure [Pa].

    In a stacked-tank system the fuel is pressurized by the oxidizer
    through the piston, so the fuel-side pressure is the oxidizer tank
    pressure minus the piston pressure loss; ``fuel_mass`` is unused here.
    """
    return (
        self.get_oxidizer_tank_pressure(oxidizer_mass=oxidizer_mass)
        - self.piston_loss
    )

get_mass_flow_fuel(chamber_pressure, *, injector, fuel_mass, oxidizer_mass)

Compute the current fuel mass flow rate by delegating to the injector.

The upstream pressure is the oxidizer tank pressure minus the piston loss, since this models a stacked tank pressurized through the piston.

Parameters:

Name Type Description Default
chamber_pressure float

Chamber pressure [Pa].

required
injector BipropellantInjector

Bipropellant injector handling the orifice dispatch.

required
fuel_mass float

Current fuel mass in the tank [kg].

required
oxidizer_mass float

Current oxidizer mass in the tank [kg].

required

Returns:

Type Description
float

Fuel mass flow rate [kg/s].

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def get_mass_flow_fuel(
    self,
    chamber_pressure: float,
    *,
    injector: injector_models.BipropellantInjector,
    fuel_mass: float,
    oxidizer_mass: float,
) -> float:
    """
    Compute the current fuel mass flow rate by delegating to the injector.

    The upstream pressure is the oxidizer tank pressure minus the piston
    loss, since this models a stacked tank pressurized through the piston.

    Args:
        chamber_pressure: Chamber pressure [Pa].
        injector: Bipropellant injector handling the orifice dispatch.
        fuel_mass: Current fuel mass in the tank [kg].
        oxidizer_mass: Current oxidizer mass in the tank [kg].

    Returns:
        Fuel mass flow rate [kg/s].
    """
    return injector.get_mass_flow_fuel(
        tank=self.fuel_tank,
        pressure_upstream=self.get_fuel_tank_pressure(
            oxidizer_mass=oxidizer_mass, fuel_mass=fuel_mass
        ),
        chamber_pressure=chamber_pressure,
        fluid_mass=fuel_mass,
    )

get_mass_flow_ox(chamber_pressure, *, injector, oxidizer_mass)

Compute the current oxidizer mass flow rate by delegating to the injector.

Parameters:

Name Type Description Default
chamber_pressure float

Chamber pressure [Pa].

required
injector BipropellantInjector

Bipropellant injector handling the orifice dispatch.

required
oxidizer_mass float

Current oxidizer mass in the tank [kg].

required

Returns:

Type Description
float

Oxidizer mass flow rate [kg/s].

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def get_mass_flow_ox(
    self,
    chamber_pressure: float,
    *,
    injector: injector_models.BipropellantInjector,
    oxidizer_mass: float,
) -> float:
    """
    Compute the current oxidizer mass flow rate by delegating to the injector.

    Args:
        chamber_pressure: Chamber pressure [Pa].
        injector: Bipropellant injector handling the orifice dispatch.
        oxidizer_mass: Current oxidizer mass in the tank [kg].

    Returns:
        Oxidizer mass flow rate [kg/s].
    """
    return injector.get_mass_flow_ox(
        tank=self.oxidizer_tank,
        pressure_upstream=self.get_oxidizer_tank_pressure(
            oxidizer_mass=oxidizer_mass
        ),
        chamber_pressure=chamber_pressure,
        fluid_mass=oxidizer_mass,
    )

get_oxidizer_tank_pressure(*, oxidizer_mass)

Returns the tank pressure [Pa].

Source code in machwave/models/feed_systems/cycles/stacked_tank_pressure_fed.py
def get_oxidizer_tank_pressure(self, *, oxidizer_mass: float) -> float:
    """Returns the tank pressure [Pa]."""
    return self.oxidizer_tank.get_pressure(oxidizer_mass)