Skip to content

Commit a576560

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 b24b1ab commit a576560

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

rmgpy/tools/uncertainty.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929

3030
import os
3131
import re
32+
import pickle
3233

3334
import numpy as np
3435

3536
import rmgpy.util as util
3637
from rmgpy.species import Species
3738
from rmgpy.tools.data import GenericData
3839
from rmgpy.tools.plot import parse_csv_data, plot_sensitivity, ReactionSensitivityPlot, ThermoSensitivityPlot
40+
from rmgpy.kinetics.surface import SurfaceArrheniusBEP, StickingCoefficientBEP, SurfaceArrheniusBEP
3941

4042

4143
class ThermoParameterUncertainty(object):
@@ -345,6 +347,9 @@ def __init__(self, species_list=None, reaction_list=None, output_directory=''):
345347
self.all_kinetic_sources = None
346348
self.thermo_input_uncertainties = None
347349
self.kinetic_input_uncertainties = None
350+
self.thermo_covariance_matrix = None
351+
self.kinetic_covariance_matrix = None
352+
self.overall_covariance_matrix = None
348353
self.output_directory = output_directory if output_directory else os.getcwd()
349354

350355
# For extra species needed for correlated analysis but not in model
@@ -915,6 +920,185 @@ def local_analysis(self, sensitive_species, reaction_system_index=0, correlated=
915920

916921
return output
917922

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

9191103
def process_local_results(results, sensitive_species, number=10):
9201104
"""

0 commit comments

Comments
 (0)