Skip to content

Commit 0a938ac

Browse files
committed
Added FIM test metrics for doe.py
1 parent cb694be commit 0a938ac

File tree

2 files changed

+99
-7
lines changed

2 files changed

+99
-7
lines changed

pyomo/contrib/doe/doe.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@ def compute_FIM_full_factorial(
15231523
FIM = self._computed_FIM
15241524

15251525
'''
1526-
# Alex said to makek this a function
1526+
# Alex said to make this a function
15271527
# This should be a static (?) function
15281528
# Making it a function allows us to perform error tests
15291529
# on the FIM
@@ -1535,9 +1535,11 @@ def compute_metrics(FIM):
15351535
15361536
'''
15371537
# Compute and record metrics on FIM
1538-
D_opt = np.log10(np.linalg.det(FIM))
1539-
A_opt = np.log10(np.trace(FIM))
1540-
E_vals, E_vecs =np.linalg.eig(FIM) # Grab eigenvalues
1538+
det_FIM = np.linalg.det(FIM) # Determinant of FIM
1539+
D_opt = np.log10(det_FIM)
1540+
trace_FIM = np.trace(FIM) # Trace of FIM
1541+
A_opt = np.log10(trace_FIM)
1542+
E_vals, E_vecs =np.linalg.eig(FIM) # Grab eigenvalues and eigenvectors
15411543

15421544
E_ind = np.argmin(E_vals.real) # Grab index of minima to check imaginary
15431545
IMG_THERESHOLD = 1e-6 # Threshold for imaginary component
@@ -1564,9 +1566,9 @@ def compute_metrics(FIM):
15641566
fim_factorial_results["log10 E-opt"].append(E_opt)
15651567
fim_factorial_results["log10 ME-opt"].append(ME_opt)
15661568
fim_factorial_results["eigval_min"].append(E_vals[0])
1567-
fim_factorial_results["eigval_max"].append(E_vals[-1])
1568-
fim_factorial_results["det_FIM"].append(np.linalg.det(FIM))
1569-
fim_factorial_results["trace_FIM"].append(np.trace(FIM))
1569+
fim_factorial_results["eigval_max"].append(E_vals.max())
1570+
fim_factorial_results["det_FIM"].append(det_FIM)
1571+
fim_factorial_results["trace_FIM"].append(trace_FIM)
15701572
fim_factorial_results["solve_time"].append(time_set[-1])
15711573

15721574
self.fim_factorial_results = fim_factorial_results
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from pyomo.common.dependencies import (
2+
numpy as np,
3+
pandas as pd
4+
)
5+
import pytest
6+
7+
def compute_FIM_metrics(FIM):
8+
# Compute and record metrics on FIM
9+
det_FIM = np.linalg.det(FIM) # Determinant of FIM
10+
D_opt = np.log10(det_FIM)
11+
trace_FIM = np.trace(FIM) # Trace of FIM
12+
A_opt = np.log10(trace_FIM)
13+
E_vals, E_vecs =np.linalg.eig(FIM) # Grab eigenvalues and eigenvectors
14+
15+
E_ind = np.argmin(E_vals.real) # Grab index of minima to check imaginary
16+
IMG_THERESHOLD = 1e-6 # Threshold for imaginary component
17+
# Warn the user if there is a ``large`` imaginary component (should not be)
18+
if abs(E_vals.imag[E_ind]) > IMG_THERESHOLD:
19+
print(
20+
f"Eigenvalue has imaginary component greater than {IMG_THERESHOLD}, contact developers if this issue persists."
21+
)
22+
23+
# If the real value is less than or equal to zero, set the E_opt value to nan
24+
if E_vals.real[E_ind] <= 0:
25+
E_opt = np.nan
26+
else:
27+
E_opt = np.log10(E_vals.real[E_ind])
28+
29+
ME_opt = np.log10(np.linalg.cond(FIM))
30+
31+
return {
32+
"det_FIM": det_FIM,
33+
"trace_FIM": trace_FIM,
34+
"E_vals": E_vals,
35+
"D_opt": D_opt,
36+
"A_opt": A_opt,
37+
"E_opt": E_opt,
38+
"ME_opt": ME_opt
39+
}
40+
41+
def test_FIM_metrics():
42+
# Create a sample Fisher Information Matrix (FIM)
43+
FIM = np.array([[4, 2], [2, 3]])
44+
45+
# Call the function to compute metrics
46+
results = compute_FIM_metrics(FIM)
47+
48+
# Use known values for assertions
49+
det_expected = np.linalg.det(FIM)
50+
D_opt_expected = np.log10(det_expected)
51+
52+
trace_expected = np.trace(FIM)
53+
A_opt_expected = np.log10(trace_expected)
54+
55+
E_vals_expected, _ = np.linalg.eig(FIM)
56+
min_eigval = np.min(E_vals_expected.real)
57+
58+
cond_expected = np.linalg.cond(FIM)
59+
60+
assert np.isclose(results['det_FIM'], det_expected)
61+
assert np.isclose(results['trace_FIM'], trace_expected)
62+
assert np.allclose(results['E_vals'], E_vals_expected)
63+
assert np.isclose(results['D_opt'], D_opt_expected)
64+
assert np.isclose(results['A_opt'], A_opt_expected)
65+
if min_eigval.real > 0:
66+
assert np.isclose(results['E_opt'], np.log10(min_eigval))
67+
else:
68+
assert np.isnan(results['E_opt'])
69+
70+
assert np.isclose(results['ME_opt'], np.log10(cond_expected))
71+
72+
73+
def test_FIM_metrics_warning_printed(capfd):
74+
# Create a matrix with an imaginary component large enough to trigger the warning
75+
FIM = np.array([
76+
[9, -2],
77+
[9, 3]
78+
])
79+
80+
# Call the function
81+
compute_FIM_metrics(FIM)
82+
83+
# Capture stdout and stderr
84+
out, err = capfd.readouterr()
85+
86+
# Correct expected message
87+
expected_message = "Eigenvalue has imaginary component greater than 1e-06, contact developers if this issue persists."
88+
89+
# Ensure expected message is in the output
90+
assert expected_message in out

0 commit comments

Comments
 (0)