Example #1
0
 def get_optical_isomers_and_symmetry_number(self):
     """
     This method uses the symmetry package from RMG's QM module
     and returns a tuple where the first element is the number
     of optical isomers and the second element is the symmetry number.
     """
     coordinates, atom_numbers, _ = self.loadGeometry()
     unique_id = '0'  # Just some name that the SYMMETRY code gives to one of its jobs
     scr_dir = os.path.join(os.path.abspath('.'), str('scratch'))  # Scratch directory that the SYMMETRY code writes its files in
     if not os.path.exists(scr_dir):
         os.makedirs(scr_dir)
     try:
         qmdata = QMData(
             groundStateDegeneracy=1,  # Only needed to check if valid QMData
             numberOfAtoms=len(atom_numbers),
             atomicNumbers=atom_numbers,
             atomCoords=(coordinates, str('angstrom')),
             energy=(0.0, str('kcal/mol'))  # Only needed to avoid error
         )
         settings = type(str(''), (), dict(symmetryPath=str('symmetry'), scratchDirectory=scr_dir))()  # Creates anonymous class
         pgc = PointGroupCalculator(settings, unique_id, qmdata)
         pg = pgc.calculate()
         if pg is not None:
             optical_isomers = 2 if pg.chiral else 1
             symmetry = pg.symmetryNumber
             logging.debug("Symmetry algorithm found {0} optical isomers and a symmetry number of {1}".format(optical_isomers,symmetry))
         else:
             logging.error("Symmetry algorithm errored when computing point group\nfor log file located at{0}.\nManually provide values in Arkane input.".format(self.path))
         return optical_isomers, symmetry
     finally:
         shutil.rmtree(scr_dir)
Example #2
0
    def calculate_symmetry_number(self):
        from rmgpy.qm.symmetry import PointGroupCalculator
        from rmgpy.qm.qmdata import QMData

        atom_numbers = self.ase_molecule.get_atomic_numbers()
        coordinates = self.ase_molecule.get_positions()

        qmdata = QMData(
            groundStateDegeneracy=1,  # Only needed to check if valid QMData
            numberOfAtoms=len(atom_numbers),
            atomicNumbers=atom_numbers,
            atomCoords=(coordinates, str('angstrom')),
            energy=(0.0, str('kcal/mol'))  # Only needed to avoid error
        )
        settings = type(str(''), (), dict(symmetryPath=str(
            'symmetry'), scratchDirectory="."))()  # Creates anonymous class
        pgc = PointGroupCalculator(settings, self.smiles, qmdata)
        pg = pgc.calculate()
        #os.remove("{}.symm".format(self.smiles))

        if pg is not None:
            symmetry_number = pg.symmetryNumber
        else:
            symmetry_number = 1

        return symmetry_number
Example #3
0
 def get_symmetry_properties(self):
     """
     This method uses the symmetry package from RMG's QM module
     and returns a tuple where the first element is the number
     of optical isomers, the second element is the symmetry number,
     and the third element is the point group identified.
     """
     coordinates, atom_numbers, _ = self.load_geometry()
     unique_id = '0'  # Just some name that the SYMMETRY code gives to one of its jobs
     # Scratch directory that the SYMMETRY code writes its files in:
     scr_dir = os.path.join(os.path.abspath('.'), str('scratch'))
     if not os.path.exists(scr_dir):
         os.makedirs(scr_dir)
     try:
         qmdata = QMData(
             groundStateDegeneracy=1,  # Only needed to check if valid QMData
             numberOfAtoms=len(atom_numbers),
             atomicNumbers=atom_numbers,
             atomCoords=(coordinates, str('angstrom')),
             energy=(0.0, str('kcal/mol'))  # Only needed to avoid error
         )
         # Dynamically create custom class to store the settings needed for the point group calculation
         # Normally, it expects an rmgpy.qm.main.QMSettings object, but we don't need all of those settings
         settings = type(
             str(''), (),
             dict(symmetryPath=str('symmetry'), scratchDirectory=scr_dir))()
         pgc = PointGroupCalculator(settings, unique_id, qmdata)
         pg = pgc.calculate()
         if pg is not None:
             optical_isomers = 2 if pg.chiral else 1
             symmetry = pg.symmetry_number
             logging.debug(
                 "Symmetry algorithm found {0} optical isomers and a symmetry number of {1}"
                 .format(optical_isomers, symmetry))
         else:
             logging.error(
                 'Symmetry algorithm errored when computing point group\nfor log file located at{0}.\n'
                 'Manually provide values in Arkane input.'.format(
                     self.path))
         return optical_isomers, symmetry, pg.point_group
     finally:
         shutil.rmtree(scr_dir)
