def quad_form(self, expr):
        """Extract quadratic, linear constant parts of a quadratic objective.
        """
        # Insert no-op such that root is never a quadratic form, for easier
        # processing
        root = LinOp(NO_OP, expr.shape, [expr], [])

        # Replace quadratic forms with dummy variables.
        quad_forms = replace_quad_forms(root, {})

        # Calculate affine parts and combine them with quadratic forms to get
        # the coefficients.
        coeffs, constant = self.extract_quadratic_coeffs(root.args[0],
                                                         quad_forms)
        # Restore expression.
        restore_quad_forms(root.args[0], quad_forms)

        # Sort variables corresponding to their starting indices, in ascending
        # order.
        offsets = sorted(list(self.id_map.items()), key=operator.itemgetter(1))

        # Concatenate quadratic matrices and vectors
        P = sp.csr_matrix((0, 0))
        q = np.zeros(0)
        for var_id, offset in offsets:
            if var_id in coeffs:
                P = sp.block_diag([P, coeffs[var_id]['P']])
                q = np.concatenate([q, coeffs[var_id]['q']])
            else:
                shape = self.var_shapes[var_id]
                size = np.prod(shape, dtype=int)
                P = sp.block_diag([P, sp.csr_matrix((size, size))])
                q = np.concatenate([q, np.zeros(size)])

        # TODO(akshayka): This chain of != smells of a bug.
        if P.shape[0] != P.shape[1] != self.N or q.shape[0] != self.N:
            raise RuntimeError("Resulting quadratic form does not have "
                               "appropriate dimensions")
        if constant.size != 1:
            raise RuntimeError("Constant must be a scalar")
        return P.tocsr(), q, constant[0]
Beispiel #2
0
    def quad_form(self, expr):
        """Extract quadratic, linear constant parts of a quadratic objective.
        """
        # Insert no-op such that root is never a quadratic form, for easier
        # processing
        root = LinOp(NO_OP, expr.shape, [expr], [])

        # Replace quadratic forms with dummy variables.
        quad_forms = replace_quad_forms(root, {})

        # Calculate affine parts and combine them with quadratic forms to get
        # the coefficients.
        coeffs, constant = self.extract_quadratic_coeffs(
            root.args[0], quad_forms)
        # Restore expression.
        restore_quad_forms(root.args[0], quad_forms)

        # Sort variables corresponding to their starting indices, in ascending
        # order.
        offsets = sorted(self.id_map.items(), key=operator.itemgetter(1))

        # Extract quadratic matrices and vectors
        num_params = constant.shape[1]
        P_list = []
        q_list = []
        P_height = 0
        P_entries = 0
        for var_id, offset in offsets:
            shape = self.var_shapes[var_id]
            size = np.prod(shape, dtype=int)
            if var_id in coeffs and 'P' in coeffs[var_id]:
                P = coeffs[var_id]['P']
                P_entries += P[0].size
            else:
                P = ([], ([], []), (size, size))
            if var_id in coeffs and 'q' in coeffs[var_id]:
                q = coeffs[var_id]['q']
            else:
                q = np.zeros((size, num_params))

            P_list.append(P)
            q_list.append(q)
            P_height += size

        if P_height != self.x_length:
            raise RuntimeError("Resulting quadratic form does not have "
                               "appropriate dimensions")

        # Conceptually we build a block diagonal matrix
        # out of all the Ps, then flatten the first two dimensions.
        # eg P1
        #      P2
        # We do this by extending each P with zero blocks above and below.
        gap_above = np.int64(0)
        acc_height = np.int64(0)
        rows = np.zeros(P_entries)
        cols = np.zeros(P_entries)
        vals = np.zeros(P_entries)
        entry_offset = 0
        for P in P_list:
            """Conceptually, the code is equivalent to
            ```
            above = np.zeros((gap_above, P.shape[1], num_params))
            below = np.zeros((gap_below, P.shape[1], num_params))
            padded_P = np.concatenate([above, P, below], axis=0)
            padded_P = np.reshape(padded_P, (P_height*P.shape[1], num_params),
                                  order='F')
            padded_P_list.append(padded_P)
            ```
            but done by constructing a COO matrix.
            """
            P_vals, (P_rows, P_cols), P_shape = P
            if len(P_vals) > 0:
                vals[entry_offset:entry_offset +
                     P_vals.size] = P_vals.flatten(order='F')
                base_rows = gap_above + acc_height + P_rows + P_cols * P_height
                full_rows = np.tile(base_rows, num_params)
                rows[entry_offset:entry_offset + P_vals.size] = full_rows
                full_cols = np.repeat(np.arange(num_params), P_cols.size)
                cols[entry_offset:entry_offset + P_vals.size] = full_cols
                entry_offset += P_vals.size
            gap_above += P_shape[0]
            acc_height += P_height * np.int64(P_shape[1])

        # Stitch together Ps and qs and constant.
        P = sp.coo_matrix((vals, (rows, cols)), shape=(acc_height, num_params))
        # Stack q with constant offset as last row.
        q = np.vstack(q_list)
        q = np.vstack([q, constant.A])
        q = sp.csr_matrix(q)
        return P, q
    def quad_form(self, expr):
        """Extract quadratic, linear constant parts of a quadratic objective.
        """
        # Insert no-op such that root is never a quadratic form, for easier
        # processing
        root = LinOp(NO_OP, expr.shape, [expr], [])

        # Replace quadratic forms with dummy variables.
        quad_forms = replace_quad_forms(root, {})

        # Calculate affine parts and combine them with quadratic forms to get
        # the coefficients.
        coeff_list, constant = self.extract_quadratic_coeffs(
            root.args[0], quad_forms)
        # Restore expression.
        restore_quad_forms(root.args[0], quad_forms)

        # Sort variables corresponding to their starting indices, in ascending
        # order.
        offsets = sorted(self.id_map.items(), key=operator.itemgetter(1))

        # Get P and q for each parameter.
        P_list = []
        q_list = []
        for coeffs in coeff_list:
            # Concatenate quadratic matrices and vectors
            P = sp.csr_matrix((0, 0))
            q = np.zeros(0)
            for var_id, offset in offsets:
                if var_id in coeffs:
                    P = sp.block_diag([P, coeffs[var_id]['P']])
                    q = np.concatenate([q, coeffs[var_id]['q']])
                else:
                    shape = self.var_shapes[var_id]
                    size = np.prod(shape, dtype=int)
                    P = sp.block_diag([P, sp.csr_matrix((size, size))])
                    q = np.concatenate([q, np.zeros(size)])

            if (P.shape[0] != P.shape[1] and P.shape[1] != self.x_length) or \
                    q.shape[0] != self.x_length:
                raise RuntimeError("Resulting quadratic form does not have "
                                   "appropriate dimensions")
            if not np.isscalar(constant) and constant.size > 1:
                raise RuntimeError("Constant must be a scalar")

            P_size = P.shape[0] * P.shape[1]
            P_list.append(P.reshape((P_size, 1), order='F'))
            q_list.append(q)

        # Here we assemble the Ps and qs into matrices
        # that we multiply by a parameter vector to get P, q
        # i.e. [P1.flatten(), P2.flatten(), ...]
        #      [q1, q2, ...]
        # where Pi, qi are coefficients for the ith entry of
        # the parameter vector.

        # Stitch together Ps and qs and constant.
        P = sp.hstack(P_list)
        # Stack q with constant offset as last row.
        q = np.stack(q_list, axis=1)
        q = sp.vstack([q, constant])
        return P, q