Coverage for moptipy / algorithms / so / fitnesses / rank_and_iteration.py: 100%
25 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""
2A fitness combining the rank of a solution with the iteration of its creation.
4This fitness assignment process is compatible with the simple (mu+lambda)
5Evolutionary Algorithm implemented in :class:`~moptipy.algorithms.so.ea.EA`.
6It will assign a better fitness to a solution which has a better objective
7value. Ties will be broken based on the iteration counter
8:attr:`~moptipy.algorithms.so.record.Record.it` of the solution records
9:class:`~moptipy.algorithms.so.record.Record`.
10"""
12from math import inf
13from typing import Final
15from numpy.random import Generator
17from moptipy.algorithms.so.fitness import Fitness, FRecord
20# start book
21class RankAndIteration(Fitness):
22 """
23 A fitness joining objective rank and creation iteration.
25 The fitness assignment strategy will use two pieces of information to
26 determine the fitness of a solution:
28 1. the rank the solutions by their objective values
29 (:attr:`~moptipy.algorithms.so.record.Record.f`). If two solutions have
30 the same fitness, they get the same rank, but the next-worst solution
31 will then get a rank with is larger by 2.
32 2. the iteration index (:attr:`~moptipy.algorithms.so.record.Record.it`)
33 relative to the maximum and minimum iteration index.
35 It will multiply the rank of the solution with the range of the iteration
36 index in the population and then add the maximum iteration index minus the
37 iteration index of the solution, i.e.,
39 `fitness(x) = rank(f(x)) * (max_it - min_it + 1) + (max_it - it(x))`
41 This way, better solutions receive better fitness and ties are broken such
42 that younger solutions (with higher iteration index) are preferred.
43 In combination with best selection
44 (:class:`moptipy.algorithms.modules.selections.best.Best`),
45 this replicates the behavior of our simple (mu+lambda) Evolutionary
46 Algorithm (:class:`~moptipy.algorithms.so.ea.EA`).
47 """
49 def assign_fitness(self, p: list[FRecord], random: Generator) -> None:
50 """
51 Assign the rank and iteration fitness.
53 :param p: the list of records
54 :param random: ignored
55 """
56 min_it: int = 9_223_372_036_854_775_808 # minimum iteration index
57 max_it: int = -1 # the maximum iteration index
59 # In the first iteration, we assign objective value as fitness
60 # (for sorting) and get the bounds of the iteration indices.
61 for rec in p: # iterate over list p
62 rec.fitness = rec.f # set f as fitness for sorting
63 it: int = rec.it # get iteration index from record
64 min_it = min(min_it, it)
65 max_it = max(max_it, it)
66 p.sort() # sort based on objective values
68 it_range: Final[int] = max_it - min_it + 1 # range of it index
69 rank: int = -1 # the variable for storing the current rank
70 last_f: int | float = -inf # the previous objective value
71 for i, rec in enumerate(p): # iterate over list
72 v = rec.fitness # get the current objective value
73 if v > last_f: # only increase rank if objective f changes
74 rank = i + 1 # +1 so smallest-possible fitness is 1
75 last_f = v # remember objective value for comparison
76 rec.fitness = (rank * it_range) + max_it - rec.it
77# end book
79 def __str__(self):
80 """
81 Get the name of this fitness assignment.
83 :return: the name of this fitness assignment strategy
84 :retval "rankAndIt": always
85 """
86 return "rankAndIt"