X-Git-Url: https://svn.cri.ensmp.fr/git/linpy.git/blobdiff_plain/30df3336bf6b16524326d28c2c15475aa2e5328d..288355579f9b2160e341e1d48910a66a0124ab04:/pypol/linear.py?ds=sidebyside diff --git a/pypol/linear.py b/pypol/linear.py index 343acc7..8f73fd3 100644 --- a/pypol/linear.py +++ b/pypol/linear.py @@ -1,32 +1,28 @@ +import ctypes, ctypes.util import functools import numbers -import ctypes, ctypes.util -from pypol import isl from fractions import Fraction, gcd -libisl = ctypes.CDLL(ctypes.util.find_library('isl')) +from . import isl +from .isl import libisl -libisl.isl_printer_get_str.restype = ctypes.c_char_p __all__ = [ - 'Expression', - 'constant', 'symbol', 'symbols', + 'Expression', 'Constant', 'Symbol', 'symbols', 'eq', 'le', 'lt', 'ge', 'gt', 'Polyhedron', 'empty', 'universe' ] -_CONTEXT = isl.Context() - def _polymorphic_method(func): @functools.wraps(func) def wrapper(a, b): if isinstance(b, Expression): return func(a, b) if isinstance(b, numbers.Rational): - b = constant(b) + b = Constant(b) return func(a, b) return NotImplemented return wrapper @@ -37,7 +33,7 @@ def _polymorphic_operator(func): @functools.wraps(func) def wrapper(a, b): if isinstance(a, numbers.Rational): - a = constant(a) + a = Constant(a) return func(a, b) elif isinstance(a, Expression): return func(a, b) @@ -45,6 +41,9 @@ def _polymorphic_operator(func): return wrapper +_main_ctx = isl.Context() + + class Expression: """ This class implements linear expressions. @@ -55,38 +54,56 @@ class Expression: if constant: raise TypeError('too many arguments') return cls.fromstring(coefficients) - self = super().__new__(cls) - self._coefficients = {} if isinstance(coefficients, dict): coefficients = coefficients.items() - if coefficients is not None: - for symbol, coefficient in coefficients: - if isinstance(symbol, Expression) and symbol.issymbol(): - symbol = str(symbol) - elif not isinstance(symbol, str): - raise TypeError('symbols must be strings') - if not isinstance(coefficient, numbers.Rational): - raise TypeError('coefficients must be rational numbers') - if coefficient != 0: - self._coefficients[symbol] = coefficient + if coefficients is None: + return Constant(constant) + coefficients = [(symbol, coefficient) + for symbol, coefficient in coefficients if coefficient != 0] + if len(coefficients) == 0: + return Constant(constant) + elif len(coefficients) == 1 and constant == 0: + symbol, coefficient = coefficients[0] + if coefficient == 1: + return Symbol(symbol) + self = object().__new__(cls) + self._coefficients = {} + for symbol, coefficient in coefficients: + if isinstance(symbol, Symbol): + symbol = str(symbol) + elif not isinstance(symbol, str): + raise TypeError('symbols must be strings or Symbol instances') + if isinstance(coefficient, Constant): + coefficient = coefficient.constant + if not isinstance(coefficient, numbers.Rational): + raise TypeError('coefficients must be rational numbers or Constant instances') + self._coefficients[symbol] = coefficient + if isinstance(constant, Constant): + constant = constant.constant if not isinstance(constant, numbers.Rational): - raise TypeError('constant must be a rational number') + raise TypeError('constant must be a rational number or a Constant instance') self._constant = constant + self._symbols = tuple(sorted(self._coefficients)) + self._dimension = len(self._symbols) return self + @classmethod + def fromstring(cls, string): + raise NotImplementedError + @property def symbols(self): - yield from sorted(self._coefficients) + return self._symbols @property def dimension(self): - return len(list(self.symbols())) + return self._dimension def coefficient(self, symbol): - if isinstance(symbol, Expression) and symbol.issymbol(): + if isinstance(symbol, Symbol): symbol = str(symbol) elif not isinstance(symbol, str): - raise TypeError('symbol must be a string') + raise TypeError('symbol must be a string or a Symbol instance') try: return self._coefficients[symbol] except KeyError: @@ -95,7 +112,7 @@ class Expression: __getitem__ = coefficient def coefficients(self): - for symbol in self.symbols(): + for symbol in self.symbols: yield symbol, self.coefficient(symbol) @property @@ -103,30 +120,22 @@ class Expression: return self._constant def isconstant(self): - return len(self._coefficients) == 0 + return False def values(self): - for symbol in self.symbols(): + for symbol in self.symbols: yield self.coefficient(symbol) yield self.constant - def values_int(self): - for symbol in self.symbols(): - return self.coefficient(symbol) - return int(self.constant) - - + @property def symbol(self): - if not self.issymbol(): - raise ValueError('not a symbol: {}'.format(self)) - for symbol in self.symbols(): - return symbol + raise ValueError('not a symbol: {}'.format(self)) def issymbol(self): - return len(self._coefficients) == 1 and self._constant == 0 + return False def __bool__(self): - return (not self.isconstant()) or bool(self.constant) + True def __pos__(self): return self @@ -202,10 +211,9 @@ class Expression: def __str__(self): string = '' - symbols = sorted(self.symbols()) i = 0 - for symbol in symbols: - coefficient = self[symbol] + for symbol in self.symbols: + coefficient = self.coefficient(symbol) if coefficient == 1: if i == 0: string += symbol @@ -254,10 +262,6 @@ class Expression: string += '}}, {!r})'.format(self.constant) return string - @classmethod - def fromstring(cls, string): - raise NotImplementedError - @_polymorphic_method def __eq__(self, other): # "normal" equality @@ -269,42 +273,84 @@ class Expression: def __hash__(self): return hash((self._coefficients, self._constant)) - def _canonify(self): + def _toint(self): lcm = functools.reduce(lambda a, b: a*b // gcd(a, b), [value.denominator for value in self.values()]) return self * lcm @_polymorphic_method def _eq(self, other): - return Polyhedron(equalities=[(self - other)._canonify()]) + return Polyhedron(equalities=[(self - other)._toint()]) @_polymorphic_method def __le__(self, other): - return Polyhedron(inequalities=[(self - other)._canonify()]) + return Polyhedron(inequalities=[(other - self)._toint()]) @_polymorphic_method def __lt__(self, other): - return Polyhedron(inequalities=[(self - other)._canonify() + 1]) + return Polyhedron(inequalities=[(other - self)._toint() - 1]) @_polymorphic_method def __ge__(self, other): - return Polyhedron(inequalities=[(other - self)._canonify()]) + return Polyhedron(inequalities=[(self - other)._toint()]) @_polymorphic_method def __gt__(self, other): - return Polyhedron(inequalities=[(other - self)._canonify() + 1]) + return Polyhedron(inequalities=[(self - other)._toint() - 1]) + + +class Constant(Expression): + + def __new__(cls, numerator=0, denominator=None): + self = object().__new__(cls) + if denominator is None: + if isinstance(numerator, numbers.Rational): + self._constant = numerator + elif isinstance(numerator, Constant): + self._constant = numerator.constant + else: + raise TypeError('constant must be a rational number or a Constant instance') + else: + self._constant = Fraction(numerator, denominator) + self._coefficients = {} + self._symbols = () + self._dimension = 0 + return self + def isconstant(self): + return True -def constant(numerator=0, denominator=None): - if denominator is None and isinstance(numerator, numbers.Rational): - return Expression(constant=3) - else: - return Expression(constant=Fraction(numerator, denominator)) + def __bool__(self): + return bool(self.constant) -def symbol(name): - if not isinstance(name, str): - raise TypeError('name must be a string') - return Expression(coefficients={name: 1}) + def __repr__(self): + return '{}({!r})'.format(self.__class__.__name__, self._constant) + + +class Symbol(Expression): + + def __new__(cls, name): + if isinstance(name, Symbol): + name = name.symbol + 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._dimension = 1 + return self + + @property + def symbol(self): + return self._symbol + + def issymbol(self): + return True + + def __repr__(self): + return '{}({!r})'.format(self.__class__.__name__, self._symbol) def symbols(names): if isinstance(names, str): @@ -352,6 +398,7 @@ class Polyhedron: raise TypeError('non-integer constraint: ' '{} == 0'.format(constraint)) self._equalities.append(constraint) + self._equalities = tuple(self._equalities) self._inequalities = [] if inequalities is not None: for constraint in inequalities: @@ -359,58 +406,41 @@ class Polyhedron: if value.denominator != 1: raise TypeError('non-integer constraint: ' '{} <= 0'.format(constraint)) - self._inequalities.append(constraint) - self._bset = self.to_isl() - return self._bset - - + self._inequalities.append(constraint) + self._inequalities = tuple(self._inequalities) + self._constraints = self._equalities + self._inequalities + self._symbols = set() + for constraint in self._constraints: + self.symbols.update(constraint.symbols) + self._symbols = tuple(sorted(self._symbols)) + return self + + @classmethod + def fromstring(cls, string): + raise NotImplementedError + @property def equalities(self): - yield from self._equalities + return self._equalities @property def inequalities(self): - yield from self._inequalities - - @property - def constant(self): - return self._constant - - def isconstant(self): - return len(self._coefficients) == 0 - - - def isempty(self): - return bool(libisl.isl_basic_set_is_empty(self._bset)) + return self._inequalities + @property def constraints(self): - yield from self.equalities - yield from self.inequalities - + return self._constraints + @property def symbols(self): - s = set() - for constraint in self.constraints(): - s.update(constraint.symbols) - yield from sorted(s) - - def symbol_count(self): - s = [] - for constraint in self.constraints(): - s.append(constraint.symbols) - return s - + return self._symbols + @property def dimension(self): - return len(self.symbols()) + return len(self.symbols) def __bool__(self): - # return false if the polyhedron is empty, true otherwise - if self._equalities or self._inequalities: - return False - else: - return True - + return not self.is_empty() def __contains__(self, value): # is the value in the polyhedron? @@ -419,11 +449,12 @@ class Polyhedron: def __eq__(self, other): raise NotImplementedError - def is_empty(self): - return + def isempty(self): + bset = self._toisl() + return bool(libisl.isl_basic_set_is_empty(bset)) def isuniverse(self): - return self == universe + raise NotImplementedError def isdisjoint(self, other): # return true if the polyhedron has no elements in common with other @@ -440,11 +471,6 @@ class Polyhedron: def issuperset(self, other): # test whether every element in other is in the polyhedron - for value in other: - if value == self.constraints(): - return True - else: - return False raise NotImplementedError def __ge__(self, other): @@ -489,7 +515,7 @@ class Polyhedron: for constraint in self.equalities: constraints.append('{} == 0'.format(constraint)) for constraint in self.inequalities: - constraints.append('{} <= 0'.format(constraint)) + constraints.append('{} >= 0'.format(constraint)) return '{{{}}}'.format(', '.join(constraints)) def __repr__(self): @@ -498,47 +524,75 @@ class Polyhedron: return '{}(equalities={!r}, inequalities={!r})' \ ''.format(self.__class__.__name__, equalities, inequalities) - @classmethod - def fromstring(cls, string): - raise NotImplementedError - - def to_isl(self): - space = libisl.isl_space_set_alloc(_CONTEXT, 0, len(self.symbol_count())) - bset = libisl.isl_basic_set_empty(libisl.isl_space_copy(space)) - ls = libisl.isl_local_space_from_space(libisl.isl_space_copy(space)) + def _symbolunion(self, *others): + symbols = set(self.symbols) + for other in others: + symbols.update(other.symbols) + return sorted(symbols) + + 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) + bset = libisl.isl_basic_set_universe(libisl.isl_space_copy(space)) + ls = libisl.isl_local_space_from_space(space) ceq = libisl.isl_equality_alloc(libisl.isl_local_space_copy(ls)) cin = libisl.isl_inequality_alloc(libisl.isl_local_space_copy(ls)) - d = Expression().__dict__ #write expression values to dictionary in form {'_constant': value, '_coefficients': value} - ''' - if there are equalities/inequalities, take each constant and coefficient and add as a constraint to the basic set - need to change the symbols method to a lookup table for the integer value for each letter that could be a symbol - ''' - if self._equalities: - if '_constant' in d: - value = d.get('_constant') - ceq = libisl.isl_constraint_set_constant_si(ceq, value) - if '_coefficients' in d: - value_co = d.get('_coefficients') - if value_co: #if dictionary not empty add coefficient as to constraint - ceq = libisl.isl_constraint_set_coefficient_si(ceq, libisl.isl_set_dim, self.symbols(), value_co) - bset = libisl.isl_set_add_constraint(bset, ceq) - - if self._inequalities: - if '_constant' in d: - value = d.get('_constant') - cin = libisl.isl_constraint_set_constant_si(cin, value) - if '_coefficients' in d: - value_co = d.get('_coefficients') - if value_co: #if dictionary not empty add coefficient as to constraint - cin = libisl.isl_constraint_set_coefficient_si(cin, libisl.isl_set_dim, self.symbols(), value_co) - bset = libisl.isl_set_add_constraint(bset, cin) - ip = libisl.isl_printer_to_str(_CONTEXT) #create string printer - ip = libisl.isl_printer_print_set(ip, bset) #print set to printer - string = libisl.isl_printer_get_str(ip) #get string from printer - print(string) + '''if there are equalities/inequalities, take each constant and coefficient and add as a constraint to the basic set''' + if list(self.equalities): #check if any equalities exist + for eq in self.equalities: + coeff_eq = dict(eq.coefficients()) + if eq.constant: + value = eq.constant + ceq = libisl.isl_constraint_set_constant_si(ceq, value) + for eq in coeff_eq: + num = coeff_eq.get(eq) + iden = symbols.index(eq) + ceq = libisl.isl_constraint_set_coefficient_si(ceq, libisl.isl_dim_set, iden, num) #use 3 for type isl_dim_set + bset = libisl.isl_basic_set_add_constraint(bset, ceq) + if list(self.inequalities): #check if any inequalities exist + for ineq in self.inequalities: + coeff_in = dict(ineq.coefficients()) + if ineq.constant: + value = ineq.constant + cin = libisl.isl_constraint_set_constant_si(cin, value) + for ineq in coeff_in: + num = coeff_in.get(ineq) + iden = symbols.index(ineq) + cin = libisl.isl_constraint_set_coefficient_si(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 -empty = eq(1, 1) - - -universe = Polyhedron() + @classmethod + def _fromisl(cls, bset): + raise NotImplementedError + equalities = ... + inequalities = ... + return cls(equalities, inequalities) + '''takes basic set in isl form and puts back into python version of polyhedron + isl example code gives isl form as: + "{[i] : exists (a : i = 2a and i >= 10 and i <= 42)}") + our printer is giving form as: + b'{ [i0] : 1 = 0 }' ''' + #bset = self + # if self._equalities: + # constraints = libisl.isl_basic_set_equalities_matrix(bset, 3) + # elif self._inequalities: + # constraints = libisl.isl_basic_set_inequalities_matrix(bset, 3) + # print(constraints) + # return constraints + +empty = None #eq(0,1) +universe = None #Polyhedron() + + +if __name__ == '__main__': + ex1 = Expression(coefficients={'a': 1, 'x': 2}, constant=2) + ex2 = Expression(coefficients={'a': 3 , 'b': 2}, constant=3) + p = Polyhedron(inequalities=[ex1, ex2]) + bs = p._toisl() + print(bs) + print('empty ?', p.isempty()) + print('empty ?', eq(0, 1).isempty())