:分析步、边界与输出请求——数据在 Model 仓库、作用域在 Step)
Abaqus 二次开发教程8分析步、边界与输出请求——数据在 Model 仓库、作用域在 Step版本声明块工具/软件Abaqus 20262026 FD01内嵌 Python 3.10.52024 GA 起此前为 Python 2.7.15旧版本语言/环境Python Abaqus Scripting InterfaceASIStep/BoundaryCondition/OutputRequest都挂在 Model 仓库上脚本可运行于 Abaqus/CAE 或abaqus cae noGUI第 1 篇本文目标把“步、边界、输出”三件事的分工写清楚并给出一条可数判定输出帧数必须与脚本请求一致一句话结论ASI 里数据挂在 Model 仓库、作用域由createStepName决定——写在Initial上的边界条件是初始状态、写在LoadStep上的边界条件是该步的变化量场输出field output与历史输出history output是两个独立仓库必须分别请求frequency是计数语义每frequency个增量写一帧未请求的变量在 ODB 里读不到长时显式分析必须显式开重启动restart否则中断只能从头算。〇、本篇要解决的认知问题createStepNameInitial与createStepNameLoadStep的边界条件BoundaryCondition在输入文件里的语义到底有什么不同StaticStep的增量控制参数initialIncrement、maxNumIncrements等为什么能让一个“简单静力”在第一个增量就失败这些默认值该信任还是显式写场输出与历史输出为什么是两个独立仓库只请求场输出时后处理读能量类历史变量会发生什么frequency设成 1 与设成 100分别如何改变 ODB 的帧数与体积脚本层怎么用可数数字判定“输出请求真的生效了”重启动restart与输入文件里的*RESTART是什么关系为什么显式长时分析必须显式请求而默认行为不一定给你一、机制解析1.1 三个仓库、一个作用域数据在 Model生效在 Stepmodel.steps ← 步仓库Initial 永远存在但域与语义不同于普通 Step model.boundaryConditions ← 边界条件仓库引用 region createStepName字符串 model.loads ← 载荷仓库同样引用 region createStepName model.fieldOutputRequests ← 场输出请求createStepName variables frequency model.historyOutputRequests ← 历史输出请求createStepName variables frequency model.amplitudes ← 幅值曲线Amplitude被载荷/边界按名字引用 model.timePoints ← 时间点TimePoint被输出请求按名字引用 model.keywordBlock ← 唯一绕过 ASI、直接写关键字的口子第 5 篇这套设计的核心是引用与生效分离边界条件对象本身不属于某个步它属于 Model 仓库只是通过createStepName这个字符串声明“在哪个步生效”。三个直接后果changeKey改名后createStepName存的字符串不会跟着变第 5 篇的命名陷阱在这里直接咬人删除一个步不会自动删除引用它的边界条件提交时会在输入文件里出现引用不存在的步输出请求写在Initial上合法但它只描述初始状态不产生时域结果。为什么这对你重要批量参数扫描里“结果读不到”最常见的原因不是求解失败而是作用域错配——边界条件写在Initial、输出请求写在另一个步两者在 ODB 里对不上而脚本层无法从报错看出这一点因为它根本没报错。过渡句作用域解释了“写在哪里”增量控制解释了“算得多细”。这两件事在隐式静力里最容易互相干扰。1.2 增量控制隐式与显式的默认行为差异项目Abaqus/Standard隐式静力 / 隐式动力Abaqus/Explicit显式动力时间增量由牛顿迭代自适应受initialIncrement、minIncrement、maxIncrement约束由稳定条件决定依赖单元尺寸与波速sqrt(E/ρ)典型失败增量收缩到下限 → 收敛不上现象文本以官方文档为准步长过小 → 计算时间爆炸与第 7 篇的联系无直接依赖密度单位错 → 质量错 → 稳定增量步长错 → 用时错帧来源每增量或按frequency计数按frequency显式增量数以万计必须降频关于默认值必须说清楚ASI 各步对象的参数默认值在参考文档里是显式列出的例如initialIncrement、maxNumIncrements、amplitude本系列不凭记忆复述具体数字——以官方文档为准。脚本层的安全写法是把这些参数显式写出显式写initialIncrement与maxNumIncrements比在结果异常后倒推默认值便宜得多。【经验法则】隐式静力在第一个增量就失败通常不是网格问题而是载荷阶跃过大——用幅值曲线Amplitude把载荷改成渐变比加密网格更有效这是本系列反复出现的一条判断。过渡句求解器算得多细是一回事你在 ODB 里能否读到是另一回事。输出请求不参与求解只参与写盘。1.3 场输出与历史输出为什么必须分开请求FieldOutputRequest → 每一帧在“空间”上的一组值节点/单元/积分点变量 例U、V、A、S、E、PE总量类ALLIE 等不在此列 判定frames 数量 按 frequency 计数得到的帧数 HistoryOutputRequest → 在“某个特定区域或点”上随时间变化的标量序列 例反力、能量ALLIE、ALLKE、ALLHLL、指定节点位移 判定ODB 的 histories 必须含该变量没请求 → 读不到三条反直觉的事实默认请求是一套变量不是全部变量脚本没写请求时ODB 里通常仍含一组默认变量但不能假设它包含你要读的那一项。判定必须以 ODB 实际内容为准第 9 篇用namedQueries与getSubset打印实际变量清单。【最佳实践】后处理脚本在读取前先打印请求清单与 ODB 实际变量清单两者不一致即判失败。历史变量不会出现在场输出里能量类是历史输出。只请求场输出的脚本在后处理里读不到沙漏能量——第 6 篇【经验法则】的 1% 沙漏阈值因此无法判定。要做这类判定的脚本必须显式请求历史输出。frequency是计数语义frequency1表示每增量写一帧显式分析一个步可能有几十万增量所以显式的输出频率通常必须远大于 1。【经验法则】显式后处理的帧数量级按后处理需求确定脚本层用“帧数 1 增量数 ÷ frequency”做可数判定具体默认值以官方文档为准。过渡句下面三段代码把这三层机制写成可判定规则——第一段完整建模并显式请求第二段用纯 Python 把帧数与体积写成硬判据第三段验证“没请求就读不到”。二、完整代码与逐行剖析2.1 完整可运行脚本步、边界、幅值与输出请求# step_bc_output.py —— 作用域、增量与输出请求的完整写法mm-N-sfromabaqusimportmdb,session# noGUI 下 session 不可用【铁律】第 8 条fromabaqusConstantsimport*#【铁律】第 3 条符号常量不可省略importregionToolsetimportsys MODEL,JOBCantilever,CantileverL,H,W100.0,10.0,10.0F100.0modelmdb.Model(nameMODEL)sketchmodel.ConstrainedSketch(nameprofile,sheetSize200.0)sketch.rectangle(point1(0.0,0.0),point2(W,H))partmodel.Part(nameBeam,dimensionalityTHREE_D,typeDEFORMABLE_BODY)part.BaseExtrusion(sketchsketch,depthL)part.regenerate()# isOutOfDate1 时后续操作不可靠第 5 篇model.Material(nameSteel)model.materials[Steel].Elastic(table((210000.0,0.3),))model.materials[Steel].Density(nameDensity,table((7.85e-9,),))#【铁律】第 1 条model.HomogeneousSolidSection(nameSection,materialSteel,thickness1.0)part.Set(nameAllCells,cellspart.cells)part.SectionAssignment(regionregionToolset.Region(cellspart.cells),sectionNameSection)instmodel.rootAssembly.Instance(nameBeamInst,partpart,dependentON)# 关键决策点 1Initial 上的 BC 是“初始状态”步里的 BC 是“该步的变化量”语义不同part.Set(nameFixedFace,facespart.faces.findAt(((0.0,W/2.0,H/2.0),)))model.DisplacementBC(nameFixedInitial,createStepNameInitial,regioninst.sets[FixedFace],u10.0,u20.0,u30.0)# 关键决策点 2显式写增量控制参数不依赖默认默认值以官方文档为准model.StaticStep(nameLoadStep,previousInitial,timePeriod1.0,initialIncrement0.1,maxNumIncrements1000)# 关键决策点 3把阶跃载荷写成渐变——比加密网格更能救收敛1.2 节的经验法则model.TabularAmplitude(nameRamp,timeSpan1.0,data((0.0,0.0),(1.0,1.0)))part.Surface(nameTipSurf,side1Facespart.faces.findAt(((W/2.0,H/2.0,L),)))model.Pressure(nameTipLoad,createStepNameLoadStep,regioninst.surfaces[TipSurf],magnitudeF/(W*H),amplitudeRamp)# 关键决策点 4场输出与历史输出是两个仓库必须分别请求1.3 节ifF-1notinmodel.fieldOutputRequests:model.FieldOutputRequest(nameF-1,createStepNameLoadStep,variables(U,S),frequency1)ifH-1notinmodel.historyOutputRequests:# 历史变量集合由请求决定这里显式请求能量类用于第 6 篇的沙漏判定model.HistoryOutputRequest(nameH-1,createStepNameLoadStep,frequency10)# 自检三条可观察判定作用域、请求内容、引用完整性print(判定 1步仓库 %s含 Initial%sorted(model.steps.keys()))print(判定 2BC 作用域 %s%model.boundaryConditions[FixedInitial].createStepName)print(判定 3场输出变量 %s历史输出请求数 %d%(sorted(model.fieldOutputRequests[F-1].variables),len(model.historyOutputRequests)))# 重启动关键字块是绕过 ASI 的口子方法名与参数以官方文档为准故用 try 包住try:model.keywordBlock.write(syntaxAbaqus,line*RESTART, WRITE, OVERWRITE, FREQUENCY100)print(判定 4关键字块已写入 restart 请求长时显式分析必写)exceptExceptionase:print(判定 4关键字块写入方式%s以官方文档为准%type(e).__name__)mdb.Job(nameJOB,modelMODEL,numCpus1)jobmdb.jobs[JOB]job.submit()job.waitForCompletion()#【最佳实践】异步提交必须显式等待第 3 篇print(判定 5job.status %s用符号常量比较不用字符串%job.status)sys.exit(0)逐行看关键决策点createStepNameInitial与createStepNameLoadStep分处两个语义域前者是初始状态、后者是步内变化量。同一个边界条件若写在LoadStep上、值为 0.0它表达的是“相对初始状态不变”这与写在Initial上并不等价——输入文件里的写法不同。TabularAmplitude的smooth、timeSpan等参数默认值以官方文档为准脚本显式传timeSpan与data比依赖默认更稳。HistoryOutputRequest没写variables默认历史变量集合由求解器决定以官方文档为准。如果脚本要做能量判定必须显式写出变量名否则第 6 篇的 1% 阈值无法判定——这是本篇与第 6 篇的直接接口。段末的job.status用符号常量比较第 2 篇不要用字符串。2.2 输出帧数与体积写成可数判定的预算脚本# output_budget.py —— 纯 Python 的帧数/体积预算可独立运行不依赖 Abaqus 环境importsys NODES125# 第 3 篇的硬校验125 节点INCREMENTS40# 本次 LoadStep 的增量数假设值实际增量数以 .sta/.msg 为准defframes_by_frequency(frequency,increments):frequency 是计数语义每 frequency 个增量写一帧第 1 帧通常是初始状态。iffrequency1:return1incrementsreturn1increments//frequencydefestimate_kb(frames,n_vars,entities,bytes_per_value8):量级估算帧数 × 变量数 × 实体数 × 每值字节浮点。这是【经验法则】量级不是精确值。returnframes*n_vars*entities*bytes_per_value/1024.0cases[(1,2),(5,2),(1,6),(10,2),(1,12)]print(%-10s %-8s %-8s %s%(frequency,变量数,帧数,估算体积 KB))forfreq,n_varsincases:framesframes_by_frequency(freq,INCREMENTS)print(%-10d %-8d %-8d %.1f%(freq,n_vars,frames,estimate_kb(frames,n_vars,NODES)))# 硬判据 1frequency1 时帧数必须等于 1 增量数 41expected_framesframes_by_frequency(1,INCREMENTS)print(硬判据 1frequency1 → 帧数必须为 %d%expected_frames)# 硬判据 2同帧数下6 个变量的估算体积必须是 2 个变量的 3 倍允许 10% 偏差v2estimate_kb(expected_frames,2,NODES)v6estimate_kb(expected_frames,6,NODES)ok2abs(v6/v2-3.0)0.10print(硬判据 2%s%(通过ifok2else变量数与体积不成线性))# 硬判据 3降频 5 倍必须把帧数降到 1/5 量级加初始帧f1frames_by_frequency(1,INCREMENTS)f5frames_by_frequency(5,INCREMENTS)ok3abs(f1/f5-5.0)0.3print(硬判据 3%s帧数 %d vs %d%(通过ifok3else频率语义与脚本假设不符,f1,f5))okexpected_frames41andok2andok3print(结论%s%(通过ifokelse输出请求的计数语义与脚本假设不符))sys.exit(0ifokelse1)要点这段刻意不调用任何 Abaqus 接口它把“帧数 1 增量数 ÷ frequency”这条语义写成断言。真正的增量数要从.sta/.msg读取第 10 篇但脚本层必须先有一个可断言的模型否则 ODB 读到 41 帧还是 4 帧时你无法判断是请求生效还是求解失败。硬判据 3 是排查“输出请求没生效”的最便宜方法如果实际帧数与1 增量数 ÷ frequency不符说明请求写在别的步上、或频率被默认请求覆盖。【最佳实践】把输出预算写进脚本头部注释本例 41 帧、2 变量、125 节点这样后处理脚本在 ODB 里读到的帧数可以直接与注释比对而不是靠肉眼。2.3 验证“没请求就读不到”可运行的对照脚本# request_vs_read.py —— 用 ODB 实际内容证明“未请求的变量读不到”【铁律】第 9 条fromabaqusimportmdb,sessionfromabaqusConstantsimport*importodbAccessimportsys JOBCantileverSTEPLoadStepWANT_FIELD(U,S)# 已在 2.1 中请求WANT_HISTORY(ALLIE,ALLHLL)# 未显式请求时这一项常常读不到odbodbAccess.openOdb(pathJOB.odb,readOnlyTrue)try:stepodb.steps[STEP]framesstep.framesprint(实际帧数 %d应与 2.2 的预算一致%len(frames))# 场输出打印这一帧实际有哪些变量——这是“请求是否生效”的唯一证据availablesorted(odb.rootAssembly.nodeOutputs.keys())print(ODB 场输出变量节点级%s%available)missing_field[vforvinWANT_FIELDifvnotinavailable]print(判定 A未请求/未写入的场变量 %s%(missing_fieldifmissing_fieldelse无))# 历史输出能量类只存在于 histories不在 fieldOutputs 里1.3 节hist_vars[]ifhasattr(odb.rootAssembly,namedQueryKeys):hist_varssorted(odb.rootAssembly.namedQueryKeys)try:hist_keyssorted(odb.rootAssembly.historyRegions.keys())print(历史区域%s%hist_keys)regionodb.rootAssembly.historyRegions[hist_keys[0]]gotsorted(region.historyOutputs.keys())print(历史变量%s%got)missing_hist[vforvinWANT_HISTORYifvnotingot]print(判定 B未请求的历史变量 %s%(missing_histifmissing_histelse无))exceptExceptionase:print(历史区域接口形态%s以官方文档为准%type(e).__name__)ok(notmissing_field)andbool(hist_vars)print(结论%s%(请求与读取一致ifokelse需要补输出请求))finally:odb.close()#【铁律】第 9 条显式关闭 ODBsys.exit(0)要点判定 A 与判定 B 是两条不同的证据链场输出看nodeOutputs历史输出看historyRegions。两者不共享变量集合——这就是 1.3 节第 2 条的可执行版本。接口形态namedQueryKeys、historyRegions的键在不同版本可能不同脚本用hasattr/try包住并明确“以官方文档为准”而不是照抄某版本的报错文本。这段的判定标准是可观察输出判定 A 必须打印“无”判定 B 必须打印出至少一个能量变量若判定 B 打印“无”说明请求阶段漏写历史输出回到 2.1 补HistoryOutputRequest。三、常见报错与排查现象后处理读不到某个变量例如能量或反力脚本没有报错。根因该变量属于历史输出而脚本只请求了场输出或请求写在另一个步上作用域错配。解法跑 2.3 的判定 A/B把HistoryOutputRequest显式写全createStepName与变量名用 2.2 的帧数断言确认请求生效。现象ODB 帧数与脚本预期的frequency不符例如请求frequency1却只有 4 帧。根因实际增量数少于假设值求解器把步内合并成少数增量或输出请求被默认请求覆盖或请求建在Initial上。解法从.sta/.msg读实际增量数第 10 篇再按“帧数 1 增量数 ÷ frequency”判定把请求名与createStepName打印出来核对。现象隐式静力在第一个增量就失败、增量不断收缩现象文本以官方文档为准。根因载荷为阶跃、接触初始状态未建立或增量控制参数被默认值限制。解法用幅值曲线TabularAmplitude把载荷改渐变显式写initialIncrement与maxNumIncrements先建立接触再加载分步。默认值一律以官方文档为准。现象KeyError: LoadStep或提交时引用了不存在的步。根因createStepName存的是字符串步被改名或删除后引用不会自动更新第 5 篇。解法脚本入口把步名当参数开头打印sorted(model.steps.keys())校验脚本检查“每个 BC/载荷的createStepName存在于 steps”。现象显式长时分析跑完或中断后无法续算只能从头重跑。根因没有请求重启动restart输入文件里没有*RESTART, WRITE显式默认行为不保证给你重启动文件。解法在脚本里显式写重启动请求关键字块或对应方法方法名与参数以官方文档为准并在提交后检查产物里是否存在重启动文件读取时用*RESTART READ或对应接口以官方文档为准。四、动手练习帧数断言在 2.1 的模型上提交作业然后运行 2.3并把frequency从 1 改成 5 再提交一次。判定标准第一次 ODB 帧数必须等于1 实际增量数第二次必须约为第一次的 1/5加初始帧。两次数字与 2.2 的预算表逐行对照偏差 30% 即为请求未生效。历史输出补请求先只请求场输出跑 2.3 的判定 B再补上HistoryOutputRequest重跑。判定标准第一次判定 B 必须打印“无”或空清单、退出码 1第二次判定 B 必须至少列出 1 个能量变量、退出码 0。这是本篇唯一的“请求层”验证。作用域对照思考题附验证要点解释同一个位移边界条件写在Initial与写在LoadStep的差别。验证要点清单① 打印model.boundaryConditions[FixedInitial].createStepName必须为Initial② 把同一个 BC 改到LoadStep后提交比较两次输入文件里该边界条件出现的段落第二次出现在步内③ 删除LoadStep后运行校验脚本必须报“引用了不存在的步”。三条都要有输出记录。五、小结与下一篇预告本篇的机制核心是三层分离数据挂在 Model 仓库、作用域靠createStepName字符串、输出靠独立的请求仓库。这解释了三类“不报错的错误”读不到变量未请求或作用域错配、帧数与预期不符频率计数语义与实际增量数不一致、引用失效改名后字符串不更新。可判定标准是三条硬判据帧数 1 增量数 ÷frequency、同帧数下体积与变量数成线性、历史变量必须显式请求才读得到。第 9 篇把这些变量从 ODB 导出成 CSV——那里getSubset与sets的键空间陷阱会直接接上本篇的判定 A/B第 10 篇讨论作业提交与abaqus_v6.env实际增量数与并行核数从哪里读、又从哪里被覆盖。第 6 篇的沙漏 1% 阈值能否判定取决于本篇是否请求了历史输出——这是本系列里最典型的一处跨篇依赖。本篇认知问题回显FAQQ1Abaqus 脚本里createStepNameInitial的边界条件与写在分析步里的边界条件有什么不同AInitial上的边界条件描述初始状态分析步里的边界条件描述该步内相对初始状态的变化量两者在输入文件里的写法与作用域不同。Q2为什么 Abaqus 的隐式静力分析常在第一个增量就失败而加密网格没用A常见根因是载荷阶跃或接触初始状态未建立导致增量收缩到下限用幅值曲线TabularAmplitude把载荷改渐变比加密网格更有效。Q3FieldOutputRequest与HistoryOutputRequest为什么必须分开请求A两者是 Model 上的两个独立仓库场输出给空间上的变量集合、历史输出给指定区域随时间的标量序列能量类变量只在历史输出里不请求就读不到。Q4Abaqus 输出请求的frequency是什么语义如何判定它生效了Afrequency是计数语义每frequency个增量写一帧判定式为 ODB 帧数 1 实际增量数 ÷frequency不符即为请求写在别的步上或被默认请求覆盖。Q5为什么 Abaqus/Explicit 的长时分析要显式请求重启动restart它与*RESTART什么关系A重启动靠输入文件里的*RESTART, WRITE产生重启动文件显式默认行为不保证生成没有该文件中断后只能从头算读取时用*RESTART READ以官方文档为准。