We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`GridSampler` does not stop the optimization when NaN in the `search_space`
### Expected behavior

`GridSampler` should stop the optimization when all grids are evaluated.

### Environment

- Optuna version: 3.0.0b1.dev
- Python version: 3.8.6
- OS: macOS-10.16-x86_64-i386-64bit
- (Optional) Other libraries and their versions:


### Error messages, stack traces, or logs

```shell
See steps to reproduce.
```


### Steps to reproduce

In the following code, `optimize` should stop with 2 trials, but it infinitely creates trials.

```python
import optuna
sampler = optuna.samplers.GridSampler({"x": [0, float("nan")]})
study = optuna.create_study(sampler=sampler)
study.optimize(lambda _: 0)
```

### Additional context (optional)

The bug is coded here.

https://github.com/optuna/optuna/blob/c23e17c70bd77489e223f3d4c322d86ec5a6ec66/optuna/samplers/_grid.py#L270

If the `param_value` is NaN, this condition is wrong.

```python
float("nan") != float("nan")  # True
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `optuna/samplers/_grid.py`
Content:
```
1 import collections
2 import itertools
3 from typing import Any
4 from typing import Dict
5 from typing import List
6 from typing import Mapping
7 from typing import Optional
8 from typing import Sequence
9 from typing import Union
10 import warnings
11 
12 import numpy as np
13 
14 from optuna.distributions import BaseDistribution
15 from optuna.logging import get_logger
16 from optuna.samplers import BaseSampler
17 from optuna.study import Study
18 from optuna.trial import FrozenTrial
19 from optuna.trial import TrialState
20 
21 
22 GridValueType = Union[str, float, int, bool, None]
23 
24 
25 _logger = get_logger(__name__)
26 
27 
28 class GridSampler(BaseSampler):
29     """Sampler using grid search.
30 
31     With :class:`~optuna.samplers.GridSampler`, the trials suggest all combinations of parameters
32     in the given search space during the study.
33 
34     Example:
35 
36         .. testcode::
37 
38             import optuna
39 
40 
41             def objective(trial):
42                 x = trial.suggest_float("x", -100, 100)
43                 y = trial.suggest_int("y", -100, 100)
44                 return x**2 + y**2
45 
46 
47             search_space = {"x": [-50, 0, 50], "y": [-99, 0, 99]}
48             study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))
49             study.optimize(objective)
50 
51     Note:
52 
53         :class:`~optuna.samplers.GridSampler` automatically stops the optimization if all
54         combinations in the passed ``search_space`` have already been evaluated, internally
55         invoking the :func:`~optuna.study.Study.stop` method.
56 
57     Note:
58 
59         :class:`~optuna.samplers.GridSampler` does not take care of a parameter's quantization
60         specified by discrete suggest methods but just samples one of values specified in the
61         search space. E.g., in the following code snippet, either of ``-0.5`` or ``0.5`` is
62         sampled as ``x`` instead of an integer point.
63 
64         .. testcode::
65 
66             import optuna
67 
68 
69             def objective(trial):
70                 # The following suggest method specifies integer points between -5 and 5.
71                 x = trial.suggest_float("x", -5, 5, step=1)
72                 return x**2
73 
74 
75             # Non-int points are specified in the grid.
76             search_space = {"x": [-0.5, 0.5]}
77             study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))
78             study.optimize(objective, n_trials=2)
79 
80     Note:
81         A parameter configuration in the grid is not considered finished until its trial is
82         finished. Therefore, during distributed optimization where trials run concurrently,
83         different workers will occasionally suggest the same parameter configuration.
84         The total number of actual trials may therefore exceed the size of the grid.
85 
86     Note:
87         All parameters must be specified when using :class:`~optuna.samplers.GridSampler` with
88         :meth:`~optuna.study.Study.enqueue_trial`.
89 
90     Args:
91         search_space:
92             A dictionary whose key and value are a parameter name and the corresponding candidates
93             of values, respectively.
94         seed:
95             A seed to fix the order of trials as the grid is randomly shuffled. Please note that
96             it is not recommended using this option in distributed optimization settings since
97             this option cannot ensure the order of trials and may increase the number of duplicate
98             suggestions during distributed optimization.
99     """
100 
101     def __init__(
102         self, search_space: Mapping[str, Sequence[GridValueType]], seed: Optional[int] = None
103     ) -> None:
104 
105         for param_name, param_values in search_space.items():
106             for value in param_values:
107                 self._check_value(param_name, value)
108 
109         self._search_space = collections.OrderedDict()
110         for param_name, param_values in sorted(search_space.items()):
111             self._search_space[param_name] = param_values
112 
113         self._all_grids = list(itertools.product(*self._search_space.values()))
114         self._param_names = sorted(search_space.keys())
115         self._n_min_trials = len(self._all_grids)
116         self._rng = np.random.RandomState(seed)
117 
118     def reseed_rng(self) -> None:
119 
120         self._rng.seed()
121 
122     def infer_relative_search_space(
123         self, study: Study, trial: FrozenTrial
124     ) -> Dict[str, BaseDistribution]:
125 
126         return {}
127 
128     def sample_relative(
129         self, study: Study, trial: FrozenTrial, search_space: Dict[str, BaseDistribution]
130     ) -> Dict[str, Any]:
131         # Instead of returning param values, GridSampler puts the target grid id as a system attr,
132         # and the values are returned from `sample_independent`. This is because the distribution
133         # object is hard to get at the beginning of trial, while we need the access to the object
134         # to validate the sampled value.
135 
136         # When the trial is created by RetryFailedTrialCallback or enqueue_trial, we should not
137         # assign a new grid_id.
138         if "grid_id" in trial.system_attrs or "fixed_params" in trial.system_attrs:
139             return {}
140 
141         target_grids = self._get_unvisited_grid_ids(study)
142 
143         if len(target_grids) == 0:
144             # This case may occur with distributed optimization or trial queue. If there is no
145             # target grid, `GridSampler` evaluates a visited, duplicated point with the current
146             # trial. After that, the optimization stops.
147 
148             _logger.warning(
149                 "`GridSampler` is re-evaluating a configuration because the grid has been "
150                 "exhausted. This may happen due to a timing issue during distributed optimization "
151                 "or when re-running optimizations on already finished studies."
152             )
153 
154             # One of all grids is randomly picked up in this case.
155             target_grids = list(range(len(self._all_grids)))
156 
157         # In distributed optimization, multiple workers may simultaneously pick up the same grid.
158         # To make the conflict less frequent, the grid is chosen randomly.
159         grid_id = self._rng.choice(target_grids)
160 
161         study._storage.set_trial_system_attr(trial._trial_id, "search_space", self._search_space)
162         study._storage.set_trial_system_attr(trial._trial_id, "grid_id", grid_id)
163 
164         return {}
165 
166     def sample_independent(
167         self,
168         study: Study,
169         trial: FrozenTrial,
170         param_name: str,
171         param_distribution: BaseDistribution,
172     ) -> Any:
173 
174         if "grid_id" not in trial.system_attrs:
175             message = "All parameters must be specified when using GridSampler with enqueue_trial."
176             raise ValueError(message)
177 
178         if param_name not in self._search_space:
179             message = "The parameter name, {}, is not found in the given grid.".format(param_name)
180             raise ValueError(message)
181 
182         # TODO(c-bata): Reduce the number of duplicated evaluations on multiple workers.
183         # Current selection logic may evaluate the same parameters multiple times.
184         # See https://gist.github.com/c-bata/f759f64becb24eea2040f4b2e3afce8f for details.
185         grid_id = trial.system_attrs["grid_id"]
186         param_value = self._all_grids[grid_id][self._param_names.index(param_name)]
187         contains = param_distribution._contains(param_distribution.to_internal_repr(param_value))
188         if not contains:
189             warnings.warn(
190                 f"The value `{param_value}` is out of range of the parameter `{param_name}`. "
191                 f"The value will be used but the actual distribution is: `{param_distribution}`."
192             )
193 
194         return param_value
195 
196     def after_trial(
197         self,
198         study: Study,
199         trial: FrozenTrial,
200         state: TrialState,
201         values: Optional[Sequence[float]],
202     ) -> None:
203         target_grids = self._get_unvisited_grid_ids(study)
204 
205         if len(target_grids) == 0:
206             study.stop()
207         elif len(target_grids) == 1:
208             grid_id = study._storage.get_trial_system_attrs(trial._trial_id)["grid_id"]
209             if grid_id == target_grids[0]:
210                 study.stop()
211 
212     @staticmethod
213     def _check_value(param_name: str, param_value: Any) -> None:
214 
215         if param_value is None or isinstance(param_value, (str, int, float, bool)):
216             return
217 
218         message = (
219             "{} contains a value with the type of {}, which is not supported by "
220             "`GridSampler`. Please make sure a value is `str`, `int`, `float`, `bool`"
221             " or `None` for persistent storage.".format(param_name, type(param_value))
222         )
223         warnings.warn(message)
224 
225     def _get_unvisited_grid_ids(self, study: Study) -> List[int]:
226 
227         # List up unvisited grids based on already finished ones.
228         visited_grids = []
229         running_grids = []
230 
231         # We directly query the storage to get trials here instead of `study.get_trials`,
232         # since some pruners such as `HyperbandPruner` use the study transformed
233         # to filter trials. See https://github.com/optuna/optuna/issues/2327 for details.
234         trials = study._storage.get_all_trials(study._study_id, deepcopy=False)
235 
236         for t in trials:
237             if "grid_id" in t.system_attrs and self._same_search_space(
238                 t.system_attrs["search_space"]
239             ):
240                 if t.state.is_finished():
241                     visited_grids.append(t.system_attrs["grid_id"])
242                 elif t.state == TrialState.RUNNING:
243                     running_grids.append(t.system_attrs["grid_id"])
244 
245         unvisited_grids = set(range(self._n_min_trials)) - set(visited_grids) - set(running_grids)
246 
247         # If evaluations for all grids have been started, return grids that have not yet finished
248         # because all grids should be evaluated before stopping the optimization.
249         if len(unvisited_grids) == 0:
250             unvisited_grids = set(range(self._n_min_trials)) - set(visited_grids)
251 
252         return list(unvisited_grids)
253 
254     def _same_search_space(self, search_space: Mapping[str, Sequence[GridValueType]]) -> bool:
255 
256         if set(search_space.keys()) != set(self._search_space.keys()):
257             return False
258 
259         for param_name in search_space.keys():
260             if len(search_space[param_name]) != len(self._search_space[param_name]):
261                 return False
262 
263             for i, param_value in enumerate(search_space[param_name]):
264                 if param_value != self._search_space[param_name][i]:
265                     return False
266 
267         return True
268
```
--- 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/optuna/samplers/_grid.py b/optuna/samplers/_grid.py
--- a/optuna/samplers/_grid.py
+++ b/optuna/samplers/_grid.py
@@ -1,5 +1,6 @@
 import collections
 import itertools
+from numbers import Real
 from typing import Any
 from typing import Dict
 from typing import List
@@ -251,6 +252,12 @@
 
         return list(unvisited_grids)
 
+    @staticmethod
+    def _grid_value_equal(value1: GridValueType, value2: GridValueType) -> bool:
+        value1_is_nan = isinstance(value1, Real) and np.isnan(float(value1))
+        value2_is_nan = isinstance(value2, Real) and np.isnan(float(value2))
+        return (value1 == value2) or (value1_is_nan and value2_is_nan)
+
     def _same_search_space(self, search_space: Mapping[str, Sequence[GridValueType]]) -> bool:
 
         if set(search_space.keys()) != set(self._search_space.keys()):
@@ -261,7 +268,7 @@
                 return False
 
             for i, param_value in enumerate(search_space[param_name]):
-                if param_value != self._search_space[param_name][i]:
+                if not self._grid_value_equal(param_value, self._search_space[param_name][i]):
                     return False
 
         return True

{"golden_diff": "diff --git a/optuna/samplers/_grid.py b/optuna/samplers/_grid.py\n--- a/optuna/samplers/_grid.py\n+++ b/optuna/samplers/_grid.py\n@@ -1,5 +1,6 @@\n import collections\n import itertools\n+from numbers import Real\n from typing import Any\n from typing import Dict\n from typing import List\n@@ -251,6 +252,12 @@\n \n         return list(unvisited_grids)\n \n+    @staticmethod\n+    def _grid_value_equal(value1: GridValueType, value2: GridValueType) -> bool:\n+        value1_is_nan = isinstance(value1, Real) and np.isnan(float(value1))\n+        value2_is_nan = isinstance(value2, Real) and np.isnan(float(value2))\n+        return (value1 == value2) or (value1_is_nan and value2_is_nan)\n+\n     def _same_search_space(self, search_space: Mapping[str, Sequence[GridValueType]]) -> bool:\n \n         if set(search_space.keys()) != set(self._search_space.keys()):\n@@ -261,7 +268,7 @@\n                 return False\n \n             for i, param_value in enumerate(search_space[param_name]):\n-                if param_value != self._search_space[param_name][i]:\n+                if not self._grid_value_equal(param_value, self._search_space[param_name][i]):\n                     return False\n \n         return True\n", "issue": "`GridSampler` does not stop the optimization when NaN in the `search_space`\n### Expected behavior\n\n`GridSampler` should stop the optimization when all grids are evaluated.\n\n### Environment\n\n- Optuna version: 3.0.0b1.dev\r\n- Python version: 3.8.6\r\n- OS: macOS-10.16-x86_64-i386-64bit\r\n- (Optional) Other libraries and their versions:\r\n\n\n### Error messages, stack traces, or logs\n\n```shell\nSee steps to reproduce.\n```\n\n\n### Steps to reproduce\n\nIn the following code, `optimize` should stop with 2 trials, but it infinitely creates trials.\r\n\r\n```python\r\nimport optuna\r\nsampler = optuna.samplers.GridSampler({\"x\": [0, float(\"nan\")]})\r\nstudy = optuna.create_study(sampler=sampler)\r\nstudy.optimize(lambda _: 0)\r\n```\n\n### Additional context (optional)\n\nThe bug is coded here.\r\n\r\nhttps://github.com/optuna/optuna/blob/c23e17c70bd77489e223f3d4c322d86ec5a6ec66/optuna/samplers/_grid.py#L270\r\n\r\nIf the `param_value` is NaN, this condition is wrong.\r\n\r\n```python\r\nfloat(\"nan\") != float(\"nan\")  # True\r\n```\n", "before_files": [{"content": "import collections\nimport itertools\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Mapping\nfrom typing import Optional\nfrom typing import Sequence\nfrom typing import Union\nimport warnings\n\nimport numpy as np\n\nfrom optuna.distributions import BaseDistribution\nfrom optuna.logging import get_logger\nfrom optuna.samplers import BaseSampler\nfrom optuna.study import Study\nfrom optuna.trial import FrozenTrial\nfrom optuna.trial import TrialState\n\n\nGridValueType = Union[str, float, int, bool, None]\n\n\n_logger = get_logger(__name__)\n\n\nclass GridSampler(BaseSampler):\n    \"\"\"Sampler using grid search.\n\n    With :class:`~optuna.samplers.GridSampler`, the trials suggest all combinations of parameters\n    in the given search space during the study.\n\n    Example:\n\n        .. testcode::\n\n            import optuna\n\n\n            def objective(trial):\n                x = trial.suggest_float(\"x\", -100, 100)\n                y = trial.suggest_int(\"y\", -100, 100)\n                return x**2 + y**2\n\n\n            search_space = {\"x\": [-50, 0, 50], \"y\": [-99, 0, 99]}\n            study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))\n            study.optimize(objective)\n\n    Note:\n\n        :class:`~optuna.samplers.GridSampler` automatically stops the optimization if all\n        combinations in the passed ``search_space`` have already been evaluated, internally\n        invoking the :func:`~optuna.study.Study.stop` method.\n\n    Note:\n\n        :class:`~optuna.samplers.GridSampler` does not take care of a parameter's quantization\n        specified by discrete suggest methods but just samples one of values specified in the\n        search space. E.g., in the following code snippet, either of ``-0.5`` or ``0.5`` is\n        sampled as ``x`` instead of an integer point.\n\n        .. testcode::\n\n            import optuna\n\n\n            def objective(trial):\n                # The following suggest method specifies integer points between -5 and 5.\n                x = trial.suggest_float(\"x\", -5, 5, step=1)\n                return x**2\n\n\n            # Non-int points are specified in the grid.\n            search_space = {\"x\": [-0.5, 0.5]}\n            study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))\n            study.optimize(objective, n_trials=2)\n\n    Note:\n        A parameter configuration in the grid is not considered finished until its trial is\n        finished. Therefore, during distributed optimization where trials run concurrently,\n        different workers will occasionally suggest the same parameter configuration.\n        The total number of actual trials may therefore exceed the size of the grid.\n\n    Note:\n        All parameters must be specified when using :class:`~optuna.samplers.GridSampler` with\n        :meth:`~optuna.study.Study.enqueue_trial`.\n\n    Args:\n        search_space:\n            A dictionary whose key and value are a parameter name and the corresponding candidates\n            of values, respectively.\n        seed:\n            A seed to fix the order of trials as the grid is randomly shuffled. Please note that\n            it is not recommended using this option in distributed optimization settings since\n            this option cannot ensure the order of trials and may increase the number of duplicate\n            suggestions during distributed optimization.\n    \"\"\"\n\n    def __init__(\n        self, search_space: Mapping[str, Sequence[GridValueType]], seed: Optional[int] = None\n    ) -> None:\n\n        for param_name, param_values in search_space.items():\n            for value in param_values:\n                self._check_value(param_name, value)\n\n        self._search_space = collections.OrderedDict()\n        for param_name, param_values in sorted(search_space.items()):\n            self._search_space[param_name] = param_values\n\n        self._all_grids = list(itertools.product(*self._search_space.values()))\n        self._param_names = sorted(search_space.keys())\n        self._n_min_trials = len(self._all_grids)\n        self._rng = np.random.RandomState(seed)\n\n    def reseed_rng(self) -> None:\n\n        self._rng.seed()\n\n    def infer_relative_search_space(\n        self, study: Study, trial: FrozenTrial\n    ) -> Dict[str, BaseDistribution]:\n\n        return {}\n\n    def sample_relative(\n        self, study: Study, trial: FrozenTrial, search_space: Dict[str, BaseDistribution]\n    ) -> Dict[str, Any]:\n        # Instead of returning param values, GridSampler puts the target grid id as a system attr,\n        # and the values are returned from `sample_independent`. This is because the distribution\n        # object is hard to get at the beginning of trial, while we need the access to the object\n        # to validate the sampled value.\n\n        # When the trial is created by RetryFailedTrialCallback or enqueue_trial, we should not\n        # assign a new grid_id.\n        if \"grid_id\" in trial.system_attrs or \"fixed_params\" in trial.system_attrs:\n            return {}\n\n        target_grids = self._get_unvisited_grid_ids(study)\n\n        if len(target_grids) == 0:\n            # This case may occur with distributed optimization or trial queue. If there is no\n            # target grid, `GridSampler` evaluates a visited, duplicated point with the current\n            # trial. After that, the optimization stops.\n\n            _logger.warning(\n                \"`GridSampler` is re-evaluating a configuration because the grid has been \"\n                \"exhausted. This may happen due to a timing issue during distributed optimization \"\n                \"or when re-running optimizations on already finished studies.\"\n            )\n\n            # One of all grids is randomly picked up in this case.\n            target_grids = list(range(len(self._all_grids)))\n\n        # In distributed optimization, multiple workers may simultaneously pick up the same grid.\n        # To make the conflict less frequent, the grid is chosen randomly.\n        grid_id = self._rng.choice(target_grids)\n\n        study._storage.set_trial_system_attr(trial._trial_id, \"search_space\", self._search_space)\n        study._storage.set_trial_system_attr(trial._trial_id, \"grid_id\", grid_id)\n\n        return {}\n\n    def sample_independent(\n        self,\n        study: Study,\n        trial: FrozenTrial,\n        param_name: str,\n        param_distribution: BaseDistribution,\n    ) -> Any:\n\n        if \"grid_id\" not in trial.system_attrs:\n            message = \"All parameters must be specified when using GridSampler with enqueue_trial.\"\n            raise ValueError(message)\n\n        if param_name not in self._search_space:\n            message = \"The parameter name, {}, is not found in the given grid.\".format(param_name)\n            raise ValueError(message)\n\n        # TODO(c-bata): Reduce the number of duplicated evaluations on multiple workers.\n        # Current selection logic may evaluate the same parameters multiple times.\n        # See https://gist.github.com/c-bata/f759f64becb24eea2040f4b2e3afce8f for details.\n        grid_id = trial.system_attrs[\"grid_id\"]\n        param_value = self._all_grids[grid_id][self._param_names.index(param_name)]\n        contains = param_distribution._contains(param_distribution.to_internal_repr(param_value))\n        if not contains:\n            warnings.warn(\n                f\"The value `{param_value}` is out of range of the parameter `{param_name}`. \"\n                f\"The value will be used but the actual distribution is: `{param_distribution}`.\"\n            )\n\n        return param_value\n\n    def after_trial(\n        self,\n        study: Study,\n        trial: FrozenTrial,\n        state: TrialState,\n        values: Optional[Sequence[float]],\n    ) -> None:\n        target_grids = self._get_unvisited_grid_ids(study)\n\n        if len(target_grids) == 0:\n            study.stop()\n        elif len(target_grids) == 1:\n            grid_id = study._storage.get_trial_system_attrs(trial._trial_id)[\"grid_id\"]\n            if grid_id == target_grids[0]:\n                study.stop()\n\n    @staticmethod\n    def _check_value(param_name: str, param_value: Any) -> None:\n\n        if param_value is None or isinstance(param_value, (str, int, float, bool)):\n            return\n\n        message = (\n            \"{} contains a value with the type of {}, which is not supported by \"\n            \"`GridSampler`. Please make sure a value is `str`, `int`, `float`, `bool`\"\n            \" or `None` for persistent storage.\".format(param_name, type(param_value))\n        )\n        warnings.warn(message)\n\n    def _get_unvisited_grid_ids(self, study: Study) -> List[int]:\n\n        # List up unvisited grids based on already finished ones.\n        visited_grids = []\n        running_grids = []\n\n        # We directly query the storage to get trials here instead of `study.get_trials`,\n        # since some pruners such as `HyperbandPruner` use the study transformed\n        # to filter trials. See https://github.com/optuna/optuna/issues/2327 for details.\n        trials = study._storage.get_all_trials(study._study_id, deepcopy=False)\n\n        for t in trials:\n            if \"grid_id\" in t.system_attrs and self._same_search_space(\n                t.system_attrs[\"search_space\"]\n            ):\n                if t.state.is_finished():\n                    visited_grids.append(t.system_attrs[\"grid_id\"])\n                elif t.state == TrialState.RUNNING:\n                    running_grids.append(t.system_attrs[\"grid_id\"])\n\n        unvisited_grids = set(range(self._n_min_trials)) - set(visited_grids) - set(running_grids)\n\n        # If evaluations for all grids have been started, return grids that have not yet finished\n        # because all grids should be evaluated before stopping the optimization.\n        if len(unvisited_grids) == 0:\n            unvisited_grids = set(range(self._n_min_trials)) - set(visited_grids)\n\n        return list(unvisited_grids)\n\n    def _same_search_space(self, search_space: Mapping[str, Sequence[GridValueType]]) -> bool:\n\n        if set(search_space.keys()) != set(self._search_space.keys()):\n            return False\n\n        for param_name in search_space.keys():\n            if len(search_space[param_name]) != len(self._search_space[param_name]):\n                return False\n\n            for i, param_value in enumerate(search_space[param_name]):\n                if param_value != self._search_space[param_name][i]:\n                    return False\n\n        return True\n", "path": "optuna/samplers/_grid.py"}], "after_files": [{"content": "import collections\nimport itertools\nfrom numbers import Real\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Mapping\nfrom typing import Optional\nfrom typing import Sequence\nfrom typing import Union\nimport warnings\n\nimport numpy as np\n\nfrom optuna.distributions import BaseDistribution\nfrom optuna.logging import get_logger\nfrom optuna.samplers import BaseSampler\nfrom optuna.study import Study\nfrom optuna.trial import FrozenTrial\nfrom optuna.trial import TrialState\n\n\nGridValueType = Union[str, float, int, bool, None]\n\n\n_logger = get_logger(__name__)\n\n\nclass GridSampler(BaseSampler):\n    \"\"\"Sampler using grid search.\n\n    With :class:`~optuna.samplers.GridSampler`, the trials suggest all combinations of parameters\n    in the given search space during the study.\n\n    Example:\n\n        .. testcode::\n\n            import optuna\n\n\n            def objective(trial):\n                x = trial.suggest_float(\"x\", -100, 100)\n                y = trial.suggest_int(\"y\", -100, 100)\n                return x**2 + y**2\n\n\n            search_space = {\"x\": [-50, 0, 50], \"y\": [-99, 0, 99]}\n            study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))\n            study.optimize(objective)\n\n    Note:\n\n        :class:`~optuna.samplers.GridSampler` automatically stops the optimization if all\n        combinations in the passed ``search_space`` have already been evaluated, internally\n        invoking the :func:`~optuna.study.Study.stop` method.\n\n    Note:\n\n        :class:`~optuna.samplers.GridSampler` does not take care of a parameter's quantization\n        specified by discrete suggest methods but just samples one of values specified in the\n        search space. E.g., in the following code snippet, either of ``-0.5`` or ``0.5`` is\n        sampled as ``x`` instead of an integer point.\n\n        .. testcode::\n\n            import optuna\n\n\n            def objective(trial):\n                # The following suggest method specifies integer points between -5 and 5.\n                x = trial.suggest_float(\"x\", -5, 5, step=1)\n                return x**2\n\n\n            # Non-int points are specified in the grid.\n            search_space = {\"x\": [-0.5, 0.5]}\n            study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))\n            study.optimize(objective, n_trials=2)\n\n    Note:\n        A parameter configuration in the grid is not considered finished until its trial is\n        finished. Therefore, during distributed optimization where trials run concurrently,\n        different workers will occasionally suggest the same parameter configuration.\n        The total number of actual trials may therefore exceed the size of the grid.\n\n    Note:\n        All parameters must be specified when using :class:`~optuna.samplers.GridSampler` with\n        :meth:`~optuna.study.Study.enqueue_trial`.\n\n    Args:\n        search_space:\n            A dictionary whose key and value are a parameter name and the corresponding candidates\n            of values, respectively.\n        seed:\n            A seed to fix the order of trials as the grid is randomly shuffled. Please note that\n            it is not recommended using this option in distributed optimization settings since\n            this option cannot ensure the order of trials and may increase the number of duplicate\n            suggestions during distributed optimization.\n    \"\"\"\n\n    def __init__(\n        self, search_space: Mapping[str, Sequence[GridValueType]], seed: Optional[int] = None\n    ) -> None:\n\n        for param_name, param_values in search_space.items():\n            for value in param_values:\n                self._check_value(param_name, value)\n\n        self._search_space = collections.OrderedDict()\n        for param_name, param_values in sorted(search_space.items()):\n            self._search_space[param_name] = param_values\n\n        self._all_grids = list(itertools.product(*self._search_space.values()))\n        self._param_names = sorted(search_space.keys())\n        self._n_min_trials = len(self._all_grids)\n        self._rng = np.random.RandomState(seed)\n\n    def reseed_rng(self) -> None:\n\n        self._rng.seed()\n\n    def infer_relative_search_space(\n        self, study: Study, trial: FrozenTrial\n    ) -> Dict[str, BaseDistribution]:\n\n        return {}\n\n    def sample_relative(\n        self, study: Study, trial: FrozenTrial, search_space: Dict[str, BaseDistribution]\n    ) -> Dict[str, Any]:\n        # Instead of returning param values, GridSampler puts the target grid id as a system attr,\n        # and the values are returned from `sample_independent`. This is because the distribution\n        # object is hard to get at the beginning of trial, while we need the access to the object\n        # to validate the sampled value.\n\n        # When the trial is created by RetryFailedTrialCallback or enqueue_trial, we should not\n        # assign a new grid_id.\n        if \"grid_id\" in trial.system_attrs or \"fixed_params\" in trial.system_attrs:\n            return {}\n\n        target_grids = self._get_unvisited_grid_ids(study)\n\n        if len(target_grids) == 0:\n            # This case may occur with distributed optimization or trial queue. If there is no\n            # target grid, `GridSampler` evaluates a visited, duplicated point with the current\n            # trial. After that, the optimization stops.\n\n            _logger.warning(\n                \"`GridSampler` is re-evaluating a configuration because the grid has been \"\n                \"exhausted. This may happen due to a timing issue during distributed optimization \"\n                \"or when re-running optimizations on already finished studies.\"\n            )\n\n            # One of all grids is randomly picked up in this case.\n            target_grids = list(range(len(self._all_grids)))\n\n        # In distributed optimization, multiple workers may simultaneously pick up the same grid.\n        # To make the conflict less frequent, the grid is chosen randomly.\n        grid_id = self._rng.choice(target_grids)\n\n        study._storage.set_trial_system_attr(trial._trial_id, \"search_space\", self._search_space)\n        study._storage.set_trial_system_attr(trial._trial_id, \"grid_id\", grid_id)\n\n        return {}\n\n    def sample_independent(\n        self,\n        study: Study,\n        trial: FrozenTrial,\n        param_name: str,\n        param_distribution: BaseDistribution,\n    ) -> Any:\n\n        if \"grid_id\" not in trial.system_attrs:\n            message = \"All parameters must be specified when using GridSampler with enqueue_trial.\"\n            raise ValueError(message)\n\n        if param_name not in self._search_space:\n            message = \"The parameter name, {}, is not found in the given grid.\".format(param_name)\n            raise ValueError(message)\n\n        # TODO(c-bata): Reduce the number of duplicated evaluations on multiple workers.\n        # Current selection logic may evaluate the same parameters multiple times.\n        # See https://gist.github.com/c-bata/f759f64becb24eea2040f4b2e3afce8f for details.\n        grid_id = trial.system_attrs[\"grid_id\"]\n        param_value = self._all_grids[grid_id][self._param_names.index(param_name)]\n        contains = param_distribution._contains(param_distribution.to_internal_repr(param_value))\n        if not contains:\n            warnings.warn(\n                f\"The value `{param_value}` is out of range of the parameter `{param_name}`. \"\n                f\"The value will be used but the actual distribution is: `{param_distribution}`.\"\n            )\n\n        return param_value\n\n    def after_trial(\n        self,\n        study: Study,\n        trial: FrozenTrial,\n        state: TrialState,\n        values: Optional[Sequence[float]],\n    ) -> None:\n        target_grids = self._get_unvisited_grid_ids(study)\n\n        if len(target_grids) == 0:\n            study.stop()\n        elif len(target_grids) == 1:\n            grid_id = study._storage.get_trial_system_attrs(trial._trial_id)[\"grid_id\"]\n            if grid_id == target_grids[0]:\n                study.stop()\n\n    @staticmethod\n    def _check_value(param_name: str, param_value: Any) -> None:\n\n        if param_value is None or isinstance(param_value, (str, int, float, bool)):\n            return\n\n        message = (\n            \"{} contains a value with the type of {}, which is not supported by \"\n            \"`GridSampler`. Please make sure a value is `str`, `int`, `float`, `bool`\"\n            \" or `None` for persistent storage.\".format(param_name, type(param_value))\n        )\n        warnings.warn(message)\n\n    def _get_unvisited_grid_ids(self, study: Study) -> List[int]:\n\n        # List up unvisited grids based on already finished ones.\n        visited_grids = []\n        running_grids = []\n\n        # We directly query the storage to get trials here instead of `study.get_trials`,\n        # since some pruners such as `HyperbandPruner` use the study transformed\n        # to filter trials. See https://github.com/optuna/optuna/issues/2327 for details.\n        trials = study._storage.get_all_trials(study._study_id, deepcopy=False)\n\n        for t in trials:\n            if \"grid_id\" in t.system_attrs and self._same_search_space(\n                t.system_attrs[\"search_space\"]\n            ):\n                if t.state.is_finished():\n                    visited_grids.append(t.system_attrs[\"grid_id\"])\n                elif t.state == TrialState.RUNNING:\n                    running_grids.append(t.system_attrs[\"grid_id\"])\n\n        unvisited_grids = set(range(self._n_min_trials)) - set(visited_grids) - set(running_grids)\n\n        # If evaluations for all grids have been started, return grids that have not yet finished\n        # because all grids should be evaluated before stopping the optimization.\n        if len(unvisited_grids) == 0:\n            unvisited_grids = set(range(self._n_min_trials)) - set(visited_grids)\n\n        return list(unvisited_grids)\n\n    @staticmethod\n    def _grid_value_equal(value1: GridValueType, value2: GridValueType) -> bool:\n        value1_is_nan = isinstance(value1, Real) and np.isnan(float(value1))\n        value2_is_nan = isinstance(value2, Real) and np.isnan(float(value2))\n        return (value1 == value2) or (value1_is_nan and value2_is_nan)\n\n    def _same_search_space(self, search_space: Mapping[str, Sequence[GridValueType]]) -> bool:\n\n        if set(search_space.keys()) != set(self._search_space.keys()):\n            return False\n\n        for param_name in search_space.keys():\n            if len(search_space[param_name]) != len(self._search_space[param_name]):\n                return False\n\n            for i, param_value in enumerate(search_space[param_name]):\n                if not self._grid_value_equal(param_value, self._search_space[param_name][i]):\n                    return False\n\n        return True\n", "path": "optuna/samplers/_grid.py"}]}