def process_arc_project( thermo_adapter: str, kinetics_adapter: str, project: str, project_directory: str, species_dict: dict, reactions: list, output_dict: dict, bac_type: Optional[str] = None, sp_level: Optional[Level] = None, freq_scale_factor: float = 1.0, compute_thermo: bool = True, compute_rates: bool = True, compute_transport: bool = False, T_min: tuple = None, T_max: tuple = None, T_count: int = 50, lib_long_desc: str = '', rmg_database: Optional[RMGDatabase] = None, compare_to_rmg: bool = True, three_params: bool = True, ) -> None: """ Process an ARC project, generate thermo and rate coefficients using statistical mechanics (statmech). Args: thermo_adapter (str): The software to use for calculating thermodynamic data. kinetics_adapter (str): The software to use for calculating rate coefficients. project (str): The ARC project name. project_directory (str): The path to the ARC project directory. species_dict (dict): Keys are labels, values are ARCSpecies objects. reactions (list): Entries are ARCReaction objects. output_dict (dict): Keys are labels, values are output file paths. See Scheduler for a description of this dictionary. 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. freq_scale_factor (float, optional): The harmonic frequencies scaling factor. compute_thermo (bool, optional): Whether to compute thermodynamic properties for the provided species. compute_rates (bool, optional): Whether to compute high pressure limit rate coefficients. compute_transport (bool, optional): Whether to compute transport properties. T_min (tuple, optional): The minimum temperature for kinetics computations, e.g., (500, 'K'). T_max (tuple, optional): The maximum temperature for kinetics computations, e.g., (3000, 'K'). T_count (int, optional): The number of temperature points between ``T_min`` and ``T_max``. lib_long_desc (str, optional): A multiline description of levels of theory for the resulting RMG libraries. rmg_database (RMGDatabase, optional): The RMG database object. compare_to_rmg (bool, optional): If ``True``, ARC's calculations will be compared against estimations from RMG's database. three_params (bool, optional): Compute rate coefficients using the modified three-parameter Arrhenius equation format (``True``, default) or classical two-parameter Arrhenius equation format (``False``). """ T_min = T_min or (300, 'K') T_max = T_max or (3000, 'K') if isinstance(T_min, (int, float)): T_min = (T_min, 'K') if isinstance(T_max, (int, float)): T_max = (T_max, 'K') T_count = T_count or 50 species_for_thermo_lib, unconverged_species = list(), list() rxns_for_kinetics_lib, unconverged_rxns = list(), list() species_for_transport_lib = list() bde_report = dict() output_directory = os.path.join(project_directory, 'output') libraries_path = os.path.join(output_directory, 'RMG libraries') if not os.path.isdir(output_directory): os.makedirs(output_directory) # guarantees that the adapters are supported: thermo_adapter_label = StatmechEnum(thermo_adapter) kinetics_adapter_label = StatmechEnum(kinetics_adapter) # 1. Rates if compute_rates: for reaction in reactions: species_converged = True considered_labels = list( ) # species labels considered in this reaction if output_dict[reaction.ts_label]['convergence']: for species in reaction.r_species + reaction.p_species: if species.label in considered_labels: # consider cases where the same species appears in a reaction both as a reactant # and as a product (e.g., H2O that catalyzes a reaction). continue considered_labels.append(species.label) if output_dict[species.label]['convergence']: statmech_adapter = statmech_factory( statmech_adapter_label=kinetics_adapter_label, output_directory=output_directory, output_dict=output_dict, bac_type=None, sp_level=sp_level, freq_scale_factor=freq_scale_factor, species=species, ) statmech_adapter.compute_thermo(kinetics_flag=True) else: logger.error( f'Species {species.label} did not converge, cannot compute a rate coefficient ' f'for {reaction.label}') unconverged_species.append(species) species_converged = False if species_converged: statmech_adapter = statmech_factory( statmech_adapter_label=kinetics_adapter_label, output_directory=output_directory, output_dict=output_dict, bac_type=None, sp_level=sp_level, freq_scale_factor=freq_scale_factor, reaction=reaction, species_dict=species_dict, T_min=T_min, T_max=T_max, T_count=T_count, three_params=three_params, ) statmech_adapter.compute_high_p_rate_coefficient() if reaction.kinetics is not None: rxns_for_kinetics_lib.append(reaction) else: unconverged_rxns.append(reaction) else: unconverged_rxns.append(reaction) if rxns_for_kinetics_lib: plotter.save_kinetics_lib(rxn_list=rxns_for_kinetics_lib, path=libraries_path, name=project, lib_long_desc=lib_long_desc) # 2. Thermo if compute_thermo: for species in species_dict.values(): if (species.compute_thermo or species.e0_only ) and output_dict[species.label]['convergence']: statmech_adapter = statmech_factory( statmech_adapter_label=thermo_adapter_label, output_directory=output_directory, output_dict=output_dict, bac_type=bac_type, sp_level=sp_level, freq_scale_factor=freq_scale_factor, species=species, ) statmech_adapter.compute_thermo(kinetics_flag=False, e0_only=species.e0_only) if species.thermo is not None: species_for_thermo_lib.append(species) elif not species.e0_only and species not in unconverged_species: unconverged_species.append(species) elif species.compute_thermo and not output_dict[species.label]['convergence'] \ and species not in unconverged_species: unconverged_species.append(species) if species_for_thermo_lib: plotter.save_thermo_lib(species_list=species_for_thermo_lib, path=libraries_path, name=project, lib_long_desc=lib_long_desc) # 3. Transport if compute_transport: for species in species_dict.values(): if output_dict[species.label]['job_types'][ 'onedmin'] and output_dict[species.label]['convergence']: pass # todo if species_for_transport_lib: plotter.save_transport_lib(species_list=species_for_thermo_lib, path=libraries_path, name=project, lib_long_desc=lib_long_desc) # 4. BDE for species in species_dict.values(): # looping again to make sure all relevant Species.e0 attributes were set if species.bdes is not None: bde_report[species.label] = process_bdes(label=species.label, species_dict=species_dict) if bde_report: bde_path = os.path.join(project_directory, 'output', 'BDE_report.txt') plotter.log_bde_report(path=bde_path, bde_report=bde_report, spc_dict=species_dict) # Comparisons if compare_to_rmg: try: load_rmg_database(rmg_database=rmg_database, species_dict=species_dict, output_dict=output_dict) except Exception as e: logger.error(f'Could not load the RMG database! Got:\n{e}') else: compare_thermo(species_for_thermo_lib=species_for_thermo_lib, rmg_database=rmg_database, output_directory=output_directory) compare_rates(rxns_for_kinetics_lib, rmg_database, output_directory=output_directory, T_min=T_min, T_max=T_max, T_count=T_count) compare_transport(species_for_transport_lib, rmg_database, output_directory=output_directory) write_unconverged_log(unconverged_species=unconverged_species, unconverged_rxns=unconverged_rxns, log_file_path=os.path.join( output_directory, 'unconverged_species.log')) clean_output_directory(project_directory)
def process(self): """ Process ARC outputs and generate thermo and kinetics. """ # Thermo: species_list_for_thermo_parity = list() species_for_thermo_lib = list() species_for_transport_lib = list() unconverged_species = list() for species in self.species_dict.values(): if not species.is_ts and 'ALL converged' in self.output[species.label]['status']: output_path = self._generate_arkane_species_file(species) unique_arkane_species_label = False while not unique_arkane_species_label: try: arkane_spc = arkane_input_species(str(species.label), species.arkane_file) except ValueError: species.label += '_' + str(randint(0, 999)) else: unique_arkane_species_label = True species.rmg_species = Species(molecule=[species.mol]) species.rmg_species.reactive = True if species.mol_list: arkane_spc.molecule = species.mol_list species.rmg_species.molecule = species.mol_list # add resonance structures for thermo determination statmech_success = self._run_statmech(arkane_spc, species.arkane_file, output_path, use_bac=self.use_bac) if not statmech_success: continue if species.generate_thermo: thermo_job = ThermoJob(arkane_spc, 'NASA') thermo_job.execute(output_directory=output_path, plot=False) species.thermo = arkane_spc.getThermoData() plotter.log_thermo(species.label, path=output_path) species_for_thermo_lib.append(species) if self.use_bac and self.sp_level: # If BAC was used, save another Arkane YAML file of this species with no BAC, so it can be used # for further rate calculations if needed (where the conformer.E0 has no BAC) statmech_success = self._run_statmech(arkane_spc, species.arkane_file, output_path, use_bac=False) # if statmech_success: # arkane_spc.label += str('_no_BAC') # arkane_spc.thermo = None # otherwise thermo won't be calculated, although we don't really care # thermo_job = ThermoJob(arkane_spc, 'NASA') # thermo_job.execute(output_directory=output_path, plot=False) try: species.rmg_thermo = self.rmgdb.thermo.getThermoData(species.rmg_species) except (ValueError, AttributeError) as e: logger.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.' '\nGot: {1}'.format(species.label, e.message)) else: if species.generate_thermo: species_list_for_thermo_parity.append(species) if 'onedmin converged' in self.output[species.label]['status'].lower(): species_for_transport_lib.append(species) elif 'ALL converged' not in self.output[species.label]['status']: unconverged_species.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: logger.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_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 self._run_statmech(arkane_ts, species.arkane_file, kinetics=True) 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_input_species(str(spc.label + '_'), spc.arkane_file) self._run_statmech(arkane_spc, spc.arkane_file, kinetics=True) 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) logger.info('Calculating rate for reaction {0}'.format(rxn.label)) try: kinetics_job.execute(output_directory=output_path, plot=False) except (ValueError, OverflowError) as e: # ValueError: One or both of the barrier heights of -9.3526 and 62.683 kJ/mol encountered in Eckart # method are invalid. # # File "/home/alongd/Code/RMG-Py/arkane/kinetics.py", line 136, in execute # self.generateKinetics(self.Tlist.value_si) # File "/home/alongd/Code/RMG-Py/arkane/kinetics.py", line 179, in generateKinetics # klist[i] = self.reaction.calculateTSTRateCoefficient(Tlist[i]) # File "rmgpy/reaction.py", line 818, in rmgpy.reaction.Reaction.calculateTSTRateCoefficient # File "rmgpy/reaction.py", line 844, in rmgpy.reaction.Reaction.calculateTSTRateCoefficient # OverflowError: math range error logger.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_path) rxn.rmg_reactions = rmgdb.determine_rmg_kinetics(rmgdb=self.rmgdb, reaction=rxn.rmg_reaction, dh_rxn298=rxn.dh_rxn298) logger.info('\n\n') output_dir = os.path.join(self.project_directory, 'output') libraries_path = os.path.join(output_dir, 'RMG libraries') if species_list_for_thermo_parity: plotter.draw_thermo_parity_plots(species_list_for_thermo_parity, path=output_dir) plotter.save_thermo_lib(species_for_thermo_lib, path=libraries_path, name=self.project, lib_long_desc=self.lib_long_desc) if species_for_transport_lib: plotter.save_transport_lib(species_for_thermo_lib, path=libraries_path, name=self.project) 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) 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() if unconverged_species: if not os.path.isdir(output_dir): os.makedirs(output_dir) with open(os.path.join(output_dir, 'unconverged_species.log'), 'w') as f: for spc in unconverged_species: f.write(spc.label) if spc.is_ts: f.write(str(' rxn: {0}'.format(spc.rxn_label))) elif spc.mol is not None: f.write(str(' SMILES: {0}'.format(spc.mol.toSMILES()))) f.write(str('\n'))
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()