Repository: projectmesa/mesa
Pull request #2524 (2024-12-01 16:53:48)

Fix: running Mesa in Docker with Schelling model
## Summary
This pull request addresses issues with the existing Docker configuration, including missing dependencies and untested references to the mesa-examples repository. The updates ensure that the Mesa Schelling model runs correctly with Solara visualization in Docker on port 8765.

## Bug / Issue
1. Missing Dependencies: The original Dockerfile did not install the mesa[all] package, which led to a solara not found error when attempting to run the docker compose.
2. Outdated Example Repository: The mesa-examples repository referenced in the original Dockerfile contains broken visualization modules and no longer includes the Schelling model.
3. Impact: Users were unable to run the Schelling model or any visualizations successfully using the provided Docker configuration.

## Implementation
1. Dependencies Fix: Updated the Dockerfile to ensure mesa[all] is installed, which includes all necessary dependencies for Solara-based visualization.
2. Updated Model Reference:
Replaced the mesa-examples repository reference with the mesa.examples module, specifically pointing to the built-in Schelling model:
```
ENV MODEL_DIR=/opt/mesa/mesa/examples/basic/schelling
```

## Testing
1. Started the container using docker compose:
```
docker compose up -d
```
2. Verified that the Schelling model runs correctly with a working visualization.



EwoutH: Thanks for the PR. @projectmesa/maintainers or @AdamZh0u, can someone remind me what the use case for running Mesa in docker is exactly?
wang-boyu: Well, I guess the motivation is probably similar for many projects that use docker. You can get something up and running locally with one single `docker run xxx` or `docker compose up`, instead of having to 1) clone the repo, 2) set up a virtual environment, 3) install dependencies, and 4) finally run some models.

I do not have docker installed on my mac, because it's not as convenient as it is on ubuntu. There might be others who prefer to use docker.
jackiekazil: +1 to what @wang-boyu said. 
Would love to hear from @AdamZh0u though as well. 
AdamZh0u: Hi all,

Docker allows seamless deployment of Mesa applications across Windows, macOS, and Linux without any platform-specific differences. It ensures that all dependencies and environments are consistent, making it easier for users to get started quickly.

In fact, on macOS, you can run an example model with just two simple commands:
```bash
brew install docker  
#cd mesa  
docker compose up -d  
```
This simplicity is particularly useful for users who want a hassle-free setup experience.


AdamZh0u: No worries! I’ve actually started promoting Docker because I’m leading a Python course with over 100 students, and their systems range from macOS 10 to Windows Home version. We’ve been using Docker, and it’s been working smoothly on almost all computers, making things easier for everyone.
EwoutH: > I’ve actually started promoting Docker because I’m in a Python course with over 100 students

I would love to hear more about the course you’re teaching!

Patch:
diff --git a/Dockerfile b/Dockerfile
index 61348e69..a5af06e2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,12 @@
 # We can't use slim because we need either git/wget/curl to
 # download mesa-examples, and so installing them requires
 # updating the system anyway.
-# We can't use alpine because NumPy doesn't support musllinux yet.
-# But it's in the RC version https://github.com/numpy/numpy/issues/20089.
 FROM python:bookworm
-LABEL maintainer="rht <rhtbot@protonmail.com>"
+LABEL maintainer="projectmesa maintainers@projectmesa.dev"
 
 # To use this Dockerfile:
-# 1. `docker build . -t mymesa_image`
-# 2. `docker run --name mymesa_instance -p 8765:8765 -it mymesa_image`
+# 1. `docker build . -t mesa_image`
+# 2. `docker run --name mesa_instance -p 8765:8765 -it mesa_image`
 # 3. In your browser, visit http://127.0.0.1:8765
 #
 # Currently, this Dockerfile defaults to running the Schelling model, as an
@@ -19,7 +17,7 @@ LABEL maintainer="rht <rhtbot@protonmail.com>"
 # `docker run --name mymesa_instance -p 8765:8765 -e MODEL_DIR=/mesa-examples/examples/sugarscape_cg -it mymesa_image`
 # Note: the model directory MUST contain an app.py file.
 
