From f58c71c2bbad3f0fd95ea6fb66f44ffbc4c4e428 Mon Sep 17 00:00:00 2001 From: dantp <1534513+dantp-ai@users.noreply.github.com> Date: Thu, 11 Jan 2024 09:52:24 +0100 Subject: [PATCH 1/2] Refactoring/type hints (#215) Co-authored-by: Sasha --- jumanji/env.py | 6 ++++-- jumanji/environments/packing/bin_pack/space.py | 11 ++++++----- jumanji/environments/routing/connector/types.py | 3 ++- jumanji/environments/routing/snake/types.py | 5 +++-- jumanji/training/loggers.py | 3 ++- jumanji/training/networks/distribution.py | 5 +++-- jumanji/training/timer.py | 3 ++- jumanji/wrappers.py | 3 ++- 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/jumanji/env.py b/jumanji/env.py index 7f855b4fd..d3ddac6bd 100644 --- a/jumanji/env.py +++ b/jumanji/env.py @@ -14,6 +14,8 @@ """Abstract environment class""" +from __future__ import annotations + import abc from typing import Any, Generic, Tuple, TypeVar @@ -105,7 +107,7 @@ def discount_spec(self) -> specs.BoundedArray: ) @property - def unwrapped(self) -> "Environment": + def unwrapped(self) -> Environment: return self def render(self, state: State) -> Any: @@ -119,7 +121,7 @@ def render(self, state: State) -> Any: def close(self) -> None: """Perform any necessary cleanup.""" - def __enter__(self) -> "Environment": + def __enter__(self) -> Environment: return self def __exit__(self, *args: Any) -> None: diff --git a/jumanji/environments/packing/bin_pack/space.py b/jumanji/environments/packing/bin_pack/space.py index 45d1fccf1..89f38dc45 100644 --- a/jumanji/environments/packing/bin_pack/space.py +++ b/jumanji/environments/packing/bin_pack/space.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations from typing import TYPE_CHECKING, Any @@ -32,7 +33,7 @@ class Space: z1: chex.Numeric z2: chex.Numeric - def astype(self, dtype: Any) -> "Space": + def astype(self, dtype: Any) -> Space: space_dict = { key: jnp.asarray(value, dtype) for key, value in self.__dict__.items() } @@ -90,7 +91,7 @@ def volume(self) -> chex.Numeric: z_len = jnp.asarray(self.z2 - self.z1, float) return x_len * y_len * z_len - def intersection(self, space: "Space") -> "Space": + def intersection(self, space: Space) -> Space: """Returns the intersected space with another space (i.e. the space that is included in both spaces whose volume is maximum). """ @@ -102,7 +103,7 @@ def intersection(self, space: "Space") -> "Space": z2 = jnp.minimum(self.z2, space.z2) return Space(x1=x1, x2=x2, y1=y1, y2=y2, z1=z1, z2=z2) - def intersect(self, space: "Space") -> chex.Numeric: + def intersect(self, space: Space) -> chex.Numeric: """Returns whether a space intersect another space or not.""" return ~(self.intersection(space).is_empty()) @@ -110,7 +111,7 @@ def is_empty(self) -> chex.Numeric: """A space is empty if at least one dimension is negative or zero.""" return (self.x1 >= self.x2) | (self.y1 >= self.y2) | (self.z1 >= self.z2) - def is_included(self, space: "Space") -> chex.Numeric: + def is_included(self, space: Space) -> chex.Numeric: """Returns whether self is included into another space.""" return ( (self.x1 >= space.x1) @@ -121,7 +122,7 @@ def is_included(self, space: "Space") -> chex.Numeric: & (self.z2 <= space.z2) ) - def hyperplane(self, axis: str, direction: str) -> "Space": + def hyperplane(self, axis: str, direction: str) -> Space: """Returns the hyperplane (e.g. lower hyperplane on the x axis) for EMS creation when packing an item. diff --git a/jumanji/environments/routing/connector/types.py b/jumanji/environments/routing/connector/types.py index 53f12edf8..acf81ea6e 100644 --- a/jumanji/environments/routing/connector/types.py +++ b/jumanji/environments/routing/connector/types.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations from typing import TYPE_CHECKING, Any, NamedTuple @@ -42,7 +43,7 @@ def connected(self) -> chex.Array: """returns: True if the agent has reached its target.""" return jnp.all(self.position == self.target, axis=-1) - def __eq__(self: "Agent", agent_2: Any) -> chex.Array: + def __eq__(self: Agent, agent_2: Any) -> chex.Array: if not isinstance(agent_2, Agent): return NotImplemented same_ids = (agent_2.id == self.id).all() diff --git a/jumanji/environments/routing/snake/types.py b/jumanji/environments/routing/snake/types.py index 6b6b0354d..7617240e8 100644 --- a/jumanji/environments/routing/snake/types.py +++ b/jumanji/environments/routing/snake/types.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations from enum import IntEnum from typing import TYPE_CHECKING, NamedTuple @@ -27,12 +28,12 @@ class Position(NamedTuple): row: chex.Array col: chex.Array - def __eq__(self, other: "Position") -> chex.Array: # type: ignore[override] + def __eq__(self, other: Position) -> chex.Array: # type: ignore[override] if not isinstance(other, Position): return NotImplemented return (self.row == other.row) & (self.col == other.col) - def __add__(self, other: "Position") -> "Position": # type: ignore[override] + def __add__(self, other: Position) -> Position: # type: ignore[override] if not isinstance(other, Position): return NotImplemented return Position(row=self.row + other.row, col=self.col + other.col) diff --git a/jumanji/training/loggers.py b/jumanji/training/loggers.py index 8ccf07b26..722bd9987 100644 --- a/jumanji/training/loggers.py +++ b/jumanji/training/loggers.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations import abc import collections @@ -56,7 +57,7 @@ def close(self) -> None: def upload_checkpoint(self) -> None: """Uploads a checkpoint when exiting the logger.""" - def __enter__(self) -> "Logger": + def __enter__(self) -> Logger: logging.info("Starting logger.") self._variables_enter = self._get_variables() return self diff --git a/jumanji/training/networks/distribution.py b/jumanji/training/networks/distribution.py index 0e7a751fb..481790d76 100644 --- a/jumanji/training/networks/distribution.py +++ b/jumanji/training/networks/distribution.py @@ -13,6 +13,7 @@ # limitations under the License. """Adapted from Brax.""" +from __future__ import annotations import abc @@ -39,7 +40,7 @@ def entropy(self) -> chex.Array: pass @abc.abstractmethod - def kl_divergence(self, other: "Distribution") -> chex.Array: + def kl_divergence(self, other: Distribution) -> chex.Array: pass @@ -77,7 +78,7 @@ def entropy(self) -> chex.Array: def kl_divergence( # type: ignore[override] self, - other: "CategoricalDistribution", + other: CategoricalDistribution, ) -> chex.Array: log_probs = jax.nn.log_softmax(self.logits) probs = jax.nn.softmax(self.logits) diff --git a/jumanji/training/timer.py b/jumanji/training/timer.py index 24c7a446b..3d03d55d3 100644 --- a/jumanji/training/timer.py +++ b/jumanji/training/timer.py @@ -14,6 +14,7 @@ # Inspired from https://stackoverflow.com/questions/51849395/how-can-we-associate-a-python-context-m # anager-to-the-variables-appearing-in-it#:~:text=also%20inspect%20the-,stack,-for%20locals()%20variables +from __future__ import annotations import inspect import logging @@ -45,7 +46,7 @@ def _get_variables(self) -> Dict: """ return {(k, id(v)): v for k, v in inspect.stack()[2].frame.f_locals.items()} - def __enter__(self) -> "Timer": + def __enter__(self) -> Timer: self._variables_enter = self._get_variables() self._start_time = time.perf_counter() return self diff --git a/jumanji/wrappers.py b/jumanji/wrappers.py index a96e29b57..98d1ec91d 100644 --- a/jumanji/wrappers.py +++ b/jumanji/wrappers.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations from typing import ( Any, @@ -120,7 +121,7 @@ def close(self) -> None: """ return self._env.close() - def __enter__(self) -> "Wrapper": + def __enter__(self) -> Wrapper: return self def __exit__(self, *args: Any) -> None: From d21d23b048975401f26fbcbfa65b41195fceee22 Mon Sep 17 00:00:00 2001 From: Callum Tilbury <37700709+callumtilbury@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:18:35 +0200 Subject: [PATCH 2/2] Revert "feat: Toggle logo in dark mode" (#209) Co-authored-by: Sasha --- README.md | 8 +++----- docs/img/jumanji_logo_dm.png | Bin 19159 -> 0 bytes 2 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 docs/img/jumanji_logo_dm.png diff --git a/README.md b/README.md index aaa11ece2..38eb32850 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@

- - - - Jumanji Logo - + + Jumanji logo +

[![Python Versions](https://img.shields.io/pypi/pyversions/jumanji.svg?style=flat-square)](https://www.python.org/doc/versions/) diff --git a/docs/img/jumanji_logo_dm.png b/docs/img/jumanji_logo_dm.png deleted file mode 100644 index 0a13109cb843390ac18bcad9c000da925e8e8df7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19159 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