gamspy 1.14.0__tar.gz → 1.15.1__tar.gz

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 (95) hide show
  1. {gamspy-1.14.0 → gamspy-1.15.1}/PKG-INFO +3 -3
  2. {gamspy-1.14.0 → gamspy-1.15.1}/pyproject.toml +3 -3
  3. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/condition.py +5 -1
  4. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/expression.py +283 -133
  5. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/operable.py +1 -1
  6. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/operation.py +6 -13
  7. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/backend.py +18 -1
  8. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/engine.py +2 -7
  9. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/local.py +2 -7
  10. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/neos.py +2 -6
  11. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/cli.py +2 -8
  12. gamspy-1.15.1/src/gamspy/_cli/gdx.py +276 -0
  13. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/install.py +1 -1
  14. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/retrieve.py +13 -6
  15. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_config.py +5 -5
  16. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_container.py +58 -8
  17. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_convert.py +1 -1
  18. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_miro.py +5 -1
  19. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_options.py +1 -1
  20. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/alias.py +12 -2
  21. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/equation.py +54 -20
  22. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_equation.py +18 -4
  23. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_parameter.py +47 -51
  24. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_set.py +16 -5
  25. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_variable.py +17 -4
  26. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/parameter.py +24 -19
  27. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/set.py +23 -19
  28. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/symbol.py +1 -1
  29. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/variable.py +22 -17
  30. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_validation.py +44 -8
  31. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/__init__.py +8 -1
  32. gamspy-1.15.1/src/gamspy/formulations/ml/__init__.py +11 -0
  33. gamspy-1.15.1/src/gamspy/formulations/ml/gradient_boosting.py +186 -0
  34. gamspy-1.15.1/src/gamspy/formulations/ml/random_forest.py +168 -0
  35. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/ml/regression_tree.py +223 -122
  36. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/avgpool2d.py +1 -1
  37. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/conv1d.py +17 -4
  38. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/conv2d.py +18 -4
  39. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/linear.py +17 -4
  40. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/mpool2d.py +1 -1
  41. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/torch_sequential.py +8 -0
  42. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/piecewise.py +7 -9
  43. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/shape.py +1 -1
  44. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/__init__.py +2 -0
  45. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/activation.py +131 -0
  46. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/PKG-INFO +3 -3
  47. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/SOURCES.txt +4 -1
  48. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/requires.txt +2 -2
  49. {gamspy-1.14.0 → gamspy-1.15.1}/tests/test_gamspy.py +1 -1
  50. gamspy-1.14.0/src/gamspy/formulations/ml/__init__.py +0 -4
  51. {gamspy-1.14.0 → gamspy-1.15.1}/LICENSE +0 -0
  52. {gamspy-1.14.0 → gamspy-1.15.1}/README.md +0 -0
  53. {gamspy-1.14.0 → gamspy-1.15.1}/README_PYPI.md +0 -0
  54. {gamspy-1.14.0 → gamspy-1.15.1}/setup.cfg +0 -0
  55. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/__init__.py +0 -0
  56. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/__main__.py +0 -0
  57. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/__init__.py +0 -0
  58. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/domain.py +0 -0
  59. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/number.py +0 -0
  60. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/__init__.py +0 -0
  61. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/__init__.py +0 -0
  62. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/list.py +0 -0
  63. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/probe.py +0 -0
  64. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/run.py +0 -0
  65. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/show.py +0 -0
  66. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/uninstall.py +0 -0
  67. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/util.py +0 -0
  68. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_database.py +0 -0
  69. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_extrinsic.py +0 -0
  70. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_model.py +0 -0
  71. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_model_instance.py +0 -0
  72. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_serialization.py +0 -0
  73. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/__init__.py +0 -0
  74. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/__init__.py +0 -0
  75. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_symbol.py +0 -0
  76. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/universe_alias.py +0 -0
  77. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_types.py +0 -0
  78. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_workspace.py +0 -0
  79. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/exceptions.py +0 -0
  80. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/ml/decision_tree_struct.py +0 -0
  81. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/__init__.py +0 -0
  82. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/maxpool2d.py +0 -0
  83. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/minpool2d.py +0 -0
  84. {gamspy-1.14.0/src/gamspy/formulations/nn → gamspy-1.15.1/src/gamspy/formulations}/utils.py +0 -0
  85. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/log_power.py +0 -0
  86. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/matrix.py +0 -0
  87. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/misc.py +0 -0
  88. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/probability.py +0 -0
  89. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/trigonometric.py +0 -0
  90. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/py.typed +0 -0
  91. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/utils.py +0 -0
  92. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/version.py +0 -0
  93. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/dependency_links.txt +0 -0
  94. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/entry_points.txt +0 -0
  95. {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gamspy
3
- Version: 1.14.0
3
+ Version: 1.15.1
4
4
  Summary: Python-based algebraic modeling interface to GAMS
5
5
  Author-email: GAMS Development Corporation <support@gams.com>
6
6
  Project-URL: homepage, https://gams.com/sales/gamspy_facts/
@@ -31,8 +31,8 @@ Classifier: Operating System :: Microsoft :: Windows
31
31
  Requires-Python: >=3.9
32
32
  Description-Content-Type: text/markdown
33
33
  License-File: LICENSE
34
- Requires-Dist: gamsapi[transfer]==50.3.0
35
- Requires-Dist: gamspy_base==50.3.0
34
+ Requires-Dist: gamsapi[transfer]==50.4.1
35
+ Requires-Dist: gamspy_base==50.4.1
36
36
  Requires-Dist: pydantic>=2.0
37
37
  Requires-Dist: certifi>=2022.09.14
38
38
  Requires-Dist: urllib3>=2.0.7
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gamspy"
7
- version = "1.14.0"
7
+ version = "1.15.1"
8
8
  authors = [
9
9
  { name = "GAMS Development Corporation", email = "support@gams.com" },
10
10
  ]
@@ -36,8 +36,8 @@ classifiers = [
36
36
  "Operating System :: Microsoft :: Windows",
37
37
  ]
38
38
  dependencies = [
39
- "gamsapi[transfer] == 50.3.0",
40
- "gamspy_base == 50.3.0",
39
+ "gamsapi[transfer] == 50.4.1",
40
+ "gamspy_base == 50.4.1",
41
41
  "pydantic >= 2.0",
42
42
  "certifi >= 2022.09.14",
43
43
  "urllib3 >= 2.0.7",
@@ -137,11 +137,15 @@ class Condition(operable.Operable):
137
137
  if hasattr(self.condition, "gamsRepr")
138
138
  else str(self.condition)
139
139
  )
140
+ conditioning_on_str = self.conditioning_on.gamsRepr()
140
141
 
141
142
  if isinstance(self.condition, bool):
142
143
  condition_str = str(int(self.condition))
143
144
 
144
- return f"({self.conditioning_on.gamsRepr()} $ {condition_str})" # type: ignore
145
+ if isinstance(self.conditioning_on, expression.Expression):
146
+ conditioning_on_str = f"({conditioning_on_str})"
147
+
148
+ return f"{conditioning_on_str} $ ({condition_str})" # type: ignore
145
149
 
146
150
  def getDeclaration(self) -> str:
147
151
  return self.gamsRepr()
@@ -20,6 +20,8 @@ from gamspy.exceptions import ValidationError
20
20
  from gamspy.math.misc import MathOp
21
21
 
22
22
  if TYPE_CHECKING:
23
+ from numbers import Real
24
+
23
25
  import pandas as pd
24
26
 
25
27
  from gamspy import Alias, Set
@@ -41,6 +43,266 @@ def peek(stack):
41
43
  return None
42
44
 
43
45
 
46
+ # Higher number means higher precedence.
47
+ PRECEDENCE = {
48
+ "..": 0,
49
+ "=": 0,
50
+ "or": 1,
51
+ "xor": 2,
52
+ "and": 3,
53
+ "=e=": 4,
54
+ "=n=": 4,
55
+ "=b=": 4,
56
+ "eq": 4,
57
+ "ne": 4,
58
+ ">=": 4,
59
+ "<=": 4,
60
+ ">": 4,
61
+ "<": 4,
62
+ "=g=": 4,
63
+ "=l=": 4,
64
+ "=x=": 4,
65
+ "+": 5,
66
+ "-": 5,
67
+ "*": 6,
68
+ "/": 6,
69
+ "not": 7,
70
+ "u-": 7,
71
+ }
72
+
73
+ # Defines how operators of the same precedence are grouped.
74
+ ASSOCIATIVITY = {
75
+ "or": "left",
76
+ "xor": "left",
77
+ "and": "left",
78
+ "=e=": "left",
79
+ "=n=": "left",
80
+ "=x=": "left",
81
+ "=b=": "left",
82
+ "eq": "left",
83
+ "ne": "left",
84
+ "=": "left",
85
+ ">=": "left",
86
+ "<=": "left",
87
+ ">": "left",
88
+ "<": "left",
89
+ "=g=": "left",
90
+ "=l=": "left",
91
+ "..": "non",
92
+ "+": "left",
93
+ "-": "left",
94
+ "*": "left",
95
+ "/": "left",
96
+ "not": "right",
97
+ "u-": "right",
98
+ }
99
+
100
+ # Precedence for a leaf node is considered infinite.
101
+ LEAF_PRECEDENCE = float("inf")
102
+
103
+
104
+ def get_operand_gams_repr(operand) -> str:
105
+ if hasattr(operand, "gamsRepr"):
106
+ return operand.gamsRepr()
107
+
108
+ representation = str(operand)
109
+
110
+ # b[i] * -1 -> not valid
111
+ # b[i] * (-1) -> valid
112
+ if isinstance(operand, (int, float)) and operand < 0:
113
+ representation = f"({representation})"
114
+
115
+ return representation
116
+
117
+
118
+ def get_operand_latex_repr(operand) -> str:
119
+ if hasattr(operand, "latexRepr"):
120
+ return operand.latexRepr()
121
+
122
+ if isinstance(operand, float):
123
+ operand = utils._map_special_values(operand)
124
+
125
+ representation = str(operand)
126
+
127
+ return representation
128
+
129
+
130
+ def create_gams_expression(root_node: Expression) -> str:
131
+ """
132
+ Creates GAMS representation of a binary expression tree without recursion.
133
+ It uses an iterative post-order traversal to build the expression,
134
+ adding parentheses only when necessary based on operator precedence and
135
+ associativity rules.
136
+ """
137
+ if not isinstance(root_node, Expression):
138
+ return get_operand_gams_repr(root_node)
139
+
140
+ # 1. Get nodes in post-order (left - right - parent).
141
+ s1: list[OperableType | ImplicitEquation | str] = [root_node]
142
+ post_order_nodes = []
143
+ while s1:
144
+ node = s1.pop()
145
+ post_order_nodes.append(node)
146
+ if isinstance(node, Expression):
147
+ if node.left is not None:
148
+ s1.append(node.left)
149
+ if node.right is not None:
150
+ s1.append(node.right)
151
+
152
+ # 2. Build the GAMS expression
153
+ eval_stack: list[tuple[str, Real]] = []
154
+ for node in reversed(post_order_nodes):
155
+ if not isinstance(node, Expression):
156
+ eval_stack.append((get_operand_gams_repr(node), LEAF_PRECEDENCE))
157
+ continue
158
+
159
+ op = node.operator
160
+ op_prec = PRECEDENCE[op]
161
+ op_assoc = ASSOCIATIVITY[op]
162
+
163
+ # Handle unary ops
164
+ if op in ("u-", "not"):
165
+ operand_str, operand_prec = eval_stack.pop()
166
+
167
+ # Add parentheses if the operand's operator has lower precedence
168
+ if operand_prec < op_prec:
169
+ operand_str = f"({operand_str})"
170
+
171
+ if op == "u-":
172
+ new_str = f"(-{operand_str})"
173
+ # A parenthesized expression has the highest precedence
174
+ eval_stack.append((new_str, LEAF_PRECEDENCE))
175
+ else: # Standard handling for 'not'
176
+ new_str = f"not {operand_str}"
177
+ eval_stack.append((new_str, op_prec))
178
+
179
+ # Handle binary ops
180
+ else:
181
+ right_str, right_prec = eval_stack.pop()
182
+ left_str, left_prec = eval_stack.pop()
183
+
184
+ if left_prec < op_prec or (
185
+ left_prec == op_prec and op_assoc == "right"
186
+ ):
187
+ left_str = f"({left_str})"
188
+
189
+ if right_prec < op_prec or (
190
+ right_prec == op_prec and op_assoc == "left"
191
+ ):
192
+ right_str = f"({right_str})"
193
+
194
+ # get around 80000 line length limitation in GAMS
195
+ length = len(left_str) + len(op) + len(right_str)
196
+ if length >= GMS_MAX_LINE_LENGTH - LINE_LENGTH_OFFSET:
197
+ new_str = f"{left_str} {op}\n {right_str}"
198
+ else:
199
+ new_str = f"{left_str} {op} {right_str}"
200
+ eval_stack.append((new_str, op_prec))
201
+
202
+ final_string = eval_stack[0][0]
203
+
204
+ if root_node.operator in ("=", ".."):
205
+ return f"{final_string};"
206
+
207
+ return final_string
208
+
209
+
210
+ def create_latex_expression(root_node: Expression) -> str:
211
+ """
212
+ Creates LaTeX representation of a binary expression tree without recursion.
213
+ It uses an iterative post-order traversal to build the expression,
214
+ adding parentheses only when necessary based on operator precedence and
215
+ associativity rules.
216
+ """
217
+ if not isinstance(root_node, Expression):
218
+ return get_operand_latex_repr(root_node)
219
+
220
+ op_map = {
221
+ "=g=": "\\geq",
222
+ "=l=": "\\leq",
223
+ "=e=": "=",
224
+ "*": "\\cdot",
225
+ "and": "\\wedge",
226
+ "or": "\\vee",
227
+ "xor": "\\oplus",
228
+ "$": "|",
229
+ }
230
+
231
+ # 1. Get nodes in post-order (left - right - parent).
232
+ s1: list[OperableType | ImplicitEquation | str] = [root_node]
233
+ post_order_nodes = []
234
+ while s1:
235
+ node = s1.pop()
236
+ post_order_nodes.append(node)
237
+ if isinstance(node, Expression):
238
+ if node.left is not None:
239
+ s1.append(node.left)
240
+ if node.right is not None:
241
+ s1.append(node.right)
242
+
243
+ # 2. Build the GAMS expression
244
+ eval_stack: list[tuple[str, Real]] = []
245
+ for node in reversed(post_order_nodes):
246
+ if not isinstance(node, Expression):
247
+ eval_stack.append((get_operand_latex_repr(node), LEAF_PRECEDENCE))
248
+ continue
249
+
250
+ op = node.operator
251
+ op_prec = PRECEDENCE[op]
252
+ op_assoc = ASSOCIATIVITY[op]
253
+
254
+ # Handle unary ops
255
+ if op in ("u-", "not"):
256
+ operand_str, operand_prec = eval_stack.pop()
257
+
258
+ # Add parentheses if the operand's operator has lower precedence
259
+ if operand_prec < op_prec:
260
+ operand_str = f"({operand_str})"
261
+
262
+ if op == "u-":
263
+ new_str = f"(-{operand_str})"
264
+ # A parenthesized expression has the highest precedence
265
+ eval_stack.append((new_str, LEAF_PRECEDENCE))
266
+ else: # Standard handling for 'not'
267
+ new_str = f"not {operand_str}"
268
+ eval_stack.append((new_str, op_prec))
269
+
270
+ # Handle binary ops
271
+ else:
272
+ right_str, right_prec = eval_stack.pop()
273
+ left_str, left_prec = eval_stack.pop()
274
+
275
+ if left_prec < op_prec or (
276
+ left_prec == op_prec and op_assoc == "right"
277
+ ):
278
+ left_str = f"({left_str})"
279
+
280
+ if right_prec < op_prec or (
281
+ right_prec == op_prec and op_assoc == "left"
282
+ ):
283
+ right_str = f"({right_str})"
284
+
285
+ if op == "/":
286
+ eval_stack.append(
287
+ (f"\\frac{{{left_str}}}{{{right_str}}}", op_prec)
288
+ )
289
+ continue
290
+
291
+ op = op_map.get(op, op)
292
+
293
+ # get around 80000 line length limitation in GAMS
294
+ length = len(left_str) + len(op) + len(right_str)
295
+ if length >= GMS_MAX_LINE_LENGTH - LINE_LENGTH_OFFSET:
296
+ new_str = f"{left_str} {op}\n {right_str}"
297
+ else:
298
+ new_str = f"{left_str} {op} {right_str}"
299
+ eval_stack.append((new_str, op_prec))
300
+
301
+ final_string = eval_stack[0][0]
302
+
303
+ return final_string
304
+
305
+
44
306
  class Expression(operable.Operable):
45
307
  """
46
308
  Expression of two operands and an operation.
@@ -62,14 +324,14 @@ class Expression(operable.Operable):
62
324
  >>> b = gp.Parameter(m, name="b")
63
325
  >>> expression = a * b
64
326
  >>> expression.gamsRepr()
65
- '(a * b)'
327
+ 'a * b'
66
328
 
67
329
  """
68
330
 
69
331
  def __init__(
70
332
  self,
71
333
  left: OperableType | ImplicitEquation | None,
72
- data: str,
334
+ operator: str,
73
335
  right: OperableType | str | None,
74
336
  ):
75
337
  self.left = (
@@ -77,20 +339,17 @@ class Expression(operable.Operable):
77
339
  if isinstance(left, float)
78
340
  else left
79
341
  )
80
- self.data = data
342
+ self.operator = operator
81
343
  self.right = (
82
344
  utils._map_special_values(right)
83
345
  if isinstance(right, float)
84
346
  else right
85
347
  )
86
348
 
87
- if data == "=" and isinstance(right, Expression):
349
+ if operator == "=" and isinstance(right, Expression):
88
350
  right._fix_equalities()
89
351
 
90
- if get_option("LAZY_EVALUATION"):
91
- self._representation = None
92
- else:
93
- self._representation = self._create_output_str()
352
+ self._representation: str | None = None
94
353
  self.where = condition.Condition(self)
95
354
  self._create_domain()
96
355
  left_control = getattr(left, "controlled_domain", [])
@@ -107,7 +366,7 @@ class Expression(operable.Operable):
107
366
  @property
108
367
  def representation(self) -> str:
109
368
  if self._representation is None:
110
- self._representation = self._create_output_str()
369
+ self._representation = create_gams_expression(self)
111
370
 
112
371
  return self._representation
113
372
 
@@ -175,7 +434,7 @@ class Expression(operable.Operable):
175
434
  left = self.left[left_domain] if left_domain else self.left
176
435
  right = self.right[right_domain] if right_domain else self.right
177
436
 
178
- return Expression(left, self.data, right)
437
+ return Expression(left, self.operator, right)
179
438
 
180
439
  @property
181
440
  def records(self) -> pd.DataFrame | None:
@@ -266,60 +525,6 @@ class Expression(operable.Operable):
266
525
 
267
526
  return None
268
527
 
269
- def _get_operand_representations(self) -> tuple[str, str]:
270
- left_str, right_str = "", ""
271
- if self.left is not None:
272
- left_str = (
273
- str(self.left)
274
- if isinstance(self.left, (int, float, str))
275
- else self.left.gamsRepr()
276
- )
277
-
278
- if self.right is not None:
279
- right_str = (
280
- str(self.right)
281
- if isinstance(self.right, (int, float, str))
282
- else self.right.gamsRepr()
283
- )
284
-
285
- # ((((ord(n) - 1) / 10) * -1) + ((ord(n) / 10) * 0)); -> not valid
286
- # ((((ord(n) - 1) / 10) * (-1)) + ((ord(n) / 10) * 0)); -> valid
287
- if isinstance(self.left, (int, float)) and self.left < 0:
288
- left_str = f"({left_str})"
289
-
290
- if isinstance(self.right, (int, float)) and self.right < 0:
291
- right_str = f"({right_str})"
292
-
293
- # (voycap(j,k)$vc(j,k)) .. sum(.) -> not valid
294
- # voycap(j,k)$vc(j,k) .. sum(.) -> valid
295
- if self.data in ("..", "=") and isinstance(
296
- self.left, condition.Condition
297
- ):
298
- left_str = left_str[1:-1]
299
-
300
- return left_str, right_str
301
-
302
- def _create_output_str(self) -> str:
303
- left_str, right_str = self._get_operand_representations()
304
-
305
- # get around 80000 line length limitation in GAMS
306
- length = len(left_str) + len(self.data) + len(right_str)
307
- if length >= GMS_MAX_LINE_LENGTH - LINE_LENGTH_OFFSET:
308
- out_str = f"{left_str} {self.data}\n {right_str}"
309
- else:
310
- out_str = f"{left_str} {self.data} {right_str}"
311
-
312
- if self.data == ".":
313
- return out_str.replace(" ", "")
314
-
315
- if self.data in ("..", "="):
316
- return f"{out_str};"
317
-
318
- if self.data in ("=g=", "=l=", "=e=", "=n=", "=x=", "=c=", "=b="):
319
- return out_str
320
-
321
- return f"({out_str})"
322
-
323
528
  def __eq__(self, other):
324
529
  return Expression(self, "=e=", other)
325
530
 
@@ -335,13 +540,10 @@ class Expression(operable.Operable):
335
540
  )
