Skip to content

Commit b8df9e1

Browse files
committed
Updated driver.rst and datarec.rst files
1 parent b9962dd commit b8df9e1

File tree

4 files changed

+89
-71
lines changed

4 files changed

+89
-71
lines changed

doc/OnlineDocs/explanation/analysis/parmest/datarec.rst

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ The following example returns model values from a Pyomo Expression.
4242
>>> for i in range(data.shape[0]):
4343
... exp_list.append(RooneyBieglerExperiment(data.loc[i, :]))
4444

45-
>>> # Define objective
46-
>>> def SSE(model):
47-
... expr = (model.experiment_outputs[model.y]
48-
... - model.response_function[model.experiment_outputs[model.hour]]
49-
... ) ** 2
50-
... return expr
51-
52-
>>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=None)
45+
>>> pest = parmest.Estimator(exp_list, obj_function="SSE", solver_options=None)
5346
>>> obj, theta, var_values = pest.theta_est(return_values=['response_function'])
5447
>>> #print(var_values)

doc/OnlineDocs/explanation/analysis/parmest/driver.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ Section.
7878

7979
>>> import pyomo.contrib.parmest.parmest as parmest
8080
>>> pest = parmest.Estimator(exp_list, obj_function="SSE")
81-
>>> obj_val, theta_val = pest.theta_est()
8281

8382
Optionally, solver options can be supplied, e.g.,
8483

@@ -87,7 +86,6 @@ Optionally, solver options can be supplied, e.g.,
8786

8887
>>> solver_options = {"max_iter": 6000}
8988
>>> pest = parmest.Estimator(exp_list, obj_function="SSE", solver_options=solver_options)
90-
>>> obj_val, theta_val = pest.theta_est()
9189

9290

9391
List of experiment objects
@@ -136,7 +134,7 @@ If the Pyomo model is not written as a two-stage stochastic programming problem
136134
this format, the user can select the "SSE" or "SSE_weighted" built-in objective
137135
functions. If the user wants to use an objective that is different from the built-in
138136
options, a custom objective function can be defined for parameter estimation. However,
139-
covariance estimation will not support this custom objective function. The objective
137+
covariance matrix estimation will not support this custom objective function. The objective
140138
function (built-in or custom) has a single argument, which is the model from a single
141139
experiment.
142140
The objective function returns a Pyomo
@@ -161,4 +159,10 @@ estimation solve from the square problem solution, set optional argument ``solve
161159
argument ``(initialize_parmest_model=True)``. Different initial guess values for the fitted
162160
parameters can be provided using optional argument `theta_values` (**Pandas Dataframe**)
163161

164-
3. Solve parameter estimation problem by calling :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est`
162+
3. Solve parameter estimation problem by calling
163+
:class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est`, e.g.,
164+
165+
.. doctest::
166+
:skipif: not __import__('pyomo.contrib.parmest.parmest').contrib.parmest.parmest.parmest_available
167+
168+
>>> obj_val, theta_val = pest.theta_est()

pyomo/contrib/parmest/parmest.py

Lines changed: 69 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def SSE(model):
239239
model: annotated Pyomo model
240240
"""
241241
# check if the model has all the required suffixes
242-
_check_model_labels_helper(model)
242+
_check_model_labels_helper(model, logging_level=logging.ERROR)
243243

244244
# SSE between the prediction and observation of the measured variables
245245
expr = sum((y - y_hat) ** 2 for y_hat, y in model.experiment_outputs.items())
@@ -256,7 +256,7 @@ def SSE_weighted(model):
256256
model: annotated Pyomo model
257257
"""
258258
# check if the model has all the required suffixes
259-
_check_model_labels_helper(model)
259+
_check_model_labels_helper(model, logging_level=logging.ERROR)
260260

261261
# Check that measurement errors exist
262262
if hasattr(model, "measurement_error"):
@@ -287,12 +287,13 @@ def SSE_weighted(model):
287287
)
288288

289289

290-
def _check_model_labels_helper(model):
290+
def _check_model_labels_helper(model, logging_level):
291291
"""
292292
Checks if the annotated Pyomo model contains the necessary suffixes
293293
294294
Argument:
295295
model: annotated Pyomo model for suffix checking
296+
logging_level: logging level specified by the user, e.g., logging.INFO
296297
"""
297298
required_attrs = ("experiment_outputs", "unknown_parameters")
298299

@@ -304,17 +305,19 @@ def _check_model_labels_helper(model):
304305
f"Experiment model is missing required attribute(s): {missing_str}"
305306
)
306307

307-
logger.setLevel(level=logging.INFO)
308-
logger.info("Model has expected labels.")
308+
# set the logging
309+
logger.setLevel(level=logging_level)
310+
if logging_level == logging.INFO:
311+
logger.info("Model has expected labels.")
309312

310313

311314
def _get_labeled_model_helper(experiment):
312315
"""
313316
Checks if the Experiment class object has a "get_labeled_model" function
314317
315318
Argument:
316-
experiment: Estimator class object that contains the model for a particular
317-
experimental condition
319+
experiment: Estimator class object that contains the Pyomo model
320+
for a particular experimental condition
318321
319322
Returns:
320323
Annotated Pyomo model
@@ -348,19 +351,20 @@ class UnsupportedArgsLib(Enum):
348351

349352

350353
# Compute the Jacobian matrix of measured variables with respect to the parameters
351-
def _compute_jacobian(experiment, theta_vals, step, solver, tee):
354+
def _compute_jacobian(experiment, theta_vals, step, solver, tee, logging_level):
352355
"""
353356
Computes the Jacobian matrix of the measured variables with respect to the
354357
parameters using the central finite difference scheme
355358
356359
Arguments:
357-
experiment: Estimator class object that contains the model for a particular
358-
experimental condition
360+
experiment: Estimator class object that contains the Pyomo model
361+
for a particular experimental condition
359362
theta_vals: dictionary containing the estimates of the unknown parameters
360363
step: float used for relative perturbation of the parameters,
361-
e.g., step=0.02 is a 2% perturbation
364+
e.g., step=0.02 is a 2% perturbation
362365
solver: string ``solver`` object specified by the user, e.g., 'ipopt'
363366
tee: boolean solver option to be passed for verbose output
367+
logging_level: logging level specified by the user, e.g., logging.INFO
364368
365369
Returns:
366370
J: Jacobian matrix
@@ -369,7 +373,7 @@ def _compute_jacobian(experiment, theta_vals, step, solver, tee):
369373
model = _get_labeled_model_helper(experiment)
370374

371375
# check if the model has all the required suffixes
372-
_check_model_labels_helper(model)
376+
_check_model_labels_helper(model, logging_level)
373377

374378
# fix the value of the unknown parameters to the estimated values
375379
params = [k for k, v in model.unknown_parameters.items()]
@@ -452,36 +456,37 @@ def _compute_jacobian(experiment, theta_vals, step, solver, tee):
452456

