def build_thermo_name_dcts(mech1_str, mech2_str, temps): """ Builds the thermo dictionaries indexed by names. :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 temps: Temperatures to calculate thermochemistry (K) :type temps: list(float) :return: mech1_thermo_dct :rtype: dict[name: [thermo]] :return: mech2_thermo_dct :rtype: dict[name: [thermo]] """ mech1_thermo_block = mech_parser.thermo_block(mech1_str) if mech1_thermo_block is not None: mech1_thermo_block = remove_whitespace(mech1_thermo_block) mech1_thermo_dct = thermo.mechanism(mech1_thermo_block, temps) else: mech1_thermo_dct = None mech2_thermo_block = mech_parser.thermo_block(mech2_str) if mech2_thermo_block is not None: mech2_thermo_block = remove_whitespace(mech2_thermo_block) mech2_thermo_dct = thermo.mechanism(mech2_thermo_block, temps) else: mech2_thermo_dct = None return mech1_thermo_dct, mech2_thermo_dct
def test_thermo_block(): """ Tests the thermo_block function """ thermo_block_good = mechanism.thermo_block(THERMO_BLOCK_GOOD) thermo_block_bad = mechanism.thermo_block(THERMO_BLOCK_BAD) assert thermo_block_good == '\nC7H15OOH-1\n' assert thermo_block_bad is None
def parse_spc_therm_dct(mech_str, temps): """ Parses a raw Chemkin mechanism string and yields a spc_therm_dct *note: the input mech_str can be from reading the entire Chemkin file, not just the thermo portion :param mech_str: raw string from reading a Chemkin file :type mech_str: str :param temps: temperatures at which to do calculations (K) :type temps: numpy.ndarray :return rxn_param_dct: rxn_param_dct object :rtype: dct {rxn1: param_tuple1, rxn2: ...} """ thermo_block_str = parser_mech.thermo_block(mech_str) spc_nasa7_dct = parser_thermo.create_spc_nasa7_dct(thermo_block_str) spc_therm_dct = calc_thermo.create_spc_therm_dct(spc_nasa7_dct, temps) return spc_therm_dct
def load_spc_thermo_dcts_chemkin(thermo_filenames, direc, temps): """ Reads one or more Chemkin-formatted thermo files and calculates thermo at the indicated temperatures. Outputs a list of spc_thermo_dcts. :param thermo_filenames: filenames containing Chemkin-formatted thermo information :type thermo_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] :return spc_thermo_dcts: list of spc_thermo_dcts :rtype: list of dcts [spc_thermo_dct1, spc_thermo_dct2, ...] """ spc_thermo_dcts = [] for thermo_filename in thermo_filenames: thermo_str = parser.read_file(direc, thermo_filename) 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) return spc_thermo_dcts
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