|
36 | 36 | from rmgpy.species import Species |
37 | 37 | from rmgpy.tools.data import GenericData |
38 | 38 | from rmgpy.tools.plot import parse_csv_data, plot_sensitivity, ReactionSensitivityPlot, ThermoSensitivityPlot |
| 39 | +from rmgpy.kinetics.surface import SurfaceArrheniusBEP, StickingCoefficientBEP, SurfaceArrheniusBEP |
39 | 40 |
|
40 | 41 |
|
41 | 42 | class ThermoParameterUncertainty(object): |
@@ -345,6 +346,9 @@ def __init__(self, species_list=None, reaction_list=None, output_directory=''): |
345 | 346 | self.all_kinetic_sources = None |
346 | 347 | self.thermo_input_uncertainties = None |
347 | 348 | self.kinetic_input_uncertainties = None |
| 349 | + self.thermo_covariance_matrix = None |
| 350 | + self.kinetic_covariance_matrix = None |
| 351 | + self.overall_covariance_matrix = None |
348 | 352 | self.output_directory = output_directory if output_directory else os.getcwd() |
349 | 353 |
|
350 | 354 | # For extra species needed for correlated analysis but not in model |
@@ -925,6 +929,195 @@ def local_analysis(self, sensitive_species, reaction_system_index=0, correlated= |
925 | 929 |
|
926 | 930 | return output |
927 | 931 |
|
| 932 | + def get_thermo_covariance_matrix(self): |
| 933 | + """ |
| 934 | + Return the thermo covariance matrix as a numpy array. |
| 935 | + NxN square matrix where N is the number of species in the model, |
| 936 | + with the covariance between species i and j in the ith row and jth column. |
| 937 | + Units are in (kcal/mol)^2. |
| 938 | + Must call assign_parameter_uncertainties first to populate the source dictionaries. |
| 939 | + """ |
| 940 | + assert self.thermo_input_uncertainties is not None, 'Must call assign_parameter_uncertainties first' |
| 941 | + assert len(self.thermo_input_uncertainties) > 0, 'No thermodynamic parameters found' |
| 942 | + if isinstance(self.thermo_input_uncertainties[0], np.float64): |
| 943 | + print("""Warning -- parameter uncertainties assigned without correlations. |
| 944 | +All off diagonals will be zero unless you call assign_parameter_uncertainties(correlated=True)""") |
| 945 | + self.thermo_covariance_matrix = np.float_power(np.diag(self.thermo_input_uncertainties), 2.0) |
| 946 | + return self.thermo_covariance_matrix |
| 947 | + |
| 948 | + self.thermo_covariance_matrix = np.zeros((len(self.species_list), len(self.species_list))) |
| 949 | + |
| 950 | + for i in range(len(self.species_list)): |
| 951 | + for j in range(len(self.species_list)): |
| 952 | + # assumes only sources that match are correlated |
| 953 | + # if sources match, add the product of their partial uncertainties to the covariance matrix |
| 954 | + for source_i in self.thermo_input_uncertainties[i].keys(): |
| 955 | + if source_i in self.thermo_input_uncertainties[j].keys(): |
| 956 | + self.thermo_covariance_matrix[i, j] += self.thermo_input_uncertainties[i][source_i] * self.thermo_input_uncertainties[j][source_i] |
| 957 | + |
| 958 | + return self.thermo_covariance_matrix |
| 959 | + |
| 960 | + def get_kinetic_covariance_matrix(self, k_param_engine=None): |
| 961 | + """ |
| 962 | + Return the kinetic covariance matrix as a numpy array. |
| 963 | + MxM square matrix where M is the number of reactions in the model, |
| 964 | + with the covariance between reaction i and j in the ith row and jth column. |
| 965 | + Units are in (ln(k))^2. |
| 966 | + Must call assign_parameter_uncertainties first to populate the source dictionaries. |
| 967 | + """ |
| 968 | + assert self.kinetic_input_uncertainties is not None, 'Must call assign_parameter_uncertainties first' |
| 969 | + assert len(self.kinetic_input_uncertainties) > 0, 'No kinetic parameters found' |
| 970 | + if isinstance(self.kinetic_input_uncertainties[0], np.float64): |
| 971 | + print("""Warning -- parameter uncertainties assigned without correlations. |
| 972 | +All off diagonals will be zero unless you call assign_parameter_uncertainties(correlated=True)""") |
| 973 | + self.kinetic_covariance_matrix = np.float_power(np.diag(self.kinetic_input_uncertainties), 2.0) |
| 974 | + return self.kinetic_covariance_matrix |
| 975 | + |
| 976 | + if k_param_engine is None: |
| 977 | + k_param_engine = KineticParameterUncertainty() |
| 978 | + |
| 979 | + self.kinetic_covariance_matrix = np.zeros((len(self.reaction_list), len(self.reaction_list))) |
| 980 | + |
| 981 | + # Load the family reaction maps for autogenerated families. |
| 982 | + # This allows us to determine which reactions overlap between nodes in the same autogenerated family. |
| 983 | + # This can take a minute or two. |
| 984 | + auto_gen_family_rxn_maps = {} |
| 985 | + if self.all_kinetic_sources is None: |
| 986 | + self.compile_all_sources() |
| 987 | + for family in self.all_kinetic_sources['Rate Rules'].keys(): |
| 988 | + if self.database.kinetics.families[family].auto_generated: |
| 989 | + auto_gen_family_rxn_maps[family] = self.database.kinetics.families[family].get_reaction_matches( |
| 990 | + thermo_database=self.database.thermo, |
| 991 | + remove_degeneracy=True, |
| 992 | + get_reverse=True, |
| 993 | + exact_matches_only=False, |
| 994 | + fix_labels=True |
| 995 | + ) |
| 996 | + |
| 997 | + for i, reaction in enumerate(self.reaction_list): |
| 998 | + source_dict_i = self.reaction_sources_dict[self.reaction_list[i]] |
| 999 | + for j, other_reaction in enumerate(self.reaction_list): |
| 1000 | + # assuming only sources that match are correlated |
| 1001 | + source_dict_j = self.reaction_sources_dict[self.reaction_list[j]] |
| 1002 | + |
| 1003 | + for source_i in self.kinetic_input_uncertainties[i].keys(): |
| 1004 | + if source_i in self.kinetic_input_uncertainties[j].keys(): |
| 1005 | + self.kinetic_covariance_matrix[i, j] += self.kinetic_input_uncertainties[i][source_i] * self.kinetic_input_uncertainties[j][source_i] |
| 1006 | + else: |
| 1007 | + # no match in rules, but there may be overlap if they're SIDT trees using the same family |
| 1008 | + if 'Rate Rules' in source_dict_i.keys() and 'Rate Rules' in source_dict_j.keys(): |
| 1009 | + if source_dict_i['Rate Rules'][1]['autogenerated'] and source_dict_j['Rate Rules'][1]['autogenerated'] and \ |
| 1010 | + source_dict_i['Rate Rules'][0] == source_dict_j['Rate Rules'][0]: |
| 1011 | + # get #training reactions in overlap |
| 1012 | + family = source_dict_i['Rate Rules'][0] |
| 1013 | + node_name_i = source_dict_i['Rate Rules'][1]['template'][0].label |
| 1014 | + node_name_j = source_dict_j['Rate Rules'][1]['template'][0].label |
| 1015 | + rxns_i = auto_gen_family_rxn_maps[family][node_name_i] |
| 1016 | + rxns_j = auto_gen_family_rxn_maps[family][node_name_j] |
| 1017 | + |
| 1018 | + # count overlapping reactions: |
| 1019 | + overlap_count = 0 |
| 1020 | + for r_i in rxns_i: |
| 1021 | + if r_i in rxns_j: |
| 1022 | + overlap_count += 1 |
| 1023 | + |
| 1024 | + self.kinetic_covariance_matrix[i, j] += (overlap_count / len(rxns_i)) * (overlap_count / len(rxns_j)) * (k_param_engine.dlnk_rule ** 2.0) |
| 1025 | + |
| 1026 | + # check if a training reaction exactly matches a rate rule data entry |
| 1027 | + if 'Training' in source_dict_i.keys() and 'Rate Rules' in source_dict_j.keys(): |
| 1028 | + rate_rules_training_reactions = [t[1] for t in source_dict_j['Rate Rules'][1]['training']] |
| 1029 | + weights = [t[2] for t in source_dict_j['Rate Rules'][1]['training']] |
| 1030 | + training_reaction = source_dict_i['Training'][1] |
| 1031 | + for k in range(len(rate_rules_training_reactions)): |
| 1032 | + if rate_rules_training_reactions[k].item.is_isomorphic(training_reaction.item): |
| 1033 | + self.kinetic_covariance_matrix[i, j] += weights[k] * k_param_engine.dlnk_training * k_param_engine.dlnk_rule |
| 1034 | + elif 'Training' in source_dict_j.keys() and 'Rate Rules' in source_dict_i.keys(): |
| 1035 | + rate_rules_training_reactions = [t[1] for t in source_dict_i['Rate Rules'][1]['training']] |
| 1036 | + weights = [t[2] for t in source_dict_i['Rate Rules'][1]['training']] |
| 1037 | + training_reaction = source_dict_j['Training'][1] |
| 1038 | + for k in range(len(rate_rules_training_reactions)): |
| 1039 | + if rate_rules_training_reactions[k].item.is_isomorphic(training_reaction.item): |
| 1040 | + self.kinetic_covariance_matrix[i, j] += weights[k] * k_param_engine.dlnk_training * k_param_engine.dlnk_rule |
| 1041 | + |
| 1042 | + # Add in thermo correlations if both BEP |
| 1043 | + if isinstance(reaction.kinetics, (SurfaceArrheniusBEP, StickingCoefficientBEP)) and \ |
| 1044 | + isinstance(other_reaction.kinetics, (SurfaceArrheniusBEP, StickingCoefficientBEP)): |
| 1045 | + |
| 1046 | + alpha_i = reaction.kinetics.alpha.value_si |
| 1047 | + alpha_j = other_reaction.kinetics.alpha.value_si |
| 1048 | + |
| 1049 | + R = 8.314472 |
| 1050 | + T = 1000.0 |
| 1051 | + r1_sp_indices = [self.species_list.index(sp) for sp in reaction.reactants + reaction.products] |
| 1052 | + r1_coefficients = [-1 for x in reaction.reactants] |
| 1053 | + r1_coefficients.extend([1 for x in reaction.products]) |
| 1054 | + |
| 1055 | + r2_sp_indices = [self.species_list.index(sp) for sp in other_reaction.reactants + other_reaction.products] |
| 1056 | + r2_coefficients = [-1 for x in other_reaction.reactants] |
| 1057 | + r2_coefficients.extend([1 for x in other_reaction.products]) |
| 1058 | + for r1 in range(len(r1_sp_indices)): |
| 1059 | + for r2 in range(len(r2_sp_indices)): |
| 1060 | + |
| 1061 | + covH = self.thermo_covariance_matrix[r1_sp_indices[r1], r2_sp_indices[r2]] * 4184 * 4184 # convert from kcal/mol to J/mol |
| 1062 | + nu_i = r1_coefficients[r1] |
| 1063 | + nu_j = r2_coefficients[r2] |
| 1064 | + |
| 1065 | + self.kinetic_covariance_matrix[i, j] += nu_i * nu_j * alpha_i * alpha_j * covH / np.float_power(R * T, 2.0) |
| 1066 | + |
| 1067 | + return self.kinetic_covariance_matrix |
| 1068 | + |
| 1069 | + def get_overall_covariance_matrix(self): |
| 1070 | + # make combined thermo and kinetics covariance matrix, species first, then reactions |
| 1071 | + |
| 1072 | + assert self.kinetic_input_uncertainties is not None, 'Must call assign_parameter_uncertainties first' |
| 1073 | + assert len(self.kinetic_input_uncertainties) > 0, 'No kinetic parameters found' |
| 1074 | + assert self.thermo_covariance_matrix is not None, 'Must create thermo covariance matrix first' |
| 1075 | + assert self.kinetic_covariance_matrix is not None, 'Must create kinetics covariance matrix first' |
| 1076 | + |
| 1077 | + self.overall_covariance_matrix = np.zeros((len(self.species_list) + len(self.reaction_list), len(self.species_list) + len(self.reaction_list))) |
| 1078 | + self.overall_covariance_matrix[:len(self.species_list), :len(self.species_list)] = self.thermo_covariance_matrix |
| 1079 | + self.overall_covariance_matrix[len(self.species_list):, len(self.species_list):] = self.kinetic_covariance_matrix |
| 1080 | + |
| 1081 | + # fill in the covariance between reaction and species based on BEP relationships if applicable |
| 1082 | + for i in range(len(self.reaction_list)): |
| 1083 | + reaction = self.reaction_list[i] |
| 1084 | + |
| 1085 | + BEP = None |
| 1086 | + BEP_types = [SurfaceArrheniusBEP, StickingCoefficientBEP] |
| 1087 | + if isinstance(reaction.kinetics, tuple(BEP_types)): |
| 1088 | + BEP = reaction.kinetics |
| 1089 | + elif 'Rate Rules' not in self.reaction_sources_dict[reaction]: |
| 1090 | + pass # nothing to do here if kinetics doesn't depend on thermo through a BEP |
| 1091 | + elif self.reaction_sources_dict[reaction]['Rate Rules'][1]['rules'] and \ |
| 1092 | + isinstance(self.reaction_sources_dict[reaction]['Rate Rules'][1]['rules'][0][0].data, tuple(BEP_types)): |
| 1093 | + BEP = self.reaction_sources_dict[reaction]['Rate Rules'][1]['rules'][0][0].data |
| 1094 | + elif self.reaction_sources_dict[reaction]['Rate Rules'][1]['training'] and \ |
| 1095 | + isinstance(self.reaction_sources_dict[reaction]['Rate Rules'][1]['training'][0][0].data, tuple(BEP_types)): |
| 1096 | + BEP = self.reaction_sources_dict[reaction]['Rate Rules'][1]['training'][0][0].data |
| 1097 | + |
| 1098 | + if BEP: |
| 1099 | + alpha_i = BEP.alpha.value_si |
| 1100 | + |
| 1101 | + R = 8.314472 |
| 1102 | + T = 1000.0 |
| 1103 | + r1_sp_indices = [self.species_list.index(sp) for sp in reaction.reactants + reaction.products] |
| 1104 | + r1_coefficients = [-1 for x in reaction.reactants] |
| 1105 | + r1_coefficients.extend([1 for x in reaction.products]) |
| 1106 | + |
| 1107 | + for r1 in range(len(r1_sp_indices)): # loop over species in the reaction |
| 1108 | + for j in range(len(self.species_list)): # loop over all species |
| 1109 | + covH = self.thermo_covariance_matrix[r1_sp_indices[r1], j] * 4184 * 4184 # convert from kcal/mol to J/mol |
| 1110 | + nu_i = r1_coefficients[r1] |
| 1111 | + |
| 1112 | + self.overall_covariance_matrix[len(self.species_list) + i, j] += nu_i * alpha_i * covH / (R * T) / 4184 # convert back to kcal/mol |
| 1113 | + |
| 1114 | + # fill in the lower triangle by copying from the top |
| 1115 | + for i in range(len(self.reaction_list)): |
| 1116 | + for j in range(len(self.species_list)): |
| 1117 | + self.overall_covariance_matrix[j, len(self.species_list) + i] = self.overall_covariance_matrix[len(self.species_list) + i, j] |
| 1118 | + |
| 1119 | + return self.overall_covariance_matrix |
| 1120 | + |
928 | 1121 |
|
929 | 1122 | def process_local_results(results, sensitive_species, number=10): |
930 | 1123 | """ |
|
0 commit comments