فهرست منبع

Upgraded Gymnasium to 1.0.0 and NumPy to 2.0.0+ (#453)

Bolun Dai 4 ماه پیش
والد
کامیت
0dba116fee

+ 1 - 1
.github/workflows/build.yml

@@ -17,6 +17,6 @@ jobs:
              --build-arg PYTHON_VERSION=${{ matrix.python-version }} \
              --tag minigrid-docker .      
       - name: Run tests
-        run: docker run minigrid-docker pytest
+        run: docker run minigrid-docker pytest --ignore tests/test_wfc
       - name: Run doctest
         run: docker run minigrid-docker pytest --doctest-modules minigrid/

+ 8 - 6
.github/workflows/pre-commit.yml

@@ -1,18 +1,20 @@
 # https://pre-commit.com
 # This GitHub Action assumes that the repo contains a valid .pre-commit-config.yaml file.
-name: pre-commit
-on: [pull_request, push]
+name: Run pre-commit
+on:
+  pull_request:
+  push:
+    branches: [master]
 
 permissions:
-  contents: read
+  contents: read # to fetch code (actions/checkout)
 
 jobs:
   pre-commit:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-python@v2
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
       - run: pip install pre-commit
       - run: pre-commit --version
-      - run: pre-commit install
       - run: pre-commit run --all-files

+ 1 - 1
.pre-commit-config.yaml

@@ -48,6 +48,6 @@ repos:
         language: node
         pass_filenames: false
         types: [python]
-        additional_dependencies: ["pyright"]
+        additional_dependencies: ["pyright@1.1.383"]
         args:
           - --project=pyproject.toml

+ 14 - 12
minigrid/__init__.py

@@ -7,18 +7,7 @@ from minigrid.core import roomgrid
 from minigrid.core.world_object import Wall
 from minigrid.envs.wfc.config import WFC_PRESETS, register_wfc_presets
 
-__version__ = "2.3.1"
-
-
-try:
-    import sys
-
-    from farama_notifications import notifications
-
-    if "minigrid" in notifications and __version__ in notifications["minigrid"]:
-        print(notifications["minigrid"][__version__], file=sys.stderr)
-except Exception:  # nosec
-    pass
+__version__ = "3.0.0"
 
 
 def register_minigrid_envs():
@@ -1133,3 +1122,16 @@ def register_minigrid_envs():
         id="BabyAI-BossLevelNoUnlock-v0",
         entry_point="minigrid.envs.babyai:BossLevelNoUnlock",
     )
+
+
+register_minigrid_envs()
+
+try:
+    import sys
+
+    from farama_notifications import notifications
+
+    if "minigrid" in notifications and __version__ in notifications["minigrid"]:
+        print(notifications["minigrid"][__version__], file=sys.stderr)
+except Exception:  # nosec
+    pass

+ 1 - 0
minigrid/envs/babyai/core/verifier.py

