Numerical Toolbox

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

Gabriel Francisco dos Santos

gabriel.fra.santos@gmail.com

August 01, 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

For a length-\(N\) sequence \(x[n]\), the DCT-II is defined as:

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

The inverse (DCT-III, also called IDCT) recovers \(x[n]\):

\[x[n] = \frac{1}{N}\left[\frac{X[0]}{2} + \sum_{k=1}^{N-1} X[k] \cos\!\left(\frac{\pi}{N}\left(n + \tfrac{1}{2}\right) k\right)\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: \(X[k] = 2 \cdot \text{Re}\!\left(W[k] \cdot Y[k]\right)\), where \(W[k] = e^{-j\pi k / 2N}\).

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,\; -2,\; -3-j]\]

Step 3 — Apply twiddle factors \(W[k] = e^{-j\pi k/8}\)

\(k\) \(W[k]\) \(W[k] \cdot Y[k]\) \(X[k] = 2 \cdot \text{Re}(\cdot)\)
0 1 10 20
1 \(e^{-j\pi/8}\) ≈ −2.22 − 1.90j ≈ −4.44
2 \(e^{-j\pi/4}\) ≈ −1.41 + 1.41j ≈ −2.83
3 \(e^{-j3\pi/8}\) ≈ −0.24 + 3.07j ≈ −0.47

Output: \(X \approx [20, -4.44, -2.83, -0.47]\)

Notice how most of the energy is in \(X[0]\) (the DC component) — energy compaction in action.

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 \(N\) steps the DFT bin follows from the final two state values:

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

Separating real and imaginary parts:

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

\[\mathrm{Im}\{X[k]\} = s[N-2]\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 - 1 = -1\)
2 -1 \(-1 + 0 - 0 = -1\) (note: \(s[-1]=0\))
3 0 \(0 + 0 - (-1) = 1\)

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

\(\mathrm{Re}\{X[1]\} = 1 - (-1)\cdot 0 = 1\)

\(\mathrm{Im}\{X[1]\} = (-1)\cdot 1 = -1\)

Direct DFT check: \(X[1] = 1 - j\). 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.

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 Hilbert Transform / Analytic Signal

1.8.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.8.2 Mathematical Theory

1.8.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.8.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.8.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.8.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.8.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.8.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.8.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.8.5 Pitfalls & Edge Cases

1.8.6 Variants & Generalizations

1.8.7 Applications

1.8.8 Connections to Other Algorithms

1.9 Convolution & Correlation

1.9.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.9.2 Mathematical Theory

1.9.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.9.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.9.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.9.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.9.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.9.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.9.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.9.5 Pitfalls & Edge Cases

1.9.6 Variants & Generalizations

1.9.7 Applications

1.9.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 PID Controller

3.2.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.2.2 Mathematical Theory

3.2.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.2.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.2.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.2.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.2.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.2.5 Pitfalls & Edge Cases

3.2.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.2.7 Applications

3.2.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.3 LQR Controller (Linear Quadratic Regulator)

3.3.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.3.2 Mathematical Theory

3.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.5 Pitfalls & Edge Cases

3.3.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.3.7 Applications

3.3.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.4 Integral State Feedback (LQI / Servo)

3.4.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.4.2 Mathematical Theory

3.4.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.4.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.4.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.4.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.4.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.4.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.4.5 Pitfalls & Edge Cases

3.4.6 Variants & Generalizations

3.4.7 Applications

3.4.8 Connections to Other Algorithms

3.5 Linear Quadratic Gaussian (LQG) Controller

3.5.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.5.2 Mathematical Theory

3.5.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.5.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.5.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.5.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.5.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.5.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.5.4 Numerical Considerations

3.5.5 Pitfalls & Edge Cases

3.5.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.5.7 Variants & Generalizations

3.5.8 Applications

3.5.9 Connections to Other Algorithms

3.6 Model Predictive Controller (MPC)

3.6.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.6.2 Mathematical Theory

3.6.2.1 Prerequisites

3.6.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.6.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.6.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.6.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.6.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.6.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.6.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.6.5 Pitfalls & Edge Cases

3.6.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.6.7 Applications

3.6.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.7 Linear Time-Invariant System Model

3.7.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.7.2 Mathematical Theory

3.7.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.7.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.7.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.7.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.7.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.7.4 Numerical Considerations

3.7.5 Pitfalls & Edge Cases

3.7.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.7.7 Variants & Generalizations

3.7.8 Applications

3.7.9 Connections to Other Algorithms

3.8 Feedforward / 2-DOF Controller

3.8.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.8.2 Mathematical Theory

3.8.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.8.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.8.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.8.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.8.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.8.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.8.5 Pitfalls & Edge Cases

3.8.6 Variants & Generalizations

3.8.7 Applications

3.8.8 Connections to Other Algorithms

3.9 Luenberger Observer

3.9.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.9.2 Mathematical Theory

3.9.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.9.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.9.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.9.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.9.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.9.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.9.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.9.5 Pitfalls & Edge Cases

3.9.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.9.7 Applications

3.9.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.10 Gain-Scheduled Controller

3.10.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.10.2 Mathematical Theory

3.10.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.10.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.10.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.10.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.10.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.10.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.10.5 Pitfalls & Edge Cases

3.10.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.10.7 Applications

3.10.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.11 Lead-Lag Compensator

3.11.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.11.2 Mathematical Theory

3.11.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.11.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.11.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.11.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.11.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.11.5 Pitfalls & Edge Cases

3.11.6 Variants & Generalizations

3.11.7 Applications

3.11.8 Connections to Other Algorithms

3.12 Saturation / Rate-Limiter / Slew Blocks

3.12.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.12.2 Mathematical Theory

3.12.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.12.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.12.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.12.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.12.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.12.5 Pitfalls & Edge Cases

3.12.6 Variants & Generalizations

3.12.7 Applications

3.12.8 Connections to Other Algorithms

4 Dynamics & Modeling

4.1 Articulated Body Algorithm (ABA)

4.1.1 Overview & Motivation

The Articulated Body Algorithm (ABA) is the most efficient method for computing forward dynamics of serial kinematic chains. Given joint positions \(q\), velocities \(\dot{q}\), and applied torques \(\tau\), it computes joint accelerations \(\ddot{q}\) in \(O(n)\) time — linear in the number of links.

This is the forward-dynamics counterpart of the Recursive Newton-Euler Algorithm (which solves inverse dynamics). Together, they form the two fundamental \(O(n)\) algorithms in rigid-body dynamics.

Alternatives like Euler-Lagrange require explicitly forming and inverting the \(n \times n\) mass matrix (\(O(n^3)\)), making ABA significantly faster for chains with more than 3-4 links.

4.1.2 Mathematical Theory

4.1.2.1 Problem Statement

Given a serial chain of \(n\) rigid bodies connected by revolute joints, find:

\[\ddot{q} = M(q)^{-1} [\tau - C(q, \dot{q})\dot{q} - g(q)]\]

ABA computes this without ever forming or inverting \(M\).

4.1.2.2 Algorithm Structure

ABA has three passes over the kinematic chain:

  1. Forward pass (base → tip): Compute link kinematics — angular velocities, linear velocities, and velocity-product accelerations from joint positions and velocities.

  2. Backward pass (tip → base): Compute articulated-body inertias \(I^A_i\) and bias wrenches \(p^A_i\) for each link. At each step, the joint projection removes one degree of freedom from the spatial inertia, and the remainder is propagated to the parent.

  3. Forward pass (base → tip): Resolve joint accelerations using the articulated-body quantities. Each joint acceleration \(\ddot{q}_i\) is computed from the projected articulated inertia and the transformed parent acceleration.

4.1.2.3 Key Formulas

Articulated-body inertia starts as the rigid-body spatial inertia of each link, then accumulates child contributions:

\[I^A_i \leftarrow I^A_i + X^*_{i+1} \hat{I}^A_{i+1} X_{i+1}\]

where \(\hat{I}^A\) is the inertia after removing the joint DOF (the “downdate”):

\[\hat{I}^A_i = I^A_i - \frac{U_i U_i^T}{D_i}\]

with \(U_i = I^A_i s_i\), \(D_i = s_i^T U_i\), and \(s_i\) the joint motion subspace (joint axis for revolute joints).

Joint acceleration:

\[\ddot{q}_i = \frac{u_i - U_i^T \hat{a}_i}{D_i}\]

where \(u_i = \tau_i - s_i^T p^A_i\) and \(\hat{a}_i\) is the spatial acceleration contribution from the parent.

4.1.3 Complexity Analysis

Case Time Space Notes
All \(O(n)\) \(O(n)\) Three linear passes over the chain

Each pass visits every link exactly once, performing constant-time spatial algebra (3×3 matrix operations) per link.

4.1.4 Step-by-Step Walkthrough

Consider a 2-link planar arm with unit masses, unit lengths, Y-axis joints, zero velocities, and zero applied torques under gravity \(g = [0, 0, -9.81]^T\).

Pass 1 — Forward kinematics: both links at \(q = [0, 0]^T\) with \(\dot{q} = [0, 0]^T\), so all velocities and velocity-product terms are zero.

Pass 2 — Backward pass: starting from link 2, compute the rigid-body inertia, project out the joint DOF, transform to link 1’s frame, and accumulate. The articulated inertia at the base now represents the effective inertia of the entire chain.

Pass 3 — Forward pass: at the base, the gravitational acceleration is transformed into the base frame and used to compute \(\ddot{q}_1\). The resulting acceleration is propagated to link 2 to compute \(\ddot{q}_2\).

The result: both joints accelerate downward due to gravity, with the base joint carrying the larger moment from the full chain.

4.1.5 Pitfalls & Edge Cases

4.1.6 Variants & Generalizations

4.1.7 Applications

4.1.8 Connections to Other Algorithms

4.2 Euler-Lagrange Dynamics

4.2.1 Overview & Motivation

The Euler-Lagrange equations provide a systematic way to derive the equations of motion for mechanical systems from energy principles. Instead of tracking forces on each body (as in Newton’s method), they express dynamics in terms of generalized coordinates \(q\) and the system’s kinetic and potential energy.

