主题063:结构动力学模态参数识别

摘要

本主题深入探讨结构动力学中的模态参数识别理论与方法。模态参数识别是结构健康监测、振动控制和有限元模型验证的核心技术。本主题系统介绍频域识别方法(峰值拾取法、频域分解法)、时域识别方法(ITD法、STD法、ERA法)、随机子空间识别方法(SSI)以及基于机器学习的智能识别方法。通过四个完整的工程案例,详细演示如何从实测振动数据中提取结构的固有频率、阻尼比和模态振型,为工程结构的动态特性分析和健康评估提供理论基础和实用工具。

关键词

模态参数识别;频域分解;随机子空间;工作模态分析;模态振型;阻尼比估计;机器学习;结构健康监测


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 理论基础

1.1 模态参数识别的基本概念

模态参数识别是通过分析结构在激励下的动力响应,提取结构的模态参数(固有频率、阻尼比、模态振型)的过程。这些参数完整地描述了结构的动态特性。

1.1.1 模态参数的定义

对于N自由度线性系统,运动方程为:

Mx¨+Cx˙+Kx=f(t)M\ddot{x} + C\dot{x} + Kx = f(t)Mx¨+Cx˙+Kx=f(t)

其中,MMMCCCKKK分别为质量、阻尼和刚度矩阵。通过模态变换,可得到模态坐标下的解耦方程:

q¨r+2ζrωrq˙r+ωr2qr=ϕrTf(t)mr\ddot{q}_r + 2\zeta_r\omega_r\dot{q}_r + \omega_r^2q_r = \frac{\phi_r^Tf(t)}{m_r}q¨r+2ζrωrq˙r+ωr2qr=mrϕrTf(t)

其中:

  • ωr\omega_rωr:第r阶固有频率(rad/s)
  • ζr\zeta_rζr:第r阶模态阻尼比
  • ϕr\phi_rϕr:第r阶模态振型
  • mrm_rmr:第r阶模态质量
1.1.2 频响函数与模态参数的关系

频响函数(FRF)是模态参数识别的基础。对于比例阻尼系统,频响函数可表示为:

Hij(ω)=∑r=1Nϕirϕjrmr(ωr2−ω2+2iζrωrω)H_{ij}(\omega) = \sum_{r=1}^{N}\frac{\phi_{ir}\phi_{jr}}{m_r(\omega_r^2 - \omega^2 + 2i\zeta_r\omega_r\omega)}Hij(ω)=r=1Nmr(ωr2ω2+2iζrωrω)ϕirϕjr

在共振频率附近,第r阶模态占主导:

Hij(ω)≈ϕirϕjrmr(ωr2−ω2+2iζrωrω)H_{ij}(\omega) \approx \frac{\phi_{ir}\phi_{jr}}{m_r(\omega_r^2 - \omega^2 + 2i\zeta_r\omega_r\omega)}Hij(ω)mr(ωr2ω2+2iζrωrω)ϕirϕjr

1.1.3 模态参数识别的分类

根据识别域的不同,模态参数识别方法可分为:

  1. 频域方法:基于频响函数或功率谱密度

    • 峰值拾取法(Peak Picking)
    • 频域分解法(FDD)
    • 多参考最小二乘复频域法(p-LSCF)
  2. 时域方法:基于脉冲响应函数或自由衰减响应

    • 特征系统实现算法(ERA)
    • 多参考 Ibrahim 时域法(ITD)
    • 特征值实现算法(ERA)
  3. 时频域方法:结合时域和频域信息

    • 小波变换法
    • Hilbert-Huang变换
  4. 随机子空间方法:基于随机过程理论

    • 数据驱动随机子空间(SSI-data)
    • 协方差驱动随机子空间(SSI-cov)
  5. 机器学习方法:基于数据驱动

    • 神经网络方法
    • 支持向量机
    • 深度学习

1.2 频域识别方法

1.2.1 峰值拾取法

峰值拾取法是最简单的频域识别方法,基于频响函数幅值的峰值确定固有频率。

基本原理
在共振频率处,频响函数的幅值达到极大值:

∣Hij(ωr)∣=∣ϕirϕjr∣2mrζrωr2|H_{ij}(\omega_r)| = \frac{|\phi_{ir}\phi_{jr}|}{2m_r\zeta_r\omega_r^2}Hij(ωr)=2mrζrωr2ϕirϕjr

半功率带宽法估计阻尼

ζr=ωb−ωa2ωr\zeta_r = \frac{\omega_b - \omega_a}{2\omega_r}ζr=2ωrωbωa

其中ωa\omega_aωaωb\omega_bωb是半功率点(幅值为峰值1/21/\sqrt{2}1/2 处的频率)。

1.2.2 频域分解法(FDD)

频域分解法利用输出谱密度的奇异值分解提取模态参数。

算法步骤

  1. 计算输出谱密度矩阵:
    Gyy(ω)=H(ω)Gff(ω)HH(ω)G_{yy}(\omega) = H(\omega)G_{ff}(\omega)H^H(\omega)Gyy(ω)=H(ω)Gff(ω)HH(ω)

  2. 对每个频率进行SVD分解:
    Gyy(ω)=U(ω)Σ(ω)VH(ω)G_{yy}(\omega) = U(\omega)\Sigma(\omega)V^H(\omega)Gyy(ω)=U(ω)Σ(ω)VH(ω)

  3. 在共振频率处,第一奇异值对应主导模态:
    σ1(ωr)≫σ2(ωr)\sigma_1(\omega_r) \gg \sigma_2(\omega_r)σ1(ωr)σ2(ωr)

  4. 模态振型从奇异向量提取:
    ϕr≈u1(ωr)\phi_r \approx u_1(\omega_r)ϕru1(ωr)

1.2.3 多参考最小二乘复频域法(p-LSCF)

p-LSCF方法通过有理多项式拟合频响函数:

Hij(s)=∑k=0nbksk∑k=0naksk,s=iωH_{ij}(s) = \frac{\sum_{k=0}^{n}b_ks^k}{\sum_{k=0}^{n}a_ks^k}, \quad s = i\omegaHij(s)=k=0nakskk=0nbksk,s=

通过最小二乘优化确定系数aka_kakbkb_kbk,然后求解分母多项式的根得到极点:

