Repository navigation
Fix PoSER merging: mode pairing, missing modes and mode-shape scaling - #58
Merged
Merged
Conversation
…ults MultiSetup_PoSER.merge_results() averaged the single-setup results by mode index, scaled the roving sensors by the inverse Modal Scale Factor and failed when a mode was missing in some setups. Changes: - Add gen.match_modes(): pairs the modes of each setup with the merged modes by linear assignment on relative frequency difference and reference-sensor MAC, gated by freq_tol and mac_min (ValueError if not positive / not in [0, 1]); unpaired modes are appended as new merged modes - merge_mode_shapes() scales each setup onto the first setup's reference sensors (MSF arguments were swapped) and treats all-NaN columns as missing modes - merge_results() averages each mode over the setups that identified it, accepts freq_tol/mac_min and records MsPoserResult.setups_used - Xi/Xi_std are None for algorithms without damping (FDD) instead of raising - Aligned modes still reproduce the previous mean/std merge - Add tests for reordered, close, mismatched and missing modes, mode-shape scaling, tolerance validation and the backward-compatible aligned case - Pass strict=True to the same-DataFrame zip() calls in dfphi_map_func (ruff B905, flagged by the pre-commit hook on the touched module) Using the single-setup SSI uncertainties in the merge is left to a separate issue.
dfm88
force-pushed
the
fix/poser-merge
branch
from
September 22, 2026 09:58
5159087 to
303fb6c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MultiSetup_PoSER.merge_results()had three problems in the way it combines the single-setup results. This PR fixes them:Nothing changes in the simple case: if every setup finds the same modes in the same order, the merged frequencies and damping ratios are identical to before. Mode shapes change only through the scaling fix in point 3. The call is also unchanged:
msp.merge_results().As agreed, using the single-setup SSI uncertainties (
calc_unc=True) in the merge is not part of this PR; it is tracked in #59. The merge still ignores them, as before.1. Modes were paired by position
Before: the k-th mode of every setup was averaged together. This only works if every setup finds every mode and
sel_freqis picked in the same order everywhere. For example, if ss1 gives[2.0, 5.0, 9.0]Hz and ss2 gives[2.1, 9.1, 5.1]Hz, the 5.0 Hz mode was averaged with the 9.1 Hz one.Now: before averaging, each mode of each setup is matched to "the same mode" in the other setups, using the two checks you would do by hand:
Both checks are needed. With only three reference sensors, the 2.63 Hz and 8.29 Hz modes of Example 3 have MAC 0.99 on the references, so the shape alone cannot tell them apart; the frequency can.
Among the admissible pairs, the program chooses the best overall one-to-one matching (small frequency difference and high MAC both count). Two close modes (e.g. 2.63 / 2.69 Hz) are therefore told apart by their shape, even when the frequency scatter between setups makes them cross.
2. Missing modes
Before: if a setup identified fewer modes, the merge failed.
Now:
NaN("not measured"). They are not set to zero, because zero would look like a node.setups_usedsays which setups contributed to each merged mode.Fn,Xiand their standard deviations are computed only over the setups that identified the mode.Fn_stdis still the scatter between those setups, so it is 0 for a mode found in one setup only.3. Mode-shape rescaling was inverted
In PoSER, each setup's mode shape has an arbitrary scale. To glue the roving parts together, each setup j is rescaled with the Modal Scale Factor αj, chosen so that its reference sensors match those of the first setup (αj φref,j ≈ φref,1). The roving part of setup j is then multiplied by αj.
The old code computed the factor in the opposite direction (the one that maps setup 1 onto setup j). It multiplied the roving part by roughly 1/αj instead of αj. Example:
The bug goes unnoticed when α ≈ ±1, which is common because SSI normalises every mode shape to its largest component. It is not always the case, though. On the Example 3 data (3SL building, settings in the next section), the roving part of each setup was off by these factors (old amplitude ÷ correct amplitude):
For example, in the merged 3.43 Hz mode the roving sensors of setups 2 and 3 were about 2.4 times too large compared with those of setup 1.
Effect on the Example 3 dataset
Settings: the ones of the Example 3 docs page, which are also the
ms_poserfixture intests/conftest.py. Each setup is decimated withq=2and analysed withSSI(method="cov", br=50, ordmax=80), with thesel_freqbelow andorder_in= 50 / 40 / 40 for ss1 / ss2 / ss3, andref_ind = [[0, 1, 2]] * 3. (The docs page importsSSIcov, which no longer exists; the script below usesSSI.) Insetups_used, 0 = ss1, 1 = ss2, 2 = ss3.Script to reproduce the table
setups_usedFrequencies are unchanged except around 14 Hz, where the setups do not agree:
[-0.40, 0.08, -0.45]). All the other ~14 Hz modes move mainly on the 2nd reference channel (e.g. ss1 at 14.050 Hz ≈[0.06, -0.51, 0.01]). Its MAC on the references with the ss2/ss3 modes it used to be averaged with is only 0.05 – 0.08, and at most 0.20 with any other ~14 Hz mode, so it is a different mode and is now kept separate.The old average (14.004 Hz) happens to be very close to the FE value (14.0053 Hz), but its merged mode shape mixed two different modes.
sel_freq=[..., 14.00, 14.09, ...], ss2 returns 14.060 Hz for both, with identical shapes. So ss2 did not really identify the second mode of the pair. Its duplicate is now merged with ss3's 14.147 Hz mode (MAC 0.76) into the new 14.103 Hz mode.This is a picking issue in the example rather than in the merge. I did not change the example in this PR: the output printed on the Example 3 docs page already differs from the current output, so the page needs a refresh anyway.
The poles picked around 14 Hz depend on the SSI settings. For example, without decimation and with
br= 45 / 30 / 30,step=2andorder_in=50, it is ss1 and ss3 that pick the same pole twice, and the extra merged mode is 13.971 Hz from ss2 only. The frequencies differ, but the conclusion is the same: the setups do not agree around 14 Hz, and the new merge keeps the modes that do not match separate instead of averaging them.If you prefer the old behaviour for this dataset,
merge_results(mac_min=0.0)gives back 9 merged modes. With that setting the shape no longer rejects a pair; it is only used to choose the best matching.How to use
Other small changes
Xi/Xi_stdareNone.freq_tol=0, ormac_minoutside 0 – 1) now raise a clear error.zip()calls indfphi_map_funcwere made explicit because the linter required it. This does not change behaviour.Where I'd like your opinion
MSFhelper does not use the complex conjugate, so it assumes nearly real mode shapes. This is fine for normalised SSI shapes, but I would fix it separately if you agree.