# Source code for pyswarms.utils.search.base_search

    # -*- coding: utf-8 -*-
    """Base class for hyperparameter optimization search functions"""

    # Import from __future__
    from __future__ import absolute_import, print_function, with_statement

    # Import standard library
    import operator as op

    class SearchBase(object):

        def assertions(self):
            """Assertion method to check :code:`optimizer` input

            Raises
            ------
            TypeError
                When :code:`optimizer` does not have an `'optimize'` attribute.
            """
            # Check type of optimizer object
            if not hasattr(self.optimizer, "optimize"):
                raise TypeError(
                    "Parameter `optimizer` must have an " "`'optimize'` attribute."
                )

        def __init__(
            self,
            optimizer,
            n_particles,
            dimensions,
            options,
            objective_func,
            iters,
            bounds=None,
            velocity_clamp=(0, 1),
        ):
            """Initialize the Search

            Attributes
            ----------
            optimizer: pyswarms.single
                either LocalBestPSO or GlobalBestPSO
            n_particles : int
                number of particles in the swarm.
            dimensions : int
                number of dimensions in the space.
            options : dict with keys :code:`{'c1', 'c2', 'w', 'k', 'p'}`
                a dictionary containing the parameters for the specific
                optimization technique

                    * c1 : float
                        cognitive parameter
                    * c2 : float
                        social parameter
                    * w : float
                        inertia parameter
                    * k : int
                        number of neighbors to be considered. Must be a
                        positive integer less than :code:`n_particles`
                    * p: int {1,2}
                        the Minkowski p-norm to use. 1 is the
                        sum-of-absolute values (or L1 distance) while 2 is
                        the Euclidean (or L2) distance.

            objective_func: function
                objective function to be evaluated
            iters: int
                number of iterations
            bounds : tuple of np.ndarray, optional (default is None)
                a tuple of size 2 where the first entry is the minimum bound
                while the second entry is the maximum bound. Each array must
                be of shape :code:`(dimensions,)`.
            velocity_clamp : tuple (default is :code:`None`)
                a tuple of size 2 where the first entry is the minimum velocity
                and the second entry is the maximum velocity. It
                sets the limits for velocity clamping.
            """

            # Assign attributes
            self.optimizer = optimizer
            self.n_particles = n_particles
            self.dims = dimensions
            self.options = options
            self.bounds = bounds
            self.vclamp = velocity_clamp
            self.objective_func = objective_func
            self.iters = iters
            # Invoke assertions
            self.assertions()

        def generate_score(self, options):
            """Generate score for optimizer's performance on objective function

            Parameters
            ----------

            options: dict
                a dict with the following keys: {'c1', 'c2', 'w', 'k', 'p'}
            """

            # Intialize optimizer
            f = self.optimizer(
                self.n_particles,
                self.dims,
                options,
                self.bounds,
                velocity_clamp=self.vclamp,
            )

            # Return score
            return f.optimize(self.objective_func, self.iters)[0]

        def search(self, maximum=False):
            """Compare optimizer's objective function performance scores
            for all combinations of provided parameters

            Parameters
            ----------

            maximum: bool
                a bool defaulting to False, returning the minimum value for the
                objective function. If set to True, will return the maximum value
                for the objective function.
            """

            # Generate the grid of all hyperparameter value combinations
            grid = self.generate_grid()

            # Calculate scores for all hyperparameter combinations
            scores = [self.generate_score(i) for i in grid]

            # Default behavior
            idx, self.best_score = min(enumerate(scores), key=op.itemgetter(1))

            # Catches the maximum bool flag
            if maximum:
                idx, self.best_score = max(enumerate(scores), key=op.itemgetter(1))

            # Return optimum hyperparameter value property from grid using index
            self.best_options = op.itemgetter(idx)(grid)
            return self.best_score, self.best_options