结构动力学仿真-主题017-结构地震响应分析与设计反应谱

Source

第十七篇:结构地震响应分析与设计反应谱

摘要

地震是结构工程中最具破坏性的自然灾害之一,对结构进行地震响应分析是确保结构抗震安全性的关键环节。本教程系统地介绍结构地震响应分析的基本理论、计算方法和工程应用。首先阐述地震动的基本特性和地震波的数学描述;然后详细介绍反应谱理论及其计算方法;接着讲解设计反应谱的构建原理和规范要求;最后通过Python实现地震波生成、反应谱计算、设计反应谱构建以及多自由度体系的地震响应分析。通过本教程的学习,读者将掌握结构抗震设计的核心理论和实用技术。

关键词

地震响应,反应谱,设计反应谱,地震波,抗震设计,Python仿真


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

1. 引言

1.1 地震工程的重要性

地震是一种突发性强、破坏性大的自然灾害,对人类社会造成巨大的人员伤亡和财产损失。结构抗震设计是土木工程领域最重要的研究方向之一,其目标是使结构在遭遇地震时能够保持足够的承载能力和变形能力,避免倒塌,保护生命安全。

地震工程的主要研究内容

  1. 地震动特性研究:地震波的传播规律和地面运动特性
  2. 结构地震响应分析:结构在地震作用下的动力响应计算
  3. 抗震设计方法:基于性能的结构抗震设计理论和方法
  4. 减震隔震技术:通过特殊装置减小结构地震响应

1.2 地震动的基本特性

地震动是指地震引起的地面运动,通常用地面加速度、速度和位移时程来描述。

地震动的主要参数

  • 峰值加速度(PGA):地面加速度的最大值,是衡量地震动强度的基本指标
  • 峰值速度(PGV):地面速度的最大值,与结构的破坏程度密切相关
  • 峰值位移(PGD):地面位移的最大值,对长周期结构影响较大
  • 持时:强震动的持续时间,影响结构的累积损伤
  • 频谱特性:地震动的频率成分,决定对不同周期结构的破坏作用

地震动的随机性
地震动具有显著的随机性,即使在同一地点、相同震级的地震,其地面运动特性也可能差异很大。这种随机性来源于:

  • 震源机制的不确定性
  • 传播路径的复杂性
  • 局部场地条件的影响

1.3 反应谱理论的意义

反应谱是地震工程中最核心的概念之一,它描述了单自由度体系在不同周期下的最大响应。

反应谱的物理意义

  • 反应谱反映了地震动对不同周期结构的破坏潜力
  • 通过反应谱可以快速估算结构的地震响应
  • 设计反应谱是抗震规范的基础

反应谱的优势

  • 将复杂的时程分析简化为谱分析
  • 便于工程设计和规范制定
  • 可以考虑地震动的统计特性

2. 地震波的数学描述

2.1 地震波的类型

根据传播方式,地震波可分为体波和面波:

体波

  • P波(纵波):质点振动方向与波的传播方向一致,传播速度最快
  • S波(横波):质点振动方向与波的传播方向垂直,传播速度次之

面波

  • 瑞利波:沿地表传播,质点做椭圆运动
  • 洛夫波:沿地表传播,质点做水平横向振动

2.2 地震动的数学模型

确定性模型
用明确的数学函数描述地震动时程,常用的有:

  • 简谐波模型
  • 脉冲模型
  • 合成地震波模型

随机模型
将地震动视为随机过程,常用的有:

  • 白噪声模型
  • 过滤白噪声模型(Kanai-Tajimi模型)
  • 非平稳随机模型

2.3 Kanai-Tajimi模型

Kanai-Tajimi模型是描述地震动最常用的随机模型,它将地震动视为白噪声通过滤波器后的输出。

功率谱密度函数
Su¨g(ω)=1+4ζg2(ω/ωg)2[1−(ω/ωg)2]2+4ζg2(ω/ωg)2S0S_{\ddot{u}_g}(\omega) = \frac{1 + 4\zeta_g^2(\omega/\omega_g)^2}{[1-(\omega/\omega_g)^2]^2 + 4\zeta_g^2(\omega/\omega_g)^2} S_0Su¨g(ω)=[1(ω/ωg)2]2+4ζg2(ω/ωg)21+4ζg2(ω/ωg)2S0

其中:

  • ωg\omega_gωg:场地特征频率
  • ζg\zeta_gζg:场地阻尼比
  • S0S_0S0:白噪声强度

不同场地条件

  • 硬土:ωg=8π\omega_g = 8\piωg=8π rad/s, ζg=0.6\zeta_g = 0.6ζg=0.6
  • 中硬土:ωg=5π\omega_g = 5\piωg=5π rad/s, ζg=0.6\zeta_g = 0.6ζg=0.6
  • 软土:ωg=2.5π\omega_g = 2.5\piωg=2.5π rad/s, ζg=0.85\zeta_g = 0.85ζg=0.85

3. 反应谱理论

3.1 反应谱的定义

反应谱是指在给定地震动作用下,单自由度体系的最大响应(位移、速度或加速度)与体系自振周期之间的关系曲线。

位移反应谱
Sd(T)=max⁡t∣x(t)∣S_d(T) = \max_t |x(t)|Sd(T)=tmaxx(t)

速度反应谱
Sv(T)=max⁡t∣x˙(t)∣S_v(T) = \max_t |\dot{x}(t)|Sv(T)=tmaxx˙(t)

加速度反应谱
Sa(T)=max⁡t∣x¨(t)+u¨g(t)∣S_a(T) = \max_t |\ddot{x}(t) + \ddot{u}_g(t)|Sa(T)=tmaxx¨(t)+u¨g(t)

3.2 反应谱的计算方法

对于单自由度体系,运动方程为:
mx¨+cx˙+kx=−mu¨gm\ddot{x} + c\dot{x} + kx = -m\ddot{u}_gmx¨+cx˙+kx=mu¨g

或:
x¨+2ζωnx˙+ωn2x=−u¨g\ddot{x} + 2\zeta\omega_n\dot{x} + \omega_n^2x = -\ddot{u}_gx¨+2ζωnx˙+ωn2x=u¨g

计算步骤

  1. 选择一系列周期值 TiT_iTi
  2. 对每个周期,计算相应的 omegan=2π/Ti\\ omega_n = 2\pi/T_iomegan=2π/Ti
  3. 数值求解运动方程,得到响应时程
  4. 提取最大响应值
  5. 绘制反应谱曲线

3.3 反应谱的特性

反应谱的形状特征

  • 短周期段(T < 0.1s):加速度反应谱接近常数,等于地面最大加速度
  • 中周期段(0.1s < T < T_g):速度反应谱接近常数
  • 长周期段(T > T_g):位移反应谱接近常数

特征周期 TgT_gTg
特征周期是反应谱形状发生显著变化的周期点,与场地条件密切相关:

  • 硬土:Tg≈0.2T_g \approx 0.2Tg0.2 s
  • 中硬土:Tg≈0.4T_g \approx 0.4Tg0.4 s
  • 软土:Tg≈0.6T_g \approx 0.6Tg0.6 s

3.4 伪反应谱

伪反应谱是工程实践中常用的简化概念:

伪速度反应谱
PSv=ωnSdPS_v = \omega_n S_dPSv=ωnSd

伪加速度反应谱
PSa=ωn2SdPS_a = \omega_n^2 S_dPSa=ωn2Sd

对于小阻尼情况,伪反应谱与真实反应谱非常接近。


4. 设计反应谱

4.1 设计反应谱的概念

设计反应谱是基于大量地震记录统计分析得到的平滑化反应谱,用于工程抗震设计。

设计反应谱的特点

  • 基于统计平均,具有代表性
  • 形状平滑,便于工程应用
  • 考虑场地条件的影响
  • 具有规定的超越概率

4.2 中国规范设计反应谱

根据《建筑抗震设计规范》(GB 50011),设计反应谱的表达式为:

上升段(0<T<0.10 < T < 0.10<T<0.1 s)
α=η2αmax\alpha = \eta_2 \alpha_{max}α=η2αmax

