PHAST-SAFETI二次开发教程(09):Safeti™ 与 QRA 原理——事件树、点火概率、暴露与风险度量
版本声明块
- 工具/软件:Phast™ / Safeti™(版本主线 9.x,具体以官方发布说明为准;Safeti™ 与 Phast™ 同版本节奏发布,共用同一后果模型内核)
- 语言/环境:Python ≥ 3.11(用于复现事件树与风险度量的算法骨架);Safeti™ 侧设置以官方 User Manual 为准
- 本文目标:不依赖任何界面,先把 QRA 的数学骨架讲透,再谈怎么在 Safeti™ 里配置
一句话结论:QRA 的核心恒等式是「风险 = Σ 频率 × 后果 × 暴露」——事件树把一次泄漏的起始频率分解为各后果分支频率(同一起始事件下所有分支概率之和恒为 1),Individual Risk 与 LSIR 是空间点的年化死亡概率,FN curve 是死亡人数与累计频率的二维社会风险曲线,而 PLL 是该曲线的期望值(Σ 频率 × 期望死亡人数,单位 死亡/年);三者互为投影,不能互相替代。
本篇铁律适用(三级标注,取自系列登记的 10 条)
- [硬约束] 铁律 8 物理量不臆造:点火概率、人员在场率、脆弱性参数一律取自项目依据文件(法规/客户标准/官方文档),本文只讲结构与算法,不给经验常数。
- [硬约束] 铁律 9 风险标准可追溯:风险度量与判据的来源必须可追。
- [强约束] 铁律 10 能力边界不越界:本篇只讲原理与桌面侧配置,不宣称 Safeti™ 侧具备未经官方证据的公开 API。
- [提示] 铁律 7 改完必验:算法骨架算出的 IR/PLL 需与 Safeti™ 结果回归对齐。
〇、本篇要解决的认知问题
- QRA 由哪些要素构成,与纯后果分析的边界在哪?
- 事件树如何把「一次泄漏的频率」变成「每个后果的频率」,分支概率为什么要归一?
- 点火概率为什么要分「即时」与「延迟」两条腿,9.0 的点火模型是什么?
- 暴露(exposure)在计算里以什么形式进入,在场率与建筑脆弱性各管什么?
- Individual Risk、LSIR、FN curve、PLL 的精确区别与相互关系是什么?
- 风险排序点(RRP)与风险度量是什么关系?
以下「认知问题回显(FAQ)」与上列问题一一对应、同序作答。
一、机制解析
1.1 QRA 的五个要素
| 要素 | 回答的问题 | 在 Safeti™ 中的落点 |
|---|---|---|
| 危险识别与起始事件 | 什么会泄漏、泄漏多大、多频繁 | Study / Scenario 组的频率设定 |
| 事件树 | 泄漏后可能发生什么 | 后果分支与分支概率 |
| 后果 | 每种分支的物理影响 | 复用 Phast™ 后果模型(扩散/火灾/爆炸/毒性) |
| 频率 | 每种后果一年发生几次 | 起始频率 × 分支概率 |
| 暴露与脆弱性 | 人在哪里、在多久、会不会死 | 受体、在场率、建筑脆弱性(OBRA) |
后果分析止于第三步;QRA 的增量正是第四、五步。理解这一点,就理解了「Safeti™ 与 Phast™ 共用同一后果内核」这句话的含义:风险层的差异全部来自频率与暴露,而不是物理模型。
1.2 事件树的结构
┌─ 即时点火 ──▶ 喷射火 / 火球(分支频率 f1) │ 起始泄漏事件 ──┬─ 有泄漏 ───────┼─ 延迟点火 ──▶ 闪火 / 爆炸(分支频率 f2) 起始频率 F0 │ │ │ └─ 未点火 ────▶ 有毒扩散 / 无后果(分支频率 f4) │ └─ 无泄漏 ──────────────────▶ 无后果(分支频率 f3) 归一化约束:f1 + f2 + f4 = F0 (有泄漏分支内) F0 × P(无泄漏) + F0 × P(有泄漏) = F0三个必须写进脚本的归一化断言:
- 同一节点的所有分支概率之和为 1;
- 所有叶节点的后果频率之和等于起始频率
F0; - 同一后果(例如爆炸)若由多条路径到达,其总频率是各路径频率之和——这里最容易犯的错是漏加或重复加。
1.3 点火概率:两条腿
点火概率必须拆成两个独立问题:
| 类型 | 物理含义 | 影响后果 | 官方相关能力 |
|---|---|---|---|
| 即时点火(immediate ignition) | 泄漏瞬间即被点燃 | 喷射火 / 火球 | Safeti™ 场景点火设置 |
| 延迟点火(delayed ignition) | 云团形成后才点燃 | 闪火 / 爆炸 | MISOF / HYEX / Outside plant boundary(9.0 新增,源自挪威 DSB 指南) |
延迟点火的三种方法适用于不同空间语义:场内设施区的点火源分布(MISOF)、更具体的点火源建模(HYEX)、以及厂界之外的点火(Outside plant boundary)。方法选择本身就是一个敏感性维度,应在脚本里作为显式字段管理,而不是埋在一个全局默认值里。
此外要区分「点火概率」与「点燃后的后果类型概率」:延迟点火后是闪火还是爆炸,取决于云团所处位置的阻塞/受限程度,这属几何条件,不属于点火概率。
1.4 暴露与脆弱性
暴露有三层,缺一层结果就会系统性偏移:
- 空间在场率(occupancy fraction):某受体位置一年中有人在场的时间比例;
- 遮蔽与保护:室内、防护墙、避难所对致死概率的折减;
- 脆弱性(vulnerability):给定影响强度下的致死概率(毒性的 probit、热辐射与超压的致死判据)。建筑类型与占用者脆弱性对应官方的OBRA(Occupied Buildings Risk Assessment)能力。
1.5 风险度量的定义与关系
| 度量 | 精确定义 | 单位 | 空间/社会 |
|---|---|---|---|
| Individual Risk (IR) | 特定位置上一个个体因事故致死的年化概率(含场景频率、后果概率与该点致死概率) | 1/年 | 空间点 |
| LSIR | Location-Specific Individual Risk,特定位置处假设个体「始终在场且无保护」的年化致死概率,常绘制为等值线 | 1/年 | 空间场 |
| FN curve | 社会风险曲线:横轴为死亡人数 N,纵轴为死亡人数 ≥ N 的累计频率F | 1/年 | 社会 |
| PLL | Potential Loss of Life,期望年死亡人数 = Σ(后果频率 × 该后果的期望死亡人数) | 死亡/年 | 社会(标量) |
| IRPA | Individual Risk Per Annum,年化个体风险度量(Safeti™ 9.1 起引入的表述) | 1/年 | 空间点 |
| RRP | Risk Ranking Points,用于在任意位置定位风险并识别主要风险贡献 | 辅助量 | 空间点 |
关系要写清三条:
IR(x) = Σ_i f_i × P_fatality(x | 后果 i) (点度量,无保护个体) LSIR(x) = 同式,但固定「始终在场、无保护」的假设个体 (IR 的一个规范化特例) FN: N→F(N)=Σ_{i: N_i ≥ N} f_i (社会度量,累计频率) PLL = Σ_i f_i × N_i ≈ ∫ N dF (FN 曲线的一阶矩/期望)也就是说:LSIR 是 IR 在「始终在场且无保护」假设下的规范形式;PLL 是 FN 曲线的期望值,同一组场景下二者必须自洽(PLL 不可由 IR 直接推出,因为 IR 不含人数)。IR 等值线、社会风险与 PLL 是 Safeti™ 的三类典型结果表达。
二、完整操作与脚本逐段剖析
2.1 第一段:事件树频率分解器(含归一化断言)
# -*- coding: utf-8 -*-# 演示:事件树频率分解。所有概率与频率均为输入量,取自项目依据文件fromdataclassesimportdataclass,field# 标准库:数据结构fromtypingimportList,Dict# 类型标注@dataclassclassBranch:"""事件树的一条分支:条件概率与后果类型。 概率取值必须来自项目依据(法规/客户标准/官方文档),本类不做任何缺省填充。 """name:str# 分支名(如 即时点火)probability:float# 条件概率,须落在 [0, 1]outcome:str# 后果类型(jet_fire / explosion / toxic 等)probability_source:str# 概率来源追溯(铁律 9)@dataclassclassReleaseCase:"""一个起始泄漏场景。"""case_id:str# 唯一键,与后果工况表对齐frequency:float# 起始频率,单位 1/年frequency_source:str# 频率来源(如 失效数据库/法规)branches:List[Branch]=field(default_factory=list)# 分支列表defvalidate(case:ReleaseCase)->None:"""校验概率归一与取值合法性;不通过直接抛错,避免错误静默传播。"""ifcase.frequency<=0:# 频率必须为正raiseValueError(case.case_id+":起始频率必须为正")total=sum(b.probabilityforbincase.branches)# 分支概率合计ifabs(total-1.0)>1e-9:# 归一化硬约束raiseValueError(case.case_id+":分支概率合计="+str(total)+",不为 1")forbranchincase.branches:# 逐分支取值范围ifnot(0.0<=branch.probability<=1.0):raiseValueError(case.case_id+":"+branch.name+" 概率越界")defexpand(case:ReleaseCase)->Dict[str,float]:"""把起始频率分解为各后果类型的总频率(同后果多路径求和)。"""validate(case)# 先校验再分解outcome_freq:Dict[str,float]={}# 后果类型 → 累计频率forbranchincase.branches:# 遍历分支contribution=case.frequency*branch.probability# 分支频率outcome_freq[branch.outcome]=outcome_freq.get(branch.outcome,0.0)+contribution total=sum(outcome_freq.values())# 各后果频率合计ifabs(total-case.frequency)>1e-9:# 守恒断言:合计必须等于起始频率raiseValueError(case.case_id+":后果频率合计与起始频率不符")returnoutcome_freq# 返回 {后果: 频率}# —— 示例装配:一条泄漏场景、三个互斥分支(概率为占位示意,实际须来自依据文件)——demo=ReleaseCase(case_id="LOC-001",# 场景标识frequency=0.02,# 起始频率 1/年(占位值,须替换)frequency_source="项目依据文件-失效频率表",# 来源追溯branches=[Branch("即时点火",0.30,"jet_fire","项目依据-点火概率表"),# 占位概率Branch("延迟点火",0.40,"explosion","项目依据-MISOF 方法"),# 占位概率Branch("未点火",0.30,"toxic","项目依据-未点火比例"),# 占位概率],)print(expand(demo))# 输出各后果频率这段把「QRA 最难对账的部分」变成了可断言的算法:任何概率笔误都会在归一化或守恒断言处立刻暴露。同时每个数值都带*_source字段,满足风险标准可追溯。
2.2 第二段:从后果频率算 IR 与 PLL
# -*- coding: utf-8 -*-# 演示:由分支频率 + 致死概率 + 人数,算 IR 与 PLL,并构造 FN 曲线importmath# 标准库defindividual_risk(outcome_freq:dict,fatality:dict)->float:"""计算某空间点的 Individual Risk(无保护个体、始终在场)。 outcome_freq: {后果类型: 频率 1/年} fatality: {后果类型: 该点致死概率}(来自脆弱性/probit 或致死判据) """returnsum(outcome_freq[outcome]*fatality.get(outcome,0.0)foroutcomeinoutcome_freq)# Σ 频率 × 致死概率deffn_curve(outcome_freq:dict,fatalities:dict)->list:"""构造 FN 曲线:返回按死亡人数降序的 (N, 累计频率 F) 列表。 fatalities: {后果类型: 该后果导致的期望死亡人数 N_i} """events=[]# (N_i, f_i) 事件集合foroutcome,freqinoutcome_freq.items():# 遍历后果n=fatalities.get(outcome,0.0)# 该后果的死亡人数iffreq>0:# 只保留有效事件events.append((n,freq))# 收集events.sort(key=lambdaitem:item[0],reverse=True)# 按人数降序curve,cumulative=[],0.0# 累积频率累加器forn,freqinevents:# 逐事件累积cumulative+=freq# F(N) = Σ f_i, N_i >= Ncurve.append((n,cumulative))# 记录一个折点returncurve# 返回曲线折点defpotential_loss_of_life(outcome_freq:dict,fatalities:dict)->float:"""PLL = Σ 频率 × 期望死亡人数,单位 死亡/年。"""returnsum(outcome_freq[outcome]*fatalities.get(outcome,0.0)foroutcomeinoutcome_freq)# 一阶矩求和defpll_from_fn_curve(events:list,upper_n:float,steps:int=20000)->float:"""由 FN 曲线反算 PLL,验证与 Σ f_i N_i 的一致性。 恒等式:PLL = ∫₀^∞ F(N) dN,其中 F(N) = Σ_{i: N_i ≥ N} f_i(累计频率)。 events:[(N_i, f_i)] 事件列表;upper_n:积分上界(取事件最大人数)。 """dn=upper_n/steps# 积分步长integral=0.0# 累积面积forkinrange(steps):# 中值法逐格积分n=(k+0.5)*dn# 格子中点人数f_n=sum(freqforn_i,freqineventsifn_i>=n)# 该点处的累计频率integral+=f_n*dn# 面积累加returnintegral# 返回积分值defcheck_consistency(events:list,pll:float)->float:"""自洽检查:曲线积分得到的期望值应与直接求和得到的 PLL 一致。"""ifnotevents:# 空集合直接返回 0return0.0upper_n=max(n_iforn_i,_inevents)# 积分上界取最大人数integral=pll_from_fn_curve(events,upper_n)# 曲线积分returnabs(integral-pll)# 返回偏差(应趋近 0)outcome_freq=expand(demo)# 复用上一段的分支频率fatality={"jet_fire":0.9,"explosion":0.7,"toxic":0.2}# 占位致死概率(须来自依据文件,铁律 8)fatalities={"jet_fire":2.0,"explosion":5.0,"toxic":1.0}# 占位人数(须来自受体建模)ir=individual_risk(outcome_freq,fatality)# 计算该点 IRcurve=fn_curve(outcome_freq,fatalities)# 构造 FN 曲线折点pll=potential_loss_of_life(outcome_freq,fatalities)# 计算 PLLprint("IR = %.3e /年"%ir)# 点风险print("FN 曲线折点 =",curve)# 社会风险曲线print("PLL = %.3e 死亡/年"%pll)# 期望年死亡人数events=[(fatalities.get(k,0.0),v)fork,vinoutcome_freq.items()]# 组装 (N_i, f_i) 事件print("PLL 一致性偏差 = %.3e"%check_consistency(events,pll))# 应趋近 0关键纪律:IR 与 PLL 的公式形状相同,但语义不同——IR 对应的是单个个体的致死概率,PLL 对应的是人数规模;把两者混算(例如用人数乘 IR)在数值上可能看似合理,在概念上已经错了。
2.3 第三段:把点火模型作为敏感性维度
# -*- coding: utf-8 -*-# 演示:延迟点火方法(MISOF / HYEX / Outside plant boundary)作为敏感维度做对照importcopy# 标准库:深拷贝,避免污染基准IGNITION_METHODS=["MISOF","HYEX","OUTSIDE_PLANT_BOUNDARY"]# 9.0 官方点火模型方法名defwith_delayed_probability(case,new_probability:float,source:str):"""返回一个把延迟点火概率替换后的新场景,基准场景不变。"""clone=copy.deepcopy(case)# 深拷贝,保证基准可复现forbranchinclone.branches:# 定位延迟点火分支ifbranch.name=="延迟点火":# 按语义名匹配delta=new_probability-branch.probability# 概率增量branch.probability=new_probability# 替换概率branch.probability_source=source# 更新来源追溯# 为维持归一化,把增量从「未点火」分支扣回(示例策略,实际按依据文件规定)forotherinclone.branches:# 寻找互补分支ifother.name=="未点火":# 语义匹配other.probability=other.probability-delta# 保持合计为 1validate(clone)# 归一化校验returnclone# 返回新场景formethodinIGNITION_METHODS:# 逐方法对照# 概率取值须来自项目依据文件;此处仅为结构演示prob={"MISOF":0.40,"HYEX":0.45,"OUTSIDE_PLANT_BOUNDARY":0.35}[method]# 占位值scenario=with_delayed_probability(demo,prob,"项目依据-"+method)# 构造场景freq=expand(scenario)# 频率分解print(method,"→ 爆炸频率 = %.4f /年"%freq.get("explosion",0.0))# 对照打印敏感性归因才是点火模型的正确用法:方法选择不改物理模型,改的是频率分配,因此风险差异必须在频率层解释。
三、常见报错与排查
现象 1:分支概率之和不等于 1,Safeti™ 计算出的总频率与起始频率对不上。
根因:分支集合不完备(漏了「无泄漏」或「未点火」路径),或两条路径重复计入同一结果。
解法:用 2.1 段的validate()与守恒断言作为门禁;先画完整事件树再填概率。
现象 2:把 IR 等值线当成 LSIR 使用,对客户论证时口径不一致。
根因:IR 含在场率与保护假设,LSIR 固定「始终在场且无保护」。二者数值必然不同。
解法:明确报告口径——LSIR 用于场地布局与等值线对比,IR 用于具体人员风险评估;两者都以官方 User Manual 的定义为准。
现象 3:FN 曲线画出来形状怪异,或与社会风险结论矛盾。
根因:纵轴用了单事件频率而非累计频率 F(N ≥ 阈值);或同一后果的多路径频率被漏加。
解法:如 2.2 段按人数降序累积;对同一后果多路径先求和再入曲线。
现象 4:PLL 与 FN 曲线不自洽。
根因:曲线上漏掉了死亡人数为 0 但有频率的事件;或人数取整导致一阶矩偏移。
解法:保留小人数事件在计算内(只在绘图时裁剪);人数使用期望值而非取整值。
现象 5:改了点火模型方法,IR 完全不变。
根因:后果类型未随之重映射(例如延迟点火的后果仍全部记为闪火,而爆炸分支未生成)。
解法:把「点火方法 → 后果类型映射」显式配置化,并断言爆炸频率随方法变化而变。
四、动手练习
- 把
demo的起始频率从 0.02 改为 0.05,验证 IR 与 PLL 按线性比例变化(频率是线性因子)。 - 增加一条「有泄漏但落在无点火源区域」的分支,重新归一化,观察 FN 曲线折点数与 PLL 的变化方向。
- 对同一后果类型构造两条路径(不同起始事件),验证
expand()会把频率相加,并在 FN 曲线上只出现一个更高频的折点。
五、小结与下一篇预告
本篇建立了 QRA 的数学骨架:事件树的归一化与守恒、点火概率的即时/延迟二分(9.0 的 MISOF/HYEX/Outside plant boundary)、暴露的三层结构,以及 IR / LSIR / FN curve / PLL / IRPA 的定义与相互关系,并强调「LSIR 是 IR 的规范化特例、PLL 是 FN 的一阶矩」。下一篇10 Safeti™ 研究设置将把本篇的原理落到工程配置:布局与地图、敏感受体、风险排序点(RRP)布点策略,以及 LSIR/FN/PLL 三类结果的读取与风险贡献分解。
本篇认知问题回显(FAQ)
Q1:QRA 由哪些要素构成,与纯后果分析的边界在哪?
答:五要素:起始事件与危险识别、事件树、后果、频率、暴露与脆弱性。边界在于——后果分析止于「给定场景的物理影响」(Phast™ 侧完成),QRA 在此之上再做两件事:把起始频率按事件树分解为后果频率,再把频率与人员在场/脆弱性相乘得到风险。Safeti™ 与 Phast™ 共用同一后果模型内核,因此风险层的全部差异来自频率与暴露假设,而非物理模型。
Q2:事件树如何把一次泄漏的频率变成每个后果的频率,分支概率为什么必须归一?
答:分支频率 = 起始频率 × 该分支的条件概率;同一后果若由多条路径到达,则其总频率为各路径频率之和。必须归一的原因是对账:同一节点所有分支概率之和为 1,等价于所有叶节点频率之和等于起始频率,这是频率守恒的充要表达;一旦不归一,后续 IR/FN/PLL 全部偏移,且难以定位。
Q3:点火概率为什么要分即时与延迟两条腿,9.0 的点火模型是什么?
答:因为两者导致的后果截然不同——即时点火直接形成喷射火或火球,延迟点火在云团形成后点燃,可能形成闪火或爆炸,且后者的后果类型还取决于云团所在位置的阻塞/受限程度。Safeti™ 9.0 新增的点火模型方法为 MISOF 与 HYEX,以及厂界之外点火(Outside plant boundary),官方口径为基于挪威 DSB 指南;方法选择属敏感性维度,应显式配置而非依赖默认值。
Q4:暴露以什么形式进入计算,在场率与建筑脆弱性各管什么?
答:三层进入。空间在场率(occupancy fraction)回答「人在不在」,作为乘性因子作用在致死概率上;遮蔽与保护(室内、防护墙、避难所)对致死概率做折减;脆弱性(probit 或致死判据)回答「给定强度下会不会死」。建筑类型与占用者脆弱性对应官方的 OBRA 能力,用于建筑选址与占用建筑风险评价。
Q5:IR、LSIR、FN curve、PLL 的区别与相互关系是什么?
答:IR 是特定位置上一个个体的年化致死概率(1/年),是空间点度量;LSIR 是同一位置在「个体始终在场且无保护」这一固定假设下的年化致死概率,因此是 IR 的一个规范化特例,常用于等值线与平面布局比较;FN curve 是横轴死亡人数、纵轴累计频率(死亡人数 ≥ N 的频率)的社会风险曲线;PLL 是期望年死亡人数(Σ 频率 × 期望死亡人数,单位 死亡/年),即 FN 曲线的一阶矩。彼此关系可概括为:LSIR 是 IR 的规范形式,PLL 是 FN 的期望值,IR 与 PLL 公式同形但语义不同(个体 vs 人数规模)。
Q6:风险排序点(RRP)与风险度量是什么关系?
答:RRP 是 Safeti™ 中用于在任意位置定位风险并识别主要风险贡献的功能,它本身不是一种独立的风险度量,而是对风险度量做空间采样与归因的手段:在关注位置上把总风险分解为各场景/各后果的贡献,从而回答「这个点的风险主要来自哪里」。因此 RRP 常与 IR 等值线配合使用——等值线给空间格局,RRP 给贡献结构。