From b160dd844c842f202837656290c8ac662ddf9d99 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Tue, 4 Apr 2023 16:01:53 -0500 Subject: [PATCH 1/8] Enable previously skipped tests --- continuous_integration/install_dependencies.sh | 4 ++-- cvxpy/tests/test_conic_solvers.py | 3 +-- cvxpy/tests/test_problem.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/continuous_integration/install_dependencies.sh b/continuous_integration/install_dependencies.sh index bd57e0d07f..323c7350a5 100644 --- a/continuous_integration/install_dependencies.sh +++ b/continuous_integration/install_dependencies.sh @@ -31,12 +31,12 @@ fi if [[ "$PYTHON_VERSION" == "3.11" ]]; then - python -m pip install gurobipy clarabel osqp + python -m pip install gurobipy clarabel osqp sdpa-python # Python 3.8 on Windows will uninstall NumPy 1.16 and install NumPy 1.24 without the exception. elif [[ "$RUNNER_OS" == "Windows" ]] && [[ "$PYTHON_VERSION" == "3.8" ]]; then python -m pip install gurobipy clarabel osqp else - python -m pip install "ortools>=9.3,<9.5" coptpy cplex sdpa-python diffcp gurobipy xpress clarabel sdpa-python + python -m pip install "ortools>=9.3,<9.5" coptpy cplex sdpa-python diffcp gurobipy xpress clarabel fi # cylp has wheels for all versions 3.7 - 3.10, except for 3.7 on Windows diff --git a/cvxpy/tests/test_conic_solvers.py b/cvxpy/tests/test_conic_solvers.py index c7dcaf8ed9..2b44903e04 100644 --- a/cvxpy/tests/test_conic_solvers.py +++ b/cvxpy/tests/test_conic_solvers.py @@ -769,11 +769,10 @@ def test_sdpa_lp_3(self) -> None: def test_sdpa_lp_4(self) -> None: StandardTestLPs.test_lp_4(solver='SDPA') - @unittest.skip('Known limitation of SDPA for degenerate LPs.') def test_sdpa_lp_5(self) -> None: # this also tests the ability to pass solver options StandardTestLPs.test_lp_5(solver='SDPA', - gammaStar=0.86, epsilonDash=8.0E-6, betaStar=0.18, betaBar=0.15) + betaBar=0.1, gammaStar=0.8, epsilonDash=8.0E-6) def test_sdpa_sdp_1(self) -> None: # minimization diff --git a/cvxpy/tests/test_problem.py b/cvxpy/tests/test_problem.py index 6565b5694d..3a34071dbb 100644 --- a/cvxpy/tests/test_problem.py +++ b/cvxpy/tests/test_problem.py @@ -282,7 +282,7 @@ def test_verbose(self) -> None: # Don't test GLPK because there's a race # condition in setting CVXOPT solver options. if solver in [cp.GLPK, cp.GLPK_MI, cp.MOSEK, cp.CBC, - cp.SCIPY, cp.SDPA, cp.COPT]: + cp.SCIPY, cp.COPT]: continue sys.stdout = StringIO() # capture output From 9fa8ed1d1d040fcd4cbe418ab275972f7b3dc127 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Tue, 4 Apr 2023 17:03:28 -0500 Subject: [PATCH 2/8] Use multiprecision backend for Windows tests --- continuous_integration/install_dependencies.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/continuous_integration/install_dependencies.sh b/continuous_integration/install_dependencies.sh index 323c7350a5..a1d93ebdce 100644 --- a/continuous_integration/install_dependencies.sh +++ b/continuous_integration/install_dependencies.sh @@ -31,12 +31,23 @@ fi if [[ "$PYTHON_VERSION" == "3.11" ]]; then - python -m pip install gurobipy clarabel osqp sdpa-python + python -m pip install gurobipy clarabel osqp + if [[ "$RUNNER_OS" == "Windows" ]]; then + # SDPA with OpenBLAS backend does not pass LP5 on Windows + python -m pip install sdpa-multiprecision + else + python -m pip install sdpa-python + fi # Python 3.8 on Windows will uninstall NumPy 1.16 and install NumPy 1.24 without the exception. elif [[ "$RUNNER_OS" == "Windows" ]] && [[ "$PYTHON_VERSION" == "3.8" ]]; then python -m pip install gurobipy clarabel osqp else - python -m pip install "ortools>=9.3,<9.5" coptpy cplex sdpa-python diffcp gurobipy xpress clarabel + python -m pip install "ortools>=9.3,<9.5" coptpy cplex diffcp gurobipy xpress clarabel + if [[ "$RUNNER_OS" == "Windows" ]]; then + python -m pip install sdpa-multiprecision + else + python -m pip install sdpa-python + fi fi # cylp has wheels for all versions 3.7 - 3.10, except for 3.7 on Windows From 21d17af944557dcd1d4b22087e2d6b1378e00b96 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Tue, 4 Apr 2023 18:31:31 -0500 Subject: [PATCH 3/8] Remove phasevalue flipping This should be handled internally by SDPA for Python, and as of version 0.2, it takes care of it. --- .../solvers/conic_solvers/sdpa_conif.py | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py b/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py index 868d322c82..1714f4edb5 100644 --- a/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py +++ b/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py @@ -200,40 +200,13 @@ def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, sol x, y, sdpapinfo, timeinfo, sdpainfo = sdpap.solve( A, -matrix(b), matrix(c), K, J, solver_opts) - # This should be set according to the value of `#define REVERSE_PRIMAL_DUAL` - # in `sdpa` (not `sdpa-python`) source. - # By default it's enabled and hence, the primal problem in SDPA takes - # the LMI form (opposite that of SeDuMi). - # If, while building `sdpa` from source you disable it, you need to change this to `False`. - reverse_primal_dual = True - # By disabling `REVERSE_PRIMAL_DUAL`, the primal-dual pair - # in SDPA becomes similar to SeDuMi, i.e. the form we want (see long note in `apply` method) - # However, with `REVERSE_PRIMAL_DUAL` disabled, we have some - # accuracy related issues on unit tests using the default parameters. - - REVERSE_PRIMAL_DUAL = { - "noINFO": "noINFO", - "pFEAS": "pFEAS", - "dFEAS": "dFEAS", - "pdFEAS": "pdFEAS", - "pdINF": "pdINF", - "pFEAS_dINF": "pINF_dFEAS", # invert flip within sdpa - "pINF_dFEAS": "pFEAS_dINF", # invert flip within sdpa - "pdOPT": "pdOPT", - "pUNBD": "dUNBD", # invert flip within sdpa - "dUNBD": "pUNBD" # invert flip within sdpa - } - - if (reverse_primal_dual): - sdpainfo['phasevalue'] = REVERSE_PRIMAL_DUAL[sdpainfo['phasevalue']] - solution = {} - solution[s.STATUS] = self.STATUS_MAP[sdpainfo['phasevalue']] + solution[s.STATUS] = self.STATUS_MAP[sdpapinfo['phasevalue']] if solution[s.STATUS] in s.SOLUTION_PRESENT: x = x.toarray() y = y.toarray() - solution[s.VALUE] = sdpainfo['primalObj'] + solution[s.VALUE] = sdpapinfo['primalObj'] solution[s.PRIMAL] = x solution[s.EQ_DUAL] = y[:dims['f']] solution[s.INEQ_DUAL] = y[dims['f']:] From 94bda7d264ed9c9ff9d44aeb06c9ee79cb20d514 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Sat, 15 Apr 2023 00:54:06 -0500 Subject: [PATCH 4/8] Add links to documentation in the comments --- .../solvers/conic_solvers/sdpa_conif.py | 68 ++++++------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py b/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py index 1714f4edb5..c7b8b7197b 100644 --- a/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py +++ b/cvxpy/reductions/solvers/conic_solvers/sdpa_conif.py @@ -84,52 +84,6 @@ def apply(self, problem): tuple (dict of arguments needed for the solver, inverse data) """ - - # CVXPY represents cone programs as - # (P) min_x { c^T x : A x + b \in K } + d, - # or, using Dualize - # (D) max_y { -b^T y : c - A^T y = 0, y \in K^* } + d. - - # SDPAP takes a conic program in CLP form: - # (P) min_x { c^T x : A x - b \in J, x \in K } - # or - # (D) max_y { b^T y : y \in J^*, c - a^T y \in K^*} - - # SDPA takes a conic program in SeDuMI form: - # (P) min_x { c^T x : A x - b = 0, x \in K } - # or - # (D) max_y { b^T y : c - A^T y \in K^*} - - # SDPA always takes an input in SeDuMi form - # SDPAP therefore converts the problem from CLP form to SeDuMi form which - # involves getting rid of constraints involving cone J (and J^*) of the CLP form - - # We have two ways to solve using SDPA - - # 1. CVXPY (P) -> CLP (P), by - # - flipping sign of b - # - setting J of CLP (P) to K of CVXPY (P) - # - setting K of CLP (P) to a free cone - # - # (a) then CLP (P)-> SeDuMi (D) if user specifies `convMethod` option to `clp_toLMI` - # (b) then CLP (P)-> SeDuMi (P) if user specifies `convMethod` option to `clp_toEQ` - # - # 2. CVXPY (P) -> CVXPY (D), by - # - using Dualize - # - then CVXPY (D) -> SeDuMi (P) by - # - setting c of SeDuMi (P) to -b of CVXPY (D) - # - setting b of SeDuMi (P) to c of CVXPY (D) - # - setting x of SeDuMi (P) to y of CVXPY (D) - # - setting K of SeDuMi (P) to K^* of CVXPY (D) - # - transposing A - - # 2 does not give a benefit over 1(a) - # 1 (a) and 2 both flip the primal and dual between CVXPY and SeDuMi - # 1 (b) does not flip primal and dual throughout, but requires `clp_toEQ` to be implemented - # TODO: Implement `clp_toEQ` in `sdpa-python` - # Thereafter, 1 will allows user to choose between solving as primal or dual - # by specifying `convMethod` option in solver options - data = {} inv_data = {self.VAR_ID: problem.x.id} @@ -179,6 +133,28 @@ def invert(self, solution, inverse_data): return failure_solution(status) def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, solver_cache=None): + """ + CVXPY represents cone programs as + (P) min_x { c^T x : A x + b \in K } + d + + SDPA Python takes a conic program in CLP format: + (P) min_x { c^T x : A x - b \in J, x \in K } + + CVXPY (P) -> CLP (P), by + - flipping sign of b + - setting J of CLP (P) to K of CVXPY (P) + - setting K of CLP (P) to a free cone + + CLP format is a generalization of the SeDuMi format. Both formats are explained at + https://sdpa-python.github.io/docs/formats/ + + Internally, SDPA Python will reduce CLP form to SeDuMi dual form using `clp_toLMI`. + In SeDuMi format, the dual is in LMI form. In SDPA format, the primal is in LMI form. + The backend (i.e. `libsdpa.a` or `libsdpa_gmp.a`) uses the SDPA format. + + For details on the reverse relationship between SDPA and SeDuMi formats, please see + https://sdpa-python.github.io/docs/formats/sdpa_sedumi.html + """ import sdpap from scipy import matrix From 271759507eb998f40726f82695f57ba910332415 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Sat, 3 Jun 2023 23:36:52 -0500 Subject: [PATCH 5/8] Add ill-posed LP to test multi-precision ability --- cvxpy/tests/solver_test_helpers.py | 38 ++++++++++++++++++++++++++++++ cvxpy/tests/test_conic_solvers.py | 3 +++ 2 files changed, 41 insertions(+) diff --git a/cvxpy/tests/solver_test_helpers.py b/cvxpy/tests/solver_test_helpers.py index 5c33f6ba26..d18908364b 100644 --- a/cvxpy/tests/solver_test_helpers.py +++ b/cvxpy/tests/solver_test_helpers.py @@ -256,6 +256,31 @@ def lp_6() -> SolverTestHelper: return sth +def lp_7() -> SolverTestHelper: + """ + An ill-posed problem to test multiprecision ability of solvers. + + This test will not pass on CVXOPT (as of v1.3.1) and on SDPA without GMP support. + """ + n = 50 + a = cp.Variable((n+1)) + delta = cp.Variable((n)) + b = cp.Variable((n+1)) + objective = cp.Minimize(cp.sum(cp.pos(delta))) + constraints = [ + a[1:] - a[:-1] == delta, + a >= cp.pos(b), + ] + con_pairs = [(constraints[0], None), + (constraints[1], None)] + var_pairs = [(a, None), + (delta, None), + (b, None)] + obj_pair = (objective, 0.) + sth = SolverTestHelper(obj_pair, var_pairs, con_pairs) + return sth + + def qp_0() -> SolverTestHelper: # univariate feasible problem x = cp.Variable(1) @@ -968,6 +993,19 @@ def test_lp_6(solver, places: int = 4, duals: bool = True, **kwargs) -> SolverTe sth.check_dual_domains(places) return sth + @staticmethod + def test_lp_7(solver, places: int = 4, duals: bool = True, **kwargs) -> SolverTestHelper: + sth = lp_7() + sth.solve(solver, **kwargs) + + import sdpap + gmp = sdpap.sdpacall.sdpacall.get_backend_info()["gmp"] + if gmp==0: + sth.expect_val = None # essentially skips the `verify_objective` test + + sth.verify_objective(places) + return sth + @staticmethod def test_mi_lp_0(solver, places: int = 4, **kwargs) -> SolverTestHelper: sth = mi_lp_0() diff --git a/cvxpy/tests/test_conic_solvers.py b/cvxpy/tests/test_conic_solvers.py index 2b44903e04..1157044f79 100644 --- a/cvxpy/tests/test_conic_solvers.py +++ b/cvxpy/tests/test_conic_solvers.py @@ -774,6 +774,9 @@ def test_sdpa_lp_5(self) -> None: StandardTestLPs.test_lp_5(solver='SDPA', betaBar=0.1, gammaStar=0.8, epsilonDash=8.0E-6) + def test_sdpa_lp_7(self) -> None: + StandardTestLPs.test_lp_7(solver='SDPA') + def test_sdpa_sdp_1(self) -> None: # minimization StandardTestSDPs.test_sdp_1min(solver='SDPA') From 9a7aae703ad96b4a4f83143b64fcb0b792a67f11 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Sun, 4 Jun 2023 23:16:03 -0500 Subject: [PATCH 6/8] Only run LP7 for GMP backend --- cvxpy/tests/solver_test_helpers.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cvxpy/tests/solver_test_helpers.py b/cvxpy/tests/solver_test_helpers.py index d18908364b..87954e5c47 100644 --- a/cvxpy/tests/solver_test_helpers.py +++ b/cvxpy/tests/solver_test_helpers.py @@ -996,14 +996,10 @@ def test_lp_6(solver, places: int = 4, duals: bool = True, **kwargs) -> SolverTe @staticmethod def test_lp_7(solver, places: int = 4, duals: bool = True, **kwargs) -> SolverTestHelper: sth = lp_7() - sth.solve(solver, **kwargs) - import sdpap - gmp = sdpap.sdpacall.sdpacall.get_backend_info()["gmp"] - if gmp==0: - sth.expect_val = None # essentially skips the `verify_objective` test - - sth.verify_objective(places) + if sdpap.sdpacall.sdpacall.get_backend_info()["gmp"] + sth.solve(solver, **kwargs) + sth.verify_objective(places) return sth @staticmethod From da6029ba6665c9f43946f414aac927f7eab113d3 Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Sun, 4 Jun 2023 23:19:04 -0500 Subject: [PATCH 7/8] Typo fix --- cvxpy/tests/solver_test_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvxpy/tests/solver_test_helpers.py b/cvxpy/tests/solver_test_helpers.py index 87954e5c47..70e9ff19d3 100644 --- a/cvxpy/tests/solver_test_helpers.py +++ b/cvxpy/tests/solver_test_helpers.py @@ -997,7 +997,7 @@ def test_lp_6(solver, places: int = 4, duals: bool = True, **kwargs) -> SolverTe def test_lp_7(solver, places: int = 4, duals: bool = True, **kwargs) -> SolverTestHelper: sth = lp_7() import sdpap - if sdpap.sdpacall.sdpacall.get_backend_info()["gmp"] + if sdpap.sdpacall.sdpacall.get_backend_info()["gmp"]: sth.solve(solver, **kwargs) sth.verify_objective(places) return sth From 1d2cb9005c845652a4f8838818e0b3ad35f6504a Mon Sep 17 00:00:00 2001 From: Usama Muneeb Date: Mon, 5 Jun 2023 12:33:58 -0500 Subject: [PATCH 8/8] Add multiprecision footnote in solver table --- doc/source/tutorial/advanced/index.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/tutorial/advanced/index.rst b/doc/source/tutorial/advanced/index.rst index eb60aa7f01..641068e027 100644 --- a/doc/source/tutorial/advanced/index.rst +++ b/doc/source/tutorial/advanced/index.rst @@ -460,7 +460,7 @@ The table below shows the types of problems the supported solvers can handle. +----------------+----+----+------+-----+-----+-----+-----+ | `CVXOPT`_ | X | X | X | X | | | | +----------------+----+----+------+-----+-----+-----+-----+ -| `SDPA`_ | X | | | X | | | | +| `SDPA`_ *** | X | | | X | | | | +----------------+----+----+------+-----+-----+-----+-----+ | `SCS`_ | X | X | X | X | X | X | | +----------------+----+----+------+-----+-----+-----+-----+ @@ -475,6 +475,8 @@ The table below shows the types of problems the supported solvers can handle. (**) Except mixed-integer SDP. +(***) Multiprecision support is available on SDPA if the appropriate SDPA package is installed. With multiprecision support, SDPA can solve your problem with much smaller `epsilonDash` and/or `epsilonStar` parameters. These parameters must be manually adjusted to achieve the desired degree of precision. Please see the solver website for details. SDPA can also solve some ill-posed problems with multiprecision support. + Here EXP refers to problems with exponential cone constraints. The exponential cone is defined as :math:`\{(x,y,z) \mid y > 0, y\exp(x/y) \leq z \} \cup \{ (x,y,z) \mid x \leq 0, y = 0, z \geq 0\}`.