Coverage for /builds/ase/ase/ase/ga/offspring_creator.py: 81.97%

61 statements  

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

1# fmt: off 

2 

3"""Base module for all operators that create offspring.""" 

4import numpy as np 

5 

6from ase import Atoms 

7 

8 

9class OffspringCreator: 

10 """Base class for all procreation operators 

11 

12 Parameters: 

13 

14 verbose: Be verbose and print some stuff 

15 

16 rng: Random number generator 

17 By default numpy.random. 

18 """ 

19 

20 def __init__(self, verbose=False, num_muts=1, rng=np.random): 

21 self.descriptor = 'OffspringCreator' 

22 self.verbose = verbose 

23 self.min_inputs = 0 

24 self.num_muts = num_muts 

25 self.rng = rng 

26 

27 def get_min_inputs(self): 

28 """Returns the number of inputs required for a mutation, 

29 this is to know how many candidates should be selected 

30 from the population.""" 

31 return self.min_inputs 

32 

33 def get_new_individual(self, parents): 

34 """Function that returns a new individual. 

35 Overwrite in subclass.""" 

36 raise NotImplementedError 

37 

38 def finalize_individual(self, indi): 

39 """Call this function just before returning the new individual""" 

40 indi.info['key_value_pairs']['origin'] = self.descriptor 

41 

42 return indi 

43 

44 @classmethod 

45 def initialize_individual(cls, parent, indi=None): 

46 """Initializes a new individual that inherits some parameters 

47 from the parent, and initializes the info dictionary. 

48 If the new individual already has more structure it can be 

49 supplied in the parameter indi.""" 

50 if indi is None: 

51 indi = Atoms(pbc=parent.get_pbc(), cell=parent.get_cell()) 

52 else: 

53 indi = indi.copy() 

54 # key_value_pairs for numbers and strings 

55 indi.info['key_value_pairs'] = {'extinct': 0} 

56 # data for lists and the like 

57 indi.info['data'] = {} 

58 

59 return indi 

60 

61 

62class OperationSelector: 

63 """Class used to randomly select a procreation operation 

64 from a list of operations. 

65 

66 Parameters: 

67 

68 probabilities: A list of probabilities with which the different 

69 mutations should be selected. The norm of this list 

70 does not need to be 1. 

71 

72 oplist: The list of operations to select from. 

73 

74 rng: Random number generator 

75 By default numpy.random. 

76 """ 

77 

78 def __init__(self, probabilities, oplist, rng=np.random): 

79 assert len(probabilities) == len(oplist) 

80 self.oplist = oplist 

81 self.rho = np.cumsum(probabilities) 

82 self.rng = rng 

83 

84 def __get_index__(self): 

85 v = self.rng.random() * self.rho[-1] 

86 for i in range(len(self.rho)): 

87 if self.rho[i] > v: 

88 return i 

89 

90 def get_new_individual(self, candidate_list): 

91 """Choose operator and use it on the candidate. """ 

92 to_use = self.__get_index__() 

93 return self.oplist[to_use].get_new_individual(candidate_list) 

94 

95 def get_operator(self): 

96 """Choose operator and return it.""" 

97 to_use = self.__get_index__() 

98 return self.oplist[to_use] 

99 

100 

101class CombinationMutation(OffspringCreator): 

102 """Combine two or more mutations into one operation. 

103 

104 Parameters: 

105 

106 mutations: Operator instances 

107 Supply two or more mutations that will applied one after the other 

108 as one mutation operation. The order of the supplied mutations prevail 

109 when applying the mutations. 

110 

111 """ 

112 

113 def __init__(self, *mutations, verbose=False): 

114 super().__init__(verbose=verbose) 

115 self.descriptor = 'CombinationMutation' 

116 

117 # Check that a combination mutation makes sense 

118 msg = "Too few operators supplied to a CombinationMutation" 

119 assert len(mutations) > 1, msg 

120 

121 self.operators = mutations 

122 

123 def get_new_individual(self, parents): 

124 f = parents[0] 

125 

126 indi = self.mutate(f) 

127 if indi is None: 

128 return indi, f'mutation: {self.descriptor}' 

129 

130 indi = self.initialize_individual(f, indi) 

131 indi.info['data']['parents'] = [f.info['confid']] 

132 

133 return (self.finalize_individual(indi), 

134 f'mutation: {self.descriptor}') 

135 

136 def mutate(self, atoms): 

137 """Perform the mutations one at a time.""" 

138 for op in self.operators: 

139 if atoms is not None: 

140 atoms = op.mutate(atoms) 

141 return atoms