-
Notifications
You must be signed in to change notification settings - Fork 923
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
Implement new SolaraViz API #2263
Conversation
Performance benchmarks:
|
Exciting! I tried both mesa>solara run mesa/visualization/myviz.py and mesa\mesa\visualization>solara run myviz.py But for some reason I can't get the imports working. Will take a better look tomorrow. |
I tried it and it worked. Will add inline comments. |
mesa/visualization/myviz.py
Outdated
|
||
|
||
model = MyModel(10) | ||
Page = MyViz(model, [ShowSteps, ShowNumAgents, make_space_altair()]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New API looks more modular. This means user can add any existing Solara components from Solara library, including Plotly if they want to.
I think it would be convenient to have a string "default" that automatically expands into [ShowSteps, make_space_altair()]
. Not sure about num agents being shown by default, but this is a minor point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think thats a good idea. Num agents are currently just shown for testing purpose - I don't think it should be default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your work on this!
Am I right to say that, for Mesa-Geo, we would only need to implement a make_geospace_leaflet()
method and a GeoSpaceLeaflet
solara component?
@rht from where did you run it? I would also like to try it. |
mesa/visualization/utils.py
Outdated
@@ -0,0 +1,7 @@ | |||
import solara | |||
|
|||
_update_counter = solara.reactive(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this global variable a temporary artifact? Because we want to support showing multiple SolaraViz in one go: https://simrisk.pulcloud.io/.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that has been an issue in older versionis of solara, but solara might handle this gracefully now. The docs now include this sentence
Reactive objects in Solara are also context-aware, meaning that they can maintain separate values for each browser tab or user session. This enables each user to have their own independent state, allowing them to interact with the web application without affecting the state of other users.
https://solara.dev/documentation/api/utilities/reactive
But I haven't tested his yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the case when there are multiple page
objects in a single Jupyter notebook? As can be seen in the visualization tutorial. They seem to share the same context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That shouldn't be a big problem luckily. It only forces a rerender, no data change. So at worst you have a few unnecessary rerenders in your notebook. Let's wait and see if this causes serious performance issues and if yes reconsider. But for now it should be good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also notice that all the components take model
as an argument, and so, would it be possible to smuggle in the update counter as an attribute of the model?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting idea. Not sure this will work. I am rather sceptical, but will definitely try it out.
I ran it In the visualization directory. |
It was a namespace conflict between the installed Mesa package and the GitHub repo. Removing mesa and adding import sys
import os
sys.path.insert(0, os.path.abspath('C:/Users/Ewout/Documents/GitHub/mesa')) solved it. Let's take a look! |
The 2 buttons having the same functionality is out of scope for the PR. It is due to ipywidget displaying both and there is no way to turn either off. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mesa/visualization/ModelCreator.py
Outdated
|
||
|
||
@solara.component | ||
def ModelCreator(model, model_cls, model_params): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you can input a model instance, model class and params? I get either the first or the second two are required?
If you input an instance, will it be updated in place, so you can access it from your interpreter later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it expects a reactive version of a model, so that the model from the outside still receives the recreated versions.
I now removed the model_cls, as it can be resolved from the model instance.
So now it receives an actual model (as a reactive variable) and user params. So I think ModelCreator isn't a good name
I have updated the code so it now updates solara_viz.py instead of creating a new myviz. Also moved back the modified ModelController. All in all the diff should be much more reflective of the actual code changes. Things that are still missing (please add if you notice something):
|
Yes, exactly! Although for it to fully work, even if you reset the model or change parameters, you should pass in the model as a reactive model, i.e. call |
If you need any input let me know! |
Now that you say it, what's your opinion on the layout? I thought at first to implement how it's currently done. But maybe we can be more unopinionated. In my implementation all components are just displayed with no surrounding layout. But the components themselves can of course include any layout. We could keep it that way and provide some layout components. So users could just pass Also, regarding naming. Is Components a good enough name? |
I think the current layout is quite good. I like controls on the left, visualisation and output in the center/right.
Many names could work here, but Components is quite good. |
I actually find the naming to be quite restrictive in order to encompass other Solara components, because it needs to be general, e.g. if you want to use the |
This is just a clarifying question from my end. In the various conversations on data collection, we converged on building this on top of some pub-sub or signal-based API. How might that choice affect this effort, or vice versa, what would be convenient to use inside MESA such that both data collection and model visualization can utilize it? In #2176, @maartenbreddels suggested using either Solara reactive variables or psygnal. I have looked at the latter, and it is very elegant and does a lot of what we want for data collection. Importantly, it also allows for subscribing to updates in collections. However, it would add an additional dependency to MESA. I also started exploring Solara's reactive variables. It appears this covers a subset of what we would need for data collection, but I am not sure until I actually play around with it more. In my vision, both the visualization side and the data collection side of MESA use the same mechanism for being informed/getting state information from a MESA model. |
Hi Jan,
Let me know what you think is missing (maybe after playing around). Regards, Maarten |
The top level view on how it currently works (in this PR) is that each component registers a "update_counter" as a render dependency. Whenever this counter increases the component rerenders. Currently the counter is increased after each mode..step. Of course it would be much more elegant to auto-update whenever an attribute changes, for example not through a call to step, but maybe through some form of user interactions. If the variable would be a reactive variable this would already happen automatically. So from a visualization point of view the use of solaras reactive values would be very charming. So I would appreciate it if you further investigate solaras reactive suitability. But overall this PR is mostly neutral to how we handle this - it mostly deals with the surroundings and establishing a cleaner API. The use of signals/reactive values/pub-sub will mostly play some role for individual components (e.g. a plot of a specifc variable) |
Performance benchmarks:
|
I just marked this as ready for review! Even though the tests are still failing, this should probably also be fixed as part of this PR? Otherwise I think this is good to review as a first PR just for the API changes. Next todos are:
Not so immediate, but also a nice todo: Rewrite ModelController. There is already code for a much simpler threaded version and from a first test this finally seems to work. Maybe it doesn't even need to be threaded and just works. In any case it can be vastly simplified (and no more unnecessary pause and stop button) But first lets get this PR through! |
@Corvince looks awesome! Any specific things you would like me to test or review? |
Yes! You can try to use it 🙈 Heres a quick migration guide. Where previous you could have something like this for a minimal frontend SolaraViz(model_cls, model_params, agent_portrayal=agent_portrayal) you would now do SolaraViz(model, components=[make_space_altair(agent_portrayal)]) you can test if that works for That would be awesome! |
Awesome, I'm going to
and test some examples. I just talked to Jan, he is going to try to dust of hist pub-sub efforts to see if that will integrate nicely with this. |
Playing a bit around! Got
Yeah this would be really useful. We could think of another/more robust API to parse measures other than just a string name (@quaquel?), but I think this is important to get right. I like that's way more flexible: You don't need to input Another this that's interesting is that the slider overrides default/other values. I think that's good to document. It does show that something like can be useful. |
I also tried def make_plot(model):
# This is for the case when we want to plot multiple measures in 1 figure.
fig = Figure()
ax = fig.subplots()
measures = ["Infected", "Susceptible", "Resistant"]
colors = ["tab:red", "tab:green", "tab:gray"]
for i, m in enumerate(measures):
color = colors[i]
df = model.datacollector.get_model_vars_dataframe()
ax.plot(df.loc[:, m], label=m, color=color)
fig.legend()
# Set integer x axis
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
solara.FigureMatplotlib(fig) page = SolaraViz(
VirusOnNetwork,
model_params,
measures=[
make_plot,
make_text(get_resistant_susceptible_ratio),
],
name="Virus Model",
agent_portrayal=agent_portrayal,
)
page # noqa |
I rebased this PR. Hopefully tonight or tomorrow at the latest I will update the tests, so ci will be happy again |
Yes, see my last comment |
Perfect. Also feel free to remove any tests if those are blocking you. A new name for the new viz would help a lot. Maybe just |
How about |
I would like to extend the functionality to allow for more model interactions, adding plots interactively and having an "agent observer" for single agents. So not just a pure visualization. After a chat with chatgpt my best idea so far might be "ModelExplorer" |
I would everything that's strictly in Solara (like Solara-components) to keep a Solara-like name. Then if we add non-Solara stuff, we can give them non-Solara names. |
Hi @tpike3, we probably need to rethink the jupyter visualization for mesa-geo, considering the new API design from this PR. |
Can you point me to that? I can take a look tomorrow |
Thanks! The current implementation is here: https://github.com/projectmesa/mesa-geo/tree/main/mesa_geo/visualization, from PR projectmesa/mesa-geo#212. There was some comments from @rht that needs to be addressed: projectmesa/mesa-geo#199 (comment) but we decided to merge and have something working first. As I mentioned above in #2263 (comment), we probably don't need to copy over anything. Instead, we could try to refactor existing implementations into some solara components for geospace. Let me know if you need anything else : ) also cc @tpike3 |
Thanks for the extensive reply! Sorry, I didn't catch your previous comment, but you are spot on! It will just require a function from mesa.visualization.utils import update_counter
def make_geospace_leaflet(agent_portrayal, view, zoom, tiles,scroll_wheel_zoom):
map_drawer = leaflet_viz.MapModule(
portrayal_method=agent_portrayal,
view=view,
zoom=zoom,
tiles=tiles,
scroll_wheel_zoom=scroll_wheel_zoom,
)
center_default= ...
@solara.component
def map(model):
update_counter.get() # <- Important line to notify the frontend whenever model updates (e..g step)
# render map in browser
zoom_map = solara.reactive(zoom)
center = solara.reactive(center_default)
base_map = map_drawer.tiles
layers = map_drawer.render(model)
ipyleaflet.Map.element(
zoom=zoom_map.value,
center=center.value,
scroll_wheel_zoom=scroll_wheel_zoom,
layers=[
ipyleaflet.TileLayer.element(url=base_map["url"]),
ipyleaflet.GeoJSON.element(data=layers["agents"][0]),
*layers["agents"][1],
],
)
return map Then in a notebook you should already be able to do GeoSpaceLeaflet = make_geospace_leaflet(...)
GeoSpaceLeaflet(model) And you should see your visualization! And then you can of course pass it to SolaraViz Would you like to give it a shot? Unfortunately I don't have the mesa_geo stack currently installed and last time it was a bit of a hassle to get the geo tools to work under windows. Just ask if you need any help |
Thanks! This is precisely what I thought. I will find some time to try it out! |
I would prefer to have a new name before a potential (alpha) release. |
What do we think of this? On another note, te Visualization tutorial, since it’s still based on the previous version of SolaraViz. I see two options:
|
I can quickly do a minimal update to the new API and then do a bit of an overhaul later on, if thats fine? |
Sounds perfect! |
I am late, but I like ModelExplorer |
This is the start of a new API for the Solara frontend. See #2236 for more background information. I will eventually update this intro to accommodate more information, but I wanted to start this draft PR now and not wait longer just because I am too lazy right now to write more sentences.
So far everything worked out nicely. The interesting changes are in the
myviz.py
file. This is where the new entrypoint can be found. The other components ModelController and UserParams only received very minor changes and were just moved to other files to not interefere with the current SolaraViz (during development).MyViz also includes a minimal example, if someone wants to try this out just run
solara run myviz.py