import copy
import logging
import gdxcc
import numpy as np
logger = logging.getLogger(__name__)
# 1E300, 2E300, 3E300, 4E300, 5E300
NUMPY_SPECIAL_VALUES = [None, np.nan, np.inf, -np.inf, np.finfo(float).eps]
"""List of numpy special values in gdxGetSpecialValues order, i.e.,
[None, np.nan, np.inf, -np.inf, np.finfo(float).eps]
"""
[docs]def convert_gdx_to_np_svs(df, num_dims):
"""
Converts GDX special values to the corresponding numpy versions.
Parmeters
---------
df : pandas.DataFrame
a GdxSymbol DataFrame as it was read directly from GDX
num_dims : int
the number of columns in df that list the dimension values for which the
symbol value is non-zero / non-default
Returns
-------
pandas.DataFrame
copy of df for which all GDX special values have been converted to
their numpy equivalents
"""
# create clean copy of df
tmp = df.copy()
# apply the map to the value columns and merge with the dimensional information
tmp = (tmp.iloc[:, :num_dims]).merge(tmp.iloc[:, num_dims:].replace(GDX_TO_NP_SVS),
left_index=True, right_index=True)
return tmp
[docs]def is_np_eps(val):
"""
Parameters
----------
val : numeric
value to test
Returns
-------
bool
True if val is equal to eps (np.finfo(float).eps), False otherwise
"""
return np.abs(val - NUMPY_SPECIAL_VALUES[-1]) < NUMPY_SPECIAL_VALUES[-1]
[docs]def is_np_sv(val):
"""
Parameters
----------
val : numeric
value to test
Returns
-------
bool
True if val is NaN, eps, or is in NUMPY_SPECIAL_VALUES; False otherwise
"""
return np.isnan(val) or (val in NUMPY_SPECIAL_VALUES) or is_np_eps(val)
[docs]def convert_np_to_gdx_svs(df, num_dims):
"""
Converts numpy special values to the corresponding GDX versions.
Parmeters
---------
df : pandas.DataFrame
a GdxSymbol DataFrame in pandas/numpy form
num_dims : int
the number of columns in df that list the dimension values for which the
symbol value is non-zero / non-default
Returns
-------
pandas.DataFrame
copy of df for which all numpy special values have been converted to
their GDX equivalents
"""
# get a clean copy of df
tmp = df.copy()
# fillna and apply map to value columns, then merge with dimensional columns
try:
values = tmp.iloc[:, num_dims:].replace(NP_TO_GDX_SVS, value=None)
# DataFrame.replace is generally not sufficient to identify EPS values
values[(values - NUMPY_SPECIAL_VALUES[-1]).abs() < NUMPY_SPECIAL_VALUES[-1]] = SPECIAL_VALUES[4]
tmp = (tmp.iloc[:, :num_dims]).merge(values, left_index=True, right_index=True)
except:
logger.error("Unable to convert numpy special values to GDX special values." + \
"num_dims: {}, dataframe:\n{}".format(num_dims, df))
raise
return tmp
[docs]def pd_isnan(val):
"""
Utility function for identifying None or NaN (which are indistinguishable in pandas).
Parameters
----------
val : numeric
value to test
Returns
-------
bool
True if val is a GDX encoded special value that maps to None or numpy.nan;
False otherwise
"""
return val is None or val != val
[docs]def pd_val_equal(val1, val2):
"""
Utility function used to test special value conversions.
Parameters
----------
val1 : float or None
first value to compare
val2 : float or None
second value to compare
Returns
-------
bool
True if val1 and val2 are equal in the sense of == or they are
both NaN/None. The values that map to None and np.nan are assumed
to be equal because pandas cannot be relied upon to make the
distinction.
"""
return pd_isnan(val1) and pd_isnan(val2) or val1 == val2
[docs]def gdx_isnan(val,gdxf):
"""
Utility function for equating the GDX special values that map to None or NaN
(which are indistinguishable in pandas).
Parameters
----------
val : numeric
value to test
gdxf : GdxFile
GDX file containing the value. Provides np_to_gdx_svs map.
Returns
-------
bool
True if val is a GDX encoded special value that maps to None or numpy.nan;
False otherwise
"""
return val in [SPECIAL_VALUES[0], SPECIAL_VALUES[1]]
[docs]def gdx_val_equal(val1,val2,gdxf):
"""
Utility function used to test special value conversions.
Parameters
----------
val1 : float or GDX special value
first value to compare
val2 : float or GDX special value
second value to compare
gdxf : GdxFile
GDX file containing val1 and val2
Returns
-------
bool
True if val1 and val2 are equal in the sense of == or they are
equivalent GDX-format special values. The values that map to None
and np.nan are assumed to be equal because pandas cannot be relied
upon to make the distinction.
"""
if gdx_isnan(val1, gdxf) and gdx_isnan(val2, gdxf):
return True
return val1 == val2
[docs]def load_specials(gams_dir_finder):
"""
Load special values
Needs to be called after gdxcc is loaded. Populates the module attributes
SPECIAL_VALUES, GDX_TO_NP_SVS, and NP_TO_GDX_SVS.
Parameters
----------
gams_dir_finder : :class:`gdxpds.tools.GamsDirFinder`
"""
global SPECIAL_VALUES
global GDX_TO_NP_SVS
global NP_TO_GDX_SVS
H = gdxcc.new_gdxHandle_tp()
rc = gdxcc.gdxCreateD(H, gams_dir_finder.gams_dir, gdxcc.GMS_SSSIZE)
if not rc:
raise Exception(rc[1])
# get special values
special_values = gdxcc.doubleArray(gdxcc.GMS_SVIDX_MAX)
gdxcc.gdxGetSpecialValues(H, special_values)
SPECIAL_VALUES = []
GDX_TO_NP_SVS = {}
NP_TO_GDX_SVS = {}
for i in range(gdxcc.GMS_SVIDX_MAX):
if i >= len(NUMPY_SPECIAL_VALUES):
break
SPECIAL_VALUES.append(special_values[i])
gdx_val = special_values[i]
GDX_TO_NP_SVS[gdx_val] = NUMPY_SPECIAL_VALUES[i]
NP_TO_GDX_SVS[NUMPY_SPECIAL_VALUES[i]] = gdx_val
gdxcc.gdxFree(H)
# These values are populated by load_specials, called in load_gdxcc
SPECIAL_VALUES = []
GDX_TO_NP_SVS = {}
NP_TO_GDX_SVS = {}