平台段(0.1≤T≤Tg0.1 \leq T \leq T_g0.1TTg
α=αmax\alpha = \alpha_{max}α=αmax

下降段(Tg<T≤5TgT_g < T \leq 5T_gTg<T5Tg
α=(TgT)γαmax\alpha = \left(\frac{T_g}{T}\right)^\gamma \alpha_{max}α=(TTg)γαmax

长周期段(5Tg<T≤6.05T_g < T \leq 6.05Tg<T6.0 s)
α=[η20.2γ−η1(T−5Tg)]αmax\alpha = [\eta_2 0.2^\gamma - \eta_1(T - 5T_g)] \alpha_{max}α=[η20.2γη1(T5Tg)]αmax

其中:

  • αmax\alpha_{max}αmax:地震影响系数最大值
  • TgT_gTg:特征周期
  • γ\gammaγ:下降段衰减指数
  • η1\eta_1η1:直线下降段斜率调整系数
  • η2\eta_2η2:阻尼调整系数

4.3 地震影响系数最大值

地震影响系数最大值与设防烈度和设计地震分组有关:

设防烈度 6度 7度 8度 9度
αmax\alpha_{max}αmax 0.04 0.08(0.12) 0.16(0.24) 0.32

4.4 特征周期

特征周期与场地类别和设计地震分组有关:

场地类别 第一组 第二组 第三组
I0 0.20 0.25 0.30
I1 0.25 0.30 0.35
II 0.35 0.40 0.45
III 0.45 0.55 0.65
IV 0.65 0.75 0.90

5. 多自由度体系地震响应分析

5.1 振型分解法

对于多自由度体系,地震响应可以通过振型分解法计算:

运动方程
Mx¨+Cx˙+Kx=−M1u¨g\mathbf{M}\ddot{\mathbf{x}} + \mathbf{C}\dot{\mathbf{x}} + \mathbf{K}\mathbf{x} = -\mathbf{M}\mathbf{1}\ddot{u}_gMx¨+Cx˙+Kx=M1u¨g

振型分解
x(t)=∑i=1nϕiqi(t)\mathbf{x}(t) = \sum_{i=1}^{n} \phi_i q_i(t)x(t)=i=1nϕiqi(t)

广义坐标方程
q¨i+2ζiωiq˙i+ωi2qi=−γiu¨g\ddot{q}_i + 2\zeta_i\omega_i\dot{q}_i + \omega_i^2 q_i = -\gamma_i \ddot{u}_gq¨i+2ζiωiq˙i+ωi2qi=γiu¨g

其中,γi=ϕiTM1ϕiTMϕi\gamma_i = \frac{\phi_i^T \mathbf{M} \mathbf{1}}{\phi_i^T \mathbf{M} \phi_i}γi=ϕiTMϕiϕiTM1 为振型参与系数。

5.2 振型组合方法

由于各振型的最大响应不会同时发生,需要采用适当的组合方法:

SRSS法(平方和开方法)
R=∑i=1nRi2R = \sqrt{\sum_{i=1}^{n} R_i^2}R=i=1nRi2

适用于频率分离较好的情况。

CQC法(完全二次组合法)
R=∑i=1n∑j=1nρijRiRjR = \sqrt{\sum_{i=1}^{n}\sum_{j=1}^{n} \rho_{ij} R_i R_j}R=i=1nj=1nρijRiRj

适用于频率密集的情况,其中 ρij\rho_{ij}ρij 为互相关系数。

5.3 底部剪力法

对于规则结构,可以采用简化的底部剪力法:

总水平地震作用
FEk=α1GeqF_{Ek} = \alpha_1 G_{eq}FEk=α1Geq

其中:

  • α1\alpha_1α1:相应于结构基本周期的水平地震影响系数
  • GeqG_{eq}Geq:结构等效总重力荷载

各层水平地震作用
Fi=GiHi∑j=1nGjHjFEk(1−δn)F_i = \frac{G_i H_i}{\sum_{j=1}^{n} G_j H_j} F_{Ek}(1 - \delta_n)Fi=j=1nGjHjGiHiFEk(1δn)

顶部附加地震作用
ΔFn=δnFEk\Delta F_n = \delta_n F_{Ek}ΔFn=δnFEk


6. Python实现案例

6.1 案例1:地震波生成与处理

本案例实现基于Kanai-Tajimi模型的地震波生成和频谱分析。

# -*- coding: utf-8 -*-
"""
主题017:结构地震响应分析与设计反应谱 - 案例1
地震波生成与处理
"""
import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
import warnings
warnings.filterwarnings('ignore')
import os

output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题017'
os.makedirs(output_dir, exist_ok=True)

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 70)
print("主题017:结构地震响应分析与设计反应谱")
print("案例1:地震波生成与处理")
print("=" * 70)

# ============================================================
# Kanai-Tajimi模型参数
# ============================================================
print("\nKanai-Tajimi模型参数:")
print("-" * 70)

# 不同场地条件
site_conditions = {
    
      
    '硬土': {
    
      'omega_g': 8*np.pi, 'zeta_g': 0.6, 'S0': 0.01},
    '中硬土': {
    
      'omega_g': 5*np.pi, 'zeta_g': 0.6, 'S0': 0.02},
    '软土': {
    
      'omega_g': 2.5*np.pi, 'zeta_g': 0.85, 'S0': 0.05}
}

for site, params in site_conditions.items():
    print(f"  {
      
        site}: ω_g = {
      
        params['omega_g']:.4f} rad/s, ζ_g = {
      
        params['zeta_g']}, S0 = {
      
        params['S0']}")

# ============================================================
# 生成地震波
# ============================================================
def generate_earthquake(dt, t_end, site_type='中硬土', seed=None):
    """
    基于Kanai-Tajimi模型生成地震波
    """
    if seed is not None:
        np.random.seed(seed)
    
    params = site_conditions[site_type]
    omega_g = params['omega_g']
    zeta_g = params['zeta_g']
    S0 = params['S0']
    
    t = np.arange(0, t_end, dt)
    n = len(t)
    
    # 生成白噪声
    white_noise = np.random.randn(n)
    
    # 构建Kanai-Tajimi滤波器
    # H(ω) = (ω_g^2 + 2iζ_gω_gω) / (ω_g^2 - ω^2 + 2iζ_gω_gω)
    
    # 使用状态空间实现
    # 滤波器系数
    a = [1, 2*zeta_g*omega_g, omega_g**2]
    b = [omega_g**2, 2*zeta_g*omega_g, 0]
    
    # 应用滤波器
    filtered = signal.lfilter(b, a, white_noise)
    
    # 应用包络函数(模拟非平稳性)
    t_mid = t_end / 2
    envelope = np.zeros(n)
    for i, ti in enumerate(t):
        if ti < t_mid:
            envelope[i] = (ti / t_mid)**2
        else:
            envelope[i] = np.exp(-2 * (ti - t_mid) / t_mid)
    
    # 合成地震波
    acc = filtered * envelope * np.sqrt(S0 / dt)
    
    # 标准化(调整PGA到目标值)
    target_pga = 0.2 * 9.8  # 目标PGA = 0.2g
    current_pga = np.max(np.abs(acc))
    acc = acc * target_pga / current_pga
    
    return t, acc

# ============================================================
# 计算速度和位移
# ============================================================
def integrate_acceleration(t, acc):
    """通过积分计算速度和位移"""
    dt = t[1] - t[0]
    
    # 高通滤波去除趋势
    sos = signal.butter(4, 0.1, 'high', fs=1/dt, output='sos')
    acc_filtered = signal.sosfilt(sos, acc)
    
    # 积分得到速度
    vel = np.cumsum(acc_filtered) * dt
    vel = signal.sosfilt(sos, vel)  # 再次滤波
    
    # 积分得到位移
    disp = np.cumsum(vel) * dt
    disp = signal.sosfilt(sos, disp)  # 再次滤波
    
    return vel, disp

# ============================================================
# 生成不同场地的地震波
# ============================================================
print("\n生成地震波...")
print("-" * 70)

dt = 0.01
t_end = 30.0

earthquake_data = {
    
      }
for site in site_conditions.keys():
    print(f"  生成{
      
        site}地震波...")
    t, acc = generate_earthquake(dt, t_end, site, seed=42)
    vel, disp = integrate_acceleration(t, acc)
    
    earthquake_data[site] = {
    
      
        't': t,
        'acc': acc,
        'vel': vel,
        'disp': disp,
        'pga': np.max(np.abs(acc)),
        'pgv': np.max(np.abs(vel)),
        'pgd': np.max(np.abs(disp))
    }

# ============================================================
# 打印地震波参数
# ============================================================
print("\n地震波参数:")
print("-" * 70)
print(f"{
      
        '场地':<10} {
      
        'PGA (m/s²)':<15} {
      
        'PGV (m/s)':<15} {
      
        'PGD (m)':<15}")
print("-" * 70)
for site, data in earthquake_data.items():
    print(f"{
      
        site:<10} {
      
        data['pga']:<15.4f} {
      
        data['pgv']:<15.4f} {
      
        data['pgd']:<15.4f}")

# ============================================================
# 绘制地震波时程
# ============================================================
print("\n正在生成可视化图表...")

fig1, axes = plt.subplots(3, 1, figsize=(14, 12))

colors = ['steelblue', 'forestgreen', 'coral']
for i, (site, data) in enumerate(earthquake_data.items()):
    # 加速度
    axes[0].plot(data['t'], data['acc'], color=colors[i], linewidth=1, label=f'{
      
        site}')
    # 速度
    axes[1].plot(data['t'], data['vel'], color=colors[i], linewidth=1, label=f'{
      
        site}')
    # 位移
    axes[2].plot(data['t'], data['disp'], color=colors[i], linewidth=1, label=f'{
      
        site}')

axes[0].set_ylabel('Acceleration (m/s²)', fontsize=12)
axes[0].set_title('Ground Acceleration Time History', fontsize=14, fontweight='bold')
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)

