Example #1
0
    def test_dpi(self):
        np.random.seed(0)
        for _ in range(10):
            zero_dim = np.random.randint(1, 10)
            pos_dim = np.random.randint(1, 10)
            soc_dim = [
                np.random.randint(1, 10)
                for _ in range(np.random.randint(1, 10))
            ]
            psd_dim = [
                np.random.randint(1, 10)
                for _ in range(np.random.randint(1, 10))
            ]
            exp_dim = np.random.randint(3, 18)
            cones = [(cone_lib.ZERO, zero_dim), (cone_lib.POS, pos_dim),
                     (cone_lib.SOC, soc_dim), (cone_lib.PSD, psd_dim),
                     (cone_lib.EXP, exp_dim), (cone_lib.EXP_DUAL, exp_dim)]
            size = zero_dim + pos_dim + sum(soc_dim) + sum(
                [cone_lib.vec_psd_dim(d) for d in psd_dim]) + 2 * 3 * exp_dim
            x = np.random.randn(size)

            for dual in [False, True]:
                cone_list_cpp = cone_lib.parse_cone_dict_cpp(cones)
                proj_x = cone_lib.pi(x, cones, dual=dual)
                dx = 1e-7 * np.random.randn(size)
                z = cone_lib.pi(x + dx, cones, dual=dual)

                Dpi = _diffcp.dprojection(x, cone_list_cpp, dual)
                np.testing.assert_allclose(Dpi.matvec(dx),
                                           z - proj_x,
                                           atol=1e-6)

                Dpi = _diffcp.dprojection_dense(x, cone_list_cpp, dual)
                np.testing.assert_allclose(Dpi @ dx, z - proj_x, atol=1e-6)
