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

1"""Test fitness assignment processes.""" 

2 

3 

4from math import inf, isfinite 

5from typing import Final, cast 

6 

7from numpy.random import Generator, default_rng 

8from pycommons.types import type_error 

9 

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 

16 

17 

18class _FRecord(FRecord): 

19 """The internal F-record.""" 

20 

21 def __init__(self, x, z): 

22 """Initialize.""" 

23 super().__init__(x, inf) 

24 #: the internal id 

25 self.z: Final[int] = z 

26 

27 

28def validate_fitness(fitness: Fitness, objective: Objective, space: Space, 

29 op0: Op0) -> None: 

30 """ 

31 Validate a fitness assignment process on a given problem. 

32 

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) 

42 

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) 

64 

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 

89 

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) 

97 

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)}.")