GAMS二次开发教程(06):参数、标量、表格与数据录入——PARAMETERS/SCALARS/TABLES、$include 与美元控制 GAMS二次开发教程06参数、标量、表格与数据录入——PARAMETERS/SCALARS/TABLES、$include 与美元控制版本与许可声明工具/软件GAMS版本号以官方发行说明为准。Scalar/Parameter/Table 三种数据格式取自官方 “Data Entry” 章运行期参数与编译期变量的机制取自官方 “The GAMS Call” 与 “Dollar Control Options” 两章。语言/环境GAMS 语言纯文本.gms。本篇示例全部不调用SOLVE因此不需要求解器许可只需要 GAMS 能正常编译与执行。本文目标把“数据怎么进模型”从“手工抄几行”升级为“有格式判据、有文件分层、有编译期开关的数据入口”。许可边界本篇不涉及求解不触碰许可但Table的列头对齐、$include的路径解析、双横线参数的写法都会在模型一变大时立刻决定可维护性——这是后续所有工程化章节的地基。一句话结论GAMS 的数据入口只有三种关键字——Scalar零维退化列表/90/、Parameter列表式/ pl1 350, pl2 600 /默认值 0、Table二维及以上唯一“非自由格式”的语句数值字符位置必须与列头对齐三者共用一条硬规则默认一个符号只能有一条数据语句要分批追加须用$onMulti把数据拆进$include的独立文件、用编译期$if not set scenario $set scenario base与运行期--scenariohigh的%scenario%替换来切换数据集再用option decimals 2控制显示精度就是本篇要建立的四个肌肉记忆。〇、本篇要解决的认知问题Q1Scalar、Parameter、Table三种数据类型的语法差异与选型判据是什么为什么Table是唯一“非自由格式”的语句Q2列表式录入的默认值与一条数据语句限制是怎么回事$onMulti、$onEmpty各自放宽了哪条约束/#set 值/简写解决什么问题Q3怎样把数据从模型文件里拆出去$include与$onEcho file ... $offEcho分别在什么时机、解决什么问题Q4编译期$if/$ifthen/$set与运行期双横线参数--scenariox如何配合实现“一次代码、多套数据”Q5option decimals与dispWidth怎么控制报表精度与列头截断$GDXIN$load从 GDX 取数的第一步怎么写、为什么能在编译期定下集合的域一、机制解析1.1 三种数据类型语法差异与选型判据官方 “Data Entry” 章把参数的录入归成三种基本格式并为每种格式配了一个关键字。先把三条语法并排放好差异就一目了然关键字维度语法骨架官方典型写法Scalar0无域scalar[s] name [text] [/numerical_value/]Scalar f freight rate / 90 / ;Parameter1 维及以上列表式parameter[s] name[(index_list)] [text] [/ element [] value {, ...} /]Parameter cap(pl) / pl1 350, pl2 600 / ;Table2 维及以上表格式table name[(index_list)] [text] EOL … 行首 … 列头 …见代码 6-1选型判据只有三条没有域 →Scalar。官方说法是“标量是一个没有域的参数”用只有一个值的退化列表录入。f / 90 /这种单位换算率、容差、开关值都属此类。稀疏或多维键值对 →Parameter列表式。默认值为 0所以只写非零项标签元组用点号a.b连接salaries(employee,manager,department) / anderson.murphy.toy 6000 /。人工维护的二维及以上稠密表 →Table。官方对Table有一句必须记住的描述默认情况下table语句是 GAMS 语言里唯一非自由格式的语句——只有它在意“端行EOL”只有它的数值字符位置必须与列头对齐。这条特性可以用美元控制选项$onDelim改成自由格式但那就丢掉了“肉眼即矩阵”的最大好处。铁律 4语句/选项名以官方表为准Scalar/Parameter/Table是语句关键字option decimals/option dispWidth是选项名$onMulti/$onEmpty/$include/$onEcho/$if/$set是美元控制选项——这四类东西的名字与拼写必须回到官方页面确认后再写。凭记忆拼$onmulti、option decimal少个 s之类编辑期就会报 “unknown”。Table的排版规则要精确掌握否则一定会在“数值到底属于哪一列”上翻车数值列必须与列头字符位置重叠列头区必须放在一行内构成一行的数值必须写在同一行行首标签可以跨行同一列在整张表里只能出现一次至少一个空白分隔标签与数值空白单元格等价于默认值 0标签可以省略当整行/整列都是零时但不能重复Table声明后只能初始化一个标识符区别于Set/Scalar/Parameter可以一条语句声明多个。第 6 条与第 1 条是最容易踩的两个坑想用一条Table同时填两张表不允许以及用制表符排版导致 GAMS 的制表位与你编辑器显示的对不齐官方提醒制表位由命令行参数TabIn控制可能与编辑器显示不同。1.2 一条数据语句、默认值与两把“放宽钥匙”官方在 “Data Entry” 章开头给了一条总原则数据应当以其最基础的形式录入且每个数据项只录入一次。围绕这条原则产生了两条硬约束与两把放宽钥匙硬约束默认情况下所有录入格式的参数只能初始化一次之后必须用赋值语句修改。也就是说给同一个参数写第二条数据语句会报错与第 05 篇讲到的元素重复定义同属一类官方报错表里对应**** $172。钥匙一$onMulti——“打开数据重定义”允许对同一符号追加数据语句$onMultiR则是替换式。官方指出这正是“默认只能初始化一次”这条限制的开关。钥匙二$onEmpty——“允许空的数据初始化语句”。官方给了精准对比$onEmptyParameter a(i) / / ;会把a的每个元素初始化为 0而只写Parameter a(i) ;是“声明但未初始化”。这个差别不是洁癖问题未初始化的符号在被读取时会触发编译错误 141Symbol declared but no values have been assigned官方在该页明写“compilation error 141”。还有一个“少写很多字”的简写值得背下来当参数在某个集合上取同一个值时官方提供parameter name[(set_name)] /(#|set.)set_name numerical_value/ ;两种等价记号例如Set j / j1, j2 / ; Parameter hh(j) / set.j 10 / gg / #j 10 / ;结果hh(j1) hh(j2) gg(j1) gg(j2) 10。读法#j就是set.j意思是“给集合 j 的每个元素都赋 10”。铁律 10数值不臆造简写最危险的地方在于它“一次写对一个集合”。/#pl 20/看起来无害但它把安全库存这类业务量硬编码进了数据文件。凡是收率、价格、产能、库存阈值一律来自你的数据源或官方示例并标注来源查不到就写“以官方文档为准”不许凭印象凑一个“看起来合理”的数。1.3 多文件工程$include与$onEcho数据一旦变大第一件事就是把它从模型文件里搬走。GAMS 提供两个方向相反的美元控制选项$include把外部文件“读进来”编译期插入。语法是一条独立行$include filename。它的价值在于模型层main.gms保持稳定数据层tdata_*.gms可以独立维护、独立版本化。这是铁律 9 要求的“唯一入口 分层”在数据侧的最小实现。$onEcho file ... $offEcho把当前文件里的文本“写出去”编译期生成。官方$onEcho/$offEcho属于“影响输出格式”的一组选项配合重定向 file就能在编译期生成一个外部文件——这正是第 10 篇要用它生成gdxxrw命令文件的机制。两者都是编译期行为它们在 GAMS 建立符号表的过程中生效所以$include进来的Set/Parameter与直接写在主文件里语义完全相同也同样受域检查保护。铁律 9工程化目录纪律多文件工程只保留一个入口。数据文件必须是“纯数据”只有Set/Parameter/Table的声明与赋值不得夹带Model/Solve否则入口就不唯一改一处数据可能改出一堆副作用。1.4 编译期开关与运行期参数一次代码、多套数据这是本篇最值钱的一节。GAMS 有两条完全不同的“参数化”通道必须分清编译期条件编译conditional compilation。官方明确它是“在编译期对输入文件做条件处理”与运行期的if/loop不是一回事。核心选项是$if/$ifI大小写不敏感/$ifE表达式求值、$ifthen/$elseIf/$else/$endif块以及$set给编译期变量赋值。已核实的官方写法包括单行形态$if not set COMPILER $set COMPILER GNU来自官方示例Cex1.gms块形态$ifThen %MYVAR%1 … $elseIf %MYVAR%2 … $else … $endIf来自官方示例dumpopt1.848带标签形态$ifThen.out %system.buildcode%LAG … $endIf.out来自官方测试库。运行期双横线参数double dash parameters。官方把用户自定义的命令行参数称为“double dash parameters”它们与编译期变量配对使用。已核实的官方例子是文件里写$log value of dd: %dd%命令行gams spacy --dd normal日志输出normal。也就是说命令行--name value或--namevalue把编译期变量name绑定成字符串模型里用%name%取值。两条通道的分界线编译期开关能决定“哪段代码被编译”运行期参数只能影响“被编译的代码怎么算”。所以“切换整套数据集”属于编译期“在同一套代码里改几个数值反复重求解”属于运行期那是第 09 篇的loop范式。铁律 1版本先声明$if/$ifthen与双横线参数都属于“随分发演进较快”的特性官方示例里的%system.filesys%、%system.buildcode%这类系统属性在不同分发上取值不同。正文与代码一律以gams.com/latest/docs指向的当前分发为口径版本号以官方发行说明为准不写死某个分发号。1.5 报表控制与“取数第一步”两件收尾的机制option decimals value官方定义为“display 语句显示的小数位数”取值是0 到 8 的整数默认 3取 0 时会连小数点一起抑制。注意它不改变字段宽度只改小数位位数调大时超出字段宽度的数会自动落回 E 格式。列头被截断的问题用option dispWidth value解决正值、上限 31——官方例子中Dar_es_Salaam作为列头时默认被截成Dar_es_Sa~设dispWidth 15后完整显示。$GDXIN$load从 GDX 取数的第一步。官方 GDX 章把读 GDX 分成编译期与执行期两条路。编译期这条有独一无二的威力它允许你用 GDX 里的数据定义集合元素并在此后把该集合当作域使用。最短形态是$gdxin demanddata.gdx $load bdemand $gdxin读法$gdxin打开 GDX 文件$load bdemand把 GDX 里的demand读进 GAMS 参数b末尾的裸$gdxin关闭输入官方 Example 1 即此形态。$load还可以不带符号名——那是“列出文件里所有符号”的诊断用法。此外官方还提供$loadDC/$loadR/$loadM等变体以及从参数投影出集合的域加载写法$load iadata aadata把 GDX 参数adata的索引位置投影成一维集合i。这一节是第 10 篇的引子$GDXIN/$load是编译期读 GDX执行期另有一套execute_load/execute_loadpoint而“把 Excel 变成 GDX”则要靠仅 Windows 可用的GDXXRW。二、完整代码与逐行剖析代码 6-1ch06_datatypes.gms—— 三种数据格式、默认值与报表控制完整可运行$title ch06_datatypes - Scalar / Parameter / Table and display controls * 适用分发口径以 gams.com/latest/docs 当前分发为准版本号以官方发行说明为准 * 求解许可本段不调用 SOLVE无需求解器许可 * 平台GAMS 语句通用 * ---- 类型一Scalar。没有域只有一个数用退化列表 /value/ 录入 ---- Scalar f freight rate (usd per case per 1000 miles) / 90 / tol relative tolerance used by later checks / 1e-6 / ; * ---- 类型二Parameter。列表式录入默认值 0只写非零项 ---- Set pl plants / pl1, pl2, pl3 / mk markets / mk1, mk2, mk3 / ; Parameter cap(pl) capacity of plant pl (cases) / pl1 350 pl2 600 pl3 450 / dem(mk) demand at market mk (cases) / mk1 325 mk2 300 / ; * ---- 简写/#set value/ 等价于 /set.set_name value/ ---- * 给集合全部元素赋同一个值这里演示语法数值仅作示例 Parameter safety(pl) safety stock at plant pl (cases) / #pl 20 / ; * ---- 声明但不赋值留给执行期由赋值语句算出来 ---- Parameter unitCost(pl,mk) unit transport cost (k usd per case) leadTime(pl,mk) lead time in days ; * ---- 类型三Table。二维及以上唯一“非自由格式”的语句 ---- * 列头写第二维 mk 的元素行首写第一维 pl 的元素空白格 0 Table dist(pl,mk) distance in thousands of miles mk1 mk2 mk3 pl1 2.5 1.7 1.8 pl2 2.5 1.8 1.4 pl3 3.1 2.2 0.9 ; * ---- 执行期赋值派生数据一律算出来绝不手工录入 ---- unitCost(pl,mk) f * dist(pl,mk) / 1000 ; leadTime(pl,mk) 1 2 * dist(pl,mk) ; * ---- 报表控制decimals 取值 0..8默认 3dispWidth 上限 31 ---- option decimals 2 ; option dispWidth 15 ; * ---- 空参数初始化只有在 $onEmpty 下才允许 / / 这种空列表 ---- $onEmpty Parameter spare(pl) idle capacity placeholder (all entries zero) / / ; $offEmpty Display f, tol, cap, dem, safety, unitCost, leadTime, spare ;逐行剖析Scalar f ... / 90 / ;用的是退化列表——只有一个值的斜杠列表。tol / 1e-6 /演示允许科学计数法。标量就是“没有域的参数”所以它没有索引。Parameter cap(pl) ... / pl1 350 ... / ;是列表式标签与数值之间用空格或等号分隔条目之间用逗号或换行分隔整个列表用斜杠包裹。dem(mk)只写了mk1、mk2mk3自动为 0——这就是“默认值 0只写非零项”。safety(pl) ... / #pl 20 / ;是官方简写#pl与pl.pl等价表示“对集合pl的每个元素赋 20”。它的语义是“全体同值”一旦某个厂的库存不同就必须展开成显式列表。Table dist(pl,mk)的排版是语义位置敏感的mk1/mk2/mk3三个列头的字符位置决定了下面每一行第几段数字属于哪一列。空白在表里不是 0而是“未指定”其效果等同于默认值 0但如果你打算以后再填用 0 显式写出更安全。unitCost(pl,mk) f * dist(pl,mk) / 1000 ;是派生数据的唯一正确写法能算的不要抄。改一个f整张单位成本表自动更新——这正是“每个数据项只录入一次”原则的落地。option decimals 2 ;让后面所有Display只显示 2 位小数option dispWidth 15让列头不被截成 9 字符。两者都是全局显示控制影响其后所有 display。$onEmpty/ /是“全零初始化”。注意与Parameter spare(pl) ;的区别后者是未初始化读取时会报 141。代码 6-2ch06_main.gms 两个数据文件 ——$include多文件工程 双横线参数切换完整可运行第一步数据文件tdata_base.gms* data file for scenario base * 本文件只放数据不得出现 Model / Solve保证入口唯一 cap(pl1) 350 ; cap(pl2) 600 ; cap(pl3) 450 ; dem(mk1) 325 ; dem(mk2) 300 ; dem(mk3) 275 ;第二步数据文件tdata_high.gms* data file for scenario high (demand grows by 20 percent) dem(mk1) 390 ; dem(mk2) 360 ; dem(mk3) 330 ;第三步入口ch06_main.gms$title ch06_main - multi-file project driven by a double dash parameter * 适用分发口径$include / $ifThen / $set 与双横线参数以官方 Dollar Control Options 与 Gams Call 页为准 * 求解许可本段不调用 SOLVE无需求解器许可 * 平台GAMS 语句通用 * 用法gams ch06_main.gms --scenariobase * gams ch06_main.gms --scenariohigh * ---- 编译期默认值调用时未给 --scenario则用 base ---- $if not set scenario $set scenario base * 把实际生效的场景值打到日志里便于复核 $log scenario in use: %scenario% Set pl plants / pl1, pl2, pl3 / mk markets / mk1, mk2, mk3 / ; Parameter cap(pl) capacity of plant pl (cases) dem(mk) demand at market mk (cases) ; * ---- 条件编译按 %scenario% 的值决定编译哪一份数据文件 ---- $ifThen %scenario%base $include tdata_base.gms $elseIf %scenario%high $include tdata_high.gms $else $error --scenario%scenario% but should be base or high $endIf * ---- 一个完全由数据决定的汇总参数用于验证分支确实生效 ---- Scalar totDem total demand across all markets (cases) ; totDem sum(mk, dem(mk)) ; Display cap, dem, totDem ;逐行剖析$if not set scenario $set scenario base是单行条件编译若编译期变量scenario尚未被“设置”命令行未给就用$set给它一个默认值base。官方示例Cex1.gms用的就是这个形态$if not set COMPILER $set COMPILER GNU。没有这一行未给--scenario时%scenario%会展开成不符合预期的文本导致$else分支被走到。$log scenario in use: %scenario%用尖括号包住变量把实际生效值打印到日志。官方spacy.gms例子正是用这个技巧证明--dd normal让%dd%输出normal。这是排查“参数到底有没有传进来”的最快手段。$ifThen %scenario%base … $elseIf … $else … $endIf是块式条件编译。官方dumpopt1.848用的是$ifThen %MYVAR%1这种带引号的比较写法引号在这里是必需的因为它把两边的值当成字符串比较。$error --scenario… but should be base or high是编译期主动报错。官方测试库ifthen6.gms就用$error --opt%opt% but should be u|uv|ua|uav|cplex拒绝非法取值。这比让模型带着错数据算下去安全得多——非法场景应当在编译期就死掉。totDem sum(mk, dem(mk)) ;是验证用的探针base场景下应得 900high场景下应得 1080。判定分支是否真的生效不要靠眼看 include 哪一行要看数据算出来的数。整段刻意没有出现Model/Solve这是铁律 9 要求的数据层与模型层解耦——数据文件只负责“填数”入口只负责“装配与调度”。代码 6-3ch06_make.gmsch06_load.gms——$GDXIN$load编译期取数两步运行第一步ch06_make.gms先产出一个 GDX 文件。$title ch06_make - produce a gdx file for the compile-time load demo * 适用分发口径execute_unload 以官方 GDX 页为准 * 求解许可不调用 SOLVE无需求解器许可 * 平台GAMS 语句通用GDX 是三平台通用的二进制交换格式 Set pl plants / pl1, pl2, pl3 / mk markets / mk1, mk2, mk3 / ; Parameter cap(pl) capacity of plant pl (cases) / pl1 350, pl2 600, pl3 450 / dem(mk) demand at market mk (cases) / mk1 325, mk2 300, mk3 275 / ; * execute_unload执行期把符号写进 GDX第 10 篇会展开讲读写全貌 execute_unload ch06_data.gdx, cap, dem ;第二步ch06_load.gms编译期从 GDX 取数并当作域使用。$title ch06_load - compile time read of gdx via $gdxin and $load * 适用分发口径$gdxin / $load 语法以官方 GDX 页 Example 1 为准 * 求解许可不调用 SOLVE无需求解器许可 * 平台GAMS 语句通用 * 前置条件先运行 ch06_make.gms 生成 ch06_data.gdx * ---- 编译期打开 GDX并把 GDX 里的元素先装进集合 ---- * 先读集合再读参数参数就能在“已知域”的情况下做域检查 $gdxin ch06_data.gdx Set pl(*) plants mk(*) markets ; $load plpl $load mkmk Parameter cap(pl) capacity of plant pl (cases) dem(mk) demand at market mk (cases) ; $load capcap $load demdem $gdxin Scalar totCap total capacity read from gdx (cases) ; totCap sum(pl, cap(pl)) ; Display pl, mk, cap, dem, totCap ;逐行剖析为什么必须是两步$GDXIN/$load是编译期行为而execute_unload是执行期行为。同一个文件里先execute_unload再$gdxin是无效的——编译早已结束。这正是“编译期/执行期分界”的实战后果也是第 04 篇分界的第一次真实代价。工程做法是数据准备脚本先跑一次产出 GDX模型脚本再读它。Set pl(*) plants ;里的(*)表示“域由外部给定”。官方 Example 2 就是这种写法Set J(*) markets;$load jmarkets官方同时给了更聪明的$load iadata aadata投影写法从参数里把索引位置投影出来当集合。$load plpl的读法是“GAMS 侧名 GDX 侧名”。这里两边同名若 GDX 里叫markets就写$load mkmarkets官方 Example 1 即$load bdemand。$gdxin的两次出现分工明确第一次带文件名是打开最后一次裸写是关闭。养成“用完就关”的习惯否则同一符号在后续语句里再$load会互相干扰。顺序有讲究先把pl/mk读进来再读cap/dem这样两个参数在声明之后、$load之时已经有确定的域域检查可以正常工作。反之先读参数会踩到“域未知”的编译错误——官方在 Example 4 里专门解释过这一点GAMS 不允许在域的集合数据未知时提供受域检查的数据。三、常见报错与排查报错 3-1**** $141 Symbol declared but no values have been assigned。现象参数被声明但从未赋值却在后续语句里被读取时编译报错。根因Parameter a(i) ;只是声明不是初始化官方在 Data Entry 章明确“声明但未初始化读取时会报 error 141”另一个常见触发是用执行期工具填数——官方在 GAMS Tools Library 页指出若符号由执行期工具首次填充编译器不知道结果会报 141。解法要么在声明处给出/ /配合$onEmpty或真实数据要么给$onImplicitAssign或先用execute_load$0 symName;这种“空操作”告诉编译器该符号已被定义。报错 3-2**** $172元素/数据重复定义。现象给同一个参数写第二条数据语句。根因默认一个符号只能有一条数据语句官方 Data Entry 章“By default, parameters in all input formats may only be initialized once”。解法把两条数据合并成一条确实要分批追加时用$onMulti追加或$onMultiR替换显式打开重定义。报错 3-3表格数值归属歧义。现象Table报错并标出某个数值。根因官方原文——“If there is any uncertainty about which column a number belongs to, GAMS will protest with an error message and mark the ambiguous entry.”即某个数值的字符位置没有落在任何列头的覆盖范围内。解法用编辑器的列标尺对齐列头与数值不要用 Tab 对齐官方提醒制表位由命令行参数TabIn控制可能与编辑器显示不一致。报错 3-4**** $170 Domain violation for element。现象Parameter列表或Table行首/列头里的标签报$170。根因标签拼写与集合声明不一致或该标签根本不属于对应集合域检查在替你工作。解法逐字对照Set声明核对Table尤其容易把两个集合的行列写反把mk的元素写到行首。报错 3-5$include找不到文件。现象编译开始即报错并中止。根因文件名或相对路径写错。GAMS 的文件名解析以当前工作目录为基准官方在 GDXXRW 页也说明命令行下相对文件名相对当前目录GAMS Studio 下相对当前项目目录。解法把数据文件与入口放在同一目录或写显式相对路径避免在文件名里嵌入空格若必须则用双引号包裹。报错 3-6双横线参数“传了却没生效”。现象%scenario%展开成非预期文本或走的始终是$else分支。根因把自定义参数写成了单横线——单横线是 GAMS 内置命令行参数如-o、-logOption对应用户自定义参数必须用双横线--scenariohigh。另外若命令行与模型内$set都给了值命令行优先它是先于模型内$set生效的编译期赋值。解法统一用--name value或--namevalue在模型里加一行$log ... %name%把实际值打出来对照。报错 3-7**** $198对非有序集用ord/lag。现象把某个集合当作“第几期”用ord却报错。根因该集合不是“一维 静态 定义顺序等于进入顺序”的有序集——把时间类集合放到了数据文件里、且晚于其它含同类标签的声明。解法时间集合的声明前移到最前面$include拆分时保证“先声明集合、再声明参数”的物理顺序不被破坏。四、动手练习练习 1三格式等价运行代码 6-1。判定(a)cap得 350/600/450dem得 325/300/0mk3默认 0(b)safety三个元素均为 20©unitCost(pl1,mk1)等于90*2.5/1000 0.225(d)spare显示为全 0 而非报错。能说出/#pl 20/与展开成pl1 20, pl2 20, pl3 20在语义上完全等价。练习 2改decimals观察把代码 6-1 的option decimals 2改成 0与 8各跑一次。判定能说出“decimals默认值 3、取值 0…8、0 会连小数点一起抑制、不改变字段宽度”并解释为什么unitCost的某些值在decimals 0时被显示为 0。练习 3多文件 双横线运行代码 6-2 的两次调用。判定(a)--scenariobase时totDem为 900(b)--scenariohigh时totDem为 1080© 不带参数时走默认base日志里%scenario%显示base(d)--scenariolow时**编译期报$error**并给出可读提示。练习 4编译期取数先跑ch06_make.gms生成ch06_data.gdx再跑ch06_load.gms。判定(a) 工作目录出现ch06_data.gdx(b)pl、mk的元素与第一步一致©totCap为 1400(d) 能解释为什么把$gdxin写在execute_unload之后一定失败。练习 5放宽约束给dem(mk)追加第二条数据语句写mk3先不加$onMulti观察报错再加$onMulti重跑。判定能复现“默认只能初始化一次”的报错并说明$onMulti与$onMultiR追加/替换的差别。五、小结与下一篇预告本篇把“数据怎么进模型”这一层打通落成三条判据与四把钥匙。三条判据没有域用Scalar、稀疏键值对用Parameter、稠密二维表用TableTable是唯一“非自由格式”的语句列头位置即语义派生数据一律用赋值语句算绝不手工录入。四把钥匙$onMulti放开重定义、$onEmpty允许空初始化并置 0、$include编译期读入外部数据文件、$onEcho file编译期生成外部文件。机制层面把两条“参数化通道”分清编译期用$if/$ifthen/$set决定“编译哪段代码”运行期用双横线参数--name value让%name%持有外部传入的字符串。最后一步是$GDXIN$load它是编译期取数唯一能做“用 GDX 数据定义集合、再把该集合当域”的通道。需要带走的纪律有三条数据文件只放数据、入口唯一铁律 9语句与选项名一律回官方页面核对铁律 4收率、价格、产能、阈值等数值必须可追溯查不到就写“以官方文档为准”铁律 10。第 07 篇《变量、方程与模型装配》转向“模型”这一层Variables的九种类型与八个属性.l/.lo/.up/.fx/.m/.scale/.prior/.stage、为什么“每个模型至少有一个自由变量”、Equations的“先声明后定义”与六个关系符e/l/g/n/x/b、Model语句的/all/与显式列名/嵌套//-、以及x(freeLinks)这种“在模型语句里限域”的写法 —— 那是把“散装的方程”装配成“可交付的模型”并用modelstat/solvestat判定成败的一篇。本篇认知问题回显FAQQ1Scalar、Parameter、Table三种数据类型的语法差异与选型判据是什么为什么Table是唯一“非自由格式”的语句AScalar是零维无域参数语法为scalar[s] name [text] [/value/]用只有一个值的退化列表录入Parameter是列表式语法为parameter[s] name[(index_list)] [text] [/element [] value .../]默认值 0只需写非零项多维标签元组用点号连接Table是表格式语法为table name[(index_list)] [text]换行后列出列头与各行数据必须二维及以上且一条table语句只能初始化一个标识符。选型判据没有域用Scalar稀疏或键值对型数据用Parameter人工维护的二维及以上稠密数据用Table。Table之所以是唯一非自由格式的语句是因为它唯一在意端行EOL数值的字符位置必须与列头位置重叠、每行数据必须同一行、同一列只能出现一次、空白格等价于默认值 0官方说这条特性可以用$onDelim改成自由格式但会失去“肉眼即矩阵”的好处。Q2列表式录入的默认值与一条数据语句限制是怎么回事$onMulti、$onEmpty各自放宽了哪条约束/#set 值/简写解决什么问题A参数默认值为 0因此列表式只需写非零项且条目顺序无关标量用单值退化列表。硬约束是“默认情况下所有输入格式的参数只能初始化一次”之后必须用赋值语句修改——对同一参数写第二条数据语句会报重复定义一类错误对应$172。$onMulti放宽这条限制允许向已有数据追加$onMultiR为替换式。$onEmpty放宽的是“不允许空初始化列表”这条约束$onEmpty加Parameter a(i) / / ;会把a的每个元素初始化为 0而只写Parameter a(i) ;是“声明但未初始化”读取时会触发编译错误 141。/#set 值/等价于/set.set_name 值/解决的是“整个集合取同一值”的批量赋值例如Parameter hh(j) /set.j 10/ gg /#j 10/令hh与gg的每个元素都是 10一旦各元素取值不同就必须展开成显式列表。Q3怎样把数据从模型文件里拆出去$include与$onEcho file ... $offEcho分别在什么时机、解决什么问题A两者都是编译期行为。$include filename把外部文件“读进来”让入口main.gms保持稳定、数据文件独立维护读入的Set/Parameter与直接写在主文件里语义完全相同同样受域检查保护工程纪律是数据文件只放数据、不得夹带Model/Solve以保证入口唯一。$onEcho file ... $offEcho方向相反把当前文件里的文本“写出去”生成一个外部文件适合在编译期生成命令文件或选项文件第 10 篇会用它生成gdxxrw的命令文件。另外 GAMS 的文件名解析以当前工作目录为基准Studio 下以当前项目目录为基准写相对路径时要留意这一点。Q4编译期$if/$ifthen/$set与运行期双横线参数--scenariox如何配合实现“一次代码、多套数据”A两条通道分工明确。编译期用条件编译$if/$ifI大小写不敏感/$ifE表达式求值以及$ifthen/$elseIf/$else/$endif块和$set决定“哪段代码被编译”运行期参数只影响“被编译的代码怎么算”。配合方式是命令行用双横线参数把值绑到编译期变量上例如gams ch06_main.gms --scenariohigh模型里用%scenario%取值。已核实的官方写法包括单行形态$if not set scenario $set scenario base、块形态$ifThen %scenario%base ... $elseIf %scenario%high ... $else ... $endIf以及非法取值时用$error主动中止编译。注意单横线是 GAMS 内置命令参数用户自定义参数必须用双横线命令行给定的值优先于模型内的$set。调试时最有效的手段是$log ... %scenario%把实际生效值打到日志里。Q5option decimals与dispWidth怎么控制报表精度与列头截断$GDXIN$load从 GDX 取数的第一步怎么写、为什么能在编译期定下集合的域Aoption decimals value控制 display 的小数位取值 0 到 8 的整数默认 3取 0 时连小数点一起抑制且它只改小数位不改字段宽度位数调大时超宽的数值会自动落回 E 格式。option dispWidth value控制列头字符宽度正值且不超过 31用于避免长标签被截断如Dar_es_Salaam默认被截成Dar_es_Sa~。$GDXIN$load是编译期读 GDX 的通道最短形态是$gdxin demanddata.gdx、$load bdemand、末尾裸$gdxin关闭$load的读法是“GAMS 侧名 GDX 侧名”不带符号名时用于列出文件内全部符号。它之所以能在编译期定下集合的域是因为编译期读允许用 GDX 里的数据定义集合元素集合一旦有了元素就能在此后作为域使用并让域检查生效因此正确顺序是先用Set pl(*)声明、再$load plpl把元素读进来、之后才$load capcap读参数。还要注意$GDXIN是编译期、execute_unload是执行期二者不能在同一文件中按“先写后读”的顺序串联。