@@ -109,6 +109,7 @@ class ObjDesc:
         the location of the object. e.g. A ball that was on "your right" initially will still be tracked as being "on
         your right" when you move.
         """
+        env = env.unwrapped
 
         if use_location:
             self.obj_set = []

+ 2 - 0
minigrid/envs/crossing.py

@@ -93,6 +93,7 @@ class CrossingEnv(MiniGridEnv):
     ):
         self.num_crossings = num_crossings
         self.obstacle_type = obstacle_type
+        self.goal_position = None
 
         if obstacle_type == Lava:
             mission_space = MissionSpace(mission_func=self._gen_mission_lava)
@@ -133,6 +134,7 @@ class CrossingEnv(MiniGridEnv):
 
         # Place a goal square in the bottom-right corner
         self.put_obj(Goal(), width - 2, height - 2)
+        self.goal_position = (width - 2, height - 2)
 
         # Place obstacles (lava or walls)
         v, h = object(), object()  # singleton `vertical` and `horizontal` objects

+ 1 - 1
minigrid/manual_control.py

@@ -36,7 +36,7 @@ class ManualControl:
 
     def step(self, action: Actions):
         _, reward, terminated, truncated, _ = self.env.step(action)
-        print(f"step={self.env.step_count}, reward={reward:.2f}")
+        print(f"step={self.env.unwrapped.step_count}, reward={reward:.2f}")
 
         if terminated:
             print("terminated!")

+ 34 - 21
minigrid/wrappers.py

@@ -24,20 +24,20 @@ class ReseedWrapper(Wrapper):
         >>> from minigrid.wrappers import ReseedWrapper
         >>> env = gym.make("MiniGrid-Empty-5x5-v0")
         >>> _ = env.reset(seed=123)
-        >>> [env.np_random.integers(10) for i in range(10)]
+        >>> [env.np_random.integers(10).item() for i in range(10)]
         [0, 6, 5, 0, 9, 2, 2, 1, 3, 1]
         >>> env = ReseedWrapper(env, seeds=[0, 1], seed_idx=0)
         >>> _, _ = env.reset()
-        >>> [env.np_random.integers(10) for i in range(10)]
+        >>> [env.np_random.integers(10).item() for i in range(10)]
         [8, 6, 5, 2, 3, 0, 0, 0, 1, 8]
         >>> _, _ = env.reset()
-        >>> [env.np_random.integers(10) for i in range(10)]
+        >>> [env.np_random.integers(10).item() for i in range(10)]
         [4, 5, 7, 9, 0, 1, 8, 9, 2, 3]
         >>> _, _ = env.reset()
-        >>> [env.np_random.integers(10) for i in range(10)]
+        >>> [env.np_random.integers(10).item() for i in range(10)]
         [8, 6, 5, 2, 3, 0, 0, 0, 1, 8]
         >>> _, _ = env.reset()
-        >>> [env.np_random.integers(10) for i in range(10)]
+        >>> [env.np_random.integers(10).item() for i in range(10)]
         [4, 5, 7, 9, 0, 1, 8, 9, 2, 3]
     """
 
@@ -325,7 +325,7 @@ class RGBImgObsWrapper(ObservationWrapper):
         )
 
     def observation(self, obs):
-        rgb_img = self.get_frame(
+        rgb_img = self.unwrapped.get_frame(
             highlight=self.unwrapped.highlight, tile_size=self.tile_size
         )
 
@@ -374,7 +374,9 @@ class RGBImgPartialObsWrapper(ObservationWrapper):
         )
 
     def observation(self, obs):
-        rgb_img_partial = self.get_frame(tile_size=self.tile_size, agent_pov=True)
+        rgb_img_partial = self.unwrapped.get_frame(
+            tile_size=self.tile_size, agent_pov=True
+        )
 
         return {**obs, "image": rgb_img_partial}
 
@@ -403,7 +405,11 @@ class FullyObsWrapper(ObservationWrapper):
         new_image_space = spaces.Box(
             low=0,
             high=255,
-            shape=(self.env.width, self.env.height, 3),  # number of cells
+            shape=(
+                self.env.unwrapped.width,
+                self.env.unwrapped.height,
+                3,
+            ),  # number of cells
             dtype="uint8",
         )
 
@@ -680,7 +686,7 @@ class DirectionObsWrapper(ObservationWrapper):
         >>> env = gym.make("MiniGrid-LavaCrossingS11N5-v0")
         >>> env_obs = DirectionObsWrapper(env, type="slope")
         >>> obs, _ = env_obs.reset()
