Skip to content

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

Description

@Rafael-SOWNet

MathS.ToSympyCode emits Python that references names it never binds, whenever the expression holds a
non-finite value. Real.ToSymPy() is

internal override string ToSymPy()
    => Stringize();

so a Real goes into the generated Python exactly as this library prints it — NaN, +oo, -oo — while
the preamble binds only the free variables:

sb.Append("import sympy\n\n");
foreach (var f in expr.Vars)
    sb.Append($"{f.Stringize()} = sympy.Symbol('{f.Stringize()}')\n");
sb.Append("expr = ").Append(expr.ToSymPy());

A NaN or an infinity is a Real and not a Variable, so it never appears in Vars and nothing declares
it. The emitted program then reads

import sympy

expr = +oo

which raises NameError on oo. The same holds for -oo, and for NaN after
#906.

The infinities have always been like this

Independent of #906: +oo and -oo have never had a binding in the emitted code. sympy.oo is the
shorthand SymPy documents for S.Infinity, and negative infinity is -sympy.oo, so the fix for those two
is to emit those spellings rather than this library's.

The NaN spelling wants checking against SymPy rather than assumed — S.NaN and sympy.nan are the
likely accessors, and I could not confirm either from the core-module documentation, which documents the
singleton registry and S.Zero without listing a NaN attribute. Worth reading SymPy's source or a REPL
before writing it into the exporter.

#906 changes how this fails, and it is worth knowing which way

Before #906, NaN in a source string parsed as a variable, so it landed in Vars, got a
NaN = sympy.Symbol('NaN') line, and the emitted program ran — meaning a symbol rather than the NaN
value. Silently wrong.

After #906 it parses as the value, so the emitted program is expr = NaN with nothing binding NaN, and
it fails with a NameError. Still wrong, but loudly. That is the better of the two failures and it is not
a fix; the exporter needs the spelling.

Where this sits

ToSymPy is internal and reached through the public MathS.ToSympyCode, whose documented purpose is
generating code "that you can run in SymPy". Code that does not run is the whole defect. It is also the
third output path with the same question, after Stringize (#906) and Latexize (already correct, with
\mathrm{undefined}, which CSharpMath decodes back to MathS.NaN).

Nothing tests it. ToSympyCode's output is never executed by the suite, which is why an unbound name has
gone unnoticed — the round-trip discipline that #906 added for Stringize has no counterpart here, and
cannot have quite the same one, since checking it means running Python. A cheaper check that would still
catch this: assert that every name the emitted body references is either declared in the preamble or
prefixed with sympy..

Found while fixing #906.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions