Example #1
0
def test_gurobipy_return_same_result_as_scipy():
    c, A, b = example_1d()
    result_gurobi = solvers.lpsolve(c, A, b, solver='gurobi')
    result_scipy = solvers.lpsolve(c, A, b, solver='scipy')
    nt.assert_equal(result_scipy['status'], result_gurobi['status'])
    nt.assert_almost_equal(result_scipy['x'][0], result_gurobi['x'][0])
    nt.assert_almost_equal(result_scipy['fun'], result_gurobi['fun'])
Example #2
0
def test_request_glpk_after_changing_default_to_scipy():
    c, A, b = example_1d()
    have_glpk = is_glpk_present()
    if not have_glpk:
        return
    assert solvers.default_solver != 'scipy'
    solvers.default_solver = 'scipy'
    solvers.lpsolve(c, A, b, solver='glpk')
Example #3
0
def test_request_glpk_after_changing_default_to_scipy():
    c, A, b = example_1d()
    have_glpk = is_glpk_present()
    if not have_glpk:
        return
    assert solvers.default_solver != 'scipy'
    solvers.default_solver = 'scipy'
    solvers.lpsolve(c, A, b, solver='glpk')
Example #4
0
def test_lpsolve_solver_selection_glpk_absent():
    c, A, b = example_1d()
    have_glpk = is_glpk_present()
    # skip if GLPK imports
    if have_glpk:
        log.info('Skipping GLPK failure test, ' 'because GLPK is present.')
        return
    with nt.assert_raises(RuntimeError):
        solvers.lpsolve(c, A, b, solver='glpk')
Example #5
0
def test_lpsolve_solver_selection_glpk_absent():
    c, A, b = example_1d()
    have_glpk = is_glpk_present()
    # skip if GLPK imports
    if have_glpk:
        log.info(
            'Skipping GLPK failure test, '
            'because GLPK is present.')
        return
    with nt.assert_raises(RuntimeError):
        solvers.lpsolve(c, A, b, solver='glpk')
Example #6
0
def test_lpsolve_solver_selection_scipy():
    # should always work, because `polytope` requires `scipy`
    c, A, b = example_1d()
    r_ = np.array([-1.0])
    # call directly to isolate from selection within `lpsolve`
    r = solvers._solve_lp_using_scipy(c, A, b)
    assert r['x'] == r_, r['x']
    r = solvers.lpsolve(c, A, b, solver='scipy')
    assert r['x'] == r_, r['x']
Example #7
0
def test_lpsolve_solver_selection_scipy():
    # should always work, because `polytope` requires `scipy`
    c, A, b = example_1d()
    r_ = np.array([-1.0])
    # call directly to isolate from selection within `lpsolve`
    r = solvers._solve_lp_using_scipy(c, A, b)
    assert r['x'] == r_, r['x']
    r = solvers.lpsolve(c, A, b, solver='scipy')
    assert r['x'] == r_, r['x']
Example #8
0
def test_lpsolve_solver_selection_glpk_present():
    c, A, b = example_1d()
    have_glpk = is_glpk_present()
    # skip if GLPK fails to import
    if not have_glpk:
        log.info('Skipping GLPK test of `lpsolve` '
                 'because GLPK failed to import, '
                 'so assume not installed.')
        return
    r = solvers.lpsolve(c, A, b, solver='glpk')
    assert r['x'] == np.array([-1.0]), r['x']
Example #9
0
def test_lpsolve():
    # Ensure same API for both `scipy` and `cvxopt`.
    # Ensured by the different testing configurations.
    # Could change `polytope.polytope.default_solver` to
    # achieve the same result, when `cvxopt.glpk` is present.
    #
    # 2-D example
    c = np.array([1, 1], dtype=float)
    A = np.array([[-1, 0], [0, -1]], dtype=float)
    b = np.array([1, 1], dtype=float)
    res = solvers.lpsolve(c, A, b)
    x = res['x']
    assert x.ndim == 1, x.ndim
    assert x.shape == (2, ), x.shape
    #
    # 1-D example
    c, A, b = example_1d()
    res = solvers.lpsolve(c, A, b)
    x = res['x']
    assert x.ndim == 1, x.ndim
    assert x.shape == (1, ), x.shape
