Quadrotor¶
This page derives the differential-flatness property of a rigid-body quadrotor under collective-thrust/body-moment actuation, as established by Mellinger and Kumar [1]. The flat output is \(y = (x_Q,\, \psi)\), with \(x_Q \in \mathbb{R}^3\) the centre-of-mass position and \(\psi \in \mathbb{R}\) the yaw angle. The flat-to-state recovery requires the position \(x_Q\) up to the snap \(y^{(4)}\) and the yaw up to \(\ddot\psi\).
System¶
Let \(x_Q \in \mathbb{R}^3\) be the quadrotor centre-of-mass position in the world frame, \(R \in SO(3)\) the attitude (body-to-world), and \(\Omega \in \mathbb{R}^3\) the body-frame angular velocity so that \(\dot R = R\,\widehat{\Omega}\) (see Preliminaries). The collective thrust \(f \in \mathbb{R}_{\geq 0}\) acts along the body \(e_3\)-axis and the moment \(M \in \mathbb{R}^3\) acts in the body frame. With mass \(m > 0\), inertia \(J = J^\top \succ 0\), and gravity \(g\) along the world up-axis \(e_3\), the equations of motion are
The four scalar control inputs are \((f,\, M) \in \mathbb{R}^4\), so \(\dim u = 4\). The full state \((x_Q,\, \dot x_Q,\, R,\, \Omega)\) lives in \(\mathbb{R}^3 \times \mathbb{R}^3 \times TSO(3)\), which has dimension \(12\).
Flat output¶
Take
Then \(\dim y = 4 = \dim u\), so the dimension-matching necessary condition for differential flatness (see Differential flatness) is satisfied. The remainder of this page constructs the algebraic map from the jet of \(y\) to \((R,\, \Omega,\, f,\, M)\), confirming that \(y\) is a flat output. Inspection of the recovery below shows that \(x_Q\) is needed up to \(y^{(4)}\) (snap) and \(\psi\) is needed up to \(\ddot\psi\).
Constructive recovery¶
Step 1 — thrust magnitude and direction¶
The translational equation of (12) rearranges to \(f\, R\, e_3 = m\, (\ddot x_Q + g\, e_3)\). Collect the right-hand side into a single vector,
Since \(R\, e_3\) is a unit vector, the thrust magnitude is \(f = \|A\|\) and the thrust direction is
Both quantities are functions of \(\ddot x_Q\) alone (through \(A\)). Differentiating (15) with respect to time and using \(\dot{\|A\|} = (A \cdot \dot A) / \|A\| = \dot A \cdot b_3\),
i.e. \(\dot b_3\) is the projection of \(\dot A / \|A\|\) onto the plane \(T_{b_3} S^2\) orthogonal to \(b_3\) — automatic because \(\|b_3\| = 1\) forces \(b_3 \cdot \dot b_3 = 0\) (see the \(S^2\) tangent-space discussion in Preliminaries). Since \(\dot A = m\, x_Q^{(3)}\), computing \(\dot b_3\) needs the jerk.
Differentiating once more, the second derivative of the norm is
so
which requires \(\ddot A = m\, x_Q^{(4)}\), i.e. the snap.
Step 2 — desired attitude¶
Fix a yaw-heading hint in the world frame,
with derivatives
Provided \(b_3\) is not parallel to \(b_{1,d}\), the pair \((b_3,\, b_{1,d})\) determines a right-handed orthonormal frame via
The attitude is then
This is the same construction performed by
SO3.from_two_vectors(b3, b1d) in Preliminaries — but the
implementation in udaan.flatness.Quadrotor builds
\(R\) inline as np.column_stack([b1, b2, b3]) rather than calling
the constructor, because the intermediate columns \(b_1\) and \(b_2\) are
also needed downstream (their derivatives feed Step 3 / Step 4 via
(23)). The third column is fixed by Step 1; the
yaw angle \(\psi\) chooses the in-plane rotation of \((b_1,\, b_2)\)
about \(b_3\).
Singularity
When \(b_3\) is parallel to \(b_{1,d}\) — i.e. the quadrotor is pointed
straight up/down while the yaw hint would lie along the body axis —
the cross product in (21) vanishes and \(R\) is
not uniquely defined. The implementation in
udaan.flatness.Quadrotor raises an error at this
configuration; the regular region for the flatness map is the open
subset of jet space on which \(b_3 \times b_{1,d} \neq 0\).
Using \(b_1 = b_2 \times b_3\) rather than a second direct projection yields a compact recursion for the derivatives of the columns:
where the cross-product and norm derivatives are
A second differentiation of the same shape (with the usual \(\|\cdot\|\)-second-derivative formula from (17)) produces \(\ddot b_1,\, \ddot b_2\), and hence \(\ddot R = [\ddot b_1\;\; \ddot b_2\;\; \ddot b_3]\).
Step 3 — body-frame angular velocity¶
From \(\dot R = R\, \widehat{\Omega}\) the body rate is
using the vee map of Preliminaries. By Step 2, \(\dot R\) is known in terms of \((x_Q^{(3)},\, \dot\psi)\), so \(\Omega\) requires the jerk and \(\dot\psi\).
Step 4 — body-frame angular acceleration¶
Recovering the moment through (12) requires
\(\dot\Omega\). The implementation in
udaan.flatness.Quadrotor computes it through a
skew-projector on \(\ddot R\):
The skew-projector form is chosen for a specific numerical reason, worth unpacking against the more obvious cancellation form.
Step 4a — expand \(R^\top \ddot R\). Differentiating \(\dot R = R\,\widehat{\Omega}\) once more gives \(\ddot R = R\,\widehat{\Omega}^{\,2} + R\,\dot{\widehat{\Omega}}\), so
Rearranging gives the cancellation form,
in which the \(\widehat{\Omega}^{\,2}\) term is subtracted explicitly using the previously computed body rate.
Step 4b — skew-projector view. Note that \((\widehat{\Omega}^{\,2})^\top = (-\widehat{\Omega})(-\widehat{\Omega}) = \widehat{\Omega}^{\,2}\), so \(\widehat{\Omega}^{\,2}\) is symmetric. The linear projector \(\operatorname{skew}(X) = \tfrac{1}{2}(X - X^\top)\) annihilates symmetric matrices and is the identity on skew-symmetric matrices. Applying it to \(R^\top \ddot R = \widehat{\Omega}^{\,2} + \dot{\widehat{\Omega}}\),
Applying vee recovers (26).
Step 4c — why prefer the projector form. Both forms yield the same answer when \(R^\top R = I\) holds exactly. Under floating-point integration, however, \(R\) drifts off \(SO(3)\) and \(R^\top R = I + \mathcal{O}(\varepsilon)\) only. The cancellation form subtracts a precomputed \(\widehat{\Omega}^{\,2}\) from \(R^\top \ddot R\) and then takes vee, leaving any symmetric residual from drift on the diagonal of the result. The projector form, by contrast, rejects any symmetric component of \(R^\top \ddot R\) regardless of its source, so it remains numerically faithful to \(\dot{\widehat{\Omega}} \in \mathfrak{so}(3)\) even in the presence of drift — a small but real robustness benefit in long trajectory rollouts.
It is not about diagonal entries being zero
For \(\Omega = (1, 2, 3)\),
whose diagonal entries \((-13, -10, -5)\) are non-zero. It is the symmetry of \(\widehat{\Omega}^{\,2}\) — every off-diagonal pair \((i, j)\) and \((j, i)\) agrees — that makes the skew projector annihilate it, not any vanishing of its entries.
With \(\dot\Omega\) in hand, the body-frame Newton–Euler equation from (12) returns the feedforward moment:
Since \(\dot\Omega\) depends on \(\ddot R\), which by Step 2 requires \(x_Q^{(4)}\) (snap) and \(\ddot\psi\), the moment requires the full fourth-order position jet and second-order yaw jet.
Theorem¶
Theorem 1 (Flatness of the rigid-body quadrotor)
The rigid-body quadrotor described by (12) is differentially flat, with flat output \(y = (x_Q,\, \psi)\) as in (13). Every state and every input is an algebraic function of the fourth-order jet of \(x_Q\) and the second-order jet of \(\psi\).
Proof. Dimension matching: \(\dim y = 4 = \dim u\). Steps 1–4 above construct the smooth map
which recovers the full state and input without integrating the dynamics: \((f,\, b_3)\) from \(\ddot x_Q\) via (14)–(15), \(R\) from \((\ddot x_Q,\, \psi)\) via (21)–(22), \(\Omega\) from \((x_Q^{(3)},\, \dot\psi)\) via (25), and \(M\) from \((x_Q^{(4)},\, \ddot\psi)\) via (26)–(27). Smoothness of \(\Phi\) holds on the open subset \(\{\,\ddot x_Q + g\, e_3 \neq 0\,\} \cap \{\,b_3 \not\parallel b_{1,d}(\psi)\,\}\) — the former excludes the free-fall singularity where the thrust direction is undefined, the latter excludes the configuration where the yaw hint aligns with the thrust axis. This is exactly the form (10) on the product jet space, so the system is differentially flat with flat output \(y\).
This recovery is the substrate of the minimum-snap trajectory optimisation in [1]; the same feedforward is paired with the geometric controller of [2] for closed-loop tracking. Historical context: differential flatness itself is due to Fliess, Lévine, Martin, and Rouchon [3][4]; the quadrotor is one of its cleanest mechanical examples.
Derivative-order accounting¶
Recovered quantity |
Needs |
|---|---|
\(f\) |
\(\ddot x_Q\) |
\(b_3 = R\, e_3\) |
\(\ddot x_Q\) |
\(R\) |
\(\ddot x_Q,\, \psi\) |
\(\Omega\) |
\(x_Q^{(3)},\, \dot\psi\) |
\(\dot\Omega\) |
\(x_Q^{(4)},\, \ddot\psi\) |
\(M\) |
\(x_Q^{(4)},\, \ddot\psi\) |
The worst case is the moment \(M\), which closes the chain at \(x_Q^{(4)}\) (snap) and \(\ddot\psi\) — the orders that give the map its \(J^4 \times J^2\) type.
Jet input¶
The map \(\Phi\) of Theorem 1 takes a pair of
udaan.flatness.Jet objects — one of order 4 for the
position, one of order 2 for the yaw — packaged into a
udaan.flatness.QuadrotorFlats struct:
import numpy as np
from udaan.flatness import Jet, Quadrotor, QuadrotorFlats
# Centre-of-mass position and its first four time derivatives at t
x_jet = Jet(np.stack([
x_Q, # row 0 — position
x_Q_dot, # row 1 — velocity
x_Q_ddot, # row 2 — acceleration (gives f, b3)
x_Q_dddot, # row 3 — jerk (gives Ω)
x_Q_snap, # row 4 — snap (gives dΩ, M)
]))
x_jet.order # 4
x_jet.dim # 3
# Yaw and its first two derivatives
psi_jet = Jet(np.array([psi, psi_dot, psi_ddot]))
psi_jet.order # 2
psi_jet.dim # 1
flats = QuadrotorFlats(x=x_jet, psi=psi_jet)
ref, inputs = Quadrotor(mass=m, inertia=J).recover(flats)
# ref.position, ref.velocity, ref.acceleration,
# ref.orientation, ref.angular_velocity, ref.angular_acceleration
# inputs.thrust, inputs.moment
Truncation (Definition 3) extracts the lower-order jets
needed by intermediate stages of the recovery (for example,
x_jet.truncate(2) is enough to produce \((f,\, R)\) but not
\((\Omega,\, M)\)).