Skip to content

models.propulsion.motors

machwave.models.propulsion.motors

LiquidEngine

Bases: Motor[BiliquidPropellant, LiquidEngineThrustChamber]

Source code in machwave/models/propulsion/motors/liquid.py
class LiquidEngine(Motor[BiliquidPropellant, LiquidEngineThrustChamber]):
    def __init__(
        self,
        propellant: BiliquidPropellant,
        thrust_chamber: LiquidEngineThrustChamber,
        feed_system: FeedSystem,
        oxidizer_tank_cog: float | None = None,
        fuel_tank_cog: float | None = None,
        other_losses: float = 12.0,
    ) -> None:
        """
        Initialize a liquid rocket engine.

        Args:
            propellant: Bi-liquid propellant properties (oxidizer + fuel).
            thrust_chamber: Thrust chamber assembly (nozzle + combustion chamber + injector).
            feed_system: Propellant feed system (tanks, lines, pumps/pressurization).
            oxidizer_tank_cog: Axial position of the oxidizer tank center (where propellant CoG is),
                measured from the nozzle exit, in meters. If None, uses a default estimate.
            fuel_tank_cog: Axial position of the fuel tank center (where propellant CoG is),
                measured from the nozzle exit, in meters. If None, uses a default estimate.
            other_losses: Additional engine losses not accounted for by specific
                loss mechanisms, in percent. Defaults to 12%.
        """
        super().__init__(propellant, thrust_chamber, other_losses)
        self.feed_system = feed_system
        self.oxidizer_tank_cog = oxidizer_tank_cog
        self.fuel_tank_cog = fuel_tank_cog

    @property
    def initial_propellant_mass(self) -> float:
        """
        Returns the initial propellant mass in kg.
        """
        return self.feed_system.get_propellant_mass()

    def get_launch_mass(self) -> float:
        return self.thrust_chamber.dry_mass + self.initial_propellant_mass

    def get_dry_mass(self) -> float:
        return self.thrust_chamber.dry_mass

    def get_center_of_gravity(
        self, propellant_fraction: float = 0.0
    ) -> np.typing.NDArray[np.float64]:
        """
        Calculate the center of gravity of the liquid engine including structural
        dry mass, oxidizer, and fuel.

        The calculation uses a mass-weighted average of:
        1. Structural dry mass (thrust chamber, tanks structure, feed lines, etc.)
        2. Oxidizer mass (from oxidizer tank)
        3. Fuel mass (from fuel tank)

        Args:
            propellant_fraction: Fraction of propellant consumed (0.0 = full, 1.0 = empty).

        Returns:
            Center of gravity in 3D space [x, y, z], in meters.
            Origin is at the nozzle exit on the chamber axis.
            Positive x-direction points forward (toward bulkhead/away from nozzle exit).

        Raises:
            ValueError: If thrust_chamber.center_of_gravity_coordinate, oxidizer_tank_cog,
                or fuel_tank_cog is not defined.
        """
        # Get current propellant masses
        initial_ox_mass = self.feed_system.oxidizer_tank.initial_fluid_mass
        initial_fuel_mass = self.feed_system.fuel_tank.initial_fluid_mass

        ox_mass = initial_ox_mass * (1.0 - propellant_fraction)
        fuel_mass = initial_fuel_mass * (1.0 - propellant_fraction)

        # Structural dry mass (thrust chamber, tank structure, feed lines, etc.)
        dry_mass = self.thrust_chamber.dry_mass

        if self.thrust_chamber.center_of_gravity_coordinate is None:
            raise ValueError(
                "Thrust chamber center of gravity coordinate is not defined."
            )

        dry_cog = self.thrust_chamber.center_of_gravity_coordinate

        # Oxidizer tank CoG
        if self.oxidizer_tank_cog is None:
            raise ValueError("Oxidizer tank center of gravity is not defined.")

        ox_cog = np.array([self.oxidizer_tank_cog, 0.0, 0.0], dtype=np.float64)

        # Fuel tank CoG
        if self.fuel_tank_cog is None:
            raise ValueError("Fuel tank center of gravity is not defined.")

        fuel_cog = np.array([self.fuel_tank_cog, 0.0, 0.0], dtype=np.float64)

        # Calculate total mass and weighted CoG
        total_mass = dry_mass + ox_mass + fuel_mass

        if total_mass <= 0:
            # Fallback if calculation fails
            return dry_cog

        weighted_cog = (
            dry_cog * dry_mass + ox_cog * ox_mass + fuel_cog * fuel_mass
        ) / total_mass

        return weighted_cog.astype(np.float64)

    def get_thrust_coefficient(
        self,
        chamber_pressure: float,
        exit_pressure: float,
        external_pressure: float,
        expansion_ratio: float,
        k_ex: float,
        n_cf: float,
    ) -> float:
        """Get thrust coefficient.

        Args:
            chamber_pressure: Chamber pressure [Pa].
            exit_pressure: Exit pressure [Pa].
            external_pressure: External pressure [Pa].
            expansion_ratio: Expansion ratio.
            k_ex: Two-phase isentropic coefficient.
            n_cf: Thrust coefficient correction factor.

        Returns:
            Instantaneous thrust coefficient.
        """
        cf_ideal = get_ideal_thrust_coefficient(
            chamber_pressure=chamber_pressure,
            exit_pressure=exit_pressure,
            external_pressure=external_pressure,
            expansion_ratio=expansion_ratio,
            k_ex=k_ex,
        )
        n_cf = self.get_thrust_coefficient_correction_factor()
        return cf_ideal * n_cf

