root / ase / utils / molecule_test.py @ 20
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() |