Example #10
0
def test_lpsolve_solver_selection_glpk_present():
    c, A, b = example_1d()
    have_glpk = is_glpk_present()
    # skip if GLPK fails to import
    if not have_glpk:
        log.info(
            'Skipping GLPK test of `lpsolve` '
            'because GLPK failed to import, '
            'so assume not installed.')
        return
    r = solvers.lpsolve(c, A, b, solver='glpk')
    assert r['x'] == np.array([-1.0]), r['x']
Example #11
0
def test_lpsolve():
    # Ensure same API for both `scipy` and `cvxopt`.
    # Ensured by the different testing configurations.
    # Could change `polytope.polytope.default_solver` to
    # achieve the same result, when `cvxopt.glpk` is present.
    #
    # 2-D example
    c = np.array([1, 1], dtype=float)
    A = np.array([[-1, 0], [0, -1]], dtype=float)
    b = np.array([1, 1], dtype=float)
    res = solvers.lpsolve(c, A, b)
    x = res['x']
    assert x.ndim == 1, x.ndim
    assert x.shape == (2,), x.shape
    #
    # 1-D example
    c, A, b = example_1d()
    res = solvers.lpsolve(c, A, b)
    x = res['x']
    assert x.ndim == 1, x.ndim
    assert x.shape == (1,), x.shape
Example #12
0
def shoot(C, D, b, maxiter=1000, abs_tol=1e-7):
    """Return random equality set of P that projects on a projection facet.

    Returns randomly selected equality set E_0 of P such
    that the projection of the equality set is a facet of the projection.

    @param C: Matrix defining the polytope Cx+Dy <= b
    @param D: Matrix defining the polytope Cx+Dy <= b
    @param b: Vector defining the polytope Cx+Dy <= b

    @return: `E_0,af,bf`: Equality set and affine hull
    """
    d = C.shape[1]
    k = D.shape[1]
    iter = 0
    while True:
        if iter > maxiter:
            raise Exception("shoot: could not find starting equality set")
        gamma = np.random.rand(d) - 0.5
        c = np.zeros(k + 1)
        c[0] = -1
        G = np.hstack([np.array([np.dot(C, gamma)]).T, D])
        sol = solvers.lpsolve(c, G, b, solver='glpk')
        opt_sol = np.array(sol['x']).flatten()
        opt_dual = np.array(sol['z']).flatten()
        r_opt = opt_sol[0]
        y_opt = np.array(opt_sol[range(1, len(opt_sol))]).flatten()
        x_opt = r_opt * gamma
        E_0 = np.nonzero(
            np.abs(np.dot(C, x_opt) + np.dot(D, y_opt) - b) < abs_tol)[0]
        DE0 = D[E_0, :]
        CE0 = C[E_0, :]
        b0 = b[E_0]
        if rank(np.dot(null_space(DE0.T).T, CE0)) == 1:
            break
        iter += 1
    af, bf = proj_aff(CE0, DE0, b0, abs_tol=abs_tol)
    if is_dual_degenerate(c,
                          G,
                          b,
                          None,
                          None,
                          opt_sol,
                          opt_dual,
                          abs_tol=abs_tol):
        E_0 = unique_equalityset(C, D, b, af, bf, abs_tol=abs_tol)
    af, bf = proj_aff(C[E_0, :], D[E_0, :], b[E_0])
    if len(bf) > 1:
        raise Exception("shoot: wrong dimension of affine hull")
    return E_0, af.flatten(), bf