initial_propellant_mass property

Returns the initial propellant mass in kg.

__init__(propellant, thrust_chamber, feed_system, oxidizer_tank_cog=None, fuel_tank_cog=None, other_losses=12.0)

Initialize a liquid rocket engine.

Parameters:

Name Type Description Default
propellant BiliquidPropellant

Bi-liquid propellant properties (oxidizer + fuel).

required
thrust_chamber LiquidEngineThrustChamber

Thrust chamber assembly (nozzle + combustion chamber + injector).

required
feed_system FeedSystem

Propellant feed system (tanks, lines, pumps/pressurization).

required
oxidizer_tank_cog float | None

Axial position of the oxidizer tank center (where propellant CoG is), measured from the nozzle exit, in meters. If None, uses a default estimate.

None
fuel_tank_cog float | None

Axial position of the fuel tank center (where propellant CoG is), measured from the nozzle exit, in meters. If None, uses a default estimate.

None
other_losses float

Additional engine losses not accounted for by specific loss mechanisms, in percent. Defaults to 12%.

12.0
Source code in machwave/models/propulsion/motors/liquid.py
def __init__(
    self,
    propellant: BiliquidPropellant,
    thrust_chamber: LiquidEngineThrustChamber,
    feed_system: FeedSystem,
    oxidizer_tank_cog: float | None = None,
    fuel_tank_cog: float | None = None,
    other_losses: float = 12.0,
) -> None:
    """
    Initialize a liquid rocket engine.

    Args:
        propellant: Bi-liquid propellant properties (oxidizer + fuel).
        thrust_chamber: Thrust chamber assembly (nozzle + combustion chamber + injector).
        feed_system: Propellant feed system (tanks, lines, pumps/pressurization).
        oxidizer_tank_cog: Axial position of the oxidizer tank center (where propellant CoG is),
            measured from the nozzle exit, in meters. If None, uses a default estimate.
        fuel_tank_cog: Axial position of the fuel tank center (where propellant CoG is),
            measured from the nozzle exit, in meters. If None, uses a default estimate.
        other_losses: Additional engine losses not accounted for by specific
            loss mechanisms, in percent. Defaults to 12%.
    """
    super().__init__(propellant, thrust_chamber, other_losses)
    self.feed_system = feed_system
    self.oxidizer_tank_cog = oxidizer_tank_cog
    self.fuel_tank_cog = fuel_tank_cog

get_center_of_gravity(propellant_fraction=0.0)

Calculate the center of gravity of the liquid engine including structural dry mass, oxidizer, and fuel.

The calculation uses a mass-weighted average of: 1. Structural dry mass (thrust chamber, tanks structure, feed lines, etc.) 2. Oxidizer mass (from oxidizer tank) 3. Fuel mass (from fuel tank)

Parameters:

Name Type Description Default
propellant_fraction float

Fraction of propellant consumed (0.0 = full, 1.0 = empty).

0.0

Returns:

Type Description
NDArray[float64]

Center of gravity in 3D space [x, y, z], in meters.

NDArray[float64]

