from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import gamspy._algebra.condition as condition
import gamspy._algebra.domain as domain
import gamspy._algebra.expression as expression
import gamspy._algebra.operable as operable
import gamspy._symbols as syms
import gamspy._symbols.implicits as implicits
import gamspy._validation as validation
import gamspy.utils as utils
from gamspy.exceptions import ValidationError
if TYPE_CHECKING:
import pandas as pd
from gamspy._algebra import Domain
from gamspy._algebra.condition import Condition
from gamspy._algebra.expression import Expression
from gamspy._symbols import Alias, Parameter, Set
from gamspy._symbols.implicits import (
ImplicitParameter,
ImplicitSet,
ImplicitVariable,
)
from gamspy.math.misc import MathOp
class Operation(operable.Operable):
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
rhs: (
Expression
| Operation
| MathOp
| ImplicitSet
| ImplicitVariable
| ImplicitParameter
| int
| bool
),
op_name: str,
):
self.op_domain = utils._to_list(domain) # type: ignore
assert len(self.op_domain) > 0, "Operation requires at least one index"
self.rhs = rhs
self._op_name = op_name
self.raw_domain = self._get_raw_domain()
# allow conditions
self.where = condition.Condition(self)
self._bare_op_domain = utils._get_set(self.op_domain)
self.container = self.raw_domain[0].container
self.domain: list[Set | Alias] = []
self._operation_indices = []
if isinstance(rhs, condition.Condition):
rhs = rhs.conditioning_on # type: ignore
if not isinstance(rhs, (bool, float, int)):
for i, x in enumerate(rhs.domain): # type: ignore
try:
sum_index = self._bare_op_domain.index(x)
self._operation_indices.append((i, sum_index))
except ValueError:
self.domain.append(x)
self.dimension: int = validation.get_dimension(self.domain)
controlled_domain = [d for d in self._bare_op_domain]
controlled_domain.extend(getattr(rhs, "controlled_domain", []))
self.controlled_domain = list(set(controlled_domain))
def __getitem__(self, indices: Sequence | str):
domain = validation.validate_domain(self, indices)
for index, sum_index in self._operation_indices:
domain.insert(index, self._bare_op_domain[sum_index])
if isinstance(self.rhs, (bool, float, int)):
return Operation(self.op_domain, self.rhs, self._op_name)
return Operation(self.op_domain, self.rhs[domain], self._op_name) # type: ignore
@property
def records(self) -> pd.DataFrame | None:
"""
Evaluates the operation and returns the resulting records.
Returns
-------
pd.DataFrame | None
"""
assert self.container is not None
temp_name = "a" + utils._get_unique_name()
temp_param = syms.Parameter._constructor_bypass(
self.container, temp_name, self.domain
)
temp_param[...] = self
del self.container.data[temp_name]
return temp_param.records
def toValue(self) -> float | None:
"""
Convenience method to return the records of the operation as a Python float. Only possible if there is a single record as a result of the operation.
Returns
-------
float | None
"""
records = self.records
if records is not None:
return records["value"][0]
return records
def toList(self) -> list | None:
"""
Convenience method to return the records of the operation as a list.
Returns
-------
list | None
"""
records = self.records
if records is not None:
return records.values.tolist()
return None
def _get_raw_domain(self) -> list[Set | Alias | ImplicitSet]:
raw_domain = []
for elem in self.op_domain:
if isinstance(elem, condition.Condition):
if isinstance(elem.conditioning_on, implicits.ImplicitSet):
raw_domain.append(elem.conditioning_on.parent)
elif isinstance(elem.conditioning_on, (syms.Set, syms.Alias)):
raw_domain.append(elem.conditioning_on)
elif isinstance(elem.conditioning_on, domain.Domain):
raw_domain += elem.conditioning_on.sets
elif isinstance(elem, domain.Domain):
raw_domain += elem.sets
elif isinstance(elem, implicits.ImplicitSet):
raw_domain.append(elem)
else:
raw_domain.append(elem)
return raw_domain
def _validate_operation(
self, control_stack: list[Set | Alias | ImplicitSet]
) -> None:
for elem in self.raw_domain:
if isinstance(elem, implicits.ImplicitSet):
control_stack += [
member
for member in elem.domain
if member not in control_stack
] + [elem.parent]
if elem in control_stack:
raise ValidationError(f"Set {elem} is already in control")
stack = control_stack + self.raw_domain
if isinstance(self.rhs, expression.Expression):
self.rhs._validate_definition(utils._unpack(stack))
elif isinstance(self.rhs, Operation):
self.rhs._validate_operation(utils._unpack(stack))
def _get_index_str(self) -> str:
if len(self.op_domain) == 1:
op_domain = self.op_domain[0]
representation = op_domain.gamsRepr()
if isinstance(op_domain, condition.Condition):
# sum((l(root,s,s1,s2) $ od(root,s)),1); -> not valid
# sum(l(root,s,s1,s2) $ od(root,s),1); -> valid
return representation[1:-1]
return representation
return (
"("
+ ",".join([index.gamsRepr() for index in self.op_domain])
+ ")"
)
def __eq__(self, other):
return expression.Expression(self, "=e=", other)
def __ne__(self, other):
return expression.Expression(self, "ne", other)
def _replace_operations(self, output: str) -> str:
output = output.replace("=l=", "<=")
output = output.replace("=g=", ">=")
output = output.replace("=e=", "eq")
return output
def gamsRepr(self) -> str:
# Ex: sum((i,j), c(i,j) * x(i,j))
output = f"{self._op_name}("
index_str = self._get_index_str()
output += index_str
output += ","
if isinstance(self.rhs, float):
self.rhs = utils._map_special_values(self.rhs)
if isinstance(self.rhs, bool):
self.rhs = (
"yes" if self.rhs is True else "no" # type: ignore
)
expression_str = (
str(self.rhs)
if isinstance(self.rhs, (bool, float, int, str))
else self.rhs.gamsRepr()
)
output += expression_str
output += ")"
output = self._replace_operations(output)
return output
def latexRepr(self) -> str:
"""
Representation of this operation in Latex.
Returns
-------
str
"""
op_map = {
"sum": "sum",
"prod": "prod",
"smax": "max",
"smin": "min",
"sand": "sand",
"sor": "sor",
}
indices = []
given_condition = None
for index in self.op_domain:
if isinstance(index, condition.Condition):
indices.append(index.conditioning_on)
given_condition = index.condition
else:
indices.append(index)
index_str = ",".join([index.latexRepr() for index in indices])
if given_condition is not None:
condition_str = str(given_condition)
if hasattr(given_condition, "latexRepr"):
condition_str = given_condition.latexRepr()
index_str += " ~ | ~ " + condition_str
expression_str = (
str(self.rhs)
if isinstance(self.rhs, (int, float, str))
else self.rhs.latexRepr()
)
representation = (
f"\\{op_map[self._op_name]}_\\text{{{index_str}}} {expression_str}"
)
return representation
[docs]
class Sum(Operation):
"""
Represents a sum operation over a domain.
Parameters
----------
domain : Set | Alias | ImplicitSet | Sequence[Set | Alias], Domain, Condition
expression : (
Expression
| MathOp
| ImplicitVariable
| ImplicitParameter
| int
| bool
| Variable
| Parameter
| Operation
)
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
>>> v = gp.Variable(m, "v")
>>> e = gp.Equation(m, "e", type="eq")
>>> d = gp.Parameter(m, "d", domain=[i], records=[("i1", 1), ("i2", 2), ("i3", 4)])
>>> e[...] = gp.Sum(i, d[i]) <= v
"""
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
expression: Operation
| Expression
| MathOp
| ImplicitSet
| ImplicitParameter
| ImplicitVariable
| int
| bool,
):
super().__init__(domain, expression, "sum")
def __repr__(self) -> str:
return f"Sum(domain={self.domain}, expression={self.rhs})"
[docs]
def gamsRepr(self):
"""
Representation of the Sum operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Parameter, Variable, Sum
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> c = Parameter(m, "c", domain=i)
>>> v = Variable(m, "v", domain=i)
>>> Sum(i, c[i]*v[i]).gamsRepr()
'sum(i,(c(i) * v(i)))'
"""
repr = super().gamsRepr()
return repr
[docs]
class Product(Operation):
"""
Represents a product operation over a domain.
Parameters
----------
domain : Set | Alias | Sequence[Set | Alias], Domain, Expression
expression : (
Expression
| MathOp
| ImplicitVariable
| ImplicitParameter
| int
| bool
| Variable
| Parameter
| Operation
)
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
>>> v = gp.Variable(m, "v")
>>> e = gp.Equation(m, "e", type="eq")
>>> p = gp.Parameter(m, "p", domain=[i], records=[("i1", 1), ("i2", 2), ("i3", 4)])
>>> e[...] = gp.Product(i, p[i]) <= v
"""
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
expression: Operation
| Expression
| MathOp
| ImplicitSet
| ImplicitParameter
| ImplicitVariable
| int
| bool,
):
super().__init__(domain, expression, "prod")
def __repr__(self) -> str:
return f"Product(domain={self.domain}, expression={self.rhs})"
[docs]
def gamsRepr(self):
"""
Representation of the Product operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Parameter, Variable, Product
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> c = Parameter(m, "c", domain=i)
>>> v = Variable(m, "v", domain=i)
>>> Product(i, c[i]*v[i]).gamsRepr()
'prod(i,(c(i) * v(i)))'
"""
repr = super().gamsRepr()
return repr
[docs]
class Smin(Operation):
"""
Represents a smin operation over a domain.
Parameters
----------
domain : Set | Alias | Sequence[Set | Alias], Domain, Expression
expression : (
Expression
| MathOp
| ImplicitVariable
| ImplicitParameter
| int
| bool
| Variable
| Parameter
| Operation
)
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
>>> v = gp.Variable(m, "v")
>>> e = gp.Equation(m, "e", type="eq")
>>> p = gp.Parameter(m, "p", domain=[i], records=[("i1", 1), ("i2", 2), ("i3", 4)])
>>> e[...] = gp.Smin(i, p[i]) <= v
"""
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
expression: Operation
| Expression
| MathOp
| ImplicitSet
| ImplicitParameter
| ImplicitVariable
| int
| bool,
):
super().__init__(domain, expression, "smin")
def __repr__(self) -> str:
return f"Smin(domain={self.domain}, expression={self.rhs})"
[docs]
def gamsRepr(self):
"""
Representation of the Smin operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Parameter, Variable, Smin
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> c = Parameter(m, "c", domain=i)
>>> v = Variable(m, "v", domain=i)
>>> Smin(i, c[i]*v[i]).gamsRepr()
'smin(i,(c(i) * v(i)))'
"""
repr = super().gamsRepr()
return repr
[docs]
class Smax(Operation):
"""
Represents a smax operation over a domain.
Parameters
----------
domain : Set | Alias | Sequence[Set | Alias], Domain, Expression
expression : (
Expression
| MathOp
| ImplicitVariable
| ImplicitParameter
| int
| bool
| Variable
| Parameter
| Operation
)
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
>>> v = gp.Variable(m, "v")
>>> e = gp.Equation(m, "e", type="eq")
>>> p = gp.Parameter(m, "p", domain=[i], records=[("i1", 1), ("i2", 2), ("i3", 4)])
>>> e[...] = gp.Smax(i, p[i]) <= v
"""
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
expression: Operation
| Expression
| MathOp
| ImplicitSet
| ImplicitParameter
| ImplicitVariable
| int
| bool,
):
super().__init__(domain, expression, "smax")
def __repr__(self) -> str:
return f"Smax(domain={self.domain}, expression={self.rhs})"
[docs]
def gamsRepr(self):
"""
Representation of the Smax operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Parameter, Variable, Smax
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> c = Parameter(m, "c", domain=i)
>>> v = Variable(m, "v", domain=i)
>>> Smax(i, c[i]*v[i]).gamsRepr()
'smax(i,(c(i) * v(i)))'
"""
repr = super().gamsRepr()
return repr
[docs]
class Sand(Operation):
"""
Represents a sand operation over a domain.
Parameters
----------
domain : Set | Alias | Sequence[Set | Alias], Domain, Expression
expression : (
Expression
| MathOp
| ImplicitVariable
| ImplicitParameter
| int
| bool
| Variable
| Parameter
| Operation
)
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
>>> p = gp.Parameter(m, domain=i)
>>> p[i] = gp.math.uniformInt(0,1)
>>> result = gp.Parameter(m)
>>> result[:] = gp.Sand(i, p[i])
"""
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
expression: Operation
| Expression
| MathOp
| ImplicitSet
| ImplicitParameter
| ImplicitVariable
| int
| bool,
):
super().__init__(domain, expression, "sand")
def __repr__(self) -> str:
return f"Sand(domain={self.domain}, expression={self.rhs})"
[docs]
def gamsRepr(self):
"""
Representation of the Sand operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Parameter, Variable, Sor, Sand
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> v = Variable(m, "v", domain=i, type="binary")
>>> Sand(i, v[i]).gamsRepr()
'sand(i,v(i))'
"""
repr = super().gamsRepr()
return repr
[docs]
class Sor(Operation):
"""
Represents a sor operation over a domain.
Parameters
----------
domain : Set | Alias | Sequence[Set | Alias], Domain, Expression
expression : (
Expression
| MathOp
| ImplicitVariable
| ImplicitParameter
| int
| bool
| Variable
| Parameter
| Operation
)
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
>>> p = gp.Parameter(m, domain=i)
>>> p[i] = gp.math.uniformInt(0,1)
>>> result = gp.Parameter(m)
>>> result[:] = gp.Sor(i, p[i])
"""
def __init__(
self,
domain: Set
| Alias
| ImplicitSet
| Sequence[Set | Alias]
| Domain
| Condition,
expression: Operation
| Expression
| MathOp
| ImplicitSet
| ImplicitParameter
| ImplicitVariable
| int
| bool,
):
super().__init__(domain, expression, "sor")
def __repr__(self) -> str:
return f"Sor(domain={self.domain}, expression={self.rhs})"
[docs]
def gamsRepr(self):
"""
Representation of the Sor operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Parameter, Variable, Sor
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> v = Variable(m, "v", domain=i, type="binary")
>>> Sor(i, v[i]).gamsRepr()
'sor(i,v(i))'
"""
repr = super().gamsRepr()
return repr
[docs]
class Ord(operable.Operable):
"""
The operator ord may be used only with one-dimensional sets.
Parameters
----------
set : Set | Alias
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> t = gp.Set(m, name="t", description="time periods", records=[str(x) for x in range(1985, 1996)])
>>> val = gp.Parameter(m, name="val", domain=[t])
>>> val[t] = gp.Ord(t)
"""
def __init__(self, symbol: Set | Alias):
if not isinstance(symbol, (syms.Set, syms.Alias)):
raise ValidationError(
"Ord operation is only for Set and Alias objects!"
)
self._symbol = symbol
self.container = symbol.container
self.domain: list[Set | Alias] = []
self.where = condition.Condition(self)
def __eq__(self, other):
return expression.Expression(self, "eq", other)
def __ge__(self, other):
return expression.Expression(self, ">=", other)
def __le__(self, other):
return expression.Expression(self, "<=", other)
def __ne__(self, other):
return expression.Expression(self, "ne", other)
[docs]
def gamsRepr(self) -> str:
"""
Representation of the Ord operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Ord
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> Ord(i).gamsRepr()
'ord(i)'
"""
return f"ord({self._symbol.name})"
[docs]
def latexRepr(self) -> str:
"""
Representation of Ord function in Latex.
Returns
-------
str
"""
return f"ord({self._symbol.name})"
[docs]
class Card(operable.Operable):
"""
The operator card may be used with any symbol and returns its number of records.
Parameters
----------
symbol : Set | Alias | Parameter | Variable | Equation | Model
Examples
--------
>>> import gamspy as gp
>>> m = gp.Container()
>>> t = gp.Set(m, name="t", description="time periods", records=[str(x) for x in range(1985, 1996)])
>>> s = gp.Parameter(m, name="s")
>>> s[...] = gp.Card(t)
"""
def __init__(
self,
symbol: Set | Alias | Parameter,
) -> None:
if not isinstance(symbol, (syms.Set, syms.Alias, syms.Parameter)):
raise ValidationError(
"Card operation is only for Set, Alias and Parameter objects!"
)
self._symbol = symbol
self.container = symbol.container
self.domain: list[Set | Alias] = []
self.where = condition.Condition(self)
def __eq__(self, other) -> Expression: # type: ignore
return expression.Expression(self, "eq", other)
def __ge__(self, other):
return expression.Expression(self, ">=", other)
def __le__(self, other):
return expression.Expression(self, "<=", other)
def __ne__(self, other): # type: ignore
return expression.Expression(self, "ne", other)
def __bool__(self):
raise ValidationError(
"Card operation cannot be used as a truth value. Use len(<symbol>.records) instead."
)
[docs]
def gamsRepr(self) -> str:
"""
Representation of the Card operation in GAMS language.
Returns
-------
str
Examples
--------
>>> from gamspy import Container, Set, Card
>>> m = Container()
>>> i = Set(m, "i", records=['i1','i2', 'i3'])
>>> Card(i).gamsRepr()
'card(i)'
"""
return f"card({self._symbol.name})"
[docs]
def latexRepr(self) -> str:
"""
Representation of Card function in Latex.
Returns
-------
str
"""
return f"card({self._symbol.name})"