Skip to content

Emit SymPy code that runs (#909) - #912

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
fix/sympy-constants
Aug 12, 2026
Merged

Emit SymPy code that runs (#909)#912
Rafael-SOWNet merged 1 commit into
masterfrom
fix/sympy-constants

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

Closes #909, and it turned out to be worse than that issue described — the issue named the unbound names,
and the same file also had a missing parenthesis.

expression was is
1/2, 1/3 + 1/6 sympy.Rational(1, 2SyntaxError: '(' was never closed sympy.Rational(1, 2)
0/0, 1/0 NaNNameError: name 'NaN' is not defined sympy.nan
+oo, -oo +oo, -ooNameError sympy.oo, -sympy.oo

The parenthesis broke every expression carrying a non-integer rational, which is most of what a
computer algebra system hands back. MathS.ToSympyCode's documented purpose is code you can run in
SymPy, so this was not a cosmetic defect.

Measured by running it

SymPy 1.14 locally, eight expressions emitted and executed. Before: two NameErrors and the rationals
refusing to parse. After: zero failures, and the values come back exact rather than merely valid —

sympy(simplify(1/2))         ran, expr = 1/2         (Half)
sympy(simplify(0/0))         ran, expr = nan         (NaN)
sympy(simplify(x + 1/2))     ran, expr = x + 1/2     (Add)
sympy(simplify(sqrt(2) + pi))ran, expr = sqrt(2) + pi(Add)

Half, not the float 0.5, which is the whole reason to emit sympy.Rational rather than a division of
two Python integers.

What was already right

Checked before touching anything, because the "what else is the same shape" guess was wrong here: pi,
e and i already emitted sympy.pi, sympy.E and sympy.I, and sqrt already emitted sympy.sqrt.
Only the two number cases above were broken.

The tests, without an interpreter in the suite

The suite cannot depend on Python, so the two properties that actually failed are what get asserted:

  • the parentheses balance — what sympy.Rational(1, 2 violated;
  • every name in the emitted body is bound — declared as a sympy.Symbol in the preamble, or reached
    through sympy. — which is what a bare NaN or +oo violated.

The second generalises: any future value that leaves here as a bare name fails it. 17 of the 23 cases
fail against the old exporter
, 23 of 23 pass here.

Not in this PR

1/2 before simplification is a Divf of two integers and emits 1 / 2, which Python evaluates to the
float 0.5 — exactness lost silently. Fixing it means deciding how much to sympify: emitting
sympy.Integer(n) for every integer is faithful but makes x + sympy.Integer(1) of x + 1. That is a
design choice rather than a defect fix, so it is filed separately as #911 with a recommendation.

ToSymPy is reached only from ToSympyCode, so evaluation and simplification cannot see any of this —
verified by grep rather than assumed. Suite 6474 passed / 0 failed, F# wrapper 130 passed, casbench
117/119 with 0 wrong.

Cut from master at 548ea178.

MathS.ToSympyCode is documented as generating code you can run in SymPy, and for two whole
classes of expression it emitted code that did not run.

Rational's exporter was missing its closing parenthesis -- sympy.Rational(1, 2 -- so every
expression carrying a non-integer rational produced SyntaxError: '(' was never closed. That is
most of what a computer algebra system hands back.

Real emitted Stringize(), so the three non-finite values arrived as this library spells them:
NaN, +oo, -oo. The generated preamble binds a sympy.Symbol for each free variable, and none of
those three is a variable, so the program stopped with NameError. They now use SymPy's own
spellings, sympy.nan, sympy.oo and -sympy.oo.

Verified by running the emitted programs against SymPy 1.14 rather than by reading them, which
is also how it was established that they come back exact: 1/2 arrives as SymPy's Half and not as
the float 0.5. Eight expressions, previously two failures and six imprecise or broken, now zero
failures.

Nothing else changes. pi, e and i were already sympy.pi, sympy.E and sympy.I, and sqrt was
already sympy.sqrt -- measured before touching them, since the same-shape guess was wrong there.

The two new tests hold the properties that failed, without an interpreter in the suite: the
parentheses balance, and every name in the emitted body is either declared in the preamble or
reached through sympy. 17 of their 23 cases fail against the old exporter and 23 pass here.

ToSymPy is called only from ToSympyCode, so evaluation and simplification cannot see this;
suite 6474 passed, F# wrapper 130 passed, casbench 117/119 with 0 wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rafael-SOWNet
Rafael-SOWNet merged commit 32a9058 into master Aug 12, 2026
25 checks passed
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.

ToSympyCode emits Python that references NaN and +oo without binding them

1 participant