Fix(Core): refact MO, clean import and fix renders

Split MO in 2 categories Atoms and Molecules
This commit is contained in:
Bertrand Benjamin 2018-12-21 11:26:37 +01:00
parent 80305671de
commit f51ffbbe8b
20 changed files with 616 additions and 413 deletions

View File

@ -10,7 +10,8 @@
MO: math objects MO: math objects
""" """
from .mo import * from .mo import MO
from .atoms import MOnumber, MOstr, moify
# ----------------------------- # -----------------------------
# Reglages pour 'vim' # Reglages pour 'vim'

View File

@ -0,0 +1,255 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2017 lafrite <lafrite@Poivre>
#
# 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)
[<MOnumber - 2>, '+', <MOstr x>, '*', <MOnumber 3.3>]
"""
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
<MOnumber 2>
>>> print(x)
2
>>> x.__txt__
'2'
>>> x.__tex__
'2'
"""
def __init__(self, value):
""" Initiate a number MO
:example:
>>> MOnumber(23)
<MOnumber 23>
>>> MOnumber(-23)
<MOnumber - 23>
As expected there will be trouble with float
>>> MOnumber(23.3)
<MOnumber 23.300000000000000710542735760100185871124267578125>
It will be better to use Decimal
>>> MOnumber(Decimal("23.3"))
<MOnumber 23.3>
>>> MOnumber(Decimal("-23.3"))
<MOnumber - 23.3>
MOnumber initialisation is idempotent
>>> a = MOnumber(23)
>>> MOnumber(a)
<MOnumber 23>
>>> 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 <class 'str'>)")
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
<MOstr x>
>>> print(x)
x
>>> x.__txt__
'x'
>>> x.__tex__
'x'
Polynoms properties
>>> x.variable
'x'
>>> x.coefficients
{1: <MOnumber 1>}
>>> x.degree
1
"""
def __init__(self, value):
""" Initiate a string MO
>>> a = MOstr("x")
>>> a
<MOstr x>
>>> b = MOstr(a)
>>> b
<MOstr x>
>>> 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: <MOnumber 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

View File

@ -7,14 +7,16 @@
# Distributed under terms of the MIT license. # Distributed under terms of the MIT license.
from mapytex.calculus.core.tree import Tree from mapytex.calculus.core.tree import Tree
from .mo import MO from .mo import Molecule, MO
__all__ = ["MOFraction"] __all__ = ["MOFraction"]
class MOFraction(MO): class MOFraction(Molecule):
""" Fraction math object""" """ Fraction math object"""
MAINOP = "/"
def __init__(self, numerator, denominator, negative=False): def __init__(self, numerator, denominator, negative=False):
""" Initiate the MOFraction """ Initiate the MOFraction
@ -28,6 +30,14 @@ class MOFraction(MO):
>>> f = MOFraction(2, 3) >>> f = MOFraction(2, 3)
>>> f >>> f
<MOFraction 2 / 3> <MOFraction 2 / 3>
>>> print(f.__txt__)
2 / 3
>>> print(f.__tex__)
\\frac{2}{3}
>>> print(f)
/
> 2
> 3
>>> f = MOFraction(2, 3, negative = True) >>> f = MOFraction(2, 3, negative = True)
>>> f >>> f
<MOFraction - 2 / 3> <MOFraction - 2 / 3>
@ -42,7 +52,7 @@ class MOFraction(MO):
tree = Tree("-", None, base_tree) tree = Tree("-", None, base_tree)
else: else:
tree = base_tree tree = base_tree
MO.__init__(self, tree) Molecule.__init__(self, tree)
self._numerator = _numerator self._numerator = _numerator
self._denominator = _denominator self._denominator = _denominator

View File

@ -6,61 +6,167 @@
# #
# Distributed under terms of the MIT license. # Distributed under terms of the MIT license.
from decimal import Decimal from abc import ABC, abstractmethod
from .exceptions import MOError from .exceptions import MOError
from ..coroutine import coroutine, STOOOP
from ..renders import tree2txt, tree2tex from ..renders import tree2txt, tree2tex
from functools import total_ordering
__all__ = ["MO"]
__all__ = ["moify", "MO", "MOstr"] class MO(ABC):
@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):
"""MO for math object """MO for math object
This base class is representing int and Decimal. It is an abstract class with wrap recognizable math objects.
:attr value: sympy compatible version of the MO There is 2 types of MO:
:attr _tree: tree version of the MO - Atom which are compose of single value builtin.
:attr _signature: Name to identify the MO in the API - 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")
<MOstr x>
>>> MO.factory(2)
<MOnumber 2>
>>> MO.factory(2.3)
<MOnumber 2.29999999999999982236431605997495353221893310546875>
>>> x = MO.factory("x")
>>> MO.factory(x)
<MOstr x>
>>> from decimal import Decimal
>>> MO.factory(Decimal("2.3"))
<MOnumber 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): def __init__(self, value):
""" Initiate the MO """ Initiate the MO
It should be idempotent. It should be idempotent.
>>> a = MO(3)
>>> a
<MO 3>
>>> a = MO(a)
>>> a
<MO 3>
""" """
try: try:
self._tree = value._tree self._tree = value._tree
@ -70,205 +176,27 @@ class MO(object):
self.is_scalar = True self.is_scalar = True
self._signature = None self._signature = None
@classmethod @property
def factory(cls, value): def tree(self):
""" Factory to ensure that a value is a MO before using it return self._tree
Idempotent?? @property
def content(self):
>>> MO.factory("x") return self._tree
<MOstr x>
>>> MO.factory(2)
<MOnumber 2>
>>> MO.factory(2.3)
<MOnumber 2.29999999999999982236431605997495353221893310546875>
>>> MO.factory(Decimal("2.3"))
<MOnumber 2.3>
>>> x = MO.factory("x")
>>> MO.factory(x)
<MOstr 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__}>"
def __str__(self): 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 @property
def __txt__(self): def __txt__(self):
try: return tree2txt(self._tree)
return tree2txt(self._tree)
except AttributeError:
return str(self._tree)
@property @property
def __tex__(self): def __tex__(self):
try: return tree2tex(self._tree)
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)
<MOnumber - 23>
>>> MOnumber(23.3)
<MOnumber 23.300000000000000710542735760100185871124267578125>
>>> MOnumber(Decimal("23.3"))
<MOnumber 23.3>
>>> MOnumber(Decimal("-23.3"))
<MOnumber - 23.3>
>>> a = MOnumber(23)
>>> MOnumber(a)
<MOnumber 23>
>>> 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
class MOstr(MO):
""" Unknown math object like x or n"""
def __init__(self, value):
""" Initiate a string MO
>>> a = MOstr("x")
>>> a
<MOstr x>
>>> b = MOstr(a)
>>> b
<MOstr x>
>>> 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: <MOnumber 1>}
"""
return {1: MOnumber(1)}
@property
def degree(self):
return 1
# ----------------------------- # -----------------------------

