Skip to content

Commit

Permalink
Simplify & inference fns moved under mathics.eval (#1140)
Browse files Browse the repository at this point in the history
  • Loading branch information
rocky authored Oct 21, 2024
1 parent 69eda63 commit eb831c2
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 76 deletions.
2 changes: 1 addition & 1 deletion mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
71 changes: 10 additions & 61 deletions mathics/builtin/numbers/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1496,7 +1498,10 @@ def eval_list(self, expr, vars, evaluation):
# FullSimplify
class Simplify(Builtin):
r"""
<url>:WMA link:
<url>:SymPy:
https://docs.sympy.org/latest/modules/simplify
/simplify.html</url>, <url>
:WMA:
https://reference.wolfram.com/language/ref/Simplify.html</url>
<dl>
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 5 additions & 2 deletions mathics/builtin/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import sympy

from mathics.builtin.inference import evaluate_predicate
from mathics.core.atoms import (
Complex,
Integer,
Expand Down Expand Up @@ -52,6 +51,7 @@
eval_RealSign,
eval_Sign,
)
from mathics.eval.inference import evaluate_predicate
from mathics.eval.nevaluator import eval_NValues


Expand Down Expand Up @@ -319,7 +319,10 @@ def eval_N(self, expr, evaluation: Evaluation):

class Piecewise(SympyFunction):
"""
<url>:WMA link:https://reference.wolfram.com/language/ref/Piecewise.html</url>
<url>:SymPy:
https://docs.sympy.org/latest/modules/functions
/elementary.html#piecewise</url>, <url>
:WMA:https://reference.wolfram.com/language/ref/Piecewise.html</url>
<dl>
<dt>'Piecewise[{{expr1, cond1}, ...}]'
Expand Down
33 changes: 21 additions & 12 deletions mathics/builtin/inference.py → mathics/eval/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
63 changes: 63 additions & 0 deletions mathics/eval/numbers/algebra/simplify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

0 comments on commit eb831c2

Please sign in to comment.