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,1528 @@
|
|
|
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 warnings import warn
|
|
27
|
+
import pandas as pd
|
|
28
|
+
import numpy as np
|
|
29
|
+
from pandas.api.types import (
|
|
30
|
+
CategoricalDtype,
|
|
31
|
+
is_float_dtype,
|
|
32
|
+
infer_dtype,
|
|
33
|
+
)
|
|
34
|
+
import gams.transfer._abcs as abcs
|
|
35
|
+
from gams.transfer._internals import (
|
|
36
|
+
GAMS_MAX_INDEX_DIM,
|
|
37
|
+
GAMS_DESCRIPTION_MAX_LENGTH,
|
|
38
|
+
DomainStatus,
|
|
39
|
+
DomainViolation,
|
|
40
|
+
generate_unique_labels,
|
|
41
|
+
)
|
|
42
|
+
from typing import Optional, Union, List
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SPVEMixin:
|
|
46
|
+
def __delitem__(self):
|
|
47
|
+
# TODO: add in some functionality that might relax the symbols down to a different domain
|
|
48
|
+
# This function would mimic the <Container>.removeSymbols() method -- is more pythonic
|
|
49
|
+
del self.container.data[self.name]
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def domain_forwarding(self):
|
|
53
|
+
"""A boolean indicating whether domain forwarding is enabled"""
|
|
54
|
+
return self._domain_forwarding
|
|
55
|
+
|
|
56
|
+
@domain_forwarding.setter
|
|
57
|
+
def domain_forwarding(self, domain_forwarding):
|
|
58
|
+
if not isinstance(domain_forwarding, (bool, list)):
|
|
59
|
+
raise TypeError("Argument 'domain_forwarding' must be type bool or list")
|
|
60
|
+
|
|
61
|
+
if isinstance(domain_forwarding, list):
|
|
62
|
+
if len(domain_forwarding) != self.dimension:
|
|
63
|
+
raise Exception(
|
|
64
|
+
"Argument 'domain_forwarding' must be of length <symbol>.dimension"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if any(not isinstance(i, bool) for i in domain_forwarding):
|
|
68
|
+
raise TypeError(
|
|
69
|
+
"Argument 'domain_forwarding' must only contain type bool"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self._domain_forwarding = domain_forwarding
|
|
73
|
+
self.modified = True
|
|
74
|
+
self.container.modified = True
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def domain_names(self):
|
|
78
|
+
"""String version of domain names"""
|
|
79
|
+
return [
|
|
80
|
+
i.name if isinstance(i, abcs.AnyContainerDomainSymbol) else i
|
|
81
|
+
for i in self.domain
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def domain_labels(self):
|
|
86
|
+
"""The column headings for the records DataFrame"""
|
|
87
|
+
if self._records is not None:
|
|
88
|
+
return self._records.columns.tolist()[: self.dimension]
|
|
89
|
+
|
|
90
|
+
@domain_labels.setter
|
|
91
|
+
def domain_labels(self, labels):
|
|
92
|
+
if not isinstance(labels, list):
|
|
93
|
+
labels = [labels]
|
|
94
|
+
|
|
95
|
+
# checks
|
|
96
|
+
if len(labels) != self.dimension:
|
|
97
|
+
raise Exception(
|
|
98
|
+
"Attempting to set symbol 'domain_labels', however, len(domain_labels) != symbol dimension."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# make unique labels if necessary
|
|
102
|
+
labels = generate_unique_labels(labels)
|
|
103
|
+
|
|
104
|
+
# set the domain_labels
|
|
105
|
+
if getattr(self, "domain_labels", None) is not None:
|
|
106
|
+
if self._records.columns.tolist() != labels + self._attributes:
|
|
107
|
+
self._records.columns = labels + self._attributes
|
|
108
|
+
self._container._requires_state_check = True
|
|
109
|
+
self._requires_state_check = True
|
|
110
|
+
self.modified = True
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def domain(self):
|
|
114
|
+
"""
|
|
115
|
+
List of domains given either as string (* for universe set) or as reference to the Set/Alias object
|
|
116
|
+
"""
|
|
117
|
+
return self._domain
|
|
118
|
+
|
|
119
|
+
@domain.setter
|
|
120
|
+
def domain(self, domain):
|
|
121
|
+
if not isinstance(domain, list):
|
|
122
|
+
domain = [domain]
|
|
123
|
+
|
|
124
|
+
if not all(isinstance(i, (abcs.AnyContainerDomainSymbol, str)) for i in domain):
|
|
125
|
+
raise TypeError(
|
|
126
|
+
"All 'domain' elements must be type Set, Alias, UniverseAlias, or str"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if not all(
|
|
130
|
+
i.dimension == 1
|
|
131
|
+
for i in domain
|
|
132
|
+
if isinstance(i, abcs.AnyContainerDomainSymbol)
|
|
133
|
+
):
|
|
134
|
+
raise ValueError("All linked 'domain' elements must have dimension == 1")
|
|
135
|
+
|
|
136
|
+
if len(domain) > GAMS_MAX_INDEX_DIM:
|
|
137
|
+
raise ValueError(f"Symbol 'domain' length cannot be > {GAMS_MAX_INDEX_DIM}")
|
|
138
|
+
|
|
139
|
+
# check to see if domain is being changed
|
|
140
|
+
if getattr(self, "domain", None) is not None:
|
|
141
|
+
if self.domain != domain:
|
|
142
|
+
self._requires_state_check = True
|
|
143
|
+
self.modified = True
|
|
144
|
+
|
|
145
|
+
self.container._requires_state_check = True
|
|
146
|
+
self.container.modified = True
|
|
147
|
+
|
|
148
|
+
self._domain = domain
|
|
149
|
+
else:
|
|
150
|
+
self._domain = domain
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def description(self):
|
|
154
|
+
"""Description of the symbol"""
|
|
155
|
+
return self._description
|
|
156
|
+
|
|
157
|
+
@description.setter
|
|
158
|
+
def description(self, description):
|
|
159
|
+
if not isinstance(description, str):
|
|
160
|
+
raise TypeError("Symbol 'description' must be type str")
|
|
161
|
+
|
|
162
|
+
if len(description) > GAMS_DESCRIPTION_MAX_LENGTH:
|
|
163
|
+
raise TypeError(
|
|
164
|
+
f"Symbol 'description' must have "
|
|
165
|
+
f"length {GAMS_DESCRIPTION_MAX_LENGTH} or smaller"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# check to see if _description is being changed
|
|
169
|
+
if getattr(self, "description", None) is not None:
|
|
170
|
+
if self.description != description:
|
|
171
|
+
self._requires_state_check = True
|
|
172
|
+
self.modified = True
|
|
173
|
+
|
|
174
|
+
self.container._requires_state_check = True
|
|
175
|
+
self.container.modified = True
|
|
176
|
+
|
|
177
|
+
# set the description
|
|
178
|
+
self._description = description
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def dimension(self):
|
|
182
|
+
"""The dimension of symbol"""
|
|
183
|
+
return len(self.domain)
|
|
184
|
+
|
|
185
|
+
@dimension.setter
|
|
186
|
+
def dimension(self, dimension):
|
|
187
|
+
if not isinstance(dimension, int) or dimension < 0:
|
|
188
|
+
raise TypeError(
|
|
189
|
+
"Symbol 'dimension' must be type int (greater than or equal to 0)"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if dimension > GAMS_MAX_INDEX_DIM:
|
|
193
|
+
raise ValueError(f"Symbol 'dimension' cannot be > {GAMS_MAX_INDEX_DIM}")
|
|
194
|
+
|
|
195
|
+
if len(self.domain) > dimension:
|
|
196
|
+
self.domain = [i for n, i in enumerate(self.domain) if n < dimension]
|
|
197
|
+
self.modified = True
|
|
198
|
+
elif dimension > len(self.domain):
|
|
199
|
+
new = self.domain
|
|
200
|
+
new.extend(["*"] * (dimension - len(self.domain)))
|
|
201
|
+
self.domain = new
|
|
202
|
+
self.modified = True
|
|
203
|
+
self.container.modified = True
|
|
204
|
+
else:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def records(self):
|
|
209
|
+
"""Records of the symbol"""
|
|
210
|
+
return self._records
|
|
211
|
+
|
|
212
|
+
@records.setter
|
|
213
|
+
def records(self, records):
|
|
214
|
+
# set records
|
|
215
|
+
self._records = records
|
|
216
|
+
|
|
217
|
+
self._requires_state_check = True
|
|
218
|
+
self.modified = True
|
|
219
|
+
|
|
220
|
+
self.container._requires_state_check = True
|
|
221
|
+
self.container.modified = True
|
|
222
|
+
|
|
223
|
+
if self._records is not None:
|
|
224
|
+
if self.domain_forwarding:
|
|
225
|
+
self._domainForwarding()
|
|
226
|
+
|
|
227
|
+
# reset state check flags for all symbols in the container
|
|
228
|
+
for symnam, symobj in self.container.data.items():
|
|
229
|
+
symobj._requires_state_check = True
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def number_records(self):
|
|
233
|
+
"""Number of records"""
|
|
234
|
+
if self.isValid():
|
|
235
|
+
if self.records is not None:
|
|
236
|
+
return len(self.records)
|
|
237
|
+
else:
|
|
238
|
+
return 0
|
|
239
|
+
else:
|
|
240
|
+
return float("nan")
|
|
241
|
+
|
|
242
|
+
def _getUELCodes(self, dimension, ignore_unused=False):
|
|
243
|
+
if not isinstance(dimension, int):
|
|
244
|
+
raise TypeError("Argument 'dimension' must be type int")
|
|
245
|
+
|
|
246
|
+
if dimension >= self.dimension:
|
|
247
|
+
raise ValueError(
|
|
248
|
+
f"Argument 'dimension' (`{dimension}`) must be < symbol "
|
|
249
|
+
f"dimension (`{self.dimension}`). (NOTE: 'dimension' is indexed from zero)"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if not isinstance(ignore_unused, bool):
|
|
253
|
+
raise TypeError("Argument 'ignore_unused' must be type bool")
|
|
254
|
+
|
|
255
|
+
cats = self.getUELs(dimension, ignore_unused=ignore_unused)
|
|
256
|
+
codes = list(range(len(cats)))
|
|
257
|
+
return dict(zip(cats, codes))
|
|
258
|
+
|
|
259
|
+
def getUELs(
|
|
260
|
+
self,
|
|
261
|
+
dimensions: Optional[Union[int, list]] = None,
|
|
262
|
+
codes: Optional[Union[int, list]] = None,
|
|
263
|
+
ignore_unused: bool = False,
|
|
264
|
+
unique_only: bool = False,
|
|
265
|
+
) -> List[str]:
|
|
266
|
+
"""
|
|
267
|
+
Gets UELs from symbol dimensions. If dimensions is None then get UELs from all dimensions (maintains order).
|
|
268
|
+
The argument codes accepts a list of str UELs and will return the corresponding int; must specify a single dimension if passing codes.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
dimensions : int | list, optional
|
|
273
|
+
Symbols' dimensions, by default None
|
|
274
|
+
codes : int | list, optional
|
|
275
|
+
Symbols' codes, by default None
|
|
276
|
+
ignore_unused : bool, optional
|
|
277
|
+
Flag to ignore unused UELs, by default False
|
|
278
|
+
unique_only : bool, optional
|
|
279
|
+
Flag to check only unique UELs, by default False
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
List[str]
|
|
284
|
+
Only UELs in the data if ignore_unused=True, otherwise return all UELs.
|
|
285
|
+
"""
|
|
286
|
+
if self.records is not None:
|
|
287
|
+
if not self.isValid():
|
|
288
|
+
raise Exception(
|
|
289
|
+
"Symbol is currently invalid -- must be valid in order to access UELs (categories)."
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# fastpath for scalars
|
|
293
|
+
if self.dimension == 0:
|
|
294
|
+
return []
|
|
295
|
+
|
|
296
|
+
# ARG: ignore_unused
|
|
297
|
+
if not isinstance(ignore_unused, bool):
|
|
298
|
+
raise TypeError(f"Argument 'ignore_unused' must be type bool")
|
|
299
|
+
|
|
300
|
+
# ARG: unique_only
|
|
301
|
+
if not isinstance(unique_only, bool):
|
|
302
|
+
raise TypeError(f"Argument 'unique_only' must be type bool")
|
|
303
|
+
|
|
304
|
+
# ARG: dimension
|
|
305
|
+
if not isinstance(dimensions, (list, int, type(None))):
|
|
306
|
+
raise TypeError("Argument 'dimensions' must be type int or NoneType")
|
|
307
|
+
|
|
308
|
+
if dimensions is None:
|
|
309
|
+
dimensions = list(range(self.dimension))
|
|
310
|
+
|
|
311
|
+
if isinstance(dimensions, int):
|
|
312
|
+
dimensions = [dimensions]
|
|
313
|
+
|
|
314
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
315
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
316
|
+
|
|
317
|
+
for n in dimensions:
|
|
318
|
+
if n >= self.dimension:
|
|
319
|
+
raise ValueError(
|
|
320
|
+
f"Cannot access symbol 'dimension' `{n}`, because `{n}` is >= symbol "
|
|
321
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# ARG: codes
|
|
325
|
+
if not isinstance(codes, (int, list, type(None))):
|
|
326
|
+
raise TypeError("Argument 'codes' must be type int, list, or NoneType")
|
|
327
|
+
|
|
328
|
+
if isinstance(codes, int):
|
|
329
|
+
codes = [codes]
|
|
330
|
+
|
|
331
|
+
if isinstance(codes, list):
|
|
332
|
+
if any(not isinstance(i, int) for i in codes):
|
|
333
|
+
raise TypeError("Argument 'codes' must only contain type int")
|
|
334
|
+
|
|
335
|
+
# ARG: codes & dimensions
|
|
336
|
+
if codes is not None and dimensions is None:
|
|
337
|
+
raise Exception(
|
|
338
|
+
"User must specify 'dimensions' if retrieving UELs with the 'codes' argument."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if codes is not None and len(dimensions) > 1:
|
|
342
|
+
raise Exception(
|
|
343
|
+
"User must specify only one dimension if retrieving UELs with the 'codes' argument"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if codes is None:
|
|
347
|
+
if len(dimensions) == 1:
|
|
348
|
+
n = dimensions[0]
|
|
349
|
+
if not ignore_unused:
|
|
350
|
+
cats = self.records.iloc[:, n].cat.categories.tolist()
|
|
351
|
+
else:
|
|
352
|
+
used_codes = np.sort(self.records.iloc[:, n].cat.codes.unique())
|
|
353
|
+
all_cats = self.records.iloc[:, n].cat.categories.tolist()
|
|
354
|
+
cats = [all_cats[i] for i in used_codes]
|
|
355
|
+
elif len(dimensions) > 1:
|
|
356
|
+
cats = {}
|
|
357
|
+
for n in dimensions:
|
|
358
|
+
if not ignore_unused:
|
|
359
|
+
cats.update(
|
|
360
|
+
dict.fromkeys(self.records.iloc[:, n].cat.categories)
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
used_codes = np.sort(
|
|
364
|
+
self.records.iloc[:, n].cat.codes.unique()
|
|
365
|
+
)
|
|
366
|
+
all_cats = self.records.iloc[:, n].cat.categories.tolist()
|
|
367
|
+
cats.update(
|
|
368
|
+
dict.fromkeys([all_cats[i] for i in used_codes])
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
cats = list(cats.keys())
|
|
372
|
+
|
|
373
|
+
if unique_only:
|
|
374
|
+
return list(CasePreservingDict().fromkeys(cats.keys()).keys())
|
|
375
|
+
else:
|
|
376
|
+
return cats
|
|
377
|
+
|
|
378
|
+
else:
|
|
379
|
+
codemap = {
|
|
380
|
+
codes: cats
|
|
381
|
+
for cats, codes in self._getUELCodes(dimensions[0]).items()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if len(codes) == 1:
|
|
385
|
+
codes = codes[0]
|
|
386
|
+
return codemap[codes]
|
|
387
|
+
|
|
388
|
+
return [codemap.get(code, None) for code in codes]
|
|
389
|
+
|
|
390
|
+
def _formatUELs(self, method, dimensions=None):
|
|
391
|
+
if self.records is not None:
|
|
392
|
+
if not self.isValid():
|
|
393
|
+
raise Exception(
|
|
394
|
+
"Symbol is currently invalid -- must be valid in order to access UELs (categories)."
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# ARG: dimension
|
|
398
|
+
if not isinstance(dimensions, (list, int, type(None))):
|
|
399
|
+
raise TypeError("Argument 'dimensions' must be type int or NoneType")
|
|
400
|
+
|
|
401
|
+
if dimensions is None:
|
|
402
|
+
dimensions = list(range(self.dimension))
|
|
403
|
+
|
|
404
|
+
if isinstance(dimensions, int):
|
|
405
|
+
dimensions = [dimensions]
|
|
406
|
+
|
|
407
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
408
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
409
|
+
|
|
410
|
+
for n in dimensions:
|
|
411
|
+
if n >= self.dimension:
|
|
412
|
+
raise ValueError(
|
|
413
|
+
f"Cannot access symbol 'dimension' `{n}`, because `{n}` is >= symbol "
|
|
414
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
for n in dimensions:
|
|
418
|
+
old_cats = self.records.iloc[:, n].cat.categories.tolist()
|
|
419
|
+
new_cats = list(map(method, self.records.iloc[:, n].cat.categories))
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
# fastpath
|
|
423
|
+
self.records.isetitem(
|
|
424
|
+
n, self.records.iloc[:, n].cat.rename_categories(new_cats)
|
|
425
|
+
)
|
|
426
|
+
except:
|
|
427
|
+
# remap data (slow path)
|
|
428
|
+
self.records.isetitem(
|
|
429
|
+
n,
|
|
430
|
+
self.records.iloc[:, n]
|
|
431
|
+
.astype("object")
|
|
432
|
+
.map(dict(zip(old_cats, new_cats))),
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# de-dup new cats
|
|
436
|
+
new_cats = list(dict.fromkeys(new_cats).keys())
|
|
437
|
+
|
|
438
|
+
# rebuild categorical
|
|
439
|
+
self.records.isetitem(
|
|
440
|
+
n,
|
|
441
|
+
self.records.iloc[:, n].astype(
|
|
442
|
+
pd.CategoricalDtype(categories=new_cats, ordered=True)
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
def lowerUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
447
|
+
"""
|
|
448
|
+
Will lowercase all UELs in the parent symbol or a subset of specified dimensions in the parent symbol, can be chain with other *UELs string operations
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
dimensions : int | List[int], optional
|
|
453
|
+
Symbols' dimensions, by default None
|
|
454
|
+
"""
|
|
455
|
+
self._formatUELs(str.lower, dimensions=dimensions)
|
|
456
|
+
return self
|
|
457
|
+
|
|
458
|
+
def upperUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
459
|
+
"""
|
|
460
|
+
Will uppercase all UELs in the parent symbol or a subset of specified dimensions in the parent symbol, can be chain with other *UELs string operations
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
dimensions : int | List[int], optional
|
|
465
|
+
Symbols' dimensions, by default None
|
|
466
|
+
"""
|
|
467
|
+
self._formatUELs(str.upper, dimensions=dimensions)
|
|
468
|
+
return self
|
|
469
|
+
|
|
470
|
+
def lstripUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
471
|
+
"""
|
|
472
|
+
Will left strip whitespace from all UELs in the parent set or a subset of specified dimensions in the parent set, can be chain with other *UELs string operations
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
dimensions : int | List[int], optional
|
|
477
|
+
Symbols' dimensions, by default None
|
|
478
|
+
"""
|
|
479
|
+
self._formatUELs(str.lstrip, dimensions=dimensions)
|
|
480
|
+
return self
|
|
481
|
+
|
|
482
|
+
def rstripUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
483
|
+
"""
|
|
484
|
+
Will right strip whitespace from all UELs in the parent set or a subset of specified dimensions in the parent set, can be chain with other *UELs string operations
|
|
485
|
+
|
|
486
|
+
Parameters
|
|
487
|
+
----------
|
|
488
|
+
dimensions : int | List[int], optional
|
|
489
|
+
Symbols' dimensions, by default None
|
|
490
|
+
"""
|
|
491
|
+
self._formatUELs(str.rstrip, dimensions=dimensions)
|
|
492
|
+
return self
|
|
493
|
+
|
|
494
|
+
def stripUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
495
|
+
"""
|
|
496
|
+
Will strip whitespace from all UELs in the parent set or a subset of specified dimensions in the parent set, can be chain with other *UELs string operations
|
|
497
|
+
|
|
498
|
+
Parameters
|
|
499
|
+
----------
|
|
500
|
+
dimensions : int | List[int], optional
|
|
501
|
+
Symbols' dimensions, by default None
|
|
502
|
+
"""
|
|
503
|
+
self._formatUELs(str.strip, dimensions=dimensions)
|
|
504
|
+
return self
|
|
505
|
+
|
|
506
|
+
def capitalizeUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
507
|
+
"""
|
|
508
|
+
Will capitalize all UELs in the Container or a subset of specified symbols, can be chained with other *UELs string operations
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
dimensions : int | List[int], optional
|
|
513
|
+
Symbols' dimensions, by default None
|
|
514
|
+
"""
|
|
515
|
+
self._formatUELs(str.capitalize, dimensions=dimensions)
|
|
516
|
+
return self
|
|
517
|
+
|
|
518
|
+
def casefoldUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
519
|
+
"""
|
|
520
|
+
Will casefold all UELs in the Container or a subset of specified symbols, can be chained with other *UELs string operations
|
|
521
|
+
|
|
522
|
+
Parameters
|
|
523
|
+
----------
|
|
524
|
+
dimensions : int | List[int], optional
|
|
525
|
+
Symbols' dimensions, by default None
|
|
526
|
+
"""
|
|
527
|
+
self._formatUELs(str.casefold, dimensions=dimensions)
|
|
528
|
+
return self
|
|
529
|
+
|
|
530
|
+
def titleUELs(self, dimensions: Optional[Union[int, List[int]]] = None):
|
|
531
|
+
"""
|
|
532
|
+
Will title (capitalize all individual words) in all UELs in the Container or a subset of specified symbols, can be chained with other *UELs string operations
|
|
533
|
+
|
|
534
|
+
Parameters
|
|
535
|
+
----------
|
|
536
|
+
dimensions : int | List[int], optional
|
|
537
|
+
Symbols' dimensions, by default None
|
|
538
|
+
"""
|
|
539
|
+
self._formatUELs(str.title, dimensions=dimensions)
|
|
540
|
+
return self
|
|
541
|
+
|
|
542
|
+
def ljustUELs(
|
|
543
|
+
self,
|
|
544
|
+
length: int,
|
|
545
|
+
fill_character: Optional[str] = None,
|
|
546
|
+
dimensions: Optional[Union[int, List[int]]] = None,
|
|
547
|
+
):
|
|
548
|
+
"""
|
|
549
|
+
Will left justify all UELs in the symbol or a subset of specified dimensions, can be chained with other *UELs string operations
|
|
550
|
+
|
|
551
|
+
Parameters
|
|
552
|
+
----------
|
|
553
|
+
length : int
|
|
554
|
+
|
|
555
|
+
fill_character : str, optional
|
|
556
|
+
Characters to fill in the empty, by default None
|
|
557
|
+
dimensions : int | List[int], optional
|
|
558
|
+
Symbols' dimensions, by default None
|
|
559
|
+
"""
|
|
560
|
+
if fill_character is None:
|
|
561
|
+
fill_character = " "
|
|
562
|
+
|
|
563
|
+
if fill_character == " ":
|
|
564
|
+
warn(
|
|
565
|
+
"Trailing spaces are not significant in GAMS, they will be dropped "
|
|
566
|
+
"automatically if written to GAMS data structures (GDX/GMD). "
|
|
567
|
+
"They are maintained if using generating other data formats (CSV, SQL, etc.)"
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
self._formatUELs(
|
|
572
|
+
lambda x: x.ljust(length, fill_character), dimensions=dimensions
|
|
573
|
+
)
|
|
574
|
+
except Exception as err:
|
|
575
|
+
raise Exception(
|
|
576
|
+
f"Could not successfully left justify (ljust) categories in `{self.name}`. Reason: {err}"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
return self
|
|
580
|
+
|
|
581
|
+
def rjustUELs(
|
|
582
|
+
self,
|
|
583
|
+
length: int,
|
|
584
|
+
fill_character: Optional[str] = None,
|
|
585
|
+
dimensions: Optional[Union[int, List[int]]] = None,
|
|
586
|
+
):
|
|
587
|
+
"""
|
|
588
|
+
Will right justify all UELs in the symbol or a subset of specified dimensions, can be chained with other *UELs string operations
|
|
589
|
+
|
|
590
|
+
Parameters
|
|
591
|
+
----------
|
|
592
|
+
length : int
|
|
593
|
+
|
|
594
|
+
fill_character : str, optional
|
|
595
|
+
Characters to fill in the empty, by default None
|
|
596
|
+
dimensions : int | List[int], optional
|
|
597
|
+
Symbols' dimensions, by default None
|
|
598
|
+
"""
|
|
599
|
+
if fill_character is None:
|
|
600
|
+
fill_character = " "
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
self._formatUELs(
|
|
604
|
+
lambda x: x.rjust(length, fill_character), dimensions=dimensions
|
|
605
|
+
)
|
|
606
|
+
except Exception as err:
|
|
607
|
+
raise Exception(
|
|
608
|
+
f"Could not successfully right justify (rjust) categories in `{self.name}`. Reason: {err}"
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
return self
|
|
612
|
+
|
|
613
|
+
def renameUELs(
|
|
614
|
+
self,
|
|
615
|
+
uels: Union[dict, list, str],
|
|
616
|
+
dimensions: Optional[Union[int, list]] = None,
|
|
617
|
+
allow_merge: bool = False,
|
|
618
|
+
) -> None:
|
|
619
|
+
"""
|
|
620
|
+
Renames UELs (case-sensitive) that appear in the symbol dimensions. If dimensions is None then operate on all dimensions of the symbol. ** All trailing whitespace is trimmed **
|
|
621
|
+
|
|
622
|
+
Parameters
|
|
623
|
+
----------
|
|
624
|
+
uels : dict | list | str
|
|
625
|
+
List of UELs (case-sensitive) that appear in the symbol dimensions
|
|
626
|
+
dimensions : int | list, optional
|
|
627
|
+
Symbols' dimensions, by default None
|
|
628
|
+
allow_merge : bool, optional
|
|
629
|
+
If True, the categorical object will be re-created to offer additional data flexibility. By default False
|
|
630
|
+
"""
|
|
631
|
+
if self.records is not None:
|
|
632
|
+
if not self.isValid():
|
|
633
|
+
raise Exception(
|
|
634
|
+
"Symbol is currently invalid -- must be valid in order to access UELs (categories)."
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
#
|
|
638
|
+
# ARG: uels
|
|
639
|
+
if not isinstance(uels, (dict, list, str)):
|
|
640
|
+
raise TypeError("Argument 'uels' must be type str, list, or dict")
|
|
641
|
+
|
|
642
|
+
if isinstance(uels, str):
|
|
643
|
+
uels = [uels]
|
|
644
|
+
|
|
645
|
+
if isinstance(uels, dict):
|
|
646
|
+
if any(
|
|
647
|
+
not isinstance(k, str) or not isinstance(v, str)
|
|
648
|
+
for k, v in uels.items()
|
|
649
|
+
):
|
|
650
|
+
raise TypeError(
|
|
651
|
+
"Argument 'uels' dict must have both keys and values "
|
|
652
|
+
"that are type str (i.e., {'<old uel name>':'<new uel name>'})"
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
# trim all trailing whitespace
|
|
656
|
+
uels = {k: v.rstrip() for k, v in uels.items()}
|
|
657
|
+
|
|
658
|
+
if isinstance(uels, list):
|
|
659
|
+
if any(not isinstance(i, str) for i in uels):
|
|
660
|
+
raise TypeError("Argument 'uels' must contain only type str")
|
|
661
|
+
|
|
662
|
+
# trim all trailing whitespace
|
|
663
|
+
uels = list(map(str.rstrip, uels))
|
|
664
|
+
|
|
665
|
+
#
|
|
666
|
+
# ARG: dimensions
|
|
667
|
+
if not isinstance(dimensions, (int, list, type(None))):
|
|
668
|
+
raise TypeError(
|
|
669
|
+
"Argument 'dimensions' must be type int, list, or NoneType"
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
if dimensions is None:
|
|
673
|
+
dimensions = list(range(self.dimension))
|
|
674
|
+
|
|
675
|
+
if isinstance(dimensions, int):
|
|
676
|
+
dimensions = [dimensions]
|
|
677
|
+
|
|
678
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
679
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
680
|
+
|
|
681
|
+
for i in dimensions:
|
|
682
|
+
if i >= self.dimension:
|
|
683
|
+
raise ValueError(
|
|
684
|
+
f"Cannot access symbol 'dimension' `{i}`, because `{i}` is >= symbol "
|
|
685
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
#
|
|
689
|
+
# ARG: allow_merge
|
|
690
|
+
if not isinstance(allow_merge, bool):
|
|
691
|
+
raise TypeError("Argument 'allow_merge' must be type bool")
|
|
692
|
+
|
|
693
|
+
#
|
|
694
|
+
# check if uels has right length
|
|
695
|
+
if isinstance(uels, list):
|
|
696
|
+
for n in dimensions:
|
|
697
|
+
if len(uels) != len(self.records.iloc[:, n].cat.categories):
|
|
698
|
+
raise Exception(
|
|
699
|
+
f"Could not rename UELs (categories) in `{self.name}` dimension `{n}`. "
|
|
700
|
+
"Reason: new categories need to have the same "
|
|
701
|
+
"number of items as the old categories!"
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
for n in dimensions:
|
|
705
|
+
if allow_merge:
|
|
706
|
+
if isinstance(uels, list):
|
|
707
|
+
uel_map = dict(
|
|
708
|
+
zip(
|
|
709
|
+
self.records.iloc[:, n].cat.categories.tolist(),
|
|
710
|
+
uels,
|
|
711
|
+
)
|
|
712
|
+
)
|
|
713
|
+
else:
|
|
714
|
+
uel_map = uels
|
|
715
|
+
|
|
716
|
+
if any(
|
|
717
|
+
uel in self.records.iloc[:, n].cat.categories
|
|
718
|
+
for uel in uel_map.keys()
|
|
719
|
+
):
|
|
720
|
+
is_ordered = self.records.iloc[:, n].dtype.ordered
|
|
721
|
+
old_uels = self.records.iloc[:, n].cat.categories.to_list()
|
|
722
|
+
|
|
723
|
+
# create and de-duplicate new_uels
|
|
724
|
+
new_uels = list(
|
|
725
|
+
dict.fromkeys(
|
|
726
|
+
[
|
|
727
|
+
uel_map[uel] if uel in uel_map.keys() else uel
|
|
728
|
+
for uel in old_uels
|
|
729
|
+
]
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# convert dimension back to object and do the string renaming
|
|
734
|
+
|
|
735
|
+
self.records.isetitem(
|
|
736
|
+
n,
|
|
737
|
+
self.records.iloc[:, n]
|
|
738
|
+
.astype("object")
|
|
739
|
+
.map(uel_map)
|
|
740
|
+
.fillna(self.records.iloc[:, n]),
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
# recreate the categorical
|
|
744
|
+
self.records.isetitem(
|
|
745
|
+
n,
|
|
746
|
+
self.records.iloc[:, n].astype(
|
|
747
|
+
CategoricalDtype(
|
|
748
|
+
categories=new_uels, ordered=is_ordered
|
|
749
|
+
)
|
|
750
|
+
),
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
self.modified = True
|
|
754
|
+
self.container.modified = True
|
|
755
|
+
|
|
756
|
+
else:
|
|
757
|
+
try:
|
|
758
|
+
self.records.isetitem(
|
|
759
|
+
n, self.records.iloc[:, n].cat.rename_categories(uels)
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
self.modified = True
|
|
763
|
+
self.container.modified = True
|
|
764
|
+
|
|
765
|
+
except Exception as err:
|
|
766
|
+
raise Exception(
|
|
767
|
+
f"Could not rename UELs (categories) in `{self.name}` dimension `{n}`. Reason: {err}"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
def removeUELs(
|
|
771
|
+
self,
|
|
772
|
+
uels: Optional[Union[dict, list, str]] = None,
|
|
773
|
+
dimensions: Optional[Union[int, list]] = None,
|
|
774
|
+
) -> None:
|
|
775
|
+
"""
|
|
776
|
+
Removes UELs that appear in the symbol dimensions, If uels is None then remove all unused UELs (categories). If dimensions is None then operate on all dimensions.
|
|
777
|
+
|
|
778
|
+
Parameters
|
|
779
|
+
----------
|
|
780
|
+
uels : dict | list | str
|
|
781
|
+
List of UELs (case-sensitive) that appear in the symbol dimensions
|
|
782
|
+
dimensions : int | list, optional
|
|
783
|
+
Symbols' dimensions, by default None
|
|
784
|
+
"""
|
|
785
|
+
if self.records is not None:
|
|
786
|
+
if not self.isValid():
|
|
787
|
+
raise Exception(
|
|
788
|
+
"Symbol is currently invalid -- must be valid in order to access UELs (categories)."
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# ARG: uels
|
|
792
|
+
if not isinstance(uels, (list, str, type(None))):
|
|
793
|
+
raise TypeError("Argument 'uels' must be type list, str for NoneType")
|
|
794
|
+
|
|
795
|
+
if isinstance(uels, str):
|
|
796
|
+
uels = [uels]
|
|
797
|
+
|
|
798
|
+
if isinstance(uels, list):
|
|
799
|
+
if any(not isinstance(i, str) for i in uels):
|
|
800
|
+
raise TypeError("Argument 'uels' must contain only type str")
|
|
801
|
+
|
|
802
|
+
# ARG: dimensions
|
|
803
|
+
if not isinstance(dimensions, (int, list, type(None))):
|
|
804
|
+
raise TypeError(
|
|
805
|
+
"Argument 'dimensions' must be type int, list, or NoneType"
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
if dimensions is None:
|
|
809
|
+
dimensions = list(range(self.dimension))
|
|
810
|
+
|
|
811
|
+
if isinstance(dimensions, int):
|
|
812
|
+
dimensions = [dimensions]
|
|
813
|
+
|
|
814
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
815
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
816
|
+
|
|
817
|
+
for i in dimensions:
|
|
818
|
+
if i >= self.dimension:
|
|
819
|
+
raise ValueError(
|
|
820
|
+
f"Cannot access symbol 'dimension' `{i}`, because `{i}` is >= symbol "
|
|
821
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
# method body
|
|
825
|
+
if uels is None:
|
|
826
|
+
for n in dimensions:
|
|
827
|
+
try:
|
|
828
|
+
self.records.isetitem(
|
|
829
|
+
n, self.records.iloc[:, n].cat.remove_unused_categories()
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
self.modified = True
|
|
833
|
+
self.container.modified = True
|
|
834
|
+
|
|
835
|
+
except Exception as err:
|
|
836
|
+
raise Exception(
|
|
837
|
+
f"Could not remove unused UELs (categories) in symbol "
|
|
838
|
+
f"dimension `{n}`. Reason: {err}"
|
|
839
|
+
)
|
|
840
|
+
else:
|
|
841
|
+
for n in dimensions:
|
|
842
|
+
try:
|
|
843
|
+
self.records.isetitem(
|
|
844
|
+
n,
|
|
845
|
+
self.records.iloc[:, n].cat.remove_categories(
|
|
846
|
+
self.records.iloc[:, n].cat.categories.intersection(
|
|
847
|
+
set(uels)
|
|
848
|
+
)
|
|
849
|
+
),
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
self.modified = True
|
|
853
|
+
self.container.modified = True
|
|
854
|
+
|
|
855
|
+
except Exception as err:
|
|
856
|
+
raise Exception(
|
|
857
|
+
f"Could not remove unused UELs (categories) in symbol "
|
|
858
|
+
f"dimension `{n}`. Reason: {err}"
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
def setUELs(
|
|
862
|
+
self,
|
|
863
|
+
uels: Union[str, List[str]],
|
|
864
|
+
dimensions: Optional[Union[int, list]] = None,
|
|
865
|
+
rename: bool = False,
|
|
866
|
+
) -> None:
|
|
867
|
+
"""
|
|
868
|
+
Set the UELs for symbol dimensions. If dimensions is None then set UELs for all dimensions. ** All trailing whitespace is trimmed **
|
|
869
|
+
|
|
870
|
+
Parameters
|
|
871
|
+
----------
|
|
872
|
+
uels : str | List[str]
|
|
873
|
+
List of UELs (case-sensitive) that appear in the symbol dimensions
|
|
874
|
+
dimensions : int | list, optional
|
|
875
|
+
Symbols' dimensions, by default None
|
|
876
|
+
rename : bool, optional
|
|
877
|
+
If True, the old UEL names will be renamed with the new UEL names. By default False
|
|
878
|
+
"""
|
|
879
|
+
if self.records is not None:
|
|
880
|
+
if not self.isValid():
|
|
881
|
+
raise Exception(
|
|
882
|
+
"Symbol is currently invalid -- must be valid in order to set UELs (categories)."
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
# ARG: uels
|
|
886
|
+
if not isinstance(uels, (str, list)):
|
|
887
|
+
raise TypeError("Argument 'uels' must be type list or str")
|
|
888
|
+
|
|
889
|
+
if isinstance(uels, str):
|
|
890
|
+
uels = [uels]
|
|
891
|
+
|
|
892
|
+
if any(not isinstance(uel, str) for uel in uels):
|
|
893
|
+
raise TypeError("Argument 'uels' must only contain type str")
|
|
894
|
+
|
|
895
|
+
# trim all trailing whitespace
|
|
896
|
+
uels = list(map(str.rstrip, uels))
|
|
897
|
+
|
|
898
|
+
# ARG: dimensions
|
|
899
|
+
if not isinstance(dimensions, (int, list, type(None))):
|
|
900
|
+
raise TypeError(
|
|
901
|
+
"Argument 'dimensions' must be type int, list, or NoneType"
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
if dimensions is None:
|
|
905
|
+
dimensions = list(range(self.dimension))
|
|
906
|
+
|
|
907
|
+
if isinstance(dimensions, int):
|
|
908
|
+
dimensions = [dimensions]
|
|
909
|
+
|
|
910
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
911
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
912
|
+
|
|
913
|
+
for i in dimensions:
|
|
914
|
+
if i >= self.dimension:
|
|
915
|
+
raise ValueError(
|
|
916
|
+
f"Cannot access symbol 'dimension' `{i}`, because `{i}` is >= symbol "
|
|
917
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
# ARG: rename
|
|
921
|
+
if not isinstance(rename, bool):
|
|
922
|
+
raise TypeError("Argument 'rename' must be type bool")
|
|
923
|
+
|
|
924
|
+
for n in dimensions:
|
|
925
|
+
try:
|
|
926
|
+
self.records.isetitem(
|
|
927
|
+
n,
|
|
928
|
+
self.records.iloc[:, n].cat.set_categories(
|
|
929
|
+
uels, ordered=True, rename=rename
|
|
930
|
+
),
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
self.modified = True
|
|
934
|
+
self.container.modified = True
|
|
935
|
+
|
|
936
|
+
except Exception as err:
|
|
937
|
+
raise Exception(
|
|
938
|
+
f"Could not set UELs (categories) in symbol dimension `{n}`. Reason: {err}"
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
def reorderUELs(
|
|
942
|
+
self,
|
|
943
|
+
uels: Optional[Union[str, List[str]]] = None,
|
|
944
|
+
dimensions: Optional[Union[int, list]] = None,
|
|
945
|
+
) -> None:
|
|
946
|
+
"""
|
|
947
|
+
Reorders the UELs in the symbol dimensions. If uels is None, reorder UELs to data order and append any unused categories. If dimensions is None then reorder UELs in all dimensions of the symbol.
|
|
948
|
+
|
|
949
|
+
Parameters
|
|
950
|
+
----------
|
|
951
|
+
uels : str | List[str], optional
|
|
952
|
+
List of UELs, by default None
|
|
953
|
+
dimensions : int | list, optional
|
|
954
|
+
Symbol dimensions, by default None
|
|
955
|
+
"""
|
|
956
|
+
if self.records is not None:
|
|
957
|
+
if not self.isValid():
|
|
958
|
+
raise Exception(
|
|
959
|
+
"Symbol is currently invalid -- must be valid in order to reorder UELs (categories)."
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
# ARG: uels
|
|
963
|
+
if not isinstance(uels, (str, list, type(None))):
|
|
964
|
+
raise TypeError("Argument 'uels' must be type list, str, or NoneType")
|
|
965
|
+
|
|
966
|
+
if isinstance(uels, str):
|
|
967
|
+
uels = [uels]
|
|
968
|
+
|
|
969
|
+
if uels is not None:
|
|
970
|
+
if any(not isinstance(uel, str) for uel in uels):
|
|
971
|
+
raise TypeError("Argument 'uels' must only contain type str")
|
|
972
|
+
|
|
973
|
+
# ARG: dimensions
|
|
974
|
+
if not isinstance(dimensions, (int, list, type(None))):
|
|
975
|
+
raise TypeError(
|
|
976
|
+
"Argument 'dimensions' must be type int, list, or NoneType"
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
if dimensions is None:
|
|
980
|
+
dimensions = list(range(self.dimension))
|
|
981
|
+
|
|
982
|
+
if isinstance(dimensions, int):
|
|
983
|
+
dimensions = [dimensions]
|
|
984
|
+
|
|
985
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
986
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
987
|
+
|
|
988
|
+
for i in dimensions:
|
|
989
|
+
if i >= self.dimension:
|
|
990
|
+
raise ValueError(
|
|
991
|
+
f"Cannot access symbol 'dimension' `{i}`, because `{i}` is >= symbol "
|
|
992
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
if uels is not None:
|
|
996
|
+
for n in dimensions:
|
|
997
|
+
try:
|
|
998
|
+
# fastpath
|
|
999
|
+
self.records.isetitem(
|
|
1000
|
+
n,
|
|
1001
|
+
self.records.iloc[:, n].cat.reorder_categories(uels),
|
|
1002
|
+
)
|
|
1003
|
+
|
|
1004
|
+
except:
|
|
1005
|
+
# need to reset categories
|
|
1006
|
+
try:
|
|
1007
|
+
self.records.isetitem(n, self.setUELs(uels, dimensions=n))
|
|
1008
|
+
self.modified = True
|
|
1009
|
+
self.container.modified = True
|
|
1010
|
+
|
|
1011
|
+
except Exception as err:
|
|
1012
|
+
raise Exception(
|
|
1013
|
+
f"Could not reorder UELs (categories) in symbol dimension `{n}`. Reason: {err}"
|
|
1014
|
+
)
|
|
1015
|
+
else:
|
|
1016
|
+
for n in dimensions:
|
|
1017
|
+
old_cats = dict.fromkeys(self.records.iloc[:, n].cat.categories)
|
|
1018
|
+
new_cats = dict.fromkeys(self.records.iloc[:, n].unique())
|
|
1019
|
+
|
|
1020
|
+
uels = new_cats
|
|
1021
|
+
uels.update(dict.fromkeys(old_cats))
|
|
1022
|
+
uels = list(uels.keys())
|
|
1023
|
+
|
|
1024
|
+
try:
|
|
1025
|
+
# fastpath
|
|
1026
|
+
self.records.isetitem(
|
|
1027
|
+
n,
|
|
1028
|
+
self.records.iloc[:, n].cat.reorder_categories(uels),
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
except:
|
|
1032
|
+
# need to reset categories
|
|
1033
|
+
try:
|
|
1034
|
+
self.records.isetitem(n, self.setUELs(uels, dimensions=n))
|
|
1035
|
+
self.modified = True
|
|
1036
|
+
self.container.modified = True
|
|
1037
|
+
|
|
1038
|
+
except Exception as err:
|
|
1039
|
+
raise Exception(
|
|
1040
|
+
f"Could not reorder UELs (categories) in symbol dimension `{n}`. Reason: {err}"
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
def addUELs(
|
|
1044
|
+
self, uels: Union[str, List[str]], dimensions: Optional[Union[int, list]] = None
|
|
1045
|
+
) -> None:
|
|
1046
|
+
"""
|
|
1047
|
+
Adds UELs to the symbol dimensions. If dimensions is None then add UELs to all dimensions. ** All trailing whitespace is trimmed **
|
|
1048
|
+
|
|
1049
|
+
Parameters
|
|
1050
|
+
----------
|
|
1051
|
+
uels : str | List[str]
|
|
1052
|
+
List of UELs
|
|
1053
|
+
dimensions : int | list, optional
|
|
1054
|
+
Symbol dimensions, by default None
|
|
1055
|
+
"""
|
|
1056
|
+
if self.records is not None:
|
|
1057
|
+
if not self.isValid():
|
|
1058
|
+
raise Exception(
|
|
1059
|
+
"Symbol is currently invalid -- must be valid in order to access UELs (categories)."
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
# ARG: uels
|
|
1063
|
+
if not isinstance(uels, (str, list)):
|
|
1064
|
+
raise TypeError("Argument 'uels' must be type list or str")
|
|
1065
|
+
|
|
1066
|
+
if isinstance(uels, str):
|
|
1067
|
+
uels = [uels]
|
|
1068
|
+
|
|
1069
|
+
if any(not isinstance(uel, str) for uel in uels):
|
|
1070
|
+
raise TypeError("Argument 'uels' must only contain type str")
|
|
1071
|
+
|
|
1072
|
+
# trim all trailing whitespace
|
|
1073
|
+
uels = list(map(str.rstrip, uels))
|
|
1074
|
+
|
|
1075
|
+
# ARG: dimensions
|
|
1076
|
+
if not isinstance(dimensions, (int, list, type(None))):
|
|
1077
|
+
raise TypeError(
|
|
1078
|
+
"Argument 'dimensions' must be type int, list, or NoneType"
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
if dimensions is None:
|
|
1082
|
+
dimensions = list(range(self.dimension))
|
|
1083
|
+
|
|
1084
|
+
if isinstance(dimensions, int):
|
|
1085
|
+
dimensions = [dimensions]
|
|
1086
|
+
|
|
1087
|
+
if any(not isinstance(i, int) for i in dimensions):
|
|
1088
|
+
raise TypeError("Argument 'dimensions' must only contain type int")
|
|
1089
|
+
|
|
1090
|
+
for i in dimensions:
|
|
1091
|
+
if i >= self.dimension:
|
|
1092
|
+
raise ValueError(
|
|
1093
|
+
f"Cannot access symbol 'dimension' `{i}`, because `{i}` is >= symbol "
|
|
1094
|
+
f"dimension (`{self.dimension}`). (NOTE: symbol 'dimension' is indexed from zero)"
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
for n in dimensions:
|
|
1098
|
+
try:
|
|
1099
|
+
self.records.isetitem(
|
|
1100
|
+
n, self.records.iloc[:, n].cat.add_categories(uels)
|
|
1101
|
+
)
|
|
1102
|
+
|
|
1103
|
+
self.modified = True
|
|
1104
|
+
self.container.modified = True
|
|
1105
|
+
|
|
1106
|
+
except Exception as err:
|
|
1107
|
+
raise Exception(
|
|
1108
|
+
f"Could not add UELs (categories) to symbol dimension `{n}`. Reason: {err}"
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
def getDomainViolations(self) -> Optional[List["DomainViolation"]]:
|
|
1112
|
+
"""
|
|
1113
|
+
Returns a list of DomainViolation objects if any (None otherwise)
|
|
1114
|
+
|
|
1115
|
+
Returns
|
|
1116
|
+
-------
|
|
1117
|
+
Optional[DomainViolation]
|
|
1118
|
+
List of DomainViolation objects if any (None otherwise)
|
|
1119
|
+
"""
|
|
1120
|
+
if self.records is None:
|
|
1121
|
+
return None
|
|
1122
|
+
|
|
1123
|
+
else:
|
|
1124
|
+
dvobjs = []
|
|
1125
|
+
for n, symobj in enumerate(self.domain):
|
|
1126
|
+
if isinstance(symobj, abcs.AnyContainerDomainSymbol):
|
|
1127
|
+
if not symobj.isValid():
|
|
1128
|
+
raise Exception(
|
|
1129
|
+
f"Cannot locate domain violations for symbol `{self.name}` "
|
|
1130
|
+
f"because the referenced domain set `{symobj.name}` is not valid"
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
self_elem = pd.Series(self.getUELs(n, ignore_unused=True))
|
|
1134
|
+
|
|
1135
|
+
# domain violations are generated for all elements if the domain set does not have records
|
|
1136
|
+
if symobj.records is not None:
|
|
1137
|
+
domain_elem = pd.Series(symobj.getUELs(ignore_unused=True))
|
|
1138
|
+
else:
|
|
1139
|
+
domain_elem = pd.Series([])
|
|
1140
|
+
|
|
1141
|
+
idx = ~self_elem.map(str.casefold).isin(
|
|
1142
|
+
domain_elem.map(str.casefold)
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
if any(idx):
|
|
1146
|
+
dvobjs.append(
|
|
1147
|
+
DomainViolation(self, n, symobj, self_elem[idx].tolist())
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
if len(dvobjs) != 0:
|
|
1151
|
+
return dvobjs
|
|
1152
|
+
|
|
1153
|
+
def findDomainViolations(self) -> Optional[pd.DataFrame]:
|
|
1154
|
+
"""
|
|
1155
|
+
Get a view of the records DataFrame that contain any domain violations
|
|
1156
|
+
|
|
1157
|
+
Returns
|
|
1158
|
+
-------
|
|
1159
|
+
Optional[pd.DataFrame]
|
|
1160
|
+
Records DataFrame that contain any domain violations
|
|
1161
|
+
"""
|
|
1162
|
+
if self.records is not None:
|
|
1163
|
+
violations = self.getDomainViolations()
|
|
1164
|
+
|
|
1165
|
+
if violations is not None:
|
|
1166
|
+
for n, v in enumerate(violations):
|
|
1167
|
+
set_v = set(v.violations)
|
|
1168
|
+
if n == 0:
|
|
1169
|
+
idx = self.records.iloc[:, v.dimension].isin(set_v)
|
|
1170
|
+
else:
|
|
1171
|
+
idx = (idx) | (self.records.iloc[:, v.dimension].isin(set_v))
|
|
1172
|
+
|
|
1173
|
+
return self.records.loc[idx, :]
|
|
1174
|
+
else:
|
|
1175
|
+
return self.records.loc[pd.Index([]), :]
|
|
1176
|
+
|
|
1177
|
+
def hasDomainViolations(self) -> bool:
|
|
1178
|
+
"""
|
|
1179
|
+
Returns True if there are domain violations in the records, returns False if not.
|
|
1180
|
+
|
|
1181
|
+
Returns
|
|
1182
|
+
-------
|
|
1183
|
+
bool
|
|
1184
|
+
True if there are domain violations in the records, returns False if not.
|
|
1185
|
+
"""
|
|
1186
|
+
if self.records is not None:
|
|
1187
|
+
return self.findDomainViolations().empty is False
|
|
1188
|
+
else:
|
|
1189
|
+
return 0
|
|
1190
|
+
|
|
1191
|
+
def countDomainViolations(self) -> int:
|
|
1192
|
+
"""
|
|
1193
|
+
Returns the count of how many records contain at least one domain violation
|
|
1194
|
+
|
|
1195
|
+
Returns
|
|
1196
|
+
-------
|
|
1197
|
+
int
|
|
1198
|
+
Count of how many records contain at least one domain violation
|
|
1199
|
+
"""
|
|
1200
|
+
if self.records is not None:
|
|
1201
|
+
return len(self.findDomainViolations())
|
|
1202
|
+
else:
|
|
1203
|
+
return 0
|
|
1204
|
+
|
|
1205
|
+
def dropDomainViolations(self):
|
|
1206
|
+
"""
|
|
1207
|
+
drop records from the symbol that contain a domain violation
|
|
1208
|
+
"""
|
|
1209
|
+
try:
|
|
1210
|
+
self.records.drop(index=self.findDomainViolations().index, inplace=True)
|
|
1211
|
+
except:
|
|
1212
|
+
return None
|
|
1213
|
+
|
|
1214
|
+
def countDuplicateRecords(self) -> int:
|
|
1215
|
+
"""
|
|
1216
|
+
Returns the count of how many (case insensitive) duplicate records exist
|
|
1217
|
+
|
|
1218
|
+
Returns
|
|
1219
|
+
-------
|
|
1220
|
+
int
|
|
1221
|
+
Count of how many (case insensitive) duplicate records exist
|
|
1222
|
+
"""
|
|
1223
|
+
try:
|
|
1224
|
+
return len(self.findDuplicateRecords())
|
|
1225
|
+
except:
|
|
1226
|
+
return 0
|
|
1227
|
+
|
|
1228
|
+
def findDuplicateRecords(self, keep: Union[str, bool] = "first") -> pd.DataFrame:
|
|
1229
|
+
"""
|
|
1230
|
+
Get a view of the records DataFrame that contain any (case insensitive) duplicate domains – keep argument can take values of "first" (finds all duplicates while keeping the first instance as unique), "last" (finds all duplicates while keeping the last instance as unique), or False (finds all duplicates)
|
|
1231
|
+
|
|
1232
|
+
Parameters
|
|
1233
|
+
----------
|
|
1234
|
+
keep : str | bool, optional
|
|
1235
|
+
Argument 'keep' must be either 'first' (returns duplicates except for the first occurrence), 'last' (returns duplicates except for the last occurrence), or False (returns all duplicates), by default "first"
|
|
1236
|
+
|
|
1237
|
+
Returns
|
|
1238
|
+
-------
|
|
1239
|
+
pd.DataFrame
|
|
1240
|
+
"""
|
|
1241
|
+
if keep not in {"first", "last", False}:
|
|
1242
|
+
raise ValueError(
|
|
1243
|
+
"Argument 'keep' must be either 'first' "
|
|
1244
|
+
"(returns duplicates except for the first occurrence), "
|
|
1245
|
+
"'last' (returns duplicates except for the last occurrence), "
|
|
1246
|
+
"or False (returns all duplicates)"
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
# create a temporary copy
|
|
1250
|
+
df2 = self.records.copy()
|
|
1251
|
+
|
|
1252
|
+
# casefold all domains
|
|
1253
|
+
for i in range(self.dimension):
|
|
1254
|
+
df2.isetitem(i, df2.iloc[:, i].map(str.casefold))
|
|
1255
|
+
|
|
1256
|
+
idx = df2.duplicated(subset=df2.columns[: self.dimension], keep=keep)
|
|
1257
|
+
|
|
1258
|
+
return self.records.loc[idx, :]
|
|
1259
|
+
|
|
1260
|
+
def hasDuplicateRecords(self) -> bool:
|
|
1261
|
+
"""
|
|
1262
|
+
Returns True if there are (case insensitive) duplicate records in the symbol, returns False if not
|
|
1263
|
+
|
|
1264
|
+
Returns
|
|
1265
|
+
-------
|
|
1266
|
+
bool
|
|
1267
|
+
True if there are (case insensitive) duplicate records in the symbol, returns False if not
|
|
1268
|
+
"""
|
|
1269
|
+
return self.countDuplicateRecords() != 0
|
|
1270
|
+
|
|
1271
|
+
def dropDuplicateRecords(self, keep: Union[str, bool] = "first") -> None:
|
|
1272
|
+
"""
|
|
1273
|
+
Drop records with (case insensitive) duplicate domains from the symbol
|
|
1274
|
+
|
|
1275
|
+
Parameters
|
|
1276
|
+
----------
|
|
1277
|
+
keep : str | bool, optional
|
|
1278
|
+
keep argument can take values of "first" (keeps the first instance of a duplicate record), "last" (keeps the last instance of a record), or False (drops all duplicates including the first and last), by default "first"
|
|
1279
|
+
"""
|
|
1280
|
+
try:
|
|
1281
|
+
self.records.drop(index=self.findDuplicateRecords(keep).index, inplace=True)
|
|
1282
|
+
except:
|
|
1283
|
+
return None
|
|
1284
|
+
|
|
1285
|
+
@property
|
|
1286
|
+
def domain_type(self):
|
|
1287
|
+
"""State of the domain links"""
|
|
1288
|
+
return self._domain_status.name
|
|
1289
|
+
|
|
1290
|
+
@property
|
|
1291
|
+
def _domain_status(self):
|
|
1292
|
+
if (
|
|
1293
|
+
all(isinstance(i, abcs.AnyContainerDomainSymbol) for i in self.domain)
|
|
1294
|
+
and self.dimension != 0
|
|
1295
|
+
):
|
|
1296
|
+
return DomainStatus.regular
|
|
1297
|
+
elif all(i == "*" for i in self.domain):
|
|
1298
|
+
return DomainStatus.none
|
|
1299
|
+
elif self.dimension == 0:
|
|
1300
|
+
return DomainStatus.none
|
|
1301
|
+
else:
|
|
1302
|
+
return DomainStatus.relaxed
|
|
1303
|
+
|
|
1304
|
+
def _domainForwarding(self):
|
|
1305
|
+
if isinstance(self.container, abcs.ABCContainer):
|
|
1306
|
+
if isinstance(self.domain_forwarding, bool):
|
|
1307
|
+
forwarding = [self.domain_forwarding] * self.dimension
|
|
1308
|
+
else:
|
|
1309
|
+
forwarding = self.domain_forwarding
|
|
1310
|
+
|
|
1311
|
+
for n, (dl, d) in enumerate(zip(self.domain_labels, self.domain)):
|
|
1312
|
+
if forwarding[n]:
|
|
1313
|
+
# find set names to grow (bottom to top)
|
|
1314
|
+
to_grow = []
|
|
1315
|
+
while isinstance(d, abcs.ABCSet):
|
|
1316
|
+
to_grow.append(d.name)
|
|
1317
|
+
d = d.domain[0]
|
|
1318
|
+
|
|
1319
|
+
# grow the sets (top to bottom)
|
|
1320
|
+
to_grow.reverse()
|
|
1321
|
+
for i in to_grow:
|
|
1322
|
+
if self.container[i].records is not None:
|
|
1323
|
+
recs = self.container[i].records
|
|
1324
|
+
|
|
1325
|
+
assert (
|
|
1326
|
+
self.container[i].dimension == 1
|
|
1327
|
+
), "attempting to forward a domain set that has dimension >1"
|
|
1328
|
+
|
|
1329
|
+
# convert all categoricals back to str to enable concat
|
|
1330
|
+
recs.isetitem(0, recs.iloc[:, 0].astype(str))
|
|
1331
|
+
|
|
1332
|
+
df = pd.DataFrame(self.records[dl])
|
|
1333
|
+
df = df.assign(element_text="")
|
|
1334
|
+
df.columns = recs.columns
|
|
1335
|
+
|
|
1336
|
+
recs = pd.concat([recs, df], ignore_index=True)
|
|
1337
|
+
|
|
1338
|
+
# clean up any non-unique set elements, should they exist
|
|
1339
|
+
recs.drop_duplicates(
|
|
1340
|
+
subset=recs.columns[0],
|
|
1341
|
+
keep="first",
|
|
1342
|
+
inplace=True,
|
|
1343
|
+
ignore_index=True,
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1346
|
+
# convert object back to categorical
|
|
1347
|
+
recs.isetitem(
|
|
1348
|
+
0,
|
|
1349
|
+
recs.iloc[:, 0].astype(
|
|
1350
|
+
CategoricalDtype(
|
|
1351
|
+
recs.iloc[:, 0].unique(), ordered=True
|
|
1352
|
+
)
|
|
1353
|
+
),
|
|
1354
|
+
)
|
|
1355
|
+
else:
|
|
1356
|
+
recs = pd.DataFrame(self.records[dl].unique())
|
|
1357
|
+
recs = recs.assign(element_text="")
|
|
1358
|
+
|
|
1359
|
+
# convert object back to unlinked categorical
|
|
1360
|
+
recs.isetitem(
|
|
1361
|
+
0,
|
|
1362
|
+
recs.iloc[:, 0].astype(
|
|
1363
|
+
CategoricalDtype(
|
|
1364
|
+
recs.iloc[:, 0].unique(), ordered=True
|
|
1365
|
+
)
|
|
1366
|
+
),
|
|
1367
|
+
)
|
|
1368
|
+
|
|
1369
|
+
# set records
|
|
1370
|
+
self.container[i].records = recs
|
|
1371
|
+
self.container[i].domain_labels = self.container[i].domain_names
|
|
1372
|
+
self.container[i].modified = True
|
|
1373
|
+
|
|
1374
|
+
def _assert_valid_records(self):
|
|
1375
|
+
if self.records is not None:
|
|
1376
|
+
# make sure and all domains have valid categories
|
|
1377
|
+
for i in range(self.dimension):
|
|
1378
|
+
if np.any(self.records.iloc[:, i].cat.codes.to_numpy() == -1):
|
|
1379
|
+
raise Exception(
|
|
1380
|
+
f"Categories are missing from the data in symbol `{self.name}` (dimension {i}) -- "
|
|
1381
|
+
"has resulted in `NaN` domains labels. "
|
|
1382
|
+
"Cannot write symbol until domain labels have been been restored."
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
def _assert_is_valid(self):
|
|
1386
|
+
if self._requires_state_check:
|
|
1387
|
+
# check if symbol has a container
|
|
1388
|
+
if self.container is None:
|
|
1389
|
+
raise Exception(
|
|
1390
|
+
"Symbol is not currently linked to a container, "
|
|
1391
|
+
"must add it to a container in order to be valid"
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
# check domain symbols
|
|
1395
|
+
self_nrecs = 0 if self.records is None else len(self.records)
|
|
1396
|
+
if self.domain_type == "regular" and self_nrecs > 0:
|
|
1397
|
+
for sym in self.domain:
|
|
1398
|
+
if not isinstance(sym, abcs.ABCUniverseAlias):
|
|
1399
|
+
dom_nrecs = 0 if sym.records is None else len(sym.records)
|
|
1400
|
+
if dom_nrecs == 0:
|
|
1401
|
+
raise Exception(
|
|
1402
|
+
f"Symbol has 'regular' domain type, but domain set ('{sym.name}') does not have any records (i.e., domain violation(s) exist)."
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
for i in self.domain:
|
|
1406
|
+
if isinstance(i, abcs.AnyContainerDomainSymbol):
|
|
1407
|
+
# must have valid links
|
|
1408
|
+
if hex(id(i)) not in [
|
|
1409
|
+
hex(id(v)) for _, v in self.container.data.items()
|
|
1410
|
+
]:
|
|
1411
|
+
raise Exception(
|
|
1412
|
+
f"Symbol defined over domain symbol `{i.name}`, "
|
|
1413
|
+
"however the object reference "
|
|
1414
|
+
f"'{hex(id(i))}' is not in the Container anymore "
|
|
1415
|
+
f"-- must reset domain for symbol '{self.name}'."
|
|
1416
|
+
)
|
|
1417
|
+
|
|
1418
|
+
# must be valid symbols
|
|
1419
|
+
if not i.isValid():
|
|
1420
|
+
raise Exception(
|
|
1421
|
+
f"Symbol defined over domain symbol `{i.name}`, "
|
|
1422
|
+
"however this object is not a valid object in the Container"
|
|
1423
|
+
" -- all domain objects must be valid."
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
# if records exist do some checks
|
|
1427
|
+
if self.records is not None:
|
|
1428
|
+
# check if records are a DataFrame
|
|
1429
|
+
if not isinstance(self.records, pd.DataFrame):
|
|
1430
|
+
raise Exception("Symbol 'records' must be type pandas.DataFrame")
|
|
1431
|
+
|
|
1432
|
+
# check if self.records has the correct number of columns and/or rows
|
|
1433
|
+
r, c = self.records.shape
|
|
1434
|
+
if not c == self.dimension + len(self._attributes):
|
|
1435
|
+
raise ValueError(
|
|
1436
|
+
"Symbol 'records' does not have the correct "
|
|
1437
|
+
" number of columns (<symbol dimension> + 1)"
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
if self.dimension == 0:
|
|
1441
|
+
if r > 1:
|
|
1442
|
+
raise ValueError(
|
|
1443
|
+
"Symbol 'records' can only have 1 row because "
|
|
1444
|
+
f"it has been defined to be a scalar (currently has {r} rows)"
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
# check that all domain_labels are unique
|
|
1448
|
+
if len(self.domain_labels) != len(set(self.domain_labels)):
|
|
1449
|
+
raise Exception(
|
|
1450
|
+
"Domain columns do not have unique names. "
|
|
1451
|
+
"Reset domain column names by setting the `<symbol>.domain_labels` property."
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
# check that all value columns have the same name as _attributes
|
|
1455
|
+
if self.records.columns[self.dimension :].tolist() != self._attributes:
|
|
1456
|
+
raise Exception(
|
|
1457
|
+
f"Value columns in 'records' must be named and ordered as: {self._attributes}. "
|
|
1458
|
+
f"Currently named: {self.records.columns[self.dimension:]}"
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
# check if domain columns are categorical dtype
|
|
1462
|
+
for i in self.domain_labels:
|
|
1463
|
+
if not isinstance(self.records[i].dtype, CategoricalDtype):
|
|
1464
|
+
raise Exception(
|
|
1465
|
+
f"Domain information in column `{i}` for "
|
|
1466
|
+
" 'records' must be categorical type"
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
# check if domain categories are all type str
|
|
1470
|
+
for i in self.domain_labels:
|
|
1471
|
+
typ = infer_dtype(self.records[i].cat.categories)
|
|
1472
|
+
if typ != "empty":
|
|
1473
|
+
if typ != "string":
|
|
1474
|
+
raise TypeError(
|
|
1475
|
+
f"Domain column `{i}` in 'records' contains non-str "
|
|
1476
|
+
"category, all domain categories must be type str."
|
|
1477
|
+
)
|
|
1478
|
+
|
|
1479
|
+
# check if set element_text columns are type str
|
|
1480
|
+
if isinstance(self, abcs.ABCSet):
|
|
1481
|
+
typ = infer_dtype(self.records["element_text"])
|
|
1482
|
+
if typ != "empty":
|
|
1483
|
+
if typ != "string":
|
|
1484
|
+
raise TypeError(
|
|
1485
|
+
"Records 'element_text' column must contain only str type"
|
|
1486
|
+
)
|
|
1487
|
+
|
|
1488
|
+
# check if all data_columns are type float
|
|
1489
|
+
if isinstance(
|
|
1490
|
+
self, (abcs.ABCParameter, abcs.ABCVariable, abcs.ABCEquation)
|
|
1491
|
+
):
|
|
1492
|
+
for i in self.records.columns[self.dimension :]:
|
|
1493
|
+
if infer_dtype(self.records[i]) != "empty":
|
|
1494
|
+
if not is_float_dtype(self.records[i]):
|
|
1495
|
+
raise Exception(
|
|
1496
|
+
f"Data in column `{i}` for 'records' must be float type"
|
|
1497
|
+
)
|
|
1498
|
+
|
|
1499
|
+
# if no exceptions, then turn self._requires_state_check 'off'
|
|
1500
|
+
self._requires_state_check = False
|
|
1501
|
+
|
|
1502
|
+
def getSparsity(self) -> float:
|
|
1503
|
+
"""
|
|
1504
|
+
Get the sparsity of the symbol w.r.t the cardinality
|
|
1505
|
+
|
|
1506
|
+
Returns
|
|
1507
|
+
-------
|
|
1508
|
+
float
|
|
1509
|
+
Sparsity of the symbol w.r.t the cardinality
|
|
1510
|
+
"""
|
|
1511
|
+
if self.domain_type in {"relaxed", "none"}:
|
|
1512
|
+
return float("nan")
|
|
1513
|
+
elif self.domain_type == "regular":
|
|
1514
|
+
if not self.isValid():
|
|
1515
|
+
raise Exception(
|
|
1516
|
+
f"Cannot calculate getSparsity because `{self.name}` is not a valid symbol object"
|
|
1517
|
+
"Use `<symbol>.isValid(verbose=True)` to debug further."
|
|
1518
|
+
)
|
|
1519
|
+
|
|
1520
|
+
# if there are any domain symbols that do not have records
|
|
1521
|
+
if any(not n.number_records for n in self.domain):
|
|
1522
|
+
return float("nan")
|
|
1523
|
+
else:
|
|
1524
|
+
dense = 1
|
|
1525
|
+
for i in [n.number_records for n in self.domain]:
|
|
1526
|
+
dense *= i
|
|
1527
|
+
|
|
1528
|
+
return 1 - self.number_records / dense
|