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