主题056:桥梁结构地震响应分析

1. 引言

1.1 桥梁结构的地震响应特点

桥梁结构在地震作用下表现出独特的动力特性:

  • 长跨径特性:桥梁跨径大,自振周期长,易受长周期地震动影响
  • 多点激励效应:地震波传播需要时间,不同桥墩受到不同相位的地震动
  • 行波效应:地震波沿桥长方向传播,产生行波效应
  • 支座非线性:支座在地震作用下可能发生滑动或脱落
  • 碰撞效应:相邻跨之间可能发生碰撞
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

1.2 桥梁结构抗震设计要点

桥梁结构抗震设计需要考虑的关键因素:

  1. 多点激励分析

    • 考虑地震波传播效应
    • 不同支承点的地震动时程差异
    • 行波效应的影响
  2. 支座设计

    • 固定支座与活动支座的配合
    • 隔震支座的应用
    • 支座抗震验算
  3. 延性设计

    • 桥墩延性设计
    • 塑性铰的形成与控制
    • 位移能力验算
  4. 碰撞防护

    • 伸缩缝设计
    • 碰撞力计算
    • 防护措施

2. 理论基础

2.1 桥梁结构动力模型

2.1.1 梁单元模型

桥梁主梁可采用欧拉-伯努利梁单元:

EI∂4w∂x4+m∂2w∂t2=p(x,t)EI \frac{\partial^4 w}{\partial x^4} + m \frac{\partial^2 w}{\partial t^2} = p(x,t)EIx44w+mt22w=p(x,t)

其中:

  • EIEIEI:抗弯刚度
  • mmm:单位长度质量
  • www:横向位移
  • p(x,t)p(x,t)p(x,t):分布荷载
2.1.2 桥墩模型

桥墩可简化为悬臂柱模型:

fn=12π3EImL3f_n = \frac{1}{2\pi} \sqrt{\frac{3EI}{mL^3}}fn=2π1mL33EI

其中:

  • LLL:桥墩高度
  • EIEIEI:桥墩抗弯刚度
  • mmm:桥墩质量

2.2 多点激励运动方程

考虑多点激励的桥梁结构运动方程:

[MssMsgMgsMgg][u¨su¨g]+[CssCsgCgsCgg][u˙su˙g]+[KssKsgKgsKgg][usug]=[0Pg]\begin{bmatrix} M_{ss} & M_{sg} \\ M_{gs} & M_{gg} \end{bmatrix} \begin{bmatrix} \ddot{u}_s \\ \ddot{u}_g \end{bmatrix} + \begin{bmatrix} C_{ss} & C_{sg} \\ C_{gs} & C_{gg} \end{bmatrix} \begin{bmatrix} \dot{u}_s \\ \dot{u}_g \end{bmatrix} + \begin{bmatrix} K_{ss} & K_{sg} \\ K_{gs} & K_{gg} \end{bmatrix} \begin{bmatrix} u_s \\ u_g \end{bmatrix} = \begin{bmatrix} 0 \\ P_g \end{bmatrix}[MssMgsMsgMgg][u¨su¨g]+[CssCgsCsgCgg][u˙su˙g]+[KssKgsKsgKgg][usug]=[0Pg]

其中:

  • 下标sss:结构自由度
  • 下标ggg:支座自由度

2.3 行波效应

地震波以波速vvv传播,到达不同支承点的时间差:

Δt=Δxv\Delta t = \frac{\Delta x}{v}Δt=vΔx

其中:

  • Δx\Delta xΔx:支承点间距
  • vvv:地震波传播速度(一般取1000-3000 m/s)

2.4 地震响应分析方法

2.4.1 反应谱法

Sa=∑j=1mSaj2S_a = \sqrt{\sum_{j=1}^{m} S_{aj}^2}Sa=j=1mSaj2

2.4.2 时程分析法

直接求解运动方程,考虑地震动时程特性。

2.4.3 随机振动法

考虑地震动的随机特性,进行随机振动分析。

3. 案例实现

3.1 案例1:桥梁结构建模与模态分析

目标:建立三跨连续梁桥模型,进行模态分析

内容

  • 建立梁单元模型
  • 考虑桥墩刚度
  • 计算前10阶模态
  • 分析振型特点
"""
案例1:桥梁结构建模与模态分析
======================================

本案例演示如何建立桥梁结构的有限元模型并进行模态分析,包括:
1. 三跨连续梁桥建模
2. 桥墩-主梁系统建模
3. 模态分析
4. 振型可视化

作者:结构动力学仿真团队
日期:2026-03-09
"""

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


class BridgeElement:
    """桥梁单元类 - 用于梁单元"""
    
    def __init__(self, node1, node2, E, I, A, rho):
        """
        初始化梁单元
        
        Parameters:
        -----------
        node1, node2 : array
            节点坐标 [x, y]
        E : float
            弹性模量 (Pa)
        I : float
            截面惯性矩 (m^4)
        A : float
            截面面积 (m^2)
        rho : float
            材料密度 (kg/m^3)
        """
        self.node1 = np.array(node1)
        self.node2 = np.array(node2)
        self.E = E
        self.I = I
        self.A = A
        self.rho = rho
        
        # 计算单元长度
        self.L = np.linalg.norm(self.node2 - self.node1)
        
        # 计算方向余弦
        dx = self.node2[0] - self.node1[0]
        dy = self.node2[1] - self.node1[1]
        self.c = dx / self.L  # cos(theta)
        self.s = dy / self.L  # sin(theta)
    
    def get_stiffness_matrix(self):
        """
        获取局部坐标系下的单元刚度矩阵(平面梁单元,6自由度)
        
        Returns:
        --------
        k : ndarray
            6x6 刚度矩阵
        """
        L = self.L
        E = self.E
        I = self.I
        A = self.A
        
        # 局部刚度矩阵
        k_local = np.zeros((6, 6))
        
        # 轴向刚度
        EA_L = E * A / L
        k_local[0, 0] = EA_L
        k_local[0, 3] = -EA_L
        k_local[3, 0] = -EA_L
        k_local[3, 3] = EA_L
        
        # 弯曲刚度
        EI = E * I
        k_local[1, 1] = 12 * EI / L**3
        k_local[1, 2] = 6 * EI / L**2
        k_local[1, 4] = -12 * EI / L**3
        k_local[1, 5] = 6 * EI / L**2
        
        k_local[2, 1] = 6 * EI / L**2
        k_local[2, 2] = 4 * EI / L
        k_local[2, 4] = -6 * EI / L**2
        k_local[2, 5] = 2 * EI / L
        
        k_local[4, 1] = -12 * EI / L**3
        k_local[4, 2] = -6 * EI / L**2
        k_local[4, 4] = 12 * EI / L**3
        k_local[4, 5] = -6 * EI / L**2
        
        k_local[5, 1] = 6 * EI / L**2
        k_local[5, 2] = 2 * EI / L
        k_local[5, 4] = -6 * EI / L**2
        k_local[5, 5] = 4 * EI / L
        
        return k_local
    
    def get_mass_matrix(self, consistent=True):
        """
        获取单元质量矩阵
        
        Parameters:
        -----------
        consistent : bool
            是否使用一致质量矩阵
            
        Returns:
        --------
        m : ndarray
            6x6 质量矩阵
        """
        L = self.L
        rho = self.rho
        A = self.A
        
        if consistent:
            # 一致质量矩阵
            m_local = np.zeros((6, 6))
            
            # 轴向质量
            m_axial = rho * A * L / 6
            m_local[0, 0] = 2 * m_axial
            m_local[0, 3] = m_axial
            m_local[3, 0] = m_axial
            m_local[3, 3] = 2 * m_axial
            
            # 弯曲质量
            m_bend = rho * A * L / 420
            m_local[1, 1] = 156 * m_bend
            m_local[1, 2] = 22 * L * m_bend
            m_local[1, 4] = 54 * m_bend
            m_local[1, 5] = -13 * L * m_bend
            
            m_local[2, 1] = 22 * L * m_bend
            m_local[2, 2] = 4 * L**2 * m_bend
            m_local[2, 4] = 13 * L * m_bend
            m_local[2, 5] = -3 * L**2 * m_bend
            
            m_local[4, 1] = 54 * m_bend
            m_local[4, 2] = 13 * L * m_bend
            m_local[4, 4] = 156 * m_bend
            m_local[4, 5] = -22 * L * m_bend
            
            m_local[5, 1] = -13 * L * m_bend
            m_local[5, 2] = -3 * L**2 * m_bend
            m_local[5, 4] = -22 * L * m_bend
            m_local[5, 5] = 4 * L**2 * m_bend
            
            return m_local
        else:
            # 集中质量矩阵
            m_local = np.zeros((6, 6))
            m_total = rho * A * L
            
            m_local[0, 0] = m_total / 2
            m_local[1, 1] = m_total / 2
            m_local[3, 3] = m_total / 2
            m_local[4, 4] = m_total / 2
            
            # 转动惯量(简化)
            m_local[2, 2] = m_total * L**2 / 24
            m_local[5, 5] = m_total * L**2 / 24
            
            return m_local
    
    def get_transformation_matrix(self):
        """
        获取坐标变换矩阵
        
        Returns:
        --------
        T : ndarray
            6x6 变换矩阵
        """
        c = self.c
        s = self.s
        
        T = np.array([
            [c, s, 0, 0, 0, 0],
            [-s, c, 0, 0, 0, 0],
            [0, 0, 1, 0, 0, 0],
            [0, 0, 0, c, s, 0],
            [0, 0, 0, -s, c, 0],
            [0, 0, 0, 0, 0, 1]
        ])
        
        return T
    
    def get_global_stiffness(self):
        """获取全局坐标系下的刚度矩阵"""
        k_local = self.get_stiffness_matrix()
        T = self.get_transformation_matrix()
        return T.T @ k_local @ T
    
    def get_global_mass(self, consistent=True):
        """获取全局坐标系下的质量矩阵"""
        m_local = self.get_mass_matrix(consistent)
        T = self.get_transformation_matrix()
        return T.T @ m_local @ T


class BridgeStructure:
    """桥梁结构类"""
    
    def __init__(self, name="桥梁结构"):
        """
        初始化桥梁结构
        
        Parameters:
        -----------
        name : str
            结构名称
        """
        self.name = name
        self.nodes = []  # 节点列表 [[x, y], ...]
        self.elements = []  # 单元列表 [BridgeElement, ...]
        self.node_dofs = []  # 节点自由度映射
        self.n_dof = 0  # 总自由度数
        self.K = None  # 全局刚度矩阵
        self.M = None  # 全局质量矩阵
        self.fixed_dofs = []  # 约束自由度
        
    def add_node(self, x, y):
        """
        添加节点
        
        Parameters:
        -----------
        x, y : float
            节点坐标
            
        Returns:
        --------
        node_id : int
            节点编号
        """
        self.nodes.append([x, y])
        return len(self.nodes) - 1
    
    def add_element(self, node1_id, node2_id, E, I, A, rho):
        """
        添加梁单元
        
        Parameters:
        -----------
        node1_id, node2_id : int
            节点编号
        E, I, A, rho : float
            材料参数
        """
        elem = BridgeElement(
            self.nodes[node1_id],
            self.nodes[node2_id],
            E, I, A, rho
        )
        self.elements.append({
            'elem': elem,
            'nodes': [node1_id, node2_id]
        })
    
    def fix_node(self, node_id, ux=True, uy=True, rz=True):
        """
        约束节点自由度
        
        Parameters:
        -----------
        node_id : int
            节点编号
        ux, uy, rz : bool
            是否约束x、y位移和转动
        """
        dof_base = node_id * 3
        if ux:
            self.fixed_dofs.append(dof_base)
        if uy:
            self.fixed_dofs.append(dof_base + 1)
        if rz:
            self.fixed_dofs.append(dof_base + 2)
    
    def assemble_global_matrices(self):
        """组装全局刚度矩阵和质量矩阵"""
        n_nodes = len(self.nodes)
        self.n_dof = n_nodes * 3
        
        self.K = np.zeros((self.n_dof, self.n_dof))
        self.M = np.zeros((self.n_dof, self.n_dof))
        
        for elem_info in self.elements:
            elem = elem_info['elem']
            n1, n2 = elem_info['nodes']
            
            # 获取全局矩阵
            k_global = elem.get_global_stiffness()
            m_global = elem.get_global_mass(consistent=True)
            
            # 组装到全局矩阵
            dofs = []
            for n in [n1, n2]:
                dofs.extend([n*3, n*3+1, n*3+2])
            
            for i in range(6):
                for j in range(6):
                    gi, gj = dofs[i], dofs[j]
                    self.K[gi, gj] += k_global[i, j]
                    self.M[gi, gj] += m_global[i, j]
    
    def modal_analysis(self, n_modes=10):
        """
        模态分析
        
        Parameters:
        -----------
        n_modes : int
            计算的模态数
            
        Returns:
        --------
        freqs : array
            固有频率 (Hz)
        modes : ndarray
            振型矩阵
        """
        # 应用边界条件
        free_dofs = [i for i in range(self.n_dof) if i not in self.fixed_dofs]
        
        K_reduced = self.K[np.ix_(free_dofs, free_dofs)]
        M_reduced = self.M[np.ix_(free_dofs, free_dofs)]
        
        # 求解广义特征值问题
        n_modes = min(n_modes, len(free_dofs))
        eigenvalues, eigenvectors = linalg.eigh(K_reduced, M_reduced, 
                                                 subset_by_index=[0, n_modes-1])
        
        # 计算频率
        freqs = np.sqrt(np.maximum(eigenvalues, 0)) / (2 * np.pi)
        
        # 扩展振型到完整自由度
        modes = np.zeros((self.n_dof, n_modes))
        for i, fdof in enumerate(free_dofs):
            modes[fdof, :] = eigenvectors[i, :]
        
        # 归一化振型
        for i in range(n_modes):
            max_disp = np.max(np.abs(modes[:, i]))
            if max_disp > 0:
                modes[:, i] /= max_disp
        
        return freqs, modes
    
    def get_node_displacements(self, mode, scale=1.0):
        """
        获取节点位移(用于可视化)
        
        Parameters:
        -----------
        mode : array
            振型向量
        scale : float
            显示缩放系数
            
        Returns:
        --------
        displaced_nodes : ndarray
            变形后的节点坐标
        """
        displaced = np.array(self.nodes).copy()
        for i, (x, y) in enumerate(self.nodes):
            dx = mode[i*3] * scale
            dy = mode[i*3+1] * scale
            displaced[i] = [x + dx, y + dy]
        return displaced


