desdeo 2.0.0__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/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 +5 -1
- 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/infix_parser.py +2 -2
- desdeo/problem/pyomo_evaluator.py +25 -6
- desdeo/problem/schema.py +69 -48
- 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/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 +48 -35
- 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.0.dist-info}/METADATA +46 -28
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.0.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.0.dist-info/licenses}/LICENSE +0 -0
desdeo/api/models/emo.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Models specific to the nimbus method."""
|
|
2
|
+
|
|
3
|
+
from pydantic import ConfigDict
|
|
4
|
+
from sqlmodel import JSON, Column, Field, SQLModel
|
|
5
|
+
|
|
6
|
+
from desdeo.emo.options.templates import PreferenceOptions, TemplateOptions
|
|
7
|
+
from desdeo.tools.score_bands import SCOREBandsConfig, SCOREBandsResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EMOIterateRequest(SQLModel):
|
|
11
|
+
"""Model of the request to iterate an EMO method."""
|
|
12
|
+
|
|
13
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
14
|
+
|
|
15
|
+
problem_id: int
|
|
16
|
+
"""Database ID of the problem to solve."""
|
|
17
|
+
session_id: int | None = Field(default=None)
|
|
18
|
+
parent_state_id: int | None = Field(default=None)
|
|
19
|
+
"""State ID of the parent state, if any. Should be None if this is the first state in a session."""
|
|
20
|
+
|
|
21
|
+
template_options: list[TemplateOptions] | None = Field(default=None)
|
|
22
|
+
"""Options for the template to use. A list of options can be given if multiple templates are used in parallel."""
|
|
23
|
+
preference_options: PreferenceOptions | None = Field(default=None)
|
|
24
|
+
"""Options for the preference handling."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EMOFetchRequest(SQLModel):
|
|
28
|
+
"""Model of the request to fetch solutions from an EMO method."""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
31
|
+
|
|
32
|
+
problem_id: int
|
|
33
|
+
"""Database ID of the problem to solve."""
|
|
34
|
+
session_id: int | None = Field(default=None)
|
|
35
|
+
parent_state_id: int | None = Field(default=None)
|
|
36
|
+
"""State ID of the parent state, if any. Should be None if this is the first state in a session."""
|
|
37
|
+
|
|
38
|
+
num_solutions: int = Field(default=0)
|
|
39
|
+
"""Number of solutions to fetch. If 0, fetch all solutions."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EMOSaveRequest(SQLModel):
|
|
43
|
+
"""Request model for saving solutions from any method's state."""
|
|
44
|
+
|
|
45
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
46
|
+
|
|
47
|
+
problem_id: int
|
|
48
|
+
"""Database ID of the problem to solve."""
|
|
49
|
+
session_id: int | None = Field(default=None)
|
|
50
|
+
parent_state_id: int | None = Field(default=None)
|
|
51
|
+
"""State ID of the parent state, if any. Should be None if this is the first state in a session."""
|
|
52
|
+
|
|
53
|
+
solution_ids: list[int] = Field()
|
|
54
|
+
"""List of solution IDs to save."""
|
|
55
|
+
solution_names: list[str | None] | None = Field(default=None)
|
|
56
|
+
"""List of names for the solutions to save. If None, no names are given."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EMOScoreRequest(SQLModel):
|
|
60
|
+
"""Request model for getting SCORE bands visualization data from state."""
|
|
61
|
+
|
|
62
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
63
|
+
|
|
64
|
+
problem_id: int
|
|
65
|
+
"""Database ID of the problem to solve."""
|
|
66
|
+
session_id: int | None = Field(default=None)
|
|
67
|
+
parent_state_id: int | None = Field(default=None)
|
|
68
|
+
"""State ID of the parent state, if any."""
|
|
69
|
+
|
|
70
|
+
config: SCOREBandsConfig | None = Field(default=None)
|
|
71
|
+
"""Configuration for the SCORE bands visualization."""
|
|
72
|
+
|
|
73
|
+
solution_ids: list[int] = Field()
|
|
74
|
+
"""List of solution IDs to score."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class EMOIterateResponse(SQLModel):
|
|
78
|
+
"""Model of the response to an EMO iterate request."""
|
|
79
|
+
|
|
80
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
81
|
+
|
|
82
|
+
method_ids: list[str]
|
|
83
|
+
"""IDs of the EMO methods using websockets to get/send updates."""
|
|
84
|
+
client_id: str
|
|
85
|
+
"""Client ID to use when connecting to the websockets."""
|
|
86
|
+
state_id: int
|
|
87
|
+
"""The state ID of the newly created state."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Solution(SQLModel):
|
|
91
|
+
solution_id: int
|
|
92
|
+
"""ID of the solution"""
|
|
93
|
+
objective_values: dict[str, list[float]]
|
|
94
|
+
"""Values of the objectives. Each inner list corresponds to a solution."""
|
|
95
|
+
constraint_values: dict[str, list[float]] | None = None
|
|
96
|
+
"""Values of the constraints. Each inner list corresponds to a solution."""
|
|
97
|
+
variable_values: dict[str, list[float | int | bool]]
|
|
98
|
+
"""Values of the decision variables. Each inner list corresponds to a solution."""
|
|
99
|
+
extra_func_values: dict[str, list[float]] | None = None
|
|
100
|
+
"""Values of the extra functions. Each inner list corresponds to a solution."""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class EMOFetchResponse(SQLModel):
|
|
104
|
+
"""Model of the response to an EMO fetch request."""
|
|
105
|
+
|
|
106
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
107
|
+
|
|
108
|
+
state_id: int
|
|
109
|
+
"""The state ID of the newly created state."""
|
|
110
|
+
objective_values: dict[str, list[float]]
|
|
111
|
+
"""Values of the objectives. Each inner list corresponds to a solution."""
|
|
112
|
+
constraint_values: dict[str, list[float]] | None = None
|
|
113
|
+
"""Values of the constraints. Each inner list corresponds to a solution."""
|
|
114
|
+
variable_values: dict[str, list[float | int | bool]]
|
|
115
|
+
"""Values of the decision variables. Each inner list corresponds to a solution."""
|
|
116
|
+
extra_func_values: dict[str, list[float]] | None = None
|
|
117
|
+
"""Values of the extra functions. Each inner list corresponds to a solution."""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class EMOScoreResponse(SQLModel):
|
|
121
|
+
"""Model of the response to an EMO score request."""
|
|
122
|
+
|
|
123
|
+
model_config = ConfigDict(use_attribute_docstrings=True)
|
|
124
|
+
|
|
125
|
+
state_id: int | None = Field(default=None)
|
|
126
|
+
"""The state ID of the newly created state."""
|
|
127
|
+
|
|
128
|
+
result: SCOREBandsResult
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Models specific to the E-NAUTILUS point method."""
|
|
2
|
+
|
|
3
|
+
from sqlmodel import JSON, Column, Field, SQLModel
|
|
4
|
+
|
|
5
|
+
from desdeo.tools import SolverResults
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ENautilusStepRequest(SQLModel):
|
|
9
|
+
"""Model of the request to the E-NAUTILUS method."""
|
|
10
|
+
|
|
11
|
+
problem_id: int
|
|
12
|
+
session_id: int | None = Field(default=None)
|
|
13
|
+
parent_state_id: int | None = Field(default=None)
|
|
14
|
+
# non_dominated points fetched from problem metadata in endpoints
|
|
15
|
+
representative_solutions_id: int = Field(description="The id of the representative solutions to be used.")
|
|
16
|
+
|
|
17
|
+
current_iteration: int = Field(description="The number of the current iteration.")
|
|
18
|
+
iterations_left: int = Field(description="The number of iterations left.")
|
|
19
|
+
selected_point: dict[str, float] | None = Field(
|
|
20
|
+
sa_column=Column(JSON),
|
|
21
|
+
description=(
|
|
22
|
+
"The selected intermediate point. If first iteration, set this to be the (approximated) nadir point. "
|
|
23
|
+
"If not set, then the point is assumed to be the nadir point of the current approximating set."
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
reachable_point_indices: list[int] = Field(
|
|
27
|
+
description=(
|
|
28
|
+
"The indices indicating the point on the non-dominated set that are "
|
|
29
|
+
"reachable from the currently selected point."
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
number_of_intermediate_points: int = Field(description="The number of intermediate points to be generated.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ENautilusStepResponse(SQLModel):
|
|
36
|
+
"""The response from E-NAUTILUS step endpoint."""
|
|
37
|
+
|
|
38
|
+
state_id: int | None = Field(description="The id of the state created by the request that generated this response")
|
|
39
|
+
|
|
40
|
+
current_iteration: int = Field(description="Number of the current iteration.")
|
|
41
|
+
iterations_left: int = Field(description="Number of iterations left.")
|
|
42
|
+
intermediate_points: list[dict[str, float]] = Field(sa_column=Column(JSON), description="New intermediate points")
|
|
43
|
+
reachable_best_bounds: list[dict[str, float]] = Field(
|
|
44
|
+
sa_column=Column(JSON),
|
|
45
|
+
description="Best bounds of the objective function values reachable from each intermediate point.",
|
|
46
|
+
)
|
|
47
|
+
reachable_worst_bounds: list[dict[str, float]] = Field(
|
|
48
|
+
sa_column=Column(JSON),
|
|
49
|
+
description="Worst bounds of the objective function values reachable from each intermediate point.",
|
|
50
|
+
)
|
|
51
|
+
closeness_measures: list[float] = Field(description="Closeness measures of each intermediate point.")
|
|
52
|
+
reachable_point_indices: list[list[int]] = Field(
|
|
53
|
+
description="Indices of the reachable points from each intermediate point."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ENautilusStateResponse(SQLModel):
|
|
58
|
+
"""The response model when requesting a state in E-NAUTILUS."""
|
|
59
|
+
|
|
60
|
+
request: ENautilusStepRequest = Field(description="The original request for generating the state.")
|
|
61
|
+
response: ENautilusStepResponse = Field(description="The state generated by the request.")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ENautilusRepresentativeSolutionsResponse(SQLModel):
|
|
65
|
+
"""Model of the response when requesting representative solutions from E-NAUTILUS."""
|
|
66
|
+
|
|
67
|
+
solutions: list[SolverResults] = Field(
|
|
68
|
+
description="The solutions on the non-dominated front closest to the intermediate points."
|
|
69
|
+
)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Classes for group decision making, aggregating all different types of data classes."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from sqlalchemy.types import TypeDecorator
|
|
6
|
+
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
|
7
|
+
|
|
8
|
+
from desdeo.api.models.gdm.gdm_base import BaseGroupInfoContainer
|
|
9
|
+
from desdeo.api.models.gdm.gdm_score_bands import GDMSCOREBandFinalSelection, GDMSCOREBandInformation
|
|
10
|
+
from desdeo.api.models.gdm.gnimbus import EndProcessPreference, OptimizationPreference, VotingPreference
|
|
11
|
+
from desdeo.tools import SolverResults
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PreferenceType(TypeDecorator):
|
|
15
|
+
"""A converter of Preference types."""
|
|
16
|
+
|
|
17
|
+
impl = JSON
|
|
18
|
+
|
|
19
|
+
# Serialize
|
|
20
|
+
def process_bind_param(self, value, dialect):
|
|
21
|
+
"""Turns a preference item into json."""
|
|
22
|
+
if isinstance(value, BaseGroupInfoContainer):
|
|
23
|
+
return value.model_dump_json()
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
# Deserialize
|
|
27
|
+
def process_result_value(self, value, dialect):
|
|
28
|
+
"""And the other way around."""
|
|
29
|
+
jsoned = json.loads(value)
|
|
30
|
+
if jsoned is not None:
|
|
31
|
+
match jsoned["method"]:
|
|
32
|
+
case "voting":
|
|
33
|
+
return VotingPreference.model_validate(jsoned)
|
|
34
|
+
case "optimization":
|
|
35
|
+
return OptimizationPreference.model_validate(jsoned)
|
|
36
|
+
# As the different methods are implemented, add new types
|
|
37
|
+
case "end":
|
|
38
|
+
return EndProcessPreference.model_validate(jsoned)
|
|
39
|
+
case "gdm-score-bands":
|
|
40
|
+
return GDMSCOREBandInformation.model_validate(jsoned)
|
|
41
|
+
case "gdm-score-bands-final":
|
|
42
|
+
return GDMSCOREBandFinalSelection.model_validate(jsoned)
|
|
43
|
+
case _:
|
|
44
|
+
print(f"Unable to deserialize Preference with method {jsoned['method']}.")
|
|
45
|
+
return None
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GroupBase(SQLModel):
|
|
50
|
+
"""Base class for group table model and group response model."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Group(GroupBase, table=True):
|
|
54
|
+
"""Table model for Group."""
|
|
55
|
+
|
|
56
|
+
id: int | None = Field(primary_key=True, default=None)
|
|
57
|
+
name: str | None = Field(default=None)
|
|
58
|
+
|
|
59
|
+
owner_id: int | None = Field(foreign_key="user.id", default=None)
|
|
60
|
+
user_ids: list[int] | None = Field(sa_column=Column(JSON))
|
|
61
|
+
|
|
62
|
+
problem_id: int = Field(default=None)
|
|
63
|
+
|
|
64
|
+
"""The id of the head GroupIteration."""
|
|
65
|
+
head_iteration_id: int | None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class GroupPublic(GroupBase):
|
|
69
|
+
"""Response model for Group."""
|
|
70
|
+
|
|
71
|
+
id: int
|
|
72
|
+
name: str
|
|
73
|
+
owner_id: int
|
|
74
|
+
user_ids: list[int]
|
|
75
|
+
problem_id: int
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GroupIteration(SQLModel, table=True):
|
|
79
|
+
"""Table model for Group Iteration (we could extend this in various ways)."""
|
|
80
|
+
|
|
81
|
+
id: int | None = Field(primary_key=True, default=None)
|
|
82
|
+
problem_id: int | None = Field(default=None)
|
|
83
|
+
|
|
84
|
+
"""ID of the associated Group."""
|
|
85
|
+
group_id: int
|
|
86
|
+
|
|
87
|
+
"""The preferences are stored in this item while the iteration is in progress."""
|
|
88
|
+
info_container: BaseGroupInfoContainer = Field(sa_column=Column(PreferenceType))
|
|
89
|
+
# NOTE: This used to be called "preferences" and the class used to be called "BasePreference"
|
|
90
|
+
|
|
91
|
+
notified: dict[int, bool] = Field(sa_column=Column(JSON))
|
|
92
|
+
|
|
93
|
+
"""State for storing post optimization/voting related data (dec vars, objectives, etc.)"""
|
|
94
|
+
state_id: int | None = Field()
|
|
95
|
+
|
|
96
|
+
"""Linked list emerges."""
|
|
97
|
+
parent_id: int | None = Field(foreign_key="groupiteration.id", default=None)
|
|
98
|
+
parent: "GroupIteration" = Relationship(
|
|
99
|
+
back_populates="children", sa_relationship_kwargs={"remote_side": "GroupIteration.id"}
|
|
100
|
+
)
|
|
101
|
+
# If parent is removed, remove the child too
|
|
102
|
+
children: list["GroupIteration"] = Relationship(
|
|
103
|
+
back_populates="parent", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class GroupInfoRequest(SQLModel):
|
|
108
|
+
"""Class for requesting group information."""
|
|
109
|
+
|
|
110
|
+
group_id: int
|
|
111
|
+
|
|
112
|
+
class GroupRevertRequest(SQLModel):
|
|
113
|
+
"""Class for requesting reverting to certain iteration."""
|
|
114
|
+
|
|
115
|
+
group_id: int = Field(description="The ID of the group we wish to revert.")
|
|
116
|
+
state_id: int = Field(
|
|
117
|
+
description="The state's ID to which we want to revert to. "\
|
|
118
|
+
"Corresponds to state_id in GroupIteration."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class GroupResult(SQLModel):
|
|
123
|
+
"""Class for group's result."""
|
|
124
|
+
|
|
125
|
+
solver_results: list[SolverResults]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class GroupModifyRequest(SQLModel):
|
|
129
|
+
"""Used for adding a user into group and removing a user from group."""
|
|
130
|
+
|
|
131
|
+
group_id: int
|
|
132
|
+
user_id: int
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class GroupCreateRequest(SQLModel):
|
|
136
|
+
"""Used for requesting a group to be created."""
|
|
137
|
+
|
|
138
|
+
group_name: str
|
|
139
|
+
problem_id: int
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Base classes for Group Decision Making. Here one can add some common data types for serialization."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
from sqlalchemy.types import TypeDecorator
|
|
7
|
+
from sqlmodel import JSON, SQLModel
|
|
8
|
+
|
|
9
|
+
from desdeo.api.models.preference import ReferencePoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ReferencePointDictType(TypeDecorator):
|
|
13
|
+
"""A converter for dict of int and preferences."""
|
|
14
|
+
|
|
15
|
+
impl = JSON
|
|
16
|
+
|
|
17
|
+
def process_bind_param(self, value, dialect):
|
|
18
|
+
"""Turns a reference point dict into json."""
|
|
19
|
+
if isinstance(value, dict):
|
|
20
|
+
for key, item in value.items():
|
|
21
|
+
if isinstance(item, ReferencePoint):
|
|
22
|
+
value[key] = item.model_dump_json()
|
|
23
|
+
return json.dumps(value)
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def process_result_value(self, value, dialect):
|
|
27
|
+
"""And the other way around."""
|
|
28
|
+
dictionary = json.loads(value)
|
|
29
|
+
for key, item in dictionary.items():
|
|
30
|
+
if item is None:
|
|
31
|
+
print("Something's wrong... Database has a NoneType entry.")
|
|
32
|
+
try:
|
|
33
|
+
dictionary[key] = ReferencePoint.model_validate(json.loads(item))
|
|
34
|
+
except ValidationError as e:
|
|
35
|
+
print(f"Validation error when deserializing PreferencePoint: {e}")
|
|
36
|
+
return dictionary
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BooleanDictTypeDecorator(TypeDecorator):
|
|
40
|
+
"""A converter of bool into json, surprising that this needs to exists."""
|
|
41
|
+
|
|
42
|
+
impl = JSON
|
|
43
|
+
|
|
44
|
+
def process_bind_param(self, value, dialect):
|
|
45
|
+
"""Turns boolean dict to json."""
|
|
46
|
+
if isinstance(value, dict):
|
|
47
|
+
for key, item in value.items():
|
|
48
|
+
if isinstance(item, bool):
|
|
49
|
+
value[key] = json.dumps(item)
|
|
50
|
+
return json.dumps(value)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def process_result_value(self, value, dialect):
|
|
54
|
+
"""And the other way around."""
|
|
55
|
+
dictionary = json.loads(value)
|
|
56
|
+
for key, item in dictionary.items():
|
|
57
|
+
if item is None:
|
|
58
|
+
print("Something's wrong... Database has a NoneType entry.")
|
|
59
|
+
try:
|
|
60
|
+
dictionary[key] = bool(item)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Validation error when deserializing boolean: {e}")
|
|
63
|
+
return dictionary
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class BaseGroupInfoContainer(SQLModel):
|
|
67
|
+
"""A base class for a method specific iteration information."""
|
|
68
|
+
|
|
69
|
+
method: str = "unset"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Models for GDM Score Bands.
|
|
2
|
+
|
|
3
|
+
Idea is that in the very first iteration, the filtered indices contains the clustering
|
|
4
|
+
information on the entire data. Since on each iteration, the clustering is different,
|
|
5
|
+
we need to include the indices over and over again. Of course with time the amount of
|
|
6
|
+
indices will get smaller and smaller, and eventually be only ~10 solutions.
|
|
7
|
+
|
|
8
|
+
The names of the classes can be renamed to fit the purpose better, currently they are
|
|
9
|
+
more or less just the first thing that came to my mind.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from sqlmodel import JSON, Column, Field, SQLModel
|
|
13
|
+
|
|
14
|
+
from desdeo.api.models.gdm.gdm_base import BaseGroupInfoContainer
|
|
15
|
+
from desdeo.gdm.score_bands import SCOREBandsGDMConfig, SCOREBandsGDMResult
|
|
16
|
+
from desdeo.problem.schema import VariableType
|
|
17
|
+
from desdeo.tools.score_bands import SCOREBandsResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GDMSCOREBandInformation(BaseGroupInfoContainer):
|
|
21
|
+
"""Class for containing info on which band was voted for."""
|
|
22
|
+
method: str = "gdm-score-bands"
|
|
23
|
+
user_votes: dict[str, int] = Field(
|
|
24
|
+
description="Dictionary of votes."
|
|
25
|
+
)
|
|
26
|
+
user_confirms: list[int] = Field(
|
|
27
|
+
description="List of users who want to move on."
|
|
28
|
+
)
|
|
29
|
+
score_bands_config: SCOREBandsGDMConfig = Field(
|
|
30
|
+
description="The configuration that led to this classification."
|
|
31
|
+
)
|
|
32
|
+
score_bands_result: SCOREBandsGDMResult = Field(
|
|
33
|
+
description="The results of the score bands."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GDMSCOREBandFinalSelection(BaseGroupInfoContainer):
|
|
38
|
+
"""Class for containing the final 10 or less solutions, the final solution and the votes that led to it."""
|
|
39
|
+
method: str = "gdm-score-bands-final"
|
|
40
|
+
user_votes: dict[str, int] = Field(
|
|
41
|
+
description="Dictionary of votes."
|
|
42
|
+
)
|
|
43
|
+
user_confirms: list[int] = Field(
|
|
44
|
+
description="List of users who want to move on."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
"""The 10 or less solutions to choose from"""
|
|
48
|
+
solution_variables: dict[str, list[VariableType]] = Field(sa_column=Column(JSON))
|
|
49
|
+
solution_objectives: dict[str, list[float]] = Field(sa_column=Column(JSON))
|
|
50
|
+
|
|
51
|
+
"""The selected (or generated??) of those 10 or less."""
|
|
52
|
+
winner_solution_variables: dict[str, VariableType] = Field(sa_column=Column(JSON))
|
|
53
|
+
winner_solution_objectives: dict[str, float] = Field(sa_column=Column(JSON))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GDMScoreBandsInitializationRequest(SQLModel):
|
|
57
|
+
"""Request class for initialization of score bands."""
|
|
58
|
+
group_id: int = Field(
|
|
59
|
+
description="The group to be initialized."
|
|
60
|
+
)
|
|
61
|
+
score_bands_config: SCOREBandsGDMConfig = Field(
|
|
62
|
+
description="The configuration for the initial score banding."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
class GDMScoreBandsVoteRequest(SQLModel):
|
|
66
|
+
"""Request for voting for a band."""
|
|
67
|
+
group_id: int = Field(
|
|
68
|
+
description="ID of the group in question"
|
|
69
|
+
)
|
|
70
|
+
vote: int = Field(
|
|
71
|
+
description="The vote. Vaalisalaisuus."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
class GDMSCOREBandsRevertRequest(SQLModel):
|
|
75
|
+
"""Request for reverting to a previous setup."""
|
|
76
|
+
group_id: int = Field(
|
|
77
|
+
description="Group ID."
|
|
78
|
+
)
|
|
79
|
+
iteration_number: int = Field(
|
|
80
|
+
description="The number of the iteration that we want to revert to."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
class GDMSCOREBandsResponse(SQLModel):
|
|
84
|
+
"""Response class for GDMSCOREBands, whether it is initialization or not."""
|
|
85
|
+
method: str = "gdm-score-bands"
|
|
86
|
+
group_id: int = Field(
|
|
87
|
+
description="The group in question."
|
|
88
|
+
)
|
|
89
|
+
group_iter_id: int = Field(
|
|
90
|
+
description="ID of the latest group iteration."
|
|
91
|
+
)
|
|
92
|
+
latest_iteration: int = Field(
|
|
93
|
+
description="The latest GDM iteration number. Different from Group Iteration id."
|
|
94
|
+
)
|
|
95
|
+
result: SCOREBandsResult = Field(
|
|
96
|
+
description="The results of the score bands procedure."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
class GDMSCOREBandsDecisionResponse(SQLModel):
|
|
100
|
+
"""Response class for gdm score bands that includes the last 10 or less solutions."""
|
|
101
|
+
method: str = "gdm-score-bands-final"
|
|
102
|
+
group_id: int = Field(
|
|
103
|
+
description="The group in question."
|
|
104
|
+
)
|
|
105
|
+
group_iter_id: int = Field(
|
|
106
|
+
description="ID of the latest group iteration."
|
|
107
|
+
)
|
|
108
|
+
result: GDMSCOREBandFinalSelection = Field(
|
|
109
|
+
description="The container for the solutions and the winner solution."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
class GDMSCOREBandsHistoryResponse(SQLModel):
|
|
113
|
+
"""Response class for all history. Allows for going to a previous iteration."""
|
|
114
|
+
history: list[GDMSCOREBandsResponse | GDMSCOREBandsDecisionResponse]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""GNIMBUS specific data classes; REMEMBER to add SER/DES into aggregator!"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
from sqlmodel import JSON, Column, Field, SQLModel, TypeDecorator
|
|
7
|
+
|
|
8
|
+
from desdeo.api.models.gdm.gdm_base import (
|
|
9
|
+
BaseGroupInfoContainer,
|
|
10
|
+
BooleanDictTypeDecorator,
|
|
11
|
+
ReferencePoint,
|
|
12
|
+
ReferencePointDictType,
|
|
13
|
+
)
|
|
14
|
+
from desdeo.api.models.generic_states import SolutionReference, SolutionReferenceLite
|
|
15
|
+
from desdeo.tools import SolverResults
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SolverResultType(TypeDecorator):
|
|
19
|
+
"""Not sure why the solver results wouldn't serialize but this should do the trick."""
|
|
20
|
+
|
|
21
|
+
impl = JSON
|
|
22
|
+
|
|
23
|
+
def process_bind_param(self, value, dialect):
|
|
24
|
+
"""Turns solver results into json."""
|
|
25
|
+
if isinstance(value, list):
|
|
26
|
+
solver_list = []
|
|
27
|
+
for item in value:
|
|
28
|
+
if isinstance(item, SolverResults):
|
|
29
|
+
solver_list.append(item.model_dump_json())
|
|
30
|
+
return json.dumps(solver_list)
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def process_result_value(self, value, dialect):
|
|
34
|
+
"""And back."""
|
|
35
|
+
if value is not None:
|
|
36
|
+
value_list = json.loads(value)
|
|
37
|
+
solver_list: SolverResults = []
|
|
38
|
+
for item in value_list:
|
|
39
|
+
item_dict = json.loads(item)
|
|
40
|
+
try:
|
|
41
|
+
solver_list.append(SolverResults.model_validate(item_dict))
|
|
42
|
+
except ValidationError as e:
|
|
43
|
+
print(f"Validation error when deserializing SolverResults: {e}")
|
|
44
|
+
continue
|
|
45
|
+
return solver_list
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GNIMBUSSwitchPhaseRequest(SQLModel):
|
|
50
|
+
"""A request for a certain phase. Comes from the group owner/analyst."""
|
|
51
|
+
|
|
52
|
+
group_id: int
|
|
53
|
+
new_phase: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GNIMBUSSwitchPhaseResponse(SQLModel):
|
|
57
|
+
"""A response for the above request."""
|
|
58
|
+
|
|
59
|
+
old_phase: str
|
|
60
|
+
new_phase: str
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class OptimizationPreference(BaseGroupInfoContainer):
|
|
64
|
+
"""A structure for storing optimization preferences. See GNIMBUS for details."""
|
|
65
|
+
|
|
66
|
+
method: str = "optimization"
|
|
67
|
+
phase: str = Field(default="learning")
|
|
68
|
+
set_preferences: dict[int, ReferencePoint] = Field(sa_column=Column(ReferencePointDictType))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class VotingPreference(BaseGroupInfoContainer):
|
|
72
|
+
"""A structure for storing voting preferences."""
|
|
73
|
+
|
|
74
|
+
method: str = "voting"
|
|
75
|
+
set_preferences: dict[int, int] = Field(
|
|
76
|
+
sa_column=Column(JSON)
|
|
77
|
+
) # A user votes for an index from the results (or something)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class EndProcessPreference(BaseGroupInfoContainer):
|
|
81
|
+
"""A structure for storing info on whether everyone is happy to end the gnimbus process."""
|
|
82
|
+
|
|
83
|
+
method: str = "end"
|
|
84
|
+
success: bool | None = Field()
|
|
85
|
+
|
|
86
|
+
# We check if everyone agrees to stop.
|
|
87
|
+
set_preferences: dict[int, bool] = Field(sa_column=Column(BooleanDictTypeDecorator))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class GNIMBUSResultResponse(SQLModel):
|
|
91
|
+
"""The response for getting GNIMBUS results. NOTE: OBSOLETE!"""
|
|
92
|
+
|
|
93
|
+
method: str
|
|
94
|
+
phase: str
|
|
95
|
+
preferences: VotingPreference | OptimizationPreference
|
|
96
|
+
common_results: list[SolutionReference]
|
|
97
|
+
user_results: list[SolutionReference]
|
|
98
|
+
personal_result_index: int | None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class FullIteration(SQLModel):
|
|
102
|
+
"""A full iteration item containing results from a complete or incomplete iteration.
|
|
103
|
+
|
|
104
|
+
This is a format to send information to the user interface.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
phase: str = Field(
|
|
108
|
+
description="The phase of the iteration."
|
|
109
|
+
)
|
|
110
|
+
optimization_preferences: OptimizationPreference | None = Field(
|
|
111
|
+
description="The preferences related to the optimization stage of the full iteration."
|
|
112
|
+
)
|
|
113
|
+
voting_preferences: VotingPreference | EndProcessPreference | None = Field(
|
|
114
|
+
description="The preferences related to the voting phase of the iteration. \
|
|
115
|
+
either actual votes or a vote to see whether to just continue."
|
|
116
|
+
)
|
|
117
|
+
starting_result: SolutionReferenceLite | None = Field(
|
|
118
|
+
description="The starting result of the optimization process. Fetched from the previous \
|
|
119
|
+
iteration's final result."
|
|
120
|
+
)
|
|
121
|
+
common_results: list[SolutionReferenceLite] = Field(
|
|
122
|
+
description="The common results (1 to 4) generated by gnimbus."
|
|
123
|
+
)
|
|
124
|
+
user_results: list[SolutionReferenceLite] = Field(
|
|
125
|
+
description="The user specific results generated by gnimbus in phases learning and crp."
|
|
126
|
+
)
|
|
127
|
+
personal_result_index: int | None = Field(
|
|
128
|
+
description="The user result index of requester."
|
|
129
|
+
)
|
|
130
|
+
final_result: SolutionReferenceLite | None = Field(
|
|
131
|
+
description="The final result after voting."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class GNIMBUSAllIterationsResponse(SQLModel):
|
|
136
|
+
"""The response model for getting all found solutions among others."""
|
|
137
|
+
|
|
138
|
+
all_full_iterations: list[FullIteration]
|