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 = list(model.unknown_parameters.values())
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
98 changes: 95 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 = list(instance.unknown_parameters.values())
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,94 @@ 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 = list(instance.unknown_parameters.values())
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)

# test hierarchical model
#########################

m = pyo.ConcreteModel()
m.p1 = pyo.Param(initialize=1, mutable=True)
m.b = pyo.Block()
m.b.p2 = pyo.Param(initialize=2, mutable=True)

param_CUIDs = [pyo.ComponentUID(m.p1), pyo.ComponentUID(m.b.p2)]
m_vars = parmest.utils.convert_params_to_vars(m, param_CUIDs)

for v in [str(CUID) for CUID in param_CUIDs]:
c = m_vars.find_component(v)
self.assertIsInstance(c, pyo.Var)
c_old = m.find_component(v)
self.assertEqual(pyo.value(c), pyo.value(c_old))


if __name__ == "__main__":
unittest.main()
94 changes: 52 additions & 42 deletions pyomo/contrib/parmest/utils/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@
from pyomo.core.base.var import IndexedVar
from pyomo.core.base.param import IndexedParam
from pyomo.common.collections import ComponentMap
from pyomo.core.base import Model

from pyomo.environ import ComponentUID

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,64 +44,68 @@ 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

# change from Param to Var
vals = theta_object.extract_values()
model.del_component(theta_object)
model.add_component(theta_object.name, pyo.Var(initialize=vals[None]))
parent = theta_object.parent_block()
parent.del_component(theta_object)
parent.add_component(theta_object.name, pyo.Var(initialize=vals[None]))

# Update substitution map
theta_var_cuid = ComponentUID(theta_object.name)
theta_var_object = theta_var_cuid.find_component_on(model)
theta_var_cuid = ComponentUID(theta_object.local_name)
theta_var_object = theta_var_cuid.find_component_on(parent)
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)

model.del_component(theta_object)
# get indexed Params
param_theta_objects = list(theta_object.values())

# get indexed Param CUIDs
indexed_param_CUIDs.extend(
ComponentUID(theta_obj) for theta_obj in theta_object.values()
)

# delete Param
parent = theta_object.parent_block()
parent.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)
model.add_component(
index_object = index_cuid.find_component_on(parent)
parent.add_component(
theta_object.name, pyo.Var(index_object, initialize=vals)
)

# Update substitution map (map each indexed param to indexed var)
theta_var_cuid = ComponentUID(theta_object.name)
theta_var_object = theta_var_cuid.find_component_on(model)
theta_var_object = theta_var_cuid.find_component_on(parent)
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 = list(theta_var_object.values())
for param_theta_obj, var_theta_obj in zip(
param_theta_objects, var_theta_objects
):
Expand All @@ -112,7 +117,7 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False):
theta_var_object = theta_object

else:
logger.warning("%s is not a Param or Var on the model", (param_name))
logger.warning("%s is not a Param or Var on the model", (theta_object))
return model

if fix_vars:
Expand All @@ -124,14 +129,18 @@ 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 to a set for look up efficiency
param_CUIDs_set = set(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)
if any(
ComponentUID(v) in param_CUIDs_set
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 +152,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_set
for v in identify_mutable_parameters(c.expr)
):
if c.equality:
model.constraints.add(
Expand Down Expand Up @@ -181,7 +191,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_set for v in identify_mutable_parameters(obj)
):
expr = replace_expressions(expr=obj.expr, substitution_map=substitution_map)
model.del_component(obj)
Expand Down
Loading