axes[1].set_ylabel('Velocity (m/s)', fontsize=12)
axes[1].set_title('Ground Velocity Time History', fontsize=14, fontweight='bold')
axes[1].legend(fontsize=11)
axes[1].grid(True, alpha=0.3)

axes[2].set_xlabel('Time (s)', fontsize=12)
axes[2].set_ylabel('Displacement (m)', fontsize=12)
axes[2].set_title('Ground Displacement Time History', fontsize=14, fontweight='bold')
axes[2].legend(fontsize=11)
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/earthquake_time_history.png', dpi=150, bbox_inches='tight')
plt.close()
print("  地震波时程图已保存")

# ============================================================
# 傅里叶谱分析
# ============================================================
print("\n进行傅里叶谱分析...")

fig2, axes = plt.subplots(1, 2, figsize=(14, 5))

for i, (site, data) in enumerate(earthquake_data.items()):
    # FFT
    n = len(data['t'])
    freqs = np.fft.fftfreq(n, dt)[:n//2]
    fft_values = np.fft.fft(data['acc'])
    fft_magnitude = np.abs(fft_values)[:n//2] * 2 / n
    
    # 傅里叶幅值谱
    axes[0].semilogy(freqs, fft_magnitude, color=colors[i], linewidth=1.5, label=f'{
      
        site}')
    
    # 功率谱密度
    f_psd, psd = signal.welch(data['acc'], fs=1/dt, nperseg=1024)
    axes[1].semilogy(f_psd, psd, color=colors[i], linewidth=1.5, label=f'{
      
        site}')

axes[0].set_xlabel('Frequency (Hz)', fontsize=12)
axes[0].set_ylabel('Fourier Amplitude', fontsize=12)
axes[0].set_title('Fourier Amplitude Spectrum', fontsize=14, fontweight='bold')
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)
axes[0].set_xlim(0, 25)

axes[1].set_xlabel('Frequency (Hz)', fontsize=12)
axes[1].set_ylabel('PSD (m²/s⁴/Hz)', fontsize=12)
axes[1].set_title('Power Spectral Density', fontsize=14, fontweight='bold')
axes[1].legend(fontsize=11)
axes[1].grid(True, alpha=0.3)
axes[1].set_xlim(0, 25)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/earthquake_spectrum.png', dpi=150, bbox_inches='tight')
plt.close()
print("  地震波频谱图已保存")

# ============================================================
# 生成地震波动画
# ============================================================
print("\n正在生成地震波动画...")

fig3, axes = plt.subplots(3, 1, figsize=(12, 10))

site = '中硬土'
data = earthquake_data[site]

# 初始化线条
line_acc, = axes[0].plot([], [], 'b-', linewidth=1.5)
line_vel, = axes[1].plot([], [], 'g-', linewidth=1.5)
line_disp, = axes[2].plot([], [], 'r-', linewidth=1.5)

# 设置坐标轴
axes[0].set_xlim(0, t_end)
axes[0].set_ylim(np.min(data['acc'])*1.1, np.max(data['acc'])*1.1)
axes[0].set_ylabel('Acceleration (m/s²)', fontsize=11)
axes[0].set_title(f'Earthquake Ground Motion ({
      
        site})', fontsize=13, fontweight='bold')
axes[0].grid(True, alpha=0.3)

axes[1].set_xlim(0, t_end)
axes[1].set_ylim(np.min(data['vel'])*1.1, np.max(data['vel'])*1.1)
axes[1].set_ylabel('Velocity (m/s)', fontsize=11)
axes[1].grid(True, alpha=0.3)

axes[2].set_xlim(0, t_end)
axes[2].set_ylim(np.min(data['disp'])*1.1, np.max(data['disp'])*1.1)
axes[2].set_xlabel('Time (s)', fontsize=11)
axes[2].set_ylabel('Displacement (m)', fontsize=11)
axes[2].grid(True, alpha=0.3)

from matplotlib.animation import FuncAnimation

def update(frame):
    idx = frame * 10
    if idx > len(data['t']):
        idx = len(data['t'])
    
    line_acc.set_data(data['t'][:idx], data['acc'][:idx])
    line_vel.set_data(data['t'][:idx], data['vel'][:idx])
    line_disp.set_data(data['t'][:idx], data['disp'][:idx])
    
    return line_acc, line_vel, line_disp

n_frames = len(data['t']) // 10
anim = FuncAnimation(fig3, update, frames=n_frames, interval=50, blit=True)
anim.save(f'{
      
        output_dir}/earthquake_animation.gif', writer='pillow', fps=20, dpi=100)
plt.close()
print("  地震波动画已保存")

print("\n" + "=" * 70)
print("地震波生成与处理完成!")
print("=" * 70)

6.2 案例2:反应谱计算

本案例实现单自由度体系地震反应谱的计算。

# -*- coding: utf-8 -*-
"""
主题017:结构地震响应分析与设计反应谱 - 案例2
反应谱计算
"""
import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
import os

output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题017'
os.makedirs(output_dir, exist_ok=True)

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 70)
print("主题017:结构地震响应分析与设计反应谱")
print("案例2:反应谱计算")
print("=" * 70)

# ============================================================
# 生成地震波
# ============================================================
def generate_earthquake(dt, t_end, omega_g=5*np.pi, zeta_g=0.6, S0=0.02, seed=None):
    """生成地震波"""
    if seed is not None:
        np.random.seed(seed)
    
    t = np.arange(0, t_end, dt)
    n = len(t)
    
    white_noise = np.random.randn(n)
    
    freqs = np.fft.fftfreq(n, dt)
    omega = 2 * np.pi * freqs
    
    omega_sq = omega**2
    numerator = omega_g**4 + 4*zeta_g**2*omega_g**2*omega_sq
    denominator = (omega_g**2 - omega_sq)**2 + 4*zeta_g**2*omega_g**2*omega_sq
    
    psd = np.zeros_like(omega)
    mask = denominator > 1e-10
    psd[mask] = S0 * numerator[mask] / denominator[mask]
    
    white_fft = np.fft.fft(white_noise)
    filtered_fft = white_fft * np.sqrt(psd * n * dt / 2)
    filtered = np.real(np.fft.ifft(filtered_fft))
    
    t_mid = t_end / 2
    envelope = np.zeros(n)
    for i, ti in enumerate(t):
        if ti < t_mid:
            envelope[i] = (ti / t_mid)**2
        else:
            envelope[i] = np.exp(-2 * (ti - t_mid) / t_mid)
    
    acc = filtered * envelope
    target_pga = 0.2 * 9.8
    current_pga = np.max(np.abs(acc))
    if current_pga > 0:
        acc = acc * target_pga / current_pga
    
    return t, acc

# ============================================================
# 反应谱计算函数
# ============================================================
def compute_response_spectrum(acc, dt, T_range, zeta=0.05):
    """
    计算反应谱
    
    参数:
        acc: 地面加速度时程
        dt: 时间步长
        T_range: 周期范围
        zeta: 阻尼比
    
    返回:
        Sd: 位移反应谱
        Sv: 速度反应谱
        Sa: 加速度反应谱
        PSv: 伪速度反应谱
        PSa: 伪加速度反应谱
    """
    n_T = len(T_range)
    Sd = np.zeros(n_T)
    Sv = np.zeros(n_T)
    Sa = np.zeros(n_T)
    PSv = np.zeros(n_T)
    PSa = np.zeros(n_T)
    
    for i, T in enumerate(T_range):
        if T == 0:
            Sd[i] = 0
            Sv[i] = 0
            Sa[i] = np.max(np.abs(acc))
            PSv[i] = 0
            PSa[i] = np.max(np.abs(acc))
        else:
            omega_n = 2 * np.pi / T
            
            # Newmark-beta方法参数(平均加速度法)
            gamma = 0.5
            beta = 0.25
            
            # 初始化
            n_steps = len(acc)
            x = np.zeros(n_steps)
            v = np.zeros(n_steps)
            a = np.zeros(n_steps)
            
            # 等效刚度
            k_eff = omega_n**2 + gamma / (beta * dt) * 2 * zeta * omega_n + 1 / (beta * dt**2)
            
            for j in range(n_steps - 1):
                # 等效荷载
                p_eff = -acc[j+1] + (1/(beta*dt**2))*x[j] + (1/(beta*dt))*v[j] + (1/(2*beta)-1)*a[j]
                
                # 计算位移
                x[j+1] = p_eff / k_eff
                
                # 计算速度和加速度
                v[j+1] = gamma/(beta*dt)*(x[j+1]-x[j]) + (1-gamma/beta)*v[j] + dt*(1-gamma/(2*beta))*a[j]
                a[j+1] = 1/(beta*dt**2)*(x[j+1]-x[j]) - 1/(beta*dt)*v[j] - (1/(2*beta)-1)*a[j]
            
            # 计算绝对加速度
            abs_acc = a + acc
            
            # 提取最大值
            Sd[i] = np.max(np.abs(x))
            Sv[i] = np.max(np.abs(v))
            Sa[i] = np.max(np.abs(abs_acc))
            PSv[i] = omega_n * Sd[i]
            PSa[i] = omega_n**2 * Sd[i]
    
    return Sd, Sv, Sa, PSv, PSa

# ============================================================
# 生成地震波并计算反应谱
# ============================================================
print("\n生成地震波...")
print("-" * 70)

dt = 0.01
t_end = 30.0
t, acc = generate_earthquake(dt, t_end, seed=42)

print(f"  时间步长: {
      
        dt} s")
print(f"  持续时间: {
      
        t_end} s")
print(f"  PGA: {
      
        np.max(np.abs(acc)):.4f} m/s²")

# 计算反应谱
print("\n计算反应谱...")
print("-" * 70)

T_range = np.linspace(0.01, 6.0, 200)
zeta_values = [0.02, 0.05, 0.10]

response_spectra = {
    
      }
for zeta in zeta_values:
    print(f"  计算阻尼比 ζ = {
      
        zeta} 的反应谱...")
    Sd, Sv, Sa, PSv, PSa = compute_response_spectrum(acc, dt, T_range, zeta)
    response_spectra[zeta] = {
    
      
        'Sd': Sd, 'Sv': Sv, 'Sa': Sa, 'PSv': PSv, 'PSa': PSa
    }

# ============================================================
# 绘制反应谱
# ============================================================
print("\n正在生成可视化图表...")

fig1, axes = plt.subplots(2, 2, figsize=(14, 10))

colors = ['steelblue', 'forestgreen', 'coral']

for i, zeta in enumerate(zeta_values):
    data = response_spectra[zeta]
    
    # 位移反应谱
    axes[0, 0].plot(T_range, data['Sd']*1000, color=colors[i], linewidth=2, label=f'ζ={
      
        zeta}')
    
    # 速度反应谱
    axes[0, 1].plot(T_range, data['Sv'], color=colors[i], linewidth=2, label=f'ζ={
      
        zeta}')
    
    # 加速度反应谱
    axes[1, 0].plot(T_range, data['Sa']/9.8, color=colors[i], linewidth=2, label=f'ζ={
      
        zeta}')
    
    # 伪加速度反应谱
    axes[1, 1].plot(T_range, data['PSa']/9.8, color=colors[i], linewidth=2, label=f'ζ={
      
        zeta}')

axes[0, 0].set_xlabel('Period (s)', fontsize=12)
axes[0, 0].set_ylabel('Displacement (mm)', fontsize=12)
axes[0, 0].set_title('Displacement Response Spectrum', fontsize=14, fontweight='bold')
axes[0, 0].legend(fontsize=11)
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].set_xlabel('Period (s)', fontsize=12)
axes[0, 1].set_ylabel('Velocity (m/s)', fontsize=12)
axes[0, 1].set_title('Velocity Response Spectrum', fontsize=14, fontweight='bold')
axes[0, 1].legend(fontsize=11)
axes[0, 1].grid(True, alpha=0.3)

axes[1, 0].set_xlabel('Period (s)', fontsize=12)
axes[1, 0].set_ylabel('Acceleration (g)', fontsize=12)
axes[1, 0].set_title('Absolute Acceleration Response Spectrum', fontsize=14, fontweight='bold')
axes[1, 0].legend(fontsize=11)
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].set_xlabel('Period (s)', fontsize=12)
axes[1, 1].set_ylabel('Pseudo Acceleration (g)', fontsize=12)
axes[1, 1].set_title('Pseudo Acceleration Response Spectrum', fontsize=14, fontweight='bold')
axes[1, 1].legend(fontsize=11)
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/response_spectrum.png', dpi=150, bbox_inches='tight')
plt.close()
print("  反应谱图已保存")

# ============================================================
# 绘制真实反应谱与伪反应谱对比
# ============================================================
fig2, axes = plt.subplots(1, 2, figsize=(14, 5))

zeta = 0.05
data = response_spectra[zeta]

# 速度反应谱对比
axes[0].plot(T_range, data['Sv'], 'b-', linewidth=2, label='True Velocity')
axes[0].plot(T_range, data['PSv'], 'r--', linewidth=2, label='Pseudo Velocity')
axes[0].set_xlabel('Period (s)', fontsize=12)
axes[0].set_ylabel('Velocity (m/s)', fontsize=12)
axes[0].set_title('True vs Pseudo Velocity Response Spectrum (ζ=0.05)', fontsize=13, fontweight='bold')
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)

# 加速度反应谱对比
axes[1].plot(T_range, data['Sa']/9.8, 'b-', linewidth=2, label='True Acceleration')
axes[1].plot(T_range, data['PSa']/9.8, 'r--', linewidth=2, label='Pseudo Acceleration')
axes[1].set_xlabel('Period (s)', fontsize=12)
axes[1].set_ylabel('Acceleration (g)', fontsize=12)
axes[1].set_title('True vs Pseudo Acceleration Response Spectrum (ζ=0.05)', fontsize=13, fontweight='bold')
axes[1].legend(fontsize=11)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/true_vs_pseudo_spectrum.png', dpi=150, bbox_inches='tight')
plt.close()
print("  真实与伪反应谱对比图已保存")

# ============================================================
# 生成反应谱动画
# ============================================================
print("\n正在生成反应谱动画...")

fig3, axes = plt.subplots(2, 2, figsize=(14, 10))

zeta = 0.05
data = response_spectra[zeta]

# 初始化线条
line_sd, = axes[0, 0].plot([], [], 'b-', linewidth=2)
line_sv, = axes[0, 1].plot([], [], 'g-', linewidth=2)
line_sa, = axes[1, 0].plot([], [], 'r-', linewidth=2)
line_psa, = axes[1, 1].plot([], [], 'm-', linewidth=2)

# 设置坐标轴
axes[0, 0].set_xlim(0, 6)
axes[0, 0].set_ylim(0, np.max(data['Sd'])*1000*1.1)
axes[0, 0].set_xlabel('Period (s)', fontsize=11)
axes[0, 0].set_ylabel('Displacement (mm)', fontsize=11)
axes[0, 0].set_title('Displacement Response Spectrum', fontsize=12, fontweight='bold')
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].set_xlim(0, 6)
axes[0, 1].set_ylim(0, np.max(data['Sv'])*1.1)
axes[0, 1].set_xlabel('Period (s)', fontsize=11)
axes[0, 1].set_ylabel('Velocity (m/s)', fontsize=11)
axes[0, 1].set_title('Velocity Response Spectrum', fontsize=12, fontweight='bold')
axes[0, 1].grid(True, alpha=0.3)

axes[1, 0].set_xlim(0, 6)
axes[1, 0].set_ylim(0, np.max(data['Sa']/9.8)*1.1)
axes[1, 0].set_xlabel('Period (s)', fontsize=11)
axes[1, 0].set_ylabel('Acceleration (g)', fontsize=11)
axes[1, 0].set_title('Acceleration Response Spectrum', fontsize=12, fontweight='bold')
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].set_xlim(0, 6)
axes[1, 1].set_ylim(0, np.max(data['PSa']/9.8)*1.1)
axes[1, 1].set_xlabel('Period (s)', fontsize=11)
axes[1, 1].set_ylabel('Pseudo Acceleration (g)', fontsize=11)
axes[1, 1].set_title('Pseudo Acceleration Response Spectrum', fontsize=12, fontweight='bold')
axes[1, 1].grid(True, alpha=0.3)

from matplotlib.animation import FuncAnimation

def update(frame):
    idx = frame + 1
    if idx > len(T_range):
        idx = len(T_range)
    
    line_sd.set_data(T_range[:idx], data['Sd'][:idx]*1000)
    line_sv.set_data(T_range[:idx], data['Sv'][:idx])
    line_sa.set_data(T_range[:idx], data['Sa'][:idx]/9.8)
    line_psa.set_data(T_range[:idx], data['PSa'][:idx]/9.8)
    
    return line_sd, line_sv, line_sa, line_psa

