Skip to content

Commit 9abd0ed

Browse files
authored
Reintegrate drone_models and drone_controllers into crazyflow (#71)
* Merge models back into crazyflow * Introduce consistent naming scheme for dynamics/physics/models * Improve naming consistency in docs * Finalize consistent naming for drones/models/physics/dynamics * Rename dynamics file, simplify so_rpy model, simplify models wrapper * Remove drone-models submodule * Simplify wrappers, add drone-models test suite to crazyflow * Improve tests * Fix test imports * Rename dynamics docs * Add drone model docs * Fix docs * Add controllers back into crazyflow * Improve API and add docs * Explicitly name controllers * Remove all submodules. Update deps * Update doc mistakes and benchmarks * Address comments * [WIP] Refactor parametrize * Move fused models into regular xml * Refactor parametrization * Fix docs and comments * Fix model docs. Restructure into core dynamics_euler and wrapper dynamics * Fix doctests * Disable flaky render tests in CI * Fix wrong drone descriptions
1 parent acf2d1c commit 9abd0ed

150 files changed

Lines changed: 7992 additions & 1718 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitmodules

Lines changed: 0 additions & 8 deletions
This file was deleted.

README.md

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
</div>
1414

15-
Crazyflow is a research simulator for quadrotors. It runs batched, differentiable simulations on CPU and GPU via JAX, with analytical and abstracted models for the Crazyflie 2.x family.
15+
Crazyflow is a research simulator for quadrotors. It runs batched, differentiable simulations on CPU and GPU via JAX, with analytical and abstracted dynamics for the Crazyflie 2.x family.
1616

1717
```python
1818
import numpy as np
@@ -36,10 +36,10 @@ for _ in range(100):
3636
## Features
3737

3838
- **n\_worlds x n\_drones** — batched over independent environments and multi-drone swarms simultaneously
39-
- **GPU-accelerated** — up to 914 M steps/s on an RTX 4090 (first-principles physics, 262 K worlds)
39+
- **GPU-accelerated** — up to 914 M steps/s on an RTX 4090 (first-principles dynamics, 262 K worlds)
4040
- **Differentiable**`jax.grad` works through the full dynamics and control pipeline
41-
- **First-principles models**physics model using first-principles equations and parameters identified from real-world measurements
42-
- **Abstracted models**three physics models fitted from real Crazyflie flight data
41+
- **First-principles dynamics**dynamics using first-principles equations and parameters identified from real-world measurements
42+
- **Abstracted dynamics**simplified dynamics in three flavors fitted from real Crazyflie flight data
4343
- **Modular pipelines** — step and reset are tuples of plain JAX functions; insert anything, anywhere
4444
- **MuJoCo integration** — onscreen and offscreen rendering, raycasting, and contact detection via MJX
4545

@@ -60,7 +60,7 @@ pixi shell
6060

6161
## Performance
6262

63-
First-principles physics, one drone. CPU: AMD Ryzen 9 7950X. GPU: NVIDIA RTX 4090.
63+
First-principles dynamics, one drone. CPU: AMD Ryzen 9 7950X. GPU: NVIDIA RTX 4090.
6464

6565
| n\_worlds | CPU steps/s | GPU steps/s |
6666
|---|---|---|
@@ -72,17 +72,6 @@ First-principles physics, one drone. CPU: AMD Ryzen 9 7950X. GPU: NVIDIA RTX 409
7272

7373
Full benchmarks including multi-drone scaling are in the [documentation](https://learnsyslab.github.io/crazyflow).
7474

75-
## Related packages
76-
77-
Crazyflow is built on two companion packages that can also be used independently:
78-
79-
| Package | Description |
80-
|---|---|
81-
| [drone-models](https://github.com/learnsyslab/drone-models) | Drone dynamics models (first-principles and fitted) compatible with NumPy, JAX, and PyTorch. Used by Crazyflow as the physics backend. |
82-
| [drone-controllers](https://github.com/learnsyslab/drone-controllers) | Reference controller implementations including the Mellinger geometric controller. Used by Crazyflow to provide the state and attitude control modes. |
83-
84-
Both are installed automatically as dependencies. For development, they are included as submodules in `submodules/` and installed in editable mode by the pixi environment.
85-
8675
## Citation
8776

8877
```bibtex

benchmark/compile.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import jax
55

66
from crazyflow import Sim
7+
from crazyflow.control import Control
8+
from crazyflow.dynamics import Dynamics
79

810

911
def main(cache: bool = False):
@@ -16,7 +18,7 @@ def main(cache: bool = False):
1618

1719
# Time initialization
1820
start = time.perf_counter()
19-
sim = Sim(n_worlds=1, n_drones=1, physics="sys_id", control="attitude")
21+
sim = Sim(n_worlds=1, n_drones=1, dynamics=Dynamics.so_rpy, control=Control.attitude)
2022
init_time = time.perf_counter() - start
2123

2224
# Time reset compilation
@@ -29,7 +31,7 @@ def main(cache: bool = False):
2931
sim._step.lower(sim.data, 1).compile()
3032
step_time = time.perf_counter() - start
3133

32-
print(f"Simulation startup times | {sim.physics} | {sim.control}")
34+
print(f"Simulation startup times | {sim.dynamics} | {sim.control}")
3335
print(f"Initialization: {init_time:.2f}s")
3436
print(f"Reset: {reset_time:.2f}s")
3537
print(f"Step: {step_time:.2f}s")

benchmark/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def profile_gym_env_step(
5555
num_envs=sim_config.n_worlds,
5656
device=sim_config.device,
5757
freq=sim_config.freq,
58-
physics=sim_config.physics,
58+
dynamics=sim_config.dynamics,
5959
)
6060

6161
# Action for going up (in attitude control)
@@ -151,7 +151,7 @@ def main(device: str = "cpu", n_worlds_exp: int = 6):
151151
sim_config = config_dict.ConfigDict()
152152
sim_config.n_worlds = 1
153153
sim_config.n_drones = 1
154-
sim_config.physics = "first_principles"
154+
sim_config.dynamics = "first_principles"
155155
sim_config.control = "attitude"
156156
sim_config.attitude_freq = 500
157157
sim_config.device = device

benchmark/op_count.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
def main():
66
"""Main entry point for profiling."""
7-
sim = Sim(n_worlds=1, n_drones=1, physics="first_principles", control="attitude")
7+
sim = Sim(n_worlds=1, n_drones=1, dynamics="first_principles", control="attitude")
88

99
compiled_reset = sim._reset.lower(sim.data, sim.default_data, None).compile()
1010
compiled_step = sim._step.lower(sim.data, 1).compile()
11-
op_count_reset = compiled_reset.cost_analysis()["flops"]
12-
op_count_step = compiled_step.cost_analysis()["flops"]
13-
print(f"Op counts:\n Reset: {op_count_reset}\n Step: {op_count_step}")
11+
print(f"Reset cost analysis: {compiled_reset.cost_analysis()}")
12+
print(f"Step cost analysis: {compiled_step.cost_analysis()}")
1413

1514

1615
if __name__ == "__main__":

benchmark/performance.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ def profile_step(sim_config: config_dict.ConfigDict, n_steps: int, device: str):
4242

4343

4444
def profile_gym_env_step(sim_config: config_dict.ConfigDict, n_steps: int, device: str):
45-
device = jax.devices(device)[0]
46-
4745
envs: ReachPosEnv = gymnasium.make_vec(
48-
"DroneReachPos-v0", time_horizon_in_seconds=2, num_envs=sim_config.n_worlds, **sim_config
46+
"DroneReachPos-v0",
47+
max_episode_time=10.0,
48+
num_envs=sim_config.n_worlds,
49+
dynamics=sim_config.dynamics,
50+
freq=50,
51+
device=device,
4952
)
5053

5154
# Action for going up (in attitude control)
@@ -77,7 +80,7 @@ def main():
7780
sim_config = config_dict.ConfigDict()
7881
sim_config.n_worlds = 1
7982
sim_config.n_drones = 1
80-
sim_config.physics = "first_principles"
83+
sim_config.dynamics = "first_principles"
8184
sim_config.control = "attitude"
8285
sim_config.device = device
8386

crazyflow/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import crazyflow.envs # noqa: F401, ensure gymnasium envs are registered
2020
from crazyflow.control import Control
21-
from crazyflow.sim import Physics, Sim
21+
from crazyflow.drones import available_drones
22+
from crazyflow.dynamics import Dynamics
23+
from crazyflow.sim import Sim
2224

23-
__all__ = ["Sim", "Physics", "Control"]
25+
__all__ = ["Sim", "Dynamics", "Control", "available_drones"]
2426
__version__ = "0.2.0"

crazyflow/_typing.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""This file is to be removed as soon as a proper typing is available by the official array-api."""
2+
3+
from typing import Any, TypeAlias
4+
5+
import numpy.typing as npt
6+
7+
Array: TypeAlias = Any # To be changed to array_api_typing later
8+
ArrayLike: TypeAlias = Array | npt.ArrayLike

crazyflow/control/__init__.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1-
from crazyflow.control.control import Control
1+
"""Implementations of onboard drone controllers in Python.
22
3-
__all__ = ["Control"]
3+
All controllers are implemented using the array API standard. This means that every controller is
4+
agnostic to the choice of framework and supports e.g. NumPy, JAX, or PyTorch. We also implement all
5+
controllers as pure functions to ensure that users can jit-compile them. All controllers use
6+
broadcasting to support batching of arbitrary leading dimensions.
7+
8+
We reimplement the onboard controller for two reasons:
9+
- We cannot use the C++ bindings of the firmware to differentiate through the onboard controller.
10+
- We need to implement it with JAX to enable efficient, batched computations.
11+
"""
12+
13+
from typing import Callable
14+
15+
__all__ = []
16+
17+
from crazyflow.control.core import Control, parametrize
18+
from crazyflow.control.mellinger import attitude2force_torque as mellinger_attitude2force_torque
19+
from crazyflow.control.mellinger import state2attitude as mellinger_state2attitude
20+
21+
available_controller: dict[str, Callable] = {
22+
"mellinger_state2attitude": mellinger_state2attitude,
23+
"mellinger_attitude2force_torque": mellinger_attitude2force_torque,
24+
}
25+
26+
__all__ = ["Control", "parametrize"]

crazyflow/control/control.py

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)