gamspy 1.17.1__tar.gz → 1.18.0__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.17.1 → gamspy-1.18.0}/PKG-INFO +3 -3
- {gamspy-1.17.1 → gamspy-1.18.0}/pyproject.toml +3 -3
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_backend/backend.py +3 -2
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_backend/engine.py +6 -3
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_backend/local.py +6 -2
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_backend/neos.py +27 -5
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/install.py +10 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/retrieve.py +6 -3
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/run.py +22 -13
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/uninstall.py +4 -0
- gamspy-1.18.0/src/gamspy/_communication.py +215 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_container.py +48 -211
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_convert.py +2 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_miro.py +8 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_model.py +118 -26
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_model_instance.py +7 -6
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_options.py +2 -8
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/alias.py +0 -3
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/equation.py +7 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/variable.py +17 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_validation.py +0 -5
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/__init__.py +2 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/torch_sequential.py +80 -8
- gamspy-1.18.0/src/gamspy/formulations/result.py +119 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/__init__.py +2 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/activation.py +132 -6
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/utils.py +2 -6
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy.egg-info/PKG-INFO +3 -3
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy.egg-info/SOURCES.txt +2 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy.egg-info/requires.txt +2 -2
- {gamspy-1.17.1 → gamspy-1.18.0}/tests/test_gamspy.py +1 -1
- {gamspy-1.17.1 → gamspy-1.18.0}/LICENSE +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/README.md +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/README_PYPI.md +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/setup.cfg +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/__main__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/condition.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/domain.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/expression.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/number.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/operable.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_algebra/operation.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_backend/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/cli.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/gdx.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/list.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/probe.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/show.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_cli/util.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_config.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_database.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_extrinsic.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_serialization.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/implicits/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/implicits/implicit_equation.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/implicits/implicit_parameter.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/implicits/implicit_set.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/implicits/implicit_symbol.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/implicits/implicit_variable.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/parameter.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/set.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/symbol.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_symbols/universe_alias.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_types.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/_workspace.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/exceptions.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/ml/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/ml/decision_tree_struct.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/ml/gradient_boosting.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/ml/random_forest.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/ml/regression_tree.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/__init__.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/avgpool2d.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/conv1d.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/conv2d.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/linear.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/maxpool2d.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/minpool2d.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/nn/mpool2d.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/piecewise.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/shape.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/formulations/utils.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/log_power.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/matrix.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/misc.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/probability.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/math/trigonometric.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/py.typed +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy/version.py +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy.egg-info/dependency_links.txt +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/src/gamspy.egg-info/entry_points.txt +0 -0
- {gamspy-1.17.1 → gamspy-1.18.0}/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.
|
|
3
|
+
Version: 1.18.0
|
|
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<
|
|
35
|
-
Requires-Dist: gamspy_base<
|
|
34
|
+
Requires-Dist: gamsapi<53.0.0,>=52.1.0
|
|
35
|
+
Requires-Dist: gamspy_base<53.0.0,>=52.1.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.
|
|
7
|
+
version = "1.18.0"
|
|
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 >=
|
|
40
|
-
"gamspy_base >=
|
|
39
|
+
"gamsapi >= 52.1.0, < 53.0.0",
|
|
40
|
+
"gamspy_base >= 52.1.0, < 53.0.0",
|
|
41
41
|
"pandas >= 2.2.2, < 2.4",
|
|
42
42
|
"pydantic >= 2.0",
|
|
43
43
|
"requests >= 2.28.0",
|
|
@@ -12,6 +12,7 @@ from gamspy.exceptions import GamspyException, ValidationError
|
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
import io
|
|
15
|
+
from pathlib import Path
|
|
15
16
|
|
|
16
17
|
from gamspy import Container, Model, Options
|
|
17
18
|
from gamspy._backend.engine import EngineClient, GAMSEngine
|
|
@@ -52,7 +53,7 @@ def backend_factory(
|
|
|
52
53
|
container: Container,
|
|
53
54
|
options: Options | None = None,
|
|
54
55
|
solver: str | None = None,
|
|
55
|
-
solver_options: dict | None = None,
|
|
56
|
+
solver_options: dict | Path | None = None,
|
|
56
57
|
output: io.TextIOWrapper | None = None,
|
|
57
58
|
backend: Literal["local", "engine", "neos"] = "local",
|
|
58
59
|
client: EngineClient | NeosClient | None = None,
|
|
@@ -126,7 +127,7 @@ class Backend(ABC):
|
|
|
126
127
|
model: Model | None,
|
|
127
128
|
options: Options,
|
|
128
129
|
solver: str | None,
|
|
129
|
-
solver_options: dict | None,
|
|
130
|
+
solver_options: dict | Path | None,
|
|
130
131
|
output: io.TextIOWrapper | None,
|
|
131
132
|
load_symbols: list[Symbol] | None,
|
|
132
133
|
):
|
|
@@ -17,6 +17,7 @@ import requests
|
|
|
17
17
|
|
|
18
18
|
import gamspy._backend.backend as backend
|
|
19
19
|
import gamspy.utils as utils
|
|
20
|
+
from gamspy._communication import send_job
|
|
20
21
|
from gamspy._options import Options
|
|
21
22
|
from gamspy.exceptions import (
|
|
22
23
|
EngineClientException,
|
|
@@ -26,6 +27,8 @@ from gamspy.exceptions import (
|
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
29
32
|
from gamspy import Container, Model
|
|
30
33
|
from gamspy._symbols.symbol import Symbol
|
|
31
34
|
|
|
@@ -742,7 +745,7 @@ class GAMSEngine(backend.Backend):
|
|
|
742
745
|
client: EngineClient | None,
|
|
743
746
|
options: Options,
|
|
744
747
|
solver: str,
|
|
745
|
-
solver_options: dict | None,
|
|
748
|
+
solver_options: dict | Path | None,
|
|
746
749
|
output: io.TextIOWrapper | None,
|
|
747
750
|
model: Model,
|
|
748
751
|
load_symbols: list[Symbol] | None,
|
|
@@ -916,7 +919,7 @@ class GAMSEngine(backend.Backend):
|
|
|
916
919
|
options._extra_options["save"] = self.restart_file
|
|
917
920
|
options._export(self.pf_file)
|
|
918
921
|
|
|
919
|
-
self.container.
|
|
922
|
+
send_job(self.container._comm_pair_id, self.job_name, self.pf_file)
|
|
920
923
|
|
|
921
924
|
def _sync(self):
|
|
922
925
|
symbols = utils._get_symbol_names_from_gdx(
|
|
@@ -931,7 +934,7 @@ class GAMSEngine(backend.Backend):
|
|
|
931
934
|
options._set_extra_options(extra_options)
|
|
932
935
|
options._export(self.pf_file)
|
|
933
936
|
|
|
934
|
-
self.container.
|
|
937
|
+
send_job(self.container._comm_pair_id, self.job_name, self.pf_file)
|
|
935
938
|
|
|
936
939
|
def _append_gamspy_files(self) -> list[str]:
|
|
937
940
|
extra_model_files = self.client.extra_model_files + [self.restart_file]
|
|
@@ -5,10 +5,12 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
|
|
6
6
|
import gamspy._backend.backend as backend
|
|
7
7
|
import gamspy._miro as miro
|
|
8
|
+
from gamspy._communication import send_job
|
|
8
9
|
from gamspy.exceptions import GamspyException, _customize_exception
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
import io
|
|
13
|
+
from pathlib import Path
|
|
12
14
|
|
|
13
15
|
from gamspy import Container, Model, Options
|
|
14
16
|
from gamspy._symbols.symbol import Symbol
|
|
@@ -20,7 +22,7 @@ class Local(backend.Backend):
|
|
|
20
22
|
container: Container,
|
|
21
23
|
options: Options,
|
|
22
24
|
solver: str | None,
|
|
23
|
-
solver_options: dict | None,
|
|
25
|
+
solver_options: dict | Path | None,
|
|
24
26
|
output: io.TextIOWrapper | None,
|
|
25
27
|
model: Model | None,
|
|
26
28
|
load_symbols: list[Symbol] | None,
|
|
@@ -89,7 +91,9 @@ class Local(backend.Backend):
|
|
|
89
91
|
self.options._export(self.pf_file, self.output)
|
|
90
92
|
|
|
91
93
|
try:
|
|
92
|
-
|
|
94
|
+
send_job(
|
|
95
|
+
self.container._comm_pair_id, self.job_name, self.pf_file, self.output
|
|
96
|
+
)
|
|
93
97
|
|
|
94
98
|
if not self.is_async() and self.model:
|
|
95
99
|
self.model._update_model_attributes()
|
|
@@ -7,12 +7,14 @@ import shutil
|
|
|
7
7
|
import ssl
|
|
8
8
|
import xmlrpc.client
|
|
9
9
|
import zipfile
|
|
10
|
-
from
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
11
12
|
|
|
12
13
|
import certifi
|
|
13
14
|
|
|
14
15
|
import gamspy._backend.backend as backend
|
|
15
16
|
import gamspy.utils as utils
|
|
17
|
+
from gamspy._communication import send_job
|
|
16
18
|
from gamspy._options import Options
|
|
17
19
|
from gamspy.exceptions import (
|
|
18
20
|
GamspyException,
|
|
@@ -256,7 +258,7 @@ class NeosClient:
|
|
|
256
258
|
def _prepare_xml(
|
|
257
259
|
self,
|
|
258
260
|
gams_string: str,
|
|
259
|
-
solver_options: dict | None,
|
|
261
|
+
solver_options: dict | Path | None,
|
|
260
262
|
gdx_path: str,
|
|
261
263
|
restart_path: str,
|
|
262
264
|
options: Options,
|
|
@@ -282,6 +284,9 @@ class NeosClient:
|
|
|
282
284
|
|
|
283
285
|
solver_options_str = ""
|
|
284
286
|
if solver_options:
|
|
287
|
+
if isinstance(solver_options, Path):
|
|
288
|
+
solver_options = _parse_solver_options(solver_options)
|
|
289
|
+
|
|
285
290
|
solver_options_str = "\n".join(
|
|
286
291
|
[f"{key} {value}" for key, value in solver_options.items()]
|
|
287
292
|
)
|
|
@@ -380,7 +385,7 @@ class NEOSServer(backend.Backend):
|
|
|
380
385
|
container: Container,
|
|
381
386
|
options: Options,
|
|
382
387
|
solver: str,
|
|
383
|
-
solver_options: dict | None,
|
|
388
|
+
solver_options: dict | Path | None,
|
|
384
389
|
client: NeosClient | None,
|
|
385
390
|
output: io.TextIOWrapper | None,
|
|
386
391
|
model: Model,
|
|
@@ -527,7 +532,7 @@ class NEOSServer(backend.Backend):
|
|
|
527
532
|
options._extra_options["save"] = self.restart_file
|
|
528
533
|
options._export(self.pf_file)
|
|
529
534
|
|
|
530
|
-
self.container.
|
|
535
|
+
send_job(self.container._comm_pair_id, self.job_name, self.pf_file)
|
|
531
536
|
|
|
532
537
|
def _sync(self):
|
|
533
538
|
symbols = utils._get_symbol_names_from_gdx(
|
|
@@ -542,4 +547,21 @@ class NEOSServer(backend.Backend):
|
|
|
542
547
|
options._set_extra_options(extra_options)
|
|
543
548
|
options._export(self.pf_file)
|
|
544
549
|
|
|
545
|
-
self.container.
|
|
550
|
+
send_job(self.container._comm_pair_id, self.job_name, self.pf_file)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _parse_solver_options(path: Path) -> dict[str, Any]:
|
|
554
|
+
"""Parse solver options to a dict"""
|
|
555
|
+
solver_options = {}
|
|
556
|
+
with open(path) as file:
|
|
557
|
+
lines = file.readlines()
|
|
558
|
+
|
|
559
|
+
for line in lines:
|
|
560
|
+
if not line or line.startswith("*"):
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
splitter = "=" if "=" in line else " "
|
|
564
|
+
key, value = line.split(splitter)
|
|
565
|
+
solver_options[key.strip()] = value
|
|
566
|
+
|
|
567
|
+
return solver_options
|
|
@@ -7,6 +7,7 @@ import subprocess
|
|
|
7
7
|
import sys
|
|
8
8
|
from typing import TYPE_CHECKING, Annotated
|
|
9
9
|
|
|
10
|
+
import certifi
|
|
10
11
|
import requests
|
|
11
12
|
import typer
|
|
12
13
|
|
|
@@ -133,11 +134,16 @@ def license(
|
|
|
133
134
|
command.append("-o")
|
|
134
135
|
command.append(license_path)
|
|
135
136
|
|
|
137
|
+
environment_variables = os.environ.copy()
|
|
138
|
+
if "CURL_CA_BUNDLE" not in environment_variables:
|
|
139
|
+
environment_variables["CURL_CA_BUNDLE"] = certifi.where()
|
|
140
|
+
|
|
136
141
|
process = subprocess.run(
|
|
137
142
|
command,
|
|
138
143
|
text=True,
|
|
139
144
|
capture_output=True,
|
|
140
145
|
encoding="utf-8",
|
|
146
|
+
env=environment_variables,
|
|
141
147
|
)
|
|
142
148
|
if process.returncode:
|
|
143
149
|
raise ValidationError(process.stderr)
|
|
@@ -243,6 +249,9 @@ def solver(
|
|
|
243
249
|
|
|
244
250
|
addons_path = os.path.join(utils.DEFAULT_DIR, "solvers.txt")
|
|
245
251
|
os.makedirs(utils.DEFAULT_DIR, exist_ok=True)
|
|
252
|
+
environment_variables = os.environ.copy()
|
|
253
|
+
if "CURL_CA_BUNDLE" not in environment_variables:
|
|
254
|
+
environment_variables["CURL_CA_BUNDLE"] = certifi.where()
|
|
246
255
|
|
|
247
256
|
def install_addons(addons: Iterable[str]):
|
|
248
257
|
for item in addons:
|
|
@@ -291,6 +300,7 @@ def solver(
|
|
|
291
300
|
check=True,
|
|
292
301
|
encoding="utf-8",
|
|
293
302
|
stderr=subprocess.PIPE,
|
|
303
|
+
env=environment_variables,
|
|
294
304
|
)
|
|
295
305
|
except subprocess.CalledProcessError as e:
|
|
296
306
|
raise GamspyException(
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
5
|
|
|
6
|
+
import certifi
|
|
6
7
|
import typer
|
|
7
8
|
|
|
8
9
|
import gamspy.utils as utils
|
|
@@ -46,6 +47,10 @@ def license(
|
|
|
46
47
|
if access_code is None:
|
|
47
48
|
raise ValidationError(f"Given licence id `{access_code}` is not valid!")
|
|
48
49
|
|
|
50
|
+
environment_variables = os.environ.copy()
|
|
51
|
+
if "CURL_CA_BUNDLE" not in environment_variables:
|
|
52
|
+
environment_variables["CURL_CA_BUNDLE"] = certifi.where()
|
|
53
|
+
|
|
49
54
|
gamspy_base_dir = utils._get_gamspy_base_directory()
|
|
50
55
|
command = [
|
|
51
56
|
os.path.join(gamspy_base_dir, "gamsgetkey"),
|
|
@@ -58,9 +63,7 @@ def license(
|
|
|
58
63
|
command.append(str(checkout_duration))
|
|
59
64
|
|
|
60
65
|
process = subprocess.run(
|
|
61
|
-
command,
|
|
62
|
-
text=True,
|
|
63
|
-
capture_output=True,
|
|
66
|
+
command, text=True, capture_output=True, env=environment_variables
|
|
64
67
|
)
|
|
65
68
|
|
|
66
69
|
if process.returncode:
|
|
@@ -5,11 +5,12 @@ import platform
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
7
|
from enum import Enum
|
|
8
|
-
from
|
|
8
|
+
from pathlib import Path # noqa: TC003
|
|
9
|
+
from typing import Annotated, Optional
|
|
9
10
|
|
|
10
11
|
import typer
|
|
11
12
|
|
|
12
|
-
from gamspy.exceptions import
|
|
13
|
+
from gamspy.exceptions import ValidationError
|
|
13
14
|
|
|
14
15
|
app = typer.Typer(
|
|
15
16
|
rich_markup_mode="rich",
|
|
@@ -19,7 +20,7 @@ app = typer.Typer(
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
class ModeEnum(Enum):
|
|
23
|
+
class ModeEnum(str, Enum):
|
|
23
24
|
config = "config"
|
|
24
25
|
base = "base"
|
|
25
26
|
deploy = "deploy"
|
|
@@ -35,10 +36,11 @@ def miro(
|
|
|
35
36
|
typer.Option("--mode", "-m", help="Execution mode of MIRO"),
|
|
36
37
|
] = ModeEnum.base, # type: ignore
|
|
37
38
|
path: Annotated[
|
|
38
|
-
|
|
39
|
+
Optional[Path], # noqa: UP045
|
|
39
40
|
typer.Option(
|
|
40
41
|
"--path",
|
|
41
42
|
"-p",
|
|
43
|
+
exists=True,
|
|
42
44
|
help="Path to the MIRO executable (.exe, .app, or .AppImage)",
|
|
43
45
|
),
|
|
44
46
|
] = None,
|
|
@@ -47,8 +49,8 @@ def miro(
|
|
|
47
49
|
typer.Option("--skip-execution", help="Whether to skip model execution."),
|
|
48
50
|
] = False,
|
|
49
51
|
model: Annotated[
|
|
50
|
-
|
|
51
|
-
typer.Option("--model", "-g", help="Path to the GAMSPy model."),
|
|
52
|
+
Optional[Path], # noqa: UP045,
|
|
53
|
+
typer.Option("--model", "-g", exists=True, help="Path to the GAMSPy model."),
|
|
52
54
|
] = None,
|
|
53
55
|
args: Annotated[
|
|
54
56
|
list[str] | None,
|
|
@@ -56,17 +58,18 @@ def miro(
|
|
|
56
58
|
] = None,
|
|
57
59
|
) -> None:
|
|
58
60
|
if model is None:
|
|
59
|
-
|
|
61
|
+
typer.echo("--model must be provided to run MIRO", file=sys.stderr)
|
|
62
|
+
sys.exit(1)
|
|
60
63
|
|
|
61
64
|
model = os.path.abspath(model)
|
|
62
65
|
execution_mode = mode.value
|
|
63
|
-
path = os.getenv("MIRO_PATH",
|
|
66
|
+
path = os.getenv("MIRO_PATH", path)
|
|
64
67
|
|
|
65
68
|
if path is None:
|
|
66
69
|
path = path if path is not None else discover_miro()
|
|
67
70
|
|
|
68
71
|
if path is None:
|
|
69
|
-
raise
|
|
72
|
+
raise ValidationError("--path must be provided to run MIRO")
|
|
70
73
|
|
|
71
74
|
if platform.system() == "Darwin" and os.path.splitext(path)[1] == ".app":
|
|
72
75
|
path = os.path.join(path, "Contents", "MacOS", "GAMS MIRO")
|
|
@@ -79,10 +82,16 @@ def miro(
|
|
|
79
82
|
if args is not None:
|
|
80
83
|
command += args
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
process = subprocess.run(
|
|
86
|
+
command,
|
|
87
|
+
env=subprocess_env,
|
|
88
|
+
capture_output=True,
|
|
89
|
+
text=True,
|
|
90
|
+
encoding="utf-8",
|
|
91
|
+
)
|
|
92
|
+
if process.returncode != 0:
|
|
93
|
+
typer.echo(process.stderr, file=sys.stderr)
|
|
94
|
+
sys.exit(process.returncode)
|
|
86
95
|
|
|
87
96
|
# Run MIRO
|
|
88
97
|
subprocess_env = os.environ.copy()
|
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
import sys
|
|
6
6
|
from typing import TYPE_CHECKING, Annotated
|
|
7
7
|
|
|
8
|
+
import certifi
|
|
8
9
|
import typer
|
|
9
10
|
|
|
10
11
|
import gamspy.utils as utils
|
|
@@ -70,6 +71,9 @@ def solver(
|
|
|
70
71
|
) from e
|
|
71
72
|
|
|
72
73
|
addons_path = os.path.join(utils.DEFAULT_DIR, "solvers.txt")
|
|
74
|
+
environment_variables = os.environ.copy()
|
|
75
|
+
if "CURL_CA_BUNDLE" not in environment_variables:
|
|
76
|
+
environment_variables["CURL_CA_BUNDLE"] = certifi.where()
|
|
73
77
|
|
|
74
78
|
def remove_addons(addons: Iterable[str]):
|
|
75
79
|
for item in addons:
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import signal
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
import certifi
|
|
13
|
+
|
|
14
|
+
from gamspy import utils
|
|
15
|
+
from gamspy.exceptions import FatalError, GamspyException, ValidationError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
import io
|
|
19
|
+
|
|
20
|
+
from gamspy import Container
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_comm_pairs: dict[str, tuple[socket.socket, subprocess.Popen]] = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def open_connection(container: Container) -> None:
|
|
27
|
+
LOOPBACK = "127.0.0.1"
|
|
28
|
+
TIMEOUT = 30
|
|
29
|
+
|
|
30
|
+
initial_pf_file = os.path.join(container._process_directory, "gamspy.pf")
|
|
31
|
+
with open(initial_pf_file, "w") as file:
|
|
32
|
+
file.write(
|
|
33
|
+
'incrementalMode="2"\n'
|
|
34
|
+
f'procdir="{container._process_directory}"\n'
|
|
35
|
+
f'license="{container._license_path}"\n'
|
|
36
|
+
f'curdir="{os.getcwd()}"\n'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
command = [
|
|
40
|
+
os.path.join(container.system_directory, "gams"),
|
|
41
|
+
"GAMSPY_JOB",
|
|
42
|
+
"pf",
|
|
43
|
+
initial_pf_file,
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
certificate_path = os.path.join(utils.DEFAULT_DIR, "gamspy_cert.crt")
|
|
47
|
+
env = os.environ.copy()
|
|
48
|
+
if os.path.isfile(certificate_path):
|
|
49
|
+
env["GAMSLICECRT"] = certificate_path
|
|
50
|
+
|
|
51
|
+
env = os.environ.copy()
|
|
52
|
+
if "CURL_CA_BUNDLE" not in env:
|
|
53
|
+
env["CURL_CA_BUNDLE"] = certifi.where()
|
|
54
|
+
|
|
55
|
+
process = subprocess.Popen(
|
|
56
|
+
command,
|
|
57
|
+
text=True,
|
|
58
|
+
stdout=subprocess.PIPE,
|
|
59
|
+
stderr=subprocess.STDOUT,
|
|
60
|
+
errors="replace",
|
|
61
|
+
start_new_session=platform.system() != "Windows",
|
|
62
|
+
env=env,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
port_info = process.stdout.readline().strip()
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
port = int(port_info.removeprefix("port: "))
|
|
69
|
+
except ValueError as e:
|
|
70
|
+
raise ValidationError(
|
|
71
|
+
f"Error while reading the port! {port_info + process.stdout.read()}"
|
|
72
|
+
) from e
|
|
73
|
+
|
|
74
|
+
def handler(signum, frame):
|
|
75
|
+
if platform.system() != "Windows":
|
|
76
|
+
os.kill(process.pid, signal.SIGINT)
|
|
77
|
+
|
|
78
|
+
if threading.current_thread() is threading.main_thread():
|
|
79
|
+
signal.signal(signal.SIGINT, handler)
|
|
80
|
+
|
|
81
|
+
start = time.time()
|
|
82
|
+
while True:
|
|
83
|
+
if process.poll() is not None: # pragma: no cover
|
|
84
|
+
raise ValidationError(process.communicate()[0])
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
new_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
88
|
+
new_socket.connect((LOOPBACK, port))
|
|
89
|
+
break
|
|
90
|
+
except (ConnectionRefusedError, OSError) as e:
|
|
91
|
+
new_socket.close()
|
|
92
|
+
end = time.time()
|
|
93
|
+
|
|
94
|
+
if end - start > TIMEOUT: # pragma: no cover
|
|
95
|
+
raise FatalError(
|
|
96
|
+
f"Timeout while establishing the connection with socket. {process.communicate()[0]}"
|
|
97
|
+
) from e
|
|
98
|
+
|
|
99
|
+
_comm_pairs[container._comm_pair_id] = (new_socket, process)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_connection(pair_id: str) -> tuple[socket.socket, subprocess.Popen]:
|
|
103
|
+
return _comm_pairs[pair_id]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def close_connection(pair_id: str):
|
|
107
|
+
try:
|
|
108
|
+
_socket, process = get_connection(pair_id)
|
|
109
|
+
except KeyError:
|
|
110
|
+
# This means that the connection is already closed.
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
_socket.sendall(b"stop")
|
|
114
|
+
_socket.close()
|
|
115
|
+
|
|
116
|
+
if not process.stdout.closed:
|
|
117
|
+
process.stdout.close()
|
|
118
|
+
|
|
119
|
+
# Wait until the GAMS process dies.
|
|
120
|
+
while process.poll() is None:
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
del _comm_pairs[pair_id]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _read_output(process: subprocess.Popen, output: io.TextIOWrapper | None) -> None:
|
|
127
|
+
if output is not None:
|
|
128
|
+
while True:
|
|
129
|
+
data = process.stdout.readline()
|
|
130
|
+
output.write(data)
|
|
131
|
+
output.flush()
|
|
132
|
+
if data.startswith("--- Job ") and "elapsed" in data:
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def check_response(response: bytes, job_name: str) -> None:
|
|
137
|
+
GAMS_STATUS = {
|
|
138
|
+
1: "Solver is to be called, the system should never return this number.",
|
|
139
|
+
2: "There was a compilation error.",
|
|
140
|
+
3: "There was an execution error.",
|
|
141
|
+
4: "System limits were reached.",
|
|
142
|
+
5: "There was a file error.",
|
|
143
|
+
6: "There was a parameter error.",
|
|
144
|
+
7: "The solve has failed due to a license error. The license you are using may impose model size limits (demo/community license) or you are using a GAMSPy incompatible professional license. Please contact sales@gams.com to find out about license options.",
|
|
145
|
+
8: "There was a GAMS system error.",
|
|
146
|
+
9: "GAMS could not be started.",
|
|
147
|
+
10: "Out of memory.",
|
|
148
|
+
11: "Out of disk.",
|
|
149
|
+
109: "Could not create process/scratch directory.",
|
|
150
|
+
110: "Too many process/scratch directories.",
|
|
151
|
+
112: "Could not delete the process/scratch directory.",
|
|
152
|
+
113: "Could not write the script gamsnext.",
|
|
153
|
+
114: "Could not write the parameter file.",
|
|
154
|
+
115: "Could not read environment variable.",
|
|
155
|
+
400: "Could not spawn the GAMS language compiler (gamscmex).",
|
|
156
|
+
401: "Current directory (curdir) does not exist.",
|
|
157
|
+
402: "Cannot set current directory (curdir).",
|
|
158
|
+
404: "Blank in system directory (UNIX only).",
|
|
159
|
+
405: "Blank in current directory (UNIX only).",
|
|
160
|
+
406: "Blank in scratch extension (scrext)",
|
|
161
|
+
407: "Unexpected cmexRC.",
|
|
162
|
+
408: "Could not find the process directory (procdir).",
|
|
163
|
+
409: "CMEX library not be found (experimental).",
|
|
164
|
+
410: "Entry point in CMEX library could not be found (experimental).",
|
|
165
|
+
411: "Blank in process directory (UNIX only).",
|
|
166
|
+
412: "Blank in scratch directory (UNIX only).",
|
|
167
|
+
909: "Cannot add path / unknown UNIX environment / cannot set environment variable.",
|
|
168
|
+
1000: "Driver error: incorrect command line parameters for gams.",
|
|
169
|
+
2000: "Driver error: internal error: cannot install interrupt handler.",
|
|
170
|
+
3000: "Driver error: problems getting current directory.",
|
|
171
|
+
4000: "Driver error: internal error: GAMS compile and execute module not found.",
|
|
172
|
+
5000: "Driver error: internal error: cannot load option handling library.",
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
value = response[: response.find(b"#")].decode("ascii")
|
|
176
|
+
if not value:
|
|
177
|
+
raise FatalError(
|
|
178
|
+
"Error while getting the return code from GAMS backend. This means that GAMS is in a bad state. Try to backtrack for previous errors."
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return_code = int(value)
|
|
182
|
+
|
|
183
|
+
if return_code in GAMS_STATUS:
|
|
184
|
+
try:
|
|
185
|
+
info = GAMS_STATUS[return_code]
|
|
186
|
+
except IndexError: # pragma: no cover
|
|
187
|
+
info = ""
|
|
188
|
+
|
|
189
|
+
raise GamspyException(
|
|
190
|
+
f"Return code {return_code}. {info} Check {job_name + '.lst'} for more information.",
|
|
191
|
+
return_code,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def send_job(
|
|
196
|
+
comm_pair_id: str,
|
|
197
|
+
job_name: str,
|
|
198
|
+
pf_file: str,
|
|
199
|
+
output: io.TextIOWrapper | None = None,
|
|
200
|
+
):
|
|
201
|
+
_socket, process = get_connection(comm_pair_id)
|
|
202
|
+
try:
|
|
203
|
+
# Send pf file
|
|
204
|
+
_socket.sendall(pf_file.encode("utf-8"))
|
|
205
|
+
|
|
206
|
+
# Read output
|
|
207
|
+
_read_output(process, output)
|
|
208
|
+
|
|
209
|
+
# Receive response
|
|
210
|
+
response = _socket.recv(256)
|
|
211
|
+
check_response(response, job_name)
|
|
212
|
+
except ConnectionError as e:
|
|
213
|
+
raise FatalError(
|
|
214
|
+
f"There was an error while communicating with GAMS server: {e}",
|
|
215
|
+
) from e
|