Example #4
0
def determine_symmetry(coords, symbols):
    """
    Determine external symmetry and chirality (optical isomers) of the species.

    Args:
        coords (list): The 3D coordinates of a molecule in array-format.
        symbols (list): Entries are atomic symbols correcponding to coords.

    Returns:
        int: The external symmetry number.
    Returns:
        int: 1 if no chiral centers are present, 2 if chiral centers are present.
    """
    atom_numbers = list()  # List of atomic numbers
    for symbol in symbols:
        atom_numbers.append(getElement(str(symbol)).number)
    coords = np.array(coords,
                      np.float64)  # N x 3 numpy.ndarray of atomic coordinates
    #  in the same order as `atom_numbers`
    unique_id = '0'  # Just some name that the SYMMETRY code gives to one of its jobs
    scr_dir = os.path.join(
        arc_path, str('scratch')
    )  # Scratch directory that the SYMMETRY code writes its files in
    if not os.path.exists(scr_dir):
        os.makedirs(scr_dir)
    symmetry = optical_isomers = 1
    qmdata = QMData(
        groundStateDegeneracy=1,  # Only needed to check if valid QMData
        numberOfAtoms=len(atom_numbers),
        atomicNumbers=atom_numbers,
        atomCoords=(coords, str('angstrom')),
        energy=(0.0, str('kcal/mol'))  # Only needed to avoid error
    )
    settings = type(
        str(''), (),
        dict(symmetryPath=str('symmetry'), scratchDirectory=scr_dir))()
    pgc = PointGroupCalculator(settings, unique_id, qmdata)
    pg = pgc.calculate()
    if pg is not None:
        symmetry = pg.symmetryNumber
        optical_isomers = 2 if pg.chiral else optical_isomers
    return symmetry, optical_isomers
Example #5
0
 def get_optical_isomers_and_symmetry_number(self):
     """
     This method uses the symmetry package from RMG's QM module
     and returns a tuple where the first element is the number
     of optical isomers and the second element is the symmetry number.
     """
     coordinates, atom_numbers, _ = self.loadGeometry()
     unique_id = '0'  # Just some name that the SYMMETRY code gives to one of its jobs
     scr_dir = os.path.join(
         os.path.abspath('.'), str('scratch')
     )  # Scratch directory that the SYMMETRY code writes its files in
     if not os.path.exists(scr_dir):
         os.makedirs(scr_dir)
     try:
         qmdata = QMData(
             groundStateDegeneracy=1,  # Only needed to check if valid QMData
             numberOfAtoms=len(atom_numbers),
             atomicNumbers=atom_numbers,
             atomCoords=(coordinates, str('angstrom')),
             energy=(0.0, str('kcal/mol'))  # Only needed to avoid error
         )
         settings = type(
             str(''), (),
             dict(symmetryPath=str('symmetry'),
                  scratchDirectory=scr_dir))()  # Creates anonymous class
         pgc = PointGroupCalculator(settings, unique_id, qmdata)
         pg = pgc.calculate()
         if pg is not None:
             optical_isomers = 2 if pg.chiral else 1
             symmetry = pg.symmetryNumber
             logging.debug(
                 "Symmetry algorithm found {0} optical isomers and a symmetry number of {1}"
                 .format(optical_isomers, symmetry))
         else:
             logging.error(
                 "Symmetry algorithm errored when computing point group\nfor log file located at{0}.\nManually provide values in Arkane input."
                 .format(self.path))
         return optical_isomers, symmetry
     finally:
         shutil.rmtree(scr_dir)
Example #6
0
def determine_symmetry(xyz: dict) -> Tuple[int, int]:
    """
    Determine external symmetry and chirality (optical isomers) of the species.

    Args:
        xyz (dict): The 3D coordinates.

    Returns: Tuple[int, int]
        - The external symmetry number.
        - ``1`` if no chiral centers are present, ``2`` if chiral centers are present.
    """
    atom_numbers = list()  # List of atomic numbers
    for symbol in xyz['symbols']:
        atom_numbers.append(get_element(symbol).number)
    # coords is an N x 3 numpy.ndarray of atomic coordinates in the same order as `atom_numbers`
    coords = np.array(xyz['coords'], np.float64)
    unique_id = '0'  # Just some name that the SYMMETRY code gives to one of its jobs
    scr_dir = os.path.join(
        arc_path, 'scratch'
    )  # Scratch directory that the SYMMETRY code writes its files in
    if not os.path.exists(scr_dir):
        os.makedirs(scr_dir)
    symmetry = optical_isomers = 1
    qmdata = QMData(
        groundStateDegeneracy=1,  # Only needed to check if valid QMData
        numberOfAtoms=len(atom_numbers),
        atomicNumbers=atom_numbers,
        atomCoords=(coords, 'angstrom'),
        energy=(0.0, 'kcal/mol')  # Only needed to avoid error
    )
    symmetry_settings = type(
        '', (), dict(symmetryPath='symmetry', scratchDirectory=scr_dir))()
    pgc = PointGroupCalculator(symmetry_settings, unique_id, qmdata)
    pg = pgc.calculate()
    if pg is not None:
        symmetry = pg.symmetry_number
        optical_isomers = 2 if pg.chiral else optical_isomers
    return symmetry, optical_isomers
