ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AFT二次开发教程(08):Scenario Manager 深潜——父/子场景、继承链与场景路径名

AFT二次开发教程(08):Scenario Manager 深潜——父/子场景、继承链与场景路径名 AFT二次开发教程08Scenario Manager 深潜——父/子场景、继承链与场景路径名版本与事实声明产品与版本AFT Fathom 15 / AFT Impulse 12当前本篇术语与机制引用官方Fathom 13帮助页Scenario Manager与相关页正文当前版以官方文档为准。语言/环境Python 3.x标准库。本文目标读完能设计出可扩展、可寻址、可对账的场景树并说清继承与对比的边界。所有场景名与编号为示例性不代表任何真实项目。一句话结论AFT 的Scenario Manager把模型的多个变体保存在同一个模型文件里场景构成一棵树根是Base Scenario可在其上再插新根属性值来自祖先即标记为linked并跨代继承当前场景由绿色对勾与状态栏双重标识、有输出的场景文字自动变蓝、场景备注向下传播跨场景批量变更则必须用全限定 Scenario Path Name官方示例Base Scenario\US Units\Pump A——场景树设计得好不好直接决定你后面几十个工况能不能管住。〇、本篇要解决的认知问题Q1Scenario Manager 到底把多个工况存放在哪里为什么它比多个文件强Q2什么是 Base Scenario为什么可以在 Base 上面再插一个场景这个能力很重要Q3linked 属性与跨代继承是怎么回事它什么时候会咬你一口Q4怎么一眼看出哪些场景有输出、哪个是当前场景Q5Scenario Path Name 为什么必须全限定它与场景对比工具是什么关系一、机制解析8.1 场景存哪里一个文件装所有工况官方总览页的第一句就给了结论性事实“All scenarios are saved within the model file.”所有场景都保存在模型文件内。这一条推翻了很多人一个工况存一个.fth的直觉。它的工程价值在于维度一工况一文件同文件多场景版本管理N 个文件各自演化1 个文件单一真相源公共参数改动改 N 次改一次继承到子代对比能力无官方工具Scenario Comparison Tool输出管理靠文件命名场景树 变蓝标记批跑靠Models from Different FilesScenarios in Current Model更省事8.2 Base Scenario 与可在其上加根官方的定义原文要点Scenario场景在 Scenario Manager 里创建的一个模型变体工况。Base Scenario基础场景场景树最顶端的根场景作为最高层祖先它不从任何场景继承。新建的单场景模型默认就叫Base Scenario。一个关键能力“A new scenario can be inserted above the base scenario”——可以在 Base 之上再插一个新场景此时新场景成为新的 Base而原 Base 变成它的子场景。为什么这条重要工程解读它意味着你可以在项目中途抬高树根。典型场景项目开始时只有一个默认 Base后来发现需要英制版 / 公制版两套单位制于是插入一层US Units / Metric作为新根。需求是演化出来的这个能力让你不必推倒重建树结构——第 03 篇的base_path用Base Scenario\US Units正是这个用法。8.3 linked 属性与跨代继承最容易咬人的机制官方原文的关键句Those attributes whose value comes from a different scenario arelinked, while those values coming from the selected scenario arenot linked. Linked attributes willinherit changes made in the same object and attribute of the linked ancestor. This inheritance will occurover as many generations as the link exists.拆开讲一个对象的某个属性其值若继承自某个祖先这个属性就是linked链接的若值是在本场景里设定的则不链接链接属性的继承是同对象、同属性维度上的——祖先改了P3 的 Fixed Speed所有该属性链接到该祖先的后代一起变继承沿链一直传下去as many generations as the link exists不是只传一代。实践含义——两个用例与两个陷阱用例收益把系统共通设定如流体物性、管径基准、单位制放在根/父场景子场景只覆盖本工况要变的那几个属性。这样你要改共通设定时只改一处。陷阱 1无意联动你以为某个子场景自己设了某值其实它是linked的——父场景一改你的子场景被悄悄改掉。批量场景调试时最怕这个。陷阱 2想独立却还连着反过来你希望子场景独立但它仍链接到父——这时必须在子场景里显式重设该属性本场景设定即解除链接。工程纪律最佳实践场景树要分层归因——根层放全局不变项流体、单位制、通用系数父层放工况族共同项叶层放该工况真正的差异项。这样 linked 的范围就可预期。乱序建树的团队一定会在某次父场景改动后收到一堆结果为什么变了的疑问。8.4 当前场景与变蓝肉眼 QA 两件宝官方给的两条状态提示当前current场景在 Workspace 显示场景树里以绿色对勾标记同时在 AFT 的Status Bar里显示有输出的场景文字颜色自动变蓝“See what scenarios have output by automatically changing the text color to blue if they have output”。为什么变蓝是无价之宝批跑前扫一眼场景树没变蓝 没输出批跑后同样一眼确认是否全跑完。这把我到底跑全了没从靠记忆/靠日志变成靠颜色——最便宜、最不易出错的 QA。提醒官方在多场景输出相关页也说明多场景输出需要在模型文件内生成输出配合.out输出文件否则多场景输出面板可能看不到某些场景的数据第 12 篇展开。8.5 场景路径名全限定才能寻址官方在 Excel 导入页给的定义与示例Scenario Path Name场景路径名变更要应用到的场景无场景时作用于当前场景重要场景名必须唯一限定——若有重名场景必须用全限定路径名示例为Base Scenario\US Units\Pump A怎么拿在 AFT Fathom 里右键场景名 → “Copy Scenario Path Name”。为什么必须全限定场景树是层级的重名是常态不同父下都叫Pump A。全限定路径是唯一寻址手段也是让变更表在几百行规模下指哪打哪的唯一办法铁律 5——第 05 篇read_change_log.py里is not unique那条错误就是漏了全限定。8.6 Scenario Comparison Tool官方的多方案比选官方另有Scenario Comparison Tool与Scenario Comparison Grid把多个场景的输入/输出并排放在一起看。它和你的 Python 聚合脚本是分工关系GUI 工具适合少场景 人工研判选型会上现场比Python 聚合第 12 篇适合几十上百场景 进数据库/报表。注意官方还提到Export Model Data导出为Preformatted Report生成的.inp文本可用于文本对比找差异作用类似 Scenario Comparison Tool——这是一条无 GUI 的差异对比路径适合放进自动化的回归测试第 20 篇。二、完整代码与逐行剖析代码 8-1scenario_tree.py场景树模型 路径规范化 寻址# -*- coding: utf-8 -*- scenario_tree.py —— 场景树的分层建模、路径规范化与全限定寻址。 把场景名升级为可寻址键避免重名导致变更打错场景铁律5。 运行python scenario_tree.py --selftest importsys SEP\\# AFT 场景路径分隔符官方示例Base Scenario\\US Units\\Pump AclassScenarioTree:def__init__(self,rootBase Scenario):self.nodes{root:None}# 路径 - 父路径根为 Noneself.notes{}# 路径 - 备注父场景备注可向下传播defadd(self,parent:str,name:str,note:str)-str:在 parent 下加子场景返回其全限定路径parent 必须已存在。ifparentnotinself.nodes:raiseKeyError(f父场景不存在{parent})pathparentSEPnameifpathinself.nodes:raiseValueError(f场景已存在重名{path})self.nodes[path]parent self.notes[path]notereturnpathdefchildren(self,path:str)-list:returnsorted(pforp,parinself.nodes.items()ifparpath)defpath_name(self,path:str)-str:规范化统一分隔符、去首尾空白保证变更表里写的路径与树一致。parts[p.strip()forpinpath.replace(/,SEP).split(SEP)ifp.strip()]returnSEP.join(parts)defresolve(self,name_or_path:str)-list:解析一个短名或路径返回匹配的全限定路径列表1 表示有歧义。tgtself.path_name(name_or_path)iftgtinself.nodes:return[tgt]returnsorted(pforpinself.nodesifp.split(SEP)[-1]tgt)defleaf_paths(self)-list:叶场景无子——批跑通常只勾叶场景。returnsorted(pforpinself.nodesifnotself.children(p))def_selftest():tScenarioTree(Base Scenario)ust.add(Base Scenario,US Units)at.add(us,Pump A)t.add(us,Pump B)assertarBase Scenario\US Units\Pump A# 全限定路径格式# 重名必须报错try:t.add(us,Pump A)raiseAssertionError(应拒绝重名)exceptValueError:pass# 路径规范化正斜杠/多余空白也能归一assertt.path_name(Base Scenario//US Units/ Pump A )a,t.path_name(Base Scenario//US Units/ Pump A )# 短名歧义Pump A 只此一处唯一assertt.resolve(Pump A)[a],t.resolve(Pump A)# 叶场景assertt.leaf_paths()sorted([a,rBase Scenario\US Units\Pump B]),t.leaf_paths()print(SELFTEST OK全限定路径、重名拒绝、路径规范化、歧义解析、叶场景识别全部正确。)if__name____main__:if--selftestinsys.argv:_selftest()逐行剖析SEP \\与官方示例一致AFT 场景路径用反斜杠。单独抽常量避免各处转义写法不一。add()必须在父存在时才能加子并对同父下重名直接抛错——这就是把铁律 5 的场景名必须唯一限定做进数据结构。让非法状态建不出来比事后检测强。path_name()做规范化把/统一成\、去空白、删空段。真实场景里变更表可能被不同人用不同分隔符写出来规范化是寻址前必做的一步。resolve()返回列表因为短名可能对应多个场景歧义返回 1 个元素时就是调用方必须改用全限定路径的信号。用类型系统表达可能歧义而不是让它悄悄猜一个。leaf_paths()返回叶场景批跑时通常只勾叶场景中间层是工况族的抽象不单跑。这条经验法则能让你少跑一堆无意义的中间场景。注意类里没有把notes写进add()的临时判断而是在__init__里统一初始化——对象的全部状态要在构造时就位避免第一次调用才创建属性这种隐性顺序依赖。代码 8-2inherit_audit.py继承归因审计的骨架# -*- coding: utf-8 -*- inherit_audit.py —— 继承归因审计记录每个叶场景的哪些属性是自设、哪些是继承。 由于 AFT 不公开模型文件结构铁律6本脚本以人工填写的归因表为输入 做一致性校验继承项必须在祖先里存在对应设定项否则告警。 输入 JSON: {nodes: {...}, assignments: {path|object|param: self|inherit:path}} 运行python inherit_audit.py --selftest importsysdefaudit(data:dict)-list:problems,nodes[],set(data.get(nodes,{}))assigndata.get(assignments,{})forkey,srcinassign.items():pathkey.split(|)[0]ifpathnotinnodes:problems.append(f归因项指向不存在的场景{path})continueifsrc.startswith(inherit:):ancestorsrc.split(:,1)[1]ifancestornotinnodes:problems.append(f{key}继承自不存在的祖先{ancestor})# 祖先必须真的设了同一对象的同一属性否则继承无从谈起eliff{ancestor}||.join(key.split(|)[1:])notinassign:problems.append(f{key}声称继承自{ancestor}但祖先未设该属性)returnproblemsdefselftest():data{nodes:[rBase Scenario,rBase Scenario\US Units,rBase Scenario\US Units\Pump A],assignments:{rBase Scenario|Fluid|Water:self,rBase Scenario\US Units|P3|Fixed Speed (%):self,rBase Scenario\US Units\Pump A|P3|Fixed Speed (%):rinherit:Base Scenario\US Units,rBase Scenario\US Units\Pump A|Fluid|Water:rinherit:Base Scenario,},}assertaudit(data)[],audit(data)bad{**data,assignments:{**data[assignments],rBase Scenario\US Units\Pump A|P9|Length:rinherit:Base Scenario\US Units}}# 祖先没设 P9|Lengthassertaudit(bad),无源继承必须被检出print(SELFTEST OK继承归因一致性校验通过正例零问题、无源继承被拦。)if__name____main__:if--selftestinsys.argv:selftest()逐行剖析归因表用场景路径|对象|参数 - self | inherit:祖先路径的扁平键这正好和 AFT 的同对象同属性继承粒度对齐8.3。核心校验是**“无源继承不容许”你说某属性继承自祖先那祖先必须真的设了它**。这条检查能拦住我以为它继承了其实它压根没值的幻觉。为什么用人工填写的归因表因为 AFT 不公开模型文件结构铁律 6我们读不到谁链了谁。所以这份表是团队自己维护的继承契约文档——它一开始是负担但当你场景上到几十个时它是唯一能解释为什么结果变了的东西。这也是本节最重要的工程态度不公开的地方用人工契约补上而不是瞎猜。三、常见报错与排查报错 8-1改了一个父场景的参数一堆子场景结果跟着变被当成 bug。现象只改一处多场景结果全变。根因这些属性是linked的按官方机制链接属性随链接祖先的同对象同属性变化而跨代继承。解法这不是 bug 而是特性若某子场景要独立就在该子场景里显式重设该属性本场景设定即不链接。报错 8-2导入变更表报Scenario name is not unique in the model。现象多个场景同名导入拒绝。根因Scenario Path Name写了短名。解法用全限定路径如Base Scenario\US Units\Pump A在 GUI 里右键场景名 → Copy Scenario Path Name铁律 5。报错 8-3批跑勾了中间层场景跑出一堆没意义的结果。现象抽象层如US Units这种工况族的父层也被当独立工况跑了。根因批跑勾选时把非叶场景也加了进来。解法只勾叶场景leaf_paths()父层是继承源不是工况。报错 8-4一直以为某个场景没跑过其实它跑过或反之。现象对跑没跑记不清。根因没利用官方的可视标记。解法看场景树里有输出的场景文字自动变蓝当前场景是绿色对勾 状态栏双重标识。报错 8-5多场景输出面板里某些场景看不到数据。现象明明跑了多场景输出里缺。根因多场景输出需要模型文件内生成输出官方博客提到需生成.out输出文件否则可能看不到全部场景输出。解法用Start Batch Run重跑一遍生成输出文件第 12 篇展开。四、动手练习练习 1建树与寻址跑python scenario_tree.py --selftest。判定输出SELFTEST OK随后手工在代码里把某子场景名改成与兄弟同名重跑必须抛 ValueError恢复后再通过。练习 2分层设计为你的项目设计三层场景树根全局/单位制父工况族叶具体工况。判定写出每层各放哪些属性且根层不含任何工况差异项叶场景数 你实际要跑的工况数。练习 3继承归因表用inherit_audit.py的格式为练习 2 的树填一张归因表至少 6 项含 2 项继承。判定audit()返回空再故意加一条继承自没设该属性的祖先重跑必须报警。练习 4变蓝 QA在 AFT 里对同一模型建 3 个场景只跑其中 2 个。判定场景树里只有已跑的 2 个变蓝借助该标记说出哪个还没跑并把它补跑后确认也变蓝。五、小结与下一篇预告本篇讲透了 Scenario Manager 的四条硬机制所有场景存在同一模型文件内、Base Scenario 可在其上加根树结构可演化、linked 属性按同对象同属性跨代继承收益与陷阱并存、当前场景靠对勾状态栏、有输出靠变蓝跨场景变更必须用全限定 Scenario Path Name。你还拿到了scenario_tree.py把非法状态建不出来与inherit_audit.py把不公开的继承关系用人工契约补上。第 09 篇《参数扫描工程化Python 生成 AFT Transfer 变更工作簿》我们正式进入进阶阶段——把20 个参数 × 3 个水平 60 个工况设计成变更矩阵用Apply门控轮次按对象类型白名单校验参数类型把第 05 篇的生成器升级成扫描设计器。FAQ与第〇节一一对应Q1Scenario Manager 把多个工况存放在哪里A所有场景都保存在同一个模型文件内官方原文 All scenarios are saved within the model file相比一工况一文件它拥有单一真相源、公共参数继承一次改、官方的场景对比工具以及更省事的 Scenarios in Current Model 批跑方式。Q2什么是 Base Scenario为什么可以加根ABase Scenario 是场景树最顶端的根场景作为最高层祖先不继承任何场景新建单场景模型默认名为 Base Scenario官方允许在 Base 之上再插入新场景使其成为新根、原 Base 降为其子场景因此不必推倒重建就能抬高树根如中途加入单位制层。Q3linked 属性与跨代继承是怎么回事A某对象的属性值若继承自某个祖先即为 linked、来自本场景则非链接链接属性会继承链接祖先同对象同属性的改动并沿链跨任意代继承因此要改共通设定只需改一处但子场景若需独立就必须在该子场景里显式重设该属性以解除链接。Q4怎么一眼看出哪些场景有输出、哪个是当前场景A有输出的场景其文字颜色会自动变蓝可直接扫场景树确认哪些还没跑当前场景由场景树里的绿色对勾标记并同时显示在 AFT 的状态栏中。Q5Scenario Path Name 为什么必须全限定它与场景对比工具是什么关系A因为场景树是层级的、同名场景常见只有全限定路径如 Base Scenario\US Units\Pump A才能唯一寻址、避免报 Scenario name is not unique场景对比工具Scenario Comparison Tool/Grid用于把多场景输入输出并排比对属于人工研判而几十上百场景的批量聚合适用 Python 脚本二者分工互补。
返回列表