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.
Files changed (89) hide show
  1. gamspy/__init__.py +86 -98
  2. gamspy/__main__.py +6 -6
  3. gamspy/_algebra/__init__.py +13 -13
  4. gamspy/_algebra/condition.py +290 -194
  5. gamspy/_algebra/domain.py +103 -93
  6. gamspy/_algebra/expression.py +820 -799
  7. gamspy/_algebra/number.py +79 -70
  8. gamspy/_algebra/operable.py +185 -185
  9. gamspy/_algebra/operation.py +948 -845
  10. gamspy/_backend/backend.py +313 -311
  11. gamspy/_backend/engine.py +960 -960
  12. gamspy/_backend/local.py +124 -124
  13. gamspy/_backend/neos.py +567 -567
  14. gamspy/_cli/__init__.py +1 -1
  15. gamspy/_cli/cli.py +64 -64
  16. gamspy/_cli/gdx.py +377 -377
  17. gamspy/_cli/install.py +375 -372
  18. gamspy/_cli/list.py +94 -94
  19. gamspy/_cli/mps2gms.py +128 -128
  20. gamspy/_cli/probe.py +52 -52
  21. gamspy/_cli/retrieve.py +79 -79
  22. gamspy/_cli/run.py +158 -158
  23. gamspy/_cli/show.py +246 -255
  24. gamspy/_cli/uninstall.py +165 -165
  25. gamspy/_cli/util.py +94 -94
  26. gamspy/_communication.py +215 -215
  27. gamspy/_config.py +132 -132
  28. gamspy/_container.py +1694 -1452
  29. gamspy/_convert.py +720 -720
  30. gamspy/_database.py +271 -271
  31. gamspy/_extrinsic.py +181 -181
  32. gamspy/_miro.py +356 -352
  33. gamspy/_model.py +1803 -1615
  34. gamspy/_model_instance.py +701 -701
  35. gamspy/_options.py +780 -700
  36. gamspy/_serialization.py +156 -144
  37. gamspy/_symbols/__init__.py +17 -17
  38. gamspy/_symbols/alias.py +305 -299
  39. gamspy/_symbols/equation.py +1407 -1298
  40. gamspy/_symbols/implicits/__init__.py +11 -11
  41. gamspy/_symbols/implicits/implicit_equation.py +186 -186
  42. gamspy/_symbols/implicits/implicit_parameter.py +272 -272
  43. gamspy/_symbols/implicits/implicit_set.py +124 -124
  44. gamspy/_symbols/implicits/implicit_symbol.py +315 -315
  45. gamspy/_symbols/implicits/implicit_variable.py +255 -255
  46. gamspy/_symbols/parameter.py +648 -609
  47. gamspy/_symbols/set.py +985 -923
  48. gamspy/_symbols/symbol.py +395 -386
  49. gamspy/_symbols/universe_alias.py +182 -182
  50. gamspy/_symbols/variable.py +1101 -1017
  51. gamspy/_types.py +7 -7
  52. gamspy/_validation.py +735 -735
  53. gamspy/_workspace.py +72 -72
  54. gamspy/exceptions.py +128 -128
  55. gamspy/formulations/__init__.py +46 -46
  56. gamspy/formulations/ml/__init__.py +11 -11
  57. gamspy/formulations/ml/decision_tree_struct.py +80 -80
  58. gamspy/formulations/ml/gradient_boosting.py +203 -203
  59. gamspy/formulations/ml/random_forest.py +187 -187
  60. gamspy/formulations/ml/regression_tree.py +533 -533
  61. gamspy/formulations/nn/__init__.py +19 -19
  62. gamspy/formulations/nn/avgpool2d.py +232 -232
  63. gamspy/formulations/nn/conv1d.py +533 -533
  64. gamspy/formulations/nn/conv2d.py +529 -529
  65. gamspy/formulations/nn/linear.py +341 -341
  66. gamspy/formulations/nn/maxpool2d.py +88 -88
  67. gamspy/formulations/nn/minpool2d.py +88 -88
  68. gamspy/formulations/nn/mpool2d.py +245 -245
  69. gamspy/formulations/nn/torch_sequential.py +278 -278
  70. gamspy/formulations/piecewise.py +682 -682
  71. gamspy/formulations/result.py +119 -119
  72. gamspy/formulations/shape.py +188 -188
  73. gamspy/formulations/utils.py +173 -173
  74. gamspy/math/__init__.py +215 -215
  75. gamspy/math/activation.py +783 -767
  76. gamspy/math/log_power.py +435 -435
  77. gamspy/math/matrix.py +534 -534
  78. gamspy/math/misc.py +1709 -1625
  79. gamspy/math/probability.py +170 -170
  80. gamspy/math/trigonometric.py +232 -232
  81. gamspy/utils.py +810 -791
  82. gamspy/version.py +5 -5
  83. {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/METADATA +90 -121
  84. gamspy-1.19.0.dist-info/RECORD +90 -0
  85. {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/WHEEL +1 -1
  86. {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/licenses/LICENSE +22 -22
  87. gamspy-1.18.3.dist-info/RECORD +0 -90
  88. {gamspy-1.18.3.dist-info → gamspy-1.19.0.dist-info}/entry_points.txt +0 -0
  89. {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
- extra_options = self._prepare_gams_options()
322
- options._set_extra_options(extra_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_gams_options(self) -> dict:
636
- scrdir = self.container._process_directory
637
- extra_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
- extra_options["netlicense"] = os.path.join(
650
- self.container._process_directory, "gamslice.dat"
651
- )
652
-
653
- return extra_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 = {}
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 = {}