45
45
from pyomo .contrib .sensitivity_toolbox .sens import get_dsdp
46
46
47
47
import pyomo .environ as pyo
48
+ from pyomo .contrib .doe .utils import (
49
+ check_FIM ,
50
+ compute_FIM_metrics ,
51
+ _SMALL_TOLERANCE_DEFINITENESS ,
52
+ )
48
53
49
54
from pyomo .opt import SolverStatus
50
55
51
- # This small and positive tolerance is used when checking
52
- # if the prior is negative definite or approximately
53
- # indefinite. It is defined as a tolerance here to ensure
54
- # consistency between the code below and the tests. The
55
- # user should not need to adjust it.
56
- _SMALL_TOLERANCE_DEFINITENESS = 1e-6
57
-
58
- # This small and positive tolerance is used to check
59
- # the FIM is approximately symmetric. It is defined as
60
- # a tolerance here to ensure consistency between the code
61
- # below and the tests. The user should not need to adjust it.
62
- _SMALL_TOLERANCE_SYMMETRY = 1e-6
63
-
64
56
65
57
class ObjectiveLib (Enum ):
66
58
determinant = "determinant"
@@ -1383,24 +1375,8 @@ def check_model_FIM(self, model=None, FIM=None):
1383
1375
)
1384
1376
)
1385
1377
1386
- # Compute the eigenvalues of the FIM
1387
- evals = np .linalg .eigvals (FIM )
1388
-
1389
- # Check if the FIM is positive definite
1390
- if np .min (evals ) < - _SMALL_TOLERANCE_DEFINITENESS :
1391
- raise ValueError (
1392
- "FIM provided is not positive definite. It has one or more negative eigenvalue(s) less than -{:.1e}" .format (
1393
- _SMALL_TOLERANCE_DEFINITENESS
1394
- )
1395
- )
1396
-
1397
- # Check if the FIM is symmetric
1398
- if not np .allclose (FIM , FIM .T , atol = _SMALL_TOLERANCE_SYMMETRY ):
1399
- raise ValueError (
1400
- "FIM provided is not symmetric using absolute tolerance {}" .format (
1401
- _SMALL_TOLERANCE_SYMMETRY
1402
- )
1403
- )
1378
+ # Check FIM is positive definite and symmetric
1379
+ check_FIM (FIM )
1404
1380
1405
1381
self .logger .info (
1406
1382
"FIM provided matches expected dimensions from model and is approximately positive (semi) definite."
@@ -1455,7 +1431,7 @@ def update_FIM_prior(self, model=None, FIM=None):
1455
1431
1456
1432
self .logger .info ("FIM prior has been updated." )
1457
1433
1458
- # ToDo : Add an update function for the parameter values? --> closed loop parameter estimation?
1434
+ # TODO : Add an update function for the parameter values? --> closed loop parameter estimation?
1459
1435
# Or leave this to the user?????
1460
1436
def update_unknown_parameter_values (self , model = None , param_vals = None ):
1461
1437
raise NotImplementedError (
@@ -1474,12 +1450,37 @@ def compute_FIM_full_factorial(
1474
1450
1475
1451
Parameters
1476
1452
----------
1477
- model: model to perform the full factorial exploration on
1478
- design_ranges: dict of lists, of the form {<var_name>: [start, stop, numsteps]}
1479
- method: string to specify which method should be used
1480
- options are ``kaug`` and ``sequential``
1453
+ model: DoE model, optional
1454
+ model to perform the full factorial exploration on
1455
+ design_ranges: dict
1456
+ dictionary of lists, of the form {<var_name>: [start, stop, numsteps]}
1457
+ method: str, optional
1458
+ to specify which method should be used.
1459
+ Options are ``kaug`` and ``sequential``
1481
1460
1461
+ Returns
1462
+ -------
1463
+ fim_factorial_results: dict
1464
+ A dictionary of the results with the following keys and their corresponding
1465
+ values as a list:
1466
+
1467
+ - keys of model's experiment_inputs
1468
+ - "log10 D-opt": list of log10(D-optimality)
1469
+ - "log10 A-opt": list of log10(A-optimality)
1470
+ - "log10 E-opt": list of log10(E-optimality)
1471
+ - "log10 ME-opt": list of log10(ME-optimality)
1472
+ - "eigval_min": list of minimum eigenvalues
1473
+ - "eigval_max": list of maximum eigenvalues
1474
+ - "det_FIM": list of determinants
1475
+ - "trace_FIM": list of traces
1476
+ - "solve_time": list of solve times
1477
+
1478
+ Raises
1479
+ ------
1480
+ ValueError
1481
+ If the design_ranges' keys do not match the model's experiment_inputs' keys.
1482
1482
"""
1483
+
1483
1484
# Start timer
1484
1485
sp_timer = TicTocTimer ()
1485
1486
sp_timer .tic (msg = None )
@@ -1514,15 +1515,19 @@ def compute_FIM_full_factorial(
1514
1515
"Design ranges keys must be a subset of experimental design names."
1515
1516
)
1516
1517
1517
- # ToDo : Add more objective types? i.e., modified-E; G-opt; V-opt; etc?
1518
- # ToDo : Also, make this a result object, or more user friendly.
1518
+ # TODO : Add more objective types? i.e., modified-E; G-opt; V-opt; etc?
1519
+ # TODO : Also, make this a result object, or more user friendly.
1519
1520
fim_factorial_results = {k .name : [] for k , v in model .experiment_inputs .items ()}
1520
1521
fim_factorial_results .update (
1521
1522
{
1522
1523
"log10 D-opt" : [],
1523
1524
"log10 A-opt" : [],
1524
1525
"log10 E-opt" : [],
1525
1526
"log10 ME-opt" : [],
1527
+ "eigval_min" : [],
1528
+ "eigval_max" : [],
1529
+ "det_FIM" : [],
1530
+ "trace_FIM" : [],
1526
1531
"solve_time" : [],
1527
1532
}
1528
1533
)
@@ -1584,24 +1589,9 @@ def compute_FIM_full_factorial(
1584
1589
1585
1590
FIM = self ._computed_FIM
1586
1591
1587
- # Compute and record metrics on FIM
1588
- D_opt = np .log10 (np .linalg .det (FIM ))
1589
- A_opt = np .log10 (np .trace (FIM ))
1590
- E_vals , E_vecs = np .linalg .eig (FIM ) # Grab eigenvalues
1591
- E_ind = np .argmin (E_vals .real ) # Grab index of minima to check imaginary
1592
- # Warn the user if there is a ``large`` imaginary component (should not be)
1593
- if abs (E_vals .imag [E_ind ]) > 1e-8 :
1594
- self .logger .warning (
1595
- "Eigenvalue has imaginary component greater than 1e-6, contact developers if this issue persists."
1596
- )
1597
-
1598
- # If the real value is less than or equal to zero, set the E_opt value to nan
1599
- if E_vals .real [E_ind ] <= 0 :
1600
- E_opt = np .nan
1601
- else :
1602
- E_opt = np .log10 (E_vals .real [E_ind ])
1603
-
1604
- ME_opt = np .log10 (np .linalg .cond (FIM ))
1592
+ det_FIM , trace_FIM , E_vals , E_vecs , D_opt , A_opt , E_opt , ME_opt = (
1593
+ compute_FIM_metrics (FIM )
1594
+ )
1605
1595
1606
1596
# Append the values for each of the experiment inputs
1607
1597
for k , v in model .experiment_inputs .items ():
@@ -1611,6 +1601,10 @@ def compute_FIM_full_factorial(
1611
1601
fim_factorial_results ["log10 A-opt" ].append (A_opt )
1612
1602
fim_factorial_results ["log10 E-opt" ].append (E_opt )
1613
1603
fim_factorial_results ["log10 ME-opt" ].append (ME_opt )
1604
+ fim_factorial_results ["eigval_min" ].append (E_vals .min ())
1605
+ fim_factorial_results ["eigval_max" ].append (E_vals .max ())
1606
+ fim_factorial_results ["det_FIM" ].append (det_FIM )
1607
+ fim_factorial_results ["trace_FIM" ].append (trace_FIM )
1614
1608
fim_factorial_results ["solve_time" ].append (time_set [- 1 ])
1615
1609
1616
1610
self .fim_factorial_results = fim_factorial_results
0 commit comments