From 06f334164cc80cdd71defbe5e932873f5e9fb327 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 13:53:49 -0800 Subject: [PATCH 01/10] feat(rules): add MultiplicativeInverse rule - for handling / in expressions --- mathy_core/rules/__init__.py | 2 + mathy_core/rules/multiplicative_inverse.py | 105 ++++++++++++++++++ .../rules/multiplicative_inverse.test.json | 21 ++++ tests/test_rules.py | 42 +++++++ 4 files changed, 170 insertions(+) create mode 100644 mathy_core/rules/multiplicative_inverse.py create mode 100644 mathy_core/rules/multiplicative_inverse.test.json diff --git a/mathy_core/rules/__init__.py b/mathy_core/rules/__init__.py index c583ad7..da949db 100644 --- a/mathy_core/rules/__init__.py +++ b/mathy_core/rules/__init__.py @@ -4,6 +4,7 @@ from .constants_simplify import ConstantsSimplifyRule # noqa from .distributive_factor_out import DistributiveFactorOutRule # noqa from .distributive_multiply_across import DistributiveMultiplyRule # noqa +from .multiplicative_inverse import MultiplicativeInverseRule # noqa from .restate_subtraction import RestateSubtractionRule # noqa from .variable_multiply import VariableMultiplyRule # noqa @@ -14,6 +15,7 @@ "ConstantsSimplifyRule", "DistributiveFactorOutRule", "DistributiveMultiplyRule", + "MultiplicativeInverseRule", "RestateSubtractionRule", "VariableMultiplyRule", ) diff --git a/mathy_core/rules/multiplicative_inverse.py b/mathy_core/rules/multiplicative_inverse.py new file mode 100644 index 0000000..2bd3186 --- /dev/null +++ b/mathy_core/rules/multiplicative_inverse.py @@ -0,0 +1,105 @@ +from typing import Optional, cast + +from ..expressions import ( + AddExpression, + ConstantExpression, + DivideExpression, + EqualExpression, + MathExpression, + MultiplyExpression, + NegateExpression, + PowerExpression, + SubtractExpression, + VariableExpression, +) +from ..rule import BaseRule, ExpressionChangeRule + +_OP_DIVISION_EXPRESSION = "division-expression" +_OP_DIVISION_VARIABLE = "division-variable" +_OP_DIVISION_COMPLEX_DENOMINATOR = "division-complex-denominator" +_OP_DIVISION_NEGATIVE_DENOMINATOR = "division-negative-denominator" + + +class MultiplicativeInverseRule(BaseRule): + """Convert division operations to multiplication by the reciprocal.""" + + @property + def name(self) -> str: + return "Multiplicative Inverse" + + @property + def code(self) -> str: + return "MI" + + def get_type(self, node: MathExpression) -> Optional[str]: + """Determine the configuration of the tree for this transformation. + + Support different types of tree configurations based on the division operation: + - DivisionExpression is a division to be restated as multiplication by reciprocal + - DivisionVariable is a division by a variable + - DivisionComplexDenominator is a division by a complex expression + - DivisionNegativeDenominator is a division by a negative term + """ + is_division = isinstance(node, DivideExpression) + if not is_division: + return None + + # Division by a variable (e.g., (2 + 3z) / z) + if isinstance(node.right, VariableExpression): + return _OP_DIVISION_VARIABLE + + # Division where the denominator is a complex expression (e.g., (x^2 + 4x + 4) / (2x - 2)) + if isinstance(node.right, AddExpression) or isinstance( + node.right, SubtractExpression + ): + return _OP_DIVISION_COMPLEX_DENOMINATOR + + # Division where the denominator is negative (e.g., (2 + 3z) / -z) + if isinstance(node.right, NegateExpression): + return _OP_DIVISION_NEGATIVE_DENOMINATOR + + # If none of the above, it's a general division expression + return _OP_DIVISION_EXPRESSION + + def can_apply_to(self, node: MathExpression) -> bool: + tree_type = self.get_type(node) + return tree_type is not None + + def apply_to(self, node: MathExpression) -> ExpressionChangeRule: + change = super().apply_to(node) + tree_type = self.get_type(node) + assert tree_type is not None, "call can_apply_to before applying a rule" + change.save_parent() # connect result to node.parent + + # Handle the division based on the tree type + if tree_type == _OP_DIVISION_EXPRESSION: + result = MultiplyExpression( + node.left.clone(), + DivideExpression(ConstantExpression(1), node.right.clone()), + ) + + elif tree_type == _OP_DIVISION_VARIABLE: + # For division by a single variable, treat it the same as a general expression + reciprocal = DivideExpression(node.right.clone(), ConstantExpression(-1)) + result = MultiplyExpression(node.left.clone(), reciprocal) + + elif tree_type == _OP_DIVISION_COMPLEX_DENOMINATOR: + result = MultiplyExpression( + node.left.clone(), + DivideExpression(ConstantExpression(1), node.right.clone()), + ) + + elif tree_type == _OP_DIVISION_NEGATIVE_DENOMINATOR: + # For division by a negative denominator, negate the numerator and use the positive reciprocal + result = MultiplyExpression( + node.left.clone(), + DivideExpression(ConstantExpression(-1), node.right.get_child().clone()), + ) + + else: + raise NotImplementedError( + "Unsupported tree configuration for MultiplicativeInverseRule" + ) + + result.set_changed() # mark this node as changed for visualization + return change.done(result) diff --git a/mathy_core/rules/multiplicative_inverse.test.json b/mathy_core/rules/multiplicative_inverse.test.json new file mode 100644 index 0000000..03fc719 --- /dev/null +++ b/mathy_core/rules/multiplicative_inverse.test.json @@ -0,0 +1,21 @@ +{ + "valid": [ + { + "input": "(21x^3 - 35x^2) / 7x", + "output": "(21x^3 - 35x^2) * 1 / 7x" + }, + { + "input": "(x^2 + 4x + 4) / (2x - 2)", + "output": "(x^2 + 4x + 4) * 1 / (2x - 2)" + }, + { + "input": "(2 + 3x) / 2x", + "output": "(2 + 3x) * 1 / 2x" + }, + { + "input": "((x + 1) / -(y + 2))", + "output": "(x + 1) * -1 / (y + 2)" + } + ], + "invalid": [] +} diff --git a/tests/test_rules.py b/tests/test_rules.py index 8b43d87..011bcb4 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -1,3 +1,4 @@ +from mathy_core import MathExpression from mathy_core.parser import ExpressionParser from mathy_core.rules import ( AssociativeSwapRule, @@ -8,6 +9,7 @@ DistributiveMultiplyRule, RestateSubtractionRule, VariableMultiplyRule, + MultiplicativeInverseRule, ) from mathy_core.testing import run_rule_tests @@ -54,6 +56,13 @@ def debug(ex): run_rule_tests("restate_subtraction", RestateSubtractionRule, debug) +def test_rules_multiplicative_inverse(): + def debug(ex): + pass + + run_rule_tests("multiplicative_inverse", MultiplicativeInverseRule, debug) + + def test_rules_variable_multiply(): def debug(ex): pass @@ -80,3 +89,36 @@ def test_rules_rule_can_apply_to(): ] for action in available_actions: assert type(action.can_apply_to(expression)) == bool + + +def debug_expressions(one: MathExpression, two: MathExpression): + one_inputs = [f"{e.__class__.__name__}" for e in one.to_list()] + two_inputs = [f"{e.__class__.__name__}" for e in two.to_list()] + print("one: ", one.raw, one_inputs) + print("two: ", two.raw, two_inputs) + + +def test_rules_rule_restate_subtraction_corner_case_1(): + parser = ExpressionParser() + expression = parser.parse("4x - 3y + 3x") + + restate = RestateSubtractionRule() + dfo = DistributiveFactorOutRule() + commute = CommutativeSwapRule(preferred=False) + + node = restate.find_node(expression) + assert node is not None, "should find node" + assert restate.can_apply_to(node), "should be able to apply" + change = restate.apply_to(node) + assert change.result is not None, "should get change" + assert change.result.get_root().raw == "4x + -3y + 3x" + + change = commute.apply_to(change.result.get_root()) + assert change.result is not None, "should get change" + node = dfo.find_node(change.result.get_root()) + assert node is not None, "should find node" + assert dfo.can_apply_to(node), "should be able to apply" + change = dfo.apply_to(node) + assert change.result is not None, "should get change" + node = change.result.get_root() + assert node.raw == "(4 + 3) * x + -3y" From f1b5bc2010aa4186ef8d47b650576318f2058af3 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:14:22 -0800 Subject: [PATCH 02/10] chore: cleanup from review --- mathy_core/rules/multiplicative_inverse.py | 36 +++------------------- tests/test_rules.py | 36 +--------------------- 2 files changed, 5 insertions(+), 67 deletions(-) diff --git a/mathy_core/rules/multiplicative_inverse.py b/mathy_core/rules/multiplicative_inverse.py index 2bd3186..8942d1f 100644 --- a/mathy_core/rules/multiplicative_inverse.py +++ b/mathy_core/rules/multiplicative_inverse.py @@ -1,22 +1,15 @@ -from typing import Optional, cast +from typing import Optional from ..expressions import ( - AddExpression, ConstantExpression, DivideExpression, - EqualExpression, MathExpression, MultiplyExpression, NegateExpression, - PowerExpression, - SubtractExpression, - VariableExpression, ) from ..rule import BaseRule, ExpressionChangeRule _OP_DIVISION_EXPRESSION = "division-expression" -_OP_DIVISION_VARIABLE = "division-variable" -_OP_DIVISION_COMPLEX_DENOMINATOR = "division-complex-denominator" _OP_DIVISION_NEGATIVE_DENOMINATOR = "division-negative-denominator" @@ -36,24 +29,12 @@ def get_type(self, node: MathExpression) -> Optional[str]: Support different types of tree configurations based on the division operation: - DivisionExpression is a division to be restated as multiplication by reciprocal - - DivisionVariable is a division by a variable - - DivisionComplexDenominator is a division by a complex expression - DivisionNegativeDenominator is a division by a negative term """ is_division = isinstance(node, DivideExpression) if not is_division: return None - # Division by a variable (e.g., (2 + 3z) / z) - if isinstance(node.right, VariableExpression): - return _OP_DIVISION_VARIABLE - - # Division where the denominator is a complex expression (e.g., (x^2 + 4x + 4) / (2x - 2)) - if isinstance(node.right, AddExpression) or isinstance( - node.right, SubtractExpression - ): - return _OP_DIVISION_COMPLEX_DENOMINATOR - # Division where the denominator is negative (e.g., (2 + 3z) / -z) if isinstance(node.right, NegateExpression): return _OP_DIVISION_NEGATIVE_DENOMINATOR @@ -78,22 +59,13 @@ def apply_to(self, node: MathExpression) -> ExpressionChangeRule: DivideExpression(ConstantExpression(1), node.right.clone()), ) - elif tree_type == _OP_DIVISION_VARIABLE: - # For division by a single variable, treat it the same as a general expression - reciprocal = DivideExpression(node.right.clone(), ConstantExpression(-1)) - result = MultiplyExpression(node.left.clone(), reciprocal) - - elif tree_type == _OP_DIVISION_COMPLEX_DENOMINATOR: - result = MultiplyExpression( - node.left.clone(), - DivideExpression(ConstantExpression(1), node.right.clone()), - ) - elif tree_type == _OP_DIVISION_NEGATIVE_DENOMINATOR: # For division by a negative denominator, negate the numerator and use the positive reciprocal result = MultiplyExpression( node.left.clone(), - DivideExpression(ConstantExpression(-1), node.right.get_child().clone()), + DivideExpression( + ConstantExpression(-1), node.right.get_child().clone() + ), ) else: diff --git a/tests/test_rules.py b/tests/test_rules.py index 011bcb4..14b18df 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -1,4 +1,3 @@ -from mathy_core import MathExpression from mathy_core.parser import ExpressionParser from mathy_core.rules import ( AssociativeSwapRule, @@ -7,9 +6,9 @@ ConstantsSimplifyRule, DistributiveFactorOutRule, DistributiveMultiplyRule, + MultiplicativeInverseRule, RestateSubtractionRule, VariableMultiplyRule, - MultiplicativeInverseRule, ) from mathy_core.testing import run_rule_tests @@ -89,36 +88,3 @@ def test_rules_rule_can_apply_to(): ] for action in available_actions: assert type(action.can_apply_to(expression)) == bool - - -def debug_expressions(one: MathExpression, two: MathExpression): - one_inputs = [f"{e.__class__.__name__}" for e in one.to_list()] - two_inputs = [f"{e.__class__.__name__}" for e in two.to_list()] - print("one: ", one.raw, one_inputs) - print("two: ", two.raw, two_inputs) - - -def test_rules_rule_restate_subtraction_corner_case_1(): - parser = ExpressionParser() - expression = parser.parse("4x - 3y + 3x") - - restate = RestateSubtractionRule() - dfo = DistributiveFactorOutRule() - commute = CommutativeSwapRule(preferred=False) - - node = restate.find_node(expression) - assert node is not None, "should find node" - assert restate.can_apply_to(node), "should be able to apply" - change = restate.apply_to(node) - assert change.result is not None, "should get change" - assert change.result.get_root().raw == "4x + -3y + 3x" - - change = commute.apply_to(change.result.get_root()) - assert change.result is not None, "should get change" - node = dfo.find_node(change.result.get_root()) - assert node is not None, "should find node" - assert dfo.can_apply_to(node), "should be able to apply" - change = dfo.apply_to(node) - assert change.result is not None, "should get change" - node = change.result.get_root() - assert node.raw == "(4 + 3) * x + -3y" From ecd8232c003e4d9d144daa5afd4bb0ea75fb589d Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:20:11 -0800 Subject: [PATCH 03/10] chore: fix lint from inadequate typing --- mathy_core/rules/multiplicative_inverse.py | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mathy_core/rules/multiplicative_inverse.py b/mathy_core/rules/multiplicative_inverse.py index 8942d1f..d54f309 100644 --- a/mathy_core/rules/multiplicative_inverse.py +++ b/mathy_core/rules/multiplicative_inverse.py @@ -52,25 +52,25 @@ def apply_to(self, node: MathExpression) -> ExpressionChangeRule: assert tree_type is not None, "call can_apply_to before applying a rule" change.save_parent() # connect result to node.parent - # Handle the division based on the tree type - if tree_type == _OP_DIVISION_EXPRESSION: - result = MultiplyExpression( - node.left.clone(), - DivideExpression(ConstantExpression(1), node.right.clone()), - ) + assert node.left is not None, "Division must have a left child" + assert node.right is not None, "Division must have a right child" - elif tree_type == _OP_DIVISION_NEGATIVE_DENOMINATOR: + if tree_type == _OP_DIVISION_NEGATIVE_DENOMINATOR: # For division by a negative denominator, negate the numerator and use the positive reciprocal + assert isinstance( + node.right, NegateExpression + ), "Right child must be a NegateExpression" + child = node.right.get_child() + assert child is not None, "NegateExpression must have a child" result = MultiplyExpression( node.left.clone(), - DivideExpression( - ConstantExpression(-1), node.right.get_child().clone() - ), + DivideExpression(ConstantExpression(-1), child.clone()), ) - + # Handle the division based on the tree type else: - raise NotImplementedError( - "Unsupported tree configuration for MultiplicativeInverseRule" + result = MultiplyExpression( + node.left.clone(), + DivideExpression(ConstantExpression(1), node.right.clone()), ) result.set_changed() # mark this node as changed for visualization From e49a44862e10d1b472214ecb857075fa69718195 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:25:58 -0800 Subject: [PATCH 04/10] chore: more cleanup from review --- mathy_core/rules/multiplicative_inverse.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathy_core/rules/multiplicative_inverse.py b/mathy_core/rules/multiplicative_inverse.py index d54f309..1aefee4 100644 --- a/mathy_core/rules/multiplicative_inverse.py +++ b/mathy_core/rules/multiplicative_inverse.py @@ -28,7 +28,7 @@ def get_type(self, node: MathExpression) -> Optional[str]: """Determine the configuration of the tree for this transformation. Support different types of tree configurations based on the division operation: - - DivisionExpression is a division to be restated as multiplication by reciprocal + - DivisionExpression restated as multiplication by reciprocal - DivisionNegativeDenominator is a division by a negative term """ is_division = isinstance(node, DivideExpression) @@ -55,8 +55,8 @@ def apply_to(self, node: MathExpression) -> ExpressionChangeRule: assert node.left is not None, "Division must have a left child" assert node.right is not None, "Division must have a right child" + # For negative denominator, negate the numerator and use the positive reciprocal if tree_type == _OP_DIVISION_NEGATIVE_DENOMINATOR: - # For division by a negative denominator, negate the numerator and use the positive reciprocal assert isinstance( node.right, NegateExpression ), "Right child must be a NegateExpression" @@ -66,7 +66,7 @@ def apply_to(self, node: MathExpression) -> ExpressionChangeRule: node.left.clone(), DivideExpression(ConstantExpression(-1), child.clone()), ) - # Handle the division based on the tree type + # Multiply the numerator by the reciprocal of the denominator else: result = MultiplyExpression( node.left.clone(), From d194eb9c64564d4d34da0094369b3f5e7c06a613 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:28:56 -0800 Subject: [PATCH 05/10] chore: update black version --- mathy_core/problems.py | 1 + mathy_core/rules/commutative_swap.py | 1 + mathy_core/rules/distributive_factor_out.py | 1 + 3 files changed, 3 insertions(+) diff --git a/mathy_core/problems.py b/mathy_core/problems.py index 43aaa58..0033cf6 100644 --- a/mathy_core/problems.py +++ b/mathy_core/problems.py @@ -3,6 +3,7 @@ Utility functions for helping generate input problems. """ + import random from dataclasses import dataclass from typing import Any, List, Optional, Set, Tuple, TypeVar, Union, cast diff --git a/mathy_core/rules/commutative_swap.py b/mathy_core/rules/commutative_swap.py index e3c849d..5fbb23d 100644 --- a/mathy_core/rules/commutative_swap.py +++ b/mathy_core/rules/commutative_swap.py @@ -30,6 +30,7 @@ class CommutativeSwapRule(BaseRule): / \ / \ a b b a """ + preferred: bool def __init__(self, preferred: bool = True): diff --git a/mathy_core/rules/distributive_factor_out.py b/mathy_core/rules/distributive_factor_out.py index 3e853e9..e6b2674 100644 --- a/mathy_core/rules/distributive_factor_out.py +++ b/mathy_core/rules/distributive_factor_out.py @@ -38,6 +38,7 @@ class DistributiveFactorOutRule(BaseRule): / \ / \ / \ a b a c b c """ + constants: bool def __init__(self, constants: bool = False): From d328c5a7a7b2a00e03db6e5cead10811fd732590 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:38:46 -0800 Subject: [PATCH 06/10] test(rules): add name/code to generic rule testing harness --- mathy_core/testing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mathy_core/testing.py b/mathy_core/testing.py index b6e17cd..4dc14ab 100644 --- a/mathy_core/testing.py +++ b/mathy_core/testing.py @@ -71,6 +71,8 @@ def run_rule_tests( if callback is not None: callback(ex) rule = init_rule_for_test(ex, rule_class) + assert rule.name is not None, "Rule must have a name" + assert rule.code is not None, "Rule must have a code" expression = parser.parse(ex["input"]).clone() before = expression.clone().get_root() print(ex) From d48db848489836e65576e1f705ad454617065c60 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:58:07 -0800 Subject: [PATCH 07/10] chore: add api docstring --- mathy_core/rules/multiplicative_inverse.py | 18 +++++++- .../docs/api/rules/distributive_factor_out.md | 2 + .../docs/api/rules/multiplicative_inverse.md | 38 +++++++++++++++++ website/docs/api/tree.md | 16 ++++---- website/mkdocs.yml | 41 ++++++++++--------- 5 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 website/docs/api/rules/multiplicative_inverse.md diff --git a/mathy_core/rules/multiplicative_inverse.py b/mathy_core/rules/multiplicative_inverse.py index 1aefee4..5c10898 100644 --- a/mathy_core/rules/multiplicative_inverse.py +++ b/mathy_core/rules/multiplicative_inverse.py @@ -14,7 +14,23 @@ class MultiplicativeInverseRule(BaseRule): - """Convert division operations to multiplication by the reciprocal.""" + r"""Multiplicative Inverse Property + `a / b = a * (1/b)` + + The multiplicative inverse property involves converting division operations + into multiplication by the reciprocal. This transformation can simplify the + structure of mathematical expressions and prepare them for further simplification. + + **Convert Division to Multiplication by Reciprocal** + + This handles the `a / b` conversion to `a * (1 / b)`. + + **Handle Division by a Negative Denominator** + + When the denominator is negative, the rule handles it by negating the + numerator and converting the division into multiplication by the positive + reciprocal of the denominator. + """ @property def name(self) -> str: diff --git a/website/docs/api/rules/distributive_factor_out.md b/website/docs/api/rules/distributive_factor_out.md index e363a91..176d1e0 100644 --- a/website/docs/api/rules/distributive_factor_out.md +++ b/website/docs/api/rules/distributive_factor_out.md @@ -72,6 +72,7 @@ DistributiveFactorOutRule.get_type( Determine the configuration of the tree for this transformation. Support the three types of tree configurations: + - Simple is where the node's left and right children are exactly terms linked by an add operation. - Chained Left is where the node's left child is a term, but the right @@ -82,6 +83,7 @@ Support the three types of tree configurations: of the child add node is the target. Structure: + - Simple * node(add),node.left(term),node.right(term) - Chained Left diff --git a/website/docs/api/rules/multiplicative_inverse.md b/website/docs/api/rules/multiplicative_inverse.md new file mode 100644 index 0000000..45ce74f --- /dev/null +++ b/website/docs/api/rules/multiplicative_inverse.md @@ -0,0 +1,38 @@ +```python + +import mathy_core.rules.multiplicative_inverse +``` + +## MultiplicativeInverseRule +```python +MultiplicativeInverseRule(self, args, kwargs) +``` +Multiplicative Inverse Property +`a / b = a * (1/b)` + +The multiplicative inverse property involves converting division operations +into multiplication by the reciprocal. This transformation can simplify the +structure of mathematical expressions and prepare them for further simplification. + +**Convert Division to Multiplication by Reciprocal** + +This handles the `a / b` conversion to `a * (1 / b)`. + +**Handle Division by a Negative Denominator** + +When the denominator is negative, the rule handles it by negating the +numerator and converting the division into multiplication by the positive +reciprocal of the denominator. + +### get_type +```python +MultiplicativeInverseRule.get_type( + self, + node: mathy_core.expressions.MathExpression, +) -> Optional[str] +``` +Determine the configuration of the tree for this transformation. + +Support different types of tree configurations based on the division operation: +- DivisionExpression restated as multiplication by reciprocal +- DivisionNegativeDenominator is a division by a negative term diff --git a/website/docs/api/tree.md b/website/docs/api/tree.md index 9b95d54..98458ed 100644 --- a/website/docs/api/tree.md +++ b/website/docs/api/tree.md @@ -6,10 +6,10 @@ import mathy_core.tree ## BinaryTreeNode ```python BinaryTreeNode( - self, - left: Optional[BinaryTreeNode] = None, - right: Optional[BinaryTreeNode] = None, - parent: Optional[BinaryTreeNode] = None, + self: ~NodeType, + left: Optional[~NodeType] = None, + right: Optional[~NodeType] = None, + parent: Optional[~NodeType] = None, id: Optional[str] = None, ) ``` @@ -37,7 +37,7 @@ BinaryTreeNode.get_root(self: ~NodeType) -> ~NodeType Return the root element of this tree ### get_root_side ```python -BinaryTreeNode.get_root_side(self: 'BinaryTreeNode') -> Literal['left', 'right'] +BinaryTreeNode.get_root_side(self: ~NodeType) -> Literal['left', 'right'] ``` Return the side of the tree that this node lives on ### get_sibling @@ -50,7 +50,7 @@ has no sibling, the return value will be None. ```python BinaryTreeNode.get_side( self, - child: Optional[BinaryTreeNode], + child: Optional[~NodeType], ) -> Literal['left', 'right'] ``` Determine whether the given `child` is the left or right child of this @@ -72,7 +72,7 @@ the order of the nodes in the tree. ```python BinaryTreeNode.set_left( self: ~NodeType, - child: Optional[BinaryTreeNode] = None, + child: Optional[~NodeType] = None, clear_old_child_parent: bool = False, ) -> ~NodeType ``` @@ -81,7 +81,7 @@ Set the left node to the passed `child` ```python BinaryTreeNode.set_right( self: ~NodeType, - child: Optional[BinaryTreeNode] = None, + child: Optional[~NodeType] = None, clear_old_child_parent: bool = False, ) -> ~NodeType ``` diff --git a/website/mkdocs.yml b/website/mkdocs.yml index 5bc1051..2724ed5 100644 --- a/website/mkdocs.yml +++ b/website/mkdocs.yml @@ -34,6 +34,7 @@ nav: - Constants Simplify: api/rules/constants_simplify.md - Distributive Factor Out: api/rules/distributive_factor_out.md - Distributive Multiply Across: api/rules/distributive_multiply_across.md + - Multiplicative Inverse: api/rules/multiplicative_inverse.md - Restate Subtraction: api/rules/restate_subtraction.md - Variable Multiply: api/rules/variable_multiply.md - Testing: api/testing.md @@ -73,26 +74,26 @@ theme: - content.tooltips favicon: img/favicon/favicon-16x16.png palette: - - media: "(prefers-color-scheme)" - primary: purple - accent: deep-purple - toggle: - icon: material/brightness-auto - name: Switch to light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: purple - accent: deep-purple - toggle: - icon: material/brightness-7 - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: purple - accent: deep-purple - toggle: - icon: material/brightness-4 - name: Switch to system preference + - media: (prefers-color-scheme) + primary: purple + accent: deep-purple + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: purple + accent: deep-purple + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: purple + accent: deep-purple + toggle: + icon: material/brightness-4 + name: Switch to system preference highlightjs: true hljs_languages: From 9663bbae0de80297b7a8bb7b27f355e97343ffc0 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 15:58:31 -0800 Subject: [PATCH 08/10] chore(ci): update docs script to use venv and latest mathy_core --- website/tools/docs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/tools/docs.py b/website/tools/docs.py index 8c99350..d49a4e2 100644 --- a/website/tools/docs.py +++ b/website/tools/docs.py @@ -74,7 +74,11 @@ def h1_to_h2(original_md: str): def render_docs(src_rel_path, src_file, to_file, modifier="++"): insert = "." + src_rel_path if src_rel_path not in ["", "."] else "" namespace = f"mathy_core{insert}.{src_file.stem}{modifier}" - args = ["mathy_pydoc", "--plain", namespace] + args = [ + parent_folder_path / ".." / ".env" / "bin" / "mathy_pydoc", + "--plain", + namespace, + ] if not to_file.parent.exists(): to_file.parent.mkdir(parents=True) call_result = check_output(args, cwd=parent_folder_path).decode("utf-8") From 3fa8012d26096df8080e29c77550496c2f881c52 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 16:17:08 -0800 Subject: [PATCH 09/10] docs(rules): better multiplicative inverse docs --- mathy_core/rules/multiplicative_inverse.md | 17 ++++++++++ mathy_core/rules/multiplicative_inverse.py | 18 +---------- .../rules/multiplicative_inverse.test.json | 4 +++ .../docs/api/rules/multiplicative_inverse.md | 32 +++++++++++-------- 4 files changed, 41 insertions(+), 30 deletions(-) create mode 100644 mathy_core/rules/multiplicative_inverse.md diff --git a/mathy_core/rules/multiplicative_inverse.md b/mathy_core/rules/multiplicative_inverse.md new file mode 100644 index 0000000..689c6af --- /dev/null +++ b/mathy_core/rules/multiplicative_inverse.md @@ -0,0 +1,17 @@ +The `Multiplicative Inverse` rule converts division operations into multiplication by the reciprocal. This transformation can simplify the structure of mathematical expressions and prepare them for further simplification. + +This rule is expressed with the equation `a / b = a * (1 / b)` + +**Convert Division to Multiplication by Reciprocal** + +This handles the `a / b` conversion to `a * (1 / b)`. + +**Handle Division by a Negative Denominator** + +When the denominator is negative, the rule handles it by negating the numerator and converting the division into multiplication by the positive reciprocal of the denominator. + +This handles the `4 / -(2 + 3)` conversion to `4 * -1 / (2 + 3)` + +### Examples + +`rule_tests:multiplicative_inverse` diff --git a/mathy_core/rules/multiplicative_inverse.py b/mathy_core/rules/multiplicative_inverse.py index 5c10898..1aefee4 100644 --- a/mathy_core/rules/multiplicative_inverse.py +++ b/mathy_core/rules/multiplicative_inverse.py @@ -14,23 +14,7 @@ class MultiplicativeInverseRule(BaseRule): - r"""Multiplicative Inverse Property - `a / b = a * (1/b)` - - The multiplicative inverse property involves converting division operations - into multiplication by the reciprocal. This transformation can simplify the - structure of mathematical expressions and prepare them for further simplification. - - **Convert Division to Multiplication by Reciprocal** - - This handles the `a / b` conversion to `a * (1 / b)`. - - **Handle Division by a Negative Denominator** - - When the denominator is negative, the rule handles it by negating the - numerator and converting the division into multiplication by the positive - reciprocal of the denominator. - """ + """Convert division operations to multiplication by the reciprocal.""" @property def name(self) -> str: diff --git a/mathy_core/rules/multiplicative_inverse.test.json b/mathy_core/rules/multiplicative_inverse.test.json index 03fc719..a3ad1b9 100644 --- a/mathy_core/rules/multiplicative_inverse.test.json +++ b/mathy_core/rules/multiplicative_inverse.test.json @@ -1,5 +1,9 @@ { "valid": [ + { + "input": "4 / -(2 + 3)", + "output": "4 * -1 / (2 + 3)" + }, { "input": "(21x^3 - 35x^2) / 7x", "output": "(21x^3 - 35x^2) * 1 / 7x" diff --git a/website/docs/api/rules/multiplicative_inverse.md b/website/docs/api/rules/multiplicative_inverse.md index 45ce74f..21c0e7d 100644 --- a/website/docs/api/rules/multiplicative_inverse.md +++ b/website/docs/api/rules/multiplicative_inverse.md @@ -2,17 +2,9 @@ import mathy_core.rules.multiplicative_inverse ``` +The `Multiplicative Inverse` rule converts division operations into multiplication by the reciprocal. This transformation can simplify the structure of mathematical expressions and prepare them for further simplification. -## MultiplicativeInverseRule -```python -MultiplicativeInverseRule(self, args, kwargs) -``` -Multiplicative Inverse Property -`a / b = a * (1/b)` - -The multiplicative inverse property involves converting division operations -into multiplication by the reciprocal. This transformation can simplify the -structure of mathematical expressions and prepare them for further simplification. +This rule is expressed with the equation `a / b = a * (1 / b)` **Convert Division to Multiplication by Reciprocal** @@ -20,10 +12,23 @@ This handles the `a / b` conversion to `a * (1 / b)`. **Handle Division by a Negative Denominator** -When the denominator is negative, the rule handles it by negating the -numerator and converting the division into multiplication by the positive -reciprocal of the denominator. +When the denominator is negative, the rule handles it by negating the numerator and converting the division into multiplication by the positive reciprocal of the denominator. + +This handles the `4 / -(2 + 3)` conversion to `4 * -1 / (2 + 3)` + +### Examples + +`rule_tests:multiplicative_inverse` + +## API + + +## MultiplicativeInverseRule +```python +MultiplicativeInverseRule(self, args, kwargs) +``` +Convert division operations to multiplication by the reciprocal. ### get_type ```python MultiplicativeInverseRule.get_type( @@ -36,3 +41,4 @@ Determine the configuration of the tree for this transformation. Support different types of tree configurations based on the division operation: - DivisionExpression restated as multiplication by reciprocal - DivisionNegativeDenominator is a division by a negative term + From 3743b4f55321215210db74f692567f6c59fa0e80 Mon Sep 17 00:00:00 2001 From: Justin DuJardin Date: Mon, 5 Feb 2024 16:21:51 -0800 Subject: [PATCH 10/10] docs: fix variable multiply markdown name - oops, the md was being ignored multiply vs multiplication. naming is hard. consistency is also hard. --- ...multiplication.md => variable_multiply.md} | 14 ++-- website/docs/api/rules/variable_multiply.md | 66 +++++++++++++++++++ website/requirements.txt | 1 - 3 files changed, 73 insertions(+), 8 deletions(-) rename mathy_core/rules/{variable_multiplication.md => variable_multiply.md} (66%) diff --git a/mathy_core/rules/variable_multiplication.md b/mathy_core/rules/variable_multiply.md similarity index 66% rename from mathy_core/rules/variable_multiplication.md rename to mathy_core/rules/variable_multiply.md index 80c80e8..8485248 100644 --- a/mathy_core/rules/variable_multiplication.md +++ b/mathy_core/rules/variable_multiply.md @@ -2,7 +2,7 @@ The `Variable Multiplication` rule restates `x^b * x^d` as `x^(b + d)`, which is !!! note - This rule can only be applied when the nodes have matching variable bases. This means that `x * y` cannot be combined, but `x * x` can be. + This rule can only be applied when the nodes have matching variable bases. This means that `x * y` cannot be combined, but `x * x` can be. ### Transformations @@ -10,13 +10,13 @@ Both implicit and explicit variable powers are recognized in this transformation !!! info "Help Wanted" - The current variable multiply rule leaves out a case where there is a power - raised to another power, they can be combined by multiplying the exponents - together. + The current variable multiply rule leaves out a case where there is a power + raised to another power, they can be combined by multiplying the exponents + together. + + For example: `x^(2^2) = x^4` - For example: `x^(2^2) = x^4` - - If you would like to help out with by updating this rule [open an issue here](https://github.com/justindujardin/mathy/issues/new?title=VariableMultiplyRaisePowerToPower){target=\_blank} + If you would like to help out with by updating this rule [open an issue here](https://github.com/justindujardin/mathy/issues/new?title=VariableMultiplyRaisePowerToPower){target=\_blank} #### Explicit powers diff --git a/website/docs/api/rules/variable_multiply.md b/website/docs/api/rules/variable_multiply.md index 22cf447..f49b585 100644 --- a/website/docs/api/rules/variable_multiply.md +++ b/website/docs/api/rules/variable_multiply.md @@ -2,6 +2,71 @@ import mathy_core.rules.variable_multiply ``` +The `Variable Multiplication` rule restates `x^b * x^d` as `x^(b + d)`, which isolates the exponents attached to the variables so they can be combined. + +!!! note + + This rule can only be applied when the nodes have matching variable bases. This means that `x * y` cannot be combined, but `x * x` can be. + +### Transformations + +Both implicit and explicit variable powers are recognized in this transformation. + +!!! info "Help Wanted" + + The current variable multiply rule leaves out a case where there is a power + raised to another power, they can be combined by multiplying the exponents + together. + + For example: `x^(2^2) = x^4` + + If you would like to help out with by updating this rule [open an issue here](https://github.com/justindujardin/mathy/issues/new?title=VariableMultiplyRaisePowerToPower){target=\_blank} + +#### Explicit powers + +In the simplest case, both variables have explicit exponents. + +Examples: `x^b * x^d = x^(b+d)` + +- `42x^2 * x^3` becomes `42x^(2 + 3)` +- `x^1 * x^7` becomes `x^(1 + 8)` + +``` + * + / \ + / \ ^ + / \ = / \ + ^ ^ x + + / \ / \ / \ + x b x d b d +``` + +#### Implicit powers + +When not explicitly stated, a variable has an implicit power of being raised to the 1, and this form is identified. + +Examples: `x * x^d = x^(1 + d)` + +- `42x * x^3` becomes `42x^(1 + 3)` +- `x * x` becomes `x^(1 + 1)` + +``` + * + / \ + / \ ^ + / \ = / \ + x ^ x + + / \ / \ + x d 1 d +``` + +### Examples + +`rule_tests:variable_multiply` + + +## API + ## VariableMultiplyRule ```python @@ -63,3 +128,4 @@ Support two types of tree configurations: Structure: - Simple node(mult),node.left(term),node.right(term) - Chained node(mult),node.left(term),node.right(mult),node.right.left(term) + diff --git a/website/requirements.txt b/website/requirements.txt index b9b4443..00e9468 100644 --- a/website/requirements.txt +++ b/website/requirements.txt @@ -14,7 +14,6 @@ ruamel.yaml # for converting snippets to ipynb notebooks nbformat mathy_pydoc>=0.7.18 -mathy_core ../ git+https://github.com/mathy/mathy_mkdocs.git # TODO: remove this when published gym \ No newline at end of file