View File

@ -7,24 +7,35 @@
# Distributed under terms of the MIT license. # Distributed under terms of the MIT license.
from mapytex.calculus.core.tree import Tree 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 .exceptions import MOError
from ..renders import tree2txt, tree2tex
__all__ = ["MOMonomial"] __all__ = ["MOMonomial"]
class MOstrPower(MO): class MOstrPower(Molecule):
""" Power of a MOstr """ """ Power of a MOstr """
MAINOP = "^"
def __init__(self, variable, power): def __init__(self, variable, power):
""" Initiate a MOstrPower """ Initiate a MOstrPower
:param variable: variable of the monomial (a MOstr or later a MOSqrt) :param variable: variable of the monomial (a MOstr or later a MOSqrt)
:param power: non negative interger (MOnumber type) :param power: non negative interger (MOnumber type)
>>> MOstrPower("x", 2) >>> s = MOstrPower("x", 2)
>>> s
<MOstrPower x^2> <MOstrPower x^2>
>>> print(s)
^
> x
> 2
>>> print(s.__txt__)
x^2
>>> print(s.__tex__)
x^{2}
>>> MOstrPower(3, 1) >>> MOstrPower(3, 1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -55,7 +66,7 @@ class MOstrPower(MO):
_power = MO.factory(power) _power = MO.factory(power)
if power <= 1: if power <= 1:
raise MOError("The power of a MOstrPower should be greater than 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") raise MOError("The power of a monomial should be a integer")
self._power = _power self._power = _power
@ -64,8 +75,7 @@ class MOstrPower(MO):
self._power, self._power,
) )
MO.__init__(self, _tree) Molecule.__init__(self, _tree)
@property @property
def coefficients(self): def coefficients(self):
@ -105,10 +115,12 @@ class MOstrPower(MO):
""" """
return f"monome{self.power}" return f"monome{self.power}"
class MOMonomial(MO): class MOMonomial(Molecule):
""" Monomial math object""" """ Monomial math object"""
MAINOP = "*"
def __init__(self, coefficient, variable, power=1): def __init__(self, coefficient, variable, power=1):
""" Initiate the MOMonomial """ Initiate the MOMonomial
@ -117,19 +129,37 @@ class MOMonomial(MO):
:param power: degree of the monomial :param power: degree of the monomial
>>> x = MOstr('x') >>> x = MOstr('x')
>>> MOMonomial(4, x) >>> m = MOMonomial(4, x)
>>> m
<MOMonomial 4x> <MOMonomial 4x>
>>> print(m)
*
> 4
> x
>>> print(m.__txt__)
4x
>>> print(m.__tex__)
4x
>>> x = MOstrPower('x', 2) >>> x = MOstrPower('x', 2)
>>> MOMonomial(4, x) >>> MOMonomial(4, x)
<MOMonomial 4x^2> <MOMonomial 4x^2>
>>> MOMonomial(4, 'x') >>> m = MOMonomial(4, 'x')
>>> m
<MOMonomial 4x> <MOMonomial 4x>
>>> print(m)
*
> 4
> x
>>> print(m.__txt__)
4x
>>> print(m.__tex__)
4x
>>> MOMonomial(4, 'x', 1) >>> MOMonomial(4, 'x', 1)
<MOMonomial 4x> <MOMonomial 4x>
>>> MOMonomial(4, 'x', 2) >>> MOMonomial(4, 'x', 2)
<MOMonomial 4x^2> <MOMonomial 4x^2>
>>> x = MOstrPower('x', 2) >>> x2 = MOstrPower('x', 2)
>>> MOMonomial(4, x, 3) >>> MOMonomial(4, x2, 3)
<MOMonomial 4x^6> <MOMonomial 4x^6>
>>> MOMonomial(0, x) >>> MOMonomial(0, x)
Traceback (most recent call last): Traceback (most recent call last):
@ -139,6 +169,8 @@ class MOMonomial(MO):
_coefficient = MO.factory(coefficient) _coefficient = MO.factory(coefficient)
if coefficient == 0: if coefficient == 0:
raise MOError("The coefficient of a monomial should not be 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 self._coefficient = _coefficient
_variable = MO.factory(variable) _variable = MO.factory(variable)
@ -147,7 +179,6 @@ class MOMonomial(MO):
_variable = _variable.variable _variable = _variable.variable
elif isinstance(_variable, MOstr): elif isinstance(_variable, MOstr):
_power = MO.factory(power) _power = MO.factory(power)
_variable = variable
else: else:
raise MOError(f"variable need to be a MOstrPower or a MOstr. Got {type(variable)}.") 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) _tree = Tree("*", self._coefficient, self.strpower)
MO.__init__(self, _tree) Molecule.__init__(self, _tree)
@property @property
def coefficient(self): def coefficient(self):

