unable to start solara app when model cannot be pickled
Closed this issue · 6 comments
Describe the bug
When a model or agents have attributes that cannot be pickled, Solara app won't start due to
TypeError: cannot pickle '_thread.lock' object
Expected behavior
Should be able to run model with Solara regardless it can be deep copied or not
To Reproduce
Take the Schelling model as an example, add the following into the __init__()
method in model.py
:
import threading
self.t = threading.Thread(target=print, args=("Hello"))
self.t.start()
Then run solara run app.py
in the command line.
Additional context
It appears to be related to
mesa/mesa/visualization/solara_viz.py
Line 188 in 1d98560
mesa/mesa/visualization/solara_viz.py
Line 212 in 1d98560
where we try to create deep copies of the model object.
If we remove all usages related to deepcopy:
diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py
index 4bca98d..c2d4960 100644
--- a/mesa/visualization/solara_viz.py
+++ b/mesa/visualization/solara_viz.py
@@ -183,14 +183,6 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
running = solara.use_reactive(True)
original_model = solara.use_reactive(None)
- def save_initial_model():
- """Save the initial model for comparison."""
- original_model.set(copy.deepcopy(model.value))
- playing.value = False
- force_update()
-
- solara.use_effect(save_initial_model, [model.value])
-
async def step():
while playing.value and running.value:
await asyncio.sleep(play_interval / 1000)
@@ -205,18 +197,11 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
model.value.step()
running.value = model.value.running
- def do_reset():
- """Reset the model to its initial state."""
- playing.value = False
- running.value = True
- model.value = copy.deepcopy(original_model.value)
-
def do_play_pause():
"""Toggle play/pause."""
playing.value = not playing.value
with solara.Row(justify="space-between"):
- solara.Button(label="Reset", color="primary", on_click=do_reset)
solara.Button(
label="▶" if not playing.value else "❚❚",
color="primary",
Then the Solara app runs but without the Reset
button.
Probably need a better way to create or save the initial model object?
The only relation is that it's triggered by deepcopy. This one is not fixable within mesa itself, unless there is a way of getting rid of deepcopy in solara_viz
and no further deepcopy operations inside solara. @Corvince, might you be able to explain why deepcopy is used in solara_viz?
Previously we called SolaraViz with the model class and init parameters. Now we pass in a model instance. So I thought the easiest way to reset a model was to restore the initial state through deepcopy.
But this also causes a problem if you call SolaraViz with a model step where already some steps have passed (say 5). Because now you can only reset to step number 5. So we should really reconsider. I think we can derive the model class through from the instance, but we don't necessarily have access to all init parameters, which makes this a bit challenging
Thanks for the clarification. I'll try to take a closer look over the weekend and see what is possible.
Now that we have tried both, it might be useful to clearly list and weigh the benefits and disadvantages of calling with a model class and calling with an model instance.
I am quite sure that the current code is not even working as intended. While cleaning up the examples, I instantiated wolf-sheep with grass=True
, but solara still errored because no grass agents existed in the model.
The cleanest long-term solution would be to abstract all this away in a dedicated run_control
functionality and have a dedicated Experiment
class. This, however, is not something that can be implemented overnight and requires a longer discussion at some later point.
So, my current idea is to just make it work with both object and class. If instantiated with a class, the cleanest solution would be to take values from the specified input controls.