Skip to content

Commit 74e2fab

Browse files
authored
Merge pull request #2 from kaklise/parmest-redesign
Parmest updates, minor code updates and docs for new API
2 parents 04f68dc + c5bdb0b commit 74e2fab

File tree

8 files changed

+126
-164
lines changed

8 files changed

+126
-164
lines changed

Diff for: doc/OnlineDocs/contributed_packages/parmest/datarec.rst

+13-22
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,27 @@
33
Data Reconciliation
44
====================
55

6-
The method :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est`
7-
can optionally return model values. This feature can be used to return
8-
reconciled data using a user specified objective. In this case, the list
9-
of variable names the user wants to estimate (theta_names) is set to an
10-
empty list and the objective function is defined to minimize
6+
The optional argument ``return_values`` in :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est`
7+
can be used for data reconciliation or to return model values based on the specified objective.
8+
9+
For data reconciliation, the ``m.unknown_parameters`` is empty
10+
and the objective function is defined to minimize
1111
measurement to model error. Note that the model used for data
1212
reconciliation may differ from the model used for parameter estimation.
1313

14-
The following example illustrates the use of parmest for data
15-
reconciliation. The functions
14+
The functions
1615
:class:`~pyomo.contrib.parmest.graphics.grouped_boxplot` or
1716
:class:`~pyomo.contrib.parmest.graphics.grouped_violinplot` can be used
1817
to visually compare the original and reconciled data.
1918

20-
Here's a stylized code snippet showing how box plots might be created:
21-
22-
.. doctest::
23-
:skipif: True
24-
25-
>>> import pyomo.contrib.parmest.parmest as parmest
26-
>>> pest = parmest.Estimator(model_function, data, [], objective_function)
27-
>>> obj, theta, data_rec = pest.theta_est(return_values=['A', 'B'])
28-
>>> parmest.graphics.grouped_boxplot(data, data_rec)
19+
The following example from the reactor design subdirectory returns reconciled values for experiment outputs
20+
(`ca`, `cb`, `cc`, and `cd`) and then uses those values in
21+
parameter estimation (`k1`, `k2`, and `k3`).
2922

30-
Returned Values
31-
^^^^^^^^^^^^^^^
32-
33-
Here's a full program that can be run to see returned values (in this case it
34-
is the response function that is defined in the model file):
23+
.. literalinclude:: ../../../../pyomo/contrib/parmest/examples/reactor_design/datarec_example.py
24+
:language: python
25+
26+
The following example returns model values from a Pyomo Expression.
3527

3628
.. doctest::
3729
:skipif: not ipopt_available or not parmest_available
@@ -60,4 +52,3 @@ is the response function that is defined in the model file):
6052
>>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=None)
6153
>>> obj, theta, var_values = pest.theta_est(return_values=['response_function'])
6254
>>> #print(var_values)
63-

Diff for: doc/OnlineDocs/contributed_packages/parmest/driver.rst

+45-60
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Parameter Estimation
44
==================================
55

66
Parameter Estimation using parmest requires a Pyomo model, experimental
7-
data which defines multiple scenarios, and a list of parameter names
7+
data which defines multiple scenarios, and parameters
88
(thetas) to estimate. parmest uses Pyomo [PyomoBookII]_ and (optionally)
99
mpi-sppy [mpisppy]_ to solve a
1010
two-stage stochastic programming problem, where the experimental data is
@@ -36,8 +36,8 @@ which includes the following methods:
3636
~pyomo.contrib.parmest.parmest.Estimator.likelihood_ratio_test
3737
~pyomo.contrib.parmest.parmest.Estimator.leaveNout_bootstrap_test
3838

39-
Additional functions are available in parmest to group data, plot
40-
results, and fit distributions to theta values.
39+
Additional functions are available in parmest to plot
40+
results and fit distributions to theta values.
4141

4242
.. autosummary::
4343
:nosignatures:
@@ -92,65 +92,43 @@ Optionally, solver options can be supplied, e.g.,
9292

