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
"""
from .mo import *
from .mo import MO
from .atoms import MOnumber, MOstr, moify
# -----------------------------
# 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.
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
<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>
@ -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

View File

@ -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.
It is an abstract class with wrap recognizable math objects.
: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
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")
<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):
""" Initiate the MO
It should be idempotent.
>>> a = MO(3)
>>> a
<MO 3>
>>> a = MO(a)
>>> a
<MO 3>
"""
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")
<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__}>"
@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)
<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
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
<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.
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
<MOstrPower x^2>
>>> 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
<MOMonomial 4x>
>>> print(m)
*
> 4
> x
>>> print(m.__txt__)
4x
>>> print(m.__tex__)
4x
>>> x = MOstrPower('x', 2)
>>> MOMonomial(4, x)
<MOMonomial 4x^2>
>>> MOMonomial(4, 'x')
>>> m = MOMonomial(4, 'x')
>>> m
<MOMonomial 4x>
>>> print(m)
*
> 4
> x
>>> print(m.__txt__)
4x
>>> print(m.__tex__)
4x
>>> MOMonomial(4, 'x', 1)
<MOMonomial 4x>
>>> MOMonomial(4, 'x', 2)
<MOMonomial 4x^2>
>>> x = MOstrPower('x', 2)
>>> MOMonomial(4, x, 3)
>>> x2 = MOstrPower('x', 2)
>>> MOMonomial(4, x2, 3)
<MOMonomial 4x^6>
>>> 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):

View File

@ -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})
<MOpolynomial x^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):

View File

@ -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))
<MOnumber 3>
>>> compute("-", None, MOnumber(2))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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