简介:《PSSE自定义模型学习资料》面向电力系统动态仿真学习者及需在PSS/E中开发用户自定义模型的工程人员,聚焦Fortran模型子程序编写、编译调用与调试中的常见问题,帮助读者理解自定义模型从逻辑设计到接入主程序的实现路径。压缩包仅含1个doc文件,约27KB,内容围绕动态仿真中的MODE等标志位展开,说明TBLCNC、CONEC等连接子程序如何依据标志量调用模型语句,并涉及动态数据输入记录格式的识读。文中还整理了与机组相关模型的子程序书写格式、STRTIN数组位置表入口含义、调用实参对应关系,以及发电机、电流补偿器、励磁稳定器、励磁、汽轮机/调速器和混合模型的调用顺序。已有680人学习,适合初学到进阶阶段查阅,作为梳理PSS/E自定义模型接口、调用关系与排错思路的参考。
1. 知识地图:PSS/E 自定义模型的关键在接口层,不在算法层
PSS/E 自定义模型这件事,第一反应往往是"用 Fortran 写控制框图",真写起来才发现难的并不是方程,而是三张表能不能对得上。一张是标志位表:MODE、KPAUSE、MSTATE、MIDTRM,它们留在 PSS/E 的 COMMON 内存里,框架靠这些取值决定这次调用子程序该做哪件事。一张是地址表:子程序头必须写成SUBROUTINE modelname(MC, ISLOT),NC、NS、NV、NI 四个计数通过 STRTIN 换成数组首地址。还有一张是数据表:动态数据输入文件里那条 USRMDL 记录,字段错一个,模型要么被当成内置模型读走,要么根本挂不上去。三张表对齐,模型才会被 TBLCNC 自动联进主框架,按发电机到调速器的固定次序被调用。这张地图适合两类人:已经跑通潮流、开始碰动态仿真、需要自己造模型的;以及"编译能过、一跑就不对",想回头补接口层的。
2. 标志位与行为链:MODE、KPAUSE、MSTATE、MIDTRM 各自管什么
这一章要解决的是"模型子程序为什么长成那个样子"。如果只把子程序当成一个从上往下执行的函数,写出来的东西一定会在某个行为上翻车。PSS/E 的框架是事件驱动的,一次动态仿真被拆成一串行为,每个行为对应一个标志量取值,模型子程序的任务是在不同的取值下做不同的事。把这条想通,后面地址和数据文件才有地方落。
2.1 四个标志位住在同一块 COMMON 内存里
原始资料里那句话很轻:MODE、KPAUSE、MSTATE 及 MIDTRM 留在 PSS/E 的 COMMON 内存中。这句话的分量在于"共享"两个字。模型子程序不是通过形参拿到这几个值,而是通过 COMMON 直接读写,这意味着两件事:第一,子程序开头不需要、也没法为它们准备实参;第二,任何一个分支里对它们的修改,对同一进程内后续被调用的模型都是可见的。实习的时候最容易犯的错,就是把 MODE 当成"我传进来什么就是什么"的局部变量来推理,结果在一个分支里动了它,下一个行为进来时读到的是被污染过的值。
逐个看这四个量。MODE 是行为代码,框架每次决定"现在该做哪件事"时把它写进去,子程序按它的取值分流,原文明确给出的一条对应是 STRT 行为与 MODE=1 内联,也就是初始化。KPAUSE 从命名和它在行为链里的位置看,是和暂停、中断相关的标志,用于在仿真推进过程中插入一个可干预的节点。MSTATE 与状态量(STATE)的处理时机相关,什么时候更新、什么时候保持,靠它来区分。MIDTRM 是中途处理标志,处理那些不属于开头也不属于结尾、但每个阶段都要过一遍的动作。
| 标志位 | 大致管什么 | 模型里通常对应什么动作 | 漏写或写错的典型表现 |
|---|---|---|---|
| MODE | 当前行为代码 | 按值分流:初始化、进入仿真、输出等 | 初始化能过,一进仿真就发散或曲线不动 |
| KPAUSE | 暂停/中断节点 | 在推进中放行或挂起 | 卡住不动,或者该停的地方直接冲过去 |
| MSTATE | 状态量处理时机 | STATE 的更新与保持 | 初值漂移,稳态点对不上 |
| MIDTRM | 中途处理 | 阶段性附加计算 | 没有报错,但曲线形状对不上 |
需要说清楚的一点:原始资料只给了这四个名字和"留在 COMMON 内存"这一条位置信息,具体取值的语义随版本和模型类型会有差别,复习时以手头版本的模型说明为准,别凭记忆硬编码。但有一条是硬约束——它们不属于模型的私有状态,谁读谁写都要按框架的时序来,不能当成自己的局部缓存。
2.2 一次动态仿真是一串行为,不是一个函数
PSS/E 主体框架里,动态仿真是以一个一个的行为串联起来的,而这些行为又和内置的标志量相联系。模型子程序的写法因此被规定死了:对应不同标志量的值,写不同的实现语句。框架实现每个行为的过程,就是根据该行为相应的标志量值,通过连接子程序(TBLCNC、CONEC、CONET 等)去调用模型子程序里对应该值的那段语句。换句话说,子程序是一次性写好的,但会被反复进入很多次,每次只走其中一小段。
原文给的例子是 STRT:进行初始化时,STRT 行为与 MODE=1 内联,框架通过连接子程序调用模型子程序里对应 MODE=1 的初始化语句。这条对应关系是整套机制的样板——行为名、标志量取值、子程序里的分支,三者是一一绑定的。理解了 STRT,剩下的行为只是把同样的绑定再做一遍。
SUBROUTINE MYMODEL(MC, ISLOT) C MC : 本模型所连机组的 PSS/E 内部机组码 C ISLOT : 本模型在内部数组位置表中的槽位指针 INTEGER MC, ISLOT C 标志位与内部数组通过 COMMON 进入,不作为形参传递 C IF (MODE .EQ. 1) THEN C STRT 行为:读入 CON,给 STATE 赋初值 ELSE IF (MODE .EQ. 2) THEN C 仿真推进:按步长计算,更新 STATE ELSE IF (MODE .EQ. 3) THEN C 输出/记录:把关心的量写出去 ELSE C 兜底分支:打标记,便于发现没预料到的行为 END IF C IF (KPAUSE .EQ. 1) THEN C 暂停点处理 END IF RETURN END这段是结构示意,不是可以直接编译的完整模型,重点在骨架。第一,MODE的每个分支对应一个行为,分支的判断值必须和框架实际传进来的值对齐,原文只明确了 MODE=1 对应 STRT,其余取值不要凭印象写,先把框架真正传进来的值打出来对一遍。第二,ELSE兜底分支不是可有可无的装饰,它是你发现"框架还调了一个我没考虑的行为"的唯一线索。第三,MC和ISLOT在子程序体里通常不直接参与运算,它们是拿去查 STRTIN 的钥匙,这一点下一章展开。
提示:调试阶段在每个分支入口加一行带计数的输出,跑一个最小算例,看每个分支是否至少被进入过一次。比对着代码猜要快得多。
2.3 自己建一张"标志位—分支"对照表
真正复习的时候,代码本身反而没那么重要,值钱的是一张自己整理的对照表。做法很简单:每遇到一个行为,就在表里加一行,四列——行为名、触发它的标志量及取值、模型里该做的事、漏写的症状。这张表写满之后,你对这个模型的理解就不再是"我记得好像有这么一段",而是"我知道它什么时候被进、进去干什么"。
| 行为 | 触发标志 | 模型里要做的事 | 用到的数组 | 漏写的症状 |
|---|---|---|---|---|
| 初始化 | MODE=1(对应 STRT) | 读 CON,给 STATE 赋初值 | CON、STATE | 初值不对,稳态点偏移 |
| 仿真推进 | MODE 的推进取值 | 每个步长算微分、更新 STATE | STATE、VAR | 曲线发散或不变化 |
| 输出记录 | MODE 的输出取值 | 把关心的量写到输出通道 | VAR | 曲线缺失或全零 |
| 暂停处理 | KPAUSE | 放行或挂起 | 无 | 卡住或直接冲过 |
| 中途处理 | MIDTRM | 阶段性附加计算 | VAR | 形状对不上但无报错 |
这张表还有一个隐藏用途:它逼你把"用到的数组"这一列填满。填不满,说明你还没搞清楚模型到底吃哪些数据;填错了,后面 STRTIN 的入口一定对不上。梳理下来会发现,大部分"跑不通"的问题,在这张表上就已经暴露了,根本轮不到编译器报错。
3. 调用契约:SUBROUTINE modelname(MC, ISLOT) 的形参与地址表
接口层的第一条硬规则在这里。与机组相关的自定义模型,子程序形式必须写成SUBROUTINE modelname(MC, ISLOT),这是契约,不是命名风格。形参名字可以按你的习惯调整,但位置、类型、含义不能变;顺序颠倒或者少写一个,编译可能照样过,运行时拿到的机组码和槽位指针就全错位了。
3.1 MC 与 ISLOT:两个整型形参,一个都不能改名换序
按原文的定义,MC 是该模型相连机组的 PSS/E 内部机组码,ISLOT 是该模型调用的内部数组位置表指针。相应地,与机组相关的自定义模型调用语句是CALL modelname(X, Y),其中 X 是对应 MC 的实参,Y 是对应 ISLOT 的实参。也就是说,框架在调用你的时候,第一个位置永远放机组码,第二个位置永远放槽位指针。
理解这两个参数的分工,比记住它们的名字更重要。MC 回答的是"我是谁家的模型"——同一条母线上挂多台机组时,同一个模型子程序会被不同机组各调用一次,靠 MC 区分。ISLOT 回答的是"我的数据在哪儿"——每个模型实例在内部数组里占一段位置,ISLOT 就是这段位置的入口编号,所有数组访问都要从它出发。
C 正确:形参顺序与含义符合契约 SUBROUTINE MYMODEL(MC, ISLOT) INTEGER MC, ISLOT C 错误示范:形参顺序颠倒,编译能过,运行时全错位 C SUBROUTINE MYMODEL(ISLOT, MC) C 错误示范:少写一个形参,框架传进来的第二个实参被丢弃 C SUBROUTINE MYMODEL(MC)上面这段只是对照,说明为什么这两行不能随手改。真正的模型体里,MC 一般用于取本机组的量(转速、机端电压之类),ISLOT 用于取本模型自己的 CON、STATE、VAR、ICON 段。两个参数都声明成整型,别写成REAL,隐式类型转换在这类老式 Fortran 代码里不会报错,只会让你查半天。
3.2 STRTIN 四个入口:NC、NS、NV、NI 到地址的映射
在 DYRE 活动结束时,与机组相关的每一个模型都会被分配好数组位置表的入口,具体落在 STRTIN 这个二维数组里。四个入口的含义是固定的:
| 入口 | 含义 | 计数为 0 时 |
|---|---|---|
STRTIN(1, ISLOT) | 模型使用的第一个 CON 的指针 | NC 为 0 时为 0 |
STRTIN(2, ISLOT) | 模型使用的第一个 STATE 的指针 | NS 为 0 时为 0 |
STRTIN(3, ISLOT) | 模型使用的第一个 VAR 的指针 | NV 为 0 时为 0 |
STRTIN(4, ISLOT) | 模型使用的第一个 ICON 的指针 | NI 为 0 时为 0 |
这张表最值得记的不是"1 是 CON、2 是 STATE",而是"计数为 0 时指针为 0"这一列。它意味着:如果你的模型声明 NC=0,那么STRTIN(1, ISLOT)拿到的就是 0,后面任何基于它做的下标运算都会把数据写到不该写的地方去。这类错误不会在编译期暴露,运行时的表现可能是别的模型数据被莫名其妙改写,查起来非常难受。
拿到首地址之后的用法是标准的偏移寻址。假设模型要用第 k 个 CON,写法就是首地址加 k-1:
C 取本模型的第一个 CON 段首地址(NC=0 时该值为 0,不可用) ICON1 = STRTIN(1, ISLOT) C 第 1 个 CON VAL1 = CON(ICON1) C 第 k 个 CON:首地址 + k - 1 VALK = CON(ICON1 + K - 1) C 同理:STATE 用 STRTIN(2, ISLOT),VAR 用 STRTIN(3, ISLOT) C ICON 用 STRTIN(4, ISLOT)参数说明就三句话:偏移量从 0 开始算,所以第 k 个元素是首地址加 k-1;访问前必须先确认对应计数大于 0;不同类别的数组不能互相借用入口,CON 的入口拿去索引 STATE,结果一定是脏数据。
3.3 自动调用:TBLCNC 对用户不可见意味着什么
这一条是最省事、也最容易被"想多了"的一条。与机组相关的自定义模型,在动态稳定计算时由子程序 TBLCNC 联入 PSS/E 主模块,而 TBLCNC 对用户不可见。因此这类模型按规则写完之后,编译通过就由 PSS/E 自动调用,相应地址也由 PSS/E 自动分配,用户不用手工在连接子程序里写调用语句,也不用指定地址。
对比一下就清楚了。与机组相关的模型走的是 TBLCNC 这条自动路径;原文同时点明,它不是通过 CONEC 调用的。反过来说,非机组类、需要自己接进框架的自定义模型,才需要在 CONEC、CONET 这类连接子程序里写明调用。两条路径的差别可以归成这样一张表:
| 对比项 | 与机组相关的自定义模型 | 需要手工连接的自定义模型 |
|---|---|---|
| 调用入口 | TBLCNC(对用户不可见) | CONEC、CONET 等连接子程序 |
| 地址分配 | PSS/E 自动分配 | 用户自己指定 |
| 是否写 CALL 语句 | 不需要 | 需要 |
| 写完之后的动作 | 编译通过即可 | 编译 + 连接子程序中登记 |
很多人第一次写机组模型,习惯性地去连接子程序里补一句CALL,结果是同一份逻辑被调用两次,输出翻倍或者状态量被算两遍,症状诡异且很难往"重复调用"上想。判断方法很直接:先确认自己的模型是不是与机组相关,是的话,连接子程序里就不该有它的名字。
3.4 模型库的调用顺序与依赖
PSS/E 模型库里,模型的调用顺序是固定的:发电机模型、电流补偿器模型、励磁稳定器模型、励磁模型、汽轮机/调速器模型、混合模型。这个次序不是随手排的,它反映的是量的依赖——先有发电机给出的机端与内电势,电流补偿器才能在其后处理电流相关的量;励磁稳定器给励磁提供附加信号,励磁再反过来作用于发电机;汽轮机/调速器管的是机械功率侧;混合模型放在最后收口。
对写自定义模型的人来说,这条次序的实际意义是:你要先回答"我这个模型属于哪一层"。属于励磁环节的模型,放到调速器之后的位置去考虑,读到的量就是过期的;属于混合模型的,指望它在励磁之前被调用也不现实。梳理下来会发现,调用顺序和标志位分支是一体两面:顺序决定"什么时候轮到我",标志位决定"轮到我时做什么",两者对不上,模型的表现就是时好时坏,很难复现。
注意:调用顺序是模型库层面的固定次序,自定义模型插进这条链时,先定位它替代或补充的是哪一类模型,再去对齐标志位分支,不要反过来做。
4. USRMDL 记录:动态数据输入文件里的字段拆解
模型写完了,还得让程序知道"有这么个模型、挂在哪儿、吃多少数据"。这件事在动态数据输入文件(Dynamics Data Input File)里完成,一条记录对应一个模型实例。这一章把这条记录拆开看,重点在字段顺序和四个计数上,因为这两处是纯粹的格式问题,错了一点提示都不给。
4.1 一条记录的字段顺序
原文给出的格式是:BUSID 'USRMDL' IM 'modelname' IC IT NI NC NS NV data list。逐个字段对下去:
| 字段 | 含义 | 写错会怎样 |
|---|---|---|
BUSID | 模型所挂的母线标识 | 挂错母线,模型作用在别的机组上 |
'USRMDL' | 固定关键字,声明这是用户自定义模型 | 拼错就被当成内置模型解析,整条记录读飞 |
IM | 机组标识,同一母线上区分多台机组 | 多机母线时张冠李戴 |
'modelname' | 模型名,必须与 Fortran 子程序名一致 | 找不到子程序,模型不生效 |
IC | 模型类别描述字段 | 分类错,可能不被对应流程调用 |
IT | 调用类别描述字段 | 同上 |
NI NC NS NV | 四类内部数组的计数 | 见 4.2,最危险的一组 |
data list | 模型初始化用的数据段 | 长度不够或顺序不对,初值全偏 |
一个具体的例子,某台机组上挂一个调速相关模型:
101 'USRMDL' '1' 'MYGOV' 1 0 4 6 3 2 0.05 1.20 0.30 0.10读法:母线 101,机组标识 '1',固定关键字 USRMDL,模型名 MYGOV,后面依次是 IC、IT、NI=4、NC=6、NS=3、NV=2,再往后是数据段。这里要说清楚,原始资料没有逐条解释 IC 与 IT 的取值含义,从位置上看它们排在计数之前,属于描述模型类别和调用类别的字段,写记录时以手头版本的模型清单为准,不要照抄别人的记录——版本之间、模型类型之间这两个值都可能不同。
4.2 NI、NC、NS、NV 四个计数
这四个计数是模型和框架之间的"内存合同":你声明用多少个 CON、多少个 STATE、多少个 VAR、多少个 ICON,PSS/E 就按这个数给你分配数组位置,STRTIN 的四个入口就是按这个分配结果写进去的。对应关系如下:
| 计数 | 全称 | 对应内部数组 | 对应 STRTIN 入口 |
|---|---|---|---|
NI | ICON | 整型常数 | STRTIN(4, ISLOT) |
NC | CON | 实型常数 | STRTIN(1, ISLOT) |
NS | STATE | 状态量 | STRTIN(2, ISLOT) |
NV | VAR | 中间变量 | STRTIN(3, ISLOT) |
注意:记录里的书写次序是 NI、NC、NS、NV,而 STRTIN 的入口次序是 NC、NS、NV、NI。两套次序不一样,这是这一块最容易背串的地方,抄格式的时候尤其要盯住。
次序不一致带来的后果是很实在的。假设你的模型实际用了 6 个 CON、3 个 STATE、2 个 VAR、4 个 ICON,正确写法是NI NC NS NV = 4 6 3 2。如果按 STRTIN 的顺序写成6 3 2 4,程序会认为你有 6 个 ICON、3 个 CON、2 个 STATE、4 个 VAR,分配出来的地址全乱,模型读到的第一个 CON 其实是别人的 STATE。这种错编译期发现不了,运行时也未必报错,只表现为曲线莫名其妙。
第二个容易忽略的点是计数与实际用量必须严格一致,不能多也不能少。多写了,白占内存还会让首地址偏移,影响后续模型的入口;少写了,代码里访问到超出声明范围的下标,写坏别人的数据。STRTIN 在计数为 0 时返回 0 这一条,就是给这种情况留的最后一个信号——只要你看到 0,就说明这类数组根本没申请。
4.3 数据段与计数的一致性检查
数据段(data list)是模型初始化时要读进去的初值,顺序和个数必须和模型里的读语句一一对应。这一步没有任何自动校验,数错了就是初值错,而初值错在动态仿真里往往要跑很久才看得出来。最稳的办法是手工核对三遍:先数记录里数据段的个数,再数子程序里读了多少个,最后对照计数看总量对不对得上。
记录多了以后手工数不现实,可以写个小脚本来数:
# 核对一条 USRMDL 记录的字段个数,不做内容校验 line = " 101 'USRMDL' '1' 'MYGOV' 1 0 4 6 3 2 0.05 1.20 0.30 0.10" # 去掉引号后按空白切分,模型的引号里通常不含空格 tokens = line.replace("'", " ").split() # 前 10 个是固定字段:BUSID, USRMDL, IM, modelname, IC, IT, NI, NC, NS, NV fixed, data = tokens[:10], tokens[10:] ni, nc, ns, nv = map(int, tokens[6:10]) # 注意次序仍是 NI NC NS NV print("模型名:", tokens[3]) print("数据段个数:", len(data), "| 计数之和:", ni + nc + ns + nv)脚本只做计数,不判断数据段该有多少个——因为不同模型的数据段构成不同,有的只给 CON,有的连 STATE 初值一起给。它的价值在于把"我数花眼了"这类人为失误挡掉,把注意力留给真正需要人判断的部分:数据段的排列顺序是否和读语句一致。
5. 易错点复盘:五处最容易翻车的地方
前面四章是正向梳理,这一章换个方向,从错的地方往回看。以下五条是这一类模型在自查时命中率最高的,每一条都按"易漏点是什么、为什么会漏、怎么补"写,可以直接当成检查表用。
5.1 只写了 MODE=1 一个分支
易漏点:子程序里只有初始化那一段,其余行为没有对应语句,或者写了但都塞在同一个分支里。为什么漏:因为初始化能跑通,稳态潮流也对得上,看起来"模型已经生效了"。而动态仿真真正吃的是推进阶段的分支,那部分没写,表现是曲线一条直线或者直接发散,很容易被误判成参数问题而不是代码问题。怎么补:把 2.3 那张对照表填满,每个行为至少有一行;在没有把握的分支里加计数输出,跑一个小扰动算例,确认每个分支都被进入过。只要有一个行为从没被进入,说明你要么分支值写错了,要么这个行为根本不需要你处理——两种情况都得弄清楚,不能含糊过去。
5.2 计数顺序抄反:NI NC NS NV 与 NC NS NV NI
易漏点:记录里四个计数的书写次序,和 STRTIN 四个入口的次序不一致,凭印象抄一遍就错位了。为什么漏:这两组数字看起来"就是一回事",加上编译期完全不校验,错了也不报错,只表现为模型读到的量不对。怎么补:固定一个记忆锚点——记录按字母序 NI、NC、NS、NV 写,入口按 CON、STATE、VAR、ICON 排;写记录时对着 4.2 那张表逐格填,不凭记忆;再用 4.3 的脚本把计数打出来看一眼,成本很低。另外,如果某个模型跑起来后第一个 CON 的值明显不像自己设的参数,优先怀疑这一条。
5.3 手工在连接子程序里补 CALL 和地址
易漏点:以为所有自定义模型都要像以前那样在 CONEC、CONET 里登记,于是给机组相关模型也补了一句调用,或者手工指定了数组地址。为什么漏:这是从别的模型类型带过来的习惯,而且"多写一句"看起来是保险而不是风险。实际后果是同一份逻辑被调用两次,状态量被更新两遍,输出翻倍,症状诡异到很难往这个方向想。怎么补:先给自己的模型定性——与机组相关的,由 TBLCNC 自动联入,地址由 PSS/E 自动分配,连接子程序里不该出现它的名字;不确定的时候,把连接子程序里所有 CALL 语句列一遍,逐个确认是不是必要的,多余的删掉再跑一次对比。
5.4 子程序头写歪:形参顺序、个数、类型
易漏点:SUBROUTINE modelname(MC, ISLOT)里的两个形参被改名、换序、少写,或者声明成了实型。为什么漏:编译能通过,因为 Fortran 对形参名的约束很松,名字随便起也合法;类型不匹配在旧式写法里还可能被隐式转换糊过去。怎么补:把子程序头当成不可改的一行,只允许在它下面写逻辑;每次接手别人的模型,第一件事是核对这一行;如果模型行为异常但又找不到逻辑错误,用调试器在入口处把 MC 和 ISLOT 的值打出来,看是否符合预期——ISLOT 出现明显不合理的值,基本就能定位到这里。
5.5 模型放错层:调用顺序与模型类型不匹配
易漏点:自定义模型插进调用链时,没先确认它属于哪一层,结果读到的量还没被算出来,或者已经被后续环节改过。为什么漏:调用顺序是框架层面的固定次序,平时看不到也感知不到,只有模型表现异常时才会想起它。怎么补:写模型之前先回答一个问题——它替代或补充的是发电机、电流补偿器、励磁稳定器、励磁、汽轮机/调速器、混合模型中的哪一类;定位清楚之后,再对照这个位置上"上游已经算完哪些量、下游还要用什么",把读写的量列出来核对。这条补起来最慢,但补一次能受益很久。
提示:这五条里有四条的共同点是"编译不报错、运行不报错、结果不对"。遇到这类问题,排查顺序建议从格式(5.2、5.4)到调用(5.3、5.5)再到逻辑(5.1),先排除不花钱的,再动需要重跑的。
6. 复习清单:把这份笔记压缩成一张纸
复习的时候不要从头读代码,按下面的顺序过一遍,二十分钟能把整个接口层捡回来。
第一轮看三个数:MC、ISLOT、四个计数。确认子程序头是SUBROUTINE modelname(MC, ISLOT),确认 ISLOT 被用来查 STRTIN,确认 NI、NC、NS、NV 四个数和记录里的写法一致、和代码里实际用量一致。这一轮只看不动,眼睛扫一遍就行。
第二轮看四张表。标志位表(MODE、KPAUSE、MSTATE、MIDTRM 各自对应哪个分支)、STRTIN 入口表(1 是 CON、2 是 STATE、3 是 VAR、4 是 ICON,计数为 0 时返回 0)、USRMDL 字段表(BUSID、'USRMDL'、IM、'modelname'、IC、IT、四个计数、data list)、调用顺序表(发电机、电流补偿器、励磁稳定器、励磁、汽轮机/调速器、混合模型)。四张表都能默出来,说明结构记住了。
| 自查项 | 通过标准 | 不通过时的动作 |
|---|---|---|
| 子程序头 | 形参、顺序、类型全对 | 直接改,不要试图绕过 |
| 计数一致性 | 记录与代码用量完全一致 | 用脚本数一遍字段个数 |
| 分支覆盖 | 每个行为分支都被进入过 | 加分计数输出,跑最小算例 |
| 连接方式 | 机组模型不在连接子程序里 | 删掉多余的 CALL |
| 层次定位 | 能说清属于六类中的哪一类 | 回到调用顺序重新定位 |
第三轮做一次"反向走查":从输出曲线倒推。曲线不对,先问是哪个行为没进、还是进了但算错;算错的话,是读到了 0 指针、还是下标偏移差了一位、还是计数少写了导致越界。这个顺序能把大部分玄学问题落到具体的一行上。
我自己踩得最深的一次,是给一个机组相关模型在连接子程序里补了调用,跑出来的曲线幅值正好是应该的两倍。当时盯着公式查了两天,最后是数分支进入次数时发现每个行为都被进了两遍,才回头看到那句多余的 CALL。从那以后我每次新建模型,都强制走一遍上面这三轮,尤其是"连接方式"那一格,先确认它该不该出现在连接子程序里,再动手写别的。希望帮到你。
本文还有配套的精品资源,点击获取