Repository: projectmesa/mesa

Issue:
UX: flat Mesa namespace alternative
Consider that instead of
```python
from mesa import Model, Agent
from mesa.time import RandomActivation
from mesa.space import SingleGrid
from mesa.datacollection import DataCollector
```

We do a single import (like `import matplotlib.pyplot as plt`)
```python
import mesa.all as ma

self.space = ma.SingleGrid
...
self.datacollector = ma.DataCollector(...)
```
This way, we can have modular namespaces that are not polluted, while at the same time have a flat namespace for ease of use.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/projectmesa/mesa/pull/1294?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) Report
> Merging [#1294](https://codecov.io/gh/projectmesa/mesa/pull/1294?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) (5b61327) into [main](https://codecov.io/gh/projectmesa/mesa/commit/e582260a117665c446286915e6d6e74e05ecd21d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) (e582260) will **increase** coverage by `0.04%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main    #1294      +/-   ##
==========================================
+ Coverage   89.07%   89.12%   +0.04%     
==========================================
  Files          19       20       +1     
  Lines        1245     1250       +5     
  Branches      246      242       -4     
==========================================
+ Hits         1109     1114       +5     
  Misses        101      101              
  Partials       35       35              
```


| [Impacted Files](https://codecov.io/gh/projectmesa/mesa/pull/1294?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) | Coverage Δ | |
|---|---|---|
| [mesa/\_\_init\_\_.py](https://codecov.io/gh/projectmesa/mesa/pull/1294/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [mesa/visualization\_flat.py](https://codecov.io/gh/projectmesa/mesa/pull/1294/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS92aXN1YWxpemF0aW9uX2ZsYXQucHk=) | `100.00% <100.00%> (ø)` | |
| [mesa/space.py](https://codecov.io/gh/projectmesa/mesa/pull/1294/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS9zcGFjZS5weQ==) | `94.44% <0.00%> (-0.04%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/projectmesa/mesa/pull/1294?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/projectmesa/mesa/pull/1294?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa). Last update [e582260...5b61327](https://codecov.io/gh/projectmesa/mesa/pull/1294?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).

tpike3: This is so simple, yet a very perceptual change and I keep going back and forth. This week I will write up a quick overview of the options you outlined here and post as the #1 agenda item for the next dev session. 

Then I will work to get feedback via two channels: 
1- Contributors can vote even if they can't make the next dev session
2-Send to user base (e,g, TU Delft, U. Mary Washington) to see what they think (particularly as they have brought this up) then show with the group

Critically I just want to make sure the contributors has  some time and data to think about it and don't have to make a rash vote in the dev sessions. 

Does this plan make sense? 
tpike3: @rht can you give me a common sense check on what I am sending out to key user groups......

*We are planning on flattening Mesa's name space to make it easier. We have narrowed it down to two alternatives. Could you please let us know what you and your students would prefer and why. The debate is whether our time.py file and visualization.py file need context. This is backwards compatible so the current approach will continue to work.* 

```
#current approach 4 examples
mesa.datacollection.DataCollector
mesa.visualization.ModularVisualization.ModularServer
mesa.visualization.UserParam.Slider
mesa.time.RandomActivation

#Option 1: As flat as possible  
mesa.Datacollector
mesa.ModularServer
mesa.Randomactivation
mesa.Slider  

#Option 2: With context for time.py and visualization
mesa.DataCollector
mesa.visualization.ModularServer
mesa.time.RandomActivation
mesa.visualization.Slider

```
rht: > The debate is whether our time.py file and visualization.py file need context.

And also space.py

The options are:
1. non-breaking: have 2 options simultaneously: 1. with context, e.g. `mesa.time.RandomActivation`, AND 2. flat, but under `mesa.flat` namespace, e.g. `mf.RandomActivation`
2. non-breaking: implements only with context, i.e. `mesa.time.RandomActivation`
3. non-breaking: implements only the `mesa.flat`
4, non-breaking: flatten everything to be directly in `mesa`

Note that the 4 options still let you import using current namespace hierarchies, and hence why they are non-breaking.
(I think it is a matter of choosing option 1 vs 4, maybe we should just like 1 and 4?)

If the `mesa.flat` is pursued, we need to vote on its names:
```
import mesa.all as ma
import mesa.flat as mf
import mesa.simple as ms
import mesa.lite as ml
```
Corvince: For me Option 1) and 4) sound like good alternatives, options 2) and 3) seem a bit half-baked.

I personally prefer Option 4), but another name suggestion for 1) could be ´mesa.interactive as mia´ to highlight its suitability for interactive prototyping.
tpike3: We got our first feedback from TU Delft: 

They like mesa (so no mesa.flat etc)  with context. Their reasoning....

