From: Vivien Maisonneuve Date: Sun, 8 Jun 2014 15:40:34 +0000 (+0200) Subject: Implementation using iscc X-Git-Url: https://scm.cri.mines-paristech.fr/git/linpy.git/commitdiff_plain/096532c477811ce4fe26f2e6a82cb0b795bdeca2?ds=inline;hp=ddd4ffae2055577597ec46bf195bd401aaa9c625 Implementation using iscc --- diff --git a/Makefile b/Makefile index 4ab5d2a..d45d3b2 100644 --- a/Makefile +++ b/Makefile @@ -3,16 +3,11 @@ RM=rm -rf .PHONY: default default: - @echo "pypol - A polyhedral library based on ISL" + @echo "polyp - A polyhedral library based on ISL" @echo @echo "Makefile usage:" - @echo " make test run the test suite" @echo " make clean remove the generated files" -.PHONY: test -test: - $(PYTHON) -m unittest - .PHONY: clean clean: - $(RM) build dist MANIFEST pypol/__pycache__ tests/__pycache__ + $(RM) build dist MANIFEST __pycache__ diff --git a/pypol/linear.py b/polyp.py similarity index 58% rename from pypol/linear.py rename to polyp.py index a5f55fa..b73814e 100644 --- a/pypol/linear.py +++ b/polyp.py @@ -1,26 +1,43 @@ import functools import numbers +import re +import subprocess from fractions import Fraction, gcd __all__ = [ 'Expression', - 'constant', 'symbol', 'symbols', - 'eq', 'le', 'lt', 'ge', 'gt', + 'Constant', 'Symbol', 'symbols', + 'Eq', 'Le', 'Lt', 'Ge', 'Gt', 'Polyhedron', 'empty', 'universe' ] +_iscc_debug = False + +def _iscc(input): + if not input.endswith(';'): + input += ';' + proc = subprocess.Popen(['iscc'], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + universal_newlines=True) + output, error = proc.communicate(input=input) + output = output.strip() + if _iscc_debug: + print('ISCC({!r}) = {!r}'.format(input, output)) + return output + + 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 @@ -31,7 +48,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,10 +62,6 @@ class Expression: """ def __new__(cls, coefficients=None, constant=0): - if isinstance(coefficients, str): - if constant: - raise TypeError('too many arguments') - return cls.fromstring(coefficients) self = super().__new__(cls) self._coefficients = {} if isinstance(coefficients, dict): @@ -68,6 +81,16 @@ class Expression: self._constant = constant return self + @classmethod + def _fromiscc(cls, symbols, string): + string = re.sub(r'(\d+)\s*([a-zA-Z_]\w*)', + lambda m: '{}*{}'.format(m.group(1), m.group(2)), + string) + context = {} + for symbol in symbols: + context[symbol] = Symbol(symbol) + return eval(string, context) + def symbols(self): yield from sorted(self._coefficients) @@ -145,7 +168,8 @@ class Expression: constant = self.constant - other.constant return Expression(coefficients, constant) - __rsub__ = __sub__ + def __rsub__(self, other): + return -(self - other) @_polymorphic_method def __mul__(self, other): @@ -157,36 +181,12 @@ class Expression: return Expression(coefficients, constant) if isinstance(other, Expression) and not self.isconstant(): raise ValueError('non-linear expression: ' - '{} * {}'.format(self._parenstr(), other._parenstr())) + '{} * {}'.format(self._prepr(), other._prepr())) return NotImplemented __rmul__ = __mul__ - @_polymorphic_method - def __truediv__(self, other): - if other.isconstant(): - coefficients = dict(self.coefficients()) - for symbol in coefficients: - coefficients[symbol] = \ - Fraction(coefficients[symbol], other.constant) - constant = Fraction(self.constant, other.constant) - return Expression(coefficients, constant) - if isinstance(other, Expression): - raise ValueError('non-linear expression: ' - '{} / {}'.format(self._parenstr(), other._parenstr())) - return NotImplemented - - def __rtruediv__(self, other): - if isinstance(other, Rational): - if self.isconstant(): - constant = Fraction(other, self.constant) - return Expression(constant=constant) - else: - raise ValueError('non-linear expression: ' - '{} / {}'.format(other._parenstr(), self._parenstr())) - return NotImplemented - - def __str__(self): + def __repr__(self): string = '' symbols = sorted(self.symbols()) i = 0 @@ -224,26 +224,13 @@ class Expression: string = '0' return string - def _parenstr(self, always=False): + def _prepr(self, always=False): string = str(self) if not always and (self.isconstant() or self.issymbol()): return string else: return '({})'.format(string) - def __repr__(self): - string = '{}({{'.format(self.__class__.__name__) - for i, (symbol, coefficient) in enumerate(self.coefficients()): - if i != 0: - string += ', ' - string += '{!r}: {!r}'.format(symbol, coefficient) - string += '}}, {!r})'.format(self.constant) - return string - - @classmethod - def fromstring(cls, string): - raise NotImplementedError - @_polymorphic_method def __eq__(self, other): # "normal" equality @@ -253,41 +240,36 @@ class Expression: self.constant == other.constant def __hash__(self): - return hash((self._coefficients, self._constant)) - - def _canonify(self): - lcm = functools.reduce(lambda a, b: a*b // gcd(a, b), - [value.denominator for value in self.values()]) - return self * lcm + return hash((tuple(self._coefficients), self._constant)) @_polymorphic_method def _eq(self, other): - return Polyhedron(equalities=[(self - other)._canonify()]) + return Polyhedron(equalities=[self - other]) @_polymorphic_method def __le__(self, other): - return Polyhedron(inequalities=[(self - other)._canonify()]) + return Polyhedron(inequalities=[self - other]) @_polymorphic_method def __lt__(self, other): - return Polyhedron(inequalities=[(self - other)._canonify() + 1]) + return Polyhedron(inequalities=[self - other + 1]) @_polymorphic_method def __ge__(self, other): - return Polyhedron(inequalities=[(other - self)._canonify()]) + return Polyhedron(inequalities=[other - self]) @_polymorphic_method def __gt__(self, other): - return Polyhedron(inequalities=[(other - self)._canonify() + 1]) + return Polyhedron(inequalities=[other - self + 1]) -def constant(numerator=0, denominator=None): +def Constant(numerator=0, denominator=None): if denominator is None and isinstance(numerator, numbers.Rational): return Expression(constant=numerator) else: return Expression(constant=Fraction(numerator, denominator)) -def symbol(name): +def Symbol(name): if not isinstance(name, str): raise TypeError('name must be a string') return Expression(coefficients={name: 1}) @@ -295,27 +277,27 @@ def symbol(name): def symbols(names): if isinstance(names, str): names = names.replace(',', ' ').split() - return (symbol(name) for name in names) + return (Symbol(name) for name in names) @_polymorphic_operator -def eq(a, b): +def Eq(a, b): return a._eq(b) @_polymorphic_operator -def le(a, b): +def Le(a, b): return a <= b @_polymorphic_operator -def lt(a, b): +def Lt(a, b): return a < b @_polymorphic_operator -def ge(a, b): +def Ge(a, b): return a >= b @_polymorphic_operator -def gt(a, b): +def Gt(a, b): return a > b @@ -325,27 +307,70 @@ class Polyhedron: """ def __new__(cls, equalities=None, inequalities=None): - if isinstance(equalities, str): - if inequalities is not None: - raise TypeError('too many arguments') - return cls.fromstring(equalities) + if equalities is None: + equalities = [] + if inequalities is None: + inequalities = [] + symbols = set() + for equality in equalities: + symbols.update(equality.symbols()) + for inequality in inequalities: + symbols.update(inequality.symbols()) + symbols = sorted(symbols) + string = cls._isccpoly(symbols, equalities, inequalities) + string = _iscc(string) + return cls._fromiscc(symbols, string) + + @classmethod + def _isccpoly(cls, symbols, equalities, inequalities): + strings = [] + for equality in equalities: + strings.append('{} = 0'.format(equality)) + for inequality in inequalities: + strings.append('{} <= 0'.format(inequality)) + string = '{{ [{}] : {} }}'.format(', '.join(symbols), ' and '.join(strings)) + return string + + def _toiscc(self, symbols): + return self._isccpoly(symbols, self.equalities, self.inequalities) + + @classmethod + def _fromiscc(cls, symbols, string): + if re.match(r'^\s*\{\s*\}\s*$', string): + return empty self = super().__new__(cls) + self._symbols = symbols self._equalities = [] - if equalities is not None: - for constraint in equalities: - for value in constraint.values(): - if value.denominator != 1: - raise TypeError('non-integer constraint: ' - '{} == 0'.format(constraint)) - self._equalities.append(constraint) self._inequalities = [] - if inequalities is not None: - for constraint in inequalities: - for value in constraint.values(): - if value.denominator != 1: - raise TypeError('non-integer constraint: ' - '{} <= 0'.format(constraint)) - self._inequalities.append(constraint) + string = re.sub(r'^\s*\{\s*(.*?)\s*\}\s*$', lambda m: m.group(1), string) + if ':' not in string: + string = string + ':' + vstr, cstr = re.split(r'\s*:\s*', string) + assert vstr != '' + vstr = re.sub(r'^\s*\[\s*(.*?)\s*\]\s*$', lambda m: m.group(1), vstr) + toks = list(filter(None, re.split(r'\s*,\s*', vstr))) + assert len(toks) == len(symbols) + for i in range(len(symbols)): + symbol = symbols[i] + if toks[i] != symbol: + expr = Expression._fromiscc(symbols, toks[i]) + self._equalities.append(Symbol(symbol) - expr) + if cstr != '': + cstrs = re.split(r'\s*and\s*', cstr) + for cstr in cstrs: + lhs, op, rhs = re.split(r'\s*(<=|>=|<|>|=)\s*', cstr) + lhs = Expression._fromiscc(symbols, lhs) + rhs = Expression._fromiscc(symbols, rhs) + if op == '=': + self._equalities.append(lhs - rhs) + elif op == '<=': + self._inequalities.append(lhs - rhs) + elif op == '>=': + self._inequalities.append(rhs - lhs) + elif op == '<': + self._inequalities.append(lhs - rhs + 1) + elif op == '>': + self._inequalities.append(rhs - lhs + 1) return self @property @@ -361,10 +386,7 @@ class Polyhedron: yield from self.inequalities def symbols(self): - s = set() - for constraint in self.constraints(): - s.update(constraint.symbols) - yield from sorted(s) + yield from self._symbols @property def dimension(self): @@ -372,14 +394,13 @@ class Polyhedron: def __bool__(self): # return false if the polyhedron is empty, true otherwise - raise NotImplementedError - - def __contains__(self, value): - # is the value in the polyhedron? - raise NotImplementedError + raise not self.isempty() def __eq__(self, other): - raise NotImplementedError + symbols = set(self.symbols()) | set(other.symbols()) + string = '{} = {}'.format(self._toiscc(symbols), other._toiscc(symbols)) + string = _iscc(string) + return string == 'True' def isempty(self): return self == empty @@ -389,46 +410,67 @@ class Polyhedron: def isdisjoint(self, other): # return true if the polyhedron has no elements in common with other - raise NotImplementedError + return (self & other).isempty() def issubset(self, other): - raise NotImplementedError + symbols = set(self.symbols()) | set(other.symbols()) + string = '{} <= {}'.format(self._toiscc(symbols), other._toiscc(symbols)) + string = _iscc(string) + return string == 'True' def __le__(self, other): return self.issubset(other) def __lt__(self, other): - raise NotImplementedError + symbols = set(self.symbols()) | set(other.symbols()) + string = '{} < {}'.format(self._toiscc(symbols), other._toiscc(symbols)) + string = _iscc(string) + return string == 'True' def issuperset(self, other): # test whether every element in other is in the polyhedron - raise NotImplementedError + symbols = set(self.symbols()) | set(other.symbols()) + string = '{} >= {}'.format(self._toiscc(symbols), other._toiscc(symbols)) + string = _iscc(string) + return string == 'True' def __ge__(self, other): return self.issuperset(other) def __gt__(self, other): - raise NotImplementedError + symbols = set(self.symbols() + other.symbols()) + string = '{} > {}'.format(self._toiscc(symbols), other._toiscc(symbols)) + string = _iscc(string) + return string == 'True' def union(self, *others): # return a new polyhedron with elements from the polyhedron and all # others (convex union) - raise NotImplementedError + symbols = set(self.symbols()) + for other in others: + symbols.update(other.symbols()) + symbols = sorted(symbols) + strings = [self._toiscc(symbols)] + for other in others: + strings.append(other._toiscc(symbols)) + string = ' + '.join(strings) + string = _iscc('poly ({})'.format(string)) + return Polyhedron._fromiscc(symbols, string) def __or__(self, other): return self.union(other) def intersection(self, *others): - # return a new polyhedron with elements common to the polyhedron and all - # others - # a poor man's implementation could be: - # equalities = list(self.equalities) - # inequalities = list(self.inequalities) - # for other in others: - # equalities.extend(other.equalities) - # inequalities.extend(other.inequalities) - # return self.__class__(equalities, inequalities) - raise NotImplementedError + symbols = set(self.symbols()) + for other in others: + symbols.update(other.symbols()) + symbols = sorted(symbols) + strings = [self._toiscc(symbols)] + for other in others: + strings.append(other._toiscc(symbols)) + string = ' * '.join(strings) + string = _iscc('poly ({})'.format(string)) + return Polyhedron._fromiscc(symbols, string) def __and__(self, other): return self.intersection(other) @@ -436,30 +478,35 @@ class Polyhedron: def difference(self, *others): # return a new polyhedron with elements in the polyhedron that are not # in the others - raise NotImplementedError + symbols = set(self.symbols()) + for other in others: + symbols.update(other.symbols()) + symbols = sorted(symbols) + strings = [self._toiscc(symbols)] + for other in others: + strings.append(other._toiscc(symbols)) + string = ' - '.join(strings) + string = _iscc('poly ({})'.format(string)) + return Polyhedron._fromiscc(symbols, string) def __sub__(self, other): return self.difference(other) - def __str__(self): + def __repr__(self): constraints = [] for constraint in self.equalities: constraints.append('{} == 0'.format(constraint)) for constraint in self.inequalities: constraints.append('{} <= 0'.format(constraint)) - return '{{{}}}'.format(', '.join(constraints)) - - def __repr__(self): - equalities = list(self.equalities) - inequalities = list(self.inequalities) - return '{}(equalities={!r}, inequalities={!r})' \ - ''.format(self.__class__.__name__, equalities, inequalities) - - @classmethod - def fromstring(cls, string): - raise NotImplementedError - + if len(constraints) == 0: + return 'universe' + elif len(constraints) == 1: + string = constraints[0] + return 'empty' if string == '1 == 0' else string + else: + strings = ['({})'.format(constraint) for constraint in constraints] + return ' & '.join(strings) -empty = le(1, 0) +empty = Eq(1, 0) universe = Polyhedron() diff --git a/pypol/__init__.py b/pypol/__init__.py deleted file mode 100644 index fde0347..0000000 --- a/pypol/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ - -""" -A polyhedral library based on ISL. -""" - -from .linear import constant, symbol, symbols -from .linear import eq, le, lt, ge, gt -from .linear import empty, universe - - -__all__ = [ - 'constant', 'symbol', 'symbols', - 'eq', 'le', 'lt', 'ge', 'gt', - 'empty', 'universe' -] diff --git a/pypol/isl.py b/pypol/isl.py deleted file mode 100644 index 74cc5d3..0000000 --- a/pypol/isl.py +++ /dev/null @@ -1,267 +0,0 @@ - -import ctypes, ctypes.util -import math -import numbers -import operator -import re -import functools - -from decimal import Decimal -from fractions import Fraction -from functools import wraps - - -libisl = ctypes.CDLL(ctypes.util.find_library('isl')) - -libisl.isl_printer_get_str.restype = ctypes.c_char_p - -def _polymorphic_method(func): - @functools.wraps(func) - def wrapper(self, other): - if isinstance(other, Value): - return func(self, other) - if isinstance(other, numbers.Rational): - other = Value(self.context, other) - return func(self, other) - raise TypeError('operand should be a Value or a Rational') - return wrapper - -class Context: - - __slots__ = ('_ic') - - def __init__(self): - self._ic = libisl.isl_ctx_alloc() - - @property - def _as_parameter_(self): - return self._ic - - def __del__(self): - libisl.isl_ctx_free(self) - - def __eq__(self, other): - if not isinstance(other, Context): - return False - return self._ic == other._ic - - -class Value: - - class _ptr(int): - def __new__(cls, iv): - return super().__new__(cls, iv) - def __repr__(self): - return '{}({})'.format(self.__class__.__name__, self) - - _RE_NONFINITE = re.compile( - r'^\s*(?P[-+])?((?PInf(inity)?)|(?PNaN))\s*$', - re.IGNORECASE) - - _RE_FRACTION = re.compile(r'^(?P[-+]?\d+)(/(?P\d+))?$') - - __slots__ = ('context', '_iv', '_numerator', '_denominator') - - def __new__(cls, context, numerator=0, denominator=None): - self = super().__new__(cls) - if not isinstance(context, Context): - raise TypeError('first argument should be a context') - self.context = context - if isinstance(numerator, cls._ptr): - assert denominator is None - self._iv = numerator - if libisl.isl_val_is_rat(self): - # retrieve numerator and denominator as strings to avoid integer - # overflows - ip = libisl.isl_printer_to_str(self.context) - ip = libisl.isl_printer_print_val(ip, self) - string = libisl.isl_printer_get_str(ip).decode() - libisl.isl_printer_free(ip) - m = self._RE_FRACTION.match(string) - assert m is not None - self._numerator = int(m.group('num')) - self._denominator = int(m.group('den')) if m.group('den') else 1 - else: - self._numerator = None - self._denominator = None - return self - if isinstance(numerator, str) and denominator is None: - m = self._RE_NONFINITE.match(numerator) - if m is not None: - self._numerator = None - self._denominator = None - if m.group('inf'): - if m.group('sign') == '-': - self._iv = libisl.isl_val_neginfty(context) - else: - self._iv = libisl.isl_val_infty(context) - else: - assert m.group('nan') - self._iv = libisl.isl_val_nan(context) - return self - try: - frac = Fraction(numerator, denominator) - except ValueError: - raise ValueError('invalid literal for {}: {!r}'.format( - cls.__name__, numerator)) - self._numerator = frac.numerator - self._denominator = frac.denominator - # values passed as strings to avoid integer overflows - if frac.denominator == 1: - numerator = str(frac.numerator).encode() - self._iv = libisl.isl_val_read_from_str(context, numerator) - else: - numerator = str(frac.numerator).encode() - numerator = libisl.isl_val_read_from_str(context, numerator) - denominator = str(frac.denominator).encode() - denominator = libisl.isl_val_read_from_str(context, denominator) - self._iv = libisl.isl_val_div(numerator, denominator) - return self - - @property - def _as_parameter_(self): - return self._iv - - def __del__(self): - libisl.isl_val_free(self) - self.context # prevents context from being GC'ed before the value - - @property - def numerator(self): - if self._numerator is None: - raise ValueError('not a rational number') - return self._numerator - - @property - def denominator(self): - if self._denominator is None: - raise ValueError('not a rational number') - return self._denominator - - def __bool__(self): - return not bool(libisl.isl_val_is_zero(self)) - - @_polymorphic_method - def __lt__(self, other): - return bool(libisl.isl_val_lt(self, other)) - - @_polymorphic_method - def __le__(self, other): - return bool(libisl.isl_val_le(self, other)) - - @_polymorphic_method - def __gt__(self, other): - return bool(libisl.isl_val_gt(self, other)) - - @_polymorphic_method - def __ge__(self, other): - return bool(libisl.isl_val_ge(self, other)) - - @_polymorphic_method - def __eq__(self, other): - return bool(libisl.isl_val_eq(self, other)) - - # __ne__ is not implemented, ISL semantics does not match Python's on - # nan != nan - - def __abs__(self): - val = libisl.isl_val_copy(self) - val = libisl.isl_val_abs(val) - return self.__class__(self.context, self._ptr(val)) - - def __pos__(self): - return self - - def __neg__(self): - val = libisl.isl_val_copy(self) - val = libisl.isl_val_neg(val) - return self.__class__(self.context, self._ptr(val)) - - def __floor__(self): - val = libisl.isl_val_copy(self) - val = libisl.isl_val_floor(val) - return self.__class__(self.context, self._ptr(val)) - - def __ceil__(self): - val = libisl.isl_val_copy(self) - val = libisl.isl_val_ceil(val) - return self.__class__(self.context, self._ptr(val)) - - def __trunc__(self): - val = libisl.isl_val_copy(self) - val = libisl.isl_val_trunc(val) - return self.__class__(self.context, self._ptr(val)) - - @_polymorphic_method - def __add__(self, other): - val1 = libisl.isl_val_copy(self) - val2 = libisl.isl_val_copy(other) - val = libisl.isl_val_add(val1, val2) - return self.__class__(self.context, self._ptr(val)) - - __radd__ = __add__ - - @_polymorphic_method - def __sub__(self, other): - val1 = libisl.isl_val_copy(self) - val2 = libisl.isl_val_copy(other) - val = libisl.isl_val_sub(val1, val2) - return self.__class__(self.context, self._ptr(val)) - - __rsub__ = __sub__ - - @_polymorphic_method - def __mul__(self, other): - val1 = libisl.isl_val_copy(self) - val2 = libisl.isl_val_copy(other) - val = libisl.isl_val_mul(val1, val2) - return self.__class__(self.context, self._ptr(val)) - - __rmul__ = __mul__ - - @_polymorphic_method - def __truediv__(self, other): - val1 = libisl.isl_val_copy(self) - val2 = libisl.isl_val_copy(other) - val = libisl.isl_val_div(val1, val2) - return self.__class__(self.context, self._ptr(val)) - - __rtruediv__ = __truediv__ - - def __float__(self): - if libisl.isl_val_is_rat(self): - return self.numerator / self.denominator - elif libisl.isl_val_is_infty(self): - return float('inf') - elif libisl.isl_val_is_neginfty(self): - return float('-inf') - else: - assert libisl.isl_val_is_nan(self) - return float('nan') - - def is_finite(self): - return bool(libisl.isl_val_is_rat(self)) - - def is_infinite(self): - return bool(libisl.isl_val_is_infty(self) or - libisl.isl_val_is_neginfty(self)) - - def is_nan(self): - return bool(libisl.isl_val_is_nan(self)) - - def __str__(self): - if libisl.isl_val_is_rat(self): - if self.denominator == 1: - return '{}'.format(self.numerator) - else: - return '{}/{}'.format(self.numerator, self.denominator) - elif libisl.isl_val_is_infty(self): - return 'Infinity' - elif libisl.isl_val_is_neginfty(self): - return '-Infinity' - else: - assert libisl.isl_val_is_nan(self) - return 'NaN' - - def __repr__(self): - return '{}({!r})'.format(self.__class__.__name__, str(self)) diff --git a/setup.py b/setup.py index a0dfb8a..10e32f5 100755 --- a/setup.py +++ b/setup.py @@ -3,8 +3,8 @@ from distutils.core import setup setup( - name='pypol', + name='polyp', description='A polyhedral library based on ISL', author='MINES ParisTech', - packages=['pypol'] + packages=['polyp'] ) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_isl.py b/tests/test_isl.py deleted file mode 100644 index 7186dad..0000000 --- a/tests/test_isl.py +++ /dev/null @@ -1,199 +0,0 @@ - -import unittest - -from math import floor, ceil, trunc - -from pypol.isl import * - - -class TestContext(unittest.TestCase): - - def test_eq(self): - ctx1, ctx2 = Context(), Context() - self.assertEqual(ctx1, ctx1) - self.assertNotEqual(ctx1, ctx2) - - -class TestValue(unittest.TestCase): - - def setUp(self): - self.context = Context() - self.zero = Value(self.context) - self.nan = Value(self.context, 'NaN') - self.inf = Value(self.context, 'Inf') - self.neginf = Value(self.context, '-Inf') - self.answer = Value(self.context, 42) - self.pi = Value(self.context, 22, 7) - - def test_init(self): - self.assertEqual(Value(self.context, 42), self.answer) - self.assertEqual(Value(self.context, '42'), self.answer) - self.assertEqual(Value(self.context, 22, 7), self.pi) - self.assertEqual(Value(self.context, '-22/7'), -self.pi) - self.assertTrue(Value(self.context, 'nan').is_nan()) - self.assertTrue(Value(self.context, '-nan').is_nan()) - self.assertTrue(Value(self.context, 'NaN').is_nan()) - self.assertEqual(Value(self.context, '-inf'), self.neginf) - self.assertEqual(Value(self.context, '-Infinity'), self.neginf) - - def test_numerator(self): - self.assertEqual(self.zero.numerator, 0) - self.assertEqual(self.answer.numerator, 42) - self.assertEqual(self.pi.numerator, 22) - with self.assertRaises(ValueError): - self.nan.numerator - with self.assertRaises(ValueError): - self.inf.numerator - - def test_denominator(self): - self.assertEqual(self.zero.denominator, 1) - self.assertEqual(self.answer.denominator, 1) - self.assertEqual(self.pi.denominator, 7) - with self.assertRaises(ValueError): - self.nan.denominator - with self.assertRaises(ValueError): - self.inf.denominator - - def test_bool(self): - self.assertFalse(self.zero) - self.assertTrue(self.answer) - self.assertTrue(self.pi) - self.assertEqual(bool(self.nan), bool(float('nan'))) - self.assertEqual(bool(self.inf), bool(float('inf'))) - - def test_lt(self): - self.assertTrue(self.neginf < self.zero) - self.assertTrue(self.zero < self.pi) - self.assertTrue(self.pi < self.answer) - self.assertTrue(self.answer < self.inf) - self.assertFalse(self.nan < self.answer) - self.assertFalse(self.nan < self.inf) - self.assertFalse(self.nan < self.neginf) - self.assertTrue(self.neginf < self.inf) - - def test_le(self): - self.assertTrue(self.pi <= self.pi) - self.assertTrue(self.pi <= self.answer) - self.assertFalse(self.answer <= self.pi) - - def test_gt(self): - self.assertFalse(self.pi > self.pi) - self.assertTrue(self.answer > self.pi) - self.assertFalse(self.pi > self.answer) - - def test_ge(self): - self.assertTrue(self.pi >= self.pi) - self.assertTrue(self.answer >= self.pi) - self.assertFalse(self.pi >= self.answer) - - def test_eq(self): - self.assertEqual(self.pi, self.pi) - self.assertEqual(self.inf, self.inf) - self.assertNotEqual(self.neginf, self.inf) - self.assertNotEqual(self.nan, self.nan) - self.assertEqual(self.zero, 0) - self.assertEqual(0, self.zero) - self.assertEqual(self.pi, Fraction(22, 7)) - self.assertEqual(Fraction(22, 7), self.pi) - with self.assertRaises(TypeError): - self.zero == 0. - - def test_ne(self): - self.assertTrue(self.pi != self.answer) - self.assertFalse(self.pi != self.pi) - self.assertTrue(self.neginf != self.inf) - self.assertTrue(self.nan != self.nan) - - def test_abs(self): - self.assertEqual(abs(self.pi), self.pi) - self.assertEqual(abs(self.neginf), self.inf) - self.assertEqual(abs(-self.pi), self.pi) - self.assertTrue(abs(self.nan).is_nan()) - - def test_pos(self): - self.assertEqual(+self.pi, self.pi) - - def test_neg(self): - self.assertEqual(-self.neginf, self.inf) - self.assertEqual(-(-self.pi), self.pi) - - def test_floor(self): - self.assertEqual(floor(self.pi), Value(self.context, 3)) - self.assertEqual(floor(-self.pi), Value(self.context, -4)) - # not float behavior, but makes sense - self.assertEqual(floor(self.inf), self.inf) - self.assertTrue(floor(self.nan).is_nan()) - - def test_ceil(self): - self.assertEqual(ceil(self.pi), Value(self.context, 4)) - self.assertRaises(ceil(-self.pi) == Value(self.context, -3)) - - def test_trunc(self): - self.assertEqual(trunc(self.pi), Value(self.context, 3)) - self.assertEqual(trunc(-self.pi), Value(self.context, -3)) - - def test_add(self): - self.assertEqual(self.answer + self.answer, Value(self.context, 84)) - self.assertEqual(self.answer + self.pi, Value(self.context, 316, 7)) - self.assertEqual(self.pi + self.pi, Value(self.context, 44, 7)) - self.assertEqual(self.pi + self.neginf, self.neginf) - self.assertEqual(self.pi + self.inf, self.inf) - self.assertTrue((self.pi + self.nan).is_nan()) - self.assertTrue((self.inf + self.nan).is_nan()) - self.assertTrue((self.inf + self.neginf).is_nan()) - self.assertEqual(self.pi + 42, Value(self.context, 316, 7)) - self.assertEqual(42 + self.pi, Value(self.context, 316, 7)) - self.assertEqual(self.pi + Fraction(22, 7), Value(self.context, 44, 7)) - with self.assertRaises(TypeError): - self.pi + float(42) - - def test_sub(self): - self.assertEqual(self.answer - self.pi, Value(self.context, 272, 7)) - - def test_mul(self): - self.assertEqual(Value(self.context, 6) * Value(self.context, 7), self.answer) - self.assertNotEqual(Value(self.context, 6) * Value(self.context, 9), self.answer) - self.assertEqual(self.inf * Value(self.context, 2), self.inf) - self.assertEqual(self.inf * Value(self.context, -2), self.neginf) - self.assertTrue((self.nan * Value(self.context, 2)).is_nan()) - self.assertTrue((self.nan * self.inf).is_nan()) - - def test_div(self): - self.assertEqual(Value(self.context, 22) / Value(self.context, 7), self.pi) - self.assertEqual(self.pi / self.pi, Value(self.context, 1)) - # not float behavior, but makes sense - self.assertTrue((self.pi / self.zero).is_nan()) - - def test_float(self): - self.assertAlmostEqual(float(Value(self.context, 1, 2)), 0.5) - self.assertTrue(math.isnan(float(Value(self.context, 'NaN')))) - self.assertAlmostEqual(float(Value(self.context, 'Inf')), float('inf')) - - def test_is_finite(self): - self.assertTrue(self.pi.is_finite()) - self.assertFalse(self.inf.is_finite()) - self.assertFalse(self.nan.is_finite()) - - def test_is_infinite(self): - self.assertFalse(self.pi.is_infinite()) - self.assertTrue(self.inf.is_infinite()) - self.assertFalse(self.nan.is_infinite()) - - def test_is_nan(self): - self.assertFalse(self.pi.is_nan()) - self.assertFalse(self.inf.is_nan()) - self.assertTrue(self.nan.is_nan()) - - def test_str(self): - self.assertEqual(str(self.answer), '42') - self.assertEqual(str(self.pi), '22/7') - self.assertEqual(str(self.nan), 'NaN') - self.assertEqual(str(self.inf), 'Infinity') - self.assertEqual(str(self.neginf), '-Infinity') - - def test_repr(self): - self.assertEqual(repr(self.answer), "Value('42')") - self.assertEqual(repr(self.pi), "Value('22/7')") - self.assertEqual(repr(self.nan), "Value('NaN')") - self.assertEqual(repr(self.inf), "Value('Infinity')") - self.assertEqual(repr(self.neginf), "Value('-Infinity')") diff --git a/tests/test_linear.py b/tests/test_linear.py deleted file mode 100644 index 4636275..0000000 --- a/tests/test_linear.py +++ /dev/null @@ -1,177 +0,0 @@ - -import unittest - -from fractions import Fraction - -from pypol.linear import * - - -class TestExpression(unittest.TestCase): - - def setUp(self): - self.x = symbol('x') - self.y = symbol('y') - self.z = symbol('z') - self.zero = constant(0) - self.pi = constant(Fraction(22, 7)) - self.e = self.x - 2*self.y + 3 - - def test_new(self): - pass - - def test_symbols(self): - self.assertCountEqual(self.x.symbols(), ['x']) - self.assertCountEqual(self.pi.symbols(), []) - self.assertCountEqual(self.e.symbols(), ['x', 'y']) - - def test_dimension(self): - self.assertEqual(self.x.dimension, 1) - self.assertEqual(self.pi.dimension, 0) - self.assertEqual(self.e.dimension, 2) - - def test_coefficient(self): - self.assertEqual(self.e.coefficient('x'), 1) - self.assertEqual(self.e.coefficient('y'), -2) - self.assertEqual(self.e.coefficient(self.y), -2) - self.assertEqual(self.e.coefficient('z'), 0) - with self.assertRaises(TypeError): - self.e.coefficient(0) - with self.assertRaises(TypeError): - self.e.coefficient(self.e) - - def test_getitem(self): - self.assertEqual(self.e['x'], 1) - self.assertEqual(self.e['y'], -2) - self.assertEqual(self.e[self.y], -2) - self.assertEqual(self.e['z'], 0) - with self.assertRaises(TypeError): - self.e[0] - with self.assertRaises(TypeError): - self.e[self.e] - - def test_coefficients(self): - self.assertCountEqual(self.e.coefficients(), [('x', 1), ('y', -2)]) - - def test_constant(self): - self.assertEqual(self.x.constant, 0) - self.assertEqual(self.pi.constant, Fraction(22, 7)) - self.assertEqual(self.e.constant, 3) - - def test_isconstant(self): - self.assertFalse(self.x.isconstant()) - self.assertTrue(self.pi.isconstant()) - self.assertFalse(self.e.isconstant()) - - def test_values(self): - self.assertCountEqual(self.e.values(), [1, -2, 3]) - - def test_symbol(self): - self.assertEqual(self.x.symbol(), 'x') - with self.assertRaises(ValueError): - self.pi.symbol() - with self.assertRaises(ValueError): - self.e.symbol() - - def test_issymbol(self): - self.assertTrue(self.x.issymbol()) - self.assertFalse(self.pi.issymbol()) - self.assertFalse(self.e.issymbol()) - - def test_bool(self): - self.assertTrue(self.x) - self.assertFalse(self.zero) - self.assertTrue(self.pi) - self.assertTrue(self.e) - - def test_pos(self): - self.assertEqual(+self.e, self.e) - - def test_neg(self): - self.assertEqual(-self.e, -self.x + 2*self.y - 3) - - def test_add(self): - self.assertEqual(self.x + Fraction(22, 7), self.x + self.pi) - self.assertEqual(Fraction(22, 7) + self.x, self.x + self.pi) - self.assertEqual(self.x + self.x, 2 * self.x) - self.assertEqual(self.e + 2*self.y, self.x + 3) - - def test_sub(self): - self.assertEqual(self.x - self.x, 0) - self.assertEqual(self.e - 3, self.x - 2*self.y) - - def test_mul(self): - self.assertEqual(self.pi * 7, 22) - self.assertEqual(self.e * 0, 0) - self.assertEqual(self.e * 2, 2*self.x - 4*self.y + 6) - - def test_div(self): - with self.assertRaises(ZeroDivisionError): - self.e / 0 - self.assertEqual(self.e / 2, self.x / 2 - self.y + Fraction(3, 2)) - - def test_str(self): - self.assertEqual(str(Expression()), '0') - self.assertEqual(str(self.x), 'x') - self.assertEqual(str(-self.x), '-x') - self.assertEqual(str(self.pi), '22/7') - self.assertEqual(str(self.e), 'x - 2*y + 3') - - def test_repr(self): - self.assertEqual(repr(self.e), "Expression({'x': 1, 'y': -2}, 3)") - - @unittest.expectedFailure - def test_fromstring(self): - self.assertEqual(Expression.fromstring('x'), self.x) - self.assertEqual(Expression.fromstring('-x'), -self.x) - self.assertEqual(Expression.fromstring('22/7'), self.pi) - self.assertEqual(Expression.fromstring('x - 2y + 3'), self.e) - self.assertEqual(Expression.fromstring('x - (3-1)y + 3'), self.e) - self.assertEqual(Expression.fromstring('x - 2*y + 3'), self.e) - - def test_eq(self): - self.assertEqual(self.e, self.e) - self.assertNotEqual(self.x, self.y) - self.assertEqual(self.zero, 0) - - def test_canonify(self): - self.assertEqual((self.x + self.y/2 + self.z/3)._canonify(), - 6*self.x + 3*self.y + 2*self.z) - - -class TestHelpers(unittest.TestCase): - - def setUp(self): - self.x = symbol('x') - self.y = symbol('y') - - def test_constant(self): - self.assertEqual(constant(3), 3) - self.assertEqual(constant('3'), 3) - self.assertEqual(constant(Fraction(3, 4)), Fraction(3, 4)) - self.assertEqual(constant('3/4'), Fraction(3, 4)) - with self.assertRaises(ValueError): - constant('a') - with self.assertRaises(TypeError): - constant([]) - - def test_symbol(self): - self.assertEqual(symbol('x'), self.x) - self.assertNotEqual(symbol('y'), self.x) - with self.assertRaises(TypeError): - symbol(0) - - def test_symbols(self): - self.assertListEqual(list(symbols('x y')), [self.x, self.y]) - self.assertListEqual(list(symbols('x,y')), [self.x, self.y]) - self.assertListEqual(list(symbols(['x', 'y'])), [self.x, self.y]) - - -class TestOperators(unittest.TestCase): - - pass - - -class TestPolyhedron(unittest.TestCase): - - pass -