def test_clear_dir_k_aug(self): m = simple_model_1() sens = SensitivityInterface(m, clone_model=False) k_aug = K_augInterface() opt_ipopt.solve(m, tee=True) m.ptb = pyo.Param(mutable=True, initialize=1.5) cwd = os.getcwd() dir_contents = os.listdir(cwd) sens_param = [m.p] sens.setup_sensitivity(sens_param) k_aug.k_aug(m, tee=True) # We are back in our working directory self.assertEqual(cwd, os.getcwd()) # The contents of this directory have not changed self.assertEqual(dir_contents, os.listdir(cwd)) # In particular, the following files do not exist self.assertFalse(os.path.exists("dsdp_in_.in")) self.assertFalse(os.path.exists("conorder.txt")) self.assertFalse(os.path.exists("timings_k_aug_dsdp.txt")) # But they have been transferred to our k_aug interface's data # dict as strings. self.assertIsInstance(k_aug.data["dsdp_in_.in"], str) self.assertIsInstance(k_aug.data["conorder.txt"], str) self.assertIsInstance(k_aug.data["timings_k_aug_dsdp.txt"], str)
def test_add_data_block(self): model = param_example.create_model() sens = SensitivityInterface(model, clone_model=False) block = sens._add_data_block() self.assertIs(sens.block.parent_block(), sens.model_instance) self.assertIs(sens.block.ctype, Block) self.assertEqual(sens.block.local_name, sens.get_default_block_name()) with self.assertRaises(RuntimeError) as ex: sens._add_data_block() # We just tried adding the same block twice. self.assertIn("Cannot add component", str(ex.exception)) # Try re-adding the same block, but this time we are prepared # for it to already exist. new_block = sens._add_data_block(existing_block=block) self.assertIsNot(block, new_block) new_block._has_replaced_expressions = True with self.assertRaises(RuntimeError) as ex: sens._add_data_block(existing_block=new_block) # Cannot remove and re-add sensitivity block if expressions # were replaced. self.assertIn("Re-using sensitivity interface", str(ex.exception))
def test_get_names(self): block_name = SensitivityInterface.get_default_block_name() self.assertEqual(block_name, "_SENSITIVITY_TOOLBOX_DATA") var_name = 'var' sens_var_name = SensitivityInterface.get_default_var_name(var_name) self.assertEqual(sens_var_name, var_name) param_name = 'param' sens_param_name = SensitivityInterface.get_default_param_name( param_name) self.assertEqual(sens_param_name, param_name)
def test_param_const(self): model = make_indexed_model() param_list = [model.eta[1], model.eta[2]] sens = SensitivityInterface(model, clone_model=False) sens.setup_sensitivity(param_list) block = sens.block param_const = block.paramConst self.assertEqual(len(param_list), len(block.paramConst)) param_var_map = ComponentMap( (param, var) for var, param, _, _ in block._sens_data_list) var_list = [param_var_map[param] for param in param_list] # Here we rely on the order of paramConst for param, var, con in zip(param_list, var_list, param_const.values()): self.assertEqual(con.body.to_string(), (var - param).to_string())
def test_param_const_indexed(self): model = make_indexed_model() param_list = [model.eta] sens = SensitivityInterface(model, clone_model=False) sens.setup_sensitivity(param_list) block = sens.block param_const = block.paramConst param_var_map = ComponentMap( (param, var) for var, param, _, _ in block._sens_data_list) for con in param_const.values(): var_list = list(identify_variables(con.expr)) mut_param_list = list(identify_mutable_parameters(con.expr)) self.assertEqual(len(var_list), 1) self.assertEqual(len(mut_param_list), 1) self.assertIs(var_list[0], param_var_map[mut_param_list[0]]) self.assertEqual(con.body.to_string(), (var_list[0] - mut_param_list[0]).to_string())
def test_suffixes_setup(self): model = make_indexed_model() param_list = [model.eta[2], model.eta[1]] sens = SensitivityInterface(model, clone_model=False) sens.setup_sensitivity(param_list) for i, (var, _, _, _) in enumerate(sens.block._sens_data_list): con = sens.block.paramConst[i + 1] self.assertEqual(model.sens_state_0[var], i + 1) self.assertEqual(model.sens_state_1[var], i + 1) self.assertEqual(model.sens_init_constr[con], i + 1) self.assertEqual(model.dcdp[con], i + 1) self.assertIs(type(model.sens_sol_state_1_z_L), Suffix) self.assertIs(type(model.sens_sol_state_1_z_U), Suffix) self.assertIs(type(model.ipopt_zL_out), Suffix) self.assertIs(type(model.ipopt_zU_out), Suffix) self.assertIs(type(model.ipopt_zL_in), Suffix) self.assertIs(type(model.ipopt_zU_in), Suffix) self.assertIs(type(model.dual), Suffix) self.assertIs(type(model.DeltaP), Suffix)
def test_perturb_parameters_indexed(self): delta = 1.0 model = make_indexed_model() param_list = [model.eta] model.perturbed_eta = Param( [1, 2], mutable=True, initialize={i: p.value + delta for i, p in model.eta.items()}) ptb_list = [model.perturbed_eta] sens = SensitivityInterface(model, clone_model=False) sens.setup_sensitivity(param_list) sens.perturb_parameters(ptb_list) instance = sens.model_instance block = sens.block param_var_map = ComponentMap( (param, var) for var, param, _, _ in sens.block._sens_data_list) param_con_map = ComponentMap( (param, block.paramConst[i + 1]) for i, (_, param, _, _) in enumerate(sens.block._sens_data_list)) for param, ptb in zip(param_list, ptb_list): for idx in param: obj = param[idx] ptb_data = ptb[idx] var = param_var_map[obj] con = param_con_map[obj] self.assertEqual(instance.sens_state_value_1[var], ptb_data.value) self.assertEqual(instance.DeltaP[con], -delta)
def test_add_sensitivity_data(self): model = make_indexed_model() sens = SensitivityInterface(model, clone_model=False) sens._add_data_block() param_list = [model.x, model.eta] with self.assertRaises(ValueError) as exc: sens._add_sensitivity_data(param_list) self.assertIn("variables must be fixed", str(exc.exception)) sens.model_instance.x.fix() # NOTE: sending the same component twice probably shouldn't # be supported. It is convenient for testing, however. param_list = [model.x, model.x[1], model.eta, model.eta[1]] sens._add_sensitivity_data(param_list) block_param_list = list(sens.block.component_data_objects(Param)) block_var_list = list(sens.block.component_data_objects(Var)) self.assertEqual(len(block_param_list), 4) self.assertEqual(len(block_var_list), 3) self.assertEqual(len(sens.block._sens_data_list), 7) pred_sens_data_list = [ (model.x[1], Param, 0, 1), (model.x[2], Param, 0, 2), (model.x[3], Param, 0, 3), (model.x[1], Param, 1, _NotAnIndex), (Var, model.eta[1], 2, 1), (Var, model.eta[2], 2, 2), (Var, model.eta[1], 3, _NotAnIndex), ] for data, pred in zip(sens.block._sens_data_list, pred_sens_data_list): if isinstance(pred[0], ComponentData): self.assertIs(data[0], pred[0]) self.assertIs(data[1].ctype, pred[1]) name = data[0].parent_component().local_name self.assertTrue( data[1].parent_component().local_name.startswith(name)) else: self.assertIs(data[0].ctype, pred[0]) self.assertIs(data[1], pred[1]) name = data[1].parent_component().local_name self.assertTrue( data[0].parent_component().local_name.startswith(name)) self.assertEqual(data[2], pred[2]) self.assertEqual(data[3], pred[3])
def test_clear_dir_dot_sens(self): m = simple_model_1() sens = SensitivityInterface(m, clone_model=False) k_aug = K_augInterface() opt_ipopt.solve(m, tee=True) m.ptb = pyo.Param(mutable=True, initialize=1.5) cwd = os.getcwd() dir_contents = os.listdir(cwd) sens_param = [m.p] sens.setup_sensitivity(sens_param) # Call k_aug k_aug.k_aug(m, tee=True) self.assertIsInstance(k_aug.data["dsdp_in_.in"], str) sens.perturb_parameters([m.ptb]) # Call dot_sens. In the process, we re-write dsdp_in_.in k_aug.dot_sens(m, tee=True) # Make sure we get the values we expect. This problem is easy enough # to solve by hand: # x = [1, 1, -2] = [v1, v2, dual] # Sensitivity system: # | 2 -2 1 | # |-2 2 1 | dx/dp = -[dL/dxdp, dc/dp]^T = -[0, 0, -1]^T # | 1 1 0 | # => dx/dp = [0.5, 0.5, 0]^T # Here, dp = [0.5] # => dx = [0.25, 0.25, 0]^T # => x_new = [1.25, 1.25, -2] self.assertAlmostEqual(m.v1.value, 1.25, 7) self.assertAlmostEqual(m.v2.value, 1.25, 7) # We are back in our working directory self.assertEqual(cwd, os.getcwd()) # The contents of this directory have not changed self.assertEqual(dir_contents, os.listdir(cwd)) self.assertFalse(os.path.exists("dsdp_in_.in")) self.assertFalse(os.path.exists("delta_p.out")) self.assertFalse(os.path.exists("dot_out.out")) self.assertFalse(os.path.exists("timings_dot_driver_dsdp.txt")) # And we have saved strings of the file contents. self.assertIsInstance(k_aug.data["dsdp_in_.in"], str) self.assertIsInstance(k_aug.data["delta_p.out"], str) self.assertIsInstance(k_aug.data["dot_out.out"], str) self.assertIsInstance(k_aug.data["timings_dot_driver_dsdp.txt"], str)
def test_process_param_list(self): model = make_indexed_model() sens = SensitivityInterface(model, clone_model=False) param_list = [model.x[1], model.eta] new_param_list = sens._process_param_list(param_list) self.assertIs(param_list, new_param_list) sens = SensitivityInterface(model, clone_model=True) new_param_list = sens._process_param_list(param_list) # The new param list contains the "same" variables in the # cloned model. self.assertIs(new_param_list[0], sens.model_instance.x[1]) self.assertIs(new_param_list[1], sens.model_instance.eta)
def test_expression_replacement_no_replacement(self): model = make_indexed_model() sens = SensitivityInterface(model, clone_model=False) sens._add_data_block() instance = sens.model_instance block = sens.block instance.x.fix() param_list = [instance.x[1], instance.x[2], instance.x[3]] sens._add_sensitivity_data(param_list) self.assertEqual(len(block.constList), 0) variable_sub_map = {} sens._replace_parameters_in_constraints(variable_sub_map) self.assertEqual(len(block.constList), 2) # Rely on order of constraints here... Fine as long as # component_data_objects iteration is deterministic pred_const_list = [instance.const[1], instance.const[2]] for orig, replaced in zip(pred_const_list, block.constList.values()): self.assertEqual(orig.expr.to_string(), replaced.expr.to_string()) self.assertFalse(orig.active) self.assertTrue(replaced.active)
def test_perturb_indexed_parameters_with_scalar(self): model = make_indexed_model() param_list = [model.eta] ptb_list = [10.0] sens = SensitivityInterface(model, clone_model=False) sens.setup_sensitivity(param_list) sens.perturb_parameters(ptb_list) instance = sens.model_instance block = sens.block param_var_map = ComponentMap( (param, var) for var, param, _, _ in sens.block._sens_data_list) param_con_map = ComponentMap( (param, block.paramConst[i + 1]) for i, (_, param, _, _) in enumerate(sens.block._sens_data_list)) for param, ptb in zip(param_list, ptb_list): for idx in param: obj = param[idx] var = param_var_map[obj] con = param_con_map[obj] self.assertEqual(instance.sens_state_value_1[var], ptb) self.assertEqual(instance.DeltaP[con], obj.value - ptb)
def test_expression_replacement_ranged_inequality(self): model = make_model_with_ranged_inequalities() sens = SensitivityInterface(model, clone_model=False) sens._add_data_block() instance = sens.model_instance block = sens.block instance.x.fix() param_list = [instance.eta[1], instance.eta[2]] sens._add_sensitivity_data(param_list) orig_components = ( list(instance.component_data_objects(Constraint, active=True)) + list(instance.component_data_objects(Objective, active=True))) orig_expr = [con.expr for con in orig_components] # These will be modified to account for expected replacements expected_variables = ComponentMap( (con, ComponentSet(identify_variables(con.expr))) for con in orig_components) expected_parameters = ComponentMap( (con, ComponentSet(identify_mutable_parameters(con.expr))) for con in orig_components) # As constructed by the `setup_sensitivity` method: variable_sub_map = dict( (id(param), var) for var, param, list_idx, _ in block._sens_data_list if param_list[list_idx].ctype is Param) # Sanity check self.assertEqual(len(variable_sub_map), 2) # Map each param to the var that should replace it param_var_map = ComponentMap( (param, var) for var, param, _, _ in block._sens_data_list) # Remove parameters we expect to replace and add vars # we expect to replace with. for con in orig_components: for param in param_var_map: if param in expected_parameters[con]: expected_variables[con].add(param_var_map[param]) expected_parameters[con].remove(param) # We check that the new components (Constraints and Objectives) contain # the expected parameters and variables. replaced = sens._replace_parameters_in_constraints(variable_sub_map) # With ranged inequalities, we end up with more constraints than we # started with: self.assertEqual(len(block.constList), 3) for con in block.constList.values(): self.assertTrue(con.active) param_set = ComponentSet(identify_mutable_parameters(con.expr)) var_set = ComponentSet(identify_variables(con.expr)) orig_con = replaced[con] self.assertIsNot(orig_con, con) # Note that for ranged inequalities, it is not valid to check # that the two sets are equal as a mutable parameter could be # contained in only one "sub-inequality" self.assertIsSubset(param_set, expected_parameters[orig_con]) self.assertEqual(var_set, expected_variables[orig_con]) self.assertIs(block.cost.ctype, Objective) obj = block.cost param_set = ComponentSet(identify_mutable_parameters(obj.expr)) var_set = ComponentSet(identify_variables(obj.expr)) orig_obj = replaced[obj] self.assertIsNot(orig_obj, obj) self.assertEqual(param_set, expected_parameters[orig_obj]) self.assertEqual(var_set, expected_variables[orig_obj]) # Original components were deactivated but otherwise not altered for con, expr in zip(orig_components, orig_expr): self.assertFalse(con.active) #self.assertIs(con.expr, expr) # ^Why does this fail? self.assertEqual(con.expr.to_string(), expr.to_string())
def test_constructor_no_clone(self): model = param_example.create_model() sens = SensitivityInterface(model, clone_model=False) self.assertIs(sens._original_model, model) self.assertIs(sens.model_instance, model)