Skip to content

Commit 2776e3a

Browse files
committed
uncertainty uses recorded std dev for SIDT nodes
Up until now the uncertainty tool has assumed the kinetics trees are the original hand-built type. This enables the uncertainty tool to grab the calculated standard deviation and number of training reactions from the specific autogen tree node used, instead of incorrectly treating it as an exact match of a single rate rule with a std dev of 0.5
1 parent 33e1e95 commit 2776e3a

3 files changed

Lines changed: 50 additions & 17 deletions

File tree

rmgpy/data/kinetics/family.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4440,8 +4440,9 @@ def extract_source_from_comments(self, reaction):
44404440
[Family_Label, {'template': originalTemplate,
44414441
'degeneracy': degeneracy,
44424442
'exact': boolean_exact?,
4443-
'rules': a list of (original rate rule entry, weight in average)
4444-
'training': a list of (original rate rule entry associated with training entry, original training entry, weight in average)}]
4443+
'rules': a list of (original rate rule entry, weight in average),
4444+
'training': a list of (original rate rule entry associated with training entry, original training entry, weight in average),
4445+
'autogenerated': boolean for whether kinetics come from autogenerated subgraph isomorphic decision tree}]
44454446
44464447
44474448
where Exact is a boolean of whether the rate is an exact match, Template is
@@ -4455,6 +4456,7 @@ def extract_source_from_comments(self, reaction):
44554456
rules = None
44564457
training_entries = None
44574458
degeneracy = 1
4459+
autogenerated = False
44584460

44594461
training_reaction_pattern = r'Matched reaction\s*(\d+).*in.*training'
44604462
degeneracy_pattern = r'Multiplied by reaction path degeneracy\s*(\d+)'
@@ -4494,6 +4496,7 @@ def extract_source_from_comments(self, reaction):
44944496
autogen_node_matches = re.search(autogen_node_search_pattern, full_comment_string)
44954497
template_matches = re.search(template_pattern, full_comment_string)
44964498
if autogen_node_matches is not None: # autogenerated trees
4499+
autogenerated = True
44974500
template_str = autogen_node_matches.group(1).split('Multiplied by reaction path degeneracy')[0].strip()
44984501
template_str = template_str.split('in family')[0].strip()
44994502
tokens = template_str.split()
@@ -4510,7 +4513,7 @@ def extract_source_from_comments(self, reaction):
45104513
raise ValueError(f'Could not find rate rule in comments for reaction {reaction}.')
45114514
rules, training_entries = self.get_sources_for_template(template)
45124515
source_dict = {'template': template, 'degeneracy': degeneracy, 'exact': exact_rule,
4513-
'rules': rules, 'training': training_entries}
4516+
'rules': rules, 'training': training_entries, 'autogenerated': autogenerated}
45144517

45154518
# Source of the kinetics is from rate rules
45164519
return False, [self.label, source_dict]

rmgpy/tools/uncertainty.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
###############################################################################
2929

3030
import os
31+
import re
3132

3233
import numpy as np
3334

@@ -165,19 +166,35 @@ def get_uncertainty_value(self, source):
165166

166167
dlnk += self.dlnk_family ** 2
167168
N = len(rule_weights) + len(training_weights)
168-
if not exact:
169-
# nonexactness contribution increases as N increases
169+
if 'node_std_dev' in source_dict:
170+
# Handle autogen BM trees
171+
if source_dict['node_std_dev'] < 0:
172+
raise ValueError('Invalid value for std dev of kinetics family rule node')
173+
dlnk += source_dict['node_std_dev']
174+
if source_dict['node_n_train'] < 0:
175+
raise ValueError('Invalid number of training reactions for kinetics family rule node')
176+
N = source_dict['node_n_train']
177+
178+
# Technically every lookup in the autogenerated trees is an "exact" match because
179+
# every node template has its own fitted rate rule by definition, but here we use the
180+
# number of training reactions as an approximation of the node's specificity/generality
181+
# and add a penalty for being too general (large # of training reactions)
170182
dlnk += np.log10(N + 1) * self.dlnk_nonexact
183+
else:
184+
# Handle hand-made trees
185+
if not exact:
186+
# nonexactness contribution increases as N increases
187+
dlnk += np.log10(N + 1) * self.dlnk_nonexact
171188

172-
# Add the contributions from rules
173-
dlnk += np.sum([weight * self.dlnk_rule for weight in rule_weights])
174-
# Add the contributions from training
175-
# Even though these source from training reactions, we actually
176-
# use the uncertainty for rate rules, since these are now approximations
177-
# of the original reaction. We consider these to be independent of original the training
178-
# parameters because the rate rules may be reversing the training reactions,
179-
# which leads to more complicated dependence
180-
dlnk += np.sum([weight * self.dlnk_rule for weight in training_weights])
189+
# Add the contributions from rules
190+
dlnk += np.sum([weight * self.dlnk_rule for weight in rule_weights])
191+
# Add the contributions from training
192+
# Even though these source from training reactions, we actually
193+
# use the uncertainty for rate rules, since these are now approximations
194+
# of the original reaction. We consider these to be independent of original the training
195+
# parameters because the rate rules may be reversing the training reactions,
196+
# which leads to more complicated dependence
197+
dlnk += np.sum([weight * self.dlnk_rule for weight in training_weights])
181198

182199
return dlnk
183200

@@ -413,8 +430,20 @@ def extract_sources_from_model(self):
413430
# Do nothing here because training source already saves the entry from the training reaction
414431
pass
415432
elif 'Rate Rules' in source:
416-
# Do nothing
417-
pass
433+
# Fetch standard deviation if autogenerated tree
434+
if source['Rate Rules'][1]['autogenerated']:
435+
long_desc = source['Rate Rules'][1]['rules'][0][0].long_desc
436+
std_dev_matches = re.search(r'Standard Deviation in ln\(k\): ([0-9]*.[0-9]*)', long_desc)
437+
std_dev = 1.329 # Default value is uniform distribution with upper bound of 10x the nominal value
438+
if std_dev_matches is not None:
439+
std_dev = float(std_dev_matches[1])
440+
441+
n_train_matches = re.search('rule fitted to ([0-9]*) training reactions', long_desc)
442+
n_train = -1
443+
if n_train_matches is not None:
444+
n_train = int(n_train_matches[1])
445+
source['Rate Rules'][1]['node_std_dev'] = std_dev
446+
source['Rate Rules'][1]['node_n_train'] = n_train
418447
else:
419448
raise Exception('Source of kinetics must be either Library, PDep, Training, or Rate Rules')
420449
self.reaction_sources_dict[reaction] = source

test/rmgpy/tools/uncertaintyTest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def test_uncertainty_assignment(self):
174174
)
175175
np.testing.assert_allclose(
176176
kinetic_unc,
177-
[0.5, 1.5, 3.169924, 3.169924, 2.553605, 0.5, 2.0, 2.553605, 2.553605, 0.5],
177+
# the 13.598 value comes from the SIDT tree node uncertainty: 11.54 + non-exact penalty for N=1: log10(1+1) * 3.5 + family uncertainty: 1.0
178+
[0.5, 1.5, 3.169924, 3.169924, 2.553605, 0.5, 2.0, 13.5938, 13.5938, 0.5],
178179
rtol=1e-4
179180
)

0 commit comments

Comments
 (0)