第八十四篇:结构动力学仿真验证与确认

摘要

仿真验证与确认(Verification and Validation, V&V)是确保结构动力学仿真结果可靠性和可信度的关键环节。本文系统介绍V&V的基本概念、理论框架和实施方法,包括代码验证、计算验证、模型确认等核心内容。通过数值实验与解析解对比、网格收敛性分析、不确定性量化等方法,建立完整的仿真可信度评估体系。文章结合典型工程案例,演示如何系统地进行仿真验证与确认,为工程实践提供理论指导和方法参考。

关键词

验证与确认;代码验证;计算验证;模型确认;不确定性量化;网格收敛性;基准测试;误差估计


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

1. 验证与确认的基本概念

1.1 V&V的定义与区别

验证(Verification)和确认(Validation)是仿真质量保障的两个核心环节,虽然常被一起提及,但具有本质区别:

验证(Verification):回答"我们是否正确地求解了方程?"关注的是数值求解过程的正确性,包括:

  • 代码验证:检查程序是否正确实现了数学模型
  • 计算验证:检查数值解是否收敛于精确解

确认(Validation):回答"我们是否求解了正确的方程?"关注的是数学模型对物理现实的描述能力,包括:

  • 模型确认:通过实验数据验证模型的预测能力
  • 适用范围确定:明确模型在什么条件下有效

1.2 V&V的重要性

在结构动力学仿真中,V&V具有特殊的重要性:

工程安全需求:结构失效可能导致灾难性后果,必须确保仿真结果可信

经济性考虑:过度保守设计造成浪费,过于激进设计带来风险

法规要求:许多行业标准(如ASME、AIAA)对V&V有明确要求

科学严谨性:确保仿真结果可以被同行复现和验证

1.3 V&V标准与指南

国际上主要的V&V标准和指南包括:

ASME V&V 10-2006:计算固体力学和流体力学的验证与确认指南

AIAA G-077-1998:计算流体力学验证与确认指南

Oberkampf et al. (2002):预测能力评估框架

Schwer (2007):结构动力学验证与确认方法

这些标准共同构成了V&V的理论基础和实践指南。


2. 代码验证方法

2.1 代码验证的基本原理

代码验证的目标是证明数值程序正确实现了数学模型。主要方法包括:

基准测试(Benchmark Testing):使用具有精确解析解的问题测试代码

代码间比较(Code-to-Code Comparison):多个独立开发的代码求解同一问题

代码审查(Code Inspection):人工检查源代码的正确性

单元测试(Unit Testing):对代码的各个模块进行独立测试

2.2 基准测试问题

基准测试是代码验证的核心方法。理想的基准测试问题应具有:

精确解已知:可以是解析解、级数解或高精度数值解

覆盖关键特性:包含待验证代码的主要功能

数学适定性:问题在数学上是良态的

物理合理性:具有明确的物理意义

结构动力学中常用的基准测试问题包括:

单自由度系统
mu¨+cu˙+ku=F(t)m\ddot{u} + c\dot{u} + ku = F(t)mu¨+cu˙+ku=F(t)

对于简谐激励 F(t)=F0sin⁡(ωt)F(t) = F_0\sin(\omega t)F(t)=F0sin(ωt),稳态响应为:
u(t)=F0/k(1−β2)2+(2ζβ)2sin⁡(ωt−ϕ)u(t) = \frac{F_0/k}{\sqrt{(1-\beta^2)^2 + (2\zeta\beta)^2}}\sin(\omega t - \phi)u(t)=(1β2)2+(2ζβ)2 F0/ksin(ωtϕ)

其中 β=ω/ωn\beta = \omega/\omega_nβ=ω/ωnϕ=arctan⁡2ζβ1−β2\phi = \arctan\frac{2\zeta\beta}{1-\beta^2}ϕ=arctan1β22ζβ

简支梁振动

固有频率:
ωn=(nπL)2EIρA\omega_n = \left(\frac{n\pi}{L}\right)^2\sqrt{\frac{EI}{\rho A}}ωn=(L)2ρAEI

振型函数:
ϕn(x)=sin⁡(nπxL)\phi_n(x) = \sin\left(\frac{n\pi x}{L}\right)ϕn(x)=sin(Lx)

圆板振动

固有频率(简支边界):
ωmn=λmn2a2Dρh\omega_{mn} = \frac{\lambda_{mn}^2}{a^2}\sqrt{\frac{D}{\rho h}}ωmn=a2λmn2ρhD

其中 D=Eh312(1−ν2)D = \frac{Eh^3}{12(1-\nu^2)}D=12(1ν2)Eh3 为弯曲刚度,λmn\lambda_{mn}λmn 为特征值

2.3 收敛性分析

收敛性分析是验证数值方法正确性的重要手段:

空间收敛:减小网格尺寸,观察解是否收敛

时间收敛:减小时间步长,观察解是否收敛

收敛阶数验证:数值解的误差应与理论收敛阶数一致

对于 ppp 阶精度的方法,误差应满足:
∥u−uh∥≤Chp\|u - u_h\| \leq Ch^puuhChp

其中 hhh 为网格尺寸,CCC 为与网格无关的常数

通过绘制 log⁡(∥u−uh∥)\log(\|u - u_h\|)log(uuh)log⁡(h)\log(h)log(h) 的曲线,斜率应接近 ppp

2.4 代码间比较

当精确解不可得时,代码间比较是一种有效的验证方法:

N-version编程:多个独立团队开发代码求解同一问题

结果一致性检验:比较不同代码的结果差异

统计分析:使用统计方法评估结果的一致性

代码间比较的局限性在于:如果多个代码存在相同的系统性误差,可能无法发现


3. 计算验证方法

3.1 网格收敛性分析

网格收敛性分析是计算验证的核心内容,用于评估数值解的精度:

Richardson外推法:利用不同网格上的解估计精确解

设细网格解为 u1u_1u1,粗网格解为 u2u_2u2,收敛阶数为 ppp,则外推解为:
uexact≈u1+u1−u2rp−1u_{exact} \approx u_1 + \frac{u_1 - u_2}{r^p - 1}uexactu1+rp1u1u2

其中 r=h2/h1r = h_2/h_1r=h2/h1 为网格细化比

网格收敛指标(GCI)
GCI=Fs∣u1−u2∣rp−1GCI = \frac{F_s|u_1 - u_2|}{r^p - 1}GCI=rp1Fsu1u2

其中 FsF_sFs 为安全因子(通常取1.25)

渐近收敛范围:只有当网格足够细时,收敛性分析才有效

判断准则:
GCIfinerp⋅GCIcoarse≈1\frac{GCI_{fine}}{r^p \cdot GCI_{coarse}} \approx 1rpGCIcoarseGCIfine1

3.2 时间步长收敛性

对于动力问题,时间步长的选择至关重要:

稳定性限制:显式积分方法存在稳定性条件

Δt≤2ωmax\Delta t \leq \frac{2}{\omega_{max}}Δtωmax2

精度要求:时间步长应足够小以捕捉动态响应

收敛性检验:通过减小时间步长检验解的收敛性

3.3 误差估计

误差估计是计算验证的重要组成部分:

先验误差估计:在计算前估计误差上界

后验误差估计:基于计算结果估计实际误差

残差法:利用控制方程的残差估计误差

恢复法:通过应力恢复等技术提高精度

对于结构动力学,常用的误差度量包括:

能量范数误差
∥e∥E=∫0T(∫Ω(σ−σh):(ϵ−ϵh)dΩ)dt\|e\|_E = \sqrt{\int_0^T\left(\int_\Omega (\sigma - \sigma_h):(\epsilon - \epsilon_h)d\Omega\right)dt}eE=0T(Ω(σσh):(ϵϵh)dΩ)dt

L2范数误差
∥e∥L2=∫0T∫Ω(u−uh)2dΩdt\|e\|_{L2} = \sqrt{\int_0^T\int_\Omega (u - u_h)^2 d\Omega dt}eL2=0TΩ(uuh)2dΩdt

3.4 自适应网格细化

自适应网格细化是提高计算效率的有效方法:

误差指示器:识别误差较大的区域

细化策略:h-细化(减小单元尺寸)、p-细化(提高多项式阶数)

收敛控制:设定误差容差,自动调整网格


4. 模型确认方法

4.1 模型确认的基本框架

模型确认是将仿真结果与实验数据对比,评估模型预测能力的过程:

确认实验设计:设计专门用于模型确认的实验

数据获取:高精度测量系统的响应

对比分析:定量比较仿真与实验结果

适用域确定:明确模型的有效适用范围

4.2 确认度量

定量评估模型确认程度需要合适的度量指标:

相对误差
ϵrel=∣usim−uexp∣∣uexp∣\epsilon_{rel} = \frac{|u_{sim} - u_{exp}|}{|u_{exp}|}ϵrel=uexpusimuexp

决定系数(R²)
R2=1−∑(uexp−usim)2∑(uexp−uˉexp)2R^2 = 1 - \frac{\sum(u_{exp} - u_{sim})^2}{\sum(u_{exp} - \bar{u}_{exp})^2}R2=1(uexpuˉexp)2(uexpusim)2

归一化均方根误差(NRMSE)
NRMSE=1N∑(usim−uexp)2uexp,max−uexp,minNRMSE = \frac{\sqrt{\frac{1}{N}\sum(u_{sim} - u_{exp})^2}}{u_{exp,max} - u_{exp,min}}NRMSE=uexp,maxuexp,minN1(usimuexp)2

相关系数
ρ=∑(usim−uˉsim)(uexp−uˉexp)∑(usim−uˉsim)2∑(uexp−uˉexp)2\rho = \frac{\sum(u_{sim} - \bar{u}_{sim})(u_{exp} - \bar{u}_{exp})}{\sqrt{\sum(u_{sim} - \bar{u}_{sim})^2\sum(u_{exp} - \bar{u}_{exp})^2}}ρ=(usimuˉsim)2(uexpuˉexp)2 (usimuˉsim)(uexpuˉexp)

4.3 模型修正与更新

当模型预测与实验结果存在偏差时,需要进行模型修正:

参数识别:调整模型参数使预测与实验吻合

模型结构修正:改进模型的数学形式

