Python-based algebraic modeling interface to GAMS
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
import sys
sys.path.insert(0, os.path.abspath('../src'))


# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "GAMSpy"
copyright = "2023, GAMS Development Corporation"
author = "GAMS"
release = "0.9.0"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.doctest",
    "sphinx.ext.duration",
    "sphinx.ext.intersphinx",
    "sphinx.ext.mathjax",
    "sphinx.ext.todo",
    "sphinx.ext.viewcode",
    "sphinx.ext.coverage",
    "sphinx.ext.graphviz",
    "sphinx.ext.ifconfig",
    "sphinx_design",
    "sphinx_copybutton",
    "nbsphinx",
    "numpydoc",
    "matplotlib.sphinxext.plot_directive",
    "IPython.sphinxext.ipython_console_highlighting",
    "IPython.sphinxext.ipython_directive",
    "sphinx.ext.mathjax",
    "sphinx_design",
    "sphinx_copybutton",
    "sphinx.ext.napoleon",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "pydata_sphinx_theme"
html_static_path = ["_static"]
html_css_files = ['custom.css']
html_show_sourcelink = False
html_theme_options = {
    "icon_links": [
        {
            "name": "GitHub",
            "url": "https://github.com/GAMS-dev",
            "icon": "fa-brands fa-square-github",
            "type": "fontawesome",
        },
        {
            "name": "Twitter",
            "url": "https://twitter.com/GamsSoftware",
            "icon": "fa-brands fa-square-twitter",
        },
    ],
    "logo": {
        "image_light": "_static/gamspy_logo.png",
        "image_dark": "_static/gamspy_logo.png",
    },
    "show_nav_level": 1,
}

autodoc_member_order = "bysource"

docs/conf.py
GAMS-dev/gamspy
import urllib.error
import urllib.request
from pathlib import Path
from pathlib import PurePosixPath

import pandas as pd


def open_url(request):
    try:
        return urllib.request.urlopen(request)
    except urllib.error.HTTPError as e:
        return e


files = list(Path("tests/integration/models").glob("*.py"))

csv = {"Model": [], "GAMSPy": [], "GAMS": [], "Data": []}

for f in files:
    name = f.stem
    title = f.read_text().split("\n")[1]

    # Find data files
    data_file = ""
    data = False
    matching_files = list(Path(f"tests/integration/models").glob(f"{name}.*"))
    if len(matching_files) > 1:
        data = True
        for m in matching_files:
            if m.suffix != ".py":
                data_file = m
                data_name = m.name

    # configure .rst file
    rst_head = (
        ":orphan:\n\n"
        f".. _{name}:\n\n{title}\n{'=' * len(title)}\n\n"
        f":download:`{f.name} <{PurePosixPath('../../..', f)}>` "
    )

    rst_foot = (
        "\n\n.. literalinclude::"
        f" {PurePosixPath(f'../../../tests/integration/models/{name}.py')}\n"
    )

    if data:
        data_link = (
            f":download:`{data_name} <{PurePosixPath('../..', data_file)}>`"
        )
        rst_data = (
            f"|{data_name}|\n\n"
            f".. |{data_name}| replace::\n"
            f"   :download:`{data_name} <{PurePosixPath('../../..', data_file)}>`\n\n"
        )
        rst_str = rst_head + rst_data + rst_foot
    else:
        data_link = ""
        rst_str = rst_head + rst_foot

    # model libraries to check
    links = [
        f"https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_{name}.html",
        f"https://www.gams.com/latest/finlib_ml/libhtml/finlib_{name}.html",
        f"https://www.gams.com/latest/noalib_ml/libhtml/noalib_{name}.html",
        f"https://www.gams.com/latest/psoptlib_ml/libhtml/psoptlib_{name}.html",
    ]

    # check in which lib the model is
    for link in links:
        found = False
        if open_url(link).status == 200:
            found = True
            # Table
            csv["Model"].append(name)
            csv["GAMSPy"].append(f":ref:`GAMSPy <{name}>`")
            csv["GAMS"].append(f"`GAMS <{link}>`__")
            csv["Data"].append(data_link)
            # write rst
            with open(f"docs/examples/model_lib/{name}.rst", "w") as rst_file:
                rst_file.write(rst_str)
            break
    if not found:
        print(f"{name} not found in lib")

# write model lib table
pd.DataFrame(csv).sort_values(
    by="Model", key=lambda col: col.str.lower()
).to_csv(Path("docs/examples/model_lib/table.csv"), index=False)

docs/user/generate_model_lib.py
GAMS-dev/gamspy
import os
import platform
import subprocess
import sys
import tempfile


platform_to_job_prefix = {
    "windows": "test-wei-",
    "linux": "test-leg-",
    "mac_x86_64": "test-deg-",
    "mac_arm64": "test-dac-",
}


def get_default_platform():
    operating_system = platform.system().lower()
    architecture = platform.machine()

    if operating_system == "darwin":
        return f"mac_{architecture}"

    return operating_system


def get_job_name():
    job_prefix = platform_to_job_prefix[get_default_platform()]
    major = sys.version_info.major
    minor = sys.version_info.minor
    return f"{job_prefix}{major}.{minor}"


def get_artifacts():
    token = os.environ["gamspy_base_token"]
    repo = os.environ["gamspy_base_artifacts"]
    job_name = get_job_name()
    repo = repo.replace("job_name", job_name)

    command = [
        "curl",
        "--location",
        "--output",
        "artifacts.zip",
        "--header",
        f"PRIVATE-TOKEN: {token}",
        repo,
    ]

    subprocess.run(command, check=True)

    command = ["unzip", "artifacts.zip"]
    subprocess.run(command, check=True)


def install_transfer():
    command = [
        "pip",
        "install",
        "gamsapi[transfer, connect]",
        "--find-links",
        ".",
        "--force-reinstall",
    ]

    subprocess.run(command, check=True)


def install_gamspy_base():
    command = [
        "pip",
        "install",
        "gamspy_base",
        "--find-links",
        "gamspy_base/dist",
        "--force-reinstall",
    ]

    subprocess.run(command, check=True)


def install_gamspy():
    subprocess.run(["python", "setup.py", "bdist_wheel"])

    command = [
        "pip",
        "install",
        "gamspy[dev,test]",
        "--find-links",
        "dist",
        "--force-reinstall",
        "--no-deps",
        "--no-cache",
    ]

    subprocess.run(command, check=True)


def install_gams_license():
    lice = os.environ["GAMS_LICENSE"]
    command = ["gamspy", "install", "license"]

    try:
        f = tempfile.NamedTemporaryFile(mode="wt", suffix=".txt", delete=False)
        f.write(lice)
        f.close()
        command.append(f.name)
        subprocess.run(command, check=True)
    finally:
        os.unlink(f.name)


def install_development_dependencies():
    command = ["pip", "install", "-r", "dev_requirements.txt"]

    subprocess.run(command, check=True)


if __name__ == "__main__":
    install_development_dependencies()
    get_artifacts()
    install_transfer()
    install_gamspy_base()
    install_gamspy()
    install_gams_license()

reinstall.py
GAMS-dev/gamspy
from setuptools import setup


setup(
    packages=["gamspy"],
    package_dir={"gamspy": "src/gamspy"},
    install_requires=["gamsapi[transfer]==45.0.0", "gamspy_base==45.0.0"],
)

setup.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
# flake8: noqa
from .version import __version__
from gamspy._algebra import Card
from gamspy._algebra import Domain
from gamspy._algebra import Number
from gamspy._algebra import Ord
from gamspy._algebra import Product
from gamspy._algebra import Smax
from gamspy._algebra import Smin
from gamspy._algebra import Sum
from gamspy._container import Container
from gamspy._engine import EngineConfig
from gamspy._model import Model
from gamspy._model import ModelStatus
from gamspy._model import Problem
from gamspy._model import Sense
from gamspy._symbols import Alias
from gamspy._symbols import Equation
from gamspy._symbols import EquationType
from gamspy._symbols import Parameter
from gamspy._symbols import Set
from gamspy._symbols import UniverseAlias
from gamspy._symbols import Variable
from gamspy._symbols import VariableType

_order = 0  # Global order for newly generated symbols with no name

__all__ = [
    "Set",
    "Alias",
    "UniverseAlias",
    "Parameter",
    "Variable",
    "Equation",
    "Model",
    "Container",
    "Problem",
    "Sense",
    "VariableType",
    "EquationType",
    "ModelStatus",
    "Sum",
    "Product",
    "Smax",
    "Smin",
    "Domain",
    "Number",
    "Ord",
    "Card",
    "EngineConfig",
]

src/gamspy/__init__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from gamspy._cli.cmdline import main

if __name__ == "__main__":
    main()

src/gamspy/__main__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
# flake8: noqa
from gamspy._algebra.domain import Domain
from gamspy._algebra.number import Number
from gamspy._algebra.operation import Card
from gamspy._algebra.operation import Ord
from gamspy._algebra.operation import Product
from gamspy._algebra.operation import Smax
from gamspy._algebra.operation import Smin
from gamspy._algebra.operation import Sum

src/gamspy/_algebra/__init__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 gamspy._algebra.expression as expression
import gamspy._symbols as syms
import gamspy._symbols.implicits as implicits
import gamspy.utils as utils
from gamspy._symbols.implicits.implicit_symbol import ImplicitSymbol


class Condition:
    """
    Condition class allows symbols to be conditioned.

    Parameters
    ----------
    symbol: Alias | Set | Parameter | Variable | Equation | Expression
        Reference to the symbol to be conditioned.

    >>> muf[i, j] = (2.48 + 0.0084 * rd[i, j]).where[rd[i, j]]
    >>> minw[t].where[tm[t]] = Sum(w.where[td[w, t]], x[w, t]) >= tm[t]
    """

    def __init__(self, symbol):
        self._symbol = symbol

    def __getitem__(self, condition_expression):
        condition = utils._replaceEqualitySigns(
            condition_expression.gamsRepr()
        )
        return expression.Expression(self._symbol, "$", condition)

    def __setitem__(self, condition_expression, right_hand_expression) -> None:
        assert hasattr(
            self._symbol, "container"  # pragma: no cover
        ), f"Container must be defined for symbol {self._symbol.name}"

        if isinstance(self._symbol, ImplicitSymbol):
            self._symbol.container[self._symbol.parent.name]._is_dirty = True
        else:
            self._symbol.container[self._symbol.name]._is_dirty = True

        if isinstance(right_hand_expression, bool):
            right_hand_expression = (
                "yes" if right_hand_expression is True else "no"
            )

        op_type = (
            ".."
            if isinstance(
                self._symbol, (syms.Equation, implicits.ImplicitEquation)
            )
            else "="
        )

        condition = utils._replaceEqualitySigns(
            condition_expression.gamsRepr()
        )

        statement = expression.Expression(
            expression.Expression(self._symbol, "$", condition),
            op_type,
            right_hand_expression,
        )

        self._symbol.container._addStatement(statement)

        if not self._symbol.container.delayed_execution:
            self._symbol.container._run()

src/gamspy/_algebra/condition.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.condition as condition
import gamspy.utils as utils

if TYPE_CHECKING:
    from gamspy import Set, Alias
    from gamspy._symbols.implicits import ImplicitSet


class DomainException(Exception):
    """Exception raised if a domain is not valid."""


class Domain:
    """
    Domain class needed for where statements on multidimensional index list
    in operations

    Parameters
    ----------
    sets: tuple[Set | str]

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> v = gp.Variable(m, "v")
    >>> eq1 = gp.Equation(m, name="eq1")
    >>> eq1 = v == 5

    """

    def __init__(self, *sets: Union["Set", "Alias", "ImplicitSet"]) -> None:
        self._sanity_check(sets)
        self.sets = sets
        self.container = self._find_container()  # type: ignore
        self.where = condition.Condition(self)

    def _sanity_check(
        self, sets: Tuple[Union["Set", "Alias", "ImplicitSet"], ...]
    ):
        if len(sets) < 2:
            raise DomainException("Domain requires at least 2 sets")

        if all(not hasattr(set, "container") for set in sets):
            raise DomainException(
                "At least one of the sets in the domain must be a Set or Alias"
            )

    def _find_container(self):
        for set in self.sets:
            if hasattr(set, "container"):
                return set.container

    def gamsRepr(self) -> str:
        """
        Representation of this Domain in GAMS language.

        Returns
        -------
        str
        """
        return utils._getDomainStr(self.sets)

src/gamspy/_algebra/domain.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import Tuple

import gamspy._algebra.condition as condition
import gamspy._algebra.domain as domain
import gamspy._algebra.operable as operable
import gamspy._symbols as syms
import gamspy._symbols.implicits as implicits
import gamspy.utils as utils


class Expression(operable.Operable):
    """
    Expression of two operands and an operation.

    Parameters
    ----------
    left: str | int | float | Parameter | Variable
        Left operand
    op_type: str
        Operation
    right: str | int | float | Parameter | Variable
        Right operand

    Examples
    --------
    >>> a = Parameter(name="a", records=[["a", 1], ["b", 2], ["c", 3]]))
    >>> b = Parameter(name="b", records=[["a", 1], ["b", 2], ["c", 3]]))
    >>> expression = a * b
    Expression(a, "*", b)
    >>> expression.gamsRepr()
    (a * b)
    """

    def __init__(self, left, op_type, right) -> None:
        self.name = utils._getUniqueName()
        self._left = left
        self._op_type = op_type
        self._right = right
        self.where = condition.Condition(self)

    def __eq__(self, other):  # type: ignore
        return Expression(self, "=e=", other)

    def _fix_condition_paranthesis(self, string: str) -> str:
        if self._op_type == "$":
            left, right = string.split("$", 1)
            right = right.strip()

            if right[0] != "(":
                right = f"({right})"

            string = f"{left}$ {right}"

        return string

    def _get_operand_representations(self) -> Tuple[str, str]:
        # Builtin Python types do not have gams representation
        left_str = (
            str(self._left)
            if isinstance(self._left, (str, int, float))
            else self._left.gamsRepr()
        )
        right_str = (
            str(self._right)
            if isinstance(self._right, (str, int, float))
            else self._right.gamsRepr()
        )

        # negative sign causes an extra operation if not in paranthesis
        # ((((ord(n) - 1) / 10) * -1) + ((ord(n) / 10) * 0)); -> not valid
        # ((((ord(n) - 1) / 10) * (-1)) + ((ord(n) / 10) * 0)); -> valid
        if isinstance(self._left, (int, float)) and self._left < 0:
            left_str = f"({left_str})"

        if isinstance(self._right, (int, float)) and self._right < 0:
            right_str = f"({right_str})"

        if self._op_type == "=" and isinstance(
            self._left,
            (
                syms.Set,
                syms.Parameter,
                syms.Variable,
                implicits.ImplicitSet,
                implicits.ImplicitParameter,
                implicits.ImplicitVariable,
            ),
        ):
            right_str = right_str.replace("=e=", "=")
            right_str = right_str.replace("=l=", "<=")
            right_str = right_str.replace("=g=", ">=")

        return left_str, right_str

    def gamsRepr(self) -> str:
        """
        Representation of this Expression in GAMS language.

        Returns
        -------
        str
        """
        left_str, right_str = self._get_operand_representations()

        out_str = f"{left_str} {self._op_type} {right_str}"

        if self._op_type not in ["..", "="]:
            # add paranthesis for right ordering
            out_str = f"({out_str})"

        if isinstance(self._left, (domain.Domain, syms.Set, syms.Alias)):
            return out_str[1:-1]

        if self._op_type in [
            "=g=",
            "=l=",
            "=e=",
            "=n=",
            "=x=",
            "=c=",
            "=b=",
            ".",
        ]:
            # (test.. a =g= b) -> test.. a =g= b
            out_str = out_str[1:-1]  # remove the paranthesis

        out_str = self._fix_condition_paranthesis(out_str)

        if self._op_type == ".":
            # name . pos -> name.pos
            out_str = out_str.replace(" ", "")  # remove spaces

        if self._op_type in ["..", "="]:
            # add ; to equation expressions and assignments
            out_str += ";"

        if self._op_type == "==":
            # volume.lo(t)$(ord(t) = card(t)) = 2000;
            out_str = out_str.replace("==", "=")

        if self._op_type in ["=", ".."] and out_str[0] == "(":
            # (voycap(j,k)$vc(j,k)).. sum(.) -> voycap(j,k)$vc(j,k).. sum(.)
            indices = utils._getMatchingParanthesisIndices(out_str)
            match_index = indices[0]
            out_str = out_str[1:match_index] + out_str[match_index + 1 :]

        return out_str

    def getStatement(self) -> str:
        """
        Statement of this Expression in .gms file.

        Returns
        -------
        str
        """
        return self.gamsRepr()

src/gamspy/_algebra/expression.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from typing import Union

import gamspy._algebra.condition as condition


class Number:
    """
    Needed for conditions on numbers.

    Parameters
    ----------
    value : int | float

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> k = gp.Set(m, "k", records=["1964-i","1964-ii","1964-iii","1964-iv"])
    >>> ki = gp.Set(m, name="ki", domain=[k], description="initial period")
    >>> ki[k] = gp.Number(1).where[gp.Ord(k) == 1]

    """

    def __init__(self, value: Union[int, float]):
        self._value = value
        self.where = condition.Condition(self)

    def gamsRepr(self) -> str:
        """
        Representation of this Number in GAMS language.

        Returns
        -------
        str
        """
        return f"{self._value}"

src/gamspy/_algebra/number.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 gamspy._algebra.expression as expression


class Operable:
    """
    A mixin class that overloads the magic operations of a class
    to be used in Expressions
    """

    # +, -, /, *, **
    def __add__(self, other):
        return expression.Expression(self, "+", other)

    def __radd__(self, other):
        return expression.Expression(other, "+", self)

    def __sub__(self, other):
        return expression.Expression(self, "-", other)

    def __rsub__(self, other):
        return expression.Expression(other, "-", self)

    def __truediv__(self, other):
        return expression.Expression(self, "/", other)

    def __rtruediv__(self, other):
        return expression.Expression(other, "/", self)

    def __mul__(self, other):
        return expression.Expression(self, "*", other)

    def __rmul__(self, other):
        return expression.Expression(other, "*", self)

    def __pow__(self, other):
        if isinstance(other, int) and other == 2:
            return expression.Expression("sqr(", self.gamsRepr(), ")")
        return expression.Expression(self, "**", other)

    # not, and, or, xor
    def __and__(self, other):
        return expression.Expression(self, "and", other)

    def __rand__(self, other):
        return expression.Expression(other, "and", self)

    def __or__(self, other):
        return expression.Expression(self, "or", other)

    def __ror__(self, other):
        return expression.Expression(other, "or", self)

    def __xor__(self, other):
        return expression.Expression(self, "xor", other)

    def __rxor__(self, other):
        return expression.Expression(other, "xor", self)

    # <, <=, >, >=, ==, !=
    def __lt__(self, other):
        return expression.Expression(self, "<", other)

    def __le__(self, other):
        return expression.Expression(self, "=l=", other)

    def __gt__(self, other):
        return expression.Expression(self, ">", other)

    def __ge__(self, other):
        return expression.Expression(self, "=g=", other)

    def __ne__(self, other):  # type: ignore
        return expression.Expression(self, "ne", other)

    # ~ -> not
    def __invert__(self):
        return expression.Expression("", "not", self)

src/gamspy/_algebra/operable.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

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.utils as utils

if TYPE_CHECKING:
    from gams.transfer import Set, Alias
    from gamspy._algebra import Domain
    from gamspy._algebra.expression import Expression


class Operation(operable.Operable):
    def __init__(
        self,
        domain: Union[
            "Set",
            "Alias",
            Tuple[Union["Set", "Alias"]],
            "Domain",
            "Expression",
        ],
        expression: Union["Expression", int, bool],
        op_name: str,
    ):
        self.domain = utils._toList(domain)
        assert len(self.domain) > 0, "Operation requires at least one index"
        self.expression = expression
        self._op_name = op_name

        # allow conditions
        self.where = condition.Condition(self)

    def _get_index_str(self) -> str:
        if len(self.domain) == 1:
            index_str = self.domain[0].gamsRepr()

            if isinstance(self.domain[0], expression.Expression):
                if (
                    "$" in index_str
                    and not isinstance(self.domain[0]._left, domain.Domain)
                    and index_str[0] == "("
                ):
                    # sum((tt(t)) $ (ord(t) <= pMinDown(g,t1)), ...) ->
                    # sum(tt(t) $ (ord(t) <= pMinDown(g,t1)), ...)
                    index_str = index_str[1:-1]

            return index_str

        return (
            "(" + ",".join([index.gamsRepr() for index in self.domain]) + ")"
        )

    def __eq__(self, other):  # type: ignore
        return expression.Expression(self, "=e=", other)

    def _replace_operations(self, output: str) -> str:
        output = output.replace("=l=", "<=")
        output = output.replace("=g=", ">=")
        output = output.replace("=e=", "==")

        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.expression, bool):
            self.expression = (
                "yes" if self.expression is True else "no"  # type: ignore
            )

        expression_str = (
            str(self.expression)
            if isinstance(self.expression, (int, str))
            else self.expression.gamsRepr()
        )

        output += expression_str
        output += ")"

        output = self._replace_operations(output)

        return output


class Sum(Operation):
    """
    Represents a sum operation over a domain.

    Parameters
    ----------
    domain : Set | Alias | Tuple[Set | Alias], Domain, Expression
    expression : Expression | int | bool

    Examples
    --------
    >>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
    >>> v = gp.Variable(m, "v")
    >>> e = gp.Equation(m, "e", type="eq", domain=[i])
    >>> e[i] = gp.Sum(i, 3) <= v
    """

    def __init__(
        self,
        domain: Union[
            "Set",
            "Alias",
            Tuple[Union["Set", "Alias"]],
            "Domain",
            "Expression",
        ],
        expression: Union["Expression", int, bool],
    ):
        super().__init__(domain, expression, "sum")


class Product(Operation):
    """
    Represents a product operation over a domain.

    Parameters
    ----------
    domain : Set | Alias | Tuple[Set | Alias], Domain, Expression
    expression : Expression | int | bool

    Examples
    --------
    >>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
    >>> v = gp.Variable(m, "v")
    >>> e = gp.Equation(m, "e", type="eq", domain=[i])
    >>> e[i] = gp.Product(i, 3) <= v
    """

    def __init__(
        self,
        domain: Union[
            "Set",
            "Alias",
            Tuple[Union["Set", "Alias"]],
            "Domain",
            "Expression",
        ],
        expression: Union["Expression", int, bool],
    ):
        super().__init__(domain, expression, "prod")


class Smin(Operation):
    """
    Represents a smin operation over a domain.

    Parameters
    ----------
    domain : Set | Alias | Tuple[Set | Alias], Domain, Expression
    expression : Expression | int | bool

    Examples
    --------
    >>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
    >>> v = gp.Variable(m, "v")
    >>> e = gp.Equation(m, "e", type="eq", domain=[i])
    >>> e[i] = gp.Smin(i, 3) <= v
    """

    def __init__(
        self,
        domain: Union[
            "Set",
            "Alias",
            Tuple[Union["Set", "Alias"]],
            "Domain",
            "Expression",
        ],
        expression: Union["Expression", int, bool],
    ):
        super().__init__(domain, expression, "smin")


class Smax(Operation):
    """
    Represents a smax operation over a domain.

    Parameters
    ----------
    domain : Set | Alias | Tuple[Set | Alias], Domain, Expression
    expression : Expression | int | bool

    Examples
    --------
    >>> i = gp.Set(m, "i", records=['i1','i2', 'i3'])
    >>> v = gp.Variable(m, "v")
    >>> e = gp.Equation(m, "e", type="eq", domain=[i])
    >>> e[i] = gp.Smax(i, 3) <= v
    """

    def __init__(
        self,
        domain: Union[
            "Set",
            "Alias",
            Tuple[Union["Set", "Alias"]],
            "Domain",
            "Expression",
        ],
        expression: Union["Expression", int, bool],
    ):
        super().__init__(domain, expression, "smax")


class Ord(operable.Operable):
    """
    Operator ord may be used only with one-dimensional sets.

    Parameters
    ----------
    set : Set | Alias
    """

    def __init__(self, set: Union["Set", "Alias"]):
        self._set = set

    def __eq__(self, other) -> "Expression":  # type: ignore
        return expression.Expression(self, "==", other)

    def __ge__(self, other):
        return expression.Expression(self, ">=", other)

    def __le__(self, other):
        return expression.Expression(self, "<=", other)

    def gamsRepr(self) -> str:
        return f"ord({self._set.name})"


class Card(operable.Operable):
    """
    The operator card may be used with any set.

    Parameters
    ----------
    set : Set | Alias
    """

    def __init__(self, set: Union["Set", "Alias"]) -> None:
        self._set = set

    def __eq__(self, other) -> "Expression":  # type: ignore
        return expression.Expression(self, "==", other)

    def gamsRepr(self) -> str:
        return f"card({self._set.name})"

src/gamspy/_algebra/operation.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#

src/gamspy/_cli/__init__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 argparse
import importlib
import os
import shutil
import subprocess
import sys
from typing import Dict

import gamspy.utils as utils
from .util import add_solver_entry
from .util import remove_solver_entry
from gamspy.exceptions import GamspyException


SOLVERS = [
    "cbc",
    "soplex",
    "highs",
    "copt",
    "gurobi",
    "xpress",
    "odhcplex",
    "mosek",
    "mpsge",
    "miles",
    "knitro",
    "ipopt",
    "minos",
    "snopt",
    "ipopth",
    "dicopt",
    "alphaecp",
    "shot",
    "octeract",
    "scip",
    "antigone",
    "baron",
    "lindo",
    "decis",
]


def get_args():
    parser = argparse.ArgumentParser(
        prog="gamspy",
        description="A script for installing solvers and licenses",
    )
    parser.add_argument("command", choices=["install", "uninstall"], type=str)
    parser.add_argument(
        "component", choices=["license", "engine_license", "solver"], type=str
    )
    parser.add_argument("name", type=str, nargs="?", default=None)
    parser.add_argument("--skip-pip-install", action="store_true")
    parser.add_argument("--skip-pip-uninstall", action="store_true")

    res = vars(parser.parse_args())
    if res["name"] is None and not (
        res["command"] == "uninstall"
        and res["component"] in ["license", "engine_license"]
    ):
        sys.stderr.write("name must be specified\n")
        sys.exit(1)

    return res


def install_license(args: Dict[str, str]):
    gamspy_base_dir = utils._getGAMSPyBaseDirectory()
    shutil.copy(args["name"], gamspy_base_dir + os.sep + "gamslice.txt")


def uninstall_license():
    gamspy_base_dir = utils._getGAMSPyBaseDirectory()
    os.unlink(gamspy_base_dir + os.sep + "gamslice.txt")


def install_solver(args: Dict[str, str]):
    solver_name = args["name"]
    if solver_name not in SOLVERS:
        raise Exception(
            f"Solver name is not valid. Possible solver names: {SOLVERS}"
        )

    if not args["skip_pip_install"]:
        # install specified solver
        try:
            _ = subprocess.run(
                ["pip", "install", f"gamspy_{solver_name}"], check=True
            )
        except subprocess.CalledProcessError as e:
            raise GamspyException(
                f"Could not install gamspy_{solver_name}: {e.output}"
            )

    # copy solver files to gamspy_base
    gamspy_base_dir = utils._getGAMSPyBaseDirectory()
    solver_lib = importlib.import_module(f"gamspy_{solver_name}")

    file_paths = solver_lib.file_paths
    if solver_name == "scip":
        mosek_lib = importlib.import_module("gamspy_mosek")
        file_paths += mosek_lib.file_paths

    for file in file_paths:
        shutil.copy(file, gamspy_base_dir)

    files = solver_lib.files
    if solver_name == "scip":
        files += mosek_lib.files

    verbatims = [solver_lib.verbatim]
    if solver_name == "scip":
        verbatims.append(mosek_lib.verbatim)
    append_dist_info(files, gamspy_base_dir)
    add_solver_entry(gamspy_base_dir, solver_name, verbatims)


def append_dist_info(files, gamspy_base_dir: str):
    """Updates dist-info/RECORD in site-packages for pip uninstall"""
    import gamspy as gp

    gamspy_path: str = gp.__path__[0]
    dist_info_path = f"{gamspy_path}-{gp.__version__}.dist-info"

    with open(dist_info_path + os.sep + "RECORD", "a") as record:
        gamspy_base_relative_path = os.sep.join(
            gamspy_base_dir.split(os.sep)[-3:]
        )

        lines = []
        for file in files:
            line = f"{gamspy_base_relative_path}{os.sep}{file},,"
            lines.append(line)

        record.write("\n".join(lines))


def uninstall_solver(args: Dict[str, str]):
    solver_name = args["name"]
    if solver_name not in SOLVERS:
        raise Exception(
            f"Solver name is not valid. Possible solver names: {SOLVERS}"
        )

    if not args["skip_pip_uninstall"]:
        # uninstall specified solver
        try:
            _ = subprocess.run(
                ["pip", "uninstall", f"gamspy_{solver_name}"], check=True
            )
        except subprocess.CalledProcessError as e:
            raise GamspyException(
                f"Could not uninstall gamspy_{solver_name}: {e.output}"
            )

    # do not delete files from gamspy_base as other solvers might depend on it
    gamspy_base_dir = utils._getGAMSPyBaseDirectory()
    remove_solver_entry(gamspy_base_dir, solver_name)


def install(args: Dict[str, str]):
    if args["component"] == "license":
        install_license(args)
    elif args["component"] == "solver":
        install_solver(args)


def uninstall(args: Dict[str, str]):
    if args["component"] == "license":
        uninstall_license()
    elif args["component"] == "solver":
        uninstall_solver(args)


def main():
    """
    Entry point for gamspy command line application.
    """
    args = get_args()

    if args["command"] == "install":
        install(args)
    elif args["command"] == "uninstall":
        uninstall(args)

src/gamspy/_cli/cmdline.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 os
import platform
from dataclasses import dataclass
from dataclasses import field
from typing import List
from typing import Tuple
from typing import Union

__all__ = ["SolverInfo", "add_solver_entry", "remove_solver_entry"]

platform_to_capabilities_file = {
    "windows": "gmscmpNT.txt",
    "linux": "gmscmpun.txt",
    "mac_x86_64": "gmscmpun.txt",
    "mac_arm64": "gmscmpun.txt",
}


def get_platform() -> str:
    operating_system = platform.system().lower()
    architecture = platform.machine()

    if operating_system == "darwin":
        return f"mac_{architecture}"

    return operating_system


@dataclass
class SolverInfo:
    solver_id: str
    file_type: str
    dict_type: str
    lic_codes: str
    default_ok_flag: str
    hidden_flag: str
    lines_to_follow: str
    model_types: list = field(default_factory=list)

    def __str__(self) -> str:
        return (
            f"{self.solver_id} {self.file_type} {self.dict_type} "
            f"{self.lic_codes} {self.default_ok_flag} {self.hidden_flag} "
            f"{self.lines_to_follow} {' '.join(self.model_types)}"
        )


def check_solver_exists(
    capabilities_file: str, solver_name: str
) -> Union[Tuple[int, int], None]:
    with open(capabilities_file) as capabilities:
        lines = capabilities.readlines()
        lines = [line for line in lines if line != "\n" and line[0] != "*"]
        idx = 0
        while True:
            start_idx = idx
            line1 = lines[idx]
            if line1 == "DEFAULTS\n":
                break

            idx += 2
            splitted_line = line1.split(" ")
            solver = SolverInfo(*splitted_line[:7])  # type: ignore
            idx += int(solver.lines_to_follow)
            if solver.solver_id.lower() == solver_name.lower():
                return start_idx, 2 + int(solver.lines_to_follow)

        return None


def get_capabilities_filename() -> str:
    current_platform = get_platform()
    return platform_to_capabilities_file[current_platform]


def add_solver_entry(
    gamspy_base_location: str,
    solver_name: str,
    verbatims: List[str],
):
    capabilities_file = (
        gamspy_base_location + os.sep + get_capabilities_filename()
    )

    if check_solver_exists(capabilities_file, solver_name):
        if solver_name == "scip":
            if check_solver_exists(capabilities_file, "mosek"):
                print(
                    "Solver already exists in the capabilities file, skipping"
                )
                return
        else:
            print("Solver already exists in the capabilities file, skipping")
            return

    with open(capabilities_file, "r") as f:
        string = f.read()

    for verbatim in verbatims:
        string = f"{verbatim}\n\n{string}"

        with open(capabilities_file, "w") as f:
            f.write(string)


def remove_solver_entry(gamspy_base_location: str, solver_name: str):
    capabilities_file = (
        gamspy_base_location + os.sep + get_capabilities_filename()
    )
    solver_tuple = check_solver_exists(capabilities_file, solver_name)

    if not solver_tuple:
        print("Solver is not in the capabilities file, skipping")
        return

    line_num, line_count = solver_tuple
    with open(capabilities_file, "r") as f:
        lines = f.readlines()

    for _ in range(line_count + 1):
        lines.pop(line_num)

    with open(capabilities_file, "w") as f:
        f.writelines(lines)

src/gamspy/_cli/util.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 io
import os
from typing import Any
from typing import Dict
from typing import List
from typing import Literal
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt
from gams import DebugLevel
from gams import GamsCheckpoint
from gams import GamsJob
from gams import GamsOptions
from gams import GamsWorkspace
from gams.control.workspace import GamsExceptionExecution
from gams.core import gdx

import gamspy as gp
import gamspy._algebra.expression as expression
import gamspy.utils as utils
from gamspy.exceptions import GamspyException

if TYPE_CHECKING:
    from gamspy import (
        Alias,
        Set,
        Parameter,
        Variable,
        Equation,
        EquationType,
        Model,
    )
    from gamspy._algebra.expression import Expression
    from gamspy._engine import EngineConfig


class Container(gt.Container):
    """
    A container is an object that holds all symbols and operates on them.

    Parameters
    ----------
    load_from : str, optional
        Path to the GDX file to be loaded from, by default None
    system_directory : str, optional
        Path to the directory that holds the GAMS installation, by default None
    name : str, optional
        Name of the Container, by default "default"
    working_directory : str, optional
        Path to the working directory to store temporary files such .lst, .gms,
        .gdx, .g00 files.

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()

    """

    def __init__(
        self,
        load_from: Optional[str] = None,
        system_directory: Optional[str] = None,
        name: str = "default",
        working_directory: Optional[str] = None,
        delayed_execution: bool = False,
    ):
        self.system_directory = (
            system_directory
            if system_directory
            else utils._getGAMSPyBaseDirectory()
        )

        self.name = name
        self._delayed_execution = delayed_execution
        self._unsaved_statements: dict = {}
        self._use_restart_from = False

        super().__init__(load_from, self.system_directory)

        self.workspace = GamsWorkspace(
            working_directory, self.system_directory, DebugLevel.KeepFiles
        )

        (
            self._save_to,
            self._restart_from,
            self._gdx_path,
        ) = self._setup_paths()

        # allows interrupt
        self._job: Optional[GamsJob] = None

    def _cast_symbols(self, symbol_names: Optional[List[str]] = None) -> None:
        """
        Casts all symbols in the GAMS Transfer container to GAMSpy symbols
        """
        symbol_names = symbol_names if symbol_names else list(self.data.keys())

        for symbol_name in symbol_names:
            symbol = self.data[symbol_name]
            new_domain = [
                self.data[set.name] if not isinstance(set, str) else set
                for set in symbol.domain
            ]

            del self.data[symbol_name]

            if isinstance(symbol, gt.Alias):
                alias_with = self[symbol.alias_with.name]
                _ = gp.Alias(
                    self,
                    symbol.name,
                    alias_with,
                )
            elif isinstance(symbol, gt.UniverseAlias):
                _ = gp.UniverseAlias(
                    self,
                    symbol.name,
                )
            elif isinstance(symbol, gt.Set):
                _ = gp.Set(
                    self,
                    symbol.name,
                    new_domain,
                    symbol.is_singleton,
                    symbol.records,
                    symbol.domain_forwarding,
                    symbol.description,
                )
            elif isinstance(symbol, gt.Parameter):
                _ = gp.Parameter(
                    self,
                    symbol.name,
                    new_domain,
                    symbol.records,
                    symbol.domain_forwarding,
                    symbol.description,
                )
            elif isinstance(symbol, gt.Variable):
                _ = gp.Variable(
                    self,
                    symbol.name,
                    symbol.type,
                    new_domain,
                    symbol.records,
                    symbol.domain_forwarding,
                    symbol.description,
                )
            elif isinstance(symbol, gt.Equation):
                symbol_type = symbol.type
                if symbol.type in ["eq", "leq", "geq"]:
                    symbol_type = "regular"
                _ = gp.Equation(
                    container=self,
                    name=symbol.name,
                    type=symbol_type,
                    domain=new_domain,
                    records=symbol.records,
                    domain_forwarding=symbol.domain_forwarding,
                    description=symbol.description,
                )

    def _setup_paths(
        self,
    ) -> Tuple[GamsCheckpoint, GamsCheckpoint, str]:
        """
        Sets up the paths for .g00, and .gdx files.

        Parameters
        ----------
        working_directory : str, optional

        Returns
        -------
        Tuple[GamsCheckpoint, GamsCheckpoint, str]
            save_to, restart_from, gdx_path
        """
        temporary_file_prefix = os.path.join(
            self.workspace.working_directory, self.name
        )

        save_to = GamsCheckpoint(
            self.workspace, temporary_file_prefix + "_save.g00"
        )
        restart_from = GamsCheckpoint(
            self.workspace, temporary_file_prefix + "_restart.g00"
        )
        gdx_path = temporary_file_prefix + ".gdx"

        return save_to, restart_from, gdx_path

    def _addStatement(self, statement) -> None:
        self._unsaved_statements[statement.name] = statement

    @property
    def delayed_execution(self):
        """
        Delayed execution status

        Returns
        -------
        bool
        """
        return self._delayed_execution

    @delayed_execution.setter
    def delayed_execution(self, status: bool):
        self._delayed_execution = status

    def addAlias(
        self, name: str, alias_with: Union["Set", "Alias"]
    ) -> "Alias":
        """
        Creates a new Alias and adds it to the container

        Returns
        -------
        Alias

        Raises
        ------
        TypeError
            In case the alias_with is different than a Set or an Alias
        ValueError
            If there is symbol with same name but different type in the
            Container
        """
        if name not in self:
            obj = gp.Alias(self, name, alias_with)

            return obj

        else:
            if not isinstance(alias_with, (gp.Set, gp.Alias)):
                raise TypeError(
                    "Symbol 'alias_with' must be type Set or Alias"
                )

            if isinstance(alias_with, gp.Alias):
                parent = alias_with
                while not isinstance(parent, gp.Set):
                    parent = parent.alias_with
                alias_with = parent

            # allow overwriting
            if isinstance(self.data[name], gp.Alias):
                self.data[name].alias_with = alias_with

                return self.data[name]

            else:
                raise ValueError(
                    f"Attempting to add an Alias symbol named `{name}`,"
                    " however a symbol with this name but different type"
                    " already exists in the Container. Symbol replacement is"
                    " only possible if this symbol is first removed from the"
                    " Container with the removeSymbols() method. "
                )

    def addSet(
        self,
        name: str,
        domain: Optional[List[Union["Set", str]]] = None,
        is_singleton: bool = False,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
    ) -> "Set":
        """
        Creates a Set and adds it to the container

        Parameters
        ----------
        name : str
        domain : List[Set | str], optional
        is_singleton : bool, optional
        records : Any, optional
        domain_forwarding : bool, optional
        description : str, optional
        uels_on_axes : bool, optional

        Returns
        -------
        Set

        Raises
        ------
        err
            In case arguments are not valid
        ValueError
            If there is symbol with same name but different type in the
            Container
        """
        if name not in self:
            obj = gp.Set(
                self,
                name,
                domain,
                is_singleton,
                records,
                domain_forwarding,
                description,
                uels_on_axes,
            )

            return obj

        else:
            # try if argument formats are valid
            m = Container(system_directory=self.system_directory)
            obj = gp.Set(
                m,
                name,
                domain,
                is_singleton,
                records=None,
                domain_forwarding=domain_forwarding,
                description=description,
            )

            # domain handling
            if domain is None:
                domain = ["*"]

            if isinstance(domain, (gp.Set, str)):
                domain = [domain]

            # allow records overwriting
            if (
                isinstance(self[name], gp.Set)
                and utils.checkAllSame(self.data[name].domain, domain)
                and self[name].is_singleton == is_singleton
                and self[name].domain_forwarding == domain_forwarding
            ):
                if records is not None:
                    self[name].setRecords(records)

                # only change the description if a new one is passed
                if description != "":
                    self[name].description = description

                return self[name]

            else:
                raise ValueError(
                    f"Attempting to add a symbol named `{name}` but one"
                    " already exists in the Container. Symbol replacement is"
                    " only possible if the symbol is first removed from the"
                    " Container with the removeSymbols() method. Overwriting"
                    " symbol 'records' and 'description' are possible if all"
                    " other properties have not changed."
                )

    def addParameter(
        self,
        name: str,
        domain: Optional[List[Union[str, "Set"]]] = None,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
    ) -> "Parameter":
        """
        Creates a Parameter and adds it to the Container

        Parameters
        ----------
        name : str
        domain : List[str | Set]], optional
        records : Any, optional
        domain_forwarding : bool, optional
        description : str, optional
        uels_on_axes : bool, optional

        Returns
        -------
        Parameter

        Raises
        ------
        err
            In case arguments are not valid
        ValueError
            If there is symbol with same name but different type in the
            Container
        """
        if name not in self:
            obj = gp.Parameter(
                self,
                name,
                domain,
                records,
                domain_forwarding,
                description,
                uels_on_axes,
            )
            return obj

        else:
            # try if argument formats are valid
            m = Container(system_directory=self.system_directory)
            obj = gp.Parameter(
                m,
                name,
                domain,
                records=None,
                domain_forwarding=domain_forwarding,
                description=description,
            )

            # domain handling
            if domain is None:
                domain = []

            if isinstance(domain, (gt._abcs.AnyContainerDomainSymbol, str)):
                domain = [domain]

            # allow records overwriting
            if (
                isinstance(self.data[name], gp.Parameter)
                and utils.checkAllSame(self.data[name].domain, domain)
                and self.data[name].domain_forwarding == domain_forwarding
            ):
                if records is not None:
                    self.data[name].setRecords(records)

                # only change the description if a new one is passed
                if description != "":
                    self.data[name].description = description

                return self.data[name]

            else:
                raise ValueError(
                    f"Attempting to add a symbol named `{name}` but one"
                    " already exists in the Container. Symbol replacement is"
                    " only possible if the symbol is first removed from the"
                    " Container with the removeSymbols() method. Overwriting"
                    " symbol 'records' and 'description' are possible if all"
                    " other properties have not changed."
                )

    def addVariable(
        self,
        name: str,
        type: str = "free",
        domain: Optional[List[Union[str, "Set"]]] = None,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
    ) -> "Variable":
        """
        Creates a Variable and adds it to the Container

        Parameters
        ----------
        name : str
        type : str, optional
        domain : List[str | Set]], optional
        records : Any, optional
        domain_forwarding : bool, optional
        description : str, optional
        uels_on_axes : bool, optional

        Returns
        -------
        Variable

        Raises
        ------
        err
            In case arguments are not valid
        ValueError
            If there is symbol with same name but different type in the
            Container
        """
        if name not in self:
            obj = gp.Variable(
                self,
                name,
                type,
                domain,
                records,
                domain_forwarding,
                description,
                uels_on_axes,
            )
            return obj

        else:
            # try if argument formats are valid
            m = Container(system_directory=self.system_directory)
            obj = gp.Variable(
                m,
                name,
                type,
                domain,
                records=None,
                domain_forwarding=domain_forwarding,
                description=description,
            )

            # domain handling
            if domain is None:
                domain = []

            if isinstance(domain, (gt._abcs.AnyContainerDomainSymbol, str)):
                domain = [domain]

            # allow records overwriting
            if (
                isinstance(self.data[name], gp.Variable)
                and self.data[name].type == type
                and utils.checkAllSame(self.data[name].domain, domain)
                and self.data[name].domain_forwarding == domain_forwarding
            ):
                if records is not None:
                    self.data[name].setRecords(records)

                # only change the description if a new one is passed
                if description != "":
                    self.data[name].description = description

                return self.data[name]

            else:
                raise ValueError(
                    f"Attempting to add a symbol named `{name}` but one"
                    " already exists in the Container. Symbol replacement is"
                    " only possible if the symbol is first removed from the"
                    " Container with the removeSymbols() method. Overwriting"
                    " symbol 'records' and 'description' are possible if all"
                    " other properties have not changed."
                )

    def addEquation(
        self,
        name: str,
        type: Union[str, "EquationType"] = "regular",
        domain: Optional[List[Union["Set", str]]] = None,
        definition: Optional["Expression"] = None,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
        definition_domain: Optional[List[Union["Set", str]]] = None,
    ) -> "Equation":
        """
        Creates an Equation and adds it to the Container

        Parameters
        ----------
        name : str
        type : str
        domain : List[Set | str], optional
        definition : Definition, optional
        records : Any, optional
        domain_forwarding : bool, optional
        description : str, optional
        uels_on_axes : bool, optional
        definition_domain : List[Set | str], optional

        Returns
        -------
        Equation

        Raises
        ------
        err
            In case arguments are not valid
        ValueError
            If there is symbol with same name but different type in the
            Container
        """
        if name not in self:
            obj = gp.Equation(
                self,
                name,
                type,
                domain,
                definition,
                records,
                domain_forwarding,
                description,
                uels_on_axes,
                definition_domain,
            )
            return obj

        else:
            # try if argument formats are valid
            m = Container(system_directory=self.system_directory)
            obj = gp.Equation(
                m,
                name,
                type,
                domain,
                records=None,
                domain_forwarding=domain_forwarding,
                description=description,
            )

            # domain handling
            if domain is None:
                domain = []

            if isinstance(domain, (gt._abcs.AnyContainerDomainSymbol, str)):
                domain = [domain]

            # allow records overwriting
            if (
                isinstance(self.data[name], gp.Equation)
                and utils.checkAllSame(self.data[name].domain, domain)
                and self.data[name].domain_forwarding == domain_forwarding
            ):
                if records is not None:
                    self.data[name].setRecords(records)

                # only change the description if a new one is passed
                if description != "":
                    self.data[name].description = description

                return self.data[name]

            else:
                raise ValueError(
                    f"Attempting to add a symbol named `{name}` but one"
                    " already exists in the Container. Symbol replacement is"
                    " only possible if the symbol is first removed from the"
                    " Container with the removeSymbols() method. Overwriting"
                    " symbol 'records' and 'description' are possible if all"
                    " other properties have not changed."
                )

    def addModel(
        self,
        name: str,
        equations: List["Equation"],
        problem: str,
        sense: Optional[Literal["MIN", "MAX"]] = None,
        objective: Optional[Union["Variable", "Expression"]] = None,
        matches: Optional[dict] = None,
        limited_variables: Optional[list] = None,
    ) -> "Model":
        """
        Creates a Model and adds it to the Container

        Parameters
        ----------
        name : str
        equations : List[Equation]
        problem : str
        sense : Optional[Literal[MIN, MAX]], optional
        objective : Optional[Union[Variable, Expression]], optional
        matches : Optional[dict], optional
        limited_variables : Optional[list], optional

        Returns
        -------
        Model
        """
        model = gp.Model(
            self,
            name,
            equations,
            problem,
            sense,
            objective,
            matches,
            limited_variables,
        )
        return model

    def addOptions(self, options: Dict[str, str]) -> None:
        """
        Allows adding options to .gms file

        Parameters
        ----------
        options : Dict[str, str]

        Raises
        ------
        ValueError
            In case the option is not valid
        """
        for key, value in options.items():
            if not key.lower() in utils.VALID_OPTION_STATEMENTS:
                raise ValueError(
                    f"{key} is not a valid option. Valid options:"
                    f" {utils.VALID_OPTION_STATEMENTS}"
                )

            self._addStatement(
                expression.Expression(f"option {key}", "=", value)
            )

    def _addGamsCode(self, gams_code: str) -> None:
        """
        Adds an arbitrary GAMS code to the generate .gms file

        Parameters
        ----------
        gams_code : str
        """
        unique_name = utils._getUniqueName()
        self._unsaved_statements[unique_name] = gams_code

    def getSets(self) -> List["Set"]:
        """
        Returns all Sets in the container

        Returns
        -------
        List[Set]
        """
        return [self[symbol_name] for symbol_name in self.listSets()]

    def getAliases(self) -> List["Alias"]:
        """
        Returns all Aliases in the container

        Returns
        -------
        List[Alias]
        """
        return [self[symbol_name] for symbol_name in self.listAliases()]

    def getParameters(self) -> List["Parameter"]:
        """
        Returns all parameters in the container

        Returns
        -------
        List[Parameter]
        """
        return [self[symbol_name] for symbol_name in self.listParameters()]

    def getVariables(self) -> List["Variable"]:
        """
        Returns all variables in the container

        Returns
        -------
        List[Variable]
        """
        return [self[symbol_name] for symbol_name in self.listVariables()]

    def getEquations(self) -> List["Equation"]:
        """
        Returns all equations in the container

        Returns
        -------
        List[Equation]
        """
        return [self[symbol_name] for symbol_name in self.listEquations()]

    def generateGamsString(self) -> str:
        """
        Generates the GAMS code

        Returns
        -------
        str
        """
        symbol_types = (gp.Set, gp.Parameter, gp.Variable, gp.Equation)
        possible_undef_types = (gp.Parameter, gp.Variable, gp.Equation)

        string = f"$gdxIn {self._gdx_path}\n"
        for statement in self._unsaved_statements.values():
            if isinstance(statement, str):
                string += statement + "\n"
            else:
                statement_str = statement.getStatement() + "\n"

                if isinstance(statement, symbol_types):
                    statement_str += f"$load {statement.name}\n"

                if isinstance(statement, possible_undef_types):
                    # save old dirty state to avoid self.records recursion
                    old_dirty_state = statement._is_dirty
                    statement._is_dirty = False

                    num_undef = statement.countUndef()

                    # restore old dirty state
                    statement._is_dirty = old_dirty_state

                    if num_undef:
                        statement_str = f"$onUNDF\n{statement_str}$offUNDF"

                string += statement_str + "\n"

        string += "$gdxIn\n"

        return string

    def interrupt(self):
        """
        Sends interrupt signal to the running job.

        Raises
        ------
        GamspyException
            If the job is not initialized
        """
        if self._job:
            self._job.interrupt()
        else:
            raise GamspyException("There is no job initialized.")

    def _run(
        self,
        options: Optional["GamsOptions"] = None,
        output: Optional[io.TextIOWrapper] = None,
        backend: Literal["local", "engine"] = "local",
        engine_config: Optional["EngineConfig"] = None,
    ):
        if options is None:
            options = GamsOptions(self.workspace)
            options.gdx = self._gdx_path
            options.forcework = 1

        for name in self.data.keys():
            if hasattr(self[name], "_is_dirty") and self[name]._is_dirty:
                self[name]._is_dirty = False

        # Create gdx file to read records from
        super().write(self._gdx_path)

        if backend in ["engine"]:
            # Engine expects gdx file to be next to the gms file
            old_path = self._gdx_path
            self._gdx_path = "default.gdx"
            gams_string = self.generateGamsString()
            self._gdx_path = old_path
        else:
            gams_string = self.generateGamsString()

        # If there is no restart checkpoint or _run is called for the first time, set it to None
        checkpoint = (
            self._restart_from
            if os.path.exists(self._restart_from._checkpoint_file_name)
            and self._use_restart_from
            else None
        )
        self._use_restart_from = True

        self._job = GamsJob(
            self.workspace,
            source=gams_string,
            checkpoint=checkpoint,
        )

        # Actual run depending on the backend
        if backend == "local":
            try:
                self._job.run(
                    gams_options=options,
                    checkpoint=self._save_to,
                    create_out_db=False,
                    output=output,
                )
            except GamsExceptionExecution as e:
                message = self._parse_message(options, self._job)
                e.value = message + e.value if message else e.value
                raise e
        elif backend in ["engine"]:
            options.gdx = "default.gdx"

            if engine_config is None:
                raise GamspyException(
                    "Engine configuration must be defined to run the job with"
                    " GAMS Engine"
                )

            self._job.run_engine(
                engine_configuration=engine_config.get_engine_config(),
                extra_model_files=self._gdx_path,
                gams_options=options,
                checkpoint=self._save_to,
                output=output,
                create_out_db=False,
                engine_options=engine_config.engine_options,
                remove_results=engine_config.remove_results,
            )
        else:
            raise GamspyException(
                "Specified backend is not supported. Possible backends: local,"
                " engine"
            )

        self._restart_from, self._save_to = self._save_to, self._restart_from

        self.loadRecordsFromGdx(self._gdx_path)

    def _parse_message(self, options: "GamsOptions", job: "GamsJob"):
        error_message = ""
        header = "=" * 80
        footer = "=" * 80
        message_format = "\n\n{header}\nError Summary\n{footer}\n{message}\n"

        lst_filename = (
            options.output if options.output else job._job_name + ".lst"
        )

        lst_path = (
            self.workspace._working_directory + os.path.sep + lst_filename
        )

        with open(lst_path) as lst_file:
            all_lines = lst_file.readlines()
            num_lines = len(all_lines)

            index = 0
            while index < num_lines:
                line = all_lines[index]

                if line.startswith("****"):
                    error_lines = [all_lines[index - 1]]
                    temp_index = index

                    while (
                        all_lines[temp_index].startswith("****")
                        and temp_index < len(all_lines) - 1
                    ):
                        error_lines.append(all_lines[temp_index])
                        temp_index += 1

                    error_message = message_format.format(
                        message="".join(error_lines),
                        header=header,
                        footer=footer,
                    )
                    break

                index += 1

        return error_message

    def _get_symbol_names_from_gdx(self, load_from: str) -> Tuple[list, list]:
        gdx_handle = utils._openGdxFile(self.system_directory, load_from)
        _, symbol_count, _ = gdx.gdxSystemInfo(gdx_handle)

        existing_names = []
        new_names = []
        for i in range(1, symbol_count + 1):
            _, symbol_name, _, _ = gdx.gdxSymbolInfo(gdx_handle, i)
            if symbol_name in self.data.keys():
                existing_names.append(symbol_name)
            else:
                new_names.append(symbol_name)

        utils._closeGdxHandle(gdx_handle)

        return existing_names, new_names

    def _get_symbol_names_to_load(
        self,
        load_from: str,
        symbol_names: Optional[List[str]] = None,
    ) -> List[str]:
        if not symbol_names:
            existing_names, new_names = self._get_symbol_names_from_gdx(
                load_from
            )

            symbol_types = (gp.Set, gp.Parameter, gp.Variable, gp.Equation)

            symbol_names = new_names
            for name in existing_names:
                if isinstance(self[name], symbol_types):
                    symbol_names.append(name)

        return symbol_names

    def loadRecordsFromGdx(
        self,
        load_from: str,
        symbol_names: Optional[List[str]] = None,
    ) -> None:
        """
        Loads data of the given symbols from a gdx file. If no
        symbol names are given, data of all symbols are loaded.

        Parameters
        ----------
        load_from : str
            Path to the gdx file
        symbols : List[str], optional
            Symbols whose data will be load from gdx, by default None
        """
        symbol_names = self._get_symbol_names_to_load(load_from, symbol_names)

        temp_container = Container(system_directory=self.system_directory)
        temp_container.read(load_from, symbol_names)

        for name in symbol_names:
            if name in self.data.keys():
                statement = self[name]
                updated_records = temp_container[name].records

                statement.records = updated_records

                if updated_records is not None:
                    statement.domain_labels = statement.domain_names
            else:
                self.read(self._gdx_path, [name])

        self._unsaved_statements = {}

    def read(
        self, load_from: str, symbol_names: Optional[List[str]] = None
    ) -> None:
        """
        Reads specified symbols from the gdx file. If symbol_names are
        not provided, it reads all symbols from the gdx file.

        Parameters
        ----------
        load_from : str
        symbol_names : List[str], optional
        """
        super().read(load_from, symbol_names)
        self._cast_symbols(symbol_names)

    def write(
        self,
        write_to: str,
        symbols: Optional[List[str]] = None,
    ) -> None:
        """
        Writes specified symbols to the gdx file. If symbol_names are
        not provided, it writes all symbols to the gdx file.

        Parameters
        ----------
        write_to : str
        symbols : List[str], optional
        """
        sequence = symbols if symbols else self.data.keys()
        dirty_symbols = []
        for name in sequence:
            if hasattr(self[name], "_is_dirty") and self[name]._is_dirty:
                dirty_symbols.append(name)
        if len(dirty_symbols) > 0:
            self._run()

        super().write(write_to, symbols)

src/gamspy/_container.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from dataclasses import dataclass
from typing import List
from typing import Optional

from gams import GamsEngineConfiguration


@dataclass
class EngineConfig:
    host: str
    username: Optional[str] = None
    password: Optional[str] = None
    jwt: Optional[str] = None
    namespace: str = "global"
    extra_model_files: Optional[List[str]] = None
    engine_options: Optional[dict] = None
    remove_results: bool = True

    def get_engine_config(self):
        return GamsEngineConfiguration(
            self.host,
            self.username,
            self.password,
            self.jwt,
            self.namespace,
        )

src/gamspy/_engine.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

import io
import os
from enum import Enum
from typing import Dict
from typing import Iterable
from typing import List
from typing import Literal
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from gams import (
    GamsOptions,
)

import gamspy as gp
import gamspy._algebra.expression as expression
import gamspy._algebra.operation as operation
import gamspy.utils as utils
from gamspy._engine import EngineConfig
from gamspy._model_instance import ModelInstance
from gamspy.exceptions import GamspyException

if TYPE_CHECKING:
    from gamspy import Parameter, Variable, Equation, Container
    from gamspy._algebra.expression import Expression
    from gamspy._algebra.operation import Operation
    from gamspy._symbols.implicits import ImplicitParameter


class Problem(Enum):
    LP = "LP"
    NLP = "NLP"
    QCP = "QCP"
    DNLP = "DNLP"
    MIP = "MIP"
    RMIP = "RMIP"
    MINLP = "MINLP"
    RMINLP = "RMINLP"
    MIQCP = "MIQCP"
    RMIQCP = "RMIQCP"
    MCP = "MCP"
    CNS = "CNS"
    MPEC = "MPEC"
    RMPEC = "RMPEC"
    EMP = "EMP"
    MPSGE = "MPSGE"

    @classmethod
    def values(cls):
        return list(cls._value2member_map_.keys())

    def __str__(self) -> str:
        return self.value


class Sense(Enum):
    MIN = "MIN"
    MAX = "MAX"
    FEASIBILITY = "FEASIBILITY"

    @classmethod
    def values(cls):
        return list(cls._value2member_map_.keys())

    def __str__(self) -> str:
        return self.value


class ModelStatus(Enum):
    OptimalGlobal = 1
    OptimalLocal = 2
    Unbounded = 3
    InfeasibleGlobal = 4
    InfeasibleLocal = 5
    InfeasibleIntermed = 6
    Feasible = 7
    Integer = 8
    NonIntegerIntermed = 9
    IntegerInfeasible = 10
    LicenseError = 11
    ErrorUnknown = 12
    ErrorNoSolution = 13
    NoSolutionReturned = 14
    SolvedUnique = 15
    Solved = 16
    SolvedSingular = 17
    UnboundedNoSolution = 18
    InfeasibleNoSolution = 19


# GAMS name -> GAMSPy name
attribute_map = {
    "domUsd": "num_domain_violations",
    "etAlg": "algorithm_time",
    "etSolve": "solve_time",
    "etSolver": "solver_time",
    "iterUsd": "num_iterations",
    "marginals": "marginals",
    "maxInfes": "max_infeasibility",
    "meanInfes": "mean_infeasibility",
    "modelStat": "status",
    "nodUsd": "num_nodes_used",
    "numDepnd": "num_dependencies",
    "numDVar": "num_discrete_variables",
    "numInfes": "num_infeasibilities",
    "numNLIns": "num_nonlinear_insts",
    "numNLNZ": "num_nonlinear_zeros",
    "numNOpt": "num_nonoptimalities",
    "numNZ": "num_nonzeros",
    "numVar": "num_variables",
    "numVarProj": "num_bound_projections",
    "objEst": "objective_estimation",
    "objVal": "objective_value",
    "resGen": "model_generation_time",
    "sumInfes": "sum_infeasibilities",
    "sysVer": "solver_version",
}


class Model:
    """
    Represents a list of equations to be solved.

    Parameters
    ----------
    container : Container
        The container that the model belongs to
    name : str
        Name of the model
    equations : str | Iterable
        List of Equation objects or str. ``all`` as a string represents
        all the equations specified before the creation of this model
    problem : str
        Problem type (e.g. LP, NLP etc.)
    sense : "MIN", "MAX", or "FEASIBILITY", optional
        Minimize or maximize
    objective_variable : Variable, optional
        Objective variable to minimize or maximize
    limited_variables : Iterable, optional
        Allows limiting the domain of variables used in a model.

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> v = gp.Variable(m, "v")
    >>> e = gp.Equation(m, "e", definition= v == 5)
    >>> my_model = gp.Model(m, "my_model", [e], "LP")

    """

    # Prefix for auto-generated symbols
    _generate_prefix = "autogenerated_"

    def __init__(
        self,
        container: "Container",
        name: str,
        equations: List["Equation"],
        problem: str,
        sense: Optional[Literal["MIN", "MAX", "FEASIBILITY"]] = None,
        objective: Optional[Union["Variable", "Expression"]] = None,
        matches: Optional[dict] = None,
        limited_variables: Optional[Iterable["Variable"]] = None,
    ):
        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        self.name = name
        self.container = container
        self.problem, self.sense = self._validate_model(
            equations, problem, sense
        )
        self._equations = equations
        self._objective_variable = self._set_objective_variable(objective)
        self._matches = matches
        self._limited_variables = limited_variables
        self.container._addStatement(self)
        self._generate_attribute_symbols()

        # allow freezing
        self._is_frozen = False

        # Attributes
        self.num_domain_violations = None
        self.algorithm_time = None
        self.solve_time = None
        self.solver_time = None
        self.num_iterations = None
        self.marginals = None
        self.max_infeasibility = None
        self.mean_infeasibility = None
        self.status: Optional[ModelStatus] = None
        self.num_nodes_used = None
        self.num_dependencies = None
        self.num_discrete_variables = None
        self.num_infeasibilities = None
        self.num_nonlinear_insts = None
        self.num_nonlinear_zeros = None
        self.num_nonoptimalities = None
        self.num_nonzeros = None
        self.num_variables = None
        self.num_bound_projections = None
        self.objective_estimation = None
        self.objective_value = None
        self.model_generation_time = None
        self.sum_infeasibilities = None
        self.solver_version = None
        self.solver_name = ""

    def _set_objective_variable(
        self,
        assignment: Optional[
            Union["Variable", "Operation", "Expression"]
        ] = None,
    ) -> Optional["Variable"]:
        """
        Returns objective variable. If the assignment is an Expression
        or an Operation (Sum, Product etc.), it automatically generates
        an objective variable and a equation.

        Returns
        -------
        Variable | None

        Raises
        ------
        TypeError
            In case assignment is not a Variable, Expression or an Operation.
        """

        if assignment is not None and not isinstance(
            assignment,
            (gp.Variable, expression.Expression, operation.Operation),
        ):
            raise TypeError(
                "Objective must be a Variable or an Expression but"
                f" {type(assignment)} given"
            )

        if self.sense == gp.Sense.FEASIBILITY:
            if assignment is not None:
                raise GamspyException(
                    "Cannot set an objective when the sense is FEASIBILITY!"
                )

            if self.problem in [gp.Problem.CNS, gp.Problem.MCP]:
                raise GamspyException(
                    "Problem type cannot be CNS or MCP when the sense is"
                    " FEASIBILITY"
                )

            # Generate an objective variable
            variable = gp.Variable(
                self.container,
                f"{self._generate_prefix}objective{utils._getUniqueName()}",
            )

            # Generate an equation
            equation = gp.Equation(
                self.container,
                f"{self._generate_prefix}equation{utils._getUniqueName()}",
            )

            equation.definition = variable == 0
            self._equations.append(equation)

            return variable

        if isinstance(
            assignment, (expression.Expression, operation.Operation)
        ):
            # Generate an objective variable
            variable = gp.Variable(
                self.container,
                f"{self._generate_prefix}objective{utils._getUniqueName()}",
            )

            # Generate an equation
            equation = gp.Equation(
                self.container,
                f"{self._generate_prefix}equation{utils._getUniqueName()}",
            )

            # Sum((i,j),c[i,j]*x[i,j])->Sum((i,j),c[i,j]*x[i,j]) =e= var
            assignment = assignment == variable

            # equation .. Sum((i,j),c[i,j]*x[i,j]) =e= var
            equation.definition = assignment
            self._equations.append(equation)

            return variable

        return assignment

    def _generate_attribute_symbols(self) -> None:
        for attr_name in attribute_map.keys():
            symbol_name = f"{self._generate_prefix}{self.name}_{attr_name}"
            self.container._unsaved_statements[symbol_name] = (
                f"Scalar {symbol_name};"
            )

    def _prepare_gams_options(
        self,
        solver: Optional[str] = None,
        options: Optional[dict] = None,
        solver_options: Optional[dict] = None,
    ) -> GamsOptions:
        gams_options = GamsOptions(self.container.workspace)
        gams_options.gdx = self.container._gdx_path

        if solver:
            gams_options.all_model_types = solver

        if options:
            if not isinstance(options, dict):
                raise GamspyException("options must be a dict")

            for option, value in options.items():
                if option.lower() not in utils.VALID_GAMS_OPTIONS:
                    raise GamspyException(
                        f"Invalid option `{option}`. Possible options:"
                        f" {utils.VALID_GAMS_OPTIONS}"
                    )

                setattr(gams_options, option.lower(), value)

        if solver_options:
            if solver is None:
                raise GamspyException(
                    "You need to provide a 'solver' to apply solver options."
                )

            solver_file_name = (
                self.container.workspace.working_directory
                + os.sep
                + f"{solver.lower()}.123"
            )

            with open(solver_file_name, "w") as solver_file:
                for key, value in solver_options.items():
                    solver_file.write(f"{key} {value}\n")

            gams_options.optfile = 123

        return gams_options

    def _validate_model(self, equations, problem, sense=None) -> Tuple:
        if not isinstance(equations, list) or any(
            not isinstance(equation, gp.Equation) for equation in equations
        ):
            raise TypeError("equations must be list of Equation objects")

        if isinstance(problem, str):
            if problem.upper() not in gp.Problem.values():
                raise ValueError(
                    f"Allowed problem types: {gp.Problem.values()} but found"
                    f" {problem}."
                )
            else:
                problem = gp.Problem(problem.upper())

        if isinstance(sense, str):
            if sense.upper() not in gp.Sense.values():
                raise ValueError(
                    f"Allowed sense values: {gp.Sense.values()} but found"
                    f" {sense}."
                )

            sense = gp.Sense(sense.upper())

        return problem, sense

    def _append_solve_string(self) -> None:
        solve_string = f"solve {self.name} using {self.problem}"

        if self.sense:
            if self.sense == gp.Sense.FEASIBILITY:
                # Set sense as min or max for feasibility
                self.sense = gp.Sense.MIN

            solve_string += f" {self.sense}"

        if self._objective_variable:
            solve_string += f" {self._objective_variable.gamsRepr()}"

        self.container._unsaved_statements[utils._getUniqueName()] = (
            solve_string + ";\n"
        )

    def _assign_model_attributes(self) -> None:
        for attr_name in attribute_map.keys():
            symbol_name = f"{self._generate_prefix}{self.name}_{attr_name}"

            self.container._unsaved_statements[utils._getUniqueName()] = (
                f"{symbol_name} = {self.name}.{attr_name};"
            )

    def _update_model_attributes(self) -> None:
        temp_container = gp.Container(
            system_directory=self.container.system_directory
        )
        temp_container.read(
            self.container._gdx_path,
            [
                f"{self._generate_prefix}{self.name}_{gams_attr}"
                for gams_attr in attribute_map.keys()
            ],
        )

        for gams_attr, python_attr in attribute_map.items():
            symbol_name = f"{self._generate_prefix}{self.name}_{gams_attr}"

            if python_attr == "status":
                setattr(
                    self,
                    python_attr,
                    ModelStatus(
                        temp_container[symbol_name].records.values[0][0]
                    ),
                )
            else:
                setattr(
                    self,
                    python_attr,
                    temp_container[symbol_name].records.values[0][0],
                )

    def _remove_autogenerated_symbols(self):
        """
        Removes autogenerated model attributes, objective variable and equation from
        the container
        """
        attribute_names = [
            f"{self._generate_prefix}{self.name}_{attr_name}"
            for attr_name in attribute_map.keys()
        ]

        autogenerated_symbol_names = [
            name
            for name in self.container.data.keys()
            if self._generate_prefix in name
        ] + attribute_names

        for name in autogenerated_symbol_names:
            if name in self.container.data.keys():
                del self.container.data[name]

    @property
    def equations(self) -> List["Equation"]:
        """
        Equations in which the model consists of

        Returns
        -------
        List[Equation]
        """
        return self._equations

    @equations.setter
    def equations(self, new_equations) -> None:
        self._equations = new_equations

    def interrupt(self):
        self.container.interrupt()

    def freeze(
        self,
        modifiables: List[Union["Parameter", "ImplicitParameter"]],
        freeze_options: Optional[dict] = None,
    ):
        """
        Freezes all symbols except modifiable symbols.

        Parameters
        ----------
        modifiables : List[Union[Parameter, ImplicitParameter]]
        freeze_options : Optional[dict], optional
        """

        self.container._run()

        self.instance = ModelInstance(
            self.container, self, modifiables, freeze_options
        )
        self._is_frozen = True

    def unfreeze(self):
        """Unfreezes all symbols"""
        for symbol in self.container.data.values():
            if hasattr(symbol, "_is_frozen") and symbol._is_frozen:
                symbol._is_frozen = False

        self._is_frozen = False

    def solve(
        self,
        solver: Optional[str] = None,
        options: Optional[Dict[str, str]] = None,
        solver_options: Optional[dict] = None,
        model_instance_options: Optional[dict] = None,
        output: Optional[io.TextIOWrapper] = None,
        backend: Literal["local", "engine-one", "engine-sass"] = "local",
        engine_config: Optional["EngineConfig"] = None,
    ):
        """
        Generates the gams string, writes it to a file and runs it

        Parameters
        ----------
        options : dict, optional
        output : TextIOWrapper, optional

        Raises
        ------
        ValueError
            In case problem is not in possible problem types
        ValueError
            In case sense is different than "MIN" or "MAX"
        """
        if not self._is_frozen:
            gams_options = self._prepare_gams_options(
                solver, options, solver_options
            )

            self._append_solve_string()
            self._assign_model_attributes()

            self.container._run(gams_options, output, backend, engine_config)

            self._update_model_attributes()
        else:
            self.instance.solve(model_instance_options, output)

        self._remove_autogenerated_symbols()

    def getStatement(self) -> str:
        """
        Statement of the Model definition

        Returns
        -------
        str
        """
        equations = []
        for equation in self._equations:
            if self._matches:
                if equation not in self._matches.keys():
                    equations.append(equation.gamsRepr())
            else:
                equations.append(equation.gamsRepr())

        equations_str = ",".join(equations)

        if self._matches:
            matches_str = ",".join(
                [
                    f"{equation.gamsRepr()}.{variable.gamsRepr()}"
                    for equation, variable in self._matches.items()
                ]
            )

            equations_str = (
                ",".join([equations_str, matches_str])
                if equations
                else matches_str
            )

        if self._limited_variables:
            limited_variables_str = ",".join(
                [variable.gamsRepr() for variable in self._limited_variables]
            )
            equations_str += "," + limited_variables_str

        model_str = f"Model {self.name} / {equations_str} /;"

        return model_str

src/gamspy/_model.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 io
from typing import List
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

from gams import EquType
from gams import GamsModelInstanceOpt
from gams import GamsModifier
from gams import GamsOptions
from gams import SymbolUpdateType
from gams import UpdateAction
from gams import VarType

import gamspy as gp
import gamspy._symbols.implicits as implicits
import gamspy.utils as utils
from gamspy.exceptions import GamspyException

if TYPE_CHECKING:
    from gamspy import Parameter, Model, Container
    from gamspy._symbols.implicits import ImplicitParameter


variable_map = {
    "binary": VarType.Binary,
    "integer": VarType.Integer,
    "positive": VarType.Positive,
    "negative": VarType.Negative,
    "free": VarType.Free,
    "sos1": VarType.SOS1,
    "sos2": VarType.SOS2,
    "semicont": VarType.SemiCont,
    "semiint": VarType.SemiInt,
}


equation_map = {
    "eq": EquType.E,
    "leq": EquType.L,
    "geq": EquType.G,
    "nonbinding": EquType.N,
    "external": EquType.X,
    "cone": EquType.C,
}

update_action_map = {
    "l": UpdateAction.Primal,
    "m": UpdateAction.Dual,
    "up": UpdateAction.Upper,
    "lo": UpdateAction.Lower,
    "fx": UpdateAction.Fixed,
}

update_type_map = {
    "0": SymbolUpdateType.Zero,
    "base_case": SymbolUpdateType.BaseCase,
    "accumulate": SymbolUpdateType.Accumulate,
    "inherit": SymbolUpdateType._Inherit,
}


class ModelInstance:
    def __init__(
        self,
        container: "Container",
        model: "Model",
        modifiables: List[Union["Parameter", "ImplicitParameter"]],
        freeze_options: Optional[dict] = None,
    ) -> None:
        self.modifiables = self._init_modifiables(modifiables)
        self.main_container = container
        self.instance_container = gp.Container(
            name="instance_container",
            system_directory=container.system_directory,
            working_directory=container.workspace.working_directory,
        )
        self.model = model

        self.checkpoint = self.main_container._restart_from
        self.instance = self.checkpoint.add_modelinstance()
        self.instantiate(model, freeze_options)

    def instantiate(
        self, model: "Model", freeze_options: Optional[dict] = None
    ):
        options = self._prepare_freeze_options(freeze_options)

        solve_string = f"{model.name} using {model.problem}"

        if model.sense:
            solve_string += f" {model.sense}"

        if model._objective_variable:
            solve_string += f" {model._objective_variable.gamsRepr()}"

        modifiers = self._create_modifiers()

        self.instance.instantiate(solve_string, modifiers, options)

    def solve(
        self,
        options_dict: Optional[dict] = None,
        output: Optional[io.TextIOWrapper] = None,
    ):
        # get options from dict
        options, update_type = self._prepare_options(options_dict)

        # update sync_db
        self.main_container.write(self.instance.sync_db._gmd)

        for modifiable in self.modifiables:
            if isinstance(modifiable, implicits.ImplicitParameter):
                if modifiable.parent.records is not None:
                    parent_name, attr = modifiable.name.split(".")
                    attr_name = "_".join([parent_name, attr])

                    columns = self._get_columns_to_drop(attr)

                    self.instance_container[attr_name].setRecords(
                        self.main_container[parent_name].records.drop(
                            columns, axis=1
                        )
                    )

                    self.instance_container.write(self.instance.sync_db._gmd)

        # solve
        self.instance.solve(
            update_type=update_type, output=output, mi_opt=options
        )

        # update main container
        self._update_main_container()

        # update model status
        self.model.status = gp.ModelStatus(self.instance.model_status)

    def _init_modifiables(
        self, modifiables: List[Union["Parameter", "ImplicitParameter"]]
    ) -> List[Union["Parameter", "ImplicitParameter"]]:
        will_be_modified: List[Union["Parameter", "ImplicitParameter"]] = []
        for modifiable in modifiables:
            if isinstance(modifiable, implicits.ImplicitParameter):
                attr_name = modifiable.name.split(".")[-1]

                # If the modifiable attr is fx, then modify level, lower and upper.
                if attr_name == "fx":
                    if not utils.isin(modifiable.parent.l, will_be_modified):
                        will_be_modified.append(modifiable.parent.l)

                    if not utils.isin(modifiable.parent.lo, will_be_modified):
                        will_be_modified.append(modifiable.parent.lo)

                    if not utils.isin(modifiable.parent.up, will_be_modified):
                        will_be_modified.append(modifiable.parent.up)
                else:
                    # if fx already added level, lower or upper, do not add again.
                    if not utils.isin(modifiable, will_be_modified):
                        will_be_modified.append(modifiable)
            else:
                will_be_modified.append(modifiable)

        return will_be_modified

    def _prepare_freeze_options(
        self, options_dict: Optional[dict]
    ) -> GamsOptions:
        if options_dict is None:
            return

        options = GamsOptions(self.model.container.workspace)

        for key, value in options_dict.items():
            setattr(options, key, value)

    def _prepare_options(
        self, options_dict: Optional[dict]
    ) -> Tuple[Optional[GamsModelInstanceOpt], SymbolUpdateType]:
        update_type = SymbolUpdateType.BaseCase

        if options_dict is None:
            return None, update_type

        possible_options = [
            "solver",
            "opt_file",
            "no_match_limit",
            "debug",
            "update_type",
        ]
        options = GamsModelInstanceOpt()

        for key, value in options_dict.items():
            if key in possible_options:
                setattr(options, key, value)
            else:
                raise GamspyException(
                    f"{key} is not a model instance option. All options:"
                    f" {possible_options}"
                )

            if key == "update_type":
                update_type = update_type_map[key]

        return options, update_type

    def _get_columns_to_drop(self, attr):
        attr_map = {
            "l": "level",
            "m": "marginal",
            "lo": "lower",
            "up": "upper",
            "scale": "scale",
        }

        columns = []
        for key, value in attr_map.items():
            if key != attr:
                columns.append(value)

        return columns

    def _create_modifiers(self):
        modifiers = []

        for modifiable in self.modifiables:
            if isinstance(modifiable, gp.Parameter):
                modifiers.append(
                    GamsModifier(
                        self.instance.sync_db.add_parameter(
                            modifiable.name,
                            modifiable.dimension,
                            modifiable.description,
                        )
                    )
                )

                modifiable._is_frozen = True
            elif isinstance(modifiable, implicits.ImplicitParameter):
                attribute = modifiable.name.split(".")[-1]
                update_action = update_action_map[attribute]

                try:
                    sync_db_symbol = self.instance.sync_db[
                        modifiable.parent.name
                    ]
                except Exception:
                    if isinstance(modifiable.parent, gp.Variable):
                        sync_db_symbol = self.instance.sync_db.add_variable(
                            modifiable.parent.name,
                            modifiable.parent.dimension,
                            variable_map[modifiable.parent.type],
                        )

                    elif isinstance(modifiable.parent, gp.Equation):
                        sync_db_symbol = self.instance.sync_db.add_equation(
                            modifiable.parent.name,
                            modifiable.parent.dimension,
                            equation_map[modifiable.parent.type],
                        )

                attr_name = "_".join(modifiable.name.split("."))

                domain = (
                    ["*"] * modifiable.parent.dimension
                    if modifiable.parent.dimension
                    else None
                )
                _ = gp.Parameter(
                    self.instance_container,
                    attr_name,
                    domain=domain,
                )

                data_symbol = self.instance.sync_db.add_parameter(
                    attr_name,
                    modifiable.parent.dimension,
                )

                modifiers.append(
                    GamsModifier(sync_db_symbol, update_action, data_symbol)
                )

                modifiable.parent._is_frozen = True
            else:
                raise GamspyException(
                    f"Symbol type {type(modifiable)} cannot be modified in a"
                    " frozen solve"
                )

        return modifiers

    @property
    def solver_status(self):
        return self.instance.solver_status

    def _update_main_container(self):
        temp = gp.Container(
            system_directory=self.main_container.system_directory,
            working_directory=self.main_container.workspace.working_directory,
        )
        temp.read(self.instance.sync_db._gmd)

        for name in temp.data.keys():
            if name in self.main_container.data.keys():
                self.main_container[name].records = temp[name].records

src/gamspy/_model_instance.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
# flake8: noqa
from gamspy._symbols.alias import Alias
from gamspy._symbols.equation import Equation
from gamspy._symbols.equation import EquationType
from gamspy._symbols.parameter import Parameter
from gamspy._symbols.set import Set
from gamspy._symbols.universe_alias import UniverseAlias
from gamspy._symbols.variable import Variable
from gamspy._symbols.variable import VariableType

src/gamspy/_symbols/__init__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from typing import Literal
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt

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.utils as utils
from gamspy._symbols.symbol import Symbol

if TYPE_CHECKING:
    from gamspy import Set, Container
    from gamspy._algebra.operable import Operable
    from gamspy._algebra.expression import Expression


class Alias(gt.Alias, operable.Operable, Symbol):
    """
    Represents an Alias symbol in GAMS.
    https://www.gams.com/latest/docs/UG_SetDefinition.html#UG_SetDefinition_TheAliasStatementMultipleNamesForASet

    Parameters
    ----------
    container : Container
    name : str
    alias_with : Set

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i")
    >>> j = gp.Alias(m, "j", i)

    """

    def __init__(self, container: "Container", name: str, alias_with: "Set"):
        # enable load on demand
        self._is_dirty = False

        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        super().__init__(container, name, alias_with)

        # allow conditions
        self.where = condition.Condition(self)

        # add statement
        self.container._addStatement(self)

        # iterator index
        self._current_index = 0

    def __len__(self):
        if self.records is not None:
            return len(self.records.index)

        return 0

    def __next__(self):
        if self._current_index < len(self):
            row = self.records.iloc[self._current_index]
            self._current_index += 1
            return row

        self._current_index = 0
        raise StopIteration

    def __iter__(self):
        return self

    def lag(
        self,
        n: Union[int, "Operable"],
        type: Literal["linear", "circular"] = "linear",
    ):
        """Lag operation shifts the values of a Set or Alias by one to the left

        Parameters
        ----------
        n : int | Operable
        type : 'linear' or 'circular', optional

        Returns
        -------
        ImplicitSet

        Raises
        ------
        ValueError
            When type is not circular or linear
        """
        jump = n if isinstance(n, int) else n.gamsRepr()  # type: ignore

        if type == "circular":
            return implicits.ImplicitSet(self, name=f"{self.name} -- {jump}")
        elif type == "linear":
            return implicits.ImplicitSet(self, name=f"{self.name} - {jump}")

        raise ValueError("Lag type must be linear or circular")

    def lead(
        self,
        n: Union[int, "Operable"],
        type: Literal["linear", "circular"] = "linear",
    ):
        """
        Lead shifts the values of a Set or Alias by one to the right

        Parameters
        ----------
        n : int | Operable
        type : 'linear' or 'circular', optional

        Returns
        -------
        ImplicitSet

        Raises
        ------
        ValueError
            When type is not circular or linear
        """
        jump = n if isinstance(n, int) else n.gamsRepr()  # type: ignore

        if type == "circular":
            return implicits.ImplicitSet(self, name=f"{self.name} ++ {jump}")
        elif type == "linear":
            return implicits.ImplicitSet(self, name=f"{self.name} + {jump}")

        raise ValueError("Lead type must be linear or circular")

    def sameAs(self, other: Union["Set", "Alias"]) -> "Expression":
        return expression.Expression(
            "sameAs(", ",".join([self.gamsRepr(), other.gamsRepr()]), ")"
        )

    def gamsRepr(self) -> str:
        """
        Representation of this Alias in GAMS language.

        Returns
        -------
        str
        """
        return self.name

    def getStatement(self) -> str:
        """
        Statement of the Alias definition

        Returns
        -------
        str
        """
        return f"Alias({self.alias_with.name},{self.name});"

src/gamspy/_symbols/alias.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from enum import Enum
from typing import Any
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt
import pandas as pd

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.utils as utils
from gamspy._symbols.symbol import Symbol

if TYPE_CHECKING:
    from gamspy import Set, Variable, Container
    from gamspy._algebra.operation import Operation
    from gamspy._algebra.expression import Expression


class EquationType(Enum):
    REGULAR = "REGULAR"
    NONBINDING = "NONBINDING"
    EXTERNAL = "EXTERNAL"
    CONE = "CONE"
    BOOLEAN = "BOOLEAN"

    @classmethod
    def values(cls):
        return list(cls._value2member_map_.keys())

    def __str__(self) -> str:
        return self.value


class Equation(gt.Equation, operable.Operable, Symbol):
    """
    Represents an Equation symbol in GAMS.
    https://www.gams.com/latest/docs/UG_Equations.html

    Parameters
    ----------
    container : Container
    name : str
    type : str
    domain : List[Set | str], optional
    definition: Expression, optional
    records : Any, optional
    domain_forwarding : bool, optional
    description : str, optional
    uels_on_axes: bool
    definition_domain: list, optional

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i", records=['i1','i2'])
    >>> a = gp.Parameter(m, "a", [i], records=[['i1',1],['i2',2]])
    >>> v = gp.Variable(m, "v", domain=[i])
    >>> e = gp.Equation(m, "e", domain=[i])
    >>> e[i] = a[i] <= v[i]

    """

    def __init__(
        self,
        container: "Container",
        name: str,
        type: Union[str, EquationType] = "regular",
        domain: Optional[List[Union["Set", str]]] = None,
        definition: Optional[
            Union["Variable", "Operation", "Expression"]
        ] = None,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
        definition_domain: Optional[list] = None,
    ):
        type = self._cast_type(type)

        # enable load on demand
        self._is_dirty = False

        # allow freezing
        self._is_frozen = False

        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        super().__init__(
            container,
            name,
            type,
            domain,
            records,
            domain_forwarding,
            description,
            uels_on_axes,
        )

        # allow conditions
        self.where = condition.Condition(self)

        # add statement
        self.container._addStatement(self)

        # add defition if exists
        self._definition_domain = definition_domain
        self.definition = definition

        # create attributes
        self._l, self._m, self._lo, self._up, self._s = self._init_attributes()
        self._stage = self._create_attr("stage")
        self._range = self._create_attr("range")
        self._slacklo = self._create_attr("slacklo")
        self._slackup = self._create_attr("slackup")
        self._slack = self._create_attr("slack")
        self._infeas = self._create_attr("infeas")

    def _cast_type(self, type: Union[str, EquationType]) -> str:
        if isinstance(type, str):
            if type.upper() not in EquationType.values():
                raise ValueError(
                    "Allowed equation types:"
                    f" {EquationType.values()} but found {type}."
                )

            # assign eq by default
            if type.upper() == "REGULAR":
                type = "EQ"

        elif isinstance(type, EquationType):
            # assign eq by default
            type = "EQ" if EquationType.REGULAR else str(type)

        return type

    def __hash__(self):
        return id(self)

    def __getitem__(self, indices: Union[tuple, str]):
        domain = utils._toList(indices)
        return implicits.ImplicitEquation(
            self, name=self.name, type=self.type, domain=domain  # type: ignore  # noqa: E501
        )

    def __setitem__(
        self,
        indices: Union[tuple, str, implicits.ImplicitSet],
        assignment: "Expression",
    ):
        domain = utils._toList(indices)

        non_regular_map = {
            "nonbinding": "=n=",
            "external": "=x=",
            "cone": "=c=",
            "boolean": "=b=",
        }

        regular_map = {
            "=e=": "eq",
            "=g=": "geq",
            "=l=": "leq",
        }

        if self.type in non_regular_map.keys():  # type: ignore
            assignment._op_type = non_regular_map[self.type]  # type: ignore
        else:
            self.type = regular_map[assignment._op_type]

        statement = expression.Expression(
            implicits.ImplicitEquation(
                self,
                name=self.name,
                type=self.type,
                domain=domain,
            ),
            "..",
            assignment,
        )

        self.container._addStatement(statement)

        if self.container.delayed_execution:
            self._is_dirty = True
        else:
            self.container._run()

    def __eq__(self, other):  # type: ignore
        return expression.Expression(self, "=e=", other)

    def _init_attributes(self) -> tuple:
        level = self._create_attr("l")
        marginal = self._create_attr("m")
        lower = self._create_attr("lo")
        upper = self._create_attr("up")
        scale = self._create_attr("scale")
        return level, marginal, lower, upper, scale

    def _create_attr(self, attr_name):
        return implicits.ImplicitParameter(
            self,
            name=f"{self.name}.{attr_name}",
            records=self.records,
        )

    @property
    def l(self):  # noqa: E741, E743
        """
        Level

        Returns
        -------
        ImplicitParameter
        """
        return self._l

    @property
    def m(self):
        """
        Marginal

        Returns
        -------
        ImplicitParameter
        """
        return self._m

    @property
    def lo(self):
        """
        Lower bound

        Returns
        -------
        ImplicitParameter
        """
        return self._lo

    @property
    def up(self):
        """
        Upper bound

        Returns
        -------
        ImplicitParameter
        """
        return self._up

    @property
    def scale(self):
        """
        Scale

        Returns
        -------
        ImplicitParameter
        """
        return self._s

    @property
    def stage(self):
        """
        Stage

        Returns
        -------
        ImplicitParameter
        """
        return self._stage

    @property
    def range(self):
        """
        Range

        Returns
        -------
        ImplicitParameter
        """
        return self._range

    @property
    def slacklo(self):
        """
        Slack lower bound

        Returns
        -------
        ImplicitParameter
        """
        return self._slacklo

    @property
    def slackup(self):
        """
        Slack upper bound

        Returns
        -------
        ImplicitParameter
        """
        return self._slackup

    @property
    def slack(self):
        """
        Slack

        Returns
        -------
        ImplicitParameter
        """
        return self._slack

    @property
    def infeas(self):
        """
        Infeasability

        Returns
        -------
        ImplicitParameter
        """
        return self._infeas

    @property
    def definition(
        self,
    ) -> Optional[Union["Variable", "Operation", "Expression"]]:
        """
        Definition of the Equation

        Returns
        -------
        Expression
        """
        return self._definition

    @definition.setter
    def definition(
        self,
        assignment: Optional[
            Union["Variable", "Operation", "Expression"]
        ] = None,
    ) -> None:
        """
        Needed for scalar equations
        >>> import gamspy as gp
        >>> m = gp.Container()
        >>> wh = gp.Set(m, "wh", records=['i1', 'i2'])
        >>> build = gp.Parameter(m, "build", domain=[wh], records=[('i1',5), ('i2', 5)])
        >>> eq = gp.Equation(m, "eq")
        >>> eq.definition = gp.Sum(wh, build[wh]) <= 1

        """
        if assignment is None:
            self._definition = assignment
            return

        eq_types = ["=e=", "=l=", "=g="]

        # In case of an MCP equation without any equality, add the equality
        if not any(eq_type in assignment.gamsRepr() for eq_type in eq_types):
            assignment = assignment == 0

        non_regular_map = {
            "nonbinding": "=n=",
            "external": "=x=",
            "cone": "=c=",
            "boolean": "=b=",
        }

        regular_map = {
            "=e=": "eq",
            "=g=": "geq",
            "=l=": "leq",
        }

        if self.type in non_regular_map.keys():
            assignment._op_type = non_regular_map[self.type]  # type: ignore
        else:
            self.type = regular_map[assignment._op_type]  # type: ignore

        domain = self._definition_domain if self._definition_domain else []
        statement = expression.Expression(
            implicits.ImplicitEquation(
                self,
                name=self.name,
                type=self.type,
                domain=domain,
            ),
            "..",
            assignment,
        )

        self.container._addStatement(statement)
        self._definition = statement

        if not self.container.delayed_execution:
            self.container._run()

    @property
    def records(self):
        """
        Records of the Equation

        Returns
        -------
        DataFrame
        """
        if not self._is_dirty:
            return self._records

        self.container._run()

        return self._records

    @records.setter
    def records(self, records):
        if records is not None:
            if not isinstance(records, pd.DataFrame):
                raise TypeError("Symbol 'records' must be type DataFrame")

        # set records
        self._records = records

        self._requires_state_check = True
        self.modified = True

        self.container._requires_state_check = True
        self.container.modified = True

        if self._records is not None:
            if self.domain_forwarding:  # pragma: no cover
                self._domainForwarding()

                # reset state check flags for all symbols in the container
                for symnam, symobj in self.container.data.items():
                    symobj._requires_state_check = True

    def gamsRepr(self) -> str:
        """
        Representation of this Equation in GAMS language.

        Returns
        -------
        str
        """
        return self.name

    def getStatement(self) -> str:
        """
        Statement of the Equation declaration

        Returns
        -------
        str
        """
        output = f"Equation {self.name}"

        if self.domain:
            domain_names = [set.name for set in self.domain]  # type: ignore
            domain_str = "(" + ",".join(domain_names) + ")"
            output += domain_str

        if self.description:
            output += ' "' + self.description + '"'

        output += ";"
        return output

src/gamspy/_symbols/equation.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
# flake8: noqa
from gamspy._symbols.implicits.implicit_equation import ImplicitEquation
from gamspy._symbols.implicits.implicit_parameter import ImplicitParameter
from gamspy._symbols.implicits.implicit_set import ImplicitSet
from gamspy._symbols.implicits.implicit_variable import ImplicitVariable

src/gamspy/_symbols/implicits/__init__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
import gamspy._symbols.alias as alias
import gamspy._symbols.implicits as implicits
import gamspy._symbols.set as gams_set
from gamspy._symbols.implicits.implicit_parameter import ImplicitParameter
from gamspy._symbols.implicits.implicit_symbol import ImplicitSymbol

if TYPE_CHECKING:
    from gamspy import Set, Equation
    from gamspy._algebra.expression import Expression


class ImplicitEquation(ImplicitSymbol):
    def __init__(
        self,
        parent: "Equation",
        name: str,
        type: str,
        domain: List[Union["Set", str]],
    ) -> None:
        """Implicit Equation

        Parameters
        ----------
        parent : Equation
        name : str
        domain : List[Set | str]
        """
        super().__init__(parent, name, domain)
        self.type = type

        # level, marginal, lower, upper, scale
        self._l, self._m, self._lo, self._up, self._s = self._init_attributes()
        self._stage = self._create_attr("stage")
        self._range = self._create_attr("range")
        self._slacklo = self._create_attr("slacklo")
        self._slackup = self._create_attr("slackup")
        self._slack = self._create_attr("slack")
        self._infeas = self._create_attr("infeas")

    def _create_attr(self, attr_name: str):
        return ImplicitParameter(self.parent, f"{self.gamsRepr()}.{attr_name}")

    def _init_attributes(self) -> tuple:
        level = self._create_attr("l")
        marginal = self._create_attr("m")
        lower = self._create_attr("lo")
        upper = self._create_attr("up")
        scale = self._create_attr("scale")
        return level, marginal, lower, upper, scale

    @property
    def l(self) -> ImplicitParameter:  # noqa: E741, E743
        return self._l

    @property
    def m(self) -> ImplicitParameter:
        return self._m

    @property
    def lo(self) -> ImplicitParameter:
        return self._lo

    @property
    def up(self) -> ImplicitParameter:
        return self._up

    @property
    def scale(self) -> ImplicitParameter:
        return self._s

    @property
    def stage(self) -> ImplicitParameter:
        return self._stage

    @property
    def range(self) -> ImplicitParameter:
        return self._range

    @property
    def slacklo(self) -> ImplicitParameter:
        return self._slacklo

    @property
    def slackup(self) -> ImplicitParameter:
        return self._slackup

    @property
    def slack(self) -> ImplicitParameter:
        return self._slack

    @property
    def infeas(self) -> ImplicitParameter:
        return self._infeas

    @property
    def definition(self) -> Optional["Expression"]:
        return self._definition  # pragma: no cover

    @definition.setter
    def definition(self, assignment: Optional["Expression"] = None) -> None:
        """Needed for scalar equations
        >>> eq..  sum(wh,build(wh)) =l= 1;
        >>> eq.definition = Sum(wh, build[wh]) <= 1
        """
        equation_map = {
            "nonbinding": "=n=",
            "external": "=x=",
            "cone": "=c=",
            "boolean": "=b=",
        }

        if assignment is not None and self.type in equation_map.keys():
            assignment._op_type = equation_map[self.type]

        statement = expression.Expression(
            implicits.ImplicitEquation(
                self.parent,
                name=self.name,
                type=self.type,
                domain=self.domain,
            ),
            "..",
            assignment,
        )

        self.container._addStatement(statement)
        self._definition = statement

        if not self.container.delayed_execution:
            self.container._run()

    def gamsRepr(self) -> str:
        representation = f"{self.name}"
        if len(self.domain):
            set_strs = []
            for set in self.domain:
                if isinstance(
                    set, (gams_set.Set, alias.Alias, implicits.ImplicitSet)
                ):
                    set_strs.append(set.gamsRepr())
                elif isinstance(set, str):
                    if set == "*":
                        set_strs.append(set)
                    else:
                        set_strs.append('"' + set + '"')

            domain_str = "(" + ",".join(set_strs) + ")"

            representation += domain_str

        return representation

src/gamspy/_symbols/implicits/implicit_equation.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import Any
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
import gamspy._algebra.operable as operable
import gamspy.utils as utils
from gamspy._symbols.implicits.implicit_symbol import ImplicitSymbol

if TYPE_CHECKING:
    from gamspy import Set, Parameter, Variable, Equation
    from gamspy._algebra.expression import Expression


class ImplicitParameter(ImplicitSymbol, operable.Operable):
    def __init__(
        self,
        parent: Union["Parameter", "Variable", "Equation"],
        name: str,
        domain: list[Union["Set", str]] = [],
        records: Optional[Any] = None,
    ) -> None:
        """Implicit Parameter

        Parameters
        ----------
        parent : Parameter | Variable | Equation
        name : str
        domain : List[Set | str], optional
        records : Any, optional
        """
        super().__init__(parent, name, domain)
        self._records = records
        self._assignment = None

    @property
    def assignment(self):
        return self._assignment  # pragma: no cover

    @assignment.setter
    def assignment(self, assignment) -> None:
        self._assignment = assignment

        statement = expression.Expression(
            ImplicitParameter(
                name=self.name,
                domain=self.domain,
                parent=self.parent,
            ),
            "=",
            assignment,
        )

        self.container._addStatement(statement)

        if self.container.delayed_execution:
            self.parent._is_dirty = True
        else:
            self.container._run()

    def __neg__(self) -> ImplicitParameter:
        return ImplicitParameter(
            parent=self.parent,
            name=f"-{self.name}",
            domain=self.domain,
        )

    def __invert__(self):
        return expression.Expression("", "not", self)

    def __getitem__(self, indices: Union[list, str]) -> ImplicitParameter:
        domain: list = utils._toList(indices)
        return ImplicitParameter(
            parent=self.parent, name=self.name, domain=domain
        )

    def __setitem__(
        self, indices: Union[list, str], assignment: "Expression"
    ) -> None:
        domain: list = utils._toList(indices)

        statement = expression.Expression(
            ImplicitParameter(
                parent=self.parent, name=self.name, domain=domain
            ),
            "=",
            assignment,
        )

        self.container._addStatement(statement)

        if self.container.delayed_execution:
            self.parent._is_dirty = True
        else:
            self.container._run()

    def __eq__(self, other):  # type: ignore
        return expression.Expression(self, "==", other)

    def gamsRepr(self) -> str:
        """Representation of the parameter in GAMS syntax.

        Returns:
            str: String representation of the parameter in GAMS syntax.
        """
        representation = self.name
        if self.domain:
            representation += utils._getDomainStr(self.domain)

        return representation

src/gamspy/_symbols/implicits/implicit_parameter.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import List
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
import gamspy._algebra.operable as operable
import gamspy.utils as utils
from gamspy._symbols.implicits.implicit_symbol import ImplicitSymbol

if TYPE_CHECKING:
    from gamspy import Alias, Set
    from gamspy._algebra.expression import Expression


class ImplicitSet(ImplicitSymbol, operable.Operable):
    """
    Implicit Set

    Parameters
    ----------
    container : Container
    name : str
    domain : List[Set | str], optional
    """

    def __init__(
        self,
        parent: Union["Set", "Alias"],
        name: str,
        domain: List[Union["Set", str]] = [],
    ) -> None:
        super().__init__(parent, name, domain)

    def __invert__(self) -> "Expression":
        return expression.Expression("", "not", self)

    def __ge__(self, other) -> "Expression":
        return expression.Expression(self, ">=", other)

    def __le__(self, other) -> "Expression":
        return expression.Expression(self, "<=", other)

    def gamsRepr(self) -> str:
        representation = self.name

        if self.domain:
            representation += utils._getDomainStr(self.domain)

        return representation

src/gamspy/_symbols/implicits/implicit_set.py
GAMS-dev/gamspy
from abc import ABC

import gamspy._algebra.condition as condition


class ImplicitSymbol(ABC):
    def __init__(self, parent, name, domain) -> None:
        self.parent = parent
        self.container = parent.container
        self.name = name
        self.domain = domain
        self.where = condition.Condition(self)

    def gamsRepr(self):
        """Representation of the implicit symbol in GAMS"""

src/gamspy/_symbols/implicits/implicit_symbol.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
import gamspy._algebra.operable as operable
import gamspy._symbols.implicits as implicits
import gamspy.utils as utils
from gamspy._symbols.implicits.implicit_symbol import ImplicitSymbol

if TYPE_CHECKING:
    from gams.transfer import Variable
    from gams.transfer import Set


class ImplicitVariable(ImplicitSymbol, operable.Operable):
    """
    Implicit Variable

    Parameters
    ----------
    parent : Variable
    name : str
    domain : List[Set | str]
    """

    def __init__(
        self,
        parent: "Variable",
        name: str,
        domain: list[Union["Set", str]],
    ):
        super().__init__(parent, name, domain)
        self._l, self._m, self._lo, self._up, self._s = self._init_attributes()
        self._fx = self._create_attr("fx")
        self._prior = self._create_attr("prior")
        self._stage = self._create_attr("stage")

    def _create_attr(self, attr_name: str):
        return implicits.ImplicitParameter(
            self.parent, f"{self.gamsRepr()}.{attr_name}"
        )

    def _init_attributes(self):
        level = self._create_attr("l")
        marginal = self._create_attr("m")
        lower = self._create_attr("lo")
        upper = self._create_attr("up")
        scale = self._create_attr("scale")
        return level, marginal, lower, upper, scale

    @property
    def l(self) -> implicits.ImplicitParameter:  # noqa: E741, E743
        return self._l

    @property
    def m(self) -> implicits.ImplicitParameter:
        return self._m

    @property
    def lo(self) -> implicits.ImplicitParameter:
        return self._lo

    @property
    def up(self) -> implicits.ImplicitParameter:
        return self._up

    @property
    def scale(self) -> implicits.ImplicitParameter:
        return self._s

    @property
    def fx(self) -> implicits.ImplicitParameter:
        return self._fx

    @property
    def prior(self) -> implicits.ImplicitParameter:
        return self._prior

    @property
    def stage(self) -> implicits.ImplicitParameter:
        return self._stage

    def __neg__(self):
        return implicits.ImplicitVariable(
            self.parent, name=f"-{self.name}", domain=self.domain
        )

    def __eq__(self, other):  # type: ignore
        return expression.Expression(self, "=e=", other)

    def gamsRepr(self) -> str:
        representation = self.name
        if self.domain:
            representation += utils._getDomainStr(self.domain)

        return representation

src/gamspy/_symbols/implicits/implicit_variable.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from typing import Any
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt
import pandas as pd

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.utils as utils
from gamspy._symbols.symbol import Symbol

if TYPE_CHECKING:
    from gamspy import Set, Container
    from gamspy._algebra.expression import Expression


class Parameter(gt.Parameter, operable.Operable, Symbol):
    """
    Represents a parameter symbol in GAMS.
    https://www.gams.com/latest/docs/UG_DataEntry.html#UG_DataEntry_Parameters

    Parameters
    ----------
    container : Container
    name : str
    domain : list, optional
    records : Any, optional
    domain_forwarding : bool, optional
    description : str, optional
    uels_on_axes : bool

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i", records=['i1','i2'])
    >>> a = gp.Parameter(m, "a", [i], records=[['i1',1],['i2',2]])

    """

    def __init__(
        self,
        container: "Container",
        name: str,
        domain: Optional[List[Union[str, "Set"]]] = None,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
    ):
        # enable load on demand
        self._is_dirty = False

        # allow freezing
        self._is_frozen = False

        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        super().__init__(
            container,
            name,
            domain,
            records,
            domain_forwarding,
            description,
            uels_on_axes,
        )

        # allow conditions
        self.where = condition.Condition(self)

        # add statement
        self.container._addStatement(self)

    def __getitem__(
        self, indices: Union[tuple, str]
    ) -> implicits.ImplicitParameter:
        domain = utils._toList(indices)
        return implicits.ImplicitParameter(self, name=self.name, domain=domain)

    def __setitem__(
        self,
        indices: Union[tuple, str, implicits.ImplicitSet],
        assignment: "Expression",
    ) -> None:
        domain = utils._toList(indices)

        statement = expression.Expression(
            implicits.ImplicitParameter(self, name=self.name, domain=domain),
            "=",
            assignment,
        )

        self.container._addStatement(statement)

        if self.container.delayed_execution:
            self._is_dirty = True
        else:
            self.container._run()

    def __eq__(self, other):  # type: ignore
        return expression.Expression(self, "==", other)

    def __neg__(self):
        return implicits.ImplicitParameter(
            self, name=f"-{self.name}", domain=self._domain
        )

    @property
    def assignment(self):
        """
        Assigned expression

        Returns
        -------
        Expression
        """
        return self._assignment

    @assignment.setter
    def assignment(self, assignment):
        self._assignment = assignment

        statement = expression.Expression(
            implicits.ImplicitParameter(self, name=self.name),
            "=",
            assignment,
        )

        self.container._addStatement(statement)

        if self.container.delayed_execution:
            self._is_dirty = True
        else:
            self.container._run()

    @property
    def records(self):
        """
        Records of the Parameter

        Returns
        -------
        DataFrame
        """
        if not self._is_dirty:
            return self._records

        self.container._run()

        return self._records

    @records.setter
    def records(self, records):
        if records is not None:
            if not isinstance(records, pd.DataFrame):
                raise TypeError("Symbol 'records' must be type DataFrame")

        # set records
        self._records = records

        self._requires_state_check = True
        self.modified = True

        self.container._requires_state_check = True
        self.container.modified = True

        if self._records is not None:
            if self.domain_forwarding:  # pragma: no cover
                self._domainForwarding()

                # reset state check flags for all symbols in the container
                for symnam, symobj in self.container.data.items():
                    symobj._requires_state_check = True

    def gamsRepr(self) -> str:
        """
        Representation of this Parameter in GAMS language.

        Returns
        -------
        str
        """
        return self.name

    def getStatement(self) -> str:
        """
        Statement of the Parameter definition

        Returns
        -------
        str
        """
        statement_name = self.name
        if self.domain:
            statement_name += utils._getDomainStr(self.domain)

        output = f"Parameter {statement_name}"

        if self.description:
            output += ' "' + self.description + '"'

        output += ";"

        return output

src/gamspy/_symbols/parameter.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from __future__ import annotations

from typing import Any
from typing import List
from typing import Literal
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt
import pandas as pd

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.utils as utils
from gamspy._symbols.symbol import Symbol
from gamspy.exceptions import GamspyException

if TYPE_CHECKING:
    from gamspy import Alias, Container
    from gamspy._algebra.operable import Operable
    from gamspy._algebra.expression import Expression


class Set(gt.Set, operable.Operable, Symbol):
    """
    Represents a Set symbol in GAMS.
    https://www.gams.com/latest/docs/UG_SetDefinition.html

    Parameters
    ----------
    container : Container
    name : str
    domain : list, optional
    is_singleton : bool, optional
    records : int | float | DataFrame, optional
    domain_forwarding : bool, optional
    description : str, optional
    uels_on_axes : bool

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i", records=['i1','i2'])

    """

    def __init__(
        self,
        container: "Container",
        name: str,
        domain: Optional[List[Union[Set, str]]] = None,
        is_singleton: bool = False,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
    ):
        # enable load on demand
        self._is_dirty = False

        # allow conditions
        self.where = condition.Condition(self)

        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        self._singleton_check(is_singleton, records, domain)

        super().__init__(
            container,
            name,
            domain,
            is_singleton,
            records,
            domain_forwarding,
            description,
            uels_on_axes,
        )

        # add statement
        self.container._addStatement(self)

        # iterator index
        self._current_index = 0

    def __len__(self):
        if self.records is not None:
            return len(self.records.index)

        return 0

    def __next__(self):
        if self._current_index < len(self):
            row = self.records.iloc[self._current_index]
            self._current_index += 1
            return row

        self._current_index = 0
        raise StopIteration

    def __iter__(self):
        return self

    def __getitem__(self, indices: Union[tuple, str]) -> implicits.ImplicitSet:
        domain = utils._toList(indices)
        return implicits.ImplicitSet(self, name=self.name, domain=domain)

    def __setitem__(
        self,
        indices: Union[tuple, str],
        assignment,
    ):
        domain = utils._toList(indices)

        if isinstance(assignment, bool):
            assignment = "yes" if assignment is True else "no"  # type: ignore

        statement = expression.Expression(
            implicits.ImplicitSet(self, name=self.name, domain=domain),
            "=",
            assignment,
        )

        self.container._addStatement(statement)

        if self.container.delayed_execution:
            self._is_dirty = True
        else:
            self.container._run()

    def _singleton_check(
        self, is_singleton: bool, records: Union[Any, None], domain
    ):
        if is_singleton:
            if records is not None and len(records) > 1:
                raise GamspyException(
                    "Singleton set records size cannot be more than one."
                )

    # Set Attributes
    @property
    def pos(self):
        """
        Element position in the current set, starting with 1.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "pos")

    @property
    def ord(self):
        """
        Same as .pos but for ordered sets only.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "ord")

    @property
    def off(self):
        """
        Element position in the current set minus 1. So .off = .pos - 1

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "off")

    @property
    def rev(self):
        """
        Reverse element position in the current set, so the value for
        the last element is 0, the value for the penultimate is 1, etc.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "rev")

    @property
    def uel(self):
        """
        Element position in the unique element list.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "uel")

    @property
    def len(self):
        """
        Length of the set element name (a count of the number of characters).

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "len")

    @property
    def tlen(self):
        """
        Length of the set element text (a count of the number of characters).

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "tlen")

    @property
    def val(self):
        """
        If a set element is a number, this attribute gives the value of the number.
        For extended range arithmetic symbols, the symbols are reproduced.
        If a set element is a string that is not a number, then this attribute is
        not defined and trying to use it results in an error.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "val")

    @property
    def tval(self):
        """
        If a set element text is a number, this attribute gives the value of the number.
        For extended range arithmetic symbols, the symbols are reproduced.
        If a set element text is a string that is not a number, then this attribute is
        not defined and trying to use it results in an error.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "tval")

    @property
    def first(self):
        """
        Returns 1 for the first set element, otherwise 0.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "first")

    @property
    def last(self):
        """
        Returns 1 for the last set element, otherwise 0.

        Returns
        -------
        Expression
        """
        return expression.Expression(f"{self.name}", ".", "last")

    def lag(
        self,
        n: Union[int, "Operable"],
        type: Literal["linear", "circular"] = "linear",
    ):
        """
        Lag operation shifts the values of a Set or Alias by one to the left

        Parameters
        ----------
        n : int | Operable
        type : 'linear' or 'circular', optional

        Returns
        -------
        ImplicitSet

        Raises
        ------
        ValueError
            When type is not circular or linear
        """
        jump = n if isinstance(n, int) else n.gamsRepr()  # type: ignore

        if type == "circular":
            return implicits.ImplicitSet(self, name=f"{self.name} -- {jump}")
        elif type == "linear":
            return implicits.ImplicitSet(self, name=f"{self.name} - {jump}")

        raise ValueError("Lag type must be linear or circular")

    def lead(
        self,
        n: Union[int, "Operable"],
        type: Literal["linear", "circular"] = "linear",
    ):
        """
        Lead shifts the values of a Set or Alias by one to the right

        Parameters
        ----------
        n : int | Operable
        type : 'linear' or 'circular', optional

        Returns
        -------
        ImplicitSet

        Raises
        ------
        ValueError
            When type is not circular or linear
        """
        jump = n if isinstance(n, int) else n.gamsRepr()  # type: ignore

        if type == "circular":
            return implicits.ImplicitSet(self, name=f"{self.name} ++ {jump}")
        elif type == "linear":
            return implicits.ImplicitSet(self, name=f"{self.name} + {jump}")

        raise ValueError("Lead type must be linear or circular")

    @property
    def records(self):
        """
        Records of the Set

        Returns
        -------
        DataFrame
        """
        if not self._is_dirty:
            return self._records

        self.container._run()

        return self._records

    @records.setter
    def records(self, records):
        if records is not None:
            if not isinstance(records, pd.DataFrame):
                raise TypeError("Symbol 'records' must be type DataFrame")

        # set records
        self._records = records

        self._requires_state_check = True
        self.modified = True

        self.container._requires_state_check = True
        self.container.modified = True

        if self._records is not None:
            if self.domain_forwarding:  # pragma: no cover
                self._domainForwarding()

                # reset state check flags for all symbols in the container
                for symnam, symobj in self.container.data.items():
                    symobj._requires_state_check = True

    def sameAs(self, other: Union["Set", "Alias"]) -> "Expression":
        return expression.Expression(
            "sameAs(", ",".join([self.gamsRepr(), other.gamsRepr()]), ")"
        )

    def gamsRepr(self) -> str:
        """
        Representation of this Set in GAMS language.

        Returns
        -------
        str
        """
        return self.name

    def getStatement(self) -> str:
        """
        Statement of the Set definition

        Returns
        -------
        str
        """
        output = f"Set {self.name}"

        if self._is_singleton:
            output = f"Singleton {output}"

        domain_str = ",".join(
            [set if isinstance(set, str) else set.name for set in self.domain]
        )
        output += f"({domain_str})"

        if self.description:
            output += f' "{self.description}"'

        output += ";"

        return output

src/gamspy/_symbols/set.py
GAMS-dev/gamspy
from abc import ABC
from abc import abstractmethod


class Symbol(ABC):
    @abstractmethod
    def gamsRepr(self):
        """Representation of the symbol in GAMS"""

    @abstractmethod
    def getStatement(self):
        """Declaration string of the symbol in GAMS"""

src/gamspy/_symbols/symbol.py
GAMS-dev/gamspy
from typing import TYPE_CHECKING

import gams.transfer as gt

import gamspy._algebra.condition as condition
import gamspy.utils as utils

if TYPE_CHECKING:
    from gamspy import Container


class UniverseAlias(gt.UniverseAlias):
    def __init__(self, container: "Container", name: str = "universe"):
        """
        Represents a UniverseAlias symbol in GAMS.

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

        Examples
        --------
        >>> import gamspy as gp
        >>> m = gp.Container()
        >>> universe = gp.UniverseAlias(m, "universe")
        >>> universe.records
        Empty DataFrame
        Columns: [uni]
        Index: []
        >>> i = gp.Set(m, "i", records=['i1', 'i2'])
        >>> universe.records
          uni
        0  i1
        1  i2

        """
        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        super().__init__(container, name)

        # allow conditions
        self.where = condition.Condition(self)

        # add statement
        self.container._addStatement(self)

        # iterator index
        self._current_index = 0

    def __len__(self):
        if not self.records.empty:
            return len(self.records.index)

        return 0

    def __next__(self):
        if self._current_index < len(self):
            row = self.records.iloc[self._current_index]
            self._current_index += 1
            return row

        self._current_index = 0
        raise StopIteration

    def __iter__(self):
        return self

src/gamspy/_symbols/universe_alias.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from enum import Enum
from typing import Any
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt
import pandas as pd

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.utils as utils
from gamspy._symbols.symbol import Symbol

if TYPE_CHECKING:
    from gamspy import Set, Container


class VariableType(Enum):
    BINARY = "binary"
    INTEGER = "integer"
    POSITIVE = "positive"
    NEGATIVE = "negative"
    FREE = "free"
    SOS1 = "sos1"
    SOS2 = "sos2"
    SEMICONT = "semicont"
    SEMIINT = "semiint"

    @classmethod
    def values(cls):
        return list(cls._value2member_map_.keys())

    def __str__(self) -> str:
        return self.value


class Variable(gt.Variable, operable.Operable, Symbol):
    """
    Represents a variable symbol in GAMS.
    https://www.gams.com/latest/docs/UG_Variables.html

    Parameters
    ----------
    container : Container
    name : str
    type : str, optional
    domain : list, optional
    records : DataFrame, optional
    domain_forwarding : bool, optional
    description : str, optional

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i", records=['i1','i2'])
    >>> v = gp.Variable(m, "a", domain=[i])

    """

    def __init__(
        self,
        container: "Container",
        name: str,
        type: str = "free",
        domain: Optional[List[Union[str, "Set"]]] = None,
        records: Optional[Any] = None,
        domain_forwarding: bool = False,
        description: str = "",
        uels_on_axes: bool = False,
    ):
        type = self._cast_type(type)

        # enable load on demand
        self._is_dirty = False

        # allow freezing
        self._is_frozen = False

        # check if the name is a reserved word
        name = utils._reservedCheck(name)

        super().__init__(
            container,
            name,
            type,
            domain,
            records,
            domain_forwarding,
            description,
            uels_on_axes,
        )

        # allow conditions
        self.where = condition.Condition(self)

        # add statement
        self.container._addStatement(self)

        # create attributes
        self._l, self._m, self._lo, self._up, self._s = self._init_attributes()
        self._fx = self._create_attr("fx")
        self._prior = self._create_attr("prior")
        self._stage = self._create_attr("stage")

    def _cast_type(self, type: Union[str, VariableType]) -> str:
        if isinstance(type, str) and type.lower() not in VariableType.values():
            raise ValueError(
                f"Allowed variable types: {VariableType.values()} but"
                f" found {type}."
            )

        if isinstance(type, VariableType):
            type = type.value

        return type.lower()

    def __getitem__(
        self, indices: Union[tuple, str]
    ) -> implicits.ImplicitVariable:
        domain = utils._toList(indices)
        return implicits.ImplicitVariable(self, name=self.name, domain=domain)

    def __neg__(self):
        return implicits.ImplicitVariable(
            self, name=f"-{self.name}", domain=self.domain
        )

    def __eq__(self, other):  # type: ignore
        return expression.Expression(self, "=e=", other)

    def _init_attributes(self):
        level = self._create_attr("l")
        marginal = self._create_attr("m")
        lower = self._create_attr("lo")
        upper = self._create_attr("up")
        scale = self._create_attr("scale")
        return level, marginal, lower, upper, scale

    def _create_attr(self, attr_name):
        return implicits.ImplicitParameter(
            self,
            name=f"{self.name}.{attr_name}",
            records=self.records,
        )

    @property
    def l(self):  # noqa: E741,E743
        """
        Level

        Returns
        -------
        ImplicitParameter
        """
        return self._l

    @property
    def m(self):
        """
        Marginal

        Returns
        -------
        ImplicitParameter
        """
        return self._m

    @property
    def lo(self):
        """
        Lower bound

        Returns
        -------
        ImplicitParameter
        """
        return self._lo

    @property
    def up(self):
        """
        Upper bound

        Returns
        -------
        ImplicitParameter
        """
        return self._up

    @property
    def scale(self):
        """
        Scale

        Returns
        -------
        ImplicitParameter
        """
        return self._s

    @property
    def fx(self):
        """
        Fx

        Returns
        -------
        ImplicitParameter
        """
        return self._fx

    @property
    def prior(self):
        """
        Prior

        Returns
        -------
        ImplicitParameter
        """
        return self._prior

    @property
    def stage(self):
        """
        Stage

        Returns
        -------
        ImplicitParameter
        """
        return self._stage

    @property
    def records(self):
        """
        Records of the Variable

        Returns
        -------
        DataFrame
        """
        if not self._is_dirty:
            return self._records

        self.container._run()

        return self._records

    @records.setter
    def records(self, records):
        if records is not None:
            if not isinstance(records, pd.DataFrame):
                raise TypeError("Symbol 'records' must be type DataFrame")

        # set records
        self._records = records

        self._requires_state_check = True
        self.modified = True

        self.container._requires_state_check = True
        self.container.modified = True

        if self._records is not None:
            if self.domain_forwarding:  # pragma: no cover
                self._domainForwarding()

                # reset state check flags for all symbols in the container
                for _, symbol in self.container.data.items():
                    symbol._requires_state_check = True

    def gamsRepr(self) -> str:
        """
        Representation of this Variable in GAMS language.

        Returns
        -------
        str
        """
        return self.name

    def getStatement(self) -> str:
        """
        Statement of the Variable definition

        Returns
        -------
        str
        """
        output = self.type + " "

        statement_name = self.name
        if self.domain:
            statement_name += utils._getDomainStr(self.domain)

        output += f"Variable {statement_name}"

        if self.description:
            output += ' "' + self.description + '"'

        output += ";"

        return output

src/gamspy/_symbols/variable.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
"""Exception classes for Gamspy"""


class GamspyException(Exception):
    """Plain Gamspy exception."""

src/gamspy/exceptions.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from gamspy.math.log_power import cv_power
from gamspy.math.log_power import exp
from gamspy.math.log_power import log
from gamspy.math.log_power import log10
from gamspy.math.log_power import log2
from gamspy.math.log_power import log_beta
from gamspy.math.log_power import log_gamma
from gamspy.math.log_power import logit
from gamspy.math.log_power import power
from gamspy.math.log_power import rpower
from gamspy.math.log_power import sign_power
from gamspy.math.log_power import sllog10
from gamspy.math.log_power import sqlog10
from gamspy.math.log_power import sqr
from gamspy.math.log_power import vc_power
from gamspy.math.misc import abs
from gamspy.math.misc import beta
from gamspy.math.misc import ceil
from gamspy.math.misc import dist
from gamspy.math.misc import div
from gamspy.math.misc import div0
from gamspy.math.misc import entropy
from gamspy.math.misc import errorf
from gamspy.math.misc import factorial
from gamspy.math.misc import floor
from gamspy.math.misc import fractional
from gamspy.math.misc import gamma
from gamspy.math.misc import ifthen
from gamspy.math.misc import lse_max
from gamspy.math.misc import lse_max_sc
from gamspy.math.misc import lse_min
from gamspy.math.misc import lse_min_sc
from gamspy.math.misc import Max
from gamspy.math.misc import Min
from gamspy.math.misc import mod
from gamspy.math.misc import ncp_cm
from gamspy.math.misc import ncp_f
from gamspy.math.misc import ncpVUpow
from gamspy.math.misc import ncpVUsin
from gamspy.math.misc import poly
from gamspy.math.misc import rand_binomial
from gamspy.math.misc import rand_linear
from gamspy.math.misc import rand_triangle
from gamspy.math.misc import regularized_beta
from gamspy.math.misc import regularized_gamma
from gamspy.math.misc import Round
from gamspy.math.misc import sigmoid
from gamspy.math.misc import sign
from gamspy.math.misc import slexp
from gamspy.math.misc import slrec
from gamspy.math.misc import sqexp
from gamspy.math.misc import sqrec
from gamspy.math.misc import sqrt
from gamspy.math.misc import truncate
from gamspy.math.probability import binomial
from gamspy.math.probability import centropy
from gamspy.math.probability import normal
from gamspy.math.probability import uniform
from gamspy.math.probability import uniformInt
from gamspy.math.trigonometric import acos
from gamspy.math.trigonometric import asin
from gamspy.math.trigonometric import atan
from gamspy.math.trigonometric import atan2
from gamspy.math.trigonometric import cos
from gamspy.math.trigonometric import cosh
from gamspy.math.trigonometric import sin
from gamspy.math.trigonometric import sinh
from gamspy.math.trigonometric import tan
from gamspy.math.trigonometric import tanh

__all__ = [
    "cos",
    "cosh",
    "sin",
    "sinh",
    "acos",
    "asin",
    "tan",
    "tanh",
    "atan",
    "atan2",
    "exp",
    "power",
    "sqr",
    "sqrt",
    "log",
    "log2",
    "log10",
    "log_beta",
    "log_gamma",
    "logit",
    "abs",
    "ceil",
    "dist",
    "div",
    "div0",
    "factorial",
    "floor",
    "fractional",
    "Min",
    "Max",
    "mod",
    "Round",
    "sign",
    "binomial",
    "centropy",
    "normal",
    "uniform",
    "uniformInt",
    "cv_power",
    "rpower",
    "sign_power",
    "sllog10",
    "sqlog10",
    "vc_power",
    "slexp",
    "sqexp",
    "truncate",
    "ifthen",
    "beta",
    "regularized_beta",
    "gamma",
    "regularized_gamma",
    "entropy",
    "lse_max",
    "lse_max_sc",
    "lse_min",
    "lse_min_sc",
    "ncp_cm",
    "ncp_f",
    "ncpVUpow",
    "ncpVUsin",
    "poly",
    "rand_binomial",
    "rand_linear",
    "rand_triangle",
    "slrec",
    "sqrec",
    "errorf",
    "sigmoid",
]

src/gamspy/math/__init__.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 math
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
from gamspy.math.misc import _stringify

if TYPE_CHECKING:
    from gamspy._algebra.expression import Expression
    from gamspy._symbols.symbol import Symbol


def exp(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Exponential of x (i.e. e^x)

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.exp(x)
    return expression.Expression("exp(", x.gamsRepr(), ")")


def log(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Natural logarithm of x (i.e. logarithm base e of x)

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.log(x)
    return expression.Expression("log(", x.gamsRepr(), ")")


def log_beta(
    x: Union[int, float, "Symbol"], y: Union[int, float, "Symbol"]
) -> "Expression":
    """
    Log beta function

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    y_str = _stringify(y)
    return expression.Expression("logBeta(", f"{x_str},{y_str}", ")")


def log_gamma(
    x: Union[int, float, "Symbol"], y: Union[int, float, "Symbol"]
) -> "Expression":
    """
    Log gamma function

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    y_str = _stringify(y)
    return expression.Expression("logGamma(", f"{x_str},{y_str}", ")")


def logit(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Natural logarithm of x (i.e. logarithm base e of x)

    Returns
    -------
    Expression | float
    """
    x_str = _stringify(x)
    return expression.Expression("logit(", x_str, ")")


def log2(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Binary logarithm (i.e. logarithm base 2 of x)

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.log2(x)
    return expression.Expression("log2(", x.gamsRepr(), ")")


def log10(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Common logarithm (i.e. logarithm base 10 of x)

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.log10(x)
    return expression.Expression("log10(", x.gamsRepr(), ")")


def power(
    base: Union[float, "Symbol"], exponent: Union[float, "Symbol"]
) -> Union["Expression", float]:
    """
    Base to the exponent power (i.e. base ^ exponent)

    Parameters
    ----------
    base : float | Symbol
    exponent : float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(base, (int, float)) and isinstance(exponent, (int, float)):
        return math.pow(base, exponent)

    base_str = (
        str(base) if isinstance(base, (int, float, str)) else base.gamsRepr()
    )
    exponent_str = (
        str(exponent)
        if isinstance(exponent, (int, float, str))
        else exponent.gamsRepr()
    )
    return expression.Expression("power(", f"{base_str},{exponent_str}", ")")


def cv_power(
    base: Union[float, "Symbol"], exponent: Union[float, "Symbol"]
) -> Union["Expression", float]:
    """
    Real power (i.e. base ^ exponent where X >= 0)

    Parameters
    ----------
    base : float | Symbol
    exponent : float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(base, (int, float)) and isinstance(exponent, (int, float)):
        return math.pow(base, exponent)

    base_str = (
        str(base) if isinstance(base, (int, float, str)) else base.gamsRepr()
    )
    exponent_str = (
        str(exponent)
        if isinstance(exponent, (int, float, str))
        else exponent.gamsRepr()
    )
    return expression.Expression("cvPower(", f"{base_str},{exponent_str}", ")")


def rpower(base: Union[float, "Symbol"], exponent: Union[float, "Symbol"]):
    """
    Returns x^y for x > 0 and also for x = 0 and restricted values of y

    Parameters
    ----------
    base : float | Symbol
    exponent : float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(base, (int, float)) and isinstance(exponent, (int, float)):
        return math.pow(base, exponent)

    base_str = (
        str(base) if isinstance(base, (int, float, str)) else base.gamsRepr()
    )
    exponent_str = (
        str(exponent)
        if isinstance(exponent, (int, float, str))
        else exponent.gamsRepr()
    )
    return expression.Expression("rPower(", f"{base_str},{exponent_str}", ")")


def sign_power(base: Union[float, "Symbol"], exponent: Union[float, "Symbol"]):
    """
    Signed power for y > 0.

    Parameters
    ----------
    base : float | Symbol
    exponent : float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(base, (int, float)) and isinstance(exponent, (int, float)):
        return math.pow(base, exponent)

    base_str = (
        str(base) if isinstance(base, (int, float, str)) else base.gamsRepr()
    )
    exponent_str = (
        str(exponent)
        if isinstance(exponent, (int, float, str))
        else exponent.gamsRepr()
    )
    return expression.Expression(
        "signPower(", f"{base_str},{exponent_str}", ")"
    )


def sllog10(
    x: Union[int, float, "Symbol"], S: Union[int, float] = 1.0e-150
) -> "Expression":
    """
    Smooth (linear) logarithm base 10

    Parameters
    ----------
    x : int | float | Symbol
    S : int | float, by default 1.0e-150

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("sllog10(", f"{x_str},{S}", ")")


def sqlog10(
    x: Union[int, float, "Symbol"], S: Union[int, float] = 1.0e-150
) -> "Expression":
    """
    Smooth (quadratic) logarithm base 10

    Parameters
    ----------
    x : int | float | Symbol
    S : int | float, by default 1.0e-150

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("sqlog10(", f"{x_str},{S}", ")")


def vc_power(base: Union[float, "Symbol"], exponent: Union[float, "Symbol"]):
    """
    Returns x^y for x >= 0

    Parameters
    ----------
    base : float | Symbol
    exponent : float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(base, (int, float)) and isinstance(exponent, (int, float)):
        return math.pow(base, exponent)

    base_str = (
        str(base) if isinstance(base, (int, float, str)) else base.gamsRepr()
    )
    exponent_str = (
        str(exponent)
        if isinstance(exponent, (int, float, str))
        else exponent.gamsRepr()
    )
    return expression.Expression("vcPower(", f"{base_str},{exponent_str}", ")")


def sqr(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Square of x

    Parameters
    ----------
    x : float | Symbol

    Returns
    -------
    Expression | float
    """
    return power(x, 2)

src/gamspy/math/log_power.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 math
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
import gamspy.utils as utils
from gamspy.exceptions import GamspyException

if TYPE_CHECKING:
    from gamspy._algebra.expression import Expression
    from gamspy._symbols.symbol import Symbol
    from gamspy._symbols.implicits.implicit_symbol import ImplicitSymbol


def _stringify(x: Union[int, float, "Symbol", "ImplicitSymbol"]):
    return str(x) if isinstance(x, (int, float)) else x.gamsRepr()


def abs(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Absolute value of x (i.e. ``|x|``)

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.fabs(x)
    return expression.Expression("abs(", x.gamsRepr(), ")")


def ceil(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    The smallest integer greater than or equal to x

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.ceil(x)
    return expression.Expression("ceil(", x.gamsRepr(), ")")


def div(
    dividend: Union[int, float, "Symbol"], divisor: Union[int, float, "Symbol"]
) -> Union["Expression", float]:
    """
    Dividing operation

    Parameters
    ----------
    dividend : int | float | Symbol
    divisor : int | float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(dividend, (int, float)) and isinstance(
        divisor, (int, float)
    ):
        return dividend / divisor

    dividend_str = _stringify(dividend)
    divisor_str = _stringify(divisor)

    return expression.Expression("div(", f"{dividend_str}, {divisor_str}", ")")


def div0(
    dividend: Union[int, float, "Symbol"], divisor: Union[int, float, "Symbol"]
) -> Union["Expression", float]:
    """
    Dividing operation

    Parameters
    ----------
    dividend : int | float | Symbol
    divisor : int | float | Symbol

    Returns
    -------
    Expression | float
    """
    if isinstance(dividend, (int, float)) and isinstance(
        divisor, (int, float)
    ):
        if divisor == 0:
            return 1e299

        return dividend / divisor

    dividend_str = _stringify(dividend)
    divisor_str = _stringify(divisor)

    return expression.Expression(
        "div0(", f"{dividend_str}, {divisor_str}", ")"
    )


def dist(
    x1: Union[Tuple[int, float], "Symbol"],
    x2: Union[Tuple[int, float], "Symbol"],
) -> Union["Expression", float]:
    if isinstance(x1, tuple) and isinstance(x2, tuple):
        return math.dist(x1, x2)

    if isinstance(x1, tuple) or isinstance(x2, tuple):
        raise Exception("Both should be a tuple or none")

    x1_str = _stringify(x1)
    x2_str = _stringify(x2)

    return expression.Expression("eDist(", f"{x1_str}, {x2_str}", ")")


def factorial(x: Union[int, "Symbol"]) -> Union["Expression", int]:
    """
    Factorial of x

    Returns
    -------
    Expression | int
    """
    if isinstance(x, int):
        return math.factorial(x)
    return expression.Expression("fact(", x.gamsRepr(), ")")


def floor(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    The greatest integer less than or equal to x

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.floor(x)
    return expression.Expression("floor(", x.gamsRepr(), ")")


def fractional(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Returns the fractional part of x

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        fraction, _ = math.modf(x)
        return fraction
    return expression.Expression("frac(", x.gamsRepr(), ")")


def Min(*values) -> "Expression":
    """
    Minimum value of the values, where the number of values may vary.

    Returns
    -------
    Expression
    """
    if all(isinstance(value, (int, float)) for value in values):
        return min(values)

    values_str = ",".join([value.gamsRepr() for value in values])
    return expression.Expression("min(", values_str, ")")


def Max(*values) -> "Expression":
    """
    Maximum value of the values, where the number of values may vary.

    Returns
    -------
    Expression
    """
    if all(isinstance(value, (int, float)) for value in values):
        return max(values)

    values_str = ",".join([value.gamsRepr() for value in values])
    return expression.Expression("max(", values_str, ")")


def mod(
    x: Union[float, "Symbol"], y: Union[float, "Symbol"]
) -> Union["Expression", int, float]:
    """
    Remainder of x divided by y.

    Returns
    -------
    Expression | int | float
    """
    if isinstance(x, (int, float)) and isinstance(y, (int, float)):
        return x % y

    x_str = _stringify(x)
    y_str = _stringify(y)
    return expression.Expression("mod(" + x_str, ",", y_str + ")")


def Round(x: "Symbol", num_decimals: int = 0) -> "Expression":
    """
    Round x to num_decimals decimal places.

    Parameters
    ----------
    x : Operable
    decimal : int, optional

    Returns
    -------
    Expression
    """
    if isinstance(x, (int, float)):
        return round(x, num_decimals)

    return expression.Expression(
        "round(", x.gamsRepr() + f", {num_decimals}", ")"
    )


def sign(x: "Symbol") -> "Expression":
    """
    Sign of x returns 1 if x > 0, -1 if x < 0, and 0 if x = 0

    Parameters
    ----------
    x : Operable

    Returns
    -------
    Expression
    """
    return expression.Expression("sign(", x.gamsRepr(), ")")


def slexp(
    x: Union[int, float, "Symbol"], S: Union[int, float] = 150
) -> "Expression":
    """
    Smooth (linear) exponential

    Parameters
    ----------
    x : int | float | Symbol
    S : int | float, by default 150

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("slexp(", f"{x_str},{S}", ")")


def sqexp(
    x: Union[int, float, "Symbol"], S: Union[int, float] = 150
) -> "Expression":
    """
    Smooth (quadratic) exponential

    Parameters
    ----------
    x : int | float | Symbol
    S : int | float, by default 150

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("sqexp(", f"{x_str},{S}", ")")


def sqrt(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Square root of x

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.sqrt(x)

    return expression.Expression("sqrt(", x.gamsRepr(), ")")


def truncate(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Returns the integer part of x

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        _, integer = math.modf(x)
        return integer
    return expression.Expression("trunc(", x.gamsRepr(), ")")


def beta(
    x: Union[int, float, "Symbol"], y: Union[int, float, "Symbol"]
) -> "Expression":
    """
    Beta function

    Parameters
    ----------
    x : int | float | Symbol
    y : int | float | Symbol

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    y_str = _stringify(y)
    return expression.Expression("beta(", f"{x_str},{y_str}", ")")


def regularized_beta(
    x: Union[int, float], y: Union[int, float], z: Union[int, float]
) -> "Expression":
    """
    Beta function

    Parameters
    ----------
    x : int | float
    y : int | float
    z : int | float

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    y_str = _stringify(y)
    z_str = _stringify(z)
    return expression.Expression("betaReg(", f"{x_str},{y_str},{z_str}", ")")


def gamma(x: Union[int, float, "Symbol"]) -> "Expression":
    """
    Gamma function

    Parameters
    ----------
    x : int | float

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("gamma(", f"{x_str}", ")")


def regularized_gamma(
    x: Union[int, float], a: Union[int, float]
) -> "Expression":
    """
    Gamma function

    Parameters
    ----------
    x : int | float
    a : int | float

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    a_str = _stringify(a)
    return expression.Expression("gammaReg(", f"{x_str},{a_str}", ")")


def lse_max(*xs) -> "Expression":
    """
    Smoothed Max via the Logarithm of the Sum of Exponentials

    Returns
    -------
    Expression
    """
    if len(xs) < 1:
        raise GamspyException("lse_max requires at least 1 x")

    x_str = ",".join([_stringify(x) for x in xs])
    return expression.Expression("lseMax(", x_str, ")")


def lse_max_sc(t, *xs) -> "Expression":
    """
    Scaled smoothed Max via the Logarithm of the Sum of Exponentials

    Returns
    -------
    Expression
    """
    t_str = _stringify(t)

    if len(xs) < 1:
        raise GamspyException("lse_max requires at least 1 x")

    x_str = ",".join([_stringify(x) for x in xs])

    return expression.Expression("lseMaxSc(", f"{t_str},{x_str}", ")")


def lse_min(*xs) -> "Expression":
    """
    Smoothed Min via the Logarithm of the Sum of Exponentials

    Returns
    -------
    Expression
    """
    if len(xs) < 1:
        raise GamspyException("lse_max requires at least 1 x")

    x_str = ",".join([_stringify(x) for x in xs])
    return expression.Expression("lseMin(", x_str, ")")


def lse_min_sc(t, *xs) -> "Expression":
    """
    Scaled smoothed Min via the Logarithm of the Sum of Exponentials

    Returns
    -------
    Expression
    """
    t_str = _stringify(t)

    if len(xs) < 1:
        raise GamspyException("lse_max requires at least 1 x")

    x_str = ",".join([_stringify(x) for x in xs])

    return expression.Expression("lseMinSc(", f"{t_str},{x_str}", ")")


def ncp_cm(
    x: "Symbol",
    y: "Symbol",
    z: Union[float, int],
) -> "Expression":
    """
    Chen-Mangasarian smoothing

    Parameters
    ----------
    x : Symbol
    y : Symbol
    z : int | float

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    y_str = _stringify(y)
    return expression.Expression(
        "ncpCM(", ",".join([x_str, y_str, str(z)]), ")"
    )


def ncp_f(
    x: "Symbol",
    y: "Symbol",
    z: Union[int, float] = 0,
) -> "Expression":
    """
    Fisher-Burmeister smoothing

    Parameters
    ----------
    x : Symbol
    y : Symbol
    z : int | float, optional

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    y_str = _stringify(y)
    return expression.Expression(
        "ncpF(", ",".join([x_str, y_str, str(z)]), ")"
    )


def ncpVUpow(
    r: "Symbol",
    s: "Symbol",
    mu: Union[int, float] = 0,
) -> "Expression":
    """
    NCP Veelken-Ulbrich: smoothed min(r,s)

    Parameters
    ----------
    r : Symbol
    s : Symbol
    mu : int | float, optional

    Returns
    -------
    Expression
    """
    r_str = _stringify(r)
    s_str = _stringify(s)
    return expression.Expression(
        "ncpVUpow(", ",".join([r_str, s_str, str(mu)]), ")"
    )


def ncpVUsin(
    r: "Symbol",
    s: "Symbol",
    mu: Union[int, float] = 0,
) -> "Expression":
    """
    NCP Veelken-Ulbrich: smoothed min(r,s)

    Parameters
    ----------
    r : Symbol
    s : Symbol
    mu : int | float, optional

    Returns
    -------
    Expression
    """
    r_str = _stringify(r)
    s_str = _stringify(s)
    return expression.Expression(
        "ncpVUsin(", ",".join([r_str, s_str, str(mu)]), ")"
    )


def poly(x, *args) -> "Expression":
    """
    Polynomial function

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    args_str = ",".join(str(arg) for arg in args)

    return expression.Expression("poly(", f"{x_str},{args_str}", ")")


def sigmoid(x: Union[int, float, "Symbol"]) -> "Expression":
    """
    Sigmoid of x

    Parameters
    ----------
    x : int | float | Symbol

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("sigmoid(", x_str, ")")


def rand_binomial(n: Union[int, float], p: Union[int, float]) -> "Expression":
    """
    Generate a random number from the binomial distribution, where n is the
    number of trials and p the probability of success for each trial

    Parameters
    ----------
    n : int | float
    p : int | float

    Returns
    -------
    Expression
    """
    return expression.Expression("randBinomial(", f"{n},{p}", ")")


def rand_linear(
    low: Union[int, float], slope: Union[int, float], high: Union[int, float]
) -> "Expression":
    """
    Generate a random number between low and high with linear distribution.
    slope must be greater than 2 / (high - low)

    Parameters
    ----------
    low : int | float
    slope : int | float
    high : int | float

    Returns
    -------
    Expression
    """
    return expression.Expression("randLinear(", f"{low},{slope},{high}", ")")


def rand_triangle(
    low: Union[int, float], mid: Union[int, float], high: Union[int, float]
) -> "Expression":
    """
    Generate a random number between low and high with triangular distribution.
    mid is the most probable number.

    Parameters
    ----------
    low : int | float
    mid : int | float
    high : int | float

    Returns
    -------
    Expression
    """
    return expression.Expression("randTriangle(", f"{low},{mid},{high}", ")")


def slrec(
    x: Union[int, float, "Symbol"], S: Union[int, float] = 1e-10
) -> "Expression":
    """
    Smooth (linear) reciprocal

    Parameters
    ----------
    x : int | float | Symbol
    S : int | float, by default 1e-10

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("slrec(", f"{x_str},{S}", ")")


def sqrec(
    x: Union[int, float, "Symbol"], S: Union[int, float] = 1e-10
) -> "Expression":
    """
    Smooth (quadratic) reciprocal

    Parameters
    ----------
    x : int | float | Symbol
    S : int | float, by default 1e-10

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("sqrec(", f"{x_str},{S}", ")")


def entropy(x: Union[int, float, "Symbol"]) -> "Expression":
    """
    L2 Norm of x

    Parameters
    ----------
    x : int | float | Symbol

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("entropy(", x_str, ")")


def errorf(x: Union[int, float, "Symbol"]) -> "Expression":
    """
    Integral of the standard normal distribution

    Parameters
    ----------
    x : int, float, Symbol

    Returns
    -------
    Expression
    """
    x_str = _stringify(x)
    return expression.Expression("errorf(", x_str, ")")


def ifthen(
    condition: "Expression",
    yes_return: Union[float, "Expression"],
    no_return: Union[float, "Expression"],
) -> "Expression":
    """
    If the logical condition is true, the function returns iftrue,
    else it returns else

    Parameters
    ----------
    condition : Expression
    yes_return : float | Expression
    no_return : float | Expression

    Returns
    -------
    Expression

    Examples
    --------
    >>> from gamspy.math import ifthen
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> tt = gp.Parameter(m, "tt", records=2)
    >>> y = gp.Parameter(m, "y", records=2)
    >>> x = ifthen(tt == 2, 3, 4 + y)

    """
    condition_str = condition.gamsRepr()
    condition_str = utils._replaceEqualitySigns(condition_str)

    ifthen_str = f"ifthen({condition_str}, {yes_return}, {no_return})"
    return expression.Expression(ifthen_str, "", "")

src/gamspy/math/misc.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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.
#
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
from gamspy.math.misc import _stringify

if TYPE_CHECKING:
    from gamspy._algebra.expression import Expression
    from gamspy._symbols.symbol import Symbol


def binomial(
    n: Union[int, float, "Symbol"], k: Union[int, float, "Symbol"]
) -> "Expression":
    """
    (Generalized) Binomial coefficient for n > -1, -1 < k < n + 1

    Parameters
    ----------
    n : int | float | Symbol
    k : int | float | Symbol

    Returns
    -------
    Expression
    """
    if isinstance(n, (int, float)) and isinstance(k, (int, float)):
        return expression.Expression("binomial(", f"{n},{k}", ")")

    n_string = _stringify(n)
    k_string = _stringify(k)

    return expression.Expression("binomial(", f"{n_string},{k_string}", ")")


def centropy(
    x: "Symbol",
    y: "Symbol",
    z: float = 1e-20,
) -> "Expression":
    """
    Cross-entropy. x . ln((x + z) / (y + z)

    Parameters
    ----------
    x : float | Operable
    y : float | Operable
    z : float, optional

    Returns
    -------
    Expression

    Raises
    ------
    ValueError
        if z is smaller than 0
    """
    if z < 0:
        raise ValueError("z must be greater than or equal to 0")

    return expression.Expression(
        "centropy(", ",".join([x.gamsRepr(), y.gamsRepr(), str(z)]), ")"
    )


def uniform(lower_bound: float, upper_bound: float) -> "Expression":
    """
    Generates a random number from the uniform distribution between
    lower_bound and higher_bound

    Parameters
    ----------
    lower_bound : float
    upper_bound : float

    Returns
    -------
    Expression
    """
    return expression.Expression(
        "uniform(", f"{lower_bound},{upper_bound}", ")"
    )


def uniformInt(
    lower_bound: Union[int, float], upper_bound: Union[int, float]
) -> "Expression":
    """
    Generates an integer random number from the discrete uniform distribution
    whose outcomes are the integers between lower_bound and higher_bound.

    Parameters
    ----------
    lower_bound : int | float
    upper_bound : int | float
    Returns
    -------
    Expression
    """
    return expression.Expression(
        "uniformInt(", f"{lower_bound},{upper_bound}", ")"
    )


def normal(mean: Union[int, float], dev: Union[int, float]) -> "Expression":
    """
    Generate a random number from the normal distribution with mean `mean`
    and `standard deviation` dev.

    Parameters
    ----------
    mean : int | float
    dev : int | float

    Returns
    -------
    Expression
    """
    return expression.Expression("normal(", f"{mean},{dev}", ")")

src/gamspy/math/probability.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 math
from typing import TYPE_CHECKING
from typing import Union

import gamspy._algebra.expression as expression
from gamspy.math.misc import _stringify

if TYPE_CHECKING:
    from gamspy._algebra.expression import Expression
    from gamspy._symbols.symbol import Symbol


def cos(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Cosine of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.cos(x)
    return expression.Expression("cos(", x.gamsRepr(), ")")


def cosh(x: Union[int, float, "Symbol"]) -> Union["Expression", float]:
    """
    Hyperbolic cosine of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.cosh(x)
    return expression.Expression("cosh(", x.gamsRepr(), ")")


def sin(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Sine of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.sin(x)
    return expression.Expression("sin(", x.gamsRepr(), ")")


def sinh(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Hyperbolic sine of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.sinh(x)
    return expression.Expression("sinh(", x.gamsRepr(), ")")


def tan(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Tangent of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.tan(x)
    return expression.Expression("tan(", x.gamsRepr(), ")")


def tanh(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Hyperbolic tangent of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.tanh(x)
    return expression.Expression("tanh(", x.gamsRepr(), ")")


def acos(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Inverse cosine of x.

    Returns
    -------
    Expresion | float
    """
    if isinstance(x, (int, float)):
        return math.acos(x)
    return expression.Expression("arccos(", x.gamsRepr(), ")")


def asin(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Inver sinus of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.asin(x)
    return expression.Expression("arcsin(", x.gamsRepr(), ")")


def atan(x: Union[float, "Symbol"]) -> Union["Expression", float]:
    """
    Inverse tangent of x.

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)):
        return math.atan(x)
    return expression.Expression("arctan(", x.gamsRepr(), ")")


def atan2(
    y: Union[int, float, "Symbol"], x: Union[int, float, "Symbol"]
) -> Union["Expression", float]:
    """
    Four-quadrant arctan function

    Returns
    -------
    Expression | float
    """
    if isinstance(x, (int, float)) and isinstance(y, (int, float)):
        return math.atan2(y, x)

    x_str = _stringify(x)
    y_str = _stringify(y)

    return expression.Expression("arctan2(", f"{y_str}, {x_str}", ")")

src/gamspy/math/trigonometric.py
GAMS-dev/gamspy
#
# GAMS - General Algebraic Modeling System Python API
#
# Copyright (c) 2023 GAMS Development Corp. <support@gams.com>
# Copyright (c) 2023 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 os
import platform
from collections.abc import Sequence
from typing import Iterable
from typing import List
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

import gams.transfer as gt
import gamspy_base
from gams.core import gdx
from gams.transfer._internals.specialvalues import SpecialValues

import gamspy
import gamspy._symbols.implicits as implicits
from gamspy.exceptions import GamspyException

if TYPE_CHECKING:
    from gamspy._symbols.implicits import ImplicitSet
    from gamspy import Alias, Set
    from gamspy import Domain
    from gamspy._algebra.expression import Expression


def getAvailableSolvers() -> List[str]:
    try:
        import gamspy_base
    except Exception:
        raise GamspyException("gamspy_base must be installed!")

    solver_names = []
    capabilities_file = {"Windows": "gmscmpNT.txt", "rest": "gmscmpun.txt"}
    user_platform = "Windows" if platform.system() == "Windows" else "rest"

    with open(
        gamspy_base.directory + os.sep + capabilities_file[user_platform]
    ) as capabilities:
        lines = capabilities.readlines()
        lines = [line for line in lines if line != "\n" and line[0] != "*"]

        for line in lines:
            if line == "DEFAULTS\n":
                break

            if line.isupper():
                solver_names.append(line.split(" ")[0])

    return solver_names


def _loadPackageGlobals() -> None:  # pragma: no cover
    gamspy._order = 0  # type: ignore


def _getUniqueName() -> str:
    """
    Generates a unique name for elements with no name (e.g. Expressions).

    Returns
    -------
    str
        Unique name in string format
    """
    gamspy._order += 1  # type: ignore
    return str(gamspy._order)  # type: ignore


def _getGAMSPyBaseDirectory() -> str:
    """
    Returns the gamspy_base directory.

    Returns
    -------
    str
        System directory
    """
    gamspy_base_directory = gamspy_base.__path__[0]
    return gamspy_base_directory


def _reservedCheck(word: str) -> str:
    reserved_words = [
        "abort",
        "acronym",
        "acronyms",
        "alias",
        "all",
        "and",
        "binary",
        "break",
        "card",
        "continue",
        "diag",
        "display",
        "do",
        "else",
        "elseif",
        "endfor",
        "endif",
        "endloop",
        "endwhile",
        "eps",
        "equation",
        "equations",
        "execute",
        "execute_load",
        "execute_loaddc",
        "execute_loadhandle",
        "execute_loadpoint",
        "execute_unload",
        "execute_unloaddi",
        "execute_unloadidx",
        "file",
        "files",
        "for",
        "free",
        "function",
        "functions",
        "gdxLoad",
        "if",
        "inf",
        "integer",
        "logic",
        "loop",
        "model",
        "models",
        "na",
        "negative",
        "nonnegative",
        "no",
        "not",
        "option",
        "options",
        "or",
        "ord",
        "parameter",
        "parameters",
        "positive",
        "prod",
        "put",
        "put_utility",
        "putclear",
        "putclose",
        "putfmcl",
        "puthd",
        "putheader",
        "putpage",
        "puttitle",
        "puttl",
        "repeat",
        "sameas",
        "sand",
        "scalar",
        "scalars",
        "semicont",
        "semiint",
        "set",
        "sets",
        "singleton",
        "smax",
        "smin",
        "solve",
        "sor",
        "sos1",
        "sos2",
        "sum",
        "system",
        "table",
        "tables",
        "then",
        "undf",
        "until",
        "variable",
        "variables",
        "while",
        "xor",
        "yes",
    ]

    if word.lower() in reserved_words:
        raise GamspyException(
            "Name cannot be one of the reserved words. List of reserved"
            f" words: {reserved_words}"
        )

    return word


def _closeGdxHandle(handle):
    """
    Closes the handle and unloads the gdx library.

    Parameters
    ----------
    handle : gdxHandle
    """
    gdx.gdxClose(handle)
    gdx.gdxFree(handle)
    gdx.gdxLibraryUnload()


def _replaceEqualitySigns(condition: str) -> str:
    condition = condition.replace("=l=", "<=")
    condition = condition.replace("=e=", "=")
    condition = condition.replace("=g=", ">=")
    return condition


def _set_special_values(gdxHandle):
    """
    Sets the special values

    Parameters
    ----------
    gdxHandle : gdxHandle

    Returns
    -------
    int
    """
    specVals = gdx.doubleArray(gdx.GMS_SVIDX_MAX)
    specVals[gdx.GMS_SVIDX_UNDEF] = SpecialValues.UNDEF
    specVals[gdx.GMS_SVIDX_NA] = SpecialValues.NA
    specVals[gdx.GMS_SVIDX_EPS] = SpecialValues.EPS
    specVals[gdx.GMS_SVIDX_PINF] = SpecialValues.POSINF
    specVals[gdx.GMS_SVIDX_MINF] = SpecialValues.NEGINF

    rc = gdx.gdxSetSpecialValues(gdxHandle, specVals)
    return rc


def _openGdxFile(system_directory: str, load_from: str):
    """
    Opens the gdx file with given path

    Parameters
    ----------
    system_directory : str
    load_from : str

    Returns
    -------
    gdxHandle

    Raises
    ------
    Exception
        Exception while creating the handle or setting the special values
    """
    try:
        gdxHandle = gdx.new_gdxHandle_tp()
        rc = gdx.gdxCreateD(gdxHandle, system_directory, gdx.GMS_SSSIZE)
        assert rc[0], rc[1]
    except AssertionError:
        raise GamspyException("GAMSPy could not create gdx handle.")

    try:
        rc = gdx.gdxOpenRead(gdxHandle, load_from)
        assert rc[0]

        rc = _set_special_values(gdxHandle)
        assert rc
    except AssertionError:
        raise GamspyException("GAMSPy could not open gdx file to read from.")

    return gdxHandle


def _toList(
    obj: Union[
        "Set",
        "Alias",
        str,
        Tuple,
        "Domain",
        "Expression",
        list,
        "ImplicitSet",
    ]
) -> list:
    """
    Converts the given object to a list

    Parameters
    ----------
    obj : Set | Alias | str | tuple | list | Domain | Expression | ImplicitSet
        Object to be converted

    Returns
    -------
    list
    """
    if isinstance(obj, tuple):
        return list(obj)

    if not isinstance(obj, list):
        obj = [obj]
    return obj


def isin(symbol, sequence: Sequence) -> bool:
    """
    Checks whether the given symbol in the sequence.
    Needed for symbol comparison since __eq__ magic
    is overloaded by the symbols.

    Parameters
    ----------
    symbol : Symbol
        _Symbol to check
    sequence : Sequence
        Sequence that holds a sequence of symbols

    Returns
    -------
    bool

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i")
    >>> j = gp.Set(m, "j")
    >>> k = gp.Set(m, "k")
    >>> sets = [i, j]
    >>> gp.utils.isin(i, sets)
    True
    >>> gp.utils.isin(k, sets)
    False

    """
    for item in sequence:
        if symbol is item:
            return True
    return False


def checkAllSame(iterable1: Sequence, iterable2: Sequence) -> bool:
    """
    Checks if all elements of a sequence are equal to the all
    elements of another sequence.

    Parameters
    ----------
    iterable1 : Sequence
    iterable2 : Sequence

    Returns
    -------
    bool

    Examples
    --------
    >>> import gamspy as gp
    >>> m = gp.Container()
    >>> i = gp.Set(m, "i")
    >>> j = gp.Set(m, "j")
    >>> k = gp.Set(m, "k")
    >>> list1 = [i, j]
    >>> list2 = [i, j]
    >>> gp.utils.checkAllSame(list1, list2)
    True
    >>> list3 = [i, j, k]
    >>> gp.utils.checkAllSame(list1, list3)
    False

    """
    if len(iterable1) != len(iterable2):
        return False

    all_same = True
    for elem1, elem2 in zip(iterable1, iterable2):
        if elem1 is not elem2:
            return False
    return all_same


def _getDomainStr(
    domain: Iterable[Union["Set", "Alias", "ImplicitSet", str]]
) -> str:
    """
    Creates the string format of a given domain

    Parameters
    ----------
    domain : Set | Alias | ImplicitSet | str

    Returns
    -------
    str

    Raises
    ------
    Exception
        Given domain must contain only sets, aliases or strings
    """
    from gamspy._algebra.domain import DomainException

    set_strs = []
    for set in domain:
        if isinstance(set, (gt.Set, gt.Alias, implicits.ImplicitSet)):
            set_strs.append(set.gamsRepr())
        elif isinstance(set, str):
            if set == "*":
                set_strs.append(set)
            else:
                set_strs.append('"' + set + '"')
        else:
            raise DomainException(
                f"Domain type must be str, Set or Alias but found {type(set)}"
            )

    return "(" + ",".join(set_strs) + ")"


def _getMatchingParanthesisIndices(string: str) -> dict:
    """
    Stack based paranthesis matcher.

    Parameters
    ----------
    string : str

    Returns
    -------
    dict

    Raises
    ------
    Exception
        In case there are more closing paranthesis than opening parantheses
    Exception
        In case there are more opening paranthesis than closing parantheses
    """
    stack = []  # stack of indices of opening parentheses
    matching_indices = {}

    for index, character in enumerate(string):
        if character == "(":
            stack.append(index)
        if character == ")":
            try:
                matching_indices[stack.pop()] = index
            except IndexError:
                raise AssertionError("Too many closing parentheses!")

    if stack:
        raise AssertionError("Too many opening parentheses!")

    return matching_indices


def _getValidOptionStatements() -> List[str]:
    # https://www.gams.com/latest/docs/UG_OptionStatement.html#UG_OptionStatement_ListOfOptions
    def lowercase(options):
        return [option.lower() for option in options]

    output_details = [
        "asyncSolLst",
        "decimals",
        "dispWidth",
        "eject",
        "epsToZero",
        "limCol",
        "limRow",
        "maxGenericFiles",
        "MCPRHoldFx",
        "profile",
        "profileTol",
        "solPrint",
        "solSlack",
        "sysOut",
    ]

    solver_specific = [
        "bRatio",
        "domLim",
        "holdFixedAsync",
        "intVarUp",
        "iterLim",
        "optCA",
        "optCR",
        "reform",
        "resLim",
        "savePoint",
        "solveLink",
        "sys12",
        "threads",
    ]

    choice_of_solver = [
        "CNS",
        "DNLP",
        "EMP",
        "LP",
        "MCP",
        "MINLP",
        "MIP",
        "MIQCP",
        "MPEC",
        "NLP",
        "QCP",
        "RMINLP",
        "RMIP",
        "RMIQCP",
        "RMPEC",
        "solver",
    ]

    input_control = [
        "ECImplicitLoad",
        "fdDelta",
        "fdOpt",
        "gdxUels",
        "seed",
        "solveOpt",
        "strictSingleton",
        "sys18",
        "zeroToEps",
    ]

    others = [
        "checkErrorLevel",
        "dmpOpt",
        "dmpSym",
        "dmpUserSym",
        "dualCheck",
        "forLim",
        "integer1..5",
        "measure",
        "memoryStat",
        "real1..5",
        "subSystems",
        "sys10",
        "sys11",
        "sys15",
        "sys16",
        "sys17",
        "sys19",
        "threadsAsync",
    ]

    return (
        lowercase(output_details)
        + lowercase(solver_specific)
        + lowercase(choice_of_solver)
        + lowercase(input_control)
        + lowercase(others)
    )


VALID_OPTION_STATEMENTS = _getValidOptionStatements()


def _getValidGamsOptions() -> List[str]:
    return [
        "action",
        "all_model_types",
        "appendexpand",
        "appendout",
        "asyncsollst",
        "bratio",
        "capturemodelinstance",
        "case",
        "cerr",
        "charset",
        "checkerrorlevel",
        "cns",
        "decryptkey",
        "defines",
        "dformat",
        "digit",
        "dnlp",
        "domlim",
        "dumpopt",
        "dumpoptgdx",
        "dumpparms",
        "dumpparmslogprefix",
        "ecimplicitload",
        "emp",
        "empty",
        "encryptkey",
        "eolcom",
        "errmsg",
        "errorlog",
        "etlim",
        "execmode",
        "expand",
        "export",
        "fddelta",
        "fdopt",
        "ferr",
        "filecase",
        "filestem",
        "filestemapfromenv",
        "filtered",
        "forceoptfile",
        "forcework",
        "forlim",
        "freeembeddedpython",
        "gdx",
        "gdxcompress",
        "gdxconvert",
        "gdxuels",
        "griddir",
        "gridscript",
        "heaplimit",
        "holdfixed",
        "holdfixedasync",
        "idcgdxinput",
        "idcgdxoutput",
        "idir",
        "implicitassign",
        "inlinecom",
        "integer1",
        "integer2",
        "integer3",
        "integer4",
        "integer5",
        "interactivesolver",
        "intvarup",
        "iterlim",
        "jobtrace",
        "keep",
        "libincdir",
        "license",
        "limcol",
        "limrow",
        "listing",
        "logline",
        "lp",
        "lsttitleleftaligned",
        "maxexecerror",
        "maxprocdir",
        "mcp",
        "miimode",
        "minlp",
        "mip",
        "miqcp",
        "mpec",
        "multi",
        "nlp",
        "nodlim",
        "nonewvarequ",
        "on115",
        "optLock",
        "optca",
        "optcr",
        "optdir",
        "optfile",
        "output",
        "pagecontr",
        "pagesize",
        "pagewidth",
        "plicense",
        "prefixloadpath",
        "previouswork",
        "proctreememmonitor",
        "proctreememticks",
        "profile",
        "profilefile",
        "profiletol",
        "putdir",
        "putnd",
        "putnr",
        "putps",
        "putpw",
        "pymultinst",
        "qcp",
        "reference",
        "referencelineno",
        "replace",
        "reslim",
        "rminlp",
        "rmip",
        "rmiqcp",
        "rmpec",
        "savepoint",
        "scriptexit",
        "seed",
        "showosmemory",
        "solprint",
        "solvelink",
        "solveopt",
        "stepsum",
        "strictsingleton",
        "stringchk",
        "suffixalgebravars",
        "suffixdlvars",
        "suppress",
        "symbol",
        "symprefix",
        "sys10",
        "sys11",
        "sys12",
        "sysincdir",
        "sysout",
        "tabin",
        "tformat",
        "threads",
        "threadsasync",
        "timer",
        "trace",
        "tracelevel",
        "traceopt",
        "user1",
        "user2",
        "user3",
        "user4",
        "user5",
        "warnings",
        "workfactor",
        "workspace",
        "zerores",
        "zeroresrep",
    ]


VALID_GAMS_OPTIONS = _getValidGamsOptions()


def _getDefaultSolvers():
    return {
        "LP": "CPLEX",
        "MIP": "CPLEX",
        "RMIP": "CPLEX",
        "NLP": "CONOPT",
        "MCP": "PATH",
        "MPEC": "NLPEC",
        "CNS": "CONOPT",
        "DNLP": "CONOPT",
        "RMINLP": "CONOPT",
        "MINLP": "SBB",
        "QCP": "CONOPT",
        "MIQCP": "SBB",
        "RMIQCP": "CONOPT",
        "EMP": "CONVERT",
    }


DEFAULT_SOLVERS = _getDefaultSolvers()

src/gamspy/utils.py
GAMS-dev/gamspy
# Starting from Python 3.8, importlib has a metadata submodule
from importlib import metadata

__version__ = metadata.version("gamspy")

src/gamspy/version.py
GAMS-dev/gamspy
"""
Tracking international bond index - GDX input

* BondIndexGDX.gms: Tracking international bond index - GDX input.
* Consiglio, Nielsen and Zenios.
* PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 8.2
* Last modified: Apr 2008.
"""
from pathlib import Path

import numpy as np

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import ModelStatus
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)
    m.read(
        load_from=str(Path(__file__).parent.absolute()) + "/BondIndex.gdx",
        symbol_names=[
            "Bonds",
            "Currencies",
            "Scenarios",
            "j",
            "i",
            "l",
            "SS",
            "JxI",
            "ExchangeRates0",
            "ExchangeRates1",
            "Price0",
            "Price1",
            "InitialHoldings",
            "Accruals0",
            "Accruals1",
            "Outstanding",
            "ReinvestmentRate",
            "IndexReturns",
            "pr",
        ],
    )

    # SETS #
    j, i, l = m.getSymbols(["j", "i", "l"])
    JxI = m.getSymbols(["JxI"])[0]

    i_recs = i.records.element_text.tolist()

    # PARAMETERS #
    (
        ExchangeRates0,
        ExchangeRates1,
        Price0,
        Price1,
        InitialHoldings,
        Accruals0,
        Accruals1,
        Outstanding,
        ReinvestmentRate,
        IndexReturns,
        pr,
    ) = m.getSymbols(
        [
            "ExchangeRates0",
            "ExchangeRates1",
            "Price0",
            "Price1",
            "InitialHoldings",
            "Accruals0",
            "Accruals1",
            "Outstanding",
            "ReinvestmentRate",
            "IndexReturns",
            "pr",
        ]
    )

    # SCALARS #
    TrnCstB = Parameter(
        m,
        name="TrnCstB",
        records=0.0025,
        description="Buying transaction costs",
    )
    TrnCstS = Parameter(
        m,
        name="TrnCstS",
        records=0.0015,
        description="Selling transaction costs",
    )
    CashInfusion = Parameter(
        m,
        name="CashInfusion",
        records=100000,
        description="Available budget infused",
    )
    EpsTolerance = Parameter(
        m, name="EpsTolerance", records=0.10, description="Tolerance"
    )
    UpprBnd = Parameter(
        m,
        name="UpprBnd",
        records=0.1,
        description="Maximum holding percentage for each bond",
    )
    CHFtrade = Parameter(
        m,
        name="CHFtrade",
        records=15000000,
        description="Maximum trading value allowed for Swiss bonds (in CHF)",
    )
    HoldVal = Parameter(
        m, name="HoldVal", description="Value of the initial holdings"
    )
    InitAccrCash = Parameter(
        m,
        name="InitAccrCash",
        description="Accrued cash originated by the initial holdings",
    )
    InitVal = Parameter(
        m, name="InitVal", description="Initial portfolio value"
    )

    # Calculate initial portfolio value
    HoldVal.assignment = Sum(
        JxI[j, i], ExchangeRates0[j] * InitialHoldings[i] * Price0[i]
    )
    InitAccrCash.assignment = Sum(
        JxI[j, i], ExchangeRates0[j] * InitialHoldings[i] * Accruals0[i]
    )
    InitVal.assignment = CashInfusion + InitAccrCash + HoldVal

    # VARIABLES #
    X0 = Variable(
        m,
        name="X0",
        type="positive",
        domain=["*"],
        description="Face value bought today.",
    )
    Y0 = Variable(
        m,
        name="Y0",
        type="positive",
        domain=["*"],
        description="Face value sold today.",
    )
    Z0 = Variable(
        m,
        name="Z0",
        type="positive",
        domain=["*"],
        description="Face value hold today for the next period.",
    )
    Cash = Variable(
        m,
        name="Cash",
        type="positive",
        description=(
            "Amount of cash resulting from trading (sell and buy) today."
        ),
    )
    FinalCash = Variable(
        m,
        name="FinalCash",
        type="positive",
        domain=[l],
        description="Amount of cash resulting from portfolio liquidation",
    )
    z = Variable(m, name="z", description="Objective function value")

    # Set the upper bound on the holdings
    for jj, ii, _ in JxI.records.itertuples(index=False):
        Z0.up[ii] = InitVal / ExchangeRates0[jj] / Price0[ii] * UpprBnd

    # Set the limit on trading (sell or buy)
    # CHF bonds for liquidity reasons
    X0.up[i].where[JxI["CHF", i]] = CHFtrade / Price0[i]
    Y0.up[i].where[JxI["CHF", i]] = CHFtrade / Price0[i]

    # EQUATIONS #
    ObjDef = Equation(
        m,
        name="ObjDef",
        type="regular",
        description="Objective function definition (Expected return)",
    )
    CashInventoryCon = Equation(
        m,
        name="CashInventoryCon",
        type="regular",
        description="Cash balance equation today.",
    )
    FinalCashCon = Equation(
        m,
        name="FinalCashCon",
        type="regular",
        domain=[l],
        description="Cash balance equations at the end of first stage.",
    )
    InventoryCon = Equation(
        m,
        name="InventoryCon",
        type="regular",
        domain=[i],
        description="Constraints defining the asset inventory balance",
    )
    MADCon = Equation(
        m,
        name="MADCon",
        type="regular",
        domain=[l],
        description="MAD constraints",
    )

    ObjDef.definition = z == 1000 * Sum(
        l, pr[l] * (FinalCash[l] / InitVal - 1)
    )

    CashInventoryCon.definition = (
        CashInfusion
        + Sum(JxI[j, i], ExchangeRates0[j] * Y0[i] * Price0[i] * (1 - TrnCstS))
        == Sum(
            JxI[j, i], ExchangeRates0[j] * X0[i] * Price0[i] * (1 + TrnCstB)
        )
        + Cash
    )

    FinalCashCon[l] = (
        Sum(JxI[j, i], ExchangeRates1[j, l] * Accruals1[i, l] * Z0[i])
        + ReinvestmentRate[l] * Cash
        + Sum(
            JxI[j, i],
            ExchangeRates1[j, l]
            * Z0[i]
            * Outstanding[i, l]
            * Price1[i, l]
            * (1 - TrnCstS),
        )
        == FinalCash[l]
    )

    InventoryCon[i] = InitialHoldings[i] + X0[i] == Y0[i] + Z0[i]

    MADCon[l] = (FinalCash[l] / InitVal - 1) - IndexReturns[l] >= -EpsTolerance

    BondIndex = Model(
        m,
        name="BondIndex",
        equations=m.getEquations(),
        problem="LP",
        sense="MAX",
        objective=z,
    )
    m.addOptions(
        {"LIMROW": 0, "LIMCOL": 0, "SOLPRINT": "off"}
    )  # 'Turn off row and colum listing'

    # Find a feasible EpsTolerance
    low = Parameter(m, name="low", description="Lower bisection value")
    high = Parameter(m, name="high", description="Upper bisection value")

    high.assignment = -np.inf
    low.assignment = 0
    EpsTolerance.assignment = 0.01

    while high.records.value[0] <= 0:
        BondIndex.solve()
        if BondIndex.status in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            high.assignment = EpsTolerance
        else:
            EpsTolerance.assignment = 2 * EpsTolerance

    # Find a small feasible EpsTolerance via bisection
    while True:
        EpsTolerance.assignment = (
            low.records.value[0] + high.records.value[0]
        ) / 2
        BondIndex.solve()
        if BondIndex.status in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            high.assignment = EpsTolerance
        else:
            low.assignment = EpsTolerance

        if (
            (high.records.value[0] - low.records.value[0]) < 0.005
        ) and BondIndex.status in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            break

    CurrentValue = Parameter(
        m, name="CurrentValue", domain=[i], description="Holdings in USD"
    )
    CurrentValue[i] = Sum(JxI[j, i], ExchangeRates0[j]) * Price0[i] * Z0.l[i]

    ColHeaders = Set(
        m,
        name="ColHeaders",
        records=["FaceValue", "USDValue", "Percent"],
        description="Column headers",
    )

    SummaryReport = Parameter(
        m, name="SummaryReport", domain=["*", ColHeaders]
    )

    SummaryReport[i, "FaceValue"] = Z0.l[i]
    SummaryReport[i, "USDValue"] = CurrentValue[i]
    SummaryReport[i, "Percent"] = CurrentValue[i] / InitVal * 100
    SummaryReport["Total", ColHeaders] = Sum(i, SummaryReport[i, ColHeaders])

    print("Summary Report: \n", SummaryReport.pivot().round(3))
    print("\nEpsTolerance: \n", round(EpsTolerance.records.value[0], 3))
    print("\nObjective Function Value: \n", round(z.records.level[0], 3))
    print("\nInitVal: \n", round(InitVal.records.value[0], 3))
    print(CurrentValue.records)

    ResultHandle = open("BondIndex.csv", "w", encoding="UTF-8")
    ResultHandle.write(
        f'"Objective Function", {round(z.records.level[0],3)}\n'
    )
    ResultHandle.write(
        f'"Final Epsilon", {round(EpsTolerance.records.value[0],3)}\n'
    )
    ResultHandle.write(
        '"Initial Portfolio Value in USD",'
        f" {round(InitVal.records.value[0],3)}\n"
    )
    ResultHandle.write("\n")
    ResultHandle.write(
        '"Bond number","CUSIP code","Holdings in unit of face value","Holdings'
        ' in USD","Percentage of the portfolio value"\n'
    )
    for ii in Z0.records.itertuples():
        if ii.level == 0:
            continue
        cv_value = CurrentValue.records.loc[
            CurrentValue.records["i"] == ii.uni, "value"
        ].values[0]
        ResultHandle.write(
            f'"{ii.uni}","{i_recs[ii.Index]}",{round(ii.level,3)},{round(cv_value,2)},{round(100*(cv_value/InitVal.records.value[0]),2)}\n'
        )

    ResultHandle.write(
        '"Cash in US'
        f' dollar",{Cash.records.level[0]},{((Cash.records.level[0]/InitVal.records.value[0])*100)}'
    )
    ResultHandle.close()

    import math

    assert math.isclose(BondIndex.objective_value, 23.206448, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/BondIndex.py
GAMS-dev/gamspy
"""
*** Simple linear programming model for determination of boundary values of an
objective function
For more details please refer to Chapter 2 (Gcode2.2), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: LP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    of = Variable(m, name="of")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")
    eq4 = Equation(m, name="eq4", type="regular")

    eq1.definition = x1 + 2 * x2 <= 3
    eq2.definition = x2 + x3 <= 2
    eq3.definition = x1 + x2 + x3 == 4
    eq4.definition = x1 + 2 * x2 - 3 * x3 == of

    LP1 = Model(
        m,
        name="LP1",
        equations=m.getEquations(),
        problem="lp",
        sense="max",
        objective=of,
    )

    x1.lo.assignment = 0
    x1.up.assignment = 5
    x2.lo.assignment = 0
    x2.up.assignment = 3
    x3.lo.assignment = 0
    x3.up.assignment = 2

    LP1.solve()

    print(" * Model LP1 *")
    print("x1:  ", round(x1.toValue(), 3))
    print("x2:  ", round(x2.toValue(), 3))
    print("x3:  ", round(x3.toValue(), 3))
    print("of:  ", round(of.toValue(), 3))

    LP2 = Model(
        m,
        name="LP2",
        equations=m.getEquations(),
        problem="lp",
        sense="min",
        objective=of,
    )
    LP2.solve()
    print("\n * Model LP2 *")
    print("x1:  ", round(x1.toValue(), 3))
    print("x2:  ", round(x2.toValue(), 3))
    print("x3:  ", round(x3.toValue(), 3))
    print("of:  ", round(of.toValue(), 3))

    import math

    assert math.isclose(LP2.objective_value, -4.000000, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/BoundaryLP.py
GAMS-dev/gamspy
"""
Conditional Value at Risk models

CVaR.gms: Conditional Value at Risk models.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 5.5
Last modified: Apr 2008.
"""
from pathlib import Path

import numpy as np

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Smax
from gamspy import Smin
from gamspy import Sum
from gamspy import Variable


def index_data():
    np.array(
        [
            1.034769211,
            1.024362083,
            1.005721076,
            1.014359531,
            1.008024402,
            1.015164086,
            1.011776948,
            0.999367312,
            1.022337255,
            1.014084294,
            1.002478561,
            1.004830772,
            1.022048865,
            1.001584376,
            1.014789245,
            1.018465908,
            1.02023507,
            1.001142139,
            1.028357519,
            1.012274672,
            1.005320247,
            0.991451365,
            1.007344745,
            1.017165464,
            0.997931432,
            0.990041853,
            0.9933217,
            0.991912481,
            1.036980768,
            0.994451813,
            1.007412617,
            0.955150659,
            0.958840232,
            1.033624988,
            1.018079311,
            1.010736753,
            1.016700717,
            1.048121923,
            1.015146812,
            1.0074283,
            1.025778693,
            0.983151788,
            1.022476292,
            1.004850227,
            1.0091122,
            0.995361207,
            0.992432266,
            1.024212556,
            1.022181138,
            1.006772127,
            0.990354861,
            1.0067649,
            1.008943286,
            0.983974563,
            1.000347655,
            1.001230771,
            1.020880921,
            1.019415483,
            1.008638044,
            1.017173042,
            1.011831605,
            1.018260066,
            1.014054495,
            1.011841085,
            0.988155751,
            1.009224769,
            1.010321982,
            1.033008126,
            0.999626315,
            1.022733809,
            1.00083866,
            1.039925522,
            1.0144168,
            0.974402144,
            0.971108237,
            1.002198078,
            1.001221179,
            0.988342113,
            1.01750901,
            1.019968377,
            0.992895551,
            1.000905761,
            0.997333999,
            0.994982525,
            0.987972315,
            1.000443735,
            1.01583315,
            1.018338346,
            1.026183302,
            0.999209138,
            1.018809687,
            1.009775599,
            1.01722367,
            0.999752239,
            1.015986198,
            1.019400469,
            1.017624299,
            0.996067065,
            1.00947457,
            1.0164916,
            0.997929107,
            1.008892447,
            0.990498799,
            1.011843222,
            1.023338977,
            1.001536085,
            1.023978065,
            0.994238826,
            1.022819962,
            1.012142345,
            0.989862601,
            1.021050206,
            1.019165002,
            1.023260624,
            1.027413626,
            0.971346297,
            1.026087902,
            0.959990261,
            1.008484984,
            1.014365951,
            1.010347985,
            1.029266037,
            1.01989954,
            0.999383722,
            0.992191473,
            0.993782093,
            1.010730888,
            0.918149916,
            1.008729179,
            1.031641438,
            1.032632304,
            1.004115995,
            1.007398109,
            0.99859111,
            1.032604061,
            1.027883261,
            0.979268923,
            1.024656356,
            0.989556024,
            1.001926578,
            0.991713793,
            1.020697126,
            1.022268183,
            1.031273723,
            0.992301368,
            1.011547524,
            1.02121809,
            0.988701862,
            0.991540642,
            1.014546705,
            0.997009882,
            1.01168258,
            0.982543898,
            0.992197467,
        ]
    )


def main():
    gdx_file = str(Path(__file__).parent.absolute()) + "/WorldIndices.gdx"
    m = Container(delayed_execution=True, load_from=gdx_file)

    # SETS #
    i, l = m.getSymbols(["i", "l"])

    # SCALARS #
    Budget = Parameter(
        m, name="Budget", description="Nominal investment budget"
    )
    alpha = Parameter(m, name="alpha", description="Confidence level")
    MU_TARGET = Parameter(
        m, name="MU_TARGET", description="Target portfolio return"
    )
    MU_STEP = Parameter(m, name="MU_STEP", description="Target return step")
    MIN_MU = Parameter(
        m, name="MIN_MU", description="Minimum return in universe"
    )
    MAX_MU = Parameter(
        m, name="MAX_MU", description="Maximum return in universe"
    )
    RISK_TARGET = Parameter(
        m, name="RISK_TARGET", description="Bound on CVaR (risk)"
    )
    LossFlag = Parameter(
        m, name="LossFlag", description="Flag selecting the type of loss"
    )

    Budget.assignment = 100.0
    alpha.assignment = 0.99

    # PARAMETERS #
    pr = Parameter(
        m, name="pr", domain=[l], description="Scenario probability"
    )
    P = Parameter(m, name="P", domain=[i, l], description="Final values")
    EP = Parameter(
        m, name="EP", domain=[i], description="Expected final values"
    )
    AssetReturns = m.getSymbols(["AssetReturns"])[0]

    pr[l] = 1.0 / Card(l)

    P[i, l] = 1 + AssetReturns[i, l]

    EP[i] = Sum(l, pr[l] * P[i, l])

    MIN_MU.assignment = Smin(i, EP[i])
    MAX_MU.assignment = Smax(i, EP[i])

    # Assume we want 20 portfolios in the frontier

    MU_STEP.assignment = (MAX_MU - MIN_MU) / 20

    TargetIndex = Parameter(
        m, name="TargetIndex", domain=[l], description="Target index returns"
    )

    # To test the model with a market index, uncomment the following two lines.
    # Note that, this index can be used only with WorldIndexes.inc.
    # Index = Parameter(m, name="Index", domain=[l], records=index_data(), description="Index returns")
    # TargetIndex[l] = Index[l]

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Holdings of assets in monetary units (not proportions)",
    )
    VaRDev = Variable(
        m,
        name="VaRDev",
        type="positive",
        domain=[l],
        description="Measures of the deviations from the VaR",
    )
    VaR = Variable(m, name="VaR", description="Value-at-Risk")
    z = Variable(m, name="z", description="Objective function value")
    Losses = Variable(
        m, name="Losses", domain=[l], description="Measures of the losses"
    )

    # EQUATIONS #
    BudgetCon = Equation(
        m,
        name="BudgetCon",
        type="regular",
        description="Equation defining the budget contraint",
    )
    ReturnCon = Equation(
        m,
        name="ReturnCon",
        type="regular",
        description="Equation defining the portfolio return constraint",
    )
    CVaRCon = Equation(
        m,
        name="CVaRCon",
        type="regular",
        description="Equation defining the CVaR allowed",
    )
    ObjDefCVaR = Equation(
        m,
        name="ObjDefCVaR",
        type="regular",
        description="Objective function definition for CVaR minimization",
    )
    ObjDefReturn = Equation(
        m,
        name="ObjDefReturn",
        type="regular",
        description="Objective function definition for return mazimization",
    )
    LossDef = Equation(
        m,
        name="LossDef",
        type="regular",
        domain=[l],
        description="Equations defining the losses",
    )
    VaRDevCon = Equation(
        m,
        name="VaRDevCon",
        type="regular",
        domain=[l],
        description="Equations defining the VaR deviation constraints",
    )

    BudgetCon.definition = Sum(i, x[i]) == Budget

    ReturnCon.definition = Sum(i, EP[i] * x[i]) >= MU_TARGET * Budget

    CVaRCon.definition = (
        VaR + Sum(l, pr[l] * VaRDev[l]) / (1 - alpha) <= RISK_TARGET
    )

    VaRDevCon[l] = VaRDev[l] >= Losses[l] - VaR

    LossDef[l] = (
        Losses[l]
        == (Budget - Sum(i, P[i, l] * x[i])).where[LossFlag == Number(1)]
        + (TargetIndex[l] * Budget - Sum(i, P[i, l] * x[i])).where[
            LossFlag == Number(2)
        ]
        + (Sum(i, EP[i] * x[i]) - Sum(i, P[i, l] * x[i])).where[
            LossFlag == Number(3)
        ]
    )

    ObjDefCVaR.definition = z == VaR + Sum(l, pr[l] * VaRDev[l]) / (1 - alpha)

    ObjDefReturn.definition = z == Sum(i, EP[i] * x[i])

    MinCVaR = Model(
        m,
        name="MinCVaR",
        equations=[BudgetCon, ReturnCon, LossDef, VaRDevCon, ObjDefCVaR],
        problem="LP",
        sense="MIN",
        objective=z,
    )

    output_csv = '"Status","VaR","CVaR","Mean",'
    i_recs = [f'"{i_rec}"' for i_rec in i.records.uni.tolist()]
    output_csv += ",".join(i_recs) + "\n"

    LossFlag.assignment = 2

    # Comment the following line if you want to track the market index.
    TargetIndex[l] = 1.01

    mu_target = MIN_MU.records.value[0]
    while mu_target <= MAX_MU.records.value[0]:
        MU_TARGET.assignment = mu_target

        MinCVaR.solve()

        output_csv += (
            f"{str(MinCVaR.status).split('.')[-1]},{VaR.records.level.round(3)[0]},{z.records.level.round(3)[0]},{round(MU_TARGET.records.value[0] * Budget.records.value[0],3)},"
        )
        x_recs = [str(x_rec) for x_rec in x.records.level.round(2).tolist()]
        output_csv += ",".join(x_recs) + "\n"

        mu_target += MU_STEP.records.value[0]

    FrontierHandle = open("CVaRFrontiers_new.csv", "w", encoding="UTF-8")
    FrontierHandle.write(output_csv)
    FrontierHandle.close()


if __name__ == "__main__":
    main()

tests/integration/models/CVaR.py
GAMS-dev/gamspy
"""
Corporate bond indexation model

Corporate.gms: Corporate bond indexation model
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 8.3
Last modified: May 2008.
"""
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Sense
from gamspy import Sum
from gamspy import Variable


def main():
    # Define container
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/Corporate.gdx",
    )

    # SETS #
    BroadAssetClassOne, BroadAssetClassTwo, BroadAssetClassThree, ACTIVE = (
        m.getSymbols(
            [
                "BroadAssetClassOne",
                "BroadAssetClassTwo",
                "BroadAssetClassThree",
                "ACTIVE",
            ]
        )
    )

    # ALIASES
    i, l, j, m1, m2, m3, a = m.getSymbols(
        ["i", "l", "j", "m1", "m2", "m3", "a"]
    )

    # PARAMETERS #
    AssetReturns = m.getSymbols(["AssetReturns"])[0]
    BroadWeights = Parameter(
        m,
        name="BroadWeights",
        domain=[j],
        description="Weights of the broad asset classes",
    )
    AssetWeights = Parameter(
        m,
        name="AssetWeights",
        domain=[i],
        description="Weights of each asset in the index",
    )

    # Assign weights randomly

    AssetWeights[i] = gams_math.uniform(1, 10)

    # DISPLAY  AssetWeights

    # Normalize the random weights
    WeightsSum = Parameter(m, name="WeightsSum")

    WeightsSum.assignment = Sum(i, AssetWeights[i])

    AssetWeights[i] = AssetWeights[i] / WeightsSum

    BroadWeights["BA_1"] = Sum(m1, AssetWeights[m1])
    BroadWeights["BA_2"] = Sum(m2, AssetWeights[m2])
    BroadWeights["BA_3"] = Sum(m3, AssetWeights[m3])

    IndexReturns = Parameter(
        m,
        name="IndexReturns",
        domain=[l],
        description="Index return scenarios",
    )
    BroadAssetReturns = Parameter(
        m,
        name="BroadAssetReturns",
        domain=[j, l],
        description="Broad asset class return scenarios",
    )
    Benchmark = Parameter(
        m,
        name="Benchmark",
        domain=[l],
        description="Current benchmark scenario returns",
    )

    BroadAssetReturns["BA_1", l] = Sum(
        m1, AssetWeights[m1] * AssetReturns[m1, l]
    )
    BroadAssetReturns["BA_2", l] = Sum(
        m2, AssetWeights[m2] * AssetReturns[m2, l]
    )
    BroadAssetReturns["BA_3", l] = Sum(
        m3, AssetWeights[m3] * AssetReturns[m3, l]
    )

    IndexReturns[l] = Sum(j, BroadWeights[j] * BroadAssetReturns[j, l])

    CurrentWeight = Parameter(
        m,
        name="CurrentWeight",
        description="Current weight for tactcal allocation",
    )
    EpsTolerance = Parameter(m, name="EpsTolerance", description="Tolerance")
    pr = Parameter(
        m, name="pr", domain=[l], description="Scenario probability"
    )

    pr[l] = 1.0 / Card(l)

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Percentage invested in each security",
    )
    z = Variable(
        m,
        name="z",
        type="positive",
        domain=[j],
        description="Percentages invested in each broad asset class",
    )
    PortRet = Variable(
        m, name="PortRet", domain=[l], description="Portfolio returns"
    )
    ObjValue = Variable(
        m, name="ObjValue", description="Objective function value"
    )

    # EQUATIONS #
    ObjDef = Equation(
        m,
        name="ObjDef",
        description=(
            "Objective function for the strategic model (Expected return)"
        ),
    )
    BroadPortRetDef = Equation(
        m,
        name="BroadPortRetDef",
        domain=[l],
        description="Portfolio return definition for broad asset classes",
    )
    PortRetDef = Equation(
        m,
        name="PortRetDef",
        domain=[l],
        description="Portfolio return definition",
    )
    BroadNormalCon = Equation(
        m,
        name="BroadNormalCon",
        description=(
            "Equation defining the normalization contraint for broad asset"
            " classes"
        ),
    )
    NormalCon = Equation(
        m,
        name="NormalCon",
        description="Equation defining the normalization contraint",
    )
    MADCon = Equation(
        m,
        name="MADCon",
        domain=[l],
        description="MAD constraints",
    )

    ObjDef.definition = ObjValue == Sum(l, pr[l] * PortRet[l])

    BroadPortRetDef[l] = PortRet[l] == Sum(j, z[j] * BroadAssetReturns[j, l])

    PortRetDef[l] = PortRet[l] == Sum(a, x[a] * AssetReturns[a, l])

    MADCon[l] = PortRet[l] >= Benchmark[l] - EpsTolerance

    BroadNormalCon.definition = Sum(j, z[j]) == 1.0

    NormalCon.definition = Sum(a, x[a]) == CurrentWeight

    m.addOptions({"SOLVEOPT": "REPLACE"})

    # MODELS #
    StrategicModel = Model(
        m,
        name="StrategicModel",
        equations=[ObjDef, BroadPortRetDef, MADCon, BroadNormalCon],
        problem="LP",
        sense=Sense.MAX,
        objective=ObjValue,
    )
    TacticalModel = Model(
        m,
        name="TacticalModel",
        equations=[ObjDef, PortRetDef, MADCon, NormalCon],
        problem="LP",
        sense=Sense.MAX,
        objective=ObjValue,
    )

    # Solve strategic model

    Benchmark[l] = IndexReturns[l]

    EpsTolerance.assignment = 0.02

    StrategicModel.solve()

    print("## Strategic Asset Allocation ##")
    print("z: \n", z.records.level.to_list())

    # Solve tactical model for Broad Asset 1 (BA_1)

    CurrentWeight.assignment = z.l["BA_1"]

    Benchmark[l] = BroadAssetReturns["BA_1", l]

    EpsTolerance.assignment = 0.02

    ACTIVE[i] = BroadAssetClassOne[i]

    if CurrentWeight.records.value[0] > 0.05:
        TacticalModel.solve()

        print("\n## Model BA_1 ##")
        print("\nx: \n", x.records.level.tolist())

    # Solve tactical model for Broad Asset 2 (BA_2)

    CurrentWeight.assignment = z.l["BA_2"]

    ACTIVE[i] = BroadAssetClassTwo[i]

    Benchmark[l] = BroadAssetReturns["BA_2", l]

    EpsTolerance.assignment = 0.03

    if CurrentWeight.records.value[0] > 0.05:
        TacticalModel.solve()

        print("\n## Model BA_2 ##")
        print("\nx: \n", x.records.level.tolist())

    # Solve tactical model for Broad Asset 3 (BA_3)

    CurrentWeight.assignment = z.l["BA_3"]

    ACTIVE[i] = BroadAssetClassThree[i]

    Benchmark[l] = BroadAssetReturns["BA_3", l]

    EpsTolerance.assignment = 0.02

    if CurrentWeight.records.value[0] > 0.05:
        TacticalModel.solve()

        print("\n## Model BA_3 ##")
        print("\nx: \n", x.records.level.tolist())

    # Solve integrated model

    CurrentWeight.assignment = 1.0

    Benchmark[l] = IndexReturns[l]

    EpsTolerance.assignment = 0.02

    ACTIVE[i] = Number(1)

    TacticalModel.solve()
    print("\n## Model Integrated ##")
    print("x: \n", x.records.level.tolist())

    print("\nObjective Function Value: ", round(ObjValue.records.level[0], 3))


if __name__ == "__main__":
    main()

tests/integration/models/Corporate.py
GAMS-dev/gamspy
"""
*** Price based Dynamic Economic Load Dispatch

For more details please refer to Chapter 4 (Gcode4.5), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # gendata records table
    cols = ["a", "b", "c", "d", "e", "f", "Pmin", "Pmax", "RU0", "RD0"]
    inds = ["p1", "p2", "p3", "p4"]
    data = [
        [0.12, 14.80, 89, 1.2, -5, 3, 28, 200, 40, 40],
        [0.17, 16.57, 83, 2.3, -4.24, 6.09, 20, 290, 30, 30],
        [0.15, 15.55, 100, 1.1, -2.15, 5.69, 30, 190, 30, 30],
        [0.19, 16.21, 70, 1.1, -3.99, 6.2, 20, 260, 50, 50],
    ]
    gen_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # data records table
    cols = ["lamda", "load"]
    inds = [f"t{i}" for i in range(1, 25)]
    data = [
        [32.71, 510],
        [34.72, 530],
        [32.71, 516],
        [32.74, 510],
        [32.96, 515],
        [34.93, 544],
        [44.9, 646],
        [52.0, 686],
        [53.03, 741],
        [47.26, 734],
        [44.07, 748],
        [38.63, 760],
        [39.91, 754],
        [39.45, 700],
        [41.14, 686],
        [39.23, 720],
        [52.12, 714],
        [40.85, 761],
        [41.2, 727],
        [41.15, 714],
        [45.76, 618],
        [45.59, 584],
        [45.56, 578],
        [34.72, 544],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return gen_recs, data_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    t = Set(
        m,
        name="t",
        records=[f"t{i}" for i in range(1, 25)],
        description="hours",
    )
    i = Set(
        m,
        name="i",
        records=[f"p{i}" for i in range(1, 5)],
        description="thermal units",
    )

    # SCALAR #
    lim = Parameter(m, name="lim", records=np.inf)

    # PARAMETERS #
    gendata = Parameter(
        m,
        name="gendata",
        domain=[i, "*"],
        records=data_records()[0],
        description="generator cost characteristics and limits",
    )
    data = Parameter(
        m, name="data", domain=[t, "*"], records=data_records()[1]
    )

    # VARIABLES #
    OF = Variable(m, name="OF", type="free", description="objective (revenue)")
    costThermal = Variable(
        m, name="costThermal", type="free", description="cost of thermal units"
    )
    p = Variable(
        m,
        name="p",
        type="free",
        domain=[i, t],
        description="power generated by thermal power plant",
    )
    EM = Variable(
        m, name="EM", type="free", description="emission calculation"
    )

    p.up[i, t] = gendata[i, "Pmax"]
    p.lo[i, t] = gendata[i, "Pmin"]

    # EQUATIONS #
    Genconst3 = Equation(m, name="Genconst3", type="regular", domain=[i, t])
    Genconst4 = Equation(m, name="Genconst4", type="regular", domain=[i, t])
    costThermalcalc = Equation(m, name="costThermalcalc", type="regular")
    balance = Equation(m, name="balance", type="regular", domain=[t])
    EMcalc = Equation(m, name="EMcalc", type="regular")
    EMlim = Equation(m, name="EMlim", type="regular")
    benefitcalc = Equation(m, name="benefitcalc", type="regular")

    costThermalcalc.definition = costThermal == Sum(
        [t, i],
        gendata[i, "a"] * gams_math.power(p[i, t], 2)
        + gendata[i, "b"] * p[i, t]
        + gendata[i, "c"],
    )

    Genconst3[i, t] = p[i, t.lead(1)] - p[i, t] <= gendata[i, "RU0"]

    Genconst4[i, t] = p[i, t.lag(1)] - p[i, t] <= gendata[i, "RD0"]

    balance[t] = Sum(i, p[i, t]) <= data[t, "load"]

    EMcalc.definition = EM == Sum(
        [t, i],
        gendata[i, "d"] * gams_math.power(p[i, t], 2)
        + gendata[i, "e"] * p[i, t]
        + gendata[i, "f"],
    )

    EMlim.definition = EM <= lim

    benefitcalc.definition = (
        OF == Sum([i, t], 1 * data[t, "lamda"] * p[i, t]) - costThermal
    )

    DEDPB = Model(
        m,
        name="DEDPB",
        equations=m.getEquations(),
        problem="qcp",
        sense="max",
        objective=OF,
    )
    DEDPB.solve()

    import math

    assert math.isclose(DEDPB.objective_value, 99552.6661, rel_tol=0.001)

    # Export results to an excel file
    p.pivot().round(3).to_excel("DEDPB.xlsx")


if __name__ == "__main__":
    main()

tests/integration/models/DED-PB.py
GAMS-dev/gamspy
"""
*** Dynamic Economic Load Dispatch

For more details please refer to Chapter 4 (Gcode4.1), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
Email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # gendata records table
    cols = ["a", "b", "c", "d", "e", "f", "Pmin", "Pmax", "RU0", "RD0"]
    inds = [f"g{i}" for i in range(1, 5)]
    data = [
        [0.12, 14.80, 89, 1.2, -5.0, 3.0, 28, 200, 40, 40],
        [0.17, 16.57, 83, 2.3, -4.24, 6.09, 20, 290, 30, 30],
        [0.15, 15.55, 100, 1.1, -2.15, 5.69, 30, 190, 30, 30],
        [0.19, 16.21, 70, 1.1, -3.99, 6.2, 20, 260, 50, 50],
    ]
    gen_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Demand data
    demand_recs = np.array(
        [
            510,
            530,
            516,
            510,
            515,
            544,
            646,
            686,
            741,
            734,
            748,
            760,
            754,
            700,
            686,
            720,
            714,
            761,
            727,
            714,
            618,
            584,
            578,
            544,
        ]
    )

    return gen_recs, demand_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    t = Set(
        m,
        name="t",
        records=[f"t{i}" for i in range(1, 25)],
        description="hours",
    )
    i = Set(
        m,
        name="i",
        records=[f"g{i}" for i in range(1, 5)],
        description="thermal units",
    )

    # PARAMETERS #
    demand = Parameter(m, name="demand", domain=[t], records=data_records()[1])
    gendata = Parameter(
        m,
        name="gendata",
        domain=[i, "*"],
        records=data_records()[0],
        description="generator cost characteristics and limits",
    )

    # VARIABLES #
    costThermal = Variable(
        m, name="costThermal", type="free", description="cost of thermal units"
    )
    p = Variable(
        m,
        name="p",
        type="free",
        domain=[i, t],
        description="power generated by thermal power plant",
    )
    EM = Variable(
        m, name="EM", type="free", description="emission calculation"
    )

    p.up[i, t] = gendata[i, "Pmax"]
    p.lo[i, t] = gendata[i, "Pmin"]

    # EQUATIONS #
    Genconst3 = Equation(m, name="Genconst3", type="regular", domain=[i, t])
    Genconst4 = Equation(m, name="Genconst4", type="regular", domain=[i, t])
    costThermalcalc = Equation(m, name="costThermalcalc", type="regular")
    balance = Equation(m, name="balance", type="regular", domain=[t])
    EMcalc = Equation(m, name="EMcalc", type="regular")

    costThermalcalc.definition = costThermal == Sum(
        [t, i],
        gendata[i, "a"] * sqr(p[i, t])
        + gendata[i, "b"] * p[i, t]
        + gendata[i, "c"],
    )

    Genconst3[i, t] = p[i, t.lead(1)] - p[i, t] <= gendata[i, "RU0"]

    Genconst4[i, t] = p[i, t.lag(1)] - p[i, t] <= gendata[i, "RD0"]

    balance[t] = Sum(i, p[i, t]) >= demand[t]

    EMcalc.definition = EM == Sum(
        [t, i],
        gendata[i, "d"] * sqr(p[i, t])
        + gendata[i, "e"] * p[i, t]
        + gendata[i, "f"],
    )

    DEDcostbased = Model(
        m,
        name="DEDcostbased",
        equations=m.getEquations(),
        problem="qcp",
        sense="min",
        objective=costThermal,
    )

    DEDcostbased.solve()

    import math

    assert math.isclose(
        DEDcostbased.objective_value, 647964.4601, rel_tol=0.001
    )

    # Export results to excel
    p.pivot().round(4).to_excel("DEDcostbased.xlsx")


if __name__ == "__main__":
    main()

tests/integration/models/DED.py
GAMS-dev/gamspy
"""
*** Cost based Dynamic Economic Dispatch integrated with Energy Storage and Wind


For more details please refer to Chapter 7 (Gcode7.2), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # gendata records table
    cols = ["a", "b", "c", "d", "e", "f", "Pmin", "Pmax", "RU0", "RD0"]
    inds = [f"p{i}" for i in range(1, 5)]
    data = [
        [0.12, 14.80, 89, 1.2, -5.0, 3.0, 28, 200, 40, 40],
        [0.17, 16.57, 83, 2.3, -4.24, 6.09, 20, 290, 30, 30],
        [0.15, 15.55, 100, 1.1, -2.15, 5.69, 30, 190, 30, 30],
        [0.19, 16.21, 70, 1.1, -3.99, 6.2, 20, 260, 50, 50],
    ]
    gen_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # data records table
    cols = ["lamda", "load", "wind"]
    inds = [f"t{i}" for i in range(1, 25)]
    data = [
        [32.71, 510, 44.1],
        [34.72, 530, 48.5],
        [32.71, 516, 65.7],
        [32.74, 510, 144.9],
        [32.96, 515, 202.3],
        [34.93, 544, 317.3],
        [44.9, 646, 364.4],
        [52.0, 686, 317.3],
        [53.03, 741, 271.0],
        [47.26, 734, 306.9],
        [44.07, 748, 424.1],
        [38.63, 760, 398.0],
        [39.91, 754, 487.6],
        [39.45, 700, 521.9],
        [41.14, 686, 541.3],
        [39.23, 720, 560.0],
        [52.12, 714, 486.8],
        [40.85, 761, 372.6],
        [41.2, 727, 367.4],
        [41.15, 714, 314.3],
        [45.76, 618, 316.6],
        [45.59, 584, 311.4],
        [45.56, 578, 405.4],
        [34.72, 544, 470.4],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return gen_recs, data_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    t = Set(
        m,
        name="t",
        records=[f"t{i}" for i in range(1, 25)],
        description="hours",
    )
    g = Set(
        m,
        name="g",
        records=[f"p{i}" for i in range(1, 5)],
        description="thermal units",
    )

    # PARAMETERS #
    gendata = Parameter(
        m,
        name="gendata",
        domain=[g, "*"],
        records=data_records()[0],
        description="generator cost characteristics and limits",
    )
    data = Parameter(
        m, name="data", domain=[t, "*"], records=data_records()[1]
    )

    # VARIABLES #
    cost = Variable(
        m, name="cost", type="free", description="cost of thermal units"
    )
    p = Variable(
        m,
        name="p",
        type="free",
        domain=[g, t],
        description="power generated by thermal power plant",
    )
    SOC = Variable(m, name="SOC", type="free", domain=[t])
    Pd = Variable(m, name="Pd", type="free", domain=[t])
    Pc = Variable(m, name="Pc", type="free", domain=[t])
    Pw = Variable(m, name="Pw", type="free", domain=[t])
    PWC = Variable(m, name="PWC", type="free", domain=[t])

    p.up[g, t] = gendata[g, "Pmax"]
    p.lo[g, t] = gendata[g, "Pmin"]

    # SCALARS #
    SOC0 = Parameter(m, name="SOC0", records=100)
    SOCmax = Parameter(m, name="SOCmax", records=300)
    eta_c = Parameter(m, name="eta_c", records=0.95)
    eta_d = Parameter(m, name="eta_d", records=0.9)
    VWC = Parameter(m, name="VWC", records=50)

    SOC.up[t] = SOCmax
    SOC.lo[t] = 0.2 * SOCmax
    SOC.fx["t24"] = SOC0

    Pc.up[t] = 0.2 * SOCmax
    Pc.lo[t] = 0
    Pd.up[t] = 0.2 * SOCmax
    Pd.lo[t] = 0
    Pw.up[t] = data[t, "wind"]
    Pw.lo[t] = 0
    PWC.up[t] = data[t, "wind"]
    PWC.lo[t] = 0

    # EQUATIONS #
    Genconst3 = Equation(m, name="Genconst3", type="regular", domain=[g, t])
    Genconst4 = Equation(m, name="Genconst4", type="regular", domain=[g, t])
    costThermalcalc = Equation(m, name="costThermalcalc", type="regular")
    constESS = Equation(m, name="constESS", type="regular", domain=[t])
    balance = Equation(m, name="balance", type="regular", domain=[t])
    wind = Equation(m, name="wind", type="regular", domain=[t])

    costThermalcalc.definition = cost == Sum(t, VWC * PWC[t]) + Sum(
        [t, g],
        gendata[g, "a"] * gams_math.power(p[g, t], 2)
        + gendata[g, "b"] * p[g, t]
        + gendata[g, "c"],
    )

    Genconst3[g, t] = p[g, t.lead(1)] - p[g, t] <= gendata[g, "RU0"]

    Genconst4[g, t] = p[g, t.lag(1)] - p[g, t] <= gendata[g, "RD0"]

    constESS[t] = (
        SOC[t]
        == SOC0.where[Ord(t) == 1]
        + SOC[t.lag(1)].where[Ord(t) > 1]
        + Pc[t] * eta_c
        - Pd[t] / eta_d
    )

    balance[t] = Pw[t] + Sum(g, p[g, t]) + Pd[t] >= data[t, "load"] + Pc[t]

    wind[t] = Pw[t] + PWC[t] == data[t, "wind"]

    DEDESScostbased = Model(
        m,
        name="DEDESScostbased",
        equations=m.getEquations(),
        problem="qcp",
        sense="min",
        objective=cost,
    )
    DEDESScostbased.solve()

    import math

    assert math.isclose(
        DEDESScostbased.objective_value, 223360.0645, rel_tol=0.001
    )

    # Reporting parameter
    rep = Parameter(m, name="rep", domain=[t, "*"])
    rep[t, "Pth"] = Sum(g, p.l[g, t])
    rep[t, "SOC"] = SOC.l[t]
    rep[t, "Pd"] = Pd.l[t]
    rep[t, "Pc"] = Pc.l[t]
    rep[t, "Pw"] = Pw.l[t]
    rep[t, "Pwc"] = PWC.l[t]
    rep[t, "Load"] = data[t, "load"]

    # Export results to an excel file
    writer = pd.ExcelWriter("DEDESScostbased.xlsx", engine="openpyxl")
    p.pivot().round(4).to_excel(writer, sheet_name="Pthermal")
    rep.pivot().round(4).to_excel(writer, sheet_name="rep")

    # close writer agent
    writer.close()


if __name__ == "__main__":
    main()

tests/integration/models/DEDESSwind.py
GAMS-dev/gamspy
"""
Dedication model with tradeability constraints

DedicationMIP.gms:  Dedication model with tradeability constraints.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 4.3.2
Last modified: Apr 2008.

First model - Simple dedication.
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def BondDataTable():
    # Bond data. Prices, coupons and maturities from the Danish market
    bond_data_recs = pd.DataFrame(
        np.array(
            [
                [112.35, 2006, 8],
                [105.33, 2003, 8],
                [111.25, 2007, 7],
                [107.30, 2004, 7],
                [107.62, 2011, 6],
                [106.68, 2009, 6],
                [101.93, 2002, 6],
                [101.30, 2005, 5],
                [101.61, 2003, 5],
                [100.06, 2002, 4],
            ]
        ),
        columns=["Price", "Maturity", "Coupon"],
        index=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
    )

    bond_data_recs = bond_data_recs.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )
    return bond_data_recs


def main():
    m = Container(delayed_execution=True)

    # SET #
    Time = Set(
        m,
        name="Time",
        records=[str(t) for t in range(2001, 2012)],
        description="Time periods",
    )

    t = Alias(m, name="t", alias_with=Time)

    # SCALARS #
    Now = Parameter(m, name="Now", description="Current year")
    Horizon = Parameter(m, name="Horizon", description="End of the Horizon")

    Now.assignment = 2001
    Horizon.assignment = Card(t) - 1

    # PARAMETER #
    tau = Parameter(m, name="tau", domain=[t], description="Time in years")

    # Note: time starts from 0
    tau[t] = Ord(t) - 1

    # SET
    Bonds = Set(
        m,
        name="Bonds",
        records=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
        description="Bonds universe",
    )

    i = Alias(m, name="i", alias_with=Bonds)

    # SCALARS #
    spread = Parameter(
        m,
        name="spread",
        description="Borrowing spread over the reinvestment rate",
    )

    # PARAMETERS #
    Price = Parameter(m, name="Price", domain=[i], description="Bond prices")
    Coupon = Parameter(m, name="Coupon", domain=[i], description="Coupons")
    Maturity = Parameter(
        m, name="Maturity", domain=[i], description="Maturities"
    )
    Liability = Parameter(
        m, name="Liability", domain=[t], description="Stream of liabilities"
    )
    rf = Parameter(m, name="rf", domain=[t], description="Reinvestment rates")
    F = Parameter(m, name="F", domain=[t, i], description="Cashflows")

    # Bond data. Prices, coupons and maturities from the Danish market
    BondData = Parameter(
        m, name="BondData", domain=[i, "*"], records=BondDataTable()
    )

    # Copy/transform data. Note division by 100 to get unit data, and
    # subtraction of "Now" from Maturity date (so consistent with tau):

    Price[i] = BondData[i, "Price"] / 100
    Coupon[i] = BondData[i, "Coupon"] / 100
    Maturity[i] = BondData[i, "Maturity"] - Now

    # Calculate the ex-coupon cashflow of Bond i in year t:
    F[t, i] = (
        Number(1).where[tau[t] == Maturity[i]]
        + Coupon[i].where[(tau[t] <= Maturity[i]) & (tau[t] > 0)]
    )

    # For simplicity, we set the short term rate to be 0.03 in each period
    rf[t] = 0.04
    spread.assignment = 0.02

    Liability.setRecords(
        np.array(
            [
                0,
                80000,
                100000,
                110000,
                120000,
                140000,
                120000,
                90000,
                50000,
                75000,
                150000,
            ]
        )
    )

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Face value purchased",
    )
    surplus = Variable(
        m,
        name="surplus",
        type="positive",
        domain=[t],
        description="Amount of money reinvested",
    )
    borrow = Variable(
        m,
        name="borrow",
        type="positive",
        domain=[t],
        description="Amount of money borrowed",
    )
    v0 = Variable(m, name="v0", description="Upfront investment")

    # EQUATION #
    CashFlowCon = Equation(
        m,
        name="CashFlowCon",
        type="regular",
        domain=[t],
        description="Equations defining the cashflow balance",
    )

    CashFlowCon[t] = (
        Sum(i, F[t, i] * x[i])
        + (v0 - Sum(i, Price[i] * x[i])).where[tau[t] == 0]
        + borrow[t].where[tau[t] < Horizon]
        + ((1 + rf[t.lag(1)]) * surplus[t.lag(1)]).where[tau[t] > 0]
        == surplus[t]
        + Liability[t].where[tau[t] > 0]
        + ((1 + rf[t.lag(1)] + spread) * borrow[t.lag(1)]).where[tau[t] > 0]
    )

    m.addOptions({"SOLVEOPT": "REPLACE"})
    Dedication = Model(
        m,
        name="Dedication",
        equations=[CashFlowCon],
        problem="LP",
        sense="MIN",
        objective=v0,
    )

    Dedication.solve()

    print("* First Model Results\n")
    print("v0: ", v0.records.level.round(3).tolist(), "\n")
    print("borrow: ", borrow.records.level.round(3).tolist(), "\n")
    print("surplus: ", surplus.records.level.round(3).tolist(), "\n")
    print("x: ", x.records.level.round(3).tolist(), "\n\n")

    output_csv = "No trading constraints\n"

    purchased_price = (x.records.level * Price.records.value).round(3).tolist()
    for idx, ii, _ in i.records.itertuples():
        output_csv += f'{round(v0.records.level[0],3)},"{ii}",{BondData.pivot().loc[ii,"Maturity"]},{Coupon.records.value[idx]},{purchased_price[idx]}\n'

    for tt, _ in t.records.itertuples(index=False):
        borrow_rec = borrow.records[borrow.records["t"] == tt]
        borrow_rec = (
            round(borrow_rec.level.array[0], 3) if not borrow_rec.empty else 0
        )
        surplus_rec = surplus.records[surplus.records["t"] == tt]
        surplus_rec = (
            round(surplus_rec.level.array[0], 3)
            if not surplus_rec.empty
            else 0
        )
        output_csv += f'"{tt}",{borrow_rec},{surplus_rec}\n'

    # Second model - Dedication plus even-lot constraints.

    # SCALARS #
    LotSize = Parameter(
        m, name="LotSize", records=1000, description="Even-Lot requirement"
    )
    FixedCost = Parameter(
        m, name="FixedCost", records=20, description="Fixed cost per trade"
    )
    VarblCost = Parameter(
        m, name="VarblCost", records=0.01, description="Variable cost"
    )

    # VARIABLE #
    Y = Variable(
        m,
        name="Y",
        type="integer",
        domain=[i],
        description="Variable counting the number of lot purchased",
    )

    # EQUATION #
    EvenLot = Equation(
        m,
        name="EvenLot",
        type="regular",
        domain=[i],
        description="Equation defining the even-lot requirements",
    )

    EvenLot[i] = x[i] == LotSize * Y[i]

    # Some reasonable upper bounds on Y[i]

    Y.up[i] = gams_math.ceil(Sum(t, Liability[t]) / Price[i] / LotSize)

    DedicationMIPEvenLot = Model(
        m,
        name="DedicationMIPEvenLot",
        equations=[CashFlowCon, EvenLot],
        problem="MIP",
        sense="MIN",
        objective=v0,
    )

    m.addOptions({"OPTCR": 0, "ITERLIM": 999999, "RESLIM": 100})
    DedicationMIPEvenLot.solve()
    print("* Second Model Results\n")
    print("x: ", x.records.level.round(3).tolist(), "\n")
    print("Y: ", Y.records.level.round(3).tolist(), "\n")
    print("v0: ", v0.records.level.round(3).tolist(), "\n\n")

    output_csv += "Even-lot constraints\n"
    purchased_price = (x.records.level * Price.records.value).round(3).tolist()

    for idx, ii, _ in i.records.itertuples():
        output_csv += f'{round(v0.records.level[0],3)},"{ii}",{BondData.pivot().loc[ii,"Maturity"]},{Coupon.records.value[idx]},{purchased_price[idx]}\n'

    for tt, _ in t.records.itertuples(index=False):
        borrow_rec = borrow.records[borrow.records["t"] == tt]
        borrow_rec = (
            round(borrow_rec.level.array[0], 3) if not borrow_rec.empty else 0
        )
        surplus_rec = surplus.records[surplus.records["t"] == tt]
        surplus_rec = (
            round(surplus_rec.level.array[0], 3)
            if not surplus_rec.empty
            else 0
        )
        output_csv += f'"{tt}",{borrow_rec},{surplus_rec}\n'

    # Third model - Dedication plus fixed and variable transaction costs

    # VARIABLES #
    TotalCost = Variable(
        m, name="TotalCost", description="Total cost to minimize"
    )
    TransCosts = Variable(
        m,
        name="TransCosts",
        description="Total transaction costs (fixed + variable)",
    )

    # VARIABLES #
    Z = Variable(
        m,
        name="Z",
        type="binary",
        domain=[i],
        description="Indicator variable for assets included in the portfolio",
    )

    # EQUATIONS #
    CostDef = Equation(
        m,
        name="CostDef",
        type="regular",
        description=(
            "Equation definining the total cost including transaction costs"
        ),
    )
    TransDef = Equation(
        m,
        name="TransDef",
        type="regular",
        description="Equation the transaction costs (fixed + variable)",
    )
    UpBounds = Equation(
        m,
        name="UpBounds",
        type="regular",
        domain=[i],
        description="Upper bounds for each variable",
    )

    CostDef.definition = TotalCost == v0 + TransCosts

    TransDef.definition = TransCosts == Sum(
        i, FixedCost * Z[i] + VarblCost * x[i]
    )

    UpBounds[i] = x[i] <= x.up[i] * Z[i]

    DedicationMIPTrnCosts = Model(
        m,
        name="DedicationMIPTrnCosts",
        equations=[CashFlowCon, CostDef, TransDef, UpBounds],
        problem="MIP",
        sense="MIN",
        objective=TotalCost,
    )

    # Some conservative bounds on investments

    x.up[i] = LotSize * Y.up[i]

    DedicationMIPTrnCosts.solve()
    print("* Third Model Results\n")
    print("x: ", x.records.level.round(3).tolist(), "\n")
    print("v0: ", v0.records.level.round(3).tolist(), "\n")
    print("TotalCost: ", TotalCost.records.level.round(3).tolist(), "\n")
    print("TransCosts: ", TransCosts.records.level.round(3).tolist(), "\n\n")

    output_csv += "Fixed and variable costs\n"
    purchased_price = (x.records.level * Price.records.value).round(3).tolist()

    for idx, ii, _ in i.records.itertuples():
        output_csv += f'{round(v0.records.level[0],3)},"{ii}",{BondData.pivot().loc[ii,"Maturity"]},{Coupon.records.value[idx]},{purchased_price[idx]}\n'

    for tt, _ in t.records.itertuples(index=False):
        borrow_rec = borrow.records[borrow.records["t"] == tt]
        borrow_rec = (
            round(borrow_rec.level.array[0], 3) if not borrow_rec.empty else 0
        )
        surplus_rec = surplus.records[surplus.records["t"] == tt]
        surplus_rec = (
            round(surplus_rec.level.array[0], 3)
            if not surplus_rec.empty
            else 0
        )
        output_csv += f'"{tt}",{borrow_rec},{surplus_rec}\n'

    # Fourth model - Dedication including even-lot restrictions and
    # transaction costs.

    DedicationMIPAll = Model(
        m,
        name="DedicationMIPAll",
        equations=[CashFlowCon, EvenLot, CostDef, TransDef, UpBounds],
        problem="MIP",
        sense="MIN",
        objective=TotalCost,
    )

    DedicationMIPAll.solve()
    print("* Fourth Model Results\n")
    print("x: ", x.records.level.round(3).tolist(), "\n")
    print("v0: ", v0.records.level.round(3).tolist(), "\n")
    print("TotalCost: ", TotalCost.records.level.round(3).tolist(), "\n")
    print("TransCosts: ", TransCosts.records.level.round(3).tolist(), "\n\n")

    output_csv += "Even-lot constraints and transaction costs\n"
    purchased_price = (x.records.level * Price.records.value).round(3).tolist()

    for idx, ii, _ in i.records.itertuples():
        output_csv += f'{round(v0.records.level[0],3)},"{ii}",{BondData.pivot().loc[ii,"Maturity"]},{Coupon.records.value[idx]},{purchased_price[idx]}\n'

    for tt, _ in t.records.itertuples(index=False):
        borrow_rec = borrow.records[borrow.records["t"] == tt]
        borrow_rec = (
            round(borrow_rec.level.array[0], 3) if not borrow_rec.empty else 0
        )
        surplus_rec = surplus.records[surplus.records["t"] == tt]
        surplus_rec = (
            round(surplus_rec.level.array[0], 3)
            if not surplus_rec.empty
            else 0
        )
        output_csv += f'"{tt}",{borrow_rec},{surplus_rec}\n'

    DedicationHandle = open(
        "DedicationMIPPortfolios.csv", "w", encoding="UTF-8"
    )
    DedicationHandle.write(output_csv)


if __name__ == "__main__":
    main()

tests/integration/models/DedicationMip.py
GAMS-dev/gamspy
"""
Dedication model without borrowing

Dedication.gms:  Dedication model without borrowing.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 2.4
Last modified: Apr 2008.
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    # Bond data. Prices, coupons and maturities from the Danish market
    bond_data_recs = pd.DataFrame(
        np.array(
            [
                [112.35, 2006, 8],
                [105.33, 2003, 8],
                [111.25, 2007, 7],
                [107.30, 2004, 7],
                [107.62, 2011, 6],
                [106.68, 2009, 6],
                [101.93, 2002, 6],
                [101.30, 2005, 5],
                [101.61, 2003, 5],
                [100.06, 2002, 4],
            ]
        ),
        columns=["Price", "Maturity", "Coupon"],
        index=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
    )

    bond_data_recs = bond_data_recs.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )

    # Define container
    m = Container(delayed_execution=True)

    # SETS #
    Time = Set(
        m,
        name="Time",
        records=[str(i) for i in range(2001, 2012)],
        description="Time periods",
    )

    Bonds = Set(
        m,
        name="Bonds",
        records=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
        description="Bonds universe",
    )

    # ALIASES #
    t = Alias(m, name="t", alias_with=Time)
    i = Alias(m, name="i", alias_with=Bonds)

    # SCALARS #
    Now = Parameter(m, name="Now", description="Current year")
    Horizon = Parameter(m, name="Horizon", description="End of the Horizon")

    Now.assignment = 2001
    Horizon.assignment = Card(t) - 1

    # PARAMETER #
    tau = Parameter(m, name="tau", domain=[t], description="Time in years")

    # Note: time starts from 0
    tau[t] = Ord(t) - 1

    Price = Parameter(m, name="Price", domain=[i], description="Bond prices")
    Coupon = Parameter(m, name="Coupon", domain=[i], description="Coupons")
    Maturity = Parameter(
        m, name="Maturity", domain=[i], description="Maturities"
    )
    rf = Parameter(m, name="rf", domain=[t], description="Reinvestment rates")
    F = Parameter(m, name="F", domain=[t, i], description="Cashflows")
    BondData = Parameter(
        m, name="BondData", domain=[i, "*"], records=bond_data_recs
    )
    Liability = Parameter(
        m, name="Liability", domain=[t], description="Stream of liabilities"
    )

    # Copy/transform data. Note division by 100 to get unit data, and
    # subtraction of "Now" from Maturity date (so consistent with tau):
    Price[i] = BondData[i, "Price"] / 100
    Coupon[i] = BondData[i, "Coupon"] / 100
    Maturity[i] = BondData[i, "Maturity"] - Now

    # Calculate the ex-coupon cashflow of Bond i in year t:
    F[t, i] = (
        Number(1).where[tau[t] == Maturity[i]]
        + Coupon[i].where[(tau[t] <= Maturity[i]) & (tau[t] > 0)]
    )

    # For simplicity, we set the short term rate to be 0.03 in each period
    rf[t] = 0.04

    Liability.setRecords(
        np.array(
            [
                0,
                80000,
                100000,
                110000,
                120000,
                140000,
                120000,
                90000,
                50000,
                75000,
                150000,
            ]
        )
    )

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Face value purchased",
    )
    surplus = Variable(
        m,
        name="surplus",
        type="positive",
        domain=[t],
        description="Amount of money reinvested",
    )
    v0 = Variable(m, name="v0", type="free", description="Upfront investment")

    # EQUATION #
    CashFlowCon = Equation(
        m,
        name="CashFlowCon",
        domain=[t],
        description="Equations defining the cashflow balance",
    )

    CashFlowCon[t] = (
        Sum(i, F[t, i] * x[i])
        + (v0 - Sum(i, Price[i] * x[i])).where[tau[t] == 0]
        + ((1 + rf[t.lag(1)]) * surplus[t.lag(1)]).where[tau[t] > 0]
        == surplus[t] + Liability[t].where[tau[t] > 0]
    )

    Dedication = Model(
        m,
        name="Dedication",
        equations=[CashFlowCon],
        problem="LP",
        sense=Sense.MIN,
        objective=v0,
    )
    Dedication.solve()

    print("Objective Function Variable: ", round(v0.records.level[0], 3))


if __name__ == "__main__":
    main()

tests/integration/models/DedicationNoBorrow.py
GAMS-dev/gamspy
"""
*** Sensitivity Analysis in Economic Load Dispatch

For more details please refer to Chapter 3 (Gcode3.2), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
Email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # data records table
    cols = ["a", "b", "c", "Pmin", "Pmax"]
    inds = [f"g{i}" for i in range(1, 6)]
    data = [
        [3.0, 20.0, 100.0, 28, 206],
        [4.05, 18.07, 98.87, 90, 284],
        [4.05, 15.55, 104.26, 68, 189],
        [3.99, 19.21, 107.21, 76, 266],
        [3.88, 26.18, 95.31, 19, 53],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return data_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    gen = Set(m, name="gen", records=[f"g{i}" for i in range(1, 6)])
    counter = Set(m, name="counter", records=[f"c{i}" for i in range(1, 12)])

    # PARAMETERS #
    report = Parameter(m, name="report", domain=[counter, "*"])
    repGen = Parameter(m, name="repGen", domain=[counter, gen])
    load = Parameter(m, name="load", records=400)
    data = Parameter(m, name="data", domain=[gen, "*"], records=data_records())

    # VARIABLES #
    P = Variable(m, name="P", domain=[gen])
    OF = Variable(m, name="OF")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")

    eq1.definition = OF == Sum(
        gen,
        data[gen, "a"] * P[gen] * P[gen]
        + data[gen, "b"] * P[gen]
        + data[gen, "c"],
    )

    eq2.definition = Sum(gen, P[gen]) >= load

    P.lo[gen] = data[gen, "Pmin"]
    P.up[gen] = data[gen, "Pmax"]

    ECD = Model(
        m,
        name="ECD",
        equations=[eq1, eq2],
        problem="qcp",
        sense="min",
        objective=OF,
    )

    for idx, cc in enumerate(counter.toList()):
        load.assignment = Sum(gen, data[gen, "Pmin"]) + (
            (idx) / (Card(counter) - 1)
        ) * Sum(gen, data[gen, "Pmax"] - data[gen, "Pmin"])
        ECD.solve()
        repGen[cc, gen] = P.l[gen]
        report[cc, "OF"] = OF.l
        report[cc, "load"] = load

    import math

    assert math.isclose(ECD.objective_value, 911044.0900, rel_tol=0.001)

    print("repgen:  \n", repGen.pivot().round(3))
    print("report:  \n", report.pivot().round(3))


if __name__ == "__main__":
    main()

tests/integration/models/EDsensitivity.py
GAMS-dev/gamspy
"""
*** Mixed integer linear programming model for optimal allocation of Emergency Centres

For more details please refer to Chapter 2 (Gcode2.5), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Binary Variables
    x1 = Variable(m, name="x1", type="binary")
    x2 = Variable(m, name="x2", type="binary")
    x3 = Variable(m, name="x3", type="binary")
    x4 = Variable(m, name="x4", type="binary")
    x5 = Variable(m, name="x5", type="binary")
    x6 = Variable(m, name="x6", type="binary")

    # Free Variable
    of = Variable(m, name="of", type="free")

    # Equations
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")
    eq4 = Equation(m, name="eq4", type="regular")
    eq5 = Equation(m, name="eq5", type="regular")
    eq6 = Equation(m, name="eq6", type="regular")
    eq7 = Equation(m, name="eq7", type="regular")

    eq1.definition = x1 + x6 >= 1
    eq2.definition = x2 >= 1
    eq3.definition = x3 + x5 >= 1
    eq4.definition = x4 + x5 >= 1
    eq5.definition = x3 + x4 + x5 + x6 >= 1
    eq6.definition = x1 + x5 + x6 >= 1
    eq7.definition = x1 + x2 + x3 + x4 + x5 + x6 == of

    emergency = Model(
        m,
        name="emergency",
        equations=m.getEquations(),
        problem="mip",
        sense="min",
        objective=of,
    )
    emergency.solve()

    import math

    assert math.isclose(emergency.objective_value, 3.0000, rel_tol=0.001)
    print("Objective Function Value:\t", emergency.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/EmergencyCentreAllocation.py
GAMS-dev/gamspy
"""
*** Optimal operation of energy hub

For more details please refer to Chapter 10 (Gcode10.3), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # data records table
    cols = ["Dh", "De", "Dc", "lamda"]
    inds = [f"t{i}" for i in range(1, 25)]
    data = [
        [21.4, 52.1, 11.5, 36.7],
        [23.2, 66.7, 13.7, 40.4],
        [26.1, 72.2, 16.0, 38.5],
        [26.7, 78.4, 21.4, 38.0],
        [25.6, 120.2, 22.0, 40.2],
        [26.4, 83.5, 30.8, 38.6],
        [39.5, 110.4, 38.9, 52.3],
        [47.3, 124.3, 46.8, 67.3],
        [52.1, 143.6, 51.0, 70.5],
        [49.1, 149.3, 48.9, 66.2],
        [69.3, 154.2, 34.8, 73.3],
        [62.0, 147.3, 32.7, 60.8],
        [68.0, 200.7, 27.8, 63.2],
        [68.6, 174.4, 32.0, 70.8],
        [56.4, 176.5, 33.2, 63.1],
        [41.3, 136.1, 34.1, 52.5],
        [37.4, 108.7, 40.8, 57.0],
        [25.4, 96.9, 43.6, 49.2],
        [25.7, 89.1, 51.5, 47.5],
        [21.9, 82.5, 43.1, 49.5],
        [22.4, 76.9, 36.5, 53.1],
        [24.6, 66.8, 27.7, 51.6],
        [22.7, 47.2, 19.1, 50.5],
        [22.6, 64.7, 11.0, 36.4],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return data_recs


def main():
    m = Container(delayed_execution=True)

    # SET #
    t = Set(
        m,
        name="t",
        records=[f"t{i}" for i in range(1, 25)],
        description="hours",
    )

    # DATA PARAMETER #
    data = Parameter(m, name="data", domain=[t, "*"], records=data_records())

    # SCALARS #
    CBmax = Parameter(m, name="CBmax", records=500)
    eta_ee = Parameter(m, name="eta_ee", records=0.98)
    eta_ghf = Parameter(m, name="eta_ghf", records=0.9)
    eta_hc = Parameter(m, name="eta_hc", records=0.95)

    # VARIABLES #
    cost = Variable(m, name="cost", type="free")

    E = Variable(m, name="E", type="positive", domain=[t])
    G = Variable(m, name="G", type="positive", domain=[t])
    H1 = Variable(m, name="H1", type="positive", domain=[t])
    H2 = Variable(m, name="H2", type="positive", domain=[t])

    H2.up[t] = CBmax

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular", domain=[t])
    eq3 = Equation(m, name="eq3", type="regular", domain=[t])
    eq4 = Equation(m, name="eq4", type="regular", domain=[t])
    eq5 = Equation(m, name="eq5", type="regular", domain=[t])

    eq1.definition = cost == Sum(t, data[t, "lamda"] * E[t] + 12 * G[t])
    eq2[t] = eta_ee * E[t] == data[t, "De"]
    eq3[t] = H1[t] == data[t, "Dh"]
    eq4[t] = eta_ghf * G[t] == H1[t] + H2[t]
    eq5[t] = eta_hc * H2[t] == data[t, "Dc"]

    hub = Model(
        m,
        name="hub",
        equations=m.getEquations(),
        problem="lp",
        sense="min",
        objective=cost,
    )
    hub.solve()

    import math

    assert math.isclose(hub.objective_value, 173570.3851, rel_tol=0.001)

    # Reporting Parameter
    report = Parameter(m, name="report", domain=[t, "*"])
    report[t, "E"] = E.l[t]
    report[t, "G"] = G.l[t]
    report[t, "h1"] = H1.l[t]
    report[t, "h2"] = H2.l[t]

    print("report:  \n", report.pivot().round(4))


if __name__ == "__main__":
    main()

tests/integration/models/EnergyHub.py
GAMS-dev/gamspy
"""
*** Environmental Load Dispatch

For more details please refer to Chapter 3 (Gcode3.3), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
Email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # data records table
    cols = ["a", "b", "c", "d", "e", "f", "Pmin", "Pmax"]
    inds = [f"g{i}" for i in range(1, 6)]
    data = [
        [3.0, 20.0, 100.0, 2.0, -5.0, 3.0, 28, 206],
        [4.05, 18.07, 98.87, 3.82, -4.24, 6.09, 90, 284],
        [4.05, 15.55, 104.26, 5.01, -2.15, 5.69, 68, 189],
        [3.99, 19.21, 107.21, 1.1, -3.99, 6.2, 76, 266],
        [3.88, 26.18, 95.31, 3.55, -6.88, 5.57, 19, 53],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return data_recs


def main():
    m = Container(delayed_execution=True)

    # SET #
    gen = Set(m, name="gen", records=[f"g{i}" for i in range(1, 6)])

    # PARAMETERS #
    report = Parameter(m, name="report", domain=[gen, "*"])
    data = Parameter(m, name="data", domain=[gen, "*"], records=data_records())

    # SCALARS #
    load = Parameter(m, name="load", records=400)
    Eprice = Parameter(m, name="Eprice", records=0.1)

    # VARIABLES #
    P = Variable(m, name="P", type="free", domain=[gen])
    OF = Variable(m, name="OF", type="free")
    TE = Variable(m, name="TE", type="free")
    TC = Variable(m, name="TC", type="free")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")
    eq4 = Equation(m, name="eq4", type="regular")

    eq1.definition = TC == Sum(
        gen,
        data[gen, "a"] * P[gen] * P[gen]
        + data[gen, "b"] * P[gen]
        + data[gen, "c"],
    )
    eq2.definition = Sum(gen, P[gen]) >= load
    eq3.definition = TE == Sum(
        gen,
        data[gen, "d"] * P[gen] * P[gen]
        + data[gen, "e"] * P[gen]
        + data[gen, "f"],
    )
    eq4.definition = OF == TC + TE * Eprice

    P.lo[gen] = data[gen, "Pmin"]
    P.up[gen] = data[gen, "Pmax"]

    END1 = Model(
        m,
        name="END1",
        equations=[eq1, eq2, eq3, eq4],
        problem="qcp",
        sense="min",
        objective=TC,
    )
    END1.solve()
    report[gen, "ED"] = P.l[gen]

    END2 = Model(
        m,
        name="END2",
        equations=[eq1, eq2, eq3, eq4],
        problem="qcp",
        sense="min",
        objective=TE,
    )
    END2.solve()
    report[gen, "END"] = P.l[gen]

    END3 = Model(
        m,
        name="END3",
        equations=[eq1, eq2, eq3, eq4],
        problem="qcp",
        sense="min",
        objective=OF,
    )
    END3.solve()
    report[gen, "penalty"] = P.l[gen]

    TE.up.assignment = 90000
    END1.solve()

    import math

    assert math.isclose(END1.objective_value, 133190.1326, rel_tol=0.001)
    report[gen, "limit"] = P.l[gen]

    print("report  \n", report.pivot().round(4))


if __name__ == "__main__":
    main()

tests/integration/models/EnvironmentalED.py
GAMS-dev/gamspy
"""
Portfolio horizon returns model

Horizon.gms:  Portfolio horizon returns model.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 4.3.1
Last modified: Apr 2008.
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def BondDataTable():
    # Bond data. Prices, coupons and maturities from the Danish market
    bond_data_recs = pd.DataFrame(
        np.array(
            [
                [112.35, 2006, 8],
                [105.33, 2003, 8],
                [111.25, 2007, 7],
                [107.30, 2004, 7],
                [107.62, 2011, 6],
                [106.68, 2009, 6],
                [101.93, 2002, 6],
                [101.30, 2005, 5],
                [101.61, 2003, 5],
                [100.06, 2002, 4],
            ]
        ),
        columns=["Price", "Maturity", "Coupon"],
        index=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
    )

    bond_data_recs = bond_data_recs.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )
    return bond_data_recs


def main():
    m = Container(delayed_execution=True)

    Time = Set(
        m,
        name="Time",
        records=[str(time) for time in range(2001, 2012)],
        description="Time periods",
    )

    t = Alias(m, name="t", alias_with=Time)

    # SCALARS #
    Now = Parameter(m, name="Now", description="Current year")
    Horizon = Parameter(m, name="Horizon", description="End of the Horizon")

    Now.assignment = 2001
    Horizon.assignment = Card(t) - 1

    # PARAMETER #

    tau = Parameter(m, name="tau", domain=[t], description="Time in years")

    # Note: time starts from 0

    tau[t] = Ord(t) - 1

    # SET
    Bonds = Set(
        m,
        name="Bonds",
        records=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
        description="Bonds universe",
    )

    i = Alias(m, name="i", alias_with=Bonds)

    # SCALARS #
    spread = Parameter(
        m,
        name="spread",
        description="Borrowing spread over the reinvestment rate",
    )
    Budget = Parameter(m, name="Budget", description="Initial budget")

    # PARAMETERS #
    Price = Parameter(m, name="Price", domain=[i], description="Bond prices")
    Coupon = Parameter(m, name="Coupon", domain=[i], description="Coupons")
    Maturity = Parameter(
        m, name="Maturity", domain=[i], description="Maturities"
    )
    Liability = Parameter(
        m, name="Liability", domain=[t], description="Stream of liabilities"
    )
    rf = Parameter(m, name="rf", domain=[t], description="Reinvestment rates")
    F = Parameter(m, name="F", domain=[t, i], description="Cashflows")

    # Bond data. Prices, coupons and maturities from the Danish market

    BondData = Parameter(
        m, name="BondData", domain=[i, "*"], records=BondDataTable()
    )

    # Copy/transform data. Note division by 100 to get unit data, and
    # subtraction of "Now" from Maturity date (so consistent with tau):

    Price[i] = BondData[i, "Price"] / 100
    Coupon[i] = BondData[i, "Coupon"] / 100
    Maturity[i] = BondData[i, "Maturity"] - Now

    # Calculate the ex-coupon cashflow of Bond i in year t:

    F[t, i] = (
        Number(1).where[tau[t] == Maturity[i]]
        + Coupon[i].where[(tau[t] <= Maturity[i]) & (tau[t] > 0)]
    )

    # For simplicity, we set the short term rate to be 0.03 in each period

    rf[t] = 0.04
    spread.assignment = 0.02

    # Initial available budget to buy the matching portfolio

    Budget.assignment = 803021.814
    # 803021.814
    # 850000

    # PARAMETER #
    Liability.setRecords(
        np.array(
            [
                0,
                80000,
                100000,
                110000,
                120000,
                140000,
                120000,
                90000,
                50000,
                75000,
                150000,
            ]
        )
    )

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Face value purchased",
    )
    surplus = Variable(
        m,
        name="surplus",
        type="positive",
        domain=[t],
        description="Amount of money reinvested",
    )
    borrow = Variable(
        m,
        name="borrow",
        type="positive",
        domain=[t],
        description="Amount of money borrowed",
    )
    HorizonRet = Variable(m, name="HorizonRet", description="Horizon Return")

    # EQUATION #
    CashFlowCon = Equation(
        m,
        name="CashFlowCon",
        type="regular",
        domain=[t],
        description="Equations defining the cashflow balance",
    )

    CashFlowCon[t] = (
        Sum(i, F[t, i] * x[i])
        + (Budget - Sum(i, Price[i] * x[i])).where[tau[t] == 0]
        + borrow[t].where[tau[t] < Horizon]
        + ((1 + rf[t.lag(1)]) * surplus[t.lag(1)]).where[tau[t] > 0]
        == Liability[t].where[tau[t] > 0]
        + surplus[t].where[tau[t] < Horizon]
        + HorizonRet.where[tau[t] == Horizon]
        + ((1 + rf[t.lag(1)] + spread) * borrow[t.lag(1)]).where[tau[t] > 0]
    )

    HorizonMod = Model(
        m,
        name="HorizonMod",
        equations=[CashFlowCon],
        problem="LP",
        sense="MAX",
        objective=HorizonRet,
    )

    HorizonMod.solve()

    print("HorizonRet: ", round(HorizonRet.toValue(), 3))
    print("borrow: ", borrow.toDict())
    print("surplus: ", surplus.toDict())
    print("x: ", x.toDict())

    # Simulation for different values of the initial budget

    HorizonHandle = open("HorizonPortfolios_new.csv", "w", encoding="UTF-8")

    budget = 778985.948
    while budget <= 818985.948:
        Budget.assignment = budget
        HorizonMod.solve()

        for ii in i.toList():
            horizon_ret = round(HorizonRet.records.level[0], 2)
            bond_mat = round(BondData.pivot().loc[ii, "Maturity"], 2)
            coupon = Coupon.records[Coupon.records["i"] == ii].value.array[0]
            purchased_price = round(
                x.records[x.records["i"] == ii].level.array[0]
                * Price.records[Price.records["i"] == ii].value.array[0],
                3,
            )

            HorizonHandle.write(
                f'{round(budget,2)},{horizon_ret},"{ii}",{bond_mat},{coupon},{purchased_price}'
            )
            HorizonHandle.write("\n")

        for tt in t.toList():
            borrow_rec = borrow.records[borrow.records["t"] == tt]
            borrow_rec = (
                round(borrow_rec.level.array[0], 3)
                if not borrow_rec.empty
                else 0
            )
            HorizonHandle.write(f'"{tt}",{borrow_rec}')
            HorizonHandle.write("\n")

        budget += 10000

    HorizonHandle.close()


if __name__ == "__main__":
    main()

tests/integration/models/Horizon.py
GAMS-dev/gamspy
"""
Immunization models

Immunization.gms: Immunization models.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 4.4
Last modified: Apr 2008.
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import sqr


def main():
    # Define container
    m = Container(delayed_execution=True)

    # Bond data. Prices, coupons and maturities from the Danish market
    bond_data_recs = pd.DataFrame(
        np.array(
            [
                [112.35, 2006, 8],
                [105.33, 2003, 8],
                [111.25, 2007, 7],
                [107.30, 2004, 7],
                [107.62, 2011, 6],
                [106.68, 2009, 6],
                [101.93, 2002, 6],
                [101.30, 2005, 5],
                [101.61, 2003, 5],
                [100.06, 2002, 4],
            ]
        ),
        columns=["Price", "Maturity", "Coupon"],
        index=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
    )

    bond_data_recs = bond_data_recs.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )

    # SETS #
    Time = Set(
        m,
        name="Time",
        records=[str(i) for i in range(2001, 2012)],
        description="Time periods",
    )

    Bonds = Set(
        m,
        name="Bonds",
        records=[
            "DS-8-06",
            "DS-8-03",
            "DS-7-07",
            "DS-7-04",
            "DS-6-11",
            "DS-6-09",
            "DS-6-02",
            "DS-5-05",
            "DS-5-03",
            "DS-4-02",
        ],
        description="Bonds universe",
    )

    # ALIASES #
    t = Alias(m, name="t", alias_with=Time)
    i = Alias(m, name="i", alias_with=Bonds)

    # ALIAS (Time, t, t1, t2)

    # SCALARS #
    Now = Parameter(m, name="Now", description="Current year")
    Horizon = Parameter(m, name="Horizon", description="End of the Horizon")

    Now.assignment = 2001
    Horizon.assignment = Card(t) - 1

    # PARAMETER #
    tau = Parameter(m, name="tau", domain=[t], description="Time in years")

    # Note: time starts from 0
    tau[t] = Ord(t) - 1

    Coupon = Parameter(m, name="Coupon", domain=[i], description="Coupons")
    Maturity = Parameter(
        m, name="Maturity", domain=[i], description="Maturities"
    )
    F = Parameter(m, name="F", domain=[t, i], description="Cashflows")
    BondData = Parameter(
        m, name="BondData", domain=[i, "*"], records=bond_data_recs
    )
    Liability = Parameter(
        m, name="Liability", domain=[t], description="Stream of liabilities"
    )

    # Copy/transform data. Note division by 100 to get unit data, and
    # subtraction of "Now" from Maturity date (so consistent with tau):

    Coupon[i] = BondData[i, "Coupon"] / 100
    Maturity[i] = BondData[i, "Maturity"] - Now

    # Calculate the ex-coupon cashflow of Bond i in year t:

    F[t, i] = (
        Number(1).where[tau[t] == Maturity[i]]
        + Coupon[i].where[(tau[t] <= Maturity[i]) & (tau[t] > 0)]
    )

    Liability.setRecords(
        np.array(
            [
                0,
                80000,
                100000,
                110000,
                120000,
                140000,
                120000,
                90000,
                50000,
                75000,
                150000,
            ]
        )
    )

    r = Parameter(
        m,
        name="r",
        domain=[t],
        records=np.array(
            [
                0,
                0.0422,
                0.0440,
                0.0450,
                0.0466,
                0.0480,
                0.0482,
                0.0485,
                0.0488,
                0.0491,
                0.0493,
            ]
        ),
        description="spot rates",
    )
    y = Parameter(
        m,
        name="y",
        domain=[i],
        records=np.array(
            [
                0.0501,
                0.0500,
                0.0469,
                0.0426,
                0.0489,
                0.0485,
                0.0392,
                0.0453,
                0.0406,
                0.0386,
            ]
        ),
        description="yield rates",
    )

    # The following are the Present value, Fischer-Weil duration (D^FW)
    # and Convexity (Q_i), for both the bonds and the liabilities:
    # Present value, Fisher & Weil duration, and convexity for
    # the bonds.
    PV = Parameter(
        m, name="PV", domain=[i], description="Present value of assets"
    )
    Dur = Parameter(
        m, name="Dur", domain=[i], description="Duration of assets"
    )
    Conv = Parameter(
        m, name="Conv", domain=[i], description="Convexity of assets"
    )

    # Present value, Fisher & Weil duration, and convexity for
    # the liability.
    PV_Liab = Parameter(
        m, name="PV_Liab", description="Present value of liability"
    )
    Dur_Liab = Parameter(
        m, name="Dur_Liab", description="Duration of liability"
    )
    Conv_Liab = Parameter(
        m, name="Conv_Liab", description="Convexity of liability"
    )

    PV[i] = Sum(t, F[t, i] * gams_math.exp(-r[t] * tau[t]))

    Dur[i] = (1.0 / PV[i]) * Sum(
        t, tau[t] * F[t, i] * gams_math.exp(-r[t] * tau[t])
    )

    Conv[i] = (1.0 / PV[i]) * Sum(
        t, sqr(tau[t]) * F[t, i] * gams_math.exp(-r[t] * tau[t])
    )

    print("PV: \n", PV.records)
    print("Dur: \n", Dur.records)
    print("Conv: \n", Conv.records)

    # Calculate the corresponding amounts for Liabilities. Use its PV as its
    # "price".

    PV_Liab.assignment = Sum(t, Liability[t] * gams_math.exp(-r[t] * tau[t]))

    Dur_Liab.assignment = (1.0 / PV_Liab) * Sum(
        t, tau[t] * Liability[t] * gams_math.exp(-r[t] * tau[t])
    )

    Conv_Liab.assignment = (1.0 / PV_Liab) * Sum(
        t, sqr(tau[t]) * Liability[t] * gams_math.exp(-r[t] * tau[t])
    )

    print("PV_Liab: ", PV_Liab.records.value[0])
    print("Dur_Liab: ", Dur_Liab.records.value[0])
    print("Conv_Liab: ", Conv_Liab.records.value[0])

    # Build a sequence of increasingly sophisticated immunuzation models.

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Holdings of bonds (amount of face value)",
    )
    z = Variable(m, name="z", description="Objective function value")

    # EQUATIONS #
    PresentValueMatch = Equation(
        m,
        name="PresentValueMatch",
        description=(
            "Equation matching the present value of asset and liability"
        ),
    )
    DurationMatch = Equation(
        m,
        name="DurationMatch",
        description="Equation matching the duration of asset and liability",
    )
    ConvexityMatch = Equation(
        m,
        name="ConvexityMatch",
        description="Equation matching the convexity of asset and liability",
    )
    ObjDef = Equation(
        m,
        name="ObjDef",
        description="Objective function definition",
    )

    ObjDef.definition = z == Sum(i, Dur[i] * PV[i] * y[i] * x[i]) / (
        PV_Liab * Dur_Liab
    )

    PresentValueMatch.definition = Sum(i, PV[i] * x[i]) == PV_Liab

    DurationMatch.definition = (
        Sum(i, Dur[i] * PV[i] * x[i]) == PV_Liab * Dur_Liab
    )

    ConvexityMatch.definition = (
        Sum(i, Conv[i] * PV[i] * x[i]) >= PV_Liab * Conv_Liab
    )

    ImmunizationOne = Model(
        m,
        name="ImmunizationOne",
        equations=[ObjDef, PresentValueMatch, DurationMatch],
        problem="LP",
        sense=Sense.MAX,
        objective=z,
    )
    ImmunizationOne.solve()

    Convexity = Parameter(m, name="Convexity")
    Convexity.assignment = (1.0 / PV_Liab) * Sum(i, Conv[i] * PV[i] * x.l[i])
    x_results = []

    x_results.append(x.records.level.tolist())
    print("Convexity: ", Convexity.records.value[0])
    print("Conv_Liab: ", Conv_Liab.records.value[0])

    ImmunizationTwo = Model(
        m,
        name="ImmunizationTwo",
        equations=[ObjDef, PresentValueMatch, DurationMatch, ConvexityMatch],
        problem="LP",
        sense=Sense.MAX,
        objective=z,
    )
    ImmunizationTwo.solve()

    DurationMatch.l.assignment = DurationMatch.l / PV_Liab

    ConvexityMatch.l.assignment = ConvexityMatch.l / PV_Liab

    x_results.append(x.records.level.tolist())
    print("PresentValueMatch: ", PresentValueMatch.records.level[0])
    print("DurationMatch: ", DurationMatch.records.level[0])
    print("ConvexityMatch: ", ConvexityMatch.records.level[0])

    ConvexityObj = Equation(m, name="ConvexityObj")

    ConvexityObj.definition = z == (1.0 / PV_Liab) * Sum(
        i, Conv[i] * PV[i] * x[i]
    )

    ImmunizationThree = Model(
        m,
        name="ImmunizationThree",
        equations=[ConvexityObj, PresentValueMatch, DurationMatch],
        problem="LP",
        sense=Sense.MIN,
        objective=z,
    )
    ImmunizationThree.solve()

    x_results.append(x.records.level.tolist())
    x_results = pd.DataFrame(
        np.array(x_results).T,
        columns=["Model1", "Model2", "Model3"],
        index=Bonds.records.uni,
    )
    print("Objective Function Value: ", z.records.level[0])

    print(x_results)


if __name__ == "__main__":
    main()

tests/integration/models/Immunization.py
GAMS-dev/gamspy
"""
International asset allocation model

InternationalMeanVar.gms:  International asset allocation model.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 3.5
Last modified: Apr 2008.


We use real data for the 10-year period 1990-01-01 to 2000-01-01,

       23 Italian Stock indices
       3 Italian Bond indices (1-3yr, 3-7yr, 5-7yr)
       Italian risk-free rate (3-month cash)

       7 international Govt. bond indices
       5 Regions Stock Indices: (EMU, Eur-ex-emu, PACIF, EMER, NORAM)
       3 risk-free rates (3-mth cash) for EUR, US, JP

       US Corporate Bond Sector Indices (Finance, Energy, Life Ins.)

       Exchange rates, ITL to: (FRF, DEM, ESP, GBP, US, YEN, EUR)
       Also US to EUR.
"""
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    gdx_file = (
        str(Path(__file__).parent.absolute()) + "/InternationalMeanVar.gdx"
    )
    m = Container(delayed_execution=True, load_from=gdx_file)

    # SETS #
    ASSETS = m.getSymbols(["ASSETS"])[0]

    # ALIASES #
    i, j = m.getSymbols(["i", "j"])

    # SUBSETS #
    IT_STOCK, IT_ALL, INT_STOCK, INT_ALL = m.getSymbols(
        ["IT_STOCK", "IT_ALL", "INT_STOCK", "INT_ALL"]
    )

    # PARAMETERS #
    MAX_MU, ExpectedReturns, VarCov, RiskFree = m.getSymbols(
        ["MAX_MU", "MU", "Q", "RiskFreeRt"]
    )

    # Build more symbols
    # SETS #
    ACTIVE = Set(m, name="ACTIVE", domain=[ASSETS])

    a = Alias(m, name="a", alias_with=ACTIVE)
    a1 = Alias(m, name="a1", alias_with=ACTIVE)
    a2 = Alias(m, name="a2", alias_with=ACTIVE)

    # Target return

    # SCALARS #
    MU_TARGET = Parameter(
        m, name="MU_TARGET", description="Target portfolio return"
    )
    MU_STEP = Parameter(m, name="MU_STEP", description="Target return step")

    # Assume we want 20 portfolios in the frontier

    MU_STEP.assignment = MAX_MU / 20

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Holdings of assets",
    )
    PortVariance = Variable(
        m, name="PortVariance", description="Portfolio variance"
    )

    # EQUATIONS #
    ReturnCon = Equation(
        m,
        name="ReturnCon",
        type="regular",
        description="Equation defining the portfolio return constraint",
    )
    VarDef = Equation(
        m,
        name="VarDef",
        type="regular",
        description="Equation defining the portfolio variance",
    )
    NormalCon = Equation(
        m,
        name="NormalCon",
        type="regular",
        description="Equation defining the normalization contraint",
    )

    ReturnCon.definition = Sum(a, ExpectedReturns[a] * x[a]) == MU_TARGET

    VarDef.definition = PortVariance == Sum(
        [a1, a2], x[a1] * VarCov[a1, a2] * x[a2]
    )

    NormalCon.definition = Sum(a, x[a]) == 1

    m.addOptions({"SOLVEOPT": "REPLACE"})

    MeanVar = Model(
        m,
        name="MeanVar",
        equations=[ReturnCon, VarDef, NormalCon],
        problem="nlp",
        sense="MIN",
        objective=PortVariance,
    )

    FrontierHandle = open(
        "InternationalMeanVarFrontier.csv", "w", encoding="UTF-8"
    )

    # Step 1: First solve only for Italian stocks:

    ACTIVE[i] = IT_STOCK[i]
    print("\nStep 1: Italian stock assets\n")

    FrontierHandle.write('"Step 1: Italian stock assets"\n')
    FrontierHandle.write('"Variance","ExpReturn",')

    # Asset labels
    i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
    FrontierHandle.write(",".join(i_recs) + "\n")

    mu = 0
    while round(mu, 7) < round(MAX_MU.toList()[0], 7):
        MU_TARGET.assignment = mu
        MeanVar.solve()
        print("PortVariance: ", round(PortVariance.toValue(), 3))

        FrontierHandle.write(
            f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
        )

        x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
        FrontierHandle.write(",".join(x_recs))

        FrontierHandle.write("\n")

        mu += MU_STEP.toList()[0]

    #
    # Step 2: Now solve for Italian stock and government indices:
    #
    ACTIVE[i] = IT_ALL[i]
    print("\nStep 2: Italian stock and government assets\n")

    FrontierHandle.write('"Step 2: Italian stock and government assets"\n')
    FrontierHandle.write('"Variance","ExpReturn",')

    # Asset labels
    i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
    FrontierHandle.write(",".join(i_recs) + "\n")

    mu = 0
    while round(mu, 7) < round(MAX_MU.toList()[0], 7):
        MU_TARGET.assignment = mu
        MeanVar.solve()
        print("PortVariance: ", round(PortVariance.toValue(), 3))

        FrontierHandle.write(
            f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
        )

        x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
        FrontierHandle.write(",".join(x_recs))

        FrontierHandle.write("\n")

        mu += MU_STEP.toList()[0]

    #
    # Step 3: Italian stock plus international stock indices
    #
    ACTIVE[i] = INT_STOCK[i]
    print("\nStep 3: Italian and international stock indices\n")

    FrontierHandle.write('"Step 3: Italian and international stock indices"\n')
    FrontierHandle.write('"Variance","ExpReturn",')

    # Asset labels
    i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
    FrontierHandle.write(",".join(i_recs) + "\n")

    mu = 0
    while round(mu, 7) < round(MAX_MU.toList()[0], 7):
        MU_TARGET.assignment = mu
        MeanVar.solve()
        print("PortVariance: ", round(PortVariance.toValue(), 3))

        FrontierHandle.write(
            f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
        )

        x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
        FrontierHandle.write(",".join(x_recs))

        FrontierHandle.write("\n")

        mu += MU_STEP.toList()[0]

    #
    # Step 4: Italian stock and government indices, international stock and government
    # indices, plus corporate indices.
    #

    ACTIVE[i] = INT_ALL[i]
    print("\nStep 4: All indices\n")

    FrontierHandle.write('"Step 4: All indices"\n')
    FrontierHandle.write('"Variance","ExpReturn",')

    # Asset labels
    i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
    FrontierHandle.write(",".join(i_recs) + "\n")

    mu = 0
    while round(mu, 7) < round(MAX_MU.toList()[0], 7):
        MU_TARGET.assignment = mu
        MeanVar.solve()
        print("PortVariance: ", round(PortVariance.toValue(), 3))

        FrontierHandle.write(
            f"{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
        )

        x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
        FrontierHandle.write(",".join(x_recs))

        FrontierHandle.write("\n")

        mu += MU_STEP.toList()[0]

    #
    # Step 5: All italian stock indices plus  risk free
    #

    # VARIABLES #
    z = Variable(m, name="z", type="free")
    d_bar = Variable(m, name="d_bar", type="free")

    # EQUATIONS #
    RiskFreeReturnDef = Equation(m, name="RiskFreeReturnDef", type="regular")
    SharpeRatio = Equation(m, name="SharpeRatio", type="regular")

    RiskFreeReturnDef.definition = (
        d_bar == Sum(a, ExpectedReturns[a] * x[a]) - RiskFree
    )

    SharpeRatio.definition = z == d_bar / gams_math.sqrt(PortVariance)

    Sharpe = Model(
        m,
        name="Sharpe",
        equations=[RiskFreeReturnDef, VarDef, NormalCon, SharpeRatio],
        problem="nlp",
        sense="MAX",
        objective=z,
    )

    Sharpe.solve()
    print("\nStep 5: Tangent portfolio\n")
    print("z: ", round(z.toValue(), 3))

    # Write the variance and expected return for the tangent portfolio

    FrontierHandle.write('"Step 5: Tangent portfolio"\n')
    FrontierHandle.write('"Variance","RiskFree","z",')

    # Asset labels
    i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
    FrontierHandle.write(",".join(i_recs) + "\n")

    FrontierHandle.write(
        f"{round(PortVariance.toValue(),4)},{round((d_bar.toValue()+RiskFree.toValue()),4)},{round(z.toValue(),4)},"
    )

    # Write the tangent portfolio.

    x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
    FrontierHandle.write(",".join(x_recs))
    FrontierHandle.write("\n")

    #
    # Step 6: Include the total Italian stock index as a liability
    #
    #
    # Build a model (very similar to the previous one)
    # which attempts to track (synthesize) the Italian total stock index,
    # ITMHIST, using the 23 Italian stock indices and 3 Italian bond indices
    # plus the Italian risk-free asset.
    #
    # This is done by including ITMHIST as an asset but fixing its weight
    # in the portfolio at -1. The 26 other assets then must try to balance
    # out the variance of ITMHIST. In addition, we pursue different levels
    # of expected return (over and above the ITMHIST return).

    # Create a convenient subset containing only the general Italian stock index:

    It_general = Set(
        m, name="It_general", domain=[ASSETS], records=["ITMHIST"]
    )

    # The only constraint which need to be redefined is the
    # normalization constraint. Indeed, it must be se to 0.

    NormalConTrack = Equation(
        m,
        name="NormalConTrack",
        type="regular",
        description=(
            "Equation defining the normalization contraint for tracking"
        ),
    )

    NormalConTrack.definition = Sum(a, x[a]) == 0

    m.addOptions({"SOLVEOPT": "REPLACE"})

    MeanVarTrack = Model(
        m,
        name="MeanVarTrack",
        equations=[ReturnCon, VarDef, NormalConTrack],
        problem="nlp",
        sense="MIN",
        objective=PortVariance,
    )

    x.fx[It_general] = -1

    FrontierHandle.write('"Step 6: Index tracking"\n')
    print("\nStep 6: Index tracking\n")

    ACTIVE[i] = IT_STOCK[i] | It_general[i]

    FrontierHandle.write('"Status","Variance","ExpReturn",')

    # Asset labels
    i_recs = [f'"{i_rec}"' for i_rec in ACTIVE.toList()]
    FrontierHandle.write(",".join(i_recs) + "\n")

    # Re-estimate MU_STEP as MAX_MU is different for the tracking problem
    MAX_MU.assignment = 0.1587
    MU_STEP.assignment = MAX_MU / 20

    mu = 0
    while round(mu, 7) < round(MAX_MU.toList()[0], 7):
        MU_TARGET.assignment = mu
        MeanVarTrack.solve()
        print("PortVariance: ", round(PortVariance.toValue(), 3))

        FrontierHandle.write(
            f"{MeanVarTrack.status},{round(PortVariance.toValue(),4)},{round(MU_TARGET.toValue(),4)},"
        )

        x_recs = [str(round(x_rec, 4)) for x_rec in x.toDict().values()]
        FrontierHandle.write(",".join(x_recs))

        FrontierHandle.write("\n")

        mu += MU_STEP.toList()[0]

    FrontierHandle.close()


if __name__ == "__main__":
    main()

tests/integration/models/InternationalMeanVar.py
GAMS-dev/gamspy
"""
Mean absolute deviation model

MAD.gms: Mean absolute deviation model.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 5.3
Last modified: Apr 2008.
"""
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Smax
from gamspy import Smin
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def main():
    gdx_file = str(Path(__file__).parent.absolute()) + "/WorldIndices.gdx"
    m = Container(delayed_execution=True, load_from=gdx_file)

    # SETS #
    i, l = m.getSymbols(["i", "l"])

    # SCALARS #
    Budget = Parameter(
        m, name="Budget", description="Nominal investment budget"
    )
    MU_TARGET = Parameter(
        m, name="MU_TARGET", description="Target portfolio return"
    )
    MU_STEP = Parameter(m, name="MU_STEP", description="Target return step")
    MIN_MU = Parameter(
        m, name="MIN_MU", description="Minimum return in universe"
    )
    MAX_MU = Parameter(
        m, name="MAX_MU", description="Maximum return in universe"
    )

    Budget.assignment = 100.0

    # PARAMETERS #
    pr = Parameter(
        m, name="pr", domain=[l], description="Scenario probability"
    )
    P = Parameter(m, name="P", domain=[i, l], description="Final values")
    EP = Parameter(
        m, name="EP", domain=[i], description="Expected final values"
    )
    AssetReturns = m.getSymbols(["AssetReturns"])[0]

    pr[l] = 1.0 / Card(l)

    P[i, l] = 1 + AssetReturns[i, l]

    EP[i] = Sum(l, pr[l] * P[i, l])

    MIN_MU.assignment = Smin(i, EP[i])
    MAX_MU.assignment = Smax(i, EP[i])

    # Assume we want 20 portfolios in the frontier

    MU_STEP.assignment = (MAX_MU - MIN_MU) / 20

    print("MAX_MU: ", MAX_MU.records.value.round(3)[0])

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Holdings of assets in monetary units (not proportions)",
    )
    y = Variable(
        m,
        name="y",
        type="positive",
        domain=[l],
        description="Measures of the absolute deviation",
    )
    z = Variable(m, name="z", description="Objective function value")

    # EQUATIONS #
    BudgetCon = Equation(
        m,
        name="BudgetCon",
        type="regular",
        description="Equation defining the budget contraint",
    )
    ReturnCon = Equation(
        m,
        name="ReturnCon",
        type="regular",
        description="Equation defining the portfolio return constraint",
    )
    ObjDef = Equation(
        m,
        name="ObjDef",
        type="regular",
        description="Objective function definition for MAD",
    )
    yPosDef = Equation(
        m,
        name="yPosDef",
        type="regular",
        domain=[l],
        description="Equations defining the positive deviations",
    )
    yNegDef = Equation(
        m,
        name="yNegDef",
        type="regular",
        domain=[l],
        description="Equations defining the negative deviations",
    )

    BudgetCon.definition = Sum(i, x[i]) == Budget

    ReturnCon.definition = Sum(i, EP[i] * x[i]) >= MU_TARGET * Budget

    yPosDef[l] = y[l] >= Sum(i, P[i, l] * x[i]) - Sum(i, EP[i] * x[i])

    yNegDef[l] = y[l] >= Sum(i, EP[i] * x[i]) - Sum(i, P[i, l] * x[i])

    ObjDef.definition = z == Sum(l, pr[l] * y[l])

    MeanAbsoluteDeviation = Model(
        m,
        name="MeanAbsoluteDeviation",
        equations=[BudgetCon, ReturnCon, yPosDef, yNegDef, ObjDef],
        problem="LP",
        sense="MIN",
        objective=z,
    )

    m.addOptions({"SOLVEOPT": "REPLACE"})

    output_csv = '"MAD","Mean"\n'

    mu_target = MIN_MU.records.value[0]
    while mu_target <= MAX_MU.records.value[0]:
        # MU_TARGET = MIN_MU TO MAX_MU BY MU_STEP,
        MU_TARGET.assignment = mu_target

        MeanAbsoluteDeviation.solve()

        output_csv += (
            f"{z.records.level.round(3)[0]},{round(MU_TARGET.records.value[0] * Budget.records.value[0],3)},"
        )
        x_recs = [str(x_rec) for x_rec in x.records.level.round(3).tolist()]
        output_csv += ",".join(x_recs) + "\n"

        mu_target += MU_STEP.records.value[0]

    # Compute variances and covariances
    # for comparison between Mean Variance and Mean Absolute Deviation

    # ALIAS
    i1 = Alias(m, name="i1", alias_with=i)
    i2 = Alias(m, name="i2", alias_with=i)

    # PARAMETERS
    VP = Parameter(m, name="VP", domain=[i, i])

    VP[i, i] = Sum(l, sqr(P[i, l] - EP[i])) / (Card(l) - 1)

    VP[i1, i2].where[Ord(i1) > Ord(i2)] = Sum(
        l, (P[i1, l] - EP[i1]) * (P[i2, l] - EP[i2])
    ) / (Card(l) - 1)

    print("VP: ", VP.records.value.round(3).tolist())

    # EQUATION
    ObjDefMV = Equation(
        m,
        name="ObjDefMV",
        type="regular",
        description="Objective function definition for Mean-Variance",
    )

    ObjDefMV.definition = z == Sum([i1, i2], x[i1] * VP[i1, i2] * x[i2])

    MeanVariance = Model(
        m,
        name="MeanVariance",
        equations=[BudgetCon, ReturnCon, ObjDefMV],
        problem="NLP",
        sense="MIN",
        objective=z,
    )
    MeanVariance.solve()

    output_csv += '"SD","Mean"\n'

    mu_target = MIN_MU.records.value[0]
    while mu_target <= MAX_MU.records.value[0]:
        MU_TARGET.assignment = mu_target

        MeanVariance.solve()
        z.l.assignment = gams_math.sqrt(z.l)

        output_csv += (
            f"{z.records.level.round(3)[0]},{round(MU_TARGET.records.value[0] * Budget.records.value[0],3)},"
        )
        x_recs = [str(x_rec) for x_rec in x.records.level.round(3).tolist()]
        output_csv += ",".join(x_recs) + "\n"

        mu_target += MU_STEP.records.value[0]

    # SCALARS
    lambdaPos = Parameter(
        m,
        name="lambdaPos",
        description="Weight attached to positive deviations",
    )
    lambdaNeg = Parameter(
        m,
        name="lambdaNeg",
        description="Weight attached to negative deviations",
    )

    lambdaPos.assignment = 0.5
    lambdaNeg.assignment = 0.5

    # EQUATIONS
    yPosWeightDef = Equation(
        m,
        name="yPosWeightDef",
        type="regular",
        domain=[l],
        description=(
            "Equations defining the positive deviations with weight attached"
        ),
    )
    yNegWeightDef = Equation(
        m,
        name="yNegWeightDef",
        type="regular",
        domain=[l],
        description=(
            "Equations defining the positive deviations with weight attached"
        ),
    )

    yPosWeightDef[l] = y[l] >= lambdaPos * (
        Sum(i, P[i, l] * x[i]) - Sum(i, EP[i] * x[i])
    )

    yNegWeightDef[l] = y[l] >= lambdaNeg * (
        Sum(i, EP[i] * x[i]) - Sum(i, P[i, l] * x[i])
    )

    MeanAbsoluteDeviationWeighted = Model(
        m,
        name="MeanAbsoluteDeviationWeighted",
        equations=[BudgetCon, ReturnCon, yPosWeightDef, yNegWeightDef, ObjDef],
        problem="LP",
        sense="MIN",
        objective=z,
    )

    output_csv += '"MADWeighted","Mean"\n'

    mu_target = MIN_MU.records.value[0]
    while mu_target <= MAX_MU.records.value[0]:
        MU_TARGET.assignment = mu_target

        MeanAbsoluteDeviationWeighted.solve()

        output_csv += (
            f"{z.records.level.round(3)[0]},{round(MU_TARGET.records.value[0] * Budget.records.value[0],3)},"
        )
        x_recs = [str(x_rec) for x_rec in x.records.level.round(3).tolist()]
        output_csv += ",".join(x_recs) + "\n"

        mu_target += MU_STEP.records.value[0]

    lambdaPos.assignment = 0.2
    lambdaNeg.assignment = 0.8

    output_csv += '"MADWeighted","Mean"\n'

    mu_target = MIN_MU.records.value[0]
    while mu_target <= MAX_MU.records.value[0]:
        MU_TARGET.assignment = mu_target

        MeanAbsoluteDeviationWeighted.solve()

        output_csv += (
            f"{z.records.level.round(3)[0]},{round(MU_TARGET.records.value[0] * Budget.records.value[0],3)},"
        )
        x_recs = [str(x_rec) for x_rec in x.records.level.round(3).tolist()]
        output_csv += ",".join(x_recs) + "\n"

        mu_target += MU_STEP.records.value[0]

    FrontierHandle = open("MADvsMV.csv", "w", encoding="UTF-8")
    FrontierHandle.write(output_csv)
    FrontierHandle.close()

    # Note that, the last two models will yield the same portfolios! See PFO Section 5.2.2.


if __name__ == "__main__":
    main()

tests/integration/models/MAD.py
GAMS-dev/gamspy
"""
*** Multi-objective Economic-Environmental Load Dispatch

For more details please refer to Chapter 3 (Gcode3.4), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
Email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # data records table
    cols = ["a", "b", "c", "d", "e", "f", "Pmin", "Pmax"]
    inds = [f"g{i}" for i in range(1, 6)]
    data = [
        [3.0, 20.0, 100.0, 2.0, -5.0, 3.0, 28, 206],
        [4.05, 18.07, 98.87, 3.82, -4.24, 6.09, 90, 284],
        [4.05, 15.55, 104.26, 5.01, -2.15, 5.69, 68, 189],
        [3.99, 19.21, 107.21, 1.1, -3.99, 6.2, 76, 266],
        [3.88, 26.18, 95.31, 3.55, -6.88, 5.57, 19, 53],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return data_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    gen = Set(m, name="gen", records=[f"g{i}" for i in range(1, 6)])
    counter = Set(m, name="counter", records=[f"c{i}" for i in range(1, 12)])

    # PARAMETERS
    report = Parameter(m, name="report", domain=["*"])
    rep = Parameter(m, name="rep", domain=[counter, "*"])
    rep2 = Parameter(m, name="rep2", domain=[counter, gen])
    data = Parameter(m, name="data", domain=[gen, "*"], records=data_records())

    # SCALARS
    load = Parameter(m, name="load", records=400)
    Elim = Parameter(m, name="Elim")

    # VARIABLES #
    P = Variable(m, name="P", domain=[gen])
    TE = Variable(m, name="TE")
    TC = Variable(m, name="TC")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")

    eq1.definition = TC == Sum(
        gen,
        data[gen, "a"] * P[gen] * P[gen]
        + data[gen, "b"] * P[gen]
        + data[gen, "c"],
    )
    eq2.definition = Sum(gen, P[gen]) >= load
    eq3.definition = TE == Sum(
        gen,
        data[gen, "d"] * P[gen] * P[gen]
        + data[gen, "e"] * P[gen]
        + data[gen, "f"],
    )

    P.lo[gen] = data[gen, "Pmin"]
    P.up[gen] = data[gen, "Pmax"]

    END1 = Model(
        m,
        name="END1",
        equations=[eq1, eq2, eq3],
        problem="qcp",
        sense="min",
        objective=TC,
    )
    END1.solve()
    report["maxTE"] = TE.l
    report["minTC"] = TC.l

    END2 = Model(
        m,
        name="END2",
        equations=[eq1, eq2, eq3],
        problem="qcp",
        sense="min",
        objective=TE,
    )
    END2.solve()
    report["maxTC"] = TC.l
    report["minTE"] = TE.l

    for idx, cc in enumerate(counter.toList()):
        Elim.assignment = (report["maxTE"] - report["minTE"]) * (idx) / (
            Card(counter) - 1
        ) + report["minTE"]
        TE.up.assignment = Elim
        END1.solve()
        rep[cc, "TC"] = TC.l
        rep[cc, "TE"] = TE.l
        rep2[cc, gen] = P.l[gen]

    print("rep  \n", rep.pivot().round(4))
    print("rep2  \n", rep2.pivot().round(4))

    import math

    assert math.isclose(END1.objective_value, 131454.0003, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/MOED.py
GAMS-dev/gamspy
"""
Mean-variance model with diversification constraints

MeanVarMip.gms:  Mean-variance model with diversification constraints.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 3.4
Last modified: Apr 2008.
"""
from pathlib import Path

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Read from MeanVarMip.gdx the data needed to run the mean-variance model
    m.read(
        str(Path(__file__).parent.absolute()) + "/MeanVarMip.gdx",
        [
            "assets",
            "SUBSET",
            "s1",
            "s2",
            "MeanRiskFreeReturn",
            "VarCov",
            "ExpectedReturns",
        ],
    )

    # SETS #
    Assets = m.getSymbols(["subset"])[0]

    # ALIASES #
    i = Alias(m, name="i", alias_with=Assets)
    j = Alias(m, name="j", alias_with=Assets)

    # PARAMETERS #
    ExpectedReturns, VarCov = m.getSymbols(["ExpectedReturns", "VarCov"])

    # Risk attitude: 0 is risk-neutral, 1 is very risk-averse.
    StockMax = Parameter(
        m, name="StockMax", records=3, description="Maximum number of stocks"
    )
    lamda = Parameter(m, name="lamda", description="Risk attitude")
    xlow = Parameter(
        m,
        name="xlow",
        domain=[i],
        description="lower bound for active variables",
    )

    # VARIABLES #
    x = Variable(m, name="x", domain=[i], description="Holdings of assets")
    Y = Variable(
        m,
        name="Y",
        type="binary",
        domain=[i],
        description="Indicator variable for assets included in the portfolio",
    )
    PortVariance = Variable(
        m, name="PortVariance", description="Portfolio variance"
    )
    PortReturn = Variable(m, name="PortReturn", description="Portfolio return")
    z = Variable(m, name="z", description="Objective function value")

    # In case short sales are allowed these bounds must be set properly.
    xlow[i] = 0.0
    x.up[i] = 1.0

    # EQUATIONS #
    ReturnDef = Equation(
        m,
        name="ReturnDef",
        type="regular",
        description="Equation defining the portfolio return",
    )
    VarDef = Equation(
        m,
        name="VarDef",
        type="regular",
        description="Equation defining the portfolio variance",
    )
    NormalCon = Equation(
        m,
        name="NormalCon",
        type="regular",
        description="Equation defining the normalization contraint",
    )
    LimitCon = Equation(
        m,
        name="LimitCon",
        type="regular",
        description="Constraint defining the maximum number of assets allowed",
    )
    UpBounds = Equation(
        m,
        name="UpBounds",
        type="regular",
        domain=[i],
        description="Upper bounds for each variable",
    )
    LoBounds = Equation(
        m,
        name="LoBounds",
        type="regular",
        domain=[i],
        description="Lower bounds for each variable",
    )
    ObjDef = Equation(
        m,
        name="ObjDef",
        type="regular",
        description="Objective function definition",
    )

    ReturnDef.definition = PortReturn == Sum(i, ExpectedReturns[i] * x[i])

    VarDef.definition = PortVariance == Sum([i, j], x[i] * VarCov[i, j] * x[j])

    LimitCon.definition = Sum(i, Y[i]) <= StockMax

    UpBounds[i] = x[i] <= x.up[i] * Y[i]

    LoBounds[i] = x[i] >= xlow[i] * Y[i]

    NormalCon.definition = Sum(i, x[i]) == 1

    ObjDef.definition = z == (1 - lamda) * PortReturn - lamda * PortVariance

    MeanVarMip = Model(
        m,
        name="MeanVarMip",
        equations=[
            ReturnDef,
            VarDef,
            LimitCon,
            UpBounds,
            LoBounds,
            NormalCon,
            ObjDef,
        ],
        problem="MINLP",
        sense="MAX",
        objective=z,
    )

    m.addOptions({"MINLP": "SBB", "optcr": 0})

    MeanVarianceMIP = '"Lambda","z","Variance","ExpReturn",'
    i_recs = [f'"{i_rec}"' for i_rec in i.records.ASSETS.tolist()]
    MeanVarianceMIP += ",".join(i_recs)
    MeanVarianceMIP += "\n"

    lamda_loop = 0
    while True:
        if lamda_loop > 1:
            break
        lamda.assignment = lamda_loop
        MeanVarMip.solve()
        MeanVarianceMIP += f"{round(lamda_loop,1)},{round(z.records.level[0],4)},{round(PortVariance.records.level[0],4)},{round(PortReturn.records.level[0],4)},"
        x_recs = [str(round(x_rec, 4)) for x_rec in x.records.level.tolist()]
        MeanVarianceMIP += ",".join(x_recs)
        MeanVarianceMIP += "\n"
        lamda_loop += 0.1

    FrontierHandle = open("MeanVarianceMIP.csv", "w", encoding="UTF-8")
    FrontierHandle.write(MeanVarianceMIP)
    FrontierHandle.close()

    # ***** Transaction Cost *****
    # In this section the MeanVar.gms model is modified by imposing transaction
    # costs. We consider a more realistic setting with fixed and proportional costs.

    FlatCost = Parameter(
        m, name="FlatCost", records=0.001, description="Flat transaction cost"
    )
    PropCost = Parameter(
        m,
        name="PropCost",
        records=0.005,
        description="Proportional transaction cost",
    )

    x_0 = Variable(
        m,
        name="x_0",
        type="positive",
        domain=[i],
        description="Holdings for the flat cost regime",
    )
    x_1 = Variable(
        m,
        name="x_1",
        type="positive",
        domain=[i],
        description="Holdings for the linear cost regime",
    )

    # Amount at which is possible to make transactions at the flat fee.

    x_0.up[i] = 0.1

    HoldingCon = Equation(
        m,
        name="HoldingCon",
        type="regular",
        domain=[i],
        description="Constraint defining the holdings",
    )
    ReturnDefWithCost = Equation(
        m,
        name="ReturnDefWithCost",
        type="regular",
        description="Equation defining the portfolio return with cost",
    )
    FlatCostBounds = Equation(
        m,
        name="FlatCostBounds",
        type="regular",
        domain=[i],
        description="Upper bounds for flat transaction fee",
    )
    LinCostBounds = Equation(
        m,
        name="LinCostBounds",
        type="regular",
        domain=[i],
        description="Upper bonds for linear transaction fee",
    )

    HoldingCon[i] = x[i] == x_0[i] + x_1[i]

    ReturnDefWithCost.definition = PortReturn == Sum(
        i, (ExpectedReturns[i] * x_0[i] - FlatCost * Y[i])
    ) + Sum(i, (ExpectedReturns[i] - PropCost) * x_1[i])

    FlatCostBounds[i] = x_0[i] <= x_0.up[i] * Y[i]

    LinCostBounds[i] = x_1[i] <= Y[i]

    MeanVarWithCost = Model(
        m,
        name="MeanVarWithCost",
        equations=[
            ReturnDefWithCost,
            VarDef,
            HoldingCon,
            NormalCon,
            FlatCostBounds,
            LinCostBounds,
            ObjDef,
        ],
        problem="MINLP",
        sense="MAX",
        objective=z,
    )

    m.addOptions({"MINLP": "SBB", "optcr": 0})

    MeanVarianceWithCost = '"Lambda","z","Variance","ExpReturn",'
    MeanVarianceWithCost += ",".join(i_recs) + ","
    MeanVarianceWithCost += ",".join(i_recs)
    MeanVarianceWithCost += "\n"

    lamda_loop = 0
    while True:
        if lamda_loop > 1:
            break
        lamda.assignment = lamda_loop
        MeanVarWithCost.solve()
        MeanVarianceWithCost += f"{round(lamda_loop,1)},{round(z.records.level[0],4)},{round(PortVariance.records.level[0],4)},{round(PortReturn.records.level[0],4)},"
        x0_recs = [
            str(round(x_rec, 4)) for x_rec in x_0.records.level.tolist()
        ]
        x1_recs = [
            str(round(x_rec, 4)) for x_rec in x_1.records.level.tolist()
        ]
        MeanVarianceWithCost += ",".join(x0_recs) + ","
        MeanVarianceWithCost += ",".join(x1_recs) + "\n"
        lamda_loop += 0.1

    FrontierHandleTwo = open("MeanVarianceWithCost.csv", "w", encoding="UTF-8")
    FrontierHandleTwo.write(MeanVarianceWithCost)
    FrontierHandleTwo.close()

    # ***** Portfolio Revision *****
    # In this section the MeanVar.gms model is modified by imposing zero-or-range
    # variable to cope with portfolio revision.

    Bound = Set(m, name="Bound", records=["Lower", "Upper"])

    BuyLimits = Parameter(m, name="BuyLimits", domain=[Bound, i])
    SellLimits = Parameter(m, name="SellLimits", domain=[Bound, i])
    InitHold = Parameter(
        m, name="InitHold", domain=[i], description="Current holdings"
    )

    # We set the curret holding to the optimal unconstrained mean-variance portfolio
    # with lamda = 0.5

    InitHold["Cash_EU"] = 0.3686
    InitHold["YRS_1_3"] = 0.3597
    InitHold["EMU"] = 0.0
    InitHold["EU_EX"] = 0.0
    InitHold["PACIFIC"] = 0.0
    InitHold["EMERGT"] = 0.0591
    InitHold["NOR_AM"] = 0.2126
    InitHold["ITMHIST"] = 0.0

    BuyLimits["Lower", i] = InitHold[i] * 0.9
    BuyLimits["Upper", i] = InitHold[i] * 1.10

    SellLimits["Lower", i] = InitHold[i] * 0.75
    SellLimits["Upper", i] = InitHold[i] * 1.25

    buy = Variable(
        m,
        name="buy",
        type="positive",
        domain=[i],
        description="Amount to be purchased",
    )
    sell = Variable(
        m,
        name="sell",
        type="positive",
        domain=[i],
        description="Amount to be sold",
    )
    Yb = Variable(
        m,
        name="Yb",
        type="binary",
        domain=[i],
        description="Indicator variable for assets to be purchased",
    )
    Ys = Variable(
        m,
        name="Ys",
        type="binary",
        domain=[i],
        description="Indicator variable for assets to be sold",
    )

    BuyTurnover = Equation(m, name="BuyTurnover", type="regular")
    LoBuyLimits = Equation(m, name="LoBuyLimits", type="regular", domain=[i])
    UpBuyLimits = Equation(m, name="UpBuyLimits", type="regular", domain=[i])
    UpSellLimits = Equation(m, name="UpSellLimits", type="regular", domain=[i])
    LoSellLimits = Equation(m, name="LoSellLimits", type="regular", domain=[i])
    BinBuyLimits = Equation(m, name="BinBuyLimits", type="regular", domain=[i])
    BinSellLimits = Equation(
        m, name="BinSellLimits", type="regular", domain=[i]
    )
    InventoryCon = Equation(
        m,
        name="InventoryCon",
        type="regular",
        domain=[i],
        description="Inventory constraints",
    )

    InventoryCon[i] = x[i] - buy[i] + sell[i] == InitHold[i]

    UpBuyLimits[i] = InitHold[i] + buy[i] <= BuyLimits["Upper", i]

    LoBuyLimits[i] = InitHold[i] + buy[i] >= BuyLimits["Lower", i]

    UpSellLimits[i] = InitHold[i] - sell[i] <= SellLimits["Upper", i]

    LoSellLimits[i] = InitHold[i] - sell[i] >= SellLimits["Lower", i]

    BinBuyLimits[i] = buy[i] <= Yb[i]

    BinSellLimits[i] = sell[i] <= Ys[i]

    BuyTurnover.definition = Sum(i, buy[i]) <= 0.05

    MeanVarRevision = Model(
        m,
        name="MeanVarRevision",
        equations=[
            NormalCon,
            HoldingCon,
            InventoryCon,
            ReturnDef,
            UpBuyLimits,
            LoBuyLimits,
            UpSellLimits,
            LoSellLimits,
            BinBuyLimits,
            BinSellLimits,
            BuyTurnover,
            VarDef,
            ObjDef,
        ],
        problem="MINLP",
        sense="MAX",
        objective=z,
    )

    m.addOptions({"MINLP": "SBB", "optcr": 0})

    MeanVarianceRevision = (
        '"Model status","Lambda","z","Variance","ExpReturn",'
    )

    MeanVarianceRevision += ",".join(i_recs) + ","
    MeanVarianceRevision += ",".join(i_recs) + ","
    MeanVarianceRevision += ",".join(i_recs) + "\n"

    lamda_loop = 0
    while True:
        if lamda_loop > 1:
            break
        lamda.assignment = lamda_loop
        MeanVarRevision.solve()
        MeanVarianceRevision += f"{MeanVarRevision.status},{round(lamda_loop,1)},{round(z.records.level[0],4)},{round(PortVariance.records.level[0],4)},{round(PortReturn.records.level[0],4)},"
        x_recs = [str(round(x_rec, 4)) for x_rec in x.records.level.tolist()]
        buy_recs = [
            str(round(x_rec, 4)) for x_rec in buy.records.level.tolist()
        ]
        sell_recs = [
            str(round(x_rec, 4)) for x_rec in sell.records.level.tolist()
        ]
        MeanVarianceRevision += ",".join(x_recs) + ","
        MeanVarianceRevision += ",".join(buy_recs) + ","
        MeanVarianceRevision += ",".join(sell_recs) + "\n"
        lamda_loop += 0.1

    FrontierHandleThree = open(
        "MeanVarianceRevision.csv", "w", encoding="UTF-8"
    )
    FrontierHandleThree.write(MeanVarianceRevision)
    FrontierHandleThree.close()


if __name__ == "__main__":
    main()

tests/integration/models/MeanVarMip.py
GAMS-dev/gamspy
"""
*** Multi-period AC-OPF for IEEE 24-bus network considering wind and load shedding

For more details please refer to Chapter 6 (Gcode6.7), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: NLP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import math

import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # GenD records table
    cols = ["pmax", "pmin", "b", "Qmax", "Qmin", "Vg", "RU", "RD"]
    inds = ["1", "2", "7", "13", "15", "16", "18", "21", "22", "23"]
    data = [
        [152, 30.4, 13.32, 192, -50, 1.035, 21, 21],
        [152, 30.4, 13.32, 192, -50, 1.035, 21, 21],
        [350, 75.0, 20.7, 300, 0, 1.025, 43, 43],
        [591, 206.85, 20.93, 591, 0, 1.02, 31, 31],
        [215, 66.25, 21.0, 215, -100, 1.014, 31, 31],
        [155, 54.25, 10.52, 155, -50, 1.017, 31, 31],
        [400, 100.0, 5.47, 400, -50, 1.05, 70, 70],
        [400, 100.0, 5.47, 400, -50, 1.05, 70, 70],
        [300, 0.0, 0.0, 300, -60, 1.05, 53, 53],
        [360, 248.5, 10.52, 310, -125, 1.05, 31, 31],
    ]
    GenD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # BD records table
    cols = ["Pd", "Qd"]
    inds = [str(ii) for ii in range(1, 25)]
    data = [
        [108, 22],
        [97, 20],
        [180, 37],
        [74, 15],
        [71, 14],
        [136, 28],
        [125, 25],
        [171, 35],
        [175, 36],
        [195, 40],
        [0, 0],
        [0, 0],
        [265, 54],
        [194, 39],
        [317, 64],
        [100, 20],
        [0, 0],
        [333, 68],
        [181, 37],
        [128, 26],
        [0, 0],
        [0, 0],
        [0, 0],
        [0, 0],
    ]
    BD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # LN records table
    cols = ["r", "x", "b", "limit"]
    inds = [
        ("1", "2"),
        ("1", "3"),
        ("1", "5"),
        ("2", "4"),
        ("2", "6"),
        ("3", "9"),
        ("3", "24"),
        ("4", "9"),
        ("5", "10"),
        ("6", "10"),
        ("7", "8"),
        ("8", "9"),
        ("8", "10"),
        ("9", "11"),
        ("9", "12"),
        ("10", "11"),
        ("10", "12"),
        ("11", "13"),
        ("11", "14"),
        ("12", "13"),
        ("12", "23"),
        ("13", "23"),
        ("14", "16"),
        ("15", "16"),
        ("15", "21"),
        ("15", "24"),
        ("16", "17"),
        ("16", "19"),
        ("17", "18"),
        ("17", "22"),
        ("18", "21"),
        ("19", "20"),
        ("20", "23"),
        ("21", "22"),
    ]
    data = [
        [0.0026, 0.0139, 0.4611, 175],
        [0.0546, 0.2112, 0.0572, 175],
        [0.0218, 0.0845, 0.0229, 175],
        [0.0328, 0.1267, 0.0343, 175],
        [0.0497, 0.192, 0.052, 175],
        [0.0308, 0.119, 0.0322, 175],
        [0.0023, 0.0839, 0.0, 400],
        [0.0268, 0.1037, 0.0281, 175],
        [0.0228, 0.0883, 0.0239, 175],
        [0.0139, 0.0605, 2.459, 175],
        [0.0159, 0.0614, 0.0166, 175],
        [0.0427, 0.1651, 0.0447, 175],
        [0.0427, 0.1651, 0.0447, 175],
        [0.0023, 0.0839, 0.0, 400],
        [0.0023, 0.0839, 0.0, 400],
        [0.0023, 0.0839, 0.0, 400],
        [0.0023, 0.0839, 0.0, 400],
        [0.0061, 0.0476, 0.0999, 500],
        [0.0054, 0.0418, 0.0879, 500],
        [0.0061, 0.0476, 0.0999, 500],
        [0.0124, 0.0966, 0.203, 500],
        [0.0111, 0.0865, 0.1818, 500],
        [0.005, 0.0389, 0.0818, 500],
        [0.0022, 0.0173, 0.0364, 500],
        [0.00315, 0.0245, 0.206, 1000],
        [0.0067, 0.0519, 0.1091, 500],
        [0.0033, 0.0259, 0.0545, 500],
        [0.003, 0.0231, 0.0485, 500],
        [0.0018, 0.0144, 0.0303, 500],
        [0.0135, 0.1053, 0.2212, 500],
        [0.00165, 0.01295, 0.109, 1000],
        [0.00255, 0.0198, 0.1666, 1000],
        [0.0014, 0.0108, 0.091, 1000],
        [0.0087, 0.0678, 0.1424, 500],
    ]
    inds = pd.MultiIndex.from_tuples(inds, names=["Index1", "Index2"])
    LN_recs = pd.DataFrame(data, columns=cols, index=inds)
    LN_recs.reset_index(inplace=True)
    LN_recs = LN_recs.melt(
        id_vars=["Index1", "Index2"], value_vars=["r", "x", "b", "limit"]
    )

    # WD records table
    cols = ["w", "d"]
    inds = [f"t{tt}" for tt in range(1, 25)]
    data = [
        [0.0786666666666667, 0.684511335492475],
        [0.0866666666666667, 0.644122690036197],
        [0.117333333333333, 0.6130691560297],
        [0.258666666666667, 0.599733282530006],
        [0.361333333333333, 0.588874071251667],
        [0.566666666666667, 0.5980186702229],
        [0.650666666666667, 0.626786054486569],
        [0.566666666666667, 0.651743189178891],
        [0.484, 0.706039245570585],
        [0.548, 0.787007048961707],
        [0.757333333333333, 0.839016955610593],
        [0.710666666666667, 0.852733854067441],
        [0.870666666666667, 0.870642027052772],
        [0.932, 0.834254143646409],
        [0.966666666666667, 0.816536483139646],
        [1.0, 0.819394170318156],
        [0.869333333333333, 0.874071251666984],
        [0.665333333333333, 1.0],
        [0.656, 0.983615926843208],
        [0.561333333333333, 0.936368832158506],
        [0.565333333333333, 0.887597637645266],
        [0.556, 0.809297008954087],
        [0.724, 0.74585635359116],
        [0.84, 0.733473042484283],
    ]
    WD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return GenD_recs, BD_recs, LN_recs, WD_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    i = Set(
        m,
        name="i",
        records=[str(ii) for ii in range(1, 25)],
        description="network buses",
    )
    slack = Set(m, name="slack", domain=[i], records=[13])
    t = Set(m, name="t", records=[f"t{tt}" for tt in range(1, 25)])

    # ALIAS #
    j = Alias(m, name="j", alias_with=i)

    # SCALARS #
    Sbase = Parameter(m, name="Sbase", records=100)

    # PARAMETERS
    GenD = Parameter(
        m,
        name="GenD",
        domain=[i, "*"],
        records=data_records()[0],
        description="generating units characteristics",
    )
    BD = Parameter(
        m,
        name="BD",
        domain=[i, "*"],
        records=data_records()[1],
        description="demands of each bus in MW",
    )
    LN = Parameter(
        m,
        name="LN",
        domain=[i, j, "*"],
        records=data_records()[2],
        description="network technical characteristics",
    )
    WD = Parameter(m, name="WD", domain=[t, "*"], records=data_records()[3])
    Wcap = Parameter(
        m,
        name="Wcap",
        domain=[i],
        records=[("8", 200), ("19", 150), ("21", 100)],
    )
    cx = Parameter(m, name="cx", domain=[i, j])

    LN[i, j, "x"].where[LN[i, j, "x"] == 0] = LN[j, i, "x"]
    LN[i, j, "r"].where[LN[i, j, "r"] == 0] = LN[j, i, "r"]
    LN[i, j, "b"].where[LN[i, j, "b"] == 0] = LN[j, i, "b"]
    LN[i, j, "Limit"].where[LN[i, j, "Limit"] == 0] = LN[j, i, "Limit"]
    LN[i, j, "bij"].where[LN[i, j, "Limit"]] = 1 / LN[i, j, "x"]
    LN[i, j, "z"].where[LN[i, j, "Limit"]] = gams_math.sqrt(
        sqr(LN[i, j, "x"]) + sqr(LN[i, j, "r"])
    )
    LN[j, i, "z"].where[LN[i, j, "z"] == 0] = LN[i, j, "z"]
    LN[i, j, "th"].where[
        (LN[i, j, "Limit"]) & (LN[i, j, "x"]) & (LN[i, j, "r"])
    ] = gams_math.atan(LN[i, j, "x"] / (LN[i, j, "r"]))
    LN[i, j, "th"].where[
        (LN[i, j, "Limit"]) & (LN[i, j, "x"]) & (LN[i, j, "r"] == 0)
    ] = (math.pi / 2)
    LN[i, j, "th"].where[
        (LN[i, j, "Limit"]) & (LN[i, j, "r"]) & (LN[i, j, "x"] == 0)
    ] = 0
    LN[j, i, "th"].where[LN[i, j, "Limit"]] = LN[i, j, "th"]

    cx[i, j].where[(LN[i, j, "limit"]) & (LN[j, i, "limit"])] = 1
    cx[i, j].where[cx[j, i]] = 1

    # VARIABLES #
    OF = Variable(m, name="OF")
    Pij = Variable(m, name="Pij", domain=[i, j, t])
    Qij = Variable(m, name="Qij", domain=[i, j, t])
    Pg = Variable(m, name="Pg", domain=[i, t])
    Qg = Variable(m, name="Qg", domain=[i, t])
    Va = Variable(m, name="Va", domain=[i, t])
    V = Variable(m, name="V", domain=[i, t])
    Pw = Variable(m, name="Pw", domain=[i, t])

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular", domain=[i, j, t])
    eq2 = Equation(m, name="eq2", type="regular", domain=[i, j, t])
    eq3 = Equation(m, name="eq3", type="regular", domain=[i, t])
    eq4 = Equation(m, name="eq4", type="regular", domain=[i, t])
    eq5 = Equation(m, name="eq5", type="regular")
    eq6 = Equation(m, name="eq6", type="regular", domain=[i, t])
    eq7 = Equation(m, name="eq7", type="regular", domain=[i, t])

    eq1[i, j, t].where[cx[i, j]] = (
        Pij[i, j, t]
        == (
            V[i, t] * V[i, t] * gams_math.cos(LN[j, i, "th"])
            - V[i, t]
            * V[j, t]
            * gams_math.cos(Va[i, t] - Va[j, t] + LN[j, i, "th"])
        )
        / LN[j, i, "z"]
    )

    eq2[i, j, t].where[cx[i, j]] = (
        Qij[i, j, t]
        == (
            V[i, t] * V[i, t] * gams_math.sin(LN[j, i, "th"])
            - V[i, t]
            * V[j, t]
            * gams_math.sin(Va[i, t] - Va[j, t] + LN[j, i, "th"])
        )
        / LN[j, i, "z"]
        - LN[j, i, "b"] * V[i, t] * V[i, t] / 2
    )

    eq3[i, t] = Pw[i, t].where[Wcap[i]] + Pg[i, t].where[GenD[i, "Pmax"]] - WD[
        t, "d"
    ] * BD[i, "pd"] / Sbase == Sum(j.where[cx[j, i]], Pij[i, j, t])

    eq4[i, t] = Qg[i, t].where[GenD[i, "Qmax"]] - WD[t, "d"] * BD[
        i, "qd"
    ] / Sbase == Sum(j.where[cx[j, i]], Qij[i, j, t])

    eq5.definition = OF >= Sum(
        [i, t], Pg[i, t] * GenD[i, "b"] * Sbase.where[GenD[i, "Pmax"]]
    )

    eq6[i, t].where[(GenD[i, "Pmax"]) & (Ord(t) > 1)] = (
        Pg[i, t] - Pg[i, t.lag(1)] <= GenD[i, "RU"] / Sbase
    )

    eq7[i, t].where[(GenD[i, "Pmax"]) & (Ord(t) < Card(t))] = (
        Pg[i, t] - Pg[i, t.lead(1)] <= GenD[i, "RD"] / Sbase
    )

    loadflow = Model(
        m,
        name="loadflow",
        equations=[eq1, eq2, eq3, eq4, eq5, eq6, eq7],
        problem="nlp",
        sense="min",
        objective=OF,
    )

    Pg.lo[i, t] = GenD[i, "Pmin"] / Sbase
    Pg.up[i, t] = GenD[i, "Pmax"] / Sbase
    Qg.lo[i, t] = GenD[i, "Qmin"] / Sbase
    Qg.up[i, t] = GenD[i, "Qmax"] / Sbase

    Va.up[i, t] = math.pi / 2
    Va.lo[i, t] = -math.pi / 2
    Va.l[i, t] = 0
    Va.fx[slack, t] = 0

    Pij.up[i, j, t].where[cx[i, j]] = 1 * LN[i, j, "Limit"] / Sbase
    Pij.lo[i, j, t].where[cx[i, j]] = -1 * LN[i, j, "Limit"] / Sbase
    Qij.up[i, j, t].where[cx[i, j]] = 1 * LN[i, j, "Limit"] / Sbase
    Qij.lo[i, j, t].where[cx[i, j]] = -1 * LN[i, j, "Limit"] / Sbase

    V.lo[i, t] = 0.9
    V.up[i, t] = 1.1
    V.l[i, t] = 1
    Pw.up[i, t] = WD[t, "w"] * Wcap[i] / Sbase
    Pw.lo[i, t] = 0

    loadflow.solve()

    # Reporting Parameters
    report = Parameter(m, name="report", domain=[t, i, "*"])
    report2 = Parameter(m, name="report2", domain=[i, t])
    report3 = Parameter(m, name="report3", domain=[i, t])

    report[t, i, "V"] = V.l[i, t]
    report[t, i, "Angle"] = Va.l[i, t]
    report[t, i, "Pg"] = Pg.l[i, t] * Sbase
    report[t, i, "Gg"] = Qg.l[i, t] * Sbase
    report[t, i, "LMP_P"] = eq3.m[i, t] / Sbase
    report[t, i, "LMP_Q"] = eq4.m[i, t] / Sbase
    report2[i, t] = Pg.l[i, t] * Sbase
    report3[i, t] = Qg.l[i, t] * Sbase
    print("report  \n", report.pivot().round(4))

    writer = pd.ExcelWriter("results.xlsx", engine="openpyxl")

    report.pivot().round(4).to_excel(writer, sheet_name="classic")
    report2.pivot().round(4).to_excel(writer, sheet_name="classic2")
    report3.pivot().round(4).to_excel(writer, sheet_name="classic3")

    writer.close()


if __name__ == "__main__":
    main()

tests/integration/models/MultiperiodACOPF24bus.py
GAMS-dev/gamspy
"""
*** Optimal power flow for a simple two-bus system

For more details please refer to Chapter 6 (Gcode6.1), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # data records table
    cols = ["a", "b", "c", "Pmin", "Pmax"]
    inds = ["G1", "G2"]
    data = [
        [3.0, 20.0, 100.0, 28, 206],
        [4.05, 18.07, 98.87, 90, 284],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))
    return data_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    gen = Set(m, name="gen", records=["g1", "g2"])
    bus = Set(m, name="bus", records=["1", "2"])

    # SCALARS #
    L2 = Parameter(m, name="L2", records=400)
    X12 = Parameter(m, name="X12", records=0.2)
    Sbase = Parameter(m, name="Sbase", records=100)
    P12_max = Parameter(m, name="P12_max", records=1.5)

    # DATA PARAMETER #
    data = Parameter(m, name="data", domain=[gen, "*"], records=data_records())

    # VARIABLES #
    P = Variable(m, name="P", domain=[gen])
    OF = Variable(m, name="OF")
    delta = Variable(m, name="delta", domain=[bus])
    P12 = Variable(m, name="P12")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")
    eq4 = Equation(m, name="eq4", type="regular")

    eq1.definition = OF == Sum(
        gen,
        data[gen, "a"] * P[gen] * P[gen]
        + data[gen, "b"] * P[gen]
        + data[gen, "c"],
    )
    eq2.definition = P["G1"] == P12
    eq3.definition = P["G2"] + P12 == L2 / Sbase
    eq4.definition = P12 == (delta["1"] - delta["2"]) / X12

    P.lo[gen] = data[gen, "Pmin"] / Sbase
    P.up[gen] = data[gen, "Pmax"] / Sbase
    P12.lo.assignment = -P12_max
    P12.up.assignment = P12_max
    delta.fx["1"] = 0

    OPF = Model(
        m,
        name="OPF",
        equations=m.getEquations(),
        problem="qcp",
        sense="min",
        objective=OF,
    )
    OPF.solve()

    import math

    assert math.isclose(OPF.objective_value, 306.1075, rel_tol=0.001)

    print("Objective Function Value:  ", OPF.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/OPF2bus.py
GAMS-dev/gamspy
"""
*** Optimal power flow for a Five-bus system

For more details please refer to Chapter 6 (Gcode6.3), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: LP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # GenD records table
    cols = ["b", "Pmin", "Pmax"]
    inds = [f"g{gg}" for gg in range(1, 6)]
    data = [
        [14, 0, 40],
        [15, 0, 170],
        [30, 0, 520],
        [40, 0, 200],
        [10, 0, 600],
    ]
    GenD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # BD records table
    cols = ["Pd"]
    inds = ["2", "3", "4"]
    data = [[300], [300], [400]]
    BD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Branch records table
    cols = ["x", "limit"]
    inds = [
        ("1", "2"),
        ("1", "4"),
        ("1", "5"),
        ("2", "3"),
        ("3", "4"),
        ("4", "5"),
    ]
    data = [
        [0.0281, 400],
        [0.0304, 400],
        [0.0064, 400],
        [0.0108, 400],
        [0.0297, 400],
        [0.0297, 240],
    ]
    inds = pd.MultiIndex.from_tuples(inds, names=["Index1", "Index2"])
    B_recs = pd.DataFrame(data, columns=cols, index=inds)
    B_recs.reset_index(inplace=True)
    B_recs = B_recs.melt(
        id_vars=["Index1", "Index2"], value_vars=["x", "limit"]
    )

    return (
        GenD_recs,
        BD_recs,
        B_recs,
    )


def main():
    m = Container(delayed_execution=True)

    # SETS #
    bus = Set(m, name="bus", records=[str(buses) for buses in range(1, 6)])
    slack = Set(m, name="slack", domain=bus, records=["1"])
    Gen = Set(m, name="Gen", records=[f"g{gg}" for gg in range(1, 6)])
    conex = Set(
        m,
        name="conex",
        domain=[bus, bus],
        records=[
            ("1", "2"),
            ("2", "3"),
            ("3", "4"),
            ("4", "1"),
            ("4", "5"),
            ("5", "1"),
        ],
        description="bus connectivity matrix",
    )
    GBconect = Set(
        m,
        name="GBconect",
        domain=[bus, Gen],
        records=[
            ("1", "g1"),
            ("1", "g2"),
            ("3", "g3"),
            ("4", "g4"),
            ("5", "g5"),
        ],
        description="connectivity index of each generating unit to each bus",
    )

    # ALIASES #
    node = Alias(m, name="node", alias_with=bus)

    conex[bus, node].where[conex[node, bus]] = 1

    # SCALARS #
    Sbase = Parameter(m, name="Sbase", records=100)

    # PARAMETERS #
    GenData = Parameter(
        m,
        name="GenData",
        domain=[Gen, "*"],
        records=data_records()[0],
        description="generating units characteristics",
    )
    BusData = Parameter(
        m,
        name="BusData",
        domain=[bus, "*"],
        records=data_records()[1],
        description="demands of each bus in MW",
    )
    branch = Parameter(
        m,
        name="branch",
        domain=[bus, node, "*"],
        records=data_records()[2],
        description="network technical characteristics",
    )

    branch[bus, node, "x"].where[branch[bus, node, "x"] == 0] = branch[
        node, bus, "x"
    ]
    branch[bus, node, "Limit"].where[branch[bus, node, "Limit"] == 0] = branch[
        node, bus, "Limit"
    ]
    branch[bus, node, "bij"].where[conex[bus, node]] = (
        1 / branch[bus, node, "x"]
    )

    # VARIABLES #
    OF = Variable(m, name="OF")
    Pij = Variable(m, name="Pij", domain=[bus, node])
    Pg = Variable(m, name="Pg", domain=[Gen])
    delta = Variable(m, name="delta", domain=[bus])

    # EQUATIONS #
    const1 = Equation(m, name="const1", type="regular", domain=[bus, node])
    const2 = Equation(m, name="const2", type="regular", domain=[bus])
    const3 = Equation(m, name="const3", type="regular")

    const1[bus, node].where[conex[bus, node]] = Pij[bus, node] == branch[
        bus, node, "bij"
    ] * (delta[bus] - delta[node])

    const2[bus] = Sum(Gen.where[GBconect[bus, Gen]], Pg[Gen]) - BusData[
        bus, "pd"
    ] / Sbase == Sum(node.where[conex[node, bus]], Pij[bus, node])

    const3.definition = OF >= Sum(Gen, Pg[Gen] * GenData[Gen, "b"] * Sbase)

    loadflow = Model(
        m,
        name="loadflow",
        equations=[const1, const2, const3],
        problem="lp",
        sense="min",
        objective=OF,
    )

    Pg.lo[Gen] = GenData[Gen, "Pmin"] / Sbase
    Pg.up[Gen] = GenData[Gen, "Pmax"] / Sbase
    delta.up[bus] = np.pi
    delta.lo[bus] = -np.pi
    delta.fx[slack] = 0
    Pij.up[bus, node].where[conex[bus, node]] = (
        1 * branch[bus, node, "Limit"] / Sbase
    )
    Pij.lo[bus, node].where[conex[bus, node]] = (
        -1 * branch[bus, node, "Limit"] / Sbase
    )

    loadflow.solve()

    #  REPORTING PARAMETERS
    report = Parameter(m, name="report", domain=[bus, "*"])
    Congestioncost = Parameter(m, name="Congestioncost")
    report[bus, "Gen(MW)"] = (
        Sum(Gen.where[GBconect[bus, Gen]], Pg.l[Gen]) * Sbase
    )
    report[bus, "Angle"] = delta.l[bus]
    report[bus, "load(MW)"] = BusData[bus, "pd"]
    report[bus, "LMP($/MWh)"] = const2.m[bus] / Sbase
    Congestioncost.assignment = (
        Sum([bus, node], Pij.l[bus, node] * (-const2.m[bus] + const2.m[node]))
        / 2
    )

    print("report:  \n", report.pivot().round(4), "\n")
    print("Pij:  \n", Pij.pivot().round(4), "\n")
    print("Congestioncost:  ", Congestioncost.toValue())
    print("Objective Function Value:  ", round(OF.toValue(), 4))


if __name__ == "__main__":
    main()

tests/integration/models/OPF5bus.py
GAMS-dev/gamspy
"""
Maximizing the network observability using a limited number of PMU for IEEE 14
network without considering zero injection nodes

For more details please refer to Chapter 8 (Gcode8.4), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    bus = Set(m, name="bus", records=[str(idx) for idx in range(1, 15)])
    node = Alias(m, name="node", alias_with=bus)
    conex = Set(
        m,
        name="conex",
        records=[
            ("1", "2"),
            ("1", "5"),
            ("2", "3"),
            ("2", "4"),
            ("2", "5"),
            ("3", "4"),
            ("4", "5"),
            ("4", "7"),
            ("4", "9"),
            ("5", "6"),
            ("6", "11"),
            ("6", "12"),
            ("6", "13"),
            ("7", "8"),
            ("7", "9"),
            ("9", "10"),
            ("9", "14"),
            ("10", "11"),
            ("12", "13"),
            ("13", "14"),
        ],
        domain=[bus, node],
    )
    conex[bus, node].where[conex[node, bus]] = 1

    # Data
    NPMU = Parameter(m, name="NPMU", records=10)

    # Variable
    OF = Variable(m, name="OF")
    PMU = Variable(m, name="PMU", domain=[bus], type="Binary")
    alpha = Variable(m, name="alpha", domain=[bus], type="Binary")

    # Equation
    eq1 = Equation(m, name="eq1")
    eq2 = Equation(m, name="eq2")
    eq3 = Equation(m, name="eq3", domain=[bus])

    eq1.definition = Sum(bus, PMU[bus]) <= NPMU
    eq2.definition = OF == Sum(node, alpha[node])
    eq3[bus] = (
        PMU[bus] + Sum(node.where[conex[bus, node]], PMU[node]) >= alpha[bus]
    )

    m.addOptions({"optCr": 0, "profile": 1})

    placement3 = Model(
        m,
        name="placement3",
        equations=m.getEquations(),
        problem="MIP",
        sense=Sense.MAX,
        objective=OF,
    )

    counter = Set(m, "counter", records=[f"c{idx}" for idx in range(1, 5)])
    report = Parameter(m, "report", domain=[bus, counter])
    OBIrep = Parameter(m, "OBIrep", domain=[counter])

    for idx, iter, _ in counter.records.itertuples():
        NPMU.assignment = idx + 1
        placement3.solve()
        report[bus, iter] = PMU.l[bus]
        OBIrep[iter] = OF.l


if __name__ == "__main__":
    main()

tests/integration/models/PMU-OBI.py
GAMS-dev/gamspy
"""
*** Min Cost PMU allocation for IEEE 14 network without considering zero injection nodes

For more details please refer to Chapter 8 (Gcode8.2), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # SETS #
    bus = Set(m, name="bus", records=[str(b) for b in range(1, 15)])
    conex = Set(
        m,
        name="conex",
        domain=[bus, bus],
        description="Bus connectivity matrix",
        records=[
            ("1", "2"),
            ("1", "5"),
            ("2", "3"),
            ("2", "4"),
            ("2", "5"),
            ("3", "4"),
            ("4", "5"),
            ("4", "7"),
            ("4", "9"),
            ("5", "6"),
            ("6", "11"),
            ("6", "12"),
            ("6", "13"),
            ("7", "8"),
            ("7", "9"),
            ("9", "10"),
            ("9", "14"),
            ("10", "11"),
            ("12", "13"),
            ("13", "14"),
        ],
    )

    # ALIAS #
    node = Alias(m, name="node", alias_with=bus)

    conex[bus, node].where[conex[node, bus]] = 1

    # PARAMETER #
    cost = Parameter(m, name="cost", domain=[bus])
    cost[bus] = 1 + 0.1 * Sum(node.where[conex[bus, node]], 1)

    # VARIABLES #
    OFc = Variable(m, name="OFc")
    PMU = Variable(m, name="PMU", type="binary", domain=[bus])

    # EQUATIONS #
    const1 = Equation(m, name="const1", type="regular")
    const2 = Equation(m, name="const2", type="regular", domain=[bus])

    const1.definition = OFc == Sum(bus, cost[bus] * PMU[bus])
    const2[bus] = PMU[bus] + Sum(node.where[conex[bus, node]], PMU[node]) >= 1

    m.addOptions({"optCr": 0})
    placement0 = Model(
        m,
        name="placement0",
        equations=[const1, const2],
        problem="mip",
        sense="min",
        objective=OFc,
    )
    placement0.solve()
    print("PMU:  \n", PMU.toDict())

    import math

    assert math.isclose(placement0.objective_value, 5, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/PMU-cost.py
GAMS-dev/gamspy
"""
*** PMU allocation for IEEE 14 network without considering zero injection nodes

For more details please refer to Chapter 8 (Gcode8.1), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # SETS #
    bus = Set(m, name="bus", records=[str(b) for b in range(1, 15)])
    conex = Set(
        m,
        name="conex",
        domain=[bus, bus],
        description="Bus connectivity matrix",
        records=[
            ("1", "2"),
            ("1", "5"),
            ("2", "3"),
            ("2", "4"),
            ("2", "5"),
            ("3", "4"),
            ("4", "5"),
            ("4", "7"),
            ("4", "9"),
            ("5", "6"),
            ("6", "11"),
            ("6", "12"),
            ("6", "13"),
            ("7", "8"),
            ("7", "9"),
            ("9", "10"),
            ("9", "14"),
            ("10", "11"),
            ("12", "13"),
            ("13", "14"),
        ],
    )

    # ALIAS #
    node = Alias(m, name="node", alias_with=bus)

    conex[bus, node].where[conex[node, bus]] = 1

    # VARIABLES #
    OF = Variable(m, name="OF")
    PMU = Variable(m, name="PMU", type="binary", domain=[bus])

    # EQUATIONS #
    const1 = Equation(m, name="const1", type="regular")
    const2 = Equation(m, name="const2", type="regular", domain=[bus])

    const1.definition = OF == Sum(bus, PMU[bus])
    const2[bus] = PMU[bus] + Sum(node.where[conex[bus, node]], PMU[node]) >= 1

    m.addOptions({"optCr": 0, "profile": 1})

    placement = Model(
        m,
        name="placement",
        equations=[const1, const2],
        problem="mip",
        sense="min",
        objective=OF,
    )
    placement.solve()
    print("PMU:  \n", PMU.toDict())


if __name__ == "__main__":
    main()

tests/integration/models/PMU.py
GAMS-dev/gamspy
"""
*** Pareto optimal front determination

For more details please refer to Chapter 2 (Gcode2.16), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: NLP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Set
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    of1 = Variable(m, name="of1", type="free")
    of2 = Variable(m, name="of2", type="free")
    x1 = Variable(m, name="x1", type="free")
    x2 = Variable(m, name="x2", type="free")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")
    eq4 = Equation(m, name="eq4", type="regular")

    eq1.definition = 4 * x1 - 0.5 * sqr(x2) == of1
    eq2.definition = Number(-1) * sqr(x1) + 5 * x2 == of2
    eq3.definition = 2 * x1 + 3 * x2 <= 10
    eq4.definition = 2 * x1 - x2 >= 0

    x1.lo.assignment = 1
    x1.up.assignment = 2
    x2.lo.assignment = 1
    x2.up.assignment = 3

    pareto1 = Model(
        m,
        name="pareto1",
        equations=m.getEquations(),
        problem="nlp",
        sense="max",
        objective=of1,
    )
    pareto2 = Model(
        m,
        name="pareto2",
        equations=m.getEquations(),
        problem="nlp",
        sense="max",
        objective=of2,
    )

    # COUNTER SET #
    counter = Set(m, name="counter", records=[f"c{c}" for c in range(1, 22)])

    # REPORTING PARAMETERS #
    E = Parameter(m, name="E")
    report = Parameter(m, name="report", domain=[counter, "*"])
    ranges = Parameter(m, name="ranges", domain=["*"])

    pareto1.solve()
    ranges["OF1max"] = of1.l
    ranges["OF2min"] = of2.l

    pareto2.solve()
    ranges["OF2max"] = of2.l
    ranges["OF1min"] = of1.l

    for idx, c in enumerate(counter.toList()):
        E.assignment = (ranges["OF2max"] - ranges["OF2min"]) * (idx) / (
            Card(counter) - 1
        ) + ranges["OF2min"]
        of2.lo.assignment = E
        pareto1.solve()
        report[c, "OF1"] = of1.l
        report[c, "OF2"] = of2.l
        report[c, "E"] = E

    print("Report:  \n", report.pivot().round(4), "\n")
    print("Ranges:  \n", ranges.toDict(), "\n")


if __name__ == "__main__":
    main()

tests/integration/models/ParetoOptimalFront.py
GAMS-dev/gamspy
"""
Put/Call efficient frontier model

MAD.gms: Put/Call efficient frontier model.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 5.7
Last modified: Apr 2008.
"""
from pathlib import Path
from sys import argv

import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def index_data():
    data = np.array(
        [
            1.034769211,
            1.024362083,
            1.005721076,
            1.014359531,
            1.008024402,
            1.015164086,
            1.011776948,
            0.999367312,
            1.022337255,
            1.014084294,
            1.002478561,
            1.004830772,
            1.022048865,
            1.001584376,
            1.014789245,
            1.018465908,
            1.02023507,
            1.001142139,
            1.028357519,
            1.012274672,
            1.005320247,
            0.991451365,
            1.007344745,
            1.017165464,
            0.997931432,
            0.990041853,
            0.9933217,
            0.991912481,
            1.036980768,
            0.994451813,
            1.007412617,
            0.955150659,
            0.958840232,
            1.033624988,
            1.018079311,
            1.010736753,
            1.016700717,
            1.048121923,
            1.015146812,
            1.0074283,
            1.025778693,
            0.983151788,
            1.022476292,
            1.004850227,
            1.0091122,
            0.995361207,
            0.992432266,
            1.024212556,
            1.022181138,
            1.006772127,
            0.990354861,
            1.0067649,
            1.008943286,
            0.983974563,
            1.000347655,
            1.001230771,
            1.020880921,
            1.019415483,
            1.008638044,
            1.017173042,
            1.011831605,
            1.018260066,
            1.014054495,
            1.011841085,
            0.988155751,
            1.009224769,
            1.010321982,
            1.033008126,
            0.999626315,
            1.022733809,
            1.00083866,
            1.039925522,
            1.0144168,
            0.974402144,
            0.971108237,
            1.002198078,
            1.001221179,
            0.988342113,
            1.01750901,
            1.019968377,
            0.992895551,
            1.000905761,
            0.997333999,
            0.994982525,
            0.987972315,
            1.000443735,
            1.01583315,
            1.018338346,
            1.026183302,
            0.999209138,
            1.018809687,
            1.009775599,
            1.01722367,
            0.999752239,
            1.015986198,
            1.019400469,
            1.017624299,
            0.996067065,
            1.00947457,
            1.0164916,
            0.997929107,
            1.008892447,
            0.990498799,
            1.011843222,
            1.023338977,
            1.001536085,
            1.023978065,
            0.994238826,
            1.022819962,
            1.012142345,
            0.989862601,
            1.021050206,
            1.019165002,
            1.023260624,
            1.027413626,
            0.971346297,
            1.026087902,
            0.959990261,
            1.008484984,
            1.014365951,
            1.010347985,
            1.029266037,
            1.01989954,
            0.999383722,
            0.992191473,
            0.993782093,
            1.010730888,
            0.918149916,
            1.008729179,
            1.031641438,
            1.032632304,
            1.004115995,
            1.007398109,
            0.99859111,
            1.032604061,
            1.027883261,
            0.979268923,
            1.024656356,
            0.989556024,
            1.001926578,
            0.991713793,
            1.020697126,
            1.022268183,
            1.031273723,
            0.992301368,
            1.011547524,
            1.02121809,
            0.988701862,
            0.991540642,
            1.014546705,
            0.997009882,
            1.01168258,
            0.982543898,
            0.992197467,
        ]
    )
    return data


def main():
    gdx_file = str(Path(__file__).parent.absolute()) + "/WorldIndices.gdx"
    m = Container(delayed_execution=True, load_from=gdx_file)

    output = argv[1] if len(argv) > 1 else "PutCallModel"

    m.addOptions({"LIMROW": 0, "LIMCOL": 0, "SOLVELINK": 2})

    # SETS #
    i, l = m.getSymbols(["i", "l"])

    # SCALARS #
    Budget = Parameter(
        m, name="Budget", description="Nominal investment budget"
    )
    Omega = Parameter(
        m, name="Omega", description="Bound on the expected shortfalls"
    )

    Budget.assignment = 100.0

    # PARAMETERS #
    pr = Parameter(
        m, name="pr", domain=[l], description="Scenario probability"
    )
    P = Parameter(m, name="P", domain=[i, l], description="Final values")
    EP = Parameter(
        m, name="EP", domain=[i], description="Expected final values"
    )
    TargetIndex = Parameter(
        m, name="TargetIndex", domain=[l], description="Target index returns"
    )
    AssetReturns = m.getSymbols(["AssetReturns"])[0]

    pr[l] = 1.0 / Card(l)

    P[i, l] = 1 + AssetReturns[i, l]

    EP[i] = Sum(l, pr[l] * P[i, l])

    # To test the model with a market index, uncomment the following line two lines.
    # Note that, this index is consistent only when using WorldIndexes.inc.
    Index = Parameter(
        m,
        name="Index",
        domain=[l],
        records=index_data(),
        description="Index returns",
    )
    TargetIndex[l] = Index[l]

    # VARIABLES
    yPos = Variable(
        m,
        name="yPos",
        type="positive",
        domain=[l],
        description="Positive deviations",
    )
    yNeg = Variable(
        m,
        name="yNeg",
        type="positive",
        domain=[l],
        description="Negative deviations",
    )
    x = Variable(
        m,
        name="x",
        type="free",
        domain=[i],
        description="Holdings of assets in monetary units (not proportions)",
    )
    z = Variable(
        m, name="z", type="free", description="Objective function value"
    )

    # EQUATIONS #
    BudgetCon = Equation(
        m,
        name="BudgetCon",
        type="regular",
        description="Equation defining the budget contraint",
    )
    ObjDef = Equation(
        m,
        name="ObjDef",
        type="regular",
        description="Objective function definition for MAD",
    )
    TargetDevDef = Equation(
        m,
        name="TargetDevDef",
        type="regular",
        domain=[l],
        description="Equations defining the positive and negative deviations",
    )
    PutCon = Equation(
        m,
        name="PutCon",
        type="regular",
        description=(
            "Constraint to bound the expected value of the negative deviations"
        ),
    )

    BudgetCon.definition = Sum(i, x[i]) == Budget

    PutCon.definition = Sum(l, pr[l] * yNeg[l]) <= Omega

    TargetDevDef[l] = (
        Sum(i, (P[i, l] - TargetIndex[l]) * x[i]) == yPos[l] - yNeg[l]
    )

    ObjDef.definition = z == Sum(l, pr[l] * yPos[l])

    UnConPutCallModel = Model(
        m,
        name="UnConPutCallModel",
        equations=[PutCon, TargetDevDef, ObjDef],
        problem="LP",
        sense="MAX",
        objective=z,
    )

    # Set the average level of downside (risk) allowed

    Omega.assignment = 0.1
    UnConPutCallModel.solve()

    # Dual of the UnConstrained Put/Call model

    # VARIABLES #
    Pi = Variable(m, name="Pi", type="positive", domain=[l])
    PiOmega = Variable(m, name="PiOmega", type="positive")

    # EQUATIONS #
    DualObjDef = Equation(m, name="DualObjDef", type="regular")
    DualTrackingDef = Equation(
        m, name="DualTrackingDef", type="regular", domain=[i]
    )
    MeasureDef = Equation(m, name="MeasureDef", type="regular", domain=[l])

    DualObjDef.definition = z == Omega * PiOmega

    DualTrackingDef[i] = Sum(l, (P[i, l] - TargetIndex[l]) * Pi[l]) == 0.0

    MeasureDef[l] = pr[l] * PiOmega - Pi[l] >= 0

    Pi.lo[l] = pr[l]

    UnConDualPutCallModel = Model(
        m,
        name="UnConDualPutCallModel",
        equations=[DualObjDef, DualTrackingDef, MeasureDef],
        problem="LP",
        sense="MIN",
        objective=z,
    )

    UnConDualPutCallModel.solve()

    # Display PiOmega.l and Pi.l and check that they are, respectively, equal
    # to TargetDevDef.m and PutCon.m
    # GAMS provides the dual prices directly, so it is not
    # really necessary to build explicitly the dual model.

    # PARAMETER
    PrimalDual = Parameter(
        m,
        name="PrimalDual",
        domain=[l, "*"],
        description="Compare primal and dual soultions",
    )

    PrimalDual[l, "pi.l"] = -Pi.l[l]
    PrimalDual[l, "TargetDevDef.m"] = TargetDevDef.m[l]
    PrimalDual[l, "Difference"] = TargetDevDef.m[l] + Pi.l[l]

    # DISPLAY z.l,PiOmega.l,PutCon.m,PrimalDual

    # We propose an alternative way to build a frontier using
    # the loop statement. Such a structure is suitable for the
    # GDX utility (for details, see gdxutility.pdf included in the doc folder )

    m.addOptions({"SOLPRINT": "OFF"})

    # SET
    FrontierPoints = Set(
        m,
        name="FrontierPoints",
        records=[f"P_{fp}" for fp in range(1, 51)],
        description="Number of points in the frontier",
    )
    j = Alias(m, name="j", alias_with=FrontierPoints)

    # PARAMETER
    FrontierPortfolios = Parameter(
        m,
        name="FrontierPortfolios",
        domain=[j, i],
        description="Frontier portfolios",
    )
    CallValues = Parameter(
        m, name="CallValues", domain=[j, "*"], description="Call values"
    )
    DualPrices = Parameter(
        m, name="DualPrices", domain=[j, "*"], description="Dual prices"
    )
    PutCall = Parameter(
        m, name="PutCall", domain=[j, "*"], description="Put and Call values"
    )
    OmegaLevels = Parameter(
        m, name="OmegaLevels", domain=[j], description="Risk levels (Omega)"
    )

    # We assign to each point a risk level Omega

    OmegaLevels["P_1"] = 0.01

    for idx, jj in enumerate(j.toList()):
        if idx == 0:
            continue
        elif (idx + 1) <= 10:
            OmegaLevels[jj] = OmegaLevels[j.toList()[idx - 1]] + Number(0.01)
        elif (idx + 1) > 10:
            OmegaLevels[jj] = OmegaLevels[j.toList()[idx - 1]] + Number(0.025)

    DualPrices[j, "Omega"] = OmegaLevels[j]

    CallValues[j, "Omega"] = OmegaLevels[j]

    # Set some liquidity constraints

    x.lo[i] = -100.0
    x.up[i] = 100.0

    for jj in j.toList():
        Omega.assignment = OmegaLevels[jj]
        UnConPutCallModel.solve()

        FrontierPortfolios[jj, i] = x.l[i]
        CallValues[jj, "Mild Constraint"] = z.l
        DualPrices[jj, "Mild Constraint"] = PutCon.m

    # EXPORT RESULT SUMMARY TO EXCEL SHEET #
    writer = pd.ExcelWriter(f"{output}.xlsx", engine="openpyxl")
    FrontierPortfolios.pivot().to_excel(writer, sheet_name="MildPortfolios")

    # Set tight liquidity constraints

    x.lo[i] = -20.0
    x.up[i] = 20.0

    for jj in j.toList():
        Omega.assignment = OmegaLevels[jj]
        UnConPutCallModel.solve()

        FrontierPortfolios[jj, i] = x.l[i]
        CallValues[jj, "Tight Constraint"] = z.l
        DualPrices[jj, "Tight Constraint"] = PutCon.m

    DualPrices.pivot().to_excel(writer, sheet_name="DualPrices")
    CallValues.pivot().to_excel(writer, sheet_name="CallValues")
    FrontierPortfolios.pivot().to_excel(writer, sheet_name="TightPortfolios")

    # Determine the liquidity and discount premium
    # for one put/call efficient portfolio.

    Omega.assignment = 0.475

    UnConPutCallModel.solve()

    # SCALAR
    Df = Parameter(m, name="Df")

    # PARAMETERS
    Price = Parameter(m, name="Price", domain=[i])
    Discount = Parameter(m, name="Discount", domain=[i])
    Premium = Parameter(m, name="Premium", domain=[i])
    BenchMarkNeutralPrice = Parameter(
        m, name="BenchMarkNeutralPrice", domain=[i]
    )
    Psi = Parameter(m, name="Psi", domain=[l])
    liquidity = Parameter(
        m, name="liquidity", domain=[i, "*"], description="Liquidity report"
    )

    Discount[i] = 0.0

    Premium[i] = 0.0

    Df.assignment = Sum(l, -TargetDevDef.m[l])

    Psi[l] = -TargetDevDef.m[l] / Df

    BenchMarkNeutralPrice[i] = Sum(l, Psi[l] * P[i, l]) / Sum(
        l, Psi[l] * TargetIndex[l]
    )

    Discount[i].where[x.m[i] > 0] = x.m[i] / Sum(
        l, (-TargetDevDef.m[l]) * TargetIndex[l]
    )

    Premium[i].where[x.m[i] < 0] = (-x.m[i]) / Sum(
        l, (-TargetDevDef.m[l]) * TargetIndex[l]
    )

    Price[i] = BenchMarkNeutralPrice[i] + Premium[i] - Discount[i]

    liquidity[i, "Premium"] = Premium[i]
    liquidity[i, "Discount"] = Discount[i]

    liquidity.pivot().to_excel(writer, sheet_name="Liquidity")

    # Put/call model with balance constraint

    PutCallModel = Model(
        m,
        name="PutCallModel",
        equations=[BudgetCon, PutCon, TargetDevDef, ObjDef],
        problem="LP",
        sense="MAX",
        objective=z,
    )

    for jj in j.toList():
        Omega.assignment = OmegaLevels[jj]
        PutCallModel.solve()
        FrontierPortfolios[jj, i] = x.l[i]
        PutCall[jj, "Put side"] = PutCon.l
        PutCall[jj, "Call side"] = z.l

    PutCall.pivot().to_excel(writer, sheet_name="Frontiers")
    FrontierPortfolios.pivot().to_excel(writer, sheet_name="Portfolios")
    writer.close()


if __name__ == "__main__":
    main()

tests/integration/models/PutCall.py
GAMS-dev/gamspy
"""
*** Ramp rate sensitivity analysis for Dynamic Economic Load Dispatch

For more details please refer to Chapter 4 (Gcode4.2), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: QCP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
Email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # gendata records table
    cols = ["a", "b", "c", "d", "e", "f", "Pmin", "Pmax", "RU0", "RD0"]
    inds = [f"p{i}" for i in range(1, 5)]
    data = [
        [0.12, 14.80, 89, 1.2, -5.0, 3.0, 28, 200, 40, 40],
        [0.17, 16.57, 83, 2.3, -4.24, 6.09, 20, 290, 30, 30],
        [0.15, 15.55, 100, 1.1, -2.15, 5.69, 30, 190, 30, 30],
        [0.19, 16.21, 70, 1.1, -3.99, 6.2, 20, 260, 50, 50],
    ]
    gendata_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # demand records list
    demands_recs = np.array(
        [
            510,
            530,
            516,
            510,
            515,
            544,
            646,
            686,
            741,
            734,
            748,
            760,
            754,
            700,
            686,
            720,
            714,
            761,
            727,
            714,
            618,
            584,
            578,
            544,
        ]
    )

    return gendata_recs, demands_recs


def sqr(x):
    return gams_math.power(x, 2)


def main():
    m = Container(delayed_execution=True)

    # SETS #
    t = Set(
        m,
        name="t",
        records=[f"t{t}" for t in range(1, 25)],
        description="hours",
    )
    i = Set(
        m,
        name="i",
        records=[f"p{i}" for i in range(1, 5)],
        description="thermal units",
    )

    # PARAMETERS #
    gendata = Parameter(
        m,
        name="gendata",
        domain=[i, "*"],
        records=data_records()[0],
        description="generator cost characteristics and limits",
    )
    demand = Parameter(m, name="demand", domain=[t], records=data_records()[1])

    # VARIABLES #
    costThermal = Variable(
        m, name="costThermal", description="cost of thermal units"
    )
    p = Variable(
        m,
        name="p",
        domain=[i, t],
        description="power generated by thermal power plant",
    )
    EM = Variable(m, name="EM", description="emission calculation")

    p.up[i, t] = gendata[i, "Pmax"]
    p.lo[i, t] = gendata[i, "Pmin"]

    # EQUATIONS #
    Genconst3 = Equation(m, name="Genconst3", type="regular", domain=[i, t])
    Genconst4 = Equation(m, name="Genconst4", type="regular", domain=[i, t])
    costThermalcalc = Equation(m, name="costThermalcalc", type="regular")
    balance = Equation(m, name="balance", type="regular", domain=[t])
    EMcalc = Equation(m, name="EMcalc", type="regular")

    costThermalcalc.definition = costThermal == Sum(
        [t, i],
        gendata[i, "a"] * sqr(p[i, t])
        + gendata[i, "b"] * p[i, t]
        + gendata[i, "c"],
    )
    Genconst3[i, t] = p[i, t.lead(1)] - p[i, t] <= gendata[i, "RU"]
    Genconst4[i, t] = p[i, t.lag(1)] - p[i, t] <= gendata[i, "RD"]
    balance[t] = Sum(i, p[i, t]) >= demand[t]
    EMcalc.definition = EM == Sum(
        [t, i],
        gendata[i, "d"] * sqr(p[i, t])
        + gendata[i, "e"] * p[i, t]
        + gendata[i, "f"],
    )

    DEDcostbased = Model(
        m,
        name="DEDcostbased",
        equations=m.getEquations(),
        problem="qcp",
        sense="min",
        objective=costThermal,
    )

    # COUNTER SET
    counter = Set(m, name="counter", records=[f"c{c}" for c in range(1, 22)])

    # SCALAR
    Rscale = Parameter(m, name="Rscale", records=1)

    # REPORTING PARAMETER
    report1 = Parameter(m, name="report1", domain=[counter, "*"])

    for idx, c in enumerate(counter.toList()):
        Rscale.assignment = 1 - (idx) * 0.02
        gendata[i, "RU"] = gendata[i, "RU0"] * Rscale
        gendata[i, "RD"] = gendata[i, "RD0"] * Rscale
        DEDcostbased.solve()
        report1[c, "Scale"] = Rscale
        report1[c, "TC"] = costThermal.l
        report1[c, "EM"] = EM.l

    print("report1:  \n", report1.pivot().round(4))

    report1.pivot().round(4).to_excel(
        "DEDcostbased.xlsx", sheet_name="Pthermal"
    )


if __name__ == "__main__":
    main()

tests/integration/models/RampSenDED.py
GAMS-dev/gamspy
"""
Regret models

Regret.gms: Regret models.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 5.4
Last modified: Apr 2008.
"""
from pathlib import Path

import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Smax
from gamspy import Smin
from gamspy import Sum
from gamspy import Variable


def main():
    # Define container
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/Regret.gdx",
    )

    # SETS #
    Assets, Time = m.getSymbols(["Assets", "Time"])

    # ALIASES #
    i, l = m.getSymbols(["i", "l"])

    AssetReturns = m.getSymbols(["AssetReturns"])[0]

    # SCALARS #
    Budget = Parameter(
        m, name="Budget", description="Nominal investment budget"
    )
    EpsRegret = Parameter(
        m,
        name="EpsRegret",
        description="Tolerance allowed for epsilon regret models",
    )
    MU_TARGET = Parameter(
        m, name="MU_TARGET", description="Target portfolio return"
    )
    MU_STEP = Parameter(m, name="MU_STEP", description="Target return step")
    MIN_MU = Parameter(
        m, name="MIN_MU", description="Minimum return in universe"
    )
    MAX_MU = Parameter(
        m, name="MAX_MU", description="Maximum return in universe"
    )
    RISK_TARGET = Parameter(
        m, name="RISK_TARGET", description="Bound on expected regret (risk)"
    )

    Budget.assignment = 100.0

    # PARAMETERS #
    pr = Parameter(
        m, name="pr", domain=[l], description="Scenario probability"
    )
    P = Parameter(m, name="P", domain=[i, l], description="Final values")
    EP = Parameter(
        m, name="EP", domain=[i], description="Expected final values"
    )

    pr[l] = 1.0 / Card(l)
    P[i, l] = 1 + AssetReturns[i, l]

    EP[i] = Sum(l, pr[l] * P[i, l])

    MIN_MU.assignment = Smin(i, EP[i])
    MAX_MU.assignment = Smax(i, EP[i])

    # Assume we want 20 portfolios in the frontier

    MU_STEP.assignment = (MAX_MU - MIN_MU) / 20

    TargetIndex = Parameter(
        m, name="TargetIndex", domain=[l], description="Target index returns"
    )

    # To test the model with a market index, uncomment the following two lines.
    # Note that, this index can be used only with WorldIndexes.inc.

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i],
        description="Holdings of assets in monetary units (not proportions)",
    )
    Regrets = Variable(
        m,
        name="Regrets",
        type="positive",
        domain=[l],
        description="Measures of the negative deviations or regrets",
    )
    z = Variable(m, name="z", description="Objective function value")

    # EQUATIONS #
    BudgetCon = Equation(
        m,
        name="BudgetCon",
        description="Equation defining the budget contraint",
    )
    ReturnCon = Equation(
        m,
        name="ReturnCon",
        description="Equation defining the portfolio return constraint",
    )
    ExpRegretCon = Equation(
        m,
        name="ExpRegretCon",
        description="Equation defining the expected regret allowed",
    )
    ObjDefRegret = Equation(
        m,
        name="ObjDefRegret",
        description="Objective function definition for regret minimization",
    )
    ObjDefReturn = Equation(
        m,
        name="ObjDefReturn",
        description="Objective function definition for return mazimization",
    )
    RegretCon = Equation(
        m,
        name="RegretCon",
        domain=[l],
        description="Equations defining the regret constraints",
    )
    EpsRegretCon = Equation(
        m,
        name="EpsRegretCon",
        domain=[l],
        description=(
            "Equations defining the regret constraints with tolerance"
            " threshold"
        ),
    )

    BudgetCon.definition = Sum(i, x[i]) == Budget

    ReturnCon.definition = Sum(i, EP[i] * x[i]) >= MU_TARGET * Budget

    ExpRegretCon.definition = Sum(l, pr[l] * Regrets[l]) <= RISK_TARGET

    RegretCon[l] = Regrets[l] >= TargetIndex[l] * Budget - Sum(
        i, P[i, l] * x[i]
    )

    EpsRegretCon[l] = Regrets[l] >= (
        TargetIndex[l] - EpsRegret
    ) * Budget - Sum(i, P[i, l] * x[i])

    ObjDefRegret.definition = z == Sum(l, pr[l] * Regrets[l])

    ObjDefReturn.definition = z == Sum(i, EP[i] * x[i])

    MinRegret = Model(
        m,
        name="MinRegret",
        equations=[BudgetCon, ReturnCon, RegretCon, ObjDefRegret],
        problem="LP",
        sense=Sense.MIN,
        objective=z,
    )
    MaxReturn = Model(
        m,
        name="MaxReturn",
        equations=[BudgetCon, ExpRegretCon, EpsRegretCon, ObjDefReturn],
        problem="LP",
        sense=Sense.MAX,
        objective=z,
    )

    TargetIndex[l] = 1.01
    EpsRegret.assignment = 0.0

    # Write a code for displaying the result into a table
    result = []
    mu_iter = MIN_MU.records.value[0]
    while mu_iter <= MAX_MU.records.value[0]:
        MU_TARGET.assignment = mu_iter
        MinRegret.solve()

        result.append(
            [
                z.records.level[0],
                (MU_TARGET.records.value[0] * Budget.records.value[0]),
            ]
        )
        result[-1] += x.records.level.tolist()

        RISK_TARGET.assignment = z.l

        result[-1].append("")

        MaxReturn.solve()

        result[-1] += [RISK_TARGET.records.value[0], z.records.level[0]]
        result[-1] += x.records.level.tolist()

        mu_iter += MU_STEP.records.value[0]

    res_columns = [
        "Regret",
        "Mean",
        *i.records.uni,
        "",
        "Regret",
        "Mean",
        *i.records.uni,
    ]
    RegretFrontiers = pd.DataFrame(result, columns=res_columns)
    RegretFrontiers.to_csv("RegretFrontiers.csv")


if __name__ == "__main__":
    main()

tests/integration/models/Regret.py
GAMS-dev/gamspy
"""
Indexation model with selective hedging

* SelectiveHedging.gms: Indexation model with selective hedging
* Consiglio, Nielsen and Zenios.
* PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 7.2.3
* Last modified: Apr 2008.
"""
from pathlib import Path

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import ModelStatus
from gamspy import Number
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main(output=None):
    # Define container
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute())
        + "/SelectiveHedging.gdx",
    )

    # SETS #
    BB, EE, BxE, SS = m.getSymbols(["BB2", "EE", "BxE2", "SS"])

    i = Alias(m, name="i", alias_with=BB)
    l = Alias(m, name="l", alias_with=SS)
    s = Alias(m, name="s", alias_with=SS)
    e = Alias(m, name="e", alias_with=EE)

    # PARAMETERS #
    (
        BondPrices1,
        BondPrices0,
        ExchangeRates0,
        ExchangeRates1,
        IndexReturns,
    ) = m.getSymbols(
        ["data2", "data3", "ExchangeRates0", "ExchangeRates1", "IndexReturns"]
    )

    mu = Parameter(m, name="mu", description="Target expected value")
    USDDEMForwardRate = Parameter(
        m, name="USDDEMForwardRate", description="USD-DEM forward rate"
    )
    USDCHFForwardRate = Parameter(
        m, name="USDCHFForwardRate", description="USD-CHF forward rate"
    )
    pr = Parameter(
        m, name="pr", domain=[l], description="Scenario probability"
    )

    USDDEMForwardRate.assignment = -0.005
    USDCHFForwardRate.assignment = 0.001

    BondReturns = Parameter(
        m, name="BondReturns", domain=[SS, BB], description="Bond returns"
    )
    UnhedgedBondReturns = Parameter(
        m,
        name="UnhedgedBondReturns",
        domain=[SS, BB],
        description="Unhedged bond returns",
    )
    HedgedBondReturns = Parameter(
        m,
        name="HedgedBondReturns",
        domain=[SS, BB],
        description="Hedged bond returns",
    )
    ExchangeRatesReturns = Parameter(
        m, name="ExchangeRatesReturns", domain=[SS, EE]
    )

    BondReturns[l, i] = (BondPrices1[l, i] - BondPrices0[i]) / BondPrices0[i]
    ExchangeRatesReturns[l, e] = (
        ExchangeRates1[l, e] - ExchangeRates0[e]
    ) / ExchangeRates0[e]

    # Unhedged bond returns in USD currency

    UnhedgedBondReturns[l, i].where[BxE[i, "USD"]] = BondReturns[l, i].where[
        BxE[i, "USD"]
    ]
    UnhedgedBondReturns[l, i].where[BxE[i, "DEM"]] = (
        BondReturns[l, i].where[BxE[i, "DEM"]] + ExchangeRatesReturns[l, "DEM"]
    )
    UnhedgedBondReturns[l, i].where[BxE[i, "CHF"]] = (
        BondReturns[l, i].where[BxE[i, "CHF"]] + ExchangeRatesReturns[l, "CHF"]
    )

    # Hedged bond returns

    HedgedBondReturns[l, i].where[BxE[i, "DEM"]] = (
        BondReturns[l, i].where[BxE[i, "DEM"]] + USDDEMForwardRate
    )
    HedgedBondReturns[l, i].where[BxE[i, "CHF"]] = (
        BondReturns[l, i].where[BxE[i, "CHF"]] + USDCHFForwardRate
    )

    pr[l] = 1.0 / Card(l)

    # VARIABLES #
    z = Variable(m, name="z")
    h = Variable(m, name="h", type="positive", domain=[i])
    u = Variable(m, name="u", type="positive", domain=[i])
    y = Variable(m, name="y", type="positive", domain=[l])

    # EQUATIONS #
    ObjDef = Equation(m, name="ObjDef")
    ReturnCon = Equation(m, name="ReturnCon")
    NormalCon = Equation(m, name="NormalCon")
    yPosDef = Equation(m, name="yPosDef", domain=[l])
    yNegDef = Equation(m, name="yNegDef", domain=[l])

    ObjDef.definition = z == Sum(l, pr[l] * y[l])

    yPosDef[l] = y[l] >= Sum(
        i, UnhedgedBondReturns[l, i] * u[i] + HedgedBondReturns[l, i] * h[i]
    ) - Sum(
        s,
        pr[s]
        * Sum(
            i,
            UnhedgedBondReturns[s, i] * u[i] + HedgedBondReturns[s, i] * h[i],
        ),
    )

    ReturnCon.definition = (
        Sum(
            l,
            pr[l]
            * Sum(
                i,
                UnhedgedBondReturns[l, i] * u[i]
                + HedgedBondReturns[l, i] * h[i],
            ),
        )
        >= mu
    )

    yNegDef[l] = y[l] >= Sum(
        s,
        pr[s]
        * Sum(
            i,
            UnhedgedBondReturns[s, i] * u[i] + HedgedBondReturns[s, i] * h[i],
        ),
    ) - Sum(
        i, UnhedgedBondReturns[l, i] * u[i] + HedgedBondReturns[l, i] * h[i]
    )

    NormalCon.definition = Sum(i, h[i] + u[i]) == 1.0

    IndexFund = Model(
        m,
        name="IndexFund",
        equations=m.getEquations(),
        problem="LP",
        sense=Sense.MIN,
        objective=z,
    )

    FrontierPoints = Set(
        m, name="FrontierPoints", records=[f"P_{i}" for i in range(1, 51)]
    )
    p = Alias(m, name="p", alias_with=FrontierPoints)

    Frontiers = Parameter(
        m, name="Frontiers", domain=[p, "*"], description="Frontiers"
    )

    # We assign to each point a return level mu

    Frontiers["P_1", "mu"] = 0.0

    for idx, pp, _ in p.records.itertuples():
        if idx == 0:
            continue
        if idx < 20:
            Frontiers[pp, "mu"] = Frontiers[f"P_{idx}", "mu"] + Number(0.0005)
        elif idx >= 20:
            Frontiers[pp, "mu"] = Frontiers[f"P_{idx}", "mu"] + Number(0.001)

    for pp, _ in p.records.itertuples(index=False):
        if IndexFund.status not in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            continue

        mu.assignment = Frontiers[pp, "mu"]

        IndexFund.solve()
        print("Objective: ", round(z.records.level[0], 3))

        if IndexFund.status in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            Frontiers[pp, "Partial Hedge"] = z.l

    # Fully hedged model
    u.fx[i] = 0.0

    IndexFund.status = None
    for pp, _ in p.records.itertuples(index=False):
        if IndexFund.status not in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            continue

        mu.assignment = Frontiers[pp, "mu"]

        IndexFund.solve()
        print("Objective: ", round(z.records.level[0], 3))

        if IndexFund.status in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            Frontiers[pp, "Fully Hedged"] = z.l

    # Unhedged model
    u.lo[i] = 0.0
    u.up[i] = 1.0
    h.fx[i] = 0.0

    IndexFund.status = None
    for pp, _ in p.records.itertuples(index=False):
        if IndexFund.status not in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            continue

        mu.assignment = Frontiers[pp, "mu"]

        IndexFund.solve()
        print("Objective: ", round(z.records.level[0], 3))

        if IndexFund.status in [
            ModelStatus.OptimalGlobal,
            ModelStatus.OptimalLocal,
        ]:
            Frontiers[pp, "Unhedged"] = z.l

    # Create an excel file with the information stored in Frontiers
    # To activate it, add the name of the output file to "main" function below
    if output is not None:
        Frontiers.pivot().to_csv(f"{output}.csv")


if __name__ == "__main__":
    main()

tests/integration/models/SelectiveHedging.py
GAMS-dev/gamspy
"""
Sharpe model

Sharpe.gms: Sharpe model.
Consiglio, Nielsen and Zenios.
PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 3.3
Last modified: Apr 2008.
"""
from pathlib import Path

import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Problem
from gamspy import Sense
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/Sharpe.gdx",
    )

    # SETS #
    Assets = m.getSymbols(["subset"])[0]
    ii = Alias(m, name="ii", alias_with=Assets)
    j = Alias(m, name="j", alias_with=Assets)

    # PARAMETERS #
    RiskFreeRate, ExExpectedReturns, ExVarCov = m.getSymbols(
        ["MeanRiskFreeReturn", "MeanExcessRet", "ExcessCov"]
    )

    # VARIABLES #
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[ii],
        description="Holdings of assets",
    )
    PortVariance = Variable(
        m, name="PortVariance", description="Portfolio variance"
    )
    d_bar = Variable(
        m, name="d_bar", description="Portfolio expected excess return"
    )
    z = Variable(m, name="z", description="Objective function value")

    # EQUATIONS #
    ReturnDef = Equation(
        m,
        name="ReturnDef",
        description="Equation defining the portfolio excess return",
    )
    VarDef = Equation(
        m,
        name="VarDef",
        description="Equation defining the portfolio excess variance",
    )
    NormalCon = Equation(
        m,
        name="NormalCon",
        description="Equation defining the normalization contraint",
    )
    ObjDef = Equation(
        m,
        name="ObjDef",
        description="Objective function definition",
    )

    ReturnDef.definition = d_bar == Sum(ii, ExExpectedReturns[ii] * x[ii])

    VarDef.definition = PortVariance == Sum(
        [ii, j], x[ii] * ExVarCov[ii, j] * x[j]
    )

    NormalCon.definition = Sum(ii, x[ii]) == 1

    ObjDef.definition = z == d_bar / gams_math.sqrt(PortVariance)

    # Put strictly positive bound on Variance to keep the model out of trouble:
    PortVariance.lo.assignment = 0.001

    Sharpe = Model(
        m,
        name="Sharpe",
        equations=[ReturnDef, VarDef, NormalCon, ObjDef],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=z,
    )
    Sharpe.solve()

    print("Objective Function Variable: ", round(z.records.level[0], 3))

    current_port_variance = 0
    results = []
    while current_port_variance <= 1:
        theta = np.sqrt(current_port_variance / PortVariance.records.level[0])
        current_port_return = (
            RiskFreeRate.records.value[0] + theta * d_bar.records.level[0]
        )
        results.append(
            [np.sqrt(current_port_variance), current_port_return, theta]
        )
        current_port_variance += 0.1

    # Also plot the tangent portfolio
    theta = 1
    results.append(
        [
            np.sqrt(PortVariance.records.level[0]),
            RiskFreeRate.records.value[0] + theta * d_bar.records.level[0],
            theta,
        ]
    )
    SharpeFrontier = pd.DataFrame(
        results, columns=["Standard Deviations", "Expected Return", "Theta"]
    )
    SharpeFrontier.to_csv("SharpeFrontier.csv")


if __name__ == "__main__":
    main()

tests/integration/models/Sharpe.py
GAMS-dev/gamspy
"""
*** Simple linear programming model

For more details please refer to Chapter 2 (Gcode2.1), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: LP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    of = Variable(m, name="of")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")
    eq4 = Equation(m, name="eq4", type="regular")

    eq1.definition = x1 + 2 * x2 >= 3
    eq2.definition = x3 + x2 >= 5
    eq3.definition = x1 + x3 == 4
    eq4.definition = x1 + 3 * x2 + 3 * x3 == of

    LP1 = Model(
        m,
        name="LP1",
        equations=m.getEquations(),
        problem="lp",
        sense="min",
        objective=of,
    )
    LP1.solve()

    print("Objective Function Value:  ", round(of.toValue(), 4), "\n")
    print("x1:  ", round(x1.toValue(), 4))
    print("x2:  ", round(x2.toValue(), 4))
    print("x3:  ", round(x3.toValue(), 4))


if __name__ == "__main__":
    main()

tests/integration/models/SimpleLP.py
GAMS-dev/gamspy
"""
*** Simple Mixed Integer Linear Programming model

For more details please refer to Chapter 2 (Gcode2.3), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    x = Variable(m, name="x", type="free")
    of = Variable(m, name="of", type="free")
    y = Variable(m, name="y", type="binary")

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular")
    eq3 = Equation(m, name="eq3", type="regular")

    eq1.definition = -3 * x + 2 * y >= 1
    eq2.definition = -8 * x + 10 * y <= 10
    eq3.definition = x + y == of

    MIP1 = Model(
        m,
        name="MIP1",
        equations=m.getEquations(),
        problem="mip",
        sense="max",
        objective=of,
    )

    x.up.assignment = 0.3
    MIP1.solve()

    print("Objective Function Value:  ", round(of.toValue(), 4))
    print("y:  ", round(y.toValue(), 4))
    print("x:  ", round(x.toValue(), 4))


if __name__ == "__main__":
    main()

tests/integration/models/SimpleMIP.py
GAMS-dev/gamspy
"""
Stochastic Dedication model with borrowing and lending variables

* StochDedicationBL.gms: Stochastic Dedication model with borrowing
* and lending variables.
* Consiglio, Nielsen and Zenios.
* PRACTICAL FINANCIAL OPTIMIZATION: A Library of GAMS Models, Section 6.2.2
* Last modified: Apr 2008.
"""
from pathlib import Path

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Sense
from gamspy import Sum
from gamspy import Variable


def main():
    # Define container
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute())
        + "/StochDedicationBL.gdx",
    )

    # Aliases
    l, t = m.getSymbols(["l", "t"])

    # Scalars
    Horizon = m.getSymbols(["Horizon"])[0]

    # Parameters
    tau = m.getSymbols(["tau"])[0]

    # Aliases
    i = m.getSymbols(["i"])[0]

    # Scalars
    spread = m.getSymbols(["spread"])[0]

    # Parameters
    Price = m.getSymbols(["Price"])[0]

    # Parameters
    Srf, SF, SLiability = m.getSymbols(
        [
            "Srf",
            "SF",
            "SLiability",
        ]
    )

    # Variables
    x = Variable(m, "x", domain=[i], type="Positive")
    surplus = Variable(m, "surplus", domain=[t, l], type="Positive")
    borrow = Variable(m, "borrow", domain=[t, l], type="Positive")
    v0 = Variable(m, "v0")

    # Equations
    CashFlowCon = Equation(m, "CashFlowCon", domain=[t, l])

    CashFlowCon[t, l] = (
        Sum(i, SF[t, i, l] * x[i]).where[tau[t] > 0]
        + (v0 - Sum(i, Price[i] * x[i])).where[tau[t] == 0]
        + ((1 + Srf[t.lag(1), l]) * surplus[t.lag(1), l]).where[tau[t] > 0]
        + borrow[t, l].where[tau[t] < Horizon]
        == surplus[t, l]
        + SLiability[t, l].where[tau[t] > 0]
        + ((1 + Srf[t.lag(1), l] + spread) * borrow[t.lag(1), l]).where[
            tau[t] > 0
        ]
    )

    StochDedicationBL = Model(
        m,
        name="StochDedicationBL",
        equations=[CashFlowCon],
        problem="LP",
        sense=Sense.MIN,
        objective=v0,
    )

    StochDedicationBL.solve()
    print("Objective Function Value: ", round(v0.records.level[0], 3))

    print(x.description, ": \n", x.records.loc[:, ["i", "level"]])


if __name__ == "__main__":
    main()

tests/integration/models/StochDedicationBL.py
GAMS-dev/gamspy
"""
*** Transmission Expansion Planning

For more details please refer to Chapter 9 (Gcode9.1), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MIP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # GenD records table
    cols = ["b", "Pmin", "Pmax"]
    inds = [f"g{g}" for g in range(1, 4)]
    data = [[20, 0, 400], [30, 0, 400], [10, 0, 600]]
    GenD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # BD records table
    cols = ["Pd"]
    inds = ["1", "2", "3", "4", "5"]
    data = [[80], [240], [40], [160], [240]]
    BD_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Branch records table
    cols = ["X", "LIMIT", "Cost", "stat"]
    inds = [
        ("1", "2"),
        ("1", "4"),
        ("1", "5"),
        ("2", "3"),
        ("2", "4"),
        ("2", "6"),
        ("3", "5"),
        ("4", "6"),
    ]
    data = [
        [0.4, 100, 40, 1],
        [0.6, 80, 60, 1],
        [0.2, 100, 20, 1],
        [0.2, 100, 20, 1],
        [0.4, 100, 40, 1],
        [0.3, 100, 30, 0],
        [0.2, 100, 20, 1],
        [0.3, 100, 30, 0],
    ]
    inds = pd.MultiIndex.from_tuples(inds, names=["Index1", "Index2"])
    B_recs = pd.DataFrame(data, columns=cols, index=inds)
    B_recs.reset_index(inplace=True)
    B_recs = B_recs.melt(
        id_vars=["Index1", "Index2"], value_vars=["X", "LIMIT", "Cost", "stat"]
    )

    return (
        GenD_recs,
        BD_recs,
        B_recs,
    )


def main():
    m = Container(delayed_execution=True)

    # SETS #
    bus = Set(m, name="bus", records=[str(buses) for buses in range(1, 7)])
    slack = Set(m, name="slack", domain=bus, records=["1"])
    Gen = Set(m, name="Gen", records=[f"g{g}" for g in range(1, 4)])
    k = Set(m, name="k", records=[f"k{k}" for k in range(1, 5)])
    GBconect = Set(
        m,
        name="GBconect",
        domain=[bus, Gen],
        records=[("1", "g1"), ("3", "g2"), ("6", "g3")],
        description="connectivity index of each generating unit to each bus",
    )
    conex = Set(
        m,
        name="conex",
        domain=[bus, bus],
        description="bus connectivity matrix",
    )

    # ALIAS #
    node = Alias(m, name="node", alias_with=bus)

    # SCALARS #
    Sbase = Parameter(m, name="Sbase", records=100)
    M = Parameter(m, name="M")

    # PARAMETERS #
    GenData = Parameter(
        m,
        name="GenData",
        domain=[Gen, "*"],
        records=data_records()[0],
        description="generating units characteristics",
    )
    BusData = Parameter(
        m,
        name="BusData",
        domain=[bus, "*"],
        records=data_records()[1],
        description="demands of each bus in MW",
    )
    branch = Parameter(
        m,
        name="branch",
        domain=[bus, node, "*"],
        records=data_records()[2],
        description="network technical characteristics",
    )

    conex[bus, node].where[branch[bus, node, "x"]] = True
    conex[bus, node].where[conex[node, bus]] = True

    branch[bus, node, "x"].where[branch[node, bus, "x"]] = branch[
        node, bus, "x"
    ]
    branch[bus, node, "cost"].where[branch[node, bus, "cost"]] = branch[
        node, bus, "cost"
    ]
    branch[bus, node, "stat"].where[branch[node, bus, "stat"]] = branch[
        node, bus, "stat"
    ]
    branch[bus, node, "Limit"].where[branch[bus, node, "Limit"] == 0] = branch[
        node, bus, "Limit"
    ]
    branch[bus, node, "bij"].where[conex[bus, node]] = (
        1 / branch[bus, node, "x"]
    )
    M.assignment = Smax(
        Domain(bus, node).where[conex[bus, node]],
        branch[bus, node, "bij"] * 3.14 * 2,
    )

    # VARIABLES #
    OF = Variable(m, name="OF", type="free")
    Pij = Variable(m, name="Pij", type="free", domain=[bus, node, k])
    Pg = Variable(m, name="Pg", type="free", domain=[Gen])
    delta = Variable(m, name="delta", type="free", domain=[bus])
    LS = Variable(m, name="LS", type="free", domain=[bus])
    alpha = Variable(m, name="alpha", type="binary", domain=[bus, node, k])

    alpha.l[bus, node, k] = 1
    alpha.fx[bus, node, k].where[
        (conex[bus, node]) & (Ord(k) == 1) & (branch[node, bus, "stat"])
    ] = 1

    # EQUATIONS #
    const1A = Equation(
        m, name="const1A", type="regular", domain=[bus, node, k]
    )
    const1B = Equation(
        m, name="const1B", type="regular", domain=[bus, node, k]
    )
    const1C = Equation(
        m, name="const1C", type="regular", domain=[bus, node, k]
    )
    const1D = Equation(
        m, name="const1D", type="regular", domain=[bus, node, k]
    )
    const1E = Equation(
        m, name="const1E", type="regular", domain=[bus, node, k]
    )
    const2 = Equation(m, name="const2", type="regular", domain=[bus])
    const3 = Equation(m, name="const3", type="regular")

    const1A[bus, node, k].where[conex[node, bus]] = Pij[bus, node, k] - branch[
        bus, node, "bij"
    ] * (delta[bus] - delta[node]) <= M * (1 - alpha[bus, node, k])

    const1B[bus, node, k].where[conex[node, bus]] = Pij[bus, node, k] - branch[
        bus, node, "bij"
    ] * (delta[bus] - delta[node]) >= -M * (1 - alpha[bus, node, k])

    const1C[bus, node, k].where[conex[node, bus]] = (
        Pij[bus, node, k]
        <= alpha[bus, node, k] * branch[bus, node, "Limit"] / Sbase
    )

    const1D[bus, node, k].where[conex[node, bus]] = (
        Pij[bus, node, k]
        >= -alpha[bus, node, k] * branch[bus, node, "Limit"] / Sbase
    )

    const1E[bus, node, k].where[conex[node, bus]] = (
        alpha[bus, node, k] == alpha[node, bus, k]
    )

    const2[bus] = LS[bus] + Sum(
        Gen.where[GBconect[bus, Gen]], Pg[Gen]
    ) - BusData[bus, "pd"] / Sbase == Sum(
        Domain(k, node).where[conex[node, bus]], Pij[bus, node, k]
    )

    const3.definition = OF >= 10 * 8760 * (
        Sum(Gen, Pg[Gen] * GenData[Gen, "b"] * Sbase)
        + 100000 * Sum(bus, LS[bus])
    ) + 1e6 * Sum(
        Domain(bus, node, k).where[conex[node, bus]],
        0.5
        * branch[bus, node, "cost"]
        * alpha[bus, node, k].where[
            (Ord(k) > 1) | (branch[node, bus, "stat"] == 0)
        ],
    )

    loadflow = Model(
        m,
        name="loadflow",
        equations=m.getEquations(),
        problem="mip",
        sense="min",
        objective=OF,
    )

    LS.up[bus] = BusData[bus, "pd"] / Sbase
    LS.lo[bus] = 0
    Pg.lo[Gen] = GenData[Gen, "Pmin"] / Sbase
    Pg.up[Gen] = GenData[Gen, "Pmax"] / Sbase

    delta.up[bus] = np.pi / 3
    delta.lo[bus] = -np.pi / 3
    delta.fx[slack] = 0
    Pij.up[bus, node, k].where[conex[bus, node]] = (
        1 * branch[bus, node, "Limit"] / Sbase
    )
    Pij.lo[bus, node, k].where[conex[bus, node]] = (
        -1 * branch[bus, node, "Limit"] / Sbase
    )

    m.addOptions({"optCr": 0, "mip": "CPLEX"})
    loadflow.solve()

    print("Objective Function Value:  ", round(OF.toValue(), 3))


if __name__ == "__main__":
    main()

tests/integration/models/TEP.py
GAMS-dev/gamspy
"""
* ThreeStageSPDA.gms: A three stage stochastic programming model for SPDA
* Consiglio, Nielsen, Vladimirou and Zenios: A Library of Financial Optimization Models, Section 5.4
* See also Zenios: Practical Financial Optimization, Section 6.4.
* Last modified: Nov. 2005.
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def prepare_yield():
    cols = ["UU", "UD", "DD", "DU"]
    idxs = [
        ("IO2", "T0"),
        ("IO2", "T1"),
        ("PO7", "T0"),
        ("PO7", "T1"),
        ("PO70", "T0"),
        ("PO70", "T1"),
        ("IO90", "T0"),
        ("IO90", "T1"),
    ]

    data = np.array(
        [
            [1.104439, 1.104439, 0.959238, 0.959238],
            [1.110009, 0.975907, 0.935106, 1.167817],
            [0.938159, 0.938159, 1.166825, 1.166825],
            [0.933668, 1.154590, 1.156536, 0.903233],
            [0.924840, 0.924840, 1.167546, 1.167546],
            [0.891527, 1.200802, 1.141917, 0.907837],
            [1.107461, 1.107461, 0.908728, 0.908728],
            [1.105168, 0.925925, 0.877669, 1.187143],
        ]
    )

    idxs = pd.MultiIndex.from_tuples(idxs, names=["Index1", "Index2"])
    data = pd.DataFrame(data, columns=cols, index=idxs)
    data.reset_index(inplace=True)
    melted_data = data.melt(
        id_vars=["Index1", "Index2"], value_vars=["UU", "UD", "DD", "DU"]
    )
    return np.array(melted_data).tolist()


def main():
    m = Container(delayed_execution=True)

    # SETS #
    Scenarios = Set(
        m,
        name="Scenarios",
        records=["uu", "ud", "dd", "du"],
        description="Set of scenarios",
    )
    Assets = Set(
        m,
        name="Assets",
        records=["io2", "po7", "po70", "io90"],
        description="Available assets",
    )
    Time = Set(
        m, name="Time", records=["t0", "t1", "t2"], description="Time steps"
    )

    # ALIASES #
    l = Alias(m, name="l", alias_with=Scenarios)
    i = Alias(m, name="i", alias_with=Assets)
    t = Alias(m, name="t", alias_with=Time)

    # PARAMETERS #
    Yield = Parameter(
        m,
        name="Yield",
        domain=[i, t, l],
        records=prepare_yield(),
        description="Asset yields",
    )
    CashYield = Parameter(
        m,
        name="CashYield",
        domain=[t, l],
        records=np.array(
            [
                [1.030414, 1.030414, 1.012735, 1.012735],
                [1.032623, 1.014298, 1.009788, 1.030481],
                [0, 0, 0, 0],
            ]
        ),
        description="Risk free (cash) yield",
    )
    Liability = Parameter(
        m,
        name="Liability",
        domain=[t, l],
        records=np.array(
            [
                [0, 0, 0, 0],
                [26.474340, 26.474340, 10.953843, 10.953843],
                [31.264791, 26.044541, 10.757200, 13.608207],
            ]
        ),
        description="Liabilities due to annuitant lapses",
    )
    FinalLiability = Parameter(
        m,
        name="FinalLiability",
        domain=[l],
        records=np.array([47.284751, 49.094838, 86.111238, 83.290085]),
        description="Final liabilities",
    )
    Output = Parameter(
        m,
        name="Output",
        domain=["*", i],
        description=(
            "Parameter used to save the optimal holdings for each model"
        ),
    )
    PropCost = Parameter(
        m, name="PropCost", description="Proportional transaction cost"
    )

    # VARIABLES #
    buy = Variable(
        m,
        name="buy",
        type="positive",
        domain=[t, i, l],
        description="Amount purchased",
    )
    sell = Variable(
        m,
        name="sell",
        type="positive",
        domain=[t, i, l],
        description="Amount sold",
    )
    hold = Variable(
        m,
        name="hold",
        type="positive",
        domain=[t, i, l],
        description="Holdings",
    )
    cash = Variable(
        m,
        name="cash",
        type="positive",
        domain=[t, l],
        description="Holding in cash",
    )
    wealth = Variable(m, name="wealth", domain=[l], description="Final wealth")
    z = Variable(m, name="z", description="Objective function value")

    # EQUATIONS #
    AssetInventoryCon = Equation(
        m,
        name="AssetInventoryCon",
        type="regular",
        domain=[t, i, l],
        description="Constraints defining the asset inventory balance",
    )
    CashInventoryCon = Equation(
        m,
        name="CashInventoryCon",
        type="regular",
        domain=[t, l],
        description="Constraint defining the inventory balance",
    )
    WealthRatioDef = Equation(
        m,
        name="WealthRatioDef",
        type="regular",
        domain=[l],
        description="Equations defining the final asset-liability ratio",
    )
    NonAnticConOne = Equation(
        m,
        name="NonAnticConOne",
        type="regular",
        domain=[i, l],
        description="Constraints defining the first nonanticipativity set",
    )
    NonAnticConTwo = Equation(
        m,
        name="NonAnticConTwo",
        type="regular",
        domain=[i, l],
        description="Constraints defining the second nonanticipativity set",
    )
    ExpWealthObjDef = Equation(
        m,
        name="ExpWealthObjDef",
        type="regular",
        description="Expected wealth objective function definition",
    )

    AssetInventoryCon[t, i, l] = (
        buy[t, i, l].where[Ord(t) < Card(t)]
        + (Yield[i, t.lag(1), l] * hold[t.lag(1), i, l]).where[
            Ord(t) > Number(1)
        ]
        == sell[t, i, l].where[Ord(t) > Number(1)]
        + hold[t, i, l].where[Ord(t) < Card(t)]
    )

    CashInventoryCon[t, l] = (
        Sum(i, sell[t, i, l] * (1 - PropCost)).where[Ord(t) > Number(1)]
        + (CashYield[t.lag(1), l] * cash[t.lag(1), l]).where[
            Ord(t) > Number(1)
        ]
        + Number(100).where[Ord(t) == Number(1)]
        == Sum(i, buy[t, i, l]).where[Ord(t) < Card(t)]
        + cash[t, l]
        + Liability[t, l]
    )

    NonAnticConOne[i, l].where[Ord(l) < Card(l)] = (
        hold["t0", i, l] == hold["t0", i, l.lead(1)]
    )

    NonAnticConTwo[i, l].where[
        (Ord(l) == Number(1)) | (Ord(l) == Number(3))
    ] = (hold["t1", i, l] == hold["t1", i, l.lead(1)])

    WealthRatioDef[l] = wealth[l] == cash["t2", l] / FinalLiability[l]

    ExpWealthObjDef.definition = z == Sum(l, wealth[l]) / Card(l)

    ThreeStageExpWealth = Model(
        m,
        name="ThreeStageExpWealth",
        equations=[
            AssetInventoryCon,
            CashInventoryCon,
            WealthRatioDef,
            ExpWealthObjDef,
            NonAnticConOne,
            NonAnticConTwo,
        ],
        problem="LP",
        sense="MAX",
        objective=z,
    )

    # Model 1: Maximize the expected wealth, without transaction cost

    PropCost.assignment = 0.0

    ThreeStageExpWealth.solve()

    print("\n\n ### Model 1 ### \n")
    print("\nbuy: \n", buy.pivot().round(3))
    print("\nsell: \n", sell.pivot().round(3))
    print("\nhold: \n", hold.pivot().round(3))
    print("\nwealth: \n", wealth.records.iloc[:, :2].round(3))

    Output["Exp Wealth no TC", i] = hold.l["t0", i, "uu"]

    # Model 2: Maximize the expected wealth, with transaction cost

    PropCost.assignment = 0.01

    ThreeStageExpWealth.solve()

    print("\n\n ### Model 2 ### ")
    print("\nbuy: \n", buy.pivot().round(3))
    print("\nsell: \n", sell.pivot().round(3))
    print("\nhold: \n", hold.pivot().round(3))
    print("\nwealth: \n", wealth.records.iloc[:, :2].round(3))
    Output["Exp Wealth with TC", i] = hold.l["t0", i, "uu"]

    # Model 3: Maximize the worst-cast outcome.

    WorstCase = Variable(m, name="WorstCase", description="Worst case outcome")
    WorstCaseDef = Equation(
        m,
        name="WorstCaseDef",
        type="regular",
        domain=[l],
        description="Equations defining the worst case outcome",
    )

    WorstCaseDef[l] = WorstCase <= wealth[l]

    ThreeStageWorstCase = Model(
        m,
        name="ThreeStageWorstCase",
        equations=[
            AssetInventoryCon,
            CashInventoryCon,
            WealthRatioDef,
            WorstCaseDef,
            NonAnticConOne,
            NonAnticConTwo,
        ],
        problem="LP",
        sense="MAX",
        objective=WorstCase,
    )

    ThreeStageWorstCase.solve()

    print("\n\n ### Model 3 ### ")
    print("\nbuy: \n", buy.pivot().round(3))
    print("\nsell: \n", sell.pivot().round(3))
    print("\nhold: \n", hold.pivot().round(3))
    print("\nwealth: \n", wealth.records.iloc[:, :2].round(3))
    print("\nWorstCase: \n", round(WorstCase.records.level[0], 3))

    Output["Worst Case", i] = hold.l["t0", i, "uu"]

    # Model 4: Maximize expected utility:

    UtilityObjDef = Equation(
        m,
        name="UtilityObjDef",
        type="regular",
        description="Utility objective function definition",
    )

    UtilityObjDef.definition = z == Sum(l, gams_math.log(wealth[l])) / Card(l)

    ThreeStageUtility = Model(
        m,
        name="ThreeStageUtility",
        equations=[
            AssetInventoryCon,
            CashInventoryCon,
            WealthRatioDef,
            UtilityObjDef,
            NonAnticConOne,
            NonAnticConTwo,
        ],
        problem="NLP",
        sense="MAX",
        objective=z,
    )

    ThreeStageUtility.solve()

    print("\n\n ### Model 4 ### ")
    print("\nbuy: \n", buy.pivot().round(3))
    print("\nsell: \n", sell.pivot().round(3))
    print("\nhold: \n", hold.pivot().round(3))
    print("\nwealth: \n", wealth.records.iloc[:, :2].round(3))
    print("\nz: \n", round(z.records.level[0], 3))

    Output["Utility", i] = hold.l["t0", i, "uu"]

    # Model 5: Maximize expected wealth with MAD constraints such that A/L > 1.1

    EpsTolerance = Parameter(m, name="EpsTolerance", description="Tolerance")

    MADCon = Equation(
        m,
        name="MADCon",
        type="regular",
        domain=[l],
        description="MAD contraints",
    )

    MADCon[l] = wealth[l] >= 1.1 - EpsTolerance

    ThreeStageMAD = Model(
        m,
        name="ThreeStageMAD",
        equations=[
            AssetInventoryCon,
            CashInventoryCon,
            WealthRatioDef,
            MADCon,
            ExpWealthObjDef,
            NonAnticConOne,
            NonAnticConTwo,
        ],
        problem="LP",
        sense="MAX",
        objective=z,
    )

    EpsTolerance.assignment = 0.09

    ThreeStageMAD.solve()

    print("\n\n ### Model 5 ### ")
    print("\nbuy: \n", buy.pivot().round(3))
    print("\nsell: \n", sell.pivot().round(3))
    print("\nhold: \n", hold.pivot().round(3))
    print("\nwealth: \n", wealth.records.iloc[:, :2].round(3))
    print("\nz: \n", round(z.records.level[0], 3))

    Output["MAD", i] = hold.l["t0", i, "uu"]

    # EXPORT RESULT SUMMARY TO EXCEL #

    writer = pd.ExcelWriter("ThreeStage.xlsx", engine="openpyxl")

    # Write each DataFrame to a separate sheet in the Excel file
    Output.pivot().to_excel(writer, sheet_name="Holdings")
    buy.pivot().to_excel(writer, sheet_name="Purchase")
    sell.pivot().to_excel(writer, sheet_name="Sell")

    # Save and close the ExcelWriter
    writer.close()


if __name__ == "__main__":
    main()

tests/integration/models/ThreeStageSPDA.py
GAMS-dev/gamspy
"""
*** Transportation model with On/off state modeling of production side

For more details please refer to Chapter 2 (Gcode2.12), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MINLP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # data records table
    cols = ["Pmin", "Pmax"]
    inds = [f"s{s}" for s in range(1, 4)]
    data = [
        [100, 450],
        [50, 350],
        [30, 500],
    ]
    data_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # c records list
    c_recs = np.array(
        [
            [0.0755, 0.0655, 0.0498, 0.0585],
            [0.0276, 0.0163, 0.096, 0.0224],
            [0.068, 0.0119, 0.034, 0.0751],
        ]
    )

    return c_recs, data_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    i = Set(m, name="i", records=[f"s{s}" for s in range(1, 4)])
    j = Set(m, name="j", records=[f"d{d}" for d in range(1, 5)])

    # PARAMETERS #
    demand = Parameter(
        m, name="demand", domain=[j], records=np.array([217, 150, 145, 244])
    )
    c = Parameter(m, name="c", domain=[i, j], records=data_records()[0])
    data = Parameter(
        m, name="data", domain=[i, "*"], records=data_records()[1]
    )

    # VARIABLES #
    of = Variable(m, name="of", type="free")
    x = Variable(m, name="x", type="free", domain=[i, j])
    P = Variable(m, name="P", type="free", domain=[i])
    U = Variable(m, name="U", type="binary", domain=[i])

    # EQUATIONS #
    eq1 = Equation(m, name="eq1", type="regular")
    eq2 = Equation(m, name="eq2", type="regular", domain=[i])
    eq3 = Equation(m, name="eq3", type="regular", domain=[i])
    eq4 = Equation(m, name="eq4", type="regular", domain=[j])
    eq5 = Equation(m, name="eq5", type="regular", domain=[i])

    eq1.definition = of == Sum([i, j], c[i, j] * sqr(x[i, j]))
    eq2[i] = P[i] <= data[i, "Pmax"] * U[i]
    eq3[i] = P[i] >= data[i, "Pmin"] * U[i]
    eq4[j] = Sum(i, x[i, j]) >= demand[j]
    eq5[i] = Sum(j, x[i, j]) == P[i]

    P.lo[i] = 0
    P.up[i] = data[i, "Pmax"]
    x.lo[i, j] = 0
    x.up[i, j] = 100

    minlp1 = Model(
        m,
        name="minlp1",
        equations=m.getEquations(),
        problem="minlp",
        sense="min",
        objective=of,
    )
    minlp1.solve()

    print("Objective Function Value:  ", round(of.toValue(), 4))


if __name__ == "__main__":
    main()

tests/integration/models/TransportationOn-Off.py
GAMS-dev/gamspy
"""
*** Water-Energy Nexus

For more details please refer to Chapter 10 (Gcode10.1), of the following book:
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
--------------------------------------------------------------------------------
Model type: MINLP
--------------------------------------------------------------------------------
Contributed by
Dr. Alireza Soroudi
IEEE Senior Member
email: alireza.soroudi@gmail.com
We do request that publications derived from the use of the developed GAMS code
explicitly acknowledge that fact by citing
Soroudi, Alireza. Power System Optimization Modeling in GAMS. Springer, 2017.
DOI: doi.org/10.1007/978-3-319-62350-4
"""
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # gendata records table
    cols = ["a", "b", "c", "Pmax", "Pmin"]
    inds = [f"p{p}" for p in range(1, 5)]
    data = [
        [0.0002069, -0.1483, 57.11, 500, 0],
        [0.0003232, -0.1854, 57.11, 400, 0],
        [0.001065, -0.6026, 126.8, 400, 0],
        [0.0004222, -0.2119, 57.11, 350, 0],
    ]
    gendata_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Coproduct records table
    cols = [
        "Pmax",
        "Pmin",
        "Wmax",
        "Wmin",
        "rmin",
        "rmax",
        "A11",
        "A12",
        "A22",
        "b1",
        "b2",
        "C",
    ]
    inds = [f"c{c}" for c in range(1, 4)]
    data = [
        [
            800,
            160,
            200,
            30,
            4,
            9,
            0.0004433,
            0.003546,
            0.007093,
            -1.106,
            -4.426,
            737.4,
        ],
        [
            600,
            120,
            150,
            23,
            4,
            9,
            0.0007881,
            0.006305,
            0.01261,
            -1.475,
            -5.901,
            737.4,
        ],
        [
            400,
            80,
            100,
            15,
            4,
            9,
            0.001773,
            0.01419,
            0.02837,
            -2.213,
            -8.851,
            737.4,
        ],
    ]
    Coproduct_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Waterdata records table
    cols = ["a", "b", "c", "Wmax", "Wmin"]
    inds = ["w1"]
    data = [[1.82e-02, -7.081e-1, 7.374, 250, 0]]
    waterdata_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # pwdata records table
    cols = ["Pd", "water"]
    inds = [f"t{t}" for t in range(1, 11)] + [f"t{t}" for t in range(16, 25)]
    data = [
        [1250, 150],
        [1125, 130],
        [875, 100],
        [750, 150],
        [950, 200],
        [1440, 350],
        [1500, 300],
        [1750, 200],
        [2000, 300],
        [2250, 400],
        [2500, 550],
        [2125, 550],
        [2375, 500],
        [2250, 400],
        [1975, 350],
        [1750, 300],
        [1625, 250],
        [1500, 200],
        [1376, 150],
    ]
    pwdata_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return gendata_recs, Coproduct_recs, waterdata_recs, pwdata_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    t = Set(m, name="t", records=[f"t{t}" for t in range(1, 25)])
    i = Set(m, name="i", records=[f"p{p}" for p in range(1, 5)])
    c = Set(m, name="c", records=[f"c{c}" for c in range(1, 4)])
    w = Set(m, name="w", records=["w1"])

    # PARAMETERS #
    gendata = Parameter(
        m,
        name="gendata",
        domain=[i, "*"],
        records=data_records()[0],
        description="generator cost characteristics and limits",
    )
    Coproduct = Parameter(
        m, name="Coproduct", domain=[c, "*"], records=data_records()[1]
    )
    waterdata = Parameter(
        m, name="waterdata", domain=[w, "*"], records=data_records()[2]
    )
    PWdata = Parameter(
        m, name="PWdata", domain=[t, "*"], records=data_records()[3]
    )

    # FREE VARIABLES #
    of = Variable(m, name="of")
    TC = Variable(m, name="TC")
    CC = Variable(m, name="CC")
    WaterCost = Variable(m, name="WaterCost")

    # BINARY VARIABLES #
    Up = Variable(m, name="Up", type="binary", domain=[i, t])
    Uc = Variable(m, name="Uc", type="binary", domain=[c, t])
    Uw = Variable(m, name="Uw", type="binary", domain=[w, t])

    # POSITIVE VARIABLES #
    p = Variable(m, name="p", type="positive", domain=[i, t])
    Pc = Variable(m, name="Pc", type="positive", domain=[c, t])
    Wc = Variable(m, name="Wc", type="positive", domain=[c, t])
    Water = Variable(m, name="Water", type="positive", domain=[w, t])

    p.up[i, t] = gendata[i, "Pmax"]
    Pc.up[c, t] = Coproduct[c, "Pmax"]
    Wc.up[c, t] = Coproduct[c, "Wmax"]
    Water.up[w, t] = waterdata[w, "Wmax"]

    # EQUATIONS #
    costThermal = Equation(m, name="costThermal", type="regular")
    balanceP = Equation(m, name="balanceP", type="regular", domain=[t])
    balanceW = Equation(m, name="balanceW", type="regular", domain=[t])
    costCoprodcalc = Equation(m, name="costCoprodcalc", type="regular")
    Objective = Equation(m, name="Objective", type="regular")
    costwatercalc = Equation(m, name="costwatercalc", type="regular")
    ratio1 = Equation(m, name="ratio1", type="regular", domain=[c, t])
    ratio2 = Equation(m, name="ratio2", type="regular", domain=[c, t])
    eq1 = Equation(m, name="eq1", type="regular", domain=[w, t])
    eq2 = Equation(m, name="eq2", type="regular", domain=[w, t])
    eq3 = Equation(m, name="eq3", type="regular", domain=[c, t])
    eq4 = Equation(m, name="eq4", type="regular", domain=[c, t])
    eq5 = Equation(m, name="eq5", type="regular", domain=[c, t])
    eq6 = Equation(m, name="eq6", type="regular", domain=[c, t])
    eq7 = Equation(m, name="eq7", type="regular", domain=[i, t])
    eq8 = Equation(m, name="eq8", type="regular", domain=[i, t])

    costThermal.definition = TC == Sum(
        [t, i],
        gendata[i, "a"] * sqr(p[i, t])
        + gendata[i, "b"] * p[i, t]
        + gendata[i, "c"] * Up[i, t],
    )
    balanceP[t] = Sum(i, p[i, t]) + Sum(c, Pc[c, t]) == PWdata[t, "Pd"]
    balanceW[t] = Sum(w, Water[w, t]) + Sum(c, Wc[c, t]) == PWdata[t, "water"]
    costCoprodcalc.definition = CC == (
        Sum(
            [c, t],
            Coproduct[c, "A11"] * sqr(Pc[c, t])
            + 2 * Coproduct[c, "A12"] * Pc[c, t] * Wc[c, t]
            + Coproduct[c, "A22"] * sqr(Wc[c, t])
            + Coproduct[c, "B1"] * Pc[c, t]
            + Coproduct[c, "B2"] * Wc[c, t]
            + Coproduct[c, "C"] * Uc[c, t],
        )
    )
    costwatercalc.definition = WaterCost == Sum(
        [t, w],
        waterdata[w, "a"] * sqr(Water[w, t])
        + waterdata[w, "b"] * Water[w, t]
        + waterdata[w, "c"] * Uw[w, t],
    )
    Objective.definition = of == TC + CC + WaterCost
    ratio1[c, t] = Pc[c, t] <= Wc[c, t] * Coproduct[c, "Rmax"]
    ratio2[c, t] = Pc[c, t] >= Wc[c, t] * Coproduct[c, "Rmin"]
    eq1[w, t] = Water[w, t] <= Uw[w, t] * waterdata[w, "Wmax"]
    eq2[w, t] = Water[w, t] >= Uw[w, t] * waterdata[w, "Wmin"]
    eq3[c, t] = Wc[c, t] <= Uc[c, t] * Coproduct[c, "Wmax"]
    eq4[c, t] = Wc[c, t] >= Uc[c, t] * Coproduct[c, "Wmin"]
    eq5[c, t] = Pc[c, t] <= Uc[c, t] * Coproduct[c, "Pmax"]
    eq6[c, t] = Pc[c, t] >= Uc[c, t] * Coproduct[c, "Pmin"]
    eq7[i, t] = p[i, t] <= Up[i, t] * gendata[i, "Pmax"]
    eq8[i, t] = p[i, t] >= Up[i, t] * gendata[i, "Pmin"]

    DEDcostbased = Model(
        m,
        name="DEDcostbased",
        equations=m.getEquations(),
        problem="minlp",
        sense="min",
        objective=of,
    )
    DEDcostbased.solve()

    print("Objective Function Value:  ", round(of.toValue(), 4))


if __name__ == "__main__":
    main()

tests/integration/models/WaterEnergy.py
GAMS-dev/gamspy
"""
Aircraft Allocation under uncertain Demand (AIRCRAF)

The objective of this model is to allocate aircrafts to routes to maximize
the expected profit when traffic demand is uncertain. Two different
formulations are used, the delta and the lambda formulation.


Dantzig, G B, Chapter 28. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: linear programming, aircraft managing, allocation problem
"""
import numpy as np

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Sets
    i = Set(m, name="i", records=["a", "b", "c", "d"])
    j = Set(m, name="j", records=[f"route-{i}" for i in range(1, 6)])
    h = Set(m, name="h", records=list(range(1, 6)))

    # Alias
    hp = Alias(m, name="hp", alias_with=h)

    # Parameters
    dd = Parameter(
        m,
        name="dd",
        domain=[j, h],
        records=np.array(
            [
                [200, 220, 250, 270, 300],
                [50, 150, 0, 0, 0],
                [140, 160, 180, 200, 220],
                [10, 50, 80, 100, 340],
                [580, 600, 620, 0, 0],
            ]
        ),
    )

    lamda = Parameter(
        m,
        name="lamda",
        domain=[j, h],
        records=np.array(
            [
                [0.2, 0.05, 0.35, 0.2, 0.2],
                [0.3, 0.7, 0, 0, 0],
                [0.1, 0.2, 0.4, 0.2, 0.1],
                [0.2, 0.2, 0.3, 0.2, 0.1],
                [0.1, 0.8, 0.1, 0, 0],
            ]
        ),
    )

    c = Parameter(
        m,
        name="c",
        domain=[i, j],
        records=np.array(
            [
                [18, 21, 18, 16, 10],
                [0, 15, 16, 14, 9],
                [0, 10, 0, 9, 6],
                [17, 16, 17, 15, 10],
            ]
        ),
    )

    p = Parameter(
        m,
        name="p",
        domain=[i, j],
        records=np.array(
            [
                [16, 15, 28, 23, 81],
                [0, 10, 14, 15, 57],
                [0, 5, 0, 7, 29],
                [9, 11, 22, 17, 55],
            ]
        ),
    )

    aa = Parameter(
        m, name="aa", domain=[i], records=np.array([10, 19, 25, 15])
    )
    k = Parameter(m, name="k", domain=[j], records=np.array([13, 13, 7, 7, 1]))
    ed = Parameter(m, name="ed", domain=[j])
    gamma = Parameter(m, name="gamma", domain=[j, h])
    deltb = Parameter(m, name="deltb", domain=[j, h])

    ed[j].assignment = Sum(h, lamda[j, h] * dd[j, h])
    gamma[j, h] = Sum(hp.where[(Ord(hp) >= Ord(h))], lamda[j, hp])
    deltb[j, h] = dd[j, h] - dd[j, h.lag(1, "linear")].where[dd[j, h]]

    # Variables
    x = Variable(m, name="x", domain=[i, j], type="positive")
    y = Variable(m, name="y", domain=[j, h], type="positive")
    b = Variable(m, name="b", domain=[j, h], type="positive")
    oc = Variable(m, name="oc", type="positive")
    bc = Variable(m, name="bc", type="positive")
    phi = Variable(m, name="phi")

    # Equations
    ab = Equation(m, name="ab", domain=[i])
    db = Equation(m, name="db", domain=[j])
    yd = Equation(m, name="yd", domain=[j, h])
    bd = Equation(m, name="bd", domain=[j, h])
    ocd = Equation(m, name="ocd")
    bcd1 = Equation(m, name="bcd1")
    bcd2 = Equation(m, name="bcd2")
    obj = Equation(m, name="obj")

    ab[i] = Sum(j, x[i, j]) <= aa[i]
    db[j] = Sum(i, p[i, j] * x[i, j]) >= Sum(h.where[deltb[j, h]], y[j, h])
    yd[j, h] = y[j, h] <= Sum(i, p[i, j] * x[i, j])
    bd[j, h] = b[j, h] == dd[j, h] - y[j, h]
    ocd.definition = oc == Sum([i, j], c[i, j] * x[i, j])
    bcd1.definition = bc == Sum(
        j, k[j] * (ed[j] - Sum(h, gamma[j, h] * y[j, h]))
    )
    bcd2.definition = bc == Sum([j, h], k[j] * lamda[j, h] * b[j, h])
    obj.definition = phi == oc + bc

    alloc1 = Model(
        m,
        name="alloc1",
        equations=[ab, db, ocd, bcd1, obj],
        problem="LP",
        sense=Sense.MIN,
        objective=phi,
    )
    alloc2 = Model(
        m,
        name="alloc2",
        equations=[ab, yd, bd, ocd, bcd2, obj],
        problem="LP",
        sense=Sense.MIN,
        objective=phi,
    )

    y.up[j, h] = deltb[j, h]
    alloc1.solve()

    print("Number of passengers carried with limit:")
    print(y.pivot())

    y.up[j, h] = np.inf

    alloc2.solve()
    print()
    print("Number of passengers carried without limit:")
    print(y.pivot())

    import math

    assert math.isclose(alloc2.objective_value, 1566.042189, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/aircraft.py
GAMS-dev/gamspy
"""
Optimal control of a batch reactor.
Find the optimal temperature profile which gives maximum intermediate product
concentration in a batch reactor with two consecutive reactions. The first
reaction is of second order and the second one is of first order with known
rate constants.

Renfro J.G., Morshedi, A.M., Osbjornsen, O.A., Simultaneous optimization and
solution of systems described by differential/algebraic equations. Computer
and Chemical Engineering, vol.11, 1987, pp.503-517.
"""
import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    nh = Set(m, name="nh", records=[str(idx) for idx in range(0, 101)])
    k = Alias(m, name="k", alias_with=nh)

    # Data
    ca_0 = Parameter(m, name="ca_0", records=1.0)
    cb_0 = Parameter(m, name="cb_0", records=0.0)
    h = Parameter(m, name="h", records=1)

    # Variable
    ca = Variable(m, name="ca", domain=[nh])
    cb = Variable(m, name="cb", domain=[nh])
    t = Variable(m, name="t", domain=[nh])
    k1 = Variable(m, name="k1", domain=[nh])
    k2 = Variable(m, name="k2", domain=[nh])
    obj = Variable(m, name="obj")

    # Equation
    eobj = Equation(m, name="eobj")
    state1 = Equation(m, domain=[nh], name="state1")
    state2 = Equation(m, domain=[nh], name="state2")
    ek1 = Equation(m, domain=[nh], name="ek1")
    ek2 = Equation(m, domain=[nh], name="ek2")

    eobj.definition = obj == cb["100"]

    ek1[nh[k]] = k1[k] == 4000 * gams_math.exp(-2500 / t[k])
    ek2[nh[k]] = k2[k] == 620000 * gams_math.exp(-5000 / t[k])

    state1[nh[k.lead(1, "linear")]] = ca[k.lead(1, "linear")] == ca[k] + (
        h / 2
    ) * (
        -k1[k] * ca[k] * ca[k]
        - k1[k.lead(1, "linear")]
        * ca[k.lead(1, "linear")]
        * ca[k.lead(1, "linear")]
    )

    state2[nh[k.lead(1, "linear")]] = cb[k.lead(1, "linear")] == cb[k] + (
        h / 2
    ) * (
        k1[k] * ca[k] * ca[k]
        - k2[k] * cb[k]
        + k1[k.lead(1, "linear")]
        * ca[k.lead(1, "linear")]
        * ca[k.lead(1, "linear")]
        - k2[k.lead(1, "linear")] * cb[k.lead(1, "linear")]
    )

    ca.l[nh] = 1.0
    cb.l[nh] = 0.0

    ca.fx["0"] = ca_0
    cb.fx["0"] = cb_0

    t.lo[nh] = 110
    t.up[nh] = 280

    batchReactor = Model(
        m,
        name="batchReactor",
        equations=[eobj, state1, state2, ek1, ek2],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=obj,
    )
    batchReactor.solve()

    import math

    assert math.isclose(batchReactor.objective_value, 0.8558, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/batchreactor.py
GAMS-dev/gamspy
"""
Stability of a wire guided Daimler-Benz 0305 bus.

References:
Neculai Andrei, "Models, Test Problems and Applications for
Mathematical Programming". Technical Press, Bucharest, 2003.

Application A38, page 402.
Ackermann, J., et al. " Robust gamma-stability analysis in a plant
parameter space. Automatica, vol. 27, 1991, pp.75.

Floudas, C.A., Pardalos, P.M., et al. "Handbook of Test Problems in
Local and Global Optimization". Kluwer Academic Publishers, Dordrecht,
1999.
Section 7.3.5. Test problem 15, page 102.
"""
import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Problem
from gamspy import Sense
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Variable
    q1 = Variable(m, name="q1")
    q2 = Variable(m, name="q2")
    w = Variable(m, name="w")
    k = Variable(m, name="k")

    # Equation
    g1 = Equation(m, name="g1")
    g2 = Equation(m, name="g2")
    b1l = Equation(m, name="b1l")
    b1u = Equation(m, name="b1u")
    b2l = Equation(m, name="b2l")
    b2u = Equation(m, name="b2u")

    g1.definition = (gams_math.power(q1, 2)) * (gams_math.power(q2, 2)) * (
        gams_math.power(w, 8)
    ) - (
        1.25 * 1000 * (gams_math.power(q1, 2)) * (gams_math.power(q2, 2))
        + 16.8 * (gams_math.power(q1, 2)) * q2
        + 53.9 * 1000 * q1 * q2
        + 270 * 1000
    ) * (
        gams_math.power(w, 6)
    ) + (
        1.45 * (10**6) * (gams_math.power(q1, 2)) * q2
        + 16.8 * (10**6) * q1 * q2
        + (10**6) * 338
    ) * (
        gams_math.power(w, 4)
    ) - (
        5.72 * (10**6) * (gams_math.power(q1, 2)) * q2
        + 113 * (10**6) * (gams_math.power(q1, 2))
        + 4250 * (10**6) * q1
    ) * (
        gams_math.power(w, 2)
    ) + (
        453 * (10**6) * (gams_math.power(q1, 2))
    ) == 0
    g2.definition = (
        50 * (gams_math.power(q1, 2)) * (gams_math.power(q2, 2))
        + 1080 * q1 * q2
    ) * (gams_math.power(w, 6)) - (
        15.6 * 1000 * (gams_math.power(q1, 2)) * (gams_math.power(q2, 2))
        + 840 * (gams_math.power(q1, 2)) * q2
        + 1.35 * (10**6) * q1 * q2
        + (10**6) * 13.5
    ) * (
        gams_math.power(w, 4)
    ) + (
        6.93 * (10**6) * (gams_math.power(q1, 2)) * q2
        + 911 * (10**6) * q1
        + (10**6) * 4220
    ) * (
        gams_math.power(w, 2)
    ) - (
        528 * (10**6) * (gams_math.power(q1, 2)) + 3640 * (10**6) * q1
    ) == 0

    b1l.definition = 17.5 - 14.5 * k <= q1
    b1u.definition = q1 <= 17.5 + 14.5 * k
    b2l.definition = 20.0 - 15.0 * k <= q2
    b2u.definition = q2 <= 20.0 + 15.0 * k

    q1.lo.assignment = 0
    q1.up.assignment = 2
    q2.lo.assignment = 0
    q2.up.assignment = 2
    w.lo.assignment = 0
    w.up.assignment = 2
    k.lo.assignment = 0
    k.up.assignment = 2
    q1.l.assignment = 0.1
    q2.l.assignment = 0.1
    w.l.assignment = 0.1
    k.l.assignment = 0.1

    benz = Model(
        m,
        name="benz",
        equations=[g1, g2, b1l, b1u, b2l, b2u],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=k,
    )
    benz.solve()

    import math

    assert math.isclose(benz.objective_value, 1.2069, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/benz.py
GAMS-dev/gamspy
"""
Blending Problem I (BLEND)

A company wishes to produce a lead-zinc-tin alloy at minimal cost.
The problem is to blend a new alloy from other purchased alloys.


Dantzig, G B, Chapter 3.4. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: linear programming, blending problem, manufacturing, alloy blending
"""
import numpy as np

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    alloy = Set(
        m, name="alloy", records=["a", "b", "c", "d", "e", "f", "g", "h", "i"]
    )
    elem = Set(m, name="elem", records=["lead", "zinc", "tin"])

    # Data
    compdat = Parameter(
        m,
        name="compdat",
        domain=[elem, alloy],
        records=np.array(
            [
                [10, 10, 40, 60, 30, 30, 30, 50, 20],
                [10, 30, 50, 30, 30, 40, 20, 40, 30],
                [80, 60, 10, 10, 40, 30, 50, 10, 50],
            ]
        ),
    )
    price = Parameter(
        m,
        name="price",
        domain=[alloy],
        records=np.array([4.1, 4.3, 5.8, 6.0, 7.6, 7.5, 7.3, 6.9, 7.3]),
    )
    rb = Parameter(m, name="rb", domain=[elem], records=np.array([30, 30, 40]))

    # Variable
    v = Variable(m, name="v", domain=[alloy], type="Positive")

    # Equation
    pc = Equation(m, name="pc", domain=[elem])
    mb = Equation(m, name="mb")

    pc[elem] = Sum(alloy, compdat[elem, alloy] * v[alloy]) == rb[elem]
    mb.definition = Sum(alloy, v[alloy]) == 1

    b1 = Model(
        m,
        name="b1",
        equations=[pc],
        problem="LP",
        sense=Sense.MIN,
        objective=Sum(alloy, price[alloy] * v[alloy]),
    )
    b2 = Model(
        m,
        name="b2",
        equations=[pc, mb],
        problem="LP",
        sense=Sense.MIN,
        objective=Sum(alloy, price[alloy] * v[alloy]),
    )

    report = Parameter(m, name="report", domain=[alloy, "*"])

    b1.solve()

    report[alloy, "blend-1"] = v.l[alloy]
    b2.solve()
    report[alloy, "blend-2"] = v.l[alloy]

    print(report.pivot())

    import math

    assert math.isclose(b2.objective_value, 4.980000, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/blend.py
GAMS-dev/gamspy
"""
Car Sequencing (CARSEQ)

A number of cars are to be produced; they are not identical, because
different options are available as variants on the basic model. The
assembly line has different stations which install the various options
(air-conditioning, sun-roof, etc.). These stations have been designed
to handle at most a certain percentage of the cars passing along the
assembly line. Furthermore, the cars requiring a certain option must not
be bunched together, otherwise the station will not be able to cope.
Consequently, the cars must be arranged in a sequence so that the capacity
of each station is never exceeded. For instance, if a particular station
can only cope with at most half of the cars passing along the line, the
sequence must be built so that at most 1 car in any 2 requires that option.
The problem has been shown to be NP-hard (Gent 1999).

This is the example given in Dincbas, et al. More instances can
be downloaded from CSPLib (problem prob001).


Dincbas et al., Dincbas, M., Simonis, H., and Van Hentenryck, P.
Solving the car-sequencing problem in constraint logic programming.
In 8th European Conference on Artificial Intelligence (ECAI 88) ,
Y. Kodratoff, Ed. Pitmann Publishing, London, Munich, Germany, 290-295, 1988

Keywords: mixed integer linear programming, mixed integer nonlinear
programming,
          production planning, car manufacturing, line problem
"""
import numpy as np
import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import ifthen


def main(mip=False):
    classData_recs = np.array(
        [
            [1, 1, 0, 1, 1, 0],
            [1, 0, 0, 0, 1, 0],
            [2, 0, 1, 0, 0, 1],
            [2, 0, 1, 0, 1, 0],
            [2, 1, 0, 1, 0, 0],
            [2, 1, 1, 0, 0, 0],
        ]
    )
    classData_recs = pd.DataFrame(
        classData_recs,
        columns=["numCars", "opt1", "opt2", "opt3", "opt4", "opt5"],
        index=["class1", "class2", "class3", "class4", "class5", "class6"],
    )
    no_of_cars = classData_recs.numCars.sum()
    classData_recs = classData_recs.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )

    m = Container(delayed_execution=True)

    # Sets
    p = Set(m, name="p", records=[f"pos{i}" for i in range(1, 11)])
    o = Set(m, name="o", records=[f"opt{i}" for i in range(1, 6)])
    c = Set(m, name="c", records=[f"class{i}" for i in range(1, 7)])

    # Parameter
    maxc = Parameter(
        m, name="maxc", domain=[o], records=np.array([1, 2, 1, 2, 1])
    )
    bs = Parameter(m, name="bs", domain=[o], records=np.array([2, 3, 3, 5, 5]))

    classData = Parameter(
        m, name="classData", domain=[c, "*"], records=classData_recs
    )

    if len(p) != no_of_cars:
        raise Exception("inconsistent number of cars")

    pp = Alias(m, name="pp", alias_with=p)

    # Sets
    blk = Set(m, name="blk", domain=[o, p])
    blkc = Set(m, name="blkc", domain=[o, p, pp])

    blkc[o, p, pp].where[Ord(p) <= Card(p) - bs[o] + Number(1)] = (
        Ord(pp) >= Ord(p)
    ) & (Ord(pp) < Ord(p) + bs[o])
    blk[o, p] = Sum(pp.where[blkc[o, p, pp]], Number(1))

    # Variables
    sumc = Variable(m, name="sumc", type="free", domain=[o, p])
    cp = Variable(m, name="cp", type="binary", domain=[c, p])
    op = Variable(m, name="op", type="free", domain=[o, p])
    v = Variable(m, name="v", type="free", domain=[o, p])
    obj = Variable(m, name="obj", type="free")

    if mip:
        v.type = "positive"
        op.type = "binary"

    # Equations
    defnumCars = Equation(m, name="defnumCars", domain=[c])
    defoneCar = Equation(m, name="defoneCar", domain=[p])
    defop = Equation(m, name="defop", domain=[o, p])
    defopLS = Equation(m, name="defopLS", domain=[o, p])
    defviol = Equation(m, name="defviol", domain=[o, p])
    defviolLS = Equation(m, name="defviolLS", domain=[o, p])
    defobj = Equation(m, name="defobj")
    defsumc = Equation(m, name="defsumc", domain=[o, p])

    defnumCars[c] = Sum(p, cp[c, p]) == classData[c, "numCars"]
    defoneCar[p] = Sum(c, cp[c, p]) == Number(1)
    defop[o, p] = Sum(c.where[classData[c, o]], cp[c, p]) <= op[o, p]
    defsumc[o, p] = sumc[o, p] == Sum(c.where[classData[c, o]], cp[c, p])
    defopLS[o, p] = op[o, p] == ifthen(sumc[o, p] >= 0.5, 1, 0)
    defviol[blk[o, p]] = Sum(blkc[blk, pp], op[o, pp]) <= maxc[o] + v[o, p]
    defviolLS[blk[o, p]] = v[o, p] == gams_math.Max(
        Sum(blkc[blk, pp], op[o, pp]) - maxc[o], Number(0)
    )

    defobj.definition = obj == Sum(blk[o, p], v[o, p])

    # Model
    carseqMIP = Model(
        m,
        name="carseqMIP",
        equations=[
            defnumCars,
            defoneCar,
            defop,
            defviol,
            defobj,
        ],
        problem="mip",
        sense=Sense.MIN,
        objective=obj,
    )
    carseqLS = Model(
        m,
        name="carseqLS",
        equations=[
            defnumCars,
            defoneCar,
            defsumc,
            defopLS,
            defviolLS,
            defobj,
        ],
        problem="minlp",
        sense=Sense.MIN,
        objective=obj,
    )

    m.addOptions({"optCr": 0})

    if mip:
        v.type = "positive"
        op.type = "binary"
        carseqMIP.solve()
    else:
        carseqLS.solve()

    rep = Parameter(m, name="rep", domain=[p, c, o])
    rep[p, c, o].where[(cp.l[c, p] > 0.5)] = classData[c, o]

    print("Objective Function Value: ", obj.records.level[0])

    import math

    assert math.isclose(carseqLS.objective_value, 11.0, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/carseq.py
GAMS-dev/gamspy
"""
Cross Entropy SAM Estimation (CESAM2)

CESAM2 illustrates a cross entropy technique for estimating the cells of a
consistent SAM assuming that the initial data are inconsistent and measured
with error. The method is applied to estimate the macro SAM used in CESAM.GMS.
Cell elements, some macro control totals, and row and column totals are assumed
to be measured with error. We assume that the user can specify prior estimates
of the values and standard errors of measurement for the cell values, macro
control totals, and row and column sums.

The original version of this code, CESAM.GMS, assumed that the SAM column
coefficients, A(i,j) = SAM(i,j)/SUM(i, SAM(i,j)), are treated as analogous to
probabilities and are included directly in the cross-entropy minimand. In this
version each SAM element, SAM(i,j), is assumed to be measured with error, and
all the errors are treated as probability-weighted sums of error support sets.
Only probabilities are included in the cross-entropy minimand, which is
consistent with the information-theoretic Bayesian approach to estimate
probabilities. The cost of this approach is that in CESAM2 there are many more
probabilities to be estimated. However, new solution algorithms are able to
solve large problems of this type, so size is no longer a serious constraint.

In the estimation procedure, we assume prior information on either:
 (1) values of cells, SAM(i,j), or
 (2) coefficients, A(i,j).

Errors can be treated as either:
 (1) additive       [e.g., SAM(i,j) = sam0(i,j) + err(i,j)], or
 (2) multiplicative [e.g., A(i,j)   = abar0(i,j)*EXP(err(i,j))]

where sam0(i,j) and abar0(i,j) are prior values of the cell value or
coefficient, and err(i,j) is the estimated measurement error.

In the first case, the prior mean of the errors is assumed to be zero. In the
second case, it will be one. In the first case, it is possible for the
posterior estimated cell value to change sign from the prior, while in the
second case the posterior estimated coefficient value can never change sign.

In the code below, we assume a prior on coefficients measured with
multiplicative errors for selected SAM accounts defined by the set acoeff(i).

Note that it is important to scale the SAM. Ideally, the SAM being estimated
should be scaled so that it does not contain values larger than about 1e3.

Note also that by default we use the GAMS intrinsic function centropy() in
the objective definition.  If you define NOCENTROPY (e.g. by running with
--NOCENTROPY = 1 on the command line) the cross-entropy function is written
explicitly using logs, etc.


References:

Robinson, S, Cattaneo, A, and El-Said, M, Updating and Estimating
a Social Accounting Matrix Using Cross Enthropy Methods. Economic
System Research 13, 1 (2001).

Golan, G, Judge, G, and Miller, D, Maximum Enthropy Econometrics.
John Wiley and Sons, 1996.

Judge, George G. and Ron C. Mittelhammer, An Information Theoretic Approach
to Econometrics. Cambridge: Cambridge University Press, 2012.

Programmed by Sherman Robinson, April 2013

Environment and Production Technology Division and
Development Strategy and Governance Division
International Food Policy Research Institute (IFPRI)
2033 K Street, N.W.
Washington, DC 20006 USA
Email: S.Robinson@CGIAR.ORG

Earlier version, CESAM, programmed by Sherman Robinson and Moataz El-Said,
November 2000.
Original version programmed by Sherman Robinson and Andrea Cattaneo.

Keywords: nonlinear programming, micro economics, cross entropy, social
accounting matrix
"""
import numpy as np

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import abs
from gamspy.math import centropy
from gamspy.math import exp
from gamspy.math import log


def main(is_centropy=False):
    m = Container(delayed_execution=True)

    SAM_recs = np.array(
        [
            [0, 14827.424, 0, 0, 2101.049, -0.327, 0, 0, 1488.157, 18416.303],
            [
                7917.504,
                0,
                0,
                0,
                6953.332,
                1564.500,
                2518.500,
                2597.798,
                0,
                20751.634,
            ],
            [9805.414, 0, 0, 0, 0, 0, 0, 0, 0, 9805.414],
            [0, 0, 3699.706, 0, 0, 33.000, 0, 0, 0, 3732.706],
            [0, 0, 6000.000, 3300.000, 0, 29.600, 0, 0, 200.000, 9687.915],
            [733.600, 357.400, 74.400, 165.200, 139.500, 0, 0, 0, 0, 1470.100],
            [0, 0, 0, 0, 0, 0, 0, 0, 1712.300, 1712.300],
            [
                0,
                0,
                0,
                150.000,
                649.156,
                -356.673,
                -406.200,
                0,
                2163.857,
                2200.140,
            ],
            [0, 5573.815, 0, 0, 0, 0, 0, 0, 0, 5573.815],
            [
                18456.518,
                20758.639,
                9805.414,
                3732.706,
                9643.037,
                1470.100,
                1712.300,
                2197.798,
                5573.815,
                0,
            ],
        ]
    )

    m.addOptions({"limCol": 0, "limRow": 0, "solPrint": "off"})

    # Set
    i = Set(
        m,
        name="i",
        records=[
            "ACT",
            "COM",
            "FAC",
            "ENT",
            "HOU",
            "GOV",
            "GIN",
            "CAP",
            "ROW",
            "TOTAL",
        ],
        description="i",
    )
    icoeff = Set(
        m,
        name="icoeff",
        domain=[i, i],
        description="SAM elements whose prior is specified as coefficients",
    )
    ival = Set(
        m,
        name="ival",
        domain=[i, i],
        description="SAM elements whose prior is specified as values",
    )
    NONZERO = Set(
        m,
        name="NONZERO",
        domain=[i, i],
        description="SAM elements that can be nonzero and hence estimated",
    )
    ii = Set(
        m, name="ii", domain=[i], description="all accounts in i except total"
    )
    macro = Set(
        m,
        name="macro",
        records=["gdpfc2", "gdp2"],
        description="macro controls",
    )

    # The set jwt defines the dimension of the support set for the error
    # distribution and the number of weights that must be estimated for each
    # error. In this case, we specify an uninformative prior for jwt1,
    # a normal prior for jwt2, and a general two-parameter distribution for
    # jwt3.
    jwt = Set(
        m,
        name="jwt",
        records=[str(ss) for ss in range(1, 8)],
        description="master set of possible weights",
    )
    jwt1 = Set(
        m,
        name="jwt1",
        domain=[jwt],
        records=[str(ss) for ss in range(1, 8)],
        description="set of weights for errors in column sums",
    )
    jwt2 = Set(
        m,
        name="jwt2",
        domain=[jwt],
        records=[str(ss) for ss in range(1, 6)],
        description="set of weights for errors in macro totals",
    )
    jwt3 = Set(
        m,
        name="jwt3",
        domain=[jwt],
        records=[str(ss) for ss in range(1, 4)],
        description="set of weights for errors in cell elements",
    )

    j = Alias(m, name="j", alias_with=i)
    jj = Alias(m, name="jj", alias_with=ii)
    ii[i] = Number(1)
    ii["Total"] = Number(0)

    # Parameters
    stderr1 = Parameter(
        m,
        name="stderr1",
        records=0.05,
        description="standard error of measurement for column sums",
    )
    stderr2 = Parameter(
        m,
        name="stderr2",
        records=0.05,
        description="standard error of measurement for macro totals",
    )
    stderr3 = Parameter(
        m,
        name="stderr3",
        records=0.25,
        description="standard error of measurement for cell elements",
    )
    scalesam = Parameter(
        m,
        name="scalesam",
        records=1e3,
        description="scale factor for scaling initial SAM",
    )
    delta = Parameter(
        m,
        name="delta",
        records=1e-8,
        description="small number for CE objective function",
    )

    # Prior unbalanced proto-SAM
    # ########################    SAM DATABASE       ########################
    # The SAM is unbalanced by adding new rows with bad data
    SAM = Parameter(
        m,
        name="SAM",
        domain=[i, j],
        records=SAM_recs,
        description="prior unbalanced social accounting matrix",
    )

    # Parameters
    SAM0 = Parameter(
        m,
        name="SAM0",
        domain=[i, j],
        description="unbalance prior or proto-SAM transactions matrix",
    )
    SAMBALCHK = Parameter(
        m,
        name="SAMBALCHK",
        domain=[i],
        description="column sums minus row sums in the SAM",
    )
    Abar0 = Parameter(
        m,
        name="Abar0",
        domain=[i, j],
        description="prior SAM coefficient matrix",
    )
    ColSum0 = Parameter(
        m,
        name="ColSum0",
        domain=[i],
        description="targets for macro SAM column totals",
    )
    macrov0 = Parameter(
        m,
        name="macrov0",
        domain=[macro],
        description="target values for macro aggregates",
    )
    vbar1 = Parameter(
        m,
        name="vbar1",
        domain=[i, jwt],
        description="error support set 1 for column sums",
    )
    vbar2 = Parameter(
        m,
        name="vbar2",
        domain=[macro, jwt],
        description="error support set 2 for macro aggregates",
    )
    vbar3 = Parameter(
        m,
        name="vbar3",
        domain=[i, j, jwt],
        description="error support set 3 for SAM elements",
    )
    wbar1 = Parameter(
        m,
        name="wbar1",
        domain=[i, jwt],
        description="weights on error support set 1 for column totals",
    )
    wbar2 = Parameter(
        m,
        name="wbar2",
        domain=[macro, jwt],
        description="weights on error support set 2 for macro aggregates",
    )
    wbar3 = Parameter(
        m,
        name="wbar3",
        domain=[i, j, jwt],
        description="weights on error support set 3 for SAM elements",
    )
    sigmay1 = Parameter(
        m,
        name="sigmay1",
        domain=[i],
        description="prior standard error of column sums",
    )
    sigmay2 = Parameter(
        m,
        name="sigmay2",
        domain=[macro],
        description="prior standard error of macro aggregates",
    )
    sigmay3 = Parameter(
        m,
        name="sigmay3",
        domain=[i, j],
        description="prior standard error of SAM elements",
    )

    # macro control totals
    gdp0 = Parameter(m, name="gdp0", description="base GDP")
    gdpfc0 = Parameter(m, name="gdpfc0", description="base GDP at factor cost")
    gdp00 = Parameter(m, name="gdp00", description="GDP from final SAM")
    gdpfc00 = Parameter(
        m, name="gdpfc00", description="GDP at factor cost from final SAM"
    )

    # ################# Initializing Parameters #################
    SAM["TOTAL", jj] = Sum(ii, SAM[ii, jj])
    SAM[ii, "TOTAL"] = Sum(jj, SAM[ii, jj])

    # Divide SAM entries by scalesam for better scaling.
    # The SAM is scaled to enhance solver efficiency. Nonlinear solvers are
    # more efficient if variables are scaled to be around 1.
    SAM[i, j] = SAM[i, j] / scalesam
    Abar0[ii, jj].where[SAM["TOTAL", jj]] = SAM[ii, jj] / SAM["TOTAL", jj]
    SAM0[ii, jj] = SAM[ii, jj]
    SAM0["TOTAL", jj] = Sum(ii, SAM[ii, jj])
    SAM0[ii, "TOTAL"] = Sum(jj, SAM[ii, jj])
    SAMBALCHK[jj] = SAM0["TOTAL", jj] - SAM0[jj, "TOTAL"]

    # display Abar0, SAM0, SAMBALCHK

    # ########################  CROSS ENTROPY  ##############################
    # Parameters
    NegSam = Parameter(
        m, name="NegSam", domain=[i, j], description="negative SAM values"
    )
    chkset = Parameter(
        m,
        name="chkset",
        domain=[i, j],
        description="check coefficient and value sets",
    )

    # identify negative SAM entries for information
    NegSam[i, j].where[SAM0[i, j] < 0] = SAM[i, j]

    # Define set of elements of SAM that can be nonzero. In this case, only
    # elements which are nonzero in initial SAM.
    NONZERO[ii, jj].where[Abar0[ii, jj]] = Number(1)

    # SAM cells with priors on coefficients. We will also assume they have
    # multiplicative errors.
    acoeff = Set(
        m,
        name="acoeff",
        domain=[i],
        records=["act", "fac", "ent", "hou"],
        description="accounts with prior on column coefficients",
    )

    icoeff[ii, acoeff].where[NONZERO[ii, acoeff]] = Number(1)
    ival[ii, jj].where[(SAM0[ii, jj]) & (~icoeff[ii, jj])] = Number(1)
    chkset[ii, jj] = (
        Number(1).where[ival[ii, jj]]
        + Number(1).where[icoeff[ii, jj]]
        - Number(1).where[NONZERO[ii, jj]]
    )

    # display icoeff, ival, chkset

    # Note that target column sums are being set to average of initial
    # row and column sums. Initial column sums or other values
    # could have been used instead, depending on knowledge of data quality
    # and any other prior information.
    ColSum0[ii] = (SAM[ii, "total"] + SAM["total", ii]) / 2
    gdpfc0.assignment = SAM["fac", "act"]
    gdp0.assignment = (
        SAM["fac", "act"]
        + SAM["gov", "act"]
        - SAM["act", "gov"]
        + SAM["gov", "com"]
    )

    macrov0["gdp2"] = gdp0
    macrov0["gdpfc2"] = gdpfc0

    # Set standard deviation for errors on column/row totals
    sigmay1[ii] = stderr1 * ColSum0[ii]

    # Set constants for 7-weight error distribution
    # (uninformative uniform prior)
    vbar1[ii, "1"] = -3 * sigmay1[ii]
    vbar1[ii, "2"] = -2 * sigmay1[ii]
    vbar1[ii, "3"] = -1 * sigmay1[ii]
    vbar1[ii, "4"] = 0
    vbar1[ii, "5"] = 1 * sigmay1[ii]
    vbar1[ii, "6"] = 2 * sigmay1[ii]
    vbar1[ii, "7"] = 3 * sigmay1[ii]
    wbar1[ii, jwt1] = 1 / 7

    # Set standard deviation for errors on macro aggregates
    sigmay2[macro] = stderr2 * macrov0[macro]

    # Set constants for 5-weight error distribution (normal prior)
    vbar2[macro, "1"] = -3 * sigmay2[macro]
    vbar2[macro, "2"] = -1.5 * sigmay2[macro]
    vbar2[macro, "3"] = 0
    vbar2[macro, "4"] = 1.5 * sigmay2[macro]
    vbar2[macro, "5"] = 3 * sigmay2[macro]
    wbar2[macro, "1"] = 1 / 162
    wbar2[macro, "2"] = 16 / 81
    wbar2[macro, "3"] = 48 / 81
    wbar2[macro, "4"] = 16 / 81
    wbar2[macro, "5"] = 1 / 162

    for record in SAM.records.itertuples(index=False):
        if record.i != "TOTAL" and record.j != "TOTAL":
            #  Set standard deviation for errors on cell values or coefficients
            #  Additive errors
            sigmay3[record.i, record.j].where[ival[record.i, record.j]] = (
                stderr3 * abs(SAM0[record.i, record.j])
            )
            #  Multiplicative errors
            sigmay3[record.i, record.j].where[
                icoeff[record.i, record.j]
            ] = stderr3
            vbar3[record.i, record.j, "1"] = -3 * sigmay3[record.i, record.j]
            vbar3[record.i, record.j, "2"] = 0
            vbar3[record.i, record.j, "3"] = 3 * sigmay3[record.i, record.j]
            wbar3[record.i, record.j, "1"] = 1 / 18
            wbar3[record.i, record.j, "2"] = 16 / 18
            wbar3[record.i, record.j, "3"] = 1 / 18

    # Variables
    A = Variable(
        m,
        name="A",
        domain=[i, j],
        description="posterior SAM coefficient matrix",
    )
    TSAM = Variable(
        m,
        name="TSAM",
        domain=[i, j],
        description="posterior matrix of SAM transactions",
    )
    MACROV = Variable(
        m, name="MACROV", domain=[macro], description="macro aggregates"
    )
    Y = Variable(m, name="Y", domain=[i], description="row Sum of SAM")
    ERR1 = Variable(
        m, name="ERR1", domain=[i], description="error value on column sums"
    )
    ERR2 = Variable(
        m,
        name="ERR2",
        domain=[macro],
        description="error value for macro aggregates",
    )
    ERR3 = Variable(
        m,
        name="ERR3",
        domain=[i, j],
        description="error value for SAM elements",
    )
    W1 = Variable(
        m,
        name="W1",
        domain=[i, jwt],
        description="error weights for column sums",
    )
    W2 = Variable(
        m,
        name="W2",
        domain=[macro, jwt],
        description="error weights for macro aggregates",
    )
    W3 = Variable(
        m,
        name="W3",
        domain=[i, j, jwt],
        description="error weights for cell elements",
    )
    DENTROPY = Variable(
        m, name="DENTROPY", description="entropy difference (objective)"
    )

    # ########################## INITIALIZE VARIABLES ##################
    A.l[ii, jj] = Abar0[ii, jj]
    TSAM.l[ii, jj] = SAM0[ii, jj]
    Y.l[ii] = ColSum0[ii]
    MACROV.l[macro] = macrov0[macro]
    ERR1.l[ii] = 0.0
    ERR2.l[macro] = 0.0
    ERR3.l[ii, jj].where[NONZERO[ii, jj]] = 0.0
    W1.l[ii, jwt] = wbar1[ii, jwt]
    W2.l[macro, jwt] = wbar2[macro, jwt]
    W3.l[ii, jj, jwt].where[NONZERO[ii, jj]] = wbar3[ii, jj, jwt]
    DENTROPY.L = 0.0

    # ############ CORE EQUATIONS ############
    # Equations
    ROWSUMEQ = Equation(
        m,
        name="ROWSUMEQ",
        domain=[i],
        description="rowsum with error",
    )
    ROWSUM = Equation(m, name="ROWSUM", domain=[i], description="row sums")
    COLSUM = Equation(m, name="COLSUM", domain=[j], description="column sums")
    SAMCOEF = Equation(
        m,
        name="SAMCOEF",
        domain=[i, j],
        description="define SAM coefficients",
    )
    TSAMEQ = Equation(
        m,
        name="TSAMEQ",
        domain=[i, j],
        description="SAM elements in values",
    )
    ASAMEQ = Equation(
        m,
        name="ASAMEQ",
        domain=[i, j],
        description="SAM coefficients",
    )
    GDPFCDEF = Equation(
        m,
        name="GDPFCDEF",
        description="define GDP at factor cost",
    )
    GDPDEF = Equation(
        m,
        name="GDPDEF",
        description="define GDP at market prices",
    )
    MACROEQ = Equation(
        m,
        name="MACROEQ",
        domain=[macro],
        description="macro aggregates with error",
    )
    ERROR1EQ = Equation(
        m,
        name="ERROR1EQ",
        domain=[i],
        description="definition of error term 1",
    )
    ERROR2EQ = Equation(
        m,
        name="ERROR2EQ",
        domain=[macro],
        description="definition of error term 2",
    )
    ERROR3EQ = Equation(
        m,
        name="ERROR3EQ",
        domain=[i, j],
        description="definition of error term 3",
    )
    SUMW1 = Equation(
        m,
        name="SUMW1",
        domain=[i],
        description="Sum of weights 1",
    )
    SUMW2 = Equation(
        m,
        name="SUMW2",
        domain=[macro],
        description="Sum of weights 2",
    )
    SUMW3 = Equation(
        m,
        name="SUMW3",
        domain=[i, j],
        description="Sum of weights 3",
    )
    ENTROPY = Equation(
        m,
        name="ENTROPY",
        description="entropy difference definition",
    )

    # Row and column sums estimation and balance
    ROWSUMEQ[ii] = Y[ii] == ColSum0[ii] + ERR1[ii]

    ROWSUM[ii] = Sum(jj, TSAM[ii, jj]) == Y[ii]

    COLSUM[jj] = Sum(ii, TSAM[ii, jj]) == Y[jj]

    # Estimating SAM elements from prior values or coefficients
    SAMCOEF[ii, jj].where[NONZERO[ii, jj]] = TSAM[ii, jj] == A[ii, jj] * Y[jj]

    TSAMEQ[ii, jj].where[ival[ii, jj]] = (
        TSAM[ii, jj] == SAM0[ii, jj] + ERR3[ii, jj]
    )

    ASAMEQ[ii, jj].where[icoeff[ii, jj]] = A[ii, jj] == Abar0[ii, jj] * exp(
        ERR3[ii, jj]
    )

    # Macro aggregates measured with error
    GDPFCDEF.definition = MACROV["gdpfc2"] == TSAM["fac", "act"]

    GDPDEF.definition = (
        MACROV["gdp2"]
        == TSAM["fac", "act"]
        + TSAM["gov", "act"]
        - TSAM["act", "gov"]
        + TSAM["gov", "com"]
    )

    MACROEQ[macro] = MACROV[macro] == macrov0[macro] + ERR2[macro]

    # Definition of errors as probability weighted sums of support sets
    ERROR1EQ[ii] = ERR1[ii] == Sum(jwt1, W1[ii, jwt1] * vbar1[ii, jwt1])

    ERROR2EQ[macro] = ERR2[macro] == Sum(
        jwt2, W2[macro, jwt2] * vbar2[macro, jwt2]
    )

    ERROR3EQ[ii, jj].where[NONZERO[ii, jj]] = ERR3[ii, jj] == Sum(
        jwt3, W3[ii, jj, jwt3] * vbar3[ii, jj, jwt3]
    )

    # Probabilities must Sum to one
    SUMW1[ii] = Sum(jwt1, W1[ii, jwt1]) == 1

    SUMW2[macro] = Sum(jwt2, W2[macro, jwt2]) == 1

    SUMW3[ii, jj].where[NONZERO[ii, jj]] = Sum(jwt3, W3[ii, jj, jwt3]) == 1

    if not is_centropy:
        # Cross-entropy objective function, explicit version
        ENTROPY.definition = DENTROPY == (
            Sum(
                Domain(ii, jj, jwt3).where[NONZERO[ii, jj]],
                W3[ii, jj, jwt3]
                * (
                    log(W3[ii, jj, jwt3] + delta)
                    - log(wbar3[ii, jj, jwt3] + delta)
                ),
            )
            + Sum(
                [ii, jwt1],
                W1[ii, jwt1]
                * (log(W1[ii, jwt1] + delta) - log(wbar1[ii, jwt1] + delta)),
            )
            + Sum(
                [macro, jwt2],
                W2[macro, jwt2]
                * (
                    log(W2[macro, jwt2] + delta)
                    - log(wbar2[macro, jwt2] + delta)
                ),
            )
        )
    else:
        ENTROPY.definition = DENTROPY == (
            Sum(
                Domain(ii, jj, jwt3).where[NONZERO[ii, jj]],
                centropy(W3[ii, jj, jwt3], wbar3[ii, jj, jwt3]),
            )
            + Sum([ii, jwt1], centropy(W1[ii, jwt1], wbar1[ii, jwt1]))
            + Sum([macro, jwt2], centropy(W2[macro, jwt2], wbar2[macro, jwt2]))
        )

    # Define bounds for cell values and fix variables not
    # included in the estimation
    A.fx[ii, jj].where[~NONZERO[ii, jj]] = 0
    TSAM.fx[ii, jj].where[~NONZERO[ii, jj]] = 0

    # Upper and lower bounds on the error weights
    W1.lo[ii, jwt1] = 0
    W1.up[ii, jwt1] = 1
    W2.lo[macro, jwt2] = 0
    W2.up[macro, jwt2] = 1
    W3.lo[ii, jj, jwt3].where[NONZERO[ii, jj]] = 0
    W3.up[ii, jj, jwt3].where[NONZERO[ii, jj]] = 1
    W3.fx[ii, jj, jwt3].where[~NONZERO[ii, jj]] = 0

    SAMENTROP = Model(
        m,
        name="SAMENTROP",
        equations=m.getEquations(),
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=DENTROPY,
    )

    m.addOptions(
        {
            "limRow": 100,
            "limCol": 0,
            "solPrint": "on",
            "domLim": 100,
            "holdfixedasync": 1,
        }
    )

    SAMENTROP.solve()

    # Parameters for reporting results
    Macsam1 = Parameter(
        m,
        name="Macsam1",
        domain=[i, j],
        description="assigned new balanced SAM flows from CE",
    )
    Macsam2 = Parameter(
        m,
        name="Macsam2",
        domain=[i, j],
        description="balanced SAM flows in original units",
    )
    percent1 = Parameter(
        m,
        name="percent1",
        domain=[i, j],
        description="percent change of new SAM from original SAM",
    )
    Diffrnce = Parameter(
        m,
        name="Diffrnce",
        domain=[i, j],
        description="differnce btw original SAM and final SAM in values",
    )
    ANEW = Parameter(m, name="ANEW", domain=[i, j])

    Macsam1[ii, jj] = TSAM.l[ii, jj]
    Macsam1["total", jj] = Sum(ii, Macsam1[ii, jj])
    Macsam1[ii, "total"] = Sum(jj, Macsam1[ii, jj])
    Macsam2[i, j] = Macsam1[i, j] * scalesam
    percent1[i, j].where[SAM0[i, j]] = (
        100 * (Macsam1[i, j] - SAM0[i, j]) / SAM0[i, j]
    )
    Diffrnce[i, j] = Macsam1[i, j] - SAM0[i, j]
    SAMBALCHK[jj] = TSAM.l["TOTAL", jj] - TSAM.l[jj, "TOTAL"]

    gdp00.assignment = (
        Macsam1["fac", "act"]
        + Macsam1["gov", "act"]
        - Macsam1["act", "gov"]
        + Macsam1["gov", "com"]
    )
    gdpfc00.assignment = Macsam1["fac", "act"]

    # print some stuff
    ANEW[ii, jj] = A.l[ii, jj]
    ANEW["total", jj] = Sum(ii, A.l[ii, jj])
    ANEW[ii, "total"] = Sum(jj, A.l[ii, jj])
    Abar0["total", jj] = Sum(ii, Abar0[ii, jj])
    Abar0[ii, "total"] = Sum(jj, Abar0[ii, jj])

    meanerr1 = Parameter(m, name="meanerr1")
    meanerr2 = Parameter(m, name="meanerr2")
    meanerr1.assignment = Sum(ii, abs(ERR1.l[ii])) / Card(ii)
    meanerr2.assignment = Sum(macro, abs(ERR2.l[macro])) / Card(macro)

    print("Objective Function Value: ", SAMENTROP.objective_value)
    print("meanerr1: ", round(meanerr1.records.value[0], 3))
    print("meanerr2: ", round(meanerr2.records.value[0], 3))

    import math

    assert math.isclose(SAMENTROP.objective_value, 0.508, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/cesam2.py
GAMS-dev/gamspy
"""
   Find the chain (of uniform density) of length L suspended between two
   points with minimal potential energy.

   This model is from the COPS benchmarking suite.
   See http://www-unix.mcs.anl.gov/~more/cops/.

   The number of intervals for the discretization can be specified using
   the command line parameter --nh. COPS performance tests have been
   reported for nh = 50, 100, 200, 400

   Tested with nh=3000, 4000, 5000     May 26, 2005

   References:
   Neculai Andrei, "Models, Test Problems and Applications for
   Mathematical Programming". Technical Press, Bucharest, 2003.
   Application A7, page 350.

   Dolan, E D, and More, J J, Benchmarking Optimization Software with COPS.
   Tech. rep., Mathematics and Computer Science Division, 2000.

   Cesari, L, Optimization - Theory and Applications. Springer Verlag, 1983.
"""
import sys

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def main():
    m = Container(delayed_execution=True)

    n_rec = int(sys.argv[1]) if len(sys.argv) > 1 else 400

    # SETS #
    nh = Set(m, name="nh", records=[f"i{i}" for i in range(n_rec + 1)])

    # ALIASES #
    i = Alias(m, name="i", alias_with=nh)

    # SCALARS #
    L = Parameter(
        m, name="L", records=4, description="length of the suspended chain"
    )
    a = Parameter(
        m, name="a", records=1, description="height of the chain at t=0 (left)"
    )
    b = Parameter(
        m, name="b", records=3, description="height of the chain at t=1 (left)"
    )
    tf = Parameter(
        m, name="tf", records=1, description="ODEs defined in [0 tf]"
    )
    h = Parameter(m, name="h", description="uniform interval length")
    n = Parameter(m, name="n", description="number of subintervals")
    tmin = Parameter(m, name="tmin")

    if b.toValue() > a.toValue():
        tmin.assignment = 0.25
    else:
        tmin.assignment = 0.75

    n.assignment = Card(nh) - 1
    h.assignment = tf / n

    # VARIABLES #
    x = Variable(m, name="x", domain=[i], description="height of the chain")
    u = Variable(m, name="u", domain=[i], description="derivative of x")
    energy = Variable(m, name="energy", description="potential energy")

    x.fx["i0"] = a
    x.fx[f"i{n_rec}"] = b

    x.l[i] = (
        4
        * gams_math.abs(b - a)
        * ((Ord(i) - 1) / n)
        * (0.5 * ((Ord(i) - 1) / n) - tmin)
        + a
    )
    u.l[i] = 4 * gams_math.abs(b - a) * (((Ord(i) - 1) / n) - tmin)

    # EQUATIONS #
    obj = Equation(m, name="obj", type="regular")
    x_eqn = Equation(m, name="x_eqn", type="regular", domain=[i])
    length_eqn = Equation(m, name="length_eqn", type="regular")

    obj.definition = energy == 0.5 * h * Sum(
        nh[i.lead(1)],
        x[i] * gams_math.sqrt(1 + sqr(u[i]))
        + x[i.lead(1)] * gams_math.sqrt(1 + sqr(u[i.lead(1)])),
    )

    x_eqn[i.lead(1)] = x[i.lead(1)] == x[i] + 0.5 * h * (u[i] + u[i.lead(1)])

    length_eqn.definition = (
        0.5
        * h
        * Sum(
            nh[i.lead(1)],
            gams_math.sqrt(1 + sqr(u[i]))
            + gams_math.sqrt(1 + sqr(u[i.lead(1)])),
        )
        == L
    )

    chain = Model(
        m,
        name="chain",
        equations=m.getEquations(),
        problem="nlp",
        sense="min",
        objective=energy,
    )

    chain.solve()
    print("Objective Function Value:  ", round(energy.toValue(), 4))

    import math

    assert math.isclose(chain.objective_value, 5.0723, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/chain.py
GAMS-dev/gamspy
"""
Substitution and Structural Change (CHENERY)

This model follows conventional input-output formulations for production
with nonlinear demand functions, import and export functions and production
functions for direct factor use.


Chenery, H B, and Raduchel, W J, Substitution and Structural Change.
In Chenery, H B, Ed, Structural Change and Development Policy. Oxford
University Press, New York and Oxford, 1979.

Keywords: nonlinear programming, econometrics, economic development
"""
from pathlib import Path

from gamspy import Container
from gamspy import Model
from gamspy import Number
from gamspy import Problem
from gamspy import Sense
from gamspy import Sum


def main():
    container = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/chenery.gdx",
    )

    # Sets
    i, t, j = container.getSymbols(["i", "t", "j"])

    # Parameters
    (
        aio,
        pdat,
        ddat,
        tdat,
        mew,
        xsi,
        gam,
        alp,
        ynot,
        sig,
        thet,
        rho,
        deli,
        efy,
    ) = container.getSymbols(
        [
            "aio",
            "pdat",
            "ddat",
            "tdat",
            "mew",
            "xsi",
            "gam",
            "alp",
            "ynot",
            "sig",
            "thet",
            "rho",
            "del",
            "efy",
        ]
    )
    lbar, plab, kbar, dbar = container.getSymbols(
        ["lbar", "plab", "kbar", "dbar"]
    )

    # Variables
    x, v, y, p, l, k, e = container.getSymbols(
        ["x", "v", "y", "p", "l", "k", "e"]
    )
    m, g, h, pk, pi, pd, td, vv = container.getSymbols(
        ["m", "g", "h", "pk", "pi", "pd", "td", "vv"]
    )

    # Equations
    dty, mb, tb, dg, dh, dem = container.getSymbols(
        ["dty", "mb", "tb", "dg", "dh", "dem"]
    )
    lc, kc, sup, fpr, dvv, dl, dk, dv = container.getSymbols(
        ["lc", "kc", "sup", "fpr", "dvv", "dl", "dk", "dv"]
    )

    dty.definition = td == Sum(i, y[i])
    mb[i] = x[i] >= y[i] + Sum(j, aio[i, j] * x[j]) + (e[i] - m[i]).where[t[i]]
    tb.definition = Sum(t, g[t] * m[t] - h[t] * e[t]) <= dbar
    dg[t] = g[t] == mew[t] + xsi[t] * m[t]
    dh[t] = h[t] == gam[t] - alp[t] * e[t]
    dem[i] = y[i] == ynot[i] * (pd * p[i]) ** thet[i]
    lc.definition = Sum(i, l[i] * x[i]) <= lbar
    kc.definition = Sum(i, k[i] * x[i]) == kbar
    sup[i] = p[i] == Sum(j, aio[j, i] * p[j]) + v[i]
    fpr.definition = pi == pk / plab
    dvv[i].where[sig[i] != 0] = vv[i] == (pi * (1 - deli[i]) / deli[i]) ** (
        -rho[i] / (1 + rho[i])
    )
    dl[i] = (
        l[i] * efy[i]
        == ((deli[i] / vv[i] + (1 - deli[i])) ** (1 / rho[i])).where[
            sig[i] != 0
        ]
        + Number(1).where[sig[i] == 0]
    )
    dk[i] = (
        k[i] * efy[i]
        == ((deli[i] + (1 - deli[i]) * vv[i]) ** (1 / rho[i])).where[
            sig[i] != 0
        ]
        + deli[i].where[sig[i] == 0]
    )
    dv[i] = v[i] == pk * k[i] + plab * l[i]

    # Model chenrad 'chenery raduchel model' / all /;
    chenrad = Model(
        container,
        name="chenrad",
        equations=container.getEquations(),
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=td,
    )

    y.up[i] = 2000
    x.up[i] = 2000
    e.up[t] = 400
    m.up[t] = 400
    g.up[t] = 4
    h.up[t] = 4
    p.up[i] = 100
    p.lo[i] = 0.1
    l.up[i] = 1
    k.up[i] = 1
    pk.lo.assignment = 0.25
    pk.up.assignment = 4
    pi.lo.assignment = 0.25
    pi.up.assignment = 4
    v.up[i] = 100
    vv.lo[i] = 0.001

    # select coefficient values for this run
    mew[t] = 1
    xsi[t] = tdat["medium", "xsi", t]
    gam[t] = tdat["medium", "gam", t]
    alp[t] = tdat["medium", "alp", t]
    ynot[i] = ddat["medium", "ynot", i]
    thet[i] = ddat["medium", "p-elas", i]
    sig[i] = pdat["medium", "a", "subst", i]
    deli[i] = pdat["medium", "a", "distr", i]
    efy[i] = pdat["medium", "a", "effic", i]
    rho[i].where[sig[i] != 0] = 1 / sig[i] - 1

    # * initial values for variables
    y.l[i] = 250
    x.l[i] = 200
    e.l[t] = 0
    m.l[t] = 0
    g.l[t] = mew[t] + xsi[t] * m.l[t]
    h.l[t] = gam[t] - alp[t] * e.l[t]
    pd.l.assignment = 0.3
    p.l[i] = 3
    pk.l.assignment = 3.5
    pi.l.assignment = pk.l / plab

    vv.l[i].where[sig[i]] = (pi.l * (1 - deli[i]) / deli[i]) ** (
        -rho[i] / (1 + rho[i])
    )
    l.l[i] = (
        ((deli[i] / vv.l[i] + (1 - deli[i])) ** (1 / rho[i])).where[
            sig[i] != 0
        ]
        + Number(1).where[sig[i] == 0]
    ) / efy[i]
    k.l[i] = (
        ((deli[i] + (1 - deli[i]) * vv.l[i]) ** (1 / rho[i])).where[
            sig[i] != 0
        ]
        + deli[i].where[sig[i] == 0]
    ) / efy[i]
    v.l[i] = pk.l * k.l[i] + plab * l.l[i]
    pd.lo.assignment = 0.01
    p.lo[i] = 0.1

    chenrad.solve()

    import math

    assert math.isclose(chenrad.objective_value, 1058.9199, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/chenery.py
GAMS-dev/gamspy
"""
Optimal design of an electrical circuit.

Ratschek, H., Rokne, J., A circuit design problem. J. Global Opt., 3,
1993, pp.501.

Neculai Andrei, "Models, Test Problems and Applications for
Mathematical Programming". Technical Press, Bucharest, 2003.
Application A34, pp.397.
"""
import numpy as np

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Variable


def main():
    cont = Container(delayed_execution=True)

    # SETS #
    n = Set(cont, name="n", records=[f"c{c}" for c in range(1, 5)])
    m = Set(cont, name="m", records=[f"r{r}" for r in range(1, 6)])

    # PARAMETER #
    g = Parameter(
        cont,
        name="g",
        domain=[m, n],
        records=np.array(
            [
                [0.4850, 0.7520, 0.8690, 0.9820],
                [0.3690, 1.2540, 0.7030, 1.4550],
                [5.2095, 10.0677, 22.9274, 20.2153],
                [23.3037, 101.7790, 111.4610, 191.2670],
                [28.5132, 111.8467, 134.3884, 211.4823],
            ]
        ),
    )

    # VARIABLES #
    x1 = Variable(cont, name="x1")
    x2 = Variable(cont, name="x2")
    x3 = Variable(cont, name="x3")
    x4 = Variable(cont, name="x4")
    x5 = Variable(cont, name="x5")
    x6 = Variable(cont, name="x6")
    x7 = Variable(cont, name="x7")
    x8 = Variable(cont, name="x8")
    x9 = Variable(cont, name="x9")
    x10 = Variable(cont, name="x10")
    obj = Variable(cont, name="obj")

    # EQUATIONS #
    e1 = Equation(cont, name="e1", type="regular", domain=[n])
    e2 = Equation(cont, name="e2", type="regular", domain=[n])
    e3 = Equation(cont, name="e3", type="regular", domain=[n])
    e4 = Equation(cont, name="e4", type="regular", domain=[n])
    e = Equation(cont, name="e", type="regular")
    eobj = Equation(cont, name="eobj", type="regular")

    e1[n] = (
        g["r4", n] * x2
        - x10
        + (1 - x1 * x2)
        * x3
        * (
            gams_math.exp(
                x5
                * (
                    g["r1", n]
                    - g["r3", n] * x7 / 1000
                    - g["r5", n] * x8 / 1000
                )
            )
            - 1
        )
        <= g["r5", n]
    )

    e2[n] = (
        -g["r4", n] * x2
        - x10
        - (1 - x1 * x2)
        * x3
        * (
            gams_math.exp(
                x5
                * (
                    g["r1", n]
                    - g["r3", n] * x7 / 1000
                    - g["r5", n] * x8 / 1000
                )
            )
            - 1
        )
        <= -g["r5", n]
    )

    e3[n] = (
        -g["r5", n] * x1
        - x10
        + (1 - x1 * x2)
        * x4
        * (
            gams_math.exp(
                x6
                * (
                    g["r1", n]
                    - g["r2", n]
                    - g["r3", n] * x7 / 1000
                    + g["r4", n] * x9 / 1000
                )
            )
            - 1
        )
        <= -g["r4", n]
    )

    e4[n] = (
        g["r5", n] * x1
        - x10
        - (1 - x1 * x2)
        * x4
        * (
            gams_math.exp(
                x6
                * (
                    g["r1", n]
                    - g["r2", n]
                    - g["r3", n] * x7 / 1000
                    + g["r4", n] * x9 / 1000
                )
            )
            - 1
        )
        <= g["r4", n]
    )

    e.definition = x1 * x3 - x2 * x4 == 0

    eobj.definition = obj == x10

    # Bounds on variables
    x1.lo.assignment = 0
    x1.up.assignment = 10
    x2.lo.assignment = 0
    x2.up.assignment = 10
    x3.lo.assignment = 0
    x3.up.assignment = 10
    x4.lo.assignment = 0
    x4.up.assignment = 10
    x5.lo.assignment = 0
    x5.up.assignment = 10
    x6.lo.assignment = 0
    x6.up.assignment = 10
    x7.lo.assignment = 0
    x7.up.assignment = 10
    x8.lo.assignment = 0
    x8.up.assignment = 10
    x9.lo.assignment = 0
    x9.up.assignment = 10
    x10.lo.assignment = 0
    x10.up.assignment = 10

    # Initial point
    x1.l.assignment = 0.7
    x2.l.assignment = 0.38
    x3.l.assignment = 0.8
    x4.l.assignment = 1.5
    x5.l.assignment = 6
    x6.l.assignment = 6
    x7.l.assignment = 4
    x8.l.assignment = 1
    x9.l.assignment = 1.6
    x10.l.assignment = 1

    circuit = Model(
        cont,
        name="circuit",
        equations=cont.getEquations(),
        problem="nlp",
        sense="min",
        objective=obj,
    )

    circuit.solve()

    import math

    assert math.isclose(circuit.objective_value, 0, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/circuit.py
GAMS-dev/gamspy
"""
Optimal control problem with a nonlinear dynamic constraint and boundary
conditions solved as a General Nonlinear Programming Problem.

Divya Garg, et al., Direct trajectory optimization and costate estimation of
finite-horizon and infinite-horizon optimal control problems using a
Radau pseudospectral method. Computational optimization and Applications,
vol.49, nr. 2, June 2011, pp. 335-358.
"""
import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # SETS #
    n = Set(m, name="n", records=["state1"], description="states")
    k = Set(m, name="k", records=[f"t{t}" for t in range(1, 101)])
    ku = Set(m, name="ku", domain=[k], description="control horizon")
    ki = Set(m, name="ki", domain=[k], description="initial ")
    kt = Set(m, name="kt", domain=[k], description="terminal period")

    ku[k] = Number(1).where[Ord(k) < Card(k)]
    ki[k] = Number(1).where[Ord(k) == 1]
    kt[k] = ~ku[k]

    # PARAMETERS #
    rk = Parameter(m, name="rk", records=0.01, description="penalty control")
    _ = Parameter(
        m,
        name="xinit",
        domain=[n],
        records=[("state1", 2)],
        description="initial value",
    )

    # VARIABLES #
    x = Variable(m, name="x", domain=[n, k], description="state variable")
    u = Variable(m, name="u", domain=[k], description="control variable")
    j = Variable(m, name="j", description="criterion")

    # EQUATIONS #
    cost = Equation(
        m, name="cost", type="regular", description="criterion definition"
    )
    stateq = Equation(
        m,
        name="stateq",
        type="regular",
        domain=[n, k],
        description="state equation",
    )

    cost.definition = j == 0.5 * Sum(
        [k, n], (x[n, k]) + 0.5 * Sum(ku, (u[ku]) * rk * (u[ku]))
    )

    stateq[n, k.lead(1)] = x[n, k.lead(1)] == 2 * x[n, k] + 2 * u[
        k
    ] * gams_math.sqrt(x[n, k])

    control3 = Model(
        m,
        name="control3",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=j,
    )

    control3.solve()

    print("x:  \n", x.pivot().round(3))
    print("u:  \n", u.records.level.round(3))

    import math

    assert math.isclose(control3.objective_value, 0, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/control3.py
GAMS-dev/gamspy
"""
Combustion of propan in air.

Hiebert's (1983) reduced variant.
Hiebert, K.L., An evaluation of mathematical software that solves systems
of nonlinear equations. ACM Transactions on Mathematical Software,
vol. 8, 1983, pp.5-20.

Shacham, M., Brauner, N., Cutlip, M.B., (2002) A web-based library for
testing performance of numerical software for solving nonlinear algebraic
equations. Computers & Chemical Engineering, vol. 26, 2002, pp.547-554.

Meintjes, K., Morgan, A.P., (1990) Chemical equilibrium systems as
numerical test problems. ACM Trans. Math. Software, 16, 1990, pp. 143-151.
"""
import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def main():
    m = Container(delayed_execution=True)

    # SCALAR #
    R = Parameter(m, name="R", records=40)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    x4 = Variable(m, name="x4")
    x5 = Variable(m, name="x5")
    x6 = Variable(m, name="x6")
    x7 = Variable(m, name="x7")
    x8 = Variable(m, name="x8")
    x9 = Variable(m, name="x9")
    x10 = Variable(m, name="x10")
    obj = Variable(m, name="obj")

    # EQUATIONS #
    e1 = Equation(m, name="e1", type="regular")
    e2 = Equation(m, name="e2", type="regular")
    e3 = Equation(m, name="e3", type="regular")
    e4 = Equation(m, name="e4", type="regular")
    e5 = Equation(m, name="e5", type="regular")
    e6 = Equation(m, name="e6", type="regular")
    e7 = Equation(m, name="e7", type="regular")
    e8 = Equation(m, name="e8", type="regular")
    e9 = Equation(m, name="e9", type="regular")
    e10 = Equation(m, name="e10", type="regular")
    eobj = Equation(m, name="eobj", type="regular")

    # CONSTRAINTS
    e1.definition = x1 + x4 - 3 == 0
    e2.definition = 2 * x1 + x2 + x4 + x7 + x8 + x9 + 2 * x10 - R == 0
    e3.definition = 2 * x2 + 2 * x5 + x6 + x7 - 8 == 0
    e4.definition = 2 * x3 + x5 - 4 * R == 0
    e5.definition = x1 * x5 - 0.193 * x2 * x4 == 0
    e6.definition = (
        x6 * gams_math.sqrt(x2)
        - 0.002597
        * gams_math.sqrt(
            x2 * x4 * (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10)
        )
        == 0
    )
    e7.definition = (
        x7 * gams_math.sqrt(x4)
        - 0.003448
        * gams_math.sqrt(
            x1 * x4 * (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10)
        )
        == 0
    )
    e8.definition = (
        x4 * x8
        - 1.799
        * x2
        * (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10)
        / 100000
        == 0
    )
    e9.definition = (
        x4 * x9
        - 0.0002155
        * x1
        * gams_math.sqrt(
            x3 * (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10)
        )
        == 0
    )
    e10.definition = (
        x10 * sqr(x4)
        - 3.84
        * sqr(x4)
        * (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10)
        / 100000
        == 0
    )

    # OBJECTIVE
    eobj.definition = obj == 1

    # Bound on variables:
    x1.lo.assignment = 0.000001
    x1.up.assignment = 100
    x2.lo.assignment = 0.000001
    x2.up.assignment = 100
    x3.lo.assignment = 0.000001
    x3.up.assignment = 100
    x4.lo.assignment = 0.000001
    x4.up.assignment = 100
    x5.lo.assignment = 0.000001
    x5.up.assignment = 100
    x6.lo.assignment = 0.000001
    x6.up.assignment = 100
    x7.lo.assignment = 0.000001
    x7.up.assignment = 100
    x8.lo.assignment = 0.000001
    x8.up.assignment = 100
    x9.lo.assignment = 0.000001
    x9.up.assignment = 100
    x10.lo.assignment = 0.000001
    x10.up.assignment = 100

    # Initial point:
    x1.l.assignment = 2
    x2.l.assignment = 5
    x3.l.assignment = 40
    x4.l.assignment = 1
    x5.l.assignment = 0
    x6.l.assignment = 0
    x7.l.assignment = 0
    x8.l.assignment = 0
    x9.l.assignment = 0
    x10.l.assignment = 5

    cpa = Model(
        m,
        name="cpa",
        equations=m.getEquations(),
        problem="nlp",
        sense="min",
        objective=obj,
    )

    cpa.solve()

    print("x1:  ", round(x1.toValue(), 3))
    print("x2:  ", round(x2.toValue(), 3))
    print("x3:  ", round(x3.toValue(), 3))
    print("x4:  ", round(x4.toValue(), 3))
    print("x5:  ", round(x5.toValue(), 3))
    print("x6:  ", round(x6.toValue(), 3))
    print("x7:  ", round(x7.toValue(), 3))
    print("x8:  ", round(x8.toValue(), 3))
    print("x9:  ", round(x9.toValue(), 3))
    print("x10:  ", round(x10.toValue(), 3))

    import math

    assert math.isclose(cpa.objective_value, 1, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/cpa.py
GAMS-dev/gamspy
"""
Packing identical size circles in the unit circle (CPACK)

Given the unit circle (of radius 1), find a set of identical
size circles with an optimized (maximal) radius r so that all
such circles are contained by the unit circle, in a non-overlapping
arrangement.

A test example from  the LGO library


Pinter, J D, Nonlinear optimization with GAMS/LGO.
Journal of Global Optimization 38 (2007), 79-101.

Keywords: quadratic constraint programming, circle packing problem, mathematics
"""
import math
import sys

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Variable
from gamspy.math import sqr


# take number of circles as first argument
k = int(sys.argv[1]) if len(sys.argv) > 1 else 5
print("Number of circles =", k)

c = Container(delayed_execution=True)

# Set
i = Set(c, name="i", description="circles")
j = Alias(c, name="j", alias_with=i)
ij = Set(c, name="ij", domain=[i, j])
ij[i, j].where[Ord(i) < Ord(j)] = Number(1)

# Variables
r = Variable(c, name="r", description="radius of circles")
x = Variable(c, name="x", domain=[i], description="abscissa of circle")
y = Variable(c, name="y", domain=[i], description="ordinate of circle")

# Equations
circumscribe = Equation(
    c,
    name="circumscribe",
    domain=[i],
    description="enforce circle is enclosed in unit circle",
)
circumscribe[i] = sqr(1 - r) >= sqr(x[i]) + sqr(y[i])

nonoverlap = Equation(
    c,
    name="nonoverlap",
    domain=[i, j],
    description="enforce that circles do not overlap",
)
nonoverlap[ij[i, j]] = sqr(x[i] - x[j]) + sqr(y[i] - y[j]) >= 4 * sqr(r)

m = Model(
    c,
    name="cpack",
    equations=c.getEquations(),
    problem=Problem.QCP,
    sense=Sense.MAX,
    objective=r,
)

# Data
i.setRecords([str(i) for i in range(k)])

x.lo[i] = -1
x.up[i] = 1
y.lo[i] = -1
y.up[i] = 1

x.l[i] = -0.2 + Ord(i) * 0.1
y.l[i] = -0.2 + Ord(i) * 0.1

r.lo.assignment = 0.05
r.up.assignment = 0.4

# solve with a good global solver
c.addOptions({"optcr": "0.01"})
print("Starting solve, be patient (log only shown afterwards)...")
m.solve()

assert math.isclose(m.objective_value, 0.3702, rel_tol=0.001)

rval = r.records.loc[0, "level"]
print("Maximized radius:", rval)

# draw solution
width = 100
height = int(width / 2)
picture = bytearray(b" " * (width * height))

# enclosing circle at origin of radius 1
for v in range(1000):
    phi = 2.0 * math.pi * v / 1000.0
    # shift coordinates by 1.1 and scale down by 2.2
    xcoord = (math.cos(phi) + 1.1) / 2.2 * width
    ycoord = (math.sin(phi) + 1.1) / 2.2 * height
    pos = int(xcoord) + int(ycoord) * width
    if pos < len(picture):
        picture[int(xcoord) + int(ycoord) * width] = 42

# circles that were packed
for circle in range(k):
    xl = x.records.loc[circle, "lower"]
    yl = y.records.loc[circle, "lower"]
    # print('circle at', xl, yl, 'radius', rval)
    for v in range(1000):
        phi = 2.0 * math.pi * v / 1000.0
        xcoord = (xl + rval * math.cos(phi) + 1.1) / 2.2 * width
        ycoord = (yl + rval * math.sin(phi) + 1.1) / 2.2 * height
        pos = int(xcoord) + int(ycoord) * width
        if pos < len(picture):
            picture[pos] = 97 + circle

# linebreaks
for v in range(height):
    picture[v * width] = 10

# print(picture.decode())

tests/integration/models/cpack.py
GAMS-dev/gamspy
"""
Statistical agencies publish data which contains items that need to be
altered to protect confidentiality. Controlled Tabular Adjustments (CTA)
is a recent method to limit disclosure and can be elegantly expressed
as a Mixed Integer Programming problem. The programming framework then
allows easy expression of other data relationships like multi-dimensional
adding up conditions. The following model uses a 3-dimensional table from
from Cox, Kelly and Patil (2005) to illustrate this method.

The data is stored in an Excel Spreadsheet.


Lawrence H Cox, James P Kelly and Rahul J Patil, Computational Aspects
of Controlled Tabular Adjustments: Algorithms and Analysis, in The Next
Wave in Computing, Optimization, and Decision Technologies, Eds Bruce L Golden,
S Raghavan and Edward A Wasil, Springer, 2005, pp 45-59.

Keywords: mixed integer linear programming, statistical disclosure limitations
"""
import math
import os
import sys

from gams.connect import ConnectDatabase

from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import Round


def main():
    m = Container(delayed_execution=True)

    # Sets
    i = Set(m, name="i", description="rows")
    j = Set(m, name="j", description="columns")
    k = Set(m, name="k", description="planes")
    v = Set(m, name="v", description="non zero cells", domain=[i, j, k])
    s = Set(m, name="s", description="sensitive cells", domain=[i, j, k])

    # Parameters
    dat = Parameter(
        m, name="dat", description="unprotected data table", domain=[k, i, j]
    )
    pro = Parameter(
        m,
        name="pro",
        description="information sensitive cells",
        domain=[k, i, j],
    )

    # extract data from Excel
    file_dir = os.path.dirname(os.path.abspath(__file__))
    cdb = ConnectDatabase(m.system_directory)
    cdb.exec_task(
        {
            "PandasExcelReader": {
                "file": os.path.join(file_dir, "cta.xlsx"),
                "symbols": [
                    {
                        "name": "dat",
                        "range": "Sheet1!A1",
                        "rowDimension": 2,
                        "columnDimension": 1,
                    },
                    {
                        "name": "pro",
                        "range": "Sheet2!A1",
                        "rowDimension": 2,
                        "columnDimension": 1,
                    },
                ],
            }
        }
    )

    dat.domain_forwarding = True  # let dat fill sets i, j, and k
    dat.setRecords(cdb.container["dat"].records)
    pro.setRecords(cdb.container["pro"].records)

    # do some basic data checks
    check = Parameter(m, name="check")
    check.assignment = Sum(
        [i, k], Round(Sum(j, dat[k, i, j]) - 2 * dat[k, i, "total"])
    )
    assert math.isclose(check.toList()[0], 0), "row totals are incorrect"
    check.assignment = Sum(
        [j, k], Round(Sum(i, dat[k, i, j]) - 2 * dat[k, "total", j])
    )
    assert math.isclose(check.toList()[0], 0), "column totals are incorrect"
    check.assignment = Sum(
        [i, j], Round(Sum(k, dat[k, i, j]) - 2 * dat["total", i, j])
    )
    assert math.isclose(check.toList()[0], 0), "plane totals are incorrect"

    # Parameter BigM
    BigM = Parameter(
        m,
        name="BigM",
        description="the famous big M - make it as small as possible",
    )

    # Variables
    t = Variable(
        m, name="t", description="adjusted cell value", domain=[i, j, k]
    )
    adjn = Variable(m, name="adjn", domain=[i, j, k], type="Positive")
    adjp = Variable(m, name="adjp", domain=[i, j, k], type="Positive")
    b = Variable(m, name="b", domain=[i, j, k], type="Binary")

    # Equations
    defadj = Equation(
        m,
        name="defadj",
        description="define new cell values",
        domain=[i, j, k],
    )
    addrow = Equation(
        m, name="addrow", description="add up for rows", domain=[i, k]
    )
    addcol = Equation(
        m, name="addcol", description="add up for columns", domain=[j, k]
    )
    addpla = Equation(
        m, name="addpla", description="add up for plane", domain=[i, j]
    )
    pmin = Equation(
        m,
        name="pmin",
        description="small value for sensitive cells",
        domain=[i, j, k],
    )
    pmax = Equation(
        m,
        name="pmax",
        description="big value for sensitive cells",
        domain=[i, j, k],
    )
    pminx = Equation(m, name="pminx", domain=[i, j, k])
    pmaxx = Equation(m, name="pmaxx", domain=[i, j, k])

    v[i, j, k] = dat[k, i, j]
    s[i, j, k] = pro[k, i, j]

    BigM.setRecords(3)

    defadj[v[i, j, k]] = t[v] == dat[k, i, j] + adjp[v] - adjn[v]
    addrow[i, k] = Sum(v[i, j, k], t[v]) == 2 * t[i, "total", k]
    addcol[j, k] = Sum(v[i, j, k], t[v]) == 2 * t["total", j, k]
    addpla[i, j] = Sum(v[i, j, k], t[v]) == 2 * t[i, j, "total"]
    pmin[s[i, j, k]] = adjn[s] >= pro[k, i, j] * (1 - b[s])
    pmax[s[i, j, k]] = adjp[s] >= pro[k, i, j] * b[s]
    pminx[s[i, j, k]] = adjn[s] <= BigM * pro[k, i, j] * (1 - b[s])
    pmaxx[s[i, j, k]] = adjp[s] <= BigM * pro[k, i, j] * b[s]

    cox3 = Model(
        m,
        name="cox3",
        equations=m.getEquations(),
        problem="MIP",
        sense=Sense.MIN,
        objective=Sum([i, j, k], adjn[i, j, k] + adjp[i, j, k]),
    )

    cmd_params = {"optCa": 0.99, "resLim": 10}
    cox3.solve(options=cmd_params, output=sys.stdout)

    rep = Parameter(
        m, name="rep", description="summary report", domain=[k, i, j]
    )
    adjsum = Parameter(
        m,
        name="adjsum",
        description="adjustment summary",
        domain=[k, i, j, "*"],
    )
    adjrep = Parameter(
        m, name="adjrep", description="adjustment report", domain=[k, i, j]
    )

    rep[k, i, j] = t.l[i, j, k]
    adjsum[k, i, j, "neg"] = adjn.l[i, j, k]
    adjsum[k, i, j, "pos"] = adjp.l[i, j, k]
    adjsum[k, i, j, "min"] = pro[k, i, j]
    adjrep[k, i, j] = -adjn.l[i, j, k] + adjp.l[i, j, k]

    cdb = ConnectDatabase(m.system_directory, m)
    cdb.exec_task(
        {
            "PandasExcelWriter": {
                "file": os.path.join(file_dir, "results.xlsx"),
                "excelWriterArguments": {"mode": "w"},
                "symbols": [
                    {
                        "name": "adjrep",
                        "range": "adjrep!A1",
                    },
                    {
                        "name": "rep",
                        "range": "rep!A1",
                    },
                    {
                        "name": "adjsum",
                        "range": "adjsum!A1",
                    },
                ],
            }
        }
    )

    binrep = Parameter(
        m,
        name="binrep",
        domain=["*", "*", "*", "*"],
    )

    obj = cox3.objective_value
    best = round(obj)
    num_nodes_used = cox3.num_nodes_used
    solve_time = cox3.solve_time

    for it in range(5):
        if (obj - best) / best > 0.01:
            break
        b_list = b.toList("level")

        sol_str = f"solution{it+1}"
        binrep[s, sol_str] = Round(b.l[s])
        binrep["", "", "Obj", sol_str] = obj
        binrep["", "", "mSec", sol_str] = solve_time * 1000
        binrep["", "", "nodes", sol_str] = num_nodes_used
        binrep["Comp", "Cells", "Adjusted", sol_str] = Sum(
            Domain(i, j, k).where[~s[i, j, k]],
            Number(1).where[Round(adjn.l[i, j, k] + adjp.l[i, j, k])],
        )

        cutone = Equation(m, name=f"cutone_{it}")
        cuttwo = Equation(m, name=f"cuttwo_{it}")
        cutone.definition = (
            sum(
                [
                    1 - b[rec[:-1]] if rec[3] > 0.5 else b[rec[:-1]]
                    for rec in b_list
                ]
            )
            >= 1
        )
        cuttwo.definition = (
            sum(
                [
                    1 - b[rec[:-1]] if rec[3] < 0.5 else b[rec[:-1]]
                    for rec in b_list
                ]
            )
            >= 1
        )

        cox3c = Model(
            m,
            name=f"cox3c_{it}",
            equations=m.getEquations(),
            problem="MIP",
            sense=Sense.MIN,
            objective=Sum([i, j, k], adjn[i, j, k] + adjp[i, j, k]),
        )

        cox3c.solve(options=cmd_params, output=sys.stdout)
        obj = cox3c.objective_value
        num_nodes_used = cox3c.num_nodes_used
        solve_time = cox3c.solve_time

    cdb = ConnectDatabase(m.system_directory, m)
    cdb.exec_task(
        {
            "PandasExcelWriter": {
                "file": os.path.join(file_dir, "results.xlsx"),
                "symbols": [
                    {
                        "name": "binrep",
                        "range": "binrep!A1",
                        "toExcelArguments": {"merge_cells": False},
                    }
                ],
            }
        }
    )


if __name__ == "__main__":
    main()

tests/integration/models/cta.py
GAMS-dev/gamspy
"""
Cutting Stock - A Column Generation Approach (CUTSTOCK)

The task is to cut out some paper products of different sizes from a
large raw paper roll, in order to meet a customer's order. The objective
is to minimize the required number of paper rolls.


P. C. Gilmore and R. E. Gomory, A linear programming approach to the
cutting stock problem, Part I, Operations Research 9 (1961), 849-859.

P. C. Gilmore and R. E. Gomory, A linear programming approach to the
cutting stock problem, Part II, Operations Research 11 (1963), 863-888.

Keywords: mixed integer linear programming, cutting stock, column generation,
          paper industry
"""
import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Sets
    i = Set(m, "i", records=[f"w{idx}" for idx in range(1, 5)])
    p = Set(m, "p", records=[f"p{idx}" for idx in range(1, 1001)])
    pp = Set(m, "pp", domain=[p])

    # Parameters
    r = Parameter(m, "r", records=100)
    w = Parameter(
        m,
        "w",
        domain=[i],
        records=[["w1", 45], ["w2", 36], ["w3", 31], ["w4", 14]],
    )
    d = Parameter(
        m,
        "d",
        domain=[i],
        records=[["w1", 97], ["w2", 610], ["w3", 395], ["w4", 211]],
    )
    aip = Parameter(m, "aip", domain=[i, p])

    # Master model variables
    xp = Variable(m, "xp", domain=[p], type="integer")
    z = Variable(m, "z")
    xp.up[p] = Sum(i, d[i])

    # Master model equations
    numpat = Equation(m, "numpat", definition=z == Sum(pp, xp[pp]))
    demand = Equation(m, "demand", domain=[i])
    demand[i] = Sum(pp, aip[i, pp] * xp[pp]) >= d[i]

    master = Model(
        m,
        "master",
        equations=[numpat, demand],
        problem="rmip",
        sense=Sense.MIN,
        objective=z,
    )

    # Pricing model variables
    y = Variable(m, "y", domain=[i], type="integer")
    y.up[i] = gams_math.ceil(r / w[i])

    defobj = Equation(
        m,
        "defobj",
        definition=z == (1 - Sum(i, demand.m[i] * y[i])),
    )
    knapsack = Equation(m, "knapsack", definition=Sum(i, w[i] * y[i]) <= r)

    pricing = Model(
        m,
        "pricing",
        equations=[defobj, knapsack],
        problem="mip",
        sense=Sense.MIN,
        objective=z,
    )

    pp[p] = Ord(p) <= Card(i)
    aip[i, pp[p]].where[Ord(i) == Ord(p)] = gams_math.floor(r / w[i])

    pi = Set(m, "pi", domain=[p])
    pi[p] = Ord(p) == Card(pp) + 1

    m.addOptions({"optCr": 0, "limRow": 0, "limCol": 0, "solPrint": "off"})

    while len(pp) < len(p):
        master.solve()
        pricing.solve()

        if z.records["level"].values[0] >= -0.001:
            break

        aip[i, pi] = gams_math.Round(y.l[i])
        pp[pi] = Number(1)
        pi[p] = pi[p.lag(1)]

    master.problem = "mip"
    master.solve()

    import math

    assert math.isclose(master.objective_value, 453.0000, rel_tol=0.001)

    patrep = Parameter(m, "patrep", domain=["*", "*"])
    demrep = Parameter(m, "demrep", domain=["*", "*"])

    patrep["# produced", p] = gams_math.Round(xp.l[p])
    patrep[i, p].where[patrep["# produced", p]] = aip[i, p]
    patrep[i, "total"] = Sum(p, patrep[i, p])
    patrep["# produced", "total"] = Sum(p, patrep["# produced", p])

    demrep[i, "produced"] = Sum(p, patrep[i, p] * patrep["# produced", p])
    demrep[i, "demand"] = d[i]
    demrep[i, "over"] = demrep[i, "produced"] - demrep[i, "demand"]

    print(patrep.records)
    print(demrep.records)


if __name__ == "__main__":
    main()

tests/integration/models/cutstock.py
GAMS-dev/gamspy
"""
Time dependent temperature field in a rectangular area.

Determination of the time dependent temperature field in a rectangular area
with heterogeneous thermal conductivity and a source points of heat
inside the area as well as heat flows temperature through the borders
of the solution domain.

Adapted from:
McKinney, D.C., Savitsky, A.G., Basic optimization models for water and
energy management. June 1999 (revision 6, February 2003).
http://www.ce.utexas.edu/prof/mckynney/ce385d/papers/GAMS-Tutorial.pdf
"""
import numpy as np

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def data_records():
    # v records table
    v_rec = np.array(
        [
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                1.0,
                1.0,
                1.0,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
            [
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
                0.5,
            ],
        ]
    )

    return v_rec


def main():
    m = Container(delayed_execution=True)

    # SETS #
    time = Set(m, name="time", records=[f"t{t}" for t in range(1, 26)])
    x = Set(m, name="x", records=[f"i{i}" for i in range(1, 21)])
    y = Set(m, name="y", records=[f"j{j}" for j in range(1, 21)])
    inside = Set(m, name="inside", domain=[x, y])

    inside[x, y] = True
    inside[x, y].where[Ord(x) == 1] = False
    inside[x, y].where[Ord(x) == Card(x)] = False
    inside[x, y].where[Ord(y) == 1] = False
    inside[x, y].where[Ord(y) == Card(y)] = False

    # SCALARS #
    # Parameters determination
    dx = Parameter(
        m, name="dx", records=0.1, description="step for space in x direction"
    )
    dy = Parameter(
        m, name="dy", records=0.1, description="step for space in y direction"
    )
    dt = Parameter(m, name="dt", records=0.1, description="step for time")
    c = Parameter(m, name="c", records=1.0)
    rho = Parameter(m, name="rho", records=1.0)
    heat = Parameter(
        m,
        name="heat",
        records=0.0,
        description="accumulation in one time step",
    )

    # PARAMETERS #
    # Temperature supply determination
    supply = Parameter(m, name="supply", domain=[x, y])
    past_T = Parameter(m, name="past_T", domain=[x, y])
    v = Parameter(m, name="v", domain=[x, y], records=data_records())

    supply[x, y] = 0
    supply["i10", "j18"] = 100 / dx / dy
    past_T[x, y] = 0

    # VARIABLES #
    # Model description
    obj = Variable(m, name="obj", description="objective variable")
    t = Variable(
        m, name="t", domain=[x, y], description="field of temperature"
    )
    Q = Variable(m, name="Q", description="temperature on boundaries")

    # Variable bounds
    t.lo[x, y] = 0.0
    t.up[x, y] = 200.0

    # Initial values
    t.l[x, y] = 0.0
    Q.l.assignment = 0.0

    # EQUATIONS #
    temp = Equation(
        m,
        name="temp",
        type="regular",
        domain=[x, y],
        description="main equation of heat transport",
    )
    f1 = Equation(
        m,
        name="f1",
        type="regular",
        domain=[x, y],
        description="boundary computation dt:dn",
    )
    f2 = Equation(
        m,
        name="f2",
        type="regular",
        domain=[x, y],
        description="boundary computation dt:dn",
    )
    f3 = Equation(
        m,
        name="f3",
        type="regular",
        domain=[x, y],
        description="boundary computation dt:dn",
    )
    f4 = Equation(
        m,
        name="f4",
        type="regular",
        domain=[x, y],
        description="boundary computation dt:dn",
    )
    fp1 = Equation(
        m,
        name="fp1",
        type="regular",
        domain=[x, y],
        description="boundary computation t",
    )
    fp2 = Equation(
        m,
        name="fp2",
        type="regular",
        domain=[x, y],
        description="boundary computation ",
    )
    fp3 = Equation(
        m,
        name="fp3",
        type="regular",
        domain=[x, y],
        description="boundary computation t",
    )
    fp4 = Equation(
        m,
        name="fp4",
        type="regular",
        domain=[x, y],
        description="boundary computation t",
    )
    eobj = Equation(
        m, name="eobj", type="regular", description="objective equation"
    )

    temp[x, y].where[inside[x, y]] = t[x, y] - past_T[x, y] == (
        dt
        * (
            supply[x, y] / c / rho
            + (
                (v[x.lead(1), y] + v[x, y]) * (t[x.lead(1), y] - t[x, y])
                - (v[x, y] + v[x.lag(1), y]) * (t[x, y] - t[x.lag(1), y])
            )
            / dx
            / dx
            / 2.0
            + (
                (v[x, y.lead(1)] + v[x, y]) * (t[x, y.lead(1)] - t[x, y])
                - (v[x, y.lag(1)] == v[x, y]) * (t[x, y] - t[x, y.lag(1)])
            )
            / dy
            / dy
            / 2.0
        )
    )

    f1[x, y].where[(Ord(x) == 1)] = t[x.lead(1), y] - t[x, y] >= 0
    f2[x, y].where[(Ord(x) == Card(x))] = t[x, y] - t[x.lag(1), y] <= 0
    f3[x, y].where[(Ord(y) == 1)] = t[x, y.lead(1)] - t[x, y] >= 0
    f4[x, y].where[(Ord(y) == Card(y))] = t[x, y] - t[x, y.lag(1)] <= 0

    fp1[x, y].where[(Ord(x) == 1)] = t[x, y] == Q
    fp2[x, y].where[(Ord(x) == Card(x))] = t[x, y] == Q
    fp3[x, y].where[(Ord(y) == 1)] = t[x, y] == Q
    fp4[x, y].where[(Ord(y) == Card(y))] = t[x, y] == Q

    eobj.definition = obj == Q

    Diffusion2 = Model(
        m,
        name="Diffusion2",
        equations=m.getEquations(),
        problem="nlp",
        sense="min",
        objective=obj,
    )

    for tu in time.toList():
        Diffusion2.solve()
        print(f"\t --- \t Time interval = {tu} \t --- \n")
        print(t.pivot().round(4))
        print("\n")
        heat.assignment = (
            heat + Sum([x, y], t.l[x, y] - past_T[x, y]) * dx * dy
        )
        past_T[x, y] = t.l[x, y]

    # End Diffusion2


if __name__ == "__main__":
    main()

tests/integration/models/diffusion2.py
GAMS-dev/gamspy
"""
A Recursive-Dynamic Standard CGE Model (DYNCGE)

This model is featured in the following book.
Hosoe, N., Gasawa, K., Hashimoto, H. Textbook of Computable General
Equilibrium Modeling: Programming and Simulations, 2nd Edition,
University of Tokyo Press. (in Japanese)

Keywords: nonlinear programming, general equilibrium model, social accounting
          matrix
"""
import numpy as np

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Problem
from gamspy import Product
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    m.addOptions({"limRow": 0, "limCol": 0})

    # ===============================================================
    # Definition of sets for suffix ---------------------------------
    # ===============================================================
    # Sets
    u = Set(
        m,
        name="u",
        records=[
            "AGR",
            "LMN",
            "HMN",
            "SRV",
            "CAP",
            "LAB",
            "HOH",
            "GOV",
            "INV",
            "EXT",
            "IDT",
            "TRF",
        ],
    )
    i = Set(m, name="i", domain=[u], records=["AGR", "LMN", "HMN", "SRV"])
    h = Set(m, name="h", domain=[u], records=["CAP", "LAB"])
    h_mob = Set(m, name="h_mob", domain=[h], records=["LAB"])
    t = Set(m, name="t", records=[str(i) for i in range(31)])

    # Alias
    v = Alias(m, name="v", alias_with=u)
    j = Alias(m, name="j", alias_with=i)
    k = Alias(m, name="k", alias_with=h)

    # ===============================================================
    # Data for Dynamics ---------------------------------------------
    # ===============================================================
    # Scalar
    ror = Parameter(m, name="ror")
    dep = Parameter(m, name="dep")
    pop = Parameter(m, name="pop")
    zeta = Parameter(m, name="zeta")

    ror.assignment = 0.05
    dep.assignment = 0.04
    pop.assignment = 0.02
    zeta.assignment = 1

    sam_data = np.array(
        [
            [
                1643.017,
                7560.896,
                237.841,
                1409.202,
                0,
                0,
                3563.257,
                0,
                919.745,
                62.464,
                0,
                0,
            ],
            [
                1485.854,
                10803.527,
                15330.764,
                18597.270,
                0,
                0,
                32220.169,
                329.469,
                802.026,
                1196.525,
                0,
                0,
            ],
            [
                1071.954,
                4277.721,
                113390.269,
                48734.424,
                0,
                0,
                27648.678,
                4.931,
                34979.803,
                55083.516,
                0,
                0,
            ],
            [
                2002.380,
                11406.260,
                50513.476,
                177675.714,
                0,
                0,
                234243.865,
                90707.177,
                79169.426,
                17426.156,
                0,
                0,
            ],
            [
                5082.506,
                7042.697,
                21058.821,
                163045.396,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
            ],
            [
                1435.010,
                8942.365,
                42510.123,
                222732.700,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
            ],
            [0, 0, 0, 0, 196229.420, 275620.198, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 52243.041, 0, 0, 0, 34024.445, 4774.091],
            [0, 0, 0, 0, 0, 0, 121930.608, 0, 0, -6059.608, 0, 0],
            [
                2092.569,
                23796.669,
                30982.559,
                10837.256,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
            ],
            [433.854, 4068.616, 9418.058, 20103.917, 0, 0, 0, 0, 0, 0, 0, 0],
            [149.278, 2866.853, 1749.385, 8.575, 0, 0, 0, 0, 0, 0, 0, 0],
        ]
    )

    # ===============================================================
    # SAM Data
    # ===============================================================
    SAM = Parameter(m, name="SAM", domain=[u, v], records=sam_data)

    # Source: compiled by N. Hosoe, based on the I/O table for 2005

    SAMGAP = Parameter(m, name="SAMGAP", domain=[u])
    SAMGAP[u] = Sum(v, SAM[u, v] - SAM[v, u])

    # print(SAMGAP.records)

    # ===============================================================
    # Loading the initial values ------------------------------------
    # ===============================================================

    # Base year values
    Y00 = Parameter(m, name="Y00", domain=[j])
    F00 = Parameter(m, name="F00", domain=[h, j])
    X00 = Parameter(m, name="X00", domain=[i, j])
    Z00 = Parameter(m, name="Z00", domain=[j])
    Xp00 = Parameter(m, name="Xp00", domain=[i])
    Xg00 = Parameter(m, name="Xg00", domain=[i])
    Xv00 = Parameter(m, name="Xv00", domain=[i])
    E00 = Parameter(m, name="E00", domain=[i])
    M00 = Parameter(m, name="M00", domain=[i])
    Q00 = Parameter(m, name="Q00", domain=[i])
    D00 = Parameter(m, name="D00", domain=[i])
    Sp00 = Parameter(m, name="Sp00")
    Td00 = Parameter(m, name="Td00")
    Tz00 = Parameter(m, name="Tz00", domain=[j])
    Tm00 = Parameter(m, name="Tm00", domain=[j])
    III00 = Parameter(m, name="III00")
    II00 = Parameter(m, name="II00", domain=[j])
    KK00 = Parameter(m, name="KK00", domain=[j])
    CC00 = Parameter(m, name="CC00")
    FF00 = Parameter(m, name="FF00", domain=[h])
    Sf00 = Parameter(m, name="Sf00")
    tauz00 = Parameter(m, name="tauz00", domain=[i])
    taum00 = Parameter(m, name="taum00", domain=[i])

    # Base run value
    Y0 = Parameter(m, name="Y0", domain=[j, t])
    F0 = Parameter(m, name="F0", domain=[h, j, t])
    X0 = Parameter(m, name="X0", domain=[i, j, t])
    Z0 = Parameter(m, name="Z0", domain=[j, t])
    Xp0 = Parameter(m, name="Xp0", domain=[i, t])
    Xv0 = Parameter(m, name="Xv0", domain=[i, t])
    E0 = Parameter(m, name="E0", domain=[i, t])
    M0 = Parameter(m, name="M0", domain=[i, t])
    Q0 = Parameter(m, name="Q0", domain=[i, t])
    D0 = Parameter(m, name="D0", domain=[i, t])
    Sp0 = Parameter(m, name="Sp0", domain=[t])
    Td0 = Parameter(m, name="Td0", domain=[t])
    Tz0 = Parameter(m, name="Tz0", domain=[j, t])
    Tm0 = Parameter(m, name="Tm0", domain=[j, t])
    III0 = Parameter(m, name="III0", domain=[t])
    II0 = Parameter(m, name="II0", domain=[j, t])
    KK0 = Parameter(m, name="KK0", domain=[j, t])
    CC0 = Parameter(m, name="CC0", domain=[t])
    FF0 = Parameter(m, name="FF0", domain=[h, t])
    pf0 = Parameter(m, name="pf0", domain=[h, j, t])
    py0 = Parameter(m, name="py0", domain=[j, t])
    pz0 = Parameter(m, name="pz0", domain=[j, t])
    pq0 = Parameter(m, name="pq0", domain=[i, t])
    pe0 = Parameter(m, name="pe0", domain=[i, t])
    pm0 = Parameter(m, name="pm0", domain=[i, t])
    pd0 = Parameter(m, name="pd0", domain=[i, t])
    pk0 = Parameter(m, name="pk0", domain=[t])
    epsilon0 = Parameter(m, name="epsilon0", domain=[t])
    PRICE0 = Parameter(m, name="PRICE0", domain=[t])

    # Exogenous variables
    Xg0 = Parameter(m, name="Xg0", domain=[i, t])
    Sf0 = Parameter(m, name="Sf0", domain=[t])
    pWe = Parameter(m, name="pWe", domain=[i])
    pWm = Parameter(m, name="pWm", domain=[i])
    tauz = Parameter(m, name="tauz", domain=[i])
    taum = Parameter(m, name="taum", domain=[i])

    # for result reporting
    Y1 = Parameter(m, name="Y1", domain=[j, t])
    F1 = Parameter(m, name="F1", domain=[h, j, t])
    X1 = Parameter(m, name="X1", domain=[i, j, t])
    Z1 = Parameter(m, name="Z1", domain=[j, t])
    Xp1 = Parameter(m, name="Xp1", domain=[i, t])
    Xv1 = Parameter(m, name="Xv1", domain=[i, t])
    E1 = Parameter(m, name="E1", domain=[i, t])
    M1 = Parameter(m, name="M1", domain=[i, t])
    Q1 = Parameter(m, name="Q1", domain=[i, t])
    D1 = Parameter(m, name="D1", domain=[i, t])
    Sp1 = Parameter(m, name="Sp1", domain=[t])
    Td1 = Parameter(m, name="Td1", domain=[t])
    Tz1 = Parameter(m, name="Tz1", domain=[j, t])
    Tm1 = Parameter(m, name="Tm1", domain=[i, t])
    FF1 = Parameter(m, name="FF1", domain=[h, t])
    II1 = Parameter(m, name="II1", domain=[j, t])
    III1 = Parameter(m, name="III1", domain=[t])
    KK1 = Parameter(m, name="KK1", domain=[j, t])
    CC1 = Parameter(m, name="CC1", domain=[t])
    tauz1 = Parameter(m, name="tauz1", domain=[i, t])
    taum1 = Parameter(m, name="taum1", domain=[i, t])
    pz1 = Parameter(m, name="pz1", domain=[j, t])
    pd1 = Parameter(m, name="pd1", domain=[j, t])
    pm1 = Parameter(m, name="pm1", domain=[j, t])
    pe1 = Parameter(m, name="pe1", domain=[j, t])
    pq1 = Parameter(m, name="pq1", domain=[j, t])
    pf1 = Parameter(m, name="pf1", domain=[h, j, t])
    py1 = Parameter(m, name="py1", domain=[j, t])
    epsilon1 = Parameter(m, name="epsilon1", domain=[t])
    pk1 = Parameter(m, name="pk1", domain=[t])
    PRICE1 = Parameter(m, name="PRICE1", domain=[t])

    Td00.assignment = SAM["GOV", "HOH"]
    Tz00[j] = SAM["IDT", j]
    Tm00[j] = SAM["TRF", j]
    F00[h, j] = SAM[h, j]
    Y00[j] = Sum(h, F00[h, j])
    X00[i, j] = SAM[i, j]
    Z00[j] = Y00[j] + Sum(i, X00[i, j])
    M00[i] = SAM["EXT", i]
    tauz00[j] = Tz00[j] / Z00[j]
    taum00[j] = Tm00[j] / M00[j]
    Xp00[i] = SAM[i, "HOH"]
    CC00.assignment = Sum(i, Xp00[i])
    FF00[h] = SAM["HOH", h]
    E00[i] = SAM[i, "EXT"]
    D00[i] = (1 + tauz00[i]) * Z00[i] - E00[i]
    Q00[i] = (1 + taum00[i]) * M00[i] + D00[i]
    Sf00.assignment = SAM["INV", "EXT"]

    # ===============================================================
    # Adjusting Investment in the SAM for the Assumed BAU Growth Path
    # ===============================================================
    # Scalars
    III_ASS = Parameter(m, name="III_ASS")
    III_SAM = Parameter(m, name="III_SAM")
    adj = Parameter(m, name="adj")

    III_ASS.assignment = (pop + dep) / ror * FF00["CAP"]
    III_SAM.assignment = Sum(i, SAM[i, "INV"])
    adj.assignment = III_ASS / III_SAM

    # Adjusting investment level
    Xv00[i] = SAM[i, "INV"] * adj

    # Reallocating the gap made by the inv. adjustment to gov. cons.
    Xg00[i] = SAM[i, "GOV"] - (Xv00[i] - SAM[i, "INV"])

    # Computing the direct tax revenue that balances the gov. budget
    Td00.assignment = Sum(i, Xg00[i]) - Sum(i, Tz00[i] + Tm00[i])

    # Computing the household sav. that balances the household budget
    Sp00.assignment = Sum(h, FF00[h]) - (Sum(i, Xp00[i]) + Td00)
    III00.assignment = Sum(i, Xv00[i])
    II00[j] = (Sp00 + Sf00) * F00["CAP", j] / Sum(i, F00["CAP", i])
    KK00[j] = F00["CAP", j] / ror

    # ===============================================================
    # Computing the BAU path
    # ===============================================================
    Y0[j, t] = Y00[j] * (1 + pop) ** (Ord(t) - 1)
    F0[h, j, t] = F00[h, j] * (1 + pop) ** (Ord(t) - 1)
    X0[i, j, t] = X00[i, j] * (1 + pop) ** (Ord(t) - 1)
    Z0[j, t] = Z00[j] * (1 + pop) ** (Ord(t) - 1)
    Xp0[i, t] = Xp00[i] * (1 + pop) ** (Ord(t) - 1)
    Xv0[i, t] = Xv00[i] * (1 + pop) ** (Ord(t) - 1)
    E0[i, t] = E00[i] * (1 + pop) ** (Ord(t) - 1)
    M0[i, t] = M00[i] * (1 + pop) ** (Ord(t) - 1)
    Q0[i, t] = Q00[i] * (1 + pop) ** (Ord(t) - 1)
    D0[i, t] = D00[i] * (1 + pop) ** (Ord(t) - 1)
    FF0[h, t] = FF00[h] * (1 + pop) ** (Ord(t) - 1)
    III0[t] = III00 * (1 + pop) ** (Ord(t) - 1)
    II0[j, t] = II00[j] * (1 + pop) ** (Ord(t) - 1)
    KK0[j, t] = KK00[j] * (1 + pop) ** (Ord(t) - 1)
    CC0[t] = CC00 * (1 + pop) ** (Ord(t) - 1)
    Sp0[t] = Sp00 * (1 + pop) ** (Ord(t) - 1)
    Td0[t] = Td00 * (1 + pop) ** (Ord(t) - 1)
    Tz0[j, t] = Tz00[j] * (1 + pop) ** (Ord(t) - 1)
    Tm0[i, t] = Tm00[i] * (1 + pop) ** (Ord(t) - 1)
    pf0[h, j, t] = 1
    py0[j, t] = 1
    pz0[j, t] = 1
    pq0[i, t] = 1
    pe0[i, t] = 1
    pm0[i, t] = 1
    pd0[i, t] = 1
    pk0[t] = 1
    epsilon0[t] = 1
    PRICE0[t] = 1

    # Setting exogenous variables
    Xg0[i, t] = Xg00[i] * (1 + pop) ** (Ord(t) - 1)
    Sf0[t] = Sf00 * (1 + pop) ** (Ord(t) - 1)
    pWe[i] = 1
    pWm[i] = 1
    tauz[i] = tauz00[i]
    taum[i] = taum00[i]

    # ===============================================================
    # Calibration ---------------------------------------------------
    # ===============================================================
    # Parameters
    sigma = Parameter(m, name="sigma", domain=[i])
    psi = Parameter(m, name="psi", domain=[i])
    eta = Parameter(m, name="eta", domain=[i])
    phi = Parameter(m, name="phi", domain=[i])

    sigma[i] = 2
    psi[i] = 2
    eta[i] = (sigma[i] - 1) / sigma[i]
    phi[i] = (psi[i] + 1) / psi[i]

    # Parameters
    alpha = Parameter(m, name="alpha", domain=[i])
    a = Parameter(m, name="a")
    beta = Parameter(m, name="beta", domain=[h, j])
    b = Parameter(m, name="b", domain=[j])
    ax = Parameter(m, name="ax", domain=[i, j])
    ay = Parameter(m, name="ay", domain=[j])
    lamda = Parameter(m, name="lamda", domain=[i])
    iota = Parameter(m, name="iota")
    deltam = Parameter(m, name="deltam", domain=[i])
    deltad = Parameter(m, name="deltad", domain=[i])
    gamma = Parameter(m, name="gamma", domain=[i])
    xid = Parameter(m, name="xid", domain=[i])
    xie = Parameter(m, name="xie", domain=[i])
    theta = Parameter(m, name="theta", domain=[i])
    ssp = Parameter(m, name="ssp")

    alpha[i] = Xp00[i] / Sum(j, Xp00[j])
    a.assignment = CC00 / Product(j, Xp00[j] ** alpha[j])
    beta[h, j] = F00[h, j] / Sum(k, F00[k, j])
    b[j] = Y00[j] / Product(h, F00[h, j] ** beta[h, j])
    ax[i, j] = X00[i, j] / Z00[j]
    ay[j] = Y00[j] / Z00[j]
    lamda[i] = Xv00[i] / Sum(j, Xv00[j])
    iota.assignment = III00 / Product(i, Xv00[i] ** lamda[i])
    deltam[i] = (
        (1 + taum00[i])
        * M00[i] ** (1 - eta[i])
        / ((1 + taum00[i]) * M00[i] ** (1 - eta[i]) + D00[i] ** (1 - eta[i]))
    )
    deltad[i] = D00[i] ** (1 - eta[i]) / (
        (1 + taum00[i]) * M00[i] ** (1 - eta[i]) + D00[i] ** (1 - eta[i])
    )
    gamma[i] = Q00[i] / (
        deltam[i] * M00[i] ** eta[i] + deltad[i] * D00[i] ** eta[i]
    ) ** (1 / eta[i])
    xie[i] = E00[i] ** (1 - phi[i]) / (
        E00[i] ** (1 - phi[i]) + D00[i] ** (1 - phi[i])
    )
    xid[i] = D00[i] ** (1 - phi[i]) / (
        E00[i] ** (1 - phi[i]) + D00[i] ** (1 - phi[i])
    )
    theta[i] = Z00[i] / (
        xie[i] * E00[i] ** phi[i] + xid[i] * D00[i] ** phi[i]
    ) ** (1 / phi[i])
    ssp.assignment = Sp00 / (Sum([h, j], F00[h, j]) - Td00)

    # ===============================================================
    # Defining model system -----------------------------------------
    # ===============================================================
    # Variables
    Y = Variable(m, name="Y", type="free", domain=[j])
    F = Variable(m, name="F", type="free", domain=[h, j])
    X = Variable(m, name="X", type="free", domain=[i, j])
    Z = Variable(m, name="Z", type="free", domain=[j])
    Xp = Variable(m, name="Xp", type="free", domain=[i])
    Xg = Variable(m, name="Xg", type="free", domain=[i])
    Xv = Variable(m, name="Xv", type="free", domain=[i])
    E = Variable(m, name="E", type="free", domain=[i])
    M = Variable(m, name="M", type="free", domain=[i])
    Q = Variable(m, name="Q", type="free", domain=[i])
    D = Variable(m, name="D", type="free", domain=[i])
    FF = Variable(m, name="FF", type="free", domain=[h])
    pf = Variable(m, name="pf", type="free", domain=[h, j])
    py = Variable(m, name="py", type="free", domain=[j])
    pz = Variable(m, name="pz", type="free", domain=[j])
    pq = Variable(m, name="pq", type="free", domain=[i])
    pe = Variable(m, name="pe", type="free", domain=[i])
    pm = Variable(m, name="pm", type="free", domain=[i])
    pd = Variable(m, name="pd", type="free", domain=[i])
    pk = Variable(m, name="pk", type="free")
    epsilon = Variable(m, name="epsilon", type="free")
    Sp = Variable(m, name="Sp", type="free")
    Sf = Variable(m, name="Sf", type="free")
    Td = Variable(m, name="Td", type="free")
    Tz = Variable(m, name="Tz", type="free", domain=[j])
    Tm = Variable(m, name="Tm", type="free", domain=[i])
    KK = Variable(m, name="KK", type="free", domain=[j])
    II = Variable(m, name="II", type="free", domain=[j])
    III = Variable(m, name="III", type="free")
    PRICE = Variable(m, name="PRICE", type="free")
    CC = Variable(m, name="CC", type="free")

    # Equations
    eqpy = Equation(m, name="eqpy", domain=[j])
    eqF = Equation(m, name="eqF", domain=[h, j])
    eqX = Equation(m, name="eqX", domain=[i, j])
    eqY = Equation(m, name="eqY", domain=[j])
    eqpzs = Equation(m, name="eqpzs", domain=[j])
    eqTd = Equation(m, name="eqTd")
    eqTz = Equation(m, name="eqTz", domain=[j])
    eqTm = Equation(m, name="eqTm", domain=[i])
    eqXv = Equation(m, name="eqXv", domain=[i])
    eqSp = Equation(m, name="eqSp")
    eqXp = Equation(m, name="eqXp", domain=[i])
    eqpe = Equation(m, name="eqpe", domain=[i])
    eqpm = Equation(m, name="eqpm", domain=[i])
    eqepsilon = Equation(m, name="eqepsilon")
    eqpqs = Equation(m, name="eqpqs", domain=[i])
    eqM = Equation(m, name="eqM", domain=[i])
    eqD = Equation(m, name="eqD", domain=[i])
    eqpzd = Equation(m, name="eqpzd", domain=[i])
    eqE = Equation(m, name="eqE", domain=[i])
    eqDs = Equation(m, name="eqDs", domain=[i])
    eqpqd = Equation(m, name="eqpqd", domain=[i])
    eqpf1 = Equation(m, name="eqpf1", domain=[h_mob])
    eqpf2 = Equation(m, name="eqpf2", domain=[h_mob, i, j])
    eqpf3 = Equation(m, name="eqpf3", domain=[j])
    eqpk = Equation(m, name="eqpk")
    eqIII = Equation(m, name="eqIII")
    eqII = Equation(m, name="eqII", domain=[j])
    eqCC = Equation(m, name="eqCC")
    eqPRICE = Equation(m, name="eqPRICE")

    # ===============================================================
    # Model equations
    # ===============================================================
    # [domestic production] -
    # composite factor production func.                  (Cobb-Douglas)
    eqpy[j] = Y[j] == b[j] * Product(h, F[h, j] ** beta[h, j])

    # factor demand function                             (Cobb-Douglas)
    eqF[h, j] = F[h, j] == beta[h, j] * py[j] * Y[j] / pf[h, j]

    # intermediate input demand function                     (Leontief)
    eqX[i, j] = X[i, j] == ax[i, j] * Z[j]

    # composite factor demand function                       (Leontief)
    eqY[j] = Y[j] == ay[j] * Z[j]

    # unit price of gross output                             (Leontief)
    eqpzs[j] = pz[j] == ay[j] * py[j] + Sum(i, ax[i, j] * pq[i])

    # [government behavior] -
    # lump Sum direct tax revenue
    eqTd.definition = Td == Sum(i, pq[i] * Xg[i]) - Sum(i, Tm[i] + Tz[i])

    # production tax revenue
    eqTz[j] = Tz[j] == tauz[j] * pz[j] * Z[j]

    # import tariff revenue
    eqTm[i] = Tm[i] == taum[i] * pm[i] * M[i]

    # [investment behavior] -
    # composite investment production function
    eqXv[i] = Xv[i] == lamda[i] * pk * Sum(j, II[j]) / pq[i]

    # [savings] ----------
    # savings function
    eqSp.definition = Sp == ssp * (Sum([h, j], pf[h, j] * F[h, j]) - Td)

    # [household consumption] --                          (Cobb-Douglas)
    eqXp[i] = (
        Xp[i] == alpha[i] * (Sum([h, j], pf[h, j] * F[h, j]) - Sp - Td) / pq[i]
    )

    # [international trade] --
    eqpe[i] = pe[i] == epsilon * pWe[i]

    eqpm[i] = pm[i] == epsilon * pWm[i]

    # BOP constraint
    eqepsilon.definition = Sum(i, pWe[i] * E[i]) + Sf == Sum(i, pWm[i] * M[i])

    # [Armington function] --
    # Armington's composite good production function              (CES)
    eqpqs[i] = Q[i] == gamma[i] * (
        deltam[i] * M[i] ** eta[i] + deltad[i] * D[i] ** eta[i]
    ) ** (1 / eta[i])

    # import demand function                                      (CES)
    eqM[i] = (
        M[i]
        == (gamma[i] ** eta[i] * deltam[i] * pq[i] / ((1 + taum[i]) * pm[i]))
        ** (1 / (1 - eta[i]))
        * Q[i]
    )

    # domestic good demand function                               (CES)
    eqD[i] = (
        D[i]
        == (gamma[i] ** eta[i] * deltad[i] * pq[i] / pd[i])
        ** (1 / (1 - eta[i]))
        * Q[i]
    )

    # [transformation function] --
    # gross domestic output disaggregation function               (CET)
    eqpzd[i] = Z[i] == theta[i] * (
        xie[i] * E[i] ** phi[i] + xid[i] * D[i] ** phi[i]
    ) ** (1 / phi[i])

    # export supply function                                       (CET)
    eqE[i] = (
        E[i]
        == (theta[i] ** phi[i] * xie[i] * (1 + tauz[i]) * pz[i] / pe[i])
        ** (1 / (1 - phi[i]))
        * Z[i]
    )

    # domestic good supply function                                (CET)
    eqDs[i] = (
        D[i]
        == (theta[i] ** phi[i] * xid[i] * (1 + tauz[i]) * pz[i] / pd[i])
        ** (1 / (1 - phi[i]))
        * Z[i]
    )

    # [market clearing condition]
    # Arminton's composite good market
    eqpqd[i] = Q[i] == Xp[i] + Xg[i] + Xv[i] + Sum(j, X[i, j])

    # labor market: quantity
    eqpf1[h_mob] = Sum(j, F[h_mob, j]) == FF[h_mob]

    # labor market: price
    eqpf2[h_mob, i, j] = pf[h_mob, j] == pf[h_mob, i]

    # capital market
    eqpf3[j] = F["CAP", j] == ror * KK[j]

    # investment goods market
    eqpk.definition = Sum(j, II[j]) == III

    # [dynamic equations]
    # composite investment good market clearing condition
    eqIII.definition = III == iota * Product(i, Xv[i] ** lamda[i])

    # sectoral investment allocation
    eqII[j] = pk * II[j] == pf["CAP", j] ** zeta * F["CAP", j] / Sum(
        i, pf["CAP", i] ** zeta * F["CAP", i]
    ) * (Sp + epsilon * Sf)

    # felicity function
    eqCC.definition = CC == a * Product(i, Xp[i] ** alpha[i])

    # Price level [numeraire]
    eqPRICE.definition = PRICE == Sum(j, pq[j] * Q00[j] / Sum(i, Q00[i]))

    # ===============================================================
    # Initializing variables ----------------------------------------
    # ===============================================================
    Y.l[j] = Y00[j]
    F.l[h, j] = F00[h, j]
    X.l[i, j] = X00[i, j]
    Z.l[j] = Z00[j]
    Xp.l[i] = Xp00[i]
    Xv.l[i] = Xv00[i]
    E.l[i] = E00[i]
    M.l[i] = M00[i]
    Q.l[i] = Q00[i]
    D.l[i] = D00[i]
    pf.l[h, j] = 1
    py.l[j] = 1
    pz.l[j] = 1
    pq.l[i] = 1
    pe.l[i] = 1
    pm.l[i] = 1
    pd.l[i] = 1
    pk.l.assignment = 1
    epsilon.l.assignment = 1
    Sp.l.assignment = Sp00
    Td.l.assignment = Td00
    Tz.l[j] = Tz00[j]
    Tm.l[i] = Tm00[i]
    FF.l[h] = FF00[h]
    III.l.assignment = III00
    II.l[j] = II00[j]

    # ---------------------------------------------------------------
    # Numeraire
    PRICE.fx.assignment = 1

    # Initial factor endowments and exogenous variables
    FF.fx[h_mob] = FF00[h_mob]
    KK.fx[j] = KK00[j]
    Xg.fx[i] = Xg00[i]
    Sf.fx.assignment = Sf00

    # ===============================================================
    # Defining and solving the model --------------------------------
    # ===============================================================
    dyncge = Model(
        m,
        name="dyncge",
        equations=m.getEquations(),
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=CC,
    )

    dyncge.solve()
    m.addOptions(
        {
            "limRow": 0,
            "limCol": 0,
            "solPrint": "off",
            "solveLink": "%solveLink.loadlibrary%",
        }
    )

    # ===============================================================
    # Simulation Runs: Abolition of Import Tariffs
    # ===============================================================

    # Scenario:
    taum[i] = taum00[i] * 0

    for iteration, _ in t.records.itertuples(index=False):
        dyncge.solve()

        #  storing results -------------------------
        Y1[j, iteration] = Y.l[j]
        F1[h, j, iteration] = F.l[h, j]
        X1[i, j, iteration] = X.l[i, j]
        Z1[j, iteration] = Z.l[j]
        Xp1[i, iteration] = Xp.l[i]
        Xv1[i, iteration] = Xv.l[i]
        E1[i, iteration] = E.l[i]
        M1[i, iteration] = M.l[i]
        Q1[i, iteration] = Q.l[i]
        D1[i, iteration] = D.l[i]
        Sp1[iteration] = Sp.l
        Td1[iteration] = Td.l
        Tz1[j, iteration] = Tz.l[j]
        Tm1[i, iteration] = Tm.l[i]
        FF1[h, iteration] = FF.l[h]
        II1[j, iteration] = II.l[j]
        III1[iteration] = III.l
        KK1[j, iteration] = KK.l[j]
        CC1[iteration] = CC.l
        tauz1[i, iteration] = tauz[i]
        taum1[i, iteration] = taum[i]
        pf1[h, j, iteration] = pf.l[h, j]
        py1[j, iteration] = py.l[j]
        pz1[j, iteration] = pz.l[j]
        pd1[j, iteration] = pd.l[j]
        pe1[j, iteration] = pe.l[j]
        pm1[j, iteration] = pm.l[j]
        pq1[j, iteration] = pq.l[j]
        pk1[iteration] = pk.l
        epsilon1[iteration] = epsilon.l
        PRICE1[iteration] = PRICE.l

        #  updating the state variables --------------
        FF.fx[h_mob] = FF.l[h_mob] * (1 + pop)
        KK.fx[j] = (1 - dep) * KK.l[j] + II.l[j]
        if int(iteration) < 30:
            Xg.fx[i] = Xg0[i, str(int(iteration) + 1)]
            Sf.fx.assignment = Sf0[str(int(iteration) + 1)]

    # ===============================================================
    # Aftermath Computation
    # ===============================================================
    # Display of changes --------------------------------------------
    import math

    assert math.isclose(dyncge.objective_value, 539570.5027, rel_tol=0.001)

    # Parameters
    # changes
    dY = Parameter(
        m,
        name="dY",
        domain=[j, t],
        description="change of composite factor             [%]",
    )
    dF = Parameter(
        m,
        name="dF",
        domain=[h, j, t],
        description="change of factor input                 [%]",
    )
    dX = Parameter(
        m,
        name="dX",
        domain=[i, j, t],
        description="change of intermediate input           [%]",
    )
    dZ = Parameter(
        m,
        name="dZ",
        domain=[j, t],
        description="change of gross output                 [%]",
    )
    dXp = Parameter(
        m,
        name="dXp",
        domain=[i, t],
        description="change of household consumption        [%]",
    )
    dXv = Parameter(
        m,
        name="dXv",
        domain=[i, t],
        description="change of investment demand            [%]",
    )
    dE = Parameter(
        m,
        name="dE",
        domain=[i, t],
        description="change of exports                      [%]",
    )
    dM = Parameter(
        m,
        name="dM",
        domain=[i, t],
        description="change of imports                      [%]",
    )
    dQ = Parameter(
        m,
        name="dQ",
        domain=[i, t],
        description="change of Armington's composite good   [%]",
    )
    dD = Parameter(
        m,
        name="dD",
        domain=[i, t],
        description="change of domestic good                [%]",
    )
    dSp = Parameter(
        m,
        name="dSp",
        domain=[t],
        description="change of private saving               [%]",
    )
    dTd = Parameter(
        m,
        name="dTd",
        domain=[t],
        description="change of direct tax                   [%]",
    )
    dTz = Parameter(
        m,
        name="dTz",
        domain=[j, t],
        description="change of production tax               [%]",
    )
    dTm = Parameter(
        m,
        name="dTm",
        domain=[i, t],
        description="change of import tariff                [%]",
    )
    dFF = Parameter(
        m,
        name="dFF",
        domain=[h, t],
        description="change of initial sectoral factor uses [%]",
    )
    dKK = Parameter(
        m,
        name="dKK",
        domain=[j, t],
        description="change of sectoral capital stock       [%]",
    )
    dII = Parameter(
        m,
        name="dII",
        domain=[j, t],
        description="change of sectoral investment          [%]",
    )
    dIII = Parameter(
        m,
        name="dIII",
        domain=[t],
        description="change of composite investment         [%]",
    )
    dCC = Parameter(
        m,
        name="dCC",
        domain=[t],
        description="change of utility                      [%]",
    )
    dpz = Parameter(
        m,
        name="dpz",
        domain=[j, t],
        description="change of gross output price           [%]",
    )
    dpd = Parameter(
        m,
        name="dpd",
        domain=[j, t],
        description="change of domestic good price          [%]",
    )
    dpm = Parameter(
        m,
        name="dpm",
        domain=[j, t],
        description="change of import price                 [%]",
    )
    dpe = Parameter(
        m,
        name="dpe",
        domain=[j, t],
        description="change of export price                 [%]",
    )
    dpq = Parameter(
        m,
        name="dpq",
        domain=[j, t],
        description="change of Armington's comp. good price [%]",
    )
    dpf = Parameter(
        m,
        name="dpf",
        domain=[h, j, t],
        description="change of factor price                 [%]",
    )
    dpy = Parameter(
        m,
        name="dpy",
        domain=[j, t],
        description="change of composite factor price       [%]",
    )
    depsilon = Parameter(
        m,
        name="depsilon",
        domain=[t],
        description="change of foreign exchange rate        [%]",
    )
    dpk = Parameter(
        m,
        name="dpk",
        domain=[t],
        description="change of capital good price           [%]",
    )

    # BAU growth rate
    gY0 = Parameter(
        m,
        name="gY0",
        domain=[j, t],
        description="growth of composite factor             [%]",
    )
    gF0 = Parameter(
        m,
        name="gF0",
        domain=[h, j, t],
        description="growth of factor input                 [%]",
    )
    gX0 = Parameter(
        m,
        name="gX0",
        domain=[i, j, t],
        description="growth of intermediate input           [%]",
    )
    gZ0 = Parameter(
        m,
        name="gZ0",
        domain=[j, t],
        description="growth of gross output                 [%]",
    )
    gXp0 = Parameter(
        m,
        name="gXp0",
        domain=[i, t],
        description="growth of household consumption        [%]",
    )
    gXv0 = Parameter(
        m,
        name="gXv0",
        domain=[i, t],
        description="growth of investment demand            [%]",
    )
    gE0 = Parameter(
        m,
        name="gE0",
        domain=[i, t],
        description="growth of exports                      [%]",
    )
    gM0 = Parameter(
        m,
        name="gM0",
        domain=[i, t],
        description="growth of imports                      [%]",
    )
    gQ0 = Parameter(
        m,
        name="gQ0",
        domain=[i, t],
        description="growth of Armington's composite good   [%]",
    )
    gD0 = Parameter(
        m,
        name="gD0",
        domain=[i, t],
        description="growth of domestic good                [%]",
    )
    gSp0 = Parameter(
        m,
        name="gSp0",
        domain=[t],
        description="growth of private saving               [%]",
    )
    gTd0 = Parameter(
        m,
        name="gTd0",
        domain=[t],
        description="growth of direct tax                   [%]",
    )
    gTz0 = Parameter(
        m,
        name="gTz0",
        domain=[j, t],
        description="growth of production tax               [%]",
    )
    gTm0 = Parameter(
        m,
        name="gTm0",
        domain=[i, t],
        description="growth of import tariff                [%]",
    )
    gFF0 = Parameter(
        m,
        name="gFF0",
        domain=[h, t],
        description="growth of initial sectoral factor uses [%]",
    )
    gKK0 = Parameter(
        m,
        name="gKK0",
        domain=[j, t],
        description="growth of sectoral capital stock       [%]",
    )
    gII0 = Parameter(
        m,
        name="gII0",
        domain=[j, t],
        description="growth of sectoral investment          [%]",
    )
    gIII0 = Parameter(
        m,
        name="gIII0",
        domain=[t],
        description="growth of composite investment         [%]",
    )
    gCC0 = Parameter(
        m,
        name="gCC0",
        domain=[t],
        description="growth of growth rate of CC            [%]",
    )

    # C/F growth rate
    gY1 = Parameter(
        m,
        name="gY1",
        domain=[j, t],
        description="growth of composite factor             [%]",
    )
    gF1 = Parameter(
        m,
        name="gF1",
        domain=[h, j, t],
        description="growth of factor input                 [%]",
    )
    gX1 = Parameter(
        m,
        name="gX1",
        domain=[i, j, t],
        description="growth of intermediate input           [%]",
    )
    gZ1 = Parameter(
        m,
        name="gZ1",
        domain=[j, t],
        description="growth of gross output                 [%]",
    )
    gXp1 = Parameter(
        m,
        name="gXp1",
        domain=[i, t],
        description="growth of household consumption        [%]",
    )
    gXv1 = Parameter(
        m,
        name="gXv1",
        domain=[i, t],
        description="growth of investment demand            [%]",
    )
    gE1 = Parameter(
        m,
        name="gE1",
        domain=[i, t],
        description="growth of exports                      [%]",
    )
    gM1 = Parameter(
        m,
        name="gM1",
        domain=[i, t],
        description="growth of imports                      [%]",
    )
    gQ1 = Parameter(
        m,
        name="gQ1",
        domain=[i, t],
        description="growth of Armington's composite good   [%]",
    )
    gD1 = Parameter(
        m,
        name="gD1",
        domain=[i, t],
        description="growth of domestic good                [%]",
    )
    gSp1 = Parameter(
        m,
        name="gSp1",
        domain=[t],
        description="growth of private saving               [%]",
    )
    gTd1 = Parameter(
        m,
        name="gTd1",
        domain=[t],
        description="growth of direct tax                   [%]",
    )
    gTz1 = Parameter(
        m,
        name="gTz1",
        domain=[j, t],
        description="growth of production tax               [%]",
    )
    gTm1 = Parameter(
        m,
        name="gTm1",
        domain=[i, t],
        description="growth of import tariff                [%]",
    )
    gFF1 = Parameter(
        m,
        name="gFF1",
        domain=[h, t],
        description="growth of initial sectoral factor uses [%]",
    )
    gKK1 = Parameter(
        m,
        name="gKK1",
        domain=[j, t],
        description="growth of sectoral capital stock       [%]",
    )
    gII1 = Parameter(
        m,
        name="gII1",
        domain=[j, t],
        description="growth of sectoral investment          [%]",
    )
    gIII1 = Parameter(
        m,
        name="gIII1",
        domain=[t],
        description="growth of composite investment         [%]",
    )
    gCC1 = Parameter(
        m,
        name="gCC1",
        domain=[t],
        description="growth of growth rate of CC            [%]",
    )

    # welfare
    EV = Parameter(
        m, name="EV", domain=[t], description="equivalent variations [current]"
    )
    EV_TTL = Parameter(
        m, name="EV_TTL", description="total EV [discounted Sum]"
    )

    dY[j, t].where[Y0[j, t]] = (Y1[j, t] / Y0[j, t] - 1) * 100
    dF[h, j, t].where[F0[h, j, t]] = (F1[h, j, t] / F0[h, j, t] - 1) * 100
    dX[i, j, t].where[X0[i, j, t]] = (X1[i, j, t] / X0[i, j, t] - 1) * 100
    dZ[j, t].where[Z0[j, t]] = (Z1[j, t] / Z0[j, t] - 1) * 100
    dXp[i, t].where[Xp0[i, t]] = (Xp1[i, t] / Xp0[i, t] - 1) * 100
    dXv[i, t].where[Xv0[i, t]] = (Xv1[i, t] / Xv0[i, t] - 1) * 100
    dE[i, t].where[E0[i, t]] = (E1[i, t] / E0[i, t] - 1) * 100
    dM[i, t].where[M0[i, t]] = (M1[i, t] / M0[i, t] - 1) * 100
    dQ[i, t].where[Q0[i, t]] = (Q1[i, t] / Q0[i, t] - 1) * 100
    dD[i, t].where[D0[i, t]] = (D1[i, t] / D0[i, t] - 1) * 100
    dSp[t].where[Sp0[t]] = (Sp1[t] / Sp0[t] - 1) * 100
    dTd[t].where[Td0[t]] = (Td1[t] / Td0[t] - 1) * 100
    dTz[j, t].where[Tz0[j, t]] = (Tz1[j, t] / Tz0[j, t] - 1) * 100
    dTm[i, t].where[Tm0[i, t]] = (Tm1[i, t] / Tm0[i, t] - 1) * 100
    dFF[h, t].where[FF0[h, t]] = (FF1[h, t] / FF0[h, t] - 1) * 100
    dII[j, t].where[II0[j, t]] = (II1[j, t] / II0[j, t] - 1) * 100
    dIII[t].where[III0[t]] = (III1[t] / III0[t] - 1) * 100
    dKK[j, t].where[KK0[j, t]] = (KK1[j, t] / KK0[j, t] - 1) * 100
    dCC[t].where[CC0[t]] = (CC1[t] / CC0[t] - 1) * 100
    dpz[j, t].where[pz0[j, t]] = (pz1[j, t] / pz0[j, t] - 1) * 100
    dpd[j, t].where[pd0[j, t]] = (pd1[j, t] / pd0[j, t] - 1) * 100
    dpm[j, t].where[pm0[j, t]] = (pm1[j, t] / pm0[j, t] - 1) * 100
    dpe[j, t].where[pe0[j, t]] = (pe1[j, t] / pe0[j, t] - 1) * 100
    dpq[j, t].where[pq0[j, t]] = (pq1[j, t] / pq0[j, t] - 1) * 100
    dpf[h, j, t].where[pf0[h, j, t]] = (pf1[h, j, t] / pf0[h, j, t] - 1) * 100
    dpy[j, t].where[py0[j, t]] = (py1[j, t] / py0[j, t] - 1) * 100
    depsilon[t].where[epsilon0[t]] = (epsilon1[t] / epsilon0[t] - 1) * 100
    dpk[t].where[pk0[t]] = (pk1[t] / pk0[t] - 1) * 100
    gY0[j, t.lead(1)].where[Y0[j, t]] = (Y0[j, t.lead(1)] / Y0[j, t] - 1) * 100
    gF0[h, j, t.lead(1)].where[F0[h, j, t]] = (
        F0[h, j, t.lead(1)] / F0[h, j, t] - 1
    ) * 100
    gX0[i, j, t.lead(1)].where[X0[i, j, t]] = (
        X0[i, j, t.lead(1)] / X0[i, j, t] - 1
    ) * 100
    gZ0[j, t.lead(1)].where[Z0[j, t]] = (Z0[j, t.lead(1)] / Z0[j, t] - 1) * 100
    gXp0[i, t.lead(1)].where[Xp0[i, t]] = (
        Xp0[i, t.lead(1)] / Xp0[i, t] - 1
    ) * 100
    gXv0[i, t.lead(1)].where[Xv0[i, t]] = (
        Xv0[i, t.lead(1)] / Xv0[i, t] - 1
    ) * 100
    gE0[i, t.lead(1)].where[E0[i, t]] = (E0[i, t.lead(1)] / E0[i, t] - 1) * 100
    gM0[i, t.lead(1)].where[M0[i, t]] = (M0[i, t.lead(1)] / M0[i, t] - 1) * 100
    gQ0[i, t.lead(1)].where[Q0[i, t]] = (Q0[i, t.lead(1)] / Q0[i, t] - 1) * 100
    gD0[i, t.lead(1)].where[D0[i, t]] = (D0[i, t.lead(1)] / D0[i, t] - 1) * 100
    gSp0[t.lead(1)].where[Sp0[t]] = (Sp0[t.lead(1)] / Sp0[t] - 1) * 100
    gTd0[t.lead(1)].where[Td0[t]] = (Td0[t.lead(1)] / Td0[t] - 1) * 100
    gTz0[j, t.lead(1)].where[Tz0[j, t]] = (
        Tz0[j, t.lead(1)] / Tz0[j, t] - 1
    ) * 100
    gTm0[i, t.lead(1)].where[Tm0[i, t]] = (
        Tm0[i, t.lead(1)] / Tm0[i, t] - 1
    ) * 100
    gFF0[h, t.lead(1)].where[FF0[h, t]] = (
        FF0[h, t.lead(1)] / FF0[h, t] - 1
    ) * 100
    gII0[j, t.lead(1)].where[II0[j, t]] = (
        II0[j, t.lead(1)] / II0[j, t] - 1
    ) * 100
    gIII0[t.lead(1)].where[III0[t]] = (III0[t.lead(1)] / III0[t] - 1) * 100
    gKK0[j, t.lead(1)].where[KK0[j, t]] = (
        KK0[j, t.lead(1)] / KK0[j, t] - 1
    ) * 100
    gCC0[t.lead(1)].where[CC0[t]] = (CC0[t.lead(1)] / CC0[t] - 1) * 100
    gY1[j, t.lead(1)].where[Y1[j, t]] = (Y1[j, t.lead(1)] / Y1[j, t] - 1) * 100
    gF1[h, j, t.lead(1)].where[F1[h, j, t]] = (
        F1[h, j, t.lead(1)] / F1[h, j, t] - 1
    ) * 100
    gX1[i, j, t.lead(1)].where[X1[i, j, t]] = (
        X1[i, j, t.lead(1)] / X1[i, j, t] - 1
    ) * 100
    gZ1[j, t.lead(1)].where[Z1[j, t]] = (Z1[j, t.lead(1)] / Z1[j, t] - 1) * 100
    gXp1[i, t.lead(1)].where[Xp1[i, t]] = (
        Xp1[i, t.lead(1)] / Xp1[i, t] - 1
    ) * 100
    gXv1[i, t.lead(1)].where[Xv1[i, t]] = (
        Xv1[i, t.lead(1)] / Xv1[i, t] - 1
    ) * 100
    gE1[i, t.lead(1)].where[E1[i, t]] = (E1[i, t.lead(1)] / E1[i, t] - 1) * 100
    gM1[i, t.lead(1)].where[M1[i, t]] = (M1[i, t.lead(1)] / M1[i, t] - 1) * 100
    gQ1[i, t.lead(1)].where[Q1[i, t]] = (Q1[i, t.lead(1)] / Q1[i, t] - 1) * 100
    gD1[i, t.lead(1)].where[D1[i, t]] = (D1[i, t.lead(1)] / D1[i, t] - 1) * 100
    gSp1[t.lead(1)].where[Sp1[t]] = (Sp1[t.lead(1)] / Sp1[t] - 1) * 100
    gTd1[t.lead(1)].where[Td1[t]] = (Td1[t.lead(1)] / Td1[t] - 1) * 100
    gTz1[j, t.lead(1)].where[Tz1[j, t]] = (
        Tz1[j, t.lead(1)] / Tz1[j, t] - 1
    ) * 100
    gTm1[i, t.lead(1)].where[Tm1[i, t]] = (
        Tm1[i, t.lead(1)] / Tm1[i, t] - 1
    ) * 100
    gFF1[h, t.lead(1)].where[FF1[h, t]] = (
        FF1[h, t.lead(1)] / FF1[h, t] - 1
    ) * 100
    gII1[j, t.lead(1)].where[II1[j, t]] = (
        II1[j, t.lead(1)] / II1[j, t] - 1
    ) * 100
    gIII1[t.lead(1)].where[III1[t]] = (III1[t.lead(1)] / III1[t] - 1) * 100
    gKK1[j, t.lead(1)].where[KK1[j, t]] = (
        KK1[j, t.lead(1)] / KK1[j, t] - 1
    ) * 100
    gCC1[t.lead(1)].where[CC1[t]] = (CC1[t.lead(1)] / CC1[t] - 1) * 100

    # Welfare measure: Hicksian equivalent variations ---------------
    EV[t] = (CC1[t] - CC0[t]) / a / Product(i, (alpha[i] / 1) ** alpha[i])
    EV_TTL.assignment = Sum(t, EV[t] / (1 + ror) ** (Ord(t) - 1))

    print("EV_TTL: ", round(EV_TTL.records.value[0], 3))


if __name__ == "__main__":
    main()

tests/integration/models/dyncge.py
GAMS-dev/gamspy
"""
Economic load dispatch for 15 generator systems with transmission losses
modeled using B-matrix formulation (Kron).
EDC of a total power of 1980 MW using 15 power generating units.
"""
import numpy as np

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def data_records():
    # bound records table
    bound_recs = np.array(
        [
            [100, 655],
            [100, 455],
            [20, 130],
            [20, 130],
            [150, 470],
            [135, 460],
            [135, 465],
            [100, 300],
            [25, 165],
            [25, 460],
            [20, 80],
            [20, 80],
            [25, 85],
            [15, 55],
            [15, 55],
        ]
    )

    # data records table
    data_recs = np.array(
        [
            [0.000299, 10.100, 671.130],
            [0.000183, 10.200, 574.010],
            [0.001126, 8.814, 374.110],
            [0.001126, 8.800, 374.000],
            [0.000205, 10.400, 461.000],
            [0.000301, 10.100, 630.000],
            [0.000364, 9.800, 548.000],
            [0.000338, 11.200, 227.000],
            [0.000807, 11.200, 173.000],
            [0.001203, 10.700, 175.200],
            [0.003586, 10.200, 186.000],
            [0.005513, 9.900, 230.000],
            [0.000371, 13.100, 225.000],
            [0.001929, 12.100, 309.000],
            [0.004447, 12.400, 323.100],
        ]
    )

    # Losscoef records table
    Losscoef_recs = np.array(
        [
            [
                1.4,
                1.2,
                0.7,
                0.1,
                0.3,
                0.1,
                0.1,
                0.1,
                0.3,
                0.5,
                0.3,
                0.2,
                0.4,
                0.3,
                0.1,
            ],
            [
                1.2,
                1.5,
                1.3,
                0.0,
                0.5,
                0.2,
                0.0,
                0.1,
                0.2,
                0.4,
                0.4,
                0.0,
                0.4,
                1.0,
                0.2,
            ],
            [
                0.7,
                1.3,
                7.6,
                0.1,
                1.3,
                0.9,
                0.1,
                0.0,
                0.8,
                1.2,
                1.7,
                0.0,
                2.6,
                11.1,
                2.8,
            ],
            [
                0.1,
                0.0,
                0.1,
                3.4,
                0.7,
                0.4,
                1.1,
                5.0,
                2.9,
                3.2,
                1.1,
                0.0,
                0.1,
                0.1,
                2.6,
            ],
            [
                0.3,
                0.5,
                1.3,
                0.7,
                9.0,
                1.4,
                0.3,
                1.2,
                1.0,
                1.3,
                0.7,
                0.2,
                0.2,
                2.4,
                0.3,
            ],
            [
                0.1,
                0.2,
                0.9,
                0.4,
                1.4,
                1.6,
                0.0,
                0.6,
                0.5,
                0.8,
                1.1,
                0.1,
                0.2,
                1.7,
                0.3,
            ],
            [
                0.1,
                0.0,
                0.1,
                1.1,
                0.3,
                0.0,
                1.5,
                1.7,
                1.5,
                0.9,
                0.5,
                0.7,
                0.0,
                0.2,
                0.8,
            ],
            [
                0.1,
                0.1,
                0.0,
                5.0,
                1.2,
                0.6,
                1.7,
                16.8,
                8.2,
                7.9,
                2.3,
                3.6,
                0.1,
                0.5,
                7.8,
            ],
            [
                0.3,
                0.2,
                0.8,
                2.9,
                1.0,
                0.5,
                1.5,
                8.2,
                12.9,
                11.6,
                2.1,
                2.5,
                0.7,
                1.2,
                7.2,
            ],
            [
                0.5,
                0.4,
                1.2,
                3.2,
                1.3,
                0.8,
                0.9,
                7.9,
                11.6,
                20.0,
                2.7,
                3.4,
                0.9,
                1.1,
                8.8,
            ],
            [
                0.3,
                0.4,
                1.7,
                1.1,
                0.7,
                1.1,
                0.5,
                2.3,
                2.1,
                2.7,
                14.0,
                0.1,
                0.4,
                3.8,
                16.8,
            ],
            [
                0.2,
                0.0,
                0.0,
                0.0,
                0.2,
                0.1,
                0.7,
                3.6,
                2.5,
                3.4,
                0.1,
                5.4,
                0.1,
                0.4,
                2.8,
            ],
            [
                0.4,
                0.4,
                2.6,
                0.1,
                0.2,
                0.2,
                0.0,
                0.1,
                0.7,
                0.9,
                0.4,
                0.1,
                10.3,
                10.1,
                2.8,
            ],
            [
                0.3,
                1.0,
                11.1,
                0.1,
                2.4,
                1.7,
                0.2,
                0.5,
                1.2,
                1.1,
                3.8,
                0.4,
                10.1,
                57.8,
                9.4,
            ],
            [
                0.1,
                0.2,
                2.8,
                2.6,
                0.3,
                0.3,
                0.8,
                7.8,
                7.2,
                8.8,
                16.8,
                2.8,
                2.8,
                9.4,
                128.3,
            ],
        ]
    )

    return bound_recs, data_recs, Losscoef_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    i = Set(
        m,
        name="i",
        records=[str(i) for i in range(1, 16)],
        description="generating units",
    )
    bou = Set(
        m, name="bou", records=["low", "upp"], description="lower and upper"
    )
    coef = Set(
        m,
        name="coef",
        records=["a", "b", "c"],
        description="coefficients in fuel cost of thermal generating unit",
    )

    # ALIAS #
    j = Alias(m, name="j", alias_with=i)

    # PARAMETERS #

    # The output of the minimum and maximum operation of the
    # generating units in MW.
    bound = Parameter(
        m, name="bound", domain=[i, bou], records=data_records()[0]
    )

    # The cost coefficients of generator units.
    data = Parameter(
        m, name="data", domain=[i, coef], records=data_records()[1]
    )

    # The loss coefficients
    Losscoef = Parameter(
        m, name="Losscoef", domain=[i, j], records=data_records()[2]
    )

    Load = Parameter(m, name="Load", records=1980)

    # VARIABLES #
    P = Variable(
        m, name="P", domain=[i], description="optimal generation level of i"
    )
    obj = Variable(m, name="obj", description="minimum cost")

    # EQUATIONS #
    cost = Equation(
        m, name="cost", type="regular", description="total generation cost"
    )
    bal = Equation(
        m, name="bal", type="regular", description="demand-supply balance"
    )

    # Objective function:
    cost.definition = obj == Sum(
        i,
        data[i, "a"] * gams_math.power(P[i], 2)
        + data[i, "b"] * P[i]
        + data[i, "c"],
    )

    # Constraints:
    bal.definition = (
        Sum(i, P[i]) - Sum([i, j], P[i] * Losscoef[i, j] * P[j] / 10000)
        == Load
    )

    # Bounds on variables:
    P.lo[i] = bound[i, "low"]
    P.up[i] = bound[i, "upp"]

    P.l[i] = (bound[i, "low"] + bound[i, "upp"]) / 2

    edc2 = Model(
        m,
        name="edc2",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=obj,
    )

    edc2.solve()

    import math

    assert math.isclose(edc2.objective_value, 29850.5910, rel_tol=0.001)

    print("Objective Function Value:  ", edc2.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/edc2.py
GAMS-dev/gamspy
"""
Linear Phase Lowpass Filter Design (FDESIGN)

This model finds the filter weights for a finite impulse response
(FIR) filter. We use rotated quadratic cones for the constraints.

This model is the minimax linear phase lowpass filter design from Lobo
et. al (Section 3.3) We model the nonlinear term 1/t in the model as
follows: introduce variables u,v, where v = 2 (and u = 1/t). Then 1/t
can be modeled as the quadratic cone

              ||[v, u-t]|| <= u+t,   u,t >=0

Contributed by Michael Ferris, University of Wisconsin, Madison


Lobo, M S, Vandenberghe, L, Boyd, S, and Lebret, H, Applications of
Second Order Cone Programming. Linear Algebra and its Applications,
Special Issue on Linear Algebra in Control, Signals and Image
Processing. 284 (November, 1998).

Keywords: quadratic constraint programming, second order cone programming,
          engineering, finite impulse response filter designment
"""
import math

import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    i = Set(m, name="i", records=[str(idx) for idx in range(0, 181)])
    omega_stop = Set(
        m,
        name="omega_stop",
        domain=[i],
        records=[str(idx) for idx in range(120, 181)],
    )
    omega_pass = Set(
        m,
        name="omega_pass",
        domain=[i],
        records=[str(idx) for idx in range(0, 91)],
    )
    k = Set(m, name="k", records=[str(idx) for idx in range(0, 11)])

    # Parameter
    beta = Parameter(m, name="beta", records=0.01)
    step = Parameter(m, name="step", records=math.pi / 180)
    n = Parameter(m, name="n", records=20)
    omega = Parameter(m, name="omega", domain=[i])
    omega[i] = (Ord(i) - 1) * step

    # Variable
    h = Variable(m, name="h", domain=[k])
    t = Variable(m, name="t")
    v2 = Variable(m, name="v2")
    v3 = Variable(m, name="v3", type="Positive")
    u = Variable(m, name="u", type="Positive")
    v = Variable(m, name="v", type="Positive")

    # Equation
    passband_up_bnds = Equation(m, name="passband_up_bnds", domain=[i])
    cone_lhs = Equation(m, name="cone_lhs")
    cone_rhs = Equation(m, name="cone_rhs")
    so = Equation(m, name="so")
    passband_lo_bnds = Equation(m, name="passband_lo_bnds", domain=[i])
    stopband_bnds = Equation(m, name="stopband_bnds", domain=[i])
    stopband_bnds2 = Equation(m, name="stopband_bnds2", domain=[i])

    passband_up_bnds[i].where[omega_pass[i]] = (
        2
        * Sum(
            k.where[Ord(k) < Card(k)],
            h[k] * gams_math.cos((Ord(k) - 1 - (n - 1) / 2) * omega[i]),
        )
        <= t
    )

    cone_rhs.definition = v2 == u - t
    cone_lhs.definition = v3 == u + t
    so.definition = v3**2 >= v**2 + v2**2

    passband_lo_bnds[i].where[omega_pass[i]] = u <= 2 * Sum(
        k.where[Ord(k) < Card(k)],
        h[k] * gams_math.cos((Ord(k) - 1 - (n - 1) / 2) * omega[i]),
    )

    stopband_bnds[i].where[omega_stop[i]] = -beta <= 2 * Sum(
        k.where[Ord(k) < Card(k)],
        h[k] * gams_math.cos((Ord(k) - 1 - (n - 1) / 2) * omega[i]),
    )

    stopband_bnds2[i].where[omega_stop[i]] = (
        2
        * Sum(
            k.where[Ord(k) < Card(k)],
            h[k] * gams_math.cos((Ord(k) - 1 - (n - 1) / 2) * omega[i]),
        )
        <= beta
    )

    t.lo.assignment = 1
    v.fx.assignment = 2

    fir_socp = Model(
        m,
        name="fir_socp",
        equations=m.getEquations(),
        problem=Problem.QCP,
        sense=Sense.MIN,
        objective=t,
    )
    fir_socp.solve()

    assert math.isclose(fir_socp.objective_value, 1.0465, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/fdesign.py
GAMS-dev/gamspy
"""
   Analysis of the stability margin of the spark ignition engine
   Fiat Dedra.

   References:
   B.R. Barmish, New tools for robustness of linear systems.
   McMillan Publishing Company, New York, 1994.

   M. Abate, B. Barmish, C. Murillo-Sanchez, R. Tempo, Application of
   some new tools to robust stability analysis of spark ignition engines:
   A case study. IEEE Trans. Contr. syst. tech., vol.2, 1994, pp. 22.

   Neculai Andrei, "Models, Test Problems and Applications for
   Mathematical Programming". Technical Press, Bucharest, 2003.
   Application A41, page 407.

   Floudas, C.A., Pardalos, P.M., et al. "Handbook of Test Problems in
   Local and Global Optimization". Kluwer Academic Publishers, Dordrecht,
   1999.
   Problem 7.3.6. Test problem 16, page 103.
"""
import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    q1 = Variable(m, name="q1")
    q2 = Variable(m, name="q2")
    q3 = Variable(m, name="q3")
    q4 = Variable(m, name="q4")
    q5 = Variable(m, name="q5")
    q6 = Variable(m, name="q6")
    q7 = Variable(m, name="q7")
    a0 = Variable(m, name="a0")
    a1 = Variable(m, name="a1")
    a2 = Variable(m, name="a2")
    a3 = Variable(m, name="a3")
    a4 = Variable(m, name="a4")
    a5 = Variable(m, name="a5")
    a6 = Variable(m, name="a6")
    a7 = Variable(m, name="a7")
    objval = Variable(
        m, name="objval", description="objective function variable"
    )
    w = Variable(m, name="w", description="frequency")
    k = Variable(m, name="k", description="stability margin")

    # EQUATIONS #
    f = Equation(m, name="f", type="regular", description="Objective function")
    g1 = Equation(m, name="g1", type="regular")
    g2 = Equation(m, name="g2", type="regular")
    b1l = Equation(m, name="b1l", type="regular")
    b1u = Equation(m, name="b1u", type="regular")
    b2l = Equation(m, name="b2l", type="regular")
    b2u = Equation(m, name="b2u", type="regular")
    b3l = Equation(m, name="b3l", type="regular")
    b3u = Equation(m, name="b3u", type="regular")
    b4l = Equation(m, name="b4l", type="regular")
    b4u = Equation(m, name="b4u", type="regular")
    b5l = Equation(m, name="b5l", type="regular")
    b5u = Equation(m, name="b5u", type="regular")
    b6l = Equation(m, name="b6l", type="regular")
    b6u = Equation(m, name="b6u", type="regular")
    b7l = Equation(m, name="b7l", type="regular")
    b7u = Equation(m, name="b7u", type="regular")
    ga0 = Equation(m, name="ga0", type="regular")
    ga1 = Equation(m, name="ga1", type="regular")
    ga2 = Equation(m, name="ga2", type="regular")
    ga3 = Equation(m, name="ga3", type="regular")
    ga4 = Equation(m, name="ga4", type="regular")
    ga5 = Equation(m, name="ga5", type="regular")
    ga6 = Equation(m, name="ga6", type="regular")
    ga7 = Equation(m, name="ga7", type="regular")

    f.definition = objval == k

    g1.definition = (
        -a6 * gams_math.power(w, 6)
        + a4 * gams_math.power(w, 4)
        - a2 * gams_math.power(w, 2)
        + a0
        == 0
    )
    g2.definition = (
        a7 * gams_math.power(w, 6)
        - a5 * gams_math.power(w, 4)
        + a3 * gams_math.power(w, 2)
        - a1
        == 0
    )

    b1l.definition = 3.4329 - 1.02721 * k <= q1
    b1u.definition = q1 <= 3.4320 + 1.02721 * k
    b2l.definition = 0.1627 - 0.06 * k <= q2
    b2u.definition = q2 <= 0.1627 + 0.06 * k
    b3l.definition = 0.1139 - 0.0782 * k <= q3
    b3u.definition = q3 <= 0.1139 + 0.0782 * k
    b4l.definition = 1.2539 - 0.3068 * k <= q4
    b4u.definition = q4 <= 1.2539 + 0.3068 * k
    b5l.definition = 0.0208 - 0.0108 * k <= q5
    b5u.definition = q5 <= 0.0208 + 0.08 * k
    b6l.definition = 5.0247 - 2.4715 * k <= q6
    b6u.definition = q6 <= 5.0247 + 2.4715 * k
    b7l.definition = 1.0 - 2 * k <= q7
    b7u.definition = q7 <= 1.0 + 2 * k

    ga0.definition = (
        a0
        == 6.82079e-05 * q1 * q3 * gams_math.power(q4, 2)
        + 6.82079e-05 * q1 * q2 * q4 * q5
    )

    ga1.definition = a1 == (
        0.00076176 * gams_math.power(q2, 2) * gams_math.power(q5, 2)
        + 0.00076176 * gams_math.power(q3, 2) * gams_math.power(q4, 2)
        + 0.000402141 * q1 * q2 * gams_math.power(q5, 2)
        + 0.00337606 * q1 * q3 * gams_math.power(q4, 2)
        + 6.82079e-05 * q1 * q4 * q5
        + 0.00051612 * gams_math.power(q2, 2) * q5 * q6
        + 0.00337606 * q1 * q2 * q4 * q5
        + 6.82079e-05 * q1 * q2 * q4 * q7
        + 6.28987e-05 * q1 * q2 * q5 * q6
        + 0.000402141 * q1 * q3 * q4 * q5
        + 6.28987e-05 * q1 * q3 * q4 * q6
        + 0.00152352 * q2 * q3 * q4 * q5
        + 0.00051612 * q2 * q3 * q4 * q6
    )

    ga2.definition = a2 == (
        0.000402141 * q1 * gams_math.power(q5, 2)
        + 0.00152352 * q2 * gams_math.power(q5, 2)
        + 0.0552 * gams_math.power(q2, 2) * gams_math.power(q5, 2)
        + 0.0552 * gams_math.power(q3, 2) * gams_math.power(q4, 2)
        + 0.0189477 * q1 * q2 * gams_math.power(q5, 2)
        + 0.034862 * q1 * q3 * gams_math.power(q4, 2)
        + 0.00336706 * q1 * q4 * q5
        + 6.82079e-05 * q1 * q4 * q7
        + 6.28987e-05 * q1 * q5 * q6
        + 0.00152352 * q3 * q4 * q5
        + 0.00051612 * q3 * q4 * q6
        - 0.00234048 * gams_math.power(q3, 2) * q4 * q6
        + 0.034862 * q1 * q2 * q4 * q5
        + 0.0237398 * gams_math.power(q2, 2) * q5 * q6
        + 0.00152352 * gams_math.power(q2, 2) * q5 * q7
        + 0.00051612 * gams_math.power(q2, 2) * q6 * q7
        + 0.00336706 * q1 * q2 * q4 * q7
        + 0.00287416 * q1 * q2 * q5 * q6
        + 0.000804282 * q1 * q2 * q5 * q7
        + 6.28987e-05 * q1 * q2 * q6 * q7
        + 0.0189477 * q1 * q3 * q4 * q5
        + 0.00287416 * q1 * q3 * q4 * q6
        + 0.000402141 * q1 * q3 * q4 * q7
        + 0.1104 * q2 * q3 * q4 * q5
        + 0.0237398 * q2 * q3 * q4 * q6
        + 0.00152352 * q2 * q3 * q4 * q7
        - 0.00234048 * q2 * q3 * q5 * q6
        + 0.00103224 * q2 * q5 * q6
    )

    ga3.definition = a3 == (
        0.189477 * q1 * gams_math.power(q5, 2)
        + 0.1104 * q2 * gams_math.power(q5, 2)
        + 0.00051612 * q5 * q6
        + gams_math.power(q2, 2) * gams_math.power(q5, 2)
        + 0.00076176 * gams_math.power(q2, 2) * gams_math.power(q7, 2)
        + gams_math.power(q3, 2) * gams_math.power(q4, 2)
        + 0.1586 * q1 * q2 * gams_math.power(q5, 2)
        + 0.000402141 * q1 * q2 * gams_math.power(q7, 2)
        + 0.0872 * q1 * q3 * gams_math.power(q4, 2)
        + 0.034862 * q1 * q4 * q5
        + 0.00336706 * q1 * q4 * q7
        + 0.00287416 * q1 * q5 * q6
        + 6.28987e-05 * q1 * q6 * q7
        + 0.00103224 * q2 * q6 * q7
        + 0.1104 * q3 * q4 * q5
        + 0.0237398 * q3 * q4 * q6
        + 0.00152352 * q3 * q4 * q7
        - 0.00234048 * q3 * q5 * q6
        + 0.1826 * gams_math.power(q2, 2) * q5 * q6
        + 0.1104 * gams_math.power(q2, 2) * q5 * q7
        + 0.0237398 * gams_math.power(q2, 2) * q6 * q7
        - 0.0848 * gams_math.power(q3, 2) * q4 * q6
        + 0.0872 * q1 * q2 * q4 * q5
        + 0.034862 * q1 * q2 * q4 * q7
        + 0.0215658 * q1 * q2 * q5 * q6
        + 0.0378954 * q1 * q2 * q5 * q7
        + 0.00287416 * q1 * q2 * q6 * q7
        + 0.1586 * q1 * q3 * q4 * q5
        + 0.0215658 * q1 * q3 * q4 * q6
        + 0.0189477 * q1 * q3 * q4 * q7
        + 2 * q2 * q3 * q4 * q5
        + 0.1826 * q2 * q3 * q4 * q6
        + 0.1104 * q2 * q3 * q4 * q7
        - 0.0848 * q2 * q3 * q5 * q6
        - 0.00234048 * q2 * q3 * q6 * q7
        + 0.00076176 * gams_math.power(q5, 2)
        + 0.0474795 * q2 * q5 * q6
        + 0.000804282 * q1 * q5 * q7
        + 0.00304704 * q2 * q5 * q7
    )

    ga4.definition = a4 == (
        0.1586 * q1 * gams_math.power(q5, 2)
        + 0.000402141 * q1 * gams_math.power(q7, 2)
        + 2 * q2 * gams_math.power(q5, 2)
        + 0.00152352 * q2 * gams_math.power(q7, 2)
        + 0.0237398 * q5 * q6
        + 0.00152352 * q5 * q7
        + 0.00051612 * q6 * q7
        + 0.0552 * gams_math.power(q2, 2) * gams_math.power(q7, 2)
        + 0.0189477 * q1 * q2 * gams_math.power(q7, 2)
        + 0.0872 * q1 * q4 * q5
        + 0.034862 * q1 * q4 * q7
        + 0.0215658 * q1 * q5 * q6
        + 0.00287416 * q1 * q6 * q7
        + 0.0474795 * q2 * q6 * q7
        + 2 * q3 * q4 * q5
        + 0.1826 * q3 * q4 * q6
        + 0.1104 * q3 * q4 * q7
        - 0.0848 * q3 * q5 * q6
        - 0.00234048 * q3 * q6 * q7
        + 2 * gams_math.power(q2, 2) * q5 * q7
        + 0.1826 * gams_math.power(q2, 2) * q6 * q7
        + 0.0872 * q1 * q2 * q4 * q7
        + 0.3172 * q1 * q2 * q5 * q7
        + 0.0215658 * q1 * q2 * q6 * q7
        + 0.1586 * q1 * q3 * q4 * q7
        + 2 * q2 * q3 * q4 * q7
        - 0.0848 * q2 * q3 * q6 * q7
        + 0.0552 * gams_math.power(q5, 2)
        + 0.3652 * q2 * q5 * q6
        + 0.0378954 * q1 * q5 * q7
        + 0.2208 * q2 * q5 * q7
    )

    ga5.definition = (
        a5
        == 0.0189477 * q1 * gams_math.power(q7, 2)
        + 0.1104 * q2 * gams_math.power(q7, 2)
        + 0.1826 * q5 * q6
        + 0.1104 * q5 * q7
        + 0.0237398 * q6 * q7
        + gams_math.power(q2, 2) * gams_math.power(q7, 2)
        + 0.1586 * q1 * q2 * gams_math.power(q7, 2)
        + 0.0872 * q1 * q4 * q7
        + 0.0215658 * q1 * q6 * q7
        + 0.3652 * q2 * q6 * q7
        + 2 * q3 * q4 * q7
        - 0.0848 * q3 * q6 * q7
        + gams_math.power(q5, 2)
        + 0.00076176 * gams_math.power(q7, 2)
        + 0.3172 * q1 * q5 * q7
        + 4 * q2 * q5 * q7
    )

    ga6.definition = a6 == (
        0.1586 * q1 * gams_math.power(q7, 2)
        + 2 * q2 * gams_math.power(q7, 2)
        + 2 * q5 * q7
        + 0.1826 * q6 * q7
        + 0.0552 * gams_math.power(q7, 2)
    )

    ga7.definition = a7 == gams_math.power(q7, 2)

    # Bounds
    # q1.up.assignment = 3.4329
    # q2.up.assignment = 0.1627
    # q3.up.assignment = 0.1139
    # q4.lo.assignment = 0.2539
    # q5.up.assignment = 0.0208
    # q6.lo.assignment = 2.0247
    # q7.lo.assignment = 1
    w.lo.assignment = 0
    w.up.assignment = 10
    k.lo.assignment = 0
    k.up.assignment = 10

    # Initial point
    q1.l.assignment = 0.2
    q2.l.assignment = 0.02
    q3.l.assignment = 0.1
    q4.l.assignment = 0.3
    q5.l.assignment = 0
    q6.l.assignment = 2
    q7.l.assignment = 4.5
    w.l.assignment = 0
    k.l.assignment = 2

    fiat = Model(
        m,
        name="fiat",
        equations=m.getEquations(),
        problem="NLP",
        sense="MIN",
        objective=objval,
    )

    fiat.solve()
    import math

    assert math.isclose(fiat.objective_value, 1.4594, rel_tol=0.001)
    print("Objective Function Value:  ", fiat.objective_value)
    # End Fiat


if __name__ == "__main__":
    main()

tests/integration/models/fiat.py
GAMS-dev/gamspy
"""
Flow Shop Scheduling - (FLOWSHOP)

A workshop that produces metal pipes on demand for automotive industry
has three machines for bending the pipes, soldering the fastenings,
and assembling the links. The workshop has to produce six items, for
which the durations of the processing steps are given below. Once
started, jobs must be carried out to completion, but the
workpieces(items) may wait between the machines.

Every machine only processes one item at a time. A workpiece(item) may
not overtake any other.

What is the sequence that minimizes the total time for completing all
items (makespan)?


Gueret, C, Prins, C, and Sevaux, M, Applications of Optimization with
Xpress-MP, Translated and revised by Susanne Heipcke. Dash
Optimization, 2002.

Keywords: mixed integer linear programming, relaxed mixed integer programming,
          scenario analysis, GUSS, flow shop scheduling, production planning
"""
import pandas as pd

import gamspy as gap


def flow_shop(process_time_df, last_machine, last_item):
    c = gap.Container(delayed_execution=True)

    # Sets
    i = c.addSet(
        name="i", description="item", records=process_time_df["item"].unique()
    )
    m = c.addSet(
        name="m",
        description="machine",
        records=process_time_df["machine"].unique(),
    )
    k = c.addAlias("k", i)

    # Parameters
    proctime = c.addParameter(
        name="proctime",
        domain=[m, i],
        description="process time of item i on machine m",
        records=process_time_df,
    )

    # Variables
    rank = c.addVariable(
        name="rank",
        type="binary",
        domain=[i, k],
        description="item i has position k",
    )
    start = c.addVariable(
        name="start",
        type="positive",
        domain=[m, k],
        description="start time for job in position k on m",
    )
    comp = c.addVariable(
        name="comp",
        type="positive",
        domain=[m, k],
        description="completion time for job in position k on m",
    )
    totwait = c.addVariable(
        name="totwait",
        type="free",
        description="before first job + times between jobs on last machine",
    )

    # Equations
    oneInPosition = c.addEquation(
        name="oneInPosition",
        domain=[k],
        description="every position gets a jobs",
    )
    oneRankPer = c.addEquation(
        name="oneRankPer",
        domain=[i],
        description="every job is assigned a rank",
    )
    onMachRel = c.addEquation(
        name="onMachRel",
        domain=[m, k],
    )
    perMachRel = c.addEquation(
        name="perMachRel",
        domain=[m, k],
    )
    defComp = c.addEquation(
        name="defComp",
        domain=[m, k],
        description="completion time based on start time and proctime",
    )
    defObj = c.addEquation(
        name="defObj",
        description="completion time of job rank last",
    )

    oneInPosition[k] = gap.Sum(i, rank[i, k]) == 1
    oneRankPer[i] = gap.Sum(k, rank[i, k]) == 1
    onMachRel[m, k.lead(1)] = start[m, k.lead(1)] >= comp[m, k]
    perMachRel[m.lead(1), k] = start[m.lead(1), k] >= comp[m, k]
    defComp[m, k] = comp[m, k] == start[m, k] + gap.Sum(
        i, proctime[m, i] * rank[i, k]
    )

    defObj.definition = totwait >= comp[last_machine, last_item]

    flowshop = gap.Model(
        container=c,
        name="flowshop",
        equations=c.getEquations(),
        problem="MIP",
        sense=gap.Sense.MIN,
        objective=totwait,
    )

    # set optCr to 0
    c.addOptions({"optcr": 0})

    flowshop.solve()

    import math

    assert math.isclose(flowshop.objective_value, 35, rel_tol=0.001)


def prepare_data():
    # Prepare data
    process_time = pd.DataFrame(
        [
            ["blending", "i1", 3],
            ["blending", "i2", 6],
            ["blending", "i3", 3],
            ["blending", "i4", 5],
            ["blending", "i5", 5],
            ["blending", "i6", 7],
            ["soldering", "i1", 5],
            ["soldering", "i2", 4],
            ["soldering", "i3", 2],
            ["soldering", "i4", 4],
            ["soldering", "i5", 4],
            ["soldering", "i6", 5],
            ["assembly", "i1", 5],
            ["assembly", "i2", 2],
            ["assembly", "i3", 4],
            ["assembly", "i4", 6],
            ["assembly", "i5", 3],
            ["assembly", "i6", 6],
        ],
        columns=["machine", "item", "value"],
    )

    last_item = process_time["item"].unique()[-1]
    last_machine = process_time["machine"].unique()[-1]

    return process_time, last_machine, last_item


if __name__ == "__main__":
    process_time, last_machine, last_item = prepare_data()
    flow_shop(
        process_time_df=process_time,
        last_machine=last_machine,
        last_item=last_item,
    )

tests/integration/models/flowshop.py
GAMS-dev/gamspy
"""
Design of a disc flywheel.

Schittkowski, K., More test examples for nonlinear programming codes.
Lecture Notes in Economics and Mathematical Systems, Vol.282, Springer-Verlag,
Berlin, 1987. (Problem 346, page 167)
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    obj = Variable(m, name="obj")

    # EQUATIONS #
    e1 = Equation(m, name="e1", type="regular")
    e2 = Equation(m, name="e2", type="regular")
    eobj = Equation(m, name="eobj", type="regular")

    # Objective function:
    eobj.definition = obj == -0.0201 * (x1**4) * x2 * (x3**2) / 10000000

    # Constraints:
    e1.definition = 675 - (x1**2) * x2 >= 0
    e2.definition = 0.419 - (x1**2) * (x3**2) / 10000000 >= 0

    # Bounds on variables:
    x1.lo.assignment = 0
    x1.up.assignment = 36
    x2.lo.assignment = 0
    x2.up.assignment = 5
    x3.lo.assignment = 0
    x3.up.assignment = 125

    # Initial point:
    x1.l.assignment = 22.3
    x2.l.assignment = 0.5
    x3.l.assignment = 125

    flywheel = Model(
        m,
        name="flywheel",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=obj,
    )

    flywheel.solve()

    print("Objective Function Value:  ", round(obj.toValue(), 4))
    print("x1:  ", round(x1.toValue(), 3))
    print("x2:  ", round(x2.toValue(), 3))
    print("x3:  ", round(x3.toValue(), 3))

    # End flywheel


if __name__ == "__main__":
    main()

tests/integration/models/flywheel.py
GAMS-dev/gamspy
"""
Food Manufacturing Problem - Blending of Oils (FOOD)

The problem is to plan the blending of five kinds of oil, organized in two
categories (two kinds of vegetable oils and three kinds of non vegetable oils)
into batches of blended products over six months.

Some of the oil is already available in storage. There is an initial stock of
oil of 500 tons of each raw type when planning begins. An equal stock should
exist in storage at the end of the plan. Up to 1000 tons of each type of raw
oil can be stored each month for later use. The price for storage of raw oils
is 5 monetary units per ton. Refined oil cannot be stored. The blended product
cannot be stored either.

The rest of the oil (that is, any not available in storage) must be bought in
quantities to meet the blending requirements. The price of each kind of oil
varies over the six-month period.

The two categories of oil cannot be refined on the same production line.
There is a limit on how much oil of each category (vegetable or non vegetable)
can be refined in a given month:
 - Not more than 200 tons of vegetable oil can be refined per month.
 - Not more than 250 tons of non vegetable oil can be refined per month.

There are constraints on the blending of oils:
 - The product cannot blend more than three oils.
 - When a given type of oil is blended into the product, at least 20 tons of
   that type must be used.
 - If either vegetable oil 1 (v1) or vegetable oil 2 (v2) is blended in the
   product, then non vegetable oil 3 (o3) must also be blended in that product.

The final product (refined and blended) sells for a known price:
150 monetary units per ton.

The aim of the six-month plan is to minimize production and storage costs while
maximizing profit.


This example is taken from the Cplex 12 User's Manual
(ILOG, Cplex 12 User's Manual, 2009)

Williams, H P, Model Building in Mathematical Programming. John Wiley
and Sons, 1978.

Keywords: mixed integer linear programming, food manufacturing, blending
problem
"""
import pandas as pd

import gamspy as gp


def main():
    vegetable_oils = ["v1", "v2"]
    non_vegetable_oils = ["o1", "o2", "o3"]

    # Prepare data
    cost_mp = pd.DataFrame(
        [
            ["m1", "v1", 110],
            ["m1", "v2", 120],
            ["m1", "o1", 130],
            ["m1", "o2", 110],
            ["m1", "o3", 115],
            ["m2", "v1", 130],
            ["m2", "v2", 130],
            ["m2", "o1", 110],
            ["m2", "o2", 90],
            ["m2", "o3", 115],
            ["m3", "v1", 110],
            ["m3", "v2", 140],
            ["m3", "o1", 130],
            ["m3", "o2", 100],
            ["m3", "o3", 95],
            ["m4", "v1", 120],
            ["m4", "v2", 110],
            ["m4", "o1", 120],
            ["m4", "o2", 120],
            ["m4", "o3", 125],
            ["m5", "v1", 100],
            ["m5", "v2", 120],
            ["m5", "o1", 150],
            ["m5", "o2", 110],
            ["m5", "o3", 105],
            ["m6", "v1", 90],
            ["m6", "v2", 100],
            ["m6", "o1", 140],
            ["m6", "o2", 80],
            ["m6", "o3", 135],
        ],
        columns=["planning period", "raw oil", "raw oil cost"],
    )

    products = cost_mp["raw oil"].unique()

    stock_p = pd.DataFrame([(pp, 500) for pp in products])

    hardness = pd.DataFrame(
        [["v1", 8.8], ["v2", 6.1], ["o1", 2], ["o2", 4.2], ["o3", 5]],
        columns=["raw oil", "hardness"],
    )

    maxstore = 1000

    c = gp.Container(delayed_execution=True)

    # Sets
    m = gp.Set(
        c,
        name="m",
        description="planing periods",
        records=cost_mp["planning period"].unique(),
    )
    p = gp.Set(c, name="p", description="raw oils", records=products)
    pv = gp.Set(
        c,
        name="pv",
        domain=p,
        description="vegetable oils",
        records=vegetable_oils,
    )
    pnv = gp.Set(
        c,
        name="pnv",
        domain=p,
        description="non-vegetable oils",
        records=non_vegetable_oils,
    )

    # Parameter
    maxusepv = gp.Parameter(
        c,
        name="maxusepv",
        description="maximum use of vegetable oils",
        records=200,
    )
    maxusepnv = gp.Parameter(
        c,
        name="maxusepnv",
        description="maximum use of non-vegetable oils",
        records=250,
    )
    minusep = gp.Parameter(
        c, name="minusep", description="minimum use of raw oil", records=20
    )
    maxnusep = gp.Parameter(
        c,
        name="maxnusep",
        description="maximum number of raw oils in a blend",
        records=3,
    )
    sp = gp.Parameter(
        c,
        name="sp",
        description="sales price of refined and blended oil",
        records=150,
    )
    sc = gp.Parameter(
        c, name="sc", description="storage cost of raw oils", records=5
    )
    stock = gp.Parameter(
        c,
        name="stock",
        description="stock at the beginning and end",
        domain=p,
        records=stock_p,
    )
    hmin = gp.Parameter(
        c,
        name="hmin",
        description="minimum hardness of refined oil",
        records=3,
    )
    hmax = gp.Parameter(
        c,
        name="hmax",
        description="maximum hardness of refined oil",
        records=6,
    )
    h = gp.Parameter(
        c,
        name="h",
        description="hardness of raw oils",
        domain=p,
        records=hardness,
    )
    cost = gp.Parameter(
        c,
        name="cost",
        description="raw oil cost",
        domain=[m, p],
        records=cost_mp,
    )

    # Variables
    produce = gp.Variable(
        c,
        name="produce",
        description="production of blended and refined oil per month",
        domain=m,
        type="positive",
    )
    use = gp.Variable(
        c,
        name="use",
        description="usage of raw oil per month",
        domain=[m, p],
        type="positive",
    )
    induse = gp.Variable(
        c,
        name="induse",
        description="indicator for usage of raw oil per month",
        domain=[m, p],
        type="binary",
    )
    buy = gp.Variable(
        c,
        name="buy",
        description="purchase of raw oil per month",
        domain=[m, p],
        type="positive",
    )
    store = gp.Variable(
        c,
        name="store",
        description="storage of raw oil at end of the month",
        domain=[m, p],
        type="positive",
    )
    profit = gp.Variable(c, name="profit", description="objective variable")

    # Equation
    defObj = gp.Equation(c, name="defObj")
    defUsePv = gp.Equation(c, name="defUsePv", domain=m)
    defUsePnv = gp.Equation(c, name="defUsePnv", domain=m)
    defProduce = gp.Equation(c, name="defProduce", domain=m)
    defHmin = gp.Equation(c, name="defHmin", domain=m)
    defHmax = gp.Equation(c, name="defHmax", domain=m)
    stockbal = gp.Equation(c, name="stockbal", domain=[m, p])
    minUse = gp.Equation(c, name="minUse", domain=[m, p])
    maxUse = gp.Equation(c, name="maxUse", domain=[m, p])
    maxNuse = gp.Equation(c, name="maxNuse", domain=m)
    defLogic1 = gp.Equation(c, name="defLogic1", domain=m)

    defObj.definition = profit == gp.Sum(m, sp * produce[m]) - gp.Sum(
        (m, p), cost[m, p] * buy[m, p]
    ) - gp.Sum((m, p), sc * store[m, p])
    defUsePv[m] = gp.Sum(pv, use[m, pv]) <= maxusepv
    defUsePnv[m] = gp.Sum(pnv, use[m, pnv]) <= maxusepnv
    defProduce[m] = produce[m] == gp.Sum(p, use[m, p])
    defHmin[m] = gp.Sum(p, h[p] * use[m, p]) >= hmin * produce[m]
    defHmax[m] = gp.Sum(p, h[p] * use[m, p]) <= hmax * produce[m]
    stockbal[m, p] = (
        store[m.lag(1, type="circular"), p] + buy[m, p]
        == use[m, p] + store[m, p]
    )
    minUse[m, p] = use[m, p] >= minusep * induse[m, p]
    maxUse[m, p] = (
        use[m, p]
        <= (maxusepv.where[pv[p]] + maxusepnv.where[pnv[p]]) * induse[m, p]
    )
    maxNuse[m] = gp.Sum(p, induse[m, p]) <= maxnusep
    defLogic1[m] = gp.Sum(pv, induse[m, pv]) <= induse[m, "o3"] * gp.Card(pv)

    # set upper bound and fix variable value
    store.up[m, p] = maxstore
    store.fx["m6", p] = stock[p]

    food = gp.Model(
        c,
        name="food",
        equations=c.getEquations(),
        problem="MIP",
        sense=gp.Sense.MAX,
        objective=profit,
    )

    # set optCr to 0
    c.addOptions({"optcr": 0})

    food.solve()

    import math

    assert math.isclose(food.objective_value, 100278.7037, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/food.py
GAMS-dev/gamspy
"""
Fuel Scheduling and Unit Commitment Problem (FUEL)


Fuel scheduling and unit commitment addresses the problem of
fuel supply to plants and determining on/off status of units
simultaneously to minimize total operating cost.
The present problem: there are two generating units to
meet a total load over a 6-hour period. One of the unit is oil-based
and has to simultaneously meet the storage requirements, flow rates
etc. There are limits on the generation levels for both the units.


Wood, A J, and Wollenberg, B F, Example Problem 4e. In Power Generation,
Operation and Control. John Wiley and Sons, 1984, pp. 85-88.

Keywords: mixed integer nonlinear programming, scheduling, engineering, power
          generation, unit commitment problem
"""
import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    t = Set(m, name="t", records=["period-1", "period-2", "period-3"])

    # Data
    load = Parameter(
        m,
        name="load",
        domain=[t],
        records=pd.DataFrame(
            [["period-1", 400], ["period-2", 900], ["period-3", 700]]
        ),
    )
    initlev = Parameter(
        m,
        name="initlev",
        domain=[t],
        records=pd.DataFrame([["period-1", 3000]]),
    )

    # Variable
    status = Variable(m, name="status", domain=[t], type="Binary")
    poil = Variable(m, name="poil", domain=[t])
    others = Variable(m, name="others", domain=[t])
    oil = Variable(m, name="oil", domain=[t], type="Positive")
    volume = Variable(m, name="volume", domain=[t], type="Positive")
    cost = Variable(m, name="cost")

    volume.up[t] = 4000
    volume.lo[t].where[Ord(t) == Card(t)] = 2000

    others.lo[t] = 50
    others.up[t] = 700

    # Equation
    costfn = Equation(m, name="costfn")
    lowoil = Equation(m, name="lowoil", domain=[t])
    maxoil = Equation(m, name="maxoil", domain=[t])
    floweq = Equation(m, name="floweq", domain=[t])
    demcons = Equation(m, name="demcons", domain=[t])
    oileq = Equation(m, name="oileq", domain=[t])

    costfn.definition = cost == Sum(
        t, 300 + 6 * others[t] + 0.0025 * (others[t] ** 2)
    )
    lowoil[t] = poil[t] >= 100 * status[t]
    maxoil[t] = poil[t] <= 500 * status[t]
    floweq[t] = volume[t] == volume[t.lag(1)] + 500 - oil[t] + initlev[t]
    oileq[t] = oil[t] == 50 * status[t] + poil[t] + 0.005 * (poil[t] ** 2)
    demcons[t] = poil[t] + others[t] >= load[t]

    model = Model(
        m,
        name="ucom",
        equations=m.getEquations(),
        problem="MINLP",
        sense=Sense.MIN,
        objective=cost,
    )
    poil.l[t] = 100

    model.solve()

    import math

    assert math.isclose(model.objective_value, 8566.1190, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/fuel.py
GAMS-dev/gamspy
"""
Lagrangian Relaxation for Generalized Assignment (GAPMIN,SEQ=182)

A general assignment problem is solved via Lagrangian Relaxation
by dualizing the multiple choice constraints and solving
the remaining knapsack subproblems.

The data for this problem are taken from Martello.
The optimal value is 223 and the optimal solution is:
    1 1 4 2 3 5 1 4 3 5, where
in columns 1 and 2, the variable in the first row is equal to 1,
in column 3, the variable in the fourth row is equal to 1, etc...


Martello, S, and Toth, P, Knapsack Problems: Algorithms and Computer
Implementations. John Wiley and Sons, Chichester, 1990.

Guignard, M, and Rosenwein, M, An Improved Dual-Based Algorithm for
the Generalized Assignment Problem. Operations Research 37 (1989), 658-663.


 --- original model definition

Keywords: mixed integer linear programming, relaxed mixed integer linear
          programming, general assignment problem, lagrangian relaxation, knapsack
"""
import numpy as np

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import ModelStatus
from gamspy import Number
from gamspy import Parameter
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException


def sqr(x):
    return gams_math.power(x, 2)


def table_records():
    a_recs = np.array(
        [
            [12, 8, 25, 17, 19, 22, 6, 22, 20, 25],
            [5, 15, 15, 14, 7, 11, 14, 16, 17, 15],
            [21, 24, 13, 24, 12, 16, 23, 20, 15, 5],
            [23, 17, 10, 6, 24, 20, 15, 10, 19, 9],
            [17, 20, 15, 16, 5, 13, 7, 16, 8, 5],
        ]
    )

    f_recs = np.array(
        [
            [16, 26, 30, 47, 18, 19, 33, 37, 42, 31],
            [38, 42, 15, 21, 26, 11, 11, 50, 24, 19],
            [48, 17, 14, 22, 14, 18, 47, 32, 17, 42],
            [22, 32, 28, 39, 37, 23, 25, 12, 44, 17],
            [31, 42, 31, 40, 16, 15, 29, 31, 44, 41],
        ]
    )

    return a_recs, f_recs


def main():
    m = Container(delayed_execution=True)

    # Original Model Definition

    # SETS
    i = Set(m, name="i", description="resources")
    j = Set(m, name="j", description="items")

    # VARIABLES
    x = Variable(
        m,
        name="x",
        type="binary",
        domain=[i, j],
        description="assignment of i to j",
    )
    z = Variable(
        m, name="z", type="free", description="total cost of assignment"
    )

    # EQUATIONS ##
    capacity = Equation(
        m, name="capacity", domain=[i], description="resource availability"
    )
    choice = Equation(
        m,
        name="choice",
        domain=[j],
        description="assignment constraint.. one resource per item",
    )
    defz = Equation(m, name="defz", description="definition of total cost")

    # PARAMETERS ##
    a = Parameter(
        m,
        name="a",
        domain=[i, j],
        description="utilization of resource i by item j",
    )
    f = Parameter(
        m,
        name="f",
        domain=[i, j],
        description="cost of assigning item j to resource i",
    )
    b = Parameter(m, name="b", domain=[i], description="available resources")

    capacity[i] = Sum(j, a[i, j] * x[i, j]) <= b[i]

    choice[j] = Sum(i, x[i, j]) == 1

    defz.definition = z == Sum([i, j], f[i, j] * x[i, j])

    _ = Model(
        m,
        name="assign_mip",
        equations=[capacity, choice, defz],
        problem="mip",
        sense="MIN",
        objective=z,
    )
    assign_rmip = Model(
        m,
        name="assign_rmip",
        equations=[capacity, choice, defz],
        problem="rmip",
        sense="MIN",
        objective=z,
    )

    # data for Martello model
    # SETS
    i.setRecords([f"r{r}" for r in range(1, 6)])
    j.setRecords([f"i{i}" for i in range(1, 11)])

    xopt = Set(
        m,
        name="xopt",
        domain=[i, j],
        records=[
            ("r1", "i1"),
            ("r1", "i2"),
            ("r1", "i7"),
            ("r2", "i4"),
            ("r3", "i5"),
            ("r3", "i9"),
            ("r4", "i3"),
            ("r4", "i8"),
            ("r5", "i6"),
            ("r5", "i10"),
        ],
        description="optimal assignment",
    )

    # PARAMETERS
    a.setRecords(table_records()[0])
    f.setRecords(table_records()[1])
    b.setRecords(np.array([28, 20, 27, 24, 19]))

    #############################################################
    # if one wants to check the data, one can
    # solve the MIP problem, this is just a check

    # m.addOptions({"optCr": 0})
    # assign_mip.solve()

    # check = 0
    # for r_loop, i_loop in xopt.toList():
    #     check += x.pivot().loc[r_loop, i_loop]

    # if check != 1:
    #     raise GamspyException('*** Something wrong with this solution', x.pivot(), xopt.pivot())
    #############################################################

    # Relaxed Problem Definition and Subgradient Optimization
    # Lagrangian subproblem definition
    # uses dynamic set to define WHICH knapsack to solve

    # Set
    id = Set(
        m,
        name="id",
        domain=[i],
        description="dynamic version of i used to define a subset of i",
    )
    iter = Set(
        m,
        name="iter",
        records=[f"iter{it}" for it in range(1, 21)],
        description="subgradient iteration index",
    )

    # Alias
    ii = Alias(m, name="ii", alias_with=i)

    # Parameters
    w = Parameter(
        m, name="w", domain=[j], description="Lagrangian multipliers"
    )
    improv = Parameter(
        m,
        name="improv",
        description=(
            "has the Lagrangian bound improved over the previous iterations"
        ),
    )

    # Variable
    zlrx = Variable(m, name="zlrx", description="relaxed objective")

    # Equations
    knapsack = Equation(
        m,
        name="knapsack",
        domain=[i],
        description="capacity with dynamic sets",
    )
    defzlrx = Equation(m, name="defzlrx", description="definition of zlrx")

    knapsack[id] = Sum(j, a[id, j] * x[id, j]) <= b[id]

    defzlrx.definition = zlrx == Sum([id, j], (f[id, j] - w[j]) * x[id, j])

    pknap = Model(
        m,
        name="pknap",
        equations=[knapsack, defzlrx],
        problem="mip",
        sense="MIN",
        objective=zlrx,
    )

    # Scalar
    target = Parameter(
        m, name="target", description="target objective function value"
    )
    alpha = Parameter(
        m, name="alpha", records=[1], description="step adjuster"
    )
    norm = Parameter(m, name="norm", description="norm of slacks")
    step = Parameter(
        m, name="step", records=[0], description="step size for subgradient"
    )
    zfeas = Parameter(
        m,
        name="zfeas",
        description="value for best known solution or valid upper bound",
    )
    zlr = Parameter(m, name="zlr", description="Lagrangian objective value")
    zl = Parameter(m, name="zl", description="Lagrangian objective value")
    zlbest = Parameter(
        m, name="zlbest", description="current best Lagrangian lower bound"
    )
    count = Parameter(
        m, name="count", description="count of iterations without improvement"
    )
    reset = Parameter(
        m, name="reset", records=[5], description="reset count counter"
    )
    tol = Parameter(
        m, name="tol", records=[1e-5], description="termination tolerance"
    )
    status = Parameter(
        m, name="status", records=[0], description="outer loop status"
    )

    # Parameter
    s = Parameter(m, name="s", domain=[j], description="slack variable")
    report = Parameter(
        m, name="report", domain=[iter, "*"], description="iteration log"
    )
    xrep = Parameter(
        m, name="xrep", domain=[j, i, "*"], description="x iteration report"
    )
    srep = Parameter(
        m, name="srep", domain=[iter, j], description="slack report"
    )
    wrep = Parameter(
        m, name="wrep", domain=[iter, j], description="w iteration report"
    )

    # calculate initial Lagrangian multipliers
    # There are many possible ways to find initial multipliers.
    # The choice of initial multipliers is very important for the
    # overall performance. The marginals of the relaxed problem
    # are often used to initialize the multipliers. Another choice
    # is simply to start with zero multipliers.

    # replace 'default' with solver of your choice.
    m.addOptions({"mip": "default", "rmip": "default"})

    results = open("solution.txt", "w", encoding="UTF-8")
    results.write("solvers used: RMIP = CPLEX\n")
    results.write("               MIP = CPLEX\n")

    # solve relaxed problem to get initial multipliers
    # Note that different solvers get different dual solutions
    # which are not as good as a zero set of initial multipliers.

    assign_rmip.solve()
    results.write(f"\nRMIP objective value = {round(z.toValue(), 3)}\n")

    if assign_rmip.status == ModelStatus.OptimalGlobal:
        status.assignment = 1  # everything is ok
    else:
        raise GamspyException(
            "*** relaxed MIP not optimal", "    no subgradient iterations", x
        )

    xrep[j, i, "initial"] = x.l[i, j]
    xrep[j, i, "optimal"] = Number(1).where[xopt[i, j]]

    # Parameter
    _ = Parameter(
        m,
        name="wopt",
        domain=[j],
        records=np.array([35, 40, 60, 69, 21, 49, 42, 47, 64, 46]),
        description="an optimal set of multipliers",
    )

    zlbest.assignment = z.l

    # use RMIP duals
    w[j] = choice.m[j]

    # use optimal duals
    # w[j] = wopt[j]

    # use zero starting point
    # w[j]   = 0
    # zlbest.assignment = 0

    results.write(
        "\n\nzlbest                    objective value  = "
        f" {round(zlbest.toValue(),3)}"
    )
    results.write("\n\nDual values on assignment constraint\n")

    for idx, j_loop in enumerate(j.toList()):
        results.write(
            f"\nw('{j_loop}') = {w.records.value.round(3).tolist()[idx]};"
        )

    # one needs a value for zfeas
    # one can compute a valid upper bound as follows:

    zfeas.assignment = Sum(j, Smax(i, f[i, j]))
    results.write(
        "\n\nzfeas quick and dirty bound obj value      = "
        f" {round(zfeas.toValue(),3)}"
    )
    print("a priori upper bound", round(zfeas.toValue(), 3))

    ########################################################################
    # another alternative to compute a value for zfeas is
    # to solve gapmin by B-B and stop
    # at first 0-1 feasible solution found
    # using gapmin.optCr = 1, as follows

    # assign_mip.optCr = 1

    # assign_mip.solve()
    # zfeas.assignment = gams_math.Min(zfeas, z.l)
    # print('final zfeas', zfeas.toValue())
    # print('heuristic solution by B-B ', z.toValue(), "\n", x.pivot())
    # results.write(f"\nzfeas IP solution bound objective value    = {zfeas.toValue()}")
    #########################################################################

    results.write(
        "\n\n\n{:<15} {:<15} {:<20} {:<15} {:<15}\n".format(
            "Iteration", "New Bound", "Previous Bound", "norm", "abs(zl-zf)"
        )
    )

    # then keep the smaller of the two values as zfeas
    pknap.optCr = 0  # ask for global solution

    # ============================================================================ #
    #                                                                              #
    #   beginning of subgradient loop                                              #
    #                                                                              #
    # ============================================================================ #
    id[i] = False  # initially empty
    count.assignment = 1
    alpha.assignment = 1

    for iter_loop in iter.toList():
        if status.toValue() != 1:
            continue

        # solve Lagrangian subproblems by solving nonoverlapping knapsack
        # problems. Note the use of the dynamic set id[i] which will
        # contain the current knapsack descriptor.

        zlr.assignment = 0
        for ii_loop in ii.toList():
            id[ii_loop] = True  # assume id was empty
            pknap.solve()
            zlr.assignment = zlr + zlrx.l
            id[ii_loop] = False  # make set empty again

        improv.assignment = 0
        zl.assignment = zlr + Sum(j, w[j])
        improv.where[zl > zlbest] = 1  # is zl better than zlbest?
        zlbest.assignment = gams_math.Max(zlbest, zl)
        s[j] = 1 - Sum(i, x.l[i, j])  # subgradient
        norm.assignment = Sum(j, sqr(s[j]))

        if norm.toValue() < tol.toValue():
            status.assignment = 2

        if abs(zlbest.toValue() - zfeas.toValue()) < 1e-4:
            status.assignment = 3

        if pknap.status != ModelStatus.OptimalGlobal:
            status.assignment = 4

        row = [
            iter_loop,
            zl.toValue(),
            zlbest.toValue(),
            norm.toValue(),
            abs(zlbest.toValue() - zfeas.toValue()),
        ]
        results.write(
            "{:<15} {:<15.3f} {:<20.3f} {:<15.1f} {:<15.3f}\n".format(
                row[0], row[1], row[2], row[3], row[4]
            )
        )

        if status.toValue() == 2:
            results.write(
                "\n\nsubgr. method has converged, status ="
                f" {status.toValue()}\n\n"
            )
            results.write(
                "\n\nlast solution found is optimal for IP problem\n\n"
            )
        # end if
        if status.toValue() == 3:
            results.write(
                "\n\nsubgr. method has converged, status ="
                f" {status.toValue()}\n\n"
            )
            results.write("\n\nno duality gap, best sol. found is optimal\n\n")
        # end if
        if status.toValue() == 4:
            results.write("\n\nsomething wrong with last Lag. subproblem\n\n")
            results.write(f"\n\nstatus = {status.toValue()}\n\n")
        # end if

        report[iter_loop, "zlr"] = zlr
        report[iter_loop, "zl"] = zl
        report[iter_loop, "zlbest"] = zlbest
        report[iter_loop, "norm"] = norm
        report[iter_loop, "step"] = step

        wrep[iter_loop, j] = w[j]
        srep[iter_loop, j] = s[j]
        xrep[j, i, iter_loop] = x.l[i, j]

        if status.toValue() == 1:
            target.assignment = (zlbest + zfeas) / 2
            step.assignment = (alpha * (target - zl) / norm).where[norm > tol]
            w[j] = w[j] + step * s[j]
            if (
                count.toValue() > reset.toValue()
            ):  # too many iterations w/o improvement
                alpha.assignment = alpha / 2
                count.assignment = 1
            else:
                if improv.toValue():  # reset count if improvement
                    count.assignment = 1
                else:
                    count.assignment = (
                        count + 1
                    )  # update count if no improvement

        print(
            "iteration #: ",
            iter_loop,
            "\t\t Lagrangian bound: ",
            round(zl.toValue(), 3),
            "\t\t Best Lagrangian bound: ",
            round(zlbest.toValue(), 3),
        )

        # end loop iter

    import math

    assert math.isclose(z.records["level"][0], 175.3883, rel_tol=0.001)

    results.write("\n\nDual values on assignment constraint\n")
    for idx, j_loop in enumerate(j.toList()):
        results.write(
            f"\nw('{j_loop}') = {w.records.value.round(3).tolist()[idx]};"
        )

    results.write(
        f"\n\nbest Lagrangian bound   =   {round(zlbest.toValue(),3)}"
    )
    results.close()

    print("\n\nreport: \n", report.pivot().round(3))
    print("\n\nwrep: \n", wrep.pivot().round(3))
    print("\n\nsrep: \n", srep.pivot().round(3))
    print("\n\nxrep: \n", xrep.pivot().round(3))


if __name__ == "__main__":
    main()

tests/integration/models/gapmin.py
GAMS-dev/gamspy
"""
Design of a network of heat exchangers in parallel (with recirculation)
with two hot streams and one cold stream.

Floudas, C.A., Pardalos, P.M., et al. "Handbook of Test Problems in
Local and Global Optimization".
Kluwer Academic Publishers, Dordrecht, 1999.
Section 5.4.3. Test Problem 2, pages 52-54.
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # SCALARS #
    Tcin = Parameter(
        m,
        name="Tcin",
        records=150,
        description="inlet temperature of cold stream",
    )
    Tcout = Parameter(
        m,
        name="Tcout",
        records=310,
        description="outlet temperature of cold stream",
    )

    # VARIABLES #
    dT11 = Variable(
        m,
        name="dT11",
        description="temperature difference at hot end of exchanger H1-",
    )
    dT12 = Variable(
        m,
        name="dT12",
        description="temperature difference at cold end of exchanger H1-",
    )
    dT21 = Variable(
        m,
        name="dT21",
        description="temperature difference at hot end of exchanger H2-C",
    )
    dT22 = Variable(
        m,
        name="dT22",
        description="temperature difference at cold end of exchanger H2-C",
    )
    f11 = Variable(m, name="f11")
    f12 = Variable(m, name="f12")
    f13 = Variable(m, name="f13")
    f14 = Variable(m, name="f14")
    f21 = Variable(m, name="f21")
    f22 = Variable(m, name="f22")
    f23 = Variable(m, name="f23")
    f24 = Variable(m, name="f24")
    t1i = Variable(m, name="t1i")
    t2i = Variable(m, name="t2i")
    t1o = Variable(m, name="t1o")
    t2o = Variable(m, name="t2o")
    objval = Variable(
        m, name="objval", description="objective function variable"
    )

    # EQUATIONS #
    g1 = Equation(m, name="g1", type="regular")
    g2 = Equation(m, name="g2", type="regular")
    g3 = Equation(m, name="g3", type="regular")
    g4 = Equation(m, name="g4", type="regular")
    g5 = Equation(m, name="g5", type="regular")
    g6 = Equation(m, name="g6", type="regular")
    g7 = Equation(m, name="g7", type="regular")
    g8 = Equation(m, name="g8", type="regular")
    g9 = Equation(m, name="g9", type="regular")
    g10 = Equation(m, name="g10", type="regular")
    g11 = Equation(m, name="g11", type="regular")
    g12 = Equation(m, name="g12", type="regular")
    g13 = Equation(m, name="g13", type="regular")
    f = Equation(m, name="f", type="regular", description="objective function")

    # Objective function:
    f.definition = (
        objval
        == 1300
        * (1000 / ((1 / 30) * (dT11 * dT12) + (1 / 6) * (dT11 + dT12))) ** 0.6
        + 1300
        * (600 / ((1 / 30) * (dT21 * dT22) + (1 / 6) * (dT21 + dT22))) ** 0.6
    )

    # Constraints:
    g1.definition = f11 + f21 == 10
    g2.definition = f11 + f23 - f12 == 0
    g3.definition = f21 + f13 - f22 == 0
    g4.definition = f14 + f13 - f12 == 0
    g5.definition = f24 + f23 - f22 == 0
    g6.definition = Tcin * f11 + t2o * f23 - t1i * f12 == 0
    g7.definition = Tcin * f21 + t1o * f13 - t2i * f22 == 0
    g8.definition = f12 * (t1o - t1i) == 1000
    g9.definition = f22 * (t2o - t2i) == 600
    g10.definition = dT11 + t1o == 500
    g11.definition = dT12 + t1i == 250
    g12.definition = dT21 + t2o == 350
    g13.definition = dT22 + t2i == 200

    # Bounds on variables:
    dT11.lo.assignment = 10
    dT11.up.assignment = 350
    dT12.lo.assignment = 10
    dT12.up.assignment = 350
    dT21.lo.assignment = 10
    dT21.up.assignment = 200
    dT22.lo.assignment = 10
    dT22.up.assignment = 200
    f11.lo.assignment = 0
    f11.up.assignment = 10
    f12.lo.assignment = 0
    f12.up.assignment = 10
    f13.lo.assignment = 0
    f13.up.assignment = 10
    f14.lo.assignment = 0
    f14.up.assignment = 10
    f21.lo.assignment = 0
    f21.up.assignment = 10
    f22.lo.assignment = 0
    f22.up.assignment = 10
    f23.lo.assignment = 0
    f23.up.assignment = 10
    f24.lo.assignment = 0
    f24.up.assignment = 10
    t1i.lo.assignment = 150
    t1i.up.assignment = Tcout
    t1o.lo.assignment = 150
    t1o.up.assignment = Tcout
    t2i.lo.assignment = 150
    t2i.up.assignment = Tcout
    t2o.lo.assignment = 150
    t2o.up.assignment = Tcout

    # Initial point:
    dT11.l.assignment = 200
    dT12.l.assignment = 50
    dT21.l.assignment = 150
    dT22.l.assignment = 50
    f11.l.assignment = 10
    f12.l.assignment = 10
    f13.l.assignment = 10
    f14.l.assignment = 10
    f21.l.assignment = 10
    f22.l.assignment = 10
    f23.l.assignment = 10
    f24.l.assignment = 10
    t1i.l.assignment = 200
    t1o.l.assignment = 100
    t2i.l.assignment = 300
    t2o.l.assignment = 200

    HeatEx3 = Model(
        m,
        name="HeatEx3",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=objval,
    )

    HeatEx3.solve()

    import math

    assert math.isclose(HeatEx3.objective_value, 4845.4620, rel_tol=0.001)

    print("Objective Function Variable:  ", HeatEx3.objective_value)

    # End HeatEx3


if __name__ == "__main__":
    main()

tests/integration/models/heatex3.py
GAMS-dev/gamspy
"""
Human Heart Dipole
The human heart dipole problem consists of the experimental electrolytic
determination of the resultant dipole moment in the human heart.

The problem was formulated by:
Nelson, C.V. and Hodgkin, B.C., Determination of magnitudes, directions and
locations of two independent dipoles in a circular conducting region from
boundary potential measurements. IEEE Trans. Biomed. Eng., 28, 1981,
pp.817-823.

Please see:
Neculai Andrei, "Models, Test Problems and Applications for
Mathematical Programming". Technical Press, Bucharest, 2003.
Application U84, page 65. Application A13, page 360.
"""
import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Variant 1
    # # SCALARS #
    # summx = Parameter(m, name="summx", records= 0.485)
    # summy = Parameter(m, name="summy", records=-0.0019)
    # suma = Parameter(m, name="suma", records=-0.0581)
    # sumb = Parameter(m, name="sumb", records= 0.015)
    # sumc = Parameter(m, name="sumc", records= 0.105)
    # sumd = Parameter(m, name="sumd", records= 0.0406)
    # sume = Parameter(m, name="sume", records= 0.167)
    # sumf = Parameter(m, name="sumf", records=-0.399)

    # Variant 2
    # # SCALARS #
    # summx = Parameter(m, name="summx", records=-0.69)
    # summy = Parameter(m, name="summy", records=-0.044)
    # suma = Parameter(m, name="suma", records=-1.57)
    # sumb = Parameter(m, name="sumb", records=-1.31)
    # sumc = Parameter(m, name="sumc", records=-2.65)
    # sumd = Parameter(m, name="sumd", records= 2)
    # sume = Parameter(m, name="sume", records=-12.6)
    # sumf = Parameter(m, name="sumf", records= 9.48)

    # Variant 3
    # SCALARS #
    summx = Parameter(m, name="summx", records=-0.816)
    summy = Parameter(m, name="summy", records=-0.017)
    suma = Parameter(m, name="suma", records=-1.826)
    sumb = Parameter(m, name="sumb", records=-0.754)
    sumc = Parameter(m, name="sumc", records=-4.839)
    sumd = Parameter(m, name="sumd", records=-3.259)
    sume = Parameter(m, name="sume", records=-14.023)
    sumf = Parameter(m, name="sumf", records=15.467)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    x4 = Variable(m, name="x4")
    x5 = Variable(m, name="x5")
    x6 = Variable(m, name="x6")
    x7 = Variable(m, name="x7")
    x8 = Variable(m, name="x8")
    obj = Variable(m, name="obj")

    # EQUATIONS #
    e1 = Equation(m, name="e1", type="regular")
    e2 = Equation(m, name="e2", type="regular")
    e3 = Equation(m, name="e3", type="regular")
    e4 = Equation(m, name="e4", type="regular")
    e5 = Equation(m, name="e5", type="regular")
    e6 = Equation(m, name="e6", type="regular")
    e7 = Equation(m, name="e7", type="regular")
    e8 = Equation(m, name="e8", type="regular")
    e = Equation(m, name="e", type="regular")

    e1.definition = x1 + x2 - summx == 0
    e2.definition = x3 + x4 - summy == 0
    e3.definition = x5 * x1 + x6 * x2 - x7 * x3 - x8 * x4 - suma == 0
    e4.definition = x7 * x1 + x8 * x2 + x5 * x3 + x6 * x4 - sumb == 0
    e5.definition = (
        x1 * (gams_math.power(x5, 2) - gams_math.power(x7, 2))
        - 2 * x3 * x5 * x7
        + x2 * (gams_math.power(x6, 2) - gams_math.power(x8, 2))
        - 2 * x4 * x6 * x8
        - sumc
        == 0
    )
    e6.definition = (
        x3 * (gams_math.power(x5, 2) - gams_math.power(x7, 2))
        - 2 * x1 * x5 * x7
        + x4 * (gams_math.power(x6, 2) - gams_math.power(x8, 2))
        - 2 * x2 * x6 * x8
        - sumd
        == 0
    )
    e7.definition = (
        x1 * x5 * (gams_math.power(x5, 2) - 3 * gams_math.power(x7, 2))
        + x3 * x7 * (gams_math.power(x7, 2) - 3 * gams_math.power(x5, 2))
        + x2 * x6 * (gams_math.power(x6, 2) - 3 * gams_math.power(x8, 2))
        + x4 * x8 * (gams_math.power(x8, 2) - 3 * gams_math.power(x6, 2))
        - sume
        == 0
    )
    e8.definition = (
        x3 * x5 * (gams_math.power(x5, 2) - 3 * gams_math.power(x7, 2))
        + x1 * x7 * (gams_math.power(x7, 2) - 3 * gams_math.power(x5, 2))
        + x4 * x6 * (gams_math.power(x6, 2) - 3 * gams_math.power(x8, 2))
        + x2 * x8 * (gams_math.power(x8, 2) - 3 * gams_math.power(x6, 2))
        - sumf
        == 0
    )

    e.definition = obj == 1

    # Initial point (Variant 1)
    # x1.l.assignment = 0.299
    # x2.l.assignment = 0.186
    # x3.l.assignment = -0.0273
    # x4.l.assignment = 0.0254
    # x5.l.assignment = -0.474
    # x6.l.assignment = 0.474
    # x7.l.assignment = -0.0892
    # x8.l.assignment = 0.0892

    # Initial point (Variant 2)
    # x1.l.assignment = -0.3
    # x2.l.assignment = -0.39
    # x3.l.assignment =  0.3
    # x4.l.assignment = -0.344
    # x5.l.assignment = -1.2
    # x6.l.assignment =  2.69
    # x7.l.assignment =  1.59
    # x8.l.assignment = -1.5

    # Initial point (Variant 3)
    x1.l.assignment = -0.041
    x2.l.assignment = -0.775
    x3.l.assignment = 0.03
    x4.l.assignment = -0.047
    x5.l.assignment = -2.565
    x6.l.assignment = 2.565
    x7.l.assignment = -0.754
    x8.l.assignment = 0.754

    hhd = Model(
        m,
        name="hhd",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=obj,
    )
    hhd.solve()

    print("Objective Function Value:  ", round(obj.toValue(), 4))

    # End hhd


if __name__ == "__main__":
    main()

tests/integration/models/hhd.py
GAMS-dev/gamspy
"""
Inscribed Square Problem (INSCRIBEDSQUARE)

The inscribed square problem, also known as the square peg problem or
the Toeplitz' conjecture, is an unsolved question in geometry:

  Does every plane simple closed curve contain all four vertices of
  some square?


This is true if the curve is convex or piecewise smooth and in other
special cases. The problem was proposed by Otto Toeplitz in 1911.
See also https://en.wikipedia.org/wiki/Inscribed_square_problem

This model computes a square of maximal area for a given curve.

Use options --fx and --fy to specify the x and y coordinates of a closed
curve as function in variable t, where t ranges from -pi to pi.
Use option --gnuplot 1 to enable plotting the curve and computed square
with gnuplot (if available and a feasible solution has been found).

Contributor: Benjamin Mueller and Felipe Serrano
"""
import math

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Sense
from gamspy import Set
from gamspy import Variable
from gamspy.math import cos
from gamspy.math import sin


def fx(t):
    return sin(t) * cos(t - t * t)


def fy(t):
    return t * sin(t)


def main():
    m = Container(delayed_execution=True)

    # Set
    i = Set(m, name="i", records=["1", "2", "3", "4"])

    # Variable
    z = Variable(m, name="z")
    t = Variable(m, name="t", domain=[i])
    x = Variable(m, name="x")
    y = Variable(m, name="y")
    a = Variable(m, name="a", type="Positive")
    b = Variable(m, name="b", type="Positive")

    t.lo[i] = -math.pi
    t.up[i] = math.pi

    # Equation
    obj = Equation(m, name="obj")
    e1x = Equation(m, name="e1x")
    e1y = Equation(m, name="e1y")
    e2x = Equation(m, name="e2x")
    e2y = Equation(m, name="e2y")
    e3x = Equation(m, name="e3x")
    e3y = Equation(m, name="e3y")
    e4x = Equation(m, name="e4x")
    e4y = Equation(m, name="e4y")

    obj.definition = z == a**2 + b**2
    e1x.definition = fx(t["1"]) == x
    e1y.definition = fy(t["1"]) == y
    e2x.definition = fx(t["2"]) == x + a
    e2y.definition = fy(t["2"]) == y + b
    e3x.definition = fx(t["3"]) == x - b
    e3y.definition = fy(t["3"]) == y + a
    e4x.definition = fx(t["4"]) == x + a - b
    e4y.definition = fy(t["4"]) == y + a + b

    square = Model(
        m,
        name="square",
        equations=m.getEquations(),
        problem="DNLP",
        sense=Sense.MAX,
        objective=z,
    )

    t.l[i] = -math.pi + (Ord(i) - 1) * 2 * math.pi / Card(i)
    x.l.assignment = fx(t.l["1"])
    y.l.assignment = fy(t.l["1"])
    a.l.assignment = 1
    b.l.assignment = 1

    square.solve()

    assert math.isclose(square.objective_value, 1.6009, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/inscribedsquare.py
GAMS-dev/gamspy
"""
Inverse of a given matrix A of order (n,n).

Two methods are considered.
The first one computes the columns of the inverse x(i) by solving n algebraic
linear systems Ax(i)=e(i), where e(i) are the i-th column of the unity matrix,
i=1,...,n.
The second one determine the inverse by solving the linear system AX=I, where
X is the inverse of A.
"""
import numpy as np

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def data_records():
    # a records
    a_recs = np.array(
        [
            [1, 2, 3, 4, 4],
            [1, 3, 4, 3, 1],
            [1, 4, 1, 2, 6],
            [2, 4, 1, 1, 1],
            [3, 1, 5, 2, 7],
        ]
    )

    return a_recs


def main():
    m = Container(delayed_execution=True)

    # SET #
    i = Set(m, name="i", records=[f"i{i}" for i in range(1, 6)])

    # ALIASES #
    j = Alias(m, name="j", alias_with=i)
    k = Alias(m, name="k", alias_with=i)

    # PARAMETERS #
    a = Parameter(
        m,
        name="a",
        domain=[i, j],
        records=data_records(),
        description="matrix to be inverted",
    )
    ident = Parameter(
        m, name="ident", domain=[i, j], description="the identity matrix"
    )

    ident[i, i] = 1

    # Method 1. Solving the systems Ax(i)=e(i)
    # -----------------------------------------

    # VARIABLES #
    obj = Variable(
        m,
        name="obj",
        description="variabile associated to a virtual objective",
    )
    col = Variable(
        m,
        name="col",
        domain=[j],
        description="the columns of the inverse matrix",
    )

    # EQUATIONS #
    eobj = Equation(
        m,
        name="eobj",
        type="regular",
        description="name of the virtual objective",
    )
    lin = Equation(
        m,
        name="lin",
        type="regular",
        domain=[i],
        description="name of the equations of the systems to be solved",
    )

    # PARAMETERS #
    b = Parameter(m, name="b", domain=[i], description="Righ-hand Side term")
    ainv = Parameter(
        m, name="ainv", domain=[i, j], description="inverse matrix of A"
    )

    eobj.definition = obj == 0
    lin[i] = Sum(k, a[i, k] * col[k]) == b[i]

    invmat1 = Model(
        m,
        name="invmat1",
        equations=m.getEquations(),
        problem="lp",
        sense="min",
        objective=obj,
    )

    for jj in j.toList():
        b[i] = ident[i, jj]
        invmat1.solve()
        ainv[i, jj] = col.l[i]

    print("a:  \n", a.pivot().round(3))
    print("ainv:  \n", ainv.pivot().round(3))

    # Checking the inverse
    aainv = Parameter(
        m,
        name="aainv",
        domain=[i, k],
        description="matrix A multiplied by ainv",
    )
    aainv[i, k] = Sum(j, a[i, j] * ainv[j, k])
    print("aainv:  \n", aainv.pivot().round(3))

    # Method 2. Solving the system AX=I
    # ----------------------------------

    # VARIABLE #
    ainverse = Variable(
        m, name="ainverse", domain=[i, j], description="the inverse of A"
    )

    # EQUATION #
    lineq = Equation(m, name="lineq", type="regular", domain=[i, j])

    lineq[i, j] = Sum(k, a[i, k] * ainverse[k, j]) == ident[i, j]

    invmat2 = Model(
        m,
        name="invmat2",
        equations=m.getEquations(),
        problem="lp",
        sense="min",
        objective=obj,
    )
    invmat2.solve()

    print("ainverse:  \n", ainverse.pivot().round(3))

    # Checking the inverse
    aainv[i, k] = Sum(j, a[i, j] * ainv[j, k])
    print("aainv:  \n", aainv.pivot().round(3))

    # End of invmat


if __name__ == "__main__":
    main()

tests/integration/models/invmat.py
GAMS-dev/gamspy
"""
Updating and Projecting Coefficients: The RAS Approach (IOBALANCE)

The RAS procedure (named after Richard A. Stone) is an iterative procedure to
update matrices. This numerical example is taken from chapter 7.4.2 of Miller
and Blair. Several additional optimization formulations will be applied to
this toy problem.


Miller R E, and Blair P D, Input-Output Analysis: Foundations and Extensions,
Cambridge University Press, New York, 2009.

Keywords: linear programming, nonlinear programming, quadratic constraints,
statistics,
          RAS approach
"""
import numpy as np

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Sets

    i = Set(m, name="i", records=list(range(1, 4)))
    j = Alias(m, name="j", alias_with=i)

    # Parameters

    a0 = Parameter(
        m,
        name="a0",
        domain=[i, j],
        records=np.array(
            [
                [0.120, 0.100, 0.049],
                [0.210, 0.247, 0.265],
                [0.026, 0.249, 0.145],
            ]
        ),
    )

    z1 = Parameter(
        m,
        name="z1",
        domain=[i, j],
        records=np.array(
            [
                [98, 72, 75],
                [65, 8, 63],
                [88, 27, 44],
            ]
        ),
    )

    x = Parameter(m, name="x", domain=[j], records=np.array([421, 284, 283]))
    u = Parameter(
        m,
        name="u",
        domain=[i],
    )
    v = Parameter(
        m,
        name="v",
        domain=[j],
    )
    a1 = Parameter(m, name="ai", domain=[i, j])

    u[i] = Sum(j, z1[i, j])
    v[j] = Sum(i, z1[i, j])

    a1[i, j] = z1[i, j] / x[j]

    print("Values of u \n", u.records)
    print("Values of v \n", v.records)
    print("Values of a1 \n", a1.records)

    # --- 1: RAS updating

    r = Parameter(m, name="r", domain=[i])
    s = Parameter(m, name="s", domain=[j])

    r[i] = 1
    s[j] = 1

    oldr = Parameter(m, name="oldr", domain=[i])
    olds = Parameter(m, name="olds", domain=[j])
    maxdelta = Parameter(m, name="maxdelta")
    maxdelta.assignment = 1

    while True:
        oldr[i] = r[i]
        olds[j] = s[j]
        r[i] = r[i] * u[i] / Sum(j, r[i] * a0[i, j] * x[j] * s[j])
        s[j] = s[j] * v[j] / Sum(i, r[i] * a0[i, j] * x[j] * s[j])
        maxdelta.assignment = max(
            Smax(i, gams_math.abs(oldr[i] - r[i])),
            Smax(j, gams_math.abs(olds[j] - s[j])),
        )
        print("In loop maxdelta: ", round(maxdelta.records.values[0][0], 3))
        if maxdelta.records.values[0][0] < 0.005:
            break

    # Parameter
    report = Parameter(m, name="report", domain=["*", i, j])
    # option report:3:1:2

    report["A0", i, j] = a0[i, j]
    report["A1", i, j] = a1[i, j]
    report["RAS", i, j] = r[i] * a0[i, j] * s[j]

    # --- 2: Entropy formulation   a*ln(a/a0)
    #        The RAS procedure gives the solution to the Entropy formulation
    # Variable
    obj = Variable(m, name="obj", type="free")
    a = Variable(m, name="a", type="positive", domain=[i, j])

    # Equation
    rowbal = Equation(m, name="rowbal", domain=[i])
    colbal = Equation(m, name="colbal", domain=[j])
    defobjent = Equation(m, name="defobjent")

    rowbal[i] = Sum(j, a[i, j] * x[j]) == u[i]

    colbal[j] = Sum(i, a[i, j] * x[j]) == v[j]

    defobjent.definition = obj == Sum(
        [i, j], x[j] * a[i, j] * gams_math.log(a[i, j] / a0[i, j])
    )

    mEntropy = Model(
        m,
        name="mEntropy",
        equations=[rowbal, colbal, defobjent],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )

    # we need to exclude small values to avoid domain violations
    a.lo[i, j] = 1e-5

    mEntropy.solve()
    report["Entropy", i, j] = a.l[i, j]

    # --- 3: Entropy with flow variable
    #        we can balance the flow matrix instead of the A matrix
    zv = Variable(m, name="zv", type="free", domain=[i, j])

    rowbalz = Equation(m, name="rowbalz", domain=[i])
    colbalz = Equation(m, name="colbalz", domain=[j])
    defobjentz = Equation(m, name="defobjentz")

    rowbalz[i] = Sum(j, zv[i, j]) == u[i]

    colbalz[j] = Sum(i, zv[i, j]) == v[j]

    zbar = Parameter(m, name="zbar", domain=[i, j])

    zbar[i, j] = a0[i, j] * x[j]
    zv.lo[i, j] = 1

    defobjentz.definition = obj == Sum(
        [i, j], zv[i, j] * gams_math.log(zv[i, j] / zbar[i, j])
    )

    mEntropyz = Model(
        m,
        name="mEntropyz",
        equations=[rowbalz, colbalz, defobjentz],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )

    # turn off detailed outputs
    m.addOptions({"limRow": 0, "limCol": 0, "solPrint": "off"})

    mEntropyz.solve()
    report["EntropyZ", i, j] = zv.l[i, j] / x[j]

    # --- 4. absolute deviation formulations result in LPs
    #        MAD Mean Absolute Deviations
    #        MAPE Mean absolute percentage error
    #        Linf Infinity norm
    # Positive Variable
    ap = Variable(m, name="ap", type="positive", domain=[i, j])
    an = Variable(m, name="an", type="positive", domain=[i, j])
    amax = Variable(m, name="amax", type="positive")

    # Equation
    defabs = Equation(m, name="defabs", domain=[i, j])
    defmaxp = Equation(m, name="defmaxp", domain=[i, j])
    defmaxn = Equation(m, name="defmaxn", domain=[i, j])
    defmad = Equation(m, name="defmad")
    defmade = Equation(m, name="defmade")
    deflinf = Equation(m, name="deflinf")

    defabs[i, j] = a[i, j] - a0[i, j] == ap[i, j] - an[i, j]

    defmaxp[i, j] = a[i, j] - a0[i, j] <= amax

    defmaxn[i, j] = a[i, j] - a0[i, j] >= -amax

    defmad.definition = obj == 1 / gams_math.power(Card(i), 2) * Sum(
        [i, j], ap[i, j] + an[i, j]
    )

    defmade.definition = obj == 100 / gams_math.power(Card(i), 2) * Sum(
        [i, j], (ap[i, j] + an[i, j]) / a0[i, j]
    )

    deflinf.definition = obj == amax

    # Model
    mMAD = Model(
        m,
        name="mMAD",
        equations=[rowbal, colbal, defabs, defmad],
        problem="LP",
        sense=Sense.MIN,
        objective=obj,
    )
    mMADE = Model(
        m,
        name="mMADE",
        equations=[rowbal, colbal, defabs, defmade],
        problem="LP",
        sense=Sense.MIN,
        objective=obj,
    )
    mLinf = Model(
        m,
        name="mLinf",
        equations=[rowbal, colbal, defmaxp, defmaxn, deflinf],
        problem="LP",
        sense=Sense.MIN,
        objective=obj,
    )

    mMAD.solve()
    report["MAD", i, j] = a.l[i, j]
    mMADE.solve()
    report["MADE", i, j] = a.l[i, j]
    mLinf.solve()
    report["Linf", i, j] = a.l[i, j]

    # --- 5. Squared Deviations can be solved with powerful QP codes
    #        SD     squared deviations
    #        RSD    relative squared deviations
    # Equation
    defsd = Equation(m, name="defsd")
    defrsd = Equation(m, name="defrsd")

    defsd.definition = obj == Sum(
        [i, j], gams_math.power(a[i, j] + a0[i, j], 2)
    )

    defrsd.definition = obj == Sum(
        [i, j], gams_math.power(a[i, j] + a0[i, j], 2) / a0[i, j]
    )

    # Model
    mSD = Model(
        m,
        name="mSD",
        equations=[rowbal, colbal, defsd],
        problem=Problem.QCP,
        sense=Sense.MIN,
        objective=obj,
    )
    mRSD = Model(
        m,
        name="mRSD",
        equations=[rowbal, colbal, defrsd],
        problem=Problem.QCP,
        sense=Sense.MIN,
        objective=obj,
    )

    mSD.solve()
    report["SD", i, j] = a.l[i, j]
    mRSD.solve()
    report["RSD", i, j] = a.l[i, j]

    import math

    assert math.isclose(mRSD.objective_value, 7.6203, rel_tol=0.001)

    print()
    print("\t SOLUTION REPORT: \n", report.pivot())


if __name__ == "__main__":
    main()

tests/integration/models/iobalance.py
GAMS-dev/gamspy
"""
General Equilibrium Model for Korea (KORCNS)

This mini equilibrium model of Korea for the year 1963 is used to
illustrate the basic use of CGE models. This version follows closely
Chapter 11 of the reference.

The original model (KORCGE) is formulated as an optimization
model, but it is really a square system of nonlinear equations.
In this version, we formulate the model directly as a square system
using the model type CNS = Constrained Nonlinear System.

An MCP version exist under the name (KORMCP).


Lewis, J, and Robinson, S, Chapter 11. In Chenery, H B, Robinson, S,
and Syrquin, S, Eds, Industrialization and Growth: A Comparative
Study. Oxford University Press, London, 1986.

Keywords: constrained nonlinear system, general equilibrium model, economic
growth,
          industrialization, economic policy, Korean economy
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Product
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    # Data

    data = [
        [0.00000, 0.00000, 0.00000],
        [0.01000, 0.03920, 0.05000],
        [0.02000, 0.07000, 0.91000],
        [0.13000, 0.29000, 0.58000],
        [0.00000, 0.00000, 0.00000],
        [0.00000, 0.00000, 0.00000],
        [0.10000, 0.22751, 0.08084],
        [0.61447, 1.60111, 0.52019],
        [0.33263, 0.43486, 0.23251],
        [0.90909, 0.81466, 0.92521],
        [1.00000, 1.00000, 1.00000],
        [2.00000, 0.66000, 0.40000],
        [0.24820, 0.05111, 0.00001],
        [1.59539, 1.34652, 1.01839],
        [2.00000, 2.00000, 2.00000],
        [0.86628, 0.84602, 0.82436],
        [3.85424, 3.51886, 3.23592],
    ]

    columns = ["agricult", "industry", "services"]
    indexes = [
        "depr",
        "itax",
        "gles",
        "kio",
        "dstr",
        "te",
        "tm",
        "ad",
        "pwts",
        "pwm",
        "pwe",
        "sigc",
        "delta",
        "ac",
        "sigt",
        "gamma",
        "at",
    ]
    zz_df = pd.DataFrame(data=data, columns=columns, index=indexes)
    zz_df = zz_df.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )

    # sectres
    data = [
        [1.0000, 1.0000, 1.0000],
        [1.0000, 1.0000, 1.0000],
        [0.7370, 0.2911, 0.6625],
        [711.6443, 930.3509, 497.4428],
        [657.3677, 840.0500, 515.4296],
        [641.7037, 812.2222, 492.0307],
        [15.6639, 27.8278, 23.3988],
        [69.9406, 118.1287, 5.4120],
        [657.5754, 338.7076, 1548.5192],
        [256.6450, 464.1656, 156.2598],
        [452.1765, 307.8561, 202.0416],
        [2.8230, 9.8806, 128.4482],
        [0.0000, 148.4488, 10.6931],
        [0.0000, 0.0000, 0.0000],
        [20.6884, 46.1511, 92.3023],
        [1.0000, 1.0000, 1.0000],
        [1.0000, 1.0000, 1.0000],
        [1.0000, 1.0000, 1.0000],
        [1.0000, 1.0000, 1.0000],
    ]
    columns = ["agricult", "industry", "services"]
    indexes = [
        "pd1",
        "pk",
        "pva",
        "x",
        "xd",
        "xxd",
        "e",
        "m",
        "k",
        "intr",
        "cd",
        "gd",
        "id",
        "dst",
        "dk",
        "pm",
        "pe",
        "px",
        "p",
    ]
    sectres_df = pd.DataFrame(data=data, columns=columns, index=indexes)
    sectres_df = sectres_df.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )

    # Model's container
    cont = Container(delayed_execution=True)

    # Sets
    i = Set(cont, name="i", records=["agricult", "industry", "services"])
    hh = Set(cont, name="hh", records=["lab_hh", "cap_hh"])
    lc = Set(cont, name="lc", records=["labor1", "labor2", "labor3"])
    it = Set(cont, name="it", domain=[i])
    inn = Set(cont, name="inn", domain=[i])

    j = Alias(cont, name="j", alias_with=i)

    # Parameters
    delta = Parameter(cont, name="delta", domain=[i])
    ac = Parameter(cont, name="ac", domain=[i])
    rhoc = Parameter(cont, name="rhoc", domain=[i])
    rhot = Parameter(cont, name="rhot", domain=[i])
    at = Parameter(cont, name="at", domain=[i])
    gamma = Parameter(cont, name="gamma", domain=[i])
    ad = Parameter(cont, name="ad", domain=[i])
    gles = Parameter(cont, name="gles", domain=[i])
    depr = Parameter(cont, name="depr", domain=[i])
    dstr = Parameter(cont, name="dstr", domain=[i])
    kio = Parameter(cont, name="kio", domain=[i])
    te = Parameter(cont, name="te", domain=[i])
    itax = Parameter(cont, name="itax", domain=[i])
    htax = Parameter(cont, name="htax", domain=[hh])
    pwm = Parameter(cont, name="pwm", domain=[i])
    pwe = Parameter(cont, name="pwe", domain=[i])
    tm = Parameter(cont, name="tm", domain=[i])
    pwts = Parameter(cont, name="pwts", domain=[i])

    htax["lab_hh"] = 0.08910
    htax["cap_hh"] = 0.08910

    alphl = Parameter(
        cont,
        name="alphl",
        domain=[i, lc],
        records=np.array(
            [
                [0.38258, 0.06740, 0.00000],
                [0.00000, 0.53476, 0.00000],
                [0.00000, 0.16234, 0.42326],
            ]
        ),
    )

    io = Parameter(
        cont,
        name="io",
        domain=[i, j],
        records=np.array(
            [
                [0.12591, 0.19834, 0.01407],
                [0.10353, 0.35524, 0.18954],
                [0.02358, 0.11608, 0.08390],
            ]
        ),
    )

    imat = Parameter(
        cont,
        name="imat",
        domain=[i, j],
        records=np.array(
            [
                [0.00000, 0.00000, 0.00000],
                [0.93076, 0.93774, 0.93080],
                [0.06924, 0.06226, 0.06920],
            ]
        ),
    )

    wdist = Parameter(
        cont,
        name="wdist",
        domain=[i, lc],
        records=np.array(
            [
                [1.00000, 0.52780, 0.00000],
                [0.00000, 1.21879, 0.00000],
                [0.00000, 1.11541, 1.00000],
            ]
        ),
    )

    cles = Parameter(
        cont,
        name="cles",
        domain=[i, hh],
        records=np.array(
            [[0.47000, 0.47000], [0.31999, 0.31999], [0.21001, 0.21001]]
        ),
    )

    zz = Parameter(cont, name="zz", domain=["*", i], records=zz_df)

    depr[i] = zz["depr", i]
    itax[i] = zz["itax", i]
    gles[i] = zz["gles", i]
    kio[i] = zz["kio", i]
    dstr[i] = zz["dstr", i]
    te[i] = zz["te", i]
    tm[i] = zz["tm", i]
    ad[i] = zz["ad", i]
    pwts[i] = zz["pwts", i]
    pwm[i] = zz["pwm", i]
    pwe[i] = zz["pwe", i]
    rhoc[i] = (1 / zz["sigc", i]) - 1
    delta[i] = zz["delta", i]
    ac[i] = zz["ac", i]
    rhot[i] = (1 / zz["sigt", i]) + 1
    gamma[i] = zz["gamma", i]
    at[i] = zz["at", i]

    # Model Definition
    # Variables
    # prices block
    er = Variable(cont, name="er", type="free")
    pd1 = Variable(cont, name="pd1", type="free", domain=[i])
    pm = Variable(cont, name="pm", type="free", domain=[i])
    pe = Variable(cont, name="pe", type="free", domain=[i])
    pk = Variable(cont, name="pk", type="free", domain=[i])
    px = Variable(cont, name="px", type="free", domain=[i])
    p = Variable(cont, name="p", type="free", domain=[i])
    pva = Variable(cont, name="pva", type="free", domain=[i])
    pr = Variable(cont, name="pr", type="free")
    pindex = Variable(cont, name="pindex", type="free")

    # production block
    x = Variable(cont, name="x", type="free", domain=[i])
    xd = Variable(cont, name="xd", type="free", domain=[i])
    xxd = Variable(cont, name="xxd", type="free", domain=[i])
    e = Variable(cont, name="e", type="free", domain=[i])
    m = Variable(cont, name="m", type="free", domain=[i])

    # factors block
    k = Variable(cont, name="k", type="free", domain=[i])
    wa = Variable(cont, name="wa", type="free", domain=[lc])
    ls = Variable(cont, name="ls", type="free", domain=[lc])
    l = Variable(cont, name="l", type="free", domain=[i, lc])

    # demand block
    intr = Variable(cont, name="intr", type="free", domain=[i])
    cd = Variable(cont, name="cd", type="free", domain=[i])
    gd = Variable(cont, name="gd", type="free", domain=[i])
    id = Variable(cont, name="id", type="free", domain=[i])
    dst = Variable(cont, name="dst", type="free", domain=[i])
    y = Variable(cont, name="y", type="free")
    gr = Variable(cont, name="gr", type="free")
    tariff = Variable(cont, name="tariff", type="free")
    indtax = Variable(cont, name="indtax", type="free")
    netsub = Variable(cont, name="netsub", type="free")
    gdtot = Variable(cont, name="gdtot", type="free")
    hhsav = Variable(cont, name="hhsav", type="free")
    govsav = Variable(cont, name="govsav", type="free")
    deprecia = Variable(cont, name="deprecia", type="free")
    invest = Variable(cont, name="invest", type="free")
    savings = Variable(cont, name="savings", type="free")
    mps = Variable(cont, name="mps", type="free", domain=[hh])
    fsav = Variable(cont, name="fsav", type="free")
    dk = Variable(cont, name="dk", type="free", domain=[i])
    ypr = Variable(cont, name="ypr", type="free")
    remit = Variable(cont, name="remit", type="free")
    fbor = Variable(cont, name="fbor", type="free")
    yh = Variable(cont, name="yh", type="free", domain=[hh])
    tothhtax = Variable(cont, name="tothhtax", type="free")

    # welfare indicator for objective function
    omega = Variable(cont, name="omega", type="free")

    er.l.assignment = 1.0000
    pr.l.assignment = 0.0000
    pindex.l.assignment = 1.0000
    gr.l.assignment = 194.0449
    tariff.l.assignment = 28.6572
    indtax.l.assignment = 65.2754
    netsub.l.assignment = 0.0000
    gdtot.l.assignment = 141.1519
    hhsav.l.assignment = 61.4089
    govsav.l.assignment = 52.8930
    deprecia.l.assignment = 0.0000
    savings.l.assignment = 159.1419
    invest.l.assignment = 159.1419
    fsav.l.assignment = 39.1744
    fbor.l.assignment = 58.7590
    remit.l.assignment = 0.0000
    tothhtax.l.assignment = 100.1122
    y.l.assignment = 1123.5941

    labres1 = Parameter(
        cont,
        name="labres1",
        domain=[i, lc],
        records=np.array(
            [
                [2515.900, 442.643, 0.000],
                [0.000, 767.776, 0.000],
                [0.000, 355.568, 948.100],
            ]
        ),
    )

    labres2 = Parameter(
        cont,
        name="labres2",
        domain=["*", lc],
        records=pd.DataFrame(
            [
                ["wa", "labor1", 0.074],
                ["ls", "labor1", 2515.9],
                ["wa", "labor2", 0.14],
                ["ls", "labor2", 1565.987],
                ["wa", "labor3", 0.152],
                ["ls", "labor3", 948.1],
            ]
        ),
    )

    hhres = Parameter(
        cont,
        name="hhres",
        domain=["*", hh],
        records=pd.DataFrame(
            [
                ["yh", "lab_hh", 548.7478],
                ["mps", "lab_hh", 0.06],
                ["yh", "cap_hh", 574.8463],
                ["mps", "cap_hh", 0.06],
            ]
        ),
    )

    l.l[i, lc] = labres1[i, lc]
    ls.l[lc] = labres2["ls", lc]
    wa.l[lc] = labres2["wa", lc]
    mps.l[hh] = hhres["mps", hh]
    yh.l[hh] = hhres["yh", hh]

    sectres = Parameter(
        cont, name="sectres", domain=["*", i], records=sectres_df
    )

    pd1.l[i] = sectres["pd1", i]
    pm.l[i] = sectres["pm", i]
    pe.l[i] = sectres["pe", i]
    pk.l[i] = sectres["pk", i]
    px.l[i] = sectres["px", i]
    p.l[i] = sectres["p", i]
    pva.l[i] = sectres["pva", i]
    x.l[i] = sectres["x", i]
    xd.l[i] = sectres["xd", i]
    xxd.l[i] = sectres["xxd", i]
    e.l[i] = sectres["e", i]
    m.l[i] = sectres["m", i]
    k.l[i] = sectres["k", i]
    intr.l[i] = sectres["intr", i]
    cd.l[i] = sectres["cd", i]
    gd.l[i] = sectres["gd", i]
    id.l[i] = sectres["id", i]
    dst.l[i] = sectres["dst", i]
    dk.l[i] = sectres["dk", i]
    it[i] = Number(1).where[e.l[i] | m.l[i]]
    inn[i] = not it[i]
    k.fx[i] = k.l[i]
    m.fx[inn] = 0
    e.fx[inn] = 0
    l.fx[i, lc].where[l.l[i, lc] == 0] = 0

    p.lo[i] = 0.01
    pd1.lo[i] = 0.01
    pm.lo[it] = 0.01
    pk.lo[i] = 0.01
    px.lo[i] = 0.01
    x.lo[i] = 0.01
    xd.lo[i] = 0.01
    m.lo[it] = 0.01
    xxd.lo[it] = 0.01
    wa.lo[lc] = 0.01
    intr.lo[i] = 0.01
    y.lo.assignment = 0.01
    e.lo[it] = 0.01
    l.lo[i, lc].where[l.l[i, lc] != 0] = 0.01

    # Equation Definitions
    # price block
    pmdef = Equation(cont, name="pmdef", domain=[i])
    pedef = Equation(cont, name="pedef", domain=[i])
    absorption = Equation(cont, name="absorption", domain=[i])
    sales = Equation(cont, name="sales", domain=[i])
    actp = Equation(cont, name="actp", domain=[i])
    pkdef = Equation(cont, name="pkdef", domain=[i])
    pindexdef = Equation(cont, name="pindexdef")

    # output block
    activity = Equation(cont, name="activity", domain=[i])
    profitmax = Equation(cont, name="profitmax", domain=[i, lc])
    lmequil = Equation(cont, name="lmequil", domain=[lc])
    cet = Equation(cont, name="cet", domain=[i])
    esupply = Equation(cont, name="esupply", domain=[i])
    armington = Equation(cont, name="armington", domain=[i])
    costmin = Equation(cont, name="costmin", domain=[i])
    xxdsn = Equation(cont, name="xxdsn", domain=[i])
    xsn = Equation(cont, name="xsn", domain=[i])

    # demand block
    inteq = Equation(cont, name="inteq", domain=[i])
    cdeq = Equation(cont, name="cdeq", domain=[i])
    dsteq = Equation(cont, name="dsteq", domain=[i])
    gdp = Equation(cont, name="gdp")
    labory = Equation(cont, name="labory")
    capitaly = Equation(cont, name="capitaly")
    hhtaxdef = Equation(cont, name="hhtaxdef")
    gdeq = Equation(cont, name="gdeq", domain=[i])
    greq = Equation(cont, name="greq")
    tariffdef = Equation(cont, name="tariffdef")
    premium = Equation(cont, name="premium")
    indtaxdef = Equation(cont, name="indtaxdef")
    netsubdef = Equation(cont, name="netsubdef")

    # savings-investment block
    hhsaveq = Equation(cont, name="hhsaveq")
    gruse = Equation(cont, name="gruse")
    depreq = Equation(cont, name="depreq")
    totsav = Equation(cont, name="totsav")
    prodinv = Equation(cont, name="prodinv", domain=[i])
    ieq = Equation(cont, name="ieq", domain=[i])

    # balance of payments
    caeq = Equation(cont, name="caeq")

    # market clearing
    equil = Equation(cont, name="equil", domain=[i])

    # objective function
    obj = Equation(cont, name="obj")

    # price block
    pmdef[it] = pm[it] == pwm[it] * er * (1 + tm[it] + pr)

    pedef[it] = pe[it] == pwe[it] * (1 + te[it]) * er

    absorption[i] = (
        p[i] * x[i] == pd1[i] * xxd[i] + (pm[i] * m[i]).where[it[i]]
    )

    sales[i] = px[i] * xd[i] == pd1[i] * xxd[i] + (pe[i] * e[i]).where[it[i]]

    actp[i] = px[i] * (1 - itax[i]) == pva[i] + Sum(j, io[j, i] * p[j])

    pkdef[i] = pk[i] == Sum(j, p[j] * imat[j, i])

    pindexdef.definition = pindex == Sum(i, pwts[i] * p[i])

    # output and factors of production block
    activity[i] = xd[i] == ad[i] * Product(
        lc.where[wdist[i, lc]], l[i, lc] ** alphl[i, lc]
    ) * k[i] ** (1 - Sum(lc, alphl[i, lc]))

    profitmax[i, lc].where[wdist[i, lc]] = (
        wa[lc] * wdist[i, lc] * l[i, lc] == xd[i] * pva[i] * alphl[i, lc]
    )

    lmequil[lc] = Sum(i, l[i, lc]) == ls[lc]

    cet[it] = xd[it] == at[it] * (
        gamma[it] * e[it] ** rhot[it] + (1 - gamma[it]) * xxd[it] ** rhot[it]
    ) ** (1 / rhot[it])

    esupply[it] = e[it] / xxd[it] == (
        pe[it] / pd1[it] * (1 - gamma[it]) / gamma[it]
    ) ** (1 / (rhot[it] - 1))

    armington[it] = x[it] == ac[it] * (
        delta[it] * m[it] ** (rhoc[it] * (-1))
        + (1 - delta[it]) * xxd[it] ** (rhoc[it] * (-1))
    ) ** (-1 / rhoc[it])

    costmin[it] = m[it] / xxd[it] == (
        pd1[it] / pm[it] * delta[it] / (1 - delta[it])
    ) ** (1 / (1 + rhoc[it]))

    xxdsn[inn] = xxd[inn] == xd[inn]

    xsn[inn] = x[inn] == xxd[inn]

    # demand block
    inteq[i] = intr[i] == Sum(j, io[i, j] * xd[j])

    dsteq[i] = dst[i] == dstr[i] * xd[i]

    cdeq[i] = p[i] * cd[i] == Sum(
        hh, cles[i, hh] * (1 - mps[hh]) * yh[hh] * (1 - htax[hh])
    )

    gdp.definition = y == Sum(hh, yh[hh])

    labory.definition = yh["lab_hh"] == Sum(lc, wa[lc] * ls[lc]) + remit * er

    capitaly.definition = (
        yh["cap_hh"]
        == Sum(i, pva[i] * xd[i])
        - deprecia
        - Sum(lc, wa[lc] * ls[lc])
        + fbor * er
        + ypr
    )

    hhsaveq.definition = hhsav == Sum(hh, mps[hh] * yh[hh] * (1 - htax[hh]))

    greq.definition = gr == tariff - netsub + indtax + tothhtax

    gruse.definition = gr == Sum(i, p[i] * gd[i]) + govsav

    gdeq[i] = gd[i] == gles[i] * gdtot

    tariffdef.definition = tariff == Sum(it, tm[it] * m[it] * pwm[it]) * er

    indtaxdef.definition = indtax == Sum(i, itax[i] * px[i] * xd[i])

    netsubdef.definition = netsub == Sum(it, te[it] * e[it] * pwe[it]) * er

    premium.definition = ypr == Sum(it, pwm[it] * m[it]) * er * pr

    hhtaxdef.definition = tothhtax == Sum(hh, htax[hh] * yh[hh])

    depreq.definition = deprecia == Sum(i, depr[i] * pk[i] * k[i])

    totsav.definition = savings == hhsav + govsav + deprecia + fsav * er

    prodinv[i] = pk[i] * dk[i] == kio[i] * invest - kio[i] * Sum(
        j, dst[j] * p[j]
    )

    ieq[i] = id[i] == Sum(j, imat[i, j] * dk[j])

    # balance of payments
    caeq.definition = (
        Sum(it, pwm[it] * m[it])
        == Sum(it, pwe[it] * e[it]) + fsav + remit + fbor
    )
    # market clearing
    equil[i] = x[i] == intr[i] + cd[i] + gd[i] + id[i] + dst[i]

    # objective function
    obj.definition = omega == Product(
        i.where[cles[i, "lab_hh"]], cd[i] ** cles[i, "lab_hh"]
    )

    er.fx.assignment = er.l
    fsav.fx.assignment = fsav.l
    remit.fx.assignment = remit.l
    fbor.fx.assignment = fbor.l
    pindex.fx.assignment = pindex.l
    mps.fx[hh] = mps.l[hh]
    gdtot.fx.assignment = gdtot.l
    ls.fx[lc] = ls.l[lc]

    model1 = Model(
        cont, name="model1", equations=cont.getEquations(), problem="cns"
    )

    model1.solve()
    import math

    assert math.isclose(
        omega.records["level"].tolist()[0], 339.21, rel_tol=0.001
    )

    print(
        "\nObjective Function Variable <omega>: ",
        round(omega.records.level.tolist()[0], 2),
    )
    print("\nDomestic prices:\n", pd1.records.set_index("i").level)


if __name__ == "__main__":
    main()

tests/integration/models/korcns.py
GAMS-dev/gamspy
"""
Linear Regression with Various Criteria (LINEAR)

This example solves linear models with differing objective functions.
Absolute deviations cannot be solved in a reliable manner with
most NLP systems and one has to resort to a formulation with
negative and positive deviations (models ending with the letter a).


Bracken, J, and McCormick, G P, Chapter 8.2. In Selected Applications of
Nonlinear Programming. John Wiley and Sons, New York, 1968, pp. 86-88.

Keywords: linear programming, nonlinear programming, discontinuous derivatives,
          linear regression, econometrics
"""
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Data
    dat_df = pd.DataFrame(
        [
            ["1", "y", 99],
            ["1", "a", 1],
            ["1", "b", 85],
            ["1", "c", 76],
            ["1", "d", 44],
            ["2", "y", 93],
            ["2", "a", 1],
            ["2", "b", 82],
            ["2", "c", 78],
            ["2", "d", 42],
            ["3", "y", 99],
            ["3", "a", 1],
            ["3", "b", 75],
            ["3", "c", 73],
            ["3", "d", 42],
            ["4", "y", 97],
            ["4", "a", 1],
            ["4", "b", 74],
            ["4", "c", 72],
            ["4", "d", 44],
            ["5", "y", 90],
            ["5", "a", 1],
            ["5", "b", 76],
            ["5", "c", 73],
            ["5", "d", 43],
            ["6", "y", 96],
            ["6", "a", 1],
            ["6", "b", 74],
            ["6", "c", 69],
            ["6", "d", 46],
            ["7", "y", 93],
            ["7", "a", 1],
            ["7", "b", 73],
            ["7", "c", 69],
            ["7", "d", 46],
            ["8", "y", 130],
            ["8", "a", 1],
            ["8", "b", 96],
            ["8", "c", 80],
            ["8", "d", 36],
            ["9", "y", 118],
            ["9", "a", 1],
            ["9", "b", 93],
            ["9", "c", 78],
            ["9", "d", 36],
            ["10", "y", 88],
            ["10", "a", 1],
            ["10", "b", 70],
            ["10", "c", 73],
            ["10", "d", 37],
            ["11", "y", 89],
            ["11", "a", 1],
            ["11", "b", 82],
            ["11", "c", 71],
            ["11", "d", 46],
            ["12", "y", 93],
            ["12", "a", 1],
            ["12", "b", 80],
            ["12", "c", 72],
            ["12", "d", 45],
            ["13", "y", 94],
            ["13", "a", 1],
            ["13", "b", 77],
            ["13", "c", 76],
            ["13", "d", 42],
            ["14", "y", 75],
            ["14", "a", 1],
            ["14", "b", 67],
            ["14", "c", 76],
            ["14", "d", 50],
            ["15", "y", 84],
            ["15", "a", 1],
            ["15", "b", 82],
            ["15", "c", 70],
            ["15", "d", 48],
            ["16", "y", 91],
            ["16", "a", 1],
            ["16", "b", 76],
            ["16", "c", 76],
            ["16", "d", 41],
            ["17", "y", 100],
            ["17", "a", 1],
            ["17", "b", 74],
            ["17", "c", 78],
            ["17", "d", 31],
            ["18", "y", 98],
            ["18", "a", 1],
            ["18", "b", 71],
            ["18", "c", 80],
            ["18", "d", 29],
            ["19", "y", 101],
            ["19", "a", 1],
            ["19", "b", 70],
            ["19", "c", 83],
            ["19", "d", 39],
            ["20", "y", 80],
            ["20", "a", 1],
            ["20", "b", 64],
            ["20", "c", 79],
            ["20", "d", 38],
        ]
    )

    # Sets
    i = Set(m, name="i", records=list(range(1, 21)))
    n = Set(m, name="n", records=["a", "b", "c", "d"])

    # Parameters
    dat = Parameter(m, name="dat", domain=[i, "*"], records=dat_df)

    # Variables
    obj = Variable(m, name="obj", type="free")
    dev = Variable(m, name="dev", type="free", domain=[i])
    devp = Variable(m, name="devp", type="positive", domain=[i])
    devn = Variable(m, name="devn", type="positive", domain=[i])
    b = Variable(m, name="b", type="free", domain=[n])

    # Equations
    ddev = Equation(m, name="ddev", domain=[i])
    ddeva = Equation(m, name="ddeva", domain=[i])
    ls1 = Equation(m, name="ls1")
    ls1a = Equation(m, name="ls1a")
    ls2 = Equation(m, name="ls2")
    ls3 = Equation(m, name="ls3")
    ls4 = Equation(m, name="ls4")
    ls5 = Equation(m, name="ls5")
    ls5a = Equation(m, name="ls5a")
    ls6 = Equation(m, name="ls6")
    ls7 = Equation(m, name="ls7")
    ls8 = Equation(m, name="ls8")

    ddev[i] = dev[i] == dat[i, "y"] - Sum(n, b[n] * dat[i, n])

    ddeva[i] = devp[i] - devn[i] == dat[i, "y"] - Sum(n, b[n] * dat[i, n])

    ls1.definition = obj == Sum(i, gams_math.abs(dev[i]))

    ls1a.definition = obj == Sum(i, devp[i] + devn[i])

    ls2.definition = obj == Sum(i, gams_math.power(dev[i], 2))

    ls3.definition = obj == Sum(i, gams_math.power(gams_math.abs(dev[i]), 3))

    ls4.definition = obj == Sum(i, gams_math.power(dev[i], 4))

    ls5.definition = obj == Sum(i, gams_math.abs(dev[i] / dat[i, "y"]))

    ls5a.definition = obj == Sum(i, (devp[i] + devn[i]) / dat[i, "y"])

    ls6.definition = obj == Sum(i, gams_math.power(dev[i] / dat[i, "y"], 2))

    ls7.definition = obj == Sum(
        i, gams_math.power(gams_math.abs(dev[i] / dat[i, "y"]), 3)
    )

    ls8.definition = obj == Sum(i, gams_math.power(dev[i] / dat[i, "y"], 4))

    # Models
    mod1 = Model(
        m,
        name="mod1",
        equations=[ddev, ls1],
        problem="dnlp",
        sense=Sense.MIN,
        objective=obj,
    )
    mod1a = Model(
        m,
        name="mod1a",
        equations=[ddeva, ls1a],
        problem="lp",
        sense=Sense.MIN,
        objective=obj,
    )
    mod2 = Model(
        m,
        name="mod2",
        equations=[ddev, ls2],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )
    mod3 = Model(
        m,
        name="mod3",
        equations=[ddev, ls3],
        problem="dnlp",
        sense=Sense.MIN,
        objective=obj,
    )
    mod4 = Model(
        m,
        name="mod4",
        equations=[ddev, ls4],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )
    mod5 = Model(
        m,
        name="mod5",
        equations=[ddev, ls5],
        problem="dnlp",
        sense=Sense.MIN,
        objective=obj,
    )
    mod5a = Model(
        m,
        name="mod5a",
        equations=[ddeva, ls5a],
        problem="lp",
        sense=Sense.MIN,
        objective=obj,
    )
    mod6 = Model(
        m,
        name="mod6",
        equations=[ddev, ls6],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )
    mod7 = Model(
        m,
        name="mod7",
        equations=[ddev, ls7],
        problem="dnlp",
        sense=Sense.MIN,
        objective=obj,
    )
    mod8 = Model(
        m,
        name="mod8",
        equations=[ddev, ls8],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )

    # Reporting Parameter
    result = Parameter(m, name="result", domain=["*", "*"])

    b.l[n] = 1
    dev.l[i] = dat[i, "y"] - Sum(n, b.l[n] * dat[i, n])
    dev.up[i] = 100
    dev.lo[i] = -100
    devp.up[i] = 100
    devn.up[i] = 100

    m.addOptions({"limRow": 0, "limCol": 0})

    mod1.solve()
    result["mod1", n] = b.l[n]
    result["mod1", "obj"] = obj.l

    mod1a.solve()
    result["mod1a", n] = b.l[n]
    result["mod1a", "obj"] = obj.l

    mod2.solve()
    result["mod2", n] = b.l[n]
    result["mod2", "obj"] = obj.l

    mod3.solve()
    result["mod3", n] = b.l[n]
    result["mod3", "obj"] = obj.l

    mod4.solve()
    result["mod4", n] = b.l[n]
    result["mod4", "obj"] = obj.l

    mod5.solve()
    result["mod5", n] = b.l[n]
    result["mod5", "obj"] = obj.l

    mod5a.solve()
    result["mod5a", n] = b.l[n]
    result["mod5a", "obj"] = obj.l

    mod6.solve()
    result["mod6", n] = b.l[n]
    result["mod6", "obj"] = obj.l

    mod7.solve()
    result["mod7", n] = b.l[n]
    result["mod7", "obj"] = obj.l

    mod8.solve()
    result["mod8", n] = b.l[n]
    result["mod8", "obj"] = obj.l

    print(result.pivot())

    import math

    assert math.isclose(mod8.objective_value, 0.0005052, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/linear.py
GAMS-dev/gamspy
"""
Line Optimization (LOP)

The problem finds line plans for a given rail network and origin
destination demand data. Models for minimum cost and direct traveler
objectives are given. The set of possible lines is defined by the
shortest paths in the rail network.


Bussieck, M R, Optimal Lines in Public Rail Transport. PhD thesis,
TU Braunschweig, 1998.

Bussieck, M R, Kreuzer, P, and Zimmermann, U T, Optimal Lines for
Railway Systems. European Journal of Operation Research 96, 1 (1996),
54-63.

Claessens, M T, van Dijk, N M, and Zwaneveld, P J, Cost Optimal
Allocation of Rail Passenger Lines. European Journal Operation
Research 110, 3 (1998), 474-489.

Keywords: linear programming, mixed integer linear programming, passenger railway optimization,
          shortest path, dutch railway, public rail transport, network optimization
"""
# flake8: noqa
import sys
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/lop.gdx",
        # system_directory="/opt/gams/gams44.0_linux_x64_64_sfx",
    )

    # Sets
    s, lf, ac, d = m.getSymbols(["s", "lf", "ac", "d"])

    # Parameters
    rt, tt, lfr, od = m.getSymbols(["rt", "tt", "lfr", "od"])

    # Scalars
    mincars, ccap, cfx, crm, trm, cmp, maxtcap = m.getSymbols(
        ["mincars", "ccap", "cfx", "crm", "trm", "cmp", "maxtcap"]
    )

    # Alias
    s1, s2, s3 = m.getSymbols(["s1", "s2", "s3"])

    # Variables
    f, spobj = m.getSymbols(["f", "spobj"])

    # Equations
    balance, defspobj = m.getSymbols(["balance", "defspobj"])

    balance[s, s1] = (
        Sum(d[s1, s2], f[s, d])
        == Sum(d[s2, s1], f[s, d]) + s.sameAs(s1) * Card(s) - 1
    )

    defspobj.definition = spobj == Sum(
        (s, d[s1, s2]), f[s, d] * gams_math.Max(rt[s1, s2], rt[s2, s1])
    )

    sp = Model(
        m,
        "sp",
        equations=[balance, defspobj],
        problem="LP",
        sense=Sense.MIN,
        objective=spobj,
    )
    sp.solve()

    tree = Set(m, "tree", domain=[s, s1, s2])
    tree[s, s1, s2] = f.l[s, s1, s2]

    r = Set(m, "r", records=[str(idx) for idx in range(1, 101)])
    k = Set(m, "k", domain=[s, s])
    v = Set(m, "v", domain=[s, r])
    unvisit = Set(m, "unvisit", domain=[s])
    visit = Set(m, "visit", domain=[s])
    from_ = Set(m, "from", domain=[s])
    to = Set(m, "to", domain=[s])
    l = Set(m, "l", domain=[s, s1, s2, s3])  # noqa: E741
    lr = Set(m, "lr", domain=[s, s1, s2, r])

    root = Alias(m, "root", s)
    r1 = Alias(m, "r1", r)

    l[root, s, s1, s2] = False
    lr[root, s, s1, r] = False

    counter = 0

    for root_elem in root:
        from_[root_elem["uni"]] = True
        unvisit[s] = True
        visit[s] = False

        for r_elem in r:
            if int(r_elem["uni"]) > 1 and len(unvisit):
                unvisit[from_] = False
                visit[from_] = True
                to[unvisit] = Sum(tree[root_elem["uni"], from_, unvisit], True)

                for f_elem in from_:
                    k[s2, s3].where[
                        l[root_elem["uni"], f_elem["s"], s2, s3]
                    ] = True
                    v[s2, r1].where[
                        lr[root_elem["uni"], f_elem["s"], s2, r1]
                    ] = True
                    v[f_elem["s"], "1"].where[Card(k) == 0] = True

                    l[root_elem["uni"], to, k].where[
                        tree[root_elem["uni"], f_elem["s"], to]
                    ] = True
                    lr[root_elem["uni"], to, v].where[
                        tree[root_elem["uni"], f_elem["s"], to]
                    ] = True
                    l[root_elem["uni"], to, f_elem["s"], to].where[
                        tree[root_elem["uni"], f_elem["s"], to]
                    ] = True
                    lr[root_elem["uni"], to, to, r_elem["uni"]].where[
                        tree[root_elem["uni"], f_elem["s"], to]
                    ] = True

                    k[s2, s3] = False
                    v[s2, r1] = False

                from_[s] = False
                from_[to] = True
                to[s] = False

                counter += 1

        from_[s] = False

    error02 = Set(m, "error02", domain=[s1, s2])
    error02[s1, s2] = lfr[s1, s2] & (Sum(l[root, s, s1, s2], 1) == 0)

    if len(error02):
        sys.exit(f"There is an error {error02.records}")

    ll = Set(m, "ll", domain=[s, s])
    ll[s1, s2] = Ord(s1) < Ord(s2)

    l[root, s, s1, s2].where[~ll[root, s]] = False
    lr[root, s, s1, r].where[~ll[root, s]] = False

    l[root, s, s1, s2].where[l[root, s, s2, s1] & rt[s1, s2]] = True
    l[root, s, s1, s2].where[~rt[s1, s2]] = False

    rp = Parameter(m, "rp", domain=[s, s, s])
    lastrp = Parameter(m, "lastrp", domain=[s, s])

    rp[ll, s] = Sum(r.where[lr[ll, s, r]], Ord(r))
    lastrp[ll] = Smax(s, rp[ll, s])

    load = Parameter(m, "load", domain=[s1, s2])
    load[s1, s2].where[rt[s1, s2]] = Sum(
        l[root, s, s1, s2].where[od[root, s]], od[root, s]
    )

    dt = Variable(m, "dt", domain=[s1, s2])
    freq = Variable(m, "freq", domain=[s1, s2])
    phi = Variable(m, "phi", domain=[s1, s2], type="integer")
    obj = Variable(m, "obj")

    deffreqlop = Equation(m, "deffreqlop", domain=[s1, s2])
    dtlimit = Equation(m, "dtlimit", domain=[s1, s2])
    defobjdtlop = Equation(m, "defobjdtlop")

    deffreqlop[s1, s2].where[rt[s1, s2]] = freq[s1, s2] == Sum(
        l[ll, s1, s2], phi[ll]
    )

    dtlimit[s1, s2].where[od[s1, s2]] = dt[s1, s2] <= gams_math.Min(
        od[s1, s2], maxtcap
    ) * Sum(ll.where[rp[ll, s1] & rp[ll, s2]], phi[ll])

    defobjdtlop.definition = obj == Sum(
        Domain(s1, s2).where[od[s1, s2]], dt[s1, s2]
    )

    lopdt = Model(
        m,
        "lopdt",
        equations=[deffreqlop, dtlimit, defobjdtlop],
        problem="mip",
        sense=Sense.MAX,
        objective=obj,
    )

    freq.lo[s1, s2].where[rt[s1, s2]] = gams_math.Max(
        lfr[s1, s2], gams_math.ceil(load[s1, s2] / maxtcap)
    )
    freq.up[s1, s2].where[rt[s1, s2]] = freq.lo[s1, s2]
    dt.up[s1, s2].where[od[s1, s2]] = od[s1, s2]

    lopdt.solve()

    solrep = Parameter(m, "solrep", domain=["*", "*", "*", "*"])
    solsum = Parameter(m, "solsum", domain=["*", "*"])

    solrep["DT", ll, "freq"] = phi.l[ll]
    solrep["DT", ll, "cars"].where[phi.l[ll]] = mincars + Card(ac) - 1

    xcost = Parameter(m, "xcost", domain=[root, s, lf])
    ycost = Parameter(m, "ycost", domain=[root, s, lf])
    length = Parameter(m, "len", domain=[s, s])
    sigma = Parameter(m, "sigma", domain=[s, s])

    length[ll] = Sum(l[ll, s1, s2], rt[s1, s2])
    sigma[ll] = (
        length[ll]
        + Sum(s.where[lr[ll, s, "1"]], tt[s])
        + Sum(s.where[rp[ll, s] == lastrp[ll]], tt[s])
    ) / 60

    xcost[ll, lf] = (
        Ord(lf) * length[ll] * (trm + mincars * crm)
        + mincars * gams_math.ceil(sigma[ll] * Ord(lf)) * cfx
    )
    ycost[ll, lf] = (
        Ord(lf) * length[ll] * crm + gams_math.ceil(sigma[ll] * Ord(lf)) * cfx
    )

    x = Variable(m, "x", type="binary", domain=[s1, s2, lf])
    y = Variable(m, "y", type="integer", domain=[s1, s2, lf])

    deffreqilp = Equation(m, "deffreqilp", domain=[s, s])
    defloadilp = Equation(m, "defloadilp", domain=[s, s])
    oneilp = Equation(m, "oneilp", domain=[s, s])
    couplexy = Equation(m, "couplexy", domain=[s, s, lf])
    defobjilp = Equation(m, "defobjilp")

    deffreqilp[s1, s2].where[rt[s1, s2]] = freq[s1, s2] == Sum(
        (l[ll, s1, s2], lf), Ord(lf) * x[ll, lf]
    )

    defloadilp[s1, s2].where[rt[s1, s2]] = gams_math.ceil(
        load[s1, s2] / ccap
    ) <= Sum((l[ll, s1, s2], lf), Ord(lf) * (mincars * x[ll, lf] + y[ll, lf]))

    oneilp[ll] = Sum(lf, x[ll, lf]) <= 1

    couplexy[ll, lf] = y[ll, lf] <= y.up[ll, lf] * x[ll, lf]

    defobjilp.definition = obj == Sum(
        (ll, lf), xcost[ll, lf] * x[ll, lf] + ycost[ll, lf] * y[ll, lf]
    )

    ilp = Model(
        m,
        "ilp",
        equations=[defobjilp, deffreqilp, defloadilp, oneilp, couplexy],
        problem="mip",
        sense=Sense.MIN,
        objective=obj,
    )

    y.up[ll, lf] = Card(ac) - 1
    freq.up[s1, s2].where[rt[s1, s2]] = 100

    m.addOptions({"optCr": 0, "resLim": 100})

    ilp.solve()

    solrep["ILP", ll, "freq"] = Sum(lf.where[x.l[ll, lf]], Ord(lf))
    solrep["ILP", ll, "cars"] = Sum(
        lf.where[x.l[ll, lf]], mincars + y.l[ll, lf]
    )
    solsum["ILP", "cost"] = obj.l

    cap = Parameter(m, "cap", domain=[s, s])
    sol = Set(m, "sol", domain=[s, s])

    dtr = Variable(m, "dtr", domain=[s, s, s, s], type="positive")

    dtllimit = Equation(m, "dtllimit", domain=[s, s1, s2, s3])
    sumbound = Equation(m, "sumbound", domain=[s, s])

    dtllimit[l[sol, s, s1]] = (
        Sum(
            Domain(s2, s3).where[
                od[s2, s3]
                & rp[sol, s2]
                & rp[sol, s3]
                & (
                    (gams_math.Min(rp[sol, s], rp[sol, s1]) >= rp[sol, s2])
                    & (gams_math.Max(rp[sol, s], rp[sol, s1]) <= rp[sol, s3])
                    | (gams_math.Min(rp[sol, s], rp[sol, s1]) >= rp[sol, s3])
                    & (gams_math.Max(rp[sol, s], rp[sol, s1]) <= rp[sol, s2])
                )
            ],
            dtr[sol, s2, s3],
        )
        <= cap[sol]
    )

    sumbound[s2, s3].where[od[s2, s3]] = (
        Sum(sol.where[rp[sol, s2] & rp[sol, s3]], dtr[sol, s2, s3])
        == dt[s2, s3]
    )

    evaldt = Model(
        m,
        "evaldt",
        equations=[dtllimit, sumbound, defobjdtlop],
        problem="lp",
        sense=Sense.MAX,
        objective=obj,
    )

    sol[ll] = solrep["DT", ll, "freq"]
    cap[sol] = solrep["DT", sol, "freq"] * solrep["DT", sol, "cars"] * ccap

    evaldt.solve()

    solsum["DT", "dtrav"] = obj.l
    solsum["DT", "cost"] = Sum(
        sol,
        solrep["DT", sol, "freq"] * length[sol] * trm
        + (
            solrep["DT", sol, "freq"] * length[sol] * crm
            + gams_math.ceil(sigma[sol] * solrep["DT", sol, "freq"]) * cfx
        )
        * solrep["DT", sol, "cars"],
    )

    sol[ll] = solrep["ILP", ll, "freq"]
    cap[sol] = solrep["DT", sol, "freq"] * solrep["DT", sol, "cars"] * ccap

    evaldt.solve()
    solsum["ILP", "dtrav"] = obj.l

    print(solrep.records)
    print(solsum.records)


if __name__ == "__main__":
    main()

tests/integration/models/lop.py
GAMS-dev/gamspy
"""
A small linear dynamic macroeconomic model of U.S. economy in which
both monetary and fiscal policy variables are used.

Linear Quadratic Riccati Equations are solved as a General Nonlinear
Programming Problem instead of the usual Matrix Recursion.

Please see:
Kendrick, D, Caution and Probing in a Macroeconomic Model.
Journal of Economic Dynamics and Control 4, 2 (1982) pp.149-170.
"""
import numpy as np

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    cont = Container(delayed_execution=True)

    # SETS #
    n = Set(
        cont, name="n", records=["consumpt", "invest"], description="states"
    )
    m = Set(
        cont, name="m", records=["gov-expend", "money"], description="controls"
    )
    k = Set(
        cont,
        name="k",
        records=[
            "1964-i",
            "1964-ii",
            "1964-iii",
            "1964-iv",
            "1965-i",
            "1965-ii",
            "1965-iii",
            "1965-iv",
        ],
        description="horizon",
    )
    ku = Set(cont, name="ku", domain=[k], description="control horizon")
    ki = Set(cont, name="ki", domain=[k], description="initial period")
    kt = Set(cont, name="kt", domain=[k], description="terminal period")

    # ALIASES #
    nn = Alias(cont, name="nn", alias_with=n)
    mp = Alias(cont, name="mp", alias_with=m)

    ku[k] = Number(1).where[Ord(k) < Card(k)]
    ki[k] = Number(1).where[Ord(k) == 1]
    kt[k] = ~ku[k]

    # PARAMETERS #
    a = Parameter(
        cont,
        name="a",
        domain=[n, nn],
        records=np.array([[0.914, -0.016], [0.097, 0.424]]),
        description="state vector matrix",
    )
    b = Parameter(
        cont,
        name="b",
        domain=[n, m],
        records=np.array([[0.305, 0.424], [-0.101, 1.459]]),
        description="control vector matrix",
    )
    wk = Parameter(
        cont,
        name="wk",
        domain=[n, nn],
        records=np.array([[0.0625, 0], [0, 1]]),
        description="penalty matrix for states - input",
    )
    rk = Parameter(
        cont,
        name="rk",
        domain=[m, mp],
        records=np.array([[1, 0], [0, 0.444]]),
        description="penalty matrix for controls",
    )

    c = Parameter(
        cont,
        name="c",
        domain=[n],
        records=[("consumpt", -59.4), ("invest", -184.7)],
        description="constant term",
    )
    xinit = Parameter(
        cont,
        name="xinit",
        domain=[n],
        records=[("consumpt", 387.9), ("invest", 85.3)],
        description="initial value",
    )
    uinit = Parameter(
        cont,
        name="uinit",
        domain=[m],
        records=[("gov-expend", 110.5), ("money", 147.1)],
        description="initial controls",
    )
    xtilde = Parameter(
        cont, name="xtilde", domain=[n, k], description="desired path for x"
    )
    utilde = Parameter(
        cont, name="utilde", domain=[m, k], description="desired path for u"
    )
    w = Parameter(
        cont,
        name="w",
        domain=[n, nn, k],
        description="penalty matrix on states",
    )

    w[n, nn, ku] = wk[n, nn]
    w[n, nn, kt] = 10000 * wk[n, nn]
    xtilde[n, k] = xinit[n] * gams_math.power(1.0075, Ord(k) - 1)
    utilde[m, k] = uinit[m] * gams_math.power(1.0075, Ord(k) - 1)

    # VARIABLES #
    x = Variable(cont, name="x", domain=[n, k], description="state variable")
    u = Variable(cont, name="u", domain=[m, k], description="control variable")
    j = Variable(cont, name="j", description="criterion")

    # EQUATIONS #

    criterion = Equation(
        cont,
        name="criterion",
        type="regular",
        description="criterion definition",
    )
    stateq = Equation(
        cont,
        name="stateq",
        type="regular",
        domain=[n, k],
        description="state equation",
    )

    criterion.definition = j == 0.5 * Sum(
        [k, n, nn],
        (x[n, k] - xtilde[n, k]) * w[n, nn, k] * (x[nn, k] - xtilde[nn, k]),
    ) + 0.5 * Sum(
        [ku, m, mp],
        (u[m, ku] - utilde[m, ku]) * rk[m, mp] * (u[mp, ku] - utilde[mp, ku]),
    )

    stateq[n, k.lead(1)] = (
        x[n, k.lead(1)]
        == Sum(nn, a[n, nn] * x[nn, k]) + Sum(m, b[n, m] * u[m, k]) + c[n]
    )

    macro = Model(
        cont,
        name="macro",
        equations=cont.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=j,
    )

    x.l[n, k] = xinit[n]
    u.l[m, k] = uinit[m]
    x.fx[n, ki] = xinit[n]

    macro.solve()

    import math

    assert math.isclose(macro.objective_value, 273.2724, rel_tol=0.001)

    # REPORTING PARAMETER
    rep = Parameter(cont, name="rep", domain=["*", k])

    rep["xtilde_consumpt", k] = xtilde["consumpt", k]
    rep["xtilde_invest", k] = xtilde["invest", k]
    rep["x_consumpt", k] = x.l["consumpt", k]
    rep["x_invest", k] = x.l["invest", k]
    rep["utilde_gov-expend", k] = utilde["gov-expend", k]
    rep["utilde_money", k] = utilde["money", k]
    rep["u_gov-expend", k] = u.l["gov-expend", k]
    rep["u_money", k] = u.l["money", k]

    print("Objective Function Value: ", round(j.toValue(), 4))
    print("Solution Summary:\n", rep.pivot().round(3))

    # End Macro


if __name__ == "__main__":
    main()

tests/integration/models/macro.py
GAMS-dev/gamspy
"""
Mexico Steel - Small Static (MEXSS)

A simplified representation of the Mexican steel sector is used
to introduce a process industry production and distribution
scheduling problem.


Kendrick, D, Meeraus, A, and Alatorre, J, The Planning of Investment
Programs in the Steel Industry. The Johns Hopkins University Press,
Baltimore and London, 1984.

A scanned version of this out-of-print book is accessible at
http://www.gams.com/docs/pdf/steel_investment.pdf

Keywords: linear programming, production problem, distribution problem,
scheduling,
          micro economics, steel industry
"""
import pandas

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    cont = Container(delayed_execution=True)

    # Prepare data
    steel_plants = ["ahmsa", "fundidora", "sicartsa", "hylsa", "hylsap"]
    markets = ["mexico-df", "monterrey", "guadalaja"]
    commodities = [
        "pellets",
        "coke",
        "nat-gas",
        "electric",
        "scrap",
        "pig-iron",
        "sponge",
        "steel",
    ]
    final_products = ["steel"]
    intermediate_products = ["sponge", "pig-iron"]
    raw_materials = ["pellets", "coke", "nat-gas", "electric", "scrap"]
    processes = ["pig-iron", "sponge", "steel-oh", "steel-el", "steel-bof"]
    productive_units = [
        "blast-furn",
        "openhearth",
        "bof",
        "direct-red",
        "elec-arc",
    ]

    io_coefficients = pandas.DataFrame(
        [
            ["pellets", "pig-iron", -1.58],
            ["pellets", "sponge", -1.38],
            ["coke", "pig-iron", -0.63],
            ["nat-gas", "sponge", -0.57],
            ["electric", "steel-el", -0.58],
            ["scrap", "steel-oh", -0.33],
            ["scrap", "steel-bof", -0.12],
            ["pig-iron", "pig-iron", 1.00],
            ["pig-iron", "steel-oh", -0.77],
            ["pig-iron", "steel-bof", -0.95],
            ["sponge", "sponge", 1.00],
            ["sponge", "steel-el", -1.09],
            ["steel", "steel-oh", 1.00],
            ["steel", "steel-el", 1.00],
            ["steel", "steel-bof", 1.00],
        ]
    )

    capacity_utilization = pandas.DataFrame(
        [
            ["blast-furn", "pig-iron", 1.0],
            ["openhearth", "steel-oh", 1.0],
            ["bof", "steel-bof", 1.0],
            ["direct-red", "sponge", 1.0],
            ["elec-arc", "steel-el", 1.0],
        ]
    )

    capacities_of_units = pandas.DataFrame(
        [
            ["blast-furn", "ahmsa", 3.25],
            ["blast-furn", "fundidora", 1.40],
            ["blast-furn", "sicartsa", 1.10],
            ["openhearth", "ahmsa", 1.50],
            ["openhearth", "fundidora", 0.85],
            ["bof", "ahmsa", 2.07],
            ["bof", "fundidora", 1.50],
            ["bof", "sicartsa", 1.30],
            ["direct-red", "hylsa", 0.98],
            ["direct-red", "hylsap", 1.00],
            ["elec-arc", "hylsa", 1.13],
            ["elec-arc", "hylsap", 0.56],
        ]
    )

    rail_distances = pandas.DataFrame(
        [
            ["ahmsa", "mexico-df", 1204],
            ["ahmsa", "monterrey", 218],
            ["ahmsa", "guadalaja", 1125],
            ["ahmsa", "export", 739],
            ["fundidora", "mexico-df", 1017],
            ["fundidora", "guadalaja", 1030],
            ["fundidora", "export", 521],
            ["sicartsa", "mexico-df", 819],
            ["sicartsa", "monterrey", 1305],
            ["sicartsa", "guadalaja", 704],
            ["hylsa", "mexico-df", 1017],
            ["hylsa", "guadalaja", 1030],
            ["hylsa", "export", 521],
            ["hylsap", "mexico-df", 185],
            ["hylsap", "monterrey", 1085],
            ["hylsap", "guadalaja", 760],
            ["hylsap", "export", 315],
            ["import", "mexico-df", 428],
            ["import", "monterrey", 521],
            ["import", "guadalaja", 300],
        ]
    )

    product_prices = pandas.DataFrame(
        [
            ["pellets", "domestic", 18.7],
            ["coke", "domestic", 52.17],
            ["nat-gas", "domestic", 14.0],
            ["electric", "domestic", 24.0],
            ["scrap", "domestic", 105.0],
            ["steel", "import", 150],
            ["steel", "export", 140],
        ]
    )

    demand_distribution = pandas.DataFrame(
        [["mexico-df", 55], ["monterrey", 30], ["guadalaja", 15]]
    )

    dt = 5.209  # total demand for final goods in 1979
    rse = 40  # raw steel equivalence
    eb = 1.0  # export bound

    # Set
    i = Set(
        cont,
        name="i",
        records=pandas.DataFrame(steel_plants),
        description="steel plants",
    )
    j = Set(
        cont,
        name="j",
        records=pandas.DataFrame(markets),
        description="markets",
    )
    c = Set(
        cont,
        name="c",
        records=pandas.DataFrame(commodities),
        description="commidities",
    )
    cf = Set(
        cont,
        name="cf",
        records=pandas.DataFrame(final_products),
        domain=[c],
        description="final products",
    )
    ci = Set(
        cont,
        name="ci",
        records=pandas.DataFrame(intermediate_products),
        domain=[c],
        description="intermediate products",
    )
    cr = Set(
        cont,
        name="cr",
        records=pandas.DataFrame(raw_materials),
        domain=[c],
        description="raw materials",
    )
    p = Set(
        cont,
        name="p",
        records=pandas.DataFrame(processes),
        description="processes",
    )
    m = Set(
        cont,
        name="m",
        records=pandas.DataFrame(productive_units),
        description="productive units",
    )

    # Data
    a = Parameter(
        cont,
        name="a",
        domain=[c, p],
        records=io_coefficients,
        description="input-output coefficients",
    )
    b = Parameter(
        cont,
        name="b",
        domain=[m, p],
        records=capacity_utilization,
        description="capacity utilization",
    )
    k = Parameter(
        cont,
        name="k",
        domain=[m, i],
        records=capacities_of_units,
        description="capacities of productive units",
    )
    dd = Parameter(
        cont,
        name="dd",
        domain=[j],
        records=demand_distribution,
        description="distribution of demand",
    )
    d = Parameter(
        cont, name="d", domain=[c, j], description="demand for steel in 1979"
    )

    d["steel", j] = dt * (1 + rse / 100) * dd[j] / 100

    rd = Parameter(
        cont,
        name="rd",
        domain=["*", "*"],
        records=rail_distances,
        description="rail distances from plants to markets",
    )

    muf = Parameter(
        cont,
        name="muf",
        domain=[i, j],
        description="transport rate: final products",
    )
    muv = Parameter(
        cont, name="muv", domain=[j], description="transport rate: imports"
    )
    mue = Parameter(
        cont, name="mue", domain=[i], description="transport rate: exports"
    )

    muf[i, j] = (2.48 + 0.0084 * rd[i, j]).where[rd[i, j]]
    muv[j] = (2.48 + 0.0084 * rd["import", j]).where[rd["import", j]]
    mue[i] = (2.48 + 0.0084 * rd[i, "export"]).where[rd[i, "export"]]

    prices = Parameter(
        cont,
        name="prices",
        domain=[c, "*"],
        records=product_prices,
        description="product prices (us$ per unit)",
    )

    pd = Parameter(cont, name="pd", domain=[c], description="domestic prices")
    pv = Parameter(cont, name="pv", domain=[c], description="import prices")
    pe = Parameter(cont, name="pe", domain=[c], description="export prices")

    pd[c] = prices[c, "domestic"]
    pv[c] = prices[c, "import"]
    pe[c] = prices[c, "export"]

    # Variable
    z = Variable(
        cont,
        name="z",
        domain=[p, i],
        type="Positive",
        description="process level",
    )
    x = Variable(
        cont,
        name="x",
        domain=[c, i, j],
        type="Positive",
        description="shipment of final products",
    )
    u = Variable(
        cont,
        name="u",
        domain=[c, i],
        type="Positive",
        description="purchase of domestic materials",
    )
    v = Variable(
        cont, name="v", domain=[c, j], type="Positive", description="imports"
    )
    e = Variable(
        cont, name="e", domain=[c, i], type="Positive", description="exports"
    )
    phi = Variable(cont, name="phi", description="total cost")
    phipsi = Variable(cont, name="phipsi", description="raw material cost")
    philam = Variable(cont, name="philam", description="transport cost")
    phipi = Variable(cont, name="phipi", description="import cost")
    phieps = Variable(cont, name="phieps", description="export revenue")

    # Equation declaration
    mbf = Equation(
        cont,
        name="mbf",
        domain=[c, i],
        description="material balances: final products",
    )
    mbi = Equation(
        cont,
        name="mbi",
        domain=[c, i],
        description="material balances: intermediates",
    )
    mbr = Equation(
        cont,
        name="mbr",
        domain=[c, i],
        description="material balances: raw materials",
    )
    cc = Equation(
        cont,
        name="cc",
        domain=[m, i],
        description="capacity constraint",
    )
    mr = Equation(
        cont,
        name="mr",
        domain=[c, j],
        description="market requirements",
    )
    me = Equation(
        cont,
        name="me",
        domain=[c],
        description="maximum export",
    )
    obj = Equation(cont, name="obj", description="accounting: total cost")
    apsi = Equation(
        cont,
        name="apsi",
        description="accounting: raw material cost",
    )
    alam = Equation(
        cont,
        name="alam",
        description="accounting: transport cost",
    )
    api = Equation(cont, name="api", description="accounting: import cost")
    aeps = Equation(
        cont,
        name="aeps",
        description="accounting: export cost",
    )

    # Equation definition
    mbf[cf, i] = Sum(p, a[cf, p] * z[p, i]) >= Sum(j, x[cf, i, j]) + e[cf, i]
    mbi[ci, i] = Sum(p, a[ci, p] * z[p, i]) >= 0
    mbr[cr, i] = Sum(p, a[cr, p] * z[p, i]) + u[cr, i] >= 0
    cc[m, i] = Sum(p, b[m, p] * z[p, i]) <= k[m, i]
    mr[cf, j] = Sum(i, x[cf, i, j]) + v[cf, j] >= d[cf, j]
    me[cf] = Sum(i, e[cf, i]) <= eb
    obj.definition = phi == phipsi + philam + phipi - phieps
    apsi.definition = phipsi == Sum((cr, i), pd[cr] * u[cr, i])
    alam.definition = philam == Sum((cf, i, j), muf[i, j] * x[cf, i, j]) + Sum(
        (cf, j), muv[j] * v[cf, j]
    ) + Sum((cf, i), mue[i] * e[cf, i])
    api.definition = phipi == Sum((cf, j), pv[cf] * v[cf, j])
    aeps.definition = phieps == Sum((cf, i), pe[cf] * e[cf, i])

    mexss = Model(
        cont,
        name="mexss",
        equations=cont.getEquations(),
        problem="LP",
        sense=Sense.MIN,
        objective=phi,
    )

    mexss.solve()

    import math

    assert math.isclose(mexss.objective_value, 538.8112, rel_tol=0.001)
    print(mexss.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/mexss.py
GAMS-dev/gamspy
"""
Heat Integrated Distillation Sequences (MINLPHI)

This problem describes a formulation and algorithmic procedure
for obtaining heat-integrated distillation sequences for the separation
of a given multi component feed stream into its pure components products.


Morari, M, and Grossmann, I E, Eds, Chemical Engineering Optimization
Models with GAMS. Computer Aids for Chemical Engineering Corporation,
1991.

Floudas, C A, and Paules IV, G E, A Mixed-Integer Nonlinear Programming
Formulation for the Synthesis of Heat Integrated Distillation Sequence.
Computers and Chemical Engineering 12, 6 (1988), 531-546.

======================================================================

   A MATHEMATICAL PROGRAMMING FORMULATION FOR PROCESS SYNTHESIS

===================================================================

   copyright    G.E. PAULES IV & C.A. FLOUDAS

            *** Dept. of Chemical Engineering ***
                 *** Princeton University ***
                         May 23, 1987


   Algorithm:  The Outer Approximation with Equality Relaxation
          Full Solution with Starting Point from FIXDT

======================================================================

        This formulation provides the Optimal Heat Integrated
    Distillation Sequence with Pressure as a continuous variable
                for a three component separation.
   The Outer Approximation with Equality Relaxation algorithm is
        used in the automatic solution procedure using GAMS

             Components:     a == Hexane
                             b == Benzene
                             c == Heptane

Total feed to superstructure == 396 kgmol/hr

Multicomponent feed composition:
                        a = 0.80
                        b = 0.10
                        c = 0.10

======================================================================


A Superstructure of the form ...



                       _______               _______
                      _|_    |              _|_    |
                     /   \  ( )            /   \  ( )
                     |   |___|__ A         |   |___|___ B
                     |   |                 |   |
           |---------| 1 |                 | 3 |
           |         |   |       ----------|   |
           |         |   |       |         |   |
           |         |   |_______|         |   |
           |         \___/  |  BC          \___/_______ C
    F      |           |   ( )               |     |
  -------->|           |____|                |----( )
  (ABC)    |
           |           _______               _______
           |          _|_    |              _|_    |
           |         /   \  ( )            /   \  ( )
           |         |   |___| AB          |   |___|___ A
           |         |   |   |_____________|   |
           |---------| 2 |                 | 4 |
                     |   |                 |   |
                     |   |                 |   |
                     |   |______ C         |   |_______ B
                     \___/  |              \___/   |
                       |   ( )               |    ( )
                       |____|                |_____|



 is used with binary variables representing:
    a_  the existence of columns in the sequence.
    b_  the selection of heat exchangers for heat integration.
    c_  the selection of hot and cold utilities.


Associated Reference:
_______________________
 "A Mixed-Integer Nonlinear Programming formulation for the
  synthesis of Heat-Integrated Distillation Sequences"

  C.A. Floudas and G.E. Paules IV,  1988.
  Computers and Chemical Engineering vol. 12 no. 6 pp. 531-546

Keywords: mixed integer linear programming, nonlinear programming, chemical engineering,
          distillation sequences, heat integrated distillation
"""
from sys import float_info

import numpy as np

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def main():
    cont = Container(delayed_execution=True)

    # Set some options
    cont.addOptions(
        {"limCol": 0, "limRow": 0, "bRatio": 1, "domLim": 1000, "optCr": 0}
    )

    # SETS #
    # the set of all columns and their condensers in the superstructure
    i = Set(cont, name="i", records=[f"c-{i}" for i in range(1, 5)])
    # the set of all reboilers in the superstructure
    j = Set(cont, name="j", records=[f"r-{i}" for i in range(1, 5)])
    # the set of all hot utilities available
    hu = Set(cont, name="hu", records=["lp", "ex"])
    # the set of all cold utilities available
    cu = Set(cont, name="cu", records=["cw"])
    # an index for linear fit coefficients
    n = Set(cont, name="n", records=["a", "b"])
    # the set of all intermediate products in superstructure
    m = Set(cont, name="m", records=["ab", "bc"])
    # this set maps columns to produced intermediate products
    pm = Set(
        cont, name="pm", domain=[i, m], records=[("c-1", "bc"), ("c-2", "ab")]
    )
    # this set maps columns to intermediate product feeds
    fm = Set(
        cont, name="fm", domain=[i, m], records=[("c-3", "bc"), ("c-4", "ab")]
    )
    # these sets are for dynamic control of solution algorithm
    km = Set(cont, name="km", records=[f"k-{i}" for i in range(1, 101)])
    k = Set(cont, name="k", domain=[km])
    kiter = Set(cont, name="kiter", domain=[km])
    kdynmax = Set(cont, name="kdynmax", domain=[km])

    # alias sets for condensers and reboilers
    ip = Alias(cont, name="ip", alias_with=i)
    jp = Alias(cont, name="jp", alias_with=j)

    # =====================================================================
    # Definition of "z" parameters for conditional control of model
    # used to map permissible matches between condensers and reboilers
    # and the position of columns in the superstructure
    # =====================================================================

    # PARAMETERS #
    # defines the set of leading columns in the superstructure
    zlead = Parameter(
        cont, name="zlead", domain=[i], records=[["c-1", 1], ["c-2", 1]]
    )

    # defines allowable matches of heat integration for superstructure
    # only permits heat integration between columns in the same sequence
    zcrhx = Parameter(
        cont,
        name="zcrhx",
        domain=[i, j],
        records=[
            ["c-1", "r-3", 1],
            ["c-2", "r-4", 1],
            ["c-3", "r-1", 1],
            ["c-4", "r-2", 1],
        ],
    )

    # Parameter used in pure integer constraint to permit only one
    # direction of heat integration between two columns
    # this would yield an infeasible solution but the constraint
    # is included explicitly to reduce milp solution time
    zlim = Parameter(cont, name="zlim", domain=[i, j])
    zlim[i, j] = Number(1).where[(zcrhx[i, j]) & (Ord(i) < Ord(j))]

    # relates appropriate reboiler to the condenser of same column
    # (preferably should use an alias rather than a different set)
    zcr = Parameter(cont, name="zcr", domain=[i, j])
    zcr[i, j] = Number(1).where[Ord(i) == Ord(j)]

    # =====================================================================
    # Binary variables are divided into 4 classes and variable/parameter
    # names starting with "y"
    #     ycol - column selection
    #     yhx  - heat integration exchanger matches
    #     yhu  - hot utility matches
    #     ycup - cold utiltiy matches

    # These parameters store first guess combination of binary variables
    # used to initialize minlp algorithm and parameterize the minlp
    # primal problem throughout the rest of the iterations
    # =====================================================================
    yhxp = Parameter(
        cont, name="yhxp", domain=[i, j], records=[["c-1", "r-3", 1]]
    )
    yhup = Parameter(
        cont, name="yhup", domain=[hu, j], records=[["lp", "r-1", 1]]
    )
    ycup = Parameter(
        cont,
        name="ycup",
        domain=[i, cu],
        records=[["c-1", "cw", 1], ["c-3", "cw", 1]],
    )
    ycolp = Parameter(
        cont, name="ycolp", domain=[i], records=[["c-1", 1], ["c-3", 1]]
    )

    # =====================================================================
    # These parameters store the values of the binary proposals
    # for all the iterations performed for use in integer cuts
    # and recovering optimal solution
    # =====================================================================
    yhxk = Parameter(cont, name="yhxk", domain=[i, j, km])
    yhuk = Parameter(cont, name="yhuk", domain=[hu, j, km])
    ycuk = Parameter(cont, name="ycuk", domain=[i, cu, km])
    ycolk = Parameter(cont, name="ycolk", domain=[i, km])

    # =====================================================================
    # Declaration of parameters for rest of model
    # =====================================================================
    # mass balances for each sharp separator
    spltfrc = Parameter(
        cont,
        name="spltfrc",
        domain=[i, m],
        records=[["c-1", "bc", 0.20], ["c-2", "ab", 0.90]],
    )
    # minimum condenser temperatures obtained from simulation data
    tcmin = Parameter(
        cont,
        name="tcmin",
        domain=[i],
        records=np.array([341.92, 343.01, 353.54, 341.92]),
    )
    # either hottest hot utility-dtmin or for individual separations
    # 2*dtmin below critical temperature of bottoms product
    trmax = Parameter(cont, name="trmax", domain=[j])

    trmax[j] = 1000

    # ====================================================================
    # scaled cost coefficients for distillation column fits
    # nonlinear fixed-charge cost model
    # cost = fc*y + vc*flow*temp
    # scaling factor = 1000
    # ====================================================================
    fc = Parameter(
        cont,
        name="fc",
        domain=[i],
        records=np.array([151.125, 180.003, 4.2286, 213.42]),
    )
    vc = Parameter(
        cont,
        name="vc",
        domain=[i],
        records=np.array([0.003375, 0.000893, 0.004458, 0.003176]),
    )
    thu = Parameter(
        cont, name="thu", domain=[hu], records=np.array([421.0, 373.0])
    )

    # hot utility cost coeff - gives cost in thousands of dollars per year
    # ucost = q(10e+6 kj/hr)*costhu[hu]
    costhu = Parameter(
        cont, name="costhu", domain=[hu], records=np.array([24.908, 9.139])
    )
    kf = Parameter(
        cont,
        name="kf",
        domain=[i, n],
        records=np.array(
            [[32.4, 0.0225], [25.0, 0.0130], [3.76, 0.0043], [35.1, 0.0156]]
        ),
    )
    af = Parameter(
        cont,
        name="af",
        domain=[i, n],
        records=np.array(
            [[9.541, 1.028], [12.24, 1.050], [8.756, 1.029], [9.181, 1.005]]
        ),
    )

    # =====================================================================
    # define scalar quantities for rest of model
    # =====================================================================
    totflow = Parameter(cont, name="totflow", records=396)
    u = Parameter(cont, name="u", records=1500)
    uint = Parameter(cont, name="uint", records=20)
    fchx = Parameter(cont, name="fchx", records=3.392)
    vchx = Parameter(cont, name="vchx", records=0.0893)
    htc = Parameter(cont, name="htc", records=0.0028)
    dtmin = Parameter(cont, name="dtmin", records=10.0)
    tcin = Parameter(cont, name="tcin", records=305.0)
    tcout = Parameter(cont, name="tcout", records=325.0)
    costcw = Parameter(cont, name="costcw", records=4.65)
    beta = Parameter(cont, name="beta", records=0.52)
    alpha = Parameter(cont, name="alpha", records=0.40)

    # =====================================================================
    # The parameters declared here are assigned throughout the
    # algorithmic procedures.
    # They perform the following tasks in the algorithm
    #     1) transfer of solution data between master and subproblem
    #     2) storage of solution data
    #     3) control of upper and lower bounds in milp master
    #     4) storage of optimal solution
    # =====================================================================

    # Storage of variable levels for each iteration
    # Identifier derived from name of variable with letter "k" appended
    fk = Parameter(cont, name="fk", domain=[i, km])
    qrk = Parameter(cont, name="qrk", domain=[j, km])
    qck = Parameter(cont, name="qck", domain=[i, km])
    qcrk = Parameter(cont, name="qcrk", domain=[i, j, km])
    qhuk = Parameter(cont, name="qhuk", domain=[hu, j, km])
    qcuk = Parameter(cont, name="qcuk", domain=[i, cu, km])
    tck = Parameter(cont, name="tck", domain=[i, km])
    trk = Parameter(cont, name="trk", domain=[j, km])
    lmtdk = Parameter(cont, name="lmtdk", domain=[i, km])

    zoaup = Parameter(cont, name="zoaup", records=np.inf)

    # storage of optimal binary variable combination
    # continuous variable levels are not stored separately as they
    # can be obtained from the xxxk storage parameters above
    yhxopt = Parameter(cont, name="yhxopt", domain=[i, j])
    yhuopt = Parameter(cont, name="yhuopt", domain=[hu, j])
    ycuopt = Parameter(cont, name="ycuopt", domain=[i, cu])
    ycolopt = Parameter(cont, name="ycolopt", domain=[i])

    kopt = Parameter(cont, name="kopt")

    # storage of sign() of Lagrange multiplier from nonlinear equalities
    lmtdmar = Parameter(cont, name="lmtdmar", domain=[i, km])

    # VARIABLES #

    # Free Variables
    zoau = Variable(cont, name="zoau")
    zoal = Variable(cont, name="zoal")
    vqcr = Variable(cont, name="vqcr", domain=[km])
    vqhu = Variable(cont, name="vqhu", domain=[km])
    vqcu = Variable(cont, name="vqcu", domain=[km])

    # Positive Variables
    f = Variable(cont, name="f", type="positive", domain=[i])
    qr = Variable(cont, name="qr", type="positive", domain=[j])
    qc = Variable(cont, name="qc", type="positive", domain=[i])
    qcr = Variable(cont, name="qcr", type="positive", domain=[i, j])
    qhu = Variable(cont, name="qhu", type="positive", domain=[hu, j])
    qcu = Variable(cont, name="qcu", type="positive", domain=[i, cu])
    tc = Variable(cont, name="tc", type="positive", domain=[i])
    tr = Variable(cont, name="tr", type="positive", domain=[j])
    lmtd = Variable(cont, name="lmtd", type="positive", domain=[i])
    sl1 = Variable(cont, name="sl1", type="positive", domain=[i])
    sl2 = Variable(cont, name="sl2", type="positive", domain=[i])
    s1 = Variable(cont, name="s1", type="positive", domain=[i])
    s2 = Variable(cont, name="s2", type="positive", domain=[i])
    s3 = Variable(cont, name="s3", type="positive", domain=[i])
    s4 = Variable(cont, name="s4", type="positive", domain=[i])

    # Binary Variables
    yhx = Variable(cont, name="yhx", type="binary", domain=[i, j])
    yhu = Variable(cont, name="yhu", type="binary", domain=[hu, j])
    ycu = Variable(cont, name="ycu", type="binary", domain=[i, cu])
    ycol = Variable(cont, name="ycol", type="binary", domain=[i])

    # =====================================================================
    # declaration of equations
    # for solution of the nlp subproblems:
    # early versions of GAMS did not permit binary variables to appear
    # in the constraints of a nonlinear programming problem even if
    # they appeared in linear constraints and were fixed at a bound
    # therefore -
    # constraints that contain the binary variables are duplicated:
    # one form contains the declared binary variable and the  other
    # substitutes a parameter that is assigned the current level of
    # the binary variable.  constraints that are duplicated and are to
    # appear in the nlp subproblem model have the letter "n" prepended
    # to the equation name.
    # =====================================================================
    # EQUATIONS #
    nlpobj = Equation(cont, name="nlpobj", type="regular")
    milpcon = Equation(cont, name="milpcon", type="regular", domain=[km])
    evqcr = Equation(cont, name="evqcr", type="regular", domain=[km])
    evqhu = Equation(cont, name="evqhu", type="regular", domain=[km])
    evqcu = Equation(cont, name="evqcu", type="regular", domain=[km])
    lmtdsn = Equation(cont, name="lmtdsn", type="regular", domain=[i])
    lmtdsm = Equation(cont, name="lmtdsm", type="regular", domain=[i, km])
    ntempset = Equation(cont, name="ntempset", type="regular", domain=[i])
    tempset = Equation(cont, name="tempset", type="regular", domain=[i])
    nartrex1 = Equation(cont, name="nartrex1", type="regular", domain=[i])
    artrex1 = Equation(cont, name="artrex1", type="regular", domain=[i])
    nartrex2 = Equation(cont, name="nartrex2", type="regular", domain=[i])
    artrex2 = Equation(cont, name="artrex2", type="regular", domain=[i])
    material = Equation(cont, name="material", type="regular", domain=[m])
    feed = Equation(cont, name="feed", type="regular")
    nmatlog = Equation(cont, name="nmatlog", type="regular", domain=[i])
    matlog = Equation(cont, name="matlog", type="regular", domain=[i])
    duty = Equation(cont, name="duty", type="regular", domain=[i])
    rebcon = Equation(cont, name="rebcon", type="regular", domain=[i, j])
    conheat = Equation(cont, name="conheat", type="regular", domain=[i])
    rebheat = Equation(cont, name="rebheat", type="regular", domain=[j])
    dtminlp = Equation(cont, name="dtminlp", type="regular", domain=[j])
    ndtminc = Equation(cont, name="ndtminc", type="regular", domain=[i])
    dtminc = Equation(cont, name="dtminc", type="regular", domain=[i])
    trtcdef = Equation(cont, name="trtcdef", type="regular", domain=[i, j])
    ndtmincr = Equation(cont, name="ndtmincr", type="regular", domain=[i, j])
    ndtminex = Equation(cont, name="ndtminex", type="regular", domain=[j])
    nhxclog = Equation(cont, name="nhxclog", type="regular", domain=[i, j])
    nhxhulog = Equation(cont, name="nhxhulog", type="regular", domain=[hu, j])
    nhxculog = Equation(cont, name="nhxculog", type="regular", domain=[i, cu])
    nqcqrlog = Equation(cont, name="nqcqrlog", type="regular", domain=[i])
    dtmincr = Equation(cont, name="dtmincr", type="regular", domain=[i, j])
    dtminex = Equation(cont, name="dtminex", type="regular", domain=[j])
    hxclog = Equation(cont, name="hxclog", type="regular", domain=[i, j])
    hxhulog = Equation(cont, name="hxhulog", type="regular", domain=[hu, j])
    hxculog = Equation(cont, name="hxculog", type="regular", domain=[i, cu])
    qcqrlog = Equation(cont, name="qcqrlog", type="regular", domain=[i])

    # these are the pure binary constraints of the minlp
    sequen = Equation(cont, name="sequen", type="regular", domain=[m])
    lead = Equation(cont, name="lead", type="regular")
    limutil = Equation(cont, name="limutil", type="regular", domain=[j])
    hidirect = Equation(cont, name="hidirect", type="regular", domain=[i, j])
    heat = Equation(cont, name="heat", type="regular", domain=[i])
    cuts = Equation(cont, name="cuts", type="regular", domain=[km])

    # =====================================================================
    # equations for nlp subproblems
    # note that some equations are duplicated in structure but
    # given different names in the nlp and milp. these equations
    # involve both continuous and binary variables. In older
    # versions of GAMS, it was not permissible to pose nonlinear
    # models with discrete variables present, even when their values
    # were held fixed (rmidnlp). This required two forms of the equation
    # two be declared: one with the discrete variables present (milp)
    # and one with binary variables replaced by parameters that have
    # been assigned the current levels of their associated binary
    # variables (nlp). These equations start with the letter "n"
    # in the nlp subproblems.
    # =====================================================================
    #                         capital costs

    nlpobj.definition = (
        zoau
        == alpha
        * (
            Sum(i, fc[i] * ycolp[i] + vc[i] * (tc[i] - tcmin[i]) * f[i])
            + Sum(
                Domain(i, j).where[zcrhx[i, j]],
                fchx * yhxp[i, j]
                + (vchx / htc) * (qcr[i, j] / (tc[i] - tr[j] + 1 - ycolp[i])),
            )
            + Sum(
                [i, cu],
                fchx * ycup[i, cu]
                + (vchx / htc) * (qcu[i, cu] / (lmtd[i] + 1 - ycolp[i])),
            )
            + Sum(
                [hu, j],
                fchx * yhup[hu, j]
                + (vchx / htc) * (qhu[hu, j] / (thu[hu] - tr[j])),
            )
        )
        # operating costs
        + beta
        * (
            (costcw * Sum([i, cu], qcu[i, cu]))
            + Sum([hu, j], costhu[hu] * qhu[hu, j])
        )
    )

    lmtdsn[i] = (
        lmtd[i]
        - (2 / 3) * gams_math.sqrt((tc[i] - tcin) * (tc[i] - tcout))
        - (1 / 6) * ((tc[i] - tcin) + (tc[i] - tcout))
        - (sl1[i] - sl2[i])
        == 0
    )

    nartrex1[i] = s1[i] + s2[i] + sl1[i] - u * (1 - ycolp[i]) <= 0

    nartrex2[i] = s3[i] + s4[i] + sl2[i] - u * (1 - ycolp[i]) <= 0

    ntempset[i] = (
        tc[i] + lmtd[i] + Sum(j.where[zcr[i, j]], tr[j]) - u * ycolp[i] <= 0
    )

    material[m] = (
        Sum(i.where[pm[i, m]], spltfrc[i, m] * f[i])
        - Sum(i.where[fm[i, m]], f[i])
        == 0
    )

    feed.definition = Sum(i.where[zlead[i]], f[i]) == totflow

    duty[i] = (
        qc[i]
        - (kf[i, "a"] + kf[i, "b"] * (tc[i] - tcmin[i]))
        - (s3[i] - s4[i])
        == 0
    )

    rebcon[i, j].where[zcr[i, j]] = qr[j] - qc[i] == 0

    conheat[i] = qc[i] == Sum(j.where[zcrhx[i, j]], qcr[i, j]) + Sum(
        cu, qcu[i, cu]
    )

    rebheat[j] = qr[j] == Sum(i.where[zcrhx[i, j]], qcr[i, j]) + Sum(
        hu, qhu[hu, j]
    )

    trtcdef[i, j].where[zcr[i, j]] = (
        tr[j]
        - (af[i, "a"] + af[i, "b"] * (tc[i] - tcmin[i]))
        - (s1[i] - s2[i])
        == 0
    )

    nmatlog[i] = f[i] - u * ycolp[i] <= 0

    ndtminc[i] = (tcmin[i] - tc[i] - u * (1 - ycolp[i])) <= 0

    dtminlp[j] = dtmin - (thu["lp"] - tr[j]) <= 0

    ndtmincr[i, j].where[zcrhx[i, j]] = (
        tr[j] - tc[i] - u * (1 - yhxp[i, j]) + dtmin <= 0
    )

    ndtminex[j] = dtmin - (thu["ex"] - tr[j]) - u * (1 - yhup["ex", j]) <= 0

    nhxclog[i, j].where[zcrhx[i, j]] = qcr[i, j] <= u * yhxp[i, j]

    nhxhulog[hu, j] = qhu[hu, j] <= u * yhup[hu, j]

    nhxculog[i, cu] = qcu[i, cu] <= u * ycup[i, cu]

    nqcqrlog[i] = qc[i] + Sum(j.where[zcr[i, j]], qr[j]) - u * ycolp[i] <= 0

    nlpsub = Model(
        cont,
        name="nlpsub",
        equations=[
            nlpobj,
            lmtdsn,
            nartrex1,
            nartrex2,
            ntempset,
            material,
            feed,
            nmatlog,
            duty,
            rebcon,
            conheat,
            rebheat,
            ndtminc,
            dtminlp,
            trtcdef,
            ndtmincr,
            ndtminex,
            nhxclog,
            nhxhulog,
            nhxculog,
            nqcqrlog,
        ],
        problem="nlp",
        sense="min",
        objective=zoau,
    )

    # ======================================================================
    # Define equations for milp master problems
    # Note: the nonlinear parts of the objective function related
    #       to heat exchanger area have been broken out into separate
    #       constraints to perform their linearizations, only a
    #       contribution term appears in the linearized objective
    #       function milpcon.
    # ======================================================================
    milpcon[k] = zoal >= alpha * (
        Sum(i, fc[i] * ycol[i])
        + fchx
        * (
            Sum(Domain(i, j).where[zcrhx[i, j]], yhx[i, j])
            + Sum([hu, j], yhu[hu, j])
            + Sum([i, cu], ycu[i, cu])
        )
        + Sum(
            i,
            (
                vc[i]
                * (
                    (tck[i, k] - tcmin[i]) * (f[i] - fk[i, k])
                    + fk[i, k] * (tc[i] - tcmin[i])
                )
            ),
        )
        + (vchx / htc) * (vqcr[k] + vqhu[k] + vqcu[k])
    ) + beta * (
        (costcw * Sum([i, cu], qcu[i, cu]))
        + Sum([hu, j], costhu[hu] * qhu[hu, j])
    )

    # ==========================================================================
    # these are the linearized contributions to the objective related
    # to heat exchange.  the appearance of the binary variable storage
    # parameters in the denominator of some of the expressions is done
    # to prevent division by zero during model generation for linearizations
    # done at points where the temperatures were set to zero for unused
    # columns.  the numerator is zero then also and no error is introduced.
    # ==========================================================================
    evqcr[k] = vqcr[k] == Sum(
        Domain(i, j).where[zcrhx[i, j]],
        (
            (qcrk[i, j, k] / (tck[i, k] - trk[j, k] + 1 - ycolk[i, k]))
            + (
                (1 / (tck[i, k] - trk[j, k] + 1 - ycolk[i, k]))
                * (qcr[i, j] - qcrk[i, j, k])
            )
            * ycolk[i, k]
            + (
                (
                    qcrk[i, j, k]
                    / (sqr(tck[i, k] - trk[j, k]) + 1 - ycolk[i, k])
                )
                * ((tr[j] - trk[j, k]) - (tc[i] - tck[i, k]))
            )
        ),
    )

    evqhu[k] = vqhu[k] == Sum(
        [hu, j],
        (
            (qhuk[hu, j, k] / (thu[hu] - trk[j, k]))
            + ((1 / (thu[hu] - trk[j, k])) * (qhu[hu, j] - qhuk[hu, j, k]))
            * Sum(i.where[zcr[i, j]], ycolk[i, k])
            + (
                (qhuk[hu, j, k] / sqr(thu[hu] - trk[j, k]))
                * (tr[j] - trk[j, k])
            )
        ),
    )

    evqcu[k] = vqcu[k] == Sum(
        [i, cu],
        (
            (qcuk[i, cu, k] / (lmtdk[i, k] + 1 - ycolk[i, k]))
            + (
                (1 / (lmtdk[i, k] + 1 - ycolk[i, k]))
                * (qcu[i, cu] - qcuk[i, cu, k])
            )
            * ycolk[i, k]
            - (
                (qcuk[i, cu, k] / (sqr(lmtdk[i, k]) + 1 - ycolk[i, k]))
                * (lmtd[i] - lmtdk[i, k])
            )
        ),
    )

    lmtdsm[i, k] = (
        lmtdmar[i, k]
        * (
            lmtd[i]
            - (2 / 3)
            * gams_math.sqrt((tck[i, k] - tcin) * (tck[i, k] - tcout))
            - (1 / 6) * ((tck[i, k] - tcin) + (tck[i, k] - tcout))
            - (
                (1 / 3)
                * (
                    (
                        (2 * tck[i, k] - (tcin + tcout))
                        / gams_math.sqrt(
                            sqr(tck[i, k])
                            - (tcin + tcout) * tck[i, k]
                            + (tcin * tcout)
                        )
                    )
                    + 1
                )
            )
            * (tc[i] - tck[i, k])
            - (sl1[i] - sl2[i])
        )
        <= 0
    )

    artrex1[i] = s1[i] + s2[i] + sl1[i] - u * (1 - ycol[i]) <= 0

    artrex2[i] = s3[i] + s4[i] + sl2[i] - u * (1 - ycol[i]) <= 0

    tempset[i] = (
        tc[i] + lmtd[i] + Sum(j.where[zcr[i, j]], tr[j]) - u * ycol[i] <= 0
    )

    matlog[i] = f[i] - u * ycol[i] <= 0

    dtminc[i] = (tcmin[i] - tc[i] - u * (1 - ycol[i])) <= 0

    dtmincr[i, j].where[zcrhx[i, j]] = (
        tr[j] - tc[i] - u * (1 - yhx[i, j]) + dtmin <= 0
    )

    dtminex[j] = dtmin - (thu["ex"] - tr[j]) - u * (1 - yhu["ex", j]) <= 0

    hxclog[i, j].where[zcrhx[i, j]] = qcr[i, j] <= u * yhx[i, j]

    hxhulog[hu, j] = qhu[hu, j] <= u * yhu[hu, j]

    hxculog[i, cu] = qcu[i, cu] <= u * ycu[i, cu]

    qcqrlog[i] = qc[i] + Sum(j.where[zcr[i, j]], qr[j]) - u * ycol[i] <= 0

    # pure binary constraints
    # material balances determine sequence
    sequen[m] = (
        Sum(i.where[pm[i, m]], ycol[i]) - Sum(i.where[fm[i, m]], ycol[i]) == 0
    )

    # select 1 sequence
    lead.definition = Sum(i.where[zlead[i]], ycol[i]) == 1

    # limit choice of hot utility to 1
    limutil[j] = Sum(hu, yhu[hu, j]) <= 1

    # only one of the mutual heat integration binaries can be 1
    hidirect[i, j].where[zlim[i, j]] = (
        yhx[i, j]
        + Sum(
            Domain(ip, jp).where[(Ord(ip) == Ord(j)) & (Ord(jp) == Ord(i))],
            yhx[ip, jp],
        )
        <= 1
    )

    # if a column doesn't exist then all binary variables associated
    # with it must also be set to zero
    heat[i] = (
        Sum(
            j.where[zcrhx[i, j]],
            yhx[i, j]
            + Sum(
                Domain(ip, jp).where[
                    (Ord(ip) == Ord(j)) & (Ord(jp) == Ord(i))
                ],
                yhx[ip, jp],
            ),
        )
        + Sum([hu, j], yhu[hu, j].where[zcr[i, j]])
        + Sum(cu, ycu[i, cu])
        - uint * ycol[i]
        <= 0
    )

    # integer cuts
    cuts[k] = (
        Sum(i, gams_math.sign(ycolk[i, k] - 0.5) * ycol[i])
        + Sum(
            Domain(i, j).where[zcrhx[i, j]],
            gams_math.sign(yhxk[i, j, k] - 0.5) * yhx[i, j],
        )
        + Sum([hu, j], gams_math.sign(yhuk[hu, j, k] - 0.5) * yhu[hu, j])
        + Sum([i, cu], gams_math.sign(ycuk[i, cu, k] - 0.5) * ycu[i, cu])
        <= Sum(i, ycolk[i, k])
        + Sum(Domain(i, j).where[zcrhx[i, j]], yhxk[i, j, k])
        + Sum([hu, j], yhuk[hu, j, k])
        + Sum([i, cu], ycuk[i, cu, k])
        - 1
    )

    # ======================================================================
    # declare the milp master problem
    # ======================================================================
    master = Model(
        cont,
        name="master",
        equations=[
            milpcon,
            evqcr,
            evqhu,
            evqcu,
            lmtdsm,
            artrex1,
            artrex2,
            tempset,
            material,
            feed,
            matlog,
            duty,
            rebcon,
            conheat,
            rebheat,
            dtminc,
            dtminlp,
            trtcdef,
            dtmincr,
            dtminex,
            hxclog,
            hxhulog,
            hxculog,
            qcqrlog,
            sequen,
            lead,
            limutil,
            hidirect,
            heat,
            cuts,
        ],
        problem="mip",
        sense="min",
        objective=zoal,
    )

    # =====================================================================
    # all declarations made, start algorithmic procedures

    # initialize the optimal storage parameters to 1st guess
    # =====================================================================
    yhxopt[i, j] = yhxp[i, j]
    yhuopt[hu, j] = yhup[hu, j]
    ycuopt[i, cu] = ycup[i, cu]
    ycolopt[i] = ycolp[i]
    kopt.assignment = 1

    # ======================================================================
    # assign the initial configuration to the binary proposal parameter
    # ======================================================================
    kiter["k-1"] = Number(1)

    yhxk[i, j, kiter] = yhxp[i, j]
    yhuk[hu, j, kiter] = yhup[hu, j]
    ycuk[i, cu, kiter] = ycup[i, cu]
    ycolk[i, kiter] = ycolp[i]
    yhx.l[i, j] = yhxp[i, j]
    yhu.l[hu, j] = yhup[hu, j]
    ycu.l[i, cu] = ycup[i, cu]
    ycol.l[i] = ycolp[i]

    # set an arbitrary initial lower bound
    zoal.l.assignment = -10e6

    # ======================================================================
    # give the continuous variables a starting point for 1st nlp
    # ======================================================================
    tr.l["r-1"] = 410
    tc.l["c-1"] = 390
    tc.l["c-3"] = 360
    tr.l["r-3"] = 380
    tc.l["c-2"] = 0
    tr.l["r-2"] = 0
    tc.l["c-4"] = 0
    tr.l["r-4"] = 0
    f.l["c-1"] = totflow
    lmtd.l["c-1"] = 75
    lmtd.l["c-3"] = 25
    lmtd.l["c-2"] = 0
    lmtd.l["c-4"] = 0
    qr.l["r-2"] = 0
    qc.l["c-2"] = 0
    qr.l["r-4"] = 0
    qc.l["c-4"] = 0

    # ======================================================================
    # add bounds on tc. A sqrt in equation lmtdsn is defined for tc > tcout
    # and for tc < tcin. The relevant interval is determined for each
    # element of tc based on the initial values given above.
    # ======================================================================
    tc.lo["c-1"] = tcout + 1
    tc.up["c-2"] = tcin - 1
    tc.lo["c-3"] = tcout + 1
    tc.up["c-4"] = tcin - 1

    # ======================================================================
    # bound the reboiler temperatures by their maximum allowable
    # ======================================================================
    tr.up[j] = trmax[j]

    # ======================================================================
    # initialize the dynamic sets for algorithm control
    # ======================================================================
    k[km] = Number(0)
    kiter[km] = Number(0)
    kdynmax[km] = Number(1)

    # ======================================================================
    # major driving loop of algorithm
    # ======================================================================

    for idx, _ in enumerate(kdynmax.toList()):
        #  update the dynamic iteration sets
        # -set kiter to contain only the current iteration element
        # -add to k the current iteration element
        kiter[km] = Number(1).where[Ord(km) == idx + 1]
        k[kiter] = Number(1)

        #  store the current binary combination
        yhxk[i, j, kiter] = yhx.l[i, j]
        yhuk[hu, j, kiter] = yhu.l[hu, j]
        ycuk[i, cu, kiter] = ycu.l[i, cu]
        ycolk[i, kiter] = ycol.l[i]

        #  set the current combination parameters that appear in the nlp constraints
        yhxp[i, j] = yhx.l[i, j]
        yhup[hu, j] = yhu.l[hu, j]
        ycup[i, cu] = ycu.l[i, cu]
        ycolp[i] = ycol.l[i]
        zoal.lo.assignment = zoal.l

        # ======================================================================
        # the current levels of the lmtds are moved away from zero
        # to prevent evaluation errors in the next nlp subproblem
        # ======================================================================
        lmtd.l[i] = lmtd.l[i] + 1

        cont.addOptions({"resLim": 15})
        #  solve the nlp subproblem
        nlpsub.solve()

        #  resolve with Conopt to get marginals for lmtdsn, if not provided by used NLP solver *****
        if nlpsub.marginals == 0:
            cont.addOptions({"nlp": "conopt"})
            nlpsub.solve()
            cont.addOptions({"nlp": "%system.nlp%"})

        # ======================================================================
        # update the optimal solution storage parameters if new nlp
        # objective function value is less than the incumbent
        # ======================================================================
        if zoau.l < zoaup:
            yhxopt[i, j] = yhx.l[i, j]
            yhuopt[hu, j] = yhu.l[hu, j]
            ycuopt[i, cu] = ycu.l[i, cu]
            ycolopt[i] = ycol.l[i]
            kopt.assignment = idx + 1

        # ======================================================================
        # assign the solution levels of the variables that appear in the
        # nonlinear equations to their corresponding storage parameters
        # ======================================================================
        fk[i, kiter] = f.l[i]
        qrk[j, kiter] = qr.l[j]
        qck[i, kiter] = qc.l[i]
        qcrk[i, j, kiter] = qcr.l[i, j]
        qhuk[hu, j, kiter] = qhu.l[hu, j]
        qcuk[i, cu, kiter] = qcu.l[i, cu]
        tck[i, kiter] = tc.l[i]
        trk[j, kiter] = tr.l[j]
        lmtdk[i, kiter] = lmtd.l[i]

        # ======================================================================
        # assign the sign of marginal values of the nonlinear equalities
        # to the storage parameter lmtdmar
        # ======================================================================
        lmtdmar[i, kiter] = (
            Number(-1)
            * gams_math.sign(lmtdsn.m[i]).where[
                lmtdsn.m[i] != float_info.epsilon
            ]
        )

        # ======================================================================
        # store the smallest nlp objective value for upper bound on master
        # ======================================================================
        zoaup.assignment = gams_math.Min(zoaup, zoau.l)
        zoal.up.assignment = zoaup
        #  protect against numerical errors introduced by the solver
        zoal.lo.assignment = gams_math.Min(zoal.lo, zoal.up)

        #  now solve the milp master problem
        master.solve()

        print(
            "new binary combination: \n\n",
            f"ycol: {ycol.toDict()}\n\n",
            f"yhx: {yhx.toDict()}\n\n",
            f"yhu: {yhu.toDict()}\n\n",
            f"ycu: {ycu.toDict()}\n\n",
        )

        # ======================================================================
        # check stopping criterion:
        # master problem integer infeasible
        # ======================================================================
        if master.status in [4.0, 10.0, 19.0]:
            kdynmax[km] = Number(0)
            print(
                "stopping criterion met: \n\n",
                f"zoaup: {round(zoaup.toValue(),3)}\n\n",
                f"yhxopt: {yhxopt.toDict()}\n\n",
                f"yhuopt: {yhuopt.toDict()}\n\n",
                f"ycuopt: {ycuopt.toDict()}\n\n",
                f"ycolopt: {ycolopt.toDict()}\n\n",
                f"kopt: {kopt.toValue()}\n\n",
            )
            break


if __name__ == "__main__":
    main()

tests/integration/models/minlphi.py
GAMS-dev/gamspy
"""
Heat Integrated Distillation Sequences - MINLP (MINLPHIX)

This is a direct MINLP formulation of the model MINLPHI.


Morari, M, and Grossmann, I E, Eds, Chemical Engineering
Optimization Models with GAMS. Computer Aids for Chemical
Engineering Corporation, 1991.

Floudas, C A, and Paules IV, G E, A Mixed-Integer Nonlinear
Programming Formulation for the Synthesis of Heat Integrated
Distillation Sequence. Computers and Chemical Engineering 12,
6 (1988), 531-546.


This formulation provides the Optimal Heat Integrated
Distillation Sequence with Pressure as a continuous variable
for a three component separation.

           Components:     a == Hexane
                           b == Benzene
                           c == Heptane

total feed to superstructure == 396 kgmol/hr

multicomponent feed composition: a = 0.80
                                 b = 0.10
                                 c = 0.10


A Superstructure of the form ...

                        _______               _______
                       _|_    |              _|_    |
                      /   \  ( )            /   \  ( )
                      |   |___|__ A         |   |___|___ B
                      |   |                 |   |
            |---------| 1 |                 | 3 |
            |         |   |       ----------|   |
            |         |   |       |         |   |
            |         |   |_______|         |   |
            |         \___/  |  BC          \___/_______ C
     F      |           |   ( )               |     |
   -------->|           |____|                |----( )
   (ABC)    |
            |           _______               _______
            |          _|_    |              _|_    |
            |         /   \  ( )            /   \  ( )
            |         |   |___| AB          |   |___|___ A
            |         |   |   |_____________|   |
            |---------| 2 |                 | 4 |
                      |   |                 |   |
                      |   |                 |   |
                      |   |______ C         |   |_______ B
                      \___/  |              \___/   |
                        |   ( )               |    ( )
                        |____|                |_____|


is used with binary variables representing:
   a_  the existence of columns in the sequence.
   b_  the selection of heat exchangers for heat integration.
   c_  the selection of hot and cold utilities.


Associated Reference:

"A Mixed-Integer Nonlinear Programming formulation for the
 synthesis of Heat-Integrated Distillation Sequences"

 C.A. Floudas and G.E. Paules IV,  1988.
 Computers and Chemical Engineering vol. 12 no. 6 pp. 531-546

Keywords: mixed integer nonlinear programming, chemical engineering,
distillation
          sequences, heat integrated distillation
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import sqrt


def main():
    cont = Container(delayed_execution=True)

    # Set
    i = Set(cont, name="i", records=[f"c-{i}" for i in range(1, 5)])
    j = Set(cont, name="j", records=[f"r-{i}" for i in range(1, 5)])
    hu = Set(cont, name="hu", records=["lp", "ex"])
    cu = Set(cont, name="cu", records=["cw"])
    n = Set(cont, name="n", records=["a", "b"])
    m = Set(cont, name="m", records=["ab", "bc"])
    pm = Set(
        cont, name="pm", domain=[i, m], records=[("c-1", "bc"), ("c-2", "ab")]
    )
    fm = Set(
        cont, name="fm", domain=[i, m], records=[("c-3", "bc"), ("c-4", "ab")]
    )

    ip = Alias(cont, name="ip", alias_with=i)
    jp = Alias(cont, name="jp", alias_with=j)

    # ====================================================================
    # Definition of "z" sets for conditional control of model
    # used to map permissible matches between condensers and reboilers
    # and the position of columns in the superstructure
    # =====================================================================

    # Set
    zlead = Set(cont, name="zlead", domain=[i], records=["c-1", "c-2"])
    zcrhx = Set(
        cont,
        name="zcrhx",
        domain=[i, j],
        records=[
            ("c-1", "r-3"),
            ("c-2", "r-4"),
            ("c-3", "r-1"),
            ("c-4", "r-2"),
        ],
    )
    zlim = Set(cont, name="zlim", domain=[i, j])
    zcr = Set(cont, name="zcr", domain=[i, j])

    zlim[i, j] = zcrhx[i, j] & (Ord(i) < Ord(j))
    zcr[i, j] = Ord(i) == Ord(j)

    # Parameter
    spltfrc = Parameter(
        cont,
        name="spltfrc",
        domain=[i, m],
        records=pd.DataFrame([["c-1", "bc", 0.20], ["c-2", "ab", 0.90]]),
    )

    tcmin = Parameter(
        cont,
        name="tcmin",
        domain=[i],
        records=np.array([341.92, 343.01, 353.54, 341.92]),
    )
    trmax = Parameter(cont, name="trmax", domain=[j])
    trmax[j] = 1000

    # ====================================================================
    # scaled cost coefficients for distillation column fits
    # nonlinear fixed-charge cost model
    #   cost = fc*y + vc*flow*temp
    # scaling factor = 1000
    # ====================================================================

    # Parameter
    fc = Parameter(
        cont,
        name="fc",
        domain=[i],
        records=np.array([151.125, 180.003, 4.2286, 213.42]),
    )
    vc = Parameter(
        cont,
        name="vc",
        domain=[i],
        records=np.array([0.003375, 0.000893, 0.004458, 0.003176]),
    )
    thu = Parameter(
        cont, name="thu", domain=[hu], records=np.array([421.0, 373.0])
    )

    # hot utility cost coeff - gives cost in thousands of dollars per year
    # ucost = q(10e+6 kj/hr)*costhu(hu)

    costhu = Parameter(
        cont, name="costhu", domain=[hu], records=np.array([24.908, 9.139])
    )

    kf = Parameter(
        cont,
        name="kf",
        domain=[i, n],
        records=np.array(
            [
                [32.4, 0.0225],
                [25.0, 0.0130],
                [3.76, 0.0043],
                [35.1, 0.0156],
            ]
        ),
    )

    af = Parameter(
        cont,
        name="af",
        domain=[i, n],
        records=np.array(
            [
                [9.541, 1.028],
                [12.24, 1.050],
                [8.756, 1.029],
                [9.181, 1.005],
            ]
        ),
    )

    # Scalar
    totflow = Parameter(cont, name="totflow", records=396)
    fchx = Parameter(cont, name="fchx", records=3.392)
    vchx = Parameter(cont, name="vchx", records=0.0893)
    htc = Parameter(cont, name="htc", records=0.0028)
    dtmin = Parameter(cont, name="dtmin", records=10.0)
    tcin = Parameter(cont, name="tcin", records=305.0)
    tcout = Parameter(cont, name="tcout", records=325.0)
    costcw = Parameter(cont, name="costcw", records=4.65)
    beta = Parameter(cont, name="beta", records=0.52)
    alpha = Parameter(cont, name="alpha", records=0.40)
    u = Parameter(cont, name="u", records=1500)
    uint = Parameter(cont, name="uint", records=20)

    # Variable
    zoau = Variable(cont, name="zoau", type="free")

    # Positive Variables
    f = Variable(cont, name="f", type="positive", domain=[i])
    qr = Variable(cont, name="qr", type="positive", domain=[j])
    qc = Variable(cont, name="qc", type="positive", domain=[i])
    qcr = Variable(cont, name="qcr", type="positive", domain=[i, j])
    qhu = Variable(cont, name="qhu", type="positive", domain=[hu, j])
    qcu = Variable(cont, name="qcu", type="positive", domain=[i, cu])
    tc = Variable(cont, name="tc", type="positive", domain=[i])
    tr = Variable(cont, name="tr", type="positive", domain=[j])
    lmtd = Variable(cont, name="lmtd", type="positive", domain=[i])
    sl1 = Variable(cont, name="sl1", type="positive", domain=[i])
    sl2 = Variable(cont, name="sl2", type="positive", domain=[i])
    s1 = Variable(cont, name="s1", type="positive", domain=[i])
    s2 = Variable(cont, name="s2", type="positive", domain=[i])
    s3 = Variable(cont, name="s3", type="positive", domain=[i])
    s4 = Variable(cont, name="s4", type="positive", domain=[i])

    # Binary Variable
    yhx = Variable(cont, name="yhx", type="binary", domain=[i, j])
    yhu = Variable(cont, name="yhu", type="binary", domain=[hu, j])
    ycu = Variable(cont, name="ycu", type="binary", domain=[i, cu])
    ycol = Variable(cont, name="ycol", type="binary", domain=[i])

    # Equation
    nlpobj = Equation(cont, name="nlpobj")
    tctrlo = Equation(cont, name="tctrlo", domain=[i, j])
    lmtdlo = Equation(cont, name="lmtdlo", domain=[i])
    lmtdsn = Equation(cont, name="lmtdsn", domain=[i])
    tempset = Equation(cont, name="tempset", domain=[i])
    artrex1 = Equation(cont, name="artrex1", domain=[i])
    artrex2 = Equation(cont, name="artrex2", domain=[i])
    material = Equation(cont, name="material", domain=[m])
    feed = Equation(cont, name="feed")
    matlog = Equation(cont, name="matlog", domain=[i])
    duty = Equation(cont, name="duty", domain=[i])
    rebcon = Equation(cont, name="rebcon", domain=[i, j])
    conheat = Equation(cont, name="conheat", domain=[i])
    rebheat = Equation(cont, name="rebheat", domain=[j])
    dtminlp = Equation(cont, name="dtminlp", domain=[j])
    dtminc = Equation(cont, name="dtminc", domain=[i])
    trtcdef = Equation(cont, name="trtcdef", domain=[i, j])
    dtmincr = Equation(cont, name="dtmincr", domain=[i, j])
    dtminex = Equation(cont, name="dtminex", domain=[j])
    hxclog = Equation(cont, name="hxclog", domain=[i, j])
    hxhulog = Equation(cont, name="hxhulog", domain=[hu, j])
    hxculog = Equation(cont, name="hxculog", domain=[i, cu])
    qcqrlog = Equation(cont, name="qcqrlog", domain=[i])

    # these are the pure binary constraints of the minlp
    sequen = Equation(cont, name="sequen", domain=[m])
    lead = Equation(cont, name="lead")
    limutil = Equation(cont, name="limutil", domain=[j])
    hidirect = Equation(cont, name="hidirect", domain=[i, j])
    heat = Equation(cont, name="heat", domain=[i])

    nlpobj.definition = zoau == (
        alpha
        * (
            Sum(i, fc[i] * ycol[i] + vc[i] * (tc[i] - tcmin[i]) * f[i])
            + Sum(
                zcrhx[i, j],
                fchx * yhx[i, j]
                + (vchx / htc) * (qcr[i, j] / (tc[i] - tr[j] + 1 - ycol[i])),
            )
            + Sum(
                [i, cu],
                fchx * ycu[i, cu]
                + (vchx / htc) * (qcu[i, cu] / (lmtd[i] + 1 - ycol[i])),
            )
            + Sum(
                [hu, j],
                fchx * yhu[hu, j]
                + (vchx / htc) * (qhu[hu, j] / (thu[hu] - tr[j])),
            )
        )
        + beta
        * (
            Sum([i, cu], costcw * qcu[i, cu])
            + Sum([hu, j], costhu[hu] * qhu[hu, j])
        )
    )

    # limit the denominator in the second line of the objective away from zero
    tctrlo[zcrhx[i, j]] = tc[i] - tr[j] + 1 - ycol[i] >= 1

    # lmtd and ycol from being 0 and 1 at the same time to prevent divding
    # by 0 in the objective
    lmtdlo[i] = lmtd[i] >= 2 * ycol[i]

    lmtdsn[i] = lmtd[i] == (
        (2 / 3) * sqrt((tc[i] - tcin) * (tc[i] - tcout))
        + (1 / 6) * ((tc[i] - tcin) + (tc[i] - tcout))
        + sl1[i]
        - sl2[i]
    )

    artrex1[i] = s1[i] + s2[i] + sl1[i] <= u * (1 - ycol[i])

    artrex2[i] = s3[i] + s4[i] + sl2[i] <= u * (1 - ycol[i])

    material[m] = Sum(pm[i, m], spltfrc[i, m] * f[i]) == Sum(fm[i, m], f[i])

    feed.definition = Sum(zlead[i], f[i]) == totflow

    duty[i] = (
        qc[i] == (kf[i, "a"] + kf[i, "b"] * (tc[i] - tcmin[i])) + s3[i] - s4[i]
    )

    rebcon[zcr[i, j]] = qr[j] == qc[i]

    conheat[i] = qc[i] == Sum(zcrhx[i, j], qcr[i, j]) + Sum(cu, qcu[i, cu])

    rebheat[j] = qr[j] == Sum(zcrhx[i, j], qcr[i, j]) + Sum(hu, qhu[hu, j])

    trtcdef[zcr[i, j]] = (
        tr[j] == (af[i, "a"] + af[i, "b"] * (tc[i] - tcmin[i])) + s1[i] - s2[i]
    )

    dtminlp[j] = dtmin - (thu["lp"] - tr[j]) <= 0

    dtminex[j] = dtmin - (thu["ex"] - tr[j]) - u * (1 - yhu["ex", j]) <= 0

    tempset[i] = tc[i] + lmtd[i] + Sum(zcr[i, j], tr[j]) <= u * ycol[i]

    matlog[i] = f[i] <= u * ycol[i]

    dtminc[i] = tcmin[i] - tc[i] <= u * (1 - ycol[i])

    dtmincr[zcrhx[i, j]] = tr[j] - tc[i] - u * (1 - yhx[i, j]) + dtmin <= 0

    hxclog[zcrhx[i, j]] = qcr[i, j] <= u * yhx[i, j]

    hxhulog[hu, j] = qhu[hu, j] <= u * yhu[hu, j]

    hxculog[i, cu] = qcu[i, cu] <= u * ycu[i, cu]

    qcqrlog[i] = qc[i] + Sum(j.where[zcr[i, j]], qr[j]) <= u * ycol[i]

    sequen[m] = Sum(pm[i, m], ycol[i]) == Sum(fm[i, m], ycol[i])

    lead.definition = Sum(zlead[i], ycol[i]) == 1

    limutil[j] = Sum(hu, yhu[hu, j]) <= 1

    # only one of the mutual heat integration binaries can be 1
    hidirect[zlim[i, j]] = (
        yhx[i, j]
        + Sum(
            Domain(ip, jp).where[(Ord(ip) == Ord(j)) & (Ord(jp) == Ord(i))],
            yhx[ip, jp],
        )
        <= 1
    )

    # if a column doesn't exist then all binary variables associated
    # with it must also be set to zero
    heat[i] = (
        Sum(
            zcrhx[i, j],
            yhx[i, j]
            + Sum(
                Domain(ip, jp).where[
                    (Ord(ip) == Ord(j)) & (Ord(jp) == Ord(i))
                ],
                yhx[ip, jp],
            ),
        )
        + Sum((hu, zcr[i, j]), yhu[hu, j])
        + Sum(cu, ycu[i, cu])
    ) <= uint * ycol[i]

    tc.lo["c-1"] = tcout + 1
    tc.up["c-2"] = tcin - 1
    tc.lo["c-3"] = tcout + 1
    tc.up["c-4"] = tcin - 1
    tr.up[j] = trmax[j]

    skip = Model(
        cont,
        name="skip",
        equations=cont.getEquations(),
        problem="minlp",
        sense=Sense.MIN,
        objective=zoau,
    )
    cont.addOptions({"domLim": 100})

    skip.solve()

    import math

    assert math.isclose(skip.objective_value, 316.6927, rel_tol=0.001)

    print("Best integer solution found:", skip.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/minlphix.py
GAMS-dev/gamspy
"""
Parts Supply Problem (PARTSSUPPLY)

This model is based on the ps2_f_s.358 .. ps10_s_mn.396 models by
Hideo Hashimoto, Kojun Hamada, and Nobuhiro Hosoe.

Using the following options, these models can be run:

ps2_f              : default
ps2_f_eff          : --nsupplier=1
ps2_f_inf          : --nsupplier=1 --alttheta=1
ps2_f_s            : --useic=1
ps2_s              : --useic=1
ps3_f              : --nsupplier=3
ps3_s              : --nsupplier=3  --uselicd=1
ps3_s_gic          : --nsupplier=3  --useic=1
ps3_s_mn  1st solve: --nsupplier=3  --uselicd=1
          2nd solve: --nsupplier=3  --uselicd=1 --altpi=1
          3rd solve: --nsupplier=3  --uselicd=1 --alttheta=1
ps3_s_scp 1st solve: --nsupplier=3  --alttheta=2 --modweight=1 --useic=1
          2nd solve: --nsupplier=3  --alttheta=2 --modweight=1 --uselicd=1
          --uselicu=1
ps5_s_mn           : --nsupplier=5  --uselicd=1 --nsamples=1000
ps10_s             : --nsupplier=10 --uselicd=1
ps10_s_mn          : --nsupplier=10 --uselicd=1 --nsamples=1000

Alternatively, the corresponding original model files can be found in
the GAMS model library.

Keywords: nonlinear programming, contract theory, principal-agent problem,
          adverse selection, parts supply problem
"""
import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    cont = Container(delayed_execution=True)

    # Set
    i = Set(cont, name="i", records=["1", "2"])
    t = Set(cont, name="t", records=["1"])
    j = Alias(cont, name="j", alias_with=i)

    # Parameter
    theta = Parameter(
        cont, name="theta", domain=[i], records=[[1, 0.2], [2, 0.3]]
    )
    pt = Parameter(cont, name="pt", domain=[i, t])
    p = Parameter(cont, name="p", domain=[i], records=[[1, 0.2], [2, 0.8]])
    icweight = Parameter(cont, name="icweight", domain=[i])
    ru = Parameter(cont, name="ru", records=0)

    pt[i, t] = gams_math.uniform(0, 1)
    pt[i, t] = pt[i, t] / Sum(j, pt[j, t])
    pt[i, t] = p[i]

    # Variable
    x = Variable(cont, name="x", domain=[i], type="Positive")
    b = Variable(cont, name="b", domain=[i], type="Positive")
    w = Variable(cont, name="w", domain=[i], type="Positive")
    util = Variable(cont, name="util")

    # Equation
    obj = Equation(cont, name="obj")
    rev = Equation(cont, name="rev", domain=[i])
    pc = Equation(cont, name="pc", domain=[i])
    ic = Equation(cont, name="ic", domain=[i, j])
    licd = Equation(cont, name="licd", domain=[i])
    licu = Equation(cont, name="licu", domain=[i])
    mn = Equation(cont, name="mn", domain=[i])

    obj.definition = util == Sum(i, p[i] * (b[i] - w[i]))
    rev[i] = b[i] == gams_math.sqrt(x[i])
    pc[i] = w[i] - theta[i] * x[i] >= ru
    ic[i, j] = w[i] - icweight[i] * x[i] >= w[j] - icweight[i] * x[j]
    licd[i].where[Ord(i) < Card(i)] = (
        w[i] - icweight[i] * x[i]
        >= w[i.lead(1, "linear")] - icweight[i] * x[i.lead(1, "linear")]
    )
    licu[i].where[Ord(i) > 1] = (
        w[i] - icweight[i] * x[i]
        >= w[i.lag(1, "linear")] - icweight[i] * x[i.lag(1, "linear")]
    )
    mn[i].where[Ord(i) < Card(i)] = x[i] >= x[i.lead(1, "linear")]

    x.lo[i] = 0.0001

    m = Model(
        cont,
        name="m",
        equations=[obj, rev, pc, licu],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=util,
    )
    m_mn = Model(
        cont,
        name="m_mn",
        equations=[obj, rev, pc, licu, mn],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=util,
    )

    cont.addOptions({"limRow": 0, "limCol": 0})

    for iter, _ in t.records.itertuples(index=False):
        p[i] = pt[i, iter]
        icweight[i] = theta[i]
        m.solve()
        m_mn.solve()
        cont.addOptions({"solPrint": "off"})

    import math

    assert math.isclose(m_mn.objective_value, 0.9167, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/partssupply.py
GAMS-dev/gamspy
"""
Floudas, C.A., Pardalos, P.M., et al. Handbook of test problems in local and global
optimization. Kluwer Academic Publishers, Dordrecht, 1999
Chapter 8. Section: Phase and Chemical Equilibrium Problems-Equations of State
Test Problem 1, pp. 180-181.

Van der Waals equation, Tangent Plane distance minimization
Ternary System
"""
import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # SET #
    i = Set(m, name="i", records=["1", "2", "3"], description="components")

    # ALIAS #
    j = Alias(m, name="j", alias_with=i)

    # PARAMETERS #
    feedmf = Parameter(m, name="feedmf", domain=[i])
    feedz = Parameter(m, name="feedz")
    feedfc = Parameter(m, name="feedfc", domain=[i])
    b = Parameter(m, name="b", domain=[i])
    a = Parameter(m, name="a", domain=[i, j])

    feedmf["1"] = 0.83
    feedmf["2"] = 0.085
    feedmf["3"] = 0.085

    feedz.assignment = 0.55716

    feedfc["1"] = -0.244654
    feedfc["2"] = -1.33572
    feedfc["3"] = -0.457869

    b["1"] = 0.14998
    b["2"] = 0.14998
    b["3"] = 0.14998

    a["1", "1"] = 0.37943
    a["1", "2"] = 0.75885
    a["1", "3"] = 0.48991
    a["2", "1"] = 0.75885
    a["2", "2"] = 0.88360
    a["2", "3"] = 0.23612
    a["3", "1"] = 0.48991
    a["3", "2"] = 0.23612
    a["3", "3"] = 0.63263

    # VARIABLES #
    dist = Variable(m, name="dist")
    x = Variable(m, name="x", domain=[i])
    z = Variable(m, name="z")
    amix = Variable(m, name="amix")
    bmix = Variable(m, name="bmix")

    # EQUATIONS #
    obj = Equation(m, name="obj", type="regular")
    eos = Equation(m, name="eos", type="regular")
    defa = Equation(m, name="defa", type="regular")
    defb = Equation(m, name="defb", type="regular")
    molesum = Equation(m, name="molesum", type="regular")

    # Objective function to be minimized:
    obj.definition = dist == (
        Sum(i, x[i] * gams_math.log(x[i]))
        + bmix / (z - bmix)
        - gams_math.log(z - bmix)
        - 2.0 * amix / z
        - Sum(i, x[i] * (gams_math.log(feedmf[i]) + feedfc[i]))
    )

    # Constraints:
    eos.definition = (
        gams_math.power(z, 3)
        - (bmix + 1) * gams_math.power(z, 2)
        + amix * z
        - amix * bmix
        == 0
    )

    defa.definition = amix - Sum(i, Sum(j, a[i, j] * x[i] * x[j])) == 0

    defb.definition = bmix - Sum(i, b[i] * x[i]) == 0

    molesum.definition = Sum(i, x[i]) == 1.0

    # Simple Bounds of variables
    z.lo.assignment = 0.001
    x.lo["1"] = 0.001
    x.lo["2"] = 0.001
    x.lo["3"] = 0.001

    phase = Model(
        m,
        name="phase",
        equations=m.getEquations(),
        problem="nlp",
        sense="min",
        objective=dist,
    )
    phase.solve()

    print("x:  \n", x.toDict(), "\n")
    print("z:  \n", round(z.toValue(), 4), "\n")

    # End phase


if __name__ == "__main__":
    main()

tests/integration/models/phase.py
GAMS-dev/gamspy
"""
Portfolio Optimization for Electric Utilities (POUTIL)

We discuss a portfolio optimization problem occurring in the energy
market. Energy distributing public services have to decide how much
of the requested energy demand has to be produced in their own power
plant, and which complementary amount has to be bought from the spot
market and from load following contracts.

This problem is formulated as a mixed-integer linear programming
problem and implemented in GAMS. The formulation is applied to real data
of a German electricity distributor.

Most equations contain the reference number of the formula in the
publication.


Rebennack, S, Kallrath, J, and Pardalos, P M, Energy Portfolio
Optimization for Electric Utilities: Case Study for Germany. In
Bj�rndal, E, Bj�rndal, M, Pardalos, P.M. and R�nnqvist, M Eds,.
Springer, pp. 221-246, 2010.

Keywords: mixed integer linear programming, energy economics, portfolio
optimization,
          unit commitment, economic dispatch, power plant control,
          day-ahead market
"""
import numpy as np

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable


def main():
    cont = Container(delayed_execution=True)

    power_forecast_recs = np.array(
        [
            287,
            275,
            262,
            250,
            255,
            260,
            265,
            270,
            267,
            265,
            262,
            260,
            262,
            265,
            267,
            270,
            277,
            285,
            292,
            300,
            310,
            320,
            330,
            340,
            357,
            375,
            392,
            410,
            405,
            400,
            395,
            390,
            400,
            410,
            420,
            430,
            428,
            427,
            426,
            425,
            432,
            440,
            447,
            455,
            458,
            462,
            466,
            470,
            466,
            462,
            458,
            455,
            446,
            437,
            428,
            420,
            416,
            412,
            408,
            405,
            396,
            387,
            378,
            370,
            375,
            380,
            385,
            390,
            383,
            377,
            371,
            365,
            368,
            372,
            376,
            380,
            386,
            392,
            398,
            405,
            408,
            412,
            416,
            420,
            413,
            407,
            401,
            395,
            386,
            377,
            368,
            360,
            345,
            330,
            315,
            300,
        ]
    )

    # Energy
    t = Set(
        cont,
        name="t",
        records=[f"t{i}" for i in range(1, 97)],
        description="time slices (quarter-hour)",
    )

    # Parameters
    PowerForecast = Parameter(
        cont,
        name="PowerForecast",
        domain=[t],
        records=power_forecast_recs,
        description="electric power forecast",
    )

    # Power Plant (PP)
    # Scalars
    cPPvar = Parameter(
        cont,
        name="cPPvar",
        records=25,
        description="variable cost of power plant [euro / MWh]",
    )
    pPPMax = Parameter(
        cont,
        name="pPPMax",
        records=300,
        description="maximal capacity of power plant      [MW]",
    )

    # Sets
    m = Set(
        cont,
        name="m",
        records=[f"m{i}" for i in range(1, 9)],
        description="'stage of the power plant",
    )
    iS = Set(
        cont,
        name="iS",
        records=[f"iS{i}" for i in range(9)],
        description="interval for constant PP operation",
    )
    iI = Set(
        cont,
        name="iI",
        records=[f"iI{i}" for i in range(17)],
        description="length of idle time period",
    )

    # Spot Market (SM)
    # Scalars
    cBL = Parameter(
        cont,
        name="cBL",
        records=32,
        description="cost for one base load contract [euro / MWh]",
    )
    cPL = Parameter(
        cont,
        name="cPL",
        records=41,
        description="cost for one peak load contract [euro / MWh]",
    )

    # Parameter
    IPL = Parameter(
        cont,
        name="IPL",
        domain=[t],
        description="indicator function for peak load contracts",
    )
    IPL[t] = (Ord(t) >= 33) & (Ord(t) <= 80)

    # Load following Contract (LFC)
    # Scalars
    pLFCref = Parameter(
        cont,
        name="pLFCref",
        records=400,
        description="power reference level for the LFC",
    )

    b = Set(
        cont,
        name="b",
        records=[f"b{i}" for i in range(1, 4)],
        description="support points of the zone prices",
    )

    # Parameters
    eLFCbY = Parameter(
        cont,
        name="eLFCbY",
        domain=[b],
        records=np.array([54750, 182500, 9000000]),
        description="amount of energy at support point b",
    )
    cLFCvar = Parameter(
        cont,
        name="cLFCvar",
        domain=[b],
        records=np.array([80.0, 65.0, 52.0]),
        description="specific energy price in segment b",
    )
    eLFCb = Parameter(
        cont,
        name="eLFCb",
        domain=[b],
        description="daily border of energy volumes for LFC",
    )
    cLFCs = Parameter(
        cont,
        name="cLFCs",
        domain=[b],
        description="accumulated cost for LFC up to segment b",
    )

    # calculate the daily borders of the energy volumes for the zones
    eLFCb[b] = eLFCbY[b] / 365

    # calculate the accumulated cost
    cLFCs["b1"] = 0
    cLFCs["b2"] = cLFCvar["b1"] * eLFCb["b1"]
    cLFCs[b].where[Ord(b) > 2] = cLFCs[b.lag(1)] + cLFCvar[b.lag(1)] * (
        eLFCb[b.lag(1)] - eLFCb[b.lag(2)]
    )

    # Variables
    c = Variable(cont, name="c", type="free", description="total cost")
    cPP = Variable(
        cont, name="cPP", type="positive", description="cost of PP usage"
    )
    pPP = Variable(
        cont,
        name="pPP",
        type="positive",
        domain=[t],
        description="power withdrawn from power plant",
    )
    delta = Variable(
        cont,
        name="delta",
        type="binary",
        domain=[m, t],
        description="indicate if the PP is in stage m at time t",
    )
    chiS = Variable(
        cont,
        name="chiS",
        type="positive",
        domain=[t],
        description="indicate if there is a PP stage change",
    )
    chiI = Variable(
        cont,
        name="chiI",
        type="positive",
        domain=[t],
        description="indicate if the PP left the idle stage",
    )
    cSM = Variable(
        cont, name="cSM", type="positive", description="cost of energy from SM"
    )
    pSM = Variable(
        cont,
        name="pSM",
        type="positive",
        domain=[t],
        description="power from the spot market",
    )
    alpha = Variable(
        cont,
        name="alpha",
        type="integer",
        description="quantity of base load contracts",
    )
    beta = Variable(
        cont,
        name="beta",
        type="integer",
        description="quantity of peak load contracts",
    )
    cLFC = Variable(
        cont,
        name="cLFC",
        type="positive",
        description="cost of LFC which is the enery rate",
    )
    eLFCtot = Variable(
        cont,
        name="eLFCtot",
        type="positive",
        description="total energy amount of LFC",
    )
    eLFCs = Variable(
        cont,
        name="eLFCs",
        type="positive",
        domain=[b],
        description="energy from LFC in segment b",
    )
    pLFC = Variable(
        cont,
        name="pLFC",
        type="positive",
        domain=[t],
        description="power from the LFC",
    )
    mu = Variable(
        cont,
        name="mu",
        type="binary",
        domain=[b],
        description="indicator for segment b (for zone prices)",
    )

    alpha.up.assignment = Smax(t, PowerForecast[t])
    beta.up.assignment = alpha.up
    pLFC.up[t] = pLFCref

    # Equations
    obj = Equation(cont, name="obj", description="objective function")
    demand = Equation(
        cont,
        name="demand",
        domain=[t],
        description="demand constraint for energy forcast",
    )
    PPcost = Equation(cont, name="PPcost", description="power plant cost")
    PPpower = Equation(
        cont,
        name="PPpower",
        domain=[t],
        description="power of power plant at time t",
    )
    PPstage = Equation(
        cont,
        name="PPstage",
        domain=[t],
        description="exactly one stage of power plant at any time",
    )
    PPchiS1 = Equation(
        cont,
        name="PPchiS1",
        domain=[t, m],
        description="relate chi and delta variables first constraint",
    )
    PPchiS2 = Equation(
        cont,
        name="PPchiS2",
        domain=[t, m],
        description="relate chi and delta variables second constraint",
    )
    PPstageChange = Equation(
        cont,
        name="PPstageChange",
        domain=[t],
        description="restrict the number of stage changes",
    )
    PPstarted = Equation(
        cont,
        name="PPstarted",
        domain=[t],
        description="connect chiZ and chi variables",
    )
    PPidleTime = Equation(
        cont,
        name="PPidleTime",
        domain=[t],
        description="control the idle time of the plant",
    )
    SMcost = Equation(
        cont,
        name="SMcost",
        description="cost associated with spot market",
    )
    SMpower = Equation(
        cont,
        name="SMpower",
        domain=[t],
        description="power from the spot market",
    )
    LFCcost = Equation(cont, name="LFCcost", description="cost for the LFC")
    LFCenergy = Equation(
        cont,
        name="LFCenergy",
        description="total energy from the LFC",
    )
    LFCmu = Equation(
        cont,
        name="LFCmu",
        description="exactly one price segment b",
    )
    LFCenergyS = Equation(
        cont,
        name="LFCenergyS",
        description="connect the mu variables with the total energy",
    )
    LFCemuo = Equation(
        cont,
        name="LFCemuo",
        description="accumulated energy amount for segement b1",
    )
    LFCemug = Equation(
        cont,
        name="LFCemug",
        domain=[b],
        description="accumulated energy amount for all other segements",
    )

    # the objective function: total cost eq. (6)
    obj.definition = c == cPP + cSM + cLFC

    # meet the power demand for each time period exactly eq. (23)
    demand[t] = pPP[t] + pSM[t] + pLFC[t] == PowerForecast[t]

    # (fix cost +) variable cost * energy amount produced eq. (7) & (8)
    PPcost.definition = cPP == cPPvar * Sum(t, 0.25 * pPP[t])

    # power produced by the power plant eq. (26)
    PPpower[t] = pPP[t] == pPPMax * Sum(
        m.where[Ord(m) > 1], 0.1 * (Ord(m) + 2) * delta[m, t]
    )

    # the power plant is in exactly one stage at any time eq. (25)
    PPstage[t] = Sum(m, delta[m, t]) == 1

    # next constraints model the minimum time period a power plant is in the
    # same state and the constraint of the minimum idle time
    # we need variable 'chiS' to find out when a status change takes place
    # eq. (27)
    PPchiS1[t, m].where[Ord(t) > 1] = (
        chiS[t] >= delta[m, t] - delta[m, t.lag(1)]
    )

    # second constraint for 'chiS' variable eq. (28)
    PPchiS2[t, m].where[Ord(t) > 1] = (
        chiS[t] >= delta[m, t.lag(1)] - delta[m, t]
    )

    # control the minimum change time period eq. (29)
    PPstageChange[t].where[Ord(t) < Card(t) - Card(iS) + 2] = (
        Sum(iS, chiS[t.lead(Ord(iS))]) <= 1
    )

    # indicate if the plant left the idle state eq. (30)
    PPstarted[t] = chiI[t] >= delta["m1", t.lag(1)] - delta["m1", t]

    # control the minimum idle time period:
    # it has to be at least Nk2 time periods long eq. (31)
    PPidleTime[t].where[Ord(t) < Card(t) - Card(iI) + 2] = (
        Sum(iI, chiI[t.lead(Ord(iI))]) <= 1
    )

    # cost for the spot market eq. (12)
    # consistent of the base load (alpha) and peak load (beta) contracts
    SMcost.definition = cSM == 24 * cBL * alpha + 12 * cPL * beta

    # Spot Market power contribution eq. (9)
    SMpower[t] = pSM[t] == alpha + IPL[t] * beta

    # cost of the LFC is given by the energy rate eq. (14) & (21)
    LFCcost.definition = cLFC == Sum(
        b, cLFCs[b] * mu[b] + cLFCvar[b] * eLFCs[b]
    )

    # total energy from the LFC eq. (16)
    # connect the eLFC[t] variables with eLFCtot
    LFCenergy.definition = eLFCtot == Sum(t, 0.25 * pLFC[t])

    # indicator variable 'mu':
    # we are in exactly one price segment b eq. (18)
    LFCmu.definition = Sum(b, mu[b]) == 1

    # connect the 'mu' variables with the total energy amount eq. (19)
    LFCenergyS.definition = eLFCtot == Sum(
        b.where[Ord(b) > 1], eLFCb[b.lag(1)] * mu[b]
    ) + Sum(b, eLFCs[b])

    # accumulated energy amount for segment "b1" eq. (20)
    LFCemuo.definition = eLFCs["b1"] <= eLFCb["b1"] * mu["b1"]

    # accumulated energy amount for all other segments (then "b1") eq. (20)
    LFCemug[b].where[Ord(b) > 1] = (
        eLFCs[b] <= (eLFCb[b] - eLFCb[b.lag(1)]) * mu[b]
    )

    energy = Model(
        cont,
        name="energy",
        equations=cont.getEquations(),
        problem="MIP",
        sense=Sense.MIN,
        objective=c,
    )

    # relative termination criterion for MIP (relative gap)
    # termination criterion is decreased to 0.1 from 0.000001
    cont.addOptions({"optCr": 0.1})

    energy.solve()

    print("Objective Function Value: ", energy.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/poutil.py
GAMS-dev/gamspy
"""
Alkylation Process Optimization (PROCESS)

Optimization of a alkylation process.


Bracken, J, and McCormick, G P, Chapter 4. In Selected Applications
of Nonlinear Programming. John Wiley and Sons, New York, 1968.

Keywords: nonlinear programming, alkylation process, chemical engineering
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Problem
from gamspy import Sense
from gamspy import Variable
from gamspy.math import sqr


def main():
    m = Container(delayed_execution=True)

    # Variables
    olefin = Variable(m, name="olefin", type="positive")
    isor = Variable(m, name="isor", type="positive")
    acid = Variable(m, name="acid", type="positive")
    alkylate = Variable(m, name="alkylate", type="positive")
    isom = Variable(m, name="isom", type="positive")
    strength = Variable(m, name="strength", type="positive")
    octane = Variable(m, name="octane", type="positive")
    ratio = Variable(m, name="ratio", type="positive")
    dilute = Variable(m, name="dilute", type="positive")
    f4 = Variable(m, name="f4", type="positive")

    profit = Variable(m, name="profit")
    rangey = Variable(m, name="rangey")
    rangem = Variable(m, name="rangem")
    ranged = Variable(m, name="ranged")
    rangef = Variable(m, name="rangef")

    # Equations
    yield1 = Equation(m, name="yield1")
    rngyield = Equation(m, name="rngyield")
    makeup = Equation(m, name="makeup")
    sdef = Equation(m, name="sdef")
    motor = Equation(m, name="motor")
    rngmotor = Equation(m, name="rngmotor")
    drat = Equation(m, name="drat")
    ddil = Equation(m, name="ddil")
    rngddil = Equation(m, name="rngddil")
    df4 = Equation(m, name="df4")
    rngdf4 = Equation(m, name="rngdf4")
    dprofit = Equation(m, name="dprofit")

    yield1.definition = alkylate == olefin * (
        1.12 + 0.13167 * ratio - 0.00667 * sqr(ratio)
    )
    makeup.definition = alkylate == olefin + isom - 0.22 * alkylate
    sdef.definition = (
        acid == alkylate * dilute * strength / (98 - strength) / 1000
    )
    motor.definition = octane == 86.35 + 1.098 * ratio - 0.038 * sqr(
        ratio
    ) - 0.325 * (89 - strength)
    drat.definition = ratio == (isor + isom) / olefin
    ddil.definition = dilute == 35.82 - 0.222 * f4
    df4.definition = f4 == -133 + 3 * octane
    dprofit.definition = (
        profit
        == 0.063 * alkylate * octane
        - 5.04 * olefin
        - 0.035 * isor
        - 10 * acid
        - 3.36 * isom
    )
    rngyield.definition = rangey * alkylate == olefin * (
        1.12 + 0.13167 * ratio - 0.00667 * sqr(ratio)
    )
    rngmotor.definition = (
        rangem * octane
        == 86.35 + 1.098 * ratio - 0.038 * sqr(ratio) - 0.325 * (89 - strength)
    )
    rngddil.definition = ranged * dilute == 35.82 - 0.222 * f4
    rngdf4.definition = rangef * f4 == -133 + 3 * octane

    # Define Models
    process = Model(
        m,
        name="process",
        equations=[yield1, makeup, sdef, motor, drat, ddil, df4, dprofit],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=profit,
    )
    rproc = Model(
        m,
        name="rproc",
        equations=[
            rngyield,
            makeup,
            sdef,
            rngmotor,
            drat,
            rngddil,
            rngdf4,
            dprofit,
        ],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=profit,
    )

    rangey.lo.assignment = 0.9
    rangey.up.assignment = 1.1
    rangey.l.assignment = 1
    rangem.lo.assignment = 0.9
    rangem.up.assignment = 1.1
    rangem.l.assignment = 1
    ranged.lo.assignment = 0.9
    ranged.up.assignment = 1.1
    ranged.l.assignment = 1
    rangef.lo.assignment = 0.9
    rangef.up.assignment = 1.1
    rangef.l.assignment = 1

    strength.lo.assignment = 85
    strength.up.assignment = 93
    octane.lo.assignment = 90
    octane.up.assignment = 95
    ratio.lo.assignment = 3
    ratio.up.assignment = 12
    dilute.lo.assignment = 1.2
    dilute.up.assignment = 4
    f4.lo.assignment = 145
    f4.up.assignment = 162
    olefin.lo.assignment = 10
    olefin.up.assignment = 2000
    isor.up.assignment = 16000
    acid.up.assignment = 120
    alkylate.up.assignment = 5000
    isom.up.assignment = 2000

    olefin.l.assignment = 1745
    isor.l.assignment = 12000
    acid.l.assignment = 110
    alkylate.l.assignment = 3048
    isom.l.assignment = 1974
    strength.l.assignment = 89.2
    octane.l.assignment = 92.8
    ratio.l.assignment = 8
    dilute.l.assignment = 3.6
    f4.l.assignment = 145
    profit.l.assignment = 872

    process.solve()
    print("Profit in model 'process': {:.2f}".format(profit.records.level[0]))
    rproc.solve()
    print("Profit in model 'rproc': {:.2f}".format(profit.records.level[0]))


if __name__ == "__main__":
    main()

tests/integration/models/process.py
GAMS-dev/gamspy
"""
A Production Mix Problem (PRODMIX)

A furniture company wants to maximize its profits from the
manufacture of different types of desks.


Dantzig, G B, Chapter 3.5. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: linear programming, production planning, manufacturing, furniture
production
"""
import numpy as np

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    desk = Set(m, name="desk", records=["d1", "d2", "d3", "d4"])
    shop = Set(m, name="shop", records=["carpentry", "finishing"])

    # Data
    labor = Parameter(
        m,
        name="labor",
        domain=[shop, desk],
        records=np.array([[4, 9, 7, 10], [1, 1, 3, 40]]),
    )
    caplim = Parameter(
        m, name="caplim", domain=[shop], records=np.array([6000, 4000])
    )
    price = Parameter(
        m, name="price", domain=[desk], records=np.array([12, 20, 18, 40])
    )

    # Variable
    mix = Variable(m, name="mix", domain=[desk], type="Positive")
    profit = Variable(m, name="profit")

    # Equation
    cap = Equation(m, name="cap", domain=[shop])
    ap = Equation(m, name="ap")

    cap[shop] = Sum(desk, labor[shop, desk] * mix[desk]) <= caplim[shop]
    ap.definition = profit == Sum(desk, price[desk] * mix[desk])

    pmp = Model(
        m,
        name="pmp",
        equations=m.getEquations(),
        problem="LP",
        sense=Sense.MAX,
        objective=profit,
    )

    pmp.solve()


if __name__ == "__main__":
    main()

tests/integration/models/prodmix.py
GAMS-dev/gamspy
"""
Optimal production of secreted protein in a fed-batch reactor.

Park, S., Ramirez, W.F., Optimal production of secreted protein in fed-batch
reactors. A.I.Ch.E. Journal, 34, 1988, pp.1550-1558.
"""
import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    n = 500

    # SET #
    nh = Set(
        m,
        name="nh",
        records=[str(i) for i in range(n + 1)],
        description="Number of subintervals",
    )

    # ALIAS #
    k = Alias(m, name="k", alias_with=nh)

    # SCALARS #
    tf = Parameter(m, name="tf", records=10, description="final time")
    x1_0 = Parameter(
        m, name="x1_0", records=1, description="initial value for x1"
    )
    x2_0 = Parameter(
        m, name="x2_0", records=5, description="initial value for x2"
    )
    x3_0 = Parameter(
        m, name="x3_0", records=0, description="initial value for x3"
    )
    x4_0 = Parameter(
        m, name="x4_0", records=0, description="initial value for x4"
    )
    x5_0 = Parameter(
        m, name="x5_0", records=1, description="initial value for x5"
    )
    h = Parameter(m, name="h")
    h.assignment = tf / n

    # VARIABLES #
    x1 = Variable(m, name="x1", domain=[nh])
    x2 = Variable(m, name="x2", domain=[nh])
    x3 = Variable(m, name="x3", domain=[nh])
    x4 = Variable(m, name="x4", domain=[nh])
    x5 = Variable(m, name="x5", domain=[nh])
    u = Variable(m, name="u", domain=[nh], description="control variable")
    a1 = Variable(m, name="a1", domain=[nh])
    a2 = Variable(m, name="a2", domain=[nh])
    a3 = Variable(m, name="a3", domain=[nh])
    obj = Variable(m, name="obj", description="criterion")

    # EQUATIONS #
    eobj = Equation(
        m, name="eobj", type="regular", description="criterion definition"
    )
    state1 = Equation(
        m,
        name="state1",
        type="regular",
        domain=[nh],
        description="state equation 1",
    )
    state2 = Equation(
        m,
        name="state2",
        type="regular",
        domain=[nh],
        description="state equation 2",
    )
    state3 = Equation(
        m,
        name="state3",
        type="regular",
        domain=[nh],
        description="state equation 3",
    )
    state4 = Equation(
        m,
        name="state4",
        type="regular",
        domain=[nh],
        description="state equation 4",
    )
    state5 = Equation(
        m,
        name="state5",
        type="regular",
        domain=[nh],
        description="state equation 5",
    )
    ea1 = Equation(m, name="ea1", type="regular", domain=[nh])
    ea2 = Equation(m, name="ea2", type="regular", domain=[nh])
    ea3 = Equation(m, name="ea3", type="regular", domain=[nh])

    eobj.definition = obj == x4[str(n)] * x5[str(n)]

    state1[nh[k.lead(1)]] = x1[k.lead(1)] == (
        x1[k]
        + (h / 2)
        * (
            a1[k] * x1[k]
            - u[k] * x1[k] / x5[k]
            + a1[k.lead(1)] * x1[k.lead(1)]
            - u[k.lead(1)] * x1[k.lead(1)] / x5[k.lead(1)]
        )
    )

    state2[nh[k.lead(1)]] = x2[k.lead(1)] == (
        x2[k]
        + (h / 2)
        * (
            -7.3 * a1[k] * x1[k]
            - u[k] * (x2[k] - 20) / x5[k]
            - 7.3 * a1[k.lead(1)] * x1[k.lead(1)]
            - u[k.lead(1)] * (x2[k.lead(1)] - 20) / x5[k.lead(1)]
        )
    )

    state3[nh[k.lead(1)]] = x3[k.lead(1)] == (
        x3[k]
        + (h / 2)
        * (
            a2[k] * x1[k]
            - u[k] * x3[k] / x5[k]
            + a2[k.lead(1)] * x1[k.lead(1)]
            - u[k.lead(1)] * x3[k.lead(1)] / x5[k.lead(1)]
        )
    )

    state4[nh[k.lead(1)]] = x4[k.lead(1)] == (
        x4[k]
        + (h / 2)
        * (
            a3[k] * (x3[k] - x4[k])
            - u[k] * x4[k] / x5[k]
            + a3[k.lead(1)] * (x3[k.lead(1)] - x4[k.lead(1)])
            - u[k.lead(1)] * x4[k.lead(1)] / x5[k.lead(1)]
        )
    )

    state5[nh[k.lead(1)]] = x5[k.lead(1)] == x5[k] + (h / 2) * (
        u[k] + u[k.lead(1)]
    )

    ea1[nh[k]] = a1[k] == 21.87 * x2[k] / ((x2[k] + 0.4) * (x2[k] + 62.5))
    ea2[nh[k]] = a2[k] == (x2[k] * gams_math.exp(-5 * x2[k])) / (0.1 + x2[k])
    ea3[nh[k]] = a3[k] == 4.75 * a1[k] / (0.12 + a1[k])

    # Initial point
    x1.l[nh] = 1.0
    x2.l[nh] = 5.0
    x3.l[nh] = 0.0
    x4.l[nh] = 0.0
    x5.l[nh] = 1.0
    u.l[nh] = 0.0

    x1.fx["0"] = x1_0
    x2.fx["0"] = x2_0
    x3.fx["0"] = x3_0
    x4.fx["0"] = x4_0
    x5.fx["0"] = x5_0

    # Bounds
    u.lo[nh] = 0.0
    u.up[nh] = 5

    protein = Model(
        m,
        name="protein",
        equations=m.getEquations(),
        problem="nlp",
        sense="max",
        objective=obj,
    )

    m.addOptions({"reslim": 60000, "iterlim": 80000})
    protein.solve()

    print("Objective Function Value:  ", round(obj.toValue(), 4), "\n")

    # REPORTING PARAMETER #
    rep = Parameter(m, name="rep", domain=[nh, "*"])
    rep[nh, "x1"] = x1.l[nh]
    rep[nh, "x2"] = x2.l[nh]
    rep[nh, "x3"] = x3.l[nh]
    rep[nh, "x4"] = x4.l[nh]
    rep[nh, "x5"] = x5.l[nh]
    rep[nh, "u"] = u.l[nh]

    rep.pivot().round(3).to_csv("protein_report.csv")

    # End of protein


if __name__ == "__main__":
    main()

tests/integration/models/protein.py
GAMS-dev/gamspy
"""
Parts Supply Problem w/ 10 Types w/ Random p(i) (PS10_S_MN)

Hideo Hashimoto, Kojun Hamada, and Nobuhiro Hosoe, "A Numerical Approach
to the Contract Theory: the Case of Adverse Selection", GRIPS Discussion
Paper 11-27, National Graduate Institute for Policy Studies, Tokyo, Japan,
March 2012.

Keywords: nonlinear programming, contract theory, principal-agent problem,
          adverse selection, parts supply problem
"""
import time

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import Round
from gamspy.math import uniform


def main():
    start = time.time()

    # Decreased no. of draw from 1001 to 11 for convenience
    # Otherwise, it takes a lot of time
    NUM_DRAWS = 11

    m = Container(delayed_execution=True)

    m.addOptions({"limCol": 0, "limRow": 0, "solPrint": "off"})

    # Sets
    i = Set(
        m,
        name="i",
        records=[str(i) for i in range(10)],
        description="type of supplier",
    )

    t = Set(
        m,
        name="t",
        records=[str(i) for i in range(1, NUM_DRAWS)],
        description="no. of Monte-Carlo draws",
    )

    j = Alias(m, name="j", alias_with=i)

    # Parameters
    theta = Parameter(m, name="theta", domain=[i], description="efficiency")
    pt = Parameter(
        m, name="pt", domain=[i, t], description="probability of type"
    )
    p = Parameter(m, name="p", domain=[i], description="probability of type")

    theta[i] = Ord(i) / Card(i)

    # Generating probability
    for tt, _ in t.records.itertuples(index=False):
        pt[i, tt] = uniform(0, 1)
    pt[i, t] = pt[i, t] / Sum(j, pt[j, t])

    # Parameters
    F = Parameter(
        m,
        name="F",
        domain=[i, t],
        description="cumulative probability (Itho p. 42)",
    )
    noMHRC0 = Parameter(
        m,
        name="noMHRC0",
        domain=[i, t],
        description="no MHRC combination between i and i-1",
    )
    # (MHRC: monotone hazard rate condition)
    noMHRC = Parameter(
        m, name="noMHRC", domain=[t], description=">=1: no MHRC case"
    )

    F[i, t] = Sum(j.where[Ord(j) <= Ord(i)], pt[j, t])
    noMHRC0[i, t].where[Ord(i) < Card(i)] = Number(1).where[
        F[i, t] / pt[i.lead(1), t] < F[i.lag(1), t] / pt[i, t]
    ]
    noMHRC[t].where[Sum(i, noMHRC0[i, t]) >= 1] = 1

    ru = Parameter(m, name="ru", records=0, description="reservation utility")

    # Definition of Primal/Dual Variables
    x = Variable(
        m, name="x", type="positive", domain=[i], description="quality"
    )
    b = Variable(
        m, name="b", type="positive", domain=[i], description="maker's revenue"
    )
    w = Variable(m, name="w", type="positive", domain=[i], description="price")
    Util = Variable(m, name="Util", type="free", description="maker's utility")

    # Equations
    obj = Equation(m, name="obj", description="maker's utility function")
    rev = Equation(
        m,
        name="rev",
        domain=[i],
        description="maker's revenue function",
    )
    pc = Equation(
        m,
        name="pc",
        domain=[i],
        description="participation constraint",
    )
    licd = Equation(
        m,
        name="licd",
        domain=[i],
        description="incentive compatibility constraint",
    )
    licu = Equation(
        m,
        name="licu",
        domain=[i],
        description="incentive compatibility constraint",
    )
    ic = Equation(
        m,
        name="ic",
        domain=[i, j],
        description="global incentive compatibility constraint",
    )
    mn = Equation(
        m,
        name="mn",
        domain=[i],
        description="monotonicity constraint",
    )

    obj.definition = Util == Sum(i, p[i] * (b[i] - w[i]))

    rev[i] = b[i] == x[i] ** (0.5)

    pc[i] = w[i] - theta[i] * x[i] >= ru

    licd[i] = w[i] - theta[i] * x[i] >= w[i.lead(1)] - theta[i] * x[i.lead(1)]

    licu[i] = w[i] - theta[i] * x[i] >= w[i.lag(1)] - theta[i] * x[i.lag(1)]

    ic[i, j] = w[i] - theta[i] * x[i] >= w[j] - theta[i] * x[j]

    mn[i] = x[i] >= x[i.lead(1)]

    # Setting Lower Bounds on Variables to Avoid Division by Zero
    x.lo[i] = 0.0001

    # Models
    SB_lic = Model(
        m,
        name="SB_lic",
        equations=[obj, rev, pc, licd],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=Util,
    )
    SB_lic2 = Model(
        m,
        name="SB_lic2",
        equations=[obj, rev, pc, licd, mn],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=Util,
    )

    # Options to solve models quickly
    m.addOptions({"solveLink": 5})

    # Parameters
    Util_lic = Parameter(
        m, name="Util_lic", domain=[t], description="util solved w/o MN"
    )
    Util_lic2 = Parameter(
        m, name="Util_lic2", domain=[t], description="util solved w/ MN"
    )
    Util_gap = Parameter(
        m,
        name="Util_gap",
        domain=[t],
        description="gap between these two util",
    )
    x_lic = Parameter(
        m, name="x_lic", domain=[i, t], description="x solved in w/o MN"
    )
    x_lic2 = Parameter(
        m, name="x_lic2", domain=[i, t], description="x solved in w/ MN"
    )
    MN_lic = Parameter(
        m,
        name="MN_lic",
        domain=[t],
        description="monotonicity of x solved w/o MN",
    )
    MN_lic2 = Parameter(
        m,
        name="MN_lic2",
        domain=[t],
        description="monotonicity of x solved w/ MN",
    )

    for tt, _ in t.records.itertuples(index=False):
        p[i] = pt[i, tt]

        #  Solving the model w/o MN
        SB_lic.solve()

        Util_lic[tt] = Util.l
        x_lic[i, tt] = x.l[i]
        MN_lic[tt] = Sum(
            i, Number(1).where[Round(x.l[i], 10) < Round(x.l[i.lead(1)], 10)]
        )

        #  Solving the model w/ MN
        SB_lic2.solve()

        Util_lic2[tt] = Util.l
        x_lic2[i, tt] = x.l[i]
        MN_lic2[tt] = Sum(
            i, Number(1).where[Round(x.l[i], 10) < Round(x.l[i.lead(1)], 10)]
        )

    Util_gap[t] = Number(1).where[
        Round(Util_lic[t], 10) != Round(Util_lic2[t], 10)
    ]

    # Computing probability that MHRC and MN holds.
    p_noMHRC = Parameter(
        m, name="p_noMHRC", description="no MHRC case          [%]"
    )
    p_noMN_lic = Parameter(
        m, name="p_noMN_lic", description="no MN case            [%]"
    )
    p_Util_gap = Parameter(
        m, name="p_Util_gap", description="no util-equality case [%]"
    )

    p_noMHRC.assignment = (
        Sum(t.where[noMHRC[t] > Number(0)], Number(1)) / Card(t) * 100
    )
    p_noMN_lic.assignment = (
        Sum(t.where[MN_lic[t] > Number(0)], Number(1)) / Card(t) * 100
    )
    p_Util_gap.assignment = (
        Sum(t.where[Util_gap[t] > Number(0)], Number(1)) / Card(t) * 100
    )

    print(f"no MHRC case: {p_noMHRC.records.value[0]}%")
    print(f"no MN case: {p_noMN_lic.records.value[0]}%")
    print(f"no util-equality case: {p_Util_gap.records.value[0]}%")
    print(f"Time Elapsed: {round(time.time()-start, 2)}s")


if __name__ == "__main__":
    main()

tests/integration/models/ps10_s_mn.py
GAMS-dev/gamspy
"""
Nonlinear Simple Agricultural Sector Model (QDEMO7)

This is a QCP version of the gamslib model DEMO7. The original NLP
formulation was concerned with good starting points. QCPs do not
need starting a point.

This is the last in a series of agricultural farm level and sector
models, this model simulates the market behavior of the sector
using a partial equilibrium framework. The technique is
the maximization of consumers and producers surplus.


Kutcher, G P, Meeraus, A, and O'Mara, G T, Agriculture Sector and
Policy Models. The World Bank, 1988.

Keywords: quadratic constraint programming, farming, agricultural economics,
          partial equilibrium, market behavior
"""
from pathlib import Path

import numpy as np

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Sum
from gamspy import Variable
from gamspy.math import sqr


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/qdemo7.gdx",
    )

    # Sets
    c, cl, t, r, s, sc, cn, ce, cm = m.getSymbols(
        ["c", "cl", "t", "r", "s", "sc", "cn", "ce", "cm"]
    )

    # Parameters
    a, lc, lio, demdat = m.getSymbols(["a", "lc", "lio", "demdat"])

    # Scalar
    fnum = Parameter(m, name="fnum", records=1000)
    land = Parameter(m, name="land", records=4)
    famlab = Parameter(m, name="famlab", records=25)
    rwage = Parameter(m, name="rwage", records=3)
    twage = Parameter(m, name="twage", records=4)
    llab = Parameter(m, name="llab", records=2)
    trent = Parameter(m, name="trent", records=40)
    hpa = Parameter(m, name="hpa", records=2)
    straw = Parameter(m, name="straw", records=1.75)

    # Parameter
    yields = Parameter(
        m,
        name="yields",
        domain=[c],
        records=np.array([1.5, 6, 1, 3, 1.5, 2, 3]),
    )
    miscost = Parameter(
        m,
        name="miscost",
        domain=[c],
        records=np.array([10, 0, 5, 50, 80, 5, 50]),
    )
    price = Parameter(m, name="price", domain=[c])
    pe = Parameter(m, name="pe", domain=[c])
    pm = Parameter(m, name="pm", domain=[c])
    alpha = Parameter(m, name="alpha", domain=[c])
    beta = Parameter(m, name="beta", domain=[c])

    cn[c] = Number(1).where[demdat[c, "ref-p"]]
    ce[c] = Number(1).where[demdat[c, "exp-p"]]
    cm[c] = Number(1).where[(demdat[c, "imp-p"] < np.inf)]
    cm["clover"] = Number(0)
    price[c] = demdat[c, "ref-p"]
    pe[ce] = demdat[ce, "exp-p"]
    pm[cm] = demdat[cm, "imp-p"]

    beta[cn].where[demdat[cn, "ref-q"]] = (
        demdat[cn, "ref-p"] / demdat[cn, "ref-q"] / demdat[cn, "elas"]
    )
    alpha[cn] = demdat[cn, "ref-p"] - beta[cn] * demdat[cn, "ref-q"]
    demdat[cn, "dem-a"] = alpha[cn]
    demdat[cn, "dem-b"] = beta[cn]

    # Variables
    xcrop = Variable(m, name="xcrop", type="positive", domain=[c])
    mcost = Variable(m, name="mcost")
    pcost = Variable(m, name="pcost")
    labcost = Variable(m, name="labcost")
    rescost = Variable(m, name="rescost")
    tcost = Variable(m, name="tcost")
    flab = Variable(m, name="flab", type="positive", domain=[t])
    tlab = Variable(m, name="tlab", type="positive", domain=[t])
    xlive = Variable(m, name="xlive", type="positive", domain=[r])
    natprod = Variable(m, name="natprod", type="positive", domain=[c])
    thire = Variable(m, name="thire", type="positive", domain=[s])
    natcon = Variable(m, name="natcon", type="positive", domain=[c])
    exports = Variable(m, name="exports", type="positive", domain=[c])
    imports = Variable(m, name="imports", type="positive", domain=[c])
    cps = Variable(m, name="cps")

    # Equation
    landbal = Equation(m, name="landbal", domain=[t])
    laborbal = Equation(m, name="laborbal", domain=[t])
    plow = Equation(m, name="plow", domain=[s])
    ares = Equation(m, name="ares")
    acost = Equation(m, name="acost")
    amisc = Equation(m, name="amisc")
    aplow = Equation(m, name="aplow")
    alab = Equation(m, name="alab")
    lclover = Equation(m, name="lclover")
    lstraw = Equation(m, name="lstraw")
    proc = Equation(m, name="proc", domain=[c])
    dem = Equation(m, name="dem", domain=[c])
    objn = Equation(m, name="objn")

    landbal[t] = Sum(c, xcrop[c] * a[t, c]) <= land * fnum

    laborbal[t] = (
        Sum(c, xcrop[c] * lc[t, c]) + Sum(r, xlive[r]) * llab
        <= flab[t] + tlab[t]
    )

    amisc.definition = mcost == Sum(c, xcrop[c] * miscost[c])

    alab.definition = labcost == Sum(t, tlab[t] * twage)

    ares.definition = rescost == Sum(t, flab[t] * rwage)

    aplow.definition = pcost == Sum(s, thire[s] * trent)

    acost.definition = tcost == mcost + labcost + rescost + pcost

    lclover.definition = xcrop["clover"] * yields["clover"] >= Sum(
        r, xlive[r] * lio["clover", r]
    )

    lstraw.definition = xcrop["wheat"] * straw >= Sum(
        r, xlive[r] * lio["straw", r]
    )

    plow[s] = (
        Sum(c.where[sc[s, c]], xcrop[c]) <= Sum(r, xlive[r]) * hpa + thire[s]
    )

    proc[c] = natprod[c] == xcrop[c] * yields[c]

    dem[cn] = (
        natcon[cn]
        == natprod[cn] + imports[cn].where[cm[cn]] - exports[cn].where[ce[cn]]
    )

    objn.definition = cps == (
        Sum(cn, alpha[cn] * natcon[cn] + 0.5 * beta[cn] * sqr(natcon[cn]))
        + Sum(ce, exports[ce] * pe[ce])
        - Sum(cm, imports[cm] * pm[cm])
        - tcost
    )

    flab.up[t] = famlab * fnum

    demo7n = Model(
        m,
        name="demo7n",
        equations=[
            landbal,
            laborbal,
            plow,
            ares,
            alab,
            acost,
            dem,
            proc,
            amisc,
            aplow,
            lclover,
            lstraw,
            objn,
        ],
        problem=Problem.QCP,
        sense=Sense.MAX,
        objective=cps,
    )

    m.addOptions({"limCol": 0, "limRow": 0})

    demo7n.solve()

    print("Value of objective:  ", round(cps.records.level[0], 3))


if __name__ == "__main__":
    main()

tests/integration/models/qdemo7.py
GAMS-dev/gamspy
"""
Standard QP Model - QP4 expressed as MCP (QP6)

Formulate the QP as an LCP, ie write down the first order
conditions of QP4 and solve.


Kalvelagen, E, Model Building with GAMS. forthcoming
de Wetering, A V, private communication.

Keywords: mixed complementarity problem, quadratic programming, finance
"""
from pathlib import Path

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import sqr


def main():
    cont = Container(
        load_from=str(Path(__file__).parent.absolute()) + "/qp6.gdx"
    )

    # Sets
    days, stocks = cont.getSymbols(["days", "stocks"])

    # Parameters
    returns, val = cont.getSymbols(["return", "val"])

    # Set
    d = Set(cont, name="d", domain=[days], description="selected days")
    s = Set(cont, name="s", domain=[stocks], description="selected stocks")

    # select subset of stocks and periods
    d[days] = (Ord(days) > 1) & (Ord(days) < 31)
    s[stocks] = Ord(stocks) < 51

    # Parameter
    mean = Parameter(
        cont, name="mean", domain=[stocks], description="mean of daily return"
    )
    dev = Parameter(
        cont, name="dev", domain=[stocks, days], description="deviations"
    )
    totmean = Parameter(cont, name="totmean", description="total mean return")

    mean[s] = Sum(d, returns[s, d]) / Card(d)
    dev[s, d] = returns[s, d] - mean[s]
    totmean.assignment = Sum(s, mean[s]) / (Card(s))

    # Variable
    x = Variable(
        cont,
        name="x",
        type="positive",
        domain=[stocks],
        description="investments",
    )
    w = Variable(
        cont,
        name="w",
        type="free",
        domain=[days],
        description="intermediate variables",
    )

    # Equation
    budget = Equation(cont, name="budget")
    retcon = Equation(cont, name="retcon", description="returns constraint")
    wdef = Equation(cont, name="wdef", domain=[days])

    wdef[d] = w[d] == Sum(s, x[s] * dev[s, d])

    budget.definition = Sum(s, x[s]) == 1.0

    retcon.definition = Sum(s, mean[s] * x[s]) >= totmean * 1.25

    # Equation
    d_x = Equation(cont, name="d_x", domain=[stocks])
    d_w = Equation(cont, name="d_w", domain=[days])

    # Variable
    m_budget = Variable(cont, name="m_budget", type="free")
    m_wdef = Variable(cont, name="m_wdef", type="free", domain=[days])

    # Positive Variable
    m_retcon = Variable(cont, name="m_retcon", type="positive")

    m_wdef.fx[days].where[~d[days]] = 0

    d_x[s] = Sum(d, m_wdef[d] * dev[s, d]) >= m_retcon * mean[s] + m_budget

    d_w[d] = 2 * w[d] / (Card(d) - 1) == m_wdef[d]

    qp6 = Model(
        cont,
        name="qp6",
        equations=[d_x, d_w, retcon, budget, wdef],
        matches={
            d_x: x,
            d_w: w,
            retcon: m_retcon,
            budget: m_budget,
            wdef: m_wdef,
        },
        problem="mcp",
    )

    qp6.solve()

    z = Parameter(cont, name="z")
    z.assignment = Sum(d, sqr(w.l[d])) / (Card(d) - 1)
    print("\ninvestments: ", x.records.set_index("stocks").level.round(3))
    print("\nObjective Function Value: ", z.records.value.round(3).tolist()[0])


if __name__ == "__main__":
    main()

tests/integration/models/qp6.py
GAMS-dev/gamspy
"""
An elementary Ramsey growth model

References:
Frank P. Ramsey, A Mathematical Theory of Saving, Economics Journal,
vol.38, No. 152, December 1928.

Erwin Kalvelagen, (2003) An elementary Ramsey growth model.
http://www.gams.com/~erwin/micro/growth.gms
"""
import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # SETS #
    t = Set(
        m,
        name="t",
        records=[f"t{t}" for t in range(1, 51)],
        description="time periods",
    )
    tfirst = Set(
        m, name="tfirst", domain=[t], description="first interval (t0)"
    )
    tlast = Set(m, name="tlast", domain=[t], description="last intervat [T]")
    tnotlast = Set(
        m, name="tnotlast", domain=[t], description="all intervals but last"
    )

    tfirst[t].where[Ord(t) == 1] = True
    tlast[t].where[Ord(t) == Card(t)] = True
    tnotlast[t] = ~tlast[t]

    # SCALARS #
    rho = Parameter(m, name="rho", records=0.04, description="discount factor")
    g = Parameter(m, name="g", records=0.03, description="labor growth rate")
    delta = Parameter(
        m,
        name="delta",
        records=0.02,
        description="capital depreciation factor",
    )
    K0 = Parameter(m, name="K0", records=3.00, description="initial capital")
    I0 = Parameter(
        m, name="I0", records=0.07, description="initial investment"
    )
    C0 = Parameter(
        m, name="C0", records=0.95, description="initial consumption"
    )
    L0 = Parameter(m, name="L0", records=1.00, description="initial labor")
    b = Parameter(
        m, name="b", records=0.25, description="Cobb Douglas coefficient"
    )
    a = Parameter(m, name="a", description="Cobb Douglas coefficient")

    # PARAMETERS #
    L = Parameter(
        m, name="L", domain=[t], description="labor (production input)"
    )
    beta = Parameter(
        m,
        name="beta",
        domain=[t],
        description="weight factor for future utilities",
    )
    tval = Parameter(
        m, name="tval", domain=[t], description="numerical value of t"
    )

    tval[t] = Ord(t) - 1

    # The terminal weight beta(tlast) computation.
    beta[tnotlast[t]] = gams_math.power(1 + rho, -tval[t])
    beta[tlast[t]] = (1 / rho) * gams_math.power(1 + rho, 1 - tval[t])
    # display beta

    # Labor is determined using an exponential growth process.
    L[t] = gams_math.power(1 + g, tval[t]) * L0

    # Cobb-Douglas coefficient a computation.
    a = (C0 + I0) / (K0**b * L0 ** (1 - b))

    # VARIABLES #
    C = Variable(m, name="C", domain=[t], description="consumption")
    Y = Variable(m, name="Y", domain=[t], description="production")
    K = Variable(m, name="K", domain=[t], description="capital")
    I = Variable(m, name="I", domain=[t], description="investment")
    W = Variable(m, name="W", description="total utility")

    # EQUATIONS #
    utility = Equation(
        m, name="utility", type="regular", description="discounted utility"
    )
    production = Equation(
        m,
        name="production",
        type="regular",
        domain=[t],
        description="Cobb-Douglas production function",
    )
    allocation = Equation(
        m,
        name="allocation",
        type="regular",
        domain=[t],
        description="household choose between consumption and saving",
    )
    accumulation = Equation(
        m,
        name="accumulation",
        type="regular",
        domain=[t],
        description="capital accumulation",
    )
    final = Equation(
        m,
        name="final",
        type="regular",
        domain=[t],
        description="minimal investment in final period",
    )

    utility.definition = W == Sum(t, beta[t] * gams_math.log(C[t]))
    production[t] = Y[t] == a * (K[t] ** b) * (L[t] ** (1 - b))
    allocation[t] = Y[t] == C[t] + I[t]
    accumulation[tnotlast[t]] = K[t.lead(1)] == (1 - delta) * K[t] + I[t]
    final[tlast] = I[tlast] >= (g + delta) * K[tlast]

    # Bounds.
    K.lo[t] = 0.001
    C.lo[t] = 0.001

    # Initial conditions
    K.fx[tfirst] = K0
    I.fx[tfirst] = I0
    C.fx[tfirst] = C0

    ramsey = Model(
        m,
        name="ramsey",
        equations=m.getEquations(),
        problem="nlp",
        sense="MAX",
        objective=W,
    )

    ramsey.solve()

    print("Objective Function Value:  ", round(W.toValue(), 4))

    # Solution visualization
    # ----------------------

    rep = Parameter(m, name="rep", domain=[t, "*"])

    rep[t, "C[t]"] = C.l[t]
    rep[t, "Y[t]"] = Y.l[t]
    rep[t, "K[t]"] = K.l[t]
    rep[t, "I[t]"] = I.l[t]

    print("Solution:\n", rep.pivot().round(3))
    # End Ramsey


if __name__ == "__main__":
    main()

tests/integration/models/ramsey.py
GAMS-dev/gamspy
"""
Resource-Constrained Project Scheduling Problem (RCPSP)

Resource-constrained project scheduling problem (RCPSP) in a formulation
which encodes the schedule using binary finishing time indicator variables
as first proposed by Pritsker.

Problem and model formulation based on:
Pritsker, A. Alan B., Lawrence J. Waiters, and Philip M. Wolfe. "Multiproject
scheduling with limited resources: A zero-one programming approach."
Management science 16.1 (1969): 93-108.

Contains embedded Python code for parsing instance data from the classic
problem library PSPLIB from Kolisch and Sprecher.

Instance library, generator and file format:
Kolisch, Rainer, and Arno Sprecher. "PSPLIB-a project scheduling problem
library: OR software-ORSEP operations research software exchange program."
European journal of operational research 96.1 (1997): 205-216.
http://www.om-db.wi.tum.de/psplib/main.html

As default the first instance from PSPLIBs subset with 30 projects is solved
to optimality (makespan=43).
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


# Utility functions
def ints(strs):
    return [int(s) for s in strs]


def my_set(prefix, cardinality):
    return [f"{prefix}{i + 1}" for i in range(cardinality)]


def index_of_line(lines, substr):
    return next(i for i, line in enumerate(lines) if substr in line)


def rhs_part(lines, prefix):
    return lines[index_of_line(lines, prefix)].split(":")[1]


def succs_from_line(line):
    return [f"j{j}" for j in line.split()[3:]]


def column(lines, col, row_start, row_count):
    return [
        int(lines[rowIx].split()[col])
        for rowIx in range(row_start, row_start + row_count)
    ]


def extract(mapping, fields_str):
    return (mapping[field_name] for field_name in fields_str.split(","))


def parse_psplib(filename):
    # Parse data from text file
    with open(filename) as fp:
        lines = fp.readlines()
    njobs = int(rhs_part(lines, "jobs (incl. supersource"))
    nres = int(rhs_part(lines, "- renewable").split()[0])
    nperiods = int(rhs_part(lines, "horizon"))
    prec_offset = index_of_line(lines, "PRECEDENCE RELATIONS:") + 2
    attrs_offset = index_of_line(lines, "REQUESTS/DURATIONS") + 3
    caps_offset = index_of_line(lines, "RESOURCEAVAILABILITIES") + 2

    jobs, res, periods = (
        my_set("j", njobs),
        my_set("r", nres),
        my_set("t", nperiods),
    )
    succs = {
        j: succs_from_line(lines[prec_offset + ix])
        for ix, j in enumerate(jobs)
    }
    job_durations = column(lines, 2, attrs_offset, njobs)
    resource_demands = [
        ints(lines[ix].split()[3:])
        for ix in range(attrs_offset, attrs_offset + njobs)
    ]
    resource_capacities = ints(lines[caps_offset].split())
    return dict(
        jobs=jobs,
        periods=periods,
        res=res,
        succs=succs,
        job_durations=job_durations,
        resource_capacities=resource_capacities,
        resource_demands=resource_demands,
    )


def decorate_with_time_windows(instance):
    jobs, periods, succs, job_durations = extract(
        instance, "jobs,periods,succs,job_durations"
    )

    def compute_earliest_finishing_times():
        eft = {j: 0 for j in jobs}
        for ix, i in enumerate(jobs):
            for j in jobs:
                if j in succs[i]:
                    eft[j] = max(eft[j], eft[i] + job_durations[ix])
        return eft

    def compute_latest_finishing_times():
        lft = {j: len(periods) for j in jobs}
        for i in reversed(jobs):
            for jix, j in reversed(list(enumerate(jobs))):
                if j in succs[i]:
                    lft[i] = min(lft[i], lft[j] - job_durations[jix])
        return lft

    instance["eft"] = compute_earliest_finishing_times()
    instance["lft"] = compute_latest_finishing_times()


def mini_project():
    return dict(
        jobs=["i1", "i2", "i3"],
        periods=["t1", "t2", "t3", "t4"],
        res=["r1"],
        succs=dict(i1=["i2"], i2=["i3"], i3=[]),
        job_durations=[0, 1, 0],
        resource_capacities=[1],
        resource_demands=[[0], [1], [0]],
    )


def fill_records(dataset, symbols):
    sym_fields = "j,t,r,lastJob,actual,pred,tw,fw,capacities,durations,demands"
    (
        j,
        t,
        r,
        lastJob,
        actual,
        pred,
        tw,
        fw,
        capacities,
        durations,
        demands,
    ) = extract(symbols, sym_fields)
    data_fields = (
        "jobs,periods,res,succs,eft,lft,job_durations,"
        "resource_capacities,resource_demands"
    )
    (
        jobs,
        periods,
        res,
        succs,
        eft,
        lft,
        job_durations,
        resource_capacities,
        resource_demands,
    ) = extract(dataset, data_fields)
    j.setRecords(jobs)
    t.setRecords(periods)
    r.setRecords(res)
    lastJob.setRecords(jobs[-1:])
    actual.setRecords(jobs[1:-1])
    pred.setRecords([(i, j) for i in jobs for j in jobs if j in succs[i]])
    tw.setRecords(
        [
            (j, t)
            for j in jobs
            for tix, t in enumerate(periods)
            if eft[j] <= tix + 1 <= lft[j]
        ]
    )
    fw.setRecords(
        [
            (j, t, tau)
            for jix, j in enumerate(jobs)
            for tix, t in enumerate(periods)
            for tauix, tau in enumerate(periods)
            if tix <= tauix <= tix + job_durations[jix] - 1
            and eft[j] <= tix + 1 <= lft[j]
        ]
    )
    capacities.setRecords(
        [(r, resource_capacities[rix]) for rix, r in enumerate(res)]
    )
    durations.setRecords([(j, job_durations[ix]) for ix, j in enumerate(jobs)])
    demands.setRecords(
        [
            (j, r, resource_demands[jix][rix])
            for jix, j in enumerate(jobs)
            for rix, r in enumerate(res)
        ]
    )


# Create model via GTP with algebra
def build_abstract_model():
    m = Container(delayed_execution=True)

    j = Set(m, name="j")
    t = Set(m, name="t")
    r = Set(m, name="r")

    i = Alias(m, name="i", alias_with=j)
    tau = Alias(m, name="tau", alias_with=t)

    lastJob = Set(m, name="lastJob")
    actual = Set(m, name="actual")

    pred = Set(m, name="pred", domain=[i, j])

    tw = Set(m, name="tw", domain=[j, t])
    fw = Set(m, name="fw", domain=[j, t, tau])

    capacities = Parameter(m, name="capacities", domain=[r])
    durations = Parameter(m, name="durations", domain=[j])
    demands = Parameter(m, name="demands", domain=[j, r])

    makespan = Variable(m, name="makespan")
    x = Variable(m, name="x", domain=[j, t], type="Binary")

    objective = Equation(m, name="objective")
    objective.definition = makespan == Sum(
        Domain(j, t).where[tw[j, t] & lastJob[j]], x[j, t] * (Ord(t) - 1)
    )

    once = Equation(m, name="once", domain=[j])
    once[j] = Sum(t.where[tw[j, t]], x[j, t]) == 1

    precedence = Equation(m, name="precedence", domain=[i, j])
    precedence[i, j].where[pred[i, j]] = (
        Sum(t.where[tw[i, t]], Ord(t) * x[i, t])
        <= Sum(t.where[tw[j, t]], Ord(t) * x[j, t]) - durations[j]
    )

    resusage = Equation(m, name="resusage", domain=[r, t])
    resusage[r, t] = (
        Sum(
            j.where[actual[j]],
            demands[j, r] * Sum(tau.where[fw[j, t, tau]], x[j, tau]),
        )
        <= capacities[r]
    )

    rcpsp = Model(
        m,
        name="rcpsp",
        equations=m.getEquations(),
        problem="MIP",
        sense=Sense.MIN,
        objective=makespan,
    )
    makespan.lo.assignment = 0

    return dict(
        m=m,
        rcpsp=rcpsp,
        j=j,
        t=t,
        r=r,
        lastJob=lastJob,
        actual=actual,
        pred=pred,
        capacities=capacities,
        durations=durations,
        demands=demands,
        tw=tw,
        fw=fw,
        x=x,
        makespan=makespan,
    )


def display_results(model, dataset):
    m, x, j, t = extract(model, "m,x,j,t")
    res = Parameter(m, name="res", domain=[j, t])
    res[j, t] = x.l[j, t]
    sts = {dataset["jobs"][jix]: t for jix, t in enumerate(res.records["t"])}
    for j, t in sts.items():
        print(f"job {j} starts at period {t}")


def main():
    load_from_file = False
    dataset = parse_psplib("rcpsp.sm") if load_from_file else mini_project()
    decorate_with_time_windows(dataset)
    model = build_abstract_model()
    fill_records(dataset, model)
    model["rcpsp"].solve()
    display_results(model, dataset)


if __name__ == "__main__":
    main()

tests/integration/models/rcpsp.py
GAMS-dev/gamspy
"""
Optimal design of industrial refrigeration system.

Paul H and Tay, Optimal design of an industrial refrigeration system.
Proc. of Int. Conf. on Optimization Techniques and Applications, 1987,
pp.427-435.

Pant, M., Thangaraj, R., Singh, V.P., (2009) Optimization of mechanical
design problems using improved differential evolution algorithm.
International Journal of Recent Trends in Engineering, vol.1, No.5,
May 2009, pp.21-25.
"""
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    x4 = Variable(m, name="x4")
    x5 = Variable(m, name="x5")
    x6 = Variable(m, name="x6")
    x7 = Variable(m, name="x7")
    x8 = Variable(m, name="x8")
    x9 = Variable(m, name="x9")
    x10 = Variable(m, name="x10")
    x11 = Variable(m, name="x11")
    x12 = Variable(m, name="x12")
    x13 = Variable(m, name="x13")
    x14 = Variable(m, name="x14")
    obj = Variable(m, name="obj")

    # EQUATIONS #
    e1 = Equation(m, name="e1", type="regular")
    e2 = Equation(m, name="e2", type="regular")
    e3 = Equation(m, name="e3", type="regular")
    e4 = Equation(m, name="e4", type="regular")
    e5 = Equation(m, name="e5", type="regular")
    e6 = Equation(m, name="e6", type="regular")
    e7 = Equation(m, name="e7", type="regular")
    e8 = Equation(m, name="e8", type="regular")
    e9 = Equation(m, name="e9", type="regular")
    e10 = Equation(m, name="e10", type="regular")
    e11 = Equation(m, name="e11", type="regular")
    e12 = Equation(m, name="e12", type="regular")
    e13 = Equation(m, name="e13", type="regular")
    e14 = Equation(m, name="e14", type="regular")
    e15 = Equation(m, name="e15", type="regular")
    eobj = Equation(m, name="eobj", type="regular")

    # Objective function to be minimized:
    eobj.definition = obj == (
        63098.88 * x2 * x4 * x12
        + 5441.5 * x12 * x2**2
        + 115055.5 * x6 * (x2**1.664)
        + 6172.27 * x6 * x2**2
        + 63098.88 * x1 * x3 * x11
        + 5441.5 * x11 * x1**2
        + 115055.5 * x5 * (x1**1.664)
        + 6172.27 * x5 * x1**2
        + 140.53 * x1 * x11
        + 281.29 * x3 * x11
        + 70.26 * x1**2
        + 281.29 * x1 * x3
        + 281.29 * x3**2
        + 14437
        * (x8**1.8812)
        * (x12**0.3424)
        * x7
        * x10
        * (x1**2)
        / (x9 * x14)
        + 20470.2 * (x7**2.893) * (x11 * 0.316) * (x1 * 82)
    )

    # Constaints:
    e1.definition = 1.524 / x7 <= 1
    e2.definition = 1.524 / x8 <= 1
    e3.definition = 0.07789 * x1 - 2 * x9 / x7 <= 1
    e4.definition = 7.05305 * (x1**2) * x10 / (x2 * x8 * x9 * x14) <= 1
    e5.definition = 0.0833 * x14 / x13 <= 1
    e6.definition = (
        47.136 * x12 * (x2**0.333) / x10
        - 1.333 * x8 * (x13**2.1195)
        + 62.08 * (x13**2.1195) * (x8**0.2) / (x10 * x12)
        <= 1
    )
    e7.definition = 0.04771 * x10 * (x8**1.8812) * (x12**0.3424) <= 1
    e8.definition = 0.0488 * x9 * (x7**1.893) * (x11**0.316) <= 1
    e9.definition = 0.0099 * x1 / x3 <= 1
    e10.definition = 0.0193 * x2 / x4 <= 1
    e11.definition = 0.0298 * x1 / x5 <= 1
    e12.definition = 0.056 * x2 / x6 <= 1
    e13.definition = 2 / x9 <= 1
    e14.definition = 2 / x10 <= 1
    e15.definition = x12 / x11 <= 1

    # Bounds on variables:
    x1.lo.assignment = 0.001
    x1.up.assignment = 5
    x2.lo.assignment = 0.001
    x2.up.assignment = 5
    x3.lo.assignment = 0.001
    x3.up.assignment = 5
    x4.lo.assignment = 0.001
    x4.up.assignment = 5
    x5.lo.assignment = 0.001
    x5.up.assignment = 5
    x6.lo.assignment = 0.001
    x6.up.assignment = 5
    x7.lo.assignment = 0.001
    x7.up.assignment = 5
    x8.lo.assignment = 0.001
    x8.up.assignment = 5
    x9.lo.assignment = 0.001
    x9.up.assignment = 5
    x10.lo.assignment = 0.001
    x10.up.assignment = 5
    x11.lo.assignment = 0.001
    x11.up.assignment = 5
    x12.lo.assignment = 0.001
    x12.up.assignment = 5
    x13.lo.assignment = 0.001
    x13.up.assignment = 5
    x14.lo.assignment = 0.001
    x14.up.assignment = 5

    refrigeration = Model(
        m,
        name="refrigeration",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=obj,
    )
    refrigeration.solve()

    # REPORTING PARAMETER
    rep = Parameter(m, name="rep", domain=["*", "*"])
    rep["x1", "value"] = x1.l
    rep["x2", "value"] = x2.l
    rep["x3", "value"] = x3.l
    rep["x4", "value"] = x4.l
    rep["x5", "value"] = x5.l
    rep["x6", "value"] = x6.l
    rep["x7", "value"] = x7.l
    rep["x8", "value"] = x8.l
    rep["x9", "value"] = x9.l
    rep["x10", "value"] = x10.l
    rep["x11", "value"] = x11.l
    rep["x12", "value"] = x12.l
    rep["x13", "value"] = x13.l
    rep["x14", "value"] = x14.l

    print("Objective Function Value: ", round(obj.toValue(), 4), "\n")
    print("Solution Summary:\n", rep.pivot().round(3))

    # End refrigeration


if __name__ == "__main__":
    main()

tests/integration/models/refrigeration.py
GAMS-dev/gamspy
"""
Onstream and offstream optimal reservoir management.

Adapted from:
McKinney, D.C., Savitsky, A.G., Basic optimization models for water and
energy management. June 1999 (revision 6, February 2003).
http://www.ce.utexas.edu/prof/mckynney/ce385d/papers/GAMS-Tutorial.pdf

Andrei, N., Optimal management of system of two reservoirs.
Revista Romana de Informatica si Automatica, vol.16, no.1, 2006, pp.15-18.
"""
import pandas as pd

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Set
    n = Set(m, name="n", records=["res1", "res2"])
    t = Set(
        m,
        name="t",
        records=[
            "ian",
            "feb",
            "mar",
            "apr",
            "mai",
            "jun",
            "jul",
            "aug",
            "sep",
            "oct",
            "nov",
            "dec",
            "enda",
        ],
    )
    tt = Set(m, name="tt", domain=[t], records=["ian"])

    # Data
    q = Parameter(
        m,
        name="q",
        domain=[n, t],
        records=pd.DataFrame(
            [
                ["res1", "ian", 128],
                ["res1", "feb", 125],
                ["res1", "mar", 234],
                ["res1", "apr", 360],
                ["res1", "mai", 541],
                ["res1", "jun", 645],
                ["res1", "jul", 807],
                ["res1", "aug", 512],
                ["res1", "sep", 267],
                ["res1", "oct", 210],
                ["res1", "nov", 981],
                ["res1", "dec", 928],
                ["res1", "enda", 250],
            ]
        ),
    )
    r = Parameter(
        m,
        name="r",
        domain=[n, t],
        records=pd.DataFrame(
            [
                ["res1", "ian", 100],
                ["res1", "feb", 150],
                ["res1", "mar", 200],
                ["res1", "apr", 500],
                ["res1", "mai", 222],
                ["res1", "jun", 700],
                ["res1", "jul", 333],
                ["res1", "aug", 333],
                ["res1", "sep", 300],
                ["res1", "oct", 250],
                ["res1", "nov", 250],
                ["res1", "dec", 250],
                ["res1", "enda", 200],
            ]
        ),
    )

    # Variable
    q2 = Variable(m, name="q2", domain=[t])
    r2 = Variable(m, name="r2", domain=[t])
    s = Variable(m, name="s", domain=[n, t])
    obj = Variable(m, name="obj")

    # Equation
    bal1 = Equation(m, domain=[n, t], name="bal1")
    bal2 = Equation(m, domain=[n, t], name="bal2")
    dec = Equation(m, domain=[n, t], name="dec")
    objf = Equation(m, name="objf")

    bal1[n, t].where[~tt[t]] = (
        s["res1", t] - s["res1", t.lag(1, "linear")]
        == q["res1", t] + r2[t] - q2[t] - r["res1", t]
    )
    bal2[n, t].where[~tt[t]] = (
        s["res2", t] - s["res2", t.lag(1, "linear")] == q2[t] - r2[t]
    )
    dec[n, t].where[~tt[t]] = (s["res2", t] - s["res1", t]) - (
        s["res2", t] - s["res1", t]
    ) * (1.0 - q2[t] / (q2[t] + 0.000001)) == 0.0
    objf.definition = obj == Sum(t.where[~tt[t]], r2[t])

    s.lo["res1", t] = 1150
    s.up["res1", t] = 4590
    s.fx["res1", "ian"] = 1200
    s.lo["res2", t] = 100
    s.up["res2", t] = 4590
    s.fx["res2", "ian"] = 1200
    r2.up[t] = 1500
    r2.lo[t] = 0.0
    q2.up[t] = 1500
    q2.lo[t] = 0.0
    q2.l[t] = 0.00001

    reservoir = Model(
        m,
        name="reservoir",
        equations=m.getEquations(),
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=obj,
    )
    reservoir.solve()


if __name__ == "__main__":
    main()

tests/integration/models/reservoir.py
GAMS-dev/gamspy
"""
October 6, 2005
Optimal management of a river system which includes water resources,
reservoirs, consumers and an estuary.

Adapted from:
McKinney, D.C. and Savitsky, A.G., "Basic optimization models
for water and energy management", Revision 6, February 2003.
"""
import pandas as pd

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def reformat_df(dataframe):
    return dataframe.reset_index().melt(
        id_vars="index", var_name="Category", value_name="Value"
    )


def data_records():
    # Source records table
    cols = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    ]
    inds = ["Source_1", "Source_2"]
    data = [
        [98, 115, 244, 390, 641, 754, 807, 512, 367, 210, 181, 128],
        [29, 49, 78, 121, 198, 144, 105, 98, 79, 72, 45, 29],
    ]
    Source_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    # Demand records table
    cols = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    ]
    inds = ["User_1", "User_2", "Outlet"]
    data = [
        [0.0, 0.0, 10, 64.5, 189.8, 184.4, 243.7, 200.9, 99.5, 0.0, 0.0, 0.0],
        [0.0, 0.0, 10, 13.5, 15.0, 22.1, 26.0, 24.9, 13.0, 0.0, 0.0, 0.0],
        [
            500,
            500,
            500,
            100.0,
            100.0,
            100.0,
            100.0,
            500.0,
            500.0,
            500,
            500,
            500,
        ],
    ]
    Demand_recs = reformat_df(pd.DataFrame(data, columns=cols, index=inds))

    return Source_recs, Demand_recs


def main():
    m = Container(delayed_execution=True)

    # SETS #
    n = Set(
        m,
        name="n",
        records=[
            "Source_1",
            "Source_2",
            *[f"Node_{n}" for n in range(1, 6)],
            "User_1",
            "User_2",
            "Res_1",
            "Res_2",
            "Outlet",
        ],
        description="nodes",
    )
    nn = Set(
        m,
        name="nn",
        domain=[n],
        records=[f"Node_{n}" for n in range(1, 6)],
        description="Nodes",
    )
    ns = Set(
        m,
        name="ns",
        domain=[n],
        records=["Source_1", "Source_2"],
        description="Sources nodes",
    )
    nr = Set(
        m,
        name="nr",
        domain=[n],
        records=["User_1", "User_2", "Outlet"],
        description="Users nodes",
    )
    nl = Set(
        m,
        name="nl",
        domain=[n],
        records=["Res_1", "Res_2"],
        description="Reservoir nodes",
    )
    n_from_n = Set(
        m,
        name="n_from_n",
        domain=[n, n],
        records=[
            ("Res_1", "Source_1"),
            ("Res_2", "Source_2"),
            ("Node_1", "Res_1"),
            ("Node_1", "Res_2"),
            ("Node_2", "Node_1"),
            ("User_1", "Node_2"),
            ("Node_3", "Node_2"),
            ("Node_3", "User_1"),
            ("Node_4", "Node_3"),
            ("User_2", "Node_4"),
            ("Node_5", "Node_4"),
            ("Node_5", "User_2"),
            ("outlet", "Node_5"),
        ],
        description="Topology of the network",
    )
    n_to_nr = Set(
        m,
        name="n_to_nr",
        domain=[n, n],
        records=[
            ("Node_2", "User_1"),
            ("Node_4", "User_2"),
            ("Node_5", "Outlet"),
        ],
        description="Topology of the network",
    )
    t = Set(
        m,
        name="t",
        records=[
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "Jun",
            "Jul",
            "Aug",
            "Sep",
            "Oct",
            "Nov",
            "Dec",
        ],
        description="months",
    )

    # ALIAS #
    n1 = Alias(m, name="n1", alias_with=n)

    # PARAMETERS #
    Ini_S = Parameter(
        m,
        name="Ini_S",
        domain=[n],
        records=[("Res_1", 1000), ("Res_2", 300)],
        description="Initial storage in reservoirs. (m3)",
    )
    ret = Parameter(
        m,
        name="ret",
        domain=[n],
        records=[("User_1", 0.5), ("User_2", 0.5), ("Outlet", 0.0)],
        description="Return flow coefficients",
    )
    Source = Parameter(
        m,
        name="Source",
        domain=[n, t],
        records=data_records()[0],
        description="Flow of water (m3 per sec)",
    )
    Demand = Parameter(
        m,
        name="Demand",
        domain=[n, t],
        records=data_records()[1],
        description="Water demands (m3 per sec)",
    )

    # VARIABLES #
    U = Variable(
        m,
        name="U",
        type="positive",
        domain=[n, t],
        description="Diversions of water from node n in period t (m3)",
    )
    q = Variable(
        m,
        name="q",
        type="positive",
        domain=[n, t],
        description="Inflows in nodde n in period t (m3)",
    )
    r = Variable(
        m,
        name="r",
        type="positive",
        domain=[n, t],
        description="Releases from node n in period t (m3)",
    )
    s = Variable(
        m,
        name="s",
        type="positive",
        domain=[n, t],
        description="Storages of water in node n in period t (m3)",
    )

    obj = Variable(m, name="obj")

    # Upper bounds on users.
    U.up[n, t] = Demand[n, t]

    # Upper bounds on reservoirs.
    s.up["Res_1", t] = 1000
    s.up["Res_2", t] = 300

    # Lower storage in reservoirs (December).
    s.lo["Res_1", "Dec"] = 1000
    s.lo["Res_2", "Dec"] = 300

    # EQUATIONS #
    R_no = Equation(
        m, name="R_no", type="regular", domain=[n, t], description="Node"
    )
    R_ns = Equation(
        m, name="R_ns", type="regular", domain=[n, t], description="Source"
    )
    R_nr = Equation(
        m,
        name="R_nr",
        type="regular",
        domain=[n, t],
        description="Irrigation node",
    )
    R_nl = Equation(
        m,
        name="R_nl",
        type="regular",
        domain=[n, t],
        description="Reservoirs node",
    )
    R_nn = Equation(
        m, name="R_nn", type="regular", domain=[n, t], description="Node"
    )
    Objective = Equation(
        m, name="Objective", type="regular", description="Objective function"
    )

    R_no[n, t].where[nn[n]] = r[n, t] == q[n, t]

    R_ns[n, t].where[ns[n]] = r[n, t] == Source[n, t]

    R_nr[n, t].where[nr[n]] = r[n, t] == ret[n] * U[n, t]

    R_nl[n, t].where[nl[n]] = (
        s[n, t]
        == Ini_S[n].where[Ord(t) == 1]
        + s[n, t.lag(1)].where[Ord(t) >= 1]
        + q[n, t]
        - r[n, t]
    )

    R_nn[n, t] = q[n, t] == Sum(n1.where[n_from_n[n, n1]], r[n1, t]) - Sum(
        n1.where[n_to_nr[n, n1]], U[n1, t]
    )

    Objective.definition = obj == Sum(
        t, Sum(n.where[nr[n]], gams_math.power((U[n, t] - Demand[n, t]), 2))
    )

    riversys = Model(
        m,
        name="riversys",
        equations=m.getEquations(),
        problem="nlp",
        sense="min",
        objective=obj,
    )

    riversys.solve()

    print("Objective Function Value:  ", round(obj.toValue(), 4))

    # Show the solution

    rep = Parameter(m, name="rep", domain=[t, "*"])

    rep[t, "User_1"] = U.l["User_1", t]
    rep[t, "Demand_1"] = Demand["User_1", t]
    rep[t, "User_2"] = U.l["User_2", t]
    rep[t, "Demand_2"] = Demand["User_2", t]
    rep[t, "Outlet"] = U.l["Outlet", t]
    rep[t, "Demand"] = Demand["Outlet", t]

    print("Solution:\n", rep.pivot().round(3))

    # End riversys


if __name__ == "__main__":
    main()

tests/integration/models/riversys.py
GAMS-dev/gamspy
"""
Robust linear programming as an SOCP (ROBUSTLP)

Consider a linear optimization problem of the form
min_x c^Tx s.t. a_i^Tx <= b_i, i=1,..,m.

In practice, the coefficient vectors a_i may not be known perfectly,
as they are subject to noise. Assume that we only know that a_i in E_i,
where E_i are given ellipsoids. In robust optimization, we seek to minimize
the original objective, but we insist that each constraint be satisfied,
irrespective of the choice of the corresponding vector a_i in E_i.
We obtain the second-order cone optimization problem
min_x c^Tx s.t. a'_i^Tx + ||R_i^Tx|| <= b_i, i=1,..,m,
where E_i = { a'_i + R_iu | ||u|| <= 1}. In the above, we observe that
the feasible set is smaller than the original one, due to the terms involving
the l_2-norms.

The figure above illustrates the kind of feasible set one obtains in a
particular instance of the above problem, with spherical uncertainties
(that is, all the ellipsoids are spheres, R_i = rho I for some rho >0).
We observe that the robust feasible set is indeed contained in the original
polyhedron.

In this particular example we allow coefficients A(i,*) to vary in an
ellipsoid. The robust LP is reformulated as a SOCP.

Contributed by Michael Ferris, University of Wisconsin, Madison


Lobo, M S, Vandenberghe, L, Boyd, S, and Lebret, H, Applications of
Second Order Cone Programming. Linear Algebra and its Applications,
Special Issue on Linear Algebra in Control, Signals and Image
Processing. 284 (November, 1998).

Keywords: linear programming, quadratic constraint programming, robust
optimization, second order cone programming
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import uniform


def main():
    m = Container(delayed_execution=True)

    mu = 1e-2

    # Set
    i = Set(m, name="i", records=[f"{idx}" for idx in range(1, 8)])
    j = Set(m, name="j", records=[f"{idx}" for idx in range(1, 5)])

    # Data
    a = Parameter(m, name="a", domain=[i, j])
    b = Parameter(m, name="b", domain=[i])
    c = Parameter(m, name="c", domain=[j])
    b[i] = 1
    c[j] = -1

    a[i, j] = uniform(0, 1)

    # Variable
    x = Variable(m, name="x", domain=[j])
    obj = Variable(m, name="obj")

    # Equation
    defobj = Equation(m, name="defobj")
    cons = Equation(m, name="cons", domain=[i])

    defobj.definition = obj == Sum(j, c[j] * x[j])
    cons[i] = Sum(j, a[i, j] * x[j]) <= b[i]

    lpmod = Model(
        m,
        name="lpmod",
        equations=[defobj, cons],
        problem="LP",
        sense=Sense.MIN,
        objective=obj,
    )
    lpmod.solve()

    results = Parameter(m, name="results", domain=["*", "*"])
    results["lp", j] = x.l[j]
    results["lp", "obj"] = obj.l

    lmbda = Variable(m, name="lambda", domain=[j])
    gamma = Variable(m, name="gamma", domain=[j])

    lpcons = Equation(m, name="lpcons", domain=[i])
    defdual = Equation(m, name="defdual", domain=[j])

    lpcons[i] = (
        mu * Sum(j, lmbda[j] + gamma[j]) + Sum(j, a[i, j] * x[j]) <= b[i]
    )
    defdual[j] = lmbda[j] - gamma[j] == x[j]

    lproblp = Model(
        m,
        name="lproblp",
        equations=[defobj, lpcons, defdual],
        problem="LP",
        sense=Sense.MIN,
        objective=obj,
    )
    lproblp.solve()

    results["roblp", j] = x.l[j]
    results["roblp", "obj"] = obj.l

    k = Alias(m, name="k", alias_with=j)
    p = Parameter(m, name="p", domain=[i, j, k])
    p[i, j, j] = mu

    y = Variable(m, name="y", domain=[i])
    v = Variable(m, name="v", domain=[i, k])

    defrhs = Equation(m, name="defrhs", domain=[i])
    defv = Equation(m, name="defv", domain=[i, k])
    socpqcpcons = Equation(m, name="socpqcpcons", domain=[i])

    defrhs[i] = y[i] == b[i] - Sum(j, a[i, j] * x[j])
    defv[i, k] = v[i, k] == Sum(j, p[i, j, k] * x[j])
    socpqcpcons[i] = y[i] ** 2 >= Sum(k, v[i, k] ** 2)

    roblpqcp = Model(
        m,
        name="roblpqcp",
        equations=[defobj, socpqcpcons, defrhs, defv],
        problem=Problem.QCP,
        sense=Sense.MIN,
        objective=obj,
    )

    y.lo[i] = 0

    roblpqcp.solve()
    results["qcp", j] = x.l[j]
    results["qcp", "obj"] = obj.l

    print(results.records)


if __name__ == "__main__":
    main()

tests/integration/models/robustlp.py
GAMS-dev/gamspy
"""
Robust Optimization (ROTDK)

Robust Optimization.


Laguna, M, Applying Robust Optimization to Capacity Expansion of
One Location in Telecommunications with Demand Uncertainty.
Management Science 44, 11 (1998), 101-110.

Keywords: mixed integer linear programming, robust optimization, capacity
expansion,
          time-dependent knapsack problem
"""
from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import normal
from gamspy.math import power
from gamspy.math import Round
from gamspy.math import uniform


def main():
    m = Container(delayed_execution=True)

    # Set
    s = Set(
        m,
        name="s",
        records=[str(i) for i in range(1, 1001)],
        description="scenarios",
    )
    t = Set(
        m,
        name="t",
        records=[f"t{i}" for i in range(1, 13)],
        description="time periods",
    )
    j = Set(
        m,
        name="j",
        records=[f"C{i:03}" for i in range(1, 11)],
        description="components",
    )

    tt = Alias(m, name="tt", alias_with=t)

    # Parameter
    di = Parameter(m, name="di", domain=[s, t], description="increment")
    d = Parameter(m, name="D", domain=[t, s], description="demand")
    c = Parameter(m, name="c", domain=[j], description="capacity size")
    p = Parameter(m, name="p", domain=[j], description="capacity cost")
    mu = Parameter(m, name="mu", description="mean capacity parameter")
    sigma = Parameter(m, name="sigma", description="std capacity parameter")

    mu_value = 100
    sigma_value = 10
    mu.assignment = mu_value
    sigma.assignment = sigma_value

    c[j] = Round(uniform(1, mu_value))
    p[j] = Round(mu_value + c[j] + uniform(-sigma_value, sigma_value))

    di[s, t].where[(Ord(s)) <= (0.25 * Card(s))] = Round(normal(50, 10))
    di[s, t].where[(Ord(s) > 0.25 * Card(s)) & (Ord(s) <= 0.75 * Card(s))] = (
        Round(normal(100, 20))
    )
    di[s, t].where[Ord(s) > 0.75 * Card(s)] = Round(normal(150, 40))

    d[t, s] = Sum(tt.where[Ord(tt) <= Ord(t)], di[s, tt])

    # Parameter
    dis = Parameter(m, name="dis", domain=[t], description="discount factor")
    w = Parameter(m, name="w", description="shortage penalty")

    dis[t] = power(0.86, Ord(t) - 1)
    w.assignment = 5

    # Variable
    x = Variable(
        m, name="x", type="integer", domain=[j, t], description="expansion"
    )
    z = Variable(
        m,
        name="z",
        type="positive",
        domain=[s],
        description="max capacity shortage",
    )
    cap = Variable(
        m,
        name="cap",
        type="free",
        domain=[t],
        description="installed capacity",
    )
    obj = Variable(m, name="obj", type="free")

    # Equation
    capbal = Equation(
        m,
        name="capbal",
        domain=[t],
        description="capacity balance",
    )
    dembal = Equation(
        m,
        name="dembal",
        domain=[t, s],
        description="demand balance",
    )
    objdef = Equation(m, name="objdef")

    objdef.definition = obj == Sum((j, t), dis[t] * p[j] * x[j, t]) + w / Card(
        s
    ) * Sum(s, z[s])

    capbal[t] = cap[t] == cap[t.lag(1)] + Sum(j, c[j] * x[j, t])

    dembal[t, s] = cap[t] + z[s] >= d[t, s]

    rotdk = Model(
        m,
        name="rotdk",
        equations=m.getEquations(),
        problem="mip",
        sense=Sense.MIN,
        objective=obj,
    )

    m.addOptions({"limCol": 0, "limRow": 0, "resLim": 3})

    rotdk.solve()

    print("Objective Function Value: ", round(obj.records.level[0], 2))


if __name__ == "__main__":
    main()

tests/integration/models/rotdk.py
GAMS-dev/gamspy
"""
Social Golfer Problem (SGOLFER)

In a golf club, there are 32 social golfers, each of whom play golf once a
week,
and always in groups of 4. The problem is to build a schedule of play for 10
weeks with ''maximum socialisation''; that is, as few repeated meetings as
possible. More generally the problem is to schedule m groups of n golfers over
p weeks, with maximum socialisation.


Warwick, H, The Fully Social Golfer Problem. In Smith, B, and Warwick, H, Eds,
Proceedings of the Third International Workshop on Symmetry in Constraint
Satisfaction Problems (SymCon 2003). 2003, pp. 75-85.

Keywords: mixed integer linear programming, mixed integer nonlinear
programming,
          social golfer problem, combinatorial optimization
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import Max as gams_max


def main(gr_c=8, gg_c=4, nw_c=10, mip=False):
    cont = Container(delayed_execution=True)

    gf_c = gr_c * gg_c

    # Set
    gf = Set(cont, name="gf", records=[str(i) for i in range(1, gf_c + 1)])
    gr = Set(cont, name="gr", records=[str(i) for i in range(1, gr_c + 1)])
    w = Set(cont, name="w", records=[str(i) for i in range(1, nw_c + 1)])

    # Alias
    gf1 = Alias(cont, name="gf1", alias_with=gf)
    gf2 = Alias(cont, name="gf2", alias_with=gf)

    mgf = Set(cont, name="mgf", domain=[gf1, gf2])
    mgf[gf1, gf2] = Ord(gf2) > Ord(gf1)

    # Variable
    x = Variable(cont, name="x", type="binary", domain=[w, gr, gf])
    m = Variable(cont, name="m", type="free", domain=[w, gr, gf, gf])
    numm = Variable(cont, name="numm", type="free", domain=[gf, gf])
    redm = Variable(cont, name="redm", type="free", domain=[gf, gf])
    obj = Variable(cont, name="obj", type="free")

    # Equation
    defx = Equation(cont, name="defx", domain=[w, gf])
    defgr = Equation(cont, name="defgr", domain=[w, gr])
    defm = Equation(cont, name="defm", domain=[w, gr, gf, gf])
    defnumm = Equation(cont, name="defnumm", domain=[gf, gf])
    defredm = Equation(cont, name="defredm", domain=[gf, gf])
    defobj = Equation(cont, name="defobj")

    if not isinstance(mip, bool):
        raise Exception(
            f"Argument <mip> should be a boolean. Not {type(mip)}."
        )

    if mip:
        m.type = "binary"
        redm.type = "positive"

        defm2 = Equation(cont, name="defm2", domain=[w, gr, gf, gf])
        defm3 = Equation(cont, name="defm3", domain=[w, gr, gf, gf])

        defm[w, gr, mgf[gf1, gf2]] = m[w, gr, mgf] <= x[w, gr, gf1]

        defm2[w, gr, mgf[gf1, gf2]] = m[w, gr, mgf] <= x[w, gr, gf2]

        defm3[w, gr, mgf[gf1, gf2]] = (
            m[w, gr, mgf] >= x[w, gr, gf1] + x[w, gr, gf2] - 1
        )

        defredm[mgf] = redm[mgf] >= numm[mgf] - 1

    else:
        defm[w, gr, mgf[gf1, gf2]] = (
            m[w, gr, mgf] == x[w, gr, gf1] & x[w, gr, gf2]
        )

        defredm[mgf] = redm[mgf] == gams_max(Number(0), numm[mgf] - 1)

    defx[w, gf] = Sum(gr, x[w, gr, gf]) == 1

    defgr[w, gr] = Sum(gf, x[w, gr, gf]) == gg_c

    defnumm[mgf] = numm[mgf] == Sum((w, gr), m[w, gr, mgf])

    defobj.definition = obj == Sum(mgf, redm[mgf])

    x.l[w, gr, gf].where[
        ((Ord(gr) - 1) * gg_c + 1 <= Ord(gf)) & (Ord(gf) <= (Ord(gr)) * gg_c)
    ] = 1

    social_golfer_mip = Model(
        cont,
        name="social_golfer_mip",
        equations=cont.getEquations(),
        problem="mip",
        sense=Sense.MIN,
        objective=obj,
    )

    social_golfer_minlp = Model(
        cont,
        name="social_golfer_minlp",
        equations=cont.getEquations(),
        problem="minlp",
        sense=Sense.MIN,
        objective=obj,
    )

    if mip:
        social_golfer_mip.solve()
    else:
        social_golfer_minlp.solve()

    print("Objective Function Variable: ", obj.records.level[0])


if __name__ == "__main__":
    main()

tests/integration/models/sgolfer.py
GAMS-dev/gamspy
"""
Spatial Equilibrium (SPATEQU)

This program is written for the spatial equilibrium model with linear supply
and demand having two products and three regions.

The model contains multiple approaches (LP, NLP, and MCP) for solving this
problem.


Phan, S H, Policy option to promote the wood-processing industry in
northern Vietnam, forth coming. PhD thesis,
The University of Queensland, Australia, 2011.

Phan, S H, and Harrison, S, A Review of the Formulation and
Application of the Spatial Equilibrium Models to Analyze
Policy. Journal of Forestry Research 22, 4 (2011).

The numerical example has been taken from:
Takayama, T, and Judge, G G, Spatial Equilibrium and Quadratic
Programming. Journal of Farm Economics 46, 1 (1964), 67-93

Contributed by: Phan Sy Hieu, November 2010

Keywords: linear programming, nonlinear programming, mixed complementarity
          problem, spatial equilibrium model
"""
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Model
from gamspy import Problem
from gamspy import Sense
from gamspy import Sum


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/spatequ.gdx",
    )

    # Sets
    c, r, rr, cc = m.getSymbols(["c", "r", "rr", "cc"])

    # Parameters
    (
        AlphaD,
        BetaD,
        BetadSq,
        AlphaS,
        BetaS,
        BetasSq,
        TCost,
    ) = m.getSymbols(
        [
            "AlphaD",
            "BetaD",
            "BetadSq",
            "AlphaS",
            "BetaS",
            "BetasSq",
            "TCost",
        ]
    )

    # Variables
    DINT, SINT, TC, Qd, Qs, X, P, OBJ = m.getSymbols(
        ["DINT", "SINT", "TC", "Qd", "Qs", "X", "P", "OBJ"]
    )

    # Equations
    (
        DEM,
        DEMLOG,
        DEMINT,
        SUP,
        SUPLOG,
        SUPINT,
        SDBAL,
        PDIF,
        TRANSCOST,
        SX,
        DX,
        OBJECT,
        IN_OUT,
        DOM_TRAD,
    ) = m.getSymbols(
        [
            "DEM",
            "DEMLOG",
            "DEMINT",
            "SUP",
            "SUPLOG",
            "SUPINT",
            "SDBAL",
            "PDIF",
            "TRANSCOST",
            "SX",
            "DX",
            "OBJECT",
            "IN_OUT",
            "DOM_TRAD",
        ]
    )

    DEM[r, c] = AlphaD[r, c] + Sum(cc, (BetaD[r, c, cc] * P[r, c])) == Qd[r, c]

    DEMLOG[r, c] = (
        AlphaD[r, c] + Sum(cc, (BetaD[r, c, cc] * gams_math.log(P[r, c])))
        == Qd[r, c]
    )

    DEMINT[r, c] = (
        DINT[r, c]
        == AlphaD[r, c] * P[r, c]
        + Sum(cc, BetadSq[r, c, cc] * P[r, cc]) * P[r, c]
    )

    SUP[r, c] = AlphaS[r, c] + Sum(cc, (BetaS[r, c, cc] * P[r, c])) == Qs[r, c]
    SUPLOG[r, c] = (
        AlphaS[r, c] + Sum(cc, (BetaS[r, c, cc] * gams_math.log(P[r, c])))
        == Qs[r, c]
    )

    SUPINT[r, c] = (
        SINT[r, c]
        == AlphaS[r, c] * P[r, c]
        + Sum(cc, BetasSq[r, c, cc] * P[r, cc]) * P[r, c]
    )

    SDBAL[c] = Sum(r, Qd[r, c]) == Sum(r, Qs[r, c])

    TRANSCOST.definition = TC == Sum((r, rr, c), X[r, rr, c] * TCost[r, rr, c])

    OBJECT.definition = OBJ == Sum([r, c], DINT[r, c] - SINT[r, c]) - TC

    PDIF[r, rr, c] = P[r, c] - P[rr, c] <= TCost[r, rr, c]

    SX[r, c] = Sum(rr, X[r, rr, c]) == Qs[r, c]

    DX[r, c] = Sum(rr, X[rr, r, c]) == Qd[r, c]

    IN_OUT[r, c] = Qs[r, c] + Sum(rr, X[rr, r, c] - X[r, rr, c]) == Qd[r, c]

    DOM_TRAD[r, rr, c] = P[r, c] + TCost[r, rr, c] >= P[rr, c]

    P2R3_Linear = Model(
        m,
        name="P2R3_Linear",
        equations=[DEM, SUP, SDBAL, PDIF, TRANSCOST, SX, DX],
        problem="LP",
        sense=Sense.MIN,
        objective=TC,
    )
    P2R3_LinearLog = Model(
        m,
        name="P2R3_LinearLog",
        equations=[DEMLOG, SUPLOG, SDBAL, PDIF, TRANSCOST, SX, DX],
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=TC,
    )
    P2R3_NonLinear = Model(
        m,
        name="P2R3_NonLinear",
        equations=P2R3_Linear.equations + [DEMINT, SUPINT, OBJECT],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=OBJ,
    )
    P2R3_MCP = Model(
        m,
        name="P2R3_MCP",
        equations=[DEM, SUP, IN_OUT, DOM_TRAD],
        matches={IN_OUT: P, DOM_TRAD: X},
        problem="MCP",
    )

    P2R3_Linear.solve()
    P2R3_LinearLog.solve()
    P2R3_NonLinear.solve()

    X.fx[r, r, c] = 0

    P2R3_MCP.solve()


if __name__ == "__main__":
    main()

tests/integration/models/spatequ.py
GAMS-dev/gamspy
"""
Stochastic Benders - Sequential GAMS Loop (SPBENDERS1)

This example demonstrates a stochastic Benders implementation for the
simple transport example.

This is the first example of a sequence of stochastic Benders
implementations using various methods to solve the master and
subproblem.

This first example implements the stochastic Benders algorithm using
sequential solves of the master and subproblems in a GAMS loop.

Keywords: linear programming, stochastic Benders algorithm, transportation
          problem
"""
import pandas as pd

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Prepare data
    cost = pd.DataFrame(
        [
            ["f1", "d1", 2.49],
            ["f1", "d2", 5.21],
            ["f1", "d3", 3.76],
            ["f1", "d4", 4.85],
            ["f1", "d5", 2.07],
            ["f2", "d1", 1.46],
            ["f2", "d2", 2.54],
            ["f2", "d3", 1.83],
            ["f2", "d4", 1.86],
            ["f2", "d5", 4.76],
            ["f3", "d1", 3.26],
            ["f3", "d2", 3.08],
            ["f3", "d3", 2.60],
            ["f3", "d4", 3.76],
            ["f3", "d5", 4.45],
        ]
    )

    scenarios = pd.DataFrame(
        [
            ["lo", "d1", 150],
            ["lo", "d2", 100],
            ["lo", "d3", 250],
            ["lo", "d4", 300],
            ["lo", "d5", 600],
            ["lo", "prob", 0.25],
            ["mid", "d1", 160],
            ["mid", "d2", 120],
            ["mid", "d3", 270],
            ["mid", "d4", 325],
            ["mid", "d5", 700],
            ["mid", "prob", 0.50],
            ["hi", "d1", 170],
            ["hi", "d2", 135],
            ["hi", "d3", 300],
            ["hi", "d4", 350],
            ["hi", "d5", 800],
            ["hi", "prob", 0.25],
        ]
    )

    cut_coefficients = pd.DataFrame(
        [
            [idx, f"d{center}", 0]
            for idx in range(1, 26)
            for center in range(1, 6)
        ]
    )

    # Set
    i = Set(m, name="i", records=["f1", "f2", "f3"])
    j = Set(m, name="j", records=["d1", "d2", "d3", "d4", "d5"])
    s = Set(m, name="s", records=["lo", "mid", "hi"])

    # Data
    capacity = Parameter(
        m,
        name="capacity",
        domain=[i],
        records=pd.DataFrame([["f1", 500], ["f2", 450], ["f3", 650]]),
    )
    demand = Parameter(
        m,
        name="demand",
        domain=[j],
        records=pd.DataFrame(
            [["d1", 160], ["d2", 120], ["d3", 270], ["d4", 325], ["d5", 700]]
        ),
    )
    prodcost = Parameter(m, name="prodcost", records=14)
    price = Parameter(m, name="price", records=24)
    wastecost = Parameter(m, name="wastecost", records=4)
    transcost = Parameter(m, name="transcost", domain=[i, j], records=cost)
    ScenarioData = Parameter(
        m, name="scenariodata", domain=[s, "*"], records=scenarios
    )

    # Set
    iter = Set(m, name="iter", records=[f"{idx}" for idx in range(1, 26)])
    itActive = Set(m, name="itActive", domain=[iter])

    # Parameter
    cutconst = Parameter(
        m,
        name="cutconst",
        domain=[iter],
        records=pd.DataFrame([[f"{idx}", 0] for idx in range(1, 26)]),
    )
    cutcoeff = Parameter(
        m, name="cutcoeff", domain=[iter, j], records=cut_coefficients
    )

    # Variable
    ship = Variable(m, name="ship", domain=[i, j], type="Positive")
    product = Variable(m, name="product", domain=[i])
    received = Variable(m, name="received", domain=[j])
    zmaster = Variable(m, name="zmaster")
    theta = Variable(m, name="theta")

    # Equation
    masterobj = Equation(m, name="masterobj")
    production = Equation(m, name="production", domain=[i])
    receive = Equation(m, name="receive", domain=[j])
    optcut = Equation(m, name="optcut", domain=[iter])

    masterobj.definition = zmaster == theta - Sum(
        (i, j), transcost[i, j] * ship[i, j]
    ) - Sum(i, prodcost * product[i])
    receive[j] = received[j] == Sum(i, ship[i, j])
    production[i] = product[i] == Sum(j, ship[i, j])
    optcut[itActive] = theta <= cutconst[itActive] + Sum(
        j, cutcoeff[itActive, j] * received[j]
    )
    product.up[i] = capacity[i]

    masterproblem = Model(
        m,
        name="masterproblem",
        equations=m.getEquations(),
        problem="LP",
        sense=Sense.MAX,
        objective=zmaster,
    )

    # Variable
    sales = Variable(m, name="sales", domain=[j], type="Positive")
    waste = Variable(m, name="waste", domain=[j], type="Positive")
    zsub = Variable(m, name="zsub")

    # Equation
    subobj = Equation(m, name="subobj")
    selling = Equation(m, name="selling", domain=[j])
    market = Equation(m, name="market", domain=[j])

    subobj.definition = zsub == Sum(j, price * sales[j]) - Sum(
        j, wastecost * waste[j]
    )
    selling[j] = sales[j] + waste[j] == received.l[j]
    market[j] = sales[j] <= demand[j]

    subproblem = Model(
        m,
        name="subproblem",
        equations=[subobj, selling, market],
        problem="LP",
        sense=Sense.MAX,
        objective=zsub,
    )

    # Scalar
    rgap = Parameter(m, name="rgap", records=0)
    lowerBound = Parameter(m, name="lowerBound", records=float("-inf"))
    upperBound = Parameter(m, name="upperBound", records=float("inf"))
    objMaster = Parameter(m, name="objMaster", records=0)
    objSub = Parameter(m, name="objSub", records=0)
    rtol = 0.001

    received.l[j] = 0

    for iteration, _ in iter.records.itertuples(index=False):
        objSub.assignment = 0

        for scenario, _ in s.records.itertuples(index=False):
            demand[j] = ScenarioData[scenario, j]
            subproblem.solve()
            objSub.assignment = (
                objSub + ScenarioData[scenario, "prob"] * zsub.l
            )
            cutconst[iteration] = cutconst[iteration] + ScenarioData[
                scenario, "prob"
            ] * Sum(j, market.m[j] * demand[j])
            cutcoeff[iteration, j] = (
                cutcoeff[iteration, j]
                + ScenarioData[scenario, "prob"] * selling.m[j]
            )

        itActive[iteration] = True

        if (
            lowerBound.records.values[0][0]
            < objMaster.records.values[0][0] + objSub.records.values[0][0]
        ):
            lowerBound.assignment = objMaster + objSub

        rgap.assignment = (upperBound - lowerBound) / (
            1 + gams_math.abs(upperBound)
        )
        if rgap.records.values[0][0] < rtol:
            break

        masterproblem.solve()
        upperBound.setRecords(zmaster.records["level"])
        objMaster.setRecords(zmaster.records["level"] - theta.records["level"])


if __name__ == "__main__":
    main()

tests/integration/models/spbenders1.py
GAMS-dev/gamspy
"""
Minimization of the weight of a speed reducer.
The weight of the speed reducer is to be minimized subject to constraints
on bending stress of the gear teeth, surface stress, transverse deflections
of the shafts and stresses in the shaft.

Datseris, P., Weight minimization of a speed reducer by heuristic
and decomposition technique. Mechanism and Machine Theory, vol.17, 1982,
pp. 255-262.

Aguirre, A.H., Munoz Zavala, A.E., Villa Diharce, E., Botello Rionada, S.,
COPSO: Constrained optimization via PSO algorithm. Comunicacion Tecnica
No I-07-04/22-02-2007. Center for Research in Mathematics (CIMAT), Mexico.
"""
import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # VARIABLES #
    x1 = Variable(m, name="x1")
    x2 = Variable(m, name="x2")
    x3 = Variable(m, name="x3")
    x4 = Variable(m, name="x4")
    x5 = Variable(m, name="x5")
    x6 = Variable(m, name="x6")
    x7 = Variable(m, name="x7")
    obj = Variable(m, name="obj")

    # EQUATION #
    g1 = Equation(m, name="g1", type="regular")
    g2 = Equation(m, name="g2", type="regular")
    g3 = Equation(m, name="g3", type="regular")
    g4 = Equation(m, name="g4", type="regular")
    g5 = Equation(m, name="g5", type="regular")
    g6 = Equation(m, name="g6", type="regular")
    g7 = Equation(m, name="g7", type="regular")
    g8 = Equation(m, name="g8", type="regular")
    g9 = Equation(m, name="g9", type="regular")
    g10 = Equation(m, name="g10", type="regular")
    g11 = Equation(m, name="g11", type="regular")
    g = Equation(m, name="g", type="regular")

    # Objective function to be minimized:
    g.definition = obj == (
        0.7854
        * x1
        * gams_math.power(x2, 2)
        * (3.3333 * gams_math.power(x3, 2) + 14.9334 * x3 - 43.0934)
        - 1.508 * x1 * (gams_math.power(x6, 2) + gams_math.power(x7, 2))
        + 7.4777 * (gams_math.power(x6, 3) + gams_math.power(x7, 3))
        + 0.7854 * (x4 * gams_math.power(x6, 2) + x5 * gams_math.power(x7, 2))
    )

    # Constraints:
    g1.definition = 27 / (x1 * gams_math.power(x2, 2) * x3) - 1 <= 0

    g2.definition = (
        397.5 / (x1 * gams_math.power(x2, 2) * gams_math.power(x3, 2)) - 1 <= 0
    )

    g3.definition = (1.93 * gams_math.power(x4, 3)) / (
        x2 * x3 * gams_math.power(x6, 4)
    ) - 1 <= 0

    g4.definition = (1.93 * gams_math.power(x5, 3)) / (
        x2 * x3 * gams_math.power(x7, 4)
    ) - 1 <= 0

    g5.definition = (
        gams_math.sqrt(gams_math.power((745 * x4) / (x2 * x3), 2) + 16900000)
    ) / (110 * gams_math.power(x6, 3)) - 1 <= 0

    g6.definition = (
        gams_math.sqrt(gams_math.power((745 * x5) / (x2 * x3), 2) + 15750000)
    ) / (85 * gams_math.power(x7, 3)) - 1 <= 0

    g7.definition = (x2 * x3) / 40 - 1 <= 0

    g8.definition = (5 * x2) / x1 - 1 <= 0

    g9.definition = x1 / (12 * x2) - 1 <= 0

    g10.definition = (1.5 * x6 + 1.9) / x4 - 1 <= 0

    g11.definition = (1.1 * x7 + 1.9) / x5 - 1 <= 0

    # Bounds on variables
    x1.lo.assignment = 2.6
    x1.up.assignment = 3.6
    x2.lo.assignment = 0.7
    x2.up.assignment = 0.8
    x3.lo.assignment = 17
    x3.up.assignment = 28
    x4.lo.assignment = 7.3
    x4.up.assignment = 8.3
    x5.lo.assignment = 7.8
    x5.up.assignment = 8.3
    x6.lo.assignment = 2.9
    x6.up.assignment = 3.9
    x7.lo.assignment = 5.0
    x7.up.assignment = 5.5

    speed = Model(
        m,
        name="speed",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=obj,
    )
    speed.solve()

    print("Objective Function Value:  ", round(obj.toValue(), 4))

    # End speed


if __name__ == "__main__":
    main()

tests/integration/models/speed.py
GAMS-dev/gamspy
"""
Equilibrium of System with Piecewise Linear Springs (SPRINGCHAIN)

This model finds the shape of a hanging chain consisting of
N springs and N-1 nodes. Each spring buckles under compression and each
node has a weight hanging from it. The springs are assumed
weightless. The goal is to minimize the potential energy of the
system.

We use rotated quadratic cone constraints to model the extension
of each spring.


M. Lobo, L. Vandenberghe, S. Boyd, and H. Lebret,
Applications of second-order cone programming, Linear Algebra and its
Applications, 284:193-228, November 1998, Special Issue on Linear Algebra
in Control, Signals and Image Processing.
"""
import math

import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    cont = Container(delayed_execution=True)

    N = 10
    NM1 = 9

    # Set
    n = Set(cont, name="n", records=[f"n{str(i)}" for i in range(N + 1)])

    # Data
    a_x = 0
    a_y = 0
    b_x = 2
    b_y = -1
    L0 = 2 * math.sqrt((a_x - b_x) ** 2 + (a_y - b_y) ** 2) / N
    g = 9.8
    k = 100

    m = Parameter(
        cont,
        name="m",
        domain=[n],
        records=pd.DataFrame([[f"n{i}", 1] for i in range(1, NM1 + 1)]),
    )

    # Variable
    obj = Variable(cont, name="obj")
    x = Variable(cont, name="x", domain=[n])
    y = Variable(cont, name="y", domain=[n])
    delta_x = Variable(cont, name="delta_x", domain=[n])
    delta_y = Variable(cont, name="delta_y", domain=[n])
    unit = Variable(cont, name="unit")
    t_L0 = Variable(cont, name="t_L0", domain=[n], type="Positive")
    t = Variable(cont, name="t", domain=[n], type="Positive")
    v = Variable(cont, name="v", type="Positive")

    # Equation
    pot_energy = Equation(cont, name="pot_energy")
    delta_x_eq = Equation(cont, name="delta_x_eq", domain=[n])
    delta_y_eq = Equation(cont, name="delta_y_eq", domain=[n])
    link_L0 = Equation(cont, name="link_L0", domain=[n])
    link_up = Equation(cont, name="link_up", domain=[n])
    cone_eq = Equation(cont, name="cone_eq")

    pot_energy.definition = (
        obj
        == Sum(n.where[Ord(n) > 1 & (Ord(n) < Card(n))], m[n] * g * y[n])
        + k * v
    )
    delta_x_eq[n] = delta_x[n] == x[n] - x[n.lag(1)]
    delta_y_eq[n] = delta_y[n] == y[n] - y[n.lag(1)]

    link_L0[n] = t_L0[n] == L0 + t[n]
    link_up[n].where[Ord(n) > 1] = (
        t_L0[n] ** 2 >= delta_x[n] ** 2 + delta_y[n] ** 2
    )

    cone_eq.definition = 2 * v * unit >= Sum(n.where[Ord(n) > 1], t[n] ** 2)

    spring = Model(
        cont,
        name="spring",
        equations=cont.getEquations(),
        problem=Problem.QCP,
        sense=Sense.MIN,
        objective=obj,
    )

    x.l[n] = ((Ord(n) - 1) / N) * b_x + (Ord(n) / N) * a_x
    y.l[n] = ((Ord(n) - 1) / N) * b_y + (Ord(n) / N) * a_y

    x.fx["n0"] = a_x
    y.fx["n0"] = a_y
    x.fx[f"n{N}"] = b_x
    y.fx[f"n{N}"] = b_y
    unit.fx.assignment = 1

    spring.solve()

    assert math.isclose(spring.objective_value, -185.4461, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/springchain.py
GAMS-dev/gamspy
"""
Minimal surface problem.
Find a function f that minimizes the array of its graph subject to some
constraints on the boundary of the domain of f.

Boyd, S., Vandenberghe, L., Convex Optimization, Cambridge University Press,
Cambridge, 2004.
"""
import gamspy.math as gams_math
from gamspy import Card
from gamspy import Container
from gamspy import Domain
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def sqr(x):
    return gams_math.power(x, 2)


def main():
    m = Container(delayed_execution=True)

    # SETS #
    X = Set(m, name="X", records=[f"I{i}" for i in range(1, 22)])
    Y = Set(m, name="Y", records=[f"J{j}" for j in range(1, 22)])
    inside = Set(m, name="inside", domain=[X, Y])

    # Exclude i1 and i21 from inside
    inside[X, Y].where[~((Ord(X) == 1) & (Ord(X) == Card(X)))] = True

    #   display inside

    # SCALAR #
    K = Parameter(m, name="K", records=10)

    # VARIABLES #
    obj = Variable(m, name="obj")
    f = Variable(m, name="f", domain=[X, Y], type="positive")

    # Bounds on variables, initial conditions, fixing conditions:
    f.up[X, Y] = 1
    f.l[X, Y] = 1.0
    f.fx[X, Y].where[(Ord(X) == 1) | (Ord(X) == Card(X))] = 1

    # EQUATION #
    objfun = Equation(m, name="objfun", type="regular")

    objfun.definition = obj == (
        (1 / sqr(K))
        * Sum(
            Domain(X, Y).where[inside[X, Y]],
            gams_math.sqrt(
                sqr((f[X.lead(1), Y] - f[X, Y]) / K)
                + sqr((f[X, Y.lead(1)] - f[X, Y]) / K)
                + 1
            ),
        )
    )

    surface = Model(
        m,
        name="surface",
        equations=m.getEquations(),
        problem="nlp",
        sense="MIN",
        objective=obj,
    )
    surface.solve()

    print("Objective Function Value:  ", round(obj.toValue(), 4))
    print("f(X,Y):  \n", f.pivot().round(3))

    # End surface


if __name__ == "__main__":
    main()

tests/integration/models/surface.py
GAMS-dev/gamspy
"""
Solving complex linear algebraic systems of equations.

References
Kalvelagen, E., (2002) Solving systems of linear equations with GAMS.
http://www.gams.com/~erwin/lineq.pdf
"""
import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def data_records():
    cols = ["i1", "i2", "rhs"]
    idxs = [("real", "i1"), ("real", "i2"), ("imag", "i1"), ("imag", "i2")]

    data = np.array([[30, 20, 14], [15, 8, 11], [10, -15, 5], [0, -4, -7]])

    idxs = pd.MultiIndex.from_tuples(idxs, names=["Index1", "Index2"])
    data = pd.DataFrame(data, columns=cols, index=idxs)
    data.reset_index(inplace=True)
    melted_data = data.melt(
        id_vars=["Index1", "Index2"], value_vars=["i1", "i2", "rhs"]
    )
    return melted_data


def main():
    m = Container(delayed_execution=True)

    # SET #
    i = Set(m, name="i", records=["i1", "i2"])

    # ALIAS #
    j = Alias(m, name="j", alias_with=i)

    # PARAMETER #
    data = Parameter(
        m, name="data", domain=["*", "*", "*"], records=data_records()
    )

    # VARIABLES #
    rx = Variable(
        m, name="rx", domain=[i], description="real part of the solution"
    )
    ix = Variable(
        m, name="ix", domain=[i], description="imaginary part of the solution"
    )
    obj = Variable(
        m, name="obj", description="variable of a virtual objective"
    )

    # EQUATIONS #
    real = Equation(
        m,
        name="real",
        type="regular",
        domain=[i],
        description="real part of the system",
    )
    imag = Equation(
        m,
        name="imag",
        type="regular",
        domain=[i],
        description="imaginary part of the system",
    )
    eobj = Equation(
        m, name="eobj", type="regular", description="name of the objective"
    )

    eobj.definition = obj == 0
    real[i] = (
        Sum(j, data["real", i, j] * rx[j] - data["imag", i, j] * ix[j])
        == data["real", i, "rhs"]
    )
    imag[i] = (
        Sum(j, data["imag", i, j] * rx[j] + data["real", i, j] * ix[j])
        == data["imag", i, "rhs"]
    )

    syscomp = Model(
        m,
        name="syscomp",
        equations=m.getEquations(),
        problem="lp",
        sense="MIN",
        objective=obj,
    )
    syscomp.solve()

    # REPORTING PARAMETER
    rep = Parameter(m, name="rep", domain=["*", i])

    rep["rx", i] = rx.l[i]
    rep["ix", i] = ix.l[i]

    print("Objective Function Value:  ", round(obj.toValue(), 4), "\n")
    print("Solution Summary:\n", rep.pivot().round(3))

    # End of SysComp


if __name__ == "__main__":
    main()

tests/integration/models/syscomp.py
GAMS-dev/gamspy
"""
Tank Size Design Problem - (TANKSIZE)

We discuss a tank design problem for a multi product plant, in which the
optimal cycle time and the optimal campaign size are unknown. A mixed in-
teger nonlinear programming formulation is presented, where non-convexities
are due to the tank investment cost, storage cost, campaign setup cost and
variable production rates. The objective of the optimization model is to
minimize the sum of the production cost per ton per product produced. A
continuous-time mathematical programming formulation for the problem is
implemented with a fixed number of event points.


Rebennack, S, Kallrath, J, and Pardalos, P M, Optimal Storage Design
for a Multi-Product Plant: A Non-Convex MINLP Formulation. Tech. rep.,
University of Florida, 2009. Submitted to Computers and Chemical
Engineering

Keywords: mixed integer nonlinear programming, storage design, global
optimization
          continuous-time model, chemical engineering
"""
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Container
from gamspy import Model
from gamspy import Sense
from gamspy import Sum


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/tanksize.gdx",
    )

    # Sets
    p, n, pp = m.getSymbols(["p", "n", "pp"])

    # Parameters
    (
        PRMIN,
        PRMAX,
        SLB,
        SUB,
        SI,
        DLB,
        DUB,
        DEMAND,
        TS,
        CSTI,
        CSTC,
        B,
        pdata,
        DPD,
        L,
        CAL,
        PRL,
        CSTCMin,
        CSTCMax,
    ) = m.getSymbols(
        [
            "PRMIN",
            "PRMAX",
            "SLB",
            "SUB",
            "SI",
            "DLB",
            "DUB",
            "DEMAND",
            "TS",
            "CSTI",
            "CSTC",
            "B",
            "pdata",
            "DPD",
            "L",
            "CAL",
            "PRL",
            "CSTCMin",
            "CSTCMax",
        ]
    )

    # Variables
    d, pC, s, sM, sH, cI, cC, cS, T, omega, cPT = m.getSymbols(
        ["d", "pC", "s", "sM", "sH", "cI", "cC", "cS", "T", "omega", "cPT"]
    )

    # Equations
    (
        TIMECAP,
        UNIQUE,
        MATBAL,
        TANKCAP,
        PPN1,
        PPN2,
        SCCam1,
        SCCam2,
        DEFcC,
        DEFcI,
        DEFcS,
        DefsH,
        DEFcPT,
        NONIDLE,
        SEQUENCE,
        SYMMETRY,
    ) = m.getSymbols(
        [
            "TIMECAP",
            "UNIQUE",
            "MATBAL",
            "TANKCAP",
            "PPN1",
            "PPN2",
            "SCCam1",
            "SCCam2",
            "DEFcC",
            "DEFcI",
            "DEFcS",
            "DefsH",
            "DEFcPT",
            "NONIDLE",
            "SEQUENCE",
            "SYMMETRY",
        ]
    )

    TIMECAP.definition = Sum(n, d[n] + Sum(p, TS[p] * omega[p, n])) == T
    UNIQUE[n] = Sum(p, omega[p, n]) <= 1
    NONIDLE[n] = Sum(p, DUB[p] * omega[p, n]) >= d[n]
    MATBAL[p, n] = s[p, n.lead(1)] == s[p, n] + pC[p, n] - DPD[p] * (
        d[n] + Sum(pp, TS[pp] * omega[pp, n])
    )
    TANKCAP[p, n] = s[p, n] <= sM[p]
    PPN1[p, n] = pC[p, n] <= PRMAX[p] * d[n] * omega[p, n]
    PPN2[p, n] = pC[p, n] >= PRMIN[p] * d[n] * omega[p, n]
    SCCam2[n] = d[n] >= Sum(p, DLB[p] * omega[p, n])
    SCCam1[n] = d[n] <= Sum(p, DUB[p] * omega[p, n])
    DEFcPT.definition = (cPT * L - cI) * T == cC + cS
    DEFcC.definition = cC == Sum([p, n], CSTC[p] * omega[p, n])
    DEFcI.definition = cI == B * Sum(p, gams_math.sqrt(sM[p]))
    DEFcS.definition = cS == Sum(
        [p, n], CSTI[p] * sH[p, n] * (d[n] + Sum(pp, TS[pp] * omega[pp, n]))
    )
    DefsH[p, n] = sH[p, n] == 0.5 * (s[p, n.lead(1)] + s[p, n]) - SLB[p]
    SEQUENCE[p, n] = 1 - omega[p, n] >= omega[p, n.lead(1, "linear")]
    SYMMETRY[n] = Sum(p, omega[p, n]) >= Sum(p, omega[p, n.lead(1, "linear")])

    s.lo[p, n] = SLB[p]
    s.up[p, n] = SUB[p]
    s.fx["P1", "N1"] = SLB["P1"]
    omega.fx[p, "N1"] = 0
    omega.fx["P1", "N1"] = 1
    omega.fx["P1", "N2"] = 0
    sM.lo[p] = SLB[p]
    sM.up[p] = SUB[p]

    Sequenz = Model(
        m,
        name="Sequenz",
        equations=m.getEquations(),
        problem="MINLP",
        sense=Sense.MIN,
        objective=cPT,
    )
    omega.l[p, n] = gams_math.uniform(0, 1)

    Sequenz.solve()


if __name__ == "__main__":
    main()

tests/integration/models/tanksize.py
GAMS-dev/gamspy
"""
Antalya Forestry Model - Steady State (TFORSS)

This model finds the best management plan for new forests in a steady state
condition.


Bergendorff, H, Glenshaw, P, and Meeraus, A, The Planning of Investment
Programs in the Paper Industry. Tech. rep., The World Bank, 1980.

Keywords: linear programming, forestry, scenario analysis, investment planning,
          forest management planning
"""
from pathlib import Path

import numpy as np

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import power
from gamspy.math import Round


def main():
    cont = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/tforss.gdx",
    )

    # Sets
    c, cf, cl, s, k, at, p, m = cont.getSymbols(
        ["c", "cf", "cl", "s", "k", "at", "p", "m"]
    )

    # Parameters
    scd, land, ymf, a, b, pc, pd, nu, age = cont.getSymbols(
        [
            "scd",
            "land",
            "ymf",
            "a",
            "b",
            "pc",
            "pd",
            "nu",
            "age",
        ]
    )

    # Scalar
    mup, muc, life, rho = cont.getSymbols(
        [
            "mup",
            "muc",
            "life",
            "rho",
        ]
    )

    age[at] = 10 * Ord(at)

    # Model Definition #

    # Equation
    lbal = Equation(
        cont,
        name="lbal",
        domain=[cl],
        description="log balances",
    )
    bal = Equation(
        cont,
        name="bal",
        domain=[c],
        description="material balances of wood processing",
    )
    cap = Equation(
        cont,
        name="cap",
        domain=[m],
        description="wood processing capacities",
    )
    landc = Equation(
        cont,
        name="landc",
        domain=[s, k],
        description="land availability constraint",
    )
    ainvc = Equation(cont, name="ainvc", description="investment cost")
    aproc = Equation(cont, name="aproc", description="process cost")
    asales = Equation(cont, name="asales", description="sales revenue")
    acutc = Equation(cont, name="acutc", description="cutting cost")
    aplnt = Equation(cont, name="aplnt", description="planting cost")
    benefit = Equation(cont, name="benefit")

    # Variable
    v = Variable(
        cont,
        name="v",
        type="positive",
        domain=[s, k, at],
        description="management of new forest   (1000ha per year)",
    )
    r = Variable(
        cont,
        name="r",
        domain=[c],
        description="supply of logs to industry (1000m3 per year)",
    )
    z = Variable(
        cont,
        name="z",
        type="positive",
        domain=[p],
        description="process level        (1000m3 input per year)",
    )
    h = Variable(
        cont,
        name="h",
        domain=[m],
        description="capacity             (1000m3 input per year)",
    )
    x = Variable(
        cont,
        name="x",
        type="positive",
        domain=[c],
        description="final shipments        (1000 units per year)",
    )
    phik = Variable(
        cont,
        name="phik",
        description="investment cost           (1000us$ per year)",
    )
    phir = Variable(
        cont,
        name="phir",
        description="process cost              (1000us$ per year)",
    )
    phix = Variable(
        cont,
        name="phix",
        description="sales revenue             (1000us$ per year)",
    )
    phil = Variable(
        cont,
        name="phil",
        description="cutting cost              (1000us$ per year)",
    )
    phip = Variable(
        cont,
        name="phip",
        description="planting cost             (1000us$ per year)",
    )
    phi = Variable(
        cont,
        name="phi",
        description="total benefits             (discounted cost)",
    )

    lbal[cl] = r[cl] == Sum([s, k, at], ymf[at, k, s, cl] * v[s, k, at])

    bal[c] = Sum(p, a[c, p] * z[p]) + r[c].where[cl[c]] >= x[c].where[cf[c]]

    cap[m] = Sum(p, b[m, p] * z[p]) == h[m]

    landc[s, k] = Sum(at, v[s, k, at] * age[at]) <= land[s] * scd[k]

    ainvc.definition = phik == rho / (1 - power((1 + rho), (-life))) * Sum(
        m, nu[m] * h[m]
    )

    aproc.definition = phir == Sum(p, pc[p] * z[p])

    asales.definition = phix == Sum(cf, pd[cf] * x[cf])

    acutc.definition = phil == muc * Sum(cl, r[cl])

    aplnt.definition = phip == mup * Sum(
        [s, k, at], v[s, k, at] * (1 + rho) ** age[at]
    )

    benefit.definition = phi == phix - phik - phir - phil - phip

    # Model definition
    forest = Model(
        cont,
        name="forest",
        equations=cont.getEquations(),
        problem="LP",
        sense=Sense.MAX,
        objective=phi,
    )

    # Case Selection and Report Definitions
    rhoset = Set(
        cont, name="rhoset", records=["rho-03", "rho-05", "rho-07", "rho-10"]
    )

    landcl = Parameter(
        cont, name="landcl", domain=[s, k], description="clean level of landc"
    )
    rep = Parameter(
        cont,
        name="rep",
        domain=[cl, rhoset],
        description="summary report on log supply      (1000m3 per year)",
    )
    reprp = Parameter(
        cont,
        name="reprp",
        domain=[s, k, rhoset],
        description="summary report on rotation period           (years)",
    )
    repsp = Parameter(
        cont,
        name="repsp",
        domain=[s, k, rhoset],
        description="summary report on shadow price of land (us$ per ha)",
    )
    rhoval = Parameter(
        cont,
        name="rhoval",
        domain=[rhoset],
        records=np.array([0.03, 0.05, 0.07, 0.1]),
    )

    for case, _ in rhoset.records.itertuples(index=False):
        rho.assignment = rhoval[case]
        forest.solve()
        landcl[s, k] = Round(landc.l[s, k], 3)
        rep[cl, case] = r.l[cl]
        reprp[s, k, case] = (landcl[s, k] / Sum(at, v.l[s, k, at])).where[
            landcl[s, k]
        ]
        repsp[s, k, case] = landc.m[s, k]

    print("Summary report on log supply (1000m3 per year):")
    print(rep.pivot().round(3))
    print()

    print("Summary report on rotation period (years)  :")
    print(reprp.pivot().round(3))
    print()

    print("Summary report on shadow price of land (us$ per ha)  :")
    print(repsp.pivot().round(3))


if __name__ == "__main__":
    main()

tests/integration/models/tforss.py
GAMS-dev/gamspy
"""
Thai Navy Problem (THAI)

This model is used to allocate ships to transport personnel from
different port to a training center.


Choypeng, P, Puakpong, P, and Rosenthal, R E, Optimal Ship Routing
and Personnel Assignment for Naval Recruitment in Thailand.
Interfaces 16, 4 (1986), 356-366.

Keywords: mixed integer linear programming, routing, naval recruitment,
          scheduling
"""
from pathlib import Path

from gamspy import Container
from gamspy import Domain
from gamspy import Model
from gamspy import Sense
from gamspy import Sum


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/thai.gdx",
    )

    # Sets
    i, j, k, sc, vc = m.getSymbols(["i", "j", "k", "sc", "vc"])

    # Parameters
    (d, shipcap, n, a, w1, w2, w3) = m.getSymbols(
        ["d", "shipcap", "n", "a", "w1", "w2", "w3"]
    )

    # Variables
    z, y, obj = m.getSymbols(["z", "y", "obj"])

    # Equations
    (
        objdef,
        demand,
        voycap,
        shiplim,
    ) = m.getSymbols(
        [
            "objdef",
            "demand",
            "voycap",
            "shiplim",
        ]
    )

    objdef.definition = obj == w1 * Sum(
        Domain(j, k).where[vc[j, k]], z[j, k]
    ) + w2 * Sum(
        Domain(j, k).where[vc[j, k]], a[j, "dist"] * z[j, k]
    ) + w3 * Sum(
        Domain(j, k, i).where[a[j, i].where[vc[j, k]]],
        a[j, "dist"] * y[j, k, i],
    )

    demand[i] = (
        Sum(Domain(j, k).where[a[j, i].where[vc[j, k]]], y[j, k, i]) >= d[i]
    )
    voycap[j, k].where[vc[j, k]] = (
        Sum(i.where[a[j, i]], y[j, k, i]) <= shipcap[k] * z[j, k]
    )
    shiplim[k] = Sum(j.where[vc[j, k]], z[j, k]) <= n[k]

    thainavy = Model(
        m,
        name="thainavy",
        equations=m.getEquations(),
        problem="MIP",
        sense=Sense.MIN,
        objective=obj,
    )
    z.up[j, k].where[vc[j, k]] = n[k]
    thainavy.solve()


if __name__ == "__main__":
    main()

tests/integration/models/thai.py
GAMS-dev/gamspy
"""
Accessing previous (or next) Time Steps in an Equation fast (TIMESTEPS)

In dynamic models one often needs access to previous or next time steps. Access
to single time steps can be easly implemented via the lag and lead operator.
It gets more difficult if one needs access to a larger set of time steps.
the expression sum(tt$(ord(tt)<=ord(t) and ord(tt)>=ord(t)-n), ...) where t is
the current time step controlled from the outside can be very slow.

The following example model shows how to do this fast in GAMS using an example
from power generation modeling. We have a set of time steps and a number of
generators. A generator can only start once in a given time slice. We implement
the equation that enforces this in three different ways:

1) naive GAMS syntax via ord() calculation
2) calculate a set of time slices for any given active time step
3) fast implementation directly in the equation using the same idas to create
   the set in 2 fast

Solution 2 is actually the fastest, but it consumes a lot of memory. We will
eventually require this much memory in the model generation (we have many
non-zero entires in the equation) but we can safe the extra amount inside GAMS
data by using method 3.

Keywords: mixed integer linear programming, GAMS language features, dynamic
          modelling, time steps, power generation
"""
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import uniformInt


def main(mt=2016, mg=17, mindt=10, maxdt=40):
    m = Container(delayed_execution=True)

    if mindt > maxdt:
        raise Exception("minimum downtime is larger than maximum downtime")

    # Sets
    t = Set(
        m,
        name="t",
        records=[f"t{i}" for i in range(1, mt + 1)],
        description="hours",
    )
    g = Set(
        m,
        name="g",
        records=[f"g{i}" for i in range(1, mg + 1)],
        description="generators",
    )

    # Parameters
    pMinDown = Parameter(
        m, name="pMinDown", domain=[g, t], description="minimum downtime"
    )
    pMinDown[g, t] = uniformInt(mindt, maxdt)

    t1 = Alias(m, name="t1", alias_with=t)
    t2 = Alias(m, name="t2", alias_with=t)

    # Subsets
    sMinDown = Set(
        m,
        name="sMinDown",
        domain=[g, t1, t2],
        description="hours t2 g cannot start if we start g in t1",
    )
    sMinDownFast = Set(
        m,
        name="sMinDownFast",
        domain=[g, t1, t2],
        description="hours t2 g cannot start if we start g in t1",
    )
    tt = Set(
        m,
        name="tt",
        domain=[t],
        records=[f"t{i}" for i in range(1, maxdt + 1)],
        description="max downtime hours",
    )

    # Slow and fast calculation for the set of time slices t2 for a given time
    # step t1
    # Output from profile=1
    # ----     50 Assignment sMinDown      5.819  5.819 SECS 26 MB  850713
    # ----     51 Assignment sMinDownFast  0.187  6.006 SECS 48 MB  850713

    sMinDown[g, t1, t2] = (Ord(t1) >= Ord(t2)) & (
        Ord(t2) > Ord(t1) - pMinDown[g, t1]
    )
    sMinDownFast[g, t1, t.lead((Ord(t1) - pMinDown[g, t1]))].where[
        (tt[t]) & (Ord(t) <= pMinDown[g, t1])
    ] = Number(1)

    diff = Set(m, name="diff", domain=[g, t1, t2])
    diff[g, t1, t2] = sMinDown[g, t1, t2] ^ sMinDownFast[g, t1, t2]
    if diff.records is not None:
        raise Exception("sets are different")

    vStart = Variable(m, name="vStart", type="binary", domain=[g, t])
    z = Variable(m, name="z")

    # Slow, fast, and fastest (but memory intensive way because we need to
    # store sMinDownFast) way to write the equation
    # Output from profile = 1
    # ----     67 Equation   eStartNaive   6.099 12.215 SECS 106 MB 34272
    # ----     68 Equation   eStartFast    0.593 12.808 SECS 144 MB 34272
    # ----     69 Equation   eStartFaster  0.468 13.276 SECS 180 MB 34272

    # Equations
    eStartNaive = Equation(m, name="eStartNaive", domain=[g, t])
    eStartFast = Equation(m, name="eStartFast", domain=[g, t])
    eStartFaster = Equation(m, name="eStartFaster", domain=[g, t])
    defobj = Equation(m, name="defobj")

    eStartNaive[g, t1] = (
        Sum(
            t2.where[
                (Ord(t1) >= Ord(t2)) & (Ord(t2) > Ord(t1) - pMinDown[g, t1])
            ],
            vStart[g, t2],
        )
        <= 1
    )

    eStartFast[g, t1] = (
        Sum(
            tt[t].where[Ord(t) <= pMinDown[g, t1]],
            vStart[g, t.lead(Ord(t1) - pMinDown[g, t1])],
        )
        <= 1
    )

    eStartFaster[g, t1] = Sum(sMinDownFast[g, t1, t2], vStart[g, t2]) <= 1

    defobj.definition = z == Sum([g, t], vStart[g, t])

    maxStarts = Model(
        m,
        name="maxStarts",
        equations=m.getEquations(),
        problem="mip",
        sense=Sense.MAX,
        objective=z,
    )

    maxStarts.solve()
    print("Objective Function Value: ", z.records.level[0])


if __name__ == "__main__":
    main()

tests/integration/models/timesteps.py
GAMS-dev/gamspy
"""
A Transportation Problem (TRNSPORT)

This problem finds a least cost shipping schedule that meets
requirements at markets and supplies at factories.


Dantzig, G B, Chapter 3.3. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

This formulation is described in detail in:
Rosenthal, R E, Chapter 2: A GAMS Tutorial. In GAMS: A User's Guide.
The Scientific Press, Redwood City, California, 1988.

The line numbers will not match those in the book because of these
comments.

Keywords: linear programming, transportation problem, scheduling
"""
import sys

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Prepare data
    distances = [
        ["seattle", "new-york", 2.5],
        ["seattle", "chicago", 1.7],
        ["seattle", "topeka", 1.8],
        ["san-diego", "new-york", 2.5],
        ["san-diego", "chicago", 1.8],
        ["san-diego", "topeka", 1.4],
    ]

    capacities = [["seattle", 350], ["san-diego", 600]]
    demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

    # Set
    i = Set(m, name="i", records=["seattle", "san-diego"])
    j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

    # Data
    a = Parameter(m, name="a", domain=[i], records=capacities)
    b = Parameter(m, name="b", domain=[j], records=demands)
    d = Parameter(m, name="d", domain=[i, j], records=distances)
    c = Parameter(m, name="c", domain=[i, j])
    c[i, j] = 90 * d[i, j] / 1000

    # Variable
    x = Variable(m, name="x", domain=[i, j], type="Positive")

    # Equation
    supply = Equation(m, name="supply", domain=[i])
    demand = Equation(m, name="demand", domain=[j])

    supply[i] = Sum(j, x[i, j]) <= a[i]
    demand[j] = Sum(i, x[i, j]) >= b[j]

    transport = Model(
        m,
        name="transport",
        equations=m.getEquations(),
        problem="LP",
        sense=Sense.MIN,
        objective=Sum((i, j), c[i, j] * x[i, j]),
    )
    transport.solve(output=sys.stdout)

    import math

    assert math.isclose(transport.objective_value, 153.675000, rel_tol=0.001)

    print(x.records)
    print(transport.objective_value)
    print(transport.status)


if __name__ == "__main__":
    main()

tests/integration/models/transportation.py
GAMS-dev/gamspy
"""
A Transportation Problem with discretized Economies of Scale (TRNSPWL)

This problem finds a least cost shipping schedule that meets
requirements at markets and supplies at factories. This instance
applies economies of scale which results in a non-convex
objective. This is an extension of the trnsport model in the GAMS
Model Library.

The original nonlinear term is "sum((i,j), c(i,j)*sqrt(x(i,j)))".
We use the following discretization f(x) of sqrt(x)

  For x<=50:  f(x) = 1/sqrt(50)*x,
  for x>=400: f(x) = (sqrt(600)-sqrt(400))/200*(x-400) + sqrt(400)
  in between we discretize with linear interpolation between points

This discretization has some good properties:
  0) f(x) is a continuous function
  1) f(0)=0, otherwise we would pick up a fixed cost even for unused
  connections
  2) a fine representation in the reasonable range of shipments
  (between 50 and 400)
  3) f(x) underestimates sqrt in the area of x=0 to 600. Past that is
  overestimates sqrt.

The model is organized as follows:
  1) We set a starting point for the NLP solver so it will get stuck
     in local optimum that is not the global optimum.

  2) We use three formulations for representing piecewise linear
     functions all based on the same discretization.

     a) a formulation with SOS2 variables. This formulation mainly is
        based on the convex combination of neighboring
        points. Moreover, the domain of the discretization can be
        unbounded: we can assign a slope in the (potentially
        unbounded) first and last segment.

     b) a formulation with SOS2 variables based on convex combinations
        of neighboring points. This formulation requires a bounded
        region for the discretization. Here we discretize between 0 and
        600.

     c) a formuation with binary variables. This also requires the
        domain to be bounded, but it does not rely on the convex
        combination of neighboring points. There are examples, where
        this formulation solves much faster than the formulation b).

     In this example x is clearly bounded by 0 from below and
     min(smax(i,a(i),smax(j,b(j)) from above, so formulation b and c
     are sufficient and perform better on this particular model and
     instance. We added the formulation a to demonstrate how to model
     an unbounded discretization, in case there are no derived
     bounds. The formulation a can be easily adjusted to accommodate
     problems where only one end of the discretization is unbounded.

  3) We restart the non-convex NLP from the solution of the discretized
     model and hope that the NLP solver finds the global solution.


Dantzig, G B, Chapter 3.3. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: non linear programming, mixed integer linear programming,
          transportation problem, scheduling, economies of scale, non-convex
          objective, special ordered sets
"""
import numpy as np

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable
from gamspy.math import sqrt


def main():
    m = Container(delayed_execution=True)

    # Sets
    i = Set(
        m,
        name="i",
        records=["seattle", "san-diego"],
        description="canning plants",
    )
    j = Set(
        m,
        name="j",
        records=["new-york", "chicago", "topeka"],
        description="markets",
    )

    # Parameters
    a = Parameter(
        m,
        name="a",
        domain=[i],
        records=np.array([350, 600]),
        description="capacity of plant i in cases",
    )

    b = Parameter(
        m,
        name="b",
        domain=[j],
        records=np.array([325, 300, 275]),
        description="demand at market j in cases",
    )

    d = Parameter(
        m,
        name="d",
        domain=[i, j],
        records=np.array([[2.5, 1.7, 1.8], [2.5, 1.8, 1.4]]),
        description="distance in thousands of miles",
    )

    f = Parameter(
        m,
        name="f",
        records=90,
        description="freight in dollars per case per thousand miles",
    )

    c = Parameter(
        m,
        name="c",
        domain=[i, j],
        description="transport cost in thousands of dollars per case",
    )
    c[i, j] = f * d[i, j] / 1000

    # Variables
    x = Variable(
        m,
        name="x",
        type="positive",
        domain=[i, j],
        description="shipment quantities in cases",
    )
    z = Variable(
        m,
        name="z",
        type="free",
        description="total transportation costs in thousands of dollars",
    )

    # Equation
    cost = Equation(m, name="cost", description="define objective function")
    supply = Equation(
        m,
        name="supply",
        domain=[i],
        description="observe supply limit at plant i",
    )
    demand = Equation(
        m,
        name="demand",
        domain=[j],
        description="satisfy demand at market j",
    )

    cost.definition = z == Sum([i, j], c[i, j] * sqrt(x[i, j]))

    supply[i] = Sum(j, x[i, j]) <= a[i]

    demand[j] = Sum(i, x[i, j]) >= b[j]

    transport = Model(
        m,
        name="transport",
        equations=m.getEquations(),
        problem=Problem.NLP,
        sense=Sense.MIN,
        objective=z,
    )

    # Start the local NLP solver in a local solution that is not globally
    # optimal
    x.l["seattle  ", "chicago "] = 25
    x.l["seattle  ", "topeka  "] = 275
    x.l["san-diego", "new-york"] = 325
    x.l["san-diego", "chicago "] = 275

    localopt = Parameter(
        m,
        name="localopt",
        description="objective of local optimum that is not globally optimal",
    )

    m.addOptions({"nlp": "conopt"})

    transport.solve()
    print("Initial Objective Function Value: ", round(z.records.level[0], 3))

    localopt.assignment = z.l

    # The first model (formulation a) implements a piecewise linear
    # approximation based on the convex combination of neighboring points
    # using SOS2 variables with unbounded segments at the beginning and
    # end of the discretization
    # Sets
    s = Set(
        m,
        name="s",
        records=["slope0", "slopeN"] + [f"s{i}" for i in range(1, 7)],
        description="SOS2 elements",
    )
    ss = Set(
        m,
        name="ss",
        domain=[s],
        records=[f"s{i}" for i in range(1, 7)],
        description="sample points",
    )

    # Parameters
    p = Parameter(
        m, name="p", domain=[s], description="x coordinate of sample point"
    )
    sqrtp = Parameter(
        m, name="sqrtp", domain=[s], description="y coordinate of sample point"
    )
    xlow = Parameter(m, name="xlow", records=50)
    xhigh = Parameter(m, name="xhigh", records=400)
    xmax = Parameter(m, name="xmax")

    xmax.assignment = Smax(i, a[i])

    if xmax.records.value[0] < xhigh.records.value[0]:
        raise Exception("xhigh too big")

    if xlow.records.value[0] < 0:
        raise Exception("xlow less than 0")

    # Equidistant sampling of the sqrt function with slopes at the beginning
    # and end
    p["slope0"] = -1
    p[ss] = xlow + (xhigh - xlow) / (Card(ss) - 1) * ss.off
    p["slopeN"] = 1

    sqrtp["slope0"] = -1 / sqrt(xlow)
    sqrtp[ss] = sqrt(p[ss])
    sqrtp["slopeN"] = (sqrt(xmax) - sqrt(xhigh)) / (xmax - xhigh)

    xs = Variable(m, name="xs", type="sos2", domain=[i, j, s])
    sqrtx = Variable(m, name="sqrtx", type="positive", domain=[i, j])

    # Equations
    defsos1 = Equation(m, name="defsos1", domain=[i, j])
    defsos2 = Equation(m, name="defsos2", domain=[i, j])
    defsos3 = Equation(m, name="defsos3", domain=[i, j])
    defobjdisc = Equation(m, name="defobjdisc")

    defsos1[i, j] = x[i, j] == Sum(s, p[s] * xs[i, j, s])

    defsos2[i, j] = sqrtx[i, j] == Sum(s, sqrtp[s] * xs[i, j, s])

    defsos3[i, j] = Sum(ss, xs[i, j, ss]) == 1

    defobjdisc.definition = z == Sum([i, j], c[i, j] * sqrtx[i, j])

    trnsdiscA = Model(
        m,
        name="trnsdiscA",
        equations=[supply, demand, defsos1, defsos2, defsos3, defobjdisc],
        problem="mip",
        sense=Sense.MIN,
        objective=z,
    )

    m.addOptions({"optCr": 0})

    trnsdiscA.solve()

    # The next model (formulation b) uses the convex combinations of
    # neighboring points but requires the discretization to be bounded
    # (here we go from 0 to xmax).
    p["slope0"] = 0
    p[ss] = xlow + (xhigh - xlow) / (Card(ss) - 1) * ss.off
    p["slopeN"] = xmax
    sqrtp[s] = sqrt(p[s])

    # We can just use model trnsdiscA but need to include the first and
    # last segment into the set ss that builds the convex combinations.
    ss[s] = Number(1)

    trnsdiscA.solve()

    # The next model (formulation c) implements another formulation for a
    # piecewise linear function. We need to assume that the domain region
    # is bounded. We use the same discretization as in the previous
    # formulation.
    # Sets
    g = Set(
        m,
        name="g",
        domain=[s],
        records=["slope0"] + [f"s{i}" for i in range(1, 7)],
        description="Segments",
    )

    # Parameters
    nseg = Parameter(
        m,
        name="nseg",
        domain=[s],
        description="relative increase of x in segment",
    )
    ninc = Parameter(
        m,
        name="ninc",
        domain=[s],
        description="relative increase of sqrtx in segment",
    )

    nseg[s].where[g[s]] = p[s.lead(1, "linear")] - p[s]
    ninc[s].where[g[s]] = sqrtp[s.lead(1, "linear")] - sqrtp[s]

    # Variables
    seg = Variable(
        m,
        name="seg",
        type="positive",
        domain=[i, j, s],
        description="shipment in segment",
    )
    gs = Variable(
        m,
        name="gs",
        type="binary",
        domain=[i, j, s],
        description="indicator for shipment in segment",
    )

    # Equations
    defx = Equation(
        m,
        name="defx",
        domain=[i, j],
        description="definition of x",
    )
    defsqrt = Equation(
        m,
        name="defsqrt",
        domain=[i, j],
        description="definition of sqrt",
    )
    defseg = Equation(
        m,
        name="defseg",
        domain=[i, j, s],
        description="segment can only have shipment if indicator is on",
    )
    defgs = Equation(
        m,
        name="defgs",
        domain=[i, j],
        description="select at most one segment",
    )

    defx[i, j] = x[i, j] == Sum(g, p[g] * gs[i, j, g] + nseg[g] * seg[i, j, g])

    defsqrt[i, j] = sqrtx[i, j] == Sum(
        g, sqrtp[g] * gs[i, j, g] + ninc[g] * seg[i, j, g]
    )

    defseg[i, j, g] = seg[i, j, g] <= gs[i, j, g]

    defgs[i, j] = Sum(g, gs[i, j, g]) <= 1

    trnsdiscB = Model(
        m,
        name="trnsdiscB",
        equations=[supply, demand, defx, defsqrt, defseg, defgs, defobjdisc],
        problem="mip",
        sense=Sense.MIN,
        objective=z,
    )

    trnsdiscB.solve()

    # Now restart the local solver from this approximate point
    transport.solve()

    print("Improved Objective Function Value: ", round(z.records.level[0], 3))

    # Ensure that we are better off than before
    if z.records.level[0] - localopt.records.value[0] > 1e-6:
        raise Exception("we should get an improved solution")


if __name__ == "__main__":
    main()

tests/integration/models/trnspwl.py
GAMS-dev/gamspy
"""
Truss Toplogy Design with Multiple Loads (TRUSSM)

A structure of n linear elastic bars connects a set of m nodes.
The task is to size the bars, i.e. determine t(i), the volume
of the bars, that yield the stiffest truss subject to constraints
such as total weight limit and k different (nonsimultaneous) loading
scenarios to be satisfied. For example, the different load scenarios
for a bridge could include rush hour traffic, night traffic, earthquake
and side wind.

The model is given as a conic program. The cone implementation comes
from Ben-Tal and Nemirovski.

Suppose we have a truss of n bars and m nodes. Now consider a set of
k fixed externally applied nodal forces f(k)=[f1, .., fn].

Let d_i denote the small node displacement resulting from the force on
each node i. The objective is to maximize the stiffness of the truss,
which is equivalent to minimizing the elastic stored energy 0.5*f^T*d,
subject to some maximum volume restriction on the truss.

Using the formulation given in Ben-Tal and Nemirovski (2001), we can
model this as the second order cone problem:

           minimize      tau
           subject to
                         sum(i, t(i)) <= maxvolume

                         s(i,k)^2 <= 2*t(i)*sigma(i,k)
                         sum(i, sigma(i,k)) <= tau
                         sum(i,k) s(i,k)*b(i)) <= f(k)

The first constraint is the material volume limitation. The latter 3
constraints and the objective are the compliance constraints, which are
equivalent to minimization of the elastic potential energy under a given
load.


A. Ben-Tal and A. Nemirovski, Lectures on Modern Convex Optimization:
Analysis, Algorithms, and Engineering Applications, MPS/SIAM Series
on Optimization, SIAM Press, 2001.

M.S. Lobo, L. Vandenberghe, S. Boyd, and H. Lebret, "Applications of
Second-order Cone Programming", Linear Algebra and its Applications,
Special Issue on Linear Algebra in Control, Signals and Image Processing.
284 (1998) 193-228.
"""
import pandas as pd

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.math import uniform


def main():
    m = Container(delayed_execution=True)

    # Prepare data
    forces = pd.DataFrame(
        [
            ["j1", "k1", 0.0008],
            ["j1", "k2", 1.0668],
            ["j1", "k3", 0.2944],
            ["j2", "k1", 0.0003],
            ["j2", "k2", 0.0593],
            ["j2", "k3", -1.3362],
            ["j3", "k1", -0.0006],
            ["j3", "k2", -0.0956],
            ["j3", "k3", 0.7143],
            ["j4", "k1", -1.0003],
            ["j4", "k2", -0.8323],
            ["j4", "k3", 1.6236],
        ]
    )

    stiff_data = pd.DataFrame(
        [
            ["j1", "i1", 1.0],
            ["j1", "i2", 0],
            ["j1", "i3", 0.5],
            ["j1", "i4", 0],
            ["j1", "i5", 0],
            ["j2", "i1", 0],
            ["j2", "i2", 0],
            ["j2", "i3", -0.5],
            ["j2", "i4", -1.0],
            ["j2", "i5", 0],
            ["j3", "i1", 0],
            ["j3", "i2", 0.5],
            ["j3", "i3", 0],
            ["j3", "i4", 0],
            ["j3", "i5", 1.0],
            ["j4", "i1", 0],
            ["j4", "i2", 0.5],
            ["j4", "i3", 0],
            ["j4", "i4", 1.0],
            ["j4", "i5", 0],
        ]
    )

    # Set
    i = Set(m, name="i", records=[f"i{idx}" for idx in range(1, 6)])
    j = Set(m, name="j", records=[f"j{idx}" for idx in range(1, 5)])
    k = Set(m, name="k", records=[f"k{idx}" for idx in range(1, 4)])

    # Data
    f = Parameter(m, name="f", domain=[j, k], records=forces)
    b = Parameter(m, name="b", domain=[j, i], records=stiff_data)

    max_volume = 10

    # Variable
    tau = Variable(m, name="tau")
    s = Variable(m, name="s", domain=[i, k])
    tk = Variable(m, name="tk", domain=[i, k], type="Positive")
    t = Variable(m, name="t", domain=[i], type="Positive")
    sigma = Variable(m, name="sigma", domain=[i, k], type="Positive")

    # Equation
    volumeeq = Equation(m, name="volumeeq", domain=[i, k])
    deftk = Equation(m, name="deftk", domain=[i, k])
    reseq = Equation(m, name="reseq", domain=[k])
    trusscomp = Equation(m, name="trusscomp")
    stiffness = Equation(m, name="stifness", domain=[j, k])

    volumeeq[i, k] = 2 * tk[i, k] * sigma[i, k] >= s[i, k] ** 2
    deftk[i, k] = tk[i, k] == t[i]
    reseq[k] = Sum(i, sigma[i, k]) <= tau
    trusscomp.definition = Sum(i, t[i]) <= max_volume
    stiffness[j, k] = Sum(i, s[i, k] * b[j, i]) == f[j, k]

    truss = Model(
        m,
        name="truss",
        equations=m.getEquations(),
        problem=Problem.QCP,
        sense=Sense.MIN,
        objective=tau,
    )
    sigma.l[i, k] = uniform(0.1, 1)
    truss.solve()

    f[j, "k2"] = 0
    f[j, "k3"] = 0

    truss.solve()


if __name__ == "__main__":
    main()

tests/integration/models/trussm.py
GAMS-dev/gamspy
"""
Traveling Salesman Problem - Four (TSP4,SEQ=180)

This is the fourth problem in a series of traveling salesman
problems. Here we revisit TSP1 and generate smarter cuts.
The first relaxation is the same as in TSP1.


Kalvelagen, E, Model Building with GAMS. forthcoming

de Wetering, A V, private communication.

Keywords: mixed integer linear programming, traveling salesman problem, iterative
          subtour elimination
"""
from pathlib import Path

import gamspy.math as gams_math
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException


def main():
    m = Container(
        delayed_execution=True,
        load_from=str(Path(__file__).parent.absolute()) + "/tsp4.gdx",
    )

    # SETS
    ii, i = m.getSymbols(["ii", "i"])

    # ALIASES
    jj, j = m.getSymbols(["jj", "j"])

    # PARAMETER
    c = m.getSymbols(["c"])[0]

    # VARIABLES
    x = Variable(
        m,
        name="x",
        type="binary",
        domain=[ii, jj],
        description="decision variables - leg of trip",
    )
    z = Variable(m, name="z", type="free", description="objective variable")

    # EQUATIONS
    objective = Equation(m, name="objective", description="total cost")
    rowsum = Equation(
        m, name="rowsum", domain=[ii], description="leave each city only once"
    )
    colsum = Equation(
        m,
        name="colsum",
        domain=[jj],
        description="arrive at each city only once",
    )

    # the assignment problem is a relaxation of the TSP
    objective.definition = z == Sum([i, j], c[i, j] * x[i, j])
    rowsum[i] = Sum(j, x[i, j]) == 1
    colsum[j] = Sum(i, x[i, j]) == 1

    # exclude diagonal
    x.fx[ii, ii] = 0

    # For this algorithm we can try a larger subset of 12 cities.
    # SETS
    i.setRecords([f"i{ir}" for ir in range(1, 13)])

    # options. Make sure MIP solver finds global optima.
    m.addOptions({"optCr": 0})
    assign = Model(
        m,
        name="assign",
        equations=[objective, rowsum, colsum],
        problem="mip",
        sense="min",
        objective=z,
    )
    assign.solve()

    # find and display tours
    t = Set(
        m,
        name="t",
        records=[f"t{t}" for t in range(1, 18)],
        description="tours",
    )

    if len(t) < len(i):
        raise GamspyException("Set t is possibly too small")

    # SETS
    tour = Set(m, name="tour", domain=[i, j, t], description="subtours")
    visited = Set(
        m,
        name="visited",
        domain=[i],
        description="flag whether a city is already visited",
    )

    # SINGLETON SETS
    fromi = Set(
        m,
        name="fromi",
        domain=[i],
        is_singleton=True,
        description="contains always one element: the from city",
    )
    nextj = Set(
        m,
        name="nextj",
        domain=[j],
        is_singleton=True,
        description="contains always one element: the to city",
    )
    tt = Set(
        m,
        name="tt",
        domain=[t],
        is_singleton=True,
        description="contains always one element: the current subtour",
    )

    # ALIASES ##
    ix = Alias(m, name="ix", alias_with=i)

    # initialize
    fromi[i].where[Ord(i) == 1] = True  # turn first element on
    tt[t].where[Ord(t) == 1] = True  # turn first element on

    # subtour elimination by adding cuts
    # Set
    cc = Set(m, name="cc", records=[f"c{c}" for c in range(1, 1001)])

    # Alias
    ccc = Alias(m, name="ccc", alias_with=cc)  # we allow up to 1000 cuts

    # Set
    curcut = Set(
        m,
        name="curcut",
        domain=[cc],
        description="current cut always one element",
    )
    allcuts = Set(m, name="allcuts", domain=[cc], description="total cuts")

    # Parameter
    cutcoeff = Parameter(m, name="cutcoeff", domain=[cc, i, j])
    rhs = Parameter(m, name="rhs", domain=[cc])
    nosubtours = Parameter(
        m, name="nosubtours", description="number of subtours"
    )

    # Equation
    cut = Equation(m, name="cut", domain=[cc], description="dynamic cuts")

    cut[allcuts] = (
        Sum([i, j], cutcoeff[allcuts, i, j] * x[i, j]) <= rhs[allcuts]
    )

    tspcut = Model(
        m,
        name="tspcut",
        equations=[objective, rowsum, colsum, cut],
        problem="mip",
        sense="MIN",
        objective=z,
    )

    curcut[cc].where[Ord(cc) == 1] = True

    for ccc_loop in ccc.toList():
        #  initialize
        fromi[i].where[Ord(i) == 1] = True  # turn first element on
        tt[t].where[Ord(t) == 1] = True  # turn first element on
        tour[i, j, t] = False
        visited[i] = False

        for i_loop in i.toList():
            nextj[j].where[
                x.l[fromi, j] > 0.5
            ] = True  # check x.l(fromi,j) = 1 would be dangerous
            tour[fromi, nextj, tt] = True  # store in table
            visited[fromi] = True  # mark city 'fromi' as visited
            fromi[j] = nextj[j]

            if nextj.toList()[0] in visited.toList():  # if already visited...
                tt[t] = tt[t.lag(1)]
                for ix_loop in ix.toList():
                    if (
                        ix_loop in visited.toList()
                    ):  # find starting point of new subtour
                        continue
                    fromi[ix_loop] = True

        nosubtours.assignment = Sum(
            t, gams_math.Max(Number(0), Smax(tour[i, j, t], Number(1)))
        )

        if nosubtours.toValue() == 1:  # done: no subtours
            break

        # introduce cut
        for idx, t_loop in enumerate(t.toList()):
            if idx + 1 > nosubtours.toValue():
                continue
            rhs[curcut] = -1

            for i_loop, j_loop, t_loop2, _ in tour.records.itertuples(
                index=False
            ):
                if t_loop2 != t_loop:
                    continue

                cutcoeff[curcut, i_loop, j_loop].where[
                    x.l[i_loop, j_loop] > 0.5
                ] = 1
                # not needed due to nature of assignment constraints
                #        cutcoeff(curcut, i, j)$(x.l[i,j] < 0.5) = -1
                rhs[curcut] = rhs[curcut] + 1
            allcuts[curcut] = True  # include this cut in set
            curcut[cc] = curcut[cc.lag(1)]

        tspcut.solve()
        m.addOptions({"limRow": 0, "limCol": 0})
        print(
            "Cut: ",
            ccc_loop,
            "\t\t # of subtours remaining: ",
            nosubtours.toValue() - 1,
        )

    if nosubtours.toValue() != 1:
        raise GamspyException("Too many cuts needed")

    print("No subtours remaining. Solution found!!\n")
    print("x: \n", x.pivot().round(1))

    import math

    assert math.isclose(tspcut.objective_value, 39, rel_tol=0.001)


if __name__ == "__main__":
    main()

tests/integration/models/tsp4.py
GAMS-dev/gamspy
"""
Weapons Assignment (WEAPONS)

This model determines an assignment of weapons to targets in order
to inflict maximum damage at minimal cost. This is a classic
NLP test problem.


Bracken, J, and McCormick, G P, Chapter 2. In Selected Applications of
Nonlinear Programming. John Wiley and Sons, New York, 1968, pp. 22-27.

Keywords: nonlinear programming, assignment problem, military application,
          nlp test problem
"""
import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Product
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    td_data = pd.DataFrame(
        [
            ["icbm", "2", 0.05],
            ["icbm", "6", 0.15],
            ["icbm", "7", 0.10],
            ["icbm", "8", 0.15],
            ["icbm", "9", 0.20],
            ["icbm", "18", 0.05],
            ["mrbm-1", "1", 0.16],
            ["mrbm-1", "2", 0.17],
            ["mrbm-1", "3", 0.15],
            ["mrbm-1", "4", 0.16],
            ["mrbm-1", "5", 0.15],
            ["mrbm-1", "6", 0.19],
            ["mrbm-1", "7", 0.19],
            ["mrbm-1", "8", 0.18],
            ["mrbm-1", "9", 0.20],
            ["mrbm-1", "10", 0.14],
            ["mrbm-1", "12", 0.02],
            ["mrbm-1", "14", 0.12],
            ["mrbm-1", "15", 0.13],
            ["mrbm-1", "16", 0.12],
            ["mrbm-1", "17", 0.15],
            ["mrbm-1", "18", 0.16],
            ["mrbm-1", "19", 0.15],
            ["mrbm-1", "20", 0.15],
            ["lr-bomber", "1", 0.04],
            ["lr-bomber", "2", 0.05],
            ["lr-bomber", "3", 0.04],
            ["lr-bomber", "4", 0.04],
            ["lr-bomber", "5", 0.04],
            ["lr-bomber", "6", 0.10],
            ["lr-bomber", "7", 0.08],
            ["lr-bomber", "8", 0.09],
            ["lr-bomber", "9", 0.08],
            ["lr-bomber", "10", 0.05],
            ["lr-bomber", "11", 0.01],
            ["lr-bomber", "12", 0.02],
            ["lr-bomber", "13", 0.01],
            ["lr-bomber", "14", 0.02],
            ["lr-bomber", "15", 0.03],
            ["lr-bomber", "16", 0.02],
            ["lr-bomber", "17", 0.05],
            ["lr-bomber", "18", 0.08],
            ["lr-bomber", "19", 0.07],
            ["lr-bomber", "20", 0.08],
            ["f-bomber", "10", 0.04],
            ["f-bomber", "11", 0.09],
            ["f-bomber", "12", 0.08],
            ["f-bomber", "13", 0.09],
            ["f-bomber", "14", 0.08],
            ["f-bomber", "15", 0.02],
            ["f-bomber", "16", 0.07],
            ["mrbm-2", "1", 0.08],
            ["mrbm-2", "2", 0.06],
            ["mrbm-2", "3", 0.08],
            ["mrbm-2", "4", 0.05],
            ["mrbm-2", "5", 0.05],
            ["mrbm-2", "6", 0.02],
            ["mrbm-2", "7", 0.02],
            ["mrbm-2", "10", 0.10],
            ["mrbm-2", "11", 0.05],
            ["mrbm-2", "12", 0.04],
            ["mrbm-2", "13", 0.09],
            ["mrbm-2", "14", 0.02],
            ["mrbm-2", "15", 0.01],
            ["mrbm-2", "16", 0.01],
        ]
    )

    wa_data = pd.DataFrame(
        [
            ["icbm", 200],
            ["mrbm-1", 100],
            ["lr-bomber", 300],
            ["f-bomber", 150],
            ["mrbm-2", 250],
        ]
    )

    tm_data = pd.DataFrame(
        [
            ["1", 30],
            ["6", 100],
            ["10", 40],
            ["14", 50],
            ["15", 70],
            ["16", 35],
            ["20", 10],
        ]
    )

    mv_data = pd.DataFrame(
        [
            ["1", 60],
            ["2", 50],
            ["3", 50],
            ["4", 75],
            ["5", 40],
            ["6", 60],
            ["7", 35],
            ["8", 30],
            ["9", 25],
            ["10", 150],
            ["11", 30],
            ["12", 45],
            ["13", 125],
            ["14", 200],
            ["15", 200],
            ["16", 130],
            ["17", 100],
            ["18", 100],
            ["19", 100],
            ["20", 150],
        ]
    )

    # Sets
    w = Set(
        m,
        name="w",
        records=["icbm", "mrbm-1", "lr-bomber", "f-bomber", "mrbm-2"],
    )
    t = Set(m, name="t", records=[str(i) for i in range(1, 21)])

    # Parameters
    td = Parameter(m, name="td", domain=[w, t], records=td_data)
    wa = Parameter(m, name="wa", domain=[w], records=wa_data)
    tm = Parameter(m, name="tm", domain=[t], records=tm_data)
    mv = Parameter(m, name="mv", domain=[t], records=mv_data)

    # Variables
    x = Variable(m, name="x", domain=[w, t], type="Positive")
    prob = Variable(m, name="prob", domain=[t])
    tetd = Variable(m, name="tetd")

    # Equations
    maxw = Equation(m, name="maxw", domain=[w])
    minw = Equation(m, name="minw", domain=[t])
    probe = Equation(m, name="probe", domain=[t])
    etdp = Equation(m, name="etdp")
    etd = Equation(m, name="etd")

    maxw[w] = Sum(t.where[td[w, t]], x[w, t]) <= wa[w]
    minw[t].where[tm[t]] = Sum(w.where[td[w, t]], x[w, t]) >= tm[t]
    probe[t] = prob[t] == 1 - Product(
        w.where[td[w, t]], (1 - td[w, t]) ** x[w, t]
    )
    etdp.definition = tetd == Sum(t, mv[t] * prob[t])
    etd.definition = tetd == Sum(
        t, mv[t] * (1 - Product(w.where[td[w, t]], (1 - td[w, t]) ** x[w, t]))
    )

    war = Model(
        m,
        name="war",
        equations=[maxw, minw, etd],
        problem=Problem.NLP,
        sense=Sense.MAX,
        objective=tetd,
    )

    x.l[w, t].where[td[w, t]] = wa[w] / Card(t)

    war.solve()

    import math

    assert math.isclose(war.objective_value, 1735.5696, rel_tol=0.001)
    print(war.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/weapons.py
GAMS-dev/gamspy
"""
Simple Warehouse Problem (WHOUSE)

A warehouse can store limited units of a commodity. Given an
initial stock, the manager has to decide when to buy or sell in
order to minimize total cost.


Dantzig, G B, Chapter 3.6. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: linear programming, warehouse management, inventory
"""
import numpy as np

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


def main():
    m = Container(delayed_execution=True)

    # Sets
    t = Set(m, name="t", records=[f"q-{i}" for i in range(1, 5)])

    # Parameters
    price = Parameter(
        m, name="price", domain=[t], records=np.array([10, 12, 8, 9])
    )
    istock = Parameter(
        m, name="istock", domain=[t], records=np.array([50, 0, 0, 0])
    )  # OR records=pd.DataFrame([["q-1", 50]])

    # Scalars
    storecost = Parameter(m, name="storecost", records=1)
    storecap = Parameter(m, name="storecap", records=100)

    # Variables
    stock = Variable(m, name="stock", domain=[t], type="Positive")
    sell = Variable(m, name="sell", domain=[t], type="Positive")
    buy = Variable(m, name="buy", domain=[t], type="Positive")
    cost = Variable(m, name="cost")

    # Equations
    sb = Equation(m, name="sb", domain=[t])
    at = Equation(m, name="at")

    sb[t] = (
        stock[t] == stock[t.lag(1, "linear")] + buy[t] - sell[t] + istock[t]
    )
    at.definition = cost == Sum(
        t, price[t] * (buy[t] - sell[t]) + storecost * stock[t]
    )

    stock.up[t] = storecap

    swp = Model(
        m,
        name="swp",
        equations=m.getEquations(),
        problem="LP",
        sense=Sense.MIN,
        objective=cost,
    )
    swp.solve()

    import math

    assert math.isclose(swp.objective_value, -600, rel_tol=0.001)

    print("Objective function value: ", swp.objective_value)


if __name__ == "__main__":
    main()

tests/integration/models/whouse.py
GAMS-dev/gamspy
import os
import shutil
import subprocess
import unittest
from pathlib import Path

import gamspy.utils as utils


class CmdSuite(unittest.TestCase):
    def test_install_license(self):
        this_folder = str(Path(__file__).parent)

        license = "dummy license"

        with open(this_folder + os.sep + "gamslice.txt", "w") as license_file:
            license_file.write(license)

        gamspy_base_dir = utils._getGAMSPyBaseDirectory()

        # copy existing license to recover later
        shutil.copy(
            gamspy_base_dir + os.sep + "gamslice.txt",
            this_folder + os.sep + "backup.txt",
        )

        old_license_modified_time = os.path.getmtime(
            gamspy_base_dir + os.sep + "gamslice.txt"
        )

        _ = subprocess.run(
            " ".join(
                [
                    "gamspy",
                    "install",
                    "license",
                    os.path.abspath(this_folder + os.sep + "gamslice.txt"),
                ]
            ),
            shell=True,
            capture_output=True,
        )

        new_license_modified_time = os.path.getmtime(
            gamspy_base_dir + os.sep + "gamslice.txt"
        )

        self.assertTrue(new_license_modified_time > old_license_modified_time)

        # recover the original license
        shutil.copyfile(
            this_folder + os.sep + "backup.txt",
            gamspy_base_dir + os.sep + "gamslice.txt",
        )


def cmd_suite():
    suite = unittest.TestSuite()
    tests = [
        CmdSuite(name) for name in dir(CmdSuite) if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(cmd_suite())

tests/integration/test_cmd_script.py
GAMS-dev/gamspy
import os
import unittest

from gams import GamsEngineConfiguration

from gamspy import Container
from gamspy import EngineConfig
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException

if os.path.exists(os.getcwd() + os.sep + ".env"):
    from dotenv import load_dotenv

    load_dotenv(os.getcwd() + os.sep + ".env")


class EngineSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_engine(self):
        m = Container(delayed_execution=True)

        # Prepare data
        distances = [
            ["seattle", "new-york", 2.5],
            ["seattle", "chicago", 1.7],
            ["seattle", "topeka", 1.8],
            ["san-diego", "new-york", 2.5],
            ["san-diego", "chicago", 1.8],
            ["san-diego", "topeka", 1.4],
        ]

        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(m, name="a", domain=[i], records=capacities)
        b = Parameter(m, name="b", domain=[j], records=demands)
        d = Parameter(m, name="d", domain=[i, j], records=distances)
        c = Parameter(m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(m, name="x", domain=[i, j], type="Positive")

        # Equation
        supply = Equation(m, name="supply", domain=[i])
        demand = Equation(m, name="demand", domain=[j])

        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= b[j]

        transport = Model(
            m,
            name="transport",
            equations=m.getEquations(),
            problem="LP",
            sense=Sense.MIN,
            objective=Sum((i, j), c[i, j] * x[i, j]),
        )

        engine_config = EngineConfig(
            host=os.environ["ENGINE_URL"],
            username=os.environ["ENGINE_USER"],
            password=os.environ["ENGINE_PASSWORD"],
            namespace=os.environ["ENGINE_NAMESPACE"],
        )

        self.assertTrue(
            isinstance(
                engine_config.get_engine_config(), GamsEngineConfiguration
            )
        )

        self.assertRaises(
            GamspyException,
            transport.solve,
            None,
            None,
            None,
            None,
            None,
            "engine",
        )

        transport.solve(backend="engine", engine_config=engine_config)

        self.assertEqual(transport.objective_value, 153.675)


def engine_suite():
    suite = unittest.TestSuite()
    tests = [
        EngineSuite(name)
        for name in dir(EngineSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(engine_suite())

tests/integration/test_engine.py
GAMS-dev/gamspy
import unittest

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


class ModelInstanceSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_parameter_change(self):
        m = Container(delayed_execution=True)

        # Prepare data
        distances = [
            ["seattle", "new-york", 2.5],
            ["seattle", "chicago", 1.7],
            ["seattle", "topeka", 1.8],
            ["san-diego", "new-york", 2.5],
            ["san-diego", "chicago", 1.8],
            ["san-diego", "topeka", 1.4],
        ]

        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(m, name="a", domain=[i], records=capacities)
        b = Parameter(m, name="b", domain=[j], records=demands)
        d = Parameter(m, name="d", domain=[i, j], records=distances)
        c = Parameter(m, name="c", domain=[i, j])
        bmult = Parameter(m, name="bmult", records=1)
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(m, name="x", domain=[i, j], type="Positive")
        z = Variable(m, name="z")

        # Equation
        cost = Equation(m, name="cost")
        supply = Equation(m, name="supply", domain=[i])
        demand = Equation(m, name="demand", domain=[j])

        cost.definition = z == Sum((i, j), c[i, j] * x[i, j])
        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= bmult * b[j]

        transport = Model(
            m,
            name="transport",
            equations=m.getEquations(),
            problem="LP",
            sense=Sense.MIN,
            objective=z,
        )

        bmult_list = [0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3]
        results = [
            92.204,
            107.572,
            122.940,
            138.307,
            153.675,
            164.002,
            164.519,
            166.117,
        ]

        transport.freeze(modifiables=[bmult])

        for b_value, result in zip(bmult_list, results):
            bmult.setRecords(b_value)
            transport.solve(model_instance_options={"solver": "conopt"})
            self.assertAlmostEqual(z.records["level"][0], result, places=2)

        transport.unfreeze()
        self.assertFalse(transport._is_frozen)

    def test_variable_change(self):
        m = Container(delayed_execution=True)

        # Prepare data
        distances = [
            ["seattle", "new-york", 2.5],
            ["seattle", "chicago", 1.7],
            ["seattle", "topeka", 1.8],
            ["san-diego", "new-york", 2.5],
            ["san-diego", "chicago", 1.8],
            ["san-diego", "topeka", 1.4],
        ]

        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(m, name="a", domain=[i], records=capacities)
        b = Parameter(m, name="b", domain=[j], records=demands)
        d = Parameter(m, name="d", domain=[i, j], records=distances)
        c = Parameter(m, name="c", domain=[i, j])
        bmult = Parameter(m, name="bmult", records=1)
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(m, name="x", domain=[i, j], type="Positive")
        z = Variable(m, name="z")

        # Equation
        cost = Equation(m, name="cost")
        supply = Equation(m, name="supply", domain=[i])
        demand = Equation(m, name="demand", domain=[j])

        cost.definition = z == Sum((i, j), c[i, j] * x[i, j])
        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= bmult * b[j]

        transport = Model(
            m,
            name="transport",
            equations=m.getEquations(),
            problem="LP",
            sense=Sense.MIN,
            objective=z,
        )

        transport.freeze(modifiables=[x.up])
        transport.solve(model_instance_options={"solver": "conopt"})
        self.assertAlmostEqual(z.records["level"][0], 153.675, places=3)

        x.records.loc[1, "upper"] = 0
        transport.solve(model_instance_options={"solver": "conopt"})
        self.assertAlmostEqual(z.records["level"][0], 156.375, places=3)

    def test_fx(self):
        m = Container(delayed_execution=True)

        # Data
        INCOME0 = Parameter(
            m, name="INCOME0", description="notional income level", records=3.5
        )

        # Variable
        IADJ = Variable(
            m,
            name="IADJ",
            description=(
                "investment scaling factor (for fixed capital formation)"
            ),
            type="Free",
        )
        MPSADJ = Variable(
            m,
            name="MPSADJ",
            description="savings rate scaling factor",
            type="Free",
        )

        # Equation
        BALANCE = Equation(
            m,
            name="BALANCE",
            description="notional balance constraint",
            definition=(1 + IADJ) + (1 + MPSADJ) == INCOME0,
        )

        mm = Model(m, name="mm", equations=[BALANCE], problem="MCP")
        mm.freeze(modifiables=[INCOME0, IADJ.fx, MPSADJ.fx])
        IADJ.setRecords({"lower": 0, "upper": 0, "scale": 1})
        mm.solve()

        self.assertEqual(MPSADJ.records["level"].tolist()[0], 1.5)

        IADJ.records = None
        MPSADJ.setRecords({"lower": 0, "upper": 0, "scale": 1})
        mm.solve()

        self.assertEqual(MPSADJ.records["level"].tolist()[0], 0)


def model_instance_suite():
    suite = unittest.TestSuite()
    tests = [
        ModelInstanceSuite(name)
        for name in dir(ModelInstanceSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(model_instance_suite())

tests/integration/test_model_instance.py
GAMS-dev/gamspy
import unittest
import subprocess
import os
import glob
from pathlib import Path


class ModelsSuite(unittest.TestCase):
    def test_full_models(self):
        paths = glob.glob(
            str(Path(__file__).parent) + os.sep + "models" + os.sep + "*.py"
        )

        print()
        for idx, path in enumerate(paths):
            print(f"[{idx + 1}/{len(paths)}] {path.split(os.sep)[-1]}")
            try:
                process = subprocess.run(
                    ["python", path], check=True, capture_output=True
                )

                self.assertTrue(process.returncode == 0)
            except subprocess.CalledProcessError as e:
                print("(x)")
                print(f"Output: {e.stderr.decode('utf-8')}")
                exit(1)


def gams_models_suite():
    suite = unittest.TestSuite()
    tests = [
        ModelsSuite(name)
        for name in dir(ModelsSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(gams_models_suite())

tests/integration/test_models.py
GAMS-dev/gamspy
import glob
import os
import time
import unittest

import numpy as np
import pandas as pd

from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import ModelStatus
from gamspy import Ord
from gamspy import Parameter
from gamspy import Sense
from gamspy import Set
from gamspy import Smax
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException


class SolveSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_read_on_demand(self):
        # Prepare data
        distances = pd.DataFrame(
            [
                ["seattle", "new-york", 2.5],
                ["seattle", "chicago", 1.7],
                ["seattle", "topeka", 1.8],
                ["san-diego", "new-york", 2.5],
                ["san-diego", "chicago", 1.8],
                ["san-diego", "topeka", 1.4],
            ]
        )
        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(self.m, name="i", records=["seattle", "san-diego"])
        j = Set(self.m, name="j", records=["new-york", "chicago", "topeka"])
        k = Set(
            self.m, name="k", records=["seattle", "san-diego", "california"]
        )
        k["seattle"] = False
        self.assertTrue(k._is_dirty)
        self.assertEqual(
            k.records.loc[0, :].values.tolist(), ["san-diego", ""]
        )
        self.assertFalse(k._is_dirty)

        # Data
        a = Parameter(self.m, name="a", domain=[i], records=capacities)
        b = Parameter(self.m, name="b", domain=[j], records=demands)
        d = Parameter(self.m, name="d", domain=[i, j], records=distances)
        c = Parameter(self.m, name="c", domain=[i, j])
        e = Parameter(self.m, name="e")

        c[i, j] = 90 * d[i, j] / 1000
        self.assertTrue(c._is_dirty)
        self.assertEqual(
            c.records.values.tolist(),
            [
                ["seattle", "new-york", 0.225],
                ["seattle", "chicago", 0.153],
                ["seattle", "topeka", 0.162],
                ["san-diego", "new-york", 0.225],
                ["san-diego", "chicago", 0.162],
                ["san-diego", "topeka", 0.126],
            ],
        )
        self.assertFalse(c._is_dirty)

        e.assignment = 5
        self.assertTrue(e._is_dirty)
        self.assertEqual(e.records.values.tolist(), [[5.0]])
        self.assertEqual(e.assignment, 5)

        with self.assertRaises(TypeError):
            e.records = 5

        # Variable
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")
        z = Variable(self.m, name="z")

        # Equation
        cost = Equation(self.m, name="cost")
        supply = Equation(self.m, name="supply", domain=[i])
        demand = Equation(self.m, name="demand", domain=[j])

        cost.definition = Sum((i, j), c[i, j] * x[i, j]) == z
        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= b[j]

        transport = Model(
            self.m,
            name="transport",
            equations=[cost, supply, demand],
            problem="LP",
            sense="min",
            objective=z,
        )
        transport.solve()

        # Test the columns of a set
        self.assertTrue(i.records.columns.tolist() == ["uni", "element_text"])

        # Test the columns of a parameter
        self.assertTrue(a.records.columns.tolist() == ["i", "value"])

        # Test the columns of scalar variable
        self.assertTrue(
            z.records.columns.tolist()
            == ["level", "marginal", "lower", "upper", "scale"]
        )

        # Test the columns of indexed variable
        self.assertTrue(
            x.records.columns.tolist()
            == ["i", "j", "level", "marginal", "lower", "upper", "scale"]
        )

        # Test the columns of equation
        self.assertTrue(
            cost.records.columns.tolist()
            == ["level", "marginal", "lower", "upper", "scale"]
        )

        # Test the columns of indexed equation
        self.assertTrue(
            supply.records.columns.tolist()
            == ["i", "level", "marginal", "lower", "upper", "scale"]
        )

    def test_after_first_solve(self):
        # Prepare data
        distances = pd.DataFrame(
            [
                ["seattle", "new-york", 2.5],
                ["seattle", "chicago", 1.7],
                ["seattle", "topeka", 1.8],
                ["san-diego", "new-york", 2.5],
                ["san-diego", "chicago", 1.8],
                ["san-diego", "topeka", 1.4],
            ]
        )
        capacities = pd.DataFrame([["seattle", 350], ["san-diego", 600]])
        demands = pd.DataFrame(
            [["new-york", 325], ["chicago", 300], ["topeka", 275]]
        )

        # Set
        i = Set(self.m, name="i", records=["seattle", "san-diego"])
        j = Set(self.m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(self.m, name="a", domain=[i], records=capacities)
        b = Parameter(self.m, name="b", domain=[j], records=demands)
        d = Parameter(self.m, name="d", domain=[i, j], records=distances)
        c = Parameter(self.m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")
        z = Variable(self.m, name="z")
        z2 = Variable(self.m, name="z2")

        # Equation
        cost = Equation(self.m, name="cost")
        cost2 = Equation(self.m, name="cost2")
        supply = Equation(self.m, name="supply", domain=[i])
        demand = Equation(self.m, name="demand", domain=[j])

        cost.definition = Sum((i, j), c[i, j] * x[i, j]) == z
        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= b[j]
        cost2.definition = Sum((i, j), c[i, j] * x[i, j]) * 5 == z2

        transport = Model(
            self.m,
            name="transport",
            equations=[cost, supply, demand],
            problem="LP",
            sense="min",
            objective=z,
        )
        transport.solve()

        first_z2_value = z2.records["level"].values[0]
        self.assertEqual(first_z2_value, 0.0)

        transport2 = Model(
            self.m,
            name="transport2",
            equations=[cost2, supply, demand],
            problem="LP",
            sense="min",
            objective=z2,
        )
        transport2.solve()
        second_z2_value = z2.records["level"].values[0]
        self.assertAlmostEqual(second_z2_value, 768.375, 3)

    def test_solve(self):
        # Prepare data
        distances = pd.DataFrame(
            [
                ["seattle", "new-york", 2.5],
                ["seattle", "chicago", 1.7],
                ["seattle", "topeka", 1.8],
                ["san-diego", "new-york", 2.5],
                ["san-diego", "chicago", 1.8],
                ["san-diego", "topeka", 1.4],
            ]
        )
        capacities = pd.DataFrame([["seattle", 350], ["san-diego", 600]])
        demands = pd.DataFrame(
            [["new-york", 325], ["chicago", 300], ["topeka", 275]]
        )

        # Set
        i = Set(self.m, name="i", records=["seattle", "san-diego"])
        j = Set(self.m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(self.m, name="a", domain=[i], records=capacities)
        b = Parameter(self.m, name="b", domain=[j], records=demands)
        d = Parameter(self.m, name="d", domain=[i, j], records=distances)
        c = Parameter(self.m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")
        z = Variable(self.m, name="z")

        # Equation
        cost = Equation(self.m, name="cost")
        supply = Equation(self.m, name="supply", domain=[i])
        demand = Equation(self.m, name="demand", domain=[j])

        cost.definition = Sum((i, j), c[i, j] * x[i, j]) == z
        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= b[j]

        transport = Model(
            self.m,
            name="transport",
            equations=[cost, supply, demand],
            problem="LP",
            sense="min",
            objective=z,
        )

        self.assertRaises(
            GamspyException,
            transport.solve,
            None,
            None,
            None,
            None,
            None,
            "bla",
        )

        # Test output redirection
        with open("test.gms", "w") as file:
            _ = transport.solve(
                options={"resLim": 100},
                output=file,
            )

        self.assertTrue(os.path.exists("test.gms"))
        self.assertTrue(transport.status == ModelStatus.OptimalGlobal)

        from gamspy._model import attribute_map

        for attr_name in attribute_map.values():
            self.assertTrue(hasattr(transport, attr_name))

            # Make sure model attributes are not in the container
            self.assertFalse(attr_name in self.m.data.keys())

        # Make sure dummy variable and equation is not in the container
        self.assertFalse(any("dummy_" in name for name in self.m.data.keys()))

        # Test invalid problem
        self.assertRaises(ValueError, Model, self.m, "dummy", [cost], "bla")

        # Test invalid sense
        self.assertRaises(
            ValueError, Model, self.m, "dummy", [cost], "LP", "bla"
        )

        # Test invalid objective variable
        self.assertRaises(
            TypeError, Model, self.m, "dummy", [cost], "LP", "min", a
        )

        # Test invalid commandline options
        self.assertRaises(
            GamspyException,
            transport.solve,
            None,
            {"bla": 100},
        )

        self.assertRaises(GamspyException, transport.solve, None, 5)

        # Try to solve invalid model
        m = Container(delayed_execution=True)
        cost = Equation(m, "cost")
        model = Model(m, "dummy", equations=[cost], problem="LP", sense="min")
        self.assertRaises(Exception, model.solve)

        # Test limited variables
        transport = Model(
            m,
            name="transport",
            equations=[cost, supply, demand],
            problem="LP",
            sense="min",
            objective=z,
            limited_variables=[x[i]],
        )

        self.assertEqual(
            transport.getStatement(),
            "Model transport / cost,supply,demand,x(i) /;",
        )

    def test_interrupt(self):
        cont = Container(delayed_execution=True)

        power_forecast_recs = np.array(
            [
                287,
                275,
                262,
                250,
                255,
                260,
                265,
                270,
                267,
                265,
                262,
                260,
                262,
                265,
                267,
                270,
                277,
                285,
                292,
                300,
                310,
                320,
                330,
                340,
                357,
                375,
                392,
                410,
                405,
                400,
                395,
                390,
                400,
                410,
                420,
                430,
                428,
                427,
                426,
                425,
                432,
                440,
                447,
                455,
                458,
                462,
                466,
                470,
                466,
                462,
                458,
                455,
                446,
                437,
                428,
                420,
                416,
                412,
                408,
                405,
                396,
                387,
                378,
                370,
                375,
                380,
                385,
                390,
                383,
                377,
                371,
                365,
                368,
                372,
                376,
                380,
                386,
                392,
                398,
                405,
                408,
                412,
                416,
                420,
                413,
                407,
                401,
                395,
                386,
                377,
                368,
                360,
                345,
                330,
                315,
                300,
            ]
        )

        # Energy
        t = Set(
            cont,
            name="t",
            records=[f"t{i}" for i in range(1, 97)],
            description="time slices (quarter-hour)",
        )

        # Parameters
        PowerForecast = Parameter(
            cont,
            name="PowerForecast",
            domain=[t],
            records=power_forecast_recs,
            description="electric power forecast",
        )

        # Power Plant (PP)
        # Scalars
        cPPvar = Parameter(
            cont,
            name="cPPvar",
            records=25,
            description="variable cost of power plant [euro / MWh]",
        )
        pPPMax = Parameter(
            cont,
            name="pPPMax",
            records=300,
            description="maximal capacity of power plant      [MW]",
        )

        # Sets
        m = Set(
            cont,
            name="m",
            records=[f"m{i}" for i in range(1, 9)],
            description="'stage of the power plant",
        )
        iS = Set(
            cont,
            name="iS",
            records=[f"iS{i}" for i in range(9)],
            description="interval for constant PP operation",
        )
        iI = Set(
            cont,
            name="iI",
            records=[f"iI{i}" for i in range(17)],
            description="length of idle time period",
        )

        # Spot Market (SM)
        # Scalars
        cBL = Parameter(
            cont,
            name="cBL",
            records=32,
            description="cost for one base load contract [euro / MWh]",
        )
        cPL = Parameter(
            cont,
            name="cPL",
            records=41,
            description="cost for one peak load contract [euro / MWh]",
        )

        # Parameter
        IPL = Parameter(
            cont,
            name="IPL",
            domain=[t],
            description="indicator function for peak load contracts",
        )
        IPL[t] = (Ord(t) >= 33) & (Ord(t) <= 80)

        # Load following Contract (LFC)
        # Scalars
        pLFCref = Parameter(
            cont,
            name="pLFCref",
            records=400,
            description="power reference level for the LFC",
        )

        b = Set(
            cont,
            name="b",
            records=[f"b{i}" for i in range(1, 4)],
            description="support points of the zone prices",
        )

        # Parameters
        eLFCbY = Parameter(
            cont,
            name="eLFCbY",
            domain=[b],
            records=np.array([54750, 182500, 9000000]),
            description="amount of energy at support point b",
        )
        cLFCvar = Parameter(
            cont,
            name="cLFCvar",
            domain=[b],
            records=np.array([80.0, 65.0, 52.0]),
            description="specific energy price in segment b",
        )
        eLFCb = Parameter(
            cont,
            name="eLFCb",
            domain=[b],
            description="daily border of energy volumes for LFC",
        )
        cLFCs = Parameter(
            cont,
            name="cLFCs",
            domain=[b],
            description="accumulated cost for LFC up to segment b",
        )

        # calculate the daily borders of the energy volumes for the zones
        eLFCb[b] = eLFCbY[b] / 365

        # calculate the accumulated cost
        cLFCs["b1"] = 0
        cLFCs["b2"] = cLFCvar["b1"] * eLFCb["b1"]
        cLFCs[b].where[Ord(b) > 2] = cLFCs[b.lag(1)] + cLFCvar[b.lag(1)] * (
            eLFCb[b.lag(1)] - eLFCb[b.lag(2)]
        )

        # Variables
        c = Variable(cont, name="c", type="free", description="total cost")
        cPP = Variable(
            cont, name="cPP", type="positive", description="cost of PP usage"
        )
        pPP = Variable(
            cont,
            name="pPP",
            type="positive",
            domain=[t],
            description="power withdrawn from power plant",
        )
        delta = Variable(
            cont,
            name="delta",
            type="binary",
            domain=[m, t],
            description="indicate if the PP is in stage m at time t",
        )
        chiS = Variable(
            cont,
            name="chiS",
            type="positive",
            domain=[t],
            description="indicate if there is a PP stage change",
        )
        chiI = Variable(
            cont,
            name="chiI",
            type="positive",
            domain=[t],
            description="indicate if the PP left the idle stage",
        )
        cSM = Variable(
            cont,
            name="cSM",
            type="positive",
            description="cost of energy from SM",
        )
        pSM = Variable(
            cont,
            name="pSM",
            type="positive",
            domain=[t],
            description="power from the spot market",
        )
        alpha = Variable(
            cont,
            name="alpha",
            type="integer",
            description="quantity of base load contracts",
        )
        beta = Variable(
            cont,
            name="beta",
            type="integer",
            description="quantity of peak load contracts",
        )
        cLFC = Variable(
            cont,
            name="cLFC",
            type="positive",
            description="cost of LFC which is the enery rate",
        )
        eLFCtot = Variable(
            cont,
            name="eLFCtot",
            type="positive",
            description="total energy amount of LFC",
        )
        eLFCs = Variable(
            cont,
            name="eLFCs",
            type="positive",
            domain=[b],
            description="energy from LFC in segment b",
        )
        pLFC = Variable(
            cont,
            name="pLFC",
            type="positive",
            domain=[t],
            description="power from the LFC",
        )
        mu = Variable(
            cont,
            name="mu",
            type="binary",
            domain=[b],
            description="indicator for segment b (for zone prices)",
        )

        alpha.up.assignment = Smax(t, PowerForecast[t])
        beta.up.assignment = alpha.up
        pLFC.up[t] = pLFCref

        # Equations
        obj = Equation(cont, name="obj", description="objective function")
        demand = Equation(
            cont,
            name="demand",
            domain=[t],
            description="demand constraint for energy forcast",
        )
        PPcost = Equation(cont, name="PPcost", description="power plant cost")
        PPpower = Equation(
            cont,
            name="PPpower",
            domain=[t],
            description="power of power plant at time t",
        )
        PPstage = Equation(
            cont,
            name="PPstage",
            domain=[t],
            description="exactly one stage of power plant at any time",
        )
        PPchiS1 = Equation(
            cont,
            name="PPchiS1",
            domain=[t, m],
            description="relate chi and delta variables first constraint",
        )
        PPchiS2 = Equation(
            cont,
            name="PPchiS2",
            domain=[t, m],
            description="relate chi and delta variables second constraint",
        )
        PPstageChange = Equation(
            cont,
            name="PPstageChange",
            domain=[t],
            description="restrict the number of stage changes",
        )
        PPstarted = Equation(
            cont,
            name="PPstarted",
            domain=[t],
            description="connect chiZ and chi variables",
        )
        PPidleTime = Equation(
            cont,
            name="PPidleTime",
            domain=[t],
            description="control the idle time of the plant",
        )
        SMcost = Equation(
            cont,
            name="SMcost",
            description="cost associated with spot market",
        )
        SMpower = Equation(
            cont,
            name="SMpower",
            domain=[t],
            description="power from the spot market",
        )
        LFCcost = Equation(
            cont, name="LFCcost", description="cost for the LFC"
        )
        LFCenergy = Equation(
            cont,
            name="LFCenergy",
            description="total energy from the LFC",
        )
        LFCmu = Equation(
            cont,
            name="LFCmu",
            description="exactly one price segment b",
        )
        LFCenergyS = Equation(
            cont,
            name="LFCenergyS",
            description="connect the mu variables with the total energy",
        )
        LFCemuo = Equation(
            cont,
            name="LFCemuo",
            description="accumulated energy amount for segement b1",
        )
        LFCemug = Equation(
            cont,
            name="LFCemug",
            domain=[b],
            description="accumulated energy amount for all other segements",
        )

        # the objective function: total cost eq. (6)
        obj.definition = c == cPP + cSM + cLFC

        # meet the power demand for each time period exactly eq. (23)
        demand[t] = pPP[t] + pSM[t] + pLFC[t] == PowerForecast[t]

        # (fix cost +) variable cost * energy amount produced eq. (7) & (8)
        PPcost.definition = cPP == cPPvar * Sum(t, 0.25 * pPP[t])

        # power produced by the power plant eq. (26)
        PPpower[t] = pPP[t] == pPPMax * Sum(
            m.where[Ord(m) > 1], 0.1 * (Ord(m) + 2) * delta[m, t]
        )

        # the power plant is in exactly one stage at any time eq. (25)
        PPstage[t] = Sum(m, delta[m, t]) == 1

        # next constraints model the minimum time period a power plant is in
        # the same state and the constraint of the minimum idle time
        # we need variable 'chiS' to find out when a status change takes place
        # eq. (27)
        PPchiS1[t, m].where[Ord(t) > 1] = (
            chiS[t] >= delta[m, t] - delta[m, t.lag(1)]
        )

        # second constraint for 'chiS' variable eq. (28)
        PPchiS2[t, m].where[Ord(t) > 1] = (
            chiS[t] >= delta[m, t.lag(1)] - delta[m, t]
        )

        # control the minimum change time period eq. (29)
        PPstageChange[t].where[Ord(t) < Card(t) - Card(iS) + 2] = (
            Sum(iS, chiS[t.lead(Ord(iS))]) <= 1
        )

        # indicate if the plant left the idle state eq. (30)
        PPstarted[t] = chiI[t] >= delta["m1", t.lag(1)] - delta["m1", t]

        # control the minimum idle time period:
        # it has to be at least Nk2 time periods long eq. (31)
        PPidleTime[t].where[Ord(t) < Card(t) - Card(iI) + 2] = (
            Sum(iI, chiI[t.lead(Ord(iI))]) <= 1
        )

        # cost for the spot market eq. (12)
        # consistent of the base load (alpha) and peak load (beta) contracts
        SMcost.definition = cSM == 24 * cBL * alpha + 12 * cPL * beta

        # Spot Market power contribution eq. (9)
        SMpower[t] = pSM[t] == alpha + IPL[t] * beta

        # cost of the LFC is given by the energy rate eq. (14) & (21)
        LFCcost.definition = cLFC == Sum(
            b, cLFCs[b] * mu[b] + cLFCvar[b] * eLFCs[b]
        )

        # total energy from the LFC eq. (16)
        # connect the eLFC[t] variables with eLFCtot
        LFCenergy.definition = eLFCtot == Sum(t, 0.25 * pLFC[t])

        # indicator variable 'mu':
        # we are in exactly one price segment b eq. (18)
        LFCmu.definition = Sum(b, mu[b]) == 1

        # connect the 'mu' variables with the total energy amount eq. (19)
        LFCenergyS.definition = eLFCtot == Sum(
            b.where[Ord(b) > 1], eLFCb[b.lag(1)] * mu[b]
        ) + Sum(b, eLFCs[b])

        # accumulated energy amount for segment "b1" eq. (20)
        LFCemuo.definition = eLFCs["b1"] <= eLFCb["b1"] * mu["b1"]

        # accumulated energy amount for all other segments (then "b1") eq. (20)
        LFCemug[b].where[Ord(b) > 1] = (
            eLFCs[b] <= (eLFCb[b] - eLFCb[b.lag(1)]) * mu[b]
        )

        energy = Model(
            cont,
            name="energy",
            equations=cont.getEquations(),
            problem="MIP",
            sense=Sense.MIN,
            objective=c,
        )
        self.assertRaises(GamspyException, energy.interrupt)

        # relative termination criterion for MIP (relative gap)
        cont.addOptions({"optCr": 0.000001})

        def interrupt_gams(model):
            time.sleep(2)
            model.interrupt()

        import threading

        threading.Thread(target=interrupt_gams, args=(energy,)).start()

        energy.solve()

        self.assertIsNotNone(energy.objective_value)

    def test_solver_options(self):
        m = Container(delayed_execution=True)

        # Prepare data
        distances = [
            ["seattle", "new-york", 2.5],
            ["seattle", "chicago", 1.7],
            ["seattle", "topeka", 1.8],
            ["san-diego", "new-york", 2.5],
            ["san-diego", "chicago", 1.8],
            ["san-diego", "topeka", 1.4],
        ]

        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(m, name="a", domain=[i], records=capacities)
        b = Parameter(m, name="b", domain=[j], records=demands)
        d = Parameter(m, name="d", domain=[i, j], records=distances)
        c = Parameter(m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(m, name="x", domain=[i, j], type="Positive")

        # Equation
        supply = Equation(m, name="supply", domain=[i])
        demand = Equation(m, name="demand", domain=[j])

        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= b[j]

        transport = Model(
            m,
            name="transport",
            equations=m.getEquations(),
            problem="LP",
            sense=Sense.MIN,
            objective=Sum((i, j), c[i, j] * x[i, j]),
        )

        # Test solver change
        transport.solve(solver="CONOPT", solver_options={"rtmaxv": "1.e12"})

        lst_file = glob.glob(f"{m.workspace.working_directory}{os.sep}*.lst")[
            0
        ]
        with open(lst_file) as file:
            content = file.read()
            self.assertTrue("CONOPT" in content)

        self.assertTrue(
            os.path.exists(
                f"{m.workspace.working_directory}{os.sep}conopt.123"
            )
        )

        self.assertRaises(
            GamspyException, transport.solve, None, None, {"rtmaxv": "1.e12"}
        )

    def test_delayed_execution(self):
        m = Container()
        m.delayed_execution = False

        # Prepare data
        distances = [
            ["seattle", "new-york", 2.5],
            ["seattle", "chicago", 1.7],
            ["seattle", "topeka", 1.8],
            ["san-diego", "new-york", 2.5],
            ["san-diego", "chicago", 1.8],
            ["san-diego", "topeka", 1.4],
        ]

        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        _ = Parameter(m, name="a", domain=[i], records=capacities)
        _ = Parameter(m, name="b", domain=[j], records=demands)
        d = Parameter(m, name="d", domain=[i, j], records=distances)
        c = Parameter(m, name="c", domain=[i, j])
        e = Parameter(m, name="e")
        e.assignment = 5
        with self.assertRaises(Exception):
            c[i] = 90 * d[i, j] / 1000

        m = Container()
        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])
        v = Variable(m, name="v", domain=[i, j])
        v.l[i, j] = 5
        self.assertIsNotNone(v.records)

        x = Variable(m, name="x")
        x.l.assignment = 5
        self.assertIsNotNone(x.records)


def solve_suite():
    suite = unittest.TestSuite()
    tests = [
        SolveSuite(name)
        for name in dir(SolveSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(solve_suite())

tests/integration/test_solve.py
GAMS-dev/gamspy
import argparse
import doctest
import glob
import os
import unittest

from integration.test_cmd_script import cmd_suite
from integration.test_engine import engine_suite
from integration.test_model_instance import model_instance_suite
from integration.test_models import gams_models_suite
from integration.test_solve import solve_suite
from unit.test_alias import alias_suite
from unit.test_condition import condition_suite
from unit.test_container import container_suite
from unit.test_domain import domain_suite
from unit.test_equation import equation_suite
from unit.test_magics import magics_suite
from unit.test_math import math_suite
from unit.test_model import model_suite
from unit.test_operation import operation_suite
from unit.test_parameter import parameter_suite
from unit.test_set import set_suite
from unit.test_utils import utils_suite
from unit.test_variable import variable_suite

import gamspy


class GamspySuite(unittest.TestCase):
    def test_version(self):
        import gamspy

        self.assertEqual(gamspy.__version__, "0.9.0rc1")


class DocsSuite(unittest.TestCase):
    def test_docs(self):
        root = gamspy.__path__[0]

        api_files = [
            f"{root}{os.sep}_container.py",
            f"{root}{os.sep}_model_instance.py",
            f"{root}{os.sep}_model.py",
            f"{root}{os.sep}utils.py",
            f"{root}{os.sep}_algebra{os.sep}domain.py",
            f"{root}{os.sep}_algebra{os.sep}number.py",
            f"{root}{os.sep}_symbols{os.sep}alias.py",
            f"{root}{os.sep}_symbols{os.sep}equation.py",
            f"{root}{os.sep}_symbols{os.sep}parameter.py",
            f"{root}{os.sep}_symbols{os.sep}set.py",
            f"{root}{os.sep}_symbols{os.sep}universe_alias.py",
            f"{root}{os.sep}_symbols{os.sep}variable.py",
            f"{root}{os.sep}math{os.sep}log_power.py",
            f"{root}{os.sep}math{os.sep}misc.py",
            f"{root}{os.sep}math{os.sep}probability.py",
            f"{root}{os.sep}math{os.sep}trigonometric.py",
        ]

        for file in api_files:
            results = doctest.testfile(
                file, verbose=True, module_relative=False
            )

            self.assertEqual(results.failed, 0)


def gamspy_suite():
    suite = unittest.TestSuite()
    tests = [
        GamspySuite(name)
        for name in dir(GamspySuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


def docs_suite():
    suite = unittest.TestSuite()
    tests = [
        DocsSuite(name) for name in dir(DocsSuite) if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


def run_integration_tests(runner: unittest.TextTestRunner):
    integration_suites = [
        solve_suite,
        model_instance_suite,
        gams_models_suite,
        cmd_suite,
        engine_suite,
    ]

    print(f"Running integration tests\n{'='*80}")
    for suite in integration_suites:
        print("=" * 80)
        print(f"\nRunning {suite.__name__}...")
        result = runner.run(suite())
        if not result.wasSuccessful():
            exit(1)
        print("=" * 80)


def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--integration", action="store_true")
    parser.add_argument("--doc", action="store_true")

    return parser.parse_args()


def main():
    args = get_args()

    runner = unittest.TextTestRunner()

    unittest_suites = [
        gamspy_suite,
        container_suite,
        math_suite,
        utils_suite,
        set_suite,
        alias_suite,
        parameter_suite,
        variable_suite,
        equation_suite,
        model_suite,
        operation_suite,
        domain_suite,
        condition_suite,
        magics_suite,
    ]

    print(f"Running unittests\n{'='*80}")
    for suite in unittest_suites:
        print("=" * 80)
        print(f"\nRunning {suite.__name__}...")
        result = runner.run(suite())
        if not result.wasSuccessful():
            return 1
        print("=" * 80)

    if args.doc:
        print("=" * 80)
        print(f"\nRunning {suite.__name__}...")
        result = runner.run(docs_suite())
        if not result.wasSuccessful():
            return 1
        print("=" * 80)

    if args.integration:
        run_integration_tests(runner)

    # clean up
    csv_paths = glob.glob("*.csv")
    for csv_path in csv_paths:
        os.remove(csv_path)

    xlsx_paths = glob.glob("*.xlsx")
    for xlsx_path in xlsx_paths:
        os.remove(xlsx_path)

    txt_paths = glob.glob("*.txt")
    for txt_path in txt_paths:
        if txt_path.endswith("dev_requirements.txt"):
            continue
        os.remove(txt_path)

    gdx_paths = glob.glob("*.gdx")
    for gdx_path in gdx_paths:
        os.remove(gdx_path)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())

tests/test_gamspy.py
GAMS-dev/gamspy
import unittest

from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Set
from gamspy import UniverseAlias
from gamspy.exceptions import GamspyException


class AliasSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_alias_string(self):
        # Set and Alias without domain
        i = Set(self.m, name="i", records=["a", "b", "c"])
        j = Alias(self.m, name="j", alias_with=i)
        self.assertEqual(j.gamsRepr(), "j")
        self.assertEqual(j.getStatement(), "Alias(i,j);")

        # Set and Alias with domain
        k = Set(self.m, name="k", domain=[i], records=["a", "b"])
        m = Alias(self.m, name="m", alias_with=k)
        self.assertEqual(m.gamsRepr(), "m")
        self.assertEqual(m.getStatement(), "Alias(k,m);")

        # Check if the name is reserved
        self.assertRaises(GamspyException, Alias, self.m, "set", i)

    def test_override(self):
        # Try to add the same Alias with non-Set alias_with
        u = Set(self.m, "u")
        v = Alias(self.m, "v", alias_with=u)
        eq = Equation(self.m, "eq", domain=[u, v])
        self.assertRaises(TypeError, self.m.addAlias, "v", eq)

        # Try to add the same alias
        self.assertRaises(ValueError, self.m.addAlias, "u", u)

    def test_universe_alias(self):
        h = UniverseAlias(self.m, "h")
        self.assertEqual(len(h), 0)
        _ = Set(self.m, "i", records=["i1", "i2"])
        _ = Set(self.m, "j", records=["j1", "j2"])

        self.assertEqual(
            h.records.values.tolist(), [["i1"], ["i2"], ["j1"], ["j2"]]
        )

        self.assertEqual(len(h), 4)

        self.m.write("test.gdx")

        bla = Container(delayed_execution=True)
        bla.read("test.gdx")
        self.assertEqual(
            bla.data["h"].records.values.tolist(), h.records.values.tolist()
        )


def alias_suite():
    suite = unittest.TestSuite()
    tests = [
        AliasSuite(name)
        for name in dir(AliasSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(alias_suite())

tests/unit/test_alias.py
GAMS-dev/gamspy
import unittest

import pandas as pd

import gamspy.math as gamspy_math
from gamspy import Container
from gamspy import Equation
from gamspy import Number
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


class ConditionSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_condition_on_expression(self):
        steel_plants = ["ahmsa", "fundidora", "sicartsa", "hylsa", "hylsap"]
        markets = ["mexico-df", "monterrey", "guadalaja"]

        rail_distances = pd.DataFrame(
            [
                ["ahmsa", "mexico-df", 1204],
                ["ahmsa", "monterrey", 218],
                ["ahmsa", "guadalaja", 1125],
                ["ahmsa", "export", 739],
                ["fundidora", "mexico-df", 1017],
                ["fundidora", "guadalaja", 1030],
                ["fundidora", "export", 521],
                ["sicartsa", "mexico-df", 819],
                ["sicartsa", "monterrey", 1305],
                ["sicartsa", "guadalaja", 704],
                ["hylsa", "mexico-df", 1017],
                ["hylsa", "guadalaja", 1030],
                ["hylsa", "export", 521],
                ["hylsap", "mexico-df", 185],
                ["hylsap", "monterrey", 1085],
                ["hylsap", "guadalaja", 760],
                ["hylsap", "export", 315],
                ["import", "mexico-df", 428],
                ["import", "monterrey", 521],
                ["import", "guadalaja", 300],
            ]
        )

        # Set
        i = Set(
            self.m,
            name="i",
            records=pd.DataFrame(steel_plants),
            description="steel plants",
        )
        j = Set(
            self.m,
            name="j",
            records=pd.DataFrame(markets),
            description="markets",
        )

        # Data
        rd = Parameter(
            self.m,
            name="rd",
            domain=["*", "*"],
            records=rail_distances,
            description="rail distances from plants to markets",
        )
        muf = Parameter(
            self.m,
            name="muf",
            domain=[i, j],
            description="transport rate: final products",
        )

        # Condition
        muf[i, j] = (2.48 + 0.0084 * rd[i, j]).where[rd[i, j]]

        last_statement = list(self.m._unsaved_statements.values())[-1]
        self.assertEqual(
            last_statement.getStatement(),
            "muf(i,j) = ((2.48 + (0.0084 * rd(i,j))) $ (rd(i,j)));",
        )

        m = Container(delayed_execution=True)

        p = Set(m, name="p", records=[f"pos{i}" for i in range(1, 11)])
        o = Set(m, name="o", records=[f"opt{i}" for i in range(1, 6)])

        sumc = Parameter(m, name="sumc", domain=[o, p])
        sumc[o, p] = gamspy_math.uniform(0, 1)

        op = Variable(m, name="op", type="free", domain=[o, p])

        # Equation
        defopLS = Equation(m, name="defopLS", domain=[o, p])
        defopLS[o, p].where[sumc[o, p] <= 0.5] = op[o, p] == Number(1)
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].getStatement(),
            "defopLS(o,p) $ (sumc(o,p) <= 0.5) .. op(o,p) =e= 1;",
        )

        expression = Sum(i, muf[i, j]).where[muf[i, j] > 0]
        self.assertEqual(
            expression.getStatement(), "(sum(i,muf(i,j)) $ (muf(i,j) > 0))"
        )

        random_eq = Equation(m, "random", domain=[i, j])
        random_eq[i, j] = Sum(i, muf[i, j]).where[muf[i, j] > 0] >= 0
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].getStatement(),
            "random(i,j) .. (sum(i,muf(i,j)) $ (muf(i,j) > 0)) =g= 0;",
        )

        i["ahmsa"] = True
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            'i("ahmsa") = yes;',
        )

        i["ahmsa"] = False
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            'i("ahmsa") = no;',
        )

    def test_condition_on_equation(self):
        td_data = pd.DataFrame(
            [
                ["icbm", "2", 0.05],
                ["icbm", "6", 0.15],
                ["icbm", "7", 0.10],
                ["icbm", "8", 0.15],
                ["icbm", "9", 0.20],
                ["icbm", "18", 0.05],
                ["mrbm-1", "1", 0.16],
                ["mrbm-1", "2", 0.17],
                ["mrbm-1", "3", 0.15],
                ["mrbm-1", "4", 0.16],
                ["mrbm-1", "5", 0.15],
                ["mrbm-1", "6", 0.19],
                ["mrbm-1", "7", 0.19],
                ["mrbm-1", "8", 0.18],
                ["mrbm-1", "9", 0.20],
                ["mrbm-1", "10", 0.14],
                ["mrbm-1", "12", 0.02],
                ["mrbm-1", "14", 0.12],
                ["mrbm-1", "15", 0.13],
                ["mrbm-1", "16", 0.12],
                ["mrbm-1", "17", 0.15],
                ["mrbm-1", "18", 0.16],
                ["mrbm-1", "19", 0.15],
                ["mrbm-1", "20", 0.15],
                ["lr-bomber", "1", 0.04],
                ["lr-bomber", "2", 0.05],
                ["lr-bomber", "3", 0.04],
                ["lr-bomber", "4", 0.04],
                ["lr-bomber", "5", 0.04],
                ["lr-bomber", "6", 0.10],
                ["lr-bomber", "7", 0.08],
                ["lr-bomber", "8", 0.09],
                ["lr-bomber", "9", 0.08],
                ["lr-bomber", "10", 0.05],
                ["lr-bomber", "11", 0.01],
                ["lr-bomber", "12", 0.02],
                ["lr-bomber", "13", 0.01],
                ["lr-bomber", "14", 0.02],
                ["lr-bomber", "15", 0.03],
                ["lr-bomber", "16", 0.02],
                ["lr-bomber", "17", 0.05],
                ["lr-bomber", "18", 0.08],
                ["lr-bomber", "19", 0.07],
                ["lr-bomber", "20", 0.08],
                ["f-bomber", "10", 0.04],
                ["f-bomber", "11", 0.09],
                ["f-bomber", "12", 0.08],
                ["f-bomber", "13", 0.09],
                ["f-bomber", "14", 0.08],
                ["f-bomber", "15", 0.02],
                ["f-bomber", "16", 0.07],
                ["mrbm-2", "1", 0.08],
                ["mrbm-2", "2", 0.06],
                ["mrbm-2", "3", 0.08],
                ["mrbm-2", "4", 0.05],
                ["mrbm-2", "5", 0.05],
                ["mrbm-2", "6", 0.02],
                ["mrbm-2", "7", 0.02],
                ["mrbm-2", "10", 0.10],
                ["mrbm-2", "11", 0.05],
                ["mrbm-2", "12", 0.04],
                ["mrbm-2", "13", 0.09],
                ["mrbm-2", "14", 0.02],
                ["mrbm-2", "15", 0.01],
                ["mrbm-2", "16", 0.01],
            ]
        )

        wa_data = pd.DataFrame(
            [
                ["icbm", 200],
                ["mrbm-1", 100],
                ["lr-bomber", 300],
                ["f-bomber", 150],
                ["mrbm-2", 250],
            ]
        )

        tm_data = pd.DataFrame(
            [
                ["1", 30],
                ["6", 100],
                ["10", 40],
                ["14", 50],
                ["15", 70],
                ["16", 35],
                ["20", 10],
            ]
        )

        # Sets
        w = Set(
            self.m,
            name="w",
            records=["icbm", "mrbm-1", "lr-bomber", "f-bomber", "mrbm-2"],
        )
        t = Set(self.m, name="t", records=[str(i) for i in range(1, 21)])

        # Parameters
        td = Parameter(self.m, name="td", domain=[w, t], records=td_data)
        wa = Parameter(self.m, name="wa", domain=[w], records=wa_data)
        tm = Parameter(self.m, name="tm", domain=[t], records=tm_data)

        # Variables
        x = Variable(self.m, name="x", domain=[w, t], type="Positive")

        # Equations
        maxw = Equation(self.m, name="maxw", domain=[w])
        minw = Equation(self.m, name="minw", domain=[t])

        maxw[w] = Sum(t.where[td[w, t]], x[w, t]) <= wa[w]
        minw[t].where[tm[t]] = Sum(w.where[td[w, t]], x[w, t]) >= tm[t]

        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "minw(t) $ (tm(t)) .. sum(w $ td(w,t),x(w,t)) =g= tm(t);",
        )

        m = Container(delayed_execution=True)

        p = Set(m, name="p", records=[f"pos{i}" for i in range(1, 11)])
        o = Set(m, name="o", records=[f"opt{i}" for i in range(1, 6)])

        # Variables
        sumc = Variable(m, name="sumc", type="free", domain=[o, p])
        op = Variable(m, name="op", type="free", domain=[o, p])

        # Equation
        defopLS = Equation(m, name="defopLS", domain=[o, p])
        defopLS[o, p] = op[o, p] == Number(1).where[sumc[o, p] >= 0.5]
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].getStatement(),
            "defopLS(o,p) .. op(o,p) =e= (1 $ (sumc(o,p) >= 0.5));",
        )

        k = Set(m, "k", domain=[p])
        k[p].where[k[p]] = True
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].gamsRepr(),
            "k(p) $ (k(p)) = yes;",
        )

    def test_operator_comparison_in_condition(self):
        m = Container(delayed_execution=True)
        s = Set(m, name="s", records=[str(i) for i in range(1, 4)])
        c = Parameter(m, name="c", domain=[s])
        c[s].where[Ord(s) <= Ord(s)] = 1
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].getStatement(),
            "c(s) $ (ord(s) <= ord(s)) = 1;",
        )


def condition_suite():
    suite = unittest.TestSuite()
    tests = [
        ConditionSuite(name)
        for name in dir(ConditionSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(condition_suite())

tests/unit/test_condition.py
GAMS-dev/gamspy
import unittest

import pandas as pd

import gamspy.utils as utils
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Problem
from gamspy import Sense
from gamspy import Set
from gamspy import Sum
from gamspy import Variable


class ContainerSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_container(self):
        import gams.transfer as gt

        i = gt.Set(self.m, "i")
        self.m._cast_symbols()
        self.assertTrue(isinstance(self.m["i"], Set))

        j = gt.Alias(self.m, "j", i)
        self.m._cast_symbols()
        self.assertTrue(isinstance(self.m["j"], Alias))

        a = gt.Parameter(self.m, "a")
        self.m._cast_symbols()
        self.assertTrue(isinstance(self.m["a"], Parameter))

        v = gt.Variable(self.m, "v")
        self.m._cast_symbols()
        self.assertTrue(isinstance(self.m["v"], Variable))

        e = gt.Equation(self.m, "e", type="eq")
        self.m._cast_symbols()
        self.assertTrue(isinstance(self.m["e"], Equation))

        # Test getters
        m = Container(delayed_execution=True)

        i = Set(m, "i")
        self.assertTrue(isinstance(m["i"], Set))

        j = Alias(m, "j", i)
        self.assertTrue(isinstance(m["j"], Alias))

        a = Parameter(m, "a")
        self.assertTrue(isinstance(m["a"], Parameter))

        v = Variable(m, "v")
        self.assertTrue(isinstance(m["v"], Variable))

        e = Equation(m, "e")
        self.assertTrue(isinstance(m["e"], Equation))

        self.assertEqual(m.getSets(), [i])
        self.assertEqual(m.getAliases(), [j])
        self.assertEqual(m.getParameters(), [a])
        self.assertEqual(m.getVariables(), [v])
        self.assertEqual(m.getEquations(), [e])

        # test addX syntax
        m = Container(delayed_execution=True)
        i1 = m.addSet("i")
        self.assertRaises(ValueError, m.addSet, "i", i1)
        self.assertTrue(isinstance(i1, Set))
        i2 = m.addSet("i")
        self.assertTrue(id(i1) == id(i2))
        i3 = m.addSet("i", records=["new_record"], description="new desc")
        self.assertTrue(id(i1) == id(i3))
        self.assertRaises(ValueError, m.addSet, "i", [j])
        self.assertRaises(TypeError, m.addSet, "i", None, 5)

        j1 = m.addAlias("j", i1)
        self.assertTrue(isinstance(j1, Alias))
        j2 = m.addAlias("j", i1)
        self.assertTrue(id(j1) == id(j2))
        j3 = m.addAlias("j", j2)
        self.assertTrue(id(j3) == id(j2))

        a1 = m.addParameter("a")
        self.assertRaises(ValueError, m.addParameter, "a", i1)
        self.assertTrue(isinstance(a1, Parameter))
        a2 = m.addParameter("a")
        self.assertTrue(id(a1) == id(a2))
        self.assertRaises(ValueError, m.addParameter, "a", ["*"])
        self.assertRaises(TypeError, m.addParameter, "a", None, None, 5)

        v1 = m.addVariable("v")
        self.assertRaises(ValueError, m.addVariable, "v", "free", domain=i1)
        self.assertTrue(isinstance(v1, Variable))
        v2 = m.addVariable("v", description="blabla", records=pd.DataFrame())
        self.assertTrue(id(v1) == id(v2))
        self.assertRaises(ValueError, m.addVariable, "v", "free", ["*"])
        self.assertRaises(ValueError, m.addVariable, "v", "dayum")

        e1 = m.addEquation("e")
        self.assertRaises(ValueError, m.addEquation, "e", "regular", i1)
        self.assertTrue(isinstance(e1, Equation))
        e2 = m.addEquation("e")
        self.assertTrue(id(e1) == id(e2))
        self.assertRaises(ValueError, m.addEquation, "e", "bla")
        self.assertRaises(ValueError, m.addEquation, "e", "leq")
        e3 = m.addEquation("e", records=pd.DataFrame())
        self.assertTrue(id(e3) == id(e1))

    def test_read_write(self):
        m = Container(delayed_execution=True)
        _ = Set(m, "i", records=["i1", "i2"])
        _ = Set(m, "j", records=["j1", "j2"])
        m.write("test.gdx")

        _ = Set(self.m, name="k", records=["k1", "k2"])
        self.m.read("test.gdx", ["i"])
        self.assertEqual(list(self.m.data.keys()), ["k", "i"])

    def test_loadRecordsFromGdx(self):
        i = Set(self.m, name="i", records=["i1", "i2"])
        a = Parameter(
            self.m, name="a", domain=[i], records=[("i1", 1), ("i2", 2)]
        )
        self.m.write("test.gdx")

        # Load all
        new_container = Container(delayed_execution=True)
        i = Set(new_container, name="i")
        a = Parameter(new_container, name="a", domain=[i])
        new_container.loadRecordsFromGdx("test.gdx")

        # Set
        self.assertEqual(i.records.values.tolist(), [["i1", ""], ["i2", ""]])

        # Parameter
        self.assertEqual(a.records.values.tolist(), [["i1", 1.0], ["i2", 2.0]])

        # Load specific symbols
        new_container2 = Container(delayed_execution=True)
        i = Set(new_container2, name="i")
        a = Parameter(new_container2, name="a", domain=[i])
        new_container2.loadRecordsFromGdx("test.gdx", ["i"])

        self.assertEqual(i.records.values.tolist(), [["i1", ""], ["i2", ""]])
        self.assertIsNone(a.records)

    def test_enums(self):
        self.assertEqual(str(Problem.LP), "LP")
        self.assertEqual(str(Sense.MAX), "MAX")

        self.assertEqual(
            Problem.values(),
            [
                "LP",
                "NLP",
                "QCP",
                "DNLP",
                "MIP",
                "RMIP",
                "MINLP",
                "RMINLP",
                "MIQCP",
                "RMIQCP",
                "MCP",
                "CNS",
                "MPEC",
                "RMPEC",
                "EMP",
                "MPSGE",
            ],
        )

        self.assertEqual(Sense.values(), ["MIN", "MAX", "FEASIBILITY"])

    def test_options(self):
        options = {
            "limRow": 0,
            "limCol": 0,
            "solPrint": "silent",
            "solver": "cplex",
            "solveLink": "%solveLink.loadLibrary%",
        }
        self.m.addOptions(options)
        self.assertTrue(len(self.m._unsaved_statements), 5)
        self.assertEqual(
            list(self.m._unsaved_statements.values())[0], "limRow = 0"
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[1], "limCol = 0"
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[2], "solPrint = silent"
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[3], "solver = cplex"
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[4],
            "solveLink = %solveLink.loadLibrary%",
        )

        options = {"bla": 0}
        self.assertRaises(ValueError, self.m.addOptions, options)

    def test_arbitrary_gams_code(self):
        self.m._addGamsCode("Set i / i1*i3 /;")
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1], "Set i / i1*i3 /;"
        )

        m = Container(delayed_execution=True)
        m._addGamsCode("scalar piHalf / [pi/2] /;")
        m._run()
        self.assertTrue("piHalf" in m.data.keys())
        self.assertEqual(m["piHalf"].records.values[0][0], 1.5707963267948966)

    def test_system_directory(self):
        import gamspy_base

        expected_path = gamspy_base.__path__[0]

        m = Container(delayed_execution=True)
        self.assertEqual(m.system_directory.lower(), expected_path.lower())

        self.assertEqual(
            utils._getGAMSPyBaseDirectory().lower(), expected_path.lower()
        )

    def test_non_empty_working_directory(self):
        import tempfile

        with tempfile.TemporaryDirectory() as td:
            # (1) run model once to fill the working directory
            m = Container(delayed_execution=True, working_directory=td)
            i = Set(m, name="i", records=["i1"])
            x = Variable(m, name="x", type="Positive")

            dummy = Model(
                m,
                name="dummy",
                equations=[],
                problem="LP",
                sense=Sense.MIN,
                objective=Sum((i,), x),
            )
            dummy.solve()

            # (2) make sure to not use default_restart.g00 generated by (1)
            m = Container(delayed_execution=True, working_directory=td)
            i = Set(m, name="i", records=["i1"])
            x = Variable(m, name="x", type="Positive")

            dummy = Model(
                m,
                name="dummy",
                equations=[],
                problem="LP",
                sense=Sense.MIN,
                objective=Sum((i,), x),
            )
            dummy.solve()

    def test_write_load_on_demand(self):
        m = Container(delayed_execution=True)
        i = Set(m, name="i", records=["i1"])
        p1 = Parameter(m, name="p1", domain=[i], records=[["i1", 1]])
        p2 = Parameter(m, name="p2", domain=[i])
        p2[i] = p1[i]
        m.write("data.gdx")
        m = Container(delayed_execution=True, load_from="data.gdx")
        self.assertEqual(m["p2"].toList(), [("i1", 1.0)])


def container_suite():
    suite = unittest.TestSuite()
    tests = [
        ContainerSuite(name)
        for name in dir(ContainerSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(container_suite())

tests/unit/test_container.py
GAMS-dev/gamspy
import unittest

from gamspy import Container
from gamspy import Domain
from gamspy import Parameter
from gamspy import Set
from gamspy._algebra.domain import DomainException


class DomainSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_domain(self):
        # Set
        i = Set(self.m, name="i", records=["seattle", "san-diego"])
        j = Set(self.m, name="j", records=["new-york", "chicago", "topeka"])

        domain = Domain(i, j)
        self.assertEqual(domain.gamsRepr(), "(i,j)")

        # Domain with less than two sets
        self.assertRaises(DomainException, Domain, i)

        # Domain with no set or alias symbols
        self.assertRaises(DomainException, Domain, "i", "j")

    def test_domain_forwarding(self):
        m = Container(delayed_execution=True)
        i = Set(m, name="i")
        _ = Parameter(
            m,
            name="p",
            domain=[i],
            domain_forwarding=True,
            records=[["i1", 1]],
        )
        self.assertEqual(i.toList(), ["i1"])


def domain_suite():
    suite = unittest.TestSuite()
    tests = [
        DomainSuite(name)
        for name in dir(DomainSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(domain_suite())

tests/unit/test_domain.py
GAMS-dev/gamspy
import unittest

import pandas as pd

import gamspy._symbols.implicits as implicits
from gamspy import Alias
from gamspy import Container
from gamspy import Equation
from gamspy import EquationType
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException


class EquationSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_equation_types(self):
        # Prepare data
        canning_plants = ["seattle", "san-diego"]

        c = Parameter(self.m, name="c", domain=[], records=0.5)
        x = Variable(
            self.m,
            name="x",
            domain=[],
            records={"lower": 1.0, "level": 1.5, "upper": 3.75},
        )

        # Sets
        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )

        # Equations
        eq1 = Equation(self.m, "eq1", type="nonbinding")
        eq1.definition = x == c
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "eq1 .. x =n= c;",
        )
        self.assertEqual(eq1.type, "nonbinding")

        eq2 = Equation(self.m, "eq2", domain=[i], type="nonbinding")
        eq2[i] = x[i] == c[i]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "eq2(i) .. x(i) =n= c(i);",
        )

        eq2[i].definition = x[i] == c[i]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "eq2(i) .. x(i) =n= c(i);",
        )

        # eq
        eq3 = Equation(self.m, "eq3", domain=[i])
        eq3[i] = x == c
        self.assertEqual(eq3.type, "eq")

        # geq
        eq4 = Equation(self.m, "eq4", domain=[i])
        eq4[i] = x >= c
        self.assertEqual(eq4.type, "geq")

        # leq
        eq5 = Equation(self.m, "eq5", domain=[i])
        eq5[i] = x <= c
        self.assertEqual(eq5.type, "leq")

        self.assertEqual(str(EquationType.REGULAR), "REGULAR")
        eq6 = Equation(self.m, "eq6", type=EquationType.REGULAR, domain=[i])
        self.assertEqual(eq6.type, "eq")

        self.assertEqual(
            EquationType.values(),
            ["REGULAR", "NONBINDING", "EXTERNAL", "CONE", "BOOLEAN"],
        )

    def test_equation_declaration(self):
        # Check if the name is reserved
        self.assertRaises(GamspyException, Equation, self.m, "set")

        # Prepare data
        canning_plants = ["seattle", "san-diego"]
        markets = ["new-york", "chicago", "topeka"]

        # Sets
        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        j = Set(self.m, name="j", records=markets, description="Markets")

        # Equation declaration without an index
        cost = Equation(
            self.m,
            name="cost",
            description="define objective function",
        )
        self.assertEqual(cost.gamsRepr(), "cost")
        self.assertEqual(
            cost.getStatement(),
            'Equation cost "define objective function";',
        )

        # Equation declaration with an index
        supply = Equation(
            self.m,
            name="supply",
            domain=[i],
            description="observe supply limit at plant i",
        )
        self.assertEqual(supply, "supply")
        self.assertEqual(
            supply.getStatement(),
            'Equation supply(i) "observe supply limit at plant i";',
        )

        # Equation declaration with more than one index
        bla = Equation(
            self.m,
            name="bla",
            domain=[i, j],
            description="some text",
        )
        self.assertEqual(bla.gamsRepr(), "bla")
        self.assertEqual(bla.getStatement(), 'Equation bla(i,j) "some text";')

        u = Set(self.m, "u")
        v = Alias(self.m, "v", alias_with=u)
        e = Set(self.m, "e", domain=[u, v])
        eq = Equation(self.m, "eq", domain=[u, v])
        self.assertEqual(eq[e[u, v]].gamsRepr(), "eq(e(u,v))")

    def test_equation_definition(self):
        # Prepare data
        distances = pd.DataFrame(
            [
                ["seattle", "new-york", 2.5],
                ["seattle", "chicago", 1.7],
                ["seattle", "topeka", 1.8],
                ["san-diego", "new-york", 2.5],
                ["san-diego", "chicago", 1.8],
                ["san-diego", "topeka", 1.4],
            ]
        )
        canning_plants = ["seattle", "san-diego"]
        markets = ["new-york", "chicago", "topeka"]
        capacities = pd.DataFrame([["seattle", 350], ["san-diego", 600]])

        # Sets
        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        j = Set(self.m, name="j", records=markets, description="Markets")

        # Params
        a = Parameter(self.m, name="a", domain=[i], records=capacities)
        d = Parameter(self.m, name="d", domain=[i, j], records=distances)
        c = Parameter(self.m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variables
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")
        z = Variable(self.m, name="z")

        # Equation definition without an index
        cost = Equation(
            self.m,
            name="cost",
            description="define objective function",
        )
        cost.definition = Sum((i, j), c[i, j] * x[i, j]) == z
        with self.assertRaises(TypeError):
            cost.records = 5

        self.assertIsNotNone(cost.definition)
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "cost .. sum((i,j),(c(i,j) * x(i,j))) =e= z;",
        )

        # Equation definition with an index
        supply = Equation(
            self.m,
            name="supply",
            domain=[i],
            description="observe supply limit at plant i",
        )
        supply[i] = Sum(j, x[i, j]) <= a[i]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "supply(i) .. sum(j,x(i,j)) =l= a(i);",
        )

        # Equation definition with more than one index
        bla = Equation(
            self.m,
            name="bla",
            domain=[i, j],
            description="observe supply limit at plant i",
        )
        bla[i, j].definition = Sum((i, j), x[i, j]) <= a[i]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "bla(i,j) .. sum((i,j),x(i,j)) =l= a(i);",
        )

        bla[i, "*"] = Sum((i, j), x[i, j]) <= a[i]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "bla(i,*) .. sum((i,j),x(i,j)) =l= a(i);",
        )

        # Equation definition in constructor
        _ = Equation(
            self.m,
            name="cost2",
            description="define objective function",
            definition=Sum((i, j), c[i, j] * x[i, j]) == z,
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "cost2 .. sum((i,j),(c(i,j) * x(i,j))) =e= z;",
        )

        # Equation definition in addEquation
        _ = self.m.addEquation(
            name="cost2",
            description="define objective function",
            definition=Sum((i, j), c[i, j] * x[i, j]) == z,
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "cost2 .. sum((i,j),(c(i,j) * x(i,j))) =e= z;",
        )

        # eq[bla].definition test
        bla2 = Equation(
            self.m,
            name="bla2",
            domain=[i, j],
            description="observe supply limit at plant i",
        )
        bla2[i, j].definition = Sum((i, j), x[i, j]) <= a[i]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "bla2(i,j) .. sum((i,j),x(i,j)) =l= a(i);",
        )

        # eq[bla] with different domain
        _ = Equation(
            self.m,
            name="bla3",
            domain=[i, j],
            description="observe supply limit at plant i",
            definition=Sum((i, j), x[i, j]) <= a[i],
            definition_domain=[i, "bla"],
        )
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            'bla3(i,"bla") .. sum((i,j),x(i,j)) =l= a(i);',
        )

        m = Container(delayed_execution=True)
        g = Set(m, name="g", records=[str(i) for i in range(1, 4)])
        t1 = Set(m, name="t1", records=[str(i) for i in range(1, 4)])
        t2 = Set(m, name="t2", records=[str(i) for i in range(1, 4)])

        eStartNaive = Equation(m, name="eStartNaive", domain=[g, t1])
        pMinDown = Parameter(m, name="pMinDown", domain=[g, t1])
        vStart = Parameter(m, name="vStart", domain=[g, t2])

        eStartNaive[g, t1] = (
            Sum(
                t2.where[
                    (Ord(t1) >= Ord(t2))
                    & (Ord(t2) > Ord(t1) - pMinDown[g, t1])
                ],
                vStart[g, t2],
            )
            <= 1
        )
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].getStatement(),
            "eStartNaive(g,t1) .. sum(t2 $ ((ord(t1) >= ord(t2)) and"
            " (ord(t2) > (ord(t1) - pMinDown(g,t1)))),vStart(g,t2)) =l= 1;",
        )

    def test_equation_attributes(self):
        pi = Equation(self.m, "pi")

        self.assertTrue(
            hasattr(pi, "l") and isinstance(pi.l, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.l.gamsRepr(), "pi.l")

        self.assertTrue(
            hasattr(pi, "m") and isinstance(pi.m, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.m.gamsRepr(), "pi.m")

        self.assertTrue(
            hasattr(pi, "lo")
            and isinstance(pi.lo, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.lo.gamsRepr(), "pi.lo")

        self.assertTrue(
            hasattr(pi, "up")
            and isinstance(pi.up, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.up.gamsRepr(), "pi.up")

        self.assertTrue(
            hasattr(pi, "scale")
            and isinstance(pi.scale, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.scale.gamsRepr(), "pi.scale")

        self.assertTrue(
            hasattr(pi, "stage")
            and isinstance(pi.stage, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.stage.gamsRepr(), "pi.stage")

        self.assertTrue(
            hasattr(pi, "range")
            and isinstance(pi.range, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.range.gamsRepr(), "pi.range")

        self.assertTrue(
            hasattr(pi, "slacklo")
            and isinstance(pi.slacklo, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.slacklo.gamsRepr(), "pi.slacklo")

        self.assertTrue(
            hasattr(pi, "slackup")
            and isinstance(pi.slackup, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.slackup.gamsRepr(), "pi.slackup")

        self.assertTrue(
            hasattr(pi, "slack")
            and isinstance(pi.slack, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.slack.gamsRepr(), "pi.slack")

        self.assertTrue(
            hasattr(pi, "infeas")
            and isinstance(pi.infeas, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.infeas.gamsRepr(), "pi.infeas")

    def test_implicit_equation(self):
        i = Set(self.m, "i", records=[f"i{i}" for i in range(10)])
        x = Variable(self.m, "x", domain=[i])
        a = Equation(self.m, "a", "regular", [i])

        self.assertTrue(
            hasattr(a[i], "l")
            and isinstance(a[i].l, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].l.gamsRepr(), "a(i).l")
        self.assertTrue(
            hasattr(a[i], "m")
            and isinstance(a[i].m, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].m.gamsRepr(), "a(i).m")
        self.assertTrue(
            hasattr(a[i], "lo")
            and isinstance(a[i].lo, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].lo.gamsRepr(), "a(i).lo")
        self.assertTrue(
            hasattr(a[i], "up")
            and isinstance(a[i].up, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].up.gamsRepr(), "a(i).up")
        self.assertTrue(
            hasattr(a[i], "scale")
            and isinstance(a[i].scale, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].scale.gamsRepr(), "a(i).scale")
        self.assertTrue(
            hasattr(a[i], "stage")
            and isinstance(a[i].stage, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].stage.gamsRepr(), "a(i).stage")
        self.assertTrue(
            hasattr(a[i], "range")
            and isinstance(a[i].range, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].range.gamsRepr(), "a(i).range")
        self.assertTrue(
            hasattr(a[i], "slacklo")
            and isinstance(a[i].slacklo, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].slacklo.gamsRepr(), "a(i).slacklo")
        self.assertTrue(
            hasattr(a[i], "slackup")
            and isinstance(a[i].slackup, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].slackup.gamsRepr(), "a(i).slackup")
        self.assertTrue(
            hasattr(a[i], "slack")
            and isinstance(a[i].slack, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].slack.gamsRepr(), "a(i).slack")
        self.assertTrue(
            hasattr(a[i], "infeas")
            and isinstance(a[i].infeas, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].infeas.gamsRepr(), "a(i).infeas")

        m = Container()
        i = Set(m, "i", records=[f"i{i}" for i in range(10)])
        x = Variable(m, "x", domain=[i])
        a = Equation(m, "a", "regular", [i])
        a[i].definition = x[i] == 5

        self.assertFalse(a._is_dirty)

    def test_mcp_equation(self):
        c = Parameter(self.m, name="c", domain=[], records=0.5)
        x = Variable(
            self.m,
            name="x",
            domain=[],
            records={"lower": 1.0, "level": 1.5, "upper": 3.75},
        )
        f = Equation(self.m, name="f", domain=[], type="nonbinding")
        f.definition = x - c

        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "f .. (x - c) =n= 0;",
        )


def equation_suite():
    suite = unittest.TestSuite()
    tests = [
        EquationSuite(name)
        for name in dir(EquationSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(equation_suite())

tests/unit/test_equation.py
GAMS-dev/gamspy
import unittest

import pandas as pd

from gamspy import Container
from gamspy import Parameter
from gamspy import Set
from gamspy import Variable


class MagicsSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_magics(self):
        # Prepare data
        demands = pd.DataFrame(
            [["new-york", 325], ["chicago", 300], ["topeka", 275]]
        )

        # Set
        i = Set(self.m, name="i", records=["seattle", "san-diego"])
        j = Set(self.m, name="j", records=["new-york", "chicago", "topeka"])

        # Parameter
        b = Parameter(self.m, name="b", domain=[j], records=demands)

        # Variable
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")

        # ADD
        # Parameter + Variable, Variable + Parameter,
        # Parameter + builtin, builtin + Parameter
        op1 = b[i] + x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) + x(i))")
        op2 = x[i] + b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) + b(i))")
        op3 = b[i] + 5
        self.assertEqual(op3.gamsRepr(), "(b(i) + 5)")
        op4 = 5 + b[i]
        self.assertEqual(op4.gamsRepr(), "(5 + b(i))")

        # SUB
        # Parameter - Variable, Variable - Parameter,
        # Parameter - builtin, builtin - Parameter
        op1 = b[i] - x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) - x(i))")
        op2 = x[i] - b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) - b(i))")
        op3 = b[i] - 5
        self.assertEqual(op3.gamsRepr(), "(b(i) - 5)")
        op4 = 5 - b[i]
        self.assertEqual(op4.gamsRepr(), "(5 - b(i))")

        # MUL
        # Parameter * Variable, Variable * Parameter,
        # Parameter * builtin, builtin * Parameter
        op1 = b[i] * x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) * x(i))")
        op2 = x[i] * b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) * b(i))")
        op3 = b[i] * -5
        self.assertEqual(op3.gamsRepr(), "(b(i) * (-5))")
        op4 = -5 * b[i]
        self.assertEqual(op4.gamsRepr(), "((-5) * b(i))")

        # DIV
        # Parameter / Variable, Variable / Parameter,
        # Parameter / builtin, builtin / Parameter
        op1 = b[i] / x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) / x(i))")
        op2 = x[i] / b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) / b(i))")
        op3 = b[i] / 5
        self.assertEqual(op3.gamsRepr(), "(b(i) / 5)")
        op4 = 5 / b[i]
        self.assertEqual(op4.gamsRepr(), "(5 / b(i))")

        # POW
        # Parameter ** Variable, Variable ** Parameter
        op1 = b[i] ** x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) ** x(i))")
        op2 = x[i] ** b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) ** b(i))")

        # Set/Parameter/Variable ** 2
        op1 = i**2
        self.assertEqual(op1.gamsRepr(), "(sqr( i ))")
        op2 = b[i] ** 2
        self.assertEqual(op2.gamsRepr(), "(sqr( b(i) ))")
        op3 = x[i] ** 2
        self.assertEqual(op3.gamsRepr(), "(sqr( x(i) ))")

        # AND
        # Parameter and Variable, Variable and Parameter
        op1 = b[i] & x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) and x(i))")
        op2 = x[i] & b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) and b(i))")

        # RAND
        op1 = 5 & b[i]
        self.assertEqual(op1.gamsRepr(), "(5 and b(i))")

        # OR
        # Parameter or Variable, Variable or Parameter
        op1 = b[i] | x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) or x(i))")
        op2 = x[i] | b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) or b(i))")

        # ROR
        op1 = 5 | b[i]
        self.assertEqual(op1.gamsRepr(), "(5 or b(i))")

        # XOR
        # Parameter xor Variable, Variable xor Parameter
        op1 = b[i] ^ x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) xor x(i))")
        op2 = x[i] ^ b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) xor b(i))")

        # RXOR
        op1 = 5 ^ x[i]
        self.assertEqual(op1.gamsRepr(), "(5 xor x(i))")

        # LT
        # Parameter < Variable, Variable < Parameter
        op1 = b[i] < x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) < x(i))")
        op2 = x[i] < b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) < b(i))")

        # LE
        # Parameter <= Variable, Variable <= Parameter
        op1 = b[i] <= x[i]
        self.assertEqual(op1.gamsRepr(), "b(i) =l= x(i)")
        op2 = x[i] <= b[i]
        self.assertEqual(op2.gamsRepr(), "x(i) =l= b(i)")

        # GT
        # Parameter > Variable, Variable > Parameter
        op1 = b[i] > x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) > x(i))")
        op2 = x[i] > b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) > b(i))")

        # GE
        # Parameter >= Variable, Variable >= Parameter
        op1 = b[i] >= x[i]
        self.assertEqual(op1.gamsRepr(), "b(i) =g= x(i)")
        op2 = x[i] >= b[i]
        self.assertEqual(op2.gamsRepr(), "x(i) =g= b(i)")

        # NE
        # Parameter != Variable, Variable != Parameter
        op1 = b[i] != x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) ne x(i))")
        op2 = x[i] != b[i]
        self.assertEqual(op2.gamsRepr(), "(x(i) ne b(i))")

        # E
        # Parameter == Variable, Variable == Parameter
        op1 = b[i] == x[i]
        self.assertEqual(op1.gamsRepr(), "(b(i) = x(i))")
        op2 = x[i] == b[i]
        self.assertEqual(op2.gamsRepr(), "x(i) =e= b(i)")
        op3 = b[i] == b[i]
        self.assertEqual(op3.gamsRepr(), "(b(i) = b(i))")

        # not
        # not Parameter/Variable
        op1 = ~b[i]
        self.assertEqual(op1.gamsRepr(), "( not b(i))")
        op2 = ~x[i]
        self.assertEqual(op2.gamsRepr(), "( not x(i))")


def magics_suite():
    suite = unittest.TestSuite()
    tests = [
        MagicsSuite(name)
        for name in dir(MagicsSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(magics_suite())

tests/unit/test_magics.py
GAMS-dev/gamspy
import math
import unittest

import pandas as pd

import gamspy._algebra.expression as expression
import gamspy.math as gams_math
from gamspy import Container
from gamspy import Equation
from gamspy import Parameter
from gamspy import Set
from gamspy import Variable
from gamspy.exceptions import GamspyException


class MathSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_math(self):
        # Prepare data
        demands = pd.DataFrame(
            [["new-york", 325], ["chicago", 300], ["topeka", 275]]
        )

        # Set
        i = Set(self.m, name="i", records=["seattle", "san-diego"])
        j = Set(self.m, name="j", records=["new-york", "chicago", "topeka"])

        # Parameter
        b = Parameter(self.m, name="b", domain=[j], records=demands)
        s1 = Parameter(self.m, name="s1", records=5)
        s2 = Parameter(self.m, name="s2", records=3)
        s3 = Parameter(self.m, name="s3", records=6)

        # Variable
        v = Variable(self.m, name="v", domain=[i])

        # abs
        op1 = gams_math.abs(-5)
        self.assertEqual(op1, 5)
        op2 = gams_math.abs(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(abs( b(i) ))")

        # ceil
        op1 = gams_math.ceil(7.5)
        self.assertTrue(isinstance(op1, int) and op1 == 8)
        op2 = gams_math.ceil(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(ceil( b(i) ))")

        # centropy
        op2 = gams_math.centropy(v[i], b[i])
        self.assertEqual(op2.gamsRepr(), "(centropy( v(i),b(i),1e-20 ))")
        op2 = gams_math.centropy(v[i], b[i], 1e-15)
        self.assertEqual(op2.gamsRepr(), "(centropy( v(i),b(i),1e-15 ))")
        self.assertRaises(ValueError, gams_math.centropy, v[i], b[i], -1)

        # cvPower
        op1 = gams_math.cv_power(5, 3)
        self.assertEqual(op1, 125)
        op2 = gams_math.cv_power(b[i], 3)
        self.assertEqual(op2.gamsRepr(), "(cvPower( b(i),3 ))")

        # rPower
        op1 = gams_math.rpower(5, 3)
        self.assertEqual(op1, 125)
        op2 = gams_math.rpower(b[i], 3)
        self.assertEqual(op2.gamsRepr(), "(rPower( b(i),3 ))")

        # signPower
        op1 = gams_math.sign_power(5, 3)
        self.assertEqual(op1, 125)
        op2 = gams_math.sign_power(b[i], 3)
        self.assertEqual(op2.gamsRepr(), "(signPower( b(i),3 ))")

        # vcPower
        op1 = gams_math.vc_power(5, 3)
        self.assertEqual(op1, 125)
        op2 = gams_math.vc_power(b[i], 3)
        self.assertEqual(op2.gamsRepr(), "(vcPower( b(i),3 ))")

        # sllog10
        op1 = gams_math.sllog10(5)
        self.assertEqual(op1.gamsRepr(), "(sllog10( 5,1e-150 ))")

        # sqlog10
        op1 = gams_math.sqlog10(5)
        self.assertEqual(op1.gamsRepr(), "(sqlog10( 5,1e-150 ))")

        # sqrt
        op1 = gams_math.sqrt(9)
        self.assertEqual(op1, 3)
        op2 = gams_math.sqrt(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(sqrt( b(i) ))")

        # exp
        op1 = gams_math.exp(3)
        self.assertEqual(op1, math.exp(3))
        op2 = gams_math.exp(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(exp( b(i) ))")

        # power
        op1 = gams_math.power(2, 3)
        self.assertEqual(op1, math.pow(2, 3))
        op2 = gams_math.power(b[i], 3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(power( b(i),3 ))")

        # sqr
        op1 = gams_math.sqr(4)
        self.assertEqual(op1, 4**2)
        op2 = gams_math.sqr(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(power( b(i),2 ))")

        # mod
        op1 = gams_math.mod(5, 2)
        self.assertEqual(op1, 1)
        op2 = gams_math.mod(b[i], 3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(mod(b(i) , 3))")

        # min
        op1 = gams_math.Min(1, 2, 3)
        self.assertEqual(op1, 1)

        op2 = gams_math.Min(s1, s2, s3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(min( s1,s2,s3 ))")

        # max
        op1 = gams_math.Max(1, 2, 3)
        self.assertEqual(op1, 3)

        op2 = gams_math.Max(s1, s2, s3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(max( s1,s2,s3 ))")

        # log
        op1 = gams_math.log(3)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.log(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(log( b(i) ))")

        # logit
        op1 = gams_math.logit(5)
        self.assertEqual(op1.gamsRepr(), "(logit( 5 ))")

        # logBeta
        op1 = gams_math.log_beta(3, 5)
        self.assertEqual(op1.gamsRepr(), "(logBeta( 3,5 ))")

        # logGamma
        op1 = gams_math.log_gamma(3, 5)
        self.assertEqual(op1.gamsRepr(), "(logGamma( 3,5 ))")

        # log2
        op1 = gams_math.log2(8)
        self.assertEqual(op1, 3)
        op2 = gams_math.log2(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(log2( b(i) ))")

        # log10
        op1 = gams_math.log10(100)
        self.assertEqual(op1, 2)
        op2 = gams_math.log10(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(log10( b(i) ))")

        # round
        op1 = gams_math.Round(5.3)
        self.assertEqual(op1, 5)

        op2 = gams_math.Round(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(round( b(i), 0 ))")

        # sin
        op1 = gams_math.sin(8)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.sin(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(sin( b(i) ))")

        # sinh
        op1 = gams_math.sinh(5)
        self.assertAlmostEqual(op1, 74.203, 3)
        op2 = gams_math.sinh(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(sinh( b(i) ))")

        # asin
        op1 = gams_math.asin(0.5)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.asin(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(arcsin( b(i) ))")

        # cos
        op1 = gams_math.cos(8)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.cos(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(cos( b(i) ))")

        # cosh
        op1 = gams_math.cosh(5)
        self.assertAlmostEqual(op1, 74.209, 2)
        op2 = gams_math.cosh(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(cosh( b(i) ))")

        # arccos
        op1 = gams_math.acos(0.5)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.acos(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(arccos( b(i) ))")

        # cos
        op1 = gams_math.cos(8)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.cos(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(cos( b(i) ))")

        # tan
        op1 = gams_math.tan(0.5)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.tan(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(tan( b(i) ))")

        # tanh
        op1 = gams_math.tanh(5)
        self.assertAlmostEqual(op1, 0.999, 2)
        op2 = gams_math.tanh(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(tanh( b(i) ))")

        # arctan
        op1 = gams_math.atan(7.5)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.atan(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(arctan( b(i) ))")

        # arctan2
        op1 = gams_math.atan2(5, 3)
        self.assertTrue(isinstance(op1, float))
        op2 = gams_math.atan2(b[i], 3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(arctan2( b(i), 3 ))")

        # floor
        op1 = gams_math.floor(7.5)
        self.assertTrue(isinstance(op1, int) and op1 == 7)
        op2 = gams_math.floor(b[i])
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(floor( b(i) ))")

        # div
        op1 = gams_math.div(6, 3)
        self.assertTrue(op1 == 2)
        op2 = gams_math.div(b[i], 3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(div( b(i), 3 ))")

        # div0
        op1 = gams_math.div0(6, 0)
        self.assertTrue(op1 == 1e299)
        op1 = gams_math.div0(6, 2)
        self.assertTrue(op1 == 3)
        op2 = gams_math.div0(b[i], 3)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(div0( b(i), 3 ))")

        # factorial
        op1 = gams_math.factorial(5)
        self.assertEqual(op1, 120)
        op2 = gams_math.factorial(b[i])
        self.assertEqual(op2.gamsRepr(), "(fact( b(i) ))")

        # fractional
        op1 = gams_math.fractional(5.3)
        self.assertAlmostEqual(op1, 0.299, 2)
        op2 = gams_math.fractional(b[i])
        self.assertEqual(op2.gamsRepr(), "(frac( b(i) ))")

        # truncate
        op1 = gams_math.truncate(5.3)
        self.assertEqual(op1, 5)
        op2 = gams_math.truncate(b[i])
        self.assertEqual(op2.gamsRepr(), "(trunc( b(i) ))")

        # slexp
        op1 = gams_math.slexp(5)
        self.assertEqual(op1.gamsRepr(), "(slexp( 5,150 ))")

        # sqexp
        op1 = gams_math.sqexp(5)
        self.assertEqual(op1.gamsRepr(), "(sqexp( 5,150 ))")

        # dist
        op1 = gams_math.dist((1, 2), (2, 3))
        self.assertTrue(isinstance(op1, float))
        self.assertRaises(Exception, gams_math.dist, (1, 2), 5)

        op2 = gams_math.dist(b[i], 3)
        self.assertEqual(op2.gamsRepr(), "(eDist( b(i), 3 ))")

        # uniform
        op2 = gams_math.uniform(0, 1)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(uniform( 0,1 ))")

        # uniformInt
        op2 = gams_math.uniformInt(0, 1)
        self.assertTrue(isinstance(op2, expression.Expression))
        self.assertEqual(op2.gamsRepr(), "(uniformInt( 0,1 ))")

        # normal
        op2 = gams_math.normal(mean=0, dev=1)
        self.assertTrue(op2, expression.Expression)
        self.assertEqual(op2.gamsRepr(), "(normal( 0,1 ))")

        # sign
        p = Parameter(self.m, "p", domain=[i])
        op2 = gams_math.sign(p[i])
        self.assertEqual(op2.gamsRepr(), "(sign( p(i) ))")

        # binomial
        op1 = gams_math.binomial(3, 5)
        self.assertTrue(isinstance(op1, expression.Expression))
        self.assertEqual(op1.gamsRepr(), "(binomial( 3,5 ))")
        op2 = gams_math.binomial(b[i], 3)
        self.assertEqual(op2.gamsRepr(), "(binomial( b(i),3 ))")

    def test_math_2(self):
        m = Container(delayed_execution=True)
        i = Set(m, "i", records=["1", "2"])
        a = Parameter(m, "a", domain=[i], records=[("1", 1), ("2", 2)])

        op1 = gams_math.entropy(1)
        self.assertEqual(op1.gamsRepr(), "(entropy( 1 ))")

        op1 = gams_math.beta(1, 2)
        self.assertEqual(op1.gamsRepr(), "(beta( 1,2 ))")

        op1 = gams_math.regularized_beta(1, 2, 3)
        self.assertEqual(op1.gamsRepr(), "(betaReg( 1,2,3 ))")

        op1 = gams_math.gamma(1)
        self.assertEqual(op1.gamsRepr(), "(gamma( 1 ))")

        op1 = gams_math.regularized_gamma(1, 2)
        self.assertEqual(op1.gamsRepr(), "(gammaReg( 1,2 ))")

        op1 = gams_math.lse_max(a[i])
        self.assertEqual(op1.gamsRepr(), "(lseMax( a(i) ))")

        self.assertRaises(GamspyException, gams_math.lse_max)

        op1 = gams_math.lse_max_sc(a[i], a[i])
        self.assertEqual(op1.gamsRepr(), "(lseMaxSc( a(i),a(i) ))")

        self.assertRaises(GamspyException, gams_math.lse_max_sc, 5)

        op1 = gams_math.lse_min(a[i])
        self.assertEqual(op1.gamsRepr(), "(lseMin( a(i) ))")

        self.assertRaises(GamspyException, gams_math.lse_min)

        op1 = gams_math.lse_min_sc(a[i], a[i])
        self.assertEqual(op1.gamsRepr(), "(lseMinSc( a(i),a(i) ))")

        self.assertRaises(GamspyException, gams_math.lse_min_sc, 5)

        op1 = gams_math.ncp_cm(a[i], a[i], 3)
        self.assertEqual(op1.gamsRepr(), "(ncpCM( a(i),a(i),3 ))")

        op1 = gams_math.ncp_f(a[i], a[i])
        self.assertEqual(op1.gamsRepr(), "(ncpF( a(i),a(i),0 ))")

        op1 = gams_math.ncpVUpow(a[i], a[i])
        self.assertEqual(op1.gamsRepr(), "(ncpVUpow( a(i),a(i),0 ))")

        op1 = gams_math.ncpVUsin(a[i], a[i])
        self.assertEqual(op1.gamsRepr(), "(ncpVUsin( a(i),a(i),0 ))")

        op1 = gams_math.poly(a[i], 3, 5)
        self.assertEqual(op1.gamsRepr(), "(poly( a(i),3,5 ))")

        op1 = gams_math.rand_binomial(1, 2)
        self.assertEqual(op1.gamsRepr(), "(randBinomial( 1,2 ))")

        op1 = gams_math.rand_linear(1, 2, 3)
        self.assertEqual(op1.gamsRepr(), "(randLinear( 1,2,3 ))")

        op1 = gams_math.rand_triangle(1, 2, 3)
        self.assertEqual(op1.gamsRepr(), "(randTriangle( 1,2,3 ))")

        op1 = gams_math.slrec(a[i])
        self.assertEqual(op1.gamsRepr(), "(slrec( a(i),1e-10 ))")

        op1 = gams_math.sqrec(a[i])
        self.assertEqual(op1.gamsRepr(), "(sqrec( a(i),1e-10 ))")

        op1 = gams_math.errorf(a[i])
        self.assertEqual(op1.gamsRepr(), "(errorf( a(i) ))")

        # sigmoid
        op1 = gams_math.sigmoid(2.3)
        self.assertEqual(op1.gamsRepr(), "(sigmoid( 2.3 ))")

        op1 = gams_math.sigmoid(a[i])
        self.assertEqual(op1.gamsRepr(), "(sigmoid( a(i) ))")

    def test_logical(self):
        m = Container(delayed_execution=True)

        o = Set(m, "o", records=[f"pos{idx}" for idx in range(1, 11)])
        p = Set(m, "p", records=[f"opt{idx}" for idx in range(1, 6)])
        sumc = Variable(m, "sumc", domain=[o, p])
        op = Variable(m, "op", domain=[o, p])
        defopLS = Equation(m, "defopLS", domain=[o, p])
        defopLS[o, p] = op[o, p] == gams_math.ifthen(sumc[o, p] >= 0.5, 1, 0)
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].gamsRepr(),
            "defopLS(o,p) .. op(o,p) =e= (ifthen(sumc(o,p) >= 0.5, 1, 0)  );",
        )


def math_suite():
    suite = unittest.TestSuite()
    tests = [
        MathSuite(name) for name in dir(MathSuite) if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(math_suite())

tests/unit/test_math.py
GAMS-dev/gamspy
import unittest

import pandas as pd

from gamspy import Container
from gamspy import Equation
from gamspy import Model
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException


class ModelSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_model(self):
        # Prepare data
        distances = pd.DataFrame(
            [
                ["seattle", "new-york", 2.5],
                ["seattle", "chicago", 1.7],
                ["seattle", "topeka", 1.8],
                ["san-diego", "new-york", 2.5],
                ["san-diego", "chicago", 1.8],
                ["san-diego", "topeka", 1.4],
            ]
        )
        canning_plants = ["seattle", "san-diego"]
        markets = ["new-york", "chicago", "topeka"]
        capacities = pd.DataFrame([["seattle", 350], ["san-diego", 600]])
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Sets
        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        j = Set(self.m, name="j", records=markets, description="Markets")

        # Params
        a = Parameter(self.m, name="a", domain=[i], records=capacities)
        b = Parameter(self.m, name="b", domain=[j], records=demands)
        d = Parameter(self.m, name="d", domain=[i, j], records=distances)
        c = Parameter(self.m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variables
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")
        z = Variable(self.m, name="z")

        # Equation definition without an index
        cost = Equation(
            self.m,
            name="cost",
            description="define objective function",
        )
        cost.expr = Sum((i, j), c[i, j] * x[i, j]) == z

        # Equation definition with an index
        supply = Equation(
            self.m,
            name="supply",
            domain=[i],
            description="observe supply limit at plant i",
        )
        supply[i] = Sum(j, x[i, j]) <= a[i]

        demand = Equation(self.m, name="demand", domain=[j])
        demand[j] = Sum(i, x[i, j]) >= b[j]

        # Model with implicit objective
        test_model = Model(
            self.m,
            name="test_model",
            equations=[supply, demand],
            problem="LP",
            sense="min",
            objective=Sum((i, j), c[i, j] * x[i, j]),
        )
        test_model.solve()
        self.assertEqual(
            list(self.m.data.keys()),
            [
                "i",
                "j",
                "a",
                "b",
                "d",
                "c",
                "x",
                "z",
                "cost",
                "supply",
                "demand",
            ],
        )
        self.assertEqual(test_model.objective_value, 153.675)

        # Check if the name is reserved
        self.assertRaises(GamspyException, Model, self.m, "set", [], "LP")

        # Equation definition with more than one index
        bla = Equation(
            self.m,
            name="bla",
            domain=[i, j],
            description="observe supply limit at plant i",
        )
        bla[i, j] = Sum((i, j), x[i, j]) <= a[i]

        # Test model with specific equations
        test_model2 = Model(
            self.m,
            name="test_model2",
            equations=[cost, supply],
            problem="LP",
            sense="min",
            objective=z,
        )
        self.assertEqual(
            test_model2.getStatement(), "Model test_model2 / cost,supply /;"
        )
        self.assertEqual(test_model2.equations, [cost, supply])

        test_model3 = Model(
            self.m,
            name="test_model3",
            equations=[cost],
            problem="LP",
            sense="min",
            objective=z,
        )
        test_model3.equations = [cost, supply]
        self.assertEqual(test_model3.equations, [cost, supply])

        test_model4 = self.m.addModel(
            name="test_model4",
            equations=[cost, supply],
            problem="LP",
            sense="min",
            objective=z,
        )

        self.assertTrue(test_model4.equations == test_model3.equations)

        test_model5 = self.m.addModel(
            name="test_model5",
            equations=[cost, supply],
            problem="LP",
            sense="min",
            objective=z,
            matches={supply: x, cost: z},
        )
        self.assertEqual(
            test_model5.getStatement(),
            "Model test_model5 / supply.x,cost.z /;",
        )

        # Equations provided as strings
        self.assertRaises(
            TypeError, Model, self.m, "test_model5", ["cost", "supply"], "LP"
        )

        # Test matches
        test_model6 = Model(
            self.m,
            name="test_model6",
            equations=[supply],
            matches={demand: x},
            problem="LP",
            sense="min",
        )
        self.assertEqual(
            test_model6.getStatement(),
            "Model test_model6 / supply,demand.x /;",
        )

    def test_feasibility(self):
        m = Container(delayed_execution=True)

        # Prepare data
        distances = [
            ["seattle", "new-york", 2.5],
            ["seattle", "chicago", 1.7],
            ["seattle", "topeka", 1.8],
            ["san-diego", "new-york", 2.5],
            ["san-diego", "chicago", 1.8],
            ["san-diego", "topeka", 1.4],
        ]

        capacities = [["seattle", 350], ["san-diego", 600]]
        demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]

        # Set
        i = Set(m, name="i", records=["seattle", "san-diego"])
        j = Set(m, name="j", records=["new-york", "chicago", "topeka"])

        # Data
        a = Parameter(m, name="a", domain=[i], records=capacities)
        b = Parameter(m, name="b", domain=[j], records=demands)
        d = Parameter(m, name="d", domain=[i, j], records=distances)
        c = Parameter(m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variable
        x = Variable(m, name="x", domain=[i, j], type="Positive")

        # Equation
        supply = Equation(m, name="supply", domain=[i])
        demand = Equation(m, name="demand", domain=[j])

        supply[i] = Sum(j, x[i, j]) <= a[i]
        demand[j] = Sum(i, x[i, j]) >= b[j]

        transport = Model(
            m,
            name="transport",
            equations=m.getEquations(),
            problem="LP",
            sense="feasibility",
        )
        transport.solve()
        self.assertIsNotNone(x.records)

        self.assertRaises(
            GamspyException,
            Model,
            m,
            "transport2",
            m.getEquations(),
            "LP",
            "feasibility",
            Sum((i, j), c[i, j] * x[i, j]),
        )

        self.assertRaises(
            GamspyException,
            Model,
            m,
            "transport2",
            m.getEquations(),
            "CNS",
            "feasibility",
        )


def model_suite():
    suite = unittest.TestSuite()
    tests = [
        ModelSuite(name)
        for name in dir(ModelSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(model_suite())

tests/unit/test_model.py
GAMS-dev/gamspy
import unittest

import pandas as pd

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Equation
from gamspy import Ord
from gamspy import Parameter
from gamspy import Product
from gamspy import Set
from gamspy import Smax
from gamspy import Smin
from gamspy import Sum
from gamspy import Variable


class OperationSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_operations(self):
        # Prepare data
        distances = pd.DataFrame(
            [
                ["seattle", "new-york", 2.5],
                ["seattle", "chicago", 1.7],
                ["seattle", "topeka", 1.8],
                ["san-diego", "new-york", 2.5],
                ["san-diego", "chicago", 1.8],
                ["san-diego", "topeka", 1.4],
            ]
        )
        canning_plants = ["seattle", "san-diego"]
        markets = ["new-york", "chicago", "topeka"]
        capacities = pd.DataFrame([["seattle", 350], ["san-diego", 600]])

        # Sets
        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        j = Set(self.m, name="j", records=markets, description="Markets")

        # Params
        a = Parameter(self.m, name="a", domain=[i], records=capacities)
        d = Parameter(self.m, name="d", domain=[i, j], records=distances)
        c = Parameter(self.m, name="c", domain=[i, j])
        c[i, j] = 90 * d[i, j] / 1000

        # Variables
        x = Variable(self.m, name="x", domain=[i, j], type="Positive")
        z = Variable(self.m, name="z")

        # SUM
        # Operation with one index
        sum_op = Sum(j, x[i, j]) <= a[i]
        self.assertEqual(sum_op.gamsRepr(), "sum(j,x(i,j)) =l= a(i)")

        expression = Sum(a[i], True)
        self.assertEqual(expression.gamsRepr(), "sum(a(i),yes)")

        # Operation with two indices
        sum_op = Sum((i, j), c[i, j] * x[i, j]) == z
        self.assertEqual(
            sum_op.gamsRepr(), "sum((i,j),(c(i,j) * x(i,j))) =e= z"
        )

        # PROD
        # Operation with one index
        sum_op = Product(j, x[i, j]) <= a[i]
        self.assertEqual(sum_op.gamsRepr(), "prod(j,x(i,j)) =l= a(i)")

        # Operation with two indices
        sum_op = Product((i, j), c[i, j] * x[i, j]) == z
        self.assertEqual(
            sum_op.gamsRepr(), "prod((i,j),(c(i,j) * x(i,j))) =e= z"
        )

        # Smin
        # Operation with one index
        sum_op = Smin(j, x[i, j]) <= a[i]
        self.assertEqual(sum_op.gamsRepr(), "smin(j,x(i,j)) =l= a(i)")

        # Operation with two indices
        sum_op = Smin((i, j), c[i, j] * x[i, j]) == z
        self.assertEqual(
            sum_op.gamsRepr(), "smin((i,j),(c(i,j) * x(i,j))) =e= z"
        )

        # Smax
        # Operation with one index
        sum_op = Smax(j, x[i, j]) <= a[i]
        self.assertEqual(sum_op.gamsRepr(), "smax(j,x(i,j)) =l= a(i)")

        # Operation with two indices
        sum_op = Smax((i, j), c[i, j] * x[i, j]) == z
        self.assertEqual(
            sum_op.gamsRepr(), "smax((i,j),(c(i,j) * x(i,j))) =e= z"
        )

        # Ord, Card
        expression = Ord(i) == Ord(j)
        self.assertEqual(expression.gamsRepr(), "(ord(i) = ord(j))")
        expression = Card(i) == 5
        self.assertEqual(expression.gamsRepr(), "(card(i) = 5)")

    def test_operation_indices(self):
        # Test operation index
        m = Container(delayed_execution=True)
        mt = 2016
        mg = 17
        maxdt = 40
        t = Set(
            m,
            name="t",
            records=[f"t{i}" for i in range(1, mt + 1)],
            description="hours",
        )
        g = Set(
            m,
            name="g",
            records=[f"g{i}" for i in range(1, mg + 1)],
            description="generators",
        )
        t1 = Alias(m, name="t1", alias_with=t)
        tt = Set(
            m,
            name="tt",
            domain=[t],
            records=[f"t{i}" for i in range(1, maxdt + 1)],
            description="max downtime hours",
        )
        pMinDown = Parameter(
            m, name="pMinDown", domain=[g, t], description="minimum downtime"
        )
        vStart = Variable(m, name="vStart", type="binary", domain=[g, t])
        eStartFast = Equation(m, name="eStartFast", domain=[g, t])
        eStartFast[g, t1] = (
            Sum(
                tt[t].where[Ord(t) <= pMinDown[g, t1]],
                vStart[g, t.lead(Ord(t1) - pMinDown[g, t1])],
            )
            <= 1
        )
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].gamsRepr(),
            "eStartFast(g,t1) .. sum(tt(t) $ (ord(t) <="
            " pMinDown(g,t1)),vStart(g,t + (ord(t1) - pMinDown(g,t1))))"
            " =l= 1;",
        )


def operation_suite():
    suite = unittest.TestSuite()
    tests = [
        OperationSuite(name)
        for name in dir(OperationSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(operation_suite())

tests/unit/test_operation.py
GAMS-dev/gamspy
import os
import unittest

import numpy as np
import pandas as pd

from gamspy import Alias
from gamspy import Container
from gamspy import Ord
from gamspy import Parameter
from gamspy import Set
from gamspy import Sum
from gamspy import Variable
from gamspy.exceptions import GamspyException


class ParameterSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_parameter_string(self):
        canning_plants = pd.DataFrame(["seattle", "san-diego", "topeka"])

        # Check if the name is reserved
        self.assertRaises(GamspyException, Parameter, self.m, "set")

        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        a = Parameter(
            self.m,
            name="a",
            domain=[i],
            records=pd.DataFrame(
                [["seattle", 350], ["san-diego", 600], ["topeka", 500]]
            ),
            description="distances",
        )

        self.assertEqual(
            a.getStatement(),
            'Parameter a(i) "distances";',
        )

        b = Parameter(self.m, "b")
        self.assertEqual(b.getStatement(), "Parameter b;")
        self.assertEqual((b == 5).gamsRepr(), "(b = 5)")
        self.assertEqual((-b).name, "-b")

    def test_implicit_parameter_string(self):
        canning_plants = pd.DataFrame(["seattle", "san-diego", "topeka"])

        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        a = Parameter(
            self.m,
            name="a",
            domain=[i],
            records=pd.DataFrame(
                [["seattle", 350], ["san-diego", 600], ["topeka", 500]]
            ),
        )

        self.assertEqual(a[i].gamsRepr(), "a(i)")
        a[i].assignment = a[i] * 5
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "a(i) = (a(i) * 5);",
        )

        a[i] = -a[i] * 5
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "a(i) = (-a(i) * 5);",
        )

    def test_implicit_parameter_assignment(self):
        canning_plants = pd.DataFrame(["seattle", "san-diego", "topeka"])

        i = Set(
            self.m,
            name="i",
            records=canning_plants,
            description="Canning Plants",
        )
        a = Parameter(
            self.m,
            name="a",
            domain=[i],
            records=pd.DataFrame(
                [["seattle", 350], ["san-diego", 600], ["topeka", 500]]
            ),
        )

        b = Parameter(
            self.m,
            name="b",
            domain=[i],
            records=pd.DataFrame(
                [["seattle", 350], ["san-diego", 600], ["topeka", 500]]
            ),
        )

        a[i] = Sum(i, b[i])
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "a(i) = sum(i,b(i));",
        )

        v = Variable(self.m, "v", domain=[i])
        v.l[i] = v.l[i] * 5
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].getStatement(),
            "v.l(i) = (v.l(i) * 5);",
        )

    def test_equality(self):
        j = Set(self.m, "j")
        h = Set(self.m, "h")
        hp = Alias(self.m, "hp", h)
        lamb = Parameter(self.m, "lambda", domain=[j, h])
        gamma = Parameter(self.m, "gamma", domain=[j, h])
        gamma[j, h] = Sum(hp.where[Ord(hp) >= Ord(h)], lamb[j, hp])
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "gamma(j,h) = sum(hp $ (ord(hp) >= ord(h)),lambda(j,hp));",
        )

    def test_override(self):
        # Parameter record override
        s = Set(self.m, name="s", records=[str(i) for i in range(1, 4)])
        c = Parameter(self.m, name="c", domain=[s])
        c = self.m.addParameter(
            name="c",
            domain=[s],
            records=[("1", 1), ("2", 2), ("3", 3)],
            description="new description",
        )
        self.assertEqual(c.description, "new description")

        # Try to add the same parameter
        self.assertRaises(ValueError, self.m.addParameter, "c", [s, s])

    def test_undef(self):
        m = Container(delayed_execution=True)
        _ = Parameter(
            m, name="rho", records=[np.nan]
        )  # Instead of using numpy there might be a NA from the math package

        self.assertEqual(
            m.generateGamsString(),
            f"$gdxIn {m.workspace.working_directory}{os.sep}default.gdx\n"
            "$onUNDF\nParameter rho;\n$load"
            " rho\n$offUNDF\n"
            "$gdxIn\n",
        )


def parameter_suite():
    suite = unittest.TestSuite()
    tests = [
        ParameterSuite(name)
        for name in dir(ParameterSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(parameter_suite())

tests/unit/test_parameter.py
GAMS-dev/gamspy
import unittest

from gamspy import Alias
from gamspy import Card
from gamspy import Container
from gamspy import Ord
from gamspy import Set
from gamspy import UniverseAlias
from gamspy.exceptions import GamspyException


class SetSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_set_string(self):
        # Check if the name is reserved
        self.assertRaises(GamspyException, Set, self.m, "set")

        # Without records
        b = Set(self.m, "b")
        self.assertEqual(b.gamsRepr(), "b")
        self.assertEqual(b.getStatement(), "Set b(*);")

        # Without domain
        i = Set(
            self.m,
            "i",
            records=["seattle", "san-diego"],
            description="dummy set",
        )
        self.assertEqual(i.gamsRepr(), "i")
        self.assertEqual(
            i.getStatement(),
            'Set i(*) "dummy set";',
        )

        # With one domain
        j = Set(self.m, "j", records=["seattle", "san-diego", "california"])
        k = Set(self.m, "k", domain=[j], records=["seattle", "san-diego"])
        self.assertEqual(k.gamsRepr(), "k")
        self.assertEqual(k.getStatement(), "Set k(j);")

        # With two domain
        m = Set(self.m, "m", records=[f"i{i}" for i in range(2)])
        n = Set(self.m, "n", records=[f"j{i}" for i in range(2)])
        a = Set(self.m, "a", [m, n])
        a.generateRecords(density=1)
        self.assertEqual(a.gamsRepr(), "a")
        self.assertEqual(a.getStatement(), "Set a(m,n);")

        s = Set(self.m, "s", is_singleton=True)
        self.assertEqual(
            s.getStatement(),
            "Singleton Set s(*);",
        )

        self.assertRaises(
            GamspyException, Set, self.m, "s2", None, True, ["i1", "i2"]
        )

    def test_records_assignment(self):
        s = Set(self.m, "s")
        with self.assertRaises(TypeError):
            s.records = 5

    def test_set_operators(self):
        i = Set(self.m, "i", records=["seattle", "san-diego"])
        card = Card(i)
        self.assertEqual(card.gamsRepr(), "card(i)")

        ord = Ord(i)
        self.assertEqual(ord.gamsRepr(), "ord(i)")

    def test_implicit_sets(self):
        j = Set(self.m, "j", records=["seattle", "san-diego", "california"])
        k = Set(self.m, "k", domain=[j], records=["seattle", "san-diego"])

        expr = k[j] <= k[j]
        self.assertEqual(expr.gamsRepr(), "(k(j) <= k(j))")
        expr = k[j] >= k[j]
        self.assertEqual(expr.gamsRepr(), "(k(j) >= k(j))")

        k[j] = ~k[j]
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "k(j) = ( not k(j));",
        )

    def test_set_operations(self):
        i = Set(self.m, "i", records=["seattle", "san-diego"])
        k = Set(self.m, "k", records=["seattle", "san-diego"])
        union = i + k
        self.assertEqual(union.gamsRepr(), "i + k")

        intersection = i * k
        self.assertEqual(intersection.gamsRepr(), "i * k")

        complement = ~i
        self.assertEqual(complement.gamsRepr(), "( not i)")

        difference = i - k
        self.assertEqual(difference.gamsRepr(), "i - k")

    def test_dynamic_sets(self):
        m = Container(delayed_execution=True)
        i = Set(m, name="i", records=[f"i{idx}" for idx in range(1, 4)])
        i["i1"] = False

        self.assertEqual(
            list(m._unsaved_statements.values())[-1].getStatement(),
            'i("i1") = no;',
        )

        m = Container()
        k = Set(m, name="k", records=[f"k{idx}" for idx in range(1, 4)])
        k["k1"] = False
        self.assertEqual(k._is_dirty, False)

    def test_lag_and_lead(self):
        set = Set(
            self.m, name="S", records=["a", "b", "c"], description="Test text"
        )
        alias = Alias(self.m, "A", alias_with=set)

        # Circular lag
        new_set = set.lag(n=5, type="circular")
        self.assertEqual(new_set.name, "S -- 5")
        new_set = alias.lag(n=5, type="circular")
        self.assertEqual(new_set.name, "A -- 5")

        # Circular lead
        new_set = set.lead(n=5, type="circular")
        self.assertEqual(new_set.name, "S ++ 5")
        new_set = alias.lead(n=5, type="circular")
        self.assertEqual(new_set.name, "A ++ 5")

        # Linear lag
        new_set = set.lag(n=5, type="linear")
        self.assertEqual(new_set.name, "S - 5")
        new_set = alias.lag(n=5, type="linear")
        self.assertEqual(new_set.name, "A - 5")

        # Linear lead
        new_set = set.lead(n=5, type="linear")
        self.assertEqual(new_set.name, "S + 5")
        new_set = alias.lead(n=5, type="linear")
        self.assertEqual(new_set.name, "A + 5")

        # Incorrect type
        self.assertRaises(ValueError, set.lead, 5, "bla")
        self.assertRaises(ValueError, alias.lead, 5, "bla")
        self.assertRaises(ValueError, set.lag, 5, "bla")
        self.assertRaises(ValueError, alias.lag, 5, "bla")

        m = Container(delayed_execution=True)
        s = Set(m, name="s", records=[f"s{i}" for i in range(1, 4)])
        t = Set(m, name="t", records=[f"t{i}" for i in range(1, 6)])

        sMinDown = Set(m, name="sMinDown", domain=[s, t])
        sMinDown[s, t.lead((Ord(t) - Ord(s)))] = 1
        self.assertEqual(
            list(m._unsaved_statements.values())[-1].gamsRepr(),
            "sMinDown(s,t + (ord(t) - ord(s))) = 1;",
        )

    def test_set_attributes(self):
        i = Set(self.m, "i")
        self.assertEqual(i.pos.gamsRepr(), "i.pos")
        self.assertEqual(i.ord.gamsRepr(), "i.ord")
        self.assertEqual(i.off.gamsRepr(), "i.off")
        self.assertEqual(i.rev.gamsRepr(), "i.rev")
        self.assertEqual(i.uel.gamsRepr(), "i.uel")
        self.assertEqual(i.len.gamsRepr(), "i.len")
        self.assertEqual(i.tlen.gamsRepr(), "i.tlen")
        self.assertEqual(i.val.gamsRepr(), "i.val")
        self.assertEqual(i.tval.gamsRepr(), "i.tval")
        self.assertEqual(i.first.gamsRepr(), "i.first")
        self.assertEqual(i.last.gamsRepr(), "i.last")

    def test_iterable(self):
        # Set with no records
        i = Set(self.m, "i")
        count = 0
        for _ in i:
            count += 1

        self.assertEqual(count, 0)

        # Set with records
        k = Set(self.m, "k", records=[str(idx) for idx in range(1, 3)])
        count = 0
        for _ in k:
            count += 1

        self.assertEqual(count, 2)

        # Alias with no records
        x = Set(self.m, "x")
        a = Alias(self.m, "a", x)
        count = 0
        for _ in a:
            count += 1

        self.assertEqual(count, 0)

        # Alias with records
        b = Set(self.m, "b", records=[str(idx) for idx in range(1, 3)])
        c = Alias(self.m, "c", b)

        count = 0
        for _ in c:
            count += 1

        self.assertEqual(count, 2)

        # UniverseAlias with no records
        m = Container(delayed_execution=True)
        x = Set(m, "set1")
        a = UniverseAlias(m, "universe1")
        count = 0
        for _ in a:
            count += 1

        self.assertEqual(count, 0)

        # UniverseAlias with records
        b = Set(m, "set2", records=[str(idx) for idx in range(1, 3)])
        c = UniverseAlias(m, "universe2")

        count = 0
        for _ in c:
            count += 1

        self.assertEqual(count, 2)

    def test_sameas(self):
        i = Set(self.m, "i")
        j = Alias(self.m, "j", i)
        self.assertEqual(i.sameAs(j).gamsRepr(), "(sameAs( i,j ))")
        self.assertEqual(j.sameAs(i).gamsRepr(), "(sameAs( j,i ))")


def set_suite():
    suite = unittest.TestSuite()
    tests = [
        SetSuite(name) for name in dir(SetSuite) if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(set_suite())

tests/unit/test_set.py
GAMS-dev/gamspy
import unittest

import gamspy.utils as utils
from gamspy import Container
from gamspy import Set
from gamspy._algebra.domain import DomainException
from gamspy.exceptions import GamspyException


class UtilsSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_utils(self):
        string = "(bla))"
        self.assertRaises(
            AssertionError, utils._getMatchingParanthesisIndices, string
        )

        string2 = "((bla)"
        self.assertRaises(
            AssertionError, utils._getMatchingParanthesisIndices, string2
        )

        i = Set(self.m, "i", records=["i1", "i2"])
        self.assertEqual(utils._getDomainStr([i, "b", "*"]), '(i,"b",*)')
        self.assertRaises(DomainException, utils._getDomainStr, [5])

        # invalid system directory
        self.assertRaises(GamspyException, utils._openGdxFile, "bla", "bla")

        self.assertFalse(utils.checkAllSame([1, 2], [2]))
        self.assertFalse(utils.checkAllSame([1, 2], [2, 3]))
        self.assertTrue(utils.checkAllSame([1, 2], [1, 2]))

        # invalid load from path
        self.assertRaises(
            Exception, utils._openGdxFile, self.m.system_directory, "bla.gdx"
        )

    def test_isin(self):
        i = Set(self.m, "i")
        j = Set(self.m, "j")
        k = Set(self.m, "k")
        symbols = [i, j]

        self.assertTrue(utils.isin(i, symbols))
        self.assertFalse(utils.isin(k, symbols))

    def test_available_solvers(self):
        solvers = utils.getAvailableSolvers()

        self.assertEqual(
            solvers,
            [
                "CONOPT",
                "CONVERT",
                "CPLEX",
                "NLPEC",
                "PATH",
                "SBB",
                "SCENSOLVER",
            ],
        )


def utils_suite():
    suite = unittest.TestSuite()
    tests = [
        UtilsSuite(name)
        for name in dir(UtilsSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(utils_suite())

tests/unit/test_utils.py
GAMS-dev/gamspy
import unittest

import pandas as pd

import gamspy._symbols.implicits as implicits
from gamspy import Container
from gamspy import Set
from gamspy import Variable
from gamspy import VariableType
from gamspy.exceptions import GamspyException


class VariableSuite(unittest.TestCase):
    def setUp(self):
        self.m = Container(delayed_execution=True)

    def test_variable_string(self):
        # Check if the name is reserved
        self.assertRaises(GamspyException, Variable, self.m, "set")

        # Set
        i = Set(self.m, name="i", records=["bla", "damn"])
        j = Set(self.m, name="j", records=["test", "test2"])

        # Variable without data
        v4 = Variable(self.m, "v4")
        self.assertEqual(v4.gamsRepr(), "v4")
        self.assertEqual(
            v4.getStatement(),
            "free Variable v4;",
        )

        with self.assertRaises(TypeError):
            v4.records = 5

        # Variable without domain
        v0 = Variable(self.m, name="v0", description="some text")
        self.assertEqual(v0.gamsRepr(), "v0")
        self.assertEqual(
            v0.getStatement(),
            'free Variable v0 "some text";',
        )

        expression = -v0
        self.assertEqual(expression.name, "-v0")

        # Variable one domain
        v1 = Variable(self.m, name="v1", domain=[i])
        self.assertEqual(v1.gamsRepr(), "v1")
        self.assertEqual(
            v1.getStatement(),
            "free Variable v1(i);",
        )

        self.assertEqual((v1[i] == v1[i]).gamsRepr(), "v1(i) =e= v1(i)")

        # Variable two domain
        v2 = Variable(self.m, name="v2", domain=[i, j])
        self.assertEqual(v2.gamsRepr(), "v2")
        self.assertEqual(
            v2.getStatement(),
            "free Variable v2(i,j);",
        )

        # Scalar variable with records
        pi = Variable(
            self.m,
            "pi",
            records=pd.DataFrame(data=[3.14159], columns=["level"]),
        )
        self.assertEqual(
            pi.getStatement(),
            "free Variable pi;",
        )
        new_pi = -pi
        self.assertEqual(new_pi.gamsRepr(), "-pi")

        # 1D variable with records
        v = Variable(
            self.m,
            "v",
            "free",
            domain=["*"],
            records=pd.DataFrame(
                data=[("i" + str(i), i) for i in range(5)],
                columns=["domain", "marginal"],
            ),
        )
        self.assertEqual(
            v.getStatement(),
            "free Variable v(*);",
        )

        v3 = Variable(
            self.m,
            "v3",
            "positive",
            ["*", "*"],
            records=pd.DataFrame(
                [("seattle", "san-diego"), ("chicago", "madison")]
            ),
        )
        self.assertEqual(
            v3.getStatement(),
            "positive Variable v3(*,*);",
        )

    def test_variable_types(self):
        i = Set(self.m, "i", records=["1", "2"])

        v = Variable(self.m, name="v", type="Positive")
        self.assertEqual(
            v.getStatement(),
            "positive Variable v;",
        )

        v1 = Variable(self.m, name="v1", type="Negative")
        self.assertEqual(
            v1.getStatement(),
            "negative Variable v1;",
        )

        v2 = Variable(self.m, name="v2", type="Binary")
        self.assertEqual(
            v2.getStatement(),
            "binary Variable v2;",
        )

        v3 = Variable(self.m, name="v3", domain=[i], type="Integer")
        self.assertEqual(
            v3.getStatement(),
            "integer Variable v3(i);",
        )

        self.assertEqual(str(VariableType.FREE), "free")
        self.assertEqual(
            VariableType.values(),
            [
                "binary",
                "integer",
                "positive",
                "negative",
                "free",
                "sos1",
                "sos2",
                "semicont",
                "semiint",
            ],
        )

        v4 = Variable(
            self.m, name="v4", domain=[i], type=VariableType.POSITIVE
        )
        self.assertEqual(v4.type, "positive")

    def test_variable_attributes(self):
        pi = Variable(
            self.m,
            "pi",
            records=pd.DataFrame(data=[3.14159], columns=["level"]),
        )

        self.assertTrue(
            hasattr(pi, "l") and isinstance(pi.l, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.l.gamsRepr(), "pi.l")

        self.assertTrue(
            hasattr(pi, "m") and isinstance(pi.m, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.m.gamsRepr(), "pi.m")

        self.assertTrue(
            hasattr(pi, "lo")
            and isinstance(pi.lo, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.lo.gamsRepr(), "pi.lo")

        self.assertTrue(
            hasattr(pi, "up")
            and isinstance(pi.up, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.up.gamsRepr(), "pi.up")

        self.assertTrue(
            hasattr(pi, "scale")
            and isinstance(pi.scale, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.scale.gamsRepr(), "pi.scale")

        self.assertTrue(
            hasattr(pi, "fx")
            and isinstance(pi.fx, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.fx.gamsRepr(), "pi.fx")

        self.assertTrue(
            hasattr(pi, "prior")
            and isinstance(pi.prior, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.prior.gamsRepr(), "pi.prior")

        self.assertTrue(
            hasattr(pi, "stage")
            and isinstance(pi.stage, implicits.ImplicitParameter)
        )
        self.assertEqual(pi.stage.gamsRepr(), "pi.stage")

        i = Set(self.m, name="i", records=["bla", "damn"])
        test = Variable(self.m, "test", domain=[i])
        self.assertTrue(
            hasattr(test, "l")
            and isinstance(test.l, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "m")
            and isinstance(test.m, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "lo")
            and isinstance(test.lo, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "up")
            and isinstance(test.up, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "scale")
            and isinstance(test.scale, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "fx")
            and isinstance(test.fx, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "prior")
            and isinstance(test.prior, implicits.ImplicitParameter)
        )
        self.assertTrue(
            hasattr(test, "stage")
            and isinstance(test.stage, implicits.ImplicitParameter)
        )

        k = Set(self.m, "k")
        x = Variable(self.m, "x", domain=[k])
        x.l[k] = 5
        self.assertEqual(
            list(self.m._unsaved_statements.values())[-1].gamsRepr(),
            "x.l(k) = 5;",
        )
        self.assertTrue(x._is_dirty)

    def test_implicit_variable(self):
        i = Set(self.m, "i", records=[f"i{i}" for i in range(10)])
        a = Variable(self.m, "a", "free", [i])
        a.generateRecords()
        self.assertTrue(a.isValid())

        expression = -a[i] * 5
        self.assertEqual(expression.gamsRepr(), "(-a(i) * 5)")

        a.l[i].assignment = 5
        self.assertTrue(a._is_dirty)

        # load the records of a
        self.assertEqual(
            a.records["level"].to_list(),
            [5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
        )

        self.assertTrue(
            hasattr(a[i], "l")
            and isinstance(a[i].l, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].l.gamsRepr(), "a(i).l")
        self.assertTrue(
            hasattr(a[i], "m")
            and isinstance(a[i].m, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].m.gamsRepr(), "a(i).m")
        self.assertTrue(
            hasattr(a[i], "lo")
            and isinstance(a[i].lo, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].lo.gamsRepr(), "a(i).lo")
        self.assertTrue(
            hasattr(a[i], "up")
            and isinstance(a[i].up, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].up.gamsRepr(), "a(i).up")
        self.assertTrue(
            hasattr(a[i], "scale")
            and isinstance(a[i].scale, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].scale.gamsRepr(), "a(i).scale")
        self.assertTrue(
            hasattr(a[i], "fx")
            and isinstance(a[i].fx, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].fx.gamsRepr(), "a(i).fx")
        self.assertTrue(
            hasattr(a[i], "prior")
            and isinstance(a[i].prior, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].prior.gamsRepr(), "a(i).prior")
        self.assertTrue(
            hasattr(a[i], "stage")
            and isinstance(a[i].stage, implicits.ImplicitParameter)
        )
        self.assertEqual(a[i].stage.gamsRepr(), "a(i).stage")


def variable_suite():
    suite = unittest.TestSuite()
    tests = [
        VariableSuite(name)
        for name in dir(VariableSuite)
        if name.startswith("test_")
    ]
    suite.addTests(tests)

    return suite


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(variable_suite())

tests/unit/test_variable.py
GAMS-dev/gamspy