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.
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.1.3.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
  121. desdeo-1.1.3.dist-info/METADATA +0 -18
  122. desdeo-1.1.3.dist-info/RECORD +0 -4
@@ -0,0 +1,701 @@
1
+ """Tests related to the SQLModels."""
2
+
3
+ from sqlmodel import Session, select
4
+
5
+ from desdeo.api.models import (
6
+ ArchiveEntryBase,
7
+ ArchiveEntryDB,
8
+ Bounds,
9
+ ConstantDB,
10
+ ConstraintDB,
11
+ DiscreteRepresentationDB,
12
+ ExtraFunctionDB,
13
+ InteractiveSessionDB,
14
+ ObjectiveDB,
15
+ PreferenceDB,
16
+ ProblemDB,
17
+ ReferencePoint,
18
+ RPMState,
19
+ ScalarizationFunctionDB,
20
+ SimulatorDB,
21
+ StateDB,
22
+ TensorConstantDB,
23
+ TensorVariableDB,
24
+ User,
25
+ VariableDB,
26
+ )
27
+ from desdeo.mcdm import rpm_solve_solutions
28
+ from desdeo.problem.schema import (
29
+ Constant,
30
+ Constraint,
31
+ ConstraintTypeEnum,
32
+ DiscreteRepresentation,
33
+ ExtraFunction,
34
+ Objective,
35
+ ObjectiveTypeEnum,
36
+ Problem,
37
+ ScalarizationFunction,
38
+ Simulator,
39
+ TensorConstant,
40
+ TensorVariable,
41
+ Variable,
42
+ VariableTypeEnum,
43
+ )
44
+ from desdeo.problem.testproblems import (
45
+ binh_and_korn,
46
+ dtlz2,
47
+ momip_ti2,
48
+ momip_ti7,
49
+ nimbus_test_problem,
50
+ pareto_navigator_test_problem,
51
+ re21,
52
+ re22,
53
+ re23,
54
+ re24,
55
+ river_pollution_problem,
56
+ river_pollution_problem_discrete,
57
+ simple_data_problem,
58
+ simple_knapsack,
59
+ simple_knapsack_vectors,
60
+ simple_linear_test_problem,
61
+ simple_scenario_test_problem,
62
+ simple_test_problem,
63
+ spanish_sustainability_problem,
64
+ zdt1,
65
+ )
66
+ from desdeo.tools import available_solvers
67
+
68
+
69
+ def compare_models(
70
+ model_1,
71
+ model_2,
72
+ unordered_fields=None,
73
+ ) -> bool:
74
+ """Compares two Pydantic models.
75
+
76
+ Args:
77
+ model_1 (Any): Pydantic model 1.
78
+ model_2 (Any): Pydantic model 2.
79
+ unordered_fields (list[str]): field names that are unordered and should be compared for
80
+ having the same contents.
81
+
82
+ Returns:
83
+ bool: Whether the two models have identical contents.
84
+ """
85
+ if unordered_fields is None:
86
+ unordered_fields = [
87
+ "variables",
88
+ "constants",
89
+ "objectives",
90
+ "constraints",
91
+ "extra_funcs",
92
+ "simulators",
93
+ "scenario_keys",
94
+ ]
95
+
96
+ dict_1 = model_1.model_dump()
97
+ dict_2 = model_2.model_dump()
98
+
99
+ for field in unordered_fields:
100
+ if field in dict_1 and field in dict_2 and isinstance(dict_1[field], list) and isinstance(dict_2[field], list):
101
+ if len(dict_1[field]) != len(dict_2[field]):
102
+ return False
103
+
104
+ for key_1, key_2 in zip(dict_1, dict_2, strict=True):
105
+ if key_1 not in dict_2 or key_2 not in dict_1:
106
+ return False
107
+
108
+ if dict_1[key_1] != dict_1[key_2]:
109
+ return False
110
+
111
+ if dict_2[key_1] != dict_2[key_2]:
112
+ return False
113
+
114
+ del dict_1[field], dict_2[field]
115
+
116
+ return dict_1 == dict_2
117
+
118
+
119
+ def test_tensor_constant(session_and_user: dict[str, Session | list[User]]):
120
+ """Test that a tensor constant can be transformed to an SQLModel and back after adding it to the database."""
121
+ session = session_and_user["session"]
122
+
123
+ t_tensor = TensorConstant(name="tensor", symbol="T", shape=[2, 2], values=[[1, 2], [3, 4]])
124
+ t_tensor_dump = t_tensor.model_dump()
125
+ t_tensor_dump["problem_id"] = 1
126
+
127
+ db_tensor = TensorConstantDB.model_validate(t_tensor_dump)
128
+
129
+ session.add(db_tensor)
130
+ session.commit()
131
+
132
+ statement = select(TensorConstantDB).where(TensorConstantDB.problem_id == 1)
133
+ from_db_tensor = session.exec(statement).first()
134
+
135
+ # check that original added TensorConstant and fetched match
136
+ assert db_tensor == from_db_tensor
137
+
138
+ from_db_tensor_dump = from_db_tensor.model_dump()
139
+ t_tensor_validated = TensorConstant.model_validate(from_db_tensor_dump)
140
+
141
+ assert t_tensor_validated == t_tensor
142
+
143
+
144
+ def test_constant(session_and_user: dict[str, Session | list[User]]):
145
+ """Test that a scalar constant can be transformed to an SQLModel and back after adding it to the database."""
146
+ session = session_and_user["session"]
147
+
148
+ constant = Constant(name="constant", symbol="c", value=69.420)
149
+ constant_dump = constant.model_dump()
150
+ constant_dump["problem_id"] = 1
151
+
152
+ db_constant = ConstantDB.model_validate(constant_dump)
153
+
154
+ session.add(db_constant)
155
+ session.commit()
156
+
157
+ statement = select(ConstantDB).where(ConstantDB.problem_id == 1)
158
+ from_db_constant = session.exec(statement).first()
159
+
160
+ assert db_constant == from_db_constant
161
+
162
+ from_db_constant_dump = from_db_constant.model_dump()
163
+ constant_validated = Constant.model_validate(from_db_constant_dump)
164
+
165
+ assert constant_validated == constant
166
+
167
+
168
+ def test_variable(session_and_user: dict[str, Session | list[User]]):
169
+ """Test that a scalar variable can be transformed to an SQLModel and back after adding it to the database."""
170
+ session = session_and_user["session"]
171
+
172
+ variable = Variable(
173
+ name="test variable",
174
+ symbol="x_1",
175
+ initial_value=69,
176
+ lowerbound=42,
177
+ upperbound=420,
178
+ variable_type=VariableTypeEnum.integer,
179
+ )
180
+
181
+ variable_dump = variable.model_dump()
182
+ variable_dump["problem_id"] = 1
183
+
184
+ db_variable = VariableDB.model_validate(variable_dump)
185
+
186
+ session.add(db_variable)
187
+ session.commit()
188
+ session.refresh(db_variable)
189
+
190
+ from_db_variable = session.get(VariableDB, db_variable.id)
191
+
192
+ assert db_variable == from_db_variable
193
+
194
+ from_db_variable_dump = from_db_variable.model_dump()
195
+ variable_validated = Variable.model_validate(from_db_variable_dump)
196
+
197
+ assert variable_validated == variable
198
+
199
+
200
+ def test_tensor_variable(session_and_user: dict[str, Session | list[User]]):
201
+ """Test that a tensor variable can be transformed to an SQLModel and back after adding it to the database."""
202
+ session = session_and_user["session"]
203
+
204
+ t_variable = TensorVariable(
205
+ name="test variable",
206
+ symbol="X",
207
+ shape=[2, 2],
208
+ initial_values=[[1, 2], [3, 4]],
209
+ lowerbounds=[[0, 1], [1, 0]],
210
+ upperbounds=[[99, 89], [88, 77]],
211
+ variable_type=VariableTypeEnum.integer,
212
+ )
213
+
214
+ t_variable_dump = t_variable.model_dump()
215
+ t_variable_dump["problem_id"] = 69
216
+
217
+ db_t_variable = TensorVariableDB.model_validate(t_variable_dump)
218
+
219
+ session.add(db_t_variable)
220
+ session.commit()
221
+ session.refresh(db_t_variable)
222
+
223
+ from_db_t_variable = session.get(TensorVariableDB, db_t_variable.id)
224
+
225
+ assert db_t_variable == from_db_t_variable
226
+
227
+ from_db_t_variable_dump = from_db_t_variable.model_dump()
228
+ t_variable_validated = TensorVariable.model_validate(from_db_t_variable_dump)
229
+
230
+ assert t_variable_validated == t_variable
231
+
232
+
233
+ def test_objective(session_and_user: dict[str, Session | list[User]]):
234
+ """Test that an objective can be transformed to an SQLModel and back after adding it to the database."""
235
+ session = session_and_user["session"]
236
+
237
+ objective = Objective(
238
+ name="Test Objective",
239
+ symbol="f_1",
240
+ func="x_1 + x_2 + Sin(y)",
241
+ objective_type=ObjectiveTypeEnum.analytical,
242
+ ideal=10.5,
243
+ nadir=20.0,
244
+ maximize=False,
245
+ scenario_keys=["s_1", "s_2"],
246
+ unit="m",
247
+ is_convex=False,
248
+ is_linear=True,
249
+ is_twice_differentiable=True,
250
+ simulator_path="/dev/null",
251
+ surrogates=["/var/log", "/dev/sda/sda1"],
252
+ )
253
+
254
+ objective_dump = objective.model_dump()
255
+ objective_dump["problem_id"] = 420 # yes
256
+
257
+ db_objective = ObjectiveDB.model_validate(objective_dump)
258
+
259
+ session.add(db_objective)
260
+ session.commit()
261
+ session.refresh(db_objective)
262
+
263
+ from_db_objective = session.get(ObjectiveDB, db_objective.id)
264
+
265
+ assert db_objective == from_db_objective
266
+
267
+ from_db_objective_dump = from_db_objective.model_dump()
268
+ objective_validated = Objective.model_validate(from_db_objective_dump)
269
+
270
+ assert objective_validated == objective
271
+
272
+
273
+ def test_constraint(session_and_user: dict[str, Session | list[User]]):
274
+ """Test that an constraint can be transformed to an SQLModel and back after adding it to the database."""
275
+ session = session_and_user["session"]
276
+
277
+ constraint = Constraint(
278
+ name="Test Constraint",
279
+ symbol="g_1",
280
+ func="x_1 + x_1 + x_1 - 10",
281
+ cons_type=ConstraintTypeEnum.LTE,
282
+ is_convex=True,
283
+ is_linear=False,
284
+ is_twice_differentiable=False,
285
+ scenario_keys=["Abloy", "MasterLock", "MasterLockToOpenMasterLock"],
286
+ simulator_path="/dev/null/aaaaaaaaaa",
287
+ surrogates=["/var/log", "/dev/sda/sda1/no"],
288
+ )
289
+
290
+ constraint_dump = constraint.model_dump()
291
+ constraint_dump["problem_id"] = 72
292
+
293
+ db_constraint = ConstraintDB.model_validate(constraint_dump)
294
+
295
+ session.add(db_constraint)
296
+ session.commit()
297
+ session.refresh(db_constraint)
298
+
299
+ from_db_constraint = session.get(ConstraintDB, db_constraint.id)
300
+
301
+ assert db_constraint == from_db_constraint
302
+
303
+ from_db_constraint_dump = from_db_constraint.model_dump()
304
+ constraint_validated = Constraint.model_validate(from_db_constraint_dump)
305
+
306
+ assert constraint_validated == constraint
307
+
308
+
309
+ def test_scalarization_function(session_and_user: dict[str, Session | list[User]]):
310
+ """Test that a scalarization function can be transformed to an SQLModel and back after adding it to the database."""
311
+ session = session_and_user["session"]
312
+
313
+ scalarization = ScalarizationFunction(
314
+ name="Test ScalarizationFunction",
315
+ symbol="s_1",
316
+ func="x_1 + x_1 + x_1 - 10 - 99999 + Sin(y_3)",
317
+ is_convex=True,
318
+ is_linear=True,
319
+ is_twice_differentiable=False,
320
+ scenario_keys=["Abloy", "MasterLock", "MasterLockToOpenMasterLock", "MyHandsHurt"],
321
+ )
322
+
323
+ scalarization_dump = scalarization.model_dump()
324
+ scalarization_dump["problem_id"] = 2
325
+
326
+ db_scalarization = ScalarizationFunctionDB.model_validate(scalarization_dump)
327
+
328
+ session.add(db_scalarization)
329
+ session.commit()
330
+ session.refresh(db_scalarization)
331
+
332
+ from_db_scalarization = session.get(ScalarizationFunctionDB, db_scalarization.id)
333
+
334
+ assert db_scalarization == from_db_scalarization
335
+
336
+ from_db_scalarization_dump = from_db_scalarization.model_dump()
337
+ scalarization_validated = ScalarizationFunction.model_validate(from_db_scalarization_dump)
338
+
339
+ assert scalarization_validated == scalarization
340
+
341
+
342
+ def test_extra_function(session_and_user: dict[str, Session | list[User]]):
343
+ """Test that an extra function can be transformed to an SQLModel and back after adding it to the database."""
344
+ session = session_and_user["session"]
345
+
346
+ extra = ExtraFunction(
347
+ name="Test ExtraFunction",
348
+ symbol="extra_1",
349
+ func="x_1 + x_2 + x_9000 - 10 - 99999 + Cos(y_3)",
350
+ is_convex=False,
351
+ is_linear=False,
352
+ is_twice_differentiable=True,
353
+ scenario_keys=["Abloy", "MasterLock", "MasterLockToOpenMasterLock", "MyHandsHurt", "RunningOutOfIdeas"],
354
+ )
355
+
356
+ extra_dump = extra.model_dump()
357
+ extra_dump["problem_id"] = 5
358
+
359
+ db_extra = ExtraFunctionDB.model_validate(extra_dump)
360
+
361
+ session.add(db_extra)
362
+ session.commit()
363
+ session.refresh(db_extra)
364
+
365
+ from_db_extra = session.get(ExtraFunctionDB, db_extra.id)
366
+
367
+ assert db_extra == from_db_extra
368
+
369
+ from_db_extra_dump = from_db_extra.model_dump()
370
+ extra_validated = ExtraFunction.model_validate(from_db_extra_dump)
371
+
372
+ assert extra_validated == extra
373
+
374
+
375
+ def test_discrete_representation(session_and_user: dict[str, Session | list[User]]):
376
+ """Test that a DiscreteRepresentation can be transformed to an SQLModel and back after adding it to the database."""
377
+ session = session_and_user["session"]
378
+
379
+ discrete = DiscreteRepresentation(
380
+ variable_values={"x_1": [1, 2, 3, 4, 5], "x_2": [6, 7, 8, 9, 10]},
381
+ objective_values={"f_1": [0.5, 1.0, 2.0, 3.5, 9], "f_2": [-1, -2, -3, -4, -5]},
382
+ non_dominated=True,
383
+ )
384
+
385
+ discrete_dump = discrete.model_dump()
386
+ discrete_dump["problem_id"] = 3
387
+
388
+ db_discrete = DiscreteRepresentationDB.model_validate(discrete_dump)
389
+
390
+ session.add(db_discrete)
391
+ session.commit()
392
+ session.refresh(db_discrete)
393
+
394
+ from_db_discrete = session.get(DiscreteRepresentationDB, db_discrete.id)
395
+
396
+ assert db_discrete == from_db_discrete
397
+
398
+ from_db_discrete_dump = from_db_discrete.model_dump()
399
+ discrete_validated = DiscreteRepresentation.model_validate(from_db_discrete_dump)
400
+
401
+ assert discrete_validated == discrete
402
+
403
+
404
+ def test_simulator(session_and_user: dict[str, Session | list[User]]):
405
+ """Test that a Simulator can be transformed to an SQLModel and back after adding it to the database."""
406
+ session = session_and_user["session"]
407
+
408
+ simulator = Simulator(
409
+ file="/my/favorite/simulator.exe",
410
+ name="simulator",
411
+ symbol="simu",
412
+ parameter_options={"param1": 69, "nice": True},
413
+ )
414
+
415
+ simulator_dump = simulator.model_dump()
416
+ simulator_dump["problem_id"] = 2
417
+
418
+ db_simulator = SimulatorDB.model_validate(simulator_dump)
419
+
420
+ session.add(db_simulator)
421
+ session.commit()
422
+ session.refresh(db_simulator)
423
+
424
+ from_db_simulator = session.get(SimulatorDB, db_simulator.id)
425
+
426
+ assert db_simulator == from_db_simulator
427
+
428
+ from_db_simulator_dump = from_db_simulator.model_dump()
429
+ simulator_validated = Simulator.model_validate(from_db_simulator_dump)
430
+
431
+ assert simulator_validated == simulator
432
+
433
+
434
+ def test_from_pydantic(session_and_user: dict[str, Session | list[User]]):
435
+ """Test that a problem can be added and fetched from the database correctly."""
436
+ session = session_and_user["session"]
437
+ user = session_and_user["user"]
438
+
439
+ problem_binh = binh_and_korn()
440
+
441
+ problemdb = ProblemDB.from_problem(problem_binh, user=user)
442
+ session.add(problemdb)
443
+ session.commit()
444
+ session.refresh(problemdb)
445
+
446
+ from_db_problem = session.get(ProblemDB, problemdb.id)
447
+
448
+ assert compare_models(problemdb, from_db_problem)
449
+
450
+
451
+ def test_from_problem_to_d_and_back(session_and_user: dict[str, Session | list[User]]):
452
+ """Test that Problem converts to ProblemDB and back."""
453
+ session = session_and_user["session"]
454
+ user = session_and_user["user"]
455
+
456
+ problems = [
457
+ binh_and_korn(),
458
+ river_pollution_problem(),
459
+ simple_knapsack(),
460
+ simple_data_problem(),
461
+ simple_scenario_test_problem(),
462
+ re24(),
463
+ simple_knapsack_vectors(),
464
+ spanish_sustainability_problem(),
465
+ zdt1(10),
466
+ dtlz2(5, 3),
467
+ momip_ti2(),
468
+ momip_ti7(),
469
+ nimbus_test_problem(),
470
+ pareto_navigator_test_problem(),
471
+ river_pollution_problem_discrete(),
472
+ simple_test_problem(),
473
+ simple_linear_test_problem(),
474
+ re21(),
475
+ re22(),
476
+ re23(),
477
+ ]
478
+
479
+ for problem in problems:
480
+ # convert to SQLModel
481
+ problem_db = ProblemDB.from_problem(problem, user=user)
482
+
483
+ session.add(problem_db)
484
+ session.commit()
485
+ session.refresh(problem_db)
486
+
487
+ from_db = session.get(ProblemDB, problem_db.id)
488
+
489
+ # Back to pure pydantic
490
+ problem_db = Problem.from_problemdb(from_db)
491
+
492
+ # check that problems are equal
493
+ assert compare_models(problem, problem_db)
494
+
495
+
496
+ def test_archive_entry(session_and_user: dict[str, Session | list[User]]):
497
+ """Test that the archive works as intended."""
498
+ session = session_and_user["session"]
499
+ user = session_and_user["user"]
500
+
501
+ problem = dtlz2(n_variables=5, n_objectives=3)
502
+ problem_db = ProblemDB.from_problem(problem, user)
503
+
504
+ session.add(problem_db)
505
+ session.commit()
506
+ session.refresh(problem_db)
507
+
508
+ variable_values = {"x_1": 0.3, "x_2": 0.8, "x_3": 0.1, "x_4": 0.6, "x_5": 0.9}
509
+ objective_values = {"f_1": 1.2, "f_2": 0.9, "f_3": 1.5}
510
+ constraint_values = {"g_1": 0.1}
511
+ extra_func_values = {"extra_1": 5000, "extra_2": 600000}
512
+
513
+ archive_entry = ArchiveEntryBase(variable_values=variable_values, objective_values=objective_values)
514
+
515
+ archive_entry_db = ArchiveEntryDB.model_validate(
516
+ archive_entry,
517
+ update={
518
+ "user_id": user.id,
519
+ "problem_id": problem_db.id,
520
+ "constraint_values": constraint_values,
521
+ "extra_func_values": extra_func_values,
522
+ },
523
+ )
524
+
525
+ session.add(archive_entry_db)
526
+ session.commit()
527
+ session.refresh(archive_entry_db)
528
+
529
+ from_db = session.get(ArchiveEntryDB, archive_entry_db.id)
530
+
531
+ assert from_db.user_id == user.id
532
+ assert from_db.problem_id == problem_db.id
533
+ assert from_db == user.archive[0]
534
+ assert compare_models(from_db.problem, problem_db)
535
+ assert from_db.variable_values == variable_values
536
+ assert from_db.objective_values == objective_values
537
+ assert from_db.constraint_values == constraint_values
538
+ assert from_db.extra_func_values == extra_func_values
539
+
540
+
541
+ def test_preference_models(session_and_user: dict[str, Session | list[User]]):
542
+ """Test that the archive works as intended."""
543
+ session = session_and_user["session"]
544
+ user = session_and_user["user"]
545
+
546
+ problem = ProblemDB.from_problem(dtlz2(5, 3), user=user)
547
+
548
+ session.add(problem)
549
+ session.commit()
550
+ session.refresh(problem)
551
+
552
+ aspiration_levels = {"f_1": 0.1, "f_2": 5, "f_3": -3.1}
553
+ lower_bounds = {"f_1": -4.1, "f_2": 0, "f_3": 2.2}
554
+ upper_bounds = {"f_1": 2.1, "f_2": 0.1, "f_3": 12.2}
555
+
556
+ reference_point = ReferencePoint(aspiration_levels=aspiration_levels)
557
+ bounds = Bounds(lower_bounds=lower_bounds, upper_bounds=upper_bounds)
558
+
559
+ reference_point_db = PreferenceDB(user_id=user.id, problem_id=problem.id, preference=reference_point)
560
+ bounds_db = PreferenceDB(user_id=user.id, problem_id=problem.id, preference=bounds)
561
+
562
+ session.add(reference_point_db)
563
+ session.add(bounds_db)
564
+ session.commit()
565
+ session.refresh(reference_point_db)
566
+ session.refresh(bounds_db)
567
+
568
+ from_db_ref_point = session.get(PreferenceDB, reference_point_db.id)
569
+ from_db_bounds = session.get(PreferenceDB, bounds_db.id)
570
+
571
+ assert from_db_ref_point.preference.aspiration_levels == aspiration_levels
572
+ assert from_db_bounds.preference.lower_bounds == lower_bounds
573
+ assert from_db_bounds.preference.upper_bounds == upper_bounds
574
+
575
+ assert from_db_ref_point.problem == problem
576
+ assert from_db_ref_point.problem == problem
577
+ assert from_db_bounds.problem == problem
578
+
579
+ assert from_db_bounds.user == user
580
+ assert from_db_ref_point.user == user
581
+
582
+ assert from_db_bounds.solutions == []
583
+ assert from_db_ref_point.solutions == []
584
+
585
+
586
+ def test_rpm_state(session_and_user: dict[str, Session | list[User]]):
587
+ """Test the RPM state that it works correctly."""
588
+ session = session_and_user["session"]
589
+ user = session_and_user["user"]
590
+ problem_db = user.problems[0]
591
+
592
+ # create interactive session
593
+ isession = InteractiveSessionDB(user_id=user.id)
594
+
595
+ session.add(isession)
596
+ session.commit()
597
+ session.refresh(isession)
598
+
599
+ # use the reference point method
600
+ asp_levels_1 = {"f_1": 0.4, "f_2": 0.8, "f_3": 0.6}
601
+
602
+ problem = Problem.from_problemdb(problem_db)
603
+
604
+ scalarization_options = None
605
+ solver = "pyomo_bonmin"
606
+ solver_options = None
607
+
608
+ results = rpm_solve_solutions(
609
+ problem,
610
+ asp_levels_1,
611
+ scalarization_options=scalarization_options,
612
+ solver=available_solvers[solver],
613
+ solver_options=solver_options,
614
+ )
615
+
616
+ # create preferences
617
+
618
+ rp_1 = ReferencePoint(aspiration_levels=asp_levels_1)
619
+ preferences = PreferenceDB(user_id=user.id, problem_id=problem_db.id, preference=rp_1)
620
+
621
+ session.add(preferences)
622
+ session.commit()
623
+ session.refresh(preferences)
624
+
625
+ # create state
626
+
627
+ rpm_state = RPMState(
628
+ scalarization_options=scalarization_options,
629
+ solver=solver,
630
+ solver_options=solver_options,
631
+ solver_results=results,
632
+ )
633
+
634
+ state_1 = StateDB(
635
+ problem_id=problem_db.id, preference_id=preferences.id, session_id=isession.id, parent_id=None, state=rpm_state
636
+ )
637
+
638
+ session.add(state_1)
639
+ session.commit()
640
+ session.refresh(state_1)
641
+
642
+ asp_levels_2 = {"f_1": 0.6, "f_2": 0.4, "f_3": 0.5}
643
+
644
+ problem = Problem.from_problemdb(problem_db)
645
+
646
+ scalarization_options = None
647
+ solver = "pyomo_bonmin"
648
+ solver_options = None
649
+
650
+ results = rpm_solve_solutions(
651
+ problem,
652
+ asp_levels_2,
653
+ scalarization_options=scalarization_options,
654
+ solver=available_solvers[solver],
655
+ solver_options=solver_options,
656
+ )
657
+
658
+ # create preferences
659
+
660
+ rp_2 = ReferencePoint(aspiration_levels=asp_levels_2)
661
+ preferences = PreferenceDB(user_id=user.id, problem_id=problem_db.id, preference=rp_2)
662
+
663
+ session.add(preferences)
664
+ session.commit()
665
+ session.refresh(preferences)
666
+
667
+ # create state
668
+
669
+ rpm_state = RPMState(
670
+ scalarization_options=scalarization_options,
671
+ solver=solver,
672
+ solver_options=solver_options,
673
+ solver_results=results,
674
+ )
675
+
676
+ state_2 = StateDB(
677
+ problem_id=problem_db.id,
678
+ preference_id=preferences.id,
679
+ session_id=isession.id,
680
+ parent_id=state_1.id,
681
+ state=rpm_state,
682
+ )
683
+
684
+ session.add(state_2)
685
+ session.commit()
686
+ session.refresh(state_2)
687
+
688
+ assert state_1.parent is None
689
+ assert state_2.parent == state_1
690
+ assert len(state_1.children) == 1
691
+ assert state_1.children[0] == state_2
692
+
693
+ assert state_1.preference.preference == rp_1
694
+ assert state_2.preference.preference == rp_2
695
+
696
+ assert state_2.problem == problem_db
697
+ assert state_2.session.user == user
698
+
699
+ assert state_2.children == []
700
+ assert state_2.parent.problem == problem_db
701
+ assert state_2.parent.session.user == user