Proposal: adding some form of event sourcing to MESA #1947
Replies: 5 comments 5 replies
-
Adding a note on @quaquel's take on the existing library eventsourcing: #1930 (comment). |
Beta Was this translation helpful? Give feedback.
-
@Corvince what's your take on this? |
Beta Was this translation helpful? Give feedback.
-
Thanks for this extensive proposal. On a high-level, I like the direction. Few questions:
class MoneyAgent(Agent):
""" An agent with fixed initial wealth."""
wealth = Observable()
def __init__(self, unique_id, model):
super().__init__(unique_id, model)
self.wealth = wealth
While I think this is useful in some cases, I haven't seen the killer application yet. Is there one? |
Beta Was this translation helpful? Give feedback.
-
I haven't commented on this idea yet, because I think @quaquel wrote a very nice proposal and I didn't want to sound too negative immedieately. So I will try to lay down my words carefully. I think the idea or wish to use some form of event sourcing is sound and understandable for some use cases. I am just not sure if this needs to be included in mesa itself. This proposal suggests that we should somewhat write our own Observable library, with all the advantages, but also pitfalls. In my mind the nice thing about mesa is that it gives you the full range of possibilities Python has to offer. That means if someone has a need for an observable pattern they should be free to use it. There are multiple observable libraries out there and I see no reason not to use them. If you want to fire an event when agents are created just do it explicitly when you create an agent. As a model developer you have full control over such things. My main worry is that if we provide such functionality we have to tell users when to use it and when not and we create situations where there is more than one obvious way of how to do things. Therefore if we include event sourcing I think we would also need to make it the default. But then the whole of mesa would look completely different. I think its a very interesting and also promising idea to base an ABM library purely on reactive event-based triggers. But I don't think we should transform mesa into that. There are also some caveats with observables. One thing that might be solveable is the need to unsubscribe from event listeners, which otherwise can cause memory issues. This is a common point of errors in reactive libraries. Another thing is
This one is tricky, because with descriptors that is still hard to do. Take this example: agent.wealth = 12 # <= This one triggers an event
agent.neighbors.append(other_agent) # <= This one doesn't The second one uses a mutable data structure, so the descriptor isn't fired, because the reference stays the same. So you would need to use immutable state updates like agent.neighbors = [*agent.neighbors, other_agent] So its not true that this wouldn't require rewrites to the model logic in some cases, which are easy to miss if you are not familiar with this topic. |
Beta Was this translation helpful? Give feedback.
-
In the possible implementation, I use an Enum to list all events. Thinking about this more over the last few weeks, I think it might be better to declare events on the class level. So, it is clear what events it can fire for each class. Declaring this at the class is the proper way to do it because these events are part of the public API of the class. No idea how to implement this cleanly yet. |
Beta Was this translation helpful? Give feedback.
-
Why?
Imagine you want to keep a log of all agents added and removed from the model and track all the changes to their positions. Doing this at the moment requires modifying your model in several places and modifying/overriding some of the source of MESA itself. Imagine you want to track one specific agent through the model and log its state changes. Again, there is currently no easy way to do this without heavily adapting your model code. Or imagine a loose coupling between the simulation model and whatever animation platform you want to use; again, this is currently not easy to implement. The observer software design pattern, or the more generic architectural design idea of event sourcing, exists to solve this type of problem.
I have three key questions:
How might it be done?
At its core, adding some form of event sourcing following the observer pattern requires at least two things: an event producer that is responsible for firing events and a way of subscribing and unsubscribing to specific events.
In MESA, at a minimum, it seems sensible that
Agent
andModel
are (i.e., inheritance) or have (i.e., composition) an EventProducer.As events, I currently see the following key events
State change requires a bit more discussion. I see three possible options.
pos
), in which case the state of the agent is changed, but the change is done by the space. Who is responsible for firing the STATE_CHANGE event?A first draft API
So, what might the API look like? Let's start with the example of logging all events in the simulation. Look at the code below. Here, we have a single class that encapsulates all the logging, and this can work without having to touch any of the underlying model implementation.
Next, let's consider the tracing of a single agent. Again, this can very easily be achieved, assuming we identify the agent by ID:
Or let's consider a situation where I want to track the lifetime of agents in some evolutionary model and tie this to the discussion in (#1944)[#1944]. Again, we can easily support this by subscribing to AGENT_REMOVED events and extracting whatever statistics we want from each agent. Again, we can add a statistics observer without changing anything fundamental in the underlying model.
We can now use this collector (depending evidently on #1944), for example like this to get the dynamic evolution of our average lifetime over time:
possible implementation
Below, I give a sketch of the core classes that would be required. The
Event
enum is for convenience. The user knows what default events are defined within MESA. The API is open to users defining their own events of they choose to do so because events are just strings in this proposed implementation.EventProducer
is the core class. It is what you subscribe to and which is responsible for firing events. Again, It is left to the user to decide which information is part of the event. Note thatfire_event
calls theevent_handler
with itself as the first argument.Model, Agent, and possibly Space should have an EventProducer or should be EventProducers. I am largely indifferent between these two options. If they have an event producer, all classes need to implement subscribe and unsubscribe and pass it to their event producer:
Moreover,
Model._agents
or theAgent
itself should be modified to fireAGENT_ADDED
andAGENT_REMOVED
events. In my view, agents are added to or removed from the model, so the cleaner implementation is to handle this at the Model class (rather than the current implementation inAgent
:self.model._agents[type(self)][self] = None
). Likewise, TIME_INCREMENT should be fired by whatever handles time in the simulation (see discussion in #1942).The one thing that is tricky is STATE_CHANGE events. In my view, it should be up to the user of MESA to decide which attributes in their agents and model should be observable and thus fire these events (i.e., my second option above). Moreover, I don't want to force the user to add
fire_event
calls whenever they are changing the relevant state. So, therefore I suggest the Observable descriptor. It is used like this:By declaring
wealth
to beObservable
in the class definition, now whenever a value is assigned towealth
(i.e.,self.wealth = 1
),Observable__set__
is called. This handles the firing of the event and assigning the new value to_private_attribute
.small update: As pointed out by @Corvince in #1933 (comment), it is possible to automatically add descriptors to classes from the outside, which offers additional possibilities.
Beta Was this translation helpful? Give feedback.
All reactions