Source code for udaan.control.quadrotor.geometric_attitude

"""Geometric attitude controller on SE(3) for quadrotor.

Taeyoung Lee, Melvin Leok, and N. Harris McClamroch,
"Geometric Tracking Control of a Quadrotor UAV on SE(3)", CDC 2010.
Paper: http://www.math.ucsd.edu/~mleok/pdf/LeLeMc2010_quadrotor.pdf
"""

from __future__ import annotations

import numpy as np

from ...control import Controller, Gains
from ...core.defaults import DEFAULT_ATT_KD, DEFAULT_ATT_KP
from ...core.types import Vec3
from ...manif import SO3, TSO3
from ...utils import hat
from ...utils.logging import get_logger

_logger = get_logger(__name__)


[docs] class GeometricAttitudeController(Controller): """Geometric tracking control of a quadrotor UAV on SE(3). Control law (Lee, Leok, McClamroch 2010, Eq. 18-19): M = -kR·eR - kΩ·eΩ + Ω × J·Ω - J·(hat(Ω)·Rᵀ·Rd·Ωd - Rᵀ·Rd·dΩd) where: eR = 0.5·vee(Rdᵀ·R - Rᵀ·Rd) (attitude error on so(3)) eΩ = Ω - Rᵀ·Rd·Ωd (angular velocity error) f = F · R·e3 (scalar thrust) """
[docs] def __init__(self, **kwargs): self._gains = Gains(kp=DEFAULT_ATT_KP.copy(), kd=DEFAULT_ATT_KD.copy()) self._inertia = np.eye(3) self._inertia_inv = np.eye(3) if "inertia" in kwargs: self.inertia = kwargs["inertia"] else: _logger.error("Inertia not provided")
@property def inertia(self): return self._inertia @inertia.setter def inertia(self, inertia): self._inertia = inertia self._inertia_inv = np.linalg.inv(inertia) def _cmd_accel_to_cmd_att(self, accel: Vec3) -> SO3: """Convert desired acceleration to desired attitude. Returns identity for near-zero acceleration to avoid singularity. """ MIN_ACCEL_NORM = 0.001 norm_accel = np.linalg.norm(accel) if norm_accel < MIN_ACCEL_NORM: return SO3() b3 = accel / norm_accel b1d = np.array([1.0, 0.0, 0.0]) return SO3.from_two_vectors(b3, b1d)
[docs] def compute( self, t: float, state: tuple[SO3, TSO3], thrust_force: Vec3, desired_att: tuple[SO3, TSO3, Vec3] | None = None, ) -> tuple[float, Vec3]: """Compute scalar thrust and torque vector. Args: state: (R, Omega) — rotation matrix and body angular velocity. thrust_force: desired thrust force vector in world frame. desired_att: optional (Rd, Omegad, dOmegad) from differential flatness. When provided, overrides the attitude derived from thrust direction and adds feedforward terms. Returns: (f, M) — scalar thrust and 3D torque vector. """ R, Omega = state Rd: SO3 = self._cmd_accel_to_cmd_att(thrust_force) if desired_att is not None: _, Omegad, dOmegad = desired_att else: Omegad: TSO3 = TSO3() dOmegad: Vec3 = np.zeros(3) eR: TSO3 = R - Rd eOm: TSO3 = Omega - Omegad.transport(Rd, R) RtRd = R.T @ Rd M = ( -self._gains.kp * eR.vector - self._gains.kd * eOm.vector + np.cross(Omega, self.inertia @ Omega) ) M += -self.inertia @ (hat(Omega) @ RtRd @ Omegad - RtRd @ dOmegad) f = thrust_force.dot(R[:, 2]) return f, M