sr=−ζrωr±iωr1−ζr2s_r = -\zeta_r\omega_r \pm i\omega_r\sqrt{1-\zeta_r^2}sr=ζrωr±iωr1ζr2

1.3 时域识别方法

1.3.1 特征系统实现算法(ERA)

ERA方法基于系统的脉冲响应函数,通过Hankel矩阵的奇异值分解提取模态参数。

算法步骤

  1. 构建Hankel矩阵:
    H(k)=[h(k)h(k+1)⋯h(k+β−1)h(k+1)h(k+2)⋯h(k+β)⋮⋮⋱⋮h(k+α−1)h(k+α)⋯h(k+α+β−2)]H(k) = \begin{bmatrix} h(k) & h(k+1) & \cdots & h(k+\beta-1) \\ h(k+1) & h(k+2) & \cdots & h(k+\beta) \\ \vdots & \vdots & \ddots & \vdots \\ h(k+\alpha-1) & h(k+\alpha) & \cdots & h(k+\alpha+\beta-2) \end{bmatrix}H(k)= h(k)h(k+1)h(k+α1)h(k+1)h(k+2)h(k+α)h(k+β1)h(k+β)h(k+α+β2)

  2. H(0)H(0)H(0)进行SVD分解:
    H(0)=UΣVTH(0) = U\Sigma V^TH(0)=UΣVT

  3. 确定系统阶数,保留前n个奇异值:
    H(0)≈UnΣnVnTH(0) \approx U_n\Sigma_nV_n^TH(0)UnΣnVnT

  4. 构建系统矩阵:
    A=Σn−1/2UnTH(1)VnΣn−1/2A = \Sigma_n^{-1/2}U_n^TH(1)V_n\Sigma_n^{-1/2}A=Σn1/2UnTH(1)VnΣn1/2

  5. 对A进行特征值分解:
    Aψr=λrψrA\psi_r = \lambda_r\psi_rAψr=λrψr

  6. 提取模态参数:
    ωr=∣ln⁡(λr)∣/Δt\omega_r = |\ln(\lambda_r)|/\Delta tωr=ln(λr)∣/Δt
    ζr=−Re(ln⁡(λr))/∣ln⁡(λr)∣\zeta_r = -\text{Re}(\ln(\lambda_r))/|\ln(\lambda_r)|ζr=Re(ln(λr))/∣ln(λr)

1.3.2 Ibrahim时域法(ITD)

ITD方法利用自由响应数据构建特征值问题。

基本原理

对于自由振动响应:

x(t)=∑r=1Nϕre−ζrωrtsin⁡(ωdrt+θr)x(t) = \sum_{r=1}^{N}\phi_re^{-\zeta_r\omega_rt}\sin(\omega_{dr}t + \theta_r)x(t)=r=1Nϕreζrωrtsin(ωdrt+θr)

通过构造响应矩阵和延迟响应矩阵,形成广义特征值问题:

AΦ=BΦΛA\Phi = B\Phi\LambdaAΦ=BΦΛ

其中Λ\LambdaΛ包含系统的特征值,从中可提取模态参数。

1.4 随机子空间识别方法

1.4.1 随机状态空间模型

对于随机激励下的线性系统,状态空间方程为:

xk+1=Axk+wkx_{k+1} = Ax_k + w_kxk+1=Axk+wk
yk=Cxk+vky_k = Cx_k + v_kyk=Cxk+vk

其中:

  • xkx_kxk:状态向量
  • yky_kyk:观测向量
  • wkw_kwk:过程噪声
  • vkv_kvk:测量噪声
1.4.2 协方差驱动随机子空间(SSI-cov)

算法步骤

  1. 计算输出协方差矩阵:
    Ri=E[yk+iykT]R_i = E[y_{k+i}y_k^T]Ri=E[yk+iykT]

  2. 构建块Toeplitz矩阵:
    T1∣i=[RiRi−1⋯R1Ri+1Ri⋯R2⋮⋮⋱⋮R2i−1R2i−2⋯Ri]T_{1|i} = \begin{bmatrix} R_i & R_{i-1} & \cdots & R_1 \\ R_{i+1} & R_i & \cdots & R_2 \\ \vdots & \vdots & \ddots & \vdots \\ R_{2i-1} & R_{2i-2} & \cdots & R_i \end{bmatrix}T1∣i= RiRi+1R2i1Ri1RiR2i2R1R2Ri

  3. 对Toeplitz矩阵进行SVD分解:
    T1∣i=UΣVTT_{1|i} = U\Sigma V^TT1∣i=UΣVT

  4. 确定系统阶数,提取观测矩阵和状态矩阵:
    Oi=UnΣn1/2O_i = U_n\Sigma_n^{1/2}Oi=UnΣn1/2
    Γi=Σn1/2VnT\Gamma_i = \Sigma_n^{1/2}V_n^TΓi=Σn1/2VnT

  5. 从观测矩阵提取C,从状态矩阵提取A

  6. 对A进行特征值分解,提取模态参数

1.4.3 数据驱动随机子空间(SSI-data)

SSI-data方法直接使用测量数据,无需计算协方差。

算法步骤

  1. 构建数据矩阵:
    Y0∣i−1=[y0y1⋯yj−1y1y2⋯yj⋮⋮⋱⋮yi−1yi⋯yi+j−2]Y_{0|i-1} = \begin{bmatrix} y_0 & y_1 & \cdots & y_{j-1} \\ y_1 & y_2 & \cdots & y_j \\ \vdots & \vdots & \ddots & \vdots \\ y_{i-1} & y_i & \cdots & y_{i+j-2} \end{bmatrix}Y0∣i1= y0y1yi1y1y2yiyj1yjyi+j2

  2. 对数据矩阵进行QR分解或SVD分解

  3. 提取扩展观测矩阵

  4. 计算系统矩阵A和C

  5. 特征值分解提取模态参数

1.5 稳定图与模态定阶

1.5.1 稳定图的概念

稳定图是确定系统真实模态阶数的重要工具。通过逐渐增加模型阶数,观察模态参数的稳定性。

