facebookresearch / nevergrad

A Python toolbox for performing gradient-free optimization
https://facebookresearch.github.io/nevergrad/
MIT License
3.9k stars 350 forks source link

Constrained Optimization - penalty within tell method #1510

Closed GMbeltrami-Cosmo closed 10 months ago

GMbeltrami-Cosmo commented 1 year ago

I have run some tests and as noted in a previous issue, sometimes the constraint is not taken into consideration in a optimization problem, but with my tests I think i have found a quick hotfix for the problem.

https://drive.google.com/file/d/1tC47DbiA1qYN2m5bZyR_flvflat4Knrc/view?usp=share_link

In this link I show four plots with the same function. The left plots show the result the optimizer gives considering the function (the center is the ideal point except when constrained, which was the case in both simulations). The upper plots show the solution with the modification i am about to discuss and the down plots show the original nevergrad code.

It seems evident for me that the modification has some positive effects on the result given.

For clarification: function minimized: x1^2+x2^2 constraint applied: max(0, 2-x1) penalty style = default [1e5, 1, 0.5, 1, 0.5, 1] algorithms tested: ["NGO", "NGOpt", "NGOptBase", "NGOpt4", "NGOpt8", "NGOpt10",
"NGOpt15", "NGOpt16", "NGOpt36", "NGOpt39", 'AvgMetaRecenteringNoHull', 'HullAvgMetaRecentering', 'HullAvgMetaTuneRecentering', 'MetaRecentering', 'MetaTuneRecentering', "CMandAS2", "DiscreteDoerrOnePlusOne", "ChoiceBase", "CmaFmin2"]

The solution: Go to nevergrad/optimzation/base.py In line the tell method definition, change the lines:

if isinstance(loss, float) and ( self.num_objectives == 1 or self.num_objectives > 1 and not self._no_hypervolume ): self._update_archive_and_bests(candidate, loss)

    if constraint_violation is not None:
        if penalty_style is not None:
            a, b, c, d, e, f = penalty_style
        else:
            a, b, c, d, e, f = (1e5, 1.0, 0.5, 1.0, 0.5, 1.0)

        violation = float(
            (a + np.sum(np.maximum(loss, 0.0)))
            * ((f + self._num_tell) ** e)
            * (b * np.sum(np.maximum(constraint_violation, 0.0) ** c) ** d)
        )
        loss += violation

to:

    if constraint_violation is not None:
        if penalty_style is not None:
            a, b, c, d, e, f = penalty_style
        else:
            a, b, c, d, e, f = (1e5, 1.0, 0.5, 1.0, 0.5, 1.0)

        violation = float(
            (a + np.sum(np.maximum(loss, 0.0)))
            * ((f + self._num_tell) ** e)
            * (b * np.sum(np.maximum(constraint_violation, 0.0) ** c) ** d)
        )
        loss += violation

    if isinstance(loss, float) and (
        self.num_objectives == 1 or self.num_objectives > 1 and not self._no_hypervolume
    ):
        self._update_archive_and_bests(candidate, loss)

basically inverting the order of these operations seems to resolve the problem without damaging other functionalities.

teytaud commented 10 months ago

Pull request for this just now. Makes sense.

teytaud commented 10 months ago

0.13.0 merged. Issue presumably solved.