Termination Criterion

Whenever an algorithm is executed, it needs to be decided in each iteration whether the optimization run shall be continued or not. Many different ways exist of how to determine when a run of an algorithm should be terminated. Next, termination criteria specifically developed for single or multi-objective optimization as well as more generalized, for instance, limiting the number of iterations of an algorithm, are described

Tip

The termination of your optimization procedure is important. Running the algorithm not long enough can lead to unsatisfactory results; however, running it too long might waste function evaluations and thus computational resources.

Default Termination (‘default’)

We have added recently developed a termination criterion set if no termination is supplied to the minimize() method:

[1]:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.optimize import minimize

problem = get_problem("zdt1")
algorithm = NSGA2(pop_size=100)

res = minimize(problem,
               algorithm,
               seed=1)

print(res.algorithm.n_gen)
404

This allows you to terminated based on a couple of criteria also explained later on this page. Commonly used are the movement in the design space f_tol and the convergence in the constraint cv_tol and objective space f_tol. To provide an upper bound for the algorithm, we recommend supplying a maximum number of generations n_max_gen or function evaluations n_max_evals.

Moreover, it is worth mentioning that tolerance termination is based on a sliding window. Not only the last, but a sequence of the period generations are used to calculate compare the tolerances with an bound defined by the user.

By default for multi-objective problems, the termination will be set to

[2]:
from pymoo.termination.default import DefaultMultiObjectiveTermination

termination = DefaultMultiObjectiveTermination(
    xtol=1e-8,
    cvtol=1e-6,
    ftol=0.0025,
    period=30,
    n_max_gen=1000,
    n_max_evals=100000
)

And for single-optimization to

[3]:
from pymoo.termination.default import DefaultSingleObjectiveTermination

termination = DefaultSingleObjectiveTermination(
    xtol=1e-8,
    cvtol=1e-6,
    ftol=1e-6,
    period=20,
    n_max_gen=1000,
    n_max_evals=100000
)
.. _nb_n_eval:

Number of Evaluations (‘n_eval’)

The termination can simply be reached by providing an upper bound for the number of function evaluations. Whenever in an iteration, the number of function evaluations is greater than this upper bound the algorithm terminates.

[4]:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.termination import get_termination
from pymoo.optimize import minimize

problem = get_problem("zdt3")
algorithm = NSGA2(pop_size=100)
termination = get_termination("n_eval", 300)

res = minimize(problem,
               algorithm,
               termination,
               pf=problem.pareto_front(),
               seed=1,
               verbose=True)
==========================================================================
n_gen  |  n_eval  | n_nds  |      igd      |       gd      |       hv
==========================================================================
     1 |      100 |     15 |  1.3046483561 |  1.5301910684 |  0.000000E+00
     2 |      200 |     14 |  1.1732282301 |  1.5839299556 |  0.000000E+00
     3 |      300 |      8 |  0.7682114387 |  1.4199468654 |  0.0019023847
.. _nb_n_gen:

Number of Generations (‘n_gen’)

Moreover, the number of generations / iterations can be limited as well.

[5]:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.termination import get_termination
from pymoo.optimize import minimize

problem = get_problem("zdt3")
algorithm = NSGA2(pop_size=100)
termination = get_termination("n_gen", 10)

res = minimize(problem,
               algorithm,
               termination,
               pf=problem.pareto_front(),
               seed=1,
               verbose=True)
==========================================================================
n_gen  |  n_eval  | n_nds  |      igd      |       gd      |       hv
==========================================================================
     1 |      100 |     15 |  1.3046483561 |  1.5301910684 |  0.000000E+00
     2 |      200 |     14 |  1.1732282301 |  1.5839299556 |  0.000000E+00
     3 |      300 |      8 |  0.7682114387 |  1.4199468654 |  0.0019023847
     4 |      400 |     11 |  0.7014058244 |  1.1652929281 |  0.0031340151
     5 |      500 |     10 |  0.5994618317 |  1.0149545681 |  0.0049391590
     6 |      600 |     10 |  0.5345265548 |  0.6999861453 |  0.0058738016
     7 |      700 |     13 |  0.5121888499 |  0.6197808588 |  0.0058738016
     8 |      800 |     20 |  0.4374300453 |  0.5764126457 |  0.0135488174
     9 |      900 |     18 |  0.3588517253 |  0.4648948899 |  0.0725713269
    10 |     1000 |     19 |  0.3513252016 |  0.4759517897 |  0.0853619770
.. _nb_time:

Based on Time (‘time’)

The termination can also be based on the time of the algorithm to be executed. For instance, to run an algorithm for 3 seconds the termination can be defined by get_termination("time", "00:00:03") or for 1 hour and 30 minutes by get_termination("time", "01:30:00").

[6]:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.termination import get_termination
from pymoo.optimize import minimize

problem = get_problem("zdt3")
algorithm = NSGA2(pop_size=100)
termination = get_termination("time", "00:00:03")

res = minimize(problem,
               algorithm,
               termination,
               pf=problem.pareto_front(),
               seed=1,
               verbose=False)

print(res.algorithm.n_gen)

349
.. _nb_xtol:

Design Space Tolerance (‘xtol’)

Also, we can track the change in the design space. For a parameter explanation, please have a look at ‘ftol’.

[7]:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.optimize import minimize
from pymoo.termination.xtol import DesignSpaceTermination
from pymoo.termination.robust import RobustTermination

problem = get_problem("zdt3")
algorithm = NSGA2(pop_size=100)
termination = RobustTermination(DesignSpaceTermination(tol=0.01), period=20)

res = minimize(problem,
               algorithm,
               termination,
               pf=problem.pareto_front(),
               seed=1,
               verbose=False)

print(res.algorithm.n_gen)

103
.. _nb_ftol:

Objective Space Tolerance (‘ftol’)

The most interesting stopping criterion is to use objective space change to decide whether to terminate the algorithm. Here, we mostly use a simple and efficient procedure to determine whether to stop or not. We aim to improve it further in the future. If somebody is interested in collaborating, please let us know.

The parameters of our implementation are:

tol: What is the tolerance in the objective space on average. If the value is below this bound, we terminate.

n_last: To make the criterion more robust, we consider the last \(n\) generations and take the maximum. This considers the worst case in a window.

n_max_gen: As a fallback, the generation number can be used. For some problems, the termination criterion might not be reached; however, an upper bound for generations can be defined to stop in that case.

nth_gen: Defines whenever the termination criterion is calculated by default, every 10th generation.

[8]:
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.optimize import minimize
from pymoo.termination.ftol import MultiObjectiveSpaceTermination
from pymoo.visualization.scatter import Scatter

problem = get_problem("zdt3")

algorithm = NSGA2(pop_size=100)

termination = RobustTermination(
    MultiObjectiveSpaceTermination(tol=0.005, n_skip=5), period=20)


res = minimize(problem,
               algorithm,
               termination,
               pf=True,
               seed=1,
               verbose=False)

print("Generations", res.algorithm.n_gen)
plot = Scatter(title="ZDT3")
plot.add(problem.pareto_front(use_cache=False, flatten=False), plot_type="line", color="black")
plot.add(res.F, facecolor="none", edgecolor="red", alpha=0.8, s=20)
plot.show()

Generations 93
[8]:
<pymoo.visualization.scatter.Scatter at 0x7f7e76d7f520>
../_images/interface_termination_30_2.png