Coverage for moptipy / tests / fitness.py: 90%
87 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"""Test fitness assignment processes."""
4from math import inf, isfinite
5from typing import Final, cast
7from numpy.random import Generator, default_rng
8from pycommons.types import type_error
10from moptipy.algorithms.so.fitness import Fitness, FRecord, check_fitness
11from moptipy.api.objective import Objective
12from moptipy.api.operators import Op0
13from moptipy.api.space import Space
14from moptipy.tests.component import validate_component
15from moptipy.utils.nputils import rand_seed_generate
18class _FRecord(FRecord):
19 """The internal F-record."""
21 def __init__(self, x, z):
22 """Initialize."""
23 super().__init__(x, inf)
24 #: the internal id
25 self.z: Final[int] = z
28def validate_fitness(fitness: Fitness, objective: Objective, space: Space,
29 op0: Op0) -> None:
30 """
31 Validate a fitness assignment process on a given problem.
33 :param fitness: the fitness assignment process
34 :param objective: the objective function
35 :param space: the space of solutions
36 :param op0: the nullary operator
37 """
38 if not isinstance(fitness, Fitness):
39 raise type_error(fitness, "fitness", Fitness)
40 check_fitness(fitness)
41 validate_component(fitness)
43 random: Final[Generator] = default_rng()
44 pop1: Final[list[_FRecord]] = []
45 pop2: Final[list[_FRecord]] = []
46 pop3: Final[list[_FRecord]] = []
47 ps: Final[int] = int(1 + random.integers(48))
48 for i in range(ps):
49 fr: _FRecord = _FRecord(space.create(), i)
50 op0.op0(random, fr.x)
51 fr.f = objective.evaluate(fr.x)
52 fr.it = int(random.integers(1, 20))
53 if fr.fitness != inf:
54 raise ValueError(f"v = {fr.fitness}, should be inf")
55 pop1.append(fr)
56 fr2: _FRecord = _FRecord(fr.x, fr.z)
57 fr2.f = fr.f
58 fr2.it = fr.it
59 pop2.append(fr2)
60 fr2 = _FRecord(fr.x, fr.z)
61 fr2.f = fr.f
62 fr2.it = fr.it
63 pop3.append(fr2)
65 for k in range(6):
66 if k >= 3:
67 # make all records identical
68 fr0 = pop1[0]
69 for i in range(ps):
70 fr = _FRecord(fr0.x, i)
71 fr.f = fr0.f
72 fr.it = fr0.it
73 fr.fitness = fr0.fitness
74 pop1[i] = fr
75 fr = _FRecord(fr0.x, i)
76 fr.f = fr0.f
77 fr.it = fr0.it
78 fr.fitness = fr0.fitness
79 pop2[i] = fr
80 fr = _FRecord(fr0.x, i)
81 fr.f = fr0.f
82 fr.it = fr0.it
83 fr.fitness = fr0.fitness
84 pop3[i] = fr
85 if k in {2, 4}:
86 for fr in pop1:
87 if random.integers(2) <= 0:
88 fr.fitness = inf if random.integers(2) <= 0 else -inf
90 seed: int = rand_seed_generate()
91 fitness.initialize()
92 fitness.assign_fitness(cast("list[FRecord]", pop1), default_rng(seed))
93 fitness.initialize()
94 fitness.assign_fitness(cast("list[FRecord]", pop3), default_rng(seed))
95 pop1.sort(key=lambda r: r.z)
96 pop3.sort(key=lambda r: r.z)
98 for i, fr in enumerate(pop1):
99 if not isinstance(fr, _FRecord):
100 raise type_error(fr, f"pop[{i}]", _FRecord)
101 fr2 = pop2[i]
102 if fr.x is not fr2.x:
103 raise ValueError("fitness assignment changed x reference!")
104 if fr.it is not fr2.it:
105 raise ValueError(
106 f"fitness assignment assigned rec.it to {fr.it}!")
107 if fr.f is not fr2.f:
108 raise ValueError(
109 f"fitness assignment assigned rec.f to {fr.f}!")
110 if not isinstance(fr.fitness, int | float):
111 raise type_error(fr.fitness, "rec.fitness", (int, float))
112 if not isfinite(fr.fitness):
113 raise ValueError(
114 f"rec.fitness should be finite, but is {fr.fitness}")
115 fr2 = pop3[i]
116 if (fr2.fitness != fr.fitness) or (fr2.f is not fr.f) or \
117 (fr2.it is not fr.it) or (fr2.x is not fr.x):
118 raise ValueError(f"inconsistency detected when repeating "
119 f"fitness assignment: {str(fr2)!r} != "
120 f"{str(fr)!r} at index {i} of population "
121 f"of length {len(pop1)}.")