Example #1
0
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)
Example #2
0
    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'))
Example #3
0
    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()