Example #13
0
def shoot(C, D, b, maxiter=1000, abs_tol=1e-7):
    """Return random equality set of P that projects on a projection facet.

    Returns randomly selected equality set E_0 of P such
    that the projection of the equality set is a facet of the projection.

    @param C: Matrix defining the polytope Cx+Dy <= b
    @param D: Matrix defining the polytope Cx+Dy <= b
    @param b: Vector defining the polytope Cx+Dy <= b

    @return: `E_0,af,bf`: Equality set and affine hull
    """
    d = C.shape[1]
    k = D.shape[1]
    iter = 0
    while True:
        if iter > maxiter:
            raise Exception(
                "shoot: could not find starting equality set")
        gamma = np.random.rand(d) - 0.5
        c = np.zeros(k + 1)
        c[0] = -1
        G = np.hstack([np.array([np.dot(C, gamma)]).T, D])
        sol = solvers.lpsolve(c, G, b, solver='glpk')
        opt_sol = np.array(sol['x']).flatten()
        opt_dual = np.array(sol['z']).flatten()
        r_opt = opt_sol[0]
        y_opt = np.array(opt_sol[range(1, len(opt_sol))]).flatten()
        x_opt = r_opt * gamma
        E_0 = np.nonzero(
            np.abs(np.dot(C, x_opt) + np.dot(D, y_opt) - b) < abs_tol)[0]
        DE0 = D[E_0, :]
        CE0 = C[E_0, :]
        b0 = b[E_0]
        if rank(np.dot(null_space(DE0.T).T, CE0)) == 1:
            break
        iter += 1
    af, bf = proj_aff(CE0, DE0, b0, abs_tol=abs_tol)
    if is_dual_degenerate(c, G, b, None, None, opt_sol,
                          opt_dual, abs_tol=abs_tol):
        E_0 = unique_equalityset(C, D, b, af, bf, abs_tol=abs_tol)
    af, bf = proj_aff(C[E_0, :], D[E_0, :], b[E_0])
    if len(bf) > 1:
        raise Exception("shoot: wrong dimension of affine hull")
    return E_0, af.flatten(), bf
Example #14
0
def unique_equalityset2(C, D, b, opt_sol, abs_tol=1e-7):
    A = np.hstack([C, D])
    E0 = np.nonzero(np.abs(np.dot(A, opt_sol) - b) < abs_tol)[0]
    af, bf = proj_aff(C[E0, :], D[E0, :], b[E0], expected_dim=1)
    # stack
    ineq = np.hstack([af, np.zeros(D.shape[1])])
    G = np.vstack([A, np.vstack([ineq, -ineq])])
    h = np.hstack([b, np.hstack([bf, -bf])])
    # shape
    m = G.shape[0]
    n = G.shape[1]
    # ht
    e = 1e-3
    v = np.vstack([np.zeros([1, n]), np.eye(n)]).T
    v = v - np.array([np.mean(v, axis=1)]).T
    v = v * e
    ht = h + np.amin(-np.dot(G, v), axis=1)
    # stack
    H1 = np.hstack([G, -np.eye(m)])
    H2 = np.hstack([G, np.zeros([m, m])])
    H3 = np.hstack([np.zeros([m, n]), -np.eye(m)])
    H = np.vstack([H1, np.vstack([H2, H3])])
    h = np.hstack([ht, np.hstack([h, np.zeros(m)])])
    c = np.hstack([np.zeros(n), np.ones(m)])
    sol = solvers.lpsolve(c, H, h, solver='glpk')
    if not sol['status'] == "optimal":
        raise Exception(
            "unique_equalityset: LP returned status " +
            str(sol['status']))
    opt_sol2 = np.array(sol['x']).flatten()
    x = opt_sol2[range(0, n)]
    s = opt_sol2[range(n, len(opt_sol2))]
    E = np.nonzero(s > abs_tol)[0]
    print(E)
    E = np.sort(E[np.nonzero(E < C.shape[0])])
    # Check that they define the same projection
    at, bt = proj_aff(C[E, :], D[E, :], b[E])
    if bt.size != 1 or np.sum(np.abs(at - af)) + np.abs(bt - bf) > abs_tol:
        raise Exception("unique_equalityset2: affine hulls not the same")
    return E
