1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
# fmt: off
import numpy as np
from ase.ga import get_raw_score
def get_sorted_dist_list(atoms, mic=False):
""" Utility method used to calculate the sorted distance list
describing the cluster in atoms. """
numbers = atoms.numbers
unique_types = set(numbers)
pair_cor = {}
for n in unique_types:
i_un = [i for i in range(len(atoms)) if atoms[i].number == n]
d = []
for i, n1 in enumerate(i_un):
for n2 in i_un[i + 1:]:
d.append(atoms.get_distance(n1, n2, mic))
d.sort()
pair_cor[n] = np.array(d)
return pair_cor
class InteratomicDistanceComparator:
""" An implementation of the comparison criteria described in
L.B. Vilhelmsen and B. Hammer, PRL, 108, 126101 (2012)
Parameters:
n_top: The number of atoms being optimized by the GA.
Default 0 - meaning all atoms.
pair_cor_cum_diff: The limit in eq. 2 of the letter.
pair_cor_max: The limit in eq. 3 of the letter
dE: The limit of eq. 1 of the letter
mic: Determines if distances are calculated
using the minimum image convention
"""
def __init__(self, n_top=None, pair_cor_cum_diff=0.015,
pair_cor_max=0.7, dE=0.02, mic=False):
self.pair_cor_cum_diff = pair_cor_cum_diff
self.pair_cor_max = pair_cor_max
self.dE = dE
self.n_top = n_top or 0
self.mic = mic
def looks_like(self, a1, a2):
""" Return if structure a1 or a2 are similar or not. """
if len(a1) != len(a2):
raise Exception('The two configurations are not the same size')
# first we check the energy criteria
dE = abs(a1.get_potential_energy() - a2.get_potential_energy())
if dE >= self.dE:
return False
# then we check the structure
a1top = a1[-self.n_top:]
a2top = a2[-self.n_top:]
cum_diff, max_diff = self.__compare_structure__(a1top, a2top)
return (cum_diff < self.pair_cor_cum_diff
and max_diff < self.pair_cor_max)
def __compare_structure__(self, a1, a2):
""" Private method for calculating the structural difference. """
p1 = get_sorted_dist_list(a1, mic=self.mic)
p2 = get_sorted_dist_list(a2, mic=self.mic)
numbers = a1.numbers
total_cum_diff = 0.
max_diff = 0
for n in p1.keys():
cum_diff = 0.
c1 = p1[n]
c2 = p2[n]
assert len(c1) == len(c2)
if len(c1) == 0:
continue
t_size = np.sum(c1)
d = np.abs(c1 - c2)
cum_diff = np.sum(d)
max_diff = np.max(d)
ntype = float(sum(i == n for i in numbers))
total_cum_diff += cum_diff / t_size * ntype / float(len(numbers))
return (total_cum_diff, max_diff)
class SequentialComparator:
"""Use more than one comparison class and test them all in sequence.
Supply a list of integers if for example two comparison tests both
need to be positive if two atoms objects are truly equal.
Ex:
methods = [a, b, c, d], logics = [0, 1, 1, 2]
if a or d is positive -> return True
if b and c are positive -> return True
if b and not c are positive (or vice versa) -> return False
"""
def __init__(self, methods, logics=None):
if not isinstance(methods, list):
methods = [methods]
if logics is None:
logics = [i for i in range(len(methods))]
if not isinstance(logics, list):
logics = [logics]
assert len(logics) == len(methods)
self.methods = []
self.logics = []
for m, l in zip(methods, logics):
if hasattr(m, 'looks_like'):
self.methods.append(m)
self.logics.append(l)
def looks_like(self, a1, a2):
mdct = {logic: [] for logic in self.logics}
for m, logic in zip(self.methods, self.logics):
mdct[logic].append(m)
for methods in mdct.values():
for m in methods:
if not m.looks_like(a1, a2):
break
else:
return True
return False
class StringComparator:
"""Compares the calculated hash strings. These strings should be stored
in atoms.info['key_value_pairs'][key1] and
atoms.info['key_value_pairs'][key2] ...
where the keys should be supplied as parameters i.e.
StringComparator(key1, key2, ...)
"""
def __init__(self, *keys):
self.keys = keys
def looks_like(self, a1, a2):
for k in self.keys:
if a1.info['key_value_pairs'][k] == a2.info['key_value_pairs'][k]:
return True
return False
class EnergyComparator:
"""Compares the energy of the supplied atoms objects using
get_potential_energy().
Parameters:
dE: the difference in energy below which two energies are
deemed equal.
"""
def __init__(self, dE=0.02):
self.dE = dE
def looks_like(self, a1, a2):
dE = abs(a1.get_potential_energy() - a2.get_potential_energy())
if dE >= self.dE:
return False
else:
return True
class RawScoreComparator:
"""Compares the raw_score of the supplied individuals
objects using a1.info['key_value_pairs']['raw_score'].
Parameters:
dist: the difference in raw_score below which two
scores are deemed equal.
"""
def __init__(self, dist=0.02):
self.dist = dist
def looks_like(self, a1, a2):
d = abs(get_raw_score(a1) - get_raw_score(a2))
if d >= self.dist:
return False
else:
return True
class NoComparator:
"""Returns False always. If you don't want any comparator."""
def looks_like(self, *args):
return False
class AtomsComparator:
"""Compares the Atoms objects directly."""
def looks_like(self, a1, a2):
return a1 == a2
class CompositionComparator:
"""Compares the composition of the Atoms objects."""
def looks_like(self, a1, a2):
return a1.get_chemical_formula() == a2.get_chemical_formula()
|