Origin is at the nozzle exit on the chamber axis.

NDArray[float64]

Positive x-direction points forward (toward bulkhead/away from nozzle exit).

Raises:

Type Description
ValueError

If thrust_chamber.center_of_gravity_coordinate, oxidizer_tank_cog, or fuel_tank_cog is not defined.

Source code in machwave/models/propulsion/motors/liquid.py
def get_center_of_gravity(
    self, propellant_fraction: float = 0.0
) -> np.typing.NDArray[np.float64]:
    """
    Calculate the center of gravity of the liquid engine including structural
    dry mass, oxidizer, and fuel.

    The calculation uses a mass-weighted average of:
    1. Structural dry mass (thrust chamber, tanks structure, feed lines, etc.)
    2. Oxidizer mass (from oxidizer tank)
    3. Fuel mass (from fuel tank)

    Args:
        propellant_fraction: Fraction of propellant consumed (0.0 = full, 1.0 = empty).

    Returns:
        Center of gravity in 3D space [x, y, z], in meters.
        Origin is at the nozzle exit on the chamber axis.
        Positive x-direction points forward (toward bulkhead/away from nozzle exit).

    Raises:
        ValueError: If thrust_chamber.center_of_gravity_coordinate, oxidizer_tank_cog,
            or fuel_tank_cog is not defined.
    """
    # Get current propellant masses
    initial_ox_mass = self.feed_system.oxidizer_tank.initial_fluid_mass
    initial_fuel_mass = self.feed_system.fuel_tank.initial_fluid_mass

    ox_mass = initial_ox_mass * (1.0 - propellant_fraction)
    fuel_mass = initial_fuel_mass * (1.0 - propellant_fraction)

    # Structural dry mass (thrust chamber, tank structure, feed lines, etc.)
    dry_mass = self.thrust_chamber.dry_mass

    if self.thrust_chamber.center_of_gravity_coordinate is None:
        raise ValueError(
            "Thrust chamber center of gravity coordinate is not defined."
        )

    dry_cog = self.thrust_chamber.center_of_gravity_coordinate

    # Oxidizer tank CoG
    if self.oxidizer_tank_cog is None:
        raise ValueError("Oxidizer tank center of gravity is not defined.")

    ox_cog = np.array([self.oxidizer_tank_cog, 0.0, 0.0], dtype=np.float64)

    # Fuel tank CoG
    if self.fuel_tank_cog is None:
        raise ValueError("Fuel tank center of gravity is not defined.")

    fuel_cog = np.array([self.fuel_tank_cog, 0.0, 0.0], dtype=np.float64)

    # Calculate total mass and weighted CoG
    total_mass = dry_mass + ox_mass + fuel_mass

    if total_mass <= 0:
        # Fallback if calculation fails
        return dry_cog

    weighted_cog = (
        dry_cog * dry_mass + ox_cog * ox_mass + fuel_cog * fuel_mass
    ) / total_mass

    return weighted_cog.astype(np.float64)

get_thrust_coefficient(chamber_pressure, exit_pressure, external_pressure, expansion_ratio, k_ex, n_cf)

Get thrust coefficient.

Parameters:

Name Type Description Default
chamber_pressure float

Chamber pressure [Pa].

required
exit_pressure float

Exit pressure [Pa].

required
external_pressure float

External pressure [Pa].

required
expansion_ratio float

Expansion ratio.

required
k_ex float

Two-phase isentropic coefficient.

required
n_cf float

Thrust coefficient correction factor.

required

Returns:

Type Description
float

Instantaneous thrust coefficient.

Source code in machwave/models/propulsion/motors/liquid.py
def get_thrust_coefficient(
    self,
    chamber_pressure: float,
    exit_pressure: float,
    external_pressure: float,
    expansion_ratio: float,
    k_ex: float,
    n_cf: float,
) -> float:
    """Get thrust coefficient.

    Args:
        chamber_pressure: Chamber pressure [Pa].
        exit_pressure: Exit pressure [Pa].
        external_pressure: External pressure [Pa].
        expansion_ratio: Expansion ratio.
        k_ex: Two-phase isentropic coefficient.
        n_cf: Thrust coefficient correction factor.

    Returns:
        Instantaneous thrust coefficient.
    """
    cf_ideal = get_ideal_thrust_coefficient(
        chamber_pressure=chamber_pressure,
        exit_pressure=exit_pressure,
        external_pressure=external_pressure,
        expansion_ratio=expansion_ratio,
        k_ex=k_ex,
    )
    n_cf = self.get_thrust_coefficient_correction_factor()
    return cf_ideal * n_cf

Motor

Bases: Generic[P, T], ABC

Abstract rocket motor/engine class. Can be used to model any chemical rocket propulsion system, such as Solid, Hybrid and Liquid.

Source code in machwave/models/propulsion/motors/base.py
class Motor(Generic[P, T], ABC):
    """
    Abstract rocket motor/engine class. Can be used to model any chemical
    rocket propulsion system, such as Solid, Hybrid and Liquid.
    """

    def __init__(
        self,
        propellant: P,
        thrust_chamber: T,
        other_losses: float = DEFAULT_OTHER_MOTOR_LOSSES,
    ) -> None:
        """
        Instantiates object attributes common to any motor/engine (Solid,
        Hybrid or Liquid).

        Args:
            propellant: Object representing the propellant used in the motor.
            thrust_chamber: Object representing the thrust chamber of the motor.
            other_losses: Other motor losses, in percent.
        """
        self.propellant = propellant
        self.thrust_chamber = thrust_chamber

        self.other_losses = other_losses

    @abstractmethod
    def get_launch_mass(self) -> float:
        """
        Calculates the total mass of the motor before launch.

        Returns:
            Total mass of the motor before launch, in kg
        """
        pass

    @abstractmethod
    def get_dry_mass(self) -> float:
        """
        Calculates the dry mass of the rocket at any time.

        Returns:
            Dry mass of the rocket, in kg
        """
        pass

    @abstractmethod
    def get_center_of_gravity(self, *args, **kwargs) -> np.typing.NDArray[np.float64]:
        """
        Calculate the center of gravity of the propulsion system.

        The coordinate system origin corresponds to the combustion chamber axis
        at the nozzle exit plane, with positive x pointing toward the bulkhead.

        Returns:
            A 1D array of shape (3,) representing the [x, y, z] coordinates of
            the center of gravity, in meters.
        """
        pass

    def get_thrust_coefficient_correction_factor(self, *args, **kwargs) -> float:
        """
        Calculates the thrust coefficient correction factor. This factor is
        adimensional and should be applied to the ideal thrust coefficient to
        get the real thrust coefficient.

        Returns:
            Thrust coefficient correction factor
        """
        return (
            (100.0 - self.other_losses) / 100.0
        ) * self.propellant.combustion_efficiency

    @abstractmethod
    def get_thrust_coefficient(self, *args, **kwargs) -> float:
        """
        Calculates the thrust coefficient at a particular instant.

        Returns:
            Thrust coefficient
        """
        pass

    @property
    @abstractmethod
    def initial_propellant_mass(self) -> float:
        """
        Returns:
            Initial propellant mass, in kg
        """
        pass

    def get_thrust(self, cf: float, chamber_pressure: float) -> float:
        """
        Calculates the thrust based on instantaneous thrust coefficient and
        chamber pressure.

        Utilized nozzle throat area from the structure and nozzle classes.

        Args:
            cf: Instantaneous thrust coefficient, adimensional
            chamber_pressure: Instantaneous chamber pressure, in Pa

        Returns:
            Instantaneous thrust, in Newtons
        """
        return get_thrust_from_thrust_coefficient(
            cf,
            chamber_pressure,
            self.thrust_chamber.nozzle.get_throat_area(),
        )

initial_propellant_mass abstractmethod property

Returns:

Type Description
float

Initial propellant mass, in kg

__init__(propellant, thrust_chamber, other_losses=DEFAULT_OTHER_MOTOR_LOSSES)

Instantiates object attributes common to any motor/engine (Solid, Hybrid or Liquid).

Parameters:

Name Type Description Default
propellant P

Object representing the propellant used in the motor.

required
thrust_chamber T

Object representing the thrust chamber of the motor.

required
other_losses float

Other motor losses, in percent.

DEFAULT_OTHER_MOTOR_LOSSES
Source code in machwave/models/propulsion/motors/base.py
def __init__(
    self,
    propellant: P,
    thrust_chamber: T,
    other_losses: float = DEFAULT_OTHER_MOTOR_LOSSES,
) -> None:
    """
    Instantiates object attributes common to any motor/engine (Solid,
    Hybrid or Liquid).

    Args:
        propellant: Object representing the propellant used in the motor.
        thrust_chamber: Object representing the thrust chamber of the motor.
        other_losses: Other motor losses, in percent.
    """
    self.propellant = propellant
    self.thrust_chamber = thrust_chamber

    self.other_losses = other_losses

get_center_of_gravity(*args, **kwargs) abstractmethod

Calculate the center of gravity of the propulsion system.

The coordinate system origin corresponds to the combustion chamber axis at the nozzle exit plane, with positive x pointing toward the bulkhead.

Returns:

Type Description
NDArray[float64]

A 1D array of shape (3,) representing the [x, y, z] coordinates of

NDArray[float64]

the center of gravity, in meters.

Source code in machwave/models/propulsion/motors/base.py
@abstractmethod
def get_center_of_gravity(self, *args, **kwargs) -> np.typing.NDArray[np.float64]:
    """
    Calculate the center of gravity of the propulsion system.

    The coordinate system origin corresponds to the combustion chamber axis
    at the nozzle exit plane, with positive x pointing toward the bulkhead.

    Returns:
        A 1D array of shape (3,) representing the [x, y, z] coordinates of
        the center of gravity, in meters.
    """
    pass

get_dry_mass() abstractmethod

Calculates the dry mass of the rocket at any time.

Returns:

Type Description
float

Dry mass of the rocket, in kg

Source code in machwave/models/propulsion/motors/base.py
@abstractmethod
def get_dry_mass(self) -> float:
    """
    Calculates the dry mass of the rocket at any time.

    Returns:
        Dry mass of the rocket, in kg
    """
    pass

get_launch_mass() abstractmethod

Calculates the total mass of the motor before launch.

Returns:

Type Description
float

Total mass of the motor before launch, in kg

Source code in machwave/models/propulsion/motors/base.py
@abstractmethod
def get_launch_mass(self) -> float:
    """
    Calculates the total mass of the motor before launch.

    Returns:
        Total mass of the motor before launch, in kg
    """
    pass

get_thrust(cf, chamber_pressure)

Calculates the thrust based on instantaneous thrust coefficient and chamber pressure.

Utilized nozzle throat area from the structure and nozzle classes.

Parameters:

Name Type Description Default
cf float

Instantaneous thrust coefficient, adimensional

required
chamber_pressure float

Instantaneous chamber pressure, in Pa

required

Returns:

Type Description
float

Instantaneous thrust, in Newtons

Source code in machwave/models/propulsion/motors/base.py
def get_thrust(self, cf: float, chamber_pressure: float) -> float:
    """
    Calculates the thrust based on instantaneous thrust coefficient and
    chamber pressure.

    Utilized nozzle throat area from the structure and nozzle classes.

    Args:
        cf: Instantaneous thrust coefficient, adimensional
        chamber_pressure: Instantaneous chamber pressure, in Pa

    Returns:
        Instantaneous thrust, in Newtons
    """
    return get_thrust_from_thrust_coefficient(
        cf,
        chamber_pressure,
        self.thrust_chamber.nozzle.get_throat_area(),
    )

get_thrust_coefficient(*args, **kwargs) abstractmethod

Calculates the thrust coefficient at a particular instant.

Returns:

Type Description
float

Thrust coefficient

Source code in machwave/models/propulsion/motors/base.py
@abstractmethod
def get_thrust_coefficient(self, *args, **kwargs) -> float:
    """
    Calculates the thrust coefficient at a particular instant.

    Returns:
        Thrust coefficient
    """
    pass

get_thrust_coefficient_correction_factor(*args, **kwargs)

Calculates the thrust coefficient correction factor. This factor is adimensional and should be applied to the ideal thrust coefficient to get the real thrust coefficient.

Returns:

Type Description
float

Thrust coefficient correction factor

Source code in machwave/models/propulsion/motors/base.py
def get_thrust_coefficient_correction_factor(self, *args, **kwargs) -> float:
    """
    Calculates the thrust coefficient correction factor. This factor is
    adimensional and should be applied to the ideal thrust coefficient to
    get the real thrust coefficient.

    Returns:
        Thrust coefficient correction factor
    """
    return (
        (100.0 - self.other_losses) / 100.0
    ) * self.propellant.combustion_efficiency

SolidMotor

Bases: Motor[SolidPropellant, SolidMotorThrustChamber]

Source code in machwave/models/propulsion/motors/solid.py
class SolidMotor(
    motor_base.Motor[
        propellants.SolidPropellant, thrust_chamber.SolidMotorThrustChamber
    ]
):
    def __init__(
        self,
        grain: grain.Grain,
        propellant: propellants.SolidPropellant,
        thrust_chamber: thrust_chamber.SolidMotorThrustChamber,
        other_losses: float = motor_base.DEFAULT_OTHER_MOTOR_LOSSES,
    ) -> None:
        """
        Initialize a solid rocket motor.

        Args:
            grain: Grain geometry configuration.
            propellant: Solid propellant properties.
            thrust_chamber: Thrust chamber model.
            other_losses: Additional motor losses not accounted for by specific
                loss mechanisms (0-1), defaults to 0.12 (12%).
        """
        super().__init__(propellant, thrust_chamber, other_losses)

        self.grain = grain
        self.propellant: propellants.SolidPropellant = propellant
        self.cf_ideal = None  # ideal thrust coefficient
        self.cf_real = None  # real thrust coefficient

    def get_free_chamber_volume(self, propellant_volume: float) -> float:
        """
        Calculates the chamber volume without any propellant.

        Args:
            propellant_volume: Propellant volume, in m^3

        Returns:
            Free chamber volume, in m^3
        """
        return (
            self.thrust_chamber.combustion_chamber.internal_volume - propellant_volume
        )

    @property
    def initial_propellant_mass(self) -> float:
        """
        Returns:
            Initial propellant mass, in kg
        """
        return self.grain.get_propellant_mass(
            web_distance=0, ideal_density=self.propellant.ideal_density
        )

    def get_thrust_coefficient_correction_factor(
        self, n_kin: float, n_bl: float, n_tp: float
    ) -> float:
        """
        Calculates the thrust coefficient correction factor including all
        losses.

        Args:
            n_kin: Kinematic correction factor, adimensional, in percent
            n_bl: Boundary layer correction factor, adimensional, in percent
            n_tp: Two-phase correction factor, adimensional, in percent

        Returns:
            Thrust coefficient correction factor, adimensional
        """
        return (
            (100 - (n_kin + n_bl + n_tp + self.other_losses))
            * losses.get_nozzle_divergent_percentage_loss(
                self.thrust_chamber.nozzle.throat_diameter
            )
            / 100
            * self.propellant.combustion_efficiency
        )

    def get_thrust_coefficient(
        self,
        chamber_pressure: float,
        exit_pressure: float,
        external_pressure: float,
        expansion_ratio: float,
        k_ex: float,
        n_cf: float,
    ) -> float:
        """
        Args:
            chamber_pressure: Chamber pressure, in Pa
            exit_pressure: Exit pressure, in Pa
            external_pressure: External pressure, in Pa
            expansion_ratio: Expansion ratio, adimensional
            k_ex: Two-phase isentropic coefficient, adimensional
            n_cf: Thrust coefficient correction factor, adimensional

        Returns:
            Instanteneous thrust coefficient, adimensional
        """
        self.cf_ideal = nozzle.get_ideal_thrust_coefficient(
            chamber_pressure,
            exit_pressure,
            external_pressure,
            expansion_ratio,
            k_ex,
        )
        self.cf_real = nozzle.apply_thrust_coefficient_correction(self.cf_ideal, n_cf)
        return self.cf_real

    def get_launch_mass(self) -> float:
        return self.thrust_chamber.dry_mass + self.initial_propellant_mass

    def get_dry_mass(self) -> float:
        return self.thrust_chamber.dry_mass

    def get_center_of_gravity(
        self, web_distance: float = 0.0
    ) -> np.typing.NDArray[np.float64]:
        """
        Calculates the center of gravity of the solid motor including
        propellant grain (wet mass) and dry mass.

        The calculation uses a mass-weighted average of:
        1. Propellant grain CoG;
        2. Thrust chamber dry mass CoG, considered constant.

        Args:
            web_distance: Web distance traveled [m].
                Defaults to ignition state.

        Returns:
            Center of gravity in 3D space (x, y, z) [m].

        Raises:
            ValueError: If thrust chamber dry mass CoG is not defined or if
                total mass is less than or equal to zero.
        """
        grain_cog_port = self.grain.get_center_of_gravity(web_distance=web_distance)
        propellant_mass = self.grain.get_propellant_mass(
            web_distance=web_distance, ideal_density=self.propellant.ideal_density
        )

        dry_mass = self.thrust_chamber.dry_mass
        dry_mass_cog = self.thrust_chamber.center_of_gravity_coordinate
        nozzle_exit_to_port = self.thrust_chamber.nozzle_exit_to_grain_port_distance

        # Transform grain CoG from port origin to nozzle exit origin
        grain_cog = grain_cog_port.copy()
        grain_cog[0] = nozzle_exit_to_port + grain_cog_port[0]

        if dry_mass_cog is None:
            raise ValueError("Dry mass center of gravity coordinate is not defined.")

        total_mass = propellant_mass + dry_mass
        if total_mass <= 0:
            raise ValueError("Total mass must be greater than zero to calculate CoG.")

        weighted_cog = (
            grain_cog * propellant_mass + dry_mass_cog * dry_mass
        ) / total_mass

        return weighted_cog.astype(np.float64)