n_frames = len(T_range)
anim = FuncAnimation(fig3, update, frames=n_frames, interval=30, blit=True)
anim.save(f'{
      
        output_dir}/response_spectrum_animation.gif', writer='pillow', fps=30, dpi=100)
plt.close()
print("  反应谱动画已保存")

# ============================================================
# 打印关键结果
# ============================================================
print("\n反应谱关键结果:")
print("-" * 70)

for zeta in zeta_values:
    data = response_spectra[zeta]
    max_sd_idx = np.argmax(data['Sd'])
    max_sa_idx = np.argmax(data['Sa'])
    
    print(f"\n阻尼比 ζ = {
      
        zeta}:")
    print(f"  最大位移: {
      
        data['Sd'][max_sd_idx]*1000:.4f} mm (T={
      
        T_range[max_sd_idx]:.2f}s)")
    print(f"  最大加速度: {
      
        data['Sa'][max_sa_idx]/9.8:.4f} g (T={
      
        T_range[max_sa_idx]:.2f}s)")

print("\n" + "=" * 70)
print("反应谱计算完成!")
print("=" * 70)

6.3 案例3:设计反应谱(中国规范GB 50011)

本案例实现中国《建筑抗震设计规范》(GB 50011)的设计反应谱。

# -*- coding: utf-8 -*-
"""
主题017:结构地震响应分析与设计反应谱 - 案例3
设计反应谱(中国规范GB 50011)
"""
import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
import os

output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题017'
os.makedirs(output_dir, exist_ok=True)

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 70)
print("主题017:结构地震响应分析与设计反应谱")
print("案例3:设计反应谱(中国规范GB 50011)")
print("=" * 70)

# ============================================================
# 中国规范设计反应谱参数
# ============================================================
print("\n中国规范设计反应谱参数:")
print("-" * 70)

# 地震影响系数最大值(多遇地震)
alpha_max_table = {
    
      
    '6度': 0.04,
    '7度': 0.08,
    '7度(0.15g)': 0.12,
    '8度': 0.16,
    '8度(0.30g)': 0.24,
    '9度': 0.32
}

# 特征周期(第一组)
Tg_table = {
    
      
    'I0': 0.20,
    'I1': 0.25,
    'II': 0.35,
    'III': 0.45,
    'IV': 0.65
}

print("  地震影响系数最大值(多遇地震):")
for intensity, alpha_max in alpha_max_table.items():
    print(f"    {
      
        intensity}: αmax = {
      
        alpha_max}")

print("\n  特征周期(第一组):")
for site, Tg in Tg_table.items():
    print(f"    {
      
        site}类场地: Tg = {
      
        Tg} s")

# ============================================================
# 设计反应谱计算函数
# ============================================================
def compute_design_spectrum(T, alpha_max, Tg, zeta=0.05):
    """
    计算中国规范设计反应谱
    
    参数:
        T: 周期数组
        alpha_max: 地震影响系数最大值
        Tg: 特征周期
        zeta: 阻尼比
    
    返回:
        alpha: 地震影响系数
    """
    # 阻尼调整系数
    gamma = 0.9 + (0.05 - zeta) / (0.3 + 6 * zeta)
    eta2 = 1 + (0.05 - zeta) / (0.08 + 1.6 * zeta)
    eta1 = 0.02 + (0.05 - zeta) / (4 + 32 * zeta)
    
    # 确保系数在合理范围
    gamma = max(0.5, min(gamma, 1.0))
    eta2 = max(0.5, min(eta2, 1.0))
    eta1 = max(0.0, eta1)
    
    alpha = np.zeros_like(T)
    
    for i, t in enumerate(T):
        if t < 0.1:
            # 上升段
            alpha[i] = (0.45 + 5.5 * t) * alpha_max
        elif t <= Tg:
            # 平台段
            alpha[i] = alpha_max
        elif t <= 5 * Tg:
            # 下降段
            alpha[i] = (Tg / t) ** gamma * alpha_max
        elif t <= 6.0:
            # 长周期段
            alpha[i] = (eta2 * 0.2 ** gamma - eta1 * (t - 5 * Tg)) * alpha_max
        else:
            alpha[i] = 0
    
    return alpha

# ============================================================
# 生成设计反应谱
# ============================================================
print("\n生成设计反应谱...")
print("-" * 70)

T = np.linspace(0.01, 6.0, 200)

# 不同设防烈度的设计反应谱
fig1, axes = plt.subplots(1, 2, figsize=(14, 6))

colors_intensity = ['steelblue', 'forestgreen', 'coral', 'purple', 'brown', 'crimson']
Tg = 0.35  # II类场地

for i, (intensity, alpha_max) in enumerate(alpha_max_table.items()):
    alpha = compute_design_spectrum(T, alpha_max, Tg)
    axes[0].plot(T, alpha, color=colors_intensity[i], linewidth=2, label=f'{
      
        intensity}')

axes[0].set_xlabel('Period (s)', fontsize=12)
axes[0].set_ylabel('Seismic Influence Coefficient α', fontsize=12)
axes[0].set_title('Design Response Spectrum - Different Intensities (Site II)', fontsize=13, fontweight='bold')
axes[0].legend(fontsize=10)
axes[0].grid(True, alpha=0.3)
axes[0].set_xlim(0, 6)

# 不同场地类别的设计反应谱
alpha_max = 0.16  # 8度设防
colors_site = ['steelblue', 'forestgreen', 'coral', 'purple', 'brown']

for i, (site, Tg_val) in enumerate(Tg_table.items()):
    alpha = compute_design_spectrum(T, alpha_max, Tg_val)
    axes[1].plot(T, alpha, color=colors_site[i], linewidth=2, label=f'Site {
      
        site}')

axes[1].set_xlabel('Period (s)', fontsize=12)
axes[1].set_ylabel('Seismic Influence Coefficient α', fontsize=12)
axes[1].set_title('Design Response Spectrum - Different Site Classes (Intensity 8)', fontsize=13, fontweight='bold')
axes[1].legend(fontsize=10)
axes[1].grid(True, alpha=0.3)
axes[1].set_xlim(0, 6)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/design_spectrum_comparison.png', dpi=150, bbox_inches='tight')
plt.close()
print("  不同设防烈度设计反应谱已保存")

# 不同阻尼比的设计反应谱
fig2, ax = plt.subplots(figsize=(10, 6))

alpha_max = 0.16
Tg = 0.35
zeta_values = [0.02, 0.05, 0.10, 0.20]
colors_zeta = ['steelblue', 'forestgreen', 'coral', 'purple']

for i, zeta in enumerate(zeta_values):
    alpha = compute_design_spectrum(T, alpha_max, Tg, zeta)
    ax.plot(T, alpha, color=colors_zeta[i], linewidth=2, label=f'ζ={
      
        zeta}')

ax.set_xlabel('Period (s)', fontsize=12)
ax.set_ylabel('Seismic Influence Coefficient α', fontsize=12)
ax.set_title('Design Response Spectrum - Different Damping Ratios', fontsize=14, fontweight='bold')
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 6)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/design_spectrum_damping.png', dpi=150, bbox_inches='tight')
plt.close()
print("  不同阻尼比设计反应谱已保存")

# 综合对比图
fig3, axes = plt.subplots(2, 2, figsize=(14, 10))

# 子图1:不同设防烈度
for i, (intensity, alpha_max) in enumerate(alpha_max_table.items()):
    alpha = compute_design_spectrum(T, alpha_max, Tg)
    axes[0, 0].plot(T, alpha, color=colors_intensity[i], linewidth=2, label=f'{
      
        intensity}')
axes[0, 0].set_title('Different Seismic Intensities', fontsize=12, fontweight='bold')
axes[0, 0].set_xlabel('Period (s)')
axes[0, 0].set_ylabel('α')
axes[0, 0].legend(fontsize=9)
axes[0, 0].grid(True, alpha=0.3)

# 子图2:不同场地类别
for i, (site, Tg_val) in enumerate(Tg_table.items()):
    alpha = compute_design_spectrum(T, alpha_max, Tg_val)
    axes[0, 1].plot(T, alpha, color=colors_site[i], linewidth=2, label=f'Site {
      
        site}')
axes[0, 1].set_title('Different Site Classes', fontsize=12, fontweight='bold')
axes[0, 1].set_xlabel('Period (s)')
axes[0, 1].set_ylabel('α')
axes[0, 1].legend(fontsize=9)
axes[0, 1].grid(True, alpha=0.3)

