fix: encode GAMS EPS as np.finfo(float).tiny (closes #39) - #115
Conversation
BREAKING CHANGE: gdxpds.special.NUMPY_SPECIAL_VALUES[-1] is now np.finfo(float).tiny (~2.22e-308), not np.finfo(float).eps (~2.22e-16). EPS detection is exact equality on the sentinel, so a legitimate small float (e.g. 1e-200, machine epsilon) now round-trips as itself instead of silently mapping to GAMS EPS on write. If your code wrote numpy.finfo(float).eps to mean GAMS EPS, switch to numpy.finfo(float).tiny. Bumps to v4.0.0. Updates the gdxcc and gams.transfer engines, special value helpers, EPS-related tests, overview.md migration notes, and CHANGES.txt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes issue #39 by changing the numpy encoding of GAMS EPS from np.finfo(float).eps (~2.22e-16) to np.finfo(float).tiny (~2.22e-308), and switching EPS detection from a tolerance band (|val - eps| < eps) to exact equality on the sentinel. This is a breaking change bumping the package to v4.0.0; legitimate small floats (e.g. 1e-200, machine epsilon) now round-trip as themselves instead of being silently coerced to GAMS EPS on write. Changes are made consistently across the canonical mapping module, both I/O engines, tests (including new cross-engine parity regressions), docs, and changelog.
Changes:
- Replace
np.finfo(float).epswithnp.finfo(float).tinyas the canonical EPS sentinel inspecial.py, and switch all EPS detection (is_np_eps,convert_np_to_gdx_svs, gdxcc_coerce_value_col, transfer_substitute_value_col) to exact equality. - Add regression tests (
test_small_float_is_not_eps,test_write_parity_small_floats_not_eps) ensuring1e-200and machine epsilon survive the full write/read engine matrix. - Update version to 4.0.0, plus
CHANGES.txtanddoc/source/overview.md(special-values table, EPS-drop snippet, new v4.0.0 migration subsection).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/gdxpds/special.py | Canonical sentinel switched to tiny; is_np_eps and convert_np_to_gdx_svs use exact equality; docstrings updated. |
| src/gdxpds/_gdxcc_engine.py | Write-side EPS detection switched to exact equality; read-side docstring updated to tiny. |
| src/gdxpds/_transfer_engine.py | Same exact-equality switch for _substitute_value_col; docstrings clarify gt EPS is -0.0 bit pattern. |
| src/gdxpds/init.py | Version bump to 4.0.0. |
| tests/test_specials.py | Swap eps→tiny; add test_small_float_is_not_eps regression. |
| tests/test_engine_parity.py | Swap eps→tiny; add cross-engine small-float regression. |
| tests/test_write.py | Swap eps→tiny in test_write_known_value_columns. |
| doc/source/overview.md | Update special-values table, snippet, intro; add v4.0.0 migration subsection. |
| CHANGES.txt | New v4.0.0 entry describing the breaking change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
gdxpds.special.NUMPY_SPECIAL_VALUES[-1]is nownp.finfo(float).tiny(~2.22e-308), notnp.finfo(float).eps(machine epsilon, ~2.22e-16). EPS detection is exact equality on the sentinel, so a legitimate small float (e.g.1e-200, machine epsilon) now round-trips as itself instead of silently mapping to GAMSEPSon write. Closes GAMSepsisn't the same as Numpy EPS #39.numpy.finfo(float).epsto mean GAMSEPS, switch tonumpy.finfo(float).tiny.gdxpds.special.NUMPY_SPECIAL_VALUES[-1]is always the canonical sentinel.dev/modernization-wrapup.md, the final release in the modernization arc.What changed
src/gdxpds/special.py:NUMPY_SPECIAL_VALUES[-1] = np.finfo(float).tiny;is_np_epsandconvert_np_to_gdx_svsuse exact equality on the sentinel (no more|val - eps| < epsband).src/gdxpds/_gdxcc_engine.py_coerce_value_col: write-sideeps_mask = arr == eps(exact equality); docstring updated.src/gdxpds/_transfer_engine.py_substitute_value_col: same exact-equality switch; docstrings now make explicit thatgt.SpecialValues.EPSis the negative-zero bit pattern (distinguishable from+0.0only via the sign bit, not==).tests/test_specials.py,tests/test_engine_parity.py,tests/test_write.pyall swapeps→tiny. New regressions:test_small_float_is_not_epsandtest_write_parity_small_floats_not_eps(1e-200survives the full 2x2 write x read engine matrix).doc/source/overview.mdspecial-values table, "drop EPS" snippet, intro paragraph, and a new "v4.0.0 breaking changes" subsection in the migration section.CHANGES.txt: v4.0.0 entry.src/gdxpds/__init__.py:__version__ = "4.0.0".Test plan
ruff check+ruff format --check) clean.pytest testspasses on.venv-gams-34(pandas 3.0).pytest testspasses on.venv-gams-49(pandas 2.2, gamsapi 49.6.0).pytest testspasses on.venv-gams-51(pandas 2.2, gamsapi 51.3.0)..venv-no-gamswheel build +gdxpds infosucceed as expected.1e-200write/read round-trip preserved on both engines (covered by the new regressions).🤖 Generated with Claude Code