View File

@ -7,17 +7,19 @@
# Distributed under terms of the MIT license. # Distributed under terms of the MIT license.
from mapytex.calculus.core.tree import Tree 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 .exceptions import MOError
from ..renders import tree2txt, tree2tex from .monomial import MOMonomial, MOstrPower
from .monomial import MOMonomial
__all__ = ["MOpolynomial"] __all__ = ["MOpolynomial"]
class MOpolynomial(MO): class MOpolynomial(Molecule):
""" MO polynomial""" """ MO polynomial"""
MAINOP = "+"
def __init__(self, variable, coefs): def __init__(self, variable, coefs):
""" Initiate a MOpolynomial """ Initiate a MOpolynomial
@ -36,7 +38,6 @@ class MOpolynomial(MO):
>>> MOpolynomial('x', {0: 1, 3: 1}) >>> MOpolynomial('x', {0: 1, 3: 1})
<MOpolynomial x^3 + 1> <MOpolynomial x^3 + 1>
""" """
_variable = MO.factory(variable) _variable = MO.factory(variable)
if not isinstance(_variable, MOstr): if not isinstance(_variable, MOstr):
@ -57,13 +58,17 @@ class MOpolynomial(MO):
for deg, coef in self._coefs.items(): for deg, coef in self._coefs.items():
if deg == 0: if deg == 0:
monomials[deg] = coef monomials[deg] = coef
elif deg == 1 and coef == 1:
monomials[deg] = MOstr(self._variable)
elif coef == 1:
monomials[deg] = MOstrPower(self._variable, deg)
else: else:
monomials[deg] = MOMonomial(coef, self._variable, deg) monomials[deg] = MOMonomial(coef, self._variable, deg)
self._monomials = monomials self._monomials = monomials
tree = Tree.from_list("+", list(self._monomials.values())[::-1]) tree = Tree.from_list("+", list(self._monomials.values())[::-1])
MO.__init__(self, tree) Molecule.__init__(self, tree)
@property @property
def variable(self): def variable(self):

