def assess_rxn_match(rxn1, rxn_ktp_dct2): """ Assess whether the reaction should be flipped. Takes a rxn_name from mech1 and searches through all of mech2 in search of a matching rxn. If a matching rxn is found, returns the matching rxn name and whether the rxn should be flipped Note: it is possible that a poorly constructed mechanism will have more than one instance of the same reaction. This function will only return the first instance of any matching reaction. However, it will print out a warning if duplicate matching reactions are found. :param rxn1: rxn key for which a match is being sought :type rxn1: tuple (rcts, prds, third_bods) :param rxn_ktp_dct2: rxn_ktp_dct for mech2 :type rxn_ktp_dct2: dict {rxn1: ktp_dct1, rxn2: ...} :return matching_rxn: rxn key for the matching reaction :rtype: tuple (rcts, prds, third_bods) :return rev_rate: whether or not the rate should be reversed :rtype: Bool """ # Get all possible orderings of the reactants and products for mech1 [rcts1, prds1, third_bods1] = rxn1 third_bod1 = third_bods1[0] rcts1_perm = list(itertools.permutations(rcts1, len(rcts1))) prds1_perm = list(itertools.permutations(prds1, len(prds1))) matching_rxn_name = None rev_rate = None already_found = False for rxn2 in rxn_ktp_dct2.keys(): [rcts2, prds2, third_bods2] = rxn2 third_bod2 = third_bods2[0] if rcts2 in rcts1_perm and prds2 in prds1_perm and third_bod1 == third_bod2: matching_rxn_name = rxn2 rev_rate = False if already_found: rxn_name1 = writer_util.format_rxn_name(rxn1) rxn_name2 = writer_util.format_rxn_name(rxn2) print( f'For the reaction {rxn_name1}, more than one match was found: {rxn_name2}' ) print('This will cause errors!') else: already_found = True if rcts2 in prds1_perm and prds2 in rcts1_perm and third_bod1 == third_bod2: matching_rxn_name = rxn2 rev_rate = True if already_found: rxn_name1 = writer_util.format_rxn_name(rxn1) rxn_name2 = writer_util.format_rxn_name(rxn2) print( f'For the reaction {rxn_name1}, more than one match was found: {rxn_name2}' ) print('This will cause errors!') else: already_found = True return matching_rxn_name, rev_rate
def write_mismatches(mismatched_rxns): """ Write reactions with mismatching rate expressions to a string :param mismatched_rxns: list of mismatched reactions with params and types of expressions :type: dct {rxn1: ((param_tuple1, param_tuple2, ...), [type1, type2, ...], rxn2: ...} :return mismatch_str: description of the mismatching reactions :rtype: str """ mismatch_str = '\nMISMATCHED REACTIONS\n\n' if mismatched_rxns != {}: mismatch_str += ( 'The following reactions have mismatched rate expressions\n') for rxn, (_, rxn_types) in mismatched_rxns.items(): rxn_name = format_rxn_name(rxn) mismatch_str += rxn_name + ': ' for type_idx, rxn_type in enumerate(rxn_types): if type_idx != 0: mismatch_str += ', ' mismatch_str += rxn_type mismatch_str += '\n' mismatch_str += '\n\n' else: mismatch_str += ( 'No reactions with mismatching rate expressions found\n\n\n') return mismatch_str
def write_lone_spcs(lone_spcs, threshold): """ Write lone species and reactions in which they participate to a string. :param lone_spcs: dictionary containing each lone species and its reactions :type: dct {lone_spc1: [rxn1, rxn2, ...], lone_spc2: ...} :param threshold: number of reactions at and below which a species is considered "lone" :type threshold: int :return lone_spcs_str: string with lone species and their reactions :rtype: str """ lone_spcs_str = ( f'\nLONE SPECIES\n\nThese species appear in {threshold} ' + 'or less reactions\n\n' ) if lone_spcs: max_spc_len = max(map(len, list(lone_spcs.keys()))) # longest spc name buffer = 5 lone_spcs_str += ( 'Species' + ' ' * (max_spc_len - 7 + buffer) + 'Reactions\n') for spc, rxns in lone_spcs.items(): lone_spcs_str += ( '{0:<' + str(max_spc_len + buffer) + 's}').format(spc) for rxn_idx, rxn in enumerate(rxns): if rxn_idx != 0: # don't add comma/space before first rxn lone_spcs_str += ', ' lone_spcs_str += format_rxn_name(rxn) lone_spcs_str += '\n' lone_spcs_str += '\n\n' else: lone_spcs_str += 'No lone species found\n\n\n' return lone_spcs_str
def plot_single_rxn(rxn, ktp_dcts, ratio_dcts, fig, axs, mech_names, format_dct): """ Plot a single reaction's k(T,P) values from all mechanisms and the ratio values relative to a reference mechanism (the reference mechanism is the first mechanism in the ktp_dct that has rate values for that pressure). :param rxn: rxn tuple describing the reaction :type rxn: tuple (rcts, prds, third_bods) :param ktp_dcts: list of ktp_dcts, one for each mechanism (some may be None) :type ktp_dcts: list [ktp_dct_mech1, ktp_dct_mech2, ...] :param ratio_dcts: list of ratio_dcts, one for each mechanism (some may be None) :type ratio_dcts: list [ratio_dct_mech1, ratio_dct_mech1, ...] :param fig: pre-allocated figure object :type fig: MatPlotLib figure object :param axs: :type axs: list [ax1, ax2] :param mech_names: :type mech_names: list [mech_name1, mech_name2] :param format_dct: dct containing color and label for each pressure :type: dct {pressure1: (color1, label1), pressure2: ...} """ for mech_idx, ktp_dct in enumerate(ktp_dcts): if ktp_dct is not None: for pressure, (temps, kts) in ktp_dct.items(): (_color, _label) = format_dct[pressure] _label += ', ' + mech_names[mech_idx] # Plot the rate constants axs[0].plot(1000 / temps, numpy.log10(kts), label=_label, color=_color, linestyle=LINESTYLES[mech_idx]) # Plot the ratios if they exist ratios_plotted = False if ratio_dcts[mech_idx] is not None: # putting second "if" below prevents errors if ratio_dcts[mech_idx][pressure] is not None: (_, ratios) = ratio_dcts[mech_idx][pressure] ratios_plotted = True axs[1].plot(1000 / temps, numpy.log10(ratios), label=_label, color=_color, linestyle=LINESTYLES[mech_idx]) # Add legend axs[0].legend(fontsize=12, loc='upper right') if ratios_plotted: axs[1].legend(fontsize=12, loc='upper right') rxn_name_formatted = writer.format_rxn_name(rxn) fig.suptitle(rxn_name_formatted, x=0.5, y=0.94, fontsize=20) return fig
def write_dct(spc_dct, max_spc_len, buffer=7): """ Write either a source_spcs or sink_spcs dct to a string """ new_str = 'Species' + ' ' * (max_spc_len - 7 + buffer) + 'Reactions\n' for spc, rxns in spc_dct.items(): new_str += ('{0:<' + str(max_spc_len + buffer) + 's}').format(spc) for rxn_idx, rxn in enumerate(rxns): if rxn_idx != 0: # don't add comma/space before first rxn new_str += ', ' new_str += format_rxn_name(rxn) new_str += '\n' new_str += '\n' return new_str
def _write_rxn_ktp_dct(rxn_ktp_dct): """ Write a rxn_ktp_dct in an easily readable string :param rxn_ktp_dct: dictionary containing k(T,P) values for reactions :type rxn_ktp_dct: dct {rxn1: ktp_dct1, rxn2: ...} :return output_str: string describing the rxn_ktp_dct :rtype: str """ output_str = '' for rxn, ktp_dct in rxn_ktp_dct.items(): output_str += format_rxn_name(rxn) for pressure, (temps, kts) in ktp_dct.items(): output_str += f'\nPressure: {pressure} atm\n' output_str += ' Temperature (K)\n ' for temp in temps: output_str += ('{0:<12.1f}'.format(temp)) output_str += '\n Rate constant\n ' for rate in kts: output_str += ('{0:<12.3E}'.format(rate)) output_str += '\n\n\n' return output_str
def write_duplicates(duplicate_rxns): """ Write reactions with more than 2 duplicate expressions to a string :param duplicate_rxns: duplicate reactions and number of expressions :type: dct {rxn1: num_of_expressions1, rxn2: ...} :return dups_str: description of the duplicate reactions :rtype: str """ dups_str = ( '\nDUPLICATE REACTIONS\n\n' + 'These reactions have more than 2 rate expressions:\n' + '(Number of rate expressions given in parentheses)\n\n' ) if duplicate_rxns != {}: for rxn, num_dups in duplicate_rxns.items(): rxn_name = format_rxn_name(rxn) dups_str += f'{rxn_name} ({num_dups})\n' else: dups_str += 'No reactions with more than 2 expressions found\n' dups_str += '\n\n' return dups_str
def reactions_block(rxn_param_dct, comments=None): """ Writes the reaction block of the mechanism file :param rxn_param_dct: dct containing the reaction parameters :type rxn_param_dct: dct {rxn:params} :return total_rxn_str: str containing the reaction block :rtype: str """ # Get the length of the longest reaction name max_len = 0 for rxn, param_dct in rxn_param_dct.items(): rxn_name = util.format_rxn_name(rxn, param_dct) if len(rxn_name) > max_len: max_len = len(rxn_name) # Loop through each reaction and get the string to write to text file total_rxn_str = 'REACTIONS CAL/MOLE MOLES\n\n' for rxn, param_dct in rxn_param_dct.items(): # Convert the reaction name from tuple of tuples to string # (Note: this includes '+M' or '(+M)' if appropriate) rxn_name = util.format_rxn_name(rxn, param_dct) if param_dct[3] is not None: # Chebyshev assert param_dct[0] is not None, ( f'For {rxn}, Chebyshev params included but highP params absent' ) one_atm_params = param_dct[ 0] # this spot is usually high-P params, but is instead 1-atm for Chebyshev alpha = param_dct[3]['alpha_elm'] tmin = param_dct[3]['t_limits'][0] tmax = param_dct[3]['t_limits'][1] pmin = param_dct[3]['p_limits'][0] pmax = param_dct[3]['p_limits'][1] rxn_str = writer_reac.chebyshev(rxn_name, one_atm_params, alpha, tmin, tmax, pmin, pmax, max_length=max_len) elif param_dct[4] is not None: # PLOG plog_dct = param_dct[4] rxn_str = writer_reac.plog(rxn_name, plog_dct, max_length=max_len) elif param_dct[2] is not None: # Troe assert param_dct[0] is not None, ( f'For {rxn}, Troe params included, highP params absent') assert param_dct[1] is not None, ( f'For {rxn}, Troe, highP params included, lowP params absent') assert param_dct[6] is not None, ( f'For {rxn}, Troe, highP, lowP params included, (+M) absent') highp_params = param_dct[0] lowp_params = param_dct[1] troe_params = param_dct[2] collid_factors = param_dct[5] rxn_str = writer_reac.troe(rxn_name, highp_params, lowp_params, troe_params, collid_factors, max_length=max_len) elif param_dct[1] is not None: # Lindemann assert param_dct[0] is not None, ( f'For {rxn}, lowP params included, highP params absent') assert param_dct[6] is not None, ( f'For {rxn}, highP, lowP params included, (+M) absent') highp_params = param_dct[0] lowp_params = param_dct[1] collid_factors = param_dct[5] rxn_str = writer_reac.lindemann(rxn_name, highp_params, lowp_params, collid_factors, max_length=max_len) else: # Simple Arrhenius assert param_dct[0] is not None, ( f'For {rxn}, the highP params absent') highp_params = param_dct[0] collid_factors = param_dct[5] rxn_str = writer_reac.arrhenius(rxn_name, highp_params, collid_factors, max_length=max_len) if comments: # add inline comments on the first line if isinstance(comments[rxn], dict): if comments[rxn]['cmts_inline'] != '': rxn_str_split = rxn_str.split('\n') rxn_str_split[0] = rxn_str_split[0] + ' ' + comments[rxn][ 'cmts_inline'] # rewrite rxn_str rxn_str = '\n'.join(rxn_str_split) # check for comments: header if isinstance(comments[rxn], dict): total_rxn_str += comments[rxn]['cmts_top'] total_rxn_str += rxn_str total_rxn_str += '\nEND \n' return total_rxn_str
def plot_single_rxn(rxn, ktp_dcts, ratio_dcts, fig, axs, mech_names, format_dct): """ Plot a single reaction's k(T,P) values from all mechanisms and the ratio values relative to a reference mechanism (the reference mechanism is the first mechanism in the ktp_dct that has rate values for that pressure). :param rxn: rxn tuple describing the reaction :type rxn: tuple (rcts, prds, third_bods) :param ktp_dcts: list of ktp_dcts, one for each mechanism (some may be None) :type ktp_dcts: list [ktp_dct_mech1, ktp_dct_mech2, ...] :param ratio_dcts: list of ratio_dcts, one for each mechanism (some may be None) :type ratio_dcts: list [ratio_dct_mech1, ratio_dct_mech1, ...] :param fig: pre-allocated figure object :type fig: MatPlotLib figure object :param axs: :type axs: list [ax1, ax2] :param mech_names: :type mech_names: list [mech_name1, mech_name2] :param format_dct: dct containing color and label for each pressure :type: dct {pressure1: (color1, label1), pressure2: ...} """ # Gr ratios_plotted = False for mech_idx, ktp_dct in enumerate(ktp_dcts): if ktp_dct is not None: for pressure, (temps, kts) in ktp_dct.items(): (_color, _label) = format_dct[pressure] _label += ', ' + mech_names[mech_idx] # Plot the rate constants axs[0].plot(1000 / temps, numpy.log10(kts), label=_label, color=_color, linestyle=LINES[mech_idx]) # Plot the ratios if they exist if ratio_dcts[mech_idx] is not None: # if ratio_dcts[mech_idx][pressure] is not None: if pressure in ratio_dcts[mech_idx].keys(): (_, ratios) = ratio_dcts[mech_idx][pressure] ratios_plotted = True axs[1].plot(1000 / temps, numpy.log10(ratios), label=_label, color=_color, linestyle=LINES[mech_idx]) # Check for the 'max_to_high' case # Grab set of temps to calculate ratio # BELOW CODE ASSUMES ALL TEMP RANGES IN KTP DCT THE SAME ratio_temps = tuple(ktp_dct.values())[0][1] if ratio_dcts[mech_idx] is not None: if 'max_to_high' in ratio_dcts[mech_idx].keys(): (_, ratios) = ratio_dcts[mech_idx]['max_to_high'] ratios_plotted = True _color = 'k' _label = 'max to P-indep, ' + mech_names[mech_idx] axs[1].plot(1000 / ratio_temps, numpy.log10(ratios), label=_label, color=_color, linestyle=LINES[mech_idx]) # Do some formatting axs[0].legend(fontsize=12, loc='upper right') if ratios_plotted: axs[1].legend(fontsize=12, loc='upper right') rxn_name_formatted = writer.format_rxn_name(rxn) fig.suptitle(rxn_name_formatted, x=0.5, y=0.94, fontsize=20) return fig