def invert(self, solution, inverse_data): """Returns solution to original problem, given inverse_data. """ status = self.STATUS_MAP[solution['info']['exitFlag']] # Timing data attr = {} attr[s.SOLVE_TIME] = solution["info"]["timing"]["tsolve"] attr[s.SETUP_TIME] = solution["info"]["timing"]["tsetup"] attr[s.NUM_ITERS] = solution["info"]["iter"] if status in s.SOLUTION_PRESENT: primal_val = solution['info']['pcost'] opt_val = primal_val + inverse_data[s.OFFSET] primal_vars = { inverse_data[self.VAR_ID]: intf.DEFAULT_INTF.const_to_matrix(solution['x']) } dual_vars = utilities.get_dual_values( solution['z'], utilities.extract_dual_value, inverse_data[self.NEQ_CONSTR]) for con in inverse_data[self.NEQ_CONSTR]: if isinstance(con, ExpCone): cid = con.id n_cones = con.num_cones() perm = utilities.expcone_permutor(n_cones, ECOS.EXP_CONE_ORDER) dual_vars[cid] = dual_vars[cid][perm] eq_duals = utilities.get_dual_values(solution['y'], utilities.extract_dual_value, inverse_data[self.EQ_CONSTR]) dual_vars.update(eq_duals) return Solution(status, opt_val, primal_vars, dual_vars, attr) else: return failure_solution(status)
def recover_primal_variables(task, sol, K_dir): # This function applies both when slacks are introduced, and # when the problem is dualized. prim_vars = dict() idx = 0 m_free = K_dir[a2d.FREE] if m_free > 0: temp = [0.] * m_free task.getxxslice(sol, idx, len(temp), temp) prim_vars[a2d.FREE] = np.array(temp) idx += m_free if task.getnumintvar() > 0: return prim_vars # Skip the slack variables. m_pos = K_dir[a2d.NONNEG] if m_pos > 0: temp = [0.] * m_pos task.getxxslice(sol, idx, idx + m_pos, temp) prim_vars[a2d.NONNEG] = np.array(temp) idx += m_pos num_soc = len(K_dir[a2d.SOC]) if num_soc > 0: soc_vars = [] for dim in K_dir[a2d.SOC]: temp = [0.] * dim task.getxxslice(sol, idx, idx + dim, temp) soc_vars.append(np.array(temp)) idx += dim prim_vars[a2d.SOC] = soc_vars num_dexp = K_dir[a2d.DUAL_EXP] if num_dexp > 0: temp = [0.] * (3 * num_dexp) task.getxxslice(sol, idx, idx + len(temp), temp) temp = np.array(temp) perm = expcone_permutor(num_dexp, MOSEK.EXP_CONE_ORDER) prim_vars[a2d.DUAL_EXP] = temp[perm] idx += (3 * num_dexp) num_dpow = len(K_dir[a2d.DUAL_POW3D]) if num_dpow > 0: temp = [0.] * (3 * num_dpow) task.getxxslice(sol, idx, idx + len(temp), temp) temp = np.array(temp) prim_vars[a2d.DUAL_POW3D] = temp idx += (3 * num_dpow) num_psd = len(K_dir[a2d.PSD]) if num_psd > 0: psd_vars = [] for j, dim in enumerate(K_dir[a2d.PSD]): xj = [0.] * (dim * (dim + 1) // 2) task.getbarxj(sol, j, xj) psd_vars.append(vectorized_lower_tri_to_mat(xj, dim)) prim_vars[a2d.PSD] = psd_vars return prim_vars
def recover_dual_variables(task, sol, inverse_data): """ A cvxpy Constraint "constr" views itself as affine_expression(z) in K. The "apply(...)" function represents constr as G * z <=_K h for appropriate arrays (G, h). After adding slack variables, constr becomes G * z + s == h, s in K. From "apply(...)" and "solve_via_data(...)", one will find affine_expression(z) == h - G * z == s. As a result, the dual variable suitable for "constr" is the conic dual variable to the constraint "s in K". Mosek documentation refers to conic dual variables as follows: zero cone: 'y' nonnegative orthant: 'suc' second order cone: 'snx' exponential cone: 'snx' PSD cone: 'barsj'. :param task: the mosek task object which was just optimized :param sol: the mosek solution type (usually mosek.soltype.itr, but possibly mosek.soltype.bas if the problem was a linear program and the user requested a basic feasible solution). :param inverse_data: data recorded during "apply(...)". :return: a dictionary mapping a cvxpy constraint object's id to its corresponding dual variables in the current solution. """ dual_vars = dict() # Dual variables for the inequality constraints suc_len = sum(ell for _, ell in inverse_data['suc_slacks']) if suc_len > 0: suc = [0.] * suc_len task.getsucslice(sol, 0, suc_len, suc) dual_vars.update(MOSEK._parse_dual_var_block(suc, inverse_data['suc_slacks'])) # Dual variables for the original equality constraints y_len = sum(ell for _, ell in inverse_data['y_slacks']) if y_len > 0: y = [0.] * y_len task.getyslice(sol, suc_len, suc_len + y_len, y) y = [-val for val in y] dual_vars.update(MOSEK._parse_dual_var_block(y, inverse_data['y_slacks'])) # Dual variables for SOC and EXP constraints snx_len = sum(ell for _, ell in inverse_data['snx_slacks']) if snx_len > 0: snx = np.zeros(snx_len) task.getsnxslice(sol, inverse_data['n0'], inverse_data['n0'] + snx_len, snx) dual_vars.update(MOSEK._parse_dual_var_block(snx, inverse_data['snx_slacks'])) # Dual variables for PSD constraints for j, (id, dim) in enumerate(inverse_data['psd_dims']): sj = [0.] * (dim * (dim + 1) // 2) task.getbarsj(sol, j, sj) dual_vars[id] = vectorized_lower_tri_to_mat(sj, dim) # Now that all dual variables have been recovered, find those corresponding # to the exponential cone, and permute the entries to reflect the CVXPY # standard for the exponential cone. for con in inverse_data['constraints']: if isinstance(con, ExpCone): cid = con.id perm = expcone_permutor(con.num_cones(), MOSEK.EXP_CONE_ORDER) dual_vars[cid] = dual_vars[cid][perm] return dual_vars