Ejemplo n.º 1
0
    def generate_statmech(self):
        """
        Generate molecular degree of freedom data for the species. You must
        have already provided a thermodynamics model using e.g.
        :meth:`generate_thermo_data()`.
        """
        logging.debug("Generating statmech for species {}".format(self.label))
        from rmgpy.data.rmg import get_db
        try:
            statmech_db = get_db('statmech')
            if not statmech_db: raise Exception
        except Exception:
            logging.debug(
                'Could not obtain the stat. mech database. Not generating stat. mech...'
            )
            raise

        molecule = self.molecule[0]
        conformer = statmech_db.get_statmech_data(molecule,
                                                  self.get_thermo_data())

        if self.conformer is None:
            self.conformer = Conformer()

        if self.conformer.E0 is None:
            self.set_e0_with_thermo()

        self.conformer.modes = conformer.modes
        self.conformer.spin_multiplicity = conformer.spin_multiplicity
        if self.conformer.E0 is None or not self.has_statmech():
            logging.error('The conformer in question is {}'.format(
                self.conformer))
            raise StatmechError(
                'Species {0} does not have stat mech after generate_statmech called'
                .format(self.label))
Ejemplo n.º 2
0
def reaction(label, reactants, products, transitionState=None, kinetics=None, tunneling=''):
    """Load a reaction from an input file"""
    global reaction_dict, species_dict, transition_state_dict
    if label in reaction_dict:
        label = label + transitionState
        if label in reaction_dict:
            raise ValueError('Multiple occurrences of reaction with label {0!r}.'.format(label))
    logging.info('Loading reaction {0}...'.format(label))
    reactants = sorted([species_dict[spec] for spec in reactants])
    products = sorted([species_dict[spec] for spec in products])
    if transitionState:
        transitionState = transition_state_dict[transitionState]
    if transitionState and (tunneling == '' or tunneling is None):
        transitionState.tunneling = None
    elif tunneling.lower() == 'wigner':
        transitionState.tunneling = Wigner(frequency=None)
    elif tunneling.lower() == 'eckart':
        transitionState.tunneling = Eckart(frequency=None, E0_reac=None, E0_TS=None, E0_prod=None)

    elif transitionState and not isinstance(tunneling, TunnelingModel):
        raise ValueError('Unknown tunneling model {0!r}.'.format(tunneling))
    rxn = Reaction(label=label, reactants=reactants, products=products, transition_state=transitionState,
                   kinetics=kinetics)

    if rxn.transition_state is None and rxn.kinetics is None:
        logging.info('estimating rate of reaction {0} using RMG-database')
        if not all([m.molecule != [] for m in rxn.reactants + rxn.products]):
            raise ValueError('chemical structures of reactants and products not available for RMG estimation of '
                             'reaction {0}'.format(label))
        db = get_db('kinetics')
        rxns = db.generate_reactions_from_libraries(reactants=rxn.reactants, products=rxn.products)
        rxns = [r for r in rxns if r.elementary_high_p]

        if rxns:
            for r in rxns:
                if isinstance(rxn.kinetics, PDepKineticsModel):
                    boo = rxn.generate_high_p_limit_kinetics()
                if boo:
                    rxn = r
                    break

        if rxns == [] or not boo:
            logging.info('No library reactions tagged with elementary_high_p found for reaction {0}, generating '
                         'reactions from RMG families'.format(label))
            rxn = list(db.generate_reactions_from_families(reactants=rxn.reactants, products=rxn.products))
            model = CoreEdgeReactionModel()
            model.verbose_comments = True
            for r in rxn:
                model.apply_kinetics_to_reaction(r)

    if isinstance(rxn, Reaction):
        reaction_dict[label] = rxn
    else:
        for i in range(len(rxn)):
            reaction_dict[label + str(i)] = rxn[i]

    return rxn
Ejemplo n.º 3
0
def load_necessary_databases():
    """
    loads transport and statmech databases
    """
    from rmgpy.data.statmech import StatmechDatabase
    from rmgpy.data.transport import TransportDatabase

    # only load if they are not there already.
    try:
        get_db('transport')
        get_db('statmech')
    except DatabaseError:
        logging.info("Databases not found. Making databases")
        db = RMGDatabase()
        db.statmech = StatmechDatabase()
        db.statmech.load(os.path.join(settings['database.directory'], 'statmech'))

        db.transport = TransportDatabase()
        db.transport.load(os.path.join(settings['database.directory'], 'transport'))
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
    def generate_transport_data(self):
        """
        Generate the transport_data parameters for the species.
        """
        from rmgpy.data.rmg import get_db
        try:
            transport_db = get_db('transport')
            if not transport_db: raise Exception
        except Exception:
            logging.debug(
                'Could not obtain the transport database. Not generating transport...'
            )
            raise

        # count = sum([1 for atom in self.molecule[0].vertices if atom.is_non_hydrogen()])
        self.transport_data = transport_db.get_transport_properties(self)[0]
