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