From f51ffbbe8b58444d25a5523b343b54812438b204 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Fri, 21 Dec 2018 11:26:37 +0100 Subject: [PATCH] Fix(Core): refact MO, clean import and fix renders Split MO in 2 categories Atoms and Molecules --- mapytex/calculus/core/MO/__init__.py | 3 +- mapytex/calculus/core/MO/atoms.py | 255 ++++++++++++++ mapytex/calculus/core/MO/fraction.py | 16 +- mapytex/calculus/core/MO/mo.py | 384 +++++++++------------- mapytex/calculus/core/MO/monomial.py | 59 +++- mapytex/calculus/core/MO/polynomial.py | 17 +- mapytex/calculus/core/compute/__init__.py | 4 +- mapytex/calculus/core/compute/add.py | 4 +- mapytex/calculus/core/compute/divide.py | 2 +- mapytex/calculus/core/compute/minus.py | 2 +- mapytex/calculus/core/compute/multiply.py | 2 +- mapytex/calculus/core/compute/power.py | 2 +- mapytex/calculus/core/renders/tree2tex.py | 108 +++--- mapytex/calculus/core/renders/tree2txt.py | 146 ++++---- mapytex/calculus/core/tree.py | 8 +- mapytex/calculus/core/typing/__init__.py | 9 +- mapytex/calculus/core/typing/add.py | 2 +- mapytex/calculus/core/typing/divide.py | 2 +- mapytex/calculus/core/typing/multiply.py | 2 +- mapytex/calculus/core/typing/power.py | 2 +- 20 files changed, 616 insertions(+), 413 deletions(-) create mode 100644 mapytex/calculus/core/MO/atoms.py diff --git a/mapytex/calculus/core/MO/__init__.py b/mapytex/calculus/core/MO/__init__.py index b7851c5..38ab8a6 100644 --- a/mapytex/calculus/core/MO/__init__.py +++ b/mapytex/calculus/core/MO/__init__.py @@ -10,7 +10,8 @@ MO: math objects """ -from .mo import * +from .mo import MO +from .atoms import MOnumber, MOstr, moify # ----------------------------- # Reglages pour 'vim' diff --git a/mapytex/calculus/core/MO/atoms.py b/mapytex/calculus/core/MO/atoms.py new file mode 100644 index 0000000..c222437 --- /dev/null +++ b/mapytex/calculus/core/MO/atoms.py @@ -0,0 +1,255 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +from decimal import Decimal +from functools import total_ordering +from .exceptions import MOError +from .mo import Atom +from ..coroutine import coroutine, STOOOP + + +__all__ = ["moify", "MOnumber", "MOstr"] + +@coroutine +def moify(target): + """ Coroutine which try to convert a parsed token into an MO + + :example: + >>> from ..str2 import list_sink + >>> list2molist = moify(list_sink) + >>> for i in [-2, "+", "x", "*", Decimal("3.3")]: + ... list2molist.send(i) + >>> list2molist.throw(STOOOP) + [, '+', , '*', ] + + """ + try: + target_ = target() + except TypeError: + target_ = target + + try: + while True: + tok = yield + try: + target_.send(MOnumber(tok)) + except MOError: + try: + target_.send(MOstr(tok)) + except MOError: + target_.send(tok) + + except STOOOP as err: + yield target_.throw(err) + +@total_ordering +class MOnumber(Atom): + + """ Base number math object (int or Decimal) + + :example: + >>> x = MOnumber(2) + >>> x + + >>> print(x) + 2 + >>> x.__txt__ + '2' + >>> x.__tex__ + '2' + """ + + def __init__(self, value): + """ Initiate a number MO + + :example: + >>> MOnumber(23) + + >>> MOnumber(-23) + + + As expected there will be trouble with float + + >>> MOnumber(23.3) + + + It will be better to use Decimal + + >>> MOnumber(Decimal("23.3")) + + >>> MOnumber(Decimal("-23.3")) + + + MOnumber initialisation is idempotent + + >>> a = MOnumber(23) + >>> MOnumber(a) + + + >>> MOnumber("a") + Traceback (most recent call last): + ... + mapytex.calculus.core.MO.exceptions.MOError: ('The value of an MOnumber need to be a int, a float or a Decimal', "(got )") + + Atoms specific property and methods + + >>> print(a) + 23 + >>> a.value + 23 + >>> a.__txt__ + '23' + >>> a.__tex__ + '23' + >>> a._signature + 'scalar' + """ + if isinstance(value, Atom) and isinstance(value.value, (int, Decimal, float)): + Atom.__init__(self, value.value) + elif isinstance(value, (int, Decimal)): + Atom.__init__(self, value) + elif isinstance(value, float): + Atom.__init__(self, Decimal(value)) + else: + raise MOError("The value of an MOnumber need to be a int, a float or a Decimal", + f"(got {type(value)})") + + self._signature = "scalar" + + @property + def __txt__(self): + """ Txt rendering + + :example: + >>> MOnumber(3).__txt__ + '3' + >>> MOnumber(-3).__txt__ + '- 3' + """ + if self.value >= 0: + return str(self.value) + + return f"- {abs(self.value)}" + + @property + def __tex__(self): + """ Tex rendering + + :example: + >>> MOnumber(3).__tex__ + '3' + >>> MOnumber(-3).__tex__ + '- 3' + """ + if self.value > 0: + return str(self.value) + + return f"- {abs(self.value)}" + + def __lt__(self, other): + """ < a MOnumber """ + try: + return self.value < other.value + except AttributeError: + return self.value < other + + +class MOstr(Atom): + + """ Unknown math object like x or n + + :example: + >>> x = MOstr('x') + >>> x + + >>> print(x) + x + >>> x.__txt__ + 'x' + >>> x.__tex__ + 'x' + + Polynoms properties + + >>> x.variable + 'x' + >>> x.coefficients + {1: } + >>> x.degree + 1 + """ + + def __init__(self, value): + """ Initiate a string MO + + >>> a = MOstr("x") + >>> a + + >>> b = MOstr(a) + >>> b + + + >>> a = MOstr("+") + Traceback (most recent call last): + ... + mapytex.calculus.core.MO.exceptions.MOError: An MOstr should be initiate with a alpha string, got + + >>> MOstr("ui") + Traceback (most recent call last): + ... + mapytex.calculus.core.MO.exceptions.MOError: An MOstr should be initiate with a single caracter string, got ui + >>> MOstr(2) + Traceback (most recent call last): + ... + mapytex.calculus.core.MO.exceptions.MOError: An MOstr should be initiate with a string - the unknown, got 2 + + """ + + if isinstance(value, Atom): + val = value.value + else: + val = value + + if not isinstance(val, str): + raise MOError(f"An MOstr should be initiate with a string - the unknown, got {val}") + if len(val) != 1: + raise MOError(f"An MOstr should be initiate with a single caracter string, got {val}") + if not val.isalpha(): + raise MOError(f"An MOstr should be initiate with a alpha string, got {val}") + + Atom.__init__(self, val) + + self.is_scalar = False + self._signature = "monome1" + + self._variable = val + + @property + def variable(self): + return self._variable + + @property + def coefficients(self): + """ Dictionnary of coefficients + + :example: + >>> p = MOstr("x") + >>> p.coefficients + {1: } + """ + + return {1: MOnumber(1)} + + @property + def degree(self): + return 1 + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/mapytex/calculus/core/MO/fraction.py b/mapytex/calculus/core/MO/fraction.py index d854c93..1f5d783 100644 --- a/mapytex/calculus/core/MO/fraction.py +++ b/mapytex/calculus/core/MO/fraction.py @@ -7,14 +7,16 @@ # Distributed under terms of the MIT license. from mapytex.calculus.core.tree import Tree -from .mo import MO +from .mo import Molecule, MO __all__ = ["MOFraction"] -class MOFraction(MO): +class MOFraction(Molecule): """ Fraction math object""" + MAINOP = "/" + def __init__(self, numerator, denominator, negative=False): """ Initiate the MOFraction @@ -28,6 +30,14 @@ class MOFraction(MO): >>> f = MOFraction(2, 3) >>> f + >>> print(f.__txt__) + 2 / 3 + >>> print(f.__tex__) + \\frac{2}{3} + >>> print(f) + / + > 2 + > 3 >>> f = MOFraction(2, 3, negative = True) >>> f @@ -42,7 +52,7 @@ class MOFraction(MO): tree = Tree("-", None, base_tree) else: tree = base_tree - MO.__init__(self, tree) + Molecule.__init__(self, tree) self._numerator = _numerator self._denominator = _denominator diff --git a/mapytex/calculus/core/MO/mo.py b/mapytex/calculus/core/MO/mo.py index c129bd5..1f330c3 100644 --- a/mapytex/calculus/core/MO/mo.py +++ b/mapytex/calculus/core/MO/mo.py @@ -6,61 +6,167 @@ # # Distributed under terms of the MIT license. -from decimal import Decimal +from abc import ABC, abstractmethod from .exceptions import MOError -from ..coroutine import coroutine, STOOOP from ..renders import tree2txt, tree2tex -from functools import total_ordering +__all__ = ["MO"] -__all__ = ["moify", "MO", "MOstr"] - -@coroutine -def moify(target): - """ Coroutine which try to convert everything into an MO """ - try: - target_ = target() - except TypeError: - target_ = target - - try: - while True: - tok = yield - try: - target_.send(MOnumber(tok)) - except MOError: - try: - target_.send(MOstr(tok)) - except MOError: - target_.send(tok) - - except STOOOP as err: - yield target_.throw(err) - - -class MO(object): +class MO(ABC): """MO for math object - This base class is representing int and Decimal. - - :attr value: sympy compatible version of the MO - :attr _tree: tree version of the MO - :attr _signature: Name to identify the MO in the API + It is an abstract class with wrap recognizable math objects. + + There is 2 types of MO: + - Atom which are compose of single value builtin. + - Molecule which are more complex objects organised in a tree. """ + MAINOP = None + + @classmethod + def factory(cls, value): + """ Factory to ensure that a value is a MO before using it + + Idempotent + + >>> MO.factory("x") + + >>> MO.factory(2) + + >>> MO.factory(2.3) + + >>> x = MO.factory("x") + >>> MO.factory(x) + + >>> from decimal import Decimal + >>> MO.factory(Decimal("2.3")) + + """ + if isinstance(value, MO): + return value + + return Atom.factory(value) + + + @abstractmethod + def content(self): + """ content of the mo """ + pass + + def __repr__(self): + return f"<{self.__class__.__name__} {self.__txt__}>" + + @abstractmethod + def __str__(self): + pass + + @abstractmethod + def __txt__(self): + pass + + @abstractmethod + def __tex__(self): + pass + + def __hash__(self): + try: + return self._tree.__hash__() + except AttributeError: + return self._value.__hash__() + + def __eq__(self, other): + """ == a MOnumber """ + try: + return self.content == other.content + except AttributeError: + return self.content == other + + @property + def signature(self): + """ Name of the mo in the API + + :example: + >>> from .atoms import MOnumber, MOstr + >>> MOnumber(3).signature + 'scalar' + >>> MOstr("x").signature + 'monome1' + """ + return self._signature + +class Atom(MO): + + """ Base Math Object with only one component. + + It is a wrapping of int, Décimal and str builtin python object + + Its wrapping builtin can be access throw .value property + """ + + MAINOP = None + + @classmethod + def factory(cls, value): + """ Build the appropriate atom from the value + """ + for sub in cls.__subclasses__(): + try: + return sub(value) + except MOError: + pass + raise MOError(f"Can't build an atom from {type(value)}") + + def __init__(self, value): + """ Initiate an atom MO + """ + try: + # if the value is already an atom + self._value = value.value + except AttributeError: + self._value = value + + self.is_scalar = True + self._signature = None + + @property + def value(self): + return self._value + + @property + def content(self): + return self.value + + def __str__(self): + return str(self.value) + + @property + def __txt__(self): + return str(self.value) + + @property + def __tex__(self): + return str(self.value) + + +class Molecule(MO): + + """ Complex Math Object composed of multiple components + + It is a wrapping of tree + + Its wrapping tree can be access throw .tree property + """ + + MAINOP = None + def __init__(self, value): """ Initiate the MO It should be idempotent. - >>> a = MO(3) - >>> a - - >>> a = MO(a) - >>> a - """ try: self._tree = value._tree @@ -70,205 +176,27 @@ class MO(object): self.is_scalar = True self._signature = None - @classmethod - def factory(cls, value): - """ Factory to ensure that a value is a MO before using it + @property + def tree(self): + return self._tree - Idempotent?? - - >>> MO.factory("x") - - >>> MO.factory(2) - - >>> MO.factory(2.3) - - >>> MO.factory(Decimal("2.3")) - - >>> x = MO.factory("x") - >>> MO.factory(x) - - """ - if isinstance(value, str): - return MOstr(value) - elif isinstance(value, int) \ - or isinstance(value, Decimal) \ - or isinstance(value, float): - return MOnumber(value) - elif isinstance(value, MO): - return value - - raise MOError("Can't convert it into a MO." - f"Need str, int, Decimal, float or MO, got {value}") - - def __repr__(self): - return f"<{self.__class__.__name__} {self.__txt__}>" + @property + def content(self): + return self._tree def __str__(self): - return str(self._tree) + # TODO: à changer pour utiliser .__txt__ |ven. déc. 21 08:30:33 CET 2018 + return str(self.tree) @property def __txt__(self): - try: - return tree2txt(self._tree) - except AttributeError: - return str(self._tree) + return tree2txt(self._tree) @property def __tex__(self): - try: - return tree2tex(self._tree) - except AttributeError: - return str(self._tree) - - def __hash__(self): - return self._tree.__hash__() - - def __eq__(self, other): - """ == a MOnumber """ - try: - return self._tree == other._tree - except AttributeError: - return self._tree == other - - @property - def signature(self): - """ Name of the mo in the API - - :example: - >>> MOnumber(3).signature - 'scalar' - >>> MOstr("x").signature - 'monome1' - """ - return self._signature - -@total_ordering -class MOnumber(MO): - - """ Base number math object (int or Decimal) """ - - def __init__(self, value): - """ Initiate a number MO - - >>> MOnumber(23) - - >>> MOnumber(-23) - - >>> MOnumber(23.3) - - >>> MOnumber(Decimal("23.3")) - - >>> MOnumber(Decimal("-23.3")) - - >>> a = MOnumber(23) - >>> MOnumber(a) - - >>> MOnumber("a") - Traceback (most recent call last): - ... - mapytex.calculus.core.MO.exceptions.MOError: The value of an MOnumber need to be a int, a float or a Decimal - - """ - try: - val = value._tree - except AttributeError: - val = value - - if isinstance(val, (int, Decimal)): - MO.__init__(self, val) - elif isinstance(val, float): - MO.__init__(self, Decimal(val)) - else: - raise MOError("The value of an MOnumber need to be a int, a float or a Decimal") - - self.value = self._tree - self._signature = "scalar" - - @property - def __txt__(self): - if self.value >= 0: - return str(self.value) - - return f"- {abs(self.value)}" - - @property - def __tex__(self): - if self.value > 0: - return str(self.value) - - return f"- {abs(self.value)}" - - def __lt__(self, other): - """ < a MOnumber """ - try: - return self.value < other.value - except AttributeError: - return self.value < other + return tree2tex(self._tree) -class MOstr(MO): - - """ Unknown math object like x or n""" - - def __init__(self, value): - """ Initiate a string MO - - >>> a = MOstr("x") - >>> a - - >>> b = MOstr(a) - >>> b - - - >>> a = MOstr("+") - Traceback (most recent call last): - ... - mapytex.calculus.core.MO.exceptions.MOError: An MOstr should be initiate with a alpha string, got + - >>> MOstr("ui") - Traceback (most recent call last): - ... - mapytex.calculus.core.MO.exceptions.MOError: An MOstr should be initiate with a single caracter string, got ui - >>> MOstr(2) - Traceback (most recent call last): - ... - mapytex.calculus.core.MO.exceptions.MOError: An MOstr should be initiate with a string - the unknown, got 2 - - """ - try: - val = value._tree - except AttributeError: - val = value - - if not isinstance(val, str): - raise MOError(f"An MOstr should be initiate with a string - the unknown, got {val}") - if len(val) != 1: - raise MOError(f"An MOstr should be initiate with a single caracter string, got {val}") - if not val.isalpha(): - raise MOError(f"An MOstr should be initiate with a alpha string, got {val}") - - MO.__init__(self, val) - self.is_scalar = False - self._variable = val - self._signature = "monome1" - - @property - def variable(self): - return self._variable - - @property - def coefficients(self): - """ Dictionnary of coefficients - - :example: - >>> p = MOstr("x") - >>> p.coefficients - {1: } - """ - - return {1: MOnumber(1)} - @property - def degree(self): - return 1 # ----------------------------- diff --git a/mapytex/calculus/core/MO/monomial.py b/mapytex/calculus/core/MO/monomial.py index 78a4c6e..192c4f3 100644 --- a/mapytex/calculus/core/MO/monomial.py +++ b/mapytex/calculus/core/MO/monomial.py @@ -7,24 +7,35 @@ # Distributed under terms of the MIT license. from mapytex.calculus.core.tree import Tree -from .mo import MO, MOnumber, MOstr +from .mo import Molecule +from . import MO, MOnumber, MOstr from .exceptions import MOError -from ..renders import tree2txt, tree2tex __all__ = ["MOMonomial"] -class MOstrPower(MO): +class MOstrPower(Molecule): """ Power of a MOstr """ + MAINOP = "^" + def __init__(self, variable, power): """ Initiate a MOstrPower :param variable: variable of the monomial (a MOstr or later a MOSqrt) :param power: non negative interger (MOnumber type) - >>> MOstrPower("x", 2) + >>> s = MOstrPower("x", 2) + >>> s + >>> print(s) + ^ + > x + > 2 + >>> print(s.__txt__) + x^2 + >>> print(s.__tex__) + x^{2} >>> MOstrPower(3, 1) Traceback (most recent call last): ... @@ -55,7 +66,7 @@ class MOstrPower(MO): _power = MO.factory(power) if power <= 1: raise MOError("The power of a MOstrPower should be greater than 1") - elif not isinstance(_power._tree, int): + elif not isinstance(_power.content, int): raise MOError("The power of a monomial should be a integer") self._power = _power @@ -64,8 +75,7 @@ class MOstrPower(MO): self._power, ) - MO.__init__(self, _tree) - + Molecule.__init__(self, _tree) @property def coefficients(self): @@ -105,10 +115,12 @@ class MOstrPower(MO): """ return f"monome{self.power}" -class MOMonomial(MO): +class MOMonomial(Molecule): """ Monomial math object""" + MAINOP = "*" + def __init__(self, coefficient, variable, power=1): """ Initiate the MOMonomial @@ -117,19 +129,37 @@ class MOMonomial(MO): :param power: degree of the monomial >>> x = MOstr('x') - >>> MOMonomial(4, x) + >>> m = MOMonomial(4, x) + >>> m + >>> print(m) + * + > 4 + > x + >>> print(m.__txt__) + 4x + >>> print(m.__tex__) + 4x >>> x = MOstrPower('x', 2) >>> MOMonomial(4, x) - >>> MOMonomial(4, 'x') + >>> m = MOMonomial(4, 'x') + >>> m + >>> print(m) + * + > 4 + > x + >>> print(m.__txt__) + 4x + >>> print(m.__tex__) + 4x >>> MOMonomial(4, 'x', 1) >>> MOMonomial(4, 'x', 2) - >>> x = MOstrPower('x', 2) - >>> MOMonomial(4, x, 3) + >>> x2 = MOstrPower('x', 2) + >>> MOMonomial(4, x2, 3) >>> MOMonomial(0, x) Traceback (most recent call last): @@ -139,6 +169,8 @@ class MOMonomial(MO): _coefficient = MO.factory(coefficient) if coefficient == 0: raise MOError("The coefficient of a monomial should not be 0") + elif coefficient == 1: + raise MOError("The coefficient of a monomial should not be 1, it is a MOstrPower or MOstr") self._coefficient = _coefficient _variable = MO.factory(variable) @@ -147,7 +179,6 @@ class MOMonomial(MO): _variable = _variable.variable elif isinstance(_variable, MOstr): _power = MO.factory(power) - _variable = variable else: raise MOError(f"variable need to be a MOstrPower or a MOstr. Got {type(variable)}.") @@ -163,7 +194,7 @@ class MOMonomial(MO): _tree = Tree("*", self._coefficient, self.strpower) - MO.__init__(self, _tree) + Molecule.__init__(self, _tree) @property def coefficient(self): diff --git a/mapytex/calculus/core/MO/polynomial.py b/mapytex/calculus/core/MO/polynomial.py index e454c6a..5b1bcd1 100644 --- a/mapytex/calculus/core/MO/polynomial.py +++ b/mapytex/calculus/core/MO/polynomial.py @@ -7,17 +7,19 @@ # Distributed under terms of the MIT license. from mapytex.calculus.core.tree import Tree -from .mo import MO, MOnumber, MOstr +from . import MO, MOstr +from .mo import Molecule from .exceptions import MOError -from ..renders import tree2txt, tree2tex -from .monomial import MOMonomial +from .monomial import MOMonomial, MOstrPower __all__ = ["MOpolynomial"] -class MOpolynomial(MO): +class MOpolynomial(Molecule): """ MO polynomial""" + MAINOP = "+" + def __init__(self, variable, coefs): """ Initiate a MOpolynomial @@ -36,7 +38,6 @@ class MOpolynomial(MO): >>> MOpolynomial('x', {0: 1, 3: 1}) - """ _variable = MO.factory(variable) if not isinstance(_variable, MOstr): @@ -57,13 +58,17 @@ class MOpolynomial(MO): for deg, coef in self._coefs.items(): if deg == 0: monomials[deg] = coef + elif deg == 1 and coef == 1: + monomials[deg] = MOstr(self._variable) + elif coef == 1: + monomials[deg] = MOstrPower(self._variable, deg) else: monomials[deg] = MOMonomial(coef, self._variable, deg) self._monomials = monomials tree = Tree.from_list("+", list(self._monomials.values())[::-1]) - MO.__init__(self, tree) + Molecule.__init__(self, tree) @property def variable(self): diff --git a/mapytex/calculus/core/compute/__init__.py b/mapytex/calculus/core/compute/__init__.py index 4abac57..69b0a54 100644 --- a/mapytex/calculus/core/compute/__init__.py +++ b/mapytex/calculus/core/compute/__init__.py @@ -17,7 +17,7 @@ from .minus import minus from .multiply import multiply from .power import power -from ..MO.mo import MOnumber, MOstr +from ..MO import MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial @@ -41,7 +41,7 @@ def compute(node, left_v, right_v): :example: - >>> from ..MO.mo import MOnumber + >>> from ..MO import MOnumber >>> compute("+", MOnumber(1), MOnumber(2)) >>> compute("-", None, MOnumber(2)) diff --git a/mapytex/calculus/core/compute/add.py b/mapytex/calculus/core/compute/add.py index 5e3c9a3..2d81f17 100644 --- a/mapytex/calculus/core/compute/add.py +++ b/mapytex/calculus/core/compute/add.py @@ -10,14 +10,12 @@ Adding MO """ -from functools import wraps from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial -from .exceptions import AddError from .arithmetic import lcm from .filters import special_case diff --git a/mapytex/calculus/core/compute/divide.py b/mapytex/calculus/core/compute/divide.py index b02c683..bc12a78 100644 --- a/mapytex/calculus/core/compute/divide.py +++ b/mapytex/calculus/core/compute/divide.py @@ -13,7 +13,7 @@ Divide MO from decimal import Decimal from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber +from ..MO import MO, MOnumber from ..MO.fraction import MOFraction from .exceptions import DivideError from .filters import special_case diff --git a/mapytex/calculus/core/compute/minus.py b/mapytex/calculus/core/compute/minus.py index 27abbf6..c7c3a2d 100644 --- a/mapytex/calculus/core/compute/minus.py +++ b/mapytex/calculus/core/compute/minus.py @@ -12,7 +12,7 @@ Minus MO: take the opposit from multipledispatch import Dispatcher from .exceptions import MinusError -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial diff --git a/mapytex/calculus/core/compute/multiply.py b/mapytex/calculus/core/compute/multiply.py index b5d02b1..f73d941 100644 --- a/mapytex/calculus/core/compute/multiply.py +++ b/mapytex/calculus/core/compute/multiply.py @@ -12,7 +12,7 @@ Multiply MO from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial diff --git a/mapytex/calculus/core/compute/power.py b/mapytex/calculus/core/compute/power.py index fdeedc0..e3876c2 100644 --- a/mapytex/calculus/core/compute/power.py +++ b/mapytex/calculus/core/compute/power.py @@ -12,7 +12,7 @@ Power with MO from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial diff --git a/mapytex/calculus/core/renders/tree2tex.py b/mapytex/calculus/core/renders/tree2tex.py index 8dbdbd8..8643303 100644 --- a/mapytex/calculus/core/renders/tree2tex.py +++ b/mapytex/calculus/core/renders/tree2tex.py @@ -13,19 +13,19 @@ __all__ = ['tree2tex'] def plus2tex(left, right): r""" + rendering - >>> from ..MO import mo - >>> plus2tex(mo.MOnumber(2), mo.MOnumber(3)) + >>> from ..MO import MO + >>> plus2tex(MO.factory(2), MO.factory(3)) '2 + 3' >>> from ..tree import Tree >>> t = Tree.from_str("1+2") - >>> plus2tex(t, mo.MOnumber(3)) + >>> plus2tex(t, MO.factory(3)) '1 + 2 + 3' - >>> plus2tex(t, mo.MOnumber(-3)) + >>> plus2tex(t, MO.factory(-3)) '1 + 2 - 3' - >>> plus2tex(mo.MOnumber(-3), t) + >>> plus2tex(MO.factory(-3), t) '- 3 + 1 + 2' >>> t = Tree.from_str("-2*3") - >>> plus2tex(mo.MOnumber(3), t) + >>> plus2tex(MO.factory(3), t) '3 - 2 \\times 3' """ display_plus = True @@ -54,8 +54,8 @@ def plus2tex(left, right): def minus2tex(left, right): r""" - rendering - >>> from ..MO import mo - >>> minus2tex(None, mo.MO(3)) + >>> from ..MO import MO + >>> minus2tex(None, MO.factory(3)) '- 3' >>> from ..tree import Tree >>> t = Tree.from_str("1+2") @@ -79,54 +79,30 @@ def minus2tex(left, right): def mul2tex(left, right): r""" * rendering - >>> from ..MO import mo - >>> mul2tex(mo.MO(2), mo.MO(3)) + >>> from ..MO import MO + >>> mul2tex(MO.factory(2), MO.factory(3)) '2 \\times 3' >>> from ..tree import Tree >>> t = Tree.from_str("1*2") - >>> mul2tex(t, mo.MO(3)) + >>> mul2tex(t, MO.factory(3)) '1 \\times 2 \\times 3' >>> t = Tree.from_str("1+2") - >>> mul2tex(t, mo.MO(3)) + >>> mul2tex(t, MO.factory(3)) '(1 + 2) \\times 3' - >>> mul2tex(mo.MO(3), t) + >>> mul2tex(MO.factory(3), t) '3(1 + 2)' - >>> a = mo.MOstr('x') - >>> mul2tex(mo.MO(3), a) + >>> a = MO.factory('x') + >>> mul2tex(MO.factory(3), a) '3x' >>> mul2tex(a, a) 'x \\times x' """ - display_time = True - try: - left_need_parenthesis = False - if OPERATORS[left.node]["precedence"] < OPERATORS['*']["precedence"]: - left_need_parenthesis = True - except AttributeError: - left_ = left.__tex__ - else: - if left_need_parenthesis: - left_ = f"({tree2tex(left)})" - else: - left_ = tree2tex(left) + left_ = render_with_parenthesis(left, "*") + right_ = render_with_parenthesis(right, "*") - try: - right_need_parenthesis = False - if OPERATORS[right.node]["precedence"] < OPERATORS['*']["precedence"]: - right_need_parenthesis = True - except AttributeError: - try: - right_ = right.__tex__ - except AttributeError: - right_ = right - else: - if right_need_parenthesis: - display_time = False - right_ = f"({tree2tex(right)})" - else: - right_ = tree2tex(right) - finally: - if right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal()): + display_time = True + if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or \ + right_[0] == '(': display_time = False if display_time: @@ -136,18 +112,18 @@ def mul2tex(left, right): def div2tex(left, right): r""" / rendering - >>> from ..MO import mo - >>> div2tex(mo.MO(2), mo.MO(3)) + >>> from ..MO import MO + >>> div2tex(MO.factory(2), MO.factory(3)) '\\frac{2}{3}' >>> from ..tree import Tree >>> t = Tree.from_str("1/2") - >>> div2tex(t, mo.MO(3)) + >>> div2tex(t, MO.factory(3)) '\\frac{\\frac{1}{2}}{3}' >>> t = Tree.from_str("1+2") - >>> div2tex(t, mo.MO(3)) + >>> div2tex(t, MO.factory(3)) '\\frac{1 + 2}{3}' >>> t = Tree.from_str("1*2") - >>> div2tex(mo.MO(3), t) + >>> div2tex(MO.factory(3), t) '\\frac{3}{1 \\times 2}' """ try: @@ -164,21 +140,21 @@ def div2tex(left, right): def pow2tex(left, right): r""" ^ rendering - >>> from ..MO import mo - >>> pow2tex(mo.MO(2), mo.MO(3)) + >>> from ..MO import MO + >>> pow2tex(MO.factory(2), MO.factory(3)) '2^{3}' >>> from ..tree import Tree >>> t = Tree.from_str("1^2") - >>> pow2tex(t, mo.MO(3)) + >>> pow2tex(t, MO.factory(3)) '1^{2}^{3}' >>> t = Tree.from_str("1^2") - >>> pow2tex(mo.MO(3), t) + >>> pow2tex(MO.factory(3), t) '3^{1^{2}}' >>> t = Tree.from_str("1+2") - >>> pow2tex(t, mo.MO(3)) + >>> pow2tex(t, MO.factory(3)) '(1 + 2)^{3}' >>> t = Tree.from_str("1*2") - >>> pow2tex(mo.MO(3), t) + >>> pow2tex(MO.factory(3), t) '3^{1 \\times 2}' """ try: @@ -202,6 +178,28 @@ def pow2tex(left, right): return f"{left_}^{{{right_}}}" + +def render_with_parenthesis(subtree, operator): + subtree_need_parenthesis = False + try: + subtree.node + except AttributeError: + try: + if OPERATORS[subtree.MAINOP]["precedence"] < OPERATORS[operator]["precedence"]: + subtree_need_parenthesis = True + except (AttributeError, KeyError): + pass + subtree_ = subtree.__txt__ + else: + if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]: + subtree_need_parenthesis = True + subtree_ = tree2tex(subtree) + + if subtree_need_parenthesis: + return f"({subtree_})" + return subtree_ + + OPERATOR2TEX = { "+": plus2tex, "-": minus2tex, diff --git a/mapytex/calculus/core/renders/tree2txt.py b/mapytex/calculus/core/renders/tree2txt.py index 3b9da70..dd271d0 100644 --- a/mapytex/calculus/core/renders/tree2txt.py +++ b/mapytex/calculus/core/renders/tree2txt.py @@ -6,45 +6,38 @@ # # Distributed under terms of the MIT license. -from mapytex.calculus.core.operator import OPERATORS +from ..operator import OPERATORS __all__ = ['tree2txt'] def plus2txt(left, right): """ + rendering - >>> from ..MO import mo - >>> plus2txt(mo.MOnumber(2), mo.MOnumber(3)) + >>> from ..MO import MO + >>> plus2txt(MO.factory(2), MO.factory(3)) '2 + 3' >>> from ..tree import Tree >>> t = Tree.from_str("1+2") - >>> plus2txt(t, mo.MOnumber(3)) + >>> plus2txt(t, MO.factory(3)) '1 + 2 + 3' - >>> plus2txt(t, mo.MOnumber(-3)) + >>> plus2txt(t, MO.factory(-3)) '1 + 2 - 3' - >>> plus2txt(mo.MOnumber(-3), t) + >>> plus2txt(MO.factory(-3), t) '- 3 + 1 + 2' >>> t = Tree.from_str("-2*3") - >>> plus2txt(mo.MOnumber(3), t) + >>> plus2txt(MO.factory(3), t) '3 - 2 * 3' """ - display_plus = True - try: - left.node - except AttributeError: - left_ = left.__txt__ - else: - left_ = tree2txt(left) + left_ = render_with_parenthesis(left, "+") try: - right.node - except AttributeError: - right_ = right.__txt__ - else: - right_ = tree2txt(right) - finally: - if right_.startswith("-"): - display_plus = False + right_ = render_with_parenthesis(right, "+") + except ValueError: + raise TypeError(f"right -> {type(right)} {right}") + + display_plus = True + if right_.startswith("-"): + display_plus = False if display_plus: return f"{left_} + {right_}" @@ -54,8 +47,8 @@ def plus2txt(left, right): def minus2txt(left, right): """ - rendering - >>> from ..MO import mo - >>> minus2txt(None, mo.MO(3)) + >>> from ..MO import MO + >>> minus2txt(None, MO.factory(3)) '- 3' >>> from ..tree import Tree >>> t = Tree.from_str("1+2") @@ -79,55 +72,32 @@ def minus2txt(left, right): def mul2txt(left, right): """ * rendering - >>> from ..MO import mo - >>> mul2txt(mo.MO(2), mo.MO(3)) + >>> from ..MO import MO + >>> mul2txt(MO.factory(2), MO.factory(3)) '2 * 3' >>> from ..tree import Tree >>> t = Tree.from_str("1*2") - >>> mul2txt(t, mo.MO(3)) + >>> mul2txt(t, MO.factory(3)) '1 * 2 * 3' >>> t = Tree.from_str("1+2") - >>> mul2txt(t, mo.MO(3)) + >>> mul2txt(t, MO.factory(3)) '(1 + 2) * 3' - >>> mul2txt(mo.MO(3), t) + >>> mul2txt(MO.factory(3), t) '3(1 + 2)' - >>> a = mo.MOstr('x') - >>> mul2txt(mo.MO(3), a) + >>> a = MO.factory('x') + >>> mul2txt(MO.factory(3), a) '3x' >>> mul2txt(a, a) 'x * x' """ display_time = True - try: - left_need_parenthesis = False - if OPERATORS[left.node]["precedence"] < OPERATORS['*']["precedence"]: - left_need_parenthesis = True - except AttributeError: - left_ = left.__txt__ - else: - if left_need_parenthesis: - left_ = f"({tree2txt(left)})" - else: - left_ = tree2txt(left) - try: - right_need_parenthesis = False - if OPERATORS[right.node]["precedence"] < OPERATORS['*']["precedence"]: - right_need_parenthesis = True - except AttributeError: - try: - right_ = right.__txt__ - except AttributeError: - right_ = right - else: - if right_need_parenthesis: - display_time = False - right_ = f"({tree2txt(right)})" - else: - right_ = tree2txt(right) - finally: - if right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal()): - display_time = False + left_ = render_with_parenthesis(left, "*") + right_ = render_with_parenthesis(right, "*") + + if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or \ + right_[0] == '(': + display_time = False if display_time: return f"{left_} * {right_}" @@ -137,18 +107,18 @@ def mul2txt(left, right): def div2txt(left, right): """ / rendering - >>> from ..MO import mo - >>> div2txt(mo.MO(2), mo.MO(3)) + >>> from ..MO import MO + >>> div2txt(MO.factory(2), MO.factory(3)) '2 / 3' >>> from ..tree import Tree >>> t = Tree.from_str("1/2") - >>> div2txt(t, mo.MO(3)) + >>> div2txt(t, MO.factory(3)) '1 / 2 / 3' >>> t = Tree.from_str("1+2") - >>> div2txt(t, mo.MO(3)) + >>> div2txt(t, MO.factory(3)) '(1 + 2) / 3' >>> t = Tree.from_str("1*2") - >>> div2txt(mo.MO(3), t) + >>> div2txt(MO.factory(3), t) '3 / (1 * 2)' """ try: @@ -179,31 +149,22 @@ def div2txt(left, right): def pow2txt(left, right): """ ^ rendering - >>> from ..MO import mo - >>> pow2txt(mo.MO(2), mo.MO(3)) + >>> from ..MO import MO + >>> pow2txt(MO.factory(2), MO.factory(3)) '2^3' >>> from ..tree import Tree >>> t = Tree.from_str("1^2") - >>> pow2txt(t, mo.MO(3)) + >>> pow2txt(t, MO.factory(3)) '1^2^3' >>> t = Tree.from_str("1+2") - >>> pow2txt(t, mo.MO(3)) + >>> pow2txt(t, MO.factory(3)) '(1 + 2)^3' >>> t = Tree.from_str("1*2") - >>> pow2txt(mo.MO(3), t) + >>> pow2txt(MO.factory(3), t) '3^(1 * 2)' """ - try: - left_need_parenthesis = False - if OPERATORS[left.node]["precedence"] < OPERATORS['^']["precedence"]: - left_need_parenthesis = True - except AttributeError: - left_ = left.__txt__ - else: - if left_need_parenthesis: - left_ = f"({tree2txt(left)})" - else: - left_ = tree2txt(left) + left_ = render_with_parenthesis(left, "^") + try: right_need_parenthesis = False if OPERATORS[right.node]["precedence"] < OPERATORS['^']["precedence"]: @@ -218,6 +179,26 @@ def pow2txt(left, right): return f"{left_}^{right_}" +def render_with_parenthesis(subtree, operator): + subtree_need_parenthesis = False + try: + subtree.node + except AttributeError: + try: + if OPERATORS[subtree.MAINOP]["precedence"] < OPERATORS[operator]["precedence"]: + subtree_need_parenthesis = True + except (AttributeError, KeyError): + pass + subtree_ = subtree.__txt__ + else: + if OPERATORS[subtree.node]["precedence"] < OPERATORS[operator]["precedence"]: + subtree_need_parenthesis = True + subtree_ = tree2txt(subtree) + + if subtree_need_parenthesis: + return f"({subtree_})" + return subtree_ + OPERATOR2TXT = { "+": plus2txt, "-": minus2txt, @@ -240,6 +221,9 @@ def tree2txt(tree): >>> tree2txt(t) '2 + 3 * 4' """ + from ..tree import Tree + if not isinstance(tree, Tree): + raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})") return OPERATOR2TXT[tree.node](tree.left_value, tree.right_value) diff --git a/mapytex/calculus/core/tree.py b/mapytex/calculus/core/tree.py index ba82ea9..e72f489 100644 --- a/mapytex/calculus/core/tree.py +++ b/mapytex/calculus/core/tree.py @@ -412,8 +412,8 @@ class Tree(): >>> t = Tree.from_str("3+4+5*2") >>> [l for l in t.get_leafs()] [, , , ] - >>> {type(l) for l in t.get_leafs()} - {} + >>> {type(l).__name__ for l in t.get_leafs()} + {'MOnumber'} """ try: yield from self.left_value.get_leafs(callback) @@ -1377,8 +1377,8 @@ class AssocialTree(Tree): >>> t = AssocialTree.from_str("3+4+5*2") >>> [l for l in t.get_leafs(str)] ['3', '4', '*\\n > 5\\n > 2'] - >>> [ l for l in t.get_leafs(type) ] - [, , ] + >>> [ l for l in t.get_leafs(lambda x:type(x).__name__) ] + ['MOnumber', 'MOnumber', 'AssocialTree'] """ try: if self.left_value.node == self.node: diff --git a/mapytex/calculus/core/typing/__init__.py b/mapytex/calculus/core/typing/__init__.py index 638d671..057577b 100644 --- a/mapytex/calculus/core/typing/__init__.py +++ b/mapytex/calculus/core/typing/__init__.py @@ -17,7 +17,7 @@ from .multiply import multiply from .divide import divide from .power import power -from ..MO.mo import MOnumber, MOstr +from ..MO import MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial @@ -40,19 +40,12 @@ def typing(node, left_v, right_v,\ """ Typing a try base on his root node - :example: - - >>> from ..MO.mo import MOnumber """ try: operation = OPERATIONS[node] except KeyError: raise NotImplementedError(f"Unknown operation ({node}) in typing") return operation(left_v, right_v) - # try: - # return operation(left_v, right_v) - # except NotImplementedError: - # raise TypingError(f"Can't create new MO with {node}, {type(left_v)} and {type(right_v)}") def typing_capacities(node): """ Test an operation through all MOs diff --git a/mapytex/calculus/core/typing/add.py b/mapytex/calculus/core/typing/add.py index d76b5a8..57ca763 100644 --- a/mapytex/calculus/core/typing/add.py +++ b/mapytex/calculus/core/typing/add.py @@ -12,7 +12,7 @@ Add MO with typing from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.monomial import MOstrPower, MOMonomial from ..MO.polynomial import MOpolynomial from ..MO.fraction import MOFraction diff --git a/mapytex/calculus/core/typing/divide.py b/mapytex/calculus/core/typing/divide.py index ad1b191..8175eba 100644 --- a/mapytex/calculus/core/typing/divide.py +++ b/mapytex/calculus/core/typing/divide.py @@ -11,7 +11,7 @@ Typing trees with a divide root """ from multipledispatch import Dispatcher -from ..MO.mo import MO, MOnumber +from ..MO import MO, MOnumber from ..MO.fraction import MOFraction divide_doc = """ Typing trees a divide root diff --git a/mapytex/calculus/core/typing/multiply.py b/mapytex/calculus/core/typing/multiply.py index 73d7f25..e21d6fd 100644 --- a/mapytex/calculus/core/typing/multiply.py +++ b/mapytex/calculus/core/typing/multiply.py @@ -12,7 +12,7 @@ Multiply MO with typing from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.fraction import MOFraction from ..MO.monomial import MOstrPower, MOMonomial diff --git a/mapytex/calculus/core/typing/power.py b/mapytex/calculus/core/typing/power.py index 4ac7e5a..e647604 100644 --- a/mapytex/calculus/core/typing/power.py +++ b/mapytex/calculus/core/typing/power.py @@ -12,7 +12,7 @@ Typing Power with MO from multipledispatch import Dispatcher from ..tree import Tree -from ..MO.mo import MO, MOnumber, MOstr +from ..MO import MO, MOnumber, MOstr from ..MO.monomial import MOstrPower power_doc = """ Typing Power of MOs