from dragon.search_operators.addons import VarNeighborhood
from dragon.search_space.base_variables import (
FloatVar,
IntVar,
CatVar,
Constant,
ArrayVar,
DynamicBlock,
Block,
ExclusiveBlock,
DynamicExclusiveBlock
)
import random
import numpy as np
from dragon.utils.tools import logger
import copy
[docs]
class IntInterval(VarNeighborhood):
"""IntInterval
`Addon`, used to determine the neighbor of an IntVar.
Draw a random point in :math:`x \pm neighborhood`.
Parameters
----------
variable : IntVar, default=None
Targeted `Variable`.
neighborhood : int, default=None
:math:`x \pm neighborhood`
Examples
--------
>>> from dragon.search_space.base_variables import IntVar
>>> from dragon.search_operators.base_neighborhoods import IntInterval
>>> a = IntVar("test", 0, 5, neighbor=IntInterval(neighborhood=1))
>>> print(a)
IntVar(test, [0;6])
>>> a_test = a.random()
>>> print(a_test)
4
>>> a.neighbor(a_test)
5
"""
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 FloatInterval(VarNeighborhood):
"""FloatInterval
`Addon`, used to determine the neighbor of a FloatVar.
Draw a random point in :math:`x \pm neighborhood`.
Parameters
----------
variable : FloatVar, default=None
Targeted `Variable`.
neighborhood : float, default=None
:math:`x \pm neighborhood`
Examples
--------
>>> from dragon.search_space.base_variables import FloatVar
>>> from dragon.search_operators.base_neighborhoods import FloatInterval
>>> a = FloatVar("test", 0, 5, neighbor=FloatInterval(neighborhood=1))
>>> print(a)
FloatVar(test, [0;5])
>>> a_test = a.random()
>>> print(a_test)
4.0063806879878925
>>> a.neighbor(a_test)
4.477278307217116
"""
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 CatInterval(VarNeighborhood):
"""CatInterval
`Addon`, used to determine the neighbor of a CatVar.
Draw a random feature in CatVar.
Parameters
----------
variable : CatVar, default=None
Targeted `Variable`.
neighborhood : int, default=None
Undefined, for CatVar it draws a random feature.
Examples
--------
>>> from dragon.search_space.base_variables import CatVar, IntVar
>>> from dragon.search_operators.base_neighborhoods import CatInterval, IntInterval
>>> a = CatVar("test", ['a', 1, 2.56, IntVar("int", 100 , 200, neighbor=IntInterval(10))], neighbor=CatInterval())
>>> print(a)
CatVar(test, ['a', 1, 2.56, IntVar(int, [100;201])])
>>> a.neighbor(120, 10) # 10 neighbors for the value '120' within this search space
[188, 2.56, 'a', 1, 'a', 1, 2.56, 151, 151, 1]
"""
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
`Addon`, used to determine the neighbor of a Constant.
Do nothing. Return the constant.
Parameters
----------
variable : Constant, default=None
Targeted `Variable`.
Examples
--------
>>> from dragon.search_space.base_variables import Constant
>>> from dragon.search_operators.base_neighborhoods import ConstantInterval
>>> a = Constant("test", 5, neighbor=ConstantInterval())
>>> print(a)
Constant(test, 5)
>>> a_test = a.random()
>>> print(a_test)
5
>>> a.neighbor(a_test)
5
"""
def __init__(self, variable=None, neighborhood=None):
super(ConstantInterval, self).__init__(variable)
self.neighborhood = neighborhood
def __call__(self, value, size=1):
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 ArrayInterval(VarNeighborhood):
"""ArrayInterval
`Addon`, used to determine the neighbor of an ArrayVar.
neighbor kwarg must be implemented for all `Variable` of the ArrayVar.
One `Variable` is modified for each neighbor drawn.
Parameters
----------
variable : ArrayVar, default=None
Targeted `Variable`.
Examples
----------
>>> from dragon.search_space.base_variables import ArrayVar, IntVar, FloatVar, CatVar
>>> from dragon.search_operators.base_neighborhoods import IntInterval, FloatInterval, CatInterval, ArrayInterval
>>> a = ArrayVar(IntVar("int_1", 0,8, neighbor=IntInterval(2)), IntVar("int_2", 4,45, neighbor=IntInterval(10)),
... FloatVar("float_1", 2,12, neighbor=FloatInterval(0.5)), CatVar("cat_1", ["Hello", 87, 2.56], neighbor=CatInterval()), neighbor=ArrayInterval())
>>> print(a)
ArrayVar(, [IntVar(int_1, [0;9]),IntVar(int_2, [4;46]),FloatVar(float_1, [2;12]),CatVar(cat_1, ['Hello', 87, 2.56])])
>>> a_test = a.random()
>>> print(a_test)
[7, 25, 7.631003022147808, 87]
>>> a.neighbor(a_test, 10)
[[7, 25, 8.003980345265523, 87], [8, 25, 7.631003022147808, 87], [7, 25, 7.631003022147808, 2.56],
[8, 25, 7.631003022147808, 87], [7, 25, 7.631003022147808, 'Hello'], [7, 17, 7.631003022147808, 87],
[7, 25, 7.631003022147808, 2.56], [7, 25, 7.254907155441848, 87], [7, 25, 7.602659938485088, 87],
[7, 25, 7.631003022147808, 'Hello']]
"""
def __init__(self, neighborhood=None, variable=None):
super(ArrayInterval, self).__init__(variable)
self._neighborhood = neighborhood
def __call__(self, value, size=1):
values = list(self._target.values)
for v in self._target.values:
if isinstance(v, Constant):
values.remove(v)
variables = np.random.choice(values, size=size)
if size == 1:
v = variables[0]
inter = copy.deepcopy(value)
inter[v._idx] = v.neighbor(value[v._idx])
return inter
else:
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 is 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 "
f"when defining a variable "
)
[docs]
class BlockInterval(VarNeighborhood):
"""BlockInterval
`Addon`, used to determine the neighbor of an BlockInterval.
neighbor kwarg must be implemented for all `Variable` of the BlockInterval.
Parameters
----------
variable : Block, default=None
Targeted `Variable`.
Examples
----------
>>> from dragon.search_space.base_variables import Block, ArrayVar, FloatVar, IntVar
>>> from dragon.search_operators.base_neighborhoods import BlockInterval, ArrayInterval, FloatInterval, IntInterval
>>> content = ArrayVar(IntVar("int_1", 0,8, neighbor=IntInterval(2)), IntVar("int_2", 4,45, neighbor=IntInterval(10)), FloatVar("float_1", 2,12, neighbor=FloatInterval(10)), neighbor=ArrayInterval())
>>> a = Block("max size 10 Block", content, 3, neighbor=BlockInterval())
>>> print(a)
Block(max size 10 Block, [IntVar(int_1, [0;9]),IntVar(int_2, [4;46]),FloatVar(float_1, [2;12]),])
>>> test_a = a.random()
>>> print(test_a)
[[5, 4, 10.780991223247005], [1, 11, 11.446866387945619], [8, 44, 2.9377647083768217]]
>>> a.neighbor(test_a)
[[5, 7, 10.780991223247005], [0, 11, 11.446866387945619], [8, 44, 2.9377647083768217]]
"""
def __init__(self, neighborhood=None, variable=None):
self._neighborhood = neighborhood
super(BlockInterval, self).__init__(variable)
def __call__(self, value, size=1):
if size == 1:
res = copy.deepcopy(value)
variables_idx = list(set(np.random.choice(range(self.target.repeat), size=self.target.repeat)))
for i in variables_idx:
res[i] = self.target.value.neighbor(value[i])
else:
res = []
for _ in range(size):
inter = copy.deepcopy(value)
variables_idx = list(set(np.random.choice(range(self.target.repeat), size=self.target.repeat)))
for i in variables_idx:
inter[i] = self.target.value.neighbor(value[i])
res.append(inter)
return res
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert isinstance(variable, Block) or variable is None, logger.error(
f"Target object must be a `Block` for {self.__class__.__name__},\
got {variable}"
)
self._target = variable
if variable is not None:
assert hasattr(self._target.value, "neighbor"), logger.error(
f"To use `Block`, value for `Block` must have a `neighbor` method. Use `neighbor` kwarg "
f"when defining a variable "
)
[docs]
class DynamicBlockInterval(VarNeighborhood):
"""BlockInterval
`Addon`, used to determine the neighbor of a DynamicBlock.
neighbor kwarg must be implemented for all `Variable` of the BlockInterval.
Parameters
----------
variable : IntVar, default=None
Targeted `Variable`.
neighborhood : int
Neighborhood of the DynamicBlock size
Example
----------
>>> from dragon.search_space.base_variables import DynamicBlock, ArrayVar, FloatVar, IntVar
>>> from dragon.search_operators.base_neighborhoods import DynamicBlockInterval, ArrayInterval, FloatInterval, IntInterval
>>> content = ArrayVar(IntVar("int_1", 0,8, neighbor=IntInterval(2)), IntVar("int_2", 4,45, neighbor=IntInterval(10)), FloatVar("float_1", 2,12, neighbor=FloatInterval(10)), neighbor=ArrayInterval())
>>> a = DynamicBlock("max size 10 Block", content, 5, neighbor=DynamicBlockInterval(1))
>>> print(a)
DynamicBlock(max size 10 Block, [IntVar(int_1, [0;9]),IntVar(int_2, [4;46]),FloatVar(float_1, [2;12]),])
>>> test_a = a.random()
>>> print(test_a)
[[4, 10, 7.476654992446498]]
>>> a.neighbor(test_a)
[[4, 17, 7.476654992446498], [2, 5, 8.057170687346623], [2, 19, 7.316509989314727], [8, 9, 8.294482483654278], [2, 31, 5.36321423474537]]
"""
def __call__(self, value, size=1, new_repeat=None):
res = []
for _ in range(size):
if new_repeat is None:
new_repeat = np.random.randint(self.target.repeat - self._neighborhood,
self.target.repeat + self._neighborhood+1)
inter = copy.deepcopy(value)
if new_repeat > len(inter):
inter+=[l if (new_repeat - len(inter))==1 else l[0] for l in self.target.random(new_repeat - len(inter))]
if new_repeat < len(inter):
deleted_idx = list(set(random.sample(range(len(inter)), len(inter) - new_repeat)))
for index in sorted(deleted_idx, reverse=True):
del inter[index]
variables_idx = list(set(np.random.choice(range(new_repeat), size=new_repeat)))
for i in variables_idx:
inter[i] = self.target.value.neighbor(inter[i])
res.append(inter)
if size == 1:
return res[0]
else:
return res
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
if isinstance(neighborhood, list):
self._neighborhood = neighborhood[0]
self.target.value.neighborhood = neighborhood[1]
else:
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert isinstance(variable, DynamicBlock) or variable is None, logger.error(
f"Target object must be a `DynamicBlock` for {self.__class__.__name__},\
got {variable}"
)
self._target = variable
if variable is not None:
assert hasattr(self._target.value, "neighbor"), logger.error(
f"To use `DynamicBlock`, value for `DynamicBlock` must have a `neighbor` method. Use `neighbor` kwarg "
f"when defining a variable "
)
class ExclusiveBlockInterval(VarNeighborhood):
"""ExclusiveBlockInterval
`Addon`, used to determine the neighbor of an ExclusiveBlock.
neighbor kwarg must be implemented for all `Variable` of the ExclusiveBlockInterval.
Parameters
----------
variable : IntVar, default=None
Targeted `Variable`.
Example
----------
>>> from dragon.search_space.base_variables import ExclusiveBlock, ArrayVar, FloatVar, IntVar
>>> from dragon.search_operators.base_neighborhoods import ExclusiveBlockInterval, ArrayInterval, FloatInterval, IntInterval
>>> content = ArrayVar(IntVar("int_1", 0,8, neighbor=IntInterval(2)), IntVar("int_2", 4,45, neighbor=IntInterval(10)), FloatVar("float_1", 2,12, neighbor=FloatInterval(10)), neighbor=ArrayInterval())
>>> a = ExclusiveBlock("max size 10 Block", content, 5, neighbor=ExclusiveBlockInterval())
>>> print(a)
ExclusiveBlock(max size 10 Block, [IntVar(int_1, [0;9]),IntVar(int_2, [4;46]),FloatVar(float_1, [2;12]),])
>>> test_a = a.random()
>>> print(test_a)
[[2, 25, 3.407434018008985], [8, 34, 11.720825933953947], [4, 7, 5.294945631972848], [3, 17, 11.621101715902546], [5, 24, 3.194865279405992]]
>>> a.neighbor(test_a)
[[2, 25, 3.407434018008985], [8, 34, 11.720825933953947], [5, 7, 5.294945631972848], [5, 17, 11.621101715902546], [5, 22, 3.194865279405992]]
"""
def __init__(self, neighborhood=None, variable=None):
self.neighborhood = neighborhood
super(ExclusiveBlockInterval, self).__init__(variable)
def __call__(self, value, size=1):
res = []
for _ in range(size):
inter = copy.deepcopy(value)
variables_idx = sorted(list(set(np.random.choice(range(self.target.repeat), size=self.target.repeat))))
for i in variables_idx:
inter[i] = self.target.value.neighbor(value[i])
#Verification of the unicity of each value
while (inter[i] in inter[:i]):
inter[i] = self.target.value.neighbor(inter[i])
res.append(inter)
if (size == 1):
return res[0]
else:
return res
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert(
isinstance(variable, ExclusiveBlock) or variable is None
), f"""Target object must be a `Block` for {self.__class__.__name__},\
got {variable}."""
self._target = variable
if variable is not None:
assert(
hasattr(self._target.value, "neighbor")
), f"""To use `ExclusiveBlock`, value for `ExclusiveBlock` must have a `neighbor` method. Use `neighbor` kwarg when defining a variable."""
class DynamicExclusiveBlockInterval(VarNeighborhood):
"""DynamicExclusiveBlockInterval
`Addon`, used to determine the neighbor of an ExclusiveDynamicBlock.
neighbor kwarg must be implemented for all `Variable` of the ExclusiveDynamicBlockInterval.
Parameters
----------
variable : IntVar, default=None
Targeted `Variable`.
neighborhood : int
Neighborhood of the ExclusiveDynamicBlock size
Example
----------
>>> from dragon.search_space.base_variables import DynamicExclusiveBlock, ArrayVar, FloatVar, IntVar
>>> from dragon.search_operators.base_neighborhoods import DynamicExclusiveBlockInterval, ArrayInterval, FloatInterval, IntInterval
>>> content = ArrayVar(IntVar("int_1", 0,8, neighbor=IntInterval(2)), IntVar("int_2", 4,45, neighbor=IntInterval(10)), FloatVar("float_1", 2,12, neighbor=FloatInterval(10)), neighbor=ArrayInterval())
>>> a = DynamicExclusiveBlock("max size 10 Block", content, 5, neighbor=DynamicExclusiveBlockInterval(1))
>>> print(a)
DynamicExclusiveBlock(max size 10 Block, [IntVar(int_1, [0;9]),IntVar(int_2, [4;46]),FloatVar(float_1, [2;12]),])
>>> test_a = a.random()
>>> print(test_a)
[[4, 42, 10.450686412997023], [1, 37, 7.027430133368101]]
>>> a.neighbor(test_a)
[[4, 42, 10.450686412997023], [1, 29, 7.027430133368101], [5, 5, 8.421363070710674]]
"""
def __call__(self, value, size=1, new_repeat=None):
res = []
for _ in range(size):
inter = copy.deepcopy(value)
if new_repeat is None:
#If a new length for value has not explicitly been given,
#we choose a new one as a function of the given neighborhood
#while remaining in the given length limits
new_repeat = np.random.randint(max(len(inter) - self._neighborhood, self.target.min_repeat),
min(len(inter) + self._neighborhood, self.target.repeat) + 1)
#Adding new unique coefficients if the new length is greater
if new_repeat > len(inter):
diff = new_repeat - len(inter)
for _ in range(diff):
add_coef = self.target.value.random()
while add_coef in inter:
add_coef = self.target.value.random()
inter.append(add_coef)
#Removing values at random if the new length is smaller
if new_repeat < len(inter):
deleted_idx = np.random.choice(range(len(inter)), size = len(inter)-new_repeat, replace=False)
for index in sorted(deleted_idx, reverse=True):
del inter[index]
#The following line chooses value indices to be mutated
variables_idx = sorted(list(set(np.random.choice(range(new_repeat), size=new_repeat))))
for i in variables_idx:
inter[i] = self.target.value.neighbor(inter[i])
#Verification of the unicity of each value
while (inter[i] in inter[:i]):
inter[i] = self.target.value.neighbor(inter[i])
res.append(inter)
if size == 1:
return res[0]
else:
return res
@VarNeighborhood.neighborhood.setter
def neighborhood(self, neighborhood=None):
if isinstance(neighborhood, list):
self._neighborhood = neighborhood[0]
self.target.value.neighborhood = neighborhood[1]
else:
self._neighborhood = neighborhood
@VarNeighborhood.target.setter
def target(self, variable):
assert(
isinstance(variable, DynamicExclusiveBlock) or variable is None
), f"""Target object must be a `DynamicExclusiveBlock` for {self.__class__.__name__}, got {variable}."""
self._target = variable
if variable is not None:
assert(
hasattr(self._target.value, "neighbor")
), f"""To use `DynamicExclusiveBlock`, value for `DynamicExclusiveBlock` must have a `neighbor` method. Use `neighbor` kwarg when defining a variable."""