Exemplo n.º 1
0
    def affine(self, expr):
        """Extract problem data tensor from an expression that is reducible to
        A*x + b.

        Applying the tensor to a flattened parameter vector and reshaping
        will recover A and b (see the helpers in canonInterface).

        Parameters
        ----------
        expr : Expression or list of Expressions.
            The expression(s) to process.

        Returns
        -------
        SciPy CSR matrix
            Problem data tensor, of shape
            (constraint length * (variable length + 1), parameter length + 1)
        """
        if isinstance(expr, list):
            expr_list = expr
        else:
            expr_list = [expr]
        assert all([e.is_dpp() for e in expr_list])
        num_rows = sum([e.size for e in expr_list])
        op_list = [e.canonical_form[0] for e in expr_list]
        return canonInterface.get_problem_matrix(op_list, self.x_length,
                                                 self.id_map,
                                                 self.param_to_size,
                                                 self.param_id_map, num_rows)
Exemplo n.º 2
0
    def _grad(self, values):
        """Gives the (sub/super)gradient of the atom w.r.t. each argument.

        Matrix expressions are vectorized, so the gradient is a matrix.

        Args:
            values: A list of numeric values for the arguments.

        Returns:
            A list of SciPy CSC sparse matrices or None.
        """
        # TODO should be a simple function in cvxcore for this.
        # Make a fake lin op tree for the function.
        fake_args = []
        var_offsets = {}
        offset = 0
        for idx, arg in enumerate(self.args):
            if arg.is_constant():
                fake_args += [Constant(arg.value).canonical_form[0]]
            else:
                fake_args += [lu.create_var(arg.shape, idx)]
                var_offsets[idx] = offset
                offset += arg.size
        var_length = offset
        fake_expr, _ = self.graph_implementation(fake_args, self.shape,
                                                 self.get_data())
        param_to_size = {lo.CONSTANT_ID: 1}
        param_to_col = {lo.CONSTANT_ID: 0}
        # Get the matrix representation of the function.
        canon_mat = canonInterface.get_problem_matrix(
            [fake_expr],
            var_length,
            var_offsets,
            param_to_size,
            param_to_col,
            self.size,
        )
        # HACK TODO TODO convert tensors back to vectors.
        # COO = (V[lo.CONSTANT_ID][0], (J[lo.CONSTANT_ID][0], I[lo.CONSTANT_ID][0]))
        shape = (var_length + 1, self.size)
        stacked_grad = canon_mat.reshape(shape).tocsc()[:-1, :]
        # Break up into per argument matrices.
        grad_list = []
        start = 0
        for arg in self.args:
            if arg.is_constant():
                grad_shape = (arg.size, shape[1])
                if grad_shape == (1, 1):
                    grad_list += [0]
                else:
                    grad_list += [sp.coo_matrix(grad_shape, dtype='float64')]
            else:
                stop = start + arg.size
                grad_list += [stacked_grad[start:stop, :]]
                start = stop
        return grad_list
Exemplo n.º 3
0
    def presolve(objective, constr_map):
        """Eliminates unnecessary constraints and short circuits the solver
        if possible.

        Parameters
        ----------
        objective : LinOp
            The canonicalized objective.
        constr_map : dict
            A map of constraint type to a list of constraints.

        Returns
        -------
        bool
            Is the problem infeasible?
        """
        # Remove redundant constraints.
        for key, constraints in constr_map.items():
            ids = set()
            uniq_constr = []
            for c in constraints:
                if c.constr_id not in ids:
                    uniq_constr.append(c)
                    ids.add(c.constr_id)
            constr_map[key] = uniq_constr

        # If there are no constraints, the problem is unbounded
        # if any of the coefficients are non-zero.
        # If all the coefficients are zero then return the constant term
        # and set all variables to 0.
        if not any(constr_map.values()):
            str(objective)  # TODO

        # Remove constraints with no variables or parameters.
        for key in [s.EQ, s.LEQ]:
            new_constraints = []
            for constr in constr_map[key]:
                vars_ = lu.get_expr_vars(constr.expr)
                if len(vars_) == 0 and not lu.get_expr_params(constr.expr):
                    V, I, J, coeff = canonInterface.get_problem_matrix(
                        [constr])
                    is_pos, is_neg = intf.sign(coeff)
                    # For equality constraint, coeff must be zero.
                    # For inequality (i.e. <= 0) constraint,
                    # coeff must be negative.
                    if key == s.EQ and not (is_pos and is_neg) or \
                            key == s.LEQ and not is_neg:
                        return s.INFEASIBLE
                else:
                    new_constraints.append(constr)
            constr_map[key] = new_constraints

        return None
