pyNetLogo with Dash and Plotly
Dear Devs,
I'm using Netlogo for my final year project and have ran into an unusual issue. I've searched the web and talked to my supervisor but I could not solve the issue and was wondering if you could help me.
My project is Visualising Public Health Interventions Using Agent-Based Modelling, for this I have a tech stack of Netlogo -> Python -> Dash. However some of the pynetlogo commands won't run when called in a callback function from Dash, example below:

Here the netlogo.load_model(model) line works, but when I used the netlogo.command() function for any button I have in my model it wil return this error:
java.lang.NullPointerException at org.nlogo.generate.CustomClassLoader.loadClass(CustomClassLoader.scala:28) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(Unknown Source) at java.lang.ClassLoader.defineClass(Unknown Source) at org.nlogo.generate.CustomClassLoader.loadClass(CustomClassLoader.scala:27) at org.nlogo.generate.CustomClassLoader.loadBytecodeClass(CustomClassLoader.scala:32) at org.nlogo.generate.Generator$InstructionGenerator.finish(Generator.scala:254) at org.nlogo.generate.Generator$InstructionGenerator.generate(Generator.scala:105) at org.nlogo.generate.Generator.org$nlogo$generate$Generator$$recurse(Generator.scala:29) at org.nlogo.generate.Generator.$anonfun$generate$1(Generator.scala:25) at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:237) at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:36) at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:33) at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:198) at scala.collection.TraversableLike.map(TraversableLike.scala:237) at scala.collection.TraversableLike.map$(TraversableLike.scala:230) at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:198) at org.nlogo.generate.Generator.generate(Generator.scala:25) at org.nlogo.compile.CompilerMain$.assembleProcedure(CompilerMain.scala:86) at org.nlogo.compile.CompilerMain$.$anonfun$compile$1(CompilerMain.scala:62) at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:237) at scala.collection.immutable.List.foreach(List.scala:392) at scala.collection.TraversableLike.map(TraversableLike.scala:237) at scala.collection.TraversableLike.map$(TraversableLike.scala:230) at scala.collection.immutable.List.map(List.scala:298) at org.nlogo.compile.CompilerMain$.compile(CompilerMain.scala:62) at org.nlogo.compile.Compiler.compileMoreCode(Compiler.scala:86) at org.nlogo.compile.Compiler.compileMoreCode(Compiler.scala:100) at org.nlogo.workspace.Evaluator.invokeCompiler(Evaluator.scala:159) at org.nlogo.workspace.Evaluator.evaluateCommands(Evaluator.scala:20) at org.nlogo.workspace.Evaluating.evaluateCommands(Evaluating.scala:28) at org.nlogo.workspace.Evaluating.evaluateCommands$(Evaluating.scala:27) at org.nlogo.workspace.AbstractWorkspaceScala.evaluateCommands(AbstractWorkspaceScala.scala:26) at org.nlogo.app.App.command(App.scala:964) at NetLogoLinkV61.NetLogoLink.command(NetLogoLink.java:180)
java.lang.NullPointerException at org.nlogo.generate.CustomClassLoader.loadClass(CustomClassLoader.scala:28) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(Unknown Source) at java.lang.ClassLoader.defineClass(Unknown Source) at org.nlogo.generate.CustomClassLoader.loadClass(CustomClassLoader.scala:27) at org.nlogo.generate.CustomClassLoader.loadBytecodeClass(CustomClassLoader.scala:32) at org.nlogo.generate.Generator$InstructionGenerator.finish(Generator.scala:254) at org.nlogo.generate.Generator$InstructionGenerator.generate(Generator.scala:105) at org.nlogo.generate.Generator.org$nlogo$generate$Generator$$recurse(Generator.scala:29) at org.nlogo.generate.Generator.$anonfun$generate$1(Generator.scala:25) at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:237) at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:36) at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:33) at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:198) at scala.collection.TraversableLike.map(TraversableLike.scala:237) at scala.collection.TraversableLike.map$(TraversableLike.scala:230) at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:198) at org.nlogo.generate.Generator.generate(Generator.scala:25) at org.nlogo.compile.CompilerMain$.assembleProcedure(CompilerMain.scala:86) at org.nlogo.compile.CompilerMain$.$anonfun$compile$1(CompilerMain.scala:62) at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:237) at scala.collection.immutable.List.foreach(List.scala:392) at scala.collection.TraversableLike.map(TraversableLike.scala:237) at scala.collection.TraversableLike.map$(TraversableLike.scala:230) at scala.collection.immutable.List.map(List.scala:298) at org.nlogo.compile.CompilerMain$.compile(CompilerMain.scala:62) at org.nlogo.compile.Compiler.compileMoreCode(Compiler.scala:86) at org.nlogo.compile.Compiler.compileMoreCode(Compiler.scala:100) at org.nlogo.workspace.Evaluator.invokeCompiler(Evaluator.scala:159) at org.nlogo.workspace.Evaluator.evaluateCommands(Evaluator.scala:20) at org.nlogo.workspace.Evaluating.evaluateCommands(Evaluating.scala:28) at org.nlogo.workspace.Evaluating.evaluateCommands$(Evaluating.scala:27) at org.nlogo.workspace.AbstractWorkspaceScala.evaluateCommands(AbstractWorkspaceScala.scala:26) at org.nlogo.app.App.command(App.scala:964) at NetLogoLinkV61.NetLogoLink.command(NetLogoLink.java:180)
Sorry for the long error message but I'm not sure what to do, I would really like to create buttons in Dash that will make callbacks to netlogo to do things such as Setup and Go, and show the visualisation on a Dashboard like Dash that could be included on websites or news articles.
what happens if you put a command statement right after the load model command?
my first hunch is that something goes wrong with threading. I have however never used Dash before so it is a bit groping in the dark.
also, the code you posted is not complete because I miss various imports (like pynetlogo).
The whole program is quite long but I can add the cell in Jupyter in which I Imported and loaded the model below so you can see the imports I used.
Originally I stored the data in Dataframes however when you run a Dash program in jupyter it continuously runs as you make callbacks / uses your data to update its graphs over time etc. I was also thinking it could possibly be an issue with the Jupyter Kernal but I am not that experienced with using these softwares with eachother.
`#boilerplate code to connect pyNetlogo with Python %matplotlib inline
import pandas as pd import matplotlib.pyplot as plt import seaborn as sns sns.set_style('white') sns.set_context('talk')
import pyNetLogo
path=r'C:\Program Files\NetLogo 6.1.0'
netlogo = pyNetLogo.NetLogoLink(gui=True, thd=False, netlogo_home=path, netlogo_version='6.1')
model=r'C:\Users\Kevin\OneDrive - University College Dublin\Desktop\fyp\Model_V3_Society.nlogo' netlogo.load_model(model) netlogo.command('setup')
`
To answer your question,
what happens if you put a command statement right after the load model command?
The same error occurs, I also get an additional error message from Dash which I shall show below:
pyNetLogo.core.NetLogoException: java.lang.NullPointerException

