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,18 @@
|
|
|
1
|
+
"""Models specific to the reference point method."""
|
|
2
|
+
|
|
3
|
+
from sqlmodel import JSON, Column, Field, SQLModel
|
|
4
|
+
|
|
5
|
+
from .preference import ReferencePoint
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RPMSolveRequest(SQLModel):
|
|
9
|
+
"""Model of the request to the reference point method."""
|
|
10
|
+
|
|
11
|
+
problem_id: int
|
|
12
|
+
session_id: int | None = Field(default=None)
|
|
13
|
+
parent_state_id: int | None = Field(default=None)
|
|
14
|
+
|
|
15
|
+
scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
|
|
16
|
+
solver: str | None = Field(default=None)
|
|
17
|
+
solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
|
|
18
|
+
preference: ReferencePoint = Field(Column(JSON))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Defines Session models to manage user sessions."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from sqlmodel import Field, Relationship, SQLModel
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .state import StateDB
|
|
9
|
+
from .user import User
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CreateSessionRequest(SQLModel):
|
|
13
|
+
"""Model of the request to create a new session."""
|
|
14
|
+
|
|
15
|
+
info: str | None = Field(default=None)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GetSessionRequest(SQLModel):
|
|
19
|
+
"""Model of the request to get a specific session."""
|
|
20
|
+
|
|
21
|
+
session_id: int = Field()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InteractiveSessionBase(SQLModel):
|
|
25
|
+
"""The base model for representing interactive sessions."""
|
|
26
|
+
|
|
27
|
+
id: int | None
|
|
28
|
+
user_id: int | None
|
|
29
|
+
|
|
30
|
+
info: str | None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
InteractiveSessionInfo = InteractiveSessionBase
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class InteractiveSessionDB(InteractiveSessionBase, table=True):
|
|
37
|
+
"""Database model to store sessions."""
|
|
38
|
+
|
|
39
|
+
id: int | None = Field(primary_key=True, default=None)
|
|
40
|
+
user_id: int | None = Field(foreign_key="user.id", default=None)
|
|
41
|
+
|
|
42
|
+
info: str | None = Field(default=None)
|
|
43
|
+
|
|
44
|
+
# Back populates
|
|
45
|
+
states: list["StateDB"] = Relationship(
|
|
46
|
+
back_populates="session",
|
|
47
|
+
sa_relationship_kwargs={"cascade": "all, delete-orphan"},
|
|
48
|
+
)
|
|
49
|
+
user: "User" = Relationship(back_populates="sessions")
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""Defines models for representing the state of various interactive methods."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.types import TypeDecorator
|
|
7
|
+
from sqlmodel import (
|
|
8
|
+
JSON,
|
|
9
|
+
Column,
|
|
10
|
+
Field,
|
|
11
|
+
Relationship,
|
|
12
|
+
SQLModel,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from desdeo.emo.options.templates import PreferenceOptions, TemplateOptions
|
|
16
|
+
from desdeo.mcdm import ENautilusResult
|
|
17
|
+
from desdeo.problem import Tensor, VariableType
|
|
18
|
+
from desdeo.tools import SolverResults
|
|
19
|
+
from desdeo.tools.score_bands import SCOREBandsResult
|
|
20
|
+
|
|
21
|
+
from .preference import PreferenceType, ReferencePoint
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .problem import RepresentativeNonDominatedSolutions
|
|
25
|
+
from .state_table import UserSavedSolutionDB
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ResultsType(TypeDecorator):
|
|
29
|
+
"""SQLAlchemy custom type to convert a `SolverResults` and similar to JSON and back."""
|
|
30
|
+
|
|
31
|
+
impl = JSON
|
|
32
|
+
|
|
33
|
+
def process_bind_param(self, value, dialect):
|
|
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"):
|
|
42
|
+
return value.model_dump()
|
|
43
|
+
|
|
44
|
+
msg = f"No JSON serialization set for '{type(value)}'."
|
|
45
|
+
print(msg)
|
|
46
|
+
|
|
47
|
+
return value
|
|
48
|
+
|
|
49
|
+
def process_result_value(self, value, dialect):
|
|
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
|
|
58
|
+
|
|
59
|
+
if value is None:
|
|
60
|
+
return None
|
|
61
|
+
if isinstance(value, list):
|
|
62
|
+
return [model.model_validate(x) for x in value]
|
|
63
|
+
|
|
64
|
+
return model.model_validate(value)
|
|
65
|
+
|
|
66
|
+
|
|
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
|
+
)
|
|
74
|
+
|
|
75
|
+
warnings.warn(msg, category=RuntimeWarning, stacklevel=2)
|
|
76
|
+
|
|
77
|
+
return []
|
|
78
|
+
|
|
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)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NIMBUSSaveState(ResultInterface, SQLModel, table=True):
|
|
147
|
+
"""NIMBUS: save solutions."""
|
|
148
|
+
|
|
149
|
+
id: int | None = Field(default=None, primary_key=True, foreign_key="states.id")
|
|
150
|
+
|
|
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)
|
|
172
|
+
|
|
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)
|
|
180
|
+
scalarization_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
|
|
181
|
+
solver: str | None = None
|
|
182
|
+
solver_options: dict[str, float | str | bool] | None = Field(sa_column=Column(JSON), default=None)
|
|
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
|
+
|
|
375
|
+
# results
|
|
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
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def result_constraint_values(self) -> dict[str, list[float]] | None:
|
|
402
|
+
return self.constraint_values
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def result_extra_func_values(self) -> dict[str, list[float]] | None:
|
|
406
|
+
return self.extra_func_values
|
|
407
|
+
|
|
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
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class IntermediateSolutionState(SQLModel, ResultInterface, table=True):
|
|
417
|
+
"""Generic intermediate solutions requested by other methods."""
|
|
418
|
+
|
|
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",
|
|
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))
|
|
462
|
+
|
|
463
|
+
non_dominated_solutions: "RepresentativeNonDominatedSolutions" = Relationship()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Defines user models."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .archive import UserSavedSolutionDB
|
|
10
|
+
from .preference import PreferenceDB
|
|
11
|
+
from .problem import ProblemDB
|
|
12
|
+
from .session import InteractiveSessionDB
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UserRole(str, Enum):
|
|
16
|
+
"""Possible user roles."""
|
|
17
|
+
|
|
18
|
+
guest = "guest"
|
|
19
|
+
dm = "dm"
|
|
20
|
+
analyst = "analyst"
|
|
21
|
+
admin = "admin"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UserBase(SQLModel):
|
|
25
|
+
"""Base user object."""
|
|
26
|
+
|
|
27
|
+
username: str = Field(index=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class User(UserBase, table=True):
|
|
31
|
+
"""The table model of the user stored in the database."""
|
|
32
|
+
|
|
33
|
+
id: int | None = Field(primary_key=True, default=None)
|
|
34
|
+
password_hash: str = Field()
|
|
35
|
+
role: UserRole = Field()
|
|
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
|
|
38
|
+
active_session_id: int | None = Field(default=None)
|
|
39
|
+
|
|
40
|
+
# Back populates
|
|
41
|
+
archive: list["UserSavedSolutionDB"] = Relationship(back_populates="user")
|
|
42
|
+
preferences: list["PreferenceDB"] = Relationship(back_populates="user")
|
|
43
|
+
problems: list["ProblemDB"] = Relationship(back_populates="user")
|
|
44
|
+
sessions: list["InteractiveSessionDB"] = Relationship(back_populates="user")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class UserPublic(UserBase):
|
|
48
|
+
"""The object to handle public user information."""
|
|
49
|
+
|
|
50
|
+
id: int
|
|
51
|
+
role: UserRole
|
|
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.")
|