def test_external_multipliers_from_residual_multipliers(self): model = Model2by2() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [0.5, 1.0, 1.5, 2.5, 4.1] lam_init_list = [-2.5, -0.5, 0.0, 1.0, 2.0] init_list = list( itertools.product(x0_init_list, x1_init_list, lam_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x0, x1, lam in init_list: x = [x0, x1] lam = [lam] external_model.set_input_values(x) lam_g = external_model.calculate_external_constraint_multipliers( lam) pred_lam_g = model.calculate_external_multipliers(lam, x) np.testing.assert_allclose(lam_g, pred_lam_g, rtol=1e-8)
def test_cyipopt_unavailable(self): try: # HACK: Make external_pyomo_model.py think that cyipopt is not # available. epm_module.cyipopt_available = False m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] inner_solver = pyo.SolverFactory("ipopt") msg = "Constructing an ExternalPyomoModel with CyIpopt unavailable" with self.assertRaisesRegex(RuntimeError, msg): ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, use_cyipopt=True, ) finally: # HACK: Reset the global flag epm_module.cyipopt_available = cyipopt_available
def make_model(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=1.0) m.y = pyo.Var(initialize=1.0) m.u = pyo.Var(initialize=1.0) m.v = pyo.Var(initialize=1.0) m.con_1 = pyo.Constraint(expr=m.x * m.y == m.u) m.con_2 = pyo.Constraint(expr=m.x**2 * m.y**3 == m.v) m.con_3 = pyo.Constraint( expr=self.con_3_body(m.x, m.y, m.u, m.v) == self.con_3_rhs()) m.con_4 = pyo.Constraint( expr=self.con_4_body(m.x, m.y, m.u, m.v) == self.con_4_rhs()) epm_model = pyo.ConcreteModel() epm_model.x = pyo.Reference(m.x) epm_model.y = pyo.Reference(m.y) epm_model.u = pyo.Reference(m.u) epm_model.v = pyo.Reference(m.v) epm_model.epm = ExternalPyomoModel( [m.u, m.v], [m.x, m.y], [m.con_3, m.con_4], [m.con_1, m.con_2], ) epm_model.obj = pyo.Objective(expr=m.x**2 + m.y**2 + m.u**2 + m.v**2) epm_model.egb = ExternalGreyBoxBlock() epm_model.egb.set_external_model(epm_model.epm, inputs=[m.u, m.v]) return epm_model
def test_evaluate_hessian_lagrangian_SimpleModel2x2_1(self): model = SimpleModel2by2_1() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [-4.5, -2.3, 0.0, 1.0, 4.1] x_init_list = list(itertools.product(x0_init_list, x1_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x in x_init_list: external_model.set_input_values(x) external_model.set_equality_constraint_multipliers([1.0, 1.0]) hess_lag = external_model.evaluate_hessian_equality_constraints() hess_lag = hess_lag.toarray() expected_hess = model.evaluate_hessian(x) expected_hess_lag = np.tril(expected_hess[0] + expected_hess[1]) np.testing.assert_allclose(hess_lag, expected_hess_lag, rtol=1e-8)
def test_hessian_SimpleModel2(self): model = SimpleModel2() m = model.make_model() x_init_list = [ [-5.0], [-4.0], [-3.0], [-1.5], [0.5], [1.0], [2.0], [3.5] ] external_model = ExternalPyomoModel( [m.x], [m.y], [m.residual_eqn], [m.external_eqn], ) for x in x_init_list: external_model.set_input_values(x) hess = external_model.evaluate_hessians_of_residuals() self.assertAlmostEqual( hess[0][0, 0], model.evaluate_hessian(x[0]), delta=1e-7, )
def test_evaluate_SimpleModel2(self): model = SimpleModel2() m = model.make_model() x_init_list = [ [-5.0], [-4.0], [-3.0], [-1.5], [0.5], [1.0], [2.0], [3.5] ] external_model = ExternalPyomoModel( [m.x], [m.y], [m.residual_eqn], [m.external_eqn], ) for x in x_init_list: external_model.set_input_values(x) resid = external_model.evaluate_equality_constraints() self.assertAlmostEqual( resid[0], model.evaluate_residual(x[0]), delta=1e-8, )
def test_jacobian_SimpleModel1(self): model = SimpleModel1() m = model.make_model() x_init_list = [ [-5.0], [-4.0], [-3.0], [-1.5], [0.5], [1.0], [2.0], [3.5] ] external_model = ExternalPyomoModel( [m.x], [m.y], [m.residual_eqn], [m.external_eqn], ) for x in x_init_list: external_model.set_input_values(x) jac = external_model.evaluate_jacobian_equality_constraints() self.assertAlmostEqual( jac.toarray()[0][0], model.evaluate_jacobian(x[0]), delta=1e-8, )
def test_external_jacobian_SimpleModel2(self): model = SimpleModel2() m = model.make_model() x_init_list = [ [-5.0], [-4.0], [-3.0], [-1.5], [0.5], [1.0], [2.0], [3.5] ] external_model = ExternalPyomoModel( [m.x], [m.y], [m.residual_eqn], [m.external_eqn], ) for x in x_init_list: external_model.set_input_values(x) jac = external_model.evaluate_jacobian_external_variables() expected_jac = model.evaluate_external_jacobian(x[0]) self.assertAlmostEqual( jac[0,0], expected_jac, delta=1e-8, )
def test_optimize_no_cyipopt_for_inner_problem(self): # Here we don't specify a solver for the inner problem. # This is contrived, as clearly CyIpopt is available for the outer # solve. This test exercises the part of the ExternalPyomoModel # constructor that sets a default solver if CyIpopt is not available. m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block.set_external_model(ex_model) a = m.ex_block.inputs["input_0"] b = m.ex_block.inputs["input_1"] r = m.ex_block.inputs["input_2"] x = m.ex_block.inputs["input_3"] y = m.ex_block.inputs["input_4"] m.obj = pyo.Objective(expr=(x - 2.0)**2 + (y - 2.0)**2 + (a - 2.0)**2 + (b - 2.0)**2 + (r - 2.0)**2) # Solve with external model embedded solver = pyo.SolverFactory("cyipopt") solver.solve(m) m_ex.obj = pyo.Objective(expr=(m_ex.x - 2.0)**2 + (m_ex.y - 2.0)**2 + (m_ex.a - 2.0)**2 + (m_ex.b - 2.0)**2 + (m_ex.r - 2.0)**2) m_ex.a.set_value(0.0) m_ex.b.set_value(0.0) m_ex.r.set_value(0.0) m_ex.y.set_value(0.0) m_ex.x.set_value(0.0) # Solve external model, now with same objective function ipopt = pyo.SolverFactory("ipopt") ipopt.solve(m_ex) # Make sure full space and reduced space solves give same # answers. self.assertAlmostEqual(m_ex.a.value, a.value, delta=1e-8) self.assertAlmostEqual(m_ex.b.value, b.value, delta=1e-8) self.assertAlmostEqual(m_ex.r.value, r.value, delta=1e-8) self.assertAlmostEqual(m_ex.x.value, x.value, delta=1e-8) self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8)
def test_optimize_with_ipopt_for_inner_problem(self): # Use Ipopt, rather than the default CyIpopt, for the inner problem m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] inner_solver = pyo.SolverFactory("ipopt") ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, solver=inner_solver, use_cyipopt=False, ) block.set_external_model(ex_model) a = m.ex_block.inputs["input_0"] b = m.ex_block.inputs["input_1"] r = m.ex_block.inputs["input_2"] x = m.ex_block.inputs["input_3"] y = m.ex_block.inputs["input_4"] m.obj = pyo.Objective(expr=(x - 2.0)**2 + (y - 2.0)**2 + (a - 2.0)**2 + (b - 2.0)**2 + (r - 2.0)**2) # Solve with external model embedded solver = pyo.SolverFactory("cyipopt") solver.solve(m) m_ex.obj = pyo.Objective(expr=(m_ex.x - 2.0)**2 + (m_ex.y - 2.0)**2 + (m_ex.a - 2.0)**2 + (m_ex.b - 2.0)**2 + (m_ex.r - 2.0)**2) m_ex.a.set_value(0.0) m_ex.b.set_value(0.0) m_ex.r.set_value(0.0) m_ex.y.set_value(0.0) m_ex.x.set_value(0.0) # Solve external model, now with same objective function ipopt = pyo.SolverFactory("ipopt") ipopt.solve(m_ex) # Make sure full space and reduced space solves give same # answers. self.assertAlmostEqual(m_ex.a.value, a.value, delta=1e-8) self.assertAlmostEqual(m_ex.b.value, b.value, delta=1e-8) self.assertAlmostEqual(m_ex.r.value, r.value, delta=1e-8) self.assertAlmostEqual(m_ex.x.value, x.value, delta=1e-8) self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8)
def test_hessian_SimpleModel2x2_1(self): model = SimpleModel2by2_1() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [-4.5, -2.3, 0.0, 1.0, 4.1] x_init_list = list(itertools.product(x0_init_list, x1_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x in x_init_list: external_model.set_input_values(x) hess = external_model.evaluate_hessians_of_residuals() expected_hess = model.evaluate_hessian(x) np.testing.assert_allclose(hess, expected_hess, rtol=1e-8)
def test_jacobian_SimpleModel2x2_1(self): model = SimpleModel2by2_1() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [-4.5, -2.3, 0.0, 1.0, 4.1] x_init_list = list(itertools.product(x0_init_list, x1_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x in x_init_list: external_model.set_input_values(x) jac = external_model.evaluate_jacobian_equality_constraints() expected_jac = model.evaluate_jacobian(x) np.testing.assert_allclose(jac.toarray(), expected_jac, rtol=1e-8)
def test_external_jacobian_Model2by2(self): model = Model2by2() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [0.5, 1.0, 1.5, 2.5, 4.1] x_init_list = list(itertools.product(x0_init_list, x1_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x in x_init_list: external_model.set_input_values(x) jac = external_model.evaluate_jacobian_external_variables() expected_jac = model.evaluate_external_jacobian(x) np.testing.assert_allclose(jac, expected_jac, rtol=1e-8)
def test_jacobian_SimpleModel2(self): model = SimpleModel2() m = model.make_model() x_init_list = [ [-5.0], [-4.0], [-3.0], [-1.5], [0.5], [1.0], [2.0], [3.5] ] external_model = ExternalPyomoModel( [m.x], [m.y], [m.residual_eqn], [m.external_eqn], ) for x in x_init_list: external_model.set_input_values(x) jac = external_model.evaluate_jacobian_equality_constraints() # evaluate_jacobian_equality_constraints involves an LU # factorization and repeated back-solve. SciPy returns a # dense matrix from this operation. I am not sure if I should # cast it to a sparse matrix. For now it is dense... self.assertAlmostEqual( jac.toarray()[0][0], model.evaluate_jacobian(x[0]), delta=1e-7, )
def test_reduced_hessian_lagrangian(self): model = Model2by2() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [0.5, 1.0, 1.5, 2.5, 4.1] lam_init_list = [-2.5, -0.5, 0.0, 1.0, 2.0] init_list = list( itertools.product(x0_init_list, x1_init_list, lam_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x0, x1, lam in init_list: x = [x0, x1] lam = [lam] external_model.set_input_values(x) # Same comment as previous test regarding calculation order external_model.set_external_constraint_multipliers(lam) hlxx, hlxy, hlyy = \ external_model.get_full_space_lagrangian_hessians() hess = external_model.calculate_reduced_hessian_lagrangian( hlxx, hlxy, hlyy) pred_hess = model.calculate_reduced_lagrangian_hessian(lam, x) # This test asserts that we are doing the block reduction properly. np.testing.assert_allclose(np.array(hess), pred_hess, rtol=1e-8) from_individual = external_model.evaluate_hessians_of_residuals() hl_from_individual = sum(l * h for l, h in zip(lam, from_individual)) # This test asserts that the block reduction is correct. np.testing.assert_allclose(np.array(hess), hl_from_individual, rtol=1e-8)
def test_evaluate_hessian_equality_constraints_order(self): model = Model2by2() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [0.5, 1.0, 1.5, 2.5, 4.1] lam_init_list = [-2.5, -0.5, 0.0, 1.0, 2.0] init_list = list( itertools.product(x0_init_list, x1_init_list, lam_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x0, x1, lam in init_list: x = [x0, x1] lam = [lam] external_model.set_equality_constraint_multipliers(lam) external_model.set_input_values(x) # Using evaluate_hessian_equality_constraints, which calculates # external multiplier values, we can calculate the correct Hessian # regardless of the order in which primal and dual variables are # set. hess = external_model.evaluate_hessian_equality_constraints() pred_hess = model.calculate_reduced_lagrangian_hessian(lam, x) # This test asserts that we are doing the block reduction properly. np.testing.assert_allclose(hess.toarray(), np.tril(pred_hess), rtol=1e-8) from_individual = external_model.evaluate_hessians_of_residuals() hl_from_individual = sum(l * h for l, h in zip(lam, from_individual)) # This test asserts that the block reduction is correct. np.testing.assert_allclose(hess.toarray(), np.tril(hl_from_individual), rtol=1e-8)
def test_solve_square(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block.set_external_model(ex_model) _add_linking_constraints(m) m.a.fix(1) m.b.fix(2) m.r.fix(3) m.obj = pyo.Objective(expr=0) solver = pyo.SolverFactory("cyipopt") solver.solve(m) self.assertFalse(m_ex.a.fixed) self.assertFalse(m_ex.b.fixed) self.assertFalse(m_ex.r.fixed) m_ex.a.fix(1) m_ex.b.fix(2) m_ex.r.fix(3) ipopt = pyo.SolverFactory("ipopt") ipopt.solve(m_ex) x = m.ex_block.inputs["input_3"] y = m.ex_block.inputs["input_4"] self.assertAlmostEqual(m_ex.x.value, x.value, delta=1e-8) self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8)
def test_construct_scalar(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block self.assertIs(type(block), ScalarExternalGreyBoxBlock) m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block.set_external_model(ex_model) self.assertEqual(len(block.inputs), len(input_vars)) self.assertEqual(len(block.outputs), 0) self.assertEqual(len(block._equality_constraint_names), 2)
def test_construct_indexed(self): block = ExternalGreyBoxBlock([0, 1, 2], concrete=True) self.assertIs(type(block), IndexedExternalGreyBoxBlock) m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) for i in block: b = block[i] b.set_external_model(ex_model) self.assertEqual(len(b.inputs), len(input_vars)) self.assertEqual(len(b.outputs), 0) self.assertEqual(len(b._equality_constraint_names), 2)
def test_solver_with_cyipopt(self): # CyIpopt is required here just because we get a different error # (see test below) if CyIpopt is unavailable. m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] inner_solver = pyo.SolverFactory("ipopt") msg = "Please set use_cyipopt to False" with self.assertRaisesRegex(RuntimeError, msg): ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, solver=inner_solver, use_cyipopt=True, )
def test_full_space_lagrangian_hessians(self): model = Model2by2() m = model.make_model() m.x[0].set_value(1.0) m.x[1].set_value(2.0) m.y[0].set_value(3.0) m.y[1].set_value(4.0) x0_init_list = [-5.0, -3.0, 0.5, 1.0, 2.5] x1_init_list = [0.5, 1.0, 1.5, 2.5, 4.1] lam_init_list = [-2.5, -0.5, 0.0, 1.0, 2.0] init_list = list( itertools.product(x0_init_list, x1_init_list, lam_init_list)) external_model = ExternalPyomoModel( list(m.x.values()), list(m.y.values()), list(m.residual_eqn.values()), list(m.external_eqn.values()), ) for x0, x1, lam in init_list: x = [x0, x1] lam = [lam] external_model.set_input_values(x) # Note that these multiplier calculations are dependent on x, # so if we switch their order, we will get "wrong" answers. # (This is wrong in the sense that the residual and external # multipliers won't necessarily correspond). external_model.set_external_constraint_multipliers(lam) hlxx, hlxy, hlyy = \ external_model.get_full_space_lagrangian_hessians() pred_hlxx, pred_hlxy, pred_hlyy = \ model.calculate_full_space_lagrangian_hessians(lam, x) # TODO: Is comparing the array representation sufficient here? # Should I make sure I get the sparse representation I expect? np.testing.assert_allclose(hlxx.toarray(), pred_hlxx, rtol=1e-8) np.testing.assert_allclose(hlxy.toarray(), pred_hlxy, rtol=1e-8) np.testing.assert_allclose(hlyy.toarray(), pred_hlyy, rtol=1e-8)
def test_optimize_dynamic(self): # Create the "external model" m = make_dynamic_model() time = m.time t0 = m.time.first() m.h[t0].fix(1.2) m.flow_in[t0].fix(1.5) m.obj = pyo.Objective(expr=sum( (m.h[t] - 2.0)**2 for t in m.time if t != t0)) # Create the block that will hold the reduced space model. reduced_space = pyo.Block(concrete=True) reduced_space.diff_var = pyo.Reference(m.h) reduced_space.deriv_var = pyo.Reference(m.dhdt) reduced_space.input_var = pyo.Reference(m.flow_in) reduced_space.disc_eq = pyo.Reference(m.dhdt_disc_eqn) reduced_space.objective = pyo.Reference(m.obj) reduced_space.external_block = ExternalGreyBoxBlock(time) block = reduced_space.external_block block[t0].deactivate() for t in time: # TODO: skipping time.first() necessary? if t != t0: input_vars = [m.h[t], m.dhdt[t], m.flow_in[t]] external_vars = [m.flow_out[t]] residual_cons = [m.h_diff_eqn[t]] external_cons = [m.flow_out_eqn[t]] external_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block[t].set_external_model(external_model) n_inputs = len(input_vars) def linking_constraint_rule(m, i, t): if t == t0: return pyo.Constraint.Skip if i == 0: return m.diff_var[t] == m.external_block[t].inputs["input_0"] elif i == 1: return m.deriv_var[t] == m.external_block[t].inputs["input_1"] elif i == 2: return m.input_var[t] == m.external_block[t].inputs["input_2"] reduced_space.linking_constraint = pyo.Constraint( range(n_inputs), time, rule=linking_constraint_rule, ) # Initialize new variables for t in time: if t != t0: block[t].inputs["input_0"].set_value(m.h[t].value) block[t].inputs["input_1"].set_value(m.dhdt[t].value) block[t].inputs["input_2"].set_value(m.flow_in[t].value) solver = pyo.SolverFactory("cyipopt") results = solver.solve(reduced_space) # These values were obtained by solving this problem in the full # space in a separate script. h_target = [1.2, 2.0, 2.0] dhdt_target = [-0.690890, 0.80, 0.0] flow_in_target = [1.5, 3.628427, 2.828427] flow_out_target = [2.190890, 2.828427, 2.828427] for t in time: if t == t0: continue values = [ m.h[t].value, m.dhdt[t].value, m.flow_out[t].value, m.flow_in[t].value ] target_values = [ h_target[t], dhdt_target[t], flow_out_target[t], flow_in_target[t] ] self.assertStructuredAlmostEqual(values, target_values, delta=1e-5)
def test_construct_dynamic(self): m = make_dynamic_model() time = m.time t0 = m.time.first() inputs = [m.h, m.dhdt, m.flow_in] ext_vars = [m.flow_out] residuals = [m.h_diff_eqn] ext_cons = [m.flow_out_eqn] external_model_dict = { t: ExternalPyomoModel( [var[t] for var in inputs], [var[t] for var in ext_vars], [con[t] for con in residuals], [con[t] for con in ext_cons], ) for t in time } reduced_space = pyo.Block(concrete=True) reduced_space.external_block = ExternalGreyBoxBlock( time, external_model=external_model_dict, ) block = reduced_space.external_block block[t0].deactivate() self.assertIs(type(block), IndexedExternalGreyBoxBlock) for t in time: b = block[t] self.assertEqual(len(b.inputs), len(inputs)) self.assertEqual(len(b.outputs), 0) self.assertEqual(len(b._equality_constraint_names), len(residuals)) reduced_space.diff_var = pyo.Reference(m.h) reduced_space.deriv_var = pyo.Reference(m.dhdt) reduced_space.input_var = pyo.Reference(m.flow_in) reduced_space.disc_eqn = pyo.Reference(m.dhdt_disc_eqn) pyomo_vars = list(reduced_space.component_data_objects(pyo.Var)) pyomo_cons = list(reduced_space.component_data_objects(pyo.Constraint)) # NOTE: Variables in the EGBB are not found by component_data_objects self.assertEqual(len(pyomo_vars), len(inputs) * len(time)) # "Constraints" defined by the EGBB are not found either, although # this is expected. self.assertEqual(len(pyomo_cons), len(time) - 1) reduced_space._obj = pyo.Objective(expr=0) # This is required to avoid a failure in the implicit function # evaluation when "initializing" (?) the PNLPwGBB. # Why exactly is function evaluation necessary for this # initialization again? block[:].inputs[:].set_value(1.0) # This is necessary for these variables to appear in the PNLPwGBB. # Otherwise they don't appear in any "real" constraints of the # PyomoNLP. reduced_space.const_input_eqn = pyo.Constraint( expr=reduced_space.input_var[2] - reduced_space.input_var[1] == 0) nlp = PyomoNLPWithGreyBoxBlocks(reduced_space) self.assertEqual( nlp.n_primals(), # EGBB "inputs", dhdt, and flow_in exist for t != t0. # h exists for all time. (2 + len(inputs)) * (len(time) - 1) + len(time), ) self.assertEqual( nlp.n_constraints(), # EGBB equality constraints and disc_eqn exist for t != t0. # const_input_eqn is a single constraint (len(residuals) + 1) * (len(time) - 1) + 1, )
def test_solve_square_dynamic(self): # Create the "external model" m = make_dynamic_model() time = m.time t0 = m.time.first() m.h[t0].fix(1.2) m.flow_in.fix(1.5) # Create the block that will hold the reduced space model. reduced_space = pyo.Block(concrete=True) reduced_space.diff_var = pyo.Reference(m.h) reduced_space.deriv_var = pyo.Reference(m.dhdt) reduced_space.input_var = pyo.Reference(m.flow_in) reduced_space.disc_eq = pyo.Reference(m.dhdt_disc_eqn) reduced_space.external_block = ExternalGreyBoxBlock(time) block = reduced_space.external_block block[t0].deactivate() for t in time: # TODO: skipping time.first() necessary? if t != t0: input_vars = [m.h[t], m.dhdt[t]] external_vars = [m.flow_out[t]] residual_cons = [m.h_diff_eqn[t]] external_cons = [m.flow_out_eqn[t]] external_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block[t].set_external_model(external_model) n_inputs = len(input_vars) def linking_constraint_rule(m, i, t): if t == t0: return pyo.Constraint.Skip if i == 0: return m.diff_var[t] == m.external_block[t].inputs["input_0"] elif i == 1: return m.deriv_var[t] == m.external_block[t].inputs["input_1"] reduced_space.linking_constraint = pyo.Constraint( range(n_inputs), time, rule=linking_constraint_rule, ) # Initialize new variables for t in time: if t != t0: block[t].inputs["input_0"].set_value(m.h[t].value) block[t].inputs["input_1"].set_value(m.dhdt[t].value) reduced_space._obj = pyo.Objective(expr=0) solver = pyo.SolverFactory("cyipopt") results = solver.solve(reduced_space, tee=True) # Full space square model was solved in a separate script # to obtain these values. h_target = [1.2, 0.852923, 0.690725] dhdt_target = [-0.690890, -0.347077, -0.162198] flow_out_target = [2.190980, 1.847077, 1.662198] for t in time: if t == t0: continue values = [m.h[t].value, m.dhdt[t].value, m.flow_out[t].value] target_values = [h_target[t], dhdt_target[t], flow_out_target[t]] self.assertStructuredAlmostEqual(values, target_values, delta=1e-5)
def test_optimize_dynamic_references(self): """ When when pre-existing variables are attached to the EGBB as references, linking constraints are no longer necessary. """ # Create the "external model" m = make_dynamic_model() time = m.time t0 = m.time.first() m.h[t0].fix(1.2) m.flow_in[t0].fix(1.5) m.obj = pyo.Objective(expr=sum( (m.h[t] - 2.0)**2 for t in m.time if t != t0)) # Create the block that will hold the reduced space model. reduced_space = pyo.Block(concrete=True) reduced_space.diff_var = pyo.Reference(m.h) reduced_space.deriv_var = pyo.Reference(m.dhdt) reduced_space.input_var = pyo.Reference(m.flow_in) reduced_space.disc_eq = pyo.Reference(m.dhdt_disc_eqn) reduced_space.objective = pyo.Reference(m.obj) reduced_space.external_block = ExternalGreyBoxBlock(time) block = reduced_space.external_block block[t0].deactivate() for t in time: # TODO: is skipping time.first() necessary? if t != t0: input_vars = [m.h[t], m.dhdt[t], m.flow_in[t]] external_vars = [m.flow_out[t]] residual_cons = [m.h_diff_eqn[t]] external_cons = [m.flow_out_eqn[t]] external_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block[t].set_external_model(external_model, inputs=input_vars) solver = pyo.SolverFactory("cyipopt") results = solver.solve(reduced_space) # These values were obtained by solving this problem in the full # space in a separate script. h_target = [1.2, 2.0, 2.0] dhdt_target = [-0.690890, 0.80, 0.0] flow_in_target = [1.5, 3.628427, 2.828427] flow_out_target = [2.190890, 2.828427, 2.828427] for t in time: if t == t0: continue values = [ m.h[t].value, m.dhdt[t].value, m.flow_out[t].value, m.flow_in[t].value ] target_values = [ h_target[t], dhdt_target[t], flow_out_target[t], flow_in_target[t] ] self.assertStructuredAlmostEqual(values, target_values, delta=1e-5)
def test_set_and_evaluate(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block.set_external_model(ex_model) a = m.ex_block.inputs["input_0"] b = m.ex_block.inputs["input_1"] r = m.ex_block.inputs["input_2"] x = m.ex_block.inputs["input_3"] y = m.ex_block.inputs["input_4"] m.obj = pyo.Objective(expr=(x - 2.0)**2 + (y - 2.0)**2 + (a - 2.0)**2 + (b - 2.0)**2 + (r - 2.0)**2) _add_linking_constraints(m) nlp = PyomoNLPWithGreyBoxBlocks(m) # Set primals in model, get primals in nlp # set/get duals # evaluate constraints # evaluate Jacobian # evaluate Hessian self.assertEqual(nlp.n_primals(), 8) # PyomoNLPWithGreyBoxBlocks sorts variables by name primals_names = [ "a", "b", "ex_block.inputs[input_0]", "ex_block.inputs[input_1]", "ex_block.inputs[input_2]", "ex_block.inputs[input_3]", "ex_block.inputs[input_4]", "r", ] self.assertEqual(nlp.primals_names(), primals_names) np.testing.assert_equal(np.zeros(8), nlp.get_primals()) primals = np.array([0, 1, 2, 3, 4, 5, 6, 7]) nlp.set_primals(primals) np.testing.assert_equal(primals, nlp.get_primals()) nlp.load_state_into_pyomo() for name, val in zip(primals_names, primals): var = m.find_component(name) self.assertEqual(var.value, val) constraint_names = [ "linking_constraint[0]", "linking_constraint[1]", "linking_constraint[2]", "ex_block.residual_0", "ex_block.residual_1", ] self.assertEqual(constraint_names, nlp.constraint_names()) residuals = np.array([ -2.0, -2.0, 3.0, # These values were obtained by solving the same system # with Ipopt in another script. It may be better to do # the solve in this test in case the system changes. 5.0 - (-3.03051522), 6.0 - 3.583839997, ]) np.testing.assert_allclose(residuals, nlp.evaluate_constraints(), rtol=1e-8) duals = np.array([1, 2, 3, 4, 5]) nlp.set_duals(duals) self.assertEqual(ex_model.residual_con_multipliers, [4, 5]) np.testing.assert_equal(nlp.get_duals(), duals)
def test_jacobian(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block.set_external_model(ex_model) a = m.ex_block.inputs["input_0"] b = m.ex_block.inputs["input_1"] r = m.ex_block.inputs["input_2"] x = m.ex_block.inputs["input_3"] y = m.ex_block.inputs["input_4"] m.obj = pyo.Objective(expr=(x - 2.0)**2 + (y - 2.0)**2 + (a - 2.0)**2 + (b - 2.0)**2 + (r - 2.0)**2) _add_linking_constraints(m) nlp = PyomoNLPWithGreyBoxBlocks(m) primals = np.array([0, 1, 2, 3, 4, 5, 6, 7]) nlp.set_primals(primals) jac = nlp.evaluate_jacobian() # Variable and constraint orders (verified by previous test): # Rows: # [ # "linking_constraint[0]", # "linking_constraint[1]", # "linking_constraint[2]", # "ex_block.residual_0", # "ex_block.residual_1", # ] # Cols: # [ # "a", # "b", # "ex_block.inputs[input_0]", # "ex_block.inputs[input_1]", # "ex_block.inputs[input_2]", # "ex_block.inputs[input_3]", # "ex_block.inputs[input_4]", # "r", # ] row = [ 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, ] col = [ 0, 2, 1, 3, 7, 4, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, ] data = [ 1, -1, 1, -1, 1, -1, -0.16747094, -1.00068434, 1.72383729, 1, 0, -0.30708535, -0.28546127, -0.25235924, 0, 1, ] self.assertEqual(len(row), len(jac.row)) rcd_dict = dict(((i, j), val) for i, j, val in zip(row, col, data)) for i, j, val in zip(jac.row, jac.col, jac.data): self.assertIn((i, j), rcd_dict) self.assertAlmostEqual(rcd_dict[i, j], val, delta=1e-8)
def test_hessian_2(self): # Test with duals different than vector of ones m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) block = m.ex_block m_ex = _make_external_model() input_vars = [m_ex.a, m_ex.b, m_ex.r, m_ex.x_out, m_ex.y_out] external_vars = [m_ex.x, m_ex.y] residual_cons = [m_ex.c_out_1, m_ex.c_out_2] external_cons = [m_ex.c_ex_1, m_ex.c_ex_2] ex_model = ExternalPyomoModel( input_vars, external_vars, residual_cons, external_cons, ) block.set_external_model(ex_model) a = m.ex_block.inputs["input_0"] b = m.ex_block.inputs["input_1"] r = m.ex_block.inputs["input_2"] x = m.ex_block.inputs["input_3"] y = m.ex_block.inputs["input_4"] m.obj = pyo.Objective(expr=(x - 2.0)**2 + (y - 2.0)**2 + (a - 2.0)**2 + (b - 2.0)**2 + (r - 2.0)**2) _add_nonlinear_linking_constraints(m) nlp = PyomoNLPWithGreyBoxBlocks(m) primals = np.array([0, 1, 2, 3, 4, 5, 6, 7]) duals = np.array([4.4, -3.3, 2.2, -1.1, 0.0]) nlp.set_primals(primals) nlp.set_duals(duals) hess = nlp.evaluate_hessian_lag() # Variable order (verified by a previous test): # [ # "a", # "b", # "ex_block.inputs[input_0]", # "ex_block.inputs[input_1]", # "ex_block.inputs[input_2]", # "ex_block.inputs[input_3]", # "ex_block.inputs[input_4]", # "r", # ] row = [0, 1, 7] col = [0, 1, 7] # Data entries are influenced by multiplier values. data = [4.4 * 2.0, -3.3 * 2.0, 2.2 * 2.0] # ^ These variables only appear in linking constraints rcd_dict = dict(((i, j), val) for i, j, val in zip(row, col, data)) # These are the coordinates of the Hessian corresponding to # external variables with true nonzeros. The coordinates have # terms due to objective, linking constraints, and external # constraints. Values were extracted from the external model # while writing this test, which is just meant to verify # that the different Hessians combined properly. ex_block_nonzeros = { (2, 2): 2.0 + 4.4 * (-1.0) + -1.1 * (-0.10967928), (2, 3): -1.1 * (-0.10684633), (3, 2): -1.1 * (-0.10684633), (2, 4): -1.1 * (0.19329898), (4, 2): -1.1 * (0.19329898), (3, 3): 2.0 + (-3.3) * (-1.0) + -1.1 * (-1.31592135), (3, 4): -1.1 * (1.13920361), (4, 3): -1.1 * (1.13920361), (4, 4): 2.0 + 2.2 * (-1.0) + -1.1 * (-1.0891866), (5, 5): 2.0, (6, 6): 2.0, } rcd_dict.update(ex_block_nonzeros) # Because "external Hessians" are computed by factorizing matrices, # we have dense blocks in the Hessian for now. ex_block_coords = [2, 3, 4, 5, 6] for i, j in itertools.product(ex_block_coords, ex_block_coords): row.append(i) col.append(j) if (i, j) not in rcd_dict: rcd_dict[i, j] = 0.0 self.assertEqual(len(row), len(hess.row)) for i, j, val in zip(hess.row, hess.col, hess.data): self.assertIn((i, j), rcd_dict) self.assertAlmostEqual(rcd_dict[i, j], val, delta=1e-8)