Exemplo n.º 4
0
    def _grad(self, values):
        """Gives the (sub/super)gradient of the atom w.r.t. each argument.

        Matrix expressions are vectorized, so the gradient is a matrix.

        Args:
            values: A list of numeric values for the arguments.

        Returns:
            A list of SciPy CSC sparse matrices or None.
        """
        # TODO should be a simple function in cvxcore for this.
        # Make a fake lin op tree for the function.
        fake_args = []
        var_offsets = {}
        offset = 0
        for idx, arg in enumerate(self.args):
            if arg.is_constant():
                fake_args += [Constant(arg.value).canonical_form[0]]
            else:
                fake_args += [lu.create_var(arg.shape, idx)]
                var_offsets[idx] = offset
                offset += arg.size
        fake_expr, _ = self.graph_implementation(fake_args, self.shape,
                                                 self.get_data())
        # Get the matrix representation of the function.
        V, I, J, _ = canonInterface.get_problem_matrix(
            [fake_expr],
            var_offsets,
            None
        )
        shape = (offset, self.size)
        stacked_grad = sp.csc_matrix((V, (J, I)), shape=shape)
        # Break up into per argument matrices.
        grad_list = []
        start = 0
        for arg in self.args:
            if arg.is_constant():
                grad_shape = (arg.size, shape[1])
                if grad_shape == (1, 1):
                    grad_list += [0]
                else:
                    grad_list += [sp.coo_matrix(grad_shape, dtype='float64')]
            else:
                stop = start + arg.size
                grad_list += [stacked_grad[start:stop, :]]
                start = stop
        return grad_list
Exemplo n.º 5
0
    def _lin_matrix(self, mat_cache, caching=False):
        """Computes a matrix and vector representing a list of constraints.

        In the matrix, each constraint is given a block of rows.
        Each variable coefficient is inserted as a block with upper
        left corner at matrix[variable offset, constraint offset].
        The constant term in the constraint is added to the vector.

        Parameters
        ----------
        mat_cache : MatrixCache
            The cached version of the matrix-vector pair.
        caching : bool
            Is the data being cached?
        """
        active_constr = []
        constr_offsets = []
        vert_offset = 0
        for constr in mat_cache.constraints:
            # Process the constraint if it has a parameter and not caching
            # or it doesn't have a parameter and caching.
            has_param = len(lu.get_expr_params(constr.expr)) > 0
            if (has_param and not caching) or (not has_param and caching):
                # If parameterized, convert the parameters into constant nodes.
                if has_param:
                    constr = lu.copy_constr(constr,
                                            lu.replace_params_with_consts)
                active_constr.append(constr)
                constr_offsets.append(vert_offset)
            vert_offset += np.prod(constr.shape, dtype=int)
        # Convert the constraints into a matrix and vector offset
        # and add them to the matrix cache.
        if len(active_constr) > 0:
            V, I, J, const_vec = canonInterface.get_problem_matrix(
                active_constr,
                self.sym_data.var_offsets,
                constr_offsets
            )
            # Convert the constant offset to the correct data type.
            conv_vec = self.vec_intf.const_to_matrix(const_vec,
                                                     convert_scalars=True)
            mat_cache.const_vec[:const_vec.size] += conv_vec
            for i, vals in enumerate([V, I, J]):
                mat_cache.coo_tup[i].extend(vals)
Exemplo n.º 6
0
    def affine(self, expr):
        """Extract A, b from an expression that is reducable to A*x + b.

        Parameters
        ----------
        expr : Expression
            The expression to process.

        Returns
        -------
        SciPy CSR matrix
            The coefficient matrix A of shape (np.prod(expr.shape), self.N).
        NumPy.ndarray
            The offset vector b of shape (np.prod(expr.shape,)).
        """
        if not expr.is_affine():
            raise ValueError("Expression is not affine")
        s, _ = expr.canonical_form
        V, I, J, b = canonInterface.get_problem_matrix([lu.create_eq(s)],
                                                       self.id_map)
        A = sp.csr_matrix((V, (I, J)), shape=(expr.size, self.N))
        return A, b.flatten()
Exemplo n.º 7
0
    def affine(self, expr):
        """Extract A, b from an expression that is reducable to A*x + b.

        Parameters
        ----------
        expr : Expression or list of Expressions.
            The expression(s) to process.

        Returns
        -------
        SciPy CSR matrix
            The coefficient matrix A of shape (np.prod(expr.shape), self.N).
        NumPy.ndarray
            The offset vector b of shape (np.prod(expr.shape,)).
        """
        if isinstance(expr, list):
            expr_list = expr
        else:
            expr_list = [expr]
        size = sum([e.size for e in expr_list])
        op_list = [e.canonical_form[0] for e in expr_list]
        V, I, J, b = canonInterface.get_problem_matrix(op_list, self.id_map)
        A = sp.csr_matrix((V, (I, J)), shape=(size, self.N))
        return A, b.flatten()
Exemplo n.º 8
0
    def extract_quadratic_coeffs(self, affine_expr, quad_forms):
        """ Assumes quadratic forms all have variable arguments.
            Affine expressions can be anything.
        """
        assert affine_expr.is_dpp()
        # Here we take the problem objective, replace all the SymbolicQuadForm
        # atoms with variables of the same dimensions.
        # We then apply the canonInterface to reduce the "affine head"
        # of the expression tree to a coefficient vector c and constant offset d.
        # Because the expression is parameterized, we extend that to a matrix
        # [c1 c2 ...]
        # [d1 d2 ...]
        # where ci,di are the vector and constant for the ith parameter.
        affine_id_map, affine_offsets, x_length, affine_var_shapes = \
            InverseData.get_var_offsets(affine_expr.variables())
        op_list = [affine_expr.canonical_form[0]]
        param_coeffs = canonInterface.get_problem_matrix(
            op_list, x_length, affine_offsets, self.param_to_size,
            self.param_id_map, affine_expr.size)

        # Iterates over every entry of the parameters vector,
        # and obtains the Pi and qi for that entry i.
        # These are then combined into matrices [P1.flatten(), P2.flatten(), ...]
        # and [q1, q2, ...]
        constant = param_coeffs[-1, :]
        c = param_coeffs[:-1, :].A

        # coeffs stores the P and q for each quad_form,
        # as well as for true variable nodes in the objective.
        coeffs = {}
        # The goal of this loop is to appropriately multiply
        # the matrix P of each quadratic term by the coefficients
        # in param_coeffs. Later we combine all the quadratic terms
        # to form a single matrix P.
        for var in affine_expr.variables():
            # quad_forms maps the ids of the SymbolicQuadForm atoms
            # in the objective to (modified parent node of quad form,
            #                      argument index of quad form,
            #                      quad form atom)
            if var.id in quad_forms:
                var_id = var.id
                orig_id = quad_forms[var_id][2].args[0].id
                var_offset = affine_id_map[var_id][0]
                var_size = affine_id_map[var_id][1]
                c_part = c[var_offset:var_offset + var_size, :]
                if quad_forms[var_id][2].P.value is not None:
                    # Convert to sparse matrix.
                    P = quad_forms[var_id][2].P.value
                    if sp.issparse(P) and not isinstance(P, sp.coo_matrix):
                        P = P.tocoo()
                    else:
                        P = sp.coo_matrix(P)
                    if var_size == 1:
                        c_part = np.ones((P.shape[0], 1)) * c_part
                else:
                    P = sp.eye(var_size, format='coo')
                # We multiply the columns of P, by c_part
                # by operating directly on the data.
                data = P.data[:, None] * c_part[P.col]
                P_tup = (data, (P.row, P.col), P.shape)
                # Conceptually similar to
                # P = P[:, :, None] * c_part[None, :, :]
                if orig_id in coeffs:
                    if 'P' in coeffs[orig_id]:
                        # Concatenation becomes addition when constructing
                        # COO matrix because repeated indices are summed.
                        # Conceptually equivalent to
                        # coeffs[orig_id]['P'] += P_tup
                        acc_data, (acc_row, acc_col), _ = coeffs[orig_id]['P']
                        acc_data = np.concatenate([acc_data, data], axis=0)
                        acc_row = np.concatenate([acc_row, P.row], axis=0)
                        acc_col = np.concatenate([acc_col, P.col], axis=0)
                        P_tup = (acc_data, (acc_row, acc_col), P.shape)
                        coeffs[orig_id]['P'] = P_tup
                    else:
                        coeffs[orig_id]['P'] = P_tup
                else:
                    coeffs[orig_id] = dict()
                    coeffs[orig_id]['P'] = P_tup
                    coeffs[orig_id]['q'] = np.zeros((P.shape[0], c.shape[1]))
            else:
                var_offset = affine_id_map[var.id][0]
                var_size = np.prod(affine_var_shapes[var.id], dtype=int)
                if var.id in coeffs:
                    coeffs[var.id]['q'] += c[var_offset:var_offset +
                                             var_size, :]
                else:
                    coeffs[var.id] = dict()
                    coeffs[var.id]['q'] = c[var_offset:var_offset +
                                            var_size, :]
        return coeffs, constant
