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)
Esempio n. 5
0
    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,
        )