X-Git-Url: https://scm.cri.mines-paristech.fr/git/linpy.git/blobdiff_plain/36939fa400ed913afd44b37b8bb9c1a7a932e442..34bcb159c06a4725f7a2baaf5ddcc6dffd5d74fe:/pypol/linear.py?ds=sidebyside diff --git a/pypol/linear.py b/pypol/linear.py index 3cd7633..74b5477 100644 --- a/pypol/linear.py +++ b/pypol/linear.py @@ -1,10 +1,12 @@ +import ast import functools import numbers +import re from fractions import Fraction, gcd -from pypol import isl -from pypol.isl import libisl +from . import isl +from .isl import libisl __all__ = [ @@ -48,6 +50,13 @@ class Expression: This class implements linear expressions. """ + __slots__ = ( + '_coefficients', + '_constant', + '_symbols', + '_dimension', + ) + def __new__(cls, coefficients=None, constant=0): if isinstance(coefficients, str): if constant: @@ -69,7 +78,7 @@ class Expression: self._coefficients = {} for symbol, coefficient in coefficients: if isinstance(symbol, Symbol): - symbol = str(symbol) + symbol = symbol.name elif not isinstance(symbol, str): raise TypeError('symbols must be strings or Symbol instances') if isinstance(coefficient, Constant): @@ -86,9 +95,36 @@ class Expression: self._dimension = len(self._symbols) return self + @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.Name): + return Symbol(node.id) + elif isinstance(node, ast.Num): + return Constant(node.n) + elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): + return -cls._fromast(node.operand) + elif isinstance(node, ast.BinOp): + left = cls._fromast(node.left) + right = cls._fromast(node.right) + if isinstance(node.op, ast.Add): + return left + right + elif isinstance(node.op, ast.Sub): + return left - right + elif isinstance(node.op, ast.Mult): + return left * right + elif isinstance(node.op, ast.Div): + return left / right + raise SyntaxError('invalid syntax') + @classmethod def fromstring(cls, string): - raise NotImplementedError + string = re.sub(r'(\d+|\))\s*([^\W\d_]\w*|\()', r'\1*\2', string) + tree = ast.parse(string, 'eval') + return cls._fromast(tree) @property def symbols(self): @@ -126,10 +162,6 @@ class Expression: yield self.coefficient(symbol) yield self.constant - @property - def symbol(self): - raise ValueError('not a symbol: {}'.format(self)) - def issymbol(self): return False @@ -297,6 +329,31 @@ class Expression: def __gt__(self, other): return Polyhedron(inequalities=[(self - other)._toint() - 1]) + @classmethod + def fromsympy(cls, expr): + import sympy + coefficients = {} + constant = 0 + for symbol, coefficient in expr.as_coefficients_dict().items(): + coefficient = Fraction(coefficient.p, coefficient.q) + if symbol == sympy.S.One: + constant = coefficient + elif isinstance(symbol, sympy.Symbol): + symbol = symbol.name + coefficients[symbol] = coefficient + else: + raise ValueError('non-linear expression: {!r}'.format(expr)) + return cls(coefficients, constant) + + def tosympy(self): + import sympy + expr = 0 + for symbol, coefficient in self.coefficients(): + term = coefficient * sympy.Symbol(symbol) + expr += term + expr += self.constant + return expr + class Constant(Expression): @@ -323,33 +380,60 @@ class Constant(Expression): return bool(self.constant) def __repr__(self): - return '{}({!r})'.format(self.__class__.__name__, self._constant) + if self.constant.denominator == 1: + return '{}({!r})'.format(self.__class__.__name__, self.constant) + else: + return '{}({!r}, {!r})'.format(self.__class__.__name__, + self.constant.numerator, self.constant.denominator) + + @classmethod + def fromsympy(cls, expr): + import sympy + if isinstance(expr, sympy.Rational): + return cls(expr.p, expr.q) + elif isinstance(expr, numbers.Rational): + return cls(expr) + else: + raise TypeError('expr must be a sympy.Rational instance') class Symbol(Expression): + __slots__ = Expression.__slots__ + ( + '_name', + ) + def __new__(cls, name): if isinstance(name, Symbol): - name = name.symbol + name = name.name elif not isinstance(name, str): raise TypeError('name must be a string or a Symbol instance') self = object().__new__(cls) self._coefficients = {name: 1} self._constant = 0 self._symbols = tuple(name) - self._symbol = name + self._name = name self._dimension = 1 return self @property - def symbol(self): - return self._symbol + def name(self): + return self._name def issymbol(self): return True def __repr__(self): - return '{}({!r})'.format(self.__class__.__name__, self._symbol) + return '{}({!r})'.format(self.__class__.__name__, self._name) + + @classmethod + def fromsympy(cls, expr): + import sympy + if isinstance(expr, sympy.Symbol): + return cls(expr.name) + else: + raise TypeError('expr must be a sympy.Symbol instance') + def symbols(names): if isinstance(names, str): @@ -383,6 +467,13 @@ class Polyhedron: This class implements polyhedrons. """ + __slots__ = ( + '_equalities', + '_inequalities', + '_constraints', + '_symbols', + ) + def __new__(cls, equalities=None, inequalities=None): if isinstance(equalities, str): if inequalities is not None: @@ -414,9 +505,54 @@ class Polyhedron: self._symbols = tuple(sorted(self._symbols)) return self + @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): - raise NotImplementedError + 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) @property def equalities(self): @@ -528,7 +664,6 @@ class Polyhedron: difference = libisl.isl_set_subtract(bset, other) return difference - def __sub__(self, other): return self.difference(other) @@ -551,6 +686,48 @@ class Polyhedron: return '{}(equalities={!r}, inequalities={!r})' \ ''.format(self.__class__.__name__, equalities, inequalities) + @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 + + @classmethod + def fromsympy(cls, expr): + import sympy + equalities, inequalities = cls._fromsympy(expr) + return cls(equalities, inequalities) + + def tosympy(self): + import sympy + constraints = [] + for equality in self.equalities: + constraints.append(sympy.Eq(equality.tosympy(), 0)) + for inequality in self.inequalities: + constraints.append(sympy.Ge(inequality.tosympy(), 0)) + return sympy.And(*constraints) + def _symbolunion(self, *others): symbols = set(self.symbols) for other in others: @@ -560,42 +737,39 @@ class Polyhedron: def _toisl(self, symbols=None): if symbols is None: symbols = self.symbols - num_coefficients = len(symbols) - space = libisl.isl_space_set_alloc(_main_ctx, 0, num_coefficients) + dimension = len(symbols) + space = libisl.isl_space_set_alloc(_main_ctx, 0, dimension) bset = libisl.isl_basic_set_universe(libisl.isl_space_copy(space)) ls = libisl.isl_local_space_from_space(space) - #if there are equalities/inequalities, take each constant and coefficient and add as a constraint to the basic set - for eq in self.equalities: + for equality in self.equalities: ceq = libisl.isl_equality_alloc(libisl.isl_local_space_copy(ls)) - coeff_eq = dict(eq.coefficients()) - if eq.constant: - value = str(eq.constant).encode() - val = libisl.isl_val_read_from_str(_main_ctx, value) + for symbol, coefficient in equality.coefficients(): + val = str(coefficient).encode() + val = libisl.isl_val_read_from_str(_main_ctx, val) + dim = symbols.index(symbol) + ceq = libisl.isl_constraint_set_coefficient_val(ceq, libisl.isl_dim_set, dim, val) + if equality.constant != 0: + val = str(equality.constant).encode() + val = libisl.isl_val_read_from_str(_main_ctx, val) ceq = libisl.isl_constraint_set_constant_val(ceq, val) - for eq in coeff_eq: - number = str(coeff_eq.get(eq)).encode() - num = libisl.isl_val_read_from_str(_main_ctx, number) - iden = symbols.index(eq) - ceq = libisl.isl_constraint_set_coefficient_val(ceq, libisl.isl_dim_set, iden, num) #use 3 for type isl_dim_set bset = libisl.isl_basic_set_add_constraint(bset, ceq) - for ineq in self.inequalities: + for inequality in self.inequalities: cin = libisl.isl_inequality_alloc(libisl.isl_local_space_copy(ls)) - coeff_in = dict(ineq.coefficients()) - if ineq.constant: - value = str(ineq.constant).encode() - val = libisl.isl_val_read_from_str(_main_ctx, value) + for symbol, coefficient in inequality.coefficients(): + val = str(coefficient).encode() + val = libisl.isl_val_read_from_str(_main_ctx, val) + dim = symbols.index(symbol) + cin = libisl.isl_constraint_set_coefficient_val(cin, libisl.isl_dim_set, dim, val) + if inequality.constant != 0: + val = str(inequality.constant).encode() + val = libisl.isl_val_read_from_str(_main_ctx, val) cin = libisl.isl_constraint_set_constant_val(cin, val) - for ineq in coeff_in: - number = str(coeff_in.get(ineq)).encode() - num = libisl.isl_val_read_from_str(_main_ctx, number) - iden = symbols.index(ineq) - cin = libisl.isl_constraint_set_coefficient_val(cin, libisl.isl_dim_set, iden, num) #use 3 for type isl_dim_set bset = libisl.isl_basic_set_add_constraint(bset, cin) bset = isl.BasicSet(bset) return bset @classmethod - def _fromisl(cls, bset): + def _fromisl(cls, bset, symbols): raise NotImplementedError equalities = ... inequalities = ... @@ -607,11 +781,13 @@ class Polyhedron: { [i0, i1] : 2i1 >= -2 - i0 } ''' Empty = eq(0,1) + Universe = Polyhedron() + if __name__ == '__main__': - ex1 = Expression(coefficients={'a': 6, 'b': 6}, constant= 3) #this is the expression that does not work (even without adding values) - ex2 = Expression(coefficients={'x': 4, 'y': 2}, constant= 3) - p = Polyhedron(equalities=[ex2]) - p2 = Polyhedron(equalities=[ex2]) - print(p._toisl()) # checking is values works for toisl + #p = Polyhedron('2a + 2b + 1 == 0') # empty + p = Polyhedron('3x + 2y + 3 == 0, y == 0') # not empty + ip = p._toisl() + print(ip) + print(ip.constraints())