diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index bd42e7e74..0313ceb94 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -10,7 +10,6 @@ import sympy -from mathics.builtin.inference import get_assumptions_list from mathics.builtin.numeric import Abs from mathics.builtin.scoping import dynamic_scoping from mathics.core.atoms import ( @@ -72,6 +71,7 @@ SymbolUndefined, ) from mathics.eval.arithmetic import eval_Sign +from mathics.eval.inference import get_assumptions_list from mathics.eval.nevaluator import eval_N # This tells documentation how to sort this module diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 6fb082bcf..51c0a0591 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -17,7 +17,6 @@ import sympy -from mathics.builtin.inference import evaluate_predicate from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping from mathics.core.atoms import Integer, Integer0, Integer1, Number, RationalOneHalf @@ -63,7 +62,10 @@ SymbolTable, SymbolTanh, ) -from mathics.eval.numbers.algebra.simplify import default_complexity_function +from mathics.eval.numbers.algebra.simplify import ( + default_complexity_function, + eval_Simplify, +) from mathics.eval.numbers.numbers import cancel, sympy_factor from mathics.eval.parts import walk_parts from mathics.eval.patterns import match @@ -1496,7 +1498,10 @@ def eval_list(self, expr, vars, evaluation): # FullSimplify class Simplify(Builtin): r""" - :WMA link: + :SymPy: + https://docs.sympy.org/latest/modules/simplify + /simplify.html, + :WMA: https://reference.wolfram.com/language/ref/Simplify.html
@@ -1605,65 +1610,9 @@ def eval(self, expr, evaluation, options={}): {"System`$Assumptions": assumptions}, evaluation, ) - return self.do_apply(expr, evaluation, options) - - def do_apply(self, expr, evaluation, options={}): - # Check first if we are dealing with a logic expression... - if expr in (SymbolTrue, SymbolFalse, SymbolList): - return expr - - # ``evaluate_predicate`` tries to reduce expr taking into account - # the assumptions established in ``$Assumptions``. - expr = evaluate_predicate(expr, evaluation) - - # If we get an atom, return it. - if isinstance(expr, Atom): - return expr - - # Now, try to simplify the elements. - # TODO: Consider to move this step inside ``evaluate_predicate``. - # Notice that here we want to pass through the full evaluation process - # to use all the defined rules... - name = self.get_name() - symbol_name = Symbol(name) - elements = [ - Expression(symbol_name, element).evaluate(evaluation) - for element in expr._elements - ] - head = Expression(symbol_name, expr.get_head()).evaluate(evaluation) - expr = Expression(head, *elements) - # At this point, we used all the tools available in Mathics. - # If the expression has a sympy form, try to use it. - # Now, convert the expression to sympy - sympy_expr = expr.to_sympy() - # If the expression cannot be handled by Sympy, just return it. - if sympy_expr is None: - return expr - # Now, try to simplify using sympy - complexity_function = options.get("System`ComplexityFunction", None) - if complexity_function is None or complexity_function is SymbolAutomatic: - - def _default_complexity_function(x): - return default_complexity_function(from_sympy(x)) - - complexity_function = _default_complexity_function - else: - if isinstance(complexity_function, (Expression, Symbol)): - _complexity_function = complexity_function - complexity_function = ( - lambda x: Expression(_complexity_function, from_sympy(x)) - .evaluate(evaluation) - .to_python() - ) - - # At this point, ``complexity_function`` is a function that takes a - # sympy expression and returns an integer. - sympy_result = sympy.simplify(sympy_expr, measure=complexity_function) - - # and bring it back - result = from_sympy(sympy_result).evaluate(evaluation) - return result + symbol_name = Symbol(self.get_name()) + return eval_Simplify(symbol_name, expr, evaluation, options) class FullSimplify(Simplify): diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 1d5a65d32..27f283f69 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -15,7 +15,6 @@ import sympy -from mathics.builtin.inference import evaluate_predicate from mathics.core.atoms import ( Complex, Integer, @@ -52,6 +51,7 @@ eval_RealSign, eval_Sign, ) +from mathics.eval.inference import evaluate_predicate from mathics.eval.nevaluator import eval_NValues @@ -319,7 +319,10 @@ def eval_N(self, expr, evaluation: Evaluation): class Piecewise(SympyFunction): """ - :WMA link:https://reference.wolfram.com/language/ref/Piecewise.html + :SymPy: + https://docs.sympy.org/latest/modules/functions + /elementary.html#piecewise, + :WMA:https://reference.wolfram.com/language/ref/Piecewise.html
'Piecewise[{{expr1, cond1}, ...}]' diff --git a/mathics/builtin/inference.py b/mathics/eval/inference.py similarity index 95% rename from mathics/builtin/inference.py rename to mathics/eval/inference.py index b34beb384..1f1e10eab 100644 --- a/mathics/builtin/inference.py +++ b/mathics/eval/inference.py @@ -3,8 +3,7 @@ Inference Functions """ -no_doc = "no doc" - +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.parser import parse_builtin_rule from mathics.core.parser.util import SystemDefinitions @@ -14,11 +13,21 @@ # TODO: Extend these rules? +no_doc = "no doc" + + +def debug_logical_expr(pref, expr, evaluation: Evaluation): + print( + pref, expr + ) # expr.format(evaluation,"OutputForm").boxes_to_text(evaluation=evaluation)) + + +def null_debug_logical_expr(pref, expr, evaluation: Evaluation): + return + -def debug_logical_expr(pref, expr, evaluation): - pass - # return - # print(pref , expr) #expr.format(evaluation,"OutputForm").boxes_to_text(evaluation=evaluation)) +# Tracing can redefine this to provide trace information +DEBUG_LOGICAL_EXPR = null_debug_logical_expr logical_algebraic_rules_spec = { @@ -105,7 +114,7 @@ def remove_nots_when_unnecesary(pred, evaluation): cc = True while cc: pred, cc = pred.do_apply_rules(remove_not_rules, evaluation) - debug_logical_expr("-> ", pred, evaluation) + DEBUG_LOGICAL_EXPR("-> ", pred, evaluation) if pred is SymbolTrue or pred is SymbolFalse: return pred return pred @@ -362,14 +371,14 @@ def evaluate_predicate(pred, evaluation): *[evaluate_predicate(subp, evaluation) for subp in pred.elements], ) - debug_logical_expr("reducing ", pred, evaluation) + DEBUG_LOGICAL_EXPR("reducing ", pred, evaluation) ensure_logical_algebraic_rules() pred = pred.evaluate(evaluation) - debug_logical_expr("-> ", pred, evaluation) + DEBUG_LOGICAL_EXPR("-> ", pred, evaluation) cc = True while cc: pred, cc = pred.do_apply_rules(logical_algebraic_rules, evaluation) - debug_logical_expr("-> ", pred, evaluation) + DEBUG_LOGICAL_EXPR("-> ", pred, evaluation) if pred is SymbolTrue or pred is SymbolFalse: return pred @@ -378,11 +387,11 @@ def evaluate_predicate(pred, evaluation): return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) if assumption_rules is not None: - debug_logical_expr(" Now, using the assumptions over ", pred, evaluation) + DEBUG_LOGICAL_EXPR(" Now, using the assumptions over ", pred, evaluation) changed = True while changed: pred, changed = pred.do_apply_rules(assumption_rules, evaluation) - debug_logical_expr(" -> ", pred, evaluation) + DEBUG_LOGICAL_EXPR(" -> ", pred, evaluation) pred = remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) return pred diff --git a/mathics/eval/numbers/algebra/simplify.py b/mathics/eval/numbers/algebra/simplify.py index 5cf158515..fb2a40e97 100644 --- a/mathics/eval/numbers/algebra/simplify.py +++ b/mathics/eval/numbers/algebra/simplify.py @@ -4,8 +4,14 @@ Algorithms for simplifying expressions and evaluate complexity. """ +from sympy import simplify + from mathics.core.atoms import Number +from mathics.core.convert.sympy import from_sympy from mathics.core.expression import Expression +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolList, SymbolTrue +from mathics.core.systemsymbols import SymbolAutomatic +from mathics.eval.inference import evaluate_predicate def default_complexity_function(expr: Expression) -> int: @@ -24,3 +30,60 @@ def default_complexity_function(expr: Expression) -> int: ) else: return 1 + + +def eval_Simplify(symbol_name: Symbol, expr, evaluation, options: dict): + # Check first if we are dealing with a logic expression... + if expr in (SymbolTrue, SymbolFalse, SymbolList): + return expr + + # ``evaluate_predicate`` tries to reduce expr taking into account + # the assumptions established in ``$Assumptions``. + expr = evaluate_predicate(expr, evaluation) + + # If we get an atom, return it. + if isinstance(expr, Atom): + return expr + + # Now, try to simplify the elements. + # TODO: Consider to move this step inside ``evaluate_predicate``. + # Notice that here we want to pass through the full evaluation process + # to use all the defined rules... + elements = [ + Expression(symbol_name, element).evaluate(evaluation) + for element in expr._elements + ] + head = Expression(symbol_name, expr.get_head()).evaluate(evaluation) + expr = Expression(head, *elements) + + # At this point, we used all the tools available in Mathics. + # If the expression has a sympy form, try to use it. + # Now, convert the expression to sympy + sympy_expr = expr.to_sympy() + # If the expression cannot be handled by Sympy, just return it. + if sympy_expr is None: + return expr + # Now, try to simplify using sympy + complexity_function = options.get("System`ComplexityFunction", None) + if complexity_function is None or complexity_function is SymbolAutomatic: + + def _default_complexity_function(x): + return default_complexity_function(from_sympy(x)) + + complexity_function = _default_complexity_function + else: + if isinstance(complexity_function, (Expression, Symbol)): + _complexity_function = complexity_function + complexity_function = ( + lambda x: Expression(_complexity_function, from_sympy(x)) + .evaluate(evaluation) + .to_python() + ) + + # At this point, ``complexity_function`` is a function that takes a + # sympy expression and returns an integer. + sympy_result = simplify(sympy_expr, measure=complexity_function) + + # and bring it back + result = from_sympy(sympy_result).evaluate(evaluation) + return result