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,539 @@
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 copy
27
+ import re
28
+ from gams.connect.agents.connectagent import ConnectAgent
29
+ import gams.transfer as gt
30
+ import pandas as pd
31
+
32
+
33
+ class Projection(ConnectAgent):
34
+
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
+ self._aggregation_method = inst["aggregationMethod"]
41
+ self._as_set = inst["asSet"]
42
+ self._name = inst["name"]
43
+ self._new_name = inst["newName"]
44
+ self._text = inst["text"]
45
+ self._trace = inst["trace"]
46
+
47
+ def _generate_text(self, df, ssym, sdom, suffix_list, suffix_to_index):
48
+ """Generates set element text."""
49
+
50
+ if (
51
+ (isinstance(ssym, gt.Set) and not self._text in [None, ""])
52
+ or (
53
+ isinstance(ssym, (gt.Variable, gt.Equation))
54
+ and suffix_list
55
+ and not suffix_to_index
56
+ and not self._text in [None, ""]
57
+ )
58
+ or (isinstance(ssym, gt.Parameter) and not self._text in [None, ""])
59
+ ):
60
+ df.columns = [*df.columns[:-1], "element_text"]
61
+ df["element_text"] = df["element_text"].astype(str)
62
+ sdom.append("element_text")
63
+ execcmd = 'df["element_text"] = ("' + self._text + '")'
64
+ for i, r in enumerate(sdom):
65
+ execcmd = execcmd.replace(
66
+ "{" + r + "}",
67
+ '" + df[df.columns[' + str(i) + ']].astype(str) + "',
68
+ )
69
+ exec(execcmd)
70
+ if self._trace > 2:
71
+ self._cdb.print_log(f"DataFrame after text adjustment:\n{df}")
72
+
73
+ return df
74
+
75
+ def _combine_scalars(self):
76
+ """Aggregates a list of scalars of the same type into a 1-dimensional symbol (of the same type) that holds the symbol names as labels."""
77
+
78
+ symrecords_list = []
79
+ sym_types = []
80
+ for sym_name in self._name:
81
+ self._symbols_exist_cdb(sym_name, should_exist=True)
82
+ sym = self._cdb.container[sym_name]
83
+ if sym.dimension != 0:
84
+ self._connect_error(
85
+ f"Symbol '{sym_name}' needs to be a scalar when specified in <name> using a list."
86
+ )
87
+ sym_types.append(type(sym))
88
+ df = self._sym_records_no_none(sym).copy(deep=True)
89
+ symrecords_list.append(df)
90
+ if not all(t == sym_types[0] for t in sym_types):
91
+ self._connect_error(
92
+ "All symbols need to be of the same type when specified in <name> using a list."
93
+ )
94
+
95
+ df = pd.concat(symrecords_list, ignore_index=True)
96
+ df.insert(0, "uni_0", self._name)
97
+ sym0 = self._cdb.container[self._name[0]]
98
+
99
+ if isinstance(sym0, gt.Parameter):
100
+ gt.Parameter(
101
+ self._cdb.container,
102
+ self._new_name,
103
+ ["*"],
104
+ records=df,
105
+ )
106
+ elif isinstance(sym0, gt.Equation):
107
+ gt.Equation(
108
+ self._cdb.container,
109
+ self._new_name,
110
+ sym0.type,
111
+ ["*"],
112
+ records=df,
113
+ )
114
+ elif isinstance(sym0, gt.Variable):
115
+ gt.Variable(
116
+ self._cdb.container,
117
+ self._new_name,
118
+ sym0.type,
119
+ ["*"],
120
+ records=df,
121
+ )
122
+
123
+ def _split_index(self, match, symname, allow_duplicates=False):
124
+ """Splits provided index space into a list of indices. Return an empty list if no index space is provided."""
125
+
126
+ if match.group("index") is not None:
127
+ index = [d.strip() for d in match.group("index").split(",")]
128
+ if allow_duplicates:
129
+ return index
130
+
131
+ for i in index:
132
+ if index.count(i) > 1:
133
+ self._connect_error(
134
+ f"Multiple use of index >{i}< in index list of symbol >{symname}<."
135
+ )
136
+ return index
137
+ else:
138
+ return []
139
+
140
+ def _process_symbol_name(self):
141
+ """Processes strings provided by the name/newName option. Splits name/newName into the symbol name, suffix and index list."""
142
+
143
+ regex = r"(?P<name>[a-zA-Z0-9_]+)(\.?(?P<suffix>([a-zA-Z]*)|(\[[a-zA-Z,\s]*\])))?(\((?P<index>[a-zA-Z0-9_,\s]*)\))?"
144
+ ms = re.fullmatch(regex, self._name)
145
+ if not ms:
146
+ self._connect_error(f"Invalid <name>: >{self._name}<.")
147
+ mt = re.fullmatch(regex, self._new_name)
148
+ if not mt:
149
+ self._connect_error(f"Invalid <newName>: >{self._new_name}<.")
150
+
151
+ # NAME
152
+ ssym_name = ms.group("name")
153
+ self._symbols_exist_cdb(ssym_name, should_exist=True)
154
+ ssym = self._cdb.container[ssym_name]
155
+ tsym_name = mt.group("name")
156
+
157
+ # INDEX
158
+ # Source symbol
159
+ sindex_list = self._split_index(ms, ssym_name)
160
+
161
+ # Target symbol
162
+ tindex_list = self._split_index(mt, tsym_name, allow_duplicates=True)
163
+
164
+ if len(sindex_list) != ssym.dimension:
165
+ self._connect_error(
166
+ f"Number of provided indices for symbol >{ssym_name}< <> dimension of the symbol ({len(sindex_list)}<>{ssym.dimension})."
167
+ )
168
+ if set(tindex_list) - set(sindex_list):
169
+ self._connect_error(
170
+ f"Unknown index >{(set(tindex_list) - set(sindex_list))}< in <newName>: >{self._new_name}<."
171
+ )
172
+ index_map = [sindex_list.index(d) for d in tindex_list]
173
+ tsym_domain = [ssym.domain[d] for d in index_map]
174
+
175
+ # SUFFIX
176
+ suffix_dict = {
177
+ "l": "level",
178
+ "m": "marginal",
179
+ "lo": "lower",
180
+ "up": "upper",
181
+ "scale": "scale",
182
+ "all": "all",
183
+ }
184
+ attribute_list = [a for a in suffix_dict.values() if a != "all"]
185
+
186
+ if mt.group("suffix"):
187
+ self._connect_error(f"No suffix allowed on <newName>: >{self._new_name}<.")
188
+ suffix = ms.group("suffix")
189
+ if suffix == "":
190
+ suffix_list = []
191
+
192
+ suffix_to_index = False
193
+ if suffix:
194
+ if not isinstance(ssym, (gt.Variable, gt.Equation)):
195
+ self._connect_error(
196
+ f"Suffix given but symbol >{ssym_name}< is not a variable or an equation."
197
+ )
198
+
199
+ if re.search(r"[\[\]]", suffix):
200
+ suffix_to_index = True
201
+ tsym_domain.append("attribute")
202
+ suffix = re.sub(r"[\[\]]", "", suffix)
203
+ if suffix == "":
204
+ self._connect_error("Suffix list is empty.")
205
+ else:
206
+ suffix_list = list(
207
+ dict.fromkeys(s.strip() for s in suffix.split(","))
208
+ )
209
+ else:
210
+ suffix_list = [suffix]
211
+
212
+ for s in suffix_list:
213
+ if s not in suffix_dict.keys():
214
+ self._connect_error(
215
+ f"Unknown suffix >{s}< (use {', '.join([s for s in suffix_dict.keys()])})."
216
+ )
217
+ # resolve v.all and v.[all]
218
+ if "all" in suffix_list:
219
+ suffix_list = attribute_list
220
+ if not suffix_to_index: # might have been added before already
221
+ suffix_to_index = True
222
+ tsym_domain.append("attribute")
223
+ else:
224
+ suffix_list = list(map(suffix_dict.get, suffix_list))
225
+
226
+ if self._trace > 1:
227
+ self._cdb.print_log(
228
+ "Processed <name>:"
229
+ f"\n name: >{ssym_name}<"
230
+ f"\n index: >{sindex_list}<"
231
+ f"\n suffix: >{suffix_list}<"
232
+ f"\n suffix to index: >{suffix_to_index}<"
233
+ "\n"
234
+ )
235
+ self._cdb.print_log(
236
+ "Processed <newName>:"
237
+ f"\n name: >{tsym_name}<"
238
+ f"\n index: >{tindex_list}<"
239
+ "\n"
240
+ )
241
+
242
+ self._symbols_exist_cdb(tsym_name)
243
+
244
+ return (
245
+ ssym,
246
+ ssym_name,
247
+ sindex_list,
248
+ suffix_list,
249
+ suffix_to_index,
250
+ tsym_name,
251
+ tindex_list,
252
+ index_map,
253
+ tsym_domain,
254
+ )
255
+
256
+ def _create_target_symbol(
257
+ self, ssym, ssym_name, tsym_name, tsym_domain, suffix_list
258
+ ):
259
+ """Create target symbol in Connect container."""
260
+
261
+ if self._as_set or isinstance(ssym, gt.Set):
262
+ tsym = gt.Set(self._cdb.container, tsym_name, tsym_domain)
263
+ elif suffix_list or isinstance(ssym, gt.Parameter):
264
+ tsym = gt.Parameter(self._cdb.container, tsym_name, tsym_domain)
265
+ elif isinstance(ssym, gt.Equation):
266
+ tsym = gt.Equation(self._cdb.container, tsym_name, ssym.type, tsym_domain)
267
+ elif isinstance(ssym, gt.Variable):
268
+ tsym = gt.Variable(self._cdb.container, tsym_name, ssym.type, tsym_domain)
269
+ else:
270
+ self._connect_error(
271
+ f"Projection can't handle symbol type >{type(ssym)}< of symbol >{ssym_name}<."
272
+ )
273
+ if self._trace > 1:
274
+ self._cdb.print_log(
275
+ f"Created >{tsym_name}< as {len(tsym_domain)}-dim {type(tsym)}.\n"
276
+ )
277
+
278
+ return tsym
279
+
280
+ def _apply_aggregation_method(self, df, ssym_name, index_map):
281
+ """Applies selected aggregation method."""
282
+
283
+ if len(index_map) > 0:
284
+ df = df.groupby(
285
+ [self._cdb.container[ssym_name].domain_labels[d] for d in index_map]
286
+ )
287
+ if not hasattr(df, self._aggregation_method):
288
+ self._connect_error(
289
+ f"Invalid aggregationMethod >{self._aggregation_method}<."
290
+ )
291
+ func = getattr(df, self._aggregation_method)
292
+ if not callable(func):
293
+ self._connect_error(
294
+ f"Invalid aggregationMethod >{self._aggregation_method}<. Not callable."
295
+ )
296
+ df = func()
297
+ if self._trace > 2:
298
+ self._cdb.print_log(f"DataFrame after aggregation:\n{df}")
299
+ return df
300
+
301
+ def _drop_text(self, df, ssym, suffix_list, suffix_to_index):
302
+ """Drops set element text."""
303
+
304
+ if isinstance(ssym, gt.Set) and self._text == "":
305
+ df.drop(columns=df.columns[-1], inplace=True)
306
+ elif isinstance(ssym, (gt.Variable, gt.Equation)) and suffix_to_index:
307
+ df.drop(columns=df.columns[-1], inplace=True)
308
+ elif (
309
+ isinstance(ssym, (gt.Variable, gt.Equation))
310
+ and suffix_list
311
+ and self._text in [None, ""]
312
+ ):
313
+ df.drop(columns=df.columns[-1], inplace=True)
314
+ elif isinstance(ssym, (gt.Variable, gt.Equation)) and not suffix_list:
315
+ df.drop(
316
+ columns=["level", "marginal", "lower", "upper", "scale"],
317
+ inplace=True,
318
+ )
319
+ elif isinstance(ssym, gt.Parameter) and self._text in [None, ""]:
320
+ df.drop(columns=df.columns[-1], inplace=True)
321
+
322
+ return df
323
+
324
+ def _apply_categories(self, ssym, tsym, suffix_to_index, suffix_list, index_map):
325
+ """Applies categories from the source symbol to the domains of the target symbol."""
326
+
327
+ if tsym.dimension > 0:
328
+ for i in range(tsym.dimension):
329
+ if suffix_to_index and i == tsym.dimension - 1:
330
+ cats = suffix_list
331
+ else:
332
+ cats = ssym.records[
333
+ ssym.records.columns[index_map[i]]
334
+ ].cat.categories
335
+
336
+ tsym.records.isetitem(
337
+ i,
338
+ tsym.records.iloc[:, i].astype(
339
+ pd.CategoricalDtype(
340
+ categories=cats,
341
+ ordered=True,
342
+ )
343
+ ),
344
+ )
345
+
346
+ def execute(self):
347
+ if self._trace > 0:
348
+ self._log_instructions(self._inst, self._inst_raw)
349
+ self._describe_container(self._cdb.container, "Connect Container (before):")
350
+
351
+ # list of scalars into a 1-dim parameter/var/equ
352
+ if isinstance(self._name, list):
353
+ self._combine_scalars()
354
+
355
+ if self._trace > 2:
356
+ self._cdb.print_log(
357
+ f"Connect Container symbol={self._new_name}:\n {self._cdb.container[self._new_name].records}\n"
358
+ )
359
+
360
+ return
361
+
362
+ (
363
+ ssym,
364
+ ssym_name,
365
+ sindex_list,
366
+ suffix_list,
367
+ suffix_to_index,
368
+ tsym_name,
369
+ tindex_list,
370
+ index_map,
371
+ tsym_domain,
372
+ ) = self._process_symbol_name()
373
+
374
+ # Get unique index, map lists
375
+ tindex_list_unq = list(dict.fromkeys(tindex_list)) # (i,j,i) -> (i,j)
376
+ index_map_unq = list(dict.fromkeys(index_map)) # (0,1,0) -> (0,1)
377
+
378
+ if tindex_list_unq != tindex_list: # duplicate index (e.g. newName: p(i,j,i))
379
+ if self._trace > 1:
380
+ self._cdb.print_log(
381
+ f"Duplicate indices found. Processing symbol without duplicate indices: {tsym_name}({','.join(tindex_list)}) -> {tsym_name}({','.join(tindex_list_unq)}).\n"
382
+ )
383
+
384
+ tsym = self._create_target_symbol(
385
+ ssym, ssym_name, tsym_name, tsym_domain, suffix_list
386
+ )
387
+
388
+ assert len(index_map) == tsym.dimension or (
389
+ len(index_map) + 1 == tsym.dimension and suffix_to_index
390
+ ), "Number of domains for <newName> <> dimension of <newName>"
391
+ assert len(tsym_domain) == tsym.dimension or (
392
+ len(tsym_domain) + 1 == tsym.dimension and suffix_to_index
393
+ ), "Number of domains for <newName> <> dimension of <newName>"
394
+ assert (
395
+ not suffix_list or isinstance(tsym, gt.Parameter) or self._as_set
396
+ ), "Type of <newName> needs to be parameter or asSet needs to be True"
397
+ assert (
398
+ suffix_list
399
+ or suffix_to_index
400
+ or self._as_set
401
+ or isinstance(ssym, type(tsym))
402
+ ), "No suffix, asSet: False but type of <name> <> type of <newName>"
403
+
404
+ df = copy.deepcopy(self._cdb.container[ssym_name].records)
405
+ # For symbols with None records or empty dataframe, an empty df is assigned then returned
406
+ if df is None or df.empty:
407
+ self._transform_sym_none_to_empty(tsym)
408
+ return
409
+
410
+ if suffix_list:
411
+ suffixes_to_drop = set(
412
+ ["level", "marginal", "lower", "upper", "scale"]
413
+ ) - set(suffix_list)
414
+ df.drop(columns=list(suffixes_to_drop), inplace=True)
415
+ if self._trace > 2:
416
+ self._cdb.print_log(f"DataFrame after dropping suffixes:\n{df}")
417
+
418
+ if isinstance(tsym, gt.Set):
419
+ df = self._generate_text(
420
+ df, ssym, sindex_list, suffix_list, suffix_to_index
421
+ )
422
+
423
+ if self._aggregation_method is None: # no aggregation
424
+ permutation = index_map_unq != list(range(len(index_map_unq))) or len(
425
+ sindex_list
426
+ ) > len(tindex_list_unq)
427
+ if permutation:
428
+ if self._trace > 1:
429
+ self._cdb.print_log("Permutation only.")
430
+ attributes = df.columns.tolist()[ssym.dimension :]
431
+ cols_permuted = [
432
+ df.columns.tolist()[i] for i in index_map_unq
433
+ ] + attributes
434
+ if self._trace > 2:
435
+ self._cdb.print_log(f"DataFrame before permutation:\n{df}")
436
+ if self._trace > 1:
437
+ self._cdb.print_log(f"Column permutation:\n{cols_permuted}")
438
+ df = df.reindex(columns=cols_permuted)
439
+ if self._trace > 2:
440
+ self._cdb.print_log(f"DataFrame after permutation:\n{df}")
441
+
442
+ if suffix_to_index:
443
+ # stack suffix index
444
+ if ssym.dimension == 0:
445
+ # source and target symbols have 0 dimensions (scalar)
446
+ df = df.stack().droplevel(0)
447
+ df = list(dict(df).items())
448
+ else:
449
+ if len(index_map_unq) > 0:
450
+ new_index = [
451
+ df.columns.tolist()[i] for i in range(len(index_map_unq))
452
+ ]
453
+ df.set_index(new_index, inplace=True)
454
+ df = df.stack().reset_index()
455
+ else:
456
+ df = df.stack().droplevel(0)
457
+ if self._trace > 2:
458
+ self._cdb.print_log(f"DataFrame after stacking suffix index:\n{df}")
459
+
460
+ else:
461
+ # TODO: Raise error if sets, variable or equations (without suffix) are not used with first/last aggregation
462
+ drop_cols = self._cdb.container[ssym_name].domain_labels[: ssym.dimension]
463
+ df[drop_cols] = df[drop_cols].astype(str)
464
+
465
+ if (
466
+ tsym.dimension == 0 or (tsym.dimension == 1 and suffix_to_index)
467
+ ) and self._aggregation_method in [
468
+ "first",
469
+ "last",
470
+ ]:
471
+ # target symbol has 0 dimensions (scalar) and aggregation first/last -> fast aggregation
472
+
473
+ df.drop(columns=drop_cols, inplace=True)
474
+ if self._trace > 2:
475
+ self._cdb.print_log(f"DataFrame after dropping columns:\n{df}")
476
+ if self._aggregation_method == "first":
477
+ df = df.iloc[0]
478
+ else:
479
+ df = df.iloc[-1]
480
+ if isinstance(tsym, (gt.Variable, gt.Equation)):
481
+ df = dict(df)
482
+ elif suffix_to_index:
483
+ df = list(dict(df).items())
484
+ if self._trace > 2:
485
+ self._cdb.print_log(
486
+ f"DataFrame after first/last aggregation:\n{df}"
487
+ )
488
+ else:
489
+ if ssym.dimension != 0:
490
+ multi_index = pd.MultiIndex.from_frame(
491
+ df[self._cdb.container[ssym_name].domain_labels]
492
+ )
493
+ df.set_index(multi_index, inplace=True)
494
+ if self._trace > 2:
495
+ self._cdb.print_log(f"DataFrame after .set_index():\n{df}")
496
+
497
+ df.drop(columns=drop_cols, inplace=True)
498
+ if self._trace > 2:
499
+ self._cdb.print_log(f"DataFrame after dropping columns:\n{df}")
500
+
501
+ df = self._apply_aggregation_method(df, ssym_name, index_map_unq)
502
+
503
+ if isinstance(df, pd.DataFrame):
504
+ if suffix_to_index:
505
+ df = df.stack()
506
+ if self._trace > 2:
507
+ self._cdb.print_log(
508
+ f"DataFrame after stacking suffix index:\n{df}"
509
+ )
510
+
511
+ df = df.reset_index(drop=False)
512
+ if self._trace > 2:
513
+ self._cdb.print_log(f"DataFrame after .reset_index():\n{df}")
514
+
515
+ if isinstance(df, pd.DataFrame) and isinstance(tsym, gt.Set):
516
+ df = self._drop_text(df, ssym, suffix_list, suffix_to_index)
517
+
518
+ if self._trace > 2:
519
+ self._cdb.print_log(f"DataFrame before .setRecords():\n{df}")
520
+
521
+ if tindex_list_unq != tindex_list: # duplicate index (e.g. newName: p(i,j,i))
522
+ if self._trace > 1:
523
+ self._cdb.print_log(
524
+ f"Restoring duplicate indices: {tsym_name}({','.join(tindex_list_unq)}) -> {tsym_name}({','.join(tindex_list)}).\n"
525
+ )
526
+ new_index = [tindex_list_unq.index(i) for i in tindex_list]
527
+ new_index = [df.columns.tolist()[x] for x in new_index]
528
+ new_cols = new_index + df.columns.tolist()[len(tindex_list_unq) :]
529
+ df = df[new_cols]
530
+ tsym.setRecords(df)
531
+
532
+ self._apply_categories(ssym, tsym, suffix_to_index, suffix_list, index_map)
533
+
534
+ if self._trace > 2:
535
+ self._cdb.print_log(
536
+ f"Connect Container symbol={tsym_name}:\n {tsym.records}\n"
537
+ )
538
+ if self._trace > 0:
539
+ self._describe_container(self._cdb.container, "Connect Container (after):")
@@ -0,0 +1,71 @@
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 gams.connect.agents.connectagent import ConnectAgent
27
+
28
+
29
+ class PythonCode(ConnectAgent):
30
+
31
+ def __init__(self, cdb, inst, agent_index):
32
+ super().__init__(cdb, inst, agent_index)
33
+ self._parse_options(self._inst)
34
+
35
+ def _parse_options(self, inst):
36
+ self._code = inst["code"]
37
+ self._trace = inst["trace"]
38
+
39
+ def execute(self):
40
+ if self._trace > 0:
41
+ self._log_instructions(self._inst)
42
+ self._describe_container(self._cdb.container, "Connect Container (before):")
43
+
44
+ loc = dict(locals())
45
+ loc["instructions"] = []
46
+ loc["connect"] = self._cdb
47
+ loc["gams"] = self._cdb.ecdb
48
+ exec(self._inst["code"], loc)
49
+
50
+ if self._trace > 1:
51
+ self._cdb.print_log(
52
+ f"Number of generated instructions: {len(loc['instructions'])}"
53
+ )
54
+ if self._trace > 2:
55
+ self._cdb.print_log("List of generated instructions:")
56
+ for idx, gen_inst in enumerate(loc["instructions"]):
57
+ if isinstance(gen_inst, dict):
58
+ agent_name = list(gen_inst.keys())[0]
59
+ self._log_instructions(
60
+ gen_inst[agent_name], description=f"({idx + 1}) {agent_name}:"
61
+ )
62
+ else:
63
+ self._cdb.print_log(
64
+ f"Warning: Could not log generated instructions since item {idx} was not of type 'dict' but '{type(gen_inst).__name__}'."
65
+ )
66
+ self._cdb.print_log("")
67
+
68
+ if self._trace > 0:
69
+ self._describe_container(self._cdb.container, "Connect Container (after):")
70
+
71
+ return loc["instructions"]