贝叶斯模型更新:利用贝叶斯理论更新模型参数的概率分布

模型选择:在多个候选模型中选择最优模型

4.4 确认层次

模型确认通常分为多个层次进行:

单元级确认:对单个组件进行确认

子系统级确认:对子系统进行确认

系统级确认:对整个系统进行确认

这种分层方法可以降低确认难度,提高确认效率


5. 不确定性量化

5.1 不确定性的来源

结构动力学仿真中存在多种不确定性:

偶然不确定性(Aleatory):固有的随机性,如材料属性的自然变异

认知不确定性(Epistemic):由于知识不足导致的不确定性,如模型简化

输入不确定性:载荷、边界条件、几何尺寸的不确定性

模型形式不确定性:模型结构选择带来的不确定性

5.2 不确定性传播方法

蒙特卡洛模拟:通过大量随机采样传播不确定性

优点:概念简单,适用于非线性问题
缺点:计算量大,收敛速度慢

摄动法:基于泰勒展开近似不确定性传播

对于小不确定性,一阶近似:
μY≈f(μX)\mu_Y \approx f(\mu_X)μYf(μX)
σY2≈∑i∑j∂f∂Xi∂f∂XjCov(Xi,Xj)\sigma_Y^2 \approx \sum_i\sum_j\frac{\partial f}{\partial X_i}\frac{\partial f}{\partial X_j}Cov(X_i, X_j)σY2ijXifXjfCov(Xi,Xj)

多项式混沌展开:用正交多项式展开随机过程

Y(ξ)=∑k=0PykΨk(ξ)Y(\xi) = \sum_{k=0}^P y_k\Psi_k(\xi)Y(ξ)=k=0PykΨk(ξ)

其中 Ψk\Psi_kΨk 为混沌多项式,ξ\xiξ 为标准随机变量

随机有限元法:将有限元与随机分析相结合

5.3 验证与确认中的不确定性

考虑不确定性后,V&V需要采用概率方法:

概率验证:验证数值解在统计意义上收敛

概率确认:评估模型预测与实验数据的统计一致性

置信区间:给出预测结果的置信区间而非单值

假设检验:使用统计检验评估模型 adequacy


6. 工程应用案例

6.1 案例1:简支梁模态分析验证

本案例验证有限元模态分析代码的正确性:

问题描述:简支梁的横向振动

几何参数:长度 L=1.0L=1.0L=1.0 m,截面 0.01×0.010.01 \times 0.010.01×0.01

材料参数E=200E=200E=200 GPa,ρ=7850\rho=7850ρ=7850 kg/m³

理论解:前三阶固有频率
f1=16.13 Hz,f2=64.52 Hz,f3=145.17 Hzf_1 = 16.13 \text{ Hz}, \quad f_2 = 64.52 \text{ Hz}, \quad f_3 = 145.17 \text{ Hz}f1=16.13 Hz,f2=64.52 Hz,f3=145.17 Hz

验证内容

  • 网格收敛性分析
  • 频率计算精度
  • 振型正交性检验

6.2 案例2:冲击响应分析确认

本案例通过实验数据确认冲击响应分析模型:

实验设置:悬臂梁受脉冲载荷

测量内容:应变、加速度时程

仿真模型:有限元模型,考虑材料非线性

确认度量

  • 峰值响应误差
  • 响应时程相关系数
  • 频谱一致性

6.3 案例3:不确定性量化与验证

本案例演示不确定性量化在V&V中的应用:

不确定性来源

  • 材料属性:E∼N(200,102)E \sim N(200, 10^2)EN(200,102) GPa
  • 几何尺寸:厚度 h∼N(0.01,0.00052)h \sim N(0.01, 0.0005^2)hN(0.01,0.00052) m
  • 边界条件:刚度 k∼N(106,105)k \sim N(10^6, 10^5)kN(106,105) N/m

分析方法

  • 蒙特卡洛模拟
  • 敏感性分析
  • 置信区间估计

7. V&V最佳实践

7.1 V&V计划制定

有效的V&V需要系统的计划:

明确目标:确定V&V的范围和深度

选择方法:根据问题特点选择合适的V&V方法

资源分配:合理分配计算和实验资源

文档记录:详细记录V&V过程和结果

7.2 常见误区与注意事项

过度依赖单一验证方法:应综合使用多种方法

忽视网格收敛性:未充分检验网格无关性

实验数据质量问题:实验误差可能大于仿真误差

确认与验证混淆:明确区分两者的目的和方法

忽视不确定性:未考虑输入和模型不确定性

7.3 质量保证体系

建立完善的V&V质量保证体系:

标准化流程:制定V&V标准操作程序

人员培训:提高工程师的V&V意识和能力

独立审查:引入第三方进行V&V审查

持续改进:基于经验反馈改进V&V方法


8. 前沿发展方向

8.1 机器学习方法在V&V中的应用

代理模型:用机器学习模型替代昂贵的高保真仿真

误差预测:训练神经网络预测仿真误差

模型修正:利用深度学习进行模型参数识别

不确定性量化:结合贝叶斯神经网络进行不确定性估计

8.2 数字孪生与实时V&V

实时验证:基于传感器数据进行在线模型验证

