gamspy 1.17.2__py3-none-any.whl → 1.18.1__py3-none-any.whl

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.
@@ -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
  ):
gamspy/_backend/engine.py CHANGED
@@ -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._send_job(self.job_name, self.pf_file)
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._send_job(self.job_name, self.pf_file)
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]
gamspy/_backend/local.py CHANGED
@@ -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
- self.container._send_job(self.job_name, self.pf_file, self.output)
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()
gamspy/_backend/neos.py CHANGED
@@ -7,12 +7,14 @@ import shutil
7
7
  import ssl
8
8
  import xmlrpc.client
9
9
  import zipfile
10
- from typing import TYPE_CHECKING
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._send_job(self.job_name, self.pf_file)
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._send_job(self.job_name, self.pf_file)
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
gamspy/_cli/install.py CHANGED
@@ -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)
@@ -218,6 +224,8 @@ def solver(
218
224
  install_all_solvers: bool = typer.Option(
219
225
  False,
220
226
  "--install-all-solvers",
227
+ "--all",
228
+ "-a",
221
229
  help="Installs all available add-on solvers.",
222
230
  ),
223
231
  existing_solvers: bool = typer.Option(
@@ -243,6 +251,9 @@ def solver(
243
251
 
244
252
  addons_path = os.path.join(utils.DEFAULT_DIR, "solvers.txt")
245
253
  os.makedirs(utils.DEFAULT_DIR, exist_ok=True)
254
+ environment_variables = os.environ.copy()
255
+ if "CURL_CA_BUNDLE" not in environment_variables:
256
+ environment_variables["CURL_CA_BUNDLE"] = certifi.where()
246
257
 
247
258
  def install_addons(addons: Iterable[str]):
248
259
  for item in addons:
@@ -270,8 +281,6 @@ def solver(
270
281
  command = [
271
282
  "uv",
272
283
  "pip",
273
- "--python-preference",
274
- "only-system",
275
284
  "install",
276
285
  f"gamspy-{solver_name}=={solver_version}",
277
286
  "--force-reinstall",
@@ -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(
gamspy/_cli/retrieve.py CHANGED
@@ -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:
gamspy/_cli/run.py CHANGED
@@ -5,11 +5,12 @@ import platform
5
5
  import subprocess
6
6
  import sys
7
7
  from enum import Enum
8
- from typing import Annotated
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 GamspyException, ValidationError
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
- str | None,
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
- str | None,
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
- raise ValidationError("--model must be provided to run MIRO")
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", None)
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 GamspyException("--path must be provided to run MIRO")
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
- try:
83
- subprocess.run(command, env=subprocess_env, check=True)
84
- except subprocess.CalledProcessError:
85
- return
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()
gamspy/_cli/uninstall.py CHANGED
@@ -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
@@ -50,7 +51,11 @@ def solver(
50
51
  ),
51
52
  ] = None,
52
53
  uninstall_all_solvers: bool = typer.Option(
53
- False, "--uninstall-all-solvers", help="Uninstalls all add-on solvers."
54
+ False,
55
+ "--uninstall-all-solvers",
56
+ "--all",
57
+ "-a",
58
+ help="Uninstalls all add-on solvers.",
54
59
  ),
55
60
  skip_pip_uninstall: bool = typer.Option(
56
61
  False,
@@ -70,6 +75,9 @@ def solver(
70
75
  ) from e
71
76
 
72
77
  addons_path = os.path.join(utils.DEFAULT_DIR, "solvers.txt")
78
+ environment_variables = os.environ.copy()
79
+ if "CURL_CA_BUNDLE" not in environment_variables:
80
+ environment_variables["CURL_CA_BUNDLE"] = certifi.where()
73
81
 
74
82
  def remove_addons(addons: Iterable[str]):
75
83
  for item in addons:
@@ -91,8 +99,6 @@ def solver(
91
99
  command = [
92
100
  "uv",
93
101
  "pip",
94
- "--python-preference",
95
- "only-system",
96
102
  "uninstall",
97
103
  f"gamspy-{solver_name}",
98
104
  ]
@@ -106,7 +112,12 @@ def solver(
106
112
  "-y",
107
113
  ]
108
114
  try:
109
- _ = subprocess.run(command, check=True)
115
+ _ = subprocess.run(
116
+ command,
117
+ check=True,
118
+ encoding="utf-8",
119
+ stderr=subprocess.PIPE,
120
+ )
110
121
  except subprocess.CalledProcessError as e:
111
122
  raise GamspyException(
112
123
  f"Could not uninstall gamspy-{solver_name}: {e.output}"
@@ -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
gamspy/_container.py CHANGED
@@ -3,14 +3,14 @@ from __future__ import annotations
3
3
  import atexit
4
4
  import os
5
5
  import platform
6
+ import shutil
6
7
  import signal
7
- import socket
8
- import subprocess
8
+ import sys
9
9
  import tempfile
10
10
  import threading
11
- import time
12
11
  import traceback
13
12
  import weakref
13
+ from pathlib import Path
14
14
  from typing import TYPE_CHECKING
15
15
 
16
16
  import gams.transfer as gt
@@ -20,12 +20,13 @@ import gamspy._miro as miro
20
20
  import gamspy._validation as validation
21
21
  import gamspy.utils as utils
22
22
  from gamspy._backend.backend import backend_factory
23
+ from gamspy._communication import close_connection, get_connection, open_connection
23
24
  from gamspy._config import get_option
24
25
  from gamspy._extrinsic import ExtrinsicLibrary
25
26
  from gamspy._miro import MiroJSONEncoder
26
27
  from gamspy._model import Problem, Sense
27
28
  from gamspy._workspace import Workspace
28
- from gamspy.exceptions import FatalError, GamspyException, ValidationError
29
+ from gamspy.exceptions import ValidationError
29
30
 
30
31
  if TYPE_CHECKING:
31
32
  import io
@@ -67,79 +68,6 @@ def add_sysdir_to_path(system_directory: str) -> None:
67
68
  os.environ["PATH"] = system_directory
68
69
 
69
70
 
70
- def open_connection(
71
- system_directory: str, process_directory: str, license_path: str
72
- ) -> tuple[socket.socket, subprocess.Popen]:
73
- TIMEOUT = 30
74
-
75
- initial_pf_file = os.path.join(process_directory, "gamspy.pf")
76
- with open(initial_pf_file, "w") as file:
77
- file.write(
78
- 'incrementalMode="2"\n'
79
- f'procdir="{process_directory}"\n'
80
- f'license="{license_path}"\n'
81
- f'curdir="{os.getcwd()}"\n'
82
- )
83
-
84
- command = [
85
- os.path.join(system_directory, "gams"),
86
- "GAMSPY_JOB",
87
- "pf",
88
- initial_pf_file,
89
- ]
90
-
91
- certificate_path = os.path.join(utils.DEFAULT_DIR, "gamspy_cert.crt")
92
- env = os.environ.copy()
93
- if os.path.isfile(certificate_path):
94
- env["GAMSLICECRT"] = certificate_path
95
-
96
- process = subprocess.Popen(
97
- command,
98
- text=True,
99
- stdout=subprocess.PIPE,
100
- stderr=subprocess.STDOUT,
101
- errors="replace",
102
- start_new_session=platform.system() != "Windows",
103
- env=env,
104
- )
105
-
106
- port_info = process.stdout.readline().strip()
107
-
108
- try:
109
- port = int(port_info.removeprefix("port: "))
110
- except ValueError as e:
111
- raise ValidationError(
112
- f"Error while reading the port! {port_info + process.stdout.read()}"
113
- ) from e
114
-
115
- def handler(signum, frame):
116
- if platform.system() != "Windows":
117
- os.kill(process.pid, signal.SIGINT)
118
-
119
- if threading.current_thread() is threading.main_thread():
120
- signal.signal(signal.SIGINT, handler)
121
-
122
- start = time.time()
123
- while True:
124
- if process.poll() is not None: # pragma: no cover
125
- raise ValidationError(process.communicate()[0])
126
-
127
- try:
128
- new_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
129
- new_socket.connect((LOOPBACK, port))
130
- break
131
- except (ConnectionRefusedError, OSError) as e:
132
- new_socket.close()
133
- end = time.time()
134
-
135
- if end - start > TIMEOUT: # pragma: no cover
136
- raise FatalError(
137
- f"Timeout while establishing the connection with socket. {process.communicate()[0]}"
138
- ) from e
139
-
140
- return new_socket, process
141
-
142
-
143
71
  def get_system_directory(system_directory: str | os.PathLike | None) -> str:
144
72
  if isinstance(system_directory, os.PathLike):
145
73
  system_directory = os.fspath(system_directory)
@@ -171,65 +99,6 @@ def get_options_file_name(solver: str, file_number: int) -> str:
171
99
  return options_file_name
172
100
 
173
101
 
174
- def check_response(response: bytes, job_name: str) -> None:
175
- GAMS_STATUS = {
176
- 1: "Solver is to be called, the system should never return this number.",
177
- 2: "There was a compilation error.",
178
- 3: "There was an execution error.",
179
- 4: "System limits were reached.",
180
- 5: "There was a file error.",
181
- 6: "There was a parameter error.",
182
- 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.",
183
- 8: "There was a GAMS system error.",
184
- 9: "GAMS could not be started.",
185
- 10: "Out of memory.",
186
- 11: "Out of disk.",
187
- 109: "Could not create process/scratch directory.",
188
- 110: "Too many process/scratch directories.",
189
- 112: "Could not delete the process/scratch directory.",
190
- 113: "Could not write the script gamsnext.",
191
- 114: "Could not write the parameter file.",
192
- 115: "Could not read environment variable.",
193
- 400: "Could not spawn the GAMS language compiler (gamscmex).",
194
- 401: "Current directory (curdir) does not exist.",
195
- 402: "Cannot set current directory (curdir).",
196
- 404: "Blank in system directory (UNIX only).",
197
- 405: "Blank in current directory (UNIX only).",
198
- 406: "Blank in scratch extension (scrext)",
199
- 407: "Unexpected cmexRC.",
200
- 408: "Could not find the process directory (procdir).",
201
- 409: "CMEX library not be found (experimental).",
202
- 410: "Entry point in CMEX library could not be found (experimental).",
203
- 411: "Blank in process directory (UNIX only).",
204
- 412: "Blank in scratch directory (UNIX only).",
205
- 909: "Cannot add path / unknown UNIX environment / cannot set environment variable.",
206
- 1000: "Driver error: incorrect command line parameters for gams.",
207
- 2000: "Driver error: internal error: cannot install interrupt handler.",
208
- 3000: "Driver error: problems getting current directory.",
209
- 4000: "Driver error: internal error: GAMS compile and execute module not found.",
210
- 5000: "Driver error: internal error: cannot load option handling library.",
211
- }
212
-
213
- value = response[: response.find(b"#")].decode("ascii")
214
- if not value:
215
- raise FatalError(
216
- "Error while getting the return code from GAMS backend. This means that GAMS is in a bad state. Try to backtrack for previous errors."
217
- )
218
-
219
- return_code = int(value)
220
-
221
- if return_code in GAMS_STATUS:
222
- try:
223
- info = GAMS_STATUS[return_code]
224
- except IndexError: # pragma: no cover
225
- info = ""
226
-
227
- raise GamspyException(
228
- f"Return code {return_code}. {info} Check {job_name + '.lst'} for more information.",
229
- return_code,
230
- )
231
-
232
-
233
102
  class Container(gt.Container):
234
103
  """
235
104
  A container is an object that holds all symbols and operates on them.
@@ -266,14 +135,13 @@ class Container(gt.Container):
266
135
  options: Options | None = None,
267
136
  output: io.TextIOWrapper | None = None,
268
137
  ):
138
+ self._comm_pair_id = utils._get_unique_name()
269
139
  self.output = output
270
140
  self._gams_string = ""
271
141
  self.models: dict[str, Model] = {}
272
142
  if IS_MIRO_INIT:
273
143
  atexit.register(self._write_miro_files)
274
144
 
275
- self._is_socket_open = True
276
-
277
145
  system_directory = get_system_directory(system_directory)
278
146
  add_sysdir_to_path(system_directory)
279
147
 
@@ -306,16 +174,8 @@ class Container(gt.Container):
306
174
  self._miro_input_symbols: list[str] = []
307
175
  self._miro_output_symbols: list[str] = []
308
176
 
309
- self._socket, self._process = open_connection(
310
- self.system_directory, self._process_directory, self._license_path
311
- )
312
- weakref.finalize(
313
- self,
314
- self._release_resources,
315
- self._is_socket_open,
316
- self._socket,
317
- self._process,
318
- )
177
+ open_connection(self)
178
+ weakref.finalize(self, close_connection, self._comm_pair_id)
319
179
 
320
180
  self._is_restarted = False
321
181
  if load_from is not None:
@@ -434,58 +294,12 @@ class Container(gt.Container):
434
294
 
435
295
  return name
436
296
 
437
- @staticmethod
438
- def _release_resources(
439
- is_socket_open: bool, socket: socket.socket, process: subprocess.Popen
440
- ):
441
- if is_socket_open:
442
- try:
443
- socket.sendall(b"stop")
444
- socket.close()
445
-
446
- if not process.stdout.closed:
447
- process.stdout.close()
448
-
449
- while process.poll() is None:
450
- ...
451
- except OSError: # Socket may have been already closed.
452
- ...
453
-
454
- def _read_output(self, output: io.TextIOWrapper | None) -> None:
455
- if output is not None:
456
- while True:
457
- data = self._process.stdout.readline()
458
- output.write(data)
459
- output.flush()
460
- if data.startswith("--- Job ") and "elapsed" in data:
461
- break
462
-
463
297
  def _interrupt(self) -> None:
298
+ _, process = get_connection(self._comm_pair_id)
464
299
  if platform.system() == "Windows":
465
- os.kill(self._process.pid, signal.CTRL_C_EVENT)
300
+ os.kill(process.pid, signal.CTRL_C_EVENT)
466
301
  else:
467
- os.kill(self._process.pid, signal.SIGINT)
468
-
469
- def _send_job(
470
- self,
471
- job_name: str,
472
- pf_file: str,
473
- output: io.TextIOWrapper | None = None,
474
- ):
475
- try:
476
- # Send pf file
477
- self._socket.sendall(pf_file.encode("utf-8"))
478
-
479
- # Read output
480
- self._read_output(output)
481
-
482
- # Receive response
483
- response = self._socket.recv(256)
484
- check_response(response, job_name)
485
- except ConnectionError as e:
486
- raise FatalError(
487
- f"There was an error while communicating with GAMS server: {e}",
488
- ) from e
302
+ os.kill(process.pid, signal.SIGINT)
489
303
 
490
304
  def _write_miro_files(self):
491
305
  # create conf_<model>/<model>_io.json
@@ -493,9 +307,8 @@ class Container(gt.Container):
493
307
  encoder = MiroJSONEncoder(self)
494
308
  encoder.write_json()
495
309
  except Exception:
496
- self._release_resources(self._is_socket_open, self._socket, self._process)
497
- self._is_socket_open = False
498
- traceback.print_exc()
310
+ close_connection(self._comm_pair_id)
311
+ traceback.print_exc(file=sys.stderr)
499
312
  os._exit(1)
500
313
 
501
314
  def _add_statement(self, statement) -> None:
@@ -855,7 +668,7 @@ class Container(gt.Container):
855
668
  )
856
669
 
857
670
  def writeSolverOptions(
858
- self, solver: str, solver_options: dict, file_number: int = 1
671
+ self, solver: str, solver_options: dict | str | Path, file_number: int = 1
859
672
  ) -> None:
860
673
  """
861
674
  Writes solver options of the specified solver to the working directory.
@@ -864,8 +677,8 @@ class Container(gt.Container):
864
677
  ----------
865
678
  solver : str
866
679
  Name of the solver.
867
- solver_options : dict
868
- Options of the specified solver.
680
+ solver_options : dict | str | Path
681
+ Options of the specified solver or path to an existing solver options file.
869
682
  file_number : int
870
683
  Solver option file number. Equivalent to optfile option of GAMS. See https://gams.com/latest/docs/UG_GamsCall.html#GAMSAOoptfile for more details.
871
684
 
@@ -881,17 +694,24 @@ class Container(gt.Container):
881
694
  f"The smallest number `file_number` can get is 1 but received {file_number}"
882
695
  )
883
696
 
697
+ if solver.lower() == "conopt":
698
+ solver = "conopt4"
699
+
884
700
  options_file_name = os.path.join(
885
701
  self.working_directory, get_options_file_name(solver, file_number)
886
702
  )
887
703
 
888
- with open(options_file_name, "w", encoding="utf-8") as solver_file:
889
- for key, value in solver_options.items():
890
- row = f"{key} {value}\n"
891
- if solver.upper() in ("SHOT", "SOPLEX", "SCIP", "HIGHS"):
892
- row = f"{key} = {value}\n"
704
+ if isinstance(solver_options, (str, Path)):
705
+ path = Path(solver_options)
706
+ shutil.copy2(path, options_file_name)
707
+ else:
708
+ with open(options_file_name, "w", encoding="utf-8") as solver_file:
709
+ for key, value in solver_options.items():
710
+ row = f"{key} {value}\n"
711
+ if solver.upper() in ("SHOT", "SOPLEX", "SCIP", "HIGHS"):
712
+ row = f"{key} = {value}\n"
893
713
 
894
- solver_file.write(row)
714
+ solver_file.write(row)
895
715
 
896
716
  # The following solvers do not use the opt<solver>.def file
897
717
  if solver.upper() in (
@@ -1026,8 +846,7 @@ class Container(gt.Container):
1026
846
  to communicate with the GAMS execution engine, e.g. creating new symbols, changing data,
1027
847
  solves, etc. The container data (Container.data) is still available for read operations.
1028
848
  """
1029
- self._release_resources(self._is_socket_open, self._socket, self._process)
1030
- self._is_socket_open = False
849
+ close_connection(self._comm_pair_id)
1031
850
 
1032
851
  def addAlias(
1033
852
  self,
gamspy/_convert.py CHANGED
@@ -311,6 +311,8 @@ def get_convert_solver_options(
311
311
  FORMAT_RENAME_MAP = {
312
312
  "GDXJacobian": "DumpGDX",
313
313
  "GDXIntervalEval": "IntervalEval",
314
+ "GAMSDict": "Dict",
315
+ "GAMSDictMap": "DictMap",
314
316
  }
315
317
  OPTION_RENAME_MAP = {"GAMSInsert": "GmsInsert", "GAMSObjVar": "ObjVar"}
316
318
 
gamspy/_model.py CHANGED
@@ -4,7 +4,6 @@ import inspect
4
4
  import logging
5
5
  import os
6
6
  import threading
7
- import warnings
8
7
  from collections.abc import Sequence
9
8
  from enum import Enum
10
9
  from pathlib import Path
@@ -31,14 +30,12 @@ from gamspy._options import (
31
30
  MODEL_ATTR_OPTION_MAP,
32
31
  ConvertOptions,
33
32
  FreezeOptions,
34
- ModelInstanceOptions,
35
33
  Options,
36
34
  )
37
35
  from gamspy.exceptions import GamspyException, ValidationError
38
36
 
39
37
  if TYPE_CHECKING:
40
- import io
41
- from typing import Literal
38
+ from typing import Literal, TextIO
42
39
 
43
40
  import pandas as pd
44
41
 
@@ -1307,10 +1304,9 @@ class Model:
1307
1304
  self,
1308
1305
  solver: str | None = None,
1309
1306
  options: Options | None = None,
1310
- solver_options: dict | None = None,
1311
- model_instance_options: ModelInstanceOptions | None = None,
1307
+ solver_options: dict | str | Path | None = None,
1312
1308
  freeze_options: FreezeOptions | None = None,
1313
- output: io.TextIOWrapper | None = None,
1309
+ output: TextIO | None = None,
1314
1310
  backend: Literal["local", "engine", "neos"] = "local",
1315
1311
  client: EngineClient | NeosClient | None = None,
1316
1312
  load_symbols: list[Symbol] | None = None,
@@ -1324,14 +1320,12 @@ class Model:
1324
1320
  Solver name
1325
1321
  options : Options, optional
1326
1322
  GAMSPy options.
1327
- solver_options : dict, optional
1328
- Solver options.
1329
- model_instance_options : ModelInstanceOptions, optional
1330
- Options to solve a frozen model. This argument will be deprecated in GAMSPy 1.10.0. Please use freeze_options instead.
1323
+ solver_options : dict | str | Path, optional
1324
+ Dictionary of solver options or path to an existing option file.
1331
1325
  freeze_options : FreezeOptions, optional
1332
1326
  Options to solve a frozen model.
1333
- output : TextIOWrapper, optional
1334
- Output redirection target
1327
+ output : TextIO, optional
1328
+ Output redirection target. Set this to sys.stdout to see the output in your terminal.
1335
1329
  backend : str, optional
1336
1330
  Backend to run on
1337
1331
  client : EngineClient, NeosClient, optional
@@ -1395,21 +1389,20 @@ class Model:
1395
1389
  if options is None:
1396
1390
  options = self.container._options
1397
1391
 
1392
+ if isinstance(solver_options, (str, Path)):
1393
+ solver_options = Path(solver_options)
1394
+
1398
1395
  # Only for local until GAMS Engine and NEOS Server backends adopt the new GP_SolveLine option.
1399
1396
  if backend == "local":
1400
1397
  frame = inspect.currentframe().f_back
1401
1398
  assert isinstance(options, gp.Options)
1402
1399
  options._frame = frame
1403
1400
 
1401
+ if solver_options is not None:
1402
+ self.container.writeSolverOptions(solver, solver_options)
1403
+
1404
1404
  if self._is_frozen:
1405
1405
  instance_options = FreezeOptions()
1406
- if model_instance_options is not None:
1407
- warnings.warn(
1408
- "model_instance_options will be renamed to freeze_options in GAMSPy 1.10.0",
1409
- DeprecationWarning,
1410
- stacklevel=2,
1411
- )
1412
- instance_options = model_instance_options
1413
1406
 
1414
1407
  if freeze_options is not None:
1415
1408
  instance_options = freeze_options
@@ -1423,12 +1416,7 @@ class Model:
1423
1416
  self._add_runtime_options(options, backend)
1424
1417
  self.container._add_statement(self._generate_solve_string() + "\n")
1425
1418
  self._create_model_attributes()
1426
- options._set_solver_options(
1427
- container=self.container,
1428
- solver=solver,
1429
- problem=self.problem,
1430
- solver_options=solver_options,
1431
- )
1419
+ options._set_model_info(solver, self.problem, solver_options)
1432
1420
 
1433
1421
  runner = backend_factory(
1434
1422
  self.container,
gamspy/_model_instance.py CHANGED
@@ -59,6 +59,7 @@ from gams.core.gmo import (
59
59
  import gamspy as gp
60
60
  import gamspy._symbols.implicits as implicits
61
61
  import gamspy.utils as utils
62
+ from gamspy._communication import send_job
62
63
  from gamspy._database import (
63
64
  Database,
64
65
  GamsEquation,
@@ -73,6 +74,7 @@ from gamspy.exceptions import (
73
74
 
74
75
  if TYPE_CHECKING:
75
76
  import io
77
+ from pathlib import Path
76
78
 
77
79
  from gamspy import Container, Model, Parameter
78
80
  from gamspy._options import FreezeOptions, Options
@@ -324,7 +326,9 @@ class ModelInstance:
324
326
  # Run
325
327
  try:
326
328
  self.container._job = self.job_name
327
- self.container._send_job(self.job_name, self.pf_file, self.output)
329
+ send_job(
330
+ self.container._comm_pair_id, self.job_name, self.pf_file, self.output
331
+ )
328
332
  except GamspyException as exception:
329
333
  self.container._workspace._errors.append(str(exception))
330
334
  message = _customize_exception(
@@ -354,14 +358,11 @@ class ModelInstance:
354
358
  self,
355
359
  solver: str,
356
360
  instance_options: FreezeOptions,
357
- solver_options: dict | None,
361
+ solver_options: dict | Path | None,
358
362
  output: io.TextIOWrapper | None,
359
363
  ) -> pd.DataFrame:
360
364
  # write solver options file
361
- option_file = 0
362
- if solver_options:
363
- self.container.writeSolverOptions(solver, solver_options)
364
- option_file = 1
365
+ option_file = 1 if solver_options else 0
365
366
 
366
367
  names_to_write = []
367
368
  for symbol in self.modifiables:
gamspy/_options.py CHANGED
@@ -13,7 +13,6 @@ if TYPE_CHECKING:
13
13
  import io
14
14
  from types import FrameType
15
15
 
16
- from gamspy import Container
17
16
  from gamspy._model import Problem
18
17
 
19
18
  SOLVE_LINK_MAP = {"disk": 2, "memory": 5}
@@ -482,19 +481,14 @@ class Options(BaseModel):
482
481
 
483
482
  return gams_options
484
483
 
485
- def _set_solver_options(
486
- self,
487
- container: Container,
488
- solver: str,
489
- problem: Problem,
490
- solver_options: dict | None,
484
+ def _set_model_info(
485
+ self, solver: str, problem: Problem, solver_options: dict | Path | None
491
486
  ):
492
487
  """Set the solver and the solver options"""
493
488
  self._solver = solver
494
489
  self._problem = str(problem)
495
490
 
496
491
  if solver_options:
497
- container.writeSolverOptions(solver, solver_options)
498
492
  self._solver_options_file = "1"
499
493
  else:
500
494
  self._solver_options_file = "0"
gamspy/_symbols/alias.py CHANGED
@@ -88,9 +88,6 @@ class Alias(gt.Alias, operable.Operable, Symbol, SetMixin):
88
88
  f"Container must of type `Container` but found {type(container)}"
89
89
  )
90
90
 
91
- if not isinstance(alias_with, (gp.Set, gp.Alias)):
92
- raise TypeError(f"alias_with must be a Set but found {type(alias_with)}")
93
-
94
91
  if name is None:
95
92
  return object.__new__(cls)
96
93
  else:
gamspy/utils.py CHANGED
@@ -492,11 +492,6 @@ def _get_license_path(system_directory: str) -> str:
492
492
  if os.path.exists(gamspy_license_path):
493
493
  return gamspy_license_path
494
494
 
495
- # Check old license installation path.
496
- user_license_path = os.path.join(system_directory, "user_license.txt")
497
- if os.path.exists(user_license_path):
498
- return user_license_path
499
-
500
495
  # No preinstalled licenses on the machine. Use the demo license.
501
496
  return os.path.join(system_directory, "gamslice.txt")
502
497
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gamspy
3
- Version: 1.17.2
3
+ Version: 1.18.1
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<52.0.0,>=51.1.0
35
- Requires-Dist: gamspy_base<52.0.0,>=51.1.0
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
@@ -1,21 +1,22 @@
1
1
  gamspy/__init__.py,sha256=HZulKBgt2ubfX6r6v6Xv3MJVI6Prm40j5W45aUetQok,1797
2
2
  gamspy/__main__.py,sha256=3HW3NYEe4pZnGhJ10s1G0vaZAkI9Zl_lXqHT9iDNXQg,108
3
+ gamspy/_communication.py,sha256=18drDsKxi1Iv6oAKZfKGJcPYYWebBQsyeOxiEKXZngE,7148
3
4
  gamspy/_config.py,sha256=n7RXUFczIlol15-mxhnj54fHXHet8HcOnWiGm3mpMgg,3589
4
- gamspy/_container.py,sha256=6dntB1rp0jl0a_JzJkCXPPqCuBTCmcOZmgePoht0QDY,52384
5
- gamspy/_convert.py,sha256=tnD-EJbffSUdWuo0TnlNOz_p6hGgVXT5m7kv8XFQtMQ,22232
5
+ gamspy/_container.py,sha256=w5zftBCrgFSkZEOgwh2zyFjiDA4AohzNHi-BuF0Hjas,45884
6
+ gamspy/_convert.py,sha256=z_ju0T2duAAeYE0ahW7E2GMdMMVZsQ5936HV583hXzs,22294
6
7
  gamspy/_database.py,sha256=SVo4lEfS0Q2f80uMu7k71BBQNRwUei-2KFc_EGxsOcw,6873
7
8
  gamspy/_extrinsic.py,sha256=6pBam_ruzKaYjEpALjAQ5d9uPrlOf1lVMYjRYqHH8o0,4337
8
9
  gamspy/_miro.py,sha256=MeFGcaFtNgtydWjH0KS0l9y3mNy7tPyXwm43jwomodg,12245
9
- gamspy/_model.py,sha256=9JqevkvCsMnZBjQsFQjOg167nN-3TuyHxvcB1xOea5U,51787
10
- gamspy/_model_instance.py,sha256=kVDKOMhLIcRPn5KYNnqWsfSGo5nhUyZimzylwrpYi6g,24361
11
- gamspy/_options.py,sha256=VFOdyMDp6K6kZ0ZWzWiwx3DAg0dSmMda7FFBAoMVSU4,27361
10
+ gamspy/_model.py,sha256=Qd8JdDDUUSjWXQUKGR4N0-2J_PHnzh8sjSG3U7A4aKk,51359
11
+ gamspy/_model_instance.py,sha256=ZHG3-inc6vDx7F2Oo9HYWYAi8hki6upIgAQn5II_o1U,24384
12
+ gamspy/_options.py,sha256=FTty65WRdikvnhPyWSTuwuUz0Rs6aZwDFWL-UsfEgd0,27211
12
13
  gamspy/_serialization.py,sha256=fwAQpABCZQyxtAkddsvCDjtyQyPUT3EJy19r9BKR6aY,4062
13
14
  gamspy/_types.py,sha256=_KQjRntvIAicc9_sZ5BYWAHjs-WBURf2NJwTpJQT5xw,162
14
15
  gamspy/_validation.py,sha256=k4e8M8X32hEFybJyISyXLNrzO1mmWXQPnSThT7rOC5U,21608
15
16
  gamspy/_workspace.py,sha256=QrnC8aPN5C9zyqbaYvfuPlRHvBrDx_i4Imw1AzsJs7k,2175
16
17
  gamspy/exceptions.py,sha256=q-Cb6syXv1abf2JBfNFidGkvOWkp9FXU5NzJwkR5W7o,2929
17
18
  gamspy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- gamspy/utils.py,sha256=3JgNFx4jyk9XTEcLzR513H7Ah5M-Lce2OsUPjyyRhnM,19971
19
+ gamspy/utils.py,sha256=6eO4R0iBLRm2oTRCz_croiQhKpiIyF8bOa0NDfw9aUU,19777
19
20
  gamspy/version.py,sha256=GsqTw9rQ4Zz80ZSlIo44vYqgcVBJSbGOqJFJW3j_z88,109
20
21
  gamspy/_algebra/__init__.py,sha256=HdTHRBzfOGXihibQOAOAHGHKinlMuHVnpuP4pJWSQCc,479
21
22
  gamspy/_algebra/condition.py,sha256=mVJeCk65dvLgYalTQHfZt0bEFFf9yoTWLXjhODf7gvg,6229
@@ -25,23 +26,23 @@ gamspy/_algebra/number.py,sha256=Yf7kYLwkmXCipiAzQP7LtTchMMK7OccChuXnTIYl1w4,168
25
26
  gamspy/_algebra/operable.py,sha256=1sFr29_7A9RKVxDlTY9fC_g4sNRavNOYpjACWzvyjRc,6315
26
27
  gamspy/_algebra/operation.py,sha256=e3gYxS5OPh8JYGAnAS1wEpzafFpfBt0UbcL4PKZyoY0,22990
27
28
  gamspy/_backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- gamspy/_backend/backend.py,sha256=2tn9Oa-uJSBbXwOllrvc9zBl-EMEVRfsW8pqClkhOes,9174
29
- gamspy/_backend/engine.py,sha256=ajRKhJXs9f7_1oIhNJWZINQn0rlhphxXZBkG6nryfYw,31718
30
- gamspy/_backend/local.py,sha256=vZSC6GAn-xMe3h4Pl4c3HiCtwAgTcurNGYVZT3u4wAY,3681
31
- gamspy/_backend/neos.py,sha256=J78_AJ1527LRGVx43XWrykKBTVZWKyvrlZfN4PdaA0s,17089
29
+ gamspy/_backend/backend.py,sha256=VgLL3CiFydkf1ZF8ttaOlWsDtkJIoRdi_md4tVh789Q,9217
30
+ gamspy/_backend/engine.py,sha256=N3BIb4CDmH76CQIj87JSJBNnMLln3jsUuLy9_O5sEJ0,31826
31
+ gamspy/_backend/local.py,sha256=-LE7obmZT3zdB-0LJHJ9xfulMBw5vvzFIS9WJ7ISE-4,3804
32
+ gamspy/_backend/neos.py,sha256=I21TOqPZC5cBp_rQpz5S9cmLPRJcizIT0KuncwMcUrw,17762
32
33
  gamspy/_cli/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
33
34
  gamspy/_cli/cli.py,sha256=a4AtD-VI5jk0ZcO0JAwD0f0KJRwmxA8MzR_2MmhPWpg,1580
34
35
  gamspy/_cli/gdx.py,sha256=YRu1delsmDh-dpzEr8n1kTK0JFdXlNqz4YuUQzK1mvo,12024
35
- gamspy/_cli/install.py,sha256=2t2QhcyKW3xICoastrYEzsEZs19_T7mHOq6uTb3n2J4,12862
36
+ gamspy/_cli/install.py,sha256=Kr09kp146LujiO64GpoJxMPRZmY9GnhxuhWm-ZU2l4w,13257
36
37
  gamspy/_cli/list.py,sha256=fMmaIZcZf-Tf_-J37tJ4zrk2bcVO9cPSfs08mQyGWiY,2907
37
38
  gamspy/_cli/probe.py,sha256=K7fYwfdk-2mGGQKAxXFvkWMRJ1qYTUpX2tvklU56JZU,1122
38
- gamspy/_cli/retrieve.py,sha256=UjB0fvlxJ9s7RgiO3wD7fRpcFHGt0c7veLA-JKeTYSY,2254
39
- gamspy/_cli/run.py,sha256=iWvH8eiXzNempMMx-nlVIQpHOpUA6dot6XnZFI7ToWg,4076
39
+ gamspy/_cli/retrieve.py,sha256=bnTcIbHJfkq-dvrFitdkxsjQNZz-Cx4-LiaRfhe0cYM,2446
40
+ gamspy/_cli/run.py,sha256=oaWT4alxSXqPDF2mRYmfy5aOWCKoEExUfqYoAT_zSUM,4387
40
41
  gamspy/_cli/show.py,sha256=NTZJBtdns0Bd6UOdlTDIelhU92xmLJjT9HuDLqS3YJ4,11144
41
- gamspy/_cli/uninstall.py,sha256=k7IE3QVN_2vDkCcEB9JXEVMj7aM6DsUwG6-_oMJER7E,4879
42
+ gamspy/_cli/uninstall.py,sha256=_g3SbT6G7DZcYgtmHLhaGl1B9jY1K3D0sStifxIyqxQ,5183
42
43
  gamspy/_cli/util.py,sha256=ey3Wn7tCEggkr-sjcLqrVBda-jdadjWOn-APhSe4toE,2849
43
44
  gamspy/_symbols/__init__.py,sha256=p0Zd7XytuOe8kRclN1lFrRFaN_btFSSGdIBSXrlp03E,450
44
- gamspy/_symbols/alias.py,sha256=_7dldyfwORBAvC1yEia6Dabe3lsERyW01tARlgFKLWc,8941
45
+ gamspy/_symbols/alias.py,sha256=lIoNynvLd-tfXrVBs8Asy0TEkhs-xkTeNl4yLtzehiA,8795
45
46
  gamspy/_symbols/equation.py,sha256=W_8eQdZ9Mou9YVI1l0X_gwFB--TVNOK02OGpWmO3Xek,39719
46
47
  gamspy/_symbols/parameter.py,sha256=i5UWz8KQEHDBOjGV7JmL8GhnLnL-CiFMUhfSrBZOqQs,19721
47
48
  gamspy/_symbols/set.py,sha256=j3Izvgs470xiauwCbgXZOelZy-KAzslDGXuKeHn_g3g,29130
@@ -80,9 +81,9 @@ gamspy/math/matrix.py,sha256=PmYsMhF8Pq3bcgtyINryq4xV36i4KW0BtcFApOvt8GQ,16365
80
81
  gamspy/math/misc.py,sha256=FaSRYFjMN6AULqOqGIF6_AXDw6KVqgxm2EtZsDcBFmE,37432
81
82
  gamspy/math/probability.py,sha256=gAVU_PTfbjXuN6i8XNjZpIYvFg8dPk1imZCbKQloSG0,4290
82
83
  gamspy/math/trigonometric.py,sha256=VBWQcP3ggl1oGDEzRs2M_T88HPJn9N0QfMJZwEUkx4w,4276
83
- gamspy-1.17.2.dist-info/licenses/LICENSE,sha256=D4HfHbHRs3LVWo3uXq44WQc1FkBP5srUCEa-vO0W9tE,1189
84
- gamspy-1.17.2.dist-info/METADATA,sha256=zdPLlDz6jBgk76rAJMWDQuips72scwv450wLgcAj17Y,5301
85
- gamspy-1.17.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
86
- gamspy-1.17.2.dist-info/entry_points.txt,sha256=tzgC7L0Y_BiyagBCaYOPK7lE4olRJKM_PH2inmMvj60,48
87
- gamspy-1.17.2.dist-info/top_level.txt,sha256=fsq4q5lfdb2GEZC9O3PUih38s7TfIgolIaO5NgR3Hf8,7
88
- gamspy-1.17.2.dist-info/RECORD,,
84
+ gamspy-1.18.1.dist-info/licenses/LICENSE,sha256=D4HfHbHRs3LVWo3uXq44WQc1FkBP5srUCEa-vO0W9tE,1189
85
+ gamspy-1.18.1.dist-info/METADATA,sha256=E7-RHCJZIutvTFYwaNd7NtgGD35BKwa-RV-IBFyoG5Y,5301
86
+ gamspy-1.18.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
87
+ gamspy-1.18.1.dist-info/entry_points.txt,sha256=tzgC7L0Y_BiyagBCaYOPK7lE4olRJKM_PH2inmMvj60,48
88
+ gamspy-1.18.1.dist-info/top_level.txt,sha256=fsq4q5lfdb2GEZC9O3PUih38s7TfIgolIaO5NgR3Hf8,7
89
+ gamspy-1.18.1.dist-info/RECORD,,