class documentation

`class ConstrainedFitnessAL:`

Construct an unconstrained objective function from constraints.

This class constructs an unconstrained "fitness" function (to be
minimized) from an objective function and an inequality constraints
function (which returns a list of constraint values). An
equality constraint `h(x) == 0` must be expressed as two inequality
constraints like `[h(x) - eps, -h(x) - eps]` with `eps >= 0`.
Non-positive values <= 0 are considered feasible.

The `update`

method of the class instance needs to be called after each
iteration. Depending on the setting of `which`

, `update`

may call
`get_solution(es)` which shall return the solution to be used for the
constraints handling update, by default `_get_favorite_solution ==
lambda es: es.ask(1, sigma_fac=0)[0]`. The additional evaluation of
objective and constraints is avoided by the default `which='best'`,
using the best solution in the current iteration.

`find_feasible_first`

optimizes to get a feasible solution first, which
only works if no equality constraints are implemented. For this reason
the default is `False`

.

Minimal example (verbosity set for doctesting):

>>> import cma >>> def constraints(x): # define the constraint ... return [x[0] + 1, x[1]] # shall be <= 0 >>> cfun = cma.ConstrainedFitnessAL(cma.ff.sphere, constraints, ... find_feasible_first=True) >>> es = cma.CMAEvolutionStrategy(3 * [1.1], 0.1, ... {'tolstagnation': 0, 'verbose':-9}) # verbosity for doctest only >>> es = es.optimize(cfun, callback=cfun.update) >>> x = es.result.xfavorite

The best `x`

return value of `cma.fmin2`

may not be useful, because
the underlying function changes over time. Therefore, we use
`es.result.xfavorite`

, which is still not guarantied to be a feasible
solution. Alternatively, `cfun.best_feas.x`

contains the best evaluated
feasible solution. However, this is not necessarily expected to be a
good solution, see below.

>>> assert sum((x - [-1, 0, 0])**2) < 1e-9, x >>> assert es.countevals < 2200, es.countevals >>> assert cfun.best_feas.f < 10, str(cfun.best_feas) >>> # print(es.countevals, cfun.best_feas.__dict__)

To find a final feasible solution (close to `es.result.xfavorite`

) we
can use the current `CMAEvolutionStrategy`

instance `es`

:

>>> x = cfun.find_feasible(es) # uses es.optimize to find (another) feasible solution >>> assert constraints(x)[0] <= 0, (x, cfun.best_feas.x) >>> assert cfun.best_feas.f < 1 + 2e-6, str(cfun.best_feas) >>> assert len(cfun.archives) == 3 >>> assert cma.ConstrainedFitnessAL(cma.ff.sphere, constraints, archives=False).archives == []

Details: The fitness, to be minimized, is changing over time such that the overall minimal value does not indicate the best solution.

The construction is based on the `AugmentedLagrangian`

class. If, as by
default, `self.finding_feasible is False`, the fitness equals `f(x)
+ sum_i (lam_i x g_i + mu_i x g_i / 2)` where `g_i = max(g_i(x),
-lam_i / mu_i)` and lam_i and mu_i are generally positive and
dynamically adapted coefficients. Only lam_i can change the position of
the optimum in the feasible domain (and hence must converge to the
right value).

When `self.finding_feasible is True`, the fitness equals to `sum_i
(g_i > 0) x g_i^2` and omits `f + sum_i lam_i g_i` altogether.
Whenever a feasible solution is found, the `finding_feasible`

flag is
reset to `False`

.

`find_feasible(es)`

sets `finding_feasible = True` and uses
`es.optimize`

to optimize `self.__call__`

. This works well with
`CMAEvolutionStrategy`

but may easily fail with solvers that do not
consistently pass over the optimum in search space but approach the
optimum from one side only. This is not advisable if the feasible
domain has zero volume, e.g. when `g`

models an equality like
`g = lambda x: [h(x), -h(x)]`.

An equality constraint, h(x) = 0, cannot be handled like h**2 <= 0,
because the Augmented Lagrangian requires the derivative at h == 0 to
be non-zero. Using abs(h) <= 0 leads to divergence of coefficient mu
and the condition number. The best way is apparently using the two
inequality constraints [h <= 0, -h <= 0], which seems to work perfectly
well. The underlying `AugmentedLagrangian`

class also accepts equality
constraints.

Method | `__call__` |
return AL fitness, append f and g values to `self.F` and `self.G` . |

Method | `__init__` |
constructor with lazy initialization. |

