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:
tpike3: @aarav-shukla07 That is a good catch, thanks!

First, the link you updated is on the Mesa landing page actually works to go to the examples repository. However, I think linking to the examples tab like you did makes more sense, so we should merge it. 

The link that needs to be fixed is in the [introductory tutorial which is actually a jupyter notebook](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb); To see the raw code [specific line ](https://github.com/projectmesa/mesa/blob/061de7ac1bb1adee37d68fde0abce114879e08e0/docs/tutorials/intro_tutorial.ipynb#L47)

Can you update that one as well and then we can merge this. 

Thanks again. 
aarav-shukla07: Hi @tpike3 

Thanks for your valuable feedback!

I've now updated the link in the Introductory Tutorial Jupyter notebook as well, ensuring it points to the correct Examples Tab. I've tested the changes, and everything seems to be working fine.

Please let me know if there’s anything else you'd like me to adjust. Looking forward to your review!

Thanks again for your guidance.

Patch:
diff --git a/docs/index.md b/docs/index.md
index e9798d7a..69db76e5 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 32023310..06cbdc4e 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 309d56d8..63c0e7f0 100644
--- a/mesa/datacollection.py
+++ b/mesa/datacollection.py
@@ -117,6 +117,9 @@ class DataCollector:
         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 @@ class DataCollector:
             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 @@ class DataCollector:
     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 678485ae..537336ef 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 @@ model = SugarscapeG1mt()
 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 0b5153d3..7f7de853 100644
--- a/tests/test_datacollector.py
+++ b/tests/test_datacollector.py
@@ -352,5 +352,67 @@ class TestDataCollectorWithAgentTypes(unittest.TestCase):
         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()