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.
- gams/__init__.py +27 -0
- gams/_version.py +1 -0
- gams/connect/__init__.py +28 -0
- gams/connect/agents/__init__.py +24 -0
- gams/connect/agents/_excel/__init__.py +32 -0
- gams/connect/agents/_excel/excelagent.py +312 -0
- gams/connect/agents/_excel/workbook.py +155 -0
- gams/connect/agents/_sqlconnectors/__init__.py +42 -0
- gams/connect/agents/_sqlconnectors/_accesshandler.py +211 -0
- gams/connect/agents/_sqlconnectors/_databasehandler.py +250 -0
- gams/connect/agents/_sqlconnectors/_mysqlhandler.py +168 -0
- gams/connect/agents/_sqlconnectors/_postgreshandler.py +131 -0
- gams/connect/agents/_sqlconnectors/_pyodbchandler.py +112 -0
- gams/connect/agents/_sqlconnectors/_sqlalchemyhandler.py +74 -0
- gams/connect/agents/_sqlconnectors/_sqlitehandler.py +262 -0
- gams/connect/agents/_sqlconnectors/_sqlserverhandler.py +179 -0
- gams/connect/agents/concatenate.py +440 -0
- gams/connect/agents/connectagent.py +743 -0
- gams/connect/agents/csvreader.py +675 -0
- gams/connect/agents/csvwriter.py +151 -0
- gams/connect/agents/domainwriter.py +143 -0
- gams/connect/agents/excelreader.py +756 -0
- gams/connect/agents/excelwriter.py +467 -0
- gams/connect/agents/filter.py +223 -0
- gams/connect/agents/gamsreader.py +112 -0
- gams/connect/agents/gamswriter.py +239 -0
- gams/connect/agents/gdxreader.py +109 -0
- gams/connect/agents/gdxwriter.py +146 -0
- gams/connect/agents/labelmanipulator.py +303 -0
- gams/connect/agents/projection.py +539 -0
- gams/connect/agents/pythoncode.py +71 -0
- gams/connect/agents/rawcsvreader.py +248 -0
- gams/connect/agents/rawexcelreader.py +312 -0
- gams/connect/agents/schema/CSVReader.yaml +92 -0
- gams/connect/agents/schema/CSVWriter.yaml +44 -0
- gams/connect/agents/schema/Concatenate.yaml +52 -0
- gams/connect/agents/schema/DomainWriter.yaml +25 -0
- gams/connect/agents/schema/ExcelReader.yaml +121 -0
- gams/connect/agents/schema/ExcelWriter.yaml +78 -0
- gams/connect/agents/schema/Filter.yaml +74 -0
- gams/connect/agents/schema/GAMSReader.yaml +20 -0
- gams/connect/agents/schema/GAMSWriter.yaml +47 -0
- gams/connect/agents/schema/GDXReader.yaml +23 -0
- gams/connect/agents/schema/GDXWriter.yaml +32 -0
- gams/connect/agents/schema/LabelManipulator.yaml +99 -0
- gams/connect/agents/schema/Projection.yaml +24 -0
- gams/connect/agents/schema/PythonCode.yaml +6 -0
- gams/connect/agents/schema/RawCSVReader.yaml +34 -0
- gams/connect/agents/schema/RawExcelReader.yaml +42 -0
- gams/connect/agents/schema/SQLReader.yaml +75 -0
- gams/connect/agents/schema/SQLWriter.yaml +103 -0
- gams/connect/agents/sqlreader.py +301 -0
- gams/connect/agents/sqlwriter.py +276 -0
- gams/connect/connectdatabase.py +275 -0
- gams/connect/connectvalidator.py +93 -0
- gams/connect/errors.py +34 -0
- gams/control/__init__.py +136 -0
- gams/control/database.py +2231 -0
- gams/control/execution.py +1900 -0
- gams/control/options.py +2792 -0
- gams/control/workspace.py +1198 -0
- gams/core/__init__.py +24 -0
- gams/core/cfg/__init__.py +26 -0
- gams/core/cfg/_cfgmcc.cp312-win_amd64.pyd +0 -0
- gams/core/cfg/cfgmcc.py +519 -0
- gams/core/dct/__init__.py +26 -0
- gams/core/dct/_dctmcc.cp312-win_amd64.pyd +0 -0
- gams/core/dct/dctmcc.py +574 -0
- gams/core/embedded/__init__.py +26 -0
- gams/core/embedded/gamsemb.py +1024 -0
- gams/core/emp/__init__.py +24 -0
- gams/core/emp/emplexer.py +89 -0
- gams/core/emp/empyacc.py +281 -0
- gams/core/gdx/__init__.py +26 -0
- gams/core/gdx/_gdxcc.cp312-win_amd64.pyd +0 -0
- gams/core/gdx/gdxcc.py +866 -0
- gams/core/gev/__init__.py +26 -0
- gams/core/gev/_gevmcc.cp312-win_amd64.pyd +0 -0
- gams/core/gev/gevmcc.py +855 -0
- gams/core/gmd/__init__.py +26 -0
- gams/core/gmd/_gmdcc.cp312-win_amd64.pyd +0 -0
- gams/core/gmd/gmdcc.py +917 -0
- gams/core/gmo/__init__.py +26 -0
- gams/core/gmo/_gmomcc.cp312-win_amd64.pyd +0 -0
- gams/core/gmo/gmomcc.py +2046 -0
- gams/core/idx/__init__.py +26 -0
- gams/core/idx/_idxcc.cp312-win_amd64.pyd +0 -0
- gams/core/idx/idxcc.py +510 -0
- gams/core/numpy/__init__.py +29 -0
- gams/core/numpy/_gams2numpy.cp312-win_amd64.pyd +0 -0
- gams/core/numpy/gams2numpy.py +1048 -0
- gams/core/opt/__init__.py +26 -0
- gams/core/opt/_optcc.cp312-win_amd64.pyd +0 -0
- gams/core/opt/optcc.py +840 -0
- gams/engine/__init__.py +204 -0
- gams/engine/api/__init__.py +13 -0
- gams/engine/api/auth_api.py +7653 -0
- gams/engine/api/cleanup_api.py +751 -0
- gams/engine/api/default_api.py +887 -0
- gams/engine/api/hypercube_api.py +2629 -0
- gams/engine/api/jobs_api.py +5229 -0
- gams/engine/api/licenses_api.py +2220 -0
- gams/engine/api/namespaces_api.py +7783 -0
- gams/engine/api/usage_api.py +5627 -0
- gams/engine/api/users_api.py +5931 -0
- gams/engine/api_client.py +804 -0
- gams/engine/api_response.py +21 -0
- gams/engine/configuration.py +601 -0
- gams/engine/exceptions.py +216 -0
- gams/engine/models/__init__.py +86 -0
- gams/engine/models/bad_input.py +89 -0
- gams/engine/models/cleanable_job_result.py +104 -0
- gams/engine/models/cleanable_job_result_page.py +113 -0
- gams/engine/models/engine_license.py +107 -0
- gams/engine/models/files_not_found.py +93 -0
- gams/engine/models/forwarded_token_response.py +112 -0
- gams/engine/models/generic_key_value_pair.py +89 -0
- gams/engine/models/hypercube.py +160 -0
- gams/engine/models/hypercube_page.py +111 -0
- gams/engine/models/hypercube_summary.py +91 -0
- gams/engine/models/hypercube_token.py +97 -0
- gams/engine/models/identity_provider.py +107 -0
- gams/engine/models/identity_provider_ldap.py +121 -0
- gams/engine/models/identity_provider_oauth2.py +146 -0
- gams/engine/models/identity_provider_oauth2_scope.py +89 -0
- gams/engine/models/identity_provider_oauth2_with_secret.py +152 -0
- gams/engine/models/identity_provider_oidc.py +133 -0
- gams/engine/models/identity_provider_oidc_with_secret.py +143 -0
- gams/engine/models/inex.py +91 -0
- gams/engine/models/invitation.py +136 -0
- gams/engine/models/invitation_quota.py +106 -0
- gams/engine/models/invitation_token.py +87 -0
- gams/engine/models/job.py +165 -0
- gams/engine/models/job_no_text_entry.py +138 -0
- gams/engine/models/job_no_text_entry_page.py +111 -0
- gams/engine/models/license.py +91 -0
- gams/engine/models/log_piece.py +96 -0
- gams/engine/models/message.py +87 -0
- gams/engine/models/message_and_token.py +99 -0
- gams/engine/models/message_with_webhook_id.py +89 -0
- gams/engine/models/model_auth_token.py +87 -0
- gams/engine/models/model_configuration.py +125 -0
- gams/engine/models/model_default_instance.py +99 -0
- gams/engine/models/model_default_user_instance.py +98 -0
- gams/engine/models/model_hypercube_job.py +106 -0
- gams/engine/models/model_hypercube_usage.py +130 -0
- gams/engine/models/model_instance_info.py +116 -0
- gams/engine/models/model_instance_info_full.py +123 -0
- gams/engine/models/model_instance_pool_info.py +112 -0
- gams/engine/models/model_job_labels.py +179 -0
- gams/engine/models/model_job_usage.py +133 -0
- gams/engine/models/model_pool_usage.py +124 -0
- gams/engine/models/model_usage.py +115 -0
- gams/engine/models/model_user.py +96 -0
- gams/engine/models/model_userinstance_info.py +119 -0
- gams/engine/models/model_userinstancepool_info.py +95 -0
- gams/engine/models/model_version.py +91 -0
- gams/engine/models/models.py +120 -0
- gams/engine/models/namespace.py +104 -0
- gams/engine/models/namespace_quota.py +96 -0
- gams/engine/models/namespace_with_permission.py +96 -0
- gams/engine/models/not_found.py +91 -0
- gams/engine/models/password_policy.py +97 -0
- gams/engine/models/perm_and_username.py +89 -0
- gams/engine/models/quota.py +117 -0
- gams/engine/models/quota_exceeded.py +97 -0
- gams/engine/models/status_code_meaning.py +89 -0
- gams/engine/models/stream_entry.py +89 -0
- gams/engine/models/system_wide_license.py +92 -0
- gams/engine/models/text_entries.py +87 -0
- gams/engine/models/text_entry.py +101 -0
- gams/engine/models/time_span.py +95 -0
- gams/engine/models/time_span_pool_worker.py +99 -0
- gams/engine/models/token_forward_error.py +87 -0
- gams/engine/models/user.py +127 -0
- gams/engine/models/user_group_member.py +96 -0
- gams/engine/models/user_groups.py +108 -0
- gams/engine/models/vapid_info.py +87 -0
- gams/engine/models/webhook.py +138 -0
- gams/engine/models/webhook_parameterized_event.py +99 -0
- gams/engine/py.typed +0 -0
- gams/engine/rest.py +258 -0
- gams/magic/__init__.py +32 -0
- gams/magic/gams_magic.py +142 -0
- gams/magic/interactive.py +402 -0
- gams/tools/__init__.py +30 -0
- gams/tools/errors.py +34 -0
- gams/tools/toolcollection/__init__.py +24 -0
- gams/tools/toolcollection/alg/__init__.py +24 -0
- gams/tools/toolcollection/alg/rank.py +51 -0
- gams/tools/toolcollection/data/__init__.py +24 -0
- gams/tools/toolcollection/data/csvread.py +444 -0
- gams/tools/toolcollection/data/csvwrite.py +311 -0
- gams/tools/toolcollection/data/exceldump.py +47 -0
- gams/tools/toolcollection/data/sqlitewrite.py +276 -0
- gams/tools/toolcollection/gdxservice/__init__.py +24 -0
- gams/tools/toolcollection/gdxservice/gdxencoding.py +104 -0
- gams/tools/toolcollection/gdxservice/gdxrename.py +94 -0
- gams/tools/toolcollection/linalg/__init__.py +24 -0
- gams/tools/toolcollection/linalg/cholesky.py +57 -0
- gams/tools/toolcollection/linalg/eigenvalue.py +56 -0
- gams/tools/toolcollection/linalg/eigenvector.py +58 -0
- gams/tools/toolcollection/linalg/invert.py +55 -0
- gams/tools/toolcollection/linalg/ols.py +138 -0
- gams/tools/toolcollection/tooltemplate.py +321 -0
- gams/tools/toolcollection/win32/__init__.py +24 -0
- gams/tools/toolcollection/win32/excelmerge.py +93 -0
- gams/tools/toolcollection/win32/exceltalk.py +76 -0
- gams/tools/toolcollection/win32/msappavail.py +49 -0
- gams/tools/toolcollection/win32/shellexecute.py +54 -0
- gams/tools/tools.py +116 -0
- gams/transfer/__init__.py +35 -0
- gams/transfer/_abcs/__init__.py +37 -0
- gams/transfer/_abcs/container_abcs.py +433 -0
- gams/transfer/_internals/__init__.py +63 -0
- gams/transfer/_internals/algorithms.py +436 -0
- gams/transfer/_internals/casepreservingdict.py +124 -0
- gams/transfer/_internals/constants.py +270 -0
- gams/transfer/_internals/domainviolation.py +103 -0
- gams/transfer/_internals/specialvalues.py +172 -0
- gams/transfer/containers/__init__.py +26 -0
- gams/transfer/containers/_container.py +1794 -0
- gams/transfer/containers/_io/__init__.py +28 -0
- gams/transfer/containers/_io/containers.py +164 -0
- gams/transfer/containers/_io/gdx.py +1029 -0
- gams/transfer/containers/_io/gmd.py +872 -0
- gams/transfer/containers/_mixins/__init__.py +26 -0
- gams/transfer/containers/_mixins/ccc.py +1274 -0
- gams/transfer/syms/__init__.py +33 -0
- gams/transfer/syms/_methods/__init__.py +24 -0
- gams/transfer/syms/_methods/tables.py +120 -0
- gams/transfer/syms/_methods/toDict.py +115 -0
- gams/transfer/syms/_methods/toList.py +83 -0
- gams/transfer/syms/_methods/toValue.py +60 -0
- gams/transfer/syms/_mixins/__init__.py +32 -0
- gams/transfer/syms/_mixins/equals.py +626 -0
- gams/transfer/syms/_mixins/generateRecords.py +499 -0
- gams/transfer/syms/_mixins/pivot.py +313 -0
- gams/transfer/syms/_mixins/pve.py +627 -0
- gams/transfer/syms/_mixins/sa.py +27 -0
- gams/transfer/syms/_mixins/sapve.py +27 -0
- gams/transfer/syms/_mixins/saua.py +27 -0
- gams/transfer/syms/_mixins/sauapve.py +199 -0
- gams/transfer/syms/_mixins/spve.py +1528 -0
- gams/transfer/syms/_mixins/ve.py +936 -0
- gams/transfer/syms/container_syms/__init__.py +31 -0
- gams/transfer/syms/container_syms/_alias.py +984 -0
- gams/transfer/syms/container_syms/_equation.py +333 -0
- gams/transfer/syms/container_syms/_parameter.py +973 -0
- gams/transfer/syms/container_syms/_set.py +604 -0
- gams/transfer/syms/container_syms/_universe_alias.py +461 -0
- gams/transfer/syms/container_syms/_variable.py +321 -0
- gamsapi-52.5.0.dist-info/METADATA +150 -0
- gamsapi-52.5.0.dist-info/RECORD +257 -0
- gamsapi-52.5.0.dist-info/WHEEL +5 -0
- gamsapi-52.5.0.dist-info/licenses/LICENSE +22 -0
- gamsapi-52.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,756 @@
|
|
|
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 warnings
|
|
27
|
+
from copy import copy
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import datetime
|
|
31
|
+
from gams.connect.agents._excel import ExcelAgent
|
|
32
|
+
from gams.connect.agents._excel import Workbook
|
|
33
|
+
from gams.connect.connectvalidator import ConnectValidator
|
|
34
|
+
from gams.core.gdx import GMS_SV_UNDEF
|
|
35
|
+
import gams.transfer as gt
|
|
36
|
+
from gams.transfer.syms._methods.tables import (
|
|
37
|
+
_assert_axes_no_nans,
|
|
38
|
+
_get_implied_dimension_from_axes,
|
|
39
|
+
_flatten_and_convert,
|
|
40
|
+
)
|
|
41
|
+
import numpy as np
|
|
42
|
+
from openpyxl.utils.cell import column_index_from_string
|
|
43
|
+
from pandas.api.types import is_datetime64_any_dtype as is_datetime
|
|
44
|
+
import pandas as pd
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ExcelReader(ExcelAgent):
|
|
48
|
+
_index_parameter_map = {
|
|
49
|
+
"rdim": "rowDimension",
|
|
50
|
+
"rowdimension": "rowDimension",
|
|
51
|
+
"cdim": "columnDimension",
|
|
52
|
+
"columndimension": "columnDimension",
|
|
53
|
+
"skipempty": "skipEmpty",
|
|
54
|
+
"se": "skipEmpty",
|
|
55
|
+
"ignoretext": "ignoreText",
|
|
56
|
+
"automerge": "autoMerge",
|
|
57
|
+
"ignorerows": "ignoreRows",
|
|
58
|
+
"ignorecolumns": "ignoreColumns",
|
|
59
|
+
"mergedcells": "mergedCells",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def __init__(self, cdb, inst, agent_index):
|
|
63
|
+
super().__init__(cdb, inst, agent_index)
|
|
64
|
+
self._wb = None
|
|
65
|
+
self._parse_options(self._inst)
|
|
66
|
+
if os.path.splitext(self._file)[1] in [".xls"]:
|
|
67
|
+
self._connect_error("The ExcelReader does not support .xls files.")
|
|
68
|
+
|
|
69
|
+
def _parse_options(self, inst):
|
|
70
|
+
inst["file"] = os.path.abspath(inst["file"])
|
|
71
|
+
self._file = inst["file"]
|
|
72
|
+
self._cdim = inst["columnDimension"]
|
|
73
|
+
self._rdim = inst["rowDimension"]
|
|
74
|
+
self._sym_type = inst["type"]
|
|
75
|
+
self._symbols = self._dict_get(inst, "symbols", [])
|
|
76
|
+
self._merged_cells = inst["mergedCells"]
|
|
77
|
+
self._skip_empty = inst["skipEmpty"]
|
|
78
|
+
self._value_subs = inst["valueSubstitutions"]
|
|
79
|
+
self._index_subs = inst["indexSubstitutions"]
|
|
80
|
+
self._index = inst["index"]
|
|
81
|
+
self._auto_merge = inst["autoMerge"]
|
|
82
|
+
self._ignore_text = inst["ignoreText"]
|
|
83
|
+
self._trace = inst["trace"]
|
|
84
|
+
self._is_xlsb = self._file.endswith(".xlsb")
|
|
85
|
+
if self._is_xlsb:
|
|
86
|
+
if not sys.platform.startswith("win"):
|
|
87
|
+
self._connect_error(
|
|
88
|
+
f"Excel binary files (.xlsb) are supported on Windows only."
|
|
89
|
+
)
|
|
90
|
+
self._engine = "xlwings"
|
|
91
|
+
else:
|
|
92
|
+
self._engine = "openpyxl"
|
|
93
|
+
|
|
94
|
+
def _apply_skip_empty(self, dim, idx, skip_empty):
|
|
95
|
+
stop = None
|
|
96
|
+
count = 0
|
|
97
|
+
if dim > 0 and skip_empty > -1:
|
|
98
|
+
for i in range(idx.shape[1]):
|
|
99
|
+
if (np.array(idx[:, i] == None)).all():
|
|
100
|
+
count += 1
|
|
101
|
+
else:
|
|
102
|
+
count = 0
|
|
103
|
+
if count > skip_empty:
|
|
104
|
+
stop = i - skip_empty
|
|
105
|
+
break
|
|
106
|
+
return stop
|
|
107
|
+
|
|
108
|
+
def _create_index(self, dim, idx):
|
|
109
|
+
if dim > 1:
|
|
110
|
+
return pd.MultiIndex.from_arrays(idx)
|
|
111
|
+
else:
|
|
112
|
+
return idx.flatten()
|
|
113
|
+
|
|
114
|
+
def _remove_missing_index(self, values, rdim, cdim, row_idx, col_idx, method):
|
|
115
|
+
def _keep_list(idx):
|
|
116
|
+
keep = list(range(idx.shape[1]))
|
|
117
|
+
for i in reversed(range(idx.shape[1])):
|
|
118
|
+
if method(
|
|
119
|
+
v is None or v != v for v in idx[:, i]
|
|
120
|
+
): # drop None and float('nan') records
|
|
121
|
+
del keep[i]
|
|
122
|
+
return keep
|
|
123
|
+
|
|
124
|
+
if rdim > 0:
|
|
125
|
+
keep = _keep_list(row_idx)
|
|
126
|
+
row_idx = row_idx[:, keep]
|
|
127
|
+
values = values[keep]
|
|
128
|
+
|
|
129
|
+
if cdim > 0:
|
|
130
|
+
keep = _keep_list(col_idx)
|
|
131
|
+
col_idx = col_idx[:, keep]
|
|
132
|
+
values = values[:, keep]
|
|
133
|
+
|
|
134
|
+
return values, row_idx, col_idx
|
|
135
|
+
|
|
136
|
+
def _apply_auto_merge(self, idx, dim):
|
|
137
|
+
last_label = [None] * dim
|
|
138
|
+
for i in range(idx.shape[1]):
|
|
139
|
+
if any(idx[:, i] != None):
|
|
140
|
+
for j in range(idx.shape[0]):
|
|
141
|
+
if idx[j, i] is None:
|
|
142
|
+
idx[j, i] = last_label[j]
|
|
143
|
+
last_label = idx[:, i]
|
|
144
|
+
return idx
|
|
145
|
+
|
|
146
|
+
def _create_dataframe(self, col_idx, row_idx, values, rdim, cdim):
|
|
147
|
+
# create column and row index used for DataFrame
|
|
148
|
+
col_idx = self._create_index(cdim, col_idx)
|
|
149
|
+
row_idx = self._create_index(rdim, row_idx)
|
|
150
|
+
|
|
151
|
+
if cdim == rdim == 0:
|
|
152
|
+
df = pd.DataFrame(values.flatten())
|
|
153
|
+
elif cdim == 0:
|
|
154
|
+
if values.size == 0:
|
|
155
|
+
return pd.DataFrame([np.nan] * len(row_idx), index=row_idx)
|
|
156
|
+
values = values[:, 0]
|
|
157
|
+
df = pd.DataFrame(values.flatten(), index=row_idx)
|
|
158
|
+
elif rdim == 0:
|
|
159
|
+
if values.size == 0:
|
|
160
|
+
return pd.DataFrame([np.nan] * len(col_idx), index=col_idx)
|
|
161
|
+
values = values[0, :]
|
|
162
|
+
df = pd.DataFrame(values.flatten(), index=col_idx)
|
|
163
|
+
else:
|
|
164
|
+
df = pd.DataFrame(values, index=row_idx, columns=col_idx)
|
|
165
|
+
return df
|
|
166
|
+
|
|
167
|
+
def _resolve_merged_cells(self, sheet, data):
|
|
168
|
+
# TODO: do this only on the used range for better performance
|
|
169
|
+
if self._engine == "xlwings":
|
|
170
|
+
## Windows and macOS compatible approach
|
|
171
|
+
#last_cell = sheet.used_range.last_cell
|
|
172
|
+
#used = sheet.range(sheet.range("A1"), last_cell)
|
|
173
|
+
#ranges = set()
|
|
174
|
+
#for row in range(used.rows.count):
|
|
175
|
+
# for col in range(used.columns.count):
|
|
176
|
+
# cell = used[row, col]
|
|
177
|
+
# merge_area = cell.merge_area
|
|
178
|
+
# if merge_area.address != cell.address:
|
|
179
|
+
# ranges.add(merge_area)
|
|
180
|
+
|
|
181
|
+
# Windows only approach using COM
|
|
182
|
+
ranges = {sheet.range(c.MergeArea.Address) for c in sheet.api.UsedRange.Cells if c.MergeCells}
|
|
183
|
+
mr = []
|
|
184
|
+
for rng in ranges:
|
|
185
|
+
mr.append(
|
|
186
|
+
(
|
|
187
|
+
rng.column,
|
|
188
|
+
rng.row,
|
|
189
|
+
rng.column + rng.columns.count - 1,
|
|
190
|
+
rng.row + rng.rows.count - 1,
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
mr = [x.bounds for x in sheet.merged_cells.ranges]
|
|
195
|
+
for b in mr:
|
|
196
|
+
nwc, nwr, sec, ser = b
|
|
197
|
+
value = data[nwr - 1][nwc - 1]
|
|
198
|
+
data[nwr - 1 : ser, nwc - 1 : sec] = value
|
|
199
|
+
return data
|
|
200
|
+
|
|
201
|
+
def _convert_dates(self, df):
|
|
202
|
+
for col in df.columns:
|
|
203
|
+
if is_datetime(df[col]):
|
|
204
|
+
df[col] = (
|
|
205
|
+
pd.DatetimeIndex(df[col]).to_julian_date()
|
|
206
|
+
- pd.Timestamp("1899-12-30").to_julian_date()
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
has_datetime = any(
|
|
210
|
+
isinstance(x, datetime.datetime) for x in df.values.flatten()
|
|
211
|
+
)
|
|
212
|
+
if has_datetime:
|
|
213
|
+
if hasattr(pd.DataFrame, "map"):
|
|
214
|
+
df = df.map(
|
|
215
|
+
lambda x: (
|
|
216
|
+
pd.Timestamp(x).to_julian_date()
|
|
217
|
+
- pd.Timestamp("1899-12-30").to_julian_date()
|
|
218
|
+
if isinstance(x, datetime.datetime)
|
|
219
|
+
else x
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
df = df.applymap(
|
|
224
|
+
lambda x: (
|
|
225
|
+
pd.Timestamp(x).to_julian_date()
|
|
226
|
+
- pd.Timestamp("1899-12-30").to_julian_date()
|
|
227
|
+
if isinstance(x, datetime.datetime)
|
|
228
|
+
else x
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return df
|
|
233
|
+
|
|
234
|
+
def _index_substitutions(self, row_idx, col_idx, rdim, cdim, index_subs):
|
|
235
|
+
if index_subs and rdim + cdim > 0:
|
|
236
|
+
subs = copy(index_subs)
|
|
237
|
+
for k, v in subs.items():
|
|
238
|
+
if k is None:
|
|
239
|
+
break
|
|
240
|
+
if k != k: # check for float('nan')
|
|
241
|
+
if None not in subs.keys():
|
|
242
|
+
subs[None] = v
|
|
243
|
+
break
|
|
244
|
+
if rdim > 0:
|
|
245
|
+
ri_tmp = copy(row_idx)
|
|
246
|
+
for k, v in subs.items():
|
|
247
|
+
# change value in array if either row_idx==k or (element is .nan (row_idx!=row_idx) and key is .nan (k!=k))
|
|
248
|
+
ri_tmp[
|
|
249
|
+
np.logical_or(
|
|
250
|
+
row_idx == k, np.logical_and(row_idx != row_idx, k != k)
|
|
251
|
+
)
|
|
252
|
+
] = v
|
|
253
|
+
row_idx = ri_tmp
|
|
254
|
+
if cdim > 0:
|
|
255
|
+
ci_tmp = copy(col_idx)
|
|
256
|
+
for k, v in subs.items():
|
|
257
|
+
# change value in array if either col_idx==k or (element is .nan (col_idx!=col_idx) and key is .nan (k!=k))
|
|
258
|
+
ci_tmp[
|
|
259
|
+
np.logical_or(
|
|
260
|
+
col_idx == k, np.logical_and(col_idx != col_idx, k != k)
|
|
261
|
+
)
|
|
262
|
+
] = v
|
|
263
|
+
col_idx = ci_tmp
|
|
264
|
+
return row_idx, col_idx
|
|
265
|
+
# alternative approach, but much slower
|
|
266
|
+
# vectorized_replace_rows = np.vectorize(lambda v: dict().get(v, v), otypes=[row_idx.dtype])
|
|
267
|
+
# vectorized_replace_cols = np.vectorize(lambda v: dict().get(v, v), otypes=[col_idx.dtype])
|
|
268
|
+
#
|
|
269
|
+
# if rdim > 0:
|
|
270
|
+
# row_idx = vectorized_replace_rows(row_idx)
|
|
271
|
+
# if cdim > 0:
|
|
272
|
+
# col_idx = vectorized_replace_cols(col_idx)
|
|
273
|
+
|
|
274
|
+
def _value_substitutions(self, df, value_sub):
|
|
275
|
+
if value_sub:
|
|
276
|
+
# pandas-version-check
|
|
277
|
+
if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
|
|
278
|
+
df.replace(value_sub, inplace=True)
|
|
279
|
+
else: # pandas >= 2.2.0
|
|
280
|
+
with pd.option_context("future.no_silent_downcasting", True):
|
|
281
|
+
df = df.replace(value_sub).infer_objects()
|
|
282
|
+
return df
|
|
283
|
+
|
|
284
|
+
def _write(self, df, sym_name, sym_type, rdim, cdim):
|
|
285
|
+
if df is None or df.empty or df.isnull().all().all():
|
|
286
|
+
df = None
|
|
287
|
+
# pass DataFrame as Series for rdim=0 or cdim=0 to not confuse gams.transfer with dimensions
|
|
288
|
+
elif (cdim == 0 and rdim != 0) or (rdim == 0 and cdim != 0):
|
|
289
|
+
df = df[0]
|
|
290
|
+
|
|
291
|
+
if sym_type == "par":
|
|
292
|
+
sym = self._cdb.container.addParameter(
|
|
293
|
+
sym_name,
|
|
294
|
+
["*"] * (rdim + cdim),
|
|
295
|
+
records=df,
|
|
296
|
+
uels_on_axes=True,
|
|
297
|
+
)
|
|
298
|
+
if df is not None:
|
|
299
|
+
sym.dropUndef() # drop float('nan')
|
|
300
|
+
# TODO: remove this section as soon as gams.transfer supports dropping NaN values
|
|
301
|
+
sym.records = self._value_substitutions(
|
|
302
|
+
sym.records, {GMS_SV_UNDEF: gt.SpecialValues.UNDEF}
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
else: # set
|
|
306
|
+
if df is not None:
|
|
307
|
+
# TODO: remove this section as soon as gams.transfer supports dropping NaN values
|
|
308
|
+
# Nan values become empty set element text and we can not drope those values after they are in the container.
|
|
309
|
+
# This is the workaround to handle this
|
|
310
|
+
_assert_axes_no_nans(df)
|
|
311
|
+
dim = _get_implied_dimension_from_axes(df)
|
|
312
|
+
if dim != rdim + cdim:
|
|
313
|
+
self._connect_error(
|
|
314
|
+
f"Dimensionality of table ({dim}) is inconsistent with set domain specification ({rdim+cdim})"
|
|
315
|
+
)
|
|
316
|
+
df = _flatten_and_convert(df)
|
|
317
|
+
df.dropna(inplace=True)
|
|
318
|
+
sym = self._cdb.container.addSet(
|
|
319
|
+
sym_name,
|
|
320
|
+
["*"] * (rdim + cdim),
|
|
321
|
+
records=df,
|
|
322
|
+
uels_on_axes=False,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# For symbols with None records, empty df is assigned
|
|
326
|
+
self._transform_sym_none_to_empty(sym)
|
|
327
|
+
|
|
328
|
+
def _ignore_rows(self, data, offset, ignore_rows):
|
|
329
|
+
nr_rows = data.shape[0]
|
|
330
|
+
r = list(
|
|
331
|
+
filter(
|
|
332
|
+
lambda x: x + offset + 1 not in ignore_rows,
|
|
333
|
+
range(nr_rows),
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
data = data[r]
|
|
337
|
+
return data
|
|
338
|
+
|
|
339
|
+
def _ignore_columns(self, data, offset, ignore_columns):
|
|
340
|
+
nr_cols = data.shape[1]
|
|
341
|
+
r = list(
|
|
342
|
+
filter(
|
|
343
|
+
lambda x: x + offset + 1 not in ignore_columns,
|
|
344
|
+
range(nr_cols),
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
data = data[:, r]
|
|
348
|
+
return data
|
|
349
|
+
|
|
350
|
+
def _apply_ignore_rows_columns(
|
|
351
|
+
self, data, ignore_rows, ignore_columns, nw_row, nw_col, sym_name
|
|
352
|
+
):
|
|
353
|
+
# apply ignoreRows
|
|
354
|
+
if ignore_rows is not None:
|
|
355
|
+
data = self._ignore_rows(data, nw_row, ignore_rows)
|
|
356
|
+
if self._trace > 2:
|
|
357
|
+
self._cdb.print_log(
|
|
358
|
+
f"Raw data after ignoreRows ({sym_name}):\n{data}\n"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# apply ignoreColumns
|
|
362
|
+
if ignore_columns is not None:
|
|
363
|
+
data = self._ignore_columns(data, nw_col, ignore_columns)
|
|
364
|
+
if self._trace > 2:
|
|
365
|
+
self._cdb.print_log(
|
|
366
|
+
f"Raw data after ignoreColumns ({sym_name}):\n{data}\n"
|
|
367
|
+
)
|
|
368
|
+
return data
|
|
369
|
+
|
|
370
|
+
def _parse_ignore_rows(self, ignore_rows, nw_row, se_row):
|
|
371
|
+
if ignore_rows is None:
|
|
372
|
+
return []
|
|
373
|
+
if isinstance(ignore_rows, int):
|
|
374
|
+
l = [ignore_rows]
|
|
375
|
+
elif isinstance(ignore_rows, str):
|
|
376
|
+
l = self._parse_rows_range(ignore_rows)
|
|
377
|
+
else: # list
|
|
378
|
+
# [9, "4:7", 11] -> [9, 4, 5, 6, 7, 11]
|
|
379
|
+
l = [
|
|
380
|
+
i
|
|
381
|
+
for r in ignore_rows
|
|
382
|
+
for i in (self._parse_rows_range(r) if isinstance(r, str) else [r])
|
|
383
|
+
]
|
|
384
|
+
l = set(l)
|
|
385
|
+
if se_row is None:
|
|
386
|
+
return list(l)
|
|
387
|
+
l = list(filter(lambda x: x >= nw_row and x <= se_row, l))
|
|
388
|
+
return l
|
|
389
|
+
|
|
390
|
+
def _parse_ignore_columns(self, ignore_columns, nw_col, se_col):
|
|
391
|
+
if ignore_columns is None:
|
|
392
|
+
return []
|
|
393
|
+
if not isinstance(
|
|
394
|
+
ignore_columns, list
|
|
395
|
+
): # turn int and str values into a list first
|
|
396
|
+
ignore_columns = [ignore_columns]
|
|
397
|
+
l = []
|
|
398
|
+
for c in ignore_columns:
|
|
399
|
+
if isinstance(c, int):
|
|
400
|
+
l.append(c)
|
|
401
|
+
else: # string
|
|
402
|
+
if ":" in c:
|
|
403
|
+
l.extend(self._parse_columns_range(c))
|
|
404
|
+
else:
|
|
405
|
+
l.append(column_index_from_string(c))
|
|
406
|
+
l = set(l)
|
|
407
|
+
if se_col is None:
|
|
408
|
+
return list(l)
|
|
409
|
+
l = list(filter(lambda x: x >= nw_col and x <= se_col, l))
|
|
410
|
+
return l
|
|
411
|
+
|
|
412
|
+
def _read_symbol(self, sym):
|
|
413
|
+
sym_raw = sym.copy()
|
|
414
|
+
self._update_sym_inst(sym, self._inst)
|
|
415
|
+
|
|
416
|
+
sym["range"] = self._dict_get(sym, "range", sym["name"] + "!A1")
|
|
417
|
+
sym["range"] = self.normalize_range(sym["range"])
|
|
418
|
+
|
|
419
|
+
rdim = sym["rowDimension"]
|
|
420
|
+
cdim = sym["columnDimension"]
|
|
421
|
+
sym_range = sym["range"]
|
|
422
|
+
sym_name = sym["name"]
|
|
423
|
+
sym_type = sym["type"]
|
|
424
|
+
merged_cells = sym["mergedCells"]
|
|
425
|
+
value_subs = sym["valueSubstitutions"]
|
|
426
|
+
index_subs = sym["indexSubstitutions"]
|
|
427
|
+
skip_empty = sym["skipEmpty"]
|
|
428
|
+
auto_merge = sym["autoMerge"]
|
|
429
|
+
|
|
430
|
+
sheet, nw_col, nw_row, se_col, se_row, _ = self.parse_range(sym_range, self._wb)
|
|
431
|
+
nw_only = se_col is None and se_row is None
|
|
432
|
+
ignore_rows = self._parse_ignore_rows(sym["ignoreRows"], nw_row, se_row)
|
|
433
|
+
ignore_columns = self._parse_ignore_columns(
|
|
434
|
+
sym["ignoreColumns"], nw_col, se_col
|
|
435
|
+
)
|
|
436
|
+
required_rows = cdim + 1 + len(ignore_rows)
|
|
437
|
+
required_cols = rdim + 1 + len(ignore_columns)
|
|
438
|
+
if not nw_only:
|
|
439
|
+
nr_cols = se_col - nw_col
|
|
440
|
+
nr_rows = se_row - nw_row
|
|
441
|
+
|
|
442
|
+
# handle ignoreText=infer
|
|
443
|
+
if sym["type"] == "set" and sym["ignoreText"] == "infer":
|
|
444
|
+
sym["ignoreText"] = False
|
|
445
|
+
if rdim == 0:
|
|
446
|
+
if (
|
|
447
|
+
nw_only or nr_rows < required_rows
|
|
448
|
+
): # nw only or range without set element text
|
|
449
|
+
sym["ignoreText"] = True
|
|
450
|
+
if cdim == 0:
|
|
451
|
+
if (
|
|
452
|
+
nw_only or nr_cols < required_cols
|
|
453
|
+
): # nw only or range without set element text
|
|
454
|
+
sym["ignoreText"] = True
|
|
455
|
+
ignore_text = sym["ignoreText"]
|
|
456
|
+
|
|
457
|
+
if self._trace > 0:
|
|
458
|
+
self._log_instructions(
|
|
459
|
+
sym, sym_raw, description=f"Read symbol >{sym['name']}<:"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
self._symbols_exist_cdb(sym_name)
|
|
463
|
+
|
|
464
|
+
# check that sets do not have dim=0
|
|
465
|
+
if sym_type == "set" and rdim == 0 and cdim == 0:
|
|
466
|
+
self._connect_error(
|
|
467
|
+
f"Cannot read set >{sym_name}< with both >rowDimension: 0< and >columnDimension: 0<."
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# check sufficient ranges
|
|
471
|
+
if sym_type == "set" and ignore_text:
|
|
472
|
+
if cdim == 0:
|
|
473
|
+
required_cols -= 1
|
|
474
|
+
elif rdim == 0:
|
|
475
|
+
required_rows -= 1
|
|
476
|
+
if not nw_only:
|
|
477
|
+
if sym_type == "set" and not ignore_text:
|
|
478
|
+
if cdim == 0 and nr_cols == required_cols - 1:
|
|
479
|
+
self._connect_error(
|
|
480
|
+
"Range and rowDimension specification does not contain set element text but ignoreText has been set to False. Adjust range or rowDimension or set >ignoreText: True<."
|
|
481
|
+
)
|
|
482
|
+
if rdim == 0 and nr_rows == required_rows - 1:
|
|
483
|
+
self._connect_error(
|
|
484
|
+
"Range and columnDimension specification does not contain set element text but ignoreText has been set to False. Adjust range or columnDimension or set >ignoreText: True<."
|
|
485
|
+
)
|
|
486
|
+
if nr_rows < required_rows:
|
|
487
|
+
self._connect_error(
|
|
488
|
+
f"Invalid range >{sym_range}<. With columnDimension: >{cdim}< and {len(ignore_rows)} rows to be ignored, the range must include at least {required_rows} rows."
|
|
489
|
+
)
|
|
490
|
+
if nr_cols < required_cols:
|
|
491
|
+
self._connect_error(
|
|
492
|
+
f"Invalid range >{sym_range}<. With rowDimension: >{rdim}< and {len(ignore_columns)} columns to be ignored, the range must include at least {required_cols} columns."
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
data = self._wb.get_sheet_data(sheet)
|
|
496
|
+
|
|
497
|
+
if len(data) == 0: # no data at all
|
|
498
|
+
self._write(None, sym_name, sym_type, rdim, cdim)
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
if self._trace > 2:
|
|
502
|
+
self._cdb.print_log(f"Raw data ({sym_name}) :\n{data}\n")
|
|
503
|
+
|
|
504
|
+
if merged_cells:
|
|
505
|
+
data = self._resolve_merged_cells(sheet, data)
|
|
506
|
+
if self._trace > 2:
|
|
507
|
+
self._cdb.print_log(
|
|
508
|
+
f"Raw data after resolving merged cells ({sym_name}):\n{data}\n"
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
# shrink data to actual range
|
|
512
|
+
data = data[nw_row:se_row, nw_col:se_col]
|
|
513
|
+
if self._trace > 2:
|
|
514
|
+
self._cdb.print_log(
|
|
515
|
+
f"Raw data after shrinking to range ({sym_name}):\n{data}\n"
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# apply ignoreRows and ignoreColumns
|
|
519
|
+
data = self._apply_ignore_rows_columns(
|
|
520
|
+
data, ignore_rows, ignore_columns, nw_row, nw_col, sym_name
|
|
521
|
+
)
|
|
522
|
+
if data.size == 0:
|
|
523
|
+
self._write(None, sym_name, sym_type, rdim, cdim)
|
|
524
|
+
return
|
|
525
|
+
# if data.shape[0] < required_rows - len(ignore_rows):
|
|
526
|
+
# self._connect_error(
|
|
527
|
+
# f"Insufficient number of data rows ({sym_name}). Require at least {required_rows}, but got {data.shape[0]}."
|
|
528
|
+
# )
|
|
529
|
+
# if data.shape[1] < required_cols - len(ignore_columns):
|
|
530
|
+
# self._connect_error(
|
|
531
|
+
# f"Insufficient number of data columns ({sym_name}). Require at least {required_cols}, but got {data.shape[1]}."
|
|
532
|
+
# )
|
|
533
|
+
|
|
534
|
+
col_idx = data[:cdim, rdim:]
|
|
535
|
+
row_idx = data[cdim:, :rdim].transpose()
|
|
536
|
+
|
|
537
|
+
if self._trace > 2:
|
|
538
|
+
self._cdb.print_log(f"Initial column index ({sym_name}):\n{col_idx}\n")
|
|
539
|
+
self._cdb.print_log(f"Initial row index ({sym_name}):\n{row_idx}\n")
|
|
540
|
+
|
|
541
|
+
# apply skipEmpty only for nw_only, but not for explicit ranges
|
|
542
|
+
if nw_only:
|
|
543
|
+
stop_col = self._apply_skip_empty(cdim, col_idx, skip_empty)
|
|
544
|
+
col_idx = col_idx[:, :stop_col]
|
|
545
|
+
stop_row = self._apply_skip_empty(rdim, row_idx, skip_empty)
|
|
546
|
+
row_idx = row_idx[:, :stop_row]
|
|
547
|
+
if self._trace > 2:
|
|
548
|
+
self._cdb.print_log(
|
|
549
|
+
f"Column index after skipEmpty ({sym_name}):\n{col_idx}\n"
|
|
550
|
+
)
|
|
551
|
+
self._cdb.print_log(
|
|
552
|
+
f"Row index after skipEmpty ({sym_name}):\n{row_idx}\n"
|
|
553
|
+
)
|
|
554
|
+
else:
|
|
555
|
+
stop_col = None
|
|
556
|
+
stop_row = None
|
|
557
|
+
|
|
558
|
+
if stop_col is not None:
|
|
559
|
+
stop_col += rdim
|
|
560
|
+
if stop_row is not None:
|
|
561
|
+
stop_row += cdim
|
|
562
|
+
|
|
563
|
+
if cdim == 0 and rdim == 0: # handle scalars
|
|
564
|
+
stop_row = 1
|
|
565
|
+
stop_col = 1
|
|
566
|
+
if rdim == 0:
|
|
567
|
+
row_idx = np.empty((0, 0)) # dummy array for header
|
|
568
|
+
if cdim == 0:
|
|
569
|
+
col_idx = np.empty((0, 0)) # dummy array for header
|
|
570
|
+
|
|
571
|
+
values = data[cdim:stop_row, rdim:stop_col]
|
|
572
|
+
|
|
573
|
+
if self._trace > 2:
|
|
574
|
+
self._cdb.print_log(f"Values {(sym_name)}: {values}\n")
|
|
575
|
+
|
|
576
|
+
if self._engine == "xlwings":
|
|
577
|
+
col_idx = self._to_int_if_whole(col_idx)
|
|
578
|
+
row_idx = self._to_int_if_whole(row_idx)
|
|
579
|
+
values = self._to_int_if_whole(values)
|
|
580
|
+
|
|
581
|
+
if auto_merge:
|
|
582
|
+
if cdim > 1:
|
|
583
|
+
col_idx = self._apply_auto_merge(col_idx, cdim)
|
|
584
|
+
if rdim > 1:
|
|
585
|
+
row_idx = self._apply_auto_merge(row_idx, rdim)
|
|
586
|
+
if self._trace > 2:
|
|
587
|
+
self._cdb.print_log(
|
|
588
|
+
f"Row index after autoMerge ({sym_name}):\n{row_idx}\n"
|
|
589
|
+
)
|
|
590
|
+
self._cdb.print_log(
|
|
591
|
+
f"Column index after autoMerge ({sym_name}):\n{col_idx}\n"
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# replace all set text with empty string for ignoreText=True
|
|
595
|
+
if sym_type == "set" and ignore_text:
|
|
596
|
+
if values.size == 0:
|
|
597
|
+
if cdim == 0:
|
|
598
|
+
values = np.empty((values.shape[0], 1), dtype=str)
|
|
599
|
+
elif rdim == 0:
|
|
600
|
+
values = np.empty((1, values.shape[1]), dtype=str)
|
|
601
|
+
else:
|
|
602
|
+
values = np.empty_like(values, dtype=str)
|
|
603
|
+
|
|
604
|
+
if index_subs:
|
|
605
|
+
# remove all-None entries in column and row header and corresponding values
|
|
606
|
+
values, row_idx, col_idx = self._remove_missing_index(
|
|
607
|
+
values, rdim, cdim, row_idx, col_idx, all
|
|
608
|
+
)
|
|
609
|
+
row_idx, col_idx = self._index_substitutions(
|
|
610
|
+
row_idx, col_idx, rdim, cdim, index_subs
|
|
611
|
+
)
|
|
612
|
+
if self._trace > 2:
|
|
613
|
+
self._cdb.print_log(
|
|
614
|
+
f"Row index after indexSubstitutions ({sym_name}):\n{row_idx}\n"
|
|
615
|
+
)
|
|
616
|
+
self._cdb.print_log(
|
|
617
|
+
f"Column index after indexSubstitutions ({sym_name}):\n{col_idx}\n"
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
# remove any-None entries in column and row header and corresponding values
|
|
621
|
+
values, row_idx, col_idx = self._remove_missing_index(
|
|
622
|
+
values, rdim, cdim, row_idx, col_idx, any
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
if self._trace > 2:
|
|
626
|
+
self._cdb.print_log(
|
|
627
|
+
f"Column index before DataFrame creation ({sym_name}):\n{col_idx}\n"
|
|
628
|
+
)
|
|
629
|
+
self._cdb.print_log(
|
|
630
|
+
f"Row index before DataFrame creation ({sym_name}):\n{row_idx}\n"
|
|
631
|
+
)
|
|
632
|
+
self._cdb.print_log(
|
|
633
|
+
f"Values before DataFrame creation ({sym_name}):\n{values}\n"
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
df = self._create_dataframe(col_idx, row_idx, values, rdim, cdim)
|
|
637
|
+
|
|
638
|
+
if self._trace > 2:
|
|
639
|
+
self._cdb.print_log(f"Initial DataFrame ({sym_name}):\n{df}\n")
|
|
640
|
+
|
|
641
|
+
df = self._convert_dates(df)
|
|
642
|
+
|
|
643
|
+
df = self._value_substitutions(df, value_subs)
|
|
644
|
+
if self._trace > 2:
|
|
645
|
+
self._cdb.print_log(
|
|
646
|
+
f"DataFrame after valueSubstitutions ({sym_name}):\n{df}\n"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# TODO: This is a workaround to get UNDEF to survive sym.dropNA/sym.dropUndef - remove as soon as gams.transfer supports dropping NaN values
|
|
650
|
+
if sym_type == "par":
|
|
651
|
+
import re
|
|
652
|
+
|
|
653
|
+
pattern = re.compile(r"undef", re.IGNORECASE)
|
|
654
|
+
# pandas-version-check
|
|
655
|
+
if self._pandas_version_before(pd.__version__, "2.2"): # pandas < 2.2.0
|
|
656
|
+
df.replace(regex=pattern, value=GMS_SV_UNDEF, inplace=True)
|
|
657
|
+
else: # pandas >= 2.2.0
|
|
658
|
+
with pd.option_context("future.no_silent_downcasting", True):
|
|
659
|
+
df = df.replace(regex=pattern, value=GMS_SV_UNDEF).infer_objects()
|
|
660
|
+
self._write(df, sym_name, sym_type, rdim, cdim)
|
|
661
|
+
|
|
662
|
+
def _open(self):
|
|
663
|
+
read_only = not (
|
|
664
|
+
any(sym["mergedCells"] for sym in self._symbols) or self._merged_cells
|
|
665
|
+
)
|
|
666
|
+
try:
|
|
667
|
+
self._wb = Workbook(
|
|
668
|
+
self._file, engine=self._engine, read_only=read_only, data_only=True
|
|
669
|
+
) # data_only=True is required to read values instead of formulas
|
|
670
|
+
except PermissionError as e:
|
|
671
|
+
self._connect_error(
|
|
672
|
+
str(e)
|
|
673
|
+
+ "\nThe file may already be open and might need to be closed first."
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
def _read_symbols(self, symbols, validate=False):
|
|
677
|
+
if validate:
|
|
678
|
+
sym_schema = self._cdb.load_schema(self)["symbols"]["schema"]["schema"]
|
|
679
|
+
v = ConnectValidator(sym_schema)
|
|
680
|
+
for i, sym in enumerate(symbols):
|
|
681
|
+
if validate:
|
|
682
|
+
sym = v.validated(sym)
|
|
683
|
+
if sym is None:
|
|
684
|
+
self._connect_error(
|
|
685
|
+
f"Validation of item {i} in index failed: {v.errors}"
|
|
686
|
+
)
|
|
687
|
+
sym = v.normalize_of_rules(sym)
|
|
688
|
+
self._read_symbol(sym)
|
|
689
|
+
|
|
690
|
+
def _create_symbol_instructions(self, rec):
|
|
691
|
+
is_symbol = not None in (rec[0], rec[1], rec[2])
|
|
692
|
+
inst = {}
|
|
693
|
+
if is_symbol:
|
|
694
|
+
inst["type"] = rec[0].lower().strip()
|
|
695
|
+
inst["name"] = rec[1].strip()
|
|
696
|
+
inst["range"] = rec[2].strip()
|
|
697
|
+
if self._trace > 1:
|
|
698
|
+
self._cdb.print_log(
|
|
699
|
+
f"\nParsing symbol >{inst['name']}< with >type: {inst['type']}< and >range: {inst['range']}<."
|
|
700
|
+
)
|
|
701
|
+
if inst["type"] == "dset":
|
|
702
|
+
inst["type"] = "set"
|
|
703
|
+
self._cdb.print_log(
|
|
704
|
+
f"Warning: Processing unsupported >type: dset< as >type: set< for symbol >{inst['name']}<."
|
|
705
|
+
)
|
|
706
|
+
return inst
|
|
707
|
+
|
|
708
|
+
def _read_from_index(self):
|
|
709
|
+
symbols = self.parse_index(self._index, self._wb, self._index_parameter_map)
|
|
710
|
+
|
|
711
|
+
# reopen the file with read_only=False if required
|
|
712
|
+
if not self._merged_cells:
|
|
713
|
+
read_only = not any(
|
|
714
|
+
self._dict_get(sym, "mergedCells", self._merged_cells)
|
|
715
|
+
for sym in symbols
|
|
716
|
+
)
|
|
717
|
+
if not read_only and not self._is_xlsb:
|
|
718
|
+
self._wb.close()
|
|
719
|
+
try:
|
|
720
|
+
self._wb = Workbook(
|
|
721
|
+
self._file,
|
|
722
|
+
engine=self._engine,
|
|
723
|
+
read_only=read_only,
|
|
724
|
+
data_only=True,
|
|
725
|
+
) # data_only=True is required to read values instead of formulas
|
|
726
|
+
except PermissionError as e:
|
|
727
|
+
self._connect_error(
|
|
728
|
+
str(e)
|
|
729
|
+
+ "\nThe file may already be open and might need to be closed first."
|
|
730
|
+
)
|
|
731
|
+
self._read_symbols(symbols, True)
|
|
732
|
+
|
|
733
|
+
def execute(self):
|
|
734
|
+
if self._trace > 0:
|
|
735
|
+
self._log_instructions(self._inst, self._inst_raw)
|
|
736
|
+
self._describe_container(self._cdb.container, "Connect Container (before):")
|
|
737
|
+
|
|
738
|
+
try:
|
|
739
|
+
self._open()
|
|
740
|
+
|
|
741
|
+
if self._index:
|
|
742
|
+
self._read_from_index()
|
|
743
|
+
else:
|
|
744
|
+
self._read_symbols(self._symbols)
|
|
745
|
+
if self._trace > 2:
|
|
746
|
+
for name, sym in self._cdb.container.data.items():
|
|
747
|
+
self._cdb.print_log(
|
|
748
|
+
f"Connect Container symbol >{name}<:\n {sym.records}\n"
|
|
749
|
+
)
|
|
750
|
+
if self._trace > 0:
|
|
751
|
+
self._describe_container(
|
|
752
|
+
self._cdb.container, "Connect Container (after):"
|
|
753
|
+
)
|
|
754
|
+
finally:
|
|
755
|
+
if self._wb is not None:
|
|
756
|
+
self._wb.close()
|