desdeo 1.1.3__py3-none-any.whl → 2.0.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.
- desdeo/__init__.py +8 -8
- desdeo/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +40 -0
- desdeo/api/config.py +69 -0
- desdeo/api/config.toml +53 -0
- desdeo/api/db.py +25 -0
- desdeo/api/db_init.py +79 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +66 -0
- desdeo/api/models/archive.py +34 -0
- desdeo/api/models/preference.py +90 -0
- desdeo/api/models/problem.py +507 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +46 -0
- desdeo/api/models/state.py +96 -0
- desdeo/api/models/user.py +51 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +762 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/problem.py +110 -0
- desdeo/api/routers/reference_point_method.py +117 -0
- desdeo/api/routers/session.py +76 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +366 -0
- desdeo/api/schema.py +94 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +59 -0
- desdeo/api/tests/test_models.py +701 -0
- desdeo/api/tests/test_routes.py +216 -0
- desdeo/api/utils/database.py +274 -0
- desdeo/api/utils/logger.py +29 -0
- desdeo/core.py +27 -0
- desdeo/emo/__init__.py +29 -0
- desdeo/emo/hooks/archivers.py +172 -0
- desdeo/emo/methods/EAs.py +418 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +59 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +780 -0
- desdeo/emo/operators/evaluator.py +118 -0
- desdeo/emo/operators/generator.py +356 -0
- desdeo/emo/operators/mutation.py +1053 -0
- desdeo/emo/operators/selection.py +1036 -0
- desdeo/emo/operators/termination.py +178 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/mcdm/__init__.py +19 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +655 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +116 -0
- desdeo/problem/__init__.py +79 -0
- desdeo/problem/evaluator.py +561 -0
- desdeo/problem/gurobipy_evaluator.py +562 -0
- desdeo/problem/infix_parser.py +341 -0
- desdeo/problem/json_parser.py +944 -0
- desdeo/problem/pyomo_evaluator.py +468 -0
- desdeo/problem/schema.py +1808 -0
- desdeo/problem/simulator_evaluator.py +298 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +73 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +275 -0
- desdeo/problem/testproblems/knapsack_problem.py +163 -0
- desdeo/problem/testproblems/mcwb_problem.py +831 -0
- desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
- desdeo/problem/testproblems/momip_problem.py +172 -0
- desdeo/problem/testproblems/nimbus_problem.py +143 -0
- desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
- desdeo/problem/testproblems/re_problem.py +492 -0
- desdeo/problem/testproblems/river_pollution_problem.py +434 -0
- desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
- desdeo/problem/testproblems/simple_problem.py +351 -0
- desdeo/problem/testproblems/simulator_problem.py +92 -0
- desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +271 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +102 -0
- desdeo/tools/generics.py +145 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +11 -0
- desdeo/tools/indicators_unary.py +375 -0
- desdeo/tools/interaction_schema.py +38 -0
- desdeo/tools/intersection.py +54 -0
- desdeo/tools/iterative_pareto_representer.py +99 -0
- desdeo/tools/message.py +234 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +133 -0
- desdeo/tools/patterns.py +281 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +464 -0
- desdeo/tools/reference_vectors.py +462 -0
- desdeo/tools/scalarization.py +3138 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +464 -0
- desdeo/tools/utils.py +320 -0
- desdeo/utopia_stuff/__init__.py +0 -0
- desdeo/utopia_stuff/data/1.json +15 -0
- desdeo/utopia_stuff/data/2.json +13 -0
- desdeo/utopia_stuff/data/3.json +15 -0
- desdeo/utopia_stuff/data/4.json +17 -0
- desdeo/utopia_stuff/data/5.json +15 -0
- desdeo/utopia_stuff/from_json.py +40 -0
- desdeo/utopia_stuff/reinit_user.py +38 -0
- desdeo/utopia_stuff/utopia_db_init.py +212 -0
- desdeo/utopia_stuff/utopia_problem.py +403 -0
- desdeo/utopia_stuff/utopia_problem_old.py +415 -0
- desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
- desdeo-2.0.0.dist-info/LICENSE +21 -0
- desdeo-2.0.0.dist-info/METADATA +168 -0
- desdeo-2.0.0.dist-info/RECORD +120 -0
- {desdeo-1.1.3.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
- desdeo-1.1.3.dist-info/METADATA +0 -18
- desdeo-1.1.3.dist-info/RECORD +0 -4
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""Defines an evaluator compatible with the Problem JSON format and transforms it into a Pyomo model."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from operator import eq as _eq
|
|
6
|
+
from operator import le as _le
|
|
7
|
+
|
|
8
|
+
import pyomo.environ as pyomo
|
|
9
|
+
|
|
10
|
+
from desdeo.problem.json_parser import FormatEnum, MathParser
|
|
11
|
+
from desdeo.problem.schema import (
|
|
12
|
+
Constant,
|
|
13
|
+
ConstraintTypeEnum,
|
|
14
|
+
Problem,
|
|
15
|
+
TensorConstant,
|
|
16
|
+
TensorVariable,
|
|
17
|
+
Variable,
|
|
18
|
+
VariableTypeEnum,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PyomoEvaluatorError(Exception):
|
|
23
|
+
"""Raised when an error within the PyomoEvaluator class is encountered."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PyomoEvaluator:
|
|
27
|
+
"""Defines an evaluator that transforms an instance of Problem into a pyomo model."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, problem: Problem):
|
|
30
|
+
"""Initializes the evaluator.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
problem (Problem): the problem to be transformed in a pyomo model.
|
|
34
|
+
"""
|
|
35
|
+
model = pyomo.ConcreteModel()
|
|
36
|
+
|
|
37
|
+
# set the parser
|
|
38
|
+
self.parse = MathParser(to_format=FormatEnum.pyomo).parse
|
|
39
|
+
|
|
40
|
+
# Add variables
|
|
41
|
+
model = self.init_variables(problem, model)
|
|
42
|
+
|
|
43
|
+
# Add constants, if any
|
|
44
|
+
if problem.constants is not None:
|
|
45
|
+
model = self.init_constants(problem, model)
|
|
46
|
+
|
|
47
|
+
# Add extra expressions, if any
|
|
48
|
+
if problem.extra_funcs is not None:
|
|
49
|
+
model = self.init_extras(problem, model)
|
|
50
|
+
|
|
51
|
+
# Add objective function expressions
|
|
52
|
+
model = self.init_objectives(problem, model)
|
|
53
|
+
|
|
54
|
+
# Add constraints, if any
|
|
55
|
+
if problem.constraints is not None:
|
|
56
|
+
model = self.init_constraints(problem, model)
|
|
57
|
+
|
|
58
|
+
# Add scalarization functions, if any
|
|
59
|
+
if problem.scalarization_funcs is not None:
|
|
60
|
+
model = self.init_scalarizations(problem, model)
|
|
61
|
+
|
|
62
|
+
self.model = model
|
|
63
|
+
self.problem = problem
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def _bounds_rule(cls, lowerbounds, upperbounds):
|
|
67
|
+
def bounds_rule(model, *args) -> tuple:
|
|
68
|
+
indices = tuple(arg - 1 for arg in args)
|
|
69
|
+
|
|
70
|
+
lower_value = lowerbounds
|
|
71
|
+
upper_value = upperbounds
|
|
72
|
+
|
|
73
|
+
for index in indices:
|
|
74
|
+
lower_value = lower_value[index]
|
|
75
|
+
upper_value = upper_value[index]
|
|
76
|
+
|
|
77
|
+
return (lower_value, upper_value)
|
|
78
|
+
|
|
79
|
+
return bounds_rule
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def _init_rule(cls, initial_values):
|
|
83
|
+
def init_rule(model, *args):
|
|
84
|
+
indices = tuple(arg - 1 for arg in args)
|
|
85
|
+
|
|
86
|
+
initial_value = initial_values
|
|
87
|
+
|
|
88
|
+
for index in indices:
|
|
89
|
+
initial_value = initial_value[index]
|
|
90
|
+
|
|
91
|
+
return initial_value
|
|
92
|
+
|
|
93
|
+
return init_rule
|
|
94
|
+
|
|
95
|
+
def init_variables(self, problem: Problem, model: pyomo.Model) -> pyomo.Model:
|
|
96
|
+
"""Add variables to the pyomo model.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
problem (Problem): problem from which to extract the variables.
|
|
100
|
+
model (pyomo.Model): the pyomo model to add the variables to.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
PyomoEvaluator: when a problem in extracting the variables is encountered.
|
|
104
|
+
I.e., the bounds of the variables are incorrect or of a non supported type.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
pyomo.Model: the pyomo model with the variables added as attributes.
|
|
108
|
+
"""
|
|
109
|
+
for var in problem.variables:
|
|
110
|
+
if isinstance(var, Variable):
|
|
111
|
+
# handle regular variables
|
|
112
|
+
lowerbound = var.lowerbound if var.lowerbound is not None else float("-inf")
|
|
113
|
+
upperbound = var.upperbound if var.upperbound is not None else float("inf")
|
|
114
|
+
|
|
115
|
+
# figure out the variable type
|
|
116
|
+
match (lowerbound >= 0, upperbound >= 0, var.variable_type):
|
|
117
|
+
case (True, True, VariableTypeEnum.integer):
|
|
118
|
+
# variable is positive integer
|
|
119
|
+
domain = pyomo.NonNegativeIntegers
|
|
120
|
+
case (False, False, VariableTypeEnum.integer):
|
|
121
|
+
# variable is negative integer
|
|
122
|
+
domain = pyomo.NegativeIntegers
|
|
123
|
+
case (False, True, VariableTypeEnum.integer):
|
|
124
|
+
# variable can be both negative an positive integer
|
|
125
|
+
domain = pyomo.Integers
|
|
126
|
+
case (True, False, VariableTypeEnum.integer):
|
|
127
|
+
# error! lower bound is greater than upper bound
|
|
128
|
+
msg = (
|
|
129
|
+
f"The lower bound {var.lowerbound} for variable {var.symbol} is greater than the "
|
|
130
|
+
f"upper bound {var.upperbound}"
|
|
131
|
+
)
|
|
132
|
+
raise PyomoEvaluatorError(msg)
|
|
133
|
+
case (True, True, VariableTypeEnum.real):
|
|
134
|
+
# variable is positive real
|
|
135
|
+
domain = pyomo.NonNegativeReals
|
|
136
|
+
case (False, False, VariableTypeEnum.real):
|
|
137
|
+
# variable is negative real
|
|
138
|
+
domain = pyomo.NegativeReals
|
|
139
|
+
case (False, True, VariableTypeEnum.real):
|
|
140
|
+
# variable can be both negative and positive real
|
|
141
|
+
domain = pyomo.Reals
|
|
142
|
+
case (True, False, VariableTypeEnum.real):
|
|
143
|
+
# error! lower bound is greater than upper bound
|
|
144
|
+
msg = (
|
|
145
|
+
f"The lower bound {var.lowerbound} for variable {var.symbol} is greater than the "
|
|
146
|
+
f"upper bound {var.upperbound}"
|
|
147
|
+
)
|
|
148
|
+
raise PyomoEvaluatorError(msg)
|
|
149
|
+
# TODO: check binary type!
|
|
150
|
+
case _:
|
|
151
|
+
msg = f"Could not figure out the type for variable {var}."
|
|
152
|
+
raise PyomoEvaluatorError(msg)
|
|
153
|
+
|
|
154
|
+
# if a variable's initial value is set, use it. Otherwise, check if the lower and upper bounds
|
|
155
|
+
# are defined, if they are, use the mid-point of the bounds, otherwise use the initial value, which is
|
|
156
|
+
# None.
|
|
157
|
+
if var.initial_value is not None:
|
|
158
|
+
initial_value = var.initial_value
|
|
159
|
+
else:
|
|
160
|
+
initial_value = (
|
|
161
|
+
var.initial_value
|
|
162
|
+
if var.lowerbound is None and var.upperbound is None
|
|
163
|
+
else (var.lowerbound + var.upperbound) / 2
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
pyomo_var = pyomo.Var(
|
|
167
|
+
name=var.name, initialize=initial_value, bounds=(var.lowerbound, var.upperbound), domain=domain
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
elif isinstance(var, TensorVariable):
|
|
171
|
+
# handle tensor variables, i.e., vectors etc..
|
|
172
|
+
# create the needed range sets
|
|
173
|
+
index_sets = [pyomo.RangeSet(1, dim_size) for dim_size in var.shape]
|
|
174
|
+
|
|
175
|
+
# TODO: check domain properly
|
|
176
|
+
if var.variable_type == VariableTypeEnum.binary:
|
|
177
|
+
domain = pyomo.Binary
|
|
178
|
+
elif var.variable_type == VariableTypeEnum.integer:
|
|
179
|
+
domain = pyomo.Integers
|
|
180
|
+
else:
|
|
181
|
+
domain = pyomo.Reals
|
|
182
|
+
|
|
183
|
+
# create the Var
|
|
184
|
+
pyomo_var = pyomo.Var(
|
|
185
|
+
*index_sets,
|
|
186
|
+
name=var.name,
|
|
187
|
+
initialize=self._init_rule(var.get_initial_values()),
|
|
188
|
+
bounds=self._bounds_rule(var.get_lowerbound_values(), var.get_upperbound_values()),
|
|
189
|
+
domain=domain,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
else:
|
|
193
|
+
msg = f"Unsupported variable type '{type(var)} encountered."
|
|
194
|
+
raise PyomoEvaluatorError(msg)
|
|
195
|
+
|
|
196
|
+
# add and then construct the variable
|
|
197
|
+
setattr(model, var.symbol, pyomo_var)
|
|
198
|
+
getattr(model, var.symbol).construct()
|
|
199
|
+
|
|
200
|
+
return model
|
|
201
|
+
|
|
202
|
+
def init_constants(self, problem: Problem, model: pyomo.Model) -> pyomo.Model:
|
|
203
|
+
"""Add constants to a pyomo model.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
problem (Problem): problem from which to extract the constants.
|
|
207
|
+
model (pyomo.Model): the pyomo model to add the constants to.
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
PyomoEvaluatorError: when the domain of a constant cannot be figured out.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
pyomo.Model: the pyomo model with the constants added as attributes.
|
|
214
|
+
"""
|
|
215
|
+
for con in problem.constants:
|
|
216
|
+
# Handle regular constnants
|
|
217
|
+
if isinstance(con, Constant):
|
|
218
|
+
# figure out the domain of the constant
|
|
219
|
+
match (isinstance(con.value, int), isinstance(con.value, float), con.value >= 0):
|
|
220
|
+
case (True, False, True):
|
|
221
|
+
# positive integer
|
|
222
|
+
domain = pyomo.NonNegativeIntegers
|
|
223
|
+
case (True, False, False):
|
|
224
|
+
# negative integer
|
|
225
|
+
domain = pyomo.NegativeIntegers
|
|
226
|
+
case (False, True, True):
|
|
227
|
+
# positive real
|
|
228
|
+
domain = pyomo.NonNegativeReals
|
|
229
|
+
case (False, True, False):
|
|
230
|
+
# negative real
|
|
231
|
+
domain = pyomo.NegativeReals
|
|
232
|
+
case _:
|
|
233
|
+
# not possible, something went wrong
|
|
234
|
+
msg = f"Failed to figure out the domain for the constant {con.symbol}."
|
|
235
|
+
raise PyomoEvaluatorError(msg)
|
|
236
|
+
|
|
237
|
+
pyomo_param = pyomo.Param(name=con.name, default=con.value, domain=domain)
|
|
238
|
+
|
|
239
|
+
elif isinstance(con, TensorConstant):
|
|
240
|
+
# handle TensorConstants, like vectors
|
|
241
|
+
# create the needed range sets
|
|
242
|
+
index_sets = [pyomo.RangeSet(1, dim_size) for dim_size in con.shape]
|
|
243
|
+
|
|
244
|
+
# TODO: check domain properly
|
|
245
|
+
# for now, constants are always assumed to be real (which is quite safe to do...)
|
|
246
|
+
domain = pyomo.Reals
|
|
247
|
+
|
|
248
|
+
# create the Con
|
|
249
|
+
pyomo_param = pyomo.Param(
|
|
250
|
+
*index_sets,
|
|
251
|
+
name=con.name,
|
|
252
|
+
initialize=self._init_rule(con.get_values()),
|
|
253
|
+
domain=domain,
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
msg = f"Unsupported constant type '{type(con)}' encountered."
|
|
257
|
+
raise PyomoEvaluatorError(msg)
|
|
258
|
+
|
|
259
|
+
setattr(model, con.symbol, pyomo_param)
|
|
260
|
+
|
|
261
|
+
return model
|
|
262
|
+
|
|
263
|
+
def init_extras(self, problem: Problem, model: pyomo.Model) -> pyomo.Model:
|
|
264
|
+
"""Add extra function expressions to a pyomo model.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
problem (Problem): problem from which the extract the extra function expressions.
|
|
268
|
+
model (pyomo.Model): the pyomo model to add the extra function expressions to.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
pyomo.Model: the pyomo model with the expressions added as attributes.
|
|
272
|
+
"""
|
|
273
|
+
for extra in problem.extra_funcs:
|
|
274
|
+
pyomo_expr = self.parse(extra.func, model)
|
|
275
|
+
|
|
276
|
+
setattr(model, extra.symbol, pyomo.Expression(expr=pyomo_expr))
|
|
277
|
+
|
|
278
|
+
return model
|
|
279
|
+
|
|
280
|
+
def init_objectives(self, problem: Problem, model: pyomo.Model) -> pyomo.Model:
|
|
281
|
+
"""Add objective function expressions to a pyomo model.
|
|
282
|
+
|
|
283
|
+
Does not yet add any actual pyomo objectives, only the expressions of the objectives.
|
|
284
|
+
A pyomo solved must add the appropiate pyomo objective before solving.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
problem (Problem): problem from which to extract the objective function expresions.
|
|
288
|
+
model (pyomo.Model): the pyomo model to add the expressions to.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
pyomo.Model: the pyomo model with the objective expressions added as pyomo Objectives.
|
|
292
|
+
The objectives are deactivated by default.
|
|
293
|
+
"""
|
|
294
|
+
for obj in problem.objectives:
|
|
295
|
+
pyomo_expr = self.parse(obj.func, model)
|
|
296
|
+
|
|
297
|
+
setattr(model, obj.symbol, pyomo.Expression(expr=pyomo_expr))
|
|
298
|
+
|
|
299
|
+
# the obj.symbol_min objectives are used when optimizing and building scalarizations etc...
|
|
300
|
+
new_expr = (-1) * pyomo_expr if obj.maximize else pyomo_expr
|
|
301
|
+
setattr(model, f"{obj.symbol}_min", pyomo.Expression(expr=new_expr))
|
|
302
|
+
|
|
303
|
+
return model
|
|
304
|
+
|
|
305
|
+
def init_constraints(self, problem: Problem, model: pyomo.Model) -> pyomo.Model:
|
|
306
|
+
"""Add constraint expressions to a pyomo model.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
problem (Problem): the problem from which to extract the constraint function expressions.
|
|
310
|
+
model (pyomo.Model): the pyomo model to add the exprssions to.
|
|
311
|
+
|
|
312
|
+
Raises:
|
|
313
|
+
PyomoEvaluatorError: when an unsupported constraint type is encountered.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
pyomo.Model: the pyomo model with the constraint expressions added as pyomo Constraints.
|
|
317
|
+
"""
|
|
318
|
+
for cons in problem.constraints:
|
|
319
|
+
pyomo_expr = self.parse(cons.func, model)
|
|
320
|
+
|
|
321
|
+
match con_type := cons.cons_type:
|
|
322
|
+
case ConstraintTypeEnum.LTE:
|
|
323
|
+
# constraints in DESDEO are defined such that they must be less than zero
|
|
324
|
+
pyomo_expr = _le(pyomo_expr, 0)
|
|
325
|
+
case ConstraintTypeEnum.EQ:
|
|
326
|
+
pyomo_expr = _eq(pyomo_expr, 0)
|
|
327
|
+
case _:
|
|
328
|
+
msg = f"Constraint type of {con_type} not supported. Must be one of {ConstraintTypeEnum}."
|
|
329
|
+
raise PyomoEvaluatorError(msg)
|
|
330
|
+
|
|
331
|
+
cons_expr = pyomo.Constraint(expr=pyomo_expr, name=cons.name)
|
|
332
|
+
|
|
333
|
+
setattr(model, cons.symbol, cons_expr)
|
|
334
|
+
|
|
335
|
+
return model
|
|
336
|
+
|
|
337
|
+
def init_scalarizations(self, problem: Problem, model: pyomo.Model) -> pyomo.Model:
|
|
338
|
+
"""Add scalrization expressions to a pyomo model.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
problem (Problem): the problem from which to extract thescalarization function expressions.
|
|
342
|
+
model (pyomo.Model): the pyomo model to add the expressions to.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
pyomo.Model: the pyomo model with the scalarization expressions addedd as pyomo Objectives.
|
|
346
|
+
The objectives are deactivated by default. Scalarization functions are always minimized.
|
|
347
|
+
"""
|
|
348
|
+
for scal in problem.scalarization_funcs:
|
|
349
|
+
pyomo_expr = self.parse(scal.func, model)
|
|
350
|
+
|
|
351
|
+
setattr(model, scal.symbol, pyomo.Expression(expr=pyomo_expr))
|
|
352
|
+
|
|
353
|
+
return model
|
|
354
|
+
|
|
355
|
+
def evaluate(
|
|
356
|
+
self, xs: dict[str, float | int | bool | Iterable[float | int | bool]]
|
|
357
|
+
) -> dict[str, float | int | bool | list[dict[str, float | int | bool]]]:
|
|
358
|
+
"""Evaluate the current pyomo model with the given decision variable values.
|
|
359
|
+
|
|
360
|
+
Warning:
|
|
361
|
+
This should not be used for actually solving the pyomo model! For debugging mostly.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
xs (dict[str, float | int | bool | Iterable[float | int | bool]]): a dict with the decision variable symbols
|
|
365
|
+
as the keys followed by the corresponding decision variable values, which can also
|
|
366
|
+
be represented by a list for multiple values. The symbols
|
|
367
|
+
must match the symbols defined for the decision variables defined in the `Problem` being solved.
|
|
368
|
+
Each list in the dict should contain the same number of values.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
dict | list[dict]: the results of evaluating the pyomo model with its variable values set to the values
|
|
372
|
+
found in xs.
|
|
373
|
+
"""
|
|
374
|
+
res = []
|
|
375
|
+
n_samples = len(next(iter(xs.values()))) if isinstance(next(iter(xs.values())), list) else 1
|
|
376
|
+
for i in range(n_samples):
|
|
377
|
+
for var in self.problem.variables:
|
|
378
|
+
x = xs[var.symbol][i]
|
|
379
|
+
if isinstance(var, Variable):
|
|
380
|
+
# scalar variable
|
|
381
|
+
setattr(self.model, var.symbol, x)
|
|
382
|
+
else:
|
|
383
|
+
# tensor variable
|
|
384
|
+
indices = itertools.product(*[range(1, dim + 1) for dim in var.shape]) # 1-based indexing
|
|
385
|
+
for idx in indices:
|
|
386
|
+
elem = x
|
|
387
|
+
for j in idx:
|
|
388
|
+
elem = elem[j - 1] # 0-based indexing
|
|
389
|
+
|
|
390
|
+
getattr(self.model, var.symbol)[idx] = elem # 1-based indexing
|
|
391
|
+
|
|
392
|
+
res.append(self.get_values())
|
|
393
|
+
|
|
394
|
+
return res if len(res) > 1 else res[0]
|
|
395
|
+
|
|
396
|
+
def get_values(self) -> dict[str, float | int | bool]: # noqa: C901
|
|
397
|
+
"""Get the values from the pyomo model in dict.
|
|
398
|
+
|
|
399
|
+
The keys of the dict will be the symbols defined in the problem utilized to initialize the evaluator.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
dict[str, float | int | bool]: a dict with keys equivalent to the symbols defined in self.problem.
|
|
403
|
+
"""
|
|
404
|
+
result_dict = {}
|
|
405
|
+
|
|
406
|
+
for var in self.problem.variables:
|
|
407
|
+
if isinstance(var, Variable):
|
|
408
|
+
result_dict[var.symbol] = pyomo.value(getattr(self.model, var.symbol))
|
|
409
|
+
elif isinstance(var, TensorVariable):
|
|
410
|
+
result_dict[var.symbol] = getattr(self.model, var.symbol).get_values()
|
|
411
|
+
else:
|
|
412
|
+
msg = f"Unsupported variable type {type(var)} encountered."
|
|
413
|
+
raise PyomoEvaluatorError(msg)
|
|
414
|
+
|
|
415
|
+
for obj in self.problem.objectives:
|
|
416
|
+
result_dict[obj.symbol] = pyomo.value(getattr(self.model, obj.symbol))
|
|
417
|
+
|
|
418
|
+
if self.problem.constants is not None:
|
|
419
|
+
for con in self.problem.constants:
|
|
420
|
+
if isinstance(con, Constant):
|
|
421
|
+
result_dict[con.symbol] = pyomo.value(getattr(self.model, con.symbol))
|
|
422
|
+
elif isinstance(con, TensorConstant):
|
|
423
|
+
result_dict[con.symbol] = getattr(self.model, con.symbol).extract_values()
|
|
424
|
+
else:
|
|
425
|
+
msg = f"Unsupported variable type {type(var)} encountered."
|
|
426
|
+
raise PyomoEvaluatorError(msg)
|
|
427
|
+
|
|
428
|
+
if self.problem.extra_funcs is not None:
|
|
429
|
+
for extra in self.problem.extra_funcs:
|
|
430
|
+
result_dict[extra.symbol] = pyomo.value(getattr(self.model, extra.symbol))
|
|
431
|
+
|
|
432
|
+
if self.problem.constraints is not None:
|
|
433
|
+
for const in self.problem.constraints:
|
|
434
|
+
result_dict[const.symbol] = pyomo.value(getattr(self.model, const.symbol))
|
|
435
|
+
|
|
436
|
+
if self.problem.scalarization_funcs is not None:
|
|
437
|
+
for scal in self.problem.scalarization_funcs:
|
|
438
|
+
result_dict[scal.symbol] = pyomo.value(getattr(self.model, scal.symbol))
|
|
439
|
+
|
|
440
|
+
return result_dict
|
|
441
|
+
|
|
442
|
+
def set_optimization_target(self, target: str):
|
|
443
|
+
"""Creates a minimization objective from the target attribute of the pyomo model.
|
|
444
|
+
|
|
445
|
+
The attribute name of the pyomo objective will be target + _objective, e.g.,
|
|
446
|
+
'f_1' will become 'f_1_objective'. This is done so that the original f_1 expressions
|
|
447
|
+
attribute does not get reassigned.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
target (str): an str representing a symbol.
|
|
451
|
+
|
|
452
|
+
Raises:
|
|
453
|
+
PyomoEvaluatorError: the given target was not an attribute of the pyomo model.
|
|
454
|
+
"""
|
|
455
|
+
if not hasattr(self.model, target):
|
|
456
|
+
msg = f"The pyomo model has no attribute {target}."
|
|
457
|
+
raise PyomoEvaluatorError(msg)
|
|
458
|
+
|
|
459
|
+
# delete any existing objectives, if any
|
|
460
|
+
for obj in self.model.component_objects(pyomo.Objective, active=True):
|
|
461
|
+
obj.deactivate()
|
|
462
|
+
|
|
463
|
+
obj_expr = getattr(self.model, target)
|
|
464
|
+
|
|
465
|
+
objective = pyomo.Objective(expr=obj_expr, sense=pyomo.minimize, name=target)
|
|
466
|
+
|
|
467
|
+
# add the postfix '_objective' to the attribute name of the pyomo objective
|
|
468
|
+
setattr(self.model, f"{target}_objective", objective)
|