Ejemplo n.º 6
0
def react_species(species_tuple, only_families=None):
    """
    Given a tuple of Species objects, generates all possible reactions
    from the loaded reaction families and combines degenerate reactions.

    Args:
        species_tuple (tuple): tuple of 1-3 Species objects to react together
        only_families (list, optional): list of reaction families to consider

    Returns:
        list of generated reactions
    """
    reactions = get_db('kinetics').generate_reactions_from_families(
        species_tuple, only_families=only_families)

    return reactions
Ejemplo n.º 7
0
def generate_thermo_data(spc, thermo_class=NASA, solvent_name=''):
    """
    Generates thermo data, first checking Libraries, then using either QM or Database.
    
    The database generates the thermo data for each structure (resonance isomer),
    picks that with lowest H298 value.
    
    It then calls :meth:`process_thermo_data`, to convert (via Wilhoit) to NASA
    and set the E0.
    
    Result stored in `spc.thermo` and returned.
    """

    try:
        thermodb = get_db('thermo')
        if not thermodb: raise Exception
    except Exception:
        logging.debug(
            'Could not obtain the thermo database. Not generating thermo...')
        return None

    thermo0 = thermodb.get_thermo_data(spc)

    # 1. maybe only submit cyclic core
    # 2. to help radical prediction, HBI should also
    #    look up centrailThermoDB for its saturated version
    #    currently it only looks up libraries or estimates via GAV
    from rmgpy.rmg.input import get_input

    try:
        thermo_central_database = get_input('thermo_central_database')
    except Exception:
        logging.debug('thermoCentralDatabase could not be found.')
        thermo_central_database = None

    if thermo_central_database and thermo_central_database.client \
            and thermo_central_database.satisfy_registration_requirements(spc, thermo0, thermodb):
        thermo_central_database.register_in_central_thermo_db(spc)

    return process_thermo_data(spc, thermo0, thermo_class, solvent_name)
Ejemplo n.º 8
0
def ensure_reaction_direction(isotopomer_rxns):
    """
    given a list of reactions with varying isotope labels but identical structure,
    obtained from the `cluster` method, this method remakes the kinetics so that
    they all face the same direction.
    """

    # find isotopeless reaction as standard
    reference = isotopomer_rxns[0]
    family = get_db('kinetics').families[reference.family]
    if family.own_reverse:
        for rxn in isotopomer_rxns:
            if not compare_isotopomers(rxn, reference, either_direction=False):
                # the reaction is in the oposite direction
                logging.info(
                    'isotope: identified flipped reaction direction in reaction number {} of reaction {}. '
                    'Altering the direction.'.format(rxn.index, str(rxn)))
                # obtain reverse attribute with template and degeneracy
                family.add_reverse_attribute(rxn)
                if frozenset(rxn.reverse.template) != frozenset(
                        reference.template):
                    logging.warning(
                        "Reaction {} did not find proper reverse template, might cause "
                        "degeneracy error.".format(str(rxn)))
                # reverse reactants and products of original reaction
                rxn.reactants, rxn.products = rxn.products, rxn.reactants
                rxn.pairs = [(p, r) for r, p in rxn.pairs]
                # set degeneracy to isotopeless reaction
                rxn.degeneracy = reference.degeneracy
                # make this reaction have kinetics of isotopeless reaction
                new_kinetics = deepcopy(reference.kinetics)
                rxn.kinetics = new_kinetics
                rxn.template = reference.template
                # set degeneracy to new reaction
                rxn.degeneracy = rxn.reverse.degeneracy
                # delete reverse attribute
                rxn.reverse = None
