desdeo 1.2__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.
Files changed (122) hide show
  1. desdeo/__init__.py +8 -8
  2. desdeo/api/README.md +73 -0
  3. desdeo/api/__init__.py +15 -0
  4. desdeo/api/app.py +40 -0
  5. desdeo/api/config.py +69 -0
  6. desdeo/api/config.toml +53 -0
  7. desdeo/api/db.py +25 -0
  8. desdeo/api/db_init.py +79 -0
  9. desdeo/api/db_models.py +164 -0
  10. desdeo/api/malaga_db_init.py +27 -0
  11. desdeo/api/models/__init__.py +66 -0
  12. desdeo/api/models/archive.py +34 -0
  13. desdeo/api/models/preference.py +90 -0
  14. desdeo/api/models/problem.py +507 -0
  15. desdeo/api/models/reference_point_method.py +18 -0
  16. desdeo/api/models/session.py +46 -0
  17. desdeo/api/models/state.py +96 -0
  18. desdeo/api/models/user.py +51 -0
  19. desdeo/api/routers/_NAUTILUS.py +245 -0
  20. desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
  21. desdeo/api/routers/_NIMBUS.py +762 -0
  22. desdeo/api/routers/__init__.py +5 -0
  23. desdeo/api/routers/problem.py +110 -0
  24. desdeo/api/routers/reference_point_method.py +117 -0
  25. desdeo/api/routers/session.py +76 -0
  26. desdeo/api/routers/test.py +16 -0
  27. desdeo/api/routers/user_authentication.py +366 -0
  28. desdeo/api/schema.py +94 -0
  29. desdeo/api/tests/__init__.py +0 -0
  30. desdeo/api/tests/conftest.py +59 -0
  31. desdeo/api/tests/test_models.py +701 -0
  32. desdeo/api/tests/test_routes.py +216 -0
  33. desdeo/api/utils/database.py +274 -0
  34. desdeo/api/utils/logger.py +29 -0
  35. desdeo/core.py +27 -0
  36. desdeo/emo/__init__.py +29 -0
  37. desdeo/emo/hooks/archivers.py +172 -0
  38. desdeo/emo/methods/EAs.py +418 -0
  39. desdeo/emo/methods/__init__.py +0 -0
  40. desdeo/emo/methods/bases.py +59 -0
  41. desdeo/emo/operators/__init__.py +1 -0
  42. desdeo/emo/operators/crossover.py +780 -0
  43. desdeo/emo/operators/evaluator.py +118 -0
  44. desdeo/emo/operators/generator.py +356 -0
  45. desdeo/emo/operators/mutation.py +1053 -0
  46. desdeo/emo/operators/selection.py +1036 -0
  47. desdeo/emo/operators/termination.py +178 -0
  48. desdeo/explanations/__init__.py +6 -0
  49. desdeo/explanations/explainer.py +100 -0
  50. desdeo/explanations/utils.py +90 -0
  51. desdeo/mcdm/__init__.py +19 -0
  52. desdeo/mcdm/nautili.py +345 -0
  53. desdeo/mcdm/nautilus.py +477 -0
  54. desdeo/mcdm/nautilus_navigator.py +655 -0
  55. desdeo/mcdm/nimbus.py +417 -0
  56. desdeo/mcdm/pareto_navigator.py +269 -0
  57. desdeo/mcdm/reference_point_method.py +116 -0
  58. desdeo/problem/__init__.py +79 -0
  59. desdeo/problem/evaluator.py +561 -0
  60. desdeo/problem/gurobipy_evaluator.py +562 -0
  61. desdeo/problem/infix_parser.py +341 -0
  62. desdeo/problem/json_parser.py +944 -0
  63. desdeo/problem/pyomo_evaluator.py +468 -0
  64. desdeo/problem/schema.py +1808 -0
  65. desdeo/problem/simulator_evaluator.py +298 -0
  66. desdeo/problem/sympy_evaluator.py +244 -0
  67. desdeo/problem/testproblems/__init__.py +73 -0
  68. desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
  69. desdeo/problem/testproblems/dtlz2_problem.py +102 -0
  70. desdeo/problem/testproblems/forest_problem.py +275 -0
  71. desdeo/problem/testproblems/knapsack_problem.py +163 -0
  72. desdeo/problem/testproblems/mcwb_problem.py +831 -0
  73. desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
  74. desdeo/problem/testproblems/momip_problem.py +172 -0
  75. desdeo/problem/testproblems/nimbus_problem.py +143 -0
  76. desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
  77. desdeo/problem/testproblems/re_problem.py +492 -0
  78. desdeo/problem/testproblems/river_pollution_problem.py +434 -0
  79. desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
  80. desdeo/problem/testproblems/simple_problem.py +351 -0
  81. desdeo/problem/testproblems/simulator_problem.py +92 -0
  82. desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
  83. desdeo/problem/testproblems/zdt_problem.py +271 -0
  84. desdeo/problem/utils.py +245 -0
  85. desdeo/tools/GenerateReferencePoints.py +181 -0
  86. desdeo/tools/__init__.py +102 -0
  87. desdeo/tools/generics.py +145 -0
  88. desdeo/tools/gurobipy_solver_interfaces.py +258 -0
  89. desdeo/tools/indicators_binary.py +11 -0
  90. desdeo/tools/indicators_unary.py +375 -0
  91. desdeo/tools/interaction_schema.py +38 -0
  92. desdeo/tools/intersection.py +54 -0
  93. desdeo/tools/iterative_pareto_representer.py +99 -0
  94. desdeo/tools/message.py +234 -0
  95. desdeo/tools/ng_solver_interfaces.py +199 -0
  96. desdeo/tools/non_dominated_sorting.py +133 -0
  97. desdeo/tools/patterns.py +281 -0
  98. desdeo/tools/proximal_solver.py +99 -0
  99. desdeo/tools/pyomo_solver_interfaces.py +464 -0
  100. desdeo/tools/reference_vectors.py +462 -0
  101. desdeo/tools/scalarization.py +3138 -0
  102. desdeo/tools/scipy_solver_interfaces.py +454 -0
  103. desdeo/tools/score_bands.py +464 -0
  104. desdeo/tools/utils.py +320 -0
  105. desdeo/utopia_stuff/__init__.py +0 -0
  106. desdeo/utopia_stuff/data/1.json +15 -0
  107. desdeo/utopia_stuff/data/2.json +13 -0
  108. desdeo/utopia_stuff/data/3.json +15 -0
  109. desdeo/utopia_stuff/data/4.json +17 -0
  110. desdeo/utopia_stuff/data/5.json +15 -0
  111. desdeo/utopia_stuff/from_json.py +40 -0
  112. desdeo/utopia_stuff/reinit_user.py +38 -0
  113. desdeo/utopia_stuff/utopia_db_init.py +212 -0
  114. desdeo/utopia_stuff/utopia_problem.py +403 -0
  115. desdeo/utopia_stuff/utopia_problem_old.py +415 -0
  116. desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
  117. desdeo-2.0.0.dist-info/LICENSE +21 -0
  118. desdeo-2.0.0.dist-info/METADATA +168 -0
  119. desdeo-2.0.0.dist-info/RECORD +120 -0
  120. {desdeo-1.2.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
  121. desdeo-1.2.dist-info/METADATA +0 -16
  122. desdeo-1.2.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)