def test_add_setpoint_to_controller(self):
        # This test just creates an objective then makes sure it has the
        # correct variables. More detailed tests will be performed for
        # construct_objective_weights and add_objective_function.
        nmpc = self.make_nmpc()
        time = nmpc.controller_time
        t0 = time.first()
        namespace = getattr(nmpc.controller, nmpc.namespace_name)
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
        categories = [VariableCategory.DIFFERENTIAL]
        nmpc.add_setpoint_to_controller(objective_state_categories=categories)
        assert hasattr(namespace, 'tracking_objective')
        obj_variables = ComponentSet(
            identify_variables(namespace.tracking_objective.expr))

        n_samples = len(namespace.sample_points) - 1
        assert len(obj_variables) == (len(namespace.diff_vars) +
                                      len(namespace.input_vars)) * n_samples
        for var in namespace.diff_vars.varlist + namespace.input_vars.varlist:
            for ts in namespace.sample_points:
                if ts == t0:
                    continue
                assert var[ts] in obj_variables
    def test_calculate_full_state_setpoint(self):
        # come up with setpoint, solve, check data structures,
        # make sure values of setpoint make sense.
        nmpc = self.make_nmpc()
        namespace = getattr(nmpc.controller, nmpc.namespace_name)
        t0 = nmpc.controller_time.first()
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
        categories = [
            VariableCategory.DIFFERENTIAL, VariableCategory.ALGEBRAIC
        ]
        for categ in categories:
            group = namespace.category_dict[categ]
            for i, var in enumerate(group):
                assert group.setpoint[i] is not None

        assert namespace.diff_vars.setpoint[0] == pytest.approx(3.75)
        assert namespace.diff_vars.setpoint[1] == pytest.approx(1.25)
        assert namespace.alg_vars.setpoint[0] == pytest.approx(3.0)
        assert namespace.alg_vars.setpoint[1] == pytest.approx(-3.75)
        assert namespace.alg_vars.setpoint[2] == pytest.approx(3.75)
        assert namespace.input_vars.setpoint[0] == pytest.approx(3.0)
    def test_initialize_by_solving_elements(self):
        # Make nmpc
        # add setpoint to controller
        # add pwc_constraints
        # call initialize_by_solving_elements
        # assert that values are as expected.
        nmpc = self.make_nmpc()
        time = nmpc.controller_time
        t0 = time.first()
        tl = time.last()
        controller = nmpc.controller
        namespace = getattr(controller, nmpc.namespace_name)
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        copy_values_forward(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
        override = [
            (nmpc.controller.conc[t0, 'A'], 1),
            (nmpc.controller.conc[t0, 'B'], 1),
            (nmpc.controller.rate[t0, 'A'], 1),
            (nmpc.controller.rate[t0, 'B'], 1),
            (nmpc.controller.flow_out[t0], 1),
            (nmpc.controller.flow_in[t0], 1),
        ]
        nmpc.add_setpoint_to_controller(objective_weight_override=override)
        nmpc.constrain_control_inputs_piecewise_constant()
        nmpc.controller.flow_in[:].set_value(2.0)
        nmpc.initialize_by_solving_elements(
            controller,
            time,
            input_type=ElementInitializationInputOption.INITIAL,
        )
        input_vars = namespace.input_vars
        diff_vars = namespace.diff_vars
        deriv_vars = namespace.deriv_vars
        assert input_vars[0][tl].value == 2.0
        assert diff_vars[0][tl].value == pytest.approx(3.185595567867036)
        assert diff_vars[1][tl].value == pytest.approx(1.1532474073395755)
        assert deriv_vars[0][tl].value == pytest.approx(0.44321329639889284)
        assert deriv_vars[1][tl].value == pytest.approx(0.8791007531878847)

        nmpc.initialize_by_solving_elements(
            controller,
            time,
            input_type=ElementInitializationInputOption.SET_POINT,
        )
        for t in time:
            if t != t0:
                assert input_vars[0][t] == 3.0
        assert diff_vars[0][tl].value == pytest.approx(3.7037037037037037)
        assert diff_vars[1][tl].value == pytest.approx(1.0746896480968502)
        assert deriv_vars[0][tl].value == pytest.approx(0.1851851851851849)
        assert deriv_vars[1][tl].value == pytest.approx(0.47963475941315314)
    def test_construct_objective_weights(self):
        """
        Populates setpoint values,

        """
        nmpc = self.make_nmpc()
        time = nmpc.controller_time
        t0 = time.first()
        namespace = getattr(nmpc.controller, nmpc.namespace_name)
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)

        reference = namespace.diff_vars.reference
        setpoint = namespace.diff_vars.setpoint
        n_diff_vars = len(namespace.diff_vars)
        predicted_diff_weights = [
            1. / abs(reference[i] - setpoint[i]) for i in range(n_diff_vars)
        ]
        tol = 1e-5
        nmpc.construct_objective_weights(
            nmpc.controller,
            categories=[
                VariableCategory.DIFFERENTIAL,
                VariableCategory.INPUT,
            ],
            objective_weight_tolerance=tol,
        )
        for pred, act in zip(predicted_diff_weights,
                             namespace.diff_vars.weights):
            assert pred == act
        assert namespace.input_vars.weights[0] == 1 / tol

        override = [
            (nmpc.controller.rate[t0, 'A'], 2),
            (nmpc.controller.rate[t0, 'B'], 2),
            (nmpc.controller.flow_out[t0], 2),
        ]
        nmpc.construct_objective_weights(
            nmpc.controller,
            categories=[
                VariableCategory.ALGEBRAIC,
            ],
            objective_weight_override=override,
        )
        for w in namespace.alg_vars.weights:
            assert w == 2
 def test_solve_control_problem(self):
     # make nmpc
     # add setpoint
     # initialize, from ICs probably
     # solve control problem
     # assert values are as expected.
     # Could probably even do this without initialization
     nmpc = self.make_nmpc()
     time = nmpc.controller_time
     t0 = time.first()
     tl = time.last()
     controller = nmpc.controller
     namespace = getattr(controller, nmpc.namespace_name)
     t1 = namespace.sample_points[1]
     setpoint = [
         (nmpc.controller.flow_in[t0], 3.0),
     ]
     # Note: without this initialization, the setpoint arrives at a
     # local optimum with inlet flow ~= 0.
     # One way to deal with this pitfall is to add bounds to inlet flow.
     nmpc.controller.flow_in[:].set_value(3.0)
     initialize_t0(nmpc.controller)
     copy_values_forward(nmpc.controller)
     nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
     override = [
         (nmpc.controller.conc[t0, 'A'], 1),
         (nmpc.controller.conc[t0, 'B'], 1),
         (nmpc.controller.rate[t0, 'A'], 1),
         (nmpc.controller.rate[t0, 'B'], 1),
         (nmpc.controller.flow_out[t0], 1),
         (nmpc.controller.flow_in[t0], 1),
     ]
     nmpc.add_setpoint_to_controller(objective_weight_override=override)
     nmpc.constrain_control_inputs_piecewise_constant()
     nmpc.solve_control_problem()
     input_vars = namespace.input_vars
     diff_vars = namespace.diff_vars
     assert input_vars[0][t1].value == pytest.approx(3.192261151432352)
     assert input_vars[0][tl].value == pytest.approx(2.9818775607191648)
     assert diff_vars[0][tl].value == pytest.approx(3.7101450012850137)
     assert diff_vars[1][tl].value == pytest.approx(1.0898406680173942)
     assert nmpc.controller_solved
    def test_initialize_control_problem(self):
        # Each initialization strategy should be tested in more detail
        # in other functions.
        # This function should test that each strategy can be passed to
        # the user-facing function, and that the values left in the
        # controller model are somewhat reasonable
        nmpc = self.make_nmpc()
        time = nmpc.controller_time
        t0 = time.first()
        namespace = getattr(nmpc.controller, nmpc.namespace_name)
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        copy_values_forward(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
        override = [
            (nmpc.controller.conc[t0, 'A'], 1),
            (nmpc.controller.conc[t0, 'B'], 1),
            (nmpc.controller.rate[t0, 'A'], 1),
            (nmpc.controller.rate[t0, 'B'], 1),
            (nmpc.controller.flow_out[t0], 1),
            (nmpc.controller.flow_in[t0], 1),
        ]
        nmpc.add_setpoint_to_controller(objective_weight_override=override)
        nmpc.constrain_control_inputs_piecewise_constant()

        nmpc.initialize_control_problem(
            control_init_option=ControlInitOption.FROM_INITIAL_CONDITIONS)
        nmpc.initialize_control_problem(
            control_init_option=ControlInitOption.BY_TIME_ELEMENT)
        with pytest.raises(RuntimeError):
            nmpc.initialize_control_problem(
                control_init_option=ControlInitOption.FROM_PREVIOUS)
    def test_initialize_from_previous_sample(self):
        nmpc = self.make_nmpc()
        time = nmpc.controller_time
        t0 = time.first()
        tl = time.last()
        controller = nmpc.controller
        namespace = getattr(controller, nmpc.namespace_name)
        sample_points = namespace.sample_points
        t1 = sample_points[1]
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        # One way to deal with this pitfall is to add bounds to inlet flow.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        copy_values_forward(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
        override = [
            (nmpc.controller.conc[t0, 'A'], 1),
            (nmpc.controller.conc[t0, 'B'], 1),
            (nmpc.controller.rate[t0, 'A'], 1),
            (nmpc.controller.rate[t0, 'B'], 1),
            (nmpc.controller.flow_out[t0], 1),
            (nmpc.controller.flow_in[t0], 1),
        ]
        nmpc.add_setpoint_to_controller(objective_weight_override=override)
        nmpc.constrain_control_inputs_piecewise_constant()
        nmpc.solve_control_problem()

        diff_vars = namespace.diff_vars
        input_vars = namespace.input_vars
        sample_time = nmpc.sample_time
        n_samples = namespace.samples_per_horizon
        cat_dict = namespace.category_dict
        categories = [
            VariableCategory.DIFFERENTIAL,
            VariableCategory.INPUT,
        ]
        expected = {
            categ: {
                s: [{
                    t - sample_time: cat_dict[categ][i][t].value
                    for t in time
                    if sample_points[s] < t and t <= sample_points[s + 1]
                } for i in range(len(cat_dict[categ]))]
                for s in range(1, n_samples)
            }
            for categ in categories
        }
        for categ in categories:
            expected[categ][n_samples] = [{
                t: cat_dict[categ].setpoint[i]
                for t in time
                if sample_points[n_samples -
                                 1] < t and t <= sample_points[n_samples]
            } for i in range(len(cat_dict[categ]))]

        nmpc.initialize_from_previous_sample(nmpc.controller)

        for s in range(1, n_samples):
            interval = [
                t for t in time
                if sample_points[s - 1] < t and t <= sample_points[s]
            ]
            for categ in categories:
                for i, var in enumerate(cat_dict[categ]):
                    for t in interval:
                        assert var[t].value == expected[categ][s][i][t]
    def test_add_objective_function(self):
        # populate setpoints,
        # construct weights (with override everywhere)
        # add objective function
        # compare objective function to prediction
        # for diff+error, diff+action, diff+alg+action setpoints
        nmpc = self.make_nmpc()
        time = nmpc.controller_time
        t0 = time.first()
        namespace = getattr(nmpc.controller, nmpc.namespace_name)
        setpoint = [
            (nmpc.controller.flow_in[t0], 3.0),
        ]
        # Note: without this initialization, the setpoint arrives at a
        # local optimum with inlet flow ~= 0.
        nmpc.controller.flow_in[:].set_value(3.0)
        initialize_t0(nmpc.controller)
        copy_values_forward(nmpc.controller)
        nmpc.calculate_full_state_setpoint(setpoint, outlvl=idaeslog.DEBUG)
        override = [
            (nmpc.controller.conc[t0, 'A'], 1),
            (nmpc.controller.conc[t0, 'B'], 1),
            (nmpc.controller.rate[t0, 'A'], 1),
            (nmpc.controller.rate[t0, 'B'], 1),
            (nmpc.controller.flow_out[t0], 1),
            (nmpc.controller.flow_in[t0], 1),
        ]
        nmpc.construct_objective_weights(
            nmpc.controller,
            objective_weight_override=override,
        )
        pred_obj = {i: 0. for i in range(1, 4)}
        sample_points = namespace.sample_points[1:]
        setpoint = namespace.diff_vars.setpoint
        for i, v in enumerate(namespace.diff_vars):
            for t in sample_points:
                pred_obj[1] += (v[t] - setpoint[i])**2
                pred_obj[2] += (v[t] - setpoint[i])**2
                pred_obj[3] += (v[t] - setpoint[i])**2
        setpoint = namespace.alg_vars.setpoint
        for i, v in enumerate(namespace.alg_vars):
            for t in sample_points:
                pred_obj[3] += (v[t] - setpoint[i])**2
        setpoint = namespace.input_vars.setpoint
        for i, v in enumerate(namespace.input_vars):
            for t in sample_points:
                t_prev = t - nmpc.sample_time
                pred_obj[1] += (v[t] - setpoint[i])**2
                pred_obj[2] += (v[t] - v[t_prev])**2
                pred_obj[3] += (v[t] - v[t_prev])**2

        nmpc.add_objective_function(
            nmpc.controller,
            control_penalty_type=ControlPenaltyType.ERROR,
            objective_state_categories=[
                VariableCategory.DIFFERENTIAL,
            ])
        assert aml.value(namespace.objective.expr == pred_obj[1])
        namespace.del_component(namespace.objective)

        nmpc.add_objective_function(
            nmpc.controller,
            control_penalty_type=ControlPenaltyType.ACTION,
            objective_state_categories=[
                VariableCategory.DIFFERENTIAL,
            ])
        assert aml.value(namespace.objective.expr == pred_obj[2])
        namespace.del_component(namespace.objective)

        nmpc.add_objective_function(
            nmpc.controller,
            control_penalty_type=ControlPenaltyType.ACTION,
            objective_state_categories=[
                VariableCategory.DIFFERENTIAL,
                VariableCategory.ALGEBRAIC,
            ])
        assert aml.value(namespace.objective.expr == pred_obj[3])
        namespace.del_component(namespace.objective)
Beispiel #9
0
def test_calculate_full_state_setpoint(nmpc):
    controller = nmpc.controller

    controller.mixer.E_inlet.flow_vol[0].fix(0.1)
    controller.mixer.S_inlet.flow_vol[0].fix(2.0)

    nmpc.solve_consistent_initial_conditions(controller)
    assert nmpc.has_consistent_initial_conditions(controller, tolerance=1e-6)

    # Deactivate tracking objective from previous tests
    #controller._NMPC_NAMESPACE.tracking_objective.deactivate()

    set_point = [(controller.cstr.outlet.conc_mol[0, 'P'], 0.4),
#                 (controller.cstr.outlet.conc_mol[0, 'S'], 0.0),
#                 (controller.cstr.control_volume.energy_holdup[0, 'aq'], 300),
                 (controller.mixer.E_inlet.flow_vol[0], 0.2),
                 (controller.mixer.S_inlet.flow_vol[0], 2.5)]

    weight_tolerance = 5e-7
    weight_override = [
            (controller.mixer.E_inlet.flow_vol[0.], 20.),
            (controller.mixer.S_inlet.flow_vol[0.], 2.),
            (controller.cstr.control_volume.energy_holdup[0., 'aq'], 0.1),
            (controller.cstr.outlet.conc_mol[0., 'P'], 1.),
            (controller.cstr.outlet.conc_mol[0., 'S'], 1.),
            ]
    # FIXME: This steady state setpoint solve is more sensitive than I 
    # would like.

    nmpc.calculate_full_state_setpoint(set_point,
            objective_weight_tolerance=weight_tolerance,
            objective_weight_override=weight_override)

    assert hasattr(controller._NMPC_NAMESPACE, 'user_setpoint')
    user_setpoint = controller._NMPC_NAMESPACE.user_setpoint
    assert hasattr(controller._NMPC_NAMESPACE, 'user_setpoint_weights')
    user_setpoint_weights = controller._NMPC_NAMESPACE.user_setpoint_weights
    assert hasattr(controller._NMPC_NAMESPACE, 'user_setpoint_vars')
    user_setpoint_vars = controller._NMPC_NAMESPACE.user_setpoint_vars

    for i, var in enumerate(user_setpoint_vars):
#        if var.local_name.startswith('conc'):
#            assert user_setpoint_weights[i] == 1.
#        elif var.local_name.startswith('energy'):
#            assert user_setpoint_weights[i] == 0.1
        if var.local_name.startswith('E_'):
            assert user_setpoint_weights[i] == 20.
        elif var.local_name.startswith('S_'):
            assert user_setpoint_weights[i] == 2.

    alg_vars = controller._NMPC_NAMESPACE.alg_vars
    diff_vars = controller._NMPC_NAMESPACE.diff_vars
    input_vars = controller._NMPC_NAMESPACE.input_vars
    categories = [
            VariableCategory.DIFFERENTIAL,
            VariableCategory.ALGEBRAIC,
            VariableCategory.DERIVATIVE,
            VariableCategory.INPUT,
            ]
    category_dict = controller._NMPC_NAMESPACE.category_dict
    for categ in categories:
        group = category_dict[categ]
        # Assert that setpoint has been populated with non-None values
        assert not any([sp is None for sp in group.setpoint])
        # Assert that setpoint (target) and reference (initial) values are
        # different in some way
        assert not all([sp == ref for sp, ref in
            zip(group.setpoint, group.reference)])
        # Assert that initial and reference values are the same
        assert all([ref == var[0].value for ref, var in
            zip(group.reference, group.varlist)])