For a robotic manipulator or any articulated mechanism with \(n\) degrees of freedom, the Euler-Lagrange formulation yields a compact matrix equation:

\[M(q)\ddot{q} + C(q, \dot{q})\dot{q} + g(q) = \tau\]

Where: - \(q \in \mathbb{R}^n\) — generalized coordinates (joint angles/positions) - \(\dot{q}, \ddot{q}\) — generalized velocities and accelerations - \(M(q) \in \mathbb{R}^{n \times n}\) — mass (inertia) matrix, symmetric positive definite - \(C(q, \dot{q})\dot{q} \in \mathbb{R}^n\) — Coriolis and centrifugal terms - \(g(q) \in \mathbb{R}^n\) — gravitational terms - \(\tau \in \mathbb{R}^n\) — generalized forces/torques (inputs)

This formulation supports two fundamental computations: - Forward dynamics: given torques \(\tau\), compute accelerations \(\ddot{q} = M^{-1}(\tau - C\dot{q} - g)\) - Inverse dynamics: given desired accelerations \(\ddot{q}\), compute required torques \(\tau = M\ddot{q} + C\dot{q} + g\)

4.2.2 Mathematical Theory

4.2.2.1 Prerequisites

The Lagrangian of a system is defined as:

\[\mathcal{L}(q, \dot{q}) = T(q, \dot{q}) - V(q)\]

where \(T\) is kinetic energy and \(V\) is potential energy.

4.2.2.2 Core Definitions

The Euler-Lagrange equation for coordinate \(q_i\) is:

\[\frac{d}{dt}\frac{\partial \mathcal{L}}{\partial \dot{q}_i} - \frac{\partial \mathcal{L}}{\partial q_i} = \tau_i \quad \text{for } i = 1, \ldots, n\]

For a rigid-body system, kinetic energy takes the form:

\[T = \frac{1}{2}\dot{q}^T M(q) \dot{q}\]

Applying the Euler-Lagrange equation to this quadratic form yields the standard manipulator equation:

\[M(q)\ddot{q} + C(q, \dot{q})\dot{q} + g(q) = \tau\]

4.2.2.3 Mass Matrix \(M(q)\)

The mass matrix is always symmetric (\(M = M^T\)) and positive definite (\(\dot{q}^T M \dot{q} > 0\) for \(\dot{q} \neq 0\)). These properties guarantee: - The forward dynamics linear system \(M\ddot{q} = f\) always has a unique solution - Physical kinetic energy is always non-negative

4.2.2.4 Coriolis Matrix \(C(q, \dot{q})\)

The Coriolis matrix captures velocity-dependent forces using Christoffel symbols of the first kind:

\[c_{ij} = \sum_{k=1}^{n} \frac{1}{2}\left(\frac{\partial m_{ij}}{\partial q_k} + \frac{\partial m_{ik}}{\partial q_j} - \frac{\partial m_{jk}}{\partial q_i}\right)\dot{q}_k\]

A key property: \(\dot{M} - 2C\) is skew-symmetric, which is important for passivity-based control.

4.2.2.5 Gravity Vector \(g(q)\)

\[g_i(q) = \frac{\partial V}{\partial q_i}\]

where \(V(q)\) is the total potential energy of the system.

4.2.3 Complexity Analysis

Operation Time Space Notes
Forward dynamics \(O(n^3)\) \(O(n^2)\) Dominated by \(M^{-1}\) solve via Gaussian elimination
Inverse dynamics \(O(n^2)\) \(O(n^2)\) Matrix-vector multiplication \(M \ddot{q}\)
Model evaluation Model-dependent \(O(n^2)\) Computing \(M(q)\), \(C(q,\dot{q})\), \(g(q)\)

For systems with \(n \leq 6\) DOF (typical robotic manipulators), the \(O(n^3)\) cost of Gaussian elimination is negligible. For larger systems, consider the Recursive Newton-Euler Algorithm (RNEA) which achieves \(O(n)\) inverse dynamics.

4.2.4 Step-by-Step Walkthrough

4.2.4.1 Example: Simple Pendulum (1-DOF)

A point mass \(m\) on a rigid rod of length \(l\):

System parameters: \(m = 1\,\text{kg}\), \(l = 1\,\text{m}\), \(g_0 = 9.81\,\text{m/s}^2\)

Model matrices: - \(M(q) = ml^2 = 1.0\) - \(C(q, \dot{q})\dot{q} = 0\) (single DOF → no Coriolis) - \(g(q) = mgl\sin(q) = 9.81\sin(q)\)

Forward dynamics at \(q = 0.5\,\text{rad}\), \(\dot{q} = 0\), \(\tau = 0\):

\[\ddot{q} = M^{-1}(\tau - C\dot{q} - g) = \frac{0 - 0 - 9.81\sin(0.5)}{1.0} = -4.703\,\text{rad/s}^2\]

Inverse dynamics for holding position (\(\ddot{q} = 0\)):

\[\tau = M\ddot{q} + C\dot{q} + g = 0 + 0 + 9.81\sin(0.5) = 4.703\,\text{N·m}\]

For a 2-DOF arm with equal links (\(m_1 = m_2 = 1\,\text{kg}\), \(l_1 = l_2 = 1\,\text{m}\)):

At rest (\(q = [0, 0]^T\), \(\dot{q} = [0, 0]^T\)): - \(\sin(q_1) = 0\), \(\sin(q_1 + q_2) = 0\) - \(g = [0, 0]^T\) → gravity terms vanish when links hang vertically - Forward dynamics with \(\tau = 0\) produces \(\ddot{q} = [0, 0]^T\) (equilibrium)

4.2.5 Pitfalls & Edge Cases

4.2.6 Variants & Generalizations

Variant Trade-off Use Case
Euler-Lagrange (this) \(O(n^3)\) forward, closed-form, intuitive Small systems (\(n \leq 6\)), control design, analysis
Newton-Euler Body-by-body force balance Single rigid bodies, intuitive for simple systems
Recursive Newton-Euler (RNEA) \(O(n)\) inverse dynamics Large kinematic chains, real-time control
Articulated Body Algorithm (ABA) \(O(n)\) forward dynamics Large chains, simulation
Hamiltonian formulation Phase-space, symplectic integration Long-horizon simulation, energy preservation

4.2.7 Applications

4.2.8 Connections to Other Algorithms

4.3 Newton-Euler Dynamics

4.3.1 Overview & Motivation

The Newton-Euler formulation describes the dynamics of a single rigid body by directly applying Newton’s second law for translational motion and Euler’s equation for rotational motion. Unlike the Euler-Lagrange approach which works in generalized (joint-space) coordinates, Newton-Euler operates in Cartesian space, computing forces and torques on individual bodies.

In the body-fixed reference frame, the equations are:

\[F = m(\dot{v} + \omega \times v)\] \[\tau = I\dot{\omega} + \omega \times (I\omega)\]

Where: - \(F \in \mathbb{R}^3\) — applied force in the body frame - \(\tau \in \mathbb{R}^3\) — applied torque in the body frame - \(m\) — body mass (scalar) - \(I \in \mathbb{R}^{3 \times 3}\) — inertia tensor in the body frame - \(v, \dot{v} \in \mathbb{R}^3\) — linear velocity and acceleration in the body frame - \(\omega, \dot{\omega} \in \mathbb{R}^3\) — angular velocity and acceleration in the body frame - \(\omega \times v\) — Coriolis term due to body-frame formulation - \(\omega \times (I\omega)\) — gyroscopic coupling term

This supports two computations: - Forward dynamics: given forces/torques, compute linear and angular accelerations - Inverse dynamics: given desired accelerations, compute required forces/torques

4.3.2 Mathematical Theory

4.3.2.1 Newton’s Second Law (Translation)

In an inertial frame, Newton’s second law is simply \(F = ma\). In a body-fixed (rotating) frame, the transport theorem introduces an additional term:

\[F = m(\dot{v} + \omega \times v)\]

The \(\omega \times v\) term is the Coriolis acceleration that arises because the reference frame itself is rotating.

Forward: \(\dot{v} = F/m - \omega \times v\)

Inverse: \(F = m(\dot{v} + \omega \times v)\)

4.3.2.2 Euler’s Equation (Rotation)

Euler’s equation governs rotational dynamics:

\[\tau = I\dot{\omega} + \omega \times (I\omega)\]

The \(\omega \times (I\omega)\) term is the gyroscopic torque — it couples different rotational axes when the inertia tensor is non-spherical. For a body spinning about a non-principal axis, this term causes precession even without external torque.

Forward: \(\dot{\omega} = I^{-1}(\tau - \omega \times (I\omega))\)

This requires solving the \(3 \times 3\) linear system \(I\dot{\omega} = \tau - \omega \times (I\omega)\) via Gaussian elimination.

Inverse: \(\tau = I\dot{\omega} + \omega \times (I\omega)\)

This is a direct matrix-vector multiply plus cross product — no linear solve needed.

4.3.2.3 Properties of the Inertia Tensor

The inertia tensor \(I\) is: - Symmetric: \(I = I^T\) (6 independent components, not 9) - Positive definite: \(\omega^T I \omega > 0\) for \(\omega \neq 0\) - Constant in body frame: For a rigid body, \(I\) does not change with the body’s orientation when expressed in the body frame

When \(I\) is diagonal (principal axes frame), components are the principal moments of inertia \(I_{xx}, I_{yy}, I_{zz}\), and Euler’s equation simplifies to the well-known form:

\[\tau_x = I_{xx}\dot{\omega}_x + (I_{zz} - I_{yy})\omega_y\omega_z\] \[\tau_y = I_{yy}\dot{\omega}_y + (I_{xx} - I_{zz})\omega_z\omega_x\] \[\tau_z = I_{zz}\dot{\omega}_z + (I_{yy} - I_{xx})\omega_x\omega_y\]

4.3.3 Complexity Analysis

Operation Time Space Notes
Forward dynamics \(O(1)\) \(O(1)\) Fixed 3×3 system; Gaussian elimination on 3×3 matrix
Inverse dynamics \(O(1)\) \(O(1)\) Matrix-vector multiply + cross products
Cross product \(O(1)\) \(O(1)\) 6 multiplications and 3 subtractions