336
541
 
337
542
  def __repr__(self) -> str:
338
- return f"Expression(left={self.left}, data={self.data}, right={self.right})"
543
+ return f"Expression(left={self.left}, data={self.operator}, right={self.right})"
339
544
 
340
545
  def _replace_operator(self, operator: str):
341
- self.data = operator
342
-
343
- if not get_option("LAZY_EVALUATION"):
344
- self._representation = self._create_output_str()
546
+ self.operator = operator
345
547
 
346
548
  def latexRepr(self) -> str:
347
549
  """
@@ -351,55 +553,7 @@ class Expression(operable.Operable):
351
553
  -------
352
554
  str
353
555
  """
354
- data_map = {
355
- "=g=": "\\geq",
356
- "=l=": "\\leq",
357
- "=e=": "=",
358
- "*": "\\cdot",
359
- "and": "\\wedge",
360
- "or": "\\vee",
361
- "xor": "\\oplus",
362
- "$": "|",
363
- }
364
-
365
- if isinstance(self.left, float):
366
- self.left = utils._map_special_values(self.left)
367
-
368
- if isinstance(self.right, float):
369
- self.right = utils._map_special_values(self.right)
370
-
371
- if self.left is None:
372
- left_str = ""
373
- else:
374
- left_str = (
375
- str(self.left)
376
- if isinstance(self.left, (int, float, str))
377
- else self.left.latexRepr() # type: ignore
378
- )
379
-
380
- if self.right is None:
381
- right_str = ""
382
- else:
383
- right_str = (
384
- str(self.right)
385
- if isinstance(self.right, (int, float, str))
386
- else self.right.latexRepr() # type: ignore
387
- )
388
-
389
- data = self.data
390
- if isinstance(self.data, str):
391
- data = data_map.get(self.data, self.data)
392
-
393
- data_str = (
394
- str(data)
395
- if isinstance(data, (int, float, str))
396
- else data.latexRepr()
397
- )
398
-
399
- if self.data == "/":
400
- return f"\\frac{{{left_str}}}{{{right_str}}}"
401
-
402
- return f"({left_str} {data_str} {right_str})"
556
+ return create_latex_expression(self)
403
557
 
