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
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)
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
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)
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
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