Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ Attention: The newest changes should be on top -->

### Added

### Changed

### Fixed


## [v1.13.0] - 2026-07-04

### Added

- ENH: ENH: Refactor flight.py latitude/longitude to use inverted_haversine [#1055](https://github.com/RocketPy-Team/RocketPy/pull/1055)
- ENH: TST: Add unit tests for evaluate_reduced_mass and remove TODO [#1051](https://github.com/RocketPy-Team/RocketPy/pull/1051)
- ENH: FIX: pre release hardening [#1047](https://github.com/RocketPy-Team/RocketPy/pull/1047)
Expand Down Expand Up @@ -60,6 +69,11 @@ Attention: The newest changes should be on top -->

### Changed

- REL: bumps up rocketpy version to 1.13.0 [#1048](https://github.com/RocketPy-Team/RocketPy/pull/1048)
- MNT: **Breaking**: `Parachute` is now an abstract base class and can no longer be instantiated directly; use `HemisphericalParachute` (or `Rocket.add_parachute`, which builds it for you). Loading old serialized files still works [#958](https://github.com/RocketPy-Team/RocketPy/pull/958)
- MNT: Discrete controllers are now called exactly once per time node (previously twice); results may change for stateful controllers and `observed_variables` no longer contains duplicated entries [#949](https://github.com/RocketPy-Team/RocketPy/pull/949)
- MNT: Multi-dimensional linear `Function` objects now apply their extrapolation rule to points outside the data's convex hull (previously returned NaN) [#969](https://github.com/RocketPy-Team/RocketPy/pull/969)
- MNT: Informational messages previously shown with `print()` now use Python logging and are silent by default; call `rocketpy.utils.enable_logging()` to see them [#973](https://github.com/RocketPy-Team/RocketPy/pull/973)
- ENH: Refactor flight.py latitude/longitude to use inverted_haversine [#1055](https://github.com/RocketPy-Team/RocketPy/pull/1055)

### Deprecated
Expand All @@ -72,6 +86,8 @@ Attention: The newest changes should be on top -->

### Fixed

- BUG: fix individual fin (`TrapezoidalFin`, `EllipticalFin`, `FreeFormFin`) serialization crash when saving rockets/flights [#1048](https://github.com/RocketPy-Team/RocketPy/pull/1048)
- BUG: support the new Wyoming sounding WSGI page format (legacy cgi-bin endpoint was discontinued by UWyo) [#1048](https://github.com/RocketPy-Team/RocketPy/pull/1048)
- FIX: pre-release hardening — bug fixes across the unreleased features [#1047](https://github.com/RocketPy-Team/RocketPy/pull/1047)
- BUG: Remove duplicate controller process; controllers were being invoked twice per time node [#949](https://github.com/RocketPy-Team/RocketPy/pull/949)
- BUG: fix wind heading and direction wraparound interpolation [#974](https://github.com/RocketPy-Team/RocketPy/pull/974)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ Here is just a quick taste of what RocketPy is able to calculate. There are hund

![6-DOF Trajectory Plot](https://raw.githubusercontent.com/RocketPy-Team/RocketPy/master/docs/static/rocketpy_example_trajectory.svg)

If you want to see the trajectory on Google Earth, RocketPy acn easily export a KML file for you:
If you want to see the trajectory on Google Earth, RocketPy can easily export a KML file for you:

```python
from rocketpy.simulation import FlightDataExporter
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
author = "RocketPy Team"

# The full version, including alpha/beta/rc tags
release = "1.12.1"
release = "1.13.0"


# -- General configuration ---------------------------------------------------
Expand Down
5 changes: 2 additions & 3 deletions docs/development/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,7 @@ Consider the following integration test:
"""
# TODO:: this should be added to the set_atmospheric_model() method as a
# "file" option, instead of receiving the URL as a string.
URL = "http://weather.uwyo.edu/cgi-bin/sounding?region=samer&TYPE=TEXT%3ALIST&YEAR=2019&MONTH=02&FROM=0500&TO=0512&STNM=83779"
# give it at least 5 times to try to download the file
URL = "https://weather.uwyo.edu/wsgi/sounding?datetime=2019-02-05+00:00:00&id=83779&type=TEXT:LIST"
example_plain_env.set_atmospheric_model(type="wyoming_sounding", file=URL)

assert example_plain_env.all_info() is None
Expand All @@ -267,7 +266,7 @@ Consider the following integration test:
abs(example_plain_env.barometric_height(example_plain_env.pressure(0)) - 722.0)
< 1e-8
)
assert abs(example_plain_env.wind_velocity_x(0) - -2.9005178894925043) < 1e-8
assert abs(example_plain_env.wind_velocity_x(0) - -2.9130471244363165) < 1e-8
assert abs(example_plain_env.temperature(100) - 291.75) < 1e-8

This test contains two fundamental traits which defines it as an integration test:
Expand Down
3 changes: 1 addition & 2 deletions docs/reference/classes/utils/logging.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Logging functions
-----------------

.. automodule:: rocketpy.utils
:members:
.. autofunction:: rocketpy.utilities.enable_logging
3 changes: 2 additions & 1 deletion docs/reference/classes/utils/utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ At RocketPy projects, utilities are functions that:
Below you have a list of all utilities functions available in rocketpy.

.. automodule:: rocketpy.utilities
:members:
:members:
:exclude-members: enable_logging
119 changes: 16 additions & 103 deletions docs/user/airbrakes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,52 +106,12 @@ Defining the Controller Function

Lets start by defining a very simple controller function.

The ``controller_function`` must take in the following arguments, in this
order:

1. ``time`` (float): The current simulation time in seconds.
2. ``sampling_rate`` (float or ``None``): The rate at which the controller
function is called, measured in Hertz (Hz). It is ``None`` for continuous
controllers, so guard any ``1 / sampling_rate`` computation against ``None``.
3. ``state`` (list): The state vector of the simulation. The state
is a list containing the following values, in this order:

- ``x``: The x position of the rocket, in meters.
- ``y``: The y position of the rocket, in meters.
- ``z``: The z position of the rocket, in meters.
- ``v_x``: The x component of the velocity of the rocket, in meters per
second.
- ``v_y``: The y component of the velocity of the rocket, in meters per
second.
- ``v_z``: The z component of the velocity of the rocket, in meters per
second.
- ``e0``: The first component of the quaternion representing the rotation
of the rocket.
- ``e1``: The second component of the quaternion representing the rotation
of the rocket.
- ``e2``: The third component of the quaternion representing the rotation
of the rocket.
- ``e3``: The fourth component of the quaternion representing the rotation
of the rocket.
- ``w_x``: The x component of the angular velocity of the rocket, in
radians per second.
- ``w_y``: The y component of the angular velocity of the rocket, in
radians per second.
- ``w_z``: The z component of the angular velocity of the rocket, in
radians per second.

4. ``state_history`` (list): A record of the rocket's state at each
step throughout the simulation. The state_history is organized as
a list of lists, with each sublist containing a state vector. The
last item in the list always corresponds to the previous state
vector, providing a chronological sequence of the rocket's
evolving states.
5. ``observed_variables`` (list): A list containing the variables that
the controller function returns. The return of each controller
function call is appended to the observed_variables list. The
initial value in the first step of the simulation of this list is
provided by the ``initial_observed_variables`` argument.
6. ``air_brakes`` (AirBrakes): The ``AirBrakes`` instance being controlled.
The ``controller_function`` receives information about the simulation at the
current time step and sets the air brakes' deployment level. See
:ref:`controllers` for the full description of its arguments and call signature.
For air brakes, the controlled object (the ``air_brakes`` argument) is the
:class:`rocketpy.AirBrakes` instance, whose ``deployment_level`` the function
sets.

Our example ``controller_function`` will deploy the air brakes when the rocket
reaches 1500 meters above the ground. The deployment level will be function of the
Expand Down Expand Up @@ -227,22 +187,6 @@ Lets define the controller function:

.. note::

- The ``controller_function`` accepts 6, 7, or 8 parameters for backward
compatibility:

* **6 parameters** (original): ``time``, ``sampling_rate``, ``state``,
``state_history``, ``observed_variables``, ``air_brakes``
* **7 parameters** (with sensors): adds ``sensors`` as the 7th parameter
* **8 parameters** (with environment): adds ``sensors`` and ``environment``
as the 7th and 8th parameters

- The **environment parameter** provides access to atmospheric conditions
(wind, temperature, pressure, elevation) without relying on global variables.
This enables proper serialization of rockets with air brakes and improves
code modularity. Available methods include ``environment.elevation``,
``environment.wind_velocity_x(altitude)``, ``environment.wind_velocity_y(altitude)``,
``environment.speed_of_sound(altitude)``, and others.

- The code inside the ``controller_function`` can be as complex as needed.
Anything can be implemented inside the function, including filters,
apogee prediction, and any controller logic.
Expand All @@ -252,15 +196,10 @@ Lets define the controller function:
0 or higher than 1. If you want to disable this feature, set ``clamp`` to
``False`` when defining the air brakes.

- Anything can be returned by the ``controller_function``. The returned
values will be saved in the ``observed_variables`` list at every time step
and can then be accessed by the ``controller_function`` at the next time
step. The saved values can also be accessed after the simulation is
finished. This is useful for debugging and for plotting the results.

- The ``controller_function`` can also be defined in a separate file and
imported into the simulation script. This includes importing a ``c`` or
``cpp`` code into Python.
- See :ref:`controllers` for the controller-function signature (including the
6/7/8-parameter forms and the ``environment`` argument), how returned
values are stored in ``observed_variables``, and discrete vs. continuous
controllers.


Defining the Drag Coefficient
Expand Down Expand Up @@ -375,40 +314,14 @@ controller function. If you want to disable this feature, set ``clamp`` to
For more information on the :class:`rocketpy.AirBrakes` class
initialization, see :class:`rocketpy.AirBrakes.__init__` section.

.. _discrete-vs-continuous-controllers:

Discrete vs. Continuous Controllers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ``sampling_rate`` argument determines *when* the controller function is
called during the flight simulation:

- **Discrete controller** (``sampling_rate`` set to a number, e.g. ``10``):
the controller function is called at fixed intervals of ``1 / sampling_rate``
seconds. This mirrors a real flight computer that reads its sensors and
updates its actuators at a fixed frequency, and is the recommended choice
when you want the simulation to reflect the actual control-loop rate of your
hardware.
- **Continuous controller** (``sampling_rate=None``): the controller function
is called at *every* solver step of the numerical integrator. Use this when
you want the control law to act as a continuous function of the state rather
than a sampled one (for example, when validating a control model
analytically).

.. warning::

For continuous controllers, ``sampling_rate`` is passed to your controller
function as ``None``. Any computation such as ``1 / sampling_rate`` (a
common pattern for rate-limiting deployment, as shown above) must guard
against ``None`` to avoid a ``TypeError``.

.. note::

Discrete controllers add their sampling instants as time nodes to the
simulation, so remember to set ``time_overshoot=False`` in the ``Flight``
(see below) to make the integrator stop exactly at those instants.
Continuous controllers do not add time nodes; they are evaluated on the
integrator's own steps.
Because our controller uses ``sampling_rate=10``, it is a **discrete**
controller and adds its sampling instants as time nodes to the simulation.
Remember to set ``time_overshoot=False`` in the ``Flight`` (see below) so the
integrator stops exactly at those instants. See
:ref:`discrete-vs-continuous-controllers` for the difference between discrete
and continuous controllers.

Simulating a Flight
-------------------
Expand Down
132 changes: 132 additions & 0 deletions docs/user/controllers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
.. _controllers:

Controllers
===========

RocketPy can simulate active, in-flight control systems — such as air brakes or
other actuators — through a *controller function* that you attach to a
controllable object (for example, an :class:`rocketpy.AirBrakes` added with
``Rocket.add_air_brakes``). During the flight simulation, RocketPy calls your
controller function at the appropriate times, letting it read the current
simulation state and command the actuator.

This page describes the controller-function interface and how its call timing is
governed. For a complete, runnable example that puts these pieces together, see
the :doc:`Air Brakes Example <airbrakes>`.

The Controller Function
-----------------------

A controller function receives information about the simulation up to the
current time step and sets the state of the controlled object. It must take in
the following arguments, in this order:

1. ``time`` (float): The current simulation time in seconds.
2. ``sampling_rate`` (float or ``None``): The rate at which the controller
function is called, measured in Hertz (Hz). It is ``None`` for continuous
controllers, so guard any ``1 / sampling_rate`` computation against ``None``.
3. ``state`` (list): The state vector of the simulation. The state
is a list containing the following values, in this order:

- ``x``: The x position of the rocket, in meters.
- ``y``: The y position of the rocket, in meters.
- ``z``: The z position of the rocket, in meters.
- ``v_x``: The x component of the velocity of the rocket, in meters per
second.
- ``v_y``: The y component of the velocity of the rocket, in meters per
second.
- ``v_z``: The z component of the velocity of the rocket, in meters per
second.
- ``e0``: The first component of the quaternion representing the rotation
of the rocket.
- ``e1``: The second component of the quaternion representing the rotation
of the rocket.
- ``e2``: The third component of the quaternion representing the rotation
of the rocket.
- ``e3``: The fourth component of the quaternion representing the rotation
of the rocket.
- ``w_x``: The x component of the angular velocity of the rocket, in
radians per second.
- ``w_y``: The y component of the angular velocity of the rocket, in
radians per second.
- ``w_z``: The z component of the angular velocity of the rocket, in
radians per second.

4. ``state_history`` (list): A record of the rocket's state at each
step throughout the simulation. The state_history is organized as
a list of lists, with each sublist containing a state vector. The
last item in the list always corresponds to the previous state
vector, providing a chronological sequence of the rocket's
evolving states.
5. ``observed_variables`` (list): A list containing the variables that
the controller function returns. The return of each controller
function call is appended to the observed_variables list. The
initial value in the first step of the simulation of this list is
provided by the ``initial_observed_variables`` argument.
6. The **controlled object** (e.g. ``air_brakes``): the instance being
controlled, whose attributes the function sets (for example,
``air_brakes.deployment_level``).

.. note::

- The controller function accepts 6, 7, or 8 parameters for backward
compatibility:

* **6 parameters** (original): ``time``, ``sampling_rate``, ``state``,
``state_history``, ``observed_variables``, and the controlled object.
* **7 parameters** (with sensors): adds ``sensors`` as the 7th parameter.
* **8 parameters** (with environment): adds ``sensors`` and ``environment``
as the 7th and 8th parameters.

- The **environment parameter** provides access to atmospheric conditions
(wind, temperature, pressure, elevation) without relying on global
variables. This enables proper serialization of rockets with controllers
and improves code modularity. Available methods include
``environment.elevation``, ``environment.wind_velocity_x(altitude)``,
``environment.wind_velocity_y(altitude)``,
``environment.speed_of_sound(altitude)``, and others.

- Anything can be returned by the controller function. The returned values
are saved in the ``observed_variables`` list at every time step and can
then be accessed by the controller function at the next time step. The
saved values can also be accessed after the simulation is finished, which
is useful for debugging and for plotting the results.

- The controller function can also be defined in a separate file and
imported into the simulation script. This includes importing ``c`` or
``cpp`` code into Python.

.. _discrete-vs-continuous-controllers:

Discrete vs. Continuous Controllers
-----------------------------------

The ``sampling_rate`` argument determines *when* the controller function is
called during the flight simulation:

- **Discrete controller** (``sampling_rate`` set to a number, e.g. ``10``):
the controller function is called at fixed intervals of ``1 / sampling_rate``
seconds. This mirrors a real flight computer that reads its sensors and
updates its actuators at a fixed frequency, and is the recommended choice
when you want the simulation to reflect the actual control-loop rate of your
hardware.
- **Continuous controller** (``sampling_rate=None``): the controller function
is called at *every* solver step of the numerical integrator. Use this when
you want the control law to act as a continuous function of the state rather
than a sampled one (for example, when validating a control model
analytically).

.. warning::

For continuous controllers, ``sampling_rate`` is passed to your controller
function as ``None``. Any computation such as ``1 / sampling_rate`` (a
common pattern for rate-limiting deployment) must guard against ``None`` to
avoid a ``TypeError``.

.. note::

Discrete controllers add their sampling instants as time nodes to the
simulation, so remember to set ``time_overshoot=False`` in the ``Flight``
to make the integrator stop exactly at those instants. Continuous
controllers do not add time nodes; they are evaluated on the integrator's
own steps.
Loading
Loading