class documentation

class BoxConstraintsLinQuadTransformation(BoxConstraintsTransformationBase):

View In Hierarchy

implement a bijective, monotonous transformation between [lb - al, ub + au] and [lb, ub].

Generally speaking, this transformation aims to resemble sin to be a continuous differentiable (ie. C^1) transformation over R into a bounded interval; then, it also aims to improve over sin in the following two ways: (i) resemble the identity over an interval as large possible while keeping the second derivative in reasonable limits, and (ii) numerical stability in "pathological" corner cases of the boundary limit values.

The transformation is the identity (and therefore linear) in [lb + al, ub - au] (typically about 90% of the interval) and quadratic in [lb - 3*al, lb + al] and in [ub - au, ub + 3*au]. The transformation is periodically expanded beyond the limits (somewhat resembling the shape sin(x-pi/2)) with a period of 2 * (ub - lb + al + au).

Details

Partly due to numerical considerations depend the values al and au on abs(lb) and abs(ub) which makes the transformation non-translation invariant. In particular, the linear proportion decreases to zero when ub-lb becomes small. In contrast to sin(.), the transformation is also robust to "arbitrary" large values for boundaries, e.g. a lower bound of -1e99 or upper bound of np.Inf or bound None.

Examples

Example to use with cma:

>>> import warnings
>>> import cma
>>> from cma.transformations import BoxConstraintsLinQuadTransformation
>>> # only the first variable has an upper bound
>>> tf = BoxConstraintsLinQuadTransformation([[1,2], [1,None]]) # second==last pair is re-cycled
>>> with warnings.catch_warnings(record=True) as warns:
...     x, es = cma.fmin2(cma.ff.elli, 9 * [2], 1,
...                 {'transformation': [tf.transform, tf.inverse],
...                  'verb_disp':0, 'tolflatfitness': 1e9, 'verbose': -2})
>>> not warns or str(warns[0].message).startswith(('in class GenoPheno: user defi',
...                                   'flat fitness'))
True

or:

>>> es = cma.CMAEvolutionStrategy(4 * [2], 1, {'verbose':0, 'verb_log':0})  # doctest: +ELLIPSIS
(4_w,8)-aCMA-ES (mu_w=...
>>> with warnings.catch_warnings(record=True) as warns:  # flat fitness warning, not necessary anymore
...     while not es.stop():
...         X = es.ask()
...         f = [cma.ff.elli(tf(x)) for x in X]  # tf(x)==tf.transform(x)
...         es.tell(X, f)
>>> assert 'tolflatfitness' in es.stop(), str(es.stop())

Example of the internal workings:

>>> from cma.transformations import BoxConstraintsLinQuadTransformation
>>> tf = BoxConstraintsLinQuadTransformation([[1,2], [1,11], [1,11]])
>>> tf.bounds
[[1, 2], [1, 11], [1, 11]]
>>> tf([1.5, 1.5, 1.5])
[1.5, 1.5, 1.5]
>>> list(np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9))
[1.52, 4.0, 2.0, 2.0, 4.0, 10.4]
>>> res = np.round(tf._au, 2)
>>> assert list(res[:4]) == [ 0.15, 0.6, 0.6, 0.6]
>>> res = [round(x, 2) for x in tf.shift_or_mirror_into_invertible_domain([1.52, -12.2, -0.2, 2, 4, 10.4])]
>>> assert res == [1.52, 9.2, 2.0, 2.0, 4.0, 10.4]
>>> tmp = tf([1])  # call with lower dimension
Method __call__ Undocumented
Method __init__ x is defined in [lb - 3*al, ub + au + r - 2*al] with r = ub - lb + al + au, and x == transformation(x) in [lb + al, ub - au].
Method idx_infeasible return indices of "infeasible" variables, that is, variables that do not directly map into the feasible domain such that tf.inverse(tf(x)) == x.
Method initialize see __init__
Method is_feasible_i return True if value x is in the invertible domain of variable i
Method is_loosely_feasible_i never used
Method shift_or_mirror_into_invertible_domain parameter solution_genotype is changed.
Method _inverse_i return inverse of y in component i
Method _shift_or_mirror_into_invertible_i shift into the invertible domain [lb - ab, ub + au], mirror close to boundaries in order to get a smooth transformation everywhere
Method _transform_i return transform of x in component i
Instance Variable _al Undocumented
Instance Variable _au Undocumented
Instance Variable _lb Undocumented
Instance Variable _ub Undocumented

Inherited from BoxConstraintsTransformationBase:

Method bounds_i return [ith_lower_bound, ith_upper_bound]
Method inverse Undocumented
Instance Variable bounds Undocumented
Method _index Undocumented
Method _lowerupperval Undocumented
def __call__(self, solution_genotype, copy=True):
def __init__(self, bounds):

x is defined in [lb - 3*al, ub + au + r - 2*al] with r = ub - lb + al + au, and x == transformation(x) in [lb + al, ub - au].

beta*x - alphal = beta*x - alphau is then defined in [lb, ub].

alphal and alphau represent the same value, but respectively numerically better suited for values close to lb and ub.

todo: revise this to be more comprehensible.

def idx_infeasible(self, solution_genotype):

return indices of "infeasible" variables, that is, variables that do not directly map into the feasible domain such that tf.inverse(tf(x)) == x.

def initialize(self, length=None):
def is_feasible_i(self, x, i):

return True if value x is in the invertible domain of variable i

def is_loosely_feasible_i(self, x, i):

never used

def shift_or_mirror_into_invertible_domain(self, solution_genotype, copy=False):

parameter solution_genotype is changed.

The domain is [lb - al, ub + au] and in [lb - 2*al - (ub - lb) / 2, lb - al] mirroring is applied.

def _inverse_i(self, y, i):

return inverse of y in component i

def _shift_or_mirror_into_invertible_i(self, x, i):

shift into the invertible domain [lb - ab, ub + au], mirror close to boundaries in order to get a smooth transformation everywhere

def _transform_i(self, x, i):

return transform of x in component i

_al =

Undocumented

_au =

Undocumented