GAMS使用小记(二)

引言

前段时间使用GAMS进行了一些工作,本以为已经达到了通过java调整模型参数后运行并获得输出的目的。由于之前并未确定要调整哪些参数,所以当时任意选取了两个参数“gama”和“dk”进行了尝试(见本系列上一篇博文),这两个参数都成功了。然而,这几天修改为要调整其他参数时则无法正常运行,下面记录一下对这一问题的思考和解决。

补充说明:博文中涉及到的模型是DICE模型,这是一个评估气候变化的模型,使用GAMS进行建模,下载链接为DICE模型

问题

Not part of model

此问题相关代码如下。此时需要调整的参数变为gA0这个参数。

GAMSParameter gA0 = mi.SyncDB().addParameter("gA0", "Initial growth rate for TFP per 5 years");
mi.instantiate("CO2 us nlp max UTILITY", new GAMSModifier(gA0), new GAMSModifier(dk));

报错中核心的信息是:
**** The following parameters were remapped into variables
**** gA0
**** The resulting model is invalid. The following errors were detected
**** Parameter symbol gA0 not part of model
字面意思是gA0这个参数不是模型的一部分,对此我非常困惑,在我的理解里这里的gA0和之前的gama、dk的角色应该是完全一样的,同样的代码为什么只是换了应用对象就不行?

事后来看还是陷入了思维定式,以及对GAMS的理解比较浅(没有系统性学习容易出现这样的问题)。敏锐地意识到这一问题靠自己很难想通,我抱着试一试的心态给GAMS的技术支持人员发了邮件,两个小时后就收到了回复,摘录如下。


Not all parameters are going to be part of your actual model... 
and by "model" I literally mean the model instance not all the code contained in the .gms file. 
It's true that the parameters gA0, etc... are in the .gms file, 
but they are not in the model instance.  
These parameters are used in the data preparation step of the gms file...
for example the parameter aL does appear in the model, but it is calculated as:

aL("1") = A0; loop(t, aL(t+1)=aL(t)/((1-gA(t))););

and gA is calculated as:

gA(t)=gA0*exp(-delA*5*((t.val-1)));

这里指出了非常重要的一点,不是所有出现在.gms文件中的参数都会出现在模型实例(model instance)中,个人觉得这一点是有些反常的。后面又补充说这些参数是用于数据准备(data preparation)步骤,可以理解成中介的感觉,即这些变量用于计算其他出现在模型实例中的变量。(后来发现aL是出现在模型实例的,gA也没有出现,等于gA0在第三层……)

解决方案

关于解决方案也给出了建议,摘录如下。


So, you can do two things (this DICE model is not so large so you have options)...

1) you can pass in a value for gA0 like you are attempting to do with your java code, but instead of working with the model instance, 
you should just use java to re-run the entire gms file and collect the model output (this can be done with an intermediate GDX file, 
but if you are only working with scalar values you might want to consider setting a double dash parameter).

2) use the model instance and pass in the data that appears in the actual model (in this case you will need to do the extra data processing steps in java)

解决方案中提供了两个思路:一是不使用模型实例,而是使用代码修改gA0参数,并直接重新运行整个.gms文件(并且在要修改的参数是scalar时可以使用double dash parameter);二是仍然使用模型实例,但需要传入出现在模型实例中的变量,这意味着需要确定模型中的哪个变量是由gA0计算出来的,并且需要将这部分计算在java中完成,最后将这一变量传入模型实例。

方案二

一开始觉得方案二比较复杂,但尝试后觉得这种方案比较直接,在计算逻辑比较简单的情况下也不失为一种好的选择。首先应当明确什么参数属于出现在模型实例中的参数?根据之前的信息发现应当是出现在“** Equations of the model”中的参数是“真正”出现在模型中的参数。再结合参数gA0,在.gms文件中通过Crtl+F查找可以看到与gA0有关的语句如下,参数aL出现在了模型实例中。


aL("1") = A0; loop(t, aL(t+1)=aL(t)/((1-gA(t))););
gA(t)=gA0*exp(-delA*5*((t.val-1)));

总结来看,这里的逻辑是先通过gA0计算gA(t),再通过gA(t)计算aL(t),而aL是可以传入模型实例中的。这也意味着上述计算步骤需要在java中完成,部分关键代码如下。

