From 17ad91f002ee7e061886a57f338833660e6cf92a Mon Sep 17 00:00:00 2001 From: Michael Schneeberger Date: Mon, 26 Feb 2024 08:55:00 +0100 Subject: remove class SOSConstraint --- sumofsquares/__init__.py | 4 +- sumofsquares/abc.py | 12 -- sumofsquares/cvxopt.py | 22 ++- sumofsquares/impl.py | 39 ----- sumofsquares/init.py | 148 ----------------- sumofsquares/mixins/__init__.py | 0 sumofsquares/mixins/getsosconstraintmixin.py | 10 -- sumofsquares/mixins/parametermixin.py | 25 --- sumofsquares/mixins/putinarepsilonmixin.py | 28 ---- sumofsquares/mixins/sosexprmixin.py | 41 ----- sumofsquares/mixins/sosexpropmixin.py | 180 -------------------- sumofsquares/sosconstraint/__init__.py | 1 - sumofsquares/sosconstraint/abc.py | 5 - sumofsquares/sosconstraint/impl.py | 11 -- sumofsquares/sosconstraint/init.py | 14 -- sumofsquares/sosconstraint/mixins.py | 16 -- sumofsquares/sosexpr/__init__.py | 0 sumofsquares/sosexpr/abc.py | 12 ++ sumofsquares/sosexpr/impl.py | 37 +++++ sumofsquares/sosexpr/init.py | 148 +++++++++++++++++ sumofsquares/sosexpr/mixins/__init__.py | 0 .../sosexpr/mixins/getsosconstraintmixin.py | 10 ++ sumofsquares/sosexpr/mixins/parametermixin.py | 25 +++ sumofsquares/sosexpr/mixins/putinarepsilonmixin.py | 26 +++ sumofsquares/sosexpr/mixins/sosexprmixin.py | 31 ++++ sumofsquares/sosexpr/mixins/sosexpropmixin.py | 183 +++++++++++++++++++++ .../sosexprbase/mixins/sosexprbasemixin.py | 2 +- 27 files changed, 485 insertions(+), 545 deletions(-) delete mode 100644 sumofsquares/abc.py delete mode 100644 sumofsquares/impl.py delete mode 100644 sumofsquares/init.py delete mode 100644 sumofsquares/mixins/__init__.py delete mode 100644 sumofsquares/mixins/getsosconstraintmixin.py delete mode 100644 sumofsquares/mixins/parametermixin.py delete mode 100644 sumofsquares/mixins/putinarepsilonmixin.py delete mode 100644 sumofsquares/mixins/sosexprmixin.py delete mode 100644 sumofsquares/mixins/sosexpropmixin.py delete mode 100644 sumofsquares/sosconstraint/__init__.py delete mode 100644 sumofsquares/sosconstraint/abc.py delete mode 100644 sumofsquares/sosconstraint/impl.py delete mode 100644 sumofsquares/sosconstraint/init.py delete mode 100644 sumofsquares/sosconstraint/mixins.py create mode 100644 sumofsquares/sosexpr/__init__.py create mode 100644 sumofsquares/sosexpr/abc.py create mode 100644 sumofsquares/sosexpr/impl.py create mode 100644 sumofsquares/sosexpr/init.py create mode 100644 sumofsquares/sosexpr/mixins/__init__.py create mode 100644 sumofsquares/sosexpr/mixins/getsosconstraintmixin.py create mode 100644 sumofsquares/sosexpr/mixins/parametermixin.py create mode 100644 sumofsquares/sosexpr/mixins/putinarepsilonmixin.py create mode 100644 sumofsquares/sosexpr/mixins/sosexprmixin.py create mode 100644 sumofsquares/sosexpr/mixins/sosexpropmixin.py diff --git a/sumofsquares/__init__.py b/sumofsquares/__init__.py index 48b8e85..0aa52d6 100644 --- a/sumofsquares/__init__.py +++ b/sumofsquares/__init__.py @@ -1,3 +1,3 @@ -from sumofsquares.abc import ParamSOSExpr, SOSExpr -from sumofsquares.init import init_sos_expr, init_param_expr, init_param_expr_from_reference, init_putinar_epsilon +from sumofsquares.sosexpr.abc import ParamSOSExpr, SOSExpr +from sumofsquares.sosexpr.init import init_sos_expr, init_param_expr, init_param_expr_from_reference, init_putinar_epsilon from sumofsquares.cvxopt import solve_cone, solve_cone2, solve_sos_problem, solve_sos_problem2 diff --git a/sumofsquares/abc.py b/sumofsquares/abc.py deleted file mode 100644 index 03e0b5d..0000000 --- a/sumofsquares/abc.py +++ /dev/null @@ -1,12 +0,0 @@ -import abc - -from sumofsquares.mixins.parametermixin import ParamSOSExprMixin -from sumofsquares.mixins.sosexpropmixin import SOSExprOPMixin - - -class SOSExpr(SOSExprOPMixin, abc.ABC): - pass - - -class ParamSOSExpr(SOSExprOPMixin, ParamSOSExprMixin, abc.ABC): - pass diff --git a/sumofsquares/cvxopt.py b/sumofsquares/cvxopt.py index 93346c3..ec31892 100644 --- a/sumofsquares/cvxopt.py +++ b/sumofsquares/cvxopt.py @@ -4,8 +4,7 @@ import polymatrix import numpy as np import math -from sumofsquares.abc import ParamSOSExpr -from sumofsquares.sosconstraint.abc import SOSConstraint +from sumofsquares.sosexpr.abc import ParamSOSExpr, SOSExpr @dataclasses.dataclass @@ -348,18 +347,17 @@ def solve_cone2( def solve_sos_problem( cost: tuple[polymatrix.Expression], - sos_constraints: tuple[SOSConstraint], + sos_constraints: tuple[SOSExpr], state: polymatrix.ExpressionState, - free_param: tuple[ParamSOSExpr] = None, - x0: dict[ParamSOSExpr, np.ndarray] = None, + free_param: tuple[ParamSOSExpr] | None = None, + x0: dict[ParamSOSExpr, np.ndarray] | None = None, ): if x0 is None: x0 = {} def gen_all_param_expr(): for sos_constraint in sos_constraints: - for param_expr in sos_constraint.dependence: - yield param_expr + yield from sos_constraint.dependence all_param_expr = tuple(set(gen_all_param_expr())) @@ -371,7 +369,7 @@ def solve_sos_problem( def gen_inequality(): for sos_constraint in sos_constraints: if any(param_expr in free_param for param_expr in sos_constraint.dependence): - yield sos_constraint.constraint.eval(sub_vals) + yield sos_constraint.sos_matrix_vec.eval(sub_vals) inequality = tuple(gen_inequality()) @@ -387,10 +385,10 @@ def solve_sos_problem( def solve_sos_problem2( cost: tuple[polymatrix.Expression], - sos_constraints: tuple[SOSConstraint], + sos_constraints: tuple[SOSExpr], state: polymatrix.ExpressionState, - subs: tuple[ParamSOSExpr] = None, - x0: dict[ParamSOSExpr, np.ndarray] = None, + subs: tuple[ParamSOSExpr] | None = None, + x0: dict[ParamSOSExpr, np.ndarray] | None = None, print_info = False, ): if x0 is None: @@ -414,7 +412,7 @@ def solve_sos_problem2( def gen_inequality(): for sos_constraint in sos_constraints: if any(param_expr in free_param_expr for param_expr in sos_constraint.dependence): - yield sos_constraint.constraint.eval(sub_vals) + yield sos_constraint.sos_matrix_vec.eval(sub_vals) inequality = tuple(gen_inequality()) diff --git a/sumofsquares/impl.py b/sumofsquares/impl.py deleted file mode 100644 index b58b674..0000000 --- a/sumofsquares/impl.py +++ /dev/null @@ -1,39 +0,0 @@ -import dataclassabc -import polymatrix - -from sumofsquares.sosexprbase.abc import ParamSOSExprBase, SOSExprBase -from sumofsquares.sosconstraint.abc import SOSConstraint -from sumofsquares.mixins.putinarepsilonmixin import PutinarEpsilonMixin -from sumofsquares.abc import ParamSOSExpr, SOSExpr - - -@dataclassabc.dataclassabc(frozen=True) -class SOSExprImpl(SOSExpr): - underlying: SOSExprBase - - -@dataclassabc.dataclassabc(frozen=True) -class ParamSOSExprImpl(ParamSOSExpr): - underlying: ParamSOSExprBase - - def __eq__(self, other): - if isinstance(other, ParamSOSExprImpl): - return self.underlying == other.underlying - - elif isinstance(other, ParamSOSExprBase): - return self.underlying == other - - else: - return False - - def __hash__(self): - return hash(self.underlying) - - -@dataclassabc.dataclassabc(frozen=True) -class PutinarEpsilonImpl(PutinarEpsilonMixin): - name: str - epsilon: ParamSOSExpr - gamma: dict[str, ParamSOSExpr] - sos_constraints: tuple[SOSConstraint] - condition: SOSExpr diff --git a/sumofsquares/init.py b/sumofsquares/init.py deleted file mode 100644 index c36d2aa..0000000 --- a/sumofsquares/init.py +++ /dev/null @@ -1,148 +0,0 @@ -import polymatrix - -from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin -from sumofsquares.sosexprbase.init import init_param_sos_expr_base, init_sos_expr_base -from sumofsquares.mixins.sosexprmixin import SOSExprMixin -from sumofsquares.abc import SOSExpr -from sumofsquares.impl import ParamSOSExprImpl, SOSExprImpl, PutinarEpsilonImpl - - -def init_sos_expr( - expr: polymatrix.Expression, - variables: polymatrix.Expression, - dependence: tuple[ParameterMixin], -): - return SOSExprImpl( - underlying=init_sos_expr_base( - expr=expr, - variables=variables, - dependence=dependence, - ), - ) - - -def init_param_expr( - name: str, - variables: polymatrix.Expression, - monom: polymatrix.Expression | None = None, - n_row: int | None = None, - n_col: int | None = None, -): - return ParamSOSExprImpl( - underlying=init_param_sos_expr_base( - name=name, - monom=monom, - variables=variables, - n_row=n_row, - n_col=n_col, - ), - ) - - -def init_param_expr_from_reference( - name: str, - reference: SOSExpr, - # variables: polymatrix.Expression, - multiplicand: SOSExpr | polymatrix.Expression | None = None, -): - variables = reference.variables - - if multiplicand is None: - multiplicand_expr = polymatrix.from_(1) - - elif isinstance(multiplicand, polymatrix.Expression): - multiplicand_expr = multiplicand - - elif isinstance(multiplicand, SOSExprMixin): - assert multiplicand.variables == variables, f'{multiplicand.variables=}, {variables=}' - - multiplicand_expr = multiplicand.expr - - else: - multiplicand_expr = polymatrix.from_(multiplicand) - - m_sos_monom = multiplicand_expr.quadratic_monomials(variables) - - max_degree = m_sos_monom.max_degree().T.max() - - m_max_monom = m_sos_monom.filter( - m_sos_monom.max_degree() - max_degree, - inverse=True, - ) - - sos_monom = reference.expr.quadratic_monomials(variables).subtract_monomials(m_max_monom) - - expr = (sos_monom @ sos_monom.T).reshape(1, -1).sum() - - monom = expr.linear_monomials(variables).cache() - - return init_param_expr( - name=name, - monom=monom, - variables=variables, - ) - - -def init_putinar_epsilon( - name: str, - f0: SOSExpr, - fi: dict[str, SOSExpr | polymatrix.Expression] = None, - gi: dict[str, SOSExpr | polymatrix.Expression] = None, - epsilon_min: SOSExpr | None = None, - decrease_rate: SOSExpr | polymatrix.Expression | None = None, -): - sos_constraints = tuple() - - condition = f0 - - def gen_gamma(fi): - for key, val in fi.items(): - yield key, init_param_expr_from_reference( - name=f'gamma_{key}_{name}', - reference=f0, - multiplicand=val, - ) - - if fi is None: - gamma_fi = {} - - else: - gamma_fi = dict(gen_gamma(fi)) - - for key, gamma_i in gamma_fi.items(): - sos_constraints += gamma_i.sos_constraints - condition = condition - gamma_i * fi[key] - - if gi is None: - gamma_gi = {} - - else: - gamma_gi = dict(gen_gamma(gi)) - - for key, gamma_i in gamma_gi.items(): - condition = condition - gamma_i * gi[key] - - if epsilon_min is None: - epsilon = None - - else: - epsilon = init_param_expr( - name=f'epsilon_{name}', - variables=f0.variables, - ) - - sos_constraints += (epsilon - epsilon_min).sos_constraints - condition += epsilon - - if decrease_rate is not None: - condition -= decrease_rate - - sos_constraints += condition.sos_constraints - - return PutinarEpsilonImpl( - name=name, - epsilon=epsilon, - gamma=gamma_fi | gamma_gi, - sos_constraints=sos_constraints, - condition=condition, - ) diff --git a/sumofsquares/mixins/__init__.py b/sumofsquares/mixins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sumofsquares/mixins/getsosconstraintmixin.py b/sumofsquares/mixins/getsosconstraintmixin.py deleted file mode 100644 index 23f3813..0000000 --- a/sumofsquares/mixins/getsosconstraintmixin.py +++ /dev/null @@ -1,10 +0,0 @@ -import abc - -from sumofsquares.sosconstraint.abc import SOSConstraint - - -class GetSOSConstraintMixin(abc.ABC): - @property - @abc.abstractmethod - def sos_constraints(self) -> tuple[SOSConstraint]: - ... diff --git a/sumofsquares/mixins/parametermixin.py b/sumofsquares/mixins/parametermixin.py deleted file mode 100644 index 5281f35..0000000 --- a/sumofsquares/mixins/parametermixin.py +++ /dev/null @@ -1,25 +0,0 @@ -import abc -import polymatrix - -from sumofsquares.mixins.sosexprmixin import SOSExprMixin -from sumofsquares.sosexprbase.abc import ParamSOSExprBase -from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin - - -class ParamSOSExprMixin(ParameterMixin, SOSExprMixin): - @property - @abc.abstractmethod - def underlying(self) -> ParamSOSExprBase: - ... - - @property - def name(self) -> polymatrix.Expression: - return self.underlying.name - - @property - def param(self) -> polymatrix.Expression: - return self.underlying.param - - @property - def monom(self) -> polymatrix.Expression: - return self.underlying.monom diff --git a/sumofsquares/mixins/putinarepsilonmixin.py b/sumofsquares/mixins/putinarepsilonmixin.py deleted file mode 100644 index 7f08f18..0000000 --- a/sumofsquares/mixins/putinarepsilonmixin.py +++ /dev/null @@ -1,28 +0,0 @@ -import abc -import polymatrix - -from sumofsquares.abc import ParamSOSExpr, SOSExpr -from sumofsquares.mixins.getsosconstraintmixin import GetSOSConstraintMixin -from sumofsquares.sosconstraint.abc import SOSConstraint - - -class PutinarEpsilonMixin(GetSOSConstraintMixin, abc.ABC): - @property - @abc.abstractmethod - def name(self) -> str: - ... - - @property - @abc.abstractmethod - def epsilon(self) -> ParamSOSExpr: - ... - - @property - @abc.abstractmethod - def gamma(self) -> dict[str, ParamSOSExpr]: - ... - - @property - @abc.abstractmethod - def condition(self) -> SOSExpr: - ... diff --git a/sumofsquares/mixins/sosexprmixin.py b/sumofsquares/mixins/sosexprmixin.py deleted file mode 100644 index ed3fa65..0000000 --- a/sumofsquares/mixins/sosexprmixin.py +++ /dev/null @@ -1,41 +0,0 @@ -import abc -import polymatrix - -from sumofsquares.sosconstraint.abc import SOSConstraint -from sumofsquares.sosconstraint.init import init_sos_constraint -from sumofsquares.mixins.getsosconstraintmixin import GetSOSConstraintMixin -from sumofsquares.sosexprbase.abc import SOSExprBase - - -class SOSExprMixin(GetSOSConstraintMixin, abc.ABC): - @property - @abc.abstractmethod - def underlying(self) -> SOSExprBase: - ... - - @property - def expr(self) -> polymatrix.Expression: - return self.underlying.expr - - @property - def variables(self) -> polymatrix.Expression: - return self.underlying.variables - - @property - def dependence(self) -> polymatrix.Expression: - return self.underlying.dependence - - @property - def sos_matrix(self) -> polymatrix.Expression: - return self.underlying.sos_matrix - - @property - def sos_matrix_vec(self) -> polymatrix.Expression: - return self.underlying.sos_matrix_as_vector - - @property - def sos_constraints(self) -> tuple[SOSConstraint]: - return (init_sos_constraint( - dependence=self.dependence, - constraint=self.underlying.sos_matrix_as_vector, - ),) diff --git a/sumofsquares/mixins/sosexpropmixin.py b/sumofsquares/mixins/sosexpropmixin.py deleted file mode 100644 index bce2a02..0000000 --- a/sumofsquares/mixins/sosexpropmixin.py +++ /dev/null @@ -1,180 +0,0 @@ -import dataclasses -import typing - -import polymatrix -import polymatrix.expression.from_ - -from sumofsquares.mixins.sosexprmixin import SOSExprMixin -from sumofsquares.sosexprbase.init import init_sos_expr_base - - -class SOSExprOPMixin(SOSExprMixin): - @staticmethod - def _binary( - op, - left: 'SOSExprOPMixin', - right: typing.Union[polymatrix.Expression, 'SOSExprOPMixin'], - ) -> 'SOSExprOPMixin': - - if not isinstance(left, SOSExprOPMixin): - left = polymatrix.expression.from_.from_expr_or_none(left) - - if left is None: - return NotImplemented - - underlying = init_sos_expr_base( - expr=op(polymatrix.from_(left), right.expr), - variables=right.variables, - dependence=right.dependence, - ) - - return dataclasses.replace( - right, - underlying=underlying, - ) - - elif not isinstance(right, SOSExprOPMixin): - right = polymatrix.expression.from_.from_expr_or_none(right) - - if right is None: - return NotImplemented - - underlying = init_sos_expr_base( - expr=op(left.expr, polymatrix.from_(right)), - variables=left.variables, - dependence=left.dependence, - ) - - else: - # var_set_left = set(left.variables) - # var_set_right = set(right.variables) - # assert var_set_left.issubset(var_set_right) or var_set_right.issubset(var_set_left), f'{left.variables=}, {right.variables=}' - - assert left.variables == right.variables, f'{left.variables=}, {right.variables=}' - - # print(left.dependence + right.dependence) - - underlying=init_sos_expr_base( - expr=op(left.expr, right.expr), - variables=left.variables, - dependence=tuple(set(left.dependence + right.dependence)), - ) - - return dataclasses.replace( - left, - underlying=underlying, - ) - - @staticmethod - def _unary(op, expr: 'SOSExprOPMixin') -> 'SOSExprOPMixin': - return dataclasses.replace( - expr, - underlying=init_sos_expr_base( - expr=op(expr.expr), - variables=expr.variables, - dependence=expr.dependence, - ), - ) - - def __add__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': - return self._binary(polymatrix.Expression.__add__, self, other) - - def __matmul__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': - return self._binary(polymatrix.Expression.__matmul__, self, other) - - def __mul__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': - return self._binary(polymatrix.Expression.__mul__, self, other) - - def __neg__(self) -> 'SOSExprOPMixin': - return self._unary(polymatrix.Expression.__neg__, self) - - def __radd__(self, other): - return self._binary(polymatrix.Expression.__add__, other, self) - - def __rmatmul__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': - return self._binary(polymatrix.Expression.__matmul__, other, self) - - def __rmul__(self, other): - return self._binary(polymatrix.Expression.__mul__, other, self) - - def __rsub__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': - return self._binary(polymatrix.Expression.__sub__, other, self) - - def __sub__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': - return self._binary(polymatrix.Expression.__sub__, self, other) - - def __getitem__(self, key: tuple[int, int]): - return dataclasses.replace( - self, - underlying=init_sos_expr_base( - expr=self.expr[key[0], key[1]], - variables=self.variables, - dependence=self.dependence, - ), - ) - - def cache(self) -> 'SOSExprOPMixin': - return self._unary(polymatrix.Expression.cache, self) - - def diff( - self, - variables: polymatrix.Expression | None = None, - ) -> 'SOSExprOPMixin': - if variables is None: - variables = self.variables - - return dataclasses.replace( - self, - underlying=init_sos_expr_base( - expr=self.expr.diff(variables), - variables=self.variables, - dependence=self.dependence, - ), - ) - - def divergence( - self, - variables: polymatrix.Expression | None = None, - ) -> 'SOSExprOPMixin': - if variables is None: - variables = self.variables - - return dataclasses.replace( - self, - underlying=init_sos_expr_base( - expr=self.expr.divergence(variables), - variables=self.variables, - dependence=self.dependence, - ), - ) - - @property - def T(self): - return self._unary(polymatrix.Expression.transpose, self) - - def substitute(self, substitutions, variables): - return dataclasses.replace( - self, - underlying=init_sos_expr_base( - expr=self.expr.substitute(substitutions), - # variables=self.variables.substitute(substitutions), - variables=variables, - dependence=self.dependence, - ), - ) - - def set_variables(self, variables): - return dataclasses.replace( - self, - underlying=init_sos_expr_base( - expr=self.expr, - variables=variables, - dependence=self.dependence, - ), - ) - - def v_stack(self, other): - def op(left, right): - return polymatrix.v_stack((left, right)) - - return self._binary(op, self, other) diff --git a/sumofsquares/sosconstraint/__init__.py b/sumofsquares/sosconstraint/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/sumofsquares/sosconstraint/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sumofsquares/sosconstraint/abc.py b/sumofsquares/sosconstraint/abc.py deleted file mode 100644 index 1894b94..0000000 --- a/sumofsquares/sosconstraint/abc.py +++ /dev/null @@ -1,5 +0,0 @@ -from sumofsquares.sosconstraint.mixins import SOSConstraintMixin - - -class SOSConstraint(SOSConstraintMixin): - pass diff --git a/sumofsquares/sosconstraint/impl.py b/sumofsquares/sosconstraint/impl.py deleted file mode 100644 index e0d6cec..0000000 --- a/sumofsquares/sosconstraint/impl.py +++ /dev/null @@ -1,11 +0,0 @@ -import dataclassabc -import polymatrix - -from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin -from sumofsquares.sosconstraint.abc import SOSConstraint - - -@dataclassabc.dataclassabc -class SOSConstraintImpl(SOSConstraint): - dependence: tuple[ParameterMixin] - constraint: polymatrix.Expression \ No newline at end of file diff --git a/sumofsquares/sosconstraint/init.py b/sumofsquares/sosconstraint/init.py deleted file mode 100644 index 68a2f25..0000000 --- a/sumofsquares/sosconstraint/init.py +++ /dev/null @@ -1,14 +0,0 @@ -import polymatrix - -from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin -from sumofsquares.sosconstraint.impl import SOSConstraintImpl - - -def init_sos_constraint( - dependence: tuple[ParameterMixin], - constraint: polymatrix.Expression -): - return SOSConstraintImpl( - dependence=dependence, - constraint=constraint, - ) diff --git a/sumofsquares/sosconstraint/mixins.py b/sumofsquares/sosconstraint/mixins.py deleted file mode 100644 index be49620..0000000 --- a/sumofsquares/sosconstraint/mixins.py +++ /dev/null @@ -1,16 +0,0 @@ -import abc -import polymatrix - -from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin - - -class SOSConstraintMixin(abc.ABC): - @property - @abc.abstractmethod - def dependence(self) -> tuple[ParameterMixin]: - ... - - @property - @abc.abstractmethod - def constraint(self) -> polymatrix.Expression: - ... diff --git a/sumofsquares/sosexpr/__init__.py b/sumofsquares/sosexpr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sumofsquares/sosexpr/abc.py b/sumofsquares/sosexpr/abc.py new file mode 100644 index 0000000..389956f --- /dev/null +++ b/sumofsquares/sosexpr/abc.py @@ -0,0 +1,12 @@ +import abc + +from sumofsquares.sosexpr.mixins.parametermixin import ParamSOSExprMixin +from sumofsquares.sosexpr.mixins.sosexpropmixin import SOSExprOPMixin + + +class SOSExpr(SOSExprOPMixin, abc.ABC): + pass + + +class ParamSOSExpr(SOSExprOPMixin, ParamSOSExprMixin, abc.ABC): + pass diff --git a/sumofsquares/sosexpr/impl.py b/sumofsquares/sosexpr/impl.py new file mode 100644 index 0000000..4453588 --- /dev/null +++ b/sumofsquares/sosexpr/impl.py @@ -0,0 +1,37 @@ +import dataclassabc + +from sumofsquares.sosexprbase.abc import ParamSOSExprBase, SOSExprBase +from sumofsquares.sosexpr.mixins.putinarepsilonmixin import PutinarEpsilonMixin +from sumofsquares.sosexpr.abc import ParamSOSExpr, SOSExpr + + +@dataclassabc.dataclassabc(frozen=True) +class SOSExprImpl(SOSExpr): + underlying: SOSExprBase + + +@dataclassabc.dataclassabc(frozen=True) +class ParamSOSExprImpl(ParamSOSExpr): + underlying: ParamSOSExprBase + + def __eq__(self, other): + if isinstance(other, ParamSOSExprImpl): + return self.underlying == other.underlying + + elif isinstance(other, ParamSOSExprBase): + return self.underlying == other + + else: + return False + + def __hash__(self): + return hash(self.underlying) + + +@dataclassabc.dataclassabc(frozen=True) +class PutinarEpsilonImpl(PutinarEpsilonMixin): + name: str + epsilon: ParamSOSExpr + gamma: dict[str, ParamSOSExpr] + sos_constraints: tuple[SOSExpr] + condition: SOSExpr diff --git a/sumofsquares/sosexpr/init.py b/sumofsquares/sosexpr/init.py new file mode 100644 index 0000000..c94df85 --- /dev/null +++ b/sumofsquares/sosexpr/init.py @@ -0,0 +1,148 @@ +import polymatrix + +from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin +from sumofsquares.sosexprbase.init import init_param_sos_expr_base, init_sos_expr_base +from sumofsquares.sosexpr.mixins.sosexprmixin import SOSExprMixin +from sumofsquares.sosexpr.abc import SOSExpr +from sumofsquares.sosexpr.impl import ParamSOSExprImpl, SOSExprImpl, PutinarEpsilonImpl + + +def init_sos_expr( + expr: polymatrix.Expression, + variables: polymatrix.Expression, + dependence: tuple[ParameterMixin], +): + return SOSExprImpl( + underlying=init_sos_expr_base( + expr=expr, + variables=variables, + dependence=dependence, + ), + ) + + +def init_param_expr( + name: str, + variables: polymatrix.Expression, + monom: polymatrix.Expression | None = None, + n_row: int | None = None, + n_col: int | None = None, +): + return ParamSOSExprImpl( + underlying=init_param_sos_expr_base( + name=name, + monom=monom, + variables=variables, + n_row=n_row, + n_col=n_col, + ), + ) + + +def init_param_expr_from_reference( + name: str, + reference: SOSExpr, + # variables: polymatrix.Expression, + multiplicand: SOSExpr | polymatrix.Expression | None = None, +): + variables = reference.variables + + if multiplicand is None: + multiplicand_expr = polymatrix.from_(1) + + elif isinstance(multiplicand, polymatrix.Expression): + multiplicand_expr = multiplicand + + elif isinstance(multiplicand, SOSExprMixin): + assert multiplicand.variables == variables, f'{multiplicand.variables=}, {variables=}' + + multiplicand_expr = multiplicand.expr + + else: + multiplicand_expr = polymatrix.from_(multiplicand) + + m_sos_monom = multiplicand_expr.quadratic_monomials(variables) + + max_degree = m_sos_monom.degree().T.max() + + m_max_monom = m_sos_monom.filter( + m_sos_monom.degree() - max_degree, + inverse=True, + ) + + sos_monom = reference.expr.quadratic_monomials(variables).subtract_monomials(m_max_monom) + + expr = (sos_monom @ sos_monom.T).reshape(1, -1).sum() + + monom = expr.linear_monomials(variables).cache() + + return init_param_expr( + name=name, + monom=monom, + variables=variables, + ) + + +def init_putinar_epsilon( + name: str, + f0: SOSExpr, + fi: dict[str, SOSExpr | polymatrix.Expression] | None = None, + gi: dict[str, SOSExpr | polymatrix.Expression] | None = None, + epsilon_min: SOSExpr | None = None, + decrease_rate: SOSExpr | polymatrix.Expression | None = None, +): + sos_constraints = tuple() + + condition = f0 + + def gen_gamma(fi): + for key, val in fi.items(): + yield key, init_param_expr_from_reference( + name=f'gamma_{key}_{name}', + reference=f0, + multiplicand=val, + ) + + if fi is None: + gamma_fi = {} + + else: + gamma_fi = dict(gen_gamma(fi)) + + for key, gamma_i in gamma_fi.items(): + sos_constraints += (gamma_i,) + condition = condition - gamma_i * fi[key] + + if gi is None: + gamma_gi = {} + + else: + gamma_gi = dict(gen_gamma(gi)) + + for key, gamma_i in gamma_gi.items(): + condition = condition - gamma_i * gi[key] + + if epsilon_min is None: + epsilon = None + + else: + epsilon = init_param_expr( + name=f'epsilon_{name}', + variables=f0.variables, + ) + + sos_constraints += (epsilon - epsilon_min,) + condition += epsilon + + if decrease_rate is not None: + condition -= decrease_rate + + sos_constraints += (condition,) + + return PutinarEpsilonImpl( + name=name, + epsilon=epsilon, + gamma=gamma_fi | gamma_gi, + sos_constraints=sos_constraints, + condition=condition, + ) diff --git a/sumofsquares/sosexpr/mixins/__init__.py b/sumofsquares/sosexpr/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sumofsquares/sosexpr/mixins/getsosconstraintmixin.py b/sumofsquares/sosexpr/mixins/getsosconstraintmixin.py new file mode 100644 index 0000000..f3a0710 --- /dev/null +++ b/sumofsquares/sosexpr/mixins/getsosconstraintmixin.py @@ -0,0 +1,10 @@ +import abc + +from sumofsquares.sosexpr.abc import SOSExpr + + +class GetSOSConstraintMixin(abc.ABC): + @property + @abc.abstractmethod + def sos_constraints(self) -> tuple[SOSExpr]: + ... diff --git a/sumofsquares/sosexpr/mixins/parametermixin.py b/sumofsquares/sosexpr/mixins/parametermixin.py new file mode 100644 index 0000000..5f8dd5f --- /dev/null +++ b/sumofsquares/sosexpr/mixins/parametermixin.py @@ -0,0 +1,25 @@ +import abc +import polymatrix + +from sumofsquares.sosexpr.mixins.sosexprmixin import SOSExprMixin +from sumofsquares.sosexprbase.abc import ParamSOSExprBase +from sumofsquares.sosexprbase.mixins.parametermixin import ParameterMixin + + +class ParamSOSExprMixin(ParameterMixin, SOSExprMixin): + @property + @abc.abstractmethod + def underlying(self) -> ParamSOSExprBase: + ... + + @property + def name(self) -> polymatrix.Expression: + return self.underlying.name + + @property + def param(self) -> polymatrix.Expression: + return self.underlying.param + + @property + def monom(self) -> polymatrix.Expression: + return self.underlying.monom diff --git a/sumofsquares/sosexpr/mixins/putinarepsilonmixin.py b/sumofsquares/sosexpr/mixins/putinarepsilonmixin.py new file mode 100644 index 0000000..fdf6e9a --- /dev/null +++ b/sumofsquares/sosexpr/mixins/putinarepsilonmixin.py @@ -0,0 +1,26 @@ +import abc + +from sumofsquares.sosexpr.abc import ParamSOSExpr, SOSExpr +from sumofsquares.sosexpr.mixins.getsosconstraintmixin import GetSOSConstraintMixin + + +class PutinarEpsilonMixin(GetSOSConstraintMixin, abc.ABC): + @property + @abc.abstractmethod + def name(self) -> str: + ... + + @property + @abc.abstractmethod + def epsilon(self) -> ParamSOSExpr: + ... + + @property + @abc.abstractmethod + def gamma(self) -> dict[str, ParamSOSExpr]: + ... + + @property + @abc.abstractmethod + def condition(self) -> SOSExpr: + ... diff --git a/sumofsquares/sosexpr/mixins/sosexprmixin.py b/sumofsquares/sosexpr/mixins/sosexprmixin.py new file mode 100644 index 0000000..2e7eaa6 --- /dev/null +++ b/sumofsquares/sosexpr/mixins/sosexprmixin.py @@ -0,0 +1,31 @@ +import abc +import polymatrix + +from sumofsquares.sosexprbase.abc import SOSExprBase + + +class SOSExprMixin(abc.ABC): + @property + @abc.abstractmethod + def underlying(self) -> SOSExprBase: + ... + + @property + def expr(self) -> polymatrix.Expression: + return self.underlying.expr + + @property + def variables(self) -> polymatrix.Expression: + return self.underlying.variables + + @property + def dependence(self) -> polymatrix.Expression: + return self.underlying.dependence + + @property + def sos_matrix(self) -> polymatrix.Expression: + return self.underlying.sos_matrix + + @property + def sos_matrix_vec(self) -> polymatrix.Expression: + return self.underlying.sos_matrix_vec diff --git a/sumofsquares/sosexpr/mixins/sosexpropmixin.py b/sumofsquares/sosexpr/mixins/sosexpropmixin.py new file mode 100644 index 0000000..c410ea5 --- /dev/null +++ b/sumofsquares/sosexpr/mixins/sosexpropmixin.py @@ -0,0 +1,183 @@ +import dataclasses +import typing + +from numpy import isin + +import polymatrix +import polymatrix.expression.from_ + +from sumofsquares.sosexpr.mixins.sosexprmixin import SOSExprMixin +from sumofsquares.sosexprbase.init import init_sos_expr_base + + +class SOSExprOPMixin(SOSExprMixin): + @staticmethod + def _binary( + op, + left: 'SOSExprOPMixin', + right: typing.Union[polymatrix.Expression, 'SOSExprOPMixin'], + ) -> 'SOSExprOPMixin': + + # if not isinstance(left, SOSExprOPMixin): + # left = polymatrix.expression.from_.from_expr_or_none(left) + + # if left is None: + # return NotImplemented + + # underlying = init_sos_expr_base( + # expr=op(polymatrix.from_(left), right.expr), + # variables=right.variables, + # dependence=right.dependence, + # ) + + # return dataclasses.replace( + # right, + # underlying=underlying, + # ) + + if not isinstance(right, SOSExprOPMixin): + if isinstance(right, tuple): + return NotImplemented + + right = polymatrix.expression.from_.from_expr_or_none(right) + + if right is None: + return NotImplemented + + underlying = init_sos_expr_base( + expr=op(left.expr, polymatrix.from_(right)), + variables=left.variables, + dependence=left.dependence, + ) + + else: + # var_set_left = set(left.variables) + # var_set_right = set(right.variables) + # assert var_set_left.issubset(var_set_right) or var_set_right.issubset(var_set_left), f'{left.variables=}, {right.variables=}' + + assert left.variables == right.variables, f'{left.variables=}, {right.variables=}' + + underlying=init_sos_expr_base( + expr=op(left.expr, right.expr), + variables=left.variables, + dependence=tuple(set(left.dependence + right.dependence)), + ) + + return dataclasses.replace( + left, + underlying=underlying, + ) + + @staticmethod + def _unary(op, expr: 'SOSExprOPMixin') -> 'SOSExprOPMixin': + return dataclasses.replace( + expr, + underlying=init_sos_expr_base( + expr=op(expr.expr), + variables=expr.variables, + dependence=expr.dependence, + ), + ) + + def __add__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': + return self._binary(polymatrix.Expression.__add__, self, other) + + def __matmul__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': + return self._binary(polymatrix.Expression.__matmul__, self, other) + + def __mul__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': + return self._binary(polymatrix.Expression.__mul__, self, other) + + def __neg__(self) -> 'SOSExprOPMixin': + return self._unary(polymatrix.Expression.__neg__, self) + + def __radd__(self, other): + return self._binary(polymatrix.Expression.__add__, other, self) + + def __rmatmul__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': + return self._binary(polymatrix.Expression.__matmul__, other, self) + + def __rmul__(self, other): + return self._binary(polymatrix.Expression.__mul__, other, self) + + def __rsub__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': + return self._binary(polymatrix.Expression.__sub__, other, self) + + def __sub__(self, other: typing.Union[polymatrix.Expression, 'SOSExprOPMixin']) -> 'SOSExprOPMixin': + return self._binary(polymatrix.Expression.__sub__, self, other) + + def __getitem__(self, key: tuple[int, int]): + return dataclasses.replace( + self, + underlying=init_sos_expr_base( + expr=self.expr[key[0], key[1]], + variables=self.variables, + dependence=self.dependence, + ), + ) + + def cache(self) -> 'SOSExprOPMixin': + return self._unary(polymatrix.Expression.cache, self) + + def diff( + self, + variables: polymatrix.Expression | None = None, + ) -> 'SOSExprOPMixin': + if variables is None: + variables = self.variables + + return dataclasses.replace( + self, + underlying=init_sos_expr_base( + expr=self.expr.diff(variables), + variables=self.variables, + dependence=self.dependence, + ), + ) + + def divergence( + self, + variables: polymatrix.Expression | None = None, + ) -> 'SOSExprOPMixin': + if variables is None: + variables = self.variables + + return dataclasses.replace( + self, + underlying=init_sos_expr_base( + expr=self.expr.divergence(variables), + variables=self.variables, + dependence=self.dependence, + ), + ) + + @property + def T(self): + return self._unary(polymatrix.Expression.transpose, self) + + def substitute(self, substitutions, variables): + return dataclasses.replace( + self, + underlying=init_sos_expr_base( + expr=self.expr.substitute(substitutions), + # variables=self.variables.substitute(substitutions), + variables=variables, + dependence=self.dependence, + ), + ) + + def set_variables(self, variables): + return dataclasses.replace( + self, + underlying=init_sos_expr_base( + expr=self.expr, + variables=variables, + dependence=self.dependence, + ), + ) + + def v_stack(self, other): + def op(left, right): + return polymatrix.v_stack((left, right)) + + return self._binary(op, self, other) diff --git a/sumofsquares/sosexprbase/mixins/sosexprbasemixin.py b/sumofsquares/sosexprbase/mixins/sosexprbasemixin.py index c8cee19..e9eabac 100644 --- a/sumofsquares/sosexprbase/mixins/sosexprbasemixin.py +++ b/sumofsquares/sosexprbase/mixins/sosexprbasemixin.py @@ -19,5 +19,5 @@ class SOSExprBaseMixin(ExprBaseMixin): return sos_matrix @property - def sos_matrix_as_vector(self) -> polymatrix.Expression: + def sos_matrix_vec(self) -> polymatrix.Expression: return self.sos_matrix.reshape(-1, 1) -- cgit v1.2.1