Skip to content
Flynn049 edited this page Oct 8, 2023 · 18 revisions

NetLogo can be invoked and controlled by another program running on the Java Virtual Machine. For example, you might want to call NetLogo from a small program that does something simple like automate a series of model runs. Or, you might want to embed NetLogo models in a larger application.

This page introduces this facility for Java and Scala programmers. We'll assume that you know Java or Scala and associated tools and practices. But note that our API's are also usable from other languages for the Java Virtual Machine, such as Clojure, Groovy, JRuby, Jython, etc.

Note: The controlling facility is considered "experimental". It is likely to continue to change and grow. Code you write now that uses it may need changes in order to continue to work in future NetLogo versions.

For discussion of NetLogo API's and NetLogo development in general, visit http://groups.google.com/group/netlogo-devel.

The NetLogo API Specification contains further details. Details on transitioning from the NetLogo 5 to NetLogo 6 API can be found in the 6.0 extension and controlling API transition guide.

Starting a Java VM for NetLogo

NetLogo makes several assumptions about the Java VM that it is running in, and therefore there are arguments which should be given to the VM at startup.

Recommended options for both GUI and headless

-Xmx1024m

Use up to 1 gigabyte of memory for Java VM heap. You may need to grow this number in order to run some models.

-Dfile.encoding=UTF-8

Force all file I/O to use UTF-8 encoding. Ensures that NetLogo can load and save all models consistently, and that file-* primitives work consistently on all platforms, including models containing Unicode characters.

Additional recommended options for GUI only

-Djava.library.path=./lib

Not needed on Mac or Windows; may be needed on other OS's such as Linux. Ensures NetLogo can find native libraries for JOGL and other extensions. If you are not starting the VM in the top-level NetLogo directory, then ./lib should be changed to point to the lib subdirectory of the NetLogo installation.

Current working directory

The NetLogo application assumes that the current working directory at startup time is the installation directory in which NetLogo.jar resides. On Mac OS X this is NetLogo.app/Contents/Java/, on Windows this is C:\<Path to NetLogo>\app, and on linux this is <path to NetLogo>/app. The lib subdirectory (relative to the working directory) includes native libraries for Mac (is not needed on other platforms) and the natives subdirectory (relative to the working directory) includes native libraries for all platforms.

NetLogo further assumes that there will be models, docs, and extensions subdirectories of the working directory, but will look for them at a specific path if following VM options are set:

  • Docs - -Dnetlogo.docs.dir=<path-to-docs>
  • Extensions - -Dnetlogo.extensions.dir=<path-to-extensions>
  • Models - -Dnetlogo.models.dir=<path-to-models>

Example (with GUI)

Here is a small but complete program that starts the full NetLogo application, opens a model, moves a slider, sets the random seed, runs the model for 50 ticks, and then prints a result:

importorg.nlogo.app.App;
publicclassExample1 {
publicstaticvoidmain(String[] argv) {
App.main(argv);
try {
java.awt.EventQueue.invokeAndWait(
newRunnable() {
publicvoidrun() {
try {
App.app().open(
"models/Sample Models/Earth Science/"
+ "Fire.nlogo");
}
catch(java.io.IOExceptionex) {
ex.printStackTrace();
}}});
App.app().command("set density 62");
App.app().command("random-seed 0");
App.app().command("setup");
App.app().command("repeat 50 [ go ]");
System.out.println(
App.app().report("burned-trees"));
}
catch(Exceptionex) {
ex.printStackTrace();
}
}
}

The equivalent code in Scala:

importjava.awt.EventQueueimportorg.nlogo.app.AppobjectExample1 {
defmain(args: Array[String]) {
App.main(args)
wait {
App.app.open("models/Sample Models/Earth Science/Fire.nlogo")
}
App.app.command("set density 62")
App.app.command("random-seed 0")
App.app.command("setup")
App.app.command("repeat 50 [ go ]")
println(App.app.report("burned-trees"))
}
defwait(block: =>Unit) {
EventQueue.invokeAndWait(
newRunnable() { defrun() { block } } ) }
}

In order to compile and run this, NetLogo.jar (from the NetLogo distribution) must be in the classpath. In addition, the lib directory (also from the NetLogo distribution) must be in same location; it contains additional libraries used by NetLogo.jar.

If you are using Scala, you'll need to make sure you are using Scala 2.9. (2.9.1, 2.9.2, and 2.9.3 are all acceptable.) Other versions such as 2.10 will not work.