9393
>>> solver_options = {"max_iter": 6000}
9494
>>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=solver_options)
95-
96-
97-
98-
Model function
99-
--------------
100-
101-
The first argument is a function which uses data for a single scenario
102-
to return a populated and initialized Pyomo model for that scenario.
103-
104-
Parameters that the user would like to estimate can be defined as
105-
**mutable parameters (Pyomo `Param`) or variables (Pyomo `Var`)**.
106-
Within parmest, any parameters that are to be estimated are converted to unfixed variables.
107-
Variables that are to be estimated are also unfixed.
108-
109-
The model does not have to be specifically written as a
110-
two-stage stochastic programming problem for parmest.
111-
That is, parmest can modify the
112-
objective, see :ref:`ObjFunction` below.
113-
114-
Data
115-
----
116-
117-
The second argument is the data which will be used to populate the Pyomo
118-
model. Supported data formats include:
119-
120-
* **Pandas Dataframe** where each row is a separate scenario and column
121-
names refer to observed quantities. Pandas DataFrames are easily
122-
stored and read in from csv, excel, or databases, or created directly
123-
in Python.
124-
* **List of Pandas Dataframe** where each entry in the list is a separate scenario.
125-
Dataframes store observed quantities, referenced by index and column.
126-
* **List of dictionaries** where each entry in the list is a separate
127-
scenario and the keys (or nested keys) refer to observed quantities.
128-
Dictionaries are often preferred over DataFrames when using static and
129-
time series data. Dictionaries are easily stored and read in from
130-
json or yaml files, or created directly in Python.
131-
* **List of json file names** where each entry in the list contains a
132-
json file name for a separate scenario. This format is recommended
133-
when using large datasets in parallel computing.
134-
135-
The data must be compatible with the model function that returns a
136-
populated and initialized Pyomo model for a single scenario. Data can
137-
include multiple entries per variable (time series and/or duplicate
138-
sensors). This information can be included in custom objective
139-
functions, see :ref:`ObjFunction` below.
140-
141-
Theta names
142-
-----------
143-
144-
The third argument is a list of parameters or variable names that the user wants to
145-
estimate. The list contains strings with `Param` and/or `Var` names from the Pyomo
146-
model.
95+
96+
97+
List of experiment objects
98+
--------------------------
99+
100+
The first argument is a list of experiment objects which is used to
101+
create one labeled model for each expeirment.
102+
The template :class:`~pyomo.contrib.parmest.experiment.Experiment`
103+
can be used to generate a list of experiment objects.
104+
105+
A labeled Pyomo model ``m`` has the following additional suffixes (Pyomo `Suffix`):
106+
107+
* ``m.experiment_outputs`` which defines experiment output (Pyomo `Param`, `Var`, or `Expression`)
108+
and their associated data values (float, int).
109+
* ``m.unknown_parameters`` which defines the mutable parameters or variables (Pyomo `Param` or `Var`)
110+
to estimate along with their component unique identifier (Pyomo `ComponentUID`).
111+
Within parmest, any parameters that are to be estimated are converted to unfixed variables.
112+
Variables that are to be estimated are also unfixed.
113+
114+
The experiment class has one required method:
115+
116+
* :class:`~pyomo.contrib.parmest.experiment.Experiment.get_labeled_model` which returns the labeled Pyomo model.
117+
Note that the model does not have to be specifically written as a
118+
two-stage stochastic programming problem for parmest.
119+
That is, parmest can modify the
120+
objective, see :ref:`ObjFunction` below.
121+
122+
Parmest comes with several :ref:`examplesection` that illustrates how to set up the list of experiment objects.
123+
The examples commonly include additional :class:`~pyomo.contrib.parmest.experiment.Experiment` class methods to
124+
create the model, finalize the model, and label the model. The user can customize methods to suit their needs.
147125

148126
.. _ObjFunction:
149127

150128
Objective function
151129
------------------
152130

153-
The fourth argument is an optional argument which defines the
131+
The second argument is an optional argument which defines the
154132
optimization objective function to use in parameter estimation.
155133

156134
If no objective function is specified, the Pyomo model is used "as is" and
@@ -161,20 +139,27 @@ stochastic programming problem.
161139
If the Pyomo model is not written as a two-stage stochastic programming problem in
162140
this format, and/or if the user wants to use an objective that is
163141
different than the original model, a custom objective function can be
164-
defined for parameter estimation. The objective function arguments
165-
include `model` and `data` and the objective function returns a Pyomo
142+
defined for parameter estimation. The objective function has a single argument,
143+
which is the model from a single experiment.
144+
The objective function returns a Pyomo
166145
expression which is used to define "SecondStageCost". The objective
167146
function can be used to customize data points and weights that are used
168147
in parameter estimation.
169148

149+
Parmest includes one built in objective function to compute the sum of squared errors ("SSE") between the
150+
``m.experiment_outputs`` model values and data values.
151+
170152
Suggested initialization procedure for parameter estimation problems
171153
--------------------------------------------------------------------
172154

173155
To check the quality of initial guess values provided for the fitted parameters, we suggest solving a
174156
square instance of the problem prior to solving the parameter estimation problem using the following steps:
175157

176-
1. Create :class:`~pyomo.contrib.parmest.parmest.Estimator` object. To initialize the parameter estimation solve from the square problem solution, set optional argument ``solver_options = {bound_push: 1e-8}``.
158+
1. Create :class:`~pyomo.contrib.parmest.parmest.Estimator` object. To initialize the parameter
159+
estimation solve from the square problem solution, set optional argument ``solver_options = {bound_push: 1e-8}``.
177160

178-
2. Call :class:`~pyomo.contrib.parmest.parmest.Estimator.objective_at_theta` with optional argument ``(initialize_parmest_model=True)``. Different initial guess values for the fitted parameters can be provided using optional argument `theta_values` (**Pandas Dataframe**)
161+
2. Call :class:`~pyomo.contrib.parmest.parmest.Estimator.objective_at_theta` with optional
162+
argument ``(initialize_parmest_model=True)``. Different initial guess values for the fitted
163+
parameters can be provided using optional argument `theta_values` (**Pandas Dataframe**)
179164

