gamspy 1.18.3__py3-none-any.whl → 1.19.0__py3-none-any.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.
- gamspy/__init__.py +86 -98
- gamspy/__main__.py +6 -6
- gamspy/_algebra/__init__.py +13 -13
- gamspy/_algebra/condition.py +290 -194
- gamspy/_algebra/domain.py +103 -93
- gamspy/_algebra/expression.py +820 -799
- gamspy/_algebra/number.py +79 -70
- gamspy/_algebra/operable.py +185 -185
- gamspy/_algebra/operation.py +948 -845
- gamspy/_backend/backend.py +313 -311
- gamspy/_backend/engine.py +960 -960
- gamspy/_backend/local.py +124 -124
- gamspy/_backend/neos.py +567 -567
- gamspy/_cli/__init__.py +1 -1
- gamspy/_cli/cli.py +64 -64
- gamspy/_cli/gdx.py +377 -377
- gamspy/_cli/install.py +375 -372
- gamspy/_cli/list.py +94 -94
- gamspy/_cli/mps2gms.py +128 -128
- gamspy/_cli/probe.py +52 -52
- gamspy/_cli/retrieve.py +79 -79
- gamspy/_cli/run.py +158 -158
- gamspy/_cli/show.py +246 -255
- gamspy/_cli/uninstall.py +165 -165
- gamspy/_cli/util.py +94 -94
- gamspy/_communication.py +215 -215
- gamspy/_config.py +132 -132
- gamspy/_container.py +1694 -1452
- gamspy/_convert.py +720 -720
- gamspy/_database.py +271 -271
- gamspy/_extrinsic.py +181 -181
- gamspy/_miro.py +356 -352
- gamspy/_model.py +1803 -1615
- gamspy/_model_instance.py +701 -701
- gamspy/_options.py +780 -700
- gamspy/_serialization.py +156 -144
- gamspy/_symbols/__init__.py +17 -17
- gamspy/_symbols/alias.py +305 -299
- gamspy/_symbols/equation.py +1407 -1298
- gamspy/_symbols/implicits/__init__.py +11 -11
- gamspy/_symbols/implicits/implicit_equation.py +186 -186
- gamspy/_symbols/implicits/implicit_parameter.py +272 -272
- gamspy/_symbols/implicits/implicit_set.py +124 -124
- gamspy/_symbols/implicits/implicit_symbol.py +315 -315
- gamspy/_symbols/implicits/implicit_variable.py +255 -255
- gamspy/_symbols/parameter.py +648 -609
- gamspy/_symbols/set.py +985 -923
- gamspy/_symbols/symbol.py +395 -386
- gamspy/_symbols/universe_alias.py +182 -182
- gamspy/_symbols/variable.py +1101 -1017
- gamspy/_types.py +7 -7
- gamspy/_validation.py +735 -735
- gamspy/_workspace.py +72 -72
- gamspy/exceptions.py +128 -128
- gamspy/formulations/__init__.py +46 -46
- gamspy/formulations/ml/__init__.py +11 -11
- gamspy/formulations/ml/decision_tree_struct.py +80 -80
- gamspy/formulations/ml/gradient_boosting.py +203 -203
- gamspy/formulations/ml/random_forest.py +187 -187
- gamspy/formulations/ml/regression_tree.py +533 -533
- gamspy/formulations/nn/__init__.py +19 -19
- gamspy/formulations/nn/avgpool2d.py +232 -232
- gamspy/formulations/nn/conv1d.py +533 -533
- gamspy/formulations/nn/conv2d.py +529 -529
- gamspy/formulations/nn/linear.py +341 -341
- gamspy/formulations/nn/maxpool2d.py +88 -88
- gamspy/formulations/nn/minpool2d.py +88 -88
- gamspy/formulations/nn/mpool2d.py +245 -245
- gamspy/formulations/nn/torch_sequential.py +278 -278
- gamspy/formulations/piecewise.py +682 -682
- gamspy/formulations/result.py +119 -119
- gamspy/formulations/shape.py +188 -188
- gamspy/formulations/utils.py +173 -173
- gamspy/math/__init__.py +215 -215
- gamspy/math/activation.py +783 -767
- gamspy/math/log_power.py +435 -435
- gamspy/math/matrix.py +534 -534
- gamspy/math/misc.py +1709 -1625
- gamspy/math/probability.py +170 -170
- gamspy/math/trigonometric.py +232 -232
- gamspy/utils.py +810 -791
- gamspy/version.py +5 -5
- {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/METADATA +90 -121
- gamspy-1.19.0.dist-info/RECORD +90 -0
- {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/WHEEL +1 -1
- {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/licenses/LICENSE +22 -22
- gamspy-1.18.3.dist-info/RECORD +0 -90
- {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/entry_points.txt +0 -0
- {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/top_level.txt +0 -0
gamspy/_model_instance.py
CHANGED
|
@@ -1,701 +1,701 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
import time
|
|
7
|
-
import weakref
|
|
8
|
-
from collections.abc import Iterable
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
10
|
-
|
|
11
|
-
import gams.transfer as gt
|
|
12
|
-
import pandas as pd
|
|
13
|
-
from gams.core.cfg import GMS_SSSIZE
|
|
14
|
-
from gams.core.gev import (
|
|
15
|
-
gevCreateD,
|
|
16
|
-
gevFree,
|
|
17
|
-
gevGetLShandle,
|
|
18
|
-
gevGetStrOpt,
|
|
19
|
-
gevHandleToPtr,
|
|
20
|
-
gevInitEnvironmentLegacy,
|
|
21
|
-
gevNameLogFile,
|
|
22
|
-
gevNameStaFile,
|
|
23
|
-
gevRestoreLogStat,
|
|
24
|
-
gevRestoreLogStatRewrite,
|
|
25
|
-
gevSwitchLogStat,
|
|
26
|
-
new_gevHandle_tp,
|
|
27
|
-
)
|
|
28
|
-
from gams.core.gmd import (
|
|
29
|
-
GMD_NRUELS,
|
|
30
|
-
gmdCallSolver,
|
|
31
|
-
gmdCloseLicenseSession,
|
|
32
|
-
gmdInfo,
|
|
33
|
-
gmdInitFromDict,
|
|
34
|
-
gmdInitUpdate,
|
|
35
|
-
gmdUpdateModelSymbol,
|
|
36
|
-
)
|
|
37
|
-
from gams.core.gmo import (
|
|
38
|
-
gmoCreateD,
|
|
39
|
-
gmoFree,
|
|
40
|
-
gmoGetHeadnTail,
|
|
41
|
-
gmoHandleToPtr,
|
|
42
|
-
gmoHdomused,
|
|
43
|
-
gmoHetalg,
|
|
44
|
-
gmoHiterused,
|
|
45
|
-
gmoHmarginals,
|
|
46
|
-
gmoHobjval,
|
|
47
|
-
gmoHresused,
|
|
48
|
-
gmoLoadDataLegacy,
|
|
49
|
-
gmoModelStat,
|
|
50
|
-
gmoNameOptFileSet,
|
|
51
|
-
gmoOptFileSet,
|
|
52
|
-
gmoRegisterEnvironment,
|
|
53
|
-
gmoSolveStat,
|
|
54
|
-
gmoTmipbest,
|
|
55
|
-
gmoTmipnod,
|
|
56
|
-
new_gmoHandle_tp,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
import gamspy as gp
|
|
60
|
-
import gamspy._symbols.implicits as implicits
|
|
61
|
-
import gamspy.utils as utils
|
|
62
|
-
from gamspy._communication import send_job
|
|
63
|
-
from gamspy._database import (
|
|
64
|
-
Database,
|
|
65
|
-
GamsEquation,
|
|
66
|
-
GamsParameter,
|
|
67
|
-
GamsVariable,
|
|
68
|
-
)
|
|
69
|
-
from gamspy.exceptions import (
|
|
70
|
-
GamspyException,
|
|
71
|
-
ValidationError,
|
|
72
|
-
_customize_exception,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
if TYPE_CHECKING:
|
|
76
|
-
import io
|
|
77
|
-
from pathlib import Path
|
|
78
|
-
|
|
79
|
-
from gamspy import Container, Model, Parameter
|
|
80
|
-
from gamspy._options import FreezeOptions, Options
|
|
81
|
-
from gamspy._symbols.implicits import ImplicitParameter
|
|
82
|
-
|
|
83
|
-
logger = logging.getLogger("FROZEN MODEL")
|
|
84
|
-
logger.setLevel(logging.INFO)
|
|
85
|
-
stream_handler = logging.StreamHandler()
|
|
86
|
-
stream_handler.setLevel(logging.INFO)
|
|
87
|
-
formatter = logging.Formatter("[%(name)s - %(levelname)s] %(message)s")
|
|
88
|
-
stream_handler.setFormatter(formatter)
|
|
89
|
-
logger.addHandler(stream_handler)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
VARIABLE_MAP = {
|
|
93
|
-
"binary": 1,
|
|
94
|
-
"integer": 2,
|
|
95
|
-
"positive": 3,
|
|
96
|
-
"negative": 4,
|
|
97
|
-
"free": 5,
|
|
98
|
-
"sos1": 6,
|
|
99
|
-
"sos2": 7,
|
|
100
|
-
"semicont": 8,
|
|
101
|
-
"semiint": 9,
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
EQUATION_MAP = {
|
|
106
|
-
"eq": 0,
|
|
107
|
-
"geq": 1,
|
|
108
|
-
"leq": 2,
|
|
109
|
-
"nonbinding": 3,
|
|
110
|
-
"external": 4,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
UPDATE_ACTION_MAP = {
|
|
114
|
-
"up": 1,
|
|
115
|
-
"lo": 2,
|
|
116
|
-
"fx": 3,
|
|
117
|
-
"l": 4,
|
|
118
|
-
"m": 5,
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
UPDATE_TYPE_MAP = {
|
|
122
|
-
"0": 0,
|
|
123
|
-
"base_case": 1,
|
|
124
|
-
"accumulate": 2,
|
|
125
|
-
"inherit": 3,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class GamsModifier:
|
|
130
|
-
def __init__(
|
|
131
|
-
self,
|
|
132
|
-
gams_symbol: GamsParameter | GamsVariable | GamsEquation,
|
|
133
|
-
update_action: int | None = None,
|
|
134
|
-
data_symbol: GamsParameter | None = None,
|
|
135
|
-
update_type: int = 3,
|
|
136
|
-
):
|
|
137
|
-
self.update_action = None
|
|
138
|
-
self.gams_symbol = gams_symbol
|
|
139
|
-
self.update_type = update_type
|
|
140
|
-
self.data_symbol = None
|
|
141
|
-
|
|
142
|
-
# update_action and data_symbol specified
|
|
143
|
-
if update_action is not None and data_symbol is not None:
|
|
144
|
-
self._validate_update_action(update_action)
|
|
145
|
-
|
|
146
|
-
self.update_action = update_action
|
|
147
|
-
self.data_symbol = data_symbol
|
|
148
|
-
# only the gams_symbol is specified
|
|
149
|
-
elif update_action is None and data_symbol is None:
|
|
150
|
-
...
|
|
151
|
-
else:
|
|
152
|
-
raise GamspyException(
|
|
153
|
-
"Wrong combination of parameters. Specifying only update_action or data_symbol is not allowed."
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
def _validate_update_action(self, update_action: int):
|
|
157
|
-
if update_action in (1, 2, 3):
|
|
158
|
-
if not isinstance(self.gams_symbol, GamsVariable):
|
|
159
|
-
raise GamspyException(
|
|
160
|
-
f"GAMS Symbol must be GAMSVariable for {update_action}"
|
|
161
|
-
)
|
|
162
|
-
elif update_action in (4, 5):
|
|
163
|
-
if not (isinstance(self.gams_symbol, (GamsVariable, GamsEquation))):
|
|
164
|
-
raise GamspyException(
|
|
165
|
-
f"GAMS Symbol must be GAMSVariable or GAMSEquation for {update_action}"
|
|
166
|
-
)
|
|
167
|
-
else:
|
|
168
|
-
raise GamspyException(f"Unknown update action {update_action}")
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class ModelInstance:
|
|
172
|
-
"""
|
|
173
|
-
ModelInstance class provides a controlled way of modifying a model instance
|
|
174
|
-
and solving the resulting problem in the most efficient way, by communicating
|
|
175
|
-
only the changes of the model to the solver and doing a hot start (in case of
|
|
176
|
-
a continuous model like LP) without the use of disk IO.
|
|
177
|
-
|
|
178
|
-
Parameters
|
|
179
|
-
----------
|
|
180
|
-
container : Container
|
|
181
|
-
model : Model
|
|
182
|
-
modifiables : list[Parameter | ImplicitParameter]
|
|
183
|
-
freeze_options : Options | None, optional
|
|
184
|
-
"""
|
|
185
|
-
|
|
186
|
-
def __init__(
|
|
187
|
-
self,
|
|
188
|
-
container: Container,
|
|
189
|
-
model: Model,
|
|
190
|
-
modifiables: list[Parameter | ImplicitParameter],
|
|
191
|
-
freeze_options: Options,
|
|
192
|
-
output: io.TextIOWrapper | None,
|
|
193
|
-
):
|
|
194
|
-
self.container = container
|
|
195
|
-
self.job_name = container._job
|
|
196
|
-
self.gms_file = self.job_name + ".gms"
|
|
197
|
-
self.lst_file = self.job_name + ".lst"
|
|
198
|
-
self.pf_file = self.job_name + ".pf"
|
|
199
|
-
self.trace_file = self.job_name + ".txt"
|
|
200
|
-
self.solver_control_file = os.path.join(
|
|
201
|
-
self.container._process_directory, "gamscntr.dat"
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
self.model = model
|
|
205
|
-
self.output = output
|
|
206
|
-
assert self.model._is_frozen
|
|
207
|
-
|
|
208
|
-
self.modifiables = self._init_modifiables(modifiables)
|
|
209
|
-
self.instance_container = gt.Container(
|
|
210
|
-
system_directory=container.system_directory,
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
self.workspace = container._workspace
|
|
214
|
-
self.sync_db = Database(self.workspace)
|
|
215
|
-
|
|
216
|
-
self._gev = new_gevHandle_tp()
|
|
217
|
-
ret = gevCreateD(self._gev, container.system_directory, GMS_SSSIZE)
|
|
218
|
-
if not ret[0]:
|
|
219
|
-
raise GamspyException(ret[1])
|
|
220
|
-
|
|
221
|
-
self._gmo = new_gmoHandle_tp()
|
|
222
|
-
ret = gmoCreateD(self._gmo, container.system_directory, GMS_SSSIZE)
|
|
223
|
-
if not ret[0]:
|
|
224
|
-
raise GamspyException(ret[1])
|
|
225
|
-
|
|
226
|
-
self.modifiers = self._create_modifiers()
|
|
227
|
-
self.instantiate(model, freeze_options)
|
|
228
|
-
|
|
229
|
-
# preallocate summary frame for performance reasons
|
|
230
|
-
HEADER = [
|
|
231
|
-
"Solver Status",
|
|
232
|
-
"Model Status",
|
|
233
|
-
"Objective",
|
|
234
|
-
"Solver",
|
|
235
|
-
"Solver Time",
|
|
236
|
-
]
|
|
237
|
-
self.summary = pd.DataFrame(index=range(1), columns=HEADER)
|
|
238
|
-
|
|
239
|
-
weakref.finalize(self, self.cleanup, self._gmo, self._gev)
|
|
240
|
-
|
|
241
|
-
@staticmethod
|
|
242
|
-
def cleanup(gmo, gev) -> None:
|
|
243
|
-
gmoFree(gmo)
|
|
244
|
-
gevFree(gev)
|
|
245
|
-
|
|
246
|
-
def close_license_session(self) -> None:
|
|
247
|
-
gmdCloseLicenseSession(self.sync_db.gmd)
|
|
248
|
-
|
|
249
|
-
def _create_modifiers(self) -> list[GamsModifier]:
|
|
250
|
-
modifiers = []
|
|
251
|
-
|
|
252
|
-
for symbol in self.modifiables:
|
|
253
|
-
if isinstance(symbol, gp.Parameter):
|
|
254
|
-
domain = ["*"] * symbol.dimension
|
|
255
|
-
_ = gt.Parameter(self.instance_container, symbol.name, domain)
|
|
256
|
-
modifiers.append(
|
|
257
|
-
GamsModifier(
|
|
258
|
-
self.sync_db.add_parameter(
|
|
259
|
-
symbol.name,
|
|
260
|
-
symbol.dimension,
|
|
261
|
-
symbol.description,
|
|
262
|
-
)
|
|
263
|
-
)
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
elif isinstance(symbol, implicits.ImplicitParameter):
|
|
267
|
-
attribute = symbol.name.split(".")[-1]
|
|
268
|
-
update_action = UPDATE_ACTION_MAP[attribute]
|
|
269
|
-
|
|
270
|
-
try:
|
|
271
|
-
sync_db_symbol = self.sync_db[symbol.parent.name]
|
|
272
|
-
except KeyError:
|
|
273
|
-
if isinstance(symbol.parent, gp.Variable):
|
|
274
|
-
sync_db_symbol = self.sync_db.add_variable(
|
|
275
|
-
symbol.parent.name,
|
|
276
|
-
symbol.parent.dimension,
|
|
277
|
-
VARIABLE_MAP[symbol.parent.type],
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
elif isinstance(symbol.parent, gp.Equation):
|
|
281
|
-
sync_db_symbol = self.sync_db.add_equation(
|
|
282
|
-
symbol.parent.name,
|
|
283
|
-
symbol.parent.dimension,
|
|
284
|
-
EQUATION_MAP[symbol.parent.type],
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
attr_name = "_".join(symbol.name.split("."))
|
|
288
|
-
|
|
289
|
-
domain = ["*"] * symbol.parent.dimension
|
|
290
|
-
_ = gt.Parameter(
|
|
291
|
-
self.instance_container,
|
|
292
|
-
attr_name,
|
|
293
|
-
domain=domain,
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
data_symbol = self.sync_db.add_parameter(
|
|
297
|
-
attr_name,
|
|
298
|
-
symbol.parent.dimension,
|
|
299
|
-
)
|
|
300
|
-
modifiers.append(
|
|
301
|
-
GamsModifier(sync_db_symbol, update_action, data_symbol)
|
|
302
|
-
)
|
|
303
|
-
else:
|
|
304
|
-
raise ValidationError(
|
|
305
|
-
f"Symbol type {type(symbol)} cannot be modified in a frozen solve"
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
return modifiers
|
|
309
|
-
|
|
310
|
-
def instantiate(self, model: Model, options: Options) -> None:
|
|
311
|
-
# Check the gmd state.
|
|
312
|
-
rc, _, _, _ = gmdInfo(self.sync_db.gmd, GMD_NRUELS)
|
|
313
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
314
|
-
|
|
315
|
-
# Prepare the required lines to solve with model instance
|
|
316
|
-
scenario_str = self._get_scenario(model)
|
|
317
|
-
with open(self.gms_file, "w", encoding="utf-8") as gams_file:
|
|
318
|
-
gams_file.write(scenario_str)
|
|
319
|
-
|
|
320
|
-
# Write pf file
|
|
321
|
-
|
|
322
|
-
options.
|
|
323
|
-
options.log_file = os.path.join(self.container.working_directory, "gamslog.dat")
|
|
324
|
-
options._export(self.pf_file, self.output)
|
|
325
|
-
|
|
326
|
-
# Run
|
|
327
|
-
try:
|
|
328
|
-
self.container._job = self.job_name
|
|
329
|
-
send_job(
|
|
330
|
-
self.container._comm_pair_id, self.job_name, self.pf_file, self.output
|
|
331
|
-
)
|
|
332
|
-
except GamspyException as exception:
|
|
333
|
-
self.container._workspace._errors.append(str(exception))
|
|
334
|
-
message = _customize_exception(
|
|
335
|
-
options,
|
|
336
|
-
self.job_name,
|
|
337
|
-
exception.return_code,
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
exception.args = (exception.message + message,)
|
|
341
|
-
raise exception
|
|
342
|
-
finally:
|
|
343
|
-
self.container._unsaved_statements = []
|
|
344
|
-
|
|
345
|
-
# Init environments
|
|
346
|
-
if gevInitEnvironmentLegacy(self._gev, self.solver_control_file) != 0:
|
|
347
|
-
raise GamspyException("Could not initialize model instance")
|
|
348
|
-
|
|
349
|
-
gmoRegisterEnvironment(self._gmo, gevHandleToPtr(self._gev))
|
|
350
|
-
ret = gmoLoadDataLegacy(self._gmo)
|
|
351
|
-
if ret[0] != 0:
|
|
352
|
-
raise GamspyException(f"Could not load model instance: {ret[1]}")
|
|
353
|
-
|
|
354
|
-
rc = gmdInitFromDict(self.sync_db.gmd, gmoHandleToPtr(self._gmo))
|
|
355
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
356
|
-
|
|
357
|
-
def solve(
|
|
358
|
-
self,
|
|
359
|
-
solver: str,
|
|
360
|
-
instance_options: FreezeOptions,
|
|
361
|
-
solver_options: dict | Path | None,
|
|
362
|
-
output: io.TextIOWrapper | None,
|
|
363
|
-
) -> pd.DataFrame:
|
|
364
|
-
# write solver options file
|
|
365
|
-
option_file = 1 if solver_options else 0
|
|
366
|
-
|
|
367
|
-
names_to_write = []
|
|
368
|
-
for symbol in self.modifiables:
|
|
369
|
-
if isinstance(symbol, gp.Parameter):
|
|
370
|
-
self.instance_container[symbol.name].records = self.container[
|
|
371
|
-
symbol.name
|
|
372
|
-
].records
|
|
373
|
-
names_to_write.append(symbol.name)
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
isinstance(symbol, implicits.ImplicitParameter)
|
|
377
|
-
and symbol.parent.records is not None
|
|
378
|
-
):
|
|
379
|
-
parent_name, attr = symbol.name.split(".")
|
|
380
|
-
attr_name = "_".join([parent_name, attr])
|
|
381
|
-
|
|
382
|
-
columns = self._get_columns_to_drop(attr)
|
|
383
|
-
|
|
384
|
-
self.instance_container[attr_name].setRecords(
|
|
385
|
-
self.container[parent_name].records.drop(columns, axis=1)
|
|
386
|
-
)
|
|
387
|
-
names_to_write.append(attr_name)
|
|
388
|
-
|
|
389
|
-
self.instance_container.write(
|
|
390
|
-
self.sync_db.gmd, symbols=names_to_write, eps_to_zero=False
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
### Legacy code from GAMS Control. TODO: Pay the technical debt of the following legacy code. ###
|
|
394
|
-
rc = gmdInitUpdate(self.sync_db.gmd, gmoHandleToPtr(self._gmo))
|
|
395
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
396
|
-
|
|
397
|
-
# Update gmd
|
|
398
|
-
start = time.perf_counter()
|
|
399
|
-
accumulate_no_match_cnt = 0
|
|
400
|
-
no_match_cnt = 0
|
|
401
|
-
|
|
402
|
-
for modifier in self.modifiers:
|
|
403
|
-
update_type = UPDATE_TYPE_MAP[instance_options.update_type]
|
|
404
|
-
if modifier.update_type != 3:
|
|
405
|
-
update_type = modifier.update_type
|
|
406
|
-
|
|
407
|
-
if isinstance(modifier.gams_symbol, GamsParameter):
|
|
408
|
-
rc, no_match_cnt = gmdUpdateModelSymbol(
|
|
409
|
-
self.sync_db.gmd,
|
|
410
|
-
modifier.gams_symbol.sym_ptr,
|
|
411
|
-
0,
|
|
412
|
-
modifier.gams_symbol.sym_ptr,
|
|
413
|
-
update_type,
|
|
414
|
-
no_match_cnt,
|
|
415
|
-
)
|
|
416
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
417
|
-
else:
|
|
418
|
-
rc, no_match_cnt = gmdUpdateModelSymbol(
|
|
419
|
-
self.sync_db.gmd,
|
|
420
|
-
modifier.gams_symbol.sym_ptr,
|
|
421
|
-
modifier.update_action,
|
|
422
|
-
modifier.data_symbol.sym_ptr, # type: ignore
|
|
423
|
-
update_type,
|
|
424
|
-
no_match_cnt,
|
|
425
|
-
)
|
|
426
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
427
|
-
|
|
428
|
-
accumulate_no_match_cnt += no_match_cnt
|
|
429
|
-
if accumulate_no_match_cnt > instance_options.no_match_limit:
|
|
430
|
-
raise GamspyException(
|
|
431
|
-
f"Unmatched record limit exceeded while processing modifier {modifier.gams_symbol.name}, for more info check no_match_limit option."
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
model_generation_time = time.perf_counter() - start
|
|
435
|
-
|
|
436
|
-
# Close Log and status file and remove
|
|
437
|
-
if output:
|
|
438
|
-
gevSwitchLogStat(self._gev, 0, "", False, "", False, None, None, None)
|
|
439
|
-
ls_handle = gevGetLShandle(self._gev)
|
|
440
|
-
gevRestoreLogStatRewrite(self._gev, ls_handle)
|
|
441
|
-
|
|
442
|
-
if output == sys.stdout:
|
|
443
|
-
gevSwitchLogStat(
|
|
444
|
-
self._gev,
|
|
445
|
-
3,
|
|
446
|
-
gevGetStrOpt(self._gev, gevNameLogFile),
|
|
447
|
-
False,
|
|
448
|
-
gevGetStrOpt(self._gev, gevNameStaFile),
|
|
449
|
-
False,
|
|
450
|
-
None,
|
|
451
|
-
None,
|
|
452
|
-
ls_handle,
|
|
453
|
-
)
|
|
454
|
-
ls_handle = gevGetLShandle(self._gev)
|
|
455
|
-
|
|
456
|
-
if instance_options is not None and instance_options.debug:
|
|
457
|
-
with open(
|
|
458
|
-
os.path.join(self.workspace.working_directory, "convert.opt"),
|
|
459
|
-
"w",
|
|
460
|
-
) as opt_file:
|
|
461
|
-
opt_file.writelines(
|
|
462
|
-
[
|
|
463
|
-
"gams "
|
|
464
|
-
+ os.path.join(self.workspace.working_directory, "gams.gms"),
|
|
465
|
-
"dumpgdx "
|
|
466
|
-
+ os.path.join(self.workspace.working_directory, "dump.gdx\n"),
|
|
467
|
-
"dictmap "
|
|
468
|
-
+ os.path.join(self.workspace.working_directory, "dictmap.gdx"),
|
|
469
|
-
]
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
gmoOptFileSet(self._gmo, 1)
|
|
473
|
-
gmoNameOptFileSet(
|
|
474
|
-
self._gmo,
|
|
475
|
-
os.path.join(self.workspace.working_directory, "convert.opt"),
|
|
476
|
-
)
|
|
477
|
-
rc = gmdCallSolver(self.sync_db.gmd, "convert")
|
|
478
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
479
|
-
|
|
480
|
-
gmoOptFileSet(self._gmo, option_file)
|
|
481
|
-
gmoNameOptFileSet(
|
|
482
|
-
self._gmo,
|
|
483
|
-
os.path.join(self.workspace.working_directory, solver.lower() + ".opt"),
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
rc = gmdCallSolver(self.sync_db.gmd, solver)
|
|
487
|
-
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
488
|
-
|
|
489
|
-
if output == sys.stdout:
|
|
490
|
-
gevRestoreLogStat(self._gev, ls_handle)
|
|
491
|
-
|
|
492
|
-
if output is not None and output != sys.stdout:
|
|
493
|
-
gevSwitchLogStat(self._gev, 0, "", False, "", False, None, None, ls_handle)
|
|
494
|
-
ls_handle = gevGetLShandle(self._gev)
|
|
495
|
-
with open(gevGetStrOpt(self._gev, gevNameLogFile)) as file:
|
|
496
|
-
for line in file.readlines():
|
|
497
|
-
output.write(line)
|
|
498
|
-
gevRestoreLogStat(self._gev, ls_handle)
|
|
499
|
-
### end of the legacy code ###
|
|
500
|
-
|
|
501
|
-
self._update_main_container()
|
|
502
|
-
|
|
503
|
-
# update model attributes
|
|
504
|
-
from gamspy._model import INTERRUPT_STATUS
|
|
505
|
-
|
|
506
|
-
self.model._status = gp.ModelStatus(gmoModelStat(self._gmo))
|
|
507
|
-
self.model._solve_status = gp.SolveStatus(gmoSolveStat(self._gmo))
|
|
508
|
-
if self.model._solve_status in INTERRUPT_STATUS:
|
|
509
|
-
logger.warning(
|
|
510
|
-
f"The solve was interrupted! Solve status: {self.model._solve_status.name}. "
|
|
511
|
-
"For further information, see https://gamspy.readthedocs.io/en/latest/reference/gamspy._model.html#gamspy.SolveStatus."
|
|
512
|
-
)
|
|
513
|
-
self.model._model_generation_time = model_generation_time
|
|
514
|
-
self.model._solve_model_time = gmoGetHeadnTail(self._gmo, gmoHresused)
|
|
515
|
-
self.model._num_iterations = gmoGetHeadnTail(self._gmo, gmoHiterused)
|
|
516
|
-
self.model._marginals = gmoGetHeadnTail(self._gmo, gmoHmarginals)
|
|
517
|
-
self.model._algorithm_time = gmoGetHeadnTail(self._gmo, gmoHetalg)
|
|
518
|
-
self.model._objective_estimation = gmoGetHeadnTail(self._gmo, gmoTmipbest)
|
|
519
|
-
self.model._num_nodes_used = gmoGetHeadnTail(self._gmo, gmoTmipnod)
|
|
520
|
-
self.model._num_domain_violations = gmoGetHeadnTail(self._gmo, gmoHdomused)
|
|
521
|
-
self.model._objective_value = gmoGetHeadnTail(self._gmo, gmoHobjval)
|
|
522
|
-
self.summary.loc[0] = [
|
|
523
|
-
str(self.model._solve_status),
|
|
524
|
-
str(self.model._status),
|
|
525
|
-
self.model._objective_value,
|
|
526
|
-
solver,
|
|
527
|
-
self.model._solve_model_time,
|
|
528
|
-
]
|
|
529
|
-
|
|
530
|
-
return self.summary
|
|
531
|
-
|
|
532
|
-
def _get_scenario(self, model: Model) -> str:
|
|
533
|
-
auto_id = "s" + utils._get_unique_name()[:5]
|
|
534
|
-
params = [
|
|
535
|
-
modifier.gams_symbol
|
|
536
|
-
for modifier in self.modifiers
|
|
537
|
-
if isinstance(modifier.gams_symbol, GamsParameter)
|
|
538
|
-
]
|
|
539
|
-
lines = []
|
|
540
|
-
if params:
|
|
541
|
-
lines.append(f"Set {auto_id}__(*) /'s0'/;")
|
|
542
|
-
for symbol in params:
|
|
543
|
-
declaration = f"Parameter {auto_id}__{symbol.name}({auto_id}__"
|
|
544
|
-
domain = ""
|
|
545
|
-
if symbol.dimension:
|
|
546
|
-
domain = "," + ",".join("*" * symbol.dimension)
|
|
547
|
-
domain += ")"
|
|
548
|
-
|
|
549
|
-
declaration = f"{declaration}{domain};"
|
|
550
|
-
lines.append(declaration)
|
|
551
|
-
|
|
552
|
-
domain = f"({auto_id}__"
|
|
553
|
-
|
|
554
|
-
if symbol.dimension:
|
|
555
|
-
domain += ","
|
|
556
|
-
|
|
557
|
-
assign_str = f"{auto_id}__{symbol.name}({auto_id}__"
|
|
558
|
-
if symbol.dimension:
|
|
559
|
-
assign_str += "," + ",".join([f"{auto_id}__"] * symbol.dimension)
|
|
560
|
-
|
|
561
|
-
assign_str += ") = Eps;"
|
|
562
|
-
lines.append(assign_str)
|
|
563
|
-
|
|
564
|
-
scenario = f"Set {auto_id}_dict(*,*,*) / '{auto_id}__'.'scenario'.''"
|
|
565
|
-
for symbol in params:
|
|
566
|
-
scenario += f",\n'{symbol.name}'.'param'.'{auto_id}__{symbol.name}'"
|
|
567
|
-
scenario += "/;"
|
|
568
|
-
lines.append(scenario)
|
|
569
|
-
|
|
570
|
-
lines.append(f"{model.name}.justScrDir = 1;")
|
|
571
|
-
solve_string = model._generate_solve_string()
|
|
572
|
-
|
|
573
|
-
if params:
|
|
574
|
-
solve_string += f" scenario {auto_id}_dict"
|
|
575
|
-
|
|
576
|
-
solve_string += ";"
|
|
577
|
-
lines.append(solve_string)
|
|
578
|
-
|
|
579
|
-
return "\n".join(lines)
|
|
580
|
-
|
|
581
|
-
def _init_modifiables(
|
|
582
|
-
self, modifiables: list[Parameter | ImplicitParameter]
|
|
583
|
-
) -> list[Parameter | ImplicitParameter]:
|
|
584
|
-
if not isinstance(modifiables, Iterable):
|
|
585
|
-
raise ValidationError(
|
|
586
|
-
"Modifiables must be iterable (i.e. list, tuple etc.)."
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
if any(
|
|
590
|
-
not isinstance(symbol, (gp.Parameter, implicits.ImplicitParameter))
|
|
591
|
-
for symbol in modifiables
|
|
592
|
-
):
|
|
593
|
-
raise ValidationError(
|
|
594
|
-
"Type of a modifiable must be either Parameter or a Variable attribute (e.g. variable.up)"
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
symbols_in_conditions: list[str] = []
|
|
598
|
-
for equation in self.model.equations:
|
|
599
|
-
assert equation._definition is not None
|
|
600
|
-
symbols_in_conditions += equation._definition._find_symbols_in_conditions()
|
|
601
|
-
|
|
602
|
-
will_be_modified: list[Parameter | ImplicitParameter] = []
|
|
603
|
-
for symbol in modifiables:
|
|
604
|
-
if isinstance(symbol, implicits.ImplicitParameter):
|
|
605
|
-
if symbol.parent.name in symbols_in_conditions:
|
|
606
|
-
raise ValidationError(
|
|
607
|
-
f"Modifiable symbol `{symbol.parent.name}` cannot be in a condition."
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
attr_name = symbol.name.split(".")[-1]
|
|
611
|
-
|
|
612
|
-
# If the symbol attr is fx, then modify level, lower and upper.
|
|
613
|
-
if attr_name == "fx":
|
|
614
|
-
if not utils.isin(symbol.parent.l, will_be_modified):
|
|
615
|
-
will_be_modified.append(symbol.parent.l)
|
|
616
|
-
|
|
617
|
-
if not utils.isin(symbol.parent.lo, will_be_modified):
|
|
618
|
-
will_be_modified.append(symbol.parent.lo)
|
|
619
|
-
|
|
620
|
-
if not utils.isin(symbol.parent.up, will_be_modified):
|
|
621
|
-
will_be_modified.append(symbol.parent.up)
|
|
622
|
-
else:
|
|
623
|
-
# if fx already added level, lower or upper, do not add again.
|
|
624
|
-
if not utils.isin(symbol, will_be_modified):
|
|
625
|
-
will_be_modified.append(symbol)
|
|
626
|
-
else:
|
|
627
|
-
if symbol.name in symbols_in_conditions:
|
|
628
|
-
raise ValidationError(
|
|
629
|
-
f"Modifiable symbol `{symbol.name}` cannot be in a condition."
|
|
630
|
-
)
|
|
631
|
-
will_be_modified.append(symbol)
|
|
632
|
-
|
|
633
|
-
return will_be_modified
|
|
634
|
-
|
|
635
|
-
def
|
|
636
|
-
scrdir = self.container._process_directory
|
|
637
|
-
|
|
638
|
-
"trace": self.trace_file,
|
|
639
|
-
"input": self.gms_file,
|
|
640
|
-
"output": self.lst_file,
|
|
641
|
-
"optdir": self.container.working_directory,
|
|
642
|
-
"sysdir": self.container.system_directory,
|
|
643
|
-
"scrdir": scrdir,
|
|
644
|
-
"scriptnext": os.path.join(scrdir, "gamsnext.sh"),
|
|
645
|
-
"license": self.container._license_path,
|
|
646
|
-
"solvercntr": self.solver_control_file,
|
|
647
|
-
}
|
|
648
|
-
if self.container._network_license:
|
|
649
|
-
|
|
650
|
-
self.container._process_directory, "gamslice.dat"
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
return
|
|
654
|
-
|
|
655
|
-
def _get_columns_to_drop(self, attr: str) -> list[str]:
|
|
656
|
-
attr_map = {
|
|
657
|
-
"l": "level",
|
|
658
|
-
"m": "marginal",
|
|
659
|
-
"lo": "lower",
|
|
660
|
-
"up": "upper",
|
|
661
|
-
"scale": "scale",
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
columns = []
|
|
665
|
-
for key, value in attr_map.items():
|
|
666
|
-
if key != attr:
|
|
667
|
-
columns.append(value)
|
|
668
|
-
|
|
669
|
-
return columns
|
|
670
|
-
|
|
671
|
-
def _update_main_container(self) -> None:
|
|
672
|
-
temp = self.container._temp_container
|
|
673
|
-
temp.read(self.sync_db.gmd)
|
|
674
|
-
|
|
675
|
-
prev_state = self.container._options.miro_protect
|
|
676
|
-
for name in temp.data:
|
|
677
|
-
if name in self.container.data:
|
|
678
|
-
self.container._options.miro_protect = False
|
|
679
|
-
self.container[name].records = temp[name].records
|
|
680
|
-
self.container[name].domain_labels = self.container[name].domain_names
|
|
681
|
-
|
|
682
|
-
if name in (symbol.name for symbol in self.modifiables):
|
|
683
|
-
generated_var = name + "_var"
|
|
684
|
-
if generated_var not in self.container.data:
|
|
685
|
-
_ = gp.Variable(
|
|
686
|
-
self.container,
|
|
687
|
-
generated_var,
|
|
688
|
-
domain=self.container[name].domain,
|
|
689
|
-
records=temp[generated_var].records,
|
|
690
|
-
)
|
|
691
|
-
else:
|
|
692
|
-
self.container[generated_var]._records = temp[generated_var].records
|
|
693
|
-
|
|
694
|
-
self.container._options.miro_protect = prev_state
|
|
695
|
-
|
|
696
|
-
if self.model._objective_variable is not None:
|
|
697
|
-
self.model._objective_value = temp[
|
|
698
|
-
self.model._objective_variable.name
|
|
699
|
-
].toValue()
|
|
700
|
-
|
|
701
|
-
temp.data = {}
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import weakref
|
|
8
|
+
from collections.abc import Iterable
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import gams.transfer as gt
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from gams.core.cfg import GMS_SSSIZE
|
|
14
|
+
from gams.core.gev import (
|
|
15
|
+
gevCreateD,
|
|
16
|
+
gevFree,
|
|
17
|
+
gevGetLShandle,
|
|
18
|
+
gevGetStrOpt,
|
|
19
|
+
gevHandleToPtr,
|
|
20
|
+
gevInitEnvironmentLegacy,
|
|
21
|
+
gevNameLogFile,
|
|
22
|
+
gevNameStaFile,
|
|
23
|
+
gevRestoreLogStat,
|
|
24
|
+
gevRestoreLogStatRewrite,
|
|
25
|
+
gevSwitchLogStat,
|
|
26
|
+
new_gevHandle_tp,
|
|
27
|
+
)
|
|
28
|
+
from gams.core.gmd import (
|
|
29
|
+
GMD_NRUELS,
|
|
30
|
+
gmdCallSolver,
|
|
31
|
+
gmdCloseLicenseSession,
|
|
32
|
+
gmdInfo,
|
|
33
|
+
gmdInitFromDict,
|
|
34
|
+
gmdInitUpdate,
|
|
35
|
+
gmdUpdateModelSymbol,
|
|
36
|
+
)
|
|
37
|
+
from gams.core.gmo import (
|
|
38
|
+
gmoCreateD,
|
|
39
|
+
gmoFree,
|
|
40
|
+
gmoGetHeadnTail,
|
|
41
|
+
gmoHandleToPtr,
|
|
42
|
+
gmoHdomused,
|
|
43
|
+
gmoHetalg,
|
|
44
|
+
gmoHiterused,
|
|
45
|
+
gmoHmarginals,
|
|
46
|
+
gmoHobjval,
|
|
47
|
+
gmoHresused,
|
|
48
|
+
gmoLoadDataLegacy,
|
|
49
|
+
gmoModelStat,
|
|
50
|
+
gmoNameOptFileSet,
|
|
51
|
+
gmoOptFileSet,
|
|
52
|
+
gmoRegisterEnvironment,
|
|
53
|
+
gmoSolveStat,
|
|
54
|
+
gmoTmipbest,
|
|
55
|
+
gmoTmipnod,
|
|
56
|
+
new_gmoHandle_tp,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
import gamspy as gp
|
|
60
|
+
import gamspy._symbols.implicits as implicits
|
|
61
|
+
import gamspy.utils as utils
|
|
62
|
+
from gamspy._communication import send_job
|
|
63
|
+
from gamspy._database import (
|
|
64
|
+
Database,
|
|
65
|
+
GamsEquation,
|
|
66
|
+
GamsParameter,
|
|
67
|
+
GamsVariable,
|
|
68
|
+
)
|
|
69
|
+
from gamspy.exceptions import (
|
|
70
|
+
GamspyException,
|
|
71
|
+
ValidationError,
|
|
72
|
+
_customize_exception,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if TYPE_CHECKING:
|
|
76
|
+
import io
|
|
77
|
+
from pathlib import Path
|
|
78
|
+
|
|
79
|
+
from gamspy import Container, Model, Parameter
|
|
80
|
+
from gamspy._options import FreezeOptions, Options
|
|
81
|
+
from gamspy._symbols.implicits import ImplicitParameter
|
|
82
|
+
|
|
83
|
+
logger = logging.getLogger("FROZEN MODEL")
|
|
84
|
+
logger.setLevel(logging.INFO)
|
|
85
|
+
stream_handler = logging.StreamHandler()
|
|
86
|
+
stream_handler.setLevel(logging.INFO)
|
|
87
|
+
formatter = logging.Formatter("[%(name)s - %(levelname)s] %(message)s")
|
|
88
|
+
stream_handler.setFormatter(formatter)
|
|
89
|
+
logger.addHandler(stream_handler)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
VARIABLE_MAP = {
|
|
93
|
+
"binary": 1,
|
|
94
|
+
"integer": 2,
|
|
95
|
+
"positive": 3,
|
|
96
|
+
"negative": 4,
|
|
97
|
+
"free": 5,
|
|
98
|
+
"sos1": 6,
|
|
99
|
+
"sos2": 7,
|
|
100
|
+
"semicont": 8,
|
|
101
|
+
"semiint": 9,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
EQUATION_MAP = {
|
|
106
|
+
"eq": 0,
|
|
107
|
+
"geq": 1,
|
|
108
|
+
"leq": 2,
|
|
109
|
+
"nonbinding": 3,
|
|
110
|
+
"external": 4,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
UPDATE_ACTION_MAP = {
|
|
114
|
+
"up": 1,
|
|
115
|
+
"lo": 2,
|
|
116
|
+
"fx": 3,
|
|
117
|
+
"l": 4,
|
|
118
|
+
"m": 5,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
UPDATE_TYPE_MAP = {
|
|
122
|
+
"0": 0,
|
|
123
|
+
"base_case": 1,
|
|
124
|
+
"accumulate": 2,
|
|
125
|
+
"inherit": 3,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class GamsModifier:
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
gams_symbol: GamsParameter | GamsVariable | GamsEquation,
|
|
133
|
+
update_action: int | None = None,
|
|
134
|
+
data_symbol: GamsParameter | None = None,
|
|
135
|
+
update_type: int = 3,
|
|
136
|
+
):
|
|
137
|
+
self.update_action = None
|
|
138
|
+
self.gams_symbol = gams_symbol
|
|
139
|
+
self.update_type = update_type
|
|
140
|
+
self.data_symbol = None
|
|
141
|
+
|
|
142
|
+
# update_action and data_symbol specified
|
|
143
|
+
if update_action is not None and data_symbol is not None:
|
|
144
|
+
self._validate_update_action(update_action)
|
|
145
|
+
|
|
146
|
+
self.update_action = update_action
|
|
147
|
+
self.data_symbol = data_symbol
|
|
148
|
+
# only the gams_symbol is specified
|
|
149
|
+
elif update_action is None and data_symbol is None:
|
|
150
|
+
...
|
|
151
|
+
else:
|
|
152
|
+
raise GamspyException(
|
|
153
|
+
"Wrong combination of parameters. Specifying only update_action or data_symbol is not allowed."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _validate_update_action(self, update_action: int):
|
|
157
|
+
if update_action in (1, 2, 3):
|
|
158
|
+
if not isinstance(self.gams_symbol, GamsVariable):
|
|
159
|
+
raise GamspyException(
|
|
160
|
+
f"GAMS Symbol must be GAMSVariable for {update_action}"
|
|
161
|
+
)
|
|
162
|
+
elif update_action in (4, 5):
|
|
163
|
+
if not (isinstance(self.gams_symbol, (GamsVariable, GamsEquation))):
|
|
164
|
+
raise GamspyException(
|
|
165
|
+
f"GAMS Symbol must be GAMSVariable or GAMSEquation for {update_action}"
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
raise GamspyException(f"Unknown update action {update_action}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ModelInstance:
|
|
172
|
+
"""
|
|
173
|
+
ModelInstance class provides a controlled way of modifying a model instance
|
|
174
|
+
and solving the resulting problem in the most efficient way, by communicating
|
|
175
|
+
only the changes of the model to the solver and doing a hot start (in case of
|
|
176
|
+
a continuous model like LP) without the use of disk IO.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
container : Container
|
|
181
|
+
model : Model
|
|
182
|
+
modifiables : list[Parameter | ImplicitParameter]
|
|
183
|
+
freeze_options : Options | None, optional
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
container: Container,
|
|
189
|
+
model: Model,
|
|
190
|
+
modifiables: list[Parameter | ImplicitParameter],
|
|
191
|
+
freeze_options: Options,
|
|
192
|
+
output: io.TextIOWrapper | None,
|
|
193
|
+
):
|
|
194
|
+
self.container = container
|
|
195
|
+
self.job_name = container._job
|
|
196
|
+
self.gms_file = self.job_name + ".gms"
|
|
197
|
+
self.lst_file = self.job_name + ".lst"
|
|
198
|
+
self.pf_file = self.job_name + ".pf"
|
|
199
|
+
self.trace_file = self.job_name + ".txt"
|
|
200
|
+
self.solver_control_file = os.path.join(
|
|
201
|
+
self.container._process_directory, "gamscntr.dat"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self.model = model
|
|
205
|
+
self.output = output
|
|
206
|
+
assert self.model._is_frozen
|
|
207
|
+
|
|
208
|
+
self.modifiables = self._init_modifiables(modifiables)
|
|
209
|
+
self.instance_container = gt.Container(
|
|
210
|
+
system_directory=container.system_directory,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
self.workspace = container._workspace
|
|
214
|
+
self.sync_db = Database(self.workspace)
|
|
215
|
+
|
|
216
|
+
self._gev = new_gevHandle_tp()
|
|
217
|
+
ret = gevCreateD(self._gev, container.system_directory, GMS_SSSIZE)
|
|
218
|
+
if not ret[0]:
|
|
219
|
+
raise GamspyException(ret[1])
|
|
220
|
+
|
|
221
|
+
self._gmo = new_gmoHandle_tp()
|
|
222
|
+
ret = gmoCreateD(self._gmo, container.system_directory, GMS_SSSIZE)
|
|
223
|
+
if not ret[0]:
|
|
224
|
+
raise GamspyException(ret[1])
|
|
225
|
+
|
|
226
|
+
self.modifiers = self._create_modifiers()
|
|
227
|
+
self.instantiate(model, freeze_options)
|
|
228
|
+
|
|
229
|
+
# preallocate summary frame for performance reasons
|
|
230
|
+
HEADER = [
|
|
231
|
+
"Solver Status",
|
|
232
|
+
"Model Status",
|
|
233
|
+
"Objective",
|
|
234
|
+
"Solver",
|
|
235
|
+
"Solver Time",
|
|
236
|
+
]
|
|
237
|
+
self.summary = pd.DataFrame(index=range(1), columns=HEADER)
|
|
238
|
+
|
|
239
|
+
weakref.finalize(self, self.cleanup, self._gmo, self._gev)
|
|
240
|
+
|
|
241
|
+
@staticmethod
|
|
242
|
+
def cleanup(gmo, gev) -> None:
|
|
243
|
+
gmoFree(gmo)
|
|
244
|
+
gevFree(gev)
|
|
245
|
+
|
|
246
|
+
def close_license_session(self) -> None:
|
|
247
|
+
gmdCloseLicenseSession(self.sync_db.gmd)
|
|
248
|
+
|
|
249
|
+
def _create_modifiers(self) -> list[GamsModifier]:
|
|
250
|
+
modifiers = []
|
|
251
|
+
|
|
252
|
+
for symbol in self.modifiables:
|
|
253
|
+
if isinstance(symbol, gp.Parameter):
|
|
254
|
+
domain = ["*"] * symbol.dimension
|
|
255
|
+
_ = gt.Parameter(self.instance_container, symbol.name, domain)
|
|
256
|
+
modifiers.append(
|
|
257
|
+
GamsModifier(
|
|
258
|
+
self.sync_db.add_parameter(
|
|
259
|
+
symbol.name,
|
|
260
|
+
symbol.dimension,
|
|
261
|
+
symbol.description,
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
elif isinstance(symbol, implicits.ImplicitParameter):
|
|
267
|
+
attribute = symbol.name.split(".")[-1]
|
|
268
|
+
update_action = UPDATE_ACTION_MAP[attribute]
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
sync_db_symbol = self.sync_db[symbol.parent.name]
|
|
272
|
+
except KeyError:
|
|
273
|
+
if isinstance(symbol.parent, gp.Variable):
|
|
274
|
+
sync_db_symbol = self.sync_db.add_variable(
|
|
275
|
+
symbol.parent.name,
|
|
276
|
+
symbol.parent.dimension,
|
|
277
|
+
VARIABLE_MAP[symbol.parent.type],
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
elif isinstance(symbol.parent, gp.Equation):
|
|
281
|
+
sync_db_symbol = self.sync_db.add_equation(
|
|
282
|
+
symbol.parent.name,
|
|
283
|
+
symbol.parent.dimension,
|
|
284
|
+
EQUATION_MAP[symbol.parent.type],
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
attr_name = "_".join(symbol.name.split("."))
|
|
288
|
+
|
|
289
|
+
domain = ["*"] * symbol.parent.dimension
|
|
290
|
+
_ = gt.Parameter(
|
|
291
|
+
self.instance_container,
|
|
292
|
+
attr_name,
|
|
293
|
+
domain=domain,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
data_symbol = self.sync_db.add_parameter(
|
|
297
|
+
attr_name,
|
|
298
|
+
symbol.parent.dimension,
|
|
299
|
+
)
|
|
300
|
+
modifiers.append(
|
|
301
|
+
GamsModifier(sync_db_symbol, update_action, data_symbol)
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
raise ValidationError(
|
|
305
|
+
f"Symbol type {type(symbol)} cannot be modified in a frozen solve"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return modifiers
|
|
309
|
+
|
|
310
|
+
def instantiate(self, model: Model, options: Options) -> None:
|
|
311
|
+
# Check the gmd state.
|
|
312
|
+
rc, _, _, _ = gmdInfo(self.sync_db.gmd, GMD_NRUELS)
|
|
313
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
314
|
+
|
|
315
|
+
# Prepare the required lines to solve with model instance
|
|
316
|
+
scenario_str = self._get_scenario(model)
|
|
317
|
+
with open(self.gms_file, "w", encoding="utf-8") as gams_file:
|
|
318
|
+
gams_file.write(scenario_str)
|
|
319
|
+
|
|
320
|
+
# Write pf file
|
|
321
|
+
hidden_options = self._prepare_hidden_options()
|
|
322
|
+
options._set_hidden_options(hidden_options)
|
|
323
|
+
options.log_file = os.path.join(self.container.working_directory, "gamslog.dat")
|
|
324
|
+
options._export(self.pf_file, self.output)
|
|
325
|
+
|
|
326
|
+
# Run
|
|
327
|
+
try:
|
|
328
|
+
self.container._job = self.job_name
|
|
329
|
+
send_job(
|
|
330
|
+
self.container._comm_pair_id, self.job_name, self.pf_file, self.output
|
|
331
|
+
)
|
|
332
|
+
except GamspyException as exception:
|
|
333
|
+
self.container._workspace._errors.append(str(exception))
|
|
334
|
+
message = _customize_exception(
|
|
335
|
+
options,
|
|
336
|
+
self.job_name,
|
|
337
|
+
exception.return_code,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
exception.args = (exception.message + message,)
|
|
341
|
+
raise exception
|
|
342
|
+
finally:
|
|
343
|
+
self.container._unsaved_statements = []
|
|
344
|
+
|
|
345
|
+
# Init environments
|
|
346
|
+
if gevInitEnvironmentLegacy(self._gev, self.solver_control_file) != 0:
|
|
347
|
+
raise GamspyException("Could not initialize model instance")
|
|
348
|
+
|
|
349
|
+
gmoRegisterEnvironment(self._gmo, gevHandleToPtr(self._gev))
|
|
350
|
+
ret = gmoLoadDataLegacy(self._gmo)
|
|
351
|
+
if ret[0] != 0:
|
|
352
|
+
raise GamspyException(f"Could not load model instance: {ret[1]}")
|
|
353
|
+
|
|
354
|
+
rc = gmdInitFromDict(self.sync_db.gmd, gmoHandleToPtr(self._gmo))
|
|
355
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
356
|
+
|
|
357
|
+
def solve(
|
|
358
|
+
self,
|
|
359
|
+
solver: str,
|
|
360
|
+
instance_options: FreezeOptions,
|
|
361
|
+
solver_options: dict | Path | None,
|
|
362
|
+
output: io.TextIOWrapper | None,
|
|
363
|
+
) -> pd.DataFrame:
|
|
364
|
+
# write solver options file
|
|
365
|
+
option_file = 1 if solver_options else 0
|
|
366
|
+
|
|
367
|
+
names_to_write = []
|
|
368
|
+
for symbol in self.modifiables:
|
|
369
|
+
if isinstance(symbol, gp.Parameter):
|
|
370
|
+
self.instance_container[symbol.name].records = self.container[
|
|
371
|
+
symbol.name
|
|
372
|
+
].records
|
|
373
|
+
names_to_write.append(symbol.name)
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
isinstance(symbol, implicits.ImplicitParameter)
|
|
377
|
+
and symbol.parent.records is not None
|
|
378
|
+
):
|
|
379
|
+
parent_name, attr = symbol.name.split(".")
|
|
380
|
+
attr_name = "_".join([parent_name, attr])
|
|
381
|
+
|
|
382
|
+
columns = self._get_columns_to_drop(attr)
|
|
383
|
+
|
|
384
|
+
self.instance_container[attr_name].setRecords(
|
|
385
|
+
self.container[parent_name].records.drop(columns, axis=1)
|
|
386
|
+
)
|
|
387
|
+
names_to_write.append(attr_name)
|
|
388
|
+
|
|
389
|
+
self.instance_container.write(
|
|
390
|
+
self.sync_db.gmd, symbols=names_to_write, eps_to_zero=False
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
### Legacy code from GAMS Control. TODO: Pay the technical debt of the following legacy code. ###
|
|
394
|
+
rc = gmdInitUpdate(self.sync_db.gmd, gmoHandleToPtr(self._gmo))
|
|
395
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
396
|
+
|
|
397
|
+
# Update gmd
|
|
398
|
+
start = time.perf_counter()
|
|
399
|
+
accumulate_no_match_cnt = 0
|
|
400
|
+
no_match_cnt = 0
|
|
401
|
+
|
|
402
|
+
for modifier in self.modifiers:
|
|
403
|
+
update_type = UPDATE_TYPE_MAP[instance_options.update_type]
|
|
404
|
+
if modifier.update_type != 3:
|
|
405
|
+
update_type = modifier.update_type
|
|
406
|
+
|
|
407
|
+
if isinstance(modifier.gams_symbol, GamsParameter):
|
|
408
|
+
rc, no_match_cnt = gmdUpdateModelSymbol(
|
|
409
|
+
self.sync_db.gmd,
|
|
410
|
+
modifier.gams_symbol.sym_ptr,
|
|
411
|
+
0,
|
|
412
|
+
modifier.gams_symbol.sym_ptr,
|
|
413
|
+
update_type,
|
|
414
|
+
no_match_cnt,
|
|
415
|
+
)
|
|
416
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
417
|
+
else:
|
|
418
|
+
rc, no_match_cnt = gmdUpdateModelSymbol(
|
|
419
|
+
self.sync_db.gmd,
|
|
420
|
+
modifier.gams_symbol.sym_ptr,
|
|
421
|
+
modifier.update_action,
|
|
422
|
+
modifier.data_symbol.sym_ptr, # type: ignore
|
|
423
|
+
update_type,
|
|
424
|
+
no_match_cnt,
|
|
425
|
+
)
|
|
426
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
427
|
+
|
|
428
|
+
accumulate_no_match_cnt += no_match_cnt
|
|
429
|
+
if accumulate_no_match_cnt > instance_options.no_match_limit:
|
|
430
|
+
raise GamspyException(
|
|
431
|
+
f"Unmatched record limit exceeded while processing modifier {modifier.gams_symbol.name}, for more info check no_match_limit option."
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
model_generation_time = time.perf_counter() - start
|
|
435
|
+
|
|
436
|
+
# Close Log and status file and remove
|
|
437
|
+
if output:
|
|
438
|
+
gevSwitchLogStat(self._gev, 0, "", False, "", False, None, None, None)
|
|
439
|
+
ls_handle = gevGetLShandle(self._gev)
|
|
440
|
+
gevRestoreLogStatRewrite(self._gev, ls_handle)
|
|
441
|
+
|
|
442
|
+
if output == sys.stdout:
|
|
443
|
+
gevSwitchLogStat(
|
|
444
|
+
self._gev,
|
|
445
|
+
3,
|
|
446
|
+
gevGetStrOpt(self._gev, gevNameLogFile),
|
|
447
|
+
False,
|
|
448
|
+
gevGetStrOpt(self._gev, gevNameStaFile),
|
|
449
|
+
False,
|
|
450
|
+
None,
|
|
451
|
+
None,
|
|
452
|
+
ls_handle,
|
|
453
|
+
)
|
|
454
|
+
ls_handle = gevGetLShandle(self._gev)
|
|
455
|
+
|
|
456
|
+
if instance_options is not None and instance_options.debug:
|
|
457
|
+
with open(
|
|
458
|
+
os.path.join(self.workspace.working_directory, "convert.opt"),
|
|
459
|
+
"w",
|
|
460
|
+
) as opt_file:
|
|
461
|
+
opt_file.writelines(
|
|
462
|
+
[
|
|
463
|
+
"gams "
|
|
464
|
+
+ os.path.join(self.workspace.working_directory, "gams.gms"),
|
|
465
|
+
"dumpgdx "
|
|
466
|
+
+ os.path.join(self.workspace.working_directory, "dump.gdx\n"),
|
|
467
|
+
"dictmap "
|
|
468
|
+
+ os.path.join(self.workspace.working_directory, "dictmap.gdx"),
|
|
469
|
+
]
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
gmoOptFileSet(self._gmo, 1)
|
|
473
|
+
gmoNameOptFileSet(
|
|
474
|
+
self._gmo,
|
|
475
|
+
os.path.join(self.workspace.working_directory, "convert.opt"),
|
|
476
|
+
)
|
|
477
|
+
rc = gmdCallSolver(self.sync_db.gmd, "convert")
|
|
478
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
479
|
+
|
|
480
|
+
gmoOptFileSet(self._gmo, option_file)
|
|
481
|
+
gmoNameOptFileSet(
|
|
482
|
+
self._gmo,
|
|
483
|
+
os.path.join(self.workspace.working_directory, solver.lower() + ".opt"),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
rc = gmdCallSolver(self.sync_db.gmd, solver)
|
|
487
|
+
self.sync_db._check_for_gmd_error(rc, self.workspace)
|
|
488
|
+
|
|
489
|
+
if output == sys.stdout:
|
|
490
|
+
gevRestoreLogStat(self._gev, ls_handle)
|
|
491
|
+
|
|
492
|
+
if output is not None and output != sys.stdout:
|
|
493
|
+
gevSwitchLogStat(self._gev, 0, "", False, "", False, None, None, ls_handle)
|
|
494
|
+
ls_handle = gevGetLShandle(self._gev)
|
|
495
|
+
with open(gevGetStrOpt(self._gev, gevNameLogFile)) as file:
|
|
496
|
+
for line in file.readlines():
|
|
497
|
+
output.write(line)
|
|
498
|
+
gevRestoreLogStat(self._gev, ls_handle)
|
|
499
|
+
### end of the legacy code ###
|
|
500
|
+
|
|
501
|
+
self._update_main_container()
|
|
502
|
+
|
|
503
|
+
# update model attributes
|
|
504
|
+
from gamspy._model import INTERRUPT_STATUS
|
|
505
|
+
|
|
506
|
+
self.model._status = gp.ModelStatus(gmoModelStat(self._gmo))
|
|
507
|
+
self.model._solve_status = gp.SolveStatus(gmoSolveStat(self._gmo))
|
|
508
|
+
if self.model._solve_status in INTERRUPT_STATUS:
|
|
509
|
+
logger.warning(
|
|
510
|
+
f"The solve was interrupted! Solve status: {self.model._solve_status.name}. "
|
|
511
|
+
"For further information, see https://gamspy.readthedocs.io/en/latest/reference/gamspy._model.html#gamspy.SolveStatus."
|
|
512
|
+
)
|
|
513
|
+
self.model._model_generation_time = model_generation_time
|
|
514
|
+
self.model._solve_model_time = gmoGetHeadnTail(self._gmo, gmoHresused)
|
|
515
|
+
self.model._num_iterations = gmoGetHeadnTail(self._gmo, gmoHiterused)
|
|
516
|
+
self.model._marginals = gmoGetHeadnTail(self._gmo, gmoHmarginals)
|
|
517
|
+
self.model._algorithm_time = gmoGetHeadnTail(self._gmo, gmoHetalg)
|
|
518
|
+
self.model._objective_estimation = gmoGetHeadnTail(self._gmo, gmoTmipbest)
|
|
519
|
+
self.model._num_nodes_used = gmoGetHeadnTail(self._gmo, gmoTmipnod)
|
|
520
|
+
self.model._num_domain_violations = gmoGetHeadnTail(self._gmo, gmoHdomused)
|
|
521
|
+
self.model._objective_value = gmoGetHeadnTail(self._gmo, gmoHobjval)
|
|
522
|
+
self.summary.loc[0] = [
|
|
523
|
+
str(self.model._solve_status),
|
|
524
|
+
str(self.model._status),
|
|
525
|
+
self.model._objective_value,
|
|
526
|
+
solver,
|
|
527
|
+
self.model._solve_model_time,
|
|
528
|
+
]
|
|
529
|
+
|
|
530
|
+
return self.summary
|
|
531
|
+
|
|
532
|
+
def _get_scenario(self, model: Model) -> str:
|
|
533
|
+
auto_id = "s" + utils._get_unique_name()[:5]
|
|
534
|
+
params = [
|
|
535
|
+
modifier.gams_symbol
|
|
536
|
+
for modifier in self.modifiers
|
|
537
|
+
if isinstance(modifier.gams_symbol, GamsParameter)
|
|
538
|
+
]
|
|
539
|
+
lines = []
|
|
540
|
+
if params:
|
|
541
|
+
lines.append(f"Set {auto_id}__(*) /'s0'/;")
|
|
542
|
+
for symbol in params:
|
|
543
|
+
declaration = f"Parameter {auto_id}__{symbol.name}({auto_id}__"
|
|
544
|
+
domain = ""
|
|
545
|
+
if symbol.dimension:
|
|
546
|
+
domain = "," + ",".join("*" * symbol.dimension)
|
|
547
|
+
domain += ")"
|
|
548
|
+
|
|
549
|
+
declaration = f"{declaration}{domain};"
|
|
550
|
+
lines.append(declaration)
|
|
551
|
+
|
|
552
|
+
domain = f"({auto_id}__"
|
|
553
|
+
|
|
554
|
+
if symbol.dimension:
|
|
555
|
+
domain += ","
|
|
556
|
+
|
|
557
|
+
assign_str = f"{auto_id}__{symbol.name}({auto_id}__"
|
|
558
|
+
if symbol.dimension:
|
|
559
|
+
assign_str += "," + ",".join([f"{auto_id}__"] * symbol.dimension)
|
|
560
|
+
|
|
561
|
+
assign_str += ") = Eps;"
|
|
562
|
+
lines.append(assign_str)
|
|
563
|
+
|
|
564
|
+
scenario = f"Set {auto_id}_dict(*,*,*) / '{auto_id}__'.'scenario'.''"
|
|
565
|
+
for symbol in params:
|
|
566
|
+
scenario += f",\n'{symbol.name}'.'param'.'{auto_id}__{symbol.name}'"
|
|
567
|
+
scenario += "/;"
|
|
568
|
+
lines.append(scenario)
|
|
569
|
+
|
|
570
|
+
lines.append(f"{model.name}.justScrDir = 1;")
|
|
571
|
+
solve_string = model._generate_solve_string()
|
|
572
|
+
|
|
573
|
+
if params:
|
|
574
|
+
solve_string += f" scenario {auto_id}_dict"
|
|
575
|
+
|
|
576
|
+
solve_string += ";"
|
|
577
|
+
lines.append(solve_string)
|
|
578
|
+
|
|
579
|
+
return "\n".join(lines)
|
|
580
|
+
|
|
581
|
+
def _init_modifiables(
|
|
582
|
+
self, modifiables: list[Parameter | ImplicitParameter]
|
|
583
|
+
) -> list[Parameter | ImplicitParameter]:
|
|
584
|
+
if not isinstance(modifiables, Iterable):
|
|
585
|
+
raise ValidationError(
|
|
586
|
+
"Modifiables must be iterable (i.e. list, tuple etc.)."
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
if any(
|
|
590
|
+
not isinstance(symbol, (gp.Parameter, implicits.ImplicitParameter))
|
|
591
|
+
for symbol in modifiables
|
|
592
|
+
):
|
|
593
|
+
raise ValidationError(
|
|
594
|
+
"Type of a modifiable must be either Parameter or a Variable attribute (e.g. variable.up)"
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
symbols_in_conditions: list[str] = []
|
|
598
|
+
for equation in self.model.equations:
|
|
599
|
+
assert equation._definition is not None
|
|
600
|
+
symbols_in_conditions += equation._definition._find_symbols_in_conditions()
|
|
601
|
+
|
|
602
|
+
will_be_modified: list[Parameter | ImplicitParameter] = []
|
|
603
|
+
for symbol in modifiables:
|
|
604
|
+
if isinstance(symbol, implicits.ImplicitParameter):
|
|
605
|
+
if symbol.parent.name in symbols_in_conditions:
|
|
606
|
+
raise ValidationError(
|
|
607
|
+
f"Modifiable symbol `{symbol.parent.name}` cannot be in a condition."
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
attr_name = symbol.name.split(".")[-1]
|
|
611
|
+
|
|
612
|
+
# If the symbol attr is fx, then modify level, lower and upper.
|
|
613
|
+
if attr_name == "fx":
|
|
614
|
+
if not utils.isin(symbol.parent.l, will_be_modified):
|
|
615
|
+
will_be_modified.append(symbol.parent.l)
|
|
616
|
+
|
|
617
|
+
if not utils.isin(symbol.parent.lo, will_be_modified):
|
|
618
|
+
will_be_modified.append(symbol.parent.lo)
|
|
619
|
+
|
|
620
|
+
if not utils.isin(symbol.parent.up, will_be_modified):
|
|
621
|
+
will_be_modified.append(symbol.parent.up)
|
|
622
|
+
else:
|
|
623
|
+
# if fx already added level, lower or upper, do not add again.
|
|
624
|
+
if not utils.isin(symbol, will_be_modified):
|
|
625
|
+
will_be_modified.append(symbol)
|
|
626
|
+
else:
|
|
627
|
+
if symbol.name in symbols_in_conditions:
|
|
628
|
+
raise ValidationError(
|
|
629
|
+
f"Modifiable symbol `{symbol.name}` cannot be in a condition."
|
|
630
|
+
)
|
|
631
|
+
will_be_modified.append(symbol)
|
|
632
|
+
|
|
633
|
+
return will_be_modified
|
|
634
|
+
|
|
635
|
+
def _prepare_hidden_options(self) -> dict:
|
|
636
|
+
scrdir = self.container._process_directory
|
|
637
|
+
hidden_options = {
|
|
638
|
+
"trace": self.trace_file,
|
|
639
|
+
"input": self.gms_file,
|
|
640
|
+
"output": self.lst_file,
|
|
641
|
+
"optdir": self.container.working_directory,
|
|
642
|
+
"sysdir": self.container.system_directory,
|
|
643
|
+
"scrdir": scrdir,
|
|
644
|
+
"scriptnext": os.path.join(scrdir, "gamsnext.sh"),
|
|
645
|
+
"license": self.container._license_path,
|
|
646
|
+
"solvercntr": self.solver_control_file,
|
|
647
|
+
}
|
|
648
|
+
if self.container._network_license:
|
|
649
|
+
hidden_options["netlicense"] = os.path.join(
|
|
650
|
+
self.container._process_directory, "gamslice.dat"
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
return hidden_options
|
|
654
|
+
|
|
655
|
+
def _get_columns_to_drop(self, attr: str) -> list[str]:
|
|
656
|
+
attr_map = {
|
|
657
|
+
"l": "level",
|
|
658
|
+
"m": "marginal",
|
|
659
|
+
"lo": "lower",
|
|
660
|
+
"up": "upper",
|
|
661
|
+
"scale": "scale",
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
columns = []
|
|
665
|
+
for key, value in attr_map.items():
|
|
666
|
+
if key != attr:
|
|
667
|
+
columns.append(value)
|
|
668
|
+
|
|
669
|
+
return columns
|
|
670
|
+
|
|
671
|
+
def _update_main_container(self) -> None:
|
|
672
|
+
temp = self.container._temp_container
|
|
673
|
+
temp.read(self.sync_db.gmd)
|
|
674
|
+
|
|
675
|
+
prev_state = self.container._options.miro_protect
|
|
676
|
+
for name in temp.data:
|
|
677
|
+
if name in self.container.data:
|
|
678
|
+
self.container._options.miro_protect = False
|
|
679
|
+
self.container[name].records = temp[name].records
|
|
680
|
+
self.container[name].domain_labels = self.container[name].domain_names
|
|
681
|
+
|
|
682
|
+
if name in (symbol.name for symbol in self.modifiables):
|
|
683
|
+
generated_var = name + "_var"
|
|
684
|
+
if generated_var not in self.container.data:
|
|
685
|
+
_ = gp.Variable(
|
|
686
|
+
self.container,
|
|
687
|
+
generated_var,
|
|
688
|
+
domain=self.container[name].domain,
|
|
689
|
+
records=temp[generated_var].records,
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
self.container[generated_var]._records = temp[generated_var].records
|
|
693
|
+
|
|
694
|
+
self.container._options.miro_protect = prev_state
|
|
695
|
+
|
|
696
|
+
if self.model._objective_variable is not None:
|
|
697
|
+
self.model._objective_value = temp[
|
|
698
|
+
self.model._objective_variable.name
|
|
699
|
+
].toValue()
|
|
700
|
+
|
|
701
|
+
temp.data = {}
|