Exemple #1
0
    def _canonicalize_constraint(self, constr):
        """Recursively canonicalize a constraint."""
        lhs = constr.args[0]
        rhs = constr.args[1]

        if isinstance(constr, Inequality):
            lhs_val = np.array(lhs.value)
            rhs_val = np.array(rhs.value)
            if np.all(lhs_val == -np.inf) or np.all(rhs_val == np.inf):
                # constraint is redundant
                return [None]
            elif np.any(lhs_val == np.inf) or np.any(rhs_val == -np.inf):
                return [s.INFEASIBLE]

        if constr.is_dcp():
            canon_constr, aux_constr = self.canonicalize_tree(constr)
            return [canon_constr] + aux_constr

        # canonicalize lhs <= rhs
        # either lhs or rhs is quasiconvex (and not convex)
        assert isinstance(constr, Inequality)

        # short-circuit zero-valued expressions to simplify inverse logic
        if lhs.is_zero():
            return self._canonicalize_constraint(0 <= rhs)
        if rhs.is_zero():
            return self._canonicalize_constraint(lhs <= 0)

        if lhs.is_quasiconvex() and not lhs.is_convex():
            # quasiconvex <= constant
            assert rhs.is_constant(), rhs
            if inverse.invertible(lhs):
                # Apply inverse to both sides of constraint.
                rhs = inverse.inverse(lhs)(rhs)
                idx = lhs._non_const_idx()[0]
                expr = lhs.args[idx]
                if lhs.is_incr(idx):
                    return self._canonicalize_constraint(expr <= rhs)
                return self._canonicalize_constraint(expr >= rhs)
            elif isinstance(lhs, (maximum, max_atom)):
                # Lower maximum.
                return [
                    c for arg in lhs.args
                    for c in self._canonicalize_constraint(arg <= rhs)
                ]
            else:
                # Replace quasiconvex atom with a sublevel set.
                canon_args, aux_args_constr = self._canon_args(lhs)
                sublevel_set = sets.sublevel(lhs.copy(canon_args), t=rhs)
                return sublevel_set + aux_args_constr

        # constant <= quasiconcave
        assert rhs.is_quasiconcave()
        assert lhs.is_constant()
        if inverse.invertible(rhs):
            # Apply inverse to both sides of constraint.
            lhs = inverse.inverse(rhs)(lhs)
            idx = rhs._non_const_idx()[0]
            expr = rhs.args[idx]
            if rhs.is_incr(idx):
                return self._canonicalize_constraint(lhs <= expr)
            return self._canonicalize_constraint(lhs >= expr)
        elif isinstance(rhs, (minimum, min_atom)):
            # Lower minimum.
            return [
                c for arg in rhs.args
                for c in self._canonicalize_constraint(lhs <= arg)
            ]
        else:
            # Replace quasiconcave atom with a superlevel set.
            canon_args, aux_args_constr = self._canon_args(rhs)
            superlevel_set = sets.superlevel(rhs.copy(canon_args), t=lhs)
            return superlevel_set + aux_args_constr
Exemple #2
0
    def _canonicalize_constraint(self, constr):
        """Recursively canonicalize a constraint.

        The DQCP grammar has expresions of the form

            INCR* QCVX DCP

        and

            DECR* QCCV DCP

        ie, zero or more real/scalar increasing (or decreasing) atoms, composed
        with a quasiconvex (or quasiconcave) atom, composed with DCP
        expressions.

        The monotone functions are inverted by applying their inverses to
        both sides of a constraint. The QCVX (QCCV) atom is lowered by
        replacing it with its sublevel (superlevel) set. The DCP
        expressions are canonicalized via graph implementations.
        """
        lhs = constr.args[0]
        rhs = constr.args[1]

        if isinstance(constr, Inequality):
            # taking inverses can yield +/- infinity; this is handled here.
            lhs_val = np.array(lhs.value)
            rhs_val = np.array(rhs.value)
            if np.all(lhs_val == -np.inf) or np.all(rhs_val == np.inf):
                # constraint is redundant
                return [True]
            elif np.any(lhs_val == np.inf) or np.any(rhs_val == -np.inf):
                # constraint is infeasible
                return [False]

        if constr.is_dcp():
            canon_constr, aux_constr = self.canonicalize_tree(constr)
            return [canon_constr] + aux_constr

        # canonicalize lhs <= rhs
        # either lhs or rhs is quasiconvex (and not convex)
        assert isinstance(constr, Inequality)

        # short-circuit zero-valued expressions to simplify inverse logic
        if lhs.is_zero():
            return self._canonicalize_constraint(0 <= rhs)
        if rhs.is_zero():
            return self._canonicalize_constraint(lhs <= 0)

        if lhs.is_quasiconvex() and not lhs.is_convex():
            # quasiconvex <= constant
            assert rhs.is_constant(), rhs
            if inverse.invertible(lhs):
                # Apply inverse to both sides of constraint.
                rhs = inverse.inverse(lhs)(rhs)
                idx = lhs._non_const_idx()[0]
                expr = lhs.args[idx]
                if lhs.is_incr(idx):
                    return self._canonicalize_constraint(expr <= rhs)
                assert lhs.is_decr(idx)
                return self._canonicalize_constraint(expr >= rhs)
            elif isinstance(lhs, (maximum, max_atom)):
                # Lower maximum.
                return [
                    c for arg in lhs.args
                    for c in self._canonicalize_constraint(arg <= rhs)
                ]
            else:
                # Replace quasiconvex atom with a sublevel set.
                canon_args, aux_args_constr = self._canon_args(lhs)
                sublevel_set = sets.sublevel(lhs.copy(canon_args), t=rhs)
                return sublevel_set + aux_args_constr

        # constant <= quasiconcave
        assert rhs.is_quasiconcave()
        assert lhs.is_constant()
        if inverse.invertible(rhs):
            # Apply inverse to both sides of constraint.
            lhs = inverse.inverse(rhs)(lhs)
            idx = rhs._non_const_idx()[0]
            expr = rhs.args[idx]
            if rhs.is_incr(idx):
                return self._canonicalize_constraint(lhs <= expr)
            assert rhs.is_decr(idx)
            return self._canonicalize_constraint(lhs >= expr)
        elif isinstance(rhs, (minimum, min_atom)):
            # Lower minimum.
            return [
                c for arg in rhs.args
                for c in self._canonicalize_constraint(lhs <= arg)
            ]
        else:
            # Replace quasiconcave atom with a superlevel set.
            canon_args, aux_args_constr = self._canon_args(rhs)
            superlevel_set = sets.superlevel(rhs.copy(canon_args), t=lhs)
            return superlevel_set + aux_args_constr