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