Ejemplo n.º 9
0
def generate_isotope_reactions(isotopeless_reactions, isotopes):
    """
    Find the list of isotope reactions based on the reactions in the isotopeless
    reaction.

    uses the reactSpecies method to find reactions with proper degeneracies and
    then filters out those that don't match products. the proper reactions are
    given kinetics of the previous reaction modified for the degeneracy difference.
    """
    # make sure all isotopeless reactions have templates and are TemplateReaction objects
    for rxn in isotopeless_reactions:
        if not isinstance(rxn, TemplateReaction):
            raise TypeError(
                'reactions sent to generate_isotope_reactions must be a TemplateReaction object'
            )
        if rxn.template is None:
            raise AttributeError(
                'isotope reaction {0} does not have a template attribute. '
                'The object is:\n\n{1}'.format(str(rxn), repr(rxn)))

    found_reactions = []
    rxn_index = 0
    while rxn_index < len(isotopeless_reactions):
        rxn = isotopeless_reactions[rxn_index]
        # find all reactions involving same reactants
        rxns_w_same_reactants = [rxn]
        rxn_index2 = rxn_index + 1
        while rxn_index2 < len(isotopeless_reactions):
            if same_species_lists(
                    isotopeless_reactions[rxn_index].reactants,
                    isotopeless_reactions[rxn_index2].reactants,
            ):
                rxns_w_same_reactants.append(isotopeless_reactions[rxn_index2])
                del isotopeless_reactions[rxn_index2]
            else:
                rxn_index2 += 1
        ##### find all pairs of reacting isotoper species #####
        # find the lists of reactants that have identical isotopomers
        reactants = []
        for reactant in rxn.reactants:
            for iso_index, isotopomers in enumerate(isotopes):
                if compare_isotopomers(reactant, isotopomers[0]):
                    reactants.append(iso_index)
                    break
        # find pairs of all reactants to react together
        reactant_pairs = []
        if len(rxn.reactants) == 1:
            reactant_pairs = [[spec] for spec in isotopes[reactants[0]]]
        elif len(rxn.reactants) == 2:
            for spec1 in isotopes[reactants[0]]:
                for spec2 in isotopes[reactants[1]]:
                    reactant_pairs.append([spec1, spec2])
        else:
            raise ValueError('Cannot process reactions with over 2 reactants')

        # remove identical pairs
        rxn_index3 = 0
        while rxn_index3 < len(reactant_pairs):
            rxn_index4 = rxn_index3 + 1
            while rxn_index4 < len(reactant_pairs):
                if same_species_lists(reactant_pairs[rxn_index3],
                                      reactant_pairs[rxn_index4]):
                    del reactant_pairs[rxn_index4]
                else:
                    rxn_index4 += 1
            rxn_index3 += 1

        # make reaction objects
        for pair in reactant_pairs:
            # copy species so they don't get modified
            species_tuple = tuple([spc.copy(deep=True) for spc in pair])
            unfiltered_rxns = get_db(
                'kinetics').generate_reactions_from_families(
                    species_tuple, only_families=[rxn.family])
            # remove reactions whose products don't match the original reactions
            rxn_index5 = 0
            while rxn_index5 < len(unfiltered_rxns):
                for isotopeless_reaction in rxns_w_same_reactants:
                    isotopeless_kinetics = isotopeless_reaction.kinetics
                    isotopeless_degeneracy = isotopeless_reaction.degeneracy
                    if compare_isotopomers(isotopeless_reaction, unfiltered_rxns[rxn_index5], either_direction=False) \
                            and isotopeless_reaction.family == unfiltered_rxns[rxn_index5].family \
                            and frozenset(isotopeless_reaction.template) == \
                            frozenset(unfiltered_rxns[rxn_index5].template):
                        # apply kinetics to new reaction & modify for degeneracy
                        unfiltered_rxns[rxn_index5].kinetics = deepcopy(
                            isotopeless_kinetics)
                        unfiltered_rxns[rxn_index5].kinetics.change_rate(
                            unfiltered_rxns[rxn_index5].degeneracy /
                            isotopeless_degeneracy)
                        rxn_index5 += 1
                        break
                else:  # did not find same prodcuts
                    del unfiltered_rxns[rxn_index5]
            found_reactions.extend(unfiltered_rxns)
        rxn_index += 1
    return found_reactions
