-
Notifications
You must be signed in to change notification settings - Fork 875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Observables to MESA #2291
base: main
Are you sure you want to change the base?
Conversation
Looks really interesting! I will review it in detail tomorrow, and try to play with it. |
Could you update or rebase this branch on |
for more information, see https://pre-commit.ci
To give you a sense of the overhead, Here is a quick benchmark based on the current version of the code for the Boltzman wealth model. This benchmark is not based on something that is feature-complete. I have added a Table to track agent wealth. This table is then used in the gini calculation. So this table at all times reflects the value of In this version, gini is not a computed because making that work with this table is a bit tricky (which suggests that the computed stuff needs a bit more thought to make it easy to extend and use). class Table:
"""This table tracks the wealth of agents."""
def __init__(self, model: BoltzmannWealth):
self.data = {}
for agent in model.agents:
agent.observe("wealth", "on_change", self.update)
self.data[agent.unique_id] = agent.wealth
def update(self, signal):
"""handler for signal that updates the wealth for the agent in the table"""
self.data[signal.owner.unique_id] = signal.new_value
def get(self):
"""return the wealth of agents"""
return self.data.values() So, the startup is much slower (and I have not tried to improve this at all yet). Overhead while running is present but not bad. And remember, Boltzmann is a worst-case example, and none of the code here has been profiled. I have analyzed some parts a little bit for performance, but not in any great depth.
So why is startup so slow? There is quite some overhead from initializing the agents with the additional data structures for emitting signals etc. Moreover, at the moment of object instantiation, the code figures out what observables have been defined and adds these to the instance. This is silly, and I have to figure out a way to do this once during class creation (instead of 100 times for each agent). Also, the percentages mislead for startup time. We go from roughly 0.00026 seconds to 0.001 seconds. |
I am positively surprised by the benchmark! It is really not bad for a worst case scenario. I agree that startup times are not so relevant and can probably be improved. @EwoutH maybe it makes sense to also include absolute times in the comment? I think its different for different models, but in case where startup time makes up less than 1% of total runtime its totally negligible. /Edit already included! |
I am not sure what you mean. The benchmark results include both the small and the large version of the Boltzman wealth model. |
Oh, sorry, some brain fart must have happened. Yes it's already included, so just ignore my previous comment. Than it looks even better |
I like the API! |
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
A quick update: I started adding tests. The Observable and HaObservable are now fully tested. Still need to do the test for computed/computable and for the SignalList. |
see #2281
This PR adds various new classes to MESA. The key public-facing ones are, at the moment,
Observable,
ObservableList,
andHasObservables
.Observable
is a descriptor that is used to declare a given attribute to be observable. Being observable means changing the attribute using the assignment operator=
willl result in the sending of a signal. Callbacks can subscribe to this signal.ObservableList
is a proof of principle on how the idea of an observable can be expanded to collections. Future extensions could include, e.g.,ObservableDict
.HasObservables
is a mixin that can be used with e.g.,Mode
l orAgent
. It is required if you want to useObservable
orObservableList
.HasObservable
contains the key logic for subscribing to signals and the emitting of signals.Observable
emits anon_change
signal when its attribute value is changed via the assignment operator (i.e.,=
).ObservableList
at the moment defines 4 signal types:""added", "removed", "replaced", "on change"
. I aim to develop this a bit further and ideally make the emitted signals as similar as possible to how this is handled in Traitlets. The main reason for making the signals identical to traitlets is that it should make integration with solara easier. The emitted signals are currently instances of aNamedTuple
with four fields: "owner", "signal_type", "old_value", and "new_value". It is planned to elaborate this a bit more and have slightly different fields depending on the type of signal being emitted. However, the attribute access to the fields will remain.I have also added a WIP Computed and Computable approach. In javascript-style signals, but, e.g., also in solara, a Computed is a callable that is dependent on one or more signals. When it receives a signal that any of its dependencies has changed, it will mark itself as being outdated. It will only update its value if it is called to do so. This can be used to set up a hybrid push/pull structure where Obervables push signals, but a Computed is only recalculated on a pull if it knows its underlying data has changed. A Computed is also Observable, so it will emit a signal if it knows it is outdated. This allows the chaining of observables and computed's.
API
Below, there is a quick sketch of the resulting API. We define Obervable attributes at the class level. We can use the attribute normally in the rest of the code.
Implementation Details
After a long discussion in #2281, I decided to implement everything from scratch. Existing libraries like traitlets or psygnal add too much overhead because they are not tailored to the specific use case of ABMs with potentially many updates to an attribute.
TODO