def _run_statmech(self, arkane_spc, arkane_file, output_file_path=None, use_bac=False, kinetics=False, plot=False): """ A helper function for running an Arkane statmech job `arkane_spc` is the species() function from Arkane's input.py `arkane_file` is the Arkane species file (either .py or YAML form) `output_file_path` is a path to the Arkane output.py file `use_bac` is a bool flag indicating whether or not to use bond additivity corrections `kinetics` is a bool flag indicating whether this specie sis part of a kinetics job, in which case..?? `plot` is a bool flag indicating whether or not to plot a PDF of the calculated thermo properties """ success = True stat_mech_job = StatMechJob(arkane_spc, arkane_file) stat_mech_job.applyBondEnergyCorrections = use_bac and not kinetics and self.model_chemistry if not kinetics or kinetics and self.model_chemistry: # currently we have to use a model chemistry for thermo stat_mech_job.modelChemistry = self.model_chemistry else: # if this is a klinetics computation and we don't have a valid model chemistry, don't bother about it stat_mech_job.applyAtomEnergyCorrections = False stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor( self.model_chemistry) try: stat_mech_job.execute(outputFile=output_file_path, plot=plot) except Exception: success = False return success
def _run_statmech(self, arkane_spc, arkane_file, output_path=None, use_bac=False, kinetics=False, plot=False): """ A helper function for running an Arkane statmech job. Args: arkane_spc (str): An Arkane species() function representor. arkane_file (str): The path to the Arkane species file (either in .py or YAML form). output_path (str): The path to the folder containing the Arkane output.py file. use_bac (bool): A flag indicating whether or not to use bond additivity corrections (True to use). kinetics (bool) A flag indicating whether this specie is part of a kinetics job. plot (bool): A flag indicating whether to plot a PDF of the calculated thermo properties (True to plot) Returns: bool: Whether the job was successful (True for successful). """ success = True stat_mech_job = StatMechJob(arkane_spc, arkane_file) stat_mech_job.applyBondEnergyCorrections = use_bac and not kinetics and self.sp_level if not kinetics or (kinetics and self.sp_level): # currently we have to use a model chemistry for thermo stat_mech_job.modelChemistry = self.sp_level else: # if this is a kinetics computation and we don't have a valid model chemistry, don't bother about it stat_mech_job.applyAtomEnergyCorrections = False # Use the scaling factor if given, else try determining it from Arkane # (defaults to 1 and prints a warning if not found) stat_mech_job.frequencyScaleFactor = self.freq_scale_factor or assign_frequency_scale_factor(self.freq_level) try: stat_mech_job.execute(output_directory=os.path.join(output_path, 'output.py'), plot=plot) except Exception: success = False return success
def run_statmech( self, arkane_species: Type[Species], arkane_file_path: str, arkane_output_path: str = None, bac_type: Optional[str] = None, sp_level: Optional[Level] = None, plot: bool = False, ) -> bool: """ A helper function for running an Arkane statmech job. Args: arkane_species (arkane_input_species): An instance of an Arkane species() object. arkane_file_path (str): The path to the Arkane species file (either in .py or YAML form). arkane_output_path (str): The path to the folder in which the Arkane output.py file will be saved. bac_type (str, optional): The bond additivity correction type. 'p' for Petersson- or 'm' for Melius-type BAC. ``None`` to not use BAC. sp_level (Level, optional): The level of theory used for energy corrections. plot (bool): A flag indicating whether to plot a PDF of the calculated thermo properties (True to plot) Returns: bool: Whether the statmech job was successful. """ success = True stat_mech_job = StatMechJob(arkane_species, arkane_file_path) stat_mech_job.applyBondEnergyCorrections = bac_type is not None and sp_level is not None if bac_type is not None: stat_mech_job.bondEnergyCorrectionType = bac_type if sp_level is None: # if this is a kinetics computation and we don't have a valid model chemistry, don't bother about it stat_mech_job.applyAtomEnergyCorrections = False else: stat_mech_job.level_of_theory = sp_level.to_arkane_level_of_theory( ) stat_mech_job.frequencyScaleFactor = self.freq_scale_factor try: stat_mech_job.execute(output_directory=arkane_output_path, plot=plot) except Exception as e: logger.error( f'Arkane statmech job for species {arkane_species.label} failed with the error message:\n{e}' ) if stat_mech_job.applyBondEnergyCorrections \ and 'missing' in str(e).lower() and 'bac parameters for model chemistry' in str(e).lower(): # try executing Arkane w/o BACs logger.warning('Trying to run Arkane without BACs') stat_mech_job.applyBondEnergyCorrections = False try: stat_mech_job.execute(output_directory=arkane_output_path, plot=plot) except Exception as e: logger.error( f'Arkane statmech job for {arkane_species.label} failed with the error message:\n{e}' ) success = False else: success = False return success
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 test_specifying_absolute_file_paths(self): """Test specifying absolute file paths of statmech files""" h2o2_input = """#!/usr/bin/env python # -*- coding: utf-8 -*- bonds = {{'H-O': 2, 'O-O': 1}} externalSymmetry = 2 spinMultiplicity = 1 opticalIsomers = 1 energy = {{'b3lyp/6-311+g(3df,2p)': Log('{energy}')}} geometry = Log('{freq}') frequencies = Log('{freq}') rotors = [HinderedRotor(scanLog=Log('{scan}'), pivots=[1, 2], top=[1, 3], symmetry=1, fit='fourier')] """ abs_arkane_path = os.path.abspath(os.path.dirname( __file__)) # this is the absolute path to `.../RMG-Py/arkane` energy_path = os.path.join('arkane', 'data', 'H2O2', 'sp_a19032.out') freq_path = os.path.join('arkane', 'data', 'H2O2', 'freq_a19031.out') scan_path = os.path.join('arkane', 'data', 'H2O2', 'scan_a19034.out') h2o2_input = h2o2_input.format(energy=energy_path, freq=freq_path, scan=scan_path) h2o2_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'H2O2.py') if not os.path.exists(os.path.dirname(h2o2_path)): os.makedirs(os.path.dirname(h2o2_path)) with open(h2o2_path, 'w') as f: f.write(h2o2_input) h2o2 = Species(label='H2O2', smiles='OO') self.assertIsNone(h2o2.conformer) statmech_job = StatMechJob(species=h2o2, path=h2o2_path) statmech_job.level_of_theory = LevelOfTheory('b3lyp', '6-311+g(3df,2p)') statmech_job.load(pdep=False, plot=False) self.assertAlmostEqual(h2o2.conformer.E0.value_si, -146031.49933673252) os.remove(h2o2_path)
def species(label, *args, **kwargs): """Load a species from an input file""" global species_dict, job_list if label in species_dict: raise ValueError( 'Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) species_dict[label] = spec path = None if len(args) == 1: # The argument is a path to a conformer input file path = args[0] job = StatMechJob(species=spec, path=path) logging.debug('Added species {0} to a stat mech job.'.format(label)) job_list.append(job) elif len(args) > 1: raise InputError('species {0} can only have two non-keyword argument ' 'which should be the species label and the ' 'path to a quantum file.'.format(spec.label)) if len(kwargs) > 0: # The species parameters are given explicitly structure = None E0 = None modes = [] spin_multiplicity = 0 optical_isomers = 1 molecular_weight = None collision_model = None energy_transfer_model = None thermo = None reactive = True for key, value in kwargs.items(): if key == 'structure': structure = value elif key == 'E0': E0 = value elif key == 'modes': modes = value elif key == 'spinMultiplicity': spin_multiplicity = value elif key == 'opticalIsomers': optical_isomers = value elif key == 'molecularWeight': molecular_weight = value elif key == 'collisionModel': collision_model = value elif key == 'energyTransferModel': energy_transfer_model = value elif key == 'thermo': thermo = value elif key == 'reactive': reactive = value else: raise TypeError( 'species() got an unexpected keyword argument {0!r}.'. format(key)) if structure: spec.molecule = [structure] spec.conformer = Conformer(E0=E0, modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers) if molecular_weight is not None: spec.molecular_weight = molecular_weight elif spec.molecular_weight is None and is_pdep(job_list): # If a structure was given, simply calling spec.molecular_weight will calculate the molecular weight # If one of the jobs is pdep and no molecular weight is given or calculated, raise an error raise ValueError( "No molecularWeight was entered for species {0}. Since a structure wasn't given" " as well, the molecularWeight, which is important for pressure dependent jobs," " cannot be reconstructed.".format(spec.label)) spec.transport_data = collision_model spec.energy_transfer_model = energy_transfer_model spec.thermo = thermo spec.reactive = reactive if spec.reactive and path is None and spec.thermo is None and spec.conformer.E0 is None: if not spec.molecule: raise InputError( 'Neither thermo, E0, species file path, nor structure specified, cannot estimate' ' thermo properties of species {0}'.format(spec.label)) try: db = get_db('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warning( "The database isn't loaded, cannot estimate thermo for {0}. " "If it is a bath gas, set reactive = False to avoid generating " "thermo.".format(spec.label)) else: logging.info( 'No E0 or thermo found, estimating thermo and E0 of species {0} using' ' RMG-Database...'.format(spec.label)) spec.thermo = db.get_thermo_data(spec) if spec.thermo.E0 is None: th = spec.thermo.to_wilhoit() spec.conformer.E0 = th.E0 spec.thermo.E0 = th.E0 else: spec.conformer.E0 = spec.thermo.E0 if spec.reactive and spec.thermo and not spec.has_statmech( ) and structure is not None: # generate stat mech info if it wasn't provided before spec.generate_statmech() if not energy_transfer_model: # default to RMG's method of generating energy_transfer_model spec.generate_energy_transfer_model() return spec
def process(self): """Process ARC outputs and generate thermo and kinetics""" # Thermo: species_list_for_thermo_parity = list() species_for_thermo_lib = list() for species in self.species_dict.values(): if not species.is_ts and 'ALL converged' in self.output[ species.label]['status']: species_for_thermo_lib.append(species) output_file_path = self._generate_arkane_species_file(species) arkane_spc = arkane_species(str(species.label), species.arkane_file) if species.mol_list: arkane_spc.molecule = species.mol_list stat_mech_job = StatMechJob(arkane_spc, species.arkane_file) stat_mech_job.applyBondEnergyCorrections = self.use_bac stat_mech_job.modelChemistry = self.model_chemistry stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor( self.model_chemistry) stat_mech_job.execute(outputFile=output_file_path, plot=False) if species.generate_thermo: thermo_job = ThermoJob(arkane_spc, 'NASA') thermo_job.execute(outputFile=output_file_path, plot=False) species.thermo = arkane_spc.getThermoData() plotter.log_thermo(species.label, path=output_file_path) species.rmg_species = Species(molecule=[species.mol]) species.rmg_species.reactive = True if species.mol_list: species.rmg_species.molecule = species.mol_list # add resonance structures for thermo determination try: species.rmg_thermo = self.rmgdb.thermo.getThermoData( species.rmg_species) except ValueError: logging.info( 'Could not retrieve RMG thermo for species {0}, possibly due to missing 2D structure ' '(bond orders). Not including this species in the parity plots.' .format(species.label)) else: if species.generate_thermo: species_list_for_thermo_parity.append(species) # Kinetics: rxn_list_for_kinetics_plots = list() arkane_spc_dict = dict() # a dictionary with all species and the TSs for rxn in self.rxn_list: logging.info('\n\n') species = self.species_dict[rxn.ts_label] # The TS if 'ALL converged' in self.output[ species.label]['status'] and rxn.check_ts(): self.copy_freq_output_for_ts(species.label) success = True rxn_list_for_kinetics_plots.append(rxn) output_file_path = self._generate_arkane_species_file(species) arkane_ts = arkane_transition_state(str(species.label), species.arkane_file) arkane_spc_dict[species.label] = arkane_ts stat_mech_job = StatMechJob(arkane_ts, species.arkane_file) stat_mech_job.applyBondEnergyCorrections = False if not self.model_chemistry: stat_mech_job.modelChemistry = self.model_chemistry else: stat_mech_job.applyAtomEnergyCorrections = False stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor( self.model_chemistry) stat_mech_job.execute(outputFile=None, plot=False) for spc in rxn.r_species + rxn.p_species: if spc.label not in arkane_spc_dict.keys(): # add an extra character to the arkane_species label to distinguish between species calculated # for thermo and species calculated for kinetics (where we don't want to use BAC) arkane_spc = arkane_species(str(spc.label + '_'), spc.arkane_file) stat_mech_job = StatMechJob(arkane_spc, spc.arkane_file) arkane_spc_dict[spc.label] = arkane_spc stat_mech_job.applyBondEnergyCorrections = False if not self.model_chemistry: stat_mech_job.modelChemistry = self.model_chemistry else: stat_mech_job.applyAtomEnergyCorrections = False stat_mech_job.frequencyScaleFactor = assign_frequency_scale_factor( self.model_chemistry) stat_mech_job.execute(outputFile=None, plot=False) # thermo_job = ThermoJob(arkane_spc, 'NASA') # thermo_job.execute(outputFile=None, plot=False) # arkane_spc.thermo = arkane_spc.getThermoData() rxn.dh_rxn298 = sum([product.thermo.getEnthalpy(298) for product in arkane_spc_dict.values() if product.label in rxn.products])\ - sum([reactant.thermo.getEnthalpy(298) for reactant in arkane_spc_dict.values() if reactant.label in rxn.reactants]) arkane_rxn = arkane_reaction( label=str(rxn.label), reactants=[ str(label + '_') for label in arkane_spc_dict.keys() if label in rxn.reactants ], products=[ str(label + '_') for label in arkane_spc_dict.keys() if label in rxn.products ], transitionState=rxn.ts_label, tunneling='Eckart') kinetics_job = KineticsJob(reaction=arkane_rxn, Tmin=self.t_min, Tmax=self.t_max, Tcount=self.t_count) logging.info('Calculating rate for reaction {0}'.format( rxn.label)) try: kinetics_job.execute(outputFile=output_file_path, plot=False) except ValueError as e: """ ValueError: One or both of the barrier heights of -9.35259 and 62.6834 kJ/mol encountered in Eckart method are invalid. """ logging.error( 'Failed to generate kinetics for {0} with message:\n{1}' .format(rxn.label, e)) success = False if success: rxn.kinetics = kinetics_job.reaction.kinetics plotter.log_kinetics(species.label, path=output_file_path) rxn.rmg_reactions = rmgdb.determine_rmg_kinetics( rmgdb=self.rmgdb, reaction=rxn.rmg_reaction, dh_rxn298=rxn.dh_rxn298) logging.info('\n\n') output_dir = os.path.join(self.project_directory, 'output') if species_list_for_thermo_parity: plotter.draw_thermo_parity_plots(species_list_for_thermo_parity, path=output_dir) libraries_path = os.path.join(output_dir, 'RMG libraries') # species_list = [spc for spc in self.species_dict.values()] plotter.save_thermo_lib(species_for_thermo_lib, path=libraries_path, name=self.project, lib_long_desc=self.lib_long_desc) if rxn_list_for_kinetics_plots: plotter.draw_kinetics_plots(rxn_list_for_kinetics_plots, path=output_dir, t_min=self.t_min, t_max=self.t_max, t_count=self.t_count) libraries_path = os.path.join(output_dir, 'RMG libraries') plotter.save_kinetics_lib(rxn_list=rxn_list_for_kinetics_plots, path=libraries_path, name=self.project, lib_long_desc=self.lib_long_desc) self.clean_output_directory()