Statistiques
| Révision :

root / ase / utils / molecule_test.py @ 3

Historique | Voir | Annoter | Télécharger (9,49 ko)

1 1 tkerber
#!/usr/bin/env python
2 1 tkerber
3 1 tkerber
"""This module defines extensible classes for running tests on molecules.
4 1 tkerber

5 1 tkerber
Use this to compare different calculators, XC functionals and so on by
6 1 tkerber
calculating e.g. atomization energies and bond lengths across the
7 1 tkerber
g2 database of small molecules.
8 1 tkerber
"""
9 1 tkerber
10 1 tkerber
import os
11 1 tkerber
import sys
12 1 tkerber
import traceback
13 1 tkerber
14 1 tkerber
import numpy as np
15 1 tkerber
16 1 tkerber
from ase import PickleTrajectory, read
17 1 tkerber
from ase.calculators.emt import EMT
18 1 tkerber
from ase.data.molecules import molecule, atoms as g2_atoms, g1
19 1 tkerber
20 1 tkerber
21 1 tkerber
class BatchTest:
22 1 tkerber
    """Contains logic for looping over tests and file management."""
23 1 tkerber
    def __init__(self, test):
24 1 tkerber
        self.test = test
25 1 tkerber
        self.txt = sys.stdout # ?
26 1 tkerber
27 1 tkerber
    def run_single_test(self, formula):
28 1 tkerber
        print >> self.txt, self.test.name, formula, '...',
29 1 tkerber
        self.txt.flush()
30 1 tkerber
        filename = self.test.get_filename(formula)
31 1 tkerber
        if os.path.exists(filename):
32 1 tkerber
            print >> self.txt, 'Skipped.'
33 1 tkerber
            return
34 1 tkerber
        try:
35 1 tkerber
            open(filename, 'w').close() # Empty file
36 1 tkerber
            system, calc = self.test.setup(formula)
37 1 tkerber
            self.test.run(formula, system, filename)
38 1 tkerber
            print >> self.txt, 'OK!'
39 1 tkerber
            self.txt.flush()
40 1 tkerber
        except self.test.exceptions:
41 1 tkerber
            print >> self.txt, 'Failed!'
42 1 tkerber
            traceback.print_exc(file=self.txt)
43 1 tkerber
            print >> self.txt
44 1 tkerber
            self.txt.flush()
45 1 tkerber
46 1 tkerber
    def run(self, formulas):
47 1 tkerber
        """Run a batch of tests.
48 1 tkerber

49 1 tkerber
        This will invoke the test method on each formula, printing
50 1 tkerber
        status to stdout.
51 1 tkerber

52 1 tkerber
        Those formulas that already have test result files will
53 1 tkerber
        be skipped."""
54 1 tkerber
55 1 tkerber
        # Create directories if necessary
56 1 tkerber
        if self.test.dir and not os.path.isdir(self.test.dir):
57 1 tkerber
            os.mkdir(self.test.dir) # Won't work on 'dir1/dir2', but oh well
58 1 tkerber
59 1 tkerber
        for formula in formulas:
60 1 tkerber
            self.run_single_test(formula)
61 1 tkerber
62 1 tkerber
    def collect(self, formulas, verbose=False):
63 1 tkerber
        """Yield results of previous calculations."""
64 1 tkerber
        for formula in formulas:
65 1 tkerber
            try:
66 1 tkerber
                filename = self.test.get_filename(formula)
67 1 tkerber
                results = self.test.retrieve(formula, filename)
68 1 tkerber
                if verbose:
69 1 tkerber
                    print >> self.txt, 'Loaded:', formula, filename
70 1 tkerber
                yield formula, results
71 1 tkerber
            except (IOError, RuntimeError, TypeError):
72 1 tkerber
                # XXX which errors should we actually catch?
73 1 tkerber
                if verbose:
74 1 tkerber
                    print >> self.txt, 'Error:', formula, '[%s]' % filename
75 1 tkerber
                    traceback.print_exc(file=self.txt)
76 1 tkerber
77 1 tkerber
78 1 tkerber
class MoleculeTest:
79 1 tkerber
    """Generic class for runnings various tests on the g2 dataset.
80 1 tkerber

81 1 tkerber
    Usage: instantiate MoleculeTest with desired test settings and
82 1 tkerber
    invoke its run() method on the desired formulas.
83 1 tkerber

84 1 tkerber
    This class will use the ASE EMT calculator by default.  You can
85 1 tkerber
    create a subclass using an arbitrary calculator by overriding the
86 1 tkerber
    setup_calculator method.  Most methods can be overridden to
87 1 tkerber
    provide highly customized behaviour.  """
88 1 tkerber
89 1 tkerber
    def __init__(self, name, vacuum=6.0, exceptions=None):
