Numerical Toolbox

A Field Guide to Embedded DSP, Control, and Estimation Algorithms

Gabriel Francisco dos Santos

gabriel.fra.santos@gmail.com

October 23, 2026

1 Analysis

1.1 Fast Fourier Transform (FFT)

1.1.1 Overview & Motivation

Many problems in signal processing, communications, and control require knowing the frequency content of a signal. The Discrete Fourier Transform (DFT) answers this question exactly, but its direct computation costs \(O(N^2)\) multiplications — prohibitive for real-time embedded systems processing thousands of samples.

The Fast Fourier Transform is a family of algorithms that compute the DFT in \(O(N \log N)\) operations by exploiting symmetry and periodicity of the complex exponential. This library implements the Radix-2 Cooley-Tukey variant, which recursively splits an \(N\)-point DFT into two \(N/2\)-point DFTs, requiring \(N\) to be a power of two.

The core insight is that the DFT matrix has a recursive block structure: half of its entries are shared between the even-indexed and odd-indexed sub-problems, so most of the work can be reused rather than recomputed.

1.1.2 Mathematical Theory

1.1.2.1 Prerequisites

The Discrete Fourier Transform of a length-\(N\) sequence \(x[n]\) is defined as:

\[X[k] = \sum_{n=0}^{N-1} x[n] \, e^{-j2\pi kn/N}, \quad k = 0, 1, \ldots, N-1\]

The inverse DFT recovers the original sequence:

\[x[n] = \frac{1}{N} \sum_{k=0}^{N-1} X[k] \, e^{j2\pi kn/N}\]

We define the twiddle factor \(W_N = e^{-j2\pi/N}\), so \(X[k] = \sum_{n} x[n] \, W_N^{kn}\).

1.1.2.2 Cooley-Tukey Decomposition

Split the sum into even-indexed and odd-indexed terms:

\[X[k] = \underbrace{\sum_{m=0}^{N/2-1} x[2m] \, W_{N/2}^{km}}_{E[k]} \;+\; W_N^k \underbrace{\sum_{m=0}^{N/2-1} x[2m+1] \, W_{N/2}^{km}}_{O[k]}\]

Because \(E[k]\) and \(O[k]\) are periodic with period \(N/2\):

\[X[k] = E[k] + W_N^k \, O[k]\] \[X[k + N/2] = E[k] - W_N^k \, O[k]\]

This pair of equations is the butterfly operation: two outputs from two inputs and one twiddle-factor multiplication.

1.1.2.3 Bit-Reversal Permutation

The recursive decomposition reorders input by bit-reversing the sample indices. For in-place computation the input array is pre-permuted so that butterfly stages proceed sequentially from the smallest sub-problems to the full \(N\)-point result.

1.1.3 Complexity Analysis

Case Time Space Notes
All \(O(N \log_2 N)\) \(O(N)\) \(N\) must be a power of 2

Why \(O(N \log N)\): There are \(\log_2 N\) butterfly stages, each performing \(N/2\) butterfly operations (one complex multiply + two complex adds). Total: \(\frac{N}{2} \log_2 N\) complex multiplications.

Twiddle factors are pre-computed and stored in a lookup table of size \(N/2\), so no trigonometric evaluations occur at runtime.

1.1.4 Step-by-Step Walkthrough

Input: \(x = [1, 2, 3, 4]\), \(N = 4\)

Step 1 — Bit-reversal permutation

Index (binary) Reversed Value
0 (00) 0 (00) 1
1 (01) 2 (10) 3
2 (10) 1 (01) 2
3 (11) 3 (11) 4

Reordered: \([1, 3, 2, 4]\)

Step 2 — Stage 1 (size-2 butterflies), twiddle \(W_2^0 = 1\)

Result: \([4, -2, 6, -2]\)

Step 3 — Stage 2 (size-4 butterfly), twiddles \(W_4^0 = 1\), \(W_4^1 = -j\)

Output: \(X = [10,\; -2+2j,\; -2,\; -2-2j]\)

Verification: \(X[0] = 1+2+3+4 = 10\) ✓

1.1.5 Pitfalls & Edge Cases

1.1.6 Variants & Generalizations

Variant Key Difference
Mixed-radix FFT Supports arbitrary composite \(N\) using radix-2, -3, -5, etc. stages
Split-radix FFT Reduces multiplications by ~33 % compared to standard radix-2
Real-valued FFT Exploits conjugate symmetry of real inputs to halve work and memory
Bluestein’s algorithm Handles arbitrary \(N\) by converting DFT to a circular convolution
Inverse FFT Same structure; negate twiddle exponents and scale by \(1/N\)

1.1.7 Applications

1.1.8 Connections to Other Algorithms

graph LR
    FFT --> PSD["Power Spectral Density"]
    FFT --> DCT["Discrete Cosine Transform"]
    WIN["Window Functions"] --> FFT
    FFT -.-> DK["Durand-Kerner (polynomial eval)"]
Algorithm Relationship
Power Spectral Density Calls FFT internally on each windowed segment
Discrete Cosine Transform Computed via FFT with input rearrangement and twiddle-factor post-processing
Window Functions Applied before FFT to reduce spectral leakage

1.2 Real-Input FFT (RFFT)

1.2.1 Overview & Motivation

Most sensor data is real-valued: audio samples, vibration measurements, ADC readings. A general complex FFT applied to such data wastes half its work because the imaginary input channel is identically zero. The real-input FFT exploits this structure to deliver roughly twice the throughput and half the memory of a complex FFT at the same transform length. On bandwidth- and RAM-limited embedded targets this difference is significant.

1.2.2 Mathematical Theory

1.2.2.1 Prerequisites

The RFFT is a specialisation of the length-\(N\) Discrete Fourier Transform

\[X[k] = \sum_{n=0}^{N-1} x[n]\, W_N^{nk}, \quad W_N = e^{-j2\pi/N}\]

for the case where every \(x[n]\) is real-valued. Because \(x[n] \in \mathbb{R}\), the spectrum satisfies Hermitian symmetry: \(X[N-k] = X^*[k]\). Consequently only the \(N/2 + 1\) bins \(k = 0, \dots, N/2\) carry independent information.

1.2.2.2 Decimation-in-time split

Split \(x\) into its even- and odd-indexed subsequences:

\[x_e[n] = x[2n], \qquad x_o[n] = x[2n+1], \qquad n = 0, \dots, N/2 - 1.\]

Their length-\((N/2)\) DFTs are \(E[k]\) and \(O[k]\). The full transform decomposes as

\[X[k] = E[k] + W_N^k \, O[k], \qquad k = 0, \dots, N/2 - 1,\]

\[X[N/2 + k] = E[k] - W_N^k \, O[k], \qquad k = 0, \dots, N/2 - 1.\]

The twiddle factor \(W_N^k = e^{-j2\pi k/N}\) is precomputed for \(k = 1, \dots, N/2 - 1\). The boundary cases are purely real:

\[X[0] = E[0] + O[0], \qquad X[N/2] = E[0] - O[0].\]

1.2.2.3 Inverse transform

Given the \(N/2 + 1\) unique bins, the even and odd sub-spectra are recovered using the inverse of the split relations. For \(k = 1, \dots, N/2 - 1\):

\[E[k] = \tfrac{1}{2}(X[k] + X^*[N/2 - k]),\]

\[O[k] = W_N^{-k} \cdot \tfrac{1}{2}(X[k] - X^*[N/2 - k]).\]

Two independent length-\((N/2)\) inverse DFTs then reconstruct \(x_e\) and \(x_o\), which interleave back to \(x\).

1.2.3 Complexity Analysis

Case Time Space Notes
Forward \(O(N \log N / 2)\) \(O(N)\) stack (bounded) Two half-length DFTs plus \(O(N)\) combine pass
Inverse \(O(N \log N / 2)\) \(O(N)\) stack (bounded) Two half-length IDFTs plus interleave pass

The constant factor is approximately half that of a complex \(N\)-point FFT because both sub-DFTs exploit real-input symmetry internally (they receive real-only vectors).

1.2.4 Step-by-Step Walkthrough

Input: \(x = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\) (unit impulse, \(N = 16\)).

  1. Split: \(x_e = [1, 0, 0, 0, 0, 0, 0, 0]\), \(x_o = [0, 0, 0, 0, 0, 0, 0, 0]\).
  2. Sub-DFTs: \(E[k] = 1\) for all \(k\); \(O[k] = 0\) for all \(k\).
  3. Combine: \(X[k] = E[k] + W_{16}^k \cdot 0 = 1\) for \(k = 0, \dots, 8\).
  4. Result: \(|X[k]| = 1\) — flat magnitude, the hallmark of a unit impulse.

1.2.5 Pitfalls & Edge Cases

1.2.6 Variants & Generalizations

1.2.7 Applications

1.2.8 Connections to Other Algorithms

1.3 Power Spectral Density (Welch’s Method)

1.3.1 Overview & Motivation

Given a noisy signal, a natural question is: how is the signal’s power distributed across frequencies? A single FFT of the entire record gives a very noisy estimate — the variance of the periodogram does not decrease as the record grows. Welch’s method trades frequency resolution for statistical reliability by dividing the signal into overlapping, windowed segments, computing a periodogram for each, and averaging them. The result is a smooth, consistent estimate of the Power Spectral Density (PSD).

This is the standard approach in vibration analysis, audio engineering, communication system design, and any domain where reliable spectral estimates matter more than the finest possible frequency resolution.

1.3.2 Mathematical Theory

1.3.2.1 Power Spectral Density Definition

For a wide-sense stationary process \(x(t)\), the PSD is the Fourier transform of the autocorrelation function (Wiener-Khinchin theorem):

\[S_{xx}(f) = \int_{-\infty}^{\infty} R_{xx}(\tau) \, e^{-j2\pi f\tau} \, d\tau\]

In practice we estimate \(S_{xx}\) from a finite record.

1.3.2.2 The Periodogram

The periodogram of a length-\(N\) segment \(x[n]\) is:

\[\hat{P}(f_k) = \frac{1}{N} \left| \sum_{n=0}^{N-1} x[n] \, e^{-j2\pi kn/N} \right|^2 = \frac{1}{N} |X[k]|^2\]

This is an unbiased but inconsistent estimator — its variance does not shrink as \(N \to \infty\).

1.3.2.3 Welch’s Method

  1. Segment the signal of length \(L\) into \(K\) overlapping segments of length \(N\) with step size \(S = N - \text{overlap}\):

\[x_i[n] = x[n + iS], \quad i = 0, 1, \ldots, K-1\]

  1. Window each segment:

\[w_i[n] = w[n] \cdot x_i[n]\]

  1. Compute the periodogram of each windowed segment:

\[P_i[k] = \frac{1}{N} |W_i[k]|^2\]

  1. Average across all segments:

\[\hat{S}_{xx}[k] = \frac{1}{K} \sum_{i=0}^{K-1} P_i[k]\]

The averaging reduces variance by a factor of approximately \(K\) (slightly less due to overlap correlation).

1.3.2.4 Frequency Resolution

\[\Delta f = \frac{f_s}{N}\]

Increasing segment size \(N\) improves resolution but reduces the number of averages \(K\) for a fixed-length record.

1.3.3 Complexity Analysis

Case Time Space Notes
All \(O(K \cdot N \log N)\) \(O(N)\) \(K\) segments, each requiring one FFT

Where \(K = \lfloor (L - N) / S \rfloor + 1\) and \(S = N \cdot (1 - \text{overlap fraction})\).

Why: Each segment requires an \(O(N \log N)\) FFT, and there are \(K\) segments. The averaging and magnitude-squared operations are \(O(N)\) per segment.

1.3.4 Step-by-Step Walkthrough

Input: \(x = [1, 3, 5, 7, 6, 4, 2, 0]\), segment size \(N=4\), 50 % overlap, rectangular window.

Step 1 — Segmentation (step \(S = 2\)):

Segment Indices Values
\(x_0\) 0–3 \([1, 3, 5, 7]\)
\(x_1\) 2–5 \([5, 7, 6, 4]\)
\(x_2\) 4–7 \([6, 4, 2, 0]\)

Step 2 — Window (rectangular → no change)

Step 3 — FFT and periodogram for \(x_0 = [1, 3, 5, 7]\):

(Repeat for \(x_1\) and \(x_2\).)

Step 4 — Average the three periodograms element-wise.

Output: One-sided PSD estimate at frequencies \(0, \frac{f_s}{4}, \frac{f_s}{2}\).

1.3.5 Pitfalls & Edge Cases

1.3.6 Variants & Generalizations

Variant Key Difference
Bartlett’s method Non-overlapping segments, rectangular window (special case of Welch with 0 % overlap)
Modified periodogram Single segment with a non-rectangular window
Multitaper method Uses multiple orthogonal windows (tapers) per segment for lower bias
Cross-spectral density Computes \(S_{xy}\) between two signals instead of the auto-spectrum
Coherence \(|\gamma_{xy}|^2 = |S_{xy}|^2 / (S_{xx} S_{yy})\) — measures linear dependence between two signals at each frequency

1.3.7 Applications

1.3.8 Connections to Other Algorithms

graph LR
    SIG["Input Signal"] --> WIN["Window Functions"]
    WIN --> FFT["Fast Fourier Transform"]
    FFT --> PSD["Power Spectral Density"]
    PSD --> YW["Yule-Walker (parametric alternative)"]
Algorithm Relationship
Fast Fourier Transform Core building block — each segment is transformed via FFT
Window Functions Applied to each segment before FFT to control leakage
Yule-Walker Parametric alternative — estimates PSD from an AR model instead of averaging periodograms

1.4 Discrete Cosine Transform (DCT)

1.4.1 Overview & Motivation

The Discrete Cosine Transform projects a real-valued signal onto a set of cosine basis functions at different frequencies. Unlike the DFT, the DCT produces purely real coefficients and exhibits superior energy compaction — most of the signal’s energy concentrates in a few low-frequency coefficients. This property makes it the backbone of lossy compression standards (JPEG, MPEG, AAC).

This library implements DCT-II (the most common variant) by leveraging the FFT: the input is rearranged, transformed via a real-valued FFT, and post-processed with twiddle factors. This approach inherits the FFT’s \(O(N \log N)\) efficiency rather than requiring a dedicated \(O(N^2)\) computation.

1.4.2 Mathematical Theory

1.4.2.1 DCT-II Definition

This implementation uses the orthonormal DCT-II. For a length-\(N\) sequence \(x[n]\):

\[X[0] = \frac{1}{\sqrt{N}} \sum_{n=0}^{N-1} x[n]\]

\[X[k] = \sqrt{\frac{2}{N}} \sum_{n=0}^{N-1} x[n] \cos\!\left(\frac{\pi}{N}\left(n + \tfrac{1}{2}\right) k\right), \quad k = 1, \ldots, N-1\]

This normalisation makes the transform matrix unitary, so Parseval’s theorem holds exactly: \(\sum_k X[k]^2 = \sum_n x[n]^2\). The inverse (orthonormal DCT-III) recovers \(x[n]\):

\[x[n] = \frac{X[0]}{\sqrt{N}} + \sqrt{\frac{2}{N}} \sum_{k=1}^{N-1} X[k] \cos\!\left(\frac{\pi}{N}\left(n + \tfrac{1}{2}\right) k\right)\]

1.4.2.2 FFT-Based Computation

The DCT can be computed via the DFT of a reordered sequence:

  1. Form a new sequence \(y[n]\) by interleaving even-indexed and reverse odd-indexed samples of \(x\).
  2. Compute the \(N\)-point FFT: \(Y[k] = \text{FFT}\{y\}\).
  3. Apply twiddle factors and orthonormal scale:

1.4.2.3 Why Cosine Basis?

Cosine functions are symmetric (even functions). The DCT implicitly mirrors the signal rather than making it periodic, avoiding the boundary discontinuities that cause spectral leakage in the DFT. This is why DCT coefficients decay faster and energy compaction is better.

1.4.3 Complexity Analysis

Case Time Space Notes
All \(O(N \log N)\) \(O(N)\) Dominated by the internal FFT; twiddle post-processing is \(O(N)\)

The reordering step and twiddle multiplication are both linear, so the overall cost is determined by the FFT.

1.4.4 Step-by-Step Walkthrough

Input: \(x = [1, 2, 3, 4]\), \(N = 4\)

Step 1 — Reorder for FFT

Even-indexed positions filled left-to-right, odd-indexed filled right-to-left:

\[y = [x[0],\; x[2],\; x[3],\; x[1]] = [1, 3, 4, 2]\]

Step 2 — Compute FFT

\[Y = \text{FFT}([1, 3, 4, 2]) = [10,\; -3-j,\; 0,\; -3+j]\]

Step 3 — Apply twiddle factors and orthonormal scale (\(N=4\), \(W[k] = e^{-j\pi k/8}\))

\(k\) \(W[k]\) \(W[k] \cdot Y[k]\) \(X[k]\)
0 — — \(10 / \sqrt{4} = 5\)
1 \(e^{-j\pi/8}\) \(\approx -3.154 + 0.224j\) \(\sqrt{0.5} \cdot (-3.154) \approx -2.23\)
2 \(e^{-j\pi/4}\) \(0\) \(0\)
3 \(e^{-j3\pi/8}\) \(\approx -0.224 + 3.154j\) \(\sqrt{0.5} \cdot (-0.224) \approx -0.16\)

Output: \(X \approx [5,\; -2.23,\; 0,\; -0.16]\)

Notice how most of the energy is in \(X[0]\) (the DC component) — energy compaction in action. The orthonormal scale ensures \(\sum_k X[k]^2 = 1^2+2^2+3^2+4^2 = 30\).

1.4.5 Pitfalls & Edge Cases

1.4.6 Variants & Generalizations

Variant Description
DCT-I Symmetric extension; used in some filter bank designs
DCT-III (IDCT) Inverse of DCT-II; used for reconstruction in JPEG decoding
DCT-IV Self-inverse; basis of the MDCT used in MP3 and AAC
MDCT Modified DCT with 50 % overlap; foundation of modern audio codecs
2-D DCT Applied row-then-column for image compression (JPEG 8×8 blocks)

1.4.7 Applications

1.4.8 Connections to Other Algorithms

graph LR
    DCT["Discrete Cosine Transform"]
    FFT["Fast Fourier Transform"]
    WIN["Window Functions"]
    FFT --> DCT
    WIN -.-> DCT
Algorithm Relationship
Fast Fourier Transform DCT is computed via FFT with input reordering and twiddle post-processing
Window Functions Some DCT applications use windowing to control boundary effects

1.5 Signal Detectors

1.5.1 Overview & Motivation

Real-time signal monitoring requires lightweight, stateful primitives that track envelope, periodicity, and energy without buffering entire waveforms. Three composable detectors cover the most common needs: holding the peak magnitude for VU-style metering, counting sign reversals to estimate frequency, and smoothing instantaneous power to approximate RMS level. All three operate in O(1) time and O(1) memory per sample, making them suitable for tight interrupt service routines.

1.5.2 Mathematical Theory

1.5.2.1 Peak Hold

Let \(m_n = |x_n|\). With a decay coefficient \(d \in (0, 1]\), the held peak evolves as:

\[p_n = \max(m_n,\; d \cdot p_{n-1})\]

When \(d = 1\) the peak is infinite-hold; when \(d < 1\) it releases exponentially toward zero between new maxima.

1.5.2.2 Zero-Crossing Detection

A zero crossing occurs at sample \(n\) when the sign of \(x_n\) differs from the sign of \(x_{n-1}\) and \(|x_n|\) exceeds a hysteresis threshold \(h \geq 0\):

\[\text{crossed}_n = \bigl(\mathrm{sgn}(x_n) \neq \mathrm{sgn}(x_{n-1})\bigr) \land \bigl(|x_n| > h\bigr)\]

The instantaneous fundamental frequency of a periodic signal can be estimated from the cumulative count \(C\) over a window of \(N\) samples at sampling period \(T_s\):

\[f_0 \approx \frac{C}{2 \, N \, T_s}\]

1.5.2.3 RMS Envelope

A one-pole IIR filter is applied to \(x^2\), yielding an exponentially weighted mean-square:

\[E_n = E_{n-1} + \alpha\,(x_n^2 - E_{n-1}), \quad \alpha \in (0, 1)\]

The RMS envelope is:

\[r_n = \sqrt{E_n}\]

This is structurally identical to an exponential moving average applied to the squared signal.

1.5.3 Complexity Analysis

Detector Time per sample State words
Peak Hold O(1) — 1 mul, 1 compare, 1 abs 1 scalar
Zero-Crossing Counter O(1) — 1 compare, 1 abs 1 scalar + 1 uint32
RMS Envelope O(1) — 1 MAC + 1 sqrt 1 scalar

No buffers, no windows, no dynamic allocation.

1.5.4 Step-by-Step Walkthrough

1.5.4.1 Peak Hold (decay = 0.5), input = [0.8, 0, 0, 0]

n \(x_n\) \(m_n\) \(d \cdot p_{n-1}\) \(p_n\)
0 0.8 0.8 0.0 0.8
1 0.0 0.0 0.4 0.4
2 0.0 0.0 0.2 0.2
3 0.0 0.0 0.1 0.1

1.5.4.2 Zero-Crossing Counter (h = 0), input = [1, -1, 1, -1]

n \(x_n\) prev sign curr sign crossed C
0 +1 0 + no 0
1 -1 + - yes 1
2 +1 - + yes 2
3 -1 + - yes 3

1.5.4.3 RMS Envelope (\(\alpha = 0.25\)), DC input \(x = 0.4\)

\(E_0 = 0\); after \(n\) steps, \(E_n = 0.16\,(1-(1-0.25)^n)\). At \(n = 20\): \(E_{20} \approx 0.1599\), \(r_{20} \approx 0.3999\).

1.5.5 Pitfalls & Edge Cases

1.5.6 Variants & Generalizations

1.5.7 Applications

1.5.8 Connections to Other Algorithms

1.6 Decibels & Magnitude-Response Helpers

1.6.1 Overview & Motivation

Audio, filter, and control-system specifications express signal levels and filter performance in decibels (dB) because the human auditory system and most engineering metrics scale logarithmically with amplitude ratio. Specifying a stop-band attenuation of 60 dB or a pass-band ripple of 0.1 dB is natural and compact; the equivalent linear ratios (1 000 : 1 and 1.01161 : 1) are not. A small set of conversion primitives — ToDecibels, FromDecibels, and two derived helpers for attenuation and ripple — centralises this conversion and eliminates scattered, error-prone inline 20·log10 expressions throughout the rest of the library.

1.6.2 Mathematical Theory

1.6.2.1 Magnitude Decibel Conversion

For a positive amplitude ratio \(r > 0\), the equivalent level in decibels is:

\[L_{\mathrm{dB}} = 20 \log_{10}(r)\]

The factor 20 (rather than 10) reflects the voltage/pressure convention: power is proportional to the square of amplitude, so a doubling of amplitude (\(r = 2\)) gives a 6 dB increase, matching the \(10 \log_{10}(4) = 6.02\) dB power equivalent.

1.6.2.2 Inverse Conversion

\[r = 10^{L_{\mathrm{dB}}/20}\]

This inverse is exact for all finite \(L_{\mathrm{dB}}\); no guard is needed on the output side.

1.6.2.3 Zero and Negative Input Guard

\(\log_{10}(0) = -\infty\); negative ratios are physically meaningless. Both cases are mapped to a finite floor value \(L_{\min}\) chosen well below any engineering specification of interest:

\[L_{\mathrm{dB}} = \max\!\left(20\log_{10}(r),\; L_{\min}\right), \quad r > 0\] \[L_{\mathrm{dB}} = L_{\min}, \quad r \leq 0\]

A floor of \(-160\,\mathrm{dB}\) corresponds to an amplitude ratio below \(10^{-8}\), safely beyond the dynamic range of any practical floating-point computation in 32-bit single precision.

1.6.2.4 Derived Helpers

Stop-band attenuation between a pass-band ratio \(r_p\) and a stop-band ratio \(r_s\):

\[A = L_{\mathrm{dB}}(r_p) - L_{\mathrm{dB}}(r_s)\]

Pass-band ripple between the maximum and minimum in-band ratios \(r_{\max}\) and \(r_{\min}\):

\[\Delta = L_{\mathrm{dB}}(r_{\max}) - L_{\mathrm{dB}}(r_{\min})\]

Both are simple differences in decibel space, exploiting the logarithm identity \(\log(a/b) = \log a - \log b\).

1.6.3 Complexity Analysis

Operation Time Space Notes
ToDecibels O(1) O(1) One log10 + one max + one mul
FromDecibels O(1) O(1) One pow
AttenuationDb O(1) O(1) Two ToDecibels + one subtraction
RippleDb O(1) O(1) Two ToDecibels + one subtraction

No state, no buffers. All operations are pure functions.

1.6.4 Step-by-Step Walkthrough

Converting a ratio of 10 to decibels:

  1. Input \(r = 10\); guard passes (\(r > 0\)).
  2. Compute \(20 \cdot \log_{10}(10) = 20 \cdot 1 = 20\).
  3. Apply floor: \(\max(20, -160) = 20\).
  4. Output: \(20\,\mathrm{dB}\).

Round-trip for \(r = 0.5\):

  1. ToDecibels(0.5) = \(20 \cdot \log_{10}(0.5) \approx -6.0206\,\mathrm{dB}\).
  2. FromDecibels(-6.0206) = \(10^{-6.0206/20} \approx 0.5\).

1.6.5 Pitfalls & Edge Cases

1.6.6 Variants & Generalizations

1.6.7 Applications

1.6.8 Connections to Other Algorithms

1.7 Goertzel Algorithm

1.7.1 Overview & Motivation

When a system needs to detect or measure a single known frequency — a DTMF touch-tone, a pilot sync tone, mains-hum, or a narrowband alarm — computing a full FFT wastes every cycle spent on bins that will never be read. The Goertzel algorithm computes exactly one DFT bin using a second-order recurrence that processes samples one at a time, requiring no input buffer and no twiddle-factor table. On the smallest microcontrollers with a few hundred bytes of RAM, this distinction is decisive.

1.7.2 Mathematical Theory

1.7.2.1 Core Definitions

Let \(x[n]\), \(n = 0, \ldots, N-1\), be a block of \(N\) real samples. The DFT coefficient at bin \(k\) is:

\[X[k] = \sum_{n=0}^{N-1} x[n]\, e^{-j2\pi kn/N}\]

Define the resonator coefficient:

\[c = 2\cos\!\left(\frac{2\pi k}{N}\right)\]

1.7.2.2 Derivation

The DFT sum can be reorganised as the output of a single-pole IIR filter evaluated at \(n = N\). Introduce the auxiliary sequence:

\[s[n] = x[n] + c\,s[n-1] - s[n-2], \quad s[-1] = s[-2] = 0\]

After the \(N\) input samples, one further step with \(x[N] = 0\) gives \(s[N] = c\,s[N-1] - s[N-2]\), and the DFT bin follows:

\[X[k] = s[N] - s[N-1]\,e^{-j2\pi k/N}\]

Separating real and imaginary parts:

\[\mathrm{Re}\{X[k]\} = s[N] - s[N-1]\cos\!\left(\tfrac{2\pi k}{N}\right)\]

\[\mathrm{Im}\{X[k]\} = s[N-1]\sin\!\left(\tfrac{2\pi k}{N}\right)\]

The magnitude can be obtained without the final trigonometric products using the identity:

\[|X[k]|^2 = s[N-1]^2 + s[N-2]^2 - c\,s[N-1]\,s[N-2]\]

so \(|X[k]| = \sqrt{s[N-1]^2 + s[N-2]^2 - c\,s[N-1]\,s[N-2]}\), saving two multiplies on the hot path.

1.7.2.3 Frequency-to-Bin Mapping

A physical frequency \(f_t\) sampled at \(f_s\) maps to bin:

\[k = \mathrm{round}\!\left(\frac{f_t}{f_s}\,N\right)\]

Frequency resolution is \(\Delta f = f_s / N\).

1.7.3 Complexity Analysis

Case Time Space Notes
Per sample \(O(1)\) \(O(1)\) One multiply, two adds, no table lookup
Full block \(O(N)\) \(O(1)\) Two state words; trig only at block end

For \(B\) bins of interest the total cost is \(O(BN)\). An FFT computing all \(N\) bins costs \(O(N\log N)\), so the Goertzel approach is cheaper when \(B \ll \log_2 N\) — typically \(B \le 4\) for a 256-point transform.

1.7.4 Step-by-Step Walkthrough

Consider \(N = 4\), \(k = 1\), \(x = [1, 0, -1, 0]\).

\(c = 2\cos(\pi/2) = 0\)

\(n\) \(x[n]\) \(s[n] = x[n] + 0\cdot s[n-1] - s[n-2]\)
0 1 \(1 + 0 - 0 = 1\)
1 0 \(0 + 0 - 0 = 0\) (note: \(s[-1]=0\))
2 -1 \(-1 + 0 - 1 = -2\)
3 0 \(0 + 0 - 0 = 0\)

Extra step: \(s[4] = 0\cdot s[3] - s[2] = -(-2) = 2\).

\(\cos(2\pi/4) = 0\), \(\sin(2\pi/4) = 1\)

\(\mathrm{Re}\{X[1]\} = s[4] - s[3]\cdot 0 = 2\)

\(\mathrm{Im}\{X[1]\} = s[3]\cdot 1 = 0\)

Direct DFT check: \(X[1] = 1 - (-1) = 2\). Matches.

1.7.5 Pitfalls & Edge Cases

The recurrence pole sits exactly on the unit circle, making the filter marginally stable. Error accumulates with each block if Reset is not called between blocks. The algorithm is designed for finite, bounded blocks of exactly \(N\) samples.

Choosing \(N\) such that the target tone lands near an integer bin minimises spectral leakage. A mismatch of half a bin can reduce the apparent magnitude by several decibels. Window functions do not apply here because the Goertzel filter already selects one bin; applying a window changes the effective DFT kernel.

The DC bin (\(k = 0\)) and the Nyquist bin (\(k = N/2\)) are purely real; their imaginary output is zero by symmetry.

1.7.6 Variants & Generalizations

The magnitude-squared form \(s_1^2 + s_2^2 - c\,s_1 s_2\) avoids the square root when only power detection (threshold comparison) is needed, saving a transcendental call on platforms without hardware sqrt.

The recurrence and the final step need only \(\cos\omega\) and \(\sin\omega\) of the bin, \(\omega = 2\pi k/N\), evaluated once per configuration. A caller that already evaluates them by other means — a sine table refined by a short series, or an exact value for a known angle — can supply the unit bin phasor \(e^{j\omega}\) directly. On targets whose sine and cosine come from a software math library, that keeps the library’s general argument-reduction code, several kilobytes on a Cortex-M4, out of the image. The phasor’s accuracy sets the resonator frequency: an error \(\varepsilon\) in \(\cos\omega\) detunes it by about \(\varepsilon / \sin\omega\) radians per sample, which over a block of \(N\) samples rotates the result by about \(N\varepsilon / (2\sin\omega)\).

When several tones must be tracked simultaneously, one independent Goertzel instance per tone runs in \(O(BN)\) total with \(O(B)\) state — still far cheaper than an FFT for small \(B\).

A sliding Goertzel variant updates one sample at a time by subtracting the oldest sample, giving a continuous output at the cost of maintaining an input ring buffer; this trades \(O(1)\) memory for \(O(N)\) memory and is outside the scope of the no-heap constraint here.

1.7.7 Applications

1.7.8 Connections to Other Algorithms

The Goertzel algorithm computes exactly the same result as one bin of the DFT computed by the Cooley-Tukey FFT (FastFourierTransform), but limits the computation to that single bin. When all \(N\) bins are required simultaneously, the FFT is always faster. PowerDensitySpectrum builds on the FFT for broadband spectral estimation. SignalDetectors provides simpler, non-frequency-selective detectors (peak hold, RMS) that cost even less per sample.

1.8 Discrete Wavelet Transform (Haar / Daubechies)

1.8.1 Overview & Motivation

The Discrete Wavelet Transform (DWT) decomposes a finite-length signal into a hierarchy of approximation and detail coefficients at multiple scales. Unlike the DFT, which projects onto infinitely-supported sinusoids, wavelets are compactly supported: they are simultaneously localised in time and frequency. This dual localisation makes the DWT the canonical tool for detecting transients, edges, and non-stationary features — phenomena that a pure frequency representation blurs across the entire spectrum.

In embedded condition-monitoring and edge-ML pipelines, the DWT is valued for three properties: its \(O(N)\) complexity (a factor of \(\log_2 N\) faster than the FFT), its ability to concentrate signal energy in very few coefficients (sparsity), and the perfect-reconstruction guarantee that lets processed coefficients be inverted exactly.

1.8.2 Mathematical Theory

1.8.2.1 Quadrature-Mirror Filter Bank

A single DWT stage passes a length-\(N\) sequence \(x[n]\) through two complementary finite-impulse- response filters, then downsamples each output by two.

\[ c_A[i] = \sum_{k=0}^{P-1} h[k]\, x[2i + k \bmod N], \qquad c_D[i] = \sum_{k=0}^{P-1} g[k]\, x[2i + k \bmod N], \qquad i = 0,\ldots,\tfrac{N}{2}-1 \]

where \(h\) is the scaling (low-pass) filter with \(P\) taps and \(g\) is the wavelet (high-pass) filter. For an orthogonal wavelet the two filters satisfy the quadrature-mirror relation:

\[g[k] = (-1)^k\, h[P - 1 - k]\]

This guarantees that the two sub-bands together cover the full bandwidth without overlap or gap.

1.8.2.2 Multilevel Decomposition (Mallat’s Algorithm)

The approximation \(c_A\) is fed back into the same filter pair. After \(L\) levels the coefficient array contains \(L\) detail bands and one coarse approximation:

\[ \text{coeffs} = [\underbrace{c_D^{(1)}}_{N/2},\; \underbrace{c_D^{(2)}}_{N/4},\; \ldots,\; \underbrace{c_D^{(L)}}_{N/2^L},\; \underbrace{c_A^{(L)}}_{N/2^L}] \]

The layout is critical: level \(\ell\) detail starts at offset \(\sum_{j=1}^{\ell-1} N/2^j\) within the coefficient buffer, and the final approximation occupies the last \(N/2^L\) positions.

1.8.2.3 Perfect Reconstruction

The synthesis (inverse) stage upsamples each sub-band by two and applies the dual filter pair \((h_r, g_r)\). For orthogonal wavelets the synthesis filters are time-reversals of the analysis filters:

\[h_r[k] = h[P - 1 - k], \qquad g_r[k] = g[P - 1 - k]\]

Combined, analysis and synthesis satisfy \(H(z)H_r(z^{-1}) + G(z)G_r(z^{-1}) = 2\), the alias-cancellation and distortion-free conditions, giving exact reconstruction:

\[\hat{x}[n] = x[n]\]

to floating-point rounding.

1.8.2.4 Parseval Identity (Energy Preservation)

For an orthogonal wavelet the DWT is a unitary transform:

\[\sum_{n=0}^{N-1} x[n]^2 = \sum_{n=0}^{N-1} c[n]^2\]

where \(c\) contains all detail and approximation coefficients.

1.8.2.5 Wavelet Families

Haar (\(P = 2\)): \(h = [1,\, 1]/\sqrt{2}\). Piecewise-constant basis; discontinuous; simplest possible.

Daubechies-2 (\(P = 4\), denoted db2 or D4): coefficients chosen so the wavelet has two vanishing moments — it annihilates linear trends. This produces smoother reconstructions and better energy compaction for smooth signals.

1.8.3 Complexity Analysis

Case Time Space Notes
Forward \(O(N)\) \(O(N)\) Geometric series: \(N + N/2 + \cdots \leq 2N\)
Inverse \(O(N)\) \(O(N)\) Identical pass through synthesis bank

Memory is one length-\(N\) coefficient buffer, two length-\(N/2\) working arrays, and the static filter table of size \(P\) — no heap allocation.

1.8.4 Step-by-Step Walkthrough

Input: \(x = [1, 2, 3, 4]\), Haar wavelet, \(L = 1\).

Stage 1 (analysis):

\(i\) \(2i\) \(2i+1\) \(c_A[i] = (x[2i]+x[2i+1])/\sqrt{2}\) \(c_D[i] = (x[2i]-x[2i+1])/\sqrt{2}\)
0 0 1 \((1+2)/\sqrt{2} \approx 2.121\) \((1-2)/\sqrt{2} \approx -0.707\)
1 2 3 \((3+4)/\sqrt{2} \approx 4.950\) \((3-4)/\sqrt{2} \approx -0.707\)

Coefficient buffer (layout detail | approx):

\[\text{coeffs} = [-0.707,\; -0.707,\; 2.121,\; 4.950]\]

Reconstruction: upsample \(c_A\) and \(c_D\), apply synthesis filters, add:

\[\hat{x} = [1.0,\; 2.0,\; 3.0,\; 4.0] \checkmark\]

1.8.5 Pitfalls & Edge Cases

1.8.6 Variants & Generalizations

1.8.7 Applications

1.8.8 Connections to Other Algorithms

1.9 Hilbert Transform / Analytic Signal

1.9.1 Overview & Motivation

Every real-valued signal contains both positive and negative frequency components that carry redundant information. The Hilbert transform discards the negative half and pairs the original signal with a version shifted by exactly −90° at every frequency, producing a complex analytic signal whose magnitude tracks instantaneous amplitude and whose angle tracks instantaneous phase.

This is the standard technique for AM/DSB demodulation, vibration envelope detection, bearing-fault analysis, single-sideband modulation, and instantaneous-frequency measurement — all tasks where the underlying amplitude or phase varies slowly relative to a carrier and must be tracked in real time.

1.9.2 Mathematical Theory

1.9.2.1 Hilbert Transform

For a real signal \(x(t)\) the Hilbert transform is the convolution

\[\mathcal{H}\{x\}(t) = \frac{1}{\pi} \, \text{p.v.} \int_{-\infty}^{\infty} \frac{x(\tau)}{t - \tau} \, d\tau\]

which is equivalently a multiplication of each frequency component by \(-j \operatorname{sgn}(f)\), i.e. a \(-90°\) phase rotation for positive frequencies and \(+90°\) for negative.

1.9.2.2 Analytic Signal

The analytic signal is

\[z(t) = x(t) + j\,\mathcal{H}\{x\}(t)\]

Its one-sided spectrum satisfies \(Z(f) = 0\) for \(f < 0\), \(Z(0) = X(0)\), and \(Z(f) = 2X(f)\) for \(f > 0\).

1.9.2.3 Instantaneous Attributes

Quantity Formula
Amplitude (envelope) \(A(t) = | z(t) | = \sqrt{x^2 + \mathcal{H}^2\{x\}}\)
Phase \(\phi(t) = \arg z(t) = \operatorname{atan2}(\mathcal{H}\{x\}, x)\)
Frequency \(f_i(t) = \frac{1}{2\pi}\frac{d\phi}{dt}\) (phase unwrapped before differencing)

1.9.2.4 FFT Method (Marple)

Given the \(N\)-point DFT \(X[k]\) of a real sequence, the analytic signal is recovered by:

\[H[k] = \begin{cases} X[0] & k = 0 \\ 2X[k] & 1 \le k < N/2 \\ X[N/2] & k = N/2 \\ 0 & N/2 < k < N \end{cases}\]

followed by the inverse DFT of \(H\).

1.9.2.5 FIR Approximation

A Type III/IV antisymmetric FIR filter with impulse response

\[h[k] = \frac{2}{\pi k} w[k], \quad k \text{ odd}; \quad h[k] = 0, \quad k \text{ even}\]

(with a Hamming window \(w[k]\)) approximates the ideal \(-90°\) phase shift across its passband. The delayed original signal (center-tap copy) is paired with the filtered output to form the approximate analytic signal.

1.9.3 Complexity Analysis

Method Time per call Space Notes
FFT (block) \(O(N \log N)\) \(2N\) complex words Exact, latency \(N\)
FIR (streaming) \(O(P)\) per sample \(P\) words state Approx., latency \((P-1)/2\)
Feature extraction \(O(1)\) None atan2, sqrt, subtract

\(P\) = number of FIR taps.

1.9.4 Step-by-Step Walkthrough

FFT method on \(N = 8\), input \(x = \cos(2\pi n / 8)\):

  1. Forward DFT yields \(X[1] = 4\), \(X[7] = 4\), all others zero.
  2. Apply one-sided weighting: \(H[1] = 8\), \(H[7] = 0\).
  3. Inverse DFT of \(H\) produces the imaginary part \(\sin(2\pi n / 8)\).
  4. Analytic signal: \(z[n] = \cos(2\pi n/8) + j\sin(2\pi n/8)\), amplitude \(\equiv 1\).

1.9.5 Pitfalls & Edge Cases

1.9.6 Variants & Generalizations

1.9.7 Applications

1.9.8 Connections to Other Algorithms

1.10 Mel Filterbank and MFCC

1.10.1 Overview & Motivation

Keyword-spotting and small speech models on microcontrollers need a compact, perceptually motivated description of a short audio frame. The log-mel spectrum and its decorrelated form, the Mel-Frequency Cepstral Coefficients (MFCC), compress a frame of \(N\) samples into a few tens of values that follow the coarse spectral envelope on a frequency axis that matches human pitch perception. Since Davis and Mermelstein (1980) the pipeline has been the standard speech front end, and every stage has a fixed size, so it fits a no-heap, real-time budget.

The pipeline is: window → real FFT → power spectrum → triangular mel filterbank → floored natural log (log-mel) → orthonormal DCT-II (MFCC, first \(C\) coefficients).

1.10.2 Mathematical Theory

1.10.2.1 Mel scales

Two mel scales are in common use.

HTK (O’Shaughnessy):

\[m = 2595 \log_{10}\!\left(1 + \frac{f}{700}\right), \qquad f = 700\left(10^{m/2595} - 1\right)\]

Slaney (Auditory Toolbox, librosa default): linear below 1 kHz and logarithmic above, with \(f_{sp} = 200/3\) Hz per mel, a break point \(f_b = 1000\) Hz (\(m_b = f_b / f_{sp} = 15\)) and step \(\lambda = \ln(6.4)/27\):

\[m = \begin{cases} f / f_{sp} & f < f_b \\ m_b + \ln(f / f_b)/\lambda & f \ge f_b \end{cases} \qquad f = \begin{cases} f_{sp}\, m & m < m_b \\ f_b\, e^{\lambda (m - m_b)} & m \ge m_b \end{cases}\]

Both pairs are exact inverses, so a Hz → mel → Hz round trip reproduces the input up to rounding.

1.10.2.2 Triangular filterbank

For \(M\) bands over \([f_\text{min}, f_\text{max}]\), place \(M+2\) points equally spaced in mel and map them back to Hz: \(f_0 = f_\text{min} < f_1 < \dots < f_{M+1} = f_\text{max}\). Band \(m\) rises from \(f_m\) to its centre \(f_{m+1}\) and falls to \(f_{m+2}\). FFT bin \(k\) sits at \(\nu_k = k f_s / N\):

\[H_m(k) = \max\!\left(0,\; \min\!\left(\frac{\nu_k - f_m}{f_{m+1} - f_m},\; \frac{f_{m+2} - \nu_k}{f_{m+2} - f_{m+1}}\right)\right)\]

Sparse structure. If \(\nu_k\) lies in segment \(j\), \([f_j, f_{j+1}]\), only two triangles are non-zero there. Band \(j\) rises with \(r_k = (\nu_k - f_j)/(f_{j+1} - f_j)\) and band \(j-1\) falls with \(1 - r_k\). Storing one segment index and one weight per bin is therefore enough, so applying the filterbank costs one pass over the \(N/2+1\) bins and needs no \(M \times (N/2+1)\) weight matrix.

Partition of unity. Without normalisation, for every bin between the first centre \(f_1\) and the last centre \(f_M\), the two active weights add up to \(r_k + (1 - r_k) = 1\). This holds per bin and exactly, not just in the continuous limit.

Slaney (area) normalisation. Each triangle is scaled by \(2/(f_{m+2} - f_m)\), so its continuous area is 1 and wide high-frequency bands do not dominate. The scale and the normalisation are independent choices. With the Slaney scale, Slaney normalisation and the same \(f_s, N, M, f_\text{min}, f_\text{max}\), the weights equal librosa’s filters.mel defaults.

1.10.2.3 Log-mel and cepstrum

\[S_m = \sum_k H_m(k)\,|X_k|^2, \qquad \tilde S_m = \ln \max(S_m, \varepsilon)\]

The floor \(\varepsilon\) bounds silent or band-limited frames at \(\ln \varepsilon\) instead of \(-\infty\). The cepstrum is the orthonormal DCT-II, truncated to the first \(C \le M\) terms:

\[c_k = s_k \sum_{m=0}^{M-1} \tilde S_m \cos\!\left(\frac{\pi k (2m+1)}{2M}\right), \qquad s_0 = \sqrt{1/M},\; s_{k>0} = \sqrt{2/M}\]

This is scipy.fft.dct(·, type=2, norm='ortho'). The \(C \times M\) cosine basis and the \(N\) window coefficients are computed once at construction.

1.10.3 Complexity Analysis

Stage Time Memory (words) Notes
Construction \(O(N + M + C M)\) — Mel points, bin→segment map, window, DCT basis
Window + real FFT \(O(N \log N)\) \(O(N)\) Dominant cost
Power spectrum \(O(N)\) \(N/2+1\)
Filterbank \(O(N)\) \(2(N/2+1) + M\) Two weights per bin plus a gain per band
Log \(O(M)\) \(M\)
DCT-II \(O(C M)\) \(C M\) Direct product with the precomputed basis

With \(N=512\), \(M=40\), \(C=13\), the tables take about 1.6 k words, where a dense filterbank matrix would need 10 k.

1.10.4 Step-by-Step Walkthrough

\(f_s = 16\) kHz, \(N = 256\) (\(\Delta\nu = 62.5\) Hz), \(M = 20\), \(C = 13\), \([20, 8000]\) Hz, Slaney scale and normalisation, periodic Hann window, \(\varepsilon = 10^{-10}\).

  1. \(m(20) = 0.3\) and \(m(8000) = 45.25\), so the 22 mel points are spaced \(2.14\) mel apart. The first band spans \(20\)–\(305\) Hz with its centre at \(162\) Hz.
  2. Bin 2 (\(125\) Hz) lies in segment 0, giving \(r = (125 - 20)/(162 - 20) = 0.74\) for band 0. Bin 3 (\(187.5\) Hz) lies in segment 1: band 1 rises with \(r = 0.17\) and band 0 falls with \(0.83\).
  3. Band 0’s gain is \(2/(305 - 20) = 7.0\cdot10^{-3}\). Its librosa row sum is \(0.015754\).
  4. The test frame (tones at 125, 440 and 1800 Hz plus a 100–7900 Hz chirp) gives \(\tilde S \approx [-1.23, 0.30, 2.16, -0.32, -7.90, \dots, -5.76]\) and \(c \approx [-13.82, 1.49, 2.08, 7.49, \dots]\), which match the float64 librosa + SciPy reference to within \(5\cdot10^{-3}\).
  5. An all-zero frame gives \(\tilde S_m = \ln 10^{-10} = -23.03\) for every band, so \(c_0 = \sqrt{20}\cdot(-23.03)\) and \(c_{k>0} = 0\).

1.10.5 Pitfalls & Edge Cases

1.10.6 Variants & Generalizations

1.10.7 Applications

Keyword spotting (DS-CNN / “Hello Edge” class models), speaker verification, audio event detection and voice activity detection. A typical MCU configuration is 25–40 ms frames, a 10–20 ms hop, \(M = 40\) and \(C = 10\)–\(13\).

1.10.8 Connections to Other Algorithms

The stage consumes the half spectrum of the real-input FFT and the analysis windows. The cepstral stage is a truncated DCT-II. Power-spectral-density estimation shares the window → FFT → \(|X|^2\) front.

1.11 Convolution & Correlation

1.11.1 Overview & Motivation

Two signals interacting in time — one being filtered, delayed, or matched against another — are described by convolution and correlation. Convolution is the time-domain action of any linear time-invariant (LTI) filter: given an input signal and a system’s impulse response, their convolution is the output. Correlation measures the similarity between two signals as a function of relative shift (lag), making it the foundation of matched filtering, pulse detection, and time-delay estimation.

Both operations are indispensable in embedded DSP: they underlie FIR filtering, pitch detection, radar/sonar processing, and auto-regressive modeling. Bounded, no-heap implementations make these algorithms practical on microcontrollers.

1.11.2 Mathematical Theory

1.11.2.1 Linear Convolution

For finite sequences \(x\) of length \(M\) and \(h\) of length \(K\), the linear convolution \(y = x * h\) is a sequence of length \(M + K - 1\) defined by:

\[y[n] = \sum_{k=0}^{M-1} x[k] \cdot h[n - k], \quad n = 0, \ldots, M+K-2\]

where \(h[j] = 0\) for \(j < 0\) or \(j \geq K\).

1.11.2.2 Circular Convolution

For two sequences of equal length \(N\), circular (periodic) convolution wraps indices modulo \(N\):

\[y[n] = \sum_{k=0}^{N-1} x[k] \cdot h[(n - k) \bmod N], \quad n = 0, \ldots, N-1\]

Circular convolution equals linear convolution only when both operands are zero-padded to at least \(M + K - 1\) samples. Without padding, time-aliasing occurs.

1.11.2.3 Cross-Correlation

Cross-correlation of \(x\) (length \(M\)) and \(y\) (length \(K\)) is defined as:

\[r_{xy}[\ell] = \sum_{n} x[n] \cdot y[n - \ell]\]

This equals linear convolution of \(x\) with the time-reversed \(y\):

\[r_{xy} = x * \overline{y}\]

1.11.2.4 Auto-Correlation

Auto-correlation is cross-correlation of a signal with itself:

\[r_{xx}[\ell] = \sum_{n} x[n] \cdot x[n - \ell]\]

It is always symmetric about the zero-lag index, and its maximum occurs at zero lag. Dividing by \(r_{xx}[0]\) yields a normalized coefficient in \([-1, 1]\).

1.11.2.5 Convolution Theorem (Fast Path)

For long signals, the convolution theorem enables an \(O(L \log L)\) computation:

\[x * h = \mathcal{F}^{-1}\!\left(\mathcal{F}(x) \cdot \mathcal{F}(h)\right)\]

where \(\mathcal{F}\) denotes the DFT and \(L\) is the next power of two \(\geq M + K - 1\). The pointwise complex product replaces the \(O(MK)\) direct sum.

1.11.3 Complexity Analysis

Operation Time Space (extra) Notes
Linear convolution \(O(MK)\) \(O(1)\) Writes into caller-owned buffer
Circular convolution \(O(N^2)\) \(O(1)\) Same as linear for equal-length input
Cross-correlation \(O(MK)\) \(O(K)\) Reverses one operand on the stack
Auto-correlation \(O(M^2)\) \(O(1)\) Alias of cross-correlation
Fast convolution \(O(L \log L)\) \(O(L)\) \(L = 2^{\lceil\log_2(M+K-1)\rceil}\)

The fast path is beneficial when \(MK > L \log_2 L\), roughly when both operands exceed 32–64 samples.

1.11.4 Step-by-Step Walkthrough

Linear convolution of \(x = [1, 2, 3]\) and \(h = [1, 1]\) (output length \(= 4\)):

\(n\) Active \(k\) range Computation \(y[n]\)
0 \(k=0\) \(1 \cdot 1\) 1
1 \(k=0,1\) \(2 \cdot 1 + 1 \cdot 1\) 3
2 \(k=1,2\) \(3 \cdot 1 + 2 \cdot 1\) 5
3 \(k=2\) \(3 \cdot 1\) 3

Result: \([1, 3, 5, 3]\).

Auto-correlation of \([1, 1, 1, 1]\) produces the triangular sequence \([1, 2, 3, 4, 3, 2, 1]\), with the peak at the zero-lag center index (index 3 in 0-based notation).

1.11.5 Pitfalls & Edge Cases

1.11.6 Variants & Generalizations

1.11.7 Applications

1.11.8 Connections to Other Algorithms

2 Control Analysis

2.1 Frequency Response

2.1.1 Overview & Motivation

The frequency response \(H(e^{j\omega})\) characterizes how a discrete-time linear system (filter) modifies the amplitude and phase of each frequency component in a signal. Computing and plotting the frequency response (Bode plot) is essential for verifying filter designs, understanding system behavior, and tuning parameters. This implementation evaluates the transfer function at logarithmically spaced frequencies, producing magnitude (dB) and phase (degrees) curves.

2.1.2 Mathematical Theory

2.1.2.1 Transfer Function Evaluation

Given a filter with feedforward coefficients \(\{b_i\}_{i=0}^{P-1}\) and feedback coefficients \(\{a_i\}_{i=0}^{Q-1}\), the frequency response at angular frequency \(\omega\) is:

\[H(e^{j\omega}) = \frac{\sum_{i=0}^{P-1} b_i \cdot e^{-j\omega i}}{\sum_{i=0}^{Q-1} a_i \cdot e^{-j\omega i}}\]

For FIR filters, the denominator reduces to \(a_0 = 1\).

2.1.2.2 Magnitude and Phase

From the complex-valued response:

\[|H(e^{j\omega})| = \sqrt{\text{Re}(H)^2 + \text{Im}(H)^2}\]

\[\text{Magnitude (dB)} = 20 \log_{10} |H(e^{j\omega})|\]

\[\text{Phase (degrees)} = \frac{180}{\pi} \cdot \arg(H(e^{j\omega}))\]

2.1.2.3 Frequency Grid

Frequencies are evaluated on a logarithmic grid from \(f_s / N\) to \(f_s / 2\) (Nyquist), where \(f_s\) is the sampling frequency and \(N\) is the number of evaluation points. The angular frequency at each point is:

\[\omega_k = \frac{2\pi f_k}{f_s}\]

2.1.3 Complexity Analysis

Case Time Space Notes
Average \(O(N \cdot (P+Q))\) \(O(N)\) \(N\) frequency points, \(P+Q\) coefficients

2.1.4 Step-by-Step Walkthrough

Given a simple first-order low-pass FIR filter with \(b = [0.5, 0.5]\), \(a = [1.0]\), and \(f_s = 100\) Hz, evaluated at \(f = 10\) Hz:

  1. Compute \(\omega = 2\pi \cdot 10 / 100 = 0.2\pi\)
  2. Numerator: \(0.5 \cdot e^{0} + 0.5 \cdot e^{-j0.2\pi} = 0.5 + 0.5(\cos(0.2\pi) - j\sin(0.2\pi))\)
  3. Denominator: \(1.0\)
  4. \(|H| = \sqrt{0.905^2 + 0.294^2} = 0.952\)
  5. Magnitude: \(20\log_{10}(0.952) = -0.43\) dB
  6. Phase: \(\arctan(-0.294/0.905) = -18°\)

2.1.5 Pitfalls & Edge Cases

2.1.6 Variants & Generalizations

2.1.7 Applications

2.1.8 Connections to Other Algorithms

2.2 Root Locus

2.2.1 Overview & Motivation

When designing a feedback control system, a fundamental question is: how do the closed-loop poles move as I change the loop gain? The root locus answers this by plotting the trajectories of the closed-loop poles in the complex plane as a scalar gain \(K\) varies from \(K_{\min}\) to \(K_{\max}\).

This visualization immediately reveals whether increasing gain will drive the system unstable (poles crossing into the right half-plane), where oscillatory modes appear (complex pole pairs), and which gain ranges produce acceptable damping. It is one of the oldest and most intuitive tools in classical control design.

2.2.2 Mathematical Theory

2.2.2.1 Closed-Loop Characteristic Equation

Given an open-loop transfer function:

\[G(s) = K \cdot \frac{N(s)}{D(s)}\]

the closed-loop characteristic equation (for unity feedback) is:

\[D(s) + K \cdot N(s) = 0\]

The root locus is the set of all roots of this equation as \(K\) varies over \([K_{\min}, K_{\max}]\).

2.2.2.2 Key Properties

2.2.2.3 Gain Sweep

The gain is swept logarithmically to provide uniform resolution across decades:

\[K_i = 10^{\,\log_{10}(K_{\min}) \;+\; \frac{i}{N-1}\left(\log_{10}(K_{\max}) - \log_{10}(K_{\min})\right)}\]

where \(N\) is the number of gain steps and \(i \in [0, N-1]\).

At each gain step, the roots of \(D(s) + K_i \cdot N(s) = 0\) are found using the Durand-Kerner polynomial root-finder.

2.2.3 Complexity Analysis

Case Time Space Notes
All \(O(N_g \cdot n^2 \cdot k)\) \(O(N_g \cdot n)\) \(N_g\) = gain steps, \(n\) = polynomial order, \(k\) = Durand-Kerner iterations per step

Why: At each of the \(N_g\) gain steps, a degree-\(n\) polynomial is solved via Durand-Kerner, which performs \(k\) iterations each costing \(O(n^2)\) (evaluating the polynomial and computing the denominator product for all \(n\) roots).

2.2.4 Step-by-Step Walkthrough

System: \(G(s) = K \cdot \frac{s + 1}{s(s + 2)}\), sweep \(K\) from 0.01 to 10.

Step 1 — Identify poles and zeros

Step 2 — Form characteristic polynomial at \(K = 1\)

\[D(s) + K \cdot N(s) = s^2 + 2s + 1 \cdot (s + 1) = s^2 + 3s + 1\]

Step 3 — Solve using Durand-Kerner:

\[s = \frac{-3 \pm \sqrt{9 - 4}}{2} = \frac{-3 \pm \sqrt{5}}{2} \approx -0.382,\; -2.618\]

Both poles are real and negative → system is stable at \(K = 1\).

Step 4 — Repeat for each gain step and plot all root positions in the complex plane.

Im(s)
  |
  |     × zero (-1)
--●-----×------●--> Re(s)
  0    -1     -2
 pole         pole

As \(K\) increases: the two poles approach each other on the real axis, meet between 0 and −2, then split into a complex conjugate pair. One branch eventually converges to the zero at \(s = -1\); the other diverges to \(-\infty\).

2.2.5 Pitfalls & Edge Cases

2.2.6 Variants & Generalizations

Variant Key Difference
Complementary root locus Traces roots for \(K < 0\) (positive feedback)
Root contour Varies two or more parameters simultaneously
Discrete root locus Same concept applied to \(z\)-domain polynomials for digital control
Evans rules Analytical rules for sketching the root locus by hand (angle/magnitude criteria)

2.2.7 Applications

2.2.8 Connections to Other Algorithms

graph LR
    RL["Root Locus"] --> DK["Durand-Kerner"]
    RL --> LQR["LQR (pole placement alternative)"]
    DK --> RL
Algorithm Relationship
Durand-Kerner Used at each gain step to find the roots of the characteristic polynomial
LQR Alternative approach to pole placement — LQR optimizes a cost rather than manually selecting gain via root locus

2.3 Controllability and Observability

2.3.1 Overview & Motivation

Controllability and observability are fundamental structural properties of a linear dynamical system. They answer two essential design questions: can the inputs drive the state to any desired configuration, and can the initial state be inferred from the outputs alone?

A system lacking controllability has modes that no actuator can excite; a system lacking observability has modes that no sensor can detect. Both deficiencies make feedback design either impossible or degenerate. Checking these properties before attempting pole placement, LQR synthesis, or observer design prevents numerical failure and guides sensor-actuator placement.

2.3.2 Mathematical Theory

2.3.2.1 Prerequisites

Consider a discrete-time linear time-invariant system

\[x_{k+1} = A x_k + B u_k, \quad y_k = C x_k + D u_k\]

with state dimension \(n\), input dimension \(m\), and output dimension \(p\).

2.3.2.2 Controllability Matrix

The \(n \times nm\) controllability matrix is

\[\mathcal{C} = \begin{bmatrix} B & AB & A^2 B & \cdots & A^{n-1}B \end{bmatrix}\]

The pair \((A, B)\) is controllable if and only if \(\mathrm{rank}(\mathcal{C}) = n\).

2.3.2.3 Observability Matrix

The \(np \times n\) observability matrix is

\[\mathcal{O} = \begin{bmatrix} C \\ CA \\ CA^2 \\ \vdots \\ CA^{n-1} \end{bmatrix}\]

The pair \((A, C)\) is observable if and only if \(\mathrm{rank}(\mathcal{O}) = n\).

2.3.2.4 Duality

Controllability and observability are dual: \((A, B)\) is controllable if and only if \((A^\top, B^\top)\) is observable. Equivalently, \(\mathcal{C}(A, B) = \mathcal{O}(A^\top, C^\top)^\top\).

2.3.2.5 Gramians

For a Schur-stable matrix \(A\) (spectral radius \(< 1\)), the controllability and observability Gramians are the unique positive semi-definite solutions to the discrete Lyapunov equations

\[A W_c A^\top - W_c + BB^\top = 0, \quad A^\top W_o A - W_o + C^\top C = 0.\]

The Gramians encode more than rank: their eigenvalues quantify how easily each mode is excited or observed. Balanced truncation model reduction uses Gramians to identify modes that are simultaneously hard to reach and hard to see.

2.3.2.6 Rank via Gaussian Elimination

Rank is computed by Gaussian elimination with partial pivoting on a working copy of the matrix. At each step the column with the largest absolute pivot is selected among remaining rows. A pivot is counted only if its magnitude exceeds \(\tau \cdot \|M\|_\infty\) where \(\tau\) is a user-supplied tolerance.

2.3.3 Complexity Analysis

Operation Time Space Notes
Controllability matrix \(O(n^2 m)\) per column, \(n\) columns → \(O(n^3 m)\) \(O(nm)\) output Iterative matmul
Observability matrix \(O(n^2 p)\) per row-block, \(n\) blocks → \(O(n^3 p)\) \(O(np)\) output Iterative matmul
Rank (Gaussian elim) \(O(r^2 \min(r,c))\) where \(r,c\) are matrix dims \(O(rc)\) copy Partial pivoting
Gramian (Lyapunov fixed-point) \(O(K n^3)\) iterations \(O(n^2)\) Up to 200 iterations

2.3.4 Step-by-Step Walkthrough

Plant: \(A = \begin{bmatrix}0 & 1 \\ -2 & -3\end{bmatrix}\), \(B = \begin{bmatrix}0 \\ 1\end{bmatrix}\), \(C = \begin{bmatrix}1 & 0\end{bmatrix}\).

Controllability matrix:

\(AB = \begin{bmatrix}0 & 1 \\ -2 & -3\end{bmatrix}\begin{bmatrix}0 \\ 1\end{bmatrix} = \begin{bmatrix}1 \\ -3\end{bmatrix}\)

\(\mathcal{C} = \begin{bmatrix}0 & 1 \\ 1 & -3\end{bmatrix}\), \(\det(\mathcal{C}) = -1 \neq 0\), rank \(= 2\). Controllable.

Observability matrix:

\(CA = \begin{bmatrix}1 & 0\end{bmatrix}\begin{bmatrix}0 & 1 \\ -2 & -3\end{bmatrix} = \begin{bmatrix}0 & 1\end{bmatrix}\)

\(\mathcal{O} = \begin{bmatrix}1 & 0 \\ 0 & 1\end{bmatrix}\), rank \(= 2\). Observable.

2.3.5 Pitfalls & Edge Cases

2.3.6 Variants & Generalizations

2.3.7 Applications

2.3.8 Connections to Other Algorithms

2.4 Continuous-to-Discrete Conversion (c2d)

2.4.1 Overview & Motivation

Physical plants and classical controllers are designed in continuous time using differential equations and the Laplace transform. Microcontrollers, however, execute discrete update loops at a fixed sampling period \(T_s\). Continuous-to-discrete conversion bridges the two worlds: it takes a continuous-time state-space model \((A, B, C, D)\) and produces the equivalent discrete-time model \((A_d, B_d, C_d, D_d)\) that a sampled-data controller can execute directly.

Choosing the right discretization method is critical. A poor choice can push stable poles outside the unit disk, introduce spurious resonances, or distort the DC gain — all of which degrade closed-loop performance or cause instability on hardware.

2.4.2 Mathematical Theory

2.4.2.1 State-Space Representation

A continuous-time LTI system obeys

\[\dot{x}(t) = A\,x(t) + B\,u(t), \qquad y(t) = C\,x(t) + D\,u(t)\]

where \(x \in \mathbb{R}^n\) is the state, \(u \in \mathbb{R}^m\) the input, and \(y \in \mathbb{R}^p\) the output. The goal is to find \((A_d, B_d, C_d, D_d)\) such that

\[x_{k+1} = A_d\,x_k + B_d\,u_k, \qquad y_k = C_d\,x_k + D_d\,u_k\]

matches the continuous solution at sample instants \(t_k = k\,T_s\).

2.4.2.2 Zero-Order Hold (Van Loan’s Trick)

Assuming the input is held constant between samples (true for a DAC or PWM), the exact solution is

\[A_d = e^{A T_s}, \qquad B_d = \left(\int_0^{T_s} e^{A\tau}\,d\tau\right) B\]

Van Loan (1978) observed that both quantities emerge from a single matrix exponential of the augmented \((n+m) \times (n+m)\) block matrix:

\[M = \begin{bmatrix} A & B \\ 0 & 0 \end{bmatrix} T_s, \qquad e^M = \begin{bmatrix} A_d & B_d \\ 0 & I \end{bmatrix}\]

The upper-left \(n\times n\) block is \(A_d\); the upper-right \(n\times m\) block is \(B_d\). \(C\) and \(D\) are unchanged under ZOH because the output equation is instantaneous.

2.4.2.3 Tustin / Bilinear Transformation

The Tustin method replaces the Laplace variable via

\[s \;\longleftarrow\; \frac{2}{T_s}\,\frac{z-1}{z+1}\]

Setting \(\alpha = 2/T_s\) and \(P = (\alpha I - A)^{-1}\):

\[A_d = P(\alpha I + A), \quad B_d = 2P\,B, \quad C_d = \alpha\,C\,P, \quad D_d = D + C\,P\,B\]

Tustin maps the entire open left-half plane into the open unit disk, so it is stability-preserving by construction. It is the preferred method for discretizing controllers and filters when a specific corner frequency must be matched (after frequency prewarping).

2.4.2.4 Forward Euler

The cheapest approximation — first-order rectangle rule:

\[A_d = I + A\,T_s, \quad B_d = B\,T_s, \quad C_d = C, \quad D_d = D\]

Conditionally stable: poles may leave the unit disk when \(T_s\) is too large relative to the fastest mode.

2.4.2.5 Backward Euler

Implicit first-order rule, equivalent to the \(s \leftarrow (z-1)/(z\,T_s)\) substitution. Setting \(P = (I - A\,T_s)^{-1}\):

\[A_d = P, \quad B_d = P\,B\,T_s, \quad C_d = C\,P, \quad D_d = D + C\,P\,B\,T_s\]

Unconditionally stable (maps the left-half plane into the unit disk) but introduces phase lag relative to the true system.

2.4.3 Complexity Analysis

Method Time Space Notes
ZOH \(O((n+m)^3)\) \((n+m)^2\) augmented mat One matrix exponential via scaling/squaring
Tustin \(O(n^3)\) \(O(n^2)\) One \(n \times n\) LU factorisation
Forward Euler \(O(n^2)\) \(O(n^2)\) Pure matrix multiply + add
Backward Euler \(O(n^3)\) \(O(n^2)\) One \(n \times n\) LU factorisation

All working memory is allocated on the stack; no heap is used.

2.4.4 Step-by-Step Walkthrough

Consider a scalar integrator \(\dot{x} = u\), so \(A=0\), \(B=1\), \(C=1\), \(D=0\), with \(T_s = 0.1\).

ZOH:

\[M = \begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix} \times 0.1 = \begin{bmatrix} 0 & 0.1 \\ 0 & 0 \end{bmatrix}\]

\[e^M = I + M = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \end{bmatrix}\]

So \(A_d = 1\), \(B_d = 0.1\) — the integrator accumulates \(u \cdot T_s\) each step.

Tustin (\(\alpha = 20\)):

\[P = (20 - 0)^{-1} = 0.05\]

\[A_d = 0.05 \times 20 = 1, \quad B_d = 2 \times 0.05 = 0.1\]

Both methods agree exactly for a pure integrator, as the bilinear transform maps the pole at \(s=0\) to \(z=1\) without distortion.

2.4.5 Pitfalls & Edge Cases

2.4.6 Variants & Generalizations

2.4.7 Applications

2.4.8 Connections to Other Algorithms

2.5 Transfer-Function ↔︎ State-Space Conversion

2.5.1 Overview & Motivation

Classical control design and modern state-space methods speak different mathematical languages. Frequency-domain tools — Bode plots, root locus, PID/lead-lag synthesis — operate on transfer functions, ratios of polynomials in the Laplace variable \(s\). Modern methods — LQR, observers, Kalman filters — require the state-space quadruple \((A, B, C, D)\). This converter is the bridge. A compensator designed in the frequency domain can be dropped into a state-space runtime without hand-derivation, and a state-space plant model can be lifted into the frequency domain for classical analysis.

2.5.2 Mathematical Theory

2.5.2.1 Transfer Function

A SISO rational transfer function is

\[H(s) = \frac{b_0 s^n + b_1 s^{n-1} + \cdots + b_n}{a_0 s^n + a_1 s^{n-1} + \cdots + a_n}\]

Normalising by \(a_0\) produces the monic denominator \(s^n + \hat{a}_1 s^{n-1} + \cdots + \hat{a}_n\). When \(\deg(\text{num}) = \deg(\text{den})\), a polynomial division peels off the direct feed-through scalar \(D = b_0/a_0\), leaving a strictly proper remainder.

2.5.2.2 Controllable Canonical Form

The monic denominator maps directly to the companion (controllable canonical) matrix

\[A = \begin{bmatrix} 0 & 1 & 0 & \cdots & 0 \\ 0 & 0 & 1 & \cdots & 0 \\ \vdots & & & \ddots & \vdots \\ -\hat{a}_n & -\hat{a}_{n-1} & \cdots & & -\hat{a}_1 \end{bmatrix}, \quad B = \begin{bmatrix} 0 \\ \vdots \\ 0 \\ 1 \end{bmatrix}\]

The output row \(C = \begin{bmatrix} \hat{b}_n' & \cdots & \hat{b}_1' \end{bmatrix}\) is formed from the strictly-proper remainder coefficients \(\hat{b}_i'\) after the feed-through split.

2.5.2.3 Observable Canonical Form

The observable canonical form is the algebraic dual of the controllable form:

\[A_\text{ocf} = A_\text{ccf}^\top, \quad B_\text{ocf} = C_\text{ccf}^\top, \quad C_\text{ocf} = B_\text{ccf}^\top, \quad D_\text{ocf} = D_\text{ccf}\]

Both realisations represent the same input-output map and share identical transfer functions.

2.5.2.4 State-Space to Transfer Function: Faddeev–Le Verrier

Given \((A, B, C, D)\), the transfer function is recovered via

\[H(s) = C\,(sI - A)^{-1} B + D = \frac{C\,\text{adj}(sI - A)\,B + D\,\det(sI - A)}{\det(sI - A)}\]

The Faddeev–Le Verrier algorithm computes the characteristic polynomial \(\det(sI - A) = s^n + c_1 s^{n-1} + \cdots + c_n\) and the adjugate action \(\text{adj}(sI - A)B\) in a single recursion of \(n\) steps:

\[M_0 = I, \quad c_k = -\frac{1}{k}\mathrm{tr}(A M_{k-1}), \quad M_k = A M_{k-1} + c_k I\]

The numerator coefficient for degree \(n - k\) is \(C M_k B\).

2.5.3 Complexity Analysis

Operation Time Space Notes
ToControllableCanonical \(O(n)\) \(O(n^2)\) Direct coefficient placement
ToObservableCanonical \(O(n^2)\) \(O(n^2)\) One transpose after CCF
ToTransferFunction \(O(n^4)\) \(O(n^2)\) \(n\) steps, each \(O(n^3)\) matrix-matrix

Memory is \(O(n^2)\) for \(A\) and \(O(n)\) for coefficient arrays; all allocations are std::array on the stack.

2.5.4 Step-by-Step Walkthrough

\(H(s) = \dfrac{s + 2}{s^2 + 3s + 2}\), so \(n = 2\), denominator \([1, 3, 2]\), numerator \([0, 1, 2]\).

Monic normalisation: already monic; \(\hat{a}_1 = 3\), \(\hat{a}_2 = 2\).

Feed-through split: \(b_0 = 0\), so \(D = 0\) and \(\hat{b}' = [0, 1, 2]\).

Companion matrix:

\[A = \begin{bmatrix} 0 & 1 \\ -2 & -3 \end{bmatrix}, \quad B = \begin{bmatrix} 0 \\ 1 \end{bmatrix}, \quad C = \begin{bmatrix} 2 & 1 \end{bmatrix}\]

Observable form:

\[A_\text{ocf} = \begin{bmatrix} 0 & -2 \\ 1 & -3 \end{bmatrix}, \quad B_\text{ocf} = \begin{bmatrix} 2 \\ 1 \end{bmatrix}, \quad C_\text{ocf} = \begin{bmatrix} 0 & 1 \end{bmatrix}\]

Round-trip verification via Le Verrier (\(n = 2\)):

Result: \(H(s) = \dfrac{s + 2}{s^2 + 3s + 2}\). Identical to the original.

2.5.5 Pitfalls & Edge Cases

2.5.6 Variants & Generalizations

2.5.7 Applications

2.5.8 Connections to Other Algorithms

3 Controllers

3.1 Bang-Bang / Hysteresis (Relay) Controller

3.1.1 Overview & Motivation

The bang-bang controller is the simplest closed-loop regulator: its output switches between two discrete levels depending on whether the controlled variable is above or below a threshold. Without hysteresis, a plain threshold comparator chatters — switching at high frequency whenever noise nudges the signal across the boundary.

Adding a dead-band (Schmitt trigger) eliminates chatter by giving the relay memory: once the output goes High it stays High until the measurement falls all the way to a lower threshold, and vice versa. This makes the relay a practical actuator-safe control primitive for thermostats, fridge compressors, tank level switches, and power-stage on/off regulation.

3.1.2 Mathematical Theory

3.1.2.1 State Transition

The relay holds a binary state \(s \in \{\text{Low}, \text{High}\}\) with the following transition rules:

\[ s[k] = \begin{cases} \text{High} & \text{if } s[k-1] = \text{Low} \text{ and } x[k] \geq \theta_H \\ \text{Low} & \text{if } s[k-1] = \text{High} \text{ and } x[k] \leq \theta_L \\ s[k-1] & \text{otherwise} \end{cases} \]

where \(\theta_L < \theta_H\) are the lower and upper switching thresholds (the hysteresis band).

3.1.2.2 Output Map

\[ u[k] = \begin{cases} u_H & \text{if } s[k] = \text{High} \\ u_L & \text{if } s[k] = \text{Low} \end{cases} \]

The output levels \(u_L\) and \(u_H\) are arbitrary; common choices are \(\{0, 1\}\) or \(\{-1, +1\}\).

3.1.2.3 Hysteresis Band Width

The band width \(\Delta = \theta_H - \theta_L\) is the key design parameter. It bounds the switching frequency \(f_s\) given a signal slope \(\dot{x}\):

\[ f_s \leq \frac{|\dot{x}|}{2\Delta} \]

A wider band reduces \(f_s\) (protecting relays and power stages) at the cost of a larger steady-state limit cycle amplitude.

3.1.3 Complexity Analysis

Case Time Space Notes
All \(O(1)\) \(O(1)\) Two comparisons, one state bit, one select

No arithmetic on the signal path — only comparisons — so the relay introduces no numerical error and is exactly representable in any floating-point format.

3.1.4 Step-by-Step Walkthrough

Setup: band \([\theta_L, \theta_H] = [-0.2, 0.2]\), outputs \(u_L = 0\), \(u_H = 1\), initial state Low.

Step \(x[k]\) Condition \(s[k]\) \(u[k]\)
1 0.0 Low, \(x < 0.2\) Low 0
2 0.3 Low, \(x \geq 0.2\) → switch High 1
3 0.1 High, \(x > -0.2\) → hold High 1
4 −0.3 High, \(x \leq -0.2\) → switch Low 0
5 0.0 Low, \(x < 0.2\) → hold Low 0

3.1.5 Pitfalls & Edge Cases

3.1.6 Variants & Generalizations

Variant Key Difference
Plain comparator \(\Delta = 0\); no memory, chatters on noise
Asymmetric band \(\theta_H\) and \(\theta_L\) not symmetric around the set-point; biases the duty cycle
Three-state relay Adds a dead-band output level \(u_0\); used in motor direction control
Adaptive hysteresis Band width tracks signal variance online to maintain a target switching rate
Relay feedback test (Åström–Hägglund) Deliberate oscillation under relay feedback to identify the ultimate gain/period for automatic PID tuning

3.1.7 Applications

3.1.8 Connections to Other Algorithms

Algorithm Relationship
PID Controller The relay’s limit cycle can be used to identify PID tuning parameters via the Åström–Hägglund relay-feedback test
Saturation / Rate Limiter Continuous-output counterpart for actuator constraint; often combined with a relay in cascaded loops

3.2 Deadbeat Control

3.2.1 Overview & Motivation

In discrete-time control, most feedback laws drive the plant asymptotically toward a target — the state error decays exponentially but never reaches zero in finite time. Deadbeat control takes the opposite approach: given a linear, time-invariant plant and a desired terminal state, it computes a sequence of inputs that drives the state to the target exactly in a fixed, finite number of samples.

This property is valuable whenever settling time is a hard constraint. A motor current-loop running at 10 kHz, a grid-tied inverter that must complete a transient in two PWM cycles, or a precision positioning stage that cannot overshoot — all benefit from the guarantee that the error is identically zero after \(N\) steps, rather than merely small.

The runtime cost of a deadbeat controller is identical to LQR: a single matrix-vector multiply per sample. The extra cost (gain computation via the reachability matrix and pseudo-inverse) is paid once at initialization.

3.2.2 Mathematical Theory

3.2.2.1 Discrete-Time State-Space System

\[x[k+1] = Ax[k] + Bu[k]\]

where \(x \in \mathbb{R}^n\) is the state, \(u \in \mathbb{R}^m\) is the control input, \(A \in \mathbb{R}^{n \times n}\) is the state-transition matrix, and \(B \in \mathbb{R}^{n \times m}\) is the input matrix.

3.2.2.2 N-Step Reachability

After \(N\) steps, applying inputs \(u[0], u[1], \ldots, u[N-1]\) from initial state \(x[0]\), the state satisfies:

\[x[N] = A^N x[0] + \Gamma_N \begin{bmatrix} u[0] \\ u[1] \\ \vdots \\ u[N-1] \end{bmatrix}\]

where the reachability matrix is:

\[\Gamma_N = \begin{bmatrix} A^{N-1}B & A^{N-2}B & \cdots & AB & B \end{bmatrix} \in \mathbb{R}^{n \times Nm}\]

The system is N-step reachable to target \(r\) from \(x[0]\) if and only if:

\[r - A^N x[0] \in \text{range}(\Gamma_N)\]

3.2.2.3 Minimum-Norm Input Sequence

When \(\Gamma_N\) has full row rank (i.e., \(n \leq Nm\) and the plant is completely reachable in \(N\) steps), the minimum-Euclidean-norm input sequence that achieves \(x[N] = r\) is:

\[\mathbf{u}^* = \Gamma_N^+ \left( r - A^N x[0] \right)\]

where \(\Gamma_N^+ = \Gamma_N^T (\Gamma_N \Gamma_N^T)^{-1}\) is the Moore-Penrose pseudo-inverse, computed here via Singular Value Decomposition for numerical robustness.

3.2.2.4 Precomputed Gain Matrices

Only the first \(m\) elements of \(\mathbf{u}^*\) are applied at each sample (the remaining elements are recomputed at the next step). Let \(\Pi_0\) denote the operator that extracts the first \(m\) rows of a matrix. Define the open-loop state-feedback gain:

\[K_x = \Pi_0 \left(\Gamma_N^+\right) \cdot A^N \in \mathbb{R}^{m \times n}\]

For unity closed-loop DC gain in receding-horizon (closed-loop) operation, \(K_r\) must satisfy:

\[B \, K_r = I - A + B \, K_x\]

For square systems (\(m = n\)), \(B\) is invertible and the unique solution is:

\[K_r = B^{-1}(I - A + B \, K_x) \in \mathbb{R}^{m \times n}\]

For non-square systems (\(m < n\)), the open-loop first-step gain is used directly:

\[K_r = \Pi_0 \left(\Gamma_N^+\right) \in \mathbb{R}^{m \times n}\]

The resulting deadbeat control law is:

\[u[k] = K_r \, r - K_x \, x[k]\]

This is a constant-gain affine state-feedback law that can be evaluated with two matrix-vector multiplies per sample, identical in cost to LQR.

3.2.2.5 Convergence Guarantee

Under this law, the state satisfies \(x[k+N] = r\) exactly (in exact arithmetic) for any initial condition \(x[k]\) reachable to \(r\) in \(N\) steps. The closed-loop poles are all placed at the origin — the system has a finite settling time of exactly \(N\) samples.

3.2.3 Complexity Analysis

Phase Time Space Notes
Build \(\Gamma_N\) (init) \(O(N \cdot n^2 m)\) \(O(n \cdot Nm)\) \(N\) matrix-matrix multiplies of size \(n \times n\) by \(n \times m\)
SVD of \(\Gamma_N\) (init) \(O(n^2 \cdot Nm)\) \(O(n \cdot Nm)\) Golub-Kahan bidiagonalization + diagonalization
Compute \(A^N\) (init) \(O(N \cdot n^3)\) \(O(n^2)\) \(N\) matrix-matrix multiplies
Control step (online) \(O(n \cdot m)\) \(O(n \cdot m)\) Two matrix-vector multiplies

The initialization cost is negligible for the embedded use case: it is incurred once, and the gains are stored as constant matrices on the stack.

3.2.4 Step-by-Step Walkthrough

System: Double integrator — position and velocity driven by acceleration input, discretized at \(\Delta t = 0.1\,\text{s}\):

\[A = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \end{bmatrix}, \quad B = \begin{bmatrix} 0.005 \\ 0.1 \end{bmatrix}\]

Goal: Drive \(x[2] = [5, 0]^T\) from \(x[0] = [0, 0]^T\) in \(N = 2\) steps.

Step 1 — Build \(\Gamma_2\):

\[\Gamma_2 = \begin{bmatrix} AB & B \end{bmatrix} = \begin{bmatrix} 0.015 & 0.005 \\ 0.1 & 0.1 \end{bmatrix}\]

Step 2 — Compute \(\Gamma_2^{-1}\) (square and invertible here):

\[\det(\Gamma_2) = 0.015 \times 0.1 - 0.005 \times 0.1 = 0.001\]

\[\Gamma_2^{-1} = \begin{bmatrix} 100 & -5 \\ -100 & 15 \end{bmatrix}\]

Step 3 — Extract \(K_r\) (first row of \(\Gamma_2^{-1}\)):

\[K_r = \begin{bmatrix} 100 & -5 \end{bmatrix}\]

Step 4 — Compute \(A^2\):

\[A^2 = \begin{bmatrix} 1 & 0.2 \\ 0 & 1 \end{bmatrix}\]

Step 5 — Compute \(K_x = K_r A^2\):

\[K_x = \begin{bmatrix} 100 & -5 \end{bmatrix} \begin{bmatrix} 1 & 0.2 \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} 100 & 15 \end{bmatrix}\]

Step 6 — Apply control law (\(r = [5, 0]^T\), \(x[0] = [0, 0]^T\)):

\[u[0] = K_r r - K_x x[0] = 500 - 0 = 500\]

\[x[1] = Ax[0] + Bu[0] = [2.5,\; 50]^T\]

\[u[1] = 500 - K_x [2.5, 50]^T = 500 - (250 + 750) = -500\]

\[x[2] = Ax[1] + Bu[1] = [2.5 + 5,\; 50] + [-2.5,\; -50] = [5,\; 0]^T \checkmark\]

3.2.5 Pitfalls & Edge Cases

3.2.6 Variants & Generalizations

Variant Key Difference
1-step deadbeat (\(N = 1\)) Requires \(B\) to be square and invertible; gain is simply \(B^{-1}\) and \(B^{-1}A\)
N-step deadbeat (\(N > 1\)) Handles systems where \(m < n\); uses the reachability matrix pseudo-inverse
Receding-horizon deadbeat Recomputes the full \(N\)-step sequence at every sample (equivalent to unconstrained MPC with finite horizon)
Robust deadbeat Adds structured uncertainty to \(A\), \(B\) and minimizes worst-case settling time
Output deadbeat Drives the output \(y = Cx\) to target rather than the full state; requires an observer

3.2.7 Applications

3.2.8 Connections to Other Algorithms

graph LR
    DB["Deadbeat Control"]
    SVD["SVD (Pseudo-Inverse)"]
    LQR["LQR Controller"]
    MPC["MPC Controller"]
    LTI["LTI Plant Model"]
    SVD --> DB
    LTI --> DB
    DB -.->|"finite horizon, no constraints"| MPC
    DB -.->|"optimal alternative with noise"| LQR
Algorithm Relationship
SVD Solver Used to compute the pseudo-inverse of \(\Gamma_N\) during gain initialization
LQR Controller LQR is the optimal alternative when measurement noise is present; places poles away from origin
MPC Controller Deadbeat is MPC with a finite horizon equal to \(N\) and no inequality constraints
LTI Plant Model Provides \((A, B)\) matrices used to build \(\Gamma_N\) and compute \(A^N\)

3.3 PID Controller

3.3.1 Overview & Motivation

The Proportional-Integral-Derivative (PID) controller is the workhorse of industrial feedback control. It continuously computes an error \(e(t)\) — the difference between a desired setpoint and a measured process variable — and applies a correction based on three terms: one proportional to the current error, one to its accumulated history, and one to its rate of change.

Despite its simplicity, PID handles a remarkably wide range of plants — from temperature regulation to motor speed control — because each term addresses a different aspect of the transient response:

This library implements a discrete recursive form suitable for fixed-sample-rate embedded loops, where the output is computed incrementally rather than from scratch at every step.

3.3.2 Mathematical Theory

3.3.2.1 Continuous-Time PID

\[u(t) = K_p \, e(t) + K_i \int_0^t e(\tau)\,d\tau + K_d \frac{de(t)}{dt}\]

3.3.2.2 Discrete Recursive Form

Discretizing with sampling period \(T_s\) and applying the difference approximation:

\[u[n] = u[n-1] + a_0 \, e[n] + a_1 \, e[n-1] + a_2 \, e[n-2]\]

where:

\[a_0 = K_p + K_i + K_d\] \[a_1 = -(K_p + 2K_d)\] \[a_2 = K_d\]

This incremental (velocity) form has two key advantages:

  1. No integral accumulator — the integral action is implicit in the recursion, reducing overflow risk.
  2. Bumpless mode switching — toggling between auto/manual mode does not cause output jumps because the output is updated incrementally.

3.3.2.3 Output Saturation

The output is clamped to \([u_{\min}, u_{\max}]\) after each computation, which also provides implicit anti-windup: the integral term cannot drive the output beyond the saturation limits.

3.3.3 Complexity Analysis

Case Time Space Notes
Per sample \(O(1)\) \(O(1)\) 3 multiplications, 3 additions, 1 clamp

The algorithm uses a fixed-size recursive buffer storing \(e[n-1]\), \(e[n-2]\), and \(u[n-1]\). No loops, no dynamic allocation, fully deterministic execution time.

3.3.4 Step-by-Step Walkthrough

Parameters: \(K_p = 2\), \(K_i = 0.5\), \(K_d = 0.1\), limits \([-10, 10]\), setpoint \(= 5\).

Coefficients: - \(a_0 = 2 + 0.5 + 0.1 = 2.6\) - \(a_1 = -(2 + 0.2) = -2.2\) - \(a_2 = 0.1\)

Step Measured \(e[n]\) \(e[n-1]\) \(e[n-2]\) \(\Delta u\) \(u[n]\) (clamped)
0 0.0 5.0 0 0 \(2.6 \times 5 = 13\) 10.0 (clamped)
1 2.0 3.0 5.0 0 \(2.6(3) - 2.2(5) + 0.1(0) = -3.2\) 6.8
2 4.0 1.0 3.0 5.0 \(2.6(1) - 2.2(3) + 0.1(5) = -3.5\) 3.3
3 4.8 0.2 1.0 3.0 \(2.6(0.2) - 2.2(1) + 0.1(3) = -1.38\) 1.92

The output converges toward the steady-state value needed to maintain the setpoint.

3.3.5 Pitfalls & Edge Cases

3.3.6 Variants & Generalizations

Variant Key Difference
Standard (positional) PID Computes \(u[n]\) from scratch each step using an explicit integral accumulator
PI controller \(K_d = 0\); simpler, no derivative noise issues
PD controller \(K_i = 0\); no steady-state error correction, used when offset is acceptable
PID with derivative filter Low-passes the derivative term to reject high-frequency noise
Gain-scheduled PID Tuning parameters vary as a function of operating point
Cascade PID Inner and outer loops, each with its own PID — common in motor control

3.3.7 Applications

3.3.8 Connections to Other Algorithms

graph LR
    PID["PID Controller"]
    LQR["LQR Controller"]
    KF["Kalman Filter"]
    PID -.->|"alternative"| LQR
    KF -->|"state estimate"| LQR
    KF -->|"filtered measurement"| PID
Algorithm Relationship
LQR Controller Optimal alternative when a state-space model is available; PID is model-free.
Kalman Filter Can provide filtered state estimates as input to either PID or LQR

3.4 LQR Controller (Linear Quadratic Regulator)

3.4.1 Overview & Motivation

When a state-space model of the plant is available, the Linear Quadratic Regulator provides the mathematically optimal state-feedback gain. Instead of manually tuning three knobs (as with PID), LQR asks a more principled question: given how much I care about state deviation versus control effort, what is the cheapest way to drive the state to zero?

The answer is a constant gain matrix \(K\) such that the control law \(u[k] = -Kx[k]\) minimizes an infinite-horizon quadratic cost. The elegance of LQR is that this problem has a closed-form solution through the Discrete Algebraic Riccati Equation (DARE).

For embedded systems, the gain \(K\) can be computed offline and stored as a constant — the runtime cost is then a single matrix-vector multiplication per control step.

3.4.2 Mathematical Theory

3.4.2.1 Discrete-Time State-Space System

\[x[k+1] = Ax[k] + Bu[k]\]

where \(x \in \mathbb{R}^n\) is the state, \(u \in \mathbb{R}^m\) is the control input, \(A\) is the state transition matrix, and \(B\) is the input matrix.

3.4.2.2 Cost Function

The LQR minimizes the infinite-horizon quadratic cost:

\[J = \sum_{k=0}^{\infty} \left( x[k]^T Q \, x[k] + u[k]^T R \, u[k] \right)\]

where: - \(Q \succeq 0\) (positive semi-definite) penalizes state deviation. - \(R \succ 0\) (positive definite) penalizes control effort.

3.4.2.3 Optimal Gain via DARE

The solution requires finding the matrix \(P\) that satisfies the Discrete Algebraic Riccati Equation:

\[P = A^T P A - A^T P B \left(R + B^T P B\right)^{-1} B^T P A + Q\]

The optimal gain is then:

\[K = \left(R + B^T P B\right)^{-1} B^T P A\]

and the optimal control law is \(u[k] = -Kx[k]\).

3.4.2.4 Existence and Uniqueness

A unique stabilizing solution \(P\) exists when: 1. \((A, B)\) is stabilizable (all unstable modes are controllable). 2. \((A, C)\) is detectable (where \(Q = C^T C\)).

3.4.2.5 Bryson’s Rule (Initial Weight Selection)

\[Q_{ii} = \frac{1}{(\text{max acceptable } x_i)^2}, \qquad R_{jj} = \frac{1}{(\text{max acceptable } u_j)^2}\]

3.4.3 Complexity Analysis

Phase Time Space Notes
DARE solve (offline) \(O(I \cdot n^3)\) \(O(n^2)\) \(I\) iterations, each involving \(n \times n\) matrix operations
Control step (online) \(O(n \cdot m)\) \(O(n \cdot m)\) Single matrix-vector multiply \(u = -Kx\)

The DARE iteration is the expensive part, but it is done once (offline or at initialization). The per-sample cost is just a matrix-vector product.

3.4.4 Step-by-Step Walkthrough

System: Double integrator with \(\Delta t = 0.01\)

\[A = \begin{bmatrix} 1 & 0.01 \\ 0 & 1 \end{bmatrix}, \quad B = \begin{bmatrix} 0.00005 \\ 0.01 \end{bmatrix}\]

\[Q = \begin{bmatrix} 100 & 0 \\ 0 & 1 \end{bmatrix}, \quad R = [1]\]

Step 1 — Initialize \(P_0 = Q\)

Step 2 — DARE iteration (showing first iteration):

  1. \(S = R + B^T P_0 B = 1 + [0.00005\; 0.01] \begin{bmatrix}100 & 0\\0 & 1\end{bmatrix} \begin{bmatrix}0.00005\\0.01\end{bmatrix} = 1.0001\)
  2. Solve \(S \cdot K_{\text{part}} = B^T P_0 A\) via Gaussian elimination
  3. Update \(P_1 = A^T P_0 A - A^T P_0 B \cdot K_{\text{part}} + Q\)
  4. Check convergence: \(\|P_1 - P_0\|\)

Step 3 — Repeat until \(\|P_{k+1} - P_k\| < \varepsilon\) (typically 10–30 iterations).

Step 4 — Extract gain \(K = (R + B^T P B)^{-1} B^T P A\)

Runtime: \(u[k] = -K x[k]\) — a \(1 \times 2\) times \(2 \times 1\) multiply per sample.

3.4.5 Pitfalls & Edge Cases

3.4.6 Variants & Generalizations

Variant Key Difference
Continuous-time LQR Solves the Continuous ARE instead of DARE; used for continuous-time plant models
LQG (Linear Quadratic Gaussian) Combines LQR with Kalman filter for systems with noisy, partial observations
LQR with integral action Augments the state with integral of tracking error to eliminate steady-state offset
Finite-horizon LQR Time-varying gain \(K[k]\) for finite-duration tasks
Robust LQR (H∞) Accounts for model uncertainty by optimizing the worst-case cost
Pre-computed gain \(K\) is computed offline (e.g., in MATLAB with dlqr) and hard-coded for embedded targets

3.4.7 Applications

3.4.8 Connections to Other Algorithms

graph LR
    LQR["LQR Controller"]
    DARE["DARE Solver"]
    GE["Gaussian Elimination"]
    KF["Kalman Filter"]
    PID["PID Controller"]
    DARE --> LQR
    GE --> DARE
    KF -->|"state estimate"| LQR
    PID -.->|"model-free alternative"| LQR
Algorithm Relationship
DARE Solver Computes the cost-to-go matrix \(P\) from which \(K\) is derived
Gaussian Elimination Used inside DARE iteration to solve linear sub-systems
Kalman Filter Provides state estimates when direct measurement is unavailable (forming LQG)
PID Controller Model-free alternative; simpler to deploy but not optimal

3.5 Integral State Feedback (LQI / Servo)

3.5.1 Overview & Motivation

Plain LQR drives the system state toward the origin but leaves a persistent steady-state offset when a constant reference or disturbance is present. The integral state feedback controller (LQI, or servo LQR) removes this offset by augmenting the plant with integrators on the tracking error. A single LQR solve on the augmented system produces two gains: one that acts on the physical states and one that closes the integral loop, guaranteeing zero steady-state error to constant references with no manual trim.

3.5.2 Mathematical Theory

3.5.2.1 Plant Model

The discrete-time plant is

\[x_{k+1} = A x_k + B u_k, \quad y_k = C x_k\]

with state \(x \in \mathbb{R}^n\), input \(u \in \mathbb{R}^m\), and tracked output \(y \in \mathbb{R}^p\).

3.5.2.2 Augmented Plant

Define the integral-of-error state \(x_i \in \mathbb{R}^p\):

\[x_{i,k+1} = x_{i,k} + (r_k - y_k) T_s\]

Stacking \(\xi = [x^\top \; x_i^\top]^\top\) gives the augmented system

\[\xi_{k+1} = A_a \xi_k + B_a u_k + E_a r_k\]

\[A_a = \begin{bmatrix} A & 0 \\ -C T_s & I \end{bmatrix}, \quad B_a = \begin{bmatrix} B \\ 0 \end{bmatrix}, \quad E_a = \begin{bmatrix} 0 \\ T_s I \end{bmatrix}\]

3.5.2.3 LQR Design on the Augmented Plant

Minimise the infinite-horizon quadratic cost

\[J = \sum_{k=0}^{\infty} \bigl(\xi_k^\top Q \xi_k + u_k^\top R u_k\bigr)\]

by solving the Discrete Algebraic Riccati Equation (DARE) for \(P\):

\[P = A_a^\top P A_a - A_a^\top P B_a (R + B_a^\top P B_a)^{-1} B_a^\top P A_a + Q\]

The optimal gain partitions as \(K_a = [K_x \mid K_i]\) where \(K_x \in \mathbb{R}^{m \times n}\) acts on the physical states and \(K_i \in \mathbb{R}^{m \times p}\) acts on the integral states.

3.5.2.4 Control Law

\[u_k = -K_x x_k - K_i x_{i,k}\]

At equilibrium \(r - y = 0\), so \(x_{i}\) stops changing, and the control law holds \(y = r\) exactly.

3.5.3 Complexity Analysis

Case Time Space Notes
Design \(O((n+p)^3)\) \(O((n+p)^2)\) DARE iteration on augmented system
Update \(O(m(n+p))\) \(O(p)\) Two matrix-vector products

Design is a one-time offline computation. The per-sample update cost is dominated by the two gain-state products and the integral accumulation.

3.5.4 Step-by-Step Walkthrough

Consider a scalar plant (\(n=1\), \(m=1\), \(p=1\), \(T_s = 0.01\)):

  1. Form \(A_a\) (2×2), \(B_a\) (2×1) from plant matrices and \(T_s\).
  2. Choose \(Q\) (2×2 diagonal) and \(R\) (scalar) to weight states and input.
  3. Solve DARE → \(P\) (2×2) → \(K_a = [k_x \; k_i]\) (1×2).
  4. Per step: accumulate \(x_i \mathrel{+}= (r - y) T_s\), output \(u = -k_x x - k_i x_i\).
  5. After ~200 steps the output converges to \(r\) within numerical tolerance.

3.5.5 Pitfalls & Edge Cases

3.5.6 Variants & Generalizations

3.5.7 Applications

3.5.8 Connections to Other Algorithms

3.6 Linear Quadratic Gaussian (LQG) Controller

3.6.1 Overview & Motivation

The Linear Quadratic Gaussian (LQG) controller combines two classical optimal control results: the Linear Quadratic Regulator (LQR) and the Kalman Filter (KF). It is the optimal output-feedback controller for linear time-invariant (LTI) systems subject to Gaussian process and measurement noise.

While LQR assumes full state availability — a strict requirement rarely satisfied in practice — LQG relaxes this by estimating the state from noisy output measurements. The Kalman Filter provides the minimum-variance estimate, and LQR computes the optimal control law given that estimate. By the Separation Principle, these two subproblems can be solved independently and their solutions combined without loss of optimality.

LQG is widely used in aerospace guidance, robotics, and process control when sensor noise is substantial and the plant model is reasonably accurate.

3.6.2 Mathematical Theory

3.6.2.1 System Model

Consider the discrete-time LTI system:

\[x_{k+1} = A x_k + B u_k + w_k\]

\[y_k = C x_k + v_k\]

where: - \(x_k \in \mathbb{R}^n\) is the state vector - \(u_k \in \mathbb{R}^m\) is the control input - \(y_k \in \mathbb{R}^p\) is the measurement vector - \(w_k \sim \mathcal{N}(0, Q_w)\) is process noise - \(v_k \sim \mathcal{N}(0, R_v)\) is measurement noise

3.6.2.2 LQR Subproblem

The regulator minimises the infinite-horizon quadratic cost:

\[J = \sum_{k=0}^{\infty} \left( x_k^T Q x_k + u_k^T R u_k \right)\]

The optimal state-feedback gain is:

\[K = (R + B^T P B)^{-1} B^T P A\]

where \(P\) is the positive semi-definite solution to the Discrete Algebraic Riccati Equation (DARE):

\[P = Q + A^T P A - A^T P B (R + B^T P B)^{-1} B^T P A\]

The optimal control law is \(u_k = -K \hat{x}_{k|k}\).

3.6.2.3 Kalman Filter Subproblem

The optimal state estimator propagates:

Predict: \[\hat{x}_{k+1|k} = A \hat{x}_{k|k} + B u_k\]

\[P_{k+1|k} = A P_{k|k} A^T + Q_w\]

Update: \[K_f = P_{k|k-1} C^T (C P_{k|k-1} C^T + R_v)^{-1}\]

\[\hat{x}_{k|k} = \hat{x}_{k|k-1} + K_f (y_k - C \hat{x}_{k|k-1})\]

\[P_{k|k} = (I - K_f C) P_{k|k-1} (I - K_f C)^T + K_f R_v K_f^T\]

The steady-state Kalman gain converges to a fixed matrix as \(k \to \infty\).

3.6.2.4 Separation Principle

The fundamental result that makes LQG tractable is:

Theorem (Separation Principle): The optimal output-feedback control law for the LQG problem is the composition of (1) the optimal state estimator (Kalman Filter) and (2) the optimal state-feedback controller (LQR), designed independently.

This means the LQR gain \(K\) and the Kalman gain \(K_f\) do not interact — changing the noise covariances \(Q_w\), \(R_v\) affects only the estimator, and changing the cost weights \(Q\), \(R\) affects only the regulator.

3.6.2.5 Combined Control Loop

At each time step \(k\):

  1. Measurement update: \(\hat{x}_{k|k} \leftarrow \text{KF.Update}(y_k)\)
  2. Compute control: \(u_k = -K \hat{x}_{k|k}\)
  3. Time update: \(\hat{x}_{k+1|k} \leftarrow \text{KF.Predict}(u_k)\)

3.6.3 Complexity Analysis

Step Time Space Notes
Construction \(O(n^3)\) \(O(n^2)\) DARE solve for LQR gain
ComputeControl \(O(n^2 + nm)\) \(O(1)\) KF update + LQR multiply

where \(n\) = state size, \(m\) = input size, \(p\) = measurement size.

3.6.4 Numerical Considerations

3.6.5 Pitfalls & Edge Cases

3.6.6 Step-by-Step Walkthrough

Consider a double integrator plant — a mass subject to force input, observed through a noisy position sensor. The discrete-time matrices with sample period \(T_s = 0.1\,\text{s}\) are:

\[A = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \end{bmatrix}, \quad B = \begin{bmatrix} 0 \\ 0.1 \end{bmatrix}, \quad C = \begin{bmatrix} 1 & 0 \end{bmatrix}\]

Construction (offline):

  1. Choose LQR weights \(Q = 10 I_2\), \(R = 0.1\). Solve DARE for \(P\), compute gain \(K = (R + B^T P B)^{-1} B^T P A\).
  2. Choose noise covariances \(Q_w = 0.1 I_2\), \(R_v = 1.0\). Solve dual DARE for steady-state Kalman gain \(K_f\).
  3. Verify closed-loop stability: eigenvalues of \((A - BK)\) and \((A - K_f C)\) must lie strictly inside the unit disc.

Online loop (per sample):

Step Operation Notes
1 Receive sensor measurement \(y_k\) Noisy position reading
2 \(\hat{x}_{k\|k} = \hat{x}_{k\|k-1} + K_f(y_k - C\hat{x}_{k\|k-1})\) Kalman update
3 \(u_k = -K \hat{x}_{k\|k}\) LQR control law
4 \(\hat{x}_{k+1\|k} = A \hat{x}_{k\|k} + B u_k\) Kalman predict

Starting from initial position \(x_0 = [1, 0]^T\), the LQG regulator drives the position to zero while the Kalman Filter’s estimated trajectory converges to the true state despite measurement noise.

3.6.7 Variants & Generalizations

3.6.8 Applications

3.6.9 Connections to Other Algorithms

3.7 Model Predictive Controller (MPC)

3.7.1 Overview & Motivation

Model Predictive Control (MPC) is a receding-horizon optimal control strategy that computes control inputs by solving a finite-horizon optimization problem at each time step. Unlike LQR, which applies a fixed gain computed offline, MPC re-solves the optimization online, enabling it to handle constraints on control inputs directly.

At each sample instant, MPC predicts the future system trajectory over a prediction horizon \(N_p\), optimizes a sequence of control moves over a control horizon \(N_c \leq N_p\), applies only the first control move, and repeats at the next step. This receding-horizon approach provides feedback and robustness to disturbances.

Key advantages over LQR: - Explicit handling of input constraints (actuator limits) - Preview and anticipation of future reference changes - Tunable trade-off between horizon length and computational cost

When to use MPC: - Systems with actuator saturation or operational limits - Multi-input multi-output (MIMO) systems requiring coordinated control - Applications where the computational budget allows online optimization (typically \(N_c \cdot m \leq 20\))

3.7.2 Mathematical Theory

3.7.2.1 Prerequisites

3.7.2.2 Core Definitions

Cost function over prediction horizon \(N_p\) with control horizon \(N_c\):

\[J = \sum_{k=0}^{N_p-1} x_k^T Q\, x_k + \sum_{k=0}^{N_c-1} u_k^T R\, u_k + x_{N_p}^T P\, x_{N_p}\]

For \(k \geq N_c\), the control input is held at \(u_{N_c-1}\) or zero (our implementation sets it to zero beyond \(N_c\)).

3.7.2.3 Prediction Matrices

The future state trajectory can be expressed as:

\[\mathbf{X} = \Psi\, x_0 + \Theta\, \mathbf{U}\]

where \(\mathbf{X} = [x_1^T, x_2^T, \ldots, x_{N_p}^T]^T\) and \(\mathbf{U} = [u_0^T, u_1^T, \ldots, u_{N_c-1}^T]^T\).

State propagation matrix \(\Psi\) (\(N_p n \times n\)):

\[\Psi = \begin{bmatrix} A \\ A^2 \\ \vdots \\ A^{N_p} \end{bmatrix}\]

Control-to-state matrix \(\Theta\) (\(N_p n \times N_c m\)):

\[\Theta = \begin{bmatrix} B & 0 & \cdots & 0 \\ AB & B & \cdots & 0 \\ A^2B & AB & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ A^{N_p-1}B & A^{N_p-2}B & \cdots & A^{N_p-N_c}B \end{bmatrix}\]

3.7.2.4 QP Formulation

Substituting predictions into the cost yields a quadratic program in \(\mathbf{U}\):

\[J = \mathbf{U}^T H\, \mathbf{U} + 2\, x_0^T F^T \mathbf{U} + \text{const}\]

where:

\[H = \Theta^T \bar{Q}\, \Theta + \bar{R}\] \[F = \Theta^T \bar{Q}\, \Psi\]

\(\bar{Q}\) is block-diagonal with \(Q\) on the first \(N_p - 1\) blocks and \(P\) on the last block. \(\bar{R}\) is block-diagonal with \(R\) repeated \(N_c\) times.

3.7.2.5 Unconstrained Solution

Setting \(\nabla_{\mathbf{U}} J = 0\):

\[\mathbf{U}^* = -H^{-1} F\, x_0\]

Solved via Gaussian elimination (\(H\, \mathbf{U}^* = -F\, x_0\)) rather than explicit matrix inversion.

3.7.2.6 Box Constraints

For control input constraints \(u_{\min} \leq u_k \leq u_{\max}\), a simple projected approach clamps each element of the unconstrained solution. This is a first-order approximation; for tight constraints or state constraints, a full QP solver would be required.

3.7.3 Complexity Analysis

Phase Time Space Notes
Offline \(O(N_p^2 \cdot n^3)\) \(O((N_c m)^2 + N_p n \cdot N_c m)\) Prediction matrices, Hessian, gradient precomputed
Online \(O((N_c m)^3)\) \(O((N_c m)^2)\) Gaussian elimination to solve \(H\mathbf{U}=-Fx\)
Per step \(O(N_c m \cdot n)\) \(O(N_c m)\) Matrix-vector product \(Fx\) + constraint clamping

For typical embedded use (\(n=2, m=1, N_c=10\)): the online solve is a \(10 \times 10\) linear system, well within real-time budgets.

3.7.4 Step-by-Step Walkthrough

System: Double integrator with \(dt = 0.1\) s

\[A = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \end{bmatrix}, \quad B = \begin{bmatrix} 0.005 \\ 0.1 \end{bmatrix}\]

\[Q = \begin{bmatrix} 10 & 0 \\ 0 & 1 \end{bmatrix}, \quad R = [0.1], \quad N_p = N_c = 3\]

Step 1 — Build \(\Psi\):

\[\Psi = \begin{bmatrix} A \\ A^2 \\ A^3 \end{bmatrix} = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \\ 1 & 0.2 \\ 0 & 1 \\ 1 & 0.3 \\ 0 & 1 \end{bmatrix}\]

Step 2 — Build \(\Theta\):

\[\Theta = \begin{bmatrix} 0.005 & 0 & 0 \\ 0.1 & 0 & 0 \\ 0.015 & 0.005 & 0 \\ 0.2 & 0.1 & 0 \\ 0.03 & 0.015 & 0.005 \\ 0.3 & 0.2 & 0.1 \end{bmatrix}\]

Step 3 — Compute \(H\) and \(F\):

\(H = \Theta^T \bar{Q} \Theta + \bar{R}\) produces a \(3 \times 3\) positive definite matrix.

\(F = \Theta^T \bar{Q} \Psi\) produces a \(3 \times 2\) matrix.

Step 4 — Online solve for \(x_0 = [1, 0]^T\):

Solve \(H\, \mathbf{U}^* = -F \cdot [1, 0]^T\) via Gaussian elimination.

Apply first element \(u_0^*\) to the plant.

3.7.5 Pitfalls & Edge Cases

3.7.6 Variants & Generalizations

Variant Key Difference Use Case
Nonlinear MPC (NMPC) Nonlinear prediction model, requires iterative optimization Nonlinear plants, high-accuracy
Explicit MPC Precomputes control law as piecewise affine function Very fast online evaluation
Move-Blocking MPC Constrains control moves to change only at specific steps Reduces QP size
Adaptive MPC Updates plant model online Time-varying or uncertain systems
Distributed MPC Decomposes problem across subsystems Large-scale multi-agent systems
Robust MPC Accounts for bounded disturbances in constraints Safety-critical applications

3.7.7 Applications

3.7.8 Connections to Other Algorithms

graph LR
    DARE["Discrete Algebraic<br/>Riccati Equation"] -->|terminal cost P| MPC["Model Predictive<br/>Controller"]
    GE["Gaussian<br/>Elimination"] -->|solves H·U = -F·x| MPC
    LQR["LQR Controller"] -.->|special case<br/>N→∞, no constraints| MPC
    MPC -->|first control move| Plant["Plant Model"]
    Plant -->|state measurement| MPC

3.8 Linear Time-Invariant System Model

3.8.1 Overview & Motivation

A Linear Time-Invariant (LTI) system model is the fundamental mathematical object shared across state-space control, estimation, and signal processing. It captures the dynamics of a physical plant — a motor, a pendulum, a vehicle — in a compact matrix representation that enables the systematic application of optimal control and filtering theory.

Representing an LTI model as a first-class value type makes it possible to pass a single plant description into algorithms such as the Kalman Filter, LQR, LQG, and MPC, eliminating the error-prone practice of supplying the same system matrices (A, B, C, D) separately to each component.

3.8.2 Mathematical Theory

3.8.2.1 Discrete-Time State-Space Representation

A discrete-time LTI system with state \(x_k \in \mathbb{R}^n\), input \(u_k \in \mathbb{R}^m\), and output \(y_k \in \mathbb{R}^p\) is described by two equations:

State equation: \[x_{k+1} = A x_k + B u_k\]

Output equation: \[y_k = C x_k + D u_k\]

where: - \(A \in \mathbb{R}^{n \times n}\) is the state transition matrix - \(B \in \mathbb{R}^{n \times m}\) is the input matrix (sometimes called the control matrix) - \(C \in \mathbb{R}^{p \times n}\) is the output matrix (sometimes called the measurement matrix) - \(D \in \mathbb{R}^{p \times m}\) is the feedthrough matrix (often zero for strictly proper systems)

3.8.2.2 Special Cases

Full-state output (\(C = I\), \(D = 0\)): When all states are directly observable (e.g., a simulation or a state-feedback controller with perfect sensing), the output equation collapses to \(y_k = x_k\). This is the typical case for LQR.

Autonomous system (\(B = 0\), \(u_k = 0\)): A system driven only by initial conditions and process noise. Used in Kalman smoothing and spectral analysis.

3.8.2.3 Discretisation

Continuous-time plants described by \(\dot{x} = A_c x + B_c u\) must be discretised before use in digital controllers:

\[A = e^{A_c T_s}, \quad B = A_c^{-1}(A - I) B_c\]

where \(T_s\) is the sampling period. For small \(T_s\) and stable \(A_c\), the forward-Euler approximation \(A \approx I + A_c T_s\), \(B \approx B_c T_s\) may be used.

3.8.2.4 Stability and Controllability

An LTI model is: - Stable if all eigenvalues of \(A\) lie strictly inside the unit disc (\(|\lambda_i| < 1\) for discrete-time systems). - Controllable if the controllability matrix \(\mathcal{C} = [B \; AB \; A^2B \; \cdots \; A^{n-1}B]\) has full row rank. - Observable if the observability matrix \(\mathcal{O} = [C^T \; (CA)^T \; \cdots \; (CA^{n-1})^T]^T\) has full column rank.

Controllability and observability are prerequisites for LQR and Kalman Filter stability, respectively.

3.8.3 Complexity Analysis

Operation Time Space Notes
Step (state) \(O(n^2 + nm)\) \(O(n)\) One matrix-vector multiply per term
Output \(O(pn + pm)\) \(O(p)\) One matrix-vector multiply per term
Construction \(O(1)\) \(O(n^2 + nm + pn + pm)\) Value-type struct, no allocation

3.8.4 Numerical Considerations

3.8.5 Pitfalls & Edge Cases

3.8.6 Step-by-Step Walkthrough

Consider a double integrator (position and velocity state, force input, position output) with sample period \(T_s = 0.1\,\text{s}\):

Continuous-time plant: \(\dot{x} = \begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix} x + \begin{bmatrix} 0 \\ 1 \end{bmatrix} u\), \(y = \begin{bmatrix} 1 & 0 \end{bmatrix} x\)

Step 1 — Discretise using forward-Euler (\(A \approx I + A_c T_s\), \(B \approx B_c T_s\)):

\[A = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \end{bmatrix}, \quad B = \begin{bmatrix} 0 \\ 0.1 \end{bmatrix}, \quad C = \begin{bmatrix} 1 & 0 \end{bmatrix}, \quad D = \begin{bmatrix} 0 \end{bmatrix}\]

Step 2 — Simulate one step from \(x_0 = [1, 0]^T\) with \(u_0 = -2\):

\[x_1 = A x_0 + B u_0 = \begin{bmatrix} 1 & 0.1 \\ 0 & 1 \end{bmatrix} \begin{bmatrix} 1 \\ 0 \end{bmatrix} + \begin{bmatrix} 0 \\ 0.1 \end{bmatrix}(-2) = \begin{bmatrix} 1 \\ -0.2 \end{bmatrix}\]

Step 3 — Compute output at \(k=0\):

\[y_0 = C x_0 = \begin{bmatrix} 1 & 0 \end{bmatrix} \begin{bmatrix} 1 \\ 0 \end{bmatrix} = 1\]

The position measurement is \(1.0\) (exact, since \(D = 0\) and noise is excluded here).

3.8.7 Variants & Generalizations

3.8.8 Applications

3.8.9 Connections to Other Algorithms

3.9 Feedforward / 2-DOF Controller

3.9.1 Overview & Motivation

A single feedback loop must balance two competing demands: fast reference tracking and robust rejection of disturbances. Tightening the feedback gains for faster tracking reduces stability margins; relaxing them for robustness slows the response. The two-degree-of-freedom (2-DOF) structure resolves this conflict by separating the two tasks into independent paths. A feedforward path shapes how the output follows the reference, while the feedback path corrects errors and rejects disturbances. Because the feedforward term is open-loop, it can be tuned for tracking without affecting the closed-loop stability margins at all.

3.9.2 Mathematical Theory

3.9.2.1 Signal Flow

Let \(r\) be the reference, \(y\) the plant output, and \(u\) the actuator command. The 2-DOF law is:

\[u = \text{sat}\!\left(u_{ff}(r) + u_{fb}(r - y),\; u_{min},\; u_{max}\right)\]

where: - \(u_{ff}(r)\) is the feedforward map evaluated on the reference alone (open-loop), - \(u_{fb}(e) = u_{fb}(r - y)\) is the feedback law driven by the tracking error, - \(\text{sat}(\cdot)\) clips the combined command to the actuator range \([u_{min}, u_{max}]\).

3.9.2.2 Feedforward Design

The ideal feedforward is an inverse of the plant model. For a linear plant \(P(z)\), the ideal map is \(u_{ff}(r) = P^{-1}(z)\, r\), yielding perfect steady-state tracking. A simpler approximation is a scalar gain \(K_{ff}\) tuned to match the DC plant gain: \(u_{ff}(r) = K_{ff} \cdot r\). Non-minimum-phase plants require a causal approximation since the exact inverse is non-causal.

3.9.2.3 Feedback Design (Independence)

The closed-loop characteristic equation depends only on the feedback law and the plant; \(u_{ff}\) does not appear in it. Stability margins, bandwidth, and disturbance rejection are therefore set entirely by \(u_{fb}\). This decoupling is the defining property of the 2-DOF structure.

3.9.2.4 Saturation

The output clamp \(\text{sat}(u, u_{min}, u_{max})\) acts on the combined command to protect the actuator. When the sum saturates, both the feedforward and feedback contributions are implicitly reduced.

3.9.3 Complexity Analysis

Case Time Space Notes
Best \(O(1)\) \(O(1)\) Wrapper adds one addition and one clamp
Average \(O(1)\) \(O(1)\) Cost dominated by the injected components
Worst \(O(1)\) \(O(1)\) State lives entirely inside injected objects

3.9.4 Step-by-Step Walkthrough

Given \(r = 0.5\), \(y = 0.1\), feedforward gain \(K_{ff} = 0.6\), and a proportional feedback with gain \(K_p = 0.5\), clamped to \([-1, 1]\):

  1. \(u_{ff} = K_{ff} \cdot r = 0.6 \times 0.5 = 0.3\)
  2. \(e = r - y = 0.5 - 0.1 = 0.4\)
  3. \(u_{fb} = K_p \cdot e = 0.5 \times 0.4 = 0.2\)
  4. \(u_{raw} = u_{ff} + u_{fb} = 0.3 + 0.2 = 0.5\)
  5. \(u = \text{sat}(0.5, -1, 1) = 0.5\)

If the system were already tracking perfectly (\(y = r = 0.5\)), then \(e = 0\), \(u_{fb} = 0\), and the feedforward alone supplies the command: \(u = K_{ff} \cdot 0.5 = 0.3\).

3.9.5 Pitfalls & Edge Cases

3.9.6 Variants & Generalizations

3.9.7 Applications

3.9.8 Connections to Other Algorithms

3.10 Luenberger Observer

3.10.1 Overview & Motivation

State-feedback controllers require the complete state vector, but physical systems typically expose only a few measured outputs. The Luenberger observer reconstructs unmeasured states from input and output measurements using a copy of the plant model continuously corrected by the difference between predicted and actual outputs.

Unlike the Kalman filter, the Luenberger observer is deterministic — it requires no noise statistics and no covariance propagation. For embedded systems with well-characterized models and deterministic environments, it offers the same state reconstruction at a fraction of the computational cost.

3.10.2 Mathematical Theory

3.10.2.1 Discrete-Time State-Space System

\[x[k+1] = A x[k] + B u[k]\] \[y[k] = C x[k] + D u[k]\]

3.10.2.2 Observer Update Law

The observer maintains a state estimate \(\hat{x}[k]\) updated by:

\[\hat{y}[k] = C \hat{x}[k] + D u[k]\] \[\hat{x}[k+1] = A \hat{x}[k] + B u[k] + L (y[k] - \hat{y}[k])\]

The term \(y[k] - \hat{y}[k]\) is the innovation (output prediction error). The gain matrix \(L \in \mathbb{R}^{n \times p}\) scales how aggressively the estimate is corrected.

3.10.2.3 Error Dynamics

Defining the estimation error \(e[k] = x[k] - \hat{x}[k]\):

\[e[k+1] = (A - LC)\, e[k]\]

The error decays to zero if and only if all eigenvalues of \((A - LC)\) lie strictly inside the unit circle. Choosing \(L\) to place those eigenvalues at desired locations is the pole placement problem for observers.

3.10.2.4 Ackermann’s Formula (SISO Output)

For single-output systems (\(p = 1\)), the observer gain that places eigenvalues of \((A - LC)\) at \(\{\mu_1, \dots, \mu_n\}\) is:

\[L = \varphi_d(A)\, \mathcal{O}^{-1}\, e_n\]

where: - \(\varphi_d(z) = \prod_{i=1}^n (z - \mu_i)\) is the desired characteristic polynomial, evaluated at \(A\) - \(\mathcal{O} = \begin{bmatrix} C \\ CA \\ \vdots \\ CA^{n-1} \end{bmatrix}\) is the observability matrix - \(e_n = [0, \dots, 0, 1]^T\) is the last standard basis vector

This is the dual of Ackermann’s controller placement formula.

3.10.2.5 Observability Condition

Ackermann’s formula requires \(\mathcal{O}\) to be invertible, which holds if and only if the pair \((A, C)\) is observable: every state affects the output through some combination of shifts. An unobservable pair makes the formula degenerate — the gain cannot force arbitrary error convergence.

3.10.3 Complexity Analysis

Phase Time Space Notes
Design (Ackermann) \(O(n^3)\) \(O(n^2)\) Observability matrix build + linear solve
Update (per step) \(O(n^2 + np)\) \(O(n^2)\) Matrix-vector products; dominant cost is \(A\hat{x}\)

The design phase is offline. The real-time cost per sample is dominated by the \(n \times n\) state-transition multiply.

3.10.4 Step-by-Step Walkthrough

System: Double integrator, \(n=2\), \(p=1\), desired observer poles \(\{\mu_1, \mu_2\} = \{0.2,\, 0.3\}\)

\[A = \begin{bmatrix}1 & 1\\0 & 1\end{bmatrix}, \quad B = \begin{bmatrix}0\\1\end{bmatrix}, \quad C = \begin{bmatrix}1 & 0\end{bmatrix}\]

Step 1 — Build observability matrix:

\[\mathcal{O} = \begin{bmatrix}C\\CA\end{bmatrix} = \begin{bmatrix}1 & 0\\1 & 1\end{bmatrix}\]

Step 2 — Evaluate desired polynomial at \(A\):

\[\varphi_d(z) = (z - 0.2)(z - 0.3) = z^2 - 0.5z + 0.06\]

\[\varphi_d(A) = A^2 - 0.5A + 0.06I = \begin{bmatrix}0.56 & 1.5\\0 & 0.56\end{bmatrix}\]

Step 3 — Solve for gain:

\[\mathcal{O}^{-1} = \begin{bmatrix}1 & 0\\-1 & 1\end{bmatrix}, \quad \mathcal{O}^{-1} e_2 = \begin{bmatrix}0\\1\end{bmatrix}\]

\[L = \varphi_d(A) \cdot \begin{bmatrix}0\\1\end{bmatrix} = \begin{bmatrix}1.5\\0.56\end{bmatrix}\]

Verification: eigenvalues of \(A - LC = \begin{bmatrix}-0.5 & 1\\-0.56 & 1\end{bmatrix}\) are \(\{0.2, 0.3\}\). ✓

3.10.5 Pitfalls & Edge Cases

3.10.6 Variants & Generalizations

Variant Key Difference
Kalman Filter Stochastic design — minimizes covariance rather than placing poles; handles noise statistics
Extended Luenberger Observer Linearizes a nonlinear plant around the estimate for quasi-linear operation
Unknown-Input Observer Estimates states in the presence of unmeasured disturbances
Reduced-Order Observer Estimates only the unmeasured states, using measured outputs directly
Continuous-time observer Uses \(\dot{\hat{x}} = A\hat{x} + Bu + L(y - C\hat{x})\); same structure, continuous pole placement

3.10.7 Applications

3.10.8 Connections to Other Algorithms

graph LR
    LO["Luenberger Observer"]
    LTI["LinearTimeInvariant"]
    GE["Gaussian Elimination"]
    LQR["LQR Controller"]
    KF["Kalman Filter"]
    LTI -->|"plant model"| LO
    GE -->|"solve O^{-1}"| LO
    LO -->|"state estimate"| LQR
    KF -.->|"stochastic counterpart"| LO
Algorithm Relationship
Linear Time-Invariant Model Supplies the \((A, B, C, D)\) matrices used in both design and update steps
Gaussian Elimination Solves \(\mathcal{O} x = e_n\) inside Ackermann’s formula
LQR Controller Primary consumer of the observer’s state estimate
Kalman Filter Stochastic counterpart; adds noise covariance propagation

3.11 Friction Compensation (Coulomb + Viscous + Stribeck)

3.11.1 Overview & Motivation

Friction is the dominant unmodelled effect in geared drives, lead screws and robot joints. It causes stick-slip, tracking lag and steady-state error that a feedback loop can only remove by raising its gain, which erodes stability margins. Friction compensation instead predicts the drag from the axis velocity and adds the opposite torque (or force) to the command, so the feedback loop only has to correct the residual.

The model superposes three effects: Coulomb drag (constant magnitude, opposing motion), viscous drag (proportional to speed) and Stribeck stiction (an extra breakaway level that fades as the axis starts to slide). The ideal curve is discontinuous at zero velocity, so a smooth boundary layer replaces the hard sign function.

3.11.2 Mathematical Theory

3.11.2.1 Per-Axis Model

For one axis with velocity \(v\), the compensation torque is:

\[\tau = \operatorname{clamp}\!\Big(\big[F_c + (F_s - F_c)\,e^{-(|v|/v_s)^{\delta}}\big]\tanh\!\big(v/\varepsilon\big) + F_v\, v,\; -\tau_{max},\; \tau_{max}\Big)\]

Axes are independent; a multi-axis compensator applies the model to each velocity component with its own parameters.

3.11.2.2 Structure

The bracketed term is the friction level \(L(|v|)\). It starts at \(F_s\) for \(v = 0\) and decays monotonically to \(F_c\), which is why \(F_s \ge F_c\) is required: otherwise the “Stribeck dip” would turn into a rise. The factor \(\tanh(v/\varepsilon)\) supplies the direction, approximating \(\operatorname{sign}(v)\) with slope \(L/\varepsilon\) at the origin. The viscous term is added outside the boundary layer.

3.11.2.3 Feedforward Use

The result is a feedforward term, not a feedback law. It is added to the controller output, \(\tau_{cmd} = \tau_{control} + \tau\), and composes with any torque or current loop. Because it depends only on velocity, it does not enter the feedback characteristic equation.

3.11.2.4 Desired vs. Measured Velocity

In both cases \(\varepsilon\) sets the sharpness-versus-chatter trade-off.

3.11.2.5 Identification

For fixed \(v_s\), \(\delta\) and \(\varepsilon\), \(\tau\) is linear in the force coefficients: \(F_c\), \(F_s\) and \(F_v\) multiply the known regressors \((1-e^{-(|v|/v_s)^{\delta}})\tanh(v/\varepsilon)\), \(e^{-(|v|/v_s)^{\delta}}\tanh(v/\varepsilon)\) and \(v\) respectively. They can therefore be estimated by linear least squares from logged velocity and torque data, jointly with other linear parameters. \(v_s\) and \(\delta\) require an outer search.

3.11.3 Complexity Analysis

Case Time Space Notes
All \(O(N)\) \(O(N)\) \(N\) axes; per axis one exponential, one power, one hyperbolic tangent, two divisions and a clamp; \(N\) parameter sets stored, no allocation

Cost is dominated by the three transcendental calls; there is no iteration and no dependence on history, so the latency is constant per sample.

3.11.4 Step-by-Step Walkthrough

Parameters: \(F_c = 0.5\), \(F_s = 0.8\), \(F_v = 0.1\), \(v_s = 0.05\), \(\delta = 2\), \(\varepsilon = 10^{-3}\), with a non-binding cap \(\tau_{max} = 1\).

\(v\) \((\lvert v\rvert/v_s)^2\) Level \(L\) \(\tanh(v/\varepsilon)\) \(F_v v\) \(\tau\)
1 400 \(0.5 + 0.3\,e^{-400} \approx 0.5\) 1 0.1 0.6
0.01 0.04 \(0.5 + 0.3\,e^{-0.04} \approx 0.788\) \(\tanh 10 \approx 1\) 0.001 0.789
0.05 (\(=v_s\)) 1 \(0.5 + 0.3/e \approx 0.610\) \(\tanh 50 \approx 1\) 0.005 0.615

At high speed the Stribeck term has vanished and only Coulomb plus viscous drag remain. At \(v = 0.01\) the axis is still deep in the Stribeck region, so the level is close to \(F_s\). At \(v = v_s\) the stiction excess has decayed to a fraction \(1/e\) of its initial value. At \(v = 0\) the output is exactly zero, and \(v = -0.01\) gives \(-0.789\) by symmetry.

3.11.5 Pitfalls & Edge Cases

3.11.6 Variants & Generalizations

Variant Key Difference
Coulomb + viscous only \(F_s = F_c\) removes the Stribeck term; the level is constant
Asymmetric friction Separate parameters per direction of motion; handles direction-dependent drag
Karnopp model Replaces the smooth layer with a velocity dead-zone and a stick state
Dahl / LuGre Dynamic models with internal bristle state; capture presliding and hysteresis at the cost of state and a stiffer ODE
Inverse-dynamics term Same static model folded into a robot inverse-dynamics computation

3.11.7 Applications

3.11.8 Connections to Other Algorithms

Algorithm Relationship
Feedforward / 2-DOF Same additive-feedforward structure; friction is a velocity-driven feedforward term
PID Controller The feedback loop this term is added to; compensation reduces integrator work and stick-slip hunting
Bang-Bang / Hysteresis The \(\varepsilon \to 0\) limit is a relay on velocity, with the same chattering mechanism
Saturation / Rate Limiter The output clamp is the same saturation, applied to the compensation term alone
QR Decomposition Backbone of the least-squares fit of the linear friction coefficients

3.12 Gain-Scheduled Controller

3.12.1 Overview & Motivation

Many physical plants behave as linear systems only near a specific operating point. A motor’s electrical dynamics change with rotational speed, an aircraft’s aerodynamic response shifts with altitude and Mach number, and a chemical reactor’s behaviour depends on temperature. Designing a single linear controller that performs well across the entire operating envelope is often impossible or overly conservative.

The gain-scheduled controller addresses this by pre-computing a family of linear controllers — each optimal near one operating point — and blending between them at runtime based on a measured scheduling variable. This extends the reach of proven linear design methods to mildly nonlinear plants at negligible runtime cost, making it the dominant approach to flight control, powertrain management, and process control on resource-constrained hardware.

3.12.2 Mathematical Theory

3.12.2.1 Setup

Let \(\sigma \in \mathbb{R}\) be the scheduling variable (e.g., speed, load, altitude) and let \(\mathbf{g}(\sigma) \in \mathbb{R}^p\) denote the vector of \(p\) controller gains as a function of \(\sigma\).

A finite set of \(N\) breakpoints \(\sigma_1 < \sigma_2 < \cdots < \sigma_N\) partitions the scheduling axis. At each breakpoint \(\sigma_i\) a gain vector \(\mathbf{g}_i \in \mathbb{R}^p\) is stored, pre-designed offline (e.g., by LQR at each linearised operating point).

3.12.2.2 Piecewise-Linear Interpolation

For a scheduling value \(\sigma\) in the interval \([\sigma_i, \sigma_{i+1}]\), the active gain vector is the linear interpolant:

\[ \mathbf{g}(\sigma) = \mathbf{g}_i + w \, (\mathbf{g}_{i+1} - \mathbf{g}_i), \qquad w = \frac{\sigma - \sigma_i}{\sigma_{i+1} - \sigma_i} \in [0,1]. \]

Each gain component is interpolated independently. The rearranged form \(a + w(b - a)\) is numerically preferable to \((1-w)a + wb\) because it avoids catastrophic cancellation near \(w \approx 0\) and performs one fewer multiply.

3.12.2.3 Endpoint Saturation

Outside the table range the gains are held constant at the boundary values:

\[ \mathbf{g}(\sigma) = \begin{cases} \mathbf{g}_1 & \sigma \leq \sigma_1 \\ \mathbf{g}_N & \sigma \geq \sigma_N \end{cases} \]

Extrapolation is avoided because gain values outside the design envelope are undefined and potentially destabilising.

3.12.2.4 Stability Considerations

Gain scheduling does not guarantee closed-loop stability in general. The standard sufficient conditions are: (1) the scheduling variable changes slowly relative to the closed-loop bandwidth (quasi-static assumption), and (2) each frozen-\(\sigma\) system is stable. When the scheduling variable changes rapidly, a full parameter-varying analysis (LPV, \(\mathcal{H}_\infty\)) is required.

3.12.3 Complexity Analysis

Case Time Space Notes
Schedule (linear search) \(O(N + p)\) \(O(1)\) \(N\) interval scan, \(p\) blends
Schedule (binary search) \(O(\log N + p)\) \(O(1)\) Preferred for large tables
Construction \(O(N)\) \(O(N \cdot p)\) Monotonicity assertion

All storage is stack-allocated. The table occupies \(N \cdot p\) words; the active gain vector occupies \(p\) words. No dynamic allocation occurs at any point.

3.12.4 Step-by-Step Walkthrough

Table: \(\{(0, 1), (0.5, 2), (1, 4)\}\) — three breakpoints, one gain each.

Query \(\sigma = 0.75\):

  1. \(\sigma = 0.75 > \sigma_1 = 0\) and \(\sigma = 0.75 < \sigma_3 = 1.0\), so no saturation.
  2. Locate interval: \(\sigma_2 = 0.5 \leq 0.75 < \sigma_3 = 1.0\), so \(i = 2\).
  3. Blend weight: \(w = (0.75 - 0.5) / (1.0 - 0.5) = 0.5\).
  4. Active gain: \(g = 2.0 + 0.5 \times (4.0 - 2.0) = 3.0\).

Query \(\sigma = -1.0\) (below table): saturate to \(g = g_1 = 1.0\).

3.12.5 Pitfalls & Edge Cases

3.12.6 Variants & Generalizations

Variant Key Difference
Bilinear / 2-D scheduling Gains indexed by two variables (e.g., speed and load); uses bilinear interpolation on a grid
LPV (Linear Parameter-Varying) Gains are polynomial functions of \(\sigma\); stability guaranteed by parameter-dependent Lyapunov functions
Velocity-form scheduling Gains on incremental (velocity-form) controllers avoid output steps at scheduling transitions
Bumpless transfer State initialisation at switchover prevents transients when \(\sigma\) jumps discontinuously

3.12.7 Applications

3.12.8 Connections to Other Algorithms

Algorithm Relationship
LQR Common source of the per-breakpoint gain sets; each \(\mathbf{g}_i\) is an LQR solution at the \(i\)-th linearised operating point
Feedforward/2-DOF Complementary structure: the feedforward path can also be gain-scheduled to account for reference-dependent plant changes
MPC Alternative that solves an optimisation online; eliminates the need for offline scheduling but requires more computation

3.13 Lead-Lag Compensator

3.13.1 Overview & Motivation

Classical feedback loops require a mechanism to reshape the open-loop frequency response without the full overhead of a state-space design. A one-pole/one-zero compensator achieves this with three tuning parameters and two state words, making it practical for any microcontroller control loop. When phase margin is insufficient, a lead configuration injects extra phase near the gain crossover frequency, raising stability margin and permitting a higher bandwidth. When steady-state error is the concern, a lag configuration boosts low-frequency gain to drive the error toward zero while leaving the crossover region essentially unchanged.

3.13.2 Mathematical Theory

3.13.2.1 Continuous-Time Transfer Function

The compensator is defined in the Laplace domain as:

\[C(s) = K \cdot \frac{s + z}{s + p}\]

where \(K\) is the overall gain, \(z\) is the zero frequency (rad/s), and \(p\) is the pole frequency (rad/s).

The DC gain is \(C(0) = K \cdot z / p\).

3.13.2.2 Bilinear (Tustin) Discretization

The bilinear transform substitutes \(s \leftarrow \frac{2}{T_s} \cdot \frac{1 - z^{-1}}{1 + z^{-1}}\), mapping the entire left half of the \(s\)-plane to the interior of the unit circle in the \(z\)-plane and preserving stability.

Define \(c = 2/T_s\). The numerator and denominator polynomials in \(z\) are:

\[n_0 = K(c + z), \quad n_1 = K(z - c)\] \[d_0 = c + p, \quad d_1 = p - c\]

Normalizing by \(d_0\):

\[b_0 = \frac{n_0}{d_0}, \quad b_1 = \frac{n_1}{d_0}, \quad a_1 = \frac{d_1}{d_0}\]

3.13.2.3 Discrete-Time Recurrence (Direct Form I)

\[y[n] = b_0 \, x[n] + b_1 \, x[n-1] - a_1 \, y[n-1]\]

The sign convention places the feedback term with a minus sign on \(a_1\), so positive \(a_1\) in the formula corresponds to a pole at \(+a_1\) inside the unit disk.

3.13.3 Complexity Analysis

Case Time Space Notes
Best \(O(1)\) \(O(1)\) Three multiply-adds, two state updates
Average \(O(1)\) \(O(1)\) Fixed instruction count per sample
Worst \(O(1)\) \(O(1)\) No branching; deterministic real-time behavior

Design (Tustin coefficient computation) is \(O(1)\) and occurs once in the constructor.

3.13.4 Step-by-Step Walkthrough

Parameters: \(K=1\), \(z=1\,\text{rad/s}\), \(p=10\,\text{rad/s}\), \(T_s = 0.01\,\text{s}\) (lead network).

  1. \(c = 2/0.01 = 200\)
  2. \(n_0 = 1 \cdot (200 + 1) = 201\), \(\quad n_1 = 1 \cdot (1 - 200) = -199\)
  3. \(d_0 = 200 + 10 = 210\), \(\quad d_1 = 10 - 200 = -190\)
  4. \(b_0 = 201/210 \approx 0.9571\), \(\quad b_1 = -199/210 \approx -0.9476\), \(\quad a_1 = -190/210 \approx -0.9048\)

Unit-step response (first two samples):

\(n\) \(x[n]\) \(b_0 x[n]\) \(b_1 x[n-1]\) \(-a_1 y[n-1]\) \(y[n]\)
0 1 0.9571 0 0 0.9571
1 1 0.9571 −0.9476 0.8664 0.8759

The first output (≈ 0.957) already exceeds the DC steady-state gain of 0.1, illustrating the phase-lead kick.

Verification of DC gain: \(b_0 + b_1 = 2/210\); \(1 + a_1 = 20/210\); ratio \(= 2/20 = 0.1 = K \cdot z/p\).

3.13.5 Pitfalls & Edge Cases

3.13.6 Variants & Generalizations

3.13.7 Applications

3.13.8 Connections to Other Algorithms

3.14 Saturation / Rate-Limiter / Slew Blocks

3.14.1 Overview & Motivation

Real actuators — valves, motors, heaters, servos — impose two distinct constraints on any command signal: a travel limit (the actuator cannot physically move past a hard stop) and a speed limit (the mechanism cannot slew arbitrarily fast without damaging itself or the system). Ignoring either constraint causes physically impossible commands, mechanical stress, and, in closed-loop systems, integrator wind-up that degrades recovery after saturation.

These three primitives model exactly those constraints. Saturation enforces the travel limit by clipping the output to a fixed interval. The rate limiter enforces the speed limit by allowing the output to move at most a fixed amount per sample period. Composing them — applying the rate limiter first, then the saturator — guarantees every emitted sample simultaneously respects both bounds, making them the mandatory prerequisite for anti-windup in any integrating controller.

3.14.2 Mathematical Theory

3.14.2.1 Saturation

Given bounds \([\ell, h]\) with \(\ell < h\), saturation is defined as:

\[y = \text{sat}(u) = \min\!\bigl(\max(u,\, \ell),\, h\bigr)\]

This is a memoryless, instantaneous projection onto the interval \([\ell, h]\).

3.14.2.2 Rate Limiter (Slew Limiter)

Let \(\Delta_{\max} = r \cdot T_s\) be the maximum permitted change per sample, where \(r\) is the slew rate (units per second) and \(T_s\) is the sample period (seconds). Define the state \(y[n-1]\) as the last emitted output. The output at tick \(n\) is:

\[\delta[n] = \text{sat}_{\Delta_{\max}}(u[n] - y[n-1]) = \min\!\bigl(\max(u[n] - y[n-1],\,-\Delta_{\max}),\,+\Delta_{\max}\bigr)\]

\[y[n] = y[n-1] + \delta[n]\]

On the first sample (priming), \(y[0] = u[0]\) and no limiting is applied, so the output tracks the first command exactly.

3.14.2.3 Composed Block (Slew-Then-Clamp)

\[y_{\text{composed}}[n] = \text{sat}\!\bigl(y_{\text{slew}}[n]\bigr)\]

Applying the rate limiter before the saturator is essential. If the order were reversed (clamp then slew), a step command to a saturated bound would allow the rate limiter to move the output outside the saturation interval for one tick before the clamp could correct it.

3.14.3 Complexity Analysis

Case Time Space Notes
Best \(O(1)\) \(O(1)\) Two compare-and-select operations
Average \(O(1)\) \(O(1)\) One multiply + two compares for rate limit
Worst \(O(1)\) \(O(1)\) Fixed instruction count; no branching on size

Each block maintains at most two scalar parameters and one scalar state word. The hot path is branch-light: min/max compile to conditional-move instructions on modern architectures.

3.14.4 Step-by-Step Walkthrough

Parameters: \(\ell = -0.5\), \(h = 0.5\), \(r = 0.1\,\text{s}^{-1}\), \(T_s = 1\,\text{s}\) (\(\Delta_{\max} = 0.1\)). Step request: \(u = 1.0\) for all ticks, starting from \(y[-1] = 0\).

Tick \(n\) \(u[n]\) \(\delta[n]\) \(y_{\text{slew}}[n]\) \(y_{\text{composed}}[n]\)
0 1.0 0.1 0.1 0.1
1 1.0 0.1 0.2 0.2
2 1.0 0.1 0.3 0.3
3 1.0 0.1 0.4 0.4
4 1.0 0.1 0.5 0.5
5 1.0 0.1 0.6 0.5 (clamped)

The ramp is linear until the saturator clips it at \(h = 0.5\). The rate limiter continues to advance its internal state, but the composed output is held at the bound.

3.14.5 Pitfalls & Edge Cases

3.14.6 Variants & Generalizations

3.14.7 Applications

3.14.8 Connections to Other Algorithms

4 Estimators

4.1 Linear Regression

4.1.1 Overview & Motivation

Given a set of observations \(\{(x_i, y_i)\}\), we often want to find the best-fit linear relationship between features \(x\) and target \(y\). Linear regression does this by finding the coefficients \(\beta\) that minimize the sum of squared residuals — the gap between predicted and observed values.

The approach is fundamental because: - It has a closed-form solution (the normal equation), so no iterative optimization is needed. - It provides a baseline model against which more complex methods are measured. - The solution is the maximum likelihood estimator under Gaussian noise assumptions.

This library solves the normal equation directly using Gaussian elimination, making it suitable for small-to-moderate feature counts typical in embedded estimation tasks.

4.1.2 Mathematical Theory

4.1.2.1 The Model

\[y = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_p x_p + \varepsilon\]

In matrix form, augmenting \(X\) with a column of ones for the intercept:

\[\mathbf{y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\varepsilon}\]

where \(\mathbf{X} \in \mathbb{R}^{n \times (p+1)}\), \(\boldsymbol{\beta} \in \mathbb{R}^{p+1}\), \(\mathbf{y} \in \mathbb{R}^n\).

4.1.2.2 Normal Equation

Minimizing \(\|\mathbf{y} - \mathbf{X}\boldsymbol{\beta}\|^2\) with respect to \(\boldsymbol{\beta}\) yields:

\[\boldsymbol{\beta} = (\mathbf{X}^T \mathbf{X})^{-1} \mathbf{X}^T \mathbf{y}\]

This is solved in practice by forming \(\mathbf{X}^T\mathbf{X}\) and \(\mathbf{X}^T\mathbf{y}\), then using Gaussian elimination to solve the \((p+1) \times (p+1)\) system.

4.1.2.3 Geometric Interpretation

The prediction \(\hat{\mathbf{y}} = \mathbf{X}\boldsymbol{\beta}\) is the orthogonal projection of \(\mathbf{y}\) onto the column space of \(\mathbf{X}\). The residual \(\mathbf{y} - \hat{\mathbf{y}}\) is perpendicular to every column of \(\mathbf{X}\).

4.1.3 Complexity Analysis

Phase Time Space Notes
\(\mathbf{X}^T\mathbf{X}\) \(O(n p^2)\) \(O(p^2)\) Dominated by matrix multiply
\(\mathbf{X}^T\mathbf{y}\) \(O(np)\) \(O(p)\) Matrix-vector multiply
Solve \(O(p^3)\) \(O(p^2)\) Gaussian elimination
Predict \(O(p)\) \(O(1)\) Dot product

For embedded use with small \(p\) (say \(\leq 10\)), the entire fit completes in microseconds.

4.1.4 Step-by-Step Walkthrough

Data: 3 samples, 1 feature.

\(x\) \(y\)
1 2
2 4
3 5

Step 1 — Design matrix (with bias column):

\[\mathbf{X} = \begin{bmatrix} 1 & 1 \\ 1 & 2 \\ 1 & 3 \end{bmatrix}\]

Step 2 — Compute \(\mathbf{X}^T\mathbf{X}\) and \(\mathbf{X}^T\mathbf{y}\):

\[\mathbf{X}^T\mathbf{X} = \begin{bmatrix} 3 & 6 \\ 6 & 14 \end{bmatrix}, \quad \mathbf{X}^T\mathbf{y} = \begin{bmatrix} 11 \\ 23 \end{bmatrix}\]

Step 3 — Solve \(\begin{bmatrix} 3 & 6 \\ 6 & 14 \end{bmatrix} \boldsymbol{\beta} = \begin{bmatrix} 11 \\ 23 \end{bmatrix}\):

Forward elimination → back-substitution: \(\beta_1 = 1.5\), \(\beta_0 = 2/3 \approx 0.667\)

Result: \(\hat{y} = 0.667 + 1.5x\)

Prediction at \(x = 4\): \(\hat{y} = 0.667 + 6.0 = 6.667\)

4.1.5 Pitfalls & Edge Cases

4.1.6 Variants & Generalizations

Variant Key Difference
Ridge regression (L2) Adds \(\lambda\|\boldsymbol{\beta}\|^2\) to the cost; solves \((\mathbf{X}^T\mathbf{X} + \lambda I)\boldsymbol{\beta} = \mathbf{X}^T\mathbf{y}\)
Lasso regression (L1) Adds \(\lambda\|\boldsymbol{\beta}\|_1\); promotes sparsity but requires iterative optimization
Polynomial regression Adds powers of \(x\) as new features; still linear in parameters
Weighted least squares Weights each sample differently; solves \((\mathbf{X}^T W \mathbf{X})\boldsymbol{\beta} = \mathbf{X}^T W \mathbf{y}\)
Recursive least squares Updates \(\boldsymbol{\beta}\) incrementally as new data arrives; suited for online estimation

4.1.7 Applications

4.1.8 Connections to Other Algorithms

graph LR
    LR["Linear Regression"]
    GE["Gaussian Elimination"]
    YW["Yule-Walker"]
    NN["Neural Network"]
    LR --> GE
    YW -.->|"similar normal-equation structure"| LR
    NN -.->|"single linear layer = regression"| LR
Algorithm Relationship
Gaussian Elimination Used to solve the normal equation system
Yule-Walker Structurally similar — also solves a linear system derived from correlations
Neural Network (neural-network-toobox-cpp) A single-layer neural network with no activation and MSE loss reduces to linear regression

4.2 Polynomial Least-Squares Fitting

4.2.1 Overview & Motivation

Sensor calibration curves, ADC linearization, thermistor transfer functions, and drift trends all require fitting a smooth curve to a discrete set of measured points. A degree-\(d\) polynomial captures these behaviors with only \(d+1\) coefficients, making evaluation at runtime a handful of multiply-adds rather than a table lookup or expensive transcendental.

The least-squares formulation finds the polynomial that minimizes the sum of squared residuals across all measurement samples. Unlike exact interpolation, it is robust to measurement noise: extra samples average out errors rather than being forced to pass through noisy points.

4.2.2 Mathematical Theory

4.2.2.1 The Model

Given \(n\) scalar observations \(\{(x_i, y_i)\}_{i=0}^{n-1}\), the degree-\(d\) polynomial model is

\[p(x) = c_0 + c_1 x + c_2 x^2 + \cdots + c_d x^d\]

The goal is to find the coefficient vector \(\mathbf{c} \in \mathbb{R}^{d+1}\) that minimizes

\[\min_{\mathbf{c}} \sum_{i=0}^{n-1} \bigl(y_i - p(x_i)\bigr)^2\]

4.2.2.2 Vandermonde Design Matrix

Stacking the model evaluations at all sample abscissae gives the Vandermonde matrix

\[\mathbf{V} \in \mathbb{R}^{n \times (d+1)}, \quad V_{i,j} = x_i^j\]

The least-squares problem then becomes \(\min_{\mathbf{c}} \|\mathbf{V}\mathbf{c} - \mathbf{y}\|^2\).

4.2.2.3 Normal Equations

Setting the gradient of the squared residual with respect to \(\mathbf{c}\) to zero yields

\[(\mathbf{V}^\top \mathbf{V})\,\mathbf{c} = \mathbf{V}^\top \mathbf{y}\]

The \((d+1)\times(d+1)\) matrix \(\mathbf{V}^\top\mathbf{V}\) is symmetric and, when the abscissae are distinct and \(n \geq d+1\), positive-definite. Its small size allows direct solution by Gaussian elimination or Cholesky factorization in bounded time on embedded hardware.

4.2.2.4 Horner Evaluation

Once \(\mathbf{c}\) is known, evaluating \(p(x)\) at a new point uses Horner’s method

\[p(x) = c_0 + x\bigl(c_1 + x\bigl(c_2 + \cdots + x\,c_d\bigr)\cdots\bigr)\]

This requires exactly \(d\) multiplications and \(d\) additions — optimal for a degree-\(d\) polynomial.

4.2.3 Complexity Analysis

Phase Time Space Notes
Build \(\mathbf{V}\) \(O(n\,d)\) \(O(n\,d)\) Incremental powers, no pow()
Form \(\mathbf{V}^\top\mathbf{V}\) \(O(n\,d^2)\) \(O(d^2)\) Symmetric, only upper half needed
Form \(\mathbf{V}^\top\mathbf{y}\) \(O(n\,d)\) \(O(d)\) Matrix-vector product
Solve \((d+1)\times(d+1)\) system \(O(d^3)\) \(O(d^2)\) Gaussian elimination
Predict (Horner) \(O(d)\) \(O(1)\) One MAC per coefficient

All dimensions are compile-time constants; no heap allocation is required.

4.2.4 Step-by-Step Walkthrough

Data: \(n = 4\) samples, \(d = 2\) (quadratic fit).

\(x_i\) \(y_i\)
0 1
1 0.75
2 1
3 1.75

Step 1 — Build \(\mathbf{V}\):

\[\mathbf{V} = \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 1 \\ 1 & 2 & 4 \\ 1 & 3 & 9 \end{bmatrix}\]

Step 2 — Normal equations:

\[\mathbf{V}^\top\mathbf{V} = \begin{bmatrix} 4 & 6 & 14 \\ 6 & 14 & 36 \\ 14 & 36 & 98 \end{bmatrix}, \qquad \mathbf{V}^\top\mathbf{y} = \begin{bmatrix} 4.5 \\ 7.25 \\ 19.75 \end{bmatrix}\]

Step 3 — Solve: Gaussian elimination → \(\mathbf{c} \approx [1,\,-0.5,\,0.25]^\top\).

Result: \(p(x) = 1 - 0.5\,x + 0.25\,x^2\).

Prediction at \(x = 1.5\):

\[p(1.5) = 0.25\cdot1.5^2 - 0.5\cdot1.5 + 1 = 0.5625 - 0.75 + 1 = 0.8125\]

4.2.5 Pitfalls & Edge Cases

4.2.6 Variants & Generalizations

Variant Key Difference
Orthogonal polynomial basis Uses Legendre/Chebyshev basis instead of monomials; much better conditioning
Weighted least squares Each sample weighted differently (e.g., by measurement precision)
Regularized (Ridge) fitting Adds \(\lambda\|\mathbf{c}\|^2\) to damp large coefficients
Constrained fitting Enforces derivative constraints at endpoints
Savitzky-Golay smoothing Sliding-window polynomial fit for real-time derivative estimation

4.2.7 Applications

4.2.8 Connections to Other Algorithms

graph LR
    PF["Polynomial Fitting"]
    GE["Gaussian Elimination"]
    LR["Linear Regression"]
    SG["Savitzky-Golay (planned)"]
    RLS["Recursive Least Squares"]

    PF --> GE
    PF -.->|"polynomial features = special case"| LR
    SG -.->|"local polynomial fit per window"| PF
    RLS -.->|"online counterpart"| PF
Algorithm Relationship
Gaussian Elimination Solves the normal equations
Linear Regression Polynomial fitting is linear regression with polynomial features
Recursive Least Squares Online / streaming counterpart for time-varying models

4.3 Total Least Squares

4.3.1 Overview & Motivation

Ordinary least squares (OLS) assumes the regressors are known exactly and only the response is noisy — it minimizes the vertical residuals. Real embedded systems violate that assumption: calibrating one measured quantity against another (two drifting sensors, current vs. torque, strain vs. force) is an errors-in-variables problem where every channel is noisy. Fitting such data with OLS produces a systematically biased (attenuated) slope, a phenomenon known as regression dilution.

Total Least Squares (TLS) removes that bias. Geometrically it minimizes the orthogonal (perpendicular) distance from each point to the fitted hyperplane rather than the vertical distance, which is the maximum-likelihood estimate when the noise is equal on all channels.

4.3.2 Mathematical Theory

4.3.2.1 The Model

Given a design matrix \(A \in \mathbb{R}^{m \times n}\) and a response \(b \in \mathbb{R}^{m}\), TLS seeks the smallest perturbation \([\,\Delta A \mid \Delta b\,]\) (in Frobenius norm) that makes the system consistent:

\[\min_{\Delta A,\ \Delta b} \bigl\|[\,\Delta A \mid \Delta b\,]\bigr\|_F \quad\text{s.t.}\quad (A + \Delta A)\,x = b + \Delta b\]

4.3.2.2 SVD Solution

Stack the data into the augmented matrix \(M = [\,A \mid b\,] \in \mathbb{R}^{m \times (n+1)}\) and take its singular value decomposition \(M = U \Sigma V^{\top}\). The right-singular vector \(v_{n+1}\) associated with the smallest singular value \(\sigma_{n+1}\) spans the direction of least variance — the normal of the best-fit hyperplane. Partitioning

\[v_{n+1} = \begin{bmatrix} v_{1:n} \\ v_{n+1,\,n+1} \end{bmatrix}, \qquad x = -\,\frac{v_{1:n}}{v_{n+1,\,n+1}}\]

recovers the coefficients. The construction is exactly the Golub–Van Loan (1980) result: the minimal perturbation is \(\sigma_{n+1}\, u_{n+1} v_{n+1}^{\top}\), and \(\sigma_{n+1}\) is the orthogonal residual norm.

4.3.2.3 Existence

The solution exists (is generic) only when the last entry \(v_{n+1,\,n+1} \neq 0\). If it vanishes, the smallest singular direction lies entirely in the column space of \(A\) (e.g. a rank-deficient or all-zero regressor column) and no finite coefficient vector satisfies the fit — Fit returns false.

4.3.3 Complexity Analysis

Case Time Space Notes
Best \(O(m\,n^2)\) \(O(m\,n)\) Golub–Kahan bidiagonalization dominates
Average \(O(m\,n^2)\) \(O(m\,n)\) Implicit-QR sweeps converge in \(O(n)\) per value
Worst \(O(m\,n^2)\) \(O(m\,n)\) Bounded iteration cap in the SVD engine

All storage is stack-allocated (std::array-backed math::Matrix); no heap, no recursion.

4.3.4 Step-by-Step Walkthrough

Fit \(b = 2a\) from four clean points \(a = (1,2,3,4)\), \(b = (2,4,6,8)\).

  1. Augment: \(M = \begin{bmatrix} 1 & 2 \\ 2 & 4 \\ 3 & 6 \\ 4 & 8 \end{bmatrix}\) — exactly rank 1.
  2. SVD gives \(\sigma_1 \approx 12.25\), \(\sigma_2 = 0\) (the data hugs a line).
  3. The smallest right-singular vector is \(v_2 = \tfrac{1}{\sqrt5}(2, -1)\).
  4. Partition: \(x = -\,v_{2,1} / v_{2,2} = -\,\tfrac{2/\sqrt5}{-1/\sqrt5} = 2\). ✓

4.3.5 Pitfalls & Edge Cases

4.3.6 Variants & Generalizations

4.3.7 Applications

4.3.8 Connections to Other Algorithms

4.4 Yule-Walker Estimation

4.4.1 Overview & Motivation

Many signals — vibration data, speech waveforms, financial time series — exhibit short-term correlations. An autoregressive (AR) model captures these correlations by expressing the current sample as a linear combination of the previous \(p\) samples plus white noise. The Yule-Walker method estimates the AR coefficients directly from the signal’s autocovariance structure.

The appeal is threefold: 1. The resulting system of equations has Toeplitz structure, enabling efficient \(O(p^2)\) solvers. 2. The method is guaranteed to produce a stable model (all poles inside the unit circle) when the autocovariance matrix is positive definite. 3. It provides a parametric spectral estimate — the PSD is a smooth rational function rather than a noisy periodogram.

4.4.2 Mathematical Theory

4.4.2.1 Autoregressive Model

An AR(\(p\)) process:

\[x[t] = \varphi_1 x[t-1] + \varphi_2 x[t-2] + \cdots + \varphi_p x[t-p] + \varepsilon[t]\]

where \(\varepsilon[t]\) is white noise with variance \(\sigma^2\).

4.4.2.2 Yule-Walker Equations

Multiplying both sides by \(x[t-k]\) and taking expectations:

\[R[k] = \varphi_1 R[k-1] + \varphi_2 R[k-2] + \cdots + \varphi_p R[k-p], \quad k = 1, \ldots, p\]

where \(R[k] = E[x[t] \cdot x[t-k]]\) is the autocovariance at lag \(k\). In matrix form:

\[\underbrace{\begin{bmatrix} R[0] & R[1] & \cdots & R[p-1] \\ R[1] & R[0] & \cdots & R[p-2] \\ \vdots & & \ddots & \vdots \\ R[p-1] & R[p-2] & \cdots & R[0] \end{bmatrix}}_{\mathbf{R} \text{ (Toeplitz)}} \begin{bmatrix} \varphi_1 \\ \varphi_2 \\ \vdots \\ \varphi_p \end{bmatrix} = \begin{bmatrix} R[1] \\ R[2] \\ \vdots \\ R[p] \end{bmatrix}\]

4.4.2.3 Autocovariance Estimation

The biased estimator is used:

\[\hat{R}[k] = \frac{1}{N} \sum_{t=k}^{N-1} x[t] \cdot x[t-k]\]

after centering the series by subtracting its mean. The biased estimator (dividing by \(N\) instead of \(N-k\)) guarantees the Toeplitz matrix is positive semi-definite.

4.4.2.4 Spectral Interpretation

The PSD of the estimated AR model is:

\[S(f) = \frac{\sigma^2}{\left|1 - \sum_{i=1}^{p} \varphi_i \, e^{-j2\pi fi}\right|^2}\]

This gives a smooth, parametric spectral estimate.

4.4.3 Complexity Analysis

Phase Time Space Notes
Autocovariance \(O(Np)\) \(O(p)\) One pass per lag, \(p+1\) lags
Build Toeplitz matrix \(O(p^2)\) \(O(p^2)\) Fill from autocovariance vector
Solve (Levinson-Durbin) \(O(p^2)\) \(O(p)\) Exploits Toeplitz structure
Solve (Gaussian elim.) \(O(p^3)\) \(O(p^2)\) General-purpose fallback
Predict \(O(p)\) \(O(1)\) Dot product with past samples

4.4.4 Step-by-Step Walkthrough

Input: \(x = [2, 4, 6, 5, 3, 1]\), AR order \(p = 2\).

Step 1 — Compute mean and center:

\(\bar{x} = 3.5\), centered: \(x' = [-1.5, 0.5, 2.5, 1.5, -0.5, -2.5]\)

Step 2 — Autocovariances (biased, divide by \(N = 6\)):

Step 3 — Build Toeplitz system:

\[\begin{bmatrix} 2.917 & 0.792 \\ 0.792 & 2.917 \end{bmatrix} \begin{bmatrix} \varphi_1 \\ \varphi_2 \end{bmatrix} = \begin{bmatrix} 0.792 \\ -1.333 \end{bmatrix}\]

Step 4 — Solve (e.g., via Levinson-Durbin or Gaussian elimination):

\(\varphi_1 \approx 0.478\), \(\varphi_2 \approx -0.587\)

Prediction: \(\hat{x}[6] = \bar{x} + \varphi_1 (x[5] - \bar{x}) + \varphi_2 (x[4] - \bar{x})\)

4.4.5 Pitfalls & Edge Cases

4.4.6 Variants & Generalizations

Variant Key Difference
Burg’s method Estimates AR coefficients from forward + backward prediction errors; often more accurate for short records
Covariance method Uses the unbiased autocovariance; does not guarantee stability
ARMA models Extends AR to include moving-average terms; more flexible but requires iterative estimation
Vector autoregression (VAR) Multivariate extension for jointly modeling multiple time series
Recursive estimation Updates AR coefficients online as new data arrives

4.4.7 Applications

4.4.8 Connections to Other Algorithms

graph LR
    YW["Yule-Walker"]
    LD["Levinson-Durbin"]
    GE["Gaussian Elimination"]
    PSD["Power Spectral Density"]
    LR["Linear Regression"]
    YW --> LD
    YW --> GE
    YW -.->|"parametric alternative"| PSD
    LR -.->|"similar structure"| YW
Algorithm Relationship
Levinson-Durbin Efficient \(O(p^2)\) solver exploiting the Toeplitz structure of the Yule-Walker matrix
Gaussian Elimination General-purpose \(O(p^3)\) solver used as a fallback
Power Spectral Density Non-parametric alternative; the AR model provides a parametric PSD estimate
Linear Regression Structurally similar — both solve a linear system derived from data correlations

4.5 Expectation-Maximization for Kalman Filter Parameters

4.5.1 Overview & Motivation

A Kalman filter requires four parameter matrices — the state-transition matrix \(F\), measurement matrix \(H\), process-noise covariance \(Q\), and measurement-noise covariance \(R\) — to be known a priori. In many real systems these parameters are unknown or only approximately known.

The Expectation-Maximization (EM) algorithm for linear Gaussian state-space models (Shumway & Stoffer, 1982; 2000) treats the latent state sequence \(x_{1:T}\) as missing data and iteratively finds the maximum-likelihood parameter estimate from a batch of observations \(z_{1:T}\). Each iteration of EM is guaranteed to be non-decreasing in log-likelihood, providing a principled convergence criterion.

This implementation follows the Shumway-Stoffer formulation and operates exclusively in float because typical dynamics (forces, velocities, torques) exceed the \([-1, 1]\) range of Q15/Q31 fixed-point formats.

4.5.2 Mathematical Theory

4.5.2.1 State-Space Model

\[ x_{t+1} = F x_t + w_t, \quad w_t \sim \mathcal{N}(0, Q) \]

\[ z_t = H x_t + v_t, \quad v_t \sim \mathcal{N}(0, R) \]

with \(x_0 \sim \mathcal{N}(\mu_0, P_0)\) and \(T\) observations \(z_0, \ldots, z_{T-1}\).

4.5.2.2 EM Overview

EM alternates between:

4.5.2.3 E-Step (RTS Smoother)

Run KalmanSmoother::Smooth() to obtain, for each \(t\):

Quantity Notation Description
\(\hat{x}_{t\|T}\) smoothedMeans[t] Smoothed state mean
\(P_{t\|T}\) smoothedCovariances[t] Smoothed state covariance
\(P_{t,t-1\|T}\) lagCrossCovariances[t] Lag-1 cross-covariance (index 0 undefined)

4.5.2.4 Sufficient Statistics

From the smoother output, accumulate six matrices:

\[ A = \sum_{t=1}^{T-1} P_{t,t-1|T} \qquad B = \sum_{t=0}^{T-2} \hat{x}_{t|T}\hat{x}_{t|T}^\top + P_{t|T} \]

\[ C = \sum_{t=1}^{T-1} \hat{x}_{t|T}\hat{x}_{t|T}^\top + P_{t|T} \qquad D = \sum_{t=0}^{T-1} \hat{x}_{t|T}\hat{x}_{t|T}^\top + P_{t|T} \]

\[ W = \sum_{t=0}^{T-1} z_t \hat{x}_{t|T}^\top \qquad \Sigma_{zz} = \sum_{t=0}^{T-1} z_t z_t^\top \]

4.5.2.5 M-Step (Closed-Form Updates)

\[ F \leftarrow A B^{-1} \]

\[ Q \leftarrow \frac{C - F A^\top}{T - 1}, \quad Q \leftarrow \tfrac{1}{2}(Q + Q^\top) + \varepsilon I \]

\[ H \leftarrow W D^{-1} \]

\[ R \leftarrow \frac{\Sigma_{zz} - H W^\top}{T}, \quad R \leftarrow \tfrac{1}{2}(R + R^\top) + \varepsilon I \]

\[ \mu_0 \leftarrow \hat{x}_{0|T}, \quad P_0 \leftarrow P_{0|T} \]

The symmetrisation and ridge term \(\varepsilon I\) (\(\varepsilon = 10^{-6}\)) enforce that \(Q\) and \(R\) remain symmetric positive definite, preventing numerical collapse.

Matrix “division” — e.g. \(A B^{-1}\) — is implemented as SolveSystem(B, A^T)^T (Gaussian elimination) to avoid explicit matrix inversion.

4.5.2.6 Convergence

After each EM iteration, compute the log-likelihood \(\ell(\theta)\) from the RTS smoother. Convergence is declared when

\[ |\ell^{(k)} - \ell^{(k-1)}| < \delta \]

for a user-supplied tolerance \(\delta\).

4.5.3 Complexity Analysis

Case Time per iteration Space (stack) Notes
All \(O(T \cdot N^3)\) \(O(T \cdot N^2)\) Dominated by RTS smoother forward/backward

All storage is stack-allocated inside KalmanSmoother (owned by ExpectationMaximization) and in local M-step temporaries.

4.5.4 Step-by-Step Walkthrough

Consider \(N=1\), \(M=1\), \(T=3\), observations \(z=[0.5, 0.3, 0.8]\), initial guess \(F=1, H=1, Q=0.5, R=1, \mu_0=0, P_0=1\).

Iteration 1:

  1. E-step via RTS smoother: obtain \(\hat{x}_{0|3}, \hat{x}_{1|3}, \hat{x}_{2|3}\) and corresponding covariances and lag cross-covariances.
  2. Accumulate scalars:
  3. M-step: \(F \leftarrow A/B\), \(Q \leftarrow (C - F\cdot A)/(T-1)\), etc.
  4. Evaluate new log-likelihood \(\ell^{(1)}\).

Repeat until \(|\ell^{(k)} - \ell^{(k-1)}| < \delta\) or maximum iterations reached.

4.5.5 Pitfalls & Edge Cases

4.5.6 Variants & Generalizations

4.5.7 Applications

4.5.8 Connections to Other Algorithms

4.6 Incremental QR Least Squares

4.6.1 Overview & Motivation

Identification and calibration runs produce long logs — motor current against acceleration, joint torque against motion — that a microcontroller cannot store. Accumulating the normal equations fits in memory but squares the condition number, which single precision cannot afford. Real regressors are also often rank-deficient: a parameter never shows up in the data, or only in a fixed combination with another, and a plain solver then returns garbage.

Incremental QR least squares rotates each arriving row into a small triangular summary and forgets it: memory stays fixed however long the log runs and the data matrix is never squared. The final solve decomposes the summary into singular directions, inverts the strong ones, drops the weak ones, and reports which parameter combinations the data could actually determine.

4.6.2 Mathematical Theory

4.6.2.1 Streaming QR Factor

Rows \((\mathbf{y}_k^T, \tau_k)\) with \(\mathbf{y}_k \in \mathbb{R}^P\) arrive one at a time, \(m\) unbounded. The task is \(\min_{\boldsymbol{\pi}} \|Y\boldsymbol{\pi} - \boldsymbol{\tau}\|_2\) over the stacked rows, taking the minimum-norm minimizer. Write \(Y = Q \begin{bmatrix} R \\ 0 \end{bmatrix}\) with \(Q\) orthogonal and \(R\) upper triangular \(P \times P\), and split \(Q^T\boldsymbol{\tau} = (\mathbf{d}, \boldsymbol{\tau}_\perp)\). Orthogonal maps preserve norms, so

\[\|Y\boldsymbol{\pi} - \boldsymbol{\tau}\|^2 = \|R\boldsymbol{\pi} - \mathbf{d}\|^2 + \rho, \qquad \rho = \|\boldsymbol{\tau}_\perp\|^2\]

The problem lives in \(R\), \(\mathbf{d}\) and \(\rho\), the part of the right-hand side no \(\boldsymbol{\pi}\) can reach: \(P^2 + P + 2\) scalars with the row count. \(Q\) is never formed.

Row update. Place the new row beneath \([\,R \mid \mathbf{d}\,]\). For \(j = 1, \dots, P\), skip the step if \(y_j = 0\); otherwise rotate row \(j\) of \(R\) and the incoming row in their common plane:

\[\begin{bmatrix} c & s \\ -s & c \end{bmatrix} \begin{bmatrix} R_{j,j:P} & d_j \\ y_{j:P} & \tau \end{bmatrix}, \qquad c = \frac{R_{jj}}{\ell},\; s = \frac{y_j}{\ell},\; \ell = \sqrt{R_{jj}^2 + y_j^2}\]

This zeroes \(y_j\), leaves \(R_{jj} = \ell \ge 0\) and touches only columns \(j\) to \(P\). After the sweep the incoming row is zero in every column, so its leftover right-hand side \(\tilde{\tau}\) reads \(0 \cdot \boldsymbol{\pi} = \tilde{\tau}\) and adds \(\tilde{\tau}^2\) to \(\rho\) whatever \(\boldsymbol{\pi}\) is. A zero pivot degenerates to a swap and a zero pair to the identity.

Why not normal equations. \(Y^TY = R^TR\), yet the singular values of \(R\) are those of \(Y\): the factor works at \(\kappa(Y)\), the normal equations at \(\kappa(Y)^2\). With single-precision \(\epsilon \approx 1.2 \times 10^{-7}\) they fail near \(\kappa(Y) \approx \epsilon^{-1/2} \approx 3 \times 10^3\); the factor lasts to about \(\epsilon^{-1}\).

4.6.2.2 Rank-Revealing Solve

Take \(R = U\Sigma V^T\) with \(\sigma_0 \ge \dots \ge \sigma_{P-1} \ge 0\). In the coordinates \(\mathbf{z} = V^T\boldsymbol{\pi}\),

\[\|R\boldsymbol{\pi} - \mathbf{d}\|^2 = \sum_{i=0}^{P-1} \left(\sigma_i z_i - \mathbf{u}_i^T\mathbf{d}\right)^2\]

The rank is \(r = \#\{\, i : \sigma_i > \text{tol} \cdot \sigma_0 \,\}\). For \(i < r\) the term is zeroed by \(z_i = \mathbf{u}_i^T\mathbf{d} / \sigma_i\); for \(i \ge r\) it cannot be removed and \(z_i\) is free, so the minimum-norm choice is \(z_i = 0\). The discarded terms are charged to the residual:

\[\hat{\boldsymbol{\pi}} = \sum_{i < r} \mathbf{v}_i \, \frac{\mathbf{u}_i^T\mathbf{d}}{\sigma_i}, \qquad \text{RMS} = \sqrt{\frac{\rho + \sum_{i \ge r} (\mathbf{u}_i^T\mathbf{d})^2}{m}}\]

The first \(r\) rows of \(V^T\) are the combinations \(\mathbf{v}_i^T\boldsymbol{\pi}\) the data determine; the rest are the unidentifiable directions. Moving \(\boldsymbol{\pi}\) along them leaves the fit unchanged, and \(\hat{\boldsymbol{\pi}}\) is orthogonal to them.

Scaling a row and its right-hand side by \(\sqrt{w_k}\) gives weighted least squares. Every step is orthogonal, so row order changes the result only by rounding. With no rows there is no result; a reset clears the state.

4.6.3 Complexity Analysis

Operation Time Space Notes
Row, sparse (best) \(O(P)\) \(P^2 + P + 2\) Zero entries skipped; one short rotation per nonzero
Row, dense (worst) \(O(P^2)\) \(P^2 + P + 2\) \(P\) rotations on \(P - j + 1\) pairs, \(P\) square roots
Solve \(O(P^3)\) \(O(P^2)\) extra One \(P \times P\) SVD, independent of \(m\)
Whole stream, \(m\) rows \(O(mP^2 + P^3)\) \(P^2 + P + 2\) Memory does not grow with \(m\)

At \(P = 60\) the state is about 14 KB in single precision. Accumulation can run at the sample rate; the solve is an offline or commissioning step. No heap, no recursion.

4.6.4 Step-by-Step Walkthrough

Full rank. Rows \((1, x)\) at \(x = (0, 1, 2, 3)\), \(\boldsymbol{\tau} = (2.1, 4.9, 8.1, 10.9)\):

Rows \(R_{00}\) \(R_{01}\) \(R_{11}\) \(d_0\) \(d_1\) \(\rho\)
1 1 0 0 2.1 0 0
2 1.4142 0.7071 0.7071 4.9497 1.9799 0
3 1.7321 1.7321 1.4142 8.7180 4.2426 0.0267
4 2 3 2.2361 13 6.6188 0.0320

Row 1 meets a zero pivot and is adopted; row 2 rotates by \(45^\circ\) and fits two points exactly; from row 3 on, \(\rho\) grows. With \(\sigma \approx (4.1000, 1.0908)\), \(r = 2\) and back-substitution gives \(\hat{\pi}_1 = d_1 / R_{11} = 2.96\), \(\hat{\pi}_0 = (13 - 3 \cdot 2.96)/2 = 2.06\), with RMS \(= \sqrt{0.032 / 4} = 0.089443\).

Rank-deficient. Rows \((1, x, x)\) with \(\boldsymbol{\tau} = 2 + 3x\): two equal columns leave the third row of \(R\) zero, \(\rho = 0\), and \(\sigma = (5.540482,\, 1.141517,\, 0)\), so \(r = 2\). Only \(\pi_1 + \pi_2 = 3\) is determined; the minimum-norm split is \(\hat{\boldsymbol{\pi}} = (2, 1.5, 1.5)\). The last row of \(V^T\) is the unidentifiable direction \((0, 1, -1)/\sqrt2\).

Verified against double-precision normal equations on random 3-parameter problems of 5 to 205 rows: parameters agree to about \(3 \times 10^{-6}\).

4.6.5 Pitfalls & Edge Cases

4.6.6 Variants & Generalizations

4.6.7 Applications

4.6.8 Connections to Other Algorithms

4.7 Recursive Least Squares (RLS)

4.7.1 Overview & Motivation

Recursive Least Squares (RLS) is an online parameter estimation algorithm that incrementally updates a least-squares fit as new data arrives, without reprocessing past observations. It converges faster than gradient-descent methods (e.g., LMS) and is well-suited for real-time system identification and adaptive filtering on embedded systems.

4.7.2 Mathematical Theory

4.7.2.1 Problem Setting

Given a linear model \(y = \mathbf{x}^T \boldsymbol{\theta} + \epsilon\), where \(\mathbf{x} \in \mathbb{R}^p\) is the regressor, \(\boldsymbol{\theta} \in \mathbb{R}^p\) are the unknown parameters, and \(\epsilon\) is noise, RLS minimizes the exponentially weighted cost:

\[J(\boldsymbol{\theta}) = \sum_{k=1}^{n} \lambda^{n-k} (y_k - \mathbf{x}_k^T \boldsymbol{\theta})^2\]

where \(\lambda \in (0, 1]\) is the forgetting factor.

4.7.2.2 Update Equations

Given a new observation \((\mathbf{x}, y)\):

  1. Gain vector: \(\mathbf{g} = P \mathbf{x}\)
  2. Denominator: \(d = \lambda + \mathbf{x}^T P \mathbf{x}\)
  3. Innovation (pre-update error): \(e = y - \mathbf{x}^T \hat{\boldsymbol{\theta}}\)
  4. Parameter update: \(\hat{\boldsymbol{\theta}} \leftarrow \hat{\boldsymbol{\theta}} + \frac{\mathbf{g} \cdot e}{d}\)
  5. Covariance update: \(P \leftarrow \frac{1}{\lambda}\left(P - \frac{\mathbf{g} \mathbf{g}^T}{d}\right)\)

4.7.2.3 Forgetting Factor

4.7.2.4 Convergence Evaluation

The implementation provides convergence diagnostics via EstimationMetrics: - Innovation: Pre-update prediction error magnitude - Residual: Post-update prediction error magnitude - Uncertainty: Trace of the covariance matrix \(P\) (overall parameter uncertainty)

4.7.3 Complexity Analysis

Case Time Space Notes
Average \(O(p^2)\) \(O(p^2)\) Matrix-vector products + outer product

Per-update cost is \(O(p^2)\) for \(p\) parameters, dominated by the covariance update. This is significantly cheaper than the \(O(p^3)\) batch solution via matrix inversion.

4.7.4 Step-by-Step Walkthrough

Estimating the slope of \(y = 2x\) with RLS (\(p = 2\): bias + slope, \(\lambda = 1\)):

Step \(\mathbf{x}\) \(y\) \(\hat{\theta}_0\) (bias) \(\hat{\theta}_1\) (slope) Innovation
Init — — 0.0 0.0 —
1 [1, 1] 2 1.0 1.0 2.0
2 [1, 2] 4 0.0 2.0 1.0
3 [1, 3] 6 0.0 2.0 0.0

Convergence is achieved in 2 steps for this noiseless system.

4.7.5 Pitfalls & Edge Cases

4.7.6 Variants & Generalizations

4.7.7 Applications

4.7.8 Connections to Other Algorithms

4.8 Recursive Instrumental Variables (RIV)

4.8.1 Overview & Motivation

Recursive least squares assumes the regressor is known exactly and only the output is noisy. When the regressor is itself measured — a derivative taken from two noisy samples, a current read through a noisy sensor — least squares reads the noise as a weaker dependence on that column and biases its coefficient toward zero. The effect is regression dilution, and it does not shrink with more data: the estimate converges, but to the wrong value.

The instrumental-variable method removes that bias without modelling the noise. It needs a second signal per observation, the instrument, that moves with the true regressor but not with its noise. A known excitation is the usual choice: the reference a controller injects, a command, or a low-noise measurement of the same quantity. The recursive form updates the estimate sample by sample at the cost of recursive least squares.

4.8.2 Mathematical Theory

4.8.2.1 Problem Setting

The model is \(y = \mathbf{x}^T \boldsymbol{\theta} + \epsilon\), but only \(\tilde{\mathbf{x}} = \mathbf{x} + \mathbf{v}\) is observed, with \(\mathbf{v}\) zero-mean noise. Least squares solves \(\sum \tilde{\mathbf{x}}\,(y - \tilde{\mathbf{x}}^T \boldsymbol{\theta}) = 0\). Because \(E[\tilde{\mathbf{x}} \tilde{\mathbf{x}}^T] = R_x + R_v\), its limit is

\[\hat{\boldsymbol{\theta}}_{LS} \to (R_x + R_v)^{-1} R_x\, \boldsymbol{\theta}\]

For a single slope this is the attenuation \(\sigma_x^2 / (\sigma_x^2 + \sigma_v^2)\).

4.8.2.2 Instrumental-Variable Estimate

Given an instrument \(\mathbf{z}\) correlated with \(\mathbf{x}\) and uncorrelated with \(\mathbf{v}\) and \(\epsilon\), the estimate solves

\[\sum_{k} \lambda^{n-k}\, \mathbf{z}_k \left(y_k - \tilde{\mathbf{x}}_k^T \boldsymbol{\theta}\right) = 0 \quad\Rightarrow\quad \hat{\boldsymbol{\theta}} = \Big(\sum_k \lambda^{n-k} \mathbf{z}_k \tilde{\mathbf{x}}_k^T\Big)^{-1} \sum_k \lambda^{n-k} \mathbf{z}_k y_k\]

The noise enters only through \(E[\mathbf{z}\,\mathbf{v}^T] = 0\), so the estimate is consistent.

4.8.2.3 Update Equations

With \(P\) the inverse of the weighted cross-moment \(\sum \lambda^{n-k} \mathbf{z}_k \tilde{\mathbf{x}}_k^T\), a new observation \((\tilde{\mathbf{x}}, \mathbf{z}, y)\) gives:

  1. Gain vector: \(\mathbf{g} = P \mathbf{z}\)
  2. Denominator: \(d = \lambda + \tilde{\mathbf{x}}^T P \mathbf{z}\)
  3. Innovation: \(e = y - \tilde{\mathbf{x}}^T \hat{\boldsymbol{\theta}}\)
  4. Parameter update: \(\hat{\boldsymbol{\theta}} \leftarrow \hat{\boldsymbol{\theta}} + \mathbf{g}\, e / d\)
  5. Matrix update: \(P \leftarrow \frac{1}{\lambda}\left(P - \mathbf{g}\, \tilde{\mathbf{x}}^T P / d\right)\)

Steps 2 and 5 are the Sherman–Morrison update of a rank-one, non-symmetric term \(\mathbf{z}\tilde{\mathbf{x}}^T\). With \(\mathbf{z} = \tilde{\mathbf{x}}\) every step reduces to recursive least squares.

4.8.2.4 Diagnostics

The innovation, the post-update residual and the trace of \(P\) are reported as for recursive least squares. \(P\) is no longer a scaled covariance and need not be symmetric; its trace still falls as the instrument excites the parameters.

4.8.3 Complexity Analysis

Case Time Space Notes
Average \(O(p^2)\) \(O(p^2)\) Two matrix-vector products and one outer product

The cost per update matches recursive least squares: one more matrix-vector product, for \(\tilde{\mathbf{x}}^T P\), replaces the symmetric outer product.

4.8.4 Step-by-Step Walkthrough

A single slope, \(y = 2x\), observed through \(\tilde{x} = x + v\) with the instrument \(z = x\). The noise alternates so that it is uncorrelated with \(x\) over the four samples. \(\lambda = 1\), \(P_0 = 1000\), \(\hat{\theta}_0 = 0\):

Step \(x\) \(v\) \(\tilde{x}\) \(y\) Least squares \(\hat{\theta}\) Instrumental \(\hat{\theta}\)
1 1 0.5 1.5 2 1.333 1.332
2 1 -0.5 0.5 2 1.599 1.999
3 2 0.5 2.5 4 1.600 1.714
4 2 -0.5 1.5 4 1.818 2.000

After the four samples least squares holds \(2\,\sum x^2 / (\sum x^2 + \sum v^2) = 20/11\), the attenuated slope, while the instrumental estimate is back at 2: each time the noise has averaged out against the instrument, it is exact.

4.8.5 Pitfalls & Edge Cases

4.8.6 Variants & Generalizations

4.8.7 Applications

4.8.8 Connections to Other Algorithms

4.9 LMS / NLMS Adaptive Filter

4.9.1 Overview & Motivation

Real-time systems frequently need filters whose characteristics are unknown in advance or change over time. A fixed-coefficient FIR filter cannot cancel acoustic echo from an unknown loudspeaker-to-microphone path, equalize a time-varying channel, or identify the impulse response of a vibrating structure it has never seen before. The Least Mean Squares (LMS) algorithm and its normalized variant (NLMS) address this by adjusting a FIR filter’s coefficients online, one sample at a time, without requiring matrix inversions or covariance storage.

4.9.2 Mathematical Theory

4.9.2.1 Prerequisites

Let \(\mathbf{w} \in \mathbb{R}^{N}\) be the weight vector (FIR coefficients) and \(\mathbf{x}_n \in \mathbb{R}^{N}\) the tapped-delay-line vector at time \(n\), with \(x_n\) as the newest sample and \(x_{n-N+1}\) the oldest. The filter output is

\[y_n = \mathbf{w}_n^\top \mathbf{x}_n.\]

Given a desired signal \(d_n\), the estimation error is

\[e_n = d_n - y_n.\]

4.9.2.2 Core Definitions

The instantaneous squared error \(J_n = e_n^2\) is a quadratic function of \(\mathbf{w}_n\). Its gradient with respect to \(\mathbf{w}\) is

\[\nabla J_n = -2 e_n \mathbf{x}_n.\]

4.9.2.3 Derivation

LMS takes a noisy steepest-descent step using the instantaneous gradient:

\[\mathbf{w}_{n+1} = \mathbf{w}_n - \frac{\mu}{2} \nabla J_n = \mathbf{w}_n + \mu e_n \mathbf{x}_n.\]

NLMS divides the step by the input energy to make the effective step size input-level independent:

\[\mathbf{w}_{n+1} = \mathbf{w}_n + \frac{\mu}{\epsilon + \|\mathbf{x}_n\|^2} e_n \mathbf{x}_n,\]

where \(\epsilon > 0\) is a small regularizer that prevents division by zero during silence (\(\|\mathbf{x}_n\|^2 \approx 0\)).

4.9.2.4 Proof of Correctness

Under the independence assumption (successive input vectors are uncorrelated), the expected weight vector converges to the Wiener solution \(\mathbf{w}^* = \mathbf{R}^{-1}\mathbf{p}\) (where \(\mathbf{R}\) is the input autocorrelation matrix and \(\mathbf{p}\) is the cross-correlation vector) provided

\[0 < \mu < \frac{2}{\lambda_{\max}(\mathbf{R})},\]

a sufficient condition being \(\mu < 2 / (N \cdot \sigma_x^2)\). For NLMS the stability range simplifies to \(0 < \mu < 2\).

4.9.3 Complexity Analysis

Case Time Space Notes
Any \(O(N)\) \(O(N)\) Two dot products + one AXPY, all length \(N\)

No covariance matrix is stored; memory is exactly two vectors of length \(N\) (weights and delay line).

4.9.4 Step-by-Step Walkthrough

Consider a 2-tap system with true plant \(\mathbf{w}^* = [0.5,\ 0.5]^\top\), \(\mu = 0.1\), and a white input sequence.

Step Input Delay line \(\mathbf{x}\) Output \(y\) Desired \(d\) Error \(e\) Weight update direction
1 1.0 [1.0, 0.0] 0.0 0.5 0.5 \(+0.05\mathbf{x}\)
2 0.8 [0.8, 1.0] 0.04 0.9 0.86 \(+0.086\mathbf{x}\)
… … … … … … converges to \(\mathbf{w}^*\)

After many iterations the error approaches zero and the weights stabilize near \([0.5,\ 0.5]^\top\).

4.9.5 Pitfalls & Edge Cases

4.9.6 Variants & Generalizations

4.9.7 Applications

4.9.8 Connections to Other Algorithms

5 Filters

5.1 Kalman Filter

5.1.1 Overview & Motivation

In any real system, sensors are noisy and models are imperfect. The Kalman filter answers a deceptively simple question: given a noisy measurement and an imperfect prediction, what is the best estimate of the true state?

The answer is an optimal, recursive, minimum-variance estimator for linear systems with Gaussian noise. At each time step it performs two operations:

  1. Predict — propagate the state and covariance forward using the system model.
  2. Update — correct the prediction using the new measurement, weighted by the Kalman gain, which automatically balances trust in the model vs. trust in the sensor.

The filter is computationally lightweight (matrix operations on small state vectors), requires no storage of past measurements, and adapts its confidence in real time via the covariance matrix.

5.1.2 Mathematical Theory

5.1.2.1 State-Space Model

Discrete-time linear system with process noise \(w\) and measurement noise \(v\):

\[x_k = F_{k-1} x_{k-1} + B_{k-1} u_{k-1} + w_{k-1}, \quad w \sim \mathcal{N}(0, Q)\] \[z_k = H_k x_k + v_k, \quad v \sim \mathcal{N}(0, R)\]

where: - \(x_k \in \mathbb{R}^n\) — state vector - \(z_k \in \mathbb{R}^m\) — measurement vector - \(F\) — state transition matrix (\(n \times n\)) - \(B\) — control input matrix (\(n \times l\)) - \(H\) — measurement matrix (\(m \times n\)) - \(Q\) — process noise covariance (\(n \times n\)) - \(R\) — measurement noise covariance (\(m \times m\))

5.1.2.2 Predict Step

\[\hat{x}_k^- = F_{k-1} \hat{x}_{k-1}\] \[P_k^- = F_{k-1} P_{k-1} F_{k-1}^T + Q_{k-1}\]

5.1.2.3 Update Step

\[y_k = z_k - H_k \hat{x}_k^- \qquad \text{(innovation)}\] \[S_k = H_k P_k^- H_k^T + R_k \qquad \text{(innovation covariance)}\] \[K_k = P_k^- H_k^T S_k^{-1} \qquad \text{(Kalman gain)}\] \[\hat{x}_k = \hat{x}_k^- + K_k y_k \qquad \text{(state update)}\] \[P_k = (I - K_k H_k) P_k^- (I - K_k H_k)^T + K_k R_k K_k^T \qquad \text{(Joseph form covariance update)}\]

5.1.2.4 Optimality

The Kalman filter is the minimum mean-square error (MMSE) estimator among all linear estimators. Under the assumption that \(w\) and \(v\) are Gaussian, it is also the MMSE among all estimators (linear or not).

5.1.3 Complexity Analysis

Operation Time Space Notes
Predict \(O(n^2)\) \(O(n^2)\) Matrix multiply \(F P F^T\)
Update \(O(n^2 m + m^3)\) \(O(nm)\) Dominated by \(S^{-1}\) (\(m \times m\) inversion) and gain computation
Total per step \(O(n^2 m + m^3)\) \(O(n^2 + nm)\) For \(m \ll n\), approximately \(O(n^2)\)

For typical embedded use with \(n \leq 10\) and \(m \leq 5\), each step completes in microseconds.

5.1.4 Step-by-Step Walkthrough

System: Estimating position and velocity from noisy position measurements.

State: \(x = \begin{bmatrix} \text{position} \\ \text{velocity} \end{bmatrix}\), \(\Delta t = 1\) s.

\[F = \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}, \quad H = \begin{bmatrix} 1 & 0 \end{bmatrix}, \quad Q = \begin{bmatrix} 0.1 & 0 \\ 0 & 0.1 \end{bmatrix}, \quad R = [1]\]

Initial: \(\hat{x}_0 = [0, 0]^T\), \(P_0 = \begin{bmatrix} 10 & 0 \\ 0 & 10 \end{bmatrix}\)

Measurement: \(z_1 = 1.2\) (true position = 1.0)

Predict:

\[\hat{x}_1^- = \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} \begin{bmatrix} 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \end{bmatrix}\]

\[P_1^- = F P_0 F^T + Q = \begin{bmatrix} 20.1 & 10 \\ 10 & 10.1 \end{bmatrix}\]

Update:

\[y_1 = 1.2 - [1\; 0] \begin{bmatrix} 0 \\ 0 \end{bmatrix} = 1.2\]

\[S_1 = H P_1^- H^T + R = 20.1 + 1 = 21.1\]

\[K_1 = P_1^- H^T / S_1 = \begin{bmatrix} 20.1 \\ 10 \end{bmatrix} / 21.1 = \begin{bmatrix} 0.953 \\ 0.474 \end{bmatrix}\]

\[\hat{x}_1 = \begin{bmatrix} 0 \\ 0 \end{bmatrix} + \begin{bmatrix} 0.953 \\ 0.474 \end{bmatrix} \cdot 1.2 = \begin{bmatrix} 1.143 \\ 0.569 \end{bmatrix}\]

The filter quickly moves toward the measurement because the initial covariance \(P_0\) is large (low confidence). Over subsequent steps, \(P\) shrinks and the gain decreases — the filter becomes more reliant on the model.

5.1.5 Pitfalls & Edge Cases

5.1.6 Variants & Generalizations

Variant Key Difference
Extended Kalman Filter (EKF) Linearizes nonlinear \(f(x)\) and \(h(x)\) around the current estimate
Unscented Kalman Filter (UKF) Propagates sigma points through nonlinearities; avoids explicit Jacobians
Square-root Kalman Filter Maintains \(\sqrt{P}\) instead of \(P\) for improved numerical stability
Information Filter Works with the inverse covariance (information matrix); better for multi-sensor fusion
Kalman Smoother Uses future measurements to refine past estimates (offline, non-causal)
Adaptive Kalman Filter Estimates \(Q\) and \(R\) online from innovation statistics

5.1.7 Applications

5.1.8 Connections to Other Algorithms

graph LR
    KF["Kalman Filter"]
    LQR["LQR Controller"]
    DARE["DARE Solver"]
    PID["PID Controller"]
    KF -->|"state estimate"| LQR
    DARE -->|"steady-state gain"| KF
    KF -->|"filtered measurement"| PID
Algorithm Relationship
LQR Controller The Kalman filter + LQR = LQG (Linear Quadratic Gaussian), the separation principle
DARE Solver The steady-state Kalman gain can be found by solving a DARE (dual of the LQR DARE)
PID Controller Kalman-filtered measurements can serve as the PID’s process variable input

5.2 Extended Kalman Filter

5.2.1 Overview & Motivation

Many real-world systems are nonlinear: a pendulum’s restoring force is \(-\sin\theta\), not \(-\theta\); a radar measured range depends on \(\sqrt{x^2 + y^2}\), not a linear combination of states. The standard Kalman filter assumes linear dynamics \((x_k = Fx_{k-1})\) and cannot handle this directly.

The Extended Kalman Filter (EKF) extends the Kalman framework to nonlinear systems by linearizing the state transition and measurement functions around the current estimate at each step. It uses the same predict–update structure, but replaces the constant system matrices with Jacobians evaluated at the current operating point.

This makes the EKF the simplest and most widely-used nonlinear state estimator — at the cost of requiring the user to supply Jacobian functions and the assumption that linearization remains accurate within the filter’s uncertainty.

5.2.2 Mathematical Theory

5.2.2.1 Nonlinear State-Space Model

\[x_k = f(x_{k-1}, u_{k-1}) + w_{k-1}, \quad w \sim \mathcal{N}(0, Q)\] \[z_k = h(x_k) + v_k, \quad v \sim \mathcal{N}(0, R)\]

where \(f(\cdot)\) and \(h(\cdot)\) are arbitrary (differentiable) nonlinear functions.

5.2.2.2 Predict Step

  1. Compute the Jacobian of \(f\) at the current estimate:

\[F_k = \left.\frac{\partial f}{\partial x}\right|_{\hat{x}_{k-1}}\]

  1. Nonlinear state prediction:

\[\hat{x}_k^- = f(\hat{x}_{k-1}, u_{k-1})\]

  1. Covariance prediction (using the linearized model):

\[P_k^- = F_k P_{k-1} F_k^T + Q_{k-1}\]

5.2.2.3 Update Step

  1. Compute the Jacobian of \(h\) at the predicted state:

\[H_k = \left.\frac{\partial h}{\partial x}\right|_{\hat{x}_k^-}\]

  1. Nonlinear innovation:

\[y_k = z_k - h(\hat{x}_k^-)\]

  1. Innovation covariance, Kalman gain, state and covariance updates follow the standard Kalman filter equations using the Jacobian \(H_k\):

\[S_k = H_k P_k^- H_k^T + R_k\] \[K_k = P_k^- H_k^T S_k^{-1}\] \[\hat{x}_k = \hat{x}_k^- + K_k y_k\] \[P_k = (I - K_k H_k) P_k^-\]

5.2.3 Complexity Analysis

Operation Time Space Notes
Jacobian eval \(O(n^2)\) (user-defined) \(O(n^2)\) Depends on the specific nonlinear functions
Predict \(O(n^2)\) \(O(n^2)\) Matrix multiply \(F P F^T\) plus nonlinear state propagation
Update \(O(n^2 m + m^3)\) \(O(nm)\) Same as standard Kalman — the linearized equations are identical
Total per step \(O(n^2 m + m^3)\) \(O(n^2 + nm)\) Dominated by Jacobian evaluation for complex models

5.2.4 Step-by-Step Walkthrough

System: Estimating angle and angular velocity of a simple pendulum from noisy angle measurements.

State: \(x = \begin{bmatrix} \theta \\ \dot\theta \end{bmatrix}\), \(\Delta t = 0.1\) s.

Nonlinear dynamics: \[f(x) = \begin{bmatrix} \theta + \dot\theta \Delta t \\ \dot\theta - \sin(\theta) \Delta t \end{bmatrix}\]

Jacobian: \[F = \begin{bmatrix} 1 & \Delta t \\ -\cos(\theta) \Delta t & 1 \end{bmatrix}\]

Linear measurement: \(h(x) = \theta\), \(H = [1 \; 0]\).

At \(\hat{x}_0 = [0.3, 0]^T\): - Predict: \(\hat{x}_1^- = [0.3, -0.0296]^T\) (velocity decreases due to restoring force \(-\sin 0.3\)) - Update with \(z_1 = 0.28\): The Kalman gain weights the measurement against the prediction, pulling \(\theta\) toward 0.28.

After 50 iterations the EKF tracks the true pendulum oscillation closely, despite the nonlinear dynamics.

5.2.5 Pitfalls & Edge Cases

5.2.6 Variants & Generalizations

5.2.7 Comparison with Other Filters

Filter Jacobian Required? Accuracy for Nonlinear Systems Computational Cost
Kalman Filter No (linear only) Exact for linear Lowest
EKF Yes First-order approximation Low
UKF No Second-order approximation Moderate
Particle No Arbitrary (Monte Carlo) High

5.2.8 Applications

5.2.9 Connections to Other Algorithms

graph LR
    EKF["Extended Kalman Filter"]
    KF["Kalman Filter"]
    UKF["Unscented Kalman Filter"]
    EL["Euler-Lagrange"]
    EKF -->|"linear limit"| KF
    UKF -->|"alternative to"| EKF
    EL -->|"plant model"| EKF
Algorithm Relationship
Kalman Filter The EKF reduces to the standard KF when \(f\) and \(h\) are linear
Unscented Kalman Filter Avoids Jacobians using sigma points; better for highly nonlinear cases
Euler-Lagrange Provides the nonlinear dynamics model \(f(x)\) for mechanical systems

5.3 Unscented Kalman Filter

5.3.1 Overview & Motivation

The Extended Kalman Filter (EKF) linearizes nonlinearities using Jacobians. This works well for mild nonlinearities, but the first-order approximation can introduce significant errors when:

The Unscented Kalman Filter (UKF) addresses all three problems by replacing linearization with a deterministic sampling approach. Instead of approximating the nonlinear function, it approximates the probability distribution by choosing a small set of carefully weighted sample points — called sigma points — and propagating them through the true nonlinear function. The statistics (mean and covariance) are then reconstructed from the transformed points.

This captures the true mean and covariance to second-order accuracy for any nonlinearity, without computing a single Jacobian.

5.3.2 Mathematical Theory

5.3.2.1 Sigma Point Generation

Given state estimate \(\hat{x} \in \mathbb{R}^n\) and covariance \(P\), generate \(2n+1\) sigma points:

\[\sigma_0 = \hat{x}\] \[\sigma_i = \hat{x} + \left(\sqrt{(n + \lambda) P}\right)_i, \quad i = 1, \ldots, n\] \[\sigma_{i+n} = \hat{x} - \left(\sqrt{(n + \lambda) P}\right)_i, \quad i = 1, \ldots, n\]

where \(\left(\sqrt{M}\right)_i\) denotes the \(i\)-th column of the matrix square root (Cholesky decomposition \(M = LL^T\), using columns of \(L\)), and:

\[\lambda = \alpha^2 (n + \kappa) - n\]

5.3.2.2 Weights

\[w_0^{(m)} = \frac{\lambda}{n + \lambda}, \quad w_0^{(c)} = \frac{\lambda}{n + \lambda} + (1 - \alpha^2 + \beta)\] \[w_i^{(m)} = w_i^{(c)} = \frac{1}{2(n + \lambda)}, \quad i = 1, \ldots, 2n\]

Parameters: - \(\alpha\): Controls spread of sigma points (\(10^{-4} \leq \alpha \leq 1\)). Smaller values keep points closer to the mean. - \(\beta\): Incorporates prior knowledge about the distribution (\(\beta = 2\) is optimal for Gaussian). - \(\kappa\): Secondary scaling parameter (typically \(\kappa = 0\) or \(\kappa = 3 - n\)).

5.3.2.3 Predict Step

  1. Propagate sigma points through the nonlinear state transition:

\[\sigma_i^* = f(\sigma_i, u)\]

  1. Reconstruct predicted state and covariance:

\[\hat{x}_k^- = \sum_{i=0}^{2n} w_i^{(m)} \sigma_i^*\] \[P_k^- = \sum_{i=0}^{2n} w_i^{(c)} (\sigma_i^* - \hat{x}_k^-)(\sigma_i^* - \hat{x}_k^-)^T + Q\]

5.3.2.4 Update Step

  1. Generate new sigma points from the predicted state \(\hat{x}_k^-\) and \(P_k^-\).

  2. Transform sigma points through the measurement function:

\[z_i = h(\sigma_i)\]

  1. Predicted measurement and covariances:

\[\hat{z}_k = \sum_{i=0}^{2n} w_i^{(m)} z_i\] \[S_k = \sum_{i=0}^{2n} w_i^{(c)} (z_i - \hat{z}_k)(z_i - \hat{z}_k)^T + R\] \[P_{xz} = \sum_{i=0}^{2n} w_i^{(c)} (\sigma_i - \hat{x}_k^-)(z_i - \hat{z}_k)^T\]

  1. Kalman gain and state update:

\[K_k = P_{xz} S_k^{-1}\] \[\hat{x}_k = \hat{x}_k^- + K_k (z_k - \hat{z}_k)\] \[P_k = P_k^- - K_k S_k K_k^T\]

5.3.3 Complexity Analysis

Operation Time Space Notes
Sigma point gen \(O(n^3)\) \(O(n^2)\) Dominated by Cholesky decomposition
State propagation \(O((2n+1) \cdot c_f)\) \(O(n^2)\) \(c_f\) = cost of evaluating \(f(x)\)
Mean/cov reconstruct \(O(n^2 \cdot (2n+1))\) \(O(n^2)\) Weighted outer products
Update \(O(n^2 m + m^3)\) \(O(nm)\) Same as standard Kalman for the gain computation
Total per step \(O(n^3 + n^2 m)\) \(O(n^2 + nm)\) Cholesky and sigma point propagation dominate

For \(n \leq 10\), the additional cost over EKF is small — and the elimination of Jacobian derivation is a significant engineering benefit.

5.3.4 Step-by-Step Walkthrough

System: Same pendulum as the EKF example, \(x = [\theta, \dot\theta]^T\), \(n = 2\), \(\Delta t = 0.1\) s.

Parameters: \(\alpha = 0.1\), \(\beta = 2\), \(\kappa = 0\) → \(\lambda = -1.98\).

Initial: \(\hat{x}_0 = [0.3, 0]^T\), \(P_0 = \text{diag}(0.5, 0.5)\).

  1. Generate 5 sigma points from \(\hat{x}_0\) and \(P_0\) using Cholesky of \(P_0\).
  2. Propagate each sigma point through \(f(x)\) (pendulum dynamics).
  3. Reconstruct \(\hat{x}_1^-\) and \(P_1^-\) from the weighted transformed points.
  4. Generate new sigma points from \(\hat{x}_1^-\) and \(P_1^-\).
  5. Transform through \(h(x) = \theta\) for the measurement update.
  6. Compute \(S\), \(P_{xz}\), \(K\) and update state and covariance.

The UKF naturally captures the curvature of \(-\sin\theta\) without computing \(\cos\theta\) — the sigma points “feel” the nonlinearity directly.

5.3.5 Pitfalls & Edge Cases

5.3.6 Variants & Generalizations

5.3.7 Comparison with Other Filters

Filter Jacobian Required? Accuracy Order Handles High Nonlinearity? Computational Cost
Kalman Filter No (linear only) Exact (linear) No Lowest
EKF Yes 1st order Moderate Low
UKF No 2nd order Good Moderate
Particle No Arbitrary Excellent High

5.3.8 Applications

5.3.9 Connections to Other Algorithms

graph LR
    UKF["Unscented Kalman Filter"]
    KF["Kalman Filter"]
    EKF["Extended Kalman Filter"]
    CHO["Cholesky Decomposition"]
    UKF -->|"linear limit"| KF
    EKF -->|"alternative to"| UKF
    CHO -->|"sigma points"| UKF
Algorithm Relationship
Kalman Filter The UKF reduces to the standard KF when \(f\) and \(h\) are linear
Extended Kalman Filter Uses Jacobians instead of sigma points; simpler but less accurate for nonlinear cases
Cholesky Decomposition Used internally to generate sigma points from the covariance matrix

5.4 Kalman Smoother (RTS Smoother)

5.4.1 Overview & Motivation

The Kalman filter is a causal estimator: its state estimate at time \(t\) uses only observations up to and including time \(t\). When the full observation sequence is available (offline/batch setting), the Rauch-Tung-Striebel (RTS) smoother refines every filtered estimate by incorporating future measurements. The result is the minimum-mean-square-error (MMSE) state estimate given the entire sequence \(z_{1:T}\).

The RTS smoother is the prerequisite for the Expectation-Maximization (EM) algorithm for Kalman Filter parameter identification: the E-step requires smoothed means, smoothed covariances, and the lag-1 cross-covariance \(P_{t,t-1|T}\) in order to form the sufficient statistics used by the M-step.

5.4.2 Mathematical Theory

5.4.2.1 State-Space Model

\[ x_{t+1} = F x_t + w_t, \quad w_t \sim \mathcal{N}(0, Q) \]

\[ z_t = H x_t + v_t, \quad v_t \sim \mathcal{N}(0, R) \]

with \(x_0 \sim \mathcal{N}(\mu_0, P_0)\).

5.4.2.2 Forward Pass (Kalman Filter)

For \(t = 0, 1, \ldots, T-1\):

Initialise (t = 0 only):

\[\hat{x}_{0|-} = \mu_0, \quad P_{0|-} = P_0\]

Update:

\[ S_t = H P_{t|-} H^\top + R \]

\[ K_t = P_{t|-} H^\top S_t^{-1} \]

\[ \hat{x}_{t|t} = \hat{x}_{t|-} + K_t (z_t - H \hat{x}_{t|-}) \]

\[ P_{t|t} = (I - K_t H) P_{t|-} (I - K_t H)^\top + K_t R K_t^\top \quad \text{(Joseph form)} \]

Log-likelihood contribution:

\[ \ell_t = -\tfrac{1}{2}\bigl(\log\det S_t + (z_t - H\hat{x}_{t|-})^\top S_t^{-1} (z_t - H\hat{x}_{t|-}) + m\log 2\pi\bigr) \]

where \(m\) = MeasurementSize. The total is \(\ell = \sum_{t=0}^{T-1} \ell_t\).

Predict (for \(t < T-1\)):

\[ \hat{x}_{t+1|-} = F \hat{x}_{t|t}, \quad P_{t+1|-} = F P_{t|t} F^\top + Q \]

5.4.2.3 Backward Pass (RTS Smoother)

Initialise at \(t = T-1\):

\[ \hat{x}_{T-1|T} = \hat{x}_{T-1|T-1}, \quad P_{T-1|T} = P_{T-1|T-1} \]

For \(t = T-2, T-3, \ldots, 0\):

Smoother gain:

\[ G_t = P_{t|t} F^\top P_{t+1|t}^{-1} \]

Smoothed mean and covariance:

\[ \hat{x}_{t|T} = \hat{x}_{t|t} + G_t (\hat{x}_{t+1|T} - \hat{x}_{t+1|t}) \]

\[ P_{t|T} = P_{t|t} + G_t (P_{t+1|T} - P_{t+1|t}) G_t^\top \]

5.4.2.4 Lag-1 Cross-Covariance

The EM M-step requires \(P_{t,t-1|T} = \mathrm{Cov}(x_t, x_{t-1} \mid z_{1:T})\).

Initialisation (Shumway & Stoffer, 1982):

\[ P_{T-1, T-2|T} = (I - K_{T-1} H) F P_{T-2|T-2} \]

Recursion for \(t = T-3, \ldots, 0\):

\[ P_{t+1, t|T} = P_{t+1|t+1} G_t^\top + G_{t+1} (P_{t+2, t+1|T} - F P_{t+1|t+1}) G_t^\top \]

Note: \(P_{0,-1|T}\) is undefined; index 0 of lagCrossCovariances is always zero.

5.4.3 Complexity Analysis

Case Time Space Notes
All \(O(T \cdot N^3)\) \(O(T \cdot N^2)\) stack Dominant cost: \(N\times N\) solves at each step

All storage is stack-allocated via std::array. There is no heap usage.

5.4.4 Step-by-Step Walkthrough

Consider a 1-D constant-position model (\(N=1\), \(M=1\), \(T=3\)) with \(F=1\), \(H=1\), \(Q=0.1\), \(R=1\), \(\mu_0=0\), \(P_0=1\), and observations \(z = [0.5, 0.3, 0.8]\).

Forward pass:

\(t\) \(P_{t\|-}\) \(S_t\) \(K_t\) \(\hat{x}_{t\|t}\) \(P_{t\|t}\)
0 1.0 2.0 0.500 0.250 0.500
1 0.600 1.600 0.375 0.363 0.375
2 0.475 1.475 0.322 0.620 0.322

Backward pass (\(t=1\) then \(t=0\)):

\(t=1\): \(G_1 = 0.500/0.600 = 0.833\); \(\hat{x}_{1|3} = 0.363 + 0.833(0.620 - 0.363) \approx 0.577\)

\(t=0\): \(G_0 = 0.375/0.375 = 1.000\); \(\hat{x}_{0|3} = 0.250 + 1.000(0.577 - 0.363) \approx 0.464\)

Notice how the smoothed \(\hat{x}_{0|3}=0.464\) incorporates all three observations, whereas the filtered \(\hat{x}_{0|0}=0.250\) only used the first.

5.4.5 Pitfalls & Edge Cases

5.4.6 Variants & Generalizations

5.4.7 Applications

5.4.8 Connections to Other Algorithms

5.5 Square-Root Kalman Filter

5.5.1 Overview & Motivation

Standard Kalman filter implementations propagate the error covariance matrix \(P\) directly. On resource-constrained hardware — where floating-point arithmetic is limited to 32 bits — roundoff errors accumulate with every predict-update cycle. After many iterations \(P\) can lose symmetry or positive-definiteness, causing the filter to diverge catastrophically.

The square-root Kalman filter eliminates this failure mode by never storing \(P\) explicitly. Instead it carries a Cholesky factor \(S\) such that \(P = S S^\top\), and rewrites every predict/update operation as an orthogonal triangularization. Because orthogonal transforms preserve matrix geometry exactly, the factor \(S\) — and therefore the covariance \(P = S S^\top\) — remains symmetric positive-definite by construction even in single-precision arithmetic.

A further benefit is numerical conditioning: \(\mathrm{cond}(S) = \sqrt{\mathrm{cond}(P)}\), so roughly twice as many significant bits survive across each filter step compared to the conventional form.

5.5.2 Mathematical Theory

5.5.2.1 State-Space Model

The underlying discrete-time linear system is identical to the conventional Kalman filter:

\[x_k = F x_{k-1} + B u_{k-1} + w_{k-1}, \quad w \sim \mathcal{N}(0,Q)\] \[z_k = H x_k + v_k, \quad v \sim \mathcal{N}(0,R)\]

The square-root filter parameterises uncertainty through lower-triangular factors \(S\), \(S_Q\), \(S_R\) satisfying \(P = S S^\top\), \(Q = S_Q S_Q^\top\), \(R = S_R S_R^\top\).

5.5.2.2 Predict Step (Time Update)

The goal is to compute \(S^-\) such that \(P^- = F P F^\top + Q = S^- (S^-)^\top\) without forming \(P\) or \(P^-\).

Construct the \((2n \times n)\) pre-array:

\[\mathcal{A} = \begin{bmatrix} (F S)^\top \\ S_Q^\top \end{bmatrix}\]

Apply a QR decomposition \(\mathcal{A} = Q_\perp R\) (orthogonal \(Q_\perp\), upper-triangular \(R\)). Then \(S^- = R^\top\) is the new lower-triangular factor, because:

\[R^\top R = \mathcal{A}^\top \mathcal{A} = S^\top F^\top F S + S_Q^\top S_Q = F P F^\top + Q = P^-\]

5.5.2.3 Update Step (Measurement Update)

Construct the \(((m+n) \times (m+n))\) pre-array:

\[\mathcal{B} = \begin{bmatrix} S_R & H S \\ 0 & S \end{bmatrix}\]

Apply QR triangularization: \(\mathcal{B} = Q_\perp \begin{bmatrix} S_y & \tilde{K} \\ 0 & S^+ \end{bmatrix}\)

The block \(S_y\) (upper-left, \(m \times m\)) is the innovation-covariance factor satisfying \(S_y S_y^\top = H P^- H^\top + R\). The block \(S^+\) (lower-right, \(n \times n\)) is the updated covariance factor. The Kalman gain emerges from the cross block:

\[K = \tilde{K}^\top S_y^{-\top}\]

solved via a triangular back-substitution rather than an explicit matrix inverse.

The state update is the standard correction:

\[x^+ = x^- + K(z - H x^-)\]

5.5.2.4 QR via Givens Rotations

Both pre-arrays are triangularized by sequential Givens rotations applied column by column, annihilating one sub-diagonal entry per rotation. This is cache-friendly, in-place, and requires only \(O(n^2)\) temporary storage — no heap allocation.

5.5.3 Complexity Analysis

Case Time Space Notes
Predict \(O(n^3)\) \(O(n^2)\) Dominated by \(F S\) multiply and QR
Update \(O((n+m) n^2)\) \(O(n^2)\) QR on \((m+n) \times (m+n)\) pre-array

where \(n =\) StateSize, \(m =\) MeasurementSize. All arrays are fixed-size; no heap is used.

5.5.4 Step-by-Step Walkthrough

Consider a 1-state system (\(n=1\), \(m=1\)) with \(F=1\), \(H=1\), \(S=0.5\), \(S_Q=0.1\), \(S_R=0.3\):

Predict:

Pre-array: \(\mathcal{A} = [0.5;\; 0.1]\) (column vector, already a column). \(R = \sqrt{0.5^2 + 0.1^2} = \sqrt{0.26} \approx 0.5099\). New factor \(S^- = 0.5099\), i.e. \(P^- \approx 0.26\).

Update (measurement \(z = 1.2\), prior \(x^- = 1.0\)):

Pre-array \(\mathcal{B} = \begin{bmatrix}0.3 & 0.5099 \\ 0 & 0.5099\end{bmatrix}\). One Givens rotation annihilates \(\mathcal{B}_{10}=0\), yielding upper-triangular form. \(S_y \approx 0.5974\), \(S^+ \approx 0.424\), \(K \approx 0.741\). \(x^+ = 1.0 + 0.741 \times 0.2 = 1.148\).

5.5.5 Pitfalls & Edge Cases

5.5.6 Variants & Generalizations

5.5.7 Applications

5.5.8 Connections to Other Algorithms

5.6 Madgwick / Mahony AHRS Filter

5.6.1 Overview & Motivation

Any system that needs to know its 3-D orientation in space — a drone, a robot arm, an AR headset, a wearable device — must fuse data from multiple sensors. A gyroscope measures angular rate with high bandwidth and low short-term noise, but its integral drifts over time due to bias. An accelerometer measures the gravity vector, which gives a long-term absolute reference for pitch and roll, but it is contaminated by vibration. A magnetometer provides a heading reference for yaw, but it is affected by magnetic interference.

The Attitude and Heading Reference System (AHRS) filter solves the drift-correction problem in O(1) time per step with a fixed, small memory footprint. Two closely related algorithms — Madgwick’s gradient-descent filter and Mahony’s passive complementary filter — achieve this by continuously nudging the gyro-integrated quaternion so that the predicted sensor directions match the measured ones. Both are far cheaper to compute than a full quaternion Extended Kalman Filter, making them the standard choice for microcontroller-class attitude estimation.

5.6.2 Mathematical Theory

5.6.2.1 Quaternion State Representation

Orientation is maintained as a unit quaternion \(q = [q_w, q_x, q_y, q_z]^T \in \mathbb{H}\), \(\|q\| = 1\), mapping from the body frame to the Earth frame. Quaternions avoid the gimbal lock inherent in Euler angles and require fewer trigonometric operations than rotation matrices per integration step.

5.6.2.2 Gyro Integration

The pure gyro propagation step integrates the body angular rate \(\boldsymbol{\omega} = [\omega_x, \omega_y, \omega_z]^T\) in rad/s:

\[\dot{q} = \frac{1}{2} q \otimes \begin{bmatrix} 0 \\ \boldsymbol{\omega} \end{bmatrix}\]

\[q_{k+1} = q_k + \dot{q} \, T_s\]

followed by renormalization. This is the predict step; without a correction it drifts.

5.6.2.3 Madgwick: Gradient-Descent Correction

Define the objective function as the alignment error between the predicted sensor directions and the measurements. For the gravity observation:

\[\mathbf{f}(q, \hat{\mathbf{a}}) = R(q)^T \mathbf{g}_{\text{ref}} - \hat{\mathbf{a}}\]

where \(\mathbf{g}_{\text{ref}} = [0, 0, 1]^T\) and \(\hat{\mathbf{a}}\) is the normalized accelerometer vector. The steepest-descent direction in quaternion space is:

\[\nabla F = J^T \mathbf{f}\]

where \(J = \partial \mathbf{f}/\partial q\) is the \(3 \times 4\) Jacobian of \(\mathbf{f}\) with respect to \(q\). This gradient is normalized and subtracted from the gyro-driven rate:

\[\dot{q} = \frac{1}{2} q \otimes \begin{bmatrix} 0 \\ \boldsymbol{\omega} \end{bmatrix} - \beta \, \frac{\nabla F}{\|\nabla F\|}\]

The parameter \(\beta\) is the gradient-descent step size; it is set proportional to the expected gyro measurement error in rad/s.

For the magnetometer (MARG mode), the earth frame reference is \(\mathbf{b} = [b_x, 0, b_z]^T\), where \(b_x\) and \(b_z\) are computed by rotating the normalized magnetometer measurement into the Earth frame and zeroing its \(y\)-component, making the heading reference dip-angle-agnostic. A second objective function \(\mathbf{f}_\text{mag}\) and its Jacobian are added to the gradient.

5.6.2.4 Mahony: Proportional-Integral Feedback on SO(3)

Rather than gradient descent, Mahony’s filter uses a cross-product error:

\[\mathbf{e} = \hat{\mathbf{a}} \times \mathbf{v}\]

where \(\mathbf{v}\) is the third column of \(R(q)\) (the predicted gravity direction in the body frame). The angular rate is corrected before integration:

\[\boldsymbol{\omega}_c = \boldsymbol{\omega} + K_p \mathbf{e} + \mathbf{b}_\text{est}\]

\[\dot{\mathbf{b}}_\text{est} = K_i \mathbf{e}\]

The integral term \(\mathbf{b}_\text{est}\) is a running estimate of the gyro bias; once it converges, the steady-state attitude error is driven to zero even under sustained gyro drift. The proportional gain \(K_p\) sets the bandwidth of the correction loop; \(K_i\) sets the bias-learning rate.

For MARG mode a magnetometer cross-product error is added to \(\mathbf{e}\):

\[\mathbf{e} = \hat{\mathbf{a}} \times \mathbf{v} + \hat{\mathbf{m}} \times \mathbf{w}\]

where \(\mathbf{w}\) is the predicted earth-field direction in the body frame from the current tilt.

5.6.2.5 Renormalization

Both algorithms renormalize \(q\) after every integration step to enforce the unit-norm constraint, compensating for the first-order Euler integration error that would otherwise slowly push \(q\) off the unit sphere.

5.6.3 Complexity Analysis

Case Time Space Notes
UpdateImu O(1) O(1) Fixed multiply-add count; one inverse-sqrt normalization
UpdateMarg O(1) O(1) Two objective/gradient evaluations; same asymptotic cost
Memory — 7 T 4 quaternion + 3 integral bias floats; no buffers or heap

The fixed cost makes both algorithms suitable for any loop rate the MCU can sustain, from 100 Hz audio-rate IMUs to 8 kHz flight-controller IMUs.

5.6.4 Step-by-Step Walkthrough

Scenario: Quadrotor is hovering level. Gyro measures a small constant bias of 0.05 rad/s on the x-axis. Accelerometer reads \([0, 0, 9.81]\) m/s².

Madgwick step (simplified):

  1. Normalize accelerometer: \(\hat{\mathbf{a}} = [0, 0, 1]\).
  2. Predicted gravity from \(q \approx [1, 0, 0, 0]\): \(\mathbf{v} = [0, 0, 1]\).
  3. Objective: \(\mathbf{f} = \mathbf{v} - \hat{\mathbf{a}} = [0, 0, 0]\) — no error, gradient is zero.
  4. Rate: \(\dot{q} = \frac{1}{2} q \otimes [0, \text{bias}, 0, 0]\) — small drift from bias.
  5. Integrate: \(q\) drifts slightly.

Over time without correction this drift accumulates; with the gradient term driving \(\mathbf{f} \to 0\), Madgwick continuously nudges \(q\) back to level.

Mahony step (simplified):

  1. Cross-product error: \(\mathbf{e} = [0,0,1] \times [0,0,1] = [0,0,0]\).
  2. Integral accumulates: \(\mathbf{b}_\text{est} \mathrel{+}= K_i \mathbf{e} \cdot T_s = 0\).
  3. Corrected rate: \(\boldsymbol{\omega}_c = [0.05, 0, 0] + 0 + 0 = [0.05, 0, 0]\) — still biased.
  4. After the cross-product error becomes non-zero (when \(q\) drifts from level), the integral term ramps up to cancel the bias, driving attitude error back to zero.

5.6.5 Pitfalls & Edge Cases

5.6.6 Variants & Generalizations

Variant Key Difference
6-DOF (IMU-only) Accelerometer alone; roll and pitch converge, yaw is unobservable
9-DOF (MARG) Adds magnetometer; all three angles converge given a non-disturbed field
Extended Kalman AHRS Treats noise covariances explicitly; heavier but allows systematic tuning via \(Q\)/\(R\) matrices
Multiplicative EKF (MEKF) Kalman update on the error quaternion to preserve unit-norm; best-in-class accuracy, high cost
Gradient-descent with adaptive β Adjusts \(\beta\) based on the magnitude of the gradient, reducing transient overshoot at startup
Second-order Runge-Kutta integration Reduces integration error at low update rates at the cost of one extra function evaluation

5.6.7 Applications

5.6.8 Connections to Other Algorithms

Algorithm Relationship
Complementary Filter The scalar 1-D ancestor; Madgwick/Mahony extend the idea to quaternion SO(3)
Extended Kalman Filter The probabilistic alternative; heavier but allows noise covariance estimation
Quaternion The state representation shared by all three-axis attitude estimators

5.7 Alpha-Beta / Alpha-Beta-Gamma Filter

5.7.1 Overview & Motivation

In embedded control and tracking applications, a sensor delivers a position measurement every sample period, but that measurement is corrupted by noise. A simple lowpass filter smooths the noise but cannot estimate velocity, which is needed for prediction and control. A full Kalman filter computes optimal gains but requires covariance propagation — a matrix inverse every step — which is too expensive for a fast ISR.

The alpha-beta (and its extension, alpha-beta-gamma) filter resolves this tension. It maintains a position and velocity estimate (and optionally acceleration) using only a few multiply-adds per sample. The gains are fixed constants, computed once at design time from a single scalar parameter. The result is a deterministic, constant-time predictor-corrector that delivers most of the benefit of a steady-state Kalman filter at a fraction of the cost.

5.7.2 Mathematical Theory

5.7.2.1 State Model

The filter assumes constant-velocity (order 2) or constant-acceleration (order 3) kinematics. For order 2, the state is \(\mathbf{x} = [p, \dot{p}]^\top\); for order 3, \(\mathbf{x} = [p, \dot{p}, \ddot{p}]^\top\).

5.7.2.2 Predict Step

\[\hat{p}^- = \hat{p} + T_s \hat{v} + \tfrac{1}{2} T_s^2 \hat{a} \quad (\hat{a} \text{ omitted for order 2})\] \[\hat{v}^- = \hat{v} + T_s \hat{a} \quad (\hat{a} \text{ omitted for order 2})\] \[\hat{a}^- = \hat{a}\]

5.7.2.3 Correct Step

Let the innovation (residual) be \(r = z - \hat{p}^-\), where \(z\) is the measured position. Then:

\[\hat{p} = \hat{p}^- + \alpha r\] \[\hat{v} = \hat{v}^- + \frac{\beta}{T_s} r\] \[\hat{a} = \hat{a}^- + \frac{2\gamma}{T_s^2} r \quad (\text{order 3 only})\]

The denominators \(T_s\) and \(T_s^2\) convert the dimensionless residual into velocity and acceleration corrections.

5.7.2.4 Kalata Steady-State Design (Tracking Index)

For the order-2 case, Kalata (1984) defines the tracking index \(\lambda = \frac{\sigma_w T_s^2}{\sigma_v}\), where \(\sigma_w\) is process noise intensity and \(\sigma_v\) is measurement noise standard deviation. The critically-damped gains are:

\[r = \frac{4 + \lambda - \sqrt{8\lambda + \lambda^2}}{4}\] \[\alpha = 1 - r^2\] \[\beta = 2(2 - \alpha) - 4\sqrt{1 - \alpha}\]

A single scalar \(\lambda\) thus controls the smoothing/lag trade-off.

5.7.2.5 Stability Conditions

For the order-2 filter, Simpson’s triangle requires:

\[0 < \alpha < 1, \qquad 0 < \beta < 4 - 2\alpha\]

Violation of the second bound causes oscillatory divergence.

5.7.3 Complexity Analysis

Case Time Space Notes
Best \(O(1)\) \(O(N)\) \(N \in \{2, 3\}\) state words plus fixed scalars
Average \(O(1)\) \(O(N)\) same
Worst \(O(1)\) \(O(N)\) gains are precomputed; no covariance update

The hot path is a handful of fused multiply-add operations: predict costs 2–4 MACs, correct costs 2–3 MACs.

5.7.4 Step-by-Step Walkthrough

Consider an order-2 filter with \(\alpha = 0.5\), \(\beta = 0.1\), \(T_s = 1.0\,\text{s}\), measuring a ramp \(z[n] = 0.2n\).

Step \(z\) \(\hat{p}^-\) \(\hat{v}^-\) \(r\) \(\hat{p}\) \(\hat{v}\)
0 (seed) 0.0 — — — 0.0 0.0
1 0.2 0.0 0.0 0.2 0.10 0.020
2 0.4 0.12 0.020 0.28 0.26 0.048
… … … … … … …

After several hundred steps, \(\hat{v} \to 0.2\) and lag \(\to 0\).

5.7.5 Pitfalls & Edge Cases

5.7.6 Variants & Generalizations

5.7.7 Applications

5.7.8 Connections to Other Algorithms

5.8 Complementary Filter

5.8.1 Overview & Motivation

Real-time attitude and heading estimation requires fusing two fundamentally different sensor modalities: a gyroscope that integrates angular rate to produce a short-term angle estimate (fast response, low noise, but subject to drift) and an accelerometer or magnetometer that reads the angle directly (accurate at rest, noisy during motion, slow dynamics). Neither sensor alone is sufficient. The complementary filter solves the fusion problem with two multiplies and two adds per sample, making it the default tilt estimator on virtually every IMU-based embedded project where a full Kalman filter is unaffordable.

5.8.2 Mathematical Theory

5.8.2.1 Frequency-Domain Complement

The two sensor paths are complementary in the transfer-function sense: the gyro path acts as a first-order high-pass filter and the direct-angle path acts as a first-order low-pass filter sharing the same crossover frequency \(\omega_c = 1/\tau\). Their sum is identically unity for all frequencies:

\[H_{HP}(s) + H_{LP}(s) = 1\]

This ensures that no frequency content is amplified or attenuated by the fusion itself.

5.8.2.2 Discrete-Time Update Equation

Given the fused angle \(\theta_k\), gyro rate \(\omega_k\), accelerometer angle \(\theta_{acc,k}\), sample period \(T_s\), and blend weight \(\alpha \in [0,1]\):

\[\theta_{k+1} = \alpha\,(\theta_k + \omega_k\,T_s) + (1-\alpha)\,\theta_{acc,k}\]

The term \(\theta_k + \omega_k T_s\) is the high-pass path (integration of the fast sensor), and \(\theta_{acc,k}\) is the low-pass path (direct measurement from the slow sensor).

5.8.2.3 Design Parameter

The single tuning knob is the crossover time constant \(\tau\), which maps to \(\alpha\) via:

\[\alpha = \frac{\tau}{\tau + T_s}\]

Below \(1/\tau\) the filter trusts the accelerometer; above it, the gyroscope. Typical embedded values are \(\tau = 0.5\)–\(2\) s, corresponding to \(\alpha \approx 0.98\) at \(T_s = 10\) ms.

5.8.2.4 Heading Wrap (Shortest-Arc Blend)

When the state is a heading angle in \((-\pi, \pi]\), a naive linear blend can jump by \(2\pi\) near the seam. Instead the blend is performed along the shortest arc:

\[\delta = \mathrm{WrapToPi}(\theta_{acc} - \hat{\theta})\] \[\theta_{k+1} = \mathrm{WrapToPi}\!\left(\hat{\theta} + (1-\alpha)\,\delta\right)\]

where \(\mathrm{WrapToPi}\) reduces an angle into \((-\pi, \pi]\) — the library-wide angle convention, so an angle of exactly \(-\pi\) is reported as \(+\pi\) (see Angle Utilities).

5.8.3 Complexity Analysis

Operation Time Space Notes
Update \(O(1)\) \(O(1)\) 2 multiplies, 2 adds; 1 fmod when wrapping
Reset \(O(1)\) \(O(1)\) Single state write

Total storage: one angle word plus two constant coefficients.

5.8.4 Step-by-Step Walkthrough

Setup: \(\alpha = 0.98\), \(T_s = 10\) ms, initial angle \(= 0\).

Sample 1: gyro rate \(\omega = 10\) deg/s \(= 0.1745\) rad/s, accel reads \(\theta_{acc} = 0.01\) rad.

\[\hat{\theta} = 0 + 0.1745 \times 0.01 = 0.001745 \text{ rad} \quad (\text{gyro path})\] \[\theta_1 = 0.98 \times 0.001745 + 0.02 \times 0.01 = 0.001710 + 0.000200 = 0.001910 \text{ rad}\]

After many samples with zero rate and accel \(= 0.2\) rad: the low-pass term accumulates and \(\theta \to 0.2\) rad as \((1-\alpha)^n \to 0\).

5.8.5 Pitfalls & Edge Cases

5.8.6 Variants & Generalizations

Variant Key Difference
Mahony filter 3-D quaternion formulation with integral gyro-bias estimator
Madgwick filter Gradient-descent quaternion fusion; no linearisation
Alpha-Beta filter Fixed-gain tracking without a slow sensor; pure high-pass integration
Kalman filter Optimal (minimum-variance) fusion; requires noise covariance tuning
Two-step complementary Separate pitch/roll from heading; common on 6-DOF IMUs

5.8.7 Applications

5.8.8 Connections to Other Algorithms

Algorithm Relationship
Exponential Moving Average The low-pass path in isolation; \(\alpha_{EMA} = 1-\alpha_{CF}\)
Alpha-Beta Filter Complementary filter without a slow-sensor reference; fixed-gain tracker
AHRS Madgwick/Mahony 3-D quaternion generalization with gyro-bias estimation
Kalman Filter Statistically optimal generalization requiring \(Q\) and \(R\) tuning

5.9 Biquad / Second-Order-Section Cascade

5.9.1 Overview & Motivation

High-order IIR filters realized as a single monolithic direct-form transfer function suffer from severe numerical sensitivity: small perturbations in any coefficient can shift poles dramatically, and round-off noise accumulates in proportion to the filter order. These effects are catastrophic on fixed-point hardware and non-trivial even in single-precision floating-point.

The solution adopted universally in professional DSP is to factor the transfer function into quadratic terms — each with exactly two poles and two zeros — and chain them in series. This cascade of second-order sections (“biquads”) keeps each section’s coefficients well-scaled, its pole-sensitivity small, and its round-off noise bounded independently of the total filter order.

5.9.2 Mathematical Theory

5.9.2.1 Transfer Function Factorization

Any real-coefficient rational transfer function of order \(N\) factors as:

\[H(z) = G \prod_{k=1}^{\lceil N/2 \rceil} \frac{b_{0,k} + b_{1,k}z^{-1} + b_{2,k}z^{-2}}{1 + a_{1,k}z^{-1} + a_{2,k}z^{-2}}\]

Each factor is a second-order section (SOS). The overall frequency response is the product of the per-section responses; the total group delay is the sum of per-section group delays.

5.9.2.2 RBJ Cookbook Design Equations

For standard shelving, peaking, and pass/reject types, closed-form normalized coefficients derive from the bilinear transform applied to an analog prototype. Define:

\[\omega_0 = \frac{2\pi f_c}{f_s}, \qquad c_\omega = \cos(\omega_0), \qquad \alpha = \frac{\sin(\omega_0)}{2Q}\]

Low-pass:

\[b_0 = \frac{1 - c_\omega}{2(1+\alpha)}, \quad b_1 = \frac{1-c_\omega}{1+\alpha}, \quad b_2 = b_0\]

\[a_1 = \frac{-2c_\omega}{1+\alpha}, \quad a_2 = \frac{1-\alpha}{1+\alpha}\]

High-pass: replace \((1 - c_\omega)\) with \((1 + c_\omega)\) and negate \(b_1\).

Notch (band-reject):

\[b_0 = b_2 = \frac{1}{1+\alpha}, \quad b_1 = a_1 = \frac{-2c_\omega}{1+\alpha}, \quad a_2 = \frac{1-\alpha}{1+\alpha}\]

All coefficients are pre-normalized by \(a_0 = 1 + \alpha\), so the feedback denominator leading coefficient is always 1 and the hot path requires no division.

5.9.2.3 Transposed Direct Form II (TDF-II)

TDF-II is the canonical embedded realization. It minimizes the number of state registers to two per section while achieving low round-off noise. The recurrence for section \(k\) is:

\[y[n] = b_0 x[n] + z_1[n-1]\]

\[z_1[n] = b_1 x[n] - a_1 y[n] + z_2[n-1]\]

\[z_2[n] = b_2 x[n] - a_2 y[n]\]

This is exactly five multiplies and four adds per sample — the theoretical minimum for an arbitrary biquad — and requires no intermediate storage beyond \(z_1\) and \(z_2\).

5.9.3 Complexity Analysis

Case Time Space Notes
Per sample \(O(S)\) \(O(S)\) \(S\) = number of sections; 5 mults + 4 adds each
Design \(O(1)\) \(O(1)\) Closed-form RBJ formulas; no iteration
Reset \(O(S)\) — Zero two state words per section

5.9.4 Step-by-Step Walkthrough

Example: design a 4th-order low-pass as two cascaded 2nd-order sections.

Given \(f_c = 100\) Hz, \(f_s = 1000\) Hz, \(Q = 0.707\) (Butterworth factor for each section):

  1. Compute \(\omega_0 = 2\pi \cdot 0.1 \approx 0.6283\)
  2. \(c_\omega = \cos(0.6283) \approx 0.8090\), \(\alpha = \sin(0.6283)/(2 \times 0.707) \approx 0.4142\)
  3. \(a_0 = 1.4142\); normalize: \(b_0 \approx 0.06745\), \(b_1 \approx 0.13490\), \(b_2 \approx 0.06745\), \(a_1 \approx -1.1429\), \(a_2 \approx 0.4128\)
  4. Use the same section twice to realize a 4th-order filter.
  5. At each sample, pass input through section 1, then section 2: \(y = H_2(H_1(x))\).

Impulse response trace for bypass section \(\{b_0=1, b_1=b_2=a_1=a_2=0\}\): Input \([1, 0, 0, \ldots]\) → output \([1, 0, 0, \ldots]\) — identity passthrough.

5.9.5 Pitfalls & Edge Cases

5.9.6 Variants & Generalizations

5.9.7 Applications

5.9.8 Connections to Other Algorithms

5.10 CIC (Cascaded Integrator-Comb) Filter

5.10.1 Overview & Motivation

Decimation and interpolation in digital signal processing typically require a lowpass anti-aliasing filter before the rate change. Standard FIR filters require multiplications proportional to their order. The CIC filter achieves a highly efficient lowpass response using only additions and subtractions, making it ideal for resource-constrained embedded systems where multipliers are expensive or unavailable.

CIC filters are used in sigma-delta ADC interfaces, software-defined radio front-ends, and any application that must drastically reduce the sample rate of a high-frequency signal stream before further processing.

5.10.2 Mathematical Theory

5.10.2.1 Core Definitions

A CIC decimator of order \(N\) with decimation ratio \(R\) and differential delay \(M\) is defined by its \(z\)-domain transfer function:

\[H(z) = \left( \frac{1 - z^{-RM}}{1 - z^{-1}} \right)^N\]

The numerator factor \(1 - z^{-RM}\) is the \(z\)-transform of the comb (differencing) stage. The denominator \(\frac{1}{1 - z^{-1}}\) is the accumulator (integrator) stage.

5.10.2.2 Structure

The filter consists of two cascaded sections:

Integrator section (running at the high input rate \(f_s\)): \(N\) stages of first-order IIR accumulators,

\[y_k[n] = y_k[n-1] + y_{k-1}[n], \quad k = 1, \ldots, N\]

Comb section (running at the low output rate \(f_s / R\)): \(N\) stages of differencing with delay \(M\),

\[y_k[m] = y_{k-1}[m] - y_{k-1}[m - M], \quad k = 1, \ldots, N\]

5.10.2.3 DC Gain

The unnormalized DC gain of the filter is:

\[G = (R \cdot M)^N\]

All outputs are divided by \(G\) to normalize the DC gain to unity for a constant input.

5.10.2.4 Frequency Response

The magnitude response in the baseband is approximately:

\[|H(f)| = \left| \frac{\sin(\pi f M R / f_s)}{R \sin(\pi f / f_s)} \right|^N\]

This is a sinc-like response that suppresses high-frequency content before the rate change.

5.10.3 Complexity Analysis

Case Time per input sample Space Notes
Best \(O(N)\) \(O(N \cdot M)\) \(N\) integrator ops; comb only at decimation points
Average \(O(N)\) \(O(N \cdot M)\) Same
Worst \(O(N)\) \(O(N \cdot M)\) Comb adds \(N\) differencing ops at rate \(f_s/R\)

The integrator section executes \(N\) additions per input sample. The comb section executes \(N\) subtractions once every \(R\) input samples. There are no multiplications in the signal path.

5.10.4 Step-by-Step Walkthrough

Consider \(N=2\), \(R=4\), \(M=1\), input impulse \(x[0]=1\), \(x[n]=0\) for \(n>0\).

Integrator section at \(n=0,1,2,3\):

\(n\) Input Integrator 1 Integrator 2
0 1 1 1
1 0 1 2
2 0 1 3
3 0 1 4

Comb section at decimated sample \(m=0\) (triggered at \(n=3\)):

At \(m=1\) (triggered at \(n=7\)):

5.10.5 Pitfalls & Edge Cases

Integer overflow in fixed-point implementations: in fixed-point arithmetic, the integrators accumulate without bound between comb operations. Registers must be wide enough to hold \((R \cdot M)^N\) times the maximum input value. This implementation uses floating-point, which avoids this issue.

Initial transient: the filter takes several R-length blocks to settle to steady-state behavior for a constant input. DC normalization is exact only after the delay pipeline is fully flushed.

Passband droop: the sinc-shaped response causes attenuation even near DC as the input frequency increases. Compensation filters or a larger \(R\) reduce in-band droop.

Aliasing from high-order terms: if the input signal has energy above \(f_s / (2R)\), aliased components will appear at the output. A simple prefilter can reduce this.

5.10.6 Variants & Generalizations

Interpolating CIC: the comb section runs at the low rate and the integrators at the high rate, acting as an upsampler. The architecture mirrors the decimator with sections swapped.

Pruned CIC: removes multiplier-free adder stages that contribute negligibly to the response, reducing hardware at the cost of response shape.

Compensation filter: a short linear-phase FIR appended at the low rate corrects passband droop without reintroducing multipliers in the high-rate path.

Variable-rate CIC: by making \(R\) a runtime parameter, one filter structure supports multiple decimation ratios, useful in SDR front-ends.

5.10.7 Applications

5.10.8 Connections to Other Algorithms

A CIC filter of order \(N=1\), \(M=1\) is equivalent to a boxcar (rectangular window) FIR of length \(R\), identical to a Moving Average filter operating on non-overlapping blocks. The Moving Average filter in this library is the continuous-output counterpart.

Higher-order CICs approximate a Gaussian response as \(N \to \infty\), connecting them to the Gaussian filter family in theory.

The CIC is a special case of the more general Hogenauer filter structure, which can be pruned to reduce word widths at each stage.

5.11 Exponential Moving Average

5.11.1 Overview & Motivation

Sensor data in embedded systems is noisy. A full moving-average buffer costs O(N) RAM and O(N) computation per sample. The exponential moving average (EMA), also called a one-pole recursive low-pass filter or leaky integrator, achieves useful smoothing with a single state word and one multiply-accumulate per sample. It is the minimal-cost filter that tracks a slowly varying DC level while suppressing high-frequency noise.

5.11.2 Mathematical Theory

5.11.2.1 Core Definition

Given a smoothing factor \(\alpha \in (0, 1]\) and a discrete input sequence \(x[n]\), the EMA output \(y[n]\) obeys the first-order linear recurrence:

\[y[n] = \alpha \, x[n] + (1 - \alpha) \, y[n-1]\]

with \(y[-1] = y_0\) (the initial state, often zero).

5.11.2.2 Rearranged Update Form

Expanding the recurrence:

\[y[n] = y[n-1] + \alpha \bigl(x[n] - y[n-1]\bigr)\]

This form has one subtraction and one multiply-accumulate; it avoids computing \((1-\alpha)\) explicitly and keeps the error term \((x[n] - y[n-1])\) small, which is numerically preferable in fixed-point arithmetic.

5.11.2.3 Pole Location and Stability

The transfer function in the \(z\)-domain is:

\[H(z) = \frac{\alpha}{1 - (1-\alpha)\,z^{-1}}\]

The single pole is at \(z = 1 - \alpha\). For \(\alpha \in (0, 1]\) the pole lies in \([0, 1)\), which is strictly inside the unit circle, so the filter is unconditionally stable.

5.11.2.4 DC Gain

Setting \(z = 1\) (DC, \(\omega = 0\)):

\[H(1) = \frac{\alpha}{1 - (1-\alpha)} = 1\]

Unity DC gain: a constant input converges exactly to that constant.

5.11.2.5 Cutoff Frequency

Matching the EMA pole to a first-order RC low-pass filter with time constant \(\tau = 1/(2\pi f_c)\) and sample period \(T_s = 1/f_s\):

\[\alpha = \frac{T_s}{\tau + T_s} = \frac{T_s}{\frac{1}{2\pi f_c} + T_s}\]

5.11.2.6 Impulse Response

The impulse response is \(h[n] = \alpha (1-\alpha)^n \, u[n]\), which decays geometrically — hence the name “exponential” moving average.

5.11.3 Complexity Analysis

Case Time Space Notes
Best O(1) O(1) Single MAC per sample, one state word
Average O(1) O(1) Identical regardless of input
Worst O(1) O(1) No data-dependent branches on hot path

5.11.4 Step-by-Step Walkthrough

Suppose \(\alpha = 0.5\), \(y[-1] = 0\), and the input is the unit impulse \(x = [1, 0, 0, 0, \ldots]\).

\(n\) \(x[n]\) \(x[n] - y[n-1]\) \(\alpha \cdot \Delta\) \(y[n]\)
0 1 \(1 - 0 = 1\) \(0.5\) \(0.5\)
1 0 \(0 - 0.5 = -0.5\) \(-0.25\) \(0.25\)
2 0 \(0 - 0.25 = -0.25\) \(-0.125\) \(0.125\)
3 0 \(0 - 0.125 = -0.125\) \(-0.0625\) \(0.0625\)

Each output is half the previous, confirming \(h[n] = (0.5)^{n+1}\).

5.11.5 Pitfalls & Edge Cases

5.11.6 Variants & Generalizations

5.11.7 Applications

5.11.8 Connections to Other Algorithms

5.12 Finite Impulse Response (FIR) Filter

5.12.1 Overview & Motivation

A Finite Impulse Response (FIR) filter is a discrete-time filter whose impulse response has a finite duration. FIR filters are inherently stable, have linear phase properties (when symmetric), and are straightforward to design and implement. They are the default choice for applications requiring guaranteed stability and predictable phase behavior.

5.12.2 Mathematical Theory

5.12.2.1 Core Definition

A causal FIR filter of order \(N-1\) (with \(N\) taps) computes the output as a weighted sum of the current and past inputs (discrete convolution):

\[y[n] = \sum_{i=0}^{N-1} b_i \cdot x[n-i]\]

where: - \(b_i\) are the filter coefficients (tap weights) - \(x[n-i]\) are the current and \(N-1\) most recent input samples - \(N\) is the number of taps (filter length)

5.12.2.2 Transfer Function

In the z-domain, the FIR filter transfer function is a polynomial in \(z^{-1}\):

\[H(z) = \sum_{i=0}^{N-1} b_i z^{-i}\]

Since the denominator is unity, FIR filters have all poles at the origin and are always stable.

5.12.2.3 Frequency Response

Evaluating on the unit circle (\(z = e^{j\omega}\)):

\[H(e^{j\omega}) = \sum_{i=0}^{N-1} b_i e^{-j\omega i}\]

5.12.3 Complexity Analysis

Case Time Space Notes
Best \(O(N)\) \(O(N)\) Disabled (passthrough)
Average \(O(N)\) \(O(N)\) Convolution sum
Worst \(O(N)\) \(O(N)\) Same — no data-dependent branching

Time is per sample. Space is for the coefficient and delay line buffers.

5.12.4 Step-by-Step Walkthrough

Given a 3-tap FIR filter with coefficients \(b = [0.25, 0.5, 0.25]\) and input sequence \(x = [1.0, 2.0, 3.0, 4.0]\):

\(n\) \(x[n]\) \(x[n-1]\) \(x[n-2]\) \(y[n] = 0.25 x[n] + 0.5 x[n-1] + 0.25 x[n-2]\)
0 1.0 0.0 0.0 0.25
1 2.0 1.0 0.0 1.0
2 3.0 2.0 1.0 2.0
3 4.0 3.0 2.0 3.0

5.12.5 Pitfalls & Edge Cases

5.12.6 Variants & Generalizations

5.12.7 Applications

5.12.8 Connections to Other Algorithms

5.13 Infinite Impulse Response (IIR) Filter

5.13.1 Overview & Motivation

An Infinite Impulse Response (IIR) filter uses feedback from its own past outputs to achieve a desired frequency response. IIR filters can match the selectivity of high-order FIR filters using far fewer coefficients, making them efficient for resource-constrained embedded systems. The trade-off is potential instability and nonlinear phase.

5.13.2 Mathematical Theory

5.13.2.1 Core Definition

A causal IIR filter with \(P\) feedforward coefficients and \(Q\) feedback coefficients computes:

\[y[n] = \sum_{i=0}^{P-1} b_i \cdot x[n-i] + \sum_{i=0}^{Q-1} a_i \cdot y[n-i]\]

where: - \(b_i\) are the feedforward (numerator) coefficients - \(a_i\) are the feedback (denominator) coefficients - \(x[n-i]\) are past input samples - \(y[n-i]\) are past output samples

Sign convention: The implementation uses addition for the feedback term. This means the feedback coefficients \(a_i\) must be provided with the appropriate sign (negated relative to the standard form \(y[n] = \sum b_i x[n-i] - \sum a_i y[n-i]\)).

5.13.2.2 Transfer Function

\[H(z) = \frac{\sum_{i=0}^{P-1} b_i z^{-i}}{1 - \sum_{i=0}^{Q-1} a_i z^{-i}}\]

5.13.2.3 Stability Criterion

The filter is stable if and only if all poles of \(H(z)\) lie strictly inside the unit circle (\(|z| < 1\)). This must be verified at design time.

5.13.3 Complexity Analysis

Case Time Space Notes
Best \(O(P+Q)\) \(O(P+Q)\) Disabled (passthrough)
Average \(O(P+Q)\) \(O(P+Q)\) Feedforward + feedback sums
Worst \(O(P+Q)\) \(O(P+Q)\) Same — sequential, no branching

Time is per sample. Space stores coefficient and delay line buffers.

5.13.4 Step-by-Step Walkthrough

Given a first-order IIR (1 feedforward, 1 feedback) with \(b_0 = 0.5\), \(a_0 = 0.3\) and input \(x = [1.0, 0.0, 0.0]\):

\(n\) \(x[n]\) \(y[n-1]\) \(y[n] = 0.5 \cdot x[n] + 0.3 \cdot y[n-1]\)
0 1.0 0.0 0.5
1 0.0 0.5 0.15
2 0.0 0.15 0.045

The output decays exponentially — characteristic of IIR filters with \(|a_0| < 1\).

5.13.5 Pitfalls & Edge Cases

5.13.6 Variants & Generalizations

5.13.7 Applications

5.13.8 Connections to Other Algorithms

5.14 IIR Filter Design (Butterworth / Chebyshev-I + Bilinear Transform)

5.14.1 Overview & Motivation

Embedded systems often require adaptive filtering — adjustable cutoff frequencies, runtime-selected filter orders, or field-reconfigurable equalizer bands — without a host toolchain to bake in coefficients. This algorithm computes second-order-section (SOS) biquad coefficients for Butterworth and Chebyshev Type-I IIR filters entirely on the device, using only fixed-point-safe arithmetic and bounded arrays.

The design pipeline converts a normalized analog prototype (unit-cutoff Butterworth or Chebyshev ellipse of poles) to a digital filter at any requested cutoff, applying the bilinear transform with pre-warping to guarantee that the digital cut-off frequency lands exactly on the requested value.

5.14.2 Mathematical Theory

5.14.2.1 Analog Prototype Poles

5.14.2.1.1 Butterworth

The \(N\)th-order Butterworth prototype places poles uniformly on the left half of the unit circle in the \(s\)-plane:

\[s_k = e^{j\pi(2k + N + 1)/(2N)}, \quad k = 0, 1, \ldots, N-1\]

All poles have magnitude 1 and lie strictly in the open left half-plane, guaranteeing stability and a maximally flat (Butterworth) magnitude response.

5.14.2.1.2 Chebyshev Type-I

Chebyshev-I poles lie on an ellipse in the \(s\)-plane. Given ripple \(R_p\) dB:

\[\varepsilon = \sqrt{10^{R_p/10} - 1}\]

\[\alpha = \frac{1}{N}\sinh^{-1}\!\left(\frac{1}{\varepsilon}\right)\]

\[s_k = -\sinh(\alpha)\sin\theta_k + j\cosh(\alpha)\cos\theta_k, \quad \theta_k = \frac{\pi(2k+1)}{2N}\]

This trades the maximally-flat passband of Butterworth for equiripple passband with steeper transition-band roll-off at the same order.

5.14.2.2 Frequency Pre-warping

The bilinear transform compresses the entire analog frequency axis \((-\infty, +\infty)\) onto the digital unit circle non-linearly. To ensure the digital cutoff \(f_c\) maps exactly to the correct position after this compression, the analog prototype cutoff \(\omega_c\) is pre-warped:

\[\omega_c = 2 f_s \tan\!\left(\frac{\pi f_c}{f_s}\right)\]

5.14.2.3 Analog Frequency Transforms

A normalized (LP cutoff = 1 rad/s) prototype is frequency-transformed before bilinear mapping:

5.14.2.4 Bilinear Transform

Each analog root \(s_k\) is mapped to the \(z\)-plane:

\[z_k = \frac{1 + s_k / (2 f_s)}{1 - s_k / (2 f_s)}\]

This is an exact Möbius transformation that maps the left \(s\)-half-plane to the interior of the unit circle, preserving stability.

5.14.2.5 Second-Order Sections

Complex-conjugate pole pairs \((z_k, z_k^*)\) are combined into real-coefficient biquad sections:

\[H_i(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}}\]

with \(a_1 = -2\,\mathrm{Re}(z_k)\) and \(a_2 = |z_k|^2\). A single real pole yields a first-order section padded as a biquad with \(b_2 = a_2 = 0\).

5.14.3 Complexity Analysis

Case Time Space Notes
Design \(O(N)\) \(O(N)\) static One-off; no per-sample cost
Filter \(O(N/2)\) \(O(N/2)\) state Delegated to BiquadCascade

All arrays are bounded by the compile-time MaxOrder template parameter; no heap is used.

5.14.4 Step-by-Step Walkthrough

Example: 2nd-order Butterworth LP, \(f_c = 100\) Hz, \(f_s = 1000\) Hz.

  1. Prototype poles (\(N=2\)): \(s_{0,1} = e^{j\cdot 3\pi/4}, e^{j\cdot 5\pi/4} = \frac{-1 \pm j}{\sqrt{2}}\)

  2. Pre-warp: \(\omega_c = 2 \times 1000 \times \tan(\pi \times 100 / 1000) \approx 726.5\) rad/s

  3. Scale LP: \(s_{0,1} \leftarrow s_{0,1} \times \omega_c\)

  4. Bilinear map: each scaled pole \(\to z_{0,1}\) inside the unit circle.

  5. SOS: conjugate pair forms one biquad with real coefficients \(b_0, b_1, b_2, a_1, a_2\).

  6. Gain normalization: DC gain adjusted so \(|H(0)| = 1\).

5.14.5 Pitfalls & Edge Cases

5.14.6 Variants & Generalizations

5.14.7 Applications

5.14.8 Connections to Other Algorithms

5.15 Median Filter (Sliding Window)

5.15.1 Overview & Motivation

Impulsive noise — EMI clicks, dropped ADC bits, salt-and-pepper sensor glitches — corrupts embedded sensor streams in a way that linear averaging amplifies rather than rejects. A single outlier sample at 100× the signal level drags a moving-average output for the entire window duration. The median filter eliminates isolated spikes completely, using only comparisons and no arithmetic on the signal values themselves, making it an ideal non-linear pre-processor for noisy ADC and sensor streams.

5.15.2 Mathematical Theory

5.15.2.1 Core Definition

Let \(x[n]\) be the input sequence and \(N\) the window length. Define the sorted window snapshot as \(x_{(1)} \le x_{(2)} \le \cdots \le x_{(N)}\) (order statistics). The median filter output is:

\[y[n] = x_{(\lceil N/2 \rceil)}\]

For odd \(N\) this is the unique middle order statistic. For even \(N\) one common convention takes the average of the two central statistics, but this introduces arithmetic on the signal and the associated round-off.

5.15.2.2 Impulse Rejection Threshold

A spike is suppressed if and only if it does not form a strict majority of the window, i.e., if fewer than \(\lceil N/2 \rceil\) samples share the outlier level. An isolated impulse occupies exactly one slot; it is always rejected for \(N \ge 3\).

5.15.2.3 Step-Edge Preservation

A step transition from level \(a\) to level \(b\) causes the median to switch from \(a\) to \(b\) after exactly \(\lfloor N/2 \rfloor\) samples of delay — without overshoot, Gibbs ringing, or smearing. This is the fundamental advantage over linear filters: the nonlinearity of the sort operation preserves edges while rejecting impulses.

5.15.2.4 Lag

The causal sliding-window median introduces a latency of \(\lfloor (N-1)/2 \rfloor\) samples, which is the same group delay as a symmetric FIR of the same length. Unlike a linear FIR, however, the delay is signal-dependent near edges.

5.15.3 Algorithm

5.15.3.1 Insertion-Sort Approach

For small, fixed window sizes \(N\) (typically 3–9 in embedded use), copying the ring buffer into a scratch array and applying insertion sort is cache-friendly and avoids heap allocation. The comparison count is at most \(N(N-1)/2\) — fully bounded by the compile-time constant \(N\).

Insertion sort on the scratch copy:

\[\text{for } i = 1, \ldots, N-1: \text{ shift } \text{scratch}[0 \ldots i-1] \text{ right until sorted position for scratch}[i]\]

The median is then \(\text{scratch}[\lfloor N/2 \rfloor]\).

5.15.3.2 Huang Histogram Variant

Huang, Yang, and Tang (1979) describe an \(O(1)\)-per-sample update based on a running histogram: the departing sample decrements its histogram bin and the arriving sample increments its bin, then the median bin is walked left or right by one position. This reduces per-sample work from \(O(N)\) to \(O(1)\) but requires a histogram array indexed over the signal alphabet — impractical for floating-point streams on embedded targets.

5.15.4 Complexity Analysis

Variant Time per sample Space Notes
Insertion sort (used) \(O(N)\) \(O(N)\) Optimal for small fixed \(N\); no heap
Huang histogram \(O(1)\) \(O(K)\) \(K\) = alphabet size; integer signals only

For \(N \le 9\) the insertion-sort inner loop is unrolled by the compiler and runs in a handful of compare-and-swap operations — faster in practice than histogram maintenance.

5.15.5 Step-by-Step Walkthrough

Input: \(x = [0.1, 0.1, 0.9, 0.1, 0.1]\), window size \(N = 3\), initial window pre-seeded with \(0.0\).

\(n\) \(x[n]\) Window (ring buffer) Sorted scratch \(y[n]\)
0 0.1 [0.0, 0.0, 0.1] [0.0, 0.0, 0.1] 0.0
1 0.1 [0.0, 0.1, 0.1] [0.0, 0.1, 0.1] 0.1
2 0.9 [0.1, 0.1, 0.9] [0.1, 0.1, 0.9] 0.1
3 0.1 [0.1, 0.9, 0.1] [0.1, 0.1, 0.9] 0.1
4 0.1 [0.9, 0.1, 0.1] [0.1, 0.1, 0.9] 0.1

The bold value is the median at index \(\lfloor 3/2 \rfloor = 1\). The spike at \(n = 2\) (\(x = 0.9\)) appears in only one of the three window slots at any given time, so it is never the majority and never reaches the output.

5.15.6 Pitfalls & Edge Cases

5.15.7 Variants & Generalizations

5.15.8 Applications

5.15.9 Connections to Other Algorithms

5.16 Moving Average (Running-Sum Boxcar)

5.16.1 Overview & Motivation

Sensor streams in embedded systems are corrupted by zero-mean white noise. The simplest way to attenuate that noise is to average consecutive samples. The moving average does this with a sliding window of length N, treating every sample inside the window equally. Its critical property for embedded use is that the recursive running-sum form requires exactly one addition, one subtraction, and one multiply per output sample — constant work regardless of how large the window is.

5.16.2 Mathematical Theory

5.16.2.1 Core Definitions

Let \(x[n]\) be the input sequence. The length-\(N\) boxcar output is:

\[y[n] = \frac{1}{N} \sum_{k=0}^{N-1} x[n-k]\]

5.16.2.2 Recursive Form

Expanding \(y[n]\) in terms of \(y[n-1]\):

\[y[n] = y[n-1] + \frac{x[n] - x[n-N]}{N}\]

Equivalently, maintain a running sum \(S[n] = \sum_{k=0}^{N-1} x[n-k]\):

\[S[n] = S[n-1] + x[n] - x[n-N], \qquad y[n] = \frac{S[n]}{N}\]

5.16.2.3 Frequency Response

The discrete-time transfer function is:

\[H(z) = \frac{1}{N} \sum_{k=0}^{N-1} z^{-k} = \frac{1}{N} \cdot \frac{1 - z^{-N}}{1 - z^{-1}}\]

The magnitude response is a \(\mathrm{sinc}\)-shaped envelope:

\[|H(e^{j\omega})| = \frac{1}{N} \left| \frac{\sin(N\omega/2)}{\sin(\omega/2)} \right|\]

DC gain is exactly 1. Group delay is constant at \((N-1)/2\) samples — the filter is linear phase.

5.16.3 Complexity Analysis

Case Time Space Notes
All \(O(1)\) \(O(N)\) One add, one sub, one multiply

The \(O(1)\) time cost is the key advantage over the naive \(O(N)\) direct-form sum.

5.16.4 Step-by-Step Walkthrough

Input: unit step \(x[n] = 1\) for \(n \ge 0\), \(N = 4\), initial state all zeros.

\(n\) \(x[n]\) \(x[n-4]\) \(S[n]\) \(y[n]\)
0 1 0 1 0.25
1 1 0 2 0.50
2 1 0 3 0.75
3 1 0 4 1.00
4 1 1 4 1.00

The output ramps up linearly and then holds at 1 once the window is fully filled.

5.16.5 Pitfalls & Edge Cases

5.16.6 Variants & Generalizations

5.16.7 Applications

5.16.8 Connections to Other Algorithms

5.17 Notch / Comb Filter

5.17.1 Overview & Motivation

Narrow-band interference — mains hum at 50/60 Hz and its harmonics — contaminates bio-signal acquisition (ECG, EEG, EMG) and precision instrumentation. A notch filter surgically eliminates a single frequency while leaving the rest of the spectrum intact. A comb filter extends this idea to an entire harmonic series using a single delay line, making it far more efficient than cascading individual notches when the interference is periodic.

5.17.2 Mathematical Theory

5.17.2.1 Notch Filter (RBJ Biquad)

The continuous-time transfer function for a band-reject filter with centre frequency \(f_0\) and quality factor \(Q\) is:

\[H(s) = \frac{s^2 + \omega_0^2}{s^2 + \frac{\omega_0}{Q}s + \omega_0^2}\]

Applying the bilinear transform with \(\omega_0 = 2\pi f_0 / f_s\), the discrete-time biquad coefficients (Bristow-Johnson “Cookbook” notch) are:

\[\alpha = \frac{\sin(\omega_0)}{2Q}, \quad c_\omega = \cos(\omega_0)\]

\[b_0 = b_2 = \frac{1}{1+\alpha}, \quad b_1 = \frac{-2c_\omega}{1+\alpha}\]

\[a_1 = \frac{-2c_\omega}{1+\alpha}, \quad a_2 = \frac{1-\alpha}{1+\alpha}\]

The zeros lie exactly on the unit circle at \(e^{\pm j\omega_0}\), guaranteeing infinite attenuation at \(f_0\) in exact arithmetic.

Transposed Direct Form II (TDF-II) realises this with two state variables \(z_1, z_2\):

\[y[n] = b_0 x[n] + z_1[n-1]\] \[z_1[n] = b_1 x[n] - a_1 y[n] + z_2[n-1]\] \[z_2[n] = b_2 x[n] - a_2 y[n]\]

5.17.2.2 Comb Filter

A comb filter exploits the identity: subtracting (or adding) a signal delayed by \(D\) samples creates periodic nulls (or peaks) spaced every \(f_s / D\) Hz:

Feedforward (FIR) comb:

\[y[n] = x[n] - g \cdot x[n-D]\]

Zeros at \(\omega = 2\pi k / D\) for \(k = 0, 1, \ldots, D-1\). The system is always stable.

Feedback (IIR) comb:

\[y[n] = x[n] + g \cdot y[n-D]\]

Poles at the same locations. The system is stable if and only if \(|g| < 1\).

5.17.3 Complexity Analysis

Case Time Space Notes
Notch \(O(1)\) \(O(1)\) Fixed biquad; 2 multiplies/adds
Comb \(O(1)\) \(O(D)\) Single circular-buffer read/write

5.17.4 Step-by-Step Walkthrough

Notch example — \(f_0 = 50\) Hz, \(f_s = 1000\) Hz, \(Q = 10\):

  1. \(\omega_0 = 2\pi \cdot 0.05 \approx 0.3142\)
  2. \(c_\omega = \cos(0.3142) \approx 0.9511\), \(\alpha = \sin(0.3142)/(20) \approx 0.01564\)
  3. \(a_0 = 1.01564\); divide all coefficients by \(a_0\)
  4. \(b_0 = b_2 \approx 0.9846\), \(b_1 = a_1 \approx -1.8730\), \(a_2 \approx 0.9693\)
  5. At each sample: compute \(y = b_0 x + z_1\), then update \(z_1, z_2\).

Comb example — \(D = 4\), \(g = 1\) (FIR):

Impulse response: \([1, 0, 0, 0, -1, 0, 0, 0, 0, \ldots]\) — zeros at 0, fs/4, fs/2, 3fs/4.

5.17.5 Pitfalls & Edge Cases

5.17.6 Variants & Generalizations

5.17.7 Applications

5.17.8 Connections to Other Algorithms

5.18 Savitzky-Golay Filter

5.18.1 Overview & Motivation

Many sensor signals — ECG, PPG, spectroscopy, vibration — carry narrow peaks whose height, width, and area carry physical meaning. A boxcar moving average suppresses noise by treating the signal as locally constant, but this assumption flattens and widens every peak. The Savitzky-Golay filter replaces that assumption with a stronger one: the signal is locally a low-degree polynomial. Fitting that polynomial to each window by least squares and evaluating it at the window centre preserves peak shape far better while still attenuating high-frequency noise.

5.18.2 Mathematical Theory

5.18.2.1 Core Definitions

Let \(x[n]\) be the input sequence. Define a symmetric window of length \(W\) (odd) centred at sample \(n\), with half-width \(h = (W-1)/2\). The window offsets are \(j = -h, -h+1, \ldots, h\).

Fit a polynomial \(p(j) = \sum_{d=0}^{P} a_d \, j^d\) of degree \(P < W\) to the window samples \(\{x[n+j]\}\) in the least-squares sense. The output for the \(D\)-th derivative at the centre is:

\[y[n] = D! \, a_D\]

5.18.2.2 Vandermonde and Normal Equations

Assemble the Vandermonde matrix \(A \in \mathbb{R}^{W \times (P+1)}\) with \(A_{k,d} = j_k^{\,d}\), \(j_k = k - h\). The least-squares coefficients solve:

\[(A^\top A)\,\mathbf{a} = A^\top \mathbf{x}\]

The projection matrix \(C = (A^\top A)^{-1} A^\top\) is \((P+1) \times W\). Row \(D\) of \(C\), multiplied by \(D!\), gives the FIR convolution kernel \(\mathbf{c} \in \mathbb{R}^W\):

\[y[n] = \sum_{k=0}^{W-1} c_k \, x[n - h + k]\]

Because \(A\) depends only on \(W\) and \(P\), the kernel \(\mathbf{c}\) is fixed and computed once.

5.18.2.3 Smoothing Kernel (\(D = 0\))

The centre row (\(D = 0\)) reproduces a polynomial fit evaluated at \(j = 0\). For the standard 5-point, quadratic (\(P = 2\)) case the kernel is:

\[\mathbf{c} = \frac{1}{35}[-3, 12, 17, 12, -3]\]

DC gain is exactly 1; the kernel is symmetric, so the filter has linear phase with group delay \(h = (W-1)/2\) samples.

5.18.2.4 Derivative Kernels (\(D \geq 1\))

Row \(D\) of \(C\) times \(D!\) yields the \(D\)-th smoothed derivative kernel. For \(D = 1\) the output approximates \(\mathrm{d}x/\mathrm{d}n\) (in samples); multiply by \(1/T_s^D\) to convert to physical units.

5.18.3 Complexity Analysis

Case Time Space Notes
All \(O(W)\) \(O(W)\) One multiply-accumulate per tap per sample
Init \(O(P^3)\) \(O(P^2 W)\) Constexpr kernel precomputed at compile time

The runtime cost is \(W\) multiply-adds — identical to a length-\(W\) FIR filter. The \(O(P^3)\) Gaussian elimination occurs entirely at compile time with zero runtime overhead.

5.18.4 Step-by-Step Walkthrough

Window \(W = 5\), polynomial order \(P = 2\), derivative \(D = 0\), half-width \(h = 2\):

  1. Offsets \(j\): \(-2, -1, 0, 1, 2\).
  2. Build \(A\) (5 rows, 3 cols): column 0 all ones; column 1 the offsets; column 2 the squares.
  3. Compute \(A^\top A\) (3 × 3 symmetric matrix).
  4. Invert by Gaussian elimination to get \((A^\top A)^{-1}\).
  5. Project: \(C = (A^\top A)^{-1} A^\top\) (3 × 5 matrix).
  6. Row 0 of \(C\): \([-3, 12, 17, 12, -3] / 35\).
  7. Each output is \(c^\top\) times the 5-sample window.

Input \([0, 0, 0, 0, 1]\): output = \(-3/35 \approx -0.086\) (transient). After the delay line fills with a constant signal \(v\), output = \(v\) exactly.

5.18.5 Pitfalls & Edge Cases

5.18.6 Variants & Generalizations

5.18.7 Applications

5.18.8 Connections to Other Algorithms

6 Optimization

6.1 Bayesian Optimization

6.1.1 Overview & Motivation

Bayesian Optimization (BO) is a gradient-free, sample-efficient strategy for optimizing black-box functions that are expensive to evaluate. Unlike gradient-based methods (gradient descent, Adam), BO does not require access to derivatives. It is ideal for calibrating numerical controllers, tuning hyperparameters, or any optimization task where each function evaluation is costly — for example, running a full closed-loop simulation.

BO maintains a probabilistic surrogate model (typically a Gaussian Process) of the objective function. At each iteration, it uses an acquisition function to select the next query point that balances exploration (uncertain regions) with exploitation (promising regions). After evaluating the true objective at that point, the surrogate is updated and the process repeats.

6.1.2 Mathematical Theory

6.1.2.1 Gaussian Process Surrogate

A Gaussian Process (GP) defines a distribution over functions:

\[f \sim \mathcal{GP}(m(\mathbf{x}), k(\mathbf{x}, \mathbf{x}'))\]

With zero mean \(m = 0\) and squared-exponential (RBF) kernel:

\[k(\mathbf{x}, \mathbf{x}') = \sigma_f^2 \exp\!\left(-\frac{\|\mathbf{x} - \mathbf{x}'\|^2}{2\ell^2}\right)\]

where \(\ell\) is the length-scale and \(\sigma_f^2\) is the signal variance.

Given \(n\) observations \(\{(\mathbf{x}_i, y_i)\}\), the GP posterior at a new point \(\mathbf{x}_*\) is Gaussian:

\[p(f_* | \mathbf{X}, \mathbf{y}, \mathbf{x}_*) = \mathcal{N}(\mu_*, \sigma_*^2)\]

\[\mu_* = \mathbf{k}_*^T (\mathbf{K} + \sigma_n^2 \mathbf{I})^{-1} \mathbf{y}\]

\[\sigma_*^2 = k_{**} - \mathbf{k}_*^T (\mathbf{K} + \sigma_n^2 \mathbf{I})^{-1} \mathbf{k}_*\]

where: - \(\mathbf{K}_{ij} = k(\mathbf{x}_i, \mathbf{x}_j)\) is the \(n \times n\) kernel matrix - \(\mathbf{k}_{*,i} = k(\mathbf{x}_*, \mathbf{x}_i)\) is the cross-kernel vector - \(\sigma_n^2\) is the observation noise variance

6.1.2.2 Expected Improvement Acquisition Function

For minimization, the Expected Improvement (EI) at \(\mathbf{x}\) given best observed value \(f^* = \min_i y_i\) is:

\[\text{EI}(\mathbf{x}) = \mathbb{E}[\max(f^* - f(\mathbf{x}), 0)]\]

For a GP surrogate this evaluates analytically:

\[\text{EI}(\mathbf{x}) = (f^* - \mu_*)\,\Phi(z) + \sigma_*\,\phi(z), \quad z = \frac{f^* - \mu_*}{\sigma_*}\]

where \(\Phi\) is the standard normal CDF and \(\phi\) is the PDF.

6.1.2.3 Fixed-Size GP Implementation

To avoid dynamic memory allocation, the kernel matrix is pre-allocated at compile-time with maximum capacity \(M\). When only \(n < M\) observations are present, the inactive rows and columns are padded with an identity block, preserving positive-definiteness and allowing Gaussian elimination with the full \(M \times M\) system. The resulting alpha vector \(\boldsymbol{\alpha} = (\mathbf{K} + \sigma_n^2 \mathbf{I})^{-1} \mathbf{y}\) has zeros in the inactive entries.

6.1.3 Complexity Analysis

Case Time per iteration Space Notes
Best \(O(M^3)\) \(O(M^2 + NP)\) Dominated by GP solve
Average \(O(M^3 + C \cdot P)\) \(O(M^2 + NP)\) \(C\) candidates, \(P\) parameters
Worst \(O(M^3 + C \cdot P)\) \(O(M^2 + NP)\) Same as average

Where \(M\) = maximum observations, \(C\) = candidate count, \(P\) = parameter count, \(N\) = current number of observations.

All memory is pre-allocated at compile-time on the stack — no dynamic allocation required.

6.1.4 Step-by-Step Walkthrough

Example: minimize \(f(x) = (x - 0.5)^2\) on \(x \in [0, 1]\).

  1. Initialization: draw 2 random points, e.g. \(x_1 = 0.12\), \(x_2 = 0.87\).
  2. Evaluate: \(y_1 = 0.1444\), \(y_2 = 0.1369\). Best so far \(f^* = 0.1369\).
  3. Build GP: compute \(2 \times 2\) kernel matrix \(\mathbf{K}\) with hyperparameters \(\ell=1\), \(\sigma_f=1\), \(\sigma_n=0.01\).
  4. Solve: \(\boldsymbol{\alpha} = (\mathbf{K} + \sigma_n^2 \mathbf{I})^{-1} \mathbf{y}\).
  5. Maximize EI: sample 100 candidates uniformly, compute \(\text{EI}(x_k)\) for each, select \(x_3 = \arg\max \text{EI}\).
  6. Evaluate: \(y_3 = f(x_3)\). Update GP. Repeat.
  7. Convergence (after 20 iterations): best point converges near \(x = 0.5\).

6.1.5 Pitfalls & Edge Cases

6.1.6 Variants & Generalizations

6.1.7 Applications

6.1.8 Connections to Other Algorithms

6.1.9 Numerical Properties

6.2 Gradient Descent Optimizer

6.2.1 Overview & Motivation

An optimizer adjusts a model’s parameter vector \(\theta \in \mathbb{R}^P\) to minimize a loss function \(\mathcal{L}(\theta)\). The simplest and most fundamental strategy is gradient descent: repeatedly step in the direction of steepest descent:

\[\theta_{t+1} = \theta_t - \eta \, \nabla_\theta \mathcal{L}(\theta_t)\]

where \(\eta\) is the learning rate — the single most important hyper-parameter in neural network training.

This library provides a batch gradient descent optimizer with a fixed learning rate and maximum iteration count, suitable for small embedded models where training data fits in memory and simplicity is paramount.

6.2.2 Mathematical Theory

6.2.2.1 The Gradient

The gradient \(\nabla_\theta \mathcal{L}\) is obtained via back-propagation. Each component \(\frac{\partial \mathcal{L}}{\partial \theta_i}\) tells how much the loss changes when \(\theta_i\) is perturbed by a small amount. The gradient points uphill; subtracting it moves downhill.

6.2.2.2 Convergence Conditions

For a convex loss with Lipschitz-continuous gradient (\(L\)-smooth):

\[\mathcal{L}(\theta_T) - \mathcal{L}(\theta^*) \le \frac{\|\theta_0 - \theta^*\|^2}{2 \eta T}\]

provided \(\eta < \frac{1}{L}\). This gives a convergence rate of \(O(1/T)\).

For non-convex losses (typical in neural networks), gradient descent converges to a stationary point (\(\|\nabla \mathcal{L}\| \approx 0\)) which may be a local minimum or saddle point — not necessarily the global minimum.

6.2.2.3 Learning Rate Sensitivity

\(\eta\) Behavior
Too small (\(\eta \ll 1/L\)) Slow convergence, many iterations wasted
Optimal (\(\eta \approx 1/L\)) Fastest reliable convergence
Too large (\(\eta > 2/L\)) Oscillation and divergence
graph LR
    subgraph "Learning Rate Effect"
        A["η too small<br/>crawls to minimum"] 
        B["η just right<br/>smooth convergence"]
        C["η too large<br/>oscillates / diverges"]
    end

6.2.3 Complexity Analysis

Operation Time Space
One gradient evaluation \(O(P)\) per parameter \(O(P)\) gradient vector
One parameter update \(O(P)\) In-place
Total training (T iterations) \(O(T \cdot P)\) \(O(P)\) working memory

Memory overhead is minimal: only the current parameter vector, the gradient vector, and the optimizer result. No momentum buffers or second-moment estimates.

6.2.4 Step-by-Step Walkthrough

Setup: 2 parameters, \(\theta_0 = [2.0, -1.0]^T\), \(\eta = 0.1\), loss \(\mathcal{L}(\theta) = \theta_1^2 + \theta_2^2\) (quadratic bowl).

Iteration 1:

Step Computation Result
Gradient \(\nabla \mathcal{L} = [2\theta_1, 2\theta_2]\) \([4.0, -2.0]\)
Update \(\theta_1 = \theta_0 - 0.1 \cdot \nabla\mathcal{L}\) \([1.6, -0.8]\)
Loss \(\mathcal{L}(\theta_1) = 1.6^2 + 0.8^2\) \(3.20\)

Iteration 2:

Step Computation Result
Gradient \([3.2, -1.6]\)
Update \([1.28, -0.64]\)
Loss \(2.048\)

Pattern: Each iteration multiplies the loss by \((1 - 2\eta)^2 = 0.64\). After 20 iterations, \(\mathcal{L} \approx 0.0003\).

Convergence trace:

graph LR
    I0["θ₀ = (2, -1)<br/>ℒ = 5.0"] --> I1["θ₁ = (1.6, -0.8)<br/>ℒ = 3.2"]
    I1 --> I2["θ₂ = (1.28, -0.64)<br/>ℒ = 2.05"]
    I2 --> Idots["⋯"]
    Idots --> I20["θ₂₀ ≈ (0.01, -0.005)<br/>ℒ ≈ 0.0003"]

6.2.5 Pitfalls & Edge Cases

6.2.6 Variants & Generalizations

Variant Key Difference
SGD (Stochastic Gradient Descent) Uses a random mini-batch per iteration; noise helps escape local minima
SGD with Momentum Accumulates a velocity: \(v_{t+1} = \mu v_t - \eta \nabla\mathcal{L}\); smooths out oscillations
Nesterov Momentum Evaluates gradient at the lookahead position \(\theta + \mu v\); faster convergence
AdaGrad Per-parameter adaptive learning rate based on accumulated squared gradients
RMSProp Exponentially decaying average of squared gradients; handles non-stationary objectives
Adam Combines momentum and RMSProp with bias correction; most popular adaptive method

6.2.7 Applications

6.2.8 Connections to Other Algorithms

graph TD
    Opt["Optimizer<br/>(Gradient Descent)"]
    Loss["Loss Functions"]
    Model["Model"]
    Reg["Regularization"]
    LR["Linear Regression"]

    Loss -->|"∇ℒ"| Opt
    Model -->|"θ, ∇θ"| Opt
    Reg -->|"penalty gradient"| Loss
    Opt -.->|"analytical solution at η→∞, 1 step"| LR
Component Relationship
Loss Functions Provides the Cost() and Gradient() the optimizer calls each iteration
Model Passes initial parameters to the optimizer and receives optimized parameters back
Regularization Adds a penalty gradient to \(\nabla\mathcal{L}\), biasing the optimizer toward simpler models
Linear Regression For MSE on a linear model, gradient descent converges to the same solution as the normal equation

6.3 Stateful Step Optimisers (SGD / Adam)

6.3.1 Overview & Motivation

On-device neural network training and online system identification require an optimiser that accepts one externally computed gradient per call and maintains its own state between calls. Unlike batch optimisers that own the full objective, a step optimiser separates the gradient source from the update rule, enabling training loops where back-propagation runs elsewhere.

Two industry-standard rules are provided: Stochastic Gradient Descent (SGD) with optional momentum or Nesterov lookahead, and the Adam (Adaptive Moment Estimation) optimiser with bias correction.

6.3.2 Mathematical Theory

6.3.2.1 SGD

Vanilla SGD applies the raw gradient:

\[\theta_{t+1} = \theta_t - \eta \, g_t\]

where \(g_t = \nabla_\theta \mathcal{L}(\theta_t)\) and \(\eta\) is the learning rate.

Momentum accumulates a velocity \(v\), smoothing oscillations and accelerating progress in low-curvature directions:

\[v_{t+1} = \beta \, v_t + g_t, \qquad \theta_{t+1} = \theta_t - \eta \, v_{t+1}\]

with \(\beta \in [0, 1)\) the momentum coefficient (typically \(0.9\)).

Nesterov momentum incorporates a lookahead correction, replacing the plain velocity step with:

\[\theta_{t+1} = \theta_t - \eta \, \bigl(g_t + \beta \, v_{t+1}\bigr)\]

This form evaluates the effective update at the anticipated next position, yielding faster convergence on smooth convex objectives.

6.3.2.2 Adam

Adam maintains exponential moving averages of the gradient (first moment \(m\)) and the squared gradient (second moment \(v\)):

\[m_{t+1} = \beta_1 \, m_t + (1 - \beta_1) \, g_t\]

\[v_{t+1} = \beta_2 \, v_t + (1 - \beta_2) \, g_t^2 \quad (\text{element-wise})\]

Both estimates are biased toward zero at initialisation. Bias correction removes this bias:

\[\hat{m}_{t+1} = \frac{m_{t+1}}{1 - \beta_1^{t+1}}, \qquad \hat{v}_{t+1} = \frac{v_{t+1}}{1 - \beta_2^{t+1}}\]

The parameter update normalises the corrected first moment by the square root of the corrected second moment, providing a per-parameter adaptive step size:

\[\theta_{t+1} = \theta_t - \eta \, \frac{\hat{m}_{t+1}}{\sqrt{\hat{v}_{t+1}} + \varepsilon}\]

Typical hyper-parameters: \(\eta = 10^{-3}\), \(\beta_1 = 0.9\), \(\beta_2 = 0.999\), \(\varepsilon = 10^{-8}\).

6.3.3 Complexity Analysis

Algorithm Time per step Extra state (floats) Notes
SGD \(O(N)\) \(N\) One velocity vector (equals the gradient when momentum is 0)
Adam \(O(N)\) \(2N + 2\) Two moment vectors plus the running powers \(\beta_1^t\), \(\beta_2^t\)

\(N\) is the number of parameters. All operations are in-place; no heap allocation is required.

6.3.4 Step-by-Step Walkthrough

SGD with momentum on \(\mathcal{L}(\theta) = \frac{1}{2}\|\theta\|^2\), \(N=1\), \(\theta_0 = 1\), \(\eta = 0.1\), \(\beta = 0.9\):

\(t\) \(g_t = \theta_t\) \(v_t = 0.9 v_{t-1} + g_t\) \(\theta_{t+1} = \theta_t - 0.1 v_t\)
1 1.000 1.000 0.900
2 0.900 1.800 0.720
3 0.720 2.340 0.486

Adam on the same objective, \(\beta_1 = 0.9\), \(\beta_2 = 0.999\), \(\varepsilon = 10^{-8}\), \(\theta_0 = 0\), \(g_1 = 1\):

Quantity Value
\(m_1\) \(0.1\)
\(v_1\) \(0.001\)
\(\hat{m}_1\) \(1.0\)
\(\hat{v}_1\) \(1.0\)
\(\theta_1\) \(-\eta \approx -0.001\)

Bias correction is the critical step: without it, \(m_1/\sqrt{v_1} \approx 3.16\), giving a first step roughly \(\sqrt{1000}\) times larger than the bias-corrected value.

6.3.5 Pitfalls & Edge Cases

6.3.6 Variants & Generalizations

Variant Change from base
AdaGrad Non-decaying sum of squared gradients (no \(\beta_2\) decay); aggressive learning rate shrinkage
RMSProp Adam without first-moment tracking; lacks bias correction
AdamW Decoupled weight-decay applied directly to \(\theta\) before the gradient step
AMSGrad Replaces \(\hat{v}\) with the running maximum to guarantee monotone effective step-size

6.3.7 Applications

6.3.8 Connections to Other Algorithms

Component Relationship
Gradient Descent Batch counterpart; shares the learning-rate update rule but recomputes the objective each time
LMS Adaptive Filter Equivalent to online SGD for a linear regression model under MSE loss
Regularization Adds a penalty gradient to \(g_t\); compatible with any step optimiser

6.4 Levenberg–Marquardt (Damped Gauss–Newton with Retraction)

6.4.1 Overview & Motivation

Inverse kinematics, forward kinematics of parallel robots, sensor and hand-eye calibration and scan matching all ask the same question: which state makes a set of nonlinear residuals as small as possible? The problems are small, solved every control cycle or at commissioning, and must finish within a hard iteration budget without heap allocation.

Levenberg–Marquardt answers it by repeated linearization. Near the current guess the residuals behave like a linear function of a small correction, and Gauss–Newton solves that linear problem exactly, converging quadratically when a zero residual is reachable. Far from the solution, or near a singular Jacobian, the exact step can be huge or undefined. A damping term penalizes large corrections and trades speed for robustness.

The state need not be a plain vector: an angle wraps, an orientation lives on a manifold, a joint stops at a limit. The solver therefore never adds the correction itself. The problem applies it through a retraction, so the state never leaves its valid set. One careful solver replaces a hand-rolled Newton loop in every module.

6.4.2 Mathematical Theory

6.4.2.1 Problem Statement

\[ \min_{x}\; \tfrac12\,\|r(x)\|^2, \qquad r(x) \in \mathbb{R}^{R} \]

The state \(x\) may lie on a manifold or within bounds and has \(P\) local degrees of freedom. The problem supplies three things:

A damping policy supplies \(\lambda^2 \ge 0\) from the current Jacobian. A constant policy is classic Levenberg–Marquardt, and \(\lambda = 0\) is Gauss–Newton. A problem-aware schedule, such as zero away from singularities, plugs in through the same interface.

6.4.2.2 Damped Step

Linearizing \(r(x \oplus \delta) \approx r + J\delta\) and penalizing the correction gives

\[ \min_{\delta}\; \tfrac12\|r + J\delta\|^2 + \tfrac12\lambda^2\|\delta\|^2 \;\Longrightarrow\; (J^\top J + \lambda^2 I)\,\delta = -J^\top r . \]

The push-through identity \((J^\top J + \lambda^2 I)^{-1}J^\top = J^\top (JJ^\top + \lambda^2 I)^{-1}\) gives an equivalent form, so the solver always factors the smaller system:

Shape Condition System Step
Tall \(R > P\) \(P \times P\), parameter space \(\delta = -(J^\top J + \lambda^2 I)^{-1}J^\top r\)
Wide or square \(R \le P\) \(R \times R\), residual space \(\delta = -J^\top (JJ^\top + \lambda^2 I)^{-1} r\)

For \(\lambda = 0\) and full row rank, the wide form is the minimum-norm solution of \(J\delta = -r\): the shortest correction that cancels the linearized residual. The system is solved by LU decomposition with partial pivoting, never by an explicit inverse. If the factorization finds a pivot below a relative \(10^{-6}\) (for example \(\lambda = 0\) with a rank-deficient \(J\)), the solve ends unconverged at the current state instead of dividing by zero. For \(\lambda^2 > 0\) the matrix is symmetric positive definite.

6.4.2.3 Iteration

With tolerance \(\varepsilon\), optional step cap \(\Delta\) and iteration budget \(N\):

  1. Evaluate \(r\). If \(\|r\| < \varepsilon\), stop converged.
  2. Evaluate \(J\) and \(\lambda^2\), compute \(\delta\). If the system is singular, stop unconverged.
  3. If \(\|\delta\| > \Delta\), rescale \(\delta \leftarrow \delta\,\Delta/\|\delta\|\). This is trust-region-like: the direction is kept and only the length is limited.
  4. Retract: \(x \leftarrow x \oplus \delta\).

After \(N\) iterations the residual is recomputed at the returned state, which is converged only if it is within tolerance. The reported residual always belongs to the returned state.

6.4.2.4 Fixed Points and the Effect of Damping

The damped step vanishes exactly when \(J^\top r = 0\). For a tall problem this is a stationary point of the cost. For a wide problem of full row rank it forces \(r = 0\). Damping changes the path, not the destination: convergence drops from quadratic to linear, but a zero-residual fixed point stays a fixed point. A non-zero final residual therefore means an unreachable target, a singularity or an active bound.

6.4.3 Complexity Analysis

Item Cost Justification
Normal matrix \(O(R\,P\,m)\) \(J^\top J\) costs \(R P^2\), \(JJ^\top\) costs \(P R^2\); \(m = \min(R, P)\)
Factorization and solve \(O(m^3)\) LU of an \(m \times m\) matrix dominates the \(O(m^2)\) triangular solves
Whole solve at most \(N\) iterations each adds one residual and one Jacobian from the problem; \(N + 1\) residual evaluations in all, the last at the returned state
Memory one \(R \times P\) Jacobian, one \(m \times m\) system stack only; no heap, no recursion

6.4.4 Step-by-Step Walkthrough

Rosenbrock residuals. \(r = (10(y - x^2),\; 1 - x)\), \(J = \begin{pmatrix} -20x & 10 \\ -1 & 0 \end{pmatrix}\), zero residual at \((1, 1)\). Start at \((-1.2, 1)\) with \(\lambda = 0\); \(R = P = 2\), so the step is solved in residual space.

Iteration State \(r\) \(J\) Step \(\delta\) New state
1 \((-1.2, 1)\) \((-4.4, 2.2)\), \(\|r\| = 4.919\) \(\begin{pmatrix} 24 & 10 \\ -1 & 0 \end{pmatrix}\) \((2.2, -4.84)\) \((1, -3.84)\)
2 \((1, -3.84)\) \((-48.4, 0)\), \(\|r\| = 48.4\) \(\begin{pmatrix} -20 & 10 \\ -1 & 0 \end{pmatrix}\) \((0, 4.84)\) \((1, 1)\)

The second residual is linear in \(x\), so step 1 zeroes it exactly. The first is then linear in \(y\), so step 2 finishes exactly. The residual norm rises from \(4.9\) to \(48.4\) on the way: the iteration is not monotone. With a step cap \(\Delta = 1\), the first step of norm \(5.316540\) is scaled to unit length, giving \((-0.786197,\; 0.089634)\).

Angle on the unit circle. State \(\theta\), \(r = (\cos\theta, \sin\theta) - (\cos 2.5, \sin 2.5)\), \(J = (-\sin\theta,\; \cos\theta)^\top\). Here \(R = 2 > P = 1\) and \(J^\top J = 1\), so with \(\lambda = 0\) each step is \(\theta \leftarrow \theta - \sin(\theta - 2.5)\), wrapped into \((-\pi, \pi]\). Start at \(\theta = -2.5\), which is only \(1.28\) rad from the target across the \(\pm\pi\) seam.

Iteration \(\theta\) \(\sin(\theta - 2.5)\) Before wrap After wrap
1 \(-2.500000\) \(0.958924\) \(-3.458924\) \(2.824261\)
2 \(2.824261\) \(0.318608\) \(2.505653\) \(2.505653\)
3 \(2.505653\) \(0.005653\) \(2.500000\) \(2.500000\)

The wrap keeps the state in its canonical range, so the answer is \(2.5\) and not \(2.5 - 2\pi\). The error falls \(0.324 \to 0.0057 \to 3\times10^{-8}\), faster than quadratic because of the circle’s symmetry.

Unreachable target. Target \((2, 0)\) lies outside the unit circle, with \(\lambda^2 = 1\). Then \(J^\top J + \lambda^2 = 2\) and \(\delta = -\tfrac12 J^\top r = -\sin\theta\), so \(\theta \leftarrow \theta - \sin\theta \to 0\). At \(\theta = 0\) the step vanishes (\(J^\top r = 0\)) while \(\|r\| = \|(1, 0) - (2, 0)\| = 1\). The solve exhausts its budget and reports not converged.

6.4.5 Pitfalls & Edge Cases

6.4.6 Variants & Generalizations

Variant Key difference
Gauss–Newton (\(\lambda = 0\)) Quadratic convergence for zero-residual problems; fails on rank deficiency
Constant or problem-aware damping (this) Fixed \(\lambda\) gives robust linear convergence; a Jacobian-dependent \(\lambda^2\) (zero away from singularities) keeps the fast rate where safe
Gain-ratio (Nielsen) adaptation Raises or lowers \(\lambda\) from the ratio of actual to predicted cost reduction; needs a trial evaluation and accept/reject, a possible later variant

6.4.7 Applications

6.4.8 Connections to Other Algorithms

Algorithm Relationship
LU Decomposition Solves the damped normal system; its singularity report ends the solve unconverged
Damped Pseudo-Inverse The same damped step in SVD form for one linear solve; avoids squaring the condition number at higher cost
SE(3) Transform Supplies the manifold retraction for pose states
Angle Utilities Supplies the wrapped retraction for angle states
Gradient Descent First-order method for general costs; this method exploits the sum-of-squares structure through the Gauss–Newton curvature and needs far fewer iterations on small problems

7 Regularization

7.1 Regularization

7.1.1 Overview & Motivation

Regularization adds a penalty term \(\Omega(\theta)\) to the loss function that discourages overly complex models:

\[\mathcal{L}_{\text{total}} = \mathcal{L}(\hat{y}, y) + \lambda \, \Omega(\theta)\]

where \(\lambda > 0\) controls the strength of the penalty. Without regularization, a neural network with enough parameters can memorize the training data perfectly yet generalize poorly to new inputs — a phenomenon called overfitting. Regularization biases the optimizer toward simpler solutions that tend to generalize better.

7.1.2 Mathematical Theory

7.1.2.1 L2 Regularization (Ridge / Weight Decay)

\[\Omega_{L2}(\theta) = \frac{1}{2}\sum_{i=1}^{P} \theta_i^2 = \frac{1}{2}\|\theta\|_2^2\]

\[\frac{\partial \Omega_{L2}}{\partial \theta_i} = \theta_i\]

Effect on update rule:

\[\theta_{t+1} = \theta_t - \eta(\nabla_\theta \mathcal{L} + \lambda \theta_t) = (1 - \eta\lambda)\theta_t - \eta \nabla_\theta \mathcal{L}\]

The factor \((1 - \eta\lambda)\) shrinks weights toward zero each step — hence the name weight decay. Large weights are penalized quadratically, keeping the model smooth.

7.1.2.2 L1 Regularization (Lasso)

\[\Omega_{L1}(\theta) = \sum_{i=1}^{P} |\theta_i| = \|\theta\|_1\]

\[\frac{\partial \Omega_{L1}}{\partial \theta_i} = \mathrm{sign}(\theta_i)\]

Effect: L1 drives small weights exactly to zero, producing a sparse model. This is useful for feature selection — irrelevant connections are pruned automatically.

7.1.2.3 Comparison

Property L1 L2
Penalty shape Diamond (corners at axes) Sphere
Sparsity Promotes exact zeros Shrinks toward zero but rarely reaches it
Gradient at \(\theta_i = 0\) Undefined (sub-gradient) Zero
Best for Feature selection, sparse models General-purpose weight control

7.1.2.4 Geometric Interpretation

The regularized loss can be viewed as constrained optimization:

\[\min_\theta \mathcal{L}(\theta) \quad \text{subject to} \quad \Omega(\theta) \le c\]

where \(c\) is determined by \(\lambda\). L1 constrains \(\theta\) to a diamond, so the optimal point tends to lie at a corner (sparse). L2 constrains to a sphere, so the optimal point balances all dimensions.

7.1.3 Complexity Analysis

Operation Time Space
\(\Omega_{L2}(\theta)\) \(O(P)\) \(O(1)\)
\(\nabla \Omega_{L2}\) \(O(P)\) \(O(P)\)
\(\Omega_{L1}(\theta)\) \(O(P)\) \(O(1)\)
\(\nabla \Omega_{L1}\) \(O(P)\) \(O(P)\)

Regularization adds negligible computational cost — one pass over the parameter vector per training iteration.

7.1.4 Step-by-Step Walkthrough

Scenario: 3 parameters, \(\theta = [0.8, -0.3, 0.5]\), \(\lambda = 0.1\).

L2 Regularization:

Step Computation Result
Penalty \(\frac{1}{2}(0.64 + 0.09 + 0.25) = 0.49\) \(\Omega_{L2} = 0.49\)
Gradient \([0.8, -0.3, 0.5]\) Added to \(\nabla\mathcal{L}\)
Contribution to loss \(0.1 \times 0.49 = 0.049\)

L1 Regularization:

Step Computation Result
Penalty \(0.8 + 0.3 + 0.5 = 1.6\) \(\Omega_{L1} = 1.6\)
Gradient \([1, -1, 1]\) Added to \(\nabla\mathcal{L}\)
Contribution to loss \(0.1 \times 1.6 = 0.16\)

After several L1 updates (\(\eta = 0.1\), \(\lambda = 0.1\)): the \(\theta_2 = -0.3\) component, already small, is driven to exactly zero. The model effectively prunes that connection.

7.1.5 Pitfalls & Edge Cases

7.1.6 Variants & Generalizations

Variant Key Difference
Elastic Net \(\Omega = \alpha \|\theta\|_1 + (1-\alpha)\|\theta\|_2^2\); combines L1 sparsity with L2 smoothness
Dropout Randomly zeroes activations during training; implicit ensemble regularization
Early stopping Halts training before overfitting; regularization without modifying the loss
Data augmentation Expands the training set with transformed copies; reduces overfitting by increasing data diversity
Spectral normalization Constrains the spectral norm of weight matrices; stabilizes GAN training
Weight clipping Hard constraint: \(\left\|\theta_i\right\| \le c\); used in Wasserstein GANs

7.1.7 Applications

7.1.8 Connections to Other Algorithms

graph TD
    Reg["Regularization"]
    Loss["Loss Functions"]
    Opt["Optimizer"]
    Model["Model"]
    LR["Linear Regression"]

    Reg -->|"λ Ω(θ) added to ℒ"| Loss
    Loss --> Opt
    Opt --> Model
    Reg -.->|"L2 + MSE = Ridge regression"| LR
Component Relationship
Loss Functions Regularization is a penalty added to the loss: \(\mathcal{L}_{\text{total}} = \mathcal{L} + \lambda\Omega\)
Optimizer Receives the combined gradient \(\nabla\mathcal{L} + \lambda\nabla\Omega\)
Linear Regression L2-regularized MSE with a linear model is Ridge regression; L1 is Lasso

8 Math

8.1 CORDIC

8.1.1 Overview & Motivation

Trigonometric functions, magnitudes, and vector rotations are fundamental operations in motor control, radar processing, and navigation. On microcontrollers without a floating-point unit or hardware multiplier, library implementations of sin, cos, atan2, and hypot require expensive software-emulated multiplications. This limits their use in hard real-time loops where deterministic execution time is mandatory.

CORDIC (COordinate Rotation DIgital Computer) solves this by expressing any planar rotation as a sum of progressively smaller elementary rotations, each of which requires only a bit-shift and an addition. The result is a trig engine that runs on shift-add hardware with a fixed, data-independent cycle count — exactly what a deterministic real-time loop demands.

8.1.2 Mathematical Theory

8.1.2.1 Elementary Rotations

A rotation by angle \(\theta\) in two dimensions transforms a vector \((x, y)\) to

\[x' = x\cos\theta - y\sin\theta, \quad y' = y\cos\theta + x\sin\theta.\]

Factoring out \(\cos\theta\) gives

\[x' = \cos\theta\,(x - y\tan\theta), \quad y' = \cos\theta\,(y + x\tan\theta).\]

When \(\tan\theta_i = \pm 2^{-i}\), the multiplication by \(\tan\theta_i\) becomes a right-shift by \(i\) bits. The angles \(\theta_i = \arctan(2^{-i})\) form the CORDIC angle table.

8.1.2.2 Rotation Mode (sin/cos)

Starting from \((x_0, y_0, z_0) = (K, 0, \theta)\), each iteration steers the residual angle \(z\) toward zero:

\[x_{i+1} = x_i - \sigma_i \, 2^{-i} y_i\] \[y_{i+1} = y_i + \sigma_i \, 2^{-i} x_i\] \[z_{i+1} = z_i - \sigma_i \, \theta_i\]

where \(\sigma_i = \text{sign}(z_i)\). After \(N\) iterations, \(x_N \approx \cos\theta\) and \(y_N \approx \sin\theta\).

8.1.2.3 Vectoring Mode (atan2/magnitude)

Starting from \((x_0, y_0, z_0) = (x, y, 0)\), each iteration steers \(y\) toward zero:

\[\sigma_i = -\text{sign}(y_i)\]

After \(N\) iterations, \(z_N \approx \arctan(y/x)\) and \(x_N \approx \|(x, y)\| / K\).

8.1.2.4 CORDIC Gain

Each elementary rotation stretches the vector length by \(\sqrt{1 + 2^{-2i}}\). The accumulated gain over \(N\) iterations is

\[A_N = \prod_{i=0}^{N-1} \sqrt{1 + 2^{-2i}}.\]

The constant \(K = 1/A_N \approx 0.6073\) compensates for this growth. In rotation mode the initial \(x\) is pre-scaled by \(K\); in vectoring mode the final \(x\) is multiplied by \(K\).

8.1.2.5 Convergence Domain

The convergence domain is \(|z| \leq \sum_{i=0}^{N-1} \arctan(2^{-i})\). For \(N = 16\) this exceeds \(\pi/2\), so inputs outside \([-\pi/2, \pi/2]\) must be range-reduced by shifting the angle by \(\pm\pi\) and inverting the output signs. Vectoring mode uses quadrant detection on the signs of \(x\) and \(y\) to handle the full \([-\pi, \pi]\) range.

8.1.3 Complexity Analysis

Metric Value
Time \(O(N)\) — exactly \(N\) shift-add steps per call
Space \(O(N)\) — angle table in ROM; \(O(1)\) working registers
Cycle count Fixed, data-independent — no branch on input value

One additional bit of precision is gained per iteration. \(N = 16\) yields approximately 16-bit accuracy; \(N = 20\) reaches the limits of single-precision float.

8.1.4 Step-by-Step Walkthrough

Compute \(\sin(\pi/6) = 0.5\) with \(N = 4\) for brevity (gain \(K_4 \approx 0.6352\)).

\(i\) \(\theta_i\) \(\sigma_i\) \(x_i\) \(y_i\) \(z_i\)
— — — 0.6352 0.0000 0.5236
0 0.7854 +1 0.6352 0.6352 −0.2618
1 0.4636 −1 0.7940 0.3176 0.2018
2 0.2450 +1 0.7147 0.5122 −0.0432
3 0.1244 −1 0.8425 0.4248 0.0812

After iteration 3: \(y_4 \approx 0.43\), improving toward 0.5 as \(N\) grows.

8.1.5 Pitfalls & Edge Cases

The input to rotation mode must lie within the convergence domain after range reduction. Angles at exactly \(\pm\pi/2\) sit at the edge of the domain and may accumulate an extra half-ulp error.

In vectoring mode, \((x, y) = (0, 0)\) is degenerate; by convention the angle is returned as zero rather than causing a division-by-zero or NaN.

The shift \(2^{-i}\) eventually underflows in floating-point for large \(i\); iterations beyond \(\lfloor -\log_2(\epsilon) \rfloor\) contribute nothing and can be capped without loss of accuracy.

8.1.6 Variants & Generalizations

Hyperbolic CORDIC replaces the elementary angle table with \(\tanh^{-1}(2^{-i})\) and handles sinh, cosh, exp, and ln.

Linear CORDIC uses shifts alone (no rotation) to implement multiply and divide.

Double-rotation trick repeats certain iterations to extend the convergence domain to \((-\pi, \pi]\) without a range-reduction step.

8.1.7 Applications

8.1.8 Connections to Other Algorithms

TrigonometricFunctions provides a table-lookup alternative with lower iteration count but higher ROM usage for the same precision. Quaternion consumes CORDIC-generated sin/cos for axis-angle conversions. On targets with an FPU the standard library usually outperforms CORDIC; the advantage is exclusive to multiply-poor hardware.

8.2 Quaternion

8.2.1 Overview & Motivation

Three-dimensional attitude representation is a fundamental requirement in robotics, aerospace, and wearable sensing. Euler angles are intuitive but suffer from gimbal lock — a singularity that collapses three degrees of freedom into two whenever one angle reaches ±90°. Rotation matrices avoid this but carry nine words of state and require orthogonality re-enforcement.

A unit quaternion encodes the same rotation in four words, composes orientations with sixteen multiply-adds, and is free of singularities. Every modern AHRS filter — Madgwick, Mahony, Extended Kalman — stores attitude as a unit quaternion precisely because of this combination of compactness, numerical stability, and algebraic closure.

8.2.2 Mathematical Theory

8.2.2.1 Core Definitions

A quaternion is a hypercomplex number of the form

\[q = w + x\mathbf{i} + y\mathbf{j} + z\mathbf{k}\]

where \(w, x, y, z \in \mathbb{R}\) and the basis elements satisfy

\[\mathbf{i}^2 = \mathbf{j}^2 = \mathbf{k}^2 = \mathbf{ijk} = -1.\]

A unit quaternion (\(\|q\| = 1\)) encodes a rotation by angle \(\theta\) about unit axis \(\hat{n}\) as

\[q = \left(\cos\frac{\theta}{2},\; \hat{n}\sin\frac{\theta}{2}\right).\]

8.2.2.2 Hamilton Product

Composition of two rotations \(q_a\) then \(q_b\) is

\[q_a \otimes q_b = \begin{pmatrix} w_a w_b - x_a x_b - y_a y_b - z_a z_b \\ w_a x_b + x_a w_b + y_a z_b - z_a y_b \\ w_a y_b - x_a z_b + y_a w_b + z_a x_b \\ w_a z_b + x_a y_b - y_a x_b + z_a w_b \end{pmatrix}.\]

This product is non-commutative: \(q_a \otimes q_b \neq q_b \otimes q_a\) in general.

8.2.2.3 Vector Rotation

A pure quaternion \(p = (0, \mathbf{v})\) is rotated by

\[\mathbf{v}' = q \otimes p \otimes q^{-1}.\]

For unit \(q\) this simplifies (Rodrigues cross-product form) to

\[\mathbf{v}' = \mathbf{v} + 2w\,(\mathbf{u} \times \mathbf{v}) + 2\,\mathbf{u} \times (\mathbf{u} \times \mathbf{v}),\]

where \(\mathbf{u} = (x, y, z)\). This costs 15 multiply-adds vs 9 for a pre-built rotation matrix, making it preferable when rotating one vector.

8.2.2.4 Conjugate and Inverse

For any quaternion \(q^* = (w, -x, -y, -z)\). For a unit quaternion \(q^{-1} = q^*\).

8.2.2.5 Rotation Matrix

\[R(q) = \begin{pmatrix} 1-2(y^2+z^2) & 2(xy-wz) & 2(xz+wy) \\ 2(xy+wz) & 1-2(x^2+z^2) & 2(yz-wx) \\ 2(xz-wy) & 2(yz+wx) & 1-2(x^2+y^2) \end{pmatrix}.\]

8.2.2.6 Euler Angles (ZYX / 321 convention)

Converting from unit quaternion to roll \(\phi\), pitch \(\theta\), yaw \(\psi\):

\[\phi = \mathrm{atan2}(2(wx+yz),\; 1-2(x^2+y^2))\] \[\theta = \arcsin(2(wy-zx))\] \[\psi = \mathrm{atan2}(2(wz+xy),\; 1-2(y^2+z^2))\]

At \(\theta = \pm 90°\) the \(\phi\) and \(\psi\) axes align (gimbal lock); the formula still returns a bounded value but the decomposition is no longer unique.

8.2.2.7 SLERP

Spherical Linear Interpolation between unit quaternions \(q_0\) and \(q_1\) at fraction \(t \in [0,1]\):

\[\mathrm{Slerp}(q_0, q_1, t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}\,q_0 + \frac{\sin(t\Omega)}{\sin\Omega}\,q_1,\]

where \(\cos\Omega = q_0 \cdot q_1\). When \(\Omega \approx 0\) (nearly parallel quaternions) the formula degenerates; a normalized linear interpolation (nlerp) is substituted.

8.2.3 Complexity Analysis

Operation Time Space Notes
Hamilton product O(1) O(1) 16 multiply-adds, scalar only
Vector rotate O(1) O(1) 15 multiply-adds via cross-product
To rotation matrix O(1) O(1) 9 elements, 16 multiplications
From rotation matrix O(1) O(1) Branch on largest diagonal
SLERP O(1) O(1) 1 acos + 2 sin + scalar blends
Euler conversion O(1) O(1) 2 atan2 + 1 asin

All operations are stack-only with no heap allocation.

8.2.4 Step-by-Step Walkthrough

Rotating \(\hat{x} = (1,0,0)\) by 90° about \(\hat{z}\):

  1. Axis-angle: \(q = (\cos 45°,\, 0,\, 0,\, \sin 45°) = (\tfrac{\sqrt{2}}{2},\, 0,\, 0,\, \tfrac{\sqrt{2}}{2})\).
  2. \(\mathbf{u} = (0, 0, \tfrac{\sqrt{2}}{2})\), \(\mathbf{v} = (1, 0, 0)\).
  3. \(\mathbf{t} = 2\,\mathbf{u} \times \mathbf{v} = 2(0 \cdot 0 - \tfrac{\sqrt{2}}{2} \cdot 0,\; \tfrac{\sqrt{2}}{2} \cdot 1 - 0,\; 0) = (0,\, \sqrt{2},\, 0)\).
  4. \(\mathbf{u} \times \mathbf{t} = (0 \cdot 0 - \tfrac{\sqrt{2}}{2} \cdot \sqrt{2},\; \ldots) = (-1, 0, 0)\).
  5. \(\mathbf{v}' = (1,0,0) + \tfrac{\sqrt{2}}{2}(0,\sqrt{2},0) + (-1,0,0) = (0,1,0) = \hat{y}\). Correct.

8.2.5 Pitfalls & Edge Cases

8.2.6 Variants & Generalizations

8.2.7 Applications

8.2.8 Connections to Other Algorithms

8.3 Matrix & Vector Norms

8.3.1 Overview & Motivation

Linear algebra computations — solvers, Kalman filter covariance updates, regression — depend on the numerical health of the matrices involved. Norms formalise the notion of “size” for matrices and vectors and are the building blocks of every conditioning, stability, and error-bound estimate in the library. They are also the cheapest such quantities to compute: a single pass over the entries with no allocation, suitable for real-time embedded paths.

Vector normalisation, closely related, produces the unit-length direction of a vector and is a recurring primitive in geometry, attitude estimation, and gradient methods.

8.3.2 Mathematical Theory

8.3.2.1 Dot Product & Vector Norm

For vectors \(\mathbf{a}, \mathbf{b} \in \mathbb{R}^n\), the dot product is \(\mathbf{a}\cdot\mathbf{b} = \sum_{i=1}^{n} a_i b_i\). The Euclidean (L2) norm is the square root of the self dot product:

\[\|\mathbf{v}\|_2 = \sqrt{\mathbf{v}\cdot\mathbf{v}} = \sqrt{\sum_{i=1}^{n} v_i^2}\]

VectorNorm is implemented in terms of DotProduct. The unit vector \(\hat{\mathbf{v}} = \mathbf{v} / \|\mathbf{v}\|_2\) satisfies \(\|\hat{\mathbf{v}}\|_2 = 1\). Normalisation is undefined when \(\|\mathbf{v}\|_2 = 0\) and must be guarded.

8.3.2.2 Matrix Norms

Frobenius norm — treats the matrix as a flattened vector:

\[\|A\|_F = \sqrt{\sum_{i=1}^{m}\sum_{j=1}^{n} a_{ij}^2}\]

It is rotationally invariant under unitary transformations and cheap to compute.

1-norm (maximum absolute column sum):

\[\|A\|_1 = \max_{1 \le j \le n} \sum_{i=1}^{m} |a_{ij}|\]

Infinity norm (maximum absolute row sum):

\[\|A\|_\infty = \max_{1 \le i \le m} \sum_{j=1}^{n} |a_{ij}|\]

The 1-norm and infinity-norm are dual: \(\|A\|_\infty = \|A^\top\|_1\).

8.3.3 Complexity Analysis

Operation Time Space Notes
DotProduct \(O(n)\) \(O(1)\) Single pass; VectorNorm builds on it
FrobeniusNorm \(O(mn)\) \(O(1)\) Single pass, no allocation
OneNorm \(O(mn)\) \(O(1)\) Column-wise sum, running max
InfinityNorm \(O(mn)\) \(O(1)\) Row-wise sum, running max
VectorNorm \(O(n)\) \(O(1)\) Single pass
Normalize \(O(n)\) \(O(n)\) Output vector on stack

8.3.4 Step-by-Step Walkthrough

Matrix \(A = \begin{bmatrix}3 & 1 \\ 1 & 2\end{bmatrix}\):

  1. FrobeniusNorm: \(\sqrt{9 + 1 + 1 + 4} = \sqrt{15} \approx 3.873\)
  2. OneNorm: column 0 sum \(= |3| + |1| = 4\); column 1 sum \(= |1| + |2| = 3\); max \(= 4\)
  3. InfinityNorm: row 0 sum \(= |3| + |1| = 4\); row 1 sum \(= |1| + |2| = 3\); max \(= 4\)

Vector \(\mathbf{v} = [3,\, 4]^\top\): \(\|\mathbf{v}\|_2 = 5\), and \(\hat{\mathbf{v}} = [0.6,\, 0.8]^\top\).

8.3.5 Pitfalls & Edge Cases

Zero vector normalisation — dividing by \(\|\mathbf{v}\|_2 = 0\) is undefined. The implementation returns an empty optional for near-zero norms.

Fast-math semantics — -ffast-math may reorder floating-point operations. The norms are sums of non-negative values, so reordering does not change the sign of the result, but catastrophic cancellation can still occur for near-zero off-diagonal entries.

Non-square matrices — FrobeniusNorm, OneNorm, and InfinityNorm apply to any \(m \times n\) matrix.

8.3.6 Variants & Generalizations

The spectral norm (largest singular value, \(\|A\|_2\)) is the tightest but requires an SVD — \(O(N^3)\) with a large constant, unsuitable for embedded real-time paths. The 1-norm and infinity-norm are cheap upper bounds used throughout the library instead.

General p-norms and weighted norms generalise the vector case; the L2 norm is the only one currently exposed because it is the natural quantity for geometric and least-squares work.

8.3.7 Applications

8.3.8 Connections to Other Algorithms

The 1-norm is the norm used by solvers::ConditionNumber for its \(\|A\|\cdot\|A^{-1}\|\) estimate. The math::Matrix type provides the storage and transpose the norms operate on. Norm-based residual tests appear in solvers and optimization.

8.4 Householder Transform

8.4.1 Overview & Motivation

A Householder reflector is the workhorse of numerically stable dense linear algebra. A single reflector mirrors a vector onto a coordinate axis, zeroing every entry below a chosen pivot in one orthogonal step. Chaining reflectors triangularizes a matrix (QR), bidiagonalizes it (SVD), or tridiagonalizes a symmetric matrix (eigensolvers). HouseholderVector computes the reflector for one sub-column; it is the shared primitive those factorizations call.

8.4.2 Mathematical Theory

For a vector \(x\), the Householder reflector is the orthogonal matrix

\[H = I - \beta\, v v^\top\]

chosen so that \(Hx\) is zero below the pivot. With \(\sigma = \sum_{i>\text{start}} x_i^2\) and \(\|x\| = \sqrt{x_{\text{start}}^2 + \sigma}\), the reflector maps \(x_{\text{start}} \mapsto \mp\|x\|\). The pivot sign is chosen as \(-\mathrm{sign}(x_{\text{start}})\|x\|\) to avoid cancellation:

\[v_{\text{start}} = 1, \quad v_i = x_i / v_0, \quad \beta = \frac{2 v_0^2}{\sigma + v_0^2}\]

\(H\) is symmetric and orthogonal (\(H = H^\top = H^{-1}\)), so applying it is backward stable.

8.4.3 Complexity Analysis

Operation Time Space Notes
HouseholderVector \(O(n)\) \(O(1)\) Builds v (stored implicit unit pivot)
Apply \(H\) to a vector \(O(n)\) \(O(1)\) x - β·v·(vᵀx) — never form H

8.4.4 Step-by-Step Walkthrough

For \(x = [4, 3, 0, 0]^\top\), pivot start = 0: \(\sigma = 9\), \(\|x\| = 5\). Since \(x_0 > 0\), \(v_0 = -\sigma/(x_0 + \|x\|) = -1\), giving \(\beta = 1\) and \(v = [1, -3, 0, 0]^\top\). Reflecting yields \(Hx = [-5, 0, 0, 0]^\top\) — the sub-column collapsed onto the axis.

8.4.5 Pitfalls & Edge Cases

Already-zero sub-column — when \(\sigma \approx 0\) there is nothing to zero; the routine returns \(\beta = 0\) (identity reflector) and callers skip the update.

Never form H explicitly — apply it as x − β·v·(vᵀx) to keep the cost \(O(n)\) per column instead of \(O(n^2)\).

Float-only — static_assert(std::is_floating_point_v<T>); the sign-fixing and normalisation assume real floating-point arithmetic.

8.4.6 Variants & Generalizations

Givens rotations (math::GivensRotation) achieve the same zeroing one entry at a time, preferable for sparse or streaming updates. Complex Householder reflectors extend this to unitary triangularization.

8.4.7 Applications

8.4.8 Connections to Other Algorithms

Used by solvers::QrDecomposition; complements math::GivensRotation and math::SolveUpperTriangular. Operates on math::Vector.

8.5 Givens Rotation

8.5.1 Overview & Motivation

A Givens rotation zeros a single matrix entry by rotating two rows in a plane, leaving all others untouched. Because it touches only two rows, it is the tool of choice for incremental linear algebra: streaming a new row into an existing QR factor, sparse triangularization, and the implicit-shift sweeps of QR/SVD eigen-iterations. ComputeGivens derives the rotation coefficients; ApplyGivens applies them to a scalar pair.

8.5.2 Mathematical Theory

Given two scalars \(a\) (the value to keep) and \(b\) (the value to zero), the rotation

\[G = \begin{bmatrix} c & s \\ -s & c \end{bmatrix}, \quad c = \frac{a}{r},\; s = \frac{b}{r},\; r = \sqrt{a^2 + b^2}\]

is orthogonal (\(c^2 + s^2 = 1\)) and satisfies

\[G \begin{bmatrix} a \\ b \end{bmatrix} = \begin{bmatrix} r \\ 0 \end{bmatrix}\]

Applied across a pair of rows, it zeros the target entry while preserving Euclidean length.

8.5.3 Complexity Analysis

Operation Time Space Notes
ComputeGivens \(O(1)\) \(O(1)\) One sqrt, two divides
ApplyGivens \(O(1)\) \(O(1)\) Rotates one scalar pair
Rotate two rows \(O(n)\) \(O(1)\) ApplyGivens across n columns

8.5.4 Step-by-Step Walkthrough

ComputeGivens(3, 4): \(r = 5\), \(c = 0.6\), \(s = 0.8\). Applying to \((x, y) = (3, 4)\): \(x' = 0.6\cdot 3 + 0.8\cdot 4 = 5\), \(y' = -0.8\cdot 3 + 0.6\cdot 4 = 0\) — the second component is annihilated and the norm is preserved.

8.5.5 Pitfalls & Edge Cases

Degenerate pair — when \(a = b = 0\) the rotation is undefined; ComputeGivens returns the identity \((c, s) = (1, 0)\) so applying it is a safe no-op.

Float-only — static_assert(std::is_floating_point_v<T>).

8.5.6 Variants & Generalizations

Householder reflectors (math::HouseholderTransform) zero an entire sub-column at once and are cheaper for dense factorization; Givens wins when only one entry (or one streamed row) changes. Fast/“square-root-free” Givens variants trade the sqrt for extra bookkeeping.

8.5.7 Applications

8.5.8 Connections to Other Algorithms

Used by solvers::QrDecomposition; complements math::HouseholderTransform.

8.6 Triangular Solve

8.6.1 Overview & Motivation

Nearly every dense linear solver ends with a triangular system. Gaussian elimination reduces Ax = b to an upper-triangular Ux = c; QR factorization solves least squares through Rx = Qᵀb; Cholesky solves two triangular systems back-to-back. SolveUpperTriangular is the shared back-substitution kernel these routines call so the algorithm lives in exactly one place.

8.6.2 Mathematical Theory

Given an upper-triangular matrix \(R \in \mathbb{R}^{n \times n}\) (entries below the diagonal ignored) and a right-hand side \(c\), back-substitution solves \(Rx = c\) bottom-up:

\[x_i = \frac{1}{r_{ii}}\left(c_i - \sum_{j=i+1}^{n} r_{ij}\, x_j\right), \quad i = n, n-1, \dots, 1\]

Each unknown depends only on those already computed, so a single reverse sweep suffices.

8.6.3 Complexity Analysis

Operation Time Space Notes
SolveUpperTriangular \(O(n^2)\) \(O(n)\) One reverse sweep, stack output

8.6.4 Step-by-Step Walkthrough

For \(R = \begin{bmatrix}2 & -1 & 3 \\ 0 & 4 & 1 \\ 0 & 0 & 5\end{bmatrix}\), \(c = [10,\, -5,\, 15]^\top\):

  1. \(x_3 = 15 / 5 = 3\)
  2. \(x_2 = (-5 - 1\cdot 3)/4 = -2\)
  3. \(x_1 = (10 - (-1)(-2) - 3\cdot 3)/2 = 1\) ⇒ \(x = [1,\, -2,\, 3]^\top\).

8.6.5 Pitfalls & Edge Cases

Singular / near-zero pivot — a zero diagonal entry makes the system unsolvable. The routine asserts |r_{ii}| > 0 via really_assert; callers (GaussianElimination, QrDecomposition) detect rank deficiency before reaching it.

Generic on T — the kernel is templated on any supported numeric type (float, Q15, Q31) because Gaussian elimination is instantiated for all three; it uses math::ToFloat only for the pivot assertion.

8.6.6 Variants & Generalizations

A lower-triangular forward-substitution is the mirror image (top-down sweep) and can be added when Cholesky/LU forward solves need it. Block triangular solves generalise this to matrix right-hand sides.

8.6.7 Applications

8.6.8 Connections to Other Algorithms

Shared by solvers::GaussianElimination and solvers::QrDecomposition. Operates on math::Matrix / math::Vector.

8.7 Cholesky Decomposition

8.7.1 Overview & Motivation

The Cholesky decomposition factors a symmetric positive-definite matrix \(A\) into the product \(A = LL^T\), where \(L\) is a lower triangular matrix with positive diagonal entries. This is the matrix equivalent of taking a “square root.”

It is approximately twice as fast as LU decomposition for symmetric positive-definite systems and is numerically stable without pivoting. In this library, it is used primarily by the Unscented Kalman Filter to generate sigma points from the state covariance matrix.

8.7.2 Mathematical Theory

Given \(A \in \mathbb{R}^{n \times n}\) symmetric positive-definite, compute \(L\) such that \(A = LL^T\):

\[L_{jj} = \sqrt{A_{jj} - \sum_{k=0}^{j-1} L_{jk}^2}\]

\[L_{ij} = \frac{1}{L_{jj}} \left( A_{ij} - \sum_{k=0}^{j-1} L_{ik} L_{jk} \right), \quad i > j\]

8.7.3 Complexity Analysis

Operation Time Space Notes
Decomposition \(O(n^3/6)\) \(O(n^2)\) Half the cost of general LU decomposition

8.7.4 Step-by-Step Walkthrough

Input: \(A = \begin{bmatrix} 4 & 2 \\ 2 & 5 \end{bmatrix}\) (symmetric positive-definite).

Compute \(L\) column by column:

Step Formula Value
\(L_{00}\) \(\sqrt{A_{00}} = \sqrt{4}\) \(2\)
\(L_{10}\) \(A_{10} / L_{00} = 2 / 2\) \(1\)
\(L_{11}\) \(\sqrt{A_{11} - L_{10}^2} = \sqrt{5 - 1}\) \(2\)

Result:

\[L = \begin{bmatrix} 2 & 0 \\ 1 & 2 \end{bmatrix}\]

Verify: \(LL^T = \begin{bmatrix} 2 & 0 \\ 1 & 2 \end{bmatrix} \begin{bmatrix} 2 & 1 \\ 0 & 2 \end{bmatrix} = \begin{bmatrix} 4 & 2 \\ 2 & 5 \end{bmatrix} = A\) ✓

8.7.5 Pitfalls & Edge Cases

8.7.6 Variants & Generalizations

8.7.7 Applications

8.7.8 Connections to Other Algorithms

Algorithm Relationship
Unscented Kalman Filter Uses Factor to generate sigma points from the covariance matrix
Consistency Metrics Uses Solve for the SPD covariance solve behind NEES/NIS
Gaussian Elimination General-purpose alternative; does not exploit symmetry

8.8 Matrix Operations

8.8.1 Overview & Motivation

Small structural matrix utilities that are needed across the library but do not belong to any single algorithm:

Both are recurring needs in Kalman filters, EM parameter updates, and Riccati/Lyapunov solvers.

8.8.2 Mathematical Theory

Any square matrix decomposes into a symmetric and a skew-symmetric part:

\[M = \underbrace{\tfrac{1}{2}(M + M^\top)}_{\text{symmetric}} + \underbrace{\tfrac{1}{2}(M - M^\top)}_{\text{skew}}\]

Symmetrize returns the symmetric part \(\tfrac{1}{2}(M + M^\top)\). It is the orthogonal projection (in the Frobenius inner product) of \(M\) onto the subspace of symmetric matrices, so it is the closest symmetric matrix to \(M\). Applying it to an already-symmetric matrix is a no-op (idempotent).

If \(M\) is symmetric, CongruenceTransform returns a symmetric result exactly (in exact arithmetic): \((AMA^\top)^\top = A M^\top A^\top = A M A^\top\). This makes it the natural building block for propagating a covariance \(P\) through a linear map \(A\): \(P \mapsto A P A^\top\).

8.8.3 Complexity Analysis

Operation Time Space Notes
Symmetrize \(O(n^2)\) \(O(n^2)\) One transpose, add, scale
CongruenceTransform \(O(n^2 m + n m^2)\) \(O(nm)\) For \(A \in \mathbb{R}^{n\times m}\), two matrix products

8.8.4 Step-by-Step Walkthrough

Symmetrize — for \(M = \begin{bmatrix}1 & 3 \\ -1 & 2\end{bmatrix}\): \(M^\top = \begin{bmatrix}1 & -1 \\ 3 & 2\end{bmatrix}\), so \(\tfrac{1}{2}(M + M^\top) = \begin{bmatrix}1 & 1 \\ 1 & 2\end{bmatrix}\) — off-diagonals averaged, diagonal unchanged.

CongruenceTransform — with \(A \in \mathbb{R}^{n\times m}\) and symmetric \(M \in \mathbb{R}^{m\times m}\), the result \(A M A^\top \in \mathbb{R}^{n\times n}\) is the covariance of \(A x\) when \(x\) has covariance \(M\).

8.8.5 Pitfalls & Edge Cases

Symmetrize is not a fix for indefiniteness — it removes the skew part but does not make a matrix positive-definite; covariance code typically also adds a small diagonal jitter (+ εI) separately.

CongruenceTransform association — the implementation evaluates (A·M)·Aᵀ, matching the left-associative operator*; results are bit-identical to hand-written A * M * A.Transpose(). Under fast-math the symmetry of the output can still carry tiny round-off asymmetry — follow with Symmetrize when exact symmetry is required.

Float-only — both are static_assert(std::is_floating_point_v<T>).

8.8.6 Variants & Generalizations

The skew-symmetric part \(\tfrac{1}{2}(M - M^\top)\) is Symmetrize’s companion. The transposed congruence \(A^\top M A\) (used by DiscreteAlgebraicRiccatiEquation) is obtained by passing A.Transpose().

8.8.7 Applications

8.8.8 Connections to Other Algorithms

Operate on math::Matrix / math::SquareMatrix. Consumed by the filters::active Kalman family and estimators::ExpectationMaximization; CongruenceTransform pairs naturally with Symmetrize for covariance-positivity hygiene.

8.8.9 Zero-dimension Vectors

Matrix and vector dimensions must be strictly positive; a zero-length parameter vector is not representable. Element access on an empty fixed-size array has no valid index, so allowing it would trade a compile-time error for silent undefined behaviour.

A component without trainable parameters (for example a pooling or pure activation stage) should report the absence of parameters explicitly — an optional parameter vector that is empty, or an interface that has no parameter accessor at all — rather than returning a zero-length vector.

8.9 Step / Transient-Response Metrics

8.9.1 Overview & Motivation

When a control system or filter receives a step input, its output traces a transient trajectory before settling at the final value. Quantifying that trajectory with standardised scalar metrics — rise time, settling time, percent overshoot, peak time, and steady-state error — is the primary acceptance test for any closed-loop design. These metrics translate the raw sample sequence into the language of control specifications, allowing automated pass/fail decisions without manual inspection of time-domain plots.

8.9.2 Mathematical Theory

8.9.2.1 Definitions

Let \(y[k]\), \(k = 0, \ldots, N-1\) be the sampled step response and \(y_{ss}\) the steady-state value. The sample period is \(\Delta t\).

Rise Time \(T_r\)

The elapsed time for the response to travel from 10 % to 90 % of steady state:

\[T_r = (k_{90} - k_{10})\,\Delta t\]

where \(k_{10} = \min\{k : y[k] \ge 0.1\,y_{ss}\}\) and \(k_{90} = \min\{k \ge k_{10} : y[k] \ge 0.9\,y_{ss}\}\).

Settling Time \(T_s\)

The first time after which the response remains permanently inside the band \([(1-\delta)y_{ss},\,(1+\delta)y_{ss}]\) (typically \(\delta = 0.02\)):

\[T_s = (k^* + 1)\,\Delta t, \quad k^* = \max\{k : |y[k] - y_{ss}| > \delta\,|y_{ss}|\}\]

Percent Overshoot \(\%OS\)

\[\%OS = 100\,\frac{y_{\max} - y_{ss}}{y_{ss}}, \quad y_{\max} = \max_k y[k]\]

For an underdamped second-order system with damping ratio \(\zeta\):

\[\%OS = 100\,\exp\!\left(-\frac{\pi\zeta}{\sqrt{1-\zeta^2}}\right)\]

Peak Time \(T_p\)

\[T_p = k_p\,\Delta t, \quad k_p = \arg\max_k y[k]\]

For a continuous underdamped second-order system with natural frequency \(\omega_n\):

\[T_p = \frac{\pi}{\omega_n\sqrt{1-\zeta^2}}\]

Steady-State Error \(e_{ss}\)

\[e_{ss} = r - \bar{y}_{\text{tail}}\]

where \(r\) is the reference (command) value and \(\bar{y}_{\text{tail}}\) is the mean of the final quarter of the response buffer, providing a robust estimate of the achieved steady state.

8.9.3 Complexity Analysis

Case Time Space Notes
All \(O(N)\) \(O(1)\) Single forward pass; no auxiliary storage

Each metric requires at most one traversal of the \(N\)-element vector. The tail-mean for steady-state error adds a constant-fraction second scan of the same data — still \(O(N)\) total.

8.9.4 Step-by-Step Walkthrough

Consider a 10-sample ramp to \(y_{ss} = 1\) followed by a constant plateau (N = 20):

k:  0  1  2  3  4  5  6  7  8  9 10 11 …
y: 0  .1 .2 .3 .4 .5 .6 .7 .8 .9 1  1  …

8.9.5 Pitfalls & Edge Cases

Zero steady state. Division by \(y_{ss}\) in percent overshoot is guarded; the function returns zero when \(y_{ss} = 0\) to avoid a NaN.

Non-monotone ramp. If the response crosses 90 % before 10 % (e.g., DC offset or wrong initial condition), \(k_{10}\) may be found after the first 90 % crossing. The implementation returns the first pair that satisfies the threshold order.

Oscillatory settling. Settling time is defined as the last time the trajectory leaves the band, not the first time it enters it. Repeated crossings near the boundary extend the metric correctly.

Finite buffer. With a bounded vector of length \(N\), if the response has not yet settled by the final sample, SettlingTime returns \(N\,\Delta t\) and RiseTime returns \((N-1)\,\Delta t\) as conservative bounds.

Tail-mean length. Using the last \(\lfloor N/4 \rfloor + 1\) samples for the steady-state estimate assumes the transient has decayed to within numerical noise by that point. Poorly chosen \(N\) relative to the system time constant degrades the estimate.

8.9.6 Variants & Generalizations

8.9.7 Applications

8.9.8 Connections to Other Algorithms

8.10 Matrix Exponential

8.10.1 Overview & Motivation

The matrix exponential is the fundamental solution operator for linear time-invariant (LTI) systems. Given a square matrix \(A\), the matrix exponential \(e^A\) maps initial conditions of the ODE \(\dot{x} = Ax\) to their exact solution at unit time: \(x(1) = e^A x(0)\). In embedded control, it appears as the exact discretisation of a continuous-time state-space model — turning the pair \((A_c, B_c)\) into the discrete \((A_d, B_d)\) that an MCU actually executes. Accurate, bounded computation of \(e^A\) is therefore a prerequisite for correct control deployment.

8.10.2 Mathematical Theory

8.10.2.1 Series Definition

For a square matrix \(A \in \mathbb{R}^{n \times n}\), the matrix exponential is defined by the power series

\[e^A = \sum_{k=0}^{\infty} \frac{A^k}{k!} = I + A + \frac{A^2}{2!} + \frac{A^3}{3!} + \cdots\]

The series converges for every finite matrix and every field of characteristic zero.

8.10.2.2 Scaling and Squaring

Truncating the series directly is unreliable for large \(\|A\|\) because the intermediate terms grow into a large “hump” before cancelling — a phenomenon of catastrophic cancellation. The scaling-and-squaring algorithm avoids this by exploiting the identity

\[e^A = \left(e^{A/2^s}\right)^{2^s}\]

Choose \(s = \max\!\left(0, \lceil \log_2 \|A\|_\infty \rceil\right)\) so that \(\|A/2^s\|_\infty \leq 1\), compute \(e^{A/2^s}\) accurately on the shrunken argument, then recover \(e^A\) by repeated squaring.

8.10.2.3 Padé Approximant

For the scaled matrix \(\hat{A} = A/2^s\), the exponential is approximated by the diagonal \((q, q)\) Padé rational function

\[e^{\hat{A}} \approx R_q(\hat{A}) = D_q(\hat{A})^{-1} N_q(\hat{A})\]

where \(N_q\) and \(D_q\) are matrix polynomials whose scalar counterparts are the numerator and denominator of the \((q,q)\) Padé approximant to \(e^x\). The coefficients \(c_k\) satisfy

\[c_k = \frac{(2q - k)!\, q!}{(2q)!\, k!\, (q-k)!}, \quad k = 0, 1, \ldots, q\]

For \(q = 6\) the coefficients are \(c_0 = 1\), \(c_1 = \tfrac{1}{2}\), \(c_2 = \tfrac{5}{44}\), \(c_3 = \tfrac{1}{66}\), \(c_4 = \tfrac{1}{792}\), \(c_5 = \tfrac{1}{15840}\), \(c_6 = \tfrac{1}{665280}\).

The even/odd split halves the number of matrix multiplications:

\[V = c_0 I + c_2 \hat{A}^2 + c_4 \hat{A}^4 + c_6 \hat{A}^6\] \[U = \hat{A}\!\left(c_1 I + c_3 \hat{A}^2 + c_5 \hat{A}^4\right)\] \[N_q = V + U, \quad D_q = V - U\]

The ratio \(D_q^{-1} N_q\) is evaluated by solving the linear system \(D_q X = N_q\) column-by-column using LU factorisation with partial pivoting, never forming \(D_q^{-1}\) explicitly.

8.10.3 Complexity Analysis

Phase Time Space Notes
Infinity-norm \(O(n^2)\) \(O(1)\) Determines scaling exponent \(s\)
Matrix powers \(A^2, A^4, A^6\) \(3\,O(n^3)\) \(3 n^2\) Even/odd split; three multiplications total
Polynomial evaluation \(O(n^3)\) \(2 n^2\) Horner-style accumulation of \(U\) and \(V\)
LU solve (\(D_q X = N_q\)) \(O(n^3)\) \(n^2\) One factorisation, \(n\) substitution passes
Squaring (\(s\) steps) \(s\,O(n^3)\) \(n^2\) At most \(\lceil \log_2 \|A\|_\infty \rceil\) steps
Total \(O((6+s)n^3)\) \(O(n^2)\) Stack-only; no heap

8.10.4 Step-by-Step Walkthrough

Consider \(A = \begin{bmatrix}0 & -1 \\ 1 & 0\end{bmatrix}\), a rotation generator with \(\theta = 1\).

  1. Norm: \(\|A\|_\infty = 1\), so \(s = 0\) (no scaling needed).
  2. Polynomial evaluation: compute \(\hat{A}^2 = -I\), \(\hat{A}^4 = I\), \(\hat{A}^6 = -I\). Then \[V = (c_0 - c_2 + c_4 - c_6)I, \quad U = (c_1 - c_3 + c_5)\hat{A}\]
  3. Padé ratio: \(R = D^{-1}N\) solved via LU.
  4. Squaring: none (\(s = 0\)).
  5. Result: matches \(\begin{bmatrix}\cos 1 & -\sin 1 \\ \sin 1 & \cos 1\end{bmatrix}\) to within float tolerance.

8.10.5 Pitfalls & Edge Cases

Catastrophic cancellation without scaling: naive Padé on a large argument produces entries that nearly cancel, magnifying rounding error. The scaling step ensures \(\|\hat{A}\|_\infty \leq 1\) before the rational approximation is applied.

Nilpotent matrices: the series terminates in finitely many terms. Scaling-and-squaring handles this transparently; the Padé approximant reduces to a truncated polynomial.

Stiff systems (large negative eigenvalues): the result is bounded because \(e^{\lambda}\) with \(\lambda \ll 0\) is near zero. Floating-point underflow may drive these entries to zero; this is physically correct and numerically harmless.

Singular denominator: \(D_q\) is singular only if \(e^A\) has a pole, which the matrix exponential never does (\(A\) finite \(\Rightarrow\) \(e^A\) invertible). For the Padé denominator this means near-singularity can occur only at pathological arguments; the LU pivoting detects and gracefully handles it in practice.

Scaling exponent overflow: for a matrix with entries \(\approx 10^{38}\) the exponent \(s\) would be \(\approx 126\), requiring 126 squarings. This is accepted behaviour; the algorithm remains correct but slow.

8.10.6 Variants & Generalizations

Higher-order Padé: orders 8, 10, or 13 reduce the required scaling and improve accuracy for modest \(\|A\|\). Higham’s 2005 algorithm chooses the order adaptively. Order 6 is a reasonable default for embedded float arithmetic.

Schur decomposition pre-conditioning: computing \(e^A = Q e^T Q^\mathsf{T}\) (with \(T\) upper-triangular Schur form) avoids the hump phenomenon entirely and permits reuse of the Schur factors. The additional cost is the Schur decomposition itself (\(O(n^3)\)) and is not justified for small embedded matrices.

Taylor series with Horner evaluation: accurate only for \(\|A\| \ll 1\); the scaling step achieves exactly this, making scaling-and-squaring a superset.

8.10.7 Applications

8.10.8 Connections to Other Algorithms

TriangularSolve (SolveUnitLowerTriangular, SolveUpperTriangular) performs the back-substitution steps of the LU solve inside SolvePade. MatrixNorms::InfinityNorm computes the scaling exponent. ContinuousToDiscrete (roadmap item 30) is the primary consumer of this algorithm.

8.11 Estimator Consistency Metrics (NEES / NIS)

8.11.1 Overview & Motivation

State estimators such as the Kalman filter, Extended Kalman filter, and Unscented Kalman filter produce both a state estimate and a covariance matrix that quantifies estimation uncertainty. A filter is said to be consistent when the true errors are statistically compatible with the reported covariance — that is, the filter neither overestimates nor underestimates its own uncertainty.

Raw error metrics such as RMSE cannot distinguish a consistent filter (correct covariance) from an inconsistent one (wrong covariance that happens to produce low errors in a specific trial). Consistency metrics address this gap by normalising errors with respect to the predicted covariance and comparing the result against a chi-squared distribution.

8.11.2 Mathematical Theory

8.11.2.1 Normalised Estimation Error Squared (NEES)

Given the state error \(\varepsilon_k = x_k - \hat{x}_{k|k}\) and the posterior state covariance \(P_{k|k}\), the NEES at time step \(k\) is

\[\varepsilon_k^{\mathsf{T}} P_{k|k}^{-1} \varepsilon_k\]

Under the hypothesis that the filter is consistent and the errors are Gaussian, this quantity is chi-squared distributed with \(n_x\) degrees of freedom, where \(n_x\) is the state dimension.

8.11.2.2 Normalised Innovation Squared (NIS)

Given the innovation \(\nu_k = z_k - \hat{z}_{k|k-1}\) and the innovation covariance \(S_k\), the NIS at time step \(k\) is

\[\nu_k^{\mathsf{T}} S_k^{-1} \nu_k\]

Under consistency, NIS follows a chi-squared distribution with \(n_z\) degrees of freedom, where \(n_z\) is the measurement dimension. Unlike NEES, NIS is computable without knowledge of the true state and is therefore usable in real deployments.

8.11.2.3 Chi-Squared Confidence Gate

A two-sided 95% confidence region for a single NEES or NIS sample is

\[\chi^2_{n,\,0.025} \;\le\; \varepsilon_k^{\mathsf{T}} P_{k|k}^{-1} \varepsilon_k \;\le\; \chi^2_{n,\,0.975}\]

where \(\chi^2_{n,p}\) is the \(p\)-th quantile of the chi-squared distribution with \(n\) degrees of freedom.

8.11.2.4 Time-Averaged Gate

Averaging NEES or NIS over \(N\) independent samples gives a statistic that is chi-squared with \(N \cdot n\) degrees of freedom, scaled by \(1/N\). The 95% bounds for the time-averaged value are therefore

\[\frac{\chi^2_{Nn,\,0.025}}{N} \;\le\; \bar{\varepsilon} \;\le\; \frac{\chi^2_{Nn,\,0.975}}{N}\]

Averaging reduces the variance of the consistency estimate and is the standard approach in Monte-Carlo filter evaluation.

8.11.2.5 Embedded Computation

Rather than forming \(P^{-1}\) explicitly, the quadratic form \(\varepsilon^{\mathsf{T}} P^{-1} \varepsilon\) is computed by solving \(P z = \varepsilon\) for \(z\) (via Gaussian elimination with partial pivoting) and then computing \(\varepsilon^{\mathsf{T}} z\). This avoids matrix inversion, reduces floating-point operations, and is numerically more stable.

Chi-squared quantiles for degrees of freedom 1–10 at the 95% confidence level are stored in a compile-time array. Dimensions outside this range are rejected via static_assert.

8.11.3 Complexity Analysis

Case Time Space Notes
Best \(O(n^2)\) \(O(n^2)\) Dominated by back-substitution in GE
Average \(O(n^3)\) \(O(n^2)\) Gaussian elimination with partial pivoting
Worst \(O(n^3)\) \(O(n^2)\) All pivots require row swaps

All storage is stack-allocated; \(n\) is bounded at compile time by Dim.

8.11.4 Step-by-Step Walkthrough

Consider a 2-D state (\(n = 2\)) with error \(\varepsilon = [1, 2]^{\mathsf{T}}\) and covariance \(P = \mathrm{diag}(1, 4)\).

  1. Solve \(P z = \varepsilon\): \(z_1 = 1/1 = 1\), \(z_2 = 2/4 = 0.5\).
  2. Compute dot product: \(\varepsilon^{\mathsf{T}} z = 1 \cdot 1 + 2 \cdot 0.5 = 2.0\).
  3. Compare against \(\chi^2_{2, 0.025} \approx 0.051\) and \(\chi^2_{2, 0.975} \approx 7.38\).
  4. Since \(2.0 \in [0.051, 7.38]\), the filter is declared consistent at the 95% level.

8.11.5 Pitfalls & Edge Cases

A singular covariance matrix causes the linear solve to fail; the implementation detects near-zero diagonal pivots and returns std::nullopt. Callers must check the optional before using the value.

A single sample NEES or NIS value has high variance under chi-squared; a filter may appear inconsistent simply due to random variation. Time-averaging over many Monte-Carlo runs is the statistically correct procedure.

NEES requires ground-truth state access and is therefore only applicable in simulation. NIS is the consistency monitor of choice for deployed systems.

8.11.6 Variants & Generalizations

The average NEES over a Monte-Carlo ensemble of \(M\) runs and \(K\) time steps yields \(M \cdot K\) samples; consistency must hold jointly across the ensemble, not just per run. Extensions to non-Gaussian cases (e.g., particle filters) use empirical quantiles rather than chi-squared bounds.

8.11.7 Applications

8.11.8 Connections to Other Algorithms

NEES and NIS are statistical companions to the standard Kalman filter update step. They depend on the covariance propagation produced by the filters/active family (KF, EKF, UKF). The covariance being symmetric positive-definite, the linear solve reuses math::CholeskyDecomposition::Solve (factorisation plus forward/back substitution), keeping the metric a dependency-free math primitive.

8.12 Angle Utilities

8.12.1 Overview & Motivation

Angles live on a circle, not a line: a heading of \(179^\circ\) and one of \(-179^\circ\) are \(2^\circ\) apart, not \(358^\circ\). Controllers that subtract angles naively command a full turn the wrong way, integrators that never wrap lose precision as the value grows, and encoders or IMUs report wrapped values that an estimator must turn back into a continuous signal. These bugs recur in every module that touches angles, so the library provides one stateless, tested set of helpers with one convention: wrap into a canonical interval, take the shortest signed difference, and unwrap a wrapped measurement.

8.12.2 Mathematical Theory

8.12.2.1 Wrapping

Two angles are equivalent when they differ by a multiple of \(2\pi\). Wrapping picks the representative inside a fixed half-open interval of length \(2\pi\):

\[\operatorname{wrap}_{(-\pi,\pi]}(a) = r - \pi,\;\; r \in (0, 2\pi],\; r \equiv a + \pi \pmod{2\pi} \qquad \operatorname{wrap}_{[0,2\pi)}(a) = a \bmod 2\pi\]

The floating-point remainder (fmod) is truncated — it keeps the sign of the dividend. The implementation therefore shifts the angle so the interval starts at zero, takes the remainder \(r \in (-2\pi, 2\pi)\), adds \(2\pi\) if \(r\) is not positive (signed case) or is negative (unsigned case), and shifts back.

Convention (normative): the signed interval is \((-\pi, \pi]\); both \(+\pi\) and \(-\pi\) map to \(+\pi\), so a half turn has exactly one representation. The unsigned interval is \([0, 2\pi)\) and never returns \(2\pi\).

8.12.2.2 Shortest signed difference

For target \(\theta_t\) and current \(\theta_c\):

\[\Delta(\theta_t, \theta_c) = \operatorname{wrap}_{(-\pi,\pi]}(\theta_t - \theta_c)\]

Its magnitude is the shortest arc and its sign is the direction of travel from \(\theta_c\) to \(\theta_t\).

8.12.2.3 Unwrapping

Given the previous continuous value \(u_{k-1}\) and a new wrapped sample \(w_k\):

\[u_k = u_{k-1} + \Delta(w_k, u_{k-1})\]

This recovers the true continuous angle as long as consecutive true samples differ by less than \(\pi\); otherwise the step is aliased by a multiple of \(2\pi\).

8.12.3 Complexity Analysis

Operation Time Space Notes
Wrap to \((-\pi,\pi]\) \(O(1)\) \(O(1)\) One fmod, one compare, a few adds
Wrap to \([0,2\pi)\) \(O(1)\) \(O(1)\) One fmod, two compares, one conditional add
Shortest difference \(O(1)\) \(O(1)\) One subtraction plus one signed wrap
Unwrap \(O(1)\) \(O(1)\) One shortest difference plus one add; no state

Cost does not depend on the magnitude of the input: the remainder is a single operation, not a loop subtracting \(2\pi\).

8.12.4 Step-by-Step Walkthrough

Shortest difference, target \(170^\circ\), current \(-170^\circ\): plain difference \(= 340^\circ\). Shift: \(340 + 180 = 520\). Remainder mod \(360\): \(160\) (positive, no correction). Shift back: \(160 - 180 = -20^\circ\). The short way is \(20^\circ\) in the negative direction, not \(340^\circ\) forward.

Wrap, \(7\pi/3\): shift: \(7\pi/3 + \pi = 10\pi/3\). Remainder mod \(2\pi\): \(10\pi/3 - 2\pi = 4\pi/3\). Shift back: \(4\pi/3 - \pi = \pi/3\). The unsigned wrap gives the same \(\pi/3\).

Half turn: \(a = -\pi\) shifts to \(0\); the remainder is \(0\), which is not positive, so \(2\pi\) is added and the result is \(2\pi - \pi = +\pi\).

Unwrap, previous \(358^\circ\), new sample \(2^\circ\): \(\Delta = \operatorname{wrap}(2 - 358) = \operatorname{wrap}(-356) = +4^\circ\), so the continuous value is \(362^\circ\).

8.12.5 Pitfalls & Edge Cases

Precision degrades with \(|a|\) — the remainder itself is exact, but \(2\pi\) is not representable, so each full turn removed carries the representation error of \(2\pi\) (about \(1.7\times10^{-7}\) rad in single precision) and the total error grows like \(|a|\,\varepsilon\). Wrap at the source, every integration step, rather than after long accumulation. An unwrapped signal is unbounded by design, so its own resolution shrinks as it grows.

Aliasing in unwrap — if the true angle moves by \(\pi\) or more between samples, the direction is ambiguous and the result is off by a multiple of \(2\pi\). Sample faster than half a turn per period.

Half-turn asymmetry — because \(\pm\pi\) both map to \(+\pi\), the shortest difference is not antisymmetric at exactly a half turn: swapping the arguments gives \(+\pi\) again, not \(-\pi\).

Rounding at the unsigned boundary — a tiny negative remainder plus \(2\pi\) can round to exactly \(2\pi\); the unsigned wrap detects this and returns \(0\), so the result stays strictly inside \([0, 2\pi)\).

Non-finite input — infinities and NaN have no meaningful remainder; inputs must be finite.

Float-only — floating-point types only, enforced at compile time; no fixed-point variants.

8.12.6 Variants & Generalizations

Degrees or turns as the unit only rescale the period (\(360\) or \(1\) instead of \(2\pi\)); the unit-turn form avoids the inexact \(2\pi\) entirely. Other canonical intervals, such as \([-\pi, \pi)\), differ only in which half-turn representative is kept. For rotations in 3D, quaternions avoid the wrap problem altogether, at the cost of a double cover (\(q\) and \(-q\)).

8.12.7 Applications

8.12.8 Connections to Other Algorithms

Used by the complementary filter in its heading mode. Related to the quaternion rotation representation, which has no wrap discontinuity.

8.13 Tridiagonal Solver (Thomas Algorithm)

8.13.1 Overview & Motivation

A tridiagonal system has nonzeros only on the main diagonal and its two neighbours. Spline interpolation, implicit heat/diffusion steps and several smoothing problems all reduce to such systems. A dense solver wastes cubic time and quadratic memory on zeros; the Thomas algorithm is Gaussian elimination that skips every zero, so it runs in linear time using three short bands.

The elimination sweep depends only on the matrix, so it is performed once and reused: each new right-hand side costs two short sweeps and no division. This fits the common case where the matrix depends on fixed geometry (for example knot spacing) while the right-hand side changes per channel or per tick.

8.13.2 Mathematical Theory

Let \(A \in \mathbb{R}^{n \times n}\) be tridiagonal with sub-diagonal \(a_k = A_{k,k-1}\) (\(a_0\) unused), diagonal \(b_k\) and super-diagonal \(c_k = A_{k,k+1}\) (\(c_{n-1}\) unused). The system \(Ax = d\) reads

\[a_k x_{k-1} + b_k x_k + c_k x_{k+1} = d_k, \quad k = 0, \dots, n-1\]

8.13.2.1 Forward elimination (matrix only)

Eliminating \(a_k\) with the already-reduced row above leaves an upper bidiagonal system with unit diagonal. Define the pivots and modified super-diagonal

\[p_0 = b_0, \qquad p_k = b_k - a_k\, c'_{k-1}, \qquad c'_k = \frac{c_k}{p_k}\]

The reciprocals \(1/p_k\) are stored, so \(c'_k = c_k \cdot (1/p_k)\). This is the factorization \(A = LU\) with \(L\) lower bidiagonal (diagonal \(p_k\), sub-diagonal \(a_k\)) and \(U\) unit upper bidiagonal (super-diagonal \(c'_k\)).

8.13.2.2 Forward substitution (right-hand side)

\[y_0 = \frac{d_0}{p_0}, \qquad y_k = \frac{d_k - a_k\, y_{k-1}}{p_k}\]

8.13.2.3 Back substitution

\[x_{n-1} = y_{n-1}, \qquad x_k = y_k - c'_k\, x_{k+1}, \quad k = n-2, \dots, 0\]

Division by \(p_k\) is replaced by multiplication with the stored reciprocal.

8.13.3 Complexity Analysis

Operation Time Space Notes
Factorization \(O(n)\) \(O(n)\) \(n\) divisions, one multiply-subtract per row
Solve (per RHS) \(O(n)\) \(O(n)\) About \(3n\) multiply-adds, no division
Dense LU, for contrast \(O(n^3)\) \(O(n^2)\) Operates on all the zeros

Storage is three bands of fixed capacity; nothing is allocated dynamically. Factoring once and solving \(m\) right-hand sides costs \(O(n + mn)\) with all divisions paid up front.

8.13.4 Step-by-Step Walkthrough

Take \(A = \operatorname{tridiag}(-1, 2, -1)\) with \(n = 4\), so \(a_k = c_k = -1\), \(b_k = 2\).

\(k\) \(p_k\) \(1/p_k\) \(c'_k\)
0 \(2\) \(1/2\) \(-1/2\)
1 \(2 - (-1)(-1/2) = 3/2\) \(2/3\) \(-2/3\)
2 \(2 - (-1)(-2/3) = 4/3\) \(3/4\) \(-3/4\)
3 \(2 - (-1)(-3/4) = 5/4\) \(4/5\) unused

Right-hand side \(d = (1, 0, 0, 1)\): forward \(y = (1/2,\ 1/3,\ 1/4,\ 1)\); back substitution gives \(x_3 = 1\), \(x_2 = 1/4 + \tfrac34 = 1\), \(x_1 = 1/3 + \tfrac23 = 1\), \(x_0 = 1/2 + 1/2 = 1\), so \(x = (1, 1, 1, 1)\).

Right-hand side \(d = (1, 2, 3, 4)\) reuses the same table: forward \(y = (1/2,\ 5/3,\ 7/2,\ 6)\); back substitution gives \(x_3 = 6\), \(x_2 = 7/2 + 9/2 = 8\), \(x_1 = 5/3 + 16/3 = 7\), \(x_0 = 1/2 + 7/2 = 4\), so \(x = (4, 7, 8, 6)\).

Verify: row 1 of \(Ax\) is \(-4 + 14 - 8 = 2\) ✓ and row 3 is \(-8 + 12 = 4\) ✓.

8.13.5 Pitfalls & Edge Cases

8.13.6 Variants & Generalizations

8.13.7 Applications

8.13.8 Connections to Other Algorithms

Algorithm Relationship
LU Decomposition General fallback with partial pivoting; \(O(n^3)\) and dense
Triangular Solve Same forward/back substitution idea, applied to full triangular factors
Levinson–Durbin Another structured solver, for Toeplitz systems
Cubic spline interpolation Main consumer; plans the second derivatives by solving one tridiagonal system

8.14 Cubic Spline Interpolation (Vector-Valued, Clamped / Natural)

8.14.1 Overview & Motivation

Waypoint lists (robot teach-in programs, CNC paths, planner output), calibration tables and resampled signals all need a smooth curve through given samples. A single high-order polynomial through many points rings between them; a piecewise-linear curve has corners that excite mechanics. A cubic spline joins one cubic per interval so that value, slope and curvature are continuous (\(C^2\)), and it stays well behaved however many knots are used.

Several channels (the joints of a robot, the axes of a path, the columns of a table) share the same abscissae. They are fitted together: the linear system depends only on the knot spacing, so it is factored once and every channel reuses the factors. Fitting is a single linear-time pass on fixed storage; evaluation is a binary search plus a cubic, cheap and deterministic.

8.14.2 Mathematical Theory

8.14.2.1 Unknowns and per-interval form

Knots \(x_0 < x_1 < \dots < x_{K-1}\) with spans \(h_k = x_{k+1} - x_k\) and values \(y_k\) (one component per channel; channels are independent). The unknowns are the knot second derivatives \(M_k = y''(x_k)\). On interval \(k\), with \(\tau = x - x_k \in [0, h_k]\),

\[y(\tau) = y_k + \beta_k\,\tau + \tfrac{M_k}{2}\,\tau^2 + d_k\,\tau^3, \qquad d_k = \frac{M_{k+1} - M_k}{6\,h_k}, \qquad \beta_k = \frac{y_{k+1} - y_k}{h_k} - \frac{h_k\,(2M_k + M_{k+1})}{6}\]

The cubic hits both knot values and both knot curvatures, so value is \(C^0\) and curvature is \(C^2\) by construction. Only slope continuity remains to be imposed.

8.14.2.2 Interior rows

Equating the slope at the end of interval \(k-1\), \(\beta_{k-1} + h_{k-1}(M_{k-1} + M_k)/2\), with \(\beta_k\) and writing \(s_k = (y_{k+1} - y_k)/h_k\) gives, for \(k = 1, \dots, K-2\),

\[h_{k-1} M_{k-1} + 2(h_{k-1} + h_k)\,M_k + h_k M_{k+1} = 6\,(s_k - s_{k-1})\]

Each row couples a knot to its two neighbours only, so the system is tridiagonal with \(K-2\) rows. Two end conditions supply the missing two.

8.14.2.3 End conditions

Condition First row Last row
Clamped (end slopes \(\sigma_0\), \(\sigma_1\)) \(2h_0 M_0 + h_0 M_1 = 6\,(s_0 - \sigma_0)\) \(h_{K-2} M_{K-2} + 2h_{K-2} M_{K-1} = 6\,(\sigma_1 - s_{K-2})\)
Natural \(M_0 = 0\) \(M_{K-1} = 0\)

Clamping fixes the end slopes and leaves the end second derivatives free; natural ends do the reverse. Default slopes are zero.

8.14.2.4 Solvability

Every interior row has diagonal \(2(h_{k-1} + h_k)\) against off-diagonal sum \(h_{k-1} + h_k\); clamped end rows have \(2h\) against \(h\); natural rows have \(1\) against \(0\). All rows are strictly diagonally dominant, so the matrix is nonsingular and the Thomas algorithm is stable without pivoting. The matrix contains only spans and the end-condition kind, never values or slopes: one factorization serves all channels, and only the right-hand sides differ.

8.14.2.5 Evaluation

Clamp \(x\) to \([x_0, x_{K-1}]\), find the interval by binary search, then use Horner’s scheme per channel:

\[y = y_k + \tau\,\bigl(\beta_k + \tau\,(\tfrac{M_k}{2} + \tau\,d_k)\bigr), \quad y' = \beta_k + \tau\,(M_k + 3 d_k \tau), \quad y'' = M_k + 6 d_k \tau\]

An abscissa exactly on an interior knot selects the interval starting there; the last knot selects the final interval. Both choices give the same result because the spline is \(C^2\).

8.14.3 Complexity Analysis

Operation Time Space Notes
Factorization \(O(K)\) three bands Once per fit, shared by all channels
Fit \(O(D\,K)\) \(K_{\max}(2 + 2D)\) scalars \(D\) channels, each one right-hand side build and one solve of \(O(K)\); no iteration
Evaluate \(O(\log K + D)\) \(O(1)\) Binary search, then three Horner polynomials per channel

Storage holds the knots, their reciprocal spans, the values and the second derivatives: \(K_{\max}(2 + 2D)\) scalars, where \(K_{\max}\) is the compile-time capacity, plus the three bands of the solver. All of it is bounded and on the stack or inside the object; no heap.

8.14.4 Step-by-Step Walkthrough

Knots \((0, 1, 2)\), values \((0, 1, 0)\), so \(h_0 = h_1 = 1\) and \(s_0 = 1\), \(s_1 = -1\).

Clamped, zero end slopes. The system is \[\begin{pmatrix} 2 & 1 & 0 \\ 1 & 4 & 1 \\ 0 & 1 & 2 \end{pmatrix} M = \begin{pmatrix} 6 \\ -12 \\ 6 \end{pmatrix}\]

\(k\) Pivot \(p_k\) \(c'_k\) Forward \(y_k\) Back \(M_k\)
0 \(2\) \(1/2\) \(3\) \(3 + 3 = 6\)
1 \(4 - 1/2 = 7/2\) \(2/7\) \(-30/7\) \(-30/7 - 12/7 = -6\)
2 \(2 - 2/7 = 12/7\) unused \(6\) \(6\)

So \(M = (6, -6, 6)\). Interval 0 has \(\beta_0 = 1 - (12 - 6)/6 = 0\) and \(d_0 = -2\), giving \(y(\tau) = 3\tau^2 - 2\tau^3\). Hence \(y(0.5) = 0.75 - 0.25 = 0.5\), \(y'(0.5) = 3 - 1.5 = 1.5\) and \(y''(0.5) = 0\). The curve leaves and enters every knot flat, like a smooth step.

Natural. The end rows reduce to \(M_0 = M_2 = 0\), so the middle row is \(4M_1 = -12\) and \(M = (0, -3, 0)\). Interval 0 has \(\beta_0 = 1 + 3/6 = 1.5\) and \(d_0 = -0.5\), giving \(y(\tau) = 1.5\tau - 0.5\tau^3\). Hence \(y(0.5) = 0.75 - 0.0625 = 0.6875\) and \(y'(0.5) = 1.125\). The end slope \(y'(0) = 1.5\) is free, and the curvature is zero there.

8.14.4.1 Verification

Random fits are checked for interpolation of every knot, for continuity of first and second derivative across each interior knot, and for the end conditions (prescribed slopes when clamped, zero curvature when natural). Continuity holds to about \(10^{-13}\) in double precision and to the single-precision rounding level in float.

8.14.5 Pitfalls & Edge Cases

8.14.6 Variants & Generalizations

8.14.7 Applications

8.14.8 Connections to Other Algorithms

Algorithm Relationship
Tridiagonal Solver Solves the second-derivative system; its factorization is reused across channels
Polynomial Profile The two-knot clamped case is the same cubic Hermite polynomial; the spline generalizes it to many knots with \(C^2\) joins
Polynomial fitting Least-squares fit instead of interpolation

8.15 SE(3) Transform, Twists and Wrenches

8.15.1 Overview & Motivation

Every pose-level algorithm — manipulator kinematics and Jacobians, pose inverse kinematics, Cartesian interpolation, task-space control, drone navigation, visual odometry — manipulates the same few objects: a rigid transform, a velocity-like 6-vector (a twist), a force-like 6-vector (a wrench), and the maps between a constant twist and the finite motion it produces. Defining them once, with one ordering convention, prevents the silent sign and ordering mismatches that appear when independently written modules exchange 6-vectors.

A transform is a rotation plus a translation: twelve scalars, composed with a few \(3\times3\) products and inverted in closed form, with no matrix solve. A twist is re-expressed in another frame by the adjoint: rotate both parts, then add the lever-arm effect of the frame offset to the linear part. Wrenches follow the dual rule so that power is the same in every frame. The exponential turns “rotate about a line while sliding along it” into a finite motion; the logarithm recovers that motion, which is exactly what pose-error feedback needs.

8.15.2 Mathematical Theory

8.15.2.1 Rigid Transform

A transform \(T = (R, p)\) with \(R \in SO(3)\) (\(R^T R = I\), \(\det R = +1\)) and \(p \in \mathbb{R}^3\) maps coordinates in frame B to frame A, \(x_A = R\,x_B + p\). Its homogeneous form is

\[T = \begin{pmatrix} R & p \\ 0 & 1 \end{pmatrix}.\]

\[T_1 T_2 = (R_1 R_2,\; R_1 p_2 + p_1), \qquad T^{-1} = (R^T,\; -R^T p), \qquad T\cdot x = R x + p.\]

The composition applies \(T_2\) first. The inverse follows from \(R^{-1} = R^T\). A free direction is only rotated, \(x \mapsto R x\); the translation is ignored.

8.15.2.2 Ordering Convention (normative)

All 6-vectors are ordered linear part first: twists \(V = (v;\, \omega)\) and wrenches \(W = (f;\, n)\). This matches a geometric Jacobian with linear rows first and the tangent ordering of Sophus and manif. Lynch & Park and Murray et al. use \((\omega;\, v)\); the two conventions differ by a block permutation, so any formula from those texts converts by swapping the blocks (for the adjoint below, the rotation blocks stay on the diagonal and the \([p]_\times R\) block moves to the lower left).

8.15.2.3 Adjoint, Twists and Wrenches

Let \([a]_\times\) be the skew matrix with \([a]_\times b = a \times b\). A twist expressed in frame B is re-expressed in frame A by

\[V_A = \mathrm{Ad}_T V_B, \qquad \mathrm{Ad}_T = \begin{pmatrix} R & [p]_\times R \\ 0 & R \end{pmatrix}, \qquad \text{i.e. } v' = R v + p \times R\omega,\;\; \omega' = R\omega.\]

The wrench map is the power-consistent dual, \(\mathrm{Ad}_T^{-T}\):

\[f' = R f, \qquad n' = R n + p \times R f.\]

Power is frame invariant because the cross terms cancel by the antisymmetry of the scalar triple product:

\[W' \cdot V' = f \cdot v + n \cdot \omega + \underbrace{Rf \cdot (p \times R\omega) + (p \times Rf) \cdot R\omega}_{=\,0} = W \cdot V.\]

The adjoint is a group homomorphism, \(\mathrm{Ad}_{AB} = \mathrm{Ad}_A\,\mathrm{Ad}_B\), and \(\mathrm{Ad}_{T^{-1}} = \mathrm{Ad}_T^{-1}\).

8.15.2.4 Exponential

A twist \(\xi = (v;\, \omega)\) held for \(\theta\) produces the finite motion \(e^{[\xi]\theta}\) with \([\xi] = \begin{pmatrix} [\omega]_\times & v \\ 0 & 0 \end{pmatrix}\). It has the block form \(\begin{pmatrix} e^{K\theta} & G v \\ 0 & 1 \end{pmatrix}\) with \(K = [\omega]_\times\) and \(G = \int_0^\theta e^{Ks}\,ds\): Rodrigues’ formula for the rotation, integrated term by term for the translation. Without normalising \(\omega\), and with \(\varphi = \theta\,\|\omega\|\),

\[R = I + \theta\,a_1\,K + \theta^2 a_2\,K^2, \qquad p = \theta v + \theta^2 a_2\,K v + \theta^3 a_3\,K^2 v,\]

\[a_1 = \frac{\sin\varphi}{\varphi}, \qquad a_2 = \frac{1-\cos\varphi}{\varphi^2} = \frac{2\sin^2(\varphi/2)}{\varphi^2}, \qquad a_3 = \frac{\varphi - \sin\varphi}{\varphi^3}.\]

Below \(\varphi = 0.1\) the coefficients switch to short Taylor series,

\[a_1 \approx 1 - \tfrac{\varphi^2}{6} + \tfrac{\varphi^4}{120}, \qquad a_2 \approx \tfrac12 - \tfrac{\varphi^2}{24} + \tfrac{\varphi^4}{720}, \qquad a_3 \approx \tfrac16 - \tfrac{\varphi^2}{120} + \tfrac{\varphi^4}{5040},\]

so there is no singular branch at \(\omega = 0\) (pure translation falls out as \(R = I\), \(p = \theta v\)) and no \(0/0\) or cancellation for small \(\varphi\). Because \(\theta\) scales the whole twist, a non-unit \(\omega\) needs no normalisation.

Re-expressing a twist in another frame generates the same motion conjugated by that frame:

\[\mathrm{Exp}(\mathrm{Ad}_A\,\xi,\, t) = A\,\mathrm{Exp}(\xi,\, t)\,A^{-1}.\]

8.15.2.5 Logarithm of a Rotation

Let \(s = \tfrac12\,\mathrm{vee}(R - R^T) = \sin\varphi\,\hat\omega\) and \(\cos\varphi = \tfrac12(\operatorname{tr}R - 1)\). The angle is

\[\varphi = \mathrm{atan2}\big(\|s\|,\; \cos\varphi\big),\]

which is accurate near \(0\) and \(\pi\), unlike \(\arccos\).

\[\hat\omega_i \hat\omega_j = \frac{\tfrac12(R_{ij} + R_{ji}) - \delta_{ij}\cos\varphi}{1 - \cos\varphi}.\]

The pivot component \(i\) is the largest diagonal entry, so \(\hat\omega_i^2 \ge \tfrac13\) and the divisions are benign; \(\hat\omega_i = \sqrt{\hat\omega_i^2}\), the other components follow from the off-diagonal products, and the sign is taken from \(s_i\). At exactly \(\pi\) either sign is valid. The result satisfies \(\|\omega\| \le \pi\): rotations are re-expressed the short way.

8.15.2.6 Logarithm of a Transform

With \(\omega = \mathrm{Log}(R)\) and \(\varphi = \|\omega\|\), the inverse of the integral \(G\) is

\[v = \Big(I - \tfrac12[\omega]_\times + c(\varphi)\,[\omega]_\times^2\Big)\,p, \qquad c(\varphi) = \frac{1 - \tfrac{\varphi}{2}\cot\tfrac{\varphi}{2}}{\varphi^2} \approx \tfrac1{12} + \tfrac{\varphi^2}{720} + \tfrac{\varphi^4}{30240} \;\;(\varphi < 0.1).\]

The result is \((v;\, \omega)\) with \(\theta = 1\), so \(\mathrm{Exp}(\mathrm{Log}(T), 1) = T\). The coefficient stays finite on \([0, \pi]\), reaching \(1/\pi^2\) at \(\varphi = \pi\).

8.15.2.7 Pose Error

With target \((R_t, p_t)\) and current \((R_c, p_c)\), the base-frame error is

\[e = \big(p_t - p_c;\;\; \mathrm{Log}(R_t R_c^T)\big),\]

the translation difference stacked on the rotation vector that carries the current orientation onto the target. It has the linear-first ordering of a geometric Jacobian, and \(\|e_\omega\| \le \pi\) always points the short way round.

8.15.3 Complexity Analysis

Operation Time Space Notes
Compose O(1) O(1) one \(3\times3\) product, one matrix–vector product
Inverse O(1) O(1) transpose and one matrix–vector product; no solve
Apply / rotate O(1) O(1) one matrix–vector product (plus 3 additions for apply)
Twist / wrench transform O(1) O(1) two matrix–vector products and one cross product, no \(6\times6\) formed
Adjoint O(1) O(1) \(36\) scalars, one extra \(3\times3\) product; built only on request
Exp O(1) O(1) two sines of \(\varphi\) and \(\varphi/2\), none below \(\varphi = 0.1\)
Log O(1) O(1) one atan2; a half-angle sine/cosine pair above \(\varphi = 0.1\); one square root for large angles

A transform holds \(12\) scalars; the \(6\times6\) adjoint appears only when requested. All operations are stack-only: no heap, no recursion, constant work per call.

8.15.4 Step-by-Step Walkthrough

Apply. \(R = R_z(\pi/2)\), \(p = (1,0,0)\): \(R\,(1,0,0) = (0,1,0)\), plus \(p\) gives \((1,1,0)\).

Exp of a screw. Rotation about \(\hat z\) through the point \((1,0,0)\): \(\omega = \hat z\), \(v = -\hat z \times (1,0,0) = (0,-1,0)\), \(\theta = \pi/2\).

Step Value
\(\varphi = \theta\|\omega\|\) \(\pi/2 \ge 0.1\), direct forms
Coefficients \(a_1 = 2/\pi\), \(a_2 = 4/\pi^2\), \(a_3 = (\pi/2 - 1)/(\pi/2)^3\)
Vectors \(Kv = \hat z \times v = (1,0,0)\), \(K^2 v = (0,1,0)\)
\(R\) \(\theta a_1 = 1\), \(\theta^2 a_2 = 1\): \(R = I + K + K^2 = R_z(\pi/2)\)
\(p\) \(\tfrac{\pi}{2}(0,-1,0) + (1,0,0) + (\tfrac{\pi}{2} - 1)(0,1,0) = (1,-1,0)\)

Check: the origin sits at \((-1,0,0)\) relative to the axis; a quarter turn takes it to \((0,-1,0)\), and adding the axis point gives \((1,-1,0)\).

Log of that transform. \(s = (0,0,1)\), \(\cos\varphi = 0\), so \(\varphi = \pi/2\) and \(\omega = (0,0,\pi/2)\). Then \(\omega \times p = (\tfrac\pi2, \tfrac\pi2, 0)\), \(\omega \times (\omega \times p) = (-\tfrac{\pi^2}{4}, \tfrac{\pi^2}{4}, 0)\), and \(c(\pi/2)\,\pi^2/4 = 1 - \pi/4\), so

\[v = (1 - \tfrac\pi4 - (1 - \tfrac\pi4),\; -1 - \tfrac\pi4 + (1 - \tfrac\pi4),\; 0) = (0, -\tfrac\pi2, 0).\]

The result \(\big((0,-\tfrac\pi2,0);\,(0,0,\tfrac\pi2)\big)\) is \(\theta\xi\), as expected.

Pose error, short way round. Target rotated \(+190^\circ\) about \(\hat z\) relative to the current pose: \(\cos\varphi = \cos 190^\circ = -0.9848\), \(\|s\| = \sin 170^\circ = 0.1736\), so \(\varphi = 170^\circ = 2.967\) rad \(> \pi/2\). The symmetric part gives \(\hat\omega_z^2 = (1 + 0.9848)/1.9848 = 1\), and \(s_z = \sin 190^\circ < 0\) selects \(-\hat z\). The rotation error is \(-170^\circ\cdot\hat z\), not \(+190^\circ\).

8.15.5 Pitfalls & Edge Cases

8.15.6 Variants & Generalizations

Variant Key difference
Angular-first ordering \((\omega;\, v)\) Same algebra with blocks permuted; used by Lynch & Park and Murray et al.
Body vs. spatial twist Body twists are \(T^{-1}\dot T\), spatial twists \(\dot T\,T^{-1}\); related by the adjoint
Quaternion rotation part Four scalars and cheap renormalisation; the rotation vector is the vector part scaled by \(\varphi/\sin(\varphi/2)\)
Dual quaternions Rotation and translation in one algebra; screw interpolation
Similarity group Adds a uniform scale for monocular visual odometry

8.15.7 Applications

8.15.8 Connections to Other Algorithms

9 Motion Profiles

9.1 Polynomial Point-to-Point Profile

9.1.1 Overview & Motivation

A polynomial profile is a closed-form time law that drives one axis from a start state to a goal state over a fixed duration \(T\). A cubic matches endpoint position and velocity; a quintic additionally matches endpoint acceleration, so the motion has no acceleration step at either end.

Point-to-point moves are the bread and butter of servo motion: robot joints, CNC and pick-and-place axes, motor position loops. The coefficients come from a handful of algebraic expressions, solved once. Each servo tick is then a fixed number of multiply-adds, with no iteration, no lookup table and a deterministic cycle count.

9.1.2 Mathematical Theory

9.1.2.1 Boundary Conditions

Let \(t \in [0, T]\), \(\Delta q = q_f - q_0\), and let \(\alpha_0, \alpha_f\) be the start and end accelerations. A polynomial of degree \(n\) has \(n+1\) coefficients, so a cubic satisfies four conditions and a quintic six:

\[q(0) = q_0,\quad q(T) = q_f,\quad \dot{q}(0) = v_0,\quad \dot{q}(T) = v_f \qquad \text{(quintic also: } \ddot{q}(0) = \alpha_0,\ \ddot{q}(T) = \alpha_f\text{)}\]

The conditions are linear in the coefficients \(a_k\) of \(q(t) = \sum_k a_k t^k\) and the time basis is fixed, so the solution is a plug-in formula rather than a runtime matrix solve.

9.1.2.2 Cubic

The start conditions give \(a_0 = q_0\) and \(a_1 = v_0\). The end conditions leave a \(2\times 2\) system with the closed-form solution

\[a_2 = \frac{3\,\Delta q - (2 v_0 + v_f)\,T}{T^2}, \qquad a_3 = \frac{-2\,\Delta q + (v_0 + v_f)\,T}{T^3}\]

The acceleration is linear in \(t\) and the jerk is the constant \(6 a_3\).

9.1.2.3 Quintic

The start conditions give \(a_0 = q_0\), \(a_1 = v_0\), \(a_2 = \alpha_0 / 2\). The end conditions give

\[a_3 = \frac{20\,\Delta q - (8 v_f + 12 v_0)\,T - (3\alpha_0 - \alpha_f)\,T^2}{2\,T^3}\]

\[a_4 = \frac{-30\,\Delta q + (14 v_f + 16 v_0)\,T + (3\alpha_0 - 2\alpha_f)\,T^2}{2\,T^4}\]

\[a_5 = \frac{12\,\Delta q - 6 (v_f + v_0)\,T - (\alpha_0 - \alpha_f)\,T^2}{2\,T^5}\]

The acceleration is a cubic matching \(\alpha_0\) and \(\alpha_f\), hence continuous; the jerk is a quadratic.

9.1.2.4 Sampling

Position, velocity, acceleration and jerk are four Horner evaluations of the polynomial and its three derivatives:

\[q = a_0 + t\big(a_1 + t(a_2 + t(a_3 + t(a_4 + t\,a_5)))\big), \qquad \dddot{q} = 6 a_3 + t\,(24 a_4 + 60 a_5\, t)\]

The cubic is the same evaluation with \(a_4 = a_5 = 0\). The time is first clamped to \([0, T]\).

9.1.2.5 Rest-to-Rest Peaks

With zero end velocities (and accelerations, for the quintic) the cubic peaks at \(1.5\,\Delta q / T\) in velocity and \(6\,\Delta q / T^2\) in acceleration; the quintic at \(1.875\,\Delta q / T\) and \(\tfrac{10}{\sqrt{3}}\,\Delta q / T^2 \approx 5.77\,\Delta q / T^2\).

9.1.3 Complexity Analysis

Operation Time Space Notes
Coefficient solve \(O(1)\) \(O(1)\) Closed-form expressions, once at construction or reset
Sample \(O(\text{degree})\) \(O(1)\) Four Horner evaluations, at most 14 multiply-adds; no state change, so deterministic
Stored state n/a \(O(1)\) Six coefficients, the duration and the degree; stack only, no heap

9.1.4 Step-by-Step Walkthrough

Rest-to-rest cubic with \(q_0 = 0\), \(q_f = 1\), \(T = 2\), \(v_0 = v_f = 0\), so \(\Delta q = 1\) and \(a_0 = a_1 = 0\).

Step Computation Result
\(a_2\) \((3\cdot 1 - 0)/2^2\) \(0.75\)
\(a_3\) \((-2\cdot 1 + 0)/2^3\) \(-0.25\)
Position \(0.75\,t^2 - 0.25\,t^3 = 3(t/2)^2 - 2(t/2)^3\) \(q(1) = 0.5\), \(q(2) = 1\)
Velocity \(1.5\,t - 0.75\,t^2\) \(v(1) = 0.75 = 1.5\,\Delta q / T\) (the peak)
Acceleration \(1.5 - 1.5\,t\) \(+1.5\) at \(t=0\), \(-1.5\) at \(t=2\)
Jerk \(6 a_3\) \(-1.5\), constant

The position is the smoothstep of \(t/2\), symmetric about the midpoint, where the velocity peaks. The acceleration jumps from \(0\) at rest to \(1.5\) at the first instant and from \(-1.5\) back to \(0\) at the end.

9.1.5 Pitfalls & Edge Cases

9.1.6 Variants & Generalizations

Variant Key difference
Cubic (this) Position and velocity matched; constant jerk; acceleration discontinuous at the ends
Quintic (this) Adds acceleration matching; continuous acceleration at about \(25\%\) higher peak velocity for rest-to-rest
Path-parameter time law A 0 to 1 instance maps time to a normalized path coordinate; with \(v_0 = v_f = 1\), \(T = 1\) the cubic collapses to \(s = t\)

9.1.7 Applications

9.1.8 Connections to Other Algorithms

Algorithm Relationship
Trapezoidal velocity profile Respects velocity and acceleration limits with a free duration; polynomials fix the duration and let the peaks follow
S-curve profile Bounds jerk explicitly under limits; the quintic only smooths the endpoints
Cubic spline interpolation Handles many via points with continuous acceleration; for two knots it reduces to this cubic

9.2 Trapezoidal Profile (Linear Segment with Parabolic Blends)

9.2.1 Overview & Motivation

A point-to-point move has to reach its goal as fast as the actuator allows without saturating the drive. The trapezoidal profile — linear segment with parabolic blends (LSPB) — does this with the simplest rule that respects both a velocity ceiling and an acceleration ceiling: accelerate at the maximum rate, coast at the maximum speed, decelerate at the maximum rate. Velocity plotted against time is a trapezoid; a move too short to reach the ceiling becomes a triangle.

From the limits and the distance alone, the plan is the minimum-time one. A second mode fixes the duration instead, which multi-axis synchronization needs: all axes must finish together, so the faster ones are slowed rather than the slowest sped up.

9.2.2 Mathematical Theory

9.2.2.1 Problem Statement

Rest-to-rest move of length \(d = |q_f - q_0|\) in direction \(\sigma = \pm 1\) (positive when \(q_f \ge q_0\)), with limits \(v_{max} > 0\) and \(a_{max} > 0\). The profile is planned for the magnitude and mirrored by \(\sigma\).

9.2.2.2 Minimum-Time Plan

A ramp between rest and speed \(v\) takes \(v/a_{max}\) and covers \(v^2/(2a_{max})\), so the two ramps together consume the blend distance \(v^2/a_{max}\). Comparing \(d\) with the blend distance at \(v_{max}\) selects the shape:

\[ \begin{cases} d \ge v_{max}^2/a_{max}: & v = v_{max}, \quad t_a = v/a_{max}, \quad t_c = (d - v^2/a_{max})/v \quad\text{(trapezoid)} \\ d < v_{max}^2/a_{max}: & v = \sqrt{d\,a_{max}}, \quad t_a = v/a_{max}, \quad t_c = 0 \quad\text{(triangle)} \end{cases} \]

The total time is \(T = 2t_a + t_c\). The cases agree at \(d = v_{max}^2/a_{max}\), so \(T\) is continuous in \(d\).

9.2.2.3 Sampling

With \(v\), \(t_a\), \(t_c\), \(T\) precomputed, the unsigned state is:

Phase Interval Acceleration Velocity Position
Parabolic ramp-up \(0 \le t < t_a\) \(a_{max}\) \(a_{max}\,t\) \(\tfrac12 a_{max}\,t^2\)
Linear cruise \(t_a \le t < t_a + t_c\) \(0\) \(v\) \(v\,(t - t_a/2)\)
Parabolic ramp-down \(t_a + t_c \le t < T\) \(-a_{max}\) \(a_{max}\,(T-t)\) \(d - \tfrac12 a_{max}\,(T-t)^2\)

Velocity, acceleration and the position offset are multiplied by \(\sigma\). At and beyond \(t = T\) the profile holds the goal at rest (velocity and acceleration zero); times before \(0\) are treated as \(t = 0\), holding the start with zero velocity. The reported jerk is zero in every phase.

9.2.2.4 Fixed-Duration Plan

The acceleration \(a_{max}\) is kept and the cruise speed is lowered until the move takes exactly \(T\). The ramps cover \(v\,t_a\) in total and the cruise covers \(v\,(T - 2t_a)\), so with \(t_a = v/a_{max}\)

\[ d = v\left(T - \frac{v}{a_{max}}\right) \;\Longleftrightarrow\; v^2 - a_{max}T\,v + a_{max}\,d = 0 . \]

The smaller root keeps \(t_c \ge 0\). Multiplying it by its conjugate gives a form with no subtraction of nearly equal numbers:

\[ v = \frac{a_{max}T - \sqrt{a_{max}^2T^2 - 4a_{max}d}}{2} = \frac{2\,a_{max}\,d}{a_{max}T + \sqrt{a_{max}^2T^2 - 4a_{max}d}} . \]

A real root exists iff \(a_{max}\,T^2 \ge 4d\). Equality gives a double root \(v = a_{max}T/2\) and a triangle: \(T\) is then the shortest duration achievable at this acceleration. Otherwise \(t_a = v/a_{max}\) and \(t_c = T - 2t_a\).

9.2.2.5 Multi-Axis Synchronization

Let \(T_{sync}\) be the longest axis time and \(\lambda = T_{sync}/T_{axis} \ge 1\). Planning an axis again with \((v_{max}/\lambda,\; a_{max}/\lambda^2)\) leaves the blend distance \(v_{max}^2/a_{max}\) unchanged, so the same shape is selected, while \(t_a\) and \(t_c\) both scale by \(\lambda\): the duration is exactly \(\lambda\,T_{axis}\). Velocity is divided by \(\lambda\) and acceleration by \(\lambda^2\).

The fixed-duration plan is the alternative. It keeps \(a_{max}\) and only lowers the cruise speed: the smaller root decreases as \(T\) grows, so for \(T \ge T_{min}\) the cruise speed never exceeds \(v_{max}\).

9.2.3 Complexity Analysis

Operation Time Space Notes
Minimum-time plan \(O(1)\) \(O(1)\) One comparison, one square root (triangle only)
Fixed-duration plan \(O(1)\) \(O(1)\) One square root, one division
Sample \(O(1)\) \(O(1)\) One phase branch, a few multiply-adds; no state change

Memory is a fixed handful of scalars: start, direction, distance, acceleration, peak speed and three phase times.

9.2.4 Step-by-Step Walkthrough

Trapezoid. \(d = 5\), \(v_{max} = 1\), \(a_{max} = 2\). The blend distance is \(1/2 = 0.5 \le 5\), so a cruise exists: \(t_a = 0.5\), \(t_c = (5 - 0.5)/1 = 4.5\), \(T = 2(0.5) + 4.5 = 5.5\).

\(t\) Phase Position Velocity Acceleration
\(0.25\) ramp-up \(\tfrac12\cdot 2\cdot 0.25^2 = 0.0625\) \(0.5\) \(2\)
\(3\) cruise \(1\cdot(3 - 0.25) = 2.75\) \(1\) \(0\)
\(5.25\) ramp-down \(5 - \tfrac12\cdot 2\cdot 0.25^2 = 4.9375\) \(0.5\) \(-2\)
\(\ge 5.5\) held \(5\) \(0\) \(0\)

The ramp-up ends at position \(0.25\), and the cruise formula gives the same value at \(t = 0.5\). The triangle threshold for these limits is \(d = 0.5\); for \(d = 0.25\), \(v = \sqrt{0.5} \approx 0.7071\) and \(T \approx 0.7071\), with no cruise.

Fixed duration. \(d = 4\), \(a_{max} = 1\), \(T = 5\): discriminant \(25 - 16 = 9\), \(v = 2\cdot 4/(5 + 3) = 1\), \(t_a = 1\), \(t_c = 3\); check \(1\cdot(5 - 1) = 4\). For \(d = 5\), \(a_{max} = 2\), \(T = 6\): discriminant \(144 - 40 = 104\), \(v = 20/(12 + \sqrt{104}) \approx 0.900980\).

9.2.5 Pitfalls & Edge Cases

9.2.6 Variants & Generalizations

The S-curve profile adds a jerk limit (seven segments) for continuous acceleration at the cost of a longer move. Polynomial profiles (cubic, quintic) are fixed-duration and smooth by construction but enforce no velocity or acceleration ceiling. Unequal acceleration and deceleration limits use the same construction with unequal ramp times.

9.2.7 Applications

9.2.8 Connections to Other Algorithms

The S-curve profile is the jerk-limited upgrade under the same velocity and acceleration limits. The polynomial profile is fixed-time and smooth but offers no limit guarantees. The saturation rate limiter slews a changing setpoint online, whereas the trapezoid plans a whole move offline.

9.3 S-Curve (Jerk-Limited) Profile

9.3.1 Overview & Motivation

A trapezoidal velocity profile bounds speed and acceleration, but acceleration jumps instantaneously at every corner. Those jumps are impulses in jerk: they excite structural resonances, wear gearing, and make payloads wobble or liquids spill. The S-curve (double-S) profile also bounds jerk, so acceleration ramps linearly in and out and is continuous, giving far less vibration than a trapezoid. The price is a slightly longer move.

For a rest-to-rest move the phase durations have a closed form: planning is a handful of arithmetic operations, with no iteration. Three limits drive it: a velocity ceiling \(v_{\max}\), an acceleration ceiling \(a_{\max}\) and a jerk ceiling \(j_{\max}\). Short moves never reach the ceilings; the corresponding phases shrink to zero length.

9.3.2 Mathematical Theory

9.3.2.1 Phase Structure

A rest-to-rest move of length \(h > 0\) is built from seven constant-jerk phases:

Phase Jerk Acceleration Duration
1 \(+j_{\max}\) ramps \(0 \to a_{lim}\) \(T_j\)
2 \(0\) constant \(a_{lim}\) \(T_a - 2T_j\)
3 \(-j_{\max}\) ramps \(a_{lim} \to 0\) \(T_j\)
4 \(0\) zero, cruise at \(v_{lim}\) \(T_v\)
5 \(-j_{\max}\) ramps \(0 \to -a_{lim}\) \(T_j\)
6 \(0\) constant \(-a_{lim}\) \(T_a - 2T_j\)
7 \(+j_{\max}\) ramps \(-a_{lim} \to 0\) \(T_j\)

Phases 1–3 form the acceleration stage of length \(T_a\); phases 5–7 mirror it, so the profile is symmetric about its midpoint and the total time is \(T = 2T_a + T_v\). Integrating the stage gives the peaks and the distance:

\[a_{lim} = j_{\max} T_j, \qquad v_{lim} = a_{lim}\,(T_a - T_j), \qquad h = v_{lim}\,(T_a + T_v)\]

9.3.2.2 Closed-Form Three-Step Planner

This is the case \(v_0 = v_1 = 0\) of Biagiotti and Melchiorri, §3.4. Each step assumes one more ceiling is unreachable.

Step 1 — assume \(v_{\max}\) is reached (\(v_{lim} = v_{\max}\)):

\[ \text{if } v_{\max} j_{\max} \ge a_{\max}^2:\; T_j = \frac{a_{\max}}{j_{\max}},\; T_a = T_j + \frac{v_{\max}}{a_{\max}} \qquad\text{else}\qquad T_j = \sqrt{\frac{v_{\max}}{j_{\max}}},\; T_a = 2T_j \]

\[T_v = \frac{h}{v_{\max}} - T_a\]

The first case has an acceleration plateau; the second has a pure triangular ramp with peak \(j_{\max}T_j < a_{\max}\). Here \(T = h/v_{\max} + T_a\), so the jerk limit costs exactly \(a_{\max}/j_{\max}\) over a trapezoid with the same \(v_{\max}, a_{\max}\).

Step 2 — if \(T_v < 0\), set \(T_v = 0\) and assume \(a_{\max}\) is still reached, \(T_j = a_{\max}/j_{\max}\). Then \(h = a_{\max} T_a (T_a - T_j)\) is a quadratic in \(T_a\):

\[\Delta = \frac{a_{\max}^4}{j_{\max}^2} + 4\,a_{\max} h, \qquad T_a = \frac{a_{\max}^2/j_{\max} + \sqrt{\Delta}}{2\,a_{\max}}\]

Step 3 — if \(T_a < 2T_j\), the plateau would have negative length, so \(a_{\max}\) is unreachable too. Both ramps are triangles, \(T_a = 2T_j\) and \(h = 2 j_{\max} T_j^3\):

\[T_j = \sqrt[3]{\frac{h}{2\,j_{\max}}}, \qquad T_a = 2T_j\]

Step 2 applies when \(h < v_{\max} T_a\), and step 3 when \(h < 2 a_{\max}^3 / j_{\max}^2\) (from \(T_a \ge 2T_j \iff \Delta \ge 9 a_{\max}^4/j_{\max}^2\)). Neighbouring branches agree on \(T\) at each threshold, so \(T\) is continuous in \(h\). No duration is ever negative: in step 1, \(T_a - 2T_j = v_{\max}/a_{\max} - a_{\max}/j_{\max} \ge 0\) is the branch condition itself; step 2 is guarded by the step-3 test; step 3 has \(T_a - 2T_j = 0\). Zero-length phases collapse out of the profile.

9.3.2.3 Exact Evaluation

Within a phase of jerk \(j_i\) starting from state \((s_i, v_i, a_i)\), with local time \(\tau\):

\[a = a_i + j_i\tau, \qquad v = v_i + a_i\tau + \tfrac12 j_i\tau^2, \qquad s = s_i + v_i\tau + \tfrac12 a_i\tau^2 + \tfrac16 j_i\tau^3\]

The seven boundary states are integrated exactly once, at planning time, with these polynomials. Each sample then locates its phase and evaluates a cubic in time. At and beyond \(T\) the profile holds the goal at rest; before \(t = 0\) it holds the start. A negative move direction negates the offset, velocity, acceleration and jerk.

9.3.2.4 Time Scaling for Synchronization

Replanning with \((v_{\max}/\lambda,\; a_{\max}/\lambda^2,\; j_{\max}/\lambda^3)\) stretches an axis by \(\lambda \ge 1\). Every planner term scales consistently (\(T_j \to \lambda T_j\), \(T_a \to \lambda T_a\), \(\Delta \to \lambda^{-2}\Delta\)) and the branch tests are invariant, so the duration scales by exactly \(\lambda\) in every branch. Axes stretched to a common \(T\) finish together.

9.3.3 Complexity Analysis

Operation Time Space Notes
Plan \(O(1)\) \(O(1)\) At most one square root and one cube root, no iteration
Sample \(O(1)\) \(O(1)\) Locate one of seven phases, evaluate one cubic

Stored state is a few fixed-size tables of phase boundaries on the stack; no heap is used. Cost is independent of \(h\) and of the branch taken.

9.3.4 Step-by-Step Walkthrough

Limits \((v_{\max}, a_{\max}, j_{\max}) = (1, 2, 10)\), \(h = 0.3\).

  1. Step 1: \(v_{\max} j_{\max} = 10 \ge 4\), so \(T_j = 0.2\), \(T_a = 0.7\), \(T_v = 0.3 - 0.7 = -0.4 < 0\).
  2. Step 2: \(T_v = 0\), \(\Delta = 0.16 + 2.4 = 2.56\), \(T_a = (0.4 + 1.6)/4 = 0.5 \ge 2T_j = 0.4\), so step 3 is not needed. Then \(a_{lim} = 2\), \(v_{lim} = 2(0.5 - 0.2) = 0.6\), \(T = 1.0\).

Boundary states \((a, v, s)\) of the acceleration stage: at \(t = 0.2\), \((2,\, 0.2,\, 0.0133)\); at \(t = 0.3\), \((2,\, 0.4,\, 0.0433)\); at \(t = 0.5\), \((0,\, 0.6,\, 0.15)\). The mirrored stage ends at \(s = 0.3\), at rest. A sample at \(t = 0.4\) lies in phase 3 with \(\tau = 0.1\): \(a = 1\), \(v = 0.55\), \(s = 0.0917\).

\((v_{\max}, a_{\max}, j_{\max})\) \(h\) Branch \(T_j\) \(T_a\) \(T_v\) \(T\)
(1, 2, 10) 5 1, plateau 0.2 0.7 4.3 5.7
(1, 2, 1) 5 1, triangle 1 2 3 7
(1, 2, 10) 0.3 2 0.2 0.5 0 1.0
(1, 2, 10) 0.02 3 0.1 0.2 0 0.4

For \((1, 2, 10)\) the thresholds are \(h \ge 0.7\) for step 1 and \(h \ge 2a_{\max}^3/j_{\max}^2 = 0.16\) for step 2. The last row has \(a_{lim} = 1\) and \(v_{lim} = 0.1\).

9.3.5 Pitfalls & Edge Cases

9.3.6 Variants & Generalizations

9.3.7 Applications

9.3.8 Connections to Other Algorithms

Algorithm Relationship
Trapezoidal profile Jerk-unbounded predecessor; shares the synchronization scheme
Quintic polynomial profile Fixed duration and smooth, but limits are not enforced directly

10 Solvers

10.1 Gaussian Elimination

10.1.1 Overview & Motivation

Gaussian elimination is the standard direct method for solving a system of linear equations \(Ax = b\). It transforms the coefficient matrix into upper-triangular form through systematic row operations, then solves via back-substitution.

It serves as the foundational linear solver in this library — used inside the normal equation solver for Linear Regression, inside the DARE iteration for LQR, and anywhere else a small dense linear system arises at runtime.

The implementation uses partial pivoting (selecting the largest-magnitude entry in each column as pivot) to improve numerical stability.

10.1.2 Mathematical Theory

10.1.2.1 Problem Statement

Solve \(Ax = b\) where \(A \in \mathbb{R}^{n \times n}\) is non-singular, \(b \in \mathbb{R}^n\).

10.1.2.2 Forward Elimination with Partial Pivoting

For each column \(k = 0, \ldots, n-1\):

  1. Pivot selection: Find \(p = \arg\max_{i \geq k} |A_{ik}|\) and swap rows \(k\) and \(p\).
  2. Elimination: For each row \(i > k\):

\[\ell_{ik} = \frac{A_{ik}}{A_{kk}}\] \[A_{ij} \leftarrow A_{ij} - \ell_{ik} \cdot A_{kj}, \quad j = k, \ldots, n-1\] \[b_i \leftarrow b_i - \ell_{ik} \cdot b_k\]

After all columns are processed, \(A\) is upper-triangular: \(Ux = b'\).

10.1.2.3 Back-Substitution

\[x_i = \frac{b'_i - \sum_{j=i+1}^{n-1} U_{ij} \, x_j}{U_{ii}}, \quad i = n-1, \ldots, 0\]

10.1.2.4 Multi-Column Extension

For \(AX = B\) where \(B\) has \(m\) columns, each column is solved independently.

10.1.3 Complexity Analysis

Phase Time Space Notes
Forward elimination \(O(n^3)\) \(O(n^2)\) In-place on copies of \(A\) and \(b\)
Back-substitution \(O(n^2)\) \(O(n)\)
Multi-column solve \(O(n^3 + n^2 m)\) \(O(n^2)\) \(m\) back-substitutions

Why \(O(n^3)\): The elimination has \(n\) stages; stage \(k\) performs \((n-k)^2\) multiplications. Summing: \(\sum_{k=1}^{n} k^2 \approx n^3/3\).

10.1.4 Step-by-Step Walkthrough

System:

\[\begin{bmatrix} 2 & 1 & -1 \\ -3 & -1 & 2 \\ -2 & 1 & 2 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} = \begin{bmatrix} 8 \\ -11 \\ -3 \end{bmatrix}\]

Step 1 — Column 0: pivot selection

\(|A_{00}| = 2\), \(|A_{10}| = 3\) (largest), \(|A_{20}| = 2\). Swap rows 0 and 1:

\[\begin{bmatrix} -3 & -1 & 2 \\ 2 & 1 & -1 \\ -2 & 1 & 2 \end{bmatrix}, \quad b = \begin{bmatrix} -11 \\ 8 \\ -3 \end{bmatrix}\]

Step 2 — Eliminate below pivot

Step 3 — Column 1: pivot selection

\(|1/3| < |5/3|\). Swap rows 1 and 2.

Step 4 — Eliminate below:

\(\ell = (1/3)/(5/3) = 1/5\). Row 2 -= \((1/5)\) × Row 1 → \([0,\; 0,\; 1/5 \mid -1/5]\). Upper-triangular form reached.

Step 5 — Back-substitution:

\[x_3 = \frac{-1/5}{1/5} = -1, \qquad x_2 = \frac{13/3 - (2/3)(-1)}{5/3} = 3, \qquad x_1 = \frac{-11 - (-1)(3) - 2(-1)}{-3} = 2\]

Verification: \(2(2) + 1(3) + (-1)(-1) = 8\) ✓, \(\;-3(2) - 1(3) + 2(-1) = -11\) ✓, \(\;-2(2) + 1(3) + 2(-1) = -3\) ✓.

10.1.5 Pitfalls & Edge Cases

10.1.6 Variants & Generalizations

Variant Key Difference
Full pivoting Pivots on both rows and columns; more stable but rarely needed in practice
LU factorization Stores the \(L\) and \(U\) factors for reuse; solves multiple right-hand sides efficiently
Cholesky factorization Specialized for symmetric positive-definite matrices; \(O(n^3/6)\) instead of \(O(n^3/3)\)
Gauss-Jordan elimination Reduces to reduced row echelon form (identity matrix); used for matrix inversion
Iterative refinement Solves once, then iteratively corrects the residual to improve accuracy

10.1.7 Applications

10.1.8 Connections to Other Algorithms

graph LR
    GE["Gaussian Elimination"]
    LR["Linear Regression"]
    DARE["DARE Solver"]
    LQR["LQR Controller"]
    LD["Levinson-Durbin"]
    LR --> GE
    DARE --> GE
    LD -.->|"Toeplitz-specialized alternative"| GE
Algorithm Relationship
Linear Regression Uses Gaussian elimination to solve the normal equation
DARE Solver Calls Gaussian elimination at each Riccati iteration
Levinson-Durbin Specialized \(O(n^2)\) solver for Toeplitz systems; Gaussian elimination is the \(O(n^3)\) fallback

10.2 Levinson-Durbin Algorithm

10.2.1 Overview & Motivation

Many problems in signal processing produce linear systems with Toeplitz structure — each descending diagonal contains the same value. Autocorrelation matrices, for example, are always Toeplitz and symmetric. General solvers like Gaussian elimination ignore this structure and cost \(O(n^3)\).

The Levinson-Durbin algorithm exploits the Toeplitz structure to solve such systems in \(O(n^2)\) time and \(O(n)\) auxiliary space. It works by solving an order-1 system first, then growing the solution to order 2, 3, …, up to \(n\), reusing previous solutions at each step via a recursion involving reflection coefficients.

This algorithm is central to AR model estimation (Yule-Walker) and linear prediction in speech processing.

10.2.2 Mathematical Theory

10.2.2.1 Toeplitz System

A symmetric Toeplitz matrix and the system to solve:

\[\begin{bmatrix} r_0 & r_1 & r_2 & \cdots & r_{n-1} \\ r_1 & r_0 & r_1 & \cdots & r_{n-2} \\ r_2 & r_1 & r_0 & \cdots & r_{n-3} \\ \vdots & & & \ddots & \vdots \\ r_{n-1} & r_{n-2} & r_{n-3} & \cdots & r_0 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{bmatrix} = \begin{bmatrix} b_1 \\ b_2 \\ \vdots \\ b_n \end{bmatrix}\]

10.2.2.2 Recursive Solution

Define \(\varphi^{(k)}\) as the solution of the order-\(k\) system. The algorithm computes:

Reflection coefficient at order \(k\):

\[\mu_k = \frac{b_k - \sum_{j=0}^{k-1} \varphi_j^{(k-1)} \, r_{k-j}}{e_{k-1}}\]

Solution update:

\[\varphi_j^{(k)} = \varphi_j^{(k-1)} + \mu_k \, \varphi_{k-1-j}^{(k-1)}, \quad 0 \leq j < k\] \[\varphi_k^{(k)} = \mu_k\]

Prediction error update:

\[e_k = e_{k-1}(1 - \mu_k^2)\]

Starting from \(e_0 = r_0\) and \(\varphi_0^{(0)} = b_0 / r_0\).

10.2.2.3 Stability Criterion

If \(|\mu_k| < 1\) for all \(k\), the corresponding AR model is stable (all poles inside the unit circle) and the Toeplitz matrix is positive definite.

10.2.3 Complexity Analysis

Case Time Space Notes
All \(O(n^2)\) \(O(n)\) Versus \(O(n^3)\) for general solvers

Why \(O(n^2)\): At order \(k\), computing \(\mu_k\) costs \(O(k)\) and updating the solution costs \(O(k)\). Summing over \(k = 1, \ldots, n\): \(\sum k = n(n+1)/2 = O(n^2)\).

10.2.4 Step-by-Step Walkthrough

System: \(R x = b\) where \(r = [4, 2, 1]\), \(b = [2, 1, 0.5]\), \(n = 3\).

\[\begin{bmatrix} 4 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 4 \end{bmatrix} x = \begin{bmatrix} 2 \\ 1 \\ 0.5 \end{bmatrix}\]

Step 0 — Initialize:

\(e_0 = r_0 = 4\), \(\varphi_0^{(0)} = b_0 / r_0 = 2/4 = 0.5\)

Step 1 — Order 2:

\(\mu_1 = \frac{b_1 - \varphi_0^{(0)} \cdot r_1}{e_0} = \frac{1 - 0.5 \cdot 2}{4} = \frac{0}{4} = 0\)

\(\varphi_0^{(1)} = \varphi_0^{(0)} + 0 \cdot \varphi_0^{(0)} = 0.5\), \(\varphi_1^{(1)} = 0\)

\(e_1 = 4(1 - 0) = 4\)

Step 2 — Order 3:

\(\mu_2 = \frac{b_2 - (\varphi_0^{(1)} r_2 + \varphi_1^{(1)} r_1)}{e_1} = \frac{0.5 - (0.5 \cdot 1 + 0 \cdot 2)}{4} = \frac{0}{4} = 0\)

\(\varphi^{(2)} = [0.5, 0, 0]\)

Solution: \(x = [0.5, 0, 0]^T\)

Verification: \(4(0.5) + 2(0) + 1(0) = 2\) ✓, \(2(0.5) + 4(0) + 2(0) = 1\) ✓

10.2.5 Pitfalls & Edge Cases

10.2.6 Variants & Generalizations

Variant Key Difference
Split Levinson Replaces the two-vector recursion with a single vector; slightly more efficient
Block Levinson Solves block-Toeplitz systems (matrix entries instead of scalars)
Burg’s method Computes reflection coefficients from data rather than autocovariances; often more accurate for short records
Schur algorithm Computes reflection coefficients without forming the full solution; useful for VLSI implementations
Superfast Toeplitz solvers \(O(n \log^2 n)\) algorithms based on FFT and divide-and-conquer

10.2.7 Applications

10.2.8 Connections to Other Algorithms

graph LR
    LD["Levinson-Durbin"]
    YW["Yule-Walker"]
    GE["Gaussian Elimination"]
    PSD["Power Spectral Density"]
    YW --> LD
    GE -.->|"general alternative"| LD
    LD -.->|"parametric PSD"| PSD
Algorithm Relationship
Yule-Walker Primary consumer — produces the Toeplitz system that Levinson-Durbin solves
Gaussian Elimination General \(O(n^3)\) alternative that ignores Toeplitz structure
Power Spectral Density Levinson-Durbin enables parametric PSD estimation as an alternative to Welch’s non-parametric method

10.3 Durand-Kerner Polynomial Root Finder

10.3.1 Overview & Motivation

Given a polynomial \(P(z) = c_0 z^n + c_1 z^{n-1} + \cdots + c_n\), finding all roots (real and complex) is a fundamental problem in control theory, signal processing, and numerical analysis. Analytical formulas exist only for degree \(\leq 4\); beyond that, iterative methods are required.

The Durand-Kerner method (also known as the Weierstrass iteration) simultaneously refines approximations to all roots at once. Each root estimate is updated using Newton’s method, but instead of computing the derivative of \(P\), the other root estimates are used to factor out known roots. This avoids polynomial deflation — a process that accumulates errors as roots are extracted sequentially.

The method is elegant, easy to implement, and works for polynomials with complex coefficients and complex roots.

10.3.2 Mathematical Theory

10.3.2.1 Update Rule

Given current root estimates \(z_0, z_1, \ldots, z_{n-1}\), each is updated simultaneously:

\[z_r^{(k+1)} = z_r^{(k)} - \frac{P(z_r^{(k)})}{\displaystyle\prod_{j \neq r} \left(z_r^{(k)} - z_j^{(k)}\right)}\]

10.3.2.2 Relationship to Newton’s Method

For a polynomial \(P(z) = c_0 \prod_{j=0}^{n-1}(z - z_j)\), the derivative is:

\[P'(z_r) = c_0 \prod_{j \neq r}(z_r - z_j)\]

So the Durand-Kerner update is exactly Newton’s: \(z_r - P(z_r)/P'(z_r)\), but with \(P'\) approximated using the current root estimates rather than computed from coefficients.

10.3.2.3 Initial Root Placement

Roots are initialized on a circle of radius:

\[R = \left|\frac{c_n}{c_0}\right|^{1/n}\]

with angular positions offset by 0.4 radians to break symmetry. If \(R < 0.1\), it defaults to 1.0.

10.3.2.4 Convergence

10.3.3 Complexity Analysis

Case Time Space Notes
Per iteration \(O(n^2)\) \(O(n)\) Evaluating \(P\) and the denominator product for all \(n\) roots
Total \(O(n^2 k)\) \(O(n)\) \(k\) iterations until convergence (typically \(k \ll n\))

Why \(O(n^2)\): For each of the \(n\) roots, computing the denominator product requires multiplying \(n-1\) terms.

10.3.4 Step-by-Step Walkthrough

Polynomial: \(P(z) = z^3 - 6z^2 + 11z - 6 = (z-1)(z-2)(z-3)\)

Step 1 — Initialize on radius \(R = |{-6}/{1}|^{1/3} = 6^{1/3} \approx 1.817\):

Step 2 — Iteration 1 (for \(z_0\)):

  1. Evaluate \(P(z_0) = z_0^3 - 6z_0^2 + 11z_0 - 6\)
  2. Compute denominator: \((z_0 - z_1)(z_0 - z_2)\)
  3. Update: \(z_0 \leftarrow z_0 - P(z_0) / \text{denom}\)

Step 3 — Repeat for \(z_1\) and \(z_2\) (using the latest values).

Step 4 — Iterate until \(\max_r |z_r^{(k+1)} - z_r^{(k)}| < 10^{-6}\).

After ~15–20 iterations the roots converge to \(z \approx \{1, 2, 3\}\) (imaginary parts \(< 10^{-6}\)).

10.3.5 Pitfalls & Edge Cases

10.3.6 Variants & Generalizations

Variant Key Difference
Aberth-Ehrlich method Similar simultaneous iteration but includes a correction term that improves convergence for clustered roots
Jenkins-Traub Sequential algorithm (one root at a time) with superior convergence for difficult polynomials; standard in numerical libraries
Companion matrix + eigenvalues Converts to an eigenvalue problem; robust but \(O(n^3)\)
Laguerre’s method Converges cubically for simple roots; sequential (extracts one root at a time, then deflates)
Muller’s method Uses quadratic interpolation; works for non-polynomial equations too

10.3.7 Applications

10.3.8 Connections to Other Algorithms

graph LR
    DK["Durand-Kerner"]
    RL["Root Locus"]
    GE["Gaussian Elimination"]
    RL --> DK
    GE -.->|"companion matrix alternative"| DK
Algorithm Relationship
Root Locus Primary consumer — calls Durand-Kerner to find characteristic polynomial roots at each gain step
Gaussian Elimination Alternative approach: form the companion matrix and compute eigenvalues (requires a different solver)

10.4 Discrete Algebraic Riccati Equation (DARE)

10.4.1 Overview & Motivation

The Discrete Algebraic Riccati Equation arises whenever you need to find an optimal trade-off between state regulation and control effort in a discrete-time linear system. It is the mathematical core of two fundamental algorithms:

  1. LQR control — computing the optimal state-feedback gain.
  2. Steady-state Kalman filtering — computing the optimal estimator gain.

The DARE finds a symmetric positive semi-definite matrix \(P\) that encodes the cost-to-go from any state. Once \(P\) is known, the optimal gain is a simple closed-form expression. This library solves the DARE via fixed-point iteration — simple to implement, numerically transparent, and well-suited to the small state dimensions typical in embedded control.

10.4.2 Mathematical Theory

10.4.2.1 The Equation

\[P = A^T P A - A^T P B \left(R + B^T P B\right)^{-1} B^T P A + Q\]

where: - \(A \in \mathbb{R}^{n \times n}\) — state transition matrix - \(B \in \mathbb{R}^{n \times m}\) — input matrix - \(Q \in \mathbb{R}^{n \times n}\) — state weighting matrix (\(Q \succeq 0\)) - \(R \in \mathbb{R}^{m \times m}\) — input weighting matrix (\(R \succ 0\)) - \(P \in \mathbb{R}^{n \times n}\) — the unknown (\(P \succeq 0\))

10.4.2.2 Iterative Solution

Starting from \(P_0 = Q\):

  1. \(S = R + B^T P_k B\)
  2. Solve \(S \cdot K_{\text{part}} = B^T P_k A\) (via Gaussian Elimination)
  3. \(P_{k+1} = A^T P_k A - A^T P_k B \cdot K_{\text{part}} + Q\)
  4. If \(\|P_{k+1} - P_k\| < \varepsilon\), converged.

10.4.2.3 Existence and Convergence

A unique stabilizing solution exists when: - \((A, B)\) is stabilizable (unstable modes are controllable). - \((A, C)\) is detectable (where \(Q = C^T C\)).

Under these conditions, the iteration converges monotonically: \(P_0 \preceq P_1 \preceq \cdots \preceq P^*\).

10.4.2.4 Optimal Gain

\[K = \left(R + B^T P B\right)^{-1} B^T P A\]

10.4.3 Complexity Analysis

Phase Time Space Notes
Per iteration \(O(n^3 + n^2 m + m^3)\) \(O(n^2)\) Matrix multiplications + one \(m \times m\) linear solve
Total solver \(O(I(n^3 + m^3))\) \(O(n^2)\) \(I\) iterations (typically 10–30)

Why \(O(n^3)\): Each iteration involves \(n \times n\) matrix products (\(A^T P A\)) and an \(m \times m\) linear solve (for \(S^{-1}\)). When \(m \ll n\), the matrix products dominate.

10.4.4 Step-by-Step Walkthrough

System: \(n=2\), \(m=1\), \(A = \begin{bmatrix}1 & 0.1\\0 & 1\end{bmatrix}\), \(B = \begin{bmatrix}0.005\\0.1\end{bmatrix}\), \(Q = I_2\), \(R = [1]\).

Iteration 0: \(P_0 = Q = I_2\)

  1. \(S = R + B^T P_0 B = 1 + [0.005\; 0.1]\begin{bmatrix}0.005\\0.1\end{bmatrix} = 1.010025\)
  2. \(B^T P_0 A = [0.005\; 0.1]\begin{bmatrix}1 & 0.1\\0 & 1\end{bmatrix} = [0.005,\; 0.1005]\)
  3. \(K_{\text{part}} = [0.005,\; 0.1005] / 1.010025 = [0.00495,\; 0.0995]\)
  4. \(A^T P_0 A = \begin{bmatrix}1 & 0\\0.1 & 1\end{bmatrix}\begin{bmatrix}1 & 0.1\\0 & 1\end{bmatrix} = \begin{bmatrix}1 & 0.1\\0.1 & 1.01\end{bmatrix}\)
  5. Correction term \(\approx \begin{bmatrix}0.0000248 & 0.000498\\0.000498 & 0.00999\end{bmatrix}\)
  6. \(P_1 \approx \begin{bmatrix}2.0 & 0.0995\\0.0995 & 2.000\end{bmatrix}\)

Iterations 1–20: \(P\) grows monotonically until convergence. For this \(Q/R\) ratio, \(P^*\) stabilizes in ~15 iterations.

10.4.5 Pitfalls & Edge Cases

10.4.6 Variants & Generalizations

Variant Key Difference
Continuous ARE (CARE) For continuous-time systems; different matrix equation
Newton’s method for DARE Quadratic convergence but requires solving a Sylvester equation per iteration
Schur method Eigendecomposition-based; finds the solution in one pass but requires \(O(n^3)\) eigenvalue computation
Doubling algorithm \(O(\log I)\) convergence via matrix sign function; useful for large systems

10.4.7 Applications

10.4.8 Connections to Other Algorithms

graph LR
    DARE["DARE Solver"]
    GE["Gaussian Elimination"]
    LQR["LQR Controller"]
    KF["Kalman Filter"]
    GE --> DARE
    DARE --> LQR
    DARE --> KF
Algorithm Relationship
Gaussian Elimination Used at each iteration to solve the \(m \times m\) linear sub-system
LQR Controller Direct consumer — extracts optimal gain \(K\) from the DARE solution matrix \(P\)
Kalman Filter Dual problem — the steady-state Kalman gain is computed by a transposed DARE

10.5 Runge-Kutta ODE Integrators (RK4 + Dormand-Prince)

10.5.1 Overview & Motivation

Ordinary differential equations of the form \(\dot{x} = f(x, u, t)\) arise throughout embedded control and dynamics — propagating plant models for prediction, running model-based observers, or performing hardware-in-the-loop simulation on the device itself.

RK4 (the classical fourth-order Runge-Kutta method) solves this problem with a fixed step size, producing deterministic, constant-work-per-tick execution. It is the natural choice for hard-real-time control loops.

Dormand-Prince RK45 is an embedded pair that computes both a 4th- and a 5th-order estimate from the same seven slope evaluations, then uses their difference as a cheap local error gauge. The step-size controller shrinks the step when the estimated error is too large and grows it when the solution is smooth — adapting accuracy to computational budget without user intervention. It is suited for offline simulation or hardware-in-the-loop testing where timing determinism is less critical than accuracy.

10.5.2 Mathematical Theory

10.5.2.1 Problem Statement

Given \(\dot{x} = f(x, u, t)\) with \(x(t_0) = x_0\), advance the state by one step from \(t\) to \(t + h\).

10.5.2.2 RK4 — Classic Four-Stage Formula

\[ k_1 = f(x_n, u, t_n) \] \[ k_2 = f\!\left(x_n + \tfrac{h}{2}k_1,\ u,\ t_n + \tfrac{h}{2}\right) \] \[ k_3 = f\!\left(x_n + \tfrac{h}{2}k_2,\ u,\ t_n + \tfrac{h}{2}\right) \] \[ k_4 = f\!\left(x_n + h\,k_3,\ u,\ t_n + h\right) \] \[ x_{n+1} = x_n + \frac{h}{6}\left(k_1 + 2k_2 + 2k_3 + k_4\right) \]

The weights \((1, 2, 2, 1)/6\) are derived by matching the Taylor series of the exact solution through fourth order. The local truncation error is \(O(h^5)\); the global error is \(O(h^4)\).

10.5.2.3 Dormand-Prince RK45 — Butcher Tableau

Dormand and Prince (1980) selected a 7-stage Butcher tableau whose 5th-order propagator \(y_5\) and 4th-order embedded propagator \(y_4\) share stages \(k_1, \ldots, k_6\), with \(k_7 = f(y_5, u, t+h)\) added only for the 4th-order correction and for FSAL reuse.

The 5th-order solution used to advance the state:

\[ y_5 = x_n + h\left(\frac{35}{384}k_1 + \frac{500}{1113}k_3 + \frac{125}{192}k_4 - \frac{2187}{6784}k_5 + \frac{11}{84}k_6\right) \]

The 4th-order embedded solution used only for error estimation:

\[ y_4 = x_n + h\left(\frac{5179}{57600}k_1 + \frac{7571}{16695}k_3 + \frac{393}{640}k_4 - \frac{92097}{339200}k_5 + \frac{187}{2100}k_6 + \frac{1}{40}k_7\right) \]

10.5.2.4 Error Norm and Step-Size Control

The mixed absolute/relative weighted RMS norm over all \(n_s\) state components:

\[ \text{err} = \sqrt{\frac{1}{n_s} \sum_{i=1}^{n_s} \left(\frac{(y_5 - y_4)_i}{\text{atol} + \text{rtol}\,|x_i|}\right)^2} \]

A step is accepted when \(\text{err} \le 1\). The next step size is:

\[ h_{\text{new}} = h \cdot \text{clamp}\!\left(0.9 \cdot \text{err}^{-1/5},\ 0.2,\ 5\right) \]

clamped further to \([h_{\min}, h_{\max}]\).

10.5.2.5 FSAL Property

The 7th stage \(k_7 = f(y_5, u, t+h)\) equals the first stage of the next accepted step. Caching it reduces each accepted step from 7 to 6 function evaluations.

10.5.3 Complexity Analysis

Integrator RHS evaluations per accepted step State memory
RK4 (fixed) 4 (always) \(O(n_s)\)
Dormand-Prince 6 (with FSAL), 7 on first step \(O(n_s)\)

All intermediate stage vectors are stack-allocated. No heap is used. The cost of one step is \(O(s \cdot n_s)\) where \(s\) is the stage count plus the cost of evaluating \(f\).

10.5.4 Step-by-Step Walkthrough

Scalar decay \(\dot{x} = -x\), \(x(0) = 1\), exact solution \(x(t) = e^{-t}\), \(h = 0.1\):

Stage Formula Value
\(k_1\) \(f(1, 0) = -1\) \(-1\)
\(k_2\) \(f(1 - 0.05, 0.05) = -0.95\) \(-0.95\)
\(k_3\) \(f(1 - 0.0475, 0.05) = -0.9525\) \(-0.9525\)
\(k_4\) \(f(1 - 0.09525, 0.1) = -0.90475\) \(-0.90475\)
\(x_1\) \(1 + (0.1/6)(-1 - 1.9 - 1.905 - 0.90475)\) \(\approx 0.90484\)

Exact: \(e^{-0.1} \approx 0.90484\). Agreement to six significant figures — consistent with \(O(h^5)\) local error.

10.5.5 Pitfalls & Edge Cases

10.5.6 Variants & Generalizations

Variant Key Difference
Euler (1st order) One stage; \(O(h)\) global error; useful only for rough prototyping
RK4 (this) Four stages; \(O(h^4)\) global error; standard fixed-step workhorse
Dormand-Prince (this) Seven stages; \(O(h^5)\) propagator with built-in \(O(h^4)\) error estimate
Bogacki-Shampine RK23 Three-stage embedded pair; lower overhead for mildly stiff or smooth problems
Adams-Bashforth Multi-step; reuses past evaluations; efficient but requires startup phase
Implicit RK / SDIRK Solves a nonlinear system at each stage; suitable for stiff problems at the cost of a linear solve per step

10.5.7 Applications

10.5.8 Connections to Other Algorithms

graph LR
    RK["RK4 / Dormand-Prince"]
    DYN["dynamics/ (Euler-Lagrange, Newton-Euler)"]
    EKF["Extended Kalman Filter"]
    MPC["MPC Controller"]
    C2D["ContinuousToDiscrete"]
    DYN --> RK
    RK --> EKF
    RK --> MPC
    C2D -.->|"exact linear alternative"| RK
Algorithm Relationship
dynamics/ models Provide the right-hand side \(f(x, u, t)\) that RK integrates
Extended Kalman Filter Uses RK to propagate the state prediction step between measurements
MPC Controller Uses RK to simulate the plant over a prediction horizon
ContinuousToDiscrete Exact matrix-exponential discretization — an alternative for linear, time-invariant systems

10.6 Spectral Radius & Discrete Stability Margin

10.6.1 Overview & Motivation

The spectral radius of a square matrix is the largest magnitude among all its eigenvalues. In discrete-time dynamical systems, the spectral radius of the state-transition matrix is the single most important scalar quantity: if it is strictly less than one, every state trajectory converges to zero; if it equals or exceeds one, at least one mode grows without bound or fails to decay. Rather than reporting only a binary stable/unstable flag, exposing the spectral radius and the complementary stability margin (one minus the spectral radius) gives the designer a quantitative handle on how much gain or perturbation the system can tolerate before losing stability.

10.6.2 Mathematical Theory

10.6.2.1 Characteristic Polynomial via Faddeev–LeVerrier

Given an \(n \times n\) matrix \(A\), the characteristic polynomial is

\[p(\lambda) = \det(\lambda I - A) = \lambda^n + c_{n-1}\lambda^{n-1} + \cdots + c_1\lambda + c_0.\]

The Faddeev–LeVerrier recurrence computes the coefficients \(c_k\) without determinant expansion. Define auxiliary matrices \(M_k\) by

\[M_1 = A, \quad c_{n-1} = -\mathrm{tr}(M_1),\] \[M_k = A\bigl(M_{k-1} + c_{n-k+1}I\bigr), \quad c_{n-k} = -\frac{1}{k}\mathrm{tr}(M_k), \quad k = 2, \ldots, n.\]

This recurrence requires only matrix-matrix multiplication, scalar multiplication of a matrix by the identity, and the trace, making it suitable for static (stack-allocated) computation without a heap.

10.6.2.2 Spectral Radius

Given the full set of eigenvalues \(\{\lambda_i\}_{i=1}^{n}\) (roots of the characteristic polynomial),

\[\rho(A) = \max_{i} |\lambda_i|.\]

The roots are found numerically with the Durand–Kerner simultaneous root-finding iteration, which converges for arbitrary polynomials and requires only a fixed-size array of complex iterates.

10.6.2.3 Schur Stability

A matrix \(A\) is Schur-stable (the discrete-time analogue of Hurwitz stability) if and only if \(\rho(A) < 1\). Every eigenvalue then lies strictly inside the complex unit disk, guaranteeing exponential decay of all modes.

10.6.2.4 Stability Margin

\[\delta(A) = 1 - \rho(A).\]

A positive margin indicates Schur stability; \(\delta = 0\) corresponds to marginal stability (an eigenvalue on the unit circle); \(\delta < 0\) signals instability. The margin quantifies how far \(\rho(A)\) is from the stability boundary.

10.6.3 Complexity Analysis

Case Time Space Notes
Best \(O(n^3)\) \(O(n^2)\) Faddeev–LeVerrier dominates; two \(n\times n\) temporaries on the stack
Average \(O(n^3 + n \cdot I)\) \(O(n^2 + n)\) \(I\) Durand–Kerner iterations, typically \(\ll 200\)
Worst \(O(n^3 + n \cdot I_{\max})\) \(O(n^2 + n)\) Near-repeated roots slow DK convergence

The Faddeev–LeVerrier step executes \(n\) matrix multiplications each costing \(O(n^3)\), giving \(O(n^4)\) total; for the small dimensions typical of embedded control matrices (\(n \le 6\)) this is negligible.

10.6.4 Step-by-Step Walkthrough

Consider \(A = \begin{pmatrix} 0 & -1 \\ 1 & 0 \end{pmatrix}\) (90° rotation, \(\rho = 1\)).

  1. k = 1: \(M_1 = A \cdot 0 + I \cdot c_0\). With \(c_0 = 1\): \(M_1 = I\). Then \(c_1 = -\tfrac{1}{1}\mathrm{tr}(A \cdot M_1) = -\mathrm{tr}(A) = 0\).
  2. k = 2: \(M_2 = A(M_1 + c_1 I) = A \cdot I = A\). Then \(c_2 = -\tfrac{1}{2}\mathrm{tr}(A \cdot A) = -\tfrac{1}{2}\mathrm{tr}(-I) = 1\).

Characteristic polynomial: \(\lambda^2 + 0\lambda + 1 = \lambda^2 + 1\). Roots: \(\pm i\), magnitudes both 1. \(\rho = 1\).

10.6.5 Pitfalls & Edge Cases

10.6.6 Variants & Generalizations

10.6.7 Applications

10.6.8 Connections to Other Algorithms

10.7 Condition Number

10.7.1 Overview & Motivation

A matrix can be mathematically invertible yet catastrophically sensitive to perturbations in its entries or in the right-hand side of a linear system. This sensitivity, called ill-conditioning, is the root cause of accumulated floating-point error in solvers, regression, and Kalman-filter covariance updates. Measuring it before committing to a solve reveals whether the result can be trusted or whether regularisation, pivoting, or reformulation is warranted.

The condition number is the canonical scalar summary of ill-conditioning. Because it requires the norm of the inverse, it depends on a linear solver — which is why it lives in the solvers layer, on top of the math norms, rather than in math itself.

10.7.2 Mathematical Theory

For an invertible square matrix \(A \in \mathbb{R}^{N \times N}\) and a chosen matrix norm \(\|\cdot\|\):

\[\kappa(A) = \|A\| \cdot \|A^{-1}\|\]

The condition number bounds the relative error amplification in the solution \(\mathbf{x}\) of \(A\mathbf{x} = \mathbf{b}\) due to a perturbation \(\delta\mathbf{b}\):

\[\frac{\|\delta\mathbf{x}\|}{\|\mathbf{x}\|} \le \kappa(A) \cdot \frac{\|\delta\mathbf{b}\|}{\|\mathbf{b}\|}\]

\(\kappa(A) \ge 1\) always; \(\kappa(A) = 1\) only for scalar multiples of orthogonal matrices. A singular matrix has \(\kappa(A) = \infty\). The estimate here uses the 1-norm.

10.7.2.1 Computing the Inverse via Column-wise Solve

The norm of \(A^{-1}\) is obtained without forming an explicit inverse as a first-class object: solving \(AX = I\) column by column with partial-pivoting Gaussian elimination yields the inverse columns, from which the 1-norm is accumulated. This mirrors the embedded convention of reusing the existing solver rather than adding a dedicated inversion routine.

10.7.3 Complexity Analysis

Operation Time Space Notes
ConditionNumber \(O(N^3)\) \(O(N^2)\) Dominated by the \(N\)-column solve of \(AX = I\)

10.7.4 Step-by-Step Walkthrough

Matrix \(A = \begin{bmatrix}3 & 1 \\ 1 & 2\end{bmatrix}\), 1-norm condition number:

  1. \(\|A\|_1 = \max(4, 3) = 4\)
  2. Solve \(A\mathbf{x} = \mathbf{e}_0\): \(\mathbf{x}_0 = [0.4,\, -0.2]^\top\)
  3. Solve \(A\mathbf{x} = \mathbf{e}_1\): \(\mathbf{x}_1 = [-0.2,\, 0.6]^\top\)
  4. \(A^{-1} = \begin{bmatrix}0.4 & -0.2 \\ -0.2 & 0.6\end{bmatrix}\), so \(\|A^{-1}\|_1 = \max(0.6, 0.8) = 0.8\)
  5. \(\kappa_1(A) = 4 \times 0.8 = 3.2\)

10.7.5 Pitfalls & Edge Cases

Singular matrices — a row of all zeros makes the system unsolvable. The singularity check fires before entering Gaussian elimination, returning an empty result rather than dividing by zero.

Near-singular matrices — the condition number can be astronomically large without any row being identically zero. Partial-pivoting elimination still completes, and the returned value reflects the ill-conditioning faithfully.

Norm choice — the 1-norm condition number is a practical upper bound on the spectral condition number \(\kappa_2\); it is cheaper by orders of magnitude because it avoids the SVD.

10.7.6 Variants & Generalizations

The spectral condition number (\(\kappa_2\), ratio of largest to smallest singular value) is the tightest but requires an SVD. The reciprocal condition number (\(\text{RCOND} = 1/\kappa\)) avoids overflow when \(\kappa\) is very large; LAPACK-style solvers return this form and compare against machine epsilon. For positive-definite systems, Cholesky factorisation supplies the same column-wise solves at half the cost.

10.7.7 Applications

10.7.8 Connections to Other Algorithms

Gaussian elimination with partial pivoting (solvers::GaussianElimination) supplies the column-wise solves of \(AX = I\). The matrix 1-norm (math::OneNorm) supplies both factors of the product. Cholesky decomposition (solvers::CholeskyDecomposition) is the positive-definite alternative for the inverse solve.

10.8 QR Decomposition

10.8.1 Overview & Motivation

The QR decomposition factors a matrix \(A\) into an orthogonal factor \(Q\) and an upper-triangular factor \(R\). It is the numerically robust foundation for least-squares regression, eigenvalue computation, and square-root filtering. In embedded systems, fitting an overdetermined model (more sensor measurements than unknowns) arises constantly in calibration, system identification, and sensor fusion. Solving the least-squares problem via QR avoids forming the normal equations \(A^\top A x = A^\top b\), whose condition number is the square of \(A\)’s — a critical advantage when working with reduced word lengths.

10.8.2 Mathematical Theory

10.8.2.1 Definitions

For an \(m \times n\) matrix \(A\) with \(m \geq n\), the thin QR factorization is

\[A = Q R,\]

where \(Q \in \mathbb{R}^{m \times n}\) has orthonormal columns (\(Q^\top Q = I_n\)) and \(R \in \mathbb{R}^{n \times n}\) is upper triangular.

10.8.2.2 Householder Reflectors

A Householder reflector is a symmetric, orthogonal matrix of the form

\[H = I - \beta v v^\top, \quad \beta = \frac{2}{\|v\|^2},\]

chosen so that \(H x = \sigma e_1\) for a target vector \(x\), where \(\sigma = -\mathrm{sign}(x_1)\|x\|\). The sign convention \(\sigma = -\mathrm{sign}(x_1)\|x\|\) avoids catastrophic cancellation when computing \(v_1 = x_1 - \sigma\).

Applying \(k\) successive reflectors \(H_k, \ldots, H_1\) to \(A\) triangularizes it:

\[H_n \cdots H_1 A = R \implies A = H_1 \cdots H_n R = QR.\]

The reflectors are stored implicitly: below the diagonal of the working matrix, alongside a scalar \(\beta_k\) per column, consuming only \(O(mn)\) words.

10.8.2.3 Givens Rotations for Streaming Updates

A Givens rotation

\[G(i,j,\theta) = \begin{pmatrix} c & s \\ -s & c \end{pmatrix}, \quad c = \cos\theta,\ s = \sin\theta\]

zeros a single sub-diagonal entry by rotating two rows. When one new row streams in after an existing factorization, \(n\) plane rotations update \(R\) in \(O(n^2)\) flops — far cheaper than re-factorizing the augmented matrix from scratch.

10.8.2.4 Least-Squares Solve

Given the factorization \(A = QR\), the least-squares minimum-norm solution is

\[x = R^{-1} Q^\top b,\]

computed as: (1) multiply \(b \leftarrow Q^\top b\) by applying the stored reflectors in order without forming \(Q\) explicitly, then (2) back-substitute \(R x = (Q^\top b)_{1:n}\).

10.8.3 Complexity Analysis

Case Time Space Notes
Best \(O(mn^2)\) \(O(mn)\) Householder on a well-conditioned dense \(A\)
Average \(O(2mn^2 - \tfrac{2}{3}n^3)\) \(O(mn)\) Standard flop count from Golub & Van Loan
Worst same as average (no pivoting) \(O(mn)\) Rank-deficient case detected but not re-ordered
Update \(O(n^2)\) \(O(n^2)\) Single Givens row update; no re-factor

All memory is stack-allocated via std::array; the factor is stored in-place over the input.

10.8.4 Step-by-Step Walkthrough

Factor \(A = \begin{pmatrix} 12 & -51 \\ 6 & 167 \\ -4 & 24 \end{pmatrix}\) (first two columns of the Golub & Van Loan example).

Column 0 (\(k=0\)): Extract \(x = (12, 6, -4)^\top\), \(\|x\| = 14\). Choose \(\sigma = -14\) (sign rule), \(v_0 = 12 + 14 = 26\), \(v = (26, 6, -4)^\top / 26\), \(\beta = 2/\|v\|^2\). After applying \(H_0\): \(R_{00} = 14\), zeros below.

Column 1 (\(k=1\)): Repeat on the \(2 \times 1\) trailing sub-column.

The upper-triangular \(R\) then has diagonal magnitudes \(\{14, 175\}\) for this sub-matrix.

10.8.5 Pitfalls & Edge Cases

10.8.6 Variants & Generalizations

10.8.7 Applications

10.8.8 Connections to Other Algorithms

10.9 LU Decomposition with Partial Pivoting

10.9.1 Overview & Motivation

Many embedded control and estimation algorithms reduce to solving a dense linear system \(A x = b\) for a general (non-symmetric) matrix \(A\). LU decomposition with partial pivoting factors \(A\) as \(P A = L U\), where \(P\) is a permutation, \(L\) is unit lower-triangular, and \(U\) is upper-triangular. Factoring \(A\) once and reusing \(L\) and \(U\) turns every subsequent solve into two cheap triangular sweeps — a critical advantage in loops where the same plant or observer matrix must be inverted against many right-hand sides at different time steps.

10.9.2 Mathematical Theory

10.9.2.1 Definitions

For an \(n \times n\) matrix \(A\), partial-pivoting LU factorization produces

\[P A = L U,\]

where: - \(P \in \{0,1\}^{n \times n}\) is a permutation matrix encoding row swaps, - \(L \in \mathbb{R}^{n \times n}\) is unit lower-triangular (\(L_{ii} = 1\), \(L_{ij} = 0\) for \(j > i\)), - \(U \in \mathbb{R}^{n \times n}\) is upper-triangular (\(U_{ij} = 0\) for \(i > j\)).

10.9.2.2 Gaussian Elimination with Partial Pivoting (GEPP)

At step \(k\) of the outer loop (\(k = 0, \ldots, n-1\)):

  1. Pivot selection: find \(p = \arg\max_{i \geq k} |A_{ik}|\) (the largest-magnitude entry in the current column from the diagonal down).
  2. Row swap: interchange rows \(k\) and \(p\) in the working matrix and record the swap in \(P\).
  3. Elimination: for each \(i > k\) compute the multiplier \(\ell_{ik} = A_{ik} / A_{kk}\), store it below the diagonal, and update the trailing submatrix:

\[A_{ij} \leftarrow A_{ij} - \ell_{ik} A_{kj}, \quad j > k.\]

After \(n - 1\) steps the working matrix holds \(U\) on and above the diagonal and the multipliers \(\ell_{ik}\) below it, packed together in a single \(n \times n\) array.

10.9.2.3 Triangular Solves

Given \(P A = L U\) and a right-hand side \(b\), the system \(A x = b\) becomes \(L U x = P b\). Solving in two passes:

\[L y = P b \quad \text{(forward substitution)},\] \[U x = y \quad \text{(back substitution)}.\]

Both passes cost \(O(n^2)\) operations, and both reuse the shared math::SolveUnitLowerTriangular / math::SolveUpperTriangular kernels (from math/TriangularSolve.hpp) applied directly to the packed factor — the same triangular-solve building blocks that back QrDecomposition.

The factored form is exposed for reuse by other components: L(), U(), and P() return the individual factors (so callers can verify \(P A = L U\) or build derived quantities), and IsSingular() reports whether the last Decompose aborted.

10.9.2.4 Determinant

The determinant of \(A\) equals the product of \(U\)’s diagonal entries multiplied by the sign of \(P\):

\[\det(A) = \mathrm{sign}(P) \prod_{k=0}^{n-1} U_{kk}.\]

The sign flips each time rows are swapped; it starts at \(+1\) and is multiplied by \(-1\) per swap.

10.9.2.5 Inverse

The inverse is formed by solving \(A X = I\) column by column — \(n\) back-and-forward substitution pairs — at a total cost of \(O(n^3)\).

10.9.3 Complexity Analysis

Operation Time Space Notes
Decompose \(O\!\left(\tfrac{2}{3}n^3\right)\) \(O(n^2)\) GEPP in-place; multipliers packed with \(U\)
Solve \(O(n^2)\) \(O(n)\) Two triangular sweeps; reuses stored factors
Determinant \(O(n)\) \(O(1)\) One pass over \(U\)’s diagonal
Inverse \(O(n^3)\) \(O(n^2)\) \(n\) column solves; avoid when Solve suffices

All storage is stack-allocated; the packed LU matrix and pivot vector are bounded by \(N\).

10.9.4 Step-by-Step Walkthrough

Factor \(A = \begin{pmatrix} 2 & 1 & -1 \\ -3 & -1 & 2 \\ -2 & 1 & 2 \end{pmatrix}\).

Step \(k = 0\): Largest magnitude in column 0 is \(|-3| = 3\) at row 1. Swap rows 0 and 1. Working matrix: \(\begin{pmatrix} -3 & -1 & 2 \\ 2 & 1 & -1 \\ -2 & 1 & 2 \end{pmatrix}\). Multipliers: \(\ell_{10} = 2/(-3) = -2/3\), \(\ell_{20} = -2/(-3) = 2/3\). After elimination: \(U_{0\cdot} = (-3,-1,2)\); rows 1 and 2 updated.

Step \(k = 1\): Select pivot in column 1 from rows 1 onward. Possibly swap. Compute multiplier \(\ell_{21}\) and update \(U_{2\cdot}\).

Step \(k = 2\): Single diagonal entry remains; \(U_{22}\) is read directly.

The resulting \(L\) has the stored multipliers below the diagonal (with unit diagonal), and \(U\) sits on and above it.

10.9.5 Pitfalls & Edge Cases

10.9.6 Variants & Generalizations

10.9.7 Applications

10.9.8 Connections to Other Algorithms

10.10 Lyapunov and Sylvester Equation Solvers

10.10.1 Overview & Motivation

The Sylvester equation \(AX + XB = C\) and its symmetric specialisations — the continuous Lyapunov \(AX + XA^\top = -Q\) and the discrete Lyapunov \(AXA^\top - X = -Q\) — are the central matrix equations of stability analysis, gramian computation, and robust-control synthesis. A positive-definite Lyapunov solution is a formal certificate that the system \(\dot{x} = Ax\) is asymptotically stable; the same equations produce the controllability and observability gramians that measure how effectively a linear system can be driven and observed.

10.10.2 Mathematical Theory

10.10.2.1 Sylvester Equation

Given matrices \(A \in \mathbb{R}^{N \times N}\), \(B \in \mathbb{R}^{M \times M}\), and \(C \in \mathbb{R}^{N \times M}\), the Sylvester equation is

\[AX + XB = C,\]

with unknown \(X \in \mathbb{R}^{N \times M}\). The equation has a unique solution if and only if \(A\) and \(-B\) share no eigenvalue, equivalently \(\lambda_i(A) + \lambda_j(B) \neq 0\) for all \(i, j\).

10.10.2.2 Kronecker Vectorisation

Stacking \(X\) column-by-column into \(\mathrm{vec}(X) \in \mathbb{R}^{NM}\) and applying the identities \(\mathrm{vec}(AXI) = (I_M \otimes A)\mathrm{vec}(X)\) and \(\mathrm{vec}(IXB) = (B^\top \otimes I_N)\mathrm{vec}(X)\) converts the matrix equation into the standard linear system

\[\underbrace{(I_M \otimes A + B^\top \otimes I_N)}_{K \in \mathbb{R}^{NM \times NM}} \mathrm{vec}(X) = \mathrm{vec}(C).\]

The \((iN+k, jN+l)\) entry of \(K\) is \(\delta_{ij} A_{kl} + B_{ji} \delta_{kl}\). The system is solved by Gaussian elimination with partial pivoting; a near-zero pivot signals the unsolvable (degenerate eigenvalue) case.

10.10.2.3 Continuous Lyapunov Equation

Setting \(B = A^\top\) and \(C = -Q\) reduces the Sylvester equation to the continuous Lyapunov form

\[AX + XA^\top = -Q.\]

When \(A\) is Hurwitz (all eigenvalues in the open left half-plane) and \(Q\) is symmetric positive semi-definite, the unique solution \(X\) is symmetric positive definite.

10.10.2.4 Discrete Lyapunov Equation

The discrete counterpart

\[AXA^\top - X = -Q\]

is solved by the Kronecker system \((A \otimes A - I_{N^2})\mathrm{vec}(X) = -\mathrm{vec}(Q)\), with the \((iN+k, jN+l)\) entry of \(A \otimes A - I\) equal to \(A_{ij}A_{kl} - \delta_{ij}\delta_{kl}\). When \(A\) is Schur-stable (\(\rho(A) < 1\)) and \(Q \succeq 0\), the unique solution equals the controllability gramian \(X = \sum_{k=0}^{\infty} A^k Q (A^\top)^k\).

10.10.3 Complexity Analysis

Case Time Space Notes
\(N{=}M{=}2\) \(O(64)\) flops \(O(NM)^2 = 16\) floats for K 4×4 linear system
\(N{=}M{=}3\) \(O(729)\) flops 81 floats for K 9×9 linear system
General \(N{=}M\) \(O(N^6)\) flops \(O(N^4)\) words Gaussian elimination on the \(N^2 \times N^2\) Kronecker system
Alternative (Bartels–Stewart) \(O(N^3)\) \(O(N^2)\) Requires Schur decomposition; preferred for \(N \geq 6\)

For the embedded sizes (\(N \leq 5\)) targeted by this library the Kronecker approach is negligibly more expensive than Bartels–Stewart and avoids the full Schur machinery.

10.10.4 Step-by-Step Walkthrough

Solve \(AX + XB = C\) for the \(2 \times 2\) case \(A = \bigl[\begin{smallmatrix}-3&1\\0&-2\end{smallmatrix}\bigr]\), \(B = \bigl[\begin{smallmatrix}1&2\\0&1\end{smallmatrix}\bigr]\), \(C = I_2\).

Build \(K\) (the \(4 \times 4\) Kronecker matrix):

\[K = \begin{pmatrix}-2&1&0&0\\0&-1&0&0\\2&0&-2&1\\0&2&0&-1\end{pmatrix}.\]

Build \(\mathrm{vec}(C) = (1, 0, 0, 1)^\top\).

Gaussian elimination with partial pivoting produces \(X_{00} = -0.5\), \(X_{10} = 0\), \(X_{01} = -1\), \(X_{11} = -1\).

Verify: \(AX + XB = I_2\). ✓

10.10.5 Pitfalls & Edge Cases

10.10.6 Variants & Generalizations

10.10.7 Applications

10.10.8 Connections to Other Algorithms

10.11 Singular Value Decomposition

10.11.1 Overview & Motivation

The singular value decomposition (SVD) factors any \(m \times n\) matrix \(A\) into two orthonormal rotation factors and a non-negative diagonal, revealing the fundamental geometric action of \(A\) as a stretch along \(k = \min(m, n)\) orthogonal directions. It is the most numerically stable matrix factorization available and exposes rank, condition number, and the pseudo-inverse directly from its output. In embedded systems, SVD powers robust least-squares solvers for sensor calibration, redundant-actuator control allocation, and low-rank model reduction — all applications where the matrix \(A\) may be ill-conditioned or rank-deficient and normal-equation approaches fail.

10.11.2 Mathematical Theory

10.11.2.1 Factorization

For \(A \in \mathbb{R}^{m \times n}\) and \(k = \min(m, n)\), the thin SVD is

\[A = U \Sigma V^\top,\]

where \(U \in \mathbb{R}^{m \times k}\) and \(V \in \mathbb{R}^{n \times k}\) have orthonormal columns (\(U^\top U = V^\top V = I_k\)) and \(\Sigma = \mathrm{diag}(\sigma_1, \ldots, \sigma_k)\) with \(\sigma_1 \geq \cdots \geq \sigma_k \geq 0\). For a tall or square matrix (\(m \geq n\)) \(V\) is square and orthogonal; for a wide matrix (\(m < n\)) \(U\) is. The scalars \(\sigma_i\) are the singular values of \(A\); the columns of \(U\) and \(V\) are the left and right singular vectors.

10.11.2.2 Wide Matrices

The Golub–Kahan bidiagonalization below runs on a tall matrix. A wide \(A\) is handled through its transpose: factor \(A^\top = U' \Sigma V'^\top\) (tall), then \(A = V' \Sigma U'^\top\), so \(U = V'\) and \(V = U'\). The singular values are shared, and no extra storage is needed beyond the tall factors.

10.11.2.3 Connection to the Symmetric Eigenproblem

The singular values satisfy \(\sigma_i^2 = \lambda_i(A^\top A)\), where \(\lambda_i\) denotes the \(i\)-th eigenvalue of \(A^\top A\). This ties the SVD to the symmetric eigenproblem and provides a cross-check: computing the eigenvalues of \(A^\top A\) via the Jacobi or tridiagonal-QR solver should yield the squared singular values.

10.11.2.4 Pseudo-Inverse

The Moore-Penrose pseudo-inverse is

\[A^+ = V \Sigma^+ U^\top, \quad \Sigma^+_{ii} = \begin{cases} 1/\sigma_i & \sigma_i > \tau \\ 0 & \text{otherwise} \end{cases}\]

for a threshold \(\tau > 0\). Truncating small singular values regularises the solution against near-null-space components, avoiding division by near-zero values that arise from rank-deficiency or numerical noise.

10.11.2.5 Rank and Condition Number

\[\mathrm{rank}(A) = \#\{i : \sigma_i > \tau\}, \quad \kappa(A) = \sigma_1 / \sigma_k.\]

The condition number \(\kappa\) measures sensitivity: a system with \(\kappa \gg 1\) amplifies input perturbations by a factor of \(\kappa\) in the least-squares solution.

10.11.3 Complexity Analysis

Phase Time Space Notes
Bidiagonalization \(O(Mk^2 - k^3/3)\) \(O(mn)\) Two-sided Householder reflectors
QR sweeps \(O(k^2)\) per iteration, \(O(k)\) total iterations \(O(k^2)\) Golub-Reinsch implicit-shift; converges fast
Pseudo-inverse \(O(k\,mn)\) \(O(nm)\) Matrix triple product \(V \Sigma^+ U^\top\)
Total \(O(Mk^2 + k^3)\) \(O(mn)\) Dominated by bidiagonalization

Here \(M = \max(m, n)\) and \(k = \min(m, n)\): a wide matrix costs the same as its transpose.

All storage is stack-allocated; no heap is used. The bidiagonal form is stored implicitly in the working copy of \(A\) alongside the Householder reflector scalars.

10.11.4 Step-by-Step Walkthrough

Input: \(A = \begin{pmatrix} 3 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & 2 \end{pmatrix}\) (diagonal matrix).

Phase 1 — Bidiagonalization: \(A\) is already diagonal (and hence upper-bidiagonal). All Householder reflectors are trivial (\(\beta = 0\)). The bidiagonal form retains \(B = A\); \(U = I_3\), \(V = I_3\).

Phase 2 — QR sweeps: The superdiagonal is already zero, so the sweep loop exits immediately.

Sign correction and sort: \(\sigma = (3, 1, 2)\) after reading the diagonal; the entry \(-1\) has a negative sign, so it is flipped to \(+1\) and the corresponding column of \(U\) is negated. After descending sort: \(\sigma = (3, 2, 1)\).

10.11.5 Pitfalls & Edge Cases

10.11.6 Variants & Generalizations

10.11.7 Applications

10.11.8 Connections to Other Algorithms

10.12 Jacobi Eigenvalue Solver

10.12.1 Overview & Motivation

Many embedded estimation and analysis tasks reduce to the symmetric eigenvalue problem: given a real symmetric matrix \(A\), find scalars \(\lambda_k\) and orthonormal vectors \(v_k\) such that \(A v_k = \lambda_k v_k\). Covariance matrices, Gramians, inertia tensors, and modal-analysis stiffness matrices are all symmetric, and their eigen-decomposition exposes principal directions (PCA), energy distribution, conditioning, and vibration modes.

The cyclic Jacobi method is the oldest and one of the most robust ways to solve this problem. It repeatedly applies planar rotations that annihilate one off-diagonal entry at a time, gradually driving \(A\) toward a diagonal matrix whose entries are the eigenvalues. Its appeal for resource-constrained systems is threefold: it needs only a fixed-size working copy of the matrix (no heap), it produces the full set of eigenvalues and a fully orthonormal eigenvector basis, and it is exceptionally accurate for symmetric matrices — including the small relative eigenvalues that QR-based methods can lose.

10.12.2 Mathematical Theory

10.12.2.1 Jacobi Rotations

A Jacobi rotation \(G(p, q, \theta)\) is an orthogonal matrix equal to the identity except in the \(2\times 2\) block on rows/columns \(p\) and \(q\):

\[ \begin{bmatrix} c & s \\ -s & c \end{bmatrix}, \qquad c = \cos\theta, \; s = \sin\theta . \]

Applying the similarity transform \(A' = G^\top A G\) leaves the spectrum unchanged (orthogonal similarity) but modifies only rows/columns \(p\) and \(q\).

10.12.2.2 Annihilating the Pivot

The angle is chosen so that the off-diagonal entry \(a_{pq}\) becomes zero. Requiring \(a'_{pq} = 0\) gives

\[ (c^2 - s^2)\,a_{pq} + c\,s\,(a_{pp} - a_{qq}) = 0 . \]

Defining

\[ \theta = \frac{a_{qq} - a_{pp}}{2\,a_{pq}}, \qquad t = \frac{\operatorname{sign}(\theta)}{|\theta| + \sqrt{\theta^2 + 1}}, \qquad c = \frac{1}{\sqrt{t^2 + 1}}, \qquad s = t\,c , \]

selects the smaller root \(t = \tan\theta\). Choosing the smaller rotation angle keeps the transformation close to the identity, which is what guarantees numerical stability and monotone convergence.

10.12.2.3 Convergence Measure

Let \(\operatorname{off}(A) = \sqrt{\sum_{i \neq j} a_{ij}^2}\) be the Frobenius norm of the off-diagonal part. Each rotation that zeroes \(a_{pq}\) reduces \(\operatorname{off}(A)^2\) by exactly \(2\,a_{pq}^2\), since orthogonal similarity preserves the total Frobenius norm and the diagonal absorbs the removed mass. Thus \(\operatorname{off}(A)\) decreases monotonically toward zero.

10.12.2.4 Cyclic Sweeps

Rather than searching for the largest off-diagonal entry each step (classical Jacobi, \(O(n^2)\) search per rotation), the cyclic variant sweeps every pair \((p, q)\) with \(p < q\) in fixed row-major order. A full sweep touches all \(n(n-1)/2\) pairs. Near convergence the method is quadratically convergent: the off-diagonal norm is roughly squared each sweep, so a handful of sweeps suffices even for ill-conditioned inputs.

10.12.2.5 Eigenvectors

Accumulating the rotations \(V \leftarrow V\,G\) starting from \(V = I\) yields the orthogonal matrix whose columns are the eigenvectors. On termination \(A\) is (numerically) diagonal with \(\lambda_k = a_{kk}\), and \(A \approx V \operatorname{diag}(\lambda) V^\top\).

10.12.3 Complexity Analysis

Case Time Space Notes
Best \(O(n^3)\) \(O(n^2)\) Already near-diagonal; one sweep to confirm convergence
Average \(O(n^3)\) \(O(n^2)\) Typically 6–10 sweeps; each sweep costs \(O(n^3)\)
Worst \(O(S n^3)\) \(O(n^2)\) \(S\) sweeps capped by a fixed maximum; each rotation is \(O(n)\)

Why \(O(n^3)\) per sweep: a sweep performs \(n(n-1)/2 = O(n^2)\) rotations, and each rotation updates two rows and two columns at \(O(n)\) cost. Space is a single \(n\times n\) working copy plus the \(n\times n\) eigenvector accumulator — both stack/static, no dynamic allocation.

10.12.4 Step-by-Step Walkthrough

Input:

\[ A = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix} \]

Step 1 — Pick pivot \((p, q) = (0, 1)\), \(a_{pq} = 1\).

Step 2 — Compute angle. \(\theta = (a_{qq} - a_{pp}) / (2 a_{pq}) = (2 - 2)/2 = 0\), so \(t = 1\), \(c = s = 1/\sqrt{2} \approx 0.7071\) (a \(45^\circ\) rotation).

Step 3 — Rotate. The updated diagonal is

\[ a'_{pp} = c^2 a_{pp} - 2 s c\, a_{pq} + s^2 a_{qq} = 1, \qquad a'_{qq} = s^2 a_{pp} + 2 s c\, a_{pq} + c^2 a_{qq} = 3, \]

and \(a'_{pq} = 0\). The matrix is now diagonal.

Step 4 — Read results. Eigenvalues \(\{1, 3\}\); eigenvectors (columns of the accumulated rotation) \(\tfrac{1}{\sqrt 2}(1, -1)^\top\) and \(\tfrac{1}{\sqrt 2}(1, 1)^\top\). After sorting ascending, \(\lambda = (1, 3)\).

10.12.5 Pitfalls & Edge Cases

10.12.6 Variants & Generalizations

Variant Key Difference
Classical Jacobi Zeroes the largest off-diagonal entry each step; fewer rotations but an \(O(n^2)\) search each time
Threshold Jacobi Skips pivots below a per-sweep threshold, cheaper early sweeps on sparse-ish matrices
One-sided Jacobi Applies rotations to a factor only; the basis of Jacobi SVD for tall matrices
QR / tridiagonal method Reduces to tridiagonal form then iterates — faster asymptotically but less accurate on tiny eigenvalues

10.12.7 Applications

10.12.8 Connections to Other Algorithms

graph LR
    JAC["Jacobi Eigen"]
    QR["QR Decomposition"]
    CHOL["Cholesky"]
    SVD["SVD (Golub-Kahan)"]
    QR -.->|"shares Givens rotations"| JAC
    JAC -->|"symmetric building block"| SVD
    JAC -.->|"eigenvalue floor keeps SPD"| CHOL
Algorithm Relationship
QR Decomposition Both are built from orthogonal (Givens/Householder) transforms; QR underlies the alternative tridiagonal eigen-method
Cholesky Decomposition Requires symmetric positive-definite input; Jacobi eigenvalues certify or restore definiteness
Spectral Radius Returns only the dominant eigenvalue magnitude; Jacobi returns the full spectrum and vectors

10.13 Symplectic Integrators (Semi-Implicit Euler + Velocity Verlet)

10.13.1 Overview & Motivation

Mechanical plants (robot arms, pendulums, drones, a motor driving a load) are naturally second-order systems: \(\ddot{q} = a(q, \dot{q}, u)\), with acceleration computed from position, velocity and input. They are simulated before hardware exists, so controllers can be tested in model-in-the-loop runs or run on-target as observers.

A general-purpose integrator treats this as a first-order system and ignores its structure. Over a long run the simulated energy creeps up or down, so a lightly damped plant looks more (or less) stable than it really is. Symplectic integrators advance position and velocity in an interleaved order that respects the geometry of mechanics. For a conservative system the energy error stays bounded forever, at one or two acceleration evaluations per step — fewer than the four of RK4.

Two fixed-step methods are provided: semi-implicit (symplectic) Euler, first order and one evaluation, and velocity Verlet in kick–drift–kick form, second order and two evaluations.

10.13.2 Mathematical Theory

10.13.2.1 Problem Statement

State \((q, v)\) with \(v = \dot{q}\). The input \(u\) is held constant over the step (zero-order hold), and the step size \(h > 0\) is fixed.

10.13.2.2 Semi-Implicit Euler

\[ v^+ = v + h\,a(q, v, u), \qquad q^+ = q + h\,v^+ \]

The only change from explicit Euler is that the position update uses the new velocity. Local error is \(O(h^2)\), global error \(O(h)\).

10.13.2.3 Velocity Verlet (Kick–Drift–Kick)

\[ v_{1/2} = v + \tfrac{h}{2}\,a(q, v, u), \qquad q^+ = q + h\,v_{1/2}, \qquad v^+ = v_{1/2} + \tfrac{h}{2}\,a(q^+, v_{1/2}, u) \]

The step is symmetric in time, which cancels the odd error terms: global error \(O(h^2)\). For constant acceleration it is exact, since \(v^+ = v + h\,a\) and \(q^+ = q + h\,v + \tfrac{h^2}{2}a\). Semi-implicit Euler is not: it gives \(q^+ = q + h\,v + h^2 a\).

10.13.2.4 Why Energy Stays Bounded

For a position-only force, \(a = -\nabla U(q)\), each method is a composition of shears (a velocity update depending on \(q\), a position update depending on \(v\)), each with unit Jacobian determinant. The step map therefore preserves phase-space area and is symplectic. Backward error analysis then shows the numerical trajectory is the exact trajectory of a nearby modified energy \(\tilde{H} = H + O(h^p)\), so the true energy oscillates around a constant instead of drifting.

10.13.2.5 Unit Oscillator

For \(a = -\omega^2 q\) and \(\theta = h\omega\), both methods have a step matrix with determinant \(1\) and trace \(2 - \theta^2\). The eigenvalues stay on the unit circle, hence the trajectory is bounded, exactly when \(|2 - \theta^2| < 2\), i.e.

\[ h\,\omega < 2 . \]

The conserved modified energies and the resulting error of the true energy \(E = \tfrac12(v^2 + \omega^2 q^2)\) are:

Method Conserved quantity (\(\omega = 1\)) Relative energy error
Semi-implicit Euler \(\tfrac12\left(v^2 + q^2 - h\,q\,v\right)\) oscillates, amplitude \(\approx h\omega/2\)
Velocity Verlet \(\tfrac12\left(v^2 + (1 - \tfrac{\theta^2}{4})\,q^2\right)\) oscillates, amplitude \(\approx (h\omega)^2/8\)

10.13.2.6 Velocity-Dependent Forces

With damping or Coriolis terms the system is not Hamiltonian, so neither method is exactly symplectic. Verlet evaluates the second kick at the half-step velocity \(v_{1/2}\), which keeps second-order accuracy. Semi-implicit Euler evaluates its single acceleration at the old velocity. Energy decay is then physical dissipation, not a numerical artifact.

10.13.3 Complexity Analysis

Method Acceleration evaluations Order Arithmetic Memory
Semi-implicit Euler 1 1 \(O(n)\) two state copies
Velocity Verlet 2 2 \(O(n)\) two state copies

Here \(n\) is the number of generalized coordinates. The cost of a step is the cost of its evaluations plus a handful of vector operations; no heap is used. Each step is a pure function of its arguments; the second Verlet evaluation is not reused as the next step’s first. Every evaluation goes through a virtual call: fine for simulation and model-in-the-loop tests; for interrupt-service use, bind the concrete system type instead.

10.13.4 Step-by-Step Walkthrough

Unit oscillator \(a = -q\), \(h = 0.1\), start \((q, v) = (1, 0)\), true energy \(E = 0.5\).

Semi-implicit Euler

Step Velocity update Position update True \(E\) Modified energy
1 \(0 + 0.1(-1) = -0.1\) \(1 + 0.1(-0.1) = 0.99\) \(0.4950\) \(0.5000\)
2 \(-0.1 + 0.1(-0.99) = -0.199\) \(0.99 + 0.1(-0.199) = 0.9701\) \(0.4903\) \(0.5000\)

The true energy wanders (about \(1\%\) and \(2\%\) low), yet the modified energy is conserved exactly; the offset is \(\tfrac12 h\,q\,v\), which cannot grow beyond \(h/4\).

Velocity Verlet, one step: half kick \(v_{1/2} = 0 + 0.05(-1) = -0.05\); drift \(q^+ = 1 + 0.1(-0.05) = 0.995\); half kick with the new position \(v^+ = -0.05 + 0.05(-0.995) = -0.09975\). Exact values are \(\cos 0.1 = 0.995004\) and \(-\sin 0.1 = -0.099833\). The true energy is \(0.49999\), and the modified energy \(\tfrac12\left(v^2 + 0.9975\,q^2\right) = 0.49875\) equals its initial value.

10.13.5 Pitfalls & Edge Cases

10.13.6 Variants & Generalizations

Variant Key difference
Semi-implicit Euler, velocity Verlet (this) Symplectic, orders 1 and 2; explicit Euler differs only in using the old velocity for the drift and its energy grows
Higher-order composition Chains of Verlet steps with weights (Yoshida, Suzuki) reach orders 4, 6, …
Implicit midpoint / Gauss Symplectic and implicit; suited to stiff or non-separable systems at the cost of a nonlinear solve

10.13.7 Applications

10.13.8 Connections to Other Algorithms

Algorithm Relationship
Runge–Kutta 4 Four evaluations per step, fourth order, general first-order systems; energy drifts slowly on conservative problems
Dormand–Prince Adaptive step and error control for first-order systems; higher accuracy per step, no structure preservation

10.14 Damped Pseudo-Inverse and Null-Space Projector

10.14.1 Overview & Motivation

Mapping desired outputs to inputs through a non-square or ill-conditioned matrix \(A\) is everywhere: joint rates from a tool velocity, motor commands from a desired thrust and torque, calibration corrections from residuals. The plain pseudo-inverse divides by every singular value, so near rank loss it explodes and commands huge, saturating inputs. Damping replaces the division by a gain that equals \(1/\sigma\) for strong directions and fades to zero for weak ones, trading a small, bounded accuracy loss for a bounded answer.

When there are more inputs than outputs, \(A\) has a null space: input motions it cannot feel. A null-space projector removes from any vector every component the matrix sees, so a secondary goal (stay away from limits, share load, save energy) can use the spare freedom without disturbing the primary one. Both tools come from one thin SVD, factored once per matrix.

10.14.2 Mathematical Theory

10.14.2.1 Factorization and Rank

For \(A \in \mathbb{R}^{m \times n}\), tall or wide, and \(k = \min(m, n)\), the thin SVD is \(A = U\Sigma V^\top\) with \(\sigma_0 \geq \cdots \geq \sigma_{k-1} \geq 0\), left vectors \(u_i \in \mathbb{R}^m\) and right vectors \(v_i \in \mathbb{R}^n\). With a relative tolerance \(\tau\) (default \(10^{-4}\)) the rank threshold and rank are

\[\theta = \tau\,\sigma_0, \qquad r = \#\{\,i : \sigma_i > \theta\,\}.\]

Since the values are sorted, the counted directions are the leading \(r\). The zero matrix has \(\sigma_0 = 0\), hence \(r = 0\).

10.14.2.2 Damped Gain

Damped least squares (Tikhonov regularization) minimizes \(\|Ax - b\|^2 + \lambda^2\|x\|^2\). Its normal equations give

\[x = (A^\top A + \lambda^2 I)^{-1}A^\top b = A^\top (AA^\top + \lambda^2 I)^{-1} b,\]

and in the singular basis each direction is scaled by the gain

\[g(\sigma) = \begin{cases} \dfrac{\sigma}{\sigma^2 + \lambda^2} & \lambda > 0, \\[2mm] 1/\sigma & \lambda = 0,\ \sigma > \theta, \\[1mm] 0 & \lambda = 0,\ \sigma \leq \theta. \end{cases}\]

For \(\sigma \gg \lambda\) the gain is \(\approx 1/\sigma\); for \(\sigma \ll \lambda\) it is \(\approx \sigma/\lambda^2 \to 0\). Its maximum is at \(\sigma = \lambda\), so \(g \leq 1/(2\lambda)\) always. Without damping the threshold zeroes weak directions, so no division by zero occurs. The solution and the damped inverse are

\[x = \sum_{i=0}^{k-1} g(\sigma_i)\,(u_i^\top b)\,v_i, \qquad A^\dagger_\lambda = \sum_{i=0}^{k-1} g(\sigma_i)\,v_i u_i^\top.\]

Every term lies in the span of the \(v_i\), the row space of \(A\), so \(x\) has no null-space component: for \(\lambda = 0\) it is the minimum-norm solution.

10.14.2.3 Null-Space Projector

\[P = I - \sum_{i<r} v_i v_i^\top, \qquad Px = x - \sum_{i<r}(v_i^\top x)\,v_i.\]

The second form applies the projection without forming \(P\): \(r\) inner products and \(r\) vector updates. The projector is deliberately undamped. Built from the damped inverse, \(I - A^\dagger_\lambda A\) leaves a residual in the primary task:

\[A\,(I - A^\dagger_\lambda A) = \sum_i \sigma_i \frac{\lambda^2}{\sigma_i^2 + \lambda^2}\,u_i v_i^\top = \lambda^2 (AA^\top + \lambda^2 I)^{-1} A \neq 0.\]

Along each singular direction its eigenvalue is \(\lambda^2/(\sigma_i^2 + \lambda^2)\), strictly between \(0\) and \(1\) for \(\sigma_i > 0\), so it is not idempotent either. The thresholded projector is exact: \(P^2 = P\), \(P^\top = P\), and

\[AP = \sum_{i \geq r} \sigma_i\,u_i v_i^\top, \qquad \sigma_i \leq \theta,\]

which is zero for exact rank loss and at rounding level for full rank.

10.14.2.4 Damping Bias

For \(b \in \operatorname{range}(A)\) the residual is \(b - Ax = \sum_i \frac{\lambda^2}{\sigma_i^2 + \lambda^2}(u_i^\top b)\,u_i\), hence

\[\|Ax - b\| \leq \frac{\lambda^2}{\sigma_{\min}^2 + \lambda^2}\,\|b\| \leq \frac{\lambda^2}{\sigma_{\min}^2}\,\|b\|,\]

with \(\sigma_{\min}\) the smallest nonzero singular value. Robustness costs accuracy of order \(\lambda^2/\sigma_{\min}^2\). Near rank loss the threshold grows the null space: the lost direction is released to the projector by design, while the damped solution stays bounded.

10.14.3 Complexity Analysis

Operation Time Space Notes
Factor \(O(Mk^2)\) \(O(mk + nk)\) One Golub–Kahan SVD; wide matrices through their transpose
Solve \(O((m + n)\,k)\) \(O(n)\) \(k\) inner products of length \(m\), \(k\) updates of length \(n\); no division
Project \(O(n\,r)\) \(O(n)\) \(r\) inner products and updates; \(P\) never formed
Explicit inverse, projector \(O(mnk)\), \(O(n^2 r)\) \(O(mn)\), \(O(n^2)\) Diagnostics; sums of rank-one terms

Here \(M = \max(m, n)\). The SVD factors are the only persistent storage: \(U\) is \(m \times k\), \(V\) is \(n \times k\), plus \(k\) singular values. No heap is used, and the factorization is paid once per matrix; later solves and projections are inner products only.

10.14.4 Step-by-Step Walkthrough

Input: \(A = \begin{pmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \end{pmatrix}\), \(b = (1, 2)\), \(\tau = 10^{-4}\).

Factor. \(AA^\top = \begin{pmatrix} 2 & 1 \\ 1 & 2 \end{pmatrix}\) has eigenvalues \(3\) and \(1\), so \(\sigma = (\sqrt{3}, 1)\). Then \(u_0 = (1, 1)/\sqrt{2}\), \(u_1 = (1, -1)/\sqrt{2}\), \(v_0 = (1, 1, 2)/\sqrt{6}\), \(v_1 = (1, -1, 0)/\sqrt{2}\) (signs are arbitrary). The threshold is \(\theta \approx 1.7 \times 10^{-4}\), so \(r = 2\); the null space is spanned by \((1, 1, -1)\).

Gains and projections at \(\lambda = 0.1\):

\(i\) \(\sigma_i\) \(u_i^\top b\) \(1/\sigma_i\) \(g(\sigma_i)\)
0 \(1.732051\) \(2.121320\) \(0.577350\) \(0.575432\)
1 \(1\) \(-0.707107\) \(1\) \(0.990099\)

Solution. Without damping, \(x = A^\dagger b = (0, 1, 1)\): it satisfies \(Ax = b\) and is orthogonal to the null space (\(0 + 1 - 1 = 0\)). With \(\lambda = 0.1\), \(x = (0.003289, 0.993388, 0.996678)\). The residual is \(b - Ax = \lambda^2(AA^\top + \lambda^2 I)^{-1}b\), of norm \(0.009934\), within the bound \(0.022139\) (the looser \(\lambda^2\|b\|/\sigma_{\min}^2\) gives \(0.022361\)).

Projector. With both singular directions counted,

\[P = I - v_0 v_0^\top - v_1 v_1^\top = \tfrac{1}{3}\begin{pmatrix} 1 & 1 & -1 \\ 1 & 1 & -1 \\ -1 & -1 & 1 \end{pmatrix}, \qquad \operatorname{tr} P = 1 = n - r.\]

Projecting \((1, 0, 0)\) gives \((1/3, 1/3, -1/3)\), which \(A\) maps to \(0\). Projecting the damped solution gives \(\approx 0\), since it lies in the row space.

Rank loss and check. For \(A = \begin{pmatrix} 1 & 1 & 0 \\ 2 & 2 & 0 \end{pmatrix}\) the singular values are \((\sqrt{10}, 0)\): \(r = 1\) and \(\operatorname{tr} P = 2\). Undamped, the zero direction gets zero gain, and \(A^\dagger(1, 2) = (0.5, 0.5, 0)\). The results were compared with the closed forms \((A^\top A + \lambda^2 I)^{-1}A^\top b\) (tall) and \(A^\top (AA^\top + \lambda^2 I)^{-1} b\) (wide), evaluated in double precision, on random \(2 \times 3\), \(6 \times 7\), \(7 \times 6\) and \(4 \times 4\) matrices: solutions agree to about \(10^{-5}\) and \(\|P^2 - P\|\) is about \(10^{-6}\).

10.14.5 Pitfalls & Edge Cases

10.14.6 Variants & Generalizations

10.14.7 Applications

10.14.8 Connections to Other Algorithms

10.15 Active-Set Quadratic Programming (Goldfarb–Idnani)

10.15.1 Overview & Motivation

Many embedded decisions are quadratic programs: distribute a wrench over cables that can only pull, allocate thrust to motors that saturate, track prioritised tasks under joint limits, keep model-predictive inputs inside bounds. Clipping the unconstrained answer, or running a fixed number of projection sweeps, is cheap but can be wrong at the edges, where several limits interact.

This solver finds the exact minimiser of a strictly convex quadratic cost under linear equality and inequality constraints, in a bounded number of steps and without dynamic memory. It is a dual active-set method: it starts at the unconstrained minimum, which violates some constraints, and repeatedly pulls the point towards the most violated one along the direction that raises the cost least. Every intermediate point is optimal for the constraints chosen so far, so no feasible starting point is needed, and a running orthogonal factorisation keeps each step at quadratic cost.

10.15.2 Mathematical Theory

10.15.2.1 Problem Statement

\[ \min_{x \in \mathbb{R}^n} \; \tfrac12 x^\top H x + g^\top x \quad \text{s.t.} \quad E x = e, \;\; C x \ge c, \]

with \(H\) symmetric positive definite. Strict convexity makes the minimiser unique whenever the feasible set is non-empty. Write the rows of \(E\) followed by the rows of \(C\) as normals \(n_i\) with bounds \(b_i\), $m = $ (number of equalities) \(+\) (number of inequalities). A bound \(\ell_k \le x_k \le h_k\) is two inequality rows: \(x_k \ge \ell_k\) and \(-x_k \ge -h_k\).

10.15.2.2 Optimality Conditions

A point \(x\) is optimal exactly when there are multipliers with

\[ H x + g = E^\top \lambda + C^\top \mu, \qquad \mu \ge 0, \qquad \mu_i\,(n_i^\top x - b_i) = 0, \]

and \(x\) is feasible. Equality multipliers \(\lambda\) are free in sign; inequality multipliers \(\mu\) are non-negative and zero for inactive rows. The algorithm maintains stationarity and \(\mu \ge 0\) at every iteration and reaches feasibility only at the end: the cost of the iterate rises monotonically towards the optimum.

10.15.2.3 Factored Form (Powell)

Factor \(H = L L^\top\) and let \(N\) collect the active normals. The change of variables \(y = L^\top x\) turns the cost into \(\tfrac12\|y\|^2 + (L^{-1}g)^\top y\) and each normal into \(L^{-1} n_i\). A QR factorisation of these transformed normals gives the stable pair

\[ J = L^{-\top} Q, \qquad J^\top N = \begin{bmatrix} R \\ 0 \end{bmatrix}, \qquad J^\top H J = I, \]

with \(Q\) orthogonal and \(R\) upper triangular, \(q \times q\) for \(q\) active constraints. Split \(J = [J_1\; J_2]\) at column \(q\). The columns of \(J_2\) satisfy \(N^\top J_2 = 0\): moving along them keeps every active constraint satisfied. Since \(J J^\top = H^{-1}\), products with \(J\) replace any explicit inverse of \(H\), which is what keeps the update numerically stable.

10.15.2.4 Adding a Constraint

Let \(p\) be the constraint to add, \(n^+\) its normal oriented so that the current residual \(s = n^{+\top} x - b^+\) is negative. Define

\[ d = J^\top n^+ = \begin{bmatrix} d_1 \\ d_2 \end{bmatrix}, \qquad z = J_2 d_2, \qquad r = R^{-1} d_1 . \]

Then \(n^+ = N r + H z\) and \(n^{+\top} z = \|d_2\|^2\). Moving \(x \leftarrow x + t z\), \(u \leftarrow u - t r\) on the active multipliers and \(u_p \leftarrow u_p + t\) preserves stationarity, leaves the active constraints satisfied, and changes the residual of \(p\) by \(t\,\|d_2\|^2\). Two events can stop the step:

With \(t = \min(t_1, t_2)\) the multipliers and, if \(t_2\) is finite, \(x\) are updated. If \(t = t_2\), constraint \(p\) becomes active. If \(t = t_1\), the blocking constraint is dropped and the step repeats for the same \(p\) with a smaller active set. If \(t = \infty\), nothing bounds the multiplier of \(p\) and the constraints are contradictory: the problem is infeasible.

10.15.2.5 Updating the Factors

10.15.2.6 Linear Dependence Test

In exact arithmetic \(n^+\) depends on the active normals iff \(d_2 = 0\). In single precision \(d_2\) is a tiny nonzero vector, and an absolute test on \(\|z\|\) lets the step \(-s/(z^\top n^+)\) divide by a rounding artefact. The shipped solver therefore uses a relative test:

\[ \|d_2\| \le 10^{-5}\,\|d\| . \]

Since \(J\) transforms by an orthogonal \(Q\), the ratio \(\|d_2\|/\|d\|\) is the sine of the angle between the transformed new normal and the span of the active ones. A dependent constraint takes no full step; only a dual step with \(t_1\) remains, which is how degenerate active sets resolve.

10.15.2.7 Termination

Equalities are added first, in order; an equality whose residual is positive is added with its normal and bound negated, and its multiplier is reported with the original sign. Equalities are never dropped. Then, while some inactive inequality has residual below \(-\tau\,\|n_i\|\) (relative tolerance, default \(\tau = 10^{-6}\)), the one with the most negative raw residual is added. Each completed addition raises the cost, so in exact arithmetic no active set repeats; an outer cap (default \(2m + n\)) and a bound on drops per addition guard against rounding-induced cycling. Hitting either returns an iteration-limit status with the current finite \(x\), which is dual feasible but may violate constraints.

10.15.3 Complexity Analysis

With \(n\) variables, \(m\) constraints, \(q \le n\) active, and \(K\) the iteration cap:

Case Time Space Notes
Setup \(O(n^3)\) \(O(n^2)\) Cholesky, triangular inverse \(J = L^{-\top}\), start point \(x = -J J^\top g\)
Best \(O(n^3 + mn)\) \(O(n^2 + m)\) Unconstrained minimum already feasible: one residual scan
Average \(O(n^3 + k\,(n^2 + mn))\) \(O(n^2 + m)\) \(k\) additions, a few drops; per addition \(d\), \(z\), \(r\) and rotations cost \(O(n^2)\), the scan \(O(mn)\)
Worst \(O(n^3 + K\,(n + m)\,n^2)\) \(O(n^2 + m)\) Cap reached; loose bound, each addition allows at most \(n + m + 1\) add/drop passes

Storage is \(J\) and \(R\) (\(n \times n\) each), multipliers, signs and an active-set list of length at most \(n\); all on the stack, no heap.

10.15.4 Step-by-Step Walkthrough

Minimise \(\tfrac12\|x\|^2\) (\(H = I\), \(g = 0\)) subject to \(x_2 \ge 1\) and \(0.1\,x_1 + 0.5\,x_2 \ge 0.9\). Start at \(x = 0\) with \(J = I\); residuals are \(-1\) and \(-0.9\), so the first row is the most violated.

Step Action \(t_1\) \(t_2\) Result
1 Add row 1: \(z = (0,1)\), no active set \(\infty\) \(1/1 = 1\) \(x = (0, 1)\), \(u_1 = 1\), row 1 active
2 Add row 2: \(n^+ = (0.1, 0.5)\); component along row 1 is \(d_1 = 0.5\), orthogonal part \(z = (0.1, 0)\), \(r = 0.5\), residual \(-0.4\) \(1/0.5 = 2\) \(0.4/0.01 = 40\) Partial step \(t = 2\): \(x = (0.2, 1)\), \(u_1 = 0\), \(u_2 = 2\); row 1 dropped
3 Repeat row 2 with empty active set: \(z = n^+\), residual \(-0.38\) \(\infty\) \(0.38/0.26 = 1.4615\) \(x = (0.346154, 1.730769)\), \(u_2 = 3.461538\)

No inactive row is violated (\(x_2 = 1.73 \ge 1\)), so the solver stops: \(x = 0.9\,(0.1, 0.5)/0.26\), the projection onto the second row, with multiplier \(0.9/0.26 = 3.461538\) and row 1 inactive.

Other reference points:

10.15.5 Pitfalls & Edge Cases

10.15.6 Variants & Generalizations

10.15.7 Applications

10.15.8 Connections to Other Algorithms

Algorithm Relationship
Cholesky decomposition Supplies \(L\) with \(H = L L^\top\), and is the test for strict convexity
Triangular solve Builds \(J = L^{-\top}\) once and back-substitutes \(R^{-1} d_1\) every pass
Givens rotation Restores the triangular form of \(R\) when a constraint is added or dropped
QR decomposition \(J\) and \(R\) are an incrementally updated QR of the active normals in the \(H^{-1}\) metric
Damped pseudo-inverse The unconstrained counterpart: with no active constraint the answer is \(-H^{-1} g\); this solver adds exact limits
Model predictive control Its input bounds are a direct use of the bound rows; the current fixed-count projected descent could be replaced by this solver

11 Nonlinear Control

11.1 Backstepping Control

11.1.1 Overview & Motivation

Many embedded control plants are naturally cascaded: motor current drives torque, torque drives velocity, velocity drives position. Classical linear controllers treat the whole cascade as a single transfer function and tune gains empirically. Backstepping instead exploits the cascade structure directly, stabilising each integrator stage in sequence with a Lyapunov certificate attached at every step. The result is a constructive design recipe — not empirical tuning — that is provably stable by construction, which is essential for safety-critical electromechanical and flight control loops.

11.1.2 Mathematical Theory

11.1.2.1 Strict-Feedback Form

The controller targets plants in strict-feedback form:

\[\dot{x}_i = f_i(x_1,\ldots,x_i) + g_i(x_1,\ldots,x_i)\,x_{i+1}, \quad i = 1,\ldots,n-1\]

\[\dot{x}_n = f_n(x) + g_n(x)\,u\]

where \(x_i \in \mathbb{R}\) are scalar states, \(u \in \mathbb{R}\) is the real input, and \(g_i \neq 0\) everywhere in the operating region (controllability condition).

11.1.2.2 Stage Errors and Virtual Controls

Let \(r(t)\) be the reference with known derivative \(\dot{r}\). Define the stage-1 error:

\[z_1 = x_1 - r\]

To make \(z_1\) converge, treat \(x_2\) as a virtual control and choose the desired value:

\[\alpha_1 = \frac{\dot{r} - f_1 - k_1 z_1}{g_1}\]

so that \(\dot{z}_1 = -k_1 z_1\) when \(x_2 = \alpha_1\). Since \(x_2\) cannot be instantaneously set, define the next error:

\[z_2 = x_2 - \alpha_1\]

Repeating down the chain, at stage \(i\) the virtual control satisfies:

\[\alpha_i = \frac{\dot{\alpha}_{i-1} - f_i - k_i z_i - g_{i-1} z_{i-1}}{g_i}\]

The Lyapunov cross term \(g_{i-1} z_{i-1}\) cancels the coupling between successive stages. At stage \(n\), \(\alpha_n\) is the actual input \(u\).

11.1.2.3 Lyapunov Certificate

The aggregate Lyapunov function:

\[V = \frac{1}{2} \sum_{i=1}^{n} z_i^2\]

has time derivative:

\[\dot{V} = -\sum_{i=1}^{n} k_i z_i^2 < 0 \quad \forall\, z \neq 0\]

This guarantees uniform asymptotic stability of the origin \(z = 0\) (equivalently, \(x \to r\)) for all \(k_i > 0\).

11.1.2.4 Virtual Derivative Propagation

Each \(\alpha_i\) depends on \(x\), the gains, and the reference; its time derivative \(\dot{\alpha}_i\) must be propagated analytically (chain rule) down the cascade. Numerical differentiation is excluded: noise amplification destroys the stability guarantee.

11.1.3 Complexity Analysis

Operation Time Space Notes
Construction \(O(n)\) \(O(n)\) Gain validation only
ComputeControl \(O(n)\) \(O(n)\) Single forward sweep; one divide per stage

All storage is in fixed-size arrays on the stack. No heap allocation, no recursion.

11.1.4 Step-by-Step Walkthrough

Order-2 chain of integrators (\(f_i = 0\), \(g_i = 1\), \(r = 0\), \(\dot{r} = 0\)):

  1. \(z_1 = x_1\). Stage-1 virtual control: \(\alpha_1 = -k_1 z_1\).
  2. \(z_2 = x_2 - \alpha_1 = x_2 + k_1 x_1\).
  3. \(\dot{\alpha}_1 = -k_1 \dot{x}_1 = -k_1 x_2\) (propagated analytically).
  4. Stage-2 actual input: \(u = \dot{\alpha}_1 - k_2 z_2 - g_1 z_1 = -k_1 x_2 - k_2(x_2 + k_1 x_1) - x_1\).
  5. Closed-loop: \(\dot{V} = -k_1 z_1^2 - k_2 z_2^2 < 0\) — both errors decay independently at rates \(k_1\) and \(k_2\).

11.1.5 Pitfalls & Edge Cases

11.1.6 Variants & Generalizations

11.1.7 Applications

11.1.8 Connections to Other Algorithms

11.2 Feedback Linearization

11.2.1 Overview & Motivation

Nonlinear plants such as robot arms, quadrotors, and electromechanical drives are only well-controlled by a fixed linear gain over a narrow operating range. Feedback linearization resolves this by exploiting a known model of the plant’s nonlinearity to cancel it exactly in the closed loop, leaving an equivalent linear system — decoupled integrator chains — that a single outer-loop gain set can drive correctly across the full operating envelope. No gain scheduling, no lookup tables, no re-tuning when the operating point changes.

11.2.2 Mathematical Theory

11.2.2.1 Control-Affine Plant

The technique applies to plants whose output \(y \in \mathbb{R}^m\) satisfies, after \(r\) differentiations,

\[y^{(r)} = a(x) + B(x)\, u\]

where \(x \in \mathbb{R}^n\) is the state, \(u \in \mathbb{R}^m\) is the input, \(a(x) \in \mathbb{R}^m\) is the drift term (known nonlinear dynamics), and \(B(x) \in \mathbb{R}^{m \times m}\) is the decoupling matrix (state-dependent input gain). The integer \(r\) is the relative degree. For mechanical systems (\(r = 2\)), written as \(M(q)\ddot{q} + C(q,\dot{q})\dot{q} + g(q) = u\), the control-affine form has \(B(x) = M^{-1}(q)\) and \(a(x) = -M^{-1}(q)\bigl(C(q,\dot{q})\dot{q} + g(q)\bigr)\).

11.2.2.2 Inner Control Law (Cancellation)

The inner law selects \(u\) so that the term \(a(x)\) is cancelled and the decoupling matrix is factored out:

\[u = B^{-1}(x)\,(v - a(x))\]

Substituting into the plant equation yields

\[y^{(r)} = a(x) + B(x)\,B^{-1}(x)\,(v - a(x)) = a(x) + (v - a(x)) = v\]

leaving pure integrator chains \(y^{(r)} = v\), provided \(B(x)\) is nonsingular.

11.2.2.3 Outer Control Law (Linear Outer Loop)

With the plant reduced to integrators, a PD outer loop commands the virtual input:

\[v = y_d^{(r)} + K_d\,\dot{e} + K_p\, e, \quad e = y_d - y, \quad \dot{e} = \dot{y}_d - \dot{y}\]

The closed-loop error satisfies the linear ODE

\[e^{(r)} + K_d\,\dot{e} + K_p\, e = 0\]

whose eigenvalues are set by choosing \(K_p, K_d\). Critical damping per channel requires \(K_d = 2\sqrt{K_p}\).

11.2.2.4 Combined Law

Expanding yields the single expression evaluated on the hot path:

\[u = B^{-1}(x)\bigl(y_d^{(r)} + K_d\,\dot{e} + K_p\, e - a(x)\bigr)\]

The law requires solving the linear system \(B(x)\,u = v - a(x)\) on the hot path; \(B(x)\) must be nonsingular.

11.2.3 Complexity Analysis

Operation Time Space Notes
Construction \(O(m^2)\) \(O(m^2)\) Copy two gain matrices
ComputeInput \(O(m^3)\) \(O(m)\) extra One linear solve dominates
Model query \(O(m^2)\)–\(O(nm^2)\) \(O(m^2)\) Implementation-defined; injected model

All storage is in fixed-size stack arrays; the law itself performs no heap allocation.

11.2.4 Step-by-Step Walkthrough

Consider a 2-DOF planar arm with \(m = 2\), \(K_p = 100 I\), \(K_d = 20 I\), and at one instant:

  1. Compute error: \(e = [0.4, 0.3]^\top\), \(\dot{e} = [0, 0]^\top\).
  2. Compute virtual input: \(v = 0 + 20 \cdot 0 + 100 \cdot [0.4, 0.3]^\top = [40, 30]^\top\).
  3. Inner law: \(u = I^{-1}([40, 30]^\top - [0.3, 0.1]^\top) = [39.7, 29.9]^\top\).

The gravity-like drift \(a(x)\) is added directly; the outer PD term drives position error to zero.

11.2.5 Pitfalls & Edge Cases

11.2.6 Variants & Generalizations

11.2.7 Applications

11.2.8 Connections to Other Algorithms

11.3 Model Reference Adaptive Control

11.3.1 Overview & Motivation

Real hardware changes over time: motor resistance drifts with temperature, payload mass varies, actuators age. A fixed controller tuned at the factory cannot maintain performance across this variability. Model Reference Adaptive Control (MRAC) addresses this by continuously adjusting its own gains online, using only the tracking error between the real plant and a designer-specified reference model, until the plant’s response matches the desired ideal. No knowledge of the exact plant parameters is needed — only the structure (the equations of motion) and the sign of the input gain.

This makes MRAC the canonical direct-adaptive scheme for the “known structure, unknown numbers” class of problems that appears throughout embedded control: a family of units sharing the same firmware but differing in parameter values, or a single unit whose parameters change slowly during operation.

11.3.2 Mathematical Theory

11.3.2.1 Problem Setup

Given a plant of the form

\[\dot{x} = a\,x + b\,u\]

where \(a\) and \(b\) are unknown constants with \(b \neq 0\) and \(\text{sgn}(b)\) known, the goal is to choose the control \(u\) so that \(x(t) \to x_m(t)\) as \(t \to \infty\), where \(x_m\) satisfies the reference model

\[\dot{x}_m = -a_m\,x_m + b_m\,r, \quad a_m > 0.\]

11.3.2.2 Matching Conditions

The ideal control law that would make the plant identical to the reference model is

\[u^* = \theta_x^*\,x + \theta_r^*\,r\]

where

\[\theta_x^* = \frac{-(a_m + a)}{b}, \qquad \theta_r^* = \frac{b_m}{b}.\]

Since \(a\) and \(b\) are unknown, \(\theta_x^*\) and \(\theta_r^*\) cannot be computed directly; instead they are estimated online.

11.3.2.3 Tracking Error Dynamics

Let \(\hat{\theta}_x\) and \(\hat{\theta}_r\) denote the current parameter estimates and define the parameter errors \(\tilde{\theta} = \hat{\theta} - \theta^*\). The tracking error \(e = x - x_m\) satisfies

\[\dot{e} = -a_m\,e + b\,(\tilde{\theta}_x\,x + \tilde{\theta}_r\,r).\]

11.3.2.4 MIT Rule

The MIT rule minimises \(J = \tfrac{1}{2}e^2\) by gradient descent on the parameter space:

\[\dot{\hat{\theta}}_x = -\gamma\,\text{sgn}(b)\,e\,x, \qquad \dot{\hat{\theta}}_r = -\gamma\,\text{sgn}(b)\,e\,r.\]

This is a first-order gradient update. It converges when \(\gamma\) is small relative to the signal levels and the reference is persistently exciting. It has no global stability proof.

11.3.2.5 Lyapunov Redesign

The Lyapunov method chooses the same update law but derives it from the Lyapunov function candidate

\[V(e, \tilde{\theta}) = \frac{e^2}{2} + \frac{|b|}{2\gamma}\left(\tilde{\theta}_x^2 + \tilde{\theta}_r^2\right).\]

Computing \(\dot{V}\) and choosing the parameter update laws to make \(\dot{V} \leq -a_m\,e^2 \leq 0\) yields precisely the same gradient form. The Lyapunov construction guarantees that \(e\) and \(\tilde{\theta}\) remain bounded for all \(t \geq 0\) and that \(e(t) \to 0\), even under large initial errors — a global stability guarantee that the pure MIT rule lacks.

11.3.2.6 Discrete-Time Implementation

The continuous-time update is approximated by forward Euler with step \(\Delta t\):

\[x_m[k+1] = x_m[k] + (A_m\,x_m[k] + B_m\,r[k])\,\Delta t\]

\[e[k] = x[k] - x_m[k+1]\]

\[u[k] = \hat{\theta}_x[k]\,x[k] + \hat{\theta}_r[k]\,r[k]\]

\[\hat{\theta}_x[k+1] = \hat{\theta}_x[k] - \gamma\,\text{sgn}(b)\,e[k]\,x[k]\,\Delta t\]

\[\hat{\theta}_r[k+1] = \hat{\theta}_r[k] - \gamma\,\text{sgn}(b)\,e[k]\,r[k]\,\Delta t\]

The same structure extends to the multi-input multi-output case using outer products of the error and regressor vectors, yielding matrix parameter estimates.

11.3.3 Complexity Analysis

Operation Time Space Notes
ComputeControl \(O(n^2 + nm)\) \(O(1)\) working registers Reference model step + two outer products + two matrix-vector products
Memory \(O(n^2 + nm)\) static Parameter matrices and reference state All fixed-size; no heap allocation

Here \(n\) = StateSize and \(m\) = InputSize. The dominant cost is the outer product update of the parameter matrices at each step.

11.3.4 Step-by-Step Walkthrough

First-order scalar example (\(n = m = 1\), \(a = -1.5\), \(b = 2\), \(a_m = 1\), \(b_m = 1\), \(\gamma = 1\), \(\Delta t = 0.01\)):

  1. At step \(k=0\): \(x = 2\), \(r = 1\), \(x_m = 0\), \(\hat{\theta}_x = 0\), \(\hat{\theta}_r = 0\).
  2. Advance reference model: \(x_m \leftarrow 0 + (-1 \cdot 0 + 1 \cdot 1) \cdot 0.01 = 0.01\).
  3. Tracking error: \(e = 2 - 0.01 = 1.99\).
  4. Control output: \(u = 0 \cdot 2 + 0 \cdot 1 = 0\) (initial parameters are zero).
  5. Update \(\hat{\theta}_x\): \(0 - 1 \cdot (+1) \cdot 1.99 \cdot 2 \cdot 0.01 = -0.0398\).
  6. Update \(\hat{\theta}_r\): \(0 - 1 \cdot (+1) \cdot 1.99 \cdot 1 \cdot 0.01 = -0.0199\).
  7. At the next step the control becomes \(u = -0.0398 \cdot x - 0.0199 \cdot r\), already driving the plant toward the reference model. After many steps, \(\hat{\theta}_x \to \theta_x^* = 0.25\) and \(\hat{\theta}_r \to \theta_r^* = 0.5\).

11.3.5 Pitfalls & Edge Cases

11.3.6 Variants & Generalizations

11.3.7 Applications

11.3.8 Connections to Other Algorithms

12 Robust Control

12.1 Active Disturbance Rejection Control (ADRC + ESO)

12.1.1 Overview & Motivation

Active Disturbance Rejection Control addresses a fundamental tension in feedback design: high-performance control normally requires an accurate plant model, yet accurate models are expensive to identify and degrade with temperature, load, and wear. ADRC resolves this by treating everything beyond a known input gain — unmodeled dynamics, parameter variation, and external disturbances — as a single lumped signal called the total disturbance. An Extended State Observer (ESO) estimates this signal in real time, and the control law subtracts the estimate before issuing the command. What remains behaves like a clean chain of integrators that a simple PD law can regulate with textbook bandwidth.

The practical payoff on embedded hardware is significant: you need only one plant number (\(b_0\), the rough input gain) and two tuning dials. The controller then survives a bad model because any mismatch is absorbed into the disturbance estimate.

12.1.2 Mathematical Theory

12.1.2.1 Plant Representation

An \(n\)-th order SISO plant is written as the canonical integrator chain plus a total-disturbance term \(f\):

\[y^{(n)} = f(t, y, \dot{y}, \ldots, d) + b_0 u\]

where \(f\) captures unmodeled dynamics, nonlinearities, and external loads; \(b_0\) is a nominal input-gain estimate; and \(u\) is the control input.

12.1.2.2 Extended State Observer

Augmenting the \(n\) plant states with \(x_{n+1} = f\) yields an \((n+1)\)-dimensional system. The continuous ESO is a Luenberger-type observer driven by the output error:

\[\dot{\hat{x}}_i = \hat{x}_{i+1} + \beta_i (y - \hat{x}_1), \quad i = 1, \ldots, n\] \[\dot{\hat{x}}_{n+1} = \beta_{n+1} (y - \hat{x}_1)\]

with the convention \(\hat{x}_{n+1} = \hat{f}\) and \(\hat{x}_2\) through \(\hat{x}_n\) as derivative estimates.

The forward-Euler discretization used here is:

\[\hat{x}_i[k+1] = \hat{x}_i[k] + T_s \bigl(\beta_i \, e[k] + \hat{x}_{i+1}[k]\bigr), \quad e[k] = y[k] - \hat{x}_1[k]\]

with \(b_0 u[k-1]\) injected into the \((n)\)-th state to drive the highest derivative.

12.1.2.3 Bandwidth Parameterization (Gao)

All observer poles are placed at \(-\omega_o\) (Gao’s bandwidth parameterization). The resulting gains follow the binomial expansion of \((\lambda + \omega_o)^{n+1}\):

\[\beta_i = \binom{n+1}{i} \omega_o^i, \quad i = 1, \ldots, n+1\]

All control poles are placed at \(-\omega_c\) via the expansion of \((\lambda + \omega_c)^n\):

\[k_i = \binom{n}{i} \omega_c^i, \quad i = 1, \ldots, n\]

For a second-order plant (\(n = 2\)):

\[\beta = [3\omega_o,\; 3\omega_o^2,\; \omega_o^3], \quad k = [\omega_c^2,\; 2\omega_c]\]

12.1.2.4 Control Law

After disturbance estimation the control is:

\[u = \frac{u_0 - \hat{f}}{b_0}, \qquad u_0 = k_1(r - \hat{x}_1) - \sum_{i=2}^{n} k_i \hat{x}_i\]

Substituting into the plant equation and using \(\hat{f} \approx f\) gives the closed-loop residual \(y^{(n)} \approx u_0\), a pure integrator chain under a PD law — independent of the original plant dynamics.

12.1.3 Complexity Analysis

Case Time Space Notes
Best \(O(n)\) \(O(n)\) Linear sweep over \(n+1\) ESO states
Average \(O(n)\) \(O(n)\) Same; gains precomputed at construction
Worst \(O(n)\) \(O(n)\) No branching in the hot path

Gains are computed once at construction from closed-form binomial formulas in \(O(n)\) time. The Compute hot path is a pair of \(O(n)\) loops with no dynamic allocation.

12.1.4 Step-by-Step Walkthrough

Second-order plant (\(n=2\)), \(\omega_o = 30\), \(\omega_c = 6\), \(b_0 = 1\), \(T_s = 0.001\) s.

Observer gains: \(\beta_1 = 90\), \(\beta_2 = 2700\), \(\beta_3 = 27000\).
Control gains: \(k_p = 36\), \(k_d = 12\).

At sample \(k\) with state \(\hat{x} = [\hat{y}, \hat{\dot{y}}, \hat{f}]\), measurement \(y[k]\), reference \(r\):

  1. Output error: \(e = y[k] - \hat{y}\).
  2. Inject correction into all three states: \(\hat{x}_i \mathrel{+}= T_s \beta_i e\).
  3. Chain integration: \(\hat{y} \mathrel{+}= T_s \hat{\dot{y}}\); then \(\hat{\dot{y}} \mathrel{+}= T_s b_0 u_\text{applied}[k-1]\), where \(u_\text{applied}\) is the value actually delivered by the actuator. When the actuator does not saturate this equals the commanded \(u[k-1]\); when it does, pass the clipped value via the three-argument Compute(reference, measuredOutput, actualApplied) overload.
  4. PD law on integrator chain: \(u_0 = k_p(r - \hat{y}) - k_d \hat{\dot{y}}\).
  5. Disturbance cancellation: \(u = (u_0 - \hat{f}) / b_0\).

After a transient of roughly \(5/\omega_o \approx 0.17\) s the observer converges; the output tracks \(r\) with bandwidth \(\omega_c\).

12.1.5 Pitfalls & Edge Cases

Actuator saturation (anti-windup). When the downstream actuator clips the commanded value, the ESO integrates the wrong input and its disturbance estimate drifts, causing significant overshoot. Close the anti-windup loop by feeding the actually-applied signal back: u_applied = clamp(Compute(r, y, u_applied_prev), lo, hi), then pass u_applied as the third argument on the next call. The two-argument Compute(reference, measuredOutput) is a convenience overload that assumes no saturation (commanded = applied) and is equivalent to passing the last commanded value.

ESO peaking. Large initial estimation errors drive high-magnitude corrections, temporarily saturating the actuator. Mitigation: initialize the observer near the first measurement, or schedule \(\omega_o\) upward from a low value during the first few samples.

Observer bandwidth vs. noise. Increasing \(\omega_o\) speeds convergence but amplifies measurement noise because \(\beta_3 = \omega_o^3\) grows cubically. A practical rule of thumb is \(\omega_o \in [3\omega_c, 10\omega_c]\).

\(b_0\) mismatch. The ESO is robust to moderate mismatch (factor of 2–3), but large errors shrink the stability margin. If \(b_0 \gg b_\text{true}\) the effective loop gain drops and response slows; if \(b_0 \ll b_\text{true}\) the loop gain rises and may oscillate.

Euler discretization stability and accuracy. The forward-Euler ESO is unconditionally unstable once \(\omega_o T_s\) exceeds the stability boundary of the \((n+1)\)-th order observer polynomial. For a first-order plant (\(n=1\)) this boundary is \(\omega_o T_s \approx 0.83\); higher orders have progressively tighter boundaries (approximately \(0.54\) for \(n=2\), \(0.40\) for \(n=3\)). The constructor enforces \(\omega_o T_s < 0.5/n\) as a conservative order-aware hard precondition (aborts if violated). Keeping \(\omega_o T_s \leq 0.1\) is further recommended for accuracy; beyond that a ZOH or bilinear discretization is preferred.

Integer overflow in gain computation. Binomial coefficients are computed with integer arithmetic at compile time. For large orders or very high bandwidths the intermediate product may exceed std::size_t before the division; keep \(n \leq 5\) in practice.

12.1.6 Variants & Generalizations

Nonlinear ESO (NESO). Replace the linear correction \(\beta_i e\) with Han’s fal function to reduce peaking while preserving fast convergence.

Discrete ESO. Exact discretization of the observer (ZOH or pole-matched) improves accuracy when \(\omega_o T_s\) is not small.

Higher-order plants. The template parameter Order generalizes the same bandwidth-parameterized structure to \(n > 2\) — gains grow binomially and the Compute loop extends automatically.

Multi-input / multi-output (MIMO). Each output channel runs an independent ADRC; cross-coupling is absorbed into the respective disturbance estimates.

12.1.7 Applications

12.1.8 Connections to Other Algorithms

12.2 Sliding Mode Control

12.2.1 Overview & Motivation

Variable-structure control systems change their structure depending on the current system state. Sliding Mode Control (SMC) is the most widely used instantiation: the controller switches between two or more structures to drive the state onto a designer-specified manifold — the sliding surface — and keep it there. Once constrained to the surface, the closed-loop dynamics are governed entirely by the surface geometry, independent of the plant model or matched disturbances. This structural robustness makes SMC a preferred choice for motor drives, DC-DC power converters, and any embedded plant whose parameters drift or are poorly known.

12.2.2 Mathematical Theory

12.2.2.1 State-Space Plant

The controller operates on a discrete-time linear plant

\[x_{k+1} = A x_k + B u_k, \quad x \in \mathbb{R}^n,\; u \in \mathbb{R}^m\]

where \(A \in \mathbb{R}^{n \times n}\) and \(B \in \mathbb{R}^{n \times m}\).

12.2.2.2 Sliding Surface

A linear sliding surface is defined by

\[s(x) = S x, \quad S \in \mathbb{R}^{m \times n}\]

The sliding manifold \(\{x : s(x) = 0\}\) is an \((n-m)\)-dimensional subspace. The matrix \(S\) is designed so the reduced-order dynamics on the manifold are stable and meet the desired closed-loop poles.

12.2.2.3 Equivalent Control

The equivalent control \(u_{eq}\) is the unique input that holds the state on \(s = 0\) (i.e., \(\dot{s} = 0\)) for the nominal plant:

\[u_{eq} = -(S B)^{-1} S A x\]

This exists if and only if \(S B\) is nonsingular, which is the relative-degree-one condition: each input channel must directly influence its corresponding sliding variable.

12.2.2.4 Switching (Reaching) Term

The switching term adds a robust push toward the surface:

\[u_{sw} = (S B)^{-1} K \mathrm{sat}(s/\phi)\]

where \(K \in \mathbb{R}^{m}\) is the per-channel switching gain and \(\phi > 0\) is the boundary-layer thickness. The saturation function

\[\mathrm{sat}(\sigma) = \begin{cases} \sigma & |\sigma| \le 1 \\ \mathrm{sign}(\sigma) & |\sigma| > 1 \end{cases}\]

replaces the discontinuous \(\mathrm{sign}(s)\) of ideal SMC with a continuous ramp inside \(|s| \le \phi\), eliminating infinite-bandwidth chattering while maintaining the reaching property.

12.2.2.5 Total Control Law

\[u = u_{eq} - u_{sw} = -(S B)^{-1}\bigl[S A x + K\,\mathrm{sat}(s/\phi)\bigr]\]

12.2.2.6 Reaching Condition

The Lyapunov function \(V = \tfrac{1}{2} s^\top s\) satisfies \(\dot{V} < 0\) outside the boundary layer when \(K_i > |d_i|\) for each matched-disturbance channel \(d_i\). This guarantees finite-time arrival at \(|s| \le \phi\).

12.2.2.7 Sliding-Mode Dynamics

On the surface \(s = 0\), the state evolves according to the \((n-m)\)-dimensional reduced-order system. For a single-input system with \(S = [c_1, \ldots, c_{n-1}, 1]\), the sliding pole is determined by the characteristic polynomial of the first \((n-1)\) rows under the surface constraint.

12.2.3 Complexity Analysis

Operation Time Space Notes
Construction \(O(m^3 + n m)\) \(O(nm + m^2)\) \((SB)^{-1}\) inversion once
ComputeControl \(O(n^2 + m^2)\) \(O(1)\) extra \(S A x\) dominates for large \(n\)
Surface \(O(nm)\) \(O(1)\) extra monitoring only

All data is stored in fixed-size arrays; no heap allocation at any point.

12.2.4 Step-by-Step Walkthrough

Consider a double-integrator plant (\(n=2\), \(m=1\)):

\[A = \begin{bmatrix}0 & 1\\0 & 0\end{bmatrix}, \quad B = \begin{bmatrix}0\\1\end{bmatrix}\]

with surface \(S = [1\; 1]\), gain \(K = 2\), boundary layer \(\phi = 0.05\).

  1. Precompute \(SB = 1\), so \((SB)^{-1} = 1\).
  2. At state \(x = [1, 0]^\top\): \(s = 1 > \phi\), \(\mathrm{sat}(s/\phi) = 1\).
  3. Equivalent control: \(u_{eq} = -(SA)x = -[0\;1][1\;0]^\top = 0\).
  4. Switching term: \(u_{sw} = 1 \cdot 2 \cdot 1 = 2\).
  5. Total: \(u = 0 - 2 = -2\). The large negative input decelerates the state toward the surface.
  6. Once \(|s| \le 0.05\), the saturation ramps linearly and the control becomes smooth.

12.2.5 Pitfalls & Edge Cases

12.2.6 Variants & Generalizations

12.2.7 Applications

12.2.8 Connections to Other Algorithms

12.3 Disturbance Observer

12.3.1 Overview & Motivation

Real plants never match their nominal models. External loads, friction, actuator nonlinearities, and parameter drift inject unmodeled energy into the loop. A Disturbance Observer (DOB) lumps all of these effects into a single equivalent disturbance signal, estimates it online, and subtracts it from the control input so the plant behaves as if it were the clean nominal model.

The key insight is that the DOB wraps around any existing controller without redesigning it. An engineer who has already tuned a PID or LQR for the nominal plant can bolt on a DOB and gain strong disturbance rejection without revisiting the nominal design. This makes DOBs especially attractive for embedded motion controllers — motor drives, precision stages, robotic joints — where the plant is moderately well-known but subject to load variations the nominal model ignores.

12.3.2 Mathematical Theory

12.3.2.1 Setup

Let the true discrete-time plant be \(P(z)\) and the nominal model be \(P_n(z)\). The control input seen by the true plant is \(u_a = u + d\), where \(u\) is the commanded input and \(d\) is the lumped equivalent disturbance that captures model mismatch, external loads, and friction. The plant output is

\[y = P(z)\, u_a = P(z)(u + d).\]

12.3.2.2 Disturbance Estimate

If \(P_n^{-1}(z)\) is applied to \(y\), it reconstructs the effective input that the nominal plant would have needed to produce that output:

\[P_n^{-1}(z)\, y \approx u + d \quad \text{(if } P \approx P_n\text{)}.\]

Subtracting the actual commanded input \(u\) isolates the disturbance:

\[\hat{d} = P_n^{-1}(z)\, y - u.\]

12.3.2.3 Q-Filter and Properness

The plant inverse \(P_n^{-1}(z)\) is generally improper (more zeros than poles) and amplifies high-frequency measurement noise. A low-pass Q-filter \(Q(z)\) is cascaded to make the combination \(Q(z)\,P_n^{-1}(z)\) proper and bandwidth-limited:

\[\hat{d} = Q(z)\,P_n^{-1}(z)\, y - Q(z)\, u.\]

The filter \(Q(z)\) must have relative degree at least equal to the relative degree of \(P_n(z)\) so the realization does not differentiate. Unity DC gain, \(Q(1) = 1\), is required for complete rejection of constant (step) disturbances.

12.3.2.4 Control Law

The DOB corrects the nominal controller output \(c\) by subtracting the estimate:

\[u = c - \hat{d}.\]

The closed-loop system then sees an effective plant of \(P_n(z)\) inside the Q-filter bandwidth — the actual mismatch and disturbances are cancelled — and approaches the uncorrected nominal plant behaviour outside the bandwidth.

12.3.2.5 Frequency-Domain Interpretation

Let \(L(z) = Q(z)\,P_n^{-1}(z)\,P(z)\). The closed-loop sensitivity from disturbance \(d\) to output \(y\) is

\[S_d(z) = \frac{P(z)(1 - Q(z))}{1 + P(z)C(z)(1 - Q(z))}.\]

Inside the Q-filter passband (\(Q \approx 1\)): \(S_d \approx 0\) — the disturbance is rejected. Outside the passband (\(Q \approx 0\)): \(S_d\) equals the nominal sensitivity — the DOB is transparent.

12.3.2.6 Stability Robustness

Robust stability requires the complementary sensitivity of the inner DOB loop to satisfy

\[\left|Q(e^{j\omega})\,\Delta_m(e^{j\omega})\right| < 1 \quad \forall\, \omega,\]

where \(\Delta_m = (P - P_n)/P_n\) is the relative model uncertainty. Widening \(Q\) improves disturbance rejection but shrinks the robust-stability margin — this is the fundamental DOB trade-off.

12.3.3 Complexity Analysis

Operation Time Space Notes
Construct \(O(N \cdot S^2)\) \(O(S^2 + N)\) DC gain simulation, \(S\) = StateSize, \(N\) = 512
Compute \(O(N_{\rm in})\) \(O(1)\) extra Per-channel biquad filter pair
Reset \(O(N_{\rm in})\) \(O(1)\) extra Clears filter states

All storage is fixed-size; no heap allocation occurs at any point in the lifecycle.

12.3.4 Step-by-Step Walkthrough

Consider a first-order discrete plant (\(n=1\), \(m=p=1\)) with \(a=0.9\), \(b=0.1\), \(c=1\), DC gain \(= b/(1-a) = 1\), and a second-order Butterworth Q-filter at 20 Hz (sample rate 1 kHz).

Steady-state with constant disturbance \(d = 0.5\), nominal command \(c = 0\):

  1. Plant output settles to \(y_{ss} = P(1)\,d = 1 \cdot 0.5 = 0.5\).
  2. Q-filter path 1: \(Q(1)\,P_n^{-1}(1)\,y_{ss} = 1 \cdot 1 \cdot 0.5 = 0.5\).
  3. Q-filter path 2: \(Q(1)\,u_{ss} = 1 \cdot (c - \hat{d})_{ss}\).
  4. At equilibrium path 1 \(-\) path 2 \(= \hat{d}\) and \(u_{ss} = c - \hat{d}\), giving \(\hat{d} = 0.5 = d\). The estimate converges exactly.
  5. The corrected input is \(u = 0 - 0.5 = -0.5\), so the effective input to the plant is \(-0.5 + 0.5 = 0\) — the disturbance is cancelled.

12.3.5 Pitfalls & Edge Cases

12.3.6 Variants & Generalizations

12.3.7 Applications

12.3.8 Connections to Other Algorithms

12.4 H∞ State-Feedback Control

12.4.1 Overview & Motivation

Robust control guarantees performance under worst-case conditions rather than average ones. H∞ state-feedback synthesizes a linear gain that minimizes the largest possible ratio of output energy to disturbance energy across all bounded disturbances — the induced \(\mathcal{L}_2\) gain. This provable attenuation bound is what safety-critical and certification-driven embedded systems require: not a best-guess response, but a hard upper limit on how badly an unknown disturbance can degrade performance.

12.4.2 Mathematical Theory

12.4.2.1 Generalized Plant

The design is framed around the generalized plant

\[ x_{k+1} = A x_k + B_1 w_k + B_2 u_k, \quad z_k = C_1 x_k + D_{12} u_k \]

where \(w \in \mathbb{R}^{n_w}\) is the exogenous disturbance, \(u \in \mathbb{R}^{n_u}\) is the control input, and \(z \in \mathbb{R}^{n_z}\) is the performance (error) output.

12.4.2.2 H∞ Performance Criterion

The controller objective is to find \(u = -K x\) such that

\[ \sup_{w \neq 0} \frac{\|z\|_2}{\|w\|_2} < \gamma \]

where \(\gamma > 0\) is the prescribed attenuation level. Finding the smallest feasible \(\gamma\) (the optimal \(\gamma^*\)) determines the best achievable robustness.

12.4.2.3 Game-Theoretic Discrete Algebraic Riccati Equation (GARE)

The H∞ state-feedback gain is derived from the discrete Riccati equation

\[ X = A^\top X A - A^\top X B \tilde{R}^{-1} B^\top X A + Q \]

with the augmented input matrix \(B = [B_2 \mid B_1]\) and the indefinite weight matrix

\[ \tilde{R} = \begin{pmatrix} I_{n_u} & 0 \\ 0 & -\gamma^2 I_{n_w} \end{pmatrix}. \]

The negative block encodes the adversarial role of the disturbance: the disturbance player maximizes while the control player minimizes. A positive-semidefinite stabilizing solution \(X \geq 0\) exists if and only if \(\gamma\) is above the optimal level \(\gamma^*\).

12.4.2.4 Gain Extraction

From the Riccati solution \(X\), the augmented gain is

\[ K_{\text{full}} = (\tilde{R} + B^\top X B)^{-1} B^\top X A. \]

Only the top \(n_u\) rows — the control block — form the feedback gain \(K\), and the closed-loop map is \(A_{\text{cl}} = A - B_2 K\).

12.4.2.5 Feasibility Conditions

A given \(\gamma\) is feasible when: 1. The GARE has a positive-semidefinite solution \(X \geq 0\). 2. The disturbance block \(-\gamma^2 I + B_1^\top X B_1 \prec 0\) is negative definite, confirming that the disturbance remains a genuine maximizer rather than a destabilizing force.

12.4.2.6 Bisection for Optimal \(\gamma\)

Neither condition holds for \(\gamma < \gamma^*\); both hold for \(\gamma > \gamma^*\). A standard bisection on \([\gamma_{\min}, \gamma_{\max}]\) converges to \(\gamma^*\) at a linear rate.

12.4.3 Complexity Analysis

Phase Time Complexity Space Notes
Synthesize \(O(\log((\gamma_{\max}-\gamma_{\min})/\epsilon) \cdot n^3)\) \(O(n^2)\) Dominated by iterative DARE solves
ComputeControl \(O(n_u \cdot n)\) \(O(1)\) Single matrix-vector multiply

12.4.4 Step-by-Step Walkthrough

Consider a 2-state discrete plant with one disturbance and one control input.

  1. Bisect: choose \(g = (\gamma_{\min} + \gamma_{\max}) / 2\); stack \(B = [B_2 \mid B_1]\); build \(\tilde{R} = \text{diag}(1, -g^2)\).
  2. Solve GARE: run the iterative DARE solver with indefinite \(\tilde{R}\) until convergence.
  3. Check feasibility: verify \(X_{ii} \geq 0\) for all \(i\) and \(-g^2 + (B_1^\top X B_1)_{ii} < 0\).
  4. Update bisection: if feasible, tighten (\(\gamma_{\max} \leftarrow g\)); otherwise relax (\(\gamma_{\min} \leftarrow g\)).
  5. Finalize: at convergence, solve GARE at \(\gamma_{\max}\), extract the control rows of \(K_{\text{full}}\), and verify \(A - B_2 K\) is Schur-stable (all eigenvalues inside the unit disk).

12.4.5 Pitfalls & Edge Cases

12.4.6 Variants & Generalizations

12.4.7 Applications

12.4.8 Connections to Other Algorithms

13 Windowing

13.1 Window Functions

13.1.1 Overview & Motivation

A window function is a mathematical taper applied to a finite-length signal before spectral analysis. When we extract a segment of \(N\) samples from a continuous signal, we implicitly multiply by a rectangular window — a hard cut that introduces sharp discontinuities at the segment boundaries. In the frequency domain these discontinuities spread energy across all bins, an artefact called spectral leakage.

Window functions taper the segment smoothly toward zero at both ends, trading a small amount of frequency resolution (wider main lobe) for dramatically reduced leakage (lower side lobes). Choosing the right window is a fundamental step in every FFT-based pipeline.

13.1.2 Mathematical Theory

13.1.2.1 General Form

A symmetric window of length \(N\) is a sequence \(w[n],\; n = 0, 1, \ldots, N-1\), designed so that \(w[0] \approx w[N-1] \approx 0\) (except for the rectangular case).

13.1.2.2 Common Windows

Window Definition Main-Lobe Width Peak Side Lobe
Rectangular \(w[n] = 1\) \(2/N\) \(-13\) dB
Hamming \(w[n] = 0.54 - 0.46\cos\!\left(\frac{2\pi n}{N-1}\right)\) \(4/N\) \(-43\) dB
Hann \(w[n] = 0.5\left(1 - \cos\!\left(\frac{2\pi n}{N-1}\right)\right)\) \(4/N\) \(-32\) dB
Blackman \(w[n] = 0.42 - 0.5\cos\!\left(\frac{2\pi n}{N-1}\right) + 0.08\cos\!\left(\frac{4\pi n}{N-1}\right)\) \(6/N\) \(-58\) dB

13.1.2.3 Frequency-Domain Perspective

In the frequency domain, windowing is convolution with the window’s Fourier transform \(W(f)\). Each spectral line of the signal is smeared by \(W(f)\). A narrower main lobe preserves resolution; lower side lobes suppress leakage.

13.1.2.4 Coherent Gain

The coherent gain \(CG\) of a window normalizes the amplitude after windowing:

\[CG = \frac{1}{N}\sum_{n=0}^{N-1} w[n]\]

For amplitude-accurate measurements, divide the windowed FFT output by \(CG\).

13.1.2.5 Processing Gain

The processing gain \(PG\) compares the noise bandwidth of the window to the rectangular window:

\[PG = \frac{\left(\sum w[n]\right)^2}{N \sum w[n]^2}\]

Window Processing Gain
Rectangular 1.00
Hamming 0.73
Hann 0.67
Blackman 0.57

13.1.3 Complexity Analysis

Operation Time Space
Generate \(N\) window coefficients \(O(N)\) \(O(N)\)
Apply window (element-wise multiply) \(O(N)\) \(O(1)\) extra
Pre-computed lookup \(O(1)\) per sample \(O(N)\)

Window generation is dominated by trigonometric evaluations (\(\cos\)). For repeated use at a fixed \(N\), pre-compute and store the coefficients in a lookup table.

13.1.4 Step-by-Step Walkthrough

Scenario: Apply a Hann window to \(N = 8\) samples of a pure tone at bin 2.5 (non-integer frequency → leakage expected).

Step 1 — Generate the Hann window

\(n\) \(w[n] = 0.5(1 - \cos(2\pi n / 7))\)
0 0.000
1 0.188
2 0.611
3 0.950
4 0.950
5 0.611
6 0.188
7 0.000

Step 2 — Element-wise multiply

\[x_w[n] = x[n] \cdot w[n]\]

The signal tapers to zero at both ends, removing the hard edges.

Step 3 — FFT of windowed signal

Compared to the un-windowed (rectangular) FFT: - The peak at bin 2.5 is slightly broader (main lobe widens from 2 to 4 bins). - The leakage floor drops from \(-13\) dB to \(-32\) dB.

graph LR
    subgraph Before Windowing
        R["Rectangular window<br/>Sharp edges → leakage"]
    end
    subgraph After Windowing
        H["Hann window<br/>Smooth taper → low side lobes"]
    end
    R --> |"apply Hann"| H
    H --> FFT["FFT → cleaner spectrum"]

13.1.5 Pitfalls & Edge Cases

13.1.6 Variants & Generalizations

Variant Key Difference
Kaiser window Parameterized by \(\beta\); allows continuous trade-off between main-lobe width and side-lobe level
Flat-top window Near-zero amplitude error at the cost of very wide main lobe; used for calibration
Gaussian window Smooth, no side-lobe discontinuities; parameterized by \(\sigma\)
Dolph–Chebyshev Equiripple side lobes at a prescribed level; optimal for a given main-lobe width
DPSS (Slepian) Maximum energy concentration in a given bandwidth; used in multi-taper spectral estimation

13.1.7 Applications

13.1.8 Connections to Other Algorithms

graph TD
    W["Window Functions"]
    FFT["Fast Fourier Transform"]
    PSD["Power Density Spectrum"]
    DCT["Discrete Cosine Transform"]
    W --> FFT
    W --> PSD
    W --> DCT
Algorithm Relationship
FFT Window is applied before the FFT to reduce spectral leakage
Power Density Spectrum Built-in windowing per segment in Welch’s method
DCT DCT’s implicit symmetry provides partial leakage suppression, but windowing still helps

14 References