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,973 @@
|
|
|
1
|
+
#
|
|
2
|
+
# GAMS - General Algebraic Modeling System Python API
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2017-2026 GAMS Development Corp. <support@gams.com>
|
|
5
|
+
# Copyright (c) 2017-2026 GAMS Software GmbH <support@gams.com>
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
import copy
|
|
27
|
+
import itertools
|
|
28
|
+
import weakref
|
|
29
|
+
import pandas as pd
|
|
30
|
+
from pandas.api.types import (
|
|
31
|
+
CategoricalDtype,
|
|
32
|
+
infer_dtype,
|
|
33
|
+
)
|
|
34
|
+
import numpy as np
|
|
35
|
+
from gams.core import gdx
|
|
36
|
+
from gams.transfer._abcs import ABCParameter, ABCSet, ABCContainer
|
|
37
|
+
from gams.transfer.syms._mixins import PVEMixin, SAPVEMixin, SAUAPVEMixin, SPVEMixin
|
|
38
|
+
from gams.transfer._internals import (
|
|
39
|
+
generate_unique_labels,
|
|
40
|
+
cartesian_product,
|
|
41
|
+
EPS,
|
|
42
|
+
SpecialValues,
|
|
43
|
+
UNDEF,
|
|
44
|
+
NA,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from gams.transfer.syms._mixins.pivot import PivotParameterMixin
|
|
48
|
+
from gams.transfer.syms._mixins.generateRecords import GenerateRecordsParameterMixin
|
|
49
|
+
from gams.transfer.syms._mixins.equals import EqualsParameterMixin
|
|
50
|
+
from typing import Any, Optional, Union, TYPE_CHECKING
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from gams.transfer import Container
|
|
54
|
+
|
|
55
|
+
class Parameter(
|
|
56
|
+
PVEMixin,
|
|
57
|
+
SAPVEMixin,
|
|
58
|
+
SAUAPVEMixin,
|
|
59
|
+
SPVEMixin,
|
|
60
|
+
PivotParameterMixin,
|
|
61
|
+
GenerateRecordsParameterMixin,
|
|
62
|
+
EqualsParameterMixin,
|
|
63
|
+
ABCParameter,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Represents a parameter symbol in GAMS. https://www.gams.com/latest/docs/UG_DataEntry.html#UG_DataEntry_Parameters
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
container : Container
|
|
71
|
+
name : str
|
|
72
|
+
domain : list, optional
|
|
73
|
+
records : Any, optional
|
|
74
|
+
domain_forwarding : bool, optional
|
|
75
|
+
description : str, optional
|
|
76
|
+
|
|
77
|
+
Examples
|
|
78
|
+
--------
|
|
79
|
+
>>> import gams.transfer as gt
|
|
80
|
+
>>> m = gt.Container()
|
|
81
|
+
>>> i = gt.Set(m, "i", records=['i1','i2'])
|
|
82
|
+
>>> a = gt.Parameter(m, "a", [i], records=[['i1',1],['i2',2]])
|
|
83
|
+
|
|
84
|
+
Attributes
|
|
85
|
+
----------
|
|
86
|
+
container : Container object
|
|
87
|
+
Container where the symbol exists
|
|
88
|
+
description : str
|
|
89
|
+
description of symbol
|
|
90
|
+
dimension : int
|
|
91
|
+
The dimension of symbol
|
|
92
|
+
domain : List[Set | Alias | str]
|
|
93
|
+
List of domains given either as string (* for universe set) or as reference to the Set/Alias object
|
|
94
|
+
domain_forwarding : bool
|
|
95
|
+
Flag that identifies if domain forwarding is enabled for the symbol
|
|
96
|
+
domain_labels : List[str]
|
|
97
|
+
The column headings for the records DataFrame
|
|
98
|
+
domain_names : List[str]
|
|
99
|
+
String version of domain names
|
|
100
|
+
domain_type : str
|
|
101
|
+
The state of domain links
|
|
102
|
+
is_scalar : bool
|
|
103
|
+
Flag that identifies if the Parameter is scalar
|
|
104
|
+
modified: bool
|
|
105
|
+
Flag that identifies if the symbol has been modified
|
|
106
|
+
name : str
|
|
107
|
+
Name of the symbol
|
|
108
|
+
number_records : int
|
|
109
|
+
The number of symbol records
|
|
110
|
+
records : DataFrame
|
|
111
|
+
The main symbol records
|
|
112
|
+
shape : tuple
|
|
113
|
+
Shape of symbol records
|
|
114
|
+
summary : dict
|
|
115
|
+
A dict of only the metadata
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def _from_gams(cls, container, name, domain, records=None, description=""):
|
|
120
|
+
# create new symbol object
|
|
121
|
+
obj = Parameter.__new__(cls)
|
|
122
|
+
|
|
123
|
+
# set private properties directly
|
|
124
|
+
obj._requires_state_check = False
|
|
125
|
+
obj._container = weakref.proxy(container)
|
|
126
|
+
obj._name = name
|
|
127
|
+
obj._domain = domain
|
|
128
|
+
obj._domain_forwarding = False
|
|
129
|
+
obj._description = description
|
|
130
|
+
obj._records = records
|
|
131
|
+
obj._modified = True
|
|
132
|
+
|
|
133
|
+
# typing
|
|
134
|
+
obj._gams_type = gdx.GMS_DT_PAR
|
|
135
|
+
obj._gams_subtype = 0
|
|
136
|
+
|
|
137
|
+
# add to container
|
|
138
|
+
obj._container.data.update({name: obj})
|
|
139
|
+
obj._container._requires_state_check = True
|
|
140
|
+
|
|
141
|
+
return obj
|
|
142
|
+
|
|
143
|
+
def __new__(cls, *args, **kwargs):
|
|
144
|
+
# fastpath
|
|
145
|
+
if len(args) == len(kwargs) == 0:
|
|
146
|
+
return object.__new__(cls)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
container = args[0]
|
|
150
|
+
except IndexError:
|
|
151
|
+
container = kwargs.get("container", None)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
name = args[1]
|
|
155
|
+
except IndexError:
|
|
156
|
+
name = kwargs.get("name", None)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
symobj = container[name]
|
|
160
|
+
except (KeyError, IndexError, TypeError):
|
|
161
|
+
symobj = None
|
|
162
|
+
|
|
163
|
+
if symobj is None:
|
|
164
|
+
return object.__new__(cls)
|
|
165
|
+
else:
|
|
166
|
+
if isinstance(symobj, cls):
|
|
167
|
+
return symobj
|
|
168
|
+
else:
|
|
169
|
+
raise TypeError(
|
|
170
|
+
f"Cannot overwrite symbol '{symobj.name}' in container because it is not a {cls.__name__} object"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
container: "Container",
|
|
176
|
+
name: str,
|
|
177
|
+
domain: Optional[list] = None,
|
|
178
|
+
records: Optional[Any] = None,
|
|
179
|
+
domain_forwarding: bool = False,
|
|
180
|
+
description: str = "",
|
|
181
|
+
uels_on_axes: bool = False,
|
|
182
|
+
):
|
|
183
|
+
# domain handling
|
|
184
|
+
if domain is None:
|
|
185
|
+
domain = []
|
|
186
|
+
|
|
187
|
+
if isinstance(domain, (ABCSet, str)):
|
|
188
|
+
domain = [domain]
|
|
189
|
+
|
|
190
|
+
# does symbol exist
|
|
191
|
+
has_symbol = False
|
|
192
|
+
if isinstance(getattr(self, "container", None), ABCContainer):
|
|
193
|
+
has_symbol = True
|
|
194
|
+
|
|
195
|
+
if has_symbol:
|
|
196
|
+
try:
|
|
197
|
+
if any(
|
|
198
|
+
d1 != d2 for d1, d2 in itertools.zip_longest(self.domain, domain)
|
|
199
|
+
):
|
|
200
|
+
raise ValueError(
|
|
201
|
+
"Cannot overwrite symbol in container unless symbol domains are equal"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if self.domain_forwarding != domain_forwarding:
|
|
205
|
+
raise ValueError(
|
|
206
|
+
"Cannot overwrite symbol in container unless 'domain_forwarding' is left unchanged"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
except ValueError as err:
|
|
210
|
+
raise ValueError(err)
|
|
211
|
+
|
|
212
|
+
except TypeError as err:
|
|
213
|
+
raise TypeError(err)
|
|
214
|
+
|
|
215
|
+
# reset some properties
|
|
216
|
+
self._requires_state_check = True
|
|
217
|
+
self.container._requires_state_check = True
|
|
218
|
+
if description != "":
|
|
219
|
+
self.description = description
|
|
220
|
+
self.records = None
|
|
221
|
+
self.modified = True
|
|
222
|
+
|
|
223
|
+
# only set records if records are provided
|
|
224
|
+
if records is not None:
|
|
225
|
+
self.setRecords(records, uels_on_axes=uels_on_axes)
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
# populate new symbol properties
|
|
229
|
+
self._requires_state_check = True
|
|
230
|
+
self.container = container
|
|
231
|
+
self.container._requires_state_check = True
|
|
232
|
+
self.name = name
|
|
233
|
+
self.domain = domain
|
|
234
|
+
self.domain_forwarding = domain_forwarding
|
|
235
|
+
self.description = description
|
|
236
|
+
self.records = None
|
|
237
|
+
self.modified = True
|
|
238
|
+
|
|
239
|
+
# typing
|
|
240
|
+
self._gams_type = gdx.GMS_DT_PAR
|
|
241
|
+
self._gams_subtype = 0
|
|
242
|
+
|
|
243
|
+
# only set records if records are provided
|
|
244
|
+
if records is not None:
|
|
245
|
+
self.setRecords(records, uels_on_axes=uels_on_axes)
|
|
246
|
+
|
|
247
|
+
# add to container
|
|
248
|
+
container.data.update({name: self})
|
|
249
|
+
|
|
250
|
+
def __repr__(self):
|
|
251
|
+
return f"<Parameter `{self.name}` ({hex(id(self))})>"
|
|
252
|
+
|
|
253
|
+
def __delitem__(self):
|
|
254
|
+
del self.container.data[self.name]
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def _attributes(self):
|
|
258
|
+
return ["value"]
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def summary(self) -> dict:
|
|
262
|
+
"""
|
|
263
|
+
Returns a dict of only the metadata
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
dict
|
|
268
|
+
Outputs a dict of only the metadata
|
|
269
|
+
"""
|
|
270
|
+
return {
|
|
271
|
+
"name": self.name,
|
|
272
|
+
"description": self.description,
|
|
273
|
+
"domain": self.domain_names,
|
|
274
|
+
"domain_type": self.domain_type,
|
|
275
|
+
"dimension": self.dimension,
|
|
276
|
+
"number_records": self.number_records,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
def toValue(self) -> Union[float, None]:
|
|
280
|
+
"""
|
|
281
|
+
Convenience method to return symbol records as a python float. Only possible with scalar symbols.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
float | None
|
|
286
|
+
Scalar's record, None if no record was assigned
|
|
287
|
+
"""
|
|
288
|
+
from gams.transfer.syms._methods.toValue import toValueParameter
|
|
289
|
+
|
|
290
|
+
if not self.isValid():
|
|
291
|
+
raise Exception(
|
|
292
|
+
f"Cannot extract value because `{self.name}` is not a valid symbol object. "
|
|
293
|
+
f"Use `{self.name}.isValid(verbose=True)` to debug."
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return toValueParameter(self)
|
|
297
|
+
|
|
298
|
+
def toList(self) -> Union[list, None]:
|
|
299
|
+
"""
|
|
300
|
+
Convenience method to return symbol records as a python list
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
list | None
|
|
305
|
+
A list of symbol records, None if no records were assigned
|
|
306
|
+
"""
|
|
307
|
+
from gams.transfer.syms._methods.toList import toListParameter
|
|
308
|
+
|
|
309
|
+
if not self.isValid():
|
|
310
|
+
raise Exception(
|
|
311
|
+
f"Cannot extract list because `{self.name}` is not a valid symbol object. "
|
|
312
|
+
f"Use `{self.name}.isValid(verbose=True)` to debug."
|
|
313
|
+
)
|
|
314
|
+
return toListParameter(self)
|
|
315
|
+
|
|
316
|
+
def toDict(self, orient: Optional[str] = None) -> Union[dict, None]:
|
|
317
|
+
"""
|
|
318
|
+
convenience method to return symbol records as a python dictionary, orient can take values natural or columns and will control the shape of the dict.
|
|
319
|
+
Must use orient="columns" if attempting to set symbol records with setRecords
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
orient : str, optional
|
|
324
|
+
Takes 'natural' or 'columns', by default None which sets it to 'natural'.
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
dict | None
|
|
329
|
+
A dictionary with symbol records, None if no records were assigned
|
|
330
|
+
|
|
331
|
+
Examples
|
|
332
|
+
--------
|
|
333
|
+
>>> m = gt.Container()
|
|
334
|
+
>>> j = gt.Set(m, "j", records=["new-york", "chicago", "topeka"])
|
|
335
|
+
>>> s = gt.Parameter(m, "s", [j], records=np.array([3,4,5]))
|
|
336
|
+
>>> print(s.toDict(orient="natural"))
|
|
337
|
+
{'new-york': 3.0, 'chicago': 4.0, 'topeka': 5.0}
|
|
338
|
+
>>> print(s.toDict(orient="columns"))
|
|
339
|
+
{'j': {0: 'new-york', 1: 'chicago', 2: 'topeka'}, 'value': {0: 3.0, 1: 4.0, 2: 5.0}}
|
|
340
|
+
"""
|
|
341
|
+
from gams.transfer.syms._methods.toDict import toDictParameter
|
|
342
|
+
|
|
343
|
+
if not self.isValid():
|
|
344
|
+
raise Exception(
|
|
345
|
+
f"Cannot extract dict because `{self.name}` is not a valid symbol object. "
|
|
346
|
+
f"Use `{self.name}.isValid(verbose=True)` to debug."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return toDictParameter(self, orient=orient)
|
|
350
|
+
|
|
351
|
+
def dropZeros(self) -> None:
|
|
352
|
+
"""
|
|
353
|
+
Main convenience method to remove zero values from the symbol's records.
|
|
354
|
+
"""
|
|
355
|
+
mask = (self.records.iloc[:, -1] == 0.0) & (
|
|
356
|
+
~SpecialValues.isEps(self.records.iloc[:, -1])
|
|
357
|
+
)
|
|
358
|
+
self.records = self.records.loc[~mask, :].reset_index(drop=True)
|
|
359
|
+
|
|
360
|
+
def dropDefaults(self) -> None:
|
|
361
|
+
"""
|
|
362
|
+
Main convenience method to remove zero values from the symbol's records.
|
|
363
|
+
"""
|
|
364
|
+
self.dropZeros()
|
|
365
|
+
|
|
366
|
+
def dropEps(self) -> None:
|
|
367
|
+
"""
|
|
368
|
+
Main convenience method to remove epsilon values from the symbol's records.
|
|
369
|
+
"""
|
|
370
|
+
mask = pd.Series(SpecialValues.isEps(self.records.iloc[:, -1]), dtype=bool)
|
|
371
|
+
self.records = self.records[~mask].reset_index(drop=True)
|
|
372
|
+
|
|
373
|
+
def dropNA(self) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Main convenience method to remove NA (Not Available) values from the symbol's records.
|
|
376
|
+
"""
|
|
377
|
+
mask = pd.Series(SpecialValues.isNA(self.records.iloc[:, -1]), dtype=bool)
|
|
378
|
+
self.records = self.records[~mask].reset_index(drop=True)
|
|
379
|
+
|
|
380
|
+
def dropUndef(self) -> None:
|
|
381
|
+
"""
|
|
382
|
+
Main convenience method to remove undefined values from the symbol's records.
|
|
383
|
+
"""
|
|
384
|
+
mask = pd.Series(SpecialValues.isUndef(self.records.iloc[:, -1]), dtype=bool)
|
|
385
|
+
self.records = self.records[~mask].reset_index(drop=True)
|
|
386
|
+
|
|
387
|
+
def dropMissing(self) -> None:
|
|
388
|
+
"""
|
|
389
|
+
Main convenience method to remove missing values from the symbol's records.
|
|
390
|
+
"""
|
|
391
|
+
mask = pd.Series(pd.isna(self.records.iloc[:, -1]), dtype=bool)
|
|
392
|
+
self.records = self.records[~mask].reset_index(drop=True)
|
|
393
|
+
|
|
394
|
+
def setRecords(self, records: Any, uels_on_axes: bool = False) -> None:
|
|
395
|
+
"""
|
|
396
|
+
main convenience method to set standard pandas.DataFrame formatted records.
|
|
397
|
+
If uels_on_axes=True setRecords will assume that all domain information is contained in the axes of the pandas object – data will be flattened (if necessary).
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
records : Any
|
|
402
|
+
uels_on_axes : bool, optional
|
|
403
|
+
"""
|
|
404
|
+
if not isinstance(uels_on_axes, bool):
|
|
405
|
+
raise TypeError("Argument 'uels_on_axes' must be type bool.")
|
|
406
|
+
|
|
407
|
+
if isinstance(records, (int, float)):
|
|
408
|
+
self._from_int_float(records)
|
|
409
|
+
|
|
410
|
+
elif isinstance(records, np.ndarray):
|
|
411
|
+
self._from_ndarray(records)
|
|
412
|
+
|
|
413
|
+
elif isinstance(records, pd.DataFrame):
|
|
414
|
+
self._from_dataframe(records, uels_on_axes=uels_on_axes)
|
|
415
|
+
|
|
416
|
+
elif isinstance(records, pd.Series):
|
|
417
|
+
self._from_series(records)
|
|
418
|
+
|
|
419
|
+
else:
|
|
420
|
+
self._from_else(records)
|
|
421
|
+
|
|
422
|
+
def _from_series(self, records: pd.Series):
|
|
423
|
+
from gams.transfer.syms._methods.tables import (
|
|
424
|
+
_assert_axes_no_nans,
|
|
425
|
+
_get_implied_dimension_from_axes,
|
|
426
|
+
_flatten_and_convert,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
records = copy.deepcopy(records)
|
|
430
|
+
|
|
431
|
+
# check if index has NaNs
|
|
432
|
+
try:
|
|
433
|
+
_assert_axes_no_nans(records)
|
|
434
|
+
except Exception as err:
|
|
435
|
+
raise err
|
|
436
|
+
|
|
437
|
+
# check dimensionality of data
|
|
438
|
+
if self.is_scalar:
|
|
439
|
+
if records.size == 1:
|
|
440
|
+
records = pd.DataFrame(records)
|
|
441
|
+
self._from_dataframe(records)
|
|
442
|
+
|
|
443
|
+
else:
|
|
444
|
+
raise Exception(
|
|
445
|
+
f"Attempting to set records for a scalar symbol but records.size > 1. "
|
|
446
|
+
"pandas.Series must have size exactly = 1 before setting records. "
|
|
447
|
+
"(Note: pandas.Series.index is ignored for scalar symbols)"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
else:
|
|
451
|
+
dim = _get_implied_dimension_from_axes(records)
|
|
452
|
+
if dim != self.dimension:
|
|
453
|
+
raise Exception(
|
|
454
|
+
f"Dimensionality of data ({dim}) is inconsistent "
|
|
455
|
+
f"with domain specification ({self.dimension})"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
# flatten and convert to categorical
|
|
459
|
+
records = _flatten_and_convert(records)
|
|
460
|
+
|
|
461
|
+
# remap special values (str -> float)
|
|
462
|
+
records = self._remap_str_special_values(records)
|
|
463
|
+
|
|
464
|
+
# convert data column to type float
|
|
465
|
+
if not isinstance(records.iloc[:, -1].dtype, float):
|
|
466
|
+
records.isetitem(-1, records.iloc[:, -1].astype(float))
|
|
467
|
+
|
|
468
|
+
# reset columns
|
|
469
|
+
records.columns = (
|
|
470
|
+
generate_unique_labels(self.domain_names) + self._attributes
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
# set records
|
|
474
|
+
self.records = records
|
|
475
|
+
|
|
476
|
+
def _from_dataframe(self, records: pd.DataFrame, uels_on_axes: bool = False):
|
|
477
|
+
if self.is_scalar:
|
|
478
|
+
self._from_flat_dataframe(records)
|
|
479
|
+
|
|
480
|
+
else:
|
|
481
|
+
if uels_on_axes:
|
|
482
|
+
self._from_table_dataframe(records)
|
|
483
|
+
else:
|
|
484
|
+
self._from_flat_dataframe(records)
|
|
485
|
+
|
|
486
|
+
def _from_flat_dataframe(self, records: pd.DataFrame):
|
|
487
|
+
records = pd.DataFrame(copy.deepcopy(records))
|
|
488
|
+
|
|
489
|
+
# check dimensionality of data
|
|
490
|
+
r, c = records.shape
|
|
491
|
+
if c - 1 != self.dimension:
|
|
492
|
+
raise Exception(
|
|
493
|
+
f"Dimensionality of records ({c - 1}) is inconsistent "
|
|
494
|
+
f"with parameter domain specification ({self.dimension})"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
if self.is_scalar and r > 1:
|
|
498
|
+
raise Exception(
|
|
499
|
+
f"Attempting to set {r} records for a scalar symbol. "
|
|
500
|
+
f"Must define a domain for symbol `{self.name}` in order to set multiple records."
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# keep user defined categories if provided
|
|
504
|
+
for i in range(self.dimension):
|
|
505
|
+
# create categorical
|
|
506
|
+
if not isinstance(records.iloc[:, i].dtype, CategoricalDtype):
|
|
507
|
+
records.isetitem(
|
|
508
|
+
i,
|
|
509
|
+
records.iloc[:, i].astype(
|
|
510
|
+
CategoricalDtype(
|
|
511
|
+
categories=records.iloc[:, i].unique(),
|
|
512
|
+
ordered=True,
|
|
513
|
+
)
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
# capture user categories
|
|
518
|
+
old_cats = records.iloc[:, i].cat.categories.tolist()
|
|
519
|
+
is_ordered = records.iloc[:, i].cat.ordered
|
|
520
|
+
|
|
521
|
+
# convert any non-str categories to str, strip trailing white-space and de-dup
|
|
522
|
+
new_cats = list(dict.fromkeys(list(map(str.rstrip, map(str, old_cats)))))
|
|
523
|
+
|
|
524
|
+
# if categories are not unique after strip then need to remake the categorical
|
|
525
|
+
if len(old_cats) != len(new_cats):
|
|
526
|
+
# convert data to str, strip white-space and make categorical
|
|
527
|
+
records.isetitem(
|
|
528
|
+
i,
|
|
529
|
+
records.iloc[:, i]
|
|
530
|
+
.astype(str)
|
|
531
|
+
.map(str.rstrip)
|
|
532
|
+
.astype(CategoricalDtype(categories=new_cats, ordered=is_ordered)),
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
else:
|
|
536
|
+
# only need to rename the categories
|
|
537
|
+
records.isetitem(i, records.iloc[:, i].cat.rename_categories(new_cats))
|
|
538
|
+
|
|
539
|
+
# remap special values (str -> float)
|
|
540
|
+
records = self._remap_str_special_values(records)
|
|
541
|
+
|
|
542
|
+
# convert data column to type float if needed
|
|
543
|
+
if not isinstance(records.iloc[:, -1].dtype, float):
|
|
544
|
+
records.isetitem(-1, records.iloc[:, -1].astype(float))
|
|
545
|
+
|
|
546
|
+
# reset columns
|
|
547
|
+
records.columns = (
|
|
548
|
+
generate_unique_labels(records.columns[: self.dimension].tolist())
|
|
549
|
+
+ self._attributes
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# set records
|
|
553
|
+
self.records = records
|
|
554
|
+
|
|
555
|
+
def _from_table_dataframe(self, records: pd.DataFrame):
|
|
556
|
+
from gams.transfer.syms._methods.tables import (
|
|
557
|
+
_assert_axes_no_nans,
|
|
558
|
+
_get_implied_dimension_from_axes,
|
|
559
|
+
_flatten_and_convert,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
records = pd.DataFrame(copy.deepcopy(records))
|
|
563
|
+
|
|
564
|
+
# check if index has NaNs
|
|
565
|
+
try:
|
|
566
|
+
_assert_axes_no_nans(records)
|
|
567
|
+
except Exception as err:
|
|
568
|
+
raise err
|
|
569
|
+
|
|
570
|
+
# check dimensionality of data
|
|
571
|
+
dim = _get_implied_dimension_from_axes(records)
|
|
572
|
+
if dim != self.dimension:
|
|
573
|
+
raise Exception(
|
|
574
|
+
f"Dimensionality of table ({dim}) is inconsistent "
|
|
575
|
+
f"with parameter domain specification ({self.dimension})"
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# flatten and convert to categorical
|
|
579
|
+
records = _flatten_and_convert(records)
|
|
580
|
+
|
|
581
|
+
# remap special values (str -> float)
|
|
582
|
+
records = self._remap_str_special_values(records)
|
|
583
|
+
|
|
584
|
+
# convert data column to type float
|
|
585
|
+
if not isinstance(records.iloc[:, -1].dtype, float):
|
|
586
|
+
records.isetitem(-1, records.iloc[:, -1].astype(float))
|
|
587
|
+
|
|
588
|
+
# reset column names
|
|
589
|
+
records.columns = generate_unique_labels(self.domain_names) + self._attributes
|
|
590
|
+
|
|
591
|
+
# set records
|
|
592
|
+
self.records = records
|
|
593
|
+
|
|
594
|
+
def _from_int_float(self, records: Union[int, float]):
|
|
595
|
+
if not self.is_scalar:
|
|
596
|
+
raise Exception(
|
|
597
|
+
"Attempting to set a record with a scalar value, however the "
|
|
598
|
+
"symbol is not currently defined as a scalar (i.e., <symbol>.is_scalar == False)"
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# note we do not drop zeros when setting
|
|
602
|
+
records = pd.DataFrame([records], dtype=float, columns=self._attributes)
|
|
603
|
+
|
|
604
|
+
# set records
|
|
605
|
+
self.records = records
|
|
606
|
+
|
|
607
|
+
def _from_ndarray(self, records: np.ndarray):
|
|
608
|
+
try:
|
|
609
|
+
records = np.array(records, dtype=float)
|
|
610
|
+
except Exception as err:
|
|
611
|
+
raise Exception(
|
|
612
|
+
f"Attempted conversion to numpy array (dtype=float) failed. Reason {err}"
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
# user flexibility for (n,1) and (1,n) arrays (auto reshape)
|
|
616
|
+
if self.dimension == 1 and (
|
|
617
|
+
records.shape == (1, records.size) or records.shape == (records.size, 1)
|
|
618
|
+
):
|
|
619
|
+
records = records.reshape((records.size,))
|
|
620
|
+
|
|
621
|
+
# check dimension of array and symbol
|
|
622
|
+
if records.ndim != self.dimension:
|
|
623
|
+
raise Exception(
|
|
624
|
+
f"Attempting to set records for a {self.dimension}-dimensional "
|
|
625
|
+
f"symbol with a numpy array that is {records.ndim}-dimensional "
|
|
626
|
+
"-- array reshape necessary. (Note: gams.transfer will auto "
|
|
627
|
+
"reshape array if symbol is 1D and array is either (1,n) or (n,1))"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
# records must have regular domain_type if not a scalar
|
|
631
|
+
if records.ndim > 0 and self.domain_type != "regular":
|
|
632
|
+
raise Exception(
|
|
633
|
+
"Data conversion for non-scalar array (i.e., matrix) format into "
|
|
634
|
+
"records is only possible for symbols where "
|
|
635
|
+
"self.domain_type = `regular`. "
|
|
636
|
+
"Must define symbol with specific domain set objects, "
|
|
637
|
+
f"symbol domain_type is currently `{self.domain_type}`."
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
# make sure array has the proper shape
|
|
641
|
+
if records.shape != self.shape:
|
|
642
|
+
raise Exception(
|
|
643
|
+
f"User passed array with shape `{records.shape}` but anticipated "
|
|
644
|
+
f"shape was `{self.shape}` based "
|
|
645
|
+
"on domain set information -- "
|
|
646
|
+
"must reconcile before array-to-records conversion is possible."
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# check that all domains are valid
|
|
650
|
+
for i in self.domain:
|
|
651
|
+
if not i.isValid():
|
|
652
|
+
raise Exception(
|
|
653
|
+
f"Domain set `{i.name}` is invalid and cannot "
|
|
654
|
+
"be used to convert array-to-records. "
|
|
655
|
+
"Use `<symbol>.isValid(verbose=True)` to debug "
|
|
656
|
+
"this domain set symbol before proceeding."
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# create array of codes
|
|
660
|
+
codes = [np.arange(len(d.getUELs(ignore_unused=True))) for d in self.domain]
|
|
661
|
+
|
|
662
|
+
# create dataframe
|
|
663
|
+
if self.is_scalar:
|
|
664
|
+
df = pd.DataFrame(index=[0])
|
|
665
|
+
else:
|
|
666
|
+
df = pd.DataFrame(cartesian_product(*tuple(codes)))
|
|
667
|
+
|
|
668
|
+
# create categoricals
|
|
669
|
+
for n, d in enumerate(self.domain):
|
|
670
|
+
dtype = CategoricalDtype(
|
|
671
|
+
categories=d.records.iloc[:, 0].cat.categories,
|
|
672
|
+
ordered=d.records.iloc[:, 0].cat.ordered,
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
df.isetitem(
|
|
676
|
+
n, pd.Categorical.from_codes(codes=df.iloc[:, n], dtype=dtype)
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# insert matrix elements
|
|
680
|
+
df["value"] = records.reshape(-1, 1)
|
|
681
|
+
|
|
682
|
+
# drop zeros and reset index
|
|
683
|
+
df = self._filter_zero_records(df)
|
|
684
|
+
|
|
685
|
+
# reset column names
|
|
686
|
+
df.columns = generate_unique_labels(self.domain_names) + self._attributes
|
|
687
|
+
|
|
688
|
+
# set records
|
|
689
|
+
self.records = df
|
|
690
|
+
|
|
691
|
+
def _from_else(self, records: Any):
|
|
692
|
+
try:
|
|
693
|
+
records = pd.DataFrame(records)
|
|
694
|
+
except Exception as err:
|
|
695
|
+
raise Exception(
|
|
696
|
+
"Data structure passed as argument 'records' could not be "
|
|
697
|
+
f"successfully converted into a pandas DataFrame (reason: {err})."
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# check dimensionality of data
|
|
701
|
+
r, c = records.shape
|
|
702
|
+
if c - 1 != self.dimension:
|
|
703
|
+
raise Exception(
|
|
704
|
+
f"Dimensionality of records ({c - 1}) is inconsistent "
|
|
705
|
+
f"with parameter domain specification ({self.dimension})"
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
if self.is_scalar and r > 1:
|
|
709
|
+
raise Exception(
|
|
710
|
+
f"Attempting to set {r} values for a scalar symbol. "
|
|
711
|
+
f"Must define a domain for symbol `{self.name}` in order to set multiple records."
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
# keep user defined categories if provided
|
|
715
|
+
for i in range(self.dimension):
|
|
716
|
+
# create categorical
|
|
717
|
+
if not isinstance(records.iloc[:, i].dtype, CategoricalDtype):
|
|
718
|
+
records.isetitem(
|
|
719
|
+
i,
|
|
720
|
+
records.iloc[:, i].astype(
|
|
721
|
+
CategoricalDtype(
|
|
722
|
+
categories=records.iloc[:, i].unique(),
|
|
723
|
+
ordered=True,
|
|
724
|
+
)
|
|
725
|
+
),
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# capture user categories
|
|
729
|
+
old_cats = records.iloc[:, i].cat.categories.tolist()
|
|
730
|
+
is_ordered = records.iloc[:, i].cat.ordered
|
|
731
|
+
|
|
732
|
+
# convert any non-str categories to str, strip trailing white-space and de-dup
|
|
733
|
+
new_cats = list(dict.fromkeys(list(map(str.rstrip, map(str, old_cats)))))
|
|
734
|
+
|
|
735
|
+
# if categories are not unique after strip then need to remake the categorical
|
|
736
|
+
if len(old_cats) != len(new_cats):
|
|
737
|
+
# convert data to str, strip white-space and make categorical
|
|
738
|
+
records.isetitem(
|
|
739
|
+
i,
|
|
740
|
+
records.iloc[:, i]
|
|
741
|
+
.astype(str)
|
|
742
|
+
.map(str.rstrip)
|
|
743
|
+
.astype(CategoricalDtype(categories=new_cats, ordered=is_ordered)),
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
else:
|
|
747
|
+
# only need to rename the categories
|
|
748
|
+
records.isetitem(i, records.iloc[:, i].cat.rename_categories(new_cats))
|
|
749
|
+
|
|
750
|
+
# remap special values
|
|
751
|
+
records = self._remap_str_special_values(records)
|
|
752
|
+
|
|
753
|
+
# convert data column to type float if needed
|
|
754
|
+
if records.iloc[:, -1].dtype != float:
|
|
755
|
+
records.isetitem(-1, records.iloc[:, -1].astype(float))
|
|
756
|
+
|
|
757
|
+
# reset columns
|
|
758
|
+
records.columns = generate_unique_labels(self.domain_names) + self._attributes
|
|
759
|
+
|
|
760
|
+
# set records
|
|
761
|
+
self.records = records
|
|
762
|
+
|
|
763
|
+
def toSparseCoo(self) -> Optional["coo_matrix"]:
|
|
764
|
+
"""
|
|
765
|
+
Convert symbol to a sparse COOrdinate numpy.array format
|
|
766
|
+
|
|
767
|
+
Returns
|
|
768
|
+
-------
|
|
769
|
+
coo_matrix | None
|
|
770
|
+
"""
|
|
771
|
+
from scipy.sparse import coo_matrix
|
|
772
|
+
|
|
773
|
+
if not self.isValid():
|
|
774
|
+
raise Exception(
|
|
775
|
+
"Cannot create sparse array (i.e., coo_matrix) because symbol "
|
|
776
|
+
"is invalid -- use `<symbol>.isValid(verbose=True)` to debug symbol state."
|
|
777
|
+
)
|
|
778
|
+
if self.domain_type == "regular":
|
|
779
|
+
if self.hasDomainViolations():
|
|
780
|
+
raise Exception(
|
|
781
|
+
"Cannot create sparse array because there are domain violations "
|
|
782
|
+
"(i.e., UELs in the symbol are not a subset of UELs contained in domain sets)."
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
if self.records is not None:
|
|
786
|
+
if self.is_scalar:
|
|
787
|
+
row = [0]
|
|
788
|
+
col = [0]
|
|
789
|
+
m = 1
|
|
790
|
+
n = 1
|
|
791
|
+
|
|
792
|
+
elif self.dimension == 1:
|
|
793
|
+
if self.domain_type == "regular":
|
|
794
|
+
col = (
|
|
795
|
+
self.records.iloc[:, 0]
|
|
796
|
+
.map(self.domain[0]._getUELCodes(0, ignore_unused=True))
|
|
797
|
+
.to_numpy(dtype=int)
|
|
798
|
+
)
|
|
799
|
+
else:
|
|
800
|
+
col = self.records.iloc[:, 0].cat.codes.to_numpy(dtype=int)
|
|
801
|
+
|
|
802
|
+
row = np.zeros(len(col), dtype=int)
|
|
803
|
+
m, *n = self.shape
|
|
804
|
+
assert n == []
|
|
805
|
+
n = m
|
|
806
|
+
m = 1
|
|
807
|
+
|
|
808
|
+
elif self.dimension == 2:
|
|
809
|
+
if self.domain_type == "regular":
|
|
810
|
+
row = (
|
|
811
|
+
self.records.iloc[:, 0]
|
|
812
|
+
.map(self.domain[0]._getUELCodes(0, ignore_unused=True))
|
|
813
|
+
.to_numpy(dtype=int)
|
|
814
|
+
)
|
|
815
|
+
col = (
|
|
816
|
+
self.records.iloc[:, 1]
|
|
817
|
+
.map(self.domain[1]._getUELCodes(0, ignore_unused=True))
|
|
818
|
+
.to_numpy(dtype=int)
|
|
819
|
+
)
|
|
820
|
+
else:
|
|
821
|
+
row = self.records.iloc[:, 0].cat.codes.to_numpy(dtype=int)
|
|
822
|
+
col = self.records.iloc[:, 1].cat.codes.to_numpy(dtype=int)
|
|
823
|
+
|
|
824
|
+
m, n = self.shape
|
|
825
|
+
else:
|
|
826
|
+
raise Exception(
|
|
827
|
+
"Sparse coo_matrix formats are only "
|
|
828
|
+
"available for data that has dimension <= 2"
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
return coo_matrix(
|
|
832
|
+
(self.records.iloc[:, -1].to_numpy(dtype=float), (row, col)),
|
|
833
|
+
shape=(m, n),
|
|
834
|
+
dtype=float,
|
|
835
|
+
)
|
|
836
|
+
else:
|
|
837
|
+
return None
|
|
838
|
+
|
|
839
|
+
def toDense(self) -> Union[np.ndarray, None]:
|
|
840
|
+
"""
|
|
841
|
+
Convert symbol to a dense numpy.array format
|
|
842
|
+
|
|
843
|
+
Returns
|
|
844
|
+
-------
|
|
845
|
+
ndarray | None
|
|
846
|
+
A numpy array with symbol records, None if no records were assigned
|
|
847
|
+
|
|
848
|
+
Examples
|
|
849
|
+
--------
|
|
850
|
+
>>> m = gt.Container()
|
|
851
|
+
>>> j = gt.Set(m, "j", records=["new-york", "chicago", "topeka"])
|
|
852
|
+
>>> s = gt.Parameter(m, "s", [j], records=np.array([3,4,5]))
|
|
853
|
+
>>> print(s.toDense())
|
|
854
|
+
[3. 4. 5.]
|
|
855
|
+
"""
|
|
856
|
+
if not self.isValid():
|
|
857
|
+
raise Exception(
|
|
858
|
+
"Cannot create dense array (i.e., matrix) format because symbol "
|
|
859
|
+
"is invalid -- use `<symbol>.isValid(verbose=True)` to debug symbol state."
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
if self.records is not None:
|
|
863
|
+
if self.is_scalar:
|
|
864
|
+
return self.records.to_numpy(dtype=float).reshape(self.shape)
|
|
865
|
+
else:
|
|
866
|
+
#
|
|
867
|
+
#
|
|
868
|
+
# checks
|
|
869
|
+
if self.domain_type == "regular":
|
|
870
|
+
if self.hasDomainViolations():
|
|
871
|
+
raise Exception(
|
|
872
|
+
"Cannot create dense array because there are domain violations "
|
|
873
|
+
"(i.e., UELs in the symbol are not a subset of UELs contained in domain sets)."
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
# check order of domain UELs in categorical and order of domain UELs in data
|
|
877
|
+
for symobj in self.domain:
|
|
878
|
+
data_cats = symobj.records.iloc[:, 0].unique().tolist()
|
|
879
|
+
cats = symobj.records.iloc[:, 0].cat.categories.tolist()
|
|
880
|
+
|
|
881
|
+
if data_cats != cats[: len(data_cats)]:
|
|
882
|
+
raise Exception(
|
|
883
|
+
f"`toDense` requires that UEL data order of domain set `{symobj.name}` must be "
|
|
884
|
+
"equal be equal to UEL category order (i.e., the order that set elements "
|
|
885
|
+
"appear in rows of the dataframe and the order set elements are specified by the categorical). "
|
|
886
|
+
"Users can efficiently reorder their domain set UELs to data order with "
|
|
887
|
+
"the `reorderUELs()` method (no arguments) -- preexisting unused categories "
|
|
888
|
+
"will be appended (maintaining their order)."
|
|
889
|
+
)
|
|
890
|
+
else:
|
|
891
|
+
# check order of domain UELs in categorical and order of domain UELs in data
|
|
892
|
+
for n in range(self.dimension):
|
|
893
|
+
# check if any invalid codes
|
|
894
|
+
if any(
|
|
895
|
+
code == -1 for code in self.records.iloc[:, n].cat.codes
|
|
896
|
+
):
|
|
897
|
+
raise Exception(
|
|
898
|
+
f"Invalid category detected in dimension `{n}` (code == -1), "
|
|
899
|
+
"cannot create array until all categories are properly resolved"
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
data_cats = self.records.iloc[:, n].unique().tolist()
|
|
903
|
+
cats = self.records.iloc[:, n].cat.categories.tolist()
|
|
904
|
+
|
|
905
|
+
if data_cats != cats[: len(data_cats)]:
|
|
906
|
+
raise Exception(
|
|
907
|
+
f"`toDense` requires (for 'relaxed' symbols) that UEL data order must be "
|
|
908
|
+
"equal be equal to UEL category order (i.e., the order that set elements "
|
|
909
|
+
"appear in rows of the dataframe and the order set elements are specified by the categorical). "
|
|
910
|
+
"Users can efficiently reorder UELs to data order with "
|
|
911
|
+
"the `reorderUELs()` method (no arguments) -- preexisting unused categories "
|
|
912
|
+
"will be appended (maintaining their order)."
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
#
|
|
916
|
+
#
|
|
917
|
+
# create indexing scheme
|
|
918
|
+
if self.domain_type == "regular":
|
|
919
|
+
idx = [
|
|
920
|
+
self.records.iloc[:, n]
|
|
921
|
+
.map(domainobj._getUELCodes(0, ignore_unused=True))
|
|
922
|
+
.to_numpy(dtype=int)
|
|
923
|
+
for n, domainobj in enumerate(self.domain)
|
|
924
|
+
]
|
|
925
|
+
|
|
926
|
+
else:
|
|
927
|
+
idx = [
|
|
928
|
+
self.records.iloc[:, n].cat.codes.to_numpy(dtype=int)
|
|
929
|
+
for n, domainobj in enumerate(self.domain)
|
|
930
|
+
]
|
|
931
|
+
|
|
932
|
+
# fill the dense array
|
|
933
|
+
a = np.zeros(self.shape)
|
|
934
|
+
val = self.records.iloc[:, -1].to_numpy(dtype=float)
|
|
935
|
+
a[tuple(idx)] = val
|
|
936
|
+
|
|
937
|
+
return a
|
|
938
|
+
else:
|
|
939
|
+
return None
|
|
940
|
+
|
|
941
|
+
def _remap_str_special_values(self, records):
|
|
942
|
+
# convert str "eps", "na", & "undef" special value strings to float equivalents
|
|
943
|
+
if infer_dtype(records.iloc[:, -1]) not in [
|
|
944
|
+
"integer",
|
|
945
|
+
"floating",
|
|
946
|
+
"mixed-integer-float",
|
|
947
|
+
]:
|
|
948
|
+
idx = records.iloc[:, -1].isin(EPS)
|
|
949
|
+
if idx.any():
|
|
950
|
+
records.loc[
|
|
951
|
+
records[idx].index,
|
|
952
|
+
records.columns[-1],
|
|
953
|
+
] = SpecialValues.EPS
|
|
954
|
+
|
|
955
|
+
idx = records.iloc[:, -1].isin(UNDEF)
|
|
956
|
+
if idx.any():
|
|
957
|
+
records.loc[
|
|
958
|
+
records[idx].index,
|
|
959
|
+
records.columns[-1],
|
|
960
|
+
] = SpecialValues.UNDEF
|
|
961
|
+
|
|
962
|
+
idx = records.iloc[:, -1].isin(NA)
|
|
963
|
+
if idx.any():
|
|
964
|
+
records.loc[
|
|
965
|
+
records[idx].index,
|
|
966
|
+
records.columns[-1],
|
|
967
|
+
] = SpecialValues.NA
|
|
968
|
+
|
|
969
|
+
return records
|
|
970
|
+
|
|
971
|
+
def _filter_zero_records(self, records):
|
|
972
|
+
idx = (records.iloc[:, -1] == 0.0) & (~SpecialValues.isEps(records.iloc[:, -1]))
|
|
973
|
+
return records.loc[~idx, :].reset_index(drop=True)
|