Skip to content

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:

\[ f_i = c_T \omega_i^2 \]
\[ \tau_i = c_M \omega_i^2 d_i \]

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

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.

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 reduce to (1, 3), (1, 4), etc. Legacy NumPy implementations under lav2/dynamics/numpy/ use unbatched shapes (e.g. (3,), (4,)) and are not Array API-compatible.

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. The numpy/ and torch/ subpackages are legacy reference implementations preserved for benchmarking; new dynamics modules should target the Array API top-level.

API Cross-References