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.
Files changed (257) hide show
  1. gams/__init__.py +27 -0
  2. gams/_version.py +1 -0
  3. gams/connect/__init__.py +28 -0
  4. gams/connect/agents/__init__.py +24 -0
  5. gams/connect/agents/_excel/__init__.py +32 -0
  6. gams/connect/agents/_excel/excelagent.py +312 -0
  7. gams/connect/agents/_excel/workbook.py +155 -0
  8. gams/connect/agents/_sqlconnectors/__init__.py +42 -0
  9. gams/connect/agents/_sqlconnectors/_accesshandler.py +211 -0
  10. gams/connect/agents/_sqlconnectors/_databasehandler.py +250 -0
  11. gams/connect/agents/_sqlconnectors/_mysqlhandler.py +168 -0
  12. gams/connect/agents/_sqlconnectors/_postgreshandler.py +131 -0
  13. gams/connect/agents/_sqlconnectors/_pyodbchandler.py +112 -0
  14. gams/connect/agents/_sqlconnectors/_sqlalchemyhandler.py +74 -0
  15. gams/connect/agents/_sqlconnectors/_sqlitehandler.py +262 -0
  16. gams/connect/agents/_sqlconnectors/_sqlserverhandler.py +179 -0
  17. gams/connect/agents/concatenate.py +440 -0
  18. gams/connect/agents/connectagent.py +743 -0
  19. gams/connect/agents/csvreader.py +675 -0
  20. gams/connect/agents/csvwriter.py +151 -0
  21. gams/connect/agents/domainwriter.py +143 -0
  22. gams/connect/agents/excelreader.py +756 -0
  23. gams/connect/agents/excelwriter.py +467 -0
  24. gams/connect/agents/filter.py +223 -0
  25. gams/connect/agents/gamsreader.py +112 -0
  26. gams/connect/agents/gamswriter.py +239 -0
  27. gams/connect/agents/gdxreader.py +109 -0
  28. gams/connect/agents/gdxwriter.py +146 -0
  29. gams/connect/agents/labelmanipulator.py +303 -0
  30. gams/connect/agents/projection.py +539 -0
  31. gams/connect/agents/pythoncode.py +71 -0
  32. gams/connect/agents/rawcsvreader.py +248 -0
  33. gams/connect/agents/rawexcelreader.py +312 -0
  34. gams/connect/agents/schema/CSVReader.yaml +92 -0
  35. gams/connect/agents/schema/CSVWriter.yaml +44 -0
  36. gams/connect/agents/schema/Concatenate.yaml +52 -0
  37. gams/connect/agents/schema/DomainWriter.yaml +25 -0
  38. gams/connect/agents/schema/ExcelReader.yaml +121 -0
  39. gams/connect/agents/schema/ExcelWriter.yaml +78 -0
  40. gams/connect/agents/schema/Filter.yaml +74 -0
  41. gams/connect/agents/schema/GAMSReader.yaml +20 -0
  42. gams/connect/agents/schema/GAMSWriter.yaml +47 -0
  43. gams/connect/agents/schema/GDXReader.yaml +23 -0
  44. gams/connect/agents/schema/GDXWriter.yaml +32 -0
  45. gams/connect/agents/schema/LabelManipulator.yaml +99 -0
  46. gams/connect/agents/schema/Projection.yaml +24 -0
  47. gams/connect/agents/schema/PythonCode.yaml +6 -0
  48. gams/connect/agents/schema/RawCSVReader.yaml +34 -0
  49. gams/connect/agents/schema/RawExcelReader.yaml +42 -0
  50. gams/connect/agents/schema/SQLReader.yaml +75 -0
  51. gams/connect/agents/schema/SQLWriter.yaml +103 -0
  52. gams/connect/agents/sqlreader.py +301 -0
  53. gams/connect/agents/sqlwriter.py +276 -0
  54. gams/connect/connectdatabase.py +275 -0
  55. gams/connect/connectvalidator.py +93 -0
  56. gams/connect/errors.py +34 -0
  57. gams/control/__init__.py +136 -0
  58. gams/control/database.py +2231 -0
  59. gams/control/execution.py +1900 -0
  60. gams/control/options.py +2792 -0
  61. gams/control/workspace.py +1198 -0
  62. gams/core/__init__.py +24 -0
  63. gams/core/cfg/__init__.py +26 -0
  64. gams/core/cfg/_cfgmcc.cp312-win_amd64.pyd +0 -0
  65. gams/core/cfg/cfgmcc.py +519 -0
  66. gams/core/dct/__init__.py +26 -0
  67. gams/core/dct/_dctmcc.cp312-win_amd64.pyd +0 -0
  68. gams/core/dct/dctmcc.py +574 -0
  69. gams/core/embedded/__init__.py +26 -0
  70. gams/core/embedded/gamsemb.py +1024 -0
  71. gams/core/emp/__init__.py +24 -0
  72. gams/core/emp/emplexer.py +89 -0
  73. gams/core/emp/empyacc.py +281 -0
  74. gams/core/gdx/__init__.py +26 -0
  75. gams/core/gdx/_gdxcc.cp312-win_amd64.pyd +0 -0
  76. gams/core/gdx/gdxcc.py +866 -0
  77. gams/core/gev/__init__.py +26 -0
  78. gams/core/gev/_gevmcc.cp312-win_amd64.pyd +0 -0
  79. gams/core/gev/gevmcc.py +855 -0
  80. gams/core/gmd/__init__.py +26 -0
  81. gams/core/gmd/_gmdcc.cp312-win_amd64.pyd +0 -0
  82. gams/core/gmd/gmdcc.py +917 -0
  83. gams/core/gmo/__init__.py +26 -0
  84. gams/core/gmo/_gmomcc.cp312-win_amd64.pyd +0 -0
  85. gams/core/gmo/gmomcc.py +2046 -0
  86. gams/core/idx/__init__.py +26 -0
  87. gams/core/idx/_idxcc.cp312-win_amd64.pyd +0 -0
  88. gams/core/idx/idxcc.py +510 -0
  89. gams/core/numpy/__init__.py +29 -0
  90. gams/core/numpy/_gams2numpy.cp312-win_amd64.pyd +0 -0
  91. gams/core/numpy/gams2numpy.py +1048 -0
  92. gams/core/opt/__init__.py +26 -0
  93. gams/core/opt/_optcc.cp312-win_amd64.pyd +0 -0
  94. gams/core/opt/optcc.py +840 -0
  95. gams/engine/__init__.py +204 -0
  96. gams/engine/api/__init__.py +13 -0
  97. gams/engine/api/auth_api.py +7653 -0
  98. gams/engine/api/cleanup_api.py +751 -0
  99. gams/engine/api/default_api.py +887 -0
  100. gams/engine/api/hypercube_api.py +2629 -0
  101. gams/engine/api/jobs_api.py +5229 -0
  102. gams/engine/api/licenses_api.py +2220 -0
  103. gams/engine/api/namespaces_api.py +7783 -0
  104. gams/engine/api/usage_api.py +5627 -0
  105. gams/engine/api/users_api.py +5931 -0
  106. gams/engine/api_client.py +804 -0
  107. gams/engine/api_response.py +21 -0
  108. gams/engine/configuration.py +601 -0
  109. gams/engine/exceptions.py +216 -0
  110. gams/engine/models/__init__.py +86 -0
  111. gams/engine/models/bad_input.py +89 -0
  112. gams/engine/models/cleanable_job_result.py +104 -0
  113. gams/engine/models/cleanable_job_result_page.py +113 -0
  114. gams/engine/models/engine_license.py +107 -0
  115. gams/engine/models/files_not_found.py +93 -0
  116. gams/engine/models/forwarded_token_response.py +112 -0
  117. gams/engine/models/generic_key_value_pair.py +89 -0
  118. gams/engine/models/hypercube.py +160 -0
  119. gams/engine/models/hypercube_page.py +111 -0
  120. gams/engine/models/hypercube_summary.py +91 -0
  121. gams/engine/models/hypercube_token.py +97 -0
  122. gams/engine/models/identity_provider.py +107 -0
  123. gams/engine/models/identity_provider_ldap.py +121 -0
  124. gams/engine/models/identity_provider_oauth2.py +146 -0
  125. gams/engine/models/identity_provider_oauth2_scope.py +89 -0
  126. gams/engine/models/identity_provider_oauth2_with_secret.py +152 -0
  127. gams/engine/models/identity_provider_oidc.py +133 -0
  128. gams/engine/models/identity_provider_oidc_with_secret.py +143 -0
  129. gams/engine/models/inex.py +91 -0
  130. gams/engine/models/invitation.py +136 -0
  131. gams/engine/models/invitation_quota.py +106 -0
  132. gams/engine/models/invitation_token.py +87 -0
  133. gams/engine/models/job.py +165 -0
  134. gams/engine/models/job_no_text_entry.py +138 -0
  135. gams/engine/models/job_no_text_entry_page.py +111 -0
  136. gams/engine/models/license.py +91 -0
  137. gams/engine/models/log_piece.py +96 -0
  138. gams/engine/models/message.py +87 -0
  139. gams/engine/models/message_and_token.py +99 -0
  140. gams/engine/models/message_with_webhook_id.py +89 -0
  141. gams/engine/models/model_auth_token.py +87 -0
  142. gams/engine/models/model_configuration.py +125 -0
  143. gams/engine/models/model_default_instance.py +99 -0
  144. gams/engine/models/model_default_user_instance.py +98 -0
  145. gams/engine/models/model_hypercube_job.py +106 -0
  146. gams/engine/models/model_hypercube_usage.py +130 -0
  147. gams/engine/models/model_instance_info.py +116 -0
  148. gams/engine/models/model_instance_info_full.py +123 -0
  149. gams/engine/models/model_instance_pool_info.py +112 -0
  150. gams/engine/models/model_job_labels.py +179 -0
  151. gams/engine/models/model_job_usage.py +133 -0
  152. gams/engine/models/model_pool_usage.py +124 -0
  153. gams/engine/models/model_usage.py +115 -0
  154. gams/engine/models/model_user.py +96 -0
  155. gams/engine/models/model_userinstance_info.py +119 -0
  156. gams/engine/models/model_userinstancepool_info.py +95 -0
  157. gams/engine/models/model_version.py +91 -0
  158. gams/engine/models/models.py +120 -0
  159. gams/engine/models/namespace.py +104 -0
  160. gams/engine/models/namespace_quota.py +96 -0
  161. gams/engine/models/namespace_with_permission.py +96 -0
  162. gams/engine/models/not_found.py +91 -0
  163. gams/engine/models/password_policy.py +97 -0
  164. gams/engine/models/perm_and_username.py +89 -0
  165. gams/engine/models/quota.py +117 -0
  166. gams/engine/models/quota_exceeded.py +97 -0
  167. gams/engine/models/status_code_meaning.py +89 -0
  168. gams/engine/models/stream_entry.py +89 -0
  169. gams/engine/models/system_wide_license.py +92 -0
  170. gams/engine/models/text_entries.py +87 -0
  171. gams/engine/models/text_entry.py +101 -0
  172. gams/engine/models/time_span.py +95 -0
  173. gams/engine/models/time_span_pool_worker.py +99 -0
  174. gams/engine/models/token_forward_error.py +87 -0
  175. gams/engine/models/user.py +127 -0
  176. gams/engine/models/user_group_member.py +96 -0
  177. gams/engine/models/user_groups.py +108 -0
  178. gams/engine/models/vapid_info.py +87 -0
  179. gams/engine/models/webhook.py +138 -0
  180. gams/engine/models/webhook_parameterized_event.py +99 -0
  181. gams/engine/py.typed +0 -0
  182. gams/engine/rest.py +258 -0
  183. gams/magic/__init__.py +32 -0
  184. gams/magic/gams_magic.py +142 -0
  185. gams/magic/interactive.py +402 -0
  186. gams/tools/__init__.py +30 -0
  187. gams/tools/errors.py +34 -0
  188. gams/tools/toolcollection/__init__.py +24 -0
  189. gams/tools/toolcollection/alg/__init__.py +24 -0
  190. gams/tools/toolcollection/alg/rank.py +51 -0
  191. gams/tools/toolcollection/data/__init__.py +24 -0
  192. gams/tools/toolcollection/data/csvread.py +444 -0
  193. gams/tools/toolcollection/data/csvwrite.py +311 -0
  194. gams/tools/toolcollection/data/exceldump.py +47 -0
  195. gams/tools/toolcollection/data/sqlitewrite.py +276 -0
  196. gams/tools/toolcollection/gdxservice/__init__.py +24 -0
  197. gams/tools/toolcollection/gdxservice/gdxencoding.py +104 -0
  198. gams/tools/toolcollection/gdxservice/gdxrename.py +94 -0
  199. gams/tools/toolcollection/linalg/__init__.py +24 -0
  200. gams/tools/toolcollection/linalg/cholesky.py +57 -0
  201. gams/tools/toolcollection/linalg/eigenvalue.py +56 -0
  202. gams/tools/toolcollection/linalg/eigenvector.py +58 -0
  203. gams/tools/toolcollection/linalg/invert.py +55 -0
  204. gams/tools/toolcollection/linalg/ols.py +138 -0
  205. gams/tools/toolcollection/tooltemplate.py +321 -0
  206. gams/tools/toolcollection/win32/__init__.py +24 -0
  207. gams/tools/toolcollection/win32/excelmerge.py +93 -0
  208. gams/tools/toolcollection/win32/exceltalk.py +76 -0
  209. gams/tools/toolcollection/win32/msappavail.py +49 -0
  210. gams/tools/toolcollection/win32/shellexecute.py +54 -0
  211. gams/tools/tools.py +116 -0
  212. gams/transfer/__init__.py +35 -0
  213. gams/transfer/_abcs/__init__.py +37 -0
  214. gams/transfer/_abcs/container_abcs.py +433 -0
  215. gams/transfer/_internals/__init__.py +63 -0
  216. gams/transfer/_internals/algorithms.py +436 -0
  217. gams/transfer/_internals/casepreservingdict.py +124 -0
  218. gams/transfer/_internals/constants.py +270 -0
  219. gams/transfer/_internals/domainviolation.py +103 -0
  220. gams/transfer/_internals/specialvalues.py +172 -0
  221. gams/transfer/containers/__init__.py +26 -0
  222. gams/transfer/containers/_container.py +1794 -0
  223. gams/transfer/containers/_io/__init__.py +28 -0
  224. gams/transfer/containers/_io/containers.py +164 -0
  225. gams/transfer/containers/_io/gdx.py +1029 -0
  226. gams/transfer/containers/_io/gmd.py +872 -0
  227. gams/transfer/containers/_mixins/__init__.py +26 -0
  228. gams/transfer/containers/_mixins/ccc.py +1274 -0
  229. gams/transfer/syms/__init__.py +33 -0
  230. gams/transfer/syms/_methods/__init__.py +24 -0
  231. gams/transfer/syms/_methods/tables.py +120 -0
  232. gams/transfer/syms/_methods/toDict.py +115 -0
  233. gams/transfer/syms/_methods/toList.py +83 -0
  234. gams/transfer/syms/_methods/toValue.py +60 -0
  235. gams/transfer/syms/_mixins/__init__.py +32 -0
  236. gams/transfer/syms/_mixins/equals.py +626 -0
  237. gams/transfer/syms/_mixins/generateRecords.py +499 -0
  238. gams/transfer/syms/_mixins/pivot.py +313 -0
  239. gams/transfer/syms/_mixins/pve.py +627 -0
  240. gams/transfer/syms/_mixins/sa.py +27 -0
  241. gams/transfer/syms/_mixins/sapve.py +27 -0
  242. gams/transfer/syms/_mixins/saua.py +27 -0
  243. gams/transfer/syms/_mixins/sauapve.py +199 -0
  244. gams/transfer/syms/_mixins/spve.py +1528 -0
  245. gams/transfer/syms/_mixins/ve.py +936 -0
  246. gams/transfer/syms/container_syms/__init__.py +31 -0
  247. gams/transfer/syms/container_syms/_alias.py +984 -0
  248. gams/transfer/syms/container_syms/_equation.py +333 -0
  249. gams/transfer/syms/container_syms/_parameter.py +973 -0
  250. gams/transfer/syms/container_syms/_set.py +604 -0
  251. gams/transfer/syms/container_syms/_universe_alias.py +461 -0
  252. gams/transfer/syms/container_syms/_variable.py +321 -0
  253. gamsapi-52.5.0.dist-info/METADATA +150 -0
  254. gamsapi-52.5.0.dist-info/RECORD +257 -0
  255. gamsapi-52.5.0.dist-info/WHEEL +5 -0
  256. gamsapi-52.5.0.dist-info/licenses/LICENSE +22 -0
  257. 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