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"