You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
All controllers are built on Array API operations that broadcast over leading dimensions. Add a leading batch dimension to your state and command arrays and the controller evaluates all instances in a single call, with no loops and no special API.
4
+
5
+
```python
6
+
import numpy as np
7
+
from crazyflow.control import parametrize
8
+
from crazyflow.control.mellinger import state2attitude
A controller is a function that maps the current drone state and a command to actuator outputs. Every controller in `crazyflow.control` is:
4
+
5
+
-**A pure function**: no hidden state; integral errors are explicit return values you pass back on the next call
6
+
-**Array-API compatible**: works identically with NumPy, JAX, PyTorch, or any compliant library
7
+
-**Batchable**: add leading dimensions to any input array and the function evaluates all instances at once
8
+
9
+
## The Mellinger pipeline
10
+
11
+
The Mellinger controller [[1]](#references) is split into three stages that form a pipeline. Each stage can be used on its own, or all three can be chained to convert a full-state setpoint into individual motor speeds.
12
+
13
+
| Stage | Function | Takes | Produces |
14
+
|---|---|---|---|
15
+
| 1 |[`state2attitude`](mellinger.md#state-to-attitude)| State + 13-element setpoint | RPYT command + position integral error |
16
+
| 2 |[`attitude2force_torque`](mellinger.md#attitude-to-force-torque)| Attitude + RPYT command | Collective force, body torques + angular velocity integral error |
17
+
| 3 |[`force_torque2rotor_vel`](mellinger.md#force-torque-to-rotor-velocities)| Force + torques | 4 motor speeds [RPM]|
Copy file name to clipboardExpand all lines: docs/user-guide/control/index.md
+13-2Lines changed: 13 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,7 +162,18 @@ Each control mode has its own update rate. The dynamics tick (`freq`) is always
162
162
163
163
The simulator applies a new command only when the control tick fires. Between ticks, the previous command is held. The number of dynamics steps per control tick is `freq // control_freq`.
164
164
165
+
## Using the controllers standalone
166
+
167
+
The control modes above are how the simulator drives the onboard controllers. Those controllers also live in `crazyflow.control` as a self-contained library of pure functions, usable on their own for control design, learning-based policies, or as a reference implementation, independent of `Sim`. The following guides cover that standalone API:
168
+
169
+
-[Controllers](controllers.md): the controller interface and the Mellinger pipeline
170
+
-[Mellinger controller](mellinger.md): the three stages, their inputs and outputs
171
+
-[Parametrization](parametrize.md): binding a controller to a drone configuration
172
+
-[Integral errors](integral-errors.md): carrying controller state across calls
173
+
-[Batching](batching.md): evaluating many drones at once
174
+
-[JIT compilation](jit.md): compiling controllers with `jax.jit`
175
+
165
176
## Next steps
166
177
167
-
-[Functional API](functional-api.md) — running control inside JIT with `F.controllable`
168
-
-[Dynamics](dynamics/index.md) — compatibility between dynamics and control modes
178
+
-[Functional API](../functional-api.md): running control inside JIT with `F.controllable`
179
+
-[Dynamics](../dynamics/index.md): compatibility between dynamics and control modes
The Mellinger controller uses integral terms to reject steady-state errors. Because every controller in `crazyflow.control` is a **pure function**, integral state cannot be stored inside the function. It is an explicit return value that you pass back on the next call.
4
+
5
+
## Initialisation
6
+
7
+
You have two ways to start the integral error at zero:
8
+
9
+
1. Pass `None` (or omit the argument). The controller then creates the zero array internally.
10
+
2. Create the zero array yourself and pass it explicitly. The integral error has the same shape as the position (for `pos_err_i`) or angular velocity (for `r_int_error`), so a zero array of that shape works.
11
+
12
+
```python
13
+
import numpy as np
14
+
from crazyflow.control import parametrize
15
+
from crazyflow.control.mellinger import state2attitude
16
+
17
+
ctrl = parametrize(state2attitude, "cf2x_L250")
18
+
pos = np.zeros(3)
19
+
quat = np.array([0.0, 0.0, 0.0, 1.0])
20
+
vel = np.zeros(3)
21
+
cmd = np.zeros(13)
22
+
23
+
# Option 1: let the controller initialise the integral error.
Under `jax.jit`, prefer option 2. Passing `None` on the first call and an array on the following calls changes the argument structure, so JAX traces and compiles the function twice. Passing a zero array from the start keeps the input structure constant and compiles only once.
31
+
32
+
## Carrying errors across timesteps
33
+
34
+
Pass the returned error straight back as `pos_err_i` on the next call:
35
+
36
+
```python
37
+
import numpy as np
38
+
from crazyflow.control import parametrize
39
+
from crazyflow.control.mellinger import state2attitude
# After 10 steps at 100 Hz, integral error is approximately 10 * 0.01 = 0.1 m.
53
+
```
54
+
55
+
## Both stages have integral errors
56
+
57
+
`state2attitude` tracks position error via `pos_err_i`. `attitude2force_torque` tracks angular velocity error via `r_int_error`. Manage them independently:
58
+
59
+
```python
60
+
import numpy as np
61
+
from crazyflow.control import parametrize
62
+
from crazyflow.control.mellinger import attitude2force_torque, state2attitude
Every controller is a pure function with no hidden state or side effects. In addition, they are implemented exclusively with Array API operations that are compatible with lazy JIT frameworks. Together, these two properties mean that every controller can be JIT compiled without any modification.
4
+
5
+
```python
6
+
import jax
7
+
import jax.numpy as jnp
8
+
from crazyflow.control import parametrize
9
+
from crazyflow.control.mellinger import state2attitude
0 commit comments