Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parmest update of util convert_params_to_vars. #3339

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
7 changes: 4 additions & 3 deletions pyomo/contrib/parmest/parmest.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,8 @@ def TotalCost_rule(model):
)

# Convert theta Params to Vars, and unfix theta Vars
theta_names = [k.name for k, v in model.unknown_parameters.items()]
parmest_model = utils.convert_params_to_vars(model, theta_names, fix_vars=False)
theta_CUIDs = [v for k, v in model.unknown_parameters.items()]
blnicho marked this conversation as resolved.
Show resolved Hide resolved
parmest_model = utils.convert_params_to_vars(model, theta_CUIDs, fix_vars=False)

return parmest_model

Expand Down Expand Up @@ -1556,7 +1556,8 @@ def TotalCost_rule(model):
)

# Convert theta Params to Vars, and unfix theta Vars
model = utils.convert_params_to_vars(model, self.theta_names)
theta_CUIDs = [ComponentUID(theta_name) for theta_name in self.theta_names]
model = utils.convert_params_to_vars(model, theta_CUIDs)

# Update theta names list to use CUID string representation
for i, theta in enumerate(self.theta_names):
Expand Down
81 changes: 78 additions & 3 deletions pyomo/contrib/parmest/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from pyomo.common.dependencies import pandas as pd, pandas_available

from pyomo.core.base.var import IndexedVar
import pyomo.environ as pyo
import pyomo.common.unittest as unittest
import pyomo.contrib.parmest.parmest as parmest
Expand All @@ -29,6 +30,9 @@ class TestUtils(unittest.TestCase):
def test_convert_param_to_var(self):
# TODO: Check that this works for different structured models (indexed, blocks, etc)

# test params
#############

from pyomo.contrib.parmest.examples.reactor_design.reactor_design import (
ReactorDesignExperiment,
)
Expand All @@ -46,12 +50,12 @@ def test_convert_param_to_var(self):
exp = ReactorDesignExperiment(data, 0)
instance = exp.get_labeled_model()

theta_names = ['k1', 'k2', 'k3']
param_CUIDs = [v for k, v in instance.unknown_parameters.items()]
blnicho marked this conversation as resolved.
Show resolved Hide resolved
m_vars = parmest.utils.convert_params_to_vars(
instance, theta_names, fix_vars=True
instance, param_CUIDs, fix_vars=True
)

for v in theta_names:
for v in [str(CUID) for CUID in param_CUIDs]:
self.assertTrue(hasattr(m_vars, v))
c = m_vars.find_component(v)
self.assertIsInstance(c, pyo.Var)
Expand All @@ -60,6 +64,77 @@ def test_convert_param_to_var(self):
self.assertEqual(pyo.value(c), pyo.value(c_old))
self.assertTrue(c in m_vars.unknown_parameters)

# test indexed params
#####################

from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import (
RooneyBieglerExperiment,
)

self.data = pd.DataFrame(
data=[[1, 8.3], [2, 10.3], [3, 19.0], [4, 16.0], [5, 15.6], [7, 19.8]],
columns=["hour", "y"],
)

def rooney_biegler_indexed_params(data):
model = pyo.ConcreteModel()

model.param_names = pyo.Set(initialize=["asymptote", "rate_constant"])
model.theta = pyo.Param(
model.param_names,
initialize={"asymptote": 15, "rate_constant": 0.5},
mutable=True,
)

model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True)
model.y = pyo.Param(within=pyo.PositiveReals, mutable=True)

def response_rule(m, h):
expr = m.theta["asymptote"] * (
1 - pyo.exp(-m.theta["rate_constant"] * h)
)
return expr

model.response_function = pyo.Expression(data.hour, rule=response_rule)

return model

class RooneyBieglerExperimentIndexedParams(RooneyBieglerExperiment):

def create_model(self):
data_df = self.data.to_frame().transpose()
self.model = rooney_biegler_indexed_params(data_df)

def label_model(self):

m = self.model

m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.experiment_outputs.update(
[(m.hour, self.data["hour"]), (m.y, self.data["y"])]
)

m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL)
m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta])

exp = RooneyBieglerExperimentIndexedParams(self.data.loc[0, :])
instance = exp.get_labeled_model()

param_CUIDs = [v for k, v in instance.unknown_parameters.items()]
m_vars = parmest.utils.convert_params_to_vars(
instance, param_CUIDs, fix_vars=True
)

for v in [str(CUID) for CUID in param_CUIDs]:
self.assertTrue(hasattr(m_vars, v))
c = m_vars.find_component(v)
self.assertIsInstance(c, IndexedVar)
for _, iv in c.items():
self.assertTrue(iv.fixed)
iv_old = instance.find_component(iv)
self.assertEqual(pyo.value(iv), pyo.value(iv_old))
self.assertTrue(c in m_vars.unknown_parameters)


if __name__ == "__main__":
unittest.main()
73 changes: 42 additions & 31 deletions pyomo/contrib/parmest/utils/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@
logger = logging.getLogger(__name__)


