pyserep.selection

pyserep.selection — frequency bands, mode selection, DOF selection.

class pyserep.selection.FrequencyBand(f_min: float, f_max: float, label: str | None = None, n_points: int | None = None)[source]

Bases: object

A single contiguous analysis band [f_min, f_max] Hz.

Parameters:
  • f_min (float) – Lower bound (Hz). Use 0.0 for DC.

  • f_max (float) – Upper bound (Hz).

  • label (str, optional) – Human-readable label. Auto-generated if not supplied.

  • n_points (int, optional) – Number of frequency evaluation points in this band. Overrides the global n_points_per_band when set.

Examples

>>> band = FrequencyBand(0, 100, label="LowBand")
>>> band.contains(50)
True
>>> band.span
100.0
property centre: float

Centre frequency in Hz.

contains(freq: float) bool[source]

Return True if freq lies within this band (inclusive).

expanded(alpha: float) FrequencyBand[source]

Return a new band with f_max scaled by alpha (MS1 safety factor).

f_max: float
f_min: float
label: str | None = None
n_points: int | None = None
property span: float

Width of the band in Hz.

class pyserep.selection.FrequencyBandSet(bands: Sequence[FrequencyBand], n_points_per_band: int = 2000)[source]

Bases: object

Container for a collection of FrequencyBand objects.

Manages the multi-band frequency grid, mode relevance checks, and band-weighted Modal Participation Factor computation.

Parameters:
  • bands (sequence of FrequencyBand)

  • n_points_per_band (int) – Default frequency evaluation points per band.

Examples

>>> bset = FrequencyBandSet([FrequencyBand(0, 100), FrequencyBand(400, 500)])
>>> bset.n_bands
2
>>> len(bset.frequency_grid())
4000
band_weighted_mpf(phi_f: ndarray, phi_o: ndarray, omega_n: ndarray, band: FrequencyBand) ndarray[source]

Band-weighted Modal Participation Factor for all modes.

C_i = |phi_f_i * phi_o_i| * max_ω_in_band(1 / |ωᵢ² − ω²|)

Parameters:
Return type:

(n_modes,) band-weighted MPF

property bands: List[FrequencyBand]

Return a copy of the band list, sorted by f_min.

frequency_grid() ndarray[source]

Build the evaluation frequency array (Hz) as the union of all bands.

Return type:

np.ndarray, sorted and deduplicated.

frequency_mask(freqs_hz: ndarray) ndarray[source]

Boolean mask: True where freqs_hz falls inside any band.

property global_f_max: float

Upper bound of the highest band (Hz).

property global_f_min: float

Lower bound of the lowest band (Hz).

property is_selective: bool

True when there are gaps between bands.

mode_passes_ms1(freq_hz: float, rb_hz: float = 1.0, alpha: float = 1.5) bool[source]

True if a mode passes the MS1 frequency-range criterion.

property n_bands: int

Number of frequency bands in the set.

summary() str[source]

Return a human-readable multi-line summary of all bands and gaps.

pyserep.selection.compare_dof_selectors(phi: ndarray, selected_modes: ndarray, verbose: bool = True) dict[source]

Run all four DOF selectors and return a comparison table.

Parameters:
  • phi (np.ndarray)

  • selected_modes (np.ndarray of int)

  • verbose (bool)

Returns:

Keys: “DS1”, “DS2”, “DS3”, “DS4”. Values: dict with “master_dofs”, “kappa”, “rank”, “elapsed_s”.

Return type:

dict

pyserep.selection.mac_filter(phi: ndarray, candidate_indices: ndarray, freqs_hz: ndarray, force_dofs: List[int], output_dofs: List[int], mac_threshold: float = 0.9, verbose: bool = True) ndarray[source]

Remove spatially redundant modes from candidate_indices.

Two modes are considered duplicates if their MAC exceeds mac_threshold. The mode with the lower participation score is discarded.

Parameters:
  • phi (np.ndarray, shape (N, n_all_modes))

  • candidate_indices (np.ndarray of int)

  • freqs_hz (np.ndarray)

  • force_dofs (list of int)

  • output_dofs (list of int)

  • mac_threshold (float)

  • verbose (bool)

