def test_reaction_block(): """ Tests the reaction_block function """ reaction_block_good = mechanism.reaction_block(REACTION_BLOCK_GOOD) reaction_block_bad = mechanism.reaction_block(REACTION_BLOCK_BAD) assert reaction_block_good == '\nCH4+H=CH3+H2\n' assert reaction_block_bad is None
def build_reaction_name_dcts(mech1_str, mech2_str, t_ref, temps, pressures, ignore_reverse=True, remove_bad_fits=False): """ Parses the strings of two mechanism files and calculates rate constants [k(T,P)]s at an input set of temperatures and pressures. :param mech1_str: string of mechanism 1 input file :type mech1_str: str :param mech2_str: string of mechanism 2 input file :type mech2_str: str :param t_ref: Reference temperature (K) :type t_ref: float :param temps: List of Temperatures (K) :type temps: numpy.ndarray :return mech1_ktp_dct: rate constants for mechanism 1 :rtype: dict[pressure: rates] :return mech2_ktp_dct: rate constants for mechanism 2 :rtype: dict[pressure: rates] """ mech1_reaction_block = remove_whitespace( mech_parser.reaction_block(mech1_str)) mech1_units = reaction_units(mech1_str) mech1_ktp_dct = rates.mechanism(mech1_reaction_block, mech1_units, t_ref, temps, pressures, ignore_reverse=ignore_reverse, remove_bad_fits=remove_bad_fits) if mech2_str: mech2_reaction_block = remove_whitespace( mech_parser.reaction_block(mech2_str)) mech2_units = reaction_units(mech2_str) mech2_ktp_dct = rates.mechanism(mech2_reaction_block, mech2_units, t_ref, temps, pressures, ignore_reverse=ignore_reverse, remove_bad_fits=remove_bad_fits) else: mech2_ktp_dct = {} return mech1_ktp_dct, mech2_ktp_dct
def load_rxn_ktp_dcts_chemkin(mech_filenames, direc, temps, pressures): """ Read one or more Chemkin-formatted mechanisms files and calculate rates at the indicated pressures and temperatures. Return a list of rxn_ktp_dcts. :param mech_filenames: filenames containing Chemkin-formatted kinetics information :type mech_filenames: list [filename1, filename2, ...] :param direc: directory with file(s) (all files must be in the same directory) :type direc: str :param temps: temperatures at which to do calculations (Kelvin) :type temps: list [float] :param pressures: pressures at which to do calculations (atm) :type pressures: list [float] :return rxn_ktp_dcts: list of rxn_ktp_dcts :rtype: list of dcts [rxn_ktp_dct1, rxn_ktp_dct2, ...] """ rxn_ktp_dcts = [] for mech_filename in mech_filenames: mech_str = parser.read_file(direc, mech_filename) ea_units, a_units = parser_mech.reaction_units(mech_str) rxn_block_str = parser_mech.reaction_block(mech_str) rxn_param_dct = parser_rxn.param_dct(rxn_block_str, ea_units, a_units) rxn_ktp_dct = calc_rates.eval_rxn_param_dct(rxn_param_dct, pressures, temps) rxn_ktp_dcts.append(rxn_ktp_dct) return rxn_ktp_dcts
def parse_pes_dct(mech_str): """ Parses a PES dct :param mech_str: raw string from reading a Chemkin file :type mech_str: str :return rxn_param_dct: rxn_param_dct object :rtype: dct {rxn1: param_tuple1, rxn2: ...} """ rxn_block_str = parser_mech.reaction_block(mech_str) pes_dct = parser_rxn.get_pes_dct(rxn_block_str) return pes_dct
def parse_rxn_param_dct(mech_str): """ Parses a raw Chemkin mechanism string and yields a rxn_param_dct :param mech_str: raw string from reading a Chemkin file :type mech_str: str :return rxn_param_dct: rxn_param_dct object :rtype: dct {rxn1: param_tuple1, rxn2: ...} """ ea_units, a_units = parser_mech.reaction_units(mech_str) rxn_block_str = parser_mech.reaction_block(mech_str) rxn_param_dct = parser_rxn.get_rxn_param_dct(rxn_block_str, ea_units, a_units) return rxn_param_dct
def align_mechs(mech_filenames, spc_csv_filenames, thermo_filenames, temps, pressures): """ Calculates kTP dictionaries for any number of mechanisms and then flips the reactions within the mechanisms to all be in the same direction """ assert len(mech_filenames) == len(spc_csv_filenames) == len(thermo_filenames), ( f"""The lengths of the mechanism, spc_csv, and thermo filename inputs should all be the same, but are instead {len(mech_filenames)}, {len(spc_csv_filenames)}, and {len(thermo_filenames)}, respectively.""" ) num_mechs = len(mech_filenames) # Load rxn_ktp_dcts, spc_dcts, and thermo_dcts rxn_ktp_dcts = [] rxn_param_dcts = [] spc_dcts = [] thermo_dcts = [] for idx in range(num_mechs): # Get the rxn_ktp_dct and the rxn_param_dct mech_str = parser.ptt.read_inp_str(JOB_PATH,mech_filenames[idx],remove_comments=False) rxn_block_str = parser_mech.reaction_block(mech_str) rxn_param_dct = parser_rxn.param_dct(rxn_block_str) rxn_ktp_dct = calc_rates.eval_rxn_param_dct(rxn_param_dct, pressures, temps) rxn_ktp_dcts.append(rxn_ktp_dct) rxn_param_dcts.append(rxn_param_dct) # Get the spc_dct spc_csv_str = parser.ptt.read_inp_str(JOB_PATH, spc_csv_filenames[idx], remove_comments=False) spc_dct = parser_spc.build_spc_dct(spc_csv_str, 'csv') spc_dcts.append(spc_dct) # Get the thermo_dct thermo_str = parser.ptt.read_inp_str(JOB_PATH,mech_filenames[idx],remove_comments=False) thermo_block_str = parser_mech.thermo_block(thermo_str) thermo_dct = calc_thermo.mechanism(thermo_block_str, temps) thermo_dcts.append(thermo_dct) # Get the renamed dictionaries renamed_rxn_ktp_dcts = [] renamed_rxn_param_dcts = [] renamed_spc_dcts = [] renamed_thermo_dcts = [] for mech_idx in range(num_mechs-1): # If on first mech, don't rename, just copy if mech_idx == 0: renamed_rxn_ktp_dcts.append(rxn_ktp_dcts[mech_idx]) renamed_rxn_param_dcts.append(rxn_param_dcts[mech_idx]) renamed_spc_dcts.append(spc_dcts[mech_idx]) renamed_thermo_dcts.append(thermo_dcts[mech_idx]) # Loop through the mechs remaining after the current one for idx2 in range(mech_idx+1, num_mechs): # If on the first time through, do things a bit differently if mech_idx == 0: # Get the instructions on renaming species, aka the rename_spc_dct _, rename_spc_dct = calc_combine.combine_species(spc_dcts[mech_idx], spc_dcts[idx2]) # Rename the species in the various dictionaries renamed_rxn_ktp_dct = calc_combine.rename_species(rxn_ktp_dcts[idx2], rename_spc_dct, target_type='rxn') renamed_rxn_param_dct = calc_combine.rename_species(rxn_param_dcts[idx2], rename_spc_dct, target_type='rxn') renamed_spc_dct = calc_combine.rename_species(spc_dcts[idx2], rename_spc_dct, target_type='spc') renamed_thermo_dct = calc_combine.rename_species(thermo_dcts[idx2], rename_spc_dct, target_type='thermo') # Store the results renamed_rxn_ktp_dcts.append(renamed_rxn_ktp_dct) renamed_rxn_param_dcts.append(renamed_rxn_param_dct) renamed_spc_dcts.append(renamed_spc_dct) renamed_thermo_dcts.append(renamed_thermo_dct) else: # Get the instructions on renaming species, aka the rename_spc_dct _, rename_spc_dct = calc_combine.combine_species(renamed_spc_dcts[mech_idx], renamed_spc_dcts[idx2]) # Rename the species in the various dictionaries renamed_rxn_ktp_dct = calc_combine.rename_species(renamed_rxn_ktp_dcts[idx2], rename_spc_dct, target_type='rxn') renamed_rxn_param_dct = calc_combine.rename_species(renamed_rxn_param_dcts[idx2], rename_spc_dct, target_type='rxn') renamed_spc_dct = calc_combine.rename_species(renamed_spc_dcts[idx2], rename_spc_dct, target_type='spc') renamed_thermo_dct = calc_combine.rename_species(renamed_thermo_dcts[idx2], rename_spc_dct, target_type='thermo') # Store the results renamed_rxn_ktp_dcts[idx2] = renamed_rxn_ktp_dct renamed_rxn_param_dcts[idx2] = renamed_rxn_param_dct renamed_spc_dcts[idx2] = renamed_spc_dct renamed_thermo_dcts[idx2] = renamed_thermo_dct # Get the em_param_dcts renamed_em_param_dcts = [] for dct in renamed_rxn_param_dcts: renamed_em_param_dct = get_em_param_dct(dct) renamed_em_param_dcts.append(renamed_em_param_dct) # Loop over each reaction and reverse if necessary aligned_rxn_ktp_dcts = [] aligned_em_param_dcts = [] for mech_idx in range(num_mechs-1): if mech_idx == 0: aligned_rxn_ktp_dcts.append(renamed_rxn_ktp_dcts[mech_idx]) aligned_em_param_dcts.append(renamed_em_param_dcts[mech_idx]) for idx2 in range(mech_idx+1, num_mechs): if mech_idx == 0: aligned_rxn_ktp_dct = reverse_ktp_dct(renamed_rxn_ktp_dcts[mech_idx], renamed_rxn_ktp_dcts[idx2], renamed_rxn_param_dcts[mech_idx], renamed_rxn_param_dcts[idx2], renamed_thermo_dcts[mech_idx], temps) aligned_rxn_ktp_dcts.append(aligned_rxn_ktp_dct) else: aligned_rxn_ktp_dct = reverse_ktp_dct(renamed_rxn_ktp_dcts[mech_idx], renamed_rxn_ktp_dcts[idx2], renamed_rxn_param_dcts[mech_idx], renamed_rxn_param_dcts[idx2], renamed_thermo_dcts[mech_idx], temps) # Note: the preceding line does not necessarily use the thermo of the first mechanism; it only is sure # to use the thermo of the mechanism currently being used as the reference aligned_rxn_ktp_dcts[idx2] = aligned_rxn_ktp_dct # Sort the aligned dictionaries into a single output # Loop over each mechanism combined_rxn_ktp_dct = {} for mech_idx, dct in enumerate(aligned_rxn_ktp_dcts): # Loop over each rxn in the mechanism for rxn, ktp_dct in dct.items(): # If the reaction does not yet exist, add it if rxn not in combined_rxn_ktp_dct.keys(): ktp_dct_list = [None] * mech_idx combined_rxn_ktp_dct[rxn] = ktp_dct_list.append(ktp_dct) # Also, add the em_param # If the reaction already exists, append the new ktp_dct else: ktp_dct_list = combined_rxn_ktp_dct[rxn] # get the current list of ktp_dcts # If any of the previous entries were blank, add None entries to fill if len(ktp_dct_list) < mech_idx: ktp_dct_list.extend([None] * (mech_idx - len(ktp_dct_list))) ktp_dct_list.append(ktp_dct) # Clean up the combined dct in two ways: for rxn, ktp_dct_list in combined_rxn_ktp_dct.items(): # 1: Add None entries so that all are the same length if len(ktp_dct_list) < num_mechs: ktp_dct_list.extend([None] * (num_mechs - len(ktp_dct_list))) combined_rxn_ktp_dct[rxn] = ktp_dct_list # 2: Create the em_param_dct, which denotes whether a reaction has return combined_rxn_ktp_dct
def align_mechs(mech_filenames, thermo_filenames, spc_csv_filenames, temps, pressures, rev_rates=True, remove_loners=True, write_file=False, print_output=False): """ Takes any number of mechanisms and renames the species to all be the same, reverses any reactions to be in the same direction, and outputs a dictionary of all the reaction names and the corresponding k(T,P) dictionaries. :param mech_filenames: names of the Chemkin-formatted mechanism text files :type mech_filenames: list :param thermo_filenames: names of the Chemkin-formatted thermo text files; if the thermo info is contained in the mechanism.txt, just put the same filename here :type thermo_filenames: list :param spc_csv_filenames: names of the spc_csv filenames, which should have the following info: names, SMILES, inchi, multiplicity, charge, sensitivity (sensitivity not required) :type spc_csv_filenames: list :param temps: array of temperatures in Kelvin :type temps: Numpy 1-D array :param pressures: array of pressures in atm :type pressures: Numpy 1-D arrayi :param rev_rates: whether or not to reverse reactions; thermo filenames not required if False :type rev_rates: Bool :return aligned_rxn_ktp_dcts: dictionary of k(T,P) values for each mechanism, all under the same reaction name index :rtype: dict {((rcts), (prds)): [{ktp_dct1, ktp_dct2, ...], ...} :return aligned_rxn_em_dcts: dictionary of Boolean parameters indicating whether or not the rxn has a '+ M' term (i.e., third body) associated with it :rtype: dict {((rcts), (prds)): Boolean, ...} """ if rev_rates: assert len(mech_filenames) == len(spc_csv_filenames) == len( thermo_filenames ), (f"""The lengths of the mechanism, spc_csv, and thermo filename inputs should all be the same, but are instead {len(mech_filenames)}, {len(spc_csv_filenames)}, and {len(thermo_filenames)}, respectively.""") else: assert len(mech_filenames) == len(spc_csv_filenames), ( f"""The lengths of the mechanism and spc_csv inputs should all be the same, but are instead {len(mech_filenames)} and {len(spc_csv_filenames)}, respectively.""") num_mechs = len(mech_filenames) # Load rxn_ktp_dcts, spc_ident_dcts, and spc_thermo_dcts rxn_ktp_dcts = [] rxn_param_dcts = [] spc_ident_dcts = [] spc_thermo_dcts = [] for idx in range(num_mechs): # Get the rxn_ktp_dct and the rxn_param_dct mech_str = parser.ptt.read_inp_str(JOB_PATH, mech_filenames[idx], remove_comments=False) ea_units, a_units = parser_mech.reaction_units(mech_str) rxn_block_str = parser_mech.reaction_block(mech_str) rxn_param_dct = parser_rxn.param_dct(rxn_block_str, ea_units, a_units) rxn_ktp_dct = calc_rates.eval_rxn_param_dct(rxn_param_dct, pressures, temps) rxn_ktp_dcts.append(rxn_ktp_dct) rxn_param_dcts.append(rxn_param_dct) # Get the spc_dct spc_csv_str = parser.ptt.read_inp_str(JOB_PATH, spc_csv_filenames[idx], remove_comments=False) spc_dct = parser_spc.build_spc_dct(spc_csv_str, 'csv') spc_ident_dcts.append(spc_dct) # Get the thermo_dct if rev_rates: thermo_str = parser.ptt.read_inp_str(JOB_PATH, thermo_filenames[idx], remove_comments=False) thermo_block_str = parser_mech.thermo_block(thermo_str) spc_nasa7_dct = parser_thermo.create_spc_nasa7_dct( thermo_block_str) spc_thermo_dct = calc_thermo.create_spc_thermo_dct( spc_nasa7_dct, temps) spc_thermo_dcts.append(spc_thermo_dct) # Get the renamed dictionaries renamed_rxn_ktp_dcts, rename_instructions_lst = rename_dcts( rxn_ktp_dcts, 'rxn', spc_ident_dcts) renamed_rxn_param_dcts, _ = rename_dcts(rxn_param_dcts, 'rxn', spc_ident_dcts) if rev_rates: renamed_spc_thermo_dcts, _ = rename_dcts(spc_thermo_dcts, 'spc', spc_ident_dcts) # Get the em_param_dcts renamed_rxn_em_dcts = [] for dct in renamed_rxn_param_dcts: renamed_rxn_em_dct = get_rxn_em_dct(dct) renamed_rxn_em_dcts.append(renamed_rxn_em_dct) # Loop over each reaction and reverse if necessary # This creates the reversed dct, which has uniform species names and uniform reactant/product ordering reversed_rxn_ktp_dcts = copy.copy(renamed_rxn_ktp_dcts) reversed_rxn_em_dcts = copy.copy(renamed_rxn_em_dcts) for mech_idx in range(num_mechs - 1): for idx2 in range(mech_idx + 1, num_mechs): # If indicated, reverse the rxn_ktp_dcts and rxm_em_dcts if rev_rates: reversed_rxn_ktp_dct = reverse_rxn_ktp_dct( renamed_rxn_ktp_dcts[mech_idx], renamed_rxn_ktp_dcts[idx2], renamed_rxn_param_dcts[mech_idx], renamed_rxn_param_dcts[idx2], renamed_spc_thermo_dcts[mech_idx], temps, rev_rates) reversed_rxn_em_dct = reverse_rxn_em_dct( renamed_rxn_em_dcts[idx2], renamed_rxn_param_dcts[mech_idx], renamed_rxn_param_dcts[idx2], rev_rates) # Otherwise, just rename so the reactants and products are in the same order else: reversed_rxn_ktp_dct = reverse_rxn_ktp_dct( renamed_rxn_ktp_dcts[mech_idx], renamed_rxn_ktp_dcts[idx2], renamed_rxn_param_dcts[mech_idx], renamed_rxn_param_dcts[idx2], [], temps, rev_rates) reversed_rxn_em_dct = reverse_rxn_em_dct( renamed_rxn_em_dcts[idx2], renamed_rxn_param_dcts[mech_idx], renamed_rxn_param_dcts[idx2], rev_rates) reversed_rxn_ktp_dcts[idx2] = reversed_rxn_ktp_dct reversed_rxn_em_dcts[idx2] = reversed_rxn_em_dct # Sort the reversed dictionaries into a single output # Loop over each mechanism aligned_rxn_ktp_dct = {} aligned_rxn_em_dct = {} for mech_idx, dct in enumerate(reversed_rxn_ktp_dcts): for rxn, ktp_dct in dct.items(): # If the reaction does not yet exist, add it if rxn not in aligned_rxn_ktp_dct.keys(): ktp_dct_list = [ None ] * mech_idx # add empty entries to account for previous mechs that are missing ktp_dct_list.append(ktp_dct) aligned_rxn_ktp_dct[rxn] = ktp_dct_list try: aligned_rxn_em_dct[rxn] = reversed_rxn_em_dcts[mech_idx][ rxn] # add the em_param except KeyError: print( f'There was a key error in mech idx {mech_idx} for the rxn {rxn}' ) # If the reaction already exists, append the new ktp_dct else: ktp_dct_list = aligned_rxn_ktp_dct[ rxn] # get the current list of ktp_dcts # If any of the previous entries were blank, add None entries to fill if len(ktp_dct_list) < mech_idx: ktp_dct_list.extend([None] * (mech_idx - len(ktp_dct_list))) ktp_dct_list.append(ktp_dct) # Clean up the aligned dct for rxn, ktp_dct_list in aligned_rxn_ktp_dct.items(): # Add None entries so that all are the same length if len(ktp_dct_list) < num_mechs: ktp_dct_list.extend([None] * (num_mechs - len(ktp_dct_list))) aligned_rxn_ktp_dct[rxn] = ktp_dct_list if print_output: print('renamed_rxn_param_dcts', renamed_rxn_param_dcts) print('renamed_rxn_em_dcts', renamed_rxn_em_dcts) print('reversed_rxn_ktp_dcts', reversed_rxn_ktp_dcts) print('reversed_rxn_em_dcts', reversed_rxn_em_dcts) print('aligned_rxn_ktp_dct', aligned_rxn_ktp_dct) print('aligned_rxn_em_dct', aligned_rxn_em_dct) if write_file: with open("align_mechs_output.txt", "w") as f: for mech_idx, rename_instructions in enumerate( rename_instructions_lst): f.write( f"Rename instructions for converting mech {mech_idx+2} to mech {mech_idx+1}:\n" ) f.write( f"First column: mech {mech_idx+2} name, Second column: mech {mech_idx+1} name\n" ) for spc_name2, spc_name1 in rename_instructions.items(): f.write( ('{0:<1s}, {1:<5s}\n').format(spc_name2, spc_name1)) f.write("\n\n") # Write the aligned_rxn_ktp_dct f.write(f"Combined_rxn_ktp_dct\n\n") f.write( ('{0:<64s}{1:<15s}{2:<15s}').format('Rxn name', 'In mech 1?', 'In mech 2?')) for rxn, ktp_dct_lst in aligned_rxn_ktp_dct.items(): rxn_name = format_rxn_name(rxn, aligned_rxn_em_dct[rxn]) present = [] for entry in ktp_dct_lst: if entry: present.append('Yes') else: present.append('No') f.write(('\n{0:<64s}{1:<15s}{2:<15s}').format( rxn_name, present[0], present[1])) f.close() if remove_loners: aligned_rxn_ktp_dct, aligned_rxn_em_dct = remove_lone_reactions( aligned_rxn_ktp_dct, aligned_rxn_em_dct) return aligned_rxn_ktp_dct, aligned_rxn_em_dct
import numpy as np import sys import ioformat.pathtools as fileio from chemkin_io.parser import mechanism as parser_mech from chemkin_io.parser import reaction as parser_rxn from mechanalyzer.calculator import rates as calc_rates from mechanalyzer.builder import checker # INPUTS mech_filename = 'chomech_v11a_ascii_cleaned.inp' temps = np.linspace(300, 3000, 28) pressures = np.array([1, 10, 100]) k_thresholds = [1e11, 1e15, 1e22] rxn_num_threshold = 2 output_filename = 'mech_check.txt' # Load dcts JOB_PATH = sys.argv[1] mech_str = fileio.read_file(JOB_PATH, mech_filename) ea_units, a_units = parser_mech.reaction_units(mech_str) rxn_block_str = parser_mech.reaction_block(mech_str) RXN_PARAM_DCT = parser_rxn.param_dct(rxn_block_str, ea_units, a_units) RXN_KTP_DCT = calc_rates.eval_rxn_param_dct(RXN_PARAM_DCT, pressures, temps) output_str = checker.run_all_checks(RXN_PARAM_DCT, RXN_KTP_DCT, k_thresholds, rxn_num_threshold) fileio.write_file(output_str, JOB_PATH, output_filename)