Ejemplo n.º 10
0
def process_thermo_data(spc, thermo0, thermo_class=NASA, solvent_name=''):
    """
    Converts via Wilhoit into required `thermo_class` and sets `E0`.
    
    Resulting thermo is returned.
    """
    thermo = None

    # Always convert to Wilhoit so we can compute E0
    if isinstance(thermo0, Wilhoit):
        wilhoit = thermo0
    elif isinstance(thermo0, ThermoData):
        wilhoit = thermo0.to_wilhoit(B=1000.)
    else:
        wilhoit = thermo0.to_wilhoit()

    # Add on solvation correction
    solvation_database = get_db('solvation')
    if not solvent_name or solvation_database is None:
        logging.debug(
            'Solvent database or solvent_name not found. Solvent effect was not utilized'
        )
        solvent_data = None
    else:
        solvent_data = solvation_database.get_solvent_data(solvent_name)
    if solvent_data and not "Liquid thermo library" in thermo0.comment:
        solvation_database = get_db('solvation')
        solute_data = solvation_database.get_solute_data(spc)
        solvation_correction = solvation_database.get_solvation_correction(
            solute_data, solvent_data)
        # correction is added to the entropy and enthalpy
        wilhoit.S0.value_si = (wilhoit.S0.value_si +
                               solvation_correction.entropy)
        wilhoit.H0.value_si = (wilhoit.H0.value_si +
                               solvation_correction.enthalpy)

    # Compute E0 by extrapolation to 0 K
    if spc.conformer is None:
        spc.conformer = Conformer()
    spc.conformer.E0 = wilhoit.E0

    # Convert to desired thermo class
    if thermo_class is Wilhoit:
        thermo = wilhoit
    elif thermo_class is NASA:
        if solvent_data:
            # If liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary.
            # Otherwise convert wilhoit to NASA
            if "Liquid thermo library" in thermo0.comment and isinstance(
                    thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
        else:
            # gas phase with species matching thermo library keep the NASA from library or convert if group additivity
            if "Thermo library" in thermo0.comment and isinstance(
                    thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
    else:
        raise Exception(
            'thermo_class neither NASA nor Wilhoit.  Cannot process thermo data.'
        )

    return thermo
Ejemplo n.º 11
0
def react_all(core_spc_list,
              num_old_core_species,
              unimolecular_react,
              bimolecular_react,
              trimolecular_react=None,
              procnum=1):
    """
    Reacts the core species list via uni-, bi-, and trimolecular reactions.

    For parallel processing, reaction families are split per task for improved
    load balancing. This is currently hard-coded using reaction family labels.

    Args:
        core_spc_list (list): list of all core species
        num_old_core_species (int): current number of core species in the model
        unimolecular_react (np.ndarray): reaction filter flags indicating which species to react unimolecularly
        bimolecular_react (np.ndarray): reaction filter flags indicating which species to react bimolecularly
        trimolecular_react (np.ndarray, optional): reaction filter flags indicating which species to react trimolecularly
        procnum (int, optional): number of processors used for reaction generation

    Returns:
        a list of lists of reactions generated from each species tuple
        a list of species tuples corresponding to each list of reactions
    """
    # Select reactive species that can undergo unimolecular reactions:
    spc_tuples = [(core_spc_list[i], ) for i in range(num_old_core_species)
                  if (unimolecular_react[i] and core_spc_list[i].reactive)]

    for i in range(num_old_core_species):
        for j in range(i, num_old_core_species):
            # Find reactions involving the species that are bimolecular.
            # This includes a species reacting with itself (if its own concentration is high enough).
            if bimolecular_react[i, j]:
                if core_spc_list[i].reactive and core_spc_list[j].reactive:
                    spc_tuples.append((core_spc_list[i], core_spc_list[j]))

    if trimolecular_react is not None:
        for i in range(num_old_core_species):
            for j in range(i, num_old_core_species):
                for k in range(j, num_old_core_species):
                    # Find reactions involving the species that are trimolecular.
                    if trimolecular_react[i, j, k]:
                        if core_spc_list[i].reactive and core_spc_list[
                                j].reactive and core_spc_list[k].reactive:
                            spc_tuples.append(
                                (core_spc_list[i], core_spc_list[j],
                                 core_spc_list[k]))

    if procnum == 1:
        # React all families like normal (provide empty argument for only_families)
        spc_fam_tuples = list(zip(spc_tuples))
    else:
        # Identify and split families that are prone to generate many reactions into sublists.
        family_list = list(get_db('kinetics').families.keys())
        major_families = [
            'H_Abstraction',
            'R_Recombination',
            'Intra_Disproportionation',
            'Intra_RH_Add_Endocyclic',
            'Singlet_Carbene_Intra_Disproportionation',
            'Intra_ene_reaction',
            'Disproportionation',
            '1,4_Linear_birad_scission',
            'R_Addition_MultipleBond',
            '2+2_cycloaddition_Cd',
            'Diels_alder_addition',
            'Intra_RH_Add_Exocyclic',
            'Intra_Retro_Diels_alder_bicyclic',
            'Intra_2+2_cycloaddition_Cd',
            'Birad_recombination',
            'Intra_Diels_alder_monocyclic',
            '1,4_Cyclic_birad_scission',
            '1,2_Insertion_carbene',
        ]

        split_list = []
        leftovers = []
        for fam in family_list:
            if fam in major_families:
                split_list.append([fam])
            else:
                leftovers.append(fam)
        split_list.append(leftovers)

        # Only employ family splitting for reactants that have a larger number than min_atoms
        min_atoms = 10
        spc_fam_tuples = []
        for i, spc_tuple in enumerate(spc_tuples):
            if any(
                [len(spc.molecule[0].atoms) > min_atoms for spc in spc_tuple]):
                for item in split_list:
                    spc_fam_tuples.append((spc_tuple, item))
            else:
                spc_fam_tuples.append((spc_tuple, ))

    return react(spc_fam_tuples,
                 procnum), [fam_tuple[0] for fam_tuple in spc_fam_tuples]
Ejemplo n.º 12
0
def process_thermo_data(spc, thermo0, thermo_class=NASA, solvent_name=''):
    """
    Converts via Wilhoit into required `thermo_class` and sets `E0`.
    
    Resulting thermo is returned.
    """
    thermo = None

    # Always convert to Wilhoit so we can compute E0
    if isinstance(thermo0, Wilhoit):
        wilhoit = thermo0
    elif isinstance(thermo0, ThermoData):
        wilhoit = thermo0.to_wilhoit(B=1000.)
    else:
        wilhoit = thermo0.to_wilhoit()

    # Add on solvation correction
    solvation_database = get_db('solvation')
    if not solvent_name or solvation_database is None:
        logging.debug('Solvent database or solvent_name not found. Solvent effect was not utilized')
        solvent_data = None
    else:
        solvent_data = solvation_database.get_solvent_data(solvent_name)
    if solvent_data and not "Liquid thermo library" in thermo0.comment:
        solvation_database = get_db('solvation')
        solute_data = solvation_database.get_solute_data(spc)
        solvation_correction = solvation_database.get_solvation_correction(solute_data, solvent_data)
        # correction is added to the entropy and enthalpy
        wilhoit.S0.value_si = (wilhoit.S0.value_si + solvation_correction.entropy)
        wilhoit.H0.value_si = (wilhoit.H0.value_si + solvation_correction.enthalpy)

    # Compute E0 by extrapolation to 0 K
    if spc.conformer is None:
        spc.conformer = Conformer()
    spc.conformer.E0 = wilhoit.E0

    # Convert to desired thermo class
    if thermo_class is Wilhoit:
        thermo = wilhoit
    elif thermo_class is NASA:
        if solvent_data:
            # If liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary.
            # Otherwise convert wilhoit to NASA
            if "Liquid thermo library" in thermo0.comment and isinstance(thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
        else:
            # gas phase with species matching thermo library keep the NASA from library or convert if group additivity
            if "Thermo library" in thermo0.comment and isinstance(thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
    else:
        raise Exception('thermo_class neither NASA nor Wilhoit.  Cannot process thermo data.')

    if thermo.__class__ != thermo0.__class__:
        # Compute RMS error of overall transformation
        Tlist = np.array([300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], np.float64)
        err = 0.0
        for T in Tlist:
            err += (thermo.get_heat_capacity(T) - thermo0.get_heat_capacity(T)) ** 2
        err = math.sqrt(err / len(Tlist)) / constants.R
        # logging.log(logging.WARNING if err > 0.2 else 0, 'Average RMS error in heat capacity fit to {0} = {1:g}*R'.format(spc, err))

    return thermo
Ejemplo n.º 13
0
def find_degenerate_reactions(rxn_list,
                              same_reactants=None,
                              template=None,
                              kinetics_database=None,
                              kinetics_family=None,
                              save_order=False):
    """
    Given a list of Reaction objects, this method combines degenerate
    reactions and increments the reaction degeneracy value. For multiple
    transition states, this method keeps them as duplicate reactions.

    If a template is specified, then the reaction list will be filtered
    to leave only reactions which match the specified template, then the
    degeneracy will be calculated as usual.

    A KineticsDatabase or KineticsFamily instance can also be provided to
    calculate the degeneracy for reactions generated in the reverse direction.
    If not provided, then it will be retrieved from the global database.

    This algorithm used to exist in family._generate_reactions, but was moved
    here so it could operate across reaction families.

    This method returns an updated list with degenerate reactions removed.

    Args:
        rxn_list (list):                                reactions to be analyzed
        same_reactants (bool, optional):                indicate whether the reactants are identical
        template (list, optional):                      specify a specific template to filter by
        kinetics_database (KineticsDatabase, optional): provide a KineticsDatabase instance for calculating degeneracy
        kinetics_family (KineticsFamily, optional):     provide a KineticsFamily instance for calculating degeneracy
        save_order (bool, optional):                    reset atom order after performing atom isomorphism

    Returns:
        Reaction list with degenerate reactions combined with proper degeneracy values
    """
    # If a specific reaction template is requested, filter by that template
    if template is not None:
        selected_rxns = []
        template = frozenset(template)
        for rxn in rxn_list:
            if template == frozenset(rxn.template):
                selected_rxns.append(rxn)
        if not selected_rxns:
            # Only log a warning here. If a non-empty output is expected, then the caller should raise an exception
            logging.warning(
                'No reactions matched the specified template, {0}'.format(
                    template))
            return []
    else:
        selected_rxns = rxn_list

    # We want to sort all the reactions into sublists composed of isomorphic reactions
    # with degenerate transition states
    sorted_rxns = []
    for rxn0 in selected_rxns:
        rxn0.ensure_species(save_order=save_order)
        if len(sorted_rxns) == 0:
            # This is the first reaction, so create a new sublist
            sorted_rxns.append([rxn0])
        else:
            # Loop through each sublist, which represents a unique reaction
            for sub_list in sorted_rxns:
                # Try to determine if the current rxn0 is identical or isomorphic to any reactions in the sublist
                isomorphic = False
                identical = False
                same_template = True
                for rxn in sub_list:
                    isomorphic = rxn0.is_isomorphic(
                        rxn,
                        check_identical=False,
                        strict=False,
                        check_template_rxn_products=True,
                        save_order=save_order)
                    if isomorphic:
                        identical = rxn0.is_isomorphic(
                            rxn,
                            check_identical=True,
                            strict=False,
                            check_template_rxn_products=True,
                            save_order=save_order)
                        if identical:
                            # An exact copy of rxn0 is already in our list, so we can move on
                            break
                        same_template = frozenset(rxn.template) == frozenset(
                            rxn0.template)
                    else:
                        # This sublist contains a different product
                        break

                # Process the reaction depending on the results of the comparisons
                if identical:
                    # This reaction does not contribute to degeneracy
                    break
                elif isomorphic:
                    if same_template:
                        # We found the right sublist, and there is no identical reaction
                        # We should add rxn0 to the sublist as a degenerate rxn, and move on to the next rxn
                        sub_list.append(rxn0)
                        break
                    else:
                        # We found an isomorphic sublist, but the reaction templates are different
                        # We need to mark this as a duplicate and continue searching the remaining sublists
                        rxn0.duplicate = True
                        sub_list[0].duplicate = True
                        continue
                else:
                    # This is not an isomorphic sublist, so we need to continue searching the remaining sublists
                    # Note: This else statement is not technically necessary but is included for clarity
                    continue
            else:
                # We did not break, which means that there was no isomorphic sublist, so create a new one
                sorted_rxns.append([rxn0])

    rxn_list = []
    for sub_list in sorted_rxns:
        # Collapse our sorted reaction list by taking one reaction from each sublist
        rxn = sub_list[0]
        # The degeneracy of each reaction is the number of reactions that were in the sublist
        rxn.degeneracy = sum([reaction0.degeneracy for reaction0 in sub_list])
        rxn_list.append(rxn)

    for rxn in rxn_list:
        if rxn.is_forward:
            reduce_same_reactant_degeneracy(rxn, same_reactants)
        else:
            # fix the degeneracy of (not ownReverse) reactions found in the backwards direction
            try:
                family = kinetics_family or kinetics_database.families[
                    rxn.family]
            except AttributeError:
                from rmgpy.data.rmg import get_db
                family = get_db('kinetics').families[rxn.family]
            if not family.own_reverse:
                rxn.degeneracy = family.calculate_degeneracy(rxn)

    return rxn_list
Ejemplo n.º 14
0
Archivo: input.py Proyecto: qize/RMG-Py
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
Ejemplo n.º 15
0
    def execute(self,
                output_file,
                plot,
                file_format='pdf',
                print_summary=True,
                species_list=None,
                thermo_library=None,
                kinetics_library=None):
        """Execute an ExplorerJob"""
        logging.info('Exploring network...')

        rmg = RMG()

        rmg.species_constraints = {
            'allowed':
            ['input species', 'seed mechanisms', 'reaction libraries'],
            'maximumRadicalElectrons': self.maximum_radical_electrons,
            'explicitlyAllowedMolecules': []
        }

        rmgpy.rmg.input.rmg = rmg

        reaction_model = CoreEdgeReactionModel()

        reaction_model.pressure_dependence = self.pdepjob

        reaction_model.pressure_dependence.rmgmode = True

        if output_file:
            reaction_model.pressure_dependence.output_file = os.path.dirname(
                output_file)

        kinetics_database = get_db('kinetics')
        thermo_database = get_db('thermo')

        thermo_database.libraries['thermojobs'] = thermo_library
        thermo_database.library_order.insert(0, 'thermojobs')

        kinetics_database.libraries['kineticsjobs'] = kinetics_library
        kinetics_database.library_order.insert(
            0, ('kineticsjobs', 'Reaction Library'))

        self.job_rxns = [rxn for rxn in reaction_model.core.reactions]

        if output_file is not None:
            if not os.path.exists(
                    os.path.join(
                        reaction_model.pressure_dependence.output_file,
                        'pdep')):
                os.mkdir(
                    os.path.join(
                        reaction_model.pressure_dependence.output_file,
                        'pdep'))
            else:
                shutil.rmtree(
                    os.path.join(
                        reaction_model.pressure_dependence.output_file,
                        'pdep'))
                os.mkdir(
                    os.path.join(
                        reaction_model.pressure_dependence.output_file,
                        'pdep'))

        # get the molecular formula for the network
        mmol = None
        for spc in self.source:
            if mmol:
                mmol = mmol.merge(spc.molecule[0])
            else:
                mmol = spc.molecule[0].copy(deep=True)

        form = mmol.get_formula()

        for spec in list(self.bath_gas.keys()) + self.source:
            nspec, is_new = reaction_model.make_new_species(spec,
                                                            reactive=False)
            flags = np.array([
                s.molecule[0].get_formula() == form
                for s in reaction_model.core.species
            ])
            reaction_model.enlarge(nspec,
                                   react_edge=False,
                                   unimolecular_react=flags,
                                   bimolecular_react=np.zeros(
                                       (len(reaction_model.core.species),
                                        len(reaction_model.core.species))))

        reaction_model.add_seed_mechanism_to_core('kineticsjobs')

        for lib in kinetics_database.library_order:
            if lib[0] != 'kineticsjobs':
                reaction_model.add_reaction_library_to_edge(lib[0])

        for spc in reaction_model.core.species:
            for i, item in enumerate(self.source):
                if spc.is_isomorphic(item):
                    self.source[i] = spc

        # react initial species
        if len(self.source) == 1:
            flags = np.array([
                s.molecule[0].get_formula() == form
                for s in reaction_model.core.species
            ])
            biflags = np.zeros((len(reaction_model.core.species),
                                len(reaction_model.core.species)))
        elif len(self.source) == 2:
            flags = np.array([False for s in reaction_model.core.species])
            biflags = np.array(
                [[False for i in range(len(reaction_model.core.species))]
                 for j in range(len(reaction_model.core.species))])
            biflags[reaction_model.core.species.index(self.source[0]),
                    reaction_model.core.species.index(self.source[1])] = True
        else:
            raise ValueError(
                "Reactant channels with greater than 2 reactants not supported"
            )

        reaction_model.enlarge(react_edge=True,
                               unimolecular_react=flags,
                               bimolecular_react=biflags)

        # find the networks we're interested in
        networks = []
        for nwk in reaction_model.network_list:
            if set(nwk.source) == set(self.source):
                self.source = nwk.source
                networks.append(nwk)

        if len(networks) == 0:
            raise ValueError(
                'Did not generate a network with the requested source. This usually means no unimolecular '
                'reactions were generated for the source. Note that library reactions that are not '
                'properly flagged as elementary_high_p can replace RMG generated reactions that would '
                'otherwise be part of networks.')
        for network in networks:
            network.bath_gas = self.bath_gas

        self.networks = networks

        # determine T and P combinations

        if self.pdepjob.Tlist:
            t_list = self.pdepjob.Tlist.value_si
        else:
            t_list = np.linspace(self.pdepjob.Tmin.value_si,
                                 self.pdepjob.Tmax.value_si,
                                 self.pdepjob.Tcount)

        if self.pdepjob.Plist:
            p_list = self.pdepjob.Plist.value_si
        else:
            p_list = np.linspace(self.pdepjob.Pmin.value_si,
                                 self.pdepjob.Pmax.value_si,
                                 self.pdepjob.Pcount)

        # generate the network

        forbidden_structures = get_db('forbidden')
        incomplete = True
        checked_species = []

        while incomplete:
            incomplete = False
            for temperature in t_list:
                for pressure in p_list:
                    for network in self.networks:
                        # compute the characteristic rate coefficient by summing all rate coefficients
                        # from the reactant channel
                        for spc in reaction_model.edge.species:
                            if spc in checked_species:
                                continue
                            if forbidden_structures.is_molecule_forbidden(
                                    spc.molecule[0]):
                                reaction_model.remove_species_from_edge(
                                    reaction_model.reaction_systems, spc)
                                reaction_model.remove_empty_pdep_networks()
                            else:
                                checked_species.append(spc)

                        kchar = 0.0
                        for rxn in network.net_reactions:  # reaction_model.core.reactions+reaction_model.edge.reactions
                            if (set(rxn.reactants) == set(self.source) and
                                    rxn.products[0].molecule[0].get_formula()
                                    == form):
                                kchar += rxn.kinetics.get_rate_coefficient(
                                    T=temperature, P=pressure)
                            elif (set(rxn.products) == set(self.source) and
                                  rxn.reactants[0].molecule[0].get_formula()
                                  == form):
                                kchar += rxn.generate_reverse_rate_coefficient(
                                    network_kinetics=True
                                ).get_rate_coefficient(T=temperature,
                                                       P=pressure)

                        if network.get_leak_coefficient(
                                T=temperature,
                                P=pressure) > self.explore_tol * kchar:
                            incomplete = True
                            spc = network.get_maximum_leak_species(
                                T=temperature, P=pressure)
                            logging.info(
                                'adding new isomer {0} to network'.format(spc))
                            flags = np.array([
                                s.molecule[0].get_formula() == form
                                for s in reaction_model.core.species
                            ])
                            reaction_model.enlarge(
                                (network, spc),
                                react_edge=False,
                                unimolecular_react=flags,
                                bimolecular_react=np.zeros(
                                    (len(reaction_model.core.species),
                                     len(reaction_model.core.species))))

                            flags = np.array([
                                s.molecule[0].get_formula() == form
                                for s in reaction_model.core.species
                            ])
                            reaction_model.enlarge(
                                react_edge=True,
                                unimolecular_react=flags,
                                bimolecular_react=np.zeros(
                                    (len(reaction_model.core.species),
                                     len(reaction_model.core.species))))
        for network in self.networks:
            rm_rxns = []
            for rxn in network.path_reactions:  # remove reactions with forbidden species
                for r in rxn.reactants + rxn.products:
                    if forbidden_structures.is_molecule_forbidden(
                            r.molecule[0]):
                        rm_rxns.append(rxn)

            for rxn in rm_rxns:
                logging.info('Removing forbidden reaction: {0}'.format(rxn))
                network.path_reactions.remove(rxn)

            # clean up output files
            if output_file is not None:
                path = os.path.join(
                    reaction_model.pressure_dependence.output_file, 'pdep')
                for name in os.listdir(path):
                    if name.endswith('.py') and '_' in name:
                        if name.split('_')[-1].split('.')[0] != str(
                                len(network.isomers)):
                            os.remove(os.path.join(path, name))
                        else:
                            os.rename(
                                os.path.join(path, name),
                                os.path.join(
                                    path, 'network_full{}.py'.format(
                                        self.networks.index(network))))

        warns = []

        for rxn in self.job_rxns:
            if rxn not in network.path_reactions:
                warns.append(
                    'Reaction {0} in the input file was not explored during network expansion and was '
                    'not included in the full network.  This is likely because your explore_tol value is '
                    'too high.'.format(rxn))

        # reduction process
        for network in self.networks:
            if self.energy_tol != np.inf or self.flux_tol != 0.0:

                rxn_set = None
                product_set = None

                for temperature in t_list:
                    if self.energy_tol != np.inf:
                        rxns = network.get_energy_filtered_reactions(
                            temperature, self.energy_tol)
                        if rxn_set is not None:
                            rxn_set &= set(rxns)
                        else:
                            rxn_set = set(rxns)

                    for pressure in p_list:
                        if self.flux_tol != 0.0:
                            products = network.get_rate_filtered_products(
                                temperature, pressure, self.flux_tol)
                            products = [tuple(x) for x in products]
                            if product_set is not None:
                                product_set &= set(products)
                            else:
                                product_set = set(products)

                if rxn_set:
                    logging.info('removing reactions during reduction:')
                    for rxn in rxn_set:
                        logging.info(rxn)
                    rxn_set = list(rxn_set)
                if product_set:
                    logging.info('removing products during reduction:')
                    for prod in product_set:
                        logging.info([x.label for x in prod])
                    product_set = list(product_set)

                network.remove_reactions(reaction_model,
                                         rxns=rxn_set,
                                         prods=product_set)

                for rxn in self.job_rxns:
                    if rxn not in network.path_reactions:
                        warns.append(
                            'Reaction {0} in the input file was not included in the reduced model.'
                            .format(rxn))

        self.networks = networks
        for p, network in enumerate(self.networks):
            self.pdepjob.network = network

            if len(self.networks) > 1:
                root, file_name = os.path.split(output_file)
                s1, s2 = file_name.split(".")
                ind = str(self.networks.index(network))
                stot = os.path.join(root, s1 + "{}.".format(ind) + s2)
            else:
                stot = output_file

            self.pdepjob.execute(stot,
                                 plot,
                                 file_format='pdf',
                                 print_summary=True)
            if os.path.isfile('network.pdf'):
                os.rename('network.pdf', 'network' + str(p) + '.pdf')

            if warns:
                logging.info('\nOUTPUT WARNINGS:\n')
                for w in warns:
                    logging.warning(w)