We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Bug Report] Box bounded below and above doesn't work when dtype is int
**Describe the bug**
A `Box` space that is created with dtype `int` and `low` (`high`) values being `-np.inf` (`np.inf`) will return `bounded_{above,below}` as True.

**Code example**
```python3
from gym.spaces import Box
import numpy as np

unbounded_below = Box(
    np.array([0, 13, -3, -np.inf]),
    np.array([0, 20, 0, 0]),
    dtype=int
)

unbounded_above = Box(
    np.array([0, 12, 20, 0]),
    np.array([np.inf, 20, 24, np.inf]),
    dtype=int
)

print(unbounded_above.bounded_above)
print(unbounded_below.bounded_below)

>>> [ True  True  True  True]
>>> [ True  True  True  True]
```

**My analysis**
At the [broadcast](https://github.com/openai/gym/blob/master/gym/spaces/box.py#L64), [get_inf](https://github.com/openai/gym/blob/master/gym/spaces/box.py#L226) turns `np.inf` into their integer equivalents. Determining [boundedness](https://github.com/openai/gym/blob/master/gym/spaces/box.py#L86) is comparing directly with `np.inf`, not with the integer equivalents given from `get_inf`.

**System Info**
Describe the characteristic of your environment:
 * Gym installed with pip
 * MacOS 10.15.7
 * Python version: 3.7.9
 * numpy 1.21.5

**Additional context**
I know it may seem strange to use `np.inf` with an integer-Box, but I use integer-Boxes all the time, and I need to check against `np.inf`.

### Checklist

- [X] I have checked that there is no similar [issue](https://github.com/openai/gym/issues) in the repo (**required**)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gym/spaces/box.py`
Content:
```
1 from __future__ import annotations
2 
3 from typing import Tuple, SupportsFloat, Union, Type, Optional, Sequence
4 
5 import numpy as np
6 
7 from .space import Space
8 from gym import logger
9 
10 
11 def _short_repr(arr: np.ndarray) -> str:
12     """Create a shortened string representation of a numpy array.
13 
14     If arr is a multiple of the all-ones vector, return a string representation of the multiplier.
15     Otherwise, return a string representation of the entire array.
16     """
17     if arr.size != 0 and np.min(arr) == np.max(arr):
18         return str(np.min(arr))
19     return str(arr)
20 
21 
22 class Box(Space[np.ndarray]):
23     """
24     A (possibly unbounded) box in R^n. Specifically, a Box represents the
25     Cartesian product of n closed intervals. Each interval has the form of one
26     of [a, b], (-oo, b], [a, oo), or (-oo, oo).
27 
28     There are two common use cases:
29 
30     * Identical bound for each dimension::
31         >>> Box(low=-1.0, high=2.0, shape=(3, 4), dtype=np.float32)
32         Box(3, 4)
33 
34     * Independent bound for each dimension::
35         >>> Box(low=np.array([-1.0, -2.0]), high=np.array([2.0, 4.0]), dtype=np.float32)
36         Box(2,)
37 
38     """
39 
40     def __init__(
41         self,
42         low: Union[SupportsFloat, np.ndarray],
43         high: Union[SupportsFloat, np.ndarray],
44         shape: Optional[Sequence[int]] = None,
45         dtype: Type = np.float32,
46         seed: Optional[int] = None,
47     ):
48         assert dtype is not None, "dtype must be explicitly provided. "
49         self.dtype = np.dtype(dtype)
50 
51         # determine shape if it isn't provided directly
52         if shape is not None:
53             shape = tuple(shape)
54         elif not np.isscalar(low):
55             shape = low.shape  # type: ignore
56         elif not np.isscalar(high):
57             shape = high.shape  # type: ignore
58         else:
59             raise ValueError(
60                 "shape must be provided or inferred from the shapes of low or high"
61             )
62         assert isinstance(shape, tuple)
63 
64         low = _broadcast(low, dtype, shape, inf_sign="-")  # type: ignore
65         high = _broadcast(high, dtype, shape, inf_sign="+")
66 
67         assert isinstance(low, np.ndarray)
68         assert low.shape == shape, "low.shape doesn't match provided shape"
69         assert isinstance(high, np.ndarray)
70         assert high.shape == shape, "high.shape doesn't match provided shape"
71 
72         self._shape: Tuple[int, ...] = shape
73 
74         low_precision = get_precision(low.dtype)
75         high_precision = get_precision(high.dtype)
76         dtype_precision = get_precision(self.dtype)
77         if min(low_precision, high_precision) > dtype_precision:  # type: ignore
78             logger.warn(f"Box bound precision lowered by casting to {self.dtype}")
79         self.low = low.astype(self.dtype)
80         self.high = high.astype(self.dtype)
81 
82         self.low_repr = _short_repr(self.low)
83         self.high_repr = _short_repr(self.high)
84 
85         # Boolean arrays which indicate the interval type for each coordinate
86         self.bounded_below = -np.inf < self.low
87         self.bounded_above = np.inf > self.high
88 
89         super().__init__(self.shape, self.dtype, seed)
90 
91     @property
92     def shape(self) -> Tuple[int, ...]:
93         """Has stricter type than gym.Space - never None."""
94         return self._shape
95 
96     def is_bounded(self, manner: str = "both") -> bool:
97         below = bool(np.all(self.bounded_below))
98         above = bool(np.all(self.bounded_above))
99         if manner == "both":
100             return below and above
101         elif manner == "below":
102             return below
103         elif manner == "above":
104             return above
105         else:
106             raise ValueError("manner is not in {'below', 'above', 'both'}")
107 
108     def sample(self) -> np.ndarray:
109         """
110         Generates a single random sample inside of the Box.
111 
112         In creating a sample of the box, each coordinate is sampled according to
113         the form of the interval:
114 
115         * [a, b] : uniform distribution
116         * [a, oo) : shifted exponential distribution
117         * (-oo, b] : shifted negative exponential distribution
118         * (-oo, oo) : normal distribution
119         """
120         high = self.high if self.dtype.kind == "f" else self.high.astype("int64") + 1
121         sample = np.empty(self.shape)
122 
123         # Masking arrays which classify the coordinates according to interval
124         # type
125         unbounded = ~self.bounded_below & ~self.bounded_above
126         upp_bounded = ~self.bounded_below & self.bounded_above
127         low_bounded = self.bounded_below & ~self.bounded_above
128         bounded = self.bounded_below & self.bounded_above
129 
130         # Vectorized sampling by interval type
131         sample[unbounded] = self.np_random.normal(size=unbounded[unbounded].shape)
132 
133         sample[low_bounded] = (
134             self.np_random.exponential(size=low_bounded[low_bounded].shape)
135             + self.low[low_bounded]
136         )
137 
138         sample[upp_bounded] = (
139             -self.np_random.exponential(size=upp_bounded[upp_bounded].shape)
140             + self.high[upp_bounded]
141         )
142 
143         sample[bounded] = self.np_random.uniform(
144             low=self.low[bounded], high=high[bounded], size=bounded[bounded].shape
145         )
146         if self.dtype.kind == "i":
147             sample = np.floor(sample)
148 
149         return sample.astype(self.dtype)
150 
151     def contains(self, x) -> bool:
152         if not isinstance(x, np.ndarray):
153             logger.warn("Casting input x to numpy array.")
154             x = np.asarray(x, dtype=self.dtype)
155 
156         return bool(
157             np.can_cast(x.dtype, self.dtype)
158             and x.shape == self.shape
159             and np.all(x >= self.low)
160             and np.all(x <= self.high)
161         )
162 
163     def to_jsonable(self, sample_n):
164         return np.array(sample_n).tolist()
165 
166     def from_jsonable(self, sample_n: Sequence[SupportsFloat]) -> list[np.ndarray]:
167         return [np.asarray(sample) for sample in sample_n]
168 
169     def __repr__(self) -> str:
170         return f"Box({self.low_repr}, {self.high_repr}, {self.shape}, {self.dtype})"
171 
172     def __eq__(self, other) -> bool:
173         return (
174             isinstance(other, Box)
175             and (self.shape == other.shape)
176             and np.allclose(self.low, other.low)
177             and np.allclose(self.high, other.high)
178         )
179 
180 
181 def get_inf(dtype, sign: str) -> SupportsFloat:
182     """Returns an infinite that doesn't break things.
183     `dtype` must be an `np.dtype`
184     `bound` must be either `min` or `max`
185     """
186     if np.dtype(dtype).kind == "f":
187         if sign == "+":
188             return np.inf
189         elif sign == "-":
190             return -np.inf
191         else:
192             raise TypeError(f"Unknown sign {sign}, use either '+' or '-'")
193     elif np.dtype(dtype).kind == "i":
194         if sign == "+":
195             return np.iinfo(dtype).max - 2
196         elif sign == "-":
197             return np.iinfo(dtype).min + 2
198         else:
199             raise TypeError(f"Unknown sign {sign}, use either '+' or '-'")
200     else:
201         raise ValueError(f"Unknown dtype {dtype} for infinite bounds")
202 
203 
204 def get_precision(dtype) -> SupportsFloat:
205     if np.issubdtype(dtype, np.floating):
206         return np.finfo(dtype).precision
207     else:
208         return np.inf
209 
210 
211 def _broadcast(
212     value: Union[SupportsFloat, np.ndarray],
213     dtype,
214     shape: tuple[int, ...],
215     inf_sign: str,
216 ) -> np.ndarray:
217     """handle infinite bounds and broadcast at the same time if needed"""
218     if np.isscalar(value):
219         value = get_inf(dtype, inf_sign) if np.isinf(value) else value  # type: ignore
220         value = np.full(shape, value, dtype=dtype)
221     else:
222         assert isinstance(value, np.ndarray)
223         if np.any(np.isinf(value)):
224             # create new array with dtype, but maintain old one to preserve np.inf
225             temp = value.astype(dtype)
226             temp[np.isinf(value)] = get_inf(dtype, inf_sign)
227             value = temp
228     return value
229
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gym/spaces/box.py b/gym/spaces/box.py
--- a/gym/spaces/box.py
+++ b/gym/spaces/box.py
@@ -61,8 +61,14 @@
             )
         assert isinstance(shape, tuple)
 
+        # Capture the boundedness information before replacing np.inf with get_inf
+        _low = np.full(shape, low, dtype=float) if np.isscalar(low) else low
+        self.bounded_below = -np.inf < _low
+        _high = np.full(shape, high, dtype=float) if np.isscalar(high) else high
+        self.bounded_above = np.inf > _high
+
         low = _broadcast(low, dtype, shape, inf_sign="-")  # type: ignore
-        high = _broadcast(high, dtype, shape, inf_sign="+")
+        high = _broadcast(high, dtype, shape, inf_sign="+")  # type: ignore
 
         assert isinstance(low, np.ndarray)
         assert low.shape == shape, "low.shape doesn't match provided shape"
@@ -82,10 +88,6 @@
         self.low_repr = _short_repr(self.low)
         self.high_repr = _short_repr(self.high)
 
-        # Boolean arrays which indicate the interval type for each coordinate
-        self.bounded_below = -np.inf < self.low
-        self.bounded_above = np.inf > self.high
-
         super().__init__(self.shape, self.dtype, seed)
 
     @property

{"golden_diff": "diff --git a/gym/spaces/box.py b/gym/spaces/box.py\n--- a/gym/spaces/box.py\n+++ b/gym/spaces/box.py\n@@ -61,8 +61,14 @@\n             )\n         assert isinstance(shape, tuple)\n \n+        # Capture the boundedness information before replacing np.inf with get_inf\n+        _low = np.full(shape, low, dtype=float) if np.isscalar(low) else low\n+        self.bounded_below = -np.inf < _low\n+        _high = np.full(shape, high, dtype=float) if np.isscalar(high) else high\n+        self.bounded_above = np.inf > _high\n+\n         low = _broadcast(low, dtype, shape, inf_sign=\"-\")  # type: ignore\n-        high = _broadcast(high, dtype, shape, inf_sign=\"+\")\n+        high = _broadcast(high, dtype, shape, inf_sign=\"+\")  # type: ignore\n \n         assert isinstance(low, np.ndarray)\n         assert low.shape == shape, \"low.shape doesn't match provided shape\"\n@@ -82,10 +88,6 @@\n         self.low_repr = _short_repr(self.low)\n         self.high_repr = _short_repr(self.high)\n \n-        # Boolean arrays which indicate the interval type for each coordinate\n-        self.bounded_below = -np.inf < self.low\n-        self.bounded_above = np.inf > self.high\n-\n         super().__init__(self.shape, self.dtype, seed)\n \n     @property\n", "issue": "[Bug Report] Box bounded below and above doesn't work when dtype is int\n**Describe the bug**\r\nA `Box` space that is created with dtype `int` and `low` (`high`) values being `-np.inf` (`np.inf`) will return `bounded_{above,below}` as True.\r\n\r\n**Code example**\r\n```python3\r\nfrom gym.spaces import Box\r\nimport numpy as np\r\n\r\nunbounded_below = Box(\r\n    np.array([0, 13, -3, -np.inf]),\r\n    np.array([0, 20, 0, 0]),\r\n    dtype=int\r\n)\r\n\r\nunbounded_above = Box(\r\n    np.array([0, 12, 20, 0]),\r\n    np.array([np.inf, 20, 24, np.inf]),\r\n    dtype=int\r\n)\r\n\r\nprint(unbounded_above.bounded_above)\r\nprint(unbounded_below.bounded_below)\r\n\r\n>>> [ True  True  True  True]\r\n>>> [ True  True  True  True]\r\n```\r\n\r\n**My analysis**\r\nAt the [broadcast](https://github.com/openai/gym/blob/master/gym/spaces/box.py#L64), [get_inf](https://github.com/openai/gym/blob/master/gym/spaces/box.py#L226) turns `np.inf` into their integer equivalents. Determining [boundedness](https://github.com/openai/gym/blob/master/gym/spaces/box.py#L86) is comparing directly with `np.inf`, not with the integer equivalents given from `get_inf`.\r\n\r\n**System Info**\r\nDescribe the characteristic of your environment:\r\n * Gym installed with pip\r\n * MacOS 10.15.7\r\n * Python version: 3.7.9\r\n * numpy 1.21.5\r\n\r\n**Additional context**\r\nI know it may seem strange to use `np.inf` with an integer-Box, but I use integer-Boxes all the time, and I need to check against `np.inf`.\r\n\r\n### Checklist\r\n\r\n- [X] I have checked that there is no similar [issue](https://github.com/openai/gym/issues) in the repo (**required**)\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom typing import Tuple, SupportsFloat, Union, Type, Optional, Sequence\n\nimport numpy as np\n\nfrom .space import Space\nfrom gym import logger\n\n\ndef _short_repr(arr: np.ndarray) -> str:\n    \"\"\"Create a shortened string representation of a numpy array.\n\n    If arr is a multiple of the all-ones vector, return a string representation of the multiplier.\n    Otherwise, return a string representation of the entire array.\n    \"\"\"\n    if arr.size != 0 and np.min(arr) == np.max(arr):\n        return str(np.min(arr))\n    return str(arr)\n\n\nclass Box(Space[np.ndarray]):\n    \"\"\"\n    A (possibly unbounded) box in R^n. Specifically, a Box represents the\n    Cartesian product of n closed intervals. Each interval has the form of one\n    of [a, b], (-oo, b], [a, oo), or (-oo, oo).\n\n    There are two common use cases:\n\n    * Identical bound for each dimension::\n        >>> Box(low=-1.0, high=2.0, shape=(3, 4), dtype=np.float32)\n        Box(3, 4)\n\n    * Independent bound for each dimension::\n        >>> Box(low=np.array([-1.0, -2.0]), high=np.array([2.0, 4.0]), dtype=np.float32)\n        Box(2,)\n\n    \"\"\"\n\n    def __init__(\n        self,\n        low: Union[SupportsFloat, np.ndarray],\n        high: Union[SupportsFloat, np.ndarray],\n        shape: Optional[Sequence[int]] = None,\n        dtype: Type = np.float32,\n        seed: Optional[int] = None,\n    ):\n        assert dtype is not None, \"dtype must be explicitly provided. \"\n        self.dtype = np.dtype(dtype)\n\n        # determine shape if it isn't provided directly\n        if shape is not None:\n            shape = tuple(shape)\n        elif not np.isscalar(low):\n            shape = low.shape  # type: ignore\n        elif not np.isscalar(high):\n            shape = high.shape  # type: ignore\n        else:\n            raise ValueError(\n                \"shape must be provided or inferred from the shapes of low or high\"\n            )\n        assert isinstance(shape, tuple)\n\n        low = _broadcast(low, dtype, shape, inf_sign=\"-\")  # type: ignore\n        high = _broadcast(high, dtype, shape, inf_sign=\"+\")\n\n        assert isinstance(low, np.ndarray)\n        assert low.shape == shape, \"low.shape doesn't match provided shape\"\n        assert isinstance(high, np.ndarray)\n        assert high.shape == shape, \"high.shape doesn't match provided shape\"\n\n        self._shape: Tuple[int, ...] = shape\n\n        low_precision = get_precision(low.dtype)\n        high_precision = get_precision(high.dtype)\n        dtype_precision = get_precision(self.dtype)\n        if min(low_precision, high_precision) > dtype_precision:  # type: ignore\n            logger.warn(f\"Box bound precision lowered by casting to {self.dtype}\")\n        self.low = low.astype(self.dtype)\n        self.high = high.astype(self.dtype)\n\n        self.low_repr = _short_repr(self.low)\n        self.high_repr = _short_repr(self.high)\n\n        # Boolean arrays which indicate the interval type for each coordinate\n        self.bounded_below = -np.inf < self.low\n        self.bounded_above = np.inf > self.high\n\n        super().__init__(self.shape, self.dtype, seed)\n\n    @property\n    def shape(self) -> Tuple[int, ...]:\n        \"\"\"Has stricter type than gym.Space - never None.\"\"\"\n        return self._shape\n\n    def is_bounded(self, manner: str = \"both\") -> bool:\n        below = bool(np.all(self.bounded_below))\n        above = bool(np.all(self.bounded_above))\n        if manner == \"both\":\n            return below and above\n        elif manner == \"below\":\n            return below\n        elif manner == \"above\":\n            return above\n        else:\n            raise ValueError(\"manner is not in {'below', 'above', 'both'}\")\n\n    def sample(self) -> np.ndarray:\n        \"\"\"\n        Generates a single random sample inside of the Box.\n\n        In creating a sample of the box, each coordinate is sampled according to\n        the form of the interval:\n\n        * [a, b] : uniform distribution\n        * [a, oo) : shifted exponential distribution\n        * (-oo, b] : shifted negative exponential distribution\n        * (-oo, oo) : normal distribution\n        \"\"\"\n        high = self.high if self.dtype.kind == \"f\" else self.high.astype(\"int64\") + 1\n        sample = np.empty(self.shape)\n\n        # Masking arrays which classify the coordinates according to interval\n        # type\n        unbounded = ~self.bounded_below & ~self.bounded_above\n        upp_bounded = ~self.bounded_below & self.bounded_above\n        low_bounded = self.bounded_below & ~self.bounded_above\n        bounded = self.bounded_below & self.bounded_above\n\n        # Vectorized sampling by interval type\n        sample[unbounded] = self.np_random.normal(size=unbounded[unbounded].shape)\n\n        sample[low_bounded] = (\n            self.np_random.exponential(size=low_bounded[low_bounded].shape)\n            + self.low[low_bounded]\n        )\n\n        sample[upp_bounded] = (\n            -self.np_random.exponential(size=upp_bounded[upp_bounded].shape)\n            + self.high[upp_bounded]\n        )\n\n        sample[bounded] = self.np_random.uniform(\n            low=self.low[bounded], high=high[bounded], size=bounded[bounded].shape\n        )\n        if self.dtype.kind == \"i\":\n            sample = np.floor(sample)\n\n        return sample.astype(self.dtype)\n\n    def contains(self, x) -> bool:\n        if not isinstance(x, np.ndarray):\n            logger.warn(\"Casting input x to numpy array.\")\n            x = np.asarray(x, dtype=self.dtype)\n\n        return bool(\n            np.can_cast(x.dtype, self.dtype)\n            and x.shape == self.shape\n            and np.all(x >= self.low)\n            and np.all(x <= self.high)\n        )\n\n    def to_jsonable(self, sample_n):\n        return np.array(sample_n).tolist()\n\n    def from_jsonable(self, sample_n: Sequence[SupportsFloat]) -> list[np.ndarray]:\n        return [np.asarray(sample) for sample in sample_n]\n\n    def __repr__(self) -> str:\n        return f\"Box({self.low_repr}, {self.high_repr}, {self.shape}, {self.dtype})\"\n\n    def __eq__(self, other) -> bool:\n        return (\n            isinstance(other, Box)\n            and (self.shape == other.shape)\n            and np.allclose(self.low, other.low)\n            and np.allclose(self.high, other.high)\n        )\n\n\ndef get_inf(dtype, sign: str) -> SupportsFloat:\n    \"\"\"Returns an infinite that doesn't break things.\n    `dtype` must be an `np.dtype`\n    `bound` must be either `min` or `max`\n    \"\"\"\n    if np.dtype(dtype).kind == \"f\":\n        if sign == \"+\":\n            return np.inf\n        elif sign == \"-\":\n            return -np.inf\n        else:\n            raise TypeError(f\"Unknown sign {sign}, use either '+' or '-'\")\n    elif np.dtype(dtype).kind == \"i\":\n        if sign == \"+\":\n            return np.iinfo(dtype).max - 2\n        elif sign == \"-\":\n            return np.iinfo(dtype).min + 2\n        else:\n            raise TypeError(f\"Unknown sign {sign}, use either '+' or '-'\")\n    else:\n        raise ValueError(f\"Unknown dtype {dtype} for infinite bounds\")\n\n\ndef get_precision(dtype) -> SupportsFloat:\n    if np.issubdtype(dtype, np.floating):\n        return np.finfo(dtype).precision\n    else:\n        return np.inf\n\n\ndef _broadcast(\n    value: Union[SupportsFloat, np.ndarray],\n    dtype,\n    shape: tuple[int, ...],\n    inf_sign: str,\n) -> np.ndarray:\n    \"\"\"handle infinite bounds and broadcast at the same time if needed\"\"\"\n    if np.isscalar(value):\n        value = get_inf(dtype, inf_sign) if np.isinf(value) else value  # type: ignore\n        value = np.full(shape, value, dtype=dtype)\n    else:\n        assert isinstance(value, np.ndarray)\n        if np.any(np.isinf(value)):\n            # create new array with dtype, but maintain old one to preserve np.inf\n            temp = value.astype(dtype)\n            temp[np.isinf(value)] = get_inf(dtype, inf_sign)\n            value = temp\n    return value\n", "path": "gym/spaces/box.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom typing import Tuple, SupportsFloat, Union, Type, Optional, Sequence\n\nimport numpy as np\n\nfrom .space import Space\nfrom gym import logger\n\n\ndef _short_repr(arr: np.ndarray) -> str:\n    \"\"\"Create a shortened string representation of a numpy array.\n\n    If arr is a multiple of the all-ones vector, return a string representation of the multiplier.\n    Otherwise, return a string representation of the entire array.\n    \"\"\"\n    if arr.size != 0 and np.min(arr) == np.max(arr):\n        return str(np.min(arr))\n    return str(arr)\n\n\nclass Box(Space[np.ndarray]):\n    \"\"\"\n    A (possibly unbounded) box in R^n. Specifically, a Box represents the\n    Cartesian product of n closed intervals. Each interval has the form of one\n    of [a, b], (-oo, b], [a, oo), or (-oo, oo).\n\n    There are two common use cases:\n\n    * Identical bound for each dimension::\n        >>> Box(low=-1.0, high=2.0, shape=(3, 4), dtype=np.float32)\n        Box(3, 4)\n\n    * Independent bound for each dimension::\n        >>> Box(low=np.array([-1.0, -2.0]), high=np.array([2.0, 4.0]), dtype=np.float32)\n        Box(2,)\n\n    \"\"\"\n\n    def __init__(\n        self,\n        low: Union[SupportsFloat, np.ndarray],\n        high: Union[SupportsFloat, np.ndarray],\n        shape: Optional[Sequence[int]] = None,\n        dtype: Type = np.float32,\n        seed: Optional[int] = None,\n    ):\n        assert dtype is not None, \"dtype must be explicitly provided. \"\n        self.dtype = np.dtype(dtype)\n\n        # determine shape if it isn't provided directly\n        if shape is not None:\n            shape = tuple(shape)\n        elif not np.isscalar(low):\n            shape = low.shape  # type: ignore\n        elif not np.isscalar(high):\n            shape = high.shape  # type: ignore\n        else:\n            raise ValueError(\n                \"shape must be provided or inferred from the shapes of low or high\"\n            )\n        assert isinstance(shape, tuple)\n\n        # Capture the boundedness information before replacing np.inf with get_inf\n        _low = np.full(shape, low, dtype=float) if np.isscalar(low) else low\n        self.bounded_below = -np.inf < _low\n        _high = np.full(shape, high, dtype=float) if np.isscalar(high) else high\n        self.bounded_above = np.inf > _high\n\n        low = _broadcast(low, dtype, shape, inf_sign=\"-\")  # type: ignore\n        high = _broadcast(high, dtype, shape, inf_sign=\"+\")  # type: ignore\n\n        assert isinstance(low, np.ndarray)\n        assert low.shape == shape, \"low.shape doesn't match provided shape\"\n        assert isinstance(high, np.ndarray)\n        assert high.shape == shape, \"high.shape doesn't match provided shape\"\n\n        self._shape: Tuple[int, ...] = shape\n\n        low_precision = get_precision(low.dtype)\n        high_precision = get_precision(high.dtype)\n        dtype_precision = get_precision(self.dtype)\n        if min(low_precision, high_precision) > dtype_precision:  # type: ignore\n            logger.warn(f\"Box bound precision lowered by casting to {self.dtype}\")\n        self.low = low.astype(self.dtype)\n        self.high = high.astype(self.dtype)\n\n        self.low_repr = _short_repr(self.low)\n        self.high_repr = _short_repr(self.high)\n\n        super().__init__(self.shape, self.dtype, seed)\n\n    @property\n    def shape(self) -> Tuple[int, ...]:\n        \"\"\"Has stricter type than gym.Space - never None.\"\"\"\n        return self._shape\n\n    def is_bounded(self, manner: str = \"both\") -> bool:\n        below = bool(np.all(self.bounded_below))\n        above = bool(np.all(self.bounded_above))\n        if manner == \"both\":\n            return below and above\n        elif manner == \"below\":\n            return below\n        elif manner == \"above\":\n            return above\n        else:\n            raise ValueError(\"manner is not in {'below', 'above', 'both'}\")\n\n    def sample(self) -> np.ndarray:\n        \"\"\"\n        Generates a single random sample inside of the Box.\n\n        In creating a sample of the box, each coordinate is sampled according to\n        the form of the interval:\n\n        * [a, b] : uniform distribution\n        * [a, oo) : shifted exponential distribution\n        * (-oo, b] : shifted negative exponential distribution\n        * (-oo, oo) : normal distribution\n        \"\"\"\n        high = self.high if self.dtype.kind == \"f\" else self.high.astype(\"int64\") + 1\n        sample = np.empty(self.shape)\n\n        # Masking arrays which classify the coordinates according to interval\n        # type\n        unbounded = ~self.bounded_below & ~self.bounded_above\n        upp_bounded = ~self.bounded_below & self.bounded_above\n        low_bounded = self.bounded_below & ~self.bounded_above\n        bounded = self.bounded_below & self.bounded_above\n\n        # Vectorized sampling by interval type\n        sample[unbounded] = self.np_random.normal(size=unbounded[unbounded].shape)\n\n        sample[low_bounded] = (\n            self.np_random.exponential(size=low_bounded[low_bounded].shape)\n            + self.low[low_bounded]\n        )\n\n        sample[upp_bounded] = (\n            -self.np_random.exponential(size=upp_bounded[upp_bounded].shape)\n            + self.high[upp_bounded]\n        )\n\n        sample[bounded] = self.np_random.uniform(\n            low=self.low[bounded], high=high[bounded], size=bounded[bounded].shape\n        )\n        if self.dtype.kind == \"i\":\n            sample = np.floor(sample)\n\n        return sample.astype(self.dtype)\n\n    def contains(self, x) -> bool:\n        if not isinstance(x, np.ndarray):\n            logger.warn(\"Casting input x to numpy array.\")\n            x = np.asarray(x, dtype=self.dtype)\n\n        return bool(\n            np.can_cast(x.dtype, self.dtype)\n            and x.shape == self.shape\n            and np.all(x >= self.low)\n            and np.all(x <= self.high)\n        )\n\n    def to_jsonable(self, sample_n):\n        return np.array(sample_n).tolist()\n\n    def from_jsonable(self, sample_n: Sequence[SupportsFloat]) -> list[np.ndarray]:\n        return [np.asarray(sample) for sample in sample_n]\n\n    def __repr__(self) -> str:\n        return f\"Box({self.low_repr}, {self.high_repr}, {self.shape}, {self.dtype})\"\n\n    def __eq__(self, other) -> bool:\n        return (\n            isinstance(other, Box)\n            and (self.shape == other.shape)\n            and np.allclose(self.low, other.low)\n            and np.allclose(self.high, other.high)\n        )\n\n\ndef get_inf(dtype, sign: str) -> SupportsFloat:\n    \"\"\"Returns an infinite that doesn't break things.\n    `dtype` must be an `np.dtype`\n    `bound` must be either `min` or `max`\n    \"\"\"\n    if np.dtype(dtype).kind == \"f\":\n        if sign == \"+\":\n            return np.inf\n        elif sign == \"-\":\n            return -np.inf\n        else:\n            raise TypeError(f\"Unknown sign {sign}, use either '+' or '-'\")\n    elif np.dtype(dtype).kind == \"i\":\n        if sign == \"+\":\n            return np.iinfo(dtype).max - 2\n        elif sign == \"-\":\n            return np.iinfo(dtype).min + 2\n        else:\n            raise TypeError(f\"Unknown sign {sign}, use either '+' or '-'\")\n    else:\n        raise ValueError(f\"Unknown dtype {dtype} for infinite bounds\")\n\n\ndef get_precision(dtype) -> SupportsFloat:\n    if np.issubdtype(dtype, np.floating):\n        return np.finfo(dtype).precision\n    else:\n        return np.inf\n\n\ndef _broadcast(\n    value: Union[SupportsFloat, np.ndarray],\n    dtype,\n    shape: tuple[int, ...],\n    inf_sign: str,\n) -> np.ndarray:\n    \"\"\"handle infinite bounds and broadcast at the same time if needed\"\"\"\n    if np.isscalar(value):\n        value = get_inf(dtype, inf_sign) if np.isinf(value) else value  # type: ignore\n        value = np.full(shape, value, dtype=dtype)\n    else:\n        assert isinstance(value, np.ndarray)\n        if np.any(np.isinf(value)):\n            # create new array with dtype, but maintain old one to preserve np.inf\n            temp = value.astype(dtype)\n            temp[np.isinf(value)] = get_inf(dtype, inf_sign)\n            value = temp\n    return value\n", "path": "gym/spaces/box.py"}]}