Skip to content

Commit a32bfd2

Browse files
committed
Add uncertainty tool export covariance functions
This commit allows the user to export covariance matrices of a mechanism's estimated uncertainty as numpy arrays`
1 parent 3436a03 commit a32bfd2

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

rmgpy/tools/uncertainty.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from rmgpy.species import Species
3737
from rmgpy.tools.data import GenericData
3838
from rmgpy.tools.plot import parse_csv_data, plot_sensitivity, ReactionSensitivityPlot, ThermoSensitivityPlot
39+
from rmgpy.kinetics.surface import SurfaceArrheniusBEP, StickingCoefficientBEP, SurfaceArrheniusBEP
3940

4041

4142
class ThermoParameterUncertainty(object):
@@ -345,6 +346,9 @@ def __init__(self, species_list=None, reaction_list=None, output_directory=''):
345346
self.all_kinetic_sources = None
346347
self.thermo_input_uncertainties = None
347348
self.kinetic_input_uncertainties = None
349+
self.thermo_covariance_matrix = None
350+
self.kinetic_covariance_matrix = None
351+
self.overall_covariance_matrix = None
348352
self.output_directory = output_directory if output_directory else os.getcwd()
349353

350354
# For extra species needed for correlated analysis but not in model
@@ -925,6 +929,185 @@ def local_analysis(self, sensitive_species, reaction_system_index=0, correlated=
925929

926930
return output
927931

932+
def get_thermo_covariance_matrix(self):
933+
"""
934+
Export the thermo covariance matrix as a numpy array
935+
"""
936+
assert self.thermo_input_uncertainties is not None, 'Must call assign_parameter_uncertainties first'
937+
assert len(self.thermo_input_uncertainties) > 0, 'No thermodynamic parameters found'
938+
if isinstance(self.thermo_input_uncertainties[0], np.float64):
939+
print("""Warning -- parameter uncertainties assigned without correlations.
940+
All off diagonals will be zero unless you call assign_parameter_uncertainties(correlated=True)""")
941+
self.thermo_covariance_matrix = np.float_power(np.diag(self.thermo_input_uncertainties), 2.0)
942+
return self.thermo_covariance_matrix
943+
944+
self.thermo_covariance_matrix = np.zeros((len(self.species_list), len(self.species_list)))
945+
946+
for i in range(len(self.species_list)):
947+
for j in range(len(self.species_list)):
948+
949+
# assuming only sources that match are correlated
950+
for source_i in self.thermo_input_uncertainties[i].keys():
951+
if source_i in self.thermo_input_uncertainties[j].keys():
952+
self.thermo_covariance_matrix[i, j] += self.thermo_input_uncertainties[i][source_i] * self.thermo_input_uncertainties[j][source_i]
953+
954+
return self.thermo_covariance_matrix
955+
956+
def get_kinetic_covariance_matrix(self, k_param_engine=None):
957+
"""
958+
Export the kinetic covariance matrix as a numpy array
959+
"""
960+
assert self.kinetic_input_uncertainties is not None, 'Must call assign_parameter_uncertainties first'
961+
assert len(self.kinetic_input_uncertainties) > 0, 'No kinetic parameters found'
962+
if isinstance(self.kinetic_input_uncertainties[0], np.float64):
963+
print("""Warning -- parameter uncertainties assigned without correlations.
964+
All off diagonals will be zero unless you call assign_parameter_uncertainties(correlated=True)""")
965+
self.kinetic_covariance_matrix = np.float_power(np.diag(self.kinetic_input_uncertainties), 2.0)
966+
return self.kinetic_covariance_matrix
967+
968+
if k_param_engine is None:
969+
k_param_engine = KineticParameterUncertainty()
970+
971+
self.kinetic_covariance_matrix = np.zeros((len(self.reaction_list), len(self.reaction_list)))
972+
973+
# takes a while to load the family reaction maps
974+
auto_gen_family_rxn_maps = {}
975+
if self.all_kinetic_sources is None:
976+
self.compile_all_sources()
977+
for family in self.all_kinetic_sources['Rate Rules'].keys():
978+
if self.database.kinetics.families[family].auto_generated:
979+
auto_gen_family_rxn_maps[family] = self.database.kinetics.families[family].get_reaction_matches(
980+
thermo_database=self.database.thermo,
981+
remove_degeneracy=True,
982+
get_reverse=True,
983+
exact_matches_only=False,
984+
fix_labels=True
985+
)
986+
987+
for i, reaction in enumerate(self.reaction_list):
988+
source_dict_i = self.reaction_sources_dict[self.reaction_list[i]]
989+
for j, other_reaction in enumerate(self.reaction_list):
990+
# assuming only sources that match are correlated
991+
source_dict_j = self.reaction_sources_dict[self.reaction_list[j]]
992+
993+
for source_i in self.kinetic_input_uncertainties[i].keys():
994+
if source_i in self.kinetic_input_uncertainties[j].keys():
995+
self.kinetic_covariance_matrix[i, j] += self.kinetic_input_uncertainties[i][source_i] * self.kinetic_input_uncertainties[j][source_i]
996+
else:
997+
# no match in rules, but there may be overlap if they're SIDT trees using the same family
998+
if 'Rate Rules' in source_dict_i.keys() and 'Rate Rules' in source_dict_j.keys():
999+
if source_dict_i['Rate Rules'][1]['autogenerated'] and source_dict_j['Rate Rules'][1]['autogenerated'] and \
1000+
source_dict_i['Rate Rules'][0] == source_dict_j['Rate Rules'][0]:
1001+
# get #training reactions in overlap
1002+
family = source_dict_i['Rate Rules'][0]
1003+
node_name_i = source_dict_i['Rate Rules'][1]['template'][0].label
1004+
node_name_j = source_dict_j['Rate Rules'][1]['template'][0].label
1005+
rxns_i = auto_gen_family_rxn_maps[family][node_name_i]
1006+
rxns_j = auto_gen_family_rxn_maps[family][node_name_j]
1007+
1008+
# count overlapping reactions:
1009+
overlap_count = 0
1010+
for r_i in rxns_i:
1011+
if r_i in rxns_j:
1012+
overlap_count += 1
1013+
1014+
self.kinetic_covariance_matrix[i, j] += (overlap_count / len(rxns_i)) * (overlap_count / len(rxns_j)) * (k_param_engine.dlnk_rule ** 2.0)
1015+
1016+
# check if a training reaction exactly matches a rate rule data entry
1017+
if 'Training' in source_dict_i.keys() and 'Rate Rules' in source_dict_j.keys():
1018+
rate_rules_training_reactions = [t[1] for t in source_dict_j['Rate Rules'][1]['training']]
1019+
weights = [t[2] for t in source_dict_j['Rate Rules'][1]['training']]
1020+
training_reaction = source_dict_i['Training'][1]
1021+
for k in range(len(rate_rules_training_reactions)):
1022+
if rate_rules_training_reactions[k].item.is_isomorphic(training_reaction.item):
1023+
self.kinetic_covariance_matrix[i, j] += weights[k] * k_param_engine.dlnk_training * k_param_engine.dlnk_rule
1024+
elif 'Training' in source_dict_j.keys() and 'Rate Rules' in source_dict_i.keys():
1025+
rate_rules_training_reactions = [t[1] for t in source_dict_i['Rate Rules'][1]['training']]
1026+
weights = [t[2] for t in source_dict_i['Rate Rules'][1]['training']]
1027+
training_reaction = source_dict_j['Training'][1]
1028+
for k in range(len(rate_rules_training_reactions)):
1029+
if rate_rules_training_reactions[k].item.is_isomorphic(training_reaction.item):
1030+
self.kinetic_covariance_matrix[i, j] += weights[k] * k_param_engine.dlnk_training * k_param_engine.dlnk_rule
1031+
1032+
# Add in thermo correlations if both BEP
1033+
if isinstance(reaction.kinetics, (SurfaceArrheniusBEP, StickingCoefficientBEP)) and \
1034+
isinstance(other_reaction.kinetics, (SurfaceArrheniusBEP, StickingCoefficientBEP)):
1035+
1036+
alpha_i = reaction.kinetics.alpha.value_si
1037+
alpha_j = other_reaction.kinetics.alpha.value_si
1038+
1039+
R = 8.314472
1040+
T = 1000.0
1041+
r1_sp_indices = [self.species_list.index(sp) for sp in reaction.reactants + reaction.products]
1042+
r1_coefficients = [-1 for x in reaction.reactants]
1043+
r1_coefficients.extend([1 for x in reaction.products])
1044+
1045+
r2_sp_indices = [self.species_list.index(sp) for sp in other_reaction.reactants + other_reaction.products]
1046+
r2_coefficients = [-1 for x in other_reaction.reactants]
1047+
r2_coefficients.extend([1 for x in other_reaction.products])
1048+
for r1 in range(len(r1_sp_indices)):
1049+
for r2 in range(len(r2_sp_indices)):
1050+
1051+
covH = self.thermo_covariance_matrix[r1_sp_indices[r1], r2_sp_indices[r2]] * 4184 * 4184 # convert from kcal/mol to J/mol
1052+
nu_i = r1_coefficients[r1]
1053+
nu_j = r2_coefficients[r2]
1054+
1055+
self.kinetic_covariance_matrix[i, j] += nu_i * nu_j * alpha_i * alpha_j * covH / np.float_power(R * T, 2.0)
1056+
1057+
return self.kinetic_covariance_matrix
1058+
1059+
def get_overall_covariance_matrix(self):
1060+
# make combined thermo and kinetics covariance matrix, species first, then reactions
1061+
1062+
assert self.kinetic_input_uncertainties is not None, 'Must call assign_parameter_uncertainties first'
1063+
assert len(self.kinetic_input_uncertainties) > 0, 'No kinetic parameters found'
1064+
assert self.thermo_covariance_matrix is not None, 'Must create thermo covariance matrix first'
1065+
assert self.kinetic_covariance_matrix is not None, 'Must create kinetics covariance matrix first'
1066+
1067+
self.overall_covariance_matrix = np.zeros((len(self.species_list) + len(self.reaction_list), len(self.species_list) + len(self.reaction_list)))
1068+
self.overall_covariance_matrix[:len(self.species_list), :len(self.species_list)] = self.thermo_covariance_matrix
1069+
self.overall_covariance_matrix[len(self.species_list):, len(self.species_list):] = self.kinetic_covariance_matrix
1070+
1071+
# fill in the covariance between reaction and species based on BEP relationships if applicable
1072+
for i in range(len(self.reaction_list)):
1073+
reaction = self.reaction_list[i]
1074+
1075+
BEP = None
1076+
BEP_types = [SurfaceArrheniusBEP, StickingCoefficientBEP]
1077+
if isinstance(reaction.kinetics, tuple(BEP_types)):
1078+
BEP = reaction.kinetics
1079+
elif 'Rate Rules' not in self.reaction_sources_dict[reaction]:
1080+
pass # nothing to do here if kinetics doesn't depend on thermo through a BEP
1081+
elif self.reaction_sources_dict[reaction]['Rate Rules'][1]['rules'] and \
1082+
isinstance(self.reaction_sources_dict[reaction]['Rate Rules'][1]['rules'][0][0].data, tuple(BEP_types)):
1083+
BEP = self.reaction_sources_dict[reaction]['Rate Rules'][1]['rules'][0][0].data
1084+
elif self.reaction_sources_dict[reaction]['Rate Rules'][1]['training'] and \
1085+
isinstance(self.reaction_sources_dict[reaction]['Rate Rules'][1]['training'][0][0].data, tuple(BEP_types)):
1086+
BEP = self.reaction_sources_dict[reaction]['Rate Rules'][1]['training'][0][0].data
1087+
1088+
if BEP:
1089+
alpha_i = BEP.alpha.value_si
1090+
1091+
R = 8.314472
1092+
T = 1000.0
1093+
r1_sp_indices = [self.species_list.index(sp) for sp in reaction.reactants + reaction.products]
1094+
r1_coefficients = [-1 for x in reaction.reactants]
1095+
r1_coefficients.extend([1 for x in reaction.products])
1096+
1097+
for r1 in range(len(r1_sp_indices)): # loop over species in the reaction
1098+
for j in range(len(self.species_list)): # loop over all species
1099+
covH = self.thermo_covariance_matrix[r1_sp_indices[r1], j] * 4184 * 4184 # convert from kcal/mol to J/mol
1100+
nu_i = r1_coefficients[r1]
1101+
1102+
self.overall_covariance_matrix[len(self.species_list) + i, j] += nu_i * alpha_i * covH / (R * T) / 4184 # convert back to kcal/mol
1103+
1104+
# fill in the lower triangle by copying from the top
1105+
for i in range(len(self.reaction_list)):
1106+
for j in range(len(self.species_list)):
1107+
self.overall_covariance_matrix[j, len(self.species_list) + i] = self.overall_covariance_matrix[len(self.species_list) + i, j]
1108+
1109+
return self.overall_covariance_matrix
1110+
9281111

9291112
def process_local_results(results, sensitive_species, number=10):
9301113
"""

0 commit comments

Comments
 (0)