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,675 @@
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
15
+ # all 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 copy
27
+ import os
28
+ from typing import Any, List, Optional, Union
29
+ import pandas as pd
30
+ from gams import transfer as gt
31
+ from gams.connect.agents.connectagent import ConnectAgent
32
+
33
+
34
+ class CSVReader(ConnectAgent):
35
+ def __init__(self, cdb, inst, agent_index):
36
+ super().__init__(cdb, inst, agent_index)
37
+ self._parse_options(self._inst)
38
+
39
+ def _parse_options(self, inst):
40
+ inst["file"] = os.path.abspath(inst["file"])
41
+ self._file = inst["file"]
42
+ self._name = inst["name"]
43
+ self._names = inst["names"]
44
+ self._sym_type = inst["type"]
45
+ self._index_cols = inst["indexColumns"]
46
+ self._index_sub = self._dict_get(inst, "indexSubstitutions", {})
47
+ self._value_cols = inst["valueColumns"]
48
+ self._value_sub = self._dict_get(inst, "valueSubstitutions", {})
49
+ self._trace = inst["trace"]
50
+ self._header = inst["header"]
51
+ self._skip_rows = inst["skipRows"]
52
+ self._read_csv_arguments = self._dict_get(inst, "readCSVArguments", {})
53
+ self._field_sep = inst["fieldSeparator"]
54
+ self._decimal_sep = inst["decimalSeparator"]
55
+ self._thousands_sep = inst["thousandsSeparator"]
56
+ self._quoting = inst["quoting"]
57
+ self._auto_col = inst["autoColumn"]
58
+ self._auto_row = inst["autoRow"]
59
+ self._stack = inst["stack"]
60
+ self._multiheader = True if isinstance(self._header, list) else False
61
+ self._header = self._parse_header()
62
+
63
+ if isinstance(self._skip_rows, list):
64
+ # pandas skiprows is 0-indexed
65
+ self._skip_rows = [i - 1 for i in self._skip_rows]
66
+
67
+ self._read_csv_args = {
68
+ "header": self._header,
69
+ "names": self._names,
70
+ "skiprows": self._skip_rows,
71
+ "sep": self._field_sep,
72
+ "decimal": self._decimal_sep,
73
+ "thousands": self._thousands_sep,
74
+ "quoting": self._quoting,
75
+ }
76
+
77
+ self._check_invalid_input()
78
+ self._read_csv_args.update(self._read_csv_arguments)
79
+
80
+ def _check_invalid_input(self):
81
+ if self._multiheader:
82
+ if self._sym_type == "set":
83
+ self._connect_error(
84
+ "Reading sets with multi-row headers is not supported."
85
+ )
86
+
87
+ if self._stack is False:
88
+ self._connect_error(
89
+ f"Multi-row header needs to be stacked to index but stack is set to >{self._stack}<."
90
+ )
91
+
92
+ if self._value_cols:
93
+ self._connect_error(
94
+ "Cannot specify valueColumns if the data has a multi-row"
95
+ " header. All columns that are not indexColumns will be"
96
+ " read as valueColumns."
97
+ )
98
+
99
+ if isinstance(self._index_cols, list) and all(
100
+ isinstance(col, str) for col in self._index_cols
101
+ ):
102
+ self._connect_error(
103
+ "The CSVReader with multi-row header does not support to"
104
+ " specify indexColumns as column names. Please provide"
105
+ " column positions instead."
106
+ )
107
+ # consequently, multi-row header also does not support order of valueColumns
108
+ # and the same column in both indexColumns and valueColumns
109
+ elif self._sym_type == "par" and (
110
+ self._value_cols is None or self._value_cols == []
111
+ ):
112
+ self._connect_error(
113
+ "Symbol type parameter requires at least one value column."
114
+ )
115
+
116
+ if self._multiheader and self._auto_col:
117
+ self._connect_error("Cannot use autoColumn with multi-row header.")
118
+
119
+ if self._multiheader and self._auto_row:
120
+ self._connect_error("Cannot use autoRow with multi-row header.")
121
+
122
+ if (
123
+ self._stack is True
124
+ and self._header is None
125
+ and self._names is None
126
+ and self._auto_col is None
127
+ ):
128
+ self._connect_error("Cannot stack without a header, names or autoColumn.")
129
+
130
+ def _parse_header(self) -> Optional[Union[int, list]]:
131
+ """
132
+ Returns the header value that will be provided to pandas
133
+ read_csv method according to the user input
134
+
135
+ Returns
136
+ -------
137
+ int | list | None
138
+ Header value
139
+ """
140
+
141
+ # Infer: header True (0) if no names are provided otherwise False (None)
142
+ if self._header == "infer":
143
+ if self._names is None:
144
+ return 0
145
+ return None
146
+
147
+ # For non-multirow header, user inputs are booleans (True or False).
148
+ if self._header is False:
149
+ return None
150
+
151
+ if self._header is True:
152
+ return 0
153
+
154
+ if isinstance(self._header, list):
155
+ # pandas header is 0-indexed
156
+ return [i - 1 for i in self._header]
157
+
158
+ def _get_last_column(self) -> str:
159
+ """Returns the position of the last column
160
+
161
+ Returns
162
+ -------
163
+ str
164
+ Position of the last column
165
+ """
166
+ read_header_args = copy.deepcopy(self._read_csv_args)
167
+ if self._header is not None or self._names is not None:
168
+ read_header_args.update({"nrows": 0})
169
+
170
+ if self._trace > 1:
171
+ self._cdb.print_log(
172
+ "Calculate symbolic constant lastCol by reading the"
173
+ " header row. Arguments for reading the header"
174
+ f" row:\n{read_header_args}"
175
+ )
176
+
177
+ else:
178
+ read_header_args.update({"header": None, "nrows": 1})
179
+
180
+ if self._trace > 1:
181
+ self._cdb.print_log(
182
+ "Calculate symbolic constant lastCol by reading the"
183
+ " first line of data. Arguments for reading the first"
184
+ f" line of data:\n{read_header_args}"
185
+ )
186
+
187
+ header_row = pd.read_csv(self._file, **read_header_args)
188
+
189
+ return str(len(header_row.columns))
190
+
191
+ def _convert_to_valid_pd_cols(self, cols: Any) -> Union[List[int], List[str]]:
192
+ """Converts user provided indexColumns or valueColumns
193
+ to valid pandas columns
194
+
195
+ Parameters
196
+ ----------
197
+ cols : Any
198
+ Columns provided by the user
199
+
200
+ Returns
201
+ -------
202
+ list
203
+ Column positions or names
204
+ """
205
+
206
+ if isinstance(cols, str):
207
+ if "lastcol" in cols.lower():
208
+ last_col = self._get_last_column()
209
+ cols = cols.lower().replace("lastcol", last_col)
210
+ try:
211
+ positions = [
212
+ (
213
+ list(
214
+ range(
215
+ *[
216
+ int(value) + index
217
+ for index, value in enumerate(position.split(":"))
218
+ ]
219
+ )
220
+ )
221
+ if ":" in position
222
+ else [int(position)]
223
+ )
224
+ for position in cols.split(",")
225
+ ]
226
+
227
+ # convert to 0-indexed
228
+ cols = [i - 1 for sublist in positions for i in sublist]
229
+ except Exception:
230
+ self._connect_error(
231
+ "Column assignation as string can only include"
232
+ " integers, comma (,), colon (:) and symbolic constant"
233
+ " lastCol."
234
+ )
235
+ elif isinstance(cols, list):
236
+ if all(isinstance(i, int) for i in cols):
237
+ cols = [i - 1 for i in cols] # convert to 0-indexed
238
+ elif isinstance(cols, int):
239
+ cols = [cols - 1]
240
+ else:
241
+ cols = []
242
+
243
+ return cols
244
+
245
+ def _check_cols(self):
246
+ if self._stack is False and len(self._value_cols) > 1:
247
+ self._connect_error(
248
+ f"For more than one value column the column names need to be stacked to index but stack is set to >{self._stack}<."
249
+ )
250
+
251
+ if not all(
252
+ isinstance(i, str) for i in self._index_cols + self._value_cols
253
+ ) and not all(isinstance(i, int) for i in self._index_cols + self._value_cols):
254
+ self._connect_error(
255
+ "Index and value columns must be either given as positions or"
256
+ " names not both."
257
+ )
258
+
259
+ if len(self._index_cols) != len(set(self._index_cols)) or len(
260
+ self._value_cols
261
+ ) != len(set(self._value_cols)):
262
+ self._connect_error("Duplicates in index and value columns not allowed.")
263
+
264
+ if len(self._value_cols) > 1:
265
+ if self._header is None and self._names is None and self._auto_col is None:
266
+ self._connect_error(
267
+ "More than one value column requires a header, names"
268
+ " or autoColumn."
269
+ )
270
+
271
+ def _get_dtypes(
272
+ self,
273
+ index_col: Optional[Union[List[int], List[str]]],
274
+ usecols: Union[List[int], List[str]],
275
+ ) -> dict:
276
+ """Returns the data types for index and text columns
277
+
278
+ Parameters
279
+ ----------
280
+ index_col : List[int] | List[str] | None
281
+ Index columns to be used for read_csv
282
+ usecols : List[int] | List[str]
283
+ All columns to be used
284
+
285
+ Returns
286
+ -------
287
+ dict
288
+ Updated data types for columns
289
+ """
290
+ col_dtype = {}
291
+
292
+ if index_col is not None:
293
+ col_dtype.update({i: str for i in index_col})
294
+
295
+ if self._sym_type == "set" and len(self._value_cols) > 0:
296
+ value_col = [sorted(usecols).index(i) for i in self._value_cols]
297
+ col_dtype.update({i: str for i in value_col})
298
+
299
+ return col_dtype
300
+
301
+ def _substitute_index(self, df: pd.DataFrame) -> None:
302
+ """Inplace index substitution
303
+
304
+ Parameters
305
+ ----------
306
+ df : pd.DataFrame
307
+ DataFrame to be manipulated
308
+ """
309
+ if self._sym_type == "set" and len(self._value_cols) == 0:
310
+ df.replace(self._index_sub, inplace=True)
311
+ else:
312
+ df.iloc[:, :-1] = df.iloc[:, :-1].replace(self._index_sub)
313
+
314
+ if self._trace > 2:
315
+ self._cdb.print_log(f"DataFrame after index substitution:\n{df}")
316
+
317
+ def _substitute_values(self, df: pd.DataFrame):
318
+ """Inplace value substitution
319
+
320
+ Parameters
321
+ ----------
322
+ df : pd.DataFrame
323
+ DataFrame to be manipulated
324
+ """
325
+ # pandas-version-check
326
+ if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
327
+ df.isetitem(-1, df.iloc[:, -1].replace(self._value_sub))
328
+ else: # pandas >= 2.2.0
329
+ with pd.option_context("future.no_silent_downcasting", True):
330
+ df.isetitem(-1, df.iloc[:, -1].replace(self._value_sub).infer_objects())
331
+
332
+ if self._trace > 2:
333
+ self._cdb.print_log(f"DataFrame after value substitution:\n{df}")
334
+
335
+ def _generate_row_labels(self, df: pd.DataFrame) -> None:
336
+ """Generates row labels for autoRow option
337
+
338
+ Parameters
339
+ ----------
340
+ df : pd.DataFrame
341
+ DataFrame to be manipulated
342
+ """
343
+ if self._index_cols:
344
+ index_frame = df.index.to_frame()
345
+ index_frame.insert(
346
+ 0,
347
+ "autoRow",
348
+ [self._auto_row + str(i + 1) for i in range(len(df.index))],
349
+ True,
350
+ )
351
+
352
+ df.index = pd.MultiIndex.from_frame(index_frame)
353
+ else:
354
+ df.rename(
355
+ {i: self._auto_row + str(i + 1) for i in list(df.index)},
356
+ axis="index",
357
+ inplace=True,
358
+ )
359
+
360
+ if self._trace > 2:
361
+ self._cdb.print_log(f"DataFrame after inserting autoRow:\n{df}")
362
+
363
+ def _generate_column_labels(self, df: pd.DataFrame) -> None:
364
+ """Generates columns labels for autoColumn option
365
+
366
+ Parameters
367
+ ----------
368
+ df : pd.DataFrame
369
+ DataFrame to be manipulated
370
+ """
371
+ if (self._header is not None or self._names is not None) and self._trace > 1:
372
+ self._cdb.print_log("autoColumn overrides existing column names.")
373
+
374
+ df.rename(
375
+ {c: self._auto_col + str(i + 1) for i, c in enumerate(list(df.columns))},
376
+ axis="columns",
377
+ inplace=True,
378
+ )
379
+
380
+ if self._trace > 2:
381
+ self._cdb.print_log(f"DataFrame after inserting autoColumn:\n{df}")
382
+
383
+ def _set_categoricals(
384
+ self, symbol: Union["gt.Set", "gt.Parameter"], dim: int, columns: list
385
+ ) -> None:
386
+ """Sets the categoricals for the Gams Transfer symbol records
387
+
388
+ Parameters
389
+ ----------
390
+ symbol : gt.Set | gt.Parameter
391
+ Symbol for setting the categoricals
392
+ dim : int
393
+ Dimension to set the categories on
394
+ columns : list
395
+ Column names for categories
396
+ """
397
+ symbol.records.isetitem(
398
+ dim,
399
+ symbol.records.iloc[:, dim].astype(
400
+ pd.CategoricalDtype(
401
+ categories=columns.map(str).map(str.rstrip).unique(),
402
+ ordered=True,
403
+ )
404
+ ),
405
+ )
406
+
407
+ def _stack_multiheader(self, df: pd.DataFrame) -> pd.DataFrame:
408
+ """Stacks column names to index for a multi-row header
409
+
410
+ Parameters
411
+ ----------
412
+ df : pd.DataFrame
413
+ DataFrame to be stacked
414
+
415
+ Returns
416
+ -------
417
+ pd.DataFrame
418
+ Stacked DataFrame
419
+ """
420
+ # Make index and column names unique
421
+ df.columns.names = [f"column_{i}" for i in range(len(df.columns.names))]
422
+
423
+ df.index.names = [f"row_{i}" for i in range(len(df.index.names))]
424
+
425
+ # Stack columns to index
426
+ # pandas-version-check
427
+ if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
428
+ df = df.stack(level=df.columns.names, dropna=False)
429
+ else: # pandas >= 2.2.0
430
+ df = df.stack(level=df.columns.names, future_stack=True)
431
+
432
+ return df
433
+
434
+ def _sort_value_columns(self, df: pd.DataFrame) -> pd.DataFrame:
435
+ """Sorts value columns according to the order specified
436
+
437
+ Parameters
438
+ ----------
439
+ df : pd.DataFrame
440
+ DataFrame to be sorted
441
+
442
+ Returns
443
+ -------
444
+ pd.DataFrame
445
+ DataFrame with sorted value columns
446
+ """
447
+ value_cols = [c for c in self._value_cols if c not in self._index_cols]
448
+
449
+ if len(value_cols) > 1:
450
+ if all(isinstance(i, int) for i in value_cols):
451
+ value_cols_order = [
452
+ df.columns[sorted(value_cols).index(i)] for i in value_cols
453
+ ]
454
+ else:
455
+ value_cols_order = value_cols
456
+
457
+ df = df[value_cols_order]
458
+
459
+ if self._trace > 2:
460
+ self._cdb.print_log(f"DataFrame after reordering value columns:\n{df}")
461
+
462
+ return df
463
+
464
+ def _copy_from_index_to_value(
465
+ self, df: pd.DataFrame, index_col, usecols
466
+ ) -> pd.DataFrame:
467
+ """If columns appear both in index and value columns, pd.read_csv only
468
+ adds the column to index and we copy it to the value column
469
+
470
+ Parameters
471
+ ----------
472
+ df : pd.DataFrame
473
+ DataFrame to be extended
474
+ index_col : list | None
475
+ argument passed to pd.read_csv method
476
+ usecols : list
477
+ argument passed to pd.read_csv method
478
+
479
+ Returns
480
+ -------
481
+ pd.DataFrame
482
+ Extended DataFrame
483
+ """
484
+ if any(c in self._value_cols for c in self._index_cols):
485
+ value_cols = self._value_cols
486
+ if all(isinstance(i, int) for i in value_cols):
487
+ value_cols = [sorted(usecols).index(i) for i in value_cols]
488
+ for i, c in enumerate(value_cols):
489
+ if c in index_col:
490
+ index_name = df.index.names[index_col.index(c)]
491
+ df.insert(i, index_name, df.index.get_level_values(index_name))
492
+ if self._trace > 2:
493
+ self._cdb.print_log(
494
+ f"DataFrame after duplicating index to value column:\n{df}"
495
+ )
496
+
497
+ return df
498
+
499
+ def execute(self):
500
+ if self._trace > 0:
501
+ self._log_instructions(self._inst, self._inst_raw)
502
+ self._describe_container(self._cdb.container, "Connect Container (before):")
503
+
504
+ self._symbols_exist_cdb(self._name)
505
+
506
+ self._index_cols = self._convert_to_valid_pd_cols(self._index_cols)
507
+ self._value_cols = self._convert_to_valid_pd_cols(self._value_cols)
508
+ self._check_cols()
509
+
510
+ if self._stack == "infer":
511
+ if len(self._value_cols) > 1 or self._multiheader:
512
+ self._stack = True
513
+ else:
514
+ self._stack = False
515
+
516
+ self._usecols = self._index_cols + self._value_cols
517
+
518
+ # no duplicates in usecols, since pd.read_csv ignores duplicates in usecols and the indices in index_col do not match anymore
519
+ self._usecols = list(dict.fromkeys(self._usecols))
520
+
521
+ if self._index_cols:
522
+ if all(isinstance(i, int) for i in self._usecols) and not self._multiheader:
523
+ self._index_col = [sorted(self._usecols).index(i) for i in self._index_cols]
524
+ else:
525
+ self._index_col = self._index_cols
526
+ else:
527
+ self._index_col = None
528
+
529
+ # default dtype of index and text columns should be string
530
+ if "dtype" not in self._read_csv_args.keys():
531
+ dtypes = self._get_dtypes(self._index_col, self._usecols)
532
+ self._read_csv_args.update({"dtype": dtypes})
533
+
534
+ self._read_csv_args.update({"index_col": self._index_col})
535
+
536
+ # Multi-row header does not support usecols, therefore, we only
537
+ # support reading all columns and not a subset
538
+ if not self._multiheader:
539
+ self._read_csv_args.update({"usecols": self._usecols})
540
+
541
+ if self._trace > 1:
542
+ self._cdb.print_log(
543
+ f"Arguments for reading the CSV file:\n{self._read_csv_args}"
544
+ )
545
+
546
+ df = pd.read_csv(self._file, **self._read_csv_args)
547
+
548
+ if self._trace > 2:
549
+ self._cdb.print_log(
550
+ f"Raw DataFrame directly after reading the CSV file:\n{df}"
551
+ )
552
+
553
+ df = self._sort_value_columns(df)
554
+ df = self._copy_from_index_to_value(df, self._index_col, self._usecols)
555
+
556
+ dim = len(self._index_cols)
557
+ # write relaxed domain information
558
+ if dim == 0:
559
+ domain = []
560
+ else:
561
+ domain = [str(d) if d is not None else "*" for d in df.index.names]
562
+
563
+ if self._auto_row is not None and not df.index.empty:
564
+ self._generate_row_labels(df)
565
+
566
+ dim += 1
567
+ domain.insert(0, "*")
568
+
569
+ if self._auto_col is not None and not df.columns.empty:
570
+ self._generate_column_labels(df)
571
+
572
+ if self._stack:
573
+ dim += 1
574
+ domain.append("*")
575
+
576
+ elif self._stack:
577
+ if self._multiheader:
578
+ dim += len(self._header)
579
+ domain.extend(
580
+ [str(d) if d is not None else "*" for d in df.columns.names]
581
+ )
582
+ else:
583
+ dim += 1
584
+ domain.append("*")
585
+
586
+ if dim > 0:
587
+ if self._stack:
588
+ columns = df.columns
589
+
590
+ # stack from column axis to index axis
591
+ if self._multiheader:
592
+ df = self._stack_multiheader(df)
593
+ else:
594
+ # pandas-version-check
595
+ if self._pandas_version_before(
596
+ pd.__version__, "2.2"
597
+ ): # pandas < 2.2.0
598
+ df = df.stack(dropna=False)
599
+ else: # pandas >= 2.2.0
600
+ df = df.stack(future_stack=True)
601
+
602
+ if dim == 1 or (self._multiheader and dim == columns.nlevels):
603
+ # drop pandas default index level
604
+ df = df.droplevel(level=0)
605
+
606
+ if self._trace > 1:
607
+ self._cdb.print_log(
608
+ "Automatically stack column names to index for more"
609
+ " than one value column."
610
+ )
611
+ if self._trace > 2:
612
+ self._cdb.print_log(f"DataFrame after stack:\n{df}")
613
+
614
+ df = df.reset_index(allow_duplicates=True)
615
+
616
+ if self._trace > 2:
617
+ self._cdb.print_log(f"DataFrame after .reset_index():\n{df}")
618
+
619
+ # index substitution
620
+ if self._index_sub:
621
+ self._substitute_index(df)
622
+
623
+ # Substitute in categoricals
624
+ if self._stack:
625
+ if self._multiheader:
626
+ mod_columns = []
627
+ for i in range(columns.nlevels):
628
+ c = columns.get_level_values(level=i)
629
+ mod_columns.append(pd.Series(c).replace(self._index_sub))
630
+ columns = pd.MultiIndex.from_arrays(mod_columns)
631
+ else:
632
+ columns = pd.Index(pd.Series(columns).replace(self._index_sub))
633
+
634
+ elif self._sym_type == "set":
635
+ self._connect_error("Symbol type set requires at least one index column.")
636
+
637
+ if self._value_sub and (self._sym_type == "par" or self._value_cols):
638
+ self._substitute_values(df)
639
+ df.dropna(inplace=True)
640
+
641
+ if self._sym_type == "par":
642
+ sym = gt.Parameter(self._cdb.container, self._name, domain=domain)
643
+ else:
644
+ sym = gt.Set(self._cdb.container, self._name, domain=domain)
645
+
646
+ # reset the index to the default integer index
647
+ df = df.reset_index(drop=True)
648
+
649
+ if self._trace > 2:
650
+ self._cdb.print_log(
651
+ "Final DataFrame that will be processed by" f" GAMSTransfer:\n{df}"
652
+ )
653
+
654
+ sym.setRecords(df)
655
+
656
+ if dim > 0 and self._stack:
657
+ if self._multiheader:
658
+ # Reset domain labels
659
+ sym.domain_labels = domain
660
+ # Set categoricals to preserve uel order
661
+ for i in range(columns.nlevels):
662
+ c = columns.get_level_values(level=i)
663
+ c_dim = dim - columns.nlevels + i
664
+ self._set_categoricals(sym, c_dim, c)
665
+ else:
666
+ # Set categoricals to preserve uel order
667
+ self._set_categoricals(sym, dim - 1, columns)
668
+
669
+ if self._trace > 2:
670
+ self._cdb.print_log(
671
+ f"Connect Container symbol={self._name}:\n {sym.records}\n"
672
+ )
673
+
674
+ if self._trace > 0:
675
+ self._describe_container(self._cdb.container, "Connect Container (after):")