class documentation

implement a periodic transformation that is bijective from

[lb - al, ub + au] -> [lb, ub], where either al = min((ub-lb) / 2, 0.05 * (|lb| + 1)) and ul = min((ub-lb) / 2, 0.05 * (|ub| + 1)) or (default) al = min((ub-lb) / 2, 0.05 * max(|lb|, 1)) and ul = min((ub-lb) / 2, 0.05 * max(|ub|, 1)) depending on the method cma.transformations.linquad_margin_width assigned as margin_width1 or margin_width2.

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 resembles the shape sin(2*pi * x / a - pi/2)) with a period length of a = 2 * ((ub + au) - (lb - al)) = 2 * (ub - lb + al + au).

The transformation is the identity in [lb + al, ub - au] (typically about 90% of the interval) and it is quadratic to the left of lb + al down to lb - 3*al and to the right of ub - au up to ub + 3*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. When ub-lb is small compared to min(|lb|, |ub|), the linear proportion becomes zero.

In contrast to sin(.), the transformation is 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:

>>> import cma.transformations as ts
>>> ts.linquad_margin_width = ts.margin_width1
>>> tf = ts.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]
>>> np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9).tolist()
[1.52, 4.0, 2.0, 2.0, 4.0, 10.4]
>>> res = np.round(tf._au, 2)
>>> assert res[:4].tolist() == [ 0.15, 0.6, 0.6, 0.6], res[:4].tolist()
>>> 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], res
>>> tmp = tf([1])  # call with lower dimension
>>> ts.linquad_margin_width = ts.margin_width2
>>> tf = ts.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]
>>> np.round(tf([1.52, -2.2, -0.2, 2, 4, 10.4]), 9).tolist()
[1.52, 4.1, 2.1, 2.0, 4.0, 10.4]
>>> res = np.round(tf._au, 2)
>>> assert list(res[:4]) == [ 0.1, 0.55, 0.55, 0.55], list(res[:4])
>>> 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.0, 2.1, 2.0, 4.0, 10.4], res
>>> tmp = tf([1])  # call with lower dimension
>>> for i in range(5):
...     lb = np.random.randn(4) - 1000 * i * np.random.rand()
...     ub = lb + (1e-7 + np.random.rand(4)) / (1e-9 + np.random.rand(4)) + 1001 * i * np.random.rand()
...     lb[-1], ub[-1] = lb[-2], ub[-2]
...     b = ts.BoxConstraintsLinQuadTransformation([[l, u] for (l, u) in zip(lb[:3], ub[:3])])
...     for x in [(ub - lb) * np.random.randn(4) / np.sqrt(np.random.rand(4)) for _ in range(11)]:
...         assert all(lb <= b.transform(x)), (lb, ub, b.transform(x), b.__dict__)
...         assert all(b.transform(x) <= ub), (lb, ub, b.transform(x), b.__dict__)
...         assert all(b.transform(lb - b._al) == lb), (lb, ub, b.transform(x), b.__dict__)
...         assert all(b.transform(ub + b._au) == ub), (lb, ub, b.transform(x), b.__dict__)
Method __call__ Undocumented
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 __init__ Undocumented
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 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