-        >>> obs['goal_direction']
+        >>> obs['goal_direction'].item()
         1.0
     """
 
@@ -696,21 +702,21 @@ class DirectionObsWrapper(ObservationWrapper):
 
         if not self.goal_position:
             self.goal_position = [
-                x for x, y in enumerate(self.grid.grid) if isinstance(y, Goal)
+                x for x, y in enumerate(self.unwrapped.grid.grid) if isinstance(y, Goal)
             ]
             # in case there are multiple goals , needs to be handled for other env types
             if len(self.goal_position) >= 1:
                 self.goal_position = (
-                    int(self.goal_position[0] / self.height),
-                    self.goal_position[0] % self.width,
+                    int(self.goal_position[0] / self.unwrapped.height),
+                    self.goal_position[0] % self.unwrapped.width,
                 )
 
         return self.observation(obs), info
 
     def observation(self, obs):
         slope = np.divide(
-            self.goal_position[1] - self.agent_pos[1],
-            self.goal_position[0] - self.agent_pos[0],
+            self.goal_position[1] - self.unwrapped.agent_pos[1],
+            self.goal_position[0] - self.unwrapped.agent_pos[0],
         )
 
         if self.type == "angle":
@@ -746,7 +752,11 @@ class SymbolicObsWrapper(ObservationWrapper):
         new_image_space = spaces.Box(
             low=0,
             high=max(OBJECT_TO_IDX.values()),
-            shape=(self.env.width, self.env.height, 3),  # number of cells
+            shape=(
+                self.env.unwrapped.width,
+                self.env.unwrapped.height,
+                3,
+            ),  # number of cells
             dtype="uint8",
         )
         self.observation_space = spaces.Dict(
@@ -755,10 +765,13 @@ class SymbolicObsWrapper(ObservationWrapper):
 
     def observation(self, obs):
         objects = np.array(
-            [OBJECT_TO_IDX[o.type] if o is not None else -1 for o in self.grid.grid]
+            [
+                OBJECT_TO_IDX[o.type] if o is not None else -1
+                for o in self.unwrapped.grid.grid
+            ]
         )
-        agent_pos = self.env.agent_pos
-        ncol, nrow = self.width, self.height
+        agent_pos = self.env.unwrapped.agent_pos
+        ncol, nrow = self.unwrapped.width, self.unwrapped.height
         grid = np.mgrid[:ncol, :nrow]
         _objects = np.transpose(objects.reshape(1, nrow, ncol), (0, 2, 1))
 
@@ -849,9 +862,9 @@ class NoDeath(Wrapper):
     def step(self, action):
         # In Dynamic-Obstacles, obstacles move after the agent moves,
         # so we need to check for collision before self.env.step()
-        front_cell = self.grid.get(*self.front_pos)
+        front_cell = self.unwrapped.grid.get(*self.unwrapped.front_pos)
         going_to_death = (
-            action == self.actions.forward
+            action == self.unwrapped.actions.forward
             and front_cell is not None
             and front_cell.type in self.no_death_types
         )
@@ -860,7 +873,7 @@ class NoDeath(Wrapper):
 
         # We also check if the agent stays in death cells (e.g., lava)
         # without moving
-        current_cell = self.grid.get(*self.agent_pos)
+        current_cell = self.unwrapped.grid.get(*self.unwrapped.agent_pos)
         in_death = current_cell is not None and current_cell.type in self.no_death_types
 
         if terminated and (going_to_death or in_death):

+ 11 - 29
pyproject.toml

@@ -13,7 +13,7 @@ authors = [{ name = "Farama Foundation", email = "contact@farama.org" }]
 license = { text = "MIT License" }
 keywords = ["Memory, Environment, Agent, RL, Gymnasium"]
 classifiers = [
-    "Development Status :: 4 - Beta",  # change to `5 - Production/Stable` when ready
+    "Development Status :: 4 - Beta",                             # change to `5 - Production/Stable` when ready
     "License :: OSI Approved :: MIT License",
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3.7",
@@ -24,23 +24,12 @@ classifiers = [
     'Intended Audience :: Science/Research',
     'Topic :: Scientific/Engineering :: Artificial Intelligence',
 ]
-dependencies = [
-    "numpy>=1.18.0",
-    "gymnasium>=0.28.1",
-    "pygame>=2.4.0",
-]
+dependencies = ["numpy>=1.18.0", "gymnasium>=0.28.1", "pygame>=2.4.0"]
 dynamic = ["version"]
 
 [project.optional-dependencies]
-testing = [
-    "pytest>=7.0.1",
-    "pytest-mock>=3.10.0",
-    "matplotlib>=3.0"
-]
-wfc = [
-    "networkx",
-    "imageio>=2.31.1",
-]
+testing = ["pytest>=7.0.1", "pytest-mock>=3.10.0", "matplotlib>=3.0"]
+wfc = ["networkx", "imageio>=2.31.1"]
 
 [project.urls]
 Homepage = "https://farama.org"
@@ -48,9 +37,6 @@ Repository = "https://minigrid.farama.org/"
 Documentation = "https://minigrid.farama.org/"
 "Bug Report" = "https://github.com/Farama-Foundation/Minigrid/issues"
 
-[project.entry-points."gymnasium.envs"]
-__root__ = "minigrid.__init__:register_minigrid_envs"
-
 [tool.setuptools]
 include-package-data = true
 
@@ -60,24 +46,18 @@ include = ["minigrid*"]
 # Linters and Test tools #######################################################
 
 [tool.black]
-safe = true
 
 [tool.isort]
 atomic = true
 profile = "black"
 append_only = true
 src_paths = ["minigrid", "tests"]
-add_imports = [ "from __future__ import annotations" ]
+add_imports = ["from __future__ import annotations"]
 
 [tool.pyright]
-include = [
-    "minigrid/**",
-]
+include = ["minigrid/**"]
 
-exclude = [
-    "**/node_modules",
-    "**/__pycache__",
-]
+exclude = ["**/node_modules", "**/__pycache__"]
 
 strict = []
 
@@ -98,8 +78,10 @@ reportPrivateUsage = "warning"
 reportUntypedFunctionDecorator = "none"
 reportMissingTypeStubs = false
 reportUnboundVariable = "warning"
-reportGeneralTypeIssues ="none"
+reportGeneralTypeIssues = "none"
 reportPrivateImportUsage = "none"
 
 [tool.pytest.ini_options]
-filterwarnings = ['ignore:.*step API.*:DeprecationWarning'] # TODO: to be removed when old step API is removed
+filterwarnings = [
+    'ignore:.*step API.*:DeprecationWarning',
+] # TODO: to be removed when old step API is removed

+ 2 - 2
requirements.txt

@@ -1,3 +1,3 @@
-numpy>=1.18.0
-gymnasium>=0.26
+numpy>=2.0.0
+gymnasium>=1.0.0
 pygame>=2.2.0

+ 4 - 4
tests/test_envs.py

@@ -121,13 +121,13 @@ def test_render_modes(spec):
 @pytest.mark.parametrize("env_id", ["MiniGrid-DoorKey-6x6-v0"])
 def test_agent_sees_method(env_id):
     env = gym.make(env_id)
-    goal_pos = (env.grid.width - 2, env.grid.height - 2)
+    goal_pos = (env.unwrapped.grid.width - 2, env.unwrapped.grid.height - 2)
 
     # Test the env.agent_sees() function
     env.reset()
     # Test the "in" operator on grid objects
-    assert ("green", "goal") in env.grid
-    assert ("blue", "key") not in env.grid
+    assert ("green", "goal") in env.unwrapped.grid
+    assert ("blue", "key") not in env.unwrapped.grid
     for i in range(0, 500):
         action = env.action_space.sample()
         obs, reward, terminated, truncated, info = env.step(action)
@@ -135,7 +135,7 @@ def test_agent_sees_method(env_id):
         grid, _ = Grid.decode(obs["image"])
         goal_visible = ("green", "goal") in grid
 
-        agent_sees_goal = env.agent_sees(*goal_pos)
+        agent_sees_goal = env.unwrapped.agent_sees(*goal_pos)
         assert agent_sees_goal == goal_visible
         if terminated or truncated:
             env.reset()

+ 3 - 3
tests/test_obstructed_maze.py

@@ -15,13 +15,13 @@ TESTING_ENVS = [
 
 
 def find_ball_room(env):
-    for obj in env.grid.grid:
+    for obj in env.unwrapped.grid.grid:
         if isinstance(obj, Ball) and obj.color == COLOR_NAMES[0]:
-            return env.room_from_pos(*obj.cur_pos)
+            return env.unwrapped.room_from_pos(*obj.cur_pos)
 
 
 def find_target_key(env, color):
-    for obj in env.grid.grid:
+    for obj in env.unwrapped.grid.grid:
         if isinstance(obj, Box) and obj.contains.color == color:
             return True
     return False

+ 1 - 1
tests/test_wfc/test_wfc_solver.py

@@ -18,7 +18,7 @@ def test_entropyLocationHeuristic() -> None:
     wave = np.ones((5, 3, 4), dtype=bool)  # everything is possible
     wave[1:, 0, 0] = False  # first cell is fully observed
     wave[4, :, 2] = False
-    preferences: NDArray[np.float_] = np.ones((3, 4), dtype=np.float_) * 0.5
+    preferences: NDArray[np.float64] = np.ones((3, 4), dtype=np.float64) * 0.5
     preferences[1, 2] = 0.3
     preferences[1, 1] = 0.1
     heu = wfc_solver.makeEntropyLocationHeuristic(preferences)

+ 15 - 16
tests/test_wrappers.py

@@ -141,7 +141,7 @@ def test_dict_observation_space_wrapper(env_spec):
     env = env_spec.make()
     env = DictObservationSpaceWrapper(env)
     env.reset()
-    mission = env.mission
+    mission = env.unwrapped.mission
     obs, _, _, _, _ = env.step(0)
     assert env.string_to_indices(mission) == [
         value for value in obs["mission"] if value != 0
@@ -273,10 +273,9 @@ def test_direction_obs_wrapper(env_id, type):
     env = gym.make(env_id)
     env = DirectionObsWrapper(env, type=type)
     obs, _ = env.reset()
-
     slope = np.divide(
-        env.goal_position[1] - env.agent_pos[1],
-        env.goal_position[0] - env.agent_pos[0],
+        env.unwrapped.goal_position[1] - env.unwrapped.agent_pos[1],
+        env.unwrapped.goal_position[0] - env.unwrapped.agent_pos[0],
     )
     if type == "slope":
         assert obs["goal_direction"] == slope
@@ -285,8 +284,8 @@ def test_direction_obs_wrapper(env_id, type):
 
     obs, _, _, _, _ = env.step(0)
     slope = np.divide(
-        env.goal_position[1] - env.agent_pos[1],
-        env.goal_position[0] - env.agent_pos[0],
+        env.unwrapped.goal_position[1] - env.unwrapped.agent_pos[1],
+        env.unwrapped.goal_position[0] - env.unwrapped.agent_pos[0],
     )
     if type == "slope":
         assert obs["goal_direction"] == slope
@@ -302,29 +301,29 @@ def test_symbolic_obs_wrapper(env_id):
 
     env = SymbolicObsWrapper(env)
     obs, _ = env.reset(seed=123)
-    agent_pos = env.agent_pos
-    goal_pos = env.goal_pos
+    agent_pos = env.unwrapped.agent_pos
+    goal_pos = env.unwrapped.goal_pos
 
-    assert obs["image"].shape == (env.width, env.height, 3)
-    assert np.alltrue(
+    assert obs["image"].shape == (env.unwrapped.width, env.unwrapped.height, 3)
+    assert np.all(
         obs["image"][agent_pos[0], agent_pos[1], :]
         == np.array([agent_pos[0], agent_pos[1], OBJECT_TO_IDX["agent"]])
     )
-    assert np.alltrue(
+    assert np.all(
         obs["image"][goal_pos[0], goal_pos[1], :]
         == np.array([goal_pos[0], goal_pos[1], OBJECT_TO_IDX["goal"]])
     )
 
     obs, _, _, _, _ = env.step(2)
-    agent_pos = env.agent_pos
-    goal_pos = env.goal_pos
+    agent_pos = env.unwrapped.agent_pos
+    goal_pos = env.unwrapped.goal_pos
 
-    assert obs["image"].shape == (env.width, env.height, 3)
-    assert np.alltrue(
+    assert obs["image"].shape == (env.unwrapped.width, env.unwrapped.height, 3)
+    assert np.all(
         obs["image"][agent_pos[0], agent_pos[1], :]
         == np.array([agent_pos[0], agent_pos[1], OBJECT_TO_IDX["agent"]])
     )
-    assert np.alltrue(
+    assert np.all(
         obs["image"][goal_pos[0], goal_pos[1], :]
         == np.array([goal_pos[0], goal_pos[1], OBJECT_TO_IDX["goal"]])
     )