Coverage for /builds/kinetik161/ase/ase/ga/standard_comparators.py: 87.27%

110 statements  

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

1import numpy as np 

2 

3from ase.ga import get_raw_score 

4 

5 

6def get_sorted_dist_list(atoms, mic=False): 

7 """ Utility method used to calculate the sorted distance list 

8 describing the cluster in atoms. """ 

9 numbers = atoms.numbers 

10 unique_types = set(numbers) 

11 pair_cor = {} 

12 for n in unique_types: 

13 i_un = [i for i in range(len(atoms)) if atoms[i].number == n] 

14 d = [] 

15 for i, n1 in enumerate(i_un): 

16 for n2 in i_un[i + 1:]: 

17 d.append(atoms.get_distance(n1, n2, mic)) 

18 d.sort() 

19 pair_cor[n] = np.array(d) 

20 return pair_cor 

21 

22 

23class InteratomicDistanceComparator: 

24 

25 """ An implementation of the comparison criteria described in 

26 L.B. Vilhelmsen and B. Hammer, PRL, 108, 126101 (2012) 

27 

28 Parameters: 

29 

30 n_top: The number of atoms being optimized by the GA. 

31 Default 0 - meaning all atoms. 

32 

33 pair_cor_cum_diff: The limit in eq. 2 of the letter. 

34 pair_cor_max: The limit in eq. 3 of the letter 

35 dE: The limit of eq. 1 of the letter 

36 mic: Determines if distances are calculated 

37 using the minimum image convention 

38 """ 

39 

40 def __init__(self, n_top=None, pair_cor_cum_diff=0.015, 

41 pair_cor_max=0.7, dE=0.02, mic=False): 

42 self.pair_cor_cum_diff = pair_cor_cum_diff 

43 self.pair_cor_max = pair_cor_max 

44 self.dE = dE 

45 self.n_top = n_top or 0 

46 self.mic = mic 

47 

48 def looks_like(self, a1, a2): 

49 """ Return if structure a1 or a2 are similar or not. """ 

50 if len(a1) != len(a2): 

51 raise Exception('The two configurations are not the same size') 

52 

53 # first we check the energy criteria 

54 dE = abs(a1.get_potential_energy() - a2.get_potential_energy()) 

55 if dE >= self.dE: 

56 return False 

57 

58 # then we check the structure 

59 a1top = a1[-self.n_top:] 

60 a2top = a2[-self.n_top:] 

61 cum_diff, max_diff = self.__compare_structure__(a1top, a2top) 

62 

63 return (cum_diff < self.pair_cor_cum_diff 

64 and max_diff < self.pair_cor_max) 

65 

66 def __compare_structure__(self, a1, a2): 

67 """ Private method for calculating the structural difference. """ 

68 p1 = get_sorted_dist_list(a1, mic=self.mic) 

69 p2 = get_sorted_dist_list(a2, mic=self.mic) 

70 numbers = a1.numbers 

71 total_cum_diff = 0. 

72 max_diff = 0 

73 for n in p1.keys(): 

74 cum_diff = 0. 

75 c1 = p1[n] 

76 c2 = p2[n] 

77 assert len(c1) == len(c2) 

78 if len(c1) == 0: 

79 continue 

80 t_size = np.sum(c1) 

81 d = np.abs(c1 - c2) 

82 cum_diff = np.sum(d) 

83 max_diff = np.max(d) 

84 ntype = float(sum([i == n for i in numbers])) 

85 total_cum_diff += cum_diff / t_size * ntype / float(len(numbers)) 

86 return (total_cum_diff, max_diff) 

87 

88 

89class SequentialComparator: 

90 """Use more than one comparison class and test them all in sequence. 

91 

92 Supply a list of integers if for example two comparison tests both 

93 need to be positive if two atoms objects are truly equal. 

94 Ex: 

95 methods = [a, b, c, d], logics = [0, 1, 1, 2] 

96 if a or d is positive -> return True 

97 if b and c are positive -> return True 

98 if b and not c are positive (or vice versa) -> return False 

99 """ 

100 

101 def __init__(self, methods, logics=None): 

102 if not isinstance(methods, list): 

103 methods = [methods] 

104 if logics is None: 

105 logics = [i for i in range(len(methods))] 

106 if not isinstance(logics, list): 

107 logics = [logics] 

108 assert len(logics) == len(methods) 

109 

110 self.methods = [] 

111 self.logics = [] 

112 for m, l in zip(methods, logics): 

113 if hasattr(m, 'looks_like'): 

114 self.methods.append(m) 

115 self.logics.append(l) 

116 

117 def looks_like(self, a1, a2): 

118 mdct = {logic: [] for logic in self.logics} 

119 for m, logic in zip(self.methods, self.logics): 

120 mdct[logic].append(m) 

121 

122 for methods in mdct.values(): 

123 for m in methods: 

124 if not m.looks_like(a1, a2): 

125 break 

126 else: 

127 return True 

128 return False 

129 

130 

131class StringComparator: 

132 """Compares the calculated hash strings. These strings should be stored 

133 in atoms.info['key_value_pairs'][key1] and 

134 atoms.info['key_value_pairs'][key2] ... 

135 where the keys should be supplied as parameters i.e. 

136 StringComparator(key1, key2, ...) 

137 """ 

138 

139 def __init__(self, *keys): 

140 self.keys = keys 

141 

142 def looks_like(self, a1, a2): 

143 for k in self.keys: 

144 if a1.info['key_value_pairs'][k] == a2.info['key_value_pairs'][k]: 

145 return True 

146 return False 

147 

148 

149class EnergyComparator: 

150 """Compares the energy of the supplied atoms objects using 

151 get_potential_energy(). 

152 

153 Parameters: 

154 

155 dE: the difference in energy below which two energies are 

156 deemed equal. 

157 """ 

158 

159 def __init__(self, dE=0.02): 

160 self.dE = dE 

161 

162 def looks_like(self, a1, a2): 

163 dE = abs(a1.get_potential_energy() - a2.get_potential_energy()) 

164 if dE >= self.dE: 

165 return False 

166 else: 

167 return True 

168 

169 

170class RawScoreComparator: 

171 """Compares the raw_score of the supplied individuals 

172 objects using a1.info['key_value_pairs']['raw_score']. 

173 

174 Parameters: 

175 

176 dist: the difference in raw_score below which two 

177 scores are deemed equal. 

178 """ 

179 

180 def __init__(self, dist=0.02): 

181 self.dist = dist 

182 

183 def looks_like(self, a1, a2): 

184 d = abs(get_raw_score(a1) - get_raw_score(a2)) 

185 if d >= self.dist: 

186 return False 

187 else: 

188 return True 

189 

190 

191class NoComparator: 

192 """Returns False always. If you don't want any comparator.""" 

193 

194 def looks_like(self, *args): 

195 return False 

196 

197 

198class AtomsComparator: 

199 """Compares the Atoms objects directly.""" 

200 

201 def looks_like(self, a1, a2): 

202 return a1 == a2 

203 

204 

205class CompositionComparator: 

206 """Compares the composition of the Atoms objects.""" 

207 

208 def looks_like(self, a1, a2): 

209 return a1.get_chemical_formula() == a2.get_chemical_formula()