Skip to content

Commit

Permalink
Merge branch 'main' into fix/schelling-neighbor-similarity
Browse files Browse the repository at this point in the history
  • Loading branch information
quaquel authored Dec 10, 2024
2 parents d4a64a8 + bf6ab9e commit 4649746
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 64 deletions.
10 changes: 10 additions & 0 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ Mesa now uses a new browser-based visualization system called SolaraViz. This al

> **Note:** SolaraViz is experimental and still in active development for Mesa 3.0. While we attempt to minimize them, there might be API breaking changes between Mesa 3.0 and 3.1. There won't be breaking changes between Mesa 3.0.x patch releases.
> **Note:** SolaraViz instantiates new models using `**model_parameters.value`, so all model inputs must be keyword arguments.
Ensure your model's `__init__` method accepts keyword arguments matching the `model_params` keys.

```python
class MyModel(Model):
def __init__(self, n_agents=10, seed=None):
super().__init__(seed=seed)
# Initialize the model with N agents

The core functionality for building your own visualizations resides in the [`mesa.visualization`](apis/visualization) namespace

Here's a basic example of how to set up a visualization:
Expand Down
11 changes: 11 additions & 0 deletions docs/migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ the import from mesa.experimental. Otherwise here is a list of things you need t

Previously SolaraViz was initialized by providing a `model_cls` and a `model_params`. This has changed to expect a model instance `model`. You can still provide (user-settable) `model_params`, but only if users should be able to change them. It is now also possible to pass in a "reactive model" by first calling `model = solara.reactive(model)`. This is useful for notebook environments. It allows you to pass the model to the SolaraViz Module, but continue to use the model. For example calling `model.value.step()` (notice the extra .value) will automatically update the plots. This currently only automatically works for the step method, you can force visualization updates by calling `model.value.force_update()`.

### Model Initialization with Keyword Arguments

With the introduction of SolaraViz in Mesa 3.0, models are now instantiated using `**model_parameters.value`. This means all inputs for initializing a new model must be keyword arguments. Ensure your model's `__init__` method accepts keyword arguments matching the keys in `model_params`.

```python
class MyModel(mesa.Model):
def __init__(self, n_agents=10, seed=None):
super().__init__(seed=seed)
# Initialize the model with N agents
```

#### Default space visualization

Previously we included a default space drawer that you could configure with an `agent_portrayal` function. You now have to explicitly create a space drawer with the `agent_portrayal` function
Expand Down
134 changes: 70 additions & 64 deletions docs/tutorials/visualization_tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
{
"cell_type": "markdown",
"metadata": {},
"source": "# Visualization Tutorial"
"source": [
"# Visualization Tutorial"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -51,10 +53,10 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import mesa\n",
"print(f\"Mesa version: {mesa.__version__}\")\n",
Expand All @@ -66,10 +68,10 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def agent_portrayal(agent):\n",
" return {\n",
Expand All @@ -79,15 +81,17 @@
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "In addition to the portrayal method, we instantiate the model parameters, some of which are modifiable by user inputs. In this case, the number of agents, N, is specified as a slider of integers."
"metadata": {},
"source": [
"In addition to the portrayal method, we instantiate the model parameters, some of which are modifiable by user inputs. In this case, the number of agents, N, is specified as a slider of integers."
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model_params = {\n",
" \"n\": {\n",
Expand All @@ -104,8 +108,8 @@
]
},
{
"metadata": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we instantiate the visualization object which (by default) displays the grid containing the agents, and timeseries of values computed by the model's data collector. In this example, we specify the Gini coefficient.\n",
"\n",
Expand All @@ -118,13 +122,13 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create initial model instance\n",
"model1 = MoneyModel(50, 10, 10)\n",
"model1 = MoneyModel(n=50, width=10, height=10) #keyword arguments\n",
"\n",
"SpaceGraph = make_space_component(agent_portrayal)\n",
"GiniPlot = make_plot_component(\"Gini\")\n",
Expand All @@ -140,8 +144,8 @@
]
},
{
"metadata": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 2 - Dynamic Agent Representation \n",
"\n",
Expand All @@ -155,10 +159,10 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import mesa\n",
"print(f\"Mesa version: {mesa.__version__}\")\n",
Expand All @@ -169,10 +173,10 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def agent_portrayal(agent):\n",
" size = 10\n",
Expand All @@ -197,13 +201,13 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create initial model instance\n",
"model1 = MoneyModel(50, 10, 10)\n",
"model = MoneyModel(n=50, width=10, height=10)\n",
"\n",
"SpaceGraph = make_space_component(agent_portrayal)\n",
"GiniPlot = make_plot_component(\"Gini\")\n",
Expand All @@ -219,8 +223,8 @@
]
},
{
"metadata": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 3 - Custom Components \n",
"\n",
Expand All @@ -236,10 +240,10 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import mesa\n",
"print(f\"Mesa version: {mesa.__version__}\")\n",
Expand All @@ -253,10 +257,10 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def agent_portrayal(agent):\n",
" size = 10\n",
Expand All @@ -281,15 +285,17 @@
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "Next, we update our solara frontend to use this new component"
"metadata": {},
"source": [
"Next, we update our solara frontend to use this new component"
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@solara.component\n",
"def Histogram(model):\n",
Expand All @@ -306,56 +312,56 @@
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create initial model instance\n",
"model1 = MoneyModel(50, 10, 10)\n",
"model = MoneyModel(n=50, width=10, height=10)\n",
"\n",
"SpaceGraph = make_space_component(agent_portrayal)\n",
"GiniPlot = make_plot_component(\"Gini\")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2024-10-29T19:38:49.471838Z",
"start_time": "2024-10-29T19:38:47.897295Z"
}
},
"source": [
"page = SolaraViz(\n",
" model1,\n",
" components=[SpaceGraph, GiniPlot, Histogram],\n",
" model_params=model_params,\n",
" name=\"Boltzmann Wealth Model\",\n",
")\n",
"# This is required to render the visualization in the Jupyter notebook\n",
"page"
],
"outputs": [
{
"data": {
"text/plain": [
"Cannot show ipywidgets in text"
],
"application/vnd.jupyter.widget-view+json": {
"model_id": "bc71b89ee5684038a194eee4c36f4a4c",
"version_major": 2,
"version_minor": 0
},
"text/html": [
"Cannot show widget. You probably want to rerun the code cell above (<i>Click in the code cell, and press Shift+Enter <kbd>⇧</kbd>+<kbd>↩</kbd></i>)."
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "bc71b89ee5684038a194eee4c36f4a4c"
}
"text/plain": [
"Cannot show ipywidgets in text"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 12
"source": [
"page = SolaraViz(\n",
" model,\n",
" components=[SpaceGraph, GiniPlot, Histogram],\n",
" model_params=model_params,\n",
" name=\"Boltzmann Wealth Model\",\n",
")\n",
"# This is required to render the visualization in the Jupyter notebook\n",
"page"
]
},
{
"cell_type": "markdown",
Expand All @@ -366,35 +372,35 @@
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"ExecuteTime": {
"end_time": "2024-10-29T19:38:49.505725Z",
"start_time": "2024-10-29T19:38:49.472599Z"
}
},
"source": [
"Histogram(model1)"
],
"outputs": [
{
"data": {
"text/plain": [
"Cannot show ipywidgets in text"
],
"application/vnd.jupyter.widget-view+json": {
"model_id": "0491f167a1434a92b78535078bd082a8",
"version_major": 2,
"version_minor": 0
},
"text/html": [
"Cannot show widget. You probably want to rerun the code cell above (<i>Click in the code cell, and press Shift+Enter <kbd>⇧</kbd>+<kbd>↩</kbd></i>)."
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "0491f167a1434a92b78535078bd082a8"
}
"text/plain": [
"Cannot show ipywidgets in text"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 13
"source": [
"Histogram(model)"
]
},
{
"cell_type": "markdown",
Expand Down
11 changes: 11 additions & 0 deletions mesa/visualization/solara_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,17 @@ def _check_model_params(init_func, model_params):
ValueError: If a parameter is not valid for the model's initialization function
"""
model_parameters = inspect.signature(init_func).parameters

has_var_positional = any(
param.kind == inspect.Parameter.VAR_POSITIONAL
for param in model_parameters.values()
)

if has_var_positional:
raise ValueError(
"Mesa's visualization requires the use of keyword arguments to ensure the parameters are passed to Solara correctly. Please ensure all model parameters are of form param=value"
)

for name in model_parameters:
if (
model_parameters[name].default == inspect.Parameter.empty
Expand Down
17 changes: 17 additions & 0 deletions tests/test_solara_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,20 @@ def __init__(self, **kwargs):
# Test empty params dict raises ValueError if required params
with pytest.raises(ValueError, match="Missing required model parameter"):
_check_model_params(ModelWithOnlyRequired.__init__, {})


# test that _check_model_params raises ValueError when *args are present
def test_check_model_params_with_args_only():
"""Test that _check_model_params raises ValueError when *args are present."""

class ModelWithArgsOnly:
def __init__(self, param1, *args):
pass

model_params = {"param1": 1}

with pytest.raises(
ValueError,
match="Mesa's visualization requires the use of keyword arguments to ensure the parameters are passed to Solara correctly. Please ensure all model parameters are of form param=value",
):
_check_model_params(ModelWithArgsOnly.__init__, model_params)

0 comments on commit 4649746

Please sign in to comment.