Source code for pyoma2.algorithms.plscf

"""
Poly-reference Least Square Frequency Domain (pLSCF) Module.
Part of the pyOMA2 package.
Authors:
Dag Pasca
Diego Margoni
"""

from __future__ import annotations

import logging
import typing

from pyoma2.algorithms.data.result import pLSCFResult
from pyoma2.algorithms.data.run_params import pLSCFRunParams
from pyoma2.functions import fdd, gen, plot, plscf
from pyoma2.support.sel_from_plot import SelFromPlot

from .base import BaseAlgorithm

logger = logging.getLogger(__name__)


# =============================================================================
# SINGLE SETUP
# =============================================================================
[docs]class pLSCF(BaseAlgorithm[pLSCFRunParams, pLSCFResult, typing.Iterable[float]]): """ Implementation of the poly-reference Least Square Complex Frequency (pLSCF) algorithm for modal analysis. This class inherits from `BaseAlgorithm` and specializes in handling modal analysis computations and visualizations based on the pLSCF method. It provides methods to run the analysis, extract modal parameter estimation (mpe), plot stability diagrams, cluster diagrams, mode shapes, and animations of mode shapes. Parameters ---------- BaseAlgorithm : type Inherits from the BaseAlgorithm class with specified type parameters for pLSCFRunParams, pLSCFResult, and Iterable[float]. Attributes ---------- RunParamCls : pLSCFRunParams Class attribute for run parameters specific to pLSCF algorithm. ResultCls : pLSCFResult Class attribute for results specific to pLSCF algorithm. """ RunParamCls = pLSCFRunParams ResultCls = pLSCFResult
[docs] def run(self) -> pLSCFResult: """ Execute the pLSCF algorithm to perform modal analysis on the provided data. This method conducts a frequency domain analysis using the Least Square Complex Frequency method. It computes system matrices, identifies poles, and labels them based on stability and other criteria. Returns ------- pLSCFResult An instance of `pLSCFResult` containing the analysis results, including frequencies, system matrices, identified poles, and their labels. """ Y = self.data.T nxseg = self.run_params.nxseg method = self.run_params.method_SD pov = self.run_params.pov # sgn_basf = self.run_params.sgn_basf ordmax = self.run_params.ordmax ordmin = self.run_params.ordmin sc = self.run_params.sc hc = self.run_params.hc if method == "per": sgn_basf = -1 elif method == "cor": sgn_basf = +1 freq, Sy = fdd.SD_est(Y, Y, self.dt, nxseg, method=method, pov=pov) Ad, Bn = plscf.pLSCF(Sy, self.dt, ordmax, sgn_basf=sgn_basf) Fns, Xis, Phis, Lambds = plscf.pLSCF_poles( Ad, Bn, self.dt, nxseg=nxseg, methodSy=method ) # Apply HARD CRITERIA # hc_conj = hc.get("conj") hc_xi_max = hc["xi_max"] hc_mpc_lim = hc["mpc_lim"] hc_mpd_lim = hc["mpd_lim"] # HC - presence of complex conjugate # if hc_conj: Lambds, mask1 = gen.HC_conj(Lambds) lista = [Fns, Xis, Phis] Fns, Xis, Phis = gen.applymask(lista, mask1, Phis.shape[2]) # HC - damping Xis, mask2 = gen.HC_damp(Xis, hc_xi_max) lista = [Fns, Phis] Fns, Phis = gen.applymask(lista, mask2, Phis.shape[2]) # HC - MPC and MPD if hc_mpc_lim is not None: mask3 = gen.HC_MPC(Phis, hc_mpc_lim) lista = [Fns, Xis, Phis, Lambds] Fns, Xis, Phis, Lambds = gen.applymask(lista, mask3, Phis.shape[2]) if hc_mpd_lim is not None: mask4 = gen.HC_MPD(Phis, hc_mpd_lim) lista = [Fns, Xis, Phis, Lambds] Fns, Xis, Phis, Lambds = gen.applymask(lista, mask4, Phis.shape[2]) # Apply SOFT CRITERIA # Get the labels of the poles Lab = gen.SC_apply( Fns, Xis, Phis, ordmin, ordmax - 1, 1, sc["err_fn"], sc["err_xi"], sc["err_phi"], ) # Return results return self.ResultCls( freq=freq, Sy=Sy, Ad=Ad, Bn=Bn, Fn_poles=Fns, Xi_poles=Xis, Phi_poles=Phis, Lab=Lab, )
[docs] def mpe( self, sel_freq: typing.List[float], order: typing.Union[int, str] = "find_min", rtol: float = 5e-2, ) -> typing.Any: """ Extract the modal parameters at the selected frequencies and order. Parameters ---------- sel_freq : List[float] A list of frequencies for which the modal parameters are to be estimated. order : int or str, optional The order for modal parameter estimation or "find_min". Default is 'find_min'. deltaf : float, optional The frequency range around each selected frequency to consider for estimation. Default is 0.05. rtol : float, optional Relative tolerance for convergence in the iterative estimation process. Default is 1e-2. Returns ------- Any The results of the modal parameter estimation, typically including estimated frequencies, damping ratios, and mode shapes. """ super().mpe(sel_freq=sel_freq, order=order, rtol=rtol) # Save run parameters self.run_params.sel_freq = sel_freq self.run_params.order_in = order self.run_params.rtol = rtol # Get poles Fn_pol = self.result.Fn_poles Sm_pol = self.result.Xi_poles Ms_pol = self.result.Phi_poles Lab = self.result.Lab # Extract modal results Fn_pLSCF, Xi_pLSCF, Phi_pLSCF, order_out = plscf.pLSCF_mpe( sel_freq, Fn_pol, Sm_pol, Ms_pol, order, Lab=Lab, rtol=rtol ) # Save results self.result.order_out = order_out self.result.Fn = Fn_pLSCF self.result.Xi = Xi_pLSCF self.result.Phi = Phi_pLSCF
[docs] def mpe_from_plot( self, freqlim: typing.Optional[tuple[float, float]] = None, rtol: float = 5e-2, ) -> typing.Any: """ Extract the modal parameters directly from the stabilisation chart. Parameters ---------- freqlim : tuple of float, optional A tuple specifying the frequency limits (min, max) for the plot. If None, the limits are determined automatically. Default is None. deltaf : float, optional The frequency range around each selected frequency to consider for estimation. Default is 0.05. rtol : float, optional Relative tolerance for convergence in the iterative estimation process. Default is 1e-2. Returns ------- Any The results of the modal parameter estimation based on user selection from the plot. """ super().mpe_from_plot(freqlim=freqlim, rtol=rtol) # Save run parameters self.run_params.rtol = rtol # Get poles Fn_pol = self.result.Fn_poles Sm_pol = self.result.Xi_poles Ms_pol = self.result.Phi_poles # chiamare plot interattivo SFP = SelFromPlot(algo=self, freqlim=freqlim, plot="pLSCF") sel_freq = SFP.result[0] order = SFP.result[1] # e poi estrarre risultati Fn_pLSCF, Xi_pLSCF, Phi_pLSCF, order_out = plscf.pLSCF_mpe( sel_freq, Fn_pol, Sm_pol, Ms_pol, order, Lab=None, rtol=rtol ) # Save results self.result.order_out = order_out self.result.Fn = Fn_pLSCF self.result.Xi = Xi_pLSCF self.result.Phi = Phi_pLSCF
[docs] def plot_stab( self, freqlim: typing.Optional[tuple[float, float]] = None, hide_poles: typing.Optional[bool] = True, color_scheme: typing.Literal[ "default", "classic", "high_contrast", "viridis" ] = "default", ) -> typing.Any: """ Plot the Stability Diagram for the pLSCF analysis. The stability diagram helps visualize the stability of poles across different model orders. It can be used to identify stable poles, which correspond to physical modes. Parameters ---------- freqlim : tuple of float, optional Frequency limits (min, max) for the stability diagram. If None, limits are determined automatically. Default is None. hide_poles : bool, optional Option to hide the unstable poles in the diagram for clarity. Default is True. color_scheme : typing.Literal["default", "classic", "high_contrast", "viridis"], optional Color scheme for stable/unstable poles. Options: 'default', 'classic', 'high_contrast', 'viridis'. Returns ------- Any A tuple containing the matplotlib figure and axes objects for the stability diagram. """ fig, ax = plot.stab_plot( Fn=self.result.Fn_poles, Lab=self.result.Lab, step=1, ordmax=self.run_params.ordmax, ordmin=self.run_params.ordmin, freqlim=freqlim, hide_poles=hide_poles, fig=None, ax=None, color_scheme=color_scheme, ) return fig, ax
[docs] def plot_freqvsdamp( self, freqlim: typing.Optional[tuple[float, float]] = None, hide_poles: typing.Optional[bool] = True, color_scheme: typing.Literal[ "default", "classic", "high_contrast", "viridis" ] = "default", ) -> typing.Any: """ Plot the frequency-damping cluster diagram for the identified modal parameters. The cluster diagram visualizes the distribution of identified modal frequencies and their corresponding damping ratios. It helps identify clusters of stable modes. Parameters ---------- freqlim : tuple of float, optional Frequency limits for the plot. If None, limits are determined automatically. Default is None. hide_poles : bool, optional Option to hide poles in the plot for clarity. Default is True. color_scheme : typing.Literal["default", "classic", "high_contrast", "viridis"], optional Color scheme for stable/unstable poles. Options: 'default', 'classic', 'high_contrast', 'viridis'. Returns ------- typing.Any A tuple containing the matplotlib figure and axes of the cluster diagram plot. """ if not self.result: raise ValueError("Run algorithm first") fig, ax = plot.cluster_plot( Fn=self.result.Fn_poles, Xi=self.result.Xi_poles, Lab=self.result.Lab, ordmin=self.run_params.ordmin, freqlim=freqlim, hide_poles=hide_poles, color_scheme=color_scheme, ) return fig, ax
# ============================================================================= # MULTI SETUP # =============================================================================
[docs]class pLSCF_MS(pLSCF[pLSCFRunParams, pLSCFResult, typing.Iterable[dict]]): """ A multi-setup extension of the pLSCF class for the poly-reference Least Square Complex Frequency (pLSCF) algorithm. Parameters ---------- pLSCF : type Inherits from the pLSCF class with specified type parameters for pLSCFRunParams, pLSCFResult, and Iterable[dict]. Attributes ---------- RunParamCls : pLSCFRunParams Class attribute for run parameters specific to pLSCF algorithm. ResultCls : pLSCFResult Class attribute for results specific to pLSCF algorithm. """ RunParamCls = pLSCFRunParams ResultCls = pLSCFResult
[docs] def run(self) -> pLSCFResult: """ Execute the pLSCF algorithm to perform modal analysis on the provided data. This method conducts a frequency domain analysis using the Least Square Complex Frequency method. It computes system matrices, identifies poles, and labels them based on stability and other criteria. Returns ------- pLSCFResult An instance of `pLSCFResult` containing the analysis results, including frequencies, system matrices, identified poles, and their labels. """ Y = self.data nxseg = self.run_params.nxseg method = self.run_params.method_SD pov = self.run_params.pov # sgn_basf = self.run_params.sgn_basf # step = self.run_params.step ordmax = self.run_params.ordmax ordmin = self.run_params.ordmin sc = self.run_params.sc hc = self.run_params.hc if method == "per": sgn_basf = -1 elif method == "cor": sgn_basf = +1 freq, Sy = fdd.SD_PreGER(Y, self.fs, nxseg=nxseg, method=method, pov=pov) Ad, Bn = plscf.pLSCF(Sy, self.dt, ordmax, sgn_basf=sgn_basf) Fns, Xis, Phis, Lambds = plscf.pLSCF_poles( Ad, Bn, self.dt, nxseg=nxseg, methodSy=method ) # Apply HARD CRITERIA hc_xi_max = hc["xi_max"] hc_mpc_lim = hc["mpc_lim"] hc_mpd_lim = hc["mpd_lim"] # HC - presence of complex conjugate # if hc_conj: Lambds, mask1 = gen.HC_conj(Lambds) lista = [Fns, Xis, Phis] Fns, Xis, Phis = gen.applymask(lista, mask1, Phis.shape[2]) # HC - damping Xis, mask2 = gen.HC_damp(Xis, hc_xi_max) lista = [Fns, Phis] Fns, Phis = gen.applymask(lista, mask2, Phis.shape[2]) # HC - MPC and MPD if hc_mpc_lim is not None: mask3 = gen.HC_MPC(Phis, hc_mpc_lim) lista = [Fns, Xis, Phis, Lambds] Fns, Xis, Phis, Lambds = gen.applymask(lista, mask3, Phis.shape[2]) if hc_mpd_lim is not None: mask4 = gen.HC_MPD(Phis, hc_mpd_lim) lista = [Fns, Xis, Phis, Lambds] Fns, Xis, Phis, Lambds = gen.applymask(lista, mask4, Phis.shape[2]) # Apply SOFT CRITERIA # Get the labels of the poles Lab = gen.SC_apply( Fns, Xis, Phis, ordmin, ordmax - 1, 1, sc["err_fn"], sc["err_xi"], sc["err_phi"], ) # Return results return self.ResultCls( freq=freq, Sy=Sy, Ad=Ad, Bn=Bn, Fn_poles=Fns, Xi_poles=Xis, Phi_poles=Phis, Lab=Lab, )