Exemplo n.º 9
0
    def extract_quadratic_coeffs(self, affine_expr, quad_forms):
        """ Assumes quadratic forms all have variable arguments.
            Affine expressions can be anything.
        """
        assert affine_expr.is_dpp()
        # Here we take the problem objective, replace all the SymbolicQuadForm
        # atoms with variables of the same dimensions.
        # We then apply the canonInterface to reduce the "affine head"
        # of the expression tree to a coefficient vector c and constant offset d.
        # Because the expression is parameterized, we extend that to a matrix
        # [c1 c2 ...]
        # [d1 d2 ...]
        # where ci,di are the vector and constant for the ith parameter.
        affine_id_map, affine_offsets, x_length, affine_var_shapes = \
            InverseData.get_var_offsets(affine_expr.variables())
        op_list = [affine_expr.canonical_form[0]]
        param_coeffs = canonInterface.get_problem_matrix(
            op_list, x_length, affine_offsets, self.param_to_size,
            self.param_id_map, affine_expr.size)

        # TODO vectorize this code.
        # Iterates over every entry of the parameters vector,
        # and obtains the Pi and qi for that entry i.
        # These are then combined into matrices [P1.flatten(), P2.flatten(), ...]
        # and [q1, q2, ...]
        coeff_list = []
        constant = param_coeffs[-1, :]
        for p in range(param_coeffs.shape[1]):
            c = param_coeffs[:-1, p].A.flatten()

            # coeffs stores the P and q for each quad_form,
            # as well as for true variable nodes in the objective.
            coeffs = {}
            # The goal of this loop is to appropriately multiply
            # the matrix P of each quadratic term by the coefficients
            # in param_coeffs. Later we combine all the quadratic terms
            # to form a single matrix P.
            for var in affine_expr.variables():
                # quad_forms maps the ids of the SymbolicQuadForm atoms
                # in the objective to (modified parent node of quad form,
                #                      argument index of quad form,
                #                      quad form atom)
                if var.id in quad_forms:
                    var_id = var.id
                    orig_id = quad_forms[var_id][2].args[0].id
                    var_offset = affine_id_map[var_id][0]
                    var_size = affine_id_map[var_id][1]
                    c_part = c[var_offset:var_offset + var_size]
                    if quad_forms[var_id][2].P.value is not None:
                        P = quad_forms[var_id][2].P.value
                        if c_part.size == 1:
                            P = c_part[0] * P
                        else:
                            P = P @ sp.diags(c_part)
                    else:
                        P = sp.diags(c_part)
                    if orig_id in coeffs:
                        coeffs[orig_id]['P'] += P
                    else:
                        coeffs[orig_id] = dict()
                        coeffs[orig_id]['P'] = P
                        coeffs[orig_id]['q'] = np.zeros(P.shape[0])
                else:
                    var_offset = affine_id_map[var.id][0]
                    var_size = np.prod(affine_var_shapes[var.id], dtype=int)
                    if var.id in coeffs:
                        coeffs[var.id]['P'] += sp.csr_matrix(
                            (var_size, var_size))
                        coeffs[var.id]['q'] += c[var_offset:var_offset +
                                                 var_size]
                    else:
                        coeffs[var.id] = dict()
                        coeffs[var.id]['P'] = sp.csr_matrix(
                            (var_size, var_size))
                        coeffs[var.id]['q'] = c[var_offset:var_offset +
                                                var_size]
            coeff_list.append(coeffs)
        return coeff_list, constant