X-Git-Url: https://scm.cri.ensmp.fr/git/linpy.git/blobdiff_plain/7b93cea1daf2889e9ee10ca9c22a1b5124404937..7afcb0a1ded9e9a331b131689d68c085f712143f:/linpy/linexprs.py diff --git a/linpy/linexprs.py b/linpy/linexprs.py index aedf170..8267be1 100644 --- a/linpy/linexprs.py +++ b/linpy/linexprs.py @@ -25,7 +25,7 @@ from fractions import Fraction, gcd __all__ = [ - 'Expression', + 'LinExpr', 'Symbol', 'Dummy', 'symbols', 'Rational', ] @@ -34,7 +34,7 @@ __all__ = [ def _polymorphic(func): @functools.wraps(func) def wrapper(left, right): - if isinstance(right, Expression): + if isinstance(right, LinExpr): return func(left, right) elif isinstance(right, numbers.Rational): right = Rational(right) @@ -43,16 +43,19 @@ def _polymorphic(func): return wrapper -class Expression: +class LinExpr: """ This class implements linear expressions. """ def __new__(cls, coefficients=None, constant=0): + """ + Create a new expression. + """ if isinstance(coefficients, str): if constant != 0: raise TypeError('too many arguments') - return Expression.fromstring(coefficients) + return LinExpr.fromstring(coefficients) if coefficients is None: return Rational(constant) if isinstance(coefficients, Mapping): @@ -82,6 +85,9 @@ class Expression: return self def coefficient(self, symbol): + """ + Return the coefficient value of the given symbol. + """ if not isinstance(symbol, Symbol): raise TypeError('symbol must be a Symbol instance') return Rational(self._coefficients.get(symbol, 0)) @@ -89,31 +95,52 @@ class Expression: __getitem__ = coefficient def coefficients(self): + """ + Return a list of the coefficients of an expression + """ for symbol, coefficient in self._coefficients.items(): yield symbol, Rational(coefficient) @property def constant(self): + """ + Return the constant value of an expression. + """ return Rational(self._constant) @property def symbols(self): + """ + Return a list of symbols in an expression. + """ return self._symbols @property def dimension(self): + """ + Create and return a new linear expression from a string or a list of coefficients and a constant. + """ return self._dimension def __hash__(self): return hash((tuple(self._coefficients.items()), self._constant)) def isconstant(self): + """ + Return true if an expression is a constant. + """ return False def issymbol(self): + """ + Return true if an expression is a symbol. + """ return False def values(self): + """ + Return the coefficient and constant values of an expression. + """ for coefficient in self._coefficients.values(): yield Rational(coefficient) yield Rational(self._constant) @@ -129,32 +156,41 @@ class Expression: @_polymorphic def __add__(self, other): + """ + Return the sum of two expressions. + """ coefficients = defaultdict(Fraction, self._coefficients) for symbol, coefficient in other._coefficients.items(): coefficients[symbol] += coefficient constant = self._constant + other._constant - return Expression(coefficients, constant) + return LinExpr(coefficients, constant) __radd__ = __add__ @_polymorphic def __sub__(self, other): + """ + Return the difference between two expressions. + """ coefficients = defaultdict(Fraction, self._coefficients) for symbol, coefficient in other._coefficients.items(): coefficients[symbol] -= coefficient constant = self._constant - other._constant - return Expression(coefficients, constant) + return LinExpr(coefficients, constant) @_polymorphic def __rsub__(self, other): return other - self def __mul__(self, other): + """ + Return the product of two expressions if other is a rational number. + """ 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 LinExpr(coefficients, constant) return NotImplemented __rmul__ = __mul__ @@ -164,14 +200,15 @@ class Expression: coefficients = ((symbol, coefficient / other) for symbol, coefficient in self._coefficients.items()) constant = self._constant / other - return Expression(coefficients, constant) + return LinExpr(coefficients, constant) return NotImplemented @_polymorphic def __eq__(self, other): - # returns a boolean, not a constraint - # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs - return isinstance(other, Expression) and \ + """ + Test whether two expressions are equal + """ + return isinstance(other, LinExpr) and \ self._coefficients == other._coefficients and \ self._constant == other._constant @@ -192,11 +229,18 @@ class Expression: return Gt(self, other) def scaleint(self): + """ + Multiply an expression by a scalar to make all coefficients integer values. + """ lcm = functools.reduce(lambda a, b: a*b // gcd(a, b), [value.denominator for value in self.values()]) return self * lcm def subs(self, symbol, expression=None): + """ + Subsitute symbol by expression in equations and return the resulting + expression. + """ if expression is None: if isinstance(symbol, Mapping): symbol = symbol.items() @@ -212,7 +256,7 @@ class Expression: if othersymbol != symbol] coefficient = result._coefficients.get(symbol, 0) constant = result._constant - result = Expression(coefficients, constant) + coefficient*expression + result = LinExpr(coefficients, constant) + coefficient*expression return result @classmethod @@ -244,8 +288,11 @@ class Expression: @classmethod def fromstring(cls, string): + """ + Create an expression from a string. + """ # add implicit multiplication operators, e.g. '5x' -> '5*x' - string = Expression._RE_NUM_VAR.sub(r'\1*\2', string) + string = LinExpr._RE_NUM_VAR.sub(r'\1*\2', string) tree = ast.parse(string, 'eval') return cls._fromast(tree) @@ -306,6 +353,9 @@ class Expression: @classmethod def fromsympy(cls, expr): + """ + Convert sympy object to an expression. + """ import sympy coefficients = [] constant = 0 @@ -318,9 +368,12 @@ class Expression: coefficients.append((symbol, coefficient)) else: raise ValueError('non-linear expression: {!r}'.format(expr)) - return Expression(coefficients, constant) + return LinExpr(coefficients, constant) def tosympy(self): + """ + Return an expression as a sympy object. + """ import sympy expr = 0 for symbol, coefficient in self.coefficients(): @@ -330,9 +383,12 @@ class Expression: return expr -class Symbol(Expression): +class Symbol(LinExpr): def __new__(cls, name): + """ + Create and return a symbol from a string. + """ if not isinstance(name, str): raise TypeError('name must be a string') self = object().__new__(cls) @@ -360,6 +416,9 @@ class Symbol(Expression): return self.sortkey() == other.sortkey() def asdummy(self): + """ + Return a symbol as a Dummy Symbol. + """ return Dummy(self.name) @classmethod @@ -390,10 +449,15 @@ class Symbol(Expression): class Dummy(Symbol): - + """ + This class returns a dummy symbol to ensure that no variables are repeated in an expression + """ _count = 0 def __new__(cls, name=None): + """ + Create and return a new dummy symbol. + """ if name is None: name = 'Dummy_{}'.format(Dummy._count) elif not isinstance(name, str): @@ -422,12 +486,18 @@ class Dummy(Symbol): def symbols(names): + """ + Transform strings into instances of the Symbol class + """ if isinstance(names, str): names = names.replace(',', ' ').split() return tuple(Symbol(name) for name in names) -class Rational(Expression, Fraction): +class Rational(LinExpr, Fraction): + """ + This class represents integers and rational numbers of any size. + """ def __new__(cls, numerator=0, denominator=None): self = object().__new__(cls) @@ -444,9 +514,15 @@ class Rational(Expression, Fraction): @property def constant(self): + """ + Return rational as a constant. + """ return self def isconstant(self): + """ + Test whether a value is a constant. + """ return True def __bool__(self): @@ -470,6 +546,9 @@ class Rational(Expression, Fraction): @classmethod def fromsympy(cls, expr): + """ + Create a rational object from a sympy expression + """ import sympy if isinstance(expr, sympy.Rational): return Rational(expr.p, expr.q)