gamspy 1.18.1__tar.gz → 1.18.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gamspy-1.18.1 → gamspy-1.18.3}/PKG-INFO +3 -3
- {gamspy-1.18.1 → gamspy-1.18.3}/pyproject.toml +3 -3
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/domain.py +1 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/expression.py +1 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/operation.py +10 -3
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/cli.py +2 -1
- gamspy-1.18.3/src/gamspy/_cli/mps2gms.py +128 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_container.py +44 -2
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_convert.py +97 -66
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_extrinsic.py +17 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_model.py +44 -9
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/alias.py +2 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/equation.py +15 -7
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/implicits/implicit_set.py +2 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/implicits/implicit_symbol.py +6 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/parameter.py +2 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/set.py +2 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/symbol.py +1 -2
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/universe_alias.py +26 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/variable.py +2 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_validation.py +10 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/misc.py +19 -6
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/utils.py +22 -11
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy.egg-info/PKG-INFO +3 -3
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy.egg-info/SOURCES.txt +1 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy.egg-info/requires.txt +2 -2
- {gamspy-1.18.1 → gamspy-1.18.3}/tests/test_gamspy.py +1 -1
- {gamspy-1.18.1 → gamspy-1.18.3}/LICENSE +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/README.md +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/README_PYPI.md +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/setup.cfg +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/__main__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/condition.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/number.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_algebra/operable.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_backend/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_backend/backend.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_backend/engine.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_backend/local.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_backend/neos.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/gdx.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/install.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/list.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/probe.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/retrieve.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/run.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/show.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/uninstall.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_cli/util.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_communication.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_config.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_database.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_miro.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_model_instance.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_options.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_serialization.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/implicits/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/implicits/implicit_equation.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/implicits/implicit_parameter.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_symbols/implicits/implicit_variable.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_types.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/_workspace.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/exceptions.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/ml/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/ml/decision_tree_struct.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/ml/gradient_boosting.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/ml/random_forest.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/ml/regression_tree.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/avgpool2d.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/conv1d.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/conv2d.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/linear.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/maxpool2d.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/minpool2d.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/mpool2d.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/nn/torch_sequential.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/piecewise.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/result.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/shape.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/formulations/utils.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/__init__.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/activation.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/log_power.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/matrix.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/probability.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/math/trigonometric.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/py.typed +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy/version.py +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy.egg-info/dependency_links.txt +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy.egg-info/entry_points.txt +0 -0
- {gamspy-1.18.1 → gamspy-1.18.3}/src/gamspy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gamspy
|
|
3
|
-
Version: 1.18.
|
|
3
|
+
Version: 1.18.3
|
|
4
4
|
Summary: Python-based algebraic modeling interface to GAMS
|
|
5
5
|
Author-email: GAMS Development Corporation <support@gams.com>
|
|
6
6
|
Project-URL: homepage, https://gams.com/sales/gamspy_facts/
|
|
@@ -31,8 +31,8 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
31
31
|
Requires-Python: >=3.10
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
License-File: LICENSE
|
|
34
|
-
Requires-Dist: gamsapi<53.0.0,>=52.
|
|
35
|
-
Requires-Dist: gamspy_base<53.0.0,>=52.
|
|
34
|
+
Requires-Dist: gamsapi<53.0.0,>=52.2.0
|
|
35
|
+
Requires-Dist: gamspy_base<53.0.0,>=52.2.0
|
|
36
36
|
Requires-Dist: pandas<2.4,>=2.2.2
|
|
37
37
|
Requires-Dist: pydantic>=2.0
|
|
38
38
|
Requires-Dist: requests>=2.28.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gamspy"
|
|
7
|
-
version = "1.18.
|
|
7
|
+
version = "1.18.3"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "GAMS Development Corporation", email = "support@gams.com" },
|
|
10
10
|
]
|
|
@@ -36,8 +36,8 @@ classifiers = [
|
|
|
36
36
|
"Operating System :: Microsoft :: Windows",
|
|
37
37
|
]
|
|
38
38
|
dependencies = [
|
|
39
|
-
"gamsapi >= 52.
|
|
40
|
-
"gamspy_base >= 52.
|
|
39
|
+
"gamsapi >= 52.2.0, < 53.0.0",
|
|
40
|
+
"gamspy_base >= 52.2.0, < 53.0.0",
|
|
41
41
|
"pandas >= 2.2.2, < 2.4",
|
|
42
42
|
"pydantic >= 2.0",
|
|
43
43
|
"requests >= 2.28.0",
|
|
@@ -234,7 +234,7 @@ def create_latex_expression(root_node: Expression) -> str:
|
|
|
234
234
|
if node.right is not None:
|
|
235
235
|
s1.append(node.right)
|
|
236
236
|
|
|
237
|
-
# 2. Build the
|
|
237
|
+
# 2. Build the LaTeX expression
|
|
238
238
|
eval_stack: list[tuple[str, float]] = []
|
|
239
239
|
for node in reversed(post_order_nodes):
|
|
240
240
|
if not isinstance(node, Expression):
|
|
@@ -96,8 +96,15 @@ class Operation(operable.Operable):
|
|
|
96
96
|
"""
|
|
97
97
|
assert self.container is not None
|
|
98
98
|
temp_name = "a" + utils._get_unique_name()
|
|
99
|
+
domain: list[Set | Alias] = []
|
|
100
|
+
for elem in self.domain:
|
|
101
|
+
if hasattr(elem, "dimension") and elem.dimension > 1:
|
|
102
|
+
domain.extend(elem.domain)
|
|
103
|
+
else:
|
|
104
|
+
domain.append(elem)
|
|
105
|
+
|
|
99
106
|
temp_param = syms.Parameter._constructor_bypass(
|
|
100
|
-
self.container, temp_name,
|
|
107
|
+
self.container, temp_name, domain
|
|
101
108
|
)
|
|
102
109
|
temp_param[...] = self
|
|
103
110
|
del self.container.data[temp_name]
|
|
@@ -756,7 +763,7 @@ class Ord(operable.Operable):
|
|
|
756
763
|
-------
|
|
757
764
|
str
|
|
758
765
|
"""
|
|
759
|
-
return f"ord({self._symbol.
|
|
766
|
+
return f"ord({self._symbol._latex_name})"
|
|
760
767
|
|
|
761
768
|
|
|
762
769
|
class Card(operable.Operable):
|
|
@@ -835,4 +842,4 @@ class Card(operable.Operable):
|
|
|
835
842
|
-------
|
|
836
843
|
str
|
|
837
844
|
"""
|
|
838
|
-
return f"card({self._symbol.
|
|
845
|
+
return f"card({self._symbol._latex_name})"
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
|
|
5
|
-
from . import gdx, install, list, probe, retrieve, run, show, uninstall
|
|
5
|
+
from . import gdx, install, list, mps2gms, probe, retrieve, run, show, uninstall
|
|
6
6
|
|
|
7
7
|
app = typer.Typer(
|
|
8
8
|
rich_markup_mode="rich",
|
|
@@ -16,6 +16,7 @@ app.add_typer(retrieve.app, name="retrieve")
|
|
|
16
16
|
app.add_typer(run.app, name="run")
|
|
17
17
|
app.add_typer(show.app, name="show")
|
|
18
18
|
app.add_typer(uninstall.app, name="uninstall")
|
|
19
|
+
app.command(name="mps2gms")(mps2gms.mps2gms)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def version_callback(value: bool):
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path # noqa: TC003
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
import gamspy_base
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
# Valid values for categorical parameters based on help string
|
|
13
|
+
VALID_YN = {"0", "N", "1", "Y"}
|
|
14
|
+
VALID_DUPLICATES = {"NOCHECK", "ADD", "IGNORE", "ERROR"}
|
|
15
|
+
VALID_ORIGNAMES = {"NO", "MODIFIED", "ALL"}
|
|
16
|
+
VALID_CONVERTSENSE = {"0", "N", "1", "Y", "MIN", "-1", "MAX"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Completion functions for Typer
|
|
20
|
+
def complete_yn(ctx: typer.Context, incomplete: str):
|
|
21
|
+
return [v for v in VALID_YN if v.lower().startswith(incomplete.lower())]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def complete_duplicates(ctx: typer.Context, incomplete: str):
|
|
25
|
+
return [v for v in VALID_DUPLICATES if v.lower().startswith(incomplete.lower())]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def complete_orignames(ctx: typer.Context, incomplete: str):
|
|
29
|
+
return [v for v in VALID_ORIGNAMES if v.lower().startswith(incomplete.lower())]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def complete_convertsense(ctx: typer.Context, incomplete: str):
|
|
33
|
+
return [v for v in VALID_CONVERTSENSE if v.lower().startswith(incomplete.lower())]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def mps2gms(
|
|
37
|
+
input_file: Annotated[
|
|
38
|
+
Path, typer.Argument(help="MPS or LP file to translate.", exists=True)
|
|
39
|
+
],
|
|
40
|
+
gdx_file: Annotated[
|
|
41
|
+
Path | None, typer.Argument(help="Name of GDX output file.")
|
|
42
|
+
] = None,
|
|
43
|
+
gms_file: Annotated[
|
|
44
|
+
Path | None, typer.Argument(help="Name of GAMS program output file.")
|
|
45
|
+
] = None,
|
|
46
|
+
py_file: Annotated[
|
|
47
|
+
str | None, typer.Option("--py", help="Name of GAMSPy program output file.")
|
|
48
|
+
] = None,
|
|
49
|
+
dec_file: Annotated[
|
|
50
|
+
str | None, typer.Option("--dec", help="DEC file for decomposition info.")
|
|
51
|
+
] = None,
|
|
52
|
+
column_int_vars_binary: Annotated[
|
|
53
|
+
str | None,
|
|
54
|
+
typer.Option(
|
|
55
|
+
"--columnintvarsarebinary",
|
|
56
|
+
help="Integer variables appearing first are binary.",
|
|
57
|
+
autocompletion=complete_yn,
|
|
58
|
+
),
|
|
59
|
+
] = None,
|
|
60
|
+
duplicates: Annotated[
|
|
61
|
+
str | None,
|
|
62
|
+
typer.Option(
|
|
63
|
+
help="How to handle multiple coefficients.",
|
|
64
|
+
autocompletion=complete_duplicates,
|
|
65
|
+
),
|
|
66
|
+
] = None,
|
|
67
|
+
orignames: Annotated[
|
|
68
|
+
str | None,
|
|
69
|
+
typer.Option(
|
|
70
|
+
help="Whether to make original names available.",
|
|
71
|
+
autocompletion=complete_orignames,
|
|
72
|
+
),
|
|
73
|
+
] = None,
|
|
74
|
+
stageshift: Annotated[
|
|
75
|
+
int | None, typer.Option(help="Shift block numbers by this integer.")
|
|
76
|
+
] = None,
|
|
77
|
+
convertsense: Annotated[
|
|
78
|
+
str | None,
|
|
79
|
+
typer.Option(
|
|
80
|
+
help="Convert the objective function sense.",
|
|
81
|
+
autocompletion=complete_convertsense,
|
|
82
|
+
),
|
|
83
|
+
] = None,
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Translates an MPS or LP file into equivalent generic GAMS and GAMSPy programs.
|
|
87
|
+
Defaults to writing .py and .gdx files if no outputs are specified.
|
|
88
|
+
"""
|
|
89
|
+
binary_name = "mps2gms.exe" if platform.system() == "Windows" else "mps2gms"
|
|
90
|
+
MPS2GMS_PATH = os.path.join(gamspy_base.directory, binary_name)
|
|
91
|
+
|
|
92
|
+
if not os.path.exists(MPS2GMS_PATH):
|
|
93
|
+
typer.echo(f"Binary not found: {MPS2GMS_PATH}", err=True)
|
|
94
|
+
raise typer.Exit(code=1)
|
|
95
|
+
|
|
96
|
+
# Determine default names based on input stem
|
|
97
|
+
input_name = input_file.with_suffix("")
|
|
98
|
+
actual_gdx = gdx_file if gdx_file else f"{input_name}.gdx"
|
|
99
|
+
actual_py = py_file if py_file else f"{input_name}.py"
|
|
100
|
+
|
|
101
|
+
# mps2gms <input> <gdx>
|
|
102
|
+
cmd = [MPS2GMS_PATH, str(input_file), str(actual_gdx)]
|
|
103
|
+
|
|
104
|
+
# If gms_file is provided, add it as the third positional;
|
|
105
|
+
# otherwise, explicitly disable GMS output to prioritize py/gdx.
|
|
106
|
+
if gms_file:
|
|
107
|
+
cmd.append(str(gms_file))
|
|
108
|
+
else:
|
|
109
|
+
cmd.append("GMS=")
|
|
110
|
+
|
|
111
|
+
# Construct key=value parameters for the binary call
|
|
112
|
+
params = {
|
|
113
|
+
"PY": actual_py,
|
|
114
|
+
"DEC": dec_file,
|
|
115
|
+
"COLUMNINTVARSAREBINARY": column_int_vars_binary,
|
|
116
|
+
"DUPLICATES": duplicates,
|
|
117
|
+
"ORIGNAMES": orignames,
|
|
118
|
+
"STAGESHIFT": stageshift,
|
|
119
|
+
"CONVERTSENSE": convertsense,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for key, value in params.items():
|
|
123
|
+
if value is not None:
|
|
124
|
+
cmd.append(f"{key}={value}")
|
|
125
|
+
|
|
126
|
+
print(f"Running command: {' '.join(cmd)}")
|
|
127
|
+
result = subprocess.run(cmd, text=True)
|
|
128
|
+
raise typer.Exit(code=result.returncode)
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import atexit
|
|
4
4
|
import os
|
|
5
5
|
import platform
|
|
6
|
+
import re
|
|
6
7
|
import shutil
|
|
7
8
|
import signal
|
|
8
9
|
import sys
|
|
@@ -42,6 +43,7 @@ if TYPE_CHECKING:
|
|
|
42
43
|
Model,
|
|
43
44
|
Parameter,
|
|
44
45
|
Set,
|
|
46
|
+
UniverseAlias,
|
|
45
47
|
Variable,
|
|
46
48
|
)
|
|
47
49
|
from gamspy._algebra.expression import Expression
|
|
@@ -139,6 +141,7 @@ class Container(gt.Container):
|
|
|
139
141
|
self.output = output
|
|
140
142
|
self._gams_string = ""
|
|
141
143
|
self.models: dict[str, Model] = {}
|
|
144
|
+
self._mpsge_models: list[str] = []
|
|
142
145
|
if IS_MIRO_INIT:
|
|
143
146
|
atexit.register(self._write_miro_files)
|
|
144
147
|
|
|
@@ -838,6 +841,14 @@ class Container(gt.Container):
|
|
|
838
841
|
|
|
839
842
|
for _, symbol in self:
|
|
840
843
|
symbol.modified = False
|
|
844
|
+
|
|
845
|
+
# Unfortunately MPSGE requires a dirty trick
|
|
846
|
+
pattern = re.compile(r"^\$sysInclude\s+mpsgeset\s+(\w+)\s*$", re.MULTILINE)
|
|
847
|
+
match = pattern.search(gams_code)
|
|
848
|
+
if match:
|
|
849
|
+
model_name = match.group(1)
|
|
850
|
+
self._mpsge_models.append(model_name.lower())
|
|
851
|
+
|
|
841
852
|
self._unsaved_statements = []
|
|
842
853
|
|
|
843
854
|
def close(self) -> None:
|
|
@@ -888,6 +899,37 @@ class Container(gt.Container):
|
|
|
888
899
|
|
|
889
900
|
return gp.Alias(self, name, alias_with)
|
|
890
901
|
|
|
902
|
+
def addUniverseAlias(self, name: str | None = None) -> UniverseAlias:
|
|
903
|
+
"""
|
|
904
|
+
Creates a new UniverseAlias and adds it to the container
|
|
905
|
+
|
|
906
|
+
Parameters
|
|
907
|
+
----------
|
|
908
|
+
name : str, optional
|
|
909
|
+
Name of the universe alias.
|
|
910
|
+
|
|
911
|
+
Returns
|
|
912
|
+
-------
|
|
913
|
+
UniverseAlias
|
|
914
|
+
|
|
915
|
+
Raises
|
|
916
|
+
------
|
|
917
|
+
ValueError
|
|
918
|
+
If there is symbol with same name but different type in the
|
|
919
|
+
Container
|
|
920
|
+
|
|
921
|
+
Examples
|
|
922
|
+
--------
|
|
923
|
+
>>> import gamspy as gp
|
|
924
|
+
>>> m = gp.Container()
|
|
925
|
+
>>> a = m.addUniverseAlias("a")
|
|
926
|
+
|
|
927
|
+
"""
|
|
928
|
+
if name is None:
|
|
929
|
+
name = self._get_symbol_name(prefix="u")
|
|
930
|
+
|
|
931
|
+
return gp.UniverseAlias(self, name)
|
|
932
|
+
|
|
891
933
|
def addSet(
|
|
892
934
|
self,
|
|
893
935
|
name: str | None = None,
|
|
@@ -1099,7 +1141,7 @@ class Container(gt.Container):
|
|
|
1099
1141
|
self,
|
|
1100
1142
|
name: str | None = None,
|
|
1101
1143
|
type: str | EquationType = "regular",
|
|
1102
|
-
domain: Sequence[Set | Alias
|
|
1144
|
+
domain: Sequence[Set | Alias] | Set | Alias | None = None,
|
|
1103
1145
|
definition: Variable | Operation | Expression | None = None,
|
|
1104
1146
|
records: Any | None = None,
|
|
1105
1147
|
domain_forwarding: bool | list[bool] = False,
|
|
@@ -1117,7 +1159,7 @@ class Container(gt.Container):
|
|
|
1117
1159
|
Name of the equation. Name is autogenerated by default.
|
|
1118
1160
|
type : str
|
|
1119
1161
|
Type of the equation. "regular" by default.
|
|
1120
|
-
domain : Sequence[Set | Alias
|
|
1162
|
+
domain : Sequence[Set | Alias] | Set | Alias, optional
|
|
1121
1163
|
Domain of the variable.
|
|
1122
1164
|
definition: Expression, optional
|
|
1123
1165
|
Definition of the equation.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
import os
|
|
5
4
|
import subprocess
|
|
6
5
|
from collections.abc import Sequence
|
|
@@ -28,14 +27,6 @@ if TYPE_CHECKING:
|
|
|
28
27
|
|
|
29
28
|
SymbolType: TypeAlias = Alias | Set | Parameter | Variable | Equation
|
|
30
29
|
|
|
31
|
-
logger = logging.getLogger("CONVERTER")
|
|
32
|
-
logger.setLevel(logging.INFO)
|
|
33
|
-
stream_handler = logging.StreamHandler()
|
|
34
|
-
stream_handler.setLevel(logging.INFO)
|
|
35
|
-
formatter = logging.Formatter("[%(name)s - %(levelname)s] %(message)s")
|
|
36
|
-
stream_handler.setFormatter(formatter)
|
|
37
|
-
logger.addHandler(stream_handler)
|
|
38
|
-
|
|
39
30
|
GAMS_JACOBIAN_TEMPLATE = """$onEmpty
|
|
40
31
|
Set
|
|
41
32
|
i 'equations index'
|
|
@@ -388,9 +379,9 @@ class GamsConverter:
|
|
|
388
379
|
self.path = path
|
|
389
380
|
self.options = options
|
|
390
381
|
self.dump_gams_state = dump_gams_state
|
|
391
|
-
self.gdx_path =
|
|
392
|
-
self.gms_path =
|
|
393
|
-
self.g00_path =
|
|
382
|
+
self.gdx_path = path / f"{model.name}_data.gdx"
|
|
383
|
+
self.gms_path = path / f"{model.name}.gms"
|
|
384
|
+
self.g00_path = path / f"{model.name}.g00"
|
|
394
385
|
|
|
395
386
|
def get_definitions(self) -> list[str]:
|
|
396
387
|
definitions = []
|
|
@@ -438,8 +429,8 @@ class GamsConverter:
|
|
|
438
429
|
em_name = self.model._external_module
|
|
439
430
|
em_file = self.model._external_module_file
|
|
440
431
|
declarations.append(f"File {em_file} /{em_name}/;")
|
|
441
|
-
|
|
442
|
-
|
|
432
|
+
print("Converter will not copy external module files")
|
|
433
|
+
print(
|
|
443
434
|
f"You need to ensure your external module is accessible from {self.path}"
|
|
444
435
|
)
|
|
445
436
|
|
|
@@ -482,7 +473,9 @@ class GamsConverter:
|
|
|
482
473
|
with open(self.gms_path, "w", encoding="utf-8") as file:
|
|
483
474
|
file.write(gams_string)
|
|
484
475
|
|
|
485
|
-
|
|
476
|
+
print("=" * 80)
|
|
477
|
+
print(f"GAMS (.gms) file has been generated under {self.gms_path}")
|
|
478
|
+
print("=" * 80)
|
|
486
479
|
|
|
487
480
|
|
|
488
481
|
TABLE_HEADER = """\\begin{tabularx}{\\textwidth}{| l | l | X |}
|
|
@@ -496,7 +489,7 @@ TABLE_FOOTER = "\\hline\n\\end{tabularx}"
|
|
|
496
489
|
|
|
497
490
|
|
|
498
491
|
class LatexConverter:
|
|
499
|
-
def __init__(self, model: Model, path: Path) -> None:
|
|
492
|
+
def __init__(self, model: Model, path: Path, rename: dict[str, str] | None) -> None:
|
|
500
493
|
os.makedirs(path, exist_ok=True)
|
|
501
494
|
self.model = model
|
|
502
495
|
self.container = model.container
|
|
@@ -512,11 +505,21 @@ class LatexConverter:
|
|
|
512
505
|
):
|
|
513
506
|
symbols.append(elem.name)
|
|
514
507
|
|
|
508
|
+
for name in self.model._autogen_symbols:
|
|
509
|
+
if name in symbols:
|
|
510
|
+
symbols.remove(name)
|
|
511
|
+
|
|
515
512
|
self.symbols = sorted(symbols, key=list(self.container.data.keys()).index)
|
|
516
513
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
514
|
+
if rename is not None:
|
|
515
|
+
for name, latex_name in rename.items():
|
|
516
|
+
try:
|
|
517
|
+
symbol = self.container[name]
|
|
518
|
+
symbol._latex_name = latex_name
|
|
519
|
+
except KeyError as e:
|
|
520
|
+
raise KeyError(
|
|
521
|
+
f"`{name}` does not exist in the Container. You can only rename symbols that already exist in the container."
|
|
522
|
+
) from e
|
|
520
523
|
|
|
521
524
|
self.header = self.get_header()
|
|
522
525
|
self.set_header = "\\subsection*{Sets}"
|
|
@@ -551,8 +554,9 @@ class LatexConverter:
|
|
|
551
554
|
if self.model._objective is None:
|
|
552
555
|
...
|
|
553
556
|
elif isinstance(self.model._objective, syms.Variable):
|
|
557
|
+
var_name = self.model._objective._latex_name
|
|
554
558
|
latex_strs.append(
|
|
555
|
-
f"\\textbf{{{str(self.model.sense).lower()}}} ${
|
|
559
|
+
f"\\textbf{{{str(self.model.sense).lower()}}} ${var_name}$\\\\"
|
|
556
560
|
)
|
|
557
561
|
latex_strs.append("\\textbf{s.t.}")
|
|
558
562
|
else:
|
|
@@ -572,26 +576,35 @@ class LatexConverter:
|
|
|
572
576
|
with open(self.tex_path, "w", encoding="utf-8") as file: # Write the TEX file
|
|
573
577
|
file.write(latex_str)
|
|
574
578
|
|
|
575
|
-
|
|
579
|
+
print("=" * 80)
|
|
580
|
+
print(
|
|
576
581
|
f"LaTeX (.tex) file has been generated under {os.path.join(self.path, self.model.name + '.tex')}"
|
|
577
582
|
)
|
|
583
|
+
print("=" * 80)
|
|
578
584
|
|
|
579
585
|
self.latex_str = latex_str
|
|
580
586
|
|
|
581
587
|
def to_pdf(self) -> None:
|
|
582
|
-
|
|
583
|
-
|
|
588
|
+
try:
|
|
589
|
+
subprocess.run(["xelatex", "-version"], check=True, text=True)
|
|
590
|
+
except subprocess.CalledProcessError as e:
|
|
584
591
|
raise ValidationError(
|
|
585
|
-
"`
|
|
592
|
+
"`xelatex` is required to generate the pdf! Please install `xelatex` and add it to the path."
|
|
593
|
+
) from e
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
subprocess.run(
|
|
597
|
+
[
|
|
598
|
+
"xelatex",
|
|
599
|
+
"-verbose",
|
|
600
|
+
f"-output-directory={self.path}",
|
|
601
|
+
self.tex_path,
|
|
602
|
+
],
|
|
603
|
+
check=True,
|
|
604
|
+
text=True,
|
|
586
605
|
)
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
["pdflatex", f"-output-directory={self.path}", self.tex_path],
|
|
590
|
-
capture_output=True,
|
|
591
|
-
text=True,
|
|
592
|
-
)
|
|
593
|
-
if process.returncode:
|
|
594
|
-
raise LatexException(f"Could not generate pdf file: {process.stderr}")
|
|
606
|
+
except subprocess.CalledProcessError as e:
|
|
607
|
+
raise LatexException(f"Could not generate pdf file: {e}") from e
|
|
595
608
|
|
|
596
609
|
def get_table(self, symbol_type) -> str:
|
|
597
610
|
table = [TABLE_HEADER]
|
|
@@ -604,10 +617,11 @@ class LatexConverter:
|
|
|
604
617
|
if isinstance(symbol, syms.Variable) and self.model._limited_variables:
|
|
605
618
|
for elem in self.model._limited_variables:
|
|
606
619
|
if elem.name == symbol.name:
|
|
607
|
-
domain_str = utils._get_domain_str(elem.domain)[
|
|
620
|
+
domain_str = utils._get_domain_str(elem.domain, latex=True)[
|
|
621
|
+
1:-1
|
|
622
|
+
]
|
|
608
623
|
|
|
609
|
-
row = f"{
|
|
610
|
-
row = row.replace("_", "\\_")
|
|
624
|
+
row = f"{symbol._latex_name} & {domain_str} & {summary['description']}\\\\"
|
|
611
625
|
table.append(row)
|
|
612
626
|
|
|
613
627
|
table.append(TABLE_FOOTER)
|
|
@@ -615,16 +629,16 @@ class LatexConverter:
|
|
|
615
629
|
return "\n".join(table)
|
|
616
630
|
|
|
617
631
|
def get_definitions(self) -> str:
|
|
618
|
-
definitions = []
|
|
632
|
+
definitions: list[str] = []
|
|
619
633
|
for equation in self.model.equations:
|
|
620
634
|
if equation.name in self.model._autogen_symbols:
|
|
621
635
|
continue
|
|
622
636
|
|
|
623
|
-
domain_str = ",".join([elem.
|
|
624
|
-
header = "\\subsubsection*{
|
|
637
|
+
domain_str = ",".join([elem.latexRepr() for elem in equation.domain])
|
|
638
|
+
header = "\\subsubsection*{" + equation._latex_name
|
|
625
639
|
if domain_str:
|
|
626
|
-
header += f"_{{{domain_str}}}"
|
|
627
|
-
header += "
|
|
640
|
+
header += f"$_{{{domain_str}}}$"
|
|
641
|
+
header += "}\n"
|
|
628
642
|
|
|
629
643
|
footer = "\n\\vspace{5pt}\n\\hrule"
|
|
630
644
|
latex_repr = f"{header}{equation.latexRepr()}{footer}"
|
|
@@ -633,28 +647,41 @@ class LatexConverter:
|
|
|
633
647
|
return "\n".join(definitions)
|
|
634
648
|
|
|
635
649
|
def get_constraints(self) -> str:
|
|
636
|
-
constraints = ["
|
|
650
|
+
constraints = [r"\begin{flalign*}"]
|
|
637
651
|
for name in self.symbols:
|
|
638
652
|
symbol = self.container[name]
|
|
639
653
|
if not isinstance(symbol, syms.Variable):
|
|
640
654
|
continue
|
|
641
655
|
|
|
642
|
-
constraint = "
|
|
656
|
+
constraint = "&" + symbol.latexRepr()
|
|
643
657
|
if symbol.type == "binary":
|
|
644
|
-
constraint +=
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
658
|
+
constraint += "\\in " + r"\{0,1\}"
|
|
659
|
+
if symbol.domain:
|
|
660
|
+
constraint += (
|
|
661
|
+
" ~ \\forall "
|
|
662
|
+
+ utils._get_domain_str(symbol.domain, latex=True)[1:-1]
|
|
663
|
+
)
|
|
650
664
|
elif symbol.type == "integer":
|
|
651
|
-
constraint += "\\in \\mathbb{Z}_{+}
|
|
652
|
-
|
|
653
|
-
|
|
665
|
+
constraint += "\\in \\mathbb{Z}_{+}"
|
|
666
|
+
if symbol.domain:
|
|
667
|
+
constraint += (
|
|
668
|
+
" ~ \\forall "
|
|
669
|
+
+ utils._get_domain_str(symbol.domain, latex=True)[1:-1]
|
|
670
|
+
)
|
|
654
671
|
elif symbol.type == "positive":
|
|
655
|
-
constraint += "\\geq 0
|
|
672
|
+
constraint += "\\geq 0"
|
|
673
|
+
if symbol.domain:
|
|
674
|
+
constraint += (
|
|
675
|
+
" ~ \\forall "
|
|
676
|
+
+ utils._get_domain_str(symbol.domain, latex=True)[1:-1]
|
|
677
|
+
)
|
|
656
678
|
elif symbol.type == "negative":
|
|
657
|
-
constraint += "\\leq 0
|
|
679
|
+
constraint += "\\leq 0"
|
|
680
|
+
if symbol.domain:
|
|
681
|
+
constraint += (
|
|
682
|
+
" ~ \\forall "
|
|
683
|
+
+ utils._get_domain_str(symbol.domain, latex=True)[1:-1]
|
|
684
|
+
)
|
|
658
685
|
elif symbol.type == "sos1":
|
|
659
686
|
constraint += "SOS1"
|
|
660
687
|
elif symbol.type == "sos2":
|
|
@@ -666,24 +693,28 @@ class LatexConverter:
|
|
|
666
693
|
else:
|
|
667
694
|
continue
|
|
668
695
|
|
|
669
|
-
constraint += "
|
|
696
|
+
constraint += "&\\\\"
|
|
670
697
|
constraints.append(constraint)
|
|
671
698
|
|
|
699
|
+
constraints.append(r"\end{flalign*}")
|
|
672
700
|
return "\n".join(constraints)
|
|
673
701
|
|
|
674
702
|
def get_header(self) -> str:
|
|
675
|
-
header = """
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
703
|
+
header = r"""\documentclass[11pt]{article}
|
|
704
|
+
\usepackage{geometry}
|
|
705
|
+
\usepackage[american]{babel}
|
|
706
|
+
\usepackage{amsmath}
|
|
707
|
+
\usepackage{amssymb}
|
|
708
|
+
\usepackage{fontspec}
|
|
709
|
+
\setmainfont{CMU Serif} % 1. Sets a font that knows Greek in text mode
|
|
710
|
+
\usepackage{unicode-math} % 2. Allows Greek keys to work in math mode
|
|
711
|
+
\usepackage[hidelinks]{hyperref}
|
|
712
|
+
\usepackage{tabularx}
|
|
713
|
+
\usepackage{ltablex}
|
|
714
|
+
\keepXColumns
|
|
715
|
+
|
|
716
|
+
\begin{document}
|
|
717
|
+
\section*{Symbols}
|
|
687
718
|
|
|
688
719
|
"""
|
|
689
720
|
return header
|
|
@@ -120,7 +120,23 @@ class ExtrinsicFunction(operable.Operable):
|
|
|
120
120
|
-------
|
|
121
121
|
str
|
|
122
122
|
"""
|
|
123
|
-
|
|
123
|
+
representation = self.name.replace("_", r"\_")
|
|
124
|
+
|
|
125
|
+
if self.args:
|
|
126
|
+
arg_strs = []
|
|
127
|
+
for arg in self.args:
|
|
128
|
+
arg_str = (
|
|
129
|
+
str(arg).replace("_", r"\_")
|
|
130
|
+
if isinstance(arg, (int, float, str))
|
|
131
|
+
else arg.latexRepr()
|
|
132
|
+
)
|
|
133
|
+
arg_strs.append(arg_str)
|
|
134
|
+
|
|
135
|
+
args = ",".join(arg_strs)
|
|
136
|
+
|
|
137
|
+
representation = f"{representation}({args})"
|
|
138
|
+
|
|
139
|
+
return representation
|
|
124
140
|
|
|
125
141
|
|
|
126
142
|
class ExtrinsicLibrary:
|