Eigen — Eigenvalue & Modal Analysis¶
Overview¶
Eigenvalue computation and modal analysis for structural dynamics. Level 4 in the TinyMath dependency chain.
Header: #include "tiny_eigen.h" / #include "tiny_modal.h"
Architecture¶
The module is split into two complementary parts:
| Submodule | Header | Purpose |
|---|---|---|
| Eigenvalue | tiny_eigen.h | 2×2 closed-form, Schur extraction, Hessenberg reduction, Francis double-shift QR |
| Modal | tiny_modal.h | MIMO modal analysis: poles → frequency/damping, MAC, mode filtering/sorting |
Pipeline¶
A (n×n) → Hessenberg → QR iteration → Schur form → eigenvalues (tiny_cfloat_t[])
↓
Modal Analysis:
poles → ω_n, ζ → MAC → filter/sort
Eigenvalue Computation¶
tiny_error_t tiny_eig_2x2_f32(float a00, float a01, float a10, float a11,
tiny_cfloat_t *lam1, tiny_cfloat_t *lam2);
Direct analytical solution for \(2 \times 2\) matrices. No iteration needed.
| Param | Description |
|---|---|
a00, a01, a10, a11 | Matrix entries in row-major order |
lam1, lam2 | Output eigenvalues (complex) |
Formula: \(\lambda = \frac{\text{tr}(A) \pm \sqrt{\text{tr}(A)^2 - 4\det(A)}}{2}\)
Fastest path for \(2 \times 2\) sub-blocks. Used internally by the Francis QR iteration for \(2 \times 2\) Wilkinson shifts.
size_t tiny_eig_general_f32_workspace_size(int n);
tiny_error_t tiny_eig_general_f32(const float *A, int n,
tiny_cfloat_t *eigs,
void *ws, size_t ws_size);
Pipeline: \(A \xrightarrow{\text{Hessenberg}} H \xrightarrow{\text{Francis QR}} \text{Schur} \xrightarrow{\text{extract}} \Lambda\)
| Param | Description |
|---|---|
A | Input \(n \times n\) matrix (not modified) |
n | Matrix dimension |
eigs | Output array of \(n\) complex eigenvalues |
ws | Workspace buffer (\(\geq\) tiny_eig_general_f32_workspace_size(n) bytes) |
ws_size | Workspace size for safety checking |
Extract eigenvalues from a quasi-triangular (real Schur) matrix \(T\).
| Param | Description |
|---|---|
T | \(n \times n\) quasi-triangular matrix in row-major |
step | Row step (columns + padding) |
eigs | Output eigenvalues |
Real eigenvalues are on the diagonal; complex conjugate pairs appear as \(2 \times 2\) blocks.
Modal Analysis¶
tiny_error_t tiny_modal_poles_to_modal_f32(const tiny_cfloat_t *poles,
float *freq, float *damping,
int n, float fs);
Convert discrete-time poles to natural frequency and damping ratio:
| Param | Description |
|---|---|
poles | \(n\) complex poles (from SSI/ERA) |
freq | Output: natural frequencies in Hz |
damping | Output: damping ratios (%) |
n | Number of poles |
fs | Sampling frequency in Hz |
Formulas: \(\(p = \ln(\lambda) \cdot f_s, \quad \omega_n = |p|, \quad \zeta = -\frac{\Re(p)}{|p|}\)\)
tiny_error_t tiny_modal_mac_matrix_f32(const float *Phi1, int n1,
const float *Phi2, int n2,
int n_dof, float *MAC);
float tiny_modal_mac_pair_f32(const float *phi_a, const float *phi_b, int n_dof);
| Function | Description |
|---|---|
mac_matrix | Full \(n_1 \times n_2\) MAC matrix between two mode shape sets |
mac_pair | Single MAC value between two mode shape vectors |
Formula: \(\text{MAC}(\phi_a, \phi_b) = \frac{|\phi_a^T \phi_b|^2}{(\phi_a^T \phi_a)(\phi_b^T \phi_b)}\)
MAC = 1 indicates perfectly correlated modes; MAC = 0 indicates orthogonal modes.
int tiny_modal_filter_stable_poles(const tiny_cfloat_t *poles,
int n, float max_damping,
float min_freq, float max_freq,
float fs);
int tiny_modal_sort_by_freq(const tiny_cfloat_t *poles,
const float *freq, const float *damping,
int n, int *order);
| Function | Description |
|---|---|
filter_stable_poles | Returns count of poles satisfying \(\zeta < \text{max\_damping}\) and \(\omega \in [\text{min\_freq}, \text{max\_freq}]\) |
sort_by_freq | Returns permutation array sorted by natural frequency (ascending) |
Typical SSI/ERA Workflow
// 1. Compute eigenvalues from system matrix
tiny_cfloat_t poles[50];
tiny_eig_general_f32(A, 50, poles, ws, ws_size);
// 2. Convert to frequency and damping
float freq[50], damping[50];
tiny_modal_poles_to_modal_f32(poles, freq, damping, 50, fs);
// 3. Filter stable poles
int n_stable = tiny_modal_filter_stable_poles(poles, 50, 5.0f, 0.1f, 50.0f, fs);
// 4. Compute MAC between two identification runs
float mac_matrix[25 * 25];
tiny_modal_mac_matrix_f32(Phi_run1, 25, Phi_run2, 25, n_dof, mac_matrix);
Algorithm Details¶
Francis Double-Shift QR¶
The core algorithm for \(n \times n\) eigenvalue computation:
- Hessenberg reduction: \(A \to H\) via Householder reflections (\(O(n^3)\))
- Francis double-shift QR: implicit double-shift iteration on \(H\) (\(O(n^3)\) per iteration)
- Deflation: when \(H_{n,n-1} \approx 0\), split problem into \((n-1) \times (n-1)\) and \(2 \times 2\)
- Schur extraction: pull eigenvalues from the final quasi-triangular matrix
The double shift preserves real arithmetic for real matrices — complex eigenvalues appear as \(2 \times 2\) blocks on the diagonal.
Convergence¶
Typical convergence in 2-3 QR iterations for well-conditioned matrices. Matrices with clustered eigenvalues may require more iterations. The algorithm uses Wilkinson shifts for accelerated convergence.