90 1 tkerber
        """Create a molecule test.
91 1 tkerber

92 1 tkerber
        The name parameter will be part of all output files generated
93 1 tkerber
        by this molecule test.  If name contains a '/' character, the
94 1 tkerber
        preceding part will be interpreted as a directory in which to
95 1 tkerber
        put files.
96 1 tkerber

97 1 tkerber
        The vacuum parameter is used to set the cell size.
98 1 tkerber

99 1 tkerber
        A tuple of exception types can be provided which will be
100 1 tkerber
        caught during a batch of calculations.  Types not specified
101 1 tkerber
        will be considered fatal."""
102 1 tkerber
103 1 tkerber
        dir, path = os.path.split(name)
104 1 tkerber
        self.dir = dir
105 1 tkerber
        self.name = name
106 1 tkerber
        self.vacuum = vacuum
107 1 tkerber
        if exceptions is None:
108 1 tkerber
            exceptions = ()
109 1 tkerber
        self.exceptions = exceptions
110 1 tkerber
111 1 tkerber
    def setup_calculator(self, system, formula):
112 1 tkerber
        """Create a new calculator.
113 1 tkerber

114 1 tkerber
        Default is an EMT calculator.  Most implementations will want to
115 1 tkerber
        override this method."""
116 1 tkerber
        raise NotImplementedError
117 1 tkerber
118 1 tkerber
    def setup_system(self, formula):
119 1 tkerber
        """Create an Atoms object from the given formula.
120 1 tkerber

121 1 tkerber
        By default this will be loaded from the g2 database, setting
122 1 tkerber
        the cell size by means of the molecule test's vacuum parameter."""
123 1 tkerber
        system = molecule(formula)
124 1 tkerber
        system.center(vacuum=self.vacuum)
125 1 tkerber
        return system
126 1 tkerber
127 1 tkerber
    def setup(self, formula):
128 1 tkerber
        """Build calculator and atoms objects.
129 1 tkerber

130 1 tkerber
        This will invoke the setup_calculator and setup_system methods."""
131 1 tkerber
        system = self.setup_system(formula)
132 1 tkerber
        calc = self.setup_calculator(system, formula)
133 1 tkerber
        system.set_calculator(calc)
134 1 tkerber
        return system, calc
135 1 tkerber
136 1 tkerber
    def get_filename(self, formula, extension='traj'):
137 1 tkerber
        """Returns the filename for a test result file.
138 1 tkerber

139 1 tkerber
        Default format is <name>.<formula>.traj
140 1 tkerber

141 1 tkerber
        The test may write other files, but this filename is used as a
142 1 tkerber
        flag denoting whether the calculation has been done
143 1 tkerber
        already."""
144 1 tkerber
        return '.'.join([self.name, formula, extension])
145 1 tkerber
146 1 tkerber
    def run(self, formula, system, filename):
147 1 tkerber
        raise NotImplementedError
148 1 tkerber
149 1 tkerber
    def retrieve(self, formula, filename):
150 1 tkerber
        """Retrieve results of previous calculation from file.
151 1 tkerber

152 1 tkerber
        Default implementation returns the total energy.
153 1 tkerber

154 1 tkerber
        This method should be overridden whenever the test method is
155 1 tkerber
        overridden to calculate something else than the total energy."""
156 1 tkerber
        raise NotImplementedError
157 1 tkerber
158 1 tkerber
159 1 tkerber
class EnergyTest:
160 1 tkerber
    def run(self, formula, system, filename):
161 1 tkerber
        """Calculate energy of specified system and save to file."""
162 1 tkerber
        system.get_potential_energy()
163 1 tkerber
         # Won't create .bak file:
164 1 tkerber
        traj = PickleTrajectory(open(filename, 'w'), 'w')
165 1 tkerber
        traj.write(system)
166 1 tkerber
        traj.close()
167 1 tkerber
168 1 tkerber
    def retrieve(self, formula, filename):
169 1 tkerber
        system = read(filename)
170 1 tkerber
        energy = system.get_potential_energy()
171 1 tkerber
        return energy
172 1 tkerber
173 1 tkerber
    def calculate_atomization_energies(self, molecular_energies,
174 1 tkerber
                                       atomic_energies):
175 1 tkerber
        atomic_energy_dict = dict(atomic_energies)
176 1 tkerber
        for formula, molecular_energy in molecular_energies:
177 1 tkerber
            try:
178 1 tkerber
                system = molecule(formula)
179 1 tkerber
                atomic = [atomic_energy_dict[s]
180 1 tkerber
                          for s in system.get_chemical_symbols()]
181 1 tkerber
                atomization_energy = molecular_energy - sum(atomic)
182 1 tkerber
                yield formula, atomization_energy
183 1 tkerber
            except KeyError:
184 1 tkerber
                pass
185 1 tkerber
186 1 tkerber
187 1 tkerber
class BondLengthTest:
188 1 tkerber
    def run(self, formula, system, filename):
189 1 tkerber
        """Calculate bond length of a dimer.
190 1 tkerber

191 1 tkerber
        This will calculate total energies for varying atomic
192 1 tkerber
        separations close to the g2 bond length, allowing
193 1 tkerber
        determination of bond length by fitting.
194 1 tkerber
        """
