Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify & inference fns moved under mathics.eval #1140

Merged
merged 2 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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