initial_propellant_mass property

Returns:

Type Description
float

Initial propellant mass, in kg

__init__(grain, propellant, thrust_chamber, other_losses=motor_base.DEFAULT_OTHER_MOTOR_LOSSES)

Initialize a solid rocket motor.

Parameters:

Name Type Description Default
grain Grain

Grain geometry configuration.

required
propellant SolidPropellant

Solid propellant properties.

required
thrust_chamber SolidMotorThrustChamber

Thrust chamber model.

required
other_losses float

Additional motor losses not accounted for by specific loss mechanisms (0-1), defaults to 0.12 (12%).

DEFAULT_OTHER_MOTOR_LOSSES
Source code in machwave/models/propulsion/motors/solid.py
def __init__(
    self,
    grain: grain.Grain,
    propellant: propellants.SolidPropellant,
    thrust_chamber: thrust_chamber.SolidMotorThrustChamber,
    other_losses: float = motor_base.DEFAULT_OTHER_MOTOR_LOSSES,
) -> None:
    """
    Initialize a solid rocket motor.

    Args:
        grain: Grain geometry configuration.
        propellant: Solid propellant properties.
        thrust_chamber: Thrust chamber model.
        other_losses: Additional motor losses not accounted for by specific
            loss mechanisms (0-1), defaults to 0.12 (12%).
    """
    super().__init__(propellant, thrust_chamber, other_losses)

    self.grain = grain
    self.propellant: propellants.SolidPropellant = propellant
    self.cf_ideal = None  # ideal thrust coefficient
    self.cf_real = None  # real thrust coefficient

get_center_of_gravity(web_distance=0.0)

Calculates the center of gravity of the solid motor including propellant grain (wet mass) and dry mass.

The calculation uses a mass-weighted average of: 1. Propellant grain CoG; 2. Thrust chamber dry mass CoG, considered constant.

Parameters:

Name Type Description Default
web_distance float

Web distance traveled [m]. Defaults to ignition state.

0.0

Returns:

Type Description
NDArray[float64]

Center of gravity in 3D space (x, y, z) [m].

Raises:

Type Description
ValueError

If thrust chamber dry mass CoG is not defined or if total mass is less than or equal to zero.

Source code in machwave/models/propulsion/motors/solid.py
def get_center_of_gravity(
    self, web_distance: float = 0.0
) -> np.typing.NDArray[np.float64]:
    """
    Calculates the center of gravity of the solid motor including
    propellant grain (wet mass) and dry mass.

    The calculation uses a mass-weighted average of:
    1. Propellant grain CoG;
    2. Thrust chamber dry mass CoG, considered constant.

    Args:
        web_distance: Web distance traveled [m].
            Defaults to ignition state.

    Returns:
        Center of gravity in 3D space (x, y, z) [m].

    Raises:
        ValueError: If thrust chamber dry mass CoG is not defined or if
            total mass is less than or equal to zero.
    """
    grain_cog_port = self.grain.get_center_of_gravity(web_distance=web_distance)
    propellant_mass = self.grain.get_propellant_mass(
        web_distance=web_distance, ideal_density=self.propellant.ideal_density
    )

    dry_mass = self.thrust_chamber.dry_mass
    dry_mass_cog = self.thrust_chamber.center_of_gravity_coordinate
    nozzle_exit_to_port = self.thrust_chamber.nozzle_exit_to_grain_port_distance

    # Transform grain CoG from port origin to nozzle exit origin
    grain_cog = grain_cog_port.copy()
    grain_cog[0] = nozzle_exit_to_port + grain_cog_port[0]

    if dry_mass_cog is None:
        raise ValueError("Dry mass center of gravity coordinate is not defined.")

    total_mass = propellant_mass + dry_mass
    if total_mass <= 0:
        raise ValueError("Total mass must be greater than zero to calculate CoG.")

    weighted_cog = (
        grain_cog * propellant_mass + dry_mass_cog * dry_mass
    ) / total_mass

    return weighted_cog.astype(np.float64)