404
558
  def gamsRepr(self) -> str:
405
559
  """
@@ -417,7 +571,7 @@ class Expression(operable.Operable):
417
571
  >>> b = gp.Parameter(m, name="b")
418
572
  >>> expression = a * b
419
573
  >>> expression.gamsRepr()
420
- '(a * b)'
574
+ 'a * b'
421
575
 
422
576
  """
423
577
  return self.representation
@@ -438,7 +592,7 @@ class Expression(operable.Operable):
438
592
  >>> b = gp.Parameter(m, name="b")
439
593
  >>> expression = a * b
440
594
  >>> expression.getDeclaration()
441
- '(a * b)'
595
+ 'a * b'
442
596
 
443
597
  """
444
598
  return self.gamsRepr()
@@ -465,12 +619,8 @@ class Expression(operable.Operable):
465
619
 
466
620
  root = stack.pop()
467
621
 
468
- if isinstance(root, Expression):
469
- if root.data in EQ_MAP:
470
- root._replace_operator(EQ_MAP[root.data])
471
- else:
472
- if not get_option("LAZY_EVALUATION"):
473
- root._representation = root._create_output_str()
622
+ if isinstance(root, Expression) and root.operator in EQ_MAP:
623
+ root._replace_operator(EQ_MAP[root.operator])
474
624
 
475
625
  last_item = peek(stack)
476
626
  if (
@@ -500,6 +650,9 @@ class Expression(operable.Operable):
500
650
 
501
651
  if isinstance(node, Symbol):
502
652
  if node.name not in symbols:
653
+ if type(node) is gp_syms.Alias:
654
+ symbols.append(node.alias_with.name)
655
+
503
656
  symbols.append(node.name)
504
657
  stack += node.domain
505
658
  node = None
@@ -633,7 +786,7 @@ class SetExpression(Expression):
633
786
  self.right = "yes"
634
787
  else:
635
788
  raise ValidationError(
636
- f"Incompatible operand `{self.right}` for the set operation `{self.data}`."
789
+ f"Incompatible operand `{self.right}` for the set operation `{self.operator}`."
637
790
  )
638
791
  elif isinstance(self.right, condition.Condition) and isinstance(
639
792
  self.right.conditioning_on, number.Number
@@ -643,7 +796,7 @@ class SetExpression(Expression):
643
796
  elif self.right.conditioning_on._value == 1:
644
797
  self.right.conditioning_on._value = "yes"
645
798
  raise ValidationError(
646
- f"Incompatible operand `{self.right}` for the set operation `{self.data}`."
799
+ f"Incompatible operand `{self.right}` for the set operation `{self.operator}`."
647
800
  )
648
801
 
649
802
  if isinstance(self.right, (ImplicitSet, SetExpression)):
@@ -654,7 +807,7 @@ class SetExpression(Expression):
654
807
  self.left = "yes"
655
808
  else:
656
809
  raise ValidationError(
657
- f"Incompatible operand `{self.left}` for the set operation `{self.data}`."
810
+ f"Incompatible operand `{self.left}` for the set operation `{self.operator}`."
658
811
  )
659
812
  elif isinstance(self.left, condition.Condition) and isinstance(
660
813
  self.left.conditioning_on, number.Number
@@ -665,8 +818,5 @@ class SetExpression(Expression):
665
818
  self.left.conditioning_on._value = "yes"
666
819
  else:
667
820
  raise ValidationError(
668
- f"Incompatible operand `{self.left}` for the set operation `{self.data}`."
821
+ f"Incompatible operand `{self.left}` for the set operation `{self.operator}`."
669
822
  )
670
-
671
- if not get_option("LAZY_EVALUATION"):
672
- self._representation = self._create_output_str()
@@ -76,7 +76,7 @@ class Operable:
76
76
  return expression.Expression(other, "-", self)
77
77
 
78
78
  def __neg__(self):
79
- return expression.Expression(None, "-", self)
79
+ return expression.Expression(None, "u-", self)
80
80
 
81
81
  def __truediv__(self, other: OperableType):
82
82
  return expression.Expression(self, "/", other)
@@ -176,14 +176,7 @@ class Operation(operable.Operable):
176
176
 
177
177
  def _get_index_str(self) -> str:
178
178
  if len(self.op_domain) == 1:
179
- op_domain = self.op_domain[0]
180
- representation = op_domain.gamsRepr()
181
- if isinstance(op_domain, condition.Condition):
182
- # sum((l(root,s,s1,s2) $ od(root,s)),1); -> not valid
183
- # sum(l(root,s,s1,s2) $ od(root,s),1); -> valid
184
- return representation[1:-1]
185
-
186
- return representation
179
+ return self.op_domain[0].gamsRepr()
187
180
 
188
181
  return (
189
182
  "("
@@ -274,7 +267,7 @@ class Operation(operable.Operable):
274
267
  else self.rhs.latexRepr()
275
268
  )
276
269
  representation = (
277
- f"\\{op_map[self._op_name]}_\\text{{{index_str}}} {expression_str}"
270
+ f"\\{op_map[self._op_name]}_{{{index_str}}} {expression_str}"
278
271
  )
279
272
  return representation
280
273
 
@@ -348,7 +341,7 @@ class Sum(Operation):
348
341
  >>> c = Parameter(m, "c", domain=i)
349
342
  >>> v = Variable(m, "v", domain=i)
350
343
  >>> Sum(i, c[i]*v[i]).gamsRepr()
351
- 'sum(i,(c(i) * v(i)))'
344
+ 'sum(i,c(i) * v(i))'
352
345
 
353
346
  """