All operations are constant-time since the spatial dimension is fixed at 3. The \(3 \times 3\) Gaussian elimination is effectively a small constant cost.

4.3.4 Step-by-Step Walkthrough

4.3.4.1 Example: Uniform Sphere

Parameters: mass \(m = 2\,\text{kg}\), radius \(r = 0.5\,\text{m}\)

Inertia tensor: \(I = \frac{2}{5}mr^2 \cdot \mathbf{I}_3 = 0.2 \cdot \mathbf{I}_3\)

Forward dynamics with \(F = [6, 0, 0]^T\,\text{N}\), \(\tau = [0, 0, 1]^T\,\text{N·m}\), \(v = \omega = 0\):

\[\dot{v} = F/m - 0 = [3, 0, 0]^T \,\text{m/s}^2\] \[\dot{\omega} = I^{-1}\tau = [0, 0, 5]^T \,\text{rad/s}^2\]

4.3.4.2 Example: Gyroscopic Precession

Asymmetric body with \(I = \text{diag}(1, 2, 3)\,\text{kg·m}^2\)

Spinning at \(\omega = [1, 1, 0]^T\,\text{rad/s}\) with no external torque:

\[I\omega = [1, 2, 0]^T\] \[\omega \times (I\omega) = [1,1,0] \times [1,2,0] = [0, 0, 1]^T\] \[\dot{\omega} = I^{-1}(0 - [0,0,1]) = [0, 0, -\tfrac{1}{3}]^T \,\text{rad/s}^2\]

The body accelerates about the z-axis despite no external torque — this is the gyroscopic coupling effect due to asymmetric inertia.

4.3.5 Pitfalls & Edge Cases

4.3.6 Variants & Generalizations

Variant Key Difference Use Case
Newton-Euler (this) Single rigid body, Cartesian space Spacecraft attitude, single-body simulation
Recursive Newton-Euler (RNEA) Multi-body recursive algorithm, \(O(n)\) Robot inverse dynamics, real-time control
Euler-Lagrange Generalized coordinates, joint space Control design, analytical derivations
Spatial vector algebra 6D twist/wrench representation Compact multi-body formulations

4.3.7 Applications

4.3.8 Connections to Other Algorithms

4.4 Recursive Newton-Euler Algorithm (RNEA)

4.4.1 Overview & Motivation

The Recursive Newton-Euler Algorithm (RNEA) is the most efficient method for computing inverse dynamics of serial kinematic chains (robot arms, manipulators). Given joint positions, velocities, and desired accelerations, it computes the required joint torques in \(O(n)\) time — linear in the number of links.

This contrasts with the Euler-Lagrange approach, which requires explicitly forming and multiplying \(n \times n\) matrices (\(O(n^3)\)). For a 6-DOF robot arm, RNEA is roughly 10× faster than the matrix-based approach.

The algorithm has two passes over the kinematic chain:

  1. Forward pass (base → tip): propagate angular velocities, angular accelerations, and linear accelerations from link to link using kinematic relationships
  2. Backward pass (tip → base): accumulate forces and torques from the tip back to the base using Newton-Euler equations on each link

The joint torque at each joint is then the projection of the net torque onto the joint axis.

4.4.2 Mathematical Theory

4.4.2.1 Kinematic Chain Model

A serial chain of \(n\) rigid bodies (links) connected by revolute joints. Each link \(i\) is described by:

The rotation matrix \(R_i(q_i)\) transforms vectors from link \(i\) frame to parent frame, computed via Rodrigues’ formula from the joint angle \(q_i\) and joint axis \(\hat{z}_i\).

4.4.2.2 Forward Pass — Velocity and Acceleration Propagation

For link \(i\), given parent link \(i-1\) quantities:

Angular velocity: \[\omega_i = R_i^T \omega_{i-1} + \dot{q}_i \hat{z}_i\]

Angular acceleration: \[\dot{\omega}_i = R_i^T \dot{\omega}_{i-1} + \ddot{q}_i \hat{z}_i + \omega_i \times (\dot{q}_i \hat{z}_i)\]

Linear acceleration at joint origin of link \(i\): \[a_{J_i} = R_i^T \left( a_{J_{i-1}} + \dot{\omega}_{i-1} \times r_{p \to j}^{(i)} + \omega_{i-1} \times (\omega_{i-1} \times r_{p \to j}^{(i)}) \right)\]

Linear acceleration at center of mass: \[a_{C_i} = a_{J_i} + \dot{\omega}_i \times r_{j \to c}^{(i)} + \omega_i \times (\omega_i \times r_{j \to c}^{(i)})\]

For the base link (\(i = 0\)), the “parent” is the world frame with \(\omega_{-1} = 0\) and \(a_{J_{-1}} = -g\) (gravity expressed as a base acceleration, a standard RNEA convention).

4.4.2.3 Backward Pass — Force and Torque Accumulation

For each link \(i\), starting from the tip:

Net force on link \(i\): \[f_i = m_i a_{C_i} + \sum_{\text{children } j} R_j f_j\]

Net torque on link \(i\) (about joint): \[\tau_i = I_i \dot{\omega}_i + \omega_i \times (I_i \omega_i) + r_{j \to c}^{(i)} \times (m_i a_{C_i}) + \sum_{\text{children } j} \left( R_j \tau_j + r_{p \to j}^{(j)} \times R_j f_j \right)\]

Joint torque: \[\tau_{q_i} = \hat{z}_i^T \tau_i\]

4.4.2.4 Rodrigues’ Rotation Formula

The rotation matrix for angle \(\theta\) about unit axis \(\hat{u} = [u_x, u_y, u_z]^T\):

\[R = \cos\theta \cdot \mathbf{I}_3 + (1 - \cos\theta) \hat{u}\hat{u}^T + \sin\theta [\hat{u}]_\times\]

where \([\hat{u}]_\times\) is the skew-symmetric matrix of \(\hat{u}\).

4.4.3 Complexity Analysis

Operation Time Space Notes
Inverse dynamics \(O(n)\) \(O(n)\) Two linear passes over \(n\) links
Per-link forward pass \(O(1)\) \(O(1)\) Fixed 3×3 matrix-vector operations
Per-link backward pass \(O(1)\) \(O(1)\) Cross products and additions
Rotation matrix \(O(1)\) \(O(1)\) Rodrigues’ formula: trig functions + 3×3 matrix

Compared to Euler-Lagrange \(O(n^3)\) inverse dynamics, RNEA is dramatically faster for large \(n\). For \(n = 6\) (typical robot arm), RNEA performs roughly 780 floating-point operations vs. ~14,000 for the matrix approach.

4.4.4 Step-by-Step Walkthrough

4.4.4.1 Example: 1-DOF Pendulum

A single thin rod of mass \(m = 2\,\text{kg}\) and length \(l = 1\,\text{m}\), rotating about the z-axis. Joint at origin, CoM at \([l/2, 0, 0]^T\).

Inertia at CoM: \(I = ml^2/12 = 0.167\,\text{kg·m}^2\) (about y and z axes)

Setup: \(q = 0\), \(\dot{q} = 0\), \(\ddot{q} = 1\,\text{rad/s}^2\), zero gravity.

Forward pass: - \(\omega_0 = [0, 0, 0]^T + 0 \cdot [0, 0, 1]^T = [0, 0, 0]^T\) - \(\dot{\omega}_0 = [0, 0, 1]^T\) (from \(\ddot{q}\)) - \(a_{C_0} = 0 + [0, 0, 1] \times [0.5, 0, 0] + 0 = [0, 0.5, 0]^T\,\text{m/s}^2\)

Backward pass: - \(f_0 = 2 \cdot [0, 0.5, 0]^T = [0, 1, 0]^T\,\text{N}\) - \(\tau_0 = I \cdot [0, 0, 1]^T + 0 + [0.5, 0, 0] \times [0, 1, 0]^T\) - \(= [0, 0, 0.167]^T + [0, 0, 0.5]^T = [0, 0, 0.667]^T\) - \(\tau_q = [0, 0, 1]^T \cdot [0, 0, 0.667]^T = 0.667\,\text{N·m}\)

This matches \(I_{\text{end}} \cdot \ddot{q} = \frac{ml^2}{3} \cdot 1 = 0.667\).

4.4.5 Pitfalls & Edge Cases

4.4.6 Variants & Generalizations

Variant Key Difference Use Case
RNEA (this) \(O(n)\) inverse dynamics, serial chain Real-time robot control, computed torque
RNEA + mass matrix Call RNEA \(n+1\) times to build \(M(q)\) Forward dynamics via \(\ddot{q} = M^{-1}(\tau - h)\)
Articulated Body Algorithm (ABA) \(O(n)\) forward dynamics directly Simulation without forming \(M\)
Extended RNEA Includes friction, motor inertia Realistic robot modeling
RNEA for trees Handles branching chains Humanoid robots, multi-fingered hands

4.4.7 Applications

4.4.8 Connections to Other Algorithms

5 Estimators

5.1 Linear Regression

5.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.

5.1.2 Mathematical Theory

5.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\).

5.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.

5.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}\).

5.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.

5.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\)

5.1.5 Pitfalls & Edge Cases

5.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

5.1.7 Applications

5.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 A single-layer neural network with no activation and MSE loss reduces to linear regression

5.2 Polynomial Least-Squares Fitting

5.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.

5.2.2 Mathematical Theory

5.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\]

5.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\).

5.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.

5.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.

5.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.

5.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\]

5.2.5 Pitfalls & Edge Cases

5.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

5.2.7 Applications

5.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

5.3 Yule-Walker Estimation

5.3.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.

5.3.2 Mathematical Theory

5.3.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\).

5.3.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}\]

5.3.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.

5.3.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.

5.3.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

5.3.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})\)

5.3.5 Pitfalls & Edge Cases

5.3.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

5.3.7 Applications

5.3.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

5.4 Expectation-Maximization for Kalman Filter Parameters

5.4.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.

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)\) and \(T\) observations \(z_0, \ldots, z_{T-1}\).

5.4.2.2 EM Overview

EM alternates between:

5.4.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)

5.4.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 \]

5.4.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.

5.4.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\).

5.4.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.

5.4.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.

5.4.5 Pitfalls & Edge Cases

5.4.6 Variants & Generalizations

