From b88821ef667d06ae794d4b287c85657ae2652369 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Thu, 23 Apr 2026 19:20:47 +0100 Subject: [PATCH] rename: LinearSolver.{INDIRECT,GPU,DENSE} -> {CPU_INDIRECT,GPU_INDIRECT,CPU_DENSE} The old names were ambiguous along two axes: INDIRECT didn't say where the solver runs (CPU), and GPU didn't say what method (it's the indirect/iterative solver, not a direct one). The new names make location explicit and disambiguate from CUDSS (GPU direct). Both the enum member names and the underlying string values move to the new scheme. No internal users of the string form were found; if an external caller was passing `linear_solver="indirect"` etc., they will now see a ValueError from LinearSolver(...) construction. Co-Authored-By: Claude Opus 4.7 --- scs/py/__init__.py | 12 +++++------ test/solve_random_cone_prob.py | 4 ++-- test/test_free_threading.py | 2 +- test/test_mix_sd_csd_cone.py | 4 ++-- test/test_scs_basic.py | 8 +++---- test/test_scs_coverage.py | 26 +++++++++++------------ test/test_scs_object.py | 4 ++-- test/test_scs_quad.py | 2 +- test/test_scs_rand.py | 6 +++--- test/test_scs_sdp.py | 6 +++--- test/test_solve_random_cone_prob.py | 4 ++-- test/test_solve_random_cone_prob_dense.py | 6 +++--- test/test_spectral_and_complex_cones.py | 4 ++-- 13 files changed, 44 insertions(+), 44 deletions(-) diff --git a/scs/py/__init__.py b/scs/py/__init__.py index 2af0451e..5e78d58c 100644 --- a/scs/py/__init__.py +++ b/scs/py/__init__.py @@ -29,11 +29,11 @@ class LinearSolver(enum.Enum): """Linear system solver backend for SCS.""" AUTO = "auto" QDLDL = "qdldl" - INDIRECT = "indirect" + CPU_INDIRECT = "cpu_indirect" MKL = "mkl" ACCELERATE = "accelerate" - DENSE = "dense" - GPU = "gpu" + CPU_DENSE = "cpu_dense" + GPU_INDIRECT = "gpu_indirect" CUDSS = "cudss" @@ -60,11 +60,11 @@ def _resolve_auto(): _SOLVER_DISPATCH = { LinearSolver.AUTO: _resolve_auto, LinearSolver.QDLDL: lambda: _scs_direct, - LinearSolver.INDIRECT: lambda: _load_module("_scs_indirect"), + LinearSolver.CPU_INDIRECT: lambda: _load_module("_scs_indirect"), LinearSolver.MKL: lambda: _load_module("_scs_mkl"), LinearSolver.ACCELERATE: lambda: _load_module("_scs_accelerate"), - LinearSolver.DENSE: lambda: _load_module("_scs_dense"), - LinearSolver.GPU: lambda: _load_module("_scs_gpu"), + LinearSolver.CPU_DENSE: lambda: _load_module("_scs_dense"), + LinearSolver.GPU_INDIRECT: lambda: _load_module("_scs_gpu"), LinearSolver.CUDSS: lambda: _load_module("_scs_cudss"), } diff --git a/test/solve_random_cone_prob.py b/test/solve_random_cone_prob.py index 8de0a603..4d145a99 100644 --- a/test/solve_random_cone_prob.py +++ b/test/solve_random_cone_prob.py @@ -9,11 +9,11 @@ def main(): - solvers = [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT] + solvers = [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT] try: from scs import _scs_gpu - solvers.append(scs.LinearSolver.GPU) + solvers.append(scs.LinearSolver.GPU_INDIRECT) except ImportError: pass diff --git a/test/test_free_threading.py b/test/test_free_threading.py index 95fd849c..2ba73a7a 100644 --- a/test/test_free_threading.py +++ b/test/test_free_threading.py @@ -174,7 +174,7 @@ def worker(linear_solver): assert_almost_equal(sol["x"][0], expected, decimal=2) return sol["x"][0] - backends = [scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT] + backends = [scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT] with ThreadPoolExecutor(max_workers=NUM_THREADS) as pool: futures = [ pool.submit(worker, backends[i % 2]) for i in range(NUM_THREADS) diff --git a/test/test_mix_sd_csd_cone.py b/test/test_mix_sd_csd_cone.py index f918e791..4747fc78 100644 --- a/test/test_mix_sd_csd_cone.py +++ b/test/test_mix_sd_csd_cone.py @@ -22,10 +22,10 @@ def gen_feasible(m, n, rng, p_scale = 0.1): _solver_configs = [ {"linear_solver": scs.LinearSolver.AUTO}, {"linear_solver": scs.LinearSolver.QDLDL}, - {"linear_solver": scs.LinearSolver.INDIRECT}, + {"linear_solver": scs.LinearSolver.CPU_INDIRECT}, ] if _dense_available: - _solver_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _solver_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) @pytest.mark.parametrize("solver_opts", _solver_configs) diff --git a/test/test_scs_basic.py b/test/test_scs_basic.py index 5437139c..d73f4ea3 100644 --- a/test/test_scs_basic.py +++ b/test/test_scs_basic.py @@ -45,11 +45,11 @@ def import_error(msg): _solver_configs = [ scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, - scs.LinearSolver.INDIRECT, + scs.LinearSolver.CPU_INDIRECT, ] try: from scs import _scs_dense - _solver_configs.append(scs.LinearSolver.DENSE) + _solver_configs.append(scs.LinearSolver.CPU_DENSE) except ImportError: pass @@ -79,10 +79,10 @@ def test_problems(cone, linear_solver, expected): [ ({"q": [], "l": long(2)}, scs.LinearSolver.AUTO, 1), ({"q": [], "l": long(2)}, scs.LinearSolver.QDLDL, 1), - ({"q": [], "l": long(2)}, scs.LinearSolver.INDIRECT, 1), + ({"q": [], "l": long(2)}, scs.LinearSolver.CPU_INDIRECT, 1), ({"q": [long(2)], "l": 0}, scs.LinearSolver.AUTO, 0.5), ({"q": [long(2)], "l": 0}, scs.LinearSolver.QDLDL, 0.5), - ({"q": [long(2)], "l": 0}, scs.LinearSolver.INDIRECT, 0.5), + ({"q": [long(2)], "l": 0}, scs.LinearSolver.CPU_INDIRECT, 0.5), ], ) def test_problems_with_longs(cone, linear_solver, expected): diff --git a/test/test_scs_coverage.py b/test/test_scs_coverage.py index 945c6571..764f098c 100644 --- a/test/test_scs_coverage.py +++ b/test/test_scs_coverage.py @@ -298,7 +298,7 @@ def test_dense_tries_import(): """linear_solver=DENSE should attempt to import _scs_dense.""" try: scs.SCS(_make_data(), _CONE, - linear_solver=scs.LinearSolver.DENSE, verbose=False) + linear_solver=scs.LinearSolver.CPU_DENSE, verbose=False) except ImportError: pass # Expected: _scs_dense not built @@ -362,7 +362,7 @@ def test_solution_keys(): # =========================================================================== -@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT]) +@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT]) def test_tight_tolerances(linear_solver): """Tighter eps should still produce a correct solution.""" solver = scs.SCS( @@ -377,7 +377,7 @@ def test_tight_tolerances(linear_solver): assert_almost_equal(sol["x"][0], 1.0, decimal=3) -@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT]) +@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT]) def test_loose_tolerances(linear_solver): """Very loose tolerances should still converge quickly.""" solver = scs.SCS( @@ -710,7 +710,7 @@ def test_legacy_solve_partial_warmstart(): def test_indirect_solver_with_tight_tolerances(): solver = scs.SCS( _make_data(), _CONE, - linear_solver=scs.LinearSolver.INDIRECT, + linear_solver=scs.LinearSolver.CPU_INDIRECT, eps_abs=1e-8, eps_rel=1e-8, verbose=False, @@ -723,7 +723,7 @@ def test_indirect_solver_with_tight_tolerances(): def test_indirect_solver_acceleration_off(): solver = scs.SCS( _make_data(), _CONE, - linear_solver=scs.LinearSolver.INDIRECT, + linear_solver=scs.LinearSolver.CPU_INDIRECT, acceleration_lookback=0, verbose=False, ) @@ -742,7 +742,7 @@ def _make_qp_data(): return {"A": _A.copy(), "b": _b.copy(), "c": np.array([-1.0]), "P": P} -@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT]) +@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT]) def test_qp_with_settings(linear_solver): solver = scs.SCS( _make_qp_data(), _CONE, @@ -929,7 +929,7 @@ def test_exp_cone_primal_known_solution(): assert_almost_equal(sol["x"][0], np.e, decimal=4) -@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT]) +@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT]) def test_exp_cone_both_solvers(linear_solver): """Exp cone problem solved with both direct and indirect backends.""" A_exp = sp.csc_matrix(np.array([ @@ -1374,7 +1374,7 @@ def test_sdp_2x2_known_solution(): assert_almost_equal(sol["x"][0], -1.0, decimal=4) -@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT]) +@pytest.mark.parametrize("linear_solver", [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT]) def test_sdp_both_solvers(linear_solver): """SDP solved with both direct and indirect backends.""" sq2 = np.sqrt(2.0) @@ -2027,7 +2027,7 @@ def test_direct_indirect_same_answer_lp(): sol_d = scs.SCS(_make_data(), _CONE, linear_solver=scs.LinearSolver.QDLDL, **opts).solve() sol_i = scs.SCS(_make_data(), _CONE, - linear_solver=scs.LinearSolver.INDIRECT, **opts).solve() + linear_solver=scs.LinearSolver.CPU_INDIRECT, **opts).solve() assert sol_d["info"]["status"] in ("solved", "solved_inaccurate") assert sol_i["info"]["status"] in ("solved", "solved_inaccurate") assert_almost_equal(sol_d["x"][0], sol_i["x"][0], decimal=4) @@ -2040,7 +2040,7 @@ def test_direct_indirect_same_answer_qp(): sol_d = scs.SCS({"A": _A, "b": _b, "c": _c, "P": P}, _CONE, linear_solver=scs.LinearSolver.QDLDL, **opts).solve() sol_i = scs.SCS({"A": _A, "b": _b, "c": _c, "P": P}, _CONE, - linear_solver=scs.LinearSolver.INDIRECT, **opts).solve() + linear_solver=scs.LinearSolver.CPU_INDIRECT, **opts).solve() assert_almost_equal(sol_d["x"][0], sol_i["x"][0], decimal=3) @@ -2383,7 +2383,7 @@ def test_gpu_tries_import(): """linear_solver=GPU should attempt to import _scs_gpu.""" try: scs.SCS(_make_data(), _CONE, - linear_solver=scs.LinearSolver.GPU, verbose=False) + linear_solver=scs.LinearSolver.GPU_INDIRECT, verbose=False) except ImportError: pass # Expected: _scs_gpu not built @@ -3084,7 +3084,7 @@ def test_linear_solver_qdldl(): def test_linear_solver_indirect(): """linear_solver=INDIRECT should use the CG (indirect) solver.""" solver = scs.SCS(_make_data(), _CONE, - linear_solver=scs.LinearSolver.INDIRECT, verbose=False) + linear_solver=scs.LinearSolver.CPU_INDIRECT, verbose=False) sol = solver.solve() assert sol["info"]["status"] == "solved" @@ -3111,7 +3111,7 @@ def test_linear_solver_dense_tries_import(): """linear_solver=DENSE should attempt to import _scs_dense.""" try: scs.SCS(_make_data(), _CONE, - linear_solver=scs.LinearSolver.DENSE, verbose=False) + linear_solver=scs.LinearSolver.CPU_DENSE, verbose=False) except ImportError: pass # Expected: _scs_dense not built diff --git a/test/test_scs_object.py b/test/test_scs_object.py index 68c2681d..c9f42e7d 100644 --- a/test/test_scs_object.py +++ b/test/test_scs_object.py @@ -51,10 +51,10 @@ def import_error(msg): _solver_configs = [ {"linear_solver": scs.LinearSolver.AUTO}, {"linear_solver": scs.LinearSolver.QDLDL}, - {"linear_solver": scs.LinearSolver.INDIRECT}, + {"linear_solver": scs.LinearSolver.CPU_INDIRECT}, ] if _dense_available: - _solver_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _solver_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) @pytest.mark.parametrize("solver_opts", _solver_configs) diff --git a/test/test_scs_quad.py b/test/test_scs_quad.py index 48472d6c..d497970a 100644 --- a/test/test_scs_quad.py +++ b/test/test_scs_quad.py @@ -17,5 +17,5 @@ print(sol) sol = scs.solve(data, cone, linear_solver=scs.LinearSolver.QDLDL) print(sol) -sol = scs.solve(data, cone, linear_solver=scs.LinearSolver.INDIRECT) +sol = scs.solve(data, cone, linear_solver=scs.LinearSolver.CPU_INDIRECT) print(sol) diff --git a/test/test_scs_rand.py b/test/test_scs_rand.py index 30909437..c08f968f 100644 --- a/test/test_scs_rand.py +++ b/test/test_scs_rand.py @@ -83,10 +83,10 @@ def check_unbounded(sol): _solver_configs = [ {"linear_solver": scs.LinearSolver.AUTO}, {"linear_solver": scs.LinearSolver.QDLDL}, - {"linear_solver": scs.LinearSolver.INDIRECT}, + {"linear_solver": scs.LinearSolver.CPU_INDIRECT}, ] if _dense_available: - _solver_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _solver_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) @pytest.mark.parametrize("solver_opts", _solver_configs) @@ -113,7 +113,7 @@ def test_infeasible(solver_opts): # TODO: indirect solver has trouble in this test, so disable for now _unbounded_configs = [{"linear_solver": scs.LinearSolver.QDLDL}] if _dense_available: - _unbounded_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _unbounded_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) @pytest.mark.parametrize("solver_opts", _unbounded_configs) diff --git a/test/test_scs_sdp.py b/test/test_scs_sdp.py index b0967b46..bf8e37e2 100644 --- a/test/test_scs_sdp.py +++ b/test/test_scs_sdp.py @@ -83,10 +83,10 @@ def check_unbounded(sol): _solver_configs = [ {"linear_solver": scs.LinearSolver.AUTO}, {"linear_solver": scs.LinearSolver.QDLDL}, - {"linear_solver": scs.LinearSolver.INDIRECT}, + {"linear_solver": scs.LinearSolver.CPU_INDIRECT}, ] if _dense_available: - _solver_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _solver_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) @pytest.mark.parametrize("solver_opts", _solver_configs) @@ -113,7 +113,7 @@ def test_infeasible(solver_opts): # TODO: indirect solver has trouble in this test, so disable for now _unbounded_configs = [{"linear_solver": scs.LinearSolver.QDLDL}] if _dense_available: - _unbounded_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _unbounded_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) @pytest.mark.parametrize("solver_opts", _unbounded_configs) diff --git a/test/test_solve_random_cone_prob.py b/test/test_solve_random_cone_prob.py index a3882e12..7a1671dc 100644 --- a/test/test_solve_random_cone_prob.py +++ b/test/test_solve_random_cone_prob.py @@ -21,11 +21,11 @@ def import_error(msg): import_error("Please install pytest to run tests.") raise -solvers = [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.INDIRECT] +solvers = [scs.LinearSolver.AUTO, scs.LinearSolver.QDLDL, scs.LinearSolver.CPU_INDIRECT] try: from scs import _scs_gpu - solvers.append(scs.LinearSolver.GPU) + solvers.append(scs.LinearSolver.GPU_INDIRECT) except ImportError: pass diff --git a/test/test_solve_random_cone_prob_dense.py b/test/test_solve_random_cone_prob_dense.py index c95d1ef3..1fbef480 100644 --- a/test/test_solve_random_cone_prob_dense.py +++ b/test/test_solve_random_cone_prob_dense.py @@ -41,7 +41,7 @@ def import_error(msg): def test_solve_feasible(): rng = np.random.RandomState(3000) data, p_star = tools.gen_feasible(K, n=m // 3, density=0.1, rng=rng) - solver = scs.SCS(data, K, linear_solver=scs.LinearSolver.DENSE, **params) + solver = scs.SCS(data, K, linear_solver=scs.LinearSolver.CPU_DENSE, **params) sol = solver.solve() x = sol["x"] y = sol["y"] @@ -61,7 +61,7 @@ def test_solve_feasible(): def test_solve_infeasible(): rng = np.random.RandomState(3001) data = tools.gen_infeasible(K, n=m // 2, rng=rng) - solver = scs.SCS(data, K, linear_solver=scs.LinearSolver.DENSE, **params) + solver = scs.SCS(data, K, linear_solver=scs.LinearSolver.CPU_DENSE, **params) sol = solver.solve() y = sol["y"] np.testing.assert_array_less(np.linalg.norm(data["A"].T @ y), 1e-3) @@ -71,7 +71,7 @@ def test_solve_infeasible(): def test_solve_unbounded(): rng = np.random.RandomState(3002) data = tools.gen_unbounded(K, n=m // 2, rng=rng) - solver = scs.SCS(data, K, linear_solver=scs.LinearSolver.DENSE, **params) + solver = scs.SCS(data, K, linear_solver=scs.LinearSolver.CPU_DENSE, **params) sol = solver.solve() x = sol["x"] s = sol["s"] diff --git a/test/test_spectral_and_complex_cones.py b/test/test_spectral_and_complex_cones.py index 0b73725c..0eabf347 100644 --- a/test/test_spectral_and_complex_cones.py +++ b/test/test_spectral_and_complex_cones.py @@ -96,10 +96,10 @@ def _gen_feasible_qp(cone, n=None, p_scale=1.0, rng=None): _solver_configs = [ {"linear_solver": scs.LinearSolver.AUTO}, {"linear_solver": scs.LinearSolver.QDLDL}, - {"linear_solver": scs.LinearSolver.INDIRECT}, + {"linear_solver": scs.LinearSolver.CPU_INDIRECT}, ] if _dense_available: - _solver_configs.append({"linear_solver": scs.LinearSolver.DENSE}) + _solver_configs.append({"linear_solver": scs.LinearSolver.CPU_DENSE}) skip_no_spectral = pytest.mark.skipif( not _spectral_available,