"""
.. py:module:: mappers
:platform: Unix
Various mapper implementations. Mappers are functions that map possible feature
value's to the interval [-1, 1]. In Creamas, they are used by individual
agent's to represent agent's preferences over features values.
"""
from creamas.math import gaus_pdf, logistic
from creamas.rules.mapper import Mapper
__all__ = ['BooleanMapper', 'LinearMapper', 'DoubleLinearMapper',
'GaussianMapper', 'LogisticMapper']
[docs]class BooleanMapper(Mapper):
"""Boolean mapper that has four different modes.
Depending on the mode, True and False are mapped either to 1, 0, or -1.
======= ======= =======
mode True False
======= ======= =======
'10' 1.0 0.0
'01' 0.0 1.0
'1-1' 1.0 -1.0
'-11' -1.0 1.0
======= ======= =======
"""
modes = ['10', '01', '1-1', '-11']
def __init__(self, mode='10'):
self._value_set = {bool}
self.mode = mode
self._mode_maps = {'10': self._map10, '01': self._map01,
'1-1': self._map1_1, '-11': self._map_11}
def __str__(self):
return "{}({})".format(self.__class__.__name__, self._mode)
@property
def mode(self):
"""Mode of the mapper."""
return self._mode
@mode.setter
def mode(self, value):
if value not in self.modes:
raise ValueError('Value ({}) not found from modes.'.format(value))
self._mode = value
[docs] def map(self, value):
return self._mode_maps[self._mode](value)
def _map10(self, value):
return 1.0 if value else 0.0
def _map01(self, value):
return 0.0 if value else 1.0
def _map1_1(self, value):
return 1.0 if value else -1.0
def _map_11(self, value):
return -1.0 if value else 1.0
[docs]class LinearMapper(Mapper):
"""Mapper that maps values in given interval linearly.
Can be used for features that return either 'int' or 'float' values.
Based on its mode, maps *lo* and *hi* to different end points and values
between them to a straight line. Depending on the mode, *lo* and *hi* have
following end points:
======= ===== =====
mode lo hi
======= ===== =====
'10' 1.0 0.0
'01' 0.0 1.0
'1-1' 1.0 -1.0
'-11' -1.0 1.0
======= ===== =====
"""
_value_set = {int, float}
modes = ['10', '01', '1-1', '-11']
def __init__(self, lo, hi, mode='01'):
if lo > hi:
raise ValueError('lo ({}) must be smaller than hi ({}).'
.format(lo, hi))
self._lo = lo
self._hi = hi
self._mode_maps = {'10': self._map10, '01': self._map01,
'1-1': self._map1_1, '-11': self._map_11}
self.mode = mode
def __str__(self):
return "{}({}-{},{})".format(self.__class__.__name__, self._lo,
self._hi, self._mode)
@property
def mode(self):
"""Mode of the mapper."""
return self._mode
@mode.setter
def mode(self, value):
if value not in self.modes:
raise ValueError('Value ({}) not found from modes.'.format(value))
self._mode = value
@property
def value_set(self):
"""Accepted value types, i.e. this mapper can be used for the features
that return these types of values."""
return self._value_set
[docs] def map(self, value):
return self._mode_maps[self._mode](self._lo, self._hi, value)
def _map10(self, lo, hi, value):
if value < lo:
return 1.0
if value > hi:
return 0.0
diff = hi - lo
val_diff = value - lo
return 1.0 - (float(val_diff) / diff)
def _map01(self, lo, hi, value):
if value < lo:
return 0.0
if value > hi:
return 1.0
diff = hi - lo
val_diff = value - lo
return 0.0 + (float(val_diff) / diff)
def _map1_1(self, lo, hi, value):
if value < lo:
return 1.0
if value > hi:
return -1.0
diff = hi - lo
val_diff = value - lo
return 1.0 - (2 * (float(val_diff) / diff))
def _map_11(self, lo, hi, value):
if value < lo:
return -1.0
if value > hi:
return 1.0
diff = hi - lo
val_diff = value - lo
return -1.0 + (2 * (float(val_diff) / diff))
[docs]class DoubleLinearMapper(LinearMapper):
"""Mapper that concatenates two linear mappers.
Can be used for features that return either 'int' or 'float' values.
First line is created from *lo* to *mid* and second line from *mid* to
*hi*. Depending on the mode, *lo*, *mid* and *hi* are mapped to following
end points.
======= ===== ====== ======
mode lo mid hi
======= ===== ====== ======
'10' 1.0 0.0 1.0
'01' 0.0 1.0 0.0
'1-1' 1.0 -1.0 1.0
'-11' -1.0 1.0 -1.0
======= ===== ====== ======
"""
# Reverse modes (modes for second line) for the modes described in the
# LinearMapper.
reverse_modes = ['01', '10', '-11', '1-1']
def __init__(self, lo, mid, hi, mode='01'):
if lo >= mid:
raise ValueError('lo ({}) must be smaller than mid ({}).'
.format(lo, mid))
if mid >= hi:
raise ValueError('mid ({}) must be smaller than hi ({}).'
.format(mid, hi))
self._lo = lo
self._mid = mid
self._hi = hi
self._mode_maps = {'10': self._map10, '01': self._map01,
'1-1': self._map1_1, '-11': self._map_11}
self.mode = mode
self._rmode = self._get_reverse_mode(mode)
def __str__(self):
return "{}({}-{}-{},{})".format(self.__class__.__name__, self._lo,
self._mid, self._hi, self._mode)
def _get_reverse_mode(self, mode):
return self.reverse_modes[self.modes.index(mode)]
@property
def mode(self):
"""Mode of the mapper.
"""
return self._mode
@mode.setter
def mode(self, value):
if value not in self.modes:
raise ValueError('Value ({}) not found from modes.'.format(value))
self._mode = value
self._rmode = self._get_reverse_mode(self._mode)
[docs] def map(self, value):
if value <= self._mid:
return self._mode_maps[self._mode](self._lo, self._mid, value)
return self._mode_maps[self._rmode](self._mid, self._hi, value)
[docs]class GaussianMapper(Mapper):
"""Gaussian distribution mapper.
The mapped value is relative to given Gaussian distribution's
maximum point (*pmax*, evaluated at point *loc*) and the probability
density function's value at given evaluation point (*pval*).
The actual value calculation changes with the mode of the mapper:
======= =======================
mode mapped value
======= =======================
'10' :math:`1.0 - (pval / pmax)`
'01' :math:`pval / pmax`
'1-1' :math:`1.0 - 2(pval / pmax)`
'-11' :math:`-1.0 + 2(pval / pmax)`
======= =======================
"""
_value_set = {int, float}
modes = ['10', '01', '1-1', '-11']
def __init__(self, mean, std, mode='01'):
"""
:param float mean: mean of the mapping distribution
:param float std: standard deviation of the mapping distribution
:param mode: mode of the mapper: '10', '01', '1-1' or '-11'.
"""
self._mean = mean
self._std = std
self.mode = mode
self._mode_maps = {'10': self._map10, '01': self._map01,
'1-1': self._map1_1, '-11': self._map_11}
def __str__(self):
return "{}({}-{},{})".format(self.__class__.__name__, self._mean,
self._std, self._mode)
@property
def mode(self):
"""Mode of the mapper."""
return self._mode
@mode.setter
def mode(self, value):
if value not in self.modes:
raise ValueError('Value ({}) not found from modes.'.format(value))
self._mode = value
[docs] def map(self, value):
return self._mode_maps[self._mode](self._mean, self._std, value)
def _map10(self, mean, std, value):
lmax = gaus_pdf(mean, mean, std)
pdf = gaus_pdf(value, mean, std)
return 1.0 - (pdf / lmax)
def _map01(self, mean, std, value):
lmax = gaus_pdf(mean, mean, std)
pdf = gaus_pdf(value, mean, std)
return pdf / lmax
def _map1_1(self, mean, std, value):
lmax = gaus_pdf(mean, mean, std)
pdf = gaus_pdf(value, mean, std)
return 1.0 - 2 * (pdf / lmax)
def _map_11(self, mean, std, value):
lmax = gaus_pdf(mean, mean, std)
pdf = gaus_pdf(value, mean, std)
return -1.0 + 2 * (pdf / lmax)
[docs]class LogisticMapper(Mapper):
"""Logistic function mapper.
The mapped value is relative to the logistic function's value in the
mapping point. Depending on the mode, some transformations (mirroring,
shifting), might be applied to the mapped value.
"""
_value_set = {int, float}
modes = ['10', '01', '1-1', '-11']
def __init__(self, x0, k, mode='01'):
"""
:param float x0: sigmoid's midpoint
:param float k: steepness of the curve
:param mode: mode of the mapper: '10', '01', '1-1' or '-11'.
"""
self._x0 = x0
self._k = k
self.mode = mode
self._mode_maps = {'10': self._map10, '01': self._map01,
'1-1': self._map1_1, '-11': self._map_11}
@property
def mode(self):
"""Mode of the mapper."""
return self._mode
@mode.setter
def mode(self, value):
if value not in self.modes:
raise ValueError('Value ({}) not found from modes.'.format(value))
self._mode = value
def __str__(self):
return "{}({}-{},{})".format(self.__class__.__name__, self._x0,
self._k, self._mode)
[docs] def map(self, value):
return self._mode_maps[self._mode](self._x0, self._k, value)
def _map10(self, x0, k, value):
diff = value - x0
mir_value = x0 - diff
return logistic(mir_value, x0, k, 1.0)
def _map01(self, x0, k, value):
return logistic(value, x0, k, 1.0)
def _map1_1(self, x0, k, value):
diff = value - x0
mir_value = x0 - diff
return logistic(mir_value, x0, k, 2.0) - 1.0
def _map_11(self, x0, k, value):
return logistic(value, x0, k, 2.0) - 1.0