Source code for zellij.utils.neighborhoods

# @Author: Thomas Firmin <tfirmin>
# @Date:   2022-05-06T12:07:22+02:00
# @Email:  thomas.firmin@univ-lille.fr
# @Project: Zellij
# @Last modified by:   tfirmin
# @Last modified time: 2022-10-03T22:54:24+02:00
# @License: CeCILL-C (http://www.cecill.info/index.fr.html)


from zellij.core.addons import VarNeighborhood, Neighborhood
from zellij.core.variables import (
    FloatVar,
    IntVar,
    CatVar,
    Constant,
    ArrayVar,
)
import numpy as np
import copy

import logging

logger = logging.getLogger("zellij.neighborhoods")


[docs]class ArrayInterval(VarNeighborhood): """ArrayInterval :ref:`spadd`, used to determine the neighbor of an ArrayVar. neighbor kwarg must be implemented for all :ref:`var` of the ArrayVar. Parameters ---------- variable : ArrayVar, default=None Targeted :ref:`var`. neighborhood : list, default=None Not yet implemented Attributes ---------- neighborhood """ def __init__(self, variable=None, neighborhood=None): super(ArrayInterval, self).__init__(variable) self.neighborhood = neighborhood def __call__(self, value, size=1): variables = np.random.choice(self.target.values, size=size) res = [] for v in variables: inter = copy.deepcopy(value) inter[v._idx] = v.neighbor(value[v._idx]) res.append(inter) return res @VarNeighborhood.neighborhood.setter def neighborhood(self, neighborhood=None): if neighborhood: for var, neig in zip(self.target.values, neighborhood): var.neighborhood = neig self._neighborhood = None @VarNeighborhood.target.setter def target(self, variable): assert isinstance(variable, ArrayVar) or variable == None, logger.error( f"Target object must be an `ArrayVar` for {self.__class__.__name__},\ got {variable}" ) self._target = variable if variable != None: assert all( hasattr(v, "neighbor") for v in self.target.values ), logger.error( f"To use `ArrayInterval`, values in `ArrayVar` must have a `neighbor` method. Use `neighbor` kwarg when defining a variable" )
class BlockInterval(VarNeighborhood): """BlockInterval :ref:`spadd`, used to determine the neighbor of an BlockInterval. neighbor kwarg must be implemented for all :ref:`var` of the BlockInterval. Not yet implemented... """ def __call__(self, value, size=1): raise NotImplementedError( f"{self.__class__.__name__}\ neighborhood is not yet implemented" ) class DynamicBlockInterval(VarNeighborhood): """BlockInterval :ref:`spadd`, used to determine the neighbor of an BlockInterval. neighbor kwarg must be implemented for all :ref:`var` of the BlockInterval. Not yet implemented... """ def __call__(self, value, size=1): raise NotImplementedError( f"{self.__class__.__name__}\ neighborhood is not yet implemented" )
[docs]class FloatInterval(VarNeighborhood): """FloatInterval :ref:`varadd`, used to determine the neighbor of a FloatVar. Draw a random point in :math:`x \pm neighborhood`. Parameters ---------- variable : FloatVar, default=None Targeted :ref:`var`. neighborhood : float, default=None :math:`x \pm neighborhood` Attributes ---------- neighborhood """ def __call__(self, value, size=1): upper = np.min([value + self.neighborhood, self.target.up_bound]) lower = np.max([value - self.neighborhood, self.target.low_bound]) if size > 1: res = [] for _ in range(size): v = np.random.uniform(lower, upper) while v == value: v = np.random.uniform(lower, upper) res.append(float(v)) return res else: v = np.random.uniform(lower, upper) while v == value: v = np.random.uniform(lower, upper) return v @VarNeighborhood.neighborhood.setter def neighborhood(self, neighborhood): assert isinstance(neighborhood, int) or isinstance( neighborhood, float ), logger.error( f"`neighborhood` must be a float or an int, for `FloatInterval`,\ got{neighborhood}" ) self._neighborhood = neighborhood @VarNeighborhood.target.setter def target(self, variable): assert isinstance(variable, FloatVar) or variable == None, logger.error( f"Target object must be a `FloatVar` for {self.__class__.__name__},\ got {variable}" ) self._target = variable
[docs]class IntInterval(VarNeighborhood): """IntInterval :ref:`varadd`, used to determine the neighbor of an IntVar. Draw a random point in :math:`x \pm neighborhood`. Parameters ---------- variable : IntVar, default=None Targeted :ref:`var`. neighborhood : int, default=None :math:`x \pm neighborhood` Attributes ---------- neighborhood """ def __call__(self, value, size=1): upper = np.min([value + self.neighborhood + 1, self.target.up_bound]) lower = np.max([value - self.neighborhood, self.target.low_bound]) if size > 1: res = [] for _ in range(size): v = np.random.randint(lower, upper) while v == value: v = np.random.randint(lower, upper) res.append(int(v)) return res else: v = np.random.randint(lower, upper) while v == value: v = np.random.randint(lower, upper) return v @VarNeighborhood.neighborhood.setter def neighborhood(self, neighborhood): assert isinstance(neighborhood, int) or isinstance( neighborhood, float ), logger.error( f"`neighborhood` must be an int, for `IntInterval`,\ got{neighborhood}" ) self._neighborhood = neighborhood @VarNeighborhood.target.setter def target(self, variable): assert isinstance(variable, IntVar) or variable == None, logger.error( f"Target object must be a `IntInterval` for {self.__class__.__name__},\ got {variable}" ) self._target = variable
[docs]class CatInterval(VarNeighborhood): """CatInterval :ref:`varadd`, used to determine the neighbor of a CatVar. Draw a random feature in CatVar. Parameters ---------- variable : FlaotVar, default=None Targeted :ref:`var`. neighborhood : int, default=None Undefined, for CatVar it draws a random feature. Attributes ---------- neighborhood """ def __init__(self, variable=None, neighborhood=None): super(CatInterval, self).__init__(variable) self.neighborhood = neighborhood def __call__(self, value, size=1): if size > 1: res = [] for _ in range(size): v = self.target.random() while v == value: v = self.target.random() res.append(v) return res else: v = self.target.random() while v == value: v = self.target.random() return v @VarNeighborhood.neighborhood.setter def neighborhood(self, neighborhood=None): if neighborhood != None: logger.warning( f"`neighborhood`= {neighborhood} is useless for \ {self.__class__.__name__}, it will be replaced by None" ) self._neighborhood = None @VarNeighborhood.target.setter def target(self, variable): assert isinstance(variable, CatVar) or variable == None, logger.error( f"Target object must be a `CatInterval` for {self.__class__.__name__},\ got {variable}" ) self._target = variable
[docs]class ConstantInterval(VarNeighborhood): """ConstantInterval :ref:`varadd`, used to determine the neighbor of a Constant. Do nothing. Return the constant. Parameters ---------- variable : Constant, default=None Targeted :ref:`var`. neighborhood : int, default=None Not implemented. Attributes ---------- neighborhood """ def __init__(self, variable=None, neighborhood=None): super(ConstantInterval, self).__init__(variable) self.neighborhood = neighborhood def __call__(self, value, size=1): logger.warning("Calling `neighbor` of a constant is useless") if size > 1: return [self.target.value for _ in range(size)] else: return self.target.value @VarNeighborhood.neighborhood.setter def neighborhood(self, neighborhood=None): if neighborhood != None: logger.warning( f"`neighborhood`= {neighborhood} is useless for \ {self.__class__.__name__}, it will be replaced by None" ) self._neighborhood = None @VarNeighborhood.target.setter def target(self, variable): assert isinstance(variable, Constant) or variable == None, logger.error( f"Target object must be a `ConstantInterval` for {self.__class__.__name__}\ , got {variable}" ) self._target = variable
[docs]class Intervals(Neighborhood): """Intervals :ref:`spadd`, used to determine the neighbor of a given point. All :ref:`var` of the :ref:`sp` must have the neighbor addon implemented. Parameters ---------- variable : :ref:`sp`, default=None Targeted :ref:`sp`. neighborhood : list, default=None If a list of the shape of the values from the :ref:`sp`. Modify the neighborhood attribute of all :ref:`varadd` of type VarNeighborhood, for each :ref:`var`. Attributes ---------- neighborhood """ def __init__(self, search_space=None, neighborhood=None): super(Intervals, self).__init__(search_space, neighborhood) @Neighborhood.neighborhood.setter def neighborhood(self, neighborhood): if neighborhood: for var, neig in zip(self.target.values, neighborhood): var.neighbor.neighborhood = neig self._neighborhood = None @Neighborhood.target.setter def target(self, object): self._target = object if object: assert hasattr(self.target.values, "neighbor"), logger.error( f"To use `Intervals`, values in Searchspace must have a `neighbor` method. Use `neighbor` kwarg when defining a variable" ) def __call__(self, point, size=1): """__call__(point, size=1) Draw a neighbor of a solution, according to the :ref:`var` neighbor function. Parameters ---------- point : list Initial point. size : int, default=1 Draw <size> neighbors of <point>. Returns ------- out : list List of neighbors of <point>. """ attribute = self.target.random_attribute(size=size, exclude=Constant) points = [] for att in attribute: inter = copy.deepcopy(point) inter[att._idx] = att.neighbor(point[att._idx]) points.append(inter) return points