Repository: projectmesa/mesa
Pull request #2489 (2024-11-11 13:13:37)

examples/gol: Add initial fraction alive, add sliders to viz
Improve the Game of Life examples:
- Add a initial_fraction_alive keyword argument for the how many cells are alive at the start
- Add sliders for width, height and the initial fraction alive.

![image](https://github.com/user-attachments/assets/7f1a70f0-9be5-449d-8b7d-bd310355ede8)


github-actions[bot]: Performance benchmarks:

| Model           | Size   | Init time [95% CI]      | Run time [95% CI]       |
|-----------------|--------|-------------------------|-------------------------|
| BoltzmannWealth | small  | 🔵 +0.0% [-0.3%, +0.4%] | 🔵 +0.3% [+0.1%, +0.4%] |
| BoltzmannWealth | large  | 🔵 -0.4% [-0.9%, +0.2%] | 🔵 -0.2% [-0.7%, +0.2%] |
| Schelling       | small  | 🔵 -0.4% [-0.6%, -0.2%] | 🔵 +0.0% [-0.1%, +0.2%] |
| Schelling       | large  | 🔵 -0.9% [-1.0%, -0.7%] | 🔵 -0.6% [-1.0%, -0.1%] |
| WolfSheep       | small  | 🔵 -0.5% [-0.7%, -0.2%] | 🔵 -0.6% [-0.8%, -0.5%] |
| WolfSheep       | large  | 🔵 -0.3% [-0.7%, +0.2%] | 🔵 +0.4% [-0.8%, +1.7%] |
| BoidFlockers    | small  | 🔵 -0.7% [-1.2%, -0.2%] | 🔵 -0.2% [-1.1%, +0.7%] |
| BoidFlockers    | large  | 🔵 +0.5% [-0.1%, +1.1%] | 🔵 +0.9% [+0.2%, +1.5%] |


Patch:
diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py
index 8a5441cc..6564cb68 100644
--- a/mesa/examples/advanced/sugarscape_g1mt/app.py
+++ b/mesa/examples/advanced/sugarscape_g1mt/app.py
@@ -4,7 +4,7 @@ from matplotlib.figure import Figure
 
 from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
 from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
-from mesa.visualization import SolaraViz, make_plot_component
+from mesa.visualization import Slider, SolaraViz, make_plot_component
 
 
 def SpaceDrawer(model):
@@ -49,13 +49,29 @@ def SpaceDrawer(model):
 model_params = {
     "width": 50,
     "height": 50,
+    # Population parameters
+    "initial_population": Slider(
+        "Initial Population", value=200, min=50, max=500, step=10
+    ),
+    # Agent endowment parameters
+    "endowment_min": Slider("Min Initial Endowment", value=25, min=5, max=30, step=1),
+    "endowment_max": Slider("Max Initial Endowment", value=50, min=30, max=100, step=1),
+    # Metabolism parameters
+    "metabolism_min": Slider("Min Metabolism", value=1, min=1, max=3, step=1),
+    "metabolism_max": Slider("Max Metabolism", value=5, min=3, max=8, step=1),
+    # Vision parameters
+    "vision_min": Slider("Min Vision", value=1, min=1, max=3, step=1),
+    "vision_max": Slider("Max Vision", value=5, min=3, max=8, step=1),
+    # Trade parameter
+    "enable_trade": {"type": "Checkbox", "value": True, "label": "Enable Trading"},
 }
 
-model1 = SugarscapeG1mt(50, 50)
+model1 = SugarscapeG1mt()
 
 page = SolaraViz(
     model1,
     components=[SpaceDrawer, make_plot_component(["Trader", "Price"])],
+    model_params=model_params,
     name="Sugarscape {G1, M, T}",
     play_interval=150,
 )
diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py
index fa66d2bf..878c6797 100644
--- a/mesa/examples/advanced/wolf_sheep/app.py
+++ b/mesa/examples/advanced/wolf_sheep/app.py
@@ -83,7 +83,7 @@ lineplot_component = make_plot_component(
 )
 
 simulator = ABMSimulator()
-model = WolfSheep(simulator, grass=True)
+model = WolfSheep(simulator=simulator, grass=True)
 
 page = SolaraViz(
     model,
diff --git a/mesa/examples/basic/boid_flockers/agents.py b/mesa/examples/basic/boid_flockers/agents.py
index 48ce2b5a..2ff00cba 100644
--- a/mesa/examples/basic/boid_flockers/agents.py
+++ b/mesa/examples/basic/boid_flockers/agents.py
@@ -54,13 +54,14 @@ class Boid(Agent):
         self.cohere_factor = cohere
         self.separate_factor = separate
         self.match_factor = match
+        self.neighbors = []
 
     def step(self):
         """Get the Boid's neighbors, compute the new vector, and move accordingly."""
-        neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
+        self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
 
         # If no neighbors, maintain current direction
-        if not neighbors:
+        if not self.neighbors:
             new_pos = self.pos + self.direction * self.speed
             self.model.space.move_agent(self, new_pos)
             return
@@ -71,7 +72,7 @@ class Boid(Agent):
         separation_vector = np.zeros(2)  # Separation vector
 
         # Calculate the contribution of each neighbor to the three behaviors
-        for neighbor in neighbors:
+        for neighbor in self.neighbors:
             heading = self.model.space.get_heading(self.pos, neighbor.pos)
             distance = self.model.space.get_distance(self.pos, neighbor.pos)
 
@@ -86,7 +87,7 @@ class Boid(Agent):
             match_vector += neighbor.direction
 
         # Weight each behavior by its factor and normalize by number of neighbors
-        n = len(neighbors)
+        n = len(self.neighbors)
         cohere = cohere * self.cohere_factor
         separation_vector = separation_vector * self.separate_factor
         match_vector = match_vector * self.match_factor
diff --git a/mesa/examples/basic/boid_flockers/app.py b/mesa/examples/basic/boid_flockers/app.py
index effff8c2..074c1ab3 100644
--- a/mesa/examples/basic/boid_flockers/app.py
+++ b/mesa/examples/basic/boid_flockers/app.py
@@ -3,10 +3,7 @@ from mesa.visualization import Slider, SolaraViz, make_space_component
 
 
 def boid_draw(agent):
-    if not agent.neighbors:  # Only for the first Frame
-        neighbors = len(agent.model.space.get_neighbors(agent.pos, agent.vision, False))
-    else:
-        neighbors = len(agent.neighbors)
+    neighbors = len(agent.neighbors)
 
     if neighbors <= 1:
         return {"color": "red", "size": 20}
diff --git a/mesa/examples/basic/conways_game_of_life/app.py b/mesa/examples/basic/conways_game_of_life/app.py
index 168681b7..e2219c9d 100644
--- a/mesa/examples/basic/conways_game_of_life/app.py
+++ b/mesa/examples/basic/conways_game_of_life/app.py
@@ -20,12 +20,34 @@ def post_process(ax):
 
 
 model_params = {
-    "width": 50,
-    "height": 50,
+    "width": {
+        "type": "SliderInt",
+        "value": 50,
+        "label": "Width",
+        "min": 5,
+        "max": 60,
+        "step": 1,
+    },
+    "height": {
+        "type": "SliderInt",
+        "value": 50,
+        "label": "Height",
+        "min": 5,
+        "max": 60,
+        "step": 1,
+    },
+    "initial_fraction_alive": {
+        "type": "SliderFloat",
+        "value": 0.2,
+        "label": "Cells initially alive",
+        "min": 0,
+        "max": 1,
+        "step": 0.01,
+    },
 }
 
 # Create initial model instance
-model1 = ConwaysGameOfLife(50, 50)
+model1 = ConwaysGameOfLife()
 
 # Create visualization elements. The visualization elements are solara components
 # that receive the model instance as a "prop" and display it in a certain way.
diff --git a/mesa/examples/basic/conways_game_of_life/model.py b/mesa/examples/basic/conways_game_of_life/model.py
index 6e81f690..f61f71bf 100644
--- a/mesa/examples/basic/conways_game_of_life/model.py
+++ b/mesa/examples/basic/conways_game_of_life/model.py
@@ -6,7 +6,7 @@ from mesa.space import SingleGrid
 class ConwaysGameOfLife(Model):
     """Represents the 2-dimensional array of cells in Conway's Game of Life."""
 
-    def __init__(self, width=50, height=50, seed=None):
+    def __init__(self, width=50, height=50, initial_fraction_alive=0.2, seed=None):
         """Create a new playing area of (width, height) cells."""
         super().__init__(seed=seed)
         # Use a simple grid, where edges wrap around.
@@ -16,7 +16,7 @@ class ConwaysGameOfLife(Model):
         # ALIVE and some to DEAD.
         for _contents, (x, y) in self.grid.coord_iter():
             cell = Cell((x, y), self)
-            if self.random.random() < 0.1:
+            if self.random.random() < initial_fraction_alive:
                 cell.state = cell.ALIVE
             self.grid.place_agent(cell, (x, y))
 
diff --git a/mesa/examples/basic/schelling/app.py b/mesa/examples/basic/schelling/app.py
index 73492fa0..933b3c27 100644
--- a/mesa/examples/basic/schelling/app.py
+++ b/mesa/examples/basic/schelling/app.py
@@ -26,7 +26,7 @@ model_params = {
     "height": 20,
 }
 
-model1 = Schelling(20, 20, 0.8, 0.2, 3)
+model1 = Schelling()
 
 HappyPlot = make_plot_component({"happy": "tab:green"})
 
diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py
index 9819e1c9..10718e8a 100644
--- a/mesa/visualization/solara_viz.py
+++ b/mesa/visualization/solara_viz.py
@@ -43,10 +43,10 @@ if TYPE_CHECKING:
 @solara.component
 def SolaraViz(
     model: Model | solara.Reactive[Model],
-    *,
     components: list[reacton.core.Component]
     | list[Callable[[Model], reacton.core.Component]]
     | Literal["default"] = "default",
+    *,
     play_interval: int = 100,
     simulator: Simulator | None = None,
     model_params=None,
@@ -276,7 +276,7 @@ def SimulatorController(
         running.value = True
         simulator.reset()
         model.value = model.value = model.value.__class__(
-            simulator, **model_parameters.value
+            simulator=simulator, **model_parameters.value
         )
 
     def do_play_pause():


Test patch:
diff --git a/tests/test_examples.py b/tests/test_examples.py
index 98d1d580..0e8a7edc 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -13,6 +13,10 @@ from mesa.examples import (
 
 
 def test_boltzmann_model():  # noqa: D103
+    from mesa.examples.basic.boltzmann_wealth_model import app
+
+    app.page  # noqa: B018
+
     model = BoltzmannWealth(seed=42)
 
     for _i in range(10):
@@ -20,24 +24,40 @@ def test_boltzmann_model():  # noqa: D103
 
 
 def test_conways_game_model():  # noqa: D103
+    from mesa.examples.basic.conways_game_of_life import app
+
+    app.page  # noqa: B018
+
     model = ConwaysGameOfLife(seed=42)
     for _i in range(10):
         model.step()
 
 
 def test_schelling_model():  # noqa: D103
+    from mesa.examples.basic.schelling import app
+
+    app.page  # noqa: B018
+
     model = Schelling(seed=42)
     for _i in range(10):
         model.step()
 
 
 def test_virus_on_network():  # noqa: D103
+    from mesa.examples.basic.virus_on_network import app
+
+    app.page  # noqa: B018
+
     model = VirusOnNetwork(seed=42)
     for _i in range(10):
         model.step()
 
 
 def test_boid_flockers():  # noqa: D103
+    from mesa.examples.basic.boid_flockers import app
+
+    app.page  # noqa: B018
+
     model = BoidFlockers(seed=42)
 
     for _i in range(10):
@@ -45,6 +65,10 @@ def test_boid_flockers():  # noqa: D103
 
 
 def test_epstein():  # noqa: D103
+    from mesa.examples.advanced.epstein_civil_violence import app
+
+    app.page  # noqa: B018
+
     model = EpsteinCivilViolence(seed=42)
 
     for _i in range(10):
@@ -52,6 +76,10 @@ def test_epstein():  # noqa: D103
 
 
 def test_pd_grid():  # noqa: D103
+    from mesa.examples.advanced.pd_grid import app
+
+    app.page  # noqa: B018
+
     model = PdGrid(seed=42)
 
     for _i in range(10):
@@ -59,6 +87,10 @@ def test_pd_grid():  # noqa: D103
 
 
 def test_sugarscape_g1mt():  # noqa: D103
+    from mesa.examples.advanced.sugarscape_g1mt import app
+
+    app.page  # noqa: B018
+
     model = SugarscapeG1mt(seed=42)
 
     for _i in range(10):
@@ -66,8 +98,11 @@ def test_sugarscape_g1mt():  # noqa: D103
 
 
 def test_wolf_sheep():  # noqa: D103
+    from mesa.examples.advanced.wolf_sheep import app
     from mesa.experimental.devs import ABMSimulator
 
+    app.page  # noqa: B018
+
     simulator = ABMSimulator()
     WolfSheep(seed=42, simulator=simulator)
     simulator.run_for(10)