diff --git a/crazyflow/control/mellinger/control.py b/crazyflow/control/mellinger/control.py index e4a7f0a..b122970 100644 --- a/crazyflow/control/mellinger/control.py +++ b/crazyflow/control/mellinger/control.py @@ -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 force (1,), torques (3,) and i_error_m """ xp = array_namespace(quat) force_des = cmd[..., 3] # Total thrust in N @@ -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 @@ -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}" diff --git a/crazyflow/control/transform.py b/crazyflow/control/transform.py index ee1484b..7bbfcef 100644 --- a/crazyflow/control/transform.py +++ b/crazyflow/control/transform.py @@ -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 ( diff --git a/crazyflow/dynamics/so_rpy_rotor/__init__.py b/crazyflow/dynamics/so_rpy_rotor/__init__.py index 1853dc2..059b8fd 100644 --- a/crazyflow/dynamics/so_rpy_rotor/__init__.py +++ b/crazyflow/dynamics/so_rpy_rotor/__init__.py @@ -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} diff --git a/crazyflow/dynamics/so_rpy_rotor/dynamics.py b/crazyflow/dynamics/so_rpy_rotor/dynamics.py index 6abc700..eef3723 100644 --- a/crazyflow/dynamics/so_rpy_rotor/dynamics.py +++ b/crazyflow/dynamics/so_rpy_rotor/dynamics.py @@ -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 +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], @@ -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. @@ -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. @@ -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)]``. """ @@ -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)``. @@ -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)]``. """ diff --git a/crazyflow/dynamics/so_rpy_rotor_drag/__init__.py b/crazyflow/dynamics/so_rpy_rotor_drag/__init__.py index 1495895..4113992 100644 --- a/crazyflow/dynamics/so_rpy_rotor_drag/__init__.py +++ b/crazyflow/dynamics/so_rpy_rotor_drag/__init__.py @@ -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} diff --git a/crazyflow/dynamics/so_rpy_rotor_drag/dynamics.py b/crazyflow/dynamics/so_rpy_rotor_drag/dynamics.py index 6155a72..c72fdc6 100644 --- a/crazyflow/dynamics/so_rpy_rotor_drag/dynamics.py +++ b/crazyflow/dynamics/so_rpy_rotor_drag/dynamics.py @@ -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 @@ -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. @@ -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. @@ -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)]``. """ @@ -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)``. @@ -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)]``. """ diff --git a/crazyflow/sim/sensors/__init__.py b/crazyflow/sim/sensors/__init__.py index 730b49f..e345cfb 100644 --- a/crazyflow/sim/sensors/__init__.py +++ b/crazyflow/sim/sensors/__init__.py @@ -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. """ diff --git a/crazyflow/sim/sensors/splat.py b/crazyflow/sim/sensors/splat.py index d898d1c..03513e1 100644 --- a/crazyflow/sim/sensors/splat.py +++ b/crazyflow/sim/sensors/splat.py @@ -1,8 +1,9 @@ -"""Gaussian splat camera sensors built on `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 @@ -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) diff --git a/crazyflow/sim/sim.py b/crazyflow/sim/sim.py index b0a8f26..a4abc88 100644 --- a/crazyflow/sim/sim.py +++ b/crazyflow/sim/sim.py @@ -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 diff --git a/crazyflow/sim/splat.py b/crazyflow/sim/splat.py index 55a8dec..9a3fbf5 100644 --- a/crazyflow/sim/splat.py +++ b/crazyflow/sim/splat.py @@ -1,10 +1,11 @@ -"""Gaussian splat support built on `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. @@ -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. """ diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index 180b630..52d418a 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -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) """ diff --git a/docs/get-started/index.md b/docs/get-started/index.md deleted file mode 100644 index b40ff59..0000000 --- a/docs/get-started/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Get Started - -Install Crazyflow and run your first simulation in minutes. - -- [Installation](installation.md) — pip, pixi, GPU, and from-source options -- [Quick Start](quick-start.md) — step-by-step walkthrough of the object-oriented API diff --git a/docs/user-guide/dynamics/dynamics-functions.md b/docs/user-guide/dynamics/dynamics-functions.md index e5aed21..4287ddb 100644 --- a/docs/user-guide/dynamics/dynamics-functions.md +++ b/docs/user-guide/dynamics/dynamics-functions.md @@ -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 diff --git a/docs/user-guide/dynamics/symbolic.md b/docs/user-guide/dynamics/symbolic.md index 2b052da..a16b306 100644 --- a/docs/user-guide/dynamics/symbolic.md +++ b/docs/user-guide/dynamics/symbolic.md @@ -69,7 +69,7 @@ State vector layout with `model_rotor_vel=True`: | 3–5 | `rpy` (roll/pitch/yaw) | rad | | 6–8 | `vel` | m/s | | 9–11 | `drpy` (RPY rates) | rad/s | -| 12 | thrust state | N | +| 12–15 | thrust state (only index 12 enters the dynamics) | N | ## Wrapping for Acados / IPOPT diff --git a/docs/user-guide/oo-api.md b/docs/user-guide/oo-api.md index 6cd2d91..b8389bc 100644 --- a/docs/user-guide/oo-api.md +++ b/docs/user-guide/oo-api.md @@ -17,7 +17,7 @@ from crazyflow.control import Control sim = Sim( n_worlds=1, n_drones=1, - drone="cf2x_L250", + drone="cf21B_500", dynamics=Dynamics.first_principles, control=Control.state, integrator=Integrator.rk4, @@ -31,16 +31,18 @@ sim.reset() Key constructor arguments: -| Argument | Default | Description | -|---|---|---| -| `n_worlds` | 1 | Number of independent parallel environments | -| `n_drones` | 1 | Drones per world | -| `drone` | `"cf2x_L250"` | Drone configuration (see `crazyflow.available_drones`) | -| `dynamics` | `Dynamics.default` | Dynamics | -| `control` | `Control.default` | Control mode | -| `integrator` | `Integrator.default` | Numerical integrator | -| `freq` | 500 | Dynamics frequency, Hz | -| `device` | `"cpu"` | `"cpu"` or `"gpu"` | +| Argument | Description | +|---|---| +| `n_worlds` | Number of independent parallel environments | +| `n_drones` | Drones per world | +| `drone` | Drone configuration (see `crazyflow.available_drones`) | +| `dynamics` | Dynamics | +| `control` | Control mode | +| `integrator` | Numerical integrator | +| `freq` | Dynamics frequency, Hz | +| `device` | `"cpu"` or `"gpu"` | + +See [`Sim`][crazyflow.sim.Sim] in the API reference for the defaults and the full argument list. ## Control methods diff --git a/docs/user-guide/pipelines.md b/docs/user-guide/pipelines.md index aeba866..c4a79df 100644 --- a/docs/user-guide/pipelines.md +++ b/docs/user-guide/pipelines.md @@ -19,24 +19,24 @@ Both pipelines are constructed at `Sim` initialisation and compiled into a singl ## The step pipeline -`sim.step_pipeline` contains four stages by default: +`sim.step_pipeline` contains multiple stages by default: 1. **Control functions** — convert the staged command through the control hierarchy (state → attitude → force/torque → rotor velocities, depending on the selected mode) 2. **Integrator** (`integration`) — advance the ODE one dynamics step (Euler, RK4, or symplectic Euler) 3. **Step counter** (`increment_steps`) — increment `data.core.steps` 4. **Floor clip** (`clip_floor_pos`) — prevent drones from passing through the floor -```python -from crazyflow.sim import Sim +```pycon +>>> from crazyflow.sim import Sim +>>> sim = Sim() +>>> print(tuple(sim.step_pipeline.keys())) +('attitude_controller', 'force_torque_controller', 'integration', 'increment_steps', 'clip_floor_pos') -sim = Sim() -print(tuple(sim.step_pipeline.keys())) -# ('step_attitude_controller', 'step_force_torque_controller', 'integration', 'increment_steps', 'clip_floor_pos') ``` ## The reset pipeline -`sim.reset_pipeline` is empty by default. When `sim.reset()` is called, it first restores `SimData` to the default state, then runs every function in the reset pipeline in order. Each reset stage has the signature `(data: SimData, default_data: SimData, mask: Array | None) -> SimData`. The `default_data` argument holds the freshly-restored default state, which is useful for selectively reverting fields. +`sim.reset_pipeline` holds a single `reset` stage that restores `SimData` to the default state. Every stage appended after it runs in order on the restored data. Each reset stage has the signature `(data: SimData, default_data: SimData, mask: Array | None) -> SimData`. The `default_data` argument holds the freshly-restored default state, which is useful for selectively reverting fields. Populate `sim.reset_pipeline` to add episode-level randomization without modifying the default state. @@ -44,13 +44,18 @@ Populate `sim.reset_pipeline` to add episode-level randomization without modifyi Stages are addressed by name. Use `insert_fn_before` / `insert_fn_after` to place a function relative to an existing stage, `append_fn` to add it at the end, and `replace_fn` to swap a stage's implementation. New stages are named after the function's `__name__` unless an explicit name is given; names must be unique within a pipeline. -```{ .python continuation } +```python +from crazyflow.sim import Sim from crazyflow.sim.data import SimData from crazyflow.sim.pipeline import insert_fn_before +sim = Sim() + + def disturbance_fn(data: SimData) -> SimData: return data.replace(states=data.states.replace(vel=data.states.vel + 1e-5)) + insert_fn_before(sim.step_pipeline, "integration", disturbance_fn) sim.build_step_fn() # recompile ``` diff --git a/properdocs.yml b/properdocs.yml index abedf78..04f557a 100644 --- a/properdocs.yml +++ b/properdocs.yml @@ -6,6 +6,12 @@ repo_url: https://github.com/learnsyslab/crazyflow docs_dir: docs +validation: + omitted_files: warn + absolute_links: warn + unrecognized_links: warn + anchors: warn + theme: name: material logo: img/logo_white.svg @@ -57,6 +63,7 @@ nav: - System identification: user-guide/dynamics/system-identification.md - Control Modes: - user-guide/control/index.md + - Controllers: user-guide/control/controllers.md - Mellinger controller: user-guide/control/mellinger.md - Parametrization: user-guide/control/parametrize.md - Integral errors: user-guide/control/integral-errors.md diff --git a/pyproject.toml b/pyproject.toml index 835189f..c759af7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -192,7 +192,7 @@ pytest-markdown-docs = "*" [tool.pixi.feature.tests.tasks] tests = { cmd = "pytest -v tests", description = "Run tests" } -test-docs = { cmd = "pytest -v --markdown-docs --markdown-docs-syntax=superfences crazyflow/ docs/ --ignore=docs/gen_ref_pages.py", description = "Run doctests in docs and docstrings" } +test-docs = { cmd = "pytest -v --markdown-docs --markdown-docs-syntax=superfences --doctest-glob='*.md' crazyflow/ docs/ --ignore=docs/gen_ref_pages.py", description = "Run doctests in docs and docstrings" } [tool.pixi.feature.docs.dependencies] python = ">=3.10" @@ -208,7 +208,7 @@ mkdocs-section-index = ">=0.3.0" mkdocs-charts-plugin = ">=0.0.10" [tool.pixi.feature.docs.tasks] -docs-build = { cmd = "properdocs build", description = "Build docs" } +docs-build = { cmd = "properdocs build --strict", description = "Build docs" } docs-serve = { cmd = "properdocs serve --livereload", description = "Serve docs locally" } [tool.pixi.feature.dist.pypi-dependencies]