Skip to content

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
tiny_error_t tiny_schur_extract_eigs_f32(const float *T, int n, int step,
                                          tiny_cfloat_t *eigs);

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.


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:

  1. Hessenberg reduction: \(A \to H\) via Householder reflections (\(O(n^3)\))
  2. Francis double-shift QR: implicit double-shift iteration on \(H\) (\(O(n^3)\) per iteration)
  3. Deflation: when \(H_{n,n-1} \approx 0\), split problem into \((n-1) \times (n-1)\) and \(2 \times 2\)
  4. 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.