Source code for gams.transfer.syms.container_syms._universe_alias

#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2017-2024 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2017-2024 GAMS Software GmbH <support@gams.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

import pandas as pd
from gams.core import gdx
from gams.transfer._abcs import ABCUniverseAlias, ABCAlias, AnyContainerSymbol
from gams.transfer.syms._mixins import SAUAMixin, SAUAPVEMixin
from typing import Union, List, TYPE_CHECKING

if TYPE_CHECKING:
    from gams.transfer import Container, Set, Alias


class UniverseAlias(SAUAMixin, SAUAPVEMixin, ABCUniverseAlias):
    """
    Universe Alias symbol

    Parameters
    ----------
    container : Container
    name : str

    Examples
    --------
    >>> import gams.transfer as gt
    >>> m = gt.Container()
    >>> i = gt.Set(m, "i", records=["seattle", "san-diego"])
    >>> j = gt.Set(m, "j", records=["new-york", "chicago", "topeka"])
    >>> ij = gt.UniverseAlias(m, "ij")
    >>> print(ij.toList())
    ['seattle', 'san-diego', 'new-york', 'chicago', 'topeka']

    Attributes
    ----------
    alias_with : str
        Aliased object
    container : Container object
        Container where the symbol exists
    description : str
        description of symbol
    dimension : int
        The dimension of symbol
    domain : List[str]
        List of domains given as string (* for universe set)
    domain_labels : List[str]
        The column headings for the records DataFrame
    domain_names : List[str]
        String version of domain names
    domain_type : str
        The state of domain links
    is_singleton : bool
        Flag that identifies if the the alias is singleton
    modified : bool
        Flag that identifies if the symbol has been modified
    name : str
        Name of the symbol
    number_records : int
        The number of symbol records
    records : DataFrame
        The main symbol records
    summary : dict
        A dict of only the metadata       
    """
    @classmethod
    def _from_gams(cls, container, name):
        # create new symbol object
        obj = UniverseAlias.__new__(cls)

        # set private properties directly
        obj._requires_state_check = False
        obj._container = container
        container._requires_state_check = True
        obj._name = name
        obj._modified = True

        # typing
        obj._gams_type = gdx.GMS_DT_ALIAS
        obj._gams_subtype = 0

        # add to container
        container.data.update({name: obj})

        return obj

    def __new__(cls, *args, **kwargs):
        if len(args) == 0:
            return object.__new__(UniverseAlias)

        try:
            symobj = args[0][args[1]]
        except:
            symobj = None

        if symobj is None:
            return object.__new__(UniverseAlias)
        else:
            if isinstance(symobj, ABCUniverseAlias):
                return symobj
            else:
                raise TypeError(
                    f"Cannot overwrite symbol `{symobj.name}` in container because it is not a UniverseAlias object)"
                )

    def __init__(self, container: "Container", name: str) -> None:
        self._requires_state_check = True
        self.container = container
        self.name = name
        self.modified = True
        self._gams_type = gdx.GMS_DT_ALIAS
        self._gams_subtype = 0
        container.data.update({name: self})

    def __delitem__(self):
        # TODO: add in some functionality that might relax the symbols down to a different domain
        #       This function would mimic the <Container>.removeSymbols() method -- is more pythonic
        del self.container.data[self.name]

    def __repr__(self):
        return f"<UniverseAlias `{self.name}` ({hex(id(self))})>"

    @property
    def is_singleton(self) -> bool:
        """
        Whether a symbol is a singleton set

        Returns
        -------
        bool
            Always False
        """
        return False

    def _assert_is_valid(self):
        if self._requires_state_check:
            if self.container is None:
                raise Exception(
                    "Symbol is not currently linked to a container, "
                    "must add it to a container in order to be valid"
                )

            # if no exceptions, then turn self._requires_state_check 'off'
            self._requires_state_check = False

    @property
    def alias_with(self) -> str:
        """
        Returns aliased object

        Returns
        -------
        str
            Always "*"
        """
        return "*"

    @property
    def domain_names(self) -> List[str]:
        """
        Always ["*"] for universe alias

        Returns
        -------
        List[str]
            Always ["*"]
        """
        return ["*"]

    @property
    def domain_labels(self) -> List[str]:
        """
        Always ["uni"] for universe alias

        Returns
        -------
        List[str]
            Always ["uni"]
        """
        return ["uni"]

    @property
    def domain(self) -> List[str]:
        """
        Always ["*"] for universe alias

        Returns
        -------
        List[str]
            Always ["*"]
        """
        return ["*"]

    @property
    def description(self) -> str:
        """
        Always 'Aliased with *' for universe alias

        Returns
        -------
        str
            Always 'Aliased with *'
        """
        return "Aliased with *"

    @property
    def dimension(self) -> int:
        """
        Always 1 for universe alias

        Returns
        -------
        int
            Always 1
        """
        return 1