View File

@ -17,7 +17,7 @@ from .minus import minus
from .multiply import multiply from .multiply import multiply
from .power import power from .power import power
from ..MO.mo import MOnumber, MOstr from ..MO import MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial
@ -41,7 +41,7 @@ def compute(node, left_v, right_v):
:example: :example:
>>> from ..MO.mo import MOnumber >>> from ..MO import MOnumber
>>> compute("+", MOnumber(1), MOnumber(2)) >>> compute("+", MOnumber(1), MOnumber(2))
<MOnumber 3> <MOnumber 3>
>>> compute("-", None, MOnumber(2)) >>> compute("-", None, MOnumber(2))

View File

@ -10,14 +10,12 @@
Adding MO Adding MO
""" """
from functools import wraps
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree from ..tree import Tree
from ..MO.mo import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial
from .exceptions import AddError
from .arithmetic import lcm from .arithmetic import lcm
from .filters import special_case from .filters import special_case

View File

@ -13,7 +13,7 @@ Divide MO
from decimal import Decimal from decimal import Decimal
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree from ..tree import Tree
from ..MO.mo import MO, MOnumber from ..MO import MO, MOnumber
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from .exceptions import DivideError from .exceptions import DivideError
from .filters import special_case from .filters import special_case

View File

@ -12,7 +12,7 @@ Minus MO: take the opposit
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from .exceptions import MinusError from .exceptions import MinusError
from ..MO.mo import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial

View File

@ -12,7 +12,7 @@ Multiply MO
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree from ..tree import Tree
from ..MO.mo import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial

View File

@ -12,7 +12,7 @@ Power with MO
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree from ..tree import Tree
from ..MO.mo import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial

View File

@ -13,19 +13,19 @@ __all__ = ['tree2tex']
def plus2tex(left, right): def plus2tex(left, right):
r""" + rendering r""" + rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> plus2tex(mo.MOnumber(2), mo.MOnumber(3)) >>> plus2tex(MO.factory(2), MO.factory(3))
'2 + 3' '2 + 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> plus2tex(t, mo.MOnumber(3)) >>> plus2tex(t, MO.factory(3))
'1 + 2 + 3' '1 + 2 + 3'
>>> plus2tex(t, mo.MOnumber(-3)) >>> plus2tex(t, MO.factory(-3))
'1 + 2 - 3' '1 + 2 - 3'
>>> plus2tex(mo.MOnumber(-3), t) >>> plus2tex(MO.factory(-3), t)
'- 3 + 1 + 2' '- 3 + 1 + 2'
>>> t = Tree.from_str("-2*3") >>> t = Tree.from_str("-2*3")
>>> plus2tex(mo.MOnumber(3), t) >>> plus2tex(MO.factory(3), t)
'3 - 2 \\times 3' '3 - 2 \\times 3'
""" """
display_plus = True display_plus = True
@ -54,8 +54,8 @@ def plus2tex(left, right):
def minus2tex(left, right): def minus2tex(left, right):
r""" - rendering r""" - rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> minus2tex(None, mo.MO(3)) >>> minus2tex(None, MO.factory(3))
'- 3' '- 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
@ -79,54 +79,30 @@ def minus2tex(left, right):
def mul2tex(left, right): def mul2tex(left, right):
r""" * rendering r""" * rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> mul2tex(mo.MO(2), mo.MO(3)) >>> mul2tex(MO.factory(2), MO.factory(3))
'2 \\times 3' '2 \\times 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> mul2tex(t, mo.MO(3)) >>> mul2tex(t, MO.factory(3))
'1 \\times 2 \\times 3' '1 \\times 2 \\times 3'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> mul2tex(t, mo.MO(3)) >>> mul2tex(t, MO.factory(3))
'(1 + 2) \\times 3' '(1 + 2) \\times 3'
>>> mul2tex(mo.MO(3), t) >>> mul2tex(MO.factory(3), t)
'3(1 + 2)' '3(1 + 2)'
>>> a = mo.MOstr('x') >>> a = MO.factory('x')
>>> mul2tex(mo.MO(3), a) >>> mul2tex(MO.factory(3), a)
'3x' '3x'
>>> mul2tex(a, a) >>> mul2tex(a, a)
'x \\times x' 'x \\times x'
""" """
display_time = True left_ = render_with_parenthesis(left, "*")
try: right_ = render_with_parenthesis(right, "*")
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)
try: display_time = True
right_need_parenthesis = False if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or \
if OPERATORS[right.node]["precedence"] < OPERATORS['*']["precedence"]: right_[0] == '(':
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 = False display_time = False
if display_time: if display_time:
@ -136,18 +112,18 @@ def mul2tex(left, right):
def div2tex(left, right): def div2tex(left, right):
r""" / rendering r""" / rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> div2tex(mo.MO(2), mo.MO(3)) >>> div2tex(MO.factory(2), MO.factory(3))
'\\frac{2}{3}' '\\frac{2}{3}'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1/2") >>> t = Tree.from_str("1/2")
>>> div2tex(t, mo.MO(3)) >>> div2tex(t, MO.factory(3))
'\\frac{\\frac{1}{2}}{3}' '\\frac{\\frac{1}{2}}{3}'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> div2tex(t, mo.MO(3)) >>> div2tex(t, MO.factory(3))
'\\frac{1 + 2}{3}' '\\frac{1 + 2}{3}'
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> div2tex(mo.MO(3), t) >>> div2tex(MO.factory(3), t)
'\\frac{3}{1 \\times 2}' '\\frac{3}{1 \\times 2}'
""" """
try: try:
@ -164,21 +140,21 @@ def div2tex(left, right):
def pow2tex(left, right): def pow2tex(left, right):
r""" ^ rendering r""" ^ rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> pow2tex(mo.MO(2), mo.MO(3)) >>> pow2tex(MO.factory(2), MO.factory(3))
'2^{3}' '2^{3}'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1^2") >>> t = Tree.from_str("1^2")
>>> pow2tex(t, mo.MO(3)) >>> pow2tex(t, MO.factory(3))
'1^{2}^{3}' '1^{2}^{3}'
>>> t = Tree.from_str("1^2") >>> t = Tree.from_str("1^2")
>>> pow2tex(mo.MO(3), t) >>> pow2tex(MO.factory(3), t)
'3^{1^{2}}' '3^{1^{2}}'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> pow2tex(t, mo.MO(3)) >>> pow2tex(t, MO.factory(3))
'(1 + 2)^{3}' '(1 + 2)^{3}'
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> pow2tex(mo.MO(3), t) >>> pow2tex(MO.factory(3), t)
'3^{1 \\times 2}' '3^{1 \\times 2}'
""" """
try: try:
@ -202,6 +178,28 @@ def pow2tex(left, right):
return f"{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 = { OPERATOR2TEX = {
"+": plus2tex, "+": plus2tex,
"-": minus2tex, "-": minus2tex,

