diff options
-rw-r--r-- | pyproject.toml | 2 | ||||
-rw-r--r-- | sumofsquares/error.py | 7 | ||||
-rw-r--r-- | sumofsquares/solver/cvxopt.py | 15 | ||||
-rw-r--r-- | sumofsquares/solver/mosek.py | 57 |
4 files changed, 77 insertions, 4 deletions
diff --git a/pyproject.toml b/pyproject.toml index def9c86..cb9203d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,9 @@ readme = "README.md" [tool.poetry.dependencies] python = ">=3.10" polymatrix = {git = "https://gitlab.ethz.ch/npross/polymatrix.git"} +python = ">=3.10,<3.13" cvxopt = "^1.3.2" +mosek = "^10.1.31" [tool.poetry.group.notebook.dependencies] diff --git a/sumofsquares/error.py b/sumofsquares/error.py new file mode 100644 index 0000000..34ea6d8 --- /dev/null +++ b/sumofsquares/error.py @@ -0,0 +1,7 @@ +""" +Errors and exceptions raised by the sum of squares package. +""" + +class NotSupportedBySolver(Exception): + """ The chosen solver cannot solve this problem. """ + diff --git a/sumofsquares/solver/cvxopt.py b/sumofsquares/solver/cvxopt.py index 0de8dea..0e5ce37 100644 --- a/sumofsquares/solver/cvxopt.py +++ b/sumofsquares/solver/cvxopt.py @@ -1,3 +1,6 @@ +""" +Solve sumofsquares problems using CVXOPT +""" import cvxopt import numpy as np @@ -12,6 +15,7 @@ from polymatrix.polymatrix.index import MonomialIndex, VariableIndex from ..abc import Problem, SolverInfo from ..constraints import NonNegative, EqualToZero, PositiveSemiDefinite, ExponentialCone +from ..error import NotSupportedBySolver from ..variable import OptVariable @@ -25,11 +29,14 @@ class CVXOPTInfo(SolverInfo, UserDict): return self[key] -def solve_sos_cone(prob: Problem, verbose: bool = False, *args, **kwargs) -> tuple[dict[OptVariable, float], CVXOPTInfo]: +def solve_sos_cone(prob: Problem, verbose: bool = False, + *args, **kwargs) -> tuple[dict[OptVariable, float], CVXOPTInfo]: r""" - Solve a conic problem in the cone of SOS polynomials :math:`\mathbf{\Sigma}_d(x)`. + Solve a conic problem in the cone of SOS polynomials + :math:`\mathbf{\Sigma}_d(x)` using CVXOPT. - Any `*args` and `**kwargs` other than `prob` are passed to the CVXOPT solver. + Any `*args` and `**kwargs` other than `prob` are passed to the CVXOPT + solver. """ # Check that the problem can be solved by CVXOpt, i.e. # - Cost function is at most quadratic @@ -88,7 +95,7 @@ def solve_sos_cone(prob: Problem, verbose: bool = False, *args, **kwargs) -> tup raise NotImplementedError elif isinstance(c, ExponentialCone): - raise NotImplementedError + raise NotSupportedBySolver("CVXOpt cannot solve problems with the exponential cone.") elif isinstance(c, EqualToZero | NonNegative): # Convert constraints to affine expression in decision variables diff --git a/sumofsquares/solver/mosek.py b/sumofsquares/solver/mosek.py new file mode 100644 index 0000000..a454e68 --- /dev/null +++ b/sumofsquares/solver/mosek.py @@ -0,0 +1,57 @@ +""" +Solve sumofsquares problems using MOSEK +""" +from __future__ import annotations + +import sys +import mosek + +from pathlib import Path + +from ..abc import Problem, SolverInfo +from ..variable import OptVariable + + +class MOSEKInfo(SolverInfo): + pass + + +def _streamprinter(text): + # Helper function + sys.stdout.write(text) + sys.stdout.flush() + + +MOSEK_ENV: mosek.Env | None = None + +def setup(license_file: Path | str | None = None): + """ + Setup the MOSEK solver. If `license_file` is left unspecified, according to + the official documentation MOSEK will search for a license file in one of + the following directories depending on the operating system + + :: + $HOME/mosek/mosek.lic + %USERPROFILE%\mosek\mosek.lic + """ + global MOSEK_ENV + MOSEK_ENV = mosek.Env(licensefile=str(license_file)) + + +def solve_sos_cone(prob: Problem, verbose: bool = False, + *args, **kwargs) -> tuple[dict[OptVariable, float], MOSEKInfo]: + r""" + Solve a conic problem in the cone of SOS polynomials + :math:`\mathbf{\Sigma}_d(x)` using MOSEK. + """ + global MOSEK_ENV + + if not MOSEK_ENV: + raise RuntimeError("You forgot to call `sumofsquares.solvers.mosek.setup(license)`!") + + with MOSEK_ENV as env: + with env.Task() as task: + if verbose: + task.set_Stream(mosek.streamtype.log, _streamprinter) + + raise NotImplementedError |