public static void modelInstanceTest(){
       // basic setting
       String modellocation = "D:\\Working Environment\\GAMS\\2023_10\\test\\DICE2023-beta-3-17-3.gms";
       GAMSWorkspaceInfo wsInfo  = new GAMSWorkspaceInfo();
       wsInfo.setWorkingDirectory("D:\\Working Environment\\GAMS\\2023_10\\test");
       // create a workspace and related checkpoint
       GAMSWorkspace ws = new GAMSWorkspace(wsInfo);
       GAMSCheckpoint cp = ws.addCheckpoint();
       // load the original model and create the related checkpoint
       GAMSJob dice = ws.addJobFromFile(modellocation);
       dice.run(cp);
       GAMSModelInstance mi = cp.addModelInstance();
       // get the parameter "aL"
       GAMSParameter aL = mi.SyncDB().addParameter("aL",1, "Level of total factor productivity");
       // instantiate the ModelInstance and pass a model definition and Modifier to declare aL changeable
       mi.instantiate("CO2 us nlp max UTILITY", new GAMSModifier(aL));
       // need to process the data in java
       double[] gA0_list = new double[] { 0.07,0.06 }; //demo
       double[] gA_list = new double[102];
       double[] aL_list = new double[102];
       for (int i = 0; i < gA0_list.length; i++) {
           aL.clear();
           for (int t = 1; t < 102; t++) {
               gA_list[t]=gA0_list[i]*Math.exp(-0.0072*5*((t-1)));//gA(t)=gA0*exp(-delA*5*((t.val-1)));
               if (t==1){
                   aL_list[t]=5.84164; //aL("1") = A0;
               }
               else{ //loop(t, aL(t+1)=aL(t)/((1-gA(t))););
                   aL_list[t] = aL_list[t-1]/(1-gA_list[t-1]);
               }
           }
           int j = 1;
           for (GAMSParameterRecord rec : dice.OutDB().getParameter("aL")){
               aL.addRecord(rec.getKeys()).setValue( aL_list[j] );
               j++;
           }
           mi.solve();
           mi.SyncDB().export();
           System.out.println("  Modelstatus: " + mi.getModelStatus());
           System.out.println("  Solvestatus: " + mi.getSolveStatus());
           System.out.println("  utility function value: " + mi.SyncDB().getVariable("UTILITY").findRecord().getLevel());
           System.out.println("  Atmospheric temperaturer (deg c above preind)2100 : " + mi.SyncDB().getVariable("TATM").findRecord("17").getLevel());
       }
   }

计算的部分比较直观,因为对应的GAMS代码并不复杂,是一个递推的计算。然而在将计算结果赋值给aL(即在mi.SyncDB()中添加的参数)时遇到了一点挑战。难点在于aL是一个向量而不是标量,而循环添加时又涉及到index来对数组进行索引,最终摸索出上面的设置方法,可能不是最优的方案,但解决了问题。

方案一

至此,方案二算是走通了。对于方案一我首先尝试了下面两种直接修改参数的方法,代码如下。

GAMSParameter gA0 = dice.OutDB().getParameter("gA0");
for (int i = 0; i < gA0_list.length; i++) {   
   gA0.getFirstRecord().setValue(gA0_list[i]);
   dice.run(dice.OutDB()); 
}

这一方案可以看到在输出的.gdx文件(GAMS数据库文件)中gA0的值的确修改了,但是gA和aL的值都没有变化,最终模型的结果也没有变化,等于说gA0的值还是没有“真正”被修改。

dice = ws.addJobFromString( "gA0=0.07;", cp);
dice.run();

这一方法会生成一个java与GAMS的交互文件_gams_java_gjo2.gms,其中含有“gA0=0.07;”这一命令。这种情况下程序是可以正常运行的,但输出的结果不会变化,等于gA0也没有“真正”被修改。这两种方法实际上还是没有影响到模型实例,模型实例已经根据之前的gA0值生成了,再运行等于运行的还是模型实例,所以结果不会变化。

随后根据官方文档中double dash parameter的内容对模型中需要调整的参数修改了定义(使用$set)和引用(使用%…%)的方式,接着通过GAMS Studio打开terminal,在其中输入“gams DICE2023-beta-3-17-3.gms --gA0=0.07”成功地改变了参数gA0并运行了模型,于是我想着在java中传递这一指令,尝试了以下的代码。

dice = ws.addJobFromString( "gams DICE2023-beta-3-17-3.gms --gA0=0.07;", cp);
dice.run();

结果是无法运行提示语法有误,我依稀意识到“gams DICE2023-beta-3-17-3.gms --gA0=0.07;”这一句不能直接写到.gms文件中,并且上述代码也不能算是重新运行模型,但没有想明白要如何操作,于是又回复了一封邮件。非常感谢技术支持人员Adam,仍是很快地进行了回复,摘录如下。


When I wrote my last email my method #1 was something similar to this:

dice = ws.addJobFromString( "gams DICE2023-beta-3-17-3.gms --gA0=0.07;", cp);  
dice.run();

... except this call is not pure gams code, but instead a command to run a gams model file. 
I am more familiar with Python, and in Python you can launch a subprocess to loop through many runs with a command like this:

import subprocess
gA0vals = [<all your values here in a list>]
for gA0val in gA0vals:
    proc = subprocess.run(["gams", "DICE2023-beta-3-17-3.gms", f"gdx=outfile_{gA0val}.gdx", f"--gA0={gA0val}"])

I assume you can do something similar with java. 

看到这里我理解了他的思路,等于是要使用编程语言(java,python等)开启一个终端子进程并输入相关的命令,这一命令运行整个.gms文件(实现了重新运行整个模型)并指定输出的文件名,接下来再读取.gdx文件获得输出(参照教程中TransportGDX案例)。最后还有一个问题?如果要修改的是一个向量呢?这时double dash parameter不能直接使用了,再次询问后(Hope don’t bother……)得到如下的回复。


You are right, if you need to change a scalar then the double dash parameter would be good/convenient option...
however, if you have a vector... you could always create an input gdx file... 
then pass the name of the input gdx file as a double dash parameter and then read in the input gdx file in your gams model code.

意思是首先创建一个输入的.gdx文件(参照教程中TransportGDX案例),再将文件名作为double dash parameter传入,从而实现不同参数情况下模型的运行。

感想

  • 有些问题格外令人烦躁,如果基础不扎实直接去应用的话就容易这样,但有时又缺乏从头学起的动机或时间。
  • 勇于寻求外部帮助,专业人士的一点点拨都非常有帮助,有时自己就是陷入了误区或者盲点,但对别人来说这些是很清晰的。
  • 一个大型的工业软件有很多门道。
  • 感谢异国他乡的Adam。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值