diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ea9dfc00640..8804eface3c 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -443,8 +443,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 @@ -1559,7 +1559,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): diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index f29f04e1d15..854bec18591 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -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 @@ -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, ) @@ -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) @@ -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() diff --git a/pyomo/contrib/parmest/utils/model_utils.py b/pyomo/contrib/parmest/utils/model_utils.py index a604e912b86..e43bdc9316e 100644 --- a/pyomo/contrib/parmest/utils/model_utils.py +++ b/pyomo/contrib/parmest/utils/model_utils.py @@ -16,13 +16,14 @@ 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 @@ -30,8 +31,8 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False): ---------- 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 @@ -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 ): @@ -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: @@ -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 + + # 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) @@ -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( @@ -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)