稳定准则

  • 频率稳定:∣f(n+1)−f(n)f(n)∣<ϵf|\frac{f^{(n+1)} - f^{(n)}}{f^{(n)}}| < \epsilon_ff(n)f(n+1)f(n)<ϵf
  • 阻尼稳定:∣ζ(n+1)−ζ(n)ζ(n)∣<ϵζ|\frac{\zeta^{(n+1)} - \zeta^{(n)}}{\zeta^{(n)}}| < \epsilon_\zetaζ(n)ζ(n+1)ζ(n)<ϵζ
  • MAC稳定:MAC(ϕ(n+1),ϕ(n))>ϵMACMAC(\phi^{(n+1)}, \phi^{(n)}) > \epsilon_{MAC}MAC(ϕ(n+1),ϕ(n))>ϵMAC
1.5.2 模态定阶方法
  1. 奇异值跳跃法:观察奇异值曲线的拐点
  2. 稳定图法:识别稳定的模态
  3. 信息准则:AIC、MDL等统计准则
  4. 交叉验证:预测误差最小化

1.6 模态验证指标

1.6.1 模态置信准则(MAC)

MAC用于评估两个模态振型的相关性:

MACij=∣ϕiHϕj∣2(ϕiHϕi)(ϕjHϕj)MAC_{ij} = \frac{|\phi_i^H\phi_j|^2}{(\phi_i^H\phi_i)(\phi_j^H\phi_j)}MACij=(ϕiHϕi)(ϕjHϕj)ϕiHϕj2

  • MAC=1MAC = 1MAC=1:完全相关
  • MAC=0MAC = 0MAC=0:完全无关
  • MAC>0.9MAC > 0.9MAC>0.9:认为同一模态
1.6.2 模态参与因子

模态参与因子表示各模态对响应的贡献:

MPFr=ϕrTM1mrMPF_r = \frac{\phi_r^TM\mathbf{1}}{m_r}MPFr=mrϕrTM1

1.6.3 模态复杂性

模态复杂性指标评估模态振型的实模态特性:

MCI=1−∣∑ϕir2∣2(∑∣ϕir∣2)2MCI = 1 - \frac{|\sum\phi_{ir}^2|^2}{(\sum|\phi_{ir}|^2)^2}MCI=1(ϕir2)2ϕir22

  • MCI=0MCI = 0MCI=0:实模态
  • MCI>0.2MCI > 0.2MCI>0.2:复模态

1.7 机器学习方法在模态识别中的应用

1.7.1 基于神经网络的模态识别

基本原理
利用神经网络的非线性映射能力,从振动数据直接学习模态参数。

网络结构

  • 输入层:振动信号特征(频谱、小波系数等)
  • 隐藏层:多个全连接层或卷积层
  • 输出层:模态参数(频率、阻尼、振型)

损失函数
L=αLfreq+βLdamping+γLmodeL = \alpha L_{freq} + \beta L_{damping} + \gamma L_{mode}L=αLfreq+βLdamping+γLmode

1.7.2 卷积神经网络(CNN)

CNN可用于从时频图像中提取模态特征。

网络架构

输入(时频图)→ 卷积层 → 池化层 → 卷积层 → 全连接层 → 输出(模态参数)
1.7.3 自编码器与特征提取

自编码器可用于降维和特征提取:

min⁡∣∣x−x^∣∣2\min ||x - \hat{x}||^2min∣∣xx^2

其中x^=D(E(x))\hat{x} = D(E(x))x^=D(E(x))EEE为编码器,DDD为解码器。


2. 数值方法

2.1 频响函数估计

2.1.1 H1估计

H^1(f)=Gxy(f)Gxx(f)\hat{H}_1(f) = \frac{G_{xy}(f)}{G_{xx}(f)}H^1(f)=Gxx(f)Gxy(f)

适用于输出噪声占主导的情况。

2.1.2 H2估计

H^2(f)=Gyy(f)Gyx(f)\hat{H}_2(f) = \frac{G_{yy}(f)}{G_{yx}(f)}H^2(f)=Gyx(f)Gyy(f)

适用于输入噪声占主导的情况。

2.1.3 Hv估计(总最小二乘)

H^v(f)=H^1(f)H^2(f)\hat{H}_v(f) = \sqrt{\hat{H}_1(f)\hat{H}_2(f)}H^v(f)=H^1(f)H^2(f)

2.2 谱估计方法

2.2.1 周期图法

G^xx(f)=1N∣X(f)∣2\hat{G}_{xx}(f) = \frac{1}{N}|X(f)|^2G^xx(f)=N1X(f)2

2.2.2 Welch方法

将数据分段,计算平均周期图:

G^xx(f)=1M∑i=1MG^xx(i)(f)\hat{G}_{xx}(f) = \frac{1}{M}\sum_{i=1}^{M}\hat{G}_{xx}^{(i)}(f)G^xx(f)=M1i=1MG^xx(i)(f)

2.2.3 多窗法(MTM)

使用多个正交窗函数减少估计方差。

2.3 信号预处理

2.3.1 去趋势

去除信号的线性趋势:

y(t)=x(t)−(a+bt)y(t) = x(t) - (a + bt)y(t)=x(t)(a+bt)

2.3.2 滤波

带通滤波保留感兴趣的频段:

