class_docstr: Sampler using grid search.
  
  With :class:`~optuna.samplers.GridSampler`, the trials suggest all combinations of parameters
  in the given search space during the study.
  
  Example:
  
      .. testcode::
  
          import optuna
  
  
          def objective(trial):
              x = trial.suggest_float("x", -100, 100)
              y = trial.suggest_int("y", -100, 100)
              return x**2 + y**2
  
  
          search_space = {"x": [-50, 0, 50], "y": [-99, 0, 99]}
          study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))
          study.optimize(objective)
  
  Note:
  
      This sampler with :ref:`ask_and_tell` raises :exc:`RuntimeError` just after evaluating
      the final grid. This is because :class:`~optuna.samplers.GridSampler` automatically
      stops the optimization if all combinations in the passed ``search_space`` have already
      been evaluated, internally invoking the :func:`~optuna.study.Study.stop` method.
      As a workaround, we need to handle the error manually as in
      https://github.com/optuna/optuna/issues/4121#issuecomment-1305289910.
  
  Note:
  
      :class:`~optuna.samplers.GridSampler` does not take care of a parameter's quantization
      specified by discrete suggest methods but just samples one of values specified in the
      search space. E.g., in the following code snippet, either of ``-0.5`` or ``0.5`` is
      sampled as ``x`` instead of an integer point.
  
      .. testcode::
  
          import optuna
  
  
          def objective(trial):
              # The following suggest method specifies integer points between -5 and 5.
              x = trial.suggest_float("x", -5, 5, step=1)
              return x**2
  
  
          # Non-int points are specified in the grid.
          search_space = {"x": [-0.5, 0.5]}
          study = optuna.create_study(sampler=optuna.samplers.GridSampler(search_space))
          study.optimize(objective, n_trials=2)
  
  Note:
      A parameter configuration in the grid is not considered finished until its trial is
      finished. Therefore, during distributed optimization where trials run concurrently,
      different workers will occasionally suggest the same parameter configuration.
      The total number of actual trials may therefore exceed the size of the grid.
  
  Note:
      All parameters must be specified when using :class:`~optuna.samplers.GridSampler` with
      :meth:`~optuna.study.Study.enqueue_trial`.
  
  Args:
      search_space:
          A dictionary whose key and value are a parameter name and the corresponding candidates
          of values, respectively.
      seed:
          A seed to fix the order of trials as the grid is randomly shuffled. Please note that
          it is not recommended using this option in distributed optimization settings since
          this option cannot ensure the order of trials and may increase the number of duplicate
          suggestions during distributed optimization.
code: def before_trial(self, study: Study, trial: FrozenTrial) -> None:
      # Instead of returning param values, GridSampler puts the target grid id as a system attr,
      # and the values are returned from `sample_independent`. This is because the distribution
      # object is hard to get at the beginning of trial, while we need the access to the object
      # to validate the sampled value.
  
      # When the trial is created by RetryFailedTrialCallback or enqueue_trial, we should not
      # assign a new grid_id.
      if (
          "grid_id" in trial.system_attrs
          or "fixed_params" in trial.system_attrs
      ):
          return
  
      if 0 <= trial.number and trial.number < self._n_min_trials:
          study._storage.set_trial_system_attr(
              trial._trial_id, "search_space", self._search_space
          )
          study._storage.set_trial_system_attr(
              trial._trial_id, "grid_id", trial.number
          )
          return
  
      target_grids = self._get_unvisited_grid_ids(study)
  
      if len(target_grids) == 0:
          # This case may occur with distributed optimization or trial queue. If there is no
          # target grid, `GridSampler` evaluates a visited, duplicated point with the current
          # trial. After that, the optimization stops.
  
          _logger.warning(
              "`GridSampler` is re-evaluating a configuration because the grid has been "
              "exhausted. This may happen due to a timing issue during distributed optimization "
              "or when re-running optimizations on already finished studies."
          )
  
          # One of all grids is randomly picked up in this case.
          target_grids = list(range(len(self._all_grids)))
  
      # In distributed optimization, multiple workers may simultaneously pick up the same grid.
      # To make the conflict less frequent, the grid is chosen randomly.
      grid_id = int(self._rng.rng.choice(target_grids))
  
      study._storage.set_trial_system_attr(
          trial._trial_id, "search_space", self._search_space
      )
      study._storage.set_trial_system_attr(
          trial._trial_id, "grid_id", grid_id
      )
  
imports: import itertools
  from numbers import Real
  from typing import Any
  from typing import Dict
  from typing import List
  from typing import Mapping
  from typing import Optional
  from typing import Sequence
  from typing import Union
  import warnings
  import numpy
  from optuna.distributions import BaseDistribution
  from optuna.logging import get_logger
  from optuna.samplers import BaseSampler
  from optuna.samplers._lazy_random_state import LazyRandomState
  from optuna.study import Study
  from optuna.trial import FrozenTrial
  from optuna.trial import TrialState
label_desc: Use image node_id 3 for calling the GridSampler obj's underlying member method code with example usage: obj.before_trial(study, trial) without return types