desdeo 2.0.0__py3-none-any.whl → 2.1.1__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/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/__init__.py +6 -6
- desdeo/api/app.py +38 -28
- desdeo/api/config.py +65 -44
- desdeo/api/config.toml +23 -12
- desdeo/api/db.py +10 -8
- desdeo/api/db_init.py +12 -6
- desdeo/api/models/__init__.py +220 -20
- desdeo/api/models/archive.py +16 -27
- 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 +44 -6
- desdeo/api/models/problem.py +274 -64
- desdeo/api/models/session.py +4 -1
- desdeo/api/models/state.py +419 -52
- desdeo/api/models/user.py +7 -6
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NIMBUS.py +6 -3
- 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 +201 -4
- desdeo/api/routers/reference_point_method.py +20 -44
- desdeo/api/routers/session.py +50 -26
- desdeo/api/routers/user_authentication.py +180 -26
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +10 -4
- desdeo/api/tests/conftest.py +94 -2
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +550 -72
- desdeo/api/tests/test_routes.py +902 -43
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/database.py +28 -266
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +7 -0
- desdeo/emo/__init__.py +154 -24
- desdeo/emo/hooks/archivers.py +18 -2
- desdeo/emo/methods/EAs.py +128 -5
- desdeo/emo/methods/bases.py +9 -56
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/crossover.py +544 -42
- desdeo/emo/operators/evaluator.py +10 -14
- desdeo/emo/operators/generator.py +127 -24
- desdeo/emo/operators/mutation.py +212 -41
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +956 -214
- desdeo/emo/operators/termination.py +124 -16
- 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/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 +23 -1
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautilus_navigator.py +7 -6
- desdeo/mcdm/reference_point_method.py +70 -0
- desdeo/problem/__init__.py +16 -11
- desdeo/problem/evaluator.py +4 -5
- 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 +37 -12
- desdeo/problem/infix_parser.py +1 -16
- desdeo/problem/json_parser.py +7 -11
- desdeo/problem/pyomo_evaluator.py +25 -6
- desdeo/problem/schema.py +73 -55
- desdeo/problem/simulator_evaluator.py +65 -15
- desdeo/problem/testproblems/__init__.py +26 -11
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/forest_problem.py +77 -69
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/zdt_problem.py +4 -1
- desdeo/problem/utils.py +1 -1
- desdeo/tools/__init__.py +39 -21
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +22 -2
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/indicators_binary.py +107 -1
- desdeo/tools/indicators_unary.py +3 -16
- desdeo/tools/message.py +33 -2
- desdeo/tools/non_dominated_sorting.py +4 -3
- desdeo/tools/patterns.py +9 -7
- desdeo/tools/pyomo_solver_interfaces.py +49 -36
- desdeo/tools/reference_vectors.py +118 -351
- desdeo/tools/scalarization.py +340 -1413
- desdeo/tools/score_bands.py +491 -328
- desdeo/tools/utils.py +117 -49
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/utopia_problem.py +1 -1
- desdeo/utopia_stuff/utopia_problem_old.py +1 -1
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/METADATA +47 -30
- desdeo-2.1.1.dist-info/RECORD +180 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/WHEEL +1 -1
- desdeo-2.0.0.dist-info/RECORD +0 -120
- /desdeo/api/utils/{logger.py → _logger.py} +0 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Defines end-points to access generic functionalities."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
8
|
+
from sqlmodel import Session, select
|
|
9
|
+
|
|
10
|
+
from desdeo.api.db import get_session
|
|
11
|
+
from desdeo.api.models import (
|
|
12
|
+
InteractiveSessionDB,
|
|
13
|
+
IntermediateSolutionRequest,
|
|
14
|
+
IntermediateSolutionState,
|
|
15
|
+
ProblemDB,
|
|
16
|
+
ScoreBandsRequest,
|
|
17
|
+
ScoreBandsResponse,
|
|
18
|
+
SolutionReference,
|
|
19
|
+
StateDB,
|
|
20
|
+
User,
|
|
21
|
+
)
|
|
22
|
+
from desdeo.api.models.generic import GenericIntermediateSolutionResponse
|
|
23
|
+
from desdeo.api.routers.user_authentication import get_current_user
|
|
24
|
+
from desdeo.mcdm.nimbus import solve_intermediate_solutions
|
|
25
|
+
from desdeo.problem import Problem
|
|
26
|
+
from desdeo.tools import SolverResults
|
|
27
|
+
from desdeo.tools.score_bands import calculate_axes_positions, cluster, order_dimensions
|
|
28
|
+
|
|
29
|
+
router = APIRouter(prefix="/method/generic")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.post("/intermediate")
|
|
33
|
+
def solve_intermediate(
|
|
34
|
+
request: IntermediateSolutionRequest,
|
|
35
|
+
user: Annotated[User, Depends(get_current_user)],
|
|
36
|
+
session: Annotated[Session, Depends(get_session)],
|
|
37
|
+
) -> GenericIntermediateSolutionResponse:
|
|
38
|
+
"""Solve intermediate solutions between given two solutions."""
|
|
39
|
+
if request.session_id is not None:
|
|
40
|
+
statement = select(InteractiveSessionDB).where(InteractiveSessionDB.id == request.session_id)
|
|
41
|
+
interactive_session = session.exec(statement)
|
|
42
|
+
|
|
43
|
+
if interactive_session is None:
|
|
44
|
+
raise HTTPException(
|
|
45
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
46
|
+
detail=f"Could not find interactive session with id={request.session_id}.",
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
# request.session_id is None:
|
|
50
|
+
# use active session instead
|
|
51
|
+
statement = select(InteractiveSessionDB).where(InteractiveSessionDB.id == user.active_session_id)
|
|
52
|
+
|
|
53
|
+
interactive_session = session.exec(statement).first()
|
|
54
|
+
|
|
55
|
+
# query both reference solutions' variable values
|
|
56
|
+
# stored as lit of tuples, first element of each tuple are variables values, second are objective function values
|
|
57
|
+
var_and_obj_values_of_references: list[tuple[dict, dict]] = []
|
|
58
|
+
reference_states = []
|
|
59
|
+
for solution_info in [request.reference_solution_1, request.reference_solution_2]:
|
|
60
|
+
solution_state = session.exec(select(StateDB).where(StateDB.id == solution_info.state_id)).first()
|
|
61
|
+
|
|
62
|
+
if solution_state is None:
|
|
63
|
+
# no StateDB found with the given id
|
|
64
|
+
raise HTTPException(
|
|
65
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
66
|
+
detail=f"Could not find a state with the given id{solution_state.state_id}.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
reference_states.append(solution_state)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
_var_values = solution_state.state.result_variable_values
|
|
73
|
+
var_values = _var_values[solution_info.solution_index]
|
|
74
|
+
|
|
75
|
+
except IndexError as exc:
|
|
76
|
+
raise HTTPException(
|
|
77
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
78
|
+
detail=(
|
|
79
|
+
f"The index {solution_info.solution_index} is out of bounds for results with len={len(_var_values)}"
|
|
80
|
+
),
|
|
81
|
+
) from exc
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
_obj_values = solution_state.state.result_objective_values
|
|
85
|
+
obj_values = _obj_values[solution_info.solution_index]
|
|
86
|
+
|
|
87
|
+
except IndexError as exc:
|
|
88
|
+
raise HTTPException(
|
|
89
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
90
|
+
detail=(
|
|
91
|
+
f"The index {solution_info.solution_index} is out of bounds for results with len={len(_obj_values)}"
|
|
92
|
+
),
|
|
93
|
+
) from exc
|
|
94
|
+
|
|
95
|
+
var_and_obj_values_of_references.append((var_values, obj_values))
|
|
96
|
+
|
|
97
|
+
# fetch the problem from the DB
|
|
98
|
+
statement = select(ProblemDB).where(ProblemDB.user_id == user.id, ProblemDB.id == request.problem_id)
|
|
99
|
+
problem_db = session.exec(statement).first()
|
|
100
|
+
|
|
101
|
+
if problem_db is None:
|
|
102
|
+
raise HTTPException(
|
|
103
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
104
|
+
detail=f"Problem with id={request.problem_id} could not be found.",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
problem = Problem.from_problemdb(problem_db)
|
|
108
|
+
|
|
109
|
+
solver_results: list[SolverResults] = solve_intermediate_solutions(
|
|
110
|
+
problem=problem,
|
|
111
|
+
solution_1=var_and_obj_values_of_references[0][0],
|
|
112
|
+
solution_2=var_and_obj_values_of_references[1][0],
|
|
113
|
+
num_desired=request.num_desired,
|
|
114
|
+
scalarization_options=request.scalarization_options,
|
|
115
|
+
solver=request.solver,
|
|
116
|
+
solver_options=request.solver_options,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# fetch parent state
|
|
120
|
+
if request.parent_state_id is None:
|
|
121
|
+
# parent state is assumed to be the last state added to the session.
|
|
122
|
+
parent_state = (
|
|
123
|
+
interactive_session.states[-1]
|
|
124
|
+
if (interactive_session is not None and len(interactive_session.states) > 0)
|
|
125
|
+
else None
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
else:
|
|
129
|
+
# request.parent_state_id is not None
|
|
130
|
+
statement = session.select(StateDB).where(StateDB.id == request.parent_state_id)
|
|
131
|
+
parent_state = session.exec(statement).first()
|
|
132
|
+
|
|
133
|
+
if parent_state is None:
|
|
134
|
+
raise HTTPException(
|
|
135
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
136
|
+
detail=f"Could not find state with id={request.parent_state_id}",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
intermediate_state = IntermediateSolutionState(
|
|
140
|
+
scalarization_options=request.scalarization_options,
|
|
141
|
+
context=request.context,
|
|
142
|
+
solver=request.solver,
|
|
143
|
+
solver_options=request.solver_options,
|
|
144
|
+
solver_results=solver_results,
|
|
145
|
+
num_desired=request.num_desired,
|
|
146
|
+
reference_solution_1=var_and_obj_values_of_references[0][1],
|
|
147
|
+
reference_solution_2=var_and_obj_values_of_references[1][1],
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# create DB state and add it to the DB
|
|
151
|
+
state = StateDB.create(
|
|
152
|
+
database_session=session,
|
|
153
|
+
problem_id=problem_db.id,
|
|
154
|
+
session_id=interactive_session.id if interactive_session is not None else None,
|
|
155
|
+
parent_id=parent_state.id if parent_state is not None else None,
|
|
156
|
+
state=intermediate_state,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
session.add(state)
|
|
160
|
+
session.commit()
|
|
161
|
+
session.refresh(state)
|
|
162
|
+
|
|
163
|
+
return GenericIntermediateSolutionResponse(
|
|
164
|
+
state_id=state.id,
|
|
165
|
+
reference_solution_1=SolutionReference(
|
|
166
|
+
state=reference_states[0],
|
|
167
|
+
solution_index=request.reference_solution_1.solution_index,
|
|
168
|
+
name=request.reference_solution_1.name,
|
|
169
|
+
),
|
|
170
|
+
reference_solution_2=SolutionReference(
|
|
171
|
+
state=reference_states[1],
|
|
172
|
+
solution_index=request.reference_solution_2.solution_index,
|
|
173
|
+
name=request.reference_solution_2.name,
|
|
174
|
+
),
|
|
175
|
+
intermediate_solutions=[
|
|
176
|
+
SolutionReference(state=state, solution_index=i) for i in range(state.state.num_solutions)
|
|
177
|
+
],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@router.post("/score-bands-obj-data")
|
|
182
|
+
def calculate_score_bands_from_objective_data(
|
|
183
|
+
request: ScoreBandsRequest,
|
|
184
|
+
) -> ScoreBandsResponse:
|
|
185
|
+
"""Calculate SCORE bands parameters from objective data."""
|
|
186
|
+
try:
|
|
187
|
+
# Convert input data to pandas DataFrame
|
|
188
|
+
data = pd.DataFrame(request.data, columns=request.objs)
|
|
189
|
+
|
|
190
|
+
# Calculate correlation matrix and objective order
|
|
191
|
+
corr, obj_order = order_dimensions(data, use_absolute_corr=request.use_absolute_corr)
|
|
192
|
+
|
|
193
|
+
# Calculate axis positions and signs
|
|
194
|
+
ordered_data, axis_dist, axis_signs = calculate_axes_positions(
|
|
195
|
+
data,
|
|
196
|
+
obj_order,
|
|
197
|
+
corr,
|
|
198
|
+
dist_parameter=request.dist_parameter,
|
|
199
|
+
distance_formula=request.distance_formula,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Handle flip_axes parameter - if flip_axes is False, don't use axis signs
|
|
203
|
+
if not request.flip_axes:
|
|
204
|
+
axis_signs = None
|
|
205
|
+
|
|
206
|
+
# Perform clustering
|
|
207
|
+
groups = cluster(
|
|
208
|
+
ordered_data,
|
|
209
|
+
algorithm=request.clustering_algorithm,
|
|
210
|
+
score=request.clustering_score,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Translate minimum group to 1 (as done in the notebook)
|
|
214
|
+
groups = groups - np.min(groups) + 1
|
|
215
|
+
|
|
216
|
+
# Convert numpy arrays to lists for JSON serialization
|
|
217
|
+
# Handle potential None values and ensure proper type conversion
|
|
218
|
+
return ScoreBandsResponse(
|
|
219
|
+
groups=groups.tolist() if hasattr(groups, "tolist") else list(groups),
|
|
220
|
+
axis_dist=(axis_dist.tolist() if hasattr(axis_dist, "tolist") else list(axis_dist)),
|
|
221
|
+
axis_signs=(
|
|
222
|
+
axis_signs.tolist()
|
|
223
|
+
if axis_signs is not None and hasattr(axis_signs, "tolist")
|
|
224
|
+
else (list(axis_signs) if axis_signs is not None else None)
|
|
225
|
+
),
|
|
226
|
+
obj_order=(obj_order.tolist() if hasattr(obj_order, "tolist") else list(obj_order)),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
raise HTTPException(
|
|
231
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
232
|
+
detail=f"Error calculating SCORE bands parameters: {e!r}",
|
|
233
|
+
) from e
|