Example #2
0
def solve_and_derivative_internal(A,
                                  b,
                                  c,
                                  cone_dict,
                                  solve_method=None,
                                  warm_start=None,
                                  mode='lsqr',
                                  raise_on_error=True,
                                  **kwargs):
    if mode not in ["dense", "lsqr"]:
        raise ValueError("Unsupported mode {}; the supported modes are "
                         "'dense' and 'lsqr'".format(mode))

    if np.isnan(A.data).any():
        raise RuntimeError("Found a NaN in A.")

    # set explicit 0s in A to np.nan
    A.data[A.data == 0] = np.nan

    # compute rows and cols of nonzeros in A
    rows, cols = A.nonzero()

    # reset np.nan entries in A to 0.0
    A.data[np.isnan(A.data)] = 0.0

    # eliminate explicit zeros in A, we no longer need them
    A.eliminate_zeros()

    if solve_method is None:
        psd_cone = ('s' in cone_dict) and (cone_dict['s'] != [])
        ep_cone = ('ep' in cone_dict) and (cone_dict['ep'] != 0)
        ed_cone = ('ed' in cone_dict) and (cone_dict['ed'] != 0)
        if psd_cone or ep_cone or ed_cone:
            solve_method = "SCS"
        else:
            solve_method = "ECOS"

    if solve_method == "SCS":

        # SCS versions SCS 2.*
        if StrictVersion(scs.__version__) < StrictVersion('3.0.0'):
            if "eps_abs" in kwargs or "eps_rel" in kwargs:
                # Take the min of eps_rel and eps_abs to be eps
                kwargs["eps"] = min(kwargs.get("eps_abs", 1),
                                    kwargs.get("eps_rel", 1))

        # SCS version 3.*
        else:
            if "eps" in kwargs:  # eps replaced by eps_abs, eps_rel
                kwargs["eps_abs"] = kwargs["eps"]
                kwargs["eps_rel"] = kwargs["eps"]
                del kwargs["eps"]

        data = {"A": A, "b": b, "c": c}

        if warm_start is not None:
            data["x"] = warm_start[0]
            data["y"] = warm_start[1]
            data["s"] = warm_start[2]

        kwargs.setdefault("verbose", False)
        result = scs.solve(data, cone_dict, **kwargs)

        status = result["info"]["status"]
        inaccurate_status = {
            "Solved/Inaccurate", "solved (inaccurate - reached max_iters)"
        }
        if status in inaccurate_status and "acceleration_lookback" not in kwargs:
            # anderson acceleration is sometimes unstable
            result = scs.solve(data,
                               cone_dict,
                               acceleration_lookback=0,
                               **kwargs)
            status = result["info"]["status"]

        if status in inaccurate_status:
            warnings.warn("Solved/Inaccurate.")
        elif status.lower() != "solved":
            if raise_on_error:
                raise SolverError("Solver scs returned status %s" % status)
            else:
                result["D"] = None
                result["DT"] = None
                return result

        x = result["x"]
        y = result["y"]
        s = result["s"]
    elif solve_method == "ECOS":
        if warm_start is not None:
            raise ValueError('ECOS does not support warmstart.')
        if ('s' in cone_dict) and (cone_dict['s'] != []):
            raise ValueError("PSD cone not supported by ECOS.")
        if ('ep' in cone_dict) and (cone_dict['ep'] != 0):
            raise NotImplementedError("Exponential cones not supported yet.")
        if ('ed' in cone_dict) and (cone_dict['ed'] != 0):
            raise NotImplementedError("Exponential cones not supported yet.")
        if warm_start is not None:
            raise ValueError("ECOS does not support warm starting.")
        len_eq = cone_dict[cone_lib.EQ_DIM]
        C_ecos = c
        G_ecos = A[len_eq:]
        if 0 in G_ecos.shape:
            G_ecos = None
        H_ecos = b[len_eq:].flatten()
        if 0 in H_ecos.shape:
            H_ecos = None
        A_ecos = A[:len_eq]
        if 0 in A_ecos.shape:
            A_ecos = None
        B_ecos = b[:len_eq].flatten()
        if 0 in B_ecos.shape:
            B_ecos = None

        cone_dict_ecos = {}
        if 'l' in cone_dict:
            cone_dict_ecos['l'] = cone_dict['l']
        if 'q' in cone_dict:
            cone_dict_ecos['q'] = cone_dict['q']
        if A_ecos is not None and A_ecos.nnz == 0 and np.prod(
                A_ecos.shape) > 0:
            raise ValueError("ECOS cannot handle sparse data with nnz == 0.")

        kwargs.setdefault("verbose", False)
        solution = ecos.solve(C_ecos, G_ecos, H_ecos, cone_dict_ecos, A_ecos,
                              B_ecos, **kwargs)
        x = solution["x"]
        y = np.append(solution["y"], solution["z"])
        s = b - A @ x

        result = {"x": x, "y": y, "s": s}
        status = solution["info"]["exitFlag"]
        STATUS_LOOKUP = {
            0: "Optimal",
            1: "Infeasible",
            2: "Unbounded",
            10: "Optimal Inaccurate",
            11: "Infeasible Inaccurate",
            12: "Unbounded Inaccurate"
        }

        if status == 10:
            warnings.warn("Solved/Inaccurate.")
        elif status < 0:
            raise SolverError("Solver ecos errored.")
        if status not in [0, 10]:
            raise SolverError("Solver ecos returned status %s" %
                              STATUS_LOOKUP[status])

        # Convert ECOS info into SCS info to be compatible if called from
        # CVXPY DIFFCP solver
        ECOS2SCS_STATUS_MAP = {
            0: "Solved",
            1: "Infeasible",
            2: "Unbounded",
            10: "Solved/Inaccurate",
            11: "Infeasible/Inaccurate",
            12: "Unbounded/Inaccurate"
        }
        result['info'] = {
            'status': ECOS2SCS_STATUS_MAP.get(status, "Failure"),
            'solveTime': solution['info']['timing']['tsolve'],
            'setupTime': solution['info']['timing']['tsetup'],
            'iter': solution['info']['iter'],
            'pobj': solution['info']['pcost']
        }

    else:
        raise ValueError("Solver %s not supported." % solve_method)

    # pre-compute quantities for the derivative
    m, n = A.shape
    N = m + n + 1
    cones = cone_lib.parse_cone_dict(cone_dict)
    cones_parsed = cone_lib.parse_cone_dict_cpp(cones)
    z = (x, y - s, np.array([1]))
    u, v, w = z

    Q = sparse.bmat(
        [[None, A.T, np.expand_dims(c, -1)], [-A, None,
                                              np.expand_dims(b, -1)],
         [-np.expand_dims(c, -1).T, -np.expand_dims(b, -1).T, None]])

    D_proj_dual_cone = _diffcp.dprojection(v, cones_parsed, True)
    if mode == "dense":
        Q_dense = Q.todense()
        M = _diffcp.M_dense(Q_dense, cones_parsed, u, v, w)
        MT = M.T
    else:
        M = _diffcp.M_operator(Q, cones_parsed, u, v, w)
        MT = M.transpose()

    pi_z = pi(z, cones)

    def derivative(dA, db, dc, **kwargs):
        """Applies derivative at (A, b, c) to perturbations dA, db, dc
        Args:
            dA: SciPy sparse matrix in CSC format; must have same sparsity
                pattern as the matrix `A` from the cone program
            db: NumPy array representing perturbation in `b`
            dc: NumPy array representing perturbation in `c`
        Returns:
           NumPy arrays dx, dy, ds, the result of applying the derivative
           to the perturbations.
        """
        dQ = sparse.bmat(
            [[None, dA.T, np.expand_dims(dc, -1)],
             [-dA, None, np.expand_dims(db, -1)],
             [-np.expand_dims(dc, -1).T, -np.expand_dims(db, -1).T, None]])
        rhs = dQ @ pi_z
        if np.allclose(rhs, 0):
            dz = np.zeros(rhs.size)
        elif mode == "dense":
            dz = _diffcp._solve_derivative_dense(M, MT, rhs)
        else:
            dz = _diffcp.lsqr(M, rhs).solution

        du, dv, dw = np.split(dz, [n, n + m])
        dx = du - x * dw
        dy = D_proj_dual_cone.matvec(dv) - y * dw
        ds = D_proj_dual_cone.matvec(dv) - dv - s * dw
        return -dx, -dy, -ds

    def adjoint_derivative(dx, dy, ds, **kwargs):
        """Applies adjoint of derivative at (A, b, c) to perturbations dx, dy, ds
        Args:
            dx: NumPy array representing perturbation in `x`
            dy: NumPy array representing perturbation in `y`
            ds: NumPy array representing perturbation in `s`
        Returns:
            (`dA`, `db`, `dc`), the result of applying the adjoint to the
            perturbations; the sparsity pattern of `dA` matches that of `A`.
        """
        dw = -(x @ dx + y @ dy + s @ ds)
        dz = np.concatenate(
            [dx, D_proj_dual_cone.rmatvec(dy + ds) - ds,
             np.array([dw])])

        if np.allclose(dz, 0):
            r = np.zeros(dz.shape)
        elif mode == "dense":
            r = _diffcp._solve_adjoint_derivative_dense(M, MT, dz)
        else:
            r = _diffcp.lsqr(MT, dz).solution

        values = pi_z[cols] * r[rows + n] - pi_z[n + rows] * r[cols]
        dA = sparse.csc_matrix((values, (rows, cols)), shape=A.shape)
        db = pi_z[n:n + m] * r[-1] - pi_z[-1] * r[n:n + m]
        dc = pi_z[:n] * r[-1] - pi_z[-1] * r[:n]

        return dA, db, dc

    result["D"] = derivative
    result["DT"] = adjoint_derivative
    return result