Return type:

np.ndarray of int — filtered subset of candidate_indices

pyserep.selection.ms1_frequency_range(freqs_hz: ndarray, band_set: FrequencyBandSet, rb_hz: float = 1.0, ms1_alpha: float = 1.5, verbose: bool = True) ndarray[source]

MS1: Retain modes whose natural frequency is relevant to at least one band.

A mode at frequency f passes MS1 if:
  • f > rb_hz (not rigid-body)

  • f ≤ alpha × max(band.f_max) for at least one band

Parameters:
Return type:

np.ndarray of int

pyserep.selection.ms2_participation_factor(phi: ndarray, freqs_hz: ndarray, force_dofs: List[int], output_dofs: List[int], band_set: FrequencyBandSet, rb_hz: float = 1.0, threshold_pct: float = 1.0, verbose: bool = True) ndarray[source]

MS2: Retain modes with significant band-weighted Modal Participation Factor.

For each band B and DOF pair (f, o), a mode passes MS2 if:

C_i_B / C_dom_B ≥ threshold_pct / 100

where C_dom_B is the dominant elastic mode’s MPF within band B.

Parameters:
Return type:

np.ndarray of int

pyserep.selection.ms3_spatial_amplitude(phi: ndarray, freqs_hz: ndarray, target_dofs: List[int], rb_hz: float = 1.0, threshold_pct: float = 5.0, verbose: bool = True) ndarray[source]

MS3: Retain modes with significant spatial amplitude at target DOFs.

A mode i passes MS3 if, at any target DOF d:

|φᵢ(d)| / max_j(|φᵢ(j)|) ≥ threshold_pct / 100

This catches modes near nodal lines that MS2 might miss.

Parameters:
  • phi (np.ndarray, shape (N, n_modes))

  • freqs_hz (np.ndarray)

  • target_dofs (list of int) – Union of force and output DOFs.

  • rb_hz (float)

  • threshold_pct (float)

  • verbose (bool)

Return type:

np.ndarray of int

pyserep.selection.ms4_conditioning_check(phi: ndarray, mode_indices: ndarray, master_dofs: ndarray, verbose: bool = True) float[source]

MS4: Compute κ(Φₐ) to verify the master DOF set is well conditioned.

Parameters:
  • phi (np.ndarray)

  • mode_indices (np.ndarray of int)

  • master_dofs (np.ndarray of int)

  • verbose (bool)

Return type:

float — condition number κ(Φₐ)

pyserep.selection.select_dofs_eid(phi: ndarray, selected_modes: ndarray, n_master: int | None = None, candidate_dofs: ndarray | None = None, ke_prescreen_frac: float = 0.5, required_dofs: ndarray | None = None, verbose: bool = True) Tuple[ndarray, float][source]

DS4: Select master DOFs using the Effective Independence (EID) method.

The algorithm iteratively removes the DOF with the smallest diagonal entry of the EI matrix:

E = Φₛ (ΦₛᵀΦₛ)⁻¹ Φₛᵀ

which corresponds to the DOF contributing the least to the linear independence of the mode shape partition. The Fisher Information Matrix det(ΦₛᵀΦₛ) is thereby maximised.

Parameters:
  • phi (np.ndarray, shape (N, n_all_modes)) – Full modal matrix (mass-normalised).

  • selected_modes (np.ndarray of int) – Mode indices from the mode selection pipeline.

  • n_master (int, optional) – Number of master DOFs. Default: len(selected_modes) (enforces the exact-SEREP rule a = m).

  • candidate_dofs (np.ndarray of int, optional) – Pre-screened candidate DOF set. If None, a kinetic-energy pre-screen retains the top ke_prescreen_frac fraction.

  • ke_prescreen_frac (float) – Fraction to retain in the KE pre-screen (0 < f ≤ 1).

  • verbose (bool)

Returns:

  • master_dofs (np.ndarray of int) – Selected master DOF indices.

  • kappa (float) – Condition number κ(Φₐ) of the final master partition.

Examples

