AFT二次开发教程(09):参数扫描工程化——Python 生成 AFT Transfer 变更工作簿

发布时间:2026/10/3 8:43:06
AFT二次开发教程(09):参数扫描工程化——Python 生成 AFT Transfer 变更工作簿 AFT二次开发教程09参数扫描工程化——Python 生成 AFT Transfer 变更工作簿版本与事实声明产品与版本AFT Fathom 15 / AFT Impulse 12当前本篇列契约与变更码规则引用官方Fathom 13帮助页Importing From Excel正文当前版以官方文档为准。语言/环境Python 3.x openpyxlitertools标准库。本文目标读完能把一个扫描设计变量、水平、工况数自动翻译成合法且可分批导入的AFT Transfer变更工作簿。所有参数与数值为示例性建模不代表任何标准规定。一句话结论AFT 参数扫描的工程化落点是把设计矩阵翻译成AFT Transfer工作表——每个参数×水平组合对应若干变更行用Apply列的 Yes/No 做轮次门控一份模板跑多轮、用对象类型白名单校验参数与变更码避免Invalid parameter ID/Invalid change type、并在场景数过大时按块拆分成多个工作簿分批导入这就是 AFT 世界里参数扫描的正确形态。〇、本篇要解决的认知问题Q1AFT 没有 API参数扫描到底扫在哪里扫描的产物是什么Q2为什么Apply列是扫描工程化的总闸一份模板跑多轮怎么实现Q3全因子full factorial会爆炸什么情况下该改用抽样AFT 侧需要什么配合Q4一个 60 工况的扫描要不要一次性生成一张大表拆分的依据是什么Q5怎么把扫描设计做成可以断点续跑、可追溯的一、机制解析9.1 扫描的落点变更矩阵 → AFT Transfer先纠正一个直觉在 AFT 里做参数扫描扫描的产物不是脚本而是一张表。因为通道 ①Import Excel Change Data是唯一合法的写输入口第 05 篇通道 ③ Scenario Manager 负责组织工况第 08 篇通道 ④ Start Batch Run 负责执行第 10 篇。于是扫描的完整链路是设计矩阵(变量×水平×工况) → 变更矩阵(行) → AFT Transfer 工作簿 → 导入 → 批跑 → 取数 Python 生成 Python 生成 openpyxl 落盘 File 菜单 FBS Excel Export关键认识扫描设计器 设计矩阵 → 变更矩阵的编译器。这是本篇代码的主职。9.2 Apply 门控一份模板跑多轮Apply列只接受Yes/No只有 Yes 的变更会被导入第 05 篇。把它用在扫描上能得到一个很实用的模式一次生成全部候选行用Apply决定本轮生效哪些。好处省去反复生成文件60 工况的候选行一次写好第一轮把前 20 个设为 Yes导入批跑第二轮换 20 个……模板不动。天然可审计哪个工况跑过/没跑在表里一目了然配合第 10 篇的账本。误操作代价低改错一行只影响该行不会污染别的工况。注意ApplyNo的行不生效因此不会报错它根本没被处理。如果一轮导入后日志少了行先检查是不是Apply写错了。9.3 全因子 vs 抽样设计工况数适用AFT 侧配合全因子各变量水平数连乘变量少2~3 个、要完整响应面场景数工况数命名要能编码变量水平单变量OAT变量数 × (水平数-1) 1敏感性初筛、快速看方向场景树一层一个变量拉丁超立方等抽样自定义样本数变量多、算力有限用 Python 生成样本 → 每样本一个场景关键约束AFT 特性工况数 场景数。所以扫描空间裁剪在 AFT 里等价于场景树规模控制。经验法则先圈定真正的可变量常有 2~3 个再决定设计动辄 6 个变量全因子如 3^6729 场景会让场景树和批跑时长都失控。这条对应本系列先圈定参数再扫描的最佳实践。9.4 大表的拆分依据把几百上千行变更塞进一张导入表有风险一次导入过大出错时定位困难日志长场景数过大场景树难管理、批跑时间长、中途失败重跑代价高单位集/打底新字段多时打底遗漏的风险随规模上升。拆分依据经验法则按**“导入单元”**拆——一个导入单元 一个可独立批跑、可独立对账的分块。常见做法每 20~50 个场景一块每块一个.xlsx 一个账本条目。这样单块失败只重跑一块。9.5 变更矩阵的最小契约一行变更必须有Apply | Object Type | Object Number | Parameter | Change Code | Value | Scenario Path Name 门控 对象类型 对象编号 参数 变更码 值 全限定场景路径加上一条元数据写在你自己的账本里不进导入表case_id工况编号、variable变量名、level水平。这样结果回来时能 join 回设计。纪律导入表只需七列多写无害因为 A 列与第 1 行本就不读但账本必须独立维护 case_id ↔ 场景路径 ↔ 变量水平的映射否则结果永远回不到设计上。二、完整代码与逐行剖析代码 9-1sweep_designer.py设计矩阵 → 变更矩阵 → 分批工作簿# -*- coding: utf-8 -*- sweep_designer.py —— AFT 参数扫描设计器 输入变量定义对象类型/编号/参数/变更码/水平 输出分批的 AFT Transfer 工作簿 账本 JSON 运行 python sweep_designer.py --selftest python sweep_designer.py --outdir out --block 20 importargparseimportitertoolsimportjsonimportosimportsysfromopenpyxlimportWorkbook SHEETAFT TransferCOLS[Apply,Object Type,Object Number,Parameter,Change Code,Value,Scenario Path Name]# 变量定义object_type 对应官方 Table 1变更码必须是该参数类型的合法码VARIABLES[{name:pump_speed,object_type:Pump,object_number:3,parameter:Fixed Speed (%),change_code:Set equal to value,levels:[80.0,90.0,100.0]},{name:valve_open,object_type:Valve,object_number:1,parameter:Open Percentage,change_code:Set equal to value,levels:[40.0,70.0,100.0]},]BASErBase Scenario\US Units# 参数类型白名单官方 Table 1 节选用于拦截 Invalid parameter ID / typePARAM_OWNER{Fixed Speed (%):(Pump,S),Open Percentage:(Valve,S),}LEGAL_CODES{S:{Set equal to value,Change by value (/-),Change by percent (/-)},I:{Set equal to value,Change by value (/-)},C:{Set equal to value,Add string to list,Delete string from list},}defvalidate_variables(vars_):problems[]forvinvars_:ownerPARAM_OWNER.get(v[parameter])ifownerisNone:problems.append(f[待确认] 参数未登记类型{v[parameter]})continueifowner[0]!v[object_type]:problems.append(fInvalid parameter ID{v[parameter]}不属于{v[object_type]})ifv[change_code]notinLEGAL_CODES[owner[1]]:problems.append(fInvalid change type{v[change_code]}不适用于{owner[1]})ifnotv.get(levels):problems.append(f变量{v[name]}没有水平值)returnproblemsdefcase_id(combo:tuple)-str:工况 ID把各水平编码进名字便于回 join。returnC_.join(str(c).replace(.,p)forcincombo)defbuild_matrix(vars_,base):全因子返回 (rows, ledger)。rows 只含 ApplyYes 的生效行。rows,ledger[],[]forcomboinitertools.product(*[v[levels]forvinvars_]):cidcase_id(combo)scnf{base}\\{cid}forv,lvlinzip(vars_,combo):rows.append({Apply:Yes,Object Type:v[object_type],Object Number:v[object_number],Parameter:v[parameter],Change Code:v[change_code],Value:lvl,Scenario Path Name:scn,})ledger.append({case_id:cid,scenario:scn,**{v[name]:lvlforv,lvlinzip(vars_,combo)}})returnrows,ledgerdefwrite_blocks(rows,outdir,block20):按场景分块每块 block 个场景落成独立工作簿返回块清单。os.makedirs(outdir,exist_okTrue)by_scn{}forrinrows:by_scn.setdefault(r[Scenario Path Name],[]).append(r)sceneslist(by_scn)blocks[]foriinrange(0,len(scenes),block):chunkscenes[i:iblock]wbWorkbook()wswb.active ws.titleSHEET ws.append(COLS)# 第 1 行表头内容不被读取forscninchunk:forrinby_scn[scn]:ws.append([r[c]forcinCOLS])pathos.path.join(outdir,ftransfer_block_{i//block1:02d}.xlsx)wb.save(path)blocks.append({file:path,scenarios:len(chunk),rows:sum(len(by_scn[s])forsinchunk)})returnblocksdefselftest():assertvalidate_variables(VARIABLES)[],validate_variables(VARIABLES)rows,ledgerbuild_matrix(VARIABLES,BASE)# 全因子3 × 3 9 工况每工况 2 行 - 18 行assertlen(ledger)9andlen(rows)18,(len(ledger),len(rows))assertall(r[Apply]Yesforrinrows)# 非法组合必须被拦给 Valve 用 Pump 的参数bad[{name:x,object_type:Valve,object_number:1,parameter:Fixed Speed (%),change_code:Set equal to value,levels:[1.0]}]assertany(Invalid parameter IDinpforpinvalidate_variables(bad))# 分块9 场景按每块 4 个 - 3 块blockswrite_blocks(rows,selftest_out,block4)assertlen(blocks)3,blocksassertsum(b[scenarios]forbinblocks)9print(SELFTEST OK9 工况 × 2 行 18 行非法参数被拦9 场景分成 3 块。)print(json.dumps(ledger[0],ensure_asciiFalse))defmain():apargparse.ArgumentParser()ap.add_argument(--outdir,defaultout)ap.add_argument(--block,typeint,default20)ap.add_argument(--selftest,actionstore_true)aap.parse_args()ifa.selftest:selftest()return0probsvalidate_variables(VARIABLES)ifprobs:print(变量定义有问题)forpinprobs:print( -,p)return1rows,ledgerbuild_matrix(VARIABLES,BASE)blockswrite_blocks(rows,a.outdir,a.block)withopen(os.path.join(a.outdir,ledger.json),w,encodingutf-8)asf:json.dump({ledger:ledger,blocks:blocks},f,ensure_asciiFalse,indent2)print(f{len(ledger)}个工况、{len(rows)}行变更拆成{len(blocks)}块写入{a.outdir}/)return0if__name____main__:sys.exit(main())逐行剖析VARIABLES是扫描设计的唯一真相源。它把扫什么结构化对象类型、编号、参数、变更码、水平。改扫描只改这里。PARAM_OWNERLEGAL_CODES合起来实现导入前的两道静态闸Invalid parameter ID参数不属于该对象类型与Invalid change type变更码不属于该参数类型。这两条错误在导入时才暴露代价是白跑一轮所以前置拦截是硬收益。case_id()把水平编码进工况名C80p0_40p0这类工况名自带设计信息结果回来一眼能看出是哪组参数。小数点替换成p是为了避免出现在场景名里引发歧义。build_matrix()用itertools.product做全因子返回的ledger是结果回 join 的钥匙——case_id / scenario / 各变量水平。write_blocks()按场景分块不是按行保证同一场景的所有行不跨块——否则导入时该场景参数不完整。这是分块逻辑的关键正确性点。selftest()断言9×218、非法参数被拦、9 场景按 4/块 → 3 块。三个数字都能手算复核。代码 9-2gate_apply.pyApply 门控切换轮次# -*- coding: utf-8 -*- gate_apply.py —— 用 Apply 列做轮次门控只让指定 case 集合生效。 读取一个账本把某工作簿中不在集合内的场景行设为 ApplyNo。 运行python gate_apply.py --selftest importsysdefgate(rows:list,active_scenarios:set)-list:out[]forrinrows:rdict(r)r[Apply]Yesifr[Scenario Path Name]inactive_scenarioselseNoout.append(r)returnoutdefselftest():# 复用 9-1 的行结构此处内联最小样例保证脚本自洽rows[{Scenario Path Name:fB\\{i},Apply:Yes}foriinrange(5)]active{B\\1,B\\3}ggate(rows,active)assertsum(1forringifr[Apply]Yes)2assert{r[Scenario Path Name]forringifr[Apply]Yes}activeprint(SELFTEST OK门控后仅目标 2 个场景生效。)if__name____main__:if--selftestinsys.argv:selftest()为什么门控要单独成函数扫描常常是发现结果不对 → 缩小到某几个工况重跑的迭代过程。门控做成纯函数输入行、输出行不改外部状态就能被反复调用、能被测试。把可变性关进函数里是数据面工程的基本卫生。三、常见报错与排查报错 9-1全因子设计出来工况数爆炸如 3^6729批跑到天亮还没完。现象场景数失控。根因把不该扫的变量也放进了设计。解法先圈定参数再扫描最佳实践——用 OAT 初筛剔掉不敏感变量只对 2~3 个可变量做全因子必要时改用抽样。报错 9-2导入后日志里少了行但没报错。现象明明生成了 18 行日志只处理了 12 行。根因剩下 6 行Apply是No门控没开——No 的行不生效也不报错。解法检查门控集合导入前打印sum(ApplyYes)与预期比对。报错 9-3报Invalid parameter ID但参数名看着没错。现象参数与对象类型不符。根因设计中object_type与parameter搭配错了如把 Pump 的参数配给了 Valve。解法validate_variables()前置拦以官方 Table 1 为准。报错 9-4分块导入后某场景参数只改了一半。现象某工况结果异常像只应用了部分参数。根因分块时按行切割导致同一场景的行被拆到两块只导了一块。解法按场景分块write_blocks的做法保证场景完整性。报错 9-5结果回来后对不上设计——不知道哪个数对应哪组参数。现象结果表里只有场景名看不出参数组合。根因没维护账本ledger。解法生成扫描时同步写ledger.jsoncase_id ↔ 场景 ↔ 水平结果取回后用 case_id join。报错 9-6扫描空间设计过大场景树多到无法维护。现象几百个场景改名/查找都痛苦。根因全因子铺得太开或场景命名无结构。解法先圈定 2~3 个可变量最佳实践场景名用可编码的形式如C80p0_40p0编码各水平第 09 篇case_id并借场景树分层根单位制父工况族控制规模第 08 篇。四、动手练习练习 1跑通设计器跑python sweep_designer.py --selftest。判定输出SELFTEST OK生成selftest_out/下 3 个工作簿每个只有一张名为AFT Transfer的表。练习 2加一个变量在VARIABLES里加第三个变量如 Reservoir 的Liquid Surface Elevation3 个水平并把它登记进PARAM_OWNER。判定自检的断言数改成27 工况 × 3 行 81 行并全部通过说明工况数的乘法来源。练习 3门控实战用gate_apply.py只让C80p0_40p0与C90p0_70p0两个工况生效重新导入。判定日志只出现这 2 个场景的变更其余场景参数不变。练习 4对账闭环批跑后用第 03 篇read_export.py取回结果用ledger.json把case_idjoin 进去。判定结果表里每个 case 都能追溯到具体的参数水平组合写出 join 键与你使用的乘法式。练习 5规模预算为3 个变量 × 各 4 个水平估算全因子工况数再与只对 2 个变量做全因子、第 3 个用 OAT的方案对比。判定给出两个方案的工况数4³64 与 4²319 之类并说明为何后者常更划算。五、小结与下一篇预告本篇把参数扫描落到了 AFT 的正确形态扫描的产物是AFT Transfer变更矩阵Apply列是轮次总闸一份模板跑多轮全因子要先圈定参数否则场景数爆炸大表要按场景分块不能按行切结果要对回设计必须靠账本。你的sweep_designer.py现在能从变量定义一路生成分批工作簿 账本。第 10 篇《Start Batch Run 工程化》我们讲执行侧——Batch Run Type两类、Add Model Files/Save List to File与Batch File文本清单、Run Batch in Background、Export Only (Do Not Run)、Cancel后已跑输出保留以及本系列铁律 8 的落地——超时 try/finally 释放 残留进程清理的批跑看门狗。FAQ与第〇节一一对应Q1AFT 没有 API参数扫描扫在哪里、产物是什么A扫描的产物不是脚本而是数据表——设计矩阵变量×水平×工况经 Python 翻译成 AFT Transfer 变更矩阵落成工作簿后由 File Import Excel Change Data 导入再经 Scenario Manager 组织场景、Start Batch Run 批跑最后用 Excel Export Manager 取数。Q2Apply 列为什么是扫描工程化的总闸A因为 Apply 只接受 Yes/No 且只有 Yes 的变更会被导入所以可一次生成全部候选行、用 Apply 决定本轮生效哪些实现一份模板跑多轮、天然可审计、误操作只影响单行注意 ApplyNo 的行不生效也不报错故导入前应统计 Yes 行数。Q3全因子什么时候该改用抽样A当变量较多如超过 3 个导致工况数连乘爆炸时应改用拉丁超立方等抽样因为 AFT 里工况数等于场景数场景过多会让场景树管理、批跑时长与失败重跑代价都失控所以经验做法是先圈定 2~3 个可变量再做全因子其余用 OAT 初筛剔除。Q4一个 60 工况的扫描要不要一次生成一张大表A建议按场景分块经验值每 20~50 个场景一块每块一个工作簿加一条账本条目分块必须按场景而非按行切割否则同一场景的变更行会被拆到两块导致参数只改一半。Q5怎么让扫描可断点续跑、可追溯A扫描时同步生成账本 JSON记录 case_id、场景路径与各变量水平用它把结果 join 回设计配合 Apply 门控只重跑目标工况并用第 11 篇的长表落库与幂等 upsert 保证重跑不产生重复数据。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询