get_free_chamber_volume(propellant_volume)

Calculates the chamber volume without any propellant.

Parameters:

Name Type Description Default
propellant_volume float

Propellant volume, in m^3

required

Returns:

Type Description
float

Free chamber volume, in m^3

Source code in machwave/models/propulsion/motors/solid.py
def get_free_chamber_volume(self, propellant_volume: float) -> float:
    """
    Calculates the chamber volume without any propellant.

    Args:
        propellant_volume: Propellant volume, in m^3

    Returns:
        Free chamber volume, in m^3
    """
    return (
        self.thrust_chamber.combustion_chamber.internal_volume - propellant_volume
    )

get_thrust_coefficient(chamber_pressure, exit_pressure, external_pressure, expansion_ratio, k_ex, n_cf)

Parameters:

Name Type Description Default
chamber_pressure float

Chamber pressure, in Pa

required
exit_pressure float

Exit pressure, in Pa

required
external_pressure float

External pressure, in Pa

required
expansion_ratio float

Expansion ratio, adimensional

required
k_ex float

Two-phase isentropic coefficient, adimensional

required
n_cf float

Thrust coefficient correction factor, adimensional

required

Returns:

Type Description
float

Instanteneous thrust coefficient, adimensional

Source code in machwave/models/propulsion/motors/solid.py
def get_thrust_coefficient(
    self,
    chamber_pressure: float,
    exit_pressure: float,
    external_pressure: float,
    expansion_ratio: float,
    k_ex: float,
    n_cf: float,
) -> float:
    """
    Args:
        chamber_pressure: Chamber pressure, in Pa
        exit_pressure: Exit pressure, in Pa
        external_pressure: External pressure, in Pa
        expansion_ratio: Expansion ratio, adimensional
        k_ex: Two-phase isentropic coefficient, adimensional
        n_cf: Thrust coefficient correction factor, adimensional

    Returns:
        Instanteneous thrust coefficient, adimensional
    """
    self.cf_ideal = nozzle.get_ideal_thrust_coefficient(
        chamber_pressure,
        exit_pressure,
        external_pressure,
        expansion_ratio,
        k_ex,
    )
    self.cf_real = nozzle.apply_thrust_coefficient_correction(self.cf_ideal, n_cf)
    return self.cf_real

get_thrust_coefficient_correction_factor(n_kin, n_bl, n_tp)

Calculates the thrust coefficient correction factor including all losses.

Parameters:

Name Type Description Default
n_kin float

Kinematic correction factor, adimensional, in percent

required
n_bl float

Boundary layer correction factor, adimensional, in percent

required
n_tp float

Two-phase correction factor, adimensional, in percent

required

Returns:

Type Description
float

Thrust coefficient correction factor, adimensional

Source code in machwave/models/propulsion/motors/solid.py
def get_thrust_coefficient_correction_factor(
    self, n_kin: float, n_bl: float, n_tp: float
) -> float:
    """
    Calculates the thrust coefficient correction factor including all
    losses.

    Args:
        n_kin: Kinematic correction factor, adimensional, in percent
        n_bl: Boundary layer correction factor, adimensional, in percent
        n_tp: Two-phase correction factor, adimensional, in percent

    Returns:
        Thrust coefficient correction factor, adimensional
    """
    return (
        (100 - (n_kin + n_bl + n_tp + self.other_losses))
        * losses.get_nozzle_divergent_percentage_loss(
            self.thrust_chamber.nozzle.throat_diameter
        )
        / 100
        * self.propellant.combustion_efficiency
    )