自适应模型:根据实测数据自动更新模型

预测性维护:结合V&V进行结构健康预测

8.3 多尺度多物理场V&V

跨尺度验证:从原子尺度到宏观尺度的验证

多物理场耦合:热-力-电等多物理场耦合验证

异构模型集成:不同保真度模型的集成验证


9. 总结

仿真验证与确认是确保结构动力学仿真可靠性的关键环节。本文系统介绍了:

验证方法:代码验证和计算验证的基本方法,包括基准测试、收敛性分析等

确认方法:模型确认的理论框架和定量度量

不确定性量化:考虑不确定性的V&V方法

工程实践:通过典型案例演示V&V的实施过程

V&V不是一次性工作,而是贯穿仿真全过程的持续活动。只有建立完善的V&V体系,才能确保仿真结果的可信度,为工程决策提供可靠依据。



完整Python代码实现

以下是本主题的完整Python仿真代码:

import matplotlib
matplotlib.use('Agg')
"""
案例1: 仿真验证方法

本案例演示结构动力学仿真中的验证方法,包括:
1. 代码验证:基准测试问题与解析解对比
2. 计算验证:网格收敛性分析
3. 时间步长收敛性分析
4. 误差估计与收敛阶数验证
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy.linalg import eigh
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体
plt.rcParams['font.size'] = 10

# ==================== 代码验证:基准测试 ====================

def sdof_exact_solution(m, c, k, F0, omega, t):
    """
    单自由度系统简谐激励的精确解析解
    
    Parameters:
    -----------
    m, c, k : float
        质量、阻尼、刚度
    F0 : float
        激励幅值
    omega : float
        激励频率
    t : ndarray
        时间数组
    
    Returns:
    --------
    u : 位移响应
    v : 速度响应
    a : 加速度响应
    """
    omega_n = np.sqrt(k / m)
    zeta = c / (2 * np.sqrt(k * m))
    beta = omega / omega_n
    
    # 稳态响应幅值和相位
    H = 1 / np.sqrt((1 - beta**2)**2 + (2*zeta*beta)**2)
    phi = np.arctan2(2*zeta*beta, 1 - beta**2)
    
    # 稳态响应
    u_steady = (F0 / k) * H * np.sin(omega * t - phi)
    v_steady = (F0 / k) * H * omega * np.cos(omega * t - phi)
    a_steady = -(F0 / k) * H * omega**2 * np.sin(omega * t - phi)
    
    # 瞬态响应(假设初始静止)
    omega_d = omega_n * np.sqrt(1 - zeta**2)
    A = -(F0/k) * H * (np.sin(-phi))
    B = -(F0/k) * H * (omega*np.cos(-phi) + zeta*omega_n*np.sin(-phi)) / omega_d
    
    u_transient = np.exp(-zeta*omega_n*t) * (A*np.cos(omega_d*t) + B*np.sin(omega_d*t))
    v_transient = np.exp(-zeta*omega_n*t) * ((-zeta*omega_n*A + omega_d*B)*np.cos(omega_d*t) + 
                                              (-zeta*omega_n*B - omega_d*A)*np.sin(omega_d*t))
    
    u = u_steady + u_transient
    v = v_steady + v_transient
    a = a_steady + np.exp(-zeta*omega_n*t) * ((-zeta*omega_n)*(-zeta*omega_n*A + omega_d*B)*np.cos(omega_d*t) + ...)
    
    return u, v, a_steady


def newmark_beta(m, c, k, F, dt, beta=0.25, gamma=0.5):
    """
    Newmark-beta法数值积分
    
    Parameters:
    -----------
    m, c, k : float
        质量、阻尼、刚度
    F : ndarray
        激励力时程
    dt : float
        时间步长
    beta, gamma : float
        Newmark参数
    
    Returns:
    --------
    u, v, a : 位移、速度、加速度时程
    """
    n = len(F)
    u = np.zeros(n)
    v = np.zeros(n)
    a = np.zeros(n)
    
    # 初始加速度
    a[0] = (F[0] - c*v[0] - k*u[0]) / m
    
    # 等效刚度
    k_eff = m / (beta*dt**2) + gamma*c / (beta*dt) + k
    
    for i in range(n-1):
        # 等效载荷
        f_eff = (F[i+1] + 
                m/(beta*dt**2)*(u[i] + dt*v[i] + (0.5-beta)*dt**2*a[i]) +
                gamma*c/(beta*dt)*(u[i] + (1-gamma/beta)*dt*v[i] + (0.5-gamma/beta)*dt**2*a[i]) -
                (1-gamma/beta)*c*v[i] - (1-gamma/(2*beta))*c*dt*a[i])
        
        # 求解位移
        u[i+1] = f_eff / k_eff
        
        # 更新速度和加速度
        a[i+1] = (u[i+1] - u[i] - dt*v[i] - (0.5-beta)*dt**2*a[i]) / (beta*dt**2)
        v[i+1] = v[i] + dt*((1-gamma)*a[i] + gamma*a[i+1])
    
    return u, v, a


def central_difference(m, c, k, F, dt):
    """
    中心差分法数值积分
    
    Parameters:
    -----------
    m, c, k : float
        质量、阻尼、刚度
    F : ndarray
        激励力时程
    dt : float
        时间步长
    
    Returns:
    --------
    u, v, a : 位移、速度、加速度时程
    """
    n = len(F)
    u = np.zeros(n)
    v = np.zeros(n)
    a = np.zeros(n)
    
    # 初始条件
    a[0] = (F[0] - c*v[0] - k*u[0]) / m
    
    # 虚拟位移u[-1]
    u_minus1 = u[0] - dt*v[0] + dt**2/2*a[0]
    
    # 等效参数
    a0 = m/dt**2 + c/(2*dt)
    a1 = m/dt**2 - c/(2*dt)
    a2 = k - 2*m/dt**2
    
    for i in range(n-1):
        if i == 0:
            u[i+1] = (F[i] - a2*u[i] - a1*u_minus1) / a0
        else:
            u[i+1] = (F[i] - a2*u[i] - a1*u[i-1]) / a0
        
        # 速度和加速度
        if i > 0:
            v[i] = (u[i+1] - u[i-1]) / (2*dt)
            a[i] = (u[i+1] - 2*u[i] + u[i-1]) / dt**2
    
    # 最后一点
    v[-1] = (u[-1] - u[-2]) / dt
    a[-1] = (F[-1] - c*v[-1] - k*u[-1]) / m
    
    return u, v, a


# ==================== 计算验证:网格收敛性 ====================

def beam_modal_analysis(n_elements, L, E, I, rho, A):
    """
    简支梁模态分析(有限元)
    
    Parameters:
    -----------
    n_elements : int
        单元数
    L, E, I, rho, A : float
        梁的几何和材料参数
    
    Returns:
    --------
    freqs : 固有频率
    modes : 振型
    """
    n_nodes = n_elements + 1
    n_dof = 2 * n_nodes
    
    # 单元长度
    Le = L / n_elements
    
    # 单元刚度矩阵(Euler-Bernoulli梁)
    k_e = E*I/Le**3 * np.array([
        [12, 6*Le, -12, 6*Le],
        [6*Le, 4*Le**2, -6*Le, 2*Le**2],
        [-12, -6*Le, 12, -6*Le],
        [6*Le, 2*Le**2, -6*Le, 4*Le**2]
    ])
    
    # 一致质量矩阵
    m_e = rho*A*Le/420 * np.array([
        [156, 22*Le, 54, -13*Le],
        [22*Le, 4*Le**2, 13*Le, -3*Le**2],
        [54, 13*Le, 156, -22*Le],
        [-13*Le, -3*Le**2, -22*Le, 4*Le**2]
    ])
    
    # 组装全局矩阵
    K = np.zeros((n_dof, n_dof))
    M = np.zeros((n_dof, n_dof))
    
    for e in range(n_elements):
        dof = np.array([2*e, 2*e+1, 2*(e+1), 2*(e+1)+1])
        for i in range(4):
            for j in range(4):
                K[dof[i], dof[j]] += k_e[i, j]
                M[dof[i], dof[j]] += m_e[i, j]
    
    # 简支边界条件(两端位移为0)
    bc_dofs = [0, 2*(n_nodes-1)]  # 第1个和最后1个节点的位移
    free_dofs = [i for i in range(n_dof) if i not in bc_dofs]
    
    K_reduced = K[np.ix_(free_dofs, free_dofs)]
    M_reduced = M[np.ix_(free_dofs, free_dofs)]
    
    # 求解特征值问题
    eigenvalues, eigenvectors = eigh(K_reduced, M_reduced)
    
    # 固有频率
    freqs = np.sqrt(eigenvalues) / (2*np.pi)
    
    # 重构完整振型
    modes = np.zeros((n_dof, len(freqs)))
    for i, fd in enumerate(free_dofs):
        modes[fd, :] = eigenvectors[i, :]
    
    return freqs, modes


def beam_exact_frequencies(L, E, I, rho, A, n_modes=5):
    """
    简支梁精确固有频率
    
    Parameters:
    -----------
    L, E, I, rho, A : float
        梁参数
    n_modes : int
        模态数
    
    Returns:
    --------
    freqs : 精确频率
    """
    freqs = []
    for n in range(1, n_modes+1):
        omega_n = (n * np.pi / L)**2 * np.sqrt(E*I / (rho*A))
        freqs.append(omega_n / (2*np.pi))
    return np.array(freqs)


# ==================== 可视化函数 ====================

def plot_code_verification():
    """代码验证:数值解与解析解对比"""
    # 系统参数
    m = 1.0
    c = 0.2
    k = 100.0
    F0 = 10.0
    omega = 8.0
    
    # 时间参数
    T = 10.0
    dt = 0.01
    t = np.arange(0, T, dt)
    F = F0 * np.sin(omega * t)
    
    # 精确解(稳态部分)
    omega_n = np.sqrt(k / m)
    zeta = c / (2 * np.sqrt(k * m))
    beta = omega / omega_n
    H = 1 / np.sqrt((1 - beta**2)**2 + (2*zeta*beta)**2)
    phi = np.arctan2(2*zeta*beta, 1 - beta**2)
    u_exact = (F0 / k) * H * np.sin(omega * t - phi)
    
    # 数值解
    u_newmark, _, _ = newmark_beta(m, c, k, F, dt)
    u_central, _, _ = central_difference(m, c, k, F, dt)
    
    # 绘图
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 位移时程对比
    ax = axes[0, 0]
    ax.plot(t, u_exact, 'k-', linewidth=2, label='Exact (Steady-state)')
    ax.plot(t, u_newmark, 'r--', linewidth=1.5, alpha=0.8, label='Newmark-beta')
    ax.plot(t, u_central, 'b:', linewidth=1.5, alpha=0.8, label='Central Difference')
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Displacement (m)')
    ax.set_title('Code Verification: Numerical vs Exact Solution')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.set_xlim(5, 10)  # 显示稳态部分
    
    # 误差分析
    ax = axes[0, 1]
    error_newmark = np.abs(u_newmark - u_exact)
    error_central = np.abs(u_central - u_exact)
    ax.semilogy(t, error_newmark, 'r-', label='Newmark Error')
    ax.semilogy(t, error_central, 'b-', label='Central Difference Error')
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Absolute Error (log scale)')
    ax.set_title('Numerical Error vs Time')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 不同时间步长的收敛性
    ax = axes[1, 0]
    dts = [0.1, 0.05, 0.02, 0.01, 0.005, 0.002]
    errors_newmark = []
    errors_central = []
    
    for dt_test in dts:
        t_test = np.arange(0, T, dt_test)
        F_test = F0 * np.sin(omega * t_test)
        u_exact_test = (F0 / k) * H * np.sin(omega * t_test - phi)
        
        u_num, _, _ = newmark_beta(m, c, k, F_test, dt_test)
        error = np.max(np.abs(u_num[-100:] - u_exact_test[-100:]))
        errors_newmark.append(error)
        
        u_num_cd, _, _ = central_difference(m, c, k, F_test, dt_test)
        error_cd = np.max(np.abs(u_num_cd[-100:] - u_exact_test[-100:]))
        errors_central.append(error_cd)
    
    ax.loglog(dts, errors_newmark, 'ro-', linewidth=2, markersize=8, label='Newmark-beta')
    ax.loglog(dts, errors_central, 'bs-', linewidth=2, markersize=8, label='Central Difference')
    
    # 参考斜率线
    dt_ref = np.array([0.1, 0.002])
    ax.loglog(dt_ref, 0.1*dt_ref**2, 'k--', alpha=0.5, label='2nd Order Slope')
    
    ax.set_xlabel('Time Step (s)')
    ax.set_ylabel('Max Error')
    ax.set_title('Time Step Convergence Analysis')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 频谱分析
    ax = axes[1, 1]
    from scipy.fft import fft, fftfreq
    
    # 计算FFT
    N = len(t)
    yf_exact = fft(u_exact)
    yf_newmark = fft(u_newmark)
    xf = fftfreq(N, dt)[:N//2]
    
    ax.semilogy(xf, 2.0/N * np.abs(yf_exact[0:N//2]), 'k-', linewidth=2, label='Exact')
    ax.semilogy(xf, 2.0/N * np.abs(yf_newmark[0:N//2]), 'r--', linewidth=1.5, label='Newmark')
    ax.axvline(omega/(2*np.pi), color='blue', linestyle=':', linewidth=2, label=f'Excitation Freq: {omega/(2*np.pi):.2f} Hz')
    ax.set_xlabel('Frequency (Hz)')
    ax.set_ylabel('Amplitude')
    ax.set_title('Frequency Domain Comparison')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.set_xlim(0, 5)
    
    plt.tight_layout()
    plt.savefig('code_verification.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("代码验证图已保存")


def plot_mesh_convergence():
    """网格收敛性分析"""
    # 梁参数
    L = 1.0
    E = 200e9
    b = h = 0.01
    I = b * h**3 / 12
    A = b * h
    rho = 7850
    
    # 不同网格数 - 从4开始确保有足够自由度
    n_elements_list = [4, 8, 16, 32, 64, 128]
    
    # 精确频率
    n_modes_target = 5
    freqs_exact = beam_exact_frequencies(L, E, I, rho, A, n_modes=n_modes_target)
    
    # 存储结果 - 使用列表存储,允许不同长度
    freqs_fem_list = []
    
    for n_elem in n_elements_list:
        freqs_num, _ = beam_modal_analysis(n_elem, L, E, I, rho, A)
        freqs_fem_list.append(freqs_num[:n_modes_target])  # 最多取5阶
    
    # 确定实际可用的模态数(所有网格都能计算的模态数)
    min_modes = min(len(f) for f in freqs_fem_list)
    
    # 重新组织数据
    freqs_fem = {i: [] for i in range(min_modes)}
    errors = {i: [] for i in range(min_modes)}
    
    for freqs_num in freqs_fem_list:
        for i in range(min_modes):
            freqs_fem[i].append(freqs_num[i])
            errors[i].append(np.abs(freqs_num[i] - freqs_exact[i]) / freqs_exact[i] * 100)
    
    # 绘图
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 频率收敛
    ax = axes[0, 0]
    h_sizes = [L/n for n in n_elements_list]
    colors = ['red', 'blue', 'green', 'orange', 'purple']
    
    for i in range(min_modes):
        ax.loglog(h_sizes, freqs_fem[i], 'o-', color=colors[i], 
                 linewidth=2, markersize=8, label=f'Mode {i+1}')
        ax.axhline(freqs_exact[i], color=colors[i], linestyle='--', alpha=0.5)
    
    ax.set_xlabel('Element Size h (m)')
    ax.set_ylabel('Natural Frequency (Hz)')
    ax.set_title('Mesh Convergence: Natural Frequencies')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 相对误差收敛
    ax = axes[0, 1]
    for i in range(min_modes):
        ax.loglog(h_sizes, errors[i], 'o-', color=colors[i],
                 linewidth=2, markersize=8, label=f'Mode {i+1}')
    
    # 参考斜率线(2阶收敛)
    h_ref = np.array([h_sizes[0], h_sizes[-1]])
    ax.loglog(h_ref, 10*h_ref**2, 'k--', alpha=0.5, label='2nd Order Slope')
    
    ax.set_xlabel('Element Size h (m)')
    ax.set_ylabel('Relative Error (%)')
    ax.set_title('Convergence Rate Analysis')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # Richardson外推
    ax = axes[1, 0]
    # 使用最后三个网格计算外推解
    p = 2  # 假设2阶收敛
    
    n_modes_richardson = min(3, min_modes)  # 确保不超过可用模态数
    
    for i in range(n_modes_richardson):  # 前n阶模态
        f_coarse = freqs_fem[i][-3]
        f_medium = freqs_fem[i][-2]
        f_fine = freqs_fem[i][-1]
        
        r = 2  # 细化比
        f_extrap = f_fine + (f_fine - f_medium) / (r**p - 1)
        
        ax.bar([i*4, i*4+1, i*4+2, i*4+3], 
               [f_coarse, f_medium, f_fine, freqs_exact[i]],
               color=['lightblue', 'blue', 'darkblue', 'red'],
               alpha=0.7)
        ax.plot(i*4+3, f_extrap, 'go', markersize=12, markerfacecolor='none', 
               markeredgewidth=2, label='Richardson Extrapolation' if i==0 else '')
    
    ax.set_xticks([1.5, 5.5, 9.5])
    ax.set_xticklabels(['Mode 1', 'Mode 2', 'Mode 3'])
    ax.set_ylabel('Frequency (Hz)')
    ax.set_title('Richardson Extrapolation')
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    # GCI分析
    ax = axes[1, 1]
    Fs = 1.25  # 安全因子
    
    n_modes_gci = min(3, min_modes)  # 确保不超过可用模态数
    
    for i in range(n_modes_gci):
        f_medium = freqs_fem[i][-2]
        f_fine = freqs_fem[i][-1]
        r = 2
        p = 2
        
        GCI = Fs * np.abs(f_fine - f_medium) / (r**p - 1)
        relative_GCI = GCI / f_fine * 100
        
        ax.bar(i, relative_GCI, color=colors[i], alpha=0.7, label=f'Mode {i+1}')
    
    ax.set_xlabel('Mode Number')
    ax.set_ylabel('GCI (%)')
    ax.set_title('Grid Convergence Index')
    ax.set_xticks(range(n_modes_gci))
    ax.set_xticklabels([f'Mode {i+1}' for i in range(n_modes_gci)])
    ax.grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig('mesh_convergence.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("网格收敛性图已保存")
    
    # 打印结果
    print("\n网格收敛性分析结果:")
    print("="*60)
    for i in range(min_modes):
        print(f"\n第{i+1}阶模态:")
        print(f"  精确频率: {freqs_exact[i]:.4f} Hz")
        print(f"  最细网格频率: {freqs_fem[i][-1]:.4f} Hz")
        print(f"  相对误差: {errors[i][-1]:.4f}%")


def plot_benchmark_tests():
    """基准测试问题验证"""
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 测试1:自由振动衰减
    ax = axes[0, 0]
    m, c, k = 1.0, 0.4, 100.0
    omega_n = np.sqrt(k/m)
    zeta = c / (2*np.sqrt(k*m))
    omega_d = omega_n * np.sqrt(1-zeta**2)
    
    t = np.linspace(0, 10, 1000)
    u0, v0 = 1.0, 0.0
    
    # 精确解
    A = u0
    B = (v0 + zeta*omega_n*u0) / omega_d
    u_exact = np.exp(-zeta*omega_n*t) * (A*np.cos(omega_d*t) + B*np.sin(omega_d*t))
    
    # 数值解
    dt = 0.01
    F = np.zeros(len(t))
    u_num, _, _ = newmark_beta(m, c, k, F, dt)
    u_num = u_num[:len(t)]
    
    ax.plot(t, u_exact, 'k-', linewidth=2, label='Exact')
    ax.plot(t, u_num, 'r--', linewidth=1.5, alpha=0.8, label='Numerical')
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Displacement (m)')
    ax.set_title('Test 1: Free Vibration Decay')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 测试2:共振响应
    ax = axes[0, 1]
    m, c, k = 1.0, 0.2, 100.0
    omega_n = np.sqrt(k/m)
    zeta = c / (2*np.sqrt(k*m))
    
    # 扫频分析
    omega_ratios = np.linspace(0.5, 1.5, 100)
    H_exact = []
    H_numerical = []
    
    for beta in omega_ratios:
        omega = beta * omega_n
        H = 1 / np.sqrt((1-beta**2)**2 + (2*zeta*beta)**2)
        H_exact.append(H)
        
        # 数值稳态幅值
        t = np.linspace(0, 20, 2000)
        F = np.sin(omega*t)
        u, _, _ = newmark_beta(m, c, k, F, 0.01)
        H_numerical.append(np.max(u[-500:]))
    
    ax.plot(omega_ratios, H_exact, 'k-', linewidth=2, label='Exact')
    ax.plot(omega_ratios, H_numerical, 'ro', markersize=4, alpha=0.7, label='Numerical')
    ax.axvline(1.0, color='blue', linestyle='--', alpha=0.5, label='Resonance')
    ax.set_xlabel('Frequency Ratio β')
    ax.set_ylabel('Amplitude Ratio H')
    ax.set_title('Test 2: Frequency Response')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 测试3:能量守恒
    ax = axes[1, 0]
    m, k = 1.0, 100.0
    c = 0  # 无阻尼
    
    t = np.linspace(0, 10, 1000)
    u0, v0 = 1.0, 0.0
    omega_n = np.sqrt(k/m)
    
    # 精确解
    u_exact = u0 * np.cos(omega_n*t) + (v0/omega_n) * np.sin(omega_n*t)
    v_exact = -u0*omega_n * np.sin(omega_n*t) + v0 * np.cos(omega_n*t)
    
    E_kinetic = 0.5 * m * v_exact**2
    E_potential = 0.5 * k * u_exact**2
    E_total_exact = E_kinetic + E_potential
    
    # 数值解
    dt = 0.01
    F = np.zeros(len(t))
    u_num, v_num, _ = newmark_beta(m, c, k, F, dt)
    u_num = u_num[:len(t)]
    v_num = v_num[:len(t)]
    
    E_kinetic_num = 0.5 * m * v_num**2
    E_potential_num = 0.5 * k * u_num**2
    E_total_num = E_kinetic_num + E_potential_num
    
    ax.plot(t, E_total_exact, 'k-', linewidth=2, label='Exact (Constant)')
    ax.plot(t, E_total_num, 'r--', linewidth=1.5, alpha=0.8, label='Numerical')
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Total Energy (J)')
    ax.set_title('Test 3: Energy Conservation (Undamped)')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 测试4:模态正交性
    ax = axes[1, 1]
    L, E, I, rho, A = 1.0, 200e9, 1e-8, 7850, 1e-4
    
    n_elem = 32
    freqs, modes = beam_modal_analysis(n_elem, L, E, I, rho, A)
    
    # 计算模态质量矩阵(应近似对角)
    n_modes = 5
    modal_mass = np.zeros((n_modes, n_modes))
    
    for i in range(n_modes):
        for j in range(n_modes):
            phi_i = modes[:, i]
            phi_j = modes[:, j]
            # 简化的模态质量计算
            modal_mass[i, j] = np.dot(phi_i, phi_j)
    
    # 归一化
    for i in range(n_modes):
        modal_mass[i, :] /= np.sqrt(modal_mass[i, i])
        modal_mass[:, i] /= np.sqrt(modal_mass[i, i])
    
    im = ax.imshow(np.abs(modal_mass), cmap='Blues', aspect='auto')
    plt.colorbar(im, ax=ax)
    ax.set_xlabel('Mode j')
    ax.set_ylabel('Mode i')
    ax.set_title('Test 4: Modal Orthogonality Check')
    ax.set_xticks(range(n_modes))
    ax.set_yticks(range(n_modes))
    ax.set_xticklabels([f'{i+1}' for i in range(n_modes)])
    ax.set_yticklabels([f'{i+1}' for i in range(n_modes)])
    
    plt.tight_layout()
    plt.savefig('benchmark_tests.png', dpi=150, bbox_inches='tight')
    plt.close()
    print("基准测试图已保存")


# ==================== 主程序 ====================

def main():
    """主函数"""
    print("\n" + "="*70)
    print("案例1: 仿真验证方法")
    print("="*70)
    
    # 1. 代码验证
    print("\n1. 代码验证:数值解与解析解对比...")
    plot_code_verification()
    
    # 2. 网格收敛性分析
    print("\n2. 网格收敛性分析...")
    plot_mesh_convergence()
    
    # 3. 基准测试
    print("\n3. 基准测试问题验证...")
    plot_benchmark_tests()
    
    print("\n" + "="*70)
    print("所有验证分析完成!")
    print("="*70)


if __name__ == "__main__":
    main()

代码说明

  1. 参数设置区:定义系统的质量、刚度、阻尼等基本参数
  2. 核心计算模块:实现振动方程的求解算法
  3. 可视化模块:生成分析图表和动画
  4. 结果输出:保存图片文件供文档使用

运行上述代码将生成本主题所需的所有可视化素材。

Logo

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

更多推荐