diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e77184..0e56ab8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,8 +10,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-13, windows-latest] - python-version: ["3.9", "3.11", "3.13"] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.14"] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1bbc3ed..a941f13 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,11 @@ Next Release ----- +1.9.0 +----- +* enable Python 3.14 +* bump OSQP to 1.0+ + 1.8.3 ----- * fix the objective offset test for compatibility with Debian sid diff --git a/src/optlang/hybrid_interface.py b/src/optlang/hybrid_interface.py index b253063..3690d16 100644 --- a/src/optlang/hybrid_interface.py +++ b/src/optlang/hybrid_interface.py @@ -110,16 +110,18 @@ def __init__(self): def osqp_settings(self): """Map internal settings to OSQP settings.""" settings = { - "linsys_solver": "qdldl", + "solver_type": "direct", "max_iter": self.settings["iteration_limit"], "eps_abs": self.settings["optimality_tolerance"], "eps_rel": self.settings["optimality_tolerance"], "eps_prim_inf": self.settings["primal_inf_tolerance"], "eps_dual_inf": self.settings["dual_inf_tolerance"], - "polish": True, + "polishing": True, "verbose": int(self.settings["verbose"] > 0), "scaling": 10 if self.settings["presolve"] is True else 0, - "time_limit": self.settings["time_limit"], + "time_limit": ( + self.settings["time_limit"] if self.settings["time_limit"] > 0 else 1e10 + ), "adaptive_rho": True, "rho": 1.0, "alpha": 1.6, @@ -154,9 +156,15 @@ def solve_osqp(self): sp = self.build(add_variable_constraints=True) solver = osqp.OSQP() log.debug("Setting up OSQP problem.") - solver.setup( - P=sp.P, q=sp.q, A=sp.A, l=sp.bounds[:, 0], u=sp.bounds[:, 1], **settings - ) + try: + solver.setup( + P=sp.P, q=sp.q, A=sp.A, l=sp.bounds[:, 0], u=sp.bounds[:, 1], **settings + ) + except osqp.interface.OSQPException as err: + reason = ",".join(str(a) for a in err.args) + if len(err.args) > 0 and err.args[0] == 4: + reason = "non-convex problem" + raise ValueError(f"OSQP error: {reason}") if self._solution is not None: if self.still_valid(sp): solver.warm_start(x=self._solution["x"], y=self._solution["y"]) @@ -244,9 +252,7 @@ def still_valid(self, problem): """Check if previous solutions is still feasible.""" nv, nc = len(self.variables), len(self.constraints) b = problem.bounds - if len(self._solution["x"]) != nv or len( - self._solution["y"] - ) != nc + nv: + if len(self._solution["x"]) != nv or len(self._solution["y"]) != nc + nv: return False c = problem.A.dot(self._solution["x"]) tol = self.settings["primal_inf_tolerance"] diff --git a/tox.ini b/tox.ini index fe236e9..f78ee40 100644 --- a/tox.ini +++ b/tox.ini @@ -10,9 +10,8 @@ envlist = [gh-actions] python = - 3.9: safety, py39, py39-symengine - 3.11: safety, py311, py311-symengine - 3.13: safety, py313, py313-symengine + 3.10: safety, py310, py310-symengine + 3.14: safety, py314, py314-symengine ;[testenv] ;deps = @@ -32,11 +31,11 @@ deps = pytest-cov scipy <1.15.0 # until https://github.com/scipy/scipy/issues/22257 is fixed numpy >=1.13 - osqp >=0.6.2 - mip >=1.9.1; python_version < "3.13" + osqp >=1.1.0 + mip >=1.9.1; python_version < "3.14" highspy >=1.5.3 - cplex; python_version < "3.12" - gurobipy; python_version < "3.13" + cplex + gurobipy symengine: symengine passenv = CI commands = @@ -70,7 +69,7 @@ deps= pip>=21.1 safety commands= - safety check --full-report -i 70612 + safety check --full-report [testenv:mypy] skip_install = True