API Reference
- pyserep.io
- pyserep.core
- pyserep.selection
- pyserep.frf
- pyserep.analysis
ConvergencePointConvergenceStudyPerformanceMetricsValidationReportdof_count_study()eigenvalue_error()eigenvalue_sensitivity()flop_count()frf_sensitivity()material_perturbation_study()modal_assurance_criterion()mode_count_study()monte_carlo_frf()orthogonality_check()reduction_metrics()summarise_performance()validate_serep()
- pyserep.visualization
- pyserep.models
- pyserep.utils
Timeransys_dof()apply_bcs()bandwidth()build_dof_map()condition_number_estimate()diagonal_scaling()dof_to_ansys()force_positive_definite()is_diagonal()mass_normalise()matrix_stats()memory_mb()modal_residues()modal_strain_energy()rank_revealing_qr()reorder_rcm()safe_pinv()sparse_submatrix()sparsity()symmetrise()unscale_modes()
- pyserep.pipeline
pyserep — System Equivalent Reduction Expansion Process
A production-grade, publication-ready Python library for SEREP-based Reduced Order Modelling of large-scale structural dynamics problems.
Quick start (Pipeline API)
>>> from pyserep import SereпPipeline, ROMConfig, FrequencyBand
>>> cfg = ROMConfig(
... stiffness_file="K.mtx",
... mass_file="M.mtx",
... force_dofs=[3000],
... output_dofs=[3000],
... bands=[FrequencyBand(0, 100), FrequencyBand(400, 500)],
... )
>>> results = SereпPipeline(cfg).run()
Quick start (Functional API)
>>> from pyserep import load_matrices, solve_eigenproblem
>>> from pyserep import select_modes, select_dofs_eid
>>> from pyserep import build_serep_rom
>>> from pyserep import compute_frf_direct, compute_frf_modal
>>> K, M = load_matrices("K.mtx", "M.mtx")
>>> freqs, phi = solve_eigenproblem(K, M, n_modes=100)
>>> modes = select_modes(phi, freqs, force_dofs=[3000], output_dofs=[3000], f_max=500.0)
>>> dofs, kappa = select_dofs_eid(phi, modes)
>>> T, Ka, Ma = build_serep_rom(K, M, phi, modes, dofs)
>>> frf = compute_frf_direct(Ka, Ma, zeta=0.01, f_eval=range(1, 501))
- class pyserep.ConvergencePoint(param_value: float, n_modes: int, n_dofs: int, kappa: float, max_frf_err_pct: float, rms_frf_err_pct: float, max_freq_err_pct: float)[source]
Bases:
objectSingle point in a convergence sweep.
- class pyserep.ConvergenceStudy(param_name: str, param_label: str, points: List[ConvergencePoint])[source]
Bases:
objectResults from a full convergence sweep.
- plot(save_path: str | None = None, show: bool = False) None[source]
Plot FRF error and condition number vs parameter.
- points: List[ConvergencePoint]
- class pyserep.FRFResult(freqs_hz: ~numpy.ndarray, H_rom: ~typing.Dict[str, ~numpy.ndarray], H_ref: ~typing.Dict[str, ~numpy.ndarray], band_masks: ~typing.Dict[str, ~numpy.ndarray], method: str = 'direct', errors: ~typing.Dict[str, ~typing.Dict] = <factory>)[source]
Bases:
objectContainer for FRF results from both ROM and reference models.
- freqs_hz
Evaluation frequencies (Hz). May be non-uniform if selective bands are used.
- Type:
np.ndarray
- errors
Per-pair error metrics computed at construction time. Each entry is
{"max_pct": float, "rms_pct": float}.- Type:
- class pyserep.FrequencyBand(f_min: float, f_max: float, label: str | None = None, n_points: int | None = None)[source]
Bases:
objectA single contiguous analysis band [f_min, f_max] Hz.
- Parameters:
Examples
>>> band = FrequencyBand(0, 100, label="LowBand") >>> band.contains(50) True >>> band.span 100.0
- expanded(alpha: float) FrequencyBand[source]
Return a new band with f_max scaled by alpha (MS1 safety factor).
- class pyserep.FrequencyBandSet(bands: Sequence[FrequencyBand], n_points_per_band: int = 2000)[source]
Bases:
objectContainer 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:
phi_f ((n_modes,))
phi_o ((n_modes,))
omega_n ((n_modes,))
band (FrequencyBand)
- 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.
- class pyserep.PerformanceMetrics(n_full_dofs: int, n_selected_modes: int, n_master_dofs: int, dof_reduction_pct: float, mode_retention_pct: float, kappa: float, frf_method: str, n_freq_points: int, n_bands: int, frf_flops_rom: int, frf_flops_ref: int, frf_speedup: float, t_eigensolver_s: float, t_mode_select_s: float, t_dof_select_s: float, t_rom_build_s: float, t_frf_s: float, t_total_s: float)[source]
Bases:
objectAll performance and efficiency metrics for a pipeline run.
- class pyserep.PipelineResults(config: ROMConfig, freqs_hz: ndarray = <factory>, phi: ndarray = <factory>, selected_modes: ndarray = <factory>, master_dofs: ndarray = <factory>, T: ndarray | None = None, Ka: ndarray | None = None, Ma: ndarray | None = None, kappa: float = inf, freq_errors: ndarray | None = None, max_freq_err: float = nan, frf: FRFResult | None = None, validation: ValidationReport | None = None, performance: PerformanceMetrics | None = None, saved_files: Dict[str, str]=<factory>, elapsed_total_s: float = 0.0)[source]
Bases:
objectAll outputs from a completed SEREP ROM pipeline run.
- freqs_hz
All computed natural frequencies (Hz).
- Type:
np.ndarray
- phi
Full modal matrix (N × n_modes).
- Type:
np.ndarray
- T
SEREP transformation matrix (N × m).
- Type:
np.ndarray
- Ka, Ma
Reduced stiffness and mass matrices (m × m).
- Type:
np.ndarray
- freq_errors
Per-mode eigenvalue preservation errors (%).
- Type:
np.ndarray
- validation
- Type:
- performance
- Type:
- performance: PerformanceMetrics | None = None
- validation: ValidationReport | None = None
- class pyserep.ROMConfig(stiffness_file: str = '', mass_file: str = '', force_dofs: List[int] = <factory>, output_dofs: List[int] = <factory>, bands: List[FrequencyBand] | None = None, freq_range: Tuple[float, float]=(0.1, 500.0), frf_method: str = 'direct', damping_type: str = 'modal', zeta: float = 0.001, n_points_per_band: int = 2000, num_modes_eigsh: int = 100, eigsh_sigma: float = 0.01, eigsh_tol: float = 1e-10, ms1_alpha: float = 1.5, ms2_threshold: float = 1.0, ms3_threshold: float = 5.0, mac_threshold: float = 0.9, rb_hz: float = 1.0, dof_method: str = 'eid', ke_prescreen_frac: float = 0.5, export_folder: str = 'pyserep_output', save_prefix: str = 'SEREP', save_matrices: bool = True, plot: bool = True, verbose: bool = True)[source]
Bases:
objectComplete configuration for a SEREP ROM pipeline run.
- bands: List[FrequencyBand] | None = None
- property effective_bands: List[FrequencyBand]
Resolved list of FrequencyBand objects (from bands or freq_range).
- class pyserep.SereпPipeline(config: ROMConfig)[source]
Bases:
objectOrchestrates the complete SEREP ROM pipeline.
- Parameters:
config (ROMConfig)
Examples
Full-range analysis with direct FRF:
>>> cfg = ROMConfig( ... stiffness_file="K.mtx", ... mass_file="M.mtx", ... force_dofs=[3000], ... output_dofs=[3000], ... freq_range=(0.1, 500.0), ... frf_method="direct", ... ) >>> results = SereпPipeline(cfg).run()
Selective bands:
>>> from pyserep import FrequencyBand >>> cfg = ROMConfig( ... stiffness_file="K.mtx", ... mass_file="M.mtx", ... force_dofs=[3000], ... output_dofs=[3000], ... bands=[FrequencyBand(0, 100), FrequencyBand(400, 500)], ... ) >>> results = SereпPipeline(cfg).run()
- run() PipelineResults[source]
Execute the full pipeline and return
PipelineResults.
- class pyserep.ValidationReport(eigenvalue_errors_pct: ndarray, max_eigenvalue_error_pct: float, mean_eigenvalue_error_pct: float, mass_ortho_error: float, stiff_ortho_error: float, expansion_error: float, mac_values: ndarray, min_mac: float, mean_mac: float, ka_positive_definite: bool, ma_positive_definite: bool, ka_condition_number: float, ma_condition_number: float)[source]
Bases:
objectFull validation report for a SEREP ROM.
- pyserep.ansys_dof(node: int, direction: int) int[source]
Convert Ansys node number + direction to a 0-based DOF index.
DOF_index = (node_number − 1) × 3 + direction
- Parameters:
node (int — 1-based Ansys node number)
direction (int — 0=UX, 1=UY, 2=UZ)
- Return type:
int — 0-based DOF index
- pyserep.apply_bcs(K: spmatrix, M: spmatrix, fixed_dofs: List[int], penalty: float = 1000000000000000.0) Tuple[csc_matrix, csc_matrix][source]
Apply fixed-DOF boundary conditions via the penalty method.
Sets K[dof, dof] += penalty and M[dof, dof] = 1 for each fixed DOF. This is numerically equivalent to removing the rows/columns but preserves the matrix dimensions (convenient for DOF indexing).
- Parameters:
- Returns:
K_bc, M_bc
- Return type:
- pyserep.build_dof_map(master_dofs: ndarray, force_dofs: List[int], output_dofs: List[int]) Tuple[List[int], List[int]][source]
Map global force/output DOF indices → local indices within master_dofs.
- pyserep.build_rayleigh_damping(Ka: ndarray, Ma: ndarray, zeta: float, freqs_hz: ndarray, modes: ndarray) ndarray[source]
Build a Rayleigh damping matrix for the reduced model.
Rayleigh damping: Cₐ = α Mₐ + β Kₐ
where α and β are chosen to give damping ratio zeta at the first and last selected natural frequencies.
- Parameters:
- Returns:
Ca – Rayleigh damping matrix.
- Return type:
np.ndarray, shape (m, m)
- pyserep.build_serep_rom(K: csc_matrix, M: csc_matrix, phi: ndarray, selected_modes: ndarray, master_dofs: ndarray, verbose: bool = True) Tuple[ndarray, ndarray, ndarray][source]
Build the SEREP transformation matrix T and compute reduced matrices Kₐ, Mₐ.
- Parameters:
K (scipy.sparse.csc_matrix) – Full stiffness matrix (N × N). Must be real and symmetric.
M (scipy.sparse.csc_matrix) – Full mass matrix (N × N). Must be real, symmetric, and positive definite.
phi (np.ndarray, shape (N, n_all_modes)) – Full mass-normalised modal matrix.
selected_modes (np.ndarray of int) – Mode indices to retain (output of
select_modes()).master_dofs (np.ndarray of int) – Master DOF indices (output of
select_dofs_eid()). Must satisfylen(master_dofs) == len(selected_modes)for exact SEREP.verbose (bool) – Print construction diagnostics.
- Returns:
T (np.ndarray, shape (N, m)) – SEREP transformation matrix.
Ka (np.ndarray, shape (m, m)) – Reduced stiffness matrix.
Ma (np.ndarray, shape (m, m)) – Reduced mass matrix.
- Raises:
ValueError – If
len(master_dofs) != len(selected_modes)and the exact inverse would be undefined.RuntimeWarning – If the condition number of Φₐ is very large (> 10⁶), indicating a poorly conditioned master DOF set.
Notes
When
len(master_dofs) > len(selected_modes)(over-constrained), the Moore–Penrose pseudoinverse is used, resulting in a least-squares SEREP approximation.Examples
>>> T, Ka, Ma = build_serep_rom(K, M, phi, selected_modes, master_dofs) >>> Ka.shape (37, 37)
- pyserep.compare_dof_selectors(phi: ndarray, selected_modes: ndarray, verbose: bool = True) dict[source]
Run all four DOF selectors and return a comparison table.
- pyserep.compute_frf_direct(Ka: ndarray, Ma: ndarray, force_dof_indices: List[int], output_dof_indices: List[int], freq_eval: ndarray | List[float], zeta: float = 0.001, Ca: ndarray | None = None, damping_type: str = 'modal', eta: float = 0.0, verbose: bool = True) Tuple[ndarray, Dict[str, ndarray]][source]
Compute FRF of the SEREP ROM using direct impedance inversion.
At each frequency ω, solve: Z(ω) H_col = I_{force_col}
where Z(ω) = Kₐ - ω²Mₐ + jωCₐ (or Kₐ(1+jη) - ω²Mₐ for hysteretic).
- Parameters:
Ka (np.ndarray, shape (m, m)) – Reduced stiffness matrix from SEREP.
Ma (np.ndarray, shape (m, m)) – Reduced mass matrix from SEREP.
force_dof_indices (list of int) – Indices of force (input) DOFs within the master DOF set (0-based, 0..m-1).
output_dof_indices (list of int) – Indices of response (output) DOFs within the master DOF set. Must be the same length as force_dof_indices.
freq_eval (array-like) – Evaluation frequencies in Hz.
zeta (float) – Uniform modal damping ratio (used only for
damping_type="modal"anddamping_type="rayleigh").Ca (np.ndarray, shape (m, m), optional) – User-supplied damping matrix. Overrides zeta and damping_type.
damping_type (str) – One of: *
"modal"— Ca built from modal damping ratios *"rayleigh"— Ca = α Ma + β Ka *"hysteretic"— structural damping: Ka(1+jη)−ω²Ma *"none"— undampedeta (float) – Structural damping loss factor (only for
damping_type="hysteretic").verbose (bool)
- Returns:
freq_eval (np.ndarray) – Evaluation frequencies in Hz.
H (dict) – FRF arrays keyed by
"f{fi}_o{oi}". Values are complex arrays of shape (n_freq,).
Notes
This function uses LU factorisation reuse (
scipy.linalg.lu_factor) only when the frequency loop is over simple viscous damping. For hysteretic or arbitrary Ca the system matrix changes with every ω so each step requires a fresh solve; but the (m × m) system is small, making direct LU fast regardless.Examples
>>> freqs, H = compute_frf_direct(Ka, Ma, [5], [5], np.arange(1, 501)) >>> H["f5_o5"].shape (500,)
- pyserep.compute_frf_direct_fullmodel(K: csc_matrix, M: csc_matrix, master_dofs: ndarray, force_dof_global: List[int], output_dof_global: List[int], freq_eval: ndarray | List[float], zeta: float = 0.001, verbose: bool = True) Tuple[ndarray, Dict[str, ndarray]][source]
Compute reference FRF directly from the full-order physical matrices.
This is the true ground truth — no modal truncation, no approximation. Uses sparse LU factorisation at each frequency step.
WARNING: This function is computationally expensive for large systems (N > 10,000 DOFs × many frequencies). For large models, the modal reference (
compute_frf_modal_reference()) with all elastic modes is a practical alternative.- Parameters:
K (scipy.sparse.csc_matrix) – Full stiffness matrix.
M (scipy.sparse.csc_matrix) – Full mass matrix.
master_dofs (np.ndarray of int) – Global DOF indices corresponding to the ROM master DOFs. Used to determine the force/output DOF local indices.
force_dof_global (list of int) – Force DOF global indices (into the N-DOF full model).
output_dof_global (list of int) – Response DOF global indices.
freq_eval (array-like) – Evaluation frequencies in Hz.
zeta (float) – Uniform modal damping ratio (Rayleigh-type applied to full model).
verbose (bool)
- Returns:
freq_eval (np.ndarray)
H_ref (dict)
- pyserep.compute_frf_modal(phi: ndarray, freqs_hz: ndarray, mode_indices: ndarray, force_dofs: List[int], output_dofs: List[int], band_set: FrequencyBandSet, zeta: float = 0.001, per_mode_zeta: ndarray | None = None, verbose: bool = True) Tuple[ndarray, Dict[str, ndarray]][source]
Compute FRF via modal superposition, evaluated only within band regions.
- Parameters:
phi (np.ndarray, shape (N, n_modes)) – Full modal matrix (mass-normalised).
freqs_hz (np.ndarray, shape (n_modes,)) – Natural frequencies in Hz.
mode_indices (np.ndarray of int) – Indices of modes to include in the superposition.
band_set (FrequencyBandSet) – Defines the frequency evaluation grid.
zeta (float) – Uniform damping ratio (used when
per_mode_zetais None).per_mode_zeta (np.ndarray, optional) – Per-mode damping ratios, shape (n_modes,). Overrides zeta.
verbose (bool)
- Returns:
freq_eval (np.ndarray) – Evaluation frequencies (Hz).
H (dict) – FRF arrays keyed by
"f{fi}_o{oi}".
- pyserep.compute_frf_modal_reference(phi: ndarray, freqs_hz: ndarray, rb_hz: float, force_dofs: List[int], output_dofs: List[int], band_set: FrequencyBandSet, zeta: float = 0.001, verbose: bool = True) Dict[str, ndarray][source]
Compute reference FRF using ALL elastic modes of the full model.
This is used as the ground truth for FRF accuracy assessment.
- pyserep.compute_frf_pair_direct(Ka: ndarray, Ma: ndarray, phi: ndarray, freqs_hz_all: ndarray, selected_modes: ndarray, master_dofs: ndarray, force_dofs: List[int], output_dofs: List[int], band_set: FrequencyBandSet, zeta: float = 0.001, damping_type: str = 'modal', rb_hz: float = 1.0, verbose: bool = True) FRFResult[source]
Compute both ROM (direct) and reference (modal, all elastic modes) FRFs.
The reference uses all elastic modes via modal superposition — this is sufficiently accurate for large models where the direct full-model solve would be prohibitively expensive.
- Parameters:
Ka (np.ndarray, shape (m, m)) – Reduced matrices from SEREP.
Ma (np.ndarray, shape (m, m)) – Reduced matrices from SEREP.
phi (np.ndarray, shape (N, n_modes)) – Full modal matrix.
freqs_hz_all (np.ndarray) – All full-model natural frequencies.
selected_modes (np.ndarray of int)
master_dofs (np.ndarray of int)
band_set (FrequencyBandSet)
zeta (see
compute_frf_direct())damping_type (see
compute_frf_direct())rb_hz (see
compute_frf_direct())verbose (bool)
- Return type:
- pyserep.condition_number_estimate(A: ndarray, method: str = 'exact') float[source]
Estimate the 2-norm condition number of a dense matrix A.
- Parameters:
A (np.ndarray, shape (m, n))
method (str) –
"exact"— full SVD (always accurate, O(mn²))"fast"— power iteration on AᵀA (cheaper for large m)
- Return type:
float — κ(A) = σ_max / σ_min
- pyserep.diagonal_scaling(K: spmatrix, M: spmatrix) Tuple[csc_matrix, csc_matrix, ndarray][source]
Scale (K, M) by the diagonal of M to improve eigensolver conditioning.
Transformation: K̃ = D⁻½ K D⁻½, M̃ = D⁻½ M D⁻½
where D = diag(M). This converts the generalised problem to a standard one when M is diagonal (lumped mass), and improves conditioning for consistent mass matrices.
- Parameters:
K (scipy.sparse matrices)
M (scipy.sparse matrices)
- Returns:
K_scaled, M_scaled (scipy.sparse.csc_matrix)
D_inv_sqrt (np.ndarray, shape (N,) — scaling vector for back-transformation)
- pyserep.dof_count_study(K: csc_matrix, M: csc_matrix, phi: ndarray, freqs_hz: ndarray, selected_modes: ndarray, force_dofs: List[int], output_dofs: List[int], n_master_values: List[int], freq_eval: ndarray, H_ref: dict, zeta: float = 0.001, verbose: bool = True) ConvergenceStudy[source]
Study how FRF accuracy and condition number change with n_master.
Fixes the mode set and varies the number of master DOFs from
len(selected_modes)up tomax(n_master_values).- Parameters:
- Return type:
- pyserep.dof_to_ansys(dof: int) Tuple[int, int][source]
Convert a 0-based DOF index to (node_number, direction).
Inverse of
ansys_dof().- Return type:
(node_number, direction) — 1-based node, direction in {0,1,2}
- pyserep.eigenvalue_error(Ka: ndarray, Ma: ndarray, full_freqs_hz: ndarray, selected_modes: ndarray, verbose: bool = True) Tuple[ndarray, float][source]
Compute per-mode eigenvalue preservation error (%).
- Returns:
errors_pct (np.ndarray, shape (m,))
max_error_pct (float)
- pyserep.eigenvalue_sensitivity(K: spmatrix, M: spmatrix, phi: ndarray, freqs_hz: ndarray, selected_modes: ndarray, dK_dp: spmatrix, dM_dp: spmatrix, verbose: bool = True) ndarray[source]
Compute eigenvalue sensitivities ∂λᵢ/∂p using Nelson’s method.
For mass-normalised modes (φᵢᵀ M φᵢ = 1):
∂λᵢ/∂p = φᵢᵀ (∂K/∂p − λᵢ ∂M/∂p) φᵢ
- Parameters:
K (sparse matrices — full structural matrices)
M (sparse matrices — full structural matrices)
phi (np.ndarray, shape (N, n_all_modes))
freqs_hz (np.ndarray — full-model natural frequencies)
selected_modes (np.ndarray of int)
dK_dp (sparse matrices — parameter derivative matrices) – Represents ∂K/∂p and ∂M/∂p for a single parameter p. For a Young’s modulus perturbation: dK_dp = K/E, dM_dp = 0.
dM_dp (sparse matrices — parameter derivative matrices) – Represents ∂K/∂p and ∂M/∂p for a single parameter p. For a Young’s modulus perturbation: dK_dp = K/E, dM_dp = 0.
verbose (bool)
- Returns:
dlam_dp
- Return type:
np.ndarray, shape (m,) — ∂λᵢ/∂p in (rad/s)² per unit of p
Examples
Sensitivity of eigenvalues to a 1% stiffness increase:
>>> dK_dp = 0.01 * K # 1% increase in E >>> dM_dp = sp.csc_matrix(K.shape) # mass unchanged >>> dlam = eigenvalue_sensitivity(K, M, phi, freqs, modes, dK_dp, dM_dp) >>> dfreq_hz = dlam / (2 * np.pi * freqs_hz[modes]) / (2 * np.pi)
- pyserep.enforce_symmetry(mat: spmatrix) csc_matrix[source]
Enforce symmetry:
A ← 0.5 * (A + Aᵀ).- Parameters:
mat (scipy.sparse matrix)
- Returns:
Symmetric version of mat.
- Return type:
- pyserep.euler_beam(n_elements: int = 50, length: float = 1.0, EI: float = 10000.0, rho_A: float = 1.0, fixed_left: bool = True, fixed_right: bool = False) Tuple[csc_matrix, csc_matrix][source]
1D Euler-Bernoulli beam (consistent mass matrix).
DOFs per node: transverse displacement w and rotation θ. Total DOFs: 2 × (n_elements + 1). Fixed BCs applied by row/column zeroing with large diagonal penalty.
- Parameters:
- Returns:
K, M
- Return type:
scipy.sparse.csc_matrix, shape (2*(n_elements+1), …)
- pyserep.flop_count(n_modes: int, n_freq: int, n_pairs: int, method: str = 'direct') int[source]
Estimate FRF computation FLOP count.
- Parameters:
- Returns:
Approximate floating-point operation count.
- Return type:
Notes
Modal FRF: per frequency per mode per pair: ~8 FLOPs (2 multiplications + complex division + addition)
Direct FRF: per frequency: one m×m complex LU factor + m×m backsolve LU factor: (2/3) m³ FLOPs Backsolve: m² FLOPs per right-hand side × n_pairs
- pyserep.force_positive_definite(A: ndarray, shift_factor: float = 1e-08, max_iter: int = 50) Tuple[ndarray, float][source]
Make A symmetric positive definite by adding a small multiple of the identity until Cholesky succeeds.
Uses the modified Cholesky approach of Gill, Murray & Wright (1981).
- Parameters:
- Returns:
A_pd (np.ndarray — positive-definite version of A)
shift (float — total shift applied (0 if A was already PD))
Examples
>>> Ka_pd, shift = force_positive_definite(Ka) >>> np.linalg.cholesky(Ka_pd) # succeeds
- pyserep.frf_sensitivity(Ka: ndarray, Ma: ndarray, dKa_dp: ndarray, dMa_dp: ndarray, local_force: List[int], local_output: List[int], freq_eval: ndarray, zeta: float = 0.001, damping_type: str = 'modal') Dict[str, ndarray][source]
Compute the FRF parameter sensitivity ∂H/∂p via direct differentiation.
Differentiating Z(ω) H = I with respect to p gives:
∂H/∂p = −Z⁻¹ (∂Z/∂p) Z⁻¹
where ∂Z/∂p = ∂Kₐ/∂p − ω² ∂Mₐ/∂p (for viscous damping proportional to Ka, Ma).
- Parameters:
Ka (np.ndarray, shape (m, m) — reduced matrices)
Ma (np.ndarray, shape (m, m) — reduced matrices)
dKa_dp (np.ndarray, shape (m, m) — parameter derivatives of Ka, Ma)
dMa_dp (np.ndarray, shape (m, m) — parameter derivatives of Ka, Ma)
local_force (list of int — local DOF indices within master set)
local_output (list of int — local DOF indices within master set)
freq_eval (np.ndarray — evaluation frequencies (Hz))
zeta (damping parameters (same as compute_frf_direct))
damping_type (damping parameters (same as compute_frf_direct))
- Returns:
dH_dp – Values are complex arrays of shape (n_freq,) representing ∂H/∂p.
- Return type:
dict — same key structure as
compute_frf_direct()
Notes
This gives the first-order sensitivity. For a parameter change Δp, the FRF changes approximately by ΔH ≈ (∂H/∂p) Δp.
- pyserep.is_diagonal(mat: spmatrix, tol: float = 1e-12) bool[source]
Return True if mat is effectively diagonal (lumped mass).
- pyserep.load_frf_npz(path: str) Dict[str, ndarray][source]
Load a saved FRF
.npzfile.- Returns:
freqs_hz— evaluation frequencies (Hz)rom_*— ROM FRF arrays (complex)ref_*— reference FRF arrays (complex)- Return type:
dict with keys
- pyserep.load_matrices(stiffness_path: str, mass_path: str, verbose: bool = True, symmetry_tol: float = 1e-08) Tuple[csc_matrix, csc_matrix][source]
Load stiffness and mass matrices from disk.
- Parameters:
stiffness_path (str) – Path to the stiffness matrix file.
mass_path (str) – Path to the mass matrix file.
verbose (bool) – Print loading progress, matrix statistics, and memory usage.
symmetry_tol (float) – Symmetry tolerance forwarded to
load_matrix().
- Returns:
K (scipy.sparse.csc_matrix) – Stiffness matrix.
M (scipy.sparse.csc_matrix) – Mass matrix.
- Raises:
FileNotFoundError – If either file does not exist.
ValueError – If K and M have incompatible shapes or are not square.
Examples
>>> K, M = load_matrices("K.mtx", "M.mtx") >>> K.shape == M.shape True
- pyserep.load_matrix(path: str, symmetry_tol: float = 1e-08) csc_matrix[source]
Load a single sparse or dense matrix from disk, returning a CSC matrix.
- Parameters:
- Returns:
The loaded matrix in CSC format.
- Return type:
- Raises:
FileNotFoundError – If the file does not exist.
ValueError – If the format is unrecognised or the file cannot be parsed.
Examples
>>> K = load_matrix("StiffMatrixmm.mtx") >>> K.shape (66525, 66525)
- pyserep.load_metrics(folder: str, prefix: str = 'SEREP') Dict[str, Any][source]
Load a saved metrics JSON file.
- pyserep.load_reduced_matrices(folder: str, prefix: str = 'SEREP') tuple[ndarray, ndarray, ndarray][source]
Load saved Ka, Ma, and T matrices.
- Returns:
Ka, Ma, T
- Return type:
np.ndarray
- pyserep.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.
- pyserep.mass_normalise(phi: ndarray, M: spmatrix) ndarray[source]
Normalise mode shapes so that Φᵢᵀ M Φᵢ = 1 for every column i.
- Parameters:
phi (np.ndarray, shape (N, m))
M (scipy.sparse matrix, shape (N, N))
- Return type:
np.ndarray, shape (N, m) — mass-normalised phi
- pyserep.material_perturbation_study(Ka_nominal: ndarray, Ma_nominal: ndarray, local_force: List[int], local_output: List[int], freq_eval: ndarray, H_nominal: Dict[str, ndarray], param_values: List[float], Ka_func: Callable[[float], ndarray], Ma_func: Callable[[float], ndarray], zeta: float = 0.001, verbose: bool = True) Dict[str, ndarray][source]
Compute FRFs for a sweep of material parameter values.
Useful for comparing Standard vs HyAPA material configurations, or for uncertainty quantification (±5% E, ±3% ρ, etc.).
- Parameters:
Ka_nominal (np.ndarray — baseline reduced matrices)
Ma_nominal (np.ndarray — baseline reduced matrices)
freq_eval (np.ndarray)
H_nominal (dict — baseline FRF (from compute_frf_direct))
param_values (list of float — parameter values to evaluate) – e.g. [0.95, 0.97, 1.00, 1.03, 1.05] for ±5% sweep
Ka_func (callable — Ka_func(p) → np.ndarray(m, m)) – Returns Ka for parameter value p. For stiffness scaling:
lambda p: p * Ka_nominalMa_func (callable — Ma_func(p) → np.ndarray(m, m)) – For mass scaling:
lambda p: p * Ma_nominalzeta (see compute_frf_direct)
verbose (see compute_frf_direct)
- Returns:
"param_values"— np.ndarray of the swept values"H_sweep"— np.ndarray, shape (n_params, n_freq) — FRF magnitudes"max_deviation_pct"— np.ndarray, shape (n_params,)- Return type:
dict with keys
- pyserep.matrix_stats(K: spmatrix, M: spmatrix, label: str = '') str[source]
Return a formatted statistics string for a (K, M) pair.
- pyserep.memory_mb(mat: spmatrix) float[source]
Return memory usage of a sparse matrix in MB (data + indices + indptr).
- pyserep.modal_assurance_criterion(phi_ref: ndarray, phi_rom: ndarray) ndarray[source]
Compute the full MAC matrix between two mode shape sets.
MAC[i,j] = |φᵢᵀ φⱼ|² / (|φᵢ|² |φⱼ|²)
- Parameters:
phi_ref (np.ndarray, shape (N, m))
phi_rom (np.ndarray, shape (N, m))
- Returns:
mac_matrix – Values in [0, 1]. Diagonal near 1 → good mode pairing.
- Return type:
np.ndarray, shape (m, m)
- pyserep.modal_residues(phi: ndarray, force_dofs: list, output_dofs: list, selected_modes: ndarray) ndarray[source]
Compute modal residues Rᵢ = φᵢ(f) · φᵢ(o) for each mode and DOF pair.
- pyserep.modal_strain_energy(phi: ndarray, K: spmatrix, selected_modes: ndarray) ndarray[source]
Compute the modal strain energy (MSE) for each selected mode.
MSE_i = φᵢᵀ K φᵢ (= ωᵢ² for mass-normalised modes)
- Parameters:
phi (np.ndarray, shape (N, n_all_modes))
K (sparse stiffness matrix)
selected_modes (np.ndarray of int)
- Return type:
np.ndarray, shape (len(selected_modes),) — modal strain energies
- pyserep.mode_count_study(K: csc_matrix, M: csc_matrix, phi: ndarray, freqs_hz: ndarray, force_dofs: List[int], output_dofs: List[int], f_max: float, f_max_values: List[float], zeta: float = 0.001, rb_hz: float = 1.0, n_freq: int = 500, verbose: bool = True) ConvergenceStudy[source]
Study FRF convergence as the upper frequency cutoff is varied.
For each value in f_max_values, runs a full SEREP pipeline (mode selection → DOF selection → ROM build → direct FRF) and records accuracy metrics.
- Parameters:
K (sparse matrices)
M (sparse matrices)
phi (modal matrix and frequencies (pre-computed))
freqs_hz (modal matrix and frequencies (pre-computed))
f_max (float) – Upper limit of the reference FRF (Hz).
f_max_values (list of float) – Cutoff frequencies to sweep (Hz). Each defines the upper edge of a single analysis band.
zeta (float)
rb_hz (float)
n_freq (int) – Number of FRF evaluation points.
verbose (bool)
- Return type:
- pyserep.model_info(K: spmatrix, M: spmatrix, label: str = '') str[source]
Return a one-line description of a (K, M) pair.
- pyserep.monte_carlo_frf(Ka_nominal: ndarray, Ma_nominal: ndarray, local_force: List[int], local_output: List[int], freq_eval: ndarray, E_cov_pct: float = 2.0, rho_cov_pct: float = 1.0, n_samples: int = 50, seed: int = 42, zeta: float = 0.001, verbose: bool = True) Dict[str, ndarray][source]
Monte Carlo FRF uncertainty quantification.
Perturbs Ka (proportional to E uncertainty) and Ma (proportional to ρ uncertainty) and computes the FRF for each sample, returning the mean, standard deviation, and 5th/95th percentile bands.
- Parameters:
- Returns:
"H_mean"— mean FRF magnitude"H_std"— standard deviation"H_p5"— 5th percentile"H_p95"— 95th percentile"H_all"— all samples, shape (n_samples, n_freq)- Return type:
dict with keys
- pyserep.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
- pyserep.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.
- pyserep.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.
This catches modes near nodal lines that MS2 might miss.
- pyserep.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.
- pyserep.orthogonality_check(phi: ndarray, M: csc_matrix, selected_modes: ndarray) float[source]
Return
|ΦᵀMΦ − I|_max. Should be < 1e-10 for mass-normalised modes.
- pyserep.plate_2d(nx: int = 10, ny: int = 10, lx: float = 1.0, ly: float = 1.0, D: float = 1000.0, rho_h: float = 1.0) Tuple[csc_matrix, csc_matrix][source]
2D rectangular Kirchhoff plate using finite differences.
Simply supported on all four edges. DOFs: transverse displacement w at interior grid points only. Total DOFs: (nx-1) × (ny-1).
- Parameters:
nx (int) – Number of intervals in x and y (interior nodes = nx-1, ny-1).
ny (int) – Number of intervals in x and y (interior nodes = nx-1, ny-1).
lx (float) – Plate dimensions (m).
ly (float) – Plate dimensions (m).
D (float) – Flexural rigidity D = E h³ / (12(1-ν²)) (N·m).
rho_h (float) – Mass per unit area ρ·h (kg/m²).
- Returns:
K, M
- Return type:
Notes
Uses the 13-point biharmonic finite difference stencil for ∇⁴w.
- pyserep.plot_dof_selector_comparison(comparison: dict, save_path: str | None = None, show: bool = False) None[source]
Bar chart comparing κ values of all four DOF selectors.
- Parameters:
comparison (dict) – Output of
compare_dof_selectors().
- pyserep.plot_frf_comparison(results: PipelineResults, save_path: str | None = None, show: bool = False, dpi: int = 150) None[source]
Four-panel FRF comparison figure.
Panel 1: FRF magnitude (dB re 1 m/N) — ROM vs reference Panel 2: FRF phase (degrees) — ROM vs reference Panel 3: Percentage FRF error (log scale) Panel 4: Eigenvalue preservation error per mode
Gap regions between selective bands are shaded grey.
- Parameters:
results (PipelineResults)
save_path (str, optional)
show (bool)
dpi (int)
- pyserep.plot_frf_overlay(frf_result: FRFResult, title: str = 'FRF Overlay', save_path: str | None = None, show: bool = False) None[source]
Simple FRF magnitude overlay — ROM vs reference, single figure.
Useful for quick inspection without a full PipelineResults object.
- pyserep.plot_mac_matrix(mac_matrix: ndarray, labels: List[str] | None = None, save_path: str | None = None, show: bool = False) None[source]
Plot the MAC matrix as a colour map.
- pyserep.plot_mode_shapes(phi: ndarray, freqs_hz: ndarray, mode_indices: ndarray, master_dofs: ndarray | None = None, n_cols: int = 4, save_path: str | None = None, show: bool = False) None[source]
Plot mode shape amplitudes for the selected modes.
Shows the full-DOF modal amplitude profile for each selected mode. Master DOFs are highlighted with red markers if provided.
- pyserep.plot_performance_dashboard(results: PipelineResults, save_path: str | None = None, show: bool = False) None[source]
6-panel performance dashboard.
Panels: 1. Mode selection cascade (bar chart: how many modes each step passes) 2. Selected mode frequency distribution 3. Eigenvalue error per mode 4. DOF reduction summary (pie chart) 5. Timing breakdown (bar chart) 6. FLOP comparison ROM vs reference
- pyserep.random_symmetric_pd(n: int = 50, kappa_K: float = 10000.0, seed: int = 42) Tuple[csc_matrix, csc_matrix][source]
Random symmetric positive-definite (K, M) pair.
Useful for unit testing and algorithm benchmarks where the structural interpretation is irrelevant.
- Parameters:
- Returns:
K, M
- Return type:
scipy.sparse.csc_matrix (dense matrices wrapped in sparse)
- pyserep.rank_revealing_qr(A: ndarray, tol: float | None = None) Tuple[ndarray, ndarray, ndarray, int][source]
Rank-revealing QR factorisation with column pivoting.
Computes: A P = Q R where P is a permutation.
- Parameters:
- Returns:
Q (np.ndarray, shape (m, m))
R (np.ndarray, shape (m, n))
perm (np.ndarray of int, shape (n,) — column permutation)
rank (int — estimated numerical rank)
Examples
>>> Q, R, perm, r = rank_revealing_qr(phi_a) >>> perm[:r] # top-r most informative DOFs
- pyserep.reduction_metrics(n_full_dofs: int, n_master_dofs: int, n_all_modes: int, n_selected_modes: int) Dict[str, float][source]
Compute reduction ratio metrics.
- Returns:
dof_reduction_ratio,dof_retention_pct,mode_retention_pct,size_ratio- Return type:
dict with keys
- pyserep.reorder_rcm(K: spmatrix, M: spmatrix) Tuple[csc_matrix, csc_matrix, ndarray][source]
Apply Reverse Cuthill-McKee (RCM) reordering to reduce bandwidth.
Reduces the bandwidth of K (and correspondingly M), which can speed up sparse direct solvers used during FRF computation.
- Parameters:
K (sparse matrices (square, symmetric))
M (sparse matrices (square, symmetric))
- Returns:
K_rcm, M_rcm (scipy.sparse.csc_matrix — reordered matrices)
perm (np.ndarray of int — permutation vector (K_rcm = K[perm, :][:, perm]))
Notes
The same permutation must be applied to DOF index arrays (master_dofs, force_dofs, etc.) when using the reordered matrices. Use
perm_inv = np.argsort(perm)to convert global DOF indices back.
- pyserep.safe_pinv(A: ndarray, rcond: float | None = None, verbose: bool = False) ndarray[source]
Compute the Moore–Penrose pseudoinverse with automatic rank truncation.
Drops singular values below
rcond * σ_maxto avoid numerical blow-up from near-zero singular values.
- pyserep.save_results(results: PipelineResults, folder: str, prefix: str = 'SEREP', save_matrices: bool = True, verbose: bool = True) Dict[str, str][source]
Save all pipeline results to folder.
- Parameters:
results (PipelineResults)
folder (str) – Output directory (created if it does not exist).
prefix (str) – Filename prefix for all output files.
save_matrices (bool) – If True, save Ka, Ma, and T matrices. These can be large; set to False to save only indices, FRF, and metrics.
verbose (bool)
- Returns:
Mapping of result type → absolute file path.
- Return type:
- pyserep.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.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:
phi (see
select_dofs_eid())selected_modes (see
select_dofs_eid())n_master (int, optional)
- Return type:
- pyserep.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:
phi (see
select_dofs_eid())selected_modes (see
select_dofs_eid())n_master (int, optional)
- Return type:
- pyserep.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:
phi (see
select_dofs_eid())selected_modes (see
select_dofs_eid())n_master (int, optional)
- Return type:
- pyserep.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
FrequencyBandSetor plainf_min/f_maxscalars 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.
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.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
- pyserep.solve_eigenproblem(K: csc_matrix, M: csc_matrix, n_modes: int, sigma: float = 0.01, tol: float = 1e-10, maxiter: int | None = None, verbose: bool = True) Tuple[ndarray, ndarray][source]
Solve the generalised eigenvalue problem
K φ = λ M φfor the n_modes lowest eigenpairs.- Parameters:
K (scipy.sparse.csc_matrix) – Stiffness matrix (N × N), real symmetric positive semi-definite. Asymmetric K will produce complex eigenvalues and incorrect results.
M (scipy.sparse.csc_matrix) – Mass matrix (N × N), real symmetric positive definite. M must be non-singular; singular M causes ARPACK to fail.
n_modes (int) – Number of eigenpairs to extract. Must be < N − 1.
sigma (float) – Shift parameter (rad²/s²) for shift-invert. A small positive value (e.g. 0.01) ensures rigid-body modes (λ ≈ 0) are captured.
tol (float) – ARPACK convergence tolerance. 1e-10 is appropriate for structural analysis; decrease to 1e-12 for very high accuracy requirements.
maxiter (int, optional) – Maximum ARPACK iterations. Default:
10 * N.verbose (bool) – Print eigenvalue summary.
- Returns:
freqs_hz (np.ndarray, shape (n_modes,)) – Natural frequencies in Hz, sorted ascending.
phi (np.ndarray, shape (N, n_modes)) – Mass-normalised mode shapes.
phi[:, i]corresponds tofreqs_hz[i].
- Raises:
ValueError – If
n_modes >= N.RuntimeWarning – If ARPACK does not fully converge (partial results returned).
Examples
>>> freqs, phi = solve_eigenproblem(K, M, n_modes=100) >>> freqs.shape (100,) >>> phi.shape (66525, 100)
- pyserep.sparse_submatrix(A: spmatrix, row_idx: ndarray, col_idx: ndarray | None = None) csc_matrix[source]
Extract a submatrix A[row_idx, :][:, col_idx] efficiently.
- Parameters:
- Return type:
- pyserep.sparsity(mat: spmatrix) float[source]
Return the fraction of structural zeros (higher = sparser).
- pyserep.spring_chain(n: int = 300, k: float = 100000.0, m: float = 1.0, fixed_left: bool = True, fixed_right: bool = False) Tuple[csc_matrix, csc_matrix][source]
1D spring-mass chain.
- Physical model: o–k–o–k–o … –k–o
m m m m
- Parameters:
- Returns:
K, M
- Return type:
Examples
>>> K, M = spring_chain(n=500, k=2e5) >>> K.shape (500, 500)
- pyserep.summarise_performance(n_full_dofs: int, n_selected_modes: int, n_master_dofs: int, n_all_modes: int, kappa: float, n_freq: int, n_bands: int, n_pairs: int, frf_method: str = 'direct', frf_flops_rom: int | None = None, frf_flops_ref: int | None = None, t_eigensolver_s: float = 0.0, t_mode_select_s: float = 0.0, t_dof_select_s: float = 0.0, t_rom_build_s: float = 0.0, t_frf_s: float = 0.0, t_total_s: float = 0.0) PerformanceMetrics[source]
Collect all performance metrics into a
PerformanceMetrics.Parameters are self-explanatory from the return type documentation.
- pyserep.symmetrise(A)[source]
Enforce symmetry: A ← 0.5 (A + Aᵀ).
Works for both dense
np.ndarrayandscipy.sparsematrices.
- pyserep.unscale_modes(phi_scaled: ndarray, d_inv_sqrt: ndarray) ndarray[source]
Undo diagonal scaling: φ = D⁻½ φ̃ then mass-renormalise.
- pyserep.validate_serep(K: csc_matrix, M: csc_matrix, phi: ndarray, freqs_hz: ndarray, selected_modes: ndarray, master_dofs: ndarray, T: ndarray, Ka: ndarray, Ma: ndarray, verbose: bool = True) ValidationReport[source]
Run the complete SEREP validation suite.
- Parameters:
K (sparse matrices)
M (sparse matrices)
phi (np.ndarray, shape (N, n_modes))
freqs_hz (np.ndarray)
selected_modes (np.ndarray of int)
master_dofs (np.ndarray of int)
T (np.ndarray, shape (N, m) — transformation matrix)
Ka (np.ndarray, shape (m, m) — reduced matrices)
Ma (np.ndarray, shape (m, m) — reduced matrices)
verbose (bool)
- Return type:
- pyserep.verify_eigenvalues(Ka: ndarray, Ma: ndarray, full_freqs_hz: ndarray, selected_modes: ndarray, verbose: bool = True) Tuple[ndarray, float][source]
Verify SEREP’s defining property: exact eigenvalue preservation.
Solves the reduced eigenvalue problem
Kₐ φ = λ Mₐ φand compares the resulting frequencies to the full-model values.- Parameters:
- Returns:
freq_errors_pct (np.ndarray, shape (m,)) – Per-mode percentage error
|f_rom - f_full| / f_full × 100.max_error_pct (float) – Maximum absolute percentage error. Should be < 0.001% for a well-conditioned SEREP.
Examples
>>> errors, max_err = verify_eigenvalues(Ka, Ma, freqs_hz, selected_modes) >>> max_err < 0.001 True
- pyserep.write_ansys_node_list(master_dofs: ndarray, path: str, component_name: str = 'MASTER_NODES', verbose: bool = True) None[source]
Write an Ansys APDL input file that selects the master DOF nodes.
Usage in Ansys: /INPUT, master_nodes.inp
- pyserep.write_master_dofs_csv(master_dofs: ndarray, path: str, node_coords: ndarray | None = None, verbose: bool = True) None[source]
Write master DOF indices to a CSV file.
Columns: dof_index, node_number, direction, [x, y, z]
- Parameters:
master_dofs (np.ndarray of int — 0-based DOF indices)
path (str — output .csv path)
node_coords (np.ndarray, shape (N_nodes, 3), optional) – Node coordinate array (indexed by node_number − 1). If provided, x, y, z columns are written.
verbose (bool)
- pyserep.write_master_dofs_vtk(master_dofs: ndarray, node_coords: ndarray, path: str, scalar_data: ndarray | None = None, scalar_name: str = 'eid_score', verbose: bool = True) None[source]
Write master DOF locations as a VTK unstructured point cloud.
Opens in ParaView, MayaVi, VisIt, etc.
- pyserep.write_uff58_mode_shapes(phi: ndarray, freqs_hz: ndarray, selected_modes: ndarray, master_dofs: ndarray, node_coords: ndarray | None, path: str, zeta: float = 0.001, verbose: bool = True) None[source]
Write mode shapes to a UFF (Universal File Format) dataset 58 file.
Compatible with SDyPy, pyFRF, ME’scopeVES, nModal, and other EMA/OMA post-processors.
- Parameters:
phi (np.ndarray, shape (N, n_all_modes))
freqs_hz (np.ndarray)
selected_modes (np.ndarray of int)
master_dofs (np.ndarray of int — only these DOFs are exported)
node_coords (np.ndarray, shape (N_nodes, 3), optional)
path (str — output .uff path)
zeta (float — damping ratio for all modes)
verbose (bool)