Skip to content

Commit 309d469

Browse files
committed
Add controllers back into crazyflow
1 parent c99c97e commit 309d469

10 files changed

Lines changed: 878 additions & 224 deletions

File tree

crazyflow/control/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
"""Implementations of onboard drone controllers in Python.
2+
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+
9+
from typing import Callable
10+
11+
__all__ = []
12+
113
from crazyflow.control.control import Control
14+
from crazyflow.control.core import parametrize
15+
from crazyflow.control.mellinger import attitude2force_torque as mellinger_attitude2force_torque
16+
from crazyflow.control.mellinger import state2attitude as mellinger_state2attitude
17+
18+
available_controller: dict[str, Callable] = {
19+
"mellinger_state2attitude": mellinger_state2attitude,
20+
"mellinger_attitude2force_torque": mellinger_attitude2force_torque,
21+
}
222

3-
__all__ = ["Control"]
23+
__all__ = ["Control", "parametrize"]

crazyflow/control/core.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Core functionalities for controller parametrization."""
2+
3+
from __future__ import annotations
4+
5+
import inspect
6+
import tomllib
7+
from functools import partial
8+
from pathlib import Path
9+
from typing import TYPE_CHECKING, Callable, ParamSpec, TypeVar
10+
11+
import numpy as np
12+
13+
if TYPE_CHECKING:
14+
from types import ModuleType
15+
16+
from crazyflow._typing import Array # To be changed to array_api_typing later
17+
18+
P = ParamSpec("P")
19+
R = TypeVar("R")
20+
21+
22+
def parametrize(
23+
fn: Callable[P, R], drone: str, xp: ModuleType | None = None, device: str | None = None
24+
) -> Callable[P, R]:
25+
"""Parametrize a controller function with the default controller parameters for a drone.
26+
27+
Args:
28+
fn: The controller function to parametrize.
29+
drone: The drone to use.
30+
xp: The array API module to use. If not provided, numpy is used.
31+
device: The device to use. If None, the device is inferred from the xp module.
32+
33+
Example:
34+
```python
35+
import numpy as np
36+
from crazyflow.control import parametrize
37+
from crazyflow.control.mellinger import state2attitude
38+
39+
ctrl = parametrize(state2attitude, "cf2x_L250")
40+
pos = np.zeros(3)
41+
quat = np.array([0.0, 0.0, 0.0, 1.0])
42+
vel = np.zeros(3)
43+
cmd = np.zeros(13)
44+
rpyt, int_pos_err = ctrl(pos, quat, vel, cmd)
45+
```
46+
47+
Returns:
48+
The parametrized controller function with all keyword argument only parameters filled in.
49+
"""
50+
try:
51+
params = load_fn_params(fn, drone, xp=xp, device=device)
52+
except KeyError as e:
53+
controller = fn.__module__.split(".")[-2]
54+
raise KeyError(
55+
f"Controller `{controller}.{fn.__name__}` not found for drone `{drone}`"
56+
) from e
57+
return partial(fn, **params)
58+
59+
60+
def load_params(controller: str, drone: str) -> dict[str, dict]:
61+
"""Load the raw parameter table for a controller and drone.
62+
63+
Reads ``crazyflow/control/<controller>/params.toml`` and returns the drone's table, nested by
64+
section (``"core"`` and one per controller function). Use [load_fn_params][load_fn_params] to
65+
select and convert the parameters a specific controller function accepts.
66+
67+
Args:
68+
controller: Name of the controller sub-package, e.g. ``"mellinger"``.
69+
drone: Name of the drone configuration, e.g. ``"cf2x_L250"``.
70+
71+
Returns:
72+
The raw, section-nested parameter dict for the drone.
73+
74+
Raises:
75+
KeyError: If ``drone`` is not found in the params.toml file.
76+
"""
77+
with open(Path(__file__).parent / f"{controller}/params.toml", "rb") as f:
78+
params = tomllib.load(f)
79+
if drone not in params:
80+
raise KeyError(f"Drone `{drone}` not found in {controller}/params.toml")
81+
return params[drone]
82+
83+
84+
def load_fn_params(
85+
fn: Callable, drone: str, xp: ModuleType | None = None, device: str | None = None
86+
) -> dict[str, Array]:
87+
"""Load the parameters a specific controller function accepts.
88+
89+
Merges the ``"core"`` section with the function's ``[drone.<fn_name>]`` section (function values
90+
take precedence), then keeps only the parameters in ``fn``'s signature.
91+
92+
Args:
93+
fn: The controller function for which to load parameters.
94+
drone: Name of the drone configuration, e.g. ``"cf2x_L250"``.
95+
xp: The array API module to use. If not provided, numpy is used.
96+
device: The device to use. If None, the device is inferred from the xp module.
97+
98+
Returns:
99+
A flat dict mapping parameter names to arrays in the requested array namespace.
100+
"""
101+
xp = np if xp is None else xp
102+
drone_params = load_params(fn.__module__.split(".")[-2], drone)
103+
merged = drone_params.get("core", {}) | drone_params.get(fn.__name__, {})
104+
accepted = set(inspect.signature(fn).parameters.keys())
105+
return {k: xp.asarray(v, device=device) for k, v in merged.items() if k in accepted}

crazyflow/control/mellinger.py

Lines changed: 0 additions & 112 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Mellinger controller reimplementation based on the Crazyflie firmware.
2+
3+
See https://ieeexplore.ieee.org/document/5980409 for details.
4+
"""
5+
6+
from crazyflow.control.mellinger.control import (
7+
MellingerAttitudeData,
8+
MellingerForceTorqueData,
9+
MellingerStateData,
10+
attitude2force_torque,
11+
force_torque2rotor_vel,
12+
sim_attitude2force_torque,
13+
sim_commit_attitude,
14+
sim_force_torque2rotor_vel,
15+
sim_state2attitude,
16+
state2attitude,
17+
)
18+
19+
__all__ = [
20+
"state2attitude",
21+
"attitude2force_torque",
22+
"force_torque2rotor_vel",
23+
"MellingerStateData",
24+
"MellingerAttitudeData",
25+
"MellingerForceTorqueData",
26+
"sim_state2attitude",
27+
"sim_attitude2force_torque",
28+
"sim_commit_attitude",
29+
"sim_force_torque2rotor_vel",
30+
]

0 commit comments

Comments
 (0)