X-Git-Url: https://scm.cri.mines-paristech.fr/git/linpy.git/blobdiff_plain/663316ddc03c19cf06e95bad67fd5ac2bb5e1dfc..e50b2facb441d96febf17a6210370489bbcb9dbf:/pypol/polyhedra.py diff --git a/pypol/polyhedra.py b/pypol/polyhedra.py index a08213d..b0b5d0e 100644 --- a/pypol/polyhedra.py +++ b/pypol/polyhedra.py @@ -1,4 +1,3 @@ - import functools import math import numbers @@ -6,8 +5,8 @@ import numbers from . import islhelper from .islhelper import mainctx, libisl -from .coordinates import Point -from .linexprs import Expression, Symbol, Rational +from .geometry import GeometricObject, Point +from .linexprs import Expression, Rational from .domains import Domain @@ -33,11 +32,7 @@ class Polyhedron(Domain): if inequalities is not None: raise TypeError('too many arguments') return cls.fromstring(equalities) - elif isinstance(equalities, Polyhedron): - if inequalities is not None: - raise TypeError('too many arguments') - return equalities - elif isinstance(equalities, Domain): + elif isinstance(equalities, GeometricObject): if inequalities is not None: raise TypeError('too many arguments') return equalities.aspolyhedron() @@ -61,14 +56,23 @@ class Polyhedron(Domain): @property def equalities(self): + """ + Return a list of the equalities in a set. + """ return self._equalities @property def inequalities(self): + """ + Return a list of the inequalities in a set. + """ return self._inequalities @property def constraints(self): + """ + Return ta list of the constraints of a set. + """ return self._constraints @property @@ -76,9 +80,15 @@ class Polyhedron(Domain): return self, def disjoint(self): + """ + Return a set as disjoint. + """ return self def isuniverse(self): + """ + Return true if a set is the Universe set. + """ islbset = self._toislbasicset(self.equalities, self.inequalities, self.symbols) universe = bool(libisl.isl_basic_set_is_universe(islbset)) @@ -86,6 +96,9 @@ class Polyhedron(Domain): return universe def aspolyhedron(self): + """ + Return polyhedral hull of a set. + """ return self def __contains__(self, point): @@ -102,12 +115,41 @@ class Polyhedron(Domain): return True def subs(self, symbol, expression=None): + """ + Subsitute the given value into an expression and return the resulting + expression. + """ equalities = [equality.subs(symbol, expression) for equality in self.equalities] inequalities = [inequality.subs(symbol, expression) for inequality in self.inequalities] return Polyhedron(equalities, inequalities) + def _asinequalities(self): + inequalities = list(self.equalities) + inequalities.extend([-expression for expression in self.equalities]) + inequalities.extend(self.inequalities) + return inequalities + + def widen(self, other): + if not isinstance(other, Polyhedron): + raise ValueError('argument must be a Polyhedron instance') + inequalities1 = self._asinequalities() + inequalities2 = other._asinequalities() + inequalities = [] + for inequality1 in inequalities1: + if other <= Polyhedron(inequalities=[inequality1]): + inequalities.append(inequality1) + for inequality2 in inequalities2: + for i in range(len(inequalities1)): + inequalities3 = inequalities1[:i] + inequalities[i + 1:] + inequalities3.append(inequality2) + polyhedron3 = Polyhedron(inequalities=inequalities3) + if self == polyhedron3: + inequalities.append(inequality2) + break + return Polyhedron(inequalities=inequalities) + @classmethod def _fromislbasicset(cls, islbset, symbols): islconstraints = islhelper.isl_basic_set_constraints(islbset) @@ -180,29 +222,39 @@ class Polyhedron(Domain): return domain def __repr__(self): - if self.isempty(): - return 'Empty' - elif self.isuniverse(): - return 'Universe' + strings = [] + for equality in self.equalities: + strings.append('Eq({}, 0)'.format(equality)) + for inequality in self.inequalities: + strings.append('Ge({}, 0)'.format(inequality)) + if len(strings) == 1: + return strings[0] else: - strings = [] - for equality in self.equalities: - strings.append('0 == {}'.format(equality)) - for inequality in self.inequalities: - strings.append('0 <= {}'.format(inequality)) - if len(strings) == 1: - return strings[0] - else: - return 'And({})'.format(', '.join(strings)) + return 'And({})'.format(', '.join(strings)) + + + def _repr_latex_(self): + strings = [] + for equality in self.equalities: + strings.append('{} = 0'.format(equality._repr_latex_().strip('$'))) + for inequality in self.inequalities: + strings.append('{} \\ge 0'.format(inequality._repr_latex_().strip('$'))) + return '$${}$$'.format(' \\wedge '.join(strings)) @classmethod def fromsympy(cls, expr): + """ + Convert a sympy object to an expression. + """ domain = Domain.fromsympy(expr) if not isinstance(domain, Polyhedron): raise ValueError('non-polyhedral expression: {!r}'.format(expr)) return domain def tosympy(self): + """ + Return an expression as a sympy object. + """ import sympy constraints = [] for equality in self.equalities: @@ -211,123 +263,112 @@ class Polyhedron(Domain): constraints.append(sympy.Ge(inequality.tosympy(), 0)) return sympy.And(*constraints) - @classmethod - def _sort_polygon_2d(cls, points): - if len(points) <= 3: - return points - o = sum((Vector(point) for point in points)) / len(points) - o = Point(o.coordinates()) - angles = {} - for m in points: - om = Vector(o, m) - dx, dy = (coordinate for symbol, coordinates in om.coordinates()) - angle = math.atan2(dy, dx) - angles[m] = angle - return sorted(points, key=angles.get) +class EmptyType(Polyhedron): - @classmethod - def _sort_polygon_3d(cls, points): - if len(points) <= 3: - return points - o = sum((Vector(point) for point in points)) / len(points) - o = Point(o.coordinates()) - a, b = points[:2] - oa = Vector(o, a) - ob = Vector(o, b) - norm_oa = oa.norm() - u = (oa.cross(ob)).asunit() - angles = {a: 0.} - for m in points[1:]: - om = Vector(o, m) - normprod = norm_oa * om.norm() - cosinus = oa.dot(om) / normprod - sinus = u.dot(oa.cross(om)) / normprod - angle = math.acos(cosinus) - angle = math.copysign(angle, sinus) - angles[m] = angle - return sorted(points, key=angles.get) - - def plot(self): - import matplotlib.pyplot as plt - from matplotlib.path import Path - import matplotlib.patches as patches - - if len(self.symbols)> 3: - raise TypeError - - elif len(self.symbols) == 2: - verts = self.vertices() - points = [] - codes = [Path.MOVETO] - for vert in verts: - pairs = () - for sym in sorted(vert, key=Symbol.sortkey): - num = vert.get(sym) - pairs = pairs + (num,) - points.append(pairs) - points.append((0.0, 0.0)) - num = len(points) - while num > 2: - codes.append(Path.LINETO) - num = num - 1 - else: - codes.append(Path.CLOSEPOLY) - path = Path(points, codes) - fig = plt.figure() - ax = fig.add_subplot(111) - patch = patches.PathPatch(path, facecolor='blue', lw=2) - ax.add_patch(patch) - ax.set_xlim(-5,5) - ax.set_ylim(-5,5) - plt.show() + __slots__ = Polyhedron.__slots__ - elif len(self.symbols)==3: - return 0 + def __new__(cls): + self = object().__new__(cls) + self._equalities = (Rational(1),) + self._inequalities = () + self._constraints = self._equalities + self._symbols = () + self._dimension = 0 + return self - return points + def widen(self, other): + if not isinstance(other, Polyhedron): + raise ValueError('argument must be a Polyhedron instance') + return other + + def __repr__(self): + return 'Empty' + + def _repr_latex_(self): + return '$$\\emptyset$$' + +Empty = EmptyType() + + +class UniverseType(Polyhedron): + + __slots__ = Polyhedron.__slots__ + + def __new__(cls): + self = object().__new__(cls) + self._equalities = () + self._inequalities = () + self._constraints = () + self._symbols = () + self._dimension = () + return self + + def __repr__(self): + return 'Universe' + + def _repr_latex_(self): + return '$$\\Omega$$' + +Universe = UniverseType() def _polymorphic(func): @functools.wraps(func) def wrapper(left, right): - if isinstance(left, numbers.Rational): - left = Rational(left) - elif not isinstance(left, Expression): - raise TypeError('left must be a a rational number ' - 'or a linear expression') - if isinstance(right, numbers.Rational): - right = Rational(right) - elif not isinstance(right, Expression): - raise TypeError('right must be a a rational number ' - 'or a linear expression') + if not isinstance(left, Expression): + if isinstance(left, numbers.Rational): + left = Rational(left) + else: + raise TypeError('left must be a a rational number ' + 'or a linear expression') + if not isinstance(right, Expression): + if isinstance(right, numbers.Rational): + right = Rational(right) + else: + raise TypeError('right must be a a rational number ' + 'or a linear expression') return func(left, right) return wrapper @_polymorphic def Lt(left, right): + """ + Assert first set is less than the second set. + """ return Polyhedron([], [right - left - 1]) @_polymorphic def Le(left, right): + """ + Assert first set is less than or equal to the second set. + """ return Polyhedron([], [right - left]) @_polymorphic def Eq(left, right): + """ + Assert first set is equal to the second set. + """ return Polyhedron([left - right], []) @_polymorphic def Ne(left, right): + """ + Assert first set is not equal to the second set. + """ return ~Eq(left, right) @_polymorphic def Gt(left, right): + """ + Assert first set is greater than the second set. + """ return Polyhedron([], [left - right - 1]) @_polymorphic def Ge(left, right): + """ + Assert first set is greater than or equal to the second set. + """ return Polyhedron([], [left - right]) - -Empty = Eq(1, 0) - -Universe = Polyhedron([])