195 1 tkerber
        if len(system) != 2:
196 1 tkerber
            raise ValueError('Not a dimer')
197 1 tkerber
        traj = PickleTrajectory(open(filename, 'w'), 'w')
198 1 tkerber
        pos = system.positions
199 1 tkerber
        d = np.linalg.norm(pos[1] - pos[0])
200 1 tkerber
        for x in range(-2, 3):
201 1 tkerber
            system.set_distance(0, 1, d * (1.0 + x * 0.02))
202 1 tkerber
            traj.write(system)
203 1 tkerber
        traj.close()
204 1 tkerber
205 1 tkerber
    def retrieve(self, formula, filename):
206 1 tkerber
        traj = PickleTrajectory(filename, 'r')
207 1 tkerber
        distances = np.array([np.linalg.norm(a.positions[1] - a.positions[0])
208 1 tkerber
                              for a in traj])
209 1 tkerber
        energies = np.array([a.get_potential_energy() for a in traj])
210 1 tkerber
        polynomial = np.polyfit(distances, energies, 2) # or maybe 3rd order?
211 1 tkerber
        # With 3rd order it is not always obvious which root is right
212 1 tkerber
        pderiv = np.polyder(polynomial, 1)
213 1 tkerber
        d0 = np.roots(pderiv)
214 1 tkerber
        e0 = np.polyval(energies, d0)
215 1 tkerber
        return distances, energies, d0, e0, polynomial
216 1 tkerber
217 1 tkerber
218 1 tkerber
class EMTTest(MoleculeTest):
219 1 tkerber
    def setup_calculator(self, system, calculator):
220 1 tkerber
        return EMT()
221 1 tkerber
222 1 tkerber
223 1 tkerber
class EMTEnergyTest(EnergyTest, EMTTest):
224 1 tkerber
    pass
225 1 tkerber
226 1 tkerber
227 1 tkerber
class EMTBondLengthTest(BondLengthTest, EMTTest):
228 1 tkerber
    pass
229 1 tkerber
230 1 tkerber
231 1 tkerber
def main():
232 1 tkerber
    supported_elements = 'Ni, C, Pt, Ag, H, Al, O, N, Au, Pd, Cu'.split(', ')
233 1 tkerber
    formulas = [formula for formula in g1
234 1 tkerber
                if np.all([symbol in supported_elements
235 1 tkerber
                           for symbol
236 1 tkerber
                           in molecule(formula).get_chemical_symbols()])]
237 1 tkerber
238 1 tkerber
    atoms = [symbol for symbol in g2_atoms if symbol in supported_elements]
239 1 tkerber
    dimers = [formula for formula in formulas if len(molecule(formula)) == 2]
240 1 tkerber
241 1 tkerber
242 1 tkerber
    name1 = 'testfiles/energy'
243 1 tkerber
    name2 = 'testfiles/bond'
244 1 tkerber
    test1 = BatchTest(EMTEnergyTest(name1, vacuum=3.0))
245 1 tkerber
    test2 = BatchTest(EMTBondLengthTest(name2, vacuum=3.0))
246 1 tkerber
247 1 tkerber
    print 'Energy test'
248 1 tkerber
    print '-----------'
249 1 tkerber
    test1.run(formulas + atoms)
250 1 tkerber
251 1 tkerber
    print
252 1 tkerber
    print 'Bond length test'
253 1 tkerber
    print '----------------'
254 1 tkerber
    test2.run(dimers)
255 1 tkerber
256 1 tkerber
    print
257 1 tkerber
    print 'Atomization energies'
258 1 tkerber
    print '--------------------'
259 1 tkerber
    atomic_energies = dict(test1.collect(atoms))
260 1 tkerber
    molecular_energies = dict(test1.collect(formulas))
261 1 tkerber
    atomization_energies = {}
262 1 tkerber
    for formula, energy in molecular_energies.iteritems():
263 1 tkerber
        system = molecule(formula)
264 1 tkerber
        atomic = [atomic_energies[s] for s in system.get_chemical_symbols()]
265 1 tkerber
        atomization_energy = energy - sum(atomic)
266 1 tkerber
        atomization_energies[formula] = atomization_energy
267 1 tkerber
        print formula.rjust(10), '%.02f' % atomization_energy
268 1 tkerber
269 1 tkerber
    print
270 1 tkerber
    print 'Bond lengths'
271 1 tkerber
    print '------------'
272 1 tkerber
    for formula, (d_i, e_i, d0, e0, poly) in test2.collect(dimers):
273 1 tkerber
        system = molecule(formula)
274 1 tkerber
        bref = np.linalg.norm(system.positions[1] - system.positions[0])
275 1 tkerber
        print formula.rjust(10), '%6.3f' % d0, '  g2ref =', '%2.3f' % bref
276 1 tkerber
277 1 tkerber
278 1 tkerber
if __name__ == '__main__':
279 1 tkerber
    main()