def convert_params_to_vars(model, param_names=None, fix_vars=False):
def convert_params_to_vars(model, param_CUIDs=None, fix_vars=False):
"""
Convert select Params to Vars

Parameters
----------
model : Pyomo concrete model
Original model
param_names : list of strings
List of parameter names to convert, if None then all Params are converted
param_CUIDs : list of strings
List of parameter CUIDs to convert, if None then all Params are converted
fix_vars : bool
Fix the new variables, default is False

Expand All @@ -43,21 +43,31 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):

model = model.clone()

if param_names is None:
param_names = [param.name for param in model.component_data_objects(pyo.Param)]
if param_CUIDs is None:
param_CUIDs = [
ComponentUID(param) for param in model.component_data_objects(pyo.Param)
]

indexed_param_names = []
# if param_CUIDs is None:
# param_CUIDs = [
# ComponentUID(param.name)
# for param in model.component_data_objects(pyo.Param)
# ]
mrmundt marked this conversation as resolved.
Show resolved Hide resolved

# keep a list of the parameter CUIDs in the case of indexing
indexed_param_CUIDs = []

# Convert Params to Vars, unfix Vars, and create a substitution map
substitution_map = {}
comp_map = ComponentMap()
for i, param_name in enumerate(param_names):
for param_CUID in param_CUIDs:

# Leverage the parser in ComponentUID to locate the component.
theta_cuid = ComponentUID(param_name)
theta_object = theta_cuid.find_component_on(model)
theta_object = param_CUID.find_component_on(model)

# Param
if theta_object.is_parameter_type():

# Delete Param, add Var
vals = theta_object.extract_values()
model.del_component(theta_object)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this will work on hierarchical models. I think we need to add more tests for edge cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I fixed this and added a test.

Expand All @@ -69,20 +79,24 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):
substitution_map[id(theta_object)] = theta_var_object
comp_map[theta_object] = theta_var_object

# Indexed Param
# Indexed Param -- Delete Param, add Var
elif isinstance(theta_object, IndexedParam):
# Delete Param, add Var
# Before deleting the Param, create a list of the indexed param names

# save Param values
vals = theta_object.extract_values()
param_theta_objects = []
for theta_obj in theta_object:
indexed_param_name = theta_object.name + '[' + str(theta_obj) + ']'
theta_cuid = ComponentUID(indexed_param_name)
param_theta_objects.append(theta_cuid.find_component_on(model))
indexed_param_names.append(indexed_param_name)

# get indexed Params
param_theta_objects = [theta_obj for _, theta_obj in theta_object.items()]
blnicho marked this conversation as resolved.
Show resolved Hide resolved

# get indexed Param names
blnicho marked this conversation as resolved.
Show resolved Hide resolved
indexed_param_CUIDs += [
ComponentUID(theta_obj) for _, theta_obj in theta_object.items()
]
blnicho marked this conversation as resolved.
Show resolved Hide resolved

# delete Param
model.del_component(theta_object)

# add Var w/ previous Param values
index_name = theta_object.index_set().name
index_cuid = ComponentUID(index_name)
index_object = index_cuid.find_component_on(model)
Expand All @@ -94,13 +108,9 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):
theta_var_cuid = ComponentUID(theta_object.name)
theta_var_object = theta_var_cuid.find_component_on(model)
comp_map[theta_object] = theta_var_object
var_theta_objects = []
for theta_obj in theta_var_object:
theta_cuid = ComponentUID(
theta_var_object.name + '[' + str(theta_obj) + ']'
)
var_theta_objects.append(theta_cuid.find_component_on(model))

var_theta_objects = [
var_theta_obj for _, var_theta_obj in theta_var_object.items()
]
blnicho marked this conversation as resolved.
Show resolved Hide resolved
for param_theta_obj, var_theta_obj in zip(
param_theta_objects, var_theta_objects
):
Expand All @@ -124,14 +134,14 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):
if len(substitution_map) == 0:
return model

# Update the list of param_names if the parameters were indexed
if len(indexed_param_names) > 0:
param_names = indexed_param_names
# Update the list of param_CUIDs if the parameters were indexed
if len(indexed_param_CUIDs) > 0:
param_CUIDs = indexed_param_CUIDs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we replace param_CUIDs here instead of extending it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have to replace it because it might not have the indexed variables, only the main variable. For example "theta" (param_CUIDs) vs. "theta[asymptote]" and "theta[rate_constant]" (indexed_param_CUIDs).


# Convert Params to Vars in Expressions
for expr in model.component_data_objects(pyo.Expression):
if expr.active and any(
blnicho marked this conversation as resolved.
Show resolved Hide resolved
v.name in param_names for v in identify_mutable_parameters(expr)
ComponentUID(v) in param_CUIDs for v in identify_mutable_parameters(expr)
blnicho marked this conversation as resolved.
Show resolved Hide resolved
):
new_expr = replace_expressions(expr=expr, substitution_map=substitution_map)
model.del_component(expr)
Expand All @@ -143,7 +153,8 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):
model.constraints = pyo.ConstraintList()
for c in model.component_data_objects(pyo.Constraint):
if c.active and any(
v.name in param_names for v in identify_mutable_parameters(c.expr)
ComponentUID(v) in param_CUIDs
for v in identify_mutable_parameters(c.expr)
):
if c.equality:
model.constraints.add(
Expand Down Expand Up @@ -181,7 +192,7 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):
# Convert Params to Vars in Objective expressions
for obj in model.component_data_objects(pyo.Objective):
if obj.active and any(
v.name in param_names for v in identify_mutable_parameters(obj)
ComponentUID(v) in param_CUIDs for v in identify_mutable_parameters(obj)
):
expr = replace_expressions(expr=obj.expr, substitution_map=substitution_map)
model.del_component(obj)
Expand Down
Loading