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,307 @@
1
+ """Defines end-points to access and manage problems."""
2
+
3
+ import json
4
+ from typing import Annotated
5
+
6
+ from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile, status
7
+ from fastapi.responses import JSONResponse
8
+ from sqlmodel import Session, select
9
+
10
+ from desdeo.api.db import get_session
11
+ from desdeo.api.models import (
12
+ ForestProblemMetaData,
13
+ ProblemDB,
14
+ ProblemGetRequest,
15
+ ProblemInfo,
16
+ ProblemInfoSmall,
17
+ ProblemMetaDataDB,
18
+ ProblemMetaDataGetRequest,
19
+ ProblemSelectSolverRequest,
20
+ RepresentativeNonDominatedSolutions,
21
+ SolverSelectionMetadata,
22
+ User,
23
+ UserRole,
24
+ )
25
+ from desdeo.api.routers.user_authentication import get_current_user
26
+ from desdeo.problem import Problem
27
+ from desdeo.tools.utils import available_solvers
28
+
29
+ router = APIRouter(prefix="/problem")
30
+
31
+
32
+ def check_solver(problem_db: ProblemDB):
33
+ """Check if a preferred solver is set in the metadata.
34
+
35
+ Check if a preferred solver is set in the metadata.
36
+ If it exist, fetch its constructor and return it. Otherwise return None.
37
+ """
38
+ metadata: ProblemMetaDataDB = problem_db.problem_metadata
39
+ solver_metadata = None
40
+ if metadata is not None:
41
+ solver_metadata_list = [
42
+ metadata for metadata in metadata.all_metadata if metadata.metadata_type == "solver_selection_metadata"
43
+ ]
44
+ if solver_metadata_list != []:
45
+ solver_metadata = solver_metadata_list[-1]
46
+
47
+ if solver_metadata is not None:
48
+ solver = available_solvers[solver_metadata.solver_string_representation]["constructor"]
49
+ else:
50
+ solver = None
51
+ return solver
52
+
53
+
54
+ # This is needed, because otherwise fields ending in an underscore fail to parse.
55
+ async def parse_problem_json(request: Request) -> Problem:
56
+ """Helper function to pass by_name=True to model_validate when coercing the json object to a Problem object."""
57
+ data: dict = await request.json()
58
+ return Problem.model_validate(data, by_name=True)
59
+
60
+
61
+ @router.get("/all")
62
+ def get_problems(user: Annotated[User, Depends(get_current_user)]) -> list[ProblemInfoSmall]:
63
+ """Get information on all the current user's problems.
64
+
65
+ Args:
66
+ user (Annotated[User, Depends): the current user.
67
+
68
+ Returns:
69
+ list[ProblemInfoSmall]: a list of information on all the problems.
70
+ """
71
+ return user.problems
72
+
73
+
74
+ @router.get("/all_info")
75
+ def get_problems_info(user: Annotated[User, Depends(get_current_user)]) -> list[ProblemInfo]:
76
+ """Get detailed information on all the current user's problems.
77
+
78
+ Args:
79
+ user (Annotated[User, Depends): the current user.
80
+
81
+ Returns:
82
+ list[ProblemInfo]: a list of the detailed information on all the problems.
83
+ """
84
+ return user.problems
85
+
86
+
87
+ @router.post("/get")
88
+ def get_problem(
89
+ request: ProblemGetRequest,
90
+ user: Annotated[User, Depends(get_current_user)],
91
+ session: Annotated[Session, Depends(get_session)],
92
+ ) -> ProblemInfo:
93
+ """Get the model of a specific problem.
94
+
95
+ Args:
96
+ request (ProblemGetRequest): the request containing the problem's id `problem_id`.
97
+ user (Annotated[User, Depends): the current user.
98
+ session (Annotated[Session, Depends): the database session.
99
+
100
+ Raises:
101
+ HTTPException: could not find a problem with the given id.
102
+
103
+ Returns:
104
+ ProblemInfo: detailed information on the requested problem.
105
+ """
106
+ problem = session.get(ProblemDB, request.problem_id)
107
+
108
+ if problem is None:
109
+ raise HTTPException(
110
+ status_code=status.HTTP_404_NOT_FOUND,
111
+ detail=f"The problem with the requested id={request.problem_id} was not found.",
112
+ )
113
+
114
+ return problem
115
+
116
+
117
+ @router.post("/add")
118
+ def add_problem(
119
+ request: Annotated[Problem, Depends(parse_problem_json)],
120
+ user: Annotated[User, Depends(get_current_user)],
121
+ session: Annotated[Session, Depends(get_session)],
122
+ ) -> ProblemInfo:
123
+ """Add a newly defined problem to the database.
124
+
125
+ Args:
126
+ request (Problem): the JSON representation of the problem.
127
+ user (Annotated[User, Depends): the current user.
128
+ session (Annotated[Session, Depends): the database session.
129
+
130
+ Note:
131
+ Users with the role 'guest' may not add new problems.
132
+
133
+ Raises:
134
+ HTTPException: when any issue with defining the problem arises.
135
+
136
+ Returns:
137
+ ProblemInfo: the information about the problem added.
138
+ """
139
+ if user.role == UserRole.guest:
140
+ raise HTTPException(
141
+ status_code=status.HTTP_401_UNAUTHORIZED, detail="Guest users are not allowed to add new problems."
142
+ )
143
+ try:
144
+ problem_db = ProblemDB.from_problem(request, user=user)
145
+ except Exception as e:
146
+ raise HTTPException(
147
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
148
+ detail=f"Could not add problem. Possible reason: {e!r}",
149
+ ) from e
150
+
151
+ session.add(problem_db)
152
+ session.commit()
153
+ session.refresh(problem_db)
154
+
155
+ return problem_db
156
+
157
+
158
+ @router.post("/add_json")
159
+ def add_problem_json(
160
+ json_file: UploadFile,
161
+ user: Annotated[User, Depends(get_current_user)],
162
+ session: Annotated[Session, Depends(get_session)],
163
+ ) -> ProblemInfo:
164
+ """Adds a problem to the database based on its JSON definition.
165
+
166
+ Args:
167
+ json_file (UploadFile): a file in JSON format describing the problem.
168
+ user (Annotated[User, Depends): the usr for which the problem is added.
169
+ session (Annotated[Session, Depends): the database session.
170
+
171
+ Raises:
172
+ HTTPException: if the provided `json_file` is empty.
173
+ HTTPException: if the content in the provided `json_file` is not in JSON format.__annotations__
174
+
175
+ Returns:
176
+ ProblemInfo: a description of the added problem.
177
+ """
178
+ raw = json_file.file.read()
179
+
180
+ if not raw:
181
+ raise HTTPException(400, "Empty upload.")
182
+
183
+ try:
184
+ # for extra validation
185
+ json.loads(raw)
186
+ except json.JSONDecodeError as e:
187
+ raise HTTPException(400, "Invalid JSON.") from e
188
+
189
+ problem = Problem.model_validate_json(raw, by_name=True)
190
+ problem_db = ProblemDB.from_problem(problem, user=user)
191
+
192
+ session.add(problem_db)
193
+ session.commit()
194
+ session.refresh(problem_db)
195
+
196
+ return problem_db
197
+
198
+
199
+ @router.post("/get_metadata")
200
+ def get_metadata(
201
+ request: ProblemMetaDataGetRequest,
202
+ user: Annotated[User, Depends(get_current_user)],
203
+ session: Annotated[Session, Depends(get_session)],
204
+ ) -> list[ForestProblemMetaData | RepresentativeNonDominatedSolutions | SolverSelectionMetadata]:
205
+ """Fetch specific metadata for a specific problem.
206
+
207
+ Fetch specific metadata for a specific problem. See all the possible
208
+ metadata types from DESDEO/desdeo/api/models/problem.py Problem Metadata
209
+ section.
210
+
211
+ Args:
212
+ request (MetaDataGetRequest): the requested metadata type.
213
+ user (Annotated[User, Depends]): the current user.
214
+ session (Annotated[Session, Depends]): the database session.
215
+
216
+ Returns:
217
+ list[ForestProblemMetadata | RepresentativeNonDominatedSolutions]: list containing all the metadata
218
+ defined for the problem with the requested metadata type. If no match is found,
219
+ returns an empty list.
220
+ """
221
+ statement = select(ProblemDB).where(ProblemDB.id == request.problem_id)
222
+ problem_from_db = session.exec(statement).first()
223
+ if problem_from_db is None:
224
+ raise HTTPException(
225
+ status_code=status.HTTP_404_NOT_FOUND, detail=f"Problem with ID {request.problem_id} not found!"
226
+ )
227
+
228
+ problem_metadata = problem_from_db.problem_metadata
229
+
230
+ if problem_metadata is None:
231
+ # no metadata define for the problem
232
+ return []
233
+
234
+ # metadata is defined, try to find matching types based on request
235
+ return [metadata for metadata in problem_metadata.all_metadata if metadata.metadata_type == request.metadata_type]
236
+
237
+
238
+ @router.get("/assign/solver", response_model=list[str])
239
+ def get_available_solvers() -> list[str]:
240
+ """Return the list of available solver names."""
241
+ return list(available_solvers.keys())
242
+
243
+ @router.post("/assign_solver")
244
+ def select_solver(
245
+ request: ProblemSelectSolverRequest,
246
+ user: Annotated[User, Depends(get_current_user)],
247
+ session: Annotated[Session, Depends(get_session)],
248
+ ) -> JSONResponse:
249
+ """Assign a specific solver for a problem.
250
+
251
+ request: ProblemSelectSolverRequest: The request containing problem id and string representation of the solver
252
+ user: Annotated[User, Depends(get_current_user): The user that is logged in.
253
+ session: Annotated[Session, Depends(get_session)]: The database session.
254
+
255
+ Raises:
256
+ HTTPException: Unknown solver, unauthorized user
257
+
258
+ Returns:
259
+ JSONResponse: A simple confirmation.
260
+ """
261
+ if request.solver_string_representation not in [x for x, _ in available_solvers.items()]:
262
+ raise HTTPException(
263
+ detail=f"Solver of unknown type: {request.solver_string_representation}",
264
+ status_code=status.HTTP_404_NOT_FOUND,
265
+ )
266
+
267
+ """Set a specific solver for a specific problem."""
268
+ # Get the problem
269
+ problem_db = session.exec(select(ProblemDB).where(ProblemDB.id == request.problem_id)).first()
270
+ if problem_db is None:
271
+ raise HTTPException(
272
+ detail=f"No problem with ID {request.problem_id}!",
273
+ status_code=status.HTTP_404_NOT_FOUND,
274
+ )
275
+ # Auth the user
276
+ if user.id != problem_db.user_id:
277
+ raise HTTPException(detail="Unauthorized user!", status_code=status.HTTP_401_UNAUTHORIZED)
278
+
279
+ # All good, get on with it.
280
+ problem_metadata = problem_db.problem_metadata
281
+ if problem_metadata is None:
282
+ # There's no metadata for this problem! Create some.
283
+ problem_metadata = ProblemMetaDataDB(problem_id=problem_db.id, problem=problem_db)
284
+ session.add(problem_metadata)
285
+ session.commit()
286
+ session.refresh(problem_metadata)
287
+
288
+ if problem_metadata.solver_selection_metadata:
289
+ session.delete(problem_metadata.solver_selection_metadata[-1])
290
+ session.commit()
291
+
292
+ solver_selection_metadata = SolverSelectionMetadata(
293
+ metadata_id=problem_metadata.id,
294
+ solver_string_representation=request.solver_string_representation,
295
+ metadata_instance=problem_metadata,
296
+ )
297
+
298
+ session.add(solver_selection_metadata)
299
+ session.commit()
300
+ session.refresh(solver_selection_metadata)
301
+
302
+ problem_metadata.solver_selection_metadata.append(solver_selection_metadata)
303
+ session.add(problem_metadata)
304
+ session.commit()
305
+ session.refresh(problem_metadata)
306
+
307
+ return JSONResponse(content={"message": "OK"}, status_code=status.HTTP_200_OK)
@@ -0,0 +1,93 @@
1
+ """Defines end-points to access functionalities related to the reference point method."""
2
+
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter, Depends
6
+ from sqlmodel import Session
7
+
8
+ from desdeo.api.db import get_session
9
+ from desdeo.api.models import (
10
+ InteractiveSessionDB,
11
+ PreferenceDB,
12
+ ProblemDB,
13
+ RPMSolveRequest,
14
+ RPMState,
15
+ StateDB,
16
+ User,
17
+ )
18
+ from desdeo.api.routers.problem import check_solver
19
+ from desdeo.api.routers.user_authentication import get_current_user
20
+ from desdeo.mcdm import rpm_solve_solutions
21
+ from desdeo.problem import Problem
22
+ from desdeo.tools import SolverResults
23
+
24
+ from .utils import fetch_interactive_session, fetch_parent_state, fetch_user_problem
25
+
26
+ router = APIRouter(prefix="/method/rpm")
27
+
28
+
29
+ @router.post("/solve")
30
+ def solve_solutions(
31
+ request: RPMSolveRequest,
32
+ user: Annotated[User, Depends(get_current_user)],
33
+ session: Annotated[Session, Depends(get_session)],
34
+ ) -> RPMState:
35
+ """Runs an iteration of the reference point method.
36
+
37
+ Args:
38
+ request (RPMSolveRequest): a request with the needed information to run the method.
39
+ user (Annotated[User, Depends): the current user.
40
+ session (Annotated[Session, Depends): the current database session.
41
+
42
+ Returns:
43
+ RPMState: a state with information on the results of iterating the reference point method
44
+ once.
45
+ """
46
+ # fetch interactive session, parent state, and ProblemDB
47
+ interactive_session: InteractiveSessionDB = fetch_interactive_session(user, request, session)
48
+ parent_state = fetch_parent_state(user, request, session, interactive_session)
49
+
50
+ problem_db: ProblemDB = fetch_user_problem(user, request, session)
51
+
52
+ solver = check_solver(problem_db=problem_db)
53
+
54
+ problem = Problem.from_problemdb(problem_db)
55
+
56
+ # optimize for solutions
57
+ solver_results: list[SolverResults] = rpm_solve_solutions(
58
+ problem,
59
+ request.preference.aspiration_levels,
60
+ request.scalarization_options,
61
+ solver,
62
+ request.solver_options,
63
+ )
64
+
65
+ # create DB preference
66
+ preference_db = PreferenceDB(user_id=user.id, problem_id=problem_db.id, preference=request.preference)
67
+
68
+ session.add(preference_db)
69
+ session.commit()
70
+ session.refresh(preference_db)
71
+
72
+ # create state and add to DB
73
+ rpm_state = RPMState(
74
+ scalarization_options=request.scalarization_options,
75
+ solver=request.solver,
76
+ solver_options=request.solver_options,
77
+ solver_results=solver_results,
78
+ )
79
+
80
+ # create DB state and add it to the DB
81
+ state = StateDB(
82
+ problem_id=problem_db.id,
83
+ preference_id=preference_db.id,
84
+ session_id=interactive_session.id if interactive_session is not None else None,
85
+ parent_id=parent_state.id if parent_state is not None else None,
86
+ state=rpm_state,
87
+ )
88
+
89
+ session.add(state)
90
+ session.commit()
91
+ session.refresh(state)
92
+
93
+ return rpm_state
@@ -0,0 +1,100 @@
1
+ """Defines end-points to access and manage interactive sessions."""
2
+
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter, Depends, HTTPException, status
6
+ from sqlmodel import Session, select
7
+
8
+ from desdeo.api.db import get_session as get_db_session
9
+ from desdeo.api.models import (
10
+ CreateSessionRequest,
11
+ GetSessionRequest,
12
+ InteractiveSessionDB,
13
+ InteractiveSessionInfo,
14
+ User,
15
+ )
16
+ from desdeo.api.routers.user_authentication import get_current_user
17
+ from desdeo.api.routers.utils import fetch_interactive_session
18
+
19
+ router = APIRouter(prefix="/session")
20
+
21
+
22
+ @router.post("/new")
23
+ def create_new_session(
24
+ request: CreateSessionRequest,
25
+ user: Annotated[User, Depends(get_current_user)],
26
+ session: Annotated[Session, Depends(get_db_session)],
27
+ ) -> InteractiveSessionInfo:
28
+ """."""
29
+ interactive_session = InteractiveSessionDB(user_id=user.id, info=request.info)
30
+
31
+ session.add(interactive_session)
32
+ session.commit()
33
+ session.refresh(interactive_session)
34
+
35
+ user.active_session_id = interactive_session.id
36
+
37
+ session.add(user)
38
+ session.commit()
39
+ session.refresh(interactive_session)
40
+
41
+ return interactive_session
42
+
43
+
44
+ @router.get("/get/{session_id}")
45
+ def get_session(
46
+ session_id: int,
47
+ user: Annotated[User, Depends(get_current_user)],
48
+ session: Annotated[Session, Depends(get_db_session)],
49
+ ) -> InteractiveSessionInfo:
50
+ """Return an interactive session with a current user."""
51
+ request = GetSessionRequest(session_id=session_id)
52
+ return fetch_interactive_session(
53
+ user=user,
54
+ request=request,
55
+ session=session,
56
+ )
57
+
58
+
59
+ @router.get("/get_all", status_code=status.HTTP_200_OK)
60
+ def get_all_sessions(
61
+ user: Annotated[User, Depends(get_current_user)],
62
+ session: Annotated[Session, Depends(get_db_session)],
63
+ ) -> list[InteractiveSessionInfo]:
64
+ """Return all interactive sessions of the current user."""
65
+ statement = select(InteractiveSessionDB).where(InteractiveSessionDB.user_id == user.id)
66
+ result = session.exec(statement).all()
67
+
68
+ if not result:
69
+ raise HTTPException(
70
+ status_code=status.HTTP_404_NOT_FOUND,
71
+ detail="No interactive sessions found for the user.",
72
+ )
73
+
74
+ return result
75
+
76
+
77
+ @router.delete("/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
78
+ def delete_session(
79
+ session_id: int,
80
+ user: Annotated[User, Depends(get_current_user)],
81
+ session: Annotated[Session, Depends(get_db_session)],
82
+ ) -> None:
83
+ """Delete an interactive session and all its related states."""
84
+ request = GetSessionRequest(session_id=session_id)
85
+
86
+ interactive_session = fetch_interactive_session(
87
+ user=user,
88
+ request=request,
89
+ session=session,
90
+ ) # raises 404 if not found
91
+
92
+ try:
93
+ session.delete(interactive_session)
94
+ session.commit()
95
+ except Exception as exc:
96
+ session.rollback()
97
+ raise HTTPException(
98
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
99
+ detail="Failed to delete interactive session.",
100
+ ) from exc
@@ -0,0 +1,16 @@
1
+ """A test router for the DESDEO API."""
2
+
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter, Depends
6
+
7
+ from desdeo.api.routers.user_authentication import get_current_user
8
+ from desdeo.api.schema import User
9
+
10
+ router = APIRouter(prefix="/test")
11
+
12
+
13
+ @router.get("/userdetails")
14
+ def get_user(current_user: Annotated[User, Depends(get_current_user)]) -> User:
15
+ """Get information about the current user."""
16
+ return current_user