summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sumofsquares/error.py3
-rw-r--r--sumofsquares/problems.py105
2 files changed, 56 insertions, 52 deletions
diff --git a/sumofsquares/error.py b/sumofsquares/error.py
index 6fe04fd..75bdfc4 100644
--- a/sumofsquares/error.py
+++ b/sumofsquares/error.py
@@ -13,3 +13,6 @@ class SolverError(Exception):
class AlgebraicError(Exception):
""" TODO """
+
+class ConstraintWarning(UserWarning):
+ """ A constraint was removed from the problem. """
diff --git a/sumofsquares/problems.py b/sumofsquares/problems.py
index 6737341..808e51c 100644
--- a/sumofsquares/problems.py
+++ b/sumofsquares/problems.py
@@ -10,10 +10,10 @@ import numpy as np
from dataclassabc import dataclassabc
from dataclasses import replace
-from itertools import groupby
from numpy.typing import NDArray
from typing import Any, Sequence
from typing_extensions import override
+from warnings import warn
from polymatrix.expression.expression import Expression, VariableExpression
from polymatrix.expression.mixins.variableexprmixin import VariableExprMixin
@@ -25,6 +25,7 @@ from polymatrix.symbol import Symbol
from .abc import Problem, Constraint, Solver, Result
from .constraints import NonNegative, EqualToZero, PositiveSemiDefinite, ExponentialCone
+from .error import ConstraintWarning
from .solver.cvxopt import solve_cone as cvxopt_solve_cone
from .solver.scs import solve_cone as scs_solve_cone
from .utils import partition
@@ -207,7 +208,7 @@ class SOSProblem(Problem):
for i, c in enumerate(self.constraints):
if isinstance(c, EqualToZero):
- state, deg = c.expression.degree().apply(state)
+ state, deg = c.expression.linear_in(x).degree().max().apply(state)
# Polynomial equality must be converted into coefficient
# matching condition
@@ -226,28 +227,43 @@ class SOSProblem(Problem):
"to conic constraint. Domain restricted non-negativity "
"must be preprocessed by using a Positivstellensatz!")
- state, deg = c.expression.degree().apply(state)
+ if isinstance(c.expression.shape, tuple):
+ nrows, ncols = c.expression.shape
- if isinstance(c.expression.shape[0], int):
- nrows = c.expression.shape[0]
+ # it is an expression
else:
- state, shape = c.expression.shape[0].apply(state)
+ state, shape = c.expression.shape.apply(state)
nrows = shape.at(0, 0).constant()
-
- # It is a vector column goes in the non-negative orthant
- if nrows > 1:
- constraints.append(c)
-
- # Polynomial non-negativity contraints is converted to PSD
- # constraint of SOS quadratic form
- elif deg.scalar().constant() > 1:
- # TODO: it seems to work fine even without .symmetric(). Why?
- cnew = c.expression.quadratic_in(x).symmetric()
- constraints.append(PositiveSemiDefinite(cnew))
-
- # A normal (affine) constraint
- else:
- constraints.append(c)
+ ncols = shape.at(1, 0).constant()
+
+ # Row wise interpretation, we need to know the shape
+ if ncols > 1:
+ raise ValueError(f"Cannot convert non-negativity constraint {str(c)} (nr {i}) "
+ "because it has more than one column. If you want a "
+ "matrix to be PSD use PositiveSemiDefinite, if you want "
+ "to have a vector in the non-negative orthant of R^n the "
+ "interpretation is row-wise, not column wise (transpose "
+ "your vector)")
+
+ for row in range(nrows):
+ state, deg = c.expression.linear_monomials(x).degree().max().apply(state)
+ d = deg.scalar().constant()
+
+ # Polynomial non-negativity contraints is converted to PSD
+ # constraint of SOS quadratic form
+ if d > 1:
+ if d % 2 != 0:
+ raise ValueError("Cannot convert expression inside non-negativity "
+ f"constraint {str(c)} (nr. {i}) into quadratic form for PSD condition "
+ f"because it has degree {deg.scalar().constant()}, which "
+ "is not even.")
+
+ cnew = c.expression.quadratic_in(x).symmetric()
+ constraints.append(PositiveSemiDefinite(cnew))
+
+ # A normal (affine) constraint
+ else:
+ constraints.append(c)
elif isinstance(c, PositiveSemiDefinite):
state, pm = c.expression.cache().apply(state)
@@ -273,28 +289,24 @@ class SOSProblem(Problem):
# Convert Expressions into PolyMatrix objects
pm_constraints: list[Constraint[PolyMatrixMixin]] = []
- for c in constraints:
+ for i, c in enumerate(constraints):
state, pm = c.expression.apply(state)
- pm_constraints.append(replace(c, expression=pm))
-
- # TODO: can we get rid of for loop inside InternalSOSProblem.to_conic_problem?
- # Concatenate constraints so that there is only a big constraint per cone.
- # for (ctype, group) in groupby(constraints, key=type):
- # if ctype in (EqualToZero, NonNegative):
- # state, pm = poly.v_stack((c.expression for c in group)).apply(state)
- # pm_constraints.append(ctype(pm))
-
- # elif ctype is PositiveSemiDefinite:
- # expressions = (c.expression for c in group)
- # state, pm = poly.block_diag(expressions).apply(state)
- # pm_constraints.append(ctype(pm))
+ constr = poly.to_affine(pm)
- # elif ctype is ExponentialCone:
- # state, pm = poly.v_stack((c.expression for c in group)).apply(state)
- # pm_constraints.append(ctype(pm))
+ # Remove constraints that have disappeared
+ # There is only one "variable" in the affine expression
+ if len(constr.slices.keys()) == 1:
+ # That variable is actually not a variable
+ if MonomialIndex.constant() in constr.slices.keys():
+ # constraint has disappeared
+ warn(f"Constraint on {str(c.expression)} was removed.", ConstraintWarning)
+ continue
+ # FIXME: need to check for each type of constraint
+ # that it is ok to remove constant value. Example of
+ # a problematic edge case is NonNegative(a - a - 5)
+ # where a is an optimization variable.
- # else:
- # raise NotImplementedError(f"Cannot process constraint of type {ctype} (yet).")
+ pm_constraints.append(replace(c, expression=constr))
return state, InternalSOSProblem(cost, tuple(pm_constraints),
symbols, polynomial_symbols,
@@ -383,20 +395,9 @@ class InternalSOSProblem(Problem):
P = None
for c in self.constraints:
- constr = poly.to_affine(c.expression)
+ constr = c.expression
nrows, ncols = constr.shape
- # There is only one "variable" in the affine expression
- if len(constr.slices.keys()) == 1:
- # That variable is actually not a variable
- if MonomialIndex.constant() in constr.slices.keys():
- # constraint has disappeared
- continue
- # FIXME: need to check for each type of constraint
- # that it is ok to remove constant value. Example of
- # a problematic edge case is NonNegative(a - a - 5)
- # where a is an optimization variable.
-
if constr.degree > 1:
# If this error occurs an it is not the user's fault, there is
# a bug in SOSProblem.apply