news 2026/9/12 14:02:25

用Python重写地震易损性分析:从IDA数据到易损性曲线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python重写地震易损性分析:从IDA数据到易损性曲线

简介:这是一份基于Python实现的地震易损性分析源码包,面向土木工程、地震工程方向的研究生、科研人员及结构设计人员,解决从地震需求计算到易损性曲线绘制的代码实现难题。压缩包共147个文件,包含6个Python源程序、100个out结果数据文件、16个eps矢量图、16个png图片和9个txt文本说明,整体大小11.29MB。其中eps与png清晰呈现了ln(工程需求参数)回归、残差分布及不同构件的易损性曲线,txt文件则提供配套说明,便于对照运行。已有372人学习下载,代码附有详细注释,下载即可直接运行,适合快速上手。内容预览表明覆盖桥台位移、桥墩延性比、支座位移等关键构件的回归建模与易损性曲线生成,对桥梁地震易损性分析、地震风险评估类课题具有较高参考价值。

1. 地震易损性分析为什么值得用 Python 重写一遍带注释的源码

一栋 20 层钢筋混凝土框架,设防 8 度,当地表峰值加速度到 0.3g 时,它进入中等破坏的概率是多少?这个数不是拍脑袋来的——把几十条地震动输入结构做非线性时程分析,再把结果统计成易损性曲线,这就是地震易损性分析的主体。重型分析在 OpenSees、ETABS 里完成,数据清洗、损伤判定、曲线拟合、批量出图,则由 Python 源代码收尾。这样一份带注释的源码适合两类人:土木方向的研究生,想拿到能改参数直接跑的管线;做抗震风险评估或保险定价的工程师,需要把易损性参数整理成可供外部平台调用的结果。下面按模型、数据处理、拟合、验证的顺序,把源码里最容易写错也最值得看的部分拆开讲。

2. 易损性曲线的对数正态模型与 Python 数据结构

拿到这类压缩包,我建议先画数据流再动代码:地震动记录和 IDA 反应文件进来,中间做层间位移角提取与损伤状态判定,最后得到每条记录的容量 IM,再拟合成对数正态曲线。这一章先把数学假设和数据结构钉死,后面的代码才能逐段核对。

2.1 为什么默认假设易损性服从对数正态分布

结构反应与地震动强度在一阶近似下呈幂律关系:层间位移角 θ 与阻尼比无关地近似满足 θ ≈ a·(Sa(T1))^b,两边取对数后是线性回归形式。地震动记录之间的差异体现在回归残差里,残差近似正态,于是"结构达到某个损伤限值所需的地震动强度"(容量 IM)就近似服从对数正态分布。易损性曲线的标准写法是:

F(x) = Φ( (ln x − λ) / β )

其中 λ 是 ln(容量 IM) 的均值,β 是对数标准差,Φ 是标准正态 CDF。λ 和 β 决定了整条曲线:λ 控制中位数的位置,β 控制曲线斜率,β 越大曲线越平缓,说明不同地震动之间结构反应越离散。

拟合 λ、β 有两条路线。一条是逐记录提取超限 IM,再对这批数做分布拟合,参数少、评审容易,注释也好写;另一条是先对 ln θ 和 ln IM 做回归,再把回归残差和限值的不确定性合成进 β,适合写论文但工程平台用得少。我一般建议源码主线走第一条,第二条保留成可选模块,标题里这类"源码+注释"项目也通常按第一条组织。

2.2 IDA 结果文件的典型列结构

易损性分析不要求自己写有限元,常见做法是用 OpenSees 跑完增量动力分析(IDA),把结果导出成文本或 CSV,Python 只做下游处理。文件结构通常是三张表:

文件关键字段在源码里的用途
ground_motion.csvrecord_id, pga_g, sa_t1_g每条记录的谱加速度,决定 IDA 缩放档位
ida_summary.csvrecord_id, scale_factor, max_drift_rad单次分析的峰值层间位移角,主输入
out_drift_*.txttime, drift1, drift2, ...recorder 原始输出,供复核和重算

有项目把后两张表合并成宽表,有些还把缩放档位当成新的记录条数,拟合出来的 β 虚高到 0.8 以上,这是源码注释里要专门提醒的一条;反过来,如果只保留每条记录最大档位的反应,容量 IM 的插值就没法做,曲线中位数也会偏大。

2.3 用 dataclass 把三个核心对象装起来

源码里我习惯先用 dataclass 定义数据形态,而不是到处传裸字典;字段名带单位、注释写明含义,拟合和画图模块就共用同一套对象,后面加字段(比如加隔震层反应)时也只用改一处:

from dataclasses import dataclass from pathlib import Path @dataclass class GroundMotion: """一条地震动记录:唯一标识与缩放后的强度指标""" record_id: str pga_g: float = 0.0 # 缩放档位对应的峰值加速度,单位 g sa_t1_g: float = 0.0 # 结构基本周期 T1 处谱加速度,单位 g source_file: Path | None = None # 原始时程文件路径,复核时用 @dataclass class IDARecord: """单条记录在某个缩放档位下的反应结果""" gm: GroundMotion scale_factor: float = 1.0 max_drift_rad: float = 0.0 # 该档位整个时程的最大层间位移角,弧度 converged: bool = True # 非线性分析是否收敛,False 要单独处理

用 dataclass 的好处是可以用 dataclasses.asdict 直接转成 DataFrame 或 JSON,也方便在注释里写明每个字段的单位。max_drift_rad 把单位写进字段名,能避免后面把 rad 和百分数混用,这是易损性分析源码里最常见的隐性 bug 来源。

3. 地震易损性分析的 IDA 数据整理:层间位移角与损伤状态判定

易损性分析只关心每个缩放档位下结构反应的峰值,不关心峰值出现的时刻或地震动中段细节,所以数据处理的第一步是压缩:把整条时程压成一个最大层间位移角,再把它压成一个损伤状态。这一章的所有函数都只做提取与判定,不碰拟合,边界保持干净。

3.1 从 recorder 时程里提取最大层间位移角

OpenSees 的 recorder 输出通常是纯文本,第一列是时间,后面每列是一个楼层或一个单元的位移角。常见做法是先用 pandas 按空白分隔读取,跳过文件头,再对整个数值区取绝对值最大值:

import numpy as np import pandas as pd def extract_max_drift(filepath: str, skiprows: int = 8) -> float: """读取一条时程,返回最大层间位移角(rad)""" raw = pd.read_csv(filepath, sep=r"\s+", skiprows=skiprows, header=None) drift_data = raw.iloc[:, 1:] # 首列是时间 max_drift = float(np.max(np.abs(drift_data.to_numpy()))) return max_drift

skiprows 要按 recorder 输出头的实际行数调,取少了会吞掉数据,取多了直接报错。np.abs 是必要的,层间位移角有正负,取最大绝对值才代表最不利时刻。如果一条文件只记录单层位移角,drift_data 只有一列,这段代码同样成立。用整条时程取峰值而不是用包络值,是避免把静力推覆分析的结果混进易损性样本。

提示:OpenSees 输出默认单位是弧度,损伤状态阈值表里的 0.002 也是弧度,两边一致才能直接比。常见错误是把输出当百分数,阈值按 0.5% 去写,整条曲线会系统性左移。

3.2 损伤状态阈值:一套可改的配置而不是硬编码

四状态划分(轻微、中等、严重、倒塌)在文献里有多种取值,差异主要来自结构类型和规范背景。下面是针对 RC 框架较常见的一组,可作为默认值:

损伤状态层间位移角 (rad)约合表述常见出处/用途
slight0.0021/500开裂初判,接近弹性限值
moderate0.0051/200可修复性界限
extensive0.0151/67与 FEMA 356 的 LS 同量级
collapse0.0401/25倒塌判定常用下限

中国规范体系下,通常把"小震不坏"的弹性层间位移角取 1/550(约 0.0018),把"大震不倒"的弹塑性限值取 1/50(0.02)。前者可以作为 slight 的参考下限,后者用于校核整体稀有水准,不适合直接替换 collapse 阈值。处理这类源码时,阈值要集中放配置文件并在注释里写明出处,因为阈值一动,拟合出的中位数和 β 全变:

DAMAGE_LIMITS = { "slight": 0.002, # 参考 RC 框架经验取值,出处写进注释 "moderate": 0.005, "extensive": 0.015, "collapse": 0.040, }

3.3 判定损伤状态并提取每条记录的容量 IM

有了每条记录的峰值漂移,下一步做两件事:一是给它打上损伤状态标签,用于汇总各状态占比;二是按缩放档位找到首次达到限值时的 IM,作为该记录的容量观测值:

def assign_damage_state(drift: float) -> str: """按字典序逐级比较,返回不超过该限值的最高损伤档位""" for state, limit in DAMAGE_LIMITS.items(): if drift < limit: return state return "collapse" # 超过所有限值才落到倒塌 def capacity_im(sa_levels: np.ndarray, drift_levels: np.ndarray, limit: float) -> float: """在单条记录的 IDA 档位里,线性插值求首次达到限值的 IM""" idx = np.where(drift_levels >= limit)[0] if len(idx) == 0: return np.inf # 到最大档位仍未达到,右截尾 i = idx[0] if i == 0: return float(sa_levels[0]) # 从最小档位就超限,保守取该值 return float(np.interp(limit, drift_levels[i-1:i+1], sa_levels[i-1:i+1]))

assign_damage_state 依赖字典插入序,所以 config 里不要用无序 dict 或在外层重新排序。capacity_im 要求档位严格升序,np.interp 也要求 xp 单调,如果 IDA 曲线在中段出现下降(结构软化后的回复现象),取首次穿越档位而不是末段。返回 np.inf 的记录不能直接丢弃,它是右截尾样本,会在第四章的似然函数里起作用,源码注释里必须把这个约定写清楚。

4. 用 Python 最大似然估计拟合易损性曲线参数并出图

前两章得到的是每条记录、每个损伤状态的容量 IM 集合,第四章把它们拟合成 λ、β,并画出带置信区间的易损性曲线。这一章是源码的数学核心,也是代码注释最密集的部分。

4.1 为什么最大似然估计比直接画经验点更合适

一条常见错误路线是:把记录按容量 IM 排序,画出超限频率和 IM 的散点,再用 polyfit 去配平滑线。这样得到的是经验点而非分布参数,无法外推,也无法给出参数不确定性。容量法下,容量 IM 本身是一组独立观测值,假设服从对数正态分布后,MLE 的闭式解非常干净:

λ̂ = mean(ln x_i),β̂ = std(ln x_i, ddof=1)

闭式解的优点是样本量 10 到 100 时都稳定,注释里也好说明:对容量 IM 取对数,算均值与标准差即可。真正的麻烦来自截尾:如果 IDA 最大档位不够高,部分记录到顶都没达到限值,观测值是 np.inf,这时必须用截尾似然,把"未超限"的信息通过生存函数计入,否则中位数会被系统性低估。

4.2 无截尾与右截尾两套拟合的实现

import numpy as np from scipy import stats, optimize def mle_lognormal(caps: np.ndarray) -> dict: """无截尾时的闭式解拟合, 返回中位数与对数标准差""" caps = np.asarray(caps, dtype=float) valid = caps[np.isfinite(caps)] if len(valid) < 3: raise ValueError("有效容量样本少于 3 条,拒绝拟合") lam = np.log(valid).mean() beta = np.log(valid).std(ddof=1) return {"median": float(np.exp(lam)), "beta": float(beta)} def mle_lognormal_censored(caps: np.ndarray) -> dict: """含右截尾样本的 MLE:np.inf 表示到最大档位仍未达到限值""" caps = np.asarray(caps, dtype=float) observed = caps[np.isfinite(caps)] censored = caps[~np.isfinite(caps)] if len(censored) == 0: return mle_lognormal(caps) def neg_loglik(theta): lam, beta = theta if beta <= 0: return 1e12 ll = stats.norm.logpdf(np.log(observed), lam, beta).sum() ll += stats.norm.logsf(np.log(censored), lam, beta).sum() return -ll x0 = [float(np.log(observed).mean()), 0.4] res = optimize.minimize(neg_loglik, x0, method="Nelder-Mead") return {"median": float(np.exp(res.x[0])), "beta": float(res.x[1])}

第一段代码里,np.isfinite 过滤掉 inf,闭式解直接对 ln(x) 求均值和无偏标准差,ddof=1 是为了小样本下 β 不被低估。第二段是带截尾的似然,censored 数组里的每个值代表"容量 IM 至少大于该档位",因此用 logsf 加上对数生存概率,再把整个负对数似然交给 minimize。初始值 x0 用观测样本的 log 均值,β 起步取 0.4,Nelder-Mead 不依赖梯度,对这类平滑目标足够稳定。

提示:截尾占比超过 20% 时,曲线右尾基本靠外推,评审时要显式说明。遇到这种情况优先回去把 IDA 的最大缩放档位提高,而不是在拟合上反复调参。

4.3 Bootstrap 置信区间与易损性曲线绘图

单次拟合只给一组点估计,工程报告里需要知道中位数的区间。常见做法是对观测样本做 2000 次有放回重采样,每次都重新拟合,取 2.5% 和 97.5% 分位作为 95% 区间:

def bootstrap_ci(caps: np.ndarray, n_boot: int = 2000, seed: int = 42): """自助重采样得到 median 与 beta 的 95% 置信区间""" rng = np.random.default_rng(seed) caps = np.asarray(caps, dtype=float) caps = caps[np.isfinite(caps)] medians, betas = [], [] for _ in range(n_boot): sample = caps[rng.integers(0, len(caps), len(caps))] medians.append(float(np.exp(np.log(sample).mean()))) betas.append(float(np.log(sample).std(ddof=1))) return np.percentile(medians, [2.5, 97.5]), np.percentile(betas, [2.5, 97.5]) def plot_fragility(median: float, beta: float, im_grid: np.ndarray, ax): """画一条标准对数正态易损性曲线, im_grid 单位与 IM 一致""" frac = stats.lognorm(s=beta, scale=median).cdf(im_grid) ax.plot(im_grid, frac, lw=2) ax.set_xlabel("PGA / g") ax.set_ylabel("P(DS ≥ ds | IM)") return frac

画完之后有三个自检:中位数处曲线应穿过 50% 上下;β 落在 0.2 到 0.6 之间,β 小于 0.2 多半是损伤阈值设太低导致所有记录都在低 IM 处集中超限;im_grid 的起点不要取 0,对数正态在 0 处无定义,工程上从 0.05g 起步更实际。

5. 验证这套 Python 地震易损性分析源码的拟合管线

源码发到评审手里之前,先跑三件小事:合成数据冒烟测试、三个数据坑的排查、以及把结果导出成平台能直接用的格式。整套源码依赖就 numpy、scipy、pandas、matplotlib 四个包,装好 Python 后 pip install 一行到位,不需要额外的环境配置。

5.1 用已知参数的合成数据做冒烟测试

from scipy import stats import numpy as np true_median, true_beta = 0.35, 0.45 caps = stats.lognorm(s=true_beta, scale=true_median).rvs(200, random_state=7) res = mle_lognormal(caps) assert abs(res["median"] - true_median) < 0.03 assert abs(res["beta"] - true_beta) < 0.05

如果断言失败,优先查 mle_lognormal 的输入是否被 pandas 解析成整型、或者 dataclass 的字段名被 asdict 改名导致长度不匹配;这类问题在带注释的源码里最常见,示例数据本身往往没问题。

5.2 三个最容易把整条曲线带歪的坑

  • 阈值与单位不匹配:recorder 输出的是弧度,阈值表写的也是弧度,中间只要有一步把其中之一当成了百分数,整条曲线就会整体平移,拟合出的中位数直接失真。
  • 跨记录插值:容量 IM 必须在单条记录内部逐档位插值,把所有记录的所有档位混在一起插值,等于把记录间差异和档位差异混成同一个离散度,β 会虚高到 0.8 以上。
  • 非收敛档位整条删除:IDA 最后一级不收敛往往说明结构已经接近局部失效,把记录整条删掉会让曲线右尾被截短,保守做法是把这个档位的漂移判为超限,让它在拟合里体现出来。

5.3 导出 JSON 给上层风险平台调用

源码的最终交付不只是一张 png 图,建议把拟合参数连同数据来源一起导出成 JSON,上层做灾害损失评估或保险定价的平台拿到后可以直接用:

import json payload = { "im_type": "PGA", "unit": "g", "damage_state": "moderate", "median_g": res["median"], "beta": res["beta"], "model": "lognormal", "n_records": int(len(caps)), "censored_ratio": float(round((caps == np.inf).mean(), 3)), } with open("fragility_moderate.json", "w", encoding="utf-8") as f: json.dump(payload, f, indent=2, ensure_ascii=False)

censored_ratio 是评审最关心的元数据,它直接告诉下游这条曲线的右尾有多少外推成分;平台拿到 JSON 后直接调 scipy.stats.lognorm 的 cdf 就能生成任意强度下的破坏概率,不需要再跑一次 Python 拟合管线。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 13:58:47

迭代与增量开发:核心概念与实战策略解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 13:57:44

openpi 环境搭建:三条命令跑通 Docker 部署与 GPU 配置

openpi 环境搭建&#xff1a;三条命令跑通 Docker 部署与 GPU 配置 【免费下载链接】openpi 项目地址: https://gitcode.com/GitHub_Trending/op/openpi openpi 是 Physical Intelligence 开源的机器人模型&#xff08;VLA&#xff09;工具包&#xff0c;包含 π₀、π…

作者头像 李华
网站建设 2026/9/12 13:57:24

短信与VoIP路由技术解析及实践应用

1. 短信与网络电话路由基础概念 在通信技术领域&#xff0c;SMS&#xff08;Short Message Service&#xff09;和VoIP&#xff08;Voice over Internet Protocol&#xff09;是两种截然不同却又相辅相成的通信方式。短信服务作为移动通信网络的经典功能&#xff0c;已经伴随我…

作者头像 李华
网站建设 2026/9/12 13:55:42

HC-SR501+ESP32人体感应实战:从脏信号到稳定中断的嵌入式入门硬核课

1. 这不是“接个传感器就完事”的入门课——为什么HC-SR501ESP32的组合&#xff0c;是零基础跨入真实嵌入式感知世界的第一个硬核台阶你搜“ESP32教程”&#xff0c;满屏都是点亮LED、串口打印“Hello World”、连WiFi发HTTP请求——这些确实重要&#xff0c;但它们离“让设备真…

作者头像 李华
网站建设 2026/9/12 13:55:24

Android离线OCR实战:ncnn+PP-OCRv5模型部署与优化

要说清楚移动端OCR这事&#xff0c;我折腾过不少方案&#xff0c;最后让我老老实实留在生产环境里的&#xff0c;是 nihui/ncnn-android-ppocrv5 这套项目。它把百度 PP-OCRv5 的模型迁移到了腾讯 ncnn 推理框架上&#xff0c;跑在 Android 端&#xff0c;离线、免授权、CPU/GP…

作者头像 李华