pyserep.utils

pyserep.utils — timing, linear algebra, and sparse matrix utilities.

class pyserep.utils.Timer(label: str = '', verbose: bool = True)[source]

Bases: object

Context-manager wall-clock timer.

pyserep.utils.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.utils.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:
  • K (sparse matrices)

  • M (sparse matrices)

  • fixed_dofs (list of int)

  • penalty (float)

Returns:

K_bc, M_bc

Return type:

scipy.sparse.csc_matrix

pyserep.utils.bandwidth(mat: spmatrix) int[source]

Return the half-bandwidth of a sparse matrix.

pyserep.utils.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.

Parameters:
  • master_dofs (np.ndarray of int — global master DOF indices)

  • force_dofs (list of int — global DOF indices)

  • output_dofs (list of int — global DOF indices)

Returns:

local_force, local_output

Return type:

list of int

Raises:

KeyError – If any force/output DOF is not present in master_dofs.

pyserep.utils.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.utils.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.utils.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.utils.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:
  • A (np.ndarray, shape (n, n))

  • shift_factor (float) – Initial shift as a fraction of |diag(A)|_max.

  • max_iter (int)

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.utils.is_diagonal(mat: spmatrix, tol: float = 1e-12) bool[source]

Return True if mat is effectively diagonal (lumped mass).

pyserep.utils.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.utils.matrix_stats(K: spmatrix, M: spmatrix, label: str = '') str[source]

Return a formatted statistics string for a (K, M) pair.

pyserep.utils.memory_mb(mat: spmatrix) float[source]

Return memory usage of a sparse matrix in MB (data + indices + indptr).

pyserep.utils.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.

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

  • force_dofs (list of int)

  • output_dofs (list of int)

  • selected_modes (np.ndarray of int)

Return type:

np.ndarray, shape (m, n_pairs)

pyserep.utils.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.utils.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:
  • A (np.ndarray, shape (m, n))

  • tol (float, optional) – Rank threshold relative to |R[0,0]|. Default: machine epsilon × max(m,n).

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.utils.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.utils.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 * σ_max to avoid numerical blow-up from near-zero singular values.

Parameters:
  • A (np.ndarray, shape (m, n))

  • rcond (float, optional) – Relative condition threshold. Default: 100 × machine epsilon.

  • verbose (bool)

Return type:

np.ndarray, shape (n, m)

pyserep.utils.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:
  • A (scipy.sparse matrix)

  • row_idx (np.ndarray of int)

  • col_idx (np.ndarray of int, optional — defaults to row_idx (square sub-block))

Return type:

scipy.sparse.csc_matrix

pyserep.utils.sparsity(mat: spmatrix) float[source]

Return the fraction of structural zeros (higher = sparser).

pyserep.utils.symmetrise(A)[source]

Enforce symmetry: A ← 0.5 (A + Aᵀ).

Works for both dense np.ndarray and scipy.sparse matrices.

pyserep.utils.unscale_modes(phi_scaled: ndarray, d_inv_sqrt: ndarray) ndarray[source]

Undo diagonal scaling: φ = D⁻½ φ̃ then mass-renormalise.