# 子图3:不同阻尼比
for i, zeta in enumerate(zeta_values):
    alpha = compute_design_spectrum(T, alpha_max, Tg, zeta)
    axes[1, 0].plot(T, alpha, color=colors_zeta[i], linewidth=2, label=f'ζ={
      
        zeta}')
axes[1, 0].set_title('Different Damping Ratios', fontsize=12, fontweight='bold')
axes[1, 0].set_xlabel('Period (s)')
axes[1, 0].set_ylabel('α')
axes[1, 0].legend(fontsize=9)
axes[1, 0].grid(True, alpha=0.3)

# 子图4:规范反应谱四段特征
alpha = compute_design_spectrum(T, alpha_max, Tg)
axes[1, 1].plot(T, alpha, 'b-', linewidth=2.5)
axes[1, 1].axvline(x=0.1, color='r', linestyle='--', alpha=0.7, label='Rising end')
axes[1, 1].axvline(x=Tg, color='g', linestyle='--', alpha=0.7, label=f'Tg={
      
        Tg}s')
axes[1, 1].axvline(x=5*Tg, color='m', linestyle='--', alpha=0.7, label=f'5Tg={
      
        5*Tg}s')
axes[1, 1].set_title('Four Segments of Design Spectrum', fontsize=12, fontweight='bold')
axes[1, 1].set_xlabel('Period (s)')
axes[1, 1].set_ylabel('α')
axes[1, 1].legend(fontsize=9)
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/design_spectrum_comprehensive.png', dpi=150, bbox_inches='tight')
plt.close()
print("  综合对比图已保存")

# ============================================================
# 生成设计反应谱动画
# ============================================================
print("\n正在生成设计反应谱动画...")

fig4, ax = plt.subplots(figsize=(10, 6))

alpha = compute_design_spectrum(T, alpha_max, Tg)
line, = ax.plot([], [], 'b-', linewidth=2.5)

ax.set_xlim(0, 6)
ax.set_ylim(0, alpha_max * 1.1)
ax.set_xlabel('Period (s)', fontsize=12)
ax.set_ylabel('Seismic Influence Coefficient α', fontsize=12)
ax.set_title(f'Design Response Spectrum Animation (Intensity 8, Site II)', fontsize=13, fontweight='bold')
ax.grid(True, alpha=0.3)

# 添加特征周期标记
ax.axvline(x=Tg, color='r', linestyle='--', alpha=0.7, label=f'Characteristic Period Tg={
      
        Tg}s')
ax.legend(fontsize=10)

from matplotlib.animation import FuncAnimation

def update(frame):
    idx = frame + 1
    if idx > len(T):
        idx = len(T)
    
    line.set_data(T[:idx], alpha[:idx])
    
    return line,

n_frames = len(T)
anim = FuncAnimation(fig4, update, frames=n_frames, interval=30, blit=True)
anim.save(f'{
      
        output_dir}/design_spectrum_animation.gif', writer='pillow', fps=30, dpi=100)
plt.close()
print("  设计反应谱动画已保存")

# ============================================================
# 打印关键结果
# ============================================================
print("\n设计反应谱关键结果:")
print("-" * 70)

print("  设防烈度与地震影响系数最大值:")
for intensity, alpha_max in alpha_max_table.items():
    print(f"    {
      
        intensity}: αmax = {
      
        alpha_max}")

print(f"\n  场地类别与特征周期(第一组):")
for site, Tg in Tg_table.items():
    print(f"    {
      
        site}类: Tg = {
      
        Tg} s")

print("\n" + "=" * 70)
print("设计反应谱计算完成!")
print("=" * 70)

6.4 案例4:多自由度体系地震响应分析

本案例实现多自由度体系的地震响应分析,包括振型分解法和底部剪力法。

# -*- coding: utf-8 -*-
"""
主题017:结构地震响应分析与设计反应谱 - 案例4
多自由度体系地震响应分析
"""
import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
from scipy.linalg import eigh
import warnings
warnings.filterwarnings('ignore')
import os

output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题017'
os.makedirs(output_dir, exist_ok=True)

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 70)
print("主题017:结构地震响应分析与设计反应谱")
print("案例4:多自由度体系地震响应分析")
print("=" * 70)

# ============================================================
# 生成地震波
# ============================================================
def generate_earthquake(dt, t_end, omega_g=5*np.pi, zeta_g=0.6, S0=0.02, seed=None):
    """生成地震波"""
    if seed is not None:
        np.random.seed(seed)
    
    t = np.arange(0, t_end, dt)
    n = len(t)
    
    white_noise = np.random.randn(n)
    
    freqs = np.fft.fftfreq(n, dt)
    omega = 2 * np.pi * freqs
    
    omega_sq = omega**2
    numerator = omega_g**4 + 4*zeta_g**2*omega_g**2*omega_sq
    denominator = (omega_g**2 - omega_sq)**2 + 4*zeta_g**2*omega_g**2*omega_sq
    
    psd = np.zeros_like(omega)
    mask = denominator > 1e-10
    psd[mask] = S0 * numerator[mask] / denominator[mask]
    
    white_fft = np.fft.fft(white_noise)
    filtered_fft = white_fft * np.sqrt(psd * n * dt / 2)
    filtered = np.real(np.fft.ifft(filtered_fft))
    
    t_mid = t_end / 2
    envelope = np.zeros(n)
    for i, ti in enumerate(t):
        if ti < t_mid:
            envelope[i] = (ti / t_mid)**2
        else:
            envelope[i] = np.exp(-2 * (ti - t_mid) / t_mid)
    
    acc = filtered * envelope
    target_pga = 0.2 * 9.8
    current_pga = np.max(np.abs(acc))
    if current_pga > 0:
        acc = acc * target_pga / current_pga
    
    return t, acc

# ============================================================
# 定义多自由度体系
# ============================================================
print("\n定义多自由度体系参数:")
print("-" * 70)

# 5层剪切型框架
n_dof = 5
m = 1000.0  # 每层质量 (kg)
k = 50000.0  # 每层刚度 (N/m)
zeta = 0.05  # 阻尼比

# 质量矩阵
M = np.eye(n_dof) * m

# 刚度矩阵(三对角矩阵)
K = np.zeros((n_dof, n_dof))
for i in range(n_dof):
    if i == 0:
        K[i, i] = 2 * k
        K[i, i+1] = -k
    elif i == n_dof - 1:
        K[i, i] = k
        K[i, i-1] = -k
    else:
        K[i, i] = 2 * k
        K[i, i-1] = -k
        K[i, i+1] = -k

print(f"  层数: {
      
        n_dof}")
print(f"  每层质量: {
      
        m} kg")
print(f"  每层刚度: {
      
        k} N/m")
print(f"  阻尼比: {
      
        zeta}")

# ============================================================
# 模态分析
# ============================================================
print("\n进行模态分析...")
print("-" * 70)

# 求解特征值问题
eigenvalues, eigenvectors = eigh(K, M)

# 固有频率
omega_n = np.sqrt(eigenvalues)
f_n = omega_n / (2 * np.pi)
T_n = 1 / f_n

print("  模态频率和周期:")
for i in range(n_dof):
    print(f"    模态{
      
        i+1}: f = {
      
        f_n[i]:.4f} Hz, T = {
      
        T_n[i]:.4f} s")

# 振型归一化(质量归一化)
phi = np.zeros_like(eigenvectors)
for i in range(n_dof):
    phi[:, i] = eigenvectors[:, i] / np.sqrt(eigenvectors[:, i].T @ M @ eigenvectors[:, i])

# 振型参与系数
gamma = np.zeros(n_dof)
for i in range(n_dof):
    gamma[i] = phi[:, i].T @ M @ np.ones(n_dof)

print("\n  振型参与系数:")
for i in range(n_dof):
    print(f"    模态{
      
        i+1}: γ = {
      
        gamma[i]:.4f}")

# 参与质量比
participation_mass = gamma**2 / np.sum(gamma**2) * 100
print("\n  参与质量比:")
for i in range(n_dof):
    print(f"    模态{
      
        i+1}: {
      
        participation_mass[i]:.2f}%")

# ============================================================
# 生成地震波
# ============================================================
print("\n生成地震波...")
print("-" * 70)

dt = 0.01
t_end = 30.0
t, acc = generate_earthquake(dt, t_end, seed=42)

print(f"  时间步长: {
      
        dt} s")
print(f"  持续时间: {
      
        t_end} s")
print(f"  PGA: {
      
        np.max(np.abs(acc)):.4f} m/s²")

# ============================================================
# 振型分解法计算地震响应
# ============================================================
print("\n使用振型分解法计算地震响应...")
print("-" * 70)

# Newmark-beta参数
gamma_nb = 0.5
beta_nb = 0.25

