pub fn modal_analysis<const NDIM: usize, const NDOF: usize, NId, E>(
domain: &mut Domain<NDIM, NDOF, NId, E>,
num_modes: usize,
) -> Result<Vec<Mode>, AnalysisError>where
NId: Key,
E: ElementOps<NDIM, NDOF, NId>,Expand description
A shift-invert Lanczos eigensolver (shift = 0, so the operator is simply
K^-1 * M), not a dense full-spectrum nalgebra::SymmetricEigen and
not an ARPACK FFI binding either. Two things made the earlier dense
choice wrong beyond just performance, once SparseSolver itself went
sparse:
- Memory. A dense N×N matrix costs O(n²) regardless of how sparse
the model actually is — on a model
SparseSolverhandles comfortably, the dense eigensolve path could exhaust a wasm worker’s memory before it’s even “slow”.Domainno longer builds one for this at all. - Shape of the problem. A full-spectrum solve computes all n
eigenpairs even when only the lowest
num_modesare wanted — the usual case, and the one modal-superposition damping actually needs. Lanczos naturally converges the extreme eigenvalues of the shift-inverted operator first, so asking for justnum_modesdoes the proportionate amount of work.
This isn’t “roll our own ARPACK”: the hard numerical pieces are both
already library code — SparseSolver’s sparse LU, factored once and
reused for each shift-invert solve K*w = M*v, and
nalgebra::SymmetricEigen for the small
m*m (m = Lanczos subspace size, not n) projected tridiagonal
eigenproblem. What’s ours is the well-understood outer Lanczos
recurrence and (full, since m is always small) M-orthogonal
re-orthogonalization loop.
Only shift = 0 is supported (find the num_modes lowest natural
frequencies) — by far the standard structural-dynamics case. A nonzero
shift (targeting modes near a chosen frequency, or handling a
near-singular K for buckling analysis) is a natural extension, not
implemented until something actually needs it.
M (lumped, Node::mass) must be diagonal and non-negative, with at
least num_modes strictly positive entries; massless DOFs are
statically condensed.
Generic over the same kinematic profile as Domain/Analysis (see
Domain’s doc comment) — the Lanczos recurrence and mass-normalization
above only ever touch domain through its generic free-DOF assembly
interface (assemble_mass_diagonal, num_free_dofs,
assemble_tangent_and_resistance), never anything DOF-count-specific,
so one implementation covers both Domain/Domain3 — inferred from
domain’s concrete type at each call site, same as
AnalysisBuilder::build.