Quick Start¶
This page walks through a complete minimal workflow with the first-principles model: bind parameters to a drone, set up a state, and evaluate the state derivatives.
State and command¶
Every model takes the same state representation:
| Variable | Description | Shape | Convention |
|---|---|---|---|
pos |
Position in world frame | (3,) |
metres, NED or ENU depending on your setup |
quat |
Attitude as unit quaternion | (4,) |
scalar-last xyzw |
vel |
Linear velocity in world frame | (3,) |
m/s |
ang_vel |
Angular velocity in body frame | (3,) |
rad/s |
Quaternions are used instead of Euler angles because they have no singularities — no gimbal lock at 90° pitch — which matters when a drone tilts aggressively.
The command cmd depends on the model. For first_principles it is a (4,) array of motor angular velocities in RPM, one per motor.
Evaluate dynamics¶
parametrize loads the physical parameters for a specific drone configuration and returns a function you can call with just the state and command. Here we use the Crazyflie 2.x with L250 propellers.
import numpy as np
from drone_models import parametrize
from drone_models.first_principles import dynamics
# Bind physical parameters — returns a functools.partial with kwargs pre-filled.
model = parametrize(dynamics, drone_model="cf2x_L250")
# State: hovering at origin, upright, stationary.
pos = np.zeros(3) # [m]
quat = np.array([0., 0., 0., 1.]) # xyzw — identity (no rotation)
vel = np.zeros(3) # [m/s]
rotor_vel = np.ones(4) * 12_000. # [RPM] — motors are spinning but not yet at the 15 000 RPM
ang_vel = np.zeros(3) # [rad/s]
# Command: all four motors at 15 000 RPM (rough hover point for cf2x_L250).
cmd = np.full(4, 15_000.) # [RPM]
pos_dot, quat_dot, vel_dot, ang_vel_dot, rotor_vel_dot = model(
pos, quat, vel, ang_vel, cmd, rotor_vel
)
Outputs¶
The model returns state derivatives — the time derivative of each state variable:
| Return value | Description | Shape | Units |
|---|---|---|---|
pos_dot |
Linear velocity (equal to vel) |
(3,) |
m/s |
quat_dot |
Quaternion rate | (4,) |
1/s |
vel_dot |
Linear acceleration | (3,) |
m/s² |
ang_vel_dot |
Angular acceleration | (3,) |
rad/s² |
rotor_vel_dot |
Motor RPM rate of change | (4,) or None |
RPM/s |
These are the right-hand side of the continuous-time ODE \(\dot{x} = f(x, u)\). To simulate forward in time, integrate them with any ODE integrator (e.g. scipy.integrate.solve_ivp or a simple Euler step).
rotor_vel_dot is None when rotor_vel is not provided. In that case the model treats the commanded RPM as the instantaneous motor state — there is no spin-up lag.
Including rotor dynamics¶
Real motors don't respond instantaneously to commands. Passing the current motor state as rotor_vel enables the rotor dynamics model, which computes how the motors accelerate or decelerate toward the commanded RPM.
import numpy as np
from drone_models import parametrize
from drone_models.first_principles import dynamics
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)
cmd = np.full(4, 15_000.)
# Current RPMs lag behind the 15 000 RPM command — motors are still spinning up.
rotor_vel = np.full(4, 12_000.)
pos_dot, quat_dot, vel_dot, ang_vel_dot, rotor_vel_dot = model(
pos, quat, vel, ang_vel, cmd, rotor_vel=rotor_vel
)
rotor_vel_dot # positive — rotors accelerating toward cmd
Next steps¶
- Models — all available models and their command interfaces
- Parametrize — switching drone configurations and array backends
- Batching and Domain Randomization — vectorized evaluation over many drones
- Symbolic Models — CasADi variants for MPC and optimization
- System Identification — fitting parameters from flight data