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,467 @@
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
+ import os
27
+ import warnings
28
+ from gams import transfer as gt
29
+ from gams.connect.agents._excel import ExcelAgent
30
+ from gams.connect.agents._excel import Workbook
31
+ from gams.connect.connectvalidator import ConnectValidator
32
+ import openpyxl
33
+ from openpyxl.styles import Font
34
+ from openpyxl.utils.dataframe import dataframe_to_rows
35
+ import pandas as pd
36
+
37
+
38
+ class ExcelWriter(ExcelAgent):
39
+ _index_parameter_map = {
40
+ "cdim": "columnDimension",
41
+ "columndimension": "columnDimension",
42
+ "mergedcells": "mergedCells",
43
+ "clearsheet": "clearSheet",
44
+ }
45
+
46
+ _EXCEL_ROW_LIMIT = 1048576
47
+ _EXCEL_COL_LIMIT = 16384
48
+
49
+ def __init__(self, cdb, inst, agent_index):
50
+ super().__init__(cdb, inst, agent_index)
51
+ self._parse_options(self._inst)
52
+ if os.path.splitext(self._file)[1] != ".xlsx":
53
+ self._connect_error("The ExcelWriter does support .xlsx files only.")
54
+
55
+ def _check_openpyxl(self):
56
+ try:
57
+ opxl_version = list(map(int, openpyxl.__version__.split(".")))
58
+ if opxl_version[0] < 3 or (
59
+ opxl_version[0] == 3 and opxl_version[1] < 1
60
+ ): # check openpyxl<3.1.0
61
+ warnings.warn(
62
+ f"The used openpyxl version is {openpyxl.__version__}. openpyxl<3.1.0 can lead to wrong data being exported. Upgrading to version 3.1.0 or later is recommended.",
63
+ category=Warning,
64
+ )
65
+ except ValueError: # silence errors when converting to int
66
+ pass
67
+
68
+ def _parse_options(self, inst):
69
+ inst["file"] = os.path.abspath(inst["file"])
70
+ self._file = inst["file"]
71
+ self._merged_cells = inst["mergedCells"]
72
+ self._cdim = inst["columnDimension"]
73
+ self._value_subs = inst["valueSubstitutions"]
74
+ self._clear_sheet = inst["clearSheet"]
75
+ self._trace = inst["trace"]
76
+ self._symbols = inst["symbols"]
77
+ self._index = inst["index"]
78
+ self._engine = "openpyxl"
79
+
80
+ self._toc = inst["tableOfContents"]
81
+ if self._toc and not isinstance(self._toc, dict):
82
+ v = ConnectValidator(
83
+ {"tableOfContents": self._cdb.load_schema(self)["tableOfContents"]}
84
+ )
85
+ self._toc = {"tableOfContents": {}}
86
+ self._toc = v.normalized(self._toc)
87
+ self._toc = v.normalize_of_rules(self._toc)
88
+ self._toc = self._toc["tableOfContents"]
89
+ inst["tableOfContents"] = self._toc
90
+
91
+ def _open(self):
92
+ if os.path.exists(self._file):
93
+ try:
94
+ self._wb = Workbook(self._file, read_only=False, data_only=False)
95
+ except PermissionError as e:
96
+ self._connect_error(
97
+ str(e)
98
+ + "\nThe file may already be open and might need to be closed first."
99
+ )
100
+ else:
101
+ if self._index:
102
+ self._connect_error(
103
+ f"Workbook >{self._file}< needs to exist if used with index option."
104
+ )
105
+ self._wb = Workbook()
106
+
107
+ def _write(self, df, rdim, cdim, sheet, nw_row, nw_col, merged_cells):
108
+ row = nw_row + 1
109
+ use_index = True if rdim > 0 else False
110
+ use_header = True if cdim > 0 else False
111
+ last_row_labels = [None] * rdim
112
+
113
+ for idx, r in enumerate(
114
+ dataframe_to_rows(df, index=use_index, header=use_header)
115
+ ):
116
+ r2 = list(r)
117
+ if idx < cdim:
118
+ if merged_cells:
119
+ for idx2, x in enumerate(r2[rdim:]):
120
+ if idx2 == 0: # first iteration
121
+ merge_start = merge_end = nw_col + rdim + 1
122
+ elif x is None:
123
+ merge_end += 1
124
+ if idx2 == len(r2) - 1 - rdim: # last iteration
125
+ if merge_start != merge_end:
126
+ sheet.merge_cells(
127
+ start_row=row,
128
+ start_column=merge_start,
129
+ end_row=row,
130
+ end_column=merge_end,
131
+ )
132
+ else:
133
+ if merge_start != merge_end:
134
+ sheet.merge_cells(
135
+ start_row=row,
136
+ start_column=merge_start,
137
+ end_row=row,
138
+ end_column=merge_end,
139
+ )
140
+ merge_start = merge_end = merge_end + 1
141
+ else:
142
+ for idx2, x in enumerate(r2[rdim:]):
143
+ if x is None:
144
+ r2[idx2 + rdim] = r2[idx2 + rdim - 1]
145
+ # remove extra empty row
146
+ elif idx == cdim and rdim > 0:
147
+ continue
148
+ else:
149
+ if merged_cells:
150
+ if idx == cdim + 1: # first iteration
151
+ merge_start = [nw_row + cdim + 1] * rdim
152
+ merge_end = [nw_row + cdim + 1] * rdim
153
+ else:
154
+ for x in range(rdim):
155
+ if r2[x] is None:
156
+ merge_end[x] += 1
157
+ if idx == df.shape[0] + cdim: # last iteration
158
+ sheet.merge_cells(
159
+ start_row=merge_start[x],
160
+ start_column=x + nw_col + 1,
161
+ end_row=merge_end[x],
162
+ end_column=x + nw_col + 1,
163
+ )
164
+ else:
165
+ if merge_start[x] != merge_end[x]:
166
+ sheet.merge_cells(
167
+ start_row=merge_start[x],
168
+ start_column=x + nw_col + 1,
169
+ end_row=merge_end[x],
170
+ end_column=x + nw_col + 1,
171
+ )
172
+ merge_start[x] = merge_end[x] = merge_end[x] + 1
173
+ else:
174
+ for x in range(rdim):
175
+ if r2[x] is None:
176
+ r2[x] = last_row_labels[x]
177
+ last_row_labels = r2[:rdim]
178
+
179
+ for col in range(len(r2)):
180
+ if r2[col] is not None:
181
+ sheet.cell(row, col + nw_col + 1).value = r2[col]
182
+ row += 1
183
+
184
+ def _pivot_cdim_only(self, df, dim, value_text):
185
+ cols = df.columns.values.tolist()
186
+ df = df.sort_values(by=cols[:dim]).reset_index(drop=True)
187
+ df["_first"] = value_text
188
+ df = df.pivot(index=["_first"], columns=cols[:-1], values=[value_text])
189
+ df.columns = df.columns.droplevel(0) # remove column index "values"
190
+ df.rename_axis([None], axis=0, inplace=True) # remove index names
191
+ df.rename_axis([None] * dim, axis=1, inplace=True) # remove column index names
192
+ return df
193
+
194
+ def _pivot_rdim_only(self, df, dim, value_text):
195
+ cols = df.columns.values.tolist()
196
+ df["_first"] = value_text
197
+ df = df.pivot(
198
+ index=cols[:dim], columns=["_first"], values=[value_text]
199
+ ).sort_index()
200
+ df.columns = df.columns.droplevel(0) # remove column index "values"
201
+ df.rename_axis([None] * dim, axis=0, inplace=True) # remove index names
202
+ df.rename_axis([None], axis=1, inplace=True) # remove column index names
203
+ return df
204
+
205
+ def _pivot_rdim_cdim(self, df, rdim, cdim, value_text):
206
+ dim = rdim + cdim
207
+ cols = df.columns.values.tolist()
208
+ df = df.sort_values(by=cols[rdim:dim]).reset_index(drop=True)
209
+ df = df.pivot(
210
+ index=cols[:rdim], columns=cols[rdim:-1], values=[value_text]
211
+ ).sort_index()
212
+ df.columns = df.columns.droplevel(0) # remove column index "values"
213
+ df.rename_axis([None] * rdim, axis=0, inplace=True) # remove index names
214
+ df.rename_axis([None] * cdim, axis=1, inplace=True) # remove column index names
215
+ return df
216
+
217
+ def _reshape_dataframe(self, df, dim, rdim, cdim, value):
218
+ # turn integer column names into string column names to avoid problems reshaping if rdim=0
219
+ df.columns = df.columns.map(str)
220
+ if dim == 0:
221
+ pass
222
+ elif rdim == 0:
223
+ df = self._pivot_cdim_only(df, dim, value)
224
+ elif cdim == 0:
225
+ df = self._pivot_rdim_only(df, dim, value)
226
+ else:
227
+ df = self._pivot_rdim_cdim(df, rdim, cdim, value)
228
+ return df
229
+
230
+ def _expected_excel_shape(self, df, rdim, cdim):
231
+ nr_rows = df.shape[0] + cdim
232
+ nr_cols = df.shape[1] + rdim
233
+ return (nr_rows, nr_cols)
234
+
235
+ def _validate_range(self, sym_name, df, rdim, cdim, nw_col, nw_row, se_col, se_row):
236
+ required_rows, required_columns = self._expected_excel_shape(df, rdim, cdim)
237
+
238
+ if required_rows + nw_row > self._EXCEL_ROW_LIMIT:
239
+ self._connect_error(
240
+ f"Attempting to write >{required_rows}< rows starting from row >{nw_row}< exceeds Excel's row limit of >{self._EXCEL_ROW_LIMIT}< for symbol >{sym_name}<."
241
+ )
242
+
243
+ if required_columns + nw_col > self._EXCEL_COL_LIMIT:
244
+ self._connect_error(
245
+ f"Attempting to write >{required_columns}< columns starting from column >{nw_col}< exceeds Excel's column limit of >{self._EXCEL_COL_LIMIT}< for symbol >{sym_name}<."
246
+ )
247
+
248
+ if se_row is not None:
249
+ actual_rows = se_row - nw_row # + 1
250
+ if required_rows > actual_rows:
251
+ self._connect_error(f"Data exceeds range for symbol >{sym_name}<.")
252
+
253
+ if se_col is not None:
254
+ actual_columns = se_col - nw_col # + 1
255
+ if required_columns > actual_columns:
256
+ self._connect_error(f"Data exceeds range for symbol >{sym_name}<.")
257
+
258
+ def _write_toc(self):
259
+ if self._toc["sort"]:
260
+ self._toc_symbols.sort(key=lambda x: x[0].name)
261
+ sheet = self.sheet_by_name(self._toc["sheetName"], self._wb, True, True)
262
+ self._wb.move_sheet(
263
+ sheet, -self._wb.index(sheet)
264
+ ) # move toc sheet to the beginning
265
+ sheet.cell(1, 1).value = "Name"
266
+ sheet.cell(1, 2).value = "Type"
267
+ sheet.cell(1, 3).value = "Dimension"
268
+ sheet.cell(1, 4).value = "Record Count"
269
+ sheet.cell(1, 5).value = "Explanatory text"
270
+ row = 2
271
+ for sym, rng in self._toc_symbols:
272
+ sheet.cell(row, 1).value = sym.name
273
+ sheet.cell(row, 1).hyperlink = f"#{rng}"
274
+ sheet.cell(row, 1).font = Font(underline="single", color="0563C1")
275
+ sheet.cell(row, 2).value = (
276
+ "Parameter" if isinstance(sym, gt.Parameter) else "Set"
277
+ )
278
+ sheet.cell(row, 3).value = sym.dimension
279
+ sheet.cell(row, 4).value = sym.number_records
280
+ sheet.cell(row, 5).value = sym.description
281
+ row += 1
282
+
283
+ def _write_symbols(self, symbols, validate=False):
284
+ if validate:
285
+ sym_schema = self._cdb.load_schema(self)["symbols"]["oneof"][1]["schema"][
286
+ "schema"
287
+ ]
288
+ v = ConnectValidator(sym_schema)
289
+ for i, sym in enumerate(symbols):
290
+ if validate:
291
+ sym = v.validated(sym)
292
+ if sym is None:
293
+ self._connect_error(
294
+ f"Validation of item {i} in index failed: {v.errors}"
295
+ )
296
+ sym = v.normalize_of_rules(sym)
297
+ self._write_symbol(sym)
298
+
299
+ def _create_symbol_instructions(self, rec):
300
+ is_symbol = not None in (rec[1], rec[2])
301
+ inst = {}
302
+ if is_symbol:
303
+ sym_type = str(rec[0]).lower().strip() if rec[0] else ""
304
+ inst["name"] = rec[1].strip()
305
+ inst["range"] = rec[2].strip()
306
+ if self._trace > 1:
307
+ self._cdb.print_log(
308
+ f"Parsing symbol >{inst['name']}< with >type: {sym_type}< and >range: {inst['range']}<."
309
+ )
310
+ if len(sym_type) > 0:
311
+ sym_name = inst["name"]
312
+ self._symbols_exist_cdb(sym_name, should_exist=True)
313
+ symbol = self._cdb.container[sym_name]
314
+ if sym_type == "par":
315
+ if not isinstance(symbol, gt.Parameter):
316
+ self._connect_error(
317
+ f"Type mismatch: Symbol type was specified as >{sym_type}< but the symbol is of type >{type(symbol).__name__}<"
318
+ )
319
+ elif sym_type == "set":
320
+ if not isinstance(symbol, gt.Set):
321
+ self._connect_error(
322
+ f"Type mismatch: Symbol type was specified as >{sym_type}< but the symbol is of type >{type(symbol).__name__}<"
323
+ )
324
+ else:
325
+ self._connect_error(
326
+ f"Unknown symbol type >{sym_type}<. Valid symbol types are >par< and >set<."
327
+ )
328
+ return inst
329
+
330
+ def _write_from_index(self):
331
+ symbols = self.parse_index(self._index, self._wb, self._index_parameter_map)
332
+ self._write_symbols(symbols, True)
333
+
334
+ def _write_symbol(self, sym):
335
+ sym_raw = sym.copy()
336
+ self._update_sym_inst(sym, self._inst)
337
+
338
+ sym["range"] = self._dict_get(sym, "range", sym["name"] + "!A1")
339
+ sym["range"] = self.normalize_range(sym["range"])
340
+
341
+ sym_name = sym["name"]
342
+ self._symbols_exist_cdb(sym_name, should_exist=True)
343
+ gt_sym = self._cdb.container[sym_name]
344
+ dim = gt_sym.dimension
345
+ if sym["columnDimension"] == "infer":
346
+ sym["columnDimension"] = 1 if dim > 0 else 0
347
+
348
+ sym_range = sym["range"]
349
+ merged_cells = sym["mergedCells"]
350
+ cdim = sym["columnDimension"]
351
+ value_subs = sym["valueSubstitutions"]
352
+ clear_sheet = sym["clearSheet"]
353
+
354
+ if self._trace > 0:
355
+ self._log_instructions(
356
+ sym, sym_raw, description=f"Write symbol >{sym['name']}<:"
357
+ )
358
+
359
+ if self._trace > 2:
360
+ self._cdb.print_log(
361
+ f"Connect Container symbol >{sym_name}<:\n {gt_sym.records}\n"
362
+ )
363
+
364
+ if not isinstance(gt_sym, (gt.Set, gt.Parameter)):
365
+ self._connect_error(
366
+ f"Symbol type >{type(gt_sym)}< of symbol >{sym_name}< is not supported. Supported symbol types are set and parameter."
367
+ )
368
+ sym_type = "par" if isinstance(gt_sym, gt.Parameter) else "set"
369
+
370
+ df = gt_sym.records.copy(deep=True)
371
+
372
+ if isinstance(gt_sym, gt.Set):
373
+ value = "element_text"
374
+ elif isinstance(gt_sym, gt.Parameter):
375
+ value = "value"
376
+
377
+ if cdim > dim:
378
+ self._connect_error(
379
+ f"columnDimension >{cdim}< exceeds dimension of symbol >{sym_name}<."
380
+ )
381
+ rdim = dim - cdim
382
+
383
+ df = self._apply_value_substitutions(
384
+ df,
385
+ value_subs,
386
+ sym_type,
387
+ sv_eps="EPS",
388
+ sv_na="NA",
389
+ sv_undef="UNDEF",
390
+ sv_posinf="INF",
391
+ sv_neginf="-INF",
392
+ )
393
+ if self._trace > 2:
394
+ self._cdb.print_log(
395
+ f"DataFrame after valueSubstitutions ({sym_name}):\n{df}\n"
396
+ )
397
+
398
+ if (
399
+ value == "element_text" and rdim * cdim > 0
400
+ ): # replace empty element_text by Y when exporting a true table
401
+ df.loc[df[value] == "", value] = "Y"
402
+
403
+ sheet, nw_col, nw_row, se_col, se_row, toc_range = self.parse_range(
404
+ sym_range, self._wb, clear_sheet, True
405
+ )
406
+
407
+ df = self._reshape_dataframe(df, dim, rdim, cdim, value)
408
+ if self._trace > 2:
409
+ self._cdb.print_log(f"DataFrame after reshaping ({sym_name}):\n{df}\n")
410
+
411
+ self._validate_range(sym_name, df, rdim, cdim, nw_col, nw_row, se_col, se_row)
412
+
413
+ self._toc_symbols.append((gt_sym, toc_range))
414
+
415
+ if gt_sym.records is None or gt_sym.number_records == 0:
416
+ return
417
+
418
+ self._write(
419
+ df,
420
+ rdim,
421
+ cdim,
422
+ sheet,
423
+ nw_row,
424
+ nw_col,
425
+ merged_cells,
426
+ )
427
+
428
+ def execute(self):
429
+ if self._trace > 0:
430
+ self._log_instructions(self._inst, self._inst_raw)
431
+ self._describe_container(self._cdb.container, "Connect Container:")
432
+
433
+ toc = self._inst_raw.get("tableOfContents")
434
+
435
+ self._open()
436
+
437
+ self._check_openpyxl()
438
+ self._toc_symbols = []
439
+ try:
440
+ if self._index:
441
+ self._write_from_index()
442
+ elif self._symbols == "all":
443
+ self._symbols = [
444
+ {"name": s[0]}
445
+ for s in self._cdb.container
446
+ if isinstance(s[1], (gt.Parameter, gt.Set))
447
+ ]
448
+ self._write_symbols(self._symbols, True)
449
+ else:
450
+ self._write_symbols(self._symbols)
451
+ if self._toc:
452
+ self._write_toc()
453
+ finally:
454
+ self._wb.close()
455
+ self._close()
456
+
457
+ def _close(self):
458
+ if len(self._wb.sheetnames) == 0:
459
+ self._cdb.print_log(f"No sheets in Excel file >{self._file}<. Skipping.")
460
+ else:
461
+ try:
462
+ self._wb.save(self._file)
463
+ except PermissionError as e:
464
+ self._connect_error(
465
+ str(e)
466
+ + "\nThe file may already be open and might need to be closed first."
467
+ )
@@ -0,0 +1,223 @@
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
+ import re
27
+ import pandas as pd
28
+ from gams import transfer as gt
29
+ from gams.connect.agents.connectagent import ConnectAgent
30
+
31
+
32
+ class Filter(ConnectAgent):
33
+
34
+ def __init__(self, cdb, inst, agent_index):
35
+ super().__init__(cdb, inst, agent_index)
36
+ self._parse_options(self._inst)
37
+
38
+ def _parse_options(self, inst):
39
+ self._name = inst["name"]
40
+ self._new_name = inst["newName"]
41
+ self._value_filters = self._dict_get(inst, "valueFilters", [])
42
+ self._label_filters = self._dict_get(inst, "labelFilters", [])
43
+ self._trace = inst["trace"]
44
+ self._label_filters_dict = {}
45
+ for f in self._label_filters:
46
+ c = f["dimension"]
47
+ if c != "all":
48
+ c = c - 1
49
+ if c in self._label_filters_dict:
50
+ self._connect_error(f"More than one label filter for dimension {c+1}.")
51
+ self._label_filters_dict[c] = f
52
+
53
+ def _filter_labels(self, df, f, c):
54
+ if c == "all":
55
+ for c in range(0, self._sym.dimension):
56
+ df = self._filter_labels(df, f, c)
57
+ else:
58
+ if c >= self._sym.dimension:
59
+ self._connect_error(
60
+ f"Invalid dimension {c+1} for symbol with {self._sym.dimension} dimensions. Hint: dimension is 1-indexed."
61
+ )
62
+ if f.get("keep") is not None:
63
+ df = df.loc[df.iloc[:, c].isin(f["keep"])]
64
+ elif f.get("reject") is not None:
65
+ df = df.loc[~df.iloc[:, c].isin(f["reject"])]
66
+ elif f.get("regex") is not None:
67
+ regex = re.compile(f["regex"])
68
+ df = df.loc[df.iloc[:, c].str.fullmatch(regex)]
69
+ return df
70
+
71
+ def _filter_values(self, df, f, c, skip_trace=False):
72
+ if isinstance(self._sym, gt.Set):
73
+ self._connect_error(
74
+ "Value filters are not supported for symbols of type set."
75
+ )
76
+ rule_identifier = f["ruleIdentifier"]
77
+ rule = self._dict_get(f, "rule", "")
78
+ rule = (
79
+ "(" + rule.replace(rule_identifier, f'df["{c}"]') + ")"
80
+ if rule
81
+ else "[True]*len(df)"
82
+ )
83
+
84
+ include_sv = ""
85
+ exclude_sv = ""
86
+ reject_sv = self._dict_get(f, "rejectSpecialValues", [])
87
+ if isinstance(reject_sv, str):
88
+ reject_sv = [reject_sv]
89
+
90
+ if "EPS" in reject_sv:
91
+ exclude_sv += f' & (~gt.SpecialValues.isEps(df["{c}"]))'
92
+ else:
93
+ include_sv += f' | (gt.SpecialValues.isEps(df["{c}"]))'
94
+ if "INF" in reject_sv:
95
+ exclude_sv += f' & (~gt.SpecialValues.isPosInf(df["{c}"]))'
96
+ else:
97
+ include_sv += f' | (gt.SpecialValues.isPosInf(df["{c}"]))'
98
+ if "-INF" in reject_sv:
99
+ exclude_sv += f' & (~gt.SpecialValues.isNegInf(df["{c}"]))'
100
+ else:
101
+ include_sv += f' | (gt.SpecialValues.isNegInf(df["{c}"]))'
102
+ if "UNDEF" in reject_sv:
103
+ exclude_sv += f' & (~gt.SpecialValues.isUndef(df["{c}"]))'
104
+ else:
105
+ include_sv += f' | (gt.SpecialValues.isUndef(df["{c}"]))'
106
+ if "NA" in reject_sv:
107
+ exclude_sv += f' & (~gt.SpecialValues.isNA(df["{c}"]))'
108
+ else:
109
+ include_sv += f' | (gt.SpecialValues.isNA(df["{c}"]))'
110
+ rule += exclude_sv + include_sv
111
+
112
+ if self._trace > 1 and not skip_trace:
113
+ self._cdb.print_log(f'Applying rule for attribute "{c}": {rule}')
114
+ if c == "all":
115
+ if isinstance(self._sym, gt.Parameter):
116
+ value_columns = ["value"]
117
+ elif isinstance(self._sym, (gt.Variable, gt.Equation)):
118
+ value_columns = [
119
+ "level",
120
+ "marginal",
121
+ "lower",
122
+ "upper",
123
+ "scale",
124
+ ]
125
+ for c in value_columns:
126
+ df = self._filter_values(df, f, c, True)
127
+ else:
128
+ if not df.empty:
129
+ df = eval(f"df.loc[({rule})]", {"df": df, "gt": gt})
130
+ return df
131
+
132
+ def execute(self):
133
+ if self._trace > 0:
134
+ self._log_instructions(self._inst, self._inst_raw)
135
+ self._describe_container(self._cdb.container, "Connect Container (before):")
136
+
137
+ self._symbols_exist_cdb(self._name, should_exist=True)
138
+ self._sym = self._cdb.container[self._name]
139
+
140
+ if self._new_name.casefold() == self._name.casefold():
141
+ self._connect_error(
142
+ f"newName >{self._new_name}< must be different from name >{self._name}<. Hint: The names are case-insensitive."
143
+ )
144
+
145
+ self._symbols_exist_cdb(self._new_name)
146
+
147
+ if isinstance(self._sym, gt.Set):
148
+ tsym = gt.Set(self._cdb.container, self._new_name, self._sym.domain)
149
+ elif isinstance(self._sym, gt.Parameter):
150
+ tsym = gt.Parameter(self._cdb.container, self._new_name, self._sym.domain)
151
+ elif isinstance(self._sym, gt.Variable):
152
+ tsym = gt.Variable(
153
+ self._cdb.container,
154
+ self._new_name,
155
+ self._sym.type,
156
+ self._sym.domain,
157
+ )
158
+ elif isinstance(self._sym, gt.Equation):
159
+ tsym = gt.Equation(
160
+ self._cdb.container,
161
+ self._new_name,
162
+ self._sym.type,
163
+ self._sym.domain,
164
+ )
165
+ else:
166
+ self._connect_error("Symbol type not supported.")
167
+
168
+ df = self._sym_records_no_none(self._sym).copy(deep=True)
169
+
170
+ if self._trace > 2:
171
+ self._cdb.print_log(f"Original DataFrame:\n{df}")
172
+ for c in self._label_filters_dict:
173
+ f = self._label_filters_dict[c]
174
+ if self._trace > 0:
175
+ self._log_instructions(f, description="Applying label filter:")
176
+ if c == "all":
177
+ df = self._filter_labels(df, f, c)
178
+ if self._trace > 2:
179
+ self._cdb.print_log(
180
+ f"DataFrame after label filter for dimension 'all':\n{df}"
181
+ )
182
+ else:
183
+ df = self._filter_labels(df, f, c)
184
+ if self._trace > 2:
185
+ self._cdb.print_log(
186
+ f"DataFrame after label filter for dimension {c+1}:\n{df}"
187
+ )
188
+
189
+ val_cols = [f["attribute"] for f in self._value_filters]
190
+ if len(val_cols) != len(set(val_cols)):
191
+ for c in set(val_cols):
192
+ val_cols.remove(c)
193
+ self._connect_error(
194
+ f"More than one value filter for attribute {set(val_cols)}."
195
+ )
196
+
197
+ for f in self._value_filters:
198
+ c = f["attribute"]
199
+ if self._trace > 0:
200
+ self._log_instructions(f, description="Applying value filter:")
201
+ if isinstance(self._sym, gt.Parameter) and c not in ["all", "value"]:
202
+ self._connect_error(
203
+ f"Invalid attribute >{c}< for symbol type parameter."
204
+ )
205
+ if (
206
+ isinstance(self._sym, gt.Variable) or isinstance(self._sym, gt.Equation)
207
+ ) and c not in ["all", "level", "marginal", "upper", "lower", "scale"]:
208
+ self._connect_error(
209
+ f"Invalid attribute >{c}< for symbol type variable/equation."
210
+ )
211
+ df = self._filter_values(df, f, c)
212
+ if self._trace > 2:
213
+ self._cdb.print_log(
214
+ f'DataFrame after value filter for attribute "{c}":\n{df}'
215
+ )
216
+ tsym.setRecords(df)
217
+
218
+ if self._trace > 2:
219
+ self._cdb.print_log(
220
+ f"Connect Container symbol={self._new_name}:\n {tsym.records}\n"
221
+ )
222
+ if self._trace > 0:
223
+ self._describe_container(self._cdb.container, "Connect Container (after):")