Skip to main content

modal_analysis

Function modal_analysis 

Source
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 SparseSolver handles comfortably, the dense eigensolve path could exhaust a wasm worker’s memory before it’s even “slow”. Domain no 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_modes are 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 just num_modes does 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.