Skip to content

Parametrize

Each dynamics function has a large number of keyword-only parameters — mass, inertia matrix, thrust curves, drag coefficients, and so on. Passing all of them at every call would be impractical. parametrize solves this by loading the parameters for a named drone configuration and returning a functools.partial with those parameters pre-filled. You then call the returned object with just the state and command arrays.

from drone_models import parametrize
from drone_models.first_principles import dynamics

model = parametrize(dynamics, drone_model="cf2x_L250")

# Inspect what was pre-filled
list(model.keywords.keys())
# ['mass', 'L', 'prop_inertia', 'gravity_vec', 'J', 'J_inv',
#  'rpm2thrust', 'rpm2torque', 'mixing_matrix', 'rotor_dyn_coef', 'drag_matrix']

See the parametrize API reference for the full signature.

Available drone configurations

The following configurations ship with pre-fitted parameters. They cover both the brushed Crazyflie 2.x series and the brushless Crazyflie 2.1:

from drone_models.drones import available_drones

available_drones  # ('cf2x_L250', 'cf2x_P250', 'cf2x_T350', 'cf21B_500')
drone_model Platform
cf2x_L250 Crazyflie 2.x, L250 props
cf2x_P250 Crazyflie 2.x, P250 props
cf2x_T350 Crazyflie 2.x, T350 props
cf21B_500 Crazyflie 2.1 Brushless, 500 props

If your drone is not listed, you can identify the parameters from flight data using the system identification pipeline and inject them into any model.

Switching array backends

By default, parametrize stores parameters as NumPy arrays. For frameworks that would otherwise need to convert those arrays on every call — such as PyTorch, where NumPy arrays must become tensors — passing xp converts the parameters upfront. The backend of the outputs is always inferred from whatever arrays you pass in at call time.

import torch
import jax.numpy as jnp
from drone_models import parametrize
from drone_models.first_principles import dynamics

model_torch = parametrize(dynamics, drone_model="cf2x_L250", xp=torch)
model_jax   = parametrize(dynamics, drone_model="cf2x_L250", xp=jnp)

You can also specify a compute device — for example, to move JAX parameters to GPU at construction time:

import jax
model_gpu = parametrize(
    dynamics, drone_model="cf2x_L250",
    xp=jnp, device=jax.devices("gpu")[0],
)

See the Examples page for a runnable comparison using PyTorch.

Overriding parameters at call time

Because parametrize returns a functools.partial, the stored parameters are just keyword argument defaults. You can override any of them for a single call by passing a new value as a keyword argument — the call-time value takes precedence and the stored defaults are unchanged.

from drone_models import parametrize
from drone_models.first_principles import dynamics
import numpy as np
model = parametrize(dynamics, drone_model="cf2x_L250")

pos = np.zeros(3)
quat = np.array([0., 0., 0., 1.])
vel = np.zeros(3)
ang_vel = np.zeros(3)
rotor_vel = np.zeros(4)
cmd = np.zeros(4)

# Simulate with a 10 g payload for this call only — model.keywords is not modified.
pos_dot, *_ = model(pos, quat, vel, ang_vel, cmd, rotor_vel, mass=0.0419)

This becomes particularly useful for domain randomization: instead of baking randomized parameters into the partial, you can pass a batch of them as call-time arguments and keep the step function JIT-compiled across parameter changes. See the domain randomization example for the full pattern.

Mutating stored parameters

You can also modify model.keywords directly for changes that should persist across all future calls:

from drone_models import parametrize
from drone_models.first_principles import dynamics
import numpy as np
model = parametrize(dynamics, drone_model="cf2x_L250")
model.keywords["mass"] = np.float64(0.040)  # heavier drone — applies to every call

Warning

model.keywords is a mutable dict shared across all references to the same partial. Modifying it affects every call. Call parametrize again for an independent copy.

Using available_models

available_models is a dict mapping model names to their unparametrized dynamics functions. This is useful when selecting a model programmatically.

from drone_models import available_models
from drone_models import parametrize

list(available_models)  # ['first_principles', 'so_rpy', 'so_rpy_rotor', 'so_rpy_rotor_drag']

fn = available_models["so_rpy_rotor_drag"]
model = parametrize(fn, drone_model="cf2x_T350")

Loading raw parameters

If you need the parameter values directly — for example, to pass them to symbolic_dynamics — use load_params:

from drone_models.core import load_params

params = load_params("first_principles", "cf2x_L250")
params["mass"]        # 0.0319
params["rpm2thrust"]  # array([...])

See the load_params API reference for details.


With a parametrized model in hand, you can evaluate a single state. The next page covers running many drones simultaneously by adding batch dimensions — and how to randomize physical parameters across that batch for domain randomization.