Marshall Abrams
Mostly notes to self while I was working on the intermittran repo.
(cf. ClojureMASONinteropTips.md)
Note it's possible to use defrecord with protocols if needed. This allows a defrecord to implement a MASON interface. cf. Sim.clj in intermittran, in which the deftype Indiv implements MASON's Oriented2D interface. You can do something similar with defrecord. (I'd rather keep the agents free of explicit MASON stuff, but it doesn't actually hurt anything (other than code modularity and library inclusion) to do this.)
The real display stuff is all Protrayal stuff. In intermittran, I was able to put all of that in SimUI.clj (but made a decision to put that one Oriented2D interface into Sim.clj).
Yes, it appears so. Even if the model itself isn't controlled by the
scheduler--if say, you're just take
from a lazy sequence of model
states--it's the scheduler that makes the GUI window repaint. cf. page
24 of the v.19 manual. (You make this happen by calling
Display2D.reset().)
i.e. rather than the MASON scheduler driving Clojure code, let Clojure drive the MASON GUI.
Well there's a step() function in Schedule. Note though it will only do anything if there are things registered on the schedule. So I would have to add things to the schedule before calling step(), or maybe just leave them on there but repeat.
But how do you pause it? This is clearly possible, since you can do this in the GUI. But I think that step() is exposed mainly so people can override it in a subclass.
(Note though that step() in Steppable is something completely different.)
In the MASON source, look at the second def of `pressPause() in display/Console.java. This calls setPlayState(). The comments there suggest that we can call pressPause, but that we shouldn't mess with setPlayState() or anything further.
Question: If I can do this repeated pausing and starting, is it a good idea? e.g. will it make the thing a lot slower? Note that in Console.java, pausing involves messing with the state of a thread.
Summary: yes
Why?
It's clear you need to extend MASON's GUIState to display stuff via MASON. Is it necessary to have a class that extends its SimState? What does SimState provide?
Note that GUIState does not extend SimState. You must pass a SimState to GUIState's constructor. So purely formally, you have to create a SimState in order to use GUIState.
But what does the SimState give you? Can you just let it sit there, unused?
One of the main things SimState provides, if you use it, is the ability to modify parameters in the simulation from the gui. So, OK, this is important enough to use it. I can set those things from a repl, or from a commandline, but having them modifiable from the GUI is definitely useful.
Note that this means that you have to use gen-class's state field, and use atoms or deftype or something else to allow modification of parameters. So you have to have a whole bunch of boilerplate for each parameter you want accessible through the GUI.
Or just write SimState in Java. :-(
(SimState provides step() and start(), but you can use GUIState's intead, which will call the SimState's scheduler, for example.)
The actual data can go wherever you want. It's at best only marginally
going to be data in the class that extends SimState, since gen-class
gives you only one modifiable field, so if you want more than one
element, you're going to stick a map or defrecord or something into that
data element. And I don't think you really have to store it there,
actually. The GUI is going to access the data using bean accessors
anyway, so you could put the data anywhere they can find it. Heck could
be closures, or another namespace. It could even be functionally
generated data that's never modified per se, as long as you maintain
some way that the bean accessors can find the current value. (OK, that
might require atoms. But still--it doesn't have to be anywhere
particular.)
In my current conception, the internal animal processes, their births and deaths, etc. can be functional. I think it's going to make most sense to use MASON's grid structure(s) for the underlying world. They're already designed for this purpose, will return Moore neighborhoods easily, have a hex version, etc.
Q: Can I update a single grid structure (or two or four), as one's supposed to do in MASON, and pass it along with the functionally updated animals? Or is it better to create a new grid for each step, as one should do in Clojure--especially if the model is lazy?
A: One can get away with using the same data structure, I think. This will work when you use MASON to pull the system along, and it's OK up to a point at a repl.
But you loose the ability to go back in time by looking at different stages of the sequence if you reuse the same grid. Because the old grid is the same as the most recent grid, so it won't match the state of the animals at that earlier time.
On the other hand, maybe it will take up a lot of RAM to store one grid per tick. Also maybe it's silly to store old grids if you're just walking the program using the MASON scheduler from the GUI.
Q: If I did update the grids functionally, i.e. just created a new one for each step, does this cause problems with their display? What do I have to do to get the Field Portrayal that displays the content of the grid to connect to a new grid each time?
A: It looks like I just have to call .setField
on the Portrayal
to assign it the new grid each time.
In the end I decided to update all fields functionally--i.e. each
major step in next-popenv
returns a new snipe field and/or mushroom
field. snipes also are updated functionally using assoc
or
update
.
Then in the schedule loop in SimConfig/start
, I swap!
in the new
popenv on every tick, and (this is the crucial step) in
GUI/setup-portrayals
, I use scheduleRepeatingImmediatelyAfter
to add
a task to the GUI part of the scheduling system that calls setField
on
the snipe field portrayal and mushroom field portrayal on every tick.
So far in pasta, the speed difference between non-functional and functional versions are so small that I'm not sure there's even a difference. If there is a difference, it's less than 1%. I observed this at an early stage, before implementing birth, death, and eating, when I switched over to functional style from my initial in-place modification version. I also observed this at a later stage when I created a non-functional version to test a hypothesis about why inspected snipes weren't updating the inspector. (December 2016: Note however that I haven't yet started adding type hints all over the place to avoid reflection. If avoiding reflection created a big speed increase, maybe it would reveal a bigger difference between functional and non-functional styles. How big could the difference be, though, given that it's so small now?)
(YMMV. e.g. in my experiments with Clojure implementations of the
Student ABM from the MASON manual in the majure
repo, there were big
speed differences between defrecord and deftype because a Continuous2D
field is a hashtable, and hashing is handled differently for these data
structures. In pasta I use an ObjectGrid2D field, which is just an
array for Objects, so access speed will be the same no matter what you
stick in there.)
In a functional-style ABM updating indvididual agents on their own is easy. Dealing with with interactions between agents is trickier, and can be more difficult than what you'd normally write in non-functional style ABMs.
In pasta.popenv
, some of the operations are awkward and a bit
complicated because of the need to simultaneously update the snipe
field, the mushroom field, and snipe's internal states. This would be
simpler with in-place modifications of data structures. For
illustrations, look at move-snipes
or snipes-eat
and the functions
they call. (The complications are greater than what I dealt with in
popco2 [which didn't use MASON], where I simply had to reorganize
messages from agents in order to deliver them to other agents.)
The default MASON inspectors for agents work, up to a point, without any special treatement on my part. Amazingly, I didn't even have to define bean methods! Between Clojure and MASON, somehow MASON knew that defrecord fields hold properties.
In particular, agent inspectors work for one-time, static inspection of snipes, and it's easy to write a toString that will automatically display id and energy.
However, the properties won't update in the inspector tab. This is
apparently because of functional updating. What the inspector system is
watching is a single object, it seems. When a snipe moves or changes
energy, that's a brand new object, and MASON can't be expected to know
about it. I created a quick-and-dirty non-functional-style version in
branch non-fnl
and saw that snipe properties update properly there.
This is potentially a big drawback of functional style for agent-based modeling--worse than crossover problems, in a sense. Even though crossover issues are fundamental to an ABM, and it's not really fundamental to an ABM that individual agents be watched, in practice watching individual states can be very useful. You might "blame" (but not fault) MASON for the additional complexity because it's not designed for functional programming, but if you think about it, providing inspector functionality for a functional-style ABM is by its nature not really trivial: You're trying to watch a "thing" over time, that's thing isn't actually a single thing in functional style. So inspection requires special handling that isn't needed if you just maintain a pointer to single concrete data structure, as you can easily do in a non-functional style system.
On the other hand, Clojure makes functional updating very easy, and
imperative updating verbose: You have to use deftype
with special
annoations and then add accessor functions both in a protocol and in
the deftype
definition, or stick atoms inside the fields in a
defrecord
, and then either write accessor functions or use @
,
swap!
, etc. constantly--but you'd better write the accessor functions
and stick them in protocols too, because otherwise MASON won't know what
to do with them anyway. This kind of only verbose, only partially
idiomatic, Java-esque programming is what I want to avoid.
I think that I can work around this by passing special arguments to or
writing a subclass of SimpleInspector
or extending Inspector
some
other way, maybe using atoms or maps or other lookups by id
to keep
track of what snipe is being watched.
e.g. a map from ids to snipes. For any inspected snipe--for any snipe in that map--on each tick, update it with the new snipe. This is just like what I'm doing with the popenv. But when? How to find the new snipe among the bunch? Can this task be performed at the moment when the snipe is being replaced?
Could I do something like this? Add a field to the snipes. Each inspector has an atom containing snipe, *and the snipe also has a pointer to this atom in its special field. Incestuous. Can it be done? Then at the end of next-popenv, for any such snipe, the atom will be pulled out and the new snipe swapped in to it. Yes, I think, although it's tricky at the repl:
user=> (defrecord R [x])
user.R
user=> (def r (R. (atom nil)))
#'user/r
user=> (reset! (:x r) r)
StackOverflowError java.util.Formatter.parse (Formatter.java:2547)
user=> (reset! (:x r) 14)
14
user=> r
#user.R{:x #object[clojure.lang.Atom 0x54439d52 {:status :ready, :val 14}]}
The stack overflow appears to be due to the REPL trying to print out r, which contained r, which contained ...
This might be very bad if an inspector tries to display that field. So it must be hidden. This is sounding nasty. Maybe stick with the map. Maybe just have a boolean flag field that says "I'm inspected-- go look for me in the inspected map." e.g.:
(when (:inspected? new-snipe)
(swap! inspected-snipes-map assoc (:id new-snipe) new-snipe))
In the end, I used the MASON Propertied
interface so that each
tick's (usually) new snipe delegates to a Properties class instance in
which there are methods that the inspector will use. See
make-properties
and calls to it in snipes.clj. That is, each snipe
has a properties
field that's specified by Propertied
, and this
field contains an instance of a subclass of Properties
. The
inspector will be tracking that snipe instance that it found when you
first told it to follow the snipe. This instance is no longer doing
anything, but the methods in its Properties
subclass will go and
find the current instance of the "same" snipe in order to get the data
that the inspector should see. This is all pretty time-consuming--the
simulation visible slows when you watch a few snipes--but it only
slows things down when you watch snipes, and you didn't really need
speed then, anyway. (It's only when you're running without the GUI
that you really need top speed, I feel. The GUI is already a bit too
fast unless you slow it down.)
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close