Note the use of EventQueue.invokeAndWait to ensure that a method is called from the right thread. This is because most of the methods on the App class may only be called some certain threads. Most of the methods may only be called from the AWT event queue thread; but a few methods, such as main() and commmand(), may only be called from threads other than the AWT event queue thread (such as, in this example, the main thread).

Rather than continuing to discuss this example in full detail, we refer you to the NetLogo API Specification, which documents all of the ins and outs of the classes and methods used above. Additional methods are available as well.

Example (headless)

The example code in this case is very similar to the previous example, but with methods on an instance of the HeadlessWorkspace class substituted for static methods on App.

importorg.nlogo.headless.HeadlessWorkspace;
publicclassExample2 {
publicstaticvoidmain(String[] argv) {
HeadlessWorkspaceworkspace =
HeadlessWorkspace.newInstance() ;
try {
workspace.open(
"models/Sample Models/Earth Science/"
+ "Fire.nlogo");
workspace.command("set density 62");
workspace.command("random-seed 0");
workspace.command("setup");
workspace.command("repeat 50 [ go ]") ;
System.out.println(
workspace.report("burned-trees"));
workspace.dispose();
}
catch(Exceptionex) {
ex.printStackTrace();
}
}
}

The equivalent code in Scala:

importorg.nlogo.headless.HeadlessWorkspaceobjectExample2 {
defmain(args: Array[String]) {
valworkspace=HeadlessWorkspace.newInstance
workspace.open(
"models/Sample Models/Earth Science/Fire.nlogo")
workspace.command("set density 62")
workspace.command("random-seed 0")
workspace.command("setup")
workspace.command("repeat 50 [ go ]")
println(workspace.report("burned-trees"))
workspace.dispose()
}
}

And in Clojure:

(import org.nlogo.headless.HeadlessWorkspace)
(defworkspace (doto (HeadlessWorkspace/newInstance)
(.open"models/Sample Models/Earth Science/Fire.nlogo")))
(doto workspace
(.command"set density 62")
(.command"random-seed 0")
(.command"setup")
(.command"repeat 50 [ go ]"))
(println (.report workspace "burned-trees"))
(.dispose workspace)

In order to compile and run this, NetLogo.jar must be in your classpath. The lib directory, containing additional required libraries, must also be present. When running in a context that does not support a graphical display, the system property java.awt.headless must be true, to force the VM to run in headless mode.

Since there is no GUI, NetLogo primitives which send output to the command center or output area now go to standard output instead. export-world can still be used to save the model's state. export-view works for writing an image file with a snapshot of the (otherwise invisible) 2D view. The report() method is useful for getting results out of the model and into your extension code.

The files generated by export-world include the contents of all plots. You can also export the contents of plots individually using export-plot.

You can make multiple instances of HeadlessWorkspace and they will operate independently on separate threads without interfering with each other.

When running headless, there are some restrictions:

  • The movie-* primitives are not available; trying to use them will cause an exception.
  • user-* primitives which query the user for input, such as user-yes-or-no will cause an exception.

The NetLogo API Specification contains further details.

In order to run 3D headless you must make sure that the org.nlogo.is3D property is set, you can either do this by starting Java with the -Dorg.nlogo.is3d=true option, or you can set it from within Java by using System.setProperty as follows:

publicstaticvoidmain(String[] args) {
org.nlogo.awt.EventQueue.invokeLater(
newRunnable() {
publicvoidrun() {
System.setProperty("org.nlogo.is3d", "true");
HeadlessWorkspaceworkspace = HeadlessWorkspace.newInstance() ;
try {
workspace.open("models/3D/Sample Models/"
+ "DLA 3D.nlogo");
workspace.command("set wiggle-angle 70");
workspace.command("random-seed 0");
workspace.command("setup"); workspace.command("repeat 50 [ go ]") ;
System.out.println(workspace.report(
"count patches with [pcolor = green]")); workspace.dispose(); } catch(Exceptionex) {
ex.printStackTrace(); }}});
}

Note that org.nlogo.is3D must be set before creating the workspace.

Example (HubNet headless)

This example code shows how to run HubNet headlessly. It opens the Template model, runs both the hubnet-reset and setup commands, and then runs the go command in a loop, forever.

importorg.nlogo.headless.HeadlessWorkspace;
classHubNetHeadlessServerExample {
staticpublicvoidmain(String[] args) {
HeadlessWorkspaceworkspace = HeadlessWorkspace.newInstance();
try {
workspace.open("models/HubNet Activities/Code Examples/Template.nlogo");
workspace.command("hubnet-reset");
workspace.command("setup");
while(true) {
workspace.command("go");
}
}
catch(Exceptionex) {
ex.printStackTrace();
}
}
}