[docs] def toList(self) -> Union[List[str], None]: """ Convenience method to return symbol records as a python list Returns ------- List[str] | None A list of symbol records Examples -------- >>> m = gt.Container() >>> i = gt.Set(m, "i", records=["seattle", "san-diego"]) >>> j = gt.Set(m, "j", records=["new-york", "chicago", "topeka"]) >>> ij = gt.UniverseAlias(m, "ij") >>> print(ij.toList()) ['seattle', 'san-diego', 'new-york', 'chicago', 'topeka'] """ if self.records is not None: return self.records.set_index(self.records.columns[0]).index.to_list() return None
@property def records(self) -> Union[pd.DataFrame, None]: """ The main symbol records Returns ------- DataFrame | None Records dataframe if there are records, otherwise None. """ if self.isValid(): return pd.DataFrame( data=self.container.getUELs(), columns=self.domain_labels ) return None @property def number_records(self) -> Union[int]: """ Number of symbol records (i.e., returns len(records) if not NAN) Returns ------- int Number of symbol records """ if self.isValid(): return len(self.records) return float("nan") @property def domain_type(self) -> str: """ Always none for universe alias Returns ------- str Always 'none' """ return "none"
[docs] def getUELs(self, ignore_unused: bool = False) -> Union[List[str], None]: """ Gets UELs from the Container. Returns only UELs in the data if ignore_unused=True, otherwise return all UELs. Parameters ---------- ignore_unused : bool, optional Whether to get all UELs or only used ones, by default False Returns ------- list | None A list of UELs if the symbol is valid, otherwise None. """ if self.isValid(): return self.container.getUELs(ignore_unused=ignore_unused) return None
@property def summary(self) -> dict: """ Returns a dict of only the metadata Returns ------- dict Outputs a dict of only the metadata """ return { "name": self.name, "description": self.description, "alias_with": self.alias_with, }
[docs] def equals( self, other: Union["Set", "Alias"], check_meta_data: bool = True, verbose: bool = False, ): """ Used to compare the symbol to another symbol. If check_meta_data=True then check that symbol name and description are the same, otherwise skip. If verbose=True will return an exception from the asserter describing the nature of the difference. Parameters ---------- other : Set or Alias The other symbol (Set or Alias) to compare with the current alias. check_meta_data : bool, optional If True, compare the metadata of the two symbols, by default True. verbose : bool, optional If True, raise an exception with an explanation of where the symbols differ if they do differ, by default False. Returns ------- bool True if the two symbols are equal in the specified aspects; False if they are not equal and verbose is False. Examples -------- >>> m = gt.Container() >>> i = gt.Set(m, "i", records=["seattle", "san-diego"]) >>> j = gt.Set(m, "j", records=["new-york", "chicago", "topeka"]) >>> ij = gt.UniverseAlias(m, "ij") >>> print(ij.equals(i)) False """ try: # # ARG: other if not isinstance(other, AnyContainerSymbol): raise TypeError( "Argument 'other' must be a GAMS Container Symbol object" ) # adjustments if isinstance(other, ABCAlias): other = other.alias_with # # ARG: self & other if not isinstance(self, type(other)): raise TypeError( f"Symbol are not of the same type (`{type(self)}` != `{type(other)}`)" ) # # ARG: check_meta_data if not isinstance(check_meta_data, bool): raise TypeError("Argument 'check_meta_data' must be type bool") # # Mandatory checks if not self.isValid(): raise Exception( f"Cannot compare objects because `{self.name}` is not a valid symbol object" "Use `<symbol>.isValid(verbose=True)` to debug further." ) if not other.isValid(): raise Exception( f"Cannot compare objects because `{other.name}` is not a valid symbol object" "Use `<symbol>.isValid(verbose=True)` to debug further." ) # # Check metadata (optional) if check_meta_data: if self.name != other.name: raise Exception( f"Symbol names do not match (`{self.name}` != `{other.name}`)" ) return True except Exception as err: if verbose: raise err else: return False
[docs] def pivot(self, *args, **kwargs) -> None: """ Pivot the records DataFrame of a symbol that has a dimension more than 1. Always raises a dimensionality exception. """ raise Exception( "Pivoting operations only possible on symbols with dimension > 1, " f"symbol dimension is {self.dimension}" )
[docs] def getSparsity(self) -> float: """ Get the sparsity of the symbol w.r.t the cardinality Returns ------- float Always 0 """ return 0.0