Skip to content

Commit b9860ae

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 b00cca9 commit b9860ae

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):
@@ -333,6 +335,9 @@ def __init__(self, species_list=None, reaction_list=None, output_directory=''):
333335
self.all_kinetic_sources = None
334336
self.thermo_input_uncertainties = None
335337
self.kinetic_input_uncertainties = None
338+
self.thermo_covariance_matrix = None
339+
self.kinetic_covariance_matrix = None
340+
self.overall_covariance_matrix = None
336341
self.output_directory = output_directory if output_directory else os.getcwd()
337342

338343
# For extra species needed for correlated analysis but not in model
@@ -878,6 +883,185 @@ def local_analysis(self, sensitive_species, reaction_system_index=0, correlated=
878883

879884
return output
880885

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

8821066
def process_local_results(results, sensitive_species, number=10):
8831067
"""

0 commit comments

Comments
 (0)