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