>>> dofs, kappa = select_dofs_eid(phi, selected_modes)
>>> kappa < 100
True
pyserep.selection.select_dofs_kinetic(phi: ndarray, selected_modes: ndarray, n_master: int | None = None, verbose: bool = True) Tuple[ndarray, float][source]

DS1: Select master DOFs by total kinetic energy across selected modes.

Score for DOF j: KE_j = Σᵢ |φᵢ(j)|²

Selects the top-n_master DOFs by descending KE score. Fast (O(N·m)) but typically produces κ ~ 10¹⁰–10¹⁵, which is unsuitable for SEREP unless used as a pre-screen for DS4.

Parameters:
Return type:

master_dofs, kappa

pyserep.selection.select_dofs_modal_disp(phi: ndarray, selected_modes: ndarray, n_master: int | None = None, verbose: bool = True) Tuple[ndarray, float][source]

DS2: Select master DOFs by maximum modal displacement magnitude.

Score for DOF j: S_j = max_i |φᵢ(j)|

Retains DOFs where at least one mode has large amplitude.

Parameters:
Return type:

master_dofs, kappa

pyserep.selection.select_dofs_svd(phi: ndarray, selected_modes: ndarray, n_master: int | None = None, verbose: bool = True) Tuple[ndarray, float][source]

DS3: Select master DOFs via QR factorisation with column pivoting of Φᵀ.

The column permutation from scipy.linalg.qr(Φᵀ, pivoting=True) directly gives the DOF ordering from most to least informative.

This is equivalent to maximising the diagonal entries of R and gives κ values typically in the range 10³–10⁶ — better than DS1/DS2 but still inferior to DS4 for SEREP.

Parameters:
Return type:

master_dofs, kappa

pyserep.selection.select_modes(phi: ndarray, freqs_hz: ndarray, force_dofs: List[int], output_dofs: List[int], band_set: FrequencyBandSet | None = None, f_max: float = 500.0, f_min: float = 0.1, rb_hz: float = 1.0, ms1_alpha: float = 1.5, ms2_threshold: float = 1.0, ms3_threshold: float = 5.0, mac_threshold: float = 0.9, verbose: bool = True) ndarray[source]

Run the complete mode selection pipeline.

Convenience wrapper that accepts either a FrequencyBandSet or plain f_min / f_max scalars for single-band analysis.

Parameters:
  • phi (np.ndarray, shape (N, n_modes)) – Mass-normalised modal matrix.

  • freqs_hz (np.ndarray, shape (n_modes,)) – Natural frequencies in Hz.

  • force_dofs (list of int) – Force DOF global indices.

  • output_dofs (list of int) – Output DOF global indices.

  • band_set (FrequencyBandSet, optional) – Multi-band specification. If None, a single band [f_min, f_max] is created.

  • f_max (float) – Upper frequency limit (Hz) when band_set is None.

  • f_min (float) – Lower frequency limit (Hz) when band_set is None.

  • rb_hz (float) – Rigid-body exclusion threshold (Hz).

  • ms1_alpha (float) – MS1 safety factor: retains modes up to alpha × max(f_max).

  • ms2_threshold (float) – MS2 band-weighted MPF threshold (% of dominant mode).

  • ms3_threshold (float) – MS3 spatial amplitude threshold (% of global peak).

  • mac_threshold (float) – MAC duplicate removal threshold.

  • verbose (bool)

Returns:

Selected mode indices, sorted ascending.

Return type:

np.ndarray of int

pyserep.selection.select_modes_pipeline(phi: ndarray, freqs_hz: ndarray, force_dofs: List[int], output_dofs: List[int], band_set: FrequencyBandSet, rb_hz: float = 1.0, ms1_alpha: float = 1.5, ms2_threshold: float = 1.0, ms3_threshold: float = 5.0, mac_threshold: float = 0.9, verbose: bool = True) ndarray[source]

Run the complete mode selection pipeline.

S_final = (MS1 filtered by MAC) ∪ MS2 ∪ MS3

Parameters:
Return type:

np.ndarray of int — final selected mode indices, sorted ascending