View File

@ -6,45 +6,38 @@
# #
# Distributed under terms of the MIT license. # Distributed under terms of the MIT license.
from mapytex.calculus.core.operator import OPERATORS from ..operator import OPERATORS
__all__ = ['tree2txt'] __all__ = ['tree2txt']
def plus2txt(left, right): def plus2txt(left, right):
""" + rendering """ + rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> plus2txt(mo.MOnumber(2), mo.MOnumber(3)) >>> plus2txt(MO.factory(2), MO.factory(3))
'2 + 3' '2 + 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> plus2txt(t, mo.MOnumber(3)) >>> plus2txt(t, MO.factory(3))
'1 + 2 + 3' '1 + 2 + 3'
>>> plus2txt(t, mo.MOnumber(-3)) >>> plus2txt(t, MO.factory(-3))
'1 + 2 - 3' '1 + 2 - 3'
>>> plus2txt(mo.MOnumber(-3), t) >>> plus2txt(MO.factory(-3), t)
'- 3 + 1 + 2' '- 3 + 1 + 2'
>>> t = Tree.from_str("-2*3") >>> t = Tree.from_str("-2*3")
>>> plus2txt(mo.MOnumber(3), t) >>> plus2txt(MO.factory(3), t)
'3 - 2 * 3' '3 - 2 * 3'
""" """
display_plus = True
try:
left.node
except AttributeError:
left_ = left.__txt__
else:
left_ = tree2txt(left)
left_ = render_with_parenthesis(left, "+")
try: try:
right.node right_ = render_with_parenthesis(right, "+")
except AttributeError: except ValueError:
right_ = right.__txt__ raise TypeError(f"right -> {type(right)} {right}")
else:
right_ = tree2txt(right) display_plus = True
finally: if right_.startswith("-"):
if right_.startswith("-"): display_plus = False
display_plus = False
if display_plus: if display_plus:
return f"{left_} + {right_}" return f"{left_} + {right_}"
@ -54,8 +47,8 @@ def plus2txt(left, right):
def minus2txt(left, right): def minus2txt(left, right):
""" - rendering """ - rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> minus2txt(None, mo.MO(3)) >>> minus2txt(None, MO.factory(3))
'- 3' '- 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
@ -79,55 +72,32 @@ def minus2txt(left, right):
def mul2txt(left, right): def mul2txt(left, right):
""" * rendering """ * rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> mul2txt(mo.MO(2), mo.MO(3)) >>> mul2txt(MO.factory(2), MO.factory(3))
'2 * 3' '2 * 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> mul2txt(t, mo.MO(3)) >>> mul2txt(t, MO.factory(3))
'1 * 2 * 3' '1 * 2 * 3'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> mul2txt(t, mo.MO(3)) >>> mul2txt(t, MO.factory(3))
'(1 + 2) * 3' '(1 + 2) * 3'
>>> mul2txt(mo.MO(3), t) >>> mul2txt(MO.factory(3), t)
'3(1 + 2)' '3(1 + 2)'
>>> a = mo.MOstr('x') >>> a = MO.factory('x')
>>> mul2txt(mo.MO(3), a) >>> mul2txt(MO.factory(3), a)
'3x' '3x'
>>> mul2txt(a, a) >>> mul2txt(a, a)
'x * x' 'x * x'
""" """
display_time = True 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: left_ = render_with_parenthesis(left, "*")
right_need_parenthesis = False right_ = render_with_parenthesis(right, "*")
if OPERATORS[right.node]["precedence"] < OPERATORS['*']["precedence"]:
right_need_parenthesis = True if (right_[0].isalpha() and (left_.isnumeric() or left_.isdecimal())) or \
except AttributeError: right_[0] == '(':
try: display_time = False
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
if display_time: if display_time:
return f"{left_} * {right_}" return f"{left_} * {right_}"
@ -137,18 +107,18 @@ def mul2txt(left, right):
def div2txt(left, right): def div2txt(left, right):
""" / rendering """ / rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> div2txt(mo.MO(2), mo.MO(3)) >>> div2txt(MO.factory(2), MO.factory(3))
'2 / 3' '2 / 3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1/2") >>> t = Tree.from_str("1/2")
>>> div2txt(t, mo.MO(3)) >>> div2txt(t, MO.factory(3))
'1 / 2 / 3' '1 / 2 / 3'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> div2txt(t, mo.MO(3)) >>> div2txt(t, MO.factory(3))
'(1 + 2) / 3' '(1 + 2) / 3'
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> div2txt(mo.MO(3), t) >>> div2txt(MO.factory(3), t)
'3 / (1 * 2)' '3 / (1 * 2)'
""" """
try: try:
@ -179,31 +149,22 @@ def div2txt(left, right):
def pow2txt(left, right): def pow2txt(left, right):
""" ^ rendering """ ^ rendering
>>> from ..MO import mo >>> from ..MO import MO
>>> pow2txt(mo.MO(2), mo.MO(3)) >>> pow2txt(MO.factory(2), MO.factory(3))
'2^3' '2^3'
>>> from ..tree import Tree >>> from ..tree import Tree
>>> t = Tree.from_str("1^2") >>> t = Tree.from_str("1^2")
>>> pow2txt(t, mo.MO(3)) >>> pow2txt(t, MO.factory(3))
'1^2^3' '1^2^3'
>>> t = Tree.from_str("1+2") >>> t = Tree.from_str("1+2")
>>> pow2txt(t, mo.MO(3)) >>> pow2txt(t, MO.factory(3))
'(1 + 2)^3' '(1 + 2)^3'
>>> t = Tree.from_str("1*2") >>> t = Tree.from_str("1*2")
>>> pow2txt(mo.MO(3), t) >>> pow2txt(MO.factory(3), t)
'3^(1 * 2)' '3^(1 * 2)'
""" """
try: left_ = render_with_parenthesis(left, "^")
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: try:
right_need_parenthesis = False right_need_parenthesis = False
if OPERATORS[right.node]["precedence"] < OPERATORS['^']["precedence"]: if OPERATORS[right.node]["precedence"] < OPERATORS['^']["precedence"]:
@ -218,6 +179,26 @@ def pow2txt(left, right):
return f"{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 = { OPERATOR2TXT = {
"+": plus2txt, "+": plus2txt,
"-": minus2txt, "-": minus2txt,
@ -240,6 +221,9 @@ def tree2txt(tree):
>>> tree2txt(t) >>> tree2txt(t)
'2 + 3 * 4' '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) return OPERATOR2TXT[tree.node](tree.left_value, tree.right_value)

