File: standard_comparators.py

package info (click to toggle)
python-ase 3.26.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,484 kB
  • sloc: python: 148,112; xml: 2,728; makefile: 110; javascript: 47
file content (211 lines) | stat: -rw-r--r-- 6,294 bytes parent folder | download
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()