X-Git-Url: https://svn.cri.ensmp.fr/git/linpy.git/blobdiff_plain/2bad3743bd25bbcfe12db50e2b18ab8d070f2354..960f0c252361dfd696359f803aae40a9b13b14a6:/pypol/linexprs.py?ds=sidebyside diff --git a/pypol/linexprs.py b/pypol/linexprs.py index e73449e..bd3ad5a 100644 --- a/pypol/linexprs.py +++ b/pypol/linexprs.py @@ -1,3 +1,20 @@ +# Copyright 2014 MINES ParisTech +# +# This file is part of Linpy. +# +# Linpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Linpy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Linpy. If not, see . + import ast import functools import numbers @@ -40,26 +57,26 @@ class Expression: return Rational(constant) if isinstance(coefficients, Mapping): coefficients = coefficients.items() + coefficients = list(coefficients) for symbol, coefficient in coefficients: if not isinstance(symbol, Symbol): raise TypeError('symbols must be Symbol instances') if not isinstance(coefficient, numbers.Rational): - raise TypeError('coefficients must be Rational instances') - coefficients = [(symbol, Fraction(coefficient)) - for symbol, coefficient in coefficients if coefficient != 0] + raise TypeError('coefficients must be rational numbers') if not isinstance(constant, numbers.Rational): - raise TypeError('constant must be a Rational instance') - constant = Fraction(constant) + raise TypeError('constant must be a rational number') if len(coefficients) == 0: return Rational(constant) if len(coefficients) == 1 and constant == 0: symbol, coefficient = coefficients[0] if coefficient == 1: return symbol + coefficients = [(symbol, Fraction(coefficient)) + for symbol, coefficient in coefficients if coefficient != 0] + coefficients.sort(key=lambda item: item[0].sortkey()) self = object().__new__(cls) - self._coefficients = OrderedDict(sorted(coefficients, - key=lambda item: item[0].sortkey())) - self._constant = constant + self._coefficients = OrderedDict(coefficients) + self._constant = Fraction(constant) self._symbols = tuple(self._coefficients) self._dimension = len(self._symbols) return self @@ -67,10 +84,7 @@ class Expression: def coefficient(self, symbol): if not isinstance(symbol, Symbol): raise TypeError('symbol must be a Symbol instance') - try: - return Rational(self._coefficients[symbol]) - except KeyError: - return Rational(0) + return Rational(self._coefficients.get(symbol, 0)) __getitem__ = coefficient @@ -131,49 +145,48 @@ class Expression: constant = self._constant - other._constant return Expression(coefficients, constant) + @_polymorphic def __rsub__(self, other): - return -(self - other) + return other - self - @_polymorphic def __mul__(self, other): - if isinstance(other, Rational): - return other.__rmul__(self) + if isinstance(other, numbers.Rational): + coefficients = ((symbol, coefficient * other) + for symbol, coefficient in self._coefficients.items()) + constant = self._constant * other + return Expression(coefficients, constant) return NotImplemented __rmul__ = __mul__ - @_polymorphic def __truediv__(self, other): - if isinstance(other, Rational): - return other.__rtruediv__(self) + if isinstance(other, numbers.Rational): + coefficients = ((symbol, coefficient / other) + for symbol, coefficient in self._coefficients.items()) + constant = self._constant / other + return Expression(coefficients, constant) return NotImplemented - __rtruediv__ = __truediv__ - @_polymorphic def __eq__(self, other): - # "normal" equality + # returns a boolean, not a constraint # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs return isinstance(other, Expression) and \ self._coefficients == other._coefficients and \ self._constant == other._constant - @_polymorphic def __le__(self, other): from .polyhedra import Le return Le(self, other) - @_polymorphic def __lt__(self, other): from .polyhedra import Lt return Lt(self, other) - @_polymorphic def __ge__(self, other): from .polyhedra import Ge return Ge(self, other) - @_polymorphic def __gt__(self, other): from .polyhedra import Gt return Gt(self, other) @@ -240,18 +253,17 @@ class Expression: string = '' for i, (symbol, coefficient) in enumerate(self.coefficients()): if coefficient == 1: - string += '' if i == 0 else ' + ' - string += '{!r}'.format(symbol) + if i != 0: + string += ' + ' elif coefficient == -1: string += '-' if i == 0 else ' - ' - string += '{!r}'.format(symbol) + elif i == 0: + string += '{}*'.format(coefficient) + elif coefficient > 0: + string += ' + {}*'.format(coefficient) else: - if i == 0: - string += '{}*{!r}'.format(coefficient, symbol) - elif coefficient > 0: - string += ' + {}*{!r}'.format(coefficient, symbol) - else: - string += ' - {}*{!r}'.format(-coefficient, symbol) + string += ' - {}*'.format(-coefficient) + string += '{}'.format(symbol) constant = self.constant if len(string) == 0: string += '{}'.format(constant) @@ -261,6 +273,30 @@ class Expression: string += ' - {}'.format(-constant) return string + def _repr_latex_(self): + string = '' + for i, (symbol, coefficient) in enumerate(self.coefficients()): + if coefficient == 1: + if i != 0: + string += ' + ' + elif coefficient == -1: + string += '-' if i == 0 else ' - ' + elif i == 0: + string += '{}'.format(coefficient._repr_latex_().strip('$')) + elif coefficient > 0: + string += ' + {}'.format(coefficient._repr_latex_().strip('$')) + elif coefficient < 0: + string += ' - {}'.format((-coefficient)._repr_latex_().strip('$')) + string += '{}'.format(symbol._repr_latex_().strip('$')) + constant = self.constant + if len(string) == 0: + string += '{}'.format(constant._repr_latex_().strip('$')) + elif constant > 0: + string += ' + {}'.format(constant._repr_latex_().strip('$')) + elif constant < 0: + string += ' - {}'.format((-constant)._repr_latex_().strip('$')) + return '$${}$$'.format(string) + def _parenstr(self, always=False): string = str(self) if not always and (self.isconstant() or self.issymbol()): @@ -301,8 +337,8 @@ class Symbol(Expression): raise TypeError('name must be a string') self = object().__new__(cls) self._name = name.strip() - self._coefficients = {self: 1} - self._constant = 0 + self._coefficients = {self: Fraction(1)} + self._constant = Fraction(0) self._symbols = (self,) self._dimension = 1 return self @@ -321,8 +357,7 @@ class Symbol(Expression): return True def __eq__(self, other): - return not isinstance(other, Dummy) and isinstance(other, Symbol) \ - and self.name == other.name + return self.sortkey() == other.sortkey() def asdummy(self): return Dummy(self.name) @@ -340,11 +375,16 @@ class Symbol(Expression): def __repr__(self): return self.name + def _repr_latex_(self): + return '$${}$$'.format(self.name) + @classmethod def fromsympy(cls, expr): import sympy - if isinstance(expr, sympy.Symbol): - return cls(expr.name) + if isinstance(expr, sympy.Dummy): + return Dummy(expr.name) + elif isinstance(expr, sympy.Symbol): + return Symbol(expr.name) else: raise TypeError('expr must be a sympy.Symbol instance') @@ -356,11 +396,13 @@ class Dummy(Symbol): def __new__(cls, name=None): if name is None: name = 'Dummy_{}'.format(Dummy._count) + elif not isinstance(name, str): + raise TypeError('name must be a string') self = object().__new__(cls) self._index = Dummy._count self._name = name.strip() - self._coefficients = {self: 1} - self._constant = 0 + self._coefficients = {self: Fraction(1)} + self._constant = Fraction(0) self._symbols = (self,) self._dimension = 1 Dummy._count += 1 @@ -372,12 +414,12 @@ class Dummy(Symbol): def sortkey(self): return self._name, self._index - def __eq__(self, other): - return isinstance(other, Dummy) and self._index == other._index - def __repr__(self): return '_{}'.format(self.name) + def _repr_latex_(self): + return '$${}_{{{}}}$$'.format(self.name, self._index) + def symbols(names): if isinstance(names, str): @@ -388,11 +430,13 @@ def symbols(names): class Rational(Expression, Fraction): def __new__(cls, numerator=0, denominator=None): - self = Fraction.__new__(cls, numerator, denominator) + self = object().__new__(cls) self._coefficients = {} - self._constant = Fraction(self) + self._constant = Fraction(numerator, denominator) self._symbols = () self._dimension = 0 + self._numerator = self._constant.numerator + self._denominator = self._constant.denominator return self def __hash__(self): @@ -408,29 +452,21 @@ class Rational(Expression, Fraction): def __bool__(self): return Fraction.__bool__(self) - @_polymorphic - def __mul__(self, other): - coefficients = dict(other._coefficients) - for symbol in coefficients: - coefficients[symbol] *= self._constant - constant = other._constant * self._constant - return Expression(coefficients, constant) - - __rmul__ = __mul__ - - @_polymorphic - def __rtruediv__(self, other): - coefficients = dict(other._coefficients) - for symbol in coefficients: - coefficients[symbol] /= self._constant - constant = other._constant / self._constant - return Expression(coefficients, constant) - - @classmethod - def fromstring(cls, string): - if not isinstance(string, str): - raise TypeError('string must be a string instance') - return Rational(Fraction(string)) + def __repr__(self): + if self.denominator == 1: + return '{!r}'.format(self.numerator) + else: + return '{!r}/{!r}'.format(self.numerator, self.denominator) + + def _repr_latex_(self): + if self.denominator == 1: + return '$${}$$'.format(self.numerator) + elif self.numerator < 0: + return '$$-\\frac{{{}}}{{{}}}$$'.format(-self.numerator, + self.denominator) + else: + return '$$\\frac{{{}}}{{{}}}$$'.format(self.numerator, + self.denominator) @classmethod def fromsympy(cls, expr):