结构动力学仿真-主题052:核电站结构抗震安全评估

一、课程导入与学习目标

1.1 工程背景

核电站作为重要的能源基础设施,其结构安全直接关系到公共安全。核电站结构抗震安全评估是核工程领域最重要的研究课题之一,具有以下特点:

核电站结构的重要性:

  • 安全等级极高:核电站属于最高安全等级的建筑物,必须确保在极端地震作用下不发生放射性物质泄漏
  • 设计基准严格:需考虑运行基准地震(OBE)和安全停堆地震(SSE)两级地震动
  • 监管要求严格:受国际原子能机构(IAEA)和各国核安全监管机构严格监管
  • 设计寿命长久:核电站设计寿命通常为40-60年,需考虑长期老化效应

核电站结构类型:

  1. 反应堆厂房(Containment Building)

    • 预应力混凝土结构
    • 钢衬里结构
    • 双层安全壳结构
  2. 辅助厂房(Auxiliary Building)

    • 钢筋混凝土框架结构
    • 包含重要安全设备
  3. 汽轮机厂房(Turbine Building)

    • 大跨度钢结构或混凝土结构
    • 包含旋转机械设备
  4. 燃料厂房(Fuel Building)

    • 储存核燃料组件
    • 严格密封要求
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

1.2 学习目标

完成本主题学习后,你将能够:

  • 理解核电站结构抗震设计的基本原理和规范要求
  • 掌握核电站结构有限元建模方法
  • 学会地震荷载的输入和反应谱分析方法
  • 理解结构-设备相互作用的动力学特性
  • 掌握抗震安全评估的方法和裕度分析技术

1.3 核心问题

  • 核电站结构与普通建筑结构在抗震设计上有何不同?
  • 如何确定核电站结构的地震输入?
  • 结构-设备相互作用如何影响抗震安全性?
  • 如何评估核电站结构的抗震裕度?

二、核电站结构抗震设计基础

2.1 抗震设计规范体系

国际标准:

  • IAEA SSG-9:核电厂地震危害评估
  • IAEA SSG-67:核电厂结构、系统和部件的地震设计
  • ASCE 4-16:核安全相关结构抗震分析
  • ASCE 43-19:核设施结构抗震设计标准

中国标准:

  • GB 50267-2019:核电厂抗震设计标准
  • HAF 102:核动力厂设计安全规定
  • NB/T 20012-2010:核电厂抗震设计规范

设计地震等级:

地震等级 超越概率 设计目标 结构响应
运行基准地震(OBE) 50年10% 可继续运行 弹性范围内
安全停堆地震(SSE) 年0.01% 安全停堆 允许有限损伤
超设计基准地震 - 防止堆芯熔化 极限承载力

2.2 核电站结构特点

结构形式特点:

  1. 反应堆安全壳
结构组成:
- 筒体:预应力混凝土,厚度1.0-1.5m
- 穹顶:半球形或准球形
- 钢衬里:6-10mm厚钢板,保证密封性
- 基础底板:大体积混凝土,厚度3-5m
  1. 隔震技术
隔震装置类型:
- 铅芯橡胶支座
- 高阻尼橡胶支座
- 摩擦摆支座
- 液体粘滞阻尼器

设备布置特点:

  • 重型设备(反应堆压力容器、蒸汽发生器)
  • 精密设备(控制棒驱动机构、泵)
  • 管道系统(主回路、辅助回路)
  • 电气设备(开关柜、变压器)

2.3 抗震分析方法

分析方法分类:

  1. 等效静力法

    • 适用于规则结构
    • 基底剪力法
    • 振型分解反应谱法
  2. 动力时程分析法

    • 线性时程分析
    • 非线性时程分析
    • 考虑材料非线性和几何非线性
  3. 子结构法

    • 结构-地基相互作用
    • 结构-设备相互作用
    • 多点激励分析

分析软件:

  • ANSYS、ABAQUS(通用有限元)
  • OpenSees(开源地震工程)
  • SASSI(土-结构相互作用)
  • CLASSI(复杂结构分析)

三、核电站结构有限元建模

3.1 建模基本原则

几何建模:

  • 准确反映结构几何特征
  • 考虑质量分布和刚度分布
  • 合理简化次要结构

单元选择:

  • 壳单元:安全壳筒体和穹顶
  • 实体单元:基础底板、大体积混凝土
  • 梁单元:钢结构框架、设备支撑
  • 弹簧单元:隔震支座、土弹簧

材料模型:

  • 混凝土:弹性、开裂、塑性
  • 钢材:弹性、屈服、硬化
  • 预应力筋:初始应力、松弛

3.2 反应堆安全壳建模

预应力混凝土安全壳:

"""
核电站反应堆安全壳有限元模型
"""
import numpy as np
from scipy.linalg import eigh

class ContainmentVessel:
    """
    反应堆安全壳模型
    """
    def __init__(self):
        # 几何参数
        self.R = 20.0  # 筒体半径 (m)
        self.H_cylinder = 40.0  # 筒体高度 (m)
        self.t_cylinder = 1.2  # 筒体厚度 (m)
        self.R_dome = 20.0  # 穹顶半径 (m)
        self.t_dome = 1.0  # 穹顶厚度 (m)
        self.H_base = 5.0  # 基础厚度 (m)
        
        # 材料参数
        self.E_concrete = 35e9  # 混凝土弹性模量 (Pa)
        self.rho_concrete = 2500  # 混凝土密度 (kg/m^3)
        self.nu = 0.2  # 泊松比
        
        # 预应力参数
        self.P_vertical = 10e6  # 竖向预应力 (N/m)
        self.P_hoop = 15e6  # 环向预应力 (N/m)
    
    def calculate_stiffness(self):
        """
        计算安全壳整体刚度
        """
        # 筒体轴向刚度
        A_cylinder = 2 * np.pi * self.R * self.t_cylinder
        K_axial = self.E_concrete * A_cylinder / self.H_cylinder
        
        # 筒体弯曲刚度
        I_cylinder = np.pi * self.R**3 * self.t_cylinder
        K_bending = 3 * self.E_concrete * I_cylinder / self.H_cylinder**3
        
        # 穹顶薄膜刚度
        A_dome = 2 * np.pi * self.R_dome * self.t_dome
        K_dome = self.E_concrete * A_dome / self.R_dome
        
        return {
            'axial': K_axial,
            'bending': K_bending,
            'dome': K_dome
        }
    
    def calculate_mass(self):
        """
        计算安全壳质量
        """
        # 筒体质量
        V_cylinder = 2 * np.pi * self.R * self.t_cylinder * self.H_cylinder
        m_cylinder = self.rho_concrete * V_cylinder
        
        # 穹顶质量
        V_dome = 2 * np.pi * self.R_dome**2 * self.t_dome
        m_dome = self.rho_concrete * V_dome
        
        # 基础质量
        V_base = np.pi * (self.R + 5)**2 * self.H_base
        m_base = self.rho_concrete * V_base
        
        return {
            'cylinder': m_cylinder,
            'dome': m_dome,
            'base': m_base,
            'total': m_cylinder + m_dome + m_base
        }

3.3 设备建模方法

主要设备类型:

  1. 反应堆压力容器(RPV)

    • 质量:300-500吨
    • 支撑方式:下部支撑、吊挂支撑
    • 建模:集中质量+梁单元
  2. 蒸汽发生器(SG)

    • 质量:200-400吨
    • 支撑方式:下部支撑、横向支撑
    • 建模:多自由度质量-弹簧系统
  3. 主泵(RCP)

    • 质量:50-100吨
    • 支撑方式:下部支撑
    • 建模:单摆模型
def model_primary_equipment():
    """
    主要设备建模
    """
    # 反应堆压力容器
    RPV = {
        'mass': 400e3,  # kg
        'height': 12.0,  # m
        'support_stiffness': 5e9,  # N/m
        'damping_ratio': 0.02
    }
    
    # 蒸汽发生器
    SG = {
        'mass': 350e3,  # kg
        'height': 15.0,  # m
        'support_stiffness': 3e9,  # N/m
        'damping_ratio': 0.03
    }
    
    # 主泵
    RCP = {
        'mass': 80e3,  # kg
        'height': 8.0,  # m
        'support_stiffness': 2e9,  # N/m
        'damping_ratio': 0.02
    }
    
    return {'RPV': RPV, 'SG': SG, 'RCP': RCP}

四、地震荷载输入与反应谱分析

4.1 设计地震动确定

地震危险性分析:

  1. 确定性方法

    • 最大历史地震
    • 构造地震潜力
    • 特定断层地震
  2. 概率方法

    • 地震发生率模型
    • 地震动衰减关系
    • 危险性曲线

设计反应谱:

import numpy as np
import matplotlib.pyplot as plt

def nuclear_design_spectrum(T, PGA, spectrum_type='RG1.60'):
    """
    核电站设计反应谱
    
    Parameters:
    -----------
    T : array
        周期 (s)
    PGA : float
        峰值地面加速度 (g)
    spectrum_type : str
        谱类型 ('RG1.60', 'NUREG/CR-0098', 'GB50267')
    
    Returns:
    --------
    Sa : array
        谱加速度 (g)
    """
    if spectrum_type == 'RG1.60':
        # NRC RG 1.60 标准谱
        Sa = np.zeros_like(T)
        
        for i, t in enumerate(T):
            if t <= 0.03:
                Sa[i] = PGA * 1.0
            elif t <= 0.12:
                Sa[i] = PGA * (1.0 + 4.0 * (t - 0.03) / 0.09)
            elif t <= 0.4:
                Sa[i] = PGA * 5.0
            elif t <= 3.0:
                Sa[i] = PGA * 5.0 * (0.4 / t)**0.67
            else:
                Sa[i] = PGA * 5.0 * (0.4 / 3.0)**0.67 * (3.0 / t)
    
    elif spectrum_type == 'GB50267':
        # 中国核电厂抗震设计标准
        Sa = np.zeros_like(T)
        
        for i, t in enumerate(T):
            if t <= 0.1:
                Sa[i] = PGA * (1.0 + 10.0 * t)
            elif t <= 0.5:
                Sa[i] = PGA * 2.0
            elif t <= 3.0:
                Sa[i] = PGA * 2.0 * (0.5 / t)**0.6
            else:
                Sa[i] = PGA * 2.0 * (0.5 / 3.0)**0.6 * (3.0 / t)
    
    return Sa

# 绘制设计谱
T = np.linspace(0.01, 5.0, 500)
PGA = 0.3  # g

Sa_RG160 = nuclear_design_spectrum(T, PGA, 'RG1.60')
Sa_GB = nuclear_design_spectrum(T, PGA, 'GB50267')

plt.figure(figsize=(10, 6))
plt.plot(T, Sa_RG160, 'b-', linewidth=2, label='RG 1.60 (US NRC)')
plt.plot(T, Sa_GB, 'r--', linewidth=2, label='GB 50267 (China)')
plt.xlabel('Period (s)', fontsize=12)
plt.ylabel('Spectral Acceleration (g)', fontsize=12)
plt.title('Nuclear Power Plant Design Response Spectrum', fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.xlim(0, 5)
plt.ylim(0, 2)
plt.tight_layout()
plt.savefig('nuclear_design_spectrum.png', dpi=150)
plt.close()

4.2 地震波选取与调整

天然波选取原则:

  • 震级、距离、场地条件匹配
  • 反应谱与设计谱匹配
  • 持时、频谱特性合理

人工波生成方法:

def generate_artificial_earthquake(target_spectrum, dt, duration, n_waves=5):
    """
    生成人工地震波
    
    Parameters:
    -----------
    target_spectrum : function
        目标反应谱
    dt : float
        时间步长
    duration : float
        持续时间
    n_waves : int
        谐波数量
    
    Returns:
    --------
    t, acc : arrays
        时间和加速度
    """
    t = np.linspace(0, duration, int(duration/dt))
    acc = np.zeros_like(t)
    
    # 生成谐波叠加
    for i in range(n_waves):
        freq = 0.5 + i * 2.0  # 频率范围 0.5-10 Hz
        omega = 2 * np.pi * freq
        amp = target_spectrum(1/freq) if 1/freq > 0.01 else 0.1
        phase = np.random.uniform(0, 2*np.pi)
        
        acc += amp * np.sin(omega * t + phase)
    
    # 添加包络函数
    t_rise = 2.0
    t_stationary = duration - 6.0
    envelope = np.ones_like(t)
    
    for i, ti in enumerate(t):
        if ti < t_rise:
            envelope[i] = (ti / t_rise)**2
        elif ti > t_stationary:
            envelope[i] = np.exp(-2 * (ti - t_stationary))
    
    acc *= envelope
    
    # 调整峰值
    acc = acc / np.max(np.abs(acc)) * 0.3 * 9.81
    
    return t, acc

4.3 反应谱分析实施

模态组合方法:

  1. SRSS法(平方和开方)

    R = sqrt(Σ Ri²)
    

    适用于频率分离良好的模态

  2. CQC法(完全二次组合)

    R = sqrt(ΣΣ ρij * Ri * Rj)
    

    适用于密集模态

  3. ABS法(绝对值求和)

    R = Σ |Ri|
    

    保守估计

def cqc_combination(modes_response, frequencies, damping=0.05):
    """
    CQC模态组合
    
    Parameters:
    -----------
    modes_response : array
        各模态响应
    frequencies : array
        各模态频率 (Hz)
    damping : float
        阻尼比
    
    Returns:
    --------
    combined : float
        组合响应
    """
    n_modes = len(modes_response)
    correlation = np.zeros((n_modes, n_modes))
    
    for i in range(n_modes):
        for j in range(n_modes):
            if i == j:
                correlation[i, j] = 1.0
            else:
                r = frequencies[j] / frequencies[i]
                rho = (8 * damping**2 * r * (1 + r) * r**1.5) / \
                      ((1 - r**2)**2 + 4 * damping**2 * r * (1 + r)**2)
                correlation[i, j] = rho
    
    combined = 0
    for i in range(n_modes):
        for j in range(n_modes):
            combined += correlation[i, j] * modes_response[i] * modes_response[j]
    
    return np.sqrt(combined)

五、结构-设备相互作用分析

5.1 相互作用机理

相互作用类型:

  1. 惯性相互作用

    • 设备惯性力反馈到结构
    • 改变结构动力特性
    • 影响结构响应
  2. 运动相互作用

    • 结构运动传递给设备
    • 设备支座处加速度
    • 设备动力放大
  3. 反馈相互作用

    • 设备振动影响结构
    • 双向耦合效应
    • 需要整体分析

分析方法:

方法 精度 计算成本 适用场景
解耦分析 初步设计
单向耦合 详细设计
完全耦合 安全评估

5.2 设备抗震鉴定

鉴定方法:

  1. 分析方法

    • 地震反应谱分析
    • 时程分析
    • 试验验证
  2. 试验方法

    • 振动台试验
    • 激振器试验
    • 地震模拟试验

鉴定标准:

def equipment_seismic_qualification(equipment_response, equipment_capacity):
    """
    设备抗震鉴定
    
    Parameters:
    -----------
    equipment_response : dict
        设备地震响应
    equipment_capacity : dict
        设备抗震能力
    
    Returns:
    --------
    qualification : dict
        鉴定结果
    """
    results = {}
    
    # 应力鉴定
    stress_ratio = equipment_response['stress'] / equipment_capacity['allowable_stress']
    results['stress'] = {
        'ratio': stress_ratio,
        'passed': stress_ratio < 1.0,
        'margin': (1.0 - stress_ratio) * 100
    }
    
    # 位移鉴定
    displacement_ratio = equipment_response['displacement'] / equipment_capacity['allowable_displacement']
    results['displacement'] = {
        'ratio': displacement_ratio,
        'passed': displacement_ratio < 1.0,
        'margin': (1.0 - displacement_ratio) * 100
    }
    
    # 加速度鉴定
    acceleration_ratio = equipment_response['acceleration'] / equipment_capacity['allowable_acceleration']
    results['acceleration'] = {
        'ratio': acceleration_ratio,
        'passed': acceleration_ratio < 1.0,
        'margin': (1.0 - acceleration_ratio) * 100
    }
    
    # 综合评定
    all_passed = all([r['passed'] for r in results.values()])
    results['overall'] = {
        'passed': all_passed,
        'minimum_margin': min([r['margin'] for r in results.values()])
    }
    
    return results

5.3 楼层反应谱

楼层反应谱概念:

楼层反应谱是描述结构某楼层处地震响应的谱曲线,用于设备抗震设计。

def floor_response_spectrum(structure_response, frequencies, damping=0.05):
    """
    计算楼层反应谱
    
    Parameters:
    -----------
    structure_response : array
        楼层加速度时程
    frequencies : array
        分析频率范围 (Hz)
    damping : float
        阻尼比
    
    Returns:
    --------
    spectrum : array
        楼层反应谱
    """
    dt = 0.01  # 假设时间步长
    spectrum = np.zeros(len(frequencies))
    
    for i, f in enumerate(frequencies):
        omega = 2 * np.pi * f
        
        # 单自由度系统响应
        m = 1.0
        c = 2 * damping * omega * m
        k = omega**2 * m
        
        # Newmark-beta求解
        n_steps = len(structure_response)
        u = np.zeros(n_steps)
        v = np.zeros(n_steps)
        a = np.zeros(n_steps)
        
        for n in range(n_steps - 1):
            # 简化计算
            a[n+1] = structure_response[n+1] - c * v[n] - k * u[n]
            v[n+1] = v[n] + dt * a[n+1]
            u[n+1] = u[n] + dt * v[n+1]
        
        spectrum[i] = np.max(np.abs(a + structure_response))
    
    return spectrum

六、抗震安全评估与裕度分析

6.1 抗震裕度概念

高置信度低失效概率(HCLPF)能力:

HCLPF能力是核电厂结构抗震安全评估的核心指标,表示在95%置信度下,失效概率不超过5%的地震动水平。

裕度因子:

裕度因子 = HCLPF能力 / 设计基准地震

典型要求:

  • 安全相关结构:裕度因子 ≥ 1.5
  • 关键设备:裕度因子 ≥ 1.33

6.2 易损性分析

易损性曲线:

易损性曲线描述结构在不同地震动水平下的失效概率。

def fragility_curve(IM, median, beta):
    """
    计算易损性曲线
    
    Parameters:
    -----------
    IM : array
        地震动强度指标 (如PGA)
    median : float
        中位值 (50%失效概率对应的IM)
    beta : float
        对数标准差
    
    Returns:
    --------
    pf : array
        失效概率
    """
    from scipy.stats import norm
    
    # 对数正态分布
    pf = norm.cdf(np.log(IM / median) / beta)
    
    return pf

# 示例:绘制易损性曲线
IM = np.linspace(0.1, 1.0, 100)
median = 0.5  # g
beta = 0.4

pf = fragility_curve(IM, median, beta)

plt.figure(figsize=(10, 6))
plt.semilogx(IM, pf * 100, 'b-', linewidth=2)
plt.axvline(x=median, color='r', linestyle='--', label=f'Median = {median}g')
plt.axhline(y=50, color='r', linestyle='--', alpha=0.5)
plt.xlabel('Peak Ground Acceleration (g)', fontsize=12)
plt.ylabel('Probability of Failure (%)', fontsize=12)
plt.title('Fragility Curve for Nuclear Structure', fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.xlim(0.1, 1.0)
plt.ylim(0, 100)
plt.tight_layout()
plt.savefig('fragility_curve.png', dpi=150)
plt.close()

6.3 抗震裕度评估方法

地震裕度评估(SMA)方法:

  1. 保守确定性失效裕度(CDFM)法

    • 使用保守假设
    • 确定性分析
    • 快速评估
  2. 概率安全裕度(PSM)法

    • 考虑不确定性
    • 概率分析
    • 精确评估
def calculate_hclpf(fragility_median, fragility_beta, confidence=0.95):
    """
    计算HCLPF能力
    
    Parameters:
    -----------
    fragility_median : float
        易损性中位值
    fragility_beta : float
        易损性对数标准差
    confidence : float
        置信水平
    
    Returns:
    --------
    hclpf : float
        HCLPF能力
    """
    from scipy.stats import norm
    
    # HCLPF = median * exp(-beta * Phi^{-1}(confidence))
    z_score = norm.ppf(confidence)
    hclpf = fragility_median * np.exp(-fragility_beta * z_score)
    
    return hclpf

# 示例计算
median_capacity = 0.6  # g
beta_capacity = 0.35
hclpf = calculate_hclpf(median_capacity, beta_capacity)

print(f"易损性中位值: {median_capacity}g")
print(f"对数标准差: {beta_capacity}")
print(f"HCLPF能力: {hclpf:.3f}g")

# 计算裕度因子
sse_pga = 0.3  # 安全停堆地震PGA
margin_factor = hclpf / sse_pga
print(f"裕度因子: {margin_factor:.2f}")

6.4 系统抗震安全评估

系统评估流程:

  1. 结构评估

    • 整体稳定性
    • 局部强度
    • 变形能力
  2. 设备评估

    • 功能完整性
    • 结构完整性
    • 可操作性
  3. 系统功能评估

    • 安全功能维持
    • 停堆能力
    • 冷却能力
def system_seismic_assessment(structure_results, equipment_results, system_functions):
    """
    系统抗震安全评估
    
    Parameters:
    -----------
    structure_results : dict
        结构评估结果
    equipment_results : dict
        设备评估结果
    system_functions : dict
        系统功能要求
    
    Returns:
    --------
    assessment : dict
        系统评估结果
    """
    assessment = {}
    
    # 结构安全评估
    structure_safe = all([
        structure_results['stability'],
        structure_results['strength'],
        structure_results['deformation']
    ])
    assessment['structure'] = structure_safe
    
    # 设备安全评估
    equipment_safe = all(equipment_results.values())
    assessment['equipment'] = equipment_safe
    
    # 系统功能评估
    function_maintained = all(system_functions.values())
    assessment['system_function'] = function_maintained
    
    # 综合评估
    assessment['overall_safe'] = structure_safe and equipment_safe and function_maintained
    
    # 薄弱环节识别
    weaknesses = []
    if not structure_safe:
        weaknesses.append('Structure')
    if not equipment_safe:
        failed_equipment = [k for k, v in equipment_results.items() if not v]
        weaknesses.extend(failed_equipment)
    if not function_maintained:
        failed_functions = [k for k, v in system_functions.items() if not v]
        weaknesses.extend(failed_functions)
    
    assessment['weaknesses'] = weaknesses
    
    return assessment

七、案例实践

本主题包含以下四个Python仿真实践案例:

案例1:核电站结构建模与模态分析

  • 反应堆安全壳有限元建模
  • 模态频率和振型计算
  • 质量参与系数分析
# -*- coding: utf-8 -*-
"""
案例1:核电站结构建模与模态分析
本案例实现核电站反应堆安全壳的有限元建模和模态分析
"""

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import eigh
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import eigsh
import warnings
warnings.filterwarnings('ignore')
import os

# 创建输出目录
output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题052'
os.makedirs(output_dir, exist_ok=True)

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("案例1:核电站结构建模与模态分析")
print("=" * 60)

# ==================== 1. 反应堆安全壳几何参数 ====================
print("\n【1. 反应堆安全壳几何参数】")

class ContainmentVessel:
    """
    反应堆安全壳模型类
    """
    def __init__(self):
        # 几何参数 (单位: m)
        self.R_cylinder = 20.0  # 筒体半径
        self.H_cylinder = 40.0  # 筒体高度
        self.t_cylinder = 1.2   # 筒体壁厚
        self.R_dome = 20.0      # 穹顶半径
        self.t_dome = 1.0       # 穹顶厚度
        self.H_base = 5.0       # 基础厚度
        self.R_base = 25.0      # 基础半径
        
        # 材料参数 - 预应力混凝土
        self.E_concrete = 35e9   # 弹性模量 (Pa)
        self.rho_concrete = 2500  # 密度 (kg/m^3)
        self.nu = 0.2            # 泊松比
        self.fck = 40e6          # 混凝土抗压强度 (Pa)
        
        # 钢衬里参数
        self.t_steel_liner = 0.006  # 钢衬里厚度 (m)
        self.E_steel = 200e9        # 钢材弹性模量 (Pa)
        self.rho_steel = 7850       # 钢材密度 (kg/m^3)
        
        # 预应力参数
        self.P_vertical = 8e6    # 竖向预应力 (N/m)
        self.P_hoop = 12e6       # 环向预应力 (N/m)
        
    def calculate_section_properties(self):
        """
        计算截面特性
        """
        # 筒体截面面积
        A_cylinder = 2 * np.pi * self.R_cylinder * self.t_cylinder
        
        # 筒体惯性矩
        I_cylinder = np.pi * self.R_cylinder**3 * self.t_cylinder
        
        # 穹顶截面面积
        A_dome = 2 * np.pi * self.R_dome * self.t_dome
        
        # 基础截面面积
        A_base = np.pi * self.R_base**2
        
        return {
            'A_cylinder': A_cylinder,
            'I_cylinder': I_cylinder,
            'A_dome': A_dome,
            'A_base': A_base
        }
    
    def calculate_mass(self):
        """
        计算结构质量
        """
        # 筒体混凝土质量
        V_cylinder_concrete = 2 * np.pi * self.R_cylinder * self.t_cylinder * self.H_cylinder
        m_cylinder_concrete = self.rho_concrete * V_cylinder_concrete
        
        # 穹顶混凝土质量
        V_dome_concrete = 2 * np.pi * self.R_dome**2 * self.t_dome
        m_dome_concrete = self.rho_concrete * V_dome_concrete
        
        # 基础质量
        V_base = np.pi * self.R_base**2 * self.H_base
        m_base = self.rho_concrete * V_base
        
        # 钢衬里质量
        A_liner_cylinder = 2 * np.pi * self.R_cylinder * self.H_cylinder
        A_liner_dome = 2 * np.pi * self.R_dome**2
        V_liner = (A_liner_cylinder + A_liner_dome) * self.t_steel_liner
        m_liner = self.rho_steel * V_liner
        
        # 内部设备质量 (简化)
        m_equipment = 2e6  # 2000吨
        
        total_mass = m_cylinder_concrete + m_dome_concrete + m_base + m_liner + m_equipment
        
        return {
            'cylinder_concrete': m_cylinder_concrete,
            'dome_concrete': m_dome_concrete,
            'base': m_base,
            'steel_liner': m_liner,
            'equipment': m_equipment,
            'total': total_mass
        }
    
    def calculate_stiffness(self):
        """
        计算结构刚度
        """
        # 筒体轴向刚度
        A_cylinder = 2 * np.pi * self.R_cylinder * self.t_cylinder
        K_axial = self.E_concrete * A_cylinder / self.H_cylinder
        
        # 筒体弯曲刚度
        I_cylinder = np.pi * self.R_cylinder**3 * self.t_cylinder
        K_bending = 3 * self.E_concrete * I_cylinder / self.H_cylinder**3
        
        # 考虑预应力的等效刚度
        K_effective = K_bending * (1 + self.P_hoop / (self.E_concrete * A_cylinder / self.H_cylinder))
        
        # 穹顶薄膜刚度
        A_dome = 2 * np.pi * self.R_dome * self.t_dome
        K_dome = self.E_concrete * A_dome / self.R_dome
        
        # 基础刚度 (假设刚性基础)
        K_base = 1e12  # 很大值表示固定
        
        return {
            'axial': K_axial,
            'bending': K_bending,
            'effective': K_effective,
            'dome': K_dome,
            'base': K_base
        }

# 创建安全壳实例
containment = ContainmentVessel()

# 输出几何参数
print(f"筒体半径: {containment.R_cylinder} m")
print(f"筒体高度: {containment.H_cylinder} m")
print(f"筒体厚度: {containment.t_cylinder} m")
print(f"穹顶半径: {containment.R_dome} m")
print(f"基础厚度: {containment.H_base} m")

# 计算质量和刚度
mass_props = containment.calculate_mass()
stiffness_props = containment.calculate_stiffness()
section_props = containment.calculate_section_properties()

print(f"\n结构质量:")
print(f"  筒体混凝土: {mass_props['cylinder_concrete']/1000:.1f} 吨")
print(f"  穹顶混凝土: {mass_props['dome_concrete']/1000:.1f} 吨")
print(f"  基础: {mass_props['base']/1000:.1f} 吨")
print(f"  钢衬里: {mass_props['steel_liner']/1000:.1f} 吨")
print(f"  内部设备: {mass_props['equipment']/1000:.1f} 吨")
print(f"  总质量: {mass_props['total']/1000:.1f} 吨")

# ==================== 2. 有限元建模 ====================
print("\n" + "=" * 60)
print("2. 有限元建模")
print("=" * 60)

def create_simplified_npp_model():
    """
    创建简化的核电站有限元模型
    使用集中质量-弹簧模型
    """
    # 模型参数
    n_stories = 8  # 分层数
    story_height = containment.H_cylinder / n_stories
    
    # 节点坐标 (简化为一维竖向模型)
    nodes = np.zeros((n_stories + 1, 3))
    for i in range(n_stories + 1):
        nodes[i, 2] = i * story_height  # z坐标
    
    # 单元连接
    elements = []
    for i in range(n_stories):
        elements.append([i, i+1])
    elements = np.array(elements)
    
    # 质量矩阵 (集中质量)
    mass_per_story = mass_props['total'] / n_stories
    M = np.eye(n_stories + 1) * mass_per_story
    M[0, 0] *= 2  # 基础质量加倍
    
    # 刚度矩阵
    K = np.zeros((n_stories + 1, n_stories + 1))
    k_story = stiffness_props['effective'] / n_stories
    
    for i in range(n_stories):
        K[i, i] += k_story
        K[i, i+1] -= k_story
        K[i+1, i] -= k_story
        K[i+1, i+1] += k_story
    
    # 基础约束
    K[0, :] = 0
    K[:, 0] = 0
    K[0, 0] = 1e12
    
    return nodes, elements, M, K, story_height

nodes, elements, M, K, story_height = create_simplified_npp_model()

n_nodes = len(nodes)
n_dof = n_nodes  # 每个节点1个自由度 (竖向)

print(f"节点数: {n_nodes}")
print(f"单元数: {len(elements)}")
print(f"自由度数: {n_dof}")
print(f"层高: {story_height:.2f} m")

# ==================== 3. 模态分析 ====================
print("\n" + "=" * 60)
print("3. 模态分析")
print("=" * 60)

# 提取非约束自由度
free_dof = list(range(1, n_nodes))  # 排除基础节点

M_free = M[np.ix_(free_dof, free_dof)]
K_free = K[np.ix_(free_dof, free_dof)]

# 求解特征值问题
n_modes = min(6, len(free_dof))
eigenvalues, eigenvectors = eigh(K_free, M_free, subset_by_index=[0, n_modes-1])

# 计算频率和周期
omega = np.sqrt(eigenvalues)  # 圆频率 (rad/s)
frequencies = omega / (2 * np.pi)  # 频率 (Hz)
periods = 1 / frequencies  # 周期 (s)

print(f"\n前{n_modes}阶模态频率:")
for i in range(n_modes):
    print(f"  模态{i+1}: f = {frequencies[i]:.3f} Hz, T = {periods[i]:.3f} s")

# 质量参与系数计算
def calculate_mass_participation(eigenvectors, M):
    """
    计算质量参与系数
    """
    n_modes = eigenvectors.shape[1]
    mass_participation = np.zeros(n_modes)
    
    total_mass = np.sum(M)
    
    for i in range(n_modes):
        phi = eigenvectors[:, i]
        # 归一化
        phi = phi / np.sqrt(phi @ M @ phi)
        
        # 计算参与系数
        participation_factor = np.sum(M @ phi)
        effective_mass = participation_factor**2
        
        mass_participation[i] = effective_mass / total_mass * 100
    
    return mass_participation

mass_participation = calculate_mass_participation(eigenvectors, M_free)

print(f"\n质量参与系数:")
cumulative = 0
for i in range(n_modes):
    cumulative += mass_participation[i]
    print(f"  模态{i+1}: {mass_participation[i]:.2f}%, 累积: {cumulative:.2f}%")

# ==================== 4. 振型可视化 ====================
print("\n" + "=" * 60)
print("4. 振型可视化")
print("=" * 60)

fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()

# 添加基础节点位移 (为0)
full_eigenvectors = np.zeros((n_nodes, n_modes))
full_eigenvectors[free_dof, :] = eigenvectors

for i in range(n_modes):
    ax = axes[i]
    
    # 归一化振型
    mode_shape = full_eigenvectors[:, i]
    max_disp = np.max(np.abs(mode_shape))
    if max_disp > 0:
        mode_shape = mode_shape / max_disp
    
    # 绘制振型
    y = nodes[:, 2]  # 高度
    x = mode_shape * 5  # 放大显示
    
    ax.plot(x, y, 'b-o', linewidth=2, markersize=6)
    ax.fill_betweenx(y, 0, x, alpha=0.3)
    
    # 绘制未变形形状
    ax.axvline(x=0, color='k', linestyle='--', alpha=0.5, linewidth=1)
    
    ax.set_xlabel('Normalized Displacement', fontsize=10)
    ax.set_ylabel('Height (m)', fontsize=10)
    ax.set_title(f'Mode {i+1}: f = {frequencies[i]:.2f} Hz\nMass Participation: {mass_participation[i]:.1f}%', 
                 fontsize=10, fontweight='bold')
    ax.grid(True, alpha=0.3)
    ax.set_xlim(-6, 6)
    ax.set_ylim(0, containment.H_cylinder + 5)

plt.tight_layout()
plt.savefig(f'{output_dir}/case1_mode_shapes.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 振型图已保存")

# ==================== 5. 频率分析 ====================
print("\n" + "=" * 60)
print("5. 频率分析")
print("=" * 60)

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

# 频率分布图
ax1 = axes[0]
mode_numbers = np.arange(1, n_modes + 1)
bars = ax1.bar(mode_numbers, frequencies, color='steelblue', alpha=0.8, edgecolor='black')
ax1.set_xlabel('Mode Number', fontsize=11)
ax1.set_ylabel('Frequency (Hz)', fontsize=11)
ax1.set_title('Natural Frequencies of Containment Vessel', fontsize=12, fontweight='bold')
ax1.set_xticks(mode_numbers)
ax1.grid(True, alpha=0.3, axis='y')

# 添加数值标签
for bar, freq in zip(bars, frequencies):
    height = bar.get_height()
    ax1.text(bar.get_x() + bar.get_width()/2., height + 0.01,
             f'{freq:.2f}', ha='center', va='bottom', fontsize=9)

# 周期分布图
ax2 = axes[1]
bars2 = ax2.bar(mode_numbers, periods, color='coral', alpha=0.8, edgecolor='black')
ax2.set_xlabel('Mode Number', fontsize=11)
ax2.set_ylabel('Period (s)', fontsize=11)
ax2.set_title('Natural Periods of Containment Vessel', fontsize=12, fontweight='bold')
ax2.set_xticks(mode_numbers)
ax2.grid(True, alpha=0.3, axis='y')

# 添加数值标签
for bar, period in zip(bars2, periods):
    height = bar.get_height()
    ax2.text(bar.get_x() + bar.get_width()/2., height + 0.01,
             f'{period:.3f}', ha='center', va='bottom', fontsize=9)

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

# ==================== 6. 质量参与系数分析 ====================
print("\n" + "=" * 60)
print("6. 质量参与系数分析")
print("=" * 60)

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

# 各模态质量参与系数
ax1 = axes[0]
bars = ax1.bar(mode_numbers, mass_participation, color='mediumseagreen', alpha=0.8, edgecolor='black')
ax1.set_xlabel('Mode Number', fontsize=11)
ax1.set_ylabel('Mass Participation (%)', fontsize=11)
ax1.set_title('Mass Participation Factor by Mode', fontsize=12, fontweight='bold')
ax1.set_xticks(mode_numbers)
ax1.grid(True, alpha=0.3, axis='y')

# 添加数值标签
for bar, mp in zip(bars, mass_participation):
    height = bar.get_height()
    ax1.text(bar.get_x() + bar.get_width()/2., height + 0.5,
             f'{mp:.1f}%', ha='center', va='bottom', fontsize=9)

# 累积质量参与系数
ax2 = axes[1]
cumulative_mp = np.cumsum(mass_participation)
ax2.plot(mode_numbers, cumulative_mp, 'b-o', linewidth=2, markersize=8, label='Cumulative')
ax2.axhline(y=90, color='r', linestyle='--', alpha=0.7, label='90% Target')
ax2.set_xlabel('Number of Modes', fontsize=11)
ax2.set_ylabel('Cumulative Mass Participation (%)', fontsize=11)
ax2.set_title('Cumulative Mass Participation', fontsize=12, fontweight='bold')
ax2.set_xticks(mode_numbers)
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)
ax2.set_ylim(0, 105)

plt.tight_layout()
plt.savefig(f'{output_dir}/case1_mass_participation.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 质量参与系数图已保存")

# ==================== 7. 结构特性总结 ====================
print("\n" + "=" * 60)
print("7. 结构特性总结")
print("=" * 60)

print("\n【结构基本特性】")
print(f"基本周期: {periods[0]:.3f} s")
print(f"基本频率: {frequencies[0]:.3f} Hz")
print(f"总质量: {mass_props['total']/1000:.1f} 吨")

# 估算地震响应
print("\n【地震响应估算】")
# 假设设计谱在基本周期处的加速度
T1 = periods[0]
if T1 <= 0.4:
    Sa = 0.3 * 5.0  # g
else:
    Sa = 0.3 * 5.0 * (0.4 / T1)**0.67

print(f"假设SSE地震 PGA=0.3g")
print(f"在T1={T1:.3f}s处谱加速度: {Sa:.3f}g")

# 估算基底剪力
W = mass_props['total'] * 9.81  # 重量
V_base = W * Sa  # 基底剪力
print(f"估算基底剪力: {V_base/1000:.1f} kN")

# 估算顶部位移
H_total = containment.H_cylinder + containment.R_dome
delta_top = Sa * 9.81 * (H_total / (2 * np.pi * frequencies[0])**2) * 1000  # mm
print(f"估算顶部位移: {delta_top:.2f} mm")

# ==================== 8. 设计校核 ====================
print("\n" + "=" * 60)
print("8. 设计校核")
print("=" * 60)

# 与规范要求比较
print("\n【规范要求校核】")

# 基本周期范围
if 0.1 <= periods[0] <= 2.0:
    print(f"✓ 基本周期 {periods[0]:.3f}s 在合理范围内 (0.1-2.0s)")
else:
    print(f"✗ 基本周期 {periods[0]:.3f}s 超出典型范围")

# 质量参与要求
if cumulative_mp[-1] >= 90:
    print(f"✓ 累积质量参与系数 {cumulative_mp[-1]:.1f}% ≥ 90%")
else:
    print(f"✗ 累积质量参与系数 {cumulative_mp[-1]:.1f}% < 90%,需增加模态数")

# 层间位移角估算
interstory_drift = delta_top / (containment.H_cylinder * 1000)
if interstory_drift <= 0.002:
    print(f"✓ 估算层间位移角 {interstory_drift:.5f} ≤ 0.002")
else:
    print(f"✗ 估算层间位移角 {interstory_drift:.5f} > 0.002")

# ==================== 仿真总结 ====================
print("\n" + "=" * 60)
print("案例1仿真完成总结")
print("=" * 60)
print("\n本案例实现了以下建模与模态分析内容:")
print("1. ✓ 反应堆安全壳几何建模")
print("2. ✓ 材料参数定义")
print("3. ✓ 简化有限元模型建立")
print("4. ✓ 质量矩阵和刚度矩阵组装")
print("5. ✓ 模态频率和振型计算")
print("6. ✓ 质量参与系数分析")
print("7. ✓ 振型可视化")
print("8. ✓ 设计校核")

print("\n关键结果:")
print(f"  基本频率: {frequencies[0]:.3f} Hz")
print(f"  基本周期: {periods[0]:.3f} s")
print(f"  总质量: {mass_props['total']/1000:.1f} 吨")
print(f"  第一阶质量参与: {mass_participation[0]:.1f}%")

print("\n生成的文件:")
print(f"  - {output_dir}/case1_mode_shapes.png")
print(f"  - {output_dir}/case1_frequency_analysis.png")
print(f"  - {output_dir}/case1_mass_participation.png")
print("\n" + "=" * 60)


# ============================================
# 生成GIF动画
# ============================================

def create_animation():
    """创建仿真结果动画"""
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    import numpy as np
    
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.set_xlim(0, 10)
    ax.set_ylim(-2, 2)
    ax.set_xlabel('Time', fontsize=12)
    ax.set_ylabel('Response', fontsize=12)
    ax.set_title('Dynamic Response Animation', fontsize=14, fontweight='bold')
    ax.grid(True, alpha=0.3)
    
    line, = ax.plot([], [], 'b-', linewidth=2)
    
    def init():
        line.set_data([], [])
        return line,
    
    def update(frame):
        x = np.linspace(0, 10, 100)
        y = np.sin(x - frame * 0.2) * np.exp(-frame * 0.01)
        line.set_data(x, y)
        return line,
    
    anim = FuncAnimation(fig, update, init_func=init, frames=50, interval=100, blit=True)
    
    output_dir = os.path.dirname(os.path.abspath(__file__))
    anim.save(f'{output_dir}/simulation_animation.gif', writer='pillow', fps=10)
    print(f"动画已保存到: {output_dir}/simulation_animation.gif")
    plt.close()

if __name__ == '__main__':
    create_animation()

案例2:地震荷载输入与反应谱分析

  • 设计反应谱生成
  • 地震波选取与调整
  • 反应谱分析与模态组合
# -*- coding: utf-8 -*-
"""
案例2:地震荷载输入与反应谱分析
本案例实现核电站结构的地震荷载输入和反应谱分析
"""

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import eigh
from scipy.integrate import trapezoid
import warnings
warnings.filterwarnings('ignore')
import os

# 创建输出目录
output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题052'
os.makedirs(output_dir, exist_ok=True)

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("案例2:地震荷载输入与反应谱分析")
print("=" * 60)

# ==================== 1. 核电站结构参数 ====================
print("\n【1. 核电站结构参数】")

# 结构参数 (基于案例1)
n_stories = 8
story_height = 5.0  # m
H_total = n_stories * story_height  # 40 m

# 质量矩阵 (集中质量)
mass_total = 48261.6e3  # kg
mass_per_story = mass_total / n_stories

M = np.eye(n_stories + 1) * mass_per_story
M[0, 0] *= 2  # 基础质量加倍

# 刚度矩阵 (基于案例1结果调整)
K = np.zeros((n_stories + 1, n_stories + 1))
k_story = 1.5e10  # N/m

for i in range(n_stories):
    K[i, i] += k_story
    K[i, i+1] -= k_story
    K[i+1, i] -= k_story
    K[i+1, i+1] += k_story

# 基础约束
K[0, :] = 0
K[:, 0] = 0
K[0, 0] = 1e12

print(f"结构高度: {H_total} m")
print(f"层数: {n_stories}")
print(f"总质量: {mass_total/1000:.1f} 吨")

# ==================== 2. 模态分析 ====================
print("\n" + "=" * 60)
print("2. 模态分析")
print("=" * 60)

free_dof = list(range(1, n_stories + 1))
M_free = M[np.ix_(free_dof, free_dof)]
K_free = K[np.ix_(free_dof, free_dof)]

n_modes = 6
eigenvalues, eigenvectors = eigh(K_free, M_free, subset_by_index=[0, n_modes-1])

omega = np.sqrt(eigenvalues)
frequencies = omega / (2 * np.pi)
periods = 1 / frequencies

print(f"\n前{n_modes}阶模态:")
for i in range(n_modes):
    print(f"  模态{i+1}: f = {frequencies[i]:.3f} Hz, T = {periods[i]:.3f} s")

# ==================== 3. 设计反应谱生成 ====================
print("\n" + "=" * 60)
print("3. 设计反应谱生成")
print("=" * 60)

def nuclear_design_spectrum(T, PGA, spectrum_type='RG1.60'):
    """
    核电站设计反应谱
    
    Parameters:
    -----------
    T : array
        周期 (s)
    PGA : float
        峰值地面加速度 (g)
    spectrum_type : str
        谱类型 ('RG1.60', 'GB50267')
    
    Returns:
    --------
    Sa : array
        谱加速度 (g)
    """
    T = np.atleast_1d(T)
    Sa = np.zeros_like(T)
    
    if spectrum_type == 'RG1.60':
        # NRC RG 1.60 标准谱
        for i, t in enumerate(T):
            if t <= 0.03:
                Sa[i] = PGA * 1.0
            elif t <= 0.12:
                Sa[i] = PGA * (1.0 + 4.0 * (t - 0.03) / 0.09)
            elif t <= 0.4:
                Sa[i] = PGA * 5.0
            elif t <= 3.0:
                Sa[i] = PGA * 5.0 * (0.4 / t)**0.67
            else:
                Sa[i] = PGA * 5.0 * (0.4 / 3.0)**0.67 * (3.0 / t)
    
    elif spectrum_type == 'GB50267':
        # 中国核电厂抗震设计标准
        for i, t in enumerate(T):
            if t <= 0.1:
                Sa[i] = PGA * (1.0 + 10.0 * t)
            elif t <= 0.5:
                Sa[i] = PGA * 2.0
            elif t <= 3.0:
                Sa[i] = PGA * 2.0 * (0.5 / t)**0.6
            else:
                Sa[i] = PGA * 2.0 * (0.5 / 3.0)**0.6 * (3.0 / t)
    
    return Sa

# 生成设计谱
T_range = np.linspace(0.01, 5.0, 500)

# OBE (运行基准地震): PGA = 0.1g
PGA_OBE = 0.1
Sa_OBE_RG160 = nuclear_design_spectrum(T_range, PGA_OBE, 'RG1.60')
Sa_OBE_GB = nuclear_design_spectrum(T_range, PGA_OBE, 'GB50267')

# SSE (安全停堆地震): PGA = 0.3g
PGA_SSE = 0.3
Sa_SSE_RG160 = nuclear_design_spectrum(T_range, PGA_SSE, 'RG1.60')
Sa_SSE_GB = nuclear_design_spectrum(T_range, PGA_SSE, 'GB50267')

print(f"\n设计地震参数:")
print(f"  OBE (运行基准地震): PGA = {PGA_OBE}g")
print(f"  SSE (安全停堆地震): PGA = {PGA_SSE}g")

# 绘制设计谱
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# OBE设计谱
ax1 = axes[0]
ax1.plot(T_range, Sa_OBE_RG160, 'b-', linewidth=2, label='RG 1.60 (US NRC)')
ax1.plot(T_range, Sa_OBE_GB, 'r--', linewidth=2, label='GB 50267 (China)')
ax1.set_xlabel('Period (s)', fontsize=11)
ax1.set_ylabel('Spectral Acceleration (g)', fontsize=11)
ax1.set_title(f'OBE Design Response Spectrum (PGA={PGA_OBE}g)', fontsize=12, fontweight='bold')
ax1.legend(fontsize=10)
ax1.grid(True, alpha=0.3)
ax1.set_xlim(0, 5)
ax1.set_ylim(0, 0.6)

# SSE设计谱
ax2 = axes[1]
ax2.plot(T_range, Sa_SSE_RG160, 'b-', linewidth=2, label='RG 1.60 (US NRC)')
ax2.plot(T_range, Sa_SSE_GB, 'r--', linewidth=2, label='GB 50267 (China)')
ax2.set_xlabel('Period (s)', fontsize=11)
ax2.set_ylabel('Spectral Acceleration (g)', fontsize=11)
ax2.set_title(f'SSE Design Response Spectrum (PGA={PGA_SSE}g)', fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)
ax2.set_xlim(0, 5)
ax2.set_ylim(0, 1.8)

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

# ==================== 4. 地震波生成 ====================
print("\n" + "=" * 60)
print("4. 地震波生成")
print("=" * 60)

def generate_artificial_earthquake(target_spectrum, T_range, PGA, dt, duration, n_waves=30):
    """
    生成人工地震波
    """
    t = np.linspace(0, duration, int(duration/dt))
    acc = np.zeros_like(t)
    
    # 生成谐波叠加
    np.random.seed(42)  # 保证可重复
    
    freqs = np.linspace(0.5, 25, n_waves)
    for freq in freqs:
        T = 1.0 / freq if freq > 0 else 0.1
        # 从目标谱获取幅值
        idx = np.argmin(np.abs(T_range - T))
        if idx < len(target_spectrum):
            amp = target_spectrum[idx]
        else:
            amp = PGA
        
        phase = np.random.uniform(0, 2*np.pi)
        omega = 2 * np.pi * freq
        acc += amp * np.sin(omega * t + phase)
    
    # 添加包络函数
    t_rise = 2.0
    t_stationary = duration - 6.0
    envelope = np.ones_like(t)
    
    for i, ti in enumerate(t):
        if ti < t_rise:
            envelope[i] = (ti / t_rise)**2
        elif ti > t_stationary:
            envelope[i] = np.exp(-2 * (ti - t_stationary) / 5)
    
    acc *= envelope
    
    # 调整峰值
    acc = acc / np.max(np.abs(acc)) * PGA * 9.81
    
    return t, acc

# 生成地震波
dt = 0.01
duration = 30.0

t_OBE, acc_OBE = generate_artificial_earthquake(Sa_OBE_RG160, T_range, PGA_OBE, dt, duration)
t_SSE, acc_SSE = generate_artificial_earthquake(Sa_SSE_RG160, T_range, PGA_SSE, dt, duration)

print(f"\n地震波参数:")
print(f"  时间步长: {dt} s")
print(f"  持续时间: {duration} s")
print(f"  OBE峰值加速度: {np.max(np.abs(acc_OBE))/9.81:.3f}g")
print(f"  SSE峰值加速度: {np.max(np.abs(acc_SSE))/9.81:.3f}g")

# 绘制地震波
fig, axes = plt.subplots(2, 1, figsize=(12, 8))

ax1 = axes[0]
ax1.plot(t_OBE, acc_OBE/9.81, 'b-', linewidth=0.8)
ax1.set_xlabel('Time (s)', fontsize=11)
ax1.set_ylabel('Acceleration (g)', fontsize=11)
ax1.set_title(f'OBE Earthquake Time History (PGA={np.max(np.abs(acc_OBE))/9.81:.3f}g)', fontsize=12, fontweight='bold')
ax1.grid(True, alpha=0.3)
ax1.axhline(y=0, color='k', linestyle='-', alpha=0.3)

ax2 = axes[1]
ax2.plot(t_SSE, acc_SSE/9.81, 'r-', linewidth=0.8)
ax2.set_xlabel('Time (s)', fontsize=11)
ax2.set_ylabel('Acceleration (g)', fontsize=11)
ax2.set_title(f'SSE Earthquake Time History (PGA={np.max(np.abs(acc_SSE))/9.81:.3f}g)', fontsize=12, fontweight='bold')
ax2.grid(True, alpha=0.3)
ax2.axhline(y=0, color='k', linestyle='-', alpha=0.3)

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

# ==================== 5. 反应谱分析 ====================
print("\n" + "=" * 60)
print("5. 反应谱分析")
print("=" * 60)

def response_spectrum_analysis(M, K, frequencies, damping_ratio, spectrum_values):
    """
    反应谱分析
    
    Parameters:
    -----------
    M, K : arrays
        质量矩阵和刚度矩阵
    frequencies : array
        结构模态频率
    damping_ratio : float
        阻尼比
    spectrum_values : array
        设计谱在各周期处的值
    
    Returns:
    --------
    modal_responses : dict
        各模态响应
    """
    n_modes = len(frequencies)
    
    # 计算各模态响应
    modal_displacements = []
    modal_forces = []
    
    for i in range(n_modes):
        T_i = 1.0 / frequencies[i]
        
        # 从设计谱获取谱加速度
        idx = np.argmin(np.abs(T_range - T_i))
        Sa_i = spectrum_values[idx] * 9.81  # 转换为 m/s^2
        
        # 模态参与系数 (简化)
        gamma_i = 1.0  # 假设归一化
        
        # 模态位移
        Sd_i = Sa_i / (2 * np.pi * frequencies[i])**2
        modal_disp = gamma_i * Sd_i
        modal_displacements.append(modal_disp)
        
        # 模态力
        modal_force = M @ eigenvectors[:, i] * Sa_i
        modal_forces.append(modal_force)
    
    return {
        'displacements': np.array(modal_displacements),
        'forces': modal_forces
    }

# 进行反应谱分析 (SSE)
modal_responses_SSE = response_spectrum_analysis(M_free, K_free, frequencies, 0.05, Sa_SSE_RG160)

# 模态组合 (SRSS)
def srss_combination(modal_values):
    """SRSS模态组合"""
    return np.sqrt(np.sum(modal_values**2))

# 计算层间位移
story_displacements_SSE = np.zeros(n_stories)
for story in range(n_stories):
    story_disp_modal = []
    for mode in range(n_modes):
        # 简化的层间位移计算
        disp = modal_responses_SSE['displacements'][mode] * np.sin(np.pi * (story + 1) / (2 * n_stories))
        story_disp_modal.append(disp)
    story_displacements_SSE[story] = srss_combination(np.array(story_disp_modal))

print(f"\nSSE地震下各层位移 (SRSS组合):")
for i, disp in enumerate(story_displacements_SSE):
    print(f"  层{i+1}: {disp*1000:.2f} mm")

# ==================== 6. 层间剪力计算 ====================
print("\n" + "=" * 60)
print("6. 层间剪力计算")
print("=" * 60)

# 计算层间剪力
story_shear_SSE = np.zeros(n_stories)
for story in range(n_stories):
    # 该层以上质量
    mass_above = np.sum(M[story+1:, story+1:])
    # 简化的层间剪力
    story_shear_SSE[story] = mass_above * 0.3 * 9.81  # 假设0.3g

print(f"\nSSE地震下层间剪力:")
for i, shear in enumerate(story_shear_SSE):
    print(f"  层{i+1}: {shear/1000:.1f} kN")

# 绘制层间响应
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# 层间位移
ax1 = axes[0]
story_heights = np.arange(1, n_stories + 1) * story_height
ax1.plot(story_displacements_SSE * 1000, story_heights, 'b-o', linewidth=2, markersize=8)
ax1.fill_betweenx(story_heights, 0, story_displacements_SSE * 1000, alpha=0.3)
ax1.set_xlabel('Displacement (mm)', fontsize=11)
ax1.set_ylabel('Height (m)', fontsize=11)
ax1.set_title('SSE Story Displacement Profile', fontsize=12, fontweight='bold')
ax1.grid(True, alpha=0.3)
ax1.set_ylim(0, H_total + 2)

# 层间剪力
ax2 = axes[1]
ax2.plot(story_shear_SSE / 1000, story_heights, 'r-s', linewidth=2, markersize=8)
ax2.fill_betweenx(story_heights, 0, story_shear_SSE / 1000, alpha=0.3, color='red')
ax2.set_xlabel('Story Shear (kN)', fontsize=11)
ax2.set_ylabel('Height (m)', fontsize=11)
ax2.set_title('SSE Story Shear Profile', fontsize=12, fontweight='bold')
ax2.grid(True, alpha=0.3)
ax2.set_ylim(0, H_total + 2)

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

# ==================== 7. 模态组合比较 ====================
print("\n" + "=" * 60)
print("7. 模态组合方法比较")
print("=" * 60)

def cqc_combination(modal_values, frequencies, damping=0.05):
    """
    CQC模态组合
    """
    n_modes = len(modal_values)
    combined = 0
    
    for i in range(n_modes):
        for j in range(n_modes):
            if i == j:
                rho = 1.0
            else:
                r = frequencies[j] / frequencies[i]
                rho = (8 * damping**2 * r * (1 + r) * r**1.5) / \
                      ((1 - r**2)**2 + 4 * damping**2 * r * (1 + r)**2)
            
            combined += rho * modal_values[i] * modal_values[j]
    
    return np.sqrt(combined)

# 比较不同组合方法
modal_acc = np.array([1.0, 0.5, 0.3, 0.2, 0.1, 0.05])  # 假设模态加速度

srss_result = srss_combination(modal_acc)
cqc_result = cqc_combination(modal_acc, frequencies)
abs_result = np.sum(np.abs(modal_acc))

print(f"\n模态组合结果比较 (顶部加速度):")
print(f"  SRSS法: {srss_result:.3f}g")
print(f"  CQC法: {cqc_result:.3f}g")
print(f"  ABS法: {abs_result:.3f}g")
print(f"  CQC/SRSS: {cqc_result/srss_result:.3f}")

# 绘制组合方法比较
fig, ax = plt.subplots(figsize=(10, 6))

methods = ['SRSS', 'CQC', 'ABS']
values = [srss_result, cqc_result, abs_result]
colors = ['steelblue', 'mediumseagreen', 'coral']

bars = ax.bar(methods, values, color=colors, alpha=0.8, edgecolor='black')
ax.set_ylabel('Combined Acceleration (g)', fontsize=11)
ax.set_title('Modal Combination Methods Comparison', fontsize=12, fontweight='bold')
ax.grid(True, alpha=0.3, axis='y')

# 添加数值标签
for bar, val in zip(bars, values):
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height + 0.02,
            f'{val:.3f}', ha='center', va='bottom', fontsize=11, fontweight='bold')

plt.tight_layout()
plt.savefig(f'{output_dir}/case2_combination_comparison.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 组合方法比较图已保存")

# ==================== 8. 设计校核 ====================
print("\n" + "=" * 60)
print("8. 设计校核")
print("=" * 60)

print("\n【OBE地震校核】")
max_disp_OBE = np.max(story_displacements_SSE) * (PGA_OBE / PGA_SSE) * 1000  # mm
print(f"  最大位移: {max_disp_OBE:.2f} mm")
print(f"  层间位移角: {max_disp_OBE/(story_height*1000):.5f}")
if max_disp_OBE / (story_height * 1000) <= 0.002:
    print(f"  ✓ OBE层间位移角满足要求")
else:
    print(f"  ✗ OBE层间位移角超出限值")

print("\n【SSE地震校核】")
max_disp_SSE = np.max(story_displacements_SSE) * 1000  # mm
print(f"  最大位移: {max_disp_SSE:.2f} mm")
print(f"  层间位移角: {max_disp_SSE/(story_height*1000):.5f}")
if max_disp_SSE / (story_height * 1000) <= 0.005:
    print(f"  ✓ SSE层间位移角满足要求")
else:
    print(f"  ✗ SSE层间位移角超出限值")

# 基底剪力校核
V_base_SSE = np.sum(story_shear_SSE)
V_base_OBE = V_base_SSE * (PGA_OBE / PGA_SSE)
print(f"\n基底剪力:")
print(f"  OBE: {V_base_OBE/1000:.1f} kN")
print(f"  SSE: {V_base_SSE/1000:.1f} kN")

# 剪重比
shear_weight_ratio_OBE = V_base_OBE / (mass_total * 9.81)
shear_weight_ratio_SSE = V_base_SSE / (mass_total * 9.81)
print(f"\n剪重比:")
print(f"  OBE: {shear_weight_ratio_OBE:.3f}")
print(f"  SSE: {shear_weight_ratio_SSE:.3f}")

# ==================== 仿真总结 ====================
print("\n" + "=" * 60)
print("案例2仿真完成总结")
print("=" * 60)
print("\n本案例实现了以下地震荷载分析内容:")
print("1. ✓ 核电站设计反应谱生成 (RG 1.60和GB 50267)")
print("2. ✓ OBE和SSE两级地震定义")
print("3. ✓ 人工地震波生成")
print("4. ✓ 反应谱分析实施")
print("5. ✓ 层间位移和剪力计算")
print("6. ✓ SRSS和CQC模态组合")
print("7. ✓ 设计校核")

print("\n关键结果:")
print(f"  基本周期: {periods[0]:.3f} s")
print(f"  OBE PGA: {PGA_OBE}g")
print(f"  SSE PGA: {PGA_SSE}g")
print(f"  SSE最大位移: {max_disp_SSE:.2f} mm")
print(f"  SSE基底剪力: {V_base_SSE/1000:.1f} kN")

print("\n生成的文件:")
print(f"  - {output_dir}/case2_design_spectra.png")
print(f"  - {output_dir}/case2_earthquake_time_history.png")
print(f"  - {output_dir}/case2_story_response.png")
print(f"  - {output_dir}/case2_combination_comparison.png")
print("\n" + "=" * 60)


# ============================================
# 生成GIF动画
# ============================================

def create_animation():
    """创建仿真结果动画"""
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    import numpy as np
    
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.set_xlim(0, 10)
    ax.set_ylim(-2, 2)
    ax.set_xlabel('Time', fontsize=12)
    ax.set_ylabel('Response', fontsize=12)
    ax.set_title('Dynamic Response Animation', fontsize=14, fontweight='bold')
    ax.grid(True, alpha=0.3)
    
    line, = ax.plot([], [], 'b-', linewidth=2)
    
    def init():
        line.set_data([], [])
        return line,
    
    def update(frame):
        x = np.linspace(0, 10, 100)
        y = np.sin(x - frame * 0.2) * np.exp(-frame * 0.01)
        line.set_data(x, y)
        return line,
    
    anim = FuncAnimation(fig, update, init_func=init, frames=50, interval=100, blit=True)
    
    output_dir = os.path.dirname(os.path.abspath(__file__))
    anim.save(f'{output_dir}/simulation_animation.gif', writer='pillow', fps=10)
    print(f"动画已保存到: {output_dir}/simulation_animation.gif")
    plt.close()

if __name__ == '__main__':
    create_animation()

案例3:结构-设备相互作用分析

  • 设备建模与耦合分析
  • 楼层反应谱计算
  • 设备抗震鉴定
# -*- coding: utf-8 -*-
"""
案例3:结构-设备相互作用分析
本案例实现核电站结构-设备相互作用的动力学分析
"""

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import eigh
import warnings
warnings.filterwarnings('ignore')
import os

# 创建输出目录
output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题052'
os.makedirs(output_dir, exist_ok=True)

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("案例3:结构-设备相互作用分析")
print("=" * 60)

# ==================== 1. 结构模型 ====================
print("\n【1. 结构模型】")

# 核电站结构参数
n_stories = 8
story_height = 5.0  # m
H_total = n_stories * story_height

# 质量和刚度矩阵
mass_total = 48261.6e3
mass_per_story = mass_total / n_stories

M_structure = np.eye(n_stories) * mass_per_story

K_structure = np.zeros((n_stories, n_stories))
k_story = 1.5e10

for i in range(n_stories):
    K_structure[i, i] = k_story
    if i > 0:
        K_structure[i, i] += k_story
        K_structure[i, i-1] = -k_story
        K_structure[i-1, i] = -k_story

print(f"结构层数: {n_stories}")
print(f"结构高度: {H_total} m")
print(f"结构总质量: {mass_total/1000:.1f} 吨")

# ==================== 2. 设备模型 ====================
print("\n" + "=" * 60)
print("2. 设备模型")
print("=" * 60)

class NuclearEquipment:
    """
    核电设备类
    """
    def __init__(self, name, mass, height, support_stiffness, damping_ratio=0.02):
        self.name = name
        self.mass = mass
        self.height = height
        self.support_stiffness = support_stiffness
        self.damping_ratio = damping_ratio
        
        # 计算设备频率
        self.omega = np.sqrt(support_stiffness / mass)
        self.frequency = self.omega / (2 * np.pi)
        self.period = 1 / self.frequency
    
    def __str__(self):
        return f"{self.name}: m={self.mass/1000:.0f}t, f={self.frequency:.2f}Hz"

# 定义主要设备
equipments = {
    'RPV': NuclearEquipment('Reactor Pressure Vessel', 400e3, 12.0, 5e9, 0.02),
    'SG': NuclearEquipment('Steam Generator', 350e3, 15.0, 3e9, 0.03),
    'RCP': NuclearEquipment('Reactor Coolant Pump', 80e3, 8.0, 2e9, 0.02),
    'CRDM': NuclearEquipment('Control Rod Drive Mechanism', 20e3, 10.0, 1e9, 0.01),
}

print("\n主要设备参数:")
for name, eq in equipments.items():
    print(f"  {eq}")

# ==================== 3. 解耦分析 ====================
print("\n" + "=" * 60)
print("3. 解耦分析")
print("=" * 60)

# 结构模态分析
eigenvalues_str, eigenvectors_str = eigh(K_structure, M_structure)
omega_str = np.sqrt(eigenvalues_str)
frequencies_str = omega_str / (2 * np.pi)

print(f"\n结构基本频率: {frequencies_str[0]:.3f} Hz")

# 设备单独分析
print("\n设备单独频率:")
for name, eq in equipments.items():
    print(f"  {name}: {eq.frequency:.3f} Hz")

# 频率比分析
print("\n频率比 (设备/结构):")
for name, eq in equipments.items():
    ratio = eq.frequency / frequencies_str[0]
    print(f"  {name}: {ratio:.3f}")
    if 0.8 <= ratio <= 1.2:
        print(f"    ⚠ 可能发生共振!")

# ==================== 4. 耦合分析 ====================
print("\n" + "=" * 60)
print("4. 结构-设备耦合分析")
print("=" * 60)

def create_coupled_system(M_str, K_str, equipment, story_level):
    """
    创建结构-设备耦合系统
    
    Parameters:
    -----------
    M_str, K_str : arrays
        结构质量和刚度矩阵
    equipment : NuclearEquipment
        设备对象
    story_level : int
        设备所在楼层
    
    Returns:
    --------
    M_coupled, K_coupled : arrays
        耦合系统的质量和刚度矩阵
    """
    n_str = len(M_str)
    n_dof = n_str + 1  # 增加设备自由度
    
    # 质量矩阵
    M_coupled = np.eye(n_dof) * 1e-6  # 小值避免奇异
    M_coupled[:n_str, :n_str] = M_str
    M_coupled[n_str, n_str] = equipment.mass
    
    # 刚度矩阵
    K_coupled = np.zeros((n_dof, n_dof))
    K_coupled[:n_str, :n_str] = K_str
    
    # 设备与结构的耦合
    K_coupled[story_level, story_level] += equipment.support_stiffness
    K_coupled[n_str, n_str] = equipment.support_stiffness
    K_coupled[story_level, n_str] = -equipment.support_stiffness
    K_coupled[n_str, story_level] = -equipment.support_stiffness
    
    return M_coupled, K_coupled

# 以RPV为例进行耦合分析
rpv_story = 2  # RPV位于第3层
M_coupled, K_coupled = create_coupled_system(M_structure, K_structure, equipments['RPV'], rpv_story)

# 耦合系统模态分析
eigenvalues_coup, eigenvectors_coup = eigh(K_coupled, M_coupled)
omega_coup = np.sqrt(eigenvalues_coup)
frequencies_coup = omega_coup / (2 * np.pi)

print(f"\n耦合系统频率 (前6阶):")
for i in range(min(6, len(frequencies_coup))):
    print(f"  模态{i+1}: {frequencies_coup[i]:.3f} Hz")

# 比较耦合前后频率
print(f"\n频率变化 (耦合前→耦合后):")
print(f"  结构基频: {frequencies_str[0]:.3f}{frequencies_coup[0]:.3f} Hz")
print(f"  设备频率: {equipments['RPV'].frequency:.3f}{frequencies_coup[1]:.3f} Hz")

# ==================== 5. 楼层反应谱 ====================
print("\n" + "=" * 60)
print("5. 楼层反应谱计算")
print("=" * 60)

def calculate_floor_response_spectrum(structure_acc, dt, freq_range, damping=0.05):
    """
    计算楼层反应谱
    
    Parameters:
    -----------
    structure_acc : array
        楼层加速度时程
    dt : float
        时间步长
    freq_range : array
        频率范围
    damping : float
        阻尼比
    
    Returns:
    --------
    spectrum : array
        楼层反应谱
    """
    spectrum = np.zeros(len(freq_range))
    
    for i, f in enumerate(freq_range):
        if f < 0.1:  # 避免过低频率
            spectrum[i] = np.max(np.abs(structure_acc))
            continue
            
        omega = 2 * np.pi * f
        
        # 单自由度系统参数
        m = 1.0
        c = 2 * damping * omega * m
        k = omega**2 * m
        
        # 简化的绝对加速度反应谱计算
        # 使用频响函数方法
        omega_n = omega
        omega_ratio = omega_n / omega if omega > 0 else 1
        
        # 传递函数幅值
        H = 1 / np.sqrt((1 - omega_ratio**2)**2 + (2 * damping * omega_ratio)**2)
        
        # 最大响应
        spectrum[i] = np.max(np.abs(structure_acc)) * H
    
    return spectrum

# 生成地震波
dt = 0.01
duration = 30.0
t = np.linspace(0, duration, int(duration/dt))
PGA = 0.3

# 简化的地震波
np.random.seed(42)
acc_ground = np.random.randn(len(t))
# 滤波和包络
from scipy.signal import butter, filtfilt
b, a = butter(4, [0.5, 25], btype='band', fs=1/dt)
acc_ground = filtfilt(b, a, acc_ground)

# 包络
envelope = np.exp(-t/5) * np.sin(np.pi * t / duration)**2
acc_ground = acc_ground * envelope
acc_ground = acc_ground / np.max(np.abs(acc_ground)) * PGA * 9.81

# 计算结构响应 (简化)
structure_acc_top = acc_ground * 2.5  # 假设放大系数

# 计算楼层反应谱
freq_range = np.linspace(0.1, 50, 200)
floor_spectrum = calculate_floor_response_spectrum(structure_acc_top, dt, freq_range)

# 绘制楼层反应谱
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(freq_range, floor_spectrum / 9.81, 'b-', linewidth=2)
ax.set_xlabel('Frequency (Hz)', fontsize=11)
ax.set_ylabel('Floor Spectral Acceleration (g)', fontsize=11)
ax.set_title('Floor Response Spectrum at Top Level', fontsize=12, fontweight='bold')
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 50)

# 标记设备频率
for name, eq in equipments.items():
    idx = np.argmin(np.abs(freq_range - eq.frequency))
    sa_at_freq = floor_spectrum[idx] / 9.81
    ax.axvline(x=eq.frequency, color='r', linestyle='--', alpha=0.5)
    ax.text(eq.frequency, sa_at_freq + 0.2, name, rotation=90, fontsize=9)

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

# ==================== 6. 设备抗震鉴定 ====================
print("\n" + "=" * 60)
print("6. 设备抗震鉴定")
print("=" * 60)

def equipment_seismic_qualification(equipment, floor_sa):
    """
    设备抗震鉴定
    
    Parameters:
    -----------
    equipment : NuclearEquipment
        设备对象
    floor_sa : float
        楼层谱加速度
    
    Returns:
    --------
    qualification : dict
        鉴定结果
    """
    # 设备响应
    omega_ratio = equipment.omega / (2 * np.pi * 0.5)  # 假设结构频率0.5Hz
    amplification = 1 / np.sqrt((1 - omega_ratio**2)**2 + (2 * 0.05 * omega_ratio)**2)
    
    equipment_acc = floor_sa * amplification
    equipment_disp = equipment_acc / equipment.omega**2
    equipment_force = equipment.mass * equipment_acc
    
    # 假设设备能力 (简化)
    capacity_acc = 3.0 * 9.81  # 3g
    capacity_disp = 0.1  # 0.1m
    capacity_force = equipment.support_stiffness * 0.01  # 假设
    
    # 鉴定
    results = {
        'acceleration': {
            'demand': equipment_acc,
            'capacity': capacity_acc,
            'ratio': equipment_acc / capacity_acc,
            'passed': equipment_acc < capacity_acc
        },
        'displacement': {
            'demand': equipment_disp,
            'capacity': capacity_disp,
            'ratio': equipment_disp / capacity_disp,
            'passed': equipment_disp < capacity_disp
        },
        'force': {
            'demand': equipment_force,
            'capacity': capacity_force,
            'ratio': equipment_force / capacity_force,
            'passed': equipment_force < capacity_force
        }
    }
    
    results['overall'] = all([r['passed'] for r in results.values()])
    results['min_margin'] = min([1 - r['ratio'] for r in [results['acceleration'], results['displacement'], results['force']]])
    
    return results

# 鉴定所有设备
print("\n设备抗震鉴定结果:")
print("-" * 60)

floor_sa_value = np.max(floor_spectrum)
print(f"楼层谱加速度: {floor_sa_value/9.81:.3f}g\n")

for name, eq in equipments.items():
    qual = equipment_seismic_qualification(eq, floor_sa_value)
    
    print(f"{name}:")
    print(f"  加速度: {qual['acceleration']['demand']/9.81:.3f}g / {qual['acceleration']['capacity']/9.81:.3f}g (利用率: {qual['acceleration']['ratio']:.2f})")
    print(f"  位移: {qual['displacement']['demand']*1000:.2f}mm / {qual['displacement']['capacity']*1000:.2f}mm (利用率: {qual['displacement']['ratio']:.2f})")
    
    if qual['overall']:
        print(f"  ✓ 鉴定通过 (裕度: {qual['min_margin']*100:.1f}%)")
    else:
        print(f"  ✗ 鉴定未通过")
    print()

# ==================== 7. 相互作用效应分析 ====================
print("=" * 60)
print("7. 相互作用效应分析")
print("=" * 60)

# 惯性相互作用因子
print("\n惯性相互作用因子:")
for name, eq in equipments.items():
    mass_ratio = eq.mass / mass_total
    interaction_factor = 1 + mass_ratio * 10  # 简化
    print(f"  {name}: {interaction_factor:.3f} (质量比: {mass_ratio*100:.2f}%)")

# 运动相互作用
print("\n运动相互作用 (楼层放大系数):")
for story in range(n_stories):
    height_ratio = (story + 1) / n_stories
    amplification = 1 + 1.5 * height_ratio  # 简化的线性分布
    print(f"  层{story+1}: {amplification:.2f}x")

# ==================== 8. 结果可视化 ====================
print("\n" + "=" * 60)
print("8. 结果可视化")
print("=" * 60)

# 设备频率分布图
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 设备频率分布
ax1 = axes[0]
eq_names = list(equipments.keys())
eq_freqs = [eq.frequency for eq in equipments.values()]
colors = plt.cm.Set3(np.linspace(0, 1, len(eq_names)))

bars = ax1.bar(eq_names, eq_freqs, color=colors, alpha=0.8, edgecolor='black')
ax1.axhline(y=frequencies_str[0], color='r', linestyle='--', linewidth=2, label=f'Structure f1={frequencies_str[0]:.2f}Hz')
ax1.set_ylabel('Frequency (Hz)', fontsize=11)
ax1.set_title('Equipment Natural Frequencies', fontsize=12, fontweight='bold')
ax1.legend(fontsize=10)
ax1.grid(True, alpha=0.3, axis='y')

# 添加数值标签
for bar, freq in zip(bars, eq_freqs):
    height = bar.get_height()
    ax1.text(bar.get_x() + bar.get_width()/2., height + 0.2,
             f'{freq:.2f}', ha='center', va='bottom', fontsize=9)

# 设备利用率
ax2 = axes[1]
utilization_ratios = []
for name, eq in equipments.items():
    qual = equipment_seismic_qualification(eq, floor_sa_value)
    utilization_ratios.append(qual['acceleration']['ratio'])

bars2 = ax2.bar(eq_names, utilization_ratios, color='coral', alpha=0.8, edgecolor='black')
ax2.axhline(y=1.0, color='r', linestyle='--', linewidth=2, label='Limit')
ax2.set_ylabel('Utilization Ratio', fontsize=11)
ax2.set_title('Equipment Seismic Utilization', fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3, axis='y')
ax2.set_ylim(0, 1.5)

# 添加数值标签
for bar, ratio in zip(bars2, utilization_ratios):
    height = bar.get_height()
    ax2.text(bar.get_x() + bar.get_width()/2., height + 0.02,
             f'{ratio:.2f}', ha='center', va='bottom', fontsize=9)

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

# ==================== 仿真总结 ====================
print("\n" + "=" * 60)
print("案例3仿真完成总结")
print("=" * 60)
print("\n本案例实现了以下结构-设备相互作用分析:")
print("1. ✓ 核电设备建模")
print("2. ✓ 解耦频率分析")
print("3. ✓ 结构-设备耦合分析")
print("4. ✓ 楼层反应谱计算")
print("5. ✓ 设备抗震鉴定")
print("6. ✓ 相互作用效应评估")

print("\n关键结果:")
print(f"  结构基频: {frequencies_str[0]:.3f} Hz")
print(f"  RPV频率: {equipments['RPV'].frequency:.3f} Hz")
print(f"  频率比: {equipments['RPV'].frequency/frequencies_str[0]:.3f}")
print(f"  楼层谱加速度: {floor_sa_value/9.81:.3f}g")

print("\n生成的文件:")
print(f"  - {output_dir}/case3_floor_spectrum.png")
print(f"  - {output_dir}/case3_equipment_analysis.png")
print("\n" + "=" * 60)


# ============================================
# 生成GIF动画
# ============================================

def create_animation():
    """创建仿真结果动画"""
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    import numpy as np
    
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.set_xlim(0, 10)
    ax.set_ylim(-2, 2)
    ax.set_xlabel('Time', fontsize=12)
    ax.set_ylabel('Response', fontsize=12)
    ax.set_title('Dynamic Response Animation', fontsize=14, fontweight='bold')
    ax.grid(True, alpha=0.3)
    
    line, = ax.plot([], [], 'b-', linewidth=2)
    
    def init():
        line.set_data([], [])
        return line,
    
    def update(frame):
        x = np.linspace(0, 10, 100)
        y = np.sin(x - frame * 0.2) * np.exp(-frame * 0.01)
        line.set_data(x, y)
        return line,
    
    anim = FuncAnimation(fig, update, init_func=init, frames=50, interval=100, blit=True)
    
    output_dir = os.path.dirname(os.path.abspath(__file__))
    anim.save(f'{output_dir}/simulation_animation.gif', writer='pillow', fps=10)
    print(f"动画已保存到: {output_dir}/simulation_animation.gif")
    plt.close()

if __name__ == '__main__':
    create_animation()

案例4:抗震安全评估与裕度分析

  • 易损性曲线生成
  • HCLPF能力计算
  • 裕度因子评估
# -*- coding: utf-8 -*-
"""
案例4:抗震安全评估与裕度分析
本案例实现核电站结构的抗震安全评估和裕度分析
"""

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import eigh
import warnings
warnings.filterwarnings('ignore')
import os

# 创建输出目录
output_dir = r'd:\文档\500仿真领域\工程仿真\结构动力学仿真\主题052'
os.makedirs(output_dir, exist_ok=True)

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("案例4:抗震安全评估与裕度分析")
print("=" * 60)

# ==================== 1. 结构模型 ====================
print("\n【1. 结构模型】")

# 核电站结构参数
n_stories = 8
story_height = 5.0  # m
H_total = n_stories * story_height

# 质量和刚度矩阵
mass_total = 48261.6e3
mass_per_story = mass_total / n_stories

M_structure = np.eye(n_stories) * mass_per_story

K_structure = np.zeros((n_stories, n_stories))
k_story = 1.5e10

for i in range(n_stories):
    K_structure[i, i] = k_story
    if i > 0:
        K_structure[i, i] += k_story
        K_structure[i, i-1] = -k_story
        K_structure[i-1, i] = -k_story

# 材料参数
fck = 40e6  # 混凝土抗压强度
fy = 400e6  # 钢筋屈服强度

print(f"结构层数: {n_stories}")
print(f"结构高度: {H_total} m")
print(f"总质量: {mass_total/1000:.1f} 吨")
print(f"混凝土强度: {fck/1e6:.0f} MPa")

# ==================== 2. 抗震能力评估 ====================
print("\n" + "=" * 60)
print("2. 抗震能力评估")
print("=" * 60)

class SeismicCapacity:
    """
    抗震能力类
    """
    def __init__(self, structure_type='containment'):
        self.structure_type = structure_type
        self.setup_capacity_limits()
    
    def setup_capacity_limits(self):
        """
        设置能力限值
        """
        # 变形限值
        self.drift_limit_obe = 0.005  # OBE层间位移角限值
        self.drift_limit_sse = 0.015  # SSE层间位移角限值
        
        # 应力限值 (基于材料强度)
        self.stress_limit_concrete = 0.6 * fck  # 混凝土压应力限值
        self.stress_limit_steel = 0.9 * fy  # 钢筋拉应力限值
        
        # 承载力限值
        self.shear_capacity = 800e3  # kN
        self.moment_capacity = 2000e3  # kN·m
        
        # 加速度限值
        self.acc_limit_equipment = 3.0 * 9.81  # 设备加速度限值
    
    def check_drift(self, drift, earthquake_level):
        """
        检查层间位移角
        """
        if earthquake_level == 'OBE':
            limit = self.drift_limit_obe
        elif earthquake_level == 'SSE':
            limit = self.drift_limit_sse
        else:
            limit = self.drift_limit_sse * 1.5  # 裕度地震
        
        ratio = drift / limit
        passed = drift < limit
        margin = (limit - drift) / limit
        
        return {'passed': passed, 'ratio': ratio, 'margin': margin, 'limit': limit}
    
    def check_stress(self, stress, material='concrete'):
        """
        检查应力
        """
        if material == 'concrete':
            limit = self.stress_limit_concrete
        else:
            limit = self.stress_limit_steel
        
        ratio = stress / limit
        passed = stress < limit
        margin = (limit - stress) / limit
        
        return {'passed': passed, 'ratio': ratio, 'margin': margin, 'limit': limit}
    
    def check_shear(self, shear):
        """
        检查剪力
        """
        ratio = shear / self.shear_capacity
        passed = shear < self.shear_capacity
        margin = (self.shear_capacity - shear) / self.shear_capacity
        
        return {'passed': passed, 'ratio': ratio, 'margin': margin, 'limit': self.shear_capacity}

capacity = SeismicCapacity()

# ==================== 3. 地震需求计算 ====================
print("\n" + "=" * 60)
print("3. 地震需求计算")
print("=" * 60)

def calculate_seismic_demand(PGA, structure_period, damping=0.05):
    """
    计算地震需求
    
    Parameters:
    -----------
    PGA : float
        峰值地面加速度 (g)
    structure_period : float
        结构周期 (s)
    damping : float
        阻尼比
    
    Returns:
    --------
    demand : dict
        地震需求
    """
    # 设计谱放大系数
    if structure_period < 0.1:
        S = 2.5
    elif structure_period < 0.5:
        S = 2.5
    else:
        S = 2.5 * (0.5 / structure_period)**0.6
    
    # 谱加速度
    Sa = PGA * S * 9.81  # m/s^2
    
    # 基底剪力
    base_shear = mass_total * Sa * 0.85  # 考虑高阶模态折减
    
    # 顶点位移 (简化计算)
    top_displacement = Sa / (2 * np.pi / structure_period)**2 * 2.5  # 考虑变形放大
    
    # 层间位移
    story_drifts = np.ones(n_stories) * top_displacement / H_total * 1.2
    
    # 层剪力分布
    story_shears = np.zeros(n_stories)
    for i in range(n_stories):
        height_ratio = (i + 1) / n_stories
        story_shears[i] = base_shear * height_ratio**1.5
    
    return {
        'PGA': PGA,
        'Sa': Sa,
        'base_shear': base_shear,
        'top_displacement': top_displacement,
        'story_drifts': story_drifts,
        'story_shears': story_shears
    }

# 结构周期
eigenvalues, eigenvectors = eigh(K_structure, M_structure)
T1 = 2 * np.pi / np.sqrt(eigenvalues[0])

print(f"结构基本周期: {T1:.3f} s")

# 计算不同地震水平的需求
earthquake_levels = {
    'OBE': 0.1,
    'SSE': 0.3,
    'MCE': 0.5,  # 最大考虑地震
    'ULE': 0.6   # 极限地震
}

demands = {}
for level, PGA in earthquake_levels.items():
    demands[level] = calculate_seismic_demand(PGA, T1)
    print(f"\n{level} (PGA={PGA}g):")
    print(f"  谱加速度: {demands[level]['Sa']:.3f} m/s²")
    print(f"  基底剪力: {demands[level]['base_shear']/1000:.0f} kN")
    print(f"  顶点位移: {demands[level]['top_displacement']*1000:.1f} mm")

# ==================== 4. 抗震能力校核 ====================
print("\n" + "=" * 60)
print("4. 抗震能力校核")
print("=" * 60)

def perform_seismic_check(demand, capacity, level):
    """
    执行抗震校核
    """
    results = {}
    
    # 层间位移角校核
    max_drift = np.max(demand['story_drifts'])
    drift_check = capacity.check_drift(max_drift, level)
    results['drift'] = drift_check
    
    # 剪力校核
    max_shear = np.max(demand['story_shears'])
    shear_check = capacity.check_shear(max_shear)
    results['shear'] = shear_check
    
    # 综合评估
    all_passed = all([r['passed'] for r in results.values()])
    min_margin = min([r['margin'] for r in results.values()])
    
    results['overall'] = {
        'passed': all_passed,
        'min_margin': min_margin,
        'governing_factor': min_margin
    }
    
    return results

check_results = {}
for level in earthquake_levels.keys():
    check_results[level] = perform_seismic_check(demands[level], capacity, level)
    
    print(f"\n{level} 校核结果:")
    print(f"  层间位移角: {check_results[level]['drift']['ratio']:.3f} (裕度: {check_results[level]['drift']['margin']*100:.1f}%)")
    print(f"  剪力: {check_results[level]['shear']['ratio']:.3f} (裕度: {check_results[level]['shear']['margin']*100:.1f}%)")
    
    if check_results[level]['overall']['passed']:
        print(f"  ✓ 校核通过 (最小裕度: {check_results[level]['overall']['min_margin']*100:.1f}%)")
    else:
        print(f"  ✗ 校核未通过")

# ==================== 5. 地震裕度评估 ====================
print("\n" + "=" * 60)
print("5. 地震裕度评估 (SMA)")
print("=" * 60)

def calculate_seismic_margin(demands, capacity, level='SSE'):
    """
    计算地震裕度
    
    Parameters:
    -----------
    demands : dict
        各地震水平需求
    capacity : SeismicCapacity
        抗震能力对象
    level : str
        基准地震水平
    
    Returns:
    --------
    margin : dict
        裕度评估结果
    """
    # HCLPF (High Confidence of Low Probability of Failure)
    # 基于 fragility 分析
    
    # 简化计算:找出刚好满足校核的地震水平
    passed_levels = []
    for lvl, result in check_results.items():
        if result['overall']['passed']:
            passed_levels.append((lvl, earthquake_levels[lvl]))
    
    if not passed_levels:
        HCLPF_PGA = 0
        margin_factor = 0
    else:
        # 最大通过的PGA
        max_passed_PGA = max([pga for _, pga in passed_levels])
        HCLPF_PGA = max_passed_PGA
        margin_factor = HCLPF_PGA / earthquake_levels[level]
    
    return {
        'HCLPF_PGA': HCLPF_PGA,
        'margin_factor': margin_factor,
        'reference_level': level,
        'reference_PGA': earthquake_levels[level]
    }

margin_results = calculate_seismic_margin(demands, capacity, 'SSE')

print(f"\n地震裕度评估结果:")
print(f"  基准地震: SSE (PGA={margin_results['reference_PGA']}g)")
print(f"  HCLPF PGA: {margin_results['HCLPF_PGA']:.2f}g")
print(f"  裕度系数: {margin_results['margin_factor']:.2f}")

if margin_results['margin_factor'] >= 1.67:
    print(f"  ✓ 裕度满足要求 (≥1.67)")
else:
    print(f"  ✗ 裕度不足 (<1.67)")

# ==================== 6. 易损性分析 ====================
print("\n" + "=" * 60)
print("6. 易损性分析")
print("=" * 60)

def fragility_analysis(PGA_range, median_capacity, beta):
    """
    易损性分析
    
    Parameters:
    -----------
    PGA_range : array
        PGA范围
    median_capacity : float
        中值能力
    beta : float
        对数标准差
    
    Returns:
    --------
    fragility : array
        失效概率
    """
    # 对数正态分布
    fragility = 0.5 * (1 + np.sign(np.log(PGA_range / median_capacity)) * 
                       np.sqrt(1 - np.exp(-2/np.pi * (np.log(PGA_range / median_capacity) / beta)**2)))
    
    # 使用更准确的近似
    from scipy.stats import norm
    fragility = norm.cdf(np.log(PGA_range / median_capacity) / beta)
    
    return fragility

# 易损性参数
median_capacity = 0.45  # 中值能力
total_beta = 0.4  # 总不确定性
epistemic_beta = 0.25  # 认知不确定性
aleatory_beta = 0.3  # 偶然不确定性

PGA_range = np.linspace(0.05, 1.0, 100)
fragility = fragility_analysis(PGA_range, median_capacity, total_beta)

# 置信区间
fragility_95 = fragility_analysis(PGA_range, median_capacity * np.exp(-1.645 * epistemic_beta), aleatory_beta)
fragility_5 = fragility_analysis(PGA_range, median_capacity * np.exp(1.645 * epistemic_beta), aleatory_beta)

print(f"\n易损性参数:")
print(f"  中值能力: {median_capacity:.2f}g")
print(f"  总不确定性 β: {total_beta:.2f}")
print(f"  HCLPF (1%失效概率): {median_capacity * np.exp(-2.326 * total_beta):.3f}g")

# 绘制易损性曲线
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(PGA_range, fragility * 100, 'b-', linewidth=2, label='Median Fragility')
ax.fill_between(PGA_range, fragility_5 * 100, fragility_95 * 100, alpha=0.3, color='blue', label='90% Confidence')

# 标记设计地震水平
for level, PGA in earthquake_levels.items():
    idx = np.argmin(np.abs(PGA_range - PGA))
    pf = fragility[idx] * 100
    ax.axvline(x=PGA, color='r', linestyle='--', alpha=0.5)
    ax.text(PGA, pf + 5, f'{level}\n({PGA}g)', ha='center', fontsize=9)

ax.set_xlabel('Peak Ground Acceleration (g)', fontsize=11)
ax.set_ylabel('Probability of Failure (%)', fontsize=11)
ax.set_title('Seismic Fragility Curve', fontsize=12, fontweight='bold')
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 1.0)
ax.set_ylim(0, 100)

plt.tight_layout()
plt.savefig(f'{output_dir}/case4_fragility_curve.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 易损性曲线图已保存")

# ==================== 7. 裕度 walkdown ====================
print("\n" + "=" * 60)
print("7. 裕度 Walkdown 评估")
print("=" * 60)

def margin_walkdown_assessment():
    """
    裕度 walkdown 评估
    基于 EPRI NP-6041 方法
    """
    assessment_items = {
        'structural_integrity': {
            'description': '结构完整性',
            'screening': True,
            'margin': 0.25
        },
        'equipment_functionality': {
            'description': '设备功能',
            'screening': True,
            'margin': 0.30
        },
        'piping_systems': {
            'description': '管道系统',
            'screening': True,
            'margin': 0.20
        },
        'electrical_systems': {
            'description': '电气系统',
            'screening': True,
            'margin': 0.35
        },
        'anchorage': {
            'description': '锚固系统',
            'screening': True,
            'margin': 0.15
        },
        'interaction': {
            'description': '地震相互作用',
            'screening': True,
            'margin': 0.40
        }
    }
    
    # 计算最小裕度
    min_margin = min([item['margin'] for item in assessment_items.values()])
    
    # 确定HCLPF
    HCLPF = earthquake_levels['SSE'] * (1 + min_margin)
    
    return assessment_items, HCLPF

walkdown_items, walkdown_HCLPF = margin_walkdown_assessment()

print("\n裕度 Walkdown 评估结果:")
print("-" * 60)
for item, result in walkdown_items.items():
    status = "✓" if result['screening'] else "✗"
    print(f"{status} {result['description']}: 裕度 {result['margin']*100:.0f}%")

print(f"\nWalkdown HCLPF: {walkdown_HCLPF:.2f}g")

# ==================== 8. 综合安全评估 ====================
print("\n" + "=" * 60)
print("8. 综合安全评估")
print("=" * 60)

def comprehensive_safety_assessment():
    """
    综合安全评估
    """
    # 整合所有评估结果
    assessment = {
        'design_compliance': {
            'OBE': check_results['OBE']['overall']['passed'],
            'SSE': check_results['SSE']['overall']['passed']
        },
        'margin_assessment': {
            'HCLPF_method': margin_results['HCLPF_PGA'],
            'walkdown_method': walkdown_HCLPF,
            'conservative_HCLPF': min(margin_results['HCLPF_PGA'], walkdown_HCLPF)
        },
        'fragility_based': {
            'median_capacity': median_capacity,
            'HCLPF_1pct': median_capacity * np.exp(-2.326 * total_beta)
        }
    }
    
    # 最终HCLPF (保守值)
    final_HCLPF = min([
        assessment['margin_assessment']['conservative_HCLPF'],
        assessment['fragility_based']['HCLPF_1pct']
    ])
    
    assessment['final_HCLPF'] = final_HCLPF
    assessment['margin_factor'] = final_HCLPF / earthquake_levels['SSE']
    
    return assessment

final_assessment = comprehensive_safety_assessment()

print("\n综合安全评估结果:")
print("-" * 60)
print(f"设计合规性:")
print(f"  OBE: {'✓ 通过' if final_assessment['design_compliance']['OBE'] else '✗ 未通过'}")
print(f"  SSE: {'✓ 通过' if final_assessment['design_compliance']['SSE'] else '✗ 未通过'}")

print(f"\nHCLPF评估:")
print(f"  分析方法: {final_assessment['margin_assessment']['HCLPF_method']:.3f}g")
print(f"  Walkdown方法: {final_assessment['margin_assessment']['walkdown_method']:.3f}g")
print(f"  易损性方法: {final_assessment['fragility_based']['HCLPF_1pct']:.3f}g")

print(f"\n最终评估:")
print(f"  HCLPF PGA: {final_assessment['final_HCLPF']:.3f}g")
print(f"  裕度系数: {final_assessment['margin_factor']:.2f}")

if final_assessment['margin_factor'] >= 1.67:
    print(f"  安全等级: ✓ 满足核安全要求")
else:
    print(f"  安全等级: ✗ 需要改进")

# ==================== 9. 结果可视化 ====================
print("\n" + "=" * 60)
print("9. 结果可视化")
print("=" * 60)

# 创建综合评估图
fig = plt.figure(figsize=(16, 10))

# 1. 各层响应
ax1 = plt.subplot(2, 3, 1)
stories = np.arange(1, n_stories + 1)
for level in ['OBE', 'SSE']:
    ax1.plot(demands[level]['story_drifts'] * 1000, stories, 'o-', label=level, linewidth=2)
ax1.axvline(x=capacity.drift_limit_obe * 1000, color='g', linestyle='--', label='OBE Limit')
ax1.axvline(x=capacity.drift_limit_sse * 1000, color='r', linestyle='--', label='SSE Limit')
ax1.set_xlabel('Interstory Drift (mm)', fontsize=10)
ax1.set_ylabel('Story', fontsize=10)
ax1.set_title('Interstory Drift Distribution', fontsize=11, fontweight='bold')
ax1.legend(fontsize=9)
ax1.grid(True, alpha=0.3)
ax1.invert_yaxis()

# 2. 层剪力
ax2 = plt.subplot(2, 3, 2)
for level in ['OBE', 'SSE']:
    ax2.plot(demands[level]['story_shears'] / 1000, stories, 's-', label=level, linewidth=2)
ax2.axvline(x=capacity.shear_capacity / 1000, color='r', linestyle='--', label='Capacity')
ax2.set_xlabel('Story Shear (kN)', fontsize=10)
ax2.set_ylabel('Story', fontsize=10)
ax2.set_title('Story Shear Distribution', fontsize=11, fontweight='bold')
ax2.legend(fontsize=9)
ax2.grid(True, alpha=0.3)
ax2.invert_yaxis()

# 3. 裕度系数
ax3 = plt.subplot(2, 3, 3)
levels = list(earthquake_levels.keys())
margins = []
for level in levels:
    if level in check_results:
        margins.append(check_results[level]['overall']['min_margin'] * 100)
    else:
        margins.append(0)

colors = ['green' if m > 0 else 'red' for m in margins]
bars = ax3.bar(levels, margins, color=colors, alpha=0.7, edgecolor='black')
ax3.axhline(y=0, color='k', linestyle='-', linewidth=1)
ax3.set_ylabel('Margin (%)', fontsize=10)
ax3.set_title('Seismic Margin by Level', fontsize=11, fontweight='bold')
ax3.grid(True, alpha=0.3, axis='y')

for bar, margin in zip(bars, margins):
    height = bar.get_height()
    ax3.text(bar.get_x() + bar.get_width()/2., height + 2 if height > 0 else height - 5,
             f'{margin:.0f}%', ha='center', va='bottom' if height > 0 else 'top', fontsize=9)

# 4. HCLPF比较
ax4 = plt.subplot(2, 3, 4)
methods = ['Analysis', 'Walkdown', 'Fragility', 'Final']
hclpf_values = [
    final_assessment['margin_assessment']['HCLPF_method'],
    final_assessment['margin_assessment']['walkdown_method'],
    final_assessment['fragility_based']['HCLPF_1pct'],
    final_assessment['final_HCLPF']
]
colors = plt.cm.Blues(np.linspace(0.4, 0.8, len(methods)))
bars = ax4.bar(methods, hclpf_values, color=colors, alpha=0.8, edgecolor='black')
ax4.axhline(y=earthquake_levels['SSE'], color='r', linestyle='--', linewidth=2, label='SSE')
ax4.axhline(y=earthquake_levels['SSE'] * 1.67, color='g', linestyle='--', linewidth=2, label='Target (1.67×SSE)')
ax4.set_ylabel('HCLPF PGA (g)', fontsize=10)
ax4.set_title('HCLPF Comparison', fontsize=11, fontweight='bold')
ax4.legend(fontsize=9)
ax4.grid(True, alpha=0.3, axis='y')

for bar, val in zip(bars, hclpf_values):
    height = bar.get_height()
    ax4.text(bar.get_x() + bar.get_width()/2., height + 0.01,
             f'{val:.2f}', ha='center', va='bottom', fontsize=9)

# 5. 能力-需求比
ax5 = plt.subplot(2, 3, 5)
categories = ['Drift\n(OBE)', 'Drift\n(SSE)', 'Shear\n(OBE)', 'Shear\n(SSE)']
ratios = [
    check_results['OBE']['drift']['ratio'],
    check_results['SSE']['drift']['ratio'],
    check_results['OBE']['shear']['ratio'],
    check_results['SSE']['shear']['ratio']
]
colors = ['green' if r < 1 else 'red' for r in ratios]
bars = ax5.bar(categories, ratios, color=colors, alpha=0.7, edgecolor='black')
ax5.axhline(y=1.0, color='r', linestyle='--', linewidth=2, label='Limit')
ax5.set_ylabel('Capacity/Demand Ratio', fontsize=10)
ax5.set_title('Capacity/Demand Ratios', fontsize=11, fontweight='bold')
ax5.legend(fontsize=9)
ax5.grid(True, alpha=0.3, axis='y')

for bar, ratio in zip(bars, ratios):
    height = bar.get_height()
    ax5.text(bar.get_x() + bar.get_width()/2., height + 0.02,
             f'{ratio:.2f}', ha='center', va='bottom', fontsize=9)

# 6. 安全等级
ax6 = plt.subplot(2, 3, 6)
ax6.axis('off')

safety_text = f"""
综合安全评估结果

设计基准地震:
  OBE: 0.1g
  SSE: 0.3g

HCLPF PGA: {final_assessment['final_HCLPF']:.3f}g

裕度系数: {final_assessment['margin_factor']:.2f}

要求: ≥ 1.67

状态: {'✓ 满足' if final_assessment['margin_factor'] >= 1.67 else '✗ 不满足'}

安全等级: {'A' if final_assessment['margin_factor'] >= 2.0 else 'B' if final_assessment['margin_factor'] >= 1.67 else 'C'}
"""

ax6.text(0.1, 0.5, safety_text, fontsize=11, family='monospace',
         verticalalignment='center', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

plt.suptitle('Seismic Safety Assessment Summary', fontsize=14, fontweight='bold', y=0.98)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.savefig(f'{output_dir}/case4_safety_assessment.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 安全评估综合图已保存")

# ==================== 仿真总结 ====================
print("\n" + "=" * 60)
print("案例4仿真完成总结")
print("=" * 60)
print("\n本案例实现了以下抗震安全评估内容:")
print("1. ✓ 抗震能力评估")
print("2. ✓ 地震需求计算")
print("3. ✓ 抗震能力校核")
print("4. ✓ 地震裕度评估 (SMA)")
print("5. ✓ 易损性分析")
print("6. ✓ 裕度 Walkdown 评估")
print("7. ✓ 综合安全评估")

print("\n关键结果:")
print(f"  结构基本周期: {T1:.3f} s")
print(f"  SSE基底剪力: {demands['SSE']['base_shear']/1000:.0f} kN")
print(f"  HCLPF PGA: {final_assessment['final_HCLPF']:.3f}g")
print(f"  裕度系数: {final_assessment['margin_factor']:.2f}")
print(f"  安全等级: {'A' if final_assessment['margin_factor'] >= 2.0 else 'B' if final_assessment['margin_factor'] >= 1.67 else 'C'}")

print("\n生成的文件:")
print(f"  - {output_dir}/case4_fragility_curve.png")
print(f"  - {output_dir}/case4_safety_assessment.png")
print("\n" + "=" * 60)


# ============================================
# 生成GIF动画
# ============================================

def create_animation():
    """创建仿真结果动画"""
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    import numpy as np
    
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.set_xlim(0, 10)
    ax.set_ylim(-2, 2)
    ax.set_xlabel('Time', fontsize=12)
    ax.set_ylabel('Response', fontsize=12)
    ax.set_title('Dynamic Response Animation', fontsize=14, fontweight='bold')
    ax.grid(True, alpha=0.3)
    
    line, = ax.plot([], [], 'b-', linewidth=2)
    
    def init():
        line.set_data([], [])
        return line,
    
    def update(frame):
        x = np.linspace(0, 10, 100)
        y = np.sin(x - frame * 0.2) * np.exp(-frame * 0.01)
        line.set_data(x, y)
        return line,
    
    anim = FuncAnimation(fig, update, init_func=init, frames=50, interval=100, blit=True)
    
    output_dir = os.path.dirname(os.path.abspath(__file__))
    anim.save(f'{output_dir}/simulation_animation.gif', writer='pillow', fps=10)
    print(f"动画已保存到: {output_dir}/simulation_animation.gif")
    plt.close()

if __name__ == '__main__':
    create_animation()



Logo

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

更多推荐