Example #15
0
def unique_equalityset2(C, D, b, opt_sol, abs_tol=1e-7):
    A = np.hstack([C, D])
    E0 = np.nonzero(np.abs(np.dot(A, opt_sol) - b) < abs_tol)[0]
    af, bf = proj_aff(C[E0, :], D[E0, :], b[E0], expected_dim=1)
    # stack
    ineq = np.hstack([af, np.zeros(D.shape[1])])
    G = np.vstack([A, np.vstack([ineq, -ineq])])
    h = np.hstack([b, np.hstack([bf, -bf])])
    # shape
    m = G.shape[0]
    n = G.shape[1]
    # ht
    e = 1e-3
    v = np.vstack([np.zeros([1, n]), np.eye(n)]).T
    v = v - np.array([np.mean(v, axis=1)]).T
    v = v * e
    ht = h + np.amin(-np.dot(G, v), axis=1)
    # stack
    H1 = np.hstack([G, -np.eye(m)])
    H2 = np.hstack([G, np.zeros([m, m])])
    H3 = np.hstack([np.zeros([m, n]), -np.eye(m)])
    H = np.vstack([H1, np.vstack([H2, H3])])
    h = np.hstack([ht, np.hstack([h, np.zeros(m)])])
    c = np.hstack([np.zeros(n), np.ones(m)])
    sol = solvers.lpsolve(c, H, h, solver='glpk')
    if not sol['status'] == "optimal":
        raise Exception("unique_equalityset: LP returned status " +
                        str(sol['status']))
    opt_sol2 = np.array(sol['x']).flatten()
    x = opt_sol2[range(0, n)]
    s = opt_sol2[range(n, len(opt_sol2))]
    E = np.nonzero(s > abs_tol)[0]
    print(E)
    E = np.sort(E[np.nonzero(E < C.shape[0])])
    # Check that they define the same projection
    at, bt = proj_aff(C[E, :], D[E, :], b[E])
    if bt.size != 1 or np.sum(np.abs(at - af)) + np.abs(bt - bf) > abs_tol:
        raise Exception("unique_equalityset2: affine hulls not the same")
    return E
Example #16
0
def cheby_center(C, D, b):
    """Calculate Chebyshev center for the polytope `C x + D y <= b`.

    Input:
    `C, D, b`: Polytope parameters

    Output:
    `x_0, y_0`: The chebyshev centra
    `boolean`: True if a point could be found, False otherwise.
    """
    d = C.shape[1]
    k = D.shape[1]
    A = np.hstack([C, D])
    dim = np.shape(A)[1]
    c = -np.r_[np.zeros(dim), 1]
    norm2 = np.sqrt(np.sum(A * A, axis=1))
    G = np.c_[A, norm2]
    sol = solvers.lpsolve(c, G, h=b, solver='glpk')
    if sol['status'] == "optimal":
        opt = np.array(sol['x'][0:-1]).flatten()
        return opt[range(0, d)], opt[range(d, d + k)], True
    else:
        return np.zeros(d), np.zeros(k), False
Example #17
0
def cheby_center(C, D, b):
    """Calculate Chebyshev center for the polytope `C x + D y <= b`.

    Input:
    `C, D, b`: Polytope parameters

    Output:
    `x_0, y_0`: The chebyshev centra
    `boolean`: True if a point could be found, False otherwise.
    """
    d = C.shape[1]
    k = D.shape[1]
    A = np.hstack([C, D])
    dim = np.shape(A)[1]
    c = - np.r_[np.zeros(dim), 1]
    norm2 = np.sqrt(np.sum(A * A, axis=1))
    G = np.c_[A, norm2]
    sol = solvers.lpsolve(c, G, h=b, solver='glpk')
    if sol['status'] == "optimal":
        opt = np.array(sol['x'][0:-1]).flatten()
        return opt[range(0, d)], opt[range(d, d + k)], True
    else:
        return np.zeros(d), np.zeros(k), False
