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
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1import numpy as np
3from ase.ga import get_raw_score
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
23class InteratomicDistanceComparator:
25 """ An implementation of the comparison criteria described in
26 L.B. Vilhelmsen and B. Hammer, PRL, 108, 126101 (2012)
28 Parameters:
30 n_top: The number of atoms being optimized by the GA.
31 Default 0 - meaning all atoms.
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 """
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
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')
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
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)
63 return (cum_diff < self.pair_cor_cum_diff
64 and max_diff < self.pair_cor_max)
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)
89class SequentialComparator:
90 """Use more than one comparison class and test them all in sequence.
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 """
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)
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)
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)
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
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 """
139 def __init__(self, *keys):
140 self.keys = keys
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
149class EnergyComparator:
150 """Compares the energy of the supplied atoms objects using
151 get_potential_energy().
153 Parameters:
155 dE: the difference in energy below which two energies are
156 deemed equal.
157 """
159 def __init__(self, dE=0.02):
160 self.dE = dE
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
170class RawScoreComparator:
171 """Compares the raw_score of the supplied individuals
172 objects using a1.info['key_value_pairs']['raw_score'].
174 Parameters:
176 dist: the difference in raw_score below which two
177 scores are deemed equal.
178 """
180 def __init__(self, dist=0.02):
181 self.dist = dist
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
191class NoComparator:
192 """Returns False always. If you don't want any comparator."""
194 def looks_like(self, *args):
195 return False
198class AtomsComparator:
199 """Compares the Atoms objects directly."""
201 def looks_like(self, a1, a2):
202 return a1 == a2
205class CompositionComparator:
206 """Compares the composition of the Atoms objects."""
208 def looks_like(self, a1, a2):
209 return a1.get_chemical_formula() == a2.get_chemical_formula()