def main(): args = parseCommandLineArguments() if args.useOriginalReactions and not args.original: raise InputError( 'Cannot use original reactions without a previously run RMG job') maximumIsotopicAtoms = args.maximumIsotopicAtoms[0] useOriginalReactions = args.useOriginalReactions inputFile = args.input outputdir = os.path.abspath( args.output[0]) if args.output else os.path.abspath('.') original = os.path.abspath(args.original[0]) if args.original else None kie = args.kineticIsotopeEffect[0] if args.kineticIsotopeEffect else None supported_kie_methods = ['simple'] if kie not in supported_kie_methods and kie is not None: raise InputError( 'The kie input, {0}, is not one of the currently supported methods, {1}' .format(kie, supported_kie_methods)) initializeLog(logging.INFO, os.path.join(os.getcwd(), 'RMG.log')) run(inputFile, outputdir, original=original, maximumIsotopicAtoms=maximumIsotopicAtoms, useOriginalReactions=useOriginalReactions, kineticIsotopeEffect=kie)
def database( thermoLibraries=None, transportLibraries=None, reactionLibraries=None, frequenciesLibraries=None, seedMechanisms=None, kineticsFamilies='default', kineticsDepositories='default', kineticsEstimator='rate rules', ): # This function just stores the information about the database to be loaded # We don't actually load the database until after we're finished reading # the input file if isinstance(thermoLibraries, str): thermoLibraries = [thermoLibraries] if isinstance(transportLibraries, str): transportLibraries = [transportLibraries] if isinstance(reactionLibraries, str): reactionLibraries = [reactionLibraries] if isinstance(seedMechanisms, str): seedMechanisms = [seedMechanisms] if isinstance(frequenciesLibraries, str): frequenciesLibraries = [frequenciesLibraries] rmg.databaseDirectory = settings['database.directory'] rmg.thermoLibraries = thermoLibraries or [] rmg.transportLibraries = transportLibraries # Modify reactionLibraries if the user didn't specify tuple input if reactionLibraries: index = 0 while index < len(reactionLibraries): if isinstance(reactionLibraries[index], tuple): pass elif isinstance(reactionLibraries[index], str): reactionLibraries[index] = (reactionLibraries[index], False) else: raise TypeError( 'reaction libraries must be input as tuples or strings') index += 1 rmg.reactionLibraries = reactionLibraries or [] rmg.seedMechanisms = seedMechanisms or [] rmg.statmechLibraries = frequenciesLibraries or [] rmg.kineticsEstimator = kineticsEstimator if kineticsDepositories == 'default': rmg.kineticsDepositories = ['training'] elif kineticsDepositories == 'all': rmg.kineticsDepositories = None else: if not isinstance(kineticsDepositories, list): raise InputError( "kineticsDepositories should be either 'default', 'all', or a list of names eg. ['training','PrIMe']." ) rmg.kineticsDepositories = kineticsDepositories if kineticsFamilies in ('default', 'all', 'none'): rmg.kineticsFamilies = kineticsFamilies else: if not isinstance(kineticsFamilies, list): raise InputError( "kineticsFamilies should be either 'default', 'all', 'none', or a list of names eg. ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation']." ) rmg.kineticsFamilies = kineticsFamilies
def database( thermoLibraries = None, transportLibraries = None, reactionLibraries = None, frequenciesLibraries = None, kineticsFamilies = 'default', kineticsDepositories = 'default', kineticsEstimator = 'rate rules', ): if isinstance(thermoLibraries, str): thermoLibraries = [thermoLibraries] if isinstance(transportLibraries, str): transportLibraries = [transportLibraries] if isinstance(reactionLibraries, str): reactionLibraries = [reactionLibraries] if isinstance(frequenciesLibraries, str): frequenciesLibraries = [frequenciesLibraries] databaseDirectory = settings['database.directory'] thermoLibraries = thermoLibraries or [] transportLibraries = transportLibraries reactionLibraries = reactionLibraries or [] kineticsEstimator = kineticsEstimator if kineticsDepositories == 'default': kineticsDepositories = ['training'] elif kineticsDepositories == 'all': kineticsDepositories = None else: if not isinstance(kineticsDepositories,list): raise InputError("kineticsDepositories should be either 'default', 'all', or a list of names eg. ['training','PrIMe'].") kineticsDepositories = kineticsDepositories if kineticsFamilies in ('default', 'all', 'none'): kineticsFamilies = kineticsFamilies else: if not isinstance(kineticsFamilies,list): raise InputError("kineticsFamilies should be either 'default', 'all', 'none', or a list of names eg. ['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation'].") kineticsFamilies = kineticsFamilies database = getDB() or RMGDatabase() database.load( path = databaseDirectory, thermoLibraries = thermoLibraries, transportLibraries = transportLibraries, reactionLibraries = reactionLibraries, seedMechanisms = [], kineticsFamilies = kineticsFamilies, kineticsDepositories = kineticsDepositories, depository = False, # Don't bother loading the depository information, as we don't use it ) for family in database.kinetics.families.values(): #load training family.addKineticsRulesFromTrainingSet(thermoDatabase=database.thermo) for family in database.kinetics.families.values(): family.fillKineticsRulesByAveragingUp(verbose=True)
def loadGeometry(self): """ Return the optimum geometry of the molecular configuration from the QChem log file. If multiple such geometries are identified, only the last is returned. """ atom, coord, number, mass = [], [], [], [] with open(self.path) as f: log = f.read().splitlines() # First check that the QChem job file (not necessarily a geometry optimization) # has successfully completed, if not an error is thrown completed_job = False for line in reversed(log): if 'Total job time:' in line: logging.debug('Found a sucessfully completed QChem Job') completed_job = True break if not completed_job: raise InputError( 'Could not find a successfully completed QChem job in QChem output file {0}' .format(self.path)) # Now look for the geometry. # Will return the final geometry in the file under Standard Nuclear Orientation. geometry_flag = False for i in reversed(xrange(len(log))): line = log[i] if 'Standard Nuclear Orientation' in line: for line in log[(i + 3):]: if '------------' not in line: data = line.split() atom.append(data[1]) coord.append([float(c) for c in data[2:]]) geometry_flag = True else: break if geometry_flag: break # Assign appropriate mass to each atom in the molecule for atom1 in atom: mass1, num1 = get_element_mass(atom1) mass.append(mass1) number.append(num1) coord = numpy.array(coord, numpy.float64) number = numpy.array(number, numpy.int) mass = numpy.array(mass, numpy.float64) if len(number) == 0 or len(coord) == 0 or len(mass) == 0: raise InputError( 'Unable to read atoms from QChem geometry output file {0}'. format(self.path)) return coord, number, mass
def transitionState(label, *args, **kwargs): """Load a transition state from an input file""" global transition_state_dict if label in transition_state_dict: raise ValueError( 'Multiple occurrences of transition state with label {0!r}.'. format(label)) logging.info('Loading transition state {0}...'.format(label)) ts = TransitionState(label=label) transition_state_dict[label] = ts if len(args) == 1 and len(kwargs) == 0: # The argument is a path to a conformer input file path = args[0] job = StatMechJob(species=ts, path=path) job_list.append(job) elif len(args) == 0: # The species parameters are given explicitly E0 = None modes = [] spin_multiplicity = 1 optical_isomers = 1 frequency = None for key, value in kwargs.items(): if key == 'E0': E0 = value elif key == 'modes': modes = value elif key == 'spinMultiplicity': spin_multiplicity = value elif key == 'opticalIsomers': optical_isomers = value elif key == 'frequency': frequency = value else: raise TypeError( 'transition_state() got an unexpected keyword argument {0!r}.' .format(key)) ts.conformer = Conformer(E0=E0, modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers) ts.frequency = frequency else: if len(args) == 0 and len(kwargs) == 0: raise InputError( 'The transition_state needs to reference a quantum job file or contain kinetic information.' ) raise InputError( 'The transition_state can only link a quantum job or directly input information, not both.' ) return ts
def database(thermoLibraries=None, transportLibraries=None, reactionLibraries=None, frequenciesLibraries=None, kineticsFamilies='default', kineticsDepositories='default', kineticsEstimator='rate rules'): """Load the RMG database""" thermo_libraries = as_list(thermoLibraries, default=[]) transport_libraries = as_list(transportLibraries, default=None) reaction_libraries = as_list(reactionLibraries, default=[]) database_directory = settings['database.directory'] if kineticsDepositories == 'default': kinetics_depositories = ['training'] elif kineticsDepositories == 'all': kinetics_depositories = None else: if not isinstance(kineticsDepositories, list): raise InputError( "kinetics_depositories should be either 'default', 'all', or a list of names eg. ['training','PrIMe']." ) kinetics_depositories = kineticsDepositories if kineticsFamilies in ('default', 'all', 'none'): kinetics_families = kineticsFamilies else: if not isinstance(kineticsFamilies, list): raise InputError( "kineticsFamilies should be either 'default', 'all', 'none', or a list of names eg. " "['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation']." ) kinetics_families = kineticsFamilies rmg_database = get_db() or RMGDatabase() rmg_database.load( path=database_directory, thermo_libraries=thermo_libraries, transport_libraries=transport_libraries, reaction_libraries=reaction_libraries, seed_mechanisms=[], kinetics_families=kinetics_families, kinetics_depositories=kinetics_depositories, depository= False, # Don't bother loading the depository information, as we don't use it ) for family in rmg_database.kinetics.families.values(): # load training if not family.auto_generated: family.add_rules_from_training(thermo_database=rmg_database.thermo) for family in rmg_database.kinetics.families.values(): family.fill_rules_by_averaging_up(verbose=True)
def simpleReactor(temperature, pressure, initialMoleFractions, terminationConversion=None, terminationTime=None, sensitivity=None, sensitivityThreshold=1e-3): logging.debug('Found SimpleReactor reaction system') for value in initialMoleFractions.values(): if value < 0: raise InputError('Initial mole fractions cannot be negative.') for spec in initialMoleFractions: initialMoleFractions[spec] = float(initialMoleFractions[spec]) totalInitialMoles = sum(initialMoleFractions.values()) if totalInitialMoles != 1: logging.warning( 'Initial mole fractions do not sum to one; normalizing.') logging.info('') logging.info('Original composition:') for spec, molfrac in initialMoleFractions.iteritems(): logging.info("{0} = {1}".format(spec, molfrac)) for spec in initialMoleFractions: initialMoleFractions[spec] /= totalInitialMoles logging.info('') logging.info('Normalized mole fractions:') for spec, molfrac in initialMoleFractions.iteritems(): logging.info("{0} = {1}".format(spec, molfrac)) T = Quantity(temperature) P = Quantity(pressure) termination = [] if terminationConversion is not None: for spec, conv in terminationConversion.iteritems(): termination.append(TerminationConversion(speciesDict[spec], conv)) if terminationTime is not None: termination.append(TerminationTime(Quantity(terminationTime))) if len(termination) == 0: raise InputError( 'No termination conditions specified for reaction system #{0}.'. format(len(rmg.reactionSystems) + 2)) sensitiveSpecies = [] if sensitivity: if isinstance(sensitivity, str): sensitivity = [sensitivity] for spec in sensitivity: sensitiveSpecies.append(speciesDict[spec]) system = SimpleReactor(T, P, initialMoleFractions, termination, sensitiveSpecies, sensitivityThreshold) rmg.reactionSystems.append(system)
def model(toleranceMoveToCore=None, toleranceMoveEdgeReactionToCore=numpy.inf, toleranceKeepInEdge=0.0, toleranceInterruptSimulation=1.0, toleranceMoveEdgeReactionToSurface=numpy.inf, toleranceMoveSurfaceSpeciesToCore=numpy.inf, toleranceMoveSurfaceReactionToCore=numpy.inf, toleranceMoveEdgeReactionToSurfaceInterrupt=None, toleranceMoveEdgeReactionToCoreInterrupt=None, maximumEdgeSpecies=1000000, minCoreSizeForPrune=50, minSpeciesExistIterationsForPrune=2, filterReactions=False, ignoreOverallFluxCriterion=False, maxNumSpecies=None, maxNumObjsPerIter=1, terminateAtMaxObjects=False, toleranceThermoKeepSpeciesInEdge=numpy.inf, dynamicsTimeScale=(0.0, 'sec')): """ How to generate the model. `toleranceMoveToCore` must be specified. toleranceMoveReactionToCore and toleranceReactionInterruptSimulation refers to an additional criterion for forcing an edge reaction to be included in the core by default this criterion is turned off Other parameters are optional and control the pruning. ignoreOverallFluxCriterion=True will cause the toleranceMoveToCore to be only applied to the pressure dependent network expansion and not movement of species from edge to core """ if toleranceMoveToCore is None: raise InputError( "You must provide a toleranceMoveToCore value. It should be less than or equal to toleranceInterruptSimulation which is currently {0}" .format(toleranceInterruptSimulation)) if toleranceMoveToCore > toleranceInterruptSimulation: raise InputError( "toleranceMoveToCore must be less than or equal to toleranceInterruptSimulation, which is currently {0}" .format(toleranceInterruptSimulation)) rmg.modelSettingsList.append( ModelSettings(toleranceMoveToCore, toleranceMoveEdgeReactionToCore, toleranceKeepInEdge, toleranceInterruptSimulation, toleranceMoveEdgeReactionToSurface, toleranceMoveSurfaceSpeciesToCore, toleranceMoveSurfaceReactionToCore, toleranceMoveEdgeReactionToSurfaceInterrupt, toleranceMoveEdgeReactionToCoreInterrupt, maximumEdgeSpecies, minCoreSizeForPrune, minSpeciesExistIterationsForPrune, filterReactions, ignoreOverallFluxCriterion, maxNumSpecies, maxNumObjsPerIter, terminateAtMaxObjects, toleranceThermoKeepSpeciesInEdge, Quantity(dynamicsTimeScale)))
def species(label, structure, reactive=True): logging.debug('Found {0} species "{1}" ({2})'.format('reactive' if reactive else 'nonreactive', label, structure.toSMILES())) if '+' in label: raise InputError('species {0} label cannot include a + sign'.format(label)) spec, isNew = rmg.reactionModel.makeNewSpecies(structure, label=label, reactive=reactive) if not isNew: raise InputError("Species {0} is a duplicate of {1}. Species in input file must be unique".format(label,spec.label)) # Force RMG to add the species to edge first, prior to where it is added to the core, in case it is found in # any reaction libraries along the way rmg.reactionModel.addSpeciesToEdge(spec) rmg.initialSpecies.append(spec) speciesDict[label] = spec
def process_model_chemistry(model_chemistry): """Process the model chemistry string representation Args: model_chemistry (str, unicode): A representation of the model chemistry in an sp//freq format e.g., 'CCSD(T)-F12a/aug-cc-pVTZ//B3LYP/6-311++G(3df,3pd)', or a composite method, e.g. 'CBS-QB3'. Returns: str, unicode: The single point energy level of theory str, unicode: The frequency level of theory """ if model_chemistry.count('//') > 1: raise InputError( 'The model chemistry seems wrong. It should either be a composite method (like CBS-QB3) ' 'or of the form sp//geometry, e.g., CCSD(T)-F12a/aug-cc-pVTZ//B3LYP/6-311++G(3df,3pd), ' 'and should not contain more than one appearance of "//".\n' 'Got: {0}'.format(model_chemistry)) elif '//' in model_chemistry: # assume this is an sp//freq format, split sp_level, freq_level = model_chemistry.split('//') else: # assume the sp and freq levels are the same, assign the model chemistry to both # (this could also be a composite method, and we'll expect the same behavior) sp_level = freq_level = model_chemistry return sp_level, freq_level
def convertBindingEnergies(bindingEnergies): """ Process the bindingEnergies from the input file. If "None" is passed, then it returns Pt(111) values. :param bindingEnergies: a dictionary of element symbol: binding energy pairs (or None) :return: the processed and checked dictionary """ if bindingEnergies is None: bindingEnergies = { # default values for Pt(111) 'C':(-6.750, 'eV/molecule'), 'H':(-2.479, 'eV/molecule'), 'O':(-3.586, 'eV/molecule'), 'N':(-4.352, 'eV/molecule'), } logging.info("Using default binding energies for Pt(111):\n{0!r}".format(bindingEnergies)) if not isinstance(bindingEnergies, dict): raise InputError("bindingEnergies should be None (for default) or a dict.") newDict = {} for element in 'CHON': try: newDict[element] = Energy(bindingEnergies[element]) except KeyError: logging.error('Element {} missing from bindingEnergies dictionary'.format(element)) raise return newDict
def explorer(source, explore_tol=0.01, energy_tol=np.inf, flux_tol=0.0, bathGas=None, maximumRadicalElectrons=np.inf): """Generate an explorer job""" global job_list, species_dict for job in job_list: if isinstance(job, PressureDependenceJob): pdepjob = job break else: raise InputError( 'the explorer block must occur after the pressureDependence block') source = [species_dict[name] for name in source] bath_gas = { species_dict[spec]: fraction for spec, fraction in bathGas.items() } if bathGas else None job = ExplorerJob(source=source, pdepjob=pdepjob, explore_tol=explore_tol, energy_tol=energy_tol, flux_tol=flux_tol, bath_gas=bath_gas, maximum_radical_electrons=maximumRadicalElectrons) job_list.append(job)
def explorer(source, explore_tol=(0.01, 's^-1'), energy_tol=np.inf, flux_tol=0.0, bathGas=None, maximumRadicalElectrons=np.inf): global jobList, speciesDict for job in jobList: if isinstance(job, PressureDependenceJob): pdepjob = job break else: raise InputError( 'the explorer block must occur after the pressureDependence block') source = [speciesDict[name] for name in source] explore_tol = Quantity(explore_tol) if bathGas: bathGas0 = bathGas or {} bathGas = {} for spec, fraction in bathGas0.items(): bathGas[speciesDict[spec]] = fraction job = ExplorerJob(source=source, pdepjob=pdepjob, explore_tol=explore_tol.value_si, energy_tol=energy_tol, flux_tol=flux_tol, bathGas=bathGas, maximumRadicalElectrons=maximumRadicalElectrons) jobList.append(job)
def mlEstimator(thermo=True, name='main', minHeavyAtoms=1, maxHeavyAtoms=None, H298UncertaintyCutoff=(3.0, 'kcal/mol'), S298UncertaintyCutoff=(2.0, 'cal/(mol*K)'), CpUncertaintyCutoff=(2.0, 'cal/(mol*K)')): from rmgpy.ml.estimator import MLEstimator # Currently only support thermo if thermo: models_path = os.path.join(settings['database.directory'], 'thermo', 'ml', name) if not os.path.exists(models_path): raise InputError( 'Cannot find ML models folder {}'.format(models_path)) H298_path = os.path.join(models_path, 'H298') S298_path = os.path.join(models_path, 'S298') Cp_path = os.path.join(models_path, 'Cp') rmg.ml_estimator = MLEstimator(H298_path, S298_path, Cp_path) uncertainty_cutoffs = dict(H298=Quantity(*H298UncertaintyCutoff), S298=Quantity(*S298UncertaintyCutoff), Cp=Quantity(*CpUncertaintyCutoff)) rmg.ml_settings = dict( min_heavy_atoms=minHeavyAtoms, max_heavy_atoms=maxHeavyAtoms, uncertainty_cutoffs=uncertainty_cutoffs, )
def loadScanEnergies(self): """ Extract the optimized energies in J/mol from a QChem log file, e.g. the result of a QChem "PES Scan" quantum chemistry calculation. """ Vlist = [] angle = [] read = False with open(self.path, 'r') as f: for line in f: if '-----------------' in line: read = False if read: values = [float(item) for item in line.split()] angle.append(values[0]) Vlist.append(values[1]) if 'Summary of potential scan:' in line: logging.info('found a sucessfully completed QChem Job') read = True elif 'SCF failed to converge' in line: raise InputError('QChem Job did not sucessfully complete: SCF failed to converge') logging.info(' Assuming {0} is the output from a QChem PES scan...'.format(os.path.basename(self.path))) Vlist = numpy.array(Vlist, numpy.float64) # check to see if the scanlog indicates that one of your reacting species may not be the lowest energy conformer check_conformer_energy(Vlist, self.path) # Adjust energies to be relative to minimum energy conformer # Also convert units from Hartree/particle to J/mol Vlist -= numpy.min(Vlist) Vlist *= constants.E_h * constants.Na angle = numpy.arange(0.0, 2*math.pi+0.00001, 2*math.pi/(len(Vlist)-1), numpy.float64) return Vlist, angle
def loadZeroPointEnergy(self,frequencyScaleFactor=1.): """ Load the unscaled zero-point energy in J/mol from a Qchem output file. """ ZPE = None f = open(self.path, 'r') line = f.readline() while line != '': # if 'Final energy is' in line: # E0 = float(line.split()[3]) * constants.E_h * constants.Na # print 'energy is' + str(E0) if 'Zero point vibrational energy' in line: # Qchem's ZPE is in kcal/mol ZPE = float(line.split()[4]) * 4184 # scaledZPE = ZPE * frequencyScaleFactor logging.debug('ZPE is {}'.format(str(ZPE))) # Read the next line in the file line = f.readline() # Close file when finished f.close() if ZPE is not None: return ZPE else: raise InputError('Unable to find zero-point energy in Qchem output file.')
def loadEnergy(self, frequencyScaleFactor=1.): """ Load the energy in J/mol from a Qchem log file. Only the last energy in the file is returned. The zero-point energy is *not* included in the returned value. """ E0 = None f = open(self.path, 'r') line = f.readline() while line != '': if 'Final energy is' in line: E0 = float(line.split()[3]) * constants.E_h * constants.Na logging.debug('energy is {}'.format(str(E0))) # elif 'Zero point vibrational energy' in line: #Qchem's ZPE is in kcal/mol # ZPE = float(line.split()[4]) * 4184 # scaledZPE = ZPE * frequencyScaleFactor # print 'ZPE is ' + str(ZPE) # Read the next line in the file line = f.readline() # Close file when finished f.close() if E0 is not None: return E0 else: raise InputError('Unable to find energy in Qchem output file.')
def determine_qm_software(fullpath): """ Given a path to the log file of a QM software, determine whether it is Gaussian, Molpro, or QChem """ with open(fullpath, 'r') as f: line = f.readline() software_log = None while line != '': if 'gaussian' in line.lower(): f.close() software_log = GaussianLog(fullpath) break elif 'qchem' in line.lower(): f.close() software_log = QChemLog(fullpath) break elif 'molpro' in line.lower(): f.close() software_log = MolproLog(fullpath) break line = f.readline() else: raise InputError( 'File at {0} could not be identified as a Gaussian, ' 'QChem or Molpro log file.'.format(fullpath)) return software_log
def determine_qm_software(fullpath): """ Given a path to the log file of a QM software, determine whether it is Gaussian, Molpro, QChem, or TeraChem """ with open(fullpath, 'r') as f: software_log = None if os.path.splitext(fullpath)[1] in ['.xyz', '.dat', '.geometry']: software_log = TeraChemLog(fullpath) line = f.readline() while software_log is None and line != '': if 'gaussian' in line.lower(): software_log = GaussianLog(fullpath) break elif 'molpro' in line.lower(): software_log = MolproLog(fullpath) break elif 'qchem' in line.lower(): software_log = QChemLog(fullpath) break elif 'terachem' in line.lower(): software_log = TeraChemLog(fullpath) break elif 'orca' in line.lower(): f.close() software_log = OrcaLog(fullpath) break line = f.readline() if software_log is None: f.close() raise InputError( f'The file at {fullpath} could not be identified as a ' 'Gaussian, Molpro, QChem, or TeraChem log file.') return software_log
def __init__(self, source, pdepjob, explore_tol, energy_tol=np.inf, flux_tol=0.0, bathGas=None, maximumRadicalElectrons=np.inf): self.source = source self.explore_tol = explore_tol self.energy_tol = energy_tol self.flux_tol = flux_tol self.maximumRadicalElectrons = maximumRadicalElectrons self.pdepjob = pdepjob if not hasattr(self.pdepjob, 'outputFile'): self.pdepjob.outputFile = None if bathGas: self.bathGas = bathGas elif self.pdepjob.network and self.pdepjob.network.bathGas: self.bathGas = self.pdepjob.network.bathGas else: raise InputError('bathGas not specified in explorer block')
def get_moment_of_inertia_tensor(coords, numbers=None, symbols=None): """ Calculate and return the moment of inertia tensor for the current geometry in amu*angstrom^2. If the coordinates are not at the center of mass, they are temporarily shifted there for the purposes of this calculation. Adapted from J.W. Allen: https://github.com/jwallen/ChemPy/blob/master/chempy/geometry.py Args: coords (np.array): Entries are 3-length lists of xyz coordinates for an atom. numbers (np.array, list): Entries are atomic numbers corresponding to coords. symbols (list): Entries are atom symbols corresponding to coords. Returns: np.array: The 3x3 moment of inertia tensor. Raises: InputError: If neither ``symbols`` nor ``numbers`` are given, or if they have a different length than ``coords`` """ if symbols is None and numbers is None: raise InputError('Either symbols or numbers must be given.') if numbers is not None: symbols = [symbol_by_number[number] for number in numbers] if len(coords) != len(symbols): raise InputError( f'The number of atoms ({len(symbols)}) is not equal to the number of ' f'atomic coordinates ({len(list(coords))})') tensor = np.zeros((3, 3), np.float64) center_of_mass = get_center_of_mass(coords=coords, numbers=numbers, symbols=symbols) for symbol, coord in zip(symbols, coords): mass = get_element_mass(symbol)[0] cm_coord = coord - center_of_mass tensor[0, 0] += mass * (cm_coord[1] * cm_coord[1] + cm_coord[2] * cm_coord[2]) tensor[1, 1] += mass * (cm_coord[0] * cm_coord[0] + cm_coord[2] * cm_coord[2]) tensor[2, 2] += mass * (cm_coord[0] * cm_coord[0] + cm_coord[1] * cm_coord[1]) tensor[0, 1] -= mass * cm_coord[0] * cm_coord[1] tensor[0, 2] -= mass * cm_coord[0] * cm_coord[2] tensor[1, 2] -= mass * cm_coord[1] * cm_coord[2] tensor[1, 0] = tensor[0, 1] tensor[2, 0] = tensor[0, 2] tensor[2, 1] = tensor[1, 2] return tensor
def liquidReactor(temperature, initialConcentrations, terminationConversion=None, terminationTime=None, sensitivity=None, sensitivityThreshold=1e-3, constantSpecies=None): logging.debug('Found LiquidReactor reaction system') T = Quantity(temperature) for spec, conc in initialConcentrations.iteritems(): concentration = Quantity(conc) # check the dimensions are ok # convert to mol/m^3 (or something numerically nice? or must it be SI) initialConcentrations[spec] = concentration.value_si termination = [] if terminationConversion is not None: for spec, conv in terminationConversion.iteritems(): termination.append(TerminationConversion(speciesDict[spec], conv)) if terminationTime is not None: termination.append(TerminationTime(Quantity(terminationTime))) if len(termination) == 0: raise InputError( 'No termination conditions specified for reaction system #{0}.'. format(len(rmg.reactionSystems) + 2)) sensitiveSpecies = [] if sensitivity: for spec in sensitivity: sensitiveSpecies.append(speciesDict[spec]) ##chatelak: check the constant species exist if constantSpecies is not None: logging.debug(' Generation with constant species:') for constantSpecie in constantSpecies: logging.debug(" {0}".format(constantSpecie)) if not speciesDict.has_key(constantSpecie): raise InputError( 'Species {0} not found in the input file'.format( constantSpecie)) system = LiquidReactor(T, initialConcentrations, termination, sensitiveSpecies, sensitivityThreshold, constantSpecies) rmg.reactionSystems.append(system)
def loadGeometry(self): """ Return the optimum geometry of the molecular configuration from the Molpro .out file. If multiple such geometries are identified, only the last is returned. """ symbol, coord, mass, number = [], [], [], [] f = open(self.path, 'r') line = f.readline() while line != '': # Automatically determine the number of atoms if 'Current geometry' in line: symbol, coord = [], [] while 'ENERGY' not in line: line = f.readline() line = f.readline() while line != '\n': data = line.split() symbol.append(str(data[0])) coord.append([float(data[1]), float(data[2]), float(data[3])]) line = f.readline() line = f.readline() line = f.readline() # Close file when finished f.close() # If no optimized coordinates were found, uses the input geometry # (for example if reading the geometry from a frequency file) if not coord: f = open(self.path, 'r') line = f.readline() while line != '': if 'atomic coordinates' in line.lower(): symbol, coord = [], [] for i in range(4): line = f.readline() while line != '\n': data = line.split() symbol.append(str(data[1])) coord.append([float(data[3]), float(data[4]), float(data[5])]) line = f.readline() line = f.readline() # Assign appropriate mass to each atom in the molecule for atom1 in symbol: mass1, num1 = get_element_mass(atom1) mass.append(mass1) number.append(num1) number = numpy.array(number, numpy.int) mass = numpy.array(mass, numpy.float64) coord = numpy.array(coord, numpy.float64) if len(number) == 0 or len(coord) == 0 or len(mass) == 0: raise InputError('Unable to read atoms from Molpro geometry output file {0}'.format(self.path)) return coord, number, mass
def pressureDependence( method, temperatures, pressures, maximumGrainSize=0.0, minimumNumberOfGrains=0, interpolation=None, maximumAtoms=None, ): from rmgpy.cantherm.pdep import PressureDependenceJob # Setting the pressureDependence attribute to non-None enables pressure dependence rmg.pressureDependence = PressureDependenceJob(network=None) # Process method rmg.pressureDependence.method = method # Process interpolation model if isinstance(interpolation, str): interpolation = (interpolation, ) if interpolation[0].lower() not in ("chebyshev", "pdeparrhenius"): raise InputError( "Interpolation model must be set to either 'Chebyshev' or 'PDepArrhenius'." ) rmg.pressureDependence.interpolationModel = interpolation # Process temperatures Tmin, Tmax, Tunits, Tcount = temperatures rmg.pressureDependence.Tmin = Quantity(Tmin, Tunits) rmg.pressureDependence.Tmax = Quantity(Tmax, Tunits) rmg.pressureDependence.Tcount = Tcount rmg.pressureDependence.generateTemperatureList() # Process pressures Pmin, Pmax, Punits, Pcount = pressures rmg.pressureDependence.Pmin = Quantity(Pmin, Punits) rmg.pressureDependence.Pmax = Quantity(Pmax, Punits) rmg.pressureDependence.Pcount = Pcount rmg.pressureDependence.generatePressureList() # Process grain size and count rmg.pressureDependence.maximumGrainSize = Quantity(maximumGrainSize) rmg.pressureDependence.minimumGrainCount = minimumNumberOfGrains # Process maximum atoms rmg.pressureDependence.maximumAtoms = maximumAtoms rmg.pressureDependence.activeJRotor = True rmg.pressureDependence.activeKRotor = True rmg.pressureDependence.rmgmode = True
def loadZeroPointEnergy(self,frequencyScaleFactor=1.): """ Load the unscaled zero-point energy in J/mol from a QChem output file. """ ZPE = None with open(self.path, 'r') as f: for line in f: if 'Zero point vibrational energy' in line: ZPE = float(line.split()[4]) * 4184 # QChem's ZPE is in kcal/mol # scaledZPE = ZPE * frequencyScaleFactor logging.debug('ZPE is {}'.format(str(ZPE))) if ZPE is not None: return ZPE else: raise InputError('Unable to find zero-point energy in QChem output file.')
def generatedSpeciesConstraints(**kwargs): validConstraints = [ 'allowed', 'maximumCarbonAtoms', 'maximumOxygenAtoms', 'maximumNitrogenAtoms', 'maximumSiliconAtoms', 'maximumSulfurAtoms', 'maximumHeavyAtoms', 'maximumRadicalElectrons', 'maximumSingletCarbenes', 'maximumCarbeneRadicals', 'allowSingletO2', 'maximumIsotopicAtoms' ] for key, value in kwargs.items(): if key not in validConstraints: raise InputError( 'Invalid generated species constraint {0!r}.'.format(key)) rmg.speciesConstraints[key] = value
def loadNegativeFrequency(self): """ Return the imaginary frequency from a transition state frequency calculation in cm^-1. """ frequency = 0 with open(self.path, 'r') as f: for line in f: # Read imaginary frequency if ' Frequency:' in line: frequency = float((line.split()[1])) break # Make sure the frequency is imaginary: if frequency < 0: return frequency else: raise InputError('Unable to find imaginary frequency in QChem output file {0}'.format(self.path))
def loadZeroPointEnergy(self): """ Load the unscaled zero-point energy in J/mol from a QChem output file. """ zpe = None with open(self.path, 'r') as f: for line in f: if 'Zero point vibrational energy' in line: zpe = float( line.split()[4] ) * 4184 # QChem's ZPE is in kcal/mol, convert to J/mol logging.debug('ZPE is {}'.format(str(zpe))) if zpe is not None: return zpe else: raise InputError( 'Unable to find zero-point energy in QChem output file.')
def ess_factory(fullpath: str, check_for_errors: bool = True, ) -> Type[ESSAdapter]: """ A factory generating the ESS adapter corresponding to ``ess_adapter``. Given a path to the log file of a QM software, determine whether it is Gaussian, Molpro, QChem, Orca, or TeraChem Args: fullpath (str): The disk location of the output file of interest. check_for_errors (bool): Boolean indicating whether to check the QM log for common errors before parsing relevant information. Returns: Type[ESSAdapter]: The requested ESSAdapter child, initialized with the respective arguments. """ ess_name = None if os.path.splitext(fullpath)[-1] in ['.xyz', '.dat', '.geometry']: ess_name = 'TeraChemLog' else: with open(fullpath, 'r') as f: line = f.readline() while ess_name is None and line != '': if 'gaussian' in line.lower(): ess_name = 'GaussianLog' break elif 'molpro' in line.lower(): ess_name = 'MolproLog' break elif 'O R C A' in line or 'orca' in line.lower(): ess_name = 'OrcaLog' break elif 'qchem' in line.lower(): ess_name = 'QChemLog' break elif 'terachem' in line.lower(): ess_name = 'TeraChemLog' break line = f.readline() if ess_name is None: raise InputError(f'The file at {fullpath} could not be identified as a ' f'Gaussian, Molpro, Orca, QChem, or TeraChem log file.') return _registered_ess_adapters[ess_name](path=fullpath, check_for_errors=check_for_errors)
def loadEnergy(self, frequencyScaleFactor=1.): """ Load the energy in J/mol from a QChem log file. Only the last energy in the file is returned. The zero-point energy is *not* included in the returned value. """ e0 = None with open(self.path, 'r') as f: a = b = 0 for line in f: if 'Final energy is' in line: a = float(line.split()[3]) * constants.E_h * constants.Na if 'Total energy in the final basis set' in line: b = float(line.split()[8]) * constants.E_h * constants.Na e0 = a or b if e0 is None: raise InputError('Unable to find energy in QChem output file.') return e0