Skip to content

Commit

Permalink
Merge pull request #2108 from kaklise/parmest_indexing
Browse files Browse the repository at this point in the history
Parmest with indexed variables
  • Loading branch information
blnicho authored Sep 28, 2021
2 parents 6f79c07 + 173ebf0 commit f0bc6b5
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 72 deletions.
9 changes: 9 additions & 0 deletions pyomo/contrib/parmest/examples/reaction_kinetics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________
'''
Example from Y. Bard, "Nonlinear Parameter Estimation", (pg. 124)
This example shows:
1. How to define the unknown (to be regressed parameters) with an index
2. How to call parmest to only estimate some of the parameters (and fix the rest)
Code provided by Paul Akula.
'''
import pandas as pd
from pandas import DataFrame
from os import path

from pyomo.environ import (ConcreteModel, Param, Var, PositiveReals, Objective,
Constraint, RangeSet, Expression, minimize, exp, value)

#from idaes.core.util import get_default_solver
import pyomo.contrib.parmest.parmest as parmest

# =======================================================================
''' Data from Table 5.2 in Y. Bard, "Nonlinear Parameter Estimation", (pg. 124)
'''

data = [{'experiment': 1, 'x1': 0.1, 'x2': 100, 'y': 0.98},
{'experiment': 2, 'x1': 0.2, 'x2': 100, 'y': 0.983},
{'experiment': 3, 'x1': 0.3, 'x2': 100, 'y': 0.955},
{'experiment': 4, 'x1': 0.4, 'x2': 100, 'y': 0.979},
{'experiment': 5, 'x1': 0.5, 'x2': 100, 'y': 0.993},
{'experiment': 6, 'x1': 0.05, 'x2': 200, 'y': 0.626},
{'experiment': 7, 'x1': 0.1, 'x2': 200, 'y': 0.544},
{'experiment': 8, 'x1': 0.15, 'x2': 200, 'y': 0.455},
{'experiment': 9, 'x1': 0.2, 'x2': 200, 'y': 0.225},
{'experiment': 10, 'x1': 0.25, 'x2': 200, 'y': 0.167},
{'experiment': 11, 'x1': 0.02, 'x2': 300, 'y': 0.566},
{'experiment': 12, 'x1': 0.04, 'x2': 300, 'y': 0.317},
{'experiment': 13, 'x1': 0.06, 'x2': 300, 'y': 0.034},
{'experiment': 14, 'x1': 0.08, 'x2': 300, 'y': 0.016},
{'experiment': 15, 'x1': 0.1, 'x2': 300, 'y': 0.006}]

# =======================================================================

def simple_reaction_model(data):

# Create the concrete model
model = ConcreteModel()

model.x1 = Param(initialize=float(data['x1']))
model.x2 = Param(initialize=float(data['x2']))

# Rate constants
model.rxn = RangeSet(2)
initial_guess = {1: 750, 2: 1200}
model.k = Var(model.rxn, initialize=initial_guess, within=PositiveReals)

# reaction product
model.y = Expression(expr=exp(-model.k[1] *
model.x1 * exp(-model.k[2] / model.x2)))

# fix all of the regressed parameters
model.k.fix()


#===================================================================
# Stage-specific cost computations
def ComputeFirstStageCost_rule(model):
return 0
model.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule)

def AllMeasurements(m):
return (float(data['y']) - m.y) ** 2
model.SecondStageCost = Expression(rule=AllMeasurements)

def total_cost_rule(m):
return m.FirstStageCost + m.SecondStageCost
model.Total_Cost_Objective = Objective(rule=total_cost_rule,
sense=minimize)

return model

if __name__ == "__main__":

# =======================================================================
# Parameter estimation without covariance estimate
# Only estimate the parameter k[1]. The parameter k[2] will remain fixed
# at its initial value
theta_names = ['k[1]']
pest = parmest.Estimator(simple_reaction_model, data, theta_names)
obj, theta = pest.theta_est()
print(obj)
print(theta)
print()
#=======================================================================
# Estimate both k1 and k2 and compute the covariance matrix
theta_names = ['k']
pest = parmest.Estimator(simple_reaction_model, data, theta_names)
obj, theta, cov = pest.theta_est(calc_cov=True)
print(obj)
print(theta)
print(cov)
11 changes: 10 additions & 1 deletion pyomo/contrib/parmest/examples/reactor_design/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@

# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

