class BoxConstraintsLinQuadTransformation(BoxConstraintsTransformationBase):
Constructor: BoxConstraintsLinQuadTransformation(bounds)
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 |
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 |
return True if value x is in the invertible domain of variable i |
Method | is |
never used |
Method | shift |
parameter solution_genotype is changed. |
Method | _inverse |
return inverse of y in component i |
Method | _shift |
shift into the invertible domain [lb - ab, ub + au], mirror close to boundaries in order to get a smooth transformation everywhere |
Method | _transform |
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 |
return [ith_lower_bound, ith_upper_bound] |
Method | inverse |
Undocumented |
Instance Variable | bounds |
Undocumented |
Method | _index |
Undocumented |
Method | _lowerupperval |
Undocumented |
return indices of "infeasible" variables, that is, variables that do not directly map into the feasible domain such that tf.inverse(tf(x)) == x.
cma.transformations.BoxConstraintsTransformationBase.shift_or_mirror_into_invertible_domain
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.
shift into the invertible domain [lb - ab, ub + au], mirror close to boundaries in order to get a smooth transformation everywhere