Deltares / rtc-tools

The Deltares toolbox for control and optimization of environmental systems.
GNU Lesser General Public License v3.0
0 stars 2 forks source link

GPMixin: trivial constraints created when keep_soft_constraints=False #1110

Open SGeeversAtVortech opened 5 years ago

SGeeversAtVortech commented 5 years ago

In GitLab by @TPiovesan on Mar 7, 2019, 14:22

If a path goal with targets has a timestep for which both the target minimum and target maximum are equal to np.Nan, when soft constraints are transformed into hard constraints the following type of constraint is created: $-\infty \leq f(x) \leq \infty$.

These turns out to be problematic when using CPLEX. Indeed, CasADi passes the constraint $f(x) \leq \infty$ to CPLEX which leads to optimization issues when the barrier method is used.

SGeeversAtVortech commented 5 years ago

In GitLab by @TPiovesan on Mar 7, 2019, 14:26

Snip of code to prevent adding these trivial constraints in CollIntOpt (in version 2.2.2):

        if len(path_constraints) > 0:
            # We need to evaluate the path constraints at t0, as the initial time is not
            # included in the accumulation.
            [initial_path_constraints] = path_constraints_function.call(
                [parameters,
                 ca.vertcat(initial_state, initial_derivatives, initial_constant_inputs, 0.0,
                            initial_path_variables, initial_extra_constant_inputs)],
                False, True)

            lbg_path_constraints = np.empty(
                (len(path_constraints), n_collocation_times))
            ubg_path_constraints = np.empty(
                (len(path_constraints), n_collocation_times))
            for j, path_constraint in enumerate(path_constraints):
                if logger.getEffectiveLevel() == logging.DEBUG:
                    logger.debug(
                        "Adding path constraint {}, {}, {}".format(*path_constraint))

                lb = path_constraint[1]
                if isinstance(lb, ca.MX) and not lb.is_constant():
                    [lb] = ca.substitute(
                        [lb], symbolic_parameters, self.__parameter_values_ensemble_member_0)
                elif isinstance(lb, Timeseries):
                    lb = self.interpolate(
                        collocation_times, lb.times, lb.values, -np.inf, -np.inf)

                ub = path_constraint[2]
                if isinstance(ub, ca.MX) and not ub.is_constant():
                    [ub] = ca.substitute(
                        [ub], symbolic_parameters, self.__parameter_values_ensemble_member_0)
                elif isinstance(ub, Timeseries):
                    ub = self.interpolate(
                        collocation_times, ub.times, ub.values, np.inf, np.inf)

                lbg_path_constraints[j, :] = lb
                ubg_path_constraints[j, :] = ub

            lbg_path_constraints = lbg_path_constraints.transpose().ravel()
            ubg_path_constraints = ubg_path_constraints.transpose().ravel()

            inds_np = ~np.isneginf(lbg_path_constraints) | ~np.isposinf(ubg_path_constraints)
            inds_ca = np.flatnonzero(inds_np).tolist()  # Much faster to slice with list of ints for CasADi

            lbg.extend(lbg_path_constraints[inds_np])
            ubg.extend(ubg_path_constraints[inds_np])

            all_path_constraints = ca.vertcat(initial_path_constraints, discretized_path_constraints)
            g.append(all_path_constraints[inds_ca])
SGeeversAtVortech commented 5 years ago

In GitLab by @vreeken on Oct 25, 2019, 24:29

We should probably not fix this in CollInt, but in GPMixin instead.

EDIT: Because it's all path goal stuff, this can't be fixed in GoalProgrammingMixin. It's also somewhat overkill to always get rid of these constraints, especially thinking about the way SinglePassGoalProgrammingMixin can work (deliberate -np.inf/np.inf constraints!).