5.4.7 Applications

5.4.8 Connections to Other Algorithms

5.5 Recursive Least Squares (RLS)

5.5.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.

5.5.2 Mathematical Theory

5.5.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.

5.5.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)\)

5.5.2.3 Forgetting Factor

5.5.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)

5.5.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.

5.5.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.

5.5.5 Pitfalls & Edge Cases

5.5.6 Variants & Generalizations

5.5.7 Applications

5.5.8 Connections to Other Algorithms

5.6 Estimator Consistency Metrics (NEES / NIS)

5.6.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.

5.6.2 Mathematical Theory

5.6.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.

5.6.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.

5.6.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.

5.6.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.

5.6.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.

5.6.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.

5.6.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.

5.6.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.

5.6.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.

5.6.7 Applications

5.6.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 linear solve reuses solvers::GaussianElimination, and the state-error representation aligns with estimators::EstimationMetrics.

5.7 LMS / NLMS Adaptive Filter

5.7.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.

5.7.2 Mathematical Theory

5.7.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.\]

5.7.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.\]

5.7.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\)).

5.7.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\).

5.7.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).

5.7.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\).

5.7.5 Pitfalls & Edge Cases

5.7.6 Variants & Generalizations

5.7.7 Applications

5.7.8 Connections to Other Algorithms

6 Filters

6.1 Kalman Filter

6.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.

6.1.2 Mathematical Theory

6.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\))

6.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}\]

6.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)}\]

6.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).

6.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.

6.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.

6.1.5 Pitfalls & Edge Cases

6.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

6.1.7 Applications

6.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

6.2 Extended Kalman Filter

6.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.

6.2.2 Mathematical Theory

6.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.

6.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}\]

6.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^-\]

6.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

6.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.

6.2.5 Pitfalls & Edge Cases

6.2.6 Variants & Generalizations

6.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

6.2.8 Applications

6.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

6.3 Unscented Kalman Filter

6.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.

6.3.2 Mathematical Theory

6.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\]

6.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\)).

6.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\]

6.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\]

6.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.

6.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.

6.3.5 Pitfalls & Edge Cases

6.3.6 Variants & Generalizations

6.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

6.3.8 Applications

6.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

6.4 Kalman Smoother (RTS Smoother)

6.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.

6.4.2 Mathematical Theory

6.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)\).

6.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 \]

6.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 \]

6.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.

6.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.

6.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.

6.4.5 Pitfalls & Edge Cases

6.4.6 Variants & Generalizations

6.4.7 Applications

6.4.8 Connections to Other Algorithms

6.5 Madgwick / Mahony AHRS Filter

6.5.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.

6.5.2 Mathematical Theory

6.5.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.

6.5.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.

6.5.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.

6.5.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.

6.5.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.

6.5.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.

6.5.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.

6.5.5 Pitfalls & Edge Cases

6.5.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

6.5.7 Applications

6.5.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

6.6 Alpha-Beta / Alpha-Beta-Gamma Filter

6.6.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.

6.6.2 Mathematical Theory

6.6.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\).

6.6.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}\]

6.6.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.

6.6.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.

6.6.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.

6.6.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.

6.6.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\).

6.6.5 Pitfalls & Edge Cases

6.6.6 Variants & Generalizations

6.6.7 Applications

6.6.8 Connections to Other Algorithms

6.7 Complementary Filter

6.7.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.

6.7.2 Mathematical Theory

6.7.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.

6.7.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).

6.7.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.

6.7.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}(x) = \bigl((x + \pi) \bmod 2\pi\bigr) - \pi\).

6.7.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.

6.7.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\).

6.7.5 Pitfalls & Edge Cases

6.7.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

6.7.7 Applications

6.7.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

6.8 Biquad / Second-Order-Section Cascade

6.8.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.

6.8.2 Mathematical Theory

6.8.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.

6.8.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.

6.8.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\).

6.8.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

6.8.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.

6.8.5 Pitfalls & Edge Cases

6.8.6 Variants & Generalizations

6.8.7 Applications

6.8.8 Connections to Other Algorithms

6.9 CIC (Cascaded Integrator-Comb) Filter

6.9.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.

6.9.2 Mathematical Theory

6.9.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.

6.9.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\]

6.9.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.

6.9.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.

6.9.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.

6.9.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\)):

6.9.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.

6.9.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.

6.9.7 Applications

6.9.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.

6.10 Exponential Moving Average

6.10.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.

6.10.2 Mathematical Theory

6.10.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).

6.10.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.

6.10.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.

6.10.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.

6.10.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}\]

6.10.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.

6.10.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

6.10.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}\).

6.10.5 Pitfalls & Edge Cases

6.10.6 Variants & Generalizations

6.10.7 Applications

6.10.8 Connections to Other Algorithms

6.11 Finite Impulse Response (FIR) Filter

6.11.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.

6.11.2 Mathematical Theory

6.11.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)

6.11.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.

6.11.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}\]

6.11.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.

6.11.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

6.11.5 Pitfalls & Edge Cases

6.11.6 Variants & Generalizations

6.11.7 Applications

6.11.8 Connections to Other Algorithms

6.12 Infinite Impulse Response (IIR) Filter

6.12.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.

6.12.2 Mathematical Theory

6.12.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]\)).

6.12.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}}\]

6.12.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.

6.12.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.

6.12.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\).

6.12.5 Pitfalls & Edge Cases

6.12.6 Variants & Generalizations

6.12.7 Applications

6.12.8 Connections to Other Algorithms

6.13 Median Filter (Sliding Window)

6.13.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.

6.13.2 Mathematical Theory

6.13.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.

6.13.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\).

6.13.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.

6.13.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.

6.13.3 Algorithm

6.13.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]\).

6.13.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.

6.13.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.

6.13.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.

6.13.6 Pitfalls & Edge Cases

6.13.7 Variants & Generalizations

6.13.8 Applications

6.13.9 Connections to Other Algorithms

6.14 Moving Average (Running-Sum Boxcar)

6.14.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.

6.14.2 Mathematical Theory

6.14.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]\]

6.14.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}\]

6.14.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.

6.14.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.

6.14.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.

6.14.5 Pitfalls & Edge Cases

6.14.6 Variants & Generalizations

6.14.7 Applications

6.14.8 Connections to Other Algorithms

6.15 Notch / Comb Filter

6.15.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.

6.15.2 Mathematical Theory

6.15.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]\]

6.15.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\).

6.15.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

6.15.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.

6.15.5 Pitfalls & Edge Cases

6.15.6 Variants & Generalizations

6.15.7 Applications

6.15.8 Connections to Other Algorithms

6.16 Savitzky-Golay Filter

6.16.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.

6.16.2 Mathematical Theory

6.16.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\]

6.16.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.

6.16.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.

6.16.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.

6.16.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.

6.16.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.

6.16.5 Pitfalls & Edge Cases

6.16.6 Variants & Generalizations

6.16.7 Applications

6.16.8 Connections to Other Algorithms

7 Kinematics

7.1 Forward Kinematics

7.1.1 Overview & Motivation

Forward Kinematics computes the 3D Cartesian positions of all joints in a serial kinematic chain, given the joint angles. For a chain of \(n\) revolute joints, it produces \(n + 1\) position vectors (base through end-effector) in \(O(n)\) time.

This is the geometric foundation for visualization, collision detection, and workspace analysis. It answers the question: “given these joint angles, where is each joint in world space?”

7.1.2 Mathematical Theory

7.1.2.1 Position Computation

Starting from the base at the origin, the position of joint \(i + 1\) is:

\[p_{i+1} = p_i + R_{\text{world} \to i} \cdot r_{i \to i+1}\]

where:

Each rotation \(R_k(q_k)\) is computed via Rodrigues’ formula from the joint axis and angle.

7.1.2.2 End-Effector Approximation

For the last link, the offset to the end-effector is approximated as \(2 \cdot r_{j \to \text{CoM}}\) (assuming the center of mass is at the midpoint of the link).

7.1.3 Complexity Analysis

Case Time Space Notes
All \(O(n)\) \(O(n)\) Single pass over chain

7.1.4 Variants & Generalizations

7.1.5 Applications

Consider a 2-link planar arm with link lengths \(L_1 = 1\), \(L_2 = 0.8\), Y-axis joints, and \(q = [\pi/4, -\pi/6]\).

  1. \(p_0 = [0, 0, 0]^T\) (base at origin)
  2. \(R_0 = R_y(\pi/4)\), link extent \(= [1, 0, 0]^T\), so \(p_1 = R_0 \cdot [1, 0, 0]^T = [0.707, 0, 0.707]^T\)
  3. \(R_{01} = R_0 \cdot R_y(-\pi/6)\), link extent \(= [0.8, 0, 0]^T\), so \(p_2 = p_1 + R_{01} \cdot [0.8, 0, 0]^T\)

7.1.6 Pitfalls & Edge Cases

7.1.7 Connections to Other Algorithms

7.2 Inverse Kinematics (Damped Least Squares)

7.2.1 Overview & Motivation

Inverse Kinematics (IK) solves the problem dual to Forward Kinematics: given a desired 3D end-effector position \(p_{\text{target}}\), find joint angles \(q\) such that the end-effector reaches that position. This is the core computational problem in robot arm control, motion planning, and teleoperation.

The Damped Least Squares (DLS) method, also known as the Levenberg-Marquardt approach, iteratively refines a joint-angle estimate by solving a small linear system at each step. Unlike the plain pseudoinverse, DLS adds a damping term \(\lambda^2 I\) that keeps the system numerically stable even when the manipulator is near a singular configuration (such as a fully extended arm).

7.2.2 Mathematical Theory

7.2.2.1 Jacobian of a Serial Chain

For a serial revolute-joint chain with \(n\) joints, the geometric Jacobian \(J \in \mathbb{R}^{3 \times n}\) maps joint velocities \(\dot{q}\) to end-effector linear velocity:

\[\dot{p}_{\text{ee}} = J(q) \, \dot{q}\]

Column \(i\) of \(J\) is the linear velocity contribution of joint \(i\):

\[J_i = a_i^w \times (p_{\text{ee}} - p_i)\]

