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.
- desdeo/__init__.py +8 -8
- desdeo/adm/ADMAfsar.py +551 -0
- desdeo/adm/ADMChen.py +414 -0
- desdeo/adm/BaseADM.py +119 -0
- desdeo/adm/__init__.py +11 -0
- desdeo/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +50 -0
- desdeo/api/config.py +90 -0
- desdeo/api/config.toml +64 -0
- desdeo/api/db.py +27 -0
- desdeo/api/db_init.py +85 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +266 -0
- desdeo/api/models/archive.py +23 -0
- desdeo/api/models/emo.py +128 -0
- desdeo/api/models/enautilus.py +69 -0
- desdeo/api/models/gdm/gdm_aggregate.py +139 -0
- desdeo/api/models/gdm/gdm_base.py +69 -0
- desdeo/api/models/gdm/gdm_score_bands.py +114 -0
- desdeo/api/models/gdm/gnimbus.py +138 -0
- desdeo/api/models/generic.py +104 -0
- desdeo/api/models/generic_states.py +401 -0
- desdeo/api/models/nimbus.py +158 -0
- desdeo/api/models/preference.py +128 -0
- desdeo/api/models/problem.py +717 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +49 -0
- desdeo/api/models/state.py +463 -0
- desdeo/api/models/user.py +52 -0
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +765 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/emo.py +497 -0
- desdeo/api/routers/enautilus.py +237 -0
- desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
- desdeo/api/routers/gdm/gdm_base.py +420 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
- desdeo/api/routers/generic.py +233 -0
- desdeo/api/routers/nimbus.py +705 -0
- desdeo/api/routers/problem.py +307 -0
- desdeo/api/routers/reference_point_method.py +93 -0
- desdeo/api/routers/session.py +100 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +520 -0
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +100 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +151 -0
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +1179 -0
- desdeo/api/tests/test_routes.py +1075 -0
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/_logger.py +29 -0
- desdeo/api/utils/database.py +36 -0
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +34 -0
- desdeo/emo/__init__.py +159 -0
- desdeo/emo/hooks/archivers.py +188 -0
- desdeo/emo/methods/EAs.py +541 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +12 -0
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +1282 -0
- desdeo/emo/operators/evaluator.py +114 -0
- desdeo/emo/operators/generator.py +459 -0
- desdeo/emo/operators/mutation.py +1224 -0
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +1778 -0
- desdeo/emo/operators/termination.py +286 -0
- desdeo/emo/options/__init__.py +108 -0
- desdeo/emo/options/algorithms.py +435 -0
- desdeo/emo/options/crossover.py +164 -0
- desdeo/emo/options/generator.py +131 -0
- desdeo/emo/options/mutation.py +260 -0
- desdeo/emo/options/repair.py +61 -0
- desdeo/emo/options/scalar_selection.py +66 -0
- desdeo/emo/options/selection.py +127 -0
- desdeo/emo/options/templates.py +383 -0
- desdeo/emo/options/termination.py +143 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/gdm/__init__.py +22 -0
- desdeo/gdm/gdmtools.py +45 -0
- desdeo/gdm/score_bands.py +114 -0
- desdeo/gdm/voting_rules.py +50 -0
- desdeo/mcdm/__init__.py +41 -0
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +656 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +186 -0
- desdeo/problem/__init__.py +83 -0
- desdeo/problem/evaluator.py +561 -0
- desdeo/problem/external/__init__.py +18 -0
- desdeo/problem/external/core.py +356 -0
- desdeo/problem/external/pymoo_provider.py +266 -0
- desdeo/problem/external/runtime.py +44 -0
- desdeo/problem/gurobipy_evaluator.py +562 -0
- desdeo/problem/infix_parser.py +341 -0
- desdeo/problem/json_parser.py +944 -0
- desdeo/problem/pyomo_evaluator.py +487 -0
- desdeo/problem/schema.py +1829 -0
- desdeo/problem/simulator_evaluator.py +348 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +88 -0
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +283 -0
- desdeo/problem/testproblems/knapsack_problem.py +163 -0
- desdeo/problem/testproblems/mcwb_problem.py +831 -0
- desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
- desdeo/problem/testproblems/momip_problem.py +172 -0
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/nimbus_problem.py +143 -0
- desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
- desdeo/problem/testproblems/re_problem.py +492 -0
- desdeo/problem/testproblems/river_pollution_problems.py +440 -0
- desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
- desdeo/problem/testproblems/simple_problem.py +351 -0
- desdeo/problem/testproblems/simulator_problem.py +92 -0
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +274 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +120 -0
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +165 -0
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +117 -0
- desdeo/tools/indicators_unary.py +362 -0
- desdeo/tools/interaction_schema.py +38 -0
- desdeo/tools/intersection.py +54 -0
- desdeo/tools/iterative_pareto_representer.py +99 -0
- desdeo/tools/message.py +265 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +134 -0
- desdeo/tools/patterns.py +283 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +477 -0
- desdeo/tools/reference_vectors.py +229 -0
- desdeo/tools/scalarization.py +2065 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +627 -0
- desdeo/tools/utils.py +388 -0
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/__init__.py +0 -0
- desdeo/utopia_stuff/data/1.json +15 -0
- desdeo/utopia_stuff/data/2.json +13 -0
- desdeo/utopia_stuff/data/3.json +15 -0
- desdeo/utopia_stuff/data/4.json +17 -0
- desdeo/utopia_stuff/data/5.json +15 -0
- desdeo/utopia_stuff/from_json.py +40 -0
- desdeo/utopia_stuff/reinit_user.py +38 -0
- desdeo/utopia_stuff/utopia_db_init.py +212 -0
- desdeo/utopia_stuff/utopia_problem.py +403 -0
- desdeo/utopia_stuff/utopia_problem_old.py +415 -0
- desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
- desdeo-2.1.0.dist-info/METADATA +186 -0
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-1.2.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
- desdeo-2.1.0.dist-info/licenses/LICENSE +21 -0
- desdeo-1.2.dist-info/METADATA +0 -16
- desdeo-1.2.dist-info/RECORD +0 -4
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
"""GNIMBUS routers.
|
|
2
|
+
|
|
3
|
+
Separated from GNIMBUSManager file for
|
|
4
|
+
A.) Clarity and
|
|
5
|
+
B.) To avoid circular imports, since we need to access the ManagerManager singleton.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Annotated
|
|
12
|
+
|
|
13
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
14
|
+
from fastapi.responses import JSONResponse
|
|
15
|
+
from sqlmodel import Session, select
|
|
16
|
+
|
|
17
|
+
from desdeo.api.db import get_session
|
|
18
|
+
from desdeo.api.models import (
|
|
19
|
+
EndProcessPreference,
|
|
20
|
+
FullIteration,
|
|
21
|
+
GNIMBUSAllIterationsResponse,
|
|
22
|
+
GNIMBUSResultResponse,
|
|
23
|
+
GNIMBUSSwitchPhaseRequest,
|
|
24
|
+
GNIMBUSSwitchPhaseResponse,
|
|
25
|
+
GNIMBUSVotingState,
|
|
26
|
+
Group,
|
|
27
|
+
GroupInfoRequest,
|
|
28
|
+
GroupIteration,
|
|
29
|
+
GroupRevertRequest,
|
|
30
|
+
OptimizationPreference,
|
|
31
|
+
ProblemDB,
|
|
32
|
+
SolutionReference,
|
|
33
|
+
SolutionReferenceLite,
|
|
34
|
+
StateDB,
|
|
35
|
+
User,
|
|
36
|
+
VotingPreference,
|
|
37
|
+
)
|
|
38
|
+
from desdeo.api.routers.gdm.gdm_aggregate import manager
|
|
39
|
+
from desdeo.api.routers.problem import check_solver
|
|
40
|
+
from desdeo.api.routers.user_authentication import get_current_user
|
|
41
|
+
from desdeo.mcdm.nimbus import generate_starting_point
|
|
42
|
+
from desdeo.problem import Problem
|
|
43
|
+
|
|
44
|
+
logging.basicConfig(
|
|
45
|
+
stream=sys.stdout, format="[%(filename)s:%(lineno)d] %(levelname)s: %(message)s", level=logging.INFO
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
not_init_error = HTTPException(detail="Problem has not been initialized!", status_code=status.HTTP_400_BAD_REQUEST)
|
|
49
|
+
|
|
50
|
+
router = APIRouter(prefix="/gnimbus", tags=["GNIMBUS"])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@router.post("/initialize")
|
|
54
|
+
def gnimbus_initialize(
|
|
55
|
+
request: GroupInfoRequest,
|
|
56
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
57
|
+
session: Annotated[Session, Depends(get_session)],
|
|
58
|
+
):
|
|
59
|
+
"""Initialize the problem for GNIMBUS."""
|
|
60
|
+
#Check that all pre-conditions are all right
|
|
61
|
+
group = session.exec(select(Group).where(Group.id == request.group_id)).first()
|
|
62
|
+
if group is None:
|
|
63
|
+
raise HTTPException(detail=f"No group with ID {request.group_id} found!", status_code=status.HTTP_404_NOT_FOUND)
|
|
64
|
+
if not (user.id in group.user_ids or user.id is group.owner_id):
|
|
65
|
+
raise HTTPException(detail="Unauthorized user", status_code=status.HTTP_401_UNAUTHORIZED)
|
|
66
|
+
|
|
67
|
+
head_iteration = session.exec(select(GroupIteration)
|
|
68
|
+
.where(GroupIteration.id == group.head_iteration_id)).first()
|
|
69
|
+
if head_iteration is not None:
|
|
70
|
+
raise HTTPException(detail="Group problem already initialized!", status_code=status.HTTP_400_BAD_REQUEST)
|
|
71
|
+
|
|
72
|
+
problem_db = session.exec(select(ProblemDB).where(ProblemDB.id == group.problem_id)).first()
|
|
73
|
+
if problem_db is None:
|
|
74
|
+
raise HTTPException(
|
|
75
|
+
detail=f"No problem with id {group.problem_id} found!", status_code=status.HTTP_404_NOT_FOUND
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
solver = check_solver(problem_db=problem_db)
|
|
79
|
+
|
|
80
|
+
problem = Problem.from_problemdb(problem_db)
|
|
81
|
+
|
|
82
|
+
# Create the first iteration for the group
|
|
83
|
+
# As if we just voted for a result, but really is just the starting point.
|
|
84
|
+
starting_point = generate_starting_point(problem=problem, solver=solver)
|
|
85
|
+
|
|
86
|
+
gnimbus_state = GNIMBUSVotingState(votes={}, solver_results=[starting_point])
|
|
87
|
+
|
|
88
|
+
# Create init state
|
|
89
|
+
state = StateDB.create(
|
|
90
|
+
database_session=session, problem_id=problem_db.id, session_id=None, parent_id=None, state=gnimbus_state
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
session.add(state)
|
|
94
|
+
session.commit()
|
|
95
|
+
session.refresh(state)
|
|
96
|
+
|
|
97
|
+
# The starting iteration
|
|
98
|
+
start_iteration = GroupIteration(
|
|
99
|
+
problem_id=group.problem_id,
|
|
100
|
+
group_id=group.id,
|
|
101
|
+
info_container=VotingPreference(
|
|
102
|
+
set_preferences={},
|
|
103
|
+
),
|
|
104
|
+
notified={},
|
|
105
|
+
state_id=state.id,
|
|
106
|
+
parent_id=None,
|
|
107
|
+
parent=None,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
session.add(start_iteration)
|
|
111
|
+
session.commit()
|
|
112
|
+
session.refresh(start_iteration)
|
|
113
|
+
|
|
114
|
+
# New iteration for continuing; to this we add new preferences and begin building a linked list
|
|
115
|
+
new_iteration = GroupIteration(
|
|
116
|
+
problem_id=start_iteration.problem_id,
|
|
117
|
+
group_id=start_iteration.group_id,
|
|
118
|
+
info_container=OptimizationPreference(
|
|
119
|
+
set_preferences={},
|
|
120
|
+
),
|
|
121
|
+
notified={},
|
|
122
|
+
parent_id=start_iteration.id,
|
|
123
|
+
parent=start_iteration,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
session.add(new_iteration)
|
|
127
|
+
session.commit()
|
|
128
|
+
session.refresh(new_iteration)
|
|
129
|
+
|
|
130
|
+
children = start_iteration.children.copy()
|
|
131
|
+
children.append(new_iteration)
|
|
132
|
+
start_iteration.children = children
|
|
133
|
+
session.add(start_iteration)
|
|
134
|
+
group.head_iteration_id = new_iteration.id
|
|
135
|
+
session.add(group)
|
|
136
|
+
session.commit()
|
|
137
|
+
|
|
138
|
+
return JSONResponse(content={"message": f"Group {group.id} initialized."}, status_code=status.HTTP_200_OK)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@router.post("/get_latest_results")
|
|
142
|
+
def get_latest_results(
|
|
143
|
+
request: GroupInfoRequest,
|
|
144
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
145
|
+
session: Annotated[Session, Depends(get_session)],
|
|
146
|
+
) -> GNIMBUSResultResponse:
|
|
147
|
+
"""Get the latest results from group iteration.
|
|
148
|
+
|
|
149
|
+
(OBSOLETE AND OUT OF DATE!)
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
request (GroupInfoRequest): essentially just the ID of the group
|
|
153
|
+
user (Annotated[User, Depends(get_current_user)]): Current user
|
|
154
|
+
session (Annotated[Session, Depends(get_session)]): Database session.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
GNIMBUSResultResponse: A GNIMBUSResultResponse response containing the latest gnimbus results
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
HTTPException: Validation errors or no results
|
|
161
|
+
"""
|
|
162
|
+
group: Group = session.exec(select(Group).where(Group.id == request.group_id)).first()
|
|
163
|
+
if group is None:
|
|
164
|
+
raise HTTPException(detail=f"No group with ID {request.group_id} found", status_code=status.HTTP_404_NOT_FOUND)
|
|
165
|
+
|
|
166
|
+
if not (user.id in group.user_ids or user.id is group.owner_id):
|
|
167
|
+
raise HTTPException(detail="Unauthorized user.", status_code=status.HTTP_401_UNAUTHORIZED)
|
|
168
|
+
|
|
169
|
+
nores_exp = HTTPException(detail="No results found!", status_code=status.HTTP_404_NOT_FOUND)
|
|
170
|
+
|
|
171
|
+
head_iteration = session.exec(select(GroupIteration)
|
|
172
|
+
.where(GroupIteration.id == group.head_iteration_id)).first()
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
iteration = head_iteration.parent
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise nores_exp from e
|
|
178
|
+
|
|
179
|
+
if iteration is None:
|
|
180
|
+
raise nores_exp
|
|
181
|
+
|
|
182
|
+
state = session.exec(select(StateDB).where(StateDB.id == iteration.state_id)).first()
|
|
183
|
+
if state is None:
|
|
184
|
+
raise nores_exp
|
|
185
|
+
|
|
186
|
+
actual_state = state.state
|
|
187
|
+
preferences = iteration.info_container
|
|
188
|
+
|
|
189
|
+
if type(actual_state) is GNIMBUSVotingState:
|
|
190
|
+
return GNIMBUSResultResponse(
|
|
191
|
+
method="voting",
|
|
192
|
+
phase=iteration.parent.info_container.phase if iteration.parent is not None else "learning",
|
|
193
|
+
preferences=preferences,
|
|
194
|
+
common_results=[SolutionReference(state=state, solution_index=0)],
|
|
195
|
+
user_results=[],
|
|
196
|
+
personal_result_index=None,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
personal_result_index = None
|
|
200
|
+
for i, item in enumerate(preferences.set_preferences.items()):
|
|
201
|
+
if item[0] == user.id:
|
|
202
|
+
personal_result_index = i
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
if personal_result_index is None:
|
|
206
|
+
raise HTTPException(detail=f"No personal results for user ID {user.id}", status_code=status.HTTP_404_NOT_FOUND)
|
|
207
|
+
|
|
208
|
+
solution_references = []
|
|
209
|
+
for i, _ in enumerate(actual_state.solver_results):
|
|
210
|
+
solution_references.append(SolutionReference(state=state, solution_index=i))
|
|
211
|
+
|
|
212
|
+
return GNIMBUSResultResponse(
|
|
213
|
+
method="optimization",
|
|
214
|
+
phase=iteration.info_container.phase,
|
|
215
|
+
preferences=preferences,
|
|
216
|
+
common_results=solution_references[-4:],
|
|
217
|
+
user_results=solution_references[:-4],
|
|
218
|
+
personal_result_index=personal_result_index,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@router.post("/all_iterations")
|
|
223
|
+
def full_iteration( # noqa: C901, PLR0912
|
|
224
|
+
request: GroupInfoRequest,
|
|
225
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
226
|
+
session: Annotated[Session, Depends(get_session)],
|
|
227
|
+
) -> GNIMBUSAllIterationsResponse:
|
|
228
|
+
"""Get all results from all iterations of the group.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
request (GroupInfoRequest): essentially just the ID of the group
|
|
232
|
+
user (Annotated[User, Depends(get_current_user)]): current user
|
|
233
|
+
session (Annotated[Session, Depends(get_session)]): current session
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
GNIMBUSAllIterationsResponse: A GNIMBUSAllIterationsResponse response
|
|
237
|
+
containing all the results of the iterations. If last iteration was optimization,
|
|
238
|
+
the first iteration is incomplete (i.e. the voting preferences and voting results are missing)
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
HTTPException: Validation errors or no results or no states and such.
|
|
242
|
+
"""
|
|
243
|
+
group = session.exec(select(Group).where(Group.id == request.group_id)).first()
|
|
244
|
+
if group is None:
|
|
245
|
+
raise HTTPException(detail=f"No group with ID {request.group_id}!", status_code=status.HTTP_404_NOT_FOUND)
|
|
246
|
+
|
|
247
|
+
if user.id not in group.user_ids and user.id is not group.owner_id:
|
|
248
|
+
raise HTTPException(detail="Unauthorized user", status_code=status.HTTP_401_UNAUTHORIZED)
|
|
249
|
+
|
|
250
|
+
head_iteration = session.exec(select(GroupIteration)
|
|
251
|
+
.where(GroupIteration.id == group.head_iteration_id)).first()
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
groupiter = head_iteration.parent
|
|
255
|
+
except Exception as e:
|
|
256
|
+
raise not_init_error from e
|
|
257
|
+
|
|
258
|
+
if groupiter is None:
|
|
259
|
+
raise not_init_error
|
|
260
|
+
|
|
261
|
+
full_iterations: list[FullIteration] = []
|
|
262
|
+
|
|
263
|
+
user_len = len(group.user_ids)
|
|
264
|
+
|
|
265
|
+
if groupiter.info_container.method == "optimization":
|
|
266
|
+
# There are no full results because the latest iteration is optimization,
|
|
267
|
+
# so add an incomplete entry to the list to be returned.
|
|
268
|
+
prev_state = session.exec(select(StateDB).where(StateDB.id == groupiter.parent.state_id)).first()
|
|
269
|
+
if prev_state is None:
|
|
270
|
+
raise HTTPException(detail="No state for starting results!", status_code=status.HTTP_404_NOT_FOUND)
|
|
271
|
+
|
|
272
|
+
this_state = session.exec(select(StateDB).where(StateDB.id == groupiter.state_id)).first()
|
|
273
|
+
if this_state is None:
|
|
274
|
+
raise HTTPException(detail="No state in most recent iteration!", status_code=status.HTTP_404_NOT_FOUND)
|
|
275
|
+
|
|
276
|
+
personal_result_index = None
|
|
277
|
+
for i, item in enumerate(groupiter.info_container.set_preferences.items()):
|
|
278
|
+
if item[0] == user.id:
|
|
279
|
+
personal_result_index = i
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
all_results = []
|
|
283
|
+
for i, _ in enumerate(this_state.state.solver_results):
|
|
284
|
+
all_results.append(SolutionReferenceLite(state=this_state, solution_index=i))
|
|
285
|
+
|
|
286
|
+
phase = groupiter.info_container.phase
|
|
287
|
+
|
|
288
|
+
full_iterations.append(
|
|
289
|
+
FullIteration(
|
|
290
|
+
phase=phase,
|
|
291
|
+
optimization_preferences=groupiter.info_container,
|
|
292
|
+
voting_preferences=None,
|
|
293
|
+
starting_result=SolutionReferenceLite(state=prev_state, solution_index=0),
|
|
294
|
+
common_results=all_results if phase in ["decision", "compromise"] else all_results[user_len:],
|
|
295
|
+
user_results=all_results[:user_len],
|
|
296
|
+
personal_result_index=personal_result_index,
|
|
297
|
+
final_result=None,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
groupiter = groupiter.parent
|
|
302
|
+
|
|
303
|
+
# We're at voting/end method now. Construct an FullIteration item from Voting/Ending an Optimization iterations
|
|
304
|
+
# A bit of a complicated mess, I could have done this in a better manner.
|
|
305
|
+
while groupiter is not None and groupiter.parent is not None and groupiter.parent.parent is not None:
|
|
306
|
+
this_state = session.exec(select(StateDB).where(StateDB.id == groupiter.state_id)).first()
|
|
307
|
+
prev_state = session.exec(select(StateDB).where(StateDB.id == groupiter.parent.state_id)).first()
|
|
308
|
+
first_state = session.exec(select(StateDB).where(StateDB.id == groupiter.parent.parent.state_id)).first()
|
|
309
|
+
|
|
310
|
+
if this_state is None or prev_state is None or first_state is None:
|
|
311
|
+
raise HTTPException(detail="All needed states do not exist!", status_code=status.HTTP_404_NOT_FOUND)
|
|
312
|
+
|
|
313
|
+
all_results = []
|
|
314
|
+
for i, _ in enumerate(prev_state.state.solver_results):
|
|
315
|
+
all_results.append(SolutionReferenceLite(state=prev_state, solution_index=i))
|
|
316
|
+
|
|
317
|
+
personal_result_index = None
|
|
318
|
+
for i, item in enumerate(groupiter.parent.info_container.set_preferences.items()):
|
|
319
|
+
if item[0] == user.id:
|
|
320
|
+
personal_result_index = i
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
phase = groupiter.parent.info_container.phase
|
|
324
|
+
|
|
325
|
+
full_iterations.append(
|
|
326
|
+
FullIteration(
|
|
327
|
+
phase=phase,
|
|
328
|
+
optimization_preferences=groupiter.parent.info_container,
|
|
329
|
+
voting_preferences=groupiter.info_container,
|
|
330
|
+
starting_result=SolutionReferenceLite(state=first_state, solution_index=0),
|
|
331
|
+
common_results=all_results if phase in ["decision", "compromise"] else all_results[user_len:],
|
|
332
|
+
user_results=all_results[:user_len],
|
|
333
|
+
personal_result_index=personal_result_index,
|
|
334
|
+
final_result=SolutionReferenceLite(state=this_state, solution_index=0),
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
groupiter = groupiter.parent.parent
|
|
339
|
+
|
|
340
|
+
# We're at the root, so add the initialization iteration (essentially empty with just a final result)
|
|
341
|
+
if groupiter is not None and groupiter.parent is None:
|
|
342
|
+
this_state = session.exec(select(StateDB).where(StateDB.id == groupiter.state_id)).first()
|
|
343
|
+
|
|
344
|
+
if this_state is None:
|
|
345
|
+
raise HTTPException(detail="Initialization state does not exist!", status_code=status.HTTP_404_NOT_FOUND)
|
|
346
|
+
|
|
347
|
+
full_iterations.append(
|
|
348
|
+
FullIteration(
|
|
349
|
+
phase="init",
|
|
350
|
+
optimization_preferences=None,
|
|
351
|
+
voting_preferences=None,
|
|
352
|
+
starting_result=None,
|
|
353
|
+
common_results=[],
|
|
354
|
+
user_results=[],
|
|
355
|
+
personal_result_index=None,
|
|
356
|
+
final_result=SolutionReferenceLite(state=this_state, solution_index=0),
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
return GNIMBUSAllIterationsResponse(all_full_iterations=full_iterations)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@router.post("/toggle_phase")
|
|
364
|
+
async def switch_phase(
|
|
365
|
+
request: GNIMBUSSwitchPhaseRequest,
|
|
366
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
367
|
+
session: Annotated[Session, Depends(get_session)],
|
|
368
|
+
) -> GNIMBUSSwitchPhaseResponse:
|
|
369
|
+
"""Switch the phase from one to another. "learning", "crp", "decision" and "compromise" phases are allowed."""
|
|
370
|
+
#Preliminary checks etc.
|
|
371
|
+
if request.new_phase not in ["learning", "crp", "decision", "compromise"]:
|
|
372
|
+
raise HTTPException(
|
|
373
|
+
detail=f"Undefined phase: {request.new_phase}! Can only be {['learning', 'crp', 'decision', 'compromise']}",
|
|
374
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
375
|
+
)
|
|
376
|
+
group: Group = session.exec(select(Group).where(Group.id == request.group_id)).first()
|
|
377
|
+
if group is None:
|
|
378
|
+
raise HTTPException(detail=f"No group with ID {request.group_id} found", status_code=status.HTTP_404_NOT_FOUND)
|
|
379
|
+
if user.id is not group.owner_id:
|
|
380
|
+
raise HTTPException(detail="Unauthorized user.", status_code=status.HTTP_401_UNAUTHORIZED)
|
|
381
|
+
iteration = session.exec(select(GroupIteration)
|
|
382
|
+
.where(GroupIteration.id == group.head_iteration_id)).first()
|
|
383
|
+
if iteration is None:
|
|
384
|
+
raise HTTPException(
|
|
385
|
+
detail="There's no iterations! Did you forget to initialize the problem?",
|
|
386
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
387
|
+
)
|
|
388
|
+
if iteration.info_container is None:
|
|
389
|
+
raise HTTPException(details="Now this is a funky problem!", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
390
|
+
if iteration.info_container.method == "voting":
|
|
391
|
+
raise HTTPException(
|
|
392
|
+
detail="You cannot switch the phase in the middle of the iteration.",
|
|
393
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
old_phase = iteration.info_container.phase
|
|
397
|
+
new_phase = request.new_phase
|
|
398
|
+
|
|
399
|
+
# Set the phase in the preferences
|
|
400
|
+
preferences = copy.deepcopy(iteration.info_container)
|
|
401
|
+
preferences.phase = new_phase
|
|
402
|
+
|
|
403
|
+
iteration.info_container = preferences
|
|
404
|
+
session.add(iteration)
|
|
405
|
+
session.commit()
|
|
406
|
+
session.refresh(iteration)
|
|
407
|
+
|
|
408
|
+
# get the group manager and notify the connected users that the phase has changed
|
|
409
|
+
gman = await manager.get_group_manager(group_id=group.id, method="gnimbus")
|
|
410
|
+
|
|
411
|
+
await gman.broadcast(f"The phase was changed from {old_phase} to {new_phase}.")
|
|
412
|
+
|
|
413
|
+
return GNIMBUSSwitchPhaseResponse(old_phase=old_phase, new_phase=new_phase)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@router.post("/get_phase")
|
|
417
|
+
def get_phase(
|
|
418
|
+
request: GroupInfoRequest,
|
|
419
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
420
|
+
session: Annotated[Session, Depends(get_session)],
|
|
421
|
+
) -> JSONResponse:
|
|
422
|
+
"""Get the current phase of the group."""
|
|
423
|
+
group: Group = session.exec(select(Group).where(Group.id == request.group_id)).first()
|
|
424
|
+
if group is None:
|
|
425
|
+
raise HTTPException(detail=f"No group with ID {request.group_id} found", status_code=status.HTTP_404_NOT_FOUND)
|
|
426
|
+
|
|
427
|
+
g = group.user_ids
|
|
428
|
+
g.append(group.owner_id)
|
|
429
|
+
|
|
430
|
+
if user.id not in g:
|
|
431
|
+
raise HTTPException(detail="Unauthorized user.", status_code=status.HTTP_401_UNAUTHORIZED)
|
|
432
|
+
|
|
433
|
+
current_iteration= session.exec(select(GroupIteration)
|
|
434
|
+
.where(GroupIteration.id == group.head_iteration_id)).first()
|
|
435
|
+
if current_iteration is None:
|
|
436
|
+
raise not_init_error
|
|
437
|
+
|
|
438
|
+
if current_iteration.info_container.method != "optimization":
|
|
439
|
+
current_iteration = current_iteration.parent
|
|
440
|
+
|
|
441
|
+
return JSONResponse(content={"phase": current_iteration.info_container.phase}, status_code=status.HTTP_200_OK)
|
|
442
|
+
|
|
443
|
+
def get_preference_item(iteration):
|
|
444
|
+
"""Returns an empty preference item for adding preferences; The preferences are the next preferences.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
iteration: we consider this iteration and it's preferences.
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
Preference item.
|
|
451
|
+
"""
|
|
452
|
+
item = iteration.preferences
|
|
453
|
+
if type(item) is OptimizationPreference:
|
|
454
|
+
if item.phase in ["decision", "compromise"]:
|
|
455
|
+
return EndProcessPreference(
|
|
456
|
+
success=None,
|
|
457
|
+
set_preferences={}
|
|
458
|
+
)
|
|
459
|
+
return VotingPreference(
|
|
460
|
+
set_preferences={}
|
|
461
|
+
)
|
|
462
|
+
return OptimizationPreference(
|
|
463
|
+
phase=iteration.parent.preferences.phase if iteration.parent.preferences.phase is not None else "learning",
|
|
464
|
+
set_preferences={},
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@router.post("/revert_iteration")
|
|
469
|
+
async def revert_iteration(
|
|
470
|
+
request: GroupRevertRequest,
|
|
471
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
472
|
+
session: Annotated[Session, Depends(get_session)],
|
|
473
|
+
) -> JSONResponse:
|
|
474
|
+
"""Changes the starting solution of an iteration so in case of emergency the group owner can just change it.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
request (GNIMBUSChangeStartingSolutionRequest): The request containing necessary details to fulfill the change.
|
|
478
|
+
user (Annotated[User, Depends): The current user.
|
|
479
|
+
session (Annotated[Session, Depends): The database session.
|
|
480
|
+
|
|
481
|
+
Raises:
|
|
482
|
+
HTTPException
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
JSONResponse: Response that acknowledges the changes.
|
|
486
|
+
"""
|
|
487
|
+
group: Group = session.exec(select(Group).where(Group.id == request.group_id)).first()
|
|
488
|
+
if not group:
|
|
489
|
+
raise HTTPException(
|
|
490
|
+
detail=f"No group with ID {request.group_id}!",
|
|
491
|
+
status_code=status.HTTP_404_NOT_FOUND
|
|
492
|
+
)
|
|
493
|
+
if group.owner_id is not user.id:
|
|
494
|
+
raise HTTPException(
|
|
495
|
+
detail="Unauthorized user!",
|
|
496
|
+
status_code=status.HTTP_401_UNAUTHORIZED
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
current_iteration = session.exec(select(GroupIteration)
|
|
500
|
+
.where(GroupIteration.id == group.head_iteration_id)).first()
|
|
501
|
+
|
|
502
|
+
if not current_iteration:
|
|
503
|
+
raise HTTPException(
|
|
504
|
+
detail="There's no head iteration",
|
|
505
|
+
status_code=status.HTTP_404_NOT_FOUND
|
|
506
|
+
)
|
|
507
|
+
head_iteration_type = type(current_iteration.info_container)
|
|
508
|
+
if head_iteration_type in [VotingPreference, EndProcessPreference]:
|
|
509
|
+
raise HTTPException(
|
|
510
|
+
detail="Complete the iteration before reverting. Sorry for the inconvenience.",
|
|
511
|
+
status_code=status.HTTP_400_BAD_REQUEST
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# Get the GroupIteration corresponding to the requests state id
|
|
515
|
+
target_iteration = session.exec(select(GroupIteration).where(GroupIteration.state_id == request.state_id)).first()
|
|
516
|
+
if not target_iteration:
|
|
517
|
+
raise HTTPException(
|
|
518
|
+
detail=f"There's no iteration with state ID {request.state_id}!",
|
|
519
|
+
status_code=status.HTTP_404_NOT_FOUND
|
|
520
|
+
)
|
|
521
|
+
target_iteration_type = type(target_iteration.info_container)
|
|
522
|
+
if target_iteration_type == OptimizationPreference:
|
|
523
|
+
raise HTTPException(
|
|
524
|
+
detail="You can only revert to a result of a complete iteration. Sorry for the inconvenience.",
|
|
525
|
+
status_code=status.HTTP_400_BAD_REQUEST
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# We must "artificially" create some history, so that we can later on fetch stuff seamlessly,
|
|
529
|
+
# without any hiccups or changes to the "all_iterations" endpoint. Essentially we copy two latest
|
|
530
|
+
# iterations to the head of the history.
|
|
531
|
+
new_parent_1 = GroupIteration(
|
|
532
|
+
problem_id=group.problem_id,
|
|
533
|
+
group_id=group.id,
|
|
534
|
+
info_container=target_iteration.parent.info_container.model_copy(),
|
|
535
|
+
notified=target_iteration.parent.notified.copy(),
|
|
536
|
+
state_id=target_iteration.parent.state_id,
|
|
537
|
+
parent_id=current_iteration.parent.id,
|
|
538
|
+
parent=current_iteration.parent
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
session.add(new_parent_1)
|
|
542
|
+
session.commit()
|
|
543
|
+
session.refresh(new_parent_1)
|
|
544
|
+
|
|
545
|
+
new_parent_2 = GroupIteration(
|
|
546
|
+
problem_id=group.problem_id,
|
|
547
|
+
group_id=group.id,
|
|
548
|
+
info_container=target_iteration.info_container.model_copy(),
|
|
549
|
+
notified=target_iteration.notified.copy(),
|
|
550
|
+
state_id=target_iteration.state_id,
|
|
551
|
+
parent_id=new_parent_1.id,
|
|
552
|
+
parent=new_parent_1
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
session.add(new_parent_2)
|
|
556
|
+
session.commit()
|
|
557
|
+
session.refresh(new_parent_2)
|
|
558
|
+
|
|
559
|
+
# New head iteration
|
|
560
|
+
new_head = GroupIteration(
|
|
561
|
+
problem_id=group.problem_id,
|
|
562
|
+
group_id=group.id,
|
|
563
|
+
info_container=OptimizationPreference(
|
|
564
|
+
phase=target_iteration.parent.info_container.phase \
|
|
565
|
+
if target_iteration.parent.info_container.phase is not None else "learning",
|
|
566
|
+
set_preferences={}
|
|
567
|
+
),
|
|
568
|
+
notified={},
|
|
569
|
+
parent_id=new_parent_2.id,
|
|
570
|
+
parent=new_parent_2,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
session.add(new_head)
|
|
574
|
+
session.commit()
|
|
575
|
+
|
|
576
|
+
group.head_iteration_id = new_head.id
|
|
577
|
+
|
|
578
|
+
session.add(group)
|
|
579
|
+
session.delete(current_iteration)
|
|
580
|
+
session.commit()
|
|
581
|
+
session.refresh(group)
|
|
582
|
+
|
|
583
|
+
gman = await manager.get_group_manager(group_id=group.id, method="gnimbus")
|
|
584
|
+
await gman.broadcast("UPDATE: Latest iteration reversed by the group owner!")
|
|
585
|
+
|
|
586
|
+
return JSONResponse(
|
|
587
|
+
content={
|
|
588
|
+
"message": "Iteration reversed!",
|
|
589
|
+
},
|
|
590
|
+
status_code=status.HTTP_200_OK
|
|
591
|
+
)
|