Parametrize¶
Every controller function takes physical parameters (gains, mass, mixing matrix, PWM bounds) as keyword-only arguments, and the exact values differ per drone. Rather than passing them at every call site, parametrize loads them for a named drone and binds them upfront, so call sites only need to provide state and command.
The parameters stay individually accessible after binding. Because they are plain keyword-argument defaults on a functools.partial, any of them can be overridden at call time, or batched across a set of environments, without re-parametrizing the function. This makes it straightforward to randomize physical properties across a simulated batch.
from crazyflow.control import parametrize
from crazyflow.control.mellinger import state2attitude
ctrl = parametrize(state2attitude, "cf2x_L250")
# Inspect what was bound
list(ctrl.keywords.keys())
# ['mass', 'kp', 'kd', 'ki', 'gravity_vec', 'mass_thrust',
# 'int_err_max', 'thrust_max', 'pwm_max']
Overriding parameters at call time¶
Because parametrize returns a functools.partial, the bound parameters are just keyword-argument defaults. Pass a different value at call time to override for that call only; ctrl.keywords is not modified:
import numpy as np
from crazyflow.control import parametrize
from crazyflow.control.mellinger import state2attitude
ctrl = parametrize(state2attitude, "cf2x_L250")
pos = np.zeros(3)
quat = np.array([0.0, 0.0, 0.0, 1.0])
vel = np.zeros(3)
cmd = np.zeros(13)
# Simulate with a heavier drone for this call only.
rpyt, _ = ctrl(pos, quat, vel, cmd, mass=0.035)
To make a change persist across all future calls, mutate ctrl.keywords directly:
import numpy as np
from crazyflow.control import parametrize
from crazyflow.control.mellinger import state2attitude
ctrl = parametrize(state2attitude, "cf2x_L250")
ctrl.keywords["mass"] = np.float64(0.035)
Warning
ctrl.keywords is a mutable dict shared across all references to the same partial. Call parametrize again for an independent copy.
Available drone configurations¶
The following configurations ship with pre-fitted parameters:
drone |
Platform |
|---|---|
"cf2x_L250" |
Crazyflie 2.x |
"cf2x_P250" |
Crazyflie 2.x, plus propellers |
"cf2x_T350" |
Crazyflie 2.x, thrust upgrade kit |
"cf21B_500" |
Crazyflie 2.1 Brushless |
Pass the drone name as a plain string:
import numpy as np
from crazyflow.control import parametrize
from crazyflow.control.mellinger import state2attitude
ctrl = parametrize(state2attitude, "cf2x_L250")
pos = np.zeros(3)
quat = np.array([0.0, 0.0, 0.0, 1.0])
vel = np.zeros(3)
cmd = np.zeros(13)
rpyt, _ = ctrl(pos, quat, vel, cmd)
Loading raw parameters¶
Use load_params to inspect or override the values that parametrize would bind for a specific controller function:
from crazyflow.control.core import load_params
from crazyflow.control.mellinger import state2attitude
params = load_params(state2attitude, "cf2x_L250")
float(params["mass"]) # 0.029
Switching array backends¶
By default parameters are stored as NumPy arrays. Pass xp to convert them upfront, which avoids per-call conversion overhead in frameworks like PyTorch or JAX:
import torch
from crazyflow.control import parametrize
from crazyflow.control.mellinger import state2attitude
ctrl = parametrize(state2attitude, "cf2x_L250", xp=torch)
You can also specify a compute device:
import jax
import jax.numpy as jnp
from crazyflow.control import parametrize
from crazyflow.control.mellinger import state2attitude
ctrl = parametrize(state2attitude, "cf2x_L250", xp=jnp, device=jax.devices("cpu")[0])
The output backend is always inferred from the arrays you pass at call time, regardless of where the parameters live.