Skip to content
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

Updated docs and check_model param #2510

Merged
merged 20 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"model1 = MoneyModel(n=50, width=10, height=10) #keyword arguments\n",
"model = MoneyModel(n=50, width=10, height=10) #keyword arguments\n",

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be model not model1

"\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(model1)"
tpike3 marked this conversation as resolved.
Show resolved Hide resolved
]
},
{
"cell_type": "markdown",
Expand Down
13 changes: 13 additions & 0 deletions mesa/visualization/solara_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,19 @@ 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(
tpike3 marked this conversation as resolved.
Show resolved Hide resolved
param.kind == inspect.Parameter.VAR_POSITIONAL
for param in model_parameters.values()
)
has_var_keyword = any(
tpike3 marked this conversation as resolved.
Show resolved Hide resolved
param.kind == inspect.Parameter.VAR_KEYWORD
for param in model_parameters.values()
)

if has_var_positional and has_var_keyword:
tpike3 marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("Models with both *args and **kwargs are not supported")

for name in model_parameters:
if (
model_parameters[name].default == inspect.Parameter.empty
Expand Down
16 changes: 16 additions & 0 deletions tests/test_solara_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,19 @@ 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 to check if params are arguments and keyword arguments
def test_check_model_params_with_both_args_and_kwargs():
"""Test that _check_model_params raises ValueError when both *args and **kwargs are present in model initialization."""

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

model_params = {"param1": 1}

with pytest.raises(
ValueError, match="Models with both \\*args and \\*\\*kwargs are not supported"
):
_check_model_params(ModelWithArgsAndKwargs.__init__, model_params)
Loading