no idea what is going on here.
Can you confirm that the code runs fine without the dash parts. So can you load, setup, and run the model using just pynetlogo?
Yes the project runs perfectly until I attempt to run netlogo.command() in a Dash callback, although the other pynetlogo functions such as .load_model() and .kill_workspace() work as intended. I've tested this other using other methods in dash such as on button click and using a text input to control the commands.
my hunch is that his has something to do with multiple threads running concurrently, but it would require diving into Dash to fully figure it out. Also, this is most likely not a pure pynetlogo problem but more about figuring out how to make it work. Can you create a minimum example of what you try to do and share the code and setup. I might be able to offer some suggestions and guesses as to what is happening.
Sure thing, Ill write up an example and will post it here sometime tomorrow(probably 12-1pm Irish time) , thank you for the help I appreciate it
Below is a basic Dash program with the following functionality:
Type Load in the text box to Load the netlogo model Type Close in the text box to Kill workspace Type Setup to run a netlogo.command('Setup') [doesnt work, same error that I have brought up before] Click the button 10 times to run netlogo.command('Go') [Same as the other command()]
I used the fireworks model in the netlogo library under the 'art' folder. This was to ensure my project netlogo model didn't have some weird issue.
This should be replicable, I'll link my versions too: Java version is: java version "1.8.0_201" Java(TM) SE Runtime Environment (build 1.8.0_201-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
Python Version: Python 3.7.6
JPype:

CODE:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('white')
sns.set_context('talk')
import pyNetLogo
path=r'C:\Program Files\NetLogo 6.1.0'
netlogo = pyNetLogo.NetLogoLink(gui=True, thd=False, netlogo_home=path, netlogo_version='6.1')
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
#boilerplate code to connect pyNetlogo with Python
%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('white')
sns.set_context('talk')
import pyNetLogo
path=r'C:\Program Files\NetLogo 6.1.0'
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.H6("Change the value in the text box to see callbacks in action!"),
html.Div(["Input: ",
dcc.Input(id='my-input', value='initial value', type='text')]),
html.Br(),
html.Div(id='my-output'),
html.Button('Setup', id='button'),
html.Div(id='output-container-button',
children='Enter a value and press submit'),
])
@app.callback(
Output(component_id='my-output', component_property='children'),
Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
if input_value == 'Load':
model=r'C:\Users\Kevin\OneDrive - University College Dublin\Desktop\fyp\Fireworks.nlogo'
netlogo.load_model(model)
if input_value == 'Close':
netlogo.kill_workspace()
if input_value == 'Setup':
netlogo.command('Setup')
return 'Output: {}'.format(input_value)
@app.callback(
Output("output-container-button","children"),
[Input('button', 'n_clicks')]
)
def update_output(n_clicks):
if n_clicks == 10:
netlogo.command('go')
return 'The input value was and the button has been clicked {} times'.format(n_clicks)
app.run_server(debug=True, use_reloader=False)