where: - \(a_i^w = R_{0 \to i} \, a_i^{\text{link}}\) is the joint axis in world frame (accumulated rotation applied to the link-frame axis) - \(p_i\) is the world-frame position of joint \(i\) (from Forward Kinematics) - \(p_{\text{ee}}\) is the current end-effector position

7.2.2.2 DLS Update Rule

Define the position error:

\[e = p_{\text{target}} - p_{\text{ee}}(q) \in \mathbb{R}^3\]

The DLS joint update per iteration is:

\[\Delta q = J^\top \bigl(J J^\top + \lambda^2 I\bigr)^{-1} e\]

This is equivalent to solving the \(3 \times 3\) linear system:

\[\underbrace{\bigl(J J^\top + \lambda^2 I\bigr)}_{A} \, y = e, \quad \Delta q = J^\top y\]

where \(A \in \mathbb{R}^{3 \times 3}\) is always symmetric positive definite (the damping \(\lambda^2 I\) guarantees this), so GaussianElimination<T, 3> solves it reliably.

The updated joint angles are:

\[q_{k+1} = q_k + \Delta q\]

7.2.2.3 Convergence

The loop terminates when: - \(\|e\| < \epsilon_{\text{tol}}\) (success), or - The maximum iteration count is reached (failure to converge — target unreachable or tolerance too tight)

7.2.2.4 Why DLS over Plain Pseudoinverse

At a kinematic singularity (e.g., fully extended arm), \(J J^\top\) becomes rank-deficient. The pseudoinverse \(J^+ = J^\top (J J^\top)^{-1}\) would produce infinite joint velocities. The damping term \(\lambda^2 I\) bounds the solution:

\[\|\Delta q\| \leq \frac{\|e\|}{\lambda}\]

The cost is a small positional bias proportional to \(\lambda\): the algorithm converges to within approximately \(\lambda^2 \|e\|\) rather than exact zero. Choosing \(\lambda \in [0.01, 0.2]\) balances stability and accuracy for typical robot geometries.

7.2.3 Complexity Analysis

Case Time Space Notes
Per iteration \(O(n)\) \(O(n)\) FK sweep + Jacobian + \(3\times3\) solve
Total \(O(k \cdot n)\) \(O(n)\) \(k\) = iterations (typically 10–50)

The \(3 \times 3\) linear solve is \(O(1)\) (constant-size), independent of the number of joints \(n\).

7.2.4 Step-by-Step Walkthrough

Consider a 2-link planar arm with \(l_1 = 1.0\), \(l_2 = 0.8\), both joints rotating about the Z-axis, target \(p_{\text{target}} = (0.5, 1.0, 0)\), initial guess \(q = (0, 0)\), \(\lambda = 0.1\).

Iteration 1:

  1. FK gives \(p_{\text{ee}} = (1.8, 0, 0)\) (fully extended along X)
  2. Error: \(e = (-1.3, 1.0, 0)\), \(\|e\| \approx 1.64\)
  3. Jacobian (Z-axis joints, cross-product form):
  4. \(A = J J^\top + 0.01 I\) — a \(3 \times 3\) SPD matrix
  5. Solve \(Ay = e\), compute \(\Delta q = J^\top y\)
  6. Update both joint angles toward a bent configuration

After ~40 iterations \(q\) converges to joints that produce \(p_{\text{ee}} \approx (0.5, 1.0, 0)\).

7.2.5 Pitfalls & Edge Cases

7.2.6 Variants & Generalizations

7.2.7 Applications

7.2.8 Connections to Other Algorithms

8 Neural Network

8.1 Neural Networks

8.1.1 Overview & Motivation

A neural network is a parameterized function \(f_\theta: \mathbb{R}^n \to \mathbb{R}^m\) built by composing simple, differentiable transformations called layers. Each layer applies an affine map followed by a non-linear activation, and the whole composition is trained end-to-end by gradient-based optimization.

Neural networks are powerful because of the universal approximation theorem: a single hidden layer with enough neurons can approximate any continuous function on a compact set to arbitrary accuracy. In practice, depth (many layers) is more parameter-efficient than width for learning hierarchical features.

This library provides a minimal, statically-sized neural network framework designed for embedded inference and on-device training — no heap allocation, no dynamic shapes, full compile-time dimension checking.

8.1.2 Mathematical Theory

8.1.2.1 Forward Propagation

Given \(L\) layers, the network computes:

\[a_0 = x\] \[z_\ell = W_\ell \, a_{\ell-1} + b_\ell, \quad \ell = 1, \ldots, L\] \[a_\ell = f_\ell(z_\ell)\] \[\hat{y} = a_L\]

where \(W_\ell \in \mathbb{R}^{n_\ell \times n_{\ell-1}}\) are weights, \(b_\ell \in \mathbb{R}^{n_\ell}\) are biases, and \(f_\ell\) is the activation function for layer \(\ell\).

8.1.2.2 Loss Function

Training minimizes a scalar loss \(\mathcal{L}(\hat{y}, y)\) that measures how far the prediction \(\hat{y}\) is from the target \(y\). Common choices:

Loss Formula Use Case
MSE \(\frac{1}{m}\sum(\hat{y}_i - y_i)^2\) Regression
BCE \(-\sum[y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i)]\) Binary classification
CCE \(-\sum y_i \log \hat{y}_i\) Multi-class classification

8.1.2.3 Backpropagation

Backpropagation efficiently computes \(\nabla_\theta \mathcal{L}\) via the chain rule, working from the output layer backward:

\[\delta_L = \nabla_{a_L}\mathcal{L} \odot f_L'(z_L)\] \[\delta_\ell = (W_{\ell+1}^T \delta_{\ell+1}) \odot f_\ell'(z_\ell)\]

The gradients with respect to parameters are:

\[\frac{\partial \mathcal{L}}{\partial W_\ell} = \delta_\ell \, a_{\ell-1}^T, \qquad \frac{\partial \mathcal{L}}{\partial b_\ell} = \delta_\ell\]

8.1.2.4 Parameter Update

An optimizer uses the gradients to update the parameter vector \(\theta\):

\[\theta_{t+1} = \theta_t - \eta \, \nabla_\theta \mathcal{L}\]

where \(\eta\) is the learning rate. More sophisticated optimizers (momentum, Adam) modify this basic rule.

8.1.3 Complexity Analysis

Phase Time Space
Forward pass \(O\!\left(\sum_{\ell=1}^L n_\ell \cdot n_{\ell-1}\right)\) \(O\!\left(\sum n_\ell\right)\) activations
Backward pass Same as forward Same + gradient storage
Parameter update \(O(P)\) \(O(P)\) optimizer state

where \(P = \sum_\ell (n_\ell \cdot n_{\ell-1} + n_\ell)\) is the total parameter count. For small embedded networks (\(P < 10{,}000\)), both passes complete in microseconds.

8.1.4 Step-by-Step Walkthrough

Network: 2 inputs → 2 hidden (ReLU) → 1 output (Sigmoid). Learning XOR.

Architecture:

graph LR
    x1((x₁)) --> h1((h₁))
    x1 --> h2((h₂))
    x2((x₂)) --> h1
    x2 --> h2
    h1 --> y((ŷ))
    h2 --> y

Epoch 0 — Forward pass with input \(x = [1, 0]^T\), target \(y = 1\):

Step Computation Result
Hidden pre-activation \(z_1 = W_1 x + b_1\) \([0.3, -0.1]^T\)
Hidden activation \(a_1 = \text{ReLU}(z_1)\) \([0.3, 0.0]^T\)
Output pre-activation \(z_2 = W_2 a_1 + b_2\) \([0.15]\)
Output activation \(\hat{y} = \sigma(z_2)\) \([0.537]\)
Loss \(\mathcal{L} = -(y\log\hat{y} + (1-y)\log(1-\hat{y}))\) \(0.621\)

Epoch 0 — Backward pass:

