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.
- {gamspy-1.14.0 → gamspy-1.15.1}/PKG-INFO +3 -3
- {gamspy-1.14.0 → gamspy-1.15.1}/pyproject.toml +3 -3
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/condition.py +5 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/expression.py +283 -133
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/operable.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/operation.py +6 -13
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/backend.py +18 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/engine.py +2 -7
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/local.py +2 -7
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/neos.py +2 -6
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/cli.py +2 -8
- gamspy-1.15.1/src/gamspy/_cli/gdx.py +276 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/install.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/retrieve.py +13 -6
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_config.py +5 -5
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_container.py +58 -8
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_convert.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_miro.py +5 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_options.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/alias.py +12 -2
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/equation.py +54 -20
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_equation.py +18 -4
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_parameter.py +47 -51
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_set.py +16 -5
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_variable.py +17 -4
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/parameter.py +24 -19
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/set.py +23 -19
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/symbol.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/variable.py +22 -17
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_validation.py +44 -8
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/__init__.py +8 -1
- gamspy-1.15.1/src/gamspy/formulations/ml/__init__.py +11 -0
- gamspy-1.15.1/src/gamspy/formulations/ml/gradient_boosting.py +186 -0
- gamspy-1.15.1/src/gamspy/formulations/ml/random_forest.py +168 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/ml/regression_tree.py +223 -122
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/avgpool2d.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/conv1d.py +17 -4
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/conv2d.py +18 -4
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/linear.py +17 -4
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/mpool2d.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/torch_sequential.py +8 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/piecewise.py +7 -9
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/shape.py +1 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/__init__.py +2 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/activation.py +131 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/PKG-INFO +3 -3
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/SOURCES.txt +4 -1
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/requires.txt +2 -2
- {gamspy-1.14.0 → gamspy-1.15.1}/tests/test_gamspy.py +1 -1
- gamspy-1.14.0/src/gamspy/formulations/ml/__init__.py +0 -4
- {gamspy-1.14.0 → gamspy-1.15.1}/LICENSE +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/README.md +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/README_PYPI.md +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/setup.cfg +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/__main__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/domain.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_algebra/number.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_backend/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/list.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/probe.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/run.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/show.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/uninstall.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_cli/util.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_database.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_extrinsic.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_model.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_model_instance.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_serialization.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/implicits/implicit_symbol.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_symbols/universe_alias.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_types.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/_workspace.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/exceptions.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/ml/decision_tree_struct.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/__init__.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/maxpool2d.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/formulations/nn/minpool2d.py +0 -0
- {gamspy-1.14.0/src/gamspy/formulations/nn → gamspy-1.15.1/src/gamspy/formulations}/utils.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/log_power.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/matrix.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/misc.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/probability.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/math/trigonometric.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/py.typed +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/utils.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy/version.py +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/dependency_links.txt +0 -0
- {gamspy-1.14.0 → gamspy-1.15.1}/src/gamspy.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
35
|
-
Requires-Dist: gamspy_base==50.
|
|
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.
|
|
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.
|
|
40
|
-
"gamspy_base == 50.
|
|
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
|
-
|
|
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
|
-
'
|
|
327
|
+
'a * b'
|
|
66
328
|
|
|
67
329
|
"""
|
|
68
330
|
|
|
69
331
|
def __init__(
|
|
70
332
|
self,
|
|
71
333
|
left: OperableType | ImplicitEquation | None,
|
|
72
|
-
|
|
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.
|
|
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
|
|
349
|
+
if operator == "=" and isinstance(right, Expression):
|
|
88
350
|
right._fix_equalities()
|
|
89
351
|
|
|
90
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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]}_
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
572
|
+
'smax(i,c(i) * v(i))'
|
|
580
573
|
|
|
581
574
|
"""
|
|
582
575
|
repr = super().gamsRepr()
|