# 计算各模态响应
n_steps = len(t)
modal_disp = np.zeros((n_dof, n_steps))
modal_vel = np.zeros((n_dof, n_steps))
modal_acc = np.zeros((n_dof, n_steps))

for mode in range(n_dof):
    omega = omega_n[mode]
    zeta_mode = zeta
    gamma_mode = gamma[mode]
    
    # 广义坐标方程
    q = np.zeros(n_steps)
    q_dot = np.zeros(n_steps)
    q_ddot = np.zeros(n_steps)
    
    # 等效刚度
    k_eff = omega**2 + gamma_nb / (beta_nb * dt) * 2 * zeta_mode * omega + 1 / (beta_nb * dt**2)
    
    for i in range(n_steps - 1):
        # 等效荷载
        p_eff = -gamma_mode * acc[i+1] + (1/(beta_nb*dt**2))*q[i] + (1/(beta_nb*dt))*q_dot[i] + (1/(2*beta_nb)-1)*q_ddot[i]
        
        # 计算广义位移
        q[i+1] = p_eff / k_eff
        
        # 计算广义速度和加速度
        q_dot[i+1] = gamma_nb/(beta_nb*dt)*(q[i+1]-q[i]) + (1-gamma_nb/beta_nb)*q_dot[i] + dt*(1-gamma_nb/(2*beta_nb))*q_ddot[i]
        q_ddot[i+1] = 1/(beta_nb*dt**2)*(q[i+1]-q[i]) - 1/(beta_nb*dt)*q_dot[i] - (1/(2*beta_nb)-1)*q_ddot[i]
    
    modal_disp[mode, :] = q
    modal_vel[mode, :] = q_dot
    modal_acc[mode, :] = q_ddot

# SRSS法组合各模态响应
n_modes_to_use = 3  # 使用前3阶模态
disp = np.zeros((n_dof, n_steps))
vel = np.zeros((n_dof, n_steps))
acc_total = np.zeros((n_dof, n_steps))

for i in range(n_dof):
    for j in range(n_steps):
        # SRSS组合
        disp_sq = sum((phi[i, mode] * modal_disp[mode, j])**2 for mode in range(n_modes_to_use))
        vel_sq = sum((phi[i, mode] * modal_vel[mode, j])**2 for mode in range(n_modes_to_use))
        acc_sq = sum((phi[i, mode] * modal_acc[mode, j])**2 for mode in range(n_modes_to_use))
        
        disp[i, j] = np.sqrt(disp_sq)
        vel[i, j] = np.sqrt(vel_sq)
        acc_total[i, j] = np.sqrt(acc_sq)

print(f"  使用{
      
        n_modes_to_use}阶模态进行SRSS组合")

# ============================================================
# 绘制振型
# ============================================================
print("\n正在生成可视化图表...")

fig1, axes = plt.subplots(1, 2, figsize=(14, 6))

# 振型图
for i in range(n_dof):
    mode_shape = phi[:, i]
    # 归一化到最大值为1
    mode_shape = mode_shape / np.max(np.abs(mode_shape))
    axes[0].plot(mode_shape, np.arange(n_dof)+1, 'o-', linewidth=2, markersize=8, label=f'Mode {
      
        i+1}')

axes[0].set_xlabel('Normalized Mode Shape', fontsize=12)
axes[0].set_ylabel('Story', fontsize=12)
axes[0].set_title('Mode Shapes', fontsize=14, fontweight='bold')
axes[0].legend(fontsize=10)
axes[0].grid(True, alpha=0.3)
axes[0].set_ylim(0.5, n_dof+0.5)
axes[0].invert_yaxis()

# 参与质量比
axes[1].bar(range(1, n_dof+1), participation_mass, color='steelblue', alpha=0.7)
axes[1].set_xlabel('Mode Number', fontsize=12)
axes[1].set_ylabel('Participation Mass Ratio (%)', fontsize=12)
axes[1].set_title('Modal Participation Mass Ratio', fontsize=14, fontweight='bold')
axes[1].grid(True, alpha=0.3, axis='y')

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/modal_analysis.png', dpi=150, bbox_inches='tight')
plt.close()
print("  模态分析图已保存")

# ============================================================
# 绘制楼层响应时程
# ============================================================
fig2, axes = plt.subplots(3, 1, figsize=(14, 12))

# 选择几个楼层显示
display_floors = [0, 2, 4]  # 第1、3、5层
colors = ['steelblue', 'forestgreen', 'coral']

for i, floor in enumerate(display_floors):
    axes[0].plot(t, disp[floor, :]*1000, color=colors[i], linewidth=1, label=f'Floor {
      
        floor+1}')
    axes[1].plot(t, vel[floor, :], color=colors[i], linewidth=1, label=f'Floor {
      
        floor+1}')
    axes[2].plot(t, acc_total[floor, :], color=colors[i], linewidth=1, label=f'Floor {
      
        floor+1}')

axes[0].set_ylabel('Displacement (mm)', fontsize=12)
axes[0].set_title('Floor Displacement Time History', fontsize=14, fontweight='bold')
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)

axes[1].set_ylabel('Velocity (m/s)', fontsize=12)
axes[1].set_title('Floor Velocity Time History', fontsize=14, fontweight='bold')
axes[1].legend(fontsize=11)
axes[1].grid(True, alpha=0.3)

axes[2].set_xlabel('Time (s)', fontsize=12)
axes[2].set_ylabel('Acceleration (m/s²)', fontsize=12)
axes[2].set_title('Floor Acceleration Time History', fontsize=14, fontweight='bold')
axes[2].legend(fontsize=11)
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/floor_response.png', dpi=150, bbox_inches='tight')
plt.close()
print("  楼层响应时程图已保存")

# ============================================================
# 绘制楼层最大响应包络
# ============================================================
fig3, axes = plt.subplots(1, 3, figsize=(15, 5))

floors = np.arange(1, n_dof+1)
max_disp = np.max(np.abs(disp), axis=1) * 1000  # mm
max_vel = np.max(np.abs(vel), axis=1)  # m/s
max_acc = np.max(np.abs(acc_total), axis=1)  # m/s²

axes[0].plot(max_disp, floors, 'o-', linewidth=2, markersize=8, color='steelblue')
axes[0].set_xlabel('Max Displacement (mm)', fontsize=11)
axes[0].set_ylabel('Floor', fontsize=11)
axes[0].set_title('Maximum Floor Displacement', fontsize=12, fontweight='bold')
axes[0].grid(True, alpha=0.3)
axes[0].set_ylim(0.5, n_dof+0.5)
axes[0].invert_yaxis()

axes[1].plot(max_vel, floors, 'o-', linewidth=2, markersize=8, color='forestgreen')
axes[1].set_xlabel('Max Velocity (m/s)', fontsize=11)
axes[1].set_ylabel('Floor', fontsize=11)
axes[1].set_title('Maximum Floor Velocity', fontsize=12, fontweight='bold')
axes[1].grid(True, alpha=0.3)
axes[1].set_ylim(0.5, n_dof+0.5)
axes[1].invert_yaxis()

axes[2].plot(max_acc, floors, 'o-', linewidth=2, markersize=8, color='coral')
axes[2].set_xlabel('Max Acceleration (m/s²)', fontsize=11)
axes[2].set_ylabel('Floor', fontsize=11)
axes[2].set_title('Maximum Floor Acceleration', fontsize=12, fontweight='bold')
axes[2].grid(True, alpha=0.3)
axes[2].set_ylim(0.5, n_dof+0.5)
axes[2].invert_yaxis()

plt.tight_layout()
plt.savefig(f'{
      
        output_dir}/max_response_envelope.png', dpi=150, bbox_inches='tight')
plt.close()
print("  最大响应包络图已保存")

# ============================================================
# 生成结构地震响应动画
# ============================================================
print("\n正在生成结构地震响应动画...")

fig4, ax = plt.subplots(figsize=(10, 10))

