Controllers

Each model in udaan ships with a default controller stack. You can swap or extend any controller in-place on a live model instance — the base classes expose position_controller, attitude_controller, and (for payload models) payload_controller properties.

Quadrotor

Controller

Class

Role

Notes

Geometric SE(3) attitude

GeometricAttitudeController

inner attitude loop

default; see ../theory/controllers/quadrotor-se3

Geometric L1-adaptive attitude

GeometricL1AttitudeController

inner attitude loop with disturbance estimator

robust to mass/inertia error

PD position

PositionPDController

outer position loop

default; tracks \(x^d(t)\)

L1-adaptive position

PositionL1Controller

outer position loop with disturbance estimator

mass/wind compensation

Direct propeller-force

DirectPropellerForceController

motor-level passthrough

SysID / open-loop experiments

Quadrotor with cable-suspended payload

Controller

Class

Role

Notes

Cascaded geometric payload

QuadCSPayloadController

payload position → cable attitude → quad attitude

default; see ../theory/controllers/quadrotor-payload

Swapping a controller

All controllers follow a compute(t, state) -> input contract, so swapping is a single assignment:

from udaan.models.quadrotor import QuadrotorMujoco
from udaan.control.quadrotor import PositionL1Controller, GeometricL1AttitudeController

mdl = QuadrotorMujoco(render=True)
mdl.position_controller = PositionL1Controller(mass=mdl.mass, setpoint=my_traj)
mdl.attitude_controller = GeometricL1AttitudeController(
    mass=mdl.mass, inertia=mdl.inertia,
)
mdl.simulate(tf=10.0, position=[1.0, 1.0, 0.0])

Tuning gains

Each controller exposes its gains either through constructor kwargs or through a ._gains attribute. For the payload controller:

import numpy as np

mdl._payload_controller._gain_pos.kp = np.array([4.0, 4.0, 8.0])
mdl._payload_controller._gain_pos.kd = np.array([3.0, 3.0, 6.0])
mdl._payload_controller._gain_cable.kp = np.array([24.0, 24.0, 24.0])
mdl._payload_controller._gain_cable.kd = np.array([8.0, 8.0, 8.0])

The defaults ship with the values shown above — verified stable over the benchmark trajectories used in the test suite.

Writing your own controller

Subclass Controller (or one of the existing PD/L1 bases) and implement compute(self, t, state) -> np.ndarray. Any instance can then be assigned to the model’s controller property. See PositionPDController for the simplest template.