Skip to content

Time-limit SymPy simplification so numeric logs of large powers can't hang grading - #282

Draft
m-messer wants to merge 1 commit into
refactor/restructure-appfrom
fix/simplify-time-budget
Draft

m-messer wants to merge 1 commit into
refactor/restructure-appfrom
fix/simplify-time-budget

Conversation

@m-messer

Copy link
Copy Markdown
Member

Problem

Stacked on #281. Grading a number that combines the log of a constant with a large power of 10, such as log(0.1)*(10^8/ pi), ran past the 120 s Lambda timeout. The student got no grade, and the instance was held for the full timeout. This predates the refactor: the old grader stored empty responses for all 4 past submissions of this shape.

The cause is SymPy's simplify(), which calls logcombine. That rewrites -10^8*log(10)/pi as log(10**(-10**8)) and builds a 100-million-digit integer. Two places hit it: the "simplified" display in evaluation.py, and the comparison in do_comparison. Parsing is not involved.

Changes

  • app/time_budget.py (new): nested time limits enforced with SIGALRM. They're checked between bytecodes, which interrupts this simplify promptly. TimeBudgetExceeded is a BaseException, so the except Exception around SymPy calls can't swallow it. A limit does nothing off the main thread. The limits are 5 s per comparison and 60 s per request.
  • app/context/symbolic.py: the symbolic attempts in check_equality run under the comparison limit. If they run out of time, = falls back to comparing values to 50 digits. check_order, check_proportionality and equality equivalence also run under the limit and grade as incorrect if they run out of time.
  • app/context/numerical.py: adds numerically_equal, and replace_pi moves to module level. The fallback substitutes exact pi, because float pi would add noise at 50 digits.
  • app/evaluation.py:
    • The "simplified" display falls back to the raw response when it runs out of time.
    • The whole evaluation runs under the request limit. When that runs out, the request is graded incorrect with the new TIME_LIMIT_EXCEEDED feedback (app/feedback/symbolic.py).
Response vs answer Before After
log(0.1)*(10^8/ pi) vs (-1/(100*pi*k))*ln(r)+c over 120 s 10 s, incorrect
900000+10^8* pi* log(100) vs 1.47*10**8 hangs 10 s, incorrect
log(0.1)*(10^8/ pi) vs -10^8*log(10)/pi hangs 5 s, correct
-10^8/( pi) log(r) vs (-1/(100*pi*k))*ln(r)+c 0.15 s 0.15 s, incorrect

Not covered: the simplify calls in app/context/physical_quantity.py, and the variation code at symbolic.py:615/620. They're bounded only by the 60 s request limit, not by their own fallbacks.

Checklist

  • Tests added/updated
    • regression tests for the cases above, plus the request running out of time, in symbolic_evaluation_test.py;
    • unit tests for the limits in app/tests/time_budget_test.py.
    • Tests shorten the limits, so they finish in seconds.
  • Docs updated (app/docs.md) if user-facing behaviour changed: only the new feedback message is user-facing.

Testing

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant