Dynamics
LAV2 provides a unified dynamics parameter and interface framework for aerial and amphibious platforms. The implementation organizes the stack around a shared parameter object plus modality-specific models so the local MuJoCo runner and the GPU/task backends stay aligned on the same physical constants and interface assumptions.
Known modeling limitation
The module split is primarily about software structure and numerical modeling, not a claim that rotor and contact dynamics are physically fully decoupled. Coupling effects such as lift influencing ground-contact forces are not yet modeled explicitly.
Vehicle Parameters
VehicleParams is the primary parameter surface for the platform. It groups the vehicle description into:
- environment parameters
- body parameters
- propeller parameters
- ground-drive parameters
- PX4-aligned parameters
Some of those values are direct configuration inputs, while others are derived in __post_init__, such as thrust and torque coefficients, default initial RPM, and hover throttle related terms. In practice, this means VehicleParams defines both the raw platform constants and the quantities that downstream dynamics and controllers actually consume.
Mass, inertia, simulation timing, rotor coefficients, and drive geometry should be sourced from VehicleParams rather than scattered as literals across the stack.
At the module structure level, explicit dynamics implementations are organized under the common interface DynamicsBase. Ground contact is represented by wheel geometry and simulator contact constraints instead of a separate analytical track-force module.
Timing: sim_dt And step_dt
The timing fields are especially important once the same dynamics are used outside the local single-rate simulator. sim_dt is the actuator and physics integration step, while step_dt is the controller update step.
These values become distinct in frameworks with decimation, such as Isaac Lab, where the physics engine may step faster than the control policy or command update loop. The same separation is also important when higher-level and lower-level policies run asynchronously. Keeping both fields in VehicleParams avoids hiding those rate assumptions inside individual modules.
Rotor Dynamics
RotorDynamics converts per-rotor RPM commands into a batched (N, 8) output array:
- first 4 entries per environment: thrusts
- last 4 entries per environment: reaction torques
Its update rule follows the standard rotor-model structure. The commanded rotor speed is first filtered and rate-limited instead of being applied instantaneously. Thrust and reaction torque are then computed from rotor speed and motor acceleration:
Here, \(\omega_i\) is the rotor speed, \(f_i\) is thrust, \(\tau_i\) is reaction torque, \(c_T\) and \(c_M\) are the thrust and torque coefficients, and \(d_i \in \{+1, -1\}\) encodes the rotor spin direction through VehicleParams.spin_dir. The convention is +1 for clockwise and -1 for counter-clockwise. The filtered update uses separate rise and fall time constants together with explicit clipping on the RPM change rate.
For this model to be meaningful, it needs to stay aligned with the real platform parameters, especially:
- initial rotor speed
- motor inertia and time constants
- thrust and torque coefficients
- maximum RPM
- rotor spin directions
Aerodynamic Effects
The optional aerodynamic layer combines body drag, a prescribed external force, ground effect, and near-wall effect. Ground and wall effects modify each rotor force in that order; drag and the external force act on the vehicle body. Surface geometry is evaluated in each environment's local world frame, while forces and force telemetry are represented in the vehicle body frame. Backend adapters transform each aerodynamic increment into its rotor-link frame, add it to the nominal rotor force, and apply the combined force at that rotor. All four effects are disabled by default.
Body drag and prescribed force
Let \(R_{wb}\) rotate body-frame vectors into the world frame, \(R_{bw}=R_{wb}^{\mathsf T}\), and \(\mathbf v^b=R_{bw}\mathbf v^w\). The current body-force model is
where \(\mathbf C_{\mathrm{drag}}\) is diagonal, with units N s/m, and \(\mathbf F_{\mathrm{ext}}^w\) is a prescribed world-frame force. This is the force form of the mass-normalized linear drag used by Jacinto et al. in Pegasus: if their acceleration coefficient is \(\mathbf D\), then \(\mathbf C_{\mathrm{drag}}=m\mathbf D\).
\(\mathbf F_{\mathrm{ext}}^w\) represents a prescribed disturbance. A physical wind-field extension can instead derive force from relative airspeed and an identified aerodynamic law.
Per-rotor ground effect
For rotor \(i\), LAV2 uses the following per-rotor inverse-ratio model:
Here \(r_p\) is propeller radius, \(z_i\) is distance to the ground along the
negative thrust axis, and \(\kappa_g\) is a dimensionless identification
multiplier. \(\kappa_g=1\) corresponds to the Cheeseman--Bennett image-theory
relation developed for a helicopter rotor. Kan et al.'s small-UAV experiments
show substantial transfer error for hover-derived models in multirotor forward
flight. LAV2 therefore retains the empirical default \(\kappa_g=0.171\) as an
initial value for small multirotors, with platform identification providing the
task-specific value. Because this inverse-ratio model becomes singular at small
\(z_i\) for some coefficients, max_ground_effect_gain bounds the resulting
thrust multiplier (2 by default) instead of allowing unbounded amplification.
Some implementations write the denominator as
\(b-\kappa_g(r_p/(4z_i))^2\). Here \(b\) is fixed at one by the far-field condition
\(\lVert\mathbf T_{\mathrm{pre},i}\rVert/
\lVert\mathbf T_{\mathrm{ge},i}\rVert\to1\) as \(z_i\to\infty\). A fitted
\(b\ne1\) represents a height-independent thrust scale and belongs in the
baseline rotor-thrust calibration. The configurable LAV2 coefficient therefore
corresponds to ground_effect_k, while ground_effect_b=1 is implicit.
MuJoCo-Drones-Gym (arXiv:2606.08039) instead applies one additive body force,
For a weak effect, expanding LAV2's inverse ratio gives \(\Delta T_i\approx\kappa_g T_i(r_p/(4z_i))^2\); the two forms therefore agree to first order when all rotors share the same height and \(C_{\mathrm{gnd}}=\kappa_g\). The per-rotor form represents the asymmetric wrench produced by a table edge, sloped attitude, or door-adjacent surface. MuJoCo-Drones-Gym attributes this additive term to Neural Lander, while Neural Lander itself learns the unmodeled near-ground force; the analytic relation above is therefore traced to Cheeseman--Bennett.
Per-rotor wall effect
For a wall, let \(d_i\) be propeller-tip clearance, \(\bar d_i=d_i/r_p\), \(\mathbf e_3\) the world vertical, and \(\mathbf e_d\) the direction from the rotor toward the wall. LAV2 implements Ding et al.'s empirical model:
Ding et al. obtained \((a_1,b_1,a_2,b_2)=(0.21,0.34,0.06,0.54)\) by least-squares fits to a 38 mm-radius ducted propeller. Their derivation assumes quasi-steady, incompressible, inviscid, irrotational flow and neglects wall- and duct-induced drag. The coefficients and upright-flight approximation provide a starting point for a small multirotor; system identification can adapt them to a different rotor, duct, attitude range, or airframe layout.
Related model families
MuJoCo-Drones-Gym also includes three related model families:
- Its blade-drag model, attributed to Förster's Crazyflie 2.0 system identification, multiplies body velocity by the sum of rotor angular speeds. LAV2 currently uses fixed linear body drag; each model therefore has its own coefficient units and identification data.
- Its steady wind uses quadratic relative-airspeed drag; gust and Dryden modes add time-varying processes, with Dryden length scales tied to MIL-F-8785C. These provide natural extensions beyond LAV2's current prescribed force.
- Its downwash equation and coefficients are specific to vertically separated Crazyflie vehicles. Downwash is most relevant to future multi-vehicle tasks.
Surface distance is currently evaluated against infinite analytic ground and
wall planes. set_wall_plane() can update one plane per environment, while
finite edges and automatic updates for moving surfaces remain scene-level
extensions. Disturbance forces and distances are privileged simulation state;
deployment as actor observations calls for an equivalent onboard estimator.
References:
- Cheeseman and Bennett, “The Effect of the Ground on a Helicopter Rotor in Forward Flight,” ARC Reports and Memoranda No. 3021, 1955.
- Jacinto et al., “Pegasus Simulator,” ICUAS 2024, doi:10.1109/ICUAS60882.2024.10556959.
- Kan et al., “Analysis of Ground Effect for Small-Scale UAVs in Forward Flight,” RA-L 2019, doi:10.1109/LRA.2019.2929993.
- Ding et al., “Aerodynamic Effect for Collision-Free Reactive Navigation of a Small Quadcopter,” npj Robotics 2023, doi:10.1038/s44182-023-00002-9.
- Förster, “System Identification of the Crazyflie 2.0 Nano Quadrocopter,” ETH Zürich master's thesis, 2015.
- Shi et al., “Neural Lander,” ICRA 2019, doi:10.1109/ICRA.2019.8794351.
- Tayal, “MuJoCo-Drones-Gym,” 2026, arXiv:2606.08039.
- U.S. Department of Defense, MIL-F-8785C, 1980.
PX4 SITL Gazebo Classic comparison
PX4 SITL Gazebo Classic provides a useful reference for rotor-level effects,
but it is not a drop-in definition of the LAV2 aerodynamic model. Its
multirotor motor plugin applies thrust from rotor speed, reduces thrust for
velocity along the rotor axis, and adds speed-dependent in-plane rotor drag,
reaction torque, and rolling moment. Wind is supplied as a world-relative
velocity and enters through the motor model's relative-airflow calculation.
See the gazebo_motor_model.cpp
implementation and the Iris parameters in
iris.sdf.jinja.
The PX4 Classic multirotor path does not provide the analytic ground and wall effects currently exposed by LAV2. PX4 motor and drag coefficients can inform future rotor-drag work, but should not be copied directly as LAV2 wall-effect parameters.
Ground Drive Actuation
Ground platforms use articulated wheel groups and simulator contact. In MuJoCo, LAV2 sends left/right output-shaft speed targets to one drive joint per side; equality constraints synchronize each drive sprocket with its road-wheel group. ATMO likewise uses one closed-loop velocity servo per side, with equality constraints representing the synchronous belt between the front and rear wheel shells.
Isaac Lab uses the same two side-speed targets and must preserve the same one-drive-joint-per-side contract. The current LAV2 and ATMO USD assets still require follow-up topology and mechanical-coupling work to match the MJCF models. Follower joints must not receive duplicated ActionTerm targets as a substitute for asset-level coupling.
This keeps controller, actuator, and contact responsibilities separate. DifferentialDriveController performs chassis-level command allocation, the platform adapter maps each side target to physical joints, the velocity servo applies the configured gain and torque limit, and the physics engine resolves wheel-ground forces. A velocity-servo gain is a closed-loop tracking gain; it must not be replaced by the open-loop DC torque-speed slope when the command is wheel speed.
GroundDriveActuatorAdapter
represents actuator-side effects identified from real response logs. It accepts
batched ideal left/right output-shaft targets, applies a calibrated yaw
allocation scale without changing the controller's physical wheel geometry,
preserves the common/differential command under side-speed limiting, and
advances independent left/right command delays at the physics rate. Runners
only connect this model to backend actuators; they do not own drivetrain
response semantics. The same batched model exposes randomize() for
nominal-relative yaw-scale and side-gain sampling. Isaac Lab invokes it from a
reset event, configures command delay as zero here, and continues to use the
official DelayedPDActuator for randomized delay, closed-loop velocity
response, and effort limiting.
State And Command Conventions
All controller and trajectory interfaces use dict-based state and target objects. All state arrays are batched with a leading environment dimension N (num_envs). The dict keys used across the stack are:
| Key | Shape | Unit | Description |
|---|---|---|---|
pos |
(N, 3) |
m | World-frame position |
vel |
(N, 3) |
m/s | World-frame velocity |
vel_b |
(N, 3) |
m/s | Body-frame velocity |
att_euler |
(N, 3) |
rad | Roll, pitch, yaw (XYZ intrinsic) |
att_quat |
(N, 4) |
— | Quaternion (xyzw) |
att_rotmat |
(N, 9) |
— | Rotation matrix (flattened row-major) |
ang_vel |
(N, 3) |
rad/s | Body-frame angular velocity |
thrust |
(N, 1) |
N | Collective thrust target |
Flight state dicts include all of pos, vel, att_euler, att_quat, att_rotmat, and ang_vel. Ground-controller state dicts include pos, att_euler, vel_b, and ang_vel (note vel_b instead of vel).
The controller update signature is the same across flight and ground implementations of ControllerBase: update(target: dict, state: dict) -> ArrayLike. A control_mask dict gates which target channels are externally supplied and which are synthesized inside the cascade.
When N = 1 (single environment) the batched shapes remain (1, 3), (1, 4), etc. The leading environment dimension is part of the shared Array API contract for every backend.
Understanding these dict conventions makes it much easier to follow the local simulator, the controller stack, and the task wrappers.
Parallel Backend Parity
The top-level modules in lav2/dynamics/ are backend-agnostic Array API implementations. A single codebase serves NumPy, Torch, JAX, and CuPy through self._xp dispatch.
API Cross-References
- Shared parameters: VehicleParams
- Rotor model: RotorDynamics
- Aerodynamic model: AerodynamicEffects
- Aerodynamic configuration: AerodynamicEffectsCfg
- Ground actuator model: GroundDriveActuatorAdapter
- Differential-drive controller: DifferentialDriveController