Repository: projectmesa/mesa

Issue:
Backlink for Examples Tab in Introductory Tutorial gives 404.
**Describe the bug**
<!-- A clear and concise description the bug -->
A link for[ Examples Tab](https://mesa.readthedocs.io/latest/tutorials/examples.html) in Introductory Tutorial is gives 404 Page Not Found error.

**Expected behavior**
<!-- A clear and concise description of what you expected to happen -->
Should redirect to [Examples Tab.](https://mesa.readthedocs.io/stable/examples.html)

**To Reproduce**
<!-- Steps to reproduce the bug, or a link to a project where the bug is visible
Include a Minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example -->
 Head on to `More Mesa` -> `Classic ABM` section in the [Introductory Tutorial.](https://mesa.readthedocs.io/stable/tutorials/intro_tutorial.html)

![Image](https://github.com/user-attachments/assets/9bae5ae6-4c2c-42f3-8b3c-7c500133ea9a)

**Additional context**
<!--
    Add any other context here.
    Any details about your specific platform:
    * If the problem is in the browser, what browser, version, and OS?
-->

![Image](https://github.com/user-attachments/assets/dcf597aa-5e6e-43db-b650-d512c922adf5)

Discussion:

Solution patch:
diff --git a/docs/index.md b/docs/index.md
index e9798d7ab88..69db76e57fa 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -92,7 +92,7 @@ API Documentation <apis/api_main>
 [matrix chat room]: https://matrix.to/#/#project-mesa:matrix.org
 [mesa]: https://github.com/projectmesa/mesa/
 [mesa overview]: overview
-[mesa examples]: https://github.com/projectmesa/mesa-examples
+[mesa examples]: https://mesa.readthedocs.io/stable/examples.html
 [mesa introductory tutorial]: tutorials/intro_tutorial
 [mesa visualization tutorial]: tutorials/visualization_tutorial
 [migration guide]: migration_guide
diff --git a/docs/tutorials/intro_tutorial.ipynb b/docs/tutorials/intro_tutorial.ipynb
index 32023310ec3..06cbdc4ee48 100644
--- a/docs/tutorials/intro_tutorial.ipynb
+++ b/docs/tutorials/intro_tutorial.ipynb
@@ -44,7 +44,7 @@
     "If you are looking for other Mesa models or tools here are some additional resources. \n",
     "\n",
     "- Interactive Dashboard: There is a separate [visualization tutorial](https://mesa.readthedocs.io/latest/tutorials/visualization_tutorial.html) that will take users through building a dashboard for this model (aka Boltzmann Wealth Model).\n",
-    "- Classic ABMs: You can also find canonical examples of ABMs written in Mesa in the [Examples Tab](https://mesa.readthedocs.io/latest/tutorials/examples.html)\n",
+    "- Classic ABMs: You can also find canonical examples of ABMs written in Mesa in the [Examples Tab](https://mesa.readthedocs.io/stable/examples.html)\n",
     "- More Examples: Want to integrate Reinforcement Learning or work on the Traveling Salesman Problem checkout  [Mesa Examples](https://github.com/projectmesa/mesa-examples/)\n",
     "- Mesa-Geo: If you need an ABM with Geographic Information Systems (GIS) checkout [Mesa-Geo](https://mesa-geo.readthedocs.io/latest/)\n",
     "- Mesa Frames: Have a large complex model that you need to speed up, check out [Mesa Frames](https://github.com/projectmesa/mesa-frames)"
diff --git a/mesa/datacollection.py b/mesa/datacollection.py
index 309d56d8e27..63c0e7f0150 100644
--- a/mesa/datacollection.py
+++ b/mesa/datacollection.py
@@ -117,6 +117,9 @@ def __init__(
         self._agenttype_records = {}
         self.tables = {}
 
+        # add the signal of the validation of model reporter
+        self._validated = False
+
         if model_reporters is not None:
             for name, reporter in model_reporters.items():
                 self._new_model_reporter(name, reporter)
@@ -134,13 +137,66 @@ def __init__(
             for name, columns in tables.items():
                 self._new_table(name, columns)
 
+    def _validate_model_reporter(self, name, reporter, model):
+        """Validate model reporter and handle validation results appropriately.
+
+        Args:
+            name: Name of the reporter
+            reporter: Reporter definition (lambda/method/attribute/function list)
+            model: Model instance
+
+        Raises:
+            ValueError: If reporter is None or has invalid format
+            AttributeError: If model attribute doesn't exist
+            TypeError: If reporter type is not supported
+            RuntimeError: If reporter execution fails
+        """
+        self._validated = True  # put the change of signal firstly avoid losing efficacy
+
+        # Type 1: Lambda function
+        if isinstance(reporter, types.LambdaType):
+            try:
+                reporter(model)
+            except Exception as e:
+                raise RuntimeError(
+                    f"Lambda reporter '{name}' failed validation: {e!s}\n"
+                    f"Example: lambda m: len(m.agents)"
+                ) from e
+
+        # Type 2: Method of class/instance
+        if not callable(reporter) and not isinstance(reporter, types.LambdaType):
+            pass
+
+        # Type 3: Model attribute (string)
+        if isinstance(reporter, str):
+            try:
+                if not hasattr(model, reporter):
+                    raise AttributeError(
+                        f"Model reporter '{name}' references non-existent attribute '{reporter}'\n"
+                    )
+                getattr(model, reporter)  # 验证属性是否可访问
+            except AttributeError as e:
+                raise AttributeError(
+                    f"Model reporter '{name}' attribute validation failed: {e!s}\n"
+                ) from e
+
+        # Type 4: Function with parameters in list
+        if isinstance(reporter, list) and (not reporter or not callable(reporter[0])):
+            raise ValueError(
+                f"Invalid function list format for reporter '{name}'\n"
+                f"Expected: [function, [param1, param2]], got: {reporter}"
+            )
+
     def _new_model_reporter(self, name, reporter):
         """Add a new model-level reporter to collect.
 
         Args:
             name: Name of the model-level variable to collect.
-            reporter: Attribute string, or function object that returns the
-                      variable when given a model instance.
+            reporter: Can be one of four types:
+                1. Attribute name (str): "attribute_name"
+                2. Lambda function: lambda m: len(m.agents)
+                3. Method: model.get_count or Model.get_count
+                4. List of [function, [parameters]]
         """
         self.model_reporters[name] = reporter
         self.model_vars[name] = []
@@ -262,6 +318,10 @@ def get_reports(agent):
     def collect(self, model):
         """Collect all the data for the given model object."""
         if self.model_reporters:
+            if not self._validated:
+                for name, reporter in self.model_reporters.items():
+                    self._validate_model_reporter(name, reporter, model)
+
             for var, reporter in self.model_reporters.items():
                 # Check if lambda or partial function
                 if isinstance(reporter, types.LambdaType | partial):
diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py
index 678485ae33d..537336ef5ee 100644
--- a/mesa/examples/advanced/sugarscape_g1mt/app.py
+++ b/mesa/examples/advanced/sugarscape_g1mt/app.py
@@ -1,47 +1,24 @@
-import numpy as np
-import solara
-from matplotlib.figure import Figure
-
 from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
 from mesa.visualization import Slider, SolaraViz, make_plot_component
+from mesa.visualization.components.matplotlib_components import make_mpl_space_component
+
 
+def agent_portrayal(agent):
+    return {"marker": "o", "color": "red", "size": 10}
 
-def SpaceDrawer(model):
-    def portray(g):
-        layers = {
-            "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
-        }
 
-        for agent in g.all_cells.agents:
-            i, j = agent.cell.coordinate
-            layers["trader"]["x"].append(i)
-            layers["trader"]["y"].append(j)
-        return layers
+propertylayer_portrayal = {
+    "sugar": {"color": "blue", "alpha": 0.8, "colorbar": True, "vmin": 0, "vmax": 10},
+    "spice": {"color": "red", "alpha": 0.8, "colorbar": True, "vmin": 0, "vmax": 10},
+}
 
-    fig = Figure()
-    ax = fig.subplots()
-    out = portray(model.grid)
-    # Sugar
-    # Important note: imshow by default draws from upper left. You have to
-    # always explicitly specify origin="lower".
-    im = ax.imshow(
-        np.ma.masked_where(model.grid.sugar.data <= 1, model.grid.sugar.data),
-        cmap="spring",
-        origin="lower",
-    )
-    fig.colorbar(im, ax=ax, orientation="vertical", pad=0.1, fraction=0.046)
-    # Spice
-    im_spice = ax.imshow(
-        np.ma.masked_where(model.grid.spice.data <= 1, model.grid.spice.data),
-        cmap="winter",
-        origin="lower",
-    )
-    fig.colorbar(im_spice, ax=ax, orientation="vertical", fraction=0.046, pad=0.04)
-    # Trader
-    ax.scatter(**out["trader"])
-    ax.set_axis_off()
-    return solara.FigureMatplotlib(fig)
 
+sugarscape_space = make_mpl_space_component(
+    agent_portrayal=agent_portrayal,
+    propertylayer_portrayal=propertylayer_portrayal,
+    post_process=None,
+    draw_grid=False,
+)
 
 model_params = {
     "seed": {
@@ -73,7 +50,7 @@ def portray(g):
 page = SolaraViz(
     model,
     components=[
-        SpaceDrawer,
+        sugarscape_space,
         make_plot_component("#Traders"),
         make_plot_component("Price"),
     ],


Test patch:
diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py
index 0b5153d3c2c..7f7de853f6a 100644
--- a/tests/test_datacollector.py
+++ b/tests/test_datacollector.py
@@ -352,5 +352,67 @@ def test_agenttype_superclass_reporter(self):
         self.assertTrue(super_data.equals(agent_data))
 
 
+class MockModelForErrors(Model):
+    """Test model for error handling."""
+
+    def __init__(self):
+        """Initialize the test model for error handling."""
+        super().__init__()
+        self.num_agents = 10
+        self.valid_attribute = "test"
+
+    def valid_method(self):
+        """Valid method for testing."""
+        return self.num_agents
+
+
+def helper_function(model, param1):
+    """Test function with parameters."""
+    return model.num_agents * param1
+
+
+class TestDataCollectorErrorHandling(unittest.TestCase):
+    """Test error handling in DataCollector."""
+
+    def setUp(self):
+        """Set up test cases."""
+        self.model = MockModelForErrors()
+
+    def test_lambda_error(self):
+        """Test error when lambda tries to access non-existent attribute."""
+        dc_lambda = DataCollector(
+            model_reporters={"bad_lambda": lambda m: m.nonexistent_attr}
+        )
+        with self.assertRaises(RuntimeError):
+            dc_lambda.collect(self.model)
+
+    def test_method_error(self):
+        """Test error when accessing non-existent method."""
+
+        def bad_method(model):
+            raise RuntimeError("This method is not valid.")
+
+        dc_method = DataCollector(model_reporters={"test": bad_method})
+
+        with self.assertRaises(RuntimeError):
+            dc_method.collect(self.model)
+
+    def test_attribute_error(self):
+        """Test error when accessing non-existent attribute."""
+        dc_attribute = DataCollector(
+            model_reporters={"bad_attribute": "nonexistent_attribute"}
+        )
+        with self.assertRaises(Exception):
+            dc_attribute.collect(self.model)
+
+    def test_function_error(self):
+        """Test error when function list is not callable."""
+        dc_function = DataCollector(
+            model_reporters={"bad_function": ["not_callable", [1, 2]]}
+        )
+        with self.assertRaises(ValueError):
+            dc_function.collect(self.model)
+
+
 if __name__ == "__main__":
     unittest.main()