From ebb0365f6fd25cad87039f1b08a698467049e7a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 19:01:38 +0000 Subject: [PATCH 01/34] ENH: implement multistage mission architecture (body, mission, FlightBranch) Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7aeaec51-7514-4046-a8b0-037324036f38 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/__init__.py | 19 +- rocketpy/body/__init__.py | 7 + rocketpy/body/body_like.py | 150 ++++++ rocketpy/body/flight_body.py | 163 +++++++ rocketpy/body/rocket_adapter.py | 171 +++++++ rocketpy/mission/__init__.py | 35 ++ rocketpy/mission/attached_item.py | 52 ++ rocketpy/mission/attachment.py | 79 +++ rocketpy/mission/deployable.py | 78 +++ rocketpy/mission/events.py | 197 ++++++++ rocketpy/mission/mission.py | 126 +++++ rocketpy/mission/parent_update.py | 44 ++ rocketpy/mission/separation_model.py | 66 +++ rocketpy/mission/stage.py | 89 ++++ rocketpy/mission/stage_state.py | 27 ++ rocketpy/rocket/rocket.py | 142 ++++++ rocketpy/simulation/__init__.py | 2 + rocketpy/simulation/flight_branch.py | 115 +++++ tests/unit/body/__init__.py | 0 tests/unit/body/test_body.py | 285 +++++++++++ tests/unit/mission/__init__.py | 0 tests/unit/mission/test_mission.py | 450 ++++++++++++++++++ .../rocket/test_rocket_attachment_host.py | 216 +++++++++ tests/unit/simulation/test_flight_branch.py | 130 +++++ 24 files changed, 2642 insertions(+), 1 deletion(-) create mode 100644 rocketpy/body/__init__.py create mode 100644 rocketpy/body/body_like.py create mode 100644 rocketpy/body/flight_body.py create mode 100644 rocketpy/body/rocket_adapter.py create mode 100644 rocketpy/mission/__init__.py create mode 100644 rocketpy/mission/attached_item.py create mode 100644 rocketpy/mission/attachment.py create mode 100644 rocketpy/mission/deployable.py create mode 100644 rocketpy/mission/events.py create mode 100644 rocketpy/mission/mission.py create mode 100644 rocketpy/mission/parent_update.py create mode 100644 rocketpy/mission/separation_model.py create mode 100644 rocketpy/mission/stage.py create mode 100644 rocketpy/mission/stage_state.py create mode 100644 rocketpy/simulation/flight_branch.py create mode 100644 tests/unit/body/__init__.py create mode 100644 tests/unit/body/test_body.py create mode 100644 tests/unit/mission/__init__.py create mode 100644 tests/unit/mission/test_mission.py create mode 100644 tests/unit/rocket/test_rocket_attachment_host.py create mode 100644 tests/unit/simulation/test_flight_branch.py diff --git a/rocketpy/__init__.py b/rocketpy/__init__.py index 129317bea..e23be4aad 100644 --- a/rocketpy/__init__.py +++ b/rocketpy/__init__.py @@ -1,3 +1,4 @@ +from .body import BodyLike, FlightBody, RocketAdapter from .control import _Controller from .environment import Environment, EnvironmentAnalysis from .mathutils import ( @@ -6,6 +7,22 @@ funcify_method, reset_funcified_methods, ) +from .mission import ( + Attachment, + Deployable, + DeploymentEvent, + Event, + IgnitionEvent, + InstantaneousSeparation, + Mission, + NoOpParentUpdate, + ParentUpdate, + RecoveryEvent, + SeparationModel, + Stage, + StageSeparationEvent, + StageState, +) from .motors import ( CylindricalTank, EmptyMotor, @@ -48,7 +65,7 @@ ) from .sensitivity import SensitivityModel from .sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope -from .simulation import Flight, MonteCarlo, MultivariateRejectionSampler +from .simulation import Flight, FlightBranch, MonteCarlo, MultivariateRejectionSampler from .stochastic import ( CustomSampler, StochasticAirBrakes, diff --git a/rocketpy/body/__init__.py b/rocketpy/body/__init__.py new file mode 100644 index 000000000..9c26820fa --- /dev/null +++ b/rocketpy/body/__init__.py @@ -0,0 +1,7 @@ +"""rocketpy.body – body abstraction layer for multistage simulation.""" + +from rocketpy.body.body_like import BodyLike +from rocketpy.body.flight_body import FlightBody +from rocketpy.body.rocket_adapter import RocketAdapter + +__all__ = ["BodyLike", "FlightBody", "RocketAdapter"] diff --git a/rocketpy/body/body_like.py b/rocketpy/body/body_like.py new file mode 100644 index 000000000..4a2c83468 --- /dev/null +++ b/rocketpy/body/body_like.py @@ -0,0 +1,150 @@ +"""BodyLike interface for the multistage mission architecture.""" + +from abc import ABC, abstractmethod + + +class BodyLike(ABC): + """Abstract interface that every flight-ready body must satisfy. + + Any object that can be integrated by the Flight simulation engine + must implement this interface. Both the native :class:`FlightBody` + and the :class:`RocketAdapter` (which wraps a legacy :class:`Rocket`) + implement it, so that the simulation layer can treat them uniformly. + + Attributes + ---------- + name : str + Human-readable identifier for the body. + """ + + @property + @abstractmethod + def name(self) -> str: + """Human-readable name of the body. + + Returns + ------- + str + Body name. + """ + + @abstractmethod + def mass(self, t: float) -> float: + """Total mass of the body at time *t*, in kg. + + Parameters + ---------- + t : float + Simulation time in seconds. + + Returns + ------- + float + Total mass in kg. + """ + + @abstractmethod + def inertia_tensor(self, t: float): + """Inertia tensor of the body at time *t*, in kg·m². + + Parameters + ---------- + t : float + Simulation time in seconds. + + Returns + ------- + array-like + 3×3 inertia tensor in kg·m². + """ + + @abstractmethod + def center_of_mass(self, t: float) -> float: + """Position of the center of mass along the body axis at time *t*. + + The position is measured in meters relative to the body's own + coordinate system origin. + + Parameters + ---------- + t : float + Simulation time in seconds. + + Returns + ------- + float + Center-of-mass position in meters. + """ + + @abstractmethod + def aerodynamic_model(self): + """Return the aerodynamic model attached to this body. + + Returns + ------- + object + Aerodynamic model instance. + """ + + @abstractmethod + def propulsion_model(self): + """Return the propulsion model attached to this body. + + Returns + ------- + object + Propulsion model instance. + """ + + @abstractmethod + def recovery_systems(self): + """Return the list of recovery systems attached to this body. + + Returns + ------- + list + Recovery systems. + """ + + @abstractmethod + def sensors(self): + """Return the list of sensors attached to this body. + + Returns + ------- + list + Sensor instances. + """ + + @abstractmethod + def controllers(self): + """Return the list of active controllers attached to this body. + + Returns + ------- + list + Controller instances. + """ + + @abstractmethod + def coordinate_system_orientation(self) -> str: + """Orientation convention for the body's coordinate system. + + Returns + ------- + str + One of ``"tail_to_nose"`` or ``"nose_to_tail"``. + """ + + @abstractmethod + def to_branch_ready_copy(self): + """Return a simulation-ready deep copy of this body. + + The copy is detached from user-facing state so that the + simulation engine can mutate it freely without side-effects. + + Returns + ------- + BodyLike + A deep copy suitable for a :class:`FlightBranch`. + """ diff --git a/rocketpy/body/flight_body.py b/rocketpy/body/flight_body.py new file mode 100644 index 000000000..30d49cd68 --- /dev/null +++ b/rocketpy/body/flight_body.py @@ -0,0 +1,163 @@ +"""FlightBody – a first-class, fully configurable flight body.""" + +from copy import deepcopy + +from rocketpy.body.body_like import BodyLike + + +class FlightBody(BodyLike): + """A fully configurable flight body composed of interchangeable models. + + Unlike :class:`~rocketpy.rocket.Rocket`, which is a rich user-facing + builder object, :class:`FlightBody` is designed as a clean value object + that holds just enough information for the simulation engine to integrate + the equations of motion. Users who need the full Rocket builder + experience can convert a :class:`~rocketpy.rocket.Rocket` to a + :class:`FlightBody` via :class:`RocketAdapter`. + + Parameters + ---------- + name : str + Human-readable name for this body. + geometry : object + External geometry description (e.g. a reference radius value or a + geometry model object). + mass_model : callable + Callable ``mass_model(t) -> float`` returning total mass in kg at + time *t* (seconds). + inertia_model : callable + Callable ``inertia_model(t)`` returning a 3×3 inertia tensor (kg·m²) + at time *t*. + center_of_mass_model : callable + Callable ``center_of_mass_model(t) -> float`` returning the axial + position of the center of mass in meters at time *t*. + aero_model : object, optional + Aerodynamic model for the body. + propulsion : object, optional + Propulsion model for the body. + recovery_systems : list, optional + Recovery systems attached to this body. + sensors : list, optional + Sensors attached to this body. + controllers : list, optional + Active controllers attached to this body. + coordinate_system_orientation : str, optional + Orientation of the body's coordinate system. Must be + ``"tail_to_nose"`` (default) or ``"nose_to_tail"``. + + Attributes + ---------- + name : str + geometry : object + mass_model : callable + inertia_model : callable + aero_model : object + propulsion_model : object + recovery_systems : list + sensors : list + controllers : list + coordinate_system_orientation : str + """ + + def __init__( + self, + name, + geometry, + mass_model, + inertia_model, + center_of_mass_model, + aero_model=None, + propulsion=None, + recovery_systems=None, + sensors=None, + controllers=None, + coordinate_system_orientation="tail_to_nose", + ): + self._name = name + self.geometry = geometry + self.mass_model = mass_model + self.inertia_model = inertia_model + self._center_of_mass_model = center_of_mass_model + self.aero_model = aero_model + self._propulsion_model = propulsion + self._recovery_systems = list(recovery_systems or []) + self._sensors = list(sensors or []) + self._controllers = list(controllers or []) + self._coordinate_system_orientation = coordinate_system_orientation + + # ------------------------------------------------------------------ + # BodyLike interface + # ------------------------------------------------------------------ + + @property + def name(self) -> str: + return self._name + + def mass(self, t: float) -> float: + """Total body mass at time *t*, in kg.""" + return self.mass_model(t) + + def inertia_tensor(self, t: float): + """3×3 inertia tensor at time *t*, in kg·m².""" + return self.inertia_model(t) + + def center_of_mass(self, t: float) -> float: + """Axial position of the center of mass at time *t*, in m.""" + return self._center_of_mass_model(t) + + def aerodynamic_model(self): + return self.aero_model + + def propulsion_model(self): + return self._propulsion_model + + def recovery_systems(self): + """Return the recovery systems attached to this body.""" + return self._recovery_systems + + def sensors(self): + return self._sensors + + def controllers(self): + return self._controllers + + def coordinate_system_orientation(self) -> str: + return self._coordinate_system_orientation + + def to_branch_ready_copy(self): + """Return a deep copy suitable for use in a :class:`FlightBranch`.""" + return deepcopy(self) + + # ------------------------------------------------------------------ + # Mutating helpers + # ------------------------------------------------------------------ + + def add_recovery_system(self, system): + """Attach a recovery system to this body. + + Parameters + ---------- + system : object + Recovery system to add. + """ + self._recovery_systems.append(system) + + def add_sensor(self, sensor): + """Attach a sensor to this body. + + Parameters + ---------- + sensor : object + Sensor to add. + """ + self._sensors.append(sensor) + + def add_controller(self, controller): + """Attach a controller to this body. + + Parameters + ---------- + controller : object + Controller to add. + """ + self._controllers.append(controller) diff --git a/rocketpy/body/rocket_adapter.py b/rocketpy/body/rocket_adapter.py new file mode 100644 index 000000000..1a90b5c01 --- /dev/null +++ b/rocketpy/body/rocket_adapter.py @@ -0,0 +1,171 @@ +"""RocketAdapter – wraps a legacy :class:`Rocket` as a :class:`BodyLike`.""" + +from copy import deepcopy + +from rocketpy.body.body_like import BodyLike + + +class RocketAdapter(BodyLike): + """Adapts a legacy :class:`~rocketpy.rocket.Rocket` to the + :class:`BodyLike` interface. + + This allows the existing :class:`~rocketpy.rocket.Rocket` builder to be + consumed by the new multistage simulation infrastructure without any + changes to the ``Rocket`` class itself. + + Parameters + ---------- + rocket : :class:`~rocketpy.rocket.Rocket` + The rocket instance to adapt. + + Attributes + ---------- + rocket : :class:`~rocketpy.rocket.Rocket` + The wrapped rocket instance. + """ + + def __init__(self, rocket): + self.rocket = rocket + + # ------------------------------------------------------------------ + # BodyLike interface + # ------------------------------------------------------------------ + + @property + def name(self) -> str: + """Name of the underlying rocket. + + Returns + ------- + str + ``rocket.name`` if present, otherwise ``""``. + """ + return getattr(self.rocket, "name", "") + + def mass(self, t: float) -> float: + """Total rocket mass at time *t*, in kg. + + Delegates to ``rocket.total_mass(t)``. + + Parameters + ---------- + t : float + Simulation time in seconds. + + Returns + ------- + float + Total mass in kg. + """ + return self.rocket.total_mass(t) + + def inertia_tensor(self, t: float): + """Inertia tensor at time *t*, in kg·m². + + Returns a 3×3 matrix built from the scalar inertia components + available on the rocket. + + Parameters + ---------- + t : float + Simulation time in seconds. + + Returns + ------- + tuple[float, float, float, float, float, float] + Components ``(I_11, I_22, I_33, I_12, I_13, I_23)`` in kg·m². + """ + r = self.rocket + return ( + r.I_11(t), + r.I_22(t), + r.I_33(t), + r.I_12(t), + r.I_13(t), + r.I_23(t), + ) + + def center_of_mass(self, t: float) -> float: + """Axial position of the center of mass at time *t*, in m. + + Parameters + ---------- + t : float + Simulation time in seconds. + + Returns + ------- + float + Center-of-mass position in meters. + """ + return self.rocket.center_of_mass(t) + + def aerodynamic_model(self): + """Return the rocket's aerodynamic surfaces collection. + + Returns + ------- + list + ``rocket.aerodynamic_surfaces``. + """ + return self.rocket.aerodynamic_surfaces + + def propulsion_model(self): + """Return the rocket's motor. + + Returns + ------- + Motor + ``rocket.motor``. + """ + return self.rocket.motor + + def recovery_systems(self): + """Return the rocket's parachute list. + + Returns + ------- + list + ``rocket.parachutes``. + """ + return self.rocket.parachutes + + def sensors(self): + """Return the sensors attached to the rocket. + + Returns + ------- + list + ``rocket.sensors``. + """ + return getattr(self.rocket, "sensors", []) + + def controllers(self): + """Return the active controllers attached to the rocket. + + Returns + ------- + list + ``rocket._controllers``. + """ + return getattr(self.rocket, "_controllers", []) + + def coordinate_system_orientation(self) -> str: + """Coordinate system orientation of the underlying rocket. + + Returns + ------- + str + ``rocket.coordinate_system_orientation``. + """ + return self.rocket.coordinate_system_orientation + + def to_branch_ready_copy(self): + """Return a deep copy of this adapter suitable for a FlightBranch. + + Returns + ------- + RocketAdapter + Deep copy with a cloned rocket. + """ + return deepcopy(self) diff --git a/rocketpy/mission/__init__.py b/rocketpy/mission/__init__.py new file mode 100644 index 000000000..069bd2131 --- /dev/null +++ b/rocketpy/mission/__init__.py @@ -0,0 +1,35 @@ +"""rocketpy.mission – mission architecture for multistage rockets.""" + +from rocketpy.mission.attached_item import AttachedItem +from rocketpy.mission.attachment import Attachment +from rocketpy.mission.deployable import Deployable +from rocketpy.mission.events import ( + DeploymentEvent, + Event, + IgnitionEvent, + RecoveryEvent, + StageSeparationEvent, +) +from rocketpy.mission.mission import Mission +from rocketpy.mission.parent_update import NoOpParentUpdate, ParentUpdate +from rocketpy.mission.separation_model import InstantaneousSeparation, SeparationModel +from rocketpy.mission.stage import Stage +from rocketpy.mission.stage_state import StageState + +__all__ = [ + "AttachedItem", + "Attachment", + "Deployable", + "DeploymentEvent", + "Event", + "IgnitionEvent", + "InstantaneousSeparation", + "Mission", + "NoOpParentUpdate", + "ParentUpdate", + "RecoveryEvent", + "SeparationModel", + "Stage", + "StageSeparationEvent", + "StageState", +] diff --git a/rocketpy/mission/attached_item.py b/rocketpy/mission/attached_item.py new file mode 100644 index 000000000..f7bf1c1c4 --- /dev/null +++ b/rocketpy/mission/attached_item.py @@ -0,0 +1,52 @@ +"""AttachedItem – abstract base for items that can be attached to a body.""" + +from abc import ABC, abstractmethod + + +class AttachedItem(ABC): + """Abstract base class for objects that can be attached to a parent body. + + Every mission item (a :class:`Deployable` or a :class:`Stage`) is an + :class:`AttachedItem`. It owns a :class:`~rocketpy.body.BodyLike` + instance (the physical body), an :class:`~rocketpy.mission.Attachment` + (the mechanical link), and a list of :class:`~rocketpy.mission.Event` + objects that govern its lifecycle. + + Parameters + ---------- + name : str + Human-readable identifier for this item. + body : :class:`~rocketpy.body.BodyLike` + The physical body associated with this item. + attachment : :class:`~rocketpy.mission.Attachment` + The mechanical link that positions and orients this item relative + to its parent. + events : list[:class:`~rocketpy.mission.Event`], optional + Events that can be fired while this item is attached. + + Attributes + ---------- + name : str + body : BodyLike + attachment : Attachment + events : list[Event] + """ + + def __init__(self, name, body, attachment, events=None): + self.name = name + self.body = body + self.attachment = attachment + self.events = list(events or []) + + @abstractmethod + def validate(self): + """Validate that this item is correctly configured. + + Raises + ------ + ValueError + If any required field is missing or inconsistent. + """ + + def __repr__(self) -> str: + return f"{type(self).__name__}(name={self.name!r})" diff --git a/rocketpy/mission/attachment.py b/rocketpy/mission/attachment.py new file mode 100644 index 000000000..a337dc477 --- /dev/null +++ b/rocketpy/mission/attachment.py @@ -0,0 +1,79 @@ +"""Attachment – describes the mechanical link between two bodies.""" + + +class Attachment: + """Encodes how a child body is attached to a parent body. + + The attachment resolves the child's initial pose (position + orientation) + given the parent's current state, and records any mechanical constraints + that apply while the link is active. + + Parameters + ---------- + parent_frame_position : array-like + Position of the attachment point in the **parent** body frame, + in meters. Typically a 3-vector ``[x, y, z]``. + child_frame_position : array-like + Position of the attachment point in the **child** body frame, + in meters. + orientation : array-like or None, optional + Relative orientation of the child frame with respect to the parent + frame. Can be a rotation matrix, quaternion, or any representation + that the caller chooses to interpret. ``None`` means co-aligned + frames. + constraints : str, optional + Textual description of the mechanical constraint (e.g. + ``"rigid"``, ``"hinged"``). Purely informational at this stage. + tags : dict, optional + Arbitrary key-value metadata for downstream consumers. + + Attributes + ---------- + parent_frame_position : array-like + child_frame_position : array-like + orientation : array-like or None + constraints : str + tags : dict + """ + + def __init__( + self, + parent_frame_position, + child_frame_position, + orientation=None, + constraints="rigid", + tags=None, + ): + self.parent_frame_position = parent_frame_position + self.child_frame_position = child_frame_position + self.orientation = orientation + self.constraints = constraints + self.tags = dict(tags or {}) + + def resolve_child_pose(self, parent_state): + """Compute the initial child pose from the parent state. + + Parameters + ---------- + parent_state : object + Current state of the parent body (position, orientation, …). + The interpretation is left to the caller; this method simply + returns *parent_state* unchanged as a sensible default until a + full 6-DOF pose resolver is implemented. + + Returns + ------- + object + Initial state for the child body. + """ + # Placeholder: a full implementation would compose the parent + # pose with the attachment offsets and orientations. + return parent_state + + def __repr__(self) -> str: + return ( + f"Attachment(" + f"parent_frame_position={self.parent_frame_position!r}, " + f"child_frame_position={self.child_frame_position!r}, " + f"constraints={self.constraints!r})" + ) diff --git a/rocketpy/mission/deployable.py b/rocketpy/mission/deployable.py new file mode 100644 index 000000000..e4767269d --- /dev/null +++ b/rocketpy/mission/deployable.py @@ -0,0 +1,78 @@ +"""Deployable – a mission item that can be released during flight.""" + +from rocketpy.mission.attached_item import AttachedItem +from rocketpy.mission.parent_update import NoOpParentUpdate +from rocketpy.mission.separation_model import InstantaneousSeparation + + +class Deployable(AttachedItem): + """A body that can be deployed (released) from its parent during flight. + + A :class:`Deployable` is the mission-layer wrapper around a physical + body that should be released at some point in flight (e.g. a nose + cone, a payload bay, or a secondary vehicle). It is *not* the physical + body itself – that is held in :attr:`body`. + + Parameters + ---------- + name : str + Human-readable identifier (e.g. ``"nose_cone_payload"``). + body : :class:`~rocketpy.body.BodyLike` + The physical body of the deployable. + attachment : :class:`~rocketpy.mission.Attachment` + Mechanical link to the parent body. + deployment_event : :class:`~rocketpy.mission.DeploymentEvent` + The event that triggers this deployable's release. + separation : :class:`~rocketpy.mission.SeparationModel`, optional + Model for the separation dynamics. Defaults to + :class:`~rocketpy.mission.InstantaneousSeparation`. + parent_update : :class:`~rocketpy.mission.ParentUpdate`, optional + Callback that modifies the parent body after separation. Defaults + to :class:`~rocketpy.mission.NoOpParentUpdate`. + events : list[:class:`~rocketpy.mission.Event`], optional + Additional events besides *deployment_event*. + + Attributes + ---------- + deployment_event : DeploymentEvent + separation : SeparationModel + parent_update : ParentUpdate + """ + + def __init__( + self, + name, + body, + attachment, + deployment_event, + separation=None, + parent_update=None, + events=None, + ): + # Merge deployment_event into the events list so the base class + # holds *all* events for this item. + all_events = list(events or []) + if deployment_event not in all_events: + all_events.insert(0, deployment_event) + super().__init__(name, body, attachment, events=all_events) + + self.deployment_event = deployment_event + self.separation = separation if separation is not None else InstantaneousSeparation() + self.parent_update = parent_update if parent_update is not None else NoOpParentUpdate() + + def validate(self): + """Validate that this deployable is correctly configured. + + Raises + ------ + ValueError + If *body*, *attachment*, or *deployment_event* are ``None``. + """ + if self.body is None: + raise ValueError(f"Deployable '{self.name}': body must not be None.") + if self.attachment is None: + raise ValueError(f"Deployable '{self.name}': attachment must not be None.") + if self.deployment_event is None: + raise ValueError( + f"Deployable '{self.name}': deployment_event must not be None." + ) diff --git a/rocketpy/mission/events.py b/rocketpy/mission/events.py new file mode 100644 index 000000000..685742285 --- /dev/null +++ b/rocketpy/mission/events.py @@ -0,0 +1,197 @@ +"""Event hierarchy for the mission architecture.""" + +from abc import ABC, abstractmethod + + +class Event(ABC): + """Abstract base class for all mission events. + + An event monitors simulation state and, when its trigger condition is + met, applies a transformation to a :class:`FlightBranch`. + + Parameters + ---------- + name : str + Human-readable name for the event (e.g. ``"main_chute"``). + priority : int, optional + Ordering priority when multiple events fire at the same instant. + Lower values are processed first. Defaults to ``0``. + + Attributes + ---------- + name : str + priority : int + """ + + def __init__(self, name: str, priority: int = 0): + self.name = name + self.priority = priority + + @abstractmethod + def should_fire(self, state, context) -> bool: + """Determine whether this event should fire given the current state. + + Parameters + ---------- + state : object + Current simulation state vector. + context : object + Simulation context object providing environment, time, etc. + + Returns + ------- + bool + ``True`` if the event should be triggered. + """ + + @abstractmethod + def apply(self, branch, context): + """Apply the event's effect to a flight branch. + + Parameters + ---------- + branch : :class:`~rocketpy.simulation.FlightBranch` + The branch that is currently being integrated. + context : object + Simulation context. + """ + + def __repr__(self) -> str: + return f"{type(self).__name__}(name={self.name!r}, priority={self.priority})" + + +# --------------------------------------------------------------------------- +# Concrete event types +# --------------------------------------------------------------------------- + + +class DeploymentEvent(Event): + """Event that triggers the deployment of a :class:`Deployable`. + + Parameters + ---------- + name : str + Human-readable name. + trigger : callable + ``trigger(state, context) -> bool`` – returns ``True`` when the + deployable should be released. + priority : int, optional + Processing priority. Defaults to ``0``. + """ + + def __init__(self, name: str, trigger, priority: int = 0): + super().__init__(name, priority) + self._trigger = trigger + + def should_fire(self, state, context) -> bool: + return bool(self._trigger(state, context)) + + def apply(self, branch, context): + """Deploy the associated :class:`Deployable` from *branch*. + + Parameters + ---------- + branch : FlightBranch + Active branch. + context : object + Simulation context. + """ + # The actual separation logic is delegated to the Deployable's + # SeparationModel and ParentUpdate. This stub is intentional; + # the simulation engine is responsible for the full orchestration. + + +class StageSeparationEvent(Event): + """Event that triggers the mechanical separation of a :class:`Stage`. + + Parameters + ---------- + name : str + Human-readable name. + trigger : callable + ``trigger(state, context) -> bool``. + priority : int, optional + Processing priority. Defaults to ``0``. + """ + + def __init__(self, name: str, trigger, priority: int = 0): + super().__init__(name, priority) + self._trigger = trigger + + def should_fire(self, state, context) -> bool: + return bool(self._trigger(state, context)) + + def apply(self, branch, context): + """Separate the stage from its parent branch. + + Parameters + ---------- + branch : FlightBranch + Active branch. + context : object + Simulation context. + """ + + +class IgnitionEvent(Event): + """Event that triggers motor ignition on a :class:`Stage`. + + Parameters + ---------- + name : str + Human-readable name. + trigger : callable + ``trigger(state, context) -> bool``. + priority : int, optional + Processing priority. Defaults to ``0``. + """ + + def __init__(self, name: str, trigger, priority: int = 0): + super().__init__(name, priority) + self._trigger = trigger + + def should_fire(self, state, context) -> bool: + return bool(self._trigger(state, context)) + + def apply(self, branch, context): + """Ignite the stage motor. + + Parameters + ---------- + branch : FlightBranch + Active branch. + context : object + Simulation context. + """ + + +class RecoveryEvent(Event): + """Event that triggers a recovery system (e.g. parachute deployment). + + Parameters + ---------- + name : str + Human-readable name. + trigger : callable + ``trigger(state, context) -> bool``. + priority : int, optional + Processing priority. Defaults to ``0``. + """ + + def __init__(self, name: str, trigger, priority: int = 0): + super().__init__(name, priority) + self._trigger = trigger + + def should_fire(self, state, context) -> bool: + return bool(self._trigger(state, context)) + + def apply(self, branch, context): + """Activate the recovery system. + + Parameters + ---------- + branch : FlightBranch + Active branch. + context : object + Simulation context. + """ diff --git a/rocketpy/mission/mission.py b/rocketpy/mission/mission.py new file mode 100644 index 000000000..8790714af --- /dev/null +++ b/rocketpy/mission/mission.py @@ -0,0 +1,126 @@ +"""Mission – top-level container for a multistage rocket mission.""" + + +class Mission: + """Container that stores all mission items for a multistage flight. + + A :class:`Mission` groups together all the :class:`~rocketpy.mission.Stage` + and :class:`~rocketpy.mission.Deployable` objects that together define a + complete rocket mission. It acts as the single source-of-truth for the + simulation engine: when :class:`~rocketpy.simulation.Flight` is initialised + with a :class:`Mission`, it reads the stages and deployables from here. + + Parameters + ---------- + name : str, optional + Human-readable name for the mission (e.g. ``"Falcon9_Demo"``). + Defaults to ``"Mission"``. + + Attributes + ---------- + name : str + Mission name. + stages : list[:class:`~rocketpy.mission.Stage`] + Ordered list of rocket stages (first-to-fire first). + deployables : list[:class:`~rocketpy.mission.Deployable`] + List of deployable items that will be released during the mission. + + Examples + -------- + >>> from rocketpy.mission import Mission, Stage, Deployable + >>> mission = Mission(name="Demo") + >>> # mission.add_stage(some_stage) + >>> # mission.add_deployable(some_deployable) + >>> len(mission.stages) + 0 + """ + + def __init__(self, name: str = "Mission"): + self.name = name + self.stages: list = [] + self.deployables: list = [] + + # ------------------------------------------------------------------ + # Mutating helpers + # ------------------------------------------------------------------ + + def add_stage(self, stage): + """Append a stage to this mission. + + Parameters + ---------- + stage : :class:`~rocketpy.mission.Stage` + The stage to add. Will be validated before appending. + + Raises + ------ + ValueError + If *stage* fails its own :meth:`~rocketpy.mission.Stage.validate` + check. + TypeError + If *stage* is not a :class:`~rocketpy.mission.Stage` instance. + """ + from rocketpy.mission.stage import Stage # local import to avoid circularity + + if not isinstance(stage, Stage): + raise TypeError( + f"Expected a Stage instance, got {type(stage).__name__!r}." + ) + stage.validate() + self.stages.append(stage) + + def add_deployable(self, deployable): + """Append a deployable to this mission. + + Parameters + ---------- + deployable : :class:`~rocketpy.mission.Deployable` + The deployable to add. Will be validated before appending. + + Raises + ------ + ValueError + If *deployable* fails its own + :meth:`~rocketpy.mission.Deployable.validate` check. + TypeError + If *deployable* is not a + :class:`~rocketpy.mission.Deployable` instance. + """ + from rocketpy.mission.deployable import Deployable # local import + + if not isinstance(deployable, Deployable): + raise TypeError( + f"Expected a Deployable instance, got {type(deployable).__name__!r}." + ) + deployable.validate() + self.deployables.append(deployable) + + # ------------------------------------------------------------------ + # Query helpers + # ------------------------------------------------------------------ + + def attached_items(self): + """Return all attached items (stages and deployables) in priority order. + + Stages are listed before deployables. + + Returns + ------- + list[:class:`~rocketpy.mission.AttachedItem`] + Combined list of :attr:`stages` followed by :attr:`deployables`. + """ + return list(self.stages) + list(self.deployables) + + # ------------------------------------------------------------------ + # Dunder + # ------------------------------------------------------------------ + + def __repr__(self) -> str: + return ( + f"Mission(name={self.name!r}, " + f"stages={len(self.stages)}, " + f"deployables={len(self.deployables)})" + ) + + def __len__(self) -> int: + return len(self.stages) + len(self.deployables) diff --git a/rocketpy/mission/parent_update.py b/rocketpy/mission/parent_update.py new file mode 100644 index 000000000..83e244c0b --- /dev/null +++ b/rocketpy/mission/parent_update.py @@ -0,0 +1,44 @@ +"""ParentUpdate – interface for modifying a parent body after separation.""" + +from abc import ABC, abstractmethod + + +class ParentUpdate(ABC): + """Interface for updating a parent body when a child separates. + + When a :class:`~rocketpy.mission.Deployable` or + :class:`~rocketpy.mission.Stage` separates from its parent, the + parent body may need to be updated to account for the mass and + inertia change caused by the separation. Concrete implementations + of this interface perform that update in place. + """ + + @abstractmethod + def apply(self, parent_body): + """Mutate *parent_body* to reflect the separation. + + Parameters + ---------- + parent_body : :class:`~rocketpy.body.BodyLike` + The parent body to update. Implementations may, for example, + remove a nose-cone mass component or re-parametrize the mass + model. + """ + + +class NoOpParentUpdate(ParentUpdate): + """A :class:`ParentUpdate` that makes no changes to the parent. + + Use this when the parent body's properties do not change as a result + of the separation (e.g. the separated mass is negligible compared to + the remaining body). + """ + + def apply(self, parent_body): + """Do nothing. + + Parameters + ---------- + parent_body : :class:`~rocketpy.body.BodyLike` + Ignored. + """ diff --git a/rocketpy/mission/separation_model.py b/rocketpy/mission/separation_model.py new file mode 100644 index 000000000..c91571e18 --- /dev/null +++ b/rocketpy/mission/separation_model.py @@ -0,0 +1,66 @@ +"""SeparationModel – interface for child/parent separation dynamics.""" + +from abc import ABC, abstractmethod + + +class SeparationModel(ABC): + """Interface for modelling the separation impulse between two bodies. + + When a :class:`~rocketpy.mission.Deployable` or + :class:`~rocketpy.mission.Stage` separates from its parent, a + :class:`SeparationModel` is responsible for computing any + velocity/orientation perturbations applied to both the parent and + child bodies at the instant of separation. + + Implementors should override :meth:`apply` and may store any physical + parameters (e.g. spring constant, separation charge energy) as + instance attributes. + """ + + @abstractmethod + def apply(self, parent_state, child_state, context): + """Apply separation dynamics and return updated states. + + Parameters + ---------- + parent_state : object + State of the parent body just before separation. + child_state : object + State of the child body just before separation. + context : object + Simulation context (provides environment, time, etc.). + + Returns + ------- + tuple[object, object] + Updated ``(parent_state, child_state)`` after applying the + separation model. + """ + + +class InstantaneousSeparation(SeparationModel): + """Trivial separation model that applies no impulse. + + Both bodies continue with whatever velocity they had at the moment of + separation. This is the default model when no physical separation + mechanism is specified. + """ + + def apply(self, parent_state, child_state, context): + """Return states unchanged. + + Parameters + ---------- + parent_state : object + Parent state. + child_state : object + Child state. + context : object + Simulation context. + + Returns + ------- + tuple[object, object] + ``(parent_state, child_state)`` unmodified. + """ + return parent_state, child_state diff --git a/rocketpy/mission/stage.py b/rocketpy/mission/stage.py new file mode 100644 index 000000000..82f2eb66a --- /dev/null +++ b/rocketpy/mission/stage.py @@ -0,0 +1,89 @@ +"""Stage – a mission item representing a rocket stage.""" + +from rocketpy.mission.attached_item import AttachedItem +from rocketpy.mission.parent_update import NoOpParentUpdate +from rocketpy.mission.separation_model import InstantaneousSeparation +from rocketpy.mission.stage_state import StageState + + +class Stage(AttachedItem): + """A rocket stage that can ignite and/or separate from its parent. + + A :class:`Stage` is the mission-layer wrapper around the physical + body of a rocket stage. It tracks the stage lifecycle via + :class:`~rocketpy.mission.StageState` and holds references to the + events that govern separation and ignition. + + Separation and ignition are kept as independent concerns: a stage may + separate without igniting (e.g. a fairing) or may ignite before + separating (hot staging). + + Parameters + ---------- + name : str + Human-readable identifier (e.g. ``"second_stage"``). + body : :class:`~rocketpy.body.BodyLike` + Physical body of the stage. + attachment : :class:`~rocketpy.mission.Attachment` + Mechanical link to the parent body. + separation_event : :class:`~rocketpy.mission.StageSeparationEvent`, optional + Event that triggers mechanical separation. ``None`` if the stage + never separates during the simulated mission. + ignition_event : :class:`~rocketpy.mission.IgnitionEvent`, optional + Event that triggers motor ignition. ``None`` if the stage is + already burning when the simulation starts (i.e. it is the + first stage). + separation : :class:`~rocketpy.mission.SeparationModel`, optional + Separation dynamics model. Defaults to + :class:`~rocketpy.mission.InstantaneousSeparation`. + parent_update : :class:`~rocketpy.mission.ParentUpdate`, optional + Updates applied to the parent body after separation. Defaults + to :class:`~rocketpy.mission.NoOpParentUpdate`. + events : list[:class:`~rocketpy.mission.Event`], optional + Additional events besides *separation_event* and *ignition_event*. + + Attributes + ---------- + separation_event : StageSeparationEvent or None + ignition_event : IgnitionEvent or None + separation : SeparationModel + parent_update : ParentUpdate + state : StageState + """ + + def __init__( + self, + name, + body, + attachment, + separation_event=None, + ignition_event=None, + separation=None, + parent_update=None, + events=None, + ): + all_events = list(events or []) + if ignition_event is not None and ignition_event not in all_events: + all_events.insert(0, ignition_event) + if separation_event is not None and separation_event not in all_events: + all_events.insert(0, separation_event) + super().__init__(name, body, attachment, events=all_events) + + self.separation_event = separation_event + self.ignition_event = ignition_event + self.separation = separation if separation is not None else InstantaneousSeparation() + self.parent_update = parent_update if parent_update is not None else NoOpParentUpdate() + self.state = StageState.ATTACHED + + def validate(self): + """Validate that this stage is correctly configured. + + Raises + ------ + ValueError + If *body* or *attachment* are ``None``. + """ + if self.body is None: + raise ValueError(f"Stage '{self.name}': body must not be None.") + if self.attachment is None: + raise ValueError(f"Stage '{self.name}': attachment must not be None.") diff --git a/rocketpy/mission/stage_state.py b/rocketpy/mission/stage_state.py new file mode 100644 index 000000000..fa657a139 --- /dev/null +++ b/rocketpy/mission/stage_state.py @@ -0,0 +1,27 @@ +"""StageState – lifecycle states for a Stage.""" + +from enum import Enum, auto + + +class StageState(Enum): + """Enumeration of the lifecycle states a :class:`Stage` can occupy. + + Attributes + ---------- + ATTACHED : + The stage is attached to the parent body and has not yet ignited. + IGNITED : + The stage motor has ignited but the stage is still mechanically + coupled to the parent. + SEPARATED : + The stage has mechanically separated from the parent and is flying + independently. + SPENT : + The stage motor has burned out. The stage may still be attached + (e.g. coast phase) or may have separated already. + """ + + ATTACHED = auto() + IGNITED = auto() + SEPARATED = auto() + SPENT = auto() diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 84655b6d7..123b01de5 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -343,6 +343,10 @@ def __init__( # pylint: disable=too-many-statements self.surfaces_cp_to_cdm = {} self.rail_buttons = Components() + # Multistage mission items (AttachmentHost interface) + self._stages = [] + self._deployables = [] + self.cp_position = Function( lambda mach: 0, inputs="Mach Number", @@ -2037,6 +2041,144 @@ def draw(self, vis_args=None, plane="xz", *, filename=None): """ self.plots.draw(vis_args, plane, filename=filename) + # ------------------------------------------------------------------ + # AttachmentHost interface – multistage mission support + # ------------------------------------------------------------------ + + def add_stage(self, stage): + """Attach a :class:`~rocketpy.mission.Stage` to this rocket. + + Parameters + ---------- + stage : :class:`~rocketpy.mission.Stage` + Stage to attach. Must pass its own ``validate()`` check. + + Raises + ------ + TypeError + If *stage* is not a :class:`~rocketpy.mission.Stage` instance. + ValueError + If *stage* fails validation. + """ + from rocketpy.mission.stage import Stage # local import avoids circularity + + if not isinstance(stage, Stage): + raise TypeError( + f"Expected a Stage instance, got {type(stage).__name__!r}." + ) + stage.validate() + self._stages.append(stage) + + def add_deployable(self, deployable): + """Attach a :class:`~rocketpy.mission.Deployable` to this rocket. + + Parameters + ---------- + deployable : :class:`~rocketpy.mission.Deployable` + Deployable to attach. Must pass its own ``validate()`` check. + + Raises + ------ + TypeError + If *deployable* is not a + :class:`~rocketpy.mission.Deployable` instance. + ValueError + If *deployable* fails validation. + """ + from rocketpy.mission.deployable import Deployable # local import + + if not isinstance(deployable, Deployable): + raise TypeError( + f"Expected a Deployable instance, got {type(deployable).__name__!r}." + ) + deployable.validate() + self._deployables.append(deployable) + + def select_stage(self, stage): + """Return the :class:`~rocketpy.mission.Stage` identified by *stage*. + + Parameters + ---------- + stage : int or str + Either the zero-based index of the stage in :attr:`stages`, or + the ``name`` attribute of the desired stage. + + Returns + ------- + :class:`~rocketpy.mission.Stage` + The matching stage. + + Raises + ------ + IndexError + If *stage* is an integer that is out of range. + KeyError + If *stage* is a string that does not match any stage name. + TypeError + If *stage* is neither an int nor a str. + """ + if isinstance(stage, int): + return self._stages[stage] + elif isinstance(stage, str): + for s in self._stages: + if s.name == stage: + return s + raise KeyError(f"No stage named {stage!r} found.") + else: + raise TypeError( + f"stage must be int or str, got {type(stage).__name__!r}." + ) + + def attached_items(self): + """Return all attached mission items (stages then deployables). + + Returns + ------- + list[:class:`~rocketpy.mission.AttachedItem`] + Combined list of :attr:`stages` + :attr:`deployables`. + """ + return list(self._stages) + list(self._deployables) + + def as_body(self): + """Wrap this rocket in a :class:`~rocketpy.body.RocketAdapter`. + + The adapter satisfies the :class:`~rocketpy.body.BodyLike` interface + so that the simulation engine can consume this rocket without + depending on the :class:`Rocket` class directly. + + Returns + ------- + :class:`~rocketpy.body.RocketAdapter` + Adapter wrapping ``self``. + """ + from rocketpy.body.rocket_adapter import RocketAdapter # local import + + return RocketAdapter(self) + + @property + def stages(self): + """Stages attached to this rocket. + + Returns + ------- + list[:class:`~rocketpy.mission.Stage`] + """ + return list(self._stages) + + @property + def deployables(self): + """Deployables attached to this rocket. + + Returns + ------- + list[:class:`~rocketpy.mission.Deployable`] + """ + return list(self._deployables) + + # ------------------------------------------------------------------ + # End AttachmentHost interface + # ------------------------------------------------------------------ + def info(self): """Prints out a summary of the data and graphs available about the Rocket. diff --git a/rocketpy/simulation/__init__.py b/rocketpy/simulation/__init__.py index 39afd9166..ae90641a5 100644 --- a/rocketpy/simulation/__init__.py +++ b/rocketpy/simulation/__init__.py @@ -1,4 +1,5 @@ from .flight import Flight +from .flight_branch import FlightBranch from .flight_comparator import FlightComparator from .flight_data_exporter import FlightDataExporter from .flight_data_importer import FlightDataImporter @@ -7,6 +8,7 @@ __all__ = [ "Flight", + "FlightBranch", "FlightComparator", "FlightDataExporter", "FlightDataImporter", diff --git a/rocketpy/simulation/flight_branch.py b/rocketpy/simulation/flight_branch.py new file mode 100644 index 000000000..2cef3bee5 --- /dev/null +++ b/rocketpy/simulation/flight_branch.py @@ -0,0 +1,115 @@ +"""FlightBranch – a single contiguous integration arc in a multistage flight.""" + + +class FlightBranch: + """Represents one contiguous integration arc in a branching flight tree. + + When a :class:`~rocketpy.mission.Stage` separates or a + :class:`~rocketpy.mission.Deployable` is released, the simulation engine + creates one or more new :class:`FlightBranch` objects. Each branch tracks + its own body, initial state, and list of pending events. + + The branches together form a directed acyclic graph (DAG) rooted at the + first-stage branch. A :class:`~rocketpy.simulation.Flight` object holds + the root branch and drives the full simulation. + + Parameters + ---------- + body : :class:`~rocketpy.body.BodyLike` + The flight-ready body for this branch. A simulation-safe deep copy + should be provided (see + :meth:`~rocketpy.body.BodyLike.to_branch_ready_copy`). + start_state : object + Initial state vector (position, velocity, attitude, …) at the + start of this branch. + start_time : float + Simulation time, in seconds, at which this branch begins. + parent : :class:`FlightBranch` or None, optional + The branch from which this one was spawned. ``None`` for the root. + events : list[:class:`~rocketpy.mission.Event`], optional + Events that are pending on this branch. + + Attributes + ---------- + body : BodyLike + parent : FlightBranch or None + children : list[FlightBranch] + start_state : object + start_time : float + events : list[Event] + """ + + def __init__( + self, + body, + start_state=None, + start_time: float = 0.0, + parent=None, + events=None, + ): + self.body = body + self.start_state = start_state + self.start_time = start_time + self.parent = parent + self.children: list = [] + self.events: list = list(events or []) + + # Register this branch as a child of its parent. + if parent is not None: + parent.children.append(self) + + # ------------------------------------------------------------------ + # Tree helpers + # ------------------------------------------------------------------ + + def is_root(self) -> bool: + """Return ``True`` if this branch has no parent. + + Returns + ------- + bool + """ + return self.parent is None + + def spawn_child(self, body, start_state=None, start_time=None, events=None): + """Create a child branch and register it. + + Parameters + ---------- + body : :class:`~rocketpy.body.BodyLike` + Body for the new child branch. + start_state : object, optional + Initial state for the child. Defaults to this branch's + ``start_state``. + start_time : float, optional + Start time for the child in seconds. Defaults to this branch's + ``start_time``. + events : list[:class:`~rocketpy.mission.Event`], optional + Events for the child branch. + + Returns + ------- + FlightBranch + The newly created child branch. + """ + child = FlightBranch( + body=body, + start_state=start_state if start_state is not None else self.start_state, + start_time=start_time if start_time is not None else self.start_time, + parent=self, + events=events, + ) + return child + + # ------------------------------------------------------------------ + # Dunder + # ------------------------------------------------------------------ + + def __repr__(self) -> str: + body_name = getattr(self.body, "name", repr(self.body)) + return ( + f"FlightBranch(" + f"body={body_name!r}, " + f"start_time={self.start_time}, " + f"children={len(self.children)})" + ) diff --git a/tests/unit/body/__init__.py b/tests/unit/body/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/body/test_body.py b/tests/unit/body/test_body.py new file mode 100644 index 000000000..ba67c3c46 --- /dev/null +++ b/tests/unit/body/test_body.py @@ -0,0 +1,285 @@ +"""Unit tests for rocketpy.body (BodyLike, FlightBody, RocketAdapter).""" + +import pytest + +from rocketpy.body import BodyLike, FlightBody, RocketAdapter + + +# --------------------------------------------------------------------------- +# Helpers / fixtures +# --------------------------------------------------------------------------- + + +def _const_mass_model(value=10.0): + """Return a constant mass callable.""" + return lambda t: value + + +def _const_inertia_model(diag=1.0): + """Return a constant inertia callable that returns a diagonal tuple.""" + return lambda t: (diag, diag, diag, 0.0, 0.0, 0.0) + + +def _const_com_model(pos=0.5): + """Return a constant center-of-mass position callable.""" + return lambda t: pos + + +def _make_flight_body(**kwargs): + """Construct a minimal FlightBody for testing.""" + defaults = { + "name": "test_body", + "geometry": 0.05, + "mass_model": _const_mass_model(), + "inertia_model": _const_inertia_model(), + "center_of_mass_model": _const_com_model(), + } + defaults.update(kwargs) + return FlightBody(**defaults) + + +# --------------------------------------------------------------------------- +# BodyLike ABC +# --------------------------------------------------------------------------- + + +class TestBodyLikeIsAbstract: + """BodyLike cannot be instantiated directly.""" + + def test_cannot_instantiate_body_like(self): + """BodyLike must raise TypeError when instantiated directly.""" + # Arrange / Act / Assert + with pytest.raises(TypeError): + BodyLike() # type: ignore[abstract] + + def test_concrete_subclass_without_all_methods_raises(self): + """A class that only partially implements BodyLike must raise TypeError.""" + + class Partial(BodyLike): + @property + def name(self): + return "partial" + + with pytest.raises(TypeError): + Partial() + + +# --------------------------------------------------------------------------- +# FlightBody +# --------------------------------------------------------------------------- + + +class TestFlightBodyInit: + """FlightBody initializes attributes correctly.""" + + def test_name_stored(self): + """name attribute is stored and returned via .name property.""" + # Arrange & Act + body = _make_flight_body(name="rocket_stage_1") + # Assert + assert body.name == "rocket_stage_1" + + def test_default_coordinate_system_orientation(self): + """Default orientation is tail_to_nose.""" + body = _make_flight_body() + assert body.coordinate_system_orientation() == "tail_to_nose" + + def test_custom_coordinate_system_orientation(self): + """Custom orientation is stored correctly.""" + body = _make_flight_body(coordinate_system_orientation="nose_to_tail") + assert body.coordinate_system_orientation() == "nose_to_tail" + + def test_recovery_systems_defaults_to_empty(self): + """recovery_systems defaults to an empty list.""" + body = _make_flight_body() + assert body.recovery_systems() == [] + + def test_sensors_defaults_to_empty(self): + """sensors defaults to an empty list.""" + body = _make_flight_body() + assert body.sensors() == [] + + def test_controllers_defaults_to_empty(self): + """controllers defaults to an empty list.""" + body = _make_flight_body() + assert body.controllers() == [] + + +class TestFlightBodyInterface: + """FlightBody satisfies the BodyLike interface contracts.""" + + def test_mass_returns_float(self): + """mass(t) delegates to mass_model and returns a float.""" + body = _make_flight_body(mass_model=_const_mass_model(15.5)) + assert body.mass(0.0) == pytest.approx(15.5) + + def test_inertia_tensor_delegates_to_model(self): + """inertia_tensor(t) returns the value from inertia_model.""" + body = _make_flight_body(inertia_model=_const_inertia_model(2.0)) + result = body.inertia_tensor(0.0) + assert result[0] == pytest.approx(2.0) + + def test_center_of_mass_delegates_to_model(self): + """center_of_mass(t) returns value from center_of_mass_model.""" + body = _make_flight_body(center_of_mass_model=_const_com_model(1.2)) + assert body.center_of_mass(0.0) == pytest.approx(1.2) + + def test_aerodynamic_model_returns_stored_model(self): + """aerodynamic_model() returns the aero_model passed at construction.""" + aero = object() + body = _make_flight_body(aero_model=aero) + assert body.aerodynamic_model() is aero + + def test_propulsion_model_returns_stored_model(self): + """propulsion_model() returns the propulsion passed at construction.""" + prop = object() + body = _make_flight_body(propulsion=prop) + assert body.propulsion_model() is prop + + +class TestFlightBodyMutators: + """FlightBody mutating helpers work correctly.""" + + def test_add_recovery_system_appends(self): + """add_recovery_system appends to internal list.""" + body = _make_flight_body() + chute = object() + body.add_recovery_system(chute) + assert chute in body.recovery_systems() + + def test_add_sensor_appends(self): + """add_sensor appends to internal list.""" + body = _make_flight_body() + sensor = object() + body.add_sensor(sensor) + assert sensor in body.sensors() + + def test_add_controller_appends(self): + """add_controller appends to internal list.""" + body = _make_flight_body() + ctrl = object() + body.add_controller(ctrl) + assert ctrl in body.controllers() + + +class TestFlightBodyCopy: + """to_branch_ready_copy produces an independent deep copy.""" + + def test_copy_is_different_object(self): + """The copy is not the same object.""" + body = _make_flight_body() + copy = body.to_branch_ready_copy() + assert copy is not body + + def test_copy_has_same_name(self): + """The copy preserves the name.""" + body = _make_flight_body(name="original") + copy = body.to_branch_ready_copy() + assert copy.name == "original" + + def test_copy_is_independent(self): + """Mutating the copy does not affect the original.""" + body = _make_flight_body() + copy = body.to_branch_ready_copy() + copy.add_sensor(object()) + assert len(body.sensors()) == 0 + + +# --------------------------------------------------------------------------- +# RocketAdapter +# --------------------------------------------------------------------------- + + +class _FakeRocket: + """Minimal fake rocket for testing RocketAdapter.""" + + coordinate_system_orientation = "tail_to_nose" + parachutes = ["parachute_a"] + _controllers = [] + + def total_mass(self, t): + return 20.0 + + def I_11(self, t): + return 1.0 + + def I_22(self, t): + return 1.0 + + def I_33(self, t): + return 0.05 + + def I_12(self, t): + return 0.0 + + def I_13(self, t): + return 0.0 + + def I_23(self, t): + return 0.0 + + def center_of_mass(self, t): + return 0.8 + + @property + def aerodynamic_surfaces(self): + return ["fin_set"] + + @property + def motor(self): + return "solid_motor" + + @property + def sensors(self): + return [] + + +class TestRocketAdapter: + """RocketAdapter wraps a rocket and implements BodyLike.""" + + def test_is_body_like(self): + """RocketAdapter is an instance of BodyLike.""" + adapter = RocketAdapter(_FakeRocket()) + assert isinstance(adapter, BodyLike) + + def test_name_unnamed(self): + """name returns '' when the rocket has no name attribute.""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.name == "" + + def test_mass_delegates(self): + """mass(t) delegates to rocket.total_mass(t).""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.mass(0.0) == pytest.approx(20.0) + + def test_center_of_mass_delegates(self): + """center_of_mass(t) delegates to rocket.center_of_mass(t).""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.center_of_mass(0.0) == pytest.approx(0.8) + + def test_aerodynamic_model_returns_surfaces(self): + """aerodynamic_model() returns rocket.aerodynamic_surfaces.""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.aerodynamic_model() == ["fin_set"] + + def test_propulsion_model_returns_motor(self): + """propulsion_model() returns rocket.motor.""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.propulsion_model() == "solid_motor" + + def test_recovery_systems_returns_parachutes(self): + """recovery_systems() returns rocket.parachutes.""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.recovery_systems() == ["parachute_a"] + + def test_coordinate_system_orientation_delegates(self): + """coordinate_system_orientation() delegates to the rocket.""" + adapter = RocketAdapter(_FakeRocket()) + assert adapter.coordinate_system_orientation() == "tail_to_nose" + + def test_to_branch_ready_copy_is_deep(self): + """to_branch_ready_copy produces a deep copy.""" + adapter = RocketAdapter(_FakeRocket()) + copy = adapter.to_branch_ready_copy() + assert copy is not adapter + assert copy.rocket is not adapter.rocket diff --git a/tests/unit/mission/__init__.py b/tests/unit/mission/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py new file mode 100644 index 000000000..650035bda --- /dev/null +++ b/tests/unit/mission/test_mission.py @@ -0,0 +1,450 @@ +"""Unit tests for rocketpy.mission package. + +Covers: Mission, AttachedItem, Deployable, Stage, Attachment, Event +hierarchy, SeparationModel, ParentUpdate, StageState. +""" + +import pytest + +from rocketpy.body import FlightBody +from rocketpy.mission import ( + Attachment, + Deployable, + DeploymentEvent, + Event, + IgnitionEvent, + InstantaneousSeparation, + Mission, + NoOpParentUpdate, + ParentUpdate, + RecoveryEvent, + SeparationModel, + Stage, + StageSeparationEvent, + StageState, +) + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_body(name="body"): + """Return a minimal FlightBody for testing.""" + return FlightBody( + name=name, + geometry=0.05, + mass_model=lambda t: 10.0, + inertia_model=lambda t: (1.0, 1.0, 0.05, 0.0, 0.0, 0.0), + center_of_mass_model=lambda t: 0.5, + ) + + +def _make_attachment(): + """Return a minimal Attachment.""" + return Attachment( + parent_frame_position=[0.0, 0.0, 1.0], + child_frame_position=[0.0, 0.0, 0.0], + ) + + +def _always_trigger(state, context): + return True + + +def _never_trigger(state, context): + return False + + +def _make_deployment_event(name="deploy", trigger=None): + return DeploymentEvent(name, trigger or _always_trigger) + + +def _make_deployable(**kwargs): + defaults = { + "name": "payload", + "body": _make_body("payload_body"), + "attachment": _make_attachment(), + "deployment_event": _make_deployment_event(), + } + defaults.update(kwargs) + return Deployable(**defaults) + + +def _make_stage(**kwargs): + defaults = { + "name": "stage_2", + "body": _make_body("stage_body"), + "attachment": _make_attachment(), + } + defaults.update(kwargs) + return Stage(**defaults) + + +# --------------------------------------------------------------------------- +# StageState +# --------------------------------------------------------------------------- + + +class TestStageState: + """StageState enum covers all lifecycle states.""" + + def test_all_states_exist(self): + """All four expected states are present.""" + assert StageState.ATTACHED + assert StageState.IGNITED + assert StageState.SEPARATED + assert StageState.SPENT + + def test_states_are_distinct(self): + """Each state is a unique enum member.""" + states = [ + StageState.ATTACHED, + StageState.IGNITED, + StageState.SEPARATED, + StageState.SPENT, + ] + assert len(set(states)) == 4 + + +# --------------------------------------------------------------------------- +# Attachment +# --------------------------------------------------------------------------- + + +class TestAttachment: + """Attachment stores positions and resolves child pose.""" + + def test_stores_positions(self): + """parent_frame_position and child_frame_position are stored.""" + parent_pos = [1.0, 0.0, 0.5] + child_pos = [0.0, 0.0, 0.0] + attachment = Attachment(parent_pos, child_pos) + assert attachment.parent_frame_position == parent_pos + assert attachment.child_frame_position == child_pos + + def test_default_constraints(self): + """Default constraint string is 'rigid'.""" + attachment = Attachment([0, 0, 0], [0, 0, 0]) + assert attachment.constraints == "rigid" + + def test_custom_tags_stored(self): + """Custom tags dict is stored.""" + tags = {"zone": "aft"} + attachment = Attachment([0, 0, 0], [0, 0, 0], tags=tags) + assert attachment.tags["zone"] == "aft" + + def test_resolve_child_pose_returns_parent_state(self): + """resolve_child_pose returns the parent state unchanged (stub).""" + attachment = _make_attachment() + state = {"x": 0, "y": 0, "z": 100} + result = attachment.resolve_child_pose(state) + assert result is state + + +# --------------------------------------------------------------------------- +# Event hierarchy +# --------------------------------------------------------------------------- + + +class TestEventHierarchy: + """Event ABC and concrete subtypes.""" + + def test_event_is_abstract(self): + """Event cannot be instantiated directly.""" + with pytest.raises(TypeError): + Event("abstract") # type: ignore[abstract] + + def test_deployment_event_fires_on_true_trigger(self): + """DeploymentEvent.should_fire returns True when trigger is True.""" + ev = DeploymentEvent("deploy", _always_trigger) + assert ev.should_fire(None, None) is True + + def test_deployment_event_does_not_fire_on_false_trigger(self): + """DeploymentEvent.should_fire returns False when trigger is False.""" + ev = DeploymentEvent("deploy", _never_trigger) + assert ev.should_fire(None, None) is False + + def test_stage_separation_event_fires(self): + """StageSeparationEvent respects its trigger.""" + ev = StageSeparationEvent("sep", _always_trigger) + assert ev.should_fire(None, None) is True + + def test_ignition_event_fires(self): + """IgnitionEvent respects its trigger.""" + ev = IgnitionEvent("ignite", _always_trigger) + assert ev.should_fire(None, None) is True + + def test_recovery_event_fires(self): + """RecoveryEvent respects its trigger.""" + ev = RecoveryEvent("recover", _always_trigger) + assert ev.should_fire(None, None) is True + + def test_event_priority_stored(self): + """priority attribute is stored correctly.""" + ev = DeploymentEvent("hi_prio", _always_trigger, priority=5) + assert ev.priority == 5 + + @pytest.mark.parametrize( + "event_class", + [DeploymentEvent, StageSeparationEvent, IgnitionEvent, RecoveryEvent], + ) + def test_apply_does_not_raise(self, event_class): + """apply() can be called without raising on concrete event types.""" + ev = event_class("ev", _always_trigger) + ev.apply(None, None) # must not raise + + +# --------------------------------------------------------------------------- +# SeparationModel +# --------------------------------------------------------------------------- + + +class TestSeparationModel: + """SeparationModel ABC and InstantaneousSeparation.""" + + def test_separation_model_is_abstract(self): + """SeparationModel cannot be instantiated directly.""" + with pytest.raises(TypeError): + SeparationModel() # type: ignore[abstract] + + def test_instantaneous_returns_states_unchanged(self): + """InstantaneousSeparation.apply returns (parent_state, child_state) unmodified.""" + model = InstantaneousSeparation() + parent = {"v": 300} + child = {"v": 300} + p_out, c_out = model.apply(parent, child, None) + assert p_out is parent + assert c_out is child + + +# --------------------------------------------------------------------------- +# ParentUpdate +# --------------------------------------------------------------------------- + + +class TestParentUpdate: + """ParentUpdate ABC and NoOpParentUpdate.""" + + def test_parent_update_is_abstract(self): + """ParentUpdate cannot be instantiated directly.""" + with pytest.raises(TypeError): + ParentUpdate() # type: ignore[abstract] + + def test_no_op_does_not_raise(self): + """NoOpParentUpdate.apply runs without raising.""" + update = NoOpParentUpdate() + update.apply(None) # must not raise + + +# --------------------------------------------------------------------------- +# Deployable +# --------------------------------------------------------------------------- + + +class TestDeployable: + """Deployable attaches a body with a deployment event.""" + + def test_basic_creation(self): + """Deployable can be created with minimal arguments.""" + d = _make_deployable() + assert d.name == "payload" + + def test_deployment_event_in_events_list(self): + """deployment_event is included in the events list.""" + ev = _make_deployment_event() + d = _make_deployable(deployment_event=ev) + assert ev in d.events + + def test_default_separation_model(self): + """Default separation model is InstantaneousSeparation.""" + d = _make_deployable() + assert isinstance(d.separation, InstantaneousSeparation) + + def test_default_parent_update(self): + """Default parent update is NoOpParentUpdate.""" + d = _make_deployable() + assert isinstance(d.parent_update, NoOpParentUpdate) + + def test_validate_passes_with_valid_config(self): + """validate() does not raise for a properly configured deployable.""" + d = _make_deployable() + d.validate() # must not raise + + def test_validate_raises_on_missing_body(self): + """validate() raises ValueError when body is None.""" + d = _make_deployable(body=None) + with pytest.raises(ValueError, match="body"): + d.validate() + + def test_validate_raises_on_missing_attachment(self): + """validate() raises ValueError when attachment is None.""" + d = _make_deployable(attachment=None) + with pytest.raises(ValueError, match="attachment"): + d.validate() + + +# --------------------------------------------------------------------------- +# Stage +# --------------------------------------------------------------------------- + + +class TestStage: + """Stage tracks lifecycle and holds separation/ignition events.""" + + def test_initial_state_is_attached(self): + """Stage starts in ATTACHED state.""" + stage = _make_stage() + assert stage.state == StageState.ATTACHED + + def test_validate_passes_with_valid_config(self): + """validate() does not raise for a well-configured stage.""" + stage = _make_stage() + stage.validate() + + def test_validate_raises_on_missing_body(self): + """validate() raises ValueError when body is None.""" + stage = _make_stage(body=None) + with pytest.raises(ValueError, match="body"): + stage.validate() + + def test_validate_raises_on_missing_attachment(self): + """validate() raises ValueError when attachment is None.""" + stage = _make_stage(attachment=None) + with pytest.raises(ValueError, match="attachment"): + stage.validate() + + def test_separation_event_in_events(self): + """separation_event is included in the events list.""" + sep_ev = StageSeparationEvent("sep", _always_trigger) + stage = _make_stage(separation_event=sep_ev) + assert sep_ev in stage.events + + def test_ignition_event_in_events(self): + """ignition_event is included in the events list.""" + ign_ev = IgnitionEvent("ign", _always_trigger) + stage = _make_stage(ignition_event=ign_ev) + assert ign_ev in stage.events + + def test_default_separation_model(self): + """Default separation model is InstantaneousSeparation.""" + stage = _make_stage() + assert isinstance(stage.separation, InstantaneousSeparation) + + def test_default_parent_update(self): + """Default parent update is NoOpParentUpdate.""" + stage = _make_stage() + assert isinstance(stage.parent_update, NoOpParentUpdate) + + +# --------------------------------------------------------------------------- +# Mission +# --------------------------------------------------------------------------- + + +class TestMissionInit: + """Mission stores name and starts with empty lists.""" + + def test_default_name(self): + """Default mission name is 'Mission'.""" + m = Mission() + assert m.name == "Mission" + + def test_custom_name(self): + """Custom name is stored correctly.""" + m = Mission("ASTER_X") + assert m.name == "ASTER_X" + + def test_stages_starts_empty(self): + """stages list starts empty.""" + m = Mission() + assert m.stages == [] + + def test_deployables_starts_empty(self): + """deployables list starts empty.""" + m = Mission() + assert m.deployables == [] + + +class TestMissionAddStage: + """Mission.add_stage validates and appends stages.""" + + def test_add_valid_stage(self): + """add_stage appends a valid Stage.""" + m = Mission() + s = _make_stage() + m.add_stage(s) + assert s in m.stages + + def test_add_multiple_stages(self): + """add_stage appends multiple stages in order.""" + m = Mission() + s1 = _make_stage(name="stage_1") + s2 = _make_stage(name="stage_2") + m.add_stage(s1) + m.add_stage(s2) + assert m.stages == [s1, s2] + + def test_add_stage_raises_for_non_stage(self): + """add_stage raises TypeError when given a non-Stage object.""" + m = Mission() + with pytest.raises(TypeError): + m.add_stage("not_a_stage") + + def test_add_stage_raises_for_invalid_stage(self): + """add_stage raises ValueError when stage.validate() fails.""" + m = Mission() + bad_stage = _make_stage(body=None) # body=None will fail validate() + with pytest.raises(ValueError): + m.add_stage(bad_stage) + + +class TestMissionAddDeployable: + """Mission.add_deployable validates and appends deployables.""" + + def test_add_valid_deployable(self): + """add_deployable appends a valid Deployable.""" + m = Mission() + d = _make_deployable() + m.add_deployable(d) + assert d in m.deployables + + def test_add_deployable_raises_for_non_deployable(self): + """add_deployable raises TypeError when given a non-Deployable.""" + m = Mission() + with pytest.raises(TypeError): + m.add_deployable(42) + + def test_add_deployable_raises_for_invalid_deployable(self): + """add_deployable raises ValueError when deployable.validate() fails.""" + m = Mission() + bad = _make_deployable(body=None) + with pytest.raises(ValueError): + m.add_deployable(bad) + + +class TestMissionAttachedItems: + """Mission.attached_items returns all items in stages-first order.""" + + def test_attached_items_combines_stages_and_deployables(self): + """attached_items returns stages then deployables.""" + m = Mission() + s = _make_stage() + d = _make_deployable() + m.add_stage(s) + m.add_deployable(d) + items = m.attached_items() + assert items == [s, d] + + def test_attached_items_empty_when_nothing_added(self): + """attached_items returns empty list when no items added.""" + m = Mission() + assert m.attached_items() == [] + + def test_len_counts_all_items(self): + """len(mission) counts stages + deployables.""" + m = Mission() + m.add_stage(_make_stage()) + m.add_deployable(_make_deployable()) + assert len(m) == 2 diff --git a/tests/unit/rocket/test_rocket_attachment_host.py b/tests/unit/rocket/test_rocket_attachment_host.py new file mode 100644 index 000000000..5bb189105 --- /dev/null +++ b/tests/unit/rocket/test_rocket_attachment_host.py @@ -0,0 +1,216 @@ +"""Unit tests for the Rocket AttachmentHost interface methods.""" + +import pytest + +from rocketpy import Rocket +from rocketpy.body import RocketAdapter +from rocketpy.mission import ( + Attachment, + Deployable, + DeploymentEvent, + IgnitionEvent, + Stage, + StageSeparationEvent, +) +from rocketpy.body import FlightBody + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_rocket(): + """Return a minimal Rocket instance.""" + return Rocket( + radius=0.0635, + mass=16.235, + inertia=(6.321, 6.321, 0.0346), + power_off_drag=0.5, + power_on_drag=0.5, + center_of_mass_without_motor=0, + ) + + +def _make_body(name="test_body"): + return FlightBody( + name=name, + geometry=0.05, + mass_model=lambda t: 5.0, + inertia_model=lambda t: (0.5, 0.5, 0.01, 0.0, 0.0, 0.0), + center_of_mass_model=lambda t: 0.4, + ) + + +def _make_attachment(): + return Attachment([0.0, 0.0, 1.5], [0.0, 0.0, 0.0]) + + +def _always(state, ctx): + return True + + +def _make_stage(**kwargs): + defaults = dict( + name="upper_stage", + body=_make_body("upper_body"), + attachment=_make_attachment(), + separation_event=StageSeparationEvent("sep", _always), + ) + defaults.update(kwargs) + return Stage(**defaults) + + +def _make_deployable(**kwargs): + defaults = dict( + name="payload", + body=_make_body("payload"), + attachment=_make_attachment(), + deployment_event=DeploymentEvent("deploy", _always), + ) + defaults.update(kwargs) + return Deployable(**defaults) + + +# --------------------------------------------------------------------------- +# Tests – stages / deployables lists +# --------------------------------------------------------------------------- + + +class TestRocketMissionLists: + """Rocket starts with empty mission lists.""" + + def test_stages_empty_by_default(self): + """stages is empty when no stages have been added.""" + r = _make_rocket() + assert r.stages == [] + + def test_deployables_empty_by_default(self): + """deployables is empty when no deployables have been added.""" + r = _make_rocket() + assert r.deployables == [] + + +# --------------------------------------------------------------------------- +# Tests – add_stage / add_deployable +# --------------------------------------------------------------------------- + + +class TestRocketAddStage: + """Rocket.add_stage validates and stores stages.""" + + def test_add_valid_stage(self): + """add_stage appends a Stage to the internal list.""" + r = _make_rocket() + s = _make_stage() + r.add_stage(s) + assert s in r.stages + + def test_add_stage_raises_for_non_stage(self): + """add_stage raises TypeError when given a non-Stage object.""" + r = _make_rocket() + with pytest.raises(TypeError): + r.add_stage("not a stage") + + def test_add_stage_raises_for_invalid_stage(self): + """add_stage raises ValueError when stage.validate() fails.""" + r = _make_rocket() + bad_stage = _make_stage(body=None) + with pytest.raises(ValueError): + r.add_stage(bad_stage) + + +class TestRocketAddDeployable: + """Rocket.add_deployable validates and stores deployables.""" + + def test_add_valid_deployable(self): + """add_deployable appends a Deployable.""" + r = _make_rocket() + d = _make_deployable() + r.add_deployable(d) + assert d in r.deployables + + def test_add_deployable_raises_for_non_deployable(self): + """add_deployable raises TypeError for a non-Deployable object.""" + r = _make_rocket() + with pytest.raises(TypeError): + r.add_deployable({"not": "a deployable"}) + + def test_add_deployable_raises_for_invalid_deployable(self): + """add_deployable raises ValueError when deployable.validate() fails.""" + r = _make_rocket() + bad = _make_deployable(body=None) + with pytest.raises(ValueError): + r.add_deployable(bad) + + +# --------------------------------------------------------------------------- +# Tests – select_stage +# --------------------------------------------------------------------------- + + +class TestRocketSelectStage: + """Rocket.select_stage retrieves stages by index or name.""" + + def test_select_by_index(self): + """select_stage(0) returns the first stage.""" + r = _make_rocket() + s = _make_stage(name="booster") + r.add_stage(s) + assert r.select_stage(0) is s + + def test_select_by_name(self): + """select_stage('booster') returns the stage with that name.""" + r = _make_rocket() + s = _make_stage(name="booster") + r.add_stage(s) + assert r.select_stage("booster") is s + + def test_select_by_index_out_of_range_raises(self): + """select_stage raises IndexError for an out-of-range index.""" + r = _make_rocket() + with pytest.raises(IndexError): + r.select_stage(0) + + def test_select_by_name_not_found_raises(self): + """select_stage raises KeyError for an unknown stage name.""" + r = _make_rocket() + with pytest.raises(KeyError): + r.select_stage("nonexistent") + + def test_select_with_invalid_type_raises(self): + """select_stage raises TypeError for a non-int/str argument.""" + r = _make_rocket() + with pytest.raises(TypeError): + r.select_stage(3.14) # type: ignore[arg-type] + + +# --------------------------------------------------------------------------- +# Tests – attached_items / as_body +# --------------------------------------------------------------------------- + + +class TestRocketAttachmentHostHelpers: + """Rocket.attached_items and Rocket.as_body satisfy the interface.""" + + def test_attached_items_combines_stages_and_deployables(self): + """attached_items returns stages + deployables.""" + r = _make_rocket() + s = _make_stage() + d = _make_deployable() + r.add_stage(s) + r.add_deployable(d) + items = r.attached_items() + assert items == [s, d] + + def test_attached_items_empty_by_default(self): + """attached_items is empty before adding anything.""" + r = _make_rocket() + assert r.attached_items() == [] + + def test_as_body_returns_rocket_adapter(self): + """as_body() returns a RocketAdapter wrapping this rocket.""" + r = _make_rocket() + adapter = r.as_body() + assert isinstance(adapter, RocketAdapter) + assert adapter.rocket is r diff --git a/tests/unit/simulation/test_flight_branch.py b/tests/unit/simulation/test_flight_branch.py new file mode 100644 index 000000000..18d6d238b --- /dev/null +++ b/tests/unit/simulation/test_flight_branch.py @@ -0,0 +1,130 @@ +"""Unit tests for rocketpy.simulation.FlightBranch.""" + +import pytest + +from rocketpy.simulation import FlightBranch + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +class _FakeBody: + name = "fake_body" + + +# --------------------------------------------------------------------------- +# FlightBranch initialization +# --------------------------------------------------------------------------- + + +class TestFlightBranchInit: + """FlightBranch stores its constructor arguments correctly.""" + + def test_body_stored(self): + """body attribute is stored.""" + body = _FakeBody() + branch = FlightBranch(body=body) + assert branch.body is body + + def test_default_start_time_is_zero(self): + """start_time defaults to 0.0 when not provided.""" + branch = FlightBranch(body=None) + assert branch.start_time == pytest.approx(0.0) + + def test_custom_start_time(self): + """start_time is stored from the constructor argument.""" + branch = FlightBranch(body=None, start_time=5.5) + assert branch.start_time == pytest.approx(5.5) + + def test_parent_defaults_to_none(self): + """parent is None when not provided.""" + branch = FlightBranch(body=None) + assert branch.parent is None + + def test_children_starts_empty(self): + """children list starts empty.""" + branch = FlightBranch(body=None) + assert branch.children == [] + + def test_events_starts_empty(self): + """events list starts empty when not provided.""" + branch = FlightBranch(body=None) + assert branch.events == [] + + def test_events_stored(self): + """events list is stored from constructor argument.""" + ev = object() + branch = FlightBranch(body=None, events=[ev]) + assert ev in branch.events + + +# --------------------------------------------------------------------------- +# Tree structure +# --------------------------------------------------------------------------- + + +class TestFlightBranchTree: + """FlightBranch tracks parent/child relationships.""" + + def test_is_root_for_no_parent(self): + """is_root() returns True when parent is None.""" + root = FlightBranch(body=None) + assert root.is_root() is True + + def test_is_not_root_when_parent_set(self): + """is_root() returns False when a parent is set.""" + root = FlightBranch(body=None) + child = FlightBranch(body=None, parent=root) + assert child.is_root() is False + + def test_parent_registers_child(self): + """Creating a child with a parent registers it in parent.children.""" + root = FlightBranch(body=None) + child = FlightBranch(body=None, parent=root) + assert child in root.children + + def test_spawn_child_creates_child(self): + """spawn_child() creates and registers a child branch.""" + root = FlightBranch(body=_FakeBody(), start_time=0.0) + child = root.spawn_child(body=_FakeBody(), start_time=10.0) + assert child.parent is root + assert child in root.children + + def test_spawn_child_inherits_start_state_if_not_given(self): + """spawn_child inherits parent start_state when not provided.""" + state = {"x": 0} + root = FlightBranch(body=None, start_state=state) + child = root.spawn_child(body=None) + assert child.start_state is state + + def test_spawn_child_uses_provided_start_time(self): + """spawn_child uses the provided start_time.""" + root = FlightBranch(body=None, start_time=0.0) + child = root.spawn_child(body=None, start_time=42.0) + assert child.start_time == pytest.approx(42.0) + + def test_multiple_children(self): + """A single parent can have multiple child branches.""" + root = FlightBranch(body=None) + c1 = root.spawn_child(body=_FakeBody()) + c2 = root.spawn_child(body=_FakeBody()) + assert len(root.children) == 2 + assert c1 in root.children + assert c2 in root.children + + +# --------------------------------------------------------------------------- +# Repr +# --------------------------------------------------------------------------- + + +class TestFlightBranchRepr: + """FlightBranch has a useful __repr__.""" + + def test_repr_contains_body_name(self): + """__repr__ includes the body name.""" + body = _FakeBody() + branch = FlightBranch(body=body) + assert "fake_body" in repr(branch) From 7a1abf9134a5c6b715be2a215489b7c5b6967422 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 19:04:07 +0000 Subject: [PATCH 02/34] STY: address code review feedback on stubs, docstrings, and adapter comments Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7aeaec51-7514-4046-a8b0-037324036f38 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/body/rocket_adapter.py | 14 ++++++++--- rocketpy/mission/attachment.py | 18 +++++++++----- rocketpy/mission/events.py | 42 ++++++++++++++++++++++++++++++--- rocketpy/mission/stage_state.py | 6 +++++ 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/rocketpy/body/rocket_adapter.py b/rocketpy/body/rocket_adapter.py index 1a90b5c01..ff0501dbd 100644 --- a/rocketpy/body/rocket_adapter.py +++ b/rocketpy/body/rocket_adapter.py @@ -133,20 +133,28 @@ def recovery_systems(self): def sensors(self): """Return the sensors attached to the rocket. + Accesses ``rocket.sensors``, which is a public + :class:`~rocketpy.rocket.Components` collection on the + :class:`~rocketpy.rocket.Rocket` class. + Returns ------- - list - ``rocket.sensors``. + list or Components + ``rocket.sensors``, or an empty list if the attribute is absent. """ return getattr(self.rocket, "sensors", []) def controllers(self): """Return the active controllers attached to the rocket. + Accesses ``rocket._controllers``, which is a private list on the + :class:`~rocketpy.rocket.Rocket` class. + Returns ------- list - ``rocket._controllers``. + ``rocket._controllers``, or an empty list if the attribute is + absent. """ return getattr(self.rocket, "_controllers", []) diff --git a/rocketpy/mission/attachment.py b/rocketpy/mission/attachment.py index a337dc477..804d9fec6 100644 --- a/rocketpy/mission/attachment.py +++ b/rocketpy/mission/attachment.py @@ -53,21 +53,27 @@ def __init__( def resolve_child_pose(self, parent_state): """Compute the initial child pose from the parent state. + .. note:: + **Stub implementation** – this method currently returns + *parent_state* unchanged and does *not* apply the attachment + offsets (:attr:`parent_frame_position`, + :attr:`child_frame_position`) or :attr:`orientation`. A full + 6-DOF pose resolver will be implemented in a future iteration of + the multistage simulation engine. + Parameters ---------- parent_state : object Current state of the parent body (position, orientation, …). - The interpretation is left to the caller; this method simply - returns *parent_state* unchanged as a sensible default until a - full 6-DOF pose resolver is implemented. Returns ------- object - Initial state for the child body. + Initial state for the child body (currently equal to + *parent_state*). """ - # Placeholder: a full implementation would compose the parent - # pose with the attachment offsets and orientations. + # TODO: compose parent_state with attachment offsets and orientation + # to produce the true initial child pose. return parent_state def __repr__(self) -> str: diff --git a/rocketpy/mission/events.py b/rocketpy/mission/events.py index 685742285..fe31776b6 100644 --- a/rocketpy/mission/events.py +++ b/rocketpy/mission/events.py @@ -48,6 +48,12 @@ def should_fire(self, state, context) -> bool: def apply(self, branch, context): """Apply the event's effect to a flight branch. + Concrete implementations should mutate *branch* (e.g. spawn child + branches, update the body, activate recovery systems). The default + stub in each concrete subclass is intentionally a no-op; the actual + orchestration is performed by the simulation engine which calls + :meth:`should_fire` and then :meth:`apply` at each time step. + Parameters ---------- branch : :class:`~rocketpy.simulation.FlightBranch` @@ -95,10 +101,19 @@ def apply(self, branch, context): Active branch. context : object Simulation context. + + Notes + ----- + The actual separation logic is delegated to the Deployable's + :class:`~rocketpy.mission.SeparationModel` and + :class:`~rocketpy.mission.ParentUpdate`. Orchestration is performed + by the simulation engine. + + .. todo:: + Wire up ``Deployable.separation.apply()`` and + ``Deployable.parent_update.apply()`` here once the simulation + engine integration layer is implemented. """ - # The actual separation logic is delegated to the Deployable's - # SeparationModel and ParentUpdate. This stub is intentional; - # the simulation engine is responsible for the full orchestration. class StageSeparationEvent(Event): @@ -130,6 +145,14 @@ def apply(self, branch, context): Active branch. context : object Simulation context. + + Notes + ----- + .. todo:: + Implement stage separation: invoke + ``Stage.separation.apply()`` and ``Stage.parent_update.apply()``, + and spawn a new child :class:`~rocketpy.simulation.FlightBranch` + for the separated stage. """ @@ -162,6 +185,13 @@ def apply(self, branch, context): Active branch. context : object Simulation context. + + Notes + ----- + .. todo:: + Implement motor ignition: activate the propulsion model on + the stage body and transition :attr:`Stage.state` to + :attr:`~rocketpy.mission.StageState.IGNITED`. """ @@ -194,4 +224,10 @@ def apply(self, branch, context): Active branch. context : object Simulation context. + + Notes + ----- + .. todo:: + Implement recovery system activation: deploy the parachute or + other recovery device attached to the branch body. """ diff --git a/rocketpy/mission/stage_state.py b/rocketpy/mission/stage_state.py index fa657a139..06e695871 100644 --- a/rocketpy/mission/stage_state.py +++ b/rocketpy/mission/stage_state.py @@ -6,6 +6,12 @@ class StageState(Enum): """Enumeration of the lifecycle states a :class:`Stage` can occupy. + Typical lifecycle progressions are: + + - Powered stage: ``ATTACHED → IGNITED → SEPARATED → SPENT`` + - Unpowered drop stage (e.g. fairing): ``ATTACHED → SEPARATED`` + - Sustainer (never physically separated): ``ATTACHED → IGNITED → SPENT`` + Attributes ---------- ATTACHED : From 5ac9222f296fc447644f9d0e1f8e57beda6fcec0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 19:27:40 +0000 Subject: [PATCH 03/34] ENH add multistage example notebook Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/cd5d9e07-75af-4cce-8422-078f9628a69a Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 300 ++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 docs/multistage_example.ipynb diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb new file mode 100644 index 000000000..4b77657c1 --- /dev/null +++ b/docs/multistage_example.ipynb @@ -0,0 +1,300 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multistage mission setup example\n", + "\n", + "This notebook walks through a minimal, step-by-step setup of the multistage\n", + "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It focuses on\n", + "assembling the mission data model; full flight integration is still under\n", + "active development." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from rocketpy import (\n", + " Attachment,\n", + " Deployable,\n", + " DeploymentEvent,\n", + " FlightBody,\n", + " IgnitionEvent,\n", + " Mission,\n", + " Stage,\n", + " StageSeparationEvent,\n", + ")" + ], + "execution_count": 1, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Define simple time-invariant models" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def constant_mass(value):\n", + " def model(t):\n", + " return float(value)\n", + "\n", + " return model\n", + "\n", + "\n", + "def constant_inertia_tensor(i11, i22, i33):\n", + " def model(t):\n", + " return (\n", + " (i11, 0.0, 0.0),\n", + " (0.0, i22, 0.0),\n", + " (0.0, 0.0, i33),\n", + " )\n", + "\n", + " return model\n", + "\n", + "\n", + "def constant_center_of_mass(value):\n", + " def model(t):\n", + " return float(value)\n", + "\n", + " return model" + ], + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Create the root body (Stage 1)" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "stage1_body = FlightBody(\n", + " name=\"Stage 1\",\n", + " geometry={\"radius\": 0.08, \"length\": 2.4},\n", + " mass_model=constant_mass(18.0),\n", + " inertia_model=constant_inertia_tensor(2.8, 2.8, 0.15),\n", + " center_of_mass_model=constant_center_of_mass(1.1),\n", + ")\n", + "\n", + "print(\"Stage 1:\", stage1_body.name)\n", + "print(\" mass @ t=0 s:\", stage1_body.mass(0.0), \"kg\")\n", + "print(\" center of mass @ t=0 s:\", stage1_body.center_of_mass(0.0), \"m\")" + ], + "execution_count": 3, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Stage 1: Stage 1\n mass @ t=0 s: 18.0 kg\n center of mass @ t=0 s: 1.1 m\n" + } + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Create the upper-stage body (Stage 2)" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "stage2_body = FlightBody(\n", + " name=\"Stage 2\",\n", + " geometry={\"radius\": 0.06, \"length\": 1.4},\n", + " mass_model=constant_mass(7.5),\n", + " inertia_model=constant_inertia_tensor(1.1, 1.1, 0.06),\n", + " center_of_mass_model=constant_center_of_mass(0.7),\n", + ")\n", + "\n", + "print(\"Stage 2:\", stage2_body.name)\n", + "print(\" mass @ t=0 s:\", stage2_body.mass(0.0), \"kg\")" + ], + "execution_count": 4, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Stage 2: Stage 2\n mass @ t=0 s: 7.5 kg\n" + } + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Define attachment and events for Stage 2" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "stage2_attachment = Attachment(\n", + " parent_frame_position=[0.0, 0.0, 2.2],\n", + " child_frame_position=[0.0, 0.0, 0.0],\n", + " constraints=\"rigid\",\n", + ")\n", + "\n", + "\n", + "def ignition_trigger(state, context):\n", + " return context[\"time\"] >= 2.0\n", + "\n", + "\n", + "def separation_trigger(state, context):\n", + " return context[\"time\"] >= 5.0\n", + "\n", + "\n", + "ignition_event = IgnitionEvent(\"stage2_ignite\", ignition_trigger)\n", + "separation_event = StageSeparationEvent(\"stage2_separate\", separation_trigger)\n", + "\n", + "context = {\"time\": 3.0}\n", + "print(\"Ignition fires @ t=3 s?\", ignition_event.should_fire({}, context))\n", + "print(\"Separation fires @ t=3 s?\", separation_event.should_fire({}, context))" + ], + "execution_count": 5, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Ignition fires @ t=3 s? True\nSeparation fires @ t=3 s? False\n" + } + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Wrap Stage 2 as a mission `Stage`" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "stage2 = Stage(\n", + " name=\"Stage 2\",\n", + " body=stage2_body,\n", + " attachment=stage2_attachment,\n", + " separation_event=separation_event,\n", + " ignition_event=ignition_event,\n", + ")\n", + "\n", + "print(\"Stage object:\", stage2)\n", + "print(\" state:\", stage2.state)\n", + "print(\" events:\", [event.name for event in stage2.events])" + ], + "execution_count": 6, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Stage object: Stage(name='Stage 2')\n state: StageState.ATTACHED\n events: ['stage2_separate', 'stage2_ignite']\n" + } + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Create a deployable payload" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "payload_body = FlightBody(\n", + " name=\"Payload\",\n", + " geometry={\"radius\": 0.05, \"length\": 0.4},\n", + " mass_model=constant_mass(1.2),\n", + " inertia_model=constant_inertia_tensor(0.2, 0.2, 0.01),\n", + " center_of_mass_model=constant_center_of_mass(0.2),\n", + ")\n", + "\n", + "payload_attachment = Attachment(\n", + " parent_frame_position=[0.0, 0.0, 2.6],\n", + " child_frame_position=[0.0, 0.0, 0.0],\n", + " constraints=\"rigid\",\n", + ")\n", + "\n", + "\n", + "def payload_trigger(state, context):\n", + " return context[\"time\"] >= 8.0\n", + "\n", + "\n", + "payload_event = DeploymentEvent(\"payload_release\", payload_trigger)\n", + "\n", + "payload = Deployable(\n", + " name=\"Payload\",\n", + " body=payload_body,\n", + " attachment=payload_attachment,\n", + " deployment_event=payload_event,\n", + ")\n", + "\n", + "context = {\"time\": 9.0}\n", + "print(\"Payload event fires @ t=9 s?\", payload_event.should_fire({}, context))" + ], + "execution_count": 7, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Payload event fires @ t=9 s? True\n" + } + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Build the Mission" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "mission = Mission(name=\"Two-Stage Demo\")\n", + "mission.add_stage(stage2)\n", + "mission.add_deployable(payload)\n", + "\n", + "print(mission)\n", + "print(\"Attached items:\", [item.name for item in mission.attached_items()])" + ], + "execution_count": 8, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Mission(name='Two-Stage Demo', stages=1, deployables=1)\nAttached items: ['Stage 2', 'Payload']\n" + } + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 27cbd2fe35b6d7346323fb423dcb1e0ae91b2f57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 19:33:34 +0000 Subject: [PATCH 04/34] ENH align multistage notebook inertia output Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/cd5d9e07-75af-4cce-8422-078f9628a69a Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 4b77657c1..26fbcf527 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -50,11 +50,7 @@ "\n", "def constant_inertia_tensor(i11, i22, i33):\n", " def model(t):\n", - " return (\n", - " (i11, 0.0, 0.0),\n", - " (0.0, i22, 0.0),\n", - " (0.0, 0.0, i33),\n", - " )\n", + " return (i11, i22, i33, 0.0, 0.0, 0.0)\n", "\n", " return model\n", "\n", From fd06e4b73319e1a75b23ea7f3c9c18847a19aa07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:11:48 +0000 Subject: [PATCH 05/34] DOC: add flight simulation to multistage example Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/63cc6f82-b399-459b-8a12-a935a8580aec Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 66 ++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 26fbcf527..fd67a3744 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -7,9 +7,10 @@ "# Multistage mission setup example\n", "\n", "This notebook walks through a minimal, step-by-step setup of the multistage\n", - "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It focuses on\n", - "assembling the mission data model; full flight integration is still under\n", - "active development." + "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It also\n", + "runs a simplified single-stage flight simulation for Stage 1 to show how\n", + "the mission data can be paired with RocketPy's `Flight` engine while full\n", + "multistage coupling continues to evolve.\n" ] }, { @@ -20,12 +21,16 @@ " Attachment,\n", " Deployable,\n", " DeploymentEvent,\n", + " Environment,\n", + " Flight,\n", " FlightBody,\n", " IgnitionEvent,\n", " Mission,\n", + " PointMassMotor,\n", + " PointMassRocket,\n", " Stage,\n", " StageSeparationEvent,\n", - ")" + ")\n" ], "execution_count": 1, "outputs": [] @@ -278,6 +283,57 @@ "text": "Mission(name='Two-Stage Demo', stages=1, deployables=1)\nAttached items: ['Stage 2', 'Payload']\n" } ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Run a simplified flight simulation\n", + "\n", + "This point-mass simulation uses the Stage 1 mass properties to run a\n", + "single-stage ascent with a lightweight motor model.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "execution_count": null, + "source": [ + "stage1_radius = stage1_body.geometry[\"radius\"]\n", + "stage1_mass = stage1_body.mass(0.0)\n", + "stage1_com = stage1_body.center_of_mass(0.0)\n", + "\n", + "stage1_motor = PointMassMotor(\n", + " thrust_source=480.0,\n", + " dry_mass=1.2,\n", + " propellant_initial_mass=2.8,\n", + " burn_time=2.4,\n", + ")\n", + "\n", + "stage1_rocket = PointMassRocket(\n", + " radius=stage1_radius,\n", + " mass=stage1_mass,\n", + " center_of_mass_without_motor=stage1_com,\n", + " power_off_drag=0.45,\n", + " power_on_drag=0.45,\n", + ")\n", + "stage1_rocket.add_motor(stage1_motor, position=0.0)\n", + "\n", + "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", + "\n", + "flight = Flight(\n", + " rocket=stage1_rocket,\n", + " environment=environment,\n", + " rail_length=5.0,\n", + " inclination=85.0,\n", + " heading=0.0,\n", + " terminate_on_apogee=True,\n", + ")\n", + "\n", + "print(f\"Stage 1 apogee: {flight.apogee:.1f} m\")\n", + "print(f\"Stage 1 max speed: {flight.max_speed:.1f} m/s\")\n" + ], + "outputs": [] } ], "metadata": { @@ -293,4 +349,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From 841c0d92389749349ad793b073eb6b77ff4b1d3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:23:02 +0000 Subject: [PATCH 06/34] DOC: run flights for all mission items Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/1fde99ff-a638-4ca8-bd18-40587d2c0252 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 75 ++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index fd67a3744..9f927ed05 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -288,10 +288,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 8. Run a simplified flight simulation\n", + "## 8. Run simplified flight simulations\n", "\n", - "This point-mass simulation uses the Stage 1 mass properties to run a\n", - "single-stage ascent with a lightweight motor model.\n" + "Run a point-mass flight for each stage and deployable. The Stage 1\n", + "trajectory provides the initial conditions for Stage 2 and the payload\n", + "at their separation times.\n" ] }, { @@ -299,6 +300,8 @@ "metadata": {}, "execution_count": null, "source": [ + "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", + "\n", "stage1_radius = stage1_body.geometry[\"radius\"]\n", "stage1_mass = stage1_body.mass(0.0)\n", "stage1_com = stage1_body.center_of_mass(0.0)\n", @@ -319,19 +322,73 @@ ")\n", "stage1_rocket.add_motor(stage1_motor, position=0.0)\n", "\n", - "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", - "\n", - "flight = Flight(\n", + "stage1_flight = Flight(\n", " rocket=stage1_rocket,\n", " environment=environment,\n", " rail_length=5.0,\n", " inclination=85.0,\n", " heading=0.0,\n", - " terminate_on_apogee=True,\n", + " max_time=20.0,\n", + " terminate_on_apogee=False,\n", + ")\n", + "\n", + "stage2_radius = stage2_body.geometry[\"radius\"]\n", + "stage2_mass = stage2_body.mass(0.0)\n", + "stage2_com = stage2_body.center_of_mass(0.0)\n", + "\n", + "stage2_motor = PointMassMotor(\n", + " thrust_source=260.0,\n", + " dry_mass=0.6,\n", + " propellant_initial_mass=1.2,\n", + " burn_time=1.6,\n", + ")\n", + "\n", + "stage2_rocket = PointMassRocket(\n", + " radius=stage2_radius,\n", + " mass=stage2_mass,\n", + " center_of_mass_without_motor=stage2_com,\n", + " power_off_drag=0.35,\n", + " power_on_drag=0.35,\n", + ")\n", + "stage2_rocket.add_motor(stage2_motor, position=0.0)\n", + "\n", + "stage2_initial_state = stage1_flight.get_solution_at_time(5.0)\n", + "\n", + "stage2_flight = Flight(\n", + " rocket=stage2_rocket,\n", + " environment=environment,\n", + " rail_length=1.0,\n", + " initial_solution=stage2_initial_state,\n", + " max_time=20.0,\n", + " terminate_on_apogee=False,\n", + ")\n", + "\n", + "payload_radius = payload_body.geometry[\"radius\"]\n", + "payload_mass = payload_body.mass(0.0)\n", + "payload_com = payload_body.center_of_mass(0.0)\n", + "\n", + "payload_rocket = PointMassRocket(\n", + " radius=payload_radius,\n", + " mass=payload_mass,\n", + " center_of_mass_without_motor=payload_com,\n", + " power_off_drag=0.3,\n", + " power_on_drag=0.3,\n", + ")\n", + "\n", + "payload_initial_state = stage2_flight.get_solution_at_time(8.0)\n", + "\n", + "payload_flight = Flight(\n", + " rocket=payload_rocket,\n", + " environment=environment,\n", + " rail_length=1.0,\n", + " initial_solution=payload_initial_state,\n", + " max_time=20.0,\n", + " terminate_on_apogee=False,\n", ")\n", "\n", - "print(f\"Stage 1 apogee: {flight.apogee:.1f} m\")\n", - "print(f\"Stage 1 max speed: {flight.max_speed:.1f} m/s\")\n" + "print(f\"Stage 1 apogee: {stage1_flight.apogee:.1f} m\")\n", + "print(f\"Stage 2 apogee: {stage2_flight.apogee:.1f} m\")\n", + "print(f\"Payload apogee: {payload_flight.apogee:.1f} m\")\n" ], "outputs": [] } From aac9398d372e749a198a965c226a80cf4c8167e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:28:53 +0000 Subject: [PATCH 07/34] DOC: reuse mission objects for multistage flights Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/c0445262-bfd1-4f4f-9123-52023054ee8f Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 9f927ed05..318f7f9bb 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -8,9 +8,9 @@ "\n", "This notebook walks through a minimal, step-by-step setup of the multistage\n", "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It also\n", - "runs a simplified single-stage flight simulation for Stage 1 to show how\n", - "the mission data can be paired with RocketPy's `Flight` engine while full\n", - "multistage coupling continues to evolve.\n" + "runs simplified point-mass flight simulations for Stage 1, Stage 2, and\n", + "the payload to show how the mission architecture data can drive multistage\n", + "analysis while full coupling continues to evolve.\n" ] }, { @@ -149,13 +149,16 @@ " constraints=\"rigid\",\n", ")\n", "\n", + "STAGE2_IGNITION_TIME = 2.0\n", + "STAGE2_SEPARATION_TIME = 5.0\n", + "\n", "\n", "def ignition_trigger(state, context):\n", - " return context[\"time\"] >= 2.0\n", + " return context[\"time\"] >= STAGE2_IGNITION_TIME\n", "\n", "\n", "def separation_trigger(state, context):\n", - " return context[\"time\"] >= 5.0\n", + " return context[\"time\"] >= STAGE2_SEPARATION_TIME\n", "\n", "\n", "ignition_event = IgnitionEvent(\"stage2_ignite\", ignition_trigger)\n", @@ -163,16 +166,10 @@ "\n", "context = {\"time\": 3.0}\n", "print(\"Ignition fires @ t=3 s?\", ignition_event.should_fire({}, context))\n", - "print(\"Separation fires @ t=3 s?\", separation_event.should_fire({}, context))" + "print(\"Separation fires @ t=3 s?\", separation_event.should_fire({}, context))\n" ], - "execution_count": 5, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Ignition fires @ t=3 s? True\nSeparation fires @ t=3 s? False\n" - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -231,9 +228,11 @@ " constraints=\"rigid\",\n", ")\n", "\n", + "PAYLOAD_RELEASE_TIME = 8.0\n", + "\n", "\n", "def payload_trigger(state, context):\n", - " return context[\"time\"] >= 8.0\n", + " return context[\"time\"] >= PAYLOAD_RELEASE_TIME\n", "\n", "\n", "payload_event = DeploymentEvent(\"payload_release\", payload_trigger)\n", @@ -246,16 +245,10 @@ ")\n", "\n", "context = {\"time\": 9.0}\n", - "print(\"Payload event fires @ t=9 s?\", payload_event.should_fire({}, context))" + "print(\"Payload event fires @ t=9 s?\", payload_event.should_fire({}, context))\n" ], - "execution_count": 7, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Payload event fires @ t=9 s? True\n" - } - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -290,9 +283,10 @@ "source": [ "## 8. Run simplified flight simulations\n", "\n", - "Run a point-mass flight for each stage and deployable. The Stage 1\n", - "trajectory provides the initial conditions for Stage 2 and the payload\n", - "at their separation times.\n" + "Run a point-mass flight for Stage 1, then reuse the mission Stage 2 and\n", + "payload objects to build the upper-stage and deployable trajectories. The\n", + "Stage 1 trajectory provides the initial conditions at each separation\n", + "event.\n" ] }, { @@ -332,6 +326,7 @@ " terminate_on_apogee=False,\n", ")\n", "\n", + "stage2_body = stage2.body\n", "stage2_radius = stage2_body.geometry[\"radius\"]\n", "stage2_mass = stage2_body.mass(0.0)\n", "stage2_com = stage2_body.center_of_mass(0.0)\n", @@ -352,7 +347,9 @@ ")\n", "stage2_rocket.add_motor(stage2_motor, position=0.0)\n", "\n", - "stage2_initial_state = stage1_flight.get_solution_at_time(5.0)\n", + "stage2_initial_state = stage1_flight.get_solution_at_time(\n", + " STAGE2_SEPARATION_TIME\n", + ")\n", "\n", "stage2_flight = Flight(\n", " rocket=stage2_rocket,\n", @@ -363,6 +360,7 @@ " terminate_on_apogee=False,\n", ")\n", "\n", + "payload_body = payload.body\n", "payload_radius = payload_body.geometry[\"radius\"]\n", "payload_mass = payload_body.mass(0.0)\n", "payload_com = payload_body.center_of_mass(0.0)\n", @@ -375,7 +373,9 @@ " power_on_drag=0.3,\n", ")\n", "\n", - "payload_initial_state = stage2_flight.get_solution_at_time(8.0)\n", + "payload_initial_state = stage2_flight.get_solution_at_time(\n", + " PAYLOAD_RELEASE_TIME\n", + ")\n", "\n", "payload_flight = Flight(\n", " rocket=payload_rocket,\n", From ef2d6d45481c0ba0717a535be48d8409c5523e18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:30:16 +0000 Subject: [PATCH 08/34] DOC: clarify mission body variables Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/c0445262-bfd1-4f4f-9123-52023054ee8f Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 318f7f9bb..7db8b5392 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -326,10 +326,10 @@ " terminate_on_apogee=False,\n", ")\n", "\n", - "stage2_body = stage2.body\n", - "stage2_radius = stage2_body.geometry[\"radius\"]\n", - "stage2_mass = stage2_body.mass(0.0)\n", - "stage2_com = stage2_body.center_of_mass(0.0)\n", + "stage2_mission_body = stage2.body\n", + "stage2_radius = stage2_mission_body.geometry[\"radius\"]\n", + "stage2_mass = stage2_mission_body.mass(0.0)\n", + "stage2_com = stage2_mission_body.center_of_mass(0.0)\n", "\n", "stage2_motor = PointMassMotor(\n", " thrust_source=260.0,\n", @@ -360,10 +360,10 @@ " terminate_on_apogee=False,\n", ")\n", "\n", - "payload_body = payload.body\n", - "payload_radius = payload_body.geometry[\"radius\"]\n", - "payload_mass = payload_body.mass(0.0)\n", - "payload_com = payload_body.center_of_mass(0.0)\n", + "payload_mission_body = payload.body\n", + "payload_radius = payload_mission_body.geometry[\"radius\"]\n", + "payload_mass = payload_mission_body.mass(0.0)\n", + "payload_com = payload_mission_body.center_of_mass(0.0)\n", "\n", "payload_rocket = PointMassRocket(\n", " radius=payload_radius,\n", From 80ffed85a4804cd52375e2322405e5d6b46a4f42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:31:35 +0000 Subject: [PATCH 09/34] DOC: rename mission body refs Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/c0445262-bfd1-4f4f-9123-52023054ee8f Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 7db8b5392..e8a62611a 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -326,10 +326,10 @@ " terminate_on_apogee=False,\n", ")\n", "\n", - "stage2_mission_body = stage2.body\n", - "stage2_radius = stage2_mission_body.geometry[\"radius\"]\n", - "stage2_mass = stage2_mission_body.mass(0.0)\n", - "stage2_com = stage2_mission_body.center_of_mass(0.0)\n", + "stage2_body_from_mission = stage2.body\n", + "stage2_radius = stage2_body_from_mission.geometry[\"radius\"]\n", + "stage2_mass = stage2_body_from_mission.mass(0.0)\n", + "stage2_com = stage2_body_from_mission.center_of_mass(0.0)\n", "\n", "stage2_motor = PointMassMotor(\n", " thrust_source=260.0,\n", @@ -360,10 +360,10 @@ " terminate_on_apogee=False,\n", ")\n", "\n", - "payload_mission_body = payload.body\n", - "payload_radius = payload_mission_body.geometry[\"radius\"]\n", - "payload_mass = payload_mission_body.mass(0.0)\n", - "payload_com = payload_mission_body.center_of_mass(0.0)\n", + "payload_body_from_mission = payload.body\n", + "payload_radius = payload_body_from_mission.geometry[\"radius\"]\n", + "payload_mass = payload_body_from_mission.mass(0.0)\n", + "payload_com = payload_body_from_mission.center_of_mass(0.0)\n", "\n", "payload_rocket = PointMassRocket(\n", " radius=payload_radius,\n", From 70113d0bf0a48ad6c877e4590935d4aef067ad6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:42:00 +0000 Subject: [PATCH 10/34] DOC: build rockets from mission bodies Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/59acc550-0660-4213-8a0b-be68dc72eb60 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 61 ++++++++++++++--------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index e8a62611a..c13b31562 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -8,9 +8,10 @@ "\n", "This notebook walks through a minimal, step-by-step setup of the multistage\n", "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It also\n", - "runs simplified point-mass flight simulations for Stage 1, Stage 2, and\n", - "the payload to show how the mission architecture data can drive multistage\n", - "analysis while full coupling continues to evolve.\n" + "runs simplified flight simulations for Stage 1, Stage 2, and the payload\n", + "using `Rocket` objects derived from the mission bodies to show how the\n", + "mission architecture data can drive multistage analysis while full\n", + "coupling continues to evolve.\n" ] }, { @@ -27,7 +28,7 @@ " IgnitionEvent,\n", " Mission,\n", " PointMassMotor,\n", - " PointMassRocket,\n", + " Rocket,\n", " Stage,\n", " StageSeparationEvent,\n", ")\n" @@ -283,8 +284,8 @@ "source": [ "## 8. Run simplified flight simulations\n", "\n", - "Run a point-mass flight for Stage 1, then reuse the mission Stage 2 and\n", - "payload objects to build the upper-stage and deployable trajectories. The\n", + "Run a simplified flight for Stage 1, then reuse the mission Stage 2 and\n", + "payload bodies to build the upper-stage and deployable trajectories. The\n", "Stage 1 trajectory provides the initial conditions at each separation\n", "event.\n" ] @@ -296,9 +297,20 @@ "source": [ "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", "\n", - "stage1_radius = stage1_body.geometry[\"radius\"]\n", - "stage1_mass = stage1_body.mass(0.0)\n", - "stage1_com = stage1_body.center_of_mass(0.0)\n", + "def rocket_from_body(body, power_off_drag, power_on_drag):\n", + " radius = body.geometry[\"radius\"]\n", + " mass = body.mass(0.0)\n", + " center_of_mass = body.center_of_mass(0.0)\n", + " inertia = body.inertia_tensor(0.0)\n", + "\n", + " return Rocket(\n", + " radius=radius,\n", + " mass=mass,\n", + " inertia=inertia,\n", + " power_off_drag=power_off_drag,\n", + " power_on_drag=power_on_drag,\n", + " center_of_mass_without_motor=center_of_mass,\n", + " )\n", "\n", "stage1_motor = PointMassMotor(\n", " thrust_source=480.0,\n", @@ -307,13 +319,7 @@ " burn_time=2.4,\n", ")\n", "\n", - "stage1_rocket = PointMassRocket(\n", - " radius=stage1_radius,\n", - " mass=stage1_mass,\n", - " center_of_mass_without_motor=stage1_com,\n", - " power_off_drag=0.45,\n", - " power_on_drag=0.45,\n", - ")\n", + "stage1_rocket = rocket_from_body(stage1_body, 0.45, 0.45)\n", "stage1_rocket.add_motor(stage1_motor, position=0.0)\n", "\n", "stage1_flight = Flight(\n", @@ -327,9 +333,6 @@ ")\n", "\n", "stage2_body_from_mission = stage2.body\n", - "stage2_radius = stage2_body_from_mission.geometry[\"radius\"]\n", - "stage2_mass = stage2_body_from_mission.mass(0.0)\n", - "stage2_com = stage2_body_from_mission.center_of_mass(0.0)\n", "\n", "stage2_motor = PointMassMotor(\n", " thrust_source=260.0,\n", @@ -338,13 +341,7 @@ " burn_time=1.6,\n", ")\n", "\n", - "stage2_rocket = PointMassRocket(\n", - " radius=stage2_radius,\n", - " mass=stage2_mass,\n", - " center_of_mass_without_motor=stage2_com,\n", - " power_off_drag=0.35,\n", - " power_on_drag=0.35,\n", - ")\n", + "stage2_rocket = rocket_from_body(stage2_body_from_mission, 0.35, 0.35)\n", "stage2_rocket.add_motor(stage2_motor, position=0.0)\n", "\n", "stage2_initial_state = stage1_flight.get_solution_at_time(\n", @@ -361,17 +358,7 @@ ")\n", "\n", "payload_body_from_mission = payload.body\n", - "payload_radius = payload_body_from_mission.geometry[\"radius\"]\n", - "payload_mass = payload_body_from_mission.mass(0.0)\n", - "payload_com = payload_body_from_mission.center_of_mass(0.0)\n", - "\n", - "payload_rocket = PointMassRocket(\n", - " radius=payload_radius,\n", - " mass=payload_mass,\n", - " center_of_mass_without_motor=payload_com,\n", - " power_off_drag=0.3,\n", - " power_on_drag=0.3,\n", - ")\n", + "payload_rocket = rocket_from_body(payload_body_from_mission, 0.3, 0.3)\n", "\n", "payload_initial_state = stage2_flight.get_solution_at_time(\n", " PAYLOAD_RELEASE_TIME\n", From c18f5428cd3cef89eaa2f43b8cc665456f910987 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 17:38:27 +0000 Subject: [PATCH 11/34] DOC: use mission bodies in multistage example Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/a71d1b7b-84f0-4739-9883-8f7824595d57 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 63 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index c13b31562..3744b703d 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -9,9 +9,9 @@ "This notebook walks through a minimal, step-by-step setup of the multistage\n", "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It also\n", "runs simplified flight simulations for Stage 1, Stage 2, and the payload\n", - "using `Rocket` objects derived from the mission bodies to show how the\n", - "mission architecture data can drive multistage analysis while full\n", - "coupling continues to evolve.\n" + "using the mission Stage/Deployable bodies as the source of each flight\n", + "model so the mission architecture remains the single source of truth\n", + "while full coupling continues to evolve.\n" ] }, { @@ -284,10 +284,10 @@ "source": [ "## 8. Run simplified flight simulations\n", "\n", - "Run a simplified flight for Stage 1, then reuse the mission Stage 2 and\n", - "payload bodies to build the upper-stage and deployable trajectories. The\n", - "Stage 1 trajectory provides the initial conditions at each separation\n", - "event.\n" + "Run a simplified flight for Stage 1, then use the Stage 2 and payload\n", + "bodies from the mission to configure the upper-stage and deployable\n", + "trajectories. The Stage 1 trajectory provides the initial conditions at\n", + "each separation event.\n" ] }, { @@ -297,21 +297,6 @@ "source": [ "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", "\n", - "def rocket_from_body(body, power_off_drag, power_on_drag):\n", - " radius = body.geometry[\"radius\"]\n", - " mass = body.mass(0.0)\n", - " center_of_mass = body.center_of_mass(0.0)\n", - " inertia = body.inertia_tensor(0.0)\n", - "\n", - " return Rocket(\n", - " radius=radius,\n", - " mass=mass,\n", - " inertia=inertia,\n", - " power_off_drag=power_off_drag,\n", - " power_on_drag=power_on_drag,\n", - " center_of_mass_without_motor=center_of_mass,\n", - " )\n", - "\n", "stage1_motor = PointMassMotor(\n", " thrust_source=480.0,\n", " dry_mass=1.2,\n", @@ -319,7 +304,14 @@ " burn_time=2.4,\n", ")\n", "\n", - "stage1_rocket = rocket_from_body(stage1_body, 0.45, 0.45)\n", + "stage1_rocket = Rocket(\n", + " radius=stage1_body.geometry[\"radius\"],\n", + " mass=stage1_body.mass(0.0),\n", + " inertia=stage1_body.inertia_tensor(0.0),\n", + " power_off_drag=0.45,\n", + " power_on_drag=0.45,\n", + " center_of_mass_without_motor=stage1_body.center_of_mass(0.0),\n", + ")\n", "stage1_rocket.add_motor(stage1_motor, position=0.0)\n", "\n", "stage1_flight = Flight(\n", @@ -332,8 +324,6 @@ " terminate_on_apogee=False,\n", ")\n", "\n", - "stage2_body_from_mission = stage2.body\n", - "\n", "stage2_motor = PointMassMotor(\n", " thrust_source=260.0,\n", " dry_mass=0.6,\n", @@ -341,7 +331,15 @@ " burn_time=1.6,\n", ")\n", "\n", - "stage2_rocket = rocket_from_body(stage2_body_from_mission, 0.35, 0.35)\n", + "stage2_body = stage2.body\n", + "stage2_rocket = Rocket(\n", + " radius=stage2_body.geometry[\"radius\"],\n", + " mass=stage2_body.mass(0.0),\n", + " inertia=stage2_body.inertia_tensor(0.0),\n", + " power_off_drag=0.35,\n", + " power_on_drag=0.35,\n", + " center_of_mass_without_motor=stage2_body.center_of_mass(0.0),\n", + ")\n", "stage2_rocket.add_motor(stage2_motor, position=0.0)\n", "\n", "stage2_initial_state = stage1_flight.get_solution_at_time(\n", @@ -357,8 +355,15 @@ " terminate_on_apogee=False,\n", ")\n", "\n", - "payload_body_from_mission = payload.body\n", - "payload_rocket = rocket_from_body(payload_body_from_mission, 0.3, 0.3)\n", + "payload_body = payload.body\n", + "payload_rocket = Rocket(\n", + " radius=payload_body.geometry[\"radius\"],\n", + " mass=payload_body.mass(0.0),\n", + " inertia=payload_body.inertia_tensor(0.0),\n", + " power_off_drag=0.3,\n", + " power_on_drag=0.3,\n", + " center_of_mass_without_motor=payload_body.center_of_mass(0.0),\n", + ")\n", "\n", "payload_initial_state = stage2_flight.get_solution_at_time(\n", " PAYLOAD_RELEASE_TIME\n", @@ -393,4 +398,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From 0cd327a308e39beb880908e7a8f7be376856fa7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 17:58:08 +0000 Subject: [PATCH 12/34] MNT: align BodyLike protocol usage Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7d040ddd-7cc4-4d93-b829-882f8dee9c27 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/body/body_like.py | 31 +++++++++---------- rocketpy/body/flight_body.py | 8 ++--- rocketpy/body/rocket_adapter.py | 5 +--- tests/unit/body/test_body.py | 53 ++++++++++++++++++++++++++------- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/rocketpy/body/body_like.py b/rocketpy/body/body_like.py index 4a2c83468..b8dbb5431 100644 --- a/rocketpy/body/body_like.py +++ b/rocketpy/body/body_like.py @@ -1,15 +1,16 @@ """BodyLike interface for the multistage mission architecture.""" -from abc import ABC, abstractmethod +from typing import Protocol, runtime_checkable -class BodyLike(ABC): - """Abstract interface that every flight-ready body must satisfy. +@runtime_checkable +class BodyLike(Protocol): + """Structural interface that every flight-ready body must satisfy. Any object that can be integrated by the Flight simulation engine must implement this interface. Both the native :class:`FlightBody` and the :class:`RocketAdapter` (which wraps a legacy :class:`Rocket`) - implement it, so that the simulation layer can treat them uniformly. + satisfy it, so that the simulation layer can treat them uniformly. Attributes ---------- @@ -18,7 +19,6 @@ class BodyLike(ABC): """ @property - @abstractmethod def name(self) -> str: """Human-readable name of the body. @@ -27,8 +27,8 @@ def name(self) -> str: str Body name. """ + ... - @abstractmethod def mass(self, t: float) -> float: """Total mass of the body at time *t*, in kg. @@ -42,8 +42,8 @@ def mass(self, t: float) -> float: float Total mass in kg. """ + ... - @abstractmethod def inertia_tensor(self, t: float): """Inertia tensor of the body at time *t*, in kg·m². @@ -57,8 +57,8 @@ def inertia_tensor(self, t: float): array-like 3×3 inertia tensor in kg·m². """ + ... - @abstractmethod def center_of_mass(self, t: float) -> float: """Position of the center of mass along the body axis at time *t*. @@ -75,8 +75,8 @@ def center_of_mass(self, t: float) -> float: float Center-of-mass position in meters. """ + ... - @abstractmethod def aerodynamic_model(self): """Return the aerodynamic model attached to this body. @@ -85,8 +85,8 @@ def aerodynamic_model(self): object Aerodynamic model instance. """ + ... - @abstractmethod def propulsion_model(self): """Return the propulsion model attached to this body. @@ -95,8 +95,8 @@ def propulsion_model(self): object Propulsion model instance. """ + ... - @abstractmethod def recovery_systems(self): """Return the list of recovery systems attached to this body. @@ -105,8 +105,8 @@ def recovery_systems(self): list Recovery systems. """ + ... - @abstractmethod def sensors(self): """Return the list of sensors attached to this body. @@ -115,8 +115,8 @@ def sensors(self): list Sensor instances. """ + ... - @abstractmethod def controllers(self): """Return the list of active controllers attached to this body. @@ -125,8 +125,8 @@ def controllers(self): list Controller instances. """ + ... - @abstractmethod def coordinate_system_orientation(self) -> str: """Orientation convention for the body's coordinate system. @@ -135,8 +135,8 @@ def coordinate_system_orientation(self) -> str: str One of ``"tail_to_nose"`` or ``"nose_to_tail"``. """ + ... - @abstractmethod def to_branch_ready_copy(self): """Return a simulation-ready deep copy of this body. @@ -148,3 +148,4 @@ def to_branch_ready_copy(self): BodyLike A deep copy suitable for a :class:`FlightBranch`. """ + ... diff --git a/rocketpy/body/flight_body.py b/rocketpy/body/flight_body.py index 30d49cd68..8bf5ed6cc 100644 --- a/rocketpy/body/flight_body.py +++ b/rocketpy/body/flight_body.py @@ -2,10 +2,7 @@ from copy import deepcopy -from rocketpy.body.body_like import BodyLike - - -class FlightBody(BodyLike): +class FlightBody: """A fully configurable flight body composed of interchangeable models. Unlike :class:`~rocketpy.rocket.Rocket`, which is a rich user-facing @@ -13,7 +10,8 @@ class FlightBody(BodyLike): that holds just enough information for the simulation engine to integrate the equations of motion. Users who need the full Rocket builder experience can convert a :class:`~rocketpy.rocket.Rocket` to a - :class:`FlightBody` via :class:`RocketAdapter`. + :class:`FlightBody` via :class:`RocketAdapter`. It satisfies the + :class:`~rocketpy.body.BodyLike` protocol. Parameters ---------- diff --git a/rocketpy/body/rocket_adapter.py b/rocketpy/body/rocket_adapter.py index ff0501dbd..bd7ed9a48 100644 --- a/rocketpy/body/rocket_adapter.py +++ b/rocketpy/body/rocket_adapter.py @@ -2,10 +2,7 @@ from copy import deepcopy -from rocketpy.body.body_like import BodyLike - - -class RocketAdapter(BodyLike): +class RocketAdapter: """Adapts a legacy :class:`~rocketpy.rocket.Rocket` to the :class:`BodyLike` interface. diff --git a/tests/unit/body/test_body.py b/tests/unit/body/test_body.py index ba67c3c46..6149c76bd 100644 --- a/tests/unit/body/test_body.py +++ b/tests/unit/body/test_body.py @@ -39,12 +39,12 @@ def _make_flight_body(**kwargs): # --------------------------------------------------------------------------- -# BodyLike ABC +# BodyLike Protocol # --------------------------------------------------------------------------- -class TestBodyLikeIsAbstract: - """BodyLike cannot be instantiated directly.""" +class TestBodyLikeProtocol: + """BodyLike is a structural protocol.""" def test_cannot_instantiate_body_like(self): """BodyLike must raise TypeError when instantiated directly.""" @@ -52,16 +52,47 @@ def test_cannot_instantiate_body_like(self): with pytest.raises(TypeError): BodyLike() # type: ignore[abstract] - def test_concrete_subclass_without_all_methods_raises(self): - """A class that only partially implements BodyLike must raise TypeError.""" + def test_structural_conformance_without_inheritance(self): + """Objects can satisfy BodyLike without inheriting from it.""" + # Arrange + class StructuralBody: + name = "structural" - class Partial(BodyLike): - @property - def name(self): - return "partial" + def mass(self, t): + return 1.0 - with pytest.raises(TypeError): - Partial() + def inertia_tensor(self, t): + return (1.0, 1.0, 1.0, 0.0, 0.0, 0.0) + + def center_of_mass(self, t): + return 0.0 + + def aerodynamic_model(self): + return None + + def propulsion_model(self): + return None + + def recovery_systems(self): + return [] + + def sensors(self): + return [] + + def controllers(self): + return [] + + def coordinate_system_orientation(self): + return "tail_to_nose" + + def to_branch_ready_copy(self): + return self + + # Act + instance = StructuralBody() + + # Assert + assert isinstance(instance, BodyLike) # --------------------------------------------------------------------------- From 5c7b5470e4ca142d1c6bf7136de3641efb5fe0de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 17:59:22 +0000 Subject: [PATCH 13/34] MNT: tidy protocol spacing and tests Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7d040ddd-7cc4-4d93-b829-882f8dee9c27 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/body/flight_body.py | 1 + rocketpy/body/rocket_adapter.py | 1 + tests/unit/body/test_body.py | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rocketpy/body/flight_body.py b/rocketpy/body/flight_body.py index 8bf5ed6cc..5e63d7afa 100644 --- a/rocketpy/body/flight_body.py +++ b/rocketpy/body/flight_body.py @@ -2,6 +2,7 @@ from copy import deepcopy + class FlightBody: """A fully configurable flight body composed of interchangeable models. diff --git a/rocketpy/body/rocket_adapter.py b/rocketpy/body/rocket_adapter.py index bd7ed9a48..4a377a9c5 100644 --- a/rocketpy/body/rocket_adapter.py +++ b/rocketpy/body/rocket_adapter.py @@ -2,6 +2,7 @@ from copy import deepcopy + class RocketAdapter: """Adapts a legacy :class:`~rocketpy.rocket.Rocket` to the :class:`BodyLike` interface. diff --git a/tests/unit/body/test_body.py b/tests/unit/body/test_body.py index 6149c76bd..c5664aaf3 100644 --- a/tests/unit/body/test_body.py +++ b/tests/unit/body/test_body.py @@ -46,8 +46,8 @@ def _make_flight_body(**kwargs): class TestBodyLikeProtocol: """BodyLike is a structural protocol.""" - def test_cannot_instantiate_body_like(self): - """BodyLike must raise TypeError when instantiated directly.""" + def test_protocol_not_directly_instantiable(self): + """BodyLike Protocol cannot be instantiated directly.""" # Arrange / Act / Assert with pytest.raises(TypeError): BodyLike() # type: ignore[abstract] From 5abd07fbdc88531c691152aa85394601f3c7c2ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 18:00:24 +0000 Subject: [PATCH 14/34] DOC: clarify BodyLike protocol usage Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7d040ddd-7cc4-4d93-b829-882f8dee9c27 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/body/body_like.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rocketpy/body/body_like.py b/rocketpy/body/body_like.py index b8dbb5431..3cd014e33 100644 --- a/rocketpy/body/body_like.py +++ b/rocketpy/body/body_like.py @@ -11,6 +11,8 @@ class BodyLike(Protocol): must implement this interface. Both the native :class:`FlightBody` and the :class:`RocketAdapter` (which wraps a legacy :class:`Rocket`) satisfy it, so that the simulation layer can treat them uniformly. + This uses structural typing, so concrete bodies do not need to inherit + from :class:`BodyLike` to be accepted by the simulation layer. Attributes ---------- From d27604c3665fed12fa18dd573645666ebf35270c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 18:01:46 +0000 Subject: [PATCH 15/34] DOC: expand BodyLike protocol notes Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7d040ddd-7cc4-4d93-b829-882f8dee9c27 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/body/body_like.py | 6 +++++- rocketpy/body/rocket_adapter.py | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rocketpy/body/body_like.py b/rocketpy/body/body_like.py index 3cd014e33..795cd5e84 100644 --- a/rocketpy/body/body_like.py +++ b/rocketpy/body/body_like.py @@ -1,4 +1,8 @@ -"""BodyLike interface for the multistage mission architecture.""" +"""BodyLike protocol for the multistage mission architecture. + +This module defines a structural typing contract so bodies can satisfy the +interface without inheriting from a shared base class. +""" from typing import Protocol, runtime_checkable diff --git a/rocketpy/body/rocket_adapter.py b/rocketpy/body/rocket_adapter.py index 4a377a9c5..46aec78de 100644 --- a/rocketpy/body/rocket_adapter.py +++ b/rocketpy/body/rocket_adapter.py @@ -131,9 +131,10 @@ def recovery_systems(self): def sensors(self): """Return the sensors attached to the rocket. - Accesses ``rocket.sensors``, which is a public + Accesses ``rocket.sensors`` when available, which is a public :class:`~rocketpy.rocket.Components` collection on the - :class:`~rocketpy.rocket.Rocket` class. + :class:`~rocketpy.rocket.Rocket` class in newer Rocket + configurations. Returns ------- From d5ef51617eb08439ea37b96db479bc7c37ef60ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 18:02:54 +0000 Subject: [PATCH 16/34] TST: focus BodyLike protocol tests Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/7d040ddd-7cc4-4d93-b829-882f8dee9c27 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- tests/unit/body/test_body.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/unit/body/test_body.py b/tests/unit/body/test_body.py index c5664aaf3..8e544271b 100644 --- a/tests/unit/body/test_body.py +++ b/tests/unit/body/test_body.py @@ -46,12 +46,6 @@ def _make_flight_body(**kwargs): class TestBodyLikeProtocol: """BodyLike is a structural protocol.""" - def test_protocol_not_directly_instantiable(self): - """BodyLike Protocol cannot be instantiated directly.""" - # Arrange / Act / Assert - with pytest.raises(TypeError): - BodyLike() # type: ignore[abstract] - def test_structural_conformance_without_inheritance(self): """Objects can satisfy BodyLike without inheriting from it.""" # Arrange From 2a23609f0d6dac12cf7593a77b60e9484683f7d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 05:49:21 +0000 Subject: [PATCH 17/34] ENH: add MissionExecutor and mission flight input metadata Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/056f0113-d627-4259-bf22-83bd9ea0bd3a Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 128 +++++++++++++++------------ rocketpy/__init__.py | 2 + rocketpy/mission/__init__.py | 3 + rocketpy/mission/deployable.py | 8 +- rocketpy/mission/mission.py | 91 ++++++++++++++++++- rocketpy/mission/mission_executor.py | 84 ++++++++++++++++++ rocketpy/mission/stage.py | 8 +- rocketpy/rocket/rocket.py | 8 +- tests/unit/body/test_body.py | 1 + tests/unit/mission/test_mission.py | 108 +++++++++++++++++++++- 10 files changed, 370 insertions(+), 71 deletions(-) create mode 100644 rocketpy/mission/mission_executor.py diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 3744b703d..f6756c8c4 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "7fb27b941602401d91542211134fc71a", "metadata": {}, "source": [ "# Multistage mission setup example\n", @@ -16,7 +17,10 @@ }, { "cell_type": "code", + "execution_count": 1, + "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": {}, + "outputs": [], "source": [ "from rocketpy import (\n", " Attachment,\n", @@ -31,13 +35,12 @@ " Rocket,\n", " Stage,\n", " StageSeparationEvent,\n", - ")\n" - ], - "execution_count": 1, - "outputs": [] + ")" + ] }, { "cell_type": "markdown", + "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": {}, "source": [ "## 1. Define simple time-invariant models" @@ -45,7 +48,10 @@ }, { "cell_type": "code", + "execution_count": 2, + "id": "8dd0d8092fe74a7c96281538738b07e2", "metadata": {}, + "outputs": [], "source": [ "def constant_mass(value):\n", " def model(t):\n", @@ -66,12 +72,11 @@ " return float(value)\n", "\n", " return model" - ], - "execution_count": 2, - "outputs": [] + ] }, { "cell_type": "markdown", + "id": "72eea5119410473aa328ad9291626812", "metadata": {}, "source": [ "## 2. Create the root body (Stage 1)" @@ -79,7 +84,16 @@ }, { "cell_type": "code", + "execution_count": 3, + "id": "8edb47106e1a46a883d545849b8ab81b", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Stage 1: Stage 1\n mass @ t=0 s: 18.0 kg\n center of mass @ t=0 s: 1.1 m\n" + } + ], "source": [ "stage1_body = FlightBody(\n", " name=\"Stage 1\",\n", @@ -92,18 +106,11 @@ "print(\"Stage 1:\", stage1_body.name)\n", "print(\" mass @ t=0 s:\", stage1_body.mass(0.0), \"kg\")\n", "print(\" center of mass @ t=0 s:\", stage1_body.center_of_mass(0.0), \"m\")" - ], - "execution_count": 3, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Stage 1: Stage 1\n mass @ t=0 s: 18.0 kg\n center of mass @ t=0 s: 1.1 m\n" - } ] }, { "cell_type": "markdown", + "id": "10185d26023b46108eb7d9f57d49d2b3", "metadata": {}, "source": [ "## 3. Create the upper-stage body (Stage 2)" @@ -111,7 +118,16 @@ }, { "cell_type": "code", + "execution_count": 4, + "id": "8763a12b2bbd4a93a75aff182afb95dc", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Stage 2: Stage 2\n mass @ t=0 s: 7.5 kg\n" + } + ], "source": [ "stage2_body = FlightBody(\n", " name=\"Stage 2\",\n", @@ -123,18 +139,11 @@ "\n", "print(\"Stage 2:\", stage2_body.name)\n", "print(\" mass @ t=0 s:\", stage2_body.mass(0.0), \"kg\")" - ], - "execution_count": 4, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Stage 2: Stage 2\n mass @ t=0 s: 7.5 kg\n" - } ] }, { "cell_type": "markdown", + "id": "7623eae2785240b9bd12b16a66d81610", "metadata": {}, "source": [ "## 4. Define attachment and events for Stage 2" @@ -142,7 +151,10 @@ }, { "cell_type": "code", + "execution_count": null, + "id": "7cdc8c89c7104fffa095e18ddfef8986", "metadata": {}, + "outputs": [], "source": [ "stage2_attachment = Attachment(\n", " parent_frame_position=[0.0, 0.0, 2.2],\n", @@ -167,13 +179,12 @@ "\n", "context = {\"time\": 3.0}\n", "print(\"Ignition fires @ t=3 s?\", ignition_event.should_fire({}, context))\n", - "print(\"Separation fires @ t=3 s?\", separation_event.should_fire({}, context))\n" - ], - "execution_count": null, - "outputs": [] + "print(\"Separation fires @ t=3 s?\", separation_event.should_fire({}, context))" + ] }, { "cell_type": "markdown", + "id": "b118ea5561624da68c537baed56e602f", "metadata": {}, "source": [ "## 5. Wrap Stage 2 as a mission `Stage`" @@ -181,7 +192,16 @@ }, { "cell_type": "code", + "execution_count": 6, + "id": "938c804e27f84196a10c8828c723f798", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Stage object: Stage(name='Stage 2')\n state: StageState.ATTACHED\n events: ['stage2_separate', 'stage2_ignite']\n" + } + ], "source": [ "stage2 = Stage(\n", " name=\"Stage 2\",\n", @@ -194,18 +214,11 @@ "print(\"Stage object:\", stage2)\n", "print(\" state:\", stage2.state)\n", "print(\" events:\", [event.name for event in stage2.events])" - ], - "execution_count": 6, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Stage object: Stage(name='Stage 2')\n state: StageState.ATTACHED\n events: ['stage2_separate', 'stage2_ignite']\n" - } ] }, { "cell_type": "markdown", + "id": "504fb2a444614c0babb325280ed9130a", "metadata": {}, "source": [ "## 6. Create a deployable payload" @@ -213,7 +226,10 @@ }, { "cell_type": "code", + "execution_count": null, + "id": "59bbdb311c014d738909a11f9e486628", "metadata": {}, + "outputs": [], "source": [ "payload_body = FlightBody(\n", " name=\"Payload\",\n", @@ -246,13 +262,12 @@ ")\n", "\n", "context = {\"time\": 9.0}\n", - "print(\"Payload event fires @ t=9 s?\", payload_event.should_fire({}, context))\n" - ], - "execution_count": null, - "outputs": [] + "print(\"Payload event fires @ t=9 s?\", payload_event.should_fire({}, context))" + ] }, { "cell_type": "markdown", + "id": "b43b363d81ae4b689946ece5c682cd59", "metadata": {}, "source": [ "## 7. Build the Mission" @@ -260,7 +275,16 @@ }, { "cell_type": "code", + "execution_count": 8, + "id": "8a65eabff63a45729fe45fb5ade58bdc", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "Mission(name='Two-Stage Demo', stages=1, deployables=1)\nAttached items: ['Stage 2', 'Payload']\n" + } + ], "source": [ "mission = Mission(name=\"Two-Stage Demo\")\n", "mission.add_stage(stage2)\n", @@ -268,18 +292,11 @@ "\n", "print(mission)\n", "print(\"Attached items:\", [item.name for item in mission.attached_items()])" - ], - "execution_count": 8, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Mission(name='Two-Stage Demo', stages=1, deployables=1)\nAttached items: ['Stage 2', 'Payload']\n" - } ] }, { "cell_type": "markdown", + "id": "c3933fab20d04ec698c2621248eb3be0", "metadata": {}, "source": [ "## 8. Run simplified flight simulations\n", @@ -292,8 +309,10 @@ }, { "cell_type": "code", - "metadata": {}, "execution_count": null, + "id": "4dd4641cc4064e0191573fe9c69df29b", + "metadata": {}, + "outputs": [], "source": [ "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", "\n", @@ -342,9 +361,7 @@ ")\n", "stage2_rocket.add_motor(stage2_motor, position=0.0)\n", "\n", - "stage2_initial_state = stage1_flight.get_solution_at_time(\n", - " STAGE2_SEPARATION_TIME\n", - ")\n", + "stage2_initial_state = stage1_flight.get_solution_at_time(STAGE2_SEPARATION_TIME)\n", "\n", "stage2_flight = Flight(\n", " rocket=stage2_rocket,\n", @@ -365,9 +382,7 @@ " center_of_mass_without_motor=payload_body.center_of_mass(0.0),\n", ")\n", "\n", - "payload_initial_state = stage2_flight.get_solution_at_time(\n", - " PAYLOAD_RELEASE_TIME\n", - ")\n", + "payload_initial_state = stage2_flight.get_solution_at_time(PAYLOAD_RELEASE_TIME)\n", "\n", "payload_flight = Flight(\n", " rocket=payload_rocket,\n", @@ -380,9 +395,8 @@ "\n", "print(f\"Stage 1 apogee: {stage1_flight.apogee:.1f} m\")\n", "print(f\"Stage 2 apogee: {stage2_flight.apogee:.1f} m\")\n", - "print(f\"Payload apogee: {payload_flight.apogee:.1f} m\")\n" - ], - "outputs": [] + "print(f\"Payload apogee: {payload_flight.apogee:.1f} m\")" + ] } ], "metadata": { diff --git a/rocketpy/__init__.py b/rocketpy/__init__.py index e23be4aad..d353c30f1 100644 --- a/rocketpy/__init__.py +++ b/rocketpy/__init__.py @@ -15,6 +15,8 @@ IgnitionEvent, InstantaneousSeparation, Mission, + MissionExecutionResult, + MissionExecutor, NoOpParentUpdate, ParentUpdate, RecoveryEvent, diff --git a/rocketpy/mission/__init__.py b/rocketpy/mission/__init__.py index 069bd2131..e5a02f646 100644 --- a/rocketpy/mission/__init__.py +++ b/rocketpy/mission/__init__.py @@ -11,6 +11,7 @@ StageSeparationEvent, ) from rocketpy.mission.mission import Mission +from rocketpy.mission.mission_executor import MissionExecutionResult, MissionExecutor from rocketpy.mission.parent_update import NoOpParentUpdate, ParentUpdate from rocketpy.mission.separation_model import InstantaneousSeparation, SeparationModel from rocketpy.mission.stage import Stage @@ -25,6 +26,8 @@ "IgnitionEvent", "InstantaneousSeparation", "Mission", + "MissionExecutionResult", + "MissionExecutor", "NoOpParentUpdate", "ParentUpdate", "RecoveryEvent", diff --git a/rocketpy/mission/deployable.py b/rocketpy/mission/deployable.py index e4767269d..a35922909 100644 --- a/rocketpy/mission/deployable.py +++ b/rocketpy/mission/deployable.py @@ -57,8 +57,12 @@ def __init__( super().__init__(name, body, attachment, events=all_events) self.deployment_event = deployment_event - self.separation = separation if separation is not None else InstantaneousSeparation() - self.parent_update = parent_update if parent_update is not None else NoOpParentUpdate() + self.separation = ( + separation if separation is not None else InstantaneousSeparation() + ) + self.parent_update = ( + parent_update if parent_update is not None else NoOpParentUpdate() + ) def validate(self): """Validate that this deployable is correctly configured. diff --git a/rocketpy/mission/mission.py b/rocketpy/mission/mission.py index 8790714af..c0ef7ad9e 100644 --- a/rocketpy/mission/mission.py +++ b/rocketpy/mission/mission.py @@ -1,5 +1,7 @@ """Mission – top-level container for a multistage rocket mission.""" +from typing import Any + class Mission: """Container that stores all mission items for a multistage flight. @@ -39,6 +41,7 @@ def __init__(self, name: str = "Mission"): self.name = name self.stages: list = [] self.deployables: list = [] + self._flight_inputs: dict[str, dict[str, Any]] = {} # ------------------------------------------------------------------ # Mutating helpers @@ -63,11 +66,10 @@ def add_stage(self, stage): from rocketpy.mission.stage import Stage # local import to avoid circularity if not isinstance(stage, Stage): - raise TypeError( - f"Expected a Stage instance, got {type(stage).__name__!r}." - ) + raise TypeError(f"Expected a Stage instance, got {type(stage).__name__!r}.") stage.validate() self.stages.append(stage) + self._flight_inputs.setdefault(self._item_key(stage), {}) def add_deployable(self, deployable): """Append a deployable to this mission. @@ -94,6 +96,7 @@ def add_deployable(self, deployable): ) deployable.validate() self.deployables.append(deployable) + self._flight_inputs.setdefault(self._item_key(deployable), {}) # ------------------------------------------------------------------ # Query helpers @@ -111,6 +114,88 @@ def attached_items(self): """ return list(self.stages) + list(self.deployables) + @property + def number_of_stages(self) -> int: + """Number of stages registered in this mission.""" + return len(self.stages) + + def connection_map(self) -> dict[str, dict[str, Any]]: + """Return mission attachment metadata keyed by item name. + + Returns + ------- + dict[str, dict[str, Any]] + For each stage/deployable item, stores its parent/child attachment + frame positions, orientation and constraints. + """ + connections = {} + for item in self.attached_items(): + attachment = item.attachment + connections[item.name] = { + "parent_frame_position": attachment.parent_frame_position, + "child_frame_position": attachment.child_frame_position, + "orientation": attachment.orientation, + "constraints": attachment.constraints, + } + return connections + + def set_flight_inputs(self, item, **flight_inputs): + """Set per-item inputs used by :class:`MissionExecutor`. + + Parameters + ---------- + item : :class:`~rocketpy.mission.AttachedItem` or str + Attached item instance, or its name. + **flight_inputs + Keyword arguments forwarded to ``rocketpy.simulation.Flight`` + when this mission item is executed. + + Raises + ------ + KeyError + If *item* does not belong to this mission. + """ + key = self._resolve_item_key(item) + current = dict(self._flight_inputs.get(key, {})) + current.update(flight_inputs) + self._flight_inputs[key] = current + + def get_flight_inputs(self, item) -> dict[str, Any]: + """Return configured per-item flight inputs. + + Parameters + ---------- + item : :class:`~rocketpy.mission.AttachedItem` or str + Attached item instance, or its name. + + Returns + ------- + dict[str, Any] + Copy of configured inputs. Empty dict when not configured. + + Raises + ------ + KeyError + If *item* does not belong to this mission. + """ + key = self._resolve_item_key(item) + return dict(self._flight_inputs.get(key, {})) + + def _item_key(self, item) -> str: + return f"{type(item).__name__}:{item.name}" + + def _resolve_item_key(self, item) -> str: + if isinstance(item, str): + for attached_item in self.attached_items(): + if attached_item.name == item: + return self._item_key(attached_item) + raise KeyError(f"No attached mission item named {item!r}.") + + for attached_item in self.attached_items(): + if attached_item is item: + return self._item_key(attached_item) + raise KeyError("Attached mission item does not belong to this mission.") + # ------------------------------------------------------------------ # Dunder # ------------------------------------------------------------------ diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py new file mode 100644 index 000000000..f47ae82cc --- /dev/null +++ b/rocketpy/mission/mission_executor.py @@ -0,0 +1,84 @@ +"""MissionExecutor – high-level API to execute all mission flight branches.""" + +from dataclasses import dataclass +from typing import Any + +from rocketpy.body import RocketAdapter +from rocketpy.simulation import Flight + + +@dataclass(frozen=True) +class MissionExecutionResult: + """Single execution output for one mission item.""" + + item_name: str + flight: Flight + + +class MissionExecutor: + """Executes all mission stages/deployables from a :class:`Mission`. + + The executor provides a mission-first API: users configure mission items + and optional per-item flight inputs in :class:`~rocketpy.mission.Mission`, + then call :meth:`execute` once to run all corresponding flight simulations. + """ + + def __init__( + self, + mission, + environment, + rail_length: float, + default_flight_inputs=None, + flight_class=Flight, + ): + self.mission = mission + self.environment = environment + self.rail_length = rail_length + self.default_flight_inputs = dict(default_flight_inputs or {}) + self.flight_class = flight_class + + def execute(self) -> list[MissionExecutionResult]: + """Execute configured mission stage/deployable flights in order. + + Returns + ------- + list[MissionExecutionResult] + Flight results in mission order: stages first, then deployables. + """ + results: list[MissionExecutionResult] = [] + previous_flight = None + + for item in self.mission.attached_items(): + rocket = self._extract_rocket(item.body) + item_inputs = self.mission.get_flight_inputs(item) + flight_inputs = dict(self.default_flight_inputs) + flight_inputs.update(item_inputs) + + if "initial_solution" not in flight_inputs and previous_flight is not None: + flight_inputs["initial_solution"] = previous_flight + + flight = self.flight_class( + rocket=rocket, + environment=self.environment, + rail_length=self.rail_length, + **flight_inputs, + ) + results.append(MissionExecutionResult(item_name=item.name, flight=flight)) + previous_flight = flight + + return results + + @staticmethod + def _extract_rocket(body: Any): + """Extract a Flight-compatible rocket object from a mission body.""" + if isinstance(body, RocketAdapter): + return body.rocket + if all( + hasattr(body, attr) + for attr in ("add_motor", "total_mass", "center_of_mass") + ): + return body + raise TypeError( + "MissionExecutor currently supports Stage/Deployable bodies that wrap " + "a Rocket via RocketAdapter." + ) diff --git a/rocketpy/mission/stage.py b/rocketpy/mission/stage.py index 82f2eb66a..cf8e96809 100644 --- a/rocketpy/mission/stage.py +++ b/rocketpy/mission/stage.py @@ -71,8 +71,12 @@ def __init__( self.separation_event = separation_event self.ignition_event = ignition_event - self.separation = separation if separation is not None else InstantaneousSeparation() - self.parent_update = parent_update if parent_update is not None else NoOpParentUpdate() + self.separation = ( + separation if separation is not None else InstantaneousSeparation() + ) + self.parent_update = ( + parent_update if parent_update is not None else NoOpParentUpdate() + ) self.state = StageState.ATTACHED def validate(self): diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 123b01de5..da5f91127 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -2063,9 +2063,7 @@ def add_stage(self, stage): from rocketpy.mission.stage import Stage # local import avoids circularity if not isinstance(stage, Stage): - raise TypeError( - f"Expected a Stage instance, got {type(stage).__name__!r}." - ) + raise TypeError(f"Expected a Stage instance, got {type(stage).__name__!r}.") stage.validate() self._stages.append(stage) @@ -2125,9 +2123,7 @@ def select_stage(self, stage): return s raise KeyError(f"No stage named {stage!r} found.") else: - raise TypeError( - f"stage must be int or str, got {type(stage).__name__!r}." - ) + raise TypeError(f"stage must be int or str, got {type(stage).__name__!r}.") def attached_items(self): """Return all attached mission items (stages then deployables). diff --git a/tests/unit/body/test_body.py b/tests/unit/body/test_body.py index 8e544271b..8e59f1c73 100644 --- a/tests/unit/body/test_body.py +++ b/tests/unit/body/test_body.py @@ -48,6 +48,7 @@ class TestBodyLikeProtocol: def test_structural_conformance_without_inheritance(self): """Objects can satisfy BodyLike without inheriting from it.""" + # Arrange class StructuralBody: name = "structural" diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index 650035bda..5addded13 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -6,7 +6,7 @@ import pytest -from rocketpy.body import FlightBody +from rocketpy.body import FlightBody, RocketAdapter from rocketpy.mission import ( Attachment, Deployable, @@ -15,6 +15,7 @@ IgnitionEvent, InstantaneousSeparation, Mission, + MissionExecutor, NoOpParentUpdate, ParentUpdate, RecoveryEvent, @@ -82,6 +83,18 @@ def _make_stage(**kwargs): return Stage(**defaults) +class _FakeRocket: + """Minimal fake rocket object for MissionExecutor tests.""" + + def __init__(self, name="fake_rocket"): + self.name = name + + +def _make_rocket_adapter(name="rocket_body"): + """Create a RocketAdapter with a minimal fake rocket object.""" + return RocketAdapter(_FakeRocket(name=name)) + + # --------------------------------------------------------------------------- # StageState # --------------------------------------------------------------------------- @@ -448,3 +461,96 @@ def test_len_counts_all_items(self): m.add_stage(_make_stage()) m.add_deployable(_make_deployable()) assert len(m) == 2 + + +class TestMissionMissionMetadata: + """Mission exposes mission-wide stage and connection metadata.""" + + def test_number_of_stages_matches_added_stages(self): + """number_of_stages returns the stage count.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1")) + mission.add_stage(_make_stage(name="stage_2")) + assert mission.number_of_stages == 2 + + def test_connection_map_contains_attachment_data(self): + """connection_map returns attachment metadata for mission items.""" + mission = Mission() + stage = _make_stage(name="stage_1") + mission.add_stage(stage) + connection = mission.connection_map()["stage_1"] + assert connection["parent_frame_position"] == [0.0, 0.0, 1.0] + assert connection["child_frame_position"] == [0.0, 0.0, 0.0] + assert connection["constraints"] == "rigid" + + def test_set_and_get_flight_inputs_by_name(self): + """set_flight_inputs/get_flight_inputs store and return per-item inputs.""" + mission = Mission() + stage = _make_stage(name="stage_1") + mission.add_stage(stage) + mission.set_flight_inputs("stage_1", inclination=85, heading=10) + configured = mission.get_flight_inputs(stage) + assert configured["inclination"] == 85 + assert configured["heading"] == 10 + + def test_set_flight_inputs_raises_for_unknown_item(self): + """set_flight_inputs raises KeyError when item is unknown.""" + mission = Mission() + with pytest.raises(KeyError): + mission.set_flight_inputs("missing_stage", inclination=85) + + +class TestMissionExecutor: + """MissionExecutor runs mission items without requiring manual Flight setup.""" + + class _FakeFlight: + """Simple stand-in for rocketpy.simulation.Flight.""" + + def __init__(self, rocket, environment, rail_length, **kwargs): + self.rocket = rocket + self.environment = environment + self.rail_length = rail_length + self.kwargs = kwargs + + def test_execute_runs_stage_and_deployable(self): + """execute runs all mission attached items in mission order.""" + mission = Mission() + stage = _make_stage(name="stage_1", body=_make_rocket_adapter("stage_rocket")) + deployable = _make_deployable( + name="payload", body=_make_rocket_adapter("payload_rocket") + ) + mission.add_stage(stage) + mission.add_deployable(deployable) + mission.set_flight_inputs("stage_1", heading=15) + mission.set_flight_inputs("payload", heading=45) + + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + default_flight_inputs={"inclination": 80}, + flight_class=self._FakeFlight, + ) + + results = executor.execute() + + assert [result.item_name for result in results] == ["stage_1", "payload"] + assert results[0].flight.kwargs["inclination"] == 80 + assert results[0].flight.kwargs["heading"] == 15 + assert results[1].flight.kwargs["heading"] == 45 + assert results[1].flight.kwargs["initial_solution"] is results[0].flight + + def test_execute_raises_for_non_rocket_body(self): + """execute raises TypeError when body is not RocketAdapter-backed.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) + + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self._FakeFlight, + ) + + with pytest.raises(TypeError, match="RocketAdapter"): + executor.execute() From 0c9dbe5128e96841e62b6c195ddfec50dab4ed82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 05:54:13 +0000 Subject: [PATCH 18/34] TST: address review feedback for mission executor types Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/056f0113-d627-4259-bf22-83bd9ea0bd3a Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/mission/mission_executor.py | 21 ++++++++++++++++----- tests/unit/mission/test_mission.py | 10 +++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index f47ae82cc..2954974f6 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -1,12 +1,26 @@ """MissionExecutor – high-level API to execute all mission flight branches.""" from dataclasses import dataclass -from typing import Any +from typing import Any, Protocol, runtime_checkable from rocketpy.body import RocketAdapter from rocketpy.simulation import Flight +@runtime_checkable +class FlightCompatibleRocket(Protocol): + """Structural contract for rocket objects accepted by Flight.""" + + def add_motor(self, motor, position): + """Add a motor to the rocket body.""" + + def total_mass(self, t: float) -> float: + """Return total mass at time *t*.""" + + def center_of_mass(self, t: float) -> float: + """Return center-of-mass position at time *t*.""" + + @dataclass(frozen=True) class MissionExecutionResult: """Single execution output for one mission item.""" @@ -73,10 +87,7 @@ def _extract_rocket(body: Any): """Extract a Flight-compatible rocket object from a mission body.""" if isinstance(body, RocketAdapter): return body.rocket - if all( - hasattr(body, attr) - for attr in ("add_motor", "total_mass", "center_of_mass") - ): + if isinstance(body, FlightCompatibleRocket): return body raise TypeError( "MissionExecutor currently supports Stage/Deployable bodies that wrap " diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index 5addded13..f4f989d47 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -83,7 +83,7 @@ def _make_stage(**kwargs): return Stage(**defaults) -class _FakeRocket: +class FakeRocket: """Minimal fake rocket object for MissionExecutor tests.""" def __init__(self, name="fake_rocket"): @@ -92,7 +92,7 @@ def __init__(self, name="fake_rocket"): def _make_rocket_adapter(name="rocket_body"): """Create a RocketAdapter with a minimal fake rocket object.""" - return RocketAdapter(_FakeRocket(name=name)) + return RocketAdapter(FakeRocket(name=name)) # --------------------------------------------------------------------------- @@ -503,7 +503,7 @@ def test_set_flight_inputs_raises_for_unknown_item(self): class TestMissionExecutor: """MissionExecutor runs mission items without requiring manual Flight setup.""" - class _FakeFlight: + class FakeFlight: """Simple stand-in for rocketpy.simulation.Flight.""" def __init__(self, rocket, environment, rail_length, **kwargs): @@ -529,7 +529,7 @@ def test_execute_runs_stage_and_deployable(self): environment=object(), rail_length=5.0, default_flight_inputs={"inclination": 80}, - flight_class=self._FakeFlight, + flight_class=self.FakeFlight, ) results = executor.execute() @@ -549,7 +549,7 @@ def test_execute_raises_for_non_rocket_body(self): mission=mission, environment=object(), rail_length=5.0, - flight_class=self._FakeFlight, + flight_class=self.FakeFlight, ) with pytest.raises(TypeError, match="RocketAdapter"): From 5bee1c960cf7a66dd50af52ca198681df7399050 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 05:55:14 +0000 Subject: [PATCH 19/34] TST: strengthen mission fake rocket interface Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/056f0113-d627-4259-bf22-83bd9ea0bd3a Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- tests/unit/mission/test_mission.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index f4f989d47..aa15db101 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -89,6 +89,17 @@ class FakeRocket: def __init__(self, name="fake_rocket"): self.name = name + def add_motor(self, motor, position): + """No-op helper to satisfy rocket-like interface in tests.""" + + def total_mass(self, t): + """Return a deterministic mass for test doubles.""" + return 1.0 + + def center_of_mass(self, t): + """Return a deterministic center-of-mass value for test doubles.""" + return 0.0 + def _make_rocket_adapter(name="rocket_body"): """Create a RocketAdapter with a minimal fake rocket object.""" From 5c423499baf52e3eb36410cac71e13a220e2224e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 05:56:23 +0000 Subject: [PATCH 20/34] DOC: clarify mission keying and executor support contract Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/056f0113-d627-4259-bf22-83bd9ea0bd3a Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/mission/mission.py | 2 ++ rocketpy/mission/mission_executor.py | 4 ++-- tests/unit/mission/test_mission.py | 13 +++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/rocketpy/mission/mission.py b/rocketpy/mission/mission.py index c0ef7ad9e..f24af7210 100644 --- a/rocketpy/mission/mission.py +++ b/rocketpy/mission/mission.py @@ -182,6 +182,8 @@ def get_flight_inputs(self, item) -> dict[str, Any]: return dict(self._flight_inputs.get(key, {})) def _item_key(self, item) -> str: + # Internal opaque identifier used only as dict key to avoid collisions + # between Stage and Deployable items sharing the same name. return f"{type(item).__name__}:{item.name}" def _resolve_item_key(self, item) -> str: diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index 2954974f6..a26590627 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -90,6 +90,6 @@ def _extract_rocket(body: Any): if isinstance(body, FlightCompatibleRocket): return body raise TypeError( - "MissionExecutor currently supports Stage/Deployable bodies that wrap " - "a Rocket via RocketAdapter." + "MissionExecutor currently supports Stage/Deployable bodies backed " + "by RocketAdapter or by objects satisfying FlightCompatibleRocket." ) diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index aa15db101..394cc9a33 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -565,3 +565,16 @@ def test_execute_raises_for_non_rocket_body(self): with pytest.raises(TypeError, match="RocketAdapter"): executor.execute() + + def test_execute_accepts_protocol_compatible_rocket_body(self): + """execute accepts bodies satisfying FlightCompatibleRocket protocol.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1", body=FakeRocket("raw_stage"))) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + results = executor.execute() + assert results[0].flight.rocket.name == "raw_stage" From 716e8b893f90fc55c999321bf1ec1abc5e9880c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 06:05:51 +0000 Subject: [PATCH 21/34] DOC: rewrite multistage example notebook for MissionExecutor API Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/83409187-5380-4ad6-a4b2-e70214d03cf2 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 537 ++++++++++++++++++---------------- 1 file changed, 285 insertions(+), 252 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index f6756c8c4..75ae66e6b 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -5,397 +5,430 @@ "id": "7fb27b941602401d91542211134fc71a", "metadata": {}, "source": [ - "# Multistage mission setup example\n", + "# Full-Scale Multistage Mission Example (MissionExecutor API)\n", "\n", - "This notebook walks through a minimal, step-by-step setup of the multistage\n", - "mission objects (`BodyLike`, `Stage`, `Deployable`, `Mission`). It also\n", - "runs simplified flight simulations for Stage 1, Stage 2, and the payload\n", - "using the mission Stage/Deployable bodies as the source of each flight\n", - "model so the mission architecture remains the single source of truth\n", - "while full coupling continues to evolve.\n" + "This notebook demonstrates a full mission setup using the new mission architecture:\n", + "\n", + "- **Mission** stores complete mission topology and per-item simulation inputs.\n", + "- **MissionExecutor** runs all configured stages/deployables in sequence.\n", + "- Stage/deployable bodies use full RocketPy `Rocket` models (motor, aerodynamics, parachutes).\n" ] }, { - "cell_type": "code", - "execution_count": 1, + "cell_type": "markdown", "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": {}, + "source": [ + "## 1) Imports\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a63283cbaf04dbcab1f6479b197f3a8", + "metadata": {}, "outputs": [], "source": [ + "import datetime as dt\n", + "\n", "from rocketpy import (\n", " Attachment,\n", " Deployable,\n", " DeploymentEvent,\n", " Environment,\n", - " Flight,\n", - " FlightBody,\n", " IgnitionEvent,\n", " Mission,\n", - " PointMassMotor,\n", + " MissionExecutor,\n", " Rocket,\n", + " RocketAdapter,\n", + " SolidMotor,\n", " Stage,\n", " StageSeparationEvent,\n", - ")" + ")\n", + "from rocketpy.plots.compare import CompareFlights" ] }, { "cell_type": "markdown", - "id": "9a63283cbaf04dbcab1f6479b197f3a8", + "id": "8dd0d8092fe74a7c96281538738b07e2", "metadata": {}, "source": [ - "## 1. Define simple time-invariant models" + "## 2) Environment setup\n", + "\n", + "This uses a deterministic custom atmosphere (no external download) while keeping a\n", + "full 6-DOF flight setup.\n" ] }, { "cell_type": "code", - "execution_count": 2, - "id": "8dd0d8092fe74a7c96281538738b07e2", + "execution_count": null, + "id": "72eea5119410473aa328ad9291626812", "metadata": {}, "outputs": [], "source": [ - "def constant_mass(value):\n", - " def model(t):\n", - " return float(value)\n", - "\n", - " return model\n", - "\n", - "\n", - "def constant_inertia_tensor(i11, i22, i33):\n", - " def model(t):\n", - " return (i11, i22, i33, 0.0, 0.0, 0.0)\n", - "\n", - " return model\n", - "\n", - "\n", - "def constant_center_of_mass(value):\n", - " def model(t):\n", - " return float(value)\n", + "env = Environment(\n", + " latitude=32.990254,\n", + " longitude=-106.974998,\n", + " elevation=1400,\n", + " datum=\"WGS84\",\n", + ")\n", "\n", - " return model" + "tomorrow = dt.date.today() + dt.timedelta(days=1)\n", + "env.set_date((tomorrow.year, tomorrow.month, tomorrow.day, 12), timezone=\"UTC\")\n", + "env.set_atmospheric_model(\n", + " type=\"custom_atmosphere\",\n", + " pressure=101325,\n", + " temperature=298.15,\n", + " wind_u=[(0, 3.0), (2000, 5.0), (5000, 8.0)],\n", + " wind_v=[(0, -1.0), (2000, -2.0), (5000, -3.5)],\n", + ")\n", + "env.max_expected_height = 10000" ] }, { "cell_type": "markdown", - "id": "72eea5119410473aa328ad9291626812", + "id": "8edb47106e1a46a883d545849b8ab81b", "metadata": {}, "source": [ - "## 2. Create the root body (Stage 1)" + "## 3) Helper builders for motors and rockets\n" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "8edb47106e1a46a883d545849b8ab81b", + "execution_count": null, + "id": "10185d26023b46108eb7d9f57d49d2b3", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Stage 1: Stage 1\n mass @ t=0 s: 18.0 kg\n center of mass @ t=0 s: 1.1 m\n" - } - ], + "outputs": [], "source": [ - "stage1_body = FlightBody(\n", - " name=\"Stage 1\",\n", - " geometry={\"radius\": 0.08, \"length\": 2.4},\n", - " mass_model=constant_mass(18.0),\n", - " inertia_model=constant_inertia_tensor(2.8, 2.8, 0.15),\n", - " center_of_mass_model=constant_center_of_mass(1.1),\n", - ")\n", - "\n", - "print(\"Stage 1:\", stage1_body.name)\n", - "print(\" mass @ t=0 s:\", stage1_body.mass(0.0), \"kg\")\n", - "print(\" center of mass @ t=0 s:\", stage1_body.center_of_mass(0.0), \"m\")" + "def build_pro75_m1670():\n", + " return SolidMotor(\n", + " thrust_source=\"data/motors/cesaroni/Cesaroni_M1670.eng\",\n", + " burn_time=3.9,\n", + " dry_mass=1.815,\n", + " dry_inertia=(0.125, 0.125, 0.002),\n", + " center_of_dry_mass_position=0.317,\n", + " nozzle_position=0,\n", + " grain_number=5,\n", + " grain_density=1815,\n", + " nozzle_radius=33 / 1000,\n", + " throat_radius=11 / 1000,\n", + " grain_separation=5 / 1000,\n", + " grain_outer_radius=33 / 1000,\n", + " grain_initial_height=120 / 1000,\n", + " grains_center_of_mass_position=0.397,\n", + " grain_initial_inner_radius=15 / 1000,\n", + " interpolation_method=\"linear\",\n", + " coordinate_system_orientation=\"nozzle_to_combustion_chamber\",\n", + " )\n", + "\n", + "\n", + "def build_stage_rocket(name, dry_mass, motor_position, fin_position, nose_position):\n", + " rocket = Rocket(\n", + " radius=0.0635,\n", + " mass=dry_mass,\n", + " inertia=(6.321, 6.321, 0.034),\n", + " power_off_drag=\"data/rockets/calisto/powerOffDragCurve.csv\",\n", + " power_on_drag=\"data/rockets/calisto/powerOnDragCurve.csv\",\n", + " center_of_mass_without_motor=0,\n", + " coordinate_system_orientation=\"tail_to_nose\",\n", + " )\n", + " rocket.name = name\n", + " rocket.add_motor(build_pro75_m1670(), position=motor_position)\n", + " rocket.set_rail_buttons(\n", + " upper_button_position=0.0818,\n", + " lower_button_position=-0.618,\n", + " angular_position=45,\n", + " )\n", + " rocket.add_nose(length=0.55829, kind=\"von karman\", position=nose_position)\n", + " rocket.add_trapezoidal_fins(\n", + " n=4,\n", + " root_chord=0.120,\n", + " tip_chord=0.060,\n", + " span=0.110,\n", + " position=fin_position,\n", + " cant_angle=0.5,\n", + " )\n", + " rocket.add_tail(\n", + " top_radius=0.0635,\n", + " bottom_radius=0.0435,\n", + " length=0.060,\n", + " position=-1.194656,\n", + " )\n", + " return rocket\n", + "\n", + "\n", + "def build_payload_rocket(name):\n", + " payload = Rocket(\n", + " radius=0.055,\n", + " mass=4.5,\n", + " inertia=(0.25, 0.25, 0.03),\n", + " power_off_drag=0.55,\n", + " power_on_drag=0.55,\n", + " center_of_mass_without_motor=0,\n", + " coordinate_system_orientation=\"tail_to_nose\",\n", + " )\n", + " payload.name = name\n", + " payload.add_parachute(\n", + " \"Payload Drogue\",\n", + " cd_s=0.35,\n", + " trigger=\"apogee\",\n", + " sampling_rate=105,\n", + " lag=1.0,\n", + " noise=(0, 4.0, 0.2),\n", + " )\n", + " payload.add_parachute(\n", + " \"Payload Main\",\n", + " cd_s=3.5,\n", + " trigger=700,\n", + " sampling_rate=105,\n", + " lag=1.0,\n", + " noise=(0, 4.0, 0.2),\n", + " )\n", + " return payload" ] }, { "cell_type": "markdown", - "id": "10185d26023b46108eb7d9f57d49d2b3", + "id": "8763a12b2bbd4a93a75aff182afb95dc", "metadata": {}, "source": [ - "## 3. Create the upper-stage body (Stage 2)" + "## 4) Build stage and deployable bodies\n" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "8763a12b2bbd4a93a75aff182afb95dc", + "execution_count": null, + "id": "7623eae2785240b9bd12b16a66d81610", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Stage 2: Stage 2\n mass @ t=0 s: 7.5 kg\n" - } - ], + "outputs": [], "source": [ - "stage2_body = FlightBody(\n", - " name=\"Stage 2\",\n", - " geometry={\"radius\": 0.06, \"length\": 1.4},\n", - " mass_model=constant_mass(7.5),\n", - " inertia_model=constant_inertia_tensor(1.1, 1.1, 0.06),\n", - " center_of_mass_model=constant_center_of_mass(0.7),\n", + "first_stage_rocket = build_stage_rocket(\n", + " name=\"First Stage\",\n", + " dry_mass=14.426,\n", + " motor_position=-1.255,\n", + " fin_position=-1.04956,\n", + " nose_position=1.278,\n", + ")\n", + "\n", + "second_stage_rocket = build_stage_rocket(\n", + " name=\"Second Stage\",\n", + " dry_mass=9.500,\n", + " motor_position=-1.000,\n", + " fin_position=-0.920,\n", + " nose_position=1.050,\n", + ")\n", + "second_stage_rocket.add_parachute(\n", + " \"Second Stage Main\",\n", + " cd_s=5.0,\n", + " trigger=900,\n", + " sampling_rate=105,\n", + " lag=1.2,\n", + " noise=(0, 6.0, 0.25),\n", ")\n", "\n", - "print(\"Stage 2:\", stage2_body.name)\n", - "print(\" mass @ t=0 s:\", stage2_body.mass(0.0), \"kg\")" + "payload_rocket = build_payload_rocket(name=\"Payload\")" ] }, { "cell_type": "markdown", - "id": "7623eae2785240b9bd12b16a66d81610", + "id": "7cdc8c89c7104fffa095e18ddfef8986", "metadata": {}, "source": [ - "## 4. Define attachment and events for Stage 2" + "## 5) Build mission topology\n", + "\n", + "The mission contains two stages and one deployable. All bodies are wrapped in\n", + "`RocketAdapter`, so they can be managed by mission abstractions.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "7cdc8c89c7104fffa095e18ddfef8986", + "id": "b118ea5561624da68c537baed56e602f", "metadata": {}, "outputs": [], "source": [ - "stage2_attachment = Attachment(\n", - " parent_frame_position=[0.0, 0.0, 2.2],\n", + "mission = Mission(name=\"Calisto Two-Stage + Deployable Mission\")\n", + "\n", + "first_stage_attachment = Attachment(\n", + " parent_frame_position=[0.0, 0.0, 0.0],\n", " child_frame_position=[0.0, 0.0, 0.0],\n", " constraints=\"rigid\",\n", ")\n", "\n", - "STAGE2_IGNITION_TIME = 2.0\n", - "STAGE2_SEPARATION_TIME = 5.0\n", - "\n", + "second_stage_attachment = Attachment(\n", + " parent_frame_position=[0.0, 0.0, 0.85],\n", + " child_frame_position=[0.0, 0.0, -0.45],\n", + " constraints=\"rigid\",\n", + ")\n", "\n", - "def ignition_trigger(state, context):\n", - " return context[\"time\"] >= STAGE2_IGNITION_TIME\n", + "payload_attachment = Attachment(\n", + " parent_frame_position=[0.0, 0.0, 1.10],\n", + " child_frame_position=[0.0, 0.0, -0.30],\n", + " constraints=\"rigid\",\n", + ")\n", "\n", + "second_stage_sep = StageSeparationEvent(\n", + " name=\"Second Stage Separation\",\n", + " trigger=lambda state, context: False,\n", + ")\n", + "second_stage_ignition = IgnitionEvent(\n", + " name=\"Second Stage Ignition\",\n", + " trigger=lambda state, context: False,\n", + ")\n", + "payload_deploy_event = DeploymentEvent(\n", + " name=\"Payload Deployment\",\n", + " trigger=lambda state, context: False,\n", + ")\n", "\n", - "def separation_trigger(state, context):\n", - " return context[\"time\"] >= STAGE2_SEPARATION_TIME\n", + "first_stage = Stage(\n", + " name=\"first_stage\",\n", + " body=RocketAdapter(first_stage_rocket),\n", + " attachment=first_stage_attachment,\n", + ")\n", "\n", + "second_stage = Stage(\n", + " name=\"second_stage\",\n", + " body=RocketAdapter(second_stage_rocket),\n", + " attachment=second_stage_attachment,\n", + " separation_event=second_stage_sep,\n", + " ignition_event=second_stage_ignition,\n", + ")\n", "\n", - "ignition_event = IgnitionEvent(\"stage2_ignite\", ignition_trigger)\n", - "separation_event = StageSeparationEvent(\"stage2_separate\", separation_trigger)\n", + "payload = Deployable(\n", + " name=\"payload\",\n", + " body=RocketAdapter(payload_rocket),\n", + " attachment=payload_attachment,\n", + " deployment_event=payload_deploy_event,\n", + ")\n", "\n", - "context = {\"time\": 3.0}\n", - "print(\"Ignition fires @ t=3 s?\", ignition_event.should_fire({}, context))\n", - "print(\"Separation fires @ t=3 s?\", separation_event.should_fire({}, context))" + "mission.add_stage(first_stage)\n", + "mission.add_stage(second_stage)\n", + "mission.add_deployable(payload)" ] }, { "cell_type": "markdown", - "id": "b118ea5561624da68c537baed56e602f", + "id": "938c804e27f84196a10c8828c723f798", "metadata": {}, "source": [ - "## 5. Wrap Stage 2 as a mission `Stage`" + "## 6) Configure mission flight inputs\n", + "\n", + "These values are stored in `Mission` and consumed by `MissionExecutor`.\n" ] }, { "cell_type": "code", - "execution_count": 6, - "id": "938c804e27f84196a10c8828c723f798", + "execution_count": null, + "id": "504fb2a444614c0babb325280ed9130a", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Stage object: Stage(name='Stage 2')\n state: StageState.ATTACHED\n events: ['stage2_separate', 'stage2_ignite']\n" - } - ], + "outputs": [], "source": [ - "stage2 = Stage(\n", - " name=\"Stage 2\",\n", - " body=stage2_body,\n", - " attachment=stage2_attachment,\n", - " separation_event=separation_event,\n", - " ignition_event=ignition_event,\n", + "mission.set_flight_inputs(\n", + " \"first_stage\",\n", + " inclination=85,\n", + " heading=20,\n", + " terminate_on_apogee=True,\n", + " max_time=450,\n", ")\n", "\n", - "print(\"Stage object:\", stage2)\n", - "print(\" state:\", stage2.state)\n", - "print(\" events:\", [event.name for event in stage2.events])" + "mission.set_flight_inputs(\n", + " \"second_stage\",\n", + " inclination=0,\n", + " heading=0,\n", + " terminate_on_apogee=False,\n", + " max_time=500,\n", + ")\n", + "\n", + "mission.set_flight_inputs(\n", + " \"payload\",\n", + " inclination=0,\n", + " heading=0,\n", + " terminate_on_apogee=False,\n", + " max_time=700,\n", + ")" ] }, { "cell_type": "markdown", - "id": "504fb2a444614c0babb325280ed9130a", + "id": "59bbdb311c014d738909a11f9e486628", "metadata": {}, "source": [ - "## 6. Create a deployable payload" + "## 7) Inspect mission-level metadata\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "59bbdb311c014d738909a11f9e486628", + "id": "b43b363d81ae4b689946ece5c682cd59", "metadata": {}, "outputs": [], "source": [ - "payload_body = FlightBody(\n", - " name=\"Payload\",\n", - " geometry={\"radius\": 0.05, \"length\": 0.4},\n", - " mass_model=constant_mass(1.2),\n", - " inertia_model=constant_inertia_tensor(0.2, 0.2, 0.01),\n", - " center_of_mass_model=constant_center_of_mass(0.2),\n", - ")\n", - "\n", - "payload_attachment = Attachment(\n", - " parent_frame_position=[0.0, 0.0, 2.6],\n", - " child_frame_position=[0.0, 0.0, 0.0],\n", - " constraints=\"rigid\",\n", - ")\n", - "\n", - "PAYLOAD_RELEASE_TIME = 8.0\n", - "\n", - "\n", - "def payload_trigger(state, context):\n", - " return context[\"time\"] >= PAYLOAD_RELEASE_TIME\n", - "\n", - "\n", - "payload_event = DeploymentEvent(\"payload_release\", payload_trigger)\n", - "\n", - "payload = Deployable(\n", - " name=\"Payload\",\n", - " body=payload_body,\n", - " attachment=payload_attachment,\n", - " deployment_event=payload_event,\n", - ")\n", - "\n", - "context = {\"time\": 9.0}\n", - "print(\"Payload event fires @ t=9 s?\", payload_event.should_fire({}, context))" + "print(\"Mission Name:\", mission.name)\n", + "print(\"Number of stages:\", mission.number_of_stages)\n", + "print(\"Connection map:\")\n", + "for item_name, metadata in mission.connection_map().items():\n", + " print(f\" - {item_name}: {metadata}\")" ] }, { "cell_type": "markdown", - "id": "b43b363d81ae4b689946ece5c682cd59", + "id": "8a65eabff63a45729fe45fb5ade58bdc", "metadata": {}, "source": [ - "## 7. Build the Mission" + "## 8) Execute the full mission\n", + "\n", + "`MissionExecutor` runs each item in mission order. If `initial_solution` is not\n", + "explicitly provided for an item, it is chained from the previous executed flight.\n" ] }, { "cell_type": "code", - "execution_count": 8, - "id": "8a65eabff63a45729fe45fb5ade58bdc", + "execution_count": null, + "id": "c3933fab20d04ec698c2621248eb3be0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "Mission(name='Two-Stage Demo', stages=1, deployables=1)\nAttached items: ['Stage 2', 'Payload']\n" - } - ], + "outputs": [], "source": [ - "mission = Mission(name=\"Two-Stage Demo\")\n", - "mission.add_stage(stage2)\n", - "mission.add_deployable(payload)\n", + "executor = MissionExecutor(\n", + " mission=mission,\n", + " environment=env,\n", + " rail_length=5.2,\n", + " default_flight_inputs={\"time_overshoot\": True},\n", + ")\n", + "\n", + "results = executor.execute()\n", + "flights_by_name = {result.item_name: result.flight for result in results}\n", "\n", - "print(mission)\n", - "print(\"Attached items:\", [item.name for item in mission.attached_items()])" + "for result in results:\n", + " print(\n", + " f\"{result.item_name}: apogee = {result.flight.apogee:.2f} m, \"\n", + " f\"impact velocity = {result.flight.impact_velocity:.2f} m/s\"\n", + " )" ] }, { "cell_type": "markdown", - "id": "c3933fab20d04ec698c2621248eb3be0", + "id": "4dd4641cc4064e0191573fe9c69df29b", "metadata": {}, "source": [ - "## 8. Run simplified flight simulations\n", - "\n", - "Run a simplified flight for Stage 1, then use the Stage 2 and payload\n", - "bodies from the mission to configure the upper-stage and deployable\n", - "trajectories. The Stage 1 trajectory provides the initial conditions at\n", - "each separation event.\n" + "## 9) Compare mission trajectories\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "4dd4641cc4064e0191573fe9c69df29b", + "id": "8309879909854d7188b41380fd92a7c3", "metadata": {}, "outputs": [], "source": [ - "environment = Environment(latitude=0.0, longitude=0.0, elevation=0.0)\n", - "\n", - "stage1_motor = PointMassMotor(\n", - " thrust_source=480.0,\n", - " dry_mass=1.2,\n", - " propellant_initial_mass=2.8,\n", - " burn_time=2.4,\n", - ")\n", - "\n", - "stage1_rocket = Rocket(\n", - " radius=stage1_body.geometry[\"radius\"],\n", - " mass=stage1_body.mass(0.0),\n", - " inertia=stage1_body.inertia_tensor(0.0),\n", - " power_off_drag=0.45,\n", - " power_on_drag=0.45,\n", - " center_of_mass_without_motor=stage1_body.center_of_mass(0.0),\n", - ")\n", - "stage1_rocket.add_motor(stage1_motor, position=0.0)\n", - "\n", - "stage1_flight = Flight(\n", - " rocket=stage1_rocket,\n", - " environment=environment,\n", - " rail_length=5.0,\n", - " inclination=85.0,\n", - " heading=0.0,\n", - " max_time=20.0,\n", - " terminate_on_apogee=False,\n", - ")\n", - "\n", - "stage2_motor = PointMassMotor(\n", - " thrust_source=260.0,\n", - " dry_mass=0.6,\n", - " propellant_initial_mass=1.2,\n", - " burn_time=1.6,\n", - ")\n", - "\n", - "stage2_body = stage2.body\n", - "stage2_rocket = Rocket(\n", - " radius=stage2_body.geometry[\"radius\"],\n", - " mass=stage2_body.mass(0.0),\n", - " inertia=stage2_body.inertia_tensor(0.0),\n", - " power_off_drag=0.35,\n", - " power_on_drag=0.35,\n", - " center_of_mass_without_motor=stage2_body.center_of_mass(0.0),\n", - ")\n", - "stage2_rocket.add_motor(stage2_motor, position=0.0)\n", - "\n", - "stage2_initial_state = stage1_flight.get_solution_at_time(STAGE2_SEPARATION_TIME)\n", - "\n", - "stage2_flight = Flight(\n", - " rocket=stage2_rocket,\n", - " environment=environment,\n", - " rail_length=1.0,\n", - " initial_solution=stage2_initial_state,\n", - " max_time=20.0,\n", - " terminate_on_apogee=False,\n", - ")\n", - "\n", - "payload_body = payload.body\n", - "payload_rocket = Rocket(\n", - " radius=payload_body.geometry[\"radius\"],\n", - " mass=payload_body.mass(0.0),\n", - " inertia=payload_body.inertia_tensor(0.0),\n", - " power_off_drag=0.3,\n", - " power_on_drag=0.3,\n", - " center_of_mass_without_motor=payload_body.center_of_mass(0.0),\n", - ")\n", - "\n", - "payload_initial_state = stage2_flight.get_solution_at_time(PAYLOAD_RELEASE_TIME)\n", - "\n", - "payload_flight = Flight(\n", - " rocket=payload_rocket,\n", - " environment=environment,\n", - " rail_length=1.0,\n", - " initial_solution=payload_initial_state,\n", - " max_time=20.0,\n", - " terminate_on_apogee=False,\n", + "comparison = CompareFlights(\n", + " [\n", + " flights_by_name[\"first_stage\"],\n", + " flights_by_name[\"second_stage\"],\n", + " flights_by_name[\"payload\"],\n", + " ]\n", ")\n", "\n", - "print(f\"Stage 1 apogee: {stage1_flight.apogee:.1f} m\")\n", - "print(f\"Stage 2 apogee: {stage2_flight.apogee:.1f} m\")\n", - "print(f\"Payload apogee: {payload_flight.apogee:.1f} m\")" + "comparison.trajectories_3d(legend=True)" ] } ], From 76e25c41642c118fdb2bfa1df4acd4dd79fffe0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 06:40:51 +0000 Subject: [PATCH 22/34] docs: update multistage notebook payload setup Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/fbe5846f-e62e-4267-8d52-191ce170c08b Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 243 ++++++++++++++++++++-------------- 1 file changed, 143 insertions(+), 100 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 75ae66e6b..59080334d 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -30,22 +30,26 @@ "outputs": [], "source": [ "import datetime as dt\n", + "from pathlib import Path\n", "\n", + "import rocketpy as rp\n", "from rocketpy import (\n", " Attachment,\n", " Deployable,\n", " DeploymentEvent,\n", " Environment,\n", + " FlightBody,\n", " IgnitionEvent,\n", " Mission,\n", " MissionExecutor,\n", + " Parachute,\n", " Rocket,\n", " RocketAdapter,\n", " SolidMotor,\n", " Stage,\n", " StageSeparationEvent,\n", ")\n", - "from rocketpy.plots.compare import CompareFlights" + "from rocketpy.plots.compare import CompareFlights\n" ] }, { @@ -100,91 +104,143 @@ "metadata": {}, "outputs": [], "source": [ - "def build_pro75_m1670():\n", - " return SolidMotor(\n", - " thrust_source=\"data/motors/cesaroni/Cesaroni_M1670.eng\",\n", - " burn_time=3.9,\n", - " dry_mass=1.815,\n", - " dry_inertia=(0.125, 0.125, 0.002),\n", - " center_of_dry_mass_position=0.317,\n", - " nozzle_position=0,\n", - " grain_number=5,\n", - " grain_density=1815,\n", - " nozzle_radius=33 / 1000,\n", - " throat_radius=11 / 1000,\n", - " grain_separation=5 / 1000,\n", - " grain_outer_radius=33 / 1000,\n", - " grain_initial_height=120 / 1000,\n", - " grains_center_of_mass_position=0.397,\n", - " grain_initial_inner_radius=15 / 1000,\n", - " interpolation_method=\"linear\",\n", - " coordinate_system_orientation=\"nozzle_to_combustion_chamber\",\n", - " )\n", + "data_directory = Path(rp.__file__).resolve().parent.parent / \"data\"\n", + "motor_file = data_directory / \"motors\" / \"cesaroni\" / \"Cesaroni_M1670.eng\"\n", + "power_off_drag_file = (\n", + " data_directory / \"rockets\" / \"calisto\" / \"powerOffDragCurve.csv\"\n", + ")\n", + "power_on_drag_file = data_directory / \"rockets\" / \"calisto\" / \"powerOnDragCurve.csv\"\n", "\n", + "first_stage_motor = SolidMotor(\n", + " thrust_source=str(motor_file),\n", + " burn_time=3.9,\n", + " dry_mass=1.815,\n", + " dry_inertia=(0.125, 0.125, 0.002),\n", + " center_of_dry_mass_position=0.317,\n", + " nozzle_position=0,\n", + " grain_number=5,\n", + " grain_density=1815,\n", + " nozzle_radius=33 / 1000,\n", + " throat_radius=11 / 1000,\n", + " grain_separation=5 / 1000,\n", + " grain_outer_radius=33 / 1000,\n", + " grain_initial_height=120 / 1000,\n", + " grains_center_of_mass_position=0.397,\n", + " grain_initial_inner_radius=15 / 1000,\n", + " interpolation_method=\"linear\",\n", + " coordinate_system_orientation=\"nozzle_to_combustion_chamber\",\n", + ")\n", "\n", - "def build_stage_rocket(name, dry_mass, motor_position, fin_position, nose_position):\n", - " rocket = Rocket(\n", - " radius=0.0635,\n", - " mass=dry_mass,\n", - " inertia=(6.321, 6.321, 0.034),\n", - " power_off_drag=\"data/rockets/calisto/powerOffDragCurve.csv\",\n", - " power_on_drag=\"data/rockets/calisto/powerOnDragCurve.csv\",\n", - " center_of_mass_without_motor=0,\n", - " coordinate_system_orientation=\"tail_to_nose\",\n", - " )\n", - " rocket.name = name\n", - " rocket.add_motor(build_pro75_m1670(), position=motor_position)\n", - " rocket.set_rail_buttons(\n", - " upper_button_position=0.0818,\n", - " lower_button_position=-0.618,\n", - " angular_position=45,\n", - " )\n", - " rocket.add_nose(length=0.55829, kind=\"von karman\", position=nose_position)\n", - " rocket.add_trapezoidal_fins(\n", - " n=4,\n", - " root_chord=0.120,\n", - " tip_chord=0.060,\n", - " span=0.110,\n", - " position=fin_position,\n", - " cant_angle=0.5,\n", - " )\n", - " rocket.add_tail(\n", - " top_radius=0.0635,\n", - " bottom_radius=0.0435,\n", - " length=0.060,\n", - " position=-1.194656,\n", - " )\n", - " return rocket\n", + "second_stage_motor = SolidMotor(\n", + " thrust_source=str(motor_file),\n", + " burn_time=3.9,\n", + " dry_mass=1.815,\n", + " dry_inertia=(0.125, 0.125, 0.002),\n", + " center_of_dry_mass_position=0.317,\n", + " nozzle_position=0,\n", + " grain_number=5,\n", + " grain_density=1815,\n", + " nozzle_radius=33 / 1000,\n", + " throat_radius=11 / 1000,\n", + " grain_separation=5 / 1000,\n", + " grain_outer_radius=33 / 1000,\n", + " grain_initial_height=120 / 1000,\n", + " grains_center_of_mass_position=0.397,\n", + " grain_initial_inner_radius=15 / 1000,\n", + " interpolation_method=\"linear\",\n", + " coordinate_system_orientation=\"nozzle_to_combustion_chamber\",\n", + ")\n", "\n", + "first_stage_rocket = Rocket(\n", + " radius=0.0635,\n", + " mass=14.426,\n", + " inertia=(6.321, 6.321, 0.034),\n", + " power_off_drag=str(power_off_drag_file),\n", + " power_on_drag=str(power_on_drag_file),\n", + " center_of_mass_without_motor=0,\n", + " coordinate_system_orientation=\"tail_to_nose\",\n", + ")\n", + "first_stage_rocket.name = \"First Stage\"\n", + "first_stage_rocket.add_motor(first_stage_motor, position=-1.255)\n", + "first_stage_rocket.set_rail_buttons(\n", + " upper_button_position=0.0818,\n", + " lower_button_position=-0.618,\n", + " angular_position=45,\n", + ")\n", + "first_stage_rocket.add_nose(length=0.55829, kind=\"von karman\", position=1.278)\n", + "first_stage_rocket.add_trapezoidal_fins(\n", + " n=4,\n", + " root_chord=0.120,\n", + " tip_chord=0.060,\n", + " span=0.110,\n", + " position=-1.04956,\n", + " cant_angle=0.5,\n", + ")\n", + "first_stage_rocket.add_tail(\n", + " top_radius=0.0635,\n", + " bottom_radius=0.0435,\n", + " length=0.060,\n", + " position=-1.194656,\n", + ")\n", "\n", - "def build_payload_rocket(name):\n", - " payload = Rocket(\n", - " radius=0.055,\n", - " mass=4.5,\n", - " inertia=(0.25, 0.25, 0.03),\n", - " power_off_drag=0.55,\n", - " power_on_drag=0.55,\n", - " center_of_mass_without_motor=0,\n", - " coordinate_system_orientation=\"tail_to_nose\",\n", - " )\n", - " payload.name = name\n", - " payload.add_parachute(\n", - " \"Payload Drogue\",\n", - " cd_s=0.35,\n", - " trigger=\"apogee\",\n", - " sampling_rate=105,\n", - " lag=1.0,\n", - " noise=(0, 4.0, 0.2),\n", - " )\n", - " payload.add_parachute(\n", - " \"Payload Main\",\n", - " cd_s=3.5,\n", - " trigger=700,\n", - " sampling_rate=105,\n", - " lag=1.0,\n", - " noise=(0, 4.0, 0.2),\n", - " )\n", - " return payload" + "second_stage_rocket = Rocket(\n", + " radius=0.0635,\n", + " mass=9.500,\n", + " inertia=(6.321, 6.321, 0.034),\n", + " power_off_drag=str(power_off_drag_file),\n", + " power_on_drag=str(power_on_drag_file),\n", + " center_of_mass_without_motor=0,\n", + " coordinate_system_orientation=\"tail_to_nose\",\n", + ")\n", + "second_stage_rocket.name = \"Second Stage\"\n", + "second_stage_rocket.add_motor(second_stage_motor, position=-1.000)\n", + "second_stage_rocket.set_rail_buttons(\n", + " upper_button_position=0.0818,\n", + " lower_button_position=-0.618,\n", + " angular_position=45,\n", + ")\n", + "second_stage_rocket.add_nose(length=0.55829, kind=\"von karman\", position=1.050)\n", + "second_stage_rocket.add_trapezoidal_fins(\n", + " n=4,\n", + " root_chord=0.120,\n", + " tip_chord=0.060,\n", + " span=0.110,\n", + " position=-0.920,\n", + " cant_angle=0.5,\n", + ")\n", + "second_stage_rocket.add_tail(\n", + " top_radius=0.0635,\n", + " bottom_radius=0.0435,\n", + " length=0.060,\n", + " position=-1.194656,\n", + ")\n", + "\n", + "payload_body = FlightBody(\n", + " name=\"Payload\",\n", + " geometry=0.055,\n", + " mass_model=lambda t: 4.5,\n", + " inertia_model=lambda t: (0.25, 0.25, 0.03, 0.0, 0.0, 0.0),\n", + " center_of_mass_model=lambda t: 0.0,\n", + " recovery_systems=[\n", + " Parachute(\n", + " name=\"Payload Drogue\",\n", + " cd_s=0.35,\n", + " trigger=\"apogee\",\n", + " sampling_rate=105,\n", + " lag=1.0,\n", + " noise=(0, 4.0, 0.2),\n", + " ),\n", + " Parachute(\n", + " name=\"Payload Main\",\n", + " cd_s=3.5,\n", + " trigger=700,\n", + " sampling_rate=105,\n", + " lag=1.0,\n", + " noise=(0, 4.0, 0.2),\n", + " ),\n", + " ],\n", + " coordinate_system_orientation=\"tail_to_nose\",\n", + ")\n" ] }, { @@ -202,21 +258,6 @@ "metadata": {}, "outputs": [], "source": [ - "first_stage_rocket = build_stage_rocket(\n", - " name=\"First Stage\",\n", - " dry_mass=14.426,\n", - " motor_position=-1.255,\n", - " fin_position=-1.04956,\n", - " nose_position=1.278,\n", - ")\n", - "\n", - "second_stage_rocket = build_stage_rocket(\n", - " name=\"Second Stage\",\n", - " dry_mass=9.500,\n", - " motor_position=-1.000,\n", - " fin_position=-0.920,\n", - " nose_position=1.050,\n", - ")\n", "second_stage_rocket.add_parachute(\n", " \"Second Stage Main\",\n", " cd_s=5.0,\n", @@ -226,7 +267,9 @@ " noise=(0, 6.0, 0.25),\n", ")\n", "\n", - "payload_rocket = build_payload_rocket(name=\"Payload\")" + "print(f\"Using motor file: {motor_file}\")\n", + "print(f\"Using drag curve files: {power_off_drag_file.name}, {power_on_drag_file.name}\")\n", + "print(f\"Payload body type: {type(payload_body).__name__}\")\n" ] }, { @@ -296,14 +339,14 @@ "\n", "payload = Deployable(\n", " name=\"payload\",\n", - " body=RocketAdapter(payload_rocket),\n", + " body=payload_body,\n", " attachment=payload_attachment,\n", " deployment_event=payload_deploy_event,\n", ")\n", "\n", "mission.add_stage(first_stage)\n", "mission.add_stage(second_stage)\n", - "mission.add_deployable(payload)" + "mission.add_deployable(payload)\n" ] }, { From a7e853099227a69201ffff6f229a6e06f6f9794d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 06:43:17 +0000 Subject: [PATCH 23/34] docs: clarify FlightBody notebook usage Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/fbe5846f-e62e-4267-8d52-191ce170c08b Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 51 +++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 59080334d..105de1788 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -94,7 +94,12 @@ "id": "8edb47106e1a46a883d545849b8ab81b", "metadata": {}, "source": [ - "## 3) Helper builders for motors and rockets\n" + "## 3) Build the motors and bodies\n", + "\n", + "This example keeps every object definition inline so the notebook is easy to\n", + "follow and easy to copy into a local desktop workflow. The data files are\n", + "resolved from the installed RocketPy package location instead of assuming the\n", + "notebook is launched from the repository root.\n" ] }, { @@ -248,7 +253,10 @@ "id": "8763a12b2bbd4a93a75aff182afb95dc", "metadata": {}, "source": [ - "## 4) Build stage and deployable bodies\n" + "## 4) Configure the stage rockets and the payload `FlightBody`\n", + "\n", + "The two powered stages still use `Rocket`, while the deployable payload now\n", + "uses `FlightBody` directly with its own recovery systems.\n" ] }, { @@ -269,7 +277,9 @@ "\n", "print(f\"Using motor file: {motor_file}\")\n", "print(f\"Using drag curve files: {power_off_drag_file.name}, {power_on_drag_file.name}\")\n", - "print(f\"Payload body type: {type(payload_body).__name__}\")\n" + "print(f\"Payload body type: {type(payload_body).__name__}\")\n", + "print(f\"Payload dry mass at t=0 s: {payload_body.mass(0):.2f} kg\")\n", + "print(f\"Payload recovery systems: {len(payload_body.recovery_systems())}\")\n" ] }, { @@ -418,10 +428,12 @@ "id": "8a65eabff63a45729fe45fb5ade58bdc", "metadata": {}, "source": [ - "## 8) Execute the full mission\n", + "## 8) Execute the powered stage mission\n", "\n", - "`MissionExecutor` runs each item in mission order. If `initial_solution` is not\n", - "explicitly provided for an item, it is chained from the previous executed flight.\n" + "`FlightBody` is now part of the mission topology above, and the payload\n", + "configuration can be inspected directly from the notebook. The execution step\n", + "below focuses on the two powered stages, which are the bodies currently\n", + "accepted by `MissionExecutor`.\n" ] }, { @@ -431,8 +443,26 @@ "metadata": {}, "outputs": [], "source": [ + "execution_mission = Mission(name=\"Calisto Two-Stage Mission Execution\")\n", + "execution_mission.add_stage(first_stage)\n", + "execution_mission.add_stage(second_stage)\n", + "execution_mission.set_flight_inputs(\n", + " \"first_stage\",\n", + " inclination=85,\n", + " heading=20,\n", + " terminate_on_apogee=True,\n", + " max_time=450,\n", + ")\n", + "execution_mission.set_flight_inputs(\n", + " \"second_stage\",\n", + " inclination=0,\n", + " heading=0,\n", + " terminate_on_apogee=False,\n", + " max_time=500,\n", + ")\n", + "\n", "executor = MissionExecutor(\n", - " mission=mission,\n", + " mission=execution_mission,\n", " environment=env,\n", " rail_length=5.2,\n", " default_flight_inputs={\"time_overshoot\": True},\n", @@ -445,7 +475,7 @@ " print(\n", " f\"{result.item_name}: apogee = {result.flight.apogee:.2f} m, \"\n", " f\"impact velocity = {result.flight.impact_velocity:.2f} m/s\"\n", - " )" + " )\n" ] }, { @@ -453,7 +483,7 @@ "id": "4dd4641cc4064e0191573fe9c69df29b", "metadata": {}, "source": [ - "## 9) Compare mission trajectories\n" + "## 9) Compare the powered-stage trajectories\n" ] }, { @@ -467,11 +497,10 @@ " [\n", " flights_by_name[\"first_stage\"],\n", " flights_by_name[\"second_stage\"],\n", - " flights_by_name[\"payload\"],\n", " ]\n", ")\n", "\n", - "comparison.trajectories_3d(legend=True)" + "comparison.trajectories_3d(legend=True)\n" ] } ], From ce1006672d40208a16c181c2f2c2f478ac5611d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 06:43:51 +0000 Subject: [PATCH 24/34] docs: finalize multistage notebook portability updates Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/fbe5846f-e62e-4267-8d52-191ce170c08b Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 178 ++++++++++++++++++++++++++++------ 1 file changed, 146 insertions(+), 32 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 105de1788..a332aacd7 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -24,9 +24,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "9a63283cbaf04dbcab1f6479b197f3a8", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:29.816104Z", + "iopub.status.busy": "2026-05-10T06:43:29.815930Z", + "iopub.status.idle": "2026-05-10T06:43:30.928322Z", + "shell.execute_reply": "2026-05-10T06:43:30.927474Z" + } + }, "outputs": [], "source": [ "import datetime as dt\n", @@ -49,7 +56,7 @@ " Stage,\n", " StageSeparationEvent,\n", ")\n", - "from rocketpy.plots.compare import CompareFlights\n" + "from rocketpy.plots.compare import CompareFlights" ] }, { @@ -65,9 +72,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "72eea5119410473aa328ad9291626812", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:30.930518Z", + "iopub.status.busy": "2026-05-10T06:43:30.930233Z", + "iopub.status.idle": "2026-05-10T06:43:30.946274Z", + "shell.execute_reply": "2026-05-10T06:43:30.945576Z" + } + }, "outputs": [], "source": [ "env = Environment(\n", @@ -104,16 +118,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "10185d26023b46108eb7d9f57d49d2b3", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:30.948093Z", + "iopub.status.busy": "2026-05-10T06:43:30.947936Z", + "iopub.status.idle": "2026-05-10T06:43:31.030295Z", + "shell.execute_reply": "2026-05-10T06:43:31.029426Z" + } + }, "outputs": [], "source": [ "data_directory = Path(rp.__file__).resolve().parent.parent / \"data\"\n", "motor_file = data_directory / \"motors\" / \"cesaroni\" / \"Cesaroni_M1670.eng\"\n", - "power_off_drag_file = (\n", - " data_directory / \"rockets\" / \"calisto\" / \"powerOffDragCurve.csv\"\n", - ")\n", + "power_off_drag_file = data_directory / \"rockets\" / \"calisto\" / \"powerOffDragCurve.csv\"\n", "power_on_drag_file = data_directory / \"rockets\" / \"calisto\" / \"powerOnDragCurve.csv\"\n", "\n", "first_stage_motor = SolidMotor(\n", @@ -245,7 +264,7 @@ " ),\n", " ],\n", " coordinate_system_orientation=\"tail_to_nose\",\n", - ")\n" + ")" ] }, { @@ -261,10 +280,29 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "7623eae2785240b9bd12b16a66d81610", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:31.031845Z", + "iopub.status.busy": "2026-05-10T06:43:31.031648Z", + "iopub.status.idle": "2026-05-10T06:43:31.035930Z", + "shell.execute_reply": "2026-05-10T06:43:31.035225Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using motor file: /home/runner/work/RocketPy/RocketPy/data/motors/cesaroni/Cesaroni_M1670.eng\n", + "Using drag curve files: powerOffDragCurve.csv, powerOnDragCurve.csv\n", + "Payload body type: FlightBody\n", + "Payload dry mass at t=0 s: 4.50 kg\n", + "Payload recovery systems: 2\n" + ] + } + ], "source": [ "second_stage_rocket.add_parachute(\n", " \"Second Stage Main\",\n", @@ -279,7 +317,7 @@ "print(f\"Using drag curve files: {power_off_drag_file.name}, {power_on_drag_file.name}\")\n", "print(f\"Payload body type: {type(payload_body).__name__}\")\n", "print(f\"Payload dry mass at t=0 s: {payload_body.mass(0):.2f} kg\")\n", - "print(f\"Payload recovery systems: {len(payload_body.recovery_systems())}\")\n" + "print(f\"Payload recovery systems: {len(payload_body.recovery_systems())}\")" ] }, { @@ -295,9 +333,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "b118ea5561624da68c537baed56e602f", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:31.037638Z", + "iopub.status.busy": "2026-05-10T06:43:31.037467Z", + "iopub.status.idle": "2026-05-10T06:43:31.042439Z", + "shell.execute_reply": "2026-05-10T06:43:31.041701Z" + } + }, "outputs": [], "source": [ "mission = Mission(name=\"Calisto Two-Stage + Deployable Mission\")\n", @@ -356,7 +401,7 @@ "\n", "mission.add_stage(first_stage)\n", "mission.add_stage(second_stage)\n", - "mission.add_deployable(payload)\n" + "mission.add_deployable(payload)" ] }, { @@ -371,9 +416,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "504fb2a444614c0babb325280ed9130a", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:31.044111Z", + "iopub.status.busy": "2026-05-10T06:43:31.043963Z", + "iopub.status.idle": "2026-05-10T06:43:31.047158Z", + "shell.execute_reply": "2026-05-10T06:43:31.046326Z" + } + }, "outputs": [], "source": [ "mission.set_flight_inputs(\n", @@ -411,10 +463,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "b43b363d81ae4b689946ece5c682cd59", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:31.048608Z", + "iopub.status.busy": "2026-05-10T06:43:31.048444Z", + "iopub.status.idle": "2026-05-10T06:43:31.051939Z", + "shell.execute_reply": "2026-05-10T06:43:31.051254Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mission Name: Calisto Two-Stage + Deployable Mission\n", + "Number of stages: 2\n", + "Connection map:\n", + " - first_stage: {'parent_frame_position': [0.0, 0.0, 0.0], 'child_frame_position': [0.0, 0.0, 0.0], 'orientation': None, 'constraints': 'rigid'}\n", + " - second_stage: {'parent_frame_position': [0.0, 0.0, 0.85], 'child_frame_position': [0.0, 0.0, -0.45], 'orientation': None, 'constraints': 'rigid'}\n", + " - payload: {'parent_frame_position': [0.0, 0.0, 1.1], 'child_frame_position': [0.0, 0.0, -0.3], 'orientation': None, 'constraints': 'rigid'}\n" + ] + } + ], "source": [ "print(\"Mission Name:\", mission.name)\n", "print(\"Number of stages:\", mission.number_of_stages)\n", @@ -438,10 +510,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "c3933fab20d04ec698c2621248eb3be0", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:31.053466Z", + "iopub.status.busy": "2026-05-10T06:43:31.053320Z", + "iopub.status.idle": "2026-05-10T06:43:31.401859Z", + "shell.execute_reply": "2026-05-10T06:43:31.401089Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "first_stage: apogee = 4443.75 m, impact velocity = 0.00 m/s\n", + "second_stage: apogee = 4443.75 m, impact velocity = 0.00 m/s\n" + ] + } + ], "source": [ "execution_mission = Mission(name=\"Calisto Two-Stage Mission Execution\")\n", "execution_mission.add_stage(first_stage)\n", @@ -475,7 +563,7 @@ " print(\n", " f\"{result.item_name}: apogee = {result.flight.apogee:.2f} m, \"\n", " f\"impact velocity = {result.flight.impact_velocity:.2f} m/s\"\n", - " )\n" + " )" ] }, { @@ -488,10 +576,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "8309879909854d7188b41380fd92a7c3", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-10T06:43:31.403607Z", + "iopub.status.busy": "2026-05-10T06:43:31.403443Z", + "iopub.status.idle": "2026-05-10T06:43:31.564239Z", + "shell.execute_reply": "2026-05-10T06:43:31.563146Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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2dnZzGAwsPPPP58tX75c2vfLL79kF154IbNYLEyv17NJkyaxDRs2dPlM/znmcrnYM888w6qqqphGo2E5OTls5syZbOfOnUHtWbt2Lbv//vvZqFGjWEFBAVMqlSw/P59ddtllbPHixUHf4/+bNjQ0sLvuuov17duXqVSqgHNTnGedOXbsGPu///s/NmDAAKZWq5lWq2WlpaXswgsvZM8991xIO2fOnMmKioqYUqlkeXl57LLLLmOLFi0Kun80aw6ROGw2G9uxYwez2WypNiUiZs2axS6//PKgr40fP57ddddd0r8rKirYiy++yBhjzO12s0GDBrEpU6awzZs3s5UrV7Jzzz2XAWCffPIJY+zUebtx40bpM5qamqT1gTHGVq1axQCwJUuWsPr6etbe3h7Ulp7GlwQtwRXJELTHjh1j/fr1YwBYdnY2u+qqq9gVV1zBsrKy2KBBg9gVV1zRraC96667mMlkYhUVFezqq69mkyZNYlqtlgFgV1xxRcB77r33XjZ27FgGgPXr14/NmjVL+nv66acjGJmuxxVK0JaXl7Pp06czlUrFJk6cyK699lp2+umnS/vddNNNDAAbPHgwmzJlCrvmmmvY6NGjmUwmYwDY3Llzu3xnd+N89OhRdtppp0njOWnSJHbllVdKdvbp04cdOHCgy/vef/99plarJZtnzJjBrrzySjZ8+HAmCAJ79NFHGWOMffzxx9LYiwLE/6++vl76zIceeogBYIIgsLFjx7Jrr72WjRgxggFgcrmcvfXWW13sEMXZ7bffzmQyGauqqmI//elP2eTJk9kHH3zAGOte0H777bcsKytLOtbp06ezKVOmSNsmT57MnE6ntL/H42ETJ05kAJjJZGKXXHIJu/baa9mkSZOkMfO/OHTHp59+Kh1vdzcO3RHLmN13331MoVCwM844g11zzTXS+9RqNVu1ahV75ZVXmEwmY2PGjGFXX301GzhwIAPADAYD27t3b8Bn+s+xq666iimVSjZp0iT205/+lPXt21d63+rVq7vYM3HiRCaTydhpp53GLr30UjZz5kx25plnSnPmpZde6vIe8TedOnUqq6ysZFlZWWz69Ols5syZ7Oc//7m0XzBBW1tby4qLi6W5e/nll7NrrrmGnXfeeSw7O5uZzeYu3/eXv/xFOsfOOOMMdu2117IxY8ZIn//YY491eU80aw6ROHqb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VbBECu8cDgdOnz6NgwcP0qYCqbSEylYGS68pLCzEBRdcMCCX1u/30589duwYtFotFbdjPaVgGAZ79+7F+vXr0dTUhDNnzmDRokV48MEHsXz5cvHUI4mIzyuR0SDm0OYovb29KC8vx5NPPok1a9bAarXizTffxKpVqwAAhw8fxuTJkwck2be1taGwsBAA8Nprr+E//uM/4HA4BGukzydIfmVjYyPq6+uxbds2VFVVYfny5VixYgWmTp06aAR1pPzB0XYQIoUydrudeq7abDaYzWbR6mYQYivkvV5vVDHdaO8J0lSAmPYXFBREOSaIomggQ7WcHakAkgtpUkHuG2BkX2WGYfDll1/SSKzT6cSSJUuwatUqXH/99SgoKEj6exURn1cio0MUtDnCAw88gGXLlqGiogKtra144oknsHv3bhw8eBBWqxV33HEH3n33Xbz++uvQ6XS45557AAA7duwA8K0NSklJCZ5//nm0t7fje9/7Hv7lX/5FtEFJEV1dXdiwYQPWrVuH9957D6WlpaitrUVdXR20Wi3q6+vx3nvv4d///d9RXFyctM5KmbIyEgLcqDaJgCezQj4YDNIIotvthlqtpvOe6xG/wVrOJmJRx4VhGLqhczqdOHLkCN58801cd911WLFiBc6dO4eGhgZs2LAB3d3duP7667Fy5Upce+21opVdChCfVyLxIAraHOHGG2/Exx9/DLfbDavVinnz5uGZZ55BVVUVgG+Nqt98880oo2ru8UxLSwvuuOMOfPTRR8jLy8OaNWvw85//XIzepYHu7m785je/wZ///Gc0NzeDYRgUFhbi2muvxWOPPYbS0tKU/e1MmM3zCW5epsvlou1wiTNBqvKOI5EI7ZhFnCjIvOdKvvNgTURIJDZVuZSkqOuZZ57B3/72N3R0dEAmk2HmzJm4/fbbceutt4p5nClGfF6JxIMoaEVEeMyePXvw2muvoampCV1dXViyZAmuvfZaSKVSbN26FZs2bUJeXh6WL1+Ouro6zJ07N6W5r7F5ouloB5oJyJE2OY5WqVT0fWYiUsrNjSa2UyQynG0pIYNdYzabLeUNJoLBILZv347169dj06ZNkEgkqKurwxVXXIHe3l68++672Lp1K8rKylBbW4s77riDCiwREZHMIwpaEREes3nzZmzcuBG1tbW48sorB+R++f1+bNu2DfX19WhsbIRC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lpaWUrduXXFxcZFZs2bJzZs39eo5f/68BAYGiomJiVhZWcnMmTOloqJCr8y+ffukQ4cOYmxsLE5OTsp7PG21sc1ERA9DIyLybPqGiYiIiIgeH8fQEhEREZGqMaAlIiIiIlVjQEtEREREqsaAloiIiIhUjQEtEREREakaA1oiIiIiUjUGtERERESkagxoiYiIiEjVGNASERERkaoxoCUiIiIiVWNAS0RERESqxoCWiIiIiFSNAS0RERERqRoDWiIiIiJSNQa0RERERKRqDGiJiIiISNUY0BIRERGRqjGgJSIiIiJVY0BLRERERKrGgJaIiIiIVI0BLRERERGpGgNaIiIiIlI1BrREREREpGoMaImIiIhI1RjQEhEREZGqMaAlIiIiIlVjQEtEREREqsaAloiIiIhUjQEtEREREakaA1oiIiIiUjUGtERERESkagxoiYiIiEjVGNASERERkaoxoCUiIiIiVWNAS0RERESqxoCWiIiIiFSNAS0RERERqRoDWiIiIiJSNQa0RERERKRqDGiJiIiISNUY0BIRERGRqjGgJSIiIiJVY0BLRERERKrGgJaIiIiIVI0BLRERERGpGgNaIiIiIlI1BrREREREpGoMaImIiIhI1RjQEhEREZGqMaAlIiIiIlVjQEtEREREqsaAloiIiIhUjQEtEREREakaA1oiIiIiUjUGtERERESkagxoiYiIiEjVGNASERERkaoxoCUiIiIiVWNAS0RERESqxoCWiIiIiFSNAS0RERERqRoDWiIiIiJSNQa0RERERKRqDGiJiIiISNUY0BIRERGRqjGgJSIiIiJVY0BLRERERKrGgJaIiIiIVI0BLRERERGpGgNaIiIiIlK1/wem7A7RLpYbyAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "comparison = CompareFlights(\n", " [\n", @@ -500,7 +606,7 @@ " ]\n", ")\n", "\n", - "comparison.trajectories_3d(legend=True)\n" + "comparison.trajectories_3d(legend=True)" ] } ], @@ -511,8 +617,16 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "version": "3" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" } }, "nbformat": 4, From a225073d340d523490de8144ee2b1e48572bb3c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 11:50:03 +0000 Subject: [PATCH 25/34] ENH support FlightBody mission execution and fix multistage example flow Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/dd7a22c8-7e00-4af1-8f3f-0320d902b368 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 42 ++++--------------- rocketpy/mission/mission_executor.py | 61 +++++++++++++++++++++++++++- tests/unit/mission/test_mission.py | 24 +++++++++-- 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index a332aacd7..6e22681a5 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -432,22 +432,18 @@ " \"first_stage\",\n", " inclination=85,\n", " heading=20,\n", - " terminate_on_apogee=True,\n", - " max_time=450,\n", + " terminate_on_apogee=False,\n", + " max_time=8,\n", ")\n", "\n", "mission.set_flight_inputs(\n", " \"second_stage\",\n", - " inclination=0,\n", - " heading=0,\n", - " terminate_on_apogee=False,\n", - " max_time=500,\n", + " terminate_on_apogee=True,\n", + " max_time=240,\n", ")\n", "\n", "mission.set_flight_inputs(\n", " \"payload\",\n", - " inclination=0,\n", - " heading=0,\n", " terminate_on_apogee=False,\n", " max_time=700,\n", ")" @@ -500,12 +496,9 @@ "id": "8a65eabff63a45729fe45fb5ade58bdc", "metadata": {}, "source": [ - "## 8) Execute the powered stage mission\n", + "## 8) Execute the full mission (including payload)\n", "\n", - "`FlightBody` is now part of the mission topology above, and the payload\n", - "configuration can be inspected directly from the notebook. The execution step\n", - "below focuses on the two powered stages, which are the bodies currently\n", - "accepted by `MissionExecutor`.\n" + "The same mission object configured above is executed directly by `MissionExecutor`. This run includes both powered stages and the deployable payload `FlightBody`, preserving mission continuity.\n" ] }, { @@ -531,26 +524,8 @@ } ], "source": [ - "execution_mission = Mission(name=\"Calisto Two-Stage Mission Execution\")\n", - "execution_mission.add_stage(first_stage)\n", - "execution_mission.add_stage(second_stage)\n", - "execution_mission.set_flight_inputs(\n", - " \"first_stage\",\n", - " inclination=85,\n", - " heading=20,\n", - " terminate_on_apogee=True,\n", - " max_time=450,\n", - ")\n", - "execution_mission.set_flight_inputs(\n", - " \"second_stage\",\n", - " inclination=0,\n", - " heading=0,\n", - " terminate_on_apogee=False,\n", - " max_time=500,\n", - ")\n", - "\n", "executor = MissionExecutor(\n", - " mission=execution_mission,\n", + " mission=mission,\n", " environment=env,\n", " rail_length=5.2,\n", " default_flight_inputs={\"time_overshoot\": True},\n", @@ -571,7 +546,7 @@ "id": "4dd4641cc4064e0191573fe9c69df29b", "metadata": {}, "source": [ - "## 9) Compare the powered-stage trajectories\n" + "## 9) Compare all mission trajectories\n" ] }, { @@ -603,6 +578,7 @@ " [\n", " flights_by_name[\"first_stage\"],\n", " flights_by_name[\"second_stage\"],\n", + " flights_by_name[\"payload\"],\n", " ]\n", ")\n", "\n", diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index a26590627..f0e6c2bf8 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -1,9 +1,12 @@ """MissionExecutor – high-level API to execute all mission flight branches.""" +from copy import deepcopy from dataclasses import dataclass +from numbers import Real from typing import Any, Protocol, runtime_checkable -from rocketpy.body import RocketAdapter +from rocketpy.body import FlightBody, RocketAdapter +from rocketpy.rocket import PointMassRocket from rocketpy.simulation import Flight @@ -87,9 +90,63 @@ def _extract_rocket(body: Any): """Extract a Flight-compatible rocket object from a mission body.""" if isinstance(body, RocketAdapter): return body.rocket + if isinstance(body, FlightBody): + return MissionExecutor._build_point_mass_from_flight_body(body) if isinstance(body, FlightCompatibleRocket): return body raise TypeError( "MissionExecutor currently supports Stage/Deployable bodies backed " - "by RocketAdapter or by objects satisfying FlightCompatibleRocket." + "by RocketAdapter, FlightBody, or by objects satisfying " + "FlightCompatibleRocket." + ) + + @staticmethod + def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: + """Build a PointMassRocket proxy from a FlightBody mission item.""" + radius = MissionExecutor._extract_reference_radius(body.geometry) + mass = body.mass(0.0) + center_of_mass = body.center_of_mass(0.0) + rocket = PointMassRocket( + radius=radius, + mass=mass, + center_of_mass_without_motor=center_of_mass, + power_off_drag=0.75, + power_on_drag=0.75, + ) + rocket.name = body.name + + orientation = body.coordinate_system_orientation() + rocket.coordinate_system_orientation = orientation + rocket._csys = 1 if orientation == "tail_to_nose" else -1 + + rocket.parachutes = [deepcopy(system) for system in body.recovery_systems()] + + propulsion = body.propulsion_model() + if propulsion is not None: + nozzle_position = getattr(propulsion, "nozzle_position", 0.0) + rocket.add_motor(deepcopy(propulsion), position=nozzle_position) + + return rocket + + @staticmethod + def _extract_reference_radius(geometry: Any) -> float: + """Extract a positive reference radius from a FlightBody geometry field.""" + candidates = [geometry] + for attr in ("radius", "reference_radius"): + candidates.append(getattr(geometry, attr, None)) + + for candidate in candidates: + if isinstance(candidate, Real): + radius = float(candidate) + if radius > 0: + return radius + raise ValueError( + "FlightBody geometry radius must be a positive value, got " + f"{radius}." + ) + + raise TypeError( + "MissionExecutor could not infer a reference radius from FlightBody " + "geometry. Provide a numeric geometry value or an object exposing " + "'radius' or 'reference_radius'." ) diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index 394cc9a33..c52254da5 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -31,15 +31,17 @@ # --------------------------------------------------------------------------- -def _make_body(name="body"): +def _make_body(name="body", **kwargs): """Return a minimal FlightBody for testing.""" - return FlightBody( + defaults = dict( name=name, geometry=0.05, mass_model=lambda t: 10.0, inertia_model=lambda t: (1.0, 1.0, 0.05, 0.0, 0.0, 0.0), center_of_mass_model=lambda t: 0.5, ) + defaults.update(kwargs) + return FlightBody(**defaults) def _make_attachment(): @@ -554,7 +556,7 @@ def test_execute_runs_stage_and_deployable(self): def test_execute_raises_for_non_rocket_body(self): """execute raises TypeError when body is not RocketAdapter-backed.""" mission = Mission() - mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) + mission.add_stage(_make_stage(name="stage_1", body=object())) executor = MissionExecutor( mission=mission, @@ -563,7 +565,7 @@ def test_execute_raises_for_non_rocket_body(self): flight_class=self.FakeFlight, ) - with pytest.raises(TypeError, match="RocketAdapter"): + with pytest.raises(TypeError, match="FlightBody"): executor.execute() def test_execute_accepts_protocol_compatible_rocket_body(self): @@ -578,3 +580,17 @@ def test_execute_accepts_protocol_compatible_rocket_body(self): ) results = executor.execute() assert results[0].flight.rocket.name == "raw_stage" + + def test_execute_accepts_flight_body_body(self): + """execute accepts FlightBody bodies by building PointMassRocket proxies.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + results = executor.execute() + assert type(results[0].flight.rocket).__name__ == "PointMassRocket" + assert results[0].flight.rocket.name == "flight_body" From f95dbc94dafc07517618e8f39e3c00ce2ae496d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 11:51:30 +0000 Subject: [PATCH 26/34] STY clarify FlightBody proxy assumptions and notebook realism notes Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/dd7a22c8-7e00-4af1-8f3f-0320d902b368 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 4 +++- rocketpy/mission/mission_executor.py | 20 +++++++++++++++++--- tests/unit/mission/test_mission.py | 6 +++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 6e22681a5..bb7a119bc 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -428,6 +428,7 @@ }, "outputs": [], "source": [ + "# Keep stage-1 short so stage-2 starts near burnout/separation, not at apogee.\n", "mission.set_flight_inputs(\n", " \"first_stage\",\n", " inclination=85,\n", @@ -498,7 +499,8 @@ "source": [ "## 8) Execute the full mission (including payload)\n", "\n", - "The same mission object configured above is executed directly by `MissionExecutor`. This run includes both powered stages and the deployable payload `FlightBody`, preserving mission continuity.\n" + "The same mission object configured above is executed directly by `MissionExecutor`.\n", + "This run includes both powered stages and the deployable payload `FlightBody`, preserving mission continuity.\n" ] }, { diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index f0e6c2bf8..9530c310b 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -102,20 +102,34 @@ def _extract_rocket(body: Any): @staticmethod def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: - """Build a PointMassRocket proxy from a FlightBody mission item.""" + """Build a PointMassRocket proxy from a FlightBody mission item. + + Notes + ----- + This proxy uses the `FlightBody` snapshot at ``t=0`` for dry mass and + center of mass. The goal is to provide an immediate Flight-compatible + representation for mission execution of deployables, especially passive + payloads and recovery-only bodies. + """ radius = MissionExecutor._extract_reference_radius(body.geometry) + # Use t=0 as a stable initialization snapshot for point-mass proxy setup. mass = body.mass(0.0) center_of_mass = body.center_of_mass(0.0) + # Conservative default Cd for a generic bluff-body payload proxy. + # These can be superseded by parachute/recovery deployment dynamics. + default_drag_coefficient = 0.75 rocket = PointMassRocket( radius=radius, mass=mass, center_of_mass_without_motor=center_of_mass, - power_off_drag=0.75, - power_on_drag=0.75, + power_off_drag=default_drag_coefficient, + power_on_drag=default_drag_coefficient, ) rocket.name = body.name orientation = body.coordinate_system_orientation() + # PointMassRocket currently has no public API to re-evaluate `_csys` + # after initialization, so we synchronize both orientation fields here. rocket.coordinate_system_orientation = orientation rocket._csys = 1 if orientation == "tail_to_nose" else -1 diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index c52254da5..6c2798b05 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -103,6 +103,10 @@ def center_of_mass(self, t): return 0.0 +class UnsupportedBody: + """Intentional unsupported mission body type for executor error-path tests.""" + + def _make_rocket_adapter(name="rocket_body"): """Create a RocketAdapter with a minimal fake rocket object.""" return RocketAdapter(FakeRocket(name=name)) @@ -556,7 +560,7 @@ def test_execute_runs_stage_and_deployable(self): def test_execute_raises_for_non_rocket_body(self): """execute raises TypeError when body is not RocketAdapter-backed.""" mission = Mission() - mission.add_stage(_make_stage(name="stage_1", body=object())) + mission.add_stage(_make_stage(name="stage_1", body=UnsupportedBody())) executor = MissionExecutor( mission=mission, From 14f715103da62fb5e30ebaa4d8b283e05c574d6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 11:52:38 +0000 Subject: [PATCH 27/34] STY finalize validation feedback for mission executor and notebook Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/dd7a22c8-7e00-4af1-8f3f-0320d902b368 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 2 +- rocketpy/mission/mission_executor.py | 5 ++++- tests/unit/mission/test_mission.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index bb7a119bc..fec60804d 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -428,7 +428,7 @@ }, "outputs": [], "source": [ - "# Keep stage-1 short so stage-2 starts near burnout/separation, not at apogee.\n", + "# Use a short stage-1 cutoff (demo choice) so stage-2 starts near burnout, not at apogee.\n", "mission.set_flight_inputs(\n", " \"first_stage\",\n", " inclination=85,\n", diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index 9530c310b..c896cdd9e 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -9,6 +9,8 @@ from rocketpy.rocket import PointMassRocket from rocketpy.simulation import Flight +DEFAULT_FLIGHT_BODY_DRAG_COEFFICIENT = 0.75 + @runtime_checkable class FlightCompatibleRocket(Protocol): @@ -117,7 +119,7 @@ def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: center_of_mass = body.center_of_mass(0.0) # Conservative default Cd for a generic bluff-body payload proxy. # These can be superseded by parachute/recovery deployment dynamics. - default_drag_coefficient = 0.75 + default_drag_coefficient = DEFAULT_FLIGHT_BODY_DRAG_COEFFICIENT rocket = PointMassRocket( radius=radius, mass=mass, @@ -130,6 +132,7 @@ def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: orientation = body.coordinate_system_orientation() # PointMassRocket currently has no public API to re-evaluate `_csys` # after initialization, so we synchronize both orientation fields here. + # TODO: Replace this with a public PointMassRocket orientation API. rocket.coordinate_system_orientation = orientation rocket._csys = 1 if orientation == "tail_to_nose" else -1 diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index 6c2798b05..03d567d1d 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -103,7 +103,7 @@ def center_of_mass(self, t): return 0.0 -class UnsupportedBody: +class _UnsupportedBody: """Intentional unsupported mission body type for executor error-path tests.""" @@ -560,7 +560,7 @@ def test_execute_runs_stage_and_deployable(self): def test_execute_raises_for_non_rocket_body(self): """execute raises TypeError when body is not RocketAdapter-backed.""" mission = Mission() - mission.add_stage(_make_stage(name="stage_1", body=UnsupportedBody())) + mission.add_stage(_make_stage(name="stage_1", body=_UnsupportedBody())) executor = MissionExecutor( mission=mission, From 10b9ef4701e7e33881d9a2b4d0dbd069b14c149c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 11:53:46 +0000 Subject: [PATCH 28/34] STY polish mission executor and mission test readability Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/dd7a22c8-7e00-4af1-8f3f-0320d902b368 Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- rocketpy/mission/mission_executor.py | 8 +++++--- tests/unit/mission/test_mission.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index c896cdd9e..87d6fc6c8 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -109,9 +109,9 @@ def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: Notes ----- This proxy uses the `FlightBody` snapshot at ``t=0`` for dry mass and - center of mass. The goal is to provide an immediate Flight-compatible - representation for mission execution of deployables, especially passive - payloads and recovery-only bodies. + center of mass. This is a pragmatic approximation for mission execution + of deployables, especially passive payloads and recovery-only bodies, + and may be less representative for strongly time-varying bodies. """ radius = MissionExecutor._extract_reference_radius(body.geometry) # Use t=0 as a stable initialization snapshot for point-mass proxy setup. @@ -136,6 +136,8 @@ def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: rocket.coordinate_system_orientation = orientation rocket._csys = 1 if orientation == "tail_to_nose" else -1 + # Recovery systems are pre-built Parachute objects on FlightBody; assign + # deep-copied instances directly to preserve trigger/lags/noise settings. rocket.parachutes = [deepcopy(system) for system in body.recovery_systems()] propulsion = body.propulsion_model() diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index 03d567d1d..efc54a417 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -104,7 +104,7 @@ def center_of_mass(self, t): class _UnsupportedBody: - """Intentional unsupported mission body type for executor error-path tests.""" + """Minimal stub used to assert unsupported-body TypeError paths.""" def _make_rocket_adapter(name="rocket_body"): @@ -585,7 +585,7 @@ def test_execute_accepts_protocol_compatible_rocket_body(self): results = executor.execute() assert results[0].flight.rocket.name == "raw_stage" - def test_execute_accepts_flight_body_body(self): + def test_execute_accepts_flight_body(self): """execute accepts FlightBody bodies by building PointMassRocket proxies.""" mission = Mission() mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) From 35f40dad80525caf42abdbe7428f1d01d223f230 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 19:40:26 +0000 Subject: [PATCH 29/34] refactor: remove FlightBody point-mass proxy path Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/99fa3918-d664-41c6-9159-dccdd83b25cd Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 25 ++++----- rocketpy/mission/mission_executor.py | 76 +--------------------------- tests/unit/mission/test_mission.py | 8 +-- 3 files changed, 15 insertions(+), 94 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index fec60804d..11412a4c1 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -218,11 +218,6 @@ ")\n", "second_stage_rocket.name = \"Second Stage\"\n", "second_stage_rocket.add_motor(second_stage_motor, position=-1.000)\n", - "second_stage_rocket.set_rail_buttons(\n", - " upper_button_position=0.0818,\n", - " lower_button_position=-0.618,\n", - " angular_position=45,\n", - ")\n", "second_stage_rocket.add_nose(length=0.55829, kind=\"von karman\", position=1.050)\n", "second_stage_rocket.add_trapezoidal_fins(\n", " n=4,\n", @@ -307,7 +302,8 @@ "second_stage_rocket.add_parachute(\n", " \"Second Stage Main\",\n", " cd_s=5.0,\n", - " trigger=900,\n", + " trigger=lambda pressure, height_agl, state, sensors: state[0]\n", + " >= second_stage_motor.burn_out_time,\n", " sampling_rate=105,\n", " lag=1.2,\n", " noise=(0, 6.0, 0.25),\n", @@ -360,22 +356,22 @@ ")\n", "\n", "payload_attachment = Attachment(\n", - " parent_frame_position=[0.0, 0.0, 1.10],\n", + " parent_frame_position=[0.0, 0.0, 0.55],\n", " child_frame_position=[0.0, 0.0, -0.30],\n", " constraints=\"rigid\",\n", ")\n", "\n", "second_stage_sep = StageSeparationEvent(\n", " name=\"Second Stage Separation\",\n", - " trigger=lambda state, context: False,\n", + " trigger=lambda state, context: state[5] <= 0,\n", ")\n", "second_stage_ignition = IgnitionEvent(\n", " name=\"Second Stage Ignition\",\n", - " trigger=lambda state, context: False,\n", + " trigger=lambda state, context: state[5] <= 0,\n", ")\n", "payload_deploy_event = DeploymentEvent(\n", " name=\"Payload Deployment\",\n", - " trigger=lambda state, context: False,\n", + " trigger=lambda state, context: state[5] <= 0,\n", ")\n", "\n", "first_stage = Stage(\n", @@ -428,18 +424,17 @@ }, "outputs": [], "source": [ - "# Use a short stage-1 cutoff (demo choice) so stage-2 starts near burnout, not at apogee.\n", "mission.set_flight_inputs(\n", " \"first_stage\",\n", " inclination=85,\n", " heading=20,\n", - " terminate_on_apogee=False,\n", - " max_time=8,\n", + " terminate_on_apogee=True,\n", + " max_time=240,\n", ")\n", "\n", "mission.set_flight_inputs(\n", " \"second_stage\",\n", - " terminate_on_apogee=True,\n", + " terminate_on_apogee=False,\n", " max_time=240,\n", ")\n", "\n", @@ -447,7 +442,7 @@ " \"payload\",\n", " terminate_on_apogee=False,\n", " max_time=700,\n", - ")" + ")\n" ] }, { diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index 87d6fc6c8..0de331ecb 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -1,17 +1,11 @@ """MissionExecutor – high-level API to execute all mission flight branches.""" -from copy import deepcopy from dataclasses import dataclass -from numbers import Real from typing import Any, Protocol, runtime_checkable from rocketpy.body import FlightBody, RocketAdapter -from rocketpy.rocket import PointMassRocket from rocketpy.simulation import Flight -DEFAULT_FLIGHT_BODY_DRAG_COEFFICIENT = 0.75 - - @runtime_checkable class FlightCompatibleRocket(Protocol): """Structural contract for rocket objects accepted by Flight.""" @@ -93,7 +87,7 @@ def _extract_rocket(body: Any): if isinstance(body, RocketAdapter): return body.rocket if isinstance(body, FlightBody): - return MissionExecutor._build_point_mass_from_flight_body(body) + return body if isinstance(body, FlightCompatibleRocket): return body raise TypeError( @@ -101,71 +95,3 @@ def _extract_rocket(body: Any): "by RocketAdapter, FlightBody, or by objects satisfying " "FlightCompatibleRocket." ) - - @staticmethod - def _build_point_mass_from_flight_body(body: FlightBody) -> PointMassRocket: - """Build a PointMassRocket proxy from a FlightBody mission item. - - Notes - ----- - This proxy uses the `FlightBody` snapshot at ``t=0`` for dry mass and - center of mass. This is a pragmatic approximation for mission execution - of deployables, especially passive payloads and recovery-only bodies, - and may be less representative for strongly time-varying bodies. - """ - radius = MissionExecutor._extract_reference_radius(body.geometry) - # Use t=0 as a stable initialization snapshot for point-mass proxy setup. - mass = body.mass(0.0) - center_of_mass = body.center_of_mass(0.0) - # Conservative default Cd for a generic bluff-body payload proxy. - # These can be superseded by parachute/recovery deployment dynamics. - default_drag_coefficient = DEFAULT_FLIGHT_BODY_DRAG_COEFFICIENT - rocket = PointMassRocket( - radius=radius, - mass=mass, - center_of_mass_without_motor=center_of_mass, - power_off_drag=default_drag_coefficient, - power_on_drag=default_drag_coefficient, - ) - rocket.name = body.name - - orientation = body.coordinate_system_orientation() - # PointMassRocket currently has no public API to re-evaluate `_csys` - # after initialization, so we synchronize both orientation fields here. - # TODO: Replace this with a public PointMassRocket orientation API. - rocket.coordinate_system_orientation = orientation - rocket._csys = 1 if orientation == "tail_to_nose" else -1 - - # Recovery systems are pre-built Parachute objects on FlightBody; assign - # deep-copied instances directly to preserve trigger/lags/noise settings. - rocket.parachutes = [deepcopy(system) for system in body.recovery_systems()] - - propulsion = body.propulsion_model() - if propulsion is not None: - nozzle_position = getattr(propulsion, "nozzle_position", 0.0) - rocket.add_motor(deepcopy(propulsion), position=nozzle_position) - - return rocket - - @staticmethod - def _extract_reference_radius(geometry: Any) -> float: - """Extract a positive reference radius from a FlightBody geometry field.""" - candidates = [geometry] - for attr in ("radius", "reference_radius"): - candidates.append(getattr(geometry, attr, None)) - - for candidate in candidates: - if isinstance(candidate, Real): - radius = float(candidate) - if radius > 0: - return radius - raise ValueError( - "FlightBody geometry radius must be a positive value, got " - f"{radius}." - ) - - raise TypeError( - "MissionExecutor could not infer a reference radius from FlightBody " - "geometry. Provide a numeric geometry value or an object exposing " - "'radius' or 'reference_radius'." - ) diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index efc54a417..dc257ba08 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -586,9 +586,10 @@ def test_execute_accepts_protocol_compatible_rocket_body(self): assert results[0].flight.rocket.name == "raw_stage" def test_execute_accepts_flight_body(self): - """execute accepts FlightBody bodies by building PointMassRocket proxies.""" + """execute accepts FlightBody bodies without converting them.""" mission = Mission() - mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) + body = _make_body("flight_body") + mission.add_stage(_make_stage(name="stage_1", body=body)) executor = MissionExecutor( mission=mission, environment=object(), @@ -596,5 +597,4 @@ def test_execute_accepts_flight_body(self): flight_class=self.FakeFlight, ) results = executor.execute() - assert type(results[0].flight.rocket).__name__ == "PointMassRocket" - assert results[0].flight.rocket.name == "flight_body" + assert results[0].flight.rocket is body From b22544696d4d422965283a34735484bf0befe624 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 19:41:57 +0000 Subject: [PATCH 30/34] docs: clarify multistage trigger conditions in notebook Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/99fa3918-d664-41c6-9159-dccdd83b25cd Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 11412a4c1..c3c96d9b1 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -299,6 +299,7 @@ } ], "source": [ + "# Deploy second-stage parachute right after stage-2 motor burnout.\n", "second_stage_rocket.add_parachute(\n", " \"Second Stage Main\",\n", " cd_s=5.0,\n", @@ -361,6 +362,8 @@ " constraints=\"rigid\",\n", ")\n", "\n", + "# In RocketPy state vectors, state[5] is vz (vertical velocity in m/s);\n", + "# vz <= 0 is used here as a descent/apogee crossing condition.\n", "second_stage_sep = StageSeparationEvent(\n", " name=\"Second Stage Separation\",\n", " trigger=lambda state, context: state[5] <= 0,\n", From 0baab952a9fc80a8f1bb96bf974d1e82a9fd519b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 19:42:29 +0000 Subject: [PATCH 31/34] docs: clarify parachute trigger callback signature Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/99fa3918-d664-41c6-9159-dccdd83b25cd Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index c3c96d9b1..60ccb3d33 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -300,6 +300,7 @@ ], "source": [ "# Deploy second-stage parachute right after stage-2 motor burnout.\n", + "# Parachute trigger callbacks use (pressure, height_agl, state, sensors).\n", "second_stage_rocket.add_parachute(\n", " \"Second Stage Main\",\n", " cd_s=5.0,\n", From a7bc2f4adc26b9cb4acb9f5bc3700de72714cd4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 19:43:06 +0000 Subject: [PATCH 32/34] docs: refine burnout-triggered chute and payload placement notes Agent-Logs-Url: https://github.com/aZira371/RocketPy/sessions/99fa3918-d664-41c6-9159-dccdd83b25cd Co-authored-by: aZira371 <99824864+aZira371@users.noreply.github.com> --- docs/multistage_example.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 60ccb3d33..7219165b7 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -305,7 +305,7 @@ " \"Second Stage Main\",\n", " cd_s=5.0,\n", " trigger=lambda pressure, height_agl, state, sensors: state[0]\n", - " >= second_stage_motor.burn_out_time,\n", + " >= second_stage_motor.burn_out_time and state[5] <= 0,\n", " sampling_rate=105,\n", " lag=1.2,\n", " noise=(0, 6.0, 0.25),\n", @@ -357,6 +357,8 @@ " constraints=\"rigid\",\n", ")\n", "\n", + "# In this tail_to_nose frame, z=0.55 m places the payload approximately\n", + "# halfway inserted into the first-stage airframe.\n", "payload_attachment = Attachment(\n", " parent_frame_position=[0.0, 0.0, 0.55],\n", " child_frame_position=[0.0, 0.0, -0.30],\n", From 153894224fa2354bc52bf33c5611e42c1a9752d5 Mon Sep 17 00:00:00 2001 From: Ishan Date: Sun, 12 Jul 2026 23:41:45 +0530 Subject: [PATCH 33/34] modified: docs/multistage_example.ipynb modified: rocketpy/__init__.py modified: rocketpy/body/__init__.py new file: rocketpy/body/body_resolver.py modified: rocketpy/mission/__init__.py new file: rocketpy/mission/branch_result.py new file: rocketpy/mission/flight_config.py modified: rocketpy/mission/mission.py modified: rocketpy/mission/mission_executor.py new file: rocketpy/mission/mission_result.py new file: test_nb.py new file: test_nb2.py new file: test_nb_all.py new file: test_nb_seb.py new file: tests/unit/body/test_body_resolver.py new file: tests/unit/mission/test_flight_config.py modified: tests/unit/mission/test_mission.py new file: tests/unit/mission/test_mission_result.py --- docs/multistage_example.ipynb | 253 +++++++++++++------ rocketpy/__init__.py | 11 +- rocketpy/body/__init__.py | 9 +- rocketpy/body/body_resolver.py | 114 +++++++++ rocketpy/mission/__init__.py | 6 + rocketpy/mission/branch_result.py | 39 +++ rocketpy/mission/flight_config.py | 130 ++++++++++ rocketpy/mission/mission.py | 122 ++++++++- rocketpy/mission/mission_executor.py | 191 ++++++++++---- rocketpy/mission/mission_result.py | 131 ++++++++++ test_nb.py | 184 ++++++++++++++ test_nb2.py | 126 +++++++++ test_nb_all.py | 295 ++++++++++++++++++++++ test_nb_seb.py | 173 +++++++++++++ tests/unit/body/test_body_resolver.py | 111 ++++++++ tests/unit/mission/test_flight_config.py | 72 ++++++ tests/unit/mission/test_mission.py | 269 +++++++++++++++++++- tests/unit/mission/test_mission_result.py | 148 +++++++++++ 18 files changed, 2247 insertions(+), 137 deletions(-) create mode 100644 rocketpy/body/body_resolver.py create mode 100644 rocketpy/mission/branch_result.py create mode 100644 rocketpy/mission/flight_config.py create mode 100644 rocketpy/mission/mission_result.py create mode 100644 test_nb.py create mode 100644 test_nb2.py create mode 100644 test_nb_all.py create mode 100644 test_nb_seb.py create mode 100644 tests/unit/body/test_body_resolver.py create mode 100644 tests/unit/mission/test_flight_config.py create mode 100644 tests/unit/mission/test_mission_result.py diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 7219165b7..d513a4e78 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -28,10 +28,10 @@ "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:29.816104Z", - "iopub.status.busy": "2026-05-10T06:43:29.815930Z", - "iopub.status.idle": "2026-05-10T06:43:30.928322Z", - "shell.execute_reply": "2026-05-10T06:43:30.927474Z" + "iopub.execute_input": "2026-07-12T15:57:42.201214Z", + "iopub.status.busy": "2026-07-12T15:57:42.200994Z", + "iopub.status.idle": "2026-07-12T15:57:43.906176Z", + "shell.execute_reply": "2026-07-12T15:57:43.905459Z" } }, "outputs": [], @@ -49,7 +49,6 @@ " IgnitionEvent,\n", " Mission,\n", " MissionExecutor,\n", - " Parachute,\n", " Rocket,\n", " RocketAdapter,\n", " SolidMotor,\n", @@ -76,10 +75,10 @@ "id": "72eea5119410473aa328ad9291626812", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:30.930518Z", - "iopub.status.busy": "2026-05-10T06:43:30.930233Z", - "iopub.status.idle": "2026-05-10T06:43:30.946274Z", - "shell.execute_reply": "2026-05-10T06:43:30.945576Z" + "iopub.execute_input": "2026-07-12T15:57:43.908560Z", + "iopub.status.busy": "2026-07-12T15:57:43.908305Z", + "iopub.status.idle": "2026-07-12T15:57:43.919902Z", + "shell.execute_reply": "2026-07-12T15:57:43.919229Z" } }, "outputs": [], @@ -122,13 +121,24 @@ "id": "10185d26023b46108eb7d9f57d49d2b3", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:30.948093Z", - "iopub.status.busy": "2026-05-10T06:43:30.947936Z", - "iopub.status.idle": "2026-05-10T06:43:31.030295Z", - "shell.execute_reply": "2026-05-10T06:43:31.029426Z" + "iopub.execute_input": "2026-07-12T15:57:43.921669Z", + "iopub.status.busy": "2026-07-12T15:57:43.921535Z", + "iopub.status.idle": "2026-07-12T15:57:43.993664Z", + "shell.execute_reply": "2026-07-12T15:57:43.993089Z" } }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "data_directory = Path(rp.__file__).resolve().parent.parent / \"data\"\n", "motor_file = data_directory / \"motors\" / \"cesaroni\" / \"Cesaroni_M1670.eng\"\n", @@ -234,31 +244,36 @@ " position=-1.194656,\n", ")\n", "\n", - "payload_body = FlightBody(\n", - " name=\"Payload\",\n", - " geometry=0.055,\n", - " mass_model=lambda t: 4.5,\n", - " inertia_model=lambda t: (0.25, 0.25, 0.03, 0.0, 0.0, 0.0),\n", - " center_of_mass_model=lambda t: 0.0,\n", - " recovery_systems=[\n", - " Parachute(\n", - " name=\"Payload Drogue\",\n", - " cd_s=0.35,\n", - " trigger=\"apogee\",\n", - " sampling_rate=105,\n", - " lag=1.0,\n", - " noise=(0, 4.0, 0.2),\n", - " ),\n", - " Parachute(\n", - " name=\"Payload Main\",\n", - " cd_s=3.5,\n", - " trigger=700,\n", - " sampling_rate=105,\n", - " lag=1.0,\n", - " noise=(0, 4.0, 0.2),\n", - " ),\n", - " ],\n", + "# The deployable payload is an unpowered Rocket (no add_motor() call, so it\n", + "# keeps RocketPy's default EmptyMotor) with its own nose cone and recovery\n", + "# system, wrapped in RocketAdapter so MissionExecutor can fly it like any\n", + "# other mission body.\n", + "payload_rocket = Rocket(\n", + " radius=0.055,\n", + " mass=4.5,\n", + " inertia=(0.25, 0.25, 0.03),\n", + " power_off_drag=str(power_off_drag_file),\n", + " power_on_drag=str(power_on_drag_file),\n", + " center_of_mass_without_motor=0,\n", " coordinate_system_orientation=\"tail_to_nose\",\n", + ")\n", + "payload_rocket.name = \"Payload\"\n", + "payload_rocket.add_nose(length=0.20, kind=\"von karman\", position=0.30)\n", + "payload_rocket.add_parachute(\n", + " \"Payload Drogue\",\n", + " cd_s=0.35,\n", + " trigger=\"apogee\",\n", + " sampling_rate=105,\n", + " lag=1.0,\n", + " noise=(0, 4.0, 0.2),\n", + ")\n", + "payload_rocket.add_parachute(\n", + " \"Payload Main\",\n", + " cd_s=3.5,\n", + " trigger=700,\n", + " sampling_rate=105,\n", + " lag=1.0,\n", + " noise=(0, 4.0, 0.2),\n", ")" ] }, @@ -267,10 +282,14 @@ "id": "8763a12b2bbd4a93a75aff182afb95dc", "metadata": {}, "source": [ - "## 4) Configure the stage rockets and the payload `FlightBody`\n", + "## 4) Configure the stage rockets and the payload rocket\n", "\n", - "The two powered stages still use `Rocket`, while the deployable payload now\n", - "uses `FlightBody` directly with its own recovery systems.\n" + "All three mission bodies – both powered stages and the deployable payload –\n", + "use full RocketPy `Rocket` models, wrapped in `RocketAdapter` so\n", + "`MissionExecutor` can fly them uniformly. A `FlightBody` value object is\n", + "still available for lightweight bookkeeping (see the end of this notebook),\n", + "but it cannot yet drive a real `Flight` simulation directly, so it is not\n", + "used for any mission item that needs to be executed here.\n" ] }, { @@ -279,10 +298,10 @@ "id": "7623eae2785240b9bd12b16a66d81610", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:31.031845Z", - "iopub.status.busy": "2026-05-10T06:43:31.031648Z", - "iopub.status.idle": "2026-05-10T06:43:31.035930Z", - "shell.execute_reply": "2026-05-10T06:43:31.035225Z" + "iopub.execute_input": "2026-07-12T15:57:43.995389Z", + "iopub.status.busy": "2026-07-12T15:57:43.995255Z", + "iopub.status.idle": "2026-07-12T15:57:43.998633Z", + "shell.execute_reply": "2026-07-12T15:57:43.998089Z" } }, "outputs": [ @@ -290,22 +309,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "Using motor file: /home/runner/work/RocketPy/RocketPy/data/motors/cesaroni/Cesaroni_M1670.eng\n", + "Using motor file: /Users/dubeyishan371/Desktop/Rocketpy-fork/RocketPy/data/motors/cesaroni/Cesaroni_M1670.eng\n", "Using drag curve files: powerOffDragCurve.csv, powerOnDragCurve.csv\n", - "Payload body type: FlightBody\n", + "Payload body type: Rocket\n", "Payload dry mass at t=0 s: 4.50 kg\n", "Payload recovery systems: 2\n" ] } ], "source": [ - "# Deploy second-stage parachute right after stage-2 motor burnout.\n", - "# Parachute trigger callbacks use (pressure, height_agl, state, sensors).\n", + "# Deploy second-stage parachute at apogee.\n", + "# Parachute trigger callbacks can be strings like \"apogee\" or functions.\n", "second_stage_rocket.add_parachute(\n", " \"Second Stage Main\",\n", " cd_s=5.0,\n", - " trigger=lambda pressure, height_agl, state, sensors: state[0]\n", - " >= second_stage_motor.burn_out_time and state[5] <= 0,\n", + " trigger=\"apogee\",\n", " sampling_rate=105,\n", " lag=1.2,\n", " noise=(0, 6.0, 0.25),\n", @@ -313,9 +331,9 @@ "\n", "print(f\"Using motor file: {motor_file}\")\n", "print(f\"Using drag curve files: {power_off_drag_file.name}, {power_on_drag_file.name}\")\n", - "print(f\"Payload body type: {type(payload_body).__name__}\")\n", - "print(f\"Payload dry mass at t=0 s: {payload_body.mass(0):.2f} kg\")\n", - "print(f\"Payload recovery systems: {len(payload_body.recovery_systems())}\")" + "print(f\"Payload body type: {type(payload_rocket).__name__}\")\n", + "print(f\"Payload dry mass at t=0 s: {payload_rocket.total_mass(0):.2f} kg\")\n", + "print(f\"Payload recovery systems: {len(payload_rocket.parachutes)}\")" ] }, { @@ -335,10 +353,10 @@ "id": "b118ea5561624da68c537baed56e602f", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:31.037638Z", - "iopub.status.busy": "2026-05-10T06:43:31.037467Z", - "iopub.status.idle": "2026-05-10T06:43:31.042439Z", - "shell.execute_reply": "2026-05-10T06:43:31.041701Z" + "iopub.execute_input": "2026-07-12T15:57:44.000202Z", + "iopub.status.busy": "2026-07-12T15:57:44.000092Z", + "iopub.status.idle": "2026-07-12T15:57:44.004599Z", + "shell.execute_reply": "2026-07-12T15:57:44.004119Z" } }, "outputs": [], @@ -396,7 +414,7 @@ "\n", "payload = Deployable(\n", " name=\"payload\",\n", - " body=payload_body,\n", + " body=RocketAdapter(payload_rocket),\n", " attachment=payload_attachment,\n", " deployment_event=payload_deploy_event,\n", ")\n", @@ -422,10 +440,10 @@ "id": "504fb2a444614c0babb325280ed9130a", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:31.044111Z", - "iopub.status.busy": "2026-05-10T06:43:31.043963Z", - "iopub.status.idle": "2026-05-10T06:43:31.047158Z", - "shell.execute_reply": "2026-05-10T06:43:31.046326Z" + "iopub.execute_input": "2026-07-12T15:57:44.006184Z", + "iopub.status.busy": "2026-07-12T15:57:44.006041Z", + "iopub.status.idle": "2026-07-12T15:57:44.008369Z", + "shell.execute_reply": "2026-07-12T15:57:44.007954Z" } }, "outputs": [], @@ -465,10 +483,10 @@ "id": "b43b363d81ae4b689946ece5c682cd59", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:31.048608Z", - "iopub.status.busy": "2026-05-10T06:43:31.048444Z", - "iopub.status.idle": "2026-05-10T06:43:31.051939Z", - "shell.execute_reply": "2026-05-10T06:43:31.051254Z" + "iopub.execute_input": "2026-07-12T15:57:44.010028Z", + "iopub.status.busy": "2026-07-12T15:57:44.009906Z", + "iopub.status.idle": "2026-07-12T15:57:44.012644Z", + "shell.execute_reply": "2026-07-12T15:57:44.012073Z" } }, "outputs": [ @@ -481,7 +499,7 @@ "Connection map:\n", " - first_stage: {'parent_frame_position': [0.0, 0.0, 0.0], 'child_frame_position': [0.0, 0.0, 0.0], 'orientation': None, 'constraints': 'rigid'}\n", " - second_stage: {'parent_frame_position': [0.0, 0.0, 0.85], 'child_frame_position': [0.0, 0.0, -0.45], 'orientation': None, 'constraints': 'rigid'}\n", - " - payload: {'parent_frame_position': [0.0, 0.0, 1.1], 'child_frame_position': [0.0, 0.0, -0.3], 'orientation': None, 'constraints': 'rigid'}\n" + " - payload: {'parent_frame_position': [0.0, 0.0, 0.55], 'child_frame_position': [0.0, 0.0, -0.3], 'orientation': None, 'constraints': 'rigid'}\n" ] } ], @@ -501,7 +519,8 @@ "## 8) Execute the full mission (including payload)\n", "\n", "The same mission object configured above is executed directly by `MissionExecutor`.\n", - "This run includes both powered stages and the deployable payload `FlightBody`, preserving mission continuity.\n" + "This run includes both powered stages and the deployable payload, chaining each\n", + "item's flight from the previous one's final state.\n" ] }, { @@ -510,10 +529,10 @@ "id": "c3933fab20d04ec698c2621248eb3be0", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:31.053466Z", - "iopub.status.busy": "2026-05-10T06:43:31.053320Z", - "iopub.status.idle": "2026-05-10T06:43:31.401859Z", - "shell.execute_reply": "2026-05-10T06:43:31.401089Z" + "iopub.execute_input": "2026-07-12T15:57:44.014064Z", + "iopub.status.busy": "2026-07-12T15:57:44.013954Z", + "iopub.status.idle": "2026-07-12T15:57:44.245232Z", + "shell.execute_reply": "2026-07-12T15:57:44.244750Z" } }, "outputs": [ @@ -521,8 +540,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "first_stage: apogee = 4443.75 m, impact velocity = 0.00 m/s\n", - "second_stage: apogee = 4443.75 m, impact velocity = 0.00 m/s\n" + "WARNING: Trying to add flight phase starting *after* the one *proceeding* it. This may be caused by multiple parachutes being triggered simultaneously.\n", + "first_stage: apogee = 4443.71 m, impact velocity = 0.00 m/s\n", + "second_stage: apogee = 4443.71 m, impact velocity = 0.00 m/s\n", + "payload: apogee = 3130.67 m, impact velocity = -4.61 m/s\n" ] } ], @@ -558,16 +579,16 @@ "id": "8309879909854d7188b41380fd92a7c3", "metadata": { "execution": { - "iopub.execute_input": "2026-05-10T06:43:31.403607Z", - "iopub.status.busy": "2026-05-10T06:43:31.403443Z", - "iopub.status.idle": "2026-05-10T06:43:31.564239Z", - "shell.execute_reply": "2026-05-10T06:43:31.563146Z" + "iopub.execute_input": "2026-07-12T15:57:44.246753Z", + "iopub.status.busy": "2026-07-12T15:57:44.246625Z", + "iopub.status.idle": "2026-07-12T15:57:44.448149Z", + "shell.execute_reply": "2026-07-12T15:57:44.447496Z" } }, "outputs": [ { "data": { - "image/png": "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2dnZzGAwsPPPP58tX75c2vfLL79kF154IbNYLEyv17NJkyaxDRs2dPlM/znmcrnYM888w6qqqphGo2E5OTls5syZbOfOnUHtWbt2Lbv//vvZqFGjWEFBAVMqlSw/P59ddtllbPHixUHf4/+bNjQ0sLvuuov17duXqVSqgHNTnGedOXbsGPu///s/NmDAAKZWq5lWq2WlpaXswgsvZM8991xIO2fOnMmKioqYUqlkeXl57LLLLmOLFi0Kun80aw6ROGw2G9uxYwez2WypNiUiZs2axS6//PKgr40fP57ddddd0r8rKirYiy++yBhjzO12s0GDBrEpU6awzZs3s5UrV7Jzzz2XAWCffPIJY+zUebtx40bpM5qamqT1gTHGVq1axQCwJUuWsPr6etbe3h7Ulp7GlwQtwRXJELTHjh1j/fr1YwBYdnY2u+qqq9gVV1zBsrKy2KBBg9gVV1zRraC96667mMlkYhUVFezqq69mkyZNYlqtlgFgV1xxRcB77r33XjZ27FgGgPXr14/NmjVL+nv66acjGJmuxxVK0JaXl7Pp06czlUrFJk6cyK699lp2+umnS/vddNNNDAAbPHgwmzJlCrvmmmvY6NGjmUwmYwDY3Llzu3xnd+N89OhRdtppp0njOWnSJHbllVdKdvbp04cdOHCgy/vef/99plarJZtnzJjBrrzySjZ8+HAmCAJ79NFHGWOMffzxx9LYiwLE/6++vl76zIceeogBYIIgsLFjx7Jrr72WjRgxggFgcrmcvfXWW13sEMXZ7bffzmQyGauqqmI//elP2eTJk9kHH3zAGOte0H777bcsKytLOtbp06ezKVOmSNsmT57MnE6ntL/H42ETJ05kAJjJZGKXXHIJu/baa9mkSZOkMfO/OHTHp59+Kh1vdzcO3RHLmN13331MoVCwM844g11zzTXS+9RqNVu1ahV75ZVXmEwmY2PGjGFXX301GzhwIAPADAYD27t3b8Bn+s+xq666iimVSjZp0iT205/+lPXt21d63+rVq7vYM3HiRCaTydhpp53GLr30UjZz5kx25plnSnPmpZde6vIe8TedOnUqq6ysZFlZWWz69Ols5syZ7Oc//7m0XzBBW1tby4qLi6W5e/nll7NrrrmGnXfeeSw7O5uZzeYu3/eXv/xFOsfOOOMMdu2117IxY8ZIn//YY491eU80aw6ROHqb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VbBECu8cDgdOnz6NgwcP0qYCqbSEylYGS68pLCzEBRdcMCCX1u/30589duwYtFotFbdjPaVgGAZ79+7F+vXr0dTUhDNnzmDRokV48MEHsXz5cvHUI4mIzyuR0SDm0OYovb29KC8vx5NPPok1a9bAarXizTffxKpVqwAAhw8fxuTJkwck2be1taGwsBAA8Nprr+E//uM/4HA4BGukzydIfmVjYyPq6+uxbds2VFVVYfny5VixYgWmTp06aAR1pPzB0XYQIoUydrudeq7abDaYzWbR6mYQYivkvV5vVDHdaO8J0lSAmPYXFBREOSaIomggQ7WcHakAkgtpUkHuG2BkX2WGYfDll1/SSKzT6cSSJUuwatUqXH/99SgoKEj6exURn1cio0MUtDnCAw88gGXLlqGiogKtra144oknsHv3bhw8eBBWqxV33HEH3n33Xbz++uvQ6XS45557AAA7duwA8K0NSklJCZ5//nm0t7fje9/7Hv7lX/5FtEFJEV1dXdiwYQPWrVuH9957D6WlpaitrUVdXR20Wi3q6+vx3nvv4d///d9RXFyctM5KmbIyEgLcqDaJgCezQj4YDNIIotvthlqtpvOe6xG/wVrOJmJRx4VhGLqhczqdOHLkCN58801cd911WLFiBc6dO4eGhgZs2LAB3d3duP7667Fy5Upce+21opVdChCfVyLxIAraHOHGG2/Exx9/DLfbDavVinnz5uGZZ55BVVUVgG+Nqt98880oo2ru8UxLSwvuuOMOfPTRR8jLy8OaNWvw85//XIzepYHu7m785je/wZ///Gc0NzeDYRgUFhbi2muvxWOPPYbS0tKU/e1MmM3zCW5epsvlou1wiTNBqvKOI5EI7ZhFnCjIvOdKvvNgTURIJDZVuZSkqOuZZ57B3/72N3R0dEAmk2HmzJm4/fbbceutt4p5nClGfF6JxIMoaEVEeMyePXvw2muvoampCV1dXViyZAmuvfZaSKVSbN26FZs2bUJeXh6WL1+Ouro6zJ07N6W5r7F5ouloB5oJyJE2OY5WqVT0fWYiUsrNjSa2UyQynG0pIYNdYzabLeUNJoLBILZv347169dj06ZNkEgkqKurwxVXXIHe3l68++672Lp1K8rKylBbW4s77riDCiwREZHMIwpaEREes3nzZmzcuBG1tbW48sorB+R++f1+bNu2DfX19WhsbIRC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lpaWUrduXXFxcZFZs2bJzZs39eo5f/68BAYGiomJiVhZWcnMmTOloqJCr8y+ffukQ4cOYmxsLE5OTsp7PG21sc1ERA9DIyLybPqGiYiIiIgeH8fQEhEREZGqMaAlIiIiIlVjQEtEREREqsaAloiIiIhUjQEtEREREakaA1oiIiIiUjUGtERERESkagxoiYiIiEjVGNASERERkaoxoCUiIiIiVWNAS0RERESqxoCWiIiIiFSNAS0RERERqRoDWiIiIiJSNQa0RERERKRqDGiJiIiISNUY0BIRERGRqjGgJSIiIiJVY0BLRERERKrGgJaIiIiIVI0BLRERERGpGgNaIiIiIlI1BrREREREpGoMaImIiIhI1RjQEhEREZGqMaAlIiIiIlVjQEtEREREqsaAloiIiIhUjQEtEREREakaA1oiIiIiUjUGtERERESkagxoiYiIiEjVGNASERERkaoxoCUiIiIiVWNAS0RERESqxoCWiIiIiFSNAS0RERERqRoDWiIiIiJSNQa0RERERKRqDGiJiIiISNUY0BIRERGRqjGgJSIiIiJVY0BLRERERKrGgJaIiIiIVI0BLRERERGpGgNaIiIiIlI1BrREREREpGoMaImIiIhI1RjQEhEREZGqMaAlIiIiIlVjQEtEREREqsaAloiIiIhUjQEtEREREakaA1oiIiIiUjUGtERERESkagxoiYiIiEjVGNASERERkaoxoCUiIiIiVWNAS0RERESqxoCWiIiIiFSNAS0RERERqRoDWiIiIiJSNQa0RERERKRqDGiJiIiISNUY0BIRERGRqjGgJSIiIiJVY0BLRERERKrGgJaIiIiIVI0BLRERERGpGgNaIiIiIlK1/wem7A7RLpYbyAAAAABJRU5ErkJggg==", + "image/png": "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clgtmRtuuIHOPvts+u9//2tSbuF49913zXHfffcZS108VlvsNIBFixYZIWT/jXRQ3rXg1bVij8/JkyfT1VdfbUTl448/TieddJIRgLipw7WMcp34ebQxncx5x/d66623TF5fWIVh6caBxPU40Abk/XVaWpNF+pyjKOlABa2iWHCRhVjJ5bM58TyssxCzKBSAxd9JojlKUUUGnHLKKXHX54bogthAdStsA8MNIVXwmdiShtUPVlO7ao3TmupmoQ18f/QZtuT69u2b0O8ir+OVV15pDjB79my67LLLaNy4cWYr+5///Gdc1X9g8cS2IMpTwkopvc/KY/PmzeaIZKXla7NZs2bh1z744AMjVnGTgJs0J17m3YVVFges7WjD999/b1whcJ3hfGAnhPsPJUTRn5HGO/dzpUqVjMuGoij7ord1imKBrWFYbWDVmTt37j59A1/HWO4GycDbx/BZzDQbN240j9jidoIF+Z133kno85Co3xYV8QCr1VFHHRXeKk60Ek2kfoT/LpcKjlZeGD6rLALdgr//+++/n/JnwacbwhhMnTo1rt/BlvONN95o/v/ggw8aS215vtPwm85kn8XDv//9731eg583yvaCww47LK4xja34//u//6N0gHll0KBBYd9e+xxye8vrZ8xPkXzRFUVRQasoZcD2OrYDsVV87bXXGssWs2XLFvNavMIsXtiaBLGcaTiI5MMPPwwHgAEEotx7770mcCsRYJnt168fTZw40Vij2E/WBgE6KMdqC9G7777bLNwIhEFwjbPPlyxZYm46IvXjzJkzI7bl1ltvNY/Y8kXZYxsICWzvQxTfdNNN5BawzMGSiEAlBEpwcJ0NRCa2pxlY8b788stw0BKDPkAAVjRxFg2ctwMOOMBYNSFQYR10gvOLLXC4KPz4448Z7bN4q58huIvB9QqxD99ZWMXtoEce07Bo29czxOyf//znckV+MsD66hyfAH+fA8Dsc4j+w3j/5JNPyowFADefV1991fw/3l0ORclF9FZPURxg8f7111+NsECQxqGHHmrEBBZ6bMOefPLJZiGPNzCnPPbff39q0qSJsYwhwr179+5GJCAgCoIonUDMQ9RgMUaAEr47fD8nTJhgos4hGiK5IsTy7cMifcIJJxhBAaHcs2dP48MIcYet1OnTpxtBhSA/tj5BBL/++usm+O+6666jJ554wrwG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RVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURVEURVF8jQpaRVEURVEUxdeooFUURUkDoVDIHIqiKIr7BD34TEVRlJwGwrWkpMQcxcXF4aOgoIDy8/MpLy+PAoGAeQT4v6IoipI8KmgVRVFcFq9FRUXh11ms4hGv4WcMXmNhqyJXURQleQIh3QNTFEVxXbzaB1OhQoUyn8WP9jSsIldRFCVxVNAqiqJ4IF4jYQvaaH+TH1XkKoqixI8KWkVRcp7yxOusWbOofv361LBhw7jFK35v+/bttG3bNnNs3brV+M/WrFmTatSoYR6rVq1a7ufYInfRokW0a9cu6tSpk1pyFUVRLNSHVlGUnCJRy2swGAy/DkEaCfx8x44dRrSygP3999/N71SvXt0I2BYtWhg/Wbxn2bJlRiQD/IwFLh6dItfpg4u/xcFk6pOrKIpSigpaRVGylmTEazyWV4hXtro6xSuOZs2aGXFa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1iRTPRyhNU2jHTlG1kQKqDol9wnfVY67MFRn98yOmUgDR0JEgPAJpDKbyBr/nnxJpT7Eu3ft+l0lbfq5OQ9HnFiBERHCX5mUBQrKiN4SLc07ysiYJ5WAPFsdiRJh4jiVRg99NSZoxzUif54oLA88lTmepqqeBykdjsSagUKuNG4D5LISAWZsE1km95q5n3HVCOZ4rN1IVSOnXvn0pPEQ1SWyvPvuu4evkReLxSRt24n8Zse2x261cQ6taX7wRF1bqExlUgKaD5BGQJtRCkfUsYUFFE/BLbfcMuRDknuI2OBrHL8CE9L48eNrk1MsZikuodLbmHowXjhmxeYIoYb5OkecV199dcjjxA6JzZOONttdSEWxCvl8CFrSCcgb5vUhaFnk+R4fOU5u9+vhiJtjWu5RchtxKCAHmSJPNp+jRo0KgsKCoLoogFDv6xSdaYzGY5V5npQGiiZJdyDNgJxfPpKDzUc2l739TtYRmtGwXlAsKiiAZP5nzle+LSkF/L6smAWPXeOUAzMoONpZYokl0l122aX2NY56p5hiinDsyzHm888/H74+cuTI8OD74qabbkrnmWeecCwLHCVx/HTaaaelhx12WHrJJZeE41GO4jjC4vscc3366ae1I6iTTjop/ctf/lK6d/LYY49Nl1xyyXBsyBHj+uuvX7tW4pNPPkl322239Hvf+176zW9+M91www2/dvzI9VxrrbXSaaaZJvyc/fffP/3888+/dsy52GKLhSP5ueaaK73ooovSqsB44Rqdcsop6bBhw8LY5Gid6z9x4sSWpCXwM95+++1wfEqaDeP6oYceCsefjM3s8z/88MNwrH///fenv//979Nx48alTz75ZDpp0qSWvR6OarmHFlpooXB8zVE298t//dd/VSadwPdY86ln9b7OuM2mNSy66KLp9ddfX/vaF198Uft/UiFIe8jOR/U46KCDwnwYp6fVS1Ooytg1A8cRWjMoOMbdYostknXWWafHrpsoEBEhLGeISnFURESWryuZHyiCoYJXkQEit/x7fCyJXFHFToSXIyuKZfAvZSdO9TT2RliPEfktY1oCR2ocpT344IOhApniDgohKDASRM5I0+A68Hyq6DkaFkQuSNfgWpDicckll4QUEJwABNed51B4QjHVPvvsEwr2uNZVgPHC+OHv5sSAiOlWW22V3HHHHSH1hbSEX/3q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HnXUUeFavPbaa+kNN9wQ+tYPGzas9jO++OKLdMEFF0xHjBgRWrzeeuutoZXmIYccUnsOVdnTTjttesABB6TPPfdcOnr06HSKKaYIzzXlxGMnH9x21pj6WNAa0w+w6po0aVI6ceLErr5eBNrqPS666KLw/TfffDOI1+9973vp1FNPnc4999xBlNI3Pub1119P11xzzXSaaaZJp59++nS//far2Z6JCRMmpIsuumg61VRTBVGs32HKiceOMaaTuPWtMcYYY4wpNc6hNcYYY4wxpcaC1hhTeM4+++xk4YUXDoVlPCgeo0BG0AADqygM53GkoJgv66Lw5ptvhna7tDv+93//9+SAAw6oeXeKu+66K1l88cWDvRsew3FBYFHwtTDGmK9jQWuMKTxYhx1//PHJY489ljz66KPJqquumqy//vq1zmOjRo1KbrzxxtA5iW5KkyZNSjbccMPav//HP/4RxOxnn32W3H///ckll1wSxOrhhx9ee85rr70WnrPKKqsE5waaSuywww7JbbfdlhQJXwtjjKlDRzN4jTGmSehjf/7556d//vOf0ymnnDK97rrrat/DOYHp7YEHHgif33LLLenkk0+evvfee7XnnH322el3vvOd9NNPPw2fH3jggcHFImaTTTZJV1999cK/R74Wxpiq4witMaZUEG29+uqrk48++iikHhC1/fzzz5PVVlut9pz5558/mXXWWZMHHnggfM5HuprF7XZXX3310GpXUV6eE/8MPUc/o4j4WhhjzFe49a0xphTQrAEBS74sebI0f6BRA+kBNLn47ne/2+P5iNf33nsv/D8fYzGr7+t7vT0H0fvJJ58UqoOZr4UxxvTEgtYYUwrokIR4/fDDD5Prr78+2XrrrUO+bBXxtTDGmJ5Y0BpjSgFRWJwHYIkllkgeeeSR5LTTTks22WSTUOxFr/s4SovLwZAhQ8L/8/Hhhx/u8fPkghA/J+uMwOe4KhQpOgu+FsYY0xPn0BpjSsmXX36ZfPrpp0HcTjnllMn48eNr33vhhReCTRcpCsBHjunff//92nNuv/32IFZJW9Bz4p+h5+hnFBlfC2NM5el0VZoxxvTFwQcfnN59993pa6+9lj711FPh88kmmywdN25c+P4uu+ySzjrrrOmdd96ZPvroo+myyy4bHuKLL75IF1xwwXTEiBHpk08+md56663p97///fSQQw6pPefVV19Np5122tDKF5eE0aNHp1NMMUV4bpHwtTDGmK9jQWuMKTzbbbddOttss6VTTTVVEKLDhw+viVn45JNP0t122y3YVyFKN9hgg/Tdd9/t8TNef/31dM0110ynmWaadPrpp0/322+/9PPPP+/xnAkTJqSLLrpo+D1zzjlnetFFF6VFw9fCGGO+zmT8p/JhamOMMcYYU1qcQ2uMMcYYY0qNBa0xxhhjjCk1FrTGGGOMMabUWNAaY4wxxphSY0FrjDHGGGNKjQWtMcYYY4wpNRa0xhhjjDGm1FjQGmOMMcaYUmNBa4wxxhhjSo0FrTHGGGOMKTUWtMYYY4wxptRY0BpjjDHGmFJjQWuMMcYYY0qNBa0xxhhjjCk1FrTGGGOMMabUWNAaY4wxxphSY0FrjDHGGGNKjQWtMcYYY4wpNRa0xhhjjDGm1FjQGmOMMcaYUmNBa4wxxhhjSo0FrTHGGGOMKTUWtMYYY4wxptRY0BpjjDHGmFJjQWuMMcYYY0qNBa0xxhhjjCk1FrTGGGOMMabUWNAaY4wxxphSY0FrjDHGGGNKjQWtMcYYY4wpNRa0xhhjjDGm1FjQGmOMMcaYUmNBa4wxxhhjSo0FrTHGGGOMKTUWtMYYY4wxptRY0BpjjDHGmFJjQWuMMcYYY0qNBa0xxhhjjCk1FrTGGGOMMabUWNAaY4wxxphSY0FrjDHGGGNKjQWtMcYYY4wpNRa0xhhjjDGm1FjQGmOMMcaYUmNBa4wxxhhjSo0FrTHGGGOMKTUWtMYYY4wxptRY0BpjjDHGmFJjQWuMMcYYY0qNBa0xxhhjjCk1FrTGGGOMMabUWNAaY4wxxphSY0FrjDHGGGNKjQWtMcYYY4wpNRa0xhhjjDGm1FjQGmOMMcaYUmNBa4wxxhhjSo0FrTHGGGOMKTUWtMYYY4wxptRY0BpjjDHGmFJjQWuMMcYYY0qNBa0xxhhjjCk1FrTGGGOMMabUWNAaY4wxxphSY0FrjDHGGGNKjQWtMcYYY4wpNRa0xhhjjDGm1FjQGmOMMcaYUmNBa4wxxhhjSo0FrTHGGGOMKTUWtMYYY4wxJikz/wclNe4rpu9iNQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -587,6 +608,76 @@ "\n", "comparison.trajectories_3d(legend=True)" ] + }, + { + "cell_type": "markdown", + "id": "1dd1f037", + "metadata": {}, + "source": [ + "## 10) `FlightBody` is not yet supported for Flight execution\n", + "\n", + "`FlightBody` is a lightweight value object satisfying the internal `BodyLike`\n", + "interface, useful for mission-level bookkeeping (e.g. `Mission.describe()`).\n", + "It does not yet implement everything `Flight` needs to integrate a real\n", + "trajectory (`total_mass()`, `add_motor()`, ...), so `MissionExecutor` raises a\n", + "clear error instead of silently producing an incorrect or empty flight if a\n", + "bare `FlightBody` is used as a mission item's body. This is a deliberate,\n", + "temporary limitation – see the mission-architecture roadmap for planned\n", + "`Flight`/`FlightBranch` integration for `FlightBody`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "bd79bb8f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T15:57:44.449753Z", + "iopub.status.busy": "2026-07-12T15:57:44.449615Z", + "iopub.status.idle": "2026-07-12T15:57:44.453788Z", + "shell.execute_reply": "2026-07-12T15:57:44.453224Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got expected error: Cannot build a Flight from FlightBody 'Unsupported Demo Body': FlightBody does not implement total_mass()/add_motor() and cannot drive Flight's physics engine directly yet. Use a Rocket (optionally wrapped in RocketAdapter) for real flight execution; FlightBody-driven Flight integration is planned future work.\n" + ] + } + ], + "source": [ + "demo_body = FlightBody(\n", + " name=\"Unsupported Demo Body\",\n", + " geometry=0.05,\n", + " mass_model=lambda t: 1.0,\n", + " inertia_model=lambda t: (0.01, 0.01, 0.001, 0.0, 0.0, 0.0),\n", + " center_of_mass_model=lambda t: 0.0,\n", + ")\n", + "demo_mission = Mission(name=\"FlightBody Guard Demo\")\n", + "demo_mission.add_deployable(\n", + " Deployable(\n", + " name=\"unsupported_payload\",\n", + " body=demo_body,\n", + " attachment=Attachment(\n", + " parent_frame_position=[0.0, 0.0, 0.0],\n", + " child_frame_position=[0.0, 0.0, 0.0],\n", + " ),\n", + " deployment_event=DeploymentEvent(\n", + " name=\"Demo Deployment\", trigger=lambda state, context: False\n", + " ),\n", + " )\n", + ")\n", + "demo_executor = MissionExecutor(\n", + " mission=demo_mission, environment=env, rail_length=5.2\n", + ")\n", + "\n", + "try:\n", + " demo_executor.execute()\n", + "except TypeError as error:\n", + " print(f\"Got expected error: {error}\")" + ] } ], "metadata": { @@ -605,7 +696,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.14.0" } }, "nbformat": 4, diff --git a/rocketpy/__init__.py b/rocketpy/__init__.py index d353c30f1..a743c660a 100644 --- a/rocketpy/__init__.py +++ b/rocketpy/__init__.py @@ -1,4 +1,10 @@ -from .body import BodyLike, FlightBody, RocketAdapter +from .body import ( + BodyLike, + BodyResolver, + FlightBody, + FlightCompatibleRocket, + RocketAdapter, +) from .control import _Controller from .environment import Environment, EnvironmentAnalysis from .mathutils import ( @@ -9,14 +15,17 @@ ) from .mission import ( Attachment, + BranchResult, Deployable, DeploymentEvent, Event, + FlightConfig, IgnitionEvent, InstantaneousSeparation, Mission, MissionExecutionResult, MissionExecutor, + MissionResult, NoOpParentUpdate, ParentUpdate, RecoveryEvent, diff --git a/rocketpy/body/__init__.py b/rocketpy/body/__init__.py index 9c26820fa..80b7cd83d 100644 --- a/rocketpy/body/__init__.py +++ b/rocketpy/body/__init__.py @@ -1,7 +1,14 @@ """rocketpy.body – body abstraction layer for multistage simulation.""" from rocketpy.body.body_like import BodyLike +from rocketpy.body.body_resolver import BodyResolver, FlightCompatibleRocket from rocketpy.body.flight_body import FlightBody from rocketpy.body.rocket_adapter import RocketAdapter -__all__ = ["BodyLike", "FlightBody", "RocketAdapter"] +__all__ = [ + "BodyLike", + "BodyResolver", + "FlightBody", + "FlightCompatibleRocket", + "RocketAdapter", +] diff --git a/rocketpy/body/body_resolver.py b/rocketpy/body/body_resolver.py new file mode 100644 index 000000000..852bb93b4 --- /dev/null +++ b/rocketpy/body/body_resolver.py @@ -0,0 +1,114 @@ +"""BodyResolver – resolves mission bodies to BodyLike / Flight-compatible rockets.""" + +from typing import Any, Protocol, runtime_checkable + +from rocketpy.body.body_like import BodyLike +from rocketpy.body.flight_body import FlightBody +from rocketpy.body.rocket_adapter import RocketAdapter + + +@runtime_checkable +class FlightCompatibleRocket(Protocol): + """Structural contract for rocket objects accepted by Flight.""" + + def add_motor(self, motor, position): + """Add a motor to the rocket body.""" + + def total_mass(self, t: float) -> float: + """Return total mass at time *t*.""" + + def center_of_mass(self, t: float) -> float: + """Return center-of-mass position at time *t*.""" + + +class BodyResolver: + """Resolves mission bodies (``Rocket``, ``RocketAdapter``, ``FlightBody``, ...) + into the shapes the simulation engine needs. + + This is a two-step resolver: + + 1. :meth:`to_body` resolves any supported source into a :class:`BodyLike`. + This step is intentionally permissive – a bare :class:`FlightBody` is a + legitimate ``BodyLike`` for mission-level bookkeeping (e.g. + :meth:`~rocketpy.mission.Mission.describe`), even though it cannot yet + drive a real :class:`~rocketpy.simulation.Flight`. + 2. :meth:`to_flight_rocket` takes a resolved :class:`BodyLike` and returns + an object that can be passed as ``Flight(rocket=...)``. This is where + bodies that cannot actually fly (today, a bare :class:`FlightBody`) are + rejected with a clear, actionable error instead of failing silently or + deep inside ``Flight``'s constructor. + """ + + @staticmethod + def to_body(source: Any) -> BodyLike: + """Resolve *source* to a :class:`BodyLike`. + + Parameters + ---------- + source : Rocket, RocketAdapter, FlightBody, or FlightCompatibleRocket + The mission body to resolve. + + Returns + ------- + BodyLike + A body satisfying the :class:`BodyLike` interface. + + Raises + ------ + TypeError + If *source* cannot be resolved to a :class:`BodyLike`. + """ + if isinstance(source, (RocketAdapter, FlightBody)): + return source + if isinstance(source, BodyLike): + return source + if hasattr(source, "as_body"): + return source.as_body() + if isinstance(source, FlightCompatibleRocket): + return RocketAdapter(source) + raise TypeError( + f"BodyResolver cannot resolve {type(source).__name__!r} to a BodyLike. " + "Supported sources: Rocket, RocketAdapter, FlightBody, or objects " + "satisfying FlightCompatibleRocket." + ) + + @staticmethod + def to_flight_rocket(body: BodyLike): + """Return a Flight-compatible rocket object for *body*. + + Parameters + ---------- + body : BodyLike + A body previously resolved via :meth:`to_body`. + + Returns + ------- + FlightCompatibleRocket + An object that can be passed as ``Flight(rocket=...)``. + + Raises + ------ + TypeError + If *body* cannot drive a :class:`~rocketpy.simulation.Flight` yet. + This is always the case for a bare :class:`FlightBody`, since it + does not implement ``total_mass()``/``add_motor()``. + """ + if isinstance(body, RocketAdapter): + return body.rocket + if isinstance(body, FlightCompatibleRocket): + return body + if isinstance(body, FlightBody): + raise TypeError( + f"Cannot build a Flight from FlightBody {body.name!r}: FlightBody " + "does not implement total_mass()/add_motor() and cannot drive " + "Flight's physics engine directly yet. Use a Rocket (optionally " + "wrapped in RocketAdapter) for real flight execution; " + "FlightBody-driven Flight integration is planned future work." + ) + raise TypeError( + f"BodyResolver cannot use {type(body).__name__!r} to construct a " + "Flight. Supported bodies: a Rocket (optionally wrapped in " + "RocketAdapter), or any object satisfying FlightCompatibleRocket " + "(add_motor, total_mass, center_of_mass). A bare FlightBody is also " + "not yet supported for Flight execution." + ) diff --git a/rocketpy/mission/__init__.py b/rocketpy/mission/__init__.py index e5a02f646..ffb45c25e 100644 --- a/rocketpy/mission/__init__.py +++ b/rocketpy/mission/__init__.py @@ -2,6 +2,7 @@ from rocketpy.mission.attached_item import AttachedItem from rocketpy.mission.attachment import Attachment +from rocketpy.mission.branch_result import BranchResult from rocketpy.mission.deployable import Deployable from rocketpy.mission.events import ( DeploymentEvent, @@ -10,8 +11,10 @@ RecoveryEvent, StageSeparationEvent, ) +from rocketpy.mission.flight_config import FlightConfig from rocketpy.mission.mission import Mission from rocketpy.mission.mission_executor import MissionExecutionResult, MissionExecutor +from rocketpy.mission.mission_result import MissionResult from rocketpy.mission.parent_update import NoOpParentUpdate, ParentUpdate from rocketpy.mission.separation_model import InstantaneousSeparation, SeparationModel from rocketpy.mission.stage import Stage @@ -20,14 +23,17 @@ __all__ = [ "AttachedItem", "Attachment", + "BranchResult", "Deployable", "DeploymentEvent", "Event", + "FlightConfig", "IgnitionEvent", "InstantaneousSeparation", "Mission", "MissionExecutionResult", "MissionExecutor", + "MissionResult", "NoOpParentUpdate", "ParentUpdate", "RecoveryEvent", diff --git a/rocketpy/mission/branch_result.py b/rocketpy/mission/branch_result.py new file mode 100644 index 000000000..a14433c64 --- /dev/null +++ b/rocketpy/mission/branch_result.py @@ -0,0 +1,39 @@ +"""BranchResult – execution record for a single mission item's flight.""" + +from dataclasses import dataclass +from typing import Any + + +@dataclass +class BranchResult: + """Execution record for one mission item's :class:`~rocketpy.simulation.Flight`. + + Parameters + ---------- + name : str + Name of the attached item this branch was built for. + flight : :class:`~rocketpy.simulation.Flight` + The simulated flight for this branch. + item : :class:`~rocketpy.mission.AttachedItem` + The mission item (``Stage`` or ``Deployable``) this branch was built + from. + event_time : float + Simulation time, in seconds, at which this branch's flight begins. + initial_solution : list + Initial solution state vector used to start this branch's flight. + Empty when the flight started from a fresh launch-rail state. + + Attributes + ---------- + name : str + flight : Flight + item : AttachedItem + event_time : float + initial_solution : list + """ + + name: str + flight: Any + item: Any + event_time: float + initial_solution: list diff --git a/rocketpy/mission/flight_config.py b/rocketpy/mission/flight_config.py new file mode 100644 index 000000000..c6ee8645e --- /dev/null +++ b/rocketpy/mission/flight_config.py @@ -0,0 +1,130 @@ +"""FlightConfig – typed per-branch Flight construction parameters.""" + +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class FlightConfig: + """Typed configuration for constructing a single mission branch's Flight. + + A :class:`FlightConfig` mirrors the subset of + :class:`~rocketpy.simulation.Flight` constructor arguments that mission + items are expected to override on a per-branch basis. It replaces ad hoc + keyword-argument dicts as the value passed around by + :class:`~rocketpy.mission.MissionExecutor`. + + Parameters + ---------- + rail_length : float + Length in meters of the launch rail for this branch. + inclination : float, optional + Rail inclination relative to the ground, in degrees. Defaults to + ``80.0``. + heading : float, optional + Launch heading relative to north, in degrees. Defaults to ``90.0``. + initial_solution : object, optional + Initial solution passed to ``Flight``. May be a state vector or a + previously built ``Flight`` (chained by + :class:`~rocketpy.mission.MissionExecutor`). Defaults to ``None``. + max_time : float, optional + Maximum simulation time, in seconds. Defaults to ``600``. + terminate_on_apogee : bool, optional + Whether to stop the simulation at apogee. Defaults to ``False``. + time_overshoot : bool, optional + Whether ``Flight`` is allowed to overshoot requested time steps. + Defaults to ``True``. + extra_kwargs : dict, optional + Any additional keyword arguments forwarded to ``Flight`` verbatim. + + Attributes + ---------- + rail_length : float + inclination : float + heading : float + initial_solution : object or None + max_time : float + terminate_on_apogee : bool + time_overshoot : bool + extra_kwargs : dict + """ + + rail_length: float + inclination: float = 80.0 + heading: float = 90.0 + initial_solution: Any = None + max_time: float = 600 + terminate_on_apogee: bool = False + time_overshoot: bool = True + extra_kwargs: dict = field(default_factory=dict) + + _KNOWN_FIELDS = ( + "rail_length", + "inclination", + "heading", + "initial_solution", + "max_time", + "terminate_on_apogee", + "time_overshoot", + ) + + def to_flight_kwargs(self) -> dict: + """Return keyword arguments to pass to ``Flight`` (excluding ``rail_length``, + which :class:`~rocketpy.mission.MissionExecutor` passes separately + alongside ``rocket``/``environment``). + + Returns + ------- + dict + Merged mapping of ``inclination``, ``heading``, + ``initial_solution``, ``max_time``, ``terminate_on_apogee``, + ``time_overshoot``, and any entries from :attr:`extra_kwargs`. + """ + kwargs = { + "inclination": self.inclination, + "heading": self.heading, + "initial_solution": self.initial_solution, + "max_time": self.max_time, + "terminate_on_apogee": self.terminate_on_apogee, + "time_overshoot": self.time_overshoot, + } + kwargs.update(self.extra_kwargs) + return kwargs + + def for_branch(self, name: str, overrides: dict = None) -> "FlightConfig": + """Return a new :class:`FlightConfig` with *overrides* applied on top. + + Parameters + ---------- + name : str + Name of the branch these overrides apply to. Used only for + error messages; it is not stored on the returned config. + overrides : dict, optional + Values to override. Keys matching a known :class:`FlightConfig` + field are applied directly; any other keys are merged into the + returned config's :attr:`extra_kwargs`. + + Returns + ------- + FlightConfig + A new, independent :class:`FlightConfig` instance. + """ + overrides = dict(overrides or {}) + known = {k: v for k, v in overrides.items() if k in self._KNOWN_FIELDS} + extra = {k: v for k, v in overrides.items() if k not in self._KNOWN_FIELDS} + + merged_extra = dict(self.extra_kwargs) + merged_extra.update(extra) + + return FlightConfig( + rail_length=known.get("rail_length", self.rail_length), + inclination=known.get("inclination", self.inclination), + heading=known.get("heading", self.heading), + initial_solution=known.get("initial_solution", self.initial_solution), + max_time=known.get("max_time", self.max_time), + terminate_on_apogee=known.get( + "terminate_on_apogee", self.terminate_on_apogee + ), + time_overshoot=known.get("time_overshoot", self.time_overshoot), + extra_kwargs=merged_extra, + ) diff --git a/rocketpy/mission/mission.py b/rocketpy/mission/mission.py index f24af7210..4c13e4d58 100644 --- a/rocketpy/mission/mission.py +++ b/rocketpy/mission/mission.py @@ -1,9 +1,10 @@ """Mission – top-level container for a multistage rocket mission.""" +import warnings from typing import Any -class Mission: +class Mission: # pylint: disable=too-many-instance-attributes """Container that stores all mission items for a multistage flight. A :class:`Mission` groups together all the :class:`~rocketpy.mission.Stage` @@ -17,11 +18,18 @@ class Mission: name : str, optional Human-readable name for the mission (e.g. ``"Falcon9_Demo"``). Defaults to ``"Mission"``. + root_vehicle : Rocket or FlightBody, optional + The assembled vehicle before any stage/deployable event occurs. + Defaults to ``None``. Attributes ---------- name : str Mission name. + root_vehicle : Rocket, FlightBody, or None + The assembled vehicle flown before any stage/deployable event. + Not part of :meth:`attached_items` – it is the vehicle that + attached items detach *from*. stages : list[:class:`~rocketpy.mission.Stage`] Ordered list of rocket stages (first-to-fire first). deployables : list[:class:`~rocketpy.mission.Deployable`] @@ -37,11 +45,13 @@ class Mission: 0 """ - def __init__(self, name: str = "Mission"): + def __init__(self, name: str = "Mission", root_vehicle=None): self.name = name + self.root_vehicle = root_vehicle self.stages: list = [] self.deployables: list = [] self._flight_inputs: dict[str, dict[str, Any]] = {} + self._root_flight_inputs: dict[str, Any] = {} # ------------------------------------------------------------------ # Mutating helpers @@ -98,6 +108,39 @@ def add_deployable(self, deployable): self.deployables.append(deployable) self._flight_inputs.setdefault(self._item_key(deployable), {}) + def set_root_vehicle(self, vehicle): + """Set the assembled vehicle flown before any stage/deployable event. + + Parameters + ---------- + vehicle : Rocket or FlightBody + The root vehicle. Unlike stages/deployables, it is not wrapped + in an :class:`~rocketpy.mission.AttachedItem` and is not + returned by :meth:`attached_items`. + """ + self.root_vehicle = vehicle + + def set_root_flight_inputs(self, **flight_inputs): + """Set flight inputs used for the root vehicle's flight. + + Parameters + ---------- + **flight_inputs + Keyword arguments forwarded to ``rocketpy.simulation.Flight`` + when the root vehicle is executed. + """ + self._root_flight_inputs.update(flight_inputs) + + def get_root_flight_inputs(self) -> dict[str, Any]: + """Return configured flight inputs for the root vehicle. + + Returns + ------- + dict[str, Any] + Copy of configured inputs. Empty dict when not configured. + """ + return dict(self._root_flight_inputs) + # ------------------------------------------------------------------ # Query helpers # ------------------------------------------------------------------ @@ -181,6 +224,81 @@ def get_flight_inputs(self, item) -> dict[str, Any]: key = self._resolve_item_key(item) return dict(self._flight_inputs.get(key, {})) + def describe(self) -> str: + """Return a human-readable, multi-line summary of this mission. + + Returns + ------- + str + Summary including the mission name, root vehicle, stage states, + deployables, and configured per-item flight inputs. + """ + lines = [f"Mission: {self.name!r}"] + if self.root_vehicle is None: + lines.append("root_vehicle: not set") + else: + root_name = getattr(self.root_vehicle, "name", None) + lines.append( + f"root_vehicle: {type(self.root_vehicle).__name__}" + + (f" (name={root_name!r})" if root_name else "") + ) + lines.append(f"stages ({self.number_of_stages}):") + for stage in self.stages: + lines.append(f" - {stage.name!r}: state={stage.state.name}") + lines.append(f"deployables ({len(self.deployables)}):") + for deployable in self.deployables: + lines.append(f" - {deployable.name!r}") + for item in self.attached_items(): + inputs = self.get_flight_inputs(item) + if inputs: + lines.append(f"flight_inputs[{item.name!r}]: {inputs}") + return "\n".join(lines) + + def validate(self, require_root_vehicle: bool = False): + """Validate the whole mission's configuration. + + Re-validates every attached item and checks mission-wide invariants + that individual item validation cannot catch, such as duplicate + item names and root vehicle presence. + + Parameters + ---------- + require_root_vehicle : bool, optional + If ``True``, raise when :attr:`root_vehicle` is unset instead of + only warning. Defaults to ``False``. + + Raises + ------ + ValueError + If any attached item fails validation, if two items share the + same name, or if *require_root_vehicle* is ``True`` and + :attr:`root_vehicle` is unset. + """ + errors = [] + + names_seen = set() + for item in self.attached_items(): + if item.name in names_seen: + errors.append(f"Duplicate attached item name: {item.name!r}.") + names_seen.add(item.name) + + try: + item.validate() + except ValueError as exc: + errors.append(f"{item.name!r}: {exc}") + + if errors: + raise ValueError( + f"Mission {self.name!r} failed validation:\n" + + "\n".join(f" - {error}" for error in errors) + ) + + if self.root_vehicle is None: + message = f"Mission {self.name!r} has no root_vehicle set." + if require_root_vehicle: + raise ValueError(message) + warnings.warn(message) + def _item_key(self, item) -> str: # Internal opaque identifier used only as dict key to avoid collisions # between Stage and Deployable items sharing the same name. diff --git a/rocketpy/mission/mission_executor.py b/rocketpy/mission/mission_executor.py index 0de331ecb..174ee26e3 100644 --- a/rocketpy/mission/mission_executor.py +++ b/rocketpy/mission/mission_executor.py @@ -1,28 +1,24 @@ """MissionExecutor – high-level API to execute all mission flight branches.""" from dataclasses import dataclass -from typing import Any, Protocol, runtime_checkable -from rocketpy.body import FlightBody, RocketAdapter +from rocketpy.body import BodyResolver, FlightCompatibleRocket +from rocketpy.mission.branch_result import BranchResult +from rocketpy.mission.flight_config import FlightConfig +from rocketpy.mission.mission_result import MissionResult +from rocketpy.mission.stage import Stage +from rocketpy.mission.stage_state import StageState from rocketpy.simulation import Flight -@runtime_checkable -class FlightCompatibleRocket(Protocol): - """Structural contract for rocket objects accepted by Flight.""" - - def add_motor(self, motor, position): - """Add a motor to the rocket body.""" - - def total_mass(self, t: float) -> float: - """Return total mass at time *t*.""" - - def center_of_mass(self, t: float) -> float: - """Return center-of-mass position at time *t*.""" - @dataclass(frozen=True) class MissionExecutionResult: - """Single execution output for one mission item.""" + """Single execution output for one mission item. + + .. deprecated:: + Kept for backward compatibility with :meth:`MissionExecutor.execute`. + Use :meth:`MissionExecutor.run` and :class:`BranchResult` instead. + """ item_name: str flight: Flight @@ -33,7 +29,15 @@ class MissionExecutor: The executor provides a mission-first API: users configure mission items and optional per-item flight inputs in :class:`~rocketpy.mission.Mission`, - then call :meth:`execute` once to run all corresponding flight simulations. + then call :meth:`run` once to run all corresponding flight simulations. + + Each mission item's flight is built independently and chained + sequentially: an item's flight starts from the previous flight's final + state unless an explicit ``initial_solution`` is configured for it. This + is a sequential approximation of staging – it does not simulate + diverging trajectories (e.g. a spent stage falling away while the next + stage ascends) as two separate, simultaneous flights. True branching + physics is future-phase work; see :class:`~rocketpy.simulation.FlightBranch`. """ def __init__( @@ -50,48 +54,141 @@ def __init__( self.default_flight_inputs = dict(default_flight_inputs or {}) self.flight_class = flight_class - def execute(self) -> list[MissionExecutionResult]: + def run(self) -> MissionResult: + """Execute the root vehicle (if any) and all mission attached items. + + Returns + ------- + MissionResult + Aggregated result containing the root flight (if + ``mission.root_vehicle`` is set) and one branch per attached + item, in mission order: stages first, then deployables. + """ + base_config = self._base_config() + + root_flight = None + if self.mission.root_vehicle is not None: + root_config = base_config.for_branch( + "root", self.mission.get_root_flight_inputs() + ) + root_flight = self._build_flight(self.mission.root_vehicle, root_config) + + branch_results = [] + previous_flight = root_flight + for item in self.mission.attached_items(): + branch = self._build_branch(item, base_config, previous_flight) + branch_results.append(branch) + previous_flight = branch.flight + + return MissionResult( + root_flight=root_flight, + branch_flights={branch.name: branch.flight for branch in branch_results}, + branch_results=branch_results, + ) + + def dry_run(self) -> list: + """Resolve every mission body and flight configuration without simulating. + + Validates that every mission body (including the root vehicle, if + set) can drive a real :class:`~rocketpy.simulation.Flight`, and + resolves the :class:`FlightConfig` each item would be built with. + This lets callers catch body-compatibility errors (e.g. an + unsupported :class:`~rocketpy.body.FlightBody`) or misconfigured + flight inputs before paying for any simulation. + + Returns + ------- + list[FlightConfig] + Resolved configs in the same order :meth:`run` would build + flights: the root vehicle's config first (if set), then one per + attached item. + """ + base_config = self._base_config() + configs = [] + + if self.mission.root_vehicle is not None: + BodyResolver.to_flight_rocket( + BodyResolver.to_body(self.mission.root_vehicle) + ) + configs.append( + base_config.for_branch("root", self.mission.get_root_flight_inputs()) + ) + + for item in self.mission.attached_items(): + BodyResolver.to_flight_rocket(BodyResolver.to_body(item.body)) + configs.append( + base_config.for_branch(item.name, self.mission.get_flight_inputs(item)) + ) + + return configs + + def execute(self) -> list: """Execute configured mission stage/deployable flights in order. + .. deprecated:: + Use :meth:`run` instead, which also builds the root vehicle's + flight and returns a richer :class:`~rocketpy.mission.MissionResult`. + Returns ------- list[MissionExecutionResult] Flight results in mission order: stages first, then deployables. """ - results: list[MissionExecutionResult] = [] + base_config = self._base_config() + results = [] previous_flight = None for item in self.mission.attached_items(): - rocket = self._extract_rocket(item.body) - item_inputs = self.mission.get_flight_inputs(item) - flight_inputs = dict(self.default_flight_inputs) - flight_inputs.update(item_inputs) - - if "initial_solution" not in flight_inputs and previous_flight is not None: - flight_inputs["initial_solution"] = previous_flight - - flight = self.flight_class( - rocket=rocket, - environment=self.environment, - rail_length=self.rail_length, - **flight_inputs, + branch = self._build_branch(item, base_config, previous_flight) + results.append( + MissionExecutionResult(item_name=branch.name, flight=branch.flight) ) - results.append(MissionExecutionResult(item_name=item.name, flight=flight)) - previous_flight = flight + previous_flight = branch.flight return results - @staticmethod - def _extract_rocket(body: Any): - """Extract a Flight-compatible rocket object from a mission body.""" - if isinstance(body, RocketAdapter): - return body.rocket - if isinstance(body, FlightBody): - return body - if isinstance(body, FlightCompatibleRocket): - return body - raise TypeError( - "MissionExecutor currently supports Stage/Deployable bodies backed " - "by RocketAdapter, FlightBody, or by objects satisfying " - "FlightCompatibleRocket." + def _base_config(self) -> FlightConfig: + """Build the base :class:`FlightConfig` from ``default_flight_inputs``.""" + return FlightConfig(rail_length=self.rail_length).for_branch( + "default", self.default_flight_inputs + ) + + def _build_flight(self, body, config: FlightConfig): + """Resolve *body* and construct a :class:`~rocketpy.simulation.Flight`.""" + rocket = BodyResolver.to_flight_rocket(BodyResolver.to_body(body)) + return self.flight_class( + rocket=rocket, + environment=self.environment, + rail_length=config.rail_length, + **config.to_flight_kwargs(), + ) + + def _build_branch( + self, item, base_config: FlightConfig, previous_flight + ) -> BranchResult: + """Build a single :class:`BranchResult` for *item*, chaining from + *previous_flight* when the item has no explicit ``initial_solution``. + """ + config = base_config.for_branch(item.name, self.mission.get_flight_inputs(item)) + if config.initial_solution is None and previous_flight is not None: + config.initial_solution = previous_flight + + flight = self._build_flight(item.body, config) + + if isinstance(item, Stage): + # Mission-level bookkeeping only: the sequential model does not + # simulate real staging events, so a stage is considered SPENT + # once its (fully independent) branch flight has been built. + item.state = StageState.SPENT + + return BranchResult( + name=item.name, + flight=flight, + item=item, + event_time=flight.t_initial, + initial_solution=( + list(flight.initial_solution) + if flight.initial_solution is not None + else [] + ), ) diff --git a/rocketpy/mission/mission_result.py b/rocketpy/mission/mission_result.py new file mode 100644 index 000000000..86d439762 --- /dev/null +++ b/rocketpy/mission/mission_result.py @@ -0,0 +1,131 @@ +"""MissionResult – aggregated output of a full MissionExecutor.run().""" + +from dataclasses import dataclass, field +from typing import Any + +from rocketpy.mission.branch_result import BranchResult + + +@dataclass +class MissionResult: + """Aggregated result of running a :class:`~rocketpy.mission.Mission`. + + Parameters + ---------- + root_flight : :class:`~rocketpy.simulation.Flight` or None + Flight of the root vehicle, or ``None`` if the mission has no + ``root_vehicle`` set. + branch_flights : dict[str, Flight] + Mapping of attached item name to its simulated flight, in mission + order. + branch_results : list[:class:`BranchResult`] + Ordered, richer per-item execution records. + + Attributes + ---------- + root_flight : Flight or None + branch_flights : dict[str, Flight] + branch_results : list[BranchResult] + """ + + root_flight: Any + branch_flights: dict + branch_results: list = field(default_factory=list) + + def all_flights(self) -> list: + """Return every flight produced by this mission, in mission order. + + Returns + ------- + list[Flight] + ``[root_flight]`` (if set) followed by each branch's flight. + """ + flights = [] + if self.root_flight is not None: + flights.append(self.root_flight) + flights.extend(branch.flight for branch in self.branch_results) + return flights + + def get_flight(self, name: str): + """Return the flight for the given name. + + Parameters + ---------- + name : str + ``"root"`` for the root vehicle's flight, or an attached item's + name. + + Returns + ------- + Flight + The matching flight. + + Raises + ------ + KeyError + If *name* is ``"root"`` but no root flight exists, or if *name* + does not match any branch. + """ + if name == "root": + if self.root_flight is None: + raise KeyError("Mission has no root_flight.") + return self.root_flight + return self.branch_flights[name] + + def get_branch_result(self, name: str) -> BranchResult: + """Return the :class:`BranchResult` for the given attached item name. + + Parameters + ---------- + name : str + Attached item name. + + Returns + ------- + BranchResult + The matching branch result. + + Raises + ------ + KeyError + If *name* does not match any branch. + """ + for branch in self.branch_results: + if branch.name == name: + return branch + raise KeyError(f"No branch result named {name!r}.") + + def summary(self) -> str: + """Return a one-line-per-flight human-readable summary. + + Returns + ------- + str + For each flight: name, apogee, and impact velocity. + """ + lines = [] + if self.root_flight is not None: + lines.append( + f"root: apogee = {self.root_flight.apogee:.2f} m, " + f"impact velocity = {self.root_flight.impact_velocity:.2f} m/s" + ) + for branch in self.branch_results: + lines.append( + f"{branch.name}: apogee = {branch.flight.apogee:.2f} m, " + f"impact velocity = {branch.flight.impact_velocity:.2f} m/s" + ) + return "\n".join(lines) + + def plot_all(self): + """Plot the 3D trajectories of every flight in this mission result. + + Returns + ------- + CompareFlights + The comparison object used to render the plot. + """ + from rocketpy.plots import CompareFlights # local: avoid import-time dep + + comparison = CompareFlights(self.all_flights()) + comparison.trajectories_3d(legend=True) + return comparison diff --git a/test_nb.py b/test_nb.py new file mode 100644 index 000000000..601ebc84f --- /dev/null +++ b/test_nb.py @@ -0,0 +1,184 @@ +import datetime as dt +from pathlib import Path + +import rocketpy as rp +from rocketpy import ( + Attachment, + Deployable, + DeploymentEvent, + Environment, + FlightBody, + IgnitionEvent, + Mission, + MissionExecutor, + Parachute, + Rocket, + RocketAdapter, + SolidMotor, + Stage, + StageSeparationEvent, +) +from rocketpy.plots.compare import CompareFlights +env = Environment( + latitude=32.990254, + longitude=-106.974998, + elevation=1400, + datum="WGS84", +) + +tomorrow = dt.date.today() + dt.timedelta(days=1) +env.set_date((tomorrow.year, tomorrow.month, tomorrow.day, 12), timezone="UTC") +env.set_atmospheric_model( + type="custom_atmosphere", + pressure=101325, + temperature=298.15, + wind_u=[(0, 3.0), (2000, 5.0), (5000, 8.0)], + wind_v=[(0, -1.0), (2000, -2.0), (5000, -3.5)], +) +env.max_expected_height = 10000 +data_directory = Path(rp.__file__).resolve().parent.parent / "data" +motor_file = data_directory / "motors" / "cesaroni" / "Cesaroni_M1670.eng" +power_off_drag_file = data_directory / "rockets" / "calisto" / "powerOffDragCurve.csv" +power_on_drag_file = data_directory / "rockets" / "calisto" / "powerOnDragCurve.csv" + +first_stage_motor = SolidMotor( + thrust_source=str(motor_file), + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", +) + +second_stage_motor = SolidMotor( + thrust_source=str(motor_file), + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", +) + +first_stage_rocket = Rocket( + radius=0.0635, + mass=14.426, + inertia=(6.321, 6.321, 0.034), + power_off_drag=str(power_off_drag_file), + power_on_drag=str(power_on_drag_file), + center_of_mass_without_motor=0, + coordinate_system_orientation="tail_to_nose", +) +first_stage_rocket.name = "First Stage" +first_stage_rocket.add_motor(first_stage_motor, position=-1.255) +first_stage_rocket.set_rail_buttons( + upper_button_position=0.0818, + lower_button_position=-0.618, + angular_position=45, +) +first_stage_rocket.add_nose(length=0.55829, kind="von karman", position=1.278) +first_stage_rocket.add_trapezoidal_fins( + n=4, + root_chord=0.120, + tip_chord=0.060, + span=0.110, + position=-1.04956, + cant_angle=0.5, +) +first_stage_rocket.add_tail( + top_radius=0.0635, + bottom_radius=0.0435, + length=0.060, + position=-1.194656, +) + +second_stage_rocket = Rocket( + radius=0.0635, + mass=9.500, + inertia=(6.321, 6.321, 0.034), + power_off_drag=str(power_off_drag_file), + power_on_drag=str(power_on_drag_file), + center_of_mass_without_motor=0, + coordinate_system_orientation="tail_to_nose", +) +second_stage_rocket.name = "Second Stage" +second_stage_rocket.add_motor(second_stage_motor, position=-1.000) +second_stage_rocket.add_nose(length=0.55829, kind="von karman", position=1.050) +second_stage_rocket.add_trapezoidal_fins( + n=4, + root_chord=0.120, + tip_chord=0.060, + span=0.110, + position=-0.920, + cant_angle=0.5, +) +second_stage_rocket.add_tail( + top_radius=0.0635, + bottom_radius=0.0435, + length=0.060, + position=-1.194656, +) + +payload_body = FlightBody( + name="Payload", + geometry=0.055, + mass_model=lambda t: 4.5, + inertia_model=lambda t: (0.25, 0.25, 0.03, 0.0, 0.0, 0.0), + center_of_mass_model=lambda t: 0.0, + recovery_systems=[ + Parachute( + name="Payload Drogue", + cd_s=0.35, + trigger="apogee", + sampling_rate=105, + lag=1.0, + noise=(0, 4.0, 0.2), + ), + Parachute( + name="Payload Main", + cd_s=3.5, + trigger=700, + sampling_rate=105, + lag=1.0, + noise=(0, 4.0, 0.2), + ), + ], + coordinate_system_orientation="tail_to_nose", +) +# Deploy second-stage parachute at apogee. +# Parachute trigger callbacks can be strings like "apogee" or functions. +second_stage_rocket.add_parachute( + "Second Stage Main", + cd_s=5.0, + trigger="apogee", + sampling_rate=105, + lag=1.2, + noise=(0, 6.0, 0.25), +) + +print(f"Using motor file: {motor_file}") +print(f"Using drag curve files: {power_off_drag_file.name}, {power_on_drag_file.name}") +print(f"Payload body type: {type(payload_body).__name__}") +print(f"Payload dry mass at t=0 s: {payload_body.mass(0):.2f} kg") +print(f"Payload recovery systems: {len(payload_body.recovery_systems())}") diff --git a/test_nb2.py b/test_nb2.py new file mode 100644 index 000000000..803d27337 --- /dev/null +++ b/test_nb2.py @@ -0,0 +1,126 @@ + +import csv + +import cadquery as cq +import numpy as np + +from rocketpy import CylindricalTank, SphericalTank +# Create fixtures geometries +geometry_map = {} +geometry_map["sphere"] = {"spherical_oxidizer_tank": SphericalTank(0.05)} +geometry_map["cylinder"] = { + "cylindrical_oxidizer_tank": CylindricalTank(0.0744, 0.8068, True), + "cylindrical_pressurant_tank": CylindricalTank(0.135 / 2, 0.981, True), +} +def export_expected_parameters(name, datapoints): + with open(f"{name}_expected.csv", mode="w") as output_file: + writer = csv.writer(output_file) + writer.writerow(["level_height", "volume", "center_of_mass", "inertia"]) + for data_row in datapoints: + writer.writerow([*data_row]) +def evaluate_sphere_parameters(radius, level_height): + """Computes the volume, center of mass and inertia + (with respect to the origin) of a sphere 'filled' up + to a certain level height. + + Parameters + ---------- + radius : float + The radius of the sphere. + level_height : float + The height of the liquid inside the sphere. + + Returns + ------- + volume : float + The volume of the sphere. + center_of_mass : float + The center of mass of the sphere. + inertia : float + The inertia of the sphere with respect to the origin. + """ + sphere = cq.Workplane("XY").sphere(radius) + + # Cut the sphere to the level height + drill_height = 10 * radius + sphere = sphere.cut( + cq.Workplane("XY") + .cylinder(drill_height, radius) + .translate((0, 0, drill_height / 2 + level_height)) + ) + + # Uncomment to display the CAD + # display(sphere) + + primitive = sphere.val() + + volume = primitive.Volume() + center_of_mass = primitive.centerOfMass(primitive) + inertia_tensor = primitive.matrixOfInertia(primitive) + + return volume, center_of_mass.z, inertia_tensor[0][0] + volume * center_of_mass.z**2 +for name, sphere_geometry in geometry_map["sphere"].items(): + radius = sphere_geometry.total_height / 2 + datapoints = [] + for h in np.linspace(-radius, radius, 25): + datapoints.append([h, *evaluate_sphere_parameters(radius, h)]) + + export_expected_parameters(name, datapoints) +def evaluate_cylinder_with_caps(total_length, radius, level_height): + """Computes the volume, center of mass and inertia (with respect + to the origin) of a cylinder with spherical caps 'filled' up to a + certain level height. + + Parameters + ---------- + total_length : float + The total length of the cylinder (with caps). + radius : float + The radius of the cylinder. + + Returns + ------- + volume : float + The volume of the cylinder with caps. + center_of_mass : float + The z-coordinate of the center of mass. + inertia : float + The inertia of the cylinder with caps with respect to the origin. + """ + cylinder_height = total_length - 2 * radius + + cylinder = cq.Workplane("XY").cylinder(cylinder_height, radius) + top_cap = ( + cq.Workplane("XY").sphere(radius).translate((0, 0, total_length / 2 - radius)) + ) + bottom_cap = ( + cq.Workplane("XY").sphere(radius).translate((0, 0, -total_length / 2 + radius)) + ) + + solid = cylinder.union(top_cap).union(bottom_cap) + + # Remove solid above the level_height + drill_height = 10 * total_length + solid = solid.cut( + cq.Workplane("XY") + .cylinder(drill_height, radius) + .translate((0, 0, drill_height / 2 + level_height)) + ) + # Uncomment to display the CAD + # display(solid) + + primitive = solid.val() + + volume = primitive.Volume() + center_of_mass = primitive.centerOfMass(primitive) + inertia_tensor = primitive.matrixOfInertia(primitive) + + return volume, center_of_mass.z, inertia_tensor[0][0] + volume * center_of_mass.z**2 +for name, cylinder_geometry in geometry_map["cylinder"].items(): + radius = cylinder_geometry.radius(0) + length = cylinder_geometry.total_height + datapoints = [] + for h in np.linspace(-length / 2, length / 2, 25): + datapoints.append([h, *evaluate_cylinder_with_caps(length, radius, h)]) + + export_expected_parameters(name, datapoints) diff --git a/test_nb_all.py b/test_nb_all.py new file mode 100644 index 000000000..0d6cfa717 --- /dev/null +++ b/test_nb_all.py @@ -0,0 +1,295 @@ +import datetime as dt +from pathlib import Path + +import rocketpy as rp +from rocketpy import ( + Attachment, + Deployable, + DeploymentEvent, + Environment, + FlightBody, + IgnitionEvent, + Mission, + MissionExecutor, + Parachute, + Rocket, + RocketAdapter, + SolidMotor, + Stage, + StageSeparationEvent, +) +from rocketpy.plots.compare import CompareFlights +env = Environment( + latitude=32.990254, + longitude=-106.974998, + elevation=1400, + datum="WGS84", +) + +tomorrow = dt.date.today() + dt.timedelta(days=1) +env.set_date((tomorrow.year, tomorrow.month, tomorrow.day, 12), timezone="UTC") +env.set_atmospheric_model( + type="custom_atmosphere", + pressure=101325, + temperature=298.15, + wind_u=[(0, 3.0), (2000, 5.0), (5000, 8.0)], + wind_v=[(0, -1.0), (2000, -2.0), (5000, -3.5)], +) +env.max_expected_height = 10000 +data_directory = Path(rp.__file__).resolve().parent.parent / "data" +motor_file = data_directory / "motors" / "cesaroni" / "Cesaroni_M1670.eng" +power_off_drag_file = data_directory / "rockets" / "calisto" / "powerOffDragCurve.csv" +power_on_drag_file = data_directory / "rockets" / "calisto" / "powerOnDragCurve.csv" + +first_stage_motor = SolidMotor( + thrust_source=str(motor_file), + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", +) + +second_stage_motor = SolidMotor( + thrust_source=str(motor_file), + burn_time=3.9, + dry_mass=1.815, + dry_inertia=(0.125, 0.125, 0.002), + center_of_dry_mass_position=0.317, + nozzle_position=0, + grain_number=5, + grain_density=1815, + nozzle_radius=33 / 1000, + throat_radius=11 / 1000, + grain_separation=5 / 1000, + grain_outer_radius=33 / 1000, + grain_initial_height=120 / 1000, + grains_center_of_mass_position=0.397, + grain_initial_inner_radius=15 / 1000, + interpolation_method="linear", + coordinate_system_orientation="nozzle_to_combustion_chamber", +) + +first_stage_rocket = Rocket( + radius=0.0635, + mass=14.426, + inertia=(6.321, 6.321, 0.034), + power_off_drag=str(power_off_drag_file), + power_on_drag=str(power_on_drag_file), + center_of_mass_without_motor=0, + coordinate_system_orientation="tail_to_nose", +) +first_stage_rocket.name = "First Stage" +first_stage_rocket.add_motor(first_stage_motor, position=-1.255) +first_stage_rocket.set_rail_buttons( + upper_button_position=0.0818, + lower_button_position=-0.618, + angular_position=45, +) +first_stage_rocket.add_nose(length=0.55829, kind="von karman", position=1.278) +first_stage_rocket.add_trapezoidal_fins( + n=4, + root_chord=0.120, + tip_chord=0.060, + span=0.110, + position=-1.04956, + cant_angle=0.5, +) +first_stage_rocket.add_tail( + top_radius=0.0635, + bottom_radius=0.0435, + length=0.060, + position=-1.194656, +) + +second_stage_rocket = Rocket( + radius=0.0635, + mass=9.500, + inertia=(6.321, 6.321, 0.034), + power_off_drag=str(power_off_drag_file), + power_on_drag=str(power_on_drag_file), + center_of_mass_without_motor=0, + coordinate_system_orientation="tail_to_nose", +) +second_stage_rocket.name = "Second Stage" +second_stage_rocket.add_motor(second_stage_motor, position=-1.000) +second_stage_rocket.add_nose(length=0.55829, kind="von karman", position=1.050) +second_stage_rocket.add_trapezoidal_fins( + n=4, + root_chord=0.120, + tip_chord=0.060, + span=0.110, + position=-0.920, + cant_angle=0.5, +) +second_stage_rocket.add_tail( + top_radius=0.0635, + bottom_radius=0.0435, + length=0.060, + position=-1.194656, +) + +payload_body = FlightBody( + name="Payload", + geometry=0.055, + mass_model=lambda t: 4.5, + inertia_model=lambda t: (0.25, 0.25, 0.03, 0.0, 0.0, 0.0), + center_of_mass_model=lambda t: 0.0, + recovery_systems=[ + Parachute( + name="Payload Drogue", + cd_s=0.35, + trigger="apogee", + sampling_rate=105, + lag=1.0, + noise=(0, 4.0, 0.2), + ), + Parachute( + name="Payload Main", + cd_s=3.5, + trigger=700, + sampling_rate=105, + lag=1.0, + noise=(0, 4.0, 0.2), + ), + ], + coordinate_system_orientation="tail_to_nose", +) +# Deploy second-stage parachute right after stage-2 motor burnout. +# Parachute trigger callbacks use (pressure, height_agl, state, sensors). +second_stage_rocket.add_parachute( + "Second Stage Main", + cd_s=5.0, + trigger=lambda pressure, height_agl, state, sensors: state[0] + >= second_stage_motor.burn_out_time and state[5] <= 0, + sampling_rate=105, + lag=1.2, + noise=(0, 6.0, 0.25), +) + +print(f"Using motor file: {motor_file}") +print(f"Using drag curve files: {power_off_drag_file.name}, {power_on_drag_file.name}") +print(f"Payload body type: {type(payload_body).__name__}") +print(f"Payload dry mass at t=0 s: {payload_body.mass(0):.2f} kg") +print(f"Payload recovery systems: {len(payload_body.recovery_systems())}") +mission = Mission(name="Calisto Two-Stage + Deployable Mission") + +first_stage_attachment = Attachment( + parent_frame_position=[0.0, 0.0, 0.0], + child_frame_position=[0.0, 0.0, 0.0], + constraints="rigid", +) + +second_stage_attachment = Attachment( + parent_frame_position=[0.0, 0.0, 0.85], + child_frame_position=[0.0, 0.0, -0.45], + constraints="rigid", +) + +# In this tail_to_nose frame, z=0.55 m places the payload approximately +# halfway inserted into the first-stage airframe. +payload_attachment = Attachment( + parent_frame_position=[0.0, 0.0, 0.55], + child_frame_position=[0.0, 0.0, -0.30], + constraints="rigid", +) + +# In RocketPy state vectors, state[5] is vz (vertical velocity in m/s); +# vz <= 0 is used here as a descent/apogee crossing condition. +second_stage_sep = StageSeparationEvent( + name="Second Stage Separation", + trigger=lambda state, context: state[5] <= 0, +) +second_stage_ignition = IgnitionEvent( + name="Second Stage Ignition", + trigger=lambda state, context: state[5] <= 0, +) +payload_deploy_event = DeploymentEvent( + name="Payload Deployment", + trigger=lambda state, context: state[5] <= 0, +) + +first_stage = Stage( + name="first_stage", + body=RocketAdapter(first_stage_rocket), + attachment=first_stage_attachment, +) + +second_stage = Stage( + name="second_stage", + body=RocketAdapter(second_stage_rocket), + attachment=second_stage_attachment, + separation_event=second_stage_sep, + ignition_event=second_stage_ignition, +) + +payload = Deployable( + name="payload", + body=payload_body, + attachment=payload_attachment, + deployment_event=payload_deploy_event, +) + +mission.add_stage(first_stage) +mission.add_stage(second_stage) +mission.add_deployable(payload) +mission.set_flight_inputs( + "first_stage", + inclination=85, + heading=20, + terminate_on_apogee=True, + max_time=240, +) + +mission.set_flight_inputs( + "second_stage", + terminate_on_apogee=False, + max_time=240, +) + +mission.set_flight_inputs( + "payload", + terminate_on_apogee=False, + max_time=700, +) + +print("Mission Name:", mission.name) +print("Number of stages:", mission.number_of_stages) +print("Connection map:") +for item_name, metadata in mission.connection_map().items(): + print(f" - {item_name}: {metadata}") +executor = MissionExecutor( + mission=mission, + environment=env, + rail_length=5.2, + default_flight_inputs={"time_overshoot": True}, +) + +results = executor.execute() +flights_by_name = {result.item_name: result.flight for result in results} + +for result in results: + print( + f"{result.item_name}: apogee = {result.flight.apogee:.2f} m, " + f"impact velocity = {result.flight.impact_velocity:.2f} m/s" + ) +comparison = CompareFlights( + [ + flights_by_name["first_stage"], + flights_by_name["second_stage"], + flights_by_name["payload"], + ] +) + +comparison.trajectories_3d(legend=True) diff --git a/test_nb_seb.py b/test_nb_seb.py new file mode 100644 index 000000000..322488333 --- /dev/null +++ b/test_nb_seb.py @@ -0,0 +1,173 @@ +# These lines are here for debugging purposes only + +from rocketpy import ( + CylindricalTank, + Environment, + Flight, + Fluid, + Function, + LiquidMotor, + MassBasedTank, + Rocket, + UllageBasedTank, +) +# Define and set up LOX volume +lox_volume_liters = Function( + "../../data/rockets/berkeley/test124_Lox_Volume.csv", + extrapolation="zero", + inputs="Time (s)", + outputs="Volume (L)", +) +lox_volume = lox_volume_liters * 0.001 # Convert to m^3 +lox_volume.set_discrete(8.003, 19.984, 40, interpolation="linear") +lox_volume.set_outputs("Volume (m³)") +lox_volume.set_title("LOX Volume") + +# Plot LOX volume +lox_volume.plot(force_data=True) + +# Define and set up LOX tank ullage +lox_tank_ullage = 0.013167926436231077 - lox_volume +lox_tank_ullage.set_title("LOX Tank Ullage") +lox_tank_ullage.set_outputs("Ullage volume (m³)") + +# Plot LOX tank ullage +lox_tank_ullage.plot(8, 20, force_data=True) +# Define and set up Propane volume +propane_volume_liters = Function( + "../../data/rockets/berkeley/test124_Propane_Volume.csv", + inputs="Time (s)", + outputs="Volume (L)", +) +propane_volume = propane_volume_liters * 0.001 # Convert to m^3 +propane_volume.set_discrete(8.003, 19.984, 40, interpolation="linear") +propane_volume.set_outputs("Volume (m³)") + +# Plot Propane volume +propane_volume.plot(force_data=True) + +# Define and set up Propane tank ullage +propane_tank_ullage = 0.013167926436231077 - propane_volume +propane_tank_ullage.set_title("Propane Tank Ullage") +propane_tank_ullage.set_outputs("Ullage volume (m³)") + +# Plot Propane tank ullage +propane_tank_ullage.plot(force_data=True) +# Define fluids +lox = Fluid(name="LOX", density=1024) +propane = Fluid(name="Propane", density=566) + +# Define pressurizing gases with their respective pressures +lox_tank_pressurizing_gas = Fluid(name="N2", density=31.3 / 28) # 450 PSI +propane_tank_pressurizing_gas = Fluid( + name="N2", density=313 * 300 / 4500 / 28 +) # 300 PSI +pressurizing_gas = Fluid(name="N2", density=300) # 4500 PSI +lox_tank_geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) +lox_tank = UllageBasedTank( + name="LOX Tank", + flux_time=(8, 20), + geometry=lox_tank_geometry, + gas=lox_tank_pressurizing_gas, + liquid=lox, + ullage=lox_tank_ullage, +) +lox_tank.fluid_mass() +lox_tank.net_mass_flow_rate() +lox_tank.liquid_height() +lox_tank.gas_height() +lox_tank.center_of_mass() +lox_tank.inertia() +propane_tank_geometry = CylindricalTank(0.0744, 0.8068, spherical_caps=True) +propane_tank = UllageBasedTank( + name="Propane Tank", + flux_time=(8, 20), + geometry=propane_tank_geometry, + gas=propane_tank_pressurizing_gas, + liquid=propane, + ullage=propane_tank_ullage, +) +propane_tank.fluid_mass() +propane_tank.net_mass_flow_rate() +propane_tank.liquid_height() +propane_tank.gas_height() +propane_tank.center_of_mass() +propane_tank.inertia() +pressure_tank_geometry = CylindricalTank(0.135 / 2, 0.981, spherical_caps=True) +pressure_tank = MassBasedTank( + name="Pressure Tank", + geometry=pressure_tank_geometry, + liquid_mass=0, + flux_time=(8, 20), + gas_mass="../../data/rockets/berkeley/pressurantMassFiltered.csv", + gas=pressurizing_gas, + liquid=pressurizing_gas, +) +pressure_tank.fluid_mass() +pressure_tank.net_mass_flow_rate() +pressure_tank.liquid_height() +pressure_tank.gas_height() +pressure_tank.center_of_mass() +pressure_tank.inertia() +liquid_motor = LiquidMotor( + thrust_source="../../data/rockets/berkeley/test124_Thrust_Curve.csv", + center_of_dry_mass_position=0, + dry_inertia=(0, 0, 0), + dry_mass=0, + burn_time=(8, 20), + nozzle_radius=0.069 / 2, + nozzle_position=-1.364, + coordinate_system_orientation="nozzle_to_combustion_chamber", +) + +liquid_motor.add_tank(propane_tank, position=-0.6446) +liquid_motor.add_tank(lox_tank, position=1.1144) +liquid_motor.add_tank(pressure_tank, position=2.4975) +liquid_motor.all_info() +berkeley_rocket = Rocket( + radius=0.098, + mass=63.4, + inertia=(25, 25, 1), + power_off_drag="../../data/rockets/berkeley/drag.csv", + power_on_drag="../../data/rockets/berkeley/drag.csv", + center_of_mass_without_motor=3.23, + coordinate_system_orientation="nose_to_tail", +) +berkeley_rocket.add_motor(liquid_motor, position=4.2) +nose = berkeley_rocket.add_nose(length=0.7, kind="vonKarman", position=0) +tail = berkeley_rocket.add_tail( + top_radius=0.098, bottom_radius=0.058, length=0.198, position=5.69 - 0.198 +) + +fins = berkeley_rocket.add_trapezoidal_fins( + n=4, + root_chord=0.355, + tip_chord=0.0803, + span=0.156, + position=5.25, + cant_angle=0, +) +berkeley_rocket.all_info() +env = Environment(latitude=35.347122986338356, longitude=-117.80893423073582) + +env.set_date((2022, 12, 3, 14 + 7, 0, 0)) # UTC + +env.set_atmospheric_model( + type="custom_atmosphere", + pressure=None, + temperature=None, + wind_u=[(0, 1), (500, 0), (1000, 5), (2500, 5.0), (5000, 10)], + wind_v=[(0, 0), (500, 3), (1600, 2), (2500, -3), (5000, 10)], +) +env.info() +test_flight = Flight( + rocket=berkeley_rocket, + environment=env, + rail_length=18.28, + inclination=90, + heading=23, + max_time_step=0.1, + terminate_on_apogee=True, +) +test_flight.angle_of_attack.plot(test_flight.out_of_rail_time, 15) +test_flight.all_info() diff --git a/tests/unit/body/test_body_resolver.py b/tests/unit/body/test_body_resolver.py new file mode 100644 index 000000000..cecbd6090 --- /dev/null +++ b/tests/unit/body/test_body_resolver.py @@ -0,0 +1,111 @@ +"""Unit tests for rocketpy.body.BodyResolver.""" + +import pytest + +from rocketpy.body import BodyResolver, FlightBody, RocketAdapter + + +def _make_flight_body(name="flight_body"): + return FlightBody( + name=name, + geometry=0.05, + mass_model=lambda t: 10.0, + inertia_model=lambda t: (1.0, 1.0, 0.05, 0.0, 0.0, 0.0), + center_of_mass_model=lambda t: 0.5, + ) + + +class _FakeRocket: + """Rocket-like object exposing as_body(), as real Rocket does.""" + + def __init__(self, name="fake_rocket"): + self.name = name + + def as_body(self): + return RocketAdapter(self) + + def add_motor(self, motor, position): + """No-op helper to satisfy FlightCompatibleRocket.""" + + def total_mass(self, t): + return 1.0 + + def center_of_mass(self, t): + return 0.0 + + +class _ProtocolOnlyRocket: + """Satisfies FlightCompatibleRocket but has no as_body().""" + + def add_motor(self, motor, position): + """No-op helper to satisfy FlightCompatibleRocket.""" + + def total_mass(self, t): + return 1.0 + + def center_of_mass(self, t): + return 0.0 + + +class _UnsupportedBody: + """Satisfies neither BodyLike nor FlightCompatibleRocket.""" + + +class TestBodyResolverToBody: + """BodyResolver.to_body resolves supported sources to a BodyLike.""" + + def test_resolves_rocket_via_as_body(self): + """A Rocket-like object with as_body() is resolved via that method.""" + rocket = _FakeRocket() + body = BodyResolver.to_body(rocket) + assert isinstance(body, RocketAdapter) + assert body.rocket is rocket + + def test_passes_through_rocket_adapter(self): + """An existing RocketAdapter is returned unchanged.""" + adapter = RocketAdapter(_FakeRocket()) + assert BodyResolver.to_body(adapter) is adapter + + def test_passes_through_flight_body(self): + """A bare FlightBody is a legitimate BodyLike and is returned unchanged.""" + flight_body = _make_flight_body() + assert BodyResolver.to_body(flight_body) is flight_body + + def test_wraps_protocol_only_rocket(self): + """An object satisfying FlightCompatibleRocket but lacking as_body() + is wrapped in a RocketAdapter.""" + rocket = _ProtocolOnlyRocket() + body = BodyResolver.to_body(rocket) + assert isinstance(body, RocketAdapter) + assert body.rocket is rocket + + def test_raises_for_unsupported_type(self): + """An unsupported type raises TypeError.""" + with pytest.raises(TypeError, match="_UnsupportedBody"): + BodyResolver.to_body(_UnsupportedBody()) + + +class TestBodyResolverToFlightRocket: + """BodyResolver.to_flight_rocket gates what can drive a real Flight.""" + + def test_unwraps_rocket_adapter(self): + """A RocketAdapter unwraps to its underlying rocket.""" + rocket = _FakeRocket() + adapter = RocketAdapter(rocket) + assert BodyResolver.to_flight_rocket(adapter) is rocket + + def test_passes_through_protocol_compatible_rocket(self): + """A bare object satisfying FlightCompatibleRocket passes through.""" + rocket = _ProtocolOnlyRocket() + assert BodyResolver.to_flight_rocket(rocket) is rocket + + def test_raises_clear_error_for_flight_body(self): + """A bare FlightBody raises a clear, actionable TypeError.""" + flight_body = _make_flight_body("payload") + with pytest.raises(TypeError, match="FlightBody"): + BodyResolver.to_flight_rocket(flight_body) + + def test_raises_for_unsupported_type(self): + """An unsupported type raises TypeError.""" + with pytest.raises(TypeError): + BodyResolver.to_flight_rocket(_UnsupportedBody()) diff --git a/tests/unit/mission/test_flight_config.py b/tests/unit/mission/test_flight_config.py new file mode 100644 index 000000000..f962d86ee --- /dev/null +++ b/tests/unit/mission/test_flight_config.py @@ -0,0 +1,72 @@ +"""Unit tests for rocketpy.mission.FlightConfig.""" + +from rocketpy.mission import FlightConfig + + +class TestFlightConfigDefaults: + """FlightConfig field defaults match Flight's own constructor defaults.""" + + def test_defaults(self): + """Only rail_length is required; other fields carry Flight-matching + defaults.""" + config = FlightConfig(rail_length=5.0) + assert config.rail_length == 5.0 + assert config.inclination == 80.0 + assert config.heading == 90.0 + assert config.initial_solution is None + assert config.max_time == 600 + assert config.terminate_on_apogee is False + assert config.time_overshoot is True + assert config.extra_kwargs == {} + + +class TestFlightConfigToFlightKwargs: + """to_flight_kwargs() builds the keyword mapping passed to Flight.""" + + def test_shape(self): + """to_flight_kwargs() returns every field except rail_length.""" + config = FlightConfig(rail_length=5.0, inclination=85.0) + kwargs = config.to_flight_kwargs() + assert "rail_length" not in kwargs + assert kwargs["inclination"] == 85.0 + assert kwargs["heading"] == 90.0 + assert kwargs["max_time"] == 600 + assert kwargs["terminate_on_apogee"] is False + assert kwargs["time_overshoot"] is True + + def test_extra_kwargs_are_merged(self): + """extra_kwargs entries are merged into the returned mapping.""" + config = FlightConfig(rail_length=5.0, extra_kwargs={"max_time_step": 0.01}) + kwargs = config.to_flight_kwargs() + assert kwargs["max_time_step"] == 0.01 + + +class TestFlightConfigForBranch: + """for_branch() produces a new, independently overridden FlightConfig.""" + + def test_returns_new_instance(self): + """for_branch() does not mutate the original config.""" + base = FlightConfig(rail_length=5.0, inclination=80.0) + branch = base.for_branch("stage_1", {"inclination": 85.0}) + assert base.inclination == 80.0 + assert branch.inclination == 85.0 + assert branch is not base + + def test_known_field_override_precedence(self): + """Per-branch overrides win over the base config's values.""" + base = FlightConfig(rail_length=5.0, heading=90.0) + branch = base.for_branch("stage_1", {"heading": 15.0}) + assert branch.heading == 15.0 + + def test_unrecognized_keys_land_in_extra_kwargs(self): + """Unknown override keys are routed into extra_kwargs.""" + base = FlightConfig(rail_length=5.0) + branch = base.for_branch("stage_1", {"max_time_step": 0.01}) + assert branch.extra_kwargs["max_time_step"] == 0.01 + + def test_no_overrides_inherits_base_values(self): + """Calling for_branch() with no overrides copies the base config.""" + base = FlightConfig(rail_length=5.0, inclination=82.0) + branch = base.for_branch("stage_1") + assert branch.inclination == 82.0 + assert branch.rail_length == 5.0 diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index dc257ba08..e6ead5275 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -12,6 +12,7 @@ Deployable, DeploymentEvent, Event, + FlightConfig, IgnitionEvent, InstantaneousSeparation, Mission, @@ -517,11 +518,128 @@ def test_set_flight_inputs_raises_for_unknown_item(self): mission.set_flight_inputs("missing_stage", inclination=85) +class TestMissionRootVehicle: + """Mission.root_vehicle is a distinguished, non-attached-item vehicle.""" + + def test_default_root_vehicle_is_none(self): + """root_vehicle defaults to None.""" + assert Mission().root_vehicle is None + + def test_root_vehicle_via_constructor(self): + """root_vehicle can be set via the constructor.""" + vehicle = _make_rocket_adapter("root") + mission = Mission(root_vehicle=vehicle) + assert mission.root_vehicle is vehicle + + def test_set_root_vehicle(self): + """set_root_vehicle updates root_vehicle.""" + mission = Mission() + vehicle = _make_rocket_adapter("root") + mission.set_root_vehicle(vehicle) + assert mission.root_vehicle is vehicle + + def test_root_vehicle_excluded_from_attached_items(self): + """root_vehicle is not part of attached_items().""" + mission = Mission(root_vehicle=_make_rocket_adapter("root")) + mission.add_stage(_make_stage(name="stage_1")) + assert len(mission.attached_items()) == 1 + + def test_root_flight_inputs_round_trip(self): + """set_root_flight_inputs/get_root_flight_inputs store and return inputs.""" + mission = Mission() + mission.set_root_flight_inputs(inclination=85, heading=10) + inputs = mission.get_root_flight_inputs() + assert inputs == {"inclination": 85, "heading": 10} + + def test_get_root_flight_inputs_empty_by_default(self): + """get_root_flight_inputs returns an empty dict when unconfigured.""" + assert Mission().get_root_flight_inputs() == {} + + +class TestMissionDescribe: + """Mission.describe() returns a human-readable summary.""" + + def test_describe_without_root_vehicle(self): + """describe() reports root_vehicle as not set.""" + mission = Mission(name="Demo") + assert "root_vehicle: not set" in mission.describe() + assert "Demo" in mission.describe() + + def test_describe_with_root_vehicle(self): + """describe() reports the root vehicle's type.""" + mission = Mission(root_vehicle=_make_rocket_adapter("root")) + assert "RocketAdapter" in mission.describe() + + def test_describe_lists_stages_and_deployables(self): + """describe() lists each stage's name/state and each deployable's name.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1")) + mission.add_deployable(_make_deployable(name="payload")) + description = mission.describe() + assert "stage_1" in description + assert "payload" in description + assert "ATTACHED" in description + + +class TestMissionValidate: + """Mission.validate() aggregates per-item validation plus mission invariants.""" + + def test_validate_passes_for_well_formed_mission(self): + """validate() does not raise for a valid mission.""" + mission = Mission(root_vehicle=_make_rocket_adapter("root")) + mission.add_stage(_make_stage(name="stage_1")) + mission.validate() # must not raise + + def test_validate_raises_on_duplicate_names(self): + """validate() raises ValueError when two items share a name.""" + mission = Mission() + mission.add_stage(_make_stage(name="dup")) + mission.add_deployable(_make_deployable(name="dup")) + with pytest.raises(ValueError, match="Duplicate"): + mission.validate() + + def test_validate_aggregates_item_errors(self): + """validate() collects every failing item's error into one ValueError.""" + mission = Mission() + stage = _make_stage(name="stage_1") + deployable = _make_deployable(name="payload") + mission.add_stage(stage) + mission.add_deployable(deployable) + # Mutate after adding to bypass add_stage/add_deployable's own checks. + stage.body = None + deployable.body = None + + with pytest.raises(ValueError) as exc_info: + mission.validate() + assert "stage_1" in str(exc_info.value) + assert "payload" in str(exc_info.value) + + def test_validate_warns_when_root_vehicle_unset(self): + """validate() warns (does not raise) when root_vehicle is unset.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1")) + with pytest.warns(UserWarning, match="root_vehicle"): + mission.validate() + + def test_validate_raises_when_root_vehicle_required(self): + """validate(require_root_vehicle=True) raises when root_vehicle is unset.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1")) + with pytest.raises(ValueError, match="root_vehicle"): + mission.validate(require_root_vehicle=True) + + class TestMissionExecutor: """MissionExecutor runs mission items without requiring manual Flight setup.""" class FakeFlight: - """Simple stand-in for rocketpy.simulation.Flight.""" + """Simple stand-in for rocketpy.simulation.Flight. + + Mimics just enough of Flight's initial_solution-chaining contract + (see Flight.__init_flight_state) for MissionExecutor's sequential + chaining logic to be exercised: passing a previous FakeFlight as + initial_solution resolves to that flight's own final state. + """ def __init__(self, rocket, environment, rail_length, **kwargs): self.rocket = rocket @@ -529,6 +647,14 @@ def __init__(self, rocket, environment, rail_length, **kwargs): self.rail_length = rail_length self.kwargs = kwargs + initial_solution = kwargs.get("initial_solution") + if hasattr(initial_solution, "initial_solution"): + initial_solution = initial_solution.initial_solution + self.initial_solution = list(initial_solution) if initial_solution else [0.0] + self.t_initial = self.initial_solution[0] + self.apogee = 100.0 + self.impact_velocity = -5.0 + def test_execute_runs_stage_and_deployable(self): """execute runs all mission attached items in mission order.""" mission = Mission() @@ -556,6 +682,7 @@ def test_execute_runs_stage_and_deployable(self): assert results[0].flight.kwargs["heading"] == 15 assert results[1].flight.kwargs["heading"] == 45 assert results[1].flight.kwargs["initial_solution"] is results[0].flight + assert stage.state == StageState.SPENT def test_execute_raises_for_non_rocket_body(self): """execute raises TypeError when body is not RocketAdapter-backed.""" @@ -585,8 +712,9 @@ def test_execute_accepts_protocol_compatible_rocket_body(self): results = executor.execute() assert results[0].flight.rocket.name == "raw_stage" - def test_execute_accepts_flight_body(self): - """execute accepts FlightBody bodies without converting them.""" + def test_execute_raises_for_flight_body(self): + """execute raises a clear TypeError for a bare FlightBody, since it + cannot drive a real Flight (no total_mass()/add_motor()).""" mission = Mission() body = _make_body("flight_body") mission.add_stage(_make_stage(name="stage_1", body=body)) @@ -596,5 +724,136 @@ def test_execute_accepts_flight_body(self): rail_length=5.0, flight_class=self.FakeFlight, ) - results = executor.execute() - assert results[0].flight.rocket is body + with pytest.raises(TypeError, match="FlightBody"): + executor.execute() + + +class TestMissionExecutorRun: + """MissionExecutor.run()/dry_run() build on top of execute()'s logic.""" + + FakeFlight = TestMissionExecutor.FakeFlight + + def test_run_without_root_vehicle(self): + """run() returns a MissionResult with root_flight None when + mission.root_vehicle is unset.""" + mission = Mission() + mission.add_stage( + _make_stage(name="stage_1", body=_make_rocket_adapter("stage_rocket")) + ) + mission.add_deployable( + _make_deployable(name="payload", body=_make_rocket_adapter("payload_rocket")) + ) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + + result = executor.run() + + assert result.root_flight is None + assert list(result.branch_flights) == ["stage_1", "payload"] + assert [branch.name for branch in result.branch_results] == [ + "stage_1", + "payload", + ] + + def test_run_with_root_vehicle_chains_from_root(self): + """run() builds a root flight first and chains the first item from it.""" + mission = Mission(root_vehicle=_make_rocket_adapter("root_rocket")) + mission.add_stage( + _make_stage(name="stage_1", body=_make_rocket_adapter("stage_rocket")) + ) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + + result = executor.run() + + assert result.root_flight is not None + assert result.branch_results[0].flight.kwargs["initial_solution"] is ( + result.root_flight + ) + + def test_run_raises_for_flight_body_item(self): + """run() raises the BodyResolver error at the executor boundary for a + bare FlightBody attached item.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + with pytest.raises(TypeError, match="FlightBody"): + executor.run() + + def test_run_raises_for_flight_body_root_vehicle(self): + """run() raises the same clear error for a FlightBody root_vehicle.""" + mission = Mission(root_vehicle=_make_body("flight_body_root")) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + with pytest.raises(TypeError, match="FlightBody"): + executor.run() + + def test_run_marks_stage_spent(self): + """run() transitions Stage.state to SPENT once its branch is built.""" + mission = Mission() + stage = _make_stage(name="stage_1", body=_make_rocket_adapter("stage_rocket")) + mission.add_stage(stage) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + + executor.run() + + assert stage.state == StageState.SPENT + + def test_dry_run_never_constructs_a_flight(self): + """dry_run() resolves bodies/configs without invoking flight_class.""" + + def _failing_flight_class(*args, **kwargs): + raise AssertionError("dry_run() must not construct a Flight.") + + mission = Mission(root_vehicle=_make_rocket_adapter("root_rocket")) + mission.add_stage( + _make_stage(name="stage_1", body=_make_rocket_adapter("stage_rocket")) + ) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + default_flight_inputs={"inclination": 80}, + flight_class=_failing_flight_class, + ) + + configs = executor.dry_run() + + assert [config.rail_length for config in configs] == [5.0, 5.0] + assert all(isinstance(config, FlightConfig) for config in configs) + assert all(config.inclination == 80 for config in configs) + + def test_dry_run_raises_for_flight_body(self): + """dry_run() still surfaces the FlightBody guard, cheaply.""" + mission = Mission() + mission.add_stage(_make_stage(name="stage_1", body=_make_body("flight_body"))) + executor = MissionExecutor( + mission=mission, + environment=object(), + rail_length=5.0, + flight_class=self.FakeFlight, + ) + with pytest.raises(TypeError, match="FlightBody"): + executor.dry_run() diff --git a/tests/unit/mission/test_mission_result.py b/tests/unit/mission/test_mission_result.py new file mode 100644 index 000000000..c4dcb2738 --- /dev/null +++ b/tests/unit/mission/test_mission_result.py @@ -0,0 +1,148 @@ +"""Unit tests for rocketpy.mission.MissionResult and BranchResult.""" + +import pytest + +from rocketpy.mission import BranchResult, MissionResult + + +class _FakeFlight: + def __init__(self, apogee=100.0, impact_velocity=-5.0): + self.apogee = apogee + self.impact_velocity = impact_velocity + + +def _make_branch_result(name, flight=None): + flight = flight or _FakeFlight() + return BranchResult( + name=name, flight=flight, item=object(), event_time=0.0, initial_solution=[] + ) + + +class TestMissionResultAllFlights: + """all_flights() orders root_flight (if any) before branch flights.""" + + def test_includes_root_flight_first(self): + """root_flight comes before branch flights when set.""" + root_flight = _FakeFlight() + branch = _make_branch_result("stage_1") + result = MissionResult( + root_flight=root_flight, + branch_flights={"stage_1": branch.flight}, + branch_results=[branch], + ) + assert result.all_flights() == [root_flight, branch.flight] + + def test_excludes_root_flight_when_none(self): + """all_flights() omits root_flight when it is None.""" + branch = _make_branch_result("stage_1") + result = MissionResult( + root_flight=None, + branch_flights={"stage_1": branch.flight}, + branch_results=[branch], + ) + assert result.all_flights() == [branch.flight] + + +class TestMissionResultGetFlight: + """get_flight() looks up flights by name, including 'root'.""" + + def test_get_flight_by_name(self): + """get_flight(name) returns the matching branch flight.""" + branch = _make_branch_result("stage_1") + result = MissionResult( + root_flight=None, + branch_flights={"stage_1": branch.flight}, + branch_results=[branch], + ) + assert result.get_flight("stage_1") is branch.flight + + def test_get_flight_root(self): + """get_flight('root') returns root_flight when set.""" + root_flight = _FakeFlight() + result = MissionResult( + root_flight=root_flight, branch_flights={}, branch_results=[] + ) + assert result.get_flight("root") is root_flight + + def test_get_flight_root_raises_when_unset(self): + """get_flight('root') raises KeyError when root_flight is None.""" + result = MissionResult(root_flight=None, branch_flights={}, branch_results=[]) + with pytest.raises(KeyError): + result.get_flight("root") + + def test_get_flight_raises_for_unknown_name(self): + """get_flight(name) raises KeyError for an unknown branch name.""" + result = MissionResult(root_flight=None, branch_flights={}, branch_results=[]) + with pytest.raises(KeyError): + result.get_flight("unknown") + + +class TestMissionResultGetBranchResult: + """get_branch_result() looks up BranchResult records by name.""" + + def test_returns_matching_branch(self): + """get_branch_result(name) returns the matching BranchResult.""" + branch = _make_branch_result("stage_1") + result = MissionResult( + root_flight=None, + branch_flights={"stage_1": branch.flight}, + branch_results=[branch], + ) + assert result.get_branch_result("stage_1") is branch + + def test_raises_for_unknown_name(self): + """get_branch_result(name) raises KeyError for an unknown name.""" + result = MissionResult(root_flight=None, branch_flights={}, branch_results=[]) + with pytest.raises(KeyError): + result.get_branch_result("unknown") + + +class TestMissionResultSummary: + """summary() reports one line per flight.""" + + def test_summary_contains_each_flight_name(self): + """summary() mentions 'root' and each branch's name.""" + root_flight = _FakeFlight(apogee=500.0, impact_velocity=-2.0) + branch = _make_branch_result("stage_1", _FakeFlight(apogee=400.0)) + result = MissionResult( + root_flight=root_flight, + branch_flights={"stage_1": branch.flight}, + branch_results=[branch], + ) + summary = result.summary() + assert "root" in summary + assert "stage_1" in summary + assert "500.00" in summary + + +class TestMissionResultPlotAll: + """plot_all() delegates to rocketpy.plots.CompareFlights.""" + + def test_plot_all_calls_compare_flights(self, monkeypatch): + """plot_all() builds a CompareFlights over all_flights() and renders it.""" + calls = {} + + class _FakeCompareFlights: + def __init__(self, flights): + calls["flights"] = flights + + def trajectories_3d(self, legend=None): + calls["legend"] = legend + + monkeypatch.setattr( + "rocketpy.plots.CompareFlights", _FakeCompareFlights + ) + + root_flight = _FakeFlight() + branch = _make_branch_result("stage_1") + result = MissionResult( + root_flight=root_flight, + branch_flights={"stage_1": branch.flight}, + branch_results=[branch], + ) + + comparison = result.plot_all() + + assert isinstance(comparison, _FakeCompareFlights) + assert calls["flights"] == [root_flight, branch.flight] + assert calls["legend"] is True From 2e16b2cd0e1733a4b357c46a014ebd41383807e3 Mon Sep 17 00:00:00 2001 From: Ishan Date: Mon, 13 Jul 2026 00:29:05 +0530 Subject: [PATCH 34/34] STY: apply ruff format/import-sort fixes across mission architecture files Co-Authored-By: Claude Sonnet 5 --- docs/multistage_example.ipynb | 86 +++++++++---------- tests/unit/body/test_body.py | 1 - tests/unit/mission/test_mission.py | 9 +- tests/unit/mission/test_mission_result.py | 4 +- .../rocket/test_rocket_attachment_host.py | 4 +- tests/unit/simulation/test_flight_branch.py | 1 - 6 files changed, 50 insertions(+), 55 deletions(-) diff --git a/docs/multistage_example.ipynb b/docs/multistage_example.ipynb index 046694fd1..5830763c4 100644 --- a/docs/multistage_example.ipynb +++ b/docs/multistage_example.ipynb @@ -28,10 +28,10 @@ "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:38.045030Z", - "iopub.status.busy": "2026-07-12T18:50:38.044833Z", - "iopub.status.idle": "2026-07-12T18:50:39.630628Z", - "shell.execute_reply": "2026-07-12T18:50:39.629827Z" + "iopub.execute_input": "2026-07-12T18:58:26.682661Z", + "iopub.status.busy": "2026-07-12T18:58:26.682410Z", + "iopub.status.idle": "2026-07-12T18:58:28.250548Z", + "shell.execute_reply": "2026-07-12T18:58:28.249705Z" } }, "outputs": [], @@ -75,10 +75,10 @@ "id": "72eea5119410473aa328ad9291626812", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.632911Z", - "iopub.status.busy": "2026-07-12T18:50:39.632647Z", - "iopub.status.idle": "2026-07-12T18:50:39.644580Z", - "shell.execute_reply": "2026-07-12T18:50:39.643972Z" + "iopub.execute_input": "2026-07-12T18:58:28.252789Z", + "iopub.status.busy": "2026-07-12T18:58:28.252520Z", + "iopub.status.idle": "2026-07-12T18:58:28.264304Z", + "shell.execute_reply": "2026-07-12T18:58:28.263591Z" } }, "outputs": [], @@ -121,10 +121,10 @@ "id": "10185d26023b46108eb7d9f57d49d2b3", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.646604Z", - "iopub.status.busy": "2026-07-12T18:50:39.646438Z", - "iopub.status.idle": "2026-07-12T18:50:39.722105Z", - "shell.execute_reply": "2026-07-12T18:50:39.721368Z" + "iopub.execute_input": "2026-07-12T18:58:28.266133Z", + "iopub.status.busy": "2026-07-12T18:58:28.265982Z", + "iopub.status.idle": "2026-07-12T18:58:28.338925Z", + "shell.execute_reply": "2026-07-12T18:58:28.338299Z" } }, "outputs": [ @@ -303,10 +303,10 @@ "id": "7623eae2785240b9bd12b16a66d81610", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.723705Z", - "iopub.status.busy": "2026-07-12T18:50:39.723568Z", - "iopub.status.idle": "2026-07-12T18:50:39.727154Z", - "shell.execute_reply": "2026-07-12T18:50:39.726569Z" + "iopub.execute_input": "2026-07-12T18:58:28.340702Z", + "iopub.status.busy": "2026-07-12T18:58:28.340550Z", + "iopub.status.idle": "2026-07-12T18:58:28.344242Z", + "shell.execute_reply": "2026-07-12T18:58:28.343635Z" } }, "outputs": [ @@ -360,10 +360,10 @@ "id": "b118ea5561624da68c537baed56e602f", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.728771Z", - "iopub.status.busy": "2026-07-12T18:50:39.728654Z", - "iopub.status.idle": "2026-07-12T18:50:39.733426Z", - "shell.execute_reply": "2026-07-12T18:50:39.732864Z" + "iopub.execute_input": "2026-07-12T18:58:28.345736Z", + "iopub.status.busy": "2026-07-12T18:58:28.345614Z", + "iopub.status.idle": "2026-07-12T18:58:28.350366Z", + "shell.execute_reply": "2026-07-12T18:58:28.349767Z" } }, "outputs": [], @@ -449,10 +449,10 @@ "id": "504fb2a444614c0babb325280ed9130a", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.734911Z", - "iopub.status.busy": "2026-07-12T18:50:39.734801Z", - "iopub.status.idle": "2026-07-12T18:50:39.737403Z", - "shell.execute_reply": "2026-07-12T18:50:39.736812Z" + "iopub.execute_input": "2026-07-12T18:58:28.351804Z", + "iopub.status.busy": "2026-07-12T18:58:28.351691Z", + "iopub.status.idle": "2026-07-12T18:58:28.354012Z", + "shell.execute_reply": "2026-07-12T18:58:28.353565Z" } }, "outputs": [], @@ -475,7 +475,7 @@ " \"payload\",\n", " terminate_on_apogee=False,\n", " max_time=700,\n", - ")\n" + ")" ] }, { @@ -492,10 +492,10 @@ "id": "b43b363d81ae4b689946ece5c682cd59", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.738852Z", - "iopub.status.busy": "2026-07-12T18:50:39.738727Z", - "iopub.status.idle": "2026-07-12T18:50:39.741385Z", - "shell.execute_reply": "2026-07-12T18:50:39.740782Z" + "iopub.execute_input": "2026-07-12T18:58:28.355712Z", + "iopub.status.busy": "2026-07-12T18:58:28.355572Z", + "iopub.status.idle": "2026-07-12T18:58:28.358141Z", + "shell.execute_reply": "2026-07-12T18:58:28.357607Z" } }, "outputs": [ @@ -538,10 +538,10 @@ "id": "c3933fab20d04ec698c2621248eb3be0", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:39.742895Z", - "iopub.status.busy": "2026-07-12T18:50:39.742778Z", - "iopub.status.idle": "2026-07-12T18:50:40.058278Z", - "shell.execute_reply": "2026-07-12T18:50:40.057650Z" + "iopub.execute_input": "2026-07-12T18:58:28.359712Z", + "iopub.status.busy": "2026-07-12T18:58:28.359592Z", + "iopub.status.idle": "2026-07-12T18:58:28.667716Z", + "shell.execute_reply": "2026-07-12T18:58:28.666945Z" } }, "outputs": [ @@ -587,10 +587,10 @@ "id": "8309879909854d7188b41380fd92a7c3", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:40.059970Z", - "iopub.status.busy": "2026-07-12T18:50:40.059826Z", - "iopub.status.idle": "2026-07-12T18:50:40.186219Z", - "shell.execute_reply": "2026-07-12T18:50:40.185502Z" + "iopub.execute_input": "2026-07-12T18:58:28.669442Z", + "iopub.status.busy": "2026-07-12T18:58:28.669290Z", + "iopub.status.idle": "2026-07-12T18:58:28.950327Z", + "shell.execute_reply": "2026-07-12T18:58:28.949563Z" } }, "outputs": [ @@ -640,10 +640,10 @@ "id": "bd79bb8f", "metadata": { "execution": { - "iopub.execute_input": "2026-07-12T18:50:40.187923Z", - "iopub.status.busy": "2026-07-12T18:50:40.187785Z", - "iopub.status.idle": "2026-07-12T18:50:40.191859Z", - "shell.execute_reply": "2026-07-12T18:50:40.191304Z" + "iopub.execute_input": "2026-07-12T18:58:28.952246Z", + "iopub.status.busy": "2026-07-12T18:58:28.952078Z", + "iopub.status.idle": "2026-07-12T18:58:28.956732Z", + "shell.execute_reply": "2026-07-12T18:58:28.956019Z" } }, "outputs": [ @@ -677,9 +677,7 @@ " ),\n", " )\n", ")\n", - "demo_executor = MissionExecutor(\n", - " mission=demo_mission, environment=env, rail_length=5.2\n", - ")\n", + "demo_executor = MissionExecutor(mission=demo_mission, environment=env, rail_length=5.2)\n", "\n", "try:\n", " demo_executor.execute()\n", diff --git a/tests/unit/body/test_body.py b/tests/unit/body/test_body.py index 8e59f1c73..88d4bf68b 100644 --- a/tests/unit/body/test_body.py +++ b/tests/unit/body/test_body.py @@ -4,7 +4,6 @@ from rocketpy.body import BodyLike, FlightBody, RocketAdapter - # --------------------------------------------------------------------------- # Helpers / fixtures # --------------------------------------------------------------------------- diff --git a/tests/unit/mission/test_mission.py b/tests/unit/mission/test_mission.py index 0429e79d7..95d75fc1e 100644 --- a/tests/unit/mission/test_mission.py +++ b/tests/unit/mission/test_mission.py @@ -26,7 +26,6 @@ ) from rocketpy.simulation import Event - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -676,7 +675,9 @@ def __init__(self, rocket, environment, rail_length, **kwargs): initial_solution = kwargs.get("initial_solution") if hasattr(initial_solution, "initial_solution"): initial_solution = initial_solution.initial_solution - self.initial_solution = list(initial_solution) if initial_solution else [0.0] + self.initial_solution = ( + list(initial_solution) if initial_solution else [0.0] + ) self.t_initial = self.initial_solution[0] self.apogee = 100.0 self.impact_velocity = -5.0 @@ -788,7 +789,9 @@ def test_run_without_root_vehicle(self): _make_stage(name="stage_1", body=_make_rocket_adapter("stage_rocket")) ) mission.add_deployable( - _make_deployable(name="payload", body=_make_rocket_adapter("payload_rocket")) + _make_deployable( + name="payload", body=_make_rocket_adapter("payload_rocket") + ) ) executor = MissionExecutor( mission=mission, diff --git a/tests/unit/mission/test_mission_result.py b/tests/unit/mission/test_mission_result.py index c4dcb2738..bd3511fe2 100644 --- a/tests/unit/mission/test_mission_result.py +++ b/tests/unit/mission/test_mission_result.py @@ -129,9 +129,7 @@ def __init__(self, flights): def trajectories_3d(self, legend=None): calls["legend"] = legend - monkeypatch.setattr( - "rocketpy.plots.CompareFlights", _FakeCompareFlights - ) + monkeypatch.setattr("rocketpy.plots.CompareFlights", _FakeCompareFlights) root_flight = _FakeFlight() branch = _make_branch_result("stage_1") diff --git a/tests/unit/rocket/test_rocket_attachment_host.py b/tests/unit/rocket/test_rocket_attachment_host.py index d3c6618a7..5bdeea037 100644 --- a/tests/unit/rocket/test_rocket_attachment_host.py +++ b/tests/unit/rocket/test_rocket_attachment_host.py @@ -3,7 +3,7 @@ import pytest from rocketpy import Rocket -from rocketpy.body import RocketAdapter +from rocketpy.body import FlightBody, RocketAdapter from rocketpy.mission import ( Attachment, Deployable, @@ -12,8 +12,6 @@ Stage, StageSeparationEvent, ) -from rocketpy.body import FlightBody - # --------------------------------------------------------------------------- # Helpers diff --git a/tests/unit/simulation/test_flight_branch.py b/tests/unit/simulation/test_flight_branch.py index 18d6d238b..d19c1695a 100644 --- a/tests/unit/simulation/test_flight_branch.py +++ b/tests/unit/simulation/test_flight_branch.py @@ -4,7 +4,6 @@ from rocketpy.simulation import FlightBranch - # --------------------------------------------------------------------------- # Helpers # ---------------------------------------------------------------------------