Repository: projectmesa/mesa

Issue:
Bug in visualization of hexgrids
**Describe the bug**
The visualization of hexgrids seems wrong. Below, you see a simple hex grid. The central agent is shown in blue, while its neighbors are shown in orange. This visual suggests three neighbors are in the third row and only one in the bottom row. 

![Image](https://github.com/user-attachments/assets/49ca079e-f4e1-4d14-b831-380e15ecbba4)

**Expected behavior**
The visualization should show the correct neighbors 

**cause and fix**
The cause of the bug is a misalignment between the visualization and how the hexgrid is wired up. `HexGrid. _connect_cells_2d ` used (row, column) notation for its neighbors, while the visualization code treats this as (x, y) coordinates, effectively transposing the grid. 

To fix this, we have to decide on which notation to use. If we want to stick with (row, col), we need to change the visualization (as well as a bunch of other things, such as the width and height properties). The simpler solution is thus to change `HexGrid. _connect_cells_2d` to use (x,y) notation for calculating the indices of neighbors.



for more background on this kind of problem, you can check https://www.redblobgames.com/grids/hexagons/ 



Discussion:

Patch:
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index 82608c28..c8a1434f 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -28,8 +28,10 @@ jobs:
           python-version: '3.13'
       - name: Add project directory to PYTHONPATH
         run: echo "PYTHONPATH=$PYTHONPATH:$(pwd)" >> $GITHUB_ENV
+      - name: Install uv
+        run: pip install uv
       - name: Install dependencies
-        run: pip install numpy pandas tqdm tabulate matplotlib solara networkx
+        run: uv pip install --system numpy pandas tqdm tabulate matplotlib solara networkx scipy
       # Benchmarks on the projectmesa main branch
       - name: Checkout main branch
         uses: actions/checkout@v4
diff --git a/docs/README.md b/docs/README.md
index edd25ead..65f3cb66 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -15,6 +15,7 @@ Updating docs can be confusing. Here are the basic setups.
 1. Build the docs, from the inside of the docs folder.
     * **Requires** sphinx: `pip install sphinx`
     * **Requires** nbsphinx: `pip install nbsphinx` (this will render the images from jupyter in the docs)
+    * **Requires** sphinx-copybutton: `pip install sphinx-copybutton` (this will enable a copy option at the top of the code written in the docs)
     * `make html`
 1. Commit the changes. If there are new files, you will have to explicit add them.
     * `git commit -am "Updating docs."`
@@ -32,5 +33,5 @@ From this point, you will need to find someone that has access to readthedocs. C
 ### Helpful Sphnix tips
 * Build html from docs:
   * `make html`
-* Autogenerate / update sphninx from docstrings (replace your name as the author:
+* Autogenerate / update sphninx from docstrings (replace your name as the author):
   * `sphinx-apidoc -A "Jackie Kazil" -F -o docs mesa/`
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 24dfa07b..84d0f087 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -47,6 +47,7 @@ extensions = [
     "sphinx.ext.viewcode",
     "sphinx.ext.napoleon",  # for google style docstrings
     "myst_nb",  # For Markdown and Jupyter notebooks
+    "sphinx_copybutton" # For copying the code in the documentation
 ]
 
 # Add any paths that contain templates here, relative to this directory.
diff --git a/mesa/experimental/cell_space/grid.py b/mesa/experimental/cell_space/grid.py
index 5e2b993e..6078a038 100644
--- a/mesa/experimental/cell_space/grid.py
+++ b/mesa/experimental/cell_space/grid.py
@@ -273,20 +273,20 @@ class HexGrid(Grid[T]):
     def _connect_cells_2d(self) -> None:
         # fmt: off
         even_offsets = [
-                        (-1, -1), (-1, 0),
-                    ( 0, -1),        ( 0, 1),
-                        ( 1, -1), ( 1, 0),
+                        (-1, -1), (0, -1),
+                    ( -1, 0),        ( 1, 0),
+                        ( -1, 1), (0, 1),
                 ]
         odd_offsets = [
-                        (-1, 0), (-1, 1),
-                    ( 0, -1),       ( 0, 1),
-                        ( 1, 0), ( 1, 1),
+                        (0, -1), (1, -1),
+                    ( -1, 0),       ( 1, 0),
+                        ( 0, 1), ( 1, 1),
                 ]
         # fmt: on
 
         for cell in self.all_cells:
-            i = cell.coordinate[0]
-            offsets = even_offsets if i % 2 == 0 else odd_offsets
+            i = cell.coordinate[1]
+            offsets = even_offsets if i % 2 else odd_offsets
             self._connect_single_cell_2d(cell, offsets=offsets)
 
     def _connect_cells_nd(self) -> None:
diff --git a/pyproject.toml b/pyproject.toml
index 5950d071..be0dc4a3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -76,6 +76,7 @@ docs = [
   "seaborn",
   "myst-nb",
   "myst-parser",  # Markdown in Sphinx
+  "sphinx-copybutton", # Add a copy option in docs
 ]
 
 [project.urls]


Test patch:
diff --git a/tests/test_cell_space.py b/tests/test_cell_space.py
index 53fb737d..7f4dd820 100644
--- a/tests/test_cell_space.py
+++ b/tests/test_cell_space.py
@@ -354,7 +354,7 @@ def test_cell_neighborhood():
     grid = HexGrid(
         (width, height), torus=False, capacity=None, random=random.Random(42)
     )
-    for radius, n in zip(range(1, 4), [2, 6, 11]):
+    for radius, n in zip(range(1, 4), [3, 7, 13]):
         if radius == 1:
             neighborhood = grid._cells[(0, 0)].neighborhood
         else:
@@ -366,7 +366,7 @@ def test_cell_neighborhood():
     grid = HexGrid(
         (width, height), torus=False, capacity=None, random=random.Random(42)
     )
-    for radius, n in zip(range(1, 4), [5, 10, 17]):
+    for radius, n in zip(range(1, 4), [4, 10, 17]):
         if radius == 1:
             neighborhood = grid._cells[(1, 0)].neighborhood
         else:
@@ -385,25 +385,25 @@ def test_hexgrid():
     assert len(grid._cells) == width * height
 
     # first row
-    assert len(grid._cells[(0, 0)].connections.values()) == 2
+    assert len(grid._cells[(0, 0)].connections.values()) == 3
     for connection in grid._cells[(0, 0)].connections.values():
-        assert connection.coordinate in {(0, 1), (1, 0)}
+        assert connection.coordinate in {(0, 1), (1, 0), (1, 1)}
 
     # second row
-    assert len(grid._cells[(1, 0)].connections.values()) == 5
+    assert len(grid._cells[(1, 0)].connections.values()) == 4
     for connection in grid._cells[(1, 0)].connections.values():
         # fmt: off
-        assert connection.coordinate in {(0, 0), (0, 1),
-                                         (1, 1),
-                                         (2, 0), (2, 1)}
+        assert connection.coordinate in {   (1, 1), (2, 1),
+                                         (0, 0),    (2, 0),}
+        # fmt: on
 
     # middle odd row
     assert len(grid._cells[(5, 5)].connections.values()) == 6
     for connection in grid._cells[(5, 5)].connections.values():
         # fmt: off
-        assert connection.coordinate in {(4, 5), (4, 6),
-                                         (5, 4), (5, 6),
-                                         (6, 5), (6, 6)}
+        assert connection.coordinate in {  (4, 4), (5, 4),
+                                         (4, 5), (6, 5),
+                                         (4, 6), (5, 6)}
 
         # fmt: on
 
@@ -411,9 +411,9 @@ def test_hexgrid():
     assert len(grid._cells[(4, 4)].connections.values()) == 6
     for connection in grid._cells[(4, 4)].connections.values():
         # fmt: off
-        assert connection.coordinate in {(3, 3), (3, 4),
-                                         (4, 3), (4, 5),
-                                         (5, 3), (5, 4)}
+        assert connection.coordinate in {(4, 3), (5, 3),
+                                         (3, 4), (5, 4),
+                                         (4, 5), (5, 5)}
 
         # fmt: on
 
@@ -424,9 +424,9 @@ def test_hexgrid():
     assert len(grid._cells[(0, 0)].connections.values()) == 6
     for connection in grid._cells[(0, 0)].connections.values():
         # fmt: off
-        assert connection.coordinate in {(9, 9), (9, 0),
-                                         (0, 9), (0, 1),
-                                         (1, 9), (1, 0)}
+        assert connection.coordinate in {(0, 9), (1, 9),
+                                         (9, 0), (1, 0),
+                                         (0, 1), (1, 1)}
 
         # fmt: on