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,18 @@
1
+ """Models specific to the reference point method."""
2
+
3
+ from sqlmodel import JSON, Column, Field, SQLModel
4
+
5
+ from .preference import ReferencePoint
6
+
7
+
8
+ class RPMSolveRequest(SQLModel):
9
+ """Model of the request to the reference point method."""
10
+
11
+ problem_id: int
12
+ session_id: int | None = Field(default=None)
13
+ parent_state_id: int | None = Field(default=None)
14
+
15
+ scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
16
+ solver: str | None = Field(default=None)
17
+ solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
18
+ preference: ReferencePoint = Field(Column(JSON))
@@ -0,0 +1,49 @@
1
+ """Defines Session models to manage user sessions."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from sqlmodel import Field, Relationship, SQLModel
6
+
7
+ if TYPE_CHECKING:
8
+ from .state import StateDB
9
+ from .user import User
10
+
11
+
12
+ class CreateSessionRequest(SQLModel):
13
+ """Model of the request to create a new session."""
14
+
15
+ info: str | None = Field(default=None)
16
+
17
+
18
+ class GetSessionRequest(SQLModel):
19
+ """Model of the request to get a specific session."""
20
+
21
+ session_id: int = Field()
22
+
23
+
24
+ class InteractiveSessionBase(SQLModel):
25
+ """The base model for representing interactive sessions."""
26
+
27
+ id: int | None
28
+ user_id: int | None
29
+
30
+ info: str | None
31
+
32
+
33
+ InteractiveSessionInfo = InteractiveSessionBase
34
+
35
+
36
+ class InteractiveSessionDB(InteractiveSessionBase, table=True):
37
+ """Database model to store sessions."""
38
+
39
+ id: int | None = Field(primary_key=True, default=None)
40
+ user_id: int | None = Field(foreign_key="user.id", default=None)
41
+
42
+ info: str | None = Field(default=None)
43
+
44
+ # Back populates
45
+ states: list["StateDB"] = Relationship(
46
+ back_populates="session",
47
+ sa_relationship_kwargs={"cascade": "all, delete-orphan"},
48
+ )
49
+ user: "User" = Relationship(back_populates="sessions")
@@ -0,0 +1,463 @@
1
+ """Defines models for representing the state of various interactive methods."""
2
+
3
+ import warnings
4
+ from typing import TYPE_CHECKING
5
+
6
+ from sqlalchemy.types import TypeDecorator
7
+ from sqlmodel import (
8
+ JSON,
9
+ Column,
10
+ Field,
11
+ Relationship,
12
+ SQLModel,
13
+ )
14
+
15
+ from desdeo.emo.options.templates import PreferenceOptions, TemplateOptions
16
+ from desdeo.mcdm import ENautilusResult
17
+ from desdeo.problem import Tensor, VariableType
18
+ from desdeo.tools import SolverResults
19
+ from desdeo.tools.score_bands import SCOREBandsResult
20
+
21
+ from .preference import PreferenceType, ReferencePoint
22
+
23
+ if TYPE_CHECKING:
24
+ from .problem import RepresentativeNonDominatedSolutions
25
+ from .state_table import UserSavedSolutionDB
26
+
27
+
28
+ class ResultsType(TypeDecorator):
29
+ """SQLAlchemy custom type to convert a `SolverResults` and similar to JSON and back."""
30
+
31
+ impl = JSON
32
+
33
+ def process_bind_param(self, value, dialect):
34
+ """`SolverResults` to JSON."""
35
+ if value is None:
36
+ return None
37
+
38
+ if isinstance(value, list):
39
+ return [x.model_dump() if hasattr(x, "model_dump") else x for x in value]
40
+
41
+ if hasattr(value, "model_dump"):
42
+ return value.model_dump()
43
+
44
+ msg = f"No JSON serialization set for '{type(value)}'."
45
+ print(msg)
46
+
47
+ return value
48
+
49
+ def process_result_value(self, value, dialect):
50
+ """JSON to `SolverResults` or similar."""
51
+ # Stupid way to to this, but works for now. Needs to add field
52
+ # to the corresponding models so that they may be identified in dict form.
53
+ # TODO: see above
54
+ if "closeness_measures" in value: # noqa: SIM108
55
+ model = ENautilusResult
56
+ else:
57
+ model = SolverResults
58
+
59
+ if value is None:
60
+ return None
61
+ if isinstance(value, list):
62
+ return [model.model_validate(x) for x in value]
63
+
64
+ return model.model_validate(value)
65
+
66
+
67
+ class ResultInterface:
68
+ @property
69
+ def result_objective_values(self) -> list[dict[str, float]]:
70
+ msg = (
71
+ f"Calling the method `result_objective_values`, which has not been implemented "
72
+ f"for the class `{type(self).__name__}`. Returning an empty list..."
73
+ )
74
+
75
+ warnings.warn(msg, category=RuntimeWarning, stacklevel=2)
76
+
77
+ return []
78
+
79
+ @property
80
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
81
+ msg = (
82
+ f"Calling the method `result_variable_values`, which has not been implemented "
83
+ f"for the class `{type(self).__name__}`. Returning an empty list..."
84
+ )
85
+
86
+ warnings.warn(msg, category=RuntimeWarning, stacklevel=2)
87
+
88
+ return []
89
+
90
+ @property
91
+ def num_solutions(self) -> int:
92
+ msg = (
93
+ f"Calling the method `num_solutions`, which has not been implemented "
94
+ f"for the class `{type(self).__name__}`. Returning a zero."
95
+ )
96
+
97
+ warnings.warn(msg, category=RuntimeWarning, stacklevel=2)
98
+
99
+ return 0
100
+
101
+
102
+ class RPMState(SQLModel, table=True):
103
+ """Reference Point Method (k+1 candidates)."""
104
+
105
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
106
+
107
+ # inputs
108
+ preferences: ReferencePoint = Field(sa_column=Column(PreferenceType))
109
+ scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
110
+ solver: str | None = None
111
+ solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
112
+
113
+ # results
114
+ solver_results: list[SolverResults] = Field(sa_column=Column(ResultsType))
115
+
116
+
117
+ class NIMBUSClassificationState(ResultInterface, SQLModel, table=True):
118
+ """NIMBUS: classification / solve candidates."""
119
+
120
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
121
+
122
+ preferences: ReferencePoint = Field(sa_column=Column(PreferenceType))
123
+ scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
124
+ solver: str | None = None
125
+ solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
126
+ current_objectives: dict[str, float] = Field(sa_column=Column(JSON), default_factory=dict)
127
+ num_desired: int | None = 1
128
+ previous_preferences: ReferencePoint = Field(sa_column=Column(PreferenceType))
129
+
130
+ # results
131
+ solver_results: list[SolverResults] = Field(sa_column=Column(ResultsType))
132
+
133
+ @property
134
+ def result_objective_values(self) -> list[dict[str, float]]:
135
+ return [x.optimal_objectives for x in self.solver_results]
136
+
137
+ @property
138
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
139
+ return [x.optimal_variables for x in self.solver_results]
140
+
141
+ @property
142
+ def num_solutions(self) -> int:
143
+ return len(self.solver_results)
144
+
145
+
146
+ class NIMBUSSaveState(ResultInterface, SQLModel, table=True):
147
+ """NIMBUS: save solutions."""
148
+
149
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
150
+
151
+ solutions: list["UserSavedSolutionDB"] = Relationship(
152
+ sa_relationship_kwargs={
153
+ # tell SQLA which FK on the child points back to THIS parent
154
+ "foreign_keys": "[UserSavedSolutionDB.save_state_id]",
155
+ "primaryjoin": "NIMBUSSaveState.id == UserSavedSolutionDB.save_state_id",
156
+ "cascade": "all, delete-orphan",
157
+ "lazy": "selectin",
158
+ }
159
+ )
160
+
161
+ @property
162
+ def result_objective_values(self) -> list[dict[str, float]]:
163
+ return [x.objective_values for x in self.solutions]
164
+
165
+ @property
166
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
167
+ return [x.variable_values for x in self.solutions]
168
+
169
+ @property
170
+ def num_solutions(self) -> int:
171
+ return len(self.solutions)
172
+
173
+
174
+ class NIMBUSInitializationState(ResultInterface, SQLModel, table=True):
175
+ """NIMBUS: initialization."""
176
+
177
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
178
+
179
+ reference_point: dict[str, float] | None = Field(sa_column=Column(JSON), default=None)
180
+ scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
181
+ solver: str | None = None
182
+ solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
183
+
184
+ # Results
185
+ solver_results: "SolverResults" = Field(sa_column=Column(ResultsType), default_factory=list)
186
+
187
+ @property
188
+ def result_objective_values(self) -> list[dict[str, float]]:
189
+ return [self.solver_results.optimal_objectives]
190
+
191
+ @property
192
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
193
+ return [self.solver_results.optimal_variables]
194
+
195
+ @property
196
+ def num_solutions(self) -> int:
197
+ return 1
198
+
199
+
200
+ class NIMBUSFinalState(ResultInterface, SQLModel, table=True):
201
+ """NIMBUS: The Final State.
202
+
203
+ NOTE: Despite this being the "final" state, I think the user should
204
+ still be allowed to use this as a basis for new iterations. Therefore
205
+ I think this should behave/have necessary elements for that to be the case.
206
+ """
207
+
208
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
209
+
210
+ solution_origin_state_id: int = Field(description="The state from which the solution originates.")
211
+ solution_result_index: int = Field(description="The index within that state.")
212
+
213
+ solver_results: "SolverResults" = Field(
214
+ sa_column=Column(ResultsType), default_factory=list
215
+ )
216
+
217
+ @property
218
+ def result_objective_values(self) -> list[dict[str, float]]:
219
+ return [self.solver_results.optimal_objectives]
220
+
221
+ @property
222
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
223
+ return [self.solver_results.optimal_variables]
224
+
225
+ @property
226
+ def num_solutions(self) -> int:
227
+ return 1
228
+
229
+
230
+ class GNIMBUSOptimizationState(ResultInterface, SQLModel, table=True):
231
+ """GNIMBUS: classification / solving."""
232
+
233
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
234
+
235
+ # Preferences that went in
236
+ reference_points: dict[int, dict[str, float]] = Field(sa_column=Column(JSON))
237
+ # Results that came out
238
+ solver_results: list[SolverResults] = Field(sa_column=Column(ResultsType))
239
+
240
+ @property
241
+ def result_objective_values(self) -> list[dict[str, float]]:
242
+ return [x.optimal_objectives for x in self.solver_results]
243
+
244
+ @property
245
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
246
+ return [x.optimal_variables for x in self.solver_results]
247
+
248
+ @property
249
+ def num_solutions(self) -> int:
250
+ return len(self.solver_results)
251
+
252
+
253
+ class GNIMBUSVotingState(ResultInterface, SQLModel, table=True):
254
+ """GNIMBUS: voting."""
255
+
256
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
257
+
258
+ # Preferences that went in
259
+ votes: dict[int, int] = Field(sa_column=Column(JSON))
260
+ # Results that came out
261
+ solver_results: list[SolverResults] = Field(sa_column=Column(ResultsType))
262
+
263
+ @property
264
+ def result_objective_values(self) -> list[dict[str, float]]:
265
+ return [x.optimal_objectives for x in self.solver_results]
266
+
267
+ @property
268
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
269
+ return [x.optimal_variables for x in self.solver_results]
270
+
271
+ @property
272
+ def num_solutions(self) -> int:
273
+ return 1
274
+
275
+
276
+ class GNIMBUSEndState(ResultInterface, SQLModel, table=True):
277
+ """GNIMBUS: ending. We check if everyone's happy with the solution and end if yes."""
278
+
279
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
280
+
281
+ # Preferences that went in
282
+ votes: dict[int, bool] = Field(sa_column=Column(JSON))
283
+ # Success?
284
+ success: bool = Field()
285
+ # Results that came out
286
+ solver_results: list[SolverResults] = Field(sa_column=Column(ResultsType))
287
+
288
+ @property
289
+ def result_objective_values(self) -> list[dict[str, float]]:
290
+ return [x.optimal_objectives for x in self.solver_results]
291
+
292
+ @property
293
+ def result_variable_values(self) -> list[dict[str, VariableType | Tensor]]:
294
+ return [x.optimal_variables for x in self.solver_results]
295
+
296
+ @property
297
+ def num_solutions(self) -> int:
298
+ return 1
299
+
300
+
301
+ class EMOIterateState(ResultInterface, SQLModel, table=True):
302
+ """EMO run (NSGA3, RVEA, etc.)."""
303
+
304
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
305
+
306
+ # algorithm flavor
307
+ template_options: list[TemplateOptions] = Field(
308
+ sa_column=Column(JSON)
309
+ ) # TODO: This should probably be ids to another table
310
+ # preferences
311
+ preference_options: PreferenceOptions | None = Field(sa_column=Column(JSON))
312
+
313
+ # results
314
+ decision_variables: dict[str, list[VariableType]] | None = Field(
315
+ sa_column=Column(JSON), description="Optimization results (decision variables)", default=None
316
+ ) ## Unlike other methods, we have a very large number of solutions. So maybe we should store them together?
317
+ objective_values: dict[str, list[float]] | None = Field(
318
+ sa_column=Column(JSON), description="Optimization outputs", default=None
319
+ )
320
+ constraint_values: dict[str, list[float]] | None = Field(
321
+ sa_column=Column(JSON), description="Constraint values of the solutions", default=None
322
+ )
323
+ extra_func_values: dict[str, list[float]] | None = Field(
324
+ sa_column=Column(JSON), description="Extra function values of the solutions", default=None
325
+ )
326
+
327
+ @property
328
+ def result_objective_values(self) -> dict[str, list[float]]:
329
+ if self.objective_values is None:
330
+ raise ValueError("No objective values stored in this state.")
331
+ return self.objective_values
332
+
333
+ @property
334
+ def result_variable_values(self) -> dict[str, list[VariableType]]:
335
+ if self.decision_variables is None:
336
+ raise ValueError("No decision variables stored in this state.")
337
+ return self.decision_variables
338
+
339
+ @property
340
+ def result_constraint_values(self) -> dict[str, list[float]] | None:
341
+ return self.constraint_values
342
+
343
+ @property
344
+ def result_extra_func_values(self) -> dict[str, list[float]] | None:
345
+ return self.extra_func_values
346
+
347
+ @property
348
+ def num_solutions(self) -> int:
349
+ if self.objective_values:
350
+ first_key = next(iter(self.objective_values))
351
+ return len(self.objective_values[first_key])
352
+ return 0
353
+
354
+
355
+ class EMOFetchState(SQLModel, table=True):
356
+ """Request model for fetching EMO solutions."""
357
+
358
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
359
+ # More fields can be added here if needed in the future. E.g., number of solutions to fetch, filters, etc.
360
+
361
+
362
+ class EMOSCOREState(SQLModel, table=True):
363
+ """EMO: SCORE iteration."""
364
+
365
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
366
+
367
+ result: SCOREBandsResult = Field(sa_column=Column(JSON))
368
+
369
+
370
+ class EMOSaveState(SQLModel, table=True):
371
+ """EMO: save solutions."""
372
+
373
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
374
+
375
+ # results
376
+ decision_variables: dict[str, list[VariableType]] = Field(
377
+ sa_column=Column(JSON), description="Optimization results (decision variables)", default_factory=dict
378
+ ) ## Unlike other methods, we have a very large number of solutions. So maybe we should store them together?
379
+ objective_values: dict[str, list[float]] = Field(
380
+ sa_column=Column(JSON), description="Optimization outputs", default_factory=dict
381
+ )
382
+ constraint_values: dict[str, list[float]] | None = Field(
383
+ sa_column=Column(JSON), description="Constraint values of the solutions", default_factory=dict
384
+ )
385
+ extra_func_values: dict[str, list[float]] | None = Field(
386
+ sa_column=Column(JSON), description="Extra function values of the solutions", default_factory=dict
387
+ )
388
+ names: list[str | None] = Field(
389
+ default_factory=list, sa_column=Column(JSON), description="Names of the saved solutions"
390
+ )
391
+
392
+ @property
393
+ def result_objective_values(self) -> dict[str, list[float]]:
394
+ return self.objective_values
395
+
396
+ @property
397
+ def result_variable_values(self) -> dict[str, list[VariableType]]:
398
+ return self.decision_variables
399
+
400
+ @property
401
+ def result_constraint_values(self) -> dict[str, list[float]] | None:
402
+ return self.constraint_values
403
+
404
+ @property
405
+ def result_extra_func_values(self) -> dict[str, list[float]] | None:
406
+ return self.extra_func_values
407
+
408
+ @property
409
+ def num_solutions(self) -> int:
410
+ if self.objective_values:
411
+ first_key = next(iter(self.objective_values))
412
+ return len(self.objective_values[first_key])
413
+ return 0
414
+
415
+
416
+ class IntermediateSolutionState(SQLModel, ResultInterface, table=True):
417
+ """Generic intermediate solutions requested by other methods."""
418
+
419
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
420
+
421
+ context: str | None = Field(
422
+ default=None,
423
+ description="Originating method context (e.g., 'nimbus', 'rpm') that requested these solutions",
424
+ )
425
+ scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
426
+ solver: str | None = None
427
+ solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
428
+ num_desired: int | None = 1
429
+ reference_solution_1: dict[str, float] | None = Field(sa_column=Column(JSON), default=None)
430
+ reference_solution_2: dict[str, float] | None = Field(sa_column=Column(JSON), default=None)
431
+
432
+ # results
433
+ solver_results: list[SolverResults] = Field(sa_column=Column(ResultsType))
434
+
435
+ @property
436
+ def result_objective_values(self) -> list[dict[str, float]]:
437
+ return [x.optimal_objectives for x in self.solver_results]
438
+
439
+ @property
440
+ def result_variable_values(self) -> list[dict[str, VariableType]]:
441
+ return [x.optimal_variables for x in self.solver_results]
442
+
443
+ @property
444
+ def num_solutions(self) -> int:
445
+ return len(self.solver_results)
446
+
447
+
448
+ class ENautilusState(SQLModel, table=True):
449
+ """E-NAUTILUS: one stepping iteration."""
450
+
451
+ id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
452
+
453
+ non_dominated_solutions_id: int | None = Field(foreign_key="representativenondominatedsolutions.id", default=None)
454
+
455
+ current_iteration: int
456
+ iterations_left: int
457
+ selected_point: dict[str, float] | None = Field(sa_column=Column(JSON), default=None)
458
+ reachable_point_indices: list[int] = Field(sa_column=Column(JSON), default_factory=list)
459
+ number_of_intermediate_points: int
460
+
461
+ enautilus_results: "ENautilusResult" = Field(sa_column=Column(ResultsType))
462
+
463
+ non_dominated_solutions: "RepresentativeNonDominatedSolutions" = Relationship()
@@ -0,0 +1,52 @@
1
+ """Defines user models."""
2
+
3
+ from enum import Enum
4
+ from typing import TYPE_CHECKING
5
+
6
+ from sqlmodel import JSON, Column, Field, Relationship, SQLModel
7
+
8
+ if TYPE_CHECKING:
9
+ from .archive import UserSavedSolutionDB
10
+ from .preference import PreferenceDB
11
+ from .problem import ProblemDB
12
+ from .session import InteractiveSessionDB
13
+
14
+
15
+ class UserRole(str, Enum):
16
+ """Possible user roles."""
17
+
18
+ guest = "guest"
19
+ dm = "dm"
20
+ analyst = "analyst"
21
+ admin = "admin"
22
+
23
+
24
+ class UserBase(SQLModel):
25
+ """Base user object."""
26
+
27
+ username: str = Field(index=True)
28
+
29
+
30
+ class User(UserBase, table=True):
31
+ """The table model of the user stored in the database."""
32
+
33
+ id: int | None = Field(primary_key=True, default=None)
34
+ password_hash: str = Field()
35
+ role: UserRole = Field()
36
+ group: str | None = Field(default="") # TODO: Get rid of this and use proper group systems
37
+ group_ids: list[int] = Field(sa_column=Column(JSON), default=[]) # The user is either a member of a group or an owner of a group
38
+ active_session_id: int | None = Field(default=None)
39
+
40
+ # Back populates
41
+ archive: list["UserSavedSolutionDB"] = Relationship(back_populates="user")
42
+ preferences: list["PreferenceDB"] = Relationship(back_populates="user")
43
+ problems: list["ProblemDB"] = Relationship(back_populates="user")
44
+ sessions: list["InteractiveSessionDB"] = Relationship(back_populates="user")
45
+
46
+
47
+ class UserPublic(UserBase):
48
+ """The object to handle public user information."""
49
+
50
+ id: int
51
+ role: UserRole
52
+ group_ids: list[int] | None
@@ -0,0 +1,25 @@
1
+ """Request and response models for Utopia endpoint."""
2
+
3
+ from typing import Any
4
+
5
+ from sqlmodel import Field, SQLModel
6
+
7
+ from desdeo.api.models.generic import SolutionInfo
8
+
9
+
10
+ class UtopiaRequest(SQLModel):
11
+ """The request for an Utopia map."""
12
+
13
+ problem_id: int = Field(description="Problem for which the map is generated")
14
+ solution: SolutionInfo = Field(description="Solution for which to generate the map")
15
+
16
+
17
+ class UtopiaResponse(SQLModel):
18
+ """The response to an UtopiaRequest."""
19
+
20
+ is_utopia: bool = Field(description="True if map exists for this problem.")
21
+ map_name: str = Field(description="Name of the map.")
22
+ map_json: dict[str, Any] = Field(description="MapJSON representation of the geography.")
23
+ options: dict[str, Any] = Field(description="A dict with given years as keys containing options for each year.")
24
+ description: str = Field(description="Description shown above the map.")
25
+ years: list[str] = Field(description="A list of years for which the maps have been generated.")