From 8ee24d763bfaf942e8382084c6dcfcea90239669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Barth=C3=A9lemy?= Date: Tue, 20 Feb 2024 21:21:55 +0100 Subject: [PATCH] Created Exp in operator_file.py to represent Power Implemented Expand and regroup --- python_symb/Expressions/expr.py | 138 +++++++++++++++--- python_symb/Expressions/tree.py | 5 +- python_symb/MathTypes/operator_file.py | 38 +++-- python_symb/MathTypes/symbols.py | 41 +++++- python_symb/Parsing/parse.py | 3 +- python_symb/TreeModification/basic_modif.py | 153 ++++++++++++++++++++ python_symb/TreeModification/expand.py | 14 -- python_symb/tree_visual.py | 1 + 8 files changed, 335 insertions(+), 58 deletions(-) create mode 100644 python_symb/TreeModification/basic_modif.py delete mode 100644 python_symb/TreeModification/expand.py diff --git a/python_symb/Expressions/expr.py b/python_symb/Expressions/expr.py index 8fb2922..d26d828 100644 --- a/python_symb/Expressions/expr.py +++ b/python_symb/Expressions/expr.py @@ -2,12 +2,11 @@ from __future__ import annotations import python_symb.MathTypes.symbols from python_symb.Expressions.tree import Tree -from python_symb.MathTypes.operator_file import Add, Mul, Min -from python_symb.MathTypes.operator_file import UnaryOperator, BinOperator, Add, Mul, Min +from python_symb.MathTypes.operator_file import UnaryOperator, BinOperator, Add, Mul, Exp from typing import List -from python_symb.MathTypes.symbols import Var +from python_symb.MathTypes.symbols import Var, var from python_symb.Parsing.parse import infix_str_to_postfix @@ -24,9 +23,20 @@ class Expr(Tree): Expr(Mul, [Expr(5), Expr(Expr(Add, [Expr(2), Expr(3)]))]) """ + __match_args__ = ('value', 'children') def __init__(self, value, children=None): + from python_symb.MathTypes.operator_file import BinOperator, UnaryOperator super().__init__(value, children if children else []) + assert all([isinstance(child, Expr) for child in self.children]), f'Invalid children: {self.children} all child should be Expr' + + match value: + case BinOperator() as op: + assert len(self.children) == 2, f'Invalid number of children for BinOperator{op}: {len(self.children)}' + + case UnaryOperator() as op: + assert len(self.children) == 1, f'Invalid number of children for UnaryOperator{op}: {len(self.children)}' + @staticmethod def from_postfix_list(postfix: List): @@ -60,6 +70,31 @@ class Expr(Tree): expr_rev_polish = infix_str_to_postfix(expr_str) return Expr.from_postfix_list(expr_rev_polish) + + def to_infix_str(self, parent_precedence=-100, implicit_mul=True) -> str: + """ + Return the infix string of the expression + """ + match self: + case Expr(value) if self.is_leaf: + return str(value) + + case Expr(UnaryOperator() as op, [child]): + return f"{op.name}({child.to_infix_str(parent_precedence=op.precedence)})" + + case Expr(BinOperator() as op, [left, right]): + op_name = op.name if not(implicit_mul and op == Mul) else '' + if op.precedence < parent_precedence: + print("hehehe") + print(self, op.precedence, parent_precedence) + return f"({left.to_infix_str(op.precedence)}{op_name}{right.to_infix_str(op.precedence)})" + else: + return f"{left.to_infix_str(op.precedence)}{op_name}{right.to_infix_str(op.precedence)}" + + + + + @staticmethod def bin_op_constructor(self, other, op): """ Construct a binary operation @@ -73,32 +108,99 @@ class Expr(Tree): return ValueError(f'Invalid type for operation: {other} : {type(other)}') def __add__(self, other): - return self.bin_op_constructor(other, Add) + other_expr = other if isinstance(other, Expr) else Expr(other) + return Expr.bin_op_constructor(self, other_expr, Add) + + def __radd__(self, other): + other_expr = other if isinstance(other, Expr) else Expr(other) + return Expr.bin_op_constructor(other_expr, self, Add) def __mul__(self, other): - return self.bin_op_constructor(other, Mul) + other_expr = other if isinstance(other, Expr) else Expr(other) + return Expr.bin_op_constructor(self, other_expr, Mul) + + def __rmul__(self, other): + other_expr = other if isinstance(other, Expr) else Expr(other) + return Expr.bin_op_constructor(other_expr, self, Mul) def __sub__(self, other): - return self.bin_op_constructor(other, Min) + return Expr.bin_op_constructor(self, other, Min) + + def __hash__(self): + """ + Two equivalent expressions (without more modification like factorisation, or expanding) should have the same hash + see test_eq + """ + match self: + + case Expr(value) if self.is_leaf: + return hash(value) + + case Expr(UnaryOperator() as op, [child]): + return hash(op.name + str(hash(child))) + + case Expr(BinOperator() as op, [left, right]): + if op.properties.commutative and op.properties.associative: + return hash(op.name) + hash(left) + hash(right) + else: + return hash(op.name) + hash(str(hash(left)) + str(hash(right))) + + case _: + print(f'Invalid type: {type(self)}') + + def bad_eq(self, other): + return self.__hash__() == other.__hash__() + + def __eq__(self, other): + """temporary""" + return self.bad_eq(other) -def test1(): - x, y = Var('x'), Var('y') - expr1 = x + y - expr2 = 5+x - print(expr1 + expr2) +def test(): + x, y = var('x'), var('y') + a, b = var('a'), var('b') + def test1(): + expr1 = x + y + expr2 = 5+x + print(expr1 + expr2) -def test2(): - from python_symb.MathTypes.operator_file import Sin - a, b = Var('a'), Var('b') - expr = Sin(a+b) - print(expr) + def test2(): + from python_symb.MathTypes.operator_file import Sin + expr = Sin(x+y) + print(expr) + + def test_eq(): + expr = x + y + 3 + expr2 = 3 + x + y + print("----") + print(expr) + print(expr2) + print(expr == expr2) + + def test_return_to_string(): + expr = x+y + new_expr = 5*expr + print(new_expr) + print(f"new_expr: {new_expr.to_infix_str()}") + + expr2 = x*x*x + y*y*y + print(expr2) + print(f"expr2: {expr2.to_infix_str()}") + + + print("test1") + test1() + print("test2") + test2() + print("test_eq") + test_eq() + print("test_return_to_string") + test_return_to_string() if __name__ == '__main__': - test1() - test2() + test() diff --git a/python_symb/Expressions/tree.py b/python_symb/Expressions/tree.py index 0d4af3a..32ebcee 100644 --- a/python_symb/Expressions/tree.py +++ b/python_symb/Expressions/tree.py @@ -82,4 +82,7 @@ class Tree(ABC): yield from aux(child) yield tree - yield from aux(self) \ No newline at end of file + yield from aux(self) + + + diff --git a/python_symb/MathTypes/operator_file.py b/python_symb/MathTypes/operator_file.py index 0345f83..7a7f862 100644 --- a/python_symb/MathTypes/operator_file.py +++ b/python_symb/MathTypes/operator_file.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Dict, Callable +from typing import Set, Callable from python_symb.MathTypes.symbols import Symbols @@ -52,11 +52,11 @@ class BinProperties: Represent the properties of a binary operator """ - def __init__(self, associativity: bool, commutativity: True, - left_distributivity: Dict[str, bool], right_distributivity: Dict[str, bool]): + def __init__(self, associative: bool, commutative: True, + left_distributivity: Set[str], right_distributivity: Set[str]): """ - :param associativity: True if the operator is associative - :param commutativity: True if the operator is commutative + :param associative: True if the operator is associative + :param commutative: True if the operator is commutative :param left_distributivity: a dictionary of the operators that the current operator distribute over :param right_distributivity: a dictionary of the operators that distribute over the current operator @@ -68,10 +68,10 @@ class BinProperties: right_distributivity = {'+': True} """ - self.associativity = associativity - self.commutativity = commutativity - self.left_distributivity = left_distributivity - self.right_distributivity = right_distributivity + self.associative = associative + self.commutative = commutative + self.left_distributive = left_distributivity + self.right_distributive = right_distributivity class BinOperator(Operator): @@ -92,18 +92,26 @@ class BinOperator(Operator): return self.call(left, right) + + + + """ Generic operators """ +ExpProperties = BinProperties(False, False, set(), set()) +Exp = BinOperator('^', 4, ExpProperties, lambda x, y: x ** y) + +MulProperties = BinProperties(True, True, {'+'}, {'+'}) +Mul = BinOperator('*', 3, MulProperties, lambda x, y: x * y, Exp) + +AddProperties = BinProperties(True, True, set(), set()) +Add = BinOperator('+', 2, AddProperties, lambda x, y: x + y, Mul) + + -AddProperties = BinProperties(True, True, {'*': True}, {'*': True}) -Add = BinOperator('+', 2, AddProperties, lambda x, y: x + y) -MulProperties = BinProperties(True, True, {'+': True}, {'+': True}) -Mul = BinOperator('*', 3, MulProperties, lambda x, y: x * y) Sin = UnaryOperator('sin', 10, lambda x: x) -Min = BinOperator('-', 2, AddProperties, lambda x, y: x - y) - diff --git a/python_symb/MathTypes/symbols.py b/python_symb/MathTypes/symbols.py index 1fe07ec..96699a4 100644 --- a/python_symb/MathTypes/symbols.py +++ b/python_symb/MathTypes/symbols.py @@ -1,4 +1,6 @@ from __future__ import annotations +import traceback + class Symbols: @@ -18,21 +20,33 @@ class Symbols: def __str__(self): return self.name - def __add__(self, other): + def __add__(self, other) -> Expr: from python_symb.Expressions.expr import Expr - return Expr('+', [self, other]) + from python_symb.MathTypes.operator_file import Add + other_expr = other if isinstance(other, Expr) else Expr(other) + self_expr = Expr(self) + return Expr(Add, [self_expr, other_expr]) - def __radd__(self, other): + def __radd__(self, other) -> Expr: from python_symb.Expressions.expr import Expr - return Expr('+', [other, self]) + from python_symb.MathTypes.operator_file import Add + other_expr = other if isinstance(other, Expr) else Expr(other) + self_expr = Expr(self) + return Expr(Add, [other_expr, self_expr]) - def __mul__(self, other): + def __mul__(self, other) -> Expr: from python_symb.Expressions.expr import Expr - return Expr('*', [self, other]) + from python_symb.MathTypes.operator_file import Mul + other_expr = other if isinstance(other, Expr) else Expr(other) + self_expr = Expr(self) + return Expr(Mul, [self_expr, other_expr]) - def __rmul__(self, other): + def __rmul__(self, other) -> Expr: from python_symb.Expressions.expr import Expr - return Expr('*', [other, self]) + from python_symb.MathTypes.operator_file import Mul + other_expr = other if isinstance(other, Expr) else Expr(other) + self_expr = Expr(self) + return Expr(Mul, [other_expr, self_expr]) class Var(Symbols): @@ -45,4 +59,15 @@ class Var(Symbols): super().__init__(name) self.__class__.instances[name] = self + def to_expr(self): + from python_symb.Expressions.expr import Expr + return Expr(self) + +def var(name: str) -> Expr: + """ + Create a variable, return Expr + """ + from python_symb.Expressions.expr import Expr + v = Var(name) + return Expr(v) diff --git a/python_symb/Parsing/parse.py b/python_symb/Parsing/parse.py index 754069e..976597e 100644 --- a/python_symb/Parsing/parse.py +++ b/python_symb/Parsing/parse.py @@ -4,7 +4,6 @@ from python_symb.MathTypes.operator_file import BinOperator, UnaryOperator from python_symb.MathTypes.symbols import Symbols, Var from python_symb.MathTypes.fraction import Fraction -x, y = Var('x'), Var('y') ParenthesisLeft = Symbols('(') ParenthesisRight = Symbols(')') @@ -103,7 +102,7 @@ def infix_str_to_postfix(expr): if __name__ == "__main__": - + x, y = Var('x'), Var('y') expr = "(x+7)*y+sin(24-2*(1-5))" prep = preprocess(expr) print(prep) diff --git a/python_symb/TreeModification/basic_modif.py b/python_symb/TreeModification/basic_modif.py new file mode 100644 index 0000000..ce71a39 --- /dev/null +++ b/python_symb/TreeModification/basic_modif.py @@ -0,0 +1,153 @@ +from python_symb.Expressions.expr import Expr +from python_symb.MathTypes.symbols import Var +from python_symb.MathTypes.operator_file import Operator, BinOperator, Add, Mul +from typing import Union + + +Number = Union[int, float] + + +def expand(expr: Expr) -> Expr: + """ + Expand an expression + + :param expr: expression to expand + :return: expanded expression + + example : + 5*(a+b) -> 5*a + 5*b + Expr(Mul, [5, Expr(Add, [Expr(a), Expr(b)])]) -> Expr(Add, [Expr(Mul, [5, Expr(a)]), Expr(Mul, [5, Expr(b)])]) + """ + + if expr.is_leaf: + return expr + + match expr: + case Expr(BinOperator() as Op1, [Expr(Op2, op2_children), right]) if Op2.name in Op1.properties.left_distributive: + return expand(Expr(Op2, [Expr(Op1, [expand(op2_child), expand(right)]) for op2_child in op2_children])) + + case Expr(BinOperator() as Op1, [left, Expr(Op2, op2_children)]) if Op2.name in Op1.properties.right_distributive: + return expand(Expr(Op2, [Expr(Op1, [expand(left), expand(op2_child)]) for op2_child in op2_children])) + + case Expr(BinOperator() as Op, [left, right]): + return Expr(Op, [expand(left), expand(right)]) + + return expr + + +def _regroup(expr: Expr, focus_op: BinOperator) -> Expr: + """ + regroup an expression, with the contraint that the value of expr is focus_op + Will be used to regroup an expression + + :param expr: expression to regroup + :param focus_op: operator to regroup + :return + x+x+x+x -> 4*x + Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(x)])])]) -> Expr(Mul, [4, Expr(x)]) + + with Mul == Add.repeated_op + """ + assert focus_op.repeated_op is not None, f'{focus_op} has no repeated_op' + assert expr.value == focus_op, f'{expr.value} is not a {focus_op}' + + # Motifs : Key : (Expr) -> Value : int + # represent number of times the expression appears in the expression, + # custom hash make for instance x+y and y+x the same when counting + motifs = {} + + def collect_motifs(expr: Expr): + match expr: + case Expr(BinOperator() as op, [left, right]) if op == focus_op: + collect_motifs(left) + collect_motifs(right) + case Expr(BinOperator() as op, [left, right]) if op == focus_op.repeated_op and isinstance(right.value, Number): + motifs[left] = motifs.get(expr, 0) + right.value + case Expr(BinOperator() as op, [left, right]) if op == focus_op.repeated_op and op.properties.commutative and isinstance(left.value, Number): + + motifs[right] = 1 if right not in motifs else motifs[right] + left.value + + case _: + motifs[expr] = 1 if expr not in motifs else motifs[expr] + 1 + + collect_motifs(expr) + tuple_motifs = list(motifs.items()) + + def reconstruct(tuple_motifs): + match tuple_motifs: + case [(expr, int(a))]: + if focus_op.repeated_op.properties.commutative: + return Expr(focus_op.repeated_op, [Expr(a), expr]) + return Expr(focus_op.repeated_op, [expr, Expr(a)]) + case [(expr, int(a)), *rest]: + if focus_op.repeated_op.properties.commutative: + return Expr(focus_op, [Expr(focus_op.repeated_op, [Expr(a), expr]), reconstruct(rest)]) + return Expr(focus_op, [Expr(focus_op.repeated_op, [expr, Expr(a)]), reconstruct(rest)]) + + return reconstruct(tuple_motifs) + +def regroup(expr: Expr, focus_op: BinOperator) -> Expr: + """ + Regroup an expression + + :param expr: expression to regroup + :param focus_op: operator to regroup + :return: regrouped expression + + example : + x+x+x+x -> 4*x + Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(Add, [Expr(x), Expr(x)])])]) -> Expr(Mul, [4, Expr(x)]) + """ + + if expr.is_leaf: + return expr + + match expr: + case Expr(BinOperator() as op, [left, right]) if op == focus_op: + return _regroup(expr, focus_op) + + case Expr(BinOperator() as op, [left, right]): + return Expr(op, [regroup(left, focus_op), regroup(right, focus_op)]) + + return expr + + +def test(): + + x, y = Var('x'), Var('y') + a, b = Var('a'), Var('b') + + def test_expand(): + expr = (x+y)*(x+y)*(x+y) + expr = expand(expr) + expr = regroup(expr, Add) + print(f"(x+y)*(x+y)*(x+y) -> {expr.to_infix_str()}") + expr = (x+y+a)*b + print(f"(x+y+a)*b -> {expand(expr).to_infix_str()}") + + def test_regroup(): + expr = x+2*x+y+y+2*y + print(f"x+2*x+y+y+2*y -> {regroup(expr, Add).to_infix_str()}") + + def test_power(): + expr = x*x*x + y*y*y + print(f"x*x*x -> {regroup(expr, Mul).to_infix_str()}") + + def test_all(): + expr = (x+y)*(x+y)*(x+y) + expanded_expr = expand(expr) + regrouped_expr = regroup(expanded_expr, Add) + print(f"(x+y)*(x+y)*(x+y) -> {regrouped_expr.to_infix_str()}") + + + + + test_expand() + test_regroup() + test_power() + #test_all() + +if __name__ == "__main__": + test() + + diff --git a/python_symb/TreeModification/expand.py b/python_symb/TreeModification/expand.py deleted file mode 100644 index 9422868..0000000 --- a/python_symb/TreeModification/expand.py +++ /dev/null @@ -1,14 +0,0 @@ -from python_symb.Expressions.expr import Expr -from python_symb.MathTypes.symbols import Var -from python_symb.MathTypes.operator_file import Operator, BinOperator, Add, Mul - -def expand(expr: Expr) -> Expr: - """ - Expand an expression - :param expr: expression to expand - :return: expanded expression - """ - - if expr.is_leaf: - return expr - diff --git a/python_symb/tree_visual.py b/python_symb/tree_visual.py index 1f52f27..19f122d 100644 --- a/python_symb/tree_visual.py +++ b/python_symb/tree_visual.py @@ -109,6 +109,7 @@ class Visual: self.create_circle(first_x, first_y, radius, "red") self.tree_canvas.create_text(first_x, first_y, text=str(tree.value)) + if __name__ == "__main__": Visual()