pyNetLogo icon indicating copy to clipboard operation
pyNetLogo copied to clipboard

pyNetLogo with Dash and Plotly

Open KevinLalor opened this issue 4 years ago • 7 comments

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:

image

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.

KevinLalor avatar Apr 05 '21 13:04 KevinLalor

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).

quaquel avatar Apr 05 '21 13:04 quaquel

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

image

KevinLalor avatar Apr 05 '21 16:04 KevinLalor

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?

quaquel avatar Apr 05 '21 16:04 quaquel

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.

KevinLalor avatar Apr 05 '21 16:04 KevinLalor

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.

quaquel avatar Apr 05 '21 17:04 quaquel

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

KevinLalor avatar Apr 05 '21 17:04 KevinLalor

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: image

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)

KevinLalor avatar Apr 06 '21 09:04 KevinLalor