X-Git-Url: https://scm.cri.mines-paristech.fr/git/linpy.git/blobdiff_plain/1d494bb187b70135df721c13306d7f26fdf33f50..114cd46edf08bcf13c5f489c449db98f6098c468:/pypol/polyhedra.py?ds=sidebyside diff --git a/pypol/polyhedra.py b/pypol/polyhedra.py index 787e965..f8d413e 100644 --- a/pypol/polyhedra.py +++ b/pypol/polyhedra.py @@ -1,12 +1,12 @@ -import ast import functools +import math import numbers -import re from . import islhelper from .islhelper import mainctx, libisl -from .linexprs import Expression, Constant +from .geometry import GeometricObject, Point +from .linexprs import Expression, Rational from .domains import Domain @@ -32,28 +32,24 @@ class Polyhedron(Domain): if inequalities is not None: raise TypeError('too many arguments') return cls.fromstring(equalities) - elif isinstance(equalities, Polyhedron): + elif isinstance(equalities, GeometricObject): if inequalities is not None: raise TypeError('too many arguments') - return equalities - elif isinstance(equalities, Domain): - if inequalities is not None: - raise TypeError('too many arguments') - return equalities.polyhedral_hull() + return equalities.aspolyhedron() if equalities is None: equalities = [] else: for i, equality in enumerate(equalities): if not isinstance(equality, Expression): raise TypeError('equalities must be linear expressions') - equalities[i] = equality._toint() + equalities[i] = equality.scaleint() if inequalities is None: inequalities = [] else: for i, inequality in enumerate(inequalities): if not isinstance(inequality, Expression): raise TypeError('inequalities must be linear expressions') - inequalities[i] = inequality._toint() + inequalities[i] = inequality.scaleint() symbols = cls._xsymbols(equalities + inequalities) islbset = cls._toislbasicset(equalities, inequalities, symbols) return cls._fromislbasicset(islbset, symbols) @@ -75,30 +71,63 @@ class Polyhedron(Domain): return self, def disjoint(self): + """ + Return this set as disjoint. + """ return self def isuniverse(self): + """ + Return true if this set is the Universe set. + """ islbset = self._toislbasicset(self.equalities, self.inequalities, self.symbols) universe = bool(libisl.isl_basic_set_is_universe(islbset)) libisl.isl_basic_set_free(islbset) return universe - def polyhedral_hull(self): + def aspolyhedron(self): + """ + Return polyhedral hull of this set. + """ return self + def __contains__(self, point): + if not isinstance(point, Point): + raise TypeError('point must be a Point instance') + if self.symbols != point.symbols: + raise ValueError('arguments must belong to the same space') + for equality in self.equalities: + if equality.subs(point.coordinates()) != 0: + return False + for inequality in self.inequalities: + if inequality.subs(point.coordinates()) < 0: + return False + return True + + def subs(self, symbol, expression=None): + equalities = [equality.subs(symbol, expression) + for equality in self.equalities] + inequalities = [inequality.subs(symbol, expression) + for inequality in self.inequalities] + return Polyhedron(equalities, inequalities) + @classmethod def _fromislbasicset(cls, islbset, symbols): + if libisl.isl_basic_set_is_empty(islbset): + return Empty + if libisl.isl_basic_set_is_universe(islbset): + return Universe islconstraints = islhelper.isl_basic_set_constraints(islbset) equalities = [] inequalities = [] for islconstraint in islconstraints: - islpr = libisl.isl_printer_to_str(mainctx) constant = libisl.isl_constraint_get_constant_val(islconstraint) constant = islhelper.isl_val_to_int(constant) coefficients = {} - for dim, symbol in enumerate(symbols): - coefficient = libisl.isl_constraint_get_coefficient_val(islconstraint, libisl.isl_dim_set, dim) + for index, symbol in enumerate(symbols): + coefficient = libisl.isl_constraint_get_coefficient_val(islconstraint, + libisl.isl_dim_set, index) coefficient = islhelper.isl_val_to_int(coefficient) if coefficient != 0: coefficients[symbol] = coefficient @@ -119,134 +148,70 @@ class Polyhedron(Domain): @classmethod def _toislbasicset(cls, equalities, inequalities, symbols): dimension = len(symbols) + indices = {symbol: index for index, symbol in enumerate(symbols)} islsp = libisl.isl_space_set_alloc(mainctx, 0, dimension) islbset = libisl.isl_basic_set_universe(libisl.isl_space_copy(islsp)) islls = libisl.isl_local_space_from_space(islsp) for equality in equalities: isleq = libisl.isl_equality_alloc(libisl.isl_local_space_copy(islls)) for symbol, coefficient in equality.coefficients(): - val = str(coefficient).encode() - val = libisl.isl_val_read_from_str(mainctx, val) - sid = symbols.index(symbol) + islval = str(coefficient).encode() + islval = libisl.isl_val_read_from_str(mainctx, islval) + index = indices[symbol] isleq = libisl.isl_constraint_set_coefficient_val(isleq, - libisl.isl_dim_set, sid, val) + libisl.isl_dim_set, index, islval) if equality.constant != 0: - val = str(equality.constant).encode() - val = libisl.isl_val_read_from_str(mainctx, val) - isleq = libisl.isl_constraint_set_constant_val(isleq, val) + islval = str(equality.constant).encode() + islval = libisl.isl_val_read_from_str(mainctx, islval) + isleq = libisl.isl_constraint_set_constant_val(isleq, islval) islbset = libisl.isl_basic_set_add_constraint(islbset, isleq) for inequality in inequalities: islin = libisl.isl_inequality_alloc(libisl.isl_local_space_copy(islls)) for symbol, coefficient in inequality.coefficients(): - val = str(coefficient).encode() - val = libisl.isl_val_read_from_str(mainctx, val) - sid = symbols.index(symbol) + islval = str(coefficient).encode() + islval = libisl.isl_val_read_from_str(mainctx, islval) + index = indices[symbol] islin = libisl.isl_constraint_set_coefficient_val(islin, - libisl.isl_dim_set, sid, val) + libisl.isl_dim_set, index, islval) if inequality.constant != 0: - val = str(inequality.constant).encode() - val = libisl.isl_val_read_from_str(mainctx, val) - islin = libisl.isl_constraint_set_constant_val(islin, val) + islval = str(inequality.constant).encode() + islval = libisl.isl_val_read_from_str(mainctx, islval) + islin = libisl.isl_constraint_set_constant_val(islin, islval) islbset = libisl.isl_basic_set_add_constraint(islbset, islin) return islbset - @classmethod - def _fromast(cls, node): - if isinstance(node, ast.Module) and len(node.body) == 1: - return cls._fromast(node.body[0]) - elif isinstance(node, ast.Expr): - return cls._fromast(node.value) - elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitAnd): - equalities1, inequalities1 = cls._fromast(node.left) - equalities2, inequalities2 = cls._fromast(node.right) - equalities = equalities1 + equalities2 - inequalities = inequalities1 + inequalities2 - return equalities, inequalities - elif isinstance(node, ast.Compare): - equalities = [] - inequalities = [] - left = Expression._fromast(node.left) - for i in range(len(node.ops)): - op = node.ops[i] - right = Expression._fromast(node.comparators[i]) - if isinstance(op, ast.Lt): - inequalities.append(right - left - 1) - elif isinstance(op, ast.LtE): - inequalities.append(right - left) - elif isinstance(op, ast.Eq): - equalities.append(left - right) - elif isinstance(op, ast.GtE): - inequalities.append(left - right) - elif isinstance(op, ast.Gt): - inequalities.append(left - right - 1) - else: - break - left = right - else: - return equalities, inequalities - raise SyntaxError('invalid syntax') - @classmethod def fromstring(cls, string): - string = string.strip() - string = re.sub(r'^\{\s*|\s*\}$', '', string) - string = re.sub(r'([^<=>])=([^<=>])', r'\1==\2', string) - string = re.sub(r'(\d+|\))\s*([^\W\d_]\w*|\()', r'\1*\2', string) - tokens = re.split(r',|;|and|&&|/\\|∧', string, flags=re.I) - tokens = ['({})'.format(token) for token in tokens] - string = ' & '.join(tokens) - tree = ast.parse(string, 'eval') - equalities, inequalities = cls._fromast(tree) - return cls(equalities, inequalities) + domain = Domain.fromstring(string) + if not isinstance(domain, Polyhedron): + raise ValueError('non-polyhedral expression: {!r}'.format(string)) + 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('Eq({}, 0)'.format(equality)) - for inequality in self.inequalities: - strings.append('Ge({}, 0)'.format(inequality)) - if len(strings) == 1: - return strings[0] - else: - return 'And({})'.format(', '.join(strings)) + return 'And({})'.format(', '.join(strings)) - @classmethod - def _fromsympy(cls, expr): - import sympy - equalities = [] - inequalities = [] - if expr.func == sympy.And: - for arg in expr.args: - arg_eqs, arg_ins = cls._fromsympy(arg) - equalities.extend(arg_eqs) - inequalities.extend(arg_ins) - elif expr.func == sympy.Eq: - expr = Expression.fromsympy(expr.args[0] - expr.args[1]) - equalities.append(expr) - else: - if expr.func == sympy.Lt: - expr = Expression.fromsympy(expr.args[1] - expr.args[0] - 1) - elif expr.func == sympy.Le: - expr = Expression.fromsympy(expr.args[1] - expr.args[0]) - elif expr.func == sympy.Ge: - expr = Expression.fromsympy(expr.args[0] - expr.args[1]) - elif expr.func == sympy.Gt: - expr = Expression.fromsympy(expr.args[0] - expr.args[1] - 1) - else: - raise ValueError('non-polyhedral expression: {!r}'.format(expr)) - inequalities.append(expr) - return equalities, inequalities + 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): - import sympy - equalities, inequalities = cls._fromsympy(expr) - return cls(equalities, inequalities) + domain = Domain.fromsympy(expr) + if not isinstance(domain, Polyhedron): + raise ValueError('non-polyhedral expression: {!r}'.format(expr)) + return domain def tosympy(self): import sympy @@ -258,47 +223,106 @@ class Polyhedron(Domain): return sympy.And(*constraints) +class EmptyType(Polyhedron): + + __slots__ = Polyhedron.__slots__ + + def __new__(cls): + self = object().__new__(cls) + self._equalities = (Rational(1),) + self._inequalities = () + self._constraints = self._equalities + self._symbols = () + self._dimension = 0 + return self + + 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 = Constant(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 = Constant(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): + """ + Return true if the first set is less than the second. + """ return Polyhedron([], [right - left - 1]) @_polymorphic def Le(left, right): + """ + Return true the first set is less than or equal to the second. + """ return Polyhedron([], [right - left]) @_polymorphic def Eq(left, right): + """ + Return true if the sets are equal. + """ return Polyhedron([left - right], []) @_polymorphic def Ne(left, right): + """ + Return true if the sets are NOT equal. + """ return ~Eq(left, right) @_polymorphic def Gt(left, right): + """ + Return true if the first set is greater than the second set. + """ return Polyhedron([], [left - right - 1]) @_polymorphic def Ge(left, right): + """ + Return true if the first set is greater than or equal the second set. + """ return Polyhedron([], [left - right]) - - -Empty = Eq(1, 0) - -Universe = Polyhedron([])