Source code for gamspy._special_values

from __future__ import annotations

import struct
from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    import pandas as pd


[docs] class SpecialValues: """ Utility class representing GAMS special values and methods to test for their presence. In GAMS, special values are used to represent missing data, undefined operations, infinities, or structural zeros (EPS). This class defines GAMSPy equivalents for these values. It also provides vectorized static methods to efficiently check for these special values across integers, floats, strings, pandas Series, pandas DataFrames, or array-like objects. Attributes ---------- NA : float GAMS NA (Not Available), unpacked from a specific hexadecimal representation ("fffffffffffffffe"). EPS : float GAMS EPS (Epsilon), represented as a negative zero (-0.0). UNDEF : float GAMS UNDEF (Undefined), represented as Not a Number (NaN). POSINF : float GAMS POSINF (Positive Infinity), represented as float("inf"). NEGINF : float GAMS NEGINF (Negative Infinity), represented as float("-inf"). Examples -------- Basic usage of attributes >>> from gamspy._special_values import SpecialValues >>> SpecialValues.EPS -0.0 >>> SpecialValues.POSINF inf Testing for special values in data structures >>> import numpy as np >>> import pandas as pd >>> data = pd.Series([1.5, -0.0, float("inf"), SpecialValues.NA]) >>> SpecialValues.isEps(data) array([False, True, False, False]) >>> SpecialValues.isNA(data) array([False, False, False, True]) """ NA = struct.unpack(">d", bytes.fromhex("fffffffffffffffe"))[0] EPS = -0.0 UNDEF = float("nan") POSINF = float("inf") NEGINF = float("-inf") @staticmethod def _convertNPfloat64(records, sv_name) -> np.ndarray: if not ( isinstance(records, np.ndarray) and np.issubdtype(records.dtype, np.float64) ): try: records = np.array(records, dtype=np.float64) except Exception as err: raise ValueError( "Data structure passed in 'records' could not be " "converted to a numpy array (dtype=np.float64) " f"to test for GAMS {sv_name}, reason: {err}" ) from err return records
[docs] @staticmethod def isEps( records: int | float | str | pd.Series | pd.DataFrame, ) -> np.ndarray: """ Check if the input records represent a value close to zero with specific considerations for different data types. Parameters ---------- records: int | float | str | pd.Series | pd.DataFrame | array-like The input records to be checked for proximity to zero. Returns ------- np.ndarray True if the input records represent a value close to zero according to the specified conditions, False otherwise. Raises ------ Exception If the input (string) records cannot be converted to a float. Exception If the data structure passed in 'records' could not be converted to a numpy array (dtype=float) for testing. """ np_records = SpecialValues._convertNPfloat64(records, "EPS") return (np_records == 0) & (np.signbit(np_records))
[docs] @staticmethod def isNA( records: int | float | str | pd.Series | pd.DataFrame, ) -> np.ndarray: """ Check if values in records represent GAMS NA (Not Available) values. Parameters ---------- records: int | float | str | pd.Series | pd.DataFrame | array-like The input records to be checked for GAMS NA values. Returns ------- np.ndarray True if the values in records represent GAMS NA values; otherwise, False. Raises ------ Exception If the input (string) records cannot be converted to a float. Exception If the data structure passed in 'records' could not be converted to a numpy array (dtype=float) for testing. """ _NA_INT64 = np.float64(SpecialValues.NA).view(np.uint64) np_records = SpecialValues._convertNPfloat64(records, "NA") return np_records.view(np.uint64) == _NA_INT64
[docs] @staticmethod def isUndef( records: int | float | str | pd.Series | pd.DataFrame, ) -> np.ndarray: """ Determine if the given input(s) represent GAMS "undef" values. Parameters ---------- records: int | float | str | pd.Series | pd.DataFrame | array-like The input records to be checked for GAMS "undef" values. Returns ------- np.ndarray True if the values in records represent GAMS "undef" values; otherwise, False. Raises ------ Exception If the input (string) records cannot be converted to a float. Exception If the data structure passed in 'records' could not be converted to a numpy array (dtype=float) for testing. """ _NA_INT64 = np.float64(SpecialValues.NA).view(np.uint64) np_records = SpecialValues._convertNPfloat64(records, "UNDEF") return np.isnan(np_records) & (np_records.view(np.uint64) != _NA_INT64)
[docs] @staticmethod def isPosInf( records: int | float | str | pd.Series | pd.DataFrame, ) -> np.ndarray | np.bool: """ Check if the input records represent positive infinity. Parameters ---------- records: int | float | str | pd.Series | pd.DataFrame | array-like The input records to be checked for positive infinity values. Returns ------- np.ndarray | np.bool True if the values in records represent positive infinity values; otherwise, False. Raises ------ Exception If the input (string) records cannot be converted to a float. Exception If the data structure passed in 'records' could not be converted to a numpy array (dtype=float) for testing. """ np_records = SpecialValues._convertNPfloat64(records, "POSINF") return np.isposinf(np_records)
[docs] @staticmethod def isNegInf( records: int | float | str | pd.Series | pd.DataFrame, ) -> np.ndarray | np.bool: """ Check if the input records represent negative infinity. Parameters ---------- records: int | float | str | pd.Series | pd.DataFrame | array-like The input records to be checked for negative infinity values. Returns ------- np.ndarray | np.bool True if the values in records represent negative infinity values; otherwise, False. Raises ------ Exception If the input (string) records cannot be converted to a float. Exception If the data structure passed in 'records' could not be converted to a numpy array (dtype=float) for testing. """ np_records = SpecialValues._convertNPfloat64(records, "NEGINF") return np.isneginf(np_records)