View File

@ -412,8 +412,8 @@ class Tree():
>>> t = Tree.from_str("3+4+5*2") >>> t = Tree.from_str("3+4+5*2")
>>> [l for l in t.get_leafs()] >>> [l for l in t.get_leafs()]
[<MOnumber 3>, <MOnumber 4>, <MOnumber 5>, <MOnumber 2>] [<MOnumber 3>, <MOnumber 4>, <MOnumber 5>, <MOnumber 2>]
>>> {type(l) for l in t.get_leafs()} >>> {type(l).__name__ for l in t.get_leafs()}
{<class 'mapytex.calculus.core.MO.mo.MOnumber'>} {'MOnumber'}
""" """
try: try:
yield from self.left_value.get_leafs(callback) yield from self.left_value.get_leafs(callback)
@ -1377,8 +1377,8 @@ class AssocialTree(Tree):
>>> t = AssocialTree.from_str("3+4+5*2") >>> t = AssocialTree.from_str("3+4+5*2")
>>> [l for l in t.get_leafs(str)] >>> [l for l in t.get_leafs(str)]
['3', '4', '*\\n > 5\\n > 2'] ['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__) ]
[<class 'mapytex.calculus.core.MO.mo.MOnumber'>, <class 'mapytex.calculus.core.MO.mo.MOnumber'>, <class 'mapytex.calculus.core.tree.AssocialTree'>] ['MOnumber', 'MOnumber', 'AssocialTree']
""" """
try: try:
if self.left_value.node == self.node: if self.left_value.node == self.node:

View File

@ -17,7 +17,7 @@ from .multiply import multiply
from .divide import divide from .divide import divide
from .power import power from .power import power
from ..MO.mo import MOnumber, MOstr from ..MO import MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial
@ -40,19 +40,12 @@ def typing(node, left_v, right_v,\
""" """
Typing a try base on his root node Typing a try base on his root node
:example:
>>> from ..MO.mo import MOnumber
""" """
try: try:
operation = OPERATIONS[node] operation = OPERATIONS[node]
except KeyError: except KeyError:
raise NotImplementedError(f"Unknown operation ({node}) in typing") raise NotImplementedError(f"Unknown operation ({node}) in typing")
return operation(left_v, right_v) 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): def typing_capacities(node):
""" Test an operation through all MOs """ Test an operation through all MOs

View File

@ -12,7 +12,7 @@ Add MO with typing
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree 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.monomial import MOstrPower, MOMonomial
from ..MO.polynomial import MOpolynomial from ..MO.polynomial import MOpolynomial
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction

View File

@ -11,7 +11,7 @@ Typing trees with a divide root
""" """
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..MO.mo import MO, MOnumber from ..MO import MO, MOnumber
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
divide_doc = """ Typing trees a divide root divide_doc = """ Typing trees a divide root

View File

@ -12,7 +12,7 @@ Multiply MO with typing
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree from ..tree import Tree
from ..MO.mo import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.fraction import MOFraction from ..MO.fraction import MOFraction
from ..MO.monomial import MOstrPower, MOMonomial from ..MO.monomial import MOstrPower, MOMonomial

View File

@ -12,7 +12,7 @@ Typing Power with MO
from multipledispatch import Dispatcher from multipledispatch import Dispatcher
from ..tree import Tree from ..tree import Tree
from ..MO.mo import MO, MOnumber, MOstr from ..MO import MO, MOnumber, MOstr
from ..MO.monomial import MOstrPower from ..MO.monomial import MOstrPower
power_doc = """ Typing Power of MOs power_doc = """ Typing Power of MOs