Example #18
0
def esp(CC, DD, bb, centered=False, abs_tol=1e-10, verbose=0):
    """Project polytope [C D] x <= b onto C coordinates.

    Projects the polytope [C D] x <= b onto the
    coordinates that correspond to C. The projection of the polytope
    P = {[C D]x <= b} where C is M x D and D is M x K is
    defined as proj(P) = {x in R^d | exist y in R^k s.t Cx + Dy < b}
    """
    if 'glpk' in solvers.installed_solvers:
        raise Exception(
            "projection_esp error:"
            " Equality set projection requires `cvxopt.glpk` to run.")
    # Remove zero columns and rows
    nonzerorows = np.nonzero(
        np.sum(np.abs(np.hstack([CC, DD])), axis=1) > abs_tol)[0]
    nonzeroxcols = np.nonzero(np.sum(np.abs(CC), axis=0) > abs_tol)[0]
    nonzeroycols = np.nonzero(np.sum(np.abs(DD), axis=0) > abs_tol)[0]
    C = CC[nonzerorows, :].copy()
    D = DD[nonzerorows, :].copy()
    C = C[:, nonzeroxcols]
    D = D[:, nonzeroycols]
    b = bb[nonzerorows].copy()
    # Make sure origo is inside polytope
    if not centered:
        xc0, yc0, trans = cheby_center(C, D, b)
        if trans:
            b = b - np.dot(C, xc0).flatten() - np.dot(D, yc0).flatten()
        else:
            b = b
    else:
        trans = False
    d = C.shape[1]
    k = D.shape[1]
    if verbose > 0:
        print("Projecting from dim " + str(d + k) + " to " + str(d))
    if k == 0:
        # Not projecting
        return C, bb, []
    if d == 1:
        # Projection to 1D
        c = np.zeros(d + k)
        c[0] = 1
        G = np.hstack([C, D])
        sol = solvers.lpsolve(c, G, b, solver='glpk')
        if sol['status'] != "optimal":
            raise Exception(
                "esp: projection to 1D is not full-dimensional, "
                "LP returned status " + str(sol['status']))
        min_sol = np.array(sol['x']).flatten()
        min_dual_sol = np.array(sol['z']).flatten()
        sol = solvers.lpsolve(-c, G, b, solver='glpk')
        if sol['status'] != "optimal":
            raise Exception(
                "esp: projection to 1D is not full-dimensional, " +
                "LP returned status " + str(sol['status']))
        max_sol = np.array(sol['x']).flatten()
        max_dual_sol = np.array(sol['z']).flatten()
        # min, max
        x_min = min_sol[0]
        x_max = max_sol[0]
        y_min = min_sol[range(1, k + 1)]
        y_max = max_sol[range(1, k + 1)]
        if is_dual_degenerate(c, G, b, None, None, min_sol, min_dual_sol):
            # Min case, relax constraint a little to avoid infeasibility
            E_min = unique_equalityset(
                C, D, b, np.array([1.]), x_min + abs_tol / 3, abs_tol=abs_tol)
        else:
            E_min = np.nonzero(np.abs(np.dot(G, min_sol) - b) < abs_tol)[0]
        if is_dual_degenerate(c, G, b, None, None, max_sol, max_dual_sol):
            # Max case, relax constraint a little to avoid infeasibility
            E_max = unique_equalityset(
                C, D, b, np.array([1.]), x_max - abs_tol / 3, abs_tol=abs_tol)
        else:
            E_max = np.nonzero(np.abs(np.dot(G, max_sol) - b) < abs_tol)[0]
        G = np.array([[1.], [-1.]])
        g = np.array([x_max, -x_min])
        # Relocate
        if trans:
            g = g + np.dot(G, xc0)
        # Return zero cols/rows
        E_max = nonzerorows[E_max]
        E_min = nonzerorows[E_min]
        if verbose > 0:
            print(
                "Returning projection from dim " +
                str(d + k) + " to dim 1 \n")
        return G, g, [E_max, E_min]
    E = []
    L = []
    E_0, af, bf = shoot(C, D, b, abs_tol=abs_tol)
    ridge_list = ridge(C, D, b, E_0, af, bf, abs_tol=abs_tol, verbose=verbose)
    for i in range(len(ridge_list)):
        r = ridge_list[i]
        L.append(Ridge_Facet(r.E_r, r.ar, r.br, E_0, af, bf))
    G = af.T
    g = bf
    if verbose > 0:
        print("\nStarting eq set " + str(E_0) + "\nStarting ridges ")
        for rr in L:
            print(str(rr.E_r))
    E.append(E_0)
    while len(L) > 0:
        rid_fac1 = L[0]
        if verbose > 0:
            print("\nLooking for neighbors to " + str(rid_fac1.E_0) +
                  " and " + str(rid_fac1.E_r) + " ..")
        E_adj, a_adj, b_adj = adjacent(C, D, b, rid_fac1, abs_tol=abs_tol)
        if verbose > 0:
            print("found neighbor " + str(E_adj) +
                  ". \n\nLooking for ridges of neighbor..")
        ridge_list = ridge(
            C, D, b, E_adj, a_adj, b_adj,
            abs_tol=abs_tol, verbose=verbose)
        if verbose > 0:
            print("found " + str(len(ridge_list)) + " ridges\n")
        found_org = False
        for i in range(len(ridge_list)):
            r = ridge_list[i]
            E_r = r.E_r
            ar = r.ar
            br = r.br
            found = False
            for j in range(len(L)):
                rid_fac2 = L[j]
                A_r = rid_fac2.E_r
                if len(A_r) != len(E_r):
                    continue
                t1 = np.sort(np.array(A_r))
                t2 = np.sort(np.array(E_r))
                if np.sum(np.abs(t1 - t2)) < abs_tol:
                    found = True
                    break
            if found:
                if verbose > 0:
                    print("Ridge " + str(E_r) +
                          " already visited, removing from L..")
                if rid_fac2 == rid_fac1:
                    found_org = True
                L.remove(rid_fac2)
            else:
                if verbose > 0:
                    print("Adding ridge-facet " + str(E_adj) +
                          " " + str(E_r) + "")
                L.append(Ridge_Facet(E_r, ar, br, E_adj, a_adj, b_adj))
        if not found_org:
            print("Expected ridge " + str(rid_fac1.E_r))
            print("but got ridges ")
            for rid in ridge_list:
                print(rid.E_r)
            raise Exception(
                "esp: ridge did not return neighboring ridge as expected")
        G = np.vstack([G, a_adj])
        g = np.hstack([g, b_adj])
        E.append(E_adj)
    # Restore center
    if trans:
        g = g + np.dot(G, xc0)
    # Return zero rows
    for Ef in E:
        Ef = nonzerorows[Ef]
    return G, g, E