*It both delineates and simplifies. It makes it explicit that you are picking a space, or time or interface, but does not overburden the user with in between layers and extra abstractions.*

U. of Mary Washington will give us feedback Friday.

Patch:
diff --git a/.coveragerc b/.coveragerc
index 31085cb1..113b23a9 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -5,3 +5,4 @@ branch = True
 [report]
 omit =
     tests/*
+    mesa/flat/*
diff --git a/examples/sugarscape_cg/sugarscape_cg/agents.py b/examples/sugarscape_cg/sugarscape_cg/agents.py
index ad6b8741..c4bbef98 100644
--- a/examples/sugarscape_cg/sugarscape_cg/agents.py
+++ b/examples/sugarscape_cg/sugarscape_cg/agents.py
@@ -34,7 +34,7 @@ class SsAgent(Agent):
 
     def is_occupied(self, pos):
         this_cell = self.model.grid.get_cell_list_contents([pos])
-        return len(this_cell) > 1
+        return any(isinstance(agent, SsAgent) for agent in this_cell)
 
     def move(self):
         # Get neighborhood within vision
diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py
index f6a9018f..fe62192b 100644
--- a/examples/wolf_sheep/wolf_sheep/agents.py
+++ b/examples/wolf_sheep/wolf_sheep/agents.py
@@ -1,4 +1,4 @@
-from mesa import Agent
+import mesa
 from wolf_sheep.random_walk import RandomWalker
 
 
@@ -92,7 +92,7 @@ class Wolf(RandomWalker):
                 self.model.schedule.add(cub)
 
 
-class GrassPatch(Agent):
+class GrassPatch(mesa.Agent):
     """
     A patch of grass that grows at a fixed rate and it is eaten by sheep
     """
diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py
index 4910dfaf..2b8fdbde 100644
--- a/examples/wolf_sheep/wolf_sheep/model.py
+++ b/examples/wolf_sheep/wolf_sheep/model.py
@@ -9,15 +9,13 @@ Replication of the model found in NetLogo:
     Northwestern University, Evanston, IL.
 """
 
-from mesa import Model
-from mesa.space import MultiGrid
-from mesa.datacollection import DataCollector
+import mesa
 
 from wolf_sheep.scheduler import RandomActivationByTypeFiltered
 from wolf_sheep.agents import Sheep, Wolf, GrassPatch
 
 
-class WolfSheep(Model):
+class WolfSheep(mesa.Model):
     """
     Wolf-Sheep Predation Model
     """
@@ -84,8 +82,8 @@ class WolfSheep(Model):
         self.sheep_gain_from_food = sheep_gain_from_food
 
         self.schedule = RandomActivationByTypeFiltered(self)
-        self.grid = MultiGrid(self.width, self.height, torus=True)
-        self.datacollector = DataCollector(
+        self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True)
+        self.datacollector = mesa.DataCollector(
             {
                 "Wolves": lambda m: m.schedule.get_type_count(Wolf),
                 "Sheep": lambda m: m.schedule.get_type_count(Sheep),
diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py
index 14b7fde8..e532a3b8 100644
--- a/examples/wolf_sheep/wolf_sheep/server.py
+++ b/examples/wolf_sheep/wolf_sheep/server.py
@@ -1,6 +1,4 @@
-from mesa.visualization.ModularVisualization import ModularServer
-from mesa.visualization.modules import CanvasGrid, ChartModule
-from mesa.visualization.UserParam import UserSettableParameter
+import mesa
 
 from wolf_sheep.agents import Wolf, Sheep, GrassPatch
 from wolf_sheep.model import WolfSheep
@@ -40,8 +38,8 @@ def wolf_sheep_portrayal(agent):
     return portrayal
 
 
-canvas_element = CanvasGrid(wolf_sheep_portrayal, 20, 20, 500, 500)
-chart_element = ChartModule(
+canvas_element = mesa.visualization.CanvasGrid(wolf_sheep_portrayal, 20, 20, 500, 500)
+chart_element = mesa.visualization.ChartModule(
     [
         {"Label": "Wolves", "Color": "#AA0000"},
         {"Label": "Sheep", "Color": "#666666"},
@@ -50,20 +48,22 @@ chart_element = ChartModule(
 )
 
 model_params = {
-    "grass": UserSettableParameter("checkbox", "Grass Enabled", True),
-    "grass_regrowth_time": UserSettableParameter(
+    "grass": mesa.visualization.UserSettableParameter(
+        "checkbox", "Grass Enabled", True
+    ),
+    "grass_regrowth_time": mesa.visualization.UserSettableParameter(
         "slider", "Grass Regrowth Time", 20, 1, 50
     ),
-    "initial_sheep": UserSettableParameter(
+    "initial_sheep": mesa.visualization.UserSettableParameter(
         "slider", "Initial Sheep Population", 100, 10, 300
     ),
-    "sheep_reproduce": UserSettableParameter(
+    "sheep_reproduce": mesa.visualization.UserSettableParameter(
         "slider", "Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01
     ),
-    "initial_wolves": UserSettableParameter(
+    "initial_wolves": mesa.visualization.UserSettableParameter(
         "slider", "Initial Wolf Population", 50, 10, 300
     ),
-    "wolf_reproduce": UserSettableParameter(
+    "wolf_reproduce": mesa.visualization.UserSettableParameter(
         "slider",
         "Wolf Reproduction Rate",
         0.05,
@@ -72,15 +72,15 @@ model_params = {
         0.01,
         description="The rate at which wolf agents reproduce.",
     ),
-    "wolf_gain_from_food": UserSettableParameter(
+    "wolf_gain_from_food": mesa.visualization.UserSettableParameter(
         "slider", "Wolf Gain From Food Rate", 20, 1, 50
     ),
-    "sheep_gain_from_food": UserSettableParameter(
+    "sheep_gain_from_food": mesa.visualization.UserSettableParameter(
         "slider", "Sheep Gain From Food", 4, 1, 10
     ),
 }
 
-server = ModularServer(
+server = mesa.visualization.ModularServer(
     WolfSheep, [canvas_element, chart_element], "Wolf Sheep Predation", model_params
 )
 server.port = 8521
diff --git a/mesa/__init__.py b/mesa/__init__.py
index aba7754a..62aec15e 100644
--- a/mesa/__init__.py
+++ b/mesa/__init__.py
@@ -9,8 +9,12 @@ import datetime
 from mesa.model import Model
 from mesa.agent import Agent
 
+import mesa.time as time
+import mesa.space as space
+import mesa.flat.visualization as visualization
+from mesa.datacollection import DataCollector
 
-__all__ = ["Model", "Agent"]
+__all__ = ["Model", "Agent", "time", "space", "visualization", "DataCollector"]
 
 __title__ = "mesa"
 __version__ = "0.9.0"
diff --git a/mesa/flat/__init__.py b/mesa/flat/__init__.py
new file mode 100644
index 00000000..08aefa79
--- /dev/null
+++ b/mesa/flat/__init__.py
@@ -0,0 +1,5 @@
+from mesa import *  # noqa
+from mesa.time import *  # noqa
+from mesa.space import *  # noqa
+from mesa.datacollection import *  # noqa
+from .visualization import *  # noqa
diff --git a/mesa/flat/visualization.py b/mesa/flat/visualization.py
new file mode 100644
index 00000000..dfd6c804
--- /dev/null
+++ b/mesa/flat/visualization.py
@@ -0,0 +1,4 @@
+# This collects all of Mesa visualization components under a flat namespace.
+from mesa.visualization.ModularVisualization import *  # noqa
+from mesa.visualization.modules import *  # noqa
+from mesa.visualization.UserParam import *  # noqa


Test patch:
diff --git a/tests/test_import_namespace.py b/tests/test_import_namespace.py
new file mode 100644
index 00000000..01029185
--- /dev/null
+++ b/tests/test_import_namespace.py
@@ -0,0 +1,29 @@
+def test_import():
+    # This tests the new, simpler Mesa namespace. See
+    # https://github.com/projectmesa/mesa/pull/1294.
+    import mesa
+    import mesa.flat as mf
+
+    from mesa.time import RandomActivation
+
+    mesa.time.RandomActivation
+    RandomActivation
+    mf.RandomActivation
+
+    from mesa.space import MultiGrid
+
+    mesa.space.MultiGrid
+    MultiGrid
+    mf.MultiGrid
+
+    from mesa.visualization.ModularVisualization import ModularServer
+
+    mesa.visualization.ModularServer
+    ModularServer
+    mf.ModularServer
+
+    from mesa.datacollection import DataCollector
+
+    DataCollector
+    mesa.DataCollector
+    mf.DataCollector
diff --git a/tests/test_main.py b/tests/test_main.py
index 03ea23b1..2d6f0cac 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -20,9 +20,7 @@ class TestCli(unittest.TestCase):
         sys.path[:] = self.old_sys_path
 
     def test_run(self):
-        with patch(
-            "mesa.visualization.ModularVisualization.ModularServer"
-        ) as ModularServer:
+        with patch("mesa.visualization.ModularServer") as ModularServer:
             example_dir = os.path.abspath(
                 os.path.join(os.path.dirname(__file__), "../examples/wolf_sheep")
             )