354
347
  repr = super().gamsRepr()
@@ -424,7 +417,7 @@ class Product(Operation):
424
417
  >>> c = Parameter(m, "c", domain=i)
425
418
  >>> v = Variable(m, "v", domain=i)
426
419
  >>> Product(i, c[i]*v[i]).gamsRepr()
427
- 'prod(i,(c(i) * v(i)))'
420
+ 'prod(i,c(i) * v(i))'
428
421
 
429
422
  """
430
423
  repr = super().gamsRepr()
@@ -500,7 +493,7 @@ class Smin(Operation):
500
493
  >>> c = Parameter(m, "c", domain=i)
501
494
  >>> v = Variable(m, "v", domain=i)
502
495
  >>> Smin(i, c[i]*v[i]).gamsRepr()
503
- 'smin(i,(c(i) * v(i)))'
496
+ 'smin(i,c(i) * v(i))'
504
497
 
505
498
  """
506
499
  repr = super().gamsRepr()
@@ -576,7 +569,7 @@ class Smax(Operation):
576
569
  >>> c = Parameter(m, "c", domain=i)
577
570
  >>> v = Variable(m, "v", domain=i)
578
571
  >>> Smax(i, c[i]*v[i]).gamsRepr()
579
- 'smax(i,(c(i) * v(i)))'
572
+ 'smax(i,c(i) * v(i))'
580
573
 
581
574
  """
582
575
  repr = super().gamsRepr()