Hover equilibrium & the linear model · 線性化、配平與懸停平衡
Flight Dynamics & Intelligent Control Technologies Freeze the nonlinear quad at hover, and out falls the \( (A,B) \) every controller is built on.
Recap → today
From a nonlinear model to a design model
Wk 1: frames, ZYX Euler, quaternions, the conventions sheet.
Wk 2–3: the 12-state nonlinear model \( \dot{\mathbf{x}}=f(\mathbf{x},\mathbf{u}) \) and the X-frame mixer.
Today: pin it at hover, linearize → \( (A,B) \), read the open-loop poles.
Why bother linearizing?
Almost all the classical & optimal control we use next — pole placement, LQR (Wk 7),
MPC (Wk 11–12) — is designed on a linear model. Hover is where we get one.
Learning objectives
By the end of today you can…
Define the hover trim: level, at rest, thrust = weight, zero torque ⇒ \( f(\mathbf{x}_{eq},\mathbf{u}_{eq})=\mathbf 0 \).
Numerically linearize \( f(\mathbf{x},\mathbf{u}) \) about hover by finite differences → \( (A,B) \).
Read the open-loop poles: hover is a chain of integrators ⇒ not asymptotically stable.
Confirm the pair is controllable (rank 12) — the prerequisite for LQR/MPC.
Recall the RK4 closed-loop loop and the SimLog you will plot.
Lab Build \( (A,B) \), plot the pole map, confirm rank = 12.
The equilibrium
What is hover trim?
Level — zero roll, pitch, yaw: \( \boldsymbol\eta=\mathbf 0 \).
At rest — zero linear velocity and body rates: \( \mathbf v=\boldsymbol\omega=\mathbf 0 \).
Thrust balances weight — \( T=mg \), pointing \(+\)body-z (which, level, is world up).
In quadsim this is exactly analysis.hover_equilibrium(p) → returns (x_eq, u_eq) with u_eq = [p.weight, 0, 0, 0].
The design model
Linearize by finite differences
Write the state as a small deviation from trim, \( \mathbf x=\mathbf x_{eq}+\delta\mathbf x,\ \mathbf u=\mathbf u_{eq}+\delta\mathbf u \). To first order:
Built from the model the students fly — not a separate black box. Same \(f\) =
dynamics.state_derivative, so the linear model can never silently drift from the sim.
Why we need feedback
The open-loop poles sit at the origin
The poles are the eigenvalues of \(A\). For hover, many of them are exactly zero:
Position integrates velocity; attitude integrates rate ⇒ chains of integrators.
Poles on the imaginary axis ⇒ marginally stable, NOT asymptotically stable.
A torque bias is never corrected — it drifts away. This is why the open-loop quad tips.
Marginal stability is the whole motivation for the course: feedback must pull these poles
into the left half-plane. Wk 5 does it for attitude; Wk 7 (LQR) & Wk 11–12 (MPC) for the rest.
Can we even fix it?
Controllability: full rank 12
Before designing any controller, check the four inputs can actually steer all twelve states. Build the controllability matrix and take its rank:
Full rank (\(=n=12\)) ⇒ every state is reachable from the wrench \( [T,\tau_x,\tau_y,\tau_z] \).
This is the green light for LQR (Wk 7): a stabilizing gain exists. In quadsim:
C, rank = analysis.controllability(A, B) # rank == 12
How the sim runs
The RK4 closed-loop & SimLog
reference(t) ─►┌──────────────┐ wrench ┌──────────┐ motors ┌───────────────┐
│ controller ├──[T,τ]──►│ mixer + ├──f_i────►│ RK4 step on ├─► x(t+dt)
state x ──────►│ .control() │ │ saturate │ │ state_deriv f │
└──────────────┘ └──────────┘ └───────▲───────┘
▲ records into SimLog │ loop
└──────────────────────────────────────────────┘
Each step: wrench = controller.control(t, x, ref) → mixed to motor thrusts → clipped to \([f_{min},f_{max}]\) → applied.
SimLog records t, x (N×12), u (N×4 applied wrench), ref, f (per-motor) — what you plot & grade.
Integrator is fixed-step RK4 at p.dt (200 Hz); optional wind(t) adds a disturbance accel.
Worked example · examples/04_analysis.py
The whole Week-4 analysis in code
from quadsim import QuadParams, analysis as an
import numpy as np
p = QuadParams()
# 1) Linearize the real nonlinear model about hover
A, B = an.linearize(p) # A: 12x12, B: 12x4
# 2) Open-loop poles — count how many sit at the origin
ol = an.poles(A)
n_origin = np.sum(np.abs(ol.real) < 1e-6)
# 3) Controllability rank (must be 12)
_, rank = an.controllability(A, B)
print(f"{n_origin} poles at the origin (marginally stable) "
f"· controllability rank {rank}/12")
Output: several poles at the origin, controllability rank 12/12. This same \( (A,B) \) is what Wk 7 hands to an.lqr(A,B,Q,R) and Wk 11–12 to the MPC.
Second half · hands-on
Now you build it
Use analysis.linearize to get \( (A,B) \) about hover.
Plot the open-loop poles with analysis.poles + plotting.plot_poles.
Confirm controllability rank = 12 with analysis.controllability.