Method | `find_feasible` |
find feasible solution by calling es.optimize(self). |

Method | `initialize` |
set search space dimension explicitely |

Method | `log_in_es` |
a hack to have something in the cma-logger divers plot. |

Method | `reset` |
reset dynamic components |

Method | `set_mu_lam` |
set AL coefficients |

Method | `update` |
update AL coefficients, may be used as callback to `OOOptimizer.optimize` . |

Class Variable | `archive_aggregators` |
Undocumented |

Instance Variable | `archives` |
Undocumented |

Instance Variable | `best_aug` |
Undocumented |

Instance Variable | `best_f_plus_gpos` |
Undocumented |

Instance Variable | `best_feas` |
Undocumented |

Instance Variable | `constraints` |
Undocumented |

Instance Variable | `count_calls` |
Undocumented |

Instance Variable | `count_updates` |
Undocumented |

Instance Variable | `dimension` |
Undocumented |

Instance Variable | `F` |
Undocumented |

Instance Variable | `F_plus_sum_al_G` |
Undocumented |

Instance Variable | `find_feasible_aggregator` |
Undocumented |

Instance Variable | `finding_feasible` |
Undocumented |

Instance Variable | `foffset` |
Undocumented |

Instance Variable | `fun` |
Undocumented |

Instance Variable | `G` |
Undocumented |

Instance Variable | `get_solution` |
Undocumented |

Instance Variable | `logging` |
Undocumented |

Instance Variable | `omit_f_calls_when_possible` |
Undocumented |

Instance Variable | `which` |
Undocumented |

Property | `al` |
`AugmentedLagrangian` class instance |

Method | `_fg_values` |
f, g values used to update the Augmented Lagrangian coefficients |

Method | `_is_feasible` |
return True if last evaluated solution (or `gvals` ) was feasible |

Method | `_reset` |
Undocumented |

Method | `_reset_archives` |
Undocumented |

Method | `_reset_arrays` |
Undocumented |

Method | `_update_best` |
keep track of best solution and best feasible solution |

Instance Variable | `_al` |
Undocumented |

Instance Variable | `_set_coefficient_counts` |
Undocumented |

Property | `_best_fg` |
return current best f, g, where best is determined by the Augmented Lagrangian |

def __call__(self, x):

return AL fitness, append f and g values to `self.F`

and `self.G`

.

If `self.finding_feasible`

, `fun(x)`

is not called and `f = np.nan`.

def __init__(self, fun, constraints, dimension=None, which='best', find_feasible_first=False, get_solution=_get_favorite_solution, logging=None, archives=archive_aggregators):

constructor with lazy initialization.

If `which in ['mean', 'solution']`, `get_solution`

is called
(with the argument passed to the `update`

method) to determine the
solution used to update the AL coefficients.

If `find_feasible_first`

, only the constraints are optimized until
the first (fully) feasible solution is found.

`logging`

is the iteration gap for logging constraints related
data, in `AugmentedLagrangian`

. 0 means no logging and negative
values have unspecified behavior.

`archives`

are the aggregator functions for constraints for
non-dominated biobjective archives. By default, the second
objective is `max(g_+)`, `sum(g_+)` or `sum(g_+^2)`,
respectively. `archives=True` invokes the same behavior.
`archives=False` or an empty `tupel`

prevents maintaining
archives.

def find_feasible(self, es, termination=('maxiter', 'maxfevals'), aggregator=None):

find feasible solution by calling `es.optimize(self)`.

Return best ever feasible solution `self.best_feas.x`

.
See also `self.best_feas.info`

.

`aggregator`

, defaulting to `self.find_feasible_aggregator`

, is the
constraints aggregation function used as objective function to be
minimized. `aggregator`

takes as input all constraint values and
returns a value <= 0 if and only if the solution is feasible.

Terminate when either (another) feasible solution was found or any
of the `termination`

keys is matched in `es.stop()`

.

def log_in_es(self, es, f, g):

a hack to have something in the cma-logger divers plot.

Append the sum of positive g-values and the number of infeasible
constraints, displayed like 10**(number/10) (mapping [0, 10] to [1,
10]) if number < 10, to `es.more_to_write`

.

def update(self, es):

update AL coefficients, may be used as callback to `OOOptimizer.optimize`

.

- TODO: decide what happens when
`__call__`

was never called: - ignore (as for now) or update based on xfavorite by calling self(xfavorite), assuming that update was called on purpose? When method is not best, it should work without even call self(xfavorite).