453457
# Compute the covariance matrix of the estimated parameters
454458
def compute_covariance_matrix(
455-
experiment_list, method, theta_vals, step, solver, tee, estimated_var=None
459+
experiment_list,
460+
method,
461+
theta_vals,
462+
step,
463+
solver,
464+
tee,
465+
logging_level,
466+
estimated_var=None,
456467
):
457468
"""
458469
Computes the covariance matrix of the estimated parameters using
459470
'finite_difference' and 'automatic_differentiation_kaug' methods
460471
461472
Arguments:
462-
experiment_list: list of Estimator class objects containing the model for
463-
different experimental conditions
473+
experiment_list: list of Estimator class objects containing the Pyomo model
474+
for different experimental conditions
464475
method: string ``method`` object specified by the user,
465-
e.g., 'finite_difference'
476+
e.g., 'finite_difference'
466477
theta_vals: dictionary containing the estimates of the unknown parameters
467478
step: float used for relative perturbation of the parameters,
468-
e.g., step=0.02 is a 2% perturbation
479+
e.g., step=0.02 is a 2% perturbation
469480
solver: string ``solver`` object specified by the user, e.g., 'ipopt'
470481
tee: boolean solver option to be passed for verbose output
482+
logging_level: logging level specified by the user, e.g., logging.INFO
471483
estimated_var: value of the estimated variance of the measurement error
472-
in cases where the user does not supply the measurement
473-
error standard deviation
484+
in cases where the user does not supply the
485+
measurement error standard deviation
474486
475487
Returns:
476488
cov: covariance matrix of the estimated parameters
477489
"""
478-
# get the supported methods
479-
supported_methods = [e.value for e in CovarianceMethodLib]
480-
if method not in supported_methods:
481-
raise ValueError(
482-
f"Invalid method: '{method}'. " f"Choose from {supported_methods}."
483-
)
484-
485490
if method == CovarianceMethodLib.finite_difference.value:
486491
# store the FIM of all experiments
487492
FIM_all_exp = []
@@ -495,6 +500,7 @@ def compute_covariance_matrix(
495500
step=step,
496501
solver=solver,
497502
tee=tee,
503+
logging_level=logging_level,
498504
estimated_var=estimated_var,
499505
)
500506
)
@@ -506,7 +512,10 @@ def compute_covariance_matrix(
506512
cov = np.linalg.inv(FIM)
507513
except np.linalg.LinAlgError:
508514
cov = np.linalg.pinv(FIM)
509-
logger.info("The FIM is singular. Using pseudo-inverse instead.")
515+
if logging_level == logging.INFO:
516+
logger.info("The FIM is singular. Using pseudo-inverse instead.")
517+
else:
518+
print("The FIM is singular. Using pseudo-inverse instead.")
510519

511520
cov = pd.DataFrame(cov, index=theta_vals.keys(), columns=theta_vals.keys())
512521
elif method == CovarianceMethodLib.automatic_differentiation_kaug.value:
@@ -532,7 +541,10 @@ def compute_covariance_matrix(
532541
cov = np.linalg.inv(FIM)
533542
except np.linalg.LinAlgError:
534543
cov = np.linalg.pinv(FIM)
535-
logger.info("The FIM is singular. Using pseudo-inverse instead.")
544+
if logging_level == logging.INFO:
545+
logger.info("The FIM is singular. Using pseudo-inverse instead.")
546+
else:
547+
print("The FIM is singular. Using pseudo-inverse instead.")
536548

537549
cov = pd.DataFrame(cov, index=theta_vals.keys(), columns=theta_vals.keys())
538550

@@ -542,35 +554,37 @@ def compute_covariance_matrix(
542554
# compute the Fisher information matrix of the estimated parameters using
543555
# 'finite_difference'
544556
def _finite_difference_FIM(
545-
experiment, theta_vals, step, solver, tee, estimated_var=None
557+
experiment, theta_vals, step, solver, tee, logging_level, estimated_var=None
546558
):
547559
"""
548560
Computes the Fisher information matrix from 'finite_difference' Jacobian matrix
549561
and measurement errors standard deviation defined in the annotated Pyomo model
550562
551563
Arguments:
552-
experiment: Estimator class object that contains the model for a particular
553-
experimental condition
564+
experiment: Estimator class object that contains the Pyomo model
565+
for a particular experimental condition
554566
theta_vals: dictionary containing the estimates of the unknown parameters
555567
step: float used for relative perturbation of the parameters,
556-
e.g., step=0.02 is a 2% perturbation
568+
e.g., step=0.02 is a 2% perturbation
557569
solver: string ``solver`` object specified by the user, e.g., 'ipopt'
558570
tee: boolean solver option to be passed for verbose output
571+
logging_level: logging level specified by the user, e.g., logging.INFO
559572
estimated_var: value of the estimated variance of the measurement error in
560-
cases where the user does not supply the measurement error
561-
standard deviation
573+
cases where the user does not supply the
574+
measurement error standard deviation
562575
563576
Returns:
564577
FIM: Fisher information matrix about the parameters
565578
"""
566579
# compute the Jacobian matrix using finite difference
567-
J = _compute_jacobian(experiment, theta_vals, step, solver, tee)
580+
J = _compute_jacobian(experiment, theta_vals, step, solver, tee, logging_level)
568581

569582
# computing the condition number of the Jacobian matrix
570583
cond_number_jac = np.linalg.cond(J)
571-
572-
# set up logging
573-
logger.info(f"The condition number of the Jacobian matrix is {cond_number_jac}")
584+
if logging_level == logging.INFO:
585+
logger.info(
586+
f"The condition number of the Jacobian matrix " f"is {cond_number_jac}"
587+
)
574588

575589
# grab the model
576590
model = _get_labeled_model_helper(experiment)
@@ -623,14 +637,14 @@ def _kaug_FIM(experiment, theta_vals, solver, tee, estimated_var=None):
623637
Disclaimer - code adopted from the kaug function implemented in Pyomo.DoE
624638
625639
Arguments:
626-
experiment: Estimator class object that contains the model for a particular
627-
experimental condition
640+
experiment: Estimator class object that contains the Pyomo model
641+
for a particular experimental condition
628642
theta_vals: dictionary containing the estimates of the unknown parameters
629643
solver: string ``solver`` object specified by the user, e.g., 'ipopt'
630644
tee: boolean solver option to be passed for verbose output
631645
estimated_var: value of the estimated variance of the measurement error in
632-
cases where the user does not supply the measurement error
633-
standard deviation
646+
cases where the user does not supply the
647+
measurement error standard deviation
634648
635649
Returns:
636650
FIM: Fisher information matrix about the parameters
@@ -732,14 +746,16 @@ class Estimator(object):
732746
A list of experiment objects which creates one labeled model for
733747
each experiment
734748
obj_function: string or function (optional)
735-
Built in objective (currently only "SSE") or custom function used to
736-
formulate parameter estimation objective.
749+
Built-in objective ("SSE" or "SSE_weighted") or custom function
750+
used to formulate parameter estimation objective.
737751
If no function is specified, the model is used
738752
"as is" and should be defined with a "FirstStageCost" and
739753
"SecondStageCost" expression that are used to build an objective.
740754
Default is None.
741755
tee: bool, optional
742756
If True, print the solver output to the screen. Default is False.
757+
logging_level: logging level, optional
758+
e.g., logging.INFO. Default is logging.ERROR.
743759
diagnostic_mode: bool, optional
744760
If True, print diagnostics from the solver. Default is False.
745761
solver_options: dict, optional
@@ -758,6 +774,7 @@ def __init__(
758774
experiment_list,
759775
obj_function=None,
760776
tee=False,
777+
logging_level=logging.ERROR,
761778
diagnostic_mode=False,
762779
solver_options=None,
763780
):
@@ -770,7 +787,7 @@ def __init__(
770787
model = _get_labeled_model_helper(self.exp_list[0])
771788

772789
# check if the model has all the required suffixes
773-
_check_model_labels_helper(model)
790+
_check_model_labels_helper(model, logging_level)
774791

775792
# populate keyword argument options
776793
if isinstance(obj_function, str):
@@ -795,6 +812,7 @@ def __init__(
795812
self.obj_function = obj_function
796813

797814
self.tee = tee
815+
self.logging_level = logging_level
798816
self.diagnostic_mode = diagnostic_mode
799817
self.solver_options = solver_options
800818

@@ -1184,12 +1202,12 @@ def _cov_at_theta(self, method, solver, cov_n, step):
11841202
11851203
Argument:
11861204
method: string ``method`` object specified by the user,
1187-
e.g., 'finite_difference'
1205+
e.g., 'finite_difference'
11881206
solver: string ``solver`` object specified by the user, e.g., 'ipopt'
11891207
cov_n: integer, number of datapoints specified by the user which is used
1190-
in the objective function
1208+
in the objective function
11911209
step: float used for relative perturbation of the parameters,
1192-
e.g., step=0.02 is a 2% perturbation
1210+
e.g., step=0.02 is a 2% perturbation
11931211
11941212
Returns:
11951213
cov: pd.DataFrame, covariance matrix of the estimated parameters
@@ -1306,6 +1324,7 @@ def _cov_at_theta(self, method, solver, cov_n, step):
13061324
solver=solver,
13071325
step=step,
13081326
tee=self.tee,
1327+
logging_level=self.logging_level,
13091328
estimated_var=measurement_var,
13101329
)
13111330
elif all(item is not None for item in meas_error):
@@ -1324,6 +1343,7 @@ def _cov_at_theta(self, method, solver, cov_n, step):
13241343
solver=solver,
13251344
step=step,
13261345
tee=self.tee,
1346+
logging_level=self.logging_level,
13271347
)
13281348
else:
13291349
raise ValueError(
@@ -1356,6 +1376,7 @@ def _cov_at_theta(self, method, solver, cov_n, step):
13561376
step=step,
13571377
solver=solver,
13581378
tee=self.tee,
1379+
logging_level=self.logging_level,
13591380
)
13601381
else:
13611382
cov = self.inv_red_hes

0 commit comments

Comments
 (0)