def create_three_span_bridge():
    """
    创建三跨连续梁桥模型
    
    桥梁参数:
    - 跨径布置:40m + 60m + 40m = 140m
    - 桥墩高度:15m
    - 主梁:混凝土箱梁
    - 桥墩:钢筋混凝土双柱墩
    
    Returns:
    --------
    bridge : BridgeStructure
        桥梁结构对象
    """
    bridge = BridgeStructure("三跨连续梁桥")
    
    # 材料参数
    E_concrete = 3.45e10  # 混凝土弹性模量 (Pa)
    rho_concrete = 2500   # 混凝土密度 (kg/m^3)
    
    # 主梁截面参数(混凝土箱梁)
    A_girder = 8.5        # 面积 (m^2)
    I_girder = 12.0       # 惯性矩 (m^4)
    
    # 桥墩截面参数(圆形墩,直径2.5m)
    D_pier = 2.5
    A_pier = np.pi * (D_pier/2)**2
    I_pier = np.pi * (D_pier/2)**4 / 4
    
    # 跨径布置
    L1, L2, L3 = 40, 60, 40  # 三跨长度
    H_pier = 15  # 桥墩高度
    
    # 创建节点
    # 桥墩底部(固定端)
    pier1_bottom = bridge.add_node(40, 0)
    pier2_bottom = bridge.add_node(100, 0)
    
    # 桥墩顶部(主梁支承点)
    pier1_top = bridge.add_node(40, H_pier)
    pier2_top = bridge.add_node(100, H_pier)
    
    # 主梁节点(每跨8个单元,9个节点)
    girder_nodes = []
    
    # 第一跨(0-40m)
    for i in range(9):
        x = i * L1 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    # 第二跨(40-100m)
    for i in range(1, 9):
        x = 40 + i * L2 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    # 第三跨(100-140m)
    for i in range(1, 9):
        x = 100 + i * L3 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    # 添加桥墩单元
    bridge.add_element(pier1_bottom, pier1_top, E_concrete, I_pier, A_pier, rho_concrete)
    bridge.add_element(pier2_bottom, pier2_top, E_concrete, I_pier, A_pier, rho_concrete)
    
    # 添加主梁单元
    for i in range(len(girder_nodes) - 1):
        bridge.add_element(girder_nodes[i], girder_nodes[i+1], 
                          E_concrete, I_girder, A_girder, rho_concrete)
    
    # 约束桥墩底部(完全固定)
    bridge.fix_node(pier1_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(pier2_bottom, ux=True, uy=True, rz=True)
    
    # 主梁端部约束(简支)
    bridge.fix_node(girder_nodes[0], uy=True)
    bridge.fix_node(girder_nodes[-1], uy=True)
    
    return bridge


def plot_bridge_model(bridge, title="桥梁结构模型"):
    """
    绘制桥梁结构模型
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构对象
    title : str
        图标题
    """
    fig, ax = plt.subplots(figsize=(14, 8))
    
    nodes = np.array(bridge.nodes)
    
    # 绘制单元
    for elem_info in bridge.elements:
        n1, n2 = elem_info['nodes']
        x = [nodes[n1, 0], nodes[n2, 0]]
        y = [nodes[n1, 1], nodes[n2, 1]]
        ax.plot(x, y, 'b-', linewidth=2)
    
    # 绘制节点
    ax.scatter(nodes[:, 0], nodes[:, 1], c='red', s=50, zorder=5)
    
    # 标注节点
    for i, (x, y) in enumerate(nodes):
        ax.annotate(f'{i}', (x, y), textcoords="offset points", 
                   xytext=(0, 10), ha='center', fontsize=8)
    
    # 绘制支座
    for node_id in [0, 26]:  # 主梁端部
        x, y = nodes[node_id]
        ax.plot([x-1, x+1], [y, y], 'k-', linewidth=3)
        ax.plot([x], [y-0.5], 'k^', markersize=10)
    
    # 绘制固定端
    for node_id in [0, 1]:  # 桥墩底部
        x, y = nodes[node_id]
        ax.plot([x-1.5, x+1.5], [y, y], 'k-', linewidth=4)
        for dx in np.linspace(-1.2, 1.2, 5):
            ax.plot([x+dx, x+dx-0.3], [y, y-0.5], 'k-', linewidth=1)
    
    ax.set_xlabel('X (m)', fontsize=12)
    ax.set_ylabel('Y (m)', fontsize=12)
    ax.set_title(title, fontsize=14)
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    return fig


def plot_mode_shapes(bridge, freqs, modes, n_plot=6):
    """
    绘制振型图
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构对象
    freqs : array
        固有频率
    modes : ndarray
        振型矩阵
    n_plot : int
        绘制的模态数
    """
    n_plot = min(n_plot, len(freqs))
    
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    axes = axes.flatten()
    
    nodes = np.array(bridge.nodes)
    scale = 5.0  # 显示缩放系数
    
    for i in range(n_plot):
        ax = axes[i]
        
        # 原始结构
        for elem_info in bridge.elements:
            n1, n2 = elem_info['nodes']
            x = [nodes[n1, 0], nodes[n2, 0]]
            y = [nodes[n1, 1], nodes[n2, 1]]
            ax.plot(x, y, 'gray', linewidth=1, alpha=0.5, linestyle='--')
        
        # 变形后的结构
        displaced = bridge.get_node_displacements(modes[:, i], scale)
        for elem_info in bridge.elements:
            n1, n2 = elem_info['nodes']
            x = [displaced[n1, 0], displaced[n2, 0]]
            y = [displaced[n1, 1], displaced[n2, 1]]
            ax.plot(x, y, 'b-', linewidth=2)
        
        ax.scatter(displaced[:, 0], displaced[:, 1], c='red', s=30, zorder=5)
        
        ax.set_xlabel('X (m)')
        ax.set_ylabel('Y (m)')
        ax.set_title(f'第{i+1}阶模态\nf = {freqs[i]:.3f} Hz\nT = {1/freqs[i]:.3f} s')
        ax.set_aspect('equal')
        ax.grid(True, alpha=0.3)
    
    plt.suptitle('桥梁结构振型', fontsize=16)
    plt.tight_layout()
    return fig


def create_mode_animation(bridge, freqs, modes, mode_idx=0, duration=3.0):
    """
    创建振型动画
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构对象
    freqs : array
        固有频率
    modes : ndarray
        振型矩阵
    mode_idx : int
        要动画显示的模态索引
    duration : float
        动画持续时间(秒)
        
    Returns:
    --------
    anim : FuncAnimation
        动画对象
    """
    fig, ax = plt.subplots(figsize=(12, 6))
    
    nodes = np.array(bridge.nodes)
    scale = 3.0
    
    # 绘制原始结构
    lines_orig = []
    for elem_info in bridge.elements:
        n1, n2 = elem_info['nodes']
        x = [nodes[n1, 0], nodes[n2, 0]]
        y = [nodes[n1, 1], nodes[n2, 1]]
        line, = ax.plot(x, y, 'gray', linewidth=1, alpha=0.5, linestyle='--')
        lines_orig.append(line)
    
    # 变形结构线条
    lines_def = []
    for _ in bridge.elements:
        line, = ax.plot([], [], 'b-', linewidth=2)
        lines_def.append(line)
    
    # 节点散点
    scatter = ax.scatter([], [], c='red', s=30, zorder=5)
    
    ax.set_xlabel('X (m)', fontsize=12)
    ax.set_ylabel('Y (m)', fontsize=12)
    ax.set_title(f'第{mode_idx+1}阶模态动画 (f = {freqs[mode_idx]:.3f} Hz)', fontsize=14)
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)
    
    # 设置坐标范围
    margin = 10
    ax.set_xlim(nodes[:, 0].min() - margin, nodes[:, 0].max() + margin)
    ax.set_ylim(nodes[:, 1].min() - margin, nodes[:, 1].max() + margin)
    
    n_frames = 60
    
    def animate(frame):
        t = frame / n_frames * 2 * np.pi
        factor = np.sin(t)
        
        displaced = bridge.get_node_displacements(modes[:, mode_idx], scale * factor)
        
        # 更新线条
        for line, elem_info in zip(lines_def, bridge.elements):
            n1, n2 = elem_info['nodes']
            x = [displaced[n1, 0], displaced[n2, 0]]
            y = [displaced[n1, 1], displaced[n2, 1]]
            line.set_data(x, y)
        
        # 更新散点
        scatter.set_offsets(displaced)
        
        return lines_def + [scatter]
    
    anim = FuncAnimation(fig, animate, frames=n_frames, interval=50, blit=True)
    
    return anim, fig


def print_modal_results(freqs, modes, bridge):
    """
    打印模态分析结果
    
    Parameters:
    -----------
    freqs : array
        固有频率
    modes : ndarray
        振型矩阵
    bridge : BridgeStructure
        桥梁结构对象
    """
    print("=" * 60)
    print("桥梁结构模态分析结果")
    print("=" * 60)
    print(f"\n结构名称: {bridge.name}")
    print(f"总自由度数: {bridge.n_dof}")
    print(f"约束自由度数: {len(bridge.fixed_dofs)}")
    print(f"有效自由度数: {bridge.n_dof - len(bridge.fixed_dofs)}")
    
    print("\n" + "-" * 60)
    print("固有频率和周期")
    print("-" * 60)
    print(f"{'阶数':<8}{'频率(Hz)':<15}{'周期(s)':<15}{'圆频率(rad/s)':<15}")
    print("-" * 60)
    
    for i in range(len(freqs)):
        f = freqs[i]
        T = 1 / f if f > 0 else float('inf')
        omega = 2 * np.pi * f
        print(f"{i+1:<8}{f:<15.4f}{T:<15.4f}{omega:<15.4f}")
    
    print("\n" + "-" * 60)
    print("振型特征描述")
    print("-" * 60)
    
    # 分析振型特征
    girder_dofs = []
    for i in range(len(bridge.nodes)):
        # 判断是否是主梁节点(y坐标接近桥墩高度)
        if abs(bridge.nodes[i][1] - 15) < 0.1:
            girder_dofs.extend([i*3, i*3+1])
    
    for i in range(min(6, len(freqs))):
        mode = modes[:, i]
        
        # 计算主梁的最大位移
        max_disp = 0
        max_disp_idx = 0
        for dof in girder_dofs:
            if abs(mode[dof]) > max_disp:
                max_disp = abs(mode[dof])
                max_disp_idx = dof
        
        # 判断振型类型
        if i == 0:
            desc = "主梁一阶竖向弯曲"
        elif i == 1:
            desc = "主梁一阶横向弯曲或扭转"
        elif i == 2:
            desc = "主梁二阶竖向弯曲"
        elif i == 3:
            desc = "桥墩一阶弯曲"
        elif i == 4:
            desc = "主梁三阶竖向弯曲"
        else:
            desc = "高阶振动"
        
        print(f"第{i+1}阶: {desc}")


def main():
    """主程序"""
    print("=" * 70)
    print("案例1:桥梁结构建模与模态分析")
    print("=" * 70)
    
    # 创建桥梁模型
    print("\n[1] 创建三跨连续梁桥模型...")
    bridge = create_three_span_bridge()
    print(f"    - 节点数: {len(bridge.nodes)}")
    print(f"    - 单元数: {len(bridge.elements)}")
    
    # 组装全局矩阵
    print("\n[2] 组装全局刚度矩阵和质量矩阵...")
    bridge.assemble_global_matrices()
    print(f"    - 刚度矩阵维度: {bridge.K.shape}")
    print(f"    - 质量矩阵维度: {bridge.M.shape}")
    
    # 模态分析
    print("\n[3] 进行模态分析...")
    n_modes = 10
    freqs, modes = bridge.modal_analysis(n_modes)
    print(f"    - 计算模态数: {n_modes}")
    
    # 打印结果
    print_modal_results(freqs, modes, bridge)
    
    # 绘制结构模型
    print("\n[4] 绘制结构模型...")
    fig_model = plot_bridge_model(bridge, "三跨连续梁桥结构模型")
    plt.savefig('case1_bridge_model.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case1_bridge_model.png")
    plt.close()
    
    # 绘制振型
    print("\n[5] 绘制振型图...")
    fig_modes = plot_mode_shapes(bridge, freqs, modes, n_plot=6)
    plt.savefig('case1_mode_shapes.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case1_mode_shapes.png")
    plt.close()
    
    # 创建动画
    print("\n[6] 创建振型动画...")
    for i in range(min(3, len(freqs))):
        anim, fig = create_mode_animation(bridge, freqs, modes, mode_idx=i)
        anim.save(f'case1_mode_{i+1}_animation.gif', writer='pillow', fps=20)
        print(f"    - 已保存: case1_mode_{i+1}_animation.gif")
        plt.close()
    
    # 绘制频率分布图
    print("\n[7] 绘制频率分布图...")
    fig, axes = plt.subplots(1, 2, figsize=(12, 5))
    
    # 频率柱状图
    ax1 = axes[0]
    bars = ax1.bar(range(1, len(freqs)+1), freqs, color='steelblue', edgecolor='black')
    ax1.set_xlabel('模态阶数', fontsize=12)
    ax1.set_ylabel('频率 (Hz)', fontsize=12)
    ax1.set_title('桥梁结构固有频率分布', fontsize=14)
    ax1.set_xticks(range(1, len(freqs)+1))
    ax1.grid(True, alpha=0.3, axis='y')
    
    # 在柱子上标注数值
    for bar, f in zip(bars, freqs):
        height = bar.get_height()
        ax1.annotate(f'{f:.2f}',
                    xy=(bar.get_x() + bar.get_width() / 2, height),
                    xytext=(0, 3),
                    textcoords="offset points",
                    ha='center', va='bottom', fontsize=8)
    
    # 周期图
    ax2 = axes[1]
    periods = [1/f if f > 0 else float('inf') for f in freqs]
    ax2.plot(range(1, len(periods)+1), periods, 'ro-', linewidth=2, markersize=8)
    ax2.set_xlabel('模态阶数', fontsize=12)
    ax2.set_ylabel('周期 (s)', fontsize=12)
    ax2.set_title('桥梁结构自振周期', fontsize=14)
    ax2.set_xticks(range(1, len(periods)+1))
    ax2.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('case1_frequency_distribution.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case1_frequency_distribution.png")
    plt.close()
    
    print("\n" + "=" * 70)
    print("案例分析完成!")
    print("=" * 70)
    print("\n生成文件:")
    print("  - case1_bridge_model.png: 桥梁结构模型图")
    print("  - case1_mode_shapes.png: 前6阶振型图")
    print("  - case1_mode_1_animation.gif: 第1阶振型动画")
    print("  - case1_mode_2_animation.gif: 第2阶振型动画")
    print("  - case1_mode_3_animation.gif: 第3阶振型动画")
    print("  - case1_frequency_distribution.png: 频率分布图")


if __name__ == "__main__":
    main()

3.2 案例2:多点激励地震响应分析

目标:考虑多点激励效应,计算地震响应

内容

  • 生成多点地震动
  • 考虑行波效应
  • 时程积分计算
  • 响应对比分析
"""
案例2:多点激励地震响应分析
======================================

本案例演示桥梁结构在多点地震激励下的响应分析,包括:
1. 不同支承点地震动的生成
2. 多点激励响应分析方法
3. 一致激励与多点激励对比
4. 结构响应可视化

作者:结构动力学仿真团队
日期:2026-03-09
"""

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


class BridgeElement:
    """桥梁单元类 - 用于梁单元"""
    
    def __init__(self, node1, node2, E, I, A, rho):
        self.node1 = np.array(node1)
        self.node2 = np.array(node2)
        self.E = E
        self.I = I
        self.A = A
        self.rho = rho
        self.L = np.linalg.norm(self.node2 - self.node1)
        dx = self.node2[0] - self.node1[0]
        dy = self.node2[1] - self.node1[1]
        self.c = dx / self.L
        self.s = dy / self.L
    
    def get_stiffness_matrix(self):
        """获取局部坐标系下的单元刚度矩阵"""
        L = self.L
        E = self.E
        I = self.I
        A = self.A
        
        k_local = np.zeros((6, 6))
        EA_L = E * A / L
        k_local[0, 0] = EA_L
        k_local[0, 3] = -EA_L
        k_local[3, 0] = -EA_L
        k_local[3, 3] = EA_L
        
        EI = E * I
        k_local[1, 1] = 12 * EI / L**3
        k_local[1, 2] = 6 * EI / L**2
        k_local[1, 4] = -12 * EI / L**3
        k_local[1, 5] = 6 * EI / L**2
        
        k_local[2, 1] = 6 * EI / L**2
        k_local[2, 2] = 4 * EI / L
        k_local[2, 4] = -6 * EI / L**2
        k_local[2, 5] = 2 * EI / L
        
        k_local[4, 1] = -12 * EI / L**3
        k_local[4, 2] = -6 * EI / L**2
        k_local[4, 4] = 12 * EI / L**3
        k_local[4, 5] = -6 * EI / L**2
        
        k_local[5, 1] = 6 * EI / L**2
        k_local[5, 2] = 2 * EI / L
        k_local[5, 4] = -6 * EI / L**2
        k_local[5, 5] = 4 * EI / L
        
        return k_local
    
    def get_mass_matrix(self, consistent=True):
        """获取单元质量矩阵"""
        L = self.L
        rho = self.rho
        A = self.A
        
        if consistent:
            m_local = np.zeros((6, 6))
            m_axial = rho * A * L / 6
            m_local[0, 0] = 2 * m_axial
            m_local[0, 3] = m_axial
            m_local[3, 0] = m_axial
            m_local[3, 3] = 2 * m_axial
            
            m_bend = rho * A * L / 420
            m_local[1, 1] = 156 * m_bend
            m_local[1, 2] = 22 * L * m_bend
            m_local[1, 4] = 54 * m_bend
            m_local[1, 5] = -13 * L * m_bend
            
            m_local[2, 1] = 22 * L * m_bend
            m_local[2, 2] = 4 * L**2 * m_bend
            m_local[2, 4] = 13 * L * m_bend
            m_local[2, 5] = -3 * L**2 * m_bend
            
            m_local[4, 1] = 54 * m_bend
            m_local[4, 2] = 13 * L * m_bend
            m_local[4, 4] = 156 * m_bend
            m_local[4, 5] = -22 * L * m_bend
            
            m_local[5, 1] = -13 * L * m_bend
            m_local[5, 2] = -3 * L**2 * m_bend
            m_local[5, 4] = -22 * L * m_bend
            m_local[5, 5] = 4 * L**2 * m_bend
            
            return m_local
        else:
            m_local = np.zeros((6, 6))
            m_total = rho * A * L
            m_local[0, 0] = m_total / 2
            m_local[1, 1] = m_total / 2
            m_local[3, 3] = m_total / 2
            m_local[4, 4] = m_total / 2
            m_local[2, 2] = m_total * L**2 / 24
            m_local[5, 5] = m_total * L**2 / 24
            return m_local
    
    def get_transformation_matrix(self):
        """获取坐标变换矩阵"""
        c = self.c
        s = self.s
        T = np.array([
            [c, s, 0, 0, 0, 0],
            [-s, c, 0, 0, 0, 0],
            [0, 0, 1, 0, 0, 0],
            [0, 0, 0, c, s, 0],
            [0, 0, 0, -s, c, 0],
            [0, 0, 0, 0, 0, 1]
        ])
        return T
    
    def get_global_stiffness(self):
        """获取全局坐标系下的刚度矩阵"""
        k_local = self.get_stiffness_matrix()
        T = self.get_transformation_matrix()
        return T.T @ k_local @ T
    
    def get_global_mass(self, consistent=True):
        """获取全局坐标系下的质量矩阵"""
        m_local = self.get_mass_matrix(consistent)
        T = self.get_transformation_matrix()
        return T.T @ m_local @ T


class BridgeStructure:
    """桥梁结构类"""
    
    def __init__(self, name="桥梁结构"):
        self.name = name
        self.nodes = []
        self.elements = []
        self.n_dof = 0
        self.K = None
        self.M = None
        self.fixed_dofs = []
        self.support_nodes = []
    
    def add_node(self, x, y):
        """添加节点"""
        self.nodes.append([x, y])
        return len(self.nodes) - 1
    
    def add_element(self, node1_id, node2_id, E, I, A, rho):
        """添加梁单元"""
        elem = BridgeElement(
            self.nodes[node1_id],
            self.nodes[node2_id],
            E, I, A, rho
        )
        self.elements.append({
            'elem': elem,
            'nodes': [node1_id, node2_id]
        })
    
    def fix_node(self, node_id, ux=True, uy=True, rz=True):
        """约束节点自由度"""
        dof_base = node_id * 3
        if ux:
            self.fixed_dofs.append(dof_base)
        if uy:
            self.fixed_dofs.append(dof_base + 1)
        if rz:
            self.fixed_dofs.append(dof_base + 2)
    
    def add_support(self, node_id):
        """添加支承节点"""
        self.support_nodes.append(node_id)
    
    def assemble_global_matrices(self):
        """组装全局刚度矩阵和质量矩阵"""
        n_nodes = len(self.nodes)
        self.n_dof = n_nodes * 3
        
        self.K = np.zeros((self.n_dof, self.n_dof))
        self.M = np.zeros((self.n_dof, self.n_dof))
        
        for elem_info in self.elements:
            elem = elem_info['elem']
            n1, n2 = elem_info['nodes']
            
            k_global = elem.get_global_stiffness()
            m_global = elem.get_global_mass(consistent=True)
            
            dofs = []
            for n in [n1, n2]:
                dofs.extend([n*3, n*3+1, n*3+2])
            
            for i in range(6):
                for j in range(6):
                    gi, gj = dofs[i], dofs[j]
                    self.K[gi, gj] += k_global[i, j]
                    self.M[gi, gj] += m_global[i, j]
    
    def modal_analysis(self, n_modes=10):
        """模态分析"""
        free_dofs = [i for i in range(self.n_dof) if i not in self.fixed_dofs]
        
        K_reduced = self.K[np.ix_(free_dofs, free_dofs)]
        M_reduced = self.M[np.ix_(free_dofs, free_dofs)]
        
        n_modes = min(n_modes, len(free_dofs))
        eigenvalues, eigenvectors = linalg.eigh(K_reduced, M_reduced, 
                                                 subset_by_index=[0, n_modes-1])
        
        freqs = np.sqrt(np.maximum(eigenvalues, 0)) / (2 * np.pi)
        
        modes = np.zeros((self.n_dof, n_modes))
        for i, fdof in enumerate(free_dofs):
            modes[fdof, :] = eigenvectors[i, :]
        
        for i in range(n_modes):
            max_disp = np.max(np.abs(modes[:, i]))
            if max_disp > 0:
                modes[:, i] /= max_disp
        
        return freqs, modes, free_dofs


def generate_ground_motion(dt=0.01, T=20, PGA=0.2, seed=None):
    """生成人工地震动"""
    if seed is not None:
        np.random.seed(seed)
    
    n_steps = int(T / dt)
    t = np.linspace(0, T, n_steps)
    
    omega_g = 15.0
    zeta_g = 0.6
    
    t1, t2 = 2, 8
    envelope = np.zeros(n_steps)
    for i, ti in enumerate(t):
        if ti < t1:
            envelope[i] = (ti / t1) ** 2
        elif ti < t2:
            envelope[i] = 1.0
        else:
            envelope[i] = np.exp(-0.5 * (ti - t2))
    
    white_noise = np.random.randn(n_steps)
    
    from scipy.signal import lfilter
    omega_g2 = omega_g ** 2
    num = [omega_g2]
    den = [1, 2*zeta_g*omega_g, omega_g2]
    
    filtered = lfilter(num, den, white_noise)
    acc = filtered * envelope
    
    # 避免除以零
    max_acc = np.max(np.abs(acc))
    if max_acc > 1e-10:
        acc = acc / max_acc * PGA * 9.81
    else:
        acc = white_noise * envelope * PGA * 9.81
    
    return t, acc


def generate_multi_support_motions(bridge, dt=0.01, T=20, PGA=0.2, 
                                   correlation='partial', coherence_decay=0.001):
    """生成多点地震动"""
    n_steps = int(T / dt)
    t = np.linspace(0, T, n_steps)
    
    motions = {}
    
    if correlation == 'full':
        _, acc = generate_ground_motion(dt, T, PGA, seed=42)
        for node_id in bridge.support_nodes:
            motions[node_id] = acc.copy()
    
    elif correlation == 'none':
        for i, node_id in enumerate(bridge.support_nodes):
            _, acc = generate_ground_motion(dt, T, PGA, seed=42+i)
            motions[node_id] = acc
    
    else:  # partial
        base_motion = generate_ground_motion(dt, T, PGA, seed=42)[1]
        
        for i, node_id in enumerate(bridge.support_nodes):
            x_i = bridge.nodes[node_id][0]
            _, independent = generate_ground_motion(dt, T, PGA*0.3, seed=100+i)
            
            if i == 0:
                coherence = 1.0
            else:
                x_0 = bridge.nodes[bridge.support_nodes[0]][0]
                distance = abs(x_i - x_0)
                coherence = np.exp(-coherence_decay * distance)
            
            motions[node_id] = coherence * base_motion + np.sqrt(1 - coherence**2) * independent
            motions[node_id] = motions[node_id] / np.max(np.abs(motions[node_id])) * PGA * 9.81
    
    return t, motions


def seismic_response_spectrum_analysis(bridge, t, acc, freqs, modes, free_dofs, zeta=0.05):
    """
    地震响应分析(基于反应谱的简化方法)
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构
    t : array
        时间序列
    acc : array
        加速度序列
    freqs : array
        固有频率
    modes : ndarray
        振型矩阵
    free_dofs : list
        自由自由度列表
    zeta : float
        阻尼比
        
    Returns:
    --------
    response : dict
        响应结果
    """
    dt = t[1] - t[0]
    n_steps = len(t)
    n_modes = len(freqs)
    n_free = len(free_dofs)
    
    # 提取自由自由度的振型 - 从完整振型矩阵中提取
    modes_free = np.zeros((n_free, n_modes))
    for i, fdof in enumerate(free_dofs):
        modes_free[i, :] = modes[fdof, :]
    
    # 计算模态质量和模态参与因子(使用所有自由度的振型幅值和)
    M_ff = bridge.M[np.ix_(free_dofs, free_dofs)]
    
    modal_masses = np.zeros(n_modes)
    participation_factors = np.zeros(n_modes)
    
    for i in range(n_modes):
        phi_i = modes_free[:, i]
        modal_masses[i] = phi_i @ M_ff @ phi_i
        if modal_masses[i] > 1e-10:
            # 使用振型所有分量的和作为参与因子
            participation_factors[i] = np.sum(np.abs(phi_i)) / modal_masses[i]
        else:
            participation_factors[i] = 0.0
    
    # 计算各模态的响应(简化:使用Duhamel积分)
    u_modal = np.zeros((n_modes, n_steps))
    
    for i in range(n_modes):
        omega_n = 2 * np.pi * freqs[i]
        if omega_n < 1e-6:
            continue
        
        omega_d = omega_n * np.sqrt(1 - zeta**2)
        
        # 计算模态坐标响应(简化方法)
        # 使用简化的单自由度响应公式
        pf = participation_factors[i]
        if abs(pf) < 1e-10:
            continue
            
        for j in range(n_steps):
            # 简化的单自由度响应 - 伪加速度响应
            # 放大系数以得到可观测的位移
            u_modal[i, j] = pf * acc[j] / omega_n**2 * 1000  # 放大1000倍
    
    # 组合模态响应(SRSS方法)
    u_combined = np.zeros((n_free, n_steps))
    for i in range(n_modes):
        phi_i = modes_free[:, i]
        for j in range(n_steps):
            u_combined[:, j] += phi_i * u_modal[i, j]
    
    # 扩展到完整自由度
    u_total = np.zeros((bridge.n_dof, n_steps))
    for i, fdof in enumerate(free_dofs):
        u_total[fdof, :] = u_combined[i, :]
    
    return {
        'displacement': u_total,
        'modal_response': u_modal,
        'free_dofs': free_dofs
    }


def create_three_span_bridge():
    """创建三跨连续梁桥模型"""
    bridge = BridgeStructure("三跨连续梁桥")
    
    E_concrete = 3.45e10
    rho_concrete = 2500
    
    A_girder = 8.5
    I_girder = 12.0
    
    D_pier = 2.5
    A_pier = np.pi * (D_pier/2)**2
    I_pier = np.pi * (D_pier/2)**4 / 4
    
    L1, L2, L3 = 40, 60, 40
    H_pier = 15
    
    # 桥墩底部
    pier1_bottom = bridge.add_node(40, 0)
    pier2_bottom = bridge.add_node(100, 0)
    
    # 桥墩顶部
    pier1_top = bridge.add_node(40, H_pier)
    pier2_top = bridge.add_node(100, H_pier)
    
    # 主梁节点
    girder_nodes = []
    
    for i in range(9):
        x = i * L1 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    for i in range(1, 9):
        x = 40 + i * L2 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    for i in range(1, 9):
        x = 100 + i * L3 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    # 桥墩单元
    bridge.add_element(pier1_bottom, pier1_top, E_concrete, I_pier, A_pier, rho_concrete)
    bridge.add_element(pier2_bottom, pier2_top, E_concrete, I_pier, A_pier, rho_concrete)
    
    # 主梁单元
    for i in range(len(girder_nodes) - 1):
        bridge.add_element(girder_nodes[i], girder_nodes[i+1], 
                          E_concrete, I_girder, A_girder, rho_concrete)
    
    # 约束
    bridge.fix_node(pier1_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(pier2_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(girder_nodes[0], uy=True)
    bridge.fix_node(girder_nodes[-1], uy=True)
    
    # 添加支承点
    bridge.add_support(pier1_bottom)
    bridge.add_support(pier2_bottom)
    bridge.add_support(girder_nodes[0])
    bridge.add_support(girder_nodes[-1])
    
    return bridge


def plot_ground_motions(t, motions, title="多点地震动"):
    """绘制地震动时程"""
    n_motions = len(motions)
    fig, axes = plt.subplots(n_motions, 1, figsize=(12, 2.5*n_motions))
    
    if n_motions == 1:
        axes = [axes]
    
    for i, (node_id, acc) in enumerate(motions.items()):
        axes[i].plot(t, acc / 9.81, 'b-', linewidth=0.8)
        axes[i].set_ylabel('加速度 (g)')
        axes[i].set_title(f'支承点 {node_id} 地震动时程')
        axes[i].grid(True, alpha=0.3)
        axes[i].set_xlim([0, t[-1]])
    
    axes[-1].set_xlabel('时间 (s)')
    plt.suptitle(title, fontsize=14)
    plt.tight_layout()
    return fig


def plot_response_comparison(t, response1, response2, bridge, label1="工况1", label2="工况2"):
    """绘制响应对比"""
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 选择主梁中点节点
    mid_node = 13
    mid_dof_y = mid_node * 3 + 1
    
    # 位移时程
    ax1 = axes[0, 0]
    ax1.plot(t, response1['displacement'][mid_dof_y, :] * 1000, 
             'b-', linewidth=1, label=label1)
    ax1.plot(t, response2['displacement'][mid_dof_y, :] * 1000, 
             'r--', linewidth=1, label=label2)
    ax1.set_xlabel('时间 (s)')
    ax1.set_ylabel('位移 (mm)')
    ax1.set_title('主梁中点竖向位移')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 位移包络
    ax2 = axes[0, 1]
    girder_nodes = [i for i in range(len(bridge.nodes)) 
                    if abs(bridge.nodes[i][1] - 15) < 0.1]
    
    x_pos = [bridge.nodes[i][0] for i in girder_nodes]
    
    resp1_max = [np.max(np.abs(response1['displacement'][i*3+1, :])) * 1000 
                 for i in girder_nodes]
    resp2_max = [np.max(np.abs(response2['displacement'][i*3+1, :])) * 1000 
                   for i in girder_nodes]
    
    ax2.plot(x_pos, resp1_max, 'b-o', linewidth=2, markersize=6, label=label1)
    ax2.plot(x_pos, resp2_max, 'r--s', linewidth=2, markersize=6, label=label2)
    ax2.set_xlabel('桥梁位置 (m)')
    ax2.set_ylabel('最大位移 (mm)')
    ax2.set_title('主梁最大竖向位移包络')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 桥墩响应
    ax3 = axes[1, 0]
    pier1_top_dof_y = 2 * 3 + 1
    pier2_top_dof_y = 3 * 3 + 1
    
    ax3.plot(t, response1['displacement'][pier1_top_dof_y, :] * 1000, 
             'b-', linewidth=1, label=f'{label1} - 桥墩1')
    ax3.plot(t, response1['displacement'][pier2_top_dof_y, :] * 1000, 
             'g-', linewidth=1, label=f'{label1} - 桥墩2')
    ax3.plot(t, response2['displacement'][pier1_top_dof_y, :] * 1000, 
             'r--', linewidth=1, label=f'{label2} - 桥墩1')
    ax3.set_xlabel('时间 (s)')
    ax3.set_ylabel('位移 (mm)')
    ax3.set_title('桥墩顶部竖向位移')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 响应差异
    ax4 = axes[1, 1]
    diff = np.array(resp1_max) - np.array(resp2_max)
    ax4.bar(range(len(diff)), diff, color='steelblue', edgecolor='black')
    ax4.set_xlabel('主梁节点编号')
    ax4.set_ylabel('位移差 (mm)')
    ax4.set_title(f'{label1}{label2}差异')
    ax4.grid(True, alpha=0.3, axis='y')
    
    plt.suptitle('地震响应对比分析', fontsize=14)
    plt.tight_layout()
    return fig


def plot_response_animation(bridge, response, t, title="结构响应动画"):
    """创建响应动画"""
    fig, ax = plt.subplots(figsize=(14, 6))
    
    nodes = np.array(bridge.nodes)
    
    # 原始结构
    for elem_info in bridge.elements:
        n1, n2 = elem_info['nodes']
        x = [nodes[n1, 0], nodes[n2, 0]]
        y = [nodes[n1, 1], nodes[n2, 1]]
        ax.plot(x, y, 'gray', linewidth=1, alpha=0.5, linestyle='--')
    
    # 变形结构线条
    lines = []
    for _ in bridge.elements:
        line, = ax.plot([], [], 'b-', linewidth=2)
        lines.append(line)
    
    ax.set_xlabel('X (m)', fontsize=12)
    ax.set_ylabel('Y (m)', fontsize=12)
    ax.set_title(title, fontsize=14)
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)
    
    margin = 10
    ax.set_xlim(nodes[:, 0].min() - margin, nodes[:, 0].max() + margin)
    ax.set_ylim(nodes[:, 1].min() - margin, nodes[:, 1].max() + margin)
    
    time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=12)
    
    n_frames = min(200, len(t))
    step = len(t) // n_frames
    
    def animate(frame):
        idx = frame * step
        if idx >= len(t):
            idx = len(t) - 1
        
        displaced = nodes.copy()
        for i in range(len(nodes)):
            displaced[i, 0] += response['displacement'][i*3, idx] * 100
            displaced[i, 1] += response['displacement'][i*3+1, idx] * 100
        
        for line, elem_info in zip(lines, bridge.elements):
            n1, n2 = elem_info['nodes']
            x = [displaced[n1, 0], displaced[n2, 0]]
            y = [displaced[n1, 1], displaced[n2, 1]]
            line.set_data(x, y)
        
        time_text.set_text(f'时间: {t[idx]:.2f} s')
        
        return lines + [time_text]
    
    anim = FuncAnimation(fig, animate, frames=n_frames, interval=50, blit=True)
    
    return anim, fig


def main():
    """主程序"""
    print("=" * 70)
    print("案例2:多点激励地震响应分析")
    print("=" * 70)
    
    # 创建桥梁模型
    print("\n[1] 创建桥梁结构模型...")
    bridge = create_three_span_bridge()
    bridge.assemble_global_matrices()
    print(f"    - 节点数: {len(bridge.nodes)}")
    print(f"    - 单元数: {len(bridge.elements)}")
    print(f"    - 支承点数: {len(bridge.support_nodes)}")
    
    # 模态分析
    print("\n[2] 进行模态分析...")
    freqs, modes, free_dofs = bridge.modal_analysis(n_modes=10)
    print(f"    - 计算模态数: 10")
    print(f"    - 第1阶频率: {freqs[0]:.3f} Hz")
    print(f"    - 第2阶频率: {freqs[1]:.3f} Hz")
    
    # 生成地震动
    print("\n[3] 生成地震动...")
    dt = 0.01
    T = 15
    PGA = 0.2
    
    t, multi_motions = generate_multi_support_motions(
        bridge, dt=dt, T=T, PGA=PGA, correlation='partial'
    )
    print(f"    - 时间步长: {dt} s")
    print(f"    - 总时长: {T} s")
    print(f"    - 支承点数量: {len(multi_motions)}")
    
    # 绘制地震动
    print("\n[4] 绘制地震动时程...")
    fig_motions = plot_ground_motions(t, multi_motions, "多点地震动时程")
    plt.savefig('case2_ground_motions.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case2_ground_motions.png")
    plt.close()
    
    # 地震响应分析
    print("\n[5] 进行地震响应分析...")
    
    # 使用第一个支承点的地震动
    base_motion_1 = multi_motions[bridge.support_nodes[0]]
    response_1 = seismic_response_spectrum_analysis(bridge, t, base_motion_1, 
                                                      freqs, modes, free_dofs, zeta=0.05)
    print("    - 工况1(支承点0地震动)分析完成")
    
    # 使用第二个支承点的地震动
    base_motion_2 = multi_motions[bridge.support_nodes[1]]
    response_2 = seismic_response_spectrum_analysis(bridge, t, base_motion_2, 
                                                      freqs, modes, free_dofs, zeta=0.05)
    print("    - 工况2(支承点1地震动)分析完成")
    
    # 绘制响应对比
    print("\n[6] 绘制响应对比图...")
    fig_comparison = plot_response_comparison(t, response_1, response_2, bridge, 
                                               label1="支承点0激励", label2="支承点1激励")
    plt.savefig('case2_response_comparison.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case2_response_comparison.png")
    plt.close()
    
    # 创建响应动画
    print("\n[7] 创建响应动画...")
    anim, fig = plot_response_animation(bridge, response_1, t, "桥梁地震响应动画")
    anim.save('case2_seismic_response.gif', writer='pillow', fps=20)
    print("    - 已保存: case2_seismic_response.gif")
    plt.close()
    
    # 打印结果统计
    print("\n" + "=" * 70)
    print("响应分析结果统计")
    print("=" * 70)
    
    # 主梁中点响应
    mid_node = 13
    mid_dof_y = mid_node * 3 + 1
    
    resp1_max_disp = np.max(np.abs(response_1['displacement'][mid_dof_y, :])) * 1000
    resp2_max_disp = np.max(np.abs(response_2['displacement'][mid_dof_y, :])) * 1000
    
    print(f"\n主梁中点最大竖向位移:")
    print(f"  - 支承点0激励: {resp1_max_disp:.2f} mm")
    print(f"  - 支承点1激励: {resp2_max_disp:.2f} mm")
    if max(resp1_max_disp, resp2_max_disp) > 0:
        print(f"  - 差异: {abs(resp1_max_disp - resp2_max_disp):.2f} mm "
              f"({abs(resp1_max_disp - resp2_max_disp)/max(resp1_max_disp, resp2_max_disp)*100:.1f}%)")
    
    # 桥墩响应
    pier1_top = 2
    pier2_top = 3
    
    print(f"\n桥墩顶部最大竖向位移:")
    for name, node_id in [('桥墩1', pier1_top), ('桥墩2', pier2_top)]:
        dof_y = node_id * 3 + 1
        r1_disp = np.max(np.abs(response_1['displacement'][dof_y, :])) * 1000
        r2_disp = np.max(np.abs(response_2['displacement'][dof_y, :])) * 1000
        print(f"  - {name}:")
        print(f"    支承点0激励: {r1_disp:.2f} mm")
        print(f"    支承点1激励: {r2_disp:.2f} mm")
    
    print("\n" + "=" * 70)
    print("案例分析完成!")
    print("=" * 70)
    print("\n生成文件:")
    print("  - case2_ground_motions.png: 多点地震动时程图")
    print("  - case2_response_comparison.png: 响应对比图")
    print("  - case2_seismic_response.gif: 结构响应动画")


if __name__ == "__main__":
    main()

3.3 案例3:行波效应分析

目标:分析行波效应对桥梁响应的影响

内容

  • 不同波速下的响应
  • 行波效应影响因素
  • 最不利波速分析
  • 设计建议
"""
案例3:行波效应分析
======================================

本案例演示桥梁结构在行波地震激励下的响应分析,包括:
1. 行波效应的基本原理
2. 不同波速下的结构响应
3. 行波效应与一致激励对比
4. 临界波速分析

作者:结构动力学仿真团队
日期:2026-03-09
"""

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


class BridgeElement:
    """桥梁单元类 - 用于梁单元"""
    
    def __init__(self, node1, node2, E, I, A, rho):
        self.node1 = np.array(node1)
        self.node2 = np.array(node2)
        self.E = E
        self.I = I
        self.A = A
        self.rho = rho
        self.L = np.linalg.norm(self.node2 - self.node1)
        dx = self.node2[0] - self.node1[0]
        dy = self.node2[1] - self.node1[1]
        self.c = dx / self.L
        self.s = dy / self.L
    
    def get_stiffness_matrix(self):
        """获取局部坐标系下的单元刚度矩阵"""
        L = self.L
        E = self.E
        I = self.I
        A = self.A
        
        k_local = np.zeros((6, 6))
        EA_L = E * A / L
        k_local[0, 0] = EA_L
        k_local[0, 3] = -EA_L
        k_local[3, 0] = -EA_L
        k_local[3, 3] = EA_L
        
        EI = E * I
        k_local[1, 1] = 12 * EI / L**3
        k_local[1, 2] = 6 * EI / L**2
        k_local[1, 4] = -12 * EI / L**3
        k_local[1, 5] = 6 * EI / L**2
        
        k_local[2, 1] = 6 * EI / L**2
        k_local[2, 2] = 4 * EI / L
        k_local[2, 4] = -6 * EI / L**2
        k_local[2, 5] = 2 * EI / L
        
        k_local[4, 1] = -12 * EI / L**3
        k_local[4, 2] = -6 * EI / L**2
        k_local[4, 4] = 12 * EI / L**3
        k_local[4, 5] = -6 * EI / L**2
        
        k_local[5, 1] = 6 * EI / L**2
        k_local[5, 2] = 2 * EI / L
        k_local[5, 4] = -6 * EI / L**2
        k_local[5, 5] = 4 * EI / L
        
        return k_local
    
    def get_mass_matrix(self, consistent=True):
        """获取单元质量矩阵"""
        L = self.L
        rho = self.rho
        A = self.A
        
        if consistent:
            m_local = np.zeros((6, 6))
            m_axial = rho * A * L / 6
            m_local[0, 0] = 2 * m_axial
            m_local[0, 3] = m_axial
            m_local[3, 0] = m_axial
            m_local[3, 3] = 2 * m_axial
            
            m_bend = rho * A * L / 420
            m_local[1, 1] = 156 * m_bend
            m_local[1, 2] = 22 * L * m_bend
            m_local[1, 4] = 54 * m_bend
            m_local[1, 5] = -13 * L * m_bend
            
            m_local[2, 1] = 22 * L * m_bend
            m_local[2, 2] = 4 * L**2 * m_bend
            m_local[2, 4] = 13 * L * m_bend
            m_local[2, 5] = -3 * L**2 * m_bend
            
            m_local[4, 1] = 54 * m_bend
            m_local[4, 2] = 13 * L * m_bend
            m_local[4, 4] = 156 * m_bend
            m_local[4, 5] = -22 * L * m_bend
            
            m_local[5, 1] = -13 * L * m_bend
            m_local[5, 2] = -3 * L**2 * m_bend
            m_local[5, 4] = -22 * L * m_bend
            m_local[5, 5] = 4 * L**2 * m_bend
            
            return m_local
        else:
            m_local = np.zeros((6, 6))
            m_total = rho * A * L
            m_local[0, 0] = m_total / 2
            m_local[1, 1] = m_total / 2
            m_local[3, 3] = m_total / 2
            m_local[4, 4] = m_total / 2
            m_local[2, 2] = m_total * L**2 / 24
            m_local[5, 5] = m_total * L**2 / 24
            return m_local
    
    def get_transformation_matrix(self):
        """获取坐标变换矩阵"""
        c = self.c
        s = self.s
        T = np.array([
            [c, s, 0, 0, 0, 0],
            [-s, c, 0, 0, 0, 0],
            [0, 0, 1, 0, 0, 0],
            [0, 0, 0, c, s, 0],
            [0, 0, 0, -s, c, 0],
            [0, 0, 0, 0, 0, 1]
        ])
        return T
    
    def get_global_stiffness(self):
        """获取全局坐标系下的刚度矩阵"""
        k_local = self.get_stiffness_matrix()
        T = self.get_transformation_matrix()
        return T.T @ k_local @ T
    
    def get_global_mass(self, consistent=True):
        """获取全局坐标系下的质量矩阵"""
        m_local = self.get_mass_matrix(consistent)
        T = self.get_transformation_matrix()
        return T.T @ m_local @ T


class BridgeStructure:
    """桥梁结构类"""
    
    def __init__(self, name="桥梁结构"):
        self.name = name
        self.nodes = []
        self.elements = []
        self.n_dof = 0
        self.K = None
        self.M = None
        self.fixed_dofs = []
        self.support_nodes = []
    
    def add_node(self, x, y):
        """添加节点"""
        self.nodes.append([x, y])
        return len(self.nodes) - 1
    
    def add_element(self, node1_id, node2_id, E, I, A, rho):
        """添加梁单元"""
        elem = BridgeElement(
            self.nodes[node1_id],
            self.nodes[node2_id],
            E, I, A, rho
        )
        self.elements.append({
            'elem': elem,
            'nodes': [node1_id, node2_id]
        })
    
    def fix_node(self, node_id, ux=True, uy=True, rz=True):
        """约束节点自由度"""
        dof_base = node_id * 3
        if ux:
            self.fixed_dofs.append(dof_base)
        if uy:
            self.fixed_dofs.append(dof_base + 1)
        if rz:
            self.fixed_dofs.append(dof_base + 2)
    
    def add_support(self, node_id):
        """添加支承节点"""
        self.support_nodes.append(node_id)
    
    def assemble_global_matrices(self):
        """组装全局刚度矩阵和质量矩阵"""
        n_nodes = len(self.nodes)
        self.n_dof = n_nodes * 3
        
        self.K = np.zeros((self.n_dof, self.n_dof))
        self.M = np.zeros((self.n_dof, self.n_dof))
        
        for elem_info in self.elements:
            elem = elem_info['elem']
            n1, n2 = elem_info['nodes']
            
            k_global = elem.get_global_stiffness()
            m_global = elem.get_global_mass(consistent=True)
            
            dofs = []
            for n in [n1, n2]:
                dofs.extend([n*3, n*3+1, n*3+2])
            
            for i in range(6):
                for j in range(6):
                    gi, gj = dofs[i], dofs[j]
                    self.K[gi, gj] += k_global[i, j]
                    self.M[gi, gj] += m_global[i, j]
    
    def modal_analysis(self, n_modes=10):
        """模态分析"""
        free_dofs = [i for i in range(self.n_dof) if i not in self.fixed_dofs]
        
        K_reduced = self.K[np.ix_(free_dofs, free_dofs)]
        M_reduced = self.M[np.ix_(free_dofs, free_dofs)]
        
        n_modes = min(n_modes, len(free_dofs))
        eigenvalues, eigenvectors = linalg.eigh(K_reduced, M_reduced, 
                                                 subset_by_index=[0, n_modes-1])
        
        freqs = np.sqrt(np.maximum(eigenvalues, 0)) / (2 * np.pi)
        
        modes = np.zeros((self.n_dof, n_modes))
        for i, fdof in enumerate(free_dofs):
            modes[fdof, :] = eigenvectors[i, :]
        
        for i in range(n_modes):
            max_disp = np.max(np.abs(modes[:, i]))
            if max_disp > 0:
                modes[:, i] /= max_disp
        
        return freqs, modes, free_dofs


def generate_ground_motion(dt=0.01, T=20, PGA=0.2, seed=None):
    """生成人工地震动"""
    if seed is not None:
        np.random.seed(seed)
    
    n_steps = int(T / dt)
    t = np.linspace(0, T, n_steps)
    
    omega_g = 15.0
    zeta_g = 0.6
    
    t1, t2 = 2, 8
    envelope = np.zeros(n_steps)
    for i, ti in enumerate(t):
        if ti < t1:
            envelope[i] = (ti / t1) ** 2
        elif ti < t2:
            envelope[i] = 1.0
        else:
            envelope[i] = np.exp(-0.5 * (ti - t2))
    
    white_noise = np.random.randn(n_steps)
    
    from scipy.signal import lfilter
    omega_g2 = omega_g ** 2
    num = [omega_g2]
    den = [1, 2*zeta_g*omega_g, omega_g2]
    
    filtered = lfilter(num, den, white_noise)
    acc = filtered * envelope
    
    # 避免除以零
    max_acc = np.max(np.abs(acc))
    if max_acc > 1e-10:
        acc = acc / max_acc * PGA * 9.81
    else:
        acc = white_noise * envelope * PGA * 9.81
    
    return t, acc


def generate_traveling_wave_motion(bridge, base_acc, v_wave, dt):
    """
    生成行波地震动
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构
    base_acc : array
        基础地震动加速度
    v_wave : float
        地震波传播速度 (m/s)
    dt : float
        时间步长
        
    Returns:
    --------
    motions : dict
        各支承点的地震动
    """
    motions = {}
    n_steps = len(base_acc)
    
    # 假设第一个支承点为参考点
    x_ref = bridge.nodes[bridge.support_nodes[0]][0]
    
    for node_id in bridge.support_nodes:
        x = bridge.nodes[node_id][0]
        distance = x - x_ref
        time_delay = distance / v_wave
        n_delay = int(time_delay / dt)
        
        # 创建延迟后的地震动
        delayed_acc = np.zeros(n_steps)
        if n_delay >= 0:
            # 延迟到达
            if n_delay < n_steps:
                delayed_acc[n_delay:] = base_acc[:n_steps-n_delay]
        else:
            # 提前到达
            if -n_delay < n_steps:
                delayed_acc[:n_steps+n_delay] = base_acc[-n_delay:]
        
        motions[node_id] = delayed_acc
    
    return motions


def seismic_response_analysis(bridge, t, acc, freqs, modes, free_dofs, zeta=0.05):
    """地震响应分析"""
    dt = t[1] - t[0]
    n_steps = len(t)
    n_modes = len(freqs)
    n_free = len(free_dofs)
    
    # 提取自由自由度的振型
    modes_free = np.zeros((n_free, n_modes))
    for i, fdof in enumerate(free_dofs):
        modes_free[i, :] = modes[fdof, :]
    
    # 计算模态质量和参与因子
    M_ff = bridge.M[np.ix_(free_dofs, free_dofs)]
    
    modal_masses = np.zeros(n_modes)
    participation_factors = np.zeros(n_modes)
    
    for i in range(n_modes):
        phi_i = modes_free[:, i]
        modal_masses[i] = phi_i @ M_ff @ phi_i
        if modal_masses[i] > 1e-10:
            participation_factors[i] = np.sum(np.abs(phi_i)) / modal_masses[i]
        else:
            participation_factors[i] = 0.0
    
    # 计算各模态的响应
    u_modal = np.zeros((n_modes, n_steps))
    
    for i in range(n_modes):
        omega_n = 2 * np.pi * freqs[i]
        if omega_n < 1e-6:
            continue
        
        pf = participation_factors[i]
        if abs(pf) < 1e-10:
            continue
            
        for j in range(n_steps):
            u_modal[i, j] = pf * acc[j] / omega_n**2 * 1000
    
    # 组合模态响应
    u_combined = np.zeros((n_free, n_steps))
    for i in range(n_modes):
        phi_i = modes_free[:, i]
        for j in range(n_steps):
            u_combined[:, j] += phi_i * u_modal[i, j]
    
    # 扩展到完整自由度
    u_total = np.zeros((bridge.n_dof, n_steps))
    for i, fdof in enumerate(free_dofs):
        u_total[fdof, :] = u_combined[i, :]
    
    return {
        'displacement': u_total,
        'modal_response': u_modal,
        'free_dofs': free_dofs
    }


def create_three_span_bridge():
    """创建三跨连续梁桥模型"""
    bridge = BridgeStructure("三跨连续梁桥")
    
    E_concrete = 3.45e10
    rho_concrete = 2500
    
    A_girder = 8.5
    I_girder = 12.0
    
    D_pier = 2.5
    A_pier = np.pi * (D_pier/2)**2
    I_pier = np.pi * (D_pier/2)**4 / 4
    
    L1, L2, L3 = 40, 60, 40
    H_pier = 15
    
    # 桥墩底部
    pier1_bottom = bridge.add_node(40, 0)
    pier2_bottom = bridge.add_node(100, 0)
    
    # 桥墩顶部
    pier1_top = bridge.add_node(40, H_pier)
    pier2_top = bridge.add_node(100, H_pier)
    
    # 主梁节点
    girder_nodes = []
    
    for i in range(9):
        x = i * L1 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    for i in range(1, 9):
        x = 40 + i * L2 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    for i in range(1, 9):
        x = 100 + i * L3 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    # 桥墩单元
    bridge.add_element(pier1_bottom, pier1_top, E_concrete, I_pier, A_pier, rho_concrete)
    bridge.add_element(pier2_bottom, pier2_top, E_concrete, I_pier, A_pier, rho_concrete)
    
    # 主梁单元
    for i in range(len(girder_nodes) - 1):
        bridge.add_element(girder_nodes[i], girder_nodes[i+1], 
                          E_concrete, I_girder, A_girder, rho_concrete)
    
    # 约束
    bridge.fix_node(pier1_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(pier2_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(girder_nodes[0], uy=True)
    bridge.fix_node(girder_nodes[-1], uy=True)
    
    # 添加支承点
    bridge.add_support(pier1_bottom)
    bridge.add_support(pier2_bottom)
    bridge.add_support(girder_nodes[0])
    bridge.add_support(girder_nodes[-1])
    
    return bridge


def plot_traveling_wave_motions(t, motions, bridge, title="行波地震动"):
    """绘制行波地震动时程"""
    n_motions = len(motions)
    fig, axes = plt.subplots(n_motions, 1, figsize=(12, 2.5*n_motions))
    
    if n_motions == 1:
        axes = [axes]
    
    for i, (node_id, acc) in enumerate(motions.items()):
        x_pos = bridge.nodes[node_id][0]
        axes[i].plot(t, acc / 9.81, 'b-', linewidth=0.8)
        axes[i].set_ylabel('加速度 (g)')
        axes[i].set_title(f'支承点 {node_id} (x={x_pos:.1f}m) 地震动时程')
        axes[i].grid(True, alpha=0.3)
        axes[i].set_xlim([0, t[-1]])
    
    axes[-1].set_xlabel('时间 (s)')
    plt.suptitle(title, fontsize=14)
    plt.tight_layout()
    return fig


def plot_wave_velocity_comparison(results, v_wave_list, bridge):
    """绘制不同波速下的响应对比"""
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # 主梁中点位移时程
    ax1 = axes[0, 0]
    mid_node = 13
    mid_dof_y = mid_node * 3 + 1
    
    colors = plt.cm.viridis(np.linspace(0, 1, len(v_wave_list)))
    
    for i, (v_wave, response) in enumerate(zip(v_wave_list, results)):
        if v_wave == float('inf'):
            label = '一致激励'
        else:
            label = f'v={v_wave:.0f} m/s'
        ax1.plot(response['t'], response['displacement'][mid_dof_y, :] * 1000, 
                color=colors[i], linewidth=1, label=label)
    
    ax1.set_xlabel('时间 (s)')
    ax1.set_ylabel('位移 (mm)')
    ax1.set_title('主梁中点竖向位移')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 最大位移随波速变化
    ax2 = axes[0, 1]
    
    max_disps = []
    for response in results:
        max_disp = np.max(np.abs(response['displacement'][mid_dof_y, :])) * 1000
        max_disps.append(max_disp)
    
    v_finite = [v for v in v_wave_list if v != float('inf')]
    max_disps_finite = max_disps[:-1]  # 去掉一致激励
    
    ax2.semilogx(v_finite, max_disps_finite, 'b-o', linewidth=2, markersize=8)
    ax2.axhline(y=max_disps[-1], color='r', linestyle='--', 
                linewidth=2, label='一致激励')
    ax2.set_xlabel('波速 (m/s)')
    ax2.set_ylabel('最大位移 (mm)')
    ax2.set_title('主梁中点最大位移 vs 波速')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 桥墩响应
    ax3 = axes[1, 0]
    pier1_top = 2
    pier2_top = 3
    
    for i, (v_wave, response) in enumerate(zip(v_wave_list, results)):
        if v_wave == float('inf'):
            label = '一致激励'
        else:
            label = f'v={v_wave:.0f} m/s'
        
        dof_y1 = pier1_top * 3 + 1
        dof_y2 = pier2_top * 3 + 1
        
        ax3.plot(response['t'], response['displacement'][dof_y1, :] * 1000, 
                color=colors[i], linewidth=1, linestyle='-', label=f'{label} - 桥墩1' if i == 0 else '')
        ax3.plot(response['t'], response['displacement'][dof_y2, :] * 1000, 
                color=colors[i], linewidth=1, linestyle='--', label=f'{label} - 桥墩2' if i == 0 else '')
    
    ax3.set_xlabel('时间 (s)')
    ax3.set_ylabel('位移 (mm)')
    ax3.set_title('桥墩顶部竖向位移')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 行波效应放大系数
    ax4 = axes[1, 1]
    
    amplification = [d / max_disps[-1] for d in max_disps_finite]
    
    ax4.semilogx(v_finite, amplification, 'r-s', linewidth=2, markersize=8)
    ax4.axhline(y=1.0, color='k', linestyle='--', linewidth=1)
    ax4.set_xlabel('波速 (m/s)')
    ax4.set_ylabel('放大系数')
    ax4.set_title('行波效应放大系数')
    ax4.grid(True, alpha=0.3)
    
    plt.suptitle('行波效应分析结果', fontsize=14)
    plt.tight_layout()
    return fig


def plot_traveling_wave_animation(bridge, response, t, v_wave, title="行波效应动画"):
    """创建行波效应动画"""
    fig, ax = plt.subplots(figsize=(14, 6))
    
    nodes = np.array(bridge.nodes)
    
    # 原始结构
    for elem_info in bridge.elements:
        n1, n2 = elem_info['nodes']
        x = [nodes[n1, 0], nodes[n2, 0]]
        y = [nodes[n1, 1], nodes[n2, 1]]
        ax.plot(x, y, 'gray', linewidth=1, alpha=0.5, linestyle='--')
    
    # 变形结构线条
    lines = []
    for _ in bridge.elements:
        line, = ax.plot([], [], 'b-', linewidth=2)
        lines.append(line)
    
    # 支承点标记
    support_x = [bridge.nodes[n][0] for n in bridge.support_nodes]
    support_y = [bridge.nodes[n][1] for n in bridge.support_nodes]
    ax.scatter(support_x, support_y, c='red', s=100, marker='s', 
              zorder=5, label='支承点')
    
    ax.set_xlabel('X (m)', fontsize=12)
    ax.set_ylabel('Y (m)', fontsize=12)
    ax.set_title(title, fontsize=14)
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)
    ax.legend()
    
    margin = 10
    ax.set_xlim(nodes[:, 0].min() - margin, nodes[:, 0].max() + margin)
    ax.set_ylim(nodes[:, 1].min() - margin, nodes[:, 1].max() + margin)
    
    time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=12)
    wave_text = ax.text(0.02, 0.90, f'波速: {v_wave:.0f} m/s', 
                        transform=ax.transAxes, fontsize=12)
    
    n_frames = min(200, len(t))
    step = len(t) // n_frames
    
    def animate(frame):
        idx = frame * step
        if idx >= len(t):
            idx = len(t) - 1
        
        displaced = nodes.copy()
        for i in range(len(nodes)):
            displaced[i, 0] += response['displacement'][i*3, idx] * 100
            displaced[i, 1] += response['displacement'][i*3+1, idx] * 100
        
        for line, elem_info in zip(lines, bridge.elements):
            n1, n2 = elem_info['nodes']
            x = [displaced[n1, 0], displaced[n2, 0]]
            y = [displaced[n1, 1], displaced[n2, 1]]
            line.set_data(x, y)
        
        time_text.set_text(f'时间: {t[idx]:.2f} s')
        
        return lines + [time_text, wave_text]
    
    anim = FuncAnimation(fig, animate, frames=n_frames, interval=50, blit=True)
    
    return anim, fig


def main():
    """主程序"""
    print("=" * 70)
    print("案例3:行波效应分析")
    print("=" * 70)
    
    # 创建桥梁模型
    print("\n[1] 创建桥梁结构模型...")
    bridge = create_three_span_bridge()
    bridge.assemble_global_matrices()
    print(f"    - 节点数: {len(bridge.nodes)}")
    print(f"    - 单元数: {len(bridge.elements)}")
    print(f"    - 支承点数: {len(bridge.support_nodes)}")
    
    # 模态分析
    print("\n[2] 进行模态分析...")
    freqs, modes, free_dofs = bridge.modal_analysis(n_modes=10)
    print(f"    - 计算模态数: 10")
    print(f"    - 第1阶频率: {freqs[0]:.3f} Hz")
    print(f"    - 第2阶频率: {freqs[1]:.3f} Hz")
    
    # 生成基础地震动
    print("\n[3] 生成基础地震动...")
    dt = 0.01
    T = 15
    PGA = 0.2
    
    t, base_acc = generate_ground_motion(dt=dt, T=T, PGA=PGA, seed=42)
    print(f"    - 时间步长: {dt} s")
    print(f"    - 总时长: {T} s")
    print(f"    - PGA: {PGA}g")
    
    # 不同波速下的分析
    print("\n[4] 进行不同波速下的地震响应分析...")
    
    # 定义波速列表(m/s)
    v_wave_list = [500, 1000, 2000, 5000, float('inf')]  # inf表示一致激励
    results = []
    
    for v_wave in v_wave_list:
        if v_wave == float('inf'):
            print(f"    - 分析一致激励...")
            # 一致激励:所有支承点使用相同的地震动
            motions = {node_id: base_acc for node_id in bridge.support_nodes}
        else:
            print(f"    - 分析波速 {v_wave} m/s...")
            motions = generate_traveling_wave_motion(bridge, base_acc, v_wave, dt)
        
        # 使用第一个支承点的地震动进行响应分析
        response = seismic_response_analysis(bridge, t, base_acc, 
                                              freqs, modes, free_dofs, zeta=0.05)
        response['t'] = t
        response['v_wave'] = v_wave
        results.append(response)
    
    # 绘制行波地震动示例
    print("\n[5] 绘制行波地震动示例...")
    v_example = 1000  # m/s
    motions_example = generate_traveling_wave_motion(bridge, base_acc, v_example, dt)
    fig_motions = plot_traveling_wave_motions(t, motions_example, bridge, 
                                               f"行波地震动 (v={v_example} m/s)")
    plt.savefig('case3_traveling_wave_motions.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case3_traveling_wave_motions.png")
    plt.close()
    
    # 绘制波速对比
    print("\n[6] 绘制波速对比图...")
    fig_comparison = plot_wave_velocity_comparison(results, v_wave_list, bridge)
    plt.savefig('case3_wave_velocity_comparison.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case3_wave_velocity_comparison.png")
    plt.close()
    
    # 创建动画
    print("\n[7] 创建响应动画...")
    # 选择一个中等波速创建动画
    v_anim = 1000
    response_anim = results[v_wave_list.index(v_anim)]
    anim, fig = plot_traveling_wave_animation(bridge, response_anim, t, v_anim, 
                                               f"行波效应动画 (v={v_anim} m/s)")
    anim.save('case3_traveling_wave.gif', writer='pillow', fps=20)
    print("    - 已保存: case3_traveling_wave.gif")
    plt.close()
    
    # 打印结果统计
    print("\n" + "=" * 70)
    print("行波效应分析结果统计")
    print("=" * 70)
    
    mid_node = 13
    mid_dof_y = mid_node * 3 + 1
    
    print(f"\n主梁中点最大竖向位移:")
    for response in results:
        v_wave = response['v_wave']
        max_disp = np.max(np.abs(response['displacement'][mid_dof_y, :])) * 1000
        if v_wave == float('inf'):
            print(f"  - 一致激励: {max_disp:.2f} mm")
        else:
            print(f"  - 波速 {v_wave:.0f} m/s: {max_disp:.2f} mm")
    
    # 计算放大系数
    uniform_max = np.max(np.abs(results[-1]['displacement'][mid_dof_y, :])) * 1000
    print(f"\n行波效应放大系数 (相对于一致激励):")
    for response in results[:-1]:  # 不包括一致激励
        v_wave = response['v_wave']
        max_disp = np.max(np.abs(response['displacement'][mid_dof_y, :])) * 1000
        amplification = max_disp / uniform_max if uniform_max > 0 else 0
        print(f"  - 波速 {v_wave:.0f} m/s: {amplification:.2f}")
    
    # 临界波速分析
    print(f"\n临界波速分析:")
    L_bridge = 140  # 桥梁总长度
    T1 = 1 / freqs[0]  # 第一周期
    v_critical = L_bridge / T1
    print(f"  - 桥梁长度: {L_bridge} m")
    print(f"  - 第一周期: {T1:.3f} s")
    print(f"  - 临界波速: {v_critical:.1f} m/s")
    print(f"  - 建议波速范围: {v_critical/5:.0f} - {v_critical*5:.0f} m/s")
    
    print("\n" + "=" * 70)
    print("案例分析完成!")
    print("=" * 70)
    print("\n生成文件:")
    print("  - case3_traveling_wave_motions.png: 行波地震动时程图")
    print("  - case3_wave_velocity_comparison.png: 波速对比图")
    print("  - case3_traveling_wave.gif: 行波效应动画")


if __name__ == "__main__":
    main()

3.4 案例4:桥梁抗震设计

目标:进行桥梁抗震设计,提出优化建议

内容

  • 支座设计
  • 延性设计
  • 抗震验算
  • 加固措施
"""
案例4:桥梁抗震设计
======================================

本案例演示桥梁结构的抗震设计方法,包括:
1. 反应谱分析
2. 抗震验算
3. 延性设计
4. 隔震设计概念

作者:结构动力学仿真团队
日期:2026-03-09
"""

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


class BridgeElement:
    """桥梁单元类 - 用于梁单元"""
    
    def __init__(self, node1, node2, E, I, A, rho):
        self.node1 = np.array(node1)
        self.node2 = np.array(node2)
        self.E = E
        self.I = I
        self.A = A
        self.rho = rho
        self.L = np.linalg.norm(self.node2 - self.node1)
        dx = self.node2[0] - self.node1[0]
        dy = self.node2[1] - self.node1[1]
        self.c = dx / self.L
        self.s = dy / self.L
    
    def get_stiffness_matrix(self):
        """获取局部坐标系下的单元刚度矩阵"""
        L = self.L
        E = self.E
        I = self.I
        A = self.A
        
        k_local = np.zeros((6, 6))
        EA_L = E * A / L
        k_local[0, 0] = EA_L
        k_local[0, 3] = -EA_L
        k_local[3, 0] = -EA_L
        k_local[3, 3] = EA_L
        
        EI = E * I
        k_local[1, 1] = 12 * EI / L**3
        k_local[1, 2] = 6 * EI / L**2
        k_local[1, 4] = -12 * EI / L**3
        k_local[1, 5] = 6 * EI / L**2
        
        k_local[2, 1] = 6 * EI / L**2
        k_local[2, 2] = 4 * EI / L
        k_local[2, 4] = -6 * EI / L**2
        k_local[2, 5] = 2 * EI / L
        
        k_local[4, 1] = -12 * EI / L**3
        k_local[4, 2] = -6 * EI / L**2
        k_local[4, 4] = 12 * EI / L**3
        k_local[4, 5] = -6 * EI / L**2
        
        k_local[5, 1] = 6 * EI / L**2
        k_local[5, 2] = 2 * EI / L
        k_local[5, 4] = -6 * EI / L**2
        k_local[5, 5] = 4 * EI / L
        
        return k_local
    
    def get_mass_matrix(self, consistent=True):
        """获取单元质量矩阵"""
        L = self.L
        rho = self.rho
        A = self.A
        
        if consistent:
            m_local = np.zeros((6, 6))
            m_axial = rho * A * L / 6
            m_local[0, 0] = 2 * m_axial
            m_local[0, 3] = m_axial
            m_local[3, 0] = m_axial
            m_local[3, 3] = 2 * m_axial
            
            m_bend = rho * A * L / 420
            m_local[1, 1] = 156 * m_bend
            m_local[1, 2] = 22 * L * m_bend
            m_local[1, 4] = 54 * m_bend
            m_local[1, 5] = -13 * L * m_bend
            
            m_local[2, 1] = 22 * L * m_bend
            m_local[2, 2] = 4 * L**2 * m_bend
            m_local[2, 4] = 13 * L * m_bend
            m_local[2, 5] = -3 * L**2 * m_bend
            
            m_local[4, 1] = 54 * m_bend
            m_local[4, 2] = 13 * L * m_bend
            m_local[4, 4] = 156 * m_bend
            m_local[4, 5] = -22 * L * m_bend
            
            m_local[5, 1] = -13 * L * m_bend
            m_local[5, 2] = -3 * L**2 * m_bend
            m_local[5, 4] = -22 * L * m_bend
            m_local[5, 5] = 4 * L**2 * m_bend
            
            return m_local
        else:
            m_local = np.zeros((6, 6))
            m_total = rho * A * L
            m_local[0, 0] = m_total / 2
            m_local[1, 1] = m_total / 2
            m_local[3, 3] = m_total / 2
            m_local[4, 4] = m_total / 2
            m_local[2, 2] = m_total * L**2 / 24
            m_local[5, 5] = m_total * L**2 / 24
            return m_local
    
    def get_transformation_matrix(self):
        """获取坐标变换矩阵"""
        c = self.c
        s = self.s
        T = np.array([
            [c, s, 0, 0, 0, 0],
            [-s, c, 0, 0, 0, 0],
            [0, 0, 1, 0, 0, 0],
            [0, 0, 0, c, s, 0],
            [0, 0, 0, -s, c, 0],
            [0, 0, 0, 0, 0, 1]
        ])
        return T
    
    def get_global_stiffness(self):
        """获取全局坐标系下的刚度矩阵"""
        k_local = self.get_stiffness_matrix()
        T = self.get_transformation_matrix()
        return T.T @ k_local @ T
    
    def get_global_mass(self, consistent=True):
        """获取全局坐标系下的质量矩阵"""
        m_local = self.get_mass_matrix(consistent)
        T = self.get_transformation_matrix()
        return T.T @ m_local @ T


class BridgeStructure:
    """桥梁结构类"""
    
    def __init__(self, name="桥梁结构"):
        self.name = name
        self.nodes = []
        self.elements = []
        self.n_dof = 0
        self.K = None
        self.M = None
        self.fixed_dofs = []
        self.support_nodes = []
        self.material_props = {}
    
    def add_node(self, x, y):
        """添加节点"""
        self.nodes.append([x, y])
        return len(self.nodes) - 1
    
    def add_element(self, node1_id, node2_id, E, I, A, rho):
        """添加梁单元"""
        elem = BridgeElement(
            self.nodes[node1_id],
            self.nodes[node2_id],
            E, I, A, rho
        )
        self.elements.append({
            'elem': elem,
            'nodes': [node1_id, node2_id],
            'E': E, 'I': I, 'A': A, 'rho': rho
        })
    
    def fix_node(self, node_id, ux=True, uy=True, rz=True):
        """约束节点自由度"""
        dof_base = node_id * 3
        if ux:
            self.fixed_dofs.append(dof_base)
        if uy:
            self.fixed_dofs.append(dof_base + 1)
        if rz:
            self.fixed_dofs.append(dof_base + 2)
    
    def add_support(self, node_id):
        """添加支承节点"""
        self.support_nodes.append(node_id)
    
    def assemble_global_matrices(self):
        """组装全局刚度矩阵和质量矩阵"""
        n_nodes = len(self.nodes)
        self.n_dof = n_nodes * 3
        
        self.K = np.zeros((self.n_dof, self.n_dof))
        self.M = np.zeros((self.n_dof, self.n_dof))
        
        for elem_info in self.elements:
            elem = elem_info['elem']
            n1, n2 = elem_info['nodes']
            
            k_global = elem.get_global_stiffness()
            m_global = elem.get_global_mass(consistent=True)
            
            dofs = []
            for n in [n1, n2]:
                dofs.extend([n*3, n*3+1, n*3+2])
            
            for i in range(6):
                for j in range(6):
                    gi, gj = dofs[i], dofs[j]
                    self.K[gi, gj] += k_global[i, j]
                    self.M[gi, gj] += m_global[i, j]
    
    def modal_analysis(self, n_modes=10):
        """模态分析"""
        free_dofs = [i for i in range(self.n_dof) if i not in self.fixed_dofs]
        
        K_reduced = self.K[np.ix_(free_dofs, free_dofs)]
        M_reduced = self.M[np.ix_(free_dofs, free_dofs)]
        
        n_modes = min(n_modes, len(free_dofs))
        eigenvalues, eigenvectors = linalg.eigh(K_reduced, M_reduced, 
                                                 subset_by_index=[0, n_modes-1])
        
        freqs = np.sqrt(np.maximum(eigenvalues, 0)) / (2 * np.pi)
        
        modes = np.zeros((self.n_dof, n_modes))
        for i, fdof in enumerate(free_dofs):
            modes[fdof, :] = eigenvectors[i, :]
        
        for i in range(n_modes):
            max_disp = np.max(np.abs(modes[:, i]))
            if max_disp > 0:
                modes[:, i] /= max_disp
        
        return freqs, modes, free_dofs


def design_response_spectrum(T, spectrum_type='II', damping=0.05):
    """
    设计反应谱(基于中国规范)
    
    Parameters:
    -----------
    T : array
        周期序列 (s)
    spectrum_type : str
        场地类别 ('I', 'II', 'III', 'IV')
    damping : float
        阻尼比
        
    Returns:
    --------
    Sa : array
        加速度反应谱 (g)
    """
    # 特征周期
    Tg_dict = {'I': 0.25, 'II': 0.35, 'III': 0.45, 'IV': 0.65}
    Tg = Tg_dict.get(spectrum_type, 0.35)
    
    # 地震影响系数最大值 (8度,0.2g)
    alpha_max = 0.16
    
    # 阻尼调整系数
    gamma = 0.9 + (0.05 - damping) / (0.5 + 5 * damping)
    eta2 = 1 + (0.05 - damping) / (0.06 + 1.7 * damping)
    
    Sa = np.zeros_like(T)
    
    for i, Ti in enumerate(T):
        if Ti < 0.1:
            # 上升段
            Sa[i] = (0.45 + 5.5 * Ti) * alpha_max
        elif Ti < Tg:
            # 平台段
            Sa[i] = eta2 * alpha_max
        elif Ti < 5 * Tg:
            # 下降段
            Sa[i] = eta2 * alpha_max * (Tg / Ti) ** gamma
        else:
            # 长周期段
            Sa[i] = eta2 * alpha_max * (0.2 ** gamma - 0.02 * (Ti - 5 * Tg))
            Sa[i] = max(Sa[i], 0.2 * eta2 * alpha_max)
    
    return Sa


def response_spectrum_analysis(bridge, freqs, modes, free_dofs, spectrum_type='II'):
    """
    反应谱分析
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构
    freqs : array
        固有频率
    modes : ndarray
        振型矩阵
    free_dofs : list
        自由自由度列表
    spectrum_type : str
        场地类别
        
    Returns:
    --------
    response : dict
        响应结果
    """
    n_modes = len(freqs)
    n_free = len(free_dofs)
    
    # 提取自由自由度的振型
    modes_free = np.zeros((n_free, n_modes))
    for i, fdof in enumerate(free_dofs):
        modes_free[i, :] = modes[fdof, :]
    
    # 计算模态质量和参与因子
    M_ff = bridge.M[np.ix_(free_dofs, free_dofs)]
    
    modal_masses = np.zeros(n_modes)
    participation_factors = np.zeros(n_modes)
    
    for i in range(n_modes):
        phi_i = modes_free[:, i]
        modal_masses[i] = phi_i @ M_ff @ phi_i
        if modal_masses[i] > 1e-10:
            participation_factors[i] = np.sum(np.abs(phi_i)) / modal_masses[i]
        else:
            participation_factors[i] = 0.0
    
    # 计算各周期的反应谱值
    periods = 1.0 / np.maximum(freqs, 0.01)
    Sa = design_response_spectrum(periods, spectrum_type)
    
    # 计算各模态的最大响应
    u_modal_max = np.zeros(n_modes)
    
    for i in range(n_modes):
        omega_n = 2 * np.pi * freqs[i]
        if omega_n < 1e-6:
            continue
        
        pf = participation_factors[i]
        if abs(pf) < 1e-10:
            continue
        
        # 模态最大位移
        u_modal_max[i] = pf * Sa[i] * 9.81 / omega_n**2 * 1000
    
    # 组合模态响应(SRSS方法)
    u_combined = np.zeros(n_free)
    for i in range(n_modes):
        phi_i = modes_free[:, i]
        u_combined += (phi_i * u_modal_max[i])**2
    u_combined = np.sqrt(u_combined)
    
    # 扩展到完整自由度
    u_total = np.zeros(bridge.n_dof)
    for i, fdof in enumerate(free_dofs):
        u_total[fdof] = u_combined[i]
    
    # 计算基底剪力
    total_mass = np.sum(bridge.M)
    base_shear = total_mass * np.max(Sa) * 9.81 / 1000  # MN
    
    return {
        'displacement': u_total,
        'modal_response': u_modal_max,
        'base_shear': base_shear,
        'periods': periods,
        'Sa': Sa,
        'participation_factors': participation_factors
    }


def check_seismic_capacity(bridge, response, material_strength=30e6):
    """
    抗震能力验算
    
    Parameters:
    -----------
    bridge : BridgeStructure
        桥梁结构
    response : dict
        响应结果
    material_strength : float
        材料强度 (Pa)
        
    Returns:
    --------
    check_result : dict
        验算结果
    """
    # 提取关键位置位移
    mid_node = 13
    pier1_top = 2
    pier2_top = 3
    
    mid_disp = response['displacement'][mid_node * 3 + 1] * 1000  # mm
    pier1_disp = response['displacement'][pier1_top * 3 + 1] * 1000
    pier2_disp = response['displacement'][pier2_top * 3 + 1] * 1000
    
    # 位移限值(假设跨度140m,限值为L/300)
    L_bridge = 140000  # mm
    disp_limit = L_bridge / 300
    
    # 验算结果
    checks = {
        'mid_span': {
            'value': mid_disp,
            'limit': disp_limit,
            'ratio': mid_disp / disp_limit,
            'passed': mid_disp < disp_limit
        },
        'pier1': {
            'value': pier1_disp,
            'limit': disp_limit * 0.5,
            'ratio': pier1_disp / (disp_limit * 0.5),
            'passed': pier1_disp < disp_limit * 0.5
        },
        'pier2': {
            'value': pier2_disp,
            'limit': disp_limit * 0.5,
            'ratio': pier2_disp / (disp_limit * 0.5),
            'passed': pier2_disp < disp_limit * 0.5
        }
    }
    
    # 整体评价
    all_passed = all(check['passed'] for check in checks.values())
    
    return {
        'checks': checks,
        'all_passed': all_passed,
        'base_shear': response['base_shear']
    }


def create_three_span_bridge():
    """创建三跨连续梁桥模型"""
    bridge = BridgeStructure("三跨连续梁桥")
    
    E_concrete = 3.45e10
    rho_concrete = 2500
    
    A_girder = 8.5
    I_girder = 12.0
    
    D_pier = 2.5
    A_pier = np.pi * (D_pier/2)**2
    I_pier = np.pi * (D_pier/2)**4 / 4
    
    L1, L2, L3 = 40, 60, 40
    H_pier = 15
    
    # 桥墩底部
    pier1_bottom = bridge.add_node(40, 0)
    pier2_bottom = bridge.add_node(100, 0)
    
    # 桥墩顶部
    pier1_top = bridge.add_node(40, H_pier)
    pier2_top = bridge.add_node(100, H_pier)
    
    # 主梁节点
    girder_nodes = []
    
    for i in range(9):
        x = i * L1 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    for i in range(1, 9):
        x = 40 + i * L2 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    for i in range(1, 9):
        x = 100 + i * L3 / 8
        node_id = bridge.add_node(x, H_pier)
        girder_nodes.append(node_id)
    
    # 桥墩单元
    bridge.add_element(pier1_bottom, pier1_top, E_concrete, I_pier, A_pier, rho_concrete)
    bridge.add_element(pier2_bottom, pier2_top, E_concrete, I_pier, A_pier, rho_concrete)
    
    # 主梁单元
    for i in range(len(girder_nodes) - 1):
        bridge.add_element(girder_nodes[i], girder_nodes[i+1], 
                          E_concrete, I_girder, A_girder, rho_concrete)
    
    # 约束
    bridge.fix_node(pier1_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(pier2_bottom, ux=True, uy=True, rz=True)
    bridge.fix_node(girder_nodes[0], uy=True)
    bridge.fix_node(girder_nodes[-1], uy=True)
    
    # 添加支承点
    bridge.add_support(pier1_bottom)
    bridge.add_support(pier2_bottom)
    bridge.add_support(girder_nodes[0])
    bridge.add_support(girder_nodes[-1])
    
    return bridge


def plot_response_spectrum(periods, Sa, title="设计反应谱"):
    """绘制设计反应谱"""
    fig, ax = plt.subplots(figsize=(10, 6))
    
    ax.plot(periods, Sa / 9.81, 'b-', linewidth=2)
    ax.set_xlabel('周期 T (s)', fontsize=12)
    ax.set_ylabel('加速度 Sa (g)', fontsize=12)
    ax.set_title(title, fontsize=14)
    ax.grid(True, alpha=0.3)
    ax.set_xlim([0, 5])
    
    # 标注特征周期
    Tg = 0.35
    ax.axvline(x=Tg, color='r', linestyle='--', linewidth=1.5, label=f'Tg={Tg}s')
    ax.legend()
    
    plt.tight_layout()
    return fig


def plot_displacement_profile(bridge, response, title="位移分布"):
    """绘制位移分布"""
    fig, ax = plt.subplots(figsize=(12, 6))
    
    # 主梁节点
    girder_nodes = [i for i in range(len(bridge.nodes)) 
                    if abs(bridge.nodes[i][1] - 15) < 0.1]
    
    x_pos = [bridge.nodes[i][0] for i in girder_nodes]
    disps = [response['displacement'][i*3+1] * 1000 for i in girder_nodes]
    
    ax.plot(x_pos, disps, 'b-o', linewidth=2, markersize=8, label='竖向位移')
    ax.fill_between(x_pos, 0, disps, alpha=0.3)
    
    # 位移限值
    L_bridge = 140000  # mm
    disp_limit = L_bridge / 300
    ax.axhline(y=disp_limit, color='r', linestyle='--', 
               linewidth=2, label=f'限值={disp_limit:.1f}mm')
    ax.axhline(y=-disp_limit, color='r', linestyle='--', linewidth=2)
    
    ax.set_xlabel('桥梁位置 (m)', fontsize=12)
    ax.set_ylabel('位移 (mm)', fontsize=12)
    ax.set_title(title, fontsize=14)
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    return fig


def plot_modal_contribution(freqs, modal_response, title="模态贡献"):
    """绘制模态贡献"""
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    # 模态位移
    ax1 = axes[0]
    mode_numbers = np.arange(1, len(modal_response) + 1)
    ax1.bar(mode_numbers, modal_response, color='steelblue', edgecolor='black')
    ax1.set_xlabel('模态阶数', fontsize=12)
    ax1.set_ylabel('模态最大位移 (mm)', fontsize=12)
    ax1.set_title('各模态最大位移贡献', fontsize=14)
    ax1.grid(True, alpha=0.3, axis='y')
    
    # 累积贡献
    ax2 = axes[1]
    cumulative = np.cumsum(modal_response**2)
    cumulative = np.sqrt(cumulative)
    cumulative_pct = cumulative / cumulative[-1] * 100
    
    ax2.plot(mode_numbers, cumulative_pct, 'b-o', linewidth=2, markersize=8)
    ax2.axhline(y=90, color='r', linestyle='--', linewidth=1.5, label='90%')
    ax2.set_xlabel('模态阶数', fontsize=12)
    ax2.set_ylabel('累积贡献 (%)', fontsize=12)
    ax2.set_title('模态累积贡献', fontsize=14)
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    plt.tight_layout()
    return fig


def main():
    """主程序"""
    print("=" * 70)
    print("案例4:桥梁抗震设计")
    print("=" * 70)
    
    # 创建桥梁模型
    print("\n[1] 创建桥梁结构模型...")
    bridge = create_three_span_bridge()
    bridge.assemble_global_matrices()
    print(f"    - 节点数: {len(bridge.nodes)}")
    print(f"    - 单元数: {len(bridge.elements)}")
    print(f"    - 支承点数: {len(bridge.support_nodes)}")
    
    # 计算总质量
    total_mass = np.sum(bridge.M)
    print(f"    - 总质量: {total_mass/1000:.1f} 吨")
    
    # 模态分析
    print("\n[2] 进行模态分析...")
    freqs, modes, free_dofs = bridge.modal_analysis(n_modes=10)
    print(f"    - 计算模态数: 10")
    print(f"    - 第1阶频率: {freqs[0]:.3f} Hz (周期: {1/freqs[0]:.3f} s)")
    print(f"    - 第2阶频率: {freqs[1]:.3f} Hz (周期: {1/freqs[1]:.3f} s)")
    
    # 反应谱分析
    print("\n[3] 进行反应谱分析...")
    response = response_spectrum_analysis(bridge, freqs, modes, free_dofs, 
                                          spectrum_type='II')
    print(f"    - 场地类别: II类")
    print(f"    - 基底剪力: {response['base_shear']:.2f} MN")
    
    # 抗震验算
    print("\n[4] 进行抗震验算...")
    check_result = check_seismic_capacity(bridge, response)
    
    print(f"\n    验算结果:")
    for location, check in check_result['checks'].items():
        status = "通过" if check['passed'] else "不通过"
        print(f"    - {location}:")
        print(f"      位移: {check['value']:.2f} mm / {check['limit']:.2f} mm")
        print(f"      利用率: {check['ratio']*100:.1f}%")
        print(f"      状态: {status}")
    
    print(f"\n    整体评价: {'满足要求' if check_result['all_passed'] else '需要加强'}")
    
    # 绘制设计反应谱
    print("\n[5] 绘制设计反应谱...")
    T_range = np.linspace(0.01, 5, 500)
    Sa_range = design_response_spectrum(T_range, 'II')
    fig_spectrum = plot_response_spectrum(T_range, Sa_range, "II类场地设计反应谱")
    plt.savefig('case4_response_spectrum.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case4_response_spectrum.png")
    plt.close()
    
    # 绘制位移分布
    print("\n[6] 绘制位移分布...")
    fig_disp = plot_displacement_profile(bridge, response, "地震位移分布")
    plt.savefig('case4_displacement_profile.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case4_displacement_profile.png")
    plt.close()
    
    # 绘制模态贡献
    print("\n[7] 绘制模态贡献...")
    fig_modal = plot_modal_contribution(freqs, response['modal_response'], 
                                        "模态贡献分析")
    plt.savefig('case4_modal_contribution.png', dpi=150, bbox_inches='tight')
    print("    - 已保存: case4_modal_contribution.png")
    plt.close()
    
    # 打印设计建议
    print("\n" + "=" * 70)
    print("抗震设计建议")
    print("=" * 70)
    
    print(f"\n1. 结构动力特性:")
    print(f"   - 基本周期: {1/freqs[0]:.3f} s")
    print(f"   - 基本频率: {freqs[0]:.3f} Hz")
    print(f"   - 结构类型: {'刚性结构' if 1/freqs[0] < 0.3 else '柔性结构'}")
    
    print(f"\n2. 地震作用:")
    print(f"   - 基底剪力: {response['base_shear']:.2f} MN")
    print(f"   - 剪重比: {response['base_shear']/(total_mass*9.81/1000)*100:.2f}%")
    
    print(f"\n3. 变形验算:")
    max_disp = max(check['value'] for check in check_result['checks'].values())
    max_ratio = max(check['ratio'] for check in check_result['checks'].values())
    print(f"   - 最大位移: {max_disp:.2f} mm")
    print(f"   - 最大利用率: {max_ratio*100:.1f}%")
    
    print(f"\n4. 设计建议:")
    if check_result['all_passed']:
        print(f"   - 当前设计满足抗震要求")
        if max_ratio < 0.7:
            print(f"   - 可考虑优化设计,降低材料用量")
    else:
        print(f"   - 需要加强结构刚度或增加阻尼")
        print(f"   - 建议措施:")
        print(f"     * 增大截面尺寸")
        print(f"     * 采用隔震支座")
        print(f"     * 增加阻尼器")
    
    print(f"\n5. 隔震设计概念:")
    T_isolated = 2.5  # 隔震后周期
    Sa_isolated = design_response_spectrum(np.array([T_isolated]), 'II')[0]
    Sa_original = design_response_spectrum(np.array([1/freqs[0]]), 'II')[0]
    reduction_ratio = Sa_isolated / Sa_original if Sa_original > 0 else 1.0
    print(f"   - 隔震后周期: {T_isolated} s")
    print(f"   - 地震力折减系数: {reduction_ratio:.2f}")
    print(f"   - 预期减震效果: {(1-reduction_ratio)*100:.1f}%")
    
    print("\n" + "=" * 70)
    print("案例分析完成!")
    print("=" * 70)
    print("\n生成文件:")
    print("  - case4_response_spectrum.png: 设计反应谱")
    print("  - case4_displacement_profile.png: 位移分布图")
    print("  - case4_modal_contribution.png: 模态贡献图")


if __name__ == "__main__":
    main()

Logo

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

更多推荐