2 changes: 1 addition & 1 deletion pyomo/contrib/parmest/examples/rooney_biegler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ___________________________________________________________________________
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/parmest/examples/semibatch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ___________________________________________________________________________
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
Expand Down
43 changes: 22 additions & 21 deletions pyomo/contrib/parmest/parmest.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ class Estimator(object):
model_function: function
Function that generates an instance of the Pyomo model using 'data'
as the input argument
data: pandas DataFrame, list of dictionaries, or list of json file names
data: pd.DataFrame, list of dictionaries, or list of json file names
Data that is used to build an instance of the Pyomo model and build
the objective function
theta_names: list of strings
Expand Down Expand Up @@ -337,7 +337,7 @@ def _create_parmest_model(self, data):
# If the component that was found is not a variable,
# this will generate an exception (and the warning
# in the 'except')
var_validate.fixed = False
var_validate.unfix()
# We want to standardize on the CUID string
# representation
self.theta_names[i] = repr(var_cuid)
Expand Down Expand Up @@ -502,6 +502,8 @@ def _Q_opt(self, ThetaVals=None, solver="ef_ipopt",
cov = 2 * sse / (n - l) * inv_red_hes
cov = pd.DataFrame(cov, index=thetavals.keys(), columns=thetavals.keys())

thetavals = pd.Series(thetavals)

if len(return_values) > 0:
var_values = []
for exp_i in self.ef_instance.component_objects(Block, descend_into=False):
Expand Down Expand Up @@ -646,7 +648,7 @@ def theta_est(self, solver="ef_ipopt", return_values=[], bootlist=None, calc_cov
Parameters
----------
solver: string, optional
"ef_ipopt" or "k_aug". Default is "ef_ipopt".
Currently only "ef_ipopt" is supported. Default is "ef_ipopt".
return_values: list, optional
List of Variable names used to return values from the model
bootlist: list, optional
Expand All @@ -658,12 +660,10 @@ def theta_est(self, solver="ef_ipopt", return_values=[], bootlist=None, calc_cov
-------
objectiveval: float
The objective function value
thetavals: dict
A dictionary of all values for theta
thetavals: pd.Series
Estimated values for theta
variable values: pd.DataFrame
Variable values for each variable name in return_values (only for solver='ef_ipopt')
Hessian: dict
A dictionary of dictionaries for the Hessian (only for solver='k_aug')
cov: pd.DataFrame
Covariance matrix of the fitted parameters (only for solver='ef_ipopt')
"""
Expand Down Expand Up @@ -696,7 +696,7 @@ def theta_est_bootstrap(self, bootstrap_samples, samplesize=None,
Returns
-------
bootstrap_theta: DataFrame
bootstrap_theta: pd.DataFrame
Theta values for each sample and (if return_samples = True)
the sample numbers used in each estimation
"""
Expand Down Expand Up @@ -758,7 +758,7 @@ def theta_est_leaveNout(self, lNo, lNo_samples=None, seed=None,
Returns
-------
lNo_theta: DataFrame
lNo_theta: pd.DataFrame
Theta values for each sample and (if return_samples = True)
the sample numbers left out of each estimation
"""
Expand Down Expand Up @@ -887,12 +887,12 @@ def objective_at_theta(self, theta_values):
Parameters
----------
theta_values: DataFrame, columns=theta_names
theta_values: pd.DataFrame, columns=theta_names
Values of theta used to compute the objective
Returns
-------
obj_at_theta: DataFrame
obj_at_theta: pd.DataFrame
Objective value for each theta (infeasible solutions are
omitted).
"""
Expand Down Expand Up @@ -927,7 +927,7 @@ def likelihood_ratio_test(self, obj_at_theta, obj_value, alphas,
Parameters
----------
obj_at_theta: DataFrame, columns = theta_names + 'obj'
obj_at_theta: pd.DataFrame, columns = theta_names + 'obj'
Objective values for each theta value (returned by
objective_at_theta)
obj_value: int or float
Expand All @@ -939,10 +939,10 @@ def likelihood_ratio_test(self, obj_at_theta, obj_value, alphas,
Returns
-------
LR: DataFrame
LR: pd.DataFrame
Objective values for each theta value along with True or False for
each alpha
thresholds: dictionary
thresholds: pd.Series
If return_threshold = True, the thresholds are also returned.
"""
assert isinstance(obj_at_theta, pd.DataFrame)
Expand All @@ -958,6 +958,8 @@ def likelihood_ratio_test(self, obj_at_theta, obj_value, alphas,
thresholds[a] = obj_value * ((chi2_val / (S - 2)) + 1)
LR[a] = LR['obj'] < thresholds[a]

thresholds = pd.Series(thresholds)

if return_thresholds:
return LR, thresholds
else:
Expand All @@ -972,7 +974,7 @@ def confidence_region_test(self, theta_values, distribution, alphas,
Parameters
----------
theta_values: DataFrame, columns = theta_names
theta_values: pd.DataFrame, columns = theta_names
Theta values used to generate a confidence region
(generally returned by theta_est_bootstrap)
distribution: string
Expand All @@ -982,25 +984,24 @@ def confidence_region_test(self, theta_values, distribution, alphas,
alphas: list
List of alpha values used to determine if theta values are inside
or outside the region.
test_theta_values: dictionary or DataFrame, keys/columns = theta_names, optional
test_theta_values: pd.Series or pd.DataFrame, keys/columns = theta_names, optional
Additional theta values that are compared to the confidence region
to determine if they are inside or outside.
Returns
-------
training_results: DataFrame
training_results: pd.DataFrame
Theta value used to generate the confidence region along with True
(inside) or False (outside) for each alpha
test_results: DataFrame
test_results: pd.DataFrame
If test_theta_values is not None, returns test theta value along
with True (inside) or False (outside) for each alpha
"""
assert isinstance(theta_values, pd.DataFrame)
assert distribution in ['Rect', 'MVN', 'KDE']
assert isinstance(alphas, list)
assert isinstance(test_theta_values, (type(None), dict, pd.DataFrame))
assert isinstance(test_theta_values, (type(None), dict, pd.Series, pd.DataFrame))

if isinstance(test_theta_values, dict):
if isinstance(test_theta_values, (dict, pd.Series)):
test_theta_values = pd.Series(test_theta_values).to_frame().transpose()

training_results = theta_values.copy()
Expand Down
Loading

0 comments on commit f0bc6b5

Please sign in to comment.