Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ebb0365
ENH: implement multistage mission architecture (body, mission, Flight…
Copilot May 6, 2026
7a1abf9
STY: address code review feedback on stubs, docstrings, and adapter c…
Copilot May 6, 2026
5ac9222
ENH add multistage example notebook
Copilot May 6, 2026
27cbd2f
ENH align multistage notebook inertia output
Copilot May 6, 2026
fd06e4b
DOC: add flight simulation to multistage example
Copilot May 7, 2026
841c0d9
DOC: run flights for all mission items
Copilot May 7, 2026
aac9398
DOC: reuse mission objects for multistage flights
Copilot May 7, 2026
ef2d6d4
DOC: clarify mission body variables
Copilot May 7, 2026
80ffed8
DOC: rename mission body refs
Copilot May 7, 2026
70113d0
DOC: build rockets from mission bodies
Copilot May 7, 2026
c18f542
DOC: use mission bodies in multistage example
Copilot May 9, 2026
0cd327a
MNT: align BodyLike protocol usage
Copilot May 9, 2026
5c7b547
MNT: tidy protocol spacing and tests
Copilot May 9, 2026
5abd07f
DOC: clarify BodyLike protocol usage
Copilot May 9, 2026
d27604c
DOC: expand BodyLike protocol notes
Copilot May 9, 2026
d5ef516
TST: focus BodyLike protocol tests
Copilot May 9, 2026
2a23609
ENH: add MissionExecutor and mission flight input metadata
Copilot May 10, 2026
0c9dbe5
TST: address review feedback for mission executor types
Copilot May 10, 2026
5bee1c9
TST: strengthen mission fake rocket interface
Copilot May 10, 2026
5c42349
DOC: clarify mission keying and executor support contract
Copilot May 10, 2026
716e8b8
DOC: rewrite multistage example notebook for MissionExecutor API
Copilot May 10, 2026
76e25c4
docs: update multistage notebook payload setup
Copilot May 10, 2026
a7e8530
docs: clarify FlightBody notebook usage
Copilot May 10, 2026
ce10066
docs: finalize multistage notebook portability updates
Copilot May 10, 2026
a225073
ENH support FlightBody mission execution and fix multistage example flow
Copilot May 10, 2026
f95dbc9
STY clarify FlightBody proxy assumptions and notebook realism notes
Copilot May 10, 2026
14f7151
STY finalize validation feedback for mission executor and notebook
Copilot May 10, 2026
10b9ef4
STY polish mission executor and mission test readability
Copilot May 10, 2026
35f40da
refactor: remove FlightBody point-mass proxy path
Copilot May 10, 2026
b225446
docs: clarify multistage trigger conditions in notebook
Copilot May 10, 2026
0baab95
docs: clarify parachute trigger callback signature
Copilot May 10, 2026
a7bc2f4
docs: refine burnout-triggered chute and payload placement notes
Copilot May 10, 2026
1538942
modified: docs/multistage_example.ipynb
aZira371 Jul 12, 2026
1a88048
Merge remote-tracking branch 'origin/enh/events' into copilot/add-roc…
aZira371 Jul 12, 2026
2e16b2c
STY: apply ruff format/import-sort fixes across mission architecture …
aZira371 Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
710 changes: 710 additions & 0 deletions docs/multistage_example.ipynb

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions rocketpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from ._logging import enable_logging, logger, set_log_level
from .body import (
BodyLike,
BodyResolver,
FlightBody,
FlightCompatibleRocket,
RocketAdapter,
)
from .control import _Controller
from .environment import Environment, EnvironmentAnalysis
from .mathutils import (
Expand All @@ -7,6 +14,26 @@
funcify_method,
reset_funcified_methods,
)
from .mission import (
Attachment,
BranchResult,
Deployable,
DeploymentEvent,
FlightConfig,
IgnitionEvent,
InstantaneousSeparation,
Mission,
MissionExecutionResult,
MissionExecutor,
MissionResult,
NoOpParentUpdate,
ParentUpdate,
RecoveryEvent,
SeparationModel,
Stage,
StageSeparationEvent,
StageState,
)
from .motors import (
CylindricalTank,
EmptyMotor,
Expand Down Expand Up @@ -52,6 +79,7 @@
from .simulation import (
Event,
Flight,
FlightBranch,
MonteCarlo,
MultivariateRejectionSampler,
)
Expand Down
14 changes: 14 additions & 0 deletions rocketpy/body/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +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",
"BodyResolver",
"FlightBody",
"FlightCompatibleRocket",
"RocketAdapter",
]
157 changes: 157 additions & 0 deletions rocketpy/body/body_like.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""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


@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`)
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
----------
name : str
Human-readable identifier for the body.
"""

@property
def name(self) -> str:
"""Human-readable name of the body.

Returns
-------
str
Body name.
"""
...

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.
"""
...

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².
"""
...

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.
"""
...

def aerodynamic_model(self):
"""Return the aerodynamic model attached to this body.

Returns
-------
object
Aerodynamic model instance.
"""
...

def propulsion_model(self):
"""Return the propulsion model attached to this body.

Returns
-------
object
Propulsion model instance.
"""
...

def recovery_systems(self):
"""Return the list of recovery systems attached to this body.

Returns
-------
list
Recovery systems.
"""
...

def sensors(self):
"""Return the list of sensors attached to this body.

Returns
-------
list
Sensor instances.
"""
...

def controllers(self):
"""Return the list of active controllers attached to this body.

Returns
-------
list
Controller instances.
"""
...

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"``.
"""
...

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`.
"""
...
114 changes: 114 additions & 0 deletions rocketpy/body/body_resolver.py
Original file line number Diff line number Diff line change
@@ -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."
)
Loading
Loading