# 绘制结构轮廓
def draw_building(ax, displacements, floor_height=3.0):
    """绘制建筑结构"""
    ax.clear()
    
    # 楼层高度
    heights = np.arange(n_dof) * floor_height
    
    # 建筑宽度
    width = 6.0
    
    # 绘制变形后的结构
    for i in range(n_dof):
        x = displacements[i] * 1000  # 转换为mm
        y = heights[i]
        
        # 绘制楼层
        rect = plt.Rectangle((x - width/2, y), width, floor_height*0.8, 
                             linewidth=1, edgecolor='black', facecolor='lightblue', alpha=0.7)
        ax.add_patch(rect)
        
        # 标注楼层号
        ax.text(x + width/2 + 0.5, y + floor_height*0.4, f'{
      
        i+1}F', fontsize=10)
    
    # 绘制柱子
    for i in range(n_dof):
        if i == 0:
            # 底层柱
            x_left = displacements[i] * 1000 - width/2
            x_right = displacements[i] * 1000 + width/2
            y_bottom = 0
            y_top = heights[i]
            ax.plot([x_left, x_left], [y_bottom, y_top], 'k-', linewidth=2)
            ax.plot([x_right, x_right], [y_bottom, y_top], 'k-', linewidth=2)
        else:
            # 上层柱
            x_left_bottom = displacements[i-1] * 1000 - width/2
            x_left_top = displacements[i] * 1000 - width/2
            x_right_bottom = displacements[i-1] * 1000 + width/2
            x_right_top = displacements[i] * 1000 + width/2
            y_bottom = heights[i-1] + floor_height*0.8
            y_top = heights[i]
            ax.plot([x_left_bottom, x_left_top], [y_bottom, y_top], 'k-', linewidth=2)
            ax.plot([x_right_bottom, x_right_top], [y_bottom, y_top], 'k-', linewidth=2)
    
    ax.set_xlim(-20, 20)
    ax.set_ylim(-1, n_dof * floor_height + 1)
    ax.set_xlabel('Displacement (mm)', fontsize=12)
    ax.set_ylabel('Height (m)', fontsize=12)
    ax.set_title('Building Seismic Response Animation', fontsize=14, fontweight='bold')
    ax.grid(True, alpha=0.3)
    ax.set_aspect('equal')

from matplotlib.animation import FuncAnimation

def update(frame):
    idx = frame * 5
    if idx >= n_steps:
        idx = n_steps - 1
    
    # 获取当前时刻的位移
    current_disp = disp[:, idx]
    draw_building(ax, current_disp)
    
    return []

n_frames = n_steps // 5
anim = FuncAnimation(fig4, update, frames=n_frames, interval=50, blit=True)
anim.save(f'{
      
        output_dir}/building_response_animation.gif', writer='pillow', fps=20, dpi=100)
plt.close()
print("  结构地震响应动画已保存")

# ============================================================
# 底部剪力法对比
# ============================================================
print("\n底部剪力法计算...")
print("-" * 70)

# 计算底部剪力
G = np.sum(np.diag(M)) * 9.8  # 总重力荷载
alpha_1 = 0.16  # 假设8度设防,II类场地
F_ek = alpha_1 * G * 0.85  # 等效总重力荷载系数0.85

print(f"  结构总质量: {
      
        np.sum(np.diag(M)):.2f} kg")
print(f"  总重力荷载: {
      
        G:.2f} N")
print(f"  水平地震影响系数: α1 = {
      
        alpha_1}")
print(f"  底部总剪力: F_Ek = {
      
        F_ek:.2f} N")

# 各层地震作用
heights = np.arange(1, n_dof+1) * 3.0  # 假设层高3m
F_i = np.zeros(n_dof)
for i in range(n_dof):
    F_i[i] = np.diag(M)[i] * 9.8 * heights[i] / np.sum(np.diag(M) * 9.8 * heights) * F_ek

print(f"\n  各层水平地震作用:")
for i in range(n_dof):
    print(f"    第{
      
        i+1}层: F_{
      
        i+1} = {
      
        F_i[i]:.2f} N")

# ============================================================
# 打印关键结果
# ============================================================
print("\n地震响应关键结果:")
print("-" * 70)

print("  楼层最大位移:")
for i in range(n_dof):
    print(f"    第{
      
        i+1}层: {
      
        max_disp[i]:.4f} mm")

print("\n  楼层最大加速度:")
for i in range(n_dof):
    print(f"    第{
      
        i+1}层: {
      
        max_acc[i]:.4f} m/s²")

print("\n  顶层最大位移: {:.4f} mm".format(max_disp[-1]))
print("  顶层最大加速度: {:.4f} m/s²".format(max_acc[-1]))

print("\n" + "=" * 70)
print("多自由度体系地震响应分析完成!")
print("=" * 70)

7. 结果分析与讨论

7.1 地震波特性分析

通过案例1的地震波生成和频谱分析,可以观察到:

  1. 场地条件对地震波的影响

    • 硬土场地的高频成分更丰富,PGA较大
    • 软土场地的低频成分更显著,持时更长
    • 中硬土场地的频谱特性介于两者之间
  2. 包络函数的作用

    • 模拟地震动的非平稳特性
    • 强震段出现在地震波的中部
    • 符合实际地震动的能量分布规律

7.2 反应谱特性分析

通过案例2的反应谱计算,可以得出以下结论:

  1. 阻尼比的影响

    • 阻尼比越大,反应谱值越小
    • 高阻尼对长周期结构的响应减小更明显
    • 工程设计中通常取ζ=0.05作为标准值
  2. 反应谱的形状特征

    • 短周期段:加速度反应谱接近常数
    • 中周期段:速度反应谱接近常数
    • 长周期段:位移反应谱接近常数
  3. 伪反应谱的适用性

    • 对于小阻尼体系,伪反应谱与真实反应谱非常接近
    • 伪加速度反应谱便于计算地震作用
    • 伪速度反应谱与能量输入相关

7.3 设计反应谱的工程意义

通过案例3的设计反应谱分析,可以理解:

  1. 设防烈度的影响

    • 设防烈度越高,地震影响系数越大
    • 9度设防的地震作用约为6度设防的8倍
    • 设计反应谱的形状与设防烈度无关
  2. 场地类别的影响

    • 软土场地的特征周期较长
    • 长周期结构在软土场地上响应更大
    • 场地条件是抗震设计的重要考虑因素
  3. 阻尼比的影响

    • 阻尼比越大,设计反应谱值越小
    • 阻尼调整系数反映了这一影响
    • 增加结构阻尼是有效的减震措施

7.4 多自由度体系地震响应

通过案例4的多自由度体系分析,可以得出:

  1. 振型参与特性

    • 第一阶振型的参与质量比最大(约88%)
    • 前3阶振型累计参与质量超过99%
    • 高阶振型对总响应的贡献较小
  2. 楼层响应分布

    • 楼层位移随高度增加而增大
    • 顶层位移最大,底层剪力最大
    • 加速度响应在各楼层分布相对均匀
  3. 设计方法对比

    • 振型分解法结果精确,但计算复杂
    • 底部剪力法简便,适用于规则结构
    • 两种方法的结果具有可比性

8. 工程应用与展望

8.1 抗震设计方法

基于反应谱理论的抗震设计方法包括:

  1. 底部剪力法:适用于高度不超过40m、质量和刚度沿高度分布均匀的规则结构
  2. 振型分解反应谱法:适用于大多数建筑结构,考虑多振型的贡献
  3. 时程分析法:用于特别不规则结构、超高层结构或重要建筑

8.2 减震隔震技术

现代抗震工程的发展趋势:

  1. 基础隔震:通过隔震支座延长结构周期,避开地震能量集中的频段
  2. 消能减震:利用阻尼器耗散地震能量,减小结构响应
  3. 调谐质量阻尼器(TMD):通过调谐质量系统吸收主结构的振动能量

8.3 基于性能的抗震设计

新一代抗震设计理念的核心理念:

  1. 多级设防目标

    • 小震不坏:结构保持弹性
    • 中震可修:结构可修复
    • 大震不倒:保证生命安全
  2. 性能化设计:根据建筑的重要性确定性能目标

  3. 位移控制设计:以层间位移角作为控制指标

8.4 未来发展方向

地震工程领域的研究前沿:

  1. 地震动预测:基于地震学原理的强地面运动预测
  2. 结构健康监测:实时监测结构状态,评估震后损伤
  3. 韧性抗震设计:不仅保证生命安全,还要实现震后快速恢复
  4. 人工智能应用:机器学习在地震响应预测和损伤识别中的应用

9. 结论

本教程系统地介绍了结构地震响应分析与设计反应谱的理论和方法,通过Python实现了地震波生成、反应谱计算、设计反应谱构建以及多自由度体系地震响应分析。主要结论如下:

  1. 地震动特性:地震动具有显著的随机性和非平稳性,Kanai-Tajimi模型可以有效描述其统计特性
  2. 反应谱理论:反应谱是连接地震动特性和结构响应的桥梁,是抗震设计的核心工具
  3. 设计反应谱:基于统计分析和规范要求的设计反应谱,为工程抗震设计提供了科学依据
  4. 多自由度体系:振型分解法是分析多自由度体系地震响应的有效方法,SRSS组合适用于大多数工程结构

通过本教程的学习,读者应该能够:

  • 理解地震动的基本特性和数学描述
  • 掌握反应谱的计算方法和物理意义
  • 应用设计反应谱进行结构抗震设计
  • 使用Python进行地震响应分析和可视化