NetLogo 进阶:导入 .nls 文件、Headless 模式、传递 Java 虚拟机参数
__includes
导入 .nls
文件
当模型包含多个例程时,全部写在一个 .nlogo
文件会显得臃肿、结构不清晰、可读性差,这就产生了拆分文件的需求。NetLogo 提供了一个实验性的 keyword __includes
,根据官方文档,用法如下
#! model.nlogo
__includes [ "directory/lib.nls" "directory/package.nls" ]
其中 lib.nls
和 package.nls
是两个 NetLogo 源文件(source file),directory
是存放该文件的目录。为更好展示其用法,本博客设计了以下例子,源代码放在 github 上。
单个文件
下面例子将一个打印 "Hello World!" 的程序复杂化,拆分成声明全局变量、初始化全局变量和应用全局变量、主例程 4 个步骤。这个例子虽然很愚蠢,但抽象自复杂 Model。
#! model.nlogo
; 声明全局变量
globals [
greeting
]
; 初始化全局变量
to setup
set greeting "Hello World!"
end
; 应用全局变量
to say-hi
print greeting
end
; 主例程
to go
say-hi
end
多个文件
当各个步骤变得复杂时,拆分文件可以让逻辑变得更清晰。下面把前 3 个步骤放到 3 个不同的 .nls
文件中
#! libs/global.nls
globals [
greeting
]
#! libs/setup.nls
to setup
set greeting "Hello World!"
end
#! libs/package.nls
to say-hi
print greeting
end
然后在主例程中导入 3 个源文件(假设均放在目录 libs
下)
#! split_model.nlogo
__includes ["libs/global.nls" "libs/setup.nls" "libs/package.nls" ]
to go
say-hi
end
下面对上述拆分做简单的分析。可以看到,.nlogo
文件仅有一个,只有该文件能够导入源文件,一个源文件可以直接使用另一个文件声明的变量(但不能重复声明,其实也可以调用另一个源文件定义的例程)。猜测源文件依附于模型文件存在,只有在其导入模型文件后才会生效,__includes
似乎只起到了“合并”文件的作用。这和 Python 中 .py
文件的地位有很大差别。
Headless 模式最佳实践
有时我们不关心交互式界面,希望在后台运行 model.nlogo
,比如希望重复试验,希望在没有桌面的服务器上运行。通过 NetLogo 的 Headless 模式(无头模式)可以实现。推荐使用 Java 调用 NetLogo,好处是使用简单,可以结合 Java 实现更复杂的功能的。参考官方文档,写了 Test.java
文件进行测试,代码放在 github 上。
package com.test;
import org.nlogo.headless.HeadlessWorkspace;
public class Test {
public static void main (String[] args) {
System.out.println("Testing the headless mode...");
// path for model.nlogo
String model = args[0];
try {
// initialization
HeadlessWorkspace workspace = HeadlessWorkspace.newInstance();
// open the model file
workspace.open(model);
// execute NetLogo commands
workspace.command("setup");
workspace.command("repeat 1 [ go ]");
// close the model file
workspace.dispose();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
该方法需要通过 -classpath
正确指定 NetLogo 的 jar 包的位置。此外可能遇到的坑是,模型文件无法正确导入 Java 扩展,需要将相应的 jar 包放到模型文件所在的目录下。
传递 Java 虚拟机参数
BehaviorSpace 固然灵活,但有时需要在 GUI 里面查看可视化结果,那如何在 NetLogo GUI 中设置环境变量、命令行传参呢?其实跟 BehaviorSpace 的道理是一样的。注意到 NetLogo 可以通过 netlogo.sh
或 netlogo-gui.sh
运行,那么我们只需在 .sh
脚本中指定环境变量和传参就可以了。因为 NetLogo 5.1.0 的 shell 脚本比较简单,仅以该版本为例。在笔者的项目中,有一个第三方扩展需要调用 R 语言做统计分析,需要指定 JRI 的路径。原始脚本为
#!/bin/sh
cd "`dirname "$0"`" # the copious quoting is for handling paths with spaces
# -Djava.library.path=./lib ensure JOGL can find native libraries
# -Djava.ext.dirs= ignore any existing JOGL installation
# -XX:MaxPermSize=128m avoid OutOfMemory errors for large models
# -Xmx1024m use up to 1GB RAM (edit to increase)
# -Dfile.encoding=UTF-8 ensure Unicode characters in model files are compatible cross-platform
# -jar NetLogo.jar specify main jar
# "$@" pass along any command line arguments
java -Djava.library.path=./lib -Djava.ext.dirs= -XX:MaxPermSize=128m -Xmx1024m -Dfile.encoding=UTF-8 -jar NetLogo.jar "$@"
指定 JRI 目录后的脚本为
#!/bin/sh
cd "`dirname "$0"`" # the copious quoting is for handling paths with spaces
# -Djava.library.path=./lib ensure JOGL can find native libraries
# -Djava.ext.dirs= ignore any existing JOGL installation
# -XX:MaxPermSize=128m avoid OutOfMemory errors for large models
# -Xmx1024m use up to 1GB RAM (edit to increase)
# -Dfile.encoding=UTF-8 ensure Unicode characters in model files are compatible cross-platform
# -jar NetLogo.jar specify main jar
# "$@" pass along any command line arguments
java -Djava.library.path=./lib -Djava.ext.dirs= -XX:MaxPermSize=128m -Xmx1024m -Dfile.encoding=UTF-8 -Djava.library.path=/home/rotopia/R/x86_64-pc-linux-gnu-library/4.1/rJava/jri -jar NetLogo.jar "$@"
经过上述的改动,笔者项目中需要调用 JRI 的第三方扩展可以正常工作。