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))
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
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'))
def database(thermoLibraries=None, transportLibraries=None, reactionLibraries=None, frequenciesLibraries=None, kineticsFamilies='default', kineticsDepositories='default', kineticsEstimator='rate rules'): """Load the RMG database""" thermo_libraries = as_list(thermoLibraries, default=[]) transport_libraries = as_list(transportLibraries, default=None) reaction_libraries = as_list(reactionLibraries, default=[]) database_directory = settings['database.directory'] if kineticsDepositories == 'default': kinetics_depositories = ['training'] elif kineticsDepositories == 'all': kinetics_depositories = None else: if not isinstance(kineticsDepositories, list): raise InputError( "kinetics_depositories should be either 'default', 'all', or a list of names eg. ['training','PrIMe']." ) kinetics_depositories = kineticsDepositories if kineticsFamilies in ('default', 'all', 'none'): kinetics_families = kineticsFamilies else: if not isinstance(kineticsFamilies, list): raise InputError( "kineticsFamilies should be either 'default', 'all', 'none', or a list of names eg. " "['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation']." ) kinetics_families = kineticsFamilies rmg_database = get_db() or RMGDatabase() rmg_database.load( path=database_directory, thermo_libraries=thermo_libraries, transport_libraries=transport_libraries, reaction_libraries=reaction_libraries, seed_mechanisms=[], kinetics_families=kinetics_families, kinetics_depositories=kinetics_depositories, depository= False, # Don't bother loading the depository information, as we don't use it ) for family in rmg_database.kinetics.families.values(): # load training if not family.auto_generated: family.add_rules_from_training(thermo_database=rmg_database.thermo) for family in rmg_database.kinetics.families.values(): family.fill_rules_by_averaging_up(verbose=True)
def 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]
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
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)
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
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
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
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]
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
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
def species(label, *args, **kwargs): """Load a species from an input file""" global species_dict, job_list if label in species_dict: raise ValueError( 'Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) species_dict[label] = spec path = None if len(args) == 1: # The argument is a path to a conformer input file path = args[0] job = StatMechJob(species=spec, path=path) logging.debug('Added species {0} to a stat mech job.'.format(label)) job_list.append(job) elif len(args) > 1: raise InputError('species {0} can only have two non-keyword argument ' 'which should be the species label and the ' 'path to a quantum file.'.format(spec.label)) if len(kwargs) > 0: # The species parameters are given explicitly structure = None E0 = None modes = [] spin_multiplicity = 0 optical_isomers = 1 molecular_weight = None collision_model = None energy_transfer_model = None thermo = None reactive = True for key, value in kwargs.items(): if key == 'structure': structure = value elif key == 'E0': E0 = value elif key == 'modes': modes = value elif key == 'spinMultiplicity': spin_multiplicity = value elif key == 'opticalIsomers': optical_isomers = value elif key == 'molecularWeight': molecular_weight = value elif key == 'collisionModel': collision_model = value elif key == 'energyTransferModel': energy_transfer_model = value elif key == 'thermo': thermo = value elif key == 'reactive': reactive = value else: raise TypeError( 'species() got an unexpected keyword argument {0!r}.'. format(key)) if structure: spec.molecule = [structure] spec.conformer = Conformer(E0=E0, modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers) if molecular_weight is not None: spec.molecular_weight = molecular_weight elif spec.molecular_weight is None and is_pdep(job_list): # If a structure was given, simply calling spec.molecular_weight will calculate the molecular weight # If one of the jobs is pdep and no molecular weight is given or calculated, raise an error raise ValueError( "No molecularWeight was entered for species {0}. Since a structure wasn't given" " as well, the molecularWeight, which is important for pressure dependent jobs," " cannot be reconstructed.".format(spec.label)) spec.transport_data = collision_model spec.energy_transfer_model = energy_transfer_model spec.thermo = thermo spec.reactive = reactive if spec.reactive and path is None and spec.thermo is None and spec.conformer.E0 is None: if not spec.molecule: raise InputError( 'Neither thermo, E0, species file path, nor structure specified, cannot estimate' ' thermo properties of species {0}'.format(spec.label)) try: db = get_db('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warning( "The database isn't loaded, cannot estimate thermo for {0}. " "If it is a bath gas, set reactive = False to avoid generating " "thermo.".format(spec.label)) else: logging.info( 'No E0 or thermo found, estimating thermo and E0 of species {0} using' ' RMG-Database...'.format(spec.label)) spec.thermo = db.get_thermo_data(spec) if spec.thermo.E0 is None: th = spec.thermo.to_wilhoit() spec.conformer.E0 = th.E0 spec.thermo.E0 = th.E0 else: spec.conformer.E0 = spec.thermo.E0 if spec.reactive and spec.thermo and not spec.has_statmech( ) and structure is not None: # generate stat mech info if it wasn't provided before spec.generate_statmech() if not energy_transfer_model: # default to RMG's method of generating energy_transfer_model spec.generate_energy_transfer_model() return spec
def 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)