This page is a run, not a write-up. A single command loads a nonlinear aircraft model built from non-dimensional derivatives, trims it, linearises it about that trim by central differences, classifies the modes by eigenvector participation, closes one loop around the result and measures it. Every table cell and every mark on every plot below is read from the JSON that run emitted; the routine that produced each is named beneath it.
How to get this page yourself.
cmake --preset dev
cmake --build --preset dev
./build/dev/tools/report/galata-report-data > docs/assets/nt33a-fc1-run.json
python3 scripts/gen-report-page.py
CI regenerates the JSON and compares it numerically against the committed copy, then regenerates this page and diffs it. A page that had drifted from the code would still look like evidence, which is worse than no page at all.
The trim point
Three unknowns — angle of attack, elevator and thrust — and three equations: the two body-axis translational accelerations and the pitching acceleration all vanish. Everything further down this page is taken about this point, so it is the first thing to disbelieve.
| Quantity | Value | Unit |
|---|---|---|
| Altitude | 0.0 | m, geometric |
| True airspeed | 69.4944 | m/s |
| Mach | 0.2042 | — |
| Dynamic pressure | 2958.1 | Pa |
| Angle of attack | 2.1481 | deg |
| Flight-path angle | 0.0000 | deg |
| Pitch attitude | 2.1481 | deg |
| Elevator | 0.0234 | deg |
| Thrust | 8685.5 | N |
| Trim lift coefficient | 0.80841 | — |
Evidence. Residual norm 0.000000000000 (m/s² and rad/s²); trim Jacobian condition number 350682.
A trim is only as good as its residual, so the residual is reported rather than asserted, and the solver throws rather than returning a best effort: a linearisation taken about a point that is not an equilibrium produces a state-space model that is plausible and wrong. The condition number is large here for a units reason and not a physical one — an angle of order 0.04 rad and a thrust of order 104 N sit in the same unknown vector, so the columns differ in scale by five orders before any aircraft is involved. It is worth worrying about only when it is large and the residual will not come down.
Computed by galata::trim::trim_level.
The modal table
The labels are not assigned by frequency order. Each one comes from eigenvector participation — how much of the mode lives in the states that define it — and the score is the share of participation that does. Below about 0.5 a label is a guess, and the column is there so that can be seen rather than assumed.
| Mode | Axis | λ (1/s) | ωn (rad/s) | ζ | Period (s) | T½ (s) | T×2 (s) | Score | Why it carries that label |
|---|---|---|---|---|---|---|---|---|---|
| phugoid | longitudinal | -0.01626 ± 0.17063j | 0.17140 | 0.09485 | 36.824 | 42.635 | — | 0.959 | 0.959 of participation in u and theta |
| short period | longitudinal | -0.99200 ± 1.24902j | 1.59503 | 0.62193 | 5.031 | 0.699 | — | 0.993 | 0.993 of participation in w and q |
| spiral | lateral | -0.03190 | 0.03190 | — | — | 21.727 | — | 0.717 | 0.717 of participation in phi |
| roll subsidence | lateral | -2.19923 | 2.19923 | — | — | 0.315 | — | 0.838 | 0.838 of participation in p |
| Dutch roll | lateral | -0.06805 ± 1.12728j | 1.12933 | 0.06026 | 5.574 | 10.185 | — | 0.771 | 0.771 of participation in v and r |
A dash is a quantity that is not defined for that mode, never a zero: a real root has no period, and a mode has either a time to half amplitude or a time to double, never both. Printing zero there would invite a plot to draw it.
Computed by galata::analyze::analyze_modes.
The pole map
Participation factors — the evidence for the labels
Normalised to sum to one across the states of each axis. This is the measure the classification rests on, so a label whose evidence looks thin here is a label to distrust.
Longitudinal — states u, w, q, theta
| Mode | u | w | q | theta |
|---|---|---|---|---|
| phugoid | 0.477 | 0.010 | 0.031 | 0.483 |
| short period | 0.001 | 0.499 | 0.493 | 0.006 |
Eigenvector matrix condition number 68.61.
Lateral — states v, p, r, phi
| Mode | v | p | r | phi |
|---|---|---|---|---|
| spiral | 0.005 | 0.009 | 0.269 | 0.717 |
| roll subsidence | 0.070 | 0.838 | 0.034 | 0.058 |
| Dutch roll | 0.443 | 0.111 | 0.327 | 0.119 |
Eigenvector matrix condition number 81.19.
Against the published values
The input to everything above is a set of non-dimensional derivatives and some geometry. There is no matrix anywhere in it. These are the modal characteristics the same report printed, read from the committed reference file by the same loader the validation tier uses.
| Mode | Quantity | Published | Its own printing | galata | Deviation | Within the published printing? |
|---|---|---|---|---|---|---|
| phugoid | zeta | 0.09480 | ±5e-05 | 0.094852 | 0.05% | outside |
| phugoid | omega_n | 0.17200 | ±0.0005 | 0.171401 | 0.35% | outside |
| short period | zeta | 0.62200 | ±0.0005 | 0.621934 | 0.01% | inside |
| short period | omega_n | 1.59000 | ±0.005 | 1.595030 | 0.32% | outside |
| Dutch roll | zeta | 0.06090 | ±5e-05 | 0.060259 | 1.05% | outside |
| Dutch roll | omega_n | 1.13000 | ±0.005 | 1.129330 | 0.06% | inside |
| roll subsidence | 1/T | 2.20000 | ±0.005 | 2.199230 | 0.04% | inside |
| spiral | 1/T | 0.03180 | ±5e-05 | 0.031902 | 0.32% | outside |
“Its own printing” is not the gate. The source prints three significant figures, so each published value carries half a unit in its last digit — that is the column above. The full error budget also has to carry the rounding of the ten printed inputs the value was computed from, and it is derived per quantity in the validation tests and published in the V&V report. This page reports the measurement; it does not carry a second copy of the gate, because two gates are two answers to one question.
The largest deviation in this table is the Dutch roll zeta, at 1.05%. It is reported rather than absorbed by a wider tolerance, which is the whole discipline: a deviation over budget gets localised and published, never widened away. One quantity in this reference case does not reproduce at all — the phugoid damping ratio of a state matrix assembled by hand from the report’s dimensional derivatives, where the chain above reproduces it to the deviation shown in the first row. That gap is localised to a single matrix entry and written up at the phugoid-damping note; its size is published in the V&V report, which measures it rather than quoting it. It is still open, and it is still listed.
One closed loop
The loop is not the aircraft. Margins are a property of a loop, and an aircraft on its own is not one. What is measured below is the smallest control law that makes the question meaningful: measure bank angle, multiply by a gain, drive the aileron. The A and B matrices are the lateral linearisation from the run above — not a matrix read from a file — and the C row is the control law.
| Property | Value |
|---|---|
| Loop | aileron to bank_feedback |
| Feedback gain | 0.50 rad aileron per rad of bank error |
| Where the gain comes from | chosen for this example, not from a document |
| Where the loop is broken | plant input, between the gain and the aileron |
| Band searched | 0.01000 to 100.0 rad/s, 413 points |
| Worst pivot ratio over the sweep | 0.00379 |
That gain is a choice, not a published value. It is 0.50, and it is chosen because at that gain the magnitude crosses unity 3 times, so the loop has that many phase margins — which is the point worth showing. Breaking the loop at the plant output instead would give a different transfer function and different margins for the same closed-loop system. galata will not make that decision for you.
galata::analyze::single_loop_response.The governing margins
| Margin | Value | At (rad/s) |
|---|---|---|
| Gain margin | infinite | — |
| Phase margin | 48.497 deg | 1.60954 |
| Delay margin | 0.52588 s | 1.60954 |
Every crossover, not just the governing one
A loop whose magnitude crosses unity three times has three phase margins. An implementation that returned the first crossing it found would report this loop as comfortable at the largest of them.
| Kind | ω (rad/s) | Margin | Delay |
|---|---|---|---|
| |L| = 1 | 0.59337 | 101.068 deg | 2.97279 s |
| |L| = 1 | 0.91806 | 165.221 deg | 3.14103 s |
| |L| = 1 | 1.60954 | 48.497 deg | 0.52588 s |
Computed by galata::analyze::stability_margins.
Crossovers are found by searching a frequency grid. A crossover pair narrower than the grid spacing is not found, which is why the band and the point count travel with the result. Nothing here proves closed-loop stability either: margins are distances from the critical point, not a Nyquist encirclement count.
Gain and phase together
The gain margin is the tolerable gain change with no phase change; the phase margin is the tolerable phase change with no gain change. No real actuator varies one alone. The disk margin asks how much of both together, and the picture below is the whole of the argument: the perturbed loop is f·L for every f in a disk, so the closed loop is stable for all of them exactly when L(jω) misses the shaded region.
galata::analyze::disk_margin.| Quantity | Value | Where |
|---|---|---|
| Disk margin α | 0.87606 | — |
| Peak of |S + (σ−1)/2| | 1.14147 | — |
| Critical frequency | 1.75745 | rad/s |
| Guaranteed gain range | 0.3908 to 2.5589 (-8.16 to 8.16 dB) | — |
| Guaranteed phase range | ±47.310 deg | — |
| Sensitivity peak MS | 1.44941 | at 2.0441 rad/s |
| Complementary peak MT | 1.25447 | at 1.4844 rad/s |
What those peaks guarantee
Skogestad & Postlethwaite, 2nd ed., equations (2.47) and (2.48). Lower bounds: the loop’s actual margins are at least this good. The two gain-margin bounds have different functional forms — MS/(MS−1) against 1 + 1/MT — which is easy to blur from memory.
| From | Gain margin at least | Phase margin at least |
|---|---|---|
| MS | 3.2251 | 40.360 deg |
| MT | 1.7972 | 46.978 deg |
Every peak on this page is a grid maximum. The disk margin, MS and MT are all found by searching a refined frequency grid and not by the exact Hamiltonian-eigenvalue method, so each peak is a lower bound on the true H-infinity norm and each derived margin is an upper bound on the true one. The error is in the optimistic direction. Treat a marginal result as marginal.
The state matrices these came from
Central differences about the trim, with a Richardson truncation estimate per entry. The neglected coupling is the largest entry of the full 12×12 Jacobian that ties a retained state to a discarded one, relative to the largest retained entry: it is measured rather than assumed, because “the longitudinal and lateral axes decouple” is true at a wings-level symmetric trim and false in a turn, and the difference does not show in the reduced matrix.
Longitudinal
Rows and columns in the order
u, w, q, theta. Velocities m/s, angles rad, rates rad/s.
Worst relative truncation 4.26e-12;
neglected coupling 1.35e-05;
Euler-chart conditioning |cos θ| = 0.999297.
A =
u w q theta
u -0.039146 0.079936 -2.604860 -9.799760
w -0.247105 -0.936394 69.445600 -0.367583
q 0.002244 -0.022467 -1.040980 -0.000000
theta 0.000000 0.000000 1.000000 0.000000
B =
elevator aileron rudder thrust
u 0.153638 0.000000 0.000000 0.000187
w -4.095990 0.000000 0.000000 0.000000
q -4.170710 0.000000 0.000000 0.000000
theta 0.000000 0.000000 0.000000 0.000000
Lateral
Rows and columns in the order
v, p, r, phi. Velocities m/s, angles rad, rates rad/s.
Worst relative truncation 5.87e-15;
neglected coupling 0;
Euler-chart conditioning |cos θ| = 0.999297.
A =
v p r phi
v -0.124902 2.604860 -69.445600 9.799760
p -0.079099 -2.035300 0.641840 0.000000
r 0.009609 -0.115922 -0.207034 0.000000
phi 0.000000 1.000000 0.037509 0.000000
B =
elevator aileron rudder thrust
v 0.000000 0.000000 2.049440 0.000000
p 0.000000 6.014430 -0.012507 0.000000
r 0.000000 0.028629 -1.242800 0.000000
phi 0.000000 0.000000 0.000000 0.000000
Computed by galata::linearize::linearize_finite_difference.
What this page is not
Not certified, and not a handling-qualities assessment. Nothing in galata is DO-178C qualified and none of it may be used as evidence in a certification package. Nothing here says whether a mode or a margin is acceptable: Level 1/2/3 boundaries are a judgement against a specification, and that is a separate capability with its own citations, which galata does not have.
The aircraft model is a first-order expansion about one flight condition. It has no stall, no Mach effects and no engine, and it will return a confident answer at any angle of attack it is asked for. The linearisation is only as good as the trim, and the trim is reported above with its residual so that can be checked. The classification cannot tell you that a label is meaningless — only that its evidence was weak, which is what the score column is for.