Modified _parse_gdx_results in GAMS.py to replace _parse_special_value and Updated Import Statement#3642
Conversation
| from pyomo.common.dependencies import attempt_import | ||
| import numpy as np |
There was a problem hiding this comment.
This adds a hard dependency on numpy. Importing numpy from dependencies will keep this as a "soft" dependency:
| from pyomo.common.dependencies import attempt_import | |
| import numpy as np | |
| from pyomo.common.dependencies import attempt_import, numpy as np |
It will still make numpy a hard dependency if you are going to use GAMS, but that is OK (although GAMS's available() should probably check both numpy_available and gdxcc_available)
…Removed the unused _parse_special_values function
jsiirola
left a comment
There was a problem hiding this comment.
Two minor nits and I think this is good to merge (once we get the testing infrastructure back up and working)!
| specVals[gdxcc.GMS_SVIDX_UNDEF] = float("nan") | ||
| specVals[gdxcc.GMS_SVIDX_PINF] = float("inf") | ||
| specVals[gdxcc.GMS_SVIDX_MINF] = float("-inf") | ||
| specVals[gdxcc.GMS_SVIDX_NA] = struct.unpack(">d", bytes.fromhex("fffffffffffffffe"))[0] |
There was a problem hiding this comment.
Why is this not just float('nan')?
There was a problem hiding this comment.
Adam or Artharv can correct me on this. NA special value used by GAMS means “initialized, but no numerical value assigned” has the byte pattern fffffffffffffffe above, while float('nan') and UNDEF/UNDF has this byte pattern 7ff8000000000000.
This is where I found the conversation about handling nan https://forum.gams.com/t/handling-nan-missing-values/7907/2.
There was a problem hiding this comment.
OK - I didn't realize that Python supported different "flavors" of nan. This is fine. Out of curiosity, is there a way that a user could differentiate between the two nans in Python (UNDEF vs NA)?
There was a problem hiding this comment.
@jsiirola the special values in gdxSetSpecialValues must be unique (unique bytes). this NA value is still a nan in python so that tests like nan == nan are still False. but we abide by the rule of GDX.
There was a problem hiding this comment.
Should we add utility methods to the GAMS interface? Something like:
def is_UNDEF(val):
return val != val and struct.pack(">d", float('nan')) == b'\x7f\xf8\x00\x00\x00\x00\x00\x00'
def is_NA(val):
return val != val and struct.pack(">d", float('nan')) == b'\xff\xff\xff\xff\xff\xff\xff\xfe'There was a problem hiding this comment.
While I am thinking about it, it might be useful to make class attributes for the special values [UNDEV, NA, PINF, NINF, EPS]. Then this code could return those class attributes. If we do that, then the user could (legitimately) use is to distinguise NA from UNDEF
There was a problem hiding this comment.
it is not straightforward to test for two different nans but it is possible... it is also not guaranteed that other packages like pandas will maintain the bytes during their own operations. I believe numpy might make stronger claims about maintaining bytes.
import struct
NAN = float("nan")
NA = struct.unpack(">d", bytes.fromhex("fffffffffffffffe"))[0]
print(struct.pack('>d', NAN).hex())
print(struct.pack('>d', NA).hex())
print(NAN == NA)
print(NAN == NAN)
print(NA == NA)
There was a problem hiding this comment.
Should we add utility methods to the GAMS interface? Something like:
def is_UNDEF(val): return val != val and struct.pack(">d", float('nan')) == b'\x7f\xf8\x00\x00\x00\x00\x00\x00' def is_NA(val): return val != val and struct.pack(">d", float('nan')) == b'\xff\xff\xff\xff\xff\xff\xff\xfe'
We have these utilities in our gamsapi... so we could always import them somewhere. We'd probably need a little guidance on where would be appropriate though.
The methods above work but can be slow for large data so we've (i.e. Steve D.) came up with a clever method to reinterpret the NA bits as a UINT64. Then we can do a straightforward integer comparison rather than a string comparison.
These methods look something like this:
import struct
import numpy as np
NA = struct.unpack(">d", bytes.fromhex("fffffffffffffffe"))[0]
_NA_INT64 = np.float64(NA).view(np.uint64)
print(f"verify same bytes: {struct.pack('>Q', _NA_INT64).hex()}")
arr = np.array([float("nan"), float("nan"), float("nan"), NA])
which gives:
In [1]: arr.view(np.uint64) == _NA_INT64
Out[1]: array([False, False, False, True])
There was a problem hiding this comment.
if we are going to go down this road then it's probably worth mentioning that we also use -0.0 as our EPS... rather than sys.float_info.min (or sys.float_info.epsilon). The GAMS interpretation of EPS is mathematically zero... but we use the sign bit to detect this special value. I'm not sure if pyomo needs all this detail, might be implications elsewhere?
|
The codes markdowned below are used to generate a simple Pyomo model to obtain the The The The packages required to run |
simplePyomoModel.py |
timing.py |
|
@AnhTran01 - Please run |

Fixes #3624
Summary/Motivation:
When parsing the results of a model after it has been solved, the level and dual value are obtained through a series of
ifstatements in_parse_special_valuesthat may cause slowdowns. This PR added GAMS existing functions to handle data parser for these special values in_parse_gdx_results.Changes proposed in this PR:
_parse_special_valueswith GAMS special value parser in_parse_gdx_results.attempt_importto have fallback to pre-GAMS-45.0 API ifgams.core.gdxis not available.Legal Acknowledgement
By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution: