Exemplo n.º 1
0
    def _get_melius_correction(self,
                               coords: np.ndarray = None,
                               nums: Iterable[int] = None,
                               datapoint: BACDatapoint = None,
                               multiplicity: int = None,
                               params: Dict[str, Union[float, Dict[str, float]]] = None) -> ScalarQuantity:
        """
        Given the level of theory, molecular coordinates, atomic numbers,
        and dictionaries of BAC parameters, return the total BAC.

        Notes:
            A molecular correction term other than 0 destroys the size
            consistency of the quantum chemistry method. This correction
            also requires the multiplicity of the molecule.

            The negative of the total correction described in
            Anantharaman and Melius (JPCA 2005) is returned so that it
            can be added to the energy.

        Args:
            coords: Numpy array of Cartesian atomic coordinates.
            nums: Sequence of atomic numbers.
            datapoint: BACDatapoint instead of molecule.
            multiplicity: Multiplicity of the molecule (not necessary if using datapoint).
            params: Optionally provide parameters other than those stored in self.

        Returns:
            Melius-type bond additivity correction.
        """
        if params is None:
            params = self.bacs
        atom_corr = params['atom_corr']
        bond_corr_length = params['bond_corr_length']
        bond_corr_neighbor = params['bond_corr_neighbor']
        mol_corr = params.get('mol_corr', 0.0)

        # Get single-bonded RMG molecule
        mol = None
        if datapoint is not None:
            if nums is None or coords is None:
                mol = datapoint.to_mol(from_geo=True)
                multiplicity = datapoint.spc.multiplicity  # Use species multiplicity instead
            else:
                logging.warning(
                    f'Species {datapoint.spc.label} will not be used because `nums` and `coords` were specified'
                )
        if mol is None:
            mol = geo_to_mol(coords, nums=nums)

        # Molecular correction
        if mol_corr != 0 and multiplicity is None:
            raise BondAdditivityCorrectionError(f'Missing multiplicity for {mol}')
        bac_mol = mol_corr * self._get_mol_coeff(mol, multiplicity=multiplicity)

        # Atomic correction
        bac_atom = sum(count * atom_corr[symbol] for symbol, count in self._get_atom_counts(mol).items())

        # Bond correction
        bac_length = sum(
            coeff * (bond_corr_length[symbol[0]] * bond_corr_length[symbol[1]]) ** 0.5 if isinstance(symbol, tuple)
            else coeff * bond_corr_length[symbol]
            for symbol, coeff in self._get_length_coeffs(mol).items()
        )
        bac_neighbor = sum(count * bond_corr_neighbor[symbol] for
                           symbol, count in self._get_neighbor_coeffs(mol).items())
        bac_bond = bac_length + bac_neighbor

        # Note the minus sign
        return ScalarQuantity(-(bac_mol + bac_atom + bac_bond), 'kcal/mol')
Exemplo n.º 2
0
class TestBACDatapoint(unittest.TestCase):
    """
    A class for testing that the BACDatapoint class functions properly.
    """
    @classmethod
    def setUpClass(cls):
        cls.spc = list(DATABASE.reference_sets.values())[0][0]

    def setUp(self):
        self.datapoint = BACDatapoint(self.spc,
                                      level_of_theory=LEVEL_OF_THEORY)

    def test_assert_level_of_theory(self):
        """
        Test that decorator correctly determines when a level of theory
        is not defined.
        """
        self.datapoint.level_of_theory = None
        with self.assertRaises(BondAdditivityCorrectionError):
            _ = self.datapoint.calc_data

    def test_weight(self):
        """
        Test that weight is initialized to 1.
        """
        self.assertEqual(self.datapoint.weight, 1)

    def test_mol(self):
        """
        Test that BACDatapoint can be converted to a Molecule.
        """
        with self.assertRaises(ValueError):
            _ = self.datapoint.mol

        # From adjacency list
        mol_adj = self.datapoint.to_mol(from_geo=False)
        self.assertIsInstance(mol_adj, Molecule)
        self.assertIs(mol_adj, self.datapoint.mol)
        mol_adj2 = self.datapoint.to_mol(from_geo=False)
        self.assertIs(mol_adj, mol_adj2)  # Check that cached molecule is used

        # From geometry
        mol_geo = self.datapoint.to_mol(from_geo=True)
        self.assertIsNot(mol_geo,
                         mol_adj)  # Check that cached molecule is NOT used
        coords_spc = np.vstack(tuple(a.coords for a in mol_geo.atoms))
        coords_dp = self.spc.calculated_data[LEVEL_OF_THEORY].xyz_dict[
            'coords']
        self.assertIsNone(np.testing.assert_allclose(coords_dp, coords_spc))
        self.assertIsInstance(mol_geo, Molecule)
        self.assertIs(mol_geo, self.datapoint.mol)
        mol_geo2 = self.datapoint.to_mol(from_geo=True)
        self.assertIs(mol_geo, mol_geo2)  # Check that cached molecule is used

    def test_bonds(self):
        """
        Test that bonds can be obtained.
        """
        bonds = self.datapoint.bonds
        self.assertIsInstance(bonds, Counter)
        bonds2 = self.datapoint.bonds
        self.assertIs(bonds, bonds2)  # Check that cached bonds are used

    def test_ref_data(self):
        """
        Test that reference data can be obtained.
        """
        ref_data = self.datapoint.ref_data
        self.assertIsInstance(ref_data, float)

    def test_calc_data(self):
        """
        Test that calculated data can be obtained.
        """
        calc_data = self.datapoint.calc_data
        self.assertIsInstance(calc_data, float)

    def test_bac_data(self):
        """
        Test that `bac_data` can be used.
        """
        with self.assertRaises(ValueError):
            _ = self.datapoint.bac_data

        self.datapoint.bac_data = 1.0
        self.assertIsInstance(self.datapoint.bac_data, float)

    def test_substructs(self):
        """
        Test that BACDatapoint can be decomposed into substructures.
        """
        substructs = self.datapoint.substructs
        self.assertIsInstance(substructs, Counter)

        # Check that exactly one of 'neutral', 'cation', or 'anion' is set
        # and same for 'singlet', 'doublet', 'triplet+'.
        self.assertEqual(
            sum(substructs[k] for k in ('neutral', 'cation', 'anion')),
            1)  # Can only be one of these
        self.assertEqual(
            sum(substructs[k] for k in ('singlet', 'doublet', 'triplet+')), 1)

        substructs2 = self.datapoint.substructs
        self.assertIs(substructs,
                      substructs2)  # Check that cached substructures are used