Step Computation Result
Output gradient \(\delta_2 = \hat{y} - y\) \([-0.463]\)
\(\nabla W_2\) \(\delta_2 \cdot a_1^T\) \([-0.139, 0]\)
Hidden gradient \(\delta_1 = W_2^T \delta_2 \odot \text{ReLU}'(z_1)\) \([-0.231, 0]^T\)
\(\nabla W_1\) \(\delta_1 \cdot x^T\) \([[-0.231, 0], [0, 0]]\)

Update: \(W \leftarrow W - 0.1 \cdot \nabla W\). After ~500 epochs, the network correctly classifies all four XOR inputs.

8.1.5 Pitfalls & Edge Cases

8.1.6 Variants & Generalizations

Variant Key Difference
Convolutional Neural Network (CNN) Layers share weights spatially; efficient for image/signal data
Recurrent Neural Network (RNN) Layers share weights across time steps; models sequences
Residual Network (ResNet) Skip connections mitigate vanishing gradients in very deep networks
Transformer Attention-based; no recurrence; state-of-the-art for sequences
Quantized Neural Network Weights and activations in low-bit integers; optimal for MCU deployment

8.1.7 Applications

8.1.8 Connections to Other Algorithms

graph TD
    NN["Neural Network"]
    Layer["Dense Layer"]
    Act["Activation Functions"]
    Loss["Loss Functions"]
    Opt["Optimizer"]
    Reg["Regularization"]
    Model["Model"]
    LR["Linear Regression"]

    Layer --> NN
    Act --> NN
    Loss --> NN
    Opt --> NN
    Reg --> NN
    NN --> Model
    NN -.->|"single-layer, linear activation, MSE loss"| LR
Component Relationship
Dense Layer The fundamental building block; computes affine transformations
Activation Functions Introduce non-linearity after each layer
Loss Functions Define the training objective
Optimizer Drives parameter updates via gradient descent
Regularization Penalizes complexity to prevent overfitting
Model Composes layers into a trainable pipeline
Linear Regression Special case: single layer, identity activation, MSE loss

8.2 Dense Layer

8.2.1 Overview & Motivation

A dense (fully-connected) layer is the most fundamental building block of a neural network. It maps an input vector \(a_{\text{in}} \in \mathbb{R}^n\) to an output vector \(a_{\text{out}} \in \mathbb{R}^m\) through a learnable affine transformation followed by a non-linear activation:

\[a_{\text{out}} = f(W \, a_{\text{in}} + b)\]

Every input neuron is connected to every output neuron — hence “fully connected.” The layer’s parameters are the weight matrix \(W\) and bias vector \(b\); training adjusts these to minimize the loss.

In this library, input size, output size, and parameter count are all compile-time constants, enabling stack allocation and dimension checking with zero runtime overhead.

8.2.2 Mathematical Theory

8.2.2.1 Forward Pass

\[z = W \, a_{\text{in}} + b, \qquad a_{\text{out}} = f(z)\]

where \(W \in \mathbb{R}^{m \times n}\), \(b \in \mathbb{R}^m\), and \(f\) is the activation function.

8.2.2.2 Backward Pass

Given the gradient of the loss with respect to the output \(\frac{\partial \mathcal{L}}{\partial a_{\text{out}}}\):

  1. Pre-activation gradient: \[\delta = \frac{\partial \mathcal{L}}{\partial a_{\text{out}}} \odot f'(z)\]

  2. Weight gradient: \[\frac{\partial \mathcal{L}}{\partial W} = \delta \, a_{\text{in}}^T\]

  3. Bias gradient: \[\frac{\partial \mathcal{L}}{\partial b} = \delta\]

  4. Input gradient (propagated to the previous layer): \[\frac{\partial \mathcal{L}}{\partial a_{\text{in}}} = W^T \delta\]

8.2.2.3 Parameter Count

\[P = m \times n + m = m(n + 1)\]

For a layer with 128 inputs and 64 outputs: \(P = 64 \times 129 = 8{,}256\) parameters.

8.2.3 Complexity Analysis

Operation Time Space
Forward (\(W a + b\)) \(O(m \cdot n)\) \(O(m)\) output + \(O(m)\) cached \(z\)
Backward (\(\delta\), \(\nabla W\), \(\nabla b\)) \(O(m \cdot n)\) \(O(m \cdot n)\) weight gradient
Total parameters \(O(m \cdot n + m)\)

The matrix-vector product dominates both passes. For embedded networks (e.g. \(n = 32, m = 16\)), a single forward pass takes ~512 multiply-accumulate operations.

8.2.4 Step-by-Step Walkthrough

Layer: 3 inputs → 2 outputs, ReLU activation.

\[W = \begin{bmatrix} 0.5 & -0.3 & 0.8 \\ 0.1 & 0.7 & -0.2 \end{bmatrix}, \quad b = \begin{bmatrix} 0.1 \\ -0.1 \end{bmatrix}, \quad a_{\text{in}} = \begin{bmatrix} 1.0 \\ 0.5 \\ -1.0 \end{bmatrix}\]

Forward:

Step Computation Result
\(z = W a_{\text{in}} + b\) \([0.5 - 0.15 - 0.8 + 0.1,\; 0.1 + 0.35 + 0.2 - 0.1]\) \([-0.35,\; 0.55]^T\)
\(a_{\text{out}} = \text{ReLU}(z)\) \([\max(0, -0.35),\; \max(0, 0.55)]\) \([0.0,\; 0.55]^T\)

Backward with \(\frac{\partial \mathcal{L}}{\partial a_{\text{out}}} = [0.2,\; -0.4]^T\):

Step Computation Result
\(\delta = \nabla a_{\text{out}} \odot \text{ReLU}'(z)\) \([0.2 \cdot 0,\; -0.4 \cdot 1]\) \([0,\; -0.4]^T\)
\(\nabla W = \delta \, a_{\text{in}}^T\) row 1: all zeros; row 2: \(-0.4 \times [1, 0.5, -1]\) \(\begin{bmatrix}0 & 0 & 0\\-0.4 & -0.2 & 0.4\end{bmatrix}\)
\(\nabla b = \delta\) \([0,\; -0.4]^T\)
\(\nabla a_{\text{in}} = W^T \delta\) \(W^T [0, -0.4]^T\) \([-0.04,\; -0.28,\; 0.08]^T\)

8.2.5 Pitfalls & Edge Cases

8.2.6 Variants & Generalizations

Variant Key Difference
Convolutional layer Weight sharing across spatial positions; \(O(k^2 \cdot c)\) parameters per filter instead of \(O(n \cdot m)\)
Recurrent layer Shares weights across time steps; adds a hidden state feedback connection
Batch normalization layer Normalizes activations to zero mean and unit variance; accelerates training
Dropout layer Randomly zeros activations during training; regularization effect
Sparse layer Only a subset of connections exist; reduces parameter count and computation

8.2.7 Applications

8.2.8 Connections to Other Algorithms

graph TD
    Layer["Dense Layer"]
    Act["Activation Functions"]
    Model["Model"]
    Opt["Optimizer"]
    LR["Linear Regression"]

    Act --> Layer
    Layer --> Model
    Model --> Opt
    Layer -.->|"no activation, MSE loss"| LR
Component Relationship
Activation Functions Applied element-wise after the affine transformation
Model Chains multiple dense layers into a network
Optimizer Updates \(W\) and \(b\) using the computed gradients
Linear Regression A dense layer with identity activation and MSE loss is equivalent to linear regression

8.3 Activation Functions

8.3.1 Overview & Motivation

An activation function \(f\) is a non-linear, element-wise transformation applied after the affine map in each neural network layer:

\[a = f(z) = f(W x + b)\]

Without activation functions, stacking layers would collapse into a single affine transformation — the network could only represent linear mappings regardless of depth. Activation functions are what give neural networks their expressive power.

The choice of activation function controls gradient flow during back-propagation, output range, and computational cost — all critical on resource-constrained embedded targets.

8.3.2 Mathematical Theory

8.3.2.1 Forward and Backward

Every activation function exposes two operations:

Operation Definition Purpose
Forward \(a = f(z)\) Transform the pre-activation
Backward \(f'(z)\) Provide the local derivative for back-propagation

The chain rule connects them during training:

\[\frac{\partial \mathcal{L}}{\partial z} = \frac{\partial \mathcal{L}}{\partial a} \cdot f'(z)\]

8.3.2.2 Catalogue

8.3.2.2.1 ReLU (Rectified Linear Unit)

\[f(z) = \max(0, z), \qquad f'(z) = \begin{cases} 1 & z > 0 \\ 0 & z \le 0 \end{cases}\]

8.3.2.2.2 Leaky ReLU

\[f(z) = \begin{cases} z & z > 0 \\ \alpha z & z \le 0 \end{cases}, \qquad f'(z) = \begin{cases} 1 & z > 0 \\ \alpha & z \le 0 \end{cases}\]

where \(\alpha\) is a small positive constant (typically \(0.01\)). Prevents dead neurons by allowing a small gradient for \(z < 0\).

8.3.2.2.3 Sigmoid

\[f(z) = \frac{1}{1 + e^{-z}}, \qquad f'(z) = f(z)(1 - f(z))\]

8.3.2.2.4 Tanh (Hyperbolic Tangent)

\[f(z) = \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}}, \qquad f'(z) = 1 - f(z)^2\]

8.3.2.2.5 Softmax

For a vector \(\mathbf{z} \in \mathbb{R}^k\):

\[f(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{k} e^{z_j}}\]

8.3.3 Complexity Analysis

Activation Forward (per element) Backward (per element) Notes
ReLU \(O(1)\) — comparison \(O(1)\) — comparison Fastest
Leaky ReLU \(O(1)\) — comparison + multiply \(O(1)\) Negligible overhead vs ReLU
Sigmoid \(O(1)\) — exp + divide \(O(1)\) — reuse forward result Requires exp()
Tanh \(O(1)\) — exp (twice) \(O(1)\) — reuse forward result Requires exp()
Softmax \(O(k)\) — vector exp + sum \(O(k^2)\) — full Jacobian Significantly more expensive

8.3.4 Step-by-Step Walkthrough

Scenario: Forward and backward pass through a 3-neuron hidden layer with ReLU, given pre-activations \(z = [-0.5, \; 1.2, \; 0.0]\).

Forward:

Neuron \(z\) \(\text{ReLU}(z)\)
1 \(-0.5\) \(0.0\)
2 \(1.2\) \(1.2\)
3 \(0.0\) \(0.0\)

Backward with incoming gradient \(\frac{\partial \mathcal{L}}{\partial a} = [0.3, \; -0.7, \; 0.1]\):

Neuron \(f'(z)\) \(\frac{\partial \mathcal{L}}{\partial z}\)
1 \(0\) (dead) \(0.3 \times 0 = 0\)
2 \(1\) \(-0.7 \times 1 = -0.7\)
3 \(0\) (at boundary) \(0.1 \times 0 = 0\)

Neuron 1 is dead — its gradient is zero and its weights will not update. If this persists across all training samples, the neuron is permanently inactive.

8.3.5 Pitfalls & Edge Cases

8.3.6 Variants & Generalizations

Variant Key Difference
PReLU (Parametric ReLU) \(\alpha\) is a learnable parameter per channel
ELU (Exponential LU) \(\alpha(e^z - 1)\) for \(z < 0\); smooth and zero-centered
GELU (Gaussian Error LU) \(z \cdot \Phi(z)\); used in Transformers
Swish / SiLU \(z \cdot \sigma(z)\); smooth, non-monotonic
Hard Sigmoid / Hard Tanh Piece-wise linear approximations; no transcendentals

8.3.7 Applications

8.3.8 Connections to Other Algorithms

graph TD
    Act["Activation Functions"]
    Layer["Dense Layer"]
    NN["Neural Network"]
    Loss["Loss Functions"]
    Act --> Layer
    Layer --> NN
    NN --> Loss
Component Relationship
Dense Layer Applies the activation function after the affine transformation
Neural Network Activations enable the non-linear function approximation that makes deep networks useful
Loss Functions The output activation must match the loss: Sigmoid + BCE, Softmax + CCE

8.4 Loss Functions

8.4.1 Overview & Motivation

A loss function \(\mathcal{L}(\hat{y}, y)\) quantifies how far a model’s prediction \(\hat{y}\) is from the true target \(y\). Training a neural network means finding parameters \(\theta\) that minimize the expected loss over the training data:

\[\theta^* = \arg\min_\theta \; \mathbb{E}[\mathcal{L}(f_\theta(x), y)]\]

The loss function defines the entire learning objective — different losses lead to different optimal models even on the same data. It must also provide a gradient \(\nabla_{\hat{y}} \mathcal{L}\) for back-propagation.

8.4.2 Mathematical Theory

8.4.2.1 Mean Squared Error (MSE)

\[\mathcal{L}_{\text{MSE}} = \frac{1}{m} \sum_{i=1}^{m} (\hat{y}_i - y_i)^2\]

\[\frac{\partial \mathcal{L}}{\partial \hat{y}_i} = \frac{2}{m}(\hat{y}_i - y_i)\]

8.4.2.2 Mean Absolute Error (MAE)

\[\mathcal{L}_{\text{MAE}} = \frac{1}{m} \sum_{i=1}^{m} |\hat{y}_i - y_i|\]

\[\frac{\partial \mathcal{L}}{\partial \hat{y}_i} = \frac{1}{m} \mathrm{sign}(\hat{y}_i - y_i)\]

8.4.2.3 Binary Cross-Entropy (BCE)

\[\mathcal{L}_{\text{BCE}} = -\frac{1}{m}\sum_{i=1}^{m} \left[ y_i \log \hat{y}_i + (1 - y_i) \log(1 - \hat{y}_i) \right]\]

\[\frac{\partial \mathcal{L}}{\partial \hat{y}_i} = -\frac{1}{m}\left(\frac{y_i}{\hat{y}_i} - \frac{1 - y_i}{1 - \hat{y}_i}\right)\]

8.4.2.4 Categorical Cross-Entropy (CCE)

\[\mathcal{L}_{\text{CCE}} = -\sum_{i=1}^{k} y_i \log \hat{y}_i\]

\[\frac{\partial \mathcal{L}}{\partial \hat{y}_i} = -\frac{y_i}{\hat{y}_i}\]

8.4.3 Complexity Analysis

Loss Forward Backward Notes
MSE \(O(m)\) \(O(m)\) Cheapest; no transcendentals
MAE \(O(m)\) \(O(m)\) Requires sign()
BCE \(O(m)\) \(O(m)\) Requires log()
CCE \(O(k)\) \(O(k)\) Requires log()

All losses are \(O(m)\) where \(m\) is the output dimension. The computational cost is negligible compared to the dense layer matrix products.

8.4.4 Step-by-Step Walkthrough

Scenario: 3-class classification. Target \(y = [0, 1, 0]\) (class 2). Softmax output \(\hat{y} = [0.1, 0.7, 0.2]\).

CCE Forward:

\[\mathcal{L} = -(0 \cdot \log 0.1 + 1 \cdot \log 0.7 + 0 \cdot \log 0.2) = -\log(0.7) \approx 0.357\]

CCE Backward:

\(i\) \(y_i\) \(\hat{y}_i\) \(\partial \mathcal{L}/\partial \hat{y}_i = -y_i / \hat{y}_i\)
1 0 0.1 \(0\)
2 1 0.7 \(-1.429\)
3 0 0.2 \(0\)

The gradient is non-zero only for the true class, and its magnitude \(1/\hat{y}_2\) grows as the prediction worsens — providing a strong corrective signal.

For comparison — MSE on the same example:

\[\mathcal{L}_{\text{MSE}} = \frac{1}{3}[(0.1)^2 + (0.7-1)^2 + (0.2)^2] = \frac{1}{3}[0.01 + 0.09 + 0.04] = 0.047\]

MSE gives a much weaker signal and does not account for the probabilistic nature of the output.

8.4.5 Pitfalls & Edge Cases

8.4.6 Variants & Generalizations

Variant Key Difference
Huber loss Quadratic for small errors, linear for large; robust regression
Focal loss Down-weights well-classified examples; addresses class imbalance
KL divergence Measures distance between two distributions; used in variational inference
Hinge loss Margin-based; used in SVMs and some neural classifiers
Contrastive loss Learns similarity metrics; used in Siamese networks

8.4.7 Applications

8.4.8 Connections to Other Algorithms

graph TD
    Loss["Loss Functions"]
    Act["Activation Functions"]
    Opt["Optimizer"]
    Reg["Regularization"]
    Model["Model"]
    LR["Linear Regression"]

    Act -->|"output activation must match loss"| Loss
    Loss --> Opt
    Reg -->|"added to loss"| Loss
    Loss --> Model
    Loss -.->|"MSE + normal equation"| LR
Component Relationship
Activation Functions Output activation must match: Sigmoid ↔︎ BCE, Softmax ↔︎ CCE, identity ↔︎ MSE
Optimizer Uses \(\nabla \mathcal{L}\) to update parameters
Regularization Adds a penalty term to the loss: \(\mathcal{L}_{\text{total}} = \mathcal{L} + \lambda \Omega(\theta)\)
Linear Regression Solved analytically when the loss is MSE and the model is linear

8.5 Model (Neural Network Composition)

8.5.1 Overview & Motivation

A Model composes a sequence of dense layers into a single trainable function \(f: \mathbb{R}^n \to \mathbb{R}^m\). It is the orchestrator that:

  1. Chains layers so the output of each feeds into the next (forward pass).
  2. Propagates gradients backward through the chain (backward pass).
  3. Flattens all layer parameters into a single vector for the optimizer.
  4. Verifies dimensional compatibility at compile time using variadic templates.

In this library, the Model is fully statically typed — layer dimensions, parameter counts, and memory footprints are all known at compile time, enabling zero-overhead abstraction on embedded targets.

8.5.2 Mathematical Theory

8.5.2.1 Composition

For \(L\) layers with transformations \(f_1, f_2, \ldots, f_L\):

\[\hat{y} = (f_L \circ f_{L-1} \circ \cdots \circ f_1)(x) = f_L(f_{L-1}(\ldots f_1(x) \ldots))\]

Each \(f_\ell\) is a dense layer: \(f_\ell(a) = \sigma_\ell(W_\ell a + b_\ell)\).

8.5.2.2 Parameter Vector

All weights and biases are concatenated into a single vector:

\[\theta = [\text{vec}(W_1), b_1, \text{vec}(W_2), b_2, \ldots, \text{vec}(W_L), b_L] \in \mathbb{R}^P\]

where \(P = \sum_{\ell=1}^L m_\ell(n_\ell + 1)\).

8.5.2.3 Forward Pass (Chained Evaluation)

graph LR
    X["x ∈ ℝⁿ"] --> L1["Layer 1"] --> L2["Layer 2"] --> Ldots["⋯"] --> LL["Layer L"] --> Y["ŷ ∈ ℝᵐ"]

8.5.2.4 Backward Pass (Reverse Chain Rule)

\[\frac{\partial \mathcal{L}}{\partial \theta_\ell} = \frac{\partial \mathcal{L}}{\partial a_L} \cdot \frac{\partial a_L}{\partial a_{L-1}} \cdots \frac{\partial a_{\ell+1}}{\partial a_\ell} \cdot \frac{\partial a_\ell}{\partial \theta_\ell}\]

Each layer stores its input \(a_{\ell-1}\) during the forward pass so it can compute \(\nabla W_\ell\) and \(\nabla b_\ell\) during the backward pass.

8.5.2.5 Training Loop

graph TD
    FP["Forward pass: ŷ = Model(x)"]
    LC["Loss: ℒ(ŷ, y)"]
    BP["Backward pass: ∇θ ℒ"]
    UP["Update: θ ← θ − η ∇θ ℒ"]
    FP --> LC --> BP --> UP --> FP

8.5.3 Complexity Analysis

Operation Time Space
Forward pass \(O(P)\) \(O(\sum n_\ell)\) cached activations
Backward pass \(O(P)\) \(O(P)\) gradients
GetParameters() \(O(P)\) \(O(P)\) flat vector
SetParameters() \(O(P)\)

All operations scale linearly with the total parameter count \(P\).

8.5.4 Step-by-Step Walkthrough

Model: 2 → 3 → 1 (two layers).

Compile-time verification chain:

Check Condition Status
Layer 1 input size = Model input size \(2 = 2\)
Layer 1 output size = Layer 2 input size \(3 = 3\)
Layer 2 output size = Model output size \(1 = 1\)
All types derive from Layer type trait check

Parameter layout (\(P = 3(2 + 1) + 1(3 + 1) = 9 + 4 = 13\)):

Index Parameter
0–5 \(W_1\) (3×2 = 6 elements)
6–8 \(b_1\) (3 elements)
9–11 \(W_2\) (1×3 = 3 elements)
12 \(b_2\) (1 element)

Forward pass with \(x = [1.0, 0.5]^T\):

  1. Layer 1: \(a_1 = \text{ReLU}(W_1 x + b_1) = [0.0, 0.8, 0.3]^T\)
  2. Layer 2: \(\hat{y} = \sigma(W_2 a_1 + b_2) = [0.62]\)

Backward pass with loss gradient \(\delta_{\text{out}} = [0.12]\):

  1. Layer 2 backward → produces \(\nabla W_2\), \(\nabla b_2\), and \(\delta_1 = W_2^T \delta_2 \odot \text{ReLU}'(z_1)\)
  2. Layer 1 backward → produces \(\nabla W_1\), \(\nabla b_1\)

Optimizer receives the full \(\nabla \theta \in \mathbb{R}^{13}\) and updates \(\theta\).

8.5.5 Pitfalls & Edge Cases

8.5.6 Variants & Generalizations

Variant Key Difference
Sequential model (dynamic) Layers stored in a container; dimension checked at runtime instead of compile time
Functional API Supports branching and merging (DAG topology instead of linear chain)
Residual model Adds skip connections: \(a_{\ell+2} = f_{\ell+1}(a_\ell) + a_\ell\)
Recurrent model Unrolls the same layer across time steps

8.5.7 Applications

8.5.8 Connections to Other Algorithms

graph TD
    Model["Model"]
    Layer["Dense Layer"]
    Loss["Loss Functions"]
    Opt["Optimizer"]
    Reg["Regularization"]
    NN["Neural Network"]

    Layer --> Model
    Model --> Opt
    Model --> Loss
    Reg --> Loss
    Model --> NN
Component Relationship
Dense Layer The Model is a sequence of layers stored in a std::tuple
Loss Functions Measures prediction error; the Model delegates loss computation to a Loss object
Optimizer Receives the flat parameter/gradient vectors from the Model and returns updated parameters
Regularization Added to the loss before optimization

9 Optimization

9.1 Bayesian Optimization

9.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.

9.1.2 Mathematical Theory

9.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

9.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.

9.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.

9.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.

9.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\).

9.1.5 Pitfalls & Edge Cases

9.1.6 Variants & Generalizations

9.1.7 Applications

9.1.8 Connections to Other Algorithms

9.1.9 Numerical Properties

9.2 Gradient Descent Optimizer

9.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.

9.2.2 Mathematical Theory

9.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.

9.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.

9.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

9.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.

9.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"]

9.2.5 Pitfalls & Edge Cases

9.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

9.2.7 Applications

9.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

10 Regularization

10.1 Regularization

10.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.

10.1.2 Mathematical Theory

10.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.

10.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.

10.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

10.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.

10.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.

10.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.

10.1.5 Pitfalls & Edge Cases

10.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

10.1.7 Applications

10.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

11 Math

11.1 CORDIC

11.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.

11.1.2 Mathematical Theory

11.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.

11.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\).

11.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\).

11.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\).

11.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.

11.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.

11.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.

11.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.

11.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.

11.1.7 Applications

11.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.

11.2 Quaternion

11.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.

11.2.2 Mathematical Theory

11.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).\]

11.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.

11.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.

11.2.2.4 Conjugate and Inverse

For any quaternion \(q^* = (w, -x, -y, -z)\). For a unit quaternion \(q^{-1} = q^*\).

11.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}.\]

11.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.

11.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.

11.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.

11.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.

11.2.5 Pitfalls & Edge Cases

11.2.6 Variants & Generalizations

11.2.7 Applications

11.2.8 Connections to Other Algorithms

11.3 Matrix & Vector Norms

11.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.

11.3.2 Mathematical Theory

11.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.

11.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\).

