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,743 @@
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
+ from __future__ import annotations
27
+
28
+ from typing import TYPE_CHECKING
29
+
30
+ if TYPE_CHECKING:
31
+ from gams.connect.connectdatabase import ConnectDatabase
32
+
33
+ from abc import ABC, abstractmethod
34
+ import importlib.resources
35
+ import sys
36
+ import yaml
37
+ from gams.connect.connectvalidator import ConnectValidator
38
+ from gams.connect.errors import GamsConnectException
39
+ import pandas as pd
40
+ import numpy as np
41
+ import gams.transfer as gt
42
+ from gams.transfer._internals import generate_unique_labels
43
+ from gams import GamsWorkspace
44
+
45
+
46
+ class ConnectAgent(ABC):
47
+ """
48
+ An abstract base class that defines the structure for Connect agent implementations.
49
+
50
+ Subclasses must implement the abstract methods to define their own behavior.
51
+ This class provides a common interface and some shared functionality.
52
+
53
+ Parameters
54
+ ----------
55
+ cdb : gams.connect.connectdatabase.ConnectDatabase.
56
+ The Connect Database responsible for the Connect agent.
57
+ inst : dict
58
+ A nested data structure consisting of dictionaries and lists containing the instructions to be executed by the Connect agent.
59
+
60
+ Abstract methods:
61
+ - __init__: Connect agent constructor.
62
+ - execute: Called by the ConnectDatabase after open() and before close().
63
+
64
+ Non-abstract methods:
65
+ - _apply_value_substitutions: Substitutes values in the last column of df as provided in value_subs and returns the modified DataFrame.
66
+ - _connect_error: Raises a GamsConnectException.
67
+ - _describe_container: Logs the status of a gams.transfer.Container.
68
+ - _dict_get: Like dict.get() but handles keys with None value as if they were missing.
69
+ - _log_instructions: Logs raw and processed instructions in a table format.
70
+ - _normalize_instructions: Performs normalization of the given instructions.
71
+ - _pandas_version_before: Checks if the installed pandas version is before the given version string x.y.
72
+ - _sym_records_no_none: Returns the records of a symbol or an empty DataFrame with appropriate column names for None records.
73
+ - _transform_sym_none_to_empty: Sets the records of a symbol to an empty DataFrame with appropriate column names if the given records are None.
74
+ - _update_sym_inst: Updates None values in the symbols scope instructions with the corresponding values from the root scope for all keys that exist in both instruction dictionaries.
75
+ - _symbols_exist_gmd: Raises a GamsConnectException if a single symbol or at least one symbol of a given list of symbols does not exist in the GMD object.
76
+ - _symbols_exist_gdx: Raises a GamsConnectException if a single symbol or at least one symbol of a given list of symbols does not exist in the GDX file.
77
+ - _symbols_exist_cdb: Checks the Connect database for existing symbols and raises a GamsConnectException depending on parameter should_exist.
78
+ - _log_instructions_r: Recursive helper method for instruction logging.
79
+ - _replace_no_warn: Helper method for _apply_value_substitutions().
80
+ """
81
+
82
+ @abstractmethod
83
+ def __init__(self, cdb: ConnectDatabase, inst, agent_index):
84
+ self._agent_index = agent_index
85
+ self._agent_name = self.__class__.__name__
86
+ self._col_widths = [30, 30, 30]
87
+ self._no_option_list = [
88
+ "connection",
89
+ "connectionArguments",
90
+ "dimensionMap",
91
+ "dTypeMap",
92
+ "indexSubstitutions",
93
+ "readCSVArguments",
94
+ "readSQLArguments",
95
+ "toCSVArguments",
96
+ "toSQLArguments",
97
+ "valueSubstitutions",
98
+ ]
99
+ self._system_directory = cdb.system_directory
100
+ self._cdb = cdb
101
+ self._trace = 0
102
+ self._inst_raw = inst
103
+ self._inst = self._normalize_instructions(inst)
104
+
105
+ def _normalize_instructions(self, inst):
106
+ """
107
+ Performs normalization of the given instructions.
108
+
109
+ Parameters
110
+ ----------
111
+ inst : dict
112
+ The instructions to be normalized.
113
+
114
+ Returns
115
+ -------
116
+ dict
117
+ The normalized instructions.
118
+ """
119
+ v = ConnectValidator(self._cdb.load_schema(self))
120
+ inst = v.normalized(inst)
121
+ inst = v.normalize_of_rules(inst)
122
+ return inst
123
+
124
+ def _log_instructions_r(
125
+ self, inst, has_inst_raw, inst_raw=None, level=0, is_list=False, no_option=False
126
+ ):
127
+ """
128
+ Recursive helper method for instruction logging.
129
+
130
+ Parameters
131
+ ----------
132
+ inst : dict
133
+ The normalized/processed instructions.
134
+ has_inst_raw : bool
135
+ Flag indicating if raw instructions has been passed to _log_instructions().
136
+ inst_raw : dict, optional
137
+ The raw instructions, by default None.
138
+ level : int, optional
139
+ Level used for log indentation where one level corresponds to two spaces, by default 0.
140
+ is_list : bool, optional
141
+ Flag indicating if the current item is part of a list, by default False.
142
+ no_option : bool
143
+ Flag indicating if an item should be treated as a Connect agent option or as a generic data structure, by default False.
144
+ """
145
+ istr = " " * level * 2
146
+ self._cdb.print_log("")
147
+ if is_list:
148
+ iterable = enumerate(inst)
149
+ format_key = lambda idx: f"{istr}({idx+1})"
150
+ get_raw_value = lambda idx: inst_raw[idx] if inst_raw else None
151
+ else:
152
+ iterable = inst.items()
153
+ format_key = lambda k: f"{istr}{k}: "
154
+ get_raw_value = lambda k: (
155
+ inst_raw.get(k, "") if isinstance(inst_raw, dict) else inst_raw
156
+ )
157
+ for k, v in iterable:
158
+ s_option = "" if no_option else format_key(k)
159
+ self._cdb.print_log(f"{s_option:<{self._col_widths[0]}}", end="")
160
+ v_raw = get_raw_value(k)
161
+ if isinstance(v, dict) and len(v) > 0:
162
+ if not isinstance(v_raw, dict) and has_inst_raw:
163
+ self._cdb.print_log(f">{v_raw}<", end="")
164
+ if k in self._no_option_list:
165
+ self._log_instructions_r(
166
+ v, has_inst_raw, v_raw, level + 1, no_option=True
167
+ )
168
+ else:
169
+ self._log_instructions_r(v, has_inst_raw, v_raw, level + 1)
170
+ elif isinstance(v, list) and len(v) > 0:
171
+ self._log_instructions_r(
172
+ v, has_inst_raw, v_raw, level + 1, is_list=True
173
+ )
174
+ else:
175
+ if no_option:
176
+ s_input = f">{k}<: >{v_raw}<" if inst_raw else ""
177
+ s_processed = f">{k}<: >{v}<"
178
+ else:
179
+ s_input = f">{v_raw}<" if isinstance(inst_raw, (dict, list)) else ""
180
+ s_processed = f">{v}<"
181
+ if has_inst_raw:
182
+ self._cdb.print_log(
183
+ f"{s_input:<{self._col_widths[1]}}{s_processed}"
184
+ )
185
+ else:
186
+ self._cdb.print_log(f"{s_processed}")
187
+
188
+ def _log_instructions(self, inst, inst_raw=None, description=None):
189
+ """
190
+ Logs raw and processed instructions in a table format. If inst_raw is
191
+ None, only the instructions given in inst are printed to the log.
192
+
193
+ Parameters
194
+ ----------
195
+ inst : dict
196
+ The normalized/processed instructions.
197
+ inst_raw : dict, optional
198
+ The raw instructions, by default None.
199
+ description : str, optional
200
+ A describtion to be printed to the table header or the Connect agent class if omitted, by default None.
201
+ """
202
+
203
+ if description is None:
204
+ agent_info = self._cdb._get_idx_str(self._agent_name, self._agent_index)
205
+ description = f"{agent_info}:"
206
+
207
+ header = f"{'Option':<{self._col_widths[0]}}"
208
+ ruler = (
209
+ "-" * sum(self._col_widths[:2])
210
+ if inst_raw is None
211
+ else "-" * sum(self._col_widths)
212
+ )
213
+ if inst_raw is not None:
214
+ header += f"{'Input':<{self._col_widths[1]}}"
215
+ header += "Processed Input"
216
+ self._cdb.print_log("")
217
+ self._cdb.print_log(ruler)
218
+ self._cdb.print_log(f"{description}")
219
+ self._cdb.print_log(header)
220
+ self._cdb.print_log(ruler, end="")
221
+
222
+ if isinstance(inst, dict):
223
+ self._log_instructions_r(inst, inst_raw is not None, inst_raw)
224
+ else:
225
+ self._cdb.print_log(
226
+ f"Warning: Can not log instructions because type was not 'dict' but {type(inst).__name__}."
227
+ )
228
+
229
+ self._cdb.print_log(ruler + "\n")
230
+
231
+ def _connect_error(self, msg, agent_info=True):
232
+ """
233
+ Raises a GamsConnectException.
234
+
235
+ Parameters
236
+ ----------
237
+ msg : str
238
+ Message for the exception.
239
+ agent_info : bool
240
+ Add agent name and index to the exception, by default True.
241
+
242
+ Raises
243
+ ----------
244
+ GamsConnectException
245
+ Always.
246
+ """
247
+ if agent_info:
248
+ agent_info = self._cdb._get_idx_str(self._agent_name, self._agent_index)
249
+ msg = f"{agent_info} failed: " + msg
250
+ raise GamsConnectException(msg, traceback=self._trace > 0)
251
+
252
+ def _describe_container(self, m, msg):
253
+ """
254
+ Logs the status of a gams.transfer.Container
255
+
256
+ Parameters
257
+ ----------
258
+ m : gams.transfer.Container
259
+ The Container to be used.
260
+ msg : str
261
+ A custom message to be printed to the log.
262
+ """
263
+ try:
264
+ with pd.option_context("display.max_columns", None):
265
+ self._cdb.print_log(f"{msg}\n")
266
+ if len(m.listSets()):
267
+ self._cdb.print_log(f"Sets:\n{ m.describeSets() }\n")
268
+ if len(m.listAliases()):
269
+ self._cdb.print_log(f"Aliases:\n{ m.describeAliases() }\n")
270
+ if len(m.listParameters()):
271
+ self._cdb.print_log(f"Parameters:\n{m.describeParameters()}\n")
272
+ if len(m.listEquations()):
273
+ self._cdb.print_log(f"Equations:\n{ m.describeEquations() }\n")
274
+ if len(m.listVariables()):
275
+ self._cdb.print_log(f"Variables:\n{ m.describeVariables() }\n")
276
+ except Exception as e:
277
+ self._cdb.print_log(
278
+ f"Exception in ConnectDatabase._describe_container turned into warning: {e}\n"
279
+ )
280
+
281
+ def _pandas_version_before(self, pandas_version, version_string):
282
+ """
283
+ Checks if the installed pandas version is before the given version string x.y.
284
+
285
+ Parameters
286
+ ----------
287
+ pandas_version : str
288
+ Installed pandas version.
289
+ version_string : str
290
+ Pandas version to check agains in the format x.y.
291
+
292
+ Returns
293
+ ----------
294
+ bool
295
+ True if pandas version is before version string and otherwise False.
296
+ """
297
+ pd_ver = list(map(int, pandas_version.split(".")))
298
+ ver = list(map(int, version_string.split(".")))
299
+ if pd_ver[0] < ver[0] or (pd_ver[0] == ver[0] and pd_ver[1] < ver[1]):
300
+ return True
301
+ return False
302
+
303
+ def _transform_sym_none_to_empty(self, sym):
304
+ """
305
+ Sets the records of a symbol to an empty DataFrame
306
+ with appropriate column names if the given records are None.
307
+
308
+ Parameters
309
+ ----------
310
+ sym : gams.transfer.Alias, gams.transfer.Equation, gams.transfer.Parameter, gams.transfer.Set, gams.transfer.Variable
311
+ Symbol to be checked for None records.
312
+ """
313
+ if sym.records is None:
314
+ sym.setRecords(self._sym_records_no_none(sym, False))
315
+
316
+ def _sym_records_no_none(self, sym, set_dtypes=True):
317
+ """
318
+ Returns the records of a symbol or an empty DataFrame
319
+ with appropriate column names for None records.
320
+
321
+ Parameters
322
+ ----------
323
+ sym : gams.transfer.Alias, gams.transfer.Equation, gams.transfer.Parameter, gams.transfer.Set, gams.transfer.Variable
324
+ Symbol to be checked for None records.
325
+ set_dtypes : bool, optional
326
+ Sets gams.transfer-like column types if True, by default True.
327
+
328
+ Returns
329
+ ----------
330
+ pandas.DataFrame
331
+ The DataFrame of the symbol if records are not None, otherwise an empty DataFrame.
332
+ """
333
+ if sym.records is None:
334
+ attributes = sym._attributes
335
+ cols = generate_unique_labels(sym.domain_names) + attributes
336
+ df = pd.DataFrame(columns=cols)
337
+ if set_dtypes: # set column dtypes as gams.transfer would do
338
+ for col in cols[: sym.dimension]:
339
+ df[col] = df[col].astype("category")
340
+ if isinstance(sym, (gt.Parameter, gt.Variable, gt.Equation)):
341
+ for col in attributes:
342
+ df[col] = df[col].astype(float)
343
+ elif isinstance(
344
+ sym, gt.UniverseAlias
345
+ ): # nothing to do for UniverseAlias
346
+ pass
347
+ else: # sets, alias
348
+ df[attributes[0]] = df[attributes[0]].astype(object)
349
+ else:
350
+ df = sym.records
351
+ return df
352
+
353
+ def _replace_no_warn(self, df, vs, mask=None):
354
+ """
355
+ Helper method for _apply_value_substitutions(). Substitutes values in the last column of df
356
+ as provided in vs and returns the modified DataFrame.
357
+
358
+ Parameters
359
+ ----------
360
+ df : pandas.DataFrame
361
+ The DataFrame to be used for replacing values in its last column.
362
+ vs : dict
363
+ Dictionary containing the keys that will be replaced by their values.
364
+ mask : numpy.ndarray, optional
365
+ A numpy mask that can be used to exclude certain value positions from the substitution, by default None.
366
+
367
+ Returns
368
+ ----------
369
+ pandas.DataFrame
370
+ The DataFrame with the replaced values.
371
+ """
372
+ if vs and len(vs) > 0:
373
+ if df[df.columns[-1]].dtype != object and any(
374
+ isinstance(v, str) for v in vs.values()
375
+ ):
376
+ for k, v in vs.items():
377
+ if isinstance(v, str) and k in df[df.columns[-1]].values:
378
+ df[df.columns[-1]] = df[df.columns[-1]].astype(object)
379
+ break
380
+ if mask is None:
381
+ # pandas-version-check
382
+ if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
383
+ df.iloc[:, -1] = df.iloc[:, -1].replace(vs)
384
+ else: # pandas >= 2.2.0
385
+ with pd.option_context("future.no_silent_downcasting", True):
386
+ df.iloc[:, -1] = df.iloc[:, -1].replace(vs).infer_objects()
387
+ else:
388
+ # pandas-version-check
389
+ if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
390
+ df.iloc[~mask, -1] = df.iloc[~mask, -1].replace(vs)
391
+ else: # pandas >= 2.2.0
392
+ with pd.option_context("future.no_silent_downcasting", True):
393
+ df.iloc[~mask, -1] = (
394
+ df.iloc[~mask, -1].replace(vs).infer_objects()
395
+ )
396
+ return df
397
+
398
+ def _apply_value_substitutions(
399
+ self,
400
+ df,
401
+ value_subs,
402
+ sym_type,
403
+ sv_eps=gt.SpecialValues.EPS,
404
+ sv_na=gt.SpecialValues.NA,
405
+ sv_undef=gt.SpecialValues.UNDEF,
406
+ sv_posinf=gt.SpecialValues.POSINF,
407
+ sv_neginf=gt.SpecialValues.NEGINF,
408
+ ):
409
+ """
410
+ Substitutes values in the last column of df as provided in value_subs and returns the modified DataFrame.
411
+
412
+ Parameters
413
+ ----------
414
+ df : pandas.DataFrame
415
+ DataFrame on which the value substitutions is applied assuming that the last column contains the values.
416
+ value_subs : dict
417
+ Dictionary containing keys to be substituted by values. For GAMS special values, "EPS', "NA", "UNDEF", "INF", and "-INF"
418
+ should be used as keys in the dictionary.
419
+ sym_type : str
420
+ Symbol type. Can be either "par" or "set".
421
+ sv_eps : str, int, float, optional
422
+ Default value used for replacing EPS values (gams.transfer.SpecialValues.EPS) in case "EPS" is not contained in value_subs, by default gams.transfer.SpecialValues.EPS.
423
+ sv_na : str, int, float, optional
424
+ Default value used for replacing NA values (gams.transfer.SpecialValues.NA) in case "NA" is not contained in value_subs, by default gams.transfer.SpecialValues.NA.
425
+ sv_undef : str, int, float, optional
426
+ Default value used for replacing UNDEF values (gams.transfer.SpecialValues.UNDEF) in case "UNDEF" is not contained in value_subs, by default gams.transfer.SpecialValues.UNDEF.
427
+ sv_posinf : str, int, float, optional
428
+ Default value used for replacing INF values (gams.transfer.SpecialValues.POSINF) in case "INF" is not contained in value_subs, by default gams.transfer.SpecialValues.POSINF.
429
+ sv_neginf : str, int, float, optional
430
+ Default value used for replacing -INF values (gams.transfer.SpecialValues.NEGINF) in case "-INF" is not contained in value_subs, by default gams.transfer.SpecialValues.NEGINF.
431
+
432
+ Returns
433
+ -------
434
+ pandas.DataFrame
435
+ The modified DataFrame with applied substitutions for the last column. Depending on the used substitutions, the dtype of
436
+ that column might be changed to object.
437
+ """
438
+
439
+ vs = value_subs.copy() if value_subs is not None else {}
440
+ if vs: # convert int/complex to float
441
+ vs = {
442
+ k: float(v) if isinstance(v, (int, complex)) else v
443
+ for k, v in value_subs.items()
444
+ }
445
+ if sym_type == "par":
446
+
447
+ def replace_sv(df, eps_val, na_val, undef_val, posinf_val, neginf_val):
448
+ arr = df.iloc[:, -1].values
449
+
450
+ # create special values masks
451
+ eps_mask = gt.SpecialValues.isEps(arr)
452
+ has_eps = eps_mask.any()
453
+
454
+ na_mask = gt.SpecialValues.isNA(arr)
455
+ has_na = na_mask.any()
456
+
457
+ undef_mask = gt.SpecialValues.isUndef(arr)
458
+ has_undef = undef_mask.any()
459
+
460
+ posinf_mask = gt.SpecialValues.isPosInf(arr)
461
+ has_posinf = posinf_mask.any()
462
+
463
+ neginf_mask = gt.SpecialValues.isNegInf(arr)
464
+ has_neginf = neginf_mask.any()
465
+
466
+ # replace special values
467
+ if has_eps or has_na or has_undef or has_posinf or has_neginf:
468
+ if (
469
+ has_eps
470
+ and isinstance(eps_val, str)
471
+ or has_na
472
+ and isinstance(na_val, str)
473
+ or has_undef
474
+ and isinstance(undef_val, str)
475
+ or has_posinf
476
+ and isinstance(posinf_val, str)
477
+ or has_neginf
478
+ and isinstance(neginf_val, str)
479
+ ):
480
+ arr = arr.astype(object)
481
+ mask = eps_mask | na_mask | undef_mask | posinf_mask | neginf_mask
482
+ if has_eps:
483
+ arr[eps_mask] = eps_val
484
+ if has_na:
485
+ arr[na_mask] = na_val
486
+ if has_undef:
487
+ arr[undef_mask] = undef_val
488
+ if has_posinf:
489
+ arr[posinf_mask] = posinf_val
490
+ if has_neginf:
491
+ arr[neginf_mask] = neginf_val
492
+ df[df.columns[-1]] = arr
493
+ return df, mask
494
+ return df, None
495
+
496
+ # determine special values substitutions
497
+ gt_eps_in_vs = any(
498
+ not isinstance(k, str) and gt.SpecialValues.isEps(k) for k in vs.keys()
499
+ )
500
+ if "EPS" in vs.keys():
501
+ if gt_eps_in_vs:
502
+ self._cdb.print_log(
503
+ f'Warning: "EPS" ({vs["EPS"]}) overwrites -0.0 ({vs[gt.SpecialValues.EPS]}) in valueSubstitutions.'
504
+ )
505
+ del vs[gt.SpecialValues.EPS]
506
+ eps_val = vs["EPS"]
507
+ del vs["EPS"]
508
+ elif gt_eps_in_vs:
509
+ eps_val = vs[gt.SpecialValues.EPS]
510
+ del vs[gt.SpecialValues.EPS]
511
+ else:
512
+ eps_val = sv_eps
513
+
514
+ if "NA" in vs.keys():
515
+ na_val = vs["NA"]
516
+ del vs["NA"]
517
+ else:
518
+ na_val = sv_na
519
+
520
+ if "UNDEF" in vs.keys():
521
+ undef_val = vs["UNDEF"]
522
+ del vs["UNDEF"]
523
+ else:
524
+ undef_val = sv_undef
525
+
526
+ if "INF" in vs.keys():
527
+ if gt.SpecialValues.POSINF in vs:
528
+ self._cdb.print_log(
529
+ f'Warning: "INF" ({vs["INF"]}) overwrites .inf ({vs[float("inf")]}) in valueSubstitutions.'
530
+ )
531
+ del vs[gt.SpecialValues.POSINF]
532
+ posinf_val = vs["INF"]
533
+ del vs["INF"]
534
+ elif gt.SpecialValues.POSINF in vs:
535
+ posinf_val = vs[gt.SpecialValues.POSINF]
536
+ del vs[gt.SpecialValues.POSINF]
537
+ else:
538
+ posinf_val = sv_posinf
539
+
540
+ if "-INF" in vs.keys():
541
+ if gt.SpecialValues.NEGINF in vs:
542
+ self._cdb.print_log(
543
+ f'Warning: "-INF" ({vs["-INF"]}) overwrites -.inf ({vs[float("-inf")]}) in valueSubstitutions.'
544
+ )
545
+ del vs[gt.SpecialValues.NEGINF]
546
+ neginf_val = vs["-INF"]
547
+ del vs["-INF"]
548
+ elif gt.SpecialValues.NEGINF in vs:
549
+ neginf_val = vs[gt.SpecialValues.NEGINF]
550
+ del vs[gt.SpecialValues.NEGINF]
551
+ else:
552
+ neginf_val = sv_neginf
553
+
554
+ # - pandas does not distingish between gt.SpecialValues.NA and gt.SpecialValues.UNDEF and
555
+ # we have to replace NA manually first.
556
+ # - pandas replace() does not distinguish between +0.0 and -0.0 (gt.SpecialValues.EPS) and we
557
+ # have to replace EPS manually first.
558
+ # - all other special values (UNDEF, INF, -INF) are replaced manually as well for consistency and
559
+ # performance reasons
560
+ df, mask = replace_sv(
561
+ df, eps_val, na_val, undef_val, posinf_val, neginf_val
562
+ )
563
+ df = self._replace_no_warn(df, vs, mask)
564
+ else: # set
565
+ df = self._replace_no_warn(df, vs)
566
+
567
+ return df
568
+
569
+ def _compile_error_message(self, symbols, suffix, should_exist):
570
+ plural = len(symbols) > 1
571
+ symbol_label = "Symbols" if plural else "Symbol"
572
+ symbol_list = ",".join(symbols)
573
+ if should_exist:
574
+ verb = "do not exist" if plural else "does not exist"
575
+ else:
576
+ verb = "already exist" if plural else "already exists"
577
+
578
+ return f"{symbol_label} >{symbol_list}< {verb} {suffix}"
579
+
580
+ def _symbols_exist_gmd(self, gmd, sym_names):
581
+ """
582
+ Raises a GamsConnectException if a single symbol or at least one symbol
583
+ of a given list of symbols does not exist in the given GMD object.
584
+
585
+ Parameters
586
+ ----------
587
+ gmd : GMD handle
588
+ sym_names : str, list[str]
589
+ A list of symbol names or a single symbol name to be checked.
590
+
591
+ Raises
592
+ ----------
593
+ GamsConnectException
594
+ If at least one symbol does not exist.
595
+ """
596
+ sym_list = sym_names if isinstance(sym_names, list) else [sym_names]
597
+ tmp_ws = GamsWorkspace(system_directory=self._cdb._system_directory)
598
+ db = tmp_ws._add_database_from_gmd(gmd)
599
+
600
+ symbols = []
601
+ for s in sym_list:
602
+ try:
603
+ db[s]
604
+ except:
605
+ symbols.append(s)
606
+
607
+ if not symbols:
608
+ return
609
+
610
+ msg = self._compile_error_message(symbols, "in the GAMS database.", should_exist=True)
611
+ self._connect_error(msg)
612
+
613
+ def _symbols_exist_gdx(self, gdx_file, sym_names):
614
+ """
615
+ Raises a GamsConnectException if a single symbol or at least one symbol
616
+ of a given list of symbols does not exist in the given GDX file.
617
+
618
+ Parameters
619
+ ----------
620
+ gdx_file : str
621
+ sym_names : str, list[str]
622
+ A list of symbol names or a single symbol name to be checked.
623
+
624
+ Raises
625
+ ----------
626
+ GamsConnectException
627
+ If at least one symbol does not exist.
628
+ """
629
+ sym_list = sym_names if isinstance(sym_names, list) else [sym_names]
630
+ tmp_ws = GamsWorkspace(system_directory=self._cdb._system_directory)
631
+ db = tmp_ws.add_database_from_gdx(gdx_file)
632
+
633
+ symbols = []
634
+ for s in sym_list:
635
+ try:
636
+ db[s]
637
+ except:
638
+ symbols.append(s)
639
+
640
+ if not symbols:
641
+ return
642
+
643
+ msg = self._compile_error_message(symbols, f"in the GDX file >{gdx_file}<.", should_exist=True)
644
+ self._connect_error(msg)
645
+
646
+ def _symbols_exist_cdb(self, sym_names, should_exist=False):
647
+ """
648
+ Checks the Connect database for existing symbols and raises a GamsConnectException
649
+ depending on parameter should_exist.
650
+ For should_exist=False: Raises a GamsConnectException if a single symbol or at least one symbol
651
+ of a given list of symbols does exist in the Connect Database.
652
+ For should_exist=True: Raises a GamsConnectException if a single symbol or at least one symbol
653
+ of a given list of symbols does not exist in the Connect Database.
654
+
655
+ Parameters
656
+ ----------
657
+ sym_names : str, list[str]
658
+ A list of symbol names or a single symbol name to be checked.
659
+ should_exist : bool
660
+ If False, raises an exception if any symbol is missing. If True, raises an exception if any symbol already exists.
661
+
662
+ Raises
663
+ ----------
664
+ GamsConnectException
665
+ If at least one symbol already exists.
666
+ """
667
+ sym_list = sym_names if isinstance(sym_names, list) else [sym_names]
668
+ symbols = (
669
+ [s for s in sym_list if s not in self._cdb.container]
670
+ if should_exist
671
+ else [s for s in sym_list if s in self._cdb.container]
672
+ )
673
+
674
+ if not symbols:
675
+ return
676
+ msg = self._compile_error_message(symbols, "in the Connect database.", should_exist)
677
+ self._connect_error(msg)
678
+
679
+ def _update_sym_inst(self, sym_inst, root_inst):
680
+ """
681
+ Updates None values in the symbols scope instructions with the corresponding values
682
+ from the root scope for all keys that exist in both instruction dictionaries.
683
+ This method is not recursive and considers only the first level of the provided
684
+ dictionaries. sym_inst is updated inplace and also returned.
685
+
686
+ Parameters
687
+ ----------
688
+ sym_inst : dict
689
+ Symbols instructions to be updated.
690
+ root_inst : dict
691
+ Root instructions to be used for updating sym_inst
692
+
693
+ Returns
694
+ ----------
695
+ dict
696
+ The updated sym_inst dictionary
697
+ """
698
+ keys = [
699
+ k for k in sym_inst.keys() if k in root_inst
700
+ ] # all keys that exist in both dictionaries
701
+ for k in keys:
702
+ if sym_inst[k] is None:
703
+ sym_inst[k] = root_inst[k]
704
+ return sym_inst
705
+
706
+ def _dict_get(self, d, key, default):
707
+ """
708
+ Like dict.get() but handles keys with None value as if they were missing.
709
+
710
+ Parameters
711
+ ----------
712
+ d : dict
713
+ Dictionary to be used.
714
+ key : str
715
+ The key to be found in d.
716
+ default : int, str, float
717
+ Default value to be returned in case d.get(key) is None.
718
+
719
+ Returns
720
+ ----------
721
+ int, str, float
722
+ d[key] if key is in d and the value is not None, default otherwise.
723
+ """
724
+ return default if d.get(key) is None else d[key]
725
+
726
+ def setup_log(self):
727
+ if self._trace > 3:
728
+ self._restore_max_rows = pd.get_option("display.max_rows")
729
+ self._restore_max_columns = pd.get_option("display.max_columns")
730
+ self._restore_np_threshold = np.get_printoptions()["threshold"]
731
+ pd.set_option("display.max_rows", None, "display.max_columns", None)
732
+ np.set_printoptions(threshold=sys.maxsize)
733
+
734
+ def restore_log(self):
735
+ if self._trace > 3:
736
+ pd.set_option("display.max_rows", self._restore_max_rows, "display.max_columns", self._restore_max_columns)
737
+ np.set_printoptions(self._restore_np_threshold)
738
+
739
+ @abstractmethod
740
+ def execute(self):
741
+ """
742
+ Called by the ConnectDatabase after open() and before close(). This abstract method needs to be implemented by a subclass.
743
+ """