Controlling API

Jason Bertsche edited this page Mar 28, 2018 · 17 revisions
Clone this wiki locally

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 java environment variables are set:

  • Docs - -Dnetlogo.docs.dir=<path-to-docs>
  • Extensions - netlogo.extensions.dir=<path-to-extensions>
  • Models - netlogo.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:

import org.nlogo.app.App;
public class Example1 {
  public static void main(String[] argv) {
    App.main(argv);
    try {
      java.awt.EventQueue.invokeAndWait(
	new Runnable() {
	  public void run() {
	    try {
	      App.app().open(
		"models/Sample Models/Earth Science/"
		+ "Fire.nlogo");
	    }
	    catch(java.io.IOException ex) {
	      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(Exception ex) {
      ex.printStackTrace();
    }
  }
}

The equivalent code in Scala:

import java.awt.EventQueue
import org.nlogo.app.App
object Example1 {
  def main(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"))
  }
  def wait(block: => Unit) {
    EventQueue.invokeAndWait(
      new Runnable() { def run() { 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.

import org.nlogo.headless.HeadlessWorkspace;
public class Example2 {
  public static void main(String[] argv) {
    HeadlessWorkspace workspace =
	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(Exception ex) {
      ex.printStackTrace();
    }
  }
}

The equivalent code in Scala:

import org.nlogo.headless.HeadlessWorkspace
object Example2 {
  def main(args: Array[String]) {
    val workspace = 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)
(def workspace 
  (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:

public static void main(String[] args) {
  org.nlogo.awt.EventQueue.invokeLater(
    new Runnable() {
      public void run() {
	System.setProperty("org.nlogo.is3d", "true");
	HeadlessWorkspace workspace = 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(Exception ex) {
	  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.

import org.nlogo.headless.HeadlessWorkspace;

class HubNetHeadlessServerExample {
  static public void main(String[] args) {
    HeadlessWorkspace workspace = 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(Exception ex) {
      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.

class HubNetHeadlessServerExample {

  static public void main(String[] args) {
    HeadlessWorkspace workspace = HeadlessWorkspace.newInstance();
    CommandLineThread commandLine = new CommandLineThread(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 (Exception ex) {
      ex.printStackTrace();
    }
  }

  static class CommandLineThread extends Thread {
    private final HeadlessWorkspace workspace;
    private ArrayBlockingQueue<String> queuedCommands =
	new ArrayBlockingQueue<String>(10);
    public CommandLineThread(HeadlessWorkspace workspace) {
      super("Command Line Thread");
      this.workspace = workspace;
    }
    public void run() {
      System.out.println("enter command> ");
      while (true) {
	try {
	  queuedCommands.put(readLine());
	}
	catch (Exception e) {
	  e.printStackTrace();
	}
      }
    }

    private void runCommands() {
      for (String command : queuedCommands) {
	System.out.println("executing command: " + command);
	try {
	  workspace.command(command);
	}
	catch (Exception e) {
	  e.printStackTrace();
	}
	System.out.print("enter command> ");
      }
      queuedCommands.clear();
    }

    private BufferedReader reader =
	new BufferedReader(new InputStreamReader(System.in));
    private String readLine() throws IOException {
	return reader.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:

import org.nlogo.lite.InterfaceComponent;
public class Example3 {
  public static void main(String[] argv) {
    try {
      final javax.swing.JFrame frame = new javax.swing.JFrame();
      final InterfaceComponent comp = new InterfaceComponent(frame);
      java.awt.EventQueue.invokeAndWait(
	new Runnable() {
	  public void run() {
	    frame.setSize(1000, 700);
	    frame.add(comp);
	    frame.setVisible(true);
	    try {
	      comp.open
		  ("models/Sample Models/Earth Science/"
		   + "Fire.nlogo");
	    }
	    catch(Exception ex) {
	      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(Exception ex) {
      ex.printStackTrace();
    }
  }
}

The equivalent code in Scala:

import org.nlogo.lite.InterfaceComponent
object Example3 {
  def main(args: Array[String]) {
    val frame = new javax.swing.JFrame
    val comp = new InterfaceComponent(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"))
  }
  def wait(block: => Unit) {
    java.awt.EventQueue.invokeAndWait(
      new Runnable() { def run() { 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.