11.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

11.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\).

11.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#pragma GCC optimize("fast-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.

11.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.

11.3.7 Applications

11.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.

11.4 Householder Transform

11.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.

11.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.

11.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

11.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.

11.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-onlystatic_assert(std::is_floating_point_v<T>); the sign-fixing and normalisation assume real floating-point arithmetic.

11.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.

11.4.7 Applications

11.4.8 Connections to Other Algorithms

Used by solvers::QrDecomposition; complements math::GivensRotation and math::SolveUpperTriangular. Operates on math::Vector.

11.5 Givens Rotation

11.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.

11.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.

11.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

11.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.

11.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-onlystatic_assert(std::is_floating_point_v<T>).

11.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.

11.5.7 Applications

11.5.8 Connections to Other Algorithms

Used by solvers::QrDecomposition; complements math::HouseholderTransform.

11.6 Triangular Solve

11.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.

11.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.

11.6.3 Complexity Analysis

Operation Time Space Notes
SolveUpperTriangular \(O(n^2)\) \(O(n)\) One reverse sweep, stack output

11.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\).

11.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.

11.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.

11.6.7 Applications

11.6.8 Connections to Other Algorithms

Shared by solvers::GaussianElimination and solvers::QrDecomposition. Operates on math::Matrix / math::Vector.

