Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()]
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()]
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()
67 changes: 36 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):
Copy link
Member

Choose a reason for hiding this comment

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

TODO: make sure the param domain is also being transferred to the new var

"""
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,25 @@ 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 = []
# 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 +73,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()]

# get indexed Param names
indexed_param_CUIDs += [
ComponentUID(theta_obj) for _, theta_obj in theta_object.items()
]

# 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 +102,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()
]
for param_theta_obj, var_theta_obj in zip(
param_theta_objects, var_theta_objects
):
Expand All @@ -124,14 +128,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(
v.name in param_names for v in identify_mutable_parameters(expr)
ComponentUID(v) in param_CUIDs for v in identify_mutable_parameters(expr)
):
new_expr = replace_expressions(expr=expr, substitution_map=substitution_map)
model.del_component(expr)
Expand All @@ -143,7 +147,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 +186,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