gamsapi 52.5.0__cp312-cp312-win_amd64.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.
- gams/__init__.py +27 -0
- gams/_version.py +1 -0
- gams/connect/__init__.py +28 -0
- gams/connect/agents/__init__.py +24 -0
- gams/connect/agents/_excel/__init__.py +32 -0
- gams/connect/agents/_excel/excelagent.py +312 -0
- gams/connect/agents/_excel/workbook.py +155 -0
- gams/connect/agents/_sqlconnectors/__init__.py +42 -0
- gams/connect/agents/_sqlconnectors/_accesshandler.py +211 -0
- gams/connect/agents/_sqlconnectors/_databasehandler.py +250 -0
- gams/connect/agents/_sqlconnectors/_mysqlhandler.py +168 -0
- gams/connect/agents/_sqlconnectors/_postgreshandler.py +131 -0
- gams/connect/agents/_sqlconnectors/_pyodbchandler.py +112 -0
- gams/connect/agents/_sqlconnectors/_sqlalchemyhandler.py +74 -0
- gams/connect/agents/_sqlconnectors/_sqlitehandler.py +262 -0
- gams/connect/agents/_sqlconnectors/_sqlserverhandler.py +179 -0
- gams/connect/agents/concatenate.py +440 -0
- gams/connect/agents/connectagent.py +743 -0
- gams/connect/agents/csvreader.py +675 -0
- gams/connect/agents/csvwriter.py +151 -0
- gams/connect/agents/domainwriter.py +143 -0
- gams/connect/agents/excelreader.py +756 -0
- gams/connect/agents/excelwriter.py +467 -0
- gams/connect/agents/filter.py +223 -0
- gams/connect/agents/gamsreader.py +112 -0
- gams/connect/agents/gamswriter.py +239 -0
- gams/connect/agents/gdxreader.py +109 -0
- gams/connect/agents/gdxwriter.py +146 -0
- gams/connect/agents/labelmanipulator.py +303 -0
- gams/connect/agents/projection.py +539 -0
- gams/connect/agents/pythoncode.py +71 -0
- gams/connect/agents/rawcsvreader.py +248 -0
- gams/connect/agents/rawexcelreader.py +312 -0
- gams/connect/agents/schema/CSVReader.yaml +92 -0
- gams/connect/agents/schema/CSVWriter.yaml +44 -0
- gams/connect/agents/schema/Concatenate.yaml +52 -0
- gams/connect/agents/schema/DomainWriter.yaml +25 -0
- gams/connect/agents/schema/ExcelReader.yaml +121 -0
- gams/connect/agents/schema/ExcelWriter.yaml +78 -0
- gams/connect/agents/schema/Filter.yaml +74 -0
- gams/connect/agents/schema/GAMSReader.yaml +20 -0
- gams/connect/agents/schema/GAMSWriter.yaml +47 -0
- gams/connect/agents/schema/GDXReader.yaml +23 -0
- gams/connect/agents/schema/GDXWriter.yaml +32 -0
- gams/connect/agents/schema/LabelManipulator.yaml +99 -0
- gams/connect/agents/schema/Projection.yaml +24 -0
- gams/connect/agents/schema/PythonCode.yaml +6 -0
- gams/connect/agents/schema/RawCSVReader.yaml +34 -0
- gams/connect/agents/schema/RawExcelReader.yaml +42 -0
- gams/connect/agents/schema/SQLReader.yaml +75 -0
- gams/connect/agents/schema/SQLWriter.yaml +103 -0
- gams/connect/agents/sqlreader.py +301 -0
- gams/connect/agents/sqlwriter.py +276 -0
- gams/connect/connectdatabase.py +275 -0
- gams/connect/connectvalidator.py +93 -0
- gams/connect/errors.py +34 -0
- gams/control/__init__.py +136 -0
- gams/control/database.py +2231 -0
- gams/control/execution.py +1900 -0
- gams/control/options.py +2792 -0
- gams/control/workspace.py +1198 -0
- gams/core/__init__.py +24 -0
- gams/core/cfg/__init__.py +26 -0
- gams/core/cfg/_cfgmcc.cp312-win_amd64.pyd +0 -0
- gams/core/cfg/cfgmcc.py +519 -0
- gams/core/dct/__init__.py +26 -0
- gams/core/dct/_dctmcc.cp312-win_amd64.pyd +0 -0
- gams/core/dct/dctmcc.py +574 -0
- gams/core/embedded/__init__.py +26 -0
- gams/core/embedded/gamsemb.py +1024 -0
- gams/core/emp/__init__.py +24 -0
- gams/core/emp/emplexer.py +89 -0
- gams/core/emp/empyacc.py +281 -0
- gams/core/gdx/__init__.py +26 -0
- gams/core/gdx/_gdxcc.cp312-win_amd64.pyd +0 -0
- gams/core/gdx/gdxcc.py +866 -0
- gams/core/gev/__init__.py +26 -0
- gams/core/gev/_gevmcc.cp312-win_amd64.pyd +0 -0
- gams/core/gev/gevmcc.py +855 -0
- gams/core/gmd/__init__.py +26 -0
- gams/core/gmd/_gmdcc.cp312-win_amd64.pyd +0 -0
- gams/core/gmd/gmdcc.py +917 -0
- gams/core/gmo/__init__.py +26 -0
- gams/core/gmo/_gmomcc.cp312-win_amd64.pyd +0 -0
- gams/core/gmo/gmomcc.py +2046 -0
- gams/core/idx/__init__.py +26 -0
- gams/core/idx/_idxcc.cp312-win_amd64.pyd +0 -0
- gams/core/idx/idxcc.py +510 -0
- gams/core/numpy/__init__.py +29 -0
- gams/core/numpy/_gams2numpy.cp312-win_amd64.pyd +0 -0
- gams/core/numpy/gams2numpy.py +1048 -0
- gams/core/opt/__init__.py +26 -0
- gams/core/opt/_optcc.cp312-win_amd64.pyd +0 -0
- gams/core/opt/optcc.py +840 -0
- gams/engine/__init__.py +204 -0
- gams/engine/api/__init__.py +13 -0
- gams/engine/api/auth_api.py +7653 -0
- gams/engine/api/cleanup_api.py +751 -0
- gams/engine/api/default_api.py +887 -0
- gams/engine/api/hypercube_api.py +2629 -0
- gams/engine/api/jobs_api.py +5229 -0
- gams/engine/api/licenses_api.py +2220 -0
- gams/engine/api/namespaces_api.py +7783 -0
- gams/engine/api/usage_api.py +5627 -0
- gams/engine/api/users_api.py +5931 -0
- gams/engine/api_client.py +804 -0
- gams/engine/api_response.py +21 -0
- gams/engine/configuration.py +601 -0
- gams/engine/exceptions.py +216 -0
- gams/engine/models/__init__.py +86 -0
- gams/engine/models/bad_input.py +89 -0
- gams/engine/models/cleanable_job_result.py +104 -0
- gams/engine/models/cleanable_job_result_page.py +113 -0
- gams/engine/models/engine_license.py +107 -0
- gams/engine/models/files_not_found.py +93 -0
- gams/engine/models/forwarded_token_response.py +112 -0
- gams/engine/models/generic_key_value_pair.py +89 -0
- gams/engine/models/hypercube.py +160 -0
- gams/engine/models/hypercube_page.py +111 -0
- gams/engine/models/hypercube_summary.py +91 -0
- gams/engine/models/hypercube_token.py +97 -0
- gams/engine/models/identity_provider.py +107 -0
- gams/engine/models/identity_provider_ldap.py +121 -0
- gams/engine/models/identity_provider_oauth2.py +146 -0
- gams/engine/models/identity_provider_oauth2_scope.py +89 -0
- gams/engine/models/identity_provider_oauth2_with_secret.py +152 -0
- gams/engine/models/identity_provider_oidc.py +133 -0
- gams/engine/models/identity_provider_oidc_with_secret.py +143 -0
- gams/engine/models/inex.py +91 -0
- gams/engine/models/invitation.py +136 -0
- gams/engine/models/invitation_quota.py +106 -0
- gams/engine/models/invitation_token.py +87 -0
- gams/engine/models/job.py +165 -0
- gams/engine/models/job_no_text_entry.py +138 -0
- gams/engine/models/job_no_text_entry_page.py +111 -0
- gams/engine/models/license.py +91 -0
- gams/engine/models/log_piece.py +96 -0
- gams/engine/models/message.py +87 -0
- gams/engine/models/message_and_token.py +99 -0
- gams/engine/models/message_with_webhook_id.py +89 -0
- gams/engine/models/model_auth_token.py +87 -0
- gams/engine/models/model_configuration.py +125 -0
- gams/engine/models/model_default_instance.py +99 -0
- gams/engine/models/model_default_user_instance.py +98 -0
- gams/engine/models/model_hypercube_job.py +106 -0
- gams/engine/models/model_hypercube_usage.py +130 -0
- gams/engine/models/model_instance_info.py +116 -0
- gams/engine/models/model_instance_info_full.py +123 -0
- gams/engine/models/model_instance_pool_info.py +112 -0
- gams/engine/models/model_job_labels.py +179 -0
- gams/engine/models/model_job_usage.py +133 -0
- gams/engine/models/model_pool_usage.py +124 -0
- gams/engine/models/model_usage.py +115 -0
- gams/engine/models/model_user.py +96 -0
- gams/engine/models/model_userinstance_info.py +119 -0
- gams/engine/models/model_userinstancepool_info.py +95 -0
- gams/engine/models/model_version.py +91 -0
- gams/engine/models/models.py +120 -0
- gams/engine/models/namespace.py +104 -0
- gams/engine/models/namespace_quota.py +96 -0
- gams/engine/models/namespace_with_permission.py +96 -0
- gams/engine/models/not_found.py +91 -0
- gams/engine/models/password_policy.py +97 -0
- gams/engine/models/perm_and_username.py +89 -0
- gams/engine/models/quota.py +117 -0
- gams/engine/models/quota_exceeded.py +97 -0
- gams/engine/models/status_code_meaning.py +89 -0
- gams/engine/models/stream_entry.py +89 -0
- gams/engine/models/system_wide_license.py +92 -0
- gams/engine/models/text_entries.py +87 -0
- gams/engine/models/text_entry.py +101 -0
- gams/engine/models/time_span.py +95 -0
- gams/engine/models/time_span_pool_worker.py +99 -0
- gams/engine/models/token_forward_error.py +87 -0
- gams/engine/models/user.py +127 -0
- gams/engine/models/user_group_member.py +96 -0
- gams/engine/models/user_groups.py +108 -0
- gams/engine/models/vapid_info.py +87 -0
- gams/engine/models/webhook.py +138 -0
- gams/engine/models/webhook_parameterized_event.py +99 -0
- gams/engine/py.typed +0 -0
- gams/engine/rest.py +258 -0
- gams/magic/__init__.py +32 -0
- gams/magic/gams_magic.py +142 -0
- gams/magic/interactive.py +402 -0
- gams/tools/__init__.py +30 -0
- gams/tools/errors.py +34 -0
- gams/tools/toolcollection/__init__.py +24 -0
- gams/tools/toolcollection/alg/__init__.py +24 -0
- gams/tools/toolcollection/alg/rank.py +51 -0
- gams/tools/toolcollection/data/__init__.py +24 -0
- gams/tools/toolcollection/data/csvread.py +444 -0
- gams/tools/toolcollection/data/csvwrite.py +311 -0
- gams/tools/toolcollection/data/exceldump.py +47 -0
- gams/tools/toolcollection/data/sqlitewrite.py +276 -0
- gams/tools/toolcollection/gdxservice/__init__.py +24 -0
- gams/tools/toolcollection/gdxservice/gdxencoding.py +104 -0
- gams/tools/toolcollection/gdxservice/gdxrename.py +94 -0
- gams/tools/toolcollection/linalg/__init__.py +24 -0
- gams/tools/toolcollection/linalg/cholesky.py +57 -0
- gams/tools/toolcollection/linalg/eigenvalue.py +56 -0
- gams/tools/toolcollection/linalg/eigenvector.py +58 -0
- gams/tools/toolcollection/linalg/invert.py +55 -0
- gams/tools/toolcollection/linalg/ols.py +138 -0
- gams/tools/toolcollection/tooltemplate.py +321 -0
- gams/tools/toolcollection/win32/__init__.py +24 -0
- gams/tools/toolcollection/win32/excelmerge.py +93 -0
- gams/tools/toolcollection/win32/exceltalk.py +76 -0
- gams/tools/toolcollection/win32/msappavail.py +49 -0
- gams/tools/toolcollection/win32/shellexecute.py +54 -0
- gams/tools/tools.py +116 -0
- gams/transfer/__init__.py +35 -0
- gams/transfer/_abcs/__init__.py +37 -0
- gams/transfer/_abcs/container_abcs.py +433 -0
- gams/transfer/_internals/__init__.py +63 -0
- gams/transfer/_internals/algorithms.py +436 -0
- gams/transfer/_internals/casepreservingdict.py +124 -0
- gams/transfer/_internals/constants.py +270 -0
- gams/transfer/_internals/domainviolation.py +103 -0
- gams/transfer/_internals/specialvalues.py +172 -0
- gams/transfer/containers/__init__.py +26 -0
- gams/transfer/containers/_container.py +1794 -0
- gams/transfer/containers/_io/__init__.py +28 -0
- gams/transfer/containers/_io/containers.py +164 -0
- gams/transfer/containers/_io/gdx.py +1029 -0
- gams/transfer/containers/_io/gmd.py +872 -0
- gams/transfer/containers/_mixins/__init__.py +26 -0
- gams/transfer/containers/_mixins/ccc.py +1274 -0
- gams/transfer/syms/__init__.py +33 -0
- gams/transfer/syms/_methods/__init__.py +24 -0
- gams/transfer/syms/_methods/tables.py +120 -0
- gams/transfer/syms/_methods/toDict.py +115 -0
- gams/transfer/syms/_methods/toList.py +83 -0
- gams/transfer/syms/_methods/toValue.py +60 -0
- gams/transfer/syms/_mixins/__init__.py +32 -0
- gams/transfer/syms/_mixins/equals.py +626 -0
- gams/transfer/syms/_mixins/generateRecords.py +499 -0
- gams/transfer/syms/_mixins/pivot.py +313 -0
- gams/transfer/syms/_mixins/pve.py +627 -0
- gams/transfer/syms/_mixins/sa.py +27 -0
- gams/transfer/syms/_mixins/sapve.py +27 -0
- gams/transfer/syms/_mixins/saua.py +27 -0
- gams/transfer/syms/_mixins/sauapve.py +199 -0
- gams/transfer/syms/_mixins/spve.py +1528 -0
- gams/transfer/syms/_mixins/ve.py +936 -0
- gams/transfer/syms/container_syms/__init__.py +31 -0
- gams/transfer/syms/container_syms/_alias.py +984 -0
- gams/transfer/syms/container_syms/_equation.py +333 -0
- gams/transfer/syms/container_syms/_parameter.py +973 -0
- gams/transfer/syms/container_syms/_set.py +604 -0
- gams/transfer/syms/container_syms/_universe_alias.py +461 -0
- gams/transfer/syms/container_syms/_variable.py +321 -0
- gamsapi-52.5.0.dist-info/METADATA +150 -0
- gamsapi-52.5.0.dist-info/RECORD +257 -0
- gamsapi-52.5.0.dist-info/WHEEL +5 -0
- gamsapi-52.5.0.dist-info/licenses/LICENSE +22 -0
- gamsapi-52.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1900 @@
|
|
|
1
|
+
#
|
|
2
|
+
# GAMS - General Algebraic Modeling System Python API
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2017-2026 GAMS Development Corp. <support@gams.com>
|
|
5
|
+
# Copyright (c) 2017-2026 GAMS Software GmbH <support@gams.com>
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# TODO: use from os import * to avoid that os is available automatically when importing gams!
|
|
28
|
+
from gams.core.opt import *
|
|
29
|
+
from gams.core.gmo import *
|
|
30
|
+
from gams.core.cfg import *
|
|
31
|
+
from gams.core.gev import *
|
|
32
|
+
from gams.control.options import *
|
|
33
|
+
from gams.control.database import *
|
|
34
|
+
import gams.control.workspace
|
|
35
|
+
|
|
36
|
+
import json
|
|
37
|
+
import os
|
|
38
|
+
import tempfile
|
|
39
|
+
import shutil
|
|
40
|
+
import subprocess
|
|
41
|
+
import sys
|
|
42
|
+
import time
|
|
43
|
+
import urllib.parse
|
|
44
|
+
import zipfile
|
|
45
|
+
import io
|
|
46
|
+
import base64
|
|
47
|
+
import copy
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# TODO: should we move these enum types to the __init__.py of the module?
|
|
51
|
+
## @brief What field to update
|
|
52
|
+
class UpdateAction(object):
|
|
53
|
+
## @brief Supplies upper bounds for a variable
|
|
54
|
+
Upper = 1
|
|
55
|
+
## @brief Supplies lower bounds for a variable
|
|
56
|
+
Lower = 2
|
|
57
|
+
## @brief Supplies fixed bounds for a variable
|
|
58
|
+
Fixed = 3
|
|
59
|
+
## @brief Supplies level for a variable or equation
|
|
60
|
+
Primal = 4
|
|
61
|
+
## @brief Supplies marginal for a variable or equation
|
|
62
|
+
Dual = 5
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## @brief Symbol update type
|
|
66
|
+
class SymbolUpdateType(object):
|
|
67
|
+
## @brief If record does not exist use 0 (Zero)
|
|
68
|
+
Zero = 0
|
|
69
|
+
## @brief If record does not exist use values from instantiation
|
|
70
|
+
BaseCase = 1
|
|
71
|
+
## @brief If record does not exist use value from previous solve
|
|
72
|
+
Accumulate = 2
|
|
73
|
+
_Inherit = 3
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class GamsEngineJob(object):
|
|
77
|
+
def __init__(self, token, configuration, request_headers):
|
|
78
|
+
"""
|
|
79
|
+
@brief Constructor
|
|
80
|
+
@param token Job token
|
|
81
|
+
@param configuration GamsEngineConfiguration instance used to submit job
|
|
82
|
+
"""
|
|
83
|
+
self._token = token
|
|
84
|
+
self._configuration = configuration
|
|
85
|
+
self._request_headers = request_headers
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class GamsEngineConfiguration(object):
|
|
89
|
+
"""
|
|
90
|
+
@brief Configuration that allows the execution of jobs on a specific GAMS Engine instance.
|
|
91
|
+
@details
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def get_host(self):
|
|
95
|
+
return self._host
|
|
96
|
+
|
|
97
|
+
def set_host(self, host):
|
|
98
|
+
validated_url = urllib.parse.urlparse(host)
|
|
99
|
+
if validated_url.scheme not in ["http", "https"]:
|
|
100
|
+
raise gams.control.workspace.GamsException(
|
|
101
|
+
"Invalid GAMS Engine host. Only HTTP and HTTPS protocols supported"
|
|
102
|
+
)
|
|
103
|
+
if validated_url.netloc == "":
|
|
104
|
+
raise gams.control.workspace.GamsException(
|
|
105
|
+
"Invalid GAMS Engine host. Make sure you provide a valid URL."
|
|
106
|
+
)
|
|
107
|
+
host = host.rstrip("/")
|
|
108
|
+
if not host.endswith("/api"):
|
|
109
|
+
host += "/api"
|
|
110
|
+
self._host = host
|
|
111
|
+
|
|
112
|
+
## @brief Base url
|
|
113
|
+
host = property(get_host, set_host)
|
|
114
|
+
|
|
115
|
+
def get_username(self):
|
|
116
|
+
return self._username
|
|
117
|
+
|
|
118
|
+
def set_username(self, username):
|
|
119
|
+
self._username = username
|
|
120
|
+
|
|
121
|
+
## @brief Username for HTTP basic authentication
|
|
122
|
+
username = property(get_username, set_username)
|
|
123
|
+
|
|
124
|
+
def get_password(self):
|
|
125
|
+
return self._password
|
|
126
|
+
|
|
127
|
+
def set_password(self, password):
|
|
128
|
+
self._password = password
|
|
129
|
+
|
|
130
|
+
## @brief Password for HTTP basic authentication
|
|
131
|
+
password = property(get_password, set_password)
|
|
132
|
+
|
|
133
|
+
def get_jwt(self):
|
|
134
|
+
return self._jwt
|
|
135
|
+
|
|
136
|
+
def set_jwt(self, jwt):
|
|
137
|
+
self._jwt = jwt
|
|
138
|
+
|
|
139
|
+
## @brief JWT token to use for Bearer authentication. Will only be used if username is empty.
|
|
140
|
+
jwt = property(get_jwt, set_jwt)
|
|
141
|
+
|
|
142
|
+
def get_namespace(self):
|
|
143
|
+
if not self._namespace:
|
|
144
|
+
raise gams.control.workspace.GamsException("No GAMS Engine namespace set.")
|
|
145
|
+
return self._namespace
|
|
146
|
+
|
|
147
|
+
def set_namespace(self, namespace):
|
|
148
|
+
self._namespace = namespace
|
|
149
|
+
|
|
150
|
+
## @brief Namespace in which the job is to be executed
|
|
151
|
+
namespace = property(get_namespace, set_namespace)
|
|
152
|
+
|
|
153
|
+
def _get_auth_header(self):
|
|
154
|
+
"""Returns authentication header"""
|
|
155
|
+
if not self.username:
|
|
156
|
+
if not self.jwt:
|
|
157
|
+
raise gams.control.workspace.GamsException(
|
|
158
|
+
"Neither username/password nor JWT token provided for authentication with the GAMS Engine instance."
|
|
159
|
+
)
|
|
160
|
+
return "Bearer " + self.jwt
|
|
161
|
+
return "Basic " + base64.b64encode(
|
|
162
|
+
(self.username + ":" + self.password).encode("utf-8")
|
|
163
|
+
).decode("utf-8")
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self, host=None, username=None, password=None, jwt=None, namespace=None
|
|
167
|
+
):
|
|
168
|
+
"""
|
|
169
|
+
@brief Constructor
|
|
170
|
+
@param host Base url
|
|
171
|
+
@param username Username for HTTP basic authentication
|
|
172
|
+
@param password Password for HTTP basic authentication
|
|
173
|
+
@param jwt JWT token to use for Bearer authentication. Will only be used if username is empty.
|
|
174
|
+
@param namespace Namespace in which the job is to be executed
|
|
175
|
+
"""
|
|
176
|
+
self._jwt = ""
|
|
177
|
+
self._username = ""
|
|
178
|
+
self._password = ""
|
|
179
|
+
self.set_host(host)
|
|
180
|
+
self.set_username(username)
|
|
181
|
+
self.set_password(password)
|
|
182
|
+
self.set_jwt(jwt)
|
|
183
|
+
self.set_namespace(namespace)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class GamsModifier(object):
|
|
187
|
+
"""
|
|
188
|
+
@brief Instances of this class are input to GamsModelInstance.instantiate()
|
|
189
|
+
@details A GamsModifier consists either
|
|
190
|
+
of a GamsParameter or a triple: A GamsVariable or GamsEquation to be modified, the modification
|
|
191
|
+
action (e.g. . Upper, Lower or Fixed for updating bounds of a variable, or Primal/Dual for updating
|
|
192
|
+
the level/marginal of a variable or equation mainly used for starting non-linear models from different
|
|
193
|
+
starting points), and a GamsParameter that holds the data for modification. In addition the UpdateType
|
|
194
|
+
can be defined (if ommitted the type defined in the Solve call is used).
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def get_gams_symbol(self):
|
|
198
|
+
return self._gams_symbol
|
|
199
|
+
|
|
200
|
+
## @brief Symbol in the GAMS model to be modified
|
|
201
|
+
gams_symbol = property(get_gams_symbol)
|
|
202
|
+
|
|
203
|
+
def get_update_action(self):
|
|
204
|
+
return self._update_action
|
|
205
|
+
|
|
206
|
+
## @brief Type of modification
|
|
207
|
+
update_action = property(get_update_action)
|
|
208
|
+
|
|
209
|
+
def get_data_symbol(self):
|
|
210
|
+
return self._data_symbol
|
|
211
|
+
|
|
212
|
+
## @brief Symbol containing the data for the modification
|
|
213
|
+
data_symbol = property(get_data_symbol)
|
|
214
|
+
|
|
215
|
+
def get_update_type(self):
|
|
216
|
+
return self._update_type
|
|
217
|
+
|
|
218
|
+
## @brief Symbol Update Type
|
|
219
|
+
update_type = property(get_update_type)
|
|
220
|
+
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
gams_symbol,
|
|
224
|
+
update_action=None,
|
|
225
|
+
data_symbol=None,
|
|
226
|
+
update_type=SymbolUpdateType._Inherit,
|
|
227
|
+
):
|
|
228
|
+
"""
|
|
229
|
+
@brief Constructor
|
|
230
|
+
@param gams_symbol Symbol in the GAMS model to be modified.
|
|
231
|
+
This can be a GamsParameter, GamsVariable or GamsEquation.
|
|
232
|
+
If a variable or an equation is specified a data_symbol and
|
|
233
|
+
an update_action have to be specified as well.
|
|
234
|
+
@param update_action Modification action
|
|
235
|
+
@param data_symbol Parameter containing the data for the modification
|
|
236
|
+
@param update_type Symbol Update Type (default: Inherit from model instance)
|
|
237
|
+
"""
|
|
238
|
+
self._update_action = None
|
|
239
|
+
# update_action and data_symbol specified
|
|
240
|
+
if update_action and data_symbol != None:
|
|
241
|
+
if gams_symbol._dim != data_symbol._dim:
|
|
242
|
+
raise gams.control.workspace.GamsException(
|
|
243
|
+
"GAMS Symbol and Data must have same dimension, but saw "
|
|
244
|
+
+ gams_symbol._dim
|
|
245
|
+
+ " (GAMS Symbol) and "
|
|
246
|
+
+ data_symbol._dim
|
|
247
|
+
+ " (Data)"
|
|
248
|
+
)
|
|
249
|
+
if gams_symbol._database != data_symbol._database:
|
|
250
|
+
raise gams.control.workspace.GamsException(
|
|
251
|
+
"GAMS Symbol and Data must belong to same GAMSDatabase"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
update_action == UpdateAction.Upper
|
|
256
|
+
or update_action == UpdateAction.Lower
|
|
257
|
+
or update_action == UpdateAction.Fixed
|
|
258
|
+
):
|
|
259
|
+
if not isinstance(gams_symbol, GamsVariable):
|
|
260
|
+
# TODO: thiswill just print the interger of the constant
|
|
261
|
+
raise gams.control.workspace.GamsException(
|
|
262
|
+
"GAMS Symbol must be GAMSVariable for " + update_action
|
|
263
|
+
)
|
|
264
|
+
elif (
|
|
265
|
+
update_action == UpdateAction.Primal
|
|
266
|
+
or update_action == UpdateAction.Dual
|
|
267
|
+
):
|
|
268
|
+
if not (
|
|
269
|
+
isinstance(gams_symbol, GamsVariable)
|
|
270
|
+
or isinstance(gams_symbol, GamsEquation)
|
|
271
|
+
):
|
|
272
|
+
raise gams.control.workspace.GamsException(
|
|
273
|
+
"GAMS Symbol must be GAMSVariable or GAMSEquation for "
|
|
274
|
+
+ update_action
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
raise gams.control.workspace.GamsException(
|
|
278
|
+
"Unknown update action " + update_action
|
|
279
|
+
)
|
|
280
|
+
self._gams_symbol = gams_symbol
|
|
281
|
+
self._update_action = update_action
|
|
282
|
+
self._data_symbol = data_symbol
|
|
283
|
+
# only the gams_symbol is specified
|
|
284
|
+
elif update_action == None and data_symbol == None:
|
|
285
|
+
self._gams_symbol = gams_symbol
|
|
286
|
+
self._data_symbol = None
|
|
287
|
+
else:
|
|
288
|
+
raise gams.control.workspace.GamsException(
|
|
289
|
+
"Wrong combination of parameters. Specifying only update_action or data_symbol is not allowed."
|
|
290
|
+
)
|
|
291
|
+
self._update_type = update_type
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
## @brief The GamsModelInstanceOpt can be used to customize the GamsModelInstance.solve() routine.
|
|
295
|
+
class GamsModelInstanceOpt:
|
|
296
|
+
def __init__(self, solver=None, opt_file=-1, no_match_limit=0, debug=False):
|
|
297
|
+
"""
|
|
298
|
+
@brief Constructor
|
|
299
|
+
@param solver GAMS Solver
|
|
300
|
+
@param opt_file GAMS option file number
|
|
301
|
+
@param no_match_limit Controls the maximum number of accepted unmatched scenario records before terminating the solve
|
|
302
|
+
@param debug Debug Flag
|
|
303
|
+
"""
|
|
304
|
+
## @brief GAMS Solver
|
|
305
|
+
self.solver = solver
|
|
306
|
+
## @brief GAMS Optionfile number
|
|
307
|
+
self.opt_file = opt_file
|
|
308
|
+
## @brief Controls the maximum number of accepted unmatched scenario records before terminating the solve
|
|
309
|
+
self.no_match_limit = no_match_limit
|
|
310
|
+
## @brief Debug Flag
|
|
311
|
+
self.debug = debug
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class GamsModelInstance(object):
|
|
315
|
+
"""
|
|
316
|
+
@brief
|
|
317
|
+
@details <p>The GamsJob class is the standard way of dealing with a GAMS model and the
|
|
318
|
+
corresponding solution provided by a solver. The GAMS language provides
|
|
319
|
+
programming flow that allows to solve models in a loop and do other
|
|
320
|
+
sophisticated tasks, like building decomposition algorithms.</p>
|
|
321
|
+
<p>In rare cases, the GAMS model generation time dominates the solver solution time
|
|
322
|
+
and GAMS itself becomes the bottleneck in an optimization application. For a
|
|
323
|
+
model instance which is a single mathematical model generated by a GAMS solve
|
|
324
|
+
statement, the GamsModelInstance class provides a controlled way of modifying a
|
|
325
|
+
model instance and solving the resulting problem in the most efficient way, by
|
|
326
|
+
communicating only the changes of the model to the solver and doing a hot start
|
|
327
|
+
(in case of a continuous model like LP) without the use of disk IO.</p>
|
|
328
|
+
<p>The GamsModelInstance requires a GamsCheckpoint that contains the model
|
|
329
|
+
definition. Significant parts of the GAMS solve need to be provided for the
|
|
330
|
+
instantiation of the GamsModelInstance. The modification of the model instance is
|
|
331
|
+
done through data in sync_db (a property of GamsModelInstance of type GamsDatabase).
|
|
332
|
+
One needs to create GamsModifiers which contain the information on how to modify
|
|
333
|
+
the GamsModelInstance. Such a GamsModifier consists either of a GamsParameter or
|
|
334
|
+
of a triple with the GamsVariable or GamsEquation to be updated, the modification
|
|
335
|
+
action (e.g. Upper, Lower or Fixed for updating bounds of a variable, or Primal/Dual
|
|
336
|
+
for updating the level/marginal of a variable or equation mainly used for starting
|
|
337
|
+
non-linear models from different starting points), and a GamsParameter that holds
|
|
338
|
+
the data for modification. GamsSymbols of a GamsModifier must belong to sync_db.
|
|
339
|
+
The list of GamsModifiers needs to be supplied on the instantiate call. The use of
|
|
340
|
+
GamsParameters that are GamsModifiers is restricted in the GAMS model source. For
|
|
341
|
+
example, the parameter cannot be used inside $(). Such parameters become endogenous
|
|
342
|
+
to the model and will be treated by the GAMS compiler as such. Moreover, the rim of
|
|
343
|
+
the model instance is fixed: No addition of variables and equations is possible.</p>
|
|
344
|
+
<p>The instantiate call will only query the symbol information of the GamsModifiers,
|
|
345
|
+
not the data of sync_db, e.g. to retrieve the dimension of the modifiers. That's why
|
|
346
|
+
the modifier symbols have to exist (but don't have to have data) in sync_db when
|
|
347
|
+
instantiate is called. The GamsParameters that contain the update data in sync_db can
|
|
348
|
+
be filled at any time before executing the solve method. The solve method uses this
|
|
349
|
+
data to update the model instance. The solve method will iterate through all records
|
|
350
|
+
of modifier symbols in the model instance and try to find update data in sync_db. If
|
|
351
|
+
a record in sync_db is found, this data record will be copied into the model instance.
|
|
352
|
+
If no corresponding record is found in SyncDB there are different choices: 1) the
|
|
353
|
+
original data record is restored (update_type=SymbolUpdateType.BaseCase) which is the default, 2) the
|
|
354
|
+
default record of a GamsParameter (which is 0) is used (update_type=SymbolUpdateType.Zero, and 3) no
|
|
355
|
+
copy takes place and we use the previously copied record value (update_type=SymbolUpdateType.Accumulate).
|
|
356
|
+
After the model instance has been updated, the model is passed to the selected solver.</p>
|
|
357
|
+
<p>After the completion of the Solve method, the sync_db will contain the primal and
|
|
358
|
+
dual solution of the model just solved. Moreover, the GamsParameters that are
|
|
359
|
+
GamsModifiers are also accessible in sync_db as GamsVariables with the name of the
|
|
360
|
+
GamsParameter plus "_var". The Marginal of this GamsVariable can provide sensitivity
|
|
361
|
+
information about the parameter setting. The status of the solve is accessible through
|
|
362
|
+
the model_status and solver_status properties.</p>
|
|
363
|
+
<p>In general, file operations in GAMS Python API take place in the working_directory
|
|
364
|
+
defined in the GamsWorkspace. Exceptions to this rule are files read or created
|
|
365
|
+
due to solver specific options in the solve routine of GamsModelInstance. These files
|
|
366
|
+
are written to (or read from) the current directory, meaning the directory where
|
|
367
|
+
the application gets executed.</p>
|
|
368
|
+
|
|
369
|
+
Example on how to create a GAMSModelInstance from a GAMSCheckpoint that was
|
|
370
|
+
generated by the Run method of GAMSJob.
|
|
371
|
+
@code{.py}
|
|
372
|
+
ws = GamsWorkspace()
|
|
373
|
+
cp = ws.add_checkpoint()
|
|
374
|
+
|
|
375
|
+
ws.gamslib("trnsport")
|
|
376
|
+
|
|
377
|
+
job = ws.add_job_from_file("trnsport.gms")
|
|
378
|
+
job.run(checkpoint=cp)
|
|
379
|
+
|
|
380
|
+
mi = cp.add_modelinstance()
|
|
381
|
+
b = mi.sync_db.add_parameter("b", 1, "demand")
|
|
382
|
+
|
|
383
|
+
mi.instantiate("transport us lp min z", GamsModifier(b))
|
|
384
|
+
|
|
385
|
+
bmult = [ 0.7, 0.9, 1.1, 1.3 ]
|
|
386
|
+
for bm in bmult:
|
|
387
|
+
b.clear()
|
|
388
|
+
for rec in job.out_db.get_parameter("b"):
|
|
389
|
+
b.add_record(rec.keys).value = rec.value * bm
|
|
390
|
+
mi.solve()
|
|
391
|
+
print("Scenario bmult=" + str(bm) + ":")
|
|
392
|
+
print(" Modelstatus: " + str(mi.model_status))
|
|
393
|
+
print(" Solvestatus: " + str(mi.solver_status))
|
|
394
|
+
print(" Obj: " + str(mi.sync_db.get_variable("z")[()].level))
|
|
395
|
+
@endcode
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
# TODO: this will just return the integer values. In C# we receive some enum item type.
|
|
399
|
+
def get_model_status(self):
|
|
400
|
+
return gmoModelStat(self._gmo)
|
|
401
|
+
|
|
402
|
+
## @brief Status of the model. (available after a solve)
|
|
403
|
+
model_status = property(get_model_status)
|
|
404
|
+
|
|
405
|
+
def get_solver_status(self):
|
|
406
|
+
return gmoSolveStat(self._gmo)
|
|
407
|
+
|
|
408
|
+
## @brief Solve status of the model. (available after a solve)
|
|
409
|
+
solver_status = property(get_solver_status)
|
|
410
|
+
|
|
411
|
+
def get_checkpoint(self):
|
|
412
|
+
return self._checkpoint
|
|
413
|
+
|
|
414
|
+
## @brief Retrieve GamsCheckpoint
|
|
415
|
+
checkpoint = property(get_checkpoint)
|
|
416
|
+
|
|
417
|
+
def get_name(self):
|
|
418
|
+
return self._modelinstance_name
|
|
419
|
+
|
|
420
|
+
## @brief Retrieve name of GamsModelInstance
|
|
421
|
+
name = property(get_name)
|
|
422
|
+
|
|
423
|
+
def get_sync_db(self):
|
|
424
|
+
return self._sync_db
|
|
425
|
+
|
|
426
|
+
## @brief Retrieve GamsDatabase used to synchronize modifiable data
|
|
427
|
+
sync_db = property(get_sync_db)
|
|
428
|
+
|
|
429
|
+
def __init__(self, checkpoint=None, modelinstance_name=None, source=None):
|
|
430
|
+
"""
|
|
431
|
+
@brief Constructor
|
|
432
|
+
@param checkpoint GamsCheckpoint
|
|
433
|
+
@param modelinstance_name Identifier of GamsModelInstance (determined automatically if omitted)
|
|
434
|
+
@param source model instance to be copied
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
self._gmo = None
|
|
438
|
+
self._gev = None
|
|
439
|
+
# copy constructor
|
|
440
|
+
if source:
|
|
441
|
+
self._checkpoint = source._checkpoint
|
|
442
|
+
if not source._instantiated:
|
|
443
|
+
raise gams.control.workspace.GamsException(
|
|
444
|
+
"Source GamsModelInstance not instantiated, cannot copy from it"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
if not modelinstance_name:
|
|
448
|
+
self._modelinstance_name = (
|
|
449
|
+
source._checkpoint.workspace._modelinstance_add()
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
if not source._checkpoint.workspace._modelinstance_add(
|
|
453
|
+
modelinstance_name
|
|
454
|
+
):
|
|
455
|
+
raise gams.control.workspace.GamsException(
|
|
456
|
+
"ModelInstance with name "
|
|
457
|
+
+ modelinstance_name
|
|
458
|
+
+ " already exists"
|
|
459
|
+
)
|
|
460
|
+
self._modelinstance_name = modelinstance_name
|
|
461
|
+
source._checkpoint._workspace._debug_out(
|
|
462
|
+
"---- Entering GamsModelInstance constructor ----", 0
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
self._scr_dir = os.path.join(
|
|
466
|
+
self._checkpoint.workspace.working_directory, self._modelinstance_name
|
|
467
|
+
)
|
|
468
|
+
if os.path.exists(self._scr_dir):
|
|
469
|
+
try:
|
|
470
|
+
shutil.rmtree(self._scr_dir, True)
|
|
471
|
+
except:
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
self._sync_db = self._checkpoint.workspace.add_database(
|
|
475
|
+
source_database=source.sync_db
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
self._gev = new_gevHandle_tp()
|
|
479
|
+
ret = gevCreateD(
|
|
480
|
+
self._gev, self._checkpoint._workspace._system_directory, GMS_SSSIZE
|
|
481
|
+
)
|
|
482
|
+
if not ret[0]:
|
|
483
|
+
raise gams.control.workspace.GamsException(ret[1])
|
|
484
|
+
self._checkpoint._workspace._gevHandles.append(self._gev)
|
|
485
|
+
|
|
486
|
+
self._gmo = new_gmoHandle_tp()
|
|
487
|
+
ret = gmoCreateD(
|
|
488
|
+
self._gmo, self._checkpoint._workspace._system_directory, GMS_SSSIZE
|
|
489
|
+
)
|
|
490
|
+
if not ret[0]:
|
|
491
|
+
raise gams.control.workspace.GamsException(ret[1])
|
|
492
|
+
|
|
493
|
+
self._modifiers = []
|
|
494
|
+
|
|
495
|
+
for mod in source._modifiers:
|
|
496
|
+
if not mod._data_symbol: # Parameter
|
|
497
|
+
self._modifiers.append(
|
|
498
|
+
GamsModifier(
|
|
499
|
+
self.sync_db.get_parameter(mod._gams_symbol.name),
|
|
500
|
+
update_type=mod._update_type,
|
|
501
|
+
)
|
|
502
|
+
)
|
|
503
|
+
elif isinstance(mod._gams_symbol, GamsVariable):
|
|
504
|
+
self._modifiers.append(
|
|
505
|
+
GamsModifier(
|
|
506
|
+
self.sync_db.get_variable(mod._gams_symbol.name),
|
|
507
|
+
mod._update_action,
|
|
508
|
+
self.sync_db.get_parameter(mod._data_symbol.name),
|
|
509
|
+
mod._update_type,
|
|
510
|
+
)
|
|
511
|
+
)
|
|
512
|
+
elif isinstance(mod._gams_symbol, GamsEquation):
|
|
513
|
+
self._modifiers.append(
|
|
514
|
+
GamsModifier(
|
|
515
|
+
self.sync_db.get_equation(mod._gams_symbol.name),
|
|
516
|
+
mod._update_action,
|
|
517
|
+
self.sync_db.get_parameter(mod._data_symbol.name),
|
|
518
|
+
mod._update_type,
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
else:
|
|
522
|
+
raise gams.control.workspace.GamsException(
|
|
523
|
+
"Unexpected Symbol as Modifier"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
ret = gevDuplicateScratchDir(
|
|
527
|
+
source._gev, self._scr_dir, os.path.join(self._scr_dir, "gamslog.dat")
|
|
528
|
+
)
|
|
529
|
+
if ret[0] != 0:
|
|
530
|
+
raise gams.control.workspace.GamsException(
|
|
531
|
+
"Problem duplicating scratch directory"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if gevInitEnvironmentLegacy(self._gev, ret[1]) != 0:
|
|
535
|
+
raise gams.control.workspace.GamsException(
|
|
536
|
+
"Could not initialize model instance"
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
gmoRegisterEnvironment(self._gmo, gevHandleToPtr(self._gev))
|
|
540
|
+
ret = gmoLoadDataLegacy(self._gmo)
|
|
541
|
+
if ret[0] != 0:
|
|
542
|
+
raise gams.control.workspace.GamsException(
|
|
543
|
+
"Could not load model instance: " + ret[1]
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
rc = gmdRegisterGMO(self.sync_db._gmd, gmoHandleToPtr(self._gmo))
|
|
547
|
+
self.sync_db._check_for_gmd_error(rc)
|
|
548
|
+
|
|
549
|
+
self._log_available = source._log_available
|
|
550
|
+
self._selected_solver = source._selected_solver
|
|
551
|
+
|
|
552
|
+
opt_file_name = gmoNameOptFile(source._gmo)
|
|
553
|
+
gmoNameOptFileSet(
|
|
554
|
+
self._gmo,
|
|
555
|
+
os.path.join(
|
|
556
|
+
os.path.dirname(opt_file_name),
|
|
557
|
+
self._selected_solver + os.path.splitext(opt_file_name)[1],
|
|
558
|
+
),
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
self._instantiated = True
|
|
562
|
+
# Lock syncDB symbols so user can't add new symbols which would result in other exceptions
|
|
563
|
+
self.sync_db._symbol_lock = True
|
|
564
|
+
# Unlock syncDB record so user can add data for modifiers
|
|
565
|
+
self.sync_db._record_lock = False
|
|
566
|
+
|
|
567
|
+
else:
|
|
568
|
+
if not modelinstance_name:
|
|
569
|
+
self._modelinstance_name = checkpoint._workspace._modelinstance_add()
|
|
570
|
+
else:
|
|
571
|
+
if not checkpoint._workspace._modelinstance_add(modelinstance_name):
|
|
572
|
+
raise gams.control.workspace.GamsException(
|
|
573
|
+
"ModelInstance with name "
|
|
574
|
+
+ modelinstance_name
|
|
575
|
+
+ " already exists"
|
|
576
|
+
)
|
|
577
|
+
self._modelinstance_name = modelinstance_name
|
|
578
|
+
checkpoint._workspace._debug_out(
|
|
579
|
+
"---- Entering GamsModelInstance constructor ----", 0
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
self._p = None
|
|
583
|
+
self._checkpoint = checkpoint
|
|
584
|
+
self._scr_dir = (
|
|
585
|
+
self._checkpoint._workspace._working_directory
|
|
586
|
+
+ os.sep
|
|
587
|
+
+ self._modelinstance_name
|
|
588
|
+
)
|
|
589
|
+
self._sync_db = GamsDatabase(self._checkpoint._workspace)
|
|
590
|
+
self._sync_db._record_lock = True
|
|
591
|
+
|
|
592
|
+
self._gev = new_gevHandle_tp()
|
|
593
|
+
ret = gevCreateD(
|
|
594
|
+
self._gev, self._checkpoint._workspace._system_directory, GMS_SSSIZE
|
|
595
|
+
)
|
|
596
|
+
if not ret[0]:
|
|
597
|
+
raise gams.control.workspace.GamsException(ret[1])
|
|
598
|
+
checkpoint._workspace._gevHandles.append(self._gev)
|
|
599
|
+
|
|
600
|
+
self._gmo = new_gmoHandle_tp()
|
|
601
|
+
ret = gmoCreateD(
|
|
602
|
+
self._gmo, self._checkpoint._workspace._system_directory, GMS_SSSIZE
|
|
603
|
+
)
|
|
604
|
+
if not ret[0]:
|
|
605
|
+
raise gams.control.workspace.GamsException(ret[1])
|
|
606
|
+
|
|
607
|
+
self._modifiers = []
|
|
608
|
+
self._instantiated = False
|
|
609
|
+
|
|
610
|
+
def copy_modelinstance(self, modelinstance_name=None):
|
|
611
|
+
"""
|
|
612
|
+
@brief Copies this ModelInstance to a new ModelInstance
|
|
613
|
+
@param modelinstance_name Identifier of GamsModelInstance (determined automatically if omitted)
|
|
614
|
+
@return Reference to new ModelInstance
|
|
615
|
+
"""
|
|
616
|
+
|
|
617
|
+
return GamsModelInstance(modelinstance_name=modelinstance_name, source=self)
|
|
618
|
+
|
|
619
|
+
## @brief Use this to explicitly free unmanaged resources
|
|
620
|
+
def __del__(self):
|
|
621
|
+
self._checkpoint._workspace._debug_out(
|
|
622
|
+
"---- Entering GamsModelInstance destructor ----", 0
|
|
623
|
+
)
|
|
624
|
+
if self._gmo:
|
|
625
|
+
gmoFree(self._gmo)
|
|
626
|
+
if self._gev:
|
|
627
|
+
if gevHandleToPtr(self._gev) != None:
|
|
628
|
+
gevFree(self._gev)
|
|
629
|
+
|
|
630
|
+
def cleanup(self):
|
|
631
|
+
"""
|
|
632
|
+
@brief Explicitly closes the license session when using a license that
|
|
633
|
+
limits the actual uses of GAMS. This method should only be called
|
|
634
|
+
when the GamsModelInstance is not used anymore.
|
|
635
|
+
"""
|
|
636
|
+
gmdCloseLicenseSession(self.sync_db._gmd)
|
|
637
|
+
|
|
638
|
+
def instantiate(self, model_definition, modifiers=[], options=None):
|
|
639
|
+
"""
|
|
640
|
+
@brief Instantiate the GamsModelInstance
|
|
641
|
+
@param model_definition Model definition
|
|
642
|
+
@param modifiers List of GamsModifiers
|
|
643
|
+
@param options GamsOptions
|
|
644
|
+
"""
|
|
645
|
+
have_par = False
|
|
646
|
+
|
|
647
|
+
tmp_opt = GamsOptions(self._checkpoint._workspace, options)
|
|
648
|
+
|
|
649
|
+
if self._instantiated:
|
|
650
|
+
raise gams.control.workspace.GamsException(
|
|
651
|
+
"ModelInstance " + self._modelinstance_name + " already instantiated"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
rc, ival, dval, sval = gmdInfo(self._sync_db._gmd, GMD_NRUELS)
|
|
655
|
+
self._sync_db._check_for_gmd_error(rc)
|
|
656
|
+
if ival > 0:
|
|
657
|
+
raise gams.control.workspace.GamsException(
|
|
658
|
+
"Sync_db of "
|
|
659
|
+
+ self._modelinstance_name
|
|
660
|
+
+ " with unique elements. No AddRecord allowed prior to Instantiate"
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
if isinstance(modifiers, GamsModifier):
|
|
664
|
+
modifiers = [modifiers]
|
|
665
|
+
elif isinstance(modifiers, tuple):
|
|
666
|
+
modifiers = list(modifiers)
|
|
667
|
+
for mod in modifiers:
|
|
668
|
+
if mod._gams_symbol._database != self._sync_db:
|
|
669
|
+
raise gams.control.workspace.GamsException(
|
|
670
|
+
"Symbol " + mod.GamsSym.Name + " not part of SyncDB"
|
|
671
|
+
)
|
|
672
|
+
self._modifiers.append(mod)
|
|
673
|
+
if isinstance(mod._gams_symbol, GamsSet):
|
|
674
|
+
raise gams.control.workspace.GamsException(
|
|
675
|
+
"Sets cannot be model modifiers"
|
|
676
|
+
)
|
|
677
|
+
if isinstance(mod._gams_symbol, GamsParameter):
|
|
678
|
+
have_par = True
|
|
679
|
+
|
|
680
|
+
# Symbols in sync_db that are not modifier
|
|
681
|
+
if len(modifiers) < len(self._sync_db):
|
|
682
|
+
nr_parameters = 0
|
|
683
|
+
for sym in self._sync_db:
|
|
684
|
+
if isinstance(sym, GamsParameter):
|
|
685
|
+
nr_parameters += 1
|
|
686
|
+
# TODO: Should this check really be taken out? We use it deactivated for Timo at the moment
|
|
687
|
+
# if isinstance(sym, GamsSet):
|
|
688
|
+
# raise workspace.gams.control.workspace.GamsException("GAMSSet not allowed in SyncDB: " + sym.name)
|
|
689
|
+
|
|
690
|
+
if len(modifiers) < nr_parameters:
|
|
691
|
+
for sym in self._sync_db:
|
|
692
|
+
if isinstance(sym, GamsParameter):
|
|
693
|
+
found = False
|
|
694
|
+
for mod in modifiers:
|
|
695
|
+
if sym._sym_ptr == mod.gams_symbol._sym_ptr:
|
|
696
|
+
found = True
|
|
697
|
+
break
|
|
698
|
+
if not found:
|
|
699
|
+
raise gams.control.workspace.GamsException(
|
|
700
|
+
"Parameter " + sym.name + " is not a modifier"
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
model = "option limrow=0, limcol=0;\n"
|
|
704
|
+
|
|
705
|
+
# transport use lp min[imizing] z
|
|
706
|
+
# or
|
|
707
|
+
# transport min[imizing] z use lp
|
|
708
|
+
model_direction = model_definition.split()[1]
|
|
709
|
+
model_type = ""
|
|
710
|
+
if model_direction[0:3].lower() in ["min", "max"]:
|
|
711
|
+
model_type = model_definition.split()[4]
|
|
712
|
+
else:
|
|
713
|
+
model_type = model_definition.split()[2]
|
|
714
|
+
|
|
715
|
+
if have_par:
|
|
716
|
+
model += "Set s__(*) /'s0'/;\n"
|
|
717
|
+
for mod in modifiers:
|
|
718
|
+
if isinstance(mod._gams_symbol, GamsParameter):
|
|
719
|
+
model += "Parameter s__" + mod._gams_symbol._name + "(s__"
|
|
720
|
+
for i in range(mod._gams_symbol._dim):
|
|
721
|
+
model += ",*"
|
|
722
|
+
model += "); s__" + mod._gams_symbol._name + "(s__"
|
|
723
|
+
for i in range(mod._gams_symbol._dim):
|
|
724
|
+
model += ",s__"
|
|
725
|
+
model += ") = Eps ;\n"
|
|
726
|
+
|
|
727
|
+
model += "Set dict(*,*,*) /\n's__'.'scenario'.''"
|
|
728
|
+
for mod in modifiers:
|
|
729
|
+
if isinstance(mod._gams_symbol, GamsParameter):
|
|
730
|
+
model += (
|
|
731
|
+
",\n'"
|
|
732
|
+
+ mod._gams_symbol._name
|
|
733
|
+
+ "'.'param'.'s__"
|
|
734
|
+
+ mod.gams_symbol._name
|
|
735
|
+
+ "'"
|
|
736
|
+
)
|
|
737
|
+
model += "/;\n"
|
|
738
|
+
|
|
739
|
+
model_name = model_definition.split(" ")[0]
|
|
740
|
+
|
|
741
|
+
# .justScrDir was introduced with GAMS 34
|
|
742
|
+
if self._checkpoint._workspace.major_rel_number < 34:
|
|
743
|
+
raise gams.control.workspace.GamsException(
|
|
744
|
+
"GAMS 34 or newer is required to instantiate with this version of the API - Upgrade your GAMS version or switch to the API version that was shipped with the GAMS version you are using"
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
model += model_name + ".justScrDir=1;\n"
|
|
748
|
+
model += "solve " + model_definition
|
|
749
|
+
|
|
750
|
+
if have_par:
|
|
751
|
+
model += " scenario dict;"
|
|
752
|
+
|
|
753
|
+
my_job = GamsJob(
|
|
754
|
+
self._checkpoint._workspace, source=model, checkpoint=self._checkpoint
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
# TODO: use GAMSOptions class
|
|
758
|
+
optSetStrStr(tmp_opt._opt, "ScrDir", self._scr_dir)
|
|
759
|
+
if (
|
|
760
|
+
self._checkpoint._workspace._debug
|
|
761
|
+
>= gams.control.workspace.DebugLevel.ShowLog
|
|
762
|
+
):
|
|
763
|
+
optSetIntStr(tmp_opt._opt, "LogOption", 4)
|
|
764
|
+
self._log_available = False
|
|
765
|
+
else:
|
|
766
|
+
optSetIntStr(tmp_opt._opt, "LogOption", 2)
|
|
767
|
+
self._log_available = True
|
|
768
|
+
|
|
769
|
+
optSetStrStr(
|
|
770
|
+
tmp_opt._opt, "LogFile", os.path.join(self._scr_dir, "gamslog.dat")
|
|
771
|
+
)
|
|
772
|
+
optSetStrStr(tmp_opt._opt, "SolverCntr", "gamscntr.dat")
|
|
773
|
+
|
|
774
|
+
if not os.path.exists(self._scr_dir):
|
|
775
|
+
os.mkdir(self._scr_dir)
|
|
776
|
+
|
|
777
|
+
my_job.run(gams_options=tmp_opt, create_out_db=False)
|
|
778
|
+
|
|
779
|
+
solver_cntr = optGetStrStr(tmp_opt._opt, "SolverCntr")
|
|
780
|
+
if (
|
|
781
|
+
gevInitEnvironmentLegacy(self._gev, self._scr_dir + os.sep + solver_cntr)
|
|
782
|
+
!= 0
|
|
783
|
+
):
|
|
784
|
+
raise gams.control.workspace.GamsException(
|
|
785
|
+
"Could not initialize model instance"
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
gmoRegisterEnvironment(self._gmo, gevHandleToPtr(self._gev))
|
|
789
|
+
ret = gmoLoadDataLegacy(self._gmo)
|
|
790
|
+
if ret[0] != 0:
|
|
791
|
+
raise gams.control.workspace.GamsException(
|
|
792
|
+
"Could not load model instance: " + ret[1]
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
self._selected_solver = tmp_opt._selected_solvers[gmoModelType(self._gmo)]
|
|
796
|
+
opt_file_name = gmoNameOptFile(self._gmo)
|
|
797
|
+
gmoNameOptFileSet(
|
|
798
|
+
self._gmo,
|
|
799
|
+
os.path.join(
|
|
800
|
+
os.path.dirname(opt_file_name),
|
|
801
|
+
self._selected_solver + os.path.splitext(opt_file_name)[1],
|
|
802
|
+
),
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
rc = gmdInitFromDict(self._sync_db._gmd, gmoHandleToPtr(self._gmo))
|
|
806
|
+
self.sync_db._check_for_gmd_error(rc)
|
|
807
|
+
|
|
808
|
+
del tmp_opt
|
|
809
|
+
self._instantiated = True
|
|
810
|
+
|
|
811
|
+
# Lock sync_db symbols so user can't add new symbols which would result in other exceptions
|
|
812
|
+
self._sync_db._symbol_lock = True
|
|
813
|
+
# Unlock sync_db record so user can add data for modifiers
|
|
814
|
+
self._sync_db._record_lock = False
|
|
815
|
+
|
|
816
|
+
def solve(self, update_type=SymbolUpdateType.BaseCase, output=None, mi_opt=None):
|
|
817
|
+
"""
|
|
818
|
+
@brief Solve model instance
|
|
819
|
+
@param update_type Update type
|
|
820
|
+
@param output Used to capture GAMS log, (e.g. sys.stdout or an object created by the build-in function open())
|
|
821
|
+
@param mi_opt GamsModelInstance options
|
|
822
|
+
"""
|
|
823
|
+
|
|
824
|
+
if update_type not in [
|
|
825
|
+
SymbolUpdateType.Zero,
|
|
826
|
+
SymbolUpdateType.BaseCase,
|
|
827
|
+
SymbolUpdateType.Accumulate,
|
|
828
|
+
]:
|
|
829
|
+
raise gams.control.workspace.GamsException(
|
|
830
|
+
"Update type '" + str(update_type) + "' is not valid",
|
|
831
|
+
self._checkpoint._workspace,
|
|
832
|
+
)
|
|
833
|
+
no_match_limit = 0
|
|
834
|
+
if mi_opt != None:
|
|
835
|
+
no_match_limit = mi_opt.no_match_limit
|
|
836
|
+
|
|
837
|
+
if not self._instantiated:
|
|
838
|
+
raise gams.control.workspace.GamsException(
|
|
839
|
+
"Model instance " + self._model_instance_name + " not instantiated",
|
|
840
|
+
self._checkpoint._workspace,
|
|
841
|
+
)
|
|
842
|
+
rc = gmdInitUpdate(self._sync_db._gmd, gmoHandleToPtr(self._gmo))
|
|
843
|
+
self.sync_db._check_for_gmd_error(rc, self._checkpoint._workspace)
|
|
844
|
+
|
|
845
|
+
accumulate_no_match_cnt = 0
|
|
846
|
+
no_match_cnt = 0
|
|
847
|
+
|
|
848
|
+
for mod in self._modifiers:
|
|
849
|
+
loc_sut = update_type
|
|
850
|
+
if mod._update_type != SymbolUpdateType._Inherit:
|
|
851
|
+
loc_sut = mod._update_type
|
|
852
|
+
if isinstance(mod._gams_symbol, GamsParameter):
|
|
853
|
+
rc, no_match_cnt = gmdUpdateModelSymbol(
|
|
854
|
+
self._sync_db._gmd,
|
|
855
|
+
mod._gams_symbol._sym_ptr,
|
|
856
|
+
0,
|
|
857
|
+
mod._gams_symbol._sym_ptr,
|
|
858
|
+
loc_sut,
|
|
859
|
+
no_match_cnt,
|
|
860
|
+
)
|
|
861
|
+
self.sync_db._check_for_gmd_error(rc, self._checkpoint._workspace)
|
|
862
|
+
else:
|
|
863
|
+
rc, no_match_cnt = gmdUpdateModelSymbol(
|
|
864
|
+
self._sync_db._gmd,
|
|
865
|
+
mod._gams_symbol._sym_ptr,
|
|
866
|
+
mod._update_action,
|
|
867
|
+
mod._data_symbol._sym_ptr,
|
|
868
|
+
loc_sut,
|
|
869
|
+
no_match_cnt,
|
|
870
|
+
)
|
|
871
|
+
self.sync_db._check_for_gmd_error(rc, self._checkpoint._workspace)
|
|
872
|
+
|
|
873
|
+
accumulate_no_match_cnt += no_match_cnt
|
|
874
|
+
if accumulate_no_match_cnt > no_match_limit:
|
|
875
|
+
raise gams.control.workspace.GamsException(
|
|
876
|
+
"Unmatched record limit exceeded while processing modifier "
|
|
877
|
+
+ mod._gams_symbol.name
|
|
878
|
+
+ ", for more info check GamsModelInstanceOpt parameter no_match_limit",
|
|
879
|
+
self._checkpoint._workspace,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# Close Log and status file and remove
|
|
883
|
+
if self._log_available and output:
|
|
884
|
+
gevSwitchLogStat(self._gev, 0, "", False, "", False, None, None, None)
|
|
885
|
+
ls_handle = gevGetLShandle(self._gev)
|
|
886
|
+
gevRestoreLogStatRewrite(self._gev, ls_handle)
|
|
887
|
+
|
|
888
|
+
if output == sys.stdout:
|
|
889
|
+
gevSwitchLogStat(
|
|
890
|
+
self._gev,
|
|
891
|
+
3,
|
|
892
|
+
gevGetStrOpt(self._gev, gevNameLogFile),
|
|
893
|
+
False,
|
|
894
|
+
gevGetStrOpt(self._gev, gevNameStaFile),
|
|
895
|
+
False,
|
|
896
|
+
None,
|
|
897
|
+
None,
|
|
898
|
+
ls_handle,
|
|
899
|
+
)
|
|
900
|
+
ls_handle = gevGetLShandle(self._gev)
|
|
901
|
+
|
|
902
|
+
tmp_solver = self._selected_solver
|
|
903
|
+
if mi_opt != None and mi_opt.solver:
|
|
904
|
+
tmp_solver = mi_opt.solver
|
|
905
|
+
|
|
906
|
+
tmp_opt_file = gmoOptFile(self._gmo)
|
|
907
|
+
save_opt_file = tmp_opt_file
|
|
908
|
+
save_name_opt_file = gmoNameOptFile(self._gmo)
|
|
909
|
+
if mi_opt != None and mi_opt.opt_file != -1:
|
|
910
|
+
tmp_opt_file = mi_opt.opt_file
|
|
911
|
+
|
|
912
|
+
if mi_opt != None and mi_opt.debug:
|
|
913
|
+
with open(
|
|
914
|
+
os.path.join(
|
|
915
|
+
self._checkpoint._workspace._working_directory,
|
|
916
|
+
self._modelinstance_name,
|
|
917
|
+
"convert.opt",
|
|
918
|
+
),
|
|
919
|
+
"w",
|
|
920
|
+
) as opt_file:
|
|
921
|
+
opt_file.writelines(
|
|
922
|
+
[
|
|
923
|
+
"gams "
|
|
924
|
+
+ os.path.join(
|
|
925
|
+
self._checkpoint._workspace._working_directory,
|
|
926
|
+
self._modelinstance_name,
|
|
927
|
+
"gams.gms",
|
|
928
|
+
),
|
|
929
|
+
"dumpgdx "
|
|
930
|
+
+ os.path.join(
|
|
931
|
+
self._checkpoint._workspace._working_directory,
|
|
932
|
+
self._modelinstance_name,
|
|
933
|
+
"dump.gdx\n",
|
|
934
|
+
),
|
|
935
|
+
"dictmap "
|
|
936
|
+
+ os.path.join(
|
|
937
|
+
self._checkpoint._workspace._working_directory,
|
|
938
|
+
self._modelinstance_name,
|
|
939
|
+
"dictmap.gdx",
|
|
940
|
+
),
|
|
941
|
+
]
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
gmoOptFileSet(self._gmo, 1)
|
|
945
|
+
gmoNameOptFileSet(
|
|
946
|
+
self._gmo,
|
|
947
|
+
os.path.join(
|
|
948
|
+
self._checkpoint._workspace._working_directory,
|
|
949
|
+
self._modelinstance_name,
|
|
950
|
+
"convert.opt",
|
|
951
|
+
),
|
|
952
|
+
)
|
|
953
|
+
rc = gmdCallSolver(self._sync_db._gmd, "convert")
|
|
954
|
+
self.sync_db._check_for_gmd_error(rc, self._checkpoint._workspace)
|
|
955
|
+
|
|
956
|
+
gmoOptFileSet(self._gmo, tmp_opt_file)
|
|
957
|
+
gmoNameOptFileSet(
|
|
958
|
+
self._gmo,
|
|
959
|
+
os.path.join(
|
|
960
|
+
os.path.dirname(save_name_opt_file),
|
|
961
|
+
tmp_solver
|
|
962
|
+
+ "."
|
|
963
|
+
+ self._checkpoint._workspace._opt_file_extension(tmp_opt_file),
|
|
964
|
+
),
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
rc = gmdCallSolver(self._sync_db._gmd, tmp_solver)
|
|
968
|
+
self.sync_db._check_for_gmd_error(rc, self._checkpoint._workspace)
|
|
969
|
+
|
|
970
|
+
gmoOptFileSet(self._gmo, save_opt_file)
|
|
971
|
+
gmoNameOptFileSet(self._gmo, save_name_opt_file)
|
|
972
|
+
|
|
973
|
+
if output == sys.stdout:
|
|
974
|
+
gevRestoreLogStat(self._gev, ls_handle)
|
|
975
|
+
|
|
976
|
+
if (output != None) and (output != sys.stdout):
|
|
977
|
+
if self._log_available:
|
|
978
|
+
gevSwitchLogStat(
|
|
979
|
+
self._gev, 0, "", False, "", False, None, None, ls_handle
|
|
980
|
+
)
|
|
981
|
+
ls_handle = gevGetLShandle(self._gev)
|
|
982
|
+
# TODO: in C# we open the file in some advanced mode to prevent from generating errors if the file is already open by some other resource
|
|
983
|
+
with open(gevGetStrOpt(self._gev, gevNameLogFile)) as file:
|
|
984
|
+
for line in file.readlines():
|
|
985
|
+
output.write(line)
|
|
986
|
+
gevRestoreLogStat(self._gev, ls_handle)
|
|
987
|
+
else:
|
|
988
|
+
output.write("No solver log available")
|
|
989
|
+
|
|
990
|
+
def interrupt(self):
|
|
991
|
+
"""
|
|
992
|
+
@brief Send interrupt signal to running GamsModelInstance
|
|
993
|
+
"""
|
|
994
|
+
gevTerminateRaise(self._gev)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
class GamsCheckpoint(object):
|
|
998
|
+
"""
|
|
999
|
+
@brief A GamsCheckpoint class captures the state of a GamsJob after the GamsJob.run
|
|
1000
|
+
method has been carried out.
|
|
1001
|
+
@details Another GamsJob can continue (or restart) from a
|
|
1002
|
+
GamsCheckpoint. A GamsCheckpoint constructed with a file name will create a file
|
|
1003
|
+
(extension .g00) for permanent storage when supplied as parameter on the
|
|
1004
|
+
GamsJob.run method. Moreover, a GamsModelInstance is also initialized from a
|
|
1005
|
+
checkpoint that contains the model definition of the model instance.
|
|
1006
|
+
"""
|
|
1007
|
+
|
|
1008
|
+
def get_workspace(self):
|
|
1009
|
+
return self._workspace
|
|
1010
|
+
|
|
1011
|
+
## @brief Get the GamsWorkspace
|
|
1012
|
+
workspace = property(get_workspace)
|
|
1013
|
+
|
|
1014
|
+
def get_name(self):
|
|
1015
|
+
return self._name
|
|
1016
|
+
|
|
1017
|
+
## @brief Get the checkpoint name
|
|
1018
|
+
name = property(get_name)
|
|
1019
|
+
|
|
1020
|
+
def __init__(self, workspace, checkpoint_name=None):
|
|
1021
|
+
"""
|
|
1022
|
+
@brief Constructor
|
|
1023
|
+
@param workspace GamsWorkspace containing GamsCheckpoint
|
|
1024
|
+
@param checkpoint_name Identifier of GamsCheckpoint (determined automatically if omitted)
|
|
1025
|
+
"""
|
|
1026
|
+
|
|
1027
|
+
workspace._debug_out("---- Entering GamsCheckpoint constructor ----", 0)
|
|
1028
|
+
self._workspace = workspace
|
|
1029
|
+
if not checkpoint_name:
|
|
1030
|
+
self._name = self._workspace._checkpoint_add()
|
|
1031
|
+
else:
|
|
1032
|
+
if not self._workspace._checkpoint_add(checkpoint_name):
|
|
1033
|
+
raise gams.control.workspace.GamsException(
|
|
1034
|
+
"Checkpoint with name " + checkpoint_name + " already exists"
|
|
1035
|
+
)
|
|
1036
|
+
self._name = checkpoint_name
|
|
1037
|
+
if os.path.isabs(self._name):
|
|
1038
|
+
self._checkpoint_file_name = self._name
|
|
1039
|
+
else:
|
|
1040
|
+
self._checkpoint_file_name = (
|
|
1041
|
+
self._workspace._working_directory + os.sep + self._name
|
|
1042
|
+
)
|
|
1043
|
+
self._checkpoint_file_name = (
|
|
1044
|
+
os.path.splitext(self._checkpoint_file_name)[0] + ".g00"
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
def __del__(self):
|
|
1048
|
+
self._workspace._debug_out("---- Entering GamsCheckpoint destructor ----", 0)
|
|
1049
|
+
|
|
1050
|
+
def add_modelinstance(self, modelinstance_name=None):
|
|
1051
|
+
"""
|
|
1052
|
+
@brief Create model instance
|
|
1053
|
+
@param modelinstance_name Identifier of GamsModelInstance (determined automatically if omitted)
|
|
1054
|
+
@return GamsModelInstance instance
|
|
1055
|
+
"""
|
|
1056
|
+
return GamsModelInstance(self, modelinstance_name=modelinstance_name)
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
class GamsJob(object):
|
|
1060
|
+
"""
|
|
1061
|
+
@brief The GamsJob class manages the execution of a GAMS program given by GAMS model
|
|
1062
|
+
source.
|
|
1063
|
+
@details <p>The GAMS source (or more precisely the root of a model source tree) of
|
|
1064
|
+
the job can be provided as a string or by a filename (relative to the working
|
|
1065
|
+
directory of the GamsWorkspace) of a text file containing the GAMS model source.
|
|
1066
|
+
The run method organizes the export of the input GamsDatabases, calls the GAMS
|
|
1067
|
+
compiler and execution system with the supplied options and on successful
|
|
1068
|
+
completion provides through the property out_db (of type GamsDatabase) the
|
|
1069
|
+
results of the model run.</p>
|
|
1070
|
+
<p>While the result data is captured in a GamsDatabase, the run method can also
|
|
1071
|
+
create a GamsCheckpoint that not only captures data but represents the state of
|
|
1072
|
+
the entire GamsJob and allows some other GamsJob to continue from this state.
|
|
1073
|
+
In case of a compilation or execution error, the run method will throw an
|
|
1074
|
+
exception. If the log output of GAMS is of interest, this can be captured by
|
|
1075
|
+
providing the output parameter of the run method (e.g. sys.stdout).</p>
|
|
1076
|
+
"""
|
|
1077
|
+
|
|
1078
|
+
def get_job_name(self):
|
|
1079
|
+
return self._job_name
|
|
1080
|
+
|
|
1081
|
+
## @brief Retrieve name of GamsJob
|
|
1082
|
+
name = property(get_job_name)
|
|
1083
|
+
|
|
1084
|
+
def get_workspace(self):
|
|
1085
|
+
return self._workspace
|
|
1086
|
+
|
|
1087
|
+
## @brief Get GamsWorkspace containing GamsJob
|
|
1088
|
+
workspace = property(get_workspace)
|
|
1089
|
+
|
|
1090
|
+
def get_out_db(self):
|
|
1091
|
+
return self._out_db
|
|
1092
|
+
|
|
1093
|
+
## @brief Get GamsDatabase created by run method
|
|
1094
|
+
out_db = property(get_out_db)
|
|
1095
|
+
|
|
1096
|
+
def __init__(self, ws, file_name=None, source=None, checkpoint=None, job_name=None):
|
|
1097
|
+
"""
|
|
1098
|
+
@brief Constructor
|
|
1099
|
+
@note It is not allowed to specify both file_name and source at the same time.
|
|
1100
|
+
@param ws GamsWorkspace containing GamsJob
|
|
1101
|
+
@param file_name GAMS source file name
|
|
1102
|
+
@param source GAMS model as string
|
|
1103
|
+
@param checkpoint GamsCheckpoint to initialize GamsJob from
|
|
1104
|
+
@param job_name Job name (determined automatically if omitted)
|
|
1105
|
+
"""
|
|
1106
|
+
|
|
1107
|
+
ws._debug_out("---- Entering GamsJob constructor ----", 0)
|
|
1108
|
+
|
|
1109
|
+
if file_name and source:
|
|
1110
|
+
raise gams.control.workspace.GamsException(
|
|
1111
|
+
"Multiple sources specified: You can either set a file name or a source, but not both"
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
self._workspace = ws
|
|
1115
|
+
self._job_name = None
|
|
1116
|
+
self._file_name = None
|
|
1117
|
+
|
|
1118
|
+
if checkpoint != None and not os.path.exists(checkpoint._checkpoint_file_name):
|
|
1119
|
+
raise gams.control.workspace.GamsException(
|
|
1120
|
+
"Checkpoint file "
|
|
1121
|
+
+ checkpoint._checkpoint_file_name
|
|
1122
|
+
+ " does not exist"
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
self._checkpoint_start = checkpoint
|
|
1126
|
+
self._out_db = None
|
|
1127
|
+
self._http = None
|
|
1128
|
+
self._max_request_attempts = 3
|
|
1129
|
+
|
|
1130
|
+
# handle job name
|
|
1131
|
+
if not job_name:
|
|
1132
|
+
self._job_name = self._workspace._job_add()
|
|
1133
|
+
else:
|
|
1134
|
+
self._job_name = job_name
|
|
1135
|
+
if not self._workspace._job_add(self._job_name):
|
|
1136
|
+
raise gams.control.workspace.GamsException(
|
|
1137
|
+
"Job with name " + self._job_name + " already exists"
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
# create job from file
|
|
1141
|
+
if file_name:
|
|
1142
|
+
if os.path.isabs(file_name):
|
|
1143
|
+
self._file_name = file_name
|
|
1144
|
+
else:
|
|
1145
|
+
self._file_name = (
|
|
1146
|
+
self._workspace._working_directory + os.sep + file_name
|
|
1147
|
+
)
|
|
1148
|
+
# check if file does exist
|
|
1149
|
+
if not os.path.isfile(self._file_name):
|
|
1150
|
+
self._file_name = self._file_name + ".gms"
|
|
1151
|
+
if not os.path.isfile(self._file_name):
|
|
1152
|
+
raise gams.control.workspace.GamsException(
|
|
1153
|
+
f"Could not create GamsJob instance from non-existing file {self._file_name}"
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
# create job from source
|
|
1157
|
+
elif source:
|
|
1158
|
+
self._file_name = (
|
|
1159
|
+
self._workspace._working_directory + os.sep + self._job_name + ".gms"
|
|
1160
|
+
)
|
|
1161
|
+
with open(self._file_name, "w") as file:
|
|
1162
|
+
file.write(source)
|
|
1163
|
+
|
|
1164
|
+
def __del__(self):
|
|
1165
|
+
self._workspace._debug_out("---- Entering GamsJob destructor ----", 0)
|
|
1166
|
+
|
|
1167
|
+
def _remove_tmp_cp(self, tmp_cp, checkpoint):
|
|
1168
|
+
if tmp_cp:
|
|
1169
|
+
try:
|
|
1170
|
+
os.remove(
|
|
1171
|
+
os.path.join(
|
|
1172
|
+
self._workspace._working_directory,
|
|
1173
|
+
checkpoint._checkpoint_file_name,
|
|
1174
|
+
)
|
|
1175
|
+
)
|
|
1176
|
+
except (FileNotFoundError, PermissionError):
|
|
1177
|
+
pass
|
|
1178
|
+
shutil.move(tmp_cp._checkpoint_file_name, checkpoint._checkpoint_file_name)
|
|
1179
|
+
|
|
1180
|
+
def _remove_tmp_opt(self, tmp_opt, pf_file_name):
|
|
1181
|
+
del tmp_opt
|
|
1182
|
+
if self._workspace._debug < gams.control.workspace.DebugLevel.KeepFiles:
|
|
1183
|
+
try:
|
|
1184
|
+
os.remove(pf_file_name)
|
|
1185
|
+
except (FileNotFoundError, PermissionError):
|
|
1186
|
+
pass
|
|
1187
|
+
|
|
1188
|
+
def _prepare_run(
|
|
1189
|
+
self,
|
|
1190
|
+
gams_options=None,
|
|
1191
|
+
checkpoint=None,
|
|
1192
|
+
output=None,
|
|
1193
|
+
create_out_db=True,
|
|
1194
|
+
databases=None,
|
|
1195
|
+
relative_paths=False,
|
|
1196
|
+
):
|
|
1197
|
+
tmp_cp = None
|
|
1198
|
+
tmp_opt = GamsOptions(self._workspace, gams_options)
|
|
1199
|
+
|
|
1200
|
+
if self._checkpoint_start:
|
|
1201
|
+
if relative_paths:
|
|
1202
|
+
tmp_opt._restart = os.path.relpath(
|
|
1203
|
+
self._checkpoint_start._checkpoint_file_name,
|
|
1204
|
+
self._workspace._working_directory,
|
|
1205
|
+
)
|
|
1206
|
+
else:
|
|
1207
|
+
tmp_opt._restart = self._checkpoint_start._checkpoint_file_name
|
|
1208
|
+
if checkpoint:
|
|
1209
|
+
if self._checkpoint_start == checkpoint:
|
|
1210
|
+
tmp_cp = GamsCheckpoint(self._workspace)
|
|
1211
|
+
if relative_paths:
|
|
1212
|
+
tmp_opt._save = os.path.relpath(
|
|
1213
|
+
tmp_cp.name, self._workspace._working_directory
|
|
1214
|
+
)
|
|
1215
|
+
else:
|
|
1216
|
+
tmp_opt._save = tmp_cp.name
|
|
1217
|
+
else:
|
|
1218
|
+
if relative_paths:
|
|
1219
|
+
tmp_opt._save = os.path.relpath(
|
|
1220
|
+
checkpoint._checkpoint_file_name,
|
|
1221
|
+
self._workspace._working_directory,
|
|
1222
|
+
)
|
|
1223
|
+
else:
|
|
1224
|
+
tmp_opt._save = checkpoint._checkpoint_file_name
|
|
1225
|
+
|
|
1226
|
+
# implement log_option member in GamsOptions class
|
|
1227
|
+
if self._workspace._debug >= gams.control.workspace.DebugLevel.ShowLog:
|
|
1228
|
+
optSetIntStr(tmp_opt._opt, "LogOption", 4)
|
|
1229
|
+
elif optGetIntStr(tmp_opt._opt, "LogOption") != 2:
|
|
1230
|
+
if not output:
|
|
1231
|
+
optSetIntStr(tmp_opt._opt, "LogOption", 0)
|
|
1232
|
+
else:
|
|
1233
|
+
optSetIntStr(tmp_opt._opt, "LogOption", 3)
|
|
1234
|
+
|
|
1235
|
+
# handle s single database and a collection of databases
|
|
1236
|
+
db_paths = set()
|
|
1237
|
+
if databases:
|
|
1238
|
+
if isinstance(databases, GamsDatabase):
|
|
1239
|
+
databases = [databases]
|
|
1240
|
+
for db in databases:
|
|
1241
|
+
db_paths.add(
|
|
1242
|
+
os.path.join(
|
|
1243
|
+
self._workspace._working_directory, db._database_name + ".gdx"
|
|
1244
|
+
)
|
|
1245
|
+
)
|
|
1246
|
+
db.export()
|
|
1247
|
+
if db._in_model_name:
|
|
1248
|
+
tmp_opt.defines[db._in_model_name] = db.name
|
|
1249
|
+
|
|
1250
|
+
if len(tmp_opt.defines) > 0:
|
|
1251
|
+
save_eol_only = optEOLOnlySet(tmp_opt._opt, 0)
|
|
1252
|
+
gms_param = ""
|
|
1253
|
+
for k, v in iter(tmp_opt.defines.items()):
|
|
1254
|
+
gms_param = "--" + k + "=" + v
|
|
1255
|
+
optReadFromStr(tmp_opt._opt, gms_param)
|
|
1256
|
+
optEOLOnlySet(tmp_opt._opt, save_eol_only)
|
|
1257
|
+
|
|
1258
|
+
if len(tmp_opt.idir) > 0:
|
|
1259
|
+
if len(tmp_opt.idir) > 40:
|
|
1260
|
+
raise gams.control.workspace.GamsException(
|
|
1261
|
+
"Cannot handle more than 40 IDirs", self._workspace
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1264
|
+
for i in range(len(tmp_opt.idir)):
|
|
1265
|
+
optSetStrStr(tmp_opt._opt, "InputDir" + str(i + 1), tmp_opt.idir[i])
|
|
1266
|
+
|
|
1267
|
+
for i in range(1, gmoProc_nrofmodeltypes):
|
|
1268
|
+
optSetStrStr(
|
|
1269
|
+
tmp_opt._opt,
|
|
1270
|
+
cfgModelTypeName(tmp_opt._cfg, i),
|
|
1271
|
+
tmp_opt._selected_solvers[i],
|
|
1272
|
+
)
|
|
1273
|
+
if create_out_db:
|
|
1274
|
+
if tmp_opt.gdx == "":
|
|
1275
|
+
tmp_opt.gdx = self._workspace._database_add()
|
|
1276
|
+
|
|
1277
|
+
if len(tmp_opt._logfile) == 0:
|
|
1278
|
+
if relative_paths:
|
|
1279
|
+
tmp_opt._logfile = self._job_name + ".log"
|
|
1280
|
+
else:
|
|
1281
|
+
tmp_opt._logfile = (
|
|
1282
|
+
self._workspace._working_directory
|
|
1283
|
+
+ os.sep
|
|
1284
|
+
+ self._job_name
|
|
1285
|
+
+ ".log"
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
if not gams_options or not gams_options.output:
|
|
1289
|
+
tmp_opt.output = self._job_name + ".lst"
|
|
1290
|
+
|
|
1291
|
+
if relative_paths:
|
|
1292
|
+
tmp_opt._input = os.path.relpath(
|
|
1293
|
+
self._file_name, self._workspace._working_directory
|
|
1294
|
+
)
|
|
1295
|
+
else:
|
|
1296
|
+
tmp_opt._curdir = self._workspace._working_directory
|
|
1297
|
+
tmp_opt._input = self._file_name
|
|
1298
|
+
|
|
1299
|
+
pf_file_name = (
|
|
1300
|
+
self._workspace._working_directory + os.sep + self._job_name + ".pf"
|
|
1301
|
+
)
|
|
1302
|
+
if optWriteParameterFile(tmp_opt._opt, pf_file_name) != 0:
|
|
1303
|
+
raise gams.control.workspace.GamsException(
|
|
1304
|
+
"Could not write parameter file "
|
|
1305
|
+
+ pf_file_name
|
|
1306
|
+
+ " for GamsJob "
|
|
1307
|
+
+ self._job_name,
|
|
1308
|
+
self._workspace,
|
|
1309
|
+
)
|
|
1310
|
+
|
|
1311
|
+
return tmp_cp, tmp_opt, pf_file_name, db_paths
|
|
1312
|
+
|
|
1313
|
+
def run(
|
|
1314
|
+
self,
|
|
1315
|
+
gams_options=None,
|
|
1316
|
+
checkpoint=None,
|
|
1317
|
+
output=None,
|
|
1318
|
+
create_out_db=True,
|
|
1319
|
+
databases=None,
|
|
1320
|
+
):
|
|
1321
|
+
"""
|
|
1322
|
+
@brief Run GamsJob
|
|
1323
|
+
@param gams_options GAMS options to control job
|
|
1324
|
+
@param checkpoint GamsCheckpoint to be created by GamsJob
|
|
1325
|
+
@param output Stream to capture GAMS log (e.g. sys.stdout or an object created by the build-in function open())
|
|
1326
|
+
@param create_out_db Flag to define if out_db should be created
|
|
1327
|
+
@param databases Either a GamsDatabase or a list of GamsDatabases to be read by the GamsJob
|
|
1328
|
+
"""
|
|
1329
|
+
|
|
1330
|
+
tmp_cp, tmp_opt, pf_file_name, _ = self._prepare_run(
|
|
1331
|
+
gams_options, checkpoint, output, create_out_db, databases
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
capture_output = (
|
|
1335
|
+
self._workspace._debug >= gams.control.workspace.DebugLevel.ShowLog
|
|
1336
|
+
or output
|
|
1337
|
+
)
|
|
1338
|
+
stdout_val = None
|
|
1339
|
+
if capture_output:
|
|
1340
|
+
stdout_val = subprocess.PIPE
|
|
1341
|
+
|
|
1342
|
+
# TODO: Popen will throw an exception. should we capture it and throw a new exception from GAMS api
|
|
1343
|
+
# redirect output like in C#!
|
|
1344
|
+
if gams.control.workspace._is_win:
|
|
1345
|
+
si = subprocess.STARTUPINFO()
|
|
1346
|
+
try:
|
|
1347
|
+
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
1348
|
+
si.wShowWindow = subprocess.SW_HIDE
|
|
1349
|
+
except:
|
|
1350
|
+
si.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
|
|
1351
|
+
si.wShowWindow = subprocess._subprocess.SW_HIDE
|
|
1352
|
+
self._p = subprocess.Popen(
|
|
1353
|
+
self._workspace._system_directory
|
|
1354
|
+
+ os.sep
|
|
1355
|
+
+ "gams.exe dummy pf="
|
|
1356
|
+
+ self._job_name
|
|
1357
|
+
+ ".pf",
|
|
1358
|
+
stdout=stdout_val,
|
|
1359
|
+
cwd=self._workspace._working_directory,
|
|
1360
|
+
startupinfo=si,
|
|
1361
|
+
creationflags=subprocess.CREATE_NEW_CONSOLE,
|
|
1362
|
+
)
|
|
1363
|
+
else:
|
|
1364
|
+
self._p = subprocess.Popen(
|
|
1365
|
+
[
|
|
1366
|
+
self._workspace._system_directory + os.sep + "gams",
|
|
1367
|
+
"dummy",
|
|
1368
|
+
"pf=" + self._job_name + ".pf",
|
|
1369
|
+
],
|
|
1370
|
+
stdout=stdout_val,
|
|
1371
|
+
cwd=self._workspace._working_directory,
|
|
1372
|
+
)
|
|
1373
|
+
|
|
1374
|
+
if capture_output:
|
|
1375
|
+
write_buffer = hasattr(output, "buffer") and sys.platform == "win32"
|
|
1376
|
+
stdout_reader = io.TextIOWrapper(self._p.stdout, newline="")
|
|
1377
|
+
while True:
|
|
1378
|
+
try:
|
|
1379
|
+
data = stdout_reader.readline()
|
|
1380
|
+
except:
|
|
1381
|
+
pass
|
|
1382
|
+
if data == "" and self._p.poll() != None:
|
|
1383
|
+
break
|
|
1384
|
+
if self._workspace._debug >= gams.control.workspace.DebugLevel.ShowLog:
|
|
1385
|
+
print(data, end="", flush=True)
|
|
1386
|
+
else:
|
|
1387
|
+
if write_buffer:
|
|
1388
|
+
output.buffer.write(
|
|
1389
|
+
data.encode()
|
|
1390
|
+
) # write directly to the buffer if possible to avoid extra new lines e.g. for files opened without newline='' on Windows
|
|
1391
|
+
else:
|
|
1392
|
+
output.write(data)
|
|
1393
|
+
output.flush()
|
|
1394
|
+
|
|
1395
|
+
stdout_reader.close()
|
|
1396
|
+
exitcode = self._p.wait()
|
|
1397
|
+
|
|
1398
|
+
if create_out_db == True:
|
|
1399
|
+
gdx_path = os.path.splitext(tmp_opt.gdx)[0]
|
|
1400
|
+
gdx_path = gdx_path + ".gdx"
|
|
1401
|
+
if not os.path.isabs(gdx_path):
|
|
1402
|
+
gdx_path = os.path.join(self._workspace._working_directory, gdx_path)
|
|
1403
|
+
if os.path.isfile(gdx_path):
|
|
1404
|
+
self._out_db = GamsDatabase(
|
|
1405
|
+
self._workspace,
|
|
1406
|
+
database_name=os.path.splitext(os.path.basename(gdx_path))[0],
|
|
1407
|
+
gdx_file_name=gdx_path,
|
|
1408
|
+
force_name=True,
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
if exitcode != 0:
|
|
1412
|
+
if (
|
|
1413
|
+
self._workspace._debug
|
|
1414
|
+
< gams.control.workspace.DebugLevel.KeepFilesOnError
|
|
1415
|
+
and self._workspace._using_tmp_working_dir
|
|
1416
|
+
):
|
|
1417
|
+
raise gams.control.workspace.GamsExceptionExecution(
|
|
1418
|
+
"GAMS return code not 0 ("
|
|
1419
|
+
+ str(exitcode)
|
|
1420
|
+
+ "), set the debug flag of the GamsWorkspace constructor to DebugLevel.KeepFilesOnError or higher or define a working_directory to receive a listing file with more details",
|
|
1421
|
+
exitcode,
|
|
1422
|
+
self._workspace,
|
|
1423
|
+
)
|
|
1424
|
+
else:
|
|
1425
|
+
raise gams.control.workspace.GamsExceptionExecution(
|
|
1426
|
+
"GAMS return code not 0 ("
|
|
1427
|
+
+ str(exitcode)
|
|
1428
|
+
+ "), check "
|
|
1429
|
+
+ self._workspace._working_directory
|
|
1430
|
+
+ os.path.sep
|
|
1431
|
+
+ tmp_opt.output
|
|
1432
|
+
+ " for more details",
|
|
1433
|
+
exitcode,
|
|
1434
|
+
self._workspace,
|
|
1435
|
+
)
|
|
1436
|
+
self._p = None
|
|
1437
|
+
|
|
1438
|
+
self._remove_tmp_cp(tmp_cp, checkpoint)
|
|
1439
|
+
self._remove_tmp_opt(tmp_opt, pf_file_name)
|
|
1440
|
+
|
|
1441
|
+
def run_engine(
|
|
1442
|
+
self,
|
|
1443
|
+
engine_configuration,
|
|
1444
|
+
extra_model_files=None,
|
|
1445
|
+
engine_options=None,
|
|
1446
|
+
gams_options=None,
|
|
1447
|
+
checkpoint=None,
|
|
1448
|
+
output=None,
|
|
1449
|
+
create_out_db=True,
|
|
1450
|
+
databases=None,
|
|
1451
|
+
remove_results=True,
|
|
1452
|
+
):
|
|
1453
|
+
"""
|
|
1454
|
+
@brief Run GamsJob on GAMS Engine
|
|
1455
|
+
@param engine_configuration GamsEngineConfiguration object
|
|
1456
|
+
@param extra_model_files List of additional file paths (apart from main file) required to run the model (e.g. include files)
|
|
1457
|
+
@param engine_options Dictionary of GAMS Engine options to control job execution
|
|
1458
|
+
@param gams_options GAMS options to control job
|
|
1459
|
+
@param checkpoint GamsCheckpoint to be created by GamsJob
|
|
1460
|
+
@param output Stream to capture GAMS log (e.g. sys.stdout or an object created by the build-in function open())
|
|
1461
|
+
@param create_out_db Flag to define if out_db should be created
|
|
1462
|
+
@param databases Either a GamsDatabase or a list of GamsDatabases to be read by the GamsJob
|
|
1463
|
+
@param remove_results Remove results from GAMS Engine after downloading them
|
|
1464
|
+
"""
|
|
1465
|
+
|
|
1466
|
+
import urllib3
|
|
1467
|
+
import certifi
|
|
1468
|
+
|
|
1469
|
+
if not isinstance(engine_configuration, GamsEngineConfiguration):
|
|
1470
|
+
raise gams.control.workspace.GamsException(
|
|
1471
|
+
"engine_configuration is not a valid GamsEngineConfiguration instance",
|
|
1472
|
+
self._workspace,
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1475
|
+
request_headers = {
|
|
1476
|
+
"Authorization": engine_configuration._get_auth_header(),
|
|
1477
|
+
"User-Agent": "GAMS Python API",
|
|
1478
|
+
"Accept": "application/json",
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
def remove_job_results():
|
|
1482
|
+
if remove_results is not True:
|
|
1483
|
+
return
|
|
1484
|
+
for attempt_number in range(self._max_request_attempts):
|
|
1485
|
+
r = self._http.request(
|
|
1486
|
+
"DELETE",
|
|
1487
|
+
engine_configuration.host + "/jobs/" + self._p._token + "/result",
|
|
1488
|
+
headers=request_headers,
|
|
1489
|
+
)
|
|
1490
|
+
response_data = r.data.decode("utf-8", errors="replace")
|
|
1491
|
+
if r.status in [200, 403]:
|
|
1492
|
+
return
|
|
1493
|
+
elif r.status == 429:
|
|
1494
|
+
# retry
|
|
1495
|
+
time.sleep(2**attempt_number)
|
|
1496
|
+
continue
|
|
1497
|
+
raise gams.control.workspace.GamsException(
|
|
1498
|
+
"Removing job result failed with status code: "
|
|
1499
|
+
+ str(r.status)
|
|
1500
|
+
+ ". Message: "
|
|
1501
|
+
+ response_data,
|
|
1502
|
+
self._workspace,
|
|
1503
|
+
)
|
|
1504
|
+
else:
|
|
1505
|
+
raise gams.control.workspace.GamsException(
|
|
1506
|
+
"Removing job result failed after: "
|
|
1507
|
+
+ str(self._max_request_attempts)
|
|
1508
|
+
+ " attempts. Message: "
|
|
1509
|
+
+ response_data,
|
|
1510
|
+
self._workspace,
|
|
1511
|
+
)
|
|
1512
|
+
|
|
1513
|
+
if (
|
|
1514
|
+
int(urllib3.__version__.split(".")[0]) >= 2
|
|
1515
|
+
): # urllib3 uses TLS 1.2 as minimum version per default beginning with version 2.0
|
|
1516
|
+
self._http = urllib3.PoolManager(
|
|
1517
|
+
cert_reqs="CERT_REQUIRED", ca_certs=certifi.where()
|
|
1518
|
+
)
|
|
1519
|
+
else: # enforce TLS 1.2 as minimum version for urllib<2.0
|
|
1520
|
+
import ssl
|
|
1521
|
+
|
|
1522
|
+
self._http = urllib3.PoolManager(
|
|
1523
|
+
cert_reqs="CERT_REQUIRED",
|
|
1524
|
+
ca_certs=certifi.where(),
|
|
1525
|
+
ssl_minimum_version=ssl.TLSVersion.TLSv1_2,
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1528
|
+
tmp_cp, tmp_opt, pf_file_name, db_paths = self._prepare_run(
|
|
1529
|
+
gams_options,
|
|
1530
|
+
checkpoint,
|
|
1531
|
+
output,
|
|
1532
|
+
create_out_db,
|
|
1533
|
+
databases,
|
|
1534
|
+
relative_paths=True,
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
capture_output = (
|
|
1538
|
+
self._workspace._debug >= gams.control.workspace.DebugLevel.ShowLog
|
|
1539
|
+
or output
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
model_data_zip = io.BytesIO()
|
|
1543
|
+
|
|
1544
|
+
main_file_name = self._file_name
|
|
1545
|
+
model_files = {main_file_name, pf_file_name}
|
|
1546
|
+
model_files.update(db_paths)
|
|
1547
|
+
|
|
1548
|
+
if self._checkpoint_start:
|
|
1549
|
+
model_files.add(self._checkpoint_start._checkpoint_file_name)
|
|
1550
|
+
|
|
1551
|
+
if extra_model_files:
|
|
1552
|
+
if not isinstance(extra_model_files, list):
|
|
1553
|
+
extra_model_files = [extra_model_files]
|
|
1554
|
+
extra_model_files_cleaned = {
|
|
1555
|
+
(
|
|
1556
|
+
x
|
|
1557
|
+
if os.path.isabs(x)
|
|
1558
|
+
else os.path.join(self._workspace._working_directory, x)
|
|
1559
|
+
)
|
|
1560
|
+
for x in extra_model_files
|
|
1561
|
+
}
|
|
1562
|
+
model_files.update(extra_model_files_cleaned)
|
|
1563
|
+
|
|
1564
|
+
with zipfile.ZipFile(model_data_zip, "w", zipfile.ZIP_DEFLATED) as model_data:
|
|
1565
|
+
for model_file in model_files:
|
|
1566
|
+
model_data.write(
|
|
1567
|
+
model_file,
|
|
1568
|
+
arcname=(
|
|
1569
|
+
os.path.relpath(model_file, self._workspace._working_directory)
|
|
1570
|
+
if os.path.isabs(model_file)
|
|
1571
|
+
else model_file
|
|
1572
|
+
),
|
|
1573
|
+
)
|
|
1574
|
+
|
|
1575
|
+
model_data_zip.seek(0)
|
|
1576
|
+
|
|
1577
|
+
file_params = {}
|
|
1578
|
+
|
|
1579
|
+
query_params = copy.deepcopy(engine_options) if engine_options else {}
|
|
1580
|
+
|
|
1581
|
+
query_params["namespace"] = engine_configuration.namespace
|
|
1582
|
+
|
|
1583
|
+
if "data" in query_params or "model_data" in query_params:
|
|
1584
|
+
raise gams.control.workspace.GamsException(
|
|
1585
|
+
"`engine_options` must not include keys `data` or `model_data` . Please use `extra_model_files` to provide additional files to send to GAMS Engine.",
|
|
1586
|
+
self._workspace,
|
|
1587
|
+
)
|
|
1588
|
+
|
|
1589
|
+
if "inex_file" in query_params:
|
|
1590
|
+
if isinstance(query_params["inex_file"], io.IOBase):
|
|
1591
|
+
file_params["inex_file"] = (
|
|
1592
|
+
"inex.json",
|
|
1593
|
+
query_params["inex_file"].read(),
|
|
1594
|
+
"application/json",
|
|
1595
|
+
)
|
|
1596
|
+
else:
|
|
1597
|
+
with open(query_params["inex_file"], "rb") as f:
|
|
1598
|
+
file_params["inex_file"] = (
|
|
1599
|
+
"inex.json",
|
|
1600
|
+
f.read(),
|
|
1601
|
+
"application/json",
|
|
1602
|
+
)
|
|
1603
|
+
del query_params["inex_file"]
|
|
1604
|
+
|
|
1605
|
+
if "model" in query_params:
|
|
1606
|
+
file_params["data"] = ("data.zip", model_data_zip.read(), "application/zip")
|
|
1607
|
+
else:
|
|
1608
|
+
query_params["run"] = tmp_opt._input
|
|
1609
|
+
query_params["model"] = os.path.splitext(tmp_opt._input)[0]
|
|
1610
|
+
file_params["model_data"] = (
|
|
1611
|
+
"data.zip",
|
|
1612
|
+
model_data_zip.read(),
|
|
1613
|
+
"application/zip",
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
model_data_zip.close()
|
|
1617
|
+
|
|
1618
|
+
if "arguments" in query_params:
|
|
1619
|
+
if not isinstance(query_params["arguments"], list):
|
|
1620
|
+
query_params["arguments"] = [query_params["arguments"]]
|
|
1621
|
+
query_params["arguments"].append("pf=" + self._job_name + ".pf")
|
|
1622
|
+
else:
|
|
1623
|
+
query_params["arguments"] = ["pf=" + self._job_name + ".pf"]
|
|
1624
|
+
|
|
1625
|
+
for attempt_number in range(self._max_request_attempts):
|
|
1626
|
+
r = self._http.request(
|
|
1627
|
+
"POST",
|
|
1628
|
+
engine_configuration.host
|
|
1629
|
+
+ "/jobs/?"
|
|
1630
|
+
+ urllib.parse.urlencode(query_params, doseq=True),
|
|
1631
|
+
fields=file_params,
|
|
1632
|
+
headers=request_headers,
|
|
1633
|
+
)
|
|
1634
|
+
response_data = r.data.decode("utf-8", errors="replace")
|
|
1635
|
+
if r.status == 201:
|
|
1636
|
+
break
|
|
1637
|
+
elif r.status == 429:
|
|
1638
|
+
# retry
|
|
1639
|
+
time.sleep(2**attempt_number)
|
|
1640
|
+
continue
|
|
1641
|
+
raise gams.control.workspace.GamsException(
|
|
1642
|
+
"Creating job on GAMS Engine failed with status code: "
|
|
1643
|
+
+ str(r.status)
|
|
1644
|
+
+ ". Message: "
|
|
1645
|
+
+ response_data,
|
|
1646
|
+
self._workspace,
|
|
1647
|
+
)
|
|
1648
|
+
else:
|
|
1649
|
+
raise gams.control.workspace.GamsException(
|
|
1650
|
+
"Creating job on GAMS Engine failed after: "
|
|
1651
|
+
+ str(self._max_request_attempts)
|
|
1652
|
+
+ " attempts. Message: "
|
|
1653
|
+
+ response_data,
|
|
1654
|
+
self._workspace,
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1657
|
+
self._p = GamsEngineJob(
|
|
1658
|
+
json.loads(response_data)["token"], engine_configuration, request_headers
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
poll_logs_sleep_time = 1
|
|
1662
|
+
|
|
1663
|
+
finished = False
|
|
1664
|
+
while not finished:
|
|
1665
|
+
r = self._http.request(
|
|
1666
|
+
"DELETE",
|
|
1667
|
+
engine_configuration.host + "/jobs/" + self._p._token + "/unread-logs",
|
|
1668
|
+
headers=request_headers,
|
|
1669
|
+
)
|
|
1670
|
+
response_data = r.data.decode("utf-8", errors="replace")
|
|
1671
|
+
if r.status == 429:
|
|
1672
|
+
# too many requests, slow down
|
|
1673
|
+
poll_logs_sleep_time = min(poll_logs_sleep_time + 1, 5)
|
|
1674
|
+
time.sleep(poll_logs_sleep_time)
|
|
1675
|
+
continue
|
|
1676
|
+
elif r.status == 403:
|
|
1677
|
+
# job still in queue
|
|
1678
|
+
time.sleep(poll_logs_sleep_time)
|
|
1679
|
+
continue
|
|
1680
|
+
elif r.status == 308: # partial log not available -> not an error
|
|
1681
|
+
response_data = json.loads(response_data)
|
|
1682
|
+
stdout_data = response_data["message"]
|
|
1683
|
+
r = self._http.request(
|
|
1684
|
+
"GET",
|
|
1685
|
+
engine_configuration.host + "/jobs/" + self._p._token,
|
|
1686
|
+
headers=request_headers,
|
|
1687
|
+
preload_content=False,
|
|
1688
|
+
)
|
|
1689
|
+
response_data = r.data.decode("utf-8", errors="replace")
|
|
1690
|
+
if r.status == 200:
|
|
1691
|
+
response_data = json.loads(response_data)
|
|
1692
|
+
exitcode = response_data["process_status"]
|
|
1693
|
+
finished = True
|
|
1694
|
+
else:
|
|
1695
|
+
raise gams.control.workspace.GamsException(
|
|
1696
|
+
"Getting logs failed with status code: "
|
|
1697
|
+
+ str(r.status)
|
|
1698
|
+
+ ". Message: "
|
|
1699
|
+
+ response_data,
|
|
1700
|
+
self._workspace,
|
|
1701
|
+
)
|
|
1702
|
+
elif r.status == 200:
|
|
1703
|
+
response_data = json.loads(response_data)
|
|
1704
|
+
stdout_data = response_data["message"]
|
|
1705
|
+
exitcode = response_data["gams_return_code"]
|
|
1706
|
+
finished = response_data["queue_finished"] is True
|
|
1707
|
+
else:
|
|
1708
|
+
raise gams.control.workspace.GamsException(
|
|
1709
|
+
"Getting logs failed with status code: "
|
|
1710
|
+
+ str(r.status)
|
|
1711
|
+
+ ". Message: "
|
|
1712
|
+
+ response_data,
|
|
1713
|
+
self._workspace,
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
if capture_output:
|
|
1717
|
+
if self._workspace._debug >= gams.control.workspace.DebugLevel.ShowLog:
|
|
1718
|
+
if stdout_data != "":
|
|
1719
|
+
print(stdout_data, end="")
|
|
1720
|
+
sys.stdout.flush()
|
|
1721
|
+
else:
|
|
1722
|
+
output.write(stdout_data)
|
|
1723
|
+
output.flush()
|
|
1724
|
+
if not finished:
|
|
1725
|
+
time.sleep(poll_logs_sleep_time)
|
|
1726
|
+
|
|
1727
|
+
for attempt_number in range(self._max_request_attempts):
|
|
1728
|
+
r = self._http.request(
|
|
1729
|
+
"GET",
|
|
1730
|
+
engine_configuration.host + "/jobs/" + self._p._token + "/result",
|
|
1731
|
+
headers=request_headers,
|
|
1732
|
+
preload_content=False,
|
|
1733
|
+
)
|
|
1734
|
+
|
|
1735
|
+
if r.status == 200:
|
|
1736
|
+
break
|
|
1737
|
+
|
|
1738
|
+
response_data = r.data.decode("utf-8", errors="replace")
|
|
1739
|
+
if r.status == 429:
|
|
1740
|
+
# retry
|
|
1741
|
+
time.sleep(2**attempt_number)
|
|
1742
|
+
continue
|
|
1743
|
+
|
|
1744
|
+
raise gams.control.workspace.GamsException(
|
|
1745
|
+
"Downloading job result failed with status code: "
|
|
1746
|
+
+ str(r.status)
|
|
1747
|
+
+ ". Message: "
|
|
1748
|
+
+ response_data,
|
|
1749
|
+
self._workspace,
|
|
1750
|
+
)
|
|
1751
|
+
else:
|
|
1752
|
+
raise gams.control.workspace.GamsException(
|
|
1753
|
+
"Downloading job result failed after: "
|
|
1754
|
+
+ str(self._max_request_attempts)
|
|
1755
|
+
+ " attempts. Message: "
|
|
1756
|
+
+ response_data,
|
|
1757
|
+
self._workspace,
|
|
1758
|
+
)
|
|
1759
|
+
|
|
1760
|
+
fd, path = tempfile.mkstemp()
|
|
1761
|
+
|
|
1762
|
+
try:
|
|
1763
|
+
with open(path, "wb") as out:
|
|
1764
|
+
while True:
|
|
1765
|
+
data = r.read(6000)
|
|
1766
|
+
if not data:
|
|
1767
|
+
break
|
|
1768
|
+
out.write(data)
|
|
1769
|
+
|
|
1770
|
+
r.release_conn()
|
|
1771
|
+
|
|
1772
|
+
with zipfile.ZipFile(path, "r") as zip_ref:
|
|
1773
|
+
zip_ref.extractall(self._workspace._working_directory)
|
|
1774
|
+
finally:
|
|
1775
|
+
os.close(fd)
|
|
1776
|
+
os.remove(path)
|
|
1777
|
+
|
|
1778
|
+
remove_job_results()
|
|
1779
|
+
|
|
1780
|
+
if create_out_db:
|
|
1781
|
+
gdx_path = os.path.splitext(tmp_opt.gdx)[0]
|
|
1782
|
+
gdx_path = gdx_path + ".gdx"
|
|
1783
|
+
if not os.path.isabs(gdx_path):
|
|
1784
|
+
gdx_path = os.path.join(self._workspace._working_directory, gdx_path)
|
|
1785
|
+
if os.path.isfile(gdx_path):
|
|
1786
|
+
self._out_db = GamsDatabase(
|
|
1787
|
+
self._workspace,
|
|
1788
|
+
database_name=os.path.splitext(os.path.basename(gdx_path))[0],
|
|
1789
|
+
gdx_file_name=gdx_path,
|
|
1790
|
+
force_name=True,
|
|
1791
|
+
)
|
|
1792
|
+
|
|
1793
|
+
if exitcode != 0:
|
|
1794
|
+
if (
|
|
1795
|
+
self._workspace._debug < gams.control.workspace.DebugLevel.KeepFiles
|
|
1796
|
+
and self._workspace._using_tmp_working_dir
|
|
1797
|
+
):
|
|
1798
|
+
raise gams.control.workspace.GamsExceptionExecution(
|
|
1799
|
+
"GAMS return code not 0 ("
|
|
1800
|
+
+ str(exitcode)
|
|
1801
|
+
+ "), set the debug flag of the GamsWorkspace constructor to DebugLevel.KeepFiles or higher or define a working_directory to receive a listing file with more details",
|
|
1802
|
+
exitcode,
|
|
1803
|
+
self._workspace,
|
|
1804
|
+
)
|
|
1805
|
+
else:
|
|
1806
|
+
raise gams.control.workspace.GamsExceptionExecution(
|
|
1807
|
+
"GAMS return code not 0 ("
|
|
1808
|
+
+ str(exitcode)
|
|
1809
|
+
+ "), check "
|
|
1810
|
+
+ self._workspace._working_directory
|
|
1811
|
+
+ os.path.sep
|
|
1812
|
+
+ tmp_opt.output
|
|
1813
|
+
+ " for more details",
|
|
1814
|
+
exitcode,
|
|
1815
|
+
self._workspace,
|
|
1816
|
+
)
|
|
1817
|
+
self._p = None
|
|
1818
|
+
|
|
1819
|
+
self._remove_tmp_cp(tmp_cp, checkpoint)
|
|
1820
|
+
self._remove_tmp_opt(tmp_opt, pf_file_name)
|
|
1821
|
+
|
|
1822
|
+
def interrupt(self):
|
|
1823
|
+
"""
|
|
1824
|
+
@brief Send Interrupt to running Job. Note: On Mac OS this call requires the tool pstree to be installed
|
|
1825
|
+
@return False if no process available, True otherwise
|
|
1826
|
+
"""
|
|
1827
|
+
|
|
1828
|
+
if self._p != None:
|
|
1829
|
+
if isinstance(self._p, GamsEngineJob):
|
|
1830
|
+
for attempt_number in range(self._max_request_attempts):
|
|
1831
|
+
r = self._http.request(
|
|
1832
|
+
"DELETE",
|
|
1833
|
+
self._p._configuration.host
|
|
1834
|
+
+ "/jobs/"
|
|
1835
|
+
+ self._p._token
|
|
1836
|
+
+ "?hard_kill=false",
|
|
1837
|
+
headers=self._p._request_headers,
|
|
1838
|
+
)
|
|
1839
|
+
response_data = r.data.decode("utf-8", errors="replace")
|
|
1840
|
+
if r.status in [200, 400]:
|
|
1841
|
+
return True
|
|
1842
|
+
elif r.status == 429:
|
|
1843
|
+
# retry
|
|
1844
|
+
time.sleep(2**attempt_number)
|
|
1845
|
+
continue
|
|
1846
|
+
raise gams.control.workspace.GamsException(
|
|
1847
|
+
"Interrupting Engine job failed with status code: "
|
|
1848
|
+
+ str(r.status)
|
|
1849
|
+
+ ". Message: "
|
|
1850
|
+
+ response_data
|
|
1851
|
+
)
|
|
1852
|
+
else:
|
|
1853
|
+
raise gams.control.workspace.GamsException(
|
|
1854
|
+
"Interrupting Engine job failed after: "
|
|
1855
|
+
+ str(self._max_request_attempts)
|
|
1856
|
+
+ " attempts. Message: "
|
|
1857
|
+
+ response_data
|
|
1858
|
+
)
|
|
1859
|
+
|
|
1860
|
+
if gams.control.workspace._is_win:
|
|
1861
|
+
import ctypes
|
|
1862
|
+
|
|
1863
|
+
class _CopyDataStruct(ctypes.Structure):
|
|
1864
|
+
## @cond DOXYGEN_IGNORE_THIS
|
|
1865
|
+
_fields_ = [
|
|
1866
|
+
("dwData", ctypes.c_char_p),
|
|
1867
|
+
("cbData", ctypes.c_ulong),
|
|
1868
|
+
("lpData", ctypes.c_char_p),
|
|
1869
|
+
]
|
|
1870
|
+
## @endcond
|
|
1871
|
+
|
|
1872
|
+
if sys.version_info[0] >= 3:
|
|
1873
|
+
wid = bytes("___GAMSMSGWINDOW___" + str(self._p.pid), "utf-8")
|
|
1874
|
+
receiver = ctypes.windll.user32.FindWindowA(None, wid)
|
|
1875
|
+
cmd = bytes("GAMS Message Interrupt", "utf-8")
|
|
1876
|
+
else:
|
|
1877
|
+
receiver = ctypes.windll.user32.FindWindowA(
|
|
1878
|
+
None, "___GAMSMSGWINDOW___" + str(self._p.pid)
|
|
1879
|
+
)
|
|
1880
|
+
cmd = "GAMS Message Interrupt"
|
|
1881
|
+
|
|
1882
|
+
cs = _CopyDataStruct()
|
|
1883
|
+
|
|
1884
|
+
cs.dwData = 1
|
|
1885
|
+
cs.cbData = len(cmd) + 1
|
|
1886
|
+
cs.lpData = cmd
|
|
1887
|
+
|
|
1888
|
+
WM_COPYDATA = 0x4A
|
|
1889
|
+
ctypes.windll.user32.SendMessageA(
|
|
1890
|
+
receiver, WM_COPYDATA, 0, ctypes.byref(cs)
|
|
1891
|
+
)
|
|
1892
|
+
return True
|
|
1893
|
+
else:
|
|
1894
|
+
proc = subprocess.Popen(
|
|
1895
|
+
["/bin/bash", "-c", "kill -2 " + str(self._p.pid)],
|
|
1896
|
+
stdout=subprocess.PIPE,
|
|
1897
|
+
cwd=self._workspace._working_directory,
|
|
1898
|
+
)
|
|
1899
|
+
else:
|
|
1900
|
+
return False
|