-ENV MODEL_DIR=/mesa-examples/examples/schelling_experimental
+ENV MODEL_DIR=/opt/mesa/mesa/examples/basic/schelling
 
 # Don't buffer output:
 # https://docs.python.org/3.10/using/cmdline.html?highlight=pythonunbuffered#envvar-PYTHONUNBUFFERED
@@ -29,10 +27,11 @@ WORKDIR /opt/mesa
 
 COPY . /opt/mesa
 
-RUN cd / && git clone https://github.com/projectmesa/mesa-examples.git
-
 EXPOSE 8765/tcp
 
-RUN pip3 install -e /opt/mesa
+RUN pip3 install -e /opt/mesa[rec]
 
 CMD ["sh", "-c", "cd $MODEL_DIR && solara run app.py --host=0.0.0.0"]
+
+# To check file system:
+# docker exec -it mesa_instance bash
diff --git a/docker-compose.yml b/docker-compose.yml
index 9b8e165f..7831079b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,6 +16,6 @@ services:
       # within the current directory.
       # E.g. if it is at my-model, then you specify it as
       # /opt/mesa/my-model.
-      MODEL_DIR: /mesa-examples/examples/schelling_experimental
+      MODEL_DIR: /opt/mesa/mesa/examples/basic/schelling
     ports:
       - 8765:8765
diff --git a/mesa/batchrunner.py b/mesa/batchrunner.py
index bc35d1cf..bcc4dffb 100644
--- a/mesa/batchrunner.py
+++ b/mesa/batchrunner.py
@@ -106,7 +106,14 @@ def _make_model_kwargs(
     Parameters
     ----------
     parameters : Mapping[str, Union[Any, Iterable[Any]]]
-        Single or multiple values for each model parameter name
+        Single or multiple values for each model parameter name.
+
+        Allowed values for each parameter:
+        - A single value (e.g., `32`, `"relu"`).
+        - A non-empty iterable (e.g., `[0.01, 0.1]`, `["relu", "sigmoid"]`).
+
+        Not allowed:
+        - Empty lists or empty iterables (e.g., `[]`, `()`, etc.). These should be removed manually.
 
     Returns:
     -------
@@ -118,6 +125,12 @@ def _make_model_kwargs(
         if isinstance(values, str):
             # The values is a single string, so we shouldn't iterate over it.
             all_values = [(param, values)]
+        elif isinstance(values, list | tuple | set) and len(values) == 0:
+            # If it's an empty iterable, raise an error
+            raise ValueError(
+                f"Parameter '{param}' contains an empty iterable, which is not allowed."
+            )
+
         else:
             try:
                 all_values = [(param, value) for value in values]


Test patch:
diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py
index c60232b5..aec13bc4 100644
--- a/tests/test_batch_run.py
+++ b/tests/test_batch_run.py
@@ -24,6 +24,37 @@ def test_make_model_kwargs():  # noqa: D103
     assert _make_model_kwargs({"a": "value"}) == [{"a": "value"}]
 
 
+def test_batch_run_with_params_with_empty_content():
+    """Test handling of empty iterables in model kwargs."""
+    # If "a" is a single value and "b" is an empty list (should raise error for the empty list)
+    parameters_with_empty_list = {
+        "a": 3,
+        "b": [],
+    }
+
+    try:
+        _make_model_kwargs(parameters_with_empty_list)
+        raise AssertionError(
+            "Expected ValueError for empty iterable but no error was raised."
+        )
+    except ValueError as e:
+        assert "contains an empty iterable" in str(e)
+
+    # If "a" is a iterable and "b" is an empty list (should still raise error)
+    parameters_with_empty_b = {
+        "a": [1, 2],
+        "b": [],
+    }
+
+    try:
+        _make_model_kwargs(parameters_with_empty_b)
+        raise AssertionError(
+            "Expected ValueError for empty iterable but no error was raised."
+        )
+    except ValueError as e:
+        assert "contains an empty iterable" in str(e)
+
+
 class MockAgent(Agent):
     """Minimalistic agent implementation for testing purposes."""