180165
3. Solve parameter estimation problem by calling :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est`

Diff for: doc/OnlineDocs/contributed_packages/parmest/examples.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Additional use cases include:
2020
* Parameter estimation using mpi4py, the example saves results to a file
2121
for later analysis/graphics (semibatch example)
2222

23-
The description below uses the reactor design example. The file
23+
The example below uses the reactor design example. The file
2424
**reactor_design.py** includes a function which returns an populated
2525
instance of the Pyomo model. Note that the model is defined to maximize
2626
`cb` and that `k1`, `k2`, and `k3` are fixed. The _main_ program is

Diff for: doc/OnlineDocs/contributed_packages/parmest/scencreate.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ scenarios to the screen, accessing them via the ``ScensItator`` a ``print``
1818
:language: python
1919

2020
.. note::
21-
This example may produce an error message your version of Ipopt is not based
21+
This example may produce an error message if your version of Ipopt is not based
2222
on a good linear solver.

Diff for: pyomo/contrib/parmest/examples/reactor_design/datarec_example.py

+9-20
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,18 @@
2020
np.random.seed(1234)
2121

2222

23-
def reactor_design_model_for_datarec():
24-
25-
# Unfix inlet concentration for data rec
26-
model = reactor_design_model()
27-
model.caf.fixed = False
28-
29-
return model
30-
31-
32-
class ReactorDesignExperimentPreDataRec(ReactorDesignExperiment):
23+
class ReactorDesignExperimentDataRec(ReactorDesignExperiment):
3324

3425
def __init__(self, data, data_std, experiment_number):
3526

3627
super().__init__(data, experiment_number)
3728
self.data_std = data_std
3829

3930
def create_model(self):
40-
self.model = m = reactor_design_model_for_datarec()
31+
32+
self.model = m = reactor_design_model()
33+
m.caf.fixed = False
34+
4135
return m
4236

4337
def label_model(self):
@@ -121,23 +115,18 @@ def main():
121115
# Create an experiment list
122116
exp_list = []
123117
for i in range(data.shape[0]):
124-
exp_list.append(ReactorDesignExperimentPreDataRec(data, data_std, i))
118+
exp_list.append(ReactorDesignExperimentDataRec(data, data_std, i))
125119

126120
# Define sum of squared error objective function for data rec
127-
def SSE(model):
121+
def SSE_with_std(model):
128122
expr = sum(
129123
((y - yhat) / model.experiment_outputs_std[y]) ** 2
130124
for y, yhat in model.experiment_outputs.items()
131125
)
132126
return expr
133127

134-
# View one model & SSE
135-
# exp0_model = exp_list[0].get_labeled_model()
136-
# print(exp0_model.pprint())
137-
# print(SSE(exp0_model))
138-
139128
### Data reconciliation
140-
pest = parmest.Estimator(exp_list, obj_function=SSE)
129+
pest = parmest.Estimator(exp_list, obj_function=SSE_with_std)
141130

142131
obj, theta, data_rec = pest.theta_est(return_values=["ca", "cb", "cc", "cd", "caf"])
143132
print(obj)
@@ -157,7 +146,7 @@ def SSE(model):
157146
for i in range(data_rec.shape[0]):
158147
exp_list.append(ReactorDesignExperimentPostDataRec(data_rec, data_std, i))
159148

160-
pest = parmest.Estimator(exp_list, obj_function=SSE)
149+
pest = parmest.Estimator(exp_list, obj_function=SSE_with_std)
161150
obj, theta = pest.theta_est()
162151
print(obj)
163152
print(theta)

Diff for: pyomo/contrib/parmest/experiment.py

+23-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
1-
# The experiment class is a template for making experiment lists
2-
# to pass to parmest. An experiment is a pyomo model "m" which has
3-
# additional suffixes:
4-
# m.experiment_outputs -- which variables are experiment outputs
5-
# m.unknown_parameters -- which variables are parameters to estimate
6-
# The experiment class has only one required method:
7-
# get_labeled_model()
8-
# which returns the labeled pyomo model.
1+
# ___________________________________________________________________________
2+
#
3+
# Pyomo: Python Optimization Modeling Objects
4+
# Copyright (c) 2008-2024
5+
# National Technology and Engineering Solutions of Sandia, LLC
6+
# Under the terms of Contract DE-NA0003525 with National Technology and
7+
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
8+
# rights in this software.
9+
# This software is distributed under the 3-clause BSD License.
10+
# ___________________________________________________________________________
911

1012

1113
class Experiment:
14+
"""
15+
The experiment class is a template for making experiment lists
16+
to pass to parmest.
17+
18+
An experiment is a Pyomo model "m" which is labeled
19+
with additional suffixes:
20+
* m.experiment_outputs which defines experiment outputs
21+
* m.unknown_parameters which defines parameters to estimate
22+
23+
The experiment class has one required method:
24+
* get_labeled_model() which returns the labeled Pyomo model
25+
"""
26+
1227
def __init__(self, model=None):
1328
self.model = model
1429

0 commit comments

Comments
 (0)