Coverage for /builds/ase/ase/ase/ga/convergence.py: 26.42%

53 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3"""Classes that determine convergence of an algorithm run 

4based on population stagnation or max raw score reached""" 

5from ase.ga import get_raw_score 

6 

7 

8class Convergence: 

9 """ 

10 Base class for all convergence object to be based on. 

11 It is necessary to supply the population instance, to be 

12 able to obtain current and former populations. 

13 """ 

14 

15 def __init__(self, population_instance): 

16 self.pop = population_instance 

17 self.pops = {} 

18 

19 def converged(self): 

20 """This function is called to find out if the algorithm 

21 run has converged, it should return True or False. 

22 Overwrite this in the inherited class.""" 

23 raise NotImplementedError 

24 

25 def populate_pops(self, to_gen): 

26 """Populate the pops dictionary with how the population 

27 looked after i number of generations.""" 

28 for i in range(to_gen): 

29 if i not in self.pops.keys(): 

30 self.pops[i] = self.pop.get_population_after_generation(i) 

31 

32 

33class GenerationRepetitionConvergence(Convergence): 

34 """Returns True if the latest finished population is stagnated for 

35 number_of_generations. 

36 

37 Parameters: 

38 

39 number_of_generations: int 

40 How many generations need to be equal before convergence. 

41 

42 number_of_individuals: int 

43 How many of the fittest individuals should be included in the 

44 convergence test. Default is -1 meaning all in the population. 

45 

46 max_generations: int 

47 The maximum number of generations the GA is allowed to run. 

48 Default is indefinite. 

49 """ 

50 

51 def __init__(self, population_instance, number_of_generations, 

52 number_of_individuals=-1, max_generations=100000000): 

53 Convergence.__init__(self, population_instance) 

54 self.numgens = number_of_generations 

55 self.numindis = number_of_individuals 

56 self.maxgen = max_generations 

57 

58 def converged(self): 

59 size = self.pop.pop_size 

60 cur_gen_num = self.pop.dc.get_generation_number(size) 

61 

62 if cur_gen_num >= self.maxgen: 

63 return True 

64 

65 if cur_gen_num <= 1: 

66 return False 

67 

68 cur_pop = self.pop.get_current_population() 

69 newest = max( 

70 i.info['key_value_pairs']['generation'] 

71 for i in cur_pop[: self.numindis] 

72 ) 

73 if newest + self.numgens > cur_gen_num: 

74 return False 

75 

76 self.populate_pops(cur_gen_num) 

77 

78 duplicate_gens = 1 

79 latest_pop = self.pops[cur_gen_num - 1] 

80 for i in range(cur_gen_num - 2, -1, -1): 

81 test_pop = self.pops[i] 

82 if test_pop[:self.numindis] == latest_pop[:self.numindis]: 

83 duplicate_gens += 1 

84 if duplicate_gens >= self.numgens: 

85 return True 

86 return False 

87 

88 

89class RawScoreConvergence(Convergence): 

90 """Returns True if the supplied max_raw_score has been reached""" 

91 

92 def __init__(self, population_instance, max_raw_score, eps=1e-3): 

93 Convergence.__init__(self, population_instance) 

94 self.max_raw_score = max_raw_score 

95 self.eps = eps 

96 

97 def converged(self): 

98 cur_pop = self.pop.get_current_population() 

99 if abs(get_raw_score(cur_pop[0]) - self.max_raw_score) <= self.eps: 

100 return True 

101 return False 

102 

103 

104class NeverConvergence: 

105 """Test class that never converges.""" 

106 

107 def __init__(self): 

108 pass 

109 

110 def converged(self): 

111 return False