Example #7
0
def get_thermo(optfreq_log, optfreq_level, energy_level, energy_log=None,
               mol=None, bacs=None, soc=False,
               infer_symmetry=False, infer_chirality=False, unique_id='0', scr_dir='SCRATCH'):

    q = QChem(logfile=optfreq_log)
    symbols, coords = q.get_geometry()
    inertia = q.get_moments_of_inertia()
    freqs = q.get_frequencies()
    zpe = q.get_zpe()

    if energy_log is None:
        e0 = q.get_energy()
        multiplicity = q.get_multiplicity()
    else:
        m = Molpro(logfile=energy_log)
        e0 = m.get_energy()
        multiplicity = m.get_multiplicity()

    # Infer connections only if not given explicitly
    if mol is None:
        mol = geo_to_rmg_mol((symbols, coords))  # Does not contain bond orders

    # Try to infer point group to calculate symmetry number and chirality
    symmetry = optical_isomers = 1
    point_group = None
    if infer_symmetry or infer_chirality:
        qmdata = QMData(
            groundStateDegeneracy=multiplicity,  # Only needed to check if valid QMData
            numberOfAtoms=len(symbols),
            atomicNumbers=[atomic_symbol_dict[sym] for sym in symbols],
            atomCoords=(coords, 'angstrom'),
            energy=(e0 * 627.5095, 'kcal/mol')  # Only needed to avoid error
        )
        settings = type("", (), dict(symmetryPath='symmetry', scratchDirectory=scr_dir))()  # Creates anonymous class
        pgc = PointGroupCalculator(settings, unique_id, qmdata)
        point_group = pgc.calculate()
    if point_group is not None:
        if infer_symmetry:
            symmetry = point_group.symmetryNumber
        if infer_chirality and point_group.chiral:
            optical_isomers = 2

    # Translational mode
    mass = mol.getMolecularWeight()
    translation = IdealGasTranslation(mass=(mass, 'kg/mol'))

    # Rotational mode
    if isinstance(inertia, list):  # Nonlinear
        rotation = NonlinearRotor(inertia=(inertia, 'amu*angstrom^2'), symmetry=symmetry)
    else:
        rotation = LinearRotor(inertia=(inertia, 'amu*angstrom^2'), symmetry=symmetry)

    # Vibrational mode
    freq_scale_factor = freq_scale_factors.get(optfreq_level, 1.0)
    freqs = [f * freq_scale_factor for f in freqs]
    vibration = HarmonicOscillator(frequencies=(freqs, 'cm^-1'))

    # Bring energy to gas phase reference state
    e0 *= constants.E_h * constants.Na
    zpe *= constants.E_h * constants.Na * freq_scale_factor
    for sym in symbols:
        if soc:
            e0 -= (atom_energies[energy_level][sym] - atom_socs[sym]) * constants.E_h * constants.Na
        else:
            e0 -= atom_energies[energy_level][sym] * constants.E_h * constants.Na
        e0 += (h0expt[sym] - h298corr[sym]) * 4184.0

    if bacs is not None:
        e0 -= get_bac_correction(mol, **bacs) * 4184.0

    # Group modes into Conformer object
    modes = [translation, rotation, vibration]
    conformer = Conformer(modes=modes, spinMultiplicity=multiplicity, opticalIsomers=optical_isomers)

    # Calculate heat of formation, entropy of formation, and heat capacities
    conformer.E0 = (e0 + zpe, 'J/mol')
    hf298 = conformer.getEnthalpy(298.0) + conformer.E0.value_si
    s298 = conformer.getEntropy(298.0)

    Tlist = [300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0]
    cp = np.zeros(len(Tlist))
    for i, T in enumerate(Tlist):
        cp[i] = conformer.getHeatCapacity(T)

    # Return in kcal/mol and cal/mol/K
    return hf298/4184.0, s298/4.184, cp/4.184