desdeo 2.0.0__py3-none-any.whl → 2.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- desdeo/adm/ADMAfsar.py +551 -0
- desdeo/adm/ADMChen.py +414 -0
- desdeo/adm/BaseADM.py +119 -0
- desdeo/adm/__init__.py +11 -0
- desdeo/api/__init__.py +6 -6
- desdeo/api/app.py +38 -28
- desdeo/api/config.py +65 -44
- desdeo/api/config.toml +23 -12
- desdeo/api/db.py +10 -8
- desdeo/api/db_init.py +12 -6
- desdeo/api/models/__init__.py +220 -20
- desdeo/api/models/archive.py +16 -27
- desdeo/api/models/emo.py +128 -0
- desdeo/api/models/enautilus.py +69 -0
- desdeo/api/models/gdm/gdm_aggregate.py +139 -0
- desdeo/api/models/gdm/gdm_base.py +69 -0
- desdeo/api/models/gdm/gdm_score_bands.py +114 -0
- desdeo/api/models/gdm/gnimbus.py +138 -0
- desdeo/api/models/generic.py +104 -0
- desdeo/api/models/generic_states.py +401 -0
- desdeo/api/models/nimbus.py +158 -0
- desdeo/api/models/preference.py +44 -6
- desdeo/api/models/problem.py +274 -64
- desdeo/api/models/session.py +4 -1
- desdeo/api/models/state.py +419 -52
- desdeo/api/models/user.py +7 -6
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NIMBUS.py +6 -3
- desdeo/api/routers/emo.py +497 -0
- desdeo/api/routers/enautilus.py +237 -0
- desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
- desdeo/api/routers/gdm/gdm_base.py +420 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
- desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
- desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
- desdeo/api/routers/generic.py +233 -0
- desdeo/api/routers/nimbus.py +705 -0
- desdeo/api/routers/problem.py +201 -4
- desdeo/api/routers/reference_point_method.py +20 -44
- desdeo/api/routers/session.py +50 -26
- desdeo/api/routers/user_authentication.py +180 -26
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +10 -4
- desdeo/api/tests/conftest.py +94 -2
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +550 -72
- desdeo/api/tests/test_routes.py +902 -43
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/database.py +28 -266
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +7 -0
- desdeo/emo/__init__.py +154 -24
- desdeo/emo/hooks/archivers.py +18 -2
- desdeo/emo/methods/EAs.py +128 -5
- desdeo/emo/methods/bases.py +9 -56
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/crossover.py +544 -42
- desdeo/emo/operators/evaluator.py +10 -14
- desdeo/emo/operators/generator.py +127 -24
- desdeo/emo/operators/mutation.py +212 -41
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +956 -214
- desdeo/emo/operators/termination.py +124 -16
- desdeo/emo/options/__init__.py +108 -0
- desdeo/emo/options/algorithms.py +435 -0
- desdeo/emo/options/crossover.py +164 -0
- desdeo/emo/options/generator.py +131 -0
- desdeo/emo/options/mutation.py +260 -0
- desdeo/emo/options/repair.py +61 -0
- desdeo/emo/options/scalar_selection.py +66 -0
- desdeo/emo/options/selection.py +127 -0
- desdeo/emo/options/templates.py +383 -0
- desdeo/emo/options/termination.py +143 -0
- desdeo/gdm/__init__.py +22 -0
- desdeo/gdm/gdmtools.py +45 -0
- desdeo/gdm/score_bands.py +114 -0
- desdeo/gdm/voting_rules.py +50 -0
- desdeo/mcdm/__init__.py +23 -1
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautilus_navigator.py +7 -6
- desdeo/mcdm/reference_point_method.py +70 -0
- desdeo/problem/__init__.py +16 -11
- desdeo/problem/evaluator.py +4 -5
- desdeo/problem/external/__init__.py +18 -0
- desdeo/problem/external/core.py +356 -0
- desdeo/problem/external/pymoo_provider.py +266 -0
- desdeo/problem/external/runtime.py +44 -0
- desdeo/problem/gurobipy_evaluator.py +37 -12
- desdeo/problem/infix_parser.py +1 -16
- desdeo/problem/json_parser.py +7 -11
- desdeo/problem/pyomo_evaluator.py +25 -6
- desdeo/problem/schema.py +73 -55
- desdeo/problem/simulator_evaluator.py +65 -15
- desdeo/problem/testproblems/__init__.py +26 -11
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/forest_problem.py +77 -69
- desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
- desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
- desdeo/problem/testproblems/single_objective.py +289 -0
- desdeo/problem/testproblems/zdt_problem.py +4 -1
- desdeo/problem/utils.py +1 -1
- desdeo/tools/__init__.py +39 -21
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +22 -2
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/indicators_binary.py +107 -1
- desdeo/tools/indicators_unary.py +3 -16
- desdeo/tools/message.py +33 -2
- desdeo/tools/non_dominated_sorting.py +4 -3
- desdeo/tools/patterns.py +9 -7
- desdeo/tools/pyomo_solver_interfaces.py +49 -36
- desdeo/tools/reference_vectors.py +118 -351
- desdeo/tools/scalarization.py +340 -1413
- desdeo/tools/score_bands.py +491 -328
- desdeo/tools/utils.py +117 -49
- desdeo/tools/visualizations.py +67 -0
- desdeo/utopia_stuff/utopia_problem.py +1 -1
- desdeo/utopia_stuff/utopia_problem_old.py +1 -1
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/METADATA +47 -30
- desdeo-2.1.1.dist-info/RECORD +180 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/WHEEL +1 -1
- desdeo-2.0.0.dist-info/RECORD +0 -120
- /desdeo/api/utils/{logger.py → _logger.py} +0 -0
- {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info/licenses}/LICENSE +0 -0
desdeo/api/schema.py
CHANGED
|
@@ -47,8 +47,7 @@ class Methods(str, Enum):
|
|
|
47
47
|
NAUTILUS = "nautilus"
|
|
48
48
|
NAUT_NAVIGATOR = "NAUTILUS navigator"
|
|
49
49
|
NAUTILUSII = "nautilusII"
|
|
50
|
-
|
|
51
|
-
NSGAIII = "NSGAIII"
|
|
50
|
+
EMO = "EMO"
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
class MethodProperties(str, Enum):
|
|
@@ -57,6 +56,9 @@ class MethodProperties(str, Enum):
|
|
|
57
56
|
INTERACTIVE = "interactive"
|
|
58
57
|
REFERENCE_POINT = "reference_point"
|
|
59
58
|
CLASSIFICATION = "classification"
|
|
59
|
+
BOUNDS = "bounds"
|
|
60
|
+
PREFERRED_SOLUTIONS = "preferred_solutions"
|
|
61
|
+
NON_PREFERRED_SOLUTIONS = "non_preferred_solutions"
|
|
60
62
|
# TODO: Add more properties as needed.
|
|
61
63
|
|
|
62
64
|
|
|
@@ -73,8 +75,12 @@ class User(BaseModel):
|
|
|
73
75
|
)
|
|
74
76
|
password_hash: str = Field(description="SHA256 Hash of the user's password.")
|
|
75
77
|
role: UserRole = Field(description="Role of the user.")
|
|
76
|
-
privileges: list[UserPrivileges] = Field(
|
|
77
|
-
|
|
78
|
+
privileges: list[UserPrivileges] = Field(
|
|
79
|
+
description="List of privileges the user has."
|
|
80
|
+
)
|
|
81
|
+
user_group: str = Field(
|
|
82
|
+
description="User group of the user. Used for group decision making."
|
|
83
|
+
)
|
|
78
84
|
# To allows for User to be initialized from database instead of just dicts.
|
|
79
85
|
model_config = ConfigDict(from_attributes=True)
|
|
80
86
|
|
desdeo/api/tests/conftest.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"""General fixtures for API tests are defined here."""
|
|
2
2
|
|
|
3
|
+
import io
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import polars as pl
|
|
3
7
|
import pytest
|
|
4
8
|
from fastapi.testclient import TestClient
|
|
5
9
|
from sqlmodel import Session, SQLModel, create_engine
|
|
@@ -7,9 +11,16 @@ from sqlmodel.pool import StaticPool
|
|
|
7
11
|
|
|
8
12
|
from desdeo.api.app import app
|
|
9
13
|
from desdeo.api.db import get_session
|
|
10
|
-
from desdeo.api.models import
|
|
14
|
+
from desdeo.api.models import (
|
|
15
|
+
ForestProblemMetaData,
|
|
16
|
+
ProblemDB,
|
|
17
|
+
ProblemMetaDataDB,
|
|
18
|
+
RepresentativeNonDominatedSolutions,
|
|
19
|
+
User,
|
|
20
|
+
UserRole,
|
|
21
|
+
)
|
|
11
22
|
from desdeo.api.routers.user_authentication import get_password_hash
|
|
12
|
-
from desdeo.problem.testproblems import dtlz2, river_pollution_problem
|
|
23
|
+
from desdeo.problem.testproblems import dtlz2, river_pollution_problem, dmitry_forest_problem_disc
|
|
13
24
|
|
|
14
25
|
|
|
15
26
|
@pytest.fixture(name="session_and_user", scope="function")
|
|
@@ -40,6 +51,48 @@ def session_fixture():
|
|
|
40
51
|
session.commit()
|
|
41
52
|
session.refresh(problem_db_river)
|
|
42
53
|
|
|
54
|
+
metadata = ProblemMetaDataDB(problem_id=problem_db_river.id)
|
|
55
|
+
session.add(metadata)
|
|
56
|
+
session.commit()
|
|
57
|
+
session.refresh(metadata)
|
|
58
|
+
|
|
59
|
+
data_path = Path(__file__).parent.parent.parent.parent / "datasets" / "river_pollution_non_dom.parquet"
|
|
60
|
+
df = pl.read_parquet(data_path)
|
|
61
|
+
dict_data = df.to_dict(as_series=False)
|
|
62
|
+
river_nondominated_meta = RepresentativeNonDominatedSolutions(
|
|
63
|
+
metadata_id=metadata.id,
|
|
64
|
+
name="Non-dom-solutions",
|
|
65
|
+
description=(
|
|
66
|
+
"Set of non-dominated solutions representing the Pareto optimal "
|
|
67
|
+
"solutions of the river pollution problem."
|
|
68
|
+
),
|
|
69
|
+
solution_data=dict_data,
|
|
70
|
+
ideal={},
|
|
71
|
+
nadir={},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
session.add(river_nondominated_meta)
|
|
75
|
+
session.commit()
|
|
76
|
+
|
|
77
|
+
forest_metadata = ForestProblemMetaData(
|
|
78
|
+
metadata_id=metadata.id,
|
|
79
|
+
map_json="type: string",
|
|
80
|
+
schedule_dict={"type": "dict"},
|
|
81
|
+
years=["type:", "list", "of", "strings"],
|
|
82
|
+
stand_id_field="type: string",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
session.add(forest_metadata)
|
|
86
|
+
session.commit()
|
|
87
|
+
|
|
88
|
+
problem_db_discrete = ProblemDB.from_problem(
|
|
89
|
+
dmitry_forest_problem_disc(),
|
|
90
|
+
user=user_analyst
|
|
91
|
+
)
|
|
92
|
+
session.add(problem_db_discrete)
|
|
93
|
+
session.commit()
|
|
94
|
+
session.refresh(problem_db_discrete)
|
|
95
|
+
|
|
43
96
|
yield {"session": session, "user": user_analyst}
|
|
44
97
|
session.rollback()
|
|
45
98
|
|
|
@@ -57,3 +110,42 @@ def client_fixture(session_and_user):
|
|
|
57
110
|
yield client
|
|
58
111
|
|
|
59
112
|
app.dependency_overrides.clear()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def login(client: TestClient, username="analyst", password="analyst") -> str: # noqa: S107
|
|
116
|
+
"""Login, returns the access token."""
|
|
117
|
+
response_login = client.post(
|
|
118
|
+
"/login",
|
|
119
|
+
data={"username": username, "password": password, "grant_type": "password"},
|
|
120
|
+
headers={"content-type": "application/x-www-form-urlencoded"},
|
|
121
|
+
).json()
|
|
122
|
+
|
|
123
|
+
return response_login["access_token"]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def post_json(client: TestClient, endpoint: str, json: dict, access_token: str):
|
|
127
|
+
"""Makes a post request and returns the response."""
|
|
128
|
+
return client.post(
|
|
129
|
+
endpoint,
|
|
130
|
+
json=json,
|
|
131
|
+
headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def post_file_multipart(
|
|
136
|
+
client: TestClient, endpoint: str, file_bytes: bytes, access_token: str, filename: str = "test.json"
|
|
137
|
+
):
|
|
138
|
+
"""Makes a post request with an uploaded file and returns the response."""
|
|
139
|
+
return client.post(
|
|
140
|
+
endpoint,
|
|
141
|
+
files={"json_file": (filename, io.BytesIO(file_bytes), "application/json")},
|
|
142
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_json(client: TestClient, endpoint: str, access_token: str):
|
|
147
|
+
"""Makes a get request and returns the response."""
|
|
148
|
+
return client.get(
|
|
149
|
+
endpoint,
|
|
150
|
+
headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
|
|
151
|
+
)
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Tests related to E-NAUTILUS models and routes."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from fastapi.testclient import TestClient
|
|
7
|
+
from sqlmodel import Session, select
|
|
8
|
+
|
|
9
|
+
from desdeo.api.models import (
|
|
10
|
+
ENautilusRepresentativeSolutionsResponse,
|
|
11
|
+
ENautilusStateResponse,
|
|
12
|
+
ENautilusStepRequest,
|
|
13
|
+
ENautilusStepResponse,
|
|
14
|
+
ProblemDB,
|
|
15
|
+
ProblemMetaDataDB,
|
|
16
|
+
RepresentativeNonDominatedSolutions,
|
|
17
|
+
user,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .conftest import get_json, login, post_json
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_enautilus_step_request(session_and_user: dict[str, Session | list[user]]):
|
|
24
|
+
"""Tests the E-NAUTILUS step request model."""
|
|
25
|
+
problem_id = 42
|
|
26
|
+
session_id = 69
|
|
27
|
+
parent_state_id = 20
|
|
28
|
+
representative_solutions_id = 10
|
|
29
|
+
|
|
30
|
+
current_iteration = 3
|
|
31
|
+
iterations_left = 4
|
|
32
|
+
selected_point = {"f_1": 2.3, "f_2": 4.3, "f_3": -22222.3}
|
|
33
|
+
reachable_point_indices = [1, 2, 3, 46, 9]
|
|
34
|
+
number_of_intermediate_points = 9001
|
|
35
|
+
|
|
36
|
+
request = ENautilusStepRequest(
|
|
37
|
+
problem_id=problem_id,
|
|
38
|
+
session_id=session_id,
|
|
39
|
+
parent_state_id=parent_state_id,
|
|
40
|
+
representative_solutions_id=representative_solutions_id,
|
|
41
|
+
current_iteration=current_iteration,
|
|
42
|
+
iterations_left=iterations_left,
|
|
43
|
+
selected_point=selected_point,
|
|
44
|
+
reachable_point_indices=reachable_point_indices,
|
|
45
|
+
number_of_intermediate_points=number_of_intermediate_points,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert request.problem_id == problem_id
|
|
49
|
+
assert request.session_id == session_id
|
|
50
|
+
assert request.parent_state_id == parent_state_id
|
|
51
|
+
assert request.current_iteration == current_iteration
|
|
52
|
+
assert request.iterations_left == iterations_left
|
|
53
|
+
assert request.selected_point == selected_point
|
|
54
|
+
assert request.reachable_point_indices == reachable_point_indices
|
|
55
|
+
assert request.number_of_intermediate_points == number_of_intermediate_points
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_enautilus_step_router(client: TestClient, session_and_user: dict[str, Session | list[user]]):
|
|
59
|
+
"""Test the E-NAUTILUS stepping endpoint."""
|
|
60
|
+
session, user = session_and_user["session"], session_and_user["user"]
|
|
61
|
+
access_token = login(client)
|
|
62
|
+
|
|
63
|
+
problem_db = session.exec(select(ProblemDB).where(ProblemDB.name == "The river pollution problem")).first()
|
|
64
|
+
|
|
65
|
+
representative_solutions = session.exec(
|
|
66
|
+
select(RepresentativeNonDominatedSolutions)
|
|
67
|
+
.join(RepresentativeNonDominatedSolutions.metadata_instance)
|
|
68
|
+
.where(ProblemMetaDataDB.problem_id == problem_db.id)
|
|
69
|
+
).first()
|
|
70
|
+
|
|
71
|
+
current_iteration = 0
|
|
72
|
+
total_iterations = 5
|
|
73
|
+
selected_point = None # nadir point
|
|
74
|
+
reachable_point_indices = [] # empty for first iteration
|
|
75
|
+
number_of_intermediate_points = 3
|
|
76
|
+
|
|
77
|
+
request = ENautilusStepRequest(
|
|
78
|
+
problem_id=problem_db.id,
|
|
79
|
+
session_id=None,
|
|
80
|
+
parent_state_id=None,
|
|
81
|
+
representative_solutions_id=representative_solutions.id,
|
|
82
|
+
current_iteration=current_iteration,
|
|
83
|
+
iterations_left=total_iterations,
|
|
84
|
+
selected_point=selected_point,
|
|
85
|
+
reachable_point_indices=reachable_point_indices,
|
|
86
|
+
number_of_intermediate_points=number_of_intermediate_points,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
raw_response = post_json(client, "/method/enautilus/step", request.model_dump(), access_token)
|
|
90
|
+
|
|
91
|
+
assert raw_response.status_code == 200 # OK
|
|
92
|
+
|
|
93
|
+
step_response = ENautilusStepResponse.model_validate(json.loads(raw_response.content))
|
|
94
|
+
|
|
95
|
+
assert step_response.state_id == 1 # there should be no prior StateDB in the test instance of the DB
|
|
96
|
+
assert step_response.current_iteration == current_iteration + 1 # advances by 1
|
|
97
|
+
assert step_response.iterations_left == total_iterations - 1 # decrement by 1
|
|
98
|
+
assert len(step_response.intermediate_points) == number_of_intermediate_points
|
|
99
|
+
assert len(step_response.reachable_best_bounds) == number_of_intermediate_points # one for each point
|
|
100
|
+
assert len(step_response.reachable_worst_bounds) == number_of_intermediate_points # one for each point
|
|
101
|
+
assert len(step_response.closeness_measures) == number_of_intermediate_points # one for each point
|
|
102
|
+
assert len(step_response.reachable_point_indices) == number_of_intermediate_points # one set for each point
|
|
103
|
+
|
|
104
|
+
# second iteration
|
|
105
|
+
previous_step = step_response
|
|
106
|
+
new_number_of_intermediate_points = 2
|
|
107
|
+
|
|
108
|
+
request = ENautilusStepRequest(
|
|
109
|
+
problem_id=problem_db.id,
|
|
110
|
+
session_id=None,
|
|
111
|
+
parent_state_id=previous_step.state_id,
|
|
112
|
+
representative_solutions_id=representative_solutions.id,
|
|
113
|
+
current_iteration=previous_step.current_iteration,
|
|
114
|
+
iterations_left=previous_step.iterations_left,
|
|
115
|
+
selected_point=previous_step.intermediate_points[0],
|
|
116
|
+
reachable_point_indices=previous_step.reachable_point_indices[0],
|
|
117
|
+
number_of_intermediate_points=new_number_of_intermediate_points,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
raw_response = post_json(client, "/method/enautilus/step", request.model_dump(), access_token)
|
|
121
|
+
|
|
122
|
+
assert raw_response.status_code == 200 # OK
|
|
123
|
+
|
|
124
|
+
step_response = ENautilusStepResponse.model_validate(json.loads(raw_response.content))
|
|
125
|
+
|
|
126
|
+
assert step_response.state_id == 2 # there should be one prior StateDB in the test instance of the DB
|
|
127
|
+
assert step_response.current_iteration == previous_step.current_iteration + 1 # advances by 1
|
|
128
|
+
assert step_response.iterations_left == previous_step.iterations_left - 1 # decrement by 1
|
|
129
|
+
assert len(step_response.intermediate_points) == new_number_of_intermediate_points
|
|
130
|
+
assert len(step_response.reachable_best_bounds) == new_number_of_intermediate_points # one for each point
|
|
131
|
+
assert len(step_response.reachable_worst_bounds) == new_number_of_intermediate_points # one for each point
|
|
132
|
+
assert len(step_response.closeness_measures) == new_number_of_intermediate_points # one for each point
|
|
133
|
+
assert len(step_response.reachable_point_indices) == new_number_of_intermediate_points # one set for each point
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_enautilus_get_state(client: TestClient, session_and_user: dict[str, Session | list[user]]):
|
|
137
|
+
"""Test fetching a previous state for the the E-NAUTILUS method."""
|
|
138
|
+
session, _ = session_and_user["session"], session_and_user["user"]
|
|
139
|
+
access_token = login(client)
|
|
140
|
+
|
|
141
|
+
problem_db = session.exec(select(ProblemDB).where(ProblemDB.name == "The river pollution problem")).first()
|
|
142
|
+
|
|
143
|
+
representative_solutions = session.exec(
|
|
144
|
+
select(RepresentativeNonDominatedSolutions)
|
|
145
|
+
.join(RepresentativeNonDominatedSolutions.metadata_instance)
|
|
146
|
+
.where(ProblemMetaDataDB.problem_id == problem_db.id)
|
|
147
|
+
).first()
|
|
148
|
+
|
|
149
|
+
current_iteration = 0
|
|
150
|
+
total_iterations = 5
|
|
151
|
+
selected_point = None # nadir point
|
|
152
|
+
reachable_point_indices = [] # empty for first iteration
|
|
153
|
+
number_of_intermediate_points = 3
|
|
154
|
+
|
|
155
|
+
session_id = None
|
|
156
|
+
parent_state_id = None
|
|
157
|
+
|
|
158
|
+
request = ENautilusStepRequest(
|
|
159
|
+
problem_id=problem_db.id,
|
|
160
|
+
session_id=session_id,
|
|
161
|
+
parent_state_id=parent_state_id,
|
|
162
|
+
representative_solutions_id=representative_solutions.id,
|
|
163
|
+
current_iteration=current_iteration,
|
|
164
|
+
iterations_left=total_iterations,
|
|
165
|
+
selected_point=selected_point,
|
|
166
|
+
reachable_point_indices=reachable_point_indices,
|
|
167
|
+
number_of_intermediate_points=number_of_intermediate_points,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
raw_response = post_json(client, "/method/enautilus/step", request.model_dump(), access_token)
|
|
171
|
+
|
|
172
|
+
assert raw_response.status_code == 200 # OK
|
|
173
|
+
|
|
174
|
+
step1_response = ENautilusStepResponse.model_validate(json.loads(raw_response.content))
|
|
175
|
+
|
|
176
|
+
assert step1_response.state_id == 1 # there should be no prior StateDB in the test instance of the DB
|
|
177
|
+
assert step1_response.current_iteration == current_iteration + 1 # advances by 1
|
|
178
|
+
assert step1_response.iterations_left == total_iterations - 1 # decrement by 1
|
|
179
|
+
assert len(step1_response.intermediate_points) == number_of_intermediate_points
|
|
180
|
+
assert len(step1_response.reachable_best_bounds) == number_of_intermediate_points # one for each point
|
|
181
|
+
assert len(step1_response.reachable_worst_bounds) == number_of_intermediate_points # one for each point
|
|
182
|
+
assert len(step1_response.closeness_measures) == number_of_intermediate_points # one for each point
|
|
183
|
+
assert len(step1_response.reachable_point_indices) == number_of_intermediate_points # one set for each point
|
|
184
|
+
|
|
185
|
+
# second iteration
|
|
186
|
+
previous_step = step1_response
|
|
187
|
+
new_number_of_intermediate_points = 2
|
|
188
|
+
|
|
189
|
+
request = ENautilusStepRequest(
|
|
190
|
+
problem_id=problem_db.id,
|
|
191
|
+
session_id=None,
|
|
192
|
+
parent_state_id=previous_step.state_id,
|
|
193
|
+
representative_solutions_id=representative_solutions.id,
|
|
194
|
+
current_iteration=previous_step.current_iteration,
|
|
195
|
+
iterations_left=previous_step.iterations_left,
|
|
196
|
+
selected_point=previous_step.intermediate_points[0],
|
|
197
|
+
reachable_point_indices=previous_step.reachable_point_indices[0],
|
|
198
|
+
number_of_intermediate_points=new_number_of_intermediate_points,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
raw_response = post_json(client, "/method/enautilus/step", request.model_dump(), access_token)
|
|
202
|
+
|
|
203
|
+
assert raw_response.status_code == 200 # OK
|
|
204
|
+
|
|
205
|
+
step2_response = ENautilusStepResponse.model_validate(json.loads(raw_response.content))
|
|
206
|
+
|
|
207
|
+
assert step2_response.state_id == 2 # there should be one prior StateDB in the test instance of the DB
|
|
208
|
+
assert step2_response.current_iteration == previous_step.current_iteration + 1 # advances by 1
|
|
209
|
+
assert step2_response.iterations_left == previous_step.iterations_left - 1 # decrement by 1
|
|
210
|
+
assert len(step2_response.intermediate_points) == new_number_of_intermediate_points
|
|
211
|
+
assert len(step2_response.reachable_best_bounds) == new_number_of_intermediate_points # one for each point
|
|
212
|
+
assert len(step2_response.reachable_worst_bounds) == new_number_of_intermediate_points # one for each point
|
|
213
|
+
assert len(step2_response.closeness_measures) == new_number_of_intermediate_points # one for each point
|
|
214
|
+
assert len(step2_response.reachable_point_indices) == new_number_of_intermediate_points # one set for each point
|
|
215
|
+
|
|
216
|
+
raw_response = get_json(client, f"/method/enautilus/get_state/{step1_response.state_id}", access_token)
|
|
217
|
+
|
|
218
|
+
assert raw_response.status_code == 200 # OK
|
|
219
|
+
|
|
220
|
+
state_response = ENautilusStateResponse.model_validate(json.loads(raw_response.content))
|
|
221
|
+
|
|
222
|
+
previous_request = state_response.request
|
|
223
|
+
previous_response = state_response.response
|
|
224
|
+
|
|
225
|
+
# response is the result of the state
|
|
226
|
+
assert previous_response.state_id == step1_response.state_id
|
|
227
|
+
assert previous_response.current_iteration == step1_response.current_iteration
|
|
228
|
+
assert previous_response.iterations_left == step1_response.iterations_left
|
|
229
|
+
assert previous_response.intermediate_points == step1_response.intermediate_points
|
|
230
|
+
assert previous_response.reachable_best_bounds == step1_response.reachable_best_bounds
|
|
231
|
+
assert previous_response.reachable_worst_bounds == step1_response.reachable_worst_bounds
|
|
232
|
+
assert previous_response.closeness_measures == step1_response.closeness_measures
|
|
233
|
+
assert previous_response.reachable_point_indices == step1_response.reachable_point_indices
|
|
234
|
+
|
|
235
|
+
nadir_point = {
|
|
236
|
+
f"{obj.symbol}": (-1 if obj.maximize else 1)
|
|
237
|
+
* np.max(representative_solutions.solution_data[f"{obj.symbol}_min"])
|
|
238
|
+
for obj in problem_db.objectives
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
reachable_point_indices_ = list(range(len(representative_solutions.solution_data[problem_db.objectives[0].symbol])))
|
|
242
|
+
|
|
243
|
+
# request is the origin of the state
|
|
244
|
+
assert previous_request.problem_id == problem_db.id
|
|
245
|
+
assert previous_request.session_id == session_id
|
|
246
|
+
assert previous_request.parent_state_id == parent_state_id
|
|
247
|
+
assert previous_request.representative_solutions_id == representative_solutions.id
|
|
248
|
+
assert previous_request.current_iteration == current_iteration
|
|
249
|
+
assert previous_request.iterations_left == total_iterations
|
|
250
|
+
assert previous_request.selected_point == nadir_point # should be the nadir, because first iteration
|
|
251
|
+
assert previous_request.reachable_point_indices == reachable_point_indices_ # all in the first iteration
|
|
252
|
+
assert previous_request.number_of_intermediate_points == number_of_intermediate_points
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_enautilus_get_representative(client: TestClient, session_and_user: dict[str, Session | list[user]]):
|
|
256
|
+
"""Test the E-NAUTILUS endpoint for getting representative solutions."""
|
|
257
|
+
session, _ = session_and_user["session"], session_and_user["user"]
|
|
258
|
+
access_token = login(client)
|
|
259
|
+
|
|
260
|
+
problem_db = session.exec(select(ProblemDB).where(ProblemDB.name == "The river pollution problem")).first()
|
|
261
|
+
|
|
262
|
+
representative_solutions = session.exec(
|
|
263
|
+
select(RepresentativeNonDominatedSolutions)
|
|
264
|
+
.join(RepresentativeNonDominatedSolutions.metadata_instance)
|
|
265
|
+
.where(ProblemMetaDataDB.problem_id == problem_db.id)
|
|
266
|
+
).first()
|
|
267
|
+
|
|
268
|
+
current_iteration = 0
|
|
269
|
+
total_iterations = 5
|
|
270
|
+
selected_point = None # nadir point
|
|
271
|
+
reachable_point_indices = [] # empty for first iteration
|
|
272
|
+
number_of_intermediate_points = 3
|
|
273
|
+
|
|
274
|
+
request = ENautilusStepRequest(
|
|
275
|
+
problem_id=problem_db.id,
|
|
276
|
+
session_id=None,
|
|
277
|
+
parent_state_id=None,
|
|
278
|
+
representative_solutions_id=representative_solutions.id,
|
|
279
|
+
current_iteration=current_iteration,
|
|
280
|
+
iterations_left=total_iterations,
|
|
281
|
+
selected_point=selected_point,
|
|
282
|
+
reachable_point_indices=reachable_point_indices,
|
|
283
|
+
number_of_intermediate_points=number_of_intermediate_points,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
raw_response = post_json(client, "/method/enautilus/step", request.model_dump(), access_token)
|
|
287
|
+
|
|
288
|
+
assert raw_response.status_code == 200 # OK
|
|
289
|
+
|
|
290
|
+
step_response = ENautilusStepResponse.model_validate(json.loads(raw_response.content))
|
|
291
|
+
|
|
292
|
+
# iterate until last
|
|
293
|
+
while step_response.iterations_left > 0:
|
|
294
|
+
previous_step = step_response
|
|
295
|
+
|
|
296
|
+
request = ENautilusStepRequest(
|
|
297
|
+
problem_id=problem_db.id,
|
|
298
|
+
session_id=None,
|
|
299
|
+
parent_state_id=previous_step.state_id,
|
|
300
|
+
representative_solutions_id=representative_solutions.id,
|
|
301
|
+
current_iteration=previous_step.current_iteration,
|
|
302
|
+
iterations_left=previous_step.iterations_left,
|
|
303
|
+
selected_point=previous_step.intermediate_points[0],
|
|
304
|
+
reachable_point_indices=previous_step.reachable_point_indices[0],
|
|
305
|
+
number_of_intermediate_points=number_of_intermediate_points,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
raw_response = post_json(client, "/method/enautilus/step", request.model_dump(), access_token)
|
|
309
|
+
|
|
310
|
+
assert raw_response.status_code == 200 # OK
|
|
311
|
+
|
|
312
|
+
step_response = ENautilusStepResponse.model_validate(json.loads(raw_response.content))
|
|
313
|
+
|
|
314
|
+
# no iterations left, last iteration
|
|
315
|
+
raw_response = get_json(client, "/method/enautilus/get_representative/3", access_token)
|
|
316
|
+
|
|
317
|
+
assert raw_response.status_code == 200
|
|
318
|
+
|
|
319
|
+
int_response = ENautilusRepresentativeSolutionsResponse.model_validate(json.loads(raw_response.content))
|
|
320
|
+
|
|
321
|
+
assert len(int_response.solutions) == number_of_intermediate_points
|
|
322
|
+
|
|
323
|
+
# try also a previous iteration
|
|
324
|
+
raw_response = get_json(client, "/method/enautilus/get_representative/2", access_token)
|
|
325
|
+
|
|
326
|
+
assert raw_response.status_code == 200
|
|
327
|
+
|
|
328
|
+
int_response = ENautilusRepresentativeSolutionsResponse.model_validate(json.loads(raw_response.content))
|
|
329
|
+
|
|
330
|
+
assert len(int_response.solutions) == number_of_intermediate_points
|