Skip to content

Commit 7bdc9de

Browse files
committed
add tests for uncertainty covariance matrices
1 parent e526102 commit 7bdc9de

2 files changed

Lines changed: 80 additions & 14 deletions

File tree

rmgpy/tools/uncertainty.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,8 @@ def assign_intermediate_uncertainties(self, g_param_engine=None, k_param_engine=
944944

945945
for ruleEntry, trainingEntry, weight in training:
946946
# TODO - test that training reactions in a tree are correlated with the exact match kind of training reaction
947-
label = '{}Training {} {}'.format(surface_prefix, family, ruleEntry) # ruleEntry should probably be the reaction equation itself
947+
# for now, we follow the old convention of treating these as rate rules
948+
label = '{}Rate Rule {} {}'.format(surface_prefix, family, ruleEntry) # ruleEntry should probably be the reaction equation itself
948949
dlnkdq[label] = weight # dlnk/dlnk_training = weight, because the training entry is scaled by the weight when it is used in the kinetics estimation
949950

950951
# There is also estimation error if rate rules are used

test/rmgpy/tools/uncertaintyTest.py

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

3030
import os
31-
31+
import copy
3232

3333
import numpy as np
3434

@@ -302,43 +302,108 @@ def test_local_analysis(self):
302302

303303
# -------------------- repeat the exact same test for new formulation --------------------------
304304
# uncorrelated analysis first
305-
self.uncertainty.assign_intermediate_uncertainties(correlated=False)
306-
output = self.uncertainty.local_analysis_intermediate(sensitive_species=sensitive_species, correlated=False)
305+
self.uncertainty.assign_intermediate_uncertainties()
306+
output = self.uncertainty.local_analysis_intermediate(sensitive_species=sensitive_species)
307307
total_variance, kinetic_uncertainty, thermo_uncertainty = output[sensitive_species[0]]
308308
assert np.isclose(total_variance, expected_uncorrelated_total_variance)
309309

310-
# order of kinetic or thermo uncertainty is not guaranteed, this sorts by name
310+
# order of kinetic or thermo uncertainty is not guaranteed, this sorts by contribution
311311
kinetic_variances = [r[2] for r in kinetic_uncertainty]
312312
kinetics_names = [r[0] for r in kinetic_uncertainty]
313-
sorted_kinetic_variances = [x for _, x in sorted(zip(kinetics_names, kinetic_variances))]
313+
sorted_kinetics_names = [x for _, x in sorted(zip(kinetic_variances, kinetics_names))][::-1]
314+
sorted_kinetic_variances = sorted(kinetic_variances, reverse=True)
314315
assert np.isclose(sorted_kinetic_variances, expected_uncorrelated_kinetics_variances).all()
315-
assert set(kinetics_names) == expected_uncorrelated_kinetics_labels
316+
assert sorted_kinetics_names == expected_uncorrelated_kinetics_labels
316317

317318
thermo_variances = [s[2] for s in thermo_uncertainty]
318319
thermo_names = [s[0] for s in thermo_uncertainty]
319-
sorted_thermo_variances = [x for _, x in sorted(zip(thermo_names, thermo_variances))]
320+
sorted_thermo_names = [x for _, x in sorted(zip(thermo_variances, thermo_names))][::-1]
321+
sorted_thermo_variances = sorted(thermo_variances, reverse=True)
320322
assert np.isclose(sorted_thermo_variances, expected_uncorrelated_thermo_variances).all()
321-
assert set(thermo_names) == expected_uncorrelated_thermo_labels
323+
assert sorted_thermo_names == expected_uncorrelated_thermo_labels
322324

323325
# now repeat for correlated analysis
324326
self.uncertainty.assign_intermediate_uncertainties(correlated=True)
325327
output = self.uncertainty.local_analysis_intermediate(sensitive_species=sensitive_species, correlated=True)
326328
total_variance, kinetic_uncertainty, thermo_uncertainty = output[sensitive_species[0]]
327329
assert np.isclose(total_variance, expected_correlated_total_variance)
328330

329-
# order of kinetic or thermo uncertainty is not guaranteed, this sorts by name
331+
# order of kinetic or thermo uncertainty is not guaranteed, this sorts by contribution
330332
kinetic_variances = [r[2] for r in kinetic_uncertainty]
331333
kinetics_names = [r[0] for r in kinetic_uncertainty]
332-
sorted_kinetic_variances = [x for _, x in sorted(zip(kinetics_names, kinetic_variances))]
334+
sorted_kinetic_variances = sorted(kinetic_variances, reverse=True)
335+
sorted_kinetics_names = [x for _, x in sorted(zip(kinetic_variances, kinetics_names))][::-1]
333336
assert np.isclose(sorted_kinetic_variances, expected_correlated_kinetics_variances).all()
334-
assert set(kinetics_names) == expected_correlated_kinetics_labels
337+
assert sorted_kinetics_names == expected_correlated_kinetics_labels
335338

336339
thermo_variances = [s[2] for s in thermo_uncertainty]
337340
thermo_names = [s[0] for s in thermo_uncertainty]
338-
sorted_thermo_variances = [x for _, x in sorted(zip(thermo_names, thermo_variances))]
341+
sorted_thermo_variances = sorted(thermo_variances, reverse=True)
342+
sorted_thermo_names = [x for _, x in sorted(zip(thermo_variances, thermo_names))][::-1]
339343
assert np.isclose(sorted_thermo_variances, expected_correlated_thermo_variances).all()
340-
assert set(thermo_names) == expected_correlated_thermo_labels
344+
assert sorted_thermo_names == expected_correlated_thermo_labels
341345

346+
def test_covariance_matrices(self):
347+
"""
348+
Test that the covariance matrices are being constructed correctly, and that the correlated uncertainties are different from the uncorrelated ones
349+
"""
350+
351+
# have to add an extra reaction to see any kinetic correlations
352+
# copy reaction 4 and change the index so it is a new reaction, but with the same source (rate rule) as the original reaction
353+
extra_reaction = copy.deepcopy(self.uncertainty.reaction_list[4])
354+
extra_reaction.index = len(self.uncertainty.reaction_list)
355+
self.uncertainty.reaction_list.append(extra_reaction)
356+
self.uncertainty.extract_sources_from_model() # this will assign the same source to the new reaction as the original reaction
357+
358+
self.uncertainty.assign_parameter_uncertainties(correlated=False)
359+
uncorrelated_thermo_inputs = np.array(self.uncertainty.thermo_input_uncertainties)
360+
uncorrelated_kinetic_inputs = np.array(self.uncertainty.kinetic_input_uncertainties)
361+
362+
self.uncertainty.assign_intermediate_uncertainties(correlated=False)
363+
uncorrelated_thermo_covariance = self.uncertainty.get_thermo_covariance_matrix()
364+
uncorrelated_kinetic_covariance = self.uncertainty.get_kinetic_covariance_matrix()
365+
366+
self.uncertainty.assign_intermediate_uncertainties(correlated=True)
367+
correlated_thermo_covariance = self.uncertainty.get_thermo_covariance_matrix()
368+
correlated_kinetic_covariance = self.uncertainty.get_kinetic_covariance_matrix()
369+
Sigma_ww_thermo = self.uncertainty._get_intermediate_thermo_covariance_matrix()
370+
Sigma_ww_kinetics = self.uncertainty._get_intermediate_kinetics_covariance_matrix()
371+
372+
# check that the diagonal elements of the correlated and uncorrelated covariance matrices are the same and equal to the squares of the input uncertainties
373+
np.testing.assert_allclose(np.diag(uncorrelated_thermo_covariance), np.float_power(uncorrelated_thermo_inputs, 2.0), rtol=1e-4)
374+
np.testing.assert_allclose(np.diag(correlated_thermo_covariance), np.float_power(uncorrelated_thermo_inputs, 2.0), rtol=1e-4)
375+
np.testing.assert_allclose(np.diag(uncorrelated_kinetic_covariance), np.float_power(uncorrelated_kinetic_inputs, 2.0), rtol=1e-4)
376+
np.testing.assert_allclose(np.diag(correlated_kinetic_covariance), np.float_power(uncorrelated_kinetic_inputs, 2.0), rtol=1e-4)
377+
378+
# check that the off-diagonal elements of the uncorrelated covariance matrix are zero
379+
off_diagonal_kinetic_uncorrelated = uncorrelated_kinetic_covariance - np.diag(np.diag(uncorrelated_kinetic_covariance))
380+
assert np.allclose(off_diagonal_kinetic_uncorrelated, 0, atol=1e-8)
381+
off_diagonal_thermo_uncorrelated = uncorrelated_thermo_covariance - np.diag(np.diag(uncorrelated_thermo_covariance))
382+
assert np.allclose(off_diagonal_thermo_uncorrelated, 0, atol=1e-8)
383+
384+
# check that the off-diagonal elements of the correlated covariance matrix are not all zero
385+
off_diagonal_kinetic_correlated = correlated_kinetic_covariance - np.diag(np.diag(correlated_kinetic_covariance))
386+
assert not np.allclose(off_diagonal_kinetic_correlated, 0, atol=1e-8)
387+
off_diagonal_thermo_correlated = correlated_thermo_covariance - np.diag(np.diag(correlated_thermo_covariance))
388+
assert not np.allclose(off_diagonal_thermo_correlated, 0, atol=1e-8)
389+
390+
# check that the correlated covariance matrices are symmetric
391+
assert np.allclose(correlated_kinetic_covariance, correlated_kinetic_covariance.T, atol=1e-8)
392+
assert np.allclose(correlated_thermo_covariance, correlated_thermo_covariance.T, atol=1e-8)
393+
assert np.allclose(Sigma_ww_kinetics, Sigma_ww_kinetics.T, atol=1e-8)
394+
assert np.allclose(Sigma_ww_thermo, Sigma_ww_thermo.T, atol=1e-8)
395+
396+
# check that the matrix is positive semi-definite by confirming that all eigenvalues are non-negative
397+
kinetic_eigenvalues = np.linalg.eigvals(correlated_kinetic_covariance)
398+
assert np.all(kinetic_eigenvalues >= -1e-8) # allow for small numerical errors
399+
thermo_eigenvalues = np.linalg.eigvals(correlated_thermo_covariance)
400+
assert np.all(thermo_eigenvalues >= -1e-8) # allow for small numerical errors
401+
intermediate_kinetic_eigenvalues = np.linalg.eigvals(Sigma_ww_kinetics)
402+
assert np.all(intermediate_kinetic_eigenvalues >= -1e-8)
403+
intermediate_thermo_eigenvalues = np.linalg.eigvals(Sigma_ww_thermo)
404+
assert np.all(intermediate_thermo_eigenvalues >= -1e-8)
405+
406+
self.uncertainty.reaction_list.pop() # remove the extra reaction so it doesn't affect other tests
342407

343408
def test_specific_species_uncertainties(self):
344409
"""

0 commit comments

Comments
 (0)