Source code for moptipy.algorithms.so.fitnesses.rank_and_iteration

"""
A fitness combining the rank of a solution with the iteration of its creation.

This fitness assignment process is compatible with the simple (mu+lambda)
Evolutionary Algorithm implemented in :class:`~moptipy.algorithms.so.ea.EA`.
It will assign a better fitness to a solution which has a better objective
value. Ties will be broken based on the iteration counter
:attr:`~moptipy.algorithms.so.record.Record.it` of the solution records
:class:`~moptipy.algorithms.so.record.Record`.
"""

from math import inf
from typing import Final

from numpy.random import Generator

from moptipy.algorithms.so.fitness import Fitness, FRecord


# start book
[docs] class RankAndIteration(Fitness): """ A fitness joining objective rank and creation iteration. The fitness assignment strategy will use two pieces of information to determine the fitness of a solution: 1. the rank the solutions by their objective values (:attr:`~moptipy.algorithms.so.record.Record.f`). If two solutions have the same fitness, they get the same rank, but the next-worst solution will then get a rank with is larger by 2. 2. the iteration index (:attr:`~moptipy.algorithms.so.record.Record.it`) relative to the maximum and minimum iteration index. It will multiply the rank of the solution with the range of the iteration index in the population and then add the maximum iteration index minus the iteration index of the solution, i.e., `fitness(x) = rank(f(x)) * (max_it - min_it + 1) + (max_it - it(x))` This way, better solutions receive better fitness and ties are broken such that younger solutions (with higher iteration index) are preferred. In combination with best selection (:class:`moptipy.algorithms.modules.selections.best.Best`), this replicates the behavior of our simple (mu+lambda) Evolutionary Algorithm (:class:`~moptipy.algorithms.so.ea.EA`). """
[docs] def assign_fitness(self, p: list[FRecord], random: Generator) -> None: """ Assign the rank and iteration fitness. :param p: the list of records :param random: ignored """ min_it: int = 9_223_372_036_854_775_808 # minimum iteration index max_it: int = -1 # the maximum iteration index # In the first iteration, we assign objective value as fitness # (for sorting) and get the bounds of the iteration indices. for rec in p: # iterate over list p rec.fitness = rec.f # set f as fitness for sorting it: int = rec.it # get iteration index from record min_it = min(min_it, it) max_it = max(max_it, it) p.sort() # sort based on objective values it_range: Final[int] = max_it - min_it + 1 # range of it index rank: int = -1 # the variable for storing the current rank last_f: int | float = -inf # the previous objective value for i, rec in enumerate(p): # iterate over list v = rec.fitness # get the current objective value if v > last_f: # only increase rank if objective f changes rank = i + 1 # +1 so smallest-possible fitness is 1 last_f = v # remember objective value for comparison rec.fitness = (rank * it_range) + max_it - rec.it
# end book def __str__(self): """ Get the name of this fitness assignment. :return: the name of this fitness assignment strategy :retval "rankAndIt": always """ return "rankAndIt"