X-Git-Url: https://scm.cri.ensmp.fr/git/linpy.git/blobdiff_plain/ba15f3f33f837b1291f74bc94081e99b860d3228..d1c9ff71cf2d59fa6c56665d1569d16d3f1428a6:/linpy/domains.py diff --git a/linpy/domains.py b/linpy/domains.py index 4cd46a4..f26b2fc 100644 --- a/linpy/domains.py +++ b/linpy/domains.py @@ -24,7 +24,7 @@ from fractions import Fraction from . import islhelper from .islhelper import mainctx, libisl -from .linexprs import LinExpr, Symbol, Rational +from .linexprs import LinExpr, Symbol from .geometry import GeometricObject, Point, Vector @@ -38,7 +38,7 @@ __all__ = [ class Domain(GeometricObject): """ A domain is a union of polyhedra. Unlike polyhedra, domains allow exact - computation of union and complementary operations. + computation of union, subtraction and complementary operations. A domain with a unique polyhedron is automatically subclassed as a Polyhedron instance. @@ -54,22 +54,23 @@ class Domain(GeometricObject): """ Return a domain from a sequence of polyhedra. - >>> square = Polyhedron('0 <= x <= 2, 0 <= y <= 2') - >>> square2 = Polyhedron('2 <= x <= 4, 2 <= y <= 4') - >>> dom = Domain([square, square2]) + >>> square1 = Polyhedron('0 <= x <= 2, 0 <= y <= 2') + >>> square2 = Polyhedron('1 <= x <= 3, 1 <= y <= 3') + >>> dom = Domain(square1, square2) + >>> dom + Or(And(x <= 2, 0 <= x, y <= 2, 0 <= y), + And(x <= 3, 1 <= x, y <= 3, 1 <= y)) It is also possible to build domains from polyhedra using arithmetic - operators Domain.__and__(), Domain.__or__() or functions And() and Or(), - using one of the following instructions: + operators Domain.__or__(), Domain.__invert__() or functions Or() and + Not(), using one of the following instructions: - >>> square = Polyhedron('0 <= x <= 2, 0 <= y <= 2') - >>> square2 = Polyhedron('2 <= x <= 4, 2 <= y <= 4') - >>> dom = square | square2 - >>> dom = Or(square, square2) + >>> dom = square1 | square2 + >>> dom = Or(square1, square2) Alternatively, a domain can be built from a string: - >>> dom = Domain('0 <= x <= 2, 0 <= y <= 2; 2 <= x <= 4, 2 <= y <= 4') + >>> dom = Domain('0 <= x <= 2, 0 <= y <= 2; 1 <= x <= 3, 1 <= y <= 3') Finally, a domain can be built from a GeometricObject instance, calling the GeometricObject.asdomain() method. @@ -161,18 +162,22 @@ class Domain(GeometricObject): """ Return True if two domains are equal. """ - symbols = self._xsymbols([self, other]) - islset1 = self._toislset(self.polyhedra, symbols) - islset2 = other._toislset(other.polyhedra, symbols) - equal = bool(libisl.isl_set_is_equal(islset1, islset2)) - libisl.isl_set_free(islset1) - libisl.isl_set_free(islset2) - return equal + if isinstance(other, Domain): + symbols = self._xsymbols([self, other]) + islset1 = self._toislset(self.polyhedra, symbols) + islset2 = other._toislset(other.polyhedra, symbols) + equal = bool(libisl.isl_set_is_equal(islset1, islset2)) + libisl.isl_set_free(islset1) + libisl.isl_set_free(islset2) + return equal + return NotImplemented def isdisjoint(self, other): """ Return True if two domains have a null intersection. """ + if not isinstance(other, Domain): + raise TypeError('other must be a Domain instance') symbols = self._xsymbols([self, other]) islset1 = self._toislset(self.polyhedra, symbols) islset2 = self._toislset(other.polyhedra, symbols) @@ -185,29 +190,33 @@ class Domain(GeometricObject): """ Report whether another domain contains the domain. """ - symbols = self._xsymbols([self, other]) - islset1 = self._toislset(self.polyhedra, symbols) - islset2 = self._toislset(other.polyhedra, symbols) - equal = bool(libisl.isl_set_is_subset(islset1, islset2)) - libisl.isl_set_free(islset1) - libisl.isl_set_free(islset2) - return equal + return self < other def __le__(self, other): - return self.issubset(other) + if isinstance(other, Domain): + symbols = self._xsymbols([self, other]) + islset1 = self._toislset(self.polyhedra, symbols) + islset2 = self._toislset(other.polyhedra, symbols) + equal = bool(libisl.isl_set_is_subset(islset1, islset2)) + libisl.isl_set_free(islset1) + libisl.isl_set_free(islset2) + return equal + return NotImplemented __le__.__doc__ = issubset.__doc__ def __lt__(self, other): """ Report whether another domain is contained within the domain. """ - symbols = self._xsymbols([self, other]) - islset1 = self._toislset(self.polyhedra, symbols) - islset2 = self._toislset(other.polyhedra, symbols) - equal = bool(libisl.isl_set_is_strict_subset(islset1, islset2)) - libisl.isl_set_free(islset1) - libisl.isl_set_free(islset2) - return equal + if isinstance(other, Domain): + symbols = self._xsymbols([self, other]) + islset1 = self._toislset(self.polyhedra, symbols) + islset2 = self._toislset(other.polyhedra, symbols) + equal = bool(libisl.isl_set_is_strict_subset(islset1, islset2)) + libisl.isl_set_free(islset1) + libisl.isl_set_free(islset2) + return equal + return NotImplemented def complement(self): """ @@ -226,7 +235,7 @@ class Domain(GeometricObject): Return an equivalent domain, whose polyhedra are disjoint. """ islset = self._toislset(self.polyhedra, self.symbols) - islset = libisl.isl_set_make_disjoint(mainctx, islset) + islset = libisl.isl_set_make_disjoint(islset) return self._fromislset(islset, self.symbols) def coalesce(self): @@ -270,6 +279,10 @@ class Domain(GeometricObject): Project out the sequence of symbols given in arguments, and return the resulting domain. """ + symbols = list(symbols) + for symbol in symbols: + if not isinstance(symbol, Symbol): + raise TypeError('symbols must be Symbol instances') islset = self._toislset(self.polyhedra, self.symbols) n = 0 for index, symbol in reversed(list(enumerate(self.symbols))): @@ -308,17 +321,19 @@ class Domain(GeometricObject): Return the intersection of two or more domains as a new domain. As an alternative, function And() can be used. """ - if len(others) == 0: - return self - symbols = self._xsymbols((self,) + others) - islset1 = self._toislset(self.polyhedra, symbols) + result = self for other in others: - islset2 = other._toislset(other.polyhedra, symbols) - islset1 = libisl.isl_set_intersect(islset1, islset2) - return self._fromislset(islset1, symbols) + result &= other + return result def __and__(self, other): - return self.intersection(other) + if isinstance(other, Domain): + symbols = self._xsymbols([self, other]) + islset1 = self._toislset(self.polyhedra, symbols) + islset2 = other._toislset(other.polyhedra, symbols) + islset = libisl.isl_set_intersect(islset1, islset2) + return self._fromislset(islset, symbols) + return NotImplemented __and__.__doc__ = intersection.__doc__ def union(self, *others): @@ -326,35 +341,39 @@ class Domain(GeometricObject): Return the union of two or more domains as a new domain. As an alternative, function Or() can be used. """ - if len(others) == 0: - return self - symbols = self._xsymbols((self,) + others) - islset1 = self._toislset(self.polyhedra, symbols) + result = self for other in others: - islset2 = other._toislset(other.polyhedra, symbols) - islset1 = libisl.isl_set_union(islset1, islset2) - return self._fromislset(islset1, symbols) + result |= other + return result def __or__(self, other): - return self.union(other) + if isinstance(other, Domain): + symbols = self._xsymbols([self, other]) + islset1 = self._toislset(self.polyhedra, symbols) + islset2 = other._toislset(other.polyhedra, symbols) + islset = libisl.isl_set_union(islset1, islset2) + return self._fromislset(islset, symbols) + return NotImplemented __or__.__doc__ = union.__doc__ def __add__(self, other): - return self.union(other) + return self | other __add__.__doc__ = union.__doc__ def difference(self, other): """ Return the difference of two domains as a new domain. """ - symbols = self._xsymbols([self, other]) - islset1 = self._toislset(self.polyhedra, symbols) - islset2 = other._toislset(other.polyhedra, symbols) - islset = libisl.isl_set_subtract(islset1, islset2) - return self._fromislset(islset, symbols) + return self - other def __sub__(self, other): - return self.difference(other) + if isinstance(other, Domain): + symbols = self._xsymbols([self, other]) + islset1 = self._toislset(self.polyhedra, symbols) + islset2 = other._toislset(other.polyhedra, symbols) + islset = libisl.isl_set_subtract(islset1, islset2) + return self._fromislset(islset, symbols) + return NotImplemented __sub__.__doc__ = difference.__doc__ def lexmin(self): @@ -373,7 +392,10 @@ class Domain(GeometricObject): islset = libisl.isl_set_lexmax(islset) return self._fromislset(islset, self.symbols) - _RE_COORDINATE = re.compile(r'\((?P\-?\d+)\)(/(?P\d+))?') + if islhelper.isl_version >= '0.13': + _RE_COORDINATE = re.compile(r'\((?P\-?\d+)\)(/(?P\d+))?') + else: + _RE_COORDINATE = None def vertices(self): """ @@ -389,10 +411,10 @@ class Domain(GeometricObject): vertices = islhelper.isl_vertices_vertices(vertices) points = [] for vertex in vertices: - expr = libisl.isl_vertex_get_expr(vertex) + expression = libisl.isl_vertex_get_expr(vertex) coordinates = [] - if islhelper.isl_version < '0.13': - constraints = islhelper.isl_basic_set_constraints(expr) + if self._RE_COORDINATE is None: + constraints = islhelper.isl_basic_set_constraints(expression) for constraint in constraints: constant = libisl.isl_constraint_get_constant_val(constraint) constant = islhelper.isl_val_to_int(constant) @@ -404,7 +426,7 @@ class Domain(GeometricObject): coordinate = -Fraction(constant, coefficient) coordinates.append((symbol, coordinate)) else: - string = islhelper.isl_multi_aff_to_str(expr) + string = islhelper.isl_multi_aff_to_str(expression) matches = self._RE_COORDINATE.finditer(string) for symbol, match in zip(self.symbols, matches): numerator = int(match.group('num')) @@ -578,7 +600,7 @@ class Domain(GeometricObject): elif self.dimension == 3: return self._plot_3d(plot=plot, **kwargs) else: - raise ValueError('polyhedron must be 2 or 3-dimensional') + raise ValueError('domain must be 2 or 3-dimensional') def subs(self, symbol, expression=None): """ @@ -682,17 +704,17 @@ class Domain(GeometricObject): Create a domain from a string. Raise SyntaxError if the string is not properly formatted. """ - # remove curly brackets + # Remove curly brackets. string = cls._RE_BRACES.sub(r'', string) - # replace '=' by '==' + # Replace '=' by '=='. string = cls._RE_EQ.sub(r'\1==\2', string) - # replace 'and', 'or', 'not' + # Replace 'and', 'or', 'not'. string = cls._RE_AND.sub(r' & ', string) string = cls._RE_OR.sub(r' | ', string) string = cls._RE_NOT.sub(r' ~', string) - # add implicit multiplication operators, e.g. '5x' -> '5*x' + # Add implicit multiplication operators, e.g. '5x' -> '5*x'. string = cls._RE_NUM_VAR.sub(r'\1*\2', string) - # add parentheses to force precedence + # Add parentheses to force precedence. tokens = cls._RE_OPERATORS.split(string) for i, token in enumerate(tokens): if i % 2 == 0: @@ -707,16 +729,10 @@ class Domain(GeometricObject): strings = [repr(polyhedron) for polyhedron in self.polyhedra] return 'Or({})'.format(', '.join(strings)) - def _repr_latex_(self): - strings = [] - for polyhedron in self.polyhedra: - strings.append('({})'.format(polyhedron._repr_latex_().strip('$'))) - return '${}$'.format(' \\vee '.join(strings)) - @classmethod - def fromsympy(cls, expr): + def fromsympy(cls, expression): """ - Create a domain from a sympy expression. + Create a domain from a SymPy expression. """ import sympy from .polyhedra import Lt, Le, Eq, Ne, Ge, Gt @@ -726,16 +742,16 @@ class Domain(GeometricObject): sympy.Eq: Eq, sympy.Ne: Ne, sympy.Ge: Ge, sympy.Gt: Gt, } - if expr.func in funcmap: - args = [Domain.fromsympy(arg) for arg in expr.args] - return funcmap[expr.func](*args) - elif isinstance(expr, sympy.Expr): - return LinExpr.fromsympy(expr) - raise ValueError('non-domain expression: {!r}'.format(expr)) + if expression.func in funcmap: + args = [Domain.fromsympy(arg) for arg in expression.args] + return funcmap[expression.func](*args) + elif isinstance(expression, sympy.Expr): + return LinExpr.fromsympy(expression) + raise ValueError('non-domain expression: {!r}'.format(expression)) def tosympy(self): """ - Convert the domain to a sympy expression. + Convert the domain to a SymPy expression. """ import sympy polyhedra = [polyhedron.tosympy() for polyhedron in polyhedra] @@ -751,7 +767,6 @@ def And(*domains): return Universe else: return domains[0].intersection(*domains[1:]) -And.__doc__ = Domain.intersection.__doc__ def Or(*domains): """ @@ -762,11 +777,9 @@ def Or(*domains): return Empty else: return domains[0].union(*domains[1:]) -Or.__doc__ = Domain.union.__doc__ def Not(domain): """ Create the complementary domain of the domain given in argument. """ return ~domain -Not.__doc__ = Domain.complement.__doc__