-
Notifications
You must be signed in to change notification settings - Fork 546
Modified _parse_gdx_results in GAMS.py to replace _parse_special_value and Updated Import Statement #3642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
from pyomo.common.dependencies import attempt_import | ||
import numpy as np |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this not just float('nan')
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not straightforward to test for two different nan
s 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
if
statements in_parse_special_values
that 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_values
with GAMS special value parser in_parse_gdx_results
.attempt_import
to have fallback to pre-GAMS-45.0 API ifgams.core.gdx
is 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: