Source code for zellij.utils.distances

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


from zellij.core.addons import Distance
from zellij.core.variables import FloatVar, IntVar, CatVar, Constant
from scipy.spatial import distance
import numpy as np
import logging

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


[docs]class Euclidean(Distance): """Euclidean distance Compute the Euclidean distance between two points. More info on `SciPy <https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.euclidean.html#scipy.spatial.distance.euclidean>`__ Example ------- >>> from zellij.core.variables import ArrayVar, FloatVar >>> from zellij.utils.distances import Euclidean >>> from zellij.core.loss_func import Loss >>> from zellij.utils.benchmark import himmelblau >>> from zellij.core.search_space import ContinuousSearchspace >>> lf = Loss()(himmelblau) >>> a = ArrayVar(FloatVar("float_1", 0,1), ... FloatVar("float_2", 0,1)) >>> sp = ContinuousSearchspace(a,lf, distance=Euclidean()) >>> p1,p2 = sp.random_point(), sp.random_point() >>> print(p1) [0.8922761649920034, 0.12709277668616326] >>> print(p2) [0.7730279148456985, 0.14715728189857524] >>> sp.distance(p1,p2) 0.12092447863180801 See also -------- :ref:`dist`: Distance addons """ def __call__(self, point_a, point_b): return distance.euclidean(point_a, point_b, self.weights)
[docs]class Manhattan(Distance): """Manhattan distance Compute the Manhattan distance between two points. More info on `SciPy <https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cityblock.html#scipy.spatial.distance.cityblock>`__ Example ------- >>> from zellij.core.variables import ArrayVar, FloatVar >>> from zellij.utils.distances import Manhattan >>> from zellij.core.loss_func import Loss >>> from zellij.utils.benchmark import himmelblau >>> from zellij.core.search_space import ContinuousSearchspace >>> lf = Loss()(himmelblau) >>> a = ArrayVar(FloatVar("float_1", 0,1), ... FloatVar("float_2", 0,1)) >>> sp = ContinuousSearchspace(a,lf, distance=Manhattan()) >>> p1,p2 = sp.random_point(), sp.random_point() >>> print(p1) [0.12946481931952147, 0.31940702810480137] >>> print(p2) [0.32347527913737095, 0.9356077155539462] >>> sp.distance(p1,p2) 0.8102111472669943 See also -------- :ref:`dist`: Distance addons """ def __call__(self, point_a, point_b): return distance.cityblock(point_a, point_b, self.weights)
[docs]class Mixed(Distance): """Mixed distance Compute a distance between two mixed points, using following equations: .. math:: \\smash{ \\begin{cases} \\delta_{i,j}^{(n)}=\\frac{|x_{i,n}-x_{j,n}|}{max_h(x_{h,n})-min_h(x_{h,n})}, \\quad \\text{if: $x_{h,n}$ is continuous or discrete}\\\\ \\begin{cases} \\delta_{i,j}^{(n)}=0, \\quad \\text{if, $x_{i,n}=x_{j,n}$}\\\\ \\delta_{i,j}^{(n)}=1, \\quad \\text{otherwise} \\end{cases} , \\quad \\text{if: $x_{h,n}$ is categorical}\\\\ d(x_i,x_j)=\\frac{\\sum_{n=1}^{p}(\\delta_{i,j}^{(n)})^2}{\\sum_{n=1}^{p}\\delta_{i,j}^{(n)}}\\\\ \\end{cases}} Example ------- >>> from zellij.core.variables import ArrayVar, IntVar, FloatVar, CatVar >>> from zellij.utils.distances import Mixed >>> from zellij.core.loss_func import Loss >>> from zellij.utils.benchmark import himmelblau >>> from zellij.core.search_space import MixedSearchspace >>> a = ArrayVar(IntVar("int_1", 0,8), >>> IntVar("int_2", 4,45), >>> FloatVar("float_1", 2,12), >>> CatVar("cat_1", ["Hello", 87, 2.56])) >>> lf = Loss()(himmelblau) >>> sp = MixedSearchspace(a,lf, distance=Mixed()) >>> p1,p2 = sp.random_point(), sp.random_point() >>> print(p1) [5, 34, 4.8808143412719485, 87] >>> print(p2) [3, 42, 2.8196595134477738, 'Hello'] >>> sp.distance(p1,p2) 0.5990169287736146 See also -------- :ref:`dist`: Distance addons """ def __init__(self, search_space=None, weights=None): self.weights = weights self.operations = [] super(Mixed, self).__init__(search_space) def __call__(self, point_a, point_b): num = 0 denum = 0 for x, y, op, w in zip(point_a, point_b, self.operations, self.weights): res = op(x, y) * w num += res**2 denum += res if denum == 0: if num == 0: return 0 else: return float("inf") else: return num / denum @Distance.target.setter def target(self, object): from zellij.core.search_space import MixedSearchspace self._target = object assert ( isinstance(self._target, MixedSearchspace) or object == None ), logger.error( f"Target must be of type `MixedSearchspace`, got {object}" ) if self._target: if self.weights: assert len(self.weights) == self._target.size, logger.error( f"len(weights) must be equal to len(values) in `ArrayVar` of :ref:`Searchspace`" ) else: self.weights = [1] * self._target.size self.operations = [] for v in self._target.values: if isinstance(v, FloatVar) or isinstance(v, IntVar): up, lo = v.up_bound, v.low_bound self.operations.append( lambda x, y: np.abs(x - y) / (up - lo) ) elif isinstance(v, CatVar): self.operations.append(lambda x, y: 1 if x == y else 0) elif isinstance(v, Constant): self.operations.append(lambda x, y: 0)