1# -*- coding: utf-8 -*-
2#
3# Copyright (c) 2019, the cclib development team
4#
5# This file is part of cclib (http://cclib.github.io) and is distributed under
6# the terms of the BSD 3-Clause License.
7
8"""Test single point logfiles in cclib."""
9
10import datetime
11import os
12import unittest
13
14import numpy
15import packaging
16
17from common import get_minimum_carbon_separation
18
19from skip import skipForParser
20from skip import skipForLogfile
21
22
23__filedir__ = os.path.realpath(os.path.dirname(__file__))
24
25
26class GenericSPTest(unittest.TestCase):
27    """Generic restricted single point unittest"""
28
29    # Molecular mass of DVB in mD, and expected precision.
30    molecularmass = 130078.25
31    mass_precision = 0.10
32
33    # In STO-3G, H has 1, C has 5 (1 S and 4 SP).
34    nbasisdict = {1:1, 6:5}
35
36    # Approximate B3LYP energy of dvb after SCF in STO-3G.
37    b3lyp_energy = -10365
38
39    # Overlap first two atomic orbitals.
40    overlap01 = 0.24
41
42    # Generally, one criteria for SCF energy convergence.
43    num_scf_criteria = 1
44
45    def testnatom(self):
46        """Is the number of atoms equal to 20?"""
47        self.assertEqual(self.data.natom, 20)
48
49    def testatomnos(self):
50        """Are the atomnos correct?"""
51
52        # The nuclear charges should be integer values in a NumPy array.
53        self.assertTrue(numpy.alltrue([numpy.issubdtype(atomno, numpy.signedinteger)
54                                       for atomno in self.data.atomnos]))
55        self.assertEqual(self.data.atomnos.dtype.char, 'i')
56
57        self.assertEqual(self.data.atomnos.shape, (20,) )
58        self.assertEqual(sum(self.data.atomnos == 6) + sum(self.data.atomnos == 1), 20)
59
60    @skipForParser('DALTON', 'DALTON has a very low accuracy for the printed values of all populations (2 decimals rounded in a weird way), so let it slide for now')
61    @skipForParser('FChk', 'The parser is still being developed so we skip this test')
62    @skipForLogfile('Jaguar/basicJaguar7', 'We did not print the atomic partial charges in the unit tests for this version')
63    @skipForLogfile('Molpro/basicMolpro2006', "These tests were run a long time ago and since we don't have access to Molpro 2006 anymore, we can skip this test (it is tested in 2012)")
64    @skipForParser('Turbomole','The parser is still being developed so we skip this test')
65    def testatomcharges(self):
66        """Are atomcharges (at least Mulliken) consistent with natom and sum to zero?"""
67        for type in set(['mulliken'] + list(self.data.atomcharges.keys())):
68            charges = self.data.atomcharges[type]
69            self.assertEqual(len(charges), self.data.natom)
70            self.assertAlmostEqual(sum(charges), 0.0, delta=0.001)
71
72    def testatomcoords(self):
73        """Are the dimensions of atomcoords 1 x natom x 3?"""
74        expected_shape = (1, self.data.natom, 3)
75        self.assertEqual(self.data.atomcoords.shape, expected_shape)
76
77    def testatomcoords_units(self):
78        """Are atomcoords consistent with Angstroms?"""
79        min_carbon_dist = get_minimum_carbon_separation(self.data)
80        dev = abs(min_carbon_dist - 1.34)
81        self.assertTrue(dev < 0.03, "Minimum carbon dist is %.2f (not 1.34)" % min_carbon_dist)
82
83    @skipForParser('Molcas', 'missing mult')
84    def testcharge_and_mult(self):
85        """Are the charge and multiplicity correct?"""
86        self.assertEqual(self.data.charge, 0)
87        self.assertEqual(self.data.mult, 1)
88
89    def testnbasis(self):
90        """Is the number of basis set functions correct?"""
91        count = sum([self.nbasisdict[n] for n in self.data.atomnos])
92        self.assertEqual(self.data.nbasis, count)
93
94    @skipForParser('ADF', 'ADF parser does not extract atombasis')
95    @skipForLogfile('Jaguar/basicJaguar7', 'Data file does not contain enough information. Can we make a new one?')
96    @skipForParser('Molcas','The parser is still being developed so we skip this test')
97    @skipForParser('Turbomole','The parser is still being developed so we skip this test')
98    def testatombasis(self):
99        """Are the indices in atombasis the right amount and unique?"""
100        all = []
101        for i, atom in enumerate(self.data.atombasis):
102            self.assertEqual(len(atom), self.nbasisdict[self.data.atomnos[i]])
103            all += atom
104        # Test if there are as many indices as atomic orbitals.
105        self.assertEqual(len(all), self.data.nbasis)
106        # Check if all are different (every orbital indexed once).
107        self.assertEqual(len(set(all)), len(all))
108
109    @skipForParser('FChk', 'Formatted checkpoint files do not have a section for atommasses')
110    @skipForParser('GAMESS', 'atommasses not implemented yet')
111    @skipForParser('GAMESSUK', 'atommasses not implemented yet')
112    @skipForParser('Jaguar', 'atommasses not implemented yet')
113    @skipForParser('Molcas','The parser is still being developed so we skip this test')
114    @skipForParser('Molpro', 'atommasses not implemented yet')
115    @skipForParser('NWChem', 'atommasses not implemented yet')
116    @skipForLogfile('Psi4/basicPsi4.0b5', 'atommasses not implemented yet')
117    @skipForParser('QChem', 'atommasses not implemented yet')
118    @skipForParser('Turbomole','The parser is still being developed so we skip this test')
119    def testatommasses(self):
120        """Do the atom masses sum up to the molecular mass?"""
121        mm = 1000*sum(self.data.atommasses)
122        msg = "Molecule mass: %f not %f +- %fmD" % (mm, self.molecularmass, self.mass_precision)
123        self.assertAlmostEqual(mm, self.molecularmass, delta=self.mass_precision, msg=msg)
124
125    @skipForParser('Turbomole','The parser is still being developed so we skip this test')
126    def testcoreelectrons(self):
127        """Are the coreelectrons all 0?"""
128        ans = numpy.zeros(self.data.natom, 'i')
129        numpy.testing.assert_array_equal(self.data.coreelectrons, ans)
130
131    @skipForParser('FChk', 'Formatted checkpoint files do not have a section for symmetry')
132    @skipForParser('Molcas','The parser is still being developed so we skip this test')
133    @skipForParser('Molpro', '?')
134    @skipForParser('ORCA', 'ORCA has no support for symmetry yet')
135    def testsymlabels(self):
136        """Are all the symmetry labels either Ag/u or Bg/u?"""
137        sumwronglabels = sum([x not in ['Ag', 'Bu', 'Au', 'Bg'] for x in self.data.mosyms[0]])
138        self.assertEqual(sumwronglabels, 0)
139
140    def testhomos(self):
141        """Is the index of the HOMO equal to 34?"""
142        numpy.testing.assert_array_equal(self.data.homos, numpy.array([34],"i"), "%s != array([34],'i')" % numpy.array_repr(self.data.homos))
143
144    @skipForParser('FChk', 'Formatted Checkpoint files do not have a section for SCF energy')
145    def testscfvaluetype(self):
146        """Are scfvalues and its elements the right type??"""
147        self.assertEqual(type(self.data.scfvalues),type([]))
148        self.assertEqual(type(self.data.scfvalues[0]),type(numpy.array([])))
149
150    @skipForParser('FChk', 'Formatted Checkpoint files do not have a section for SCF energy')
151    def testscfenergy(self):
152        """Is the SCF energy within the target?"""
153        self.assertAlmostEqual(self.data.scfenergies[-1], self.b3lyp_energy, delta=40, msg="Final scf energy: %f not %i +- 40eV" %(self.data.scfenergies[-1], self.b3lyp_energy))
154
155    @skipForParser('FChk', 'Formatted Checkpoint files do not have a section for SCF convergence')
156    def testscftargetdim(self):
157        """Do the scf targets have the right dimensions?"""
158        self.assertEqual(self.data.scftargets.shape, (len(self.data.scfvalues), len(self.data.scfvalues[0][0])))
159
160    @skipForParser('FChk', 'Formatted Checkpoint files do not have a section for SCF convergence')
161    def testscftargets(self):
162        """Are correct number of SCF convergence criteria being parsed?"""
163        self.assertEqual(len(self.data.scftargets[0]), self.num_scf_criteria)
164
165    def testlengthmoenergies(self):
166        """Is the number of evalues equal to nmo?"""
167        if hasattr(self.data, "moenergies"):
168            self.assertEqual(len(self.data.moenergies[0]), self.data.nmo)
169
170    def testtypemoenergies(self):
171        """Is moenergies a list containing one numpy array?"""
172        if hasattr(self.data, "moenergies"):
173            self.assertIsInstance(self.data.moenergies, list)
174            self.assertIsInstance(self.data.moenergies[0], numpy.ndarray)
175
176    @skipForParser('DALTON', 'mocoeffs not implemented yet')
177    @skipForLogfile('Jaguar/basicJaguar7', 'Data file does not contain enough information. Can we make a new one?')
178    @skipForParser('Turbomole', 'Use of symmetry has reduced the number of mo coeffs')
179    def testdimmocoeffs(self):
180        """Are the dimensions of mocoeffs equal to 1 x nmo x nbasis?"""
181        if hasattr(self.data, "mocoeffs"):
182            self.assertIsInstance(self.data.mocoeffs, list)
183            self.assertEqual(len(self.data.mocoeffs), 1)
184            self.assertEqual(self.data.mocoeffs[0].shape,
185                             (self.data.nmo, self.data.nbasis))
186
187    @skipForParser('DALTON', 'mocoeffs not implemented yet')
188    @skipForLogfile('Jaguar/basicJaguar7', 'Data file does not contain enough information. Can we make a new one?')
189    def testfornoormo(self):
190        """Do we have NOs or MOs?"""
191        self.assertTrue(
192            hasattr(self.data, "nocoeffs") or hasattr(self.data, "mocoeffs")
193        )
194
195    def testdimnoccnos(self):
196        """Is the length of nooccnos equal to nmo?"""
197        if hasattr(self.data, "nooccnos"):
198            self.assertIsInstance(self.data.nooccnos, numpy.ndarray)
199            self.assertEqual(len(self.data.nooccnos), self.data.nmo)
200
201    def testdimnocoeffs(self):
202        """Are the dimensions of nocoeffs equal to nmo x nmo?"""
203        if hasattr(self.data, "nocoeffs"):
204            self.assertIsInstance(self.data.nocoeffs, numpy.ndarray)
205            self.assertEqual(
206                self.data.nocoeffs.shape, (self.data.nmo, self.data.nmo)
207            )
208
209    @skipForParser('DALTON', 'To print: **INTEGRALS\n.PROPRI')
210    @skipForParser('Molcas','The parser is still being developed so we skip this test')
211    @skipForParser('Psi4', 'Psi4 does not currently have the option to print the overlap matrix')
212    @skipForParser('QChem', 'QChem cannot print the overlap matrix')
213    @skipForParser('Turbomole','The parser is still being developed so we skip this test')
214    def testaooverlaps(self):
215        """Are the dims and values of the overlap matrix correct?"""
216
217        self.assertEqual(self.data.aooverlaps.shape, (self.data.nbasis, self.data.nbasis))
218
219        # The matrix is symmetric.
220        row = self.data.aooverlaps[0,:]
221        col = self.data.aooverlaps[:,0]
222        self.assertEqual(sum(col - row), 0.0)
223
224        # All values on diagonal should be exactly one.
225        for i in range(self.data.nbasis):
226            self.assertEqual(self.data.aooverlaps[i,i], 1.0)
227
228        # Check some additional values that don't seem to move around between programs.
229        self.assertAlmostEqual(self.data.aooverlaps[0, 1], self.overlap01, delta=0.01)
230        self.assertAlmostEqual(self.data.aooverlaps[1, 0], self.overlap01, delta=0.01)
231        self.assertAlmostEqual(self.data.aooverlaps[3,0], 0.0)
232        self.assertAlmostEqual(self.data.aooverlaps[0,3], 0.0)
233
234    def testoptdone(self):
235        """There should be no optdone attribute set."""
236        self.assertFalse(hasattr(self.data, 'optdone'))
237
238    @skipForParser('FChk', 'The parser is still being developed so we skip this test')
239    @skipForParser('Gaussian', 'Logfile needs to be updated')
240    @skipForParser('Jaguar', 'No dipole moments in the logfile')
241    @skipForParser('Molcas','The parser is still being developed so we skip this test')
242    def testmoments(self):
243        """Does the dipole and possible higher molecular moments look reasonable?"""
244
245        # The reference point is always a vector, but not necessarily the
246        # origin or center of mass. In this case, however, the center of mass
247        # is at the origin, so we now what to expect.
248        reference = self.data.moments[0]
249        self.assertEqual(len(reference), 3)
250        for x in reference:
251            self.assertEqual(x, 0.0)
252
253        # Length and value of dipole moment should always be correct (zero for this test).
254        dipole = self.data.moments[1]
255        self.assertEqual(len(dipole), 3)
256        for d in dipole:
257            self.assertAlmostEqual(d, 0.0, places=7)
258
259        # If the quadrupole is there, we can expect roughly -50B for the XX moment,
260        # -50B for the YY moment and and -60B for the ZZ moment.
261        if len(self.data.moments) > 2:
262            quadrupole = self.data.moments[2]
263            self.assertEqual(len(quadrupole), 6)
264            self.assertAlmostEqual(quadrupole[0], -50, delta=2.5)
265            self.assertAlmostEqual(quadrupole[3], -50, delta=2.5)
266            self.assertAlmostEqual(quadrupole[5], -60, delta=3)
267
268        # If the octupole is there, it should have 10 components and be zero.
269        if len(self.data.moments) > 3:
270            octupole = self.data.moments[3]
271            self.assertEqual(len(octupole), 10)
272            for m in octupole:
273                self.assertAlmostEqual(m, 0.0, delta=0.001)
274
275        # The hexadecapole should have 15 elements, an XXXX component of around -1900 Debye*ang^2,
276        # a YYYY component of -330B and a ZZZZ component of -50B.
277        if len(self.data.moments) > 4:
278            hexadecapole = self.data.moments[4]
279            self.assertEqual(len(hexadecapole), 15)
280            self.assertAlmostEqual(hexadecapole[0], -1900, delta=90)
281            self.assertAlmostEqual(hexadecapole[10], -330, delta=11)
282            self.assertAlmostEqual(hexadecapole[14], -50, delta=2.5)
283
284        # The are 21 unique 32-pole moments, and all are zero in this test case.
285        if len(self.data.moments) > 5:
286            moment32 = self.data.moments[5]
287            self.assertEqual(len(moment32), 21)
288            for m in moment32:
289                self.assertEqual(m, 0.0)
290
291    @skipForParser('ADF', 'reading basis set names is not implemented')
292    @skipForParser('GAMESSUK', 'reading basis set names is not implemented')
293    @skipForParser('Molcas', 'reading basis set names is not implemented')
294    @skipForParser('ORCA', 'reading basis set names is not implemented')
295    @skipForParser('Psi4', 'reading basis set names is not implemented')
296    def testmetadata_basis_set(self):
297        """Does metadata have expected keys and values?"""
298        self.assertEqual(self.data.metadata["basis_set"].lower(), "sto-3g")
299
300    @skipForParser('ADF', 'reading input file contents and name is not implemented')
301    @skipForParser('DALTON', 'reading input file contents and name is not implemented')
302    @skipForParser('FChk', 'Formatted checkpoint files do not have an input file section')
303    @skipForParser('GAMESS', 'reading input file contents and name is not implemented')
304    @skipForParser('GAMESSUK', 'reading input file contents and name is not implemented')
305    @skipForParser('Gaussian', 'reading input file contents and name is not implemented')
306    @skipForParser('Jaguar', 'reading input file contents and name is not implemented')
307    @skipForParser('Molcas', 'reading input file contents and name is not implemented')
308    @skipForParser('Molpro', 'reading input file contents and name is not implemented')
309    @skipForParser('NWChem', 'reading input file contents and name is not implemented')
310    @skipForParser('Psi4', 'reading input file contents and name is not implemented')
311    @skipForParser('QChem', 'reading input file contents and name is not implemented')
312    @skipForParser('Turbomole', 'reading input file contents and name is not implemented')
313    def testmetadata_input_file(self):
314        """Does metadata have expected keys and values?"""
315        self.assertIn("input_file_contents", self.data.metadata)
316        # TODO make input file names consistent where possible, though some
317        # programs do not allow arbitrary file extensions; for example, DALTON
318        # must end in `dal`.
319        self.assertIn("dvb_sp.in", self.data.metadata["input_file_name"])
320
321    def testmetadata_methods(self):
322        """Does metadata have expected keys and values?"""
323        # TODO implement and unify across parsers; current values are [],
324        # ["HF"], ["RHF"], and ["DFT"]
325        self.assertIn("methods", self.data.metadata)
326
327    def testmetadata_package(self):
328        """Does metadata have expected keys and values?"""
329        # TODO How can the value be tested when the package name comes from
330        # the parser and isn't stored on ccData?
331        self.assertIn("package", self.data.metadata)
332
333    @skipForParser('FChk', 'Formatted Checkpoint files do not have section for legacy package version')
334    def testmetadata_legacy_package_version(self):
335        """Does metadata have expected keys and values?"""
336        # TODO Test specific values for each unit test.
337        self.assertIn("legacy_package_version", self.data.metadata)
338
339    @skipForParser('FChk', 'Formatted Checkpoint files do not have section for package version')
340    def testmetadata_package_version(self):
341        """Does metadata have expected keys and values?"""
342        # TODO Test specific values for each unit test.
343        self.assertIsInstance(
344            packaging.version.parse(self.data.metadata["package_version"]),
345            packaging.version.Version
346        )
347
348    @skipForParser('ADF', 'reading point group symmetry and name is not implemented')
349    @skipForParser('FChk', 'point group symmetry cannot be printed')
350    @skipForParser('GAMESS', 'reading point group symmetry and name is not implemented')
351    @skipForParser('GAMESSUK', 'reading point group symmetry and name is not implemented')
352    @skipForParser('Gaussian', 'reading point group symmetry and name is not implemented')
353    @skipForParser('Jaguar', 'reading point group symmetry and name is not implemented')
354    @skipForParser('Molcas', 'reading point group symmetry and name is not implemented')
355    @skipForParser('Molpro', 'reading point group symmetry and name is not implemented')
356    @skipForParser('MOPAC', 'reading point group symmetry and name is not implemented')
357    @skipForParser('NWChem', 'reading point group symmetry and name is not implemented')
358    @skipForParser('ORCA', 'reading point group symmetry and name is not implemented')
359    @skipForParser('Psi3', 'reading point group symmetry and name is not implemented')
360    @skipForParser('Psi4', 'reading point group symmetry and name is not implemented')
361    @skipForParser('QChem', 'reading point group symmetry and name is not implemented')
362    @skipForParser('Turbomole', 'reading point group symmetry and name is not implemented')
363    def testmetadata_symmetry_detected(self):
364        """Does metadata have expected keys and values?"""
365        self.assertEqual(self.data.metadata["symmetry_detected"], "c2h")
366
367    @skipForParser('ADF', 'reading point group symmetry and name is not implemented')
368    @skipForParser('FChk', 'point group symmetry cannot be printed')
369    @skipForParser('GAMESS', 'reading point group symmetry and name is not implemented')
370    @skipForParser('GAMESSUK', 'reading point group symmetry and name is not implemented')
371    @skipForParser('Gaussian', 'reading point group symmetry and name is not implemented')
372    @skipForParser('Jaguar', 'reading point group symmetry and name is not implemented')
373    @skipForParser('Molcas', 'reading point group symmetry and name is not implemented')
374    @skipForParser('Molpro', 'reading point group symmetry and name is not implemented')
375    @skipForParser('MOPAC', 'reading point group symmetry and name is not implemented')
376    @skipForParser('NWChem', 'reading point group symmetry and name is not implemented')
377    @skipForParser('ORCA', 'reading point group symmetry and name is not implemented')
378    @skipForParser('Psi3', 'reading point group symmetry and name is not implemented')
379    @skipForParser('Psi4', 'reading point group symmetry and name is not implemented')
380    @skipForParser('QChem', 'reading point group symmetry and name is not implemented')
381    @skipForParser('Turbomole', 'reading point group symmetry and name is not implemented')
382    def testmetadata_symmetry_used(self):
383        """Does metadata have expected keys and values?"""
384        self.assertEqual(self.data.metadata["symmetry_used"], "c2h")
385
386    @skipForParser('ADF', 'reading cpu/wall time is not implemented for this parser')
387    @skipForParser('DALTON', 'reading cpu/wall time is not implemented for this parser')
388    @skipForParser('FChk', 'reading cpu/wall time is not implemented for this parser')
389    @skipForParser('GAMESS', 'reading cpu/wall time is not implemented for this parser')
390    @skipForParser('GAMESSUK', 'reading cpu/wall time is not implemented for this parser')
391    @skipForParser('GAMESSUS', 'reading cpu/wall time is not implemented for this parser')
392    @skipForParser('Jaguar', 'reading cpu/wall time is not implemented for this parser')
393    @skipForParser('Molcas', ' reading cpu/wall time is not implemented for this parser')
394    @skipForParser('Molpro', 'reading cpu/wall time is not implemented for this parser')
395    @skipForParser('NWChem', 'reading cpu/wall time is not implemented for this parser')
396    @skipForParser('ORCA', 'reading cpu not implemented for this parser, wall time not available')
397    @skipForParser('Psi3', 'reading cpu/wall time is not implemented for this parser')
398    @skipForParser('Psi4', 'reading cpu/wall time is not implemented for this parser')
399    @skipForParser('Turbomole', 'reading cpu/wall time is not implemented for this parser')
400    def testmetadata_times(self):
401        """Does metadata have expected keys and values of correct types?"""
402        if "wall_time" in self.data.metadata:
403            assert self.data.metadata["wall_time"]
404            assert all(isinstance(wall_time, datetime.timedelta)
405                       for wall_time in self.data.metadata["wall_time"])
406        if "cpu_time" in self.data.metadata:
407            assert self.data.metadata["cpu_time"]
408            assert all(isinstance(cpu_time, datetime.timedelta)
409                       for cpu_time in self.data.metadata["cpu_time"])
410
411class ADFSPTest(GenericSPTest):
412    """Customized restricted single point unittest"""
413
414    # ADF only prints up to 0.1mD per atom, so the precision here is worse than 0.1mD.
415    mass_precision = 0.3
416
417    foverlap00 = 1.00003
418    foverlap11 = 1.02672
419    foverlap22 = 1.03585
420    num_scf_criteria = 2
421    b3lyp_energy = -140
422
423    def testfoverlaps(self):
424        """Are the dims and values of the fragment orbital overlap matrix correct?"""
425
426        self.assertEqual(self.data.fooverlaps.shape, (self.data.nbasis, self.data.nbasis))
427
428        # The matrix is symmetric.
429        row = self.data.fooverlaps[0,:]
430        col = self.data.fooverlaps[:,0]
431        self.assertEqual(sum(col - row), 0.0)
432
433        # Although the diagonal elements are close to zero, the SFOs
434        # are generally not normalized, so test for a few specific values.
435        self.assertAlmostEqual(self.data.fooverlaps[0, 0], self.foverlap00, delta=0.0001)
436        self.assertAlmostEqual(self.data.fooverlaps[1, 1], self.foverlap11, delta=0.0001)
437        self.assertAlmostEqual(self.data.fooverlaps[2, 2], self.foverlap22, delta=0.0001)
438
439class GaussianSPTest(GenericSPTest):
440    """Customized restricted single point unittest"""
441
442    num_scf_criteria = 3
443
444class JaguarSPTest(GenericSPTest):
445    """Customized restricted single point unittest"""
446
447    num_scf_criteria = 2
448
449class Jaguar7SPTest(JaguarSPTest):
450    """Customized restricted single point unittest"""
451
452    # Jaguar prints only 10 virtual MOs by default. Can we re-run with full output?
453    def testlengthmoenergies(self):
454        """Is the number of evalues equal to the number of occ. MOs + 10?"""
455        self.assertEqual(len(self.data.moenergies[0]), self.data.homos[0]+11)
456
457class MolcasSPTest(GenericSPTest):
458    """Customized restricted single point unittest"""
459
460    num_scf_criteria = 4
461
462class MolproSPTest(GenericSPTest):
463    """Customized restricted single point unittest"""
464
465    num_scf_criteria = 2
466
467class NWChemKSSPTest(GenericSPTest):
468    """Customized restricted single point unittest"""
469
470    num_scf_criteria = 3
471
472class PsiSPTest(GenericSPTest):
473    """Customized restricted single point HF/KS unittest"""
474
475    num_scf_criteria = 2
476
477class OrcaSPTest(GenericSPTest):
478    """Customized restricted single point unittest"""
479
480    # Orca has different weights for the masses
481    molecularmass = 130190
482
483    num_scf_criteria = 3
484
485class TurbomoleSPTest(GenericSPTest):
486    """Customized restricted single point unittest"""
487
488    num_scf_criteria = 2
489
490    def testmetadata_basis_set(self):
491        """Does metadata have expected keys and values?"""
492        # One of our test cases used sto-3g hondo
493        valid_basis = self.data.metadata["basis_set"].lower() in ("sto-3g", "sto-3g hondo")
494        self.assertTrue(valid_basis)
495
496
497class GenericDispersionTest(unittest.TestCase):
498    """Generic single-geometry dispersion correction unittest"""
499
500    dispersionenergy = -0.4005496
501
502    def testdispersionenergies(self):
503        """Is the dispersion energy parsed correctly?"""
504        self.assertTrue(len(self.data.dispersionenergies), 1)
505        self.assertAlmostEqual(
506            self.data.dispersionenergies[0],
507            self.dispersionenergy,
508            delta=2.0e-7
509        )
510
511
512class FireflyDispersionTest(GenericDispersionTest):
513    """Customized single-geometry dispersion correction unittest"""
514    dispersionenergy = -0.4299821
515
516
517if __name__ == "__main__":
518
519    import sys
520    sys.path.insert(1, os.path.join(__filedir__, ".."))
521
522    from test_data import DataSuite
523    suite = DataSuite(['SP'])
524    suite.testall()
525