# @Author: Thomas Firmin <tfirmin>
# @Date: 2022-05-06T12:07:38+02:00
# @Email: thomas.firmin@univ-lille.fr
# @Project: Zellij
# @Last modified by: tfirmin
# @Last modified time: 2022-11-08T14:26:53+01:00
# @License: CeCILL-C (http://www.cecill.info/index.fr.html)
from zellij.core.addons import VarConverter, Converter
import numpy as np
import logging
logger = logging.getLogger("zellij.converters")
logger.setLevel(logging.INFO)
####################################
# DO NOTHING
####################################
[docs]class DoNothing(VarConverter):
"""DoNothing
:ref:`varadd` used when a :ref:`var` must not be converted.
It does nothing, excep returning a non converted value.
"""
[docs] def convert(self, value, *args, **kwargs):
return value
[docs] def reverse(self, value, *args, **kwargs):
return value
####################################
# TO CONTINUOUS
####################################
[docs]class ArrayMinmax(VarConverter):
"""ArrayMinmax
:ref:`varadd` used when elements of the array must be converted to
continous.
Parameters
----------
variable : ArrayVar
Targeted ArrayVar.
Attributes
----------
variable : ArrayVar
Targeted ArrayVar.
"""
def __init__(self, variable=None):
super(ArrayMinmax, self).__init__(variable)
if variable:
assert all(
hasattr(v, "to_continuous") for v in self.target.values
), logger.error(
f"To use `ArrayMinmax`, values in `ArrayVar` must have a `to_continuous` method. Use `to_continuous` kwarg when defining a variable"
)
[docs] def convert(self, value):
res = []
for variable, v in zip(self.target.values, value):
res.append(variable.to_continuous.convert(v))
return res
[docs] def reverse(self, value):
res = []
for variable, v in zip(self.target.values, value):
res.append(variable.to_continuous.reverse(v))
return res
class BlockMinmax(VarConverter):
"""BlockMinmax
:ref:`varadd` used when elements of the Block must be converted to
continous.
Parameters
----------
variable : Block
Targeted Block.
Attributes
----------
variable : Block
Targeted Block.
"""
def __init__(self, variable=None):
super(BlockMinmax, self).__init__(variable)
assert hasattr(self.target.value, "to_continuous"), logger.error(
f"To use `BlockMinmax`, value in `Block` must have a `to_continuous` method. Use `to_continuous` kwarg when defining a variable"
)
def convert(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be equal to `Block` length,\
got {len(value)}(value)=={self.target.repeat}(Block)"
)
res = []
for v in value:
res.append(self.target.value.to_continuous.convert(v))
return res
def reverse(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be equal to `Block` length,\
got {len(value)}(value)=={self.target.repeat}(Block)"
)
res = []
for v in value:
res.append(self.target.value.to_continuous.reverse(v))
return res
class DynamicBlockMinmax(VarConverter):
"""DynamicBlockMinmax
:ref:`varadd` used when elements of the DynamicBlockMinmax must be
converted to continous.
Parameters
----------
variable : DynamicBlockMinmax
Targeted DynamicBlockMinmax.
Attributes
----------
variable : DynamicBlockMinmax
Targeted DynamicBlockMinmax.
"""
def __init__(self, variable=None):
super(DynamicBlockMinmax, self).__init__(variable)
assert hasattr(self.target.value, "to_continuous"), logger.error(
f"To use `DynamicBlockMinmax`, value in `DynamicBlock` must have a `to_continuous` method. Use `to_continuous` kwarg when defining a variable"
)
def convert(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be inferior or equal to `DynamicBlock`\
length, got {len(value)}(value)<={self.target.repeat}(DynamicBlock)"
)
res = []
for v in value:
res.append(self.target.value.to_continuous.convert(v))
return res
def reverse(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be inferior or equal to `DynamicBlock`\
length, got {len(value)}(value)<={self.target.repeat}(DynamicBlock)"
)
res = []
for v in value:
res.append(self.target.value.to_continuous.reverse(v))
return res
[docs]class FloatMinmax(VarConverter):
"""FloatMinmax
Convert the value of a FloatVar, using
:math:`\\frac{x-lower}{upper-lower}=y`
.Reverse: :math:`y(upper-lower)+lower=x`
"""
[docs] def convert(self, value):
return (value - self.target.low_bound) / (
self.target.up_bound - self.target.low_bound
)
[docs] def reverse(self, value):
return (
value * (self.target.up_bound - self.target.low_bound)
+ self.target.low_bound
)
[docs]class IntMinmax(VarConverter):
"""IntMinmax
Convert the value of an IntVar, using :math:`\\frac{x-lower}{upper-lower}=y`
.Reverse: :math:`y(upper-lower)+lower=x`
"""
[docs] def convert(self, value):
return (value - self.target.low_bound) / (
self.target.up_bound - self.target.low_bound
)
[docs] def reverse(self, value):
return int(
value * (self.target.up_bound - self.target.low_bound)
+ self.target.low_bound
)
[docs]class CatMinmax(VarConverter):
"""CatMinmax
Convert the value of a CatVar, using the index of the value in the list
of the features of CatVar. :math:`\\frac{index}{len(features)}=y`
.Reverse: :math:`features[floor(y*(len(features)-1))]=x`.
"""
[docs] def convert(self, value):
return self.target.features.index(value) / len(self.target.features)
[docs] def reverse(self, value):
idx = int(value * len(self.target.features))
if idx == len(self.target.features):
idx -= 1
return self.target.features[idx]
[docs]class ConstantMinmax(VarConverter):
"""ConstantMinmax
Convert the value of a Constant. :math:`y=1.0`
.Reverse: :math:`x=value`.
"""
[docs] def convert(self, value):
return 1.0
[docs] def reverse(self, value):
return self.target.value
####################################
# TO DISCRETE
####################################
[docs]class ArrayBinning(VarConverter):
"""ArrayBinning
:ref:`varadd` used when elements of the array must be converted to
discrete. When binning some information can be lost.
Parameters
----------
variable : ArrayVar
Targeted ArrayVar.
Attributes
----------
variable : ArrayVar
Targeted ArrayVar.
"""
def __init__(self, variable=None):
super(ArrayBinning, self).__init__(variable)
if variable:
assert all(
hasattr(v, "to_discrete") for v in self.target.values
), logger.error(
f"""
To use `ArrayMinmax`, values in `ArrayVar` must have a
`to_discrete` method.
Use `to_discrete` kwarg when defining a variable
"""
)
[docs] def convert(self, value):
res = []
for variable, v in zip(self.target.values, value):
res.append(variable.to_discrete.convert(v))
return res
[docs] def reverse(self, value):
res = []
for variable, v in zip(self.target.values, value):
res.append(variable.to_discrete.reverse(v))
return res
class BlockBinning(VarConverter):
"""BlockBinning
:ref:`varadd` used when elements of the Block must be converted to
discrete.
Parameters
----------
variable : Block
Targeted Block.
Attributes
----------
variable : Block
Targeted Block.
"""
def __init__(self, variable=None):
super(BlockBinning, self).__init__(variable)
assert hasattr(self.target.value, "to_discrete"), logger.error(
f"To use `BlockBinning`, value in `Block` must have a `to_discrete` method. Use `to_discrete` kwarg when defining a variable"
)
def convert(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be equal to `Block` length,\
got {len(value)}(value)=={self.target.repeat}(Block)"
)
res = []
for v in value:
res.append(self.target.value.to_discrete.convert(v, K))
return res
def reverse(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be equal to `Block` length,\
got {len(value)}(value)=={self.target.repeat}(Block)"
)
res = []
for v in value:
res.append(self.target.value.to_discrete.reverse(v, K))
return res
class DynamicBlockBinning(VarConverter):
"""DynamicBlockMinmax
:ref:`varadd` used when elements of the DynamicBlockMinmax must be
converted to discrete.
Parameters
----------
variable : DynamicBlockMinmax
Targeted DynamicBlockMinmax.
Attributes
----------
variable : DynamicBlockMinmax
Targeted DynamicBlockMinmax.
"""
def __init__(self, variable=None):
super(DynamicBlockBinning, self).__init__(variable)
assert hasattr(self.target.value, "to_discrete"), logger.error(
f"To use `DynamicBlockBinning`, value in `DynamicBlock` must have a `to_discrete` method. Use `to_discrete` kwarg when defining a variable"
)
def convert(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be inferior or equal to `DynamicBlock`\
length, got {len(value)}(value)<={self.target.repeat}(DynamicBlock)"
)
res = []
for v in value:
res.append(self.target.value.to_discrete.convert(v, K))
return res
def reverse(self, value):
assert len(value) == self.target.repeat, logger.error(
f"Length of value must be inferior or equal to `DynamicBlock`\
length, got {len(value)}(value)<={self.target.repeat}(DynamicBlock)"
)
res = []
for v in value:
res.append(self.value.target.to_discrete.reverse(v, K))
return res
[docs]class FloatBinning(VarConverter):
"""FloatBinning
Convert a value from an FloatVar using binning between its
upper and lower bounds. Reversing a converted value will not return the
initial value. When binning some information can be lost. here the decimal
part of the float number.
"""
def __init__(self, K, variable=None):
super(FloatBinning, self).__init__(variable)
assert (
isinstance(K, int) and K > 1
), f"K must be an int >1 for FloatBinning, got {K}"
self.K = K
[docs] def convert(self, value):
bins = np.linspace(self.target.low_bound, self.target.up_bound, self.K)
return np.digitize(value, bins) - 1
[docs] def reverse(self, value):
bins = np.linspace(self.target.low_bound, self.target.up_bound, self.K)
return bins[value]
[docs]class IntBinning(VarConverter):
"""IntMinmax
Convert a value from an IntVar using binning between its
upper and lower bounds. Reversing a converted value will not return the
initial value. When binning some information can be lost.
"""
def __init__(self, K, variable=None):
super(IntBinning, self).__init__(variable)
assert (
isinstance(K, int) and K > 1
), f"K must be an int >1 for IntBinning, got {K}"
self.K = K
[docs] def convert(self, value):
bins = np.linspace(self.target.low_bound, self.target.up_bound, self.K)
return np.digitize(value, bins) - 1
[docs] def reverse(self, value):
bins = np.linspace(self.target.low_bound, self.target.up_bound, self.K)
return bins[value]
[docs]class CatBinning(VarConverter):
"""CatMinmax
Convert the value of a CatVar to its corresponding index in the
features list.
"""
[docs] def convert(self, value):
return self.target.features.index(value)
[docs] def reverse(self, value):
return self.target.features[value]
[docs]class ConstantBinning(VarConverter):
"""ConstantMinmax
Convert the value of a Constant. :math:`y=1`
Reverse: :math:`x=value`.
"""
[docs] def convert(self, value):
return 1
[docs] def reverse(self, value):
return self.target.value
####################################
# SEARCH SPACE CONVERTER
####################################
[docs]class Continuous(Converter):
"""Continuous
Convert :ref:`var` of a :ref:`sp` to continuous.
to_continuous addon must be implemented for each :ref:`var`.
Parameters
----------
search_space : :ref:`sp`
Targeted :ref:`sp`.
Attributes
----------
target : :ref:`sp`
Targeted :ref:`sp`.
"""
def __init__(self, search_space=None):
super(Continuous, self).__init__(search_space)
if search_space:
assert hasattr(self.target.values, "to_continuous"), logger.error(
f"To use `to_continuous`, values in Searchspace must have a `to_continuous` method. Use `to_continuous` kwarg when defining a variable"
)
# Convert a point to continuous
[docs] def convert(self, points, sub_values=False):
"""convert(self, points, sub_values=False)
Convert given points from mixed to continuous
Parameters
----------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of points to convert
sub_values : boolean, default=True
If the search space is a subspace and if True,
uses the original values to convert, else uses its own bounds.
See :ref:`sp`
Returns
-------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of converted points. Points are list of float if converted to continuous.
"""
# Use bounds from the original space if this object is a subspace.
if sub_values and self.target._god.values != None:
val = self.target._god.values
# Use initial bounds to convert
else:
val = self.target.values
res = []
# Mixed to continuous
if isinstance(points[0], (list, np.ndarray)):
res = []
for point in points:
res.append(val.to_continuous.convert(point))
return res
else:
return val.to_continuous.convert(points)
# Convert a continuous point to a mixed point
[docs] def reverse(self, points, sub_values=False):
"""reverse(self, points, sub_values=False)
Convert given points from continuous to mixed
Parameters
----------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of points to convert
sub_values : boolean, default=True
If the search space is a subspace and if True,
uses the original values to convert, else uses its own bounds.
See :ref:`sp`
Returns
-------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of converted points. Points are list of float if converted to continuous.
"""
# Use bounds from the original space if this object is a subspace.
if sub_values and self.target._god.values != None:
val = self.target._god.values
# Use initial bounds to convert
else:
val = self.target.values
# Mixed to continuous
if isinstance(points[0], (list, np.ndarray)):
res = []
for point in points:
res.append(val.to_continuous.reverse(point))
return res
else:
return val.to_continuous.reverse(points)
[docs]class Discrete(Converter):
"""Discrete
Convert :ref:`var` of a :ref:`sp` to discrete.
to_discrete addon must be implemented for each :ref:`var`.
Parameters
----------
search_space : :ref:`sp`
Targeted :ref:`sp`.
Attributes
----------
target : :ref:`sp`
Targeted :ref:`sp`.
"""
def __init__(self, search_space=None, K=10):
super(Discrete, self).__init__(search_space)
if search_space:
assert hasattr(self.target.values, "to_discrete"), logger.error(
f"To use `Discrete`, values in Searchspace must have a `to_discrete` method. Use `to_discrete` kwarg when defining a variable"
)
self.K = K
# Convert a point to continuous
[docs] def convert(self, points, sub_values=False):
"""convert(self, points, sub_values=False)
Convert given points from mixed to discrete
Parameters
----------
points : {list[list[{int, float, str}, {int, float, str}...], ...],\list[list[float, float...], ...]}
List of points to convert
sub_values : boolean, default=True
If the search space is a subspace and if True,
uses the original values to convert, else uses its own bounds.
See :ref:`sp`
Returns
-------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of converted points. Points are list of float if converted to continuous.
"""
# Use bounds from the original space if this object is a subspace.
if sub_values and self.target._god.values != None:
val = self.target._god.values
# Use initial bounds to convert
else:
val = self.target.values
res = []
# Mixed to discrete
for point in points:
res.append(val.to_discrete.convert(point))
return res
# Convert a continuous point to a mixed point
[docs] def reverse(self, points, sub_values=False):
"""reverse(self, points, sub_values=False)
Convert given points from continuous to mixed
Parameters
----------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of points to convert
sub_values : boolean, default=True
If the search space is a subspace and if True,
uses the original values to convert, else uses its own bounds.
See :ref:`sp`
Returns
-------
points : {list[list[{int, float, str}, {int, float, str}...], ...], list[list[float, float...], ...]}
List of converted points. Points are list of float if converted to continuous.
"""
# Use bounds from the original space if this object is a subspace.
if sub_values and self.target._god.values != None:
val = self.target._god.values
# Use initial bounds to convert
else:
val = self.target.values
res = []
# Mixed to discrete
for point in points:
res.append(val.to_discrete.reverse(point))
return res