class_docstr: Grid search tuner divides search space into evenly spaced grid, and performs brute-force traverse.
  
  Recommended when the search space is small, or if you want to find strictly optimal hyperparameters.
  
  **Implementation**
  
  The original grid search approach performs an exhaustive search through a space consists of ``choice`` and ``randint``.
  
  NNI's implementation extends grid search to support all search spaces types.
  
  When the search space contains continuous parameters like ``normal`` and ``loguniform``,
  grid search tuner works in following steps:
  
  1. Divide the search space into a grid.
  2. Perform an exhaustive searth through the grid.
  3. Subdivide the grid into a finer-grained new grid.
  4. Goto step 2, until experiment end.
  
  As a deterministic algorithm, grid search has no argument.
  
  Examples
  --------
  
  .. code-block::
  
      config.tuner.name = 'GridSearch'
code: def __init__(self, optimize_mode=None):
      self.space = None
  
      # the grid to search in this epoch
      # when the space is fully explored, grid is set to None
      self.grid = None  # list[int | float]
  
      # a paremter set is internally expressed as a vector
      # for each dimension i, self.vector[i] is the parameter's index in self.grid[i]
      # in second epoch of above example, vector [1, 2, 0] means parameters {x: 7, y: 0.67, z: 2}
      self.vector = None  # list[int]
  
      # this tells which parameters are derived from previous epoch
      # in second epoch of above example, epoch_bar is [2, 1, 1]
      self.epoch_bar = None  # list[int]
  
      # this stores which intervals are possibly divisible (low < high after log and q)
      # in first epoch of above example, divisions are:
      #     {1: [(0,1/2), (1/2,1)], 2: [(1/2,1)]}
      # in second epoch:
      #     {1: [(0,1/4), (1/4,1/2), (1/2,3/4), (3/4,1)], 2: [(1/2,3/4)]}
      # and in third epoch:
      #     {1: [(0,1/8), ..., (7/8,1)], 2: []}
      self.divisions = {}  # dict[int, list[tuple[float, float]]]
  
      # dumped JSON string of all tried parameters
      self.history = set()
  
      if optimize_mode is not None:
          _logger.info(f'Ignored optimize_mode "{optimize_mode}"')
  
imports: import logging
  import math
  import numpy
  from scipy.special import erfinv
  import nni
  from nni.common.hpo_utils import ParameterSpec, deformat_parameters, format_search_space
  from nni.tuner import Tuner
label_desc: Use image node_id 1 to create a new GridSearchTuner object from inherited base classes: Tuner with example: obj = GridSearchTuner(optimize_mode)