Example #19
0
def esp(CC, DD, bb, centered=False, abs_tol=1e-10, verbose=0):
    """Project polytope [C D] x <= b onto C coordinates.

    Projects the polytope [C D] x <= b onto the
    coordinates that correspond to C. The projection of the polytope
    P = {[C D]x <= b} where C is M x D and D is M x K is
    defined as proj(P) = {x in R^d | exist y in R^k s.t Cx + Dy < b}
    """
    if 'glpk' in solvers.installed_solvers:
        raise Exception(
            "projection_esp error:"
            " Equality set projection requires `cvxopt.glpk` to run.")
    # Remove zero columns and rows
    nonzerorows = np.nonzero(
        np.sum(np.abs(np.hstack([CC, DD])), axis=1) > abs_tol)[0]
    nonzeroxcols = np.nonzero(np.sum(np.abs(CC), axis=0) > abs_tol)[0]
    nonzeroycols = np.nonzero(np.sum(np.abs(DD), axis=0) > abs_tol)[0]
    C = CC[nonzerorows, :].copy()
    D = DD[nonzerorows, :].copy()
    C = C[:, nonzeroxcols]
    D = D[:, nonzeroycols]
    b = bb[nonzerorows].copy()
    # Make sure origo is inside polytope
    if not centered:
        xc0, yc0, trans = cheby_center(C, D, b)
        if trans:
            b = b - np.dot(C, xc0).flatten() - np.dot(D, yc0).flatten()
        else:
            b = b
    else:
        trans = False
    d = C.shape[1]
    k = D.shape[1]
    if verbose > 0:
        print("Projecting from dim " + str(d + k) + " to " + str(d))
    if k == 0:
        # Not projecting
        return C, bb, []
    if d == 1:
        # Projection to 1D
        c = np.zeros(d + k)
        c[0] = 1
        G = np.hstack([C, D])
        sol = solvers.lpsolve(c, G, b, solver='glpk')
        if sol['status'] != "optimal":
            raise Exception("esp: projection to 1D is not full-dimensional, "
                            "LP returned status " + str(sol['status']))
        min_sol = np.array(sol['x']).flatten()
        min_dual_sol = np.array(sol['z']).flatten()
        sol = solvers.lpsolve(-c, G, b, solver='glpk')
        if sol['status'] != "optimal":
            raise Exception("esp: projection to 1D is not full-dimensional, " +
                            "LP returned status " + str(sol['status']))
        max_sol = np.array(sol['x']).flatten()
        max_dual_sol = np.array(sol['z']).flatten()
        # min, max
        x_min = min_sol[0]
        x_max = max_sol[0]
        y_min = min_sol[range(1, k + 1)]
        y_max = max_sol[range(1, k + 1)]
        if is_dual_degenerate(c, G, b, None, None, min_sol, min_dual_sol):
            # Min case, relax constraint a little to avoid infeasibility
            E_min = unique_equalityset(C,
                                       D,
                                       b,
                                       np.array([1.]),
                                       x_min + abs_tol / 3,
                                       abs_tol=abs_tol)
        else:
            E_min = np.nonzero(np.abs(np.dot(G, min_sol) - b) < abs_tol)[0]
        if is_dual_degenerate(c, G, b, None, None, max_sol, max_dual_sol):
            # Max case, relax constraint a little to avoid infeasibility
            E_max = unique_equalityset(C,
                                       D,
                                       b,
                                       np.array([1.]),
                                       x_max - abs_tol / 3,
                                       abs_tol=abs_tol)
        else:
            E_max = np.nonzero(np.abs(np.dot(G, max_sol) - b) < abs_tol)[0]
        G = np.array([[1.], [-1.]])
        g = np.array([x_max, -x_min])
        # Relocate
        if trans:
            g = g + np.dot(G, xc0)
        # Return zero cols/rows
        E_max = nonzerorows[E_max]
        E_min = nonzerorows[E_min]
        if verbose > 0:
            print("Returning projection from dim " + str(d + k) +
                  " to dim 1 \n")
        return G, g, [E_max, E_min]
    E = []
    L = []
    E_0, af, bf = shoot(C, D, b, abs_tol=abs_tol)
    ridge_list = ridge(C, D, b, E_0, af, bf, abs_tol=abs_tol, verbose=verbose)
    for i in range(len(ridge_list)):
        r = ridge_list[i]
        L.append(Ridge_Facet(r.E_r, r.ar, r.br, E_0, af, bf))
    G = af.T
    g = bf
    if verbose > 0:
        print("\nStarting eq set " + str(E_0) + "\nStarting ridges ")
        for rr in L:
            print(str(rr.E_r))
    E.append(E_0)
    while len(L) > 0:
        rid_fac1 = L[0]
        if verbose > 0:
            print("\nLooking for neighbors to " + str(rid_fac1.E_0) + " and " +
                  str(rid_fac1.E_r) + " ..")
        E_adj, a_adj, b_adj = adjacent(C, D, b, rid_fac1, abs_tol=abs_tol)
        if verbose > 0:
            print("found neighbor " + str(E_adj) +
                  ". \n\nLooking for ridges of neighbor..")
        ridge_list = ridge(C,
                           D,
                           b,
                           E_adj,
                           a_adj,
                           b_adj,
                           abs_tol=abs_tol,
                           verbose=verbose)
        if verbose > 0:
            print("found " + str(len(ridge_list)) + " ridges\n")
        found_org = False
        for i in range(len(ridge_list)):
            r = ridge_list[i]
            E_r = r.E_r
            ar = r.ar
            br = r.br
            found = False
            for j in range(len(L)):
                rid_fac2 = L[j]
                A_r = rid_fac2.E_r
                if len(A_r) != len(E_r):
                    continue
                t1 = np.sort(np.array(A_r))
                t2 = np.sort(np.array(E_r))
                if np.sum(np.abs(t1 - t2)) < abs_tol:
                    found = True
                    break
            if found:
                if verbose > 0:
                    print("Ridge " + str(E_r) +
                          " already visited, removing from L..")
                if rid_fac2 == rid_fac1:
                    found_org = True
                L.remove(rid_fac2)
            else:
                if verbose > 0:
                    print("Adding ridge-facet " + str(E_adj) + " " + str(E_r) +
                          "")
                L.append(Ridge_Facet(E_r, ar, br, E_adj, a_adj, b_adj))
        if not found_org:
            print("Expected ridge " + str(rid_fac1.E_r))
            print("but got ridges ")
            for rid in ridge_list:
                print(rid.E_r)
            raise Exception(
                "esp: ridge did not return neighboring ridge as expected")
        G = np.vstack([G, a_adj])
        g = np.hstack([g, b_adj])
        E.append(E_adj)
    # Restore center
    if trans:
        g = g + np.dot(G, xc0)
    # Return zero rows
    for Ef in E:
        Ef = nonzerorows[Ef]
    return G, g, E