H(f)={1f1≤f≤f20其他H(f) = \begin{cases} 1 & f_1 \leq f \leq f_2 \\ 0 & \text{其他} \end{cases}H(f)={10f1ff2其他

2.3.3 重采样

根据奈奎斯特准则确定采样频率:

fs>2fmaxf_s > 2f_{max}fs>2fmax


3. 工程案例

3.1 案例1:基于频域的模态参数识别

问题描述:
对一个简支梁进行锤击试验,利用频响函数数据,采用峰值拾取法和频域分解法识别结构的模态参数。

Python代码实现:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
from scipy.linalg import eigh, svd
from scipy.signal import find_peaks, welch
from scipy.fft import fft, ifft, fftfreq

class FrequencyDomainIdentification:
    """频域模态参数识别"""
    
    def __init__(self, fs=1000):
        """
        初始化
        
        Parameters:
        -----------
        fs : float
            采样频率 (Hz)
        """
        self.fs = fs
        self.dt = 1.0 / fs
    
    def compute_frf(self, force, response, window='hann', nperseg=None):
        """
        计算频响函数 (H1估计)
        
        Parameters:
        -----------
        force : array
            力信号
        response : array
            响应信号
        window : str
            窗函数类型
        nperseg : int
            每段长度
        
        Returns:
        --------
        f : array
            频率向量
        H : array
            频响函数
        coherence : array
            相干函数
        """
        if nperseg is None:
            nperseg = min(256, len(force) // 4)
        
        # 计算互功率谱和自功率谱
        f, Gxf = welch(force, fs=self.fs, window=window, 
                       nperseg=nperseg, noverlap=nperseg//2, 
                       detrend='constant', return_onesided=True)
        
        _, Gyx = welch(response, force, fs=self.fs, window=window,
                       nperseg=nperseg, noverlap=nperseg//2,
                       detrend='constant', return_onesided=True)
        
        _, Gxx = welch(force, fs=self.fs, window=window,
                       nperseg=nperseg, noverlap=nperseg//2,
                       detrend='constant', return_onesided=True)
        
        _, Gyy = welch(response, fs=self.fs, window=window,
                       nperseg=nperseg, noverlap=nperseg//2,
                       detrend='constant', return_onesided=True)
        
        # H1估计
        H = Gyx / (Gxx + 1e-10)
        
        # 相干函数
        coherence = np.abs(Gyx)**2 / (Gxx * Gyy + 1e-10)
        
        return f, H, coherence
    
    def peak_picking(self, f, H, peak_height=None, min_distance=5):
        """
        峰值拾取法识别模态参数
        
        Parameters:
        -----------
        f : array
            频率向量
        H : array
            频响函数
        peak_height : float
            峰值高度阈值
        min_distance : int
            峰值间最小距离
        
        Returns:
        --------
        modes : list
            识别的模态参数 [(freq, damping, mode_shape), ...]
        """
        # 计算幅值
        magnitude = np.abs(H)
        
        if peak_height is None:
            peak_height = np.max(magnitude) * 0.1
        
        # 寻找峰值
        peaks, properties = find_peaks(magnitude, height=peak_height, 
                                       distance=min_distance)
        
        modes = []
        
        for peak_idx in peaks:
            freq = f[peak_idx]
            
            # 半功率带宽法估计阻尼
            peak_mag = magnitude[peak_idx]
            half_power = peak_mag / np.sqrt(2)
            
            # 寻找半功率点
            left_idx = peak_idx
            right_idx = peak_idx
            
            while left_idx > 0 and magnitude[left_idx] > half_power:
                left_idx -= 1
            
            while right_idx < len(magnitude) - 1 and magnitude[right_idx] > half_power:
                right_idx += 1
            
            # 计算阻尼比
            if left_idx < peak_idx and right_idx > peak_idx:
                f_left = f[left_idx]
                f_right = f[right_idx]
                damping = (f_right - f_left) / (2 * freq)
            else:
                damping = 0.01  # 默认值
            
            # 模态振型(简化,实际应从多测点数据提取)
            mode_shape = np.array([1.0])  # 单点测量
            
            modes.append({
                'frequency': freq,
                'damping': damping,
                'mode_shape': mode_shape,
                'peak_idx': peak_idx
            })
        
        return modes
    
    def frequency_domain_decomposition(self, f, H_matrix):
        """
        频域分解法 (FDD)
        
        Parameters:
        -----------
        f : array
            频率向量
        H_matrix : array
            多参考频响函数矩阵 (n_freq x n_dof x n_ref)
        
        Returns:
        --------
        modes : list
            识别的模态参数
        singular_values : array
            奇异值
        """
        n_freq = len(f)
        n_dof = H_matrix.shape[1]
        
        # 计算谱密度矩阵
        Gyy = np.zeros((n_freq, n_dof, n_dof), dtype=complex)
        
        for i in range(n_freq):
            H_i = H_matrix[i, :, :]
            Gyy[i, :, :] = H_i @ H_i.conj().T
        
        # SVD分解
        singular_values = np.zeros((n_freq, n_dof))
        mode_shapes = []
        
        for i in range(n_freq):
            U, S, Vh = svd(Gyy[i, :, :])
            singular_values[i, :] = S
            mode_shapes.append(U[:, 0])  # 第一奇异向量
        
        # 从奇异值峰值识别模态
        modes = []
        
        for mode_idx in range(min(3, n_dof)):  # 识别前3阶
            sv = singular_values[:, mode_idx]
            
            # 寻找峰值
            peaks, _ = find_peaks(sv, height=np.max(sv)*0.1, distance=10)
            
            for peak_idx in peaks:
                freq = f[peak_idx]
                
                # 半功率带宽法
                peak_sv = sv[peak_idx]
                half_power = peak_sv / np.sqrt(2)
                
                left_idx = peak_idx
                right_idx = peak_idx
                
                while left_idx > 0 and sv[left_idx] > half_power:
                    left_idx -= 1
                
                while right_idx < len(sv) - 1 and sv[right_idx] > half_power:
                    right_idx += 1
                
                if left_idx < peak_idx and right_idx > peak_idx:
                    damping = (f[right_idx] - f[left_idx]) / (2 * freq)
                else:
                    damping = 0.01
                
                modes.append({
                    'frequency': freq,
                    'damping': damping,
                    'mode_shape': mode_shapes[peak_idx],
                    'sv_peak': peak_sv
                })
        
        return modes, singular_values

def simulate_impact_test(n_dof=5, fs=1000, duration=2.0):
    """
    模拟锤击试验数据
    
    Parameters:
    -----------
    n_dof : int
        自由度数量(测点数)
    fs : float
        采样频率
    duration : float
        信号持续时间
    
    Returns:
    --------
    t : array
        时间向量
    force : array
        冲击力信号
    responses : array
        响应信号 (n_dof x n_samples)
    true_params : dict
        真实模态参数
    """
    np.random.seed(42)
    
    dt = 1.0 / fs
    t = np.arange(0, duration, dt)
    n_samples = len(t)
    
    # 真实模态参数
    true_freqs = np.array([10.5, 42.3, 95.8])  # Hz
    true_dampings = np.array([0.02, 0.015, 0.01])
    
    # 模态振型(简支梁)
    L = 1.0
    x_pos = np.linspace(0, L, n_dof)
    
    true_modes = np.zeros((n_dof, len(true_freqs)))
    for i, freq in enumerate(true_freqs):
        mode_num = i + 1
        true_modes[:, i] = np.sin(mode_num * np.pi * x_pos / L)
    
    # 归一化
    for i in range(len(true_freqs)):
        true_modes[:, i] /= np.max(np.abs(true_modes[:, i]))
    
    # 生成冲击力(半正弦脉冲)
    pulse_duration = 0.01  # 10ms
    force = np.zeros(n_samples)
    pulse_samples = int(pulse_duration * fs)
    
    if pulse_samples > 0:
        pulse_t = np.linspace(0, np.pi, pulse_samples)
        force[:pulse_samples] = 1000 * np.sin(pulse_t)
    
    # 计算响应(模态叠加)
    responses = np.zeros((n_dof, n_samples))
    
    for i in range(len(true_freqs)):
        omega = 2 * np.pi * true_freqs[i]
        zeta = true_dampings[i]
        omega_d = omega * np.sqrt(1 - zeta**2)
        
        # 单自由度响应
        h = np.zeros(n_samples)
        for j in range(1, n_samples):
            tau = j * dt
            h[j] = (1 / omega_d) * np.exp(-zeta * omega * tau) * np.sin(omega_d * tau)
        
        # 卷积计算响应
        for dof in range(n_dof):
            modal_force = true_modes[dof, i] * force
            resp = np.convolve(modal_force, h, mode='full')[:n_samples]
            responses[dof, :] += resp
    
    # 添加噪声
    noise_level = 0.05
    responses += noise_level * np.std(responses) * np.random.randn(n_dof, n_samples)
    
    true_params = {
        'frequencies': true_freqs,
        'dampings': true_dampings,
        'mode_shapes': true_modes
    }
    
    return t, force, responses, true_params

def case1_frequency_domain():
    """案例1:基于频域的模态参数识别"""
    print("="*60)
    print("主题063 - 案例1: 基于频域的模态参数识别")
    print("="*60)
    
    # 模拟锤击试验数据
    n_dof = 5
    fs = 1000
    t, force, responses, true_params = simulate_impact_test(n_dof, fs)
    
    print("\n真实模态参数:")
    for i, (f, zeta) in enumerate(zip(true_params['frequencies'], true_params['dampings'])):
        print(f"  第{i+1}阶: 频率 = {f:.2f} Hz, 阻尼比 = {zeta:.3f}")
    
    # 创建识别器
    identifier = FrequencyDomainIdentification(fs)
    
    # 方法1:峰值拾取法(使用第一个测点)
    print("\n方法1: 峰值拾取法")
    f, H, coherence = identifier.compute_frf(force, responses[0, :])
    
    modes_pp = identifier.peak_picking(f, H, peak_height=np.max(np.abs(H))*0.05)
    
    print(f"识别到 {len(modes_pp)} 个模态:")
    for i, mode in enumerate(modes_pp[:3]):
        print(f"  第{i+1}阶: 频率 = {mode['frequency']:.2f} Hz, "
              f"阻尼比 = {mode['damping']:.4f}")
    
    # 方法2:频域分解法
    print("\n方法2: 频域分解法 (FDD)")
    
    # 构建多参考频响函数矩阵
    H_matrix = np.zeros((len(f), n_dof, 1), dtype=complex)
    for dof in range(n_dof):
        _, H_dof, _ = identifier.compute_frf(force, responses[dof, :])
        H_matrix[:, dof, 0] = H_dof
    
    modes_fdd, singular_values = identifier.frequency_domain_decomposition(f, H_matrix)
    
    print(f"识别到 {len(modes_fdd)} 个模态:")
    for i, mode in enumerate(modes_fdd[:3]):
        print(f"  第{i+1}阶: 频率 = {mode['frequency']:.2f} Hz, "
              f"阻尼比 = {mode['damping']:.4f}")
    
    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 1. 力信号和响应
    ax = axes[0, 0]
    ax.plot(t[:500], force[:500], 'b-', linewidth=1.5, label='冲击力')
    ax.set_xlabel('时间 (s)')
    ax.set_ylabel('力 (N)')
    ax.set_title('锤击力信号')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 2. 响应信号
    ax = axes[0, 1]
    for i in range(min(3, n_dof)):
        ax.plot(t[:1000], responses[i, :1000], linewidth=1, label=f'测点{i+1}')
    ax.set_xlabel('时间 (s)')
    ax.set_ylabel('位移 (m)')
    ax.set_title('结构响应')
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)
    
    # 3. 频响函数幅值
    ax = axes[0, 2]
    ax.semilogy(f, np.abs(H), 'b-', linewidth=1.5)
    
    # 标记识别的峰值
    for mode in modes_pp:
        ax.axvline(mode['frequency'], color='r', linestyle='--', alpha=0.7)
        ax.plot(mode['frequency'], np.abs(H)[mode['peak_idx']], 'ro', markersize=8)
    
    ax.set_xlabel('频率 (Hz)')
    ax.set_ylabel('|H(f)|')
    ax.set_title('频响函数 (峰值拾取)')
    ax.set_xlim(0, 150)
    ax.grid(True, alpha=0.3)
    
    # 4. 相干函数
    ax = axes[1, 0]
    ax.plot(f, coherence, 'g-', linewidth=1.5)
    ax.axhline(0.9, color='r', linestyle='--', alpha=0.7, label='阈值 0.9')
    ax.set_xlabel('频率 (Hz)')
    ax.set_ylabel('相干函数')
    ax.set_title('相干函数')
    ax.set_xlim(0, 150)
    ax.set_ylim(0, 1)
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 5. 奇异值
    ax = axes[1, 1]
    for i in range(min(3, n_dof)):
        ax.semilogy(f, singular_values[:, i], linewidth=1.5, label=f'SV{i+1}')
    ax.set_xlabel('频率 (Hz)')
    ax.set_ylabel('奇异值')
    ax.set_title('FDD奇异值分解')
    ax.set_xlim(0, 150)
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 6. 模态参数对比
    ax = axes[1, 2]
    
    x = np.arange(len(true_params['frequencies']))
    width = 0.25
    
    ax.bar(x - width, true_params['frequencies'], width, label='真实值', color='green', alpha=0.8)
    
    pp_freqs = [m['frequency'] for m in modes_pp[:3]]
    while len(pp_freqs) < 3:
        pp_freqs.append(0)
    ax.bar(x, pp_freqs, width, label='峰值拾取', color='blue', alpha=0.8)
    
    fdd_freqs = [m['frequency'] for m in modes_fdd[:3]]
    while len(fdd_freqs) < 3:
        fdd_freqs.append(0)
    ax.bar(x + width, fdd_freqs, width, label='FDD', color='red', alpha=0.8)
    
    ax.set_xlabel('模态阶数')
    ax.set_ylabel('频率 (Hz)')
    ax.set_title('频率识别结果对比')
    ax.set_xticks(x)
    ax.set_xticklabels(['第1阶', '第2阶', '第3阶'])
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig('frequency_domain_identification.png', dpi=150, bbox_inches='tight')
    print("\n频域识别结果图已保存: frequency_domain_identification.png")
    plt.close()
    
    return identifier, modes_pp, modes_fdd

if __name__ == "__main__":
    identifier, modes_pp, modes_fdd = case1_frequency_domain()

结果分析:

  1. 峰值拾取法:通过识别频响函数幅值的峰值确定固有频率,使用半功率带宽法估计阻尼比。该方法简单直观,但受频率分辨率限制。

  2. 频域分解法(FDD):通过奇异值分解分离各阶模态,在密集模态情况下表现更好。第一奇异值曲线清晰显示各阶模态频率。

  3. 相干函数:用于评估测量质量,相干值接近1表示测量可靠。

  4. 识别精度:两种方法都能较好地识别固有频率,但阻尼比估计存在一定误差,这是频域方法的共同特点。


3.2 案例2:基于时域的模态参数识别

问题描述:
利用结构的自由振动响应数据,采用特征系统实现算法(ERA)和Ibrahim时域法识别模态参数。

Python代码实现:


"""
主题063 - 案例2: 基于时域的模态参数识别
使用特征系统实现算法(ERA)和Ibrahim时域法识别结构模态参数
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
from scipy.linalg import svd, eig
from scipy.signal import find_peaks


class TimeDomainIdentification:
    """时域模态参数识别"""
    
    def __init__(self, fs=1000, dt=None):
        """
        初始化
        
        Parameters:
        -----------
        fs : float
            采样频率 (Hz)
        dt : float
            采样时间间隔 (s)
        """
        if dt is None:
            self.fs = fs
            self.dt = 1.0 / fs
        else:
            self.dt = dt
            self.fs = 1.0 / dt
    
    def era_identification(self, impulse_response, alpha=None, beta=None, n_modes=None):
        """
        特征系统实现算法 (ERA)
        
        Parameters:
        -----------
        impulse_response : array
            脉冲响应函数 (n_outputs x n_samples) 或 (n_samples,)
        alpha : int
            Hankel矩阵行块数
        beta : int
            Hankel矩阵列块数
        n_modes : int
            要识别的模态数
        
        Returns:
        --------
        modes : list
            识别的模态参数
        singular_values : array
            奇异值
        """
        # 确保输入是2D数组
        if impulse_response.ndim == 1:
            impulse_response = impulse_response.reshape(1, -1)
        
        n_outputs, n_samples = impulse_response.shape
        
        # 设置默认参数
        if alpha is None:
            alpha = min(20, n_samples // 4)
        if beta is None:
            beta = min(20, n_samples // 4)
        
        if n_modes is None:
            n_modes = min(5, n_outputs)
        
        # 构建Hankel矩阵
        H0 = np.zeros((alpha * n_outputs, beta))
        H1 = np.zeros((alpha * n_outputs, beta))
        
        for i in range(alpha):
            for j in range(beta):
                if i + j < n_samples:
                    H0[i*n_outputs:(i+1)*n_outputs, j] = impulse_response[:, i+j]
                if i + j + 1 < n_samples:
                    H1[i*n_outputs:(i+1)*n_outputs, j] = impulse_response[:, i+j+1]
        
        # SVD分解
        U, S, Vt = svd(H0, full_matrices=False)
        
        # 确定系统阶数
        n_states = min(n_modes * 2, len(S))
        
        # 截断
        Un = U[:, :n_states]
        Sn = np.diag(S[:n_states])
        Vn = Vt[:n_states, :].T
        
        # 构建系统矩阵
        Sn_inv_sqrt = np.diag(1.0 / np.sqrt(S[:n_states] + 1e-10))
        
        A = Sn_inv_sqrt @ Un.T @ H1 @ Vn @ Sn_inv_sqrt
        
        # 观测矩阵
        C = Un[:n_outputs, :] @ np.sqrt(Sn + 1e-10 * np.eye(n_states))
        
        # 特征值分解
        eigenvalues, eigenvectors = eig(A)
        
        # 提取模态参数
        modes = []
        
        for i, ev in enumerate(eigenvalues):
            # 只考虑复共轭对的一半
            if np.imag(ev) >= 0:
                # 计算频率和阻尼
                lambda_log = np.log(ev + 1e-10)
                
                # 避免数值问题
                if np.isnan(lambda_log) or np.isinf(lambda_log):
                    continue
                
                omega_n = np.abs(lambda_log) / self.dt  # 固有频率 (rad/s)
                freq = omega_n / (2 * np.pi)  # 转换为Hz
                
                # 阻尼比
                if np.abs(lambda_log) > 1e-10:
                    zeta = -np.real(lambda_log) / np.abs(lambda_log)
                else:
                    zeta = 0.0
                
                # 模态振型
                mode_shape = C @ eigenvectors[:, i]
                
                # 只保留物理合理的模态
                if 0.1 < freq < self.fs / 2 and 0 < zeta < 0.5:
                    modes.append({
                        'frequency': freq,
                        'damping': zeta,
                        'mode_shape': mode_shape,
                        'eigenvalue': ev
                    })
        
        # 按频率排序
        modes.sort(key=lambda x: x['frequency'])
        
        return modes, S
    
    def itd_identification(self, free_response, n_modes=None, time_delay=None):
        """
        Ibrahim时域法 (ITD)
        
        Parameters:
        -----------
        free_response : array
            自由衰减响应 (n_dof x n_samples)
        n_modes : int
            要识别的模态数
        time_delay : int
            时间延迟点数
        
        Returns:
        --------
        modes : list
            识别的模态参数
        """
        if free_response.ndim == 1:
            free_response = free_response.reshape(1, -1)
        
        n_dof, n_samples = free_response.shape
        
        if n_modes is None:
            n_modes = min(n_dof, n_samples // 4)
        
        if time_delay is None:
            time_delay = max(1, n_samples // (2 * n_modes))
        
        # 构建响应矩阵
        X0 = np.zeros((n_dof, n_modes))
        X1 = np.zeros((n_dof, n_modes))
        
        for i in range(n_modes):
            if i * time_delay < n_samples:
                X0[:, i] = free_response[:, i * time_delay]
            if (i + 1) * time_delay < n_samples:
                X1[:, i] = free_response[:, (i + 1) * time_delay]
        
        # 构建增广矩阵(包含位移和速度)
        X_aug = np.vstack([X0, X1])
        Y_aug = np.vstack([X1, np.zeros_like(X1)])
        
        # 求解广义特征值问题
        try:
            eigenvalues, eigenvectors = eig(Y_aug, X_aug + 1e-10 * np.eye(X_aug.shape[0]))
        except:
            # 如果失败,使用标准特征值问题
            eigenvalues, eigenvectors = eig(X_aug.T @ Y_aug, X_aug.T @ X_aug + 1e-10 * np.eye(n_modes))
        
        # 提取模态参数
        modes = []
        
        for i, ev in enumerate(eigenvalues):
            if np.isnan(ev) or np.isinf(ev):
                continue
            
            # 计算频率和阻尼
            if np.abs(ev) > 1e-10:
                omega_d = np.angle(ev) / self.dt
                sigma = np.log(np.abs(ev)) / self.dt
                
                omega_n = np.sqrt(omega_d**2 + sigma**2)
                freq = omega_n / (2 * np.pi)
                zeta = -sigma / omega_n if omega_n > 1e-10 else 0
                
                # 模态振型
                if i < eigenvectors.shape[1]:
                    mode_shape = eigenvectors[:n_dof, i]
                    
                    # 只保留物理合理的模态
                    if 0.1 < freq < self.fs / 2 and 0 < zeta < 0.5:
                        modes.append({
                            'frequency': freq,
                            'damping': zeta,
                            'mode_shape': mode_shape,
                            'eigenvalue': ev
                        })
        
        # 按频率排序并去重
        modes.sort(key=lambda x: x['frequency'])
        
        # 去重(基于频率接近度)
        unique_modes = []
        for mode in modes:
            is_duplicate = False
            for existing in unique_modes:
                if abs(mode['frequency'] - existing['frequency']) / existing['frequency'] < 0.05:
                    is_duplicate = True
                    break
            if not is_duplicate:
                unique_modes.append(mode)
        
        return unique_modes[:n_modes]


def simulate_free_vibration(n_dof=5, fs=1000, duration=5.0):
    """
    模拟自由振动响应
    
    Parameters:
    -----------
    n_dof : int
        自由度数量
    fs : float
        采样频率
    duration : float
        信号持续时间
    
    Returns:
    --------
    t : array
        时间向量
    responses : array
        响应信号 (n_dof x n_samples)
    impulse_response : array
        脉冲响应函数
    true_params : dict
        真实模态参数
    """
    np.random.seed(42)
    
    dt = 1.0 / fs
    t = np.arange(0, duration, dt)
    n_samples = len(t)
    
    # 真实模态参数
    true_freqs = np.array([8.5, 34.2, 76.8])  # Hz
    true_dampings = np.array([0.025, 0.018, 0.012])
    
    # 模态振型
    L = 1.0
    x_pos = np.linspace(0, L, n_dof)
    
    true_modes = np.zeros((n_dof, len(true_freqs)))
    for i, freq in enumerate(true_freqs):
        mode_num = i + 1
        true_modes[:, i] = np.sin(mode_num * np.pi * x_pos / L)
        true_modes[:, i] /= np.max(np.abs(true_modes[:, i]))
    
    # 生成脉冲响应
    impulse_response = np.zeros((n_dof, n_samples))
    
    for i in range(len(true_freqs)):
        omega = 2 * np.pi * true_freqs[i]
        zeta = true_dampings[i]
        omega_d = omega * np.sqrt(1 - zeta**2)
        
        for dof in range(n_dof):
            h = (1 / omega_d) * np.exp(-zeta * omega * t) * np.sin(omega_d * t)
            impulse_response[dof, :] += true_modes[dof, i] * h
    
    # 添加噪声
    noise_level = 0.03
    impulse_response += noise_level * np.std(impulse_response) * np.random.randn(n_dof, n_samples)
    
    # 生成自由振动响应(初始位移激励)
    responses = np.zeros((n_dof, n_samples))
    
    for i in range(len(true_freqs)):
        omega = 2 * np.pi * true_freqs[i]
        zeta = true_dampings[i]
        omega_d = omega * np.sqrt(1 - zeta**2)
        
        # 初始条件
        x0 = 0.01  # 初始位移
        v0 = 0.0   # 初始速度
        
        for dof in range(n_dof):
            A = x0
            B = (v0 + zeta * omega * x0) / omega_d
            
            x = np.exp(-zeta * omega * t) * (A * np.cos(omega_d * t) + B * np.sin(omega_d * t))
            responses[dof, :] += true_modes[dof, i] * x
    
    # 添加噪声
    responses += noise_level * np.std(responses) * np.random.randn(n_dof, n_samples)
    
    true_params = {
        'frequencies': true_freqs,
        'dampings': true_dampings,
        'mode_shapes': true_modes
    }
    
    return t, responses, impulse_response, true_params


def case2_time_domain():
    """案例2:基于时域的模态参数识别"""
    print("="*60)
    print("主题063 - 案例2: 基于时域的模态参数识别")
    print("="*60)
    
    # 模拟自由振动数据
    n_dof = 5
    fs = 1000
    t, responses, impulse_response, true_params = simulate_free_vibration(n_dof, fs)
    
    print("\n真实模态参数:")
    for i, (f, zeta) in enumerate(zip(true_params['frequencies'], true_params['dampings'])):
        print(f"  第{i+1}阶: 频率 = {f:.2f} Hz, 阻尼比 = {zeta:.3f}")
    
    # 创建识别器
    identifier = TimeDomainIdentification(fs)
    
    # 方法1:ERA算法
    print("\n方法1: ERA (特征系统实现算法)")
    
    modes_era, singular_values = identifier.era_identification(
        impulse_response, alpha=15, beta=15, n_modes=3
    )
    
    print(f"识别到 {len(modes_era)} 个模态:")
    for i, mode in enumerate(modes_era[:3]):
        print(f"  第{i+1}阶: 频率 = {mode['frequency']:.2f} Hz, "
              f"阻尼比 = {mode['damping']:.4f}")
    
    # 方法2:ITD算法
    print("\n方法2: ITD (Ibrahim时域法)")
    
    modes_itd = identifier.itd_identification(responses, n_modes=3)
    
    print(f"识别到 {len(modes_itd)} 个模态:")
    for i, mode in enumerate(modes_itd[:3]):
        print(f"  第{i+1}阶: 频率 = {mode['frequency']:.2f} Hz, "
              f"阻尼比 = {mode['damping']:.4f}")
    
    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 1. 脉冲响应
    ax = axes[0, 0]
    for i in range(min(3, n_dof)):
        ax.plot(t[:500], impulse_response[i, :500], linewidth=1, label=f'测点{i+1}')
    ax.set_xlabel('时间 (s)')
    ax.set_ylabel('响应')
    ax.set_title('脉冲响应函数')
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)
    
    # 2. 自由振动响应
    ax = axes[0, 1]
    for i in range(min(3, n_dof)):
        ax.plot(t[:1000], responses[i, :1000], linewidth=1, label=f'测点{i+1}')
    ax.set_xlabel('时间 (s)')
    ax.set_ylabel('位移 (m)')
    ax.set_title('自由振动响应')
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)
    
    # 3. ERA奇异值
    ax = axes[0, 2]
    ax.semilogy(singular_values, 'bo-', markersize=6)
    ax.axvline(x=6, color='r', linestyle='--', alpha=0.7, label='截断点')
    ax.set_xlabel('奇异值序号')
    ax.set_ylabel('奇异值')
    ax.set_title('ERA奇异值分解')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 4. 频率识别结果对比
    ax = axes[1, 0]
    
    x = np.arange(len(true_params['frequencies']))
    width = 0.25
    
    ax.bar(x - width, true_params['frequencies'], width, label='真实值', color='green', alpha=0.8)
    
    era_freqs = [m['frequency'] for m in modes_era[:3]]
    while len(era_freqs) < 3:
        era_freqs.append(0)
    ax.bar(x, era_freqs, width, label='ERA', color='blue', alpha=0.8)
    
    itd_freqs = [m['frequency'] for m in modes_itd[:3]]
    while len(itd_freqs) < 3:
        itd_freqs.append(0)
    ax.bar(x + width, itd_freqs, width, label='ITD', color='red', alpha=0.8)
    
    ax.set_xlabel('模态阶数')
    ax.set_ylabel('频率 (Hz)')
    ax.set_title('频率识别结果对比')
    ax.set_xticks(x)
    ax.set_xticklabels(['第1阶', '第2阶', '第3阶'])
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    # 5. 阻尼比识别结果对比
    ax = axes[1, 1]
    
    ax.bar(x - width, true_params['dampings'], width, label='真实值', color='green', alpha=0.8)
    
    era_dampings = [m['damping'] for m in modes_era[:3]]
    while len(era_dampings) < 3:
        era_dampings.append(0)
    ax.bar(x, era_dampings, width, label='ERA', color='blue', alpha=0.8)
    
    itd_dampings = [m['damping'] for m in modes_itd[:3]]
    while len(itd_dampings) < 3:
        itd_dampings.append(0)
    ax.bar(x + width, itd_dampings, width, label='ITD', color='red', alpha=0.8)
    
    ax.set_xlabel('模态阶数')
    ax.set_ylabel('阻尼比')
    ax.set_title('阻尼比识别结果对比')
    ax.set_xticks(x)
    ax.set_xticklabels(['第1阶', '第2阶', '第3阶'])
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    # 6. 方法特点
    ax = axes[1, 2]
    ax.text(0.5, 0.5, '时域方法特点:\n\n' +
            'ERA:\n- 基于脉冲响应\n- 数值稳定\n- 适合多输出系统\n\n' +
            'ITD:\n- 基于自由响应\n- 计算简单\n- 对噪声敏感', 
            ha='center', va='center', fontsize=10, transform=ax.transAxes)
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1)
    ax.axis('off')
    ax.set_title('方法特点对比')
    
    plt.tight_layout()
    plt.savefig('time_domain_identification.png', dpi=150, bbox_inches='tight')
    print("\n时域识别结果图已保存: time_domain_identification.png")
    plt.close()
    
    # 创建动画
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    ax1 = axes[0]
    lines1 = []
    for i in range(min(3, n_dof)):
        line, = ax1.plot([], [], linewidth=1.5, label=f'测点{i+1}')
        lines1.append(line)
    ax1.set_xlim(0, t[500])
    ax1.set_ylim(np.min(impulse_response[:, :500]) * 1.1, 
                 np.max(impulse_response[:, :500]) * 1.1)
    ax1.set_xlabel('时间 (s)')
    ax1.set_ylabel('响应')
    ax1.set_title('脉冲响应动画')
    ax1.legend(fontsize=8)
    ax1.grid(True, alpha=0.3)
    
    ax2 = axes[1]
    lines2 = []
    for i in range(min(3, n_dof)):
        line, = ax2.plot([], [], linewidth=1.5, label=f'测点{i+1}')
        lines2.append(line)
    ax2.set_xlim(0, t[1000])
    ax2.set_ylim(np.min(responses[:, :1000]) * 1.1, 
                 np.max(responses[:, :1000]) * 1.1)
    ax2.set_xlabel('时间 (s)')
    ax2.set_ylabel('位移 (m)')
    ax2.set_title('自由振动响应动画')
    ax2.legend(fontsize=8)
    ax2.grid(True, alpha=0.3)
    
    def animate(frame):
        end_idx = min((frame + 1) * 10, 500)
        for i, line in enumerate(lines1):
            line.set_data(t[:end_idx], impulse_response[i, :end_idx])
        for i, line in enumerate(lines2):
            line.set_data(t[:end_idx*2], responses[i, :end_idx*2])
        return lines1 + lines2
    
    anim = FuncAnimation(fig, animate, frames=50, interval=100, blit=False)
    anim.save('time_domain_animation.gif', writer='pillow', fps=10, dpi=100)
    print("时域识别动画已保存: time_domain_animation.gif")
    plt.close()
    
    return identifier, modes_era, modes_itd


if __name__ == "__main__":
    identifier, modes_era, modes_itd = case2_time_domain()

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