Skip to content

GAMS writer: map non-finite float values to GAMS special values (NA / INF) - #4029

Open
cjck944084735-dot wants to merge 1 commit into
Pyomo:mainfrom
cjck944084735-dot:fix/gams-writer-nonfinite-values
Open

GAMS writer: map non-finite float values to GAMS special values (NA / INF)#4029
cjck944084735-dot wants to merge 1 commit into
Pyomo:mainfrom
cjck944084735-dot:fix/gams-writer-nonfinite-values

Conversation

@cjck944084735-dot

Copy link
Copy Markdown

Summary

The GAMS writer emits Python's string rendering of non-finite floats (nan, inf, -inf), which are not valid GAMS numeric literals, so the exported .gms file fails in GAMS with errors like the symbol nan is undefined.

Fixes #3911.

Root cause

All numeric output in pyomo/repn/plugins/gams_writer.py goes through ftoa(), which formats with '%.17g'. Non-finite floats therefore render as nan / inf / -inf, e.g. (current main, default warmstart=True):

c2.. GAMS_OBJECTIVE =e= power(x1, 2) + x2 + nan ;
x1.l = nan;
x2.l = inf;

GAMS's special numeric literals are NA, INF/+INF/-INF (and EPS), so every one of those lines is rejected by GAMS. This hits all value-emitting paths of the writer: constraint bounds, expression constants (fixed Vars/Params), warmstart .l values, and fixed-variable .fx values.

The fix

Route every numeric output site in the writer through a small wrapper that post-processes the ftoa() result:

def gams_ftoa(val, parenthesize_negative_values=False):
    a = ftoa(val, parenthesize_negative_values)
    # Python renders non-finite floats as nan/inf, which are not valid GAMS
    # numeric literals: map them to GAMS's special values NA and INF
    if a == 'nan':
        return 'NA'
    if a == '(-inf)':
        return '(-INF)'
    if a.endswith('inf'):
        return a[:-3] + 'INF'
    return a

After the change the same model exports as:

c2.. GAMS_OBJECTIVE =e= power(x1, 2) + x2 + NA ;
x1.l = NA;
x2.l = INF;

Two notes on float semantics that the new tests document:

  • -1 * nan is nan (the sign is lost), so - z with z = nan exports as + NA.
  • -inf values in monomials are parenthesized by ftoa, hence + (-INF).

Testing

  • New test_nonfinite_var_to_string (expression path: fixed nan/inf/-inf Vars) and test_nonfinite_var_values_write_gams (full model write: .l warmstart values and the fixed-expression constant, plus assertions that no bare nan/inf appears anywhere in the file).
  • Full existing suite pyomo/repn/tests/gams/ passes (24 passed, 1 skipped) and black --check is clean.
  • Verified the reporter's scenario from Handling of float('nan') especially in GAMSWriter #3911 (Var(initialize=float('nan')) + GAMS solve script) no longer emits invalid literals.

Note: gams_writer_v2

pyomo/repn/plugins/gams_writer_v2.py has the same pattern for warmstart values (ostream.write(f"{v}.l = {pyomo_v.value}; "), line ~537). I left it untouched to keep this PR focused on the default gams writer from the issue; happy to apply the equivalent mapping there in this PR or a follow-up, whichever the maintainers prefer.

Python renders non-finite floats as the strings 'nan'/'inf', which are
not valid GAMS numeric literals, so exporting a model whose variables or
fixed expressions hold such values produces a .gms file that GAMS
rejects with errors like "the symbol nan is undefined".  Route all
numeric output in the GAMS writer through a gams_ftoa() wrapper that
maps them to GAMS's special values NA and INF.

Fixes Pyomo#3911
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.

Handling of float('nan') especially in GAMSWriter

1 participant