2828###############################################################################
2929
3030import os
31- import re
31+ import pickle
3232
3333import numpy as np
3434
@@ -43,7 +43,7 @@ class ThermoParameterUncertainty(object):
4343 This class is an engine that generates the species uncertainty based on its thermo sources.
4444 """
4545
46- def __init__ (self , dG_library = 1.5 , dG_QM = 3.0 , dG_GAV = 1.5 , dG_group = 0.7159 , dG_ADS_correction = 6.918 , dG_surf_lib = 6.918 ):
46+ def __init__ (self , dG_library = 1.5 , dG_QM = 3.0 , dG_GAV = 1.5 , dG_group = 0.7159 , dG_ADS_correction = 6.918 , dG_surf_lib = 6.918 , other_covariances = None ):
4747 """
4848 Initialize the different uncertainties dG_library, dG_QM, dG_GAV, and dG_other with set values
4949 in units of kcal/mol.
@@ -57,6 +57,7 @@ def __init__(self, dG_library=1.5, dG_QM=3.0, dG_GAV=1.5, dG_group=0.7159, dG_AD
5757 self .dG_group = dG_group
5858 self .dG_ADS_correction = dG_ADS_correction
5959 self .dG_surf_lib = dG_surf_lib
60+ self .other_covariances = other_covariances # storage of covariances as a dict. Keys are sorted tuples of parameter labels and values are covariances
6061
6162 def get_uncertainty_value (self , source ):
6263 """
@@ -172,6 +173,12 @@ def _get_covariance_qq(self, q_label1, q_label2):
172173 if corr_type1 is None or corr_type2 is None :
173174 raise ValueError (f'Could not determine the type of the correlated parameters from their labels { q_label1 } and { q_label2 } ' )
174175
176+ if self .other_covariances is not None :
177+ # check if covariance is specified in other_covariances dict
178+ sorted_labels = tuple (sorted ([q_label1 , q_label2 ]))
179+ if sorted_labels in self .other_covariances :
180+ return self .other_covariances [sorted_labels ]
181+
175182 if corr_type1 != corr_type2 :
176183 return 0
177184 elif q_label1 == q_label2 :
@@ -394,11 +401,13 @@ class Uncertainty(object):
394401 for a single RMG-generated mechanism.
395402 """
396403
397- def __init__ (self , species_list = None , reaction_list = None , output_directory = '' ):
404+ def __init__ (self , species_list = None , reaction_list = None , output_directory = '' , thermo_covariance_libraries = None , kinetic_covariance_libraries = None ):
398405 """
399406 `species_list`: list of RMG species objects
400407 `reaction_list`: list of RMG reaction objects
401408 `outputDirectoy`: directory path for saving output files from the analyses
409+ `thermo_covariance_libraries`: list of library paths to pull additional thermo covariances from
410+ `kinetic_covariance_libraries`: list of library paths to pull additional kinetic covariances from
402411 """
403412 self .database = None
404413 self .species_list = species_list
@@ -418,6 +427,10 @@ def __init__(self, species_list=None, reaction_list=None, output_directory=''):
418427 self .all_thermo_intermediates = None # list of labels of underlying thermo parameters
419428 self .all_kinetics_intermediates = None # list of labels of underlying kinetic parameters
420429 self .output_directory = output_directory if output_directory else os .getcwd ()
430+ self .thermo_covariance_libraries = thermo_covariance_libraries
431+ self .kinetic_covariance_libraries = kinetic_covariance_libraries
432+ self .thermo_covariances_dict = {} # dictionary to store covariances from covariance libraries
433+ self .kinetic_covariances_dict = {} # dictionary to store covariances from covariance libraries
421434
422435 # For extra species needed for correlated analysis but not in model
423436 self .extra_species = []
@@ -507,6 +520,63 @@ def retrieve_saturated_species_from_list(self, species):
507520 else :
508521 raise Exception ('Could not retrieve saturated species form of {0} from the species list' .format (species ))
509522
523+ def load_thermo_covariances_from_libraries (self ):
524+ assert self .database is not None , 'Must load database before loading covariance libraries, since we need the path to the covariance libraries from the database'
525+ if self .thermo_covariance_libraries is not None :
526+ for cov_lib in self .thermo_covariance_libraries :
527+ library_name = os .path .basename (cov_lib )
528+ if library_name in self .database .thermo .libraries :
529+ library = self .database .thermo .libraries [library_name ]
530+ else :
531+ raise ValueError (f'Thermo covariance library { library_name } not found in the loaded database' )
532+
533+ covariance_file = os .path .join (cov_lib , 'covariance.npy' )
534+ covariance_molecules = os .path .join (cov_lib , 'molecules.pickle' )
535+ if not os .path .isfile (covariance_file ):
536+ raise ValueError (f'Thermo covariance file { covariance_file } not found in library { cov_lib } ' )
537+ if not os .path .isfile (covariance_molecules ):
538+ raise ValueError (f'Thermo molecules file { covariance_molecules } not found in library { cov_lib } ' )
539+ cov_data = np .load (covariance_file )
540+ with open (covariance_molecules , 'rb' ) as f :
541+ sp_adj_lists = pickle .load (f )
542+ cov_specs = [Species ().from_adjacency_list (adj_list ) for adj_list in sp_adj_lists ]
543+
544+ # quick check to make sure the covariance data and molecule data are consistent with each other
545+ if cov_data .shape [0 ] != len (cov_specs ):
546+ raise ValueError (f'Covariance data and molecule data in library { cov_lib } are inconsistent: covariance data has shape { cov_data .shape } but molecule data has length { len (cov_specs )} ' )
547+
548+ # load the labels, but only include species in the model
549+ subset_indices = [] # keep track of indices relevant to the model
550+ for i_lib , lib_species in enumerate (cov_specs ):
551+ i_sp = get_i_thing (lib_species , self .species_list )
552+ if i_sp < 0 :
553+ continue
554+
555+ # make sure the species actually comes from this library, otherwise skip
556+ result = self .database .thermo .get_thermo_data_from_library (lib_species , library )
557+ if result is not None :
558+ # match the label as constructed in assign_intermediate_uncertainties,
559+ # where the number corresponds to the index of the species in species_list
560+ try :
561+ label = 'Library {}' .format (self .species_list [i_sp ].to_chemkin ())
562+ except IndexError :
563+ label = 'Library {}' .format (self .extra_species [i_sp - len (self .species_list )].to_chemkin ())
564+ lib_species .label = label
565+ subset_indices .append (i_lib )
566+
567+ # fill in the dictionary of covariances from the covariance libraries,
568+ # with keys being sorted tuples of the labels of the correlated parameters
569+ # and values being the covariance between those parameters
570+ tolerance = 1e-12 # consider anything with covariance less than this to be uncorrelated
571+ for i , index_i in enumerate (subset_indices ):
572+ for j in range (i , len (subset_indices )):
573+ index_j = subset_indices [j ]
574+ if cov_data [index_i , index_j ] > tolerance :
575+ label1 = cov_specs [index_i ].label
576+ label2 = cov_specs [index_j ].label
577+ covariance = cov_data [index_i , index_j ]
578+ self .covariances_dict [tuple (sorted ([label1 , label2 ]))] = covariance
579+
510580 def extract_sources_from_model (self ):
511581 """
512582 Extract the source data from the model using its comments.
@@ -626,6 +696,10 @@ def extract_sources_from_model(self):
626696 for spc in self .extra_species :
627697 self .species_list .remove (spc )
628698
699+ # -------------------- load covariance libraries ------------------------#
700+ self .load_thermo_covariances_from_libraries ()
701+ self .load_kinetic_covariances_from_libraries ()
702+
629703 def compile_all_sources (self ):
630704 """
631705 Compile two dictionaries composed of all the thermo and kinetic sources. Must
@@ -1455,3 +1529,9 @@ def process_local_results(results, sensitive_species, number=10):
14551529 output += '================================================================================\n \n '
14561530
14571531 return processed_results , output
1532+
1533+ def get_i_thing (thing , thing_list ):
1534+ for i in range (len (thing_list )):
1535+ if thing .is_isomorphic (thing_list [i ]):
1536+ return i
1537+ return - 1
0 commit comments