diff --git a/.gitignore b/.gitignore index 3db55bf..644cdba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ __pycache__/ *.pyc dist/ -bugs *.egg-info/ diff --git a/TODO b/TODO index 375517a..db39e93 100644 --- a/TODO +++ b/TODO @@ -5,5 +5,7 @@ * bug: expression can't handle -(-2) * Overload + - * for expression (DONE ~ no steps yet) * Expression should be able to simplify expression with ":" +* Add name to polynom * Expression parents class and his children: Numerical_exp, toGenerate_exp and formal expression +* Create tbl sgn and variation render diff --git a/bugs b/bugs new file mode 100644 index 0000000..553d1d1 --- /dev/null +++ b/bugs @@ -0,0 +1,79 @@ +* Soustraction de Polynômes!!!! + + + +* Expression importe mal 4x^2 + In [9]: e = Expression("3x + 4x^2 - 1") + + In [10]: print(e) + 3 x + ( 4 x ) ^ 2 - 1 + + -> faire un test unitaire dessus + +*(solved) Render ne met pas les parenthèses là où il faut. + + In [31]: r + Out[31]: < Polynom [-4, -15, -14]> + + In [35]: print(r) + -14 * x ^ 2 + -15 x + ( -4 ) + + In [36]: r.get_postfix() + Out[36]: [-14, 'x', 2, '^', '*', -15, 'x', '*', '+', -4, '+'] + + In [37]: txt(r.get_postfix()) + Out[37]: '-14 * x ^ 2 + -15 x + ( -4 )' + + + -> faire un test unitaire dessus + +*(solved) Fraction ne simplifie pas correctement + + In [5]: for i in P.alpha.simplify(): + print(i) + ...: + \frac{ - 2 }{ 2 \times 3 } + \frac{ -2 }{ 6 } + \frac{ ( -1 ) \times 2 }{ 3 \times 2 } + \frac{ -1 }{ 3 } + \frac{ -2 }{ 6 } + +* Opération entre une expression et une fraction ou un Polynom + + In [3]: P = Polynom([1,2,1]) + + In [4]: e = Expression("1+2*3") + + In [5]: e + P + Out[5]: < [1, 2, 3, '*', '+', < Polynom [1, 2, 1]>, '+'] > + + In [6]: P + e + --------------------------------------------------------------------------- + TypeError Traceback (most recent call last) + in () + ----> 1 P + e + + /home/lafrite/scripts/pyMath/pymath/polynom.py in __add__(self, other) + 430 [< [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 5, 'x', '*', 4, '+', '+'] >, < Polynom [< [1, 4, '+'] >, < [2, 5, '+'] >, 3]>, < Polynom [< [1, 4, '+'] >, < [2, 5, '+'] >, 3]>] + 431 """ + --> 432 o_poly = self.conv2poly(other) + 433 + 434 n_coef = spe_zip(self._coef, o_poly._coef) + + /home/lafrite/scripts/pyMath/pymath/polynom.py in conv2poly(self, other) + 319 return other + 320 else: + --> 321 raise ValueError(type(other) + " can't be converted into a polynom") + 322 + 323 def reduce(self): + + TypeError: unsupported operand type(s) for +: 'type' and 'str' + +* Parenthèses abhérentes + + In [7]: P = Polynom([-6, 12, -20]) + + In [8]: print(P) + ( - 20 x^{ 2 } + 12 x ) - 6 + + diff --git a/docs/construction.mdwn b/docs/construction.mdwn index 116c65d..c5269ab 100644 --- a/docs/construction.mdwn +++ b/docs/construction.mdwn @@ -9,7 +9,7 @@ Ces objets doivent pouvoir être afficher en *txt* ou en *tex* avec les méthode ### Operator Cette classe regroupe les operateurs. Que l'on s'autorise à utiliser. On y accède à partir de deux caractérisiques le symbole et l'arité. -Liste des attributs mportants: +Liste des attributs importants: * arity: nombre d'opérande accepté * priority: où se place l'opérateur dans la règles des priorités parmis les autres opérateurs * isOperator: permet de vérifier que c'est bien un opérateur @@ -34,10 +34,90 @@ Pour définir ces anneaux, il faudra contre avoir les méthodes suivantes: #### Quotient de polynomes (racines) - ## Expression - ## Render +## Simplify-simplified / compute-child + +Dans cette partie, on va expliquer le fonctionnement des mécanismes de simplification des expressions/objets mathématiques. + +La simplification des expressions se fait avec les deux méthodes suivantes: + +* *simplify()* pour: + * un polynôme permet d'accéder à la forme canonique d'un polynôme + * une fraction permet d'avoir la fraction irréductible associée + * une expression permet de faire tous les calculs possibles de cette expression (à la fin il ne doit y avoir qu'un élément de la liste de tokens) +* *compute_exp()* pour: + * un polynôme ou une fraction fait la même chose que $simplify$. + * une expression fait tous les calculs élémentaires de cette expression. + +Ces deux méthodes fonctionnent ensuite sur le même principe. Elles vont faire le calcul qui leurs est attribuée en enregistrant les étapes dans *steps* puis elles retourneront l'objet de fin de calcul à qui sera assigné les *steps* (ce qui nécessitera par exemple de détourner la classe *int*). + +Pour accéder à ces étapes, on utilisera alors la méthode *explain* qui expliqueront les étapes intermédiaires. + +### Tentative d'explications +C'est ce que je voudrai donc le render ne sera peut être pas exactement le même. + +Comportement avec les Polynom (ce serait similaire avec les fractions) + + >>> P = Polynom([0,1,2]) + >>> Q = Polynom([1,1,1]) + >>> R = P+Q + >>> print(R) + 3x^2 + 2x + 1 + >>> for i in R.explain(): + ... print(i) + 2x^2 + x + x^2 + x + 1 + (2 + 1)x^2 + (1+1)x + 1 + 3x^3 + 2x + 1 + + >>> P = Polynom([[1,2], [3,4]]) + >>> Q = P.simplify() + >>> print(Q) + 7x + 3 + >>> for i in Q.explain(): + ... print(i) + 3x + 4x + 1 + 2 + (3+4)x + (1+2) + 7x + 3 + +Comportement avec les expressions + + >>> e = Expression("1+2*3") + >>> e1 = e.compute_exp() + >>> e1 + 1 + 6 + >>> type(e1) + Expression + >>> for i in e1.explain(): # Peu interessant mais il aurai pu y avoir des calculs de fractions + ... print(i) + 1 + 2 * 3 + 1 + 6 + >>> e2 = e.simplify() + >>> e2 + 7 + >>> type(e2) + FakeInt + >>> for i in e2.explain(): + ... print(i) + 1 + 2 * 3 + 1 + 6 + 7 + >>> f = Expression("4 - 5") + >>> g = e + f + >>> g # Les deux expressions ont été concaténée mais aucun calcul n'a été fait + < Expression [1, 2, 3, '*', '+', 4, 5, '-', '+']> + >>> for i in g.explain(): + ... print(i) + 1 + 2 * 3 + 4 - 5 + >>> for i in g.simplify().explain(): + ... print(i) + 1 + 2 \times 3 + 4 - 5 + 1 + 6 + ( -1 ) + 7 + ( -1 ) + 6 + + + diff --git a/docs/tutorial.mdwn b/docs/tutorial.mdwn index f6f09c2..1ed1ae4 100644 --- a/docs/tutorial.mdwn +++ b/docs/tutorial.mdwn @@ -8,7 +8,10 @@ pyMath est un module python qui permet la manipulation d'expressions mathématiq >>> from pymath.expression import Expression >>> ajout_fractions = Expression("2 / 5 + 2 / 3") - >>> for i in ajout_fractions.simpliy(): + >>> resultat = ajout_fractions.simplify() + >>> print(resultat) + \frac{ 16 }{ 15 } + >>> for i in resultat.explain(): ... print(i) ... \frac{ 2 }{ 5 } + \frac{ 2 }{ 3 } @@ -38,16 +41,16 @@ pyMath est un module python qui permet la manipulation d'expressions mathématiq >>> from pymath.expression import Expression >>> ajout_fractions = Expression("2 / 5 + 2 / 3") - >>> for i in ajout_fractions.simpliy(): + >>> for i in ajout_fractions.simpliy().explain(): ... print(i) \frac{ 2 }{ 5 } + \frac{ 2 }{ 3 } \frac{ 2 \times 3 }{ 5 \times 3 } + \frac{ 2 \times 5 }{ 3 \times 5 } \frac{ 6 + 10 }{ 15 } \frac{ 16 }{ 15 } >>> from pymath.render import txt - >>> Expression.set_render(txt) - >>> for i in ajout_fractions.simpliy(): - ... print(i) + >>> with Expression.tmp_render(txt): + ... for i in ajout_fractions.simpliy(): + ... print(i) ... 2 / 5 + 2 / 3 2 * 3 / 5 * 3 + 2 * 5 / 3 * 5 @@ -84,14 +87,18 @@ Et si l'on souhaite un rendu plus adapté à la console: 2 / 5 + 2 / 3 ### Simplification des expressions -Une fois les expressions créées, elles peuvent se réduire en expliquant les étapes et en respectant les règles de priorités. Les exemples suivants seront données avec un rendu texte. +Une fois les expressions créées, elles peuvent se réduire en expliquant les étapes et en respectant les règles de priorités. Ces étapes de calcul sont stockés dans l'objet résultat du calcul et sont accéssibles à travers la méthode *explain*. +Les exemples suivants seront données avec un rendu texte. >>> from pymath.expression import Expression >>> from pymath.renders import txt >>> Expression.set_render(txt) >>> exp = Expression("1 + 2 * 3") - >>> for i in exp.simplify(): + >>> exp_simplifiee = exp.simplify() + >>> print(exp_simplifiee) + 7 + >>> for i in exp_simplifiee.explain(): ... print(i) ... 1 + 2 * 3 @@ -102,7 +109,7 @@ Une fois les expressions créées, elles peuvent se réduire en expliquant les Les opérations autorisées sont les opérations "classique": + - * / ^. L'utilisation des parenthèses est aussi gérée. >>> exp = Expression("1 + 2 / 5") - >>> for i in exp.simplify(): + >>> for i in exp.simplify().explain(): ... print(i) ... 1 + 2 / 5 @@ -111,7 +118,7 @@ Les opérations autorisées sont les opérations "classique": + - * / ^. L'utili 7 / 5 >>> exp = Expression("(2 + 4)(3 - 4 * 2)") - >>> for i in exp.simplify(): + >>> for i in exp.simplify().explain(): ... print(i) ... ( 2 + 4 ) ( 3 - ( 4 * 2 ) ) @@ -126,7 +133,7 @@ On peut vouloir créer directement des objets (fractions ou polynômes) sans pas >>> fraction1 = Fraction(1,2) >>> fraction2 = Fraction(2,3) >>> print(fraction1) - + 1 / 2 >>> from pymath.polynom import Polynom >>> p = Polynom([1,2,3]) >>> print(p) @@ -134,7 +141,7 @@ On peut vouloir créer directement des objets (fractions ou polynômes) sans pas >>> q = Polynom([0,0,1]) x ^ 2 -Contrairement aux opérations sur les Expressions, qui renvoient une nouvelle expression +On peut effectuer des opérations entre les Expressions. >>> fraction_expression = Expression("2 / 3") >>> autre_fraction_expression = Expression("4 / 9") @@ -143,17 +150,19 @@ Contrairement aux opérations sur les Expressions, qui renvoient une nouvelle ex les opérations sur les fractions ou les polynômes renvoient la liste des étapes jusqu'à leur forme simplifiée - >>> fraction1 + fraction2 - [< Expression [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '+']>, < Expression [3, 4, '+', 6, '/']>, < Fraction 7 / 6>] - >>> for i in (fraction1 + fraction2): + >>> addition_fraction = fraction1 + fraction2 + >>> print(addition_fraction) + 7 / 6 + >>> for i in addition_fraction.explain(): ... print(i) ... 1 * 3 / 2 * 3 + 2 * 2 / 3 * 2 ( 3 + 4 ) / 6 7 / 6 - >>> p + q - [< Polynom [[1, 0], [2, 0], [3, 1]]>, < Polynom [1, 2, < Expression [3, 1, '+']>]>, < Polynom [1, 2, 4]> - >>> for i in (p+q): + >>> r = p + q + >>> print(r) + 4 x ^ 2 + 2 x + 1 + >>> for i in r.explain(): ... print(i) ... 3 x ^ 2 + x ^ 2 + 2 x + 1 @@ -166,7 +175,7 @@ Comme dit dans l'introduction, il y a deux types de rendus: un rendu texte (util Voici un exemple de l'utilisation du rendu latex (par défaut). >>> exp = Expression("1 + 2 / 5") - >>> for i in exp.simplify(): + >>> for i in exp.simplify().explain(): ... print(i) ... 1 + \frac{ 2 }{ 5 } @@ -181,7 +190,7 @@ Voici un exemple d'utilisation du rendu txt >>> from pymath.render import txt >>> Expression.set_render(txt) >>> exp = Expression("1 + 2 / 5") - >>> for i in exp.simplify(): + >>> for i in exp.simplify().explain(): ... print(i) ... 2 / 5 + 2 / 3 @@ -192,33 +201,23 @@ Voici un exemple d'utilisation du rendu txt ## Générer des expressions aléatoirement. -Le module qui permet de générer des expressions aléatoirement est *pymath.random_expression* - - >>> from pymath.random_expression import RdExpression - ### Créer un expression Pour créer une expression il faut au moins une chose: la forme de l'expression. Toutes les lettres entre accolades seront remplacées par des valeurs aléatoires (par défaut entre -10 et 10 et non nulles). >>> form = "2* {a} + 3" - >>> random_expression_generator = RdExpression(form) - >>> generated_expression = random_expression_generator() - >>> type(generated_expression) - pymath.expression.Expression - >>> print(generated_expression) + >>> expression_aleatoire = Expression.random(form) + >>> print(expression_aleatoire) '2 \times 9 + 3' - >>> print(random_expression_generator(30, 40)) + >>> print(Expression.random(form,val_min = 30, val_max = 40)) '2 \times 31 + 3' -On remarque que ici que *random_expression_generator* renvoie une expression. Cela permettra ensuite de simplifier ou manipuler les expressions - On verra plus tard comment se passer de cette classe pour par exemple créer des expressions avec des opérateurs non implémentés dans Expression. - ### Créer une expression avec conditions Parfois il peut être nécessaire d'imposer des conditions sur les éléments à générer pour créer des exercices spécifiques. >>> form = "{a} / {b} + {c} / {d}" >>> conditions = ["abs({b}) != 1", "{d} > 1", "{b} != {d}", "gcd({a},{b}) == 1", "gcd({c},{d}) == 1"] - >>> random_frac_add_generator = RdExpression(form, conditions) - >>> print(random_frac_add_generator()) + >>> addition_fraction_alea = Expression.random(form, conditions) + >>> print(addition_fraction_alea) '\frac{ 4 }{ 5 } + \frac{ 9 }{ 7 }' La méthode pour créer les valeurs avec des conditions est la méthode par rejet. Elle n'est pas très efficace et rien n'est encore prévu dans le cas où aucune valeur n'est possible. @@ -233,23 +232,22 @@ Pour éviter de faire tourner la méthode par rejet trop longtemps, il est possi '\frac{ -9 }{ 7 } + \frac{ 1 }{ 28 } ### Rendu des expressions -On peut vouloir ne pas passer par la classe Expression pour obtenir notre expression (si l'on veut utiliser la racine carré par exemple, ou pour créer n'importe quoi qui ne fait pas intervenir d'expression). Pour cela il va falloir changer la forme du rendu +On peut vouloir ne pas passer par la classe Expression pour obtenir notre expression (si l'on veut utiliser la racine carré par exemple, ou pour créer n'importe quoi qui ne fait pas intervenir d'expression). Ainsi pymath ne gère plus le rendu de l'expression ni son calcul. - >>> RdExpression.set_form("raw") +La fonction qui permet de faire cela est *random_str*: + + >>> from pymath.random_expression import random_str >>> form = "{a} / {b} + {c} / {k*b}" >>> conditions = ["abs({b}) != 1", "{d} > 1", "{b} != {d}", "gcd({a},{b}) == 1", "gcd({c},{k*b}) == 1"] - >>> random_frac_add_generator = RdExpression(form, conditions) - >>> exp = random_frac_add_generator() - >>> type(exp) + >>> str_addition_fraction = random_str(form, conditions) + >>> type(str_addition_fraction) str - >>> print(exp) + >>> print(str_addition_fraction) -2 / 5 + -8 / 35 - >>> form = "{a**2}x^2 + {2*a*b} x + {b**2}" - >>> random_id_rmq_generator = RdExpression(form) - >>> print(random_id_rmq_generator()) - '100x^2 + -180 x + 81' - >>> random_id_rmq_generator() - '49x^2 + 42 x + 9' + >>> form = "A({a},{b}), B({2*a}, {3*b})" + >>> points_alea = random_str(form) + >>> points_alea + 'A(7,5), B(14, 15)' On remarque le défaut d'utiliser cette forme, le rendu est moins bien fait (dans l'exemple, il n'y a pas de parenthèses autour du -8). diff --git a/pymath/explicable.py b/pymath/explicable.py new file mode 100644 index 0000000..4db74b3 --- /dev/null +++ b/pymath/explicable.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from .render import txt, tex + +class Renderable(object): + STR_RENDER = tex + DEFAULT_RENDER = tex + + @classmethod + def set_render(cls, render): + cls.STR_RENDER = render + + @classmethod + def get_render(cls): + return cls.STR_RENDER + + @classmethod + def set_default_render(cls): + cls.set_render(cls.DEFAULT_RENDER) + + @classmethod + def tmp_render(cls, render = tex): + """ Create a container in which Expression render is temporary modify + + The default temporary render is Expression in order to perform calculus inside numbers + + >>> from .expression import Expression + >>> exp = Expression("2*3/5") + >>> print(exp) + 2 \\times \\frac{ 3 }{ 5 } + >>> for i in exp.simplify().explain(): + ... print(i) + 2 \\times \\frac{ 3 }{ 5 } + \\frac{ 6 }{ 5 } + >>> with Expression.tmp_render(txt): + ... for i in exp.simplify().explain(): + ... print(i) + 2 * 3 / 5 + 6 / 5 + >>> for i in exp.simplify().explain(): + ... print(i) + 2 \\times \\frac{ 3 }{ 5 } + \\frac{ 6 }{ 5 } + + """ + class TmpRenderEnv(object): + def __enter__(self): + self.old_render = Renderable.get_render() + Renderable.set_render(render) + + def __exit__(self, type, value, traceback): + Renderable.set_render(self.old_render) + return TmpRenderEnv() + +class Explicable(Renderable): + + """ An Explicable object is an object which can be explicable! + + It's a parent class of a more classical Expression, Fraction and Polynom (and later square root) + Child class will have the following method + * explain: Generator which return steps which leed to himself + + """ + def __init__(self, *args, **kwargs): + self.steps = [] + + def explain(self): + """ Generate and render steps which leed to itself """ + old_s = '' + # les étapes pour l'atteindre + try: + for s in self.steps: + if hasattr(s, 'postfix_tokens'): + new_s = self.STR_RENDER(s.postfix_tokens) + else: + new_s = self.STR_RENDER(s) + if new_s != old_s: + old_s = new_s + yield new_s + except AttributeError: + pass + + # Lui même + new_s = self.STR_RENDER(self.postfix_tokens) + if new_s != old_s: + yield new_s + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/pymath/expression.py b/pymath/expression.py index 44dc9e9..08ec98b 100644 --- a/pymath/expression.py +++ b/pymath/expression.py @@ -2,71 +2,18 @@ # encoding: utf-8 from .generic import Stack, flatten_list, expand_list, isNumber, isOperator, isNumerand -from .render import txt, tex from .str2tokens import str2tokens from .operator import op +from .explicable import Explicable from .random_expression import RdExpression __all__ = ['Expression'] -class Expression(object): + +class Expression(Explicable): """A calculus expression. Today it can andle only expression with numbers later it will be able to manipulate unknown""" - STR_RENDER = tex - DEFAULT_RENDER = tex - - @classmethod - def set_render(cls, render): - cls.STR_RENDER = render - - @classmethod - def get_render(cls ): - return cls.STR_RENDER - - @classmethod - def set_default_render(cls): - cls.set_render(cls.DEFAULT_RENDER) - - @classmethod - def tmp_render(cls, render = lambda _,x:Expression(x)): - """ Create a container in which Expression render is temporary modify - - The default temporary render is Expression in order to perform calculus inside numbers - - >>> exp = Expression("2*3/5") - >>> print(exp) - \\frac{ 2 \\times 3 }{ 5 } - >>> for i in exp.simplify(): - ... print(i) - \\frac{ 2 \\times 3 }{ 5 } - \\frac{ 6 }{ 5 } - >>> with Expression.tmp_render(): - ... for i in exp.simplify(): - ... i - < Expression [2, 3, '*', 5, '/']> - < Expression [6, 5, '/']> - < Fraction 6 / 5> - - >>> with Expression.tmp_render(txt): - ... for i in exp.simplify(): - ... print(i) - 2 * 3 / 5 - 6 / 5 - >>> for i in exp.simplify(): - ... print(i) - \\frac{ 2 \\times 3 }{ 5 } - \\frac{ 6 }{ 5 } - - """ - class TmpRenderEnv(object): - def __enter__(self): - self.old_render = Expression.get_render() - Expression.set_render(render) - - def __exit__(self, type, value, traceback): - Expression.set_render(self.old_render) - return TmpRenderEnv() @classmethod def random(self, form="", conditions=[], val_min = -10, val_max=10): @@ -81,6 +28,38 @@ class Expression(object): random_generator = RdExpression(form, conditions) return Expression(random_generator(val_min, val_max)) + @classmethod + def tmp_render(cls, render = lambda _,x:Expression(x)): + """ Same ad tmp_render for Renderable but default render is Expression + + >>> exp = Expression("2*3/5") + >>> print(exp) + 2 \\times \\frac{ 3 }{ 5 } + >>> for i in exp.simplify().explain(): + ... print(i) + 2 \\times \\frac{ 3 }{ 5 } + \\frac{ 6 }{ 5 } + >>> with Expression.tmp_render(): + ... for i in exp.simplify().explain(): + ... i + < [2, 3, 5, '/', '*'] > + < [2, < Fraction 3 / 5>, '*'] > + < [2, < Fraction 3 / 5>, '*'] > + < [6, 5, '/'] > + >>> from .render import txt + >>> with Expression.tmp_render(txt): + ... for i in exp.simplify().explain(): + ... print(i) + 2 * 3 / 5 + 6 / 5 + >>> for i in exp.simplify().explain(): + ... print(i) + 2 \\times \\frac{ 3 }{ 5 } + \\frac{ 6 }{ 5 } + + """ + return super(Expression, cls).tmp_render(render) + def __new__(cls, exp): """Create Expression objects @@ -89,36 +68,35 @@ class Expression(object): """ expression = object.__new__(cls) if type(exp) == str: - #self._exp = exp - expression.postfix_tokens = str2tokens(exp) # les tokens seront alors stockés dans self.tokens temporairement + expression.postfix_tokens = str2tokens(exp) elif type(exp) == list: expression.postfix_tokens = flatten_list([tok.postfix_tokens if Expression.isExpression(tok) else tok for tok in exp]) + elif type(exp) == Expression: + return exp else: raise ValueError("Can't build Expression with {} object".format(type(exp))) if len(expression.postfix_tokens) == 1: token = expression.postfix_tokens[0] - if hasattr(token, 'simplify'): + if hasattr(token, 'simplify') and hasattr(token, 'explain'): return expression.postfix_tokens[0] elif type(token) == int: # On crée un faux int en ajoutant la méthode simplify et simplified et la caractérisique isNumber - simplify = lambda x:[x] - simplified = lambda x:x + simplify = lambda x:x is_number = True - methods_attr = {'simplify':simplify, 'simplified':simplified, 'isNumber': is_number} - fake_token = type('fake_int', (int,), methods_attr)(token) + methods_attr = {'simplify':simplify, 'isNumber': is_number, 'postfix_tokens': [token]} + fake_token = type('fake_int', (int,Explicable, ), methods_attr)(token) return fake_token elif type(token) == str: + # TODO: Pourquoi ne pas créer directement un polynom ici? |jeu. févr. 26 18:59:24 CET 2015 # On crée un faux str en ajoutant la méthode simplify et simplified et la caractérisique isNumber simplify = lambda x:[x] - simplified = lambda x:x is_polynom = True - methods_attr = {'simplify':simplify, 'simplified':simplified, '_isPolynom': is_polynom} - fake_token = type('fake_str', (str,), methods_attr)(token) + methods_attr = {'simplify':simplify, '_isPolynom': is_polynom, 'postfix_tokens': [token]} + fake_token = type('fake_str', (str,Explicable, ), methods_attr)(token) return fake_token - else: raise ValueError("Unknow type in Expression: {}".format(type(token))) @@ -129,74 +107,29 @@ class Expression(object): def __str__(self): """ Overload str - If you want to changer render use Expression.set_render(...) + + If you want to changer render use Expression.set_render(...) or use tmp_render context manager. """ return self.STR_RENDER(self.postfix_tokens) def __repr__(self): - return "< Expression " + str(self.postfix_tokens) + ">" - - def render(self, render = lambda x:str(x)): - """ Same as __str__ but accept render as argument - :param render: function which render the list of token (postfix form) to string - - """ - # TODO: I don't like the name of this method |ven. janv. 17 12:48:14 CET 2014 - return render(self.postfix_tokens) - - ## --------------------- - ## Mechanism functions + return " ".join(["<", str(self.__class__) , str(self.postfix_tokens), ">"]) def simplify(self): - """ Generator which return steps for computing the expression """ - if not self.can_go_further(): - yield self.STR_RENDER(self.postfix_tokens) - else: - self.compute_exp() - old_s = '' - for s in self.steps: - new_s = self.STR_RENDER(s) - # Astuce pour éviter d'avoir deux fois la même étape (par exemple pour la transfo d'une division en fraction) - if new_s != old_s: - old_s = new_s - yield new_s - - if Expression.isExpression(self.child): - for s in self.child.simplify(): - if old_s != s: - old_s = s - yield s - else: - for s in self.child.simplify(): - new_s = self.STR_RENDER([s]) - # Astuce pour éviter d'avoir deux fois la même étape (par exemple pour la transfo d'une division en fraction) - if new_s != old_s: - old_s = new_s - yield new_s - if old_s != self.STR_RENDER([self.child]): - yield self.STR_RENDER([self.child]) - - - def simplified(self): - """ Get the simplified version of the expression """ + """ Compute entirely the expression and return the result with .steps attribute """ self.compute_exp() - try: - return self.child.simplified() - except AttributeError: - return self.child - def can_go_further(self): - """Check whether it's a last step or not. If not create self.child the next expression. - :returns: 1 if it's not the last step, 0 otherwise - """ - if len(self.postfix_tokens) == 1: - return 0 - else: - return 1 + self.simplified = self.child.simplify() + try: + self.simplified.steps = self.child.steps + self.simplified.steps + except AttributeError: + pass + + return self.simplified def compute_exp(self): - """ Create self.child with self.steps to go up to it """ - self.steps = [self.postfix_tokens] + """ Create self.child with and stock steps in it """ + child_steps = [self.postfix_tokens] tokenList = self.postfix_tokens.copy() tmpTokenList = [] @@ -256,9 +189,10 @@ class Expression(object): steps = expand_list(tmpTokenList) if len(steps[:-1]) > 0: - self.steps += [flatten_list(s) for s in steps[:-1]] + child_steps += [flatten_list(s) for s in steps[:-1]] self.child = Expression(steps[-1]) + self.child.steps = child_steps @classmethod def isExpression(self, other): @@ -297,31 +231,83 @@ class Expression(object): return Expression([other] + self.postfix_tokens + [operator]) def __add__(self, other): + """ Overload + + + >>> a = Expression("1+2") + >>> print(a.postfix_tokens) + [1, 2, '+'] + >>> b = Expression("3+4") + >>> print(b.postfix_tokens) + [3, 4, '+'] + >>> c = a + b + >>> print(c.postfix_tokens) + [1, 2, '+', 3, 4, '+', '+'] + """ return self.operate(other, op.add) def __radd__(self, other): return self.roperate(other, op.add) def __sub__(self, other): + """ Overload - + + >>> a = Expression("1+2") + >>> print(a.postfix_tokens) + [1, 2, '+'] + >>> b = Expression("3+4") + >>> print(b.postfix_tokens) + [3, 4, '+'] + >>> c = a - b + >>> print(c.postfix_tokens) + [1, 2, '+', 3, 4, '+', '-'] + """ return self.operate(other, op.sub) def __rsub__(self, other): return self.roperate(other, op.sub) def __mul__(self, other): + """ Overload * + + >>> a = Expression("1+2") + >>> print(a.postfix_tokens) + [1, 2, '+'] + >>> b = Expression("3+4") + >>> print(b.postfix_tokens) + [3, 4, '+'] + >>> c = a * b + >>> print(c.postfix_tokens) + [1, 2, '+', 3, 4, '+', '*'] + """ return self.operate(other, op.mul) def __rmul__(self, other): return self.roperate(other, op.mul) - def __div__(self, other): + def __truediv__(self, other): + """ Overload / + + >>> a = Expression("1+2") + >>> print(a.postfix_tokens) + [1, 2, '+'] + >>> b = Expression("3+4") + >>> print(b.postfix_tokens) + [3, 4, '+'] + >>> c = a / b + >>> print(c.postfix_tokens) + [1, 2, '+', 3, 4, '+', '/'] + >>> + """ return self.operate(other, op.div) - def __rdiv__(self, other): + def __rtruediv__(self, other): return self.roperate(other, op.div) def __pow__(self, other): - return self.operate(other, op.pow) + return self.operate(other, op.pw) + + def __xor__(self, other): + return self.operate(other, op.pw) def __neg__(self): return Expression(self.postfix_tokens + [op.sub1]) @@ -329,9 +315,10 @@ class Expression(object): def test(exp): a = Expression(exp) - print(a) - for i in a.simplify(): - print(type(i)) + b = a.simplify() + + for i in b.explain(): + #print(type(i)) print(i) #print(type(a.simplified()), ":", a.simplified()) diff --git a/pymath/fraction.py b/pymath/fraction.py index 4b9ff3e..7a21356 100644 --- a/pymath/fraction.py +++ b/pymath/fraction.py @@ -5,12 +5,14 @@ from .arithmetic import gcd from .generic import isNumber from .operator import op from .expression import Expression +from .explicable import Explicable from .render import txt, tex +from copy import copy __all__ = ['Fraction'] -class Fraction(object): +class Fraction(Explicable): """Fractions!""" def __init__(self, num, denom = 1): @@ -20,6 +22,7 @@ class Fraction(object): :param denom: the denominator """ + super(Fraction, self).__init__() self._num = num self._denom = denom @@ -32,44 +35,57 @@ class Fraction(object): >>> f = Fraction(3, 6) >>> f.simplify() - [< Expression [1, 3, '*', 2, 3, '*', '/']>, < Fraction 1 / 2>] + < Fraction 1 / 2> + >>> for i in f.simplify().explain(): + ... print(i) + \\frac{ 3 }{ 6 } + \\frac{ 1 \\times 3 }{ 2 \\times 3 } + \\frac{ 1 }{ 2 } + >>> f = Fraction(6,9) + >>> f.simplify() + < Fraction 2 / 3> + >>> for i in f.simplify().explain(): + ... print(i) + \\frac{ 6 }{ 9 } + \\frac{ 2 \\times 3 }{ 3 \\times 3 } + \\frac{ 2 }{ 3 } >>> f = Fraction(0,3) >>> f.simplify() - [0] + 0 """ - steps = [] + ini_step = [Expression(self.postfix_tokens)] if self._num == 0: - steps.append(0) + return Expression([0]) - return steps - - if self._denom < 0: + elif self._denom < 0: n_frac = Fraction(-self._num, -self._denom) - steps.append(n_frac) - else: - n_frac = self + ans = n_frac.simplify() + ans.steps = ini_step + ans.steps + return ans - gcd_ = gcd(abs(n_frac._num), abs(n_frac._denom)) - if gcd_ == n_frac._denom: - n_frac = n_frac._num // gcd_ - steps.append(n_frac) + gcd_ = gcd(abs(self._num), abs(self._denom)) + if gcd_ == self._denom: + n_frac = self._num // gcd_ + return Expression([n_frac]) elif gcd_ != 1: - n_frac = Fraction(n_frac._num // gcd_ , n_frac._denom // gcd_) - steps.append(Expression([n_frac._num, gcd_, op.mul, n_frac._denom, gcd_, op.mul, op.div ])) + n_frac = Fraction(self._num // gcd_ , self._denom // gcd_) + ini_step += [Expression([n_frac._num, gcd_, op.mul, n_frac._denom, gcd_, op.mul, op.div ])] - steps.append(n_frac) + n_frac.steps = ini_step + n_frac.steps + return n_frac - return steps + else: + return self @property - def postfix(self): + def postfix_tokens(self): """Postfix form of the fraction >>> f = Fraction(3, 5) - >>> f.postfix + >>> f.postfix_tokens [3, 5, '/'] """ @@ -79,7 +95,7 @@ class Fraction(object): return [self._num, self._denom, op.div] def __str__(self): - return str(Expression(self.postfix)) + return str(Expression(self.postfix_tokens)) def __repr__(self): return "< Fraction {num} / {denom}>".format(num=self._num, denom = self._denom) @@ -95,10 +111,10 @@ class Fraction(object): def __tex__(self): old_render = Expression.get_render() Expression.set_render(tex) - _txt = self.__str__() + _tex = self.__str__() Expression.set_render(old_render) - return _txt + return _tex def __float__(self): return self._num / self._denom @@ -119,18 +135,28 @@ class Fraction(object): >>> f = Fraction(1, 2) >>> g = Fraction(2, 3) >>> f + g - [< Expression [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '+']>, < Expression [3, 6, '/', 4, 6, '/', '+']>, < Expression [< Fraction 3 / 6>, < Fraction 4 / 6>, '+']>, < Expression [3, 4, '+', 6, '/']>, < Expression [7, 6, '/']>, < Fraction 7 / 6>] + < Fraction 7 / 6> + >>> (f+g).steps + [< [1, 2, '/', 2, 3, '/', '+'] >, [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '+'], [3, 6, '/', 4, 6, '/', '+'], [< Fraction 3 / 6>, < Fraction 4 / 6>, '+'], [< Fraction 3 / 6>, < Fraction 4 / 6>, '+']] >>> f + 2 - [< Expression [1, 1, '*', 2, 1, '*', '/', 2, 2, '*', 1, 2, '*', '/', '+']>, < Expression [1, 2, '/', 4, 2, '/', '+']>, < Expression [< Fraction 1 / 2>, < Fraction 4 / 2>, '+']>, < Expression [1, 4, '+', 2, '/']>, < Expression [5, 2, '/']>, < Fraction 5 / 2>] + < Fraction 5 / 2> + >>> (f+2).steps + [< [1, 2, '/', 2, '+'] >, [1, 1, '*', 2, 1, '*', '/', 2, 2, '*', 1, 2, '*', '/', '+'], [1, 2, '/', 4, 2, '/', '+'], [< Fraction 1 / 2>, < Fraction 4 / 2>, '+'], [< Fraction 1 / 2>, < Fraction 4 / 2>, '+']] >>> f = Fraction(3, 4) >>> g = Fraction(5, 4) >>> f + g - [< Expression [3, 5, '+', 4, '/']>, < Expression [8, 4, '/']>, 2] + 2 + >>> (f+g).steps + [< [3, 4, '/', 5, 4, '/', '+'] >, [3, 5, '+', 4, '/'], [8, 4, '/']] + >>> f+0 + < Fraction 3 / 4> + >>> (f+0).steps + [] """ if other == 0: - return [self] + return copy(self) number = self.convert2fraction(other) @@ -148,14 +174,14 @@ class Fraction(object): exp = Expression([self._num, coef1, op.mul, self._denom, coef1, op.mul, op.div, number._num, coef2, op.mul, number._denom, coef2, op.mul, op.div,op.add]) - with Expression.tmp_render(): - steps = list(exp.simplify()) - - return steps + ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.add]) + ans = exp.simplify() + ans.steps = [ini_step] + ans.steps + return ans def __radd__(self, other): if other == 0: - return [self] + return Expression(self.postfix_tokens) number = self.convert2fraction(other) @@ -167,11 +193,17 @@ class Fraction(object): >>> f = Fraction(1, 2) >>> g = Fraction(2, 3) >>> f - g - [< Expression [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '-']>, < Expression [3, 6, '/', 4, 6, '/', '-']>, < Expression [< Fraction 3 / 6>, < Fraction 4 / 6>, '-']>, < Expression [3, 4, '-', 6, '/']>, < Expression [-1, 6, '/']>, < Fraction -1 / 6>] + < Fraction -1 / 6> + >>> (f-g).steps + [< [1, 2, '/', 2, 3, '/', '-'] >, [1, 3, '*', 2, 3, '*', '/', 2, 2, '*', 3, 2, '*', '/', '-'], [3, 6, '/', 4, 6, '/', '-'], [< Fraction 3 / 6>, < Fraction 4 / 6>, '-'], [< Fraction 3 / 6>, < Fraction 4 / 6>, '-']] + >>> f - 0 + < Fraction 1 / 2> + >>> (f-0).steps + [] """ if other == 0: - return [self] + return copy(self) number = self.convert2fraction(other) @@ -189,38 +221,40 @@ class Fraction(object): exp = Expression([self._num, coef1, op.mul, self._denom, coef1, op.mul, op.div, number._num, coef2, op.mul, number._denom, coef2, op.mul, op.div,op.sub]) - with Expression.tmp_render(): - steps = list(exp.simplify()) - - return steps + ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.sub]) + ans = exp.simplify() + ans.steps = [ini_step] + ans.steps + return ans def __rsub__(self, other): if other == 0: - return [self] + return copy(self) number = self.convert2fraction(other) return number - self def __neg__(self): - """ overload - (as arity 1 operator + """ overload - (as arity 1 operator) >>> f = Fraction(1, 2) >>> -f - [< Fraction -1 / 2>] + < Fraction -1 / 2> + >>> (-f).steps + [] >>> f = Fraction(1, -2) >>> f < Fraction 1 / -2> >>> -f - [< Fraction -1 / -2>, < Fraction 1 / 2>] + < Fraction 1 / 2> + >>> (-f).steps + [< [-1, -2, '/'] >] """ f = Fraction(-self._num, self._denom) + ans = f.simplify() - with Expression.tmp_render(): - steps = [f] + f.simplify() - - return steps + return ans def __mul__(self, other): """ overload * @@ -228,21 +262,29 @@ class Fraction(object): >>> f = Fraction(1, 2) >>> g = Fraction(2, 3) >>> f*g - [< Expression [1, 1, 2, '*', '*', 1, 2, '*', 3, '*', '/']>, < Expression [1, 2, '*', 2, 3, '*', '/']>, < Expression [2, 6, '/']>, < Expression [1, 2, '*', 3, 2, '*', '/']>, < Fraction 1 / 3>] + < Fraction 1 / 3> + >>> (f*g).steps + [< [1, 2, '/', 2, 3, '/', '*'] >, [1, 1, 2, '*', '*', 1, 2, '*', 3, '*', '/'], [1, 2, '*', 2, 3, '*', '/'], [2, 6, '/'], < [2, 6, '/'] >, < [1, 2, '*', 3, 2, '*', '/'] >] >>> f * 0 - [0] + 0 + >>> (f*0).steps + [] >>> f*1 - [< Fraction 1 / 2>] + < Fraction 1 / 2> + >>> (f*1).steps + [] >>> f*4 - [< Expression [1, 2, '*', 2, '*', 1, 2, '*', '/']>, < Expression [2, 2, '*', 2, '/']>, < Expression [4, 2, '/']>, 2] + 2 + >>> (f*4).steps + [< [1, 2, '/', 4, '*'] >, [1, 2, '*', 2, '*', 1, 2, '*', '/'], [2, 2, '*', 2, '/'], [4, 2, '/']] """ steps = [] if other == 0: - return [0] + return Expression([0]) elif other == 1: - return [self] + return copy(self) # TODO: Changer dans le cas où il y a trop de 1 |dim. déc. 28 10:44:10 CET 2014 @@ -256,6 +298,7 @@ class Fraction(object): denom = [self._denom] exp = Expression(num + denom + [op.div]) + ini_step = Expression(self.postfix_tokens + [other, op.mul]) else: number = self.convert2fraction(other) @@ -279,28 +322,45 @@ class Fraction(object): exp = Expression(num1 + num2 + [ op.mul] + denom1 + denom2 + [op.mul, op.div]) - with Expression.tmp_render(): - steps = list(exp.simplify()) - - return steps + ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.mul]) + ans = exp.simplify() + ans.steps = [ini_step] + ans.steps + return ans def __rmul__(self, other): return self * other def __truediv__(self, other): + """ overload / + + >>> f = Fraction(1,2) + >>> g = Fraction(3,4) + >>> f / 0 + Traceback (most recent call last): + ... + ZeroDivisionError: division by zero + >>> f / 1 + < Fraction 1 / 2> + >>> (f/1).steps + [] + >>> f / g + < Fraction 2 / 3> + + """ if other == 0: raise ZeroDivisionError("division by zero") elif other == 1: - return [self] + return copy(self) number = self.convert2fraction(other) - steps = [] - number = Fraction(number._denom, number._num) - steps.append(Expression([self, number, op.mul])) - steps += self * number + ini_step = Expression(self.postfix_tokens + number.postfix_tokens + [op.div]) - return steps + number = Fraction(number._denom, number._num) + ans = self * number + + ans.steps = [ini_step] + ans.steps + return ans def __rtruediv__(self, other): number = self.convert2fraction(other) @@ -312,44 +372,51 @@ class Fraction(object): >>> f = Fraction(3, 4) >>> f**0 - [1] + 1 + >>> (f**0).steps + [] >>> f**1 - [< Fraction 3 / 4>] + < Fraction 3 / 4> + >>> (f**1).steps + [] >>> f**3 - [< Expression [3, 3, '^', 4, 3, '^', '/']>, < Expression [27, 64, '/']>, < Fraction 27 / 64>] + < Fraction 27 / 64> + >>> (f**3).steps + [< [3, 4, '/', 3, '^'] >, [3, 3, '^', 4, 3, '^', '/'], [27, 64, '/'], [27, 64, '/']] >>> f = Fraction(6, 4) >>> f**3 - [< Expression [6, 3, '^', 4, 3, '^', '/']>, < Expression [216, 64, '/']>, < Expression [27, 8, '*', 8, 8, '*', '/']>, < Fraction 27 / 8>] + < Fraction 27 / 8> + >>> (f**3).steps + [< [6, 4, '/', 3, '^'] >, [6, 3, '^', 4, 3, '^', '/'], [216, 64, '/'], < [216, 64, '/'] >, < [27, 8, '*', 8, 8, '*', '/'] >] """ if not type(power) == int: raise ValueError("Can't raise fraction to power {}".format(str(power))) if power == 0: - return [1] + return Expression([1]) elif power == 1: - return [self] + return copy(self) else: + ini_step = Expression(self.postfix_tokens + [power, op.pw]) exp = Expression([self._num, power, op.pw, self._denom, power, op.pw, op.div]) - with Expression.tmp_render(): - steps = list(exp.simplify()) - return steps + + ans = exp.simplify() + ans.steps = [ini_step] + ans.steps + return ans def __xor__(self, power): """ overload ^ + Work like ** >>> f = Fraction(3, 4) >>> f^0 - [1] + 1 >>> f^1 - [< Fraction 3 / 4>] + < Fraction 3 / 4> >>> f^3 - [< Expression [3, 3, '^', 4, 3, '^', '/']>, < Expression [27, 64, '/']>, < Fraction 27 / 64>] - >>> f = Fraction(6, 4) - >>> f^3 - [< Expression [6, 3, '^', 4, 3, '^', '/']>, < Expression [216, 64, '/']>, < Expression [27, 8, '*', 8, 8, '*', '/']>, < Fraction 27 / 8>] - + < Fraction 27 / 64> """ return self.__pow__(power) @@ -382,17 +449,22 @@ class Fraction(object): """ >= """ return float(self) >= float(other) + def __copy__(self): + """ Copying the fraction removing steps where it is from """ + return Fraction(self._num, self._denom) + if __name__ == '__main__': - #f = Fraction(1, 12) - #g = Fraction(1, 12) - #h = Fraction(1,-5) - #t = Fraction(10,3) - #print("---------") - #print("1 + ", str(h)) - #for i in (1 + h): - # print(i) + f = Fraction(1, 12) + g = Fraction(6, 12) + for i in g.simplify().explain(): + print("g = ",i) + h = Fraction(1,-5) + t = Fraction(10,3) + print("---------") + for i in (0 + h).explain(): + print('0 + h = ',i) #print("---------") #print(str(f) , "+", str(t)) #for i in (f + t): diff --git a/pymath/operator.py b/pymath/operator.py index 2ba1234..4d171c2 100644 --- a/pymath/operator.py +++ b/pymath/operator.py @@ -49,9 +49,15 @@ class Operator(str): :returns: the string with operator and operands """ - replacement = {"op"+str(i+1): ' '.join(self.add_parenthesis(op)) for (i,op) in enumerate(args)} - - ans = link.format(**replacement) + if self.arity == 1: + op1 = self.l_parenthesis(args[0], True) + ans = link.format(op1 = op1) + + elif self.arity == 2: + op1 = self.l_parenthesis(args[0], True) + op2 = self.r_parenthesis(args[1], True) + ans = link.format(op1 = op1, op2 = op2) + ans = save_mainOp(ans, self) return ans @@ -127,22 +133,40 @@ class Operator(str): # TODO: Attention à gestion des fractions qui se comportent chelou avec les parenthèses |dim. nov. 9 09:21:52 CET 2014 if self.arity == 1: # TODO: Marche juste avec -, il faudra voir quand il y aura d'autres operateurs unitaires |dim. nov. 9 09:24:53 CET 2014 - op1 = self.add_parenthesis(args[0]) + op1 = self.l_parenthesis(args[0]) ans = flatten_list([self, op1]) elif self.arity == 2: - op1 = self.add_parenthesis(args[0]) - op2 = self.add_parenthesis(args[1]) + op1 = self.l_parenthesis(args[0]) + op2 = self.r_parenthesis(args[1]) ans = flatten_list([op1, self, op2]) ans = save_mainOp(ans, self) return ans - def add_parenthesis(self, op): - """ Add parenthesis if necessary """ + def l_parenthesis(self, opl, str_join=False): + """ Add parenthesis for left operand if necessary """ + ans = opl + try: + if opl.mainOp == op.sub1: + ans = opl + elif opl.mainOp.priority < self.priority: + ans = flatten_list(["(", opl, ")"]) + except AttributeError as e: + # op has not the attribute priority + pass + + ans = flatten_list([ans]) + if str_join: + ans = ' '.join([str(i) for i in ans]) + return ans + + def r_parenthesis(self, op, str_join=False): + """ Add parenthesis for left operand if necessary """ + # TODO: /!\ Parenthèses pour -2abc et l'opérateur * |lun. mars 9 19:02:32 CET 2015 try: if op.mainOp.priority < self.priority: - op = flatten_list(["("] + [op] + [")"]) + op = flatten_list(["(", op, ")"]) except AttributeError: # op has not the attribute priority try: @@ -150,7 +174,10 @@ class Operator(str): op = ['(', op, ')'] except ValueError: pass - return flatten_list([op]) + ans = flatten_list([op]) + if str_join: + ans = ' '.join([str(i) for i in ans]) + return ans def save_mainOp(obj, mainOp): """Create a temporary class build over built-in type to stock the main operation of a calculus @@ -159,12 +186,6 @@ def save_mainOp(obj, mainOp): :mainOp: the main operator :returns: the same object with the main operation attribute """ - #class Fake(type(obj)): - # """ The fake class """ - # def __new__(cls, obj): - # op = type(obj).__new__(cls, obj) - # op.mainOp = mainOp - # return op Fake = type('fake_'+str(type(obj)), (type(obj),), {'mainOp': mainOp}) return Fake(obj) @@ -183,7 +204,8 @@ def operatorize(fun): * "_render": action use in __txt__ and __tex__ * "__txt__": txt rendering * "__tex__": tex rendering - * "add_parenthesis": mechanism to add parenthesis + * "l_parenthesis": mechanism to add parenthesis for left operande + * "r_parenthesis": mechanism to add parenthesis for rigth operande """ def mod_fun(self, *args): ans = fun(self, *args) @@ -259,8 +281,32 @@ class op(object): >>> add.__txt__('1','2') '1 + 2' >>> add.__tex__('1','-2') - '1 + (-2)' + '1 - 2' """ + + def _render(self, link, *args): + """Global step for __txt__ and __tex__ + + :param link: the link between operators + :param *args: the operands + :returns: the string with operator and operands + + """ + if args[1][0] == "-": + op1 = self.l_parenthesis(args[0], True) + op2 = self.r_parenthesis(args[1][1:], True) + ans = link.replace('+','-').format(op1 = op1, op2 = op2) + + ans = save_mainOp(ans, self) + return ans + else: + op1 = self.l_parenthesis(args[0], True) + op2 = self.r_parenthesis(args[1], True) + ans = link.format(op1 = op1, op2 = op2) + + ans = save_mainOp(ans, self) + return ans + caract = { "operator" : "+", \ "name" : "add",\ @@ -269,6 +315,7 @@ class op(object): "actions" : ("__add__","__radd__"), \ "txt" : "{op1} + {op2}",\ "tex" : "{op1} + {op2}",\ + "_render": _render,\ } return caract @@ -289,15 +336,40 @@ class op(object): '1 - 2' >>> sub.__tex__('1','-2') '1 - (-2)' + >>> sub.__tex__('-1','2') + 'i-1 - 2' """ + def l_parenthesis(self, op, str_join=False): + return op + + def r_parenthesis(self, op, str_join=False): + try: + if op.mainOp.priority <= self.priority: + op = flatten_list(["(", op, ")"]) + elif op[0] == '-': + op = flatten_list(["(", op, ")"]) + except AttributeError: + # op has not the attribute priority + try: + if int(op) < 0: + op = ['(', op, ')'] + except ValueError: + pass + ans = flatten_list([op]) + if str_join: + ans = ' '.join([str(i) for i in ans]) + return ans + caract = { "operator" : "-", \ "name" : "sub",\ - "priority" : 1, \ + "priority" : 2, \ "arity" : 2, \ "actions" : ("__sub__","__rsub__"), \ "txt" : "{op1} - {op2}",\ "tex" : "{op1} - {op2}",\ + "l_parenthesis": l_parenthesis,\ + "r_parenthesis": r_parenthesis,\ } return caract @@ -319,7 +391,7 @@ class op(object): >>> sub1.__tex__('-1') '- (-1)' """ - def add_parenthesis(self, op): + def l_parenthesis(self, op, str_join=False): """ Add parenthesis if necessary """ try: if op.mainOp.priority <= self.priority: @@ -331,17 +403,21 @@ class op(object): op = ['(', op, ')'] except ValueError: pass - return flatten_list([op]) + + ans = flatten_list([op]) + if str_join: + ans = ' '.join([str(i) for i in ans]) + return ans caract = { "operator" : "-", \ "name" : "sub1",\ - "priority" : 2, \ + "priority" : 3, \ "arity" : 1, \ "actions" : "__neg__",\ "txt" : "- {op1}",\ "tex" : "- {op1}",\ - "add_parenthesis": add_parenthesis,\ + "l_parenthesis": l_parenthesis,\ } return caract @@ -358,25 +434,55 @@ class op(object): 2 >>> mul.__tex__('1','2') '1 \\times 2' + >>> mul.__tex__('2','a') + '2 a' >>> mul.__txt__('1','2') '1 * 2' + >>> mul.__txt__('2','a') + '2 a' + >>> mul.__txt__('a','2') + 'a * 2' >>> mul.__tex__('1','-2') '1 \\times (-2)' """ # * can not be display in some cases + def is_visible(self, op1, op2): + """ Tells whether self has to be visible or not + + :param op1: left operande + :param op2: rigth operande + + """ + # TODO: À finir!!! |lun. mars 9 00:03:40 CET 2015 + if type(op2) == int: + # op2 est maintenant une chaine de caractères + return True + elif op2.isdecimal(): + return True + elif op2.isalpha(): + return False + elif op2[0].isdecimal(): + return True + elif op2[0] == "(" and not ("+" in op2 or "-" in op2[3:]): + return True + # Giga bricolage... + elif "frac" in op2: + return True + else: + return False + def _render(self, link, *args): - replacement = {"op"+str(i+1): ' '.join(self.add_parenthesis(op)) for (i,op) in enumerate(args)} + op1 = self.l_parenthesis(args[0], True) + op2 = self.r_parenthesis(args[1], True) - if not self.visibility or args[1][0] == "(" or \ - (type(args[1][0]) == str and args[1][0].isalpha()): - ans = "{op1} {op2}".format(**replacement) - ans = save_mainOp(ans, self) - return ans + if not self.is_visible(op1, op2): + ans = "{op1} {op2}".format(op1 = op1, op2 = op2) else: - ans = link.format(**replacement) - ans = save_mainOp(ans, self) - return ans + ans = link.format(op1 = op1, op2 = op2) + + ans = save_mainOp(ans, self) + return ans caract = { "operator" : "*", \ @@ -387,7 +493,8 @@ class op(object): "txt" : "{op1} * {op2}",\ "tex" : "{op1} \\times {op2}",\ "visibility": 1,\ - "_render": _render + "_render": _render,\ + "is_visible": is_visible,\ } return caract @@ -461,7 +568,7 @@ class op(object): caract = { "operator" : "^", \ "name" : "pw",\ - "priority" : 5, \ + "priority" : 6, \ "arity" : 2, \ "actions" : ("__pow__",""), \ "txt" : "{op1} ^ {op2}",\ @@ -484,28 +591,39 @@ class op(object): return caract if __name__ == '__main__': - print(op.add.__tex__('1','2')) - print(op.mul.__tex__('1','2')) - print(op.sub.__tex__('1','2')) - f = save_mainOp('2 + 3',op.add) - print(op.mul.__txt__(f, '4')) - f = save_mainOp('-3',op.sub1) - print(op.sub1.__txt__(f)) - print(op.sub1.__txt__('-3')) - f = save_mainOp('2 + 3',op.add) - print(op.sub1.__txt__(f)) + #print(op.add.__tex__('1','2')) + #print(op.mul.__tex__('1','2')) + #print(op.sub.__tex__('1','2')) + #f = save_mainOp('2 + 3',op.add) + #print(op.mul.__txt__(f, '4')) + #f = save_mainOp('-3',op.sub1) + #print(op.sub1.__txt__(f)) + #print(op.sub1.__txt__('-3')) + #f = save_mainOp('2 + 3',op.add) + #print(op.sub1.__txt__(f)) + #from .fraction import Fraction + #f = Fraction(1, 2) + #print(op.add.__txt__(f.__txt__(),'2')) + #print(op.add.__tex__(f.__tex__(),'2')) + + #print("\t op.can_be_operator('+') :" + str(op.can_be_operator('+'))) + #print("\t op.can_be_operator('t') :" + str(op.can_be_operator('t'))) + + from .render import tex + print(tex([-2, 3, op.add ])) + print("-----------------") + print(tex([-2, 3, op.mul ])) + print("-----------------") + from .polynom import Polynom + print(tex([Polynom([1,2,3]), 2, op.mul])) + print("-----------------") from .fraction import Fraction - f = Fraction(1, 2) - print(op.add.__txt__(f.__txt__(),'2')) - print(op.add.__tex__(f.__tex__(),'2')) + print(tex([2, Fraction(1,2), op.mul])) + print("-----------------") - print("\t op.can_be_operator('+') :" + str(op.can_be_operator('+'))) - print("\t op.can_be_operator('t') :" + str(op.can_be_operator('t'))) - - - import doctest - doctest.testmod() + #import doctest + #doctest.testmod() diff --git a/pymath/polynom.py b/pymath/polynom.py index 0c1bc1b..2bef1eb 100644 --- a/pymath/polynom.py +++ b/pymath/polynom.py @@ -3,11 +3,13 @@ from .expression import Expression +from .explicable import Explicable from .operator import op -from .generic import spe_zip, expand_list, isNumber, transpose_fill, flatten_list, isPolynom -from .render import txt +from .generic import spe_zip, expand_list, isNumber, transpose_fill, flatten_list, isPolynom, isNumerand +from .render import txt,tex from .random_expression import RdExpression from itertools import chain +from functools import wraps __all__ = ["Polynom"] @@ -15,6 +17,7 @@ __all__ = ["Polynom"] def power_cache(fun): """Decorator which cache calculated powers of polynoms """ cache = {} + @wraps(fun) def cached_fun(self, power): #print("cache -> ", cache) if (tuple(self._coef), power) in cache.keys(): @@ -25,12 +28,12 @@ def power_cache(fun): return poly_powered return cached_fun -class Polynom(object): +class Polynom(Explicable): """Docstring for Polynom. """ @classmethod - def random(self, coefs_form=[], conditions=[], letter = "x", degree = 0): + def random(self, coefs_form=[], conditions=[], letter = "x", degree = 0, name = "P"): """ Create a random polynom from coefs_form and conditions :param coefs_form: list of forms (one by coef) (ascending degree sorted) @@ -41,19 +44,19 @@ class Polynom(object): /!\ variables need to be in brackets {} >>> Polynom.random(["{b}", "{a}"]) # doctest:+ELLIPSIS - ... + < Polynom ... >>> Polynom.random(degree = 2) # doctest:+ELLIPSIS - ... + < Polynom ... >>> Polynom.random(degree = 2, conditions=["{b**2-4*a*c}>0"]) # Polynom deg 2 with positive Delta (ax^2 + bx + c) - ... + < Polynom ... >>> Polynom.random(["{c}", "{b}", "{a}"], conditions=["{b**2-4*a*c}>0"]) # Same as above - ... + < Polynom ... """ if (degree > 0 and degree < 26): # Générer assez de lettre pour les coefs coefs_name = map(chr, range(97, 98+degree)) - coefs_form = ["{" + i + "}" for i in coefs_name].reverse() + coefs_form = ["{" + i + "}" for i in coefs_name][::-1] form = str(coefs_form) # On créé les valeurs toutes concaténées dans un string @@ -61,9 +64,9 @@ class Polynom(object): # On "parse" ce string pour créer les coefs coefs = [eval(i) if type(i)==str else i for i in eval(coefs)] # Création du polynom - return Polynom(coef = coefs, letter = letter) + return Polynom(coefs = coefs, letter = letter, name = name) - def __init__(self, coef = [1], letter = "x" ): + def __init__(self, coefs = [1], letter = "x", name = "P"): """Initiate the polynom :param coef: coefficients of the polynom (ascending degree sorted) @@ -72,24 +75,36 @@ class Polynom(object): - [a,b,c]: list of coeficient for same degree. [1,[2,3],4] designate 1 + 2x + 3x + 4x^2 - a: a Expression. [1, Expression("2+3"), 4] designate 1 + (2+3)x + 4x^2 :param letter: the string describing the unknown + :param name: Name of the polynom - >>> Polynom([1,2,3]).mainOp + >>> P = Polynom([1, 2, 3]) + >>> P.mainOp '+' + >>> P.name + 'P' + >>> P._letter + 'x' >>> Polynom([1]).mainOp '*' - >>> Polynom([1,2, 3])._letter + >>> Polynom([0, 0, 3]).mainOp + '*' + >>> Polynom([1, 2, 3])._letter 'x' >>> Polynom([1, 2, 3], "y")._letter 'y' + >>> Polynom([1, 2, 3], name = "Q").name + 'Q' """ - self.feed_coef(coef) + super(Polynom, self).__init__() + self.feed_coef(coefs) self._letter = letter + self.name = name if self.is_monom(): - self.mainOp = "*" + self.mainOp = op.mul else: - self.mainOp = "+" + self.mainOp = op.add self._isPolynom = 1 @@ -98,13 +113,27 @@ class Polynom(object): :returns: Expression ready to be simplify + >>> P = Polynom([1, 2, 3]) + >>> P(2) + 17 + >>> for i in P(2).explain(): + ... print(i) + 3 \\times 2^{ 2 } + 2 \\times 2 + 1 + 3 \\times 4 + 4 + 1 + 12 + 4 + 1 + 16 + 1 + 17 + >>> Q = P("1+h") + >>> print(Q) + 3 h^{ 2 } + 8 h + 6 + >>> R = P(Q) """ - if isNumber(value): - postfix_exp = [value if i==self._letter else i for i in self.postfix] + if isNumerand(value) or Expression.isExpression(value): + postfix_exp = [value if i==self._letter else i for i in self.postfix_tokens] else: - postfix_exp = [Expression(value) if i==self._letter else i for i in self.postfix] + postfix_exp = [Expression(value) if i==self._letter else i for i in self.postfix_tokens] - return Expression(postfix_exp) + return Expression(postfix_exp).simplify() def feed_coef(self, l_coef): """Feed coef of the polynom. Manage differently whether it's a number or an expression @@ -146,12 +175,21 @@ class Polynom(object): else: return 0 + def give_name(self, name): + self.name = name + def __str__(self): - return str(Expression(self.postfix)) + return str(Expression(self.postfix_tokens)) def __repr__(self): return "< Polynom " + str(self._coef) + ">" + def __txt__(self): + return txt(self.postfix_tokens) + + def __tex__(self): + return tex(self.postfix_tokens) + def coef_postfix(self, a, i): """Return the postfix display of a coeficient @@ -188,37 +226,45 @@ class Polynom(object): return ans @property - def postfix(self): + def postfix_tokens(self): """Return the postfix form of the polynom :returns: the postfix list of polynom's tokens >>> p = Polynom([1, 2]) - >>> p.postfix + >>> p.postfix_tokens [2, 'x', '*', 1, '+'] >>> p = Polynom([1, -2]) - >>> p.postfix + >>> p.postfix_tokens [2, 'x', '*', '-', 1, '+'] >>> p = Polynom([1,2,3]) - >>> p.postfix + >>> p.postfix_tokens [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+'] + >>> p = Polynom([1]) + >>> p.postfix_tokens + [1] + >>> p = Polynom([0]) + >>> p.postfix_tokens + [0] >>> p = Polynom([1,[2,3]]) - >>> p.postfix + >>> p.postfix_tokens [2, 'x', '*', 3, 'x', '*', '+', 1, '+'] >>> p = Polynom([1,[2,-3]]) - >>> p.postfix + >>> p.postfix_tokens [2, 'x', '*', 3, 'x', '*', '-', 1, '+'] >>> p = Polynom([1,[-2,-3]]) - >>> p.postfix + >>> p.postfix_tokens [2, 'x', '*', '-', 3, 'x', '*', '-', 1, '+'] >>> from pymath.expression import Expression >>> from pymath.operator import op >>> e = Expression([2,3,op.add]) >>> p = Polynom([1,e]) - >>> p.postfix + >>> p.postfix_tokens [2, 3, '+', 'x', '*', 1, '+'] """ + if self == 0: + return [0] # TODO: Faudrait factoriser un peu tout ça..! |dim. déc. 21 16:02:34 CET 2014 postfix = [] for (i,a) in list(enumerate(self._coef))[::-1]: @@ -286,7 +332,15 @@ class Polynom(object): return flatten_list(postfix) def conv2poly(self, other): - """Convert anything number into a polynom""" + """Convert anything number into a polynom + + >>> P = Polynom([1,2,3]) + >>> P.conv2poly(1) + < Polynom [1]> + >>> P.conv2poly(0) + < Polynom [0]> + + """ if isNumber(other) and not isPolynom(other): return Polynom([other], letter = self._letter) elif isPolynom(other): @@ -298,8 +352,23 @@ class Polynom(object): """Compute coefficients which have same degree :returns: new Polynom with numbers coefficients + + >>> P = Polynom([1,2,3]) + >>> Q = P.reduce() + >>> Q + < Polynom [1, 2, 3]> + >>> Q.steps + [] + >>> P = Polynom([[1,2], [3,4,5], 6]) + >>> Q = P.reduce() + >>> Q + < Polynom [3, 12, 6]> + >>> Q.steps + [< Polynom [< [1, 2, '+'] >, < [3, 4, '+', 5, '+'] >, 6]>, < Polynom [< [1, 2, '+'] >, < [7, 5, '+'] >, 6]>, < Polynom [3, < [7, 5, '+'] >, 6]>] """ - steps = [] + + # TODO: It doesn't not compute quick enough |ven. févr. 27 18:04:01 CET 2015 + # gather steps for every coeficients coefs_steps = [] for coef in self._coef: @@ -311,20 +380,20 @@ class Polynom(object): coef_exp = Expression(postfix_add) with Expression.tmp_render(): - coef_steps = list(coef_exp.simplify()) + coef_steps = list(coef_exp.simplify().explain()) #print('\t 1.coef_steps -> ', coef_steps) elif type(coef) == Expression: with Expression.tmp_render(): - coef_steps = list(coef.simplify()) + coef_steps = list(coef.simplify().explain()) #print('\t 2.coef_steps -> ', coef_steps) else: try: - coef_steps += coef.simplify() + coef_steps += coef.simplify().explaine() except AttributeError: coef_steps = [coef] @@ -335,12 +404,37 @@ class Polynom(object): #print('\t coefs_steps -> ', coefs_steps) # On retourne la matrice - ans = [] + steps = [] for coefs in transpose_fill(coefs_steps): - ans.append(Polynom(coefs, self._letter)) + steps.append(Polynom(coefs, self._letter)) + + ans, steps = steps[-1], steps[:-1] + ans.steps = steps return ans + def derivate(self): + """ Return the derivated polynom + + >>> P = Polynom([1, 2, 3]) + >>> Q = P.derivate() + >>> Q + < Polynom [2, 6]> + >>> print(Q.name) + P' + >>> for i in Q.explain(): + ... print(i) + 2 \\times 3 x + 1 \\times 2 + 6 x + 2 + """ + derv_coefs = [] + for (i,c) in enumerate(self._coef): + derv_coefs += [Expression([i, c, op.mul])] + + ans = Polynom(derv_coefs[1:]).simplify() + ans.name = self.name + "'" + return ans + @staticmethod def postfix_add(numbers): """Convert a list of numbers into a postfix addition @@ -375,56 +469,104 @@ class Polynom(object): return 0 def __add__(self, other): - steps = [] + """ Overload + + >>> P = Polynom([1,2,3]) + >>> Q = Polynom([4,5]) + >>> R = P+Q + >>> R + < Polynom [5, 7, 3]> + >>> R.steps + [< [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 5, 'x', '*', 4, '+', '+'] >, < Polynom [< [1, 4, '+'] >, < [2, 5, '+'] >, 3]>, < Polynom [< [1, 4, '+'] >, < [2, 5, '+'] >, 3]>] + """ o_poly = self.conv2poly(other) n_coef = spe_zip(self._coef, o_poly._coef) p = Polynom(n_coef, letter = self._letter) - steps.append(p) - steps += p.simplify() - return steps + ini_step = [Expression(self.postfix_tokens + o_poly.postfix_tokens + [op.add])] + ans = p.simplify() + ans.steps = ini_step + ans.steps + return ans def __radd__(self, other): - return self.__add__(other) + o_poly = self.conv2poly(other) + return o_poly.__add__(self) def __neg__(self): - return Polynom([-i for i in self._coef], letter = self._letter) + """ overload - (as arity 1 operator) + + >>> P = Polynom([1,2,3]) + >>> Q = -P + >>> Q + < Polynom [-1, -2, -3]> + >>> Q.steps + [< [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', '-'] >] + """ + ini_step = [Expression(self.postfix_tokens + [op.sub1])] + ans = Polynom([-i for i in self._coef], letter = self._letter).simplify() + ans.steps = ini_step + ans.steps + return ans def __sub__(self, other): - o_poly = self.conv2poly(other) - o_poly = -o_poly + """ overload - - return self.__add__(o_poly) + >>> P = Polynom([1,2,3]) + >>> Q = Polynom([4,5,6]) + >>> R = P - Q + >>> R + < Polynom [-3, -3, -3]> + >>> R.steps + [< [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 6, 'x', 2, '^', '*', 5, 'x', '*', '+', 4, '+', '-'] >, < [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+', 6, 'x', 2, '^', '*', '-', 5, 'x', '*', '-', 4, '-', '+'] >, < Polynom [< [1, -4, '+'] >, < [2, -5, '+'] >, < [3, -6, '+'] >]>, < Polynom [< [1, -4, '+'] >, < [2, -5, '+'] >, < [3, -6, '+'] >]>] + """ + o_poly = self.conv2poly(other) + ini_step = [Expression(self.postfix_tokens + o_poly.postfix_tokens + [op.sub])] + o_poly = -o_poly + #ini_step += o_poly.steps + + ans = self + o_poly + ans.steps = ini_step + ans.steps + + return ans def __rsub__(self, other): o_poly = self.conv2poly(other) - return o_poly.__sub__(-self) + return o_poly.__sub__(self) def __mul__(self, other): """ Overload * >>> p = Polynom([1,2]) >>> p*3 - [< Polynom [3, < Expression [2, 3, '*']>]>, < Polynom [3, < Expression [2, 3, '*']>]>, < Polynom [3, 6]>] + < Polynom [3, 6]> + >>> (p*3).steps + [[< [2, 'x', '*', 1, '+', 3, '*'] >], < Polynom [3, < [2, 3, '*'] >]>, < Polynom [3, < [2, 3, '*'] >]>] >>> q = Polynom([0,0,4]) >>> q*3 - [< Polynom [0, 0, < Expression [4, 3, '*']>]>, < Polynom [0, 0, < Expression [4, 3, '*']>]>, < Polynom [0, 0, 12]>] + < Polynom [0, 0, 12]> + >>> (q*3).steps + [[< [4, 'x', 2, '^', '*', 3, '*'] >], < Polynom [0, 0, < [4, 3, '*'] >]>, < Polynom [0, 0, < [4, 3, '*'] >]>] >>> r = Polynom([0,1]) >>> r*3 - [< Polynom [0, 3]>, < Polynom [0, 3]>] + < Polynom [0, 3]> + >>> (r*3).steps + [[< ['x', 3, '*'] >]] >>> p*q - [< Polynom [0, 0, 4, < Expression [2, 4, '*']>]>, < Polynom [0, 0, 4, < Expression [2, 4, '*']>]>, < Polynom [0, 0, 4, 8]>] + < Polynom [0, 0, 4, 8]> + >>> (p*q).steps + [[< [2, 'x', '*', 1, '+', 4, 'x', 2, '^', '*', '*'] >], < Polynom [0, 0, 4, < [2, 4, '*'] >]>, < Polynom [0, 0, 4, < [2, 4, '*'] >]>] >>> p*r - [< Polynom [0, 1, 2]>, < Polynom [0, 1, 2]>] - + < Polynom [0, 1, 2]> + >>> P = Polynom([1,2,3]) + >>> Q = Polynom([4,5,6]) + >>> P*Q + < Polynom [4, 13, 28, 27, 18]> """ - steps = [] + # TODO: Je trouve qu'elle grille trop d'étapes... |ven. févr. 27 19:08:44 CET 2015 o_poly = self.conv2poly(other) - coefs = [] + coefs = [0]*(self.degree + o_poly.degree + 1) for (i,a) in enumerate(self._coef): for (j,b) in enumerate(o_poly._coef): if a == 0 or b == 0: @@ -435,22 +577,21 @@ class Polynom(object): elem = a else: elem = Expression([a, b, op.mul]) - try: - if coefs[i+j]==0: - coefs[i+j] = elem - elif elem != 0: - coefs[i+j] = [coefs[i+j], elem] - except IndexError: - coefs.append(elem) + + if coefs[i+j]==0: + coefs[i+j] = elem + elif elem != 0: + if type(coefs[i+j]) == list: + coefs[i+j] += [elem] + else: + coefs[i+j] = [coefs[i+j] , elem] p = Polynom(coefs, letter = self._letter) - steps.append(p) + ini_step = [Expression(self.postfix_tokens + o_poly.postfix_tokens + [op.mul])] + ans = p.simplify() - steps += p.simplify() - - #print("steps -> \n", "\n".join(["\t {}".format(s.postfix) for s in steps])) - - return steps + ans.steps = [ini_step] + ans.steps + return ans def __rmul__(self, other): o_poly = self.conv2poly(other) @@ -463,39 +604,45 @@ class Polynom(object): >>> p = Polynom([0,0,3]) >>> p**2 - [< Polynom [0, 0, 0, 0, < Expression [3, 2, '^']>]>, < Polynom [0, 0, 0, 0, < Expression [3, 2, '^']>]>, < Polynom [0, 0, 0, 0, 9]>, < Polynom [0, 0, 0, 0, 9]>] + < Polynom [0, 0, 0, 0, 9]> + >>> (p**2).steps + [< [3, 'x', 2, '^', '*', 2, '^'] >, < Polynom [0, 0, 0, 0, < [3, 2, '^'] >]>, < Polynom [0, 0, 0, 0, < [3, 2, '^'] >]>] >>> p = Polynom([1,2]) >>> p**2 - [[< Polynom [1, 2]>, < Polynom [1, 2]>, '*'], < Polynom [< Expression [1, 1, '*']>, [< Expression [1, 2, '*']>, < Expression [2, 1, '*']>], < Expression [2, 2, '*']>]>, < Polynom [< Expression [1, 1, '*']>, < Expression [1, 2, '*', 2, 1, '*', '+']>, < Expression [2, 2, '*']>]>, < Polynom [1, < Expression [2, 2, '+']>, 4]>, < Polynom [1, 4, 4]>] + < Polynom [1, 4, 4]> + >>> (p**2).steps + [< [2, 'x', '*', 1, '+', 2, '^'] >, [< [2, 'x', '*', 1, '+', 2, 'x', '*', 1, '+', '*'] >], < Polynom [1, < [2, 2, '+'] >, < [2, 2, '*'] >]>, < Polynom [1, < [2, 2, '+'] >, < [2, 2, '*'] >]>] >>> p = Polynom([0,0,1]) >>> p**3 - [< Polynom [0, 0, 0, 0, 0, 0, 1]>] - + < Polynom [0, 0, 0, 0, 0, 0, 1]> + >>> p = Polynom([1,2,3]) + >>> p**2 + < Polynom [1, 4, 10, 12, 9]> """ if not type(power): raise ValueError("Can't raise Polynom to {} power".format(str(power))) - steps = [] + ini_step = [Expression(self.postfix_tokens + [power, op.pw])] if self.is_monom(): if self._coef[self.degree] == 1: coefs = [0]*self.degree*power + [1] p = Polynom(coefs, letter = self._letter) - steps.append(p) + ans = p else: coefs = [0]*self.degree*power + [Expression([self._coef[self.degree] , power, op.pw])] p = Polynom(coefs, letter = self._letter) - steps.append(p) - - steps += p.simplify() + ans = p.simplify() else: if power == 2: - return [[self, self, op.mul]] + self * self + ans = self * self else: + # TODO: faudrait changer ça c'est pas très sérieux |ven. févr. 27 22:08:00 CET 2015 raise AttributeError("__pw__ not implemented yet when power is greatter than 2") - return steps + ans.steps = ini_step + ans.steps + return ans def __xor__(self, power): return self.__pow__(power) @@ -513,19 +660,19 @@ def test(p,q): print(p, "+", q) for i in (p + q): #print(repr(i)) - #print("\t", str(i.postfix)) + #print("\t", str(i.postfix_tokens)) print(i) print("\n Moins ------") for i in (p - q): #print(repr(i)) - #print("\t", str(i.postfix)) + #print("\t", str(i.postfix_tokens)) print(i) print("\n Multiplier ------") for i in (p * q): #print(repr(i)) - #print("\t", str(i.postfix)) + #print("\t", str(i.postfix_tokens)) print(i) print("\n Evaluer p ------") @@ -539,16 +686,27 @@ def test(p,q): if __name__ == '__main__': #from .fraction import Fraction - with Expression.tmp_render(txt): - p = Polynom([10, -5]) - q = Polynom([3, -9]) - print(p-q) - for i in p-q: - print(i) + #with Expression.tmp_render(txt): + # p = Polynom([1, 2, 3]) + # q = Polynom([4, 5, 6]) + # for i in (p*q).explain(): + # print(i) + # r = Polynom([0,1]) + # for i in (r*3).explain(): + # print(i) + # print("q = ", q) + # r = q.reduce() + # print("r = ", r) + # for i in r.explain(): + # print("q = ", i) + # print(p-q) + # for i in p-q: + # print(i) + #Polynom.random(degree = 2, conditions=["{b**2-4*a*c}>0"]) # Polynom deg 2 with positive Delta (ax^2 + bx + c) import doctest - doctest.testmod() + doctest.testmod(optionflags=doctest.ELLIPSIS) # ----------------------------- diff --git a/pymath/polynomDeg2.py b/pymath/polynomDeg2.py new file mode 100644 index 0000000..9a87efb --- /dev/null +++ b/pymath/polynomDeg2.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from .polynom import Polynom +from .expression import Expression +from .operator import op +from .random_expression import RdExpression +from math import sqrt + +__all__ = ["Polynom_deg2"] + +class Polynom_deg2(Polynom): + + """ Degree 2 polynoms + Child of Polynom with some extra tools + """ + + @classmethod + def random(self, coefs_form = ["{c}", "{b}", "{a}"], conditions = [], letter = "x", name = "P"): + """ Create a 2nd degree poly from coefs_form ans conditions + + :param coefs_form: list of forms (one by coef) (ascending degree sorted) + :param conditions: condition on variables + :param letter: the letter for the polynom + + """ + if len(coefs_form) != 3: + raise ValueError("Polynom_deg2 have to be degree 2 polynoms, they need 3 coefficients, {} are given".format(len(coefs_form))) + + form = str(coefs_form) + # On créé les valeurs toutes concaténées dans un string + coefs = RdExpression(form, conditions)() + # On "parse" ce string pour créer les coefs + coefs = [eval(i) if type(i)==str else i for i in eval(coefs)] + # Création du polynom + return Polynom_deg2(coefs = coefs, letter = letter, name = name) + + def __init__(self, coefs = [0, 0, 1], letter = "x", name = "P"): + if len(coefs) < 3 or len(coefs) > 4: + raise ValueError("Polynom_deg2 have to be degree 2 polynoms, they need 3 coefficients, {} are given".format(len(coefs))) + if coefs[2] == 0: + raise ValueError("Polynom_deg2 have to be degree 2 polynoms, coefficient of x^2 can't be 0") + Polynom.__init__(self, coefs, letter, name = name) + + @property + def a(self): + return self._coef[2] + + @property + def b(self): + return self._coef[1] + + @property + def c(self): + return self._coef[0] + + @property + def delta(self): + """Compute the discriminant expression + :returns: discriminant expression + + >>> P = Polynom_deg2([1,2,3]) + >>> P.delta + -8 + >>> for i in P.delta.explain(): + ... print(i) + 2^{ 2 } - 4 \\times 3 \\times 1 + 4 - 4 \\times 3 + 4 - 12 + -8 + """ + + return Expression([self.b, 2, op.pw, 4, self.a, self.c, op.mul, op.mul, op.sub]).simplify() + + @property + def alpha(self): + """ Compute alpha the abcisse of the extremum + + >>> P = Polynom_deg2([1,2,3]) + >>> P.alpha + < Fraction -1 / 3> + >>> for i in P.alpha.explain(): + ... print(i) + \\frac{ - 2 }{ 2 \\times 3 } + \\frac{ -2 }{ 6 } + \\frac{ ( -1 ) \\times 2 }{ 3 \\times 2 } + \\frac{ -1 }{ 3 } + """ + return Expression([self.b, op.sub1, 2, self.a, op.mul, op.div]).simplify() + + @property + def beta(self): + """ Compute beta the extremum of self + + >>> P = Polynom_deg2([1,2,3]) + >>> P.beta + < Fraction 2 / 3> + >>> for i in P.beta.explain(): # Ça serait bien que l'on puisse enlever des étapes maintenant... + ... print(i) + 3 \\times \\frac{ -1 }{ 3 }^{ 2 } + 2 \\times \\frac{ -1 }{ 3 } + 1 + 3 \\times \\frac{ 1 }{ 9 } + \\frac{ -2 }{ 3 } + 1 + \\frac{ 1 }{ 3 } + \\frac{ -2 }{ 3 } + 1 + \\frac{ -1 }{ 3 } + 1 + \\frac{ 2 }{ 3 } + """ + return self(self.alpha).simplify() + + def roots(self, after_coma = 2): + """ Compute roots of the polynom + + /!\ Can't manage exact solution because of pymath does not handle sqare root yet + + # TODO: Pymath has to know how to compute with sqare root |mar. févr. 24 18:40:04 CET 2015 + + >>> P = Polynom_deg2([1, 1, 1]) + >>> P.roots() + [] + >>> P = Polynom_deg2([1, 2, 1]) + >>> P.roots() + [-1.0] + >>> P = Polynom_deg2([-1, 0, 1]) + >>> P.roots() + [-1.0, 1.0] + """ + if self.delta > 0: + self._roots = [round((-self.b - sqrt(self.delta))/(2*self.a),after_coma), round((-self.b + sqrt(self.delta))/(2*self.a),after_coma)] + elif self.delta == 0: + self._roots = [round(-self.b /(2*self.a), after_coma)] + else: + self._roots = [] + return self._roots + + def tbl_sgn_header(self): + """ Return header of the sign line for tkzTabLine""" + if self.delta > 0: + return "{$-\\infty$, " + str(min(self.roots())) + " , " + str( max(self.roots())) + " , $+\\infty$}" + elif self.delta == 0: + return "{$-\\infty$, " + str(self.roots()[0]) + " , $+\\infty$}" + else: + return "{$-\\infty$, $+\\infty$}" + + def tbl_sgn(self): + """ Return the sign line for tkzTabLine + + >>> P = Polynom_deg2([2, 5, 2]) + >>> print(P.tbl_sgn()) + \\tkzTabLine{, +, z, -, z , +,} + >>> P = Polynom_deg2([2, 1, -2]) + >>> print(P.tbl_sgn()) + \\tkzTabLine{, -, z, +, z , -,} + >>> P = Polynom_deg2([1, 2, 1]) + >>> print(P.tbl_sgn()) + \\tkzTabLine{, +, z, +,} + >>> P = Polynom_deg2([0, 0, -2]) + >>> print(P.tbl_sgn()) + \\tkzTabLine{, -, z, -,} + >>> P = Polynom_deg2([1, 0, 1]) + >>> print(P.tbl_sgn()) + \\tkzTabLine{, +,} + >>> P = Polynom_deg2([-1, 0, -1]) + >>> print(P.tbl_sgn()) + \\tkzTabLine{, -,} + """ + if self.delta > 0: + if self.a > 0: + return "\\tkzTabLine{, +, z, -, z , +,}" + else: + return "\\tkzTabLine{, -, z, +, z , -,}" + elif self.delta == 0: + if self.a > 0: + return "\\tkzTabLine{, +, z, +,}" + else: + return "\\tkzTabLine{, -, z, -,}" + else: + if self.a > 0: + return "\\tkzTabLine{, +,}" + else: + return "\\tkzTabLine{, -,}" + + def tbl_variation(self, limits = False): + """Return the variation line for tkzTabVar + + :param limit: Display or not limits in tabular + + >>> P = Polynom_deg2([1,2,3]) + >>> print(P.tbl_variation()) + \\tkzTabVar{+/{}, -/{$\\frac{ 2 }{ 3 }$}, +/{}} + >>> print(P.tbl_variation(limits = True)) + \\tkzTabVar{+/{$+\\infty$}, -/{$\\frac{ 2 }{ 3 }$}, +/{$+\\infty$}} + + """ + beta = self.beta + if limits: + if self.a > 0: + return "\\tkzTabVar{+/{$+\\infty$}, -/{$" + str(beta) + "$}, +/{$+\\infty$}}" + else: + return "\\tkzTabVar{-/{$-\\infty$}, +/{$" + str(beta) + "$}, -/{$-\\infty$}}" + else: + if self.a > 0: + return "\\tkzTabVar{+/{}, -/{$" + str(beta) + "$}, +/{}}" + else: + return "\\tkzTabVar{-/{}, +/{$" + str(beta) + "$}, -/{}}" + + + + + + +if __name__ == '__main__': + # from .render import txt + # with Expression.tmp_render(txt): + # P = Polynom_deg2([2, 3, 4]) + # print(P) + + # print("Delta") + # for i in P.delta.simplify(): + # print(i) + + import doctest + doctest.testmod() + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/pymath/str2tokens.py b/pymath/str2tokens.py index ad340d7..7899a03 100644 --- a/pymath/str2tokens.py +++ b/pymath/str2tokens.py @@ -12,7 +12,7 @@ def str2tokens(exp): >>> str2tokens('2*3+4') [2, 3, '*', 4, '+'] >>> str2tokens('2x+4') - [2, < Polynom [0, 1]>, '*', 1, '+'] + [2, < Polynom [0, 1]>, '*', 4, '+'] """ in_tokens = str2in_tokens(exp) post_tokens = in2post_fix(in_tokens) diff --git a/test/test_expression.py b/test/test_expression.py index d8f6eb6..003e986 100644 --- a/test/test_expression.py +++ b/test/test_expression.py @@ -1,63 +1,77 @@ #!/usr/bin/env python # encoding: utf-8 +""" Testing Expression """ -import unittest - from pymath.expression import Expression from pymath.fraction import Fraction from pymath.generic import first_elem from pymath.render import txt, tex -class TestExpression(unittest.TestCase): - """Testing functions from pymath.expression""" - - def setup(self): - Expression.set_render(txt) - - def test_init_from_str(self): +def test_init_from_str(): exp = Expression("2 + 3") - self.assertEqual(exp.postfix_tokens, [2, 3, "+"]) + assert exp.postfix_tokens == [2, 3, "+"] - def test_init_from_exp(self): - pass +def test_init_from_exp(): + pass - def test_list(self): - exp = Expression([2, 3, "+"]) - self.assertEqual(exp.postfix_tokens, [2, 3, "+"]) +def test_init_list(): + exp = Expression([2, 3, "+"]) + assert exp.postfix_tokens == [2, 3, "+"] - def test_simplify_frac(self): - exp = Expression("1/2 - 4") - steps = ['\\frac{ 1 }{ 2 } - 4', \ - '\\frac{ 1 \\times 1 }{ 2 \\times 1 } - \\frac{ 4 \\times 2 }{ 1 \\times 2 }',\ - '\\frac{ 1 }{ 2 } - \\frac{ 8 }{ 2 }',\ - '\\frac{ 1 - 8 }{ 2 }',\ - '\\frac{ -7 }{ 2 }'] - self.assertEqual(steps, list(exp.simplify())) - Expression.set_render(txt) +def test_init_one_element_int_from_str(): + exp = Expression("1") - def test_add_exp(self): - e = Expression("12- 4") - f = Expression("4 + 1") - g = e + f - self.assertEqual(g.postfix_tokens, [12, 4, '-', 4, 1, "+", "+"]) +def test_init_one_element_int_from_list(): + exp = Expression([1]) - def test_mul_exp(self): - e = Expression("12- 4") - f = Expression("4 + 1") - g = e * f - self.assertEqual(g.postfix_tokens, [12, 4, '-', 4, 1, "+", "*"]) +#def test_init_one_element_str_from_str(): +# exp = Expression("x") +# +#def test_init_one_element_str_from_list(): +# exp = Expression(["x"]) - def test_neg_exp(self): - e = Expression("12- 4") - g = -e - self.assertEqual(g.postfix_tokens, [12, 4, '-', '-']) +def test_simplify_exp(): + exp = Expression("1 + 2 * 3") + simplified = exp.simplify() + ans = Expression("7") + assert ans == simplified +#def test_simplify_frac(): +# exp = Expression("1/2 - 4") +# simplified = exp.simplify() +# ans = Expression("-7/2") +# assert simplified == ans +# +#def test_explain_frac(): +# exp = Expression("1/2 - 4") +# simplified = exp.simplify() +# +# steps = ['\\frac{ 1 }{ 2 } - 4', \ +# '\\frac{ 1 \\times 1 }{ 2 \\times 1 } - \\frac{ 4 \\times 2 }{ 1 \\times 2 }',\ +# '\\frac{ 1 }{ 2 } - \\frac{ 8 }{ 2 }',\ +# '\\frac{ 1 - 8 }{ 2 }',\ +# '\\frac{ -7 }{ 2 }'] +# assert simplified.steps == list(exp.simplify()) -if __name__ == '__main__': - unittest.main() +def test_add_exp(): + e = Expression("12- 4") + f = Expression("4 + 1") + g = e + f + assert g.postfix_tokens == [12, 4, '-', 4, 1, "+", "+"] + +def test_mul_exp(): + e = Expression("12- 4") + f = Expression("4 + 1") + g = e * f + assert g.postfix_tokens == [12, 4, '-', 4, 1, "+", "*"] + +def test_neg_exp(): + e = Expression("12- 4") + g = -e + assert g.postfix_tokens == [12, 4, '-', '-'] # ----------------------------- diff --git a/test/test_fraction.py b/test/test_fraction.py index 3ed27f9..b631d9e 100644 --- a/test/test_fraction.py +++ b/test/test_fraction.py @@ -22,43 +22,21 @@ class TestFraction(unittest.TestCase): ans = [[Fraction(2, 3), 1, Fraction(17, 15), 0, 0, Fraction(4,3)], \ [Fraction(4,3), Fraction(5,3), Fraction(9,5), Fraction(2,3), Fraction(2,3), 2] \ ] - # TODO: Bug pour 1 + 1/-3 |sam. févr. 22 07:01:29 CET 2014 for (i, f1) in enumerate(self.listFrom): for (j, f2) in enumerate(self.listAgainst): res = f1 + f2 - - #print("-----------") - #print("f1 : ", f1) - #print("f2 : ", f2) - #print(res) - - # On est obligé de faire ça pour gérer le cas de 1+1 qui ne passe pas par la classe Fraction - if type(res) == list: - self.assertEqual(res[-1], ans[i][j]) - else: - self.assertEqual(res, ans[i][j]) + self.assertEqual(res, ans[i][j]) def test_sub(self): ans = [[0, Fraction(-1,3), Fraction(-7, 15), Fraction(2,3), Fraction(2,3), Fraction(-2,3)], \ [Fraction(2,3), Fraction(1,3), Fraction(1,5), Fraction(4,3), Fraction(4,3), 0] \ ] - # TODO: bug pour 1 - 1/-3 |sam. févr. 22 07:05:15 CET 2014 for (i, f1) in enumerate(self.listFrom): for (j, f2) in enumerate(self.listAgainst): res = f1 - f2 - - #print("-----------") - #print("f1 : ", f1) - #print("f2 : ", f2) - #print(res) - - # On est obligé de faire ça pour gérer le cas de 1-1 qui ne passe pas par la classe Fraction - if type(res) == list: - self.assertEqual(res[-1], ans[i][j]) - else: - self.assertEqual(res, ans[i][j]) + self.assertEqual(res, ans[i][j]) def test_neg(self): ans = [ Fraction(-1,3), \ @@ -70,10 +48,7 @@ class TestFraction(unittest.TestCase): ] for (j, f) in enumerate(self.listAgainst): res = -f - if type(res) == list: - self.assertEqual(res[-1], ans[j]) - else: - self.assertEqual(res, ans[j]) + self.assertEqual(res, ans[j]) def test_mul(self): ans = [[Fraction(1, 9), Fraction(2,9), Fraction(4, 15), Fraction(-1,9), Fraction(-1,9), Fraction(1,3)], \ @@ -83,17 +58,7 @@ class TestFraction(unittest.TestCase): for (i, f1) in enumerate(self.listFrom): for (j, f2) in enumerate(self.listAgainst): res = f1 * f2 - - #print("-----------") - #print("f1 : ", f1) - #print("f2 : ", f2) - #print(res) - - # On est obligé de faire ça pour gérer le cas de 1*1 qui ne passe pas par la classe Fraction - if type(res) == list: - self.assertEqual(res[-1], ans[i][j]) - else: - self.assertEqual(res, ans[i][j]) + self.assertEqual(res, ans[i][j]) def test_truediv(self): ans = [[1, Fraction(1,2), Fraction(5, 12), -1, -1, Fraction(1,3)], \ @@ -103,17 +68,7 @@ class TestFraction(unittest.TestCase): for (i, f1) in enumerate(self.listFrom): for (j, f2) in enumerate(self.listAgainst): res = f1 / f2 - - #print("-----------") - #print("f1 : ", f1) - #print("f2 : ", f2) - #print(res) - - # On est obligé de faire ça pour gérer le cas de 1/1 qui ne passe pas par la classe Fraction - if type(res) == list: - self.assertEqual(res[-1], ans[i][j]) - else: - self.assertEqual(res, ans[i][j]) + self.assertEqual(res, ans[i][j]) def test_lt(self): pass @@ -130,8 +85,6 @@ class TestFraction(unittest.TestCase): f = Fraction(2, 3) ans = "2 / 3" self.assertEqual(f.__txt__(), ans) - - if __name__ == '__main__': unittest.main() diff --git a/test/test_operator.py b/test/test_operator.py new file mode 100644 index 0000000..c89f9fd --- /dev/null +++ b/test/test_operator.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from pymath.operator import op + + +def test_mul_is_visible(): + assert op.mul.is_visible(2,3) == True + assert op.mul.is_visible(2,-3) == True + assert op.mul.is_visible(-2,3) == True + assert op.mul.is_visible('a',2) == True + assert op.mul.is_visible('(2a + 1)', 2) == True + assert op.mul.is_visible(2, '(-2)') == True + assert op.mul.is_visible(2, '2a') == True + assert op.mul.is_visible(2, '(-2a)') == True + assert op.mul.is_visible(2, '(-2abc)') == True + + assert op.mul.is_visible(2,'a') == False + assert op.mul.is_visible(2, '(2a + 1)') == False + assert op.mul.is_visible('(3x - 1)', '(2a + 1)') == False + assert op.mul.is_visible(2, '(-2x + 1)(3x + 2)') == False + + + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del + diff --git a/test/test_polynom.py b/test/test_polynom.py index 33ba472..79743a7 100644 --- a/test/test_polynom.py +++ b/test/test_polynom.py @@ -33,19 +33,19 @@ class TestPolynom(unittest.TestCase): def test_eval_base(self): p = Polynom([1, 2]) - self.assertEqual(p(3).simplified(), 7) + self.assertEqual(p(3), 7) def test_eval_const(self): p = Polynom([1]) - self.assertEqual(p(3).simplified(), 1) + self.assertEqual(p(3), 1) def test_eval_const_neg(self): p = Polynom([-1]) - self.assertEqual(p(3).simplified(), -1) + self.assertEqual(p(3), -1) def test_eval_poly(self): p = Polynom([1, 2]) - self.assertEqual(p("1+h").simplified(), Polynom([3,2], "h")) + self.assertEqual(p("h+1"), Polynom([3,2], "h")) #def test_print(self): # p = Polynom([1,2,3]) @@ -71,97 +71,104 @@ class TestPolynom(unittest.TestCase): p = Polynom([1,2,3]) #ans = [1, 2, "x", "*", "+", 3, "x", 2, "^", "*", "+"] ans = [3, 'x', 2, '^', '*', 2, 'x', '*', '+', 1, '+'] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) def test_postfix_monom(self): p = Polynom([0,2]) ans = [2, "x", "*"] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) def test_postfix_0_coef(self): p = Polynom([0,2,0,4]) #ans = [2, "x", "*", 4, "x", 3, "^", "*", "+"] ans = [4, 'x', 3, '^', '*', 2, 'x', '*', '+'] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) def test_postfix_1_coef(self): p = Polynom([0,1,1]) #ans = ["x", "x", 2, "^", "+"] ans = ["x", 2, "^", "x", "+"] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) def test_postfix_neg_coef(self): # TODO: Choix arbitraire (vis à vis des + et des -) il faudra faire en fonction de render |sam. juin 14 09:45:55 CEST 2014 p = Polynom([-1,-2,-3]) #ans = [-1, -2, "x", "*", "+", -3, "x", 2, "^", "*", "+"] ans = [3, 'x', 2, '^', '*', '-', 2, 'x', '*', '-', 1, '-'] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) def test_postfix_multi_coef(self): p = Polynom([1,[2, 3],4]) #ans = [1, 2, "x", "*", "+", 3, "x", "*", "+", 4, "x", 2, "^", "*", "+"] ans = [4, 'x', 2, '^', '*', 2, 'x', '*', '+', 3, 'x', '*', '+', 1, '+'] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) def test_postfix_arithm_coef(self): p = Polynom([1,Expression([2, 3, "+"]),4]) #ans = [1, 2, 3, "+", "x", "*", "+", 4, "x", 2, "^", "*", "+"] ans = [4, 'x', 2, '^', '*', 2, 3, '+', 'x', '*', '+', 1, '+'] - self.assertEqual(ans, p.postfix) + self.assertEqual(ans, p.postfix_tokens) - #def test_reduce_nilpo(self): - # p = Polynom([1, 2, 3]) - # self.assertEqual(p, p.reduce()[-1]) + def test_reduce_nilpo(self): + p = Polynom([1, 2, 3]) + self.assertEqual(p, p.reduce()) def test_reduce(self): p = Polynom([1, [2, 3], 4]) - self.assertEqual(str(p.reduce()[-1]),'4 x ^ 2 + 5 x + 1') + ans = '4 x^{ 2 } + 5 x + 1' + self.assertEqual(str(p.reduce()), ans) def test_add_int(self): p = Polynom([1, 2, 3]) - q = (p + 2)[-1] - self.assertEqual(str(q), '3 x ^ 2 + 2 x + 3') + q = p + 2 + ans = '3 x^{ 2 } + 2 x + 3' + self.assertEqual(str(q), ans) def test_add_frac(self): p = Polynom([1, 2, 3]) f = Fraction(1, 2) - q = (p + f)[-1] - #ans = repr(Polynom([Expression(Fraction(3, 2)), Expression(2), Expression(3)])) - self.assertEqual(str(q),'3 x ^ 2 + 2 x + 3 / 2') + q = p + f + ans = '3 x^{ 2 } + 2 x + \\frac{ 3 }{ 2 }' + self.assertEqual(str(q),ans) def test_add_poly(self): p = Polynom([1, 0, 3]) q = Polynom([0, 2, 3]) - r = (p + q)[-1] - self.assertEqual(str(r), '6 x ^ 2 + 2 x + 1') + r = p + q + ans = '6 x^{ 2 } + 2 x + 1' + self.assertEqual(str(r), ans) def test_sub_int(self): p = Polynom([1, 2, 3]) - q = (p - 2)[-1] - self.assertEqual(str(q), '3 x ^ 2 + 2 x - 1') + q = p - 2 + ans = '3 x^{ 2 } + 2 x - 1' + self.assertEqual(str(q),ans ) def test_sub_frac(self): p = Polynom([1, 2, 3]) f = Fraction(1, 2) - q = (p - f)[-1] - #ans = repr(Polynom([Expression(Fraction(3, 2)), Expression(2), Expression(3)])) - self.assertEqual(str(q),'3 x ^ 2 + 2 x + 1 / 2') + q = p - f + ans = '3 x^{ 2 } + 2 x + \\frac{ 1 }{ 2 }' + self.assertEqual(str(q), ans) def test_sub_poly(self): p = Polynom([1, 0, 2]) q = Polynom([0, 2, 3]) - r = (p - q)[-1] - self.assertEqual(str(r), '- x ^ 2 - 2 x + 1') + r = p - q + ans = '- x^{ 2 } - 2 x + 1' + self.assertEqual(str(r), ans) def test_pow_monome(self): p = Polynom([0,-2]) - r = (p**3)[-1] - self.assertEqual(str(r), '- 8 x ^ 3') + r = p**3 + ans = '- 8 x^{ 3 }' + self.assertEqual(str(r), ans) def test_pow2_monome(self): p = Polynom([0,-2]) - r = (p^3)[-1] - self.assertEqual(str(r), '- 8 x ^ 3') + r = p^3 + ans = '- 8 x^{ 3 }' + self.assertEqual(str(r), ans) diff --git a/test/test_polynomDeg2.py b/test/test_polynomDeg2.py new file mode 100644 index 0000000..e32a79a --- /dev/null +++ b/test/test_polynomDeg2.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# encoding: utf-8 + + +import unittest + +from pymath.polynomDeg2 import Polynom_deg2 + + + +class TestPolynomDeg2(unittest.TestCase): + """Testing functions from pymath.polynomDeg2""" + + pass + + +if __name__ == '__main__': + unittest.main() + + + + + + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del + diff --git a/test/test_render.py b/test/test_render.py index 0dee1dd..50394b5 100644 --- a/test/test_render.py +++ b/test/test_render.py @@ -6,6 +6,7 @@ import unittest from pymath.render import tex, txt,p2i from pymath.fraction import Fraction +from pymath.polynom import Polynom from pymath.operator import op @@ -22,41 +23,141 @@ class TestTexRender(unittest.TestCase): def test_type_render_fraction(self): self.assertEqual(tex([Fraction(1,2)]), "\\frac{ 1 }{ 2 }") + def test_type_render_poly(self): + P = Polynom([1,2,3]) + self.assertEqual(tex([P]), "3 x^{ 2 } + 2 x + 1") + + def test_add_interger(self): + exps = [ [2, 3, op.add ], + [2, -3, op.add ], + [-2, 3, op.add ], + ] + wanted_render = [ "2 + 3", + "2 - 3", + "-2 + 3", + ] + for (i,e) in enumerate(exps): + rend = tex(e) + self.assertEqual(rend, wanted_render[i]) + + def test_add_letter(self): + exps = [[2, "a", op.add ], + ["a", 3, op.add ], + [-2, "a", op.add ], + ["a", -2, op.add ], + ] + wanted_render = [ "2 + a", + "a + 3", + "-2 + a", + "a - 2", + ] + for (i,e) in enumerate(exps): + rend = tex(e) + self.assertEqual(rend, wanted_render[i]) + + def test_add_fraction(self): + exps = [[2, Fraction(1,2), op.add], + [Fraction(1,2), 3, op.add], + ] + wanted_render = [ "2 + \\frac{ 1 }{ 2 }", + "\\frac{ 1 }{ 2 } + 3", + ] + for (i,e) in enumerate(exps): + rend = tex(e) + self.assertEqual(rend, wanted_render[i]) + + def test_add_poly(self): + exps = [[2, Polynom([1,2,3]), op.mul], + [Polynom([1,2,3]), 2, op.mul], + [Polynom([1,2,3]), Polynom([4,5,6]), op.mul], + ] + wanted_render = [ "2 ( 3 x^{ 2 } + 2 x + 1 )", + "( 3 x^{ 2 } + 2 x + 1 ) \\times 2", + "( 3 x^{ 2 } + 2 x + 1 ) ( 6 x^{ 2 } + 5 x + 4 )", + ] + for (i,e) in enumerate(exps): + rend = tex(e) + self.assertEqual(rend, wanted_render[i]) + def test_mult_interger(self): - exps = [ [2, 3, op.get_op("*", 2)], [2, -3, op.get_op("*", 2)], [-2, 3, op.get_op("*", 2)]] - wanted_render = [ "2 \\times 3", "2 \\times ( -3 )", "-2 \\times 3"] + exps = [[2, 3, op.mul], + [2, -3, op.mul], + [-2, 3, op.mul], + ] + wanted_render = [ "2 \\times 3", + "2 \\times ( -3 )", + "-2 \\times 3", + ] for (i,e) in enumerate(exps): rend = tex(e) self.assertEqual(rend, wanted_render[i]) def test_mult_letter(self): - exps = [ [2, "a", op.get_op("*", 2)], ["a", 3, op.get_op("*", 2)], [-2, "a", op.get_op("*", 2)], ["a", -2, op.get_op("*", 2)]] + exps = [[2, "a", op.mul], + ["a", 3, op.mul], + [-2, "a", op.mul], + ["a", -2, op.mul], + ] wanted_render = [ "2 a", "a \\times 3", "-2 a", "a \\times ( -2 )"] for (i,e) in enumerate(exps): rend = tex(e) self.assertEqual(rend, wanted_render[i]) def test_mult_fraction(self): - exps = [ [2, Fraction(1,2), op.get_op("*", 2)], [Fraction(1,2), 3, op.get_op("*", 2)]] + exps = [ [2, Fraction(1,2), op.mul], [Fraction(1,2), 3, op.mul]] wanted_render = [ "2 \\times \\frac{ 1 }{ 2 }", "\\frac{ 1 }{ 2 } \\times 3"] for (i,e) in enumerate(exps): rend = tex(e) self.assertEqual(rend, wanted_render[i]) - def test_parentheses(self): - mul = op.get_op("*", 2) - add = op.get_op("+", 2) + def test_mult_poly(self): + exps = [[2, Polynom([1,2,3]), op.mul], + [Polynom([1,2,3]), 2, op.mul], + [Polynom([1,2,3]), Polynom([4,5,6]), op.mul], + ] + wanted_render = [ "2 ( 3 x^{ 2 } + 2 x + 1 )", + "( 3 x^{ 2 } + 2 x + 1 ) \\times 2", + "( 3 x^{ 2 } + 2 x + 1 ) ( 6 x^{ 2 } + 5 x + 4 )", + ] + for (i,e) in enumerate(exps): + rend = tex(e) + self.assertEqual(rend, wanted_render[i]) + + def test_parentheses_int(self): exps = [\ - [ 2, 3, add, 4, mul],\ - [ 2, 3, mul, 4, add],\ - [ 2, 3, 4, mul, add],\ - [ 2, 3, 4, add, add],\ + [ 2, 3, op.add, 4, op.mul],\ + [ 2, 3, op.mul, 4, op.add],\ + [ 2, 3, 4, op.mul, op.add],\ + [ 2, 3, 4, op.add, op.add],\ + [ 2, 3, 4, op.add, op.sub],\ ] wanted_render = [\ '( 2 + 3 ) \\times 4',\ '2 \\times 3 + 4',\ '2 + 3 \\times 4',\ '2 + 3 + 4',\ + '2 - ( 3 + 4 )',\ + ] + for (i,e) in enumerate(exps): + rend = tex(e) + self.assertEqual(rend, wanted_render[i]) + + def test_parentheses_poly(self): + P = Polynom([1,2,3]) + Q = Polynom([4,5,6]) + exps = [\ + [ 2, P, op.add],\ + [ 2, P, op.sub],\ + [ 2, P, P, op.mul, op.sub],\ + [ Q, P, op.add],\ + [ Q, P, op.sub],\ + ] + wanted_render = [\ + '2 + 3 x^{ 2 } + 2 x + 1' ,\ + '2 - ( 3 x^{ 2 } + 2 x + 1 )' ,\ + '2 - ( 3 x^{ 2 } + 2 x + 1 ) ( 3 x^{ 2 } + 2 x + 1 )' ,\ + '6 x^{ 2 } + 5 x + 4 + 3 x^{ 2 } + 2 x + 1' ,\ + '6 x^{ 2 } + 5 x + 4 - ( 3 x^{ 2 } + 2 x + 1 )' ,\ ] for (i,e) in enumerate(exps): rend = tex(e) @@ -81,27 +182,36 @@ class TesttxtRender(unittest.TestCase): self.assertEqual(txt([Fraction(1,2)]), "1 / 2") def test_mult_interger(self): - exps = [ [2, 3, op.get_op("*", 2)], \ - [2, -3, op.get_op("*", 2)], \ - [-2, 3, op.get_op("*", 2)]] + exps = [ [2, 3, op.mul], \ + [2, -3, op.mul], \ + [-2, 3, op.mul]] wanted_render = [ "2 * 3", "2 * ( -3 )", "-2 * 3"] for (i,e) in enumerate(exps): rend = txt(e) self.assertEqual(rend, wanted_render[i]) def test_mult_letter(self): - exps = [ [2, "a", op.get_op("*", 2)], \ - ["a", 3, op.get_op("*", 2)], \ - [-2, "a", op.get_op("*", 2)], \ - ["a", -2, op.get_op("*", 2)]] - wanted_render = [ "2 a", "a * 3", "-2 a", "a * ( -2 )"] + exps = [ [2, "a", op.mul], \ + ["a", 3, op.mul], \ + [-2, "a", op.mul], \ + ["a", -2, op.mul], + ["a", -2, op.mul, -2, op.mul], + ["a", -2, op.mul, "a", op.mul], + ] + wanted_render = [ "2 a", + "a * 3", + "-2 a", + "a * ( -2 )", + "a * ( -2 ) * ( -2 )", + "a * ( -2 ) a", + ] for (i,e) in enumerate(exps): rend = txt(e) self.assertEqual(rend, wanted_render[i]) def test_mult_fraction(self): - exps = [ [2, Fraction(1,2), op.get_op("*", 2)], \ - [Fraction(1,2), 3, op.get_op("*", 2)]] + exps = [ [2, Fraction(1,2), op.mul], \ + [Fraction(1,2), 3, op.mul]] wanted_render = [ "2 * 1 / 2", "1 / 2 * 3"] for (i,e) in enumerate(exps): rend = txt(e)