From 2109fc46cbf34f487e39457f5ae5cbec6f35bae1 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sat, 12 Dec 2020 23:14:44 +0100 Subject: [PATCH] Fix: simplify rendering by expanding tree. still bugs with set_render --- mapytex/calculus/API/__init__.py | 40 ++++++++++++ mapytex/calculus/API/expression.py | 39 +++--------- mapytex/calculus/API/tokens/token.py | 2 +- mapytex/calculus/core/MO/atoms.py | 11 ++-- mapytex/calculus/core/MO/fraction.py | 10 +-- mapytex/calculus/core/MO/mo.py | 30 +++------ mapytex/calculus/core/MO/monomial.py | 76 +++++++++++------------ mapytex/calculus/core/compute/__init__.py | 5 +- mapytex/calculus/core/compute/minus.py | 2 +- mapytex/calculus/core/renders/tree2tex.py | 16 ++++- mapytex/calculus/core/renders/tree2txt.py | 16 ++++- mapytex/calculus/core/str2.py | 13 ++-- 12 files changed, 140 insertions(+), 120 deletions(-) diff --git a/mapytex/calculus/API/__init__.py b/mapytex/calculus/API/__init__.py index 79ceb81..5539a3f 100644 --- a/mapytex/calculus/API/__init__.py +++ b/mapytex/calculus/API/__init__.py @@ -108,12 +108,52 @@ x^7 (6 + 6) * x + 4x^2 + 9 4x^2 + 12x + 9 +>>> e = Expression.from_str("(2x+3)(-x+1)") +>>> e_simplified = e.simplify() +>>> e_simplified + +>>> for s in e_simplified.explain(): +... print(s) """ from .expression import Expression +from .tokens import Token from .tokens.number import Integer, Decimal + +def set_render(render): + """ + :example: + >>> e = Expression.from_str("2+3*4") + >>> print(e) + 2 + 3 * 4 + >>> e = Expression.from_str("2+3/4") + >>> print(e) + 2 + 3 / 4 + >>> es = e.simplify() + >>> print(es) + 11 / 4 + >>> set_render('tex') + >>> Expression.RENDER + 'tex' + >>> Token.RENDER + 'tex' + >>> e = Expression.from_str("2+3*4") + >>> print(e) + 2 + 3 \\times 4 + >>> e = Expression.from_str("2+3/4") + >>> print(e) + 2 + \\dfrac{3}{4} + >>> es = e.simplify() + >>> print(es) + \\dfrac{11}{4} + >>> set_render('txt') + """ + Expression.set_render(render) + Token.set_render(render) + + if __name__ == "__main__": e = Expression.from_str("1+2/3/4/5") et = e._typing() diff --git a/mapytex/calculus/API/expression.py b/mapytex/calculus/API/expression.py index 4ac4857..29e15d7 100644 --- a/mapytex/calculus/API/expression.py +++ b/mapytex/calculus/API/expression.py @@ -56,33 +56,7 @@ class Expression(object): :param render: render name (txt or tex) - :example: - >>> e = Expression.from_str("2+3*4") - >>> print(e) - 2 + 3 * 4 - >>> e = Expression.from_str("2+3/4") - >>> print(e) - 2 + 3 / 4 - >>> es = e.simplify() - >>> print(es) - 11 / 4 - >>> Expression.set_render('tex') - >>> Expression.RENDER - 'tex' - >>> e = Expression.from_str("2+3*4") - >>> print(e) - 2 + 3 \\times 4 - >>> e = Expression.from_str("2+3/4") - >>> print(e) - 2 + \\dfrac{3}{4} - >>> es = e.simplify() - >>> print(es) - \\dfrac{11}{4} - >>> Expression.set_render('txt') """ - from .tokens.token import Token - - Token.set_render(render) cls.RENDER = render @classmethod @@ -183,7 +157,7 @@ class Expression(object): """ Order the expression base on types :example: - + >>> e = Expression.from_str("1 + 2x + 3 + 4x") >>> print(e) 1 + 2x + 3 + 4x @@ -204,7 +178,8 @@ class Expression(object): return type(leaf) else: try: - typed_leaf = typing(leaf.node, leaf.left_value, leaf.right_value) + typed_leaf = typing( + leaf.node, leaf.left_value, leaf.right_value) return typed_leaf.signature except (AttributeError, NotImplementedError, TypingError): return type(leaf) @@ -221,7 +196,7 @@ class Expression(object): def _optimize(self, exclude_nodes=["/", "**"]): """ Return a copy of self with an optimize tree - + :example: >>> e = Expression.from_str("2x^2+2x+3x") >>> print(e._tree) @@ -262,7 +237,7 @@ class Expression(object): def _typing(self): """ Build a copy of self with as much typing as possible - + :example: >>> e = Expression.from_str("2x", typing=False) >>> print(e._tree.map_on_leaf(lambda x: type(x).__name__)) @@ -307,7 +282,7 @@ class Expression(object): def _compute(self): """" Compute one step of self - + """ try: return Expression(self._tree.apply_on_last_level(compute)) @@ -372,7 +347,7 @@ class Expression(object): def explain(self): """ Yield every calculus step which have lead to self - + :example: >>> e = Expression.from_str("2+3*4") >>> f = e.simplify() diff --git a/mapytex/calculus/API/tokens/token.py b/mapytex/calculus/API/tokens/token.py index a4e2665..d30c4fe 100644 --- a/mapytex/calculus/API/tokens/token.py +++ b/mapytex/calculus/API/tokens/token.py @@ -246,8 +246,8 @@ class Token(object): >>> c = a ** 2 >>> c + """ - """ return self._operate(other, "^") def _roperate(self, other, operation): diff --git a/mapytex/calculus/core/MO/atoms.py b/mapytex/calculus/core/MO/atoms.py index 1f4270b..91bd3b7 100644 --- a/mapytex/calculus/core/MO/atoms.py +++ b/mapytex/calculus/core/MO/atoms.py @@ -37,7 +37,7 @@ def moify_cor(target): >>> for i in [-2, "+", "x", "*", Decimal("3.3")]: ... list2molist.send(i) >>> list2molist.throw(STOOOP) - [, '+', , '*', ] + [, '+', , '*', ] """ try: @@ -78,7 +78,7 @@ class MOnumber(Atom): >>> MOnumber(23) >>> MOnumber(-23) - + As expected there will be trouble with float @@ -90,13 +90,13 @@ class MOnumber(Atom): >>> MOnumber(Decimal("23.3")) >>> MOnumber(Decimal("-23.3")) - + Or directly passe a decimal string >>> MOnumber("23.3") >>> MOnumber("-23.3") - + MOnumber initialisation is idempotent @@ -259,7 +259,8 @@ class MOstr(Atom): 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}") + raise MOError( + f"An MOstr should be initiate with a alpha string, got {val}") Atom.__init__(self, val) diff --git a/mapytex/calculus/core/MO/fraction.py b/mapytex/calculus/core/MO/fraction.py index c73dacc..798920f 100644 --- a/mapytex/calculus/core/MO/fraction.py +++ b/mapytex/calculus/core/MO/fraction.py @@ -32,10 +32,10 @@ class MOFraction(Molecule): >>> f = MOFraction(2, 3) >>> f - >>> print(f.__txt__) - 2 / 3 - >>> print(f.__tex__) - \\dfrac{2}{3} + >>> print(f.tree) + / + > 2 + > 3 >>> print(f) 2 / 3 >>> f = MOFraction(2, 3, negative = True) @@ -44,11 +44,13 @@ class MOFraction(Molecule): """ _numerator = MO.factory(numerator) _denominator = MO.factory(denominator) + base_tree = Tree("/", _numerator, _denominator) if negative: tree = Tree("-", None, base_tree) else: tree = base_tree + Molecule.__init__(self, tree) self._numerator = _numerator diff --git a/mapytex/calculus/core/MO/mo.py b/mapytex/calculus/core/MO/mo.py index 74149cc..2ca7f34 100644 --- a/mapytex/calculus/core/MO/mo.py +++ b/mapytex/calculus/core/MO/mo.py @@ -57,20 +57,12 @@ class MO(ABC): pass def __repr__(self): - return f"<{self.__class__.__name__} {self.__txt__}>" + return f"<{self.__class__.__name__} {self.__str__()}>" @abstractmethod def __str__(self): pass - @abstractmethod - def __txt__(self): - pass - - @abstractmethod - def __tex__(self): - pass - def __hash__(self): try: return self._tree.__hash__() @@ -161,21 +153,21 @@ class Molecule(MO): It is a wrapping of tree - Its wrapping tree can be access throw .tree property + Its wrapping tree can be access through .tree property """ MAINOP = None - def __init__(self, value): + def __init__(self, tree): """ Initiate the MO It should be idempotent. """ try: - self._tree = value._tree + self._tree = tree._tree except AttributeError: - self._tree = value + self._tree = tree self.is_scalar = True self._signature = None @@ -186,18 +178,10 @@ class Molecule(MO): @property def content(self): - return self._tree + return self.tree def __str__(self): - return str(self.__txt__) - - @property - def __txt__(self): - return tree2txt(self._tree) - - @property - def __tex__(self): - return tree2tex(self._tree) + return tree2txt(self.tree) # ----------------------------- diff --git a/mapytex/calculus/core/MO/monomial.py b/mapytex/calculus/core/MO/monomial.py index b02412b..32cfdfd 100644 --- a/mapytex/calculus/core/MO/monomial.py +++ b/mapytex/calculus/core/MO/monomial.py @@ -31,10 +31,10 @@ class MOstrPower(Molecule): >>> print(s) x^2 - >>> print(s.__txt__) - x^2 - >>> print(s.__tex__) - x^{2} + >>> print(s.tree) + ^ + > x + > 2 >>> MOstrPower(3, 1) Traceback (most recent call last): ... @@ -59,7 +59,8 @@ class MOstrPower(Molecule): """ _variable = MO.factory(variable) if not isinstance(_variable, MOstr): - raise MOError("The variable of a monomial should be convertible into MOstr") + raise MOError( + "The variable of a monomial should be convertible into MOstr") self._variable = _variable _power = MO.factory(power) @@ -102,7 +103,7 @@ class MOstrPower(Molecule): @property def signature(self): """ Name of the mo in the API - + :example: >>> MOstrPower("x", 3).signature 'monome3' @@ -123,7 +124,8 @@ class MOstrPower(Molecule): """ if self._power > 2: return Tree( - "*", self.power, MOstrPower(self.variable, self._power._value - 1) + "*", self.power, MOstrPower(self.variable, + self._power._value - 1) ) return Tree("*", self.power, MOstr(self.variable)) @@ -147,22 +149,20 @@ class MOMonomial(Molecule): >>> print(m) 4x - >>> print(m.__txt__) - 4x - >>> print(m.__tex__) - 4x + >>> print(m.tree) + * + > 4 + > x >>> x = MOstrPower('x', 2) >>> MOMonomial(4, x) - >>> m = MOMonomial(4, 'x') + >>> m = MOMonomial(-1, 'x') >>> m - - >>> print(m) - 4x - >>> print(m.__txt__) - 4x - >>> print(m.__tex__) - 4x + + >>> print(m.tree) + - + > None + > x >>> MOMonomial(4, 'x', 1) >>> MOMonomial(4, 'x', 2) @@ -170,6 +170,13 @@ class MOMonomial(Molecule): >>> x2 = MOstrPower('x', 2) >>> MOMonomial(4, x2, 3) + >>> m = MOMonomial(-1, 'x', 2) + >>> m + + >>> print(m.tree) + - + > None + > x^2 >>> MOMonomial(0, x) Traceback (most recent call last): ... @@ -199,34 +206,21 @@ class MOMonomial(Molecule): self._power = _power try: - if self._coefficient.value != 1: - _tree = Tree("*", self._coefficient, self.strpower) - else: + if self.coefficient.value == 1: _tree = self.strpower + else: + _tree = Tree("*", self.coefficient, self.strpower) except AttributeError: - _tree = Tree("*", self._coefficient, self.strpower) + _tree = Tree("*", self.coefficient, self.strpower) Molecule.__init__(self, _tree) - def __str__(self): - if self._coefficient != -1: - return super(MOMonomial, self).__str__() - else: - return "- " + self.strpower.__str__() - @property - def __txt__(self): - if self._coefficient != -1: - return super(MOMonomial, self).__txt__ - else: - return "- " + self.strpower.__txt__ + def tree(self): + if self._coefficient == -1: + return Tree("-", None, self.strpower) - @property - def __tex__(self): - if self._coefficient != -1: - return super(MOMonomial, self).__tex__ - else: - return "- " + self.strpower.__tex__ + return Tree("*", self.coefficient, self.strpower) @property def coefficient(self): @@ -265,7 +259,7 @@ class MOMonomial(Molecule): @property def signature(self): """ Name of the mo in the API - + :example: >>> MOMonomial(2, "x").signature 'monome1' diff --git a/mapytex/calculus/core/compute/__init__.py b/mapytex/calculus/core/compute/__init__.py index b764ffb..13bd3f1 100644 --- a/mapytex/calculus/core/compute/__init__.py +++ b/mapytex/calculus/core/compute/__init__.py @@ -40,7 +40,7 @@ def compute(node, left_v, right_v): >>> compute("+", MOnumber(1), MOnumber(2)) >>> compute("-", None, MOnumber(2)) - + >>> compute("*", MOnumber(1), MOnumber(2)) >>> compute("~", MOnumber(1), MOnumber(2)) @@ -70,7 +70,8 @@ def compute_capacities(node): op = OPERATIONS[node] lines = [[node] + [mo.__name__ for mo in MOS]] for left_mo in MOS: - lines.append([left_mo.__name__] + [(left_mo, i) in op.funcs for i in MOS]) + lines.append([left_mo.__name__] + + [(left_mo, i) in op.funcs for i in MOS]) return lines diff --git a/mapytex/calculus/core/compute/minus.py b/mapytex/calculus/core/compute/minus.py index 1e8f6b5..1ac2f5a 100644 --- a/mapytex/calculus/core/compute/minus.py +++ b/mapytex/calculus/core/compute/minus.py @@ -35,7 +35,7 @@ def monumber(_, right): >>> a = MOnumber(4) >>> minus(None, a) - + """ return MO.factory(-right.value) diff --git a/mapytex/calculus/core/renders/tree2tex.py b/mapytex/calculus/core/renders/tree2tex.py index c6dc8b5..531c08b 100644 --- a/mapytex/calculus/core/renders/tree2tex.py +++ b/mapytex/calculus/core/renders/tree2tex.py @@ -210,7 +210,8 @@ def render_with_parenthesis(subtree, operator): return subtree_ -OPERATOR2TEX = {"+": plus2tex, "-": minus2tex, "*": mul2tex, "/": div2tex, "^": pow2tex} +OPERATOR2TEX = {"+": plus2tex, "-": minus2tex, + "*": mul2tex, "/": div2tex, "^": pow2tex} def tree2tex(tree): @@ -230,8 +231,17 @@ def tree2tex(tree): from ..tree import Tree if not isinstance(tree, Tree): - raise ValueError(f"Can only render a Tree (got {type(tree).__name__}: {tree})") - return OPERATOR2TEX[tree.node](tree.left_value, tree.right_value) + raise ValueError( + f"Can only render a Tree (got {type(tree).__name__}: {tree})") + + def expand(leaf): + try: + return leaf.tree + except AttributeError: + return leaf + expanded_tree = tree.map_on_leaf(expand) + + return OPERATOR2TEX[expanded_tree.node](expanded_tree.left_value, expanded_tree.right_value) # ----------------------------- diff --git a/mapytex/calculus/core/renders/tree2txt.py b/mapytex/calculus/core/renders/tree2txt.py index 826ee0d..2942d9e 100644 --- a/mapytex/calculus/core/renders/tree2txt.py +++ b/mapytex/calculus/core/renders/tree2txt.py @@ -214,7 +214,8 @@ def render_with_parenthesis(subtree, operator): return subtree_ -OPERATOR2TXT = {"+": plus2txt, "-": minus2txt, "*": mul2txt, "/": div2txt, "^": pow2txt} +OPERATOR2TXT = {"+": plus2txt, "-": minus2txt, + "*": mul2txt, "/": div2txt, "^": pow2txt} def tree2txt(tree): @@ -234,8 +235,17 @@ def tree2txt(tree): 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) + raise ValueError( + f"Can only render a Tree (got {type(tree).__name__}: {tree})") + + def expand(leaf): + try: + return leaf.tree + except AttributeError: + return leaf + expanded_tree = tree.map_on_leaf(expand) + + return OPERATOR2TXT[expanded_tree.node](expanded_tree.left_value, expanded_tree.right_value) # ----------------------------- diff --git a/mapytex/calculus/core/str2.py b/mapytex/calculus/core/str2.py index 0dce4ba..aa6d4d2 100644 --- a/mapytex/calculus/core/str2.py +++ b/mapytex/calculus/core/str2.py @@ -271,7 +271,7 @@ def concurent_broadcast(target, lookfors=[]): >>> a = searcher.throw(STOOOP) >>> print(a) ['az', 'ABC', 'a', 'b', 'az', 'b'] - + >>> lfop = lookfor(something_in("+-*/()"), lambda x: f"op{x}") >>> searcher = concurent_broadcast(list_sink, [lfop]) >>> for i in '12+3+234': @@ -484,7 +484,8 @@ def lookforNumbers(target): if current.replace("-", "", 1).isdigit(): current += tok else: - raise ParsingError(f"Can't build decimal with '{current}'") + raise ParsingError( + f"Can't build decimal with '{current}'") elif tok == "-": if current == "": current = tok @@ -802,7 +803,8 @@ def str2(sink, convert_to_mo=True): operator_corout(missing_times(moify_cor(pparser(sink)))) ) else: - str2_corout = lookforNumbers(operator_corout(missing_times(pparser(sink)))) + str2_corout = lookforNumbers( + operator_corout(missing_times(pparser(sink)))) for i in expression.replace(" ", ""): str2_corout.send(i) @@ -819,7 +821,7 @@ def rdstr2(sink): :example: >>> rdstr2list = rdstr2(list_sink) >>> rdstr2list("{a}+{a*b}-2") - [, '+', , '+', ] + [, '+', , '+', ] >>> rdstr2list("{a}({b}x+{c})") [, '*', [, '*', , '+', ]] """ @@ -828,7 +830,8 @@ def rdstr2(sink): def pipeline(expression): str2_corout = look_for_rdleaf( - lookforNumbers(operator_corout(missing_times(moify_cor(pparser(sink))))) + lookforNumbers(operator_corout( + missing_times(moify_cor(pparser(sink))))) ) for i in expression.replace(" ", ""):