Modal Analysis Module¶
Modal analysis tools for mode fitting and modal parameter extraction.
modal ¶
Created on Mon Aug 19 17:29:30 2019
@author: tb267
Attributes¶
Functions:¶
modal_fit_single_channel ¶
Fit modal parameters for a single mode to data within specified freq_range
modal_fit_all_channels ¶
Fit modal parameters for a single mode to data within specified freq_range.
Assumes all tf_data in tf_data_list have same frequency axes
unpack_matrix ¶
Unpack a stacked modal matrix X (one packed mode per row,
[fn, zn, an*N, pn*N, rk*N, rm*N]) into per-parameter arrays.
Robust to an EMPTY model: a (0, 2+4*N) matrix (every mode deleted)
returns zero-length fn/zn and (0, N) an/pn/rk/rm
instead of raising. The channel count is read from the column count
(X.shape[1]), NOT by indexing row 0 — indexing X[0, :] on an
emptied (0, 6) matrix is exactly what crashed
ModalData.delete_mode when the last mode was removed (the round-4
"Fit -> Reject" IndexError, also on Qt's Reject path).
reconstruct_transfer_function ¶
Reconstructs transfer functions from modal_data and returns TfData object. Includes the per-channel local residual terms (rk, rm). Does not modify modal_data.
_measurement_power ¶
The (jw)^p exponent for 'dsp' (0) / 'vel' (1) / 'acc' (2).
_modes_band ¶
Default estimation/refinement band: the modes' fn span padded so each
peak's half-power skirts are included (the modal_refine convention),
clamped to the measured axis. Returns [lo, hi] in Hz.
_measured_columns ¶
Stack every measured (non-reconstruction) TF column over the sel
frequency indices, cal-scaled — the modal_fit_all_channels /
modal_refine G0 assembly, shared. Returns (len(sel), n_cols).
NB the shared-pole machinery assumes every set shares ONE frequency axis
(tf_data_list[0].freq_axis) — the same assumption the fitters make.
estimate_global_constants ¶
Re-estimate the COMPLEX modal constants and per-channel GLOBAL residual terms for FIXED poles — the linear half of the global fit (round-7g).
With the poles {fn, zn} held fixed, the modal model is LINEAR in the
remaining parameters, so they solve in one least-squares with no
convergence risk. In receptance space (measured columns divided by
(jw)^p):
``G0_c/(jw)^p ~= sum_n A_nc * phi_n(w) + RH_c - RL_c/w^2``
with phi_n = 1/(wn^2 + 2j*wn*zn*w - w^2). RH (stiffness-like
constant) and RL/w^2 (mass-like) are ONE pair of GLOBAL residues per
channel, standing for above-band and below-band modes respectively —
unlike the local fits' per-mode rk/rm, which are mutually
redundant in a joint model (each mode's residues can impersonate a
neighbour's tail: flat cost directions that let refinement drag poles
without penalty). The constants that emerge are the GLOBALLY consistent
ones: neighbour interaction is explained by the neighbours themselves,
so any remaining phase in A is genuine mode complexity, not
circle-rotation leakage from nearby modes.
Parameters:
-
fn(array_like) –pole natural frequencies (Hz), length N — held fixed during the solve
-
zn(array_like) –pole damping ratios, length N — held fixed
-
f(ndarray) –frequency axis (Hz); rows with
f <= 0are excluded (the1/w^2term and the(jw)^pdivision are singular at DC) -
G0(ndarray) –measured complex columns, shape
(len(f), n_cols), cal-scaled -
measurement_type(str, default:'acc') –'acc' | 'vel' | 'dsp' (the
(jw)^pconvention)
Returns (A, RH, RL, cost): A complex (N, n_cols), RH and
RL complex (n_cols,), and cost = 0.5*sum(|model - G0|^2) over
the used rows in MEASURED space (identical to the stacked real/imag
least-squares cost convention used by modal_refine).
_evaluate_global_model ¶
Evaluate the re-estimated global model on axis f (any axis — the
reconstruction axis need not match the estimation axis). Guards the
w = 0 singularity the way f_TF_all_channels does (first bin
borrows the second bin's frequency).
reconstruct_transfer_function_global ¶
Reconstructs the GLOBAL (whole-model) transfer functions from modal_data and returns a TfData object. Does not modify modal_data.
With tf_data_list (the measured TFs) the reconstruction uses the
round-7g GLOBAL RE-ESTIMATION: the modal constants (amplitude AND phase,
per channel) plus one pair of global residues per channel (RH const +
RL/w^2) are re-solved linearly against the measured data over the
modes' padded band, with the stored poles held fixed (see
estimate_global_constants — this removes the double-counting of
neighbour interactions that each mode's LOCALLY-fitted phase absorbs).
Without tf_data_list (e.g. a ModalData loaded on its own) the legacy
behaviour is kept: the stored per-mode rows are summed with their local
residual terms (rk, rm) zeroed.
modal_refine ¶
Simultaneously refine ALL modes in modal_data against the measured
transfer functions, seeded from the current fit.
Round-7g VARIABLE-PROJECTION rebuild: the nonlinear search runs over the
POLES ONLY ([fn, zn] per mode — 2N parameters), and at every candidate
pole set the modal constants + per-channel GLOBAL residues are re-solved
linearly (estimate_global_constants). The previous refine optimised the
whole packed parameter set including every mode's LOCAL residual terms
(rk, rm) — which are mutually redundant in a joint model (one mode's
residues can impersonate a neighbour's tail), creating flat directions in
the cost surface along which a pole could drift far while the residues
compensated, "improving" the residual as it went. Projecting the linear
parameters out removes those flat directions and makes the pole search
far stiffer on overlapping-mode data (e.g. instrument bodies).
Like modal_fit_all_channels, the measured TFs are cal-scaled when
building the target G0, so the seed and the refined result live in
the SAME parameter space. freq_range defaults to the modes' padded
band (see _modes_band), clamped to the measured axis.
The refined M rows carry the poles plus the GLOBALLY re-estimated
constants (an = |A|, pn = arg A) with the per-mode local residues
ZEROED — after a joint refine the local-window residues are meaningless,
and the global reconstruction re-estimates its own residues from the
measured data anyway (reconstruct_transfer_function_global).
Convergence / non-convergence is REPORTED, never enforced. Returns
(ModalData, info) with
info = {'converged': bool, 'cost_before': float, 'cost_after': float};
both costs are the SAME projected-model cost (linear solve at the seed
poles vs at the refined poles), so directly comparable. The refined model
and info are returned even when not converged — the CALLER decides
whether to keep or revert (the webui auto-reverts on
converged == False). On a pathological failure the seed model is
handed back unchanged with converged == False.
Mac-runnable, no hardware. Mirrors modal_fit_all_channels for
settings / id_link / test_name provenance.