Note: This is an IPython notebook. You can download the raw notebook here.
I remember seeing my friend David's post and notebook on using calling Java from Python using py4j. For the most part, Python is too slow for the simulations I build. And, the simulations are complex enough that writing them in C and binding them to Python would be error-prone. I want a high-level language that is fast enough; but, I want to explore it through IPython. My high-level/fast-enough language of choice is Scala. Since IScala proved buggy (but promising!), I decided to follow David's lead.
After walking through his tutorial, I realized I didn't like py4j. There is too much friction in setting up gateways. So, I gave pyjnius a try.
TLDR: Pyjnius is useful.
# Just some imports and constant definitions. # Nothing to see here. import os import matplotlib.pyplot as plt import seaborn as sns NETLOGO_BASE = '/Applications/NetLogo 5.2/' NETLOGO_JAR = os.path.join(NETLOGO_BASE, 'NetLogo.jar') MODELS_DIR = os.path.join(NETLOGO_BASE, 'models', 'Sample Models') FIRE_MODEL = os.path.join(MODELS_DIR, 'Earth Science', 'Fire.nlogo') %matplotlib inline
Setting up the JVM¶
Unless you are running the IPython notebook in the same directory as your jar file, you will want to setup up the class path. To run a NetLogo model, we need
I also set a (esoteric to me) JVM switch. Without this switch, the script will hang on
HeadlessWorkspace.newInstance(). I'm not exactly sure why, but it seems to be an issue with awt.
Note: You must call jnius_config before importing jnius. You (obviously) cannot change JVM parameters after you launch it.
import jnius_config jnius_config.set_classpath(NETLOGO_JAR) jnius_config.add_options('-Djava.awt.headless=true') from jnius import autoclass
HeadlessWorkspace = autoclass('org.nlogo.headless.HeadlessWorkspace') workspace = HeadlessWorkspace.newInstance()
Now, following David's lead, I open the forest fire model, and set up some initial parameters.
workspace.open(FIRE_MODEL) workspace.command("set density 62") workspace.command("random-seed 0") workspace.command("setup") workspace.command("repeat 50 [go]")
I'm now ready to run the model. Looking at David's code, I see that, conditional upon these parameters, 4,256 trees should burn. NetLogo strives to provide easy replication. My run should also burn 4,256 trees.
assert workspace.report("burned-trees") == 4256, "Replication Failed"
Imitation is the sincerest form of flattery¶
run_model function is almost an exact copy of David's. The only difference is that I am calling a reflectively created workspace object instead of a py4j Bridge.
def run_model(workspace, density, steps): ''' Run the forest fire model, and return the number of trees burned. Args: workspace: The HeadlessWorkspace object density: Integer density percent, from 0 to 100 steps: How many steps to run Returns: The number of trees burned, as a float. ''' workspace.command("set density " + str(density)) workspace.command("setup") workspace.command("repeat " + str(steps) + " [go]") return workspace.report("burned-trees")
Looking at my plot and his, they are identical. (Although, I used Seaborn, just because it's a beautiful library that I want to draw attention to...)
burned_trees = [run_model(workspace, i, 100) for i in range(0,100)] sns.tsplot(burned_trees) plt.title('Param Sweep of Forest Fire Model') plt.xlabel('% Density') plt.ylabel('% of Trees Burned');
This is pretty much exactly what I want in an interface to Java/Scala. I can now call my models without relying upon my old method of using zmq. It is essentially friction-less. I'm sure I'll encounter problems interfacing to Scala at some point. I'll document them as they arise. For now, consider this a "Hello, pyjnius! Nice to meet you!" post.