def test_pyomo_nlp(self): m = self.make_model() scaling_factors = [1e-4, 1e4] m.epm.set_equality_constraint_scaling_factors(scaling_factors) nlp = PyomoNLPWithGreyBoxBlocks(m) nlp_sf = nlp.get_constraints_scaling() np.testing.assert_array_equal(scaling_factors, nlp_sf)
def test_cyipopt_callback(self): # Use a callback to check that the reported infeasibility is # due to the scaled equality constraints. # Note that the scaled infeasibility is not what we see if we # call solve with tee=True, as by default the displayed infeasibility # is unscaled. Luckily, we can still access the scaled infeasibility # with a callback. m = self.make_model() scaling_factors = [1e-4, 1e4] m.epm.set_equality_constraint_scaling_factors(scaling_factors) nlp = PyomoNLPWithGreyBoxBlocks(m) def callback( local_nlp, alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, ): primals = tuple(local_nlp.get_primals()) # I happen to know the order of the primals here u, v, x, y = primals # Calculate the scaled residuals I expect con_3_resid = scaling_factors[0] * abs( self.con_3_body(x, y, u, v) - self.con_3_rhs()) con_4_resid = scaling_factors[1] * abs( self.con_4_body(x, y, u, v) - self.con_4_rhs()) pred_inf_pr = max(con_3_resid, con_4_resid) # Make sure Ipopt is using the scaled constraints internally self.assertAlmostEqual(inf_pr, pred_inf_pr) cyipopt_nlp = CyIpoptNLP( nlp, intermediate_callback=callback, ) x0 = nlp.get_primals() cyipopt = CyIpoptSolver( cyipopt_nlp, options={ "max_iter": 0, "nlp_scaling_method": "user-scaling", }, ) cyipopt.solve(x0=x0)
def test_cyipopt_nlp(self): m = self.make_model() scaling_factors = [1e-4, 1e4] m.epm.set_equality_constraint_scaling_factors(scaling_factors) nlp = PyomoNLPWithGreyBoxBlocks(m) cyipopt_nlp = CyIpoptNLP(nlp) obj_scaling, x_scaling, g_scaling = cyipopt_nlp.scaling_factors() np.testing.assert_array_equal(scaling_factors, g_scaling)
def test_pressure_drop_model_nlp(self): m = self._create_pressure_drop_model() cons = [m.c_con, m.F_con, m.Pin_con, m.P2_con] inputs = [m.Pin, m.c, m.F] outputs = [m.P2, m.Pout] nlp = PyomoNLPWithGreyBoxBlocks(m) n_primals = len(inputs) + len(outputs) n_eq_con = len(cons) + len(outputs) self.assertEqual(nlp.n_primals(), n_primals) self.assertEqual(nlp.n_constraints(), n_eq_con) constraint_names = [ "c_con", "F_con", "Pin_con", "P2_con", "egb.output_constraints[P2]", "egb.output_constraints[Pout]", ] primals = inputs + outputs nlp_constraints = nlp.constraint_names() nlp_vars = nlp.primals_names() con_idx_map = {} for name in constraint_names: # Quadratic scan to get constraint indices is not ideal. # Could this map be created while PyNLPwGBB is being constructed? con_idx_map[name] = nlp_constraints.index(name) var_idx_map = ComponentMap() for var in primals: name = var.name var_idx_map[var] = nlp_vars.index(name) incident_vars = { con.name: list(identify_variables(con.expr)) for con in cons } incident_vars["egb.output_constraints[P2]"] = inputs + [outputs[0]] incident_vars["egb.output_constraints[Pout]"] = inputs + [outputs[1]] expected_nonzeros = set() for con, varlist in incident_vars.items(): i = con_idx_map[con] for var in varlist: j = var_idx_map[var] expected_nonzeros.add((i, j)) self.assertEqual(len(expected_nonzeros), nlp.nnz_jacobian()) jac = nlp.evaluate_jacobian() for i, j in zip(jac.row, jac.col): self.assertIn((i, j), expected_nonzeros)
def test_compare_evaluations(self): A1 = 5 A2 = 10 c1 = 3 c2 = 4 N = 6 dt = 1 m = create_pyomo_model(A1, A2, c1, c2, N, dt) solver = pyo.SolverFactory('ipopt') solver.options['linear_solver'] = 'mumps' status = solver.solve(m, tee=False) m_nlp = PyomoNLP(m) mex = create_pyomo_external_grey_box_model(A1, A2, c1, c2, N, dt) # mex_nlp = PyomoGreyBoxNLP(mex) mex_nlp = PyomoNLPWithGreyBoxBlocks(mex) # get the variable and constraint order and create the maps # reliable order independent comparisons m_x_order = m_nlp.primals_names() m_c_order = m_nlp.constraint_names() mex_x_order = mex_nlp.primals_names() mex_c_order = mex_nlp.constraint_names() x1list = [ 'h1[0]', 'h1[1]', 'h1[2]', 'h1[3]', 'h1[4]', 'h1[5]', 'h2[0]', 'h2[1]', 'h2[2]', 'h2[3]', 'h2[4]', 'h2[5]', 'F1[1]', 'F1[2]', 'F1[3]', 'F1[4]', 'F1[5]', 'F2[1]', 'F2[2]', 'F2[3]', 'F2[4]', 'F2[5]', 'F12[0]', 'F12[1]', 'F12[2]', 'F12[3]', 'F12[4]', 'F12[5]', 'Fo[0]', 'Fo[1]', 'Fo[2]', 'Fo[3]', 'Fo[4]', 'Fo[5]' ] x2list = [ 'egb.inputs[h1_0]', 'egb.inputs[h1_1]', 'egb.inputs[h1_2]', 'egb.inputs[h1_3]', 'egb.inputs[h1_4]', 'egb.inputs[h1_5]', 'egb.inputs[h2_0]', 'egb.inputs[h2_1]', 'egb.inputs[h2_2]', 'egb.inputs[h2_3]', 'egb.inputs[h2_4]', 'egb.inputs[h2_5]', 'egb.inputs[F1_1]', 'egb.inputs[F1_2]', 'egb.inputs[F1_3]', 'egb.inputs[F1_4]', 'egb.inputs[F1_5]', 'egb.inputs[F2_1]', 'egb.inputs[F2_2]', 'egb.inputs[F2_3]', 'egb.inputs[F2_4]', 'egb.inputs[F2_5]', 'egb.outputs[F12_0]', 'egb.outputs[F12_1]', 'egb.outputs[F12_2]', 'egb.outputs[F12_3]', 'egb.outputs[F12_4]', 'egb.outputs[F12_5]', 'egb.outputs[Fo_0]', 'egb.outputs[Fo_1]', 'egb.outputs[Fo_2]', 'egb.outputs[Fo_3]', 'egb.outputs[Fo_4]', 'egb.outputs[Fo_5]' ] x1_x2_map = dict(zip(x1list, x2list)) x1idx_x2idx_map = { i: mex_x_order.index(x1_x2_map[m_x_order[i]]) for i in range(len(m_x_order)) } c1list = [ 'h1bal[1]', 'h1bal[2]', 'h1bal[3]', 'h1bal[4]', 'h1bal[5]', 'h2bal[1]', 'h2bal[2]', 'h2bal[3]', 'h2bal[4]', 'h2bal[5]', 'F12con[0]', 'F12con[1]', 'F12con[2]', 'F12con[3]', 'F12con[4]', 'F12con[5]', 'Focon[0]', 'Focon[1]', 'Focon[2]', 'Focon[3]', 'Focon[4]', 'Focon[5]', 'min_inflow[1]', 'min_inflow[2]', 'min_inflow[3]', 'min_inflow[4]', 'min_inflow[5]', 'max_outflow[0]', 'max_outflow[1]', 'max_outflow[2]', 'max_outflow[3]', 'max_outflow[4]', 'max_outflow[5]', 'h10', 'h20' ] c2list = [ 'egb.h1bal_1', 'egb.h1bal_2', 'egb.h1bal_3', 'egb.h1bal_4', 'egb.h1bal_5', 'egb.h2bal_1', 'egb.h2bal_2', 'egb.h2bal_3', 'egb.h2bal_4', 'egb.h2bal_5', 'egb.output_constraints[F12_0]', 'egb.output_constraints[F12_1]', 'egb.output_constraints[F12_2]', 'egb.output_constraints[F12_3]', 'egb.output_constraints[F12_4]', 'egb.output_constraints[F12_5]', 'egb.output_constraints[Fo_0]', 'egb.output_constraints[Fo_1]', 'egb.output_constraints[Fo_2]', 'egb.output_constraints[Fo_3]', 'egb.output_constraints[Fo_4]', 'egb.output_constraints[Fo_5]', 'min_inflow[1]', 'min_inflow[2]', 'min_inflow[3]', 'min_inflow[4]', 'min_inflow[5]', 'max_outflow[0]', 'max_outflow[1]', 'max_outflow[2]', 'max_outflow[3]', 'max_outflow[4]', 'max_outflow[5]', 'h10', 'h20' ] c1_c2_map = dict(zip(c1list, c2list)) c1idx_c2idx_map = { i: mex_c_order.index(c1_c2_map[m_c_order[i]]) for i in range(len(m_c_order)) } # get the primals from m and put them in the correct order for mex m_x = m_nlp.get_primals() mex_x = np.zeros(len(m_x)) for i in range(len(m_x)): mex_x[x1idx_x2idx_map[i]] = m_x[i] # get the duals from m and put them in the correct order for mex m_lam = m_nlp.get_duals() mex_lam = np.zeros(len(m_lam)) for i in range(len(m_x)): mex_lam[c1idx_c2idx_map[i]] = m_lam[i] mex_nlp.set_primals(mex_x) mex_nlp.set_duals(mex_lam) m_obj = m_nlp.evaluate_objective() mex_obj = mex_nlp.evaluate_objective() self.assertAlmostEqual(m_obj, mex_obj, places=4) m_gobj = m_nlp.evaluate_grad_objective() mex_gobj = mex_nlp.evaluate_grad_objective() check_vectors_specific_order(self, m_gobj, m_x_order, mex_gobj, mex_x_order, x1_x2_map) m_c = m_nlp.evaluate_constraints() mex_c = mex_nlp.evaluate_constraints() check_vectors_specific_order(self, m_c, m_c_order, mex_c, mex_c_order, c1_c2_map) m_j = m_nlp.evaluate_jacobian() mex_j = mex_nlp.evaluate_jacobian().todense() check_sparse_matrix_specific_order(self, m_j, m_c_order, m_x_order, mex_j, mex_c_order, mex_x_order, c1_c2_map, x1_x2_map) m_h = m_nlp.evaluate_hessian_lag() mex_h = mex_nlp.evaluate_hessian_lag() check_sparse_matrix_specific_order(self, m_h, m_x_order, m_x_order, mex_h, mex_x_order, mex_x_order, x1_x2_map, x1_x2_map) mex_h = 0 * mex_h mex_nlp.evaluate_hessian_lag(out=mex_h) check_sparse_matrix_specific_order(self, m_h, m_x_order, m_x_order, mex_h, mex_x_order, mex_x_order, x1_x2_map, x1_x2_map)
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)
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_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_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, )