Example #3
0
def solve_and_derivative_internal(A,
                                  b,
                                  c,
                                  cone_dict,
                                  warm_start=None,
                                  mode='lsqr',
                                  raise_on_error=True,
                                  **kwargs):
    if mode not in ["dense", "lsqr"]:
        raise ValueError("Unsupported mode {}; the supported modes are "
                         "'dense' and 'lsqr'".format(mode))

    if np.isnan(A.data).any():
        raise RuntimeError("Found a NaN in A.")

    # set explicit 0s in A to np.nan
    A.data[A.data == 0] = np.nan

    # compute rows and cols of nonzeros in A
    rows, cols = A.nonzero()

    # reset np.nan entries in A to 0.0
    A.data[np.isnan(A.data)] = 0.0

    # eliminate explicit zeros in A, we no longer need them
    A.eliminate_zeros()

    data = {"A": A, "b": b, "c": c}

    if warm_start is not None:
        data["x"] = warm_start[0]
        data["y"] = warm_start[1]
        data["s"] = warm_start[2]

    kwargs.setdefault("verbose", False)
    result = scs.solve(data, cone_dict, **kwargs)

    status = result["info"]["status"]
    if status == "Solved/Inaccurate" and "acceleration_lookback" not in kwargs:
        # anderson acceleration is sometimes unstable
        result = scs.solve(data, cone_dict, acceleration_lookback=0, **kwargs)
        status = result["info"]["status"]

    if status == "Solved/Inaccurate":
        warnings.warn("Solved/Inaccurate.")
    elif status != "Solved":
        if raise_on_error:
            raise SolverError("Solver scs returned status %s" % status)
        else:
            result["D"] = None
            result["DT"] = None
            return result

    x = result["x"]
    y = result["y"]
    s = result["s"]

    # pre-compute quantities for the derivative
    m, n = A.shape
    N = m + n + 1
    cones = cone_lib.parse_cone_dict(cone_dict)
    cones_parsed = cone_lib.parse_cone_dict_cpp(cones)
    z = (x, y - s, np.array([1]))
    u, v, w = z

    Q = sparse.bmat(
        [[None, A.T, np.expand_dims(c, -1)], [-A, None,
                                              np.expand_dims(b, -1)],
         [-np.expand_dims(c, -1).T, -np.expand_dims(b, -1).T, None]])

    D_proj_dual_cone = _diffcp.dprojection(v, cones_parsed, True)
    if mode == "dense":
        Q_dense = Q.todense()
        M = _diffcp.M_dense(Q_dense, cones_parsed, u, v, w)
        MT = M.T
    else:
        M = _diffcp.M_operator(Q, cones_parsed, u, v, w)
        MT = M.transpose()

    pi_z = pi(z, cones)

    def derivative(dA, db, dc, **kwargs):
        """Applies derivative at (A, b, c) to perturbations dA, db, dc
        Args:
            dA: SciPy sparse matrix in CSC format; must have same sparsity
                pattern as the matrix `A` from the cone program
            db: NumPy array representing perturbation in `b`
            dc: NumPy array representing perturbation in `c`
        Returns:
           NumPy arrays dx, dy, ds, the result of applying the derivative
           to the perturbations.
        """
        dQ = sparse.bmat(
            [[None, dA.T, np.expand_dims(dc, -1)],
             [-dA, None, np.expand_dims(db, -1)],
             [-np.expand_dims(dc, -1).T, -np.expand_dims(db, -1).T, None]])
        rhs = dQ @ pi_z
        if np.allclose(rhs, 0):
            dz = np.zeros(rhs.size)
        elif mode == "dense":
            dz = _diffcp._solve_derivative_dense(M, MT, rhs)
        else:
            dz = _diffcp.lsqr(M, rhs).solution

        du, dv, dw = np.split(dz, [n, n + m])
        dx = du - x * dw
        dy = D_proj_dual_cone.matvec(dv) - y * dw
        ds = D_proj_dual_cone.matvec(dv) - dv - s * dw
        return -dx, -dy, -ds

    def adjoint_derivative(dx, dy, ds, **kwargs):
        """Applies adjoint of derivative at (A, b, c) to perturbations dx, dy, ds
        Args:
            dx: NumPy array representing perturbation in `x`
            dy: NumPy array representing perturbation in `y`
            ds: NumPy array representing perturbation in `s`
        Returns:
            (`dA`, `db`, `dc`), the result of applying the adjoint to the
            perturbations; the sparsity pattern of `dA` matches that of `A`.
        """
        dw = -(x @ dx + y @ dy + s @ ds)
        dz = np.concatenate(
            [dx, D_proj_dual_cone.rmatvec(dy + ds) - ds,
             np.array([dw])])

        if np.allclose(dz, 0):
            r = np.zeros(dz.shape)
        elif mode == "dense":
            r = _diffcp._solve_adjoint_derivative_dense(M, MT, dz)
        else:
            r = _diffcp.lsqr(MT, dz).solution

        values = pi_z[cols] * r[rows + n] - pi_z[n + rows] * r[cols]
        dA = sparse.csc_matrix((values, (rows, cols)), shape=A.shape)
        db = pi_z[n:n + m] * r[-1] - pi_z[-1] * r[n:n + m]
        dc = pi_z[:n] * r[-1] - pi_z[-1] * r[:n]

        return dA, db, dc

    result["D"] = derivative
    result["DT"] = adjoint_derivative
    return result