Skip to content

Commit 313391d

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 313391d

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

rmgpy/tools/uncertainty.py

Lines changed: 193 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,195 @@ 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+
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+
9281121

9291122
def process_local_results(results, sensitive_species, number=10):
9301123
"""

0 commit comments

Comments
 (0)