Skip to content
Open
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
6 changes: 3 additions & 3 deletions crazyflow/control/mellinger/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def attitude2force_torque(
mixing_matrix: Mixing matrix for the motor forces with shape (4, 3).

Returns:
4 Motor forces [N], i_error_m
Desired forces (3,), torques (3,) and i_error_m

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We return the total force, so shape 1, no?

"""
xp = array_namespace(quat)
force_des = cmd[..., 3] # Total thrust in N
Expand Down Expand Up @@ -276,7 +276,7 @@ def force_torque2rotor_vel(

The firmware calculates PWMs for each motor, compensates for the battery voltage, and then
applies the modified PWMs to the motors. We assume perfect battery compensation here, skip the
PWM interface except for clipping, and instead return desired motor forces.
PWM interface except for clipping, and instead return desired motor velocities.

Note:
The equivalent function in the crazyflie firmware is power_distribution from
Expand All @@ -296,7 +296,7 @@ def force_torque2rotor_vel(
mixing_matrix: Mixing matrix for the motor forces with shape (4, 3).

Returns:
The desired motor forces in SI units with shape (..., 4).
The desired rotor velocities in RPMs with shape (..., 4).
"""
xp = array_namespace(torque)
assert torque.shape[-1] == 3, f"Torque must have shape (..., 3), but has {torque.shape}"
Expand Down
2 changes: 1 addition & 1 deletion crazyflow/control/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def motor_force2rotor_vel(motor_forces: Array, rpm2thrust: Array) -> Array:
rpm2thrust: RPM to thrust conversion factors.

Returns:
Array of rotor velocities in rad/s with shape (..., N).
Array of rotor velocities in RPMs with shape (..., N).
"""
xp = array_namespace(motor_forces)
return (
Expand Down
3 changes: 2 additions & 1 deletion crazyflow/dynamics/so_rpy_rotor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Extends ``so_rpy`` by adding a scalar thrust state \(F\) that captures motor spin-up and spin-down
with a first-order lag. Rotational dynamics remain a fitted second-order linear system driven by RPY
commands. The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The ``rotor_vel``
state is the current thrust in Newtons (not motor RPMs).
state is the current thrust in Newtons (not motor RPMs), carried as four entries of which only
the first enters the dynamics.

\[
\begin{aligned}
Expand Down
36 changes: 19 additions & 17 deletions crazyflow/dynamics/so_rpy_rotor/dynamics.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Second-order fitted RPY dynamics with first-order thrust dynamics.

This module extends the ``so_rpy`` dynamics by adding a scalar thrust state that models motor
spin-up and spin-down with a first-order lag. Rotational dynamics are still modelled as a fitted
second-order linear system driven by RPY commands.
This module extends the ``so_rpy`` dynamics by adding a thrust state that models motor spin-up and
spin-down with a first-order lag. Rotational dynamics are still modelled as a fitted second-order
Comment thread
ratheron marked this conversation as resolved.
linear system driven by RPY commands.

The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The ``rotor_vel`` state is a
**scalar thrust state in Newtons** (not motor RPMs).
The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The ``rotor_vel`` state holds
the thrust in Newtons rather than motor RPMs. It keeps the four entries of the shared state layout,
and only the first entry enters the dynamics.

Both a numeric implementation ([dynamics][crazyflow.dynamics.so_rpy_rotor.dynamics]) and symbolic
CasADi implementations ([symbolic_dynamics][crazyflow.dynamics.so_rpy_rotor.symbolic_dynamics],
Expand Down Expand Up @@ -70,8 +71,9 @@ def dynamics(
vel: Velocity of the drone (m/s).
ang_vel: Angular velocity of the drone (rad/s).
cmd: Roll pitch yaw (rad) and collective thrust (N) command.
rotor_vel: Speed of the 4 motors (RPMs). If None, the commanded thrust is directly
applied (not recommended). If value is given, rotor dynamics are calculated.
rotor_vel: Thrust state (N) of shape ``(4,)``, of which only the first entry is used. If
None, the commanded thrust is directly applied (not recommended). If a value is given,
thrust dynamics are calculated.
dist_f: Disturbance force (N) in the world frame acting on the CoM.
dist_t: Disturbance torque (Nm) in the world frame acting on the CoM.

Expand Down Expand Up @@ -200,8 +202,8 @@ def symbolic_dynamics(
of [symbolic_dynamics][crazyflow.dynamics.first_principles.symbolic_dynamics].

Args:
model_rotor_vel: If ``True``, the scalar thrust state is included in ``X`` and first-order
thrust dynamics are modelled. Defaults to ``_True``.
model_rotor_vel: If ``True``, the thrust state is included in ``X`` and first-order thrust
dynamics are modelled. Defaults to ``True``.
model_dist_f: If ``True``, a 3-D force disturbance is appended to ``X``.
model_dist_t: If ``True``, a 3-D torque disturbance is appended to ``X``.
mass: Drone mass in kg.
Expand All @@ -218,11 +220,11 @@ def symbolic_dynamics(
Returns:
Tuple ``(X_dot, X, U, Y)`` of CasADi ``MX`` expressions:

* ``X_dot``: State derivative, length 14 when ``model_rotor_vel=True`` (13 otherwise), plus
* ``X_dot``: State derivative, length 17 when ``model_rotor_vel=True`` (13 otherwise), plus
3 per enabled disturbance.
* ``X``: State vector ``[pos(3), quat(4), vel(3), ang_vel(3)]``, with ``rotor_vel(1)``
* ``X``: State vector ``[pos(3), quat(4), vel(3), ang_vel(3)]``, with ``rotor_vel(4)``
appended if ``model_rotor_vel=True``. Note that ``rotor_vel`` here represents the thrust
state in Newtons.
state in Newtons, and that only its first entry enters the dynamics.
* ``U``: Input vector ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``.
* ``Y``: Output ``[pos(3), quat(4)]``.
"""
Expand Down Expand Up @@ -313,8 +315,8 @@ def symbolic_dynamics_euler(
trigonometric overhead inside CasADi-based solvers.

Args:
model_rotor_vel: If ``True``, the scalar thrust state is included in ``X`` and first-order
thrust dynamics are modelled. Defaults to ``True``.
model_rotor_vel: If ``True``, the thrust state is included in ``X`` and first-order thrust
dynamics are modelled. Defaults to ``True``.
mass: Drone mass in kg.
gravity_vec: Gravity vector, shape ``(3,)``.
J: Inertia matrix, shape ``(3, 3)``.
Expand All @@ -329,10 +331,10 @@ def symbolic_dynamics_euler(
Returns:
Tuple ``(X_dot, X, U, Y)`` of CasADi ``MX`` expressions:

* ``X_dot``: State derivative, length 13 when ``model_rotor_vel=True`` (12 otherwise).
* ``X``: State vector ``[pos(3), rpy(3), vel(3), drpy(3)]``, with ``rotor_vel(1)`` appended
* ``X_dot``: State derivative, length 16 when ``model_rotor_vel=True`` (12 otherwise).
* ``X``: State vector ``[pos(3), rpy(3), vel(3), drpy(3)]``, with ``rotor_vel(4)`` appended
if ``model_rotor_vel=True``. Note that ``rotor_vel`` here represents the thrust state in
Newtons.
Newtons, and that only its first entry enters the dynamics.
* ``U``: Input vector ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``.
* ``Y``: Output ``[pos(3), rpy(3)]``.
"""
Expand Down
5 changes: 3 additions & 2 deletions crazyflow/dynamics/so_rpy_rotor_drag/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

Extends ``so_rpy_rotor`` by adding a body-frame linear drag term to the translational dynamics.
Rotational dynamics remain a fitted second-order linear system, and thrust spin-up uses a
first-order lag. The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The
``rotor_vel`` state is the current thrust in Newtons (not motor RPMs).
first-order lag. The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The
``rotor_vel`` state is the current thrust in Newtons (not motor RPMs), carried as four entries
of which only the first enters the dynamics.

\[
\begin{aligned}
Expand Down
30 changes: 16 additions & 14 deletions crazyflow/dynamics/so_rpy_rotor_drag/dynamics.py
Comment thread
ratheron marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
translational dynamics. Rotational dynamics are still modelled as a fitted second-order linear
system, and thrust spin-up uses a first-order lag.

The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The ``rotor_vel`` state is a
**scalar thrust state in Newtons** (not motor RPMs).
The command interface is ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``. The ``rotor_vel`` state holds
the thrust in Newtons (not motor RPMs). It keeps the four entries of the shared state layout,
and only the first entry enters the dynamics.

Both a numeric implementation ([dynamics][crazyflow.dynamics.so_rpy_rotor_drag.dynamics]) and
symbolic CasADi implementations
Expand Down Expand Up @@ -82,8 +83,9 @@ def dynamics(
vel: Velocity of the drone (m/s).
ang_vel: Angular velocity of the drone (rad/s).
cmd: Roll pitch yaw (rad) and collective thrust (N) command.
rotor_vel: Speed of the 4 motors (RPMs). If None, the commanded thrust is directly
applied (not recommended). If value is given, rotor dynamics are calculated.
rotor_vel: Thrust state (N) of shape ``(4,)``, of which only the first entry is used. If
None, the commanded thrust is directly applied (not recommended). If a value is given,
thrust dynamics are calculated.
dist_f: Disturbance force (N) in the world frame acting on the CoM.
dist_t: Disturbance torque (Nm) in the world frame acting on the CoM.

Expand Down Expand Up @@ -222,8 +224,8 @@ def symbolic_dynamics(
matches that of [symbolic_dynamics][crazyflow.dynamics.first_principles.symbolic_dynamics].

Args:
model_rotor_vel: If ``True``, the scalar thrust state is included in ``X`` and first-order
thrust dynamics are modelled. Defaults to ``True``.
model_rotor_vel: If ``True``, the thrust state is included in ``X`` and first-order thrust
dynamics are modelled. Defaults to ``True``.
model_dist_f: If ``True``, a 3-D force disturbance is appended to ``X``.
model_dist_t: If ``True``, a 3-D torque disturbance is appended to ``X``.
mass: Drone mass in kg.
Expand All @@ -242,11 +244,11 @@ def symbolic_dynamics(
Returns:
Tuple ``(X_dot, X, U, Y)`` of CasADi ``MX`` expressions:

* ``X_dot``: State derivative, length 14 when ``model_rotor_vel=True`` (13 otherwise), plus
* ``X_dot``: State derivative, length 17 when ``model_rotor_vel=True`` (13 otherwise), plus
3 per enabled disturbance.
* ``X``: State vector ``[pos(3), quat(4), vel(3), ang_vel(3)]``, with ``rotor_vel(1)``
* ``X``: State vector ``[pos(3), quat(4), vel(3), ang_vel(3)]``, with ``rotor_vel(4)``
appended if ``model_rotor_vel=True``. Note that ``rotor_vel`` here represents the thrust
state in Newtons.
state in Newtons, and that only its first entry enters the dynamics.
* ``U``: Input vector ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``.
* ``Y``: Output ``[pos(3), quat(4)]``.
"""
Expand Down Expand Up @@ -341,8 +343,8 @@ def symbolic_dynamics_euler(
trigonometric overhead inside CasADi-based solvers.

Args:
model_rotor_vel: If ``True``, the scalar thrust state is included in ``X`` and first-order
thrust dynamics are modelled. Defaults to ``True``.
model_rotor_vel: If ``True``, the thrust state is included in ``X`` and first-order thrust
dynamics are modelled. Defaults to ``True``.
mass: Drone mass in kg.
gravity_vec: Gravity vector, shape ``(3,)``.
J: Inertia matrix, shape ``(3, 3)``.
Expand All @@ -359,10 +361,10 @@ def symbolic_dynamics_euler(
Returns:
Tuple ``(X_dot, X, U, Y)`` of CasADi ``MX`` expressions:

* ``X_dot``: State derivative, length 13 when ``model_rotor_vel=True`` (12 otherwise).
* ``X``: State vector ``[pos(3), rpy(3), vel(3), drpy(3)]``, with ``rotor_vel(1)`` appended
* ``X_dot``: State derivative, length 16 when ``model_rotor_vel=True`` (12 otherwise).
* ``X``: State vector ``[pos(3), rpy(3), vel(3), drpy(3)]``, with ``rotor_vel(4)`` appended
if ``model_rotor_vel=True``. Note that ``rotor_vel`` here represents the thrust state in
Newtons.
Newtons, and that only its first entry enters the dynamics.
* ``U``: Input vector ``[roll_rad, pitch_rad, yaw_rad, thrust_N]``.
* ``Y``: Output ``[pos(3), rpy(3)]``.
"""
Expand Down
4 changes: 2 additions & 2 deletions crazyflow/sim/sensors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Sensors for the simulation.

:mod:`crazyflow.sim.sensors.depth` renders depth images with MuJoCo raycasting and is always
available. :mod:`crazyflow.sim.sensors.splat` renders photorealistic RGB(-D) images from gaussian
[depth][crazyflow.sim.sensors.depth] renders depth images with MuJoCo raycasting and is always
available. [splat][crazyflow.sim.sensors.splat] renders photorealistic RGB(-D) images from gaussian
splats and requires the optional ``splats`` extra.
"""
11 changes: 6 additions & 5 deletions crazyflow/sim/sensors/splat.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Gaussian splat camera sensors built on `splax <https://github.com/learnsyslab/splax>`_.
"""Gaussian splat camera sensors built on [splax](https://github.com/learnsyslab/splax).

Renders batched RGB(-D) images of the splats attached via :func:`crazyflow.sim.splat.attach_splats`
from any model camera. splax rasterizes with Warp kernels only, so this module requires the
``splats`` extra and a simulation constructed with ``device="gpu"``.
Renders batched RGB(-D) images of the splats attached via
[attach_splats][crazyflow.sim.splat.attach_splats] from any model camera. splax rasterizes with Warp
kernels only, so this module requires the ``splats`` extra and a simulation constructed with
``device="gpu"``.
"""

from __future__ import annotations
Expand Down Expand Up @@ -196,7 +197,7 @@ def build_render_splat_rgbd_fn(
) -> Callable[[SimData], Array]:
"""Build a splat RGB-D renderer for a drone selection, camera prefix, and resolution.

Mirrors :func:`build_render_splat_fn`.
Mirrors [build_render_splat_fn][crazyflow.sim.sensors.splat.build_render_splat_fn].
"""
drone_ids = _resolve_drones(sim, drones)
camera_ids = tuple(_camera_id(sim.mj_model, camera_prefix, d) for d in drone_ids)
Expand Down
7 changes: 4 additions & 3 deletions crazyflow/sim/sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ def wrapper(sim: Sim, *args: Any, **kwargs: Any) -> SimData:
class Sim:
"""Crazyflow simulation.

Used both through its object-oriented methods (:meth:`step`, :meth:`reset`, the ``*_control``
setters) and as the builder for the functional API in :mod:`crazyflow.sim.functional`, which
operates on the ``sim.data`` and pipelines constructed here.
Used both through its object-oriented methods ([step][crazyflow.sim.Sim.step],
[reset][crazyflow.sim.Sim.reset], the ``*_control`` setters) and as the builder for the
functional API in [crazyflow.sim.functional][], which operates on the ``sim.data`` and pipelines
constructed here.

The simulation is always batched. Every quantity in ``sim.data`` has a leading
``(n_worlds, n_drones, ...)`` shape, even for a single world and drone. ``n_worlds`` indexes
Expand Down
19 changes: 11 additions & 8 deletions crazyflow/sim/splat.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Gaussian splat support built on `splax <https://github.com/learnsyslab/splax>`_.
"""Gaussian splat support built on [splax](https://github.com/learnsyslab/splax).

This module owns the splat plugin state and its visualization. :func:`attach_splats` loads 3D
gaussian splatting ``.ply`` files and stores them in the simulation's plugin data, and
:class:`SplatViewer` streams the splats to a web-based viewer.
This module owns the splat plugin state and its visualization.
[attach_splats][crazyflow.sim.splat.attach_splats] loads 3D gaussian splatting ``.ply`` files and
stores them in the simulation's plugin data, and [SplatViewer][crazyflow.sim.splat.SplatViewer]
streams the splats to a web-based viewer.

The batched RGB camera sensor that renders splats lives in :mod:`crazyflow.sim.sensors.splat`.
The batched RGB camera sensor that renders splats lives in [crazyflow.sim.sensors.splat][].

Splat files must be aligned to the simulation frames. The viewer renders in the browser and works on
any device. Camera sensors rasterize on the GPU only.
Expand Down Expand Up @@ -112,11 +113,13 @@ class SplatViewer:
"""Web-based gaussian splat viewer.

Starts a ``splax.viewer.Viewer`` (viser web server) and uploads all attached splats once.
:meth:`update` then only pushes the current drone poses, so the viewer runs at real-time rates
on any device. The viewer is owned by its creator and is independent of ``sim.render()``.
[update][crazyflow.sim.splat.SplatViewer.update] then only pushes the current drone poses, so
the viewer runs at real-time rates on any device. The viewer is owned by its creator and is
independent of ``sim.render()``.

Args:
sim: The simulation to visualize. Requires :func:`attach_splats` to have been called.
sim: The simulation to visualize. Requires
[attach_splats][crazyflow.sim.splat.attach_splats] to have been called.
port: Port of the web server.
"""

Expand Down
24 changes: 16 additions & 8 deletions docs/gen_ref_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,38 +44,46 @@
summary = """\
* [Overview](index.md)
* [crazyflow](crazyflow/index.md)
* Sim
* [sim](crazyflow/sim/index.md)
* [drones](crazyflow/drones/index.md)
* [sim](crazyflow/sim/index.md)
* [sim.data](crazyflow/sim/data.md)
* [sim.functional](crazyflow/sim/functional.md)
* [sim.integration](crazyflow/sim/integration.md)
* [sim.pipeline](crazyflow/sim/pipeline.md)
* [sim.sensors](crazyflow/sim/sensors/index.md)
* [sim.sensors.depth](crazyflow/sim/sensors/depth.md)
* [sim.sensors.splat](crazyflow/sim/sensors/splat.md)
* [sim.sim](crazyflow/sim/sim.md)
* [sim.splat](crazyflow/sim/splat.md)
* [sim.visualize](crazyflow/sim/visualize.md)
* Dynamics
* [dynamics](crazyflow/dynamics/index.md)
* [dynamics](crazyflow/dynamics/index.md)
* [dynamics.core](crazyflow/dynamics/core.md)
* [dynamics.first_principles](crazyflow/dynamics/first_principles/index.md)
* [dynamics.first_principles.dynamics](crazyflow/dynamics/first_principles/dynamics.md)
* [dynamics.so_rpy](crazyflow/dynamics/so_rpy/index.md)
* [dynamics.so_rpy.dynamics](crazyflow/dynamics/so_rpy/dynamics.md)
* [dynamics.so_rpy_rotor](crazyflow/dynamics/so_rpy_rotor/index.md)
* [dynamics.so_rpy_rotor.dynamics](crazyflow/dynamics/so_rpy_rotor/dynamics.md)
* [dynamics.so_rpy_rotor_drag](crazyflow/dynamics/so_rpy_rotor_drag/index.md)
* [dynamics.so_rpy_rotor_drag.dynamics](crazyflow/dynamics/so_rpy_rotor_drag/dynamics.md)
* [dynamics.symbols](crazyflow/dynamics/symbols.md)
* Control
* [control](crazyflow/control/index.md)
* [dynamics.utils](crazyflow/dynamics/utils/index.md)
* [dynamics.utils.data_utils](crazyflow/dynamics/utils/data_utils.md)
* [dynamics.utils.identification](crazyflow/dynamics/utils/identification.md)
* [dynamics.utils.rotation](crazyflow/dynamics/utils/rotation.md)
* [control](crazyflow/control/index.md)
* [control.core](crazyflow/control/core.md)
* [control.transform](crazyflow/control/transform.md)
* [control.mellinger](crazyflow/control/mellinger/index.md)
* [control.mellinger.control](crazyflow/control/mellinger/control.md)
* Environments
* [envs](crazyflow/envs/index.md)
* [envs](crazyflow/envs/index.md)
* [envs.drone_env](crazyflow/envs/drone_env.md)
* [envs.figure_8_env](crazyflow/envs/figure_8_env.md)
* [envs.landing_env](crazyflow/envs/landing_env.md)
* [envs.reach_pos_env](crazyflow/envs/reach_pos_env.md)
* [envs.reach_vel_env](crazyflow/envs/reach_vel_env.md)
* [envs.norm_actions_wrapper](crazyflow/envs/norm_actions_wrapper.md)
* [exception](crazyflow/exception.md)
* [utils](crazyflow/utils.md)
"""

Expand Down
6 changes: 0 additions & 6 deletions docs/get-started/index.md
Comment thread
amacati marked this conversation as resolved.

This file was deleted.

2 changes: 1 addition & 1 deletion docs/user-guide/dynamics/dynamics-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dynamics = parametrize(dynamics, drone="cf2x_L250")

# Reuses pos, quat, vel, ang_vel from above; the command interface is what differs
cmd = np.array([0.0, 0.0, 0.0, 0.31]) # [roll_rad, pitch_rad, yaw_rad, thrust_N]
rotor_vel = np.array([0.31]) # shape (1,) — current thrust state [N]; None to skip thrust dynamics
rotor_vel = np.full(4, 0.31) # shape (4,) — thrust state [N]; None to skip thrust dynamics

pos_dot, quat_dot, vel_dot, ang_vel_dot, rotor_vel_dot = dynamics(
pos, quat, vel, ang_vel, cmd, rotor_vel
Expand Down
Loading
Loading