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]
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