Source code for pyserep.visualization.mode_plots

"""
pyserep.visualization.mode_plots
=====================================
Mode shape and DOF selection visualisation tools.
"""

from __future__ import annotations

from typing import List, Optional

import numpy as np


[docs] def plot_mode_shapes( phi: np.ndarray, freqs_hz: np.ndarray, mode_indices: np.ndarray, master_dofs: Optional[np.ndarray] = None, n_cols: int = 4, save_path: Optional[str] = None, show: bool = False, ) -> None: """ 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. Parameters ---------- phi : np.ndarray, shape (N, n_modes) freqs_hz : np.ndarray mode_indices : np.ndarray of int master_dofs : np.ndarray of int, optional n_cols : int save_path, show : optional """ try: import matplotlib.pyplot as plt except ImportError: return m = len(mode_indices) n_rows = (m + n_cols - 1) // n_cols fig, axes = plt.subplots(n_rows, n_cols, figsize=(4 * n_cols, 2.5 * n_rows)) axes = np.array(axes).ravel() for idx, mode_idx in enumerate(mode_indices): ax = axes[idx] amp = np.abs(phi[:, mode_idx]) ax.plot(amp, lw=0.8, color="steelblue", alpha=0.7) if master_dofs is not None: ax.scatter(master_dofs, amp[master_dofs], s=12, c="red", zorder=5, label="Master DOFs") ax.set_title( f"Mode {mode_idx}\n{freqs_hz[mode_idx]:.2f} Hz", fontsize=8, ) ax.set_xlabel("DOF", fontsize=7) ax.set_ylabel("|φ|", fontsize=7) ax.tick_params(labelsize=6) for idx in range(m, len(axes)): axes[idx].set_visible(False) fig.suptitle(f"Selected Mode Shapes ({m} modes)", fontweight="bold") plt.tight_layout() if save_path: fig.savefig(save_path, dpi=120, bbox_inches="tight") print(f"[plot] Mode shapes saved: {save_path}") if show: plt.show() plt.close(fig)
[docs] def plot_mac_matrix( mac_matrix: np.ndarray, labels: Optional[List[str]] = None, save_path: Optional[str] = None, show: bool = False, ) -> None: """ Plot the MAC matrix as a colour map. Parameters ---------- mac_matrix : np.ndarray, shape (m, m) labels : list of str, optional save_path, show : optional """ try: import matplotlib.pyplot as plt except ImportError: return m = mac_matrix.shape[0] fig, ax = plt.subplots(figsize=(max(6, m // 3), max(5, m // 3))) im = ax.imshow(mac_matrix, vmin=0, vmax=1, cmap="RdYlGn", aspect="auto") plt.colorbar(im, ax=ax, label="MAC value") if labels: ax.set_xticks(range(m)) ax.set_yticks(range(m)) ax.set_xticklabels(labels, rotation=90, fontsize=6) ax.set_yticklabels(labels, fontsize=6) ax.set_title("Modal Assurance Criterion (MAC) Matrix") ax.set_xlabel("ROM modes") ax.set_ylabel("Reference modes") plt.tight_layout() if save_path: fig.savefig(save_path, dpi=120, bbox_inches="tight") if show: plt.show() plt.close(fig)
[docs] def plot_dof_selector_comparison( comparison: dict, save_path: Optional[str] = None, show: bool = False, ) -> None: """ Bar chart comparing κ values of all four DOF selectors. Parameters ---------- comparison : dict Output of :func:`~pyserep.selection.dof_selector.compare_dof_selectors`. """ try: import matplotlib.pyplot as plt except ImportError: return methods = list(comparison.keys()) kappas = [comparison[m]["kappa"] for m in methods] colours = ["#e74c3c", "#e67e22", "#f1c40f", "#27ae60"] fig, ax = plt.subplots(figsize=(7, 4)) bars = ax.bar(methods, kappas, color=colours[:len(methods)], edgecolor="black", lw=0.7) ax.set_yscale("log") ax.set_ylabel("Condition number κ(Φₐ) [log scale]") ax.set_title("DOF Selector Comparison — Condition Number κ(Φₐ)") for bar, kappa in zip(bars, kappas): ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() * 1.1, f"{kappa:.1e}", ha="center", va="bottom", fontsize=9) ax.axhline(1e3, color="green", ls="--", lw=1, label="Good (κ < 10³)") ax.axhline(1e6, color="orange", ls="--", lw=1, label="Marginal (κ < 10⁶)") ax.legend(fontsize=8) ax.grid(True, axis="y", alpha=0.3) plt.tight_layout() if save_path: fig.savefig(save_path, dpi=120, bbox_inches="tight") if show: plt.show() plt.close(fig)