11.7 Matrix Operations

11.7.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.

11.7.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\).

11.7.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

11.7.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\).

11.7.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>).

11.7.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().

11.7.7 Applications

11.7.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.

11.8 Step / Transient-Response Metrics

11.8.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.

11.8.2 Mathematical Theory

11.8.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.

11.8.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.

11.8.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  …

11.8.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.

11.8.6 Variants & Generalizations

11.8.7 Applications

11.8.8 Connections to Other Algorithms

11.9 Matrix Exponential

11.9.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.

11.9.2 Mathematical Theory

11.9.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.

11.9.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.

11.9.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.

11.9.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

11.9.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.

11.9.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.

11.9.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.

11.9.7 Applications

11.9.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.

12 Solvers

12.1 Cholesky Decomposition

12.1.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.

12.1.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\]

12.1.3 Complexity Analysis

Operation Time Space Notes
Decomposition \(O(n^3/6)\) \(O(n^2)\) Half the cost of general LU decomposition

12.1.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\)

12.1.5 Pitfalls & Edge Cases

12.1.6 Variants & Generalizations

12.1.7 Applications

12.1.8 Connections to Other Algorithms

Algorithm Relationship
Unscented Kalman Filter Uses Cholesky to generate sigma points from the covariance matrix
Gaussian Elimination General-purpose alternative; does not exploit symmetry

12.2 Gaussian Elimination

12.2.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.

12.2.2 Mathematical Theory

12.2.2.1 Problem Statement

Solve \(Ax = b\) where \(A \in \mathbb{R}^{n \times n}\) is non-singular, \(b \in \mathbb{R}^n\).

12.2.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'\).

12.2.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\]

12.2.2.4 Multi-Column Extension

For \(AX = B\) where \(B\) has \(m\) columns, each column is solved independently.

12.2.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\).

12.2.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\) ✓.

12.2.5 Pitfalls & Edge Cases

12.2.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

12.2.7 Applications

12.2.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

12.3 Levinson-Durbin Algorithm

12.3.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.

12.3.2 Mathematical Theory

12.3.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}\]

12.3.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\).

12.3.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.

12.3.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)\).

12.3.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\)

12.3.5 Pitfalls & Edge Cases

12.3.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

12.3.7 Applications

12.3.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

12.4 Durand-Kerner Polynomial Root Finder

12.4.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.

12.4.2 Mathematical Theory

12.4.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)}\]

12.4.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.

12.4.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.

12.4.2.4 Convergence

12.4.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.

12.4.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}\)).

12.4.5 Pitfalls & Edge Cases

12.4.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

12.4.7 Applications

12.4.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)

12.5 Discrete Algebraic Riccati Equation (DARE)

12.5.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.

12.5.2 Mathematical Theory

12.5.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\))

12.5.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.

12.5.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^*\).

12.5.2.4 Optimal Gain

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

12.5.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.

12.5.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.

12.5.5 Pitfalls & Edge Cases

12.5.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

12.5.7 Applications

12.5.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

12.6 Runge-Kutta ODE Integrators (RK4 + Dormand-Prince)

12.6.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.

12.6.2 Mathematical Theory

12.6.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\).

12.6.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)\).

12.6.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) \]

12.6.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}]\).

12.6.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.

12.6.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\).

12.6.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.

12.6.5 Pitfalls & Edge Cases

12.6.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

12.6.7 Applications

12.6.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

12.7 Spectral Radius & Discrete Stability Margin

12.7.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.

12.7.2 Mathematical Theory

12.7.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.

12.7.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.

12.7.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.

12.7.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.

12.7.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.

12.7.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\).

12.7.5 Pitfalls & Edge Cases

12.7.6 Variants & Generalizations

12.7.7 Applications

12.7.8 Connections to Other Algorithms

12.8 Condition Number

12.8.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.

12.8.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.

12.8.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.

12.8.3 Complexity Analysis

Operation Time Space Notes
ConditionNumber \(O(N^3)\) \(O(N^2)\) Dominated by the \(N\)-column solve of \(AX = I\)

12.8.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\)

12.8.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.

12.8.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.

12.8.7 Applications

12.8.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.

12.9 QR Decomposition

12.9.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.

12.9.2 Mathematical Theory

12.9.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.

12.9.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.

12.9.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.

12.9.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}\).

12.9.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.

12.9.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.

12.9.5 Pitfalls & Edge Cases

12.9.6 Variants & Generalizations

12.9.7 Applications

12.9.8 Connections to Other Algorithms

12.10 LU Decomposition with Partial Pivoting

12.10.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.

12.10.2 Mathematical Theory

12.10.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\)).

12.10.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.

12.10.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.

12.10.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.

12.10.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)\).

12.10.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\).

12.10.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.

12.10.5 Pitfalls & Edge Cases

12.10.6 Variants & Generalizations

12.10.7 Applications

12.10.8 Connections to Other Algorithms

12.11 Lyapunov and Sylvester Equation Solvers

12.11.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.

12.11.2 Mathematical Theory

12.11.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\).

12.11.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.

12.11.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.

12.11.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\).

12.11.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.

12.11.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\). ✓

12.11.5 Pitfalls & Edge Cases

12.11.6 Variants & Generalizations

12.11.7 Applications

12.11.8 Connections to Other Algorithms

13 Robust Control

13.1 Active Disturbance Rejection Control (ADRC + ESO)

13.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.

13.1.2 Mathematical Theory

13.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.

13.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.

13.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]\]

13.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.

13.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.

13.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[k-1]\).
  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\).

13.1.5 Pitfalls & Edge Cases

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 accuracy. The forward-Euler ESO introduces phase lag proportional to \(\omega_o T_s\). Keeping \(\omega_o T_s \ll 1\) (e.g., \(\omega_o T_s \leq 0.1\)) maintains accuracy; at higher \(\omega_o T_s\) 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.

13.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.

13.1.7 Applications

13.1.8 Connections to Other Algorithms

13.2 Sliding Mode Control

13.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.

13.2.2 Mathematical Theory

13.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}\).

13.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.

13.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.

13.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.

13.2.2.5 Total Control Law

\[u = u_{eq} - u_{sw} = -(S B)^{-1}\bigl[S A x + K\,\mathrm{sat}(s/\phi)\bigr]\]

13.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\).

13.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.

13.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.

13.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.

13.2.5 Pitfalls & Edge Cases

13.2.6 Variants & Generalizations

13.2.7 Applications

13.2.8 Connections to Other Algorithms

13.3 Disturbance Observer

13.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.

13.3.2 Mathematical Theory

13.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).\]

13.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.\]

13.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.

13.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.

13.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.

13.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.

13.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.

13.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.

13.3.5 Pitfalls & Edge Cases

13.3.6 Variants & Generalizations

13.3.7 Applications

13.3.8 Connections to Other Algorithms

14 Windowing

14.1 Window Functions

14.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.

14.1.2 Mathematical Theory

14.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).

14.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

14.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.

14.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\).

14.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

14.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.

14.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"]

14.1.5 Pitfalls & Edge Cases

14.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

14.1.7 Applications

14.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

15 References