from __future__ import annotations
import os
import threading
from typing import TYPE_CHECKING, Any
import gams.transfer as gt
from gams.core.gdx import GMS_DT_ALIAS
import gamspy as gp
import gamspy._algebra.condition as condition
import gamspy._algebra.expression as expression
import gamspy._algebra.operable as operable
import gamspy._symbols.implicits as implicits
import gamspy._validation as validation
import gamspy.utils as utils
from gamspy._symbols.set import SetMixin
from gamspy._symbols.symbol import Symbol
from gamspy.exceptions import ValidationError
if TYPE_CHECKING:
from gamspy import Container, Set
from gamspy._algebra.expression import Expression
from gamspy._algebra.operation import Operation
from gamspy._symbols.implicits import ImplicitSet
from gamspy._types import IndexType
[docs]
class Alias(gt.Alias, operable.Operable, Symbol, SetMixin):
"""
Represents an Alias symbol in GAMS.
https://gamspy.readthedocs.io/en/latest/user/basics/alias.html
Parameters
----------
container : Container
Container of the alias.
name : str, optional
Name of the alias.
alias_with : Set
Alias set object.
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i")
>>> j = gp.Alias(m, "j", i)
"""
@classmethod
def _constructor_bypass(
cls, container: Container, name: str, alias_with: Set | Alias
):
# create new symbol object
obj = Alias.__new__(cls, container, name, alias_with)
# set private properties directly
obj._requires_state_check = False
obj._container = container
container._requires_state_check = True
obj._name = name
obj._alias_with = alias_with
obj._modified = True
# typing
obj._gams_type = GMS_DT_ALIAS
obj._gams_subtype = 1
# add to container
container.data.update({name: obj})
# gamspy attributes
obj.where = condition.Condition(obj)
obj._latex_name = name.replace("_", r"\_")
obj.container._add_statement(obj)
obj._metadata = {}
return obj
def __new__(
cls,
container: Container | None = None,
name: str | None = None,
alias_with: Set | Alias | None = None,
):
if container is not None and not isinstance(container, gp.Container):
raise TypeError(
f"Container must of type `Container` but found {type(container)}"
)
if name is None:
return object.__new__(cls)
else:
if not isinstance(name, str):
raise TypeError(f"Name must of type `str` but found {type(name)}")
try:
if not container:
container = gp._ctx_managers[
(os.getpid(), threading.get_native_id())
]
symbol = container[name]
if isinstance(symbol, cls):
return symbol
raise TypeError(
f"Cannot overwrite symbol `{name}` in container"
" because it is not an Alias object)"
)
except KeyError:
return object.__new__(cls)
def __init__(
self,
container: Container | None = None,
name: str | None = None,
alias_with: Set | Alias = None, # type: ignore
):
self._metadata: dict[str, Any] = {}
self._assignment: Expression | None = None
# does symbol exist
has_symbol = False
if isinstance(getattr(self, "container", None), gp.Container):
has_symbol = True
if has_symbol:
# reset some properties
self._requires_state_check = True
self.container._requires_state_check = True
self._modified = True
self.alias_with = alias_with
else:
if container is None:
try:
container = gp._ctx_managers[
(os.getpid(), threading.get_native_id())
]
except KeyError as e:
raise ValidationError("Alias requires a container.") from e
assert container is not None
if name is not None:
name = validation.validate_name(name)
else:
name = container._get_symbol_name(prefix="a")
super().__init__(container, name, alias_with)
self._latex_name = self.name.replace("_", r"\_")
validation.validate_container(self, self.domain)
self.where = condition.Condition(self)
self.container._add_statement(self)
self._modified = False
self.container._synch_with_gams()
def _serialize(self) -> dict:
info: dict[str, Any] = {"_metadata": self._metadata}
if self._assignment is not None:
info["_assignment"] = self._assignment.getDeclaration()
return info
def _deserialize(self, info: dict) -> None:
for key, value in info.items():
if key == "_assignment":
left, right = value.split(" = ")
value = expression.Expression(left, "=", right)
setattr(self, key, value)
def __bool__(self):
raise ValidationError(
"Alias cannot be used as a truth value. Use len(<symbol>.records) instead."
)
def __repr__(self) -> str:
return f"Alias(name='{self.name}', alias_with={self.alias_with})"
def __getitem__(self, indices: IndexType) -> ImplicitSet:
domain = validation.validate_domain(self, indices)
return implicits.ImplicitSet(self, name=self.name, domain=domain)
def __setitem__(self, indices: IndexType, rhs: Expression | Operation | bool | str):
# self[domain] = rhs
domain = validation.validate_domain(self, indices)
if isinstance(rhs, bool):
rhs = "yes" if rhs is True else "no"
statement = expression.Expression(
implicits.ImplicitSet(self, name=self.name, domain=domain),
"=",
rhs,
)
# Cannot validate definition if we are in a gp.Loop since the control indices can be provided by the gp.Loop
if not self.container._in_loop:
statement._validate_definition(utils._unpack(domain))
self.container._add_statement(statement)
self._assignment = statement
if self.alias_with.synchronize: # type: ignore
self.container._synch_with_gams(
gams_to_gamspy=True, load_symbols=[self.alias_with]
)
def _setRecords(self, records: Any, *, uels_on_axes: bool = False) -> None:
super().setRecords(records, uels_on_axes)
if gp.get_option("DROP_DOMAIN_VIOLATIONS"):
if self.hasDomainViolations():
self._domain_violations = self.getDomainViolations()
self.dropDomainViolations()
else:
self._domain_violations = None
@property
def synchronize(self):
raise ValidationError(
"Each Alias object is tied to a Set. Change the synchronization setting of the set instead."
)
@synchronize.setter
def synchronize(self, value: bool):
assert self.alias_with is not None
raise ValidationError(
f"Alias `{self.name}` object is tied to a Set `{self.alias_with.name}`."
f"Change the synchronization setting of the Set `{self.alias_with.name}` instead."
)
[docs]
def gamsRepr(self) -> str:
"""
Returns the string representation of this Alias in the GAMS language.
(e.g., 'j').
Returns
-------
str
The GAMS string representation.
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", domain=["*"], records=['i1','i2'])
>>> j = gp.Alias(m, "j", i)
>>> j.gamsRepr()
'j'
"""
return self.name
[docs]
def getDeclaration(self) -> str:
"""
Returns the GAMS declaration statement for this Alias.
(e.g., 'Alias(i, j);').
Returns
-------
str
The GAMS declaration string.
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2'])
>>> j = gp.Alias(m, "j", i)
>>> j.getDeclaration()
'Alias(i,j);'
"""
return f"Alias({self.alias_with.name},{self.name});"
def getAssignment(self) -> str:
"""
Latest assignment to the Set in GAMS
Returns
-------
str
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2'])
>>> j = gp.Alias(m, "j", alias_with=i)
>>> j['i1'] = False
>>> j.getAssignment()
'j("i1") = no;'
"""
if self._assignment is None:
raise ValidationError("Set was not assigned!")
return self._assignment.getDeclaration()