FRI 与 KG-TOWER 二次开发教程(04):水力学数据模型——塔段、内件、工况、结果四层对象与单位制

📅 发布时间:2026/10/1 11:01:32
FRI 与 KG-TOWER 二次开发教程(04):水力学数据模型——塔段、内件、工况、结果四层对象与单位制
FRI 与 KG-TOWER 二次开发教程04水力学数据模型——塔段、内件、工况、结果四层对象与单位制版本与事实声明环境Python 3.8dataclasses自 3.7 起为内置仅标准库即可运行。数据模型中的内件特性比表面、空隙率、Fp取自 Perry’s 8th ed. Table 14-13 公开表关联式沿用系列弹药库 E1/E2/E5。本文不涉及 FRI/KG-TOWER 的任何 API两者均无公开 API凡涉及软件数据面只讨论契约与字段不猜测其内部结构底账 U3/U4。文中任何数值为示例性建模不代表任何标准规定不对应任何真实装置数据。KG-TOWER 版本口径v5.4.x以官方当前发布为准。一句话结论水力学核算不是一个函数而是一组契约——用Bag塔段—Internals内件—Case工况—Result结果四层 dataclass固化输入输出用UnitSystem显式承载 SI 与英制并存Fp 的 ft⁻¹、压降的 in H₂O/ft用塔段ID内件ID工况哈希三元组做工况唯一键才能让第 11 篇的批量扫描和第 12 篇的判据落盘不变成互相打架的散装脚本。〇、本篇要解决的认知问题Q1为什么水力学核算不能一个函数算到底而必须先建数据模型Q2塔段、内件、工况、结果这四层各自承载什么边界怎么划Q3英制与 SI 并存是硬现实数据模型里应该怎么表达单位制Q4批量核算时如何保证每一行结果都能被唯一回溯到哪条第几次工况Q5结果层应该有哪些字段才能支撑第 12 篇的裕度判定与第 18 篇的报表交付一、机制解析1.1 为什么这对你重要核算的复杂度不在公式在组合为什么这对你重要一位工程师做一次手算输入大概十几个数而一个批量核算平台要处理的是若干塔段 × 若干内件方案 × 若干工况 × 若干物性的组合动辄上万次计算。这时候公式是否优雅已不重要输入契约是否稳定才是生死线。第 03 篇我们已经体会到单位口径一错结果差 2.26 倍而单位错在批量场景里会被复制上万次规模把错误放大。所以先把数据的形状定下来。四层模型如下┌─────────────────────────────┐ │ Bag塔段几何功能定位 │ 塔径候选、板间距/填料层高、 │ │ 液流安排、段顶/段底物性引用 └──────────────┬──────────────┘ │ 1..n ┌──────────────▼──────────────┐ │ Internals内件方案属性 │ 板型/填料型号尺寸、a、ε、Fp、 │ │ 开孔率、堰高、降液管面积… └──────────────┬──────────────┘ │ × ┌──────────────▼──────────────┐ │ Case工况负荷物性 │ 气相/液相质量流、ρ、μ、σ、 │ │ 操作压力/温度、负荷系数 └──────────────┬──────────────┘ │ → ┌──────────────▼──────────────┐ │ Result结果判据与裕度 │ 压降、泛点率、降液管背压、 │ │ F 因子、裕度、来源与单位集 └─────────────────────────────┘四层的划分原则是**“变化的频率”**塔段几何很少变→ 内件方案按方案变→ 工况按点变→ 结果每次算都变。把变化频率不同的东西分开扫描时你只需要重组 Case 层其余两层被复用——这是第 11 篇扫描性能的基础。1.2 单位制不是注释问题是一等公民本领域同时存在四套压降单位in H₂O/ft、mm H₂O/m、Pa/m、kPa/m和两套填料因子单位ft⁻¹、m⁻¹。把单位写进注释是远远不够的因为注释不会参与运算。正确做法是在模型里携带单位标签让每个量都知道自己是什么量本系列内部口径说明密度 ρkg/m³SI黏度 μPa·s液相常给 mPa·s输入时换算SI表面张力 σN/m工程常给 dyn/cm1 dyn/cm 1e-3 N/mSI气/液质量流速kg/(m²·s)SI填料因子 Fpm⁻¹与 Perry 表一致用 E2 时在函数内换算 ft⁻¹压降Pa/m对外展示可换算 in H₂O/ft内部统一 SI铁律 5 的落地方式模型内部只存 SI所有英制输入在构造 Case 时立刻换算成 SI所有英制展示在输出层换算。这样能消掉 90% 的单位 bug——因为混乱只允许存在于边界不允许渗透到内核。1.3 工况唯一键可回溯性的根批量核算最常见的灾难不是算错而是算对了但不知道这行结果属于哪次输入。解决方案是给每个 Case 一个确定性唯一键case_id由塔段ID 内件ID 工况哈希构成。工况哈希 对全部输入字段做规范化后取短哈希。case_id f{bag_id}__{internals_id}__{hash8(case_payload)} 例: BAG-01__INT-IMTP40__a1b2c3d4为什么用哈希而不是自增序号自增序号在你的脚本改了扫描顺序后就会错位哈希是内容寻址——同样的输入永远得到同样的键因此断点续扫第 11 篇可以安全跳过已算过的工况幂等重跑也不会产生重复行。1.4 结果层字段为判据而不是为展示设计结果层的字段要直接服务于判据第 12 篇与报表第 18 篇。最小可用字段集字段含义服务对象case_id工况唯一键回溯、幂等dP_total总压降Pa/m 或 Pa/段压降裕度flood_pct泛点率%容量判据u_flood / u_op泛点气速 / 操作气速容量判据f_factorF 因子对标、经验法则dc_backup_pct降液管背压占板间距比塔板塔板液泛判据weep_margin漏液裕度塔板操作下限判据method关联式编号E1/E2/Ergun…可审计units单位集标签可审计warnings适用边界告警如 Fp60质量门禁注意method、units、warnings三列的存在本身就是设计它们让结果的出处成为数据的一部分而不是留在某个工程师的记忆里。这是与可复现、可审计铁律 8直接对齐的。1.5 单位制在跨软件场景的延伸数据模型的单位制设计在跨软件场景下会立刻显出价值。设想你同时从三个来源拿数据流程模拟软件导出的级次负荷常带 kPa、mPa·s、mN/m、填料厂商手册给出的 Fpft⁻¹ 或 m⁻¹ 口径不定、以及人工录入的塔径与板间距m 与 mm 混用。如果三处都按约定用 SI你几乎必然在某处漏换算而如果每个来源都必须过一道边界换算函数并在列名/字段名上内嵌单位标签则错误会在读入阶段暴露而不是在算完之后以看着像对的结果浮现。为什么这对你重要工程上最难查的错误不是报错的错误而是不报错但错得合理的错误——比如把 mPa·s 当 Pa·s 传进去效率会被估低而不是估高落在一个保守但正常的区间被所有人接受。单位制作为一等公民的意义就是把这类错误从事后发现提前到入口拦截。1.6 从注释里的约定到结构里的约束注释会过期结构不会。三条例子的威力差异极大注释写此处单位为 SI最弱靠记性、变量名写muL_pa_s较强靠命名、类型/构造器强制换算最强靠结构。本系列推荐组合是变量名带单位 构造器强制换算 入口量级守卫三层叠加——它们分别对应人能读到“调用方躲不开”“异常数据被拦下”。二、完整代码与逐行剖析代码 2-1四层数据模型与单位换算可直接运行# -*- coding: utf-8 -*- model.py —— 水力学核算四层数据模型 单位制载体 用 dataclass 固化契约内部统一 SI英制只在边界换算。 fromdataclassesimportdataclass,field,asdictfromtypingimportOptional,List,Dictimporthashlib,json# ---------- 单位换算英制/SI 边界 ----------INH2O_PER_FT_TO_PA_PER_M817.0FT_INV_PER_M_INV3.28084deffp_m_to_ft(fp_m_inv:float)-float:returnfp_m_inv*FT_INV_PER_M_INVdefdP_pam_to_inh2o_per_ft(dp_pam:float)-float:returndp_pam/INH2O_PER_FT_TO_PA_PER_Mdefmpa_s_to_pa_s(mu_mpa_s:float)-float:returnmu_mpa_s*1e-3# ---------- 第 2 层内件 ----------dataclass(frozenTrue)classInternals:kind:str# packing | traymodel:str# 型号名如 IMTP#40、sieve-1passa:Optional[float]None# 比表面 m^2/m^3填料eps:Optional[float]None# 空隙率填料fp_m_inv:Optional[float]None# 填料因子 m^-1与 Perry 表一致# 塔板字段可选tray_spacing_m:Optional[float]Nonehole_area_ratio:Optional[float]Noneweir_height_m:Optional[float]Nonedefvalidate(self):ifself.kindpacking:forname,vin((a,self.a),(eps,self.eps),(fp_m_inv,self.fp_m_inv)):ifvisNone:raiseValueError(f填料{self.model}缺少字段{name})ifnot(0.0self.eps1.0):raiseValueError(f空隙率越界:{self.eps})# ---------- 第 1 层塔段 ----------dataclassclassBag:bag_id:strservice:str# rectifying | stripping | absorber ...diameter_m:floatheight_m:float# 填料层高 / 塔板数×板间距internals_id:strdefvalidate(self):ifself.diameter_m0orself.height_m0:raiseValueError(塔段几何必须为正)# ---------- 第 3 层工况 ----------dataclassclassCase:rhoG:float;rhoL:float# kg/m^3muG:float;muL:float# Pa·ssigma:float# N/mG_ms:float;L_ms:float# kg/(m^2 s)P_Pa:float101325.0T_K:float298.15hL:Optional[float]None# 运行持液量填料声明输入defpayload(self)-Dict:用于哈希的规范化载荷字段排序、浮点四舍五入保证同输入同键dasdict(self)return{k:(round(v,6)ifisinstance(v,float)elsev)fork,vinsorted(d.items())}# ---------- 第 4 层结果 ----------dataclassclassResult:case_id:strdP_total_pam:floatflood_pct:Optional[float]Noneu_flood:Optional[float]Noneu_op:Optional[float]Nonef_factor:Optional[float]Nonemethod:strunits:strSI(dPPa/m; Fp inputm^-1)warnings:List[str]field(default_factorylist)# ---------- 工况唯一键 ----------defmake_case_id(bag_id:str,internals_id:str,case:Case)-str:rawjson.dumps({bag:bag_id,int:internals_id,case:case.payload()},ensure_asciiFalse,sort_keysTrue)hhashlib.sha1(raw.encode(utf-8)).hexdigest()[:8]returnf{bag_id}__{internals_id}__{h}if__name____main__:intsInternals(kindpacking,modelIMTP#40,a151.0,eps0.97,fp_m_inv79.0)ints.validate()bagBag(bag_idBAG-01,servicerectifying,diameter_m1.2,height_m6.0,internals_idints.model)bag.validate()caseCase(rhoG2.5,rhoL780.0,muG1.2e-5,muLmpa_s_to_pa_s(0.30),sigma0.02,G_ms6.0,L_ms12.0,hL0.05)print(case_id:,make_case_id(bag.bag_id,ints.model,case))print(Fp(m^-1 - ft^-1):,f{ints.fp_m_inv}-{fp_m_to_ft(ints.fp_m_inv):.2f})print(dP 100 Pa/m - inH2O/ft:,f{dP_pam_to_inh2o_per_ft(100.0):.4f})实测输出case_id: BAG-01__IMTP#40__b20d8139 Fp(m^-1 - ft^-1): 79.0 - 259.19 dP 100 Pa/m - inH2O/ft: 0.1224逐段剖析dataclass(frozenTrue)用在Internals内件方案是值对象一旦确定就不该被就地修改。不可变对象让方案复用天然安全——扫描时把同一个Internals实例喂给上千个工况不必担心某处代码偷偷改了它。validate()方法而不是__post_init__把校验做成显式调用便于在批量入口处一次性校验所有对象并汇总错误而不是在构造第一个对象时就被异常打断。Internals的校验还额外卡住0eps1——空隙率越界是后续 Ergun 崩掉的直接原因。Case.payload()里round(v, 6)sorted浮点四舍五入是哈希稳定性的关键。否则 3.6000000001 和 3.6 会生成两个不同的 case_id断点续扫就会出现重复计算。字段排序则保证字典序无关。make_case_id()用 SHA-1 截 8 位内容寻址。示例输出BAG-01__IMTP#40__b20d8139里三段各司其职——人能读塔段与内件机器用哈希判等。反直觉提醒fp_m_to_ft(79.0)打印出259.1979 × 3.28084这是维度正确的换算——1 m⁻¹ 就是 3.28084 ft⁻¹。但第 03 篇已经看到把 79 严格当 m⁻¹ 换算到 259.2 ft⁻¹ 后E2 给出的ΔPflood\Delta P_{flood}ΔPflood​是 5.871 in H₂O/ft越出物理常识区间而把表中的 85 直接当 ft⁻¹ 读才得到合理的 2.690 in H₂O/ft。这正说明换算方向不能靠猜模型里必须只存一种口径本系列统一存 m⁻¹而某个填料库的 Fp 到底按哪种口径发布必须与内件库/软件交叉核对——这是铁律 8常数必须写明出处的具体落点。代码 2-2单位制边界断言把内部统一 SI变成可执行约束# -*- coding: utf-8 -*-unit_guard.py —— 内部统一 SI 的可执行约束defassert_si(rhoG,rhoL,mu,sigma,G_ms):checks[(rhoG,rhoG,0.001,500.0),# 真空到高压气相密度量级(rhoL,rhoL,200.0,2000.0),# 常见液相密度量级(mu,mu,1e-6,1e-1),# 气体到高黏液(sigma,sigma,1e-3,0.1),# 1~100 mN/m(G_ms,G_ms,0.01,100.0),# 表观质量流速量级]bad[f{n}{v}超出量级 [{lo},{hi}]forn,v,lo,hiinchecksifnot(lovhi)]returnbadif__name____main__:badassert_si(2.5,780.0,0.0003,0.02,1.2)print(通过ifnotbadelsebad)bad2assert_si(2.5,780.0,0.3,0.02,1.2)# 误把 mPa·s 当 Pa·sprint(bad2)逐段剖析assert_si用量级区间而不是等值判断——因为工程上很多错误是数量级错误把 mPa·s 当 Pa·s、把 dyn/cm 当 N/m、把 kg/h 当 kg/s。运行后第一行输出通过第二行会揪出mu0.3超界。这一段小守卫能挡掉批量核算里最常见的一类系统性偏差。三、常见报错与排查报错 3-1同一组输入两次运行生成了不同的 case_id。现象断点续扫认为工况没算过重复计算。根因payload()未做浮点规范化或字典未排序导致序列化顺序不稳定。解法保留round(v, 6)与sort_keysTrue若输入来自 CSV注意读入时字符串→浮点的表示差异建议在入口统一float()并四舍五入。报错 3-2ValueError: 填料 IMTP#40 缺少字段 fp_m_inv。现象构造Internals后校验失败。根因不同内件类型字段集不同塔板内件没有a/eps/fp_m_inv。解法按kind分派校验把塔板必填字段和填料必填字段分成两组避免用一个通用校验器误杀另一种内件。报错 3-3结果里 flood_pct 是 300%但 warnings 是空的。现象异常结果没有告警。根因Result.warnings只在调用方手动填时才非空缺少自动化告警生成。解法在引擎侧集中生成告警——单位越界、Fp60 例外、泛点率110%、降液管背压板间距等一律写入warnings并把warnings非空作为质量门禁第 16 篇。报错 3-4TypeError: unhashable type或直接TypeError: cannot assign to field。现象想就地修改冻结的Internals或把它塞进set。根因frozenTrue禁止赋值set要求可哈希。解法方案变更应新建对象而非原地改若确需集合去重用dataclasses.replace生成新实例或改用(model, a, eps, fp_m_inv)元组做键。报错 3-5把 Case 的物性留在英制如 ρ 用 lb/ft³就喂进了引擎。现象Ergun 算出离谱的压降。根因模型内部约定 SI但数据来源老数据表、人工录入常用英制。解法所有入口CSV 读取、GUI 输入、软件导出解析都必须经过边界换算层且用assert_si做量级守卫——这是铁律 5 在代码里的最后一道闸。四、动手练习练习 1跑通模型运行代码 2-1。判定输出含case_id: BAG-01__IMTP#40__开头的一行Fp(m^-1 - ft^-1): 79.0 - 259.19dP 100 Pa/m - inH2O/ft: 0.1224容差 ±0.5%。练习 2哈希稳定性把case.L_ms从 12.0 改成 12.0000001 再改回 12.0观察 case_id。判定两次运行 case_id 完全一致因为round(v,6)说明哈希对浮点噪声稳定。练习 3单位守卫运行代码 2-2。判定第一行输出通过第二行输出的列表包含mu0.3 超出量级。练习 4字段设计给你的结果层补三个字段提示可从液泛类型标注 flood_type“效率估算 efficiency”对标软件结果 software_ref中选并说明每个字段服务哪条判据。判定三个字段均写出且每个都指明服务的判据或报表用途无为了好看的字段。五、小结与下一篇预告本篇把核算从公式升级成契约四层 dataclassBag/Internals/Case/Result按变化频率分层内部统一 SI、换算只在边界铁律 5 的结构化落地case_id 用内容寻址保证可回溯与幂等结果层自带 method/units/warnings 三列保证可审计。有了这套契约第 11 篇的扫描与第 12 篇的判据才能真正长得起来。第 05 篇《塔板水力学机制》我们转到塔板这一侧讲透泛点、液泛、压降、持液量与传质效率的物理内核——三类泛点降液管液泛、雾沫夹带泛点、泡沫夹带泛点如何区分板压降如何拆成四项降液管背压怎么成为塔板最大负荷的控制因素以及 O’Connell 效率如何把手算板数与真实板数联系起来。本篇认知问题回显FAQQ1为什么水力学核算必须先建数据模型而不能一个函数算到底A因为工程上面的复杂度不在公式而在组合一个平台要处理若干塔段×若干内件方案×若干工况×若干物性的组合可达上万次计算。若没有稳定的输入契约单位口径等错误会被规模放大如 Fp 单位错一档结果差 2.26 倍且结果无法回溯到具体输入。四层模型Bag/Internals/Case/Result按变化频率分层让扫描只需重组 Case 层。Q2塔段、内件、工况、结果四层各承载什么边界怎么划ABag塔段承载几何与功能定位塔径、板间距/填料层高、液流安排、段顶段底物性引用Internals内件承载方案属性板型或填料型号尺寸、比表面 a、空隙率 ε、填料因子 Fp、开孔率、堰高、降液管面积Case工况承载负荷与物性气液质量流、ρ、μ、σ、压力温度Result结果承载判据与裕度压降、泛点率、降液管背压、F 因子、method、units、warnings。边界按变化频率划分。Q3英制与 SI 并存数据模型里应该怎么表达单位制A做法是内部统一 SI、换算只在边界。模型内部只存 SIρ 用 kg/m³、μ 用 Pa·s、σ 用 N/m、质量流速用 kg/(m²·s)、Fp 统一存 m⁻¹ 与 Perry 表一致、压降统一 Pa/m所有英制输入在构造 Case 的边界层立刻换算所有英制展示在输出层换算。再用量级守卫如 μ 在 1e-6~1e-1 Pa·s拦截把 mPa·s 当 Pa·s这类数量级错误。Q4批量核算时如何保证每行结果都能唯一回溯A用确定性内容寻址的工况唯一键case_id f{bag_id}__{internals_id}__{hash8(case_payload)}其中 case_payload 对全部输入字段排序并做浮点四舍五入round 6 位后取短哈希。内容寻址保证同输入同键因此断点续扫可安全跳过已算工况、幂等重跑不产生重复行而自增序号在改变扫描顺序后会错位不可用。Q5结果层应该有哪些字段才能支撑裕度判定与报表交付A最小可用集为case_id回溯与幂等、dP_total压降裕度、flood_pct 与 u_flood/u_op容量判据、f_factor对标与经验法则、dc_backup_pct塔板降液管液泛判据、weep_margin塔板操作下限判据、method关联式编号可审计、units单位集标签可审计、warnings适用边界告警作质量门禁。其中 method/units/warnings 的存在让结果出处成为数据的一部分。