The next example shows not only how to run HubNet headlessly, but allows for commands to be entered while the model is running. Any commands entered by the while the go command is executing will be run immediately after it finishes.

classHubNetHeadlessServerExample {
staticpublicvoidmain(String[] args) {
HeadlessWorkspaceworkspace = HeadlessWorkspace.newInstance();
CommandLineThreadcommandLine = newCommandLineThread(workspace);
commandLine.start();
try {
workspace.open(
"models/HubNet Activities/Code Examples/Template.nlogo");
workspace.command("hubnet-reset");
workspace.command("setup");
while (true) {
workspace.command("go");
commandLine.runCommands();
}
}
catch (Exceptionex) {
ex.printStackTrace();
}
}
staticclassCommandLineThreadextendsThread {
privatefinalHeadlessWorkspaceworkspace;
privateArrayBlockingQueue<String> queuedCommands =
newArrayBlockingQueue<String>(10);
publicCommandLineThread(HeadlessWorkspaceworkspace) {
super("Command Line Thread");
this.workspace = workspace;
}
publicvoidrun() {
System.out.println("enter command> ");
while (true) {
try {
queuedCommands.put(readLine());
}
catch (Exceptione) {
e.printStackTrace();
}
}
}
privatevoidrunCommands() {
for (Stringcommand : queuedCommands) {
System.out.println("executing command: " + command);
try {
workspace.command(command);
}
catch (Exceptione) {
e.printStackTrace();
}
System.out.print("enter command> ");
}
queuedCommands.clear();
}
privateBufferedReaderreader =
newBufferedReader(newInputStreamReader(System.in));
privateStringreadLine() throwsIOException {
returnreader.readLine();
}
}
}

Example (embedding)

When your program controls NetLogo using the App class, the entire NetLogo application is present, including tabs, menubar, and so forth. This arrangement is suitable for controlling or "scripting" NetLogo, but not ideal for embedding a NetLogo model in a larger application.

We also have a distinct but similar API which allows embedding only the interface tab, not the whole window, in another application. To access this functionality use the org.nlogo.lite.InterfaceComponent class, which extends javax.swing.JPanel. You can use the embedded component much the same way that you use App's static methods. Here is the App example from above converted to use InterfaceComponent:

importorg.nlogo.lite.InterfaceComponent;
publicclassExample3 {
publicstaticvoidmain(String[] argv) {
try {
finaljavax.swing.JFrameframe = newjavax.swing.JFrame();
finalInterfaceComponentcomp = newInterfaceComponent(frame);
java.awt.EventQueue.invokeAndWait(
newRunnable() {
publicvoidrun() {
frame.setSize(1000, 700);
frame.add(comp);
frame.setVisible(true);
try {
comp.open
("models/Sample Models/Earth Science/"
+ "Fire.nlogo");
}
catch(Exceptionex) {
ex.printStackTrace();
}}});
comp.command("set density 62");
comp.command("random-seed 0");
comp.command("setup");
comp.command("repeat 50 [ go ]");
System.out.println(comp.report("burned-trees"));
}
catch(Exceptionex) {
ex.printStackTrace();
}
}
}

The equivalent code in Scala:

importorg.nlogo.lite.InterfaceComponentobjectExample3 {
defmain(args: Array[String]) {
valframe=new javax.swing.JFramevalcomp=newInterfaceComponent(frame)
wait {
frame.setSize(1000, 700)
frame.add(comp)
frame.setVisible(true)
comp.open(
"models/Sample Models/Earth Science/Fire.nlogo")
}
comp.command("set density 62")
comp.command("random-seed 0")
comp.command("setup")
comp.command("repeat 50 [ go ]")
println(comp.report("burned-trees"))
}
defwait(block: =>Unit) {
java.awt.EventQueue.invokeAndWait(
newRunnable() { defrun() { block } } ) }
}

The embedding API gives you a variety of model control features in addition to those provided in the App class. You can simulate button presses, enable logging, create and hide widgets, and so on. See the NetLogo API Specification for details.

To use the embedded component you must have NetLogo.jar in your classpath. If you want to use logging you must also have the log4j jar from the lib directory in your classpath.

Conclusion

Don't forget to consult the NetLogo API Specification for full details on these classes and methods.

You may also find it useful to consult the NetLogo source code on GitHub.

Some API facilities exist, but are not yet documented. Please do not hesitate to ask questions on netlogo-devel, as the NetLogo developers can often provide information about undocumented calls and other kinds of guidance.

Clone this wiki locally