Rudolph Johnson
Rudolph Johnson

Reputation: 51

Passing information between two models WITHOUT the use of Levelspace or Hubnet

Is it possible to pass information between Netlogo models without using LevelSpace or Hubnet? I'm trying to output variables from one model and into a second model. The reason I don't want to use LevelSpace for this is because I'm using the xw (xtraWidget) extension as a frontend UI for my models and LevelSpace does not allow the xw extension to activate when models open. Hubnet is for LAN operations and these models will be on a server online.

Upvotes: 0

Views: 119

Answers (1)

Bryan Head
Bryan Head

Reputation: 12580

Do all of the models involved use xw? If only one does, you could have the model be the LevelSpace parent.

If more than one does, probably the simplest way to do this is by communicating by writing to and reading from a file. The safest version of this is to have the sender wait for a file to not exist, open the file, write to it, and closes the file. The receiver, similarly, waits for the file to exist, opens the file, reads from it, then closes the file and deletes it, indicating to the sender that it has finished reading. This should avoid most weird timing issues and, for instance, Windows locking open files and the like, but it does require the two models stay in lock step, as they will block on sending and receiving. This would look like so:

Sender:

to send-data [ filename data ]
  while [ file-exists? filename ] [] ; Could add a `wait` so this doesn't eat as much cpu while waiting
  file-open filename
  file-write data
  file-close
end

Receiver:

to-report receive-data [ filename ]
  while [ not file-exists? filename ] [] ; Could also add a `wait` here 
  file-open "filename"
  let data file-read
  file-close
  file-delete filename
  report data
end

Example usage:

Sender:

observer> send-data "foo" 123 send-data "foo" 456

Receiver:

observer> show receive-data "foo" show receive-data "foo"
observer: 123
observer: 456

Another way to do this (and probably much better) would be to use the Python extension to perform inter-process communication via the multiprocessing library. If you're interested in this method, add a comment, and I can write up some example code. Here are the related docs to get you started: https://docs.python.org/3/library/multiprocessing.html#using-a-remote-manager

Update with Python solution:

The idea here is to start a multiprocessing server that the models then connect to. One model will start the server, then both models will create clients that connect to that server. They will then pass data via a queue in that server.

First, we setup python and import the libraries we care about. This will be run on both models.

to setup-python
  py:setup py:python3
  (py:run
    "from multiprocessing.managers import BaseManager"
    "from multiprocessing import Process"
    "from queue import Queue")
end

We start the server (in just one of the models) like so:

to start-server
  (py:run
    "def start_server():"
    "    class QueueManager(BaseManager): pass"
    "    queue = Queue()"
    "    QueueManager.register('get_queue', callable=lambda:queue)"
    "    m = QueueManager(address=('', 50000), authkey=b'turtles')"
    "    s = m.get_server()"
    "    s.serve_forever()"
    "proc = Process(target=start_server)  "
    "proc.start()")
end

We then start the client in both models like so:

to start-client
  (py:run
    "class QueueManager(BaseManager): pass"
    "QueueManager.register('get_queue')"
    "m = QueueManager(address=('', 50000), authkey=b'turtles')"
    "m.connect()"
    "queue = m.get_queue()")
end

We can then send and receive data with the following:

to send-data [ data ]
  py:set "data" data
  (py:run "queue.put(data)")
end

to-report receive-data
  report (py:runresult "queue.get(block=False)")
end

Note that I set receive-data to not wait for data; it will just error if there's no data waiting. If you do block, it's pretty easy to freeze NetLogo (and then you have to go and kill the underlying python process manually). You'll probably want to adjust that behavior. See https://docs.python.org/3/library/queue.html#queue.Queue.get for various options, or just throw a carefully around the whole thing and report a default value when you get an error.

Putting it all together, on one model we have something like:

observer> setup-python
observer> start-server
observer> start-client
observer> send-data 123
observer> send-data 456

and in the other model we have:

observer> setup-python
observer> start-client
observer> show receive-data
observer: 123
observer> show receive-data
observer: 456

A few notes:

  • start-client and start-server should be run exactly once after setup-python. If you want to call them again, you need to call setup-python again to shutdown the python process. You might get something like [Errno 98] Address already in use when restarting a client after restarting the server; just wait a sec for the old server to die and try again.
  • If you want to send and receive data in both directions, I recommend adding an additional queue. While a queue is bidirectional here, you might have the senders eating their own messages if you try to reuse it.
  • Be prepared to manually kill the python process if NetLogo locks up. It's supposed respect halt (and does sometimes), but not always.

Hope that helps!

Upvotes: 1

Related Questions