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

Redo TraceEvaluation using a decorator... #1169

Merged
merged 4 commits into from
Nov 25, 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
16 changes: 15 additions & 1 deletion mathics/builtin/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from time import time
from typing import Callable

import mathics.eval.tracing
from mathics.core.attributes import A_HOLD_ALL, A_HOLD_ALL_COMPLETE, A_PROTECTED
from mathics.core.builtin import Builtin
from mathics.core.convert.python import from_bool, from_python
Expand Down Expand Up @@ -354,6 +355,8 @@ class TraceEvaluation(Builtin):
<dd>Evaluate $expr$ and print each step of the evaluation.
</dl>

The 'ShowTimeBySteps' option prints the elapsed time before an evaluation occurs.

>> TraceEvaluation[(x + x)^2]
| ...
= ...
Expand All @@ -367,19 +370,30 @@ class TraceEvaluation(Builtin):
options = {
"System`ShowTimeBySteps": "False",
}
summary_text = "trace the successive evaluations"
summary_text = "trace expression evaluation"

def eval(self, expr, evaluation: Evaluation, options: dict):
"TraceEvaluation[expr_, OptionsPattern[]]"

curr_trace_evaluation = evaluation.definitions.trace_evaluation
curr_time_by_steps = evaluation.definitions.timing_trace_evaluation

old_evaluation_hook = mathics.eval.tracing.trace_evaluate_on_call

mathics.eval.tracing.trace_evaluate_on_call = (
mathics.eval.tracing.print_evaluate
)

evaluation.definitions.trace_evaluation = True
evaluation.definitions.timing_trace_evaluation = (
options["System`ShowTimeBySteps"] is SymbolTrue
)
result = expr.evaluate(evaluation)
evaluation.definitions.trace_evaluation = curr_trace_evaluation
evaluation.definitions.timing_trace_evaluation = curr_time_by_steps

mathics.eval.tracing.trace_evaluate_on_call = old_evaluation_hook

return result


Expand Down
3 changes: 3 additions & 0 deletions mathics/core/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ def __init__(
# ``mathics.builtin.numeric.N``.
self._preferred_n_method: List[str] = []

self.is_boxing = False

def parse(self, query, src_name: str = ""):
"Parse a single expression and print the messages."
from mathics.core.parser import MathicsSingleLineFeeder
Expand Down Expand Up @@ -450,6 +452,7 @@ def print_out(self, text) -> None:
if self.definitions.trace_evaluation:
self.definitions.trace_evaluation = False
text = self.format_output(from_python(text), "text")
self.is_boxing = False
self.definitions.trace_evaluation = True
else:
text = self.format_output(from_python(text), "text")
Expand Down
9 changes: 2 additions & 7 deletions mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-

import math
import time
from bisect import bisect_left
from itertools import chain
from typing import (
Expand Down Expand Up @@ -78,6 +77,7 @@
SymbolSubtract,
SymbolUnevaluated,
)
from mathics.eval.tracing import trace_evaluate

# from mathics.timing import timeit

Expand Down Expand Up @@ -521,6 +521,7 @@ def equal2(self, rhs: Any) -> Optional[bool]:
# Note that the return type is some subclass of BaseElement, it could be
# a Real, an Expression, etc. It probably will *not* be a BaseElement since
# the point of evaluation when there is not an error is to produce a concrete result.
@trace_evaluate
def evaluate(
self,
evaluation: Evaluation,
Expand All @@ -544,12 +545,6 @@ def evaluate(

old_options = evaluation.options
evaluation.inc_recursion_depth()
if evaluation.definitions.trace_evaluation:
if evaluation.definitions.timing_trace_evaluation:
evaluation.print_out(time.time() - evaluation.start_time)
evaluation.print_out(
" " * evaluation.recursion_depth + "Evaluating: %s" % expr
)
try:
# Evaluation loop:
while reevaluate:
Expand Down
11 changes: 3 additions & 8 deletions mathics/core/symbols.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# cython: language_level=3
# -*- coding: utf-8 -*-

import time
from typing import TYPE_CHECKING, Any, Dict, FrozenSet, List, Optional, Sequence, Union

from mathics.core.element import (
Expand All @@ -14,6 +13,8 @@
if TYPE_CHECKING:
from mathics.core.atoms import String

from mathics.eval.tracing import trace_evaluate

# I put this constants here instead of inside `mathics.core.convert.sympy`
# to avoid a circular reference. Maybe they should be in its own module.

Expand Down Expand Up @@ -483,18 +484,12 @@ def equal2(self, rhs: Any) -> Optional[bool]:
return self == rhs
return None

@trace_evaluate
def evaluate(self, evaluation):
"""
Evaluates the symbol by applying the rules (ownvalues) in its definition,
recursively.
"""
if evaluation.definitions.trace_evaluation:
if evaluation.definitions.timing_trace_evaluation:
evaluation.print_out(time.time() - evaluation.start_time)
evaluation.print_out(
" " * evaluation.recursion_depth + " Evaluating: %s" % self
)

rules = evaluation.definitions.get_ownvalues(self.name)
for rule in rules:
result = rule.apply(self, evaluation, fully=True)
Expand Down
1 change: 1 addition & 0 deletions mathics/eval/makeboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def format_element(
"""
Applies formats associated to the expression, and then calls Makeboxes
"""
evaluation.is_boxing = True
expr = do_format(element, evaluation, form)
if expr is None:
return None
Expand Down
53 changes: 52 additions & 1 deletion mathics/eval/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,68 @@
"""

import inspect
import time
from enum import Enum
from typing import Any, Callable, Optional

TraceEventNames = ("SymPy", "Numpy", "mpmath", "apply", "debugger")
TraceEventNames = ("SymPy", "Numpy", "mpmath", "apply", "evaluate", "debugger")
TraceEvent = Enum("TraceEvent", TraceEventNames)


hook_entry_fn: Optional[Callable] = None
hook_exit_fn: Optional[Callable] = None


def print_evaluate(expr, evaluation, status: str, orig_expr=None) -> bool:
"""
Called from a decorated Python @trace_evaluate .evaluate()
method when TraceActivate["evaluate" -> True]
"""
if evaluation.definitions.timing_trace_evaluation:
evaluation.print_out(time.time() - evaluation.start_time)
indents = " " * evaluation.recursion_depth
evaluation.print_out(f"{indents}{status}: {expr}")
return False


# When not None, evaluate() methods call this
# to show the status of evaluation on entry and return.
trace_evaluate_on_call: Optional[Callable] = None
trace_evaluate_on_return: Optional[Callable] = None


def trace_evaluate(func: Callable) -> Callable:
"""
Wrap a method evaluate() call event with trace_evaluate_on_call()
and trace_evaluate_on_return() callback so we can trace the
progress in evaluation.
"""

def wrapper(expr, evaluation) -> Any:
from mathics.core.symbols import SymbolConstant

skip_call = False
result = None
if (
trace_evaluate_on_call is not None
and not evaluation.is_boxing
and not isinstance(expr, SymbolConstant)
):
# We may use boxing in print_evaluate_fn(). So turn off
# boxing temporarily.
was_boxing = evaluation.is_boxing
evaluation.is_boxing = True
skip_call = trace_evaluate_on_call(expr, evaluation, "Evaluating")
evaluation.is_boxing = was_boxing
if not skip_call:
result = func(expr, evaluation)
if trace_evaluate_on_return is not None and not evaluation.is_boxing:
trace_evaluate_on_return(result, evaluation, "Returning", expr)
return result

return wrapper


def trace_fn_call_event(func: Callable) -> Callable:
"""
Wrap a call event with callbacks,
Expand Down