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

61 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +0000

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

2import numpy as np 

3 

4from ase import Atoms 

5 

6 

7class OffspringCreator: 

8 """Base class for all procreation operators 

9 

10 Parameters: 

11 

12 verbose: Be verbose and print some stuff 

13 

14 rng: Random number generator 

15 By default numpy.random. 

16 """ 

17 

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

19 self.descriptor = 'OffspringCreator' 

20 self.verbose = verbose 

21 self.min_inputs = 0 

22 self.num_muts = num_muts 

23 self.rng = rng 

24 

25 def get_min_inputs(self): 

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

27 this is to know how many candidates should be selected 

28 from the population.""" 

29 return self.min_inputs 

30 

31 def get_new_individual(self, parents): 

32 """Function that returns a new individual. 

33 Overwrite in subclass.""" 

34 raise NotImplementedError 

35 

36 def finalize_individual(self, indi): 

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

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

39 

40 return indi 

41 

42 @classmethod 

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

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

45 from the parent, and initializes the info dictionary. 

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

47 supplied in the parameter indi.""" 

48 if indi is None: 

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

50 else: 

51 indi = indi.copy() 

52 # key_value_pairs for numbers and strings 

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

54 # data for lists and the like 

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

56 

57 return indi 

58 

59 

60class OperationSelector: 

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

62 from a list of operations. 

63 

64 Parameters: 

65 

66 probabilities: A list of probabilities with which the different 

67 mutations should be selected. The norm of this list 

68 does not need to be 1. 

69 

70 oplist: The list of operations to select from. 

71 

72 rng: Random number generator 

73 By default numpy.random. 

74 """ 

75 

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

77 assert len(probabilities) == len(oplist) 

78 self.oplist = oplist 

79 self.rho = np.cumsum(probabilities) 

80 self.rng = rng 

81 

82 def __get_index__(self): 

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

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

85 if self.rho[i] > v: 

86 return i 

87 

88 def get_new_individual(self, candidate_list): 

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

90 to_use = self.__get_index__() 

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

92 

93 def get_operator(self): 

94 """Choose operator and return it.""" 

95 to_use = self.__get_index__() 

96 return self.oplist[to_use] 

97 

98 

99class CombinationMutation(OffspringCreator): 

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

101 

102 Parameters: 

103 

104 mutations: Operator instances 

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

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

107 when applying the mutations. 

108 

109 """ 

110 

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

112 super().__init__(verbose=verbose) 

113 self.descriptor = 'CombinationMutation' 

114 

115 # Check that a combination mutation makes sense 

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

117 assert len(mutations) > 1, msg 

118 

119 self.operators = mutations 

120 

121 def get_new_individual(self, parents): 

122 f = parents[0] 

123 

124 indi = self.mutate(f) 

125 if indi is None: 

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

127 

128 indi = self.initialize_individual(f, indi) 

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

130 

131 return (self.finalize_individual(indi), 

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

133 

134 def mutate(self, atoms): 

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

136 for op in self.operators: 

137 if atoms is not None: 

138 atoms = op.mutate(atoms) 

139 return atoms