desdeo 1.1.3__py3-none-any.whl → 2.0.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/api/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +40 -0
- desdeo/api/config.py +69 -0
- desdeo/api/config.toml +53 -0
- desdeo/api/db.py +25 -0
- desdeo/api/db_init.py +79 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +66 -0
- desdeo/api/models/archive.py +34 -0
- desdeo/api/models/preference.py +90 -0
- desdeo/api/models/problem.py +507 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +46 -0
- desdeo/api/models/state.py +96 -0
- desdeo/api/models/user.py +51 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +762 -0
- desdeo/api/routers/__init__.py +5 -0
- desdeo/api/routers/problem.py +110 -0
- desdeo/api/routers/reference_point_method.py +117 -0
- desdeo/api/routers/session.py +76 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +366 -0
- desdeo/api/schema.py +94 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +59 -0
- desdeo/api/tests/test_models.py +701 -0
- desdeo/api/tests/test_routes.py +216 -0
- desdeo/api/utils/database.py +274 -0
- desdeo/api/utils/logger.py +29 -0
- desdeo/core.py +27 -0
- desdeo/emo/__init__.py +29 -0
- desdeo/emo/hooks/archivers.py +172 -0
- desdeo/emo/methods/EAs.py +418 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +59 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +780 -0
- desdeo/emo/operators/evaluator.py +118 -0
- desdeo/emo/operators/generator.py +356 -0
- desdeo/emo/operators/mutation.py +1053 -0
- desdeo/emo/operators/selection.py +1036 -0
- desdeo/emo/operators/termination.py +178 -0
- desdeo/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -0
- desdeo/mcdm/__init__.py +19 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +655 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +116 -0
- desdeo/problem/__init__.py +79 -0
- desdeo/problem/evaluator.py +561 -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 +468 -0
- desdeo/problem/schema.py +1808 -0
- desdeo/problem/simulator_evaluator.py +298 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +73 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +275 -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/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_problem.py +434 -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/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +271 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +102 -0
- desdeo/tools/generics.py +145 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +11 -0
- desdeo/tools/indicators_unary.py +375 -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 +234 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +133 -0
- desdeo/tools/patterns.py +281 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +464 -0
- desdeo/tools/reference_vectors.py +462 -0
- desdeo/tools/scalarization.py +3138 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +464 -0
- desdeo/tools/utils.py +320 -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.0.0.dist-info/LICENSE +21 -0
- desdeo-2.0.0.dist-info/METADATA +168 -0
- desdeo-2.0.0.dist-info/RECORD +120 -0
- {desdeo-1.1.3.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
- desdeo-1.1.3.dist-info/METADATA +0 -18
- desdeo-1.1.3.dist-info/RECORD +0 -4
desdeo/__init__.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
"desdeo_problem",
|
|
3
|
-
"desdeo_tools",
|
|
4
|
-
"desdeo_mcdm",
|
|
5
|
-
]
|
|
1
|
+
"""Configuration stuff of the DESDEO framework."""
|
|
6
2
|
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
from desdeo.core import _check_executables
|
|
4
|
+
|
|
5
|
+
# List of required executables
|
|
6
|
+
required_executables = ["bonmin", "cbc", "ipopt"]
|
|
7
|
+
|
|
8
|
+
# Check for executables when the library is imported
|
|
9
|
+
_check_executables(required_executables)
|
desdeo/api/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# DESDEO web API
|
|
2
|
+
Experimental. The instructions below assume that the current working directory
|
|
3
|
+
is `desdeo/api` and that the DESDEO framework has been installed successfully
|
|
4
|
+
with the extra dependencies in the `api` group.
|
|
5
|
+
|
|
6
|
+
## API configuration
|
|
7
|
+
|
|
8
|
+
The API configuration is handled by the settings found in `config.toml`. This
|
|
9
|
+
file should be used for reading existing configuration parameters and adding
|
|
10
|
+
new ones. Except for deployment (TODO).
|
|
11
|
+
|
|
12
|
+
## Initializing the database
|
|
13
|
+
|
|
14
|
+
To initialize the database, run the script `db_init.py` with the following command:
|
|
15
|
+
|
|
16
|
+
```shell
|
|
17
|
+
python db_init.py
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This will create an initial databse, which allows testing for, e.g., testing
|
|
21
|
+
the databse. However, the tests themselves (see below), do not depend on this
|
|
22
|
+
database, and handle the database for running the tests on their own.
|
|
23
|
+
|
|
24
|
+
Importantly, this will create an analyst user, which can be used to test out
|
|
25
|
+
the end-points using the FastAPI generated documentation (see below). The
|
|
26
|
+
default name of the analyst user will be 'analyst' with the password 'analyst'.
|
|
27
|
+
|
|
28
|
+
This is mainly for testing purposes, which will create and manage a local
|
|
29
|
+
dblite-databse.
|
|
30
|
+
|
|
31
|
+
## Running the API
|
|
32
|
+
|
|
33
|
+
To run the API, invoke `uvicorn` as followsi:
|
|
34
|
+
|
|
35
|
+
```shell
|
|
36
|
+
uvicorn --app-dir=./ app:app --reload
|
|
37
|
+
```
|
|
38
|
+
See the outputs of the command to figure out where the API is running. This is
|
|
39
|
+
likely something along the lines `http:127.0.0.1:8000`, but may vary.
|
|
40
|
+
|
|
41
|
+
## Exploring the API
|
|
42
|
+
|
|
43
|
+
Once the API is running, its endpoints can be interactively explored by accessing
|
|
44
|
+
`<api_url>/docs`. For example, `http:127.0.0:8000/docs`. The `analyst` user created by
|
|
45
|
+
invoking `init_db.py` can be used to authorize and access the protected endpoints.
|
|
46
|
+
|
|
47
|
+
## Running (unit) tests
|
|
48
|
+
|
|
49
|
+
Pytest can be used to run tests relate to the API with the following command:
|
|
50
|
+
|
|
51
|
+
```shell
|
|
52
|
+
pytest
|
|
53
|
+
```
|
|
54
|
+
Again, it is assumed for the current working directory to be `desdeo/api`.
|
|
55
|
+
Otherwise a lot of tests will be executed.
|
|
56
|
+
|
|
57
|
+
## How the API works
|
|
58
|
+
|
|
59
|
+
The API consists of two important concepts: __models__ and __routers__. At the
|
|
60
|
+
core of the API, is a database.
|
|
61
|
+
|
|
62
|
+
Models utilize SQLite to define databse models. In other words, it describes
|
|
63
|
+
stuff we want to store in the databse. These models are very similar to
|
|
64
|
+
pydantic models, and are almost interchangeable. One major difference is that
|
|
65
|
+
the models are relational. This means that models often relate to each other.
|
|
66
|
+
E.g., an `ArchiveEntryDB` is the child of the model `User`.
|
|
67
|
+
|
|
68
|
+
Routers define HTTP endpoints to access and database and the models within.
|
|
69
|
+
These endpoints often modify the contents of the database, e.g., by solving a
|
|
70
|
+
problem with an interactive method, and then saving the solutions back to the
|
|
71
|
+
database. Endpoints often make use of code found in the core-logic (the
|
|
72
|
+
algorithms containing part of DESDEO), and are, in fact, a way to utilize this
|
|
73
|
+
code through the web (which is the whole point of this API!).
|
desdeo/api/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Exports for desdeo.api."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"AuthDebugConfig",
|
|
5
|
+
"DatabaseDebugConfig",
|
|
6
|
+
"ServerDebugConfig",
|
|
7
|
+
"SettingsConfig",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
from .config import (
|
|
11
|
+
AuthDebugConfig,
|
|
12
|
+
DatabaseDebugConfig,
|
|
13
|
+
ServerDebugConfig,
|
|
14
|
+
SettingsConfig,
|
|
15
|
+
)
|
desdeo/api/app.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""The main FastAPI application for the DESDEO API."""
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
|
|
6
|
+
from desdeo.api.config import AuthDebugConfig, SettingsConfig
|
|
7
|
+
from desdeo.api.routers import (
|
|
8
|
+
problem,
|
|
9
|
+
reference_point_method,
|
|
10
|
+
session,
|
|
11
|
+
user_authentication,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if SettingsConfig.debug:
|
|
15
|
+
# debug and development stuff
|
|
16
|
+
|
|
17
|
+
app = FastAPI(
|
|
18
|
+
title="DESDEO (fast)API",
|
|
19
|
+
version="0.1.0",
|
|
20
|
+
description="A rest API for the DESDEO framework.",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
app.include_router(user_authentication.router)
|
|
24
|
+
app.include_router(problem.router)
|
|
25
|
+
app.include_router(session.router)
|
|
26
|
+
app.include_router(reference_point_method.router)
|
|
27
|
+
|
|
28
|
+
origins = AuthDebugConfig.cors_origins
|
|
29
|
+
|
|
30
|
+
app.add_middleware(
|
|
31
|
+
CORSMiddleware,
|
|
32
|
+
allow_origins=origins,
|
|
33
|
+
allow_credentials=True,
|
|
34
|
+
allow_methods=["*"],
|
|
35
|
+
allow_headers=["*"],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
# deployment stuff
|
|
40
|
+
pass
|
desdeo/api/config.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Defines dataclasses to store configurations loaded from 'config.toml'."""
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
# Load the config data once
|
|
10
|
+
config_path = Path(__file__).resolve().parent / "config.toml"
|
|
11
|
+
with config_path.open("rb") as fp:
|
|
12
|
+
config_data = tomllib.load(fp)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SettingsConfig(BaseModel):
|
|
16
|
+
"""General settings."""
|
|
17
|
+
|
|
18
|
+
debug: ClassVar[bool] = config_data["settings"]["debug"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ServerDebugConfig(BaseModel):
|
|
22
|
+
"""Server setup settings (development)."""
|
|
23
|
+
|
|
24
|
+
test_user_analyst_name: ClassVar[str] = config_data["server-debug"]["test_user_analyst_name"]
|
|
25
|
+
test_user_analyst_password: ClassVar[str] = config_data["server-debug"]["test_user_analyst_password"]
|
|
26
|
+
test_user_dm1_name: ClassVar[str] = config_data["server-debug"]["test_user_dm1_name"]
|
|
27
|
+
test_user_dm1_password: ClassVar[str] = config_data["server-debug"]["test_user_dm1_password"]
|
|
28
|
+
test_user_dm2_name: ClassVar[str] = config_data["server-debug"]["test_user_dm2_name"]
|
|
29
|
+
test_user_dm2_password: ClassVar[str] = config_data["server-debug"]["test_user_dm2_password"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AuthDebugConfig(BaseModel):
|
|
33
|
+
"""Authentication settings (development)."""
|
|
34
|
+
|
|
35
|
+
authjwt_secret_key: ClassVar[str] = config_data["auth-debug"]["authjwt_secret_key"]
|
|
36
|
+
authjwt_algorithm: ClassVar[str] = config_data["auth-debug"]["authjwt_algorithm"]
|
|
37
|
+
authjwt_access_token_expires: ClassVar[int] = config_data["auth-debug"]["authjwt_access_token_expires"]
|
|
38
|
+
authjwt_refresh_token_expires: ClassVar[int] = config_data["auth-debug"]["authjwt_refresh_token_expires"]
|
|
39
|
+
cors_origins: ClassVar[list[str]] = config_data["auth-debug"]["cors_origins"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DatabaseDebugConfig(BaseModel):
|
|
43
|
+
"""Database setting (development)."""
|
|
44
|
+
|
|
45
|
+
db_host: ClassVar[str] = config_data["database-debug"]["db_host"]
|
|
46
|
+
db_port: ClassVar[str] = config_data["database-debug"]["db_port"]
|
|
47
|
+
db_database: ClassVar[str] = config_data["database-debug"]["db_database"]
|
|
48
|
+
db_username: ClassVar[str] = config_data["database-debug"]["db_username"]
|
|
49
|
+
db_password: ClassVar[str] = config_data["database-debug"]["db_password"]
|
|
50
|
+
db_pool_size: ClassVar[int] = config_data["database-debug"]["db_pool_size"]
|
|
51
|
+
db_max_overflow: ClassVar[int] = config_data["database-debug"]["db_max_overflow"]
|
|
52
|
+
db_pool: ClassVar[bool] = config_data["database-debug"]["db_pool"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# class DatabaseDeployConfig(BaseModel):
|
|
56
|
+
# # db_host: str = config_data["database-deploy"]["db_host"]
|
|
57
|
+
# db_port: str = config_data["database-deploy"]["db_port"]
|
|
58
|
+
# db_database: str = config_data["database-deploy"]["db_database"]
|
|
59
|
+
# db_username: str = config_data["database-deploy"]["db_username"]
|
|
60
|
+
# db_password: str = config_data["database-deploy"]["db_password"]
|
|
61
|
+
# db_pool_size: int = config_data["database-deploy"]["db_pool_size"]
|
|
62
|
+
# db_max_overflow: int = config_data["database-deploy"]["db_max_overflow"]
|
|
63
|
+
# db_pool: bool = config_data["database-deploy"]["db_pool"]
|
|
64
|
+
|
|
65
|
+
# class AuthDeployConfig(BaseModel):
|
|
66
|
+
# authjwt_algorithm: str = config_data["auth-deploy"]["authjwt_algorithm"]
|
|
67
|
+
# authjwt_access_token_expires: int = config_data["auth-deploy"]["authjwt_access_token_expires"]
|
|
68
|
+
# authjwt_refresh_token_expires: int = config_data["auth-deploy"]["authjwt_refresh_token_expires"]
|
|
69
|
+
# Note: authjwt_secret_key should be retrieved securely in production
|
desdeo/api/config.toml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[settings]
|
|
2
|
+
debug = true
|
|
3
|
+
|
|
4
|
+
# development configs
|
|
5
|
+
[server-debug]
|
|
6
|
+
test_user_analyst_name = "analyst"
|
|
7
|
+
test_user_analyst_password = "analyst"
|
|
8
|
+
test_user_dm1_name = "dm1"
|
|
9
|
+
test_user_dm1_password = "dm1"
|
|
10
|
+
test_user_dm2_name = "dm2"
|
|
11
|
+
test_user_dm2_password = "dm2"
|
|
12
|
+
|
|
13
|
+
# Auth configuration
|
|
14
|
+
[auth-debug]
|
|
15
|
+
authjwt_secret_key = "36b96a23d24cebdeadce6d98fa53356111e6f3e85b8144d7273dcba230b9eb18"
|
|
16
|
+
authjwt_algorithm = "HS256"
|
|
17
|
+
authjwt_access_token_expires = 15 # in minutes
|
|
18
|
+
authjwt_refresh_token_expires = 30 # in minutes
|
|
19
|
+
cors_origins = [
|
|
20
|
+
"http://localhost",
|
|
21
|
+
"http://localhost:8080",
|
|
22
|
+
"http://localhost:5173",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[auth-deploy]
|
|
26
|
+
# TODO
|
|
27
|
+
# secret key should be read from env DO NOT EXPOSE!!!!
|
|
28
|
+
authjwt_algorithm = "HS256"
|
|
29
|
+
authjwt_access_token_expires = 15 # in minutes
|
|
30
|
+
authjwt_refresh_token_expires = 30 # in minutes
|
|
31
|
+
|
|
32
|
+
# SQLite setup (enabled for local development)
|
|
33
|
+
[database-debug]
|
|
34
|
+
db_host = ""
|
|
35
|
+
db_port = ""
|
|
36
|
+
db_database = "sqlite:///./test.db"
|
|
37
|
+
db_username = ""
|
|
38
|
+
db_password = ""
|
|
39
|
+
db_pool_size = 1
|
|
40
|
+
db_max_overflow = 0
|
|
41
|
+
db_pool = false
|
|
42
|
+
|
|
43
|
+
# Database configuration (deployment)
|
|
44
|
+
[database-deploy]
|
|
45
|
+
# READ FROM ENV!!!
|
|
46
|
+
# db_host = "localhost"
|
|
47
|
+
# db_port = "5432"
|
|
48
|
+
# db_database = "test"
|
|
49
|
+
# db_username = "test"
|
|
50
|
+
# db_password = "testpw"
|
|
51
|
+
# db_pool_size = 20
|
|
52
|
+
# db_max_overflow = 20
|
|
53
|
+
# db_pool = true
|
desdeo/api/db.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Database configuration file for the API."""
|
|
2
|
+
|
|
3
|
+
from sqlmodel import Session, create_engine
|
|
4
|
+
|
|
5
|
+
from desdeo.api.config import DatabaseDebugConfig, SettingsConfig
|
|
6
|
+
|
|
7
|
+
if SettingsConfig.debug:
|
|
8
|
+
# debug and development stuff
|
|
9
|
+
|
|
10
|
+
# SQLite setup
|
|
11
|
+
engine = create_engine(DatabaseDebugConfig.db_database, connect_args={"check_same_thread": False})
|
|
12
|
+
|
|
13
|
+
else:
|
|
14
|
+
# deployment stuff
|
|
15
|
+
|
|
16
|
+
# Postgresql setup
|
|
17
|
+
# check from config.toml
|
|
18
|
+
# SQLALCHEMY_DATABASE_URL = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_session():
|
|
23
|
+
"""Yield the current database session."""
|
|
24
|
+
with Session(engine) as session:
|
|
25
|
+
yield session
|
desdeo/api/db_init.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""This module initializes the database."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from sqlalchemy_utils import database_exists
|
|
6
|
+
from sqlmodel import Session, SQLModel
|
|
7
|
+
|
|
8
|
+
from desdeo.api.config import ServerDebugConfig, SettingsConfig
|
|
9
|
+
from desdeo.api.db import engine
|
|
10
|
+
from desdeo.api.models import ProblemDB, User, UserRole
|
|
11
|
+
from desdeo.api.routers.user_authentication import get_password_hash
|
|
12
|
+
from desdeo.problem.testproblems import river_pollution_problem
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
if SettingsConfig.debug:
|
|
16
|
+
# debug stuff
|
|
17
|
+
|
|
18
|
+
print("Creating database tables.")
|
|
19
|
+
if not database_exists(engine.url):
|
|
20
|
+
SQLModel.metadata.create_all(engine)
|
|
21
|
+
else:
|
|
22
|
+
warnings.warn("Database already exists. Clearing it.", stacklevel=1)
|
|
23
|
+
# Drop all tables
|
|
24
|
+
SQLModel.metadata.drop_all(bind=engine)
|
|
25
|
+
SQLModel.metadata.create_all(engine)
|
|
26
|
+
print("Database tables created.")
|
|
27
|
+
|
|
28
|
+
with Session(engine) as session:
|
|
29
|
+
user_analyst = User(
|
|
30
|
+
username=ServerDebugConfig.test_user_analyst_name,
|
|
31
|
+
password_hash=get_password_hash(ServerDebugConfig.test_user_analyst_password),
|
|
32
|
+
role=UserRole.analyst,
|
|
33
|
+
group="test",
|
|
34
|
+
)
|
|
35
|
+
session.add(user_analyst)
|
|
36
|
+
session.commit()
|
|
37
|
+
session.refresh(user_analyst)
|
|
38
|
+
|
|
39
|
+
problem_db = ProblemDB.from_problem(river_pollution_problem(), user_analyst)
|
|
40
|
+
|
|
41
|
+
session.add(problem_db)
|
|
42
|
+
session.commit()
|
|
43
|
+
session.refresh(problem_db)
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
db.add(user_analyst)
|
|
47
|
+
db.commit()
|
|
48
|
+
db.refresh(user_analyst)
|
|
49
|
+
|
|
50
|
+
# add first test DM user
|
|
51
|
+
user_dm1 = db_models.User(
|
|
52
|
+
username=ServerDebugConfig.test_user_dm1_name,
|
|
53
|
+
password_hash=get_password_hash(ServerDebugConfig.test_user_dm1_password),
|
|
54
|
+
role=UserRole.DM,
|
|
55
|
+
privileges=[],
|
|
56
|
+
user_group="",
|
|
57
|
+
)
|
|
58
|
+
db.add(user_dm1)
|
|
59
|
+
db.commit()
|
|
60
|
+
db.refresh(user_dm1)
|
|
61
|
+
|
|
62
|
+
# add second test DM user
|
|
63
|
+
user_dm2 = db_models.User(
|
|
64
|
+
username=ServerDebugConfig.test_user_dm2_name,
|
|
65
|
+
password_hash=get_password_hash(ServerDebugConfig.test_user_dm2_password),
|
|
66
|
+
role=UserRole.DM,
|
|
67
|
+
privileges=[],
|
|
68
|
+
user_group="",
|
|
69
|
+
)
|
|
70
|
+
db.add(user_dm2)
|
|
71
|
+
db.commit()
|
|
72
|
+
db.refresh(user_dm2)
|
|
73
|
+
|
|
74
|
+
db.close()
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
# deployment stuff
|
|
79
|
+
pass
|
desdeo/api/db_models.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""All models for the API. I put them all in a single file for simplicity."""
|
|
2
|
+
|
|
3
|
+
# TODO: ADD TIMESTAMP COLUMNS TO ALL TABLES
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import Enum, ForeignKey, Integer, String
|
|
6
|
+
from sqlalchemy.dialects.postgresql import ARRAY, FLOAT, JSON, JSONB
|
|
7
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
8
|
+
|
|
9
|
+
from desdeo.api import schema
|
|
10
|
+
from desdeo.api.db import Base
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class User(Base):
|
|
14
|
+
"""A user with a password, stored problems, role, and user group."""
|
|
15
|
+
|
|
16
|
+
__tablename__ = "user"
|
|
17
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
18
|
+
username: Mapped[str] = mapped_column(unique=True, nullable=False)
|
|
19
|
+
password_hash: Mapped[str] = mapped_column(nullable=False)
|
|
20
|
+
role: Mapped[schema.UserRole] = mapped_column(nullable=False)
|
|
21
|
+
user_group: Mapped[str] = mapped_column(nullable=True)
|
|
22
|
+
# privilages: Mapped[list[schema.UserPrivileges]] = mapped_column(ARRAY(Enum(schema.UserPrivileges)), nullable=False)
|
|
23
|
+
privileges: Mapped[list[schema.UserPrivileges]] = mapped_column(JSON, nullable=False)
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
"""Return a string representation of the user (username)."""
|
|
27
|
+
return f"User: ('{self.username}')"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Problem(Base):
|
|
31
|
+
"""A model to store a problem and its associated data."""
|
|
32
|
+
|
|
33
|
+
__tablename__ = "problem"
|
|
34
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
35
|
+
owner = mapped_column(Integer, ForeignKey("user.id"), nullable=True) # Null if problem is public.
|
|
36
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
|
37
|
+
# kind and obj_kind are also in value, but we need them as columns for querying. Maybe?
|
|
38
|
+
kind: Mapped[schema.ProblemKind] = mapped_column(nullable=False)
|
|
39
|
+
obj_kind: Mapped[schema.ObjectiveKind] = mapped_column(nullable=False)
|
|
40
|
+
# role_permission: Mapped[list[schema.UserRole]] = mapped_column(ARRAY(Enum(schema.UserRole)), nullable=True)
|
|
41
|
+
role_permission: Mapped[list[schema.UserRole]] = mapped_column(JSON, nullable=True)
|
|
42
|
+
# Mapped doesn't work with JSON, so we use JSON directly.
|
|
43
|
+
value = mapped_column(JSON, nullable=False) # desdeo.problem.schema.Problem
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UserProblemAccess(Base):
|
|
47
|
+
"""A model to store user's access to problems."""
|
|
48
|
+
|
|
49
|
+
__tablename__ = "user_problem_access"
|
|
50
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
51
|
+
user_id = mapped_column(Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False)
|
|
52
|
+
problem_access: Mapped[int] = mapped_column(Integer, ForeignKey("problem.id"), nullable=False)
|
|
53
|
+
problem = relationship("Problem", foreign_keys=[problem_access], lazy="selectin")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Method(Base):
|
|
57
|
+
"""A model to store a method and its associated data."""
|
|
58
|
+
|
|
59
|
+
__tablename__ = "method"
|
|
60
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
61
|
+
kind: Mapped[schema.Methods] = mapped_column(Enum(schema.Methods), nullable=False)
|
|
62
|
+
# properties: Mapped[list[schema.MethodProperties]] = mapped_column(
|
|
63
|
+
# ARRAY(Enum(schema.MethodProperties)), nullable=False
|
|
64
|
+
# )
|
|
65
|
+
properties: Mapped[list[schema.MethodProperties]] = mapped_column(JSON, nullable=False)
|
|
66
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
|
67
|
+
parameters = mapped_column(JSON, nullable=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Preference(Base):
|
|
71
|
+
"""A model to store user preferences provided by the DM."""
|
|
72
|
+
|
|
73
|
+
__tablename__ = "preference"
|
|
74
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
75
|
+
user = mapped_column(Integer, ForeignKey("user.id"), nullable=False)
|
|
76
|
+
problem = mapped_column(Integer, ForeignKey("problem.id"), nullable=False)
|
|
77
|
+
previous_preference = mapped_column(Integer, ForeignKey("preference.id"), nullable=True)
|
|
78
|
+
method = mapped_column(Integer, ForeignKey("method.id"), nullable=False)
|
|
79
|
+
kind: Mapped[str] # Depends on the method
|
|
80
|
+
value = mapped_column(JSON, nullable=False)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MethodState(Base):
|
|
84
|
+
"""A model to store the state of a method. Contains all the information needed to restore the state of a method."""
|
|
85
|
+
|
|
86
|
+
__tablename__ = "method_state"
|
|
87
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
88
|
+
user = mapped_column(Integer, ForeignKey("user.id"), nullable=False)
|
|
89
|
+
problem = mapped_column(Integer, ForeignKey("problem.id"), nullable=False)
|
|
90
|
+
method = mapped_column(Integer, ForeignKey("method.id"), nullable=False) # Honestly, this can just be a string.
|
|
91
|
+
preference = mapped_column(Integer, ForeignKey("preference.id"), nullable=True)
|
|
92
|
+
value = mapped_column(JSON, nullable=False) # Depends on the method.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Results(Base):
|
|
96
|
+
"""A model to store the results of a method run.
|
|
97
|
+
|
|
98
|
+
The results can be partial or complete, depending on the method. For example, NAUTILUS can return ranges instead of
|
|
99
|
+
solutions. The overlap between the Results and SolutionArchive tables is intentional. Though if you have a better
|
|
100
|
+
idea, feel free to change it.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
__tablename__ = "results"
|
|
104
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
105
|
+
user = mapped_column(Integer, ForeignKey("user.id"), nullable=False)
|
|
106
|
+
problem = mapped_column(Integer, ForeignKey("problem.id"), nullable=False)
|
|
107
|
+
# TODO: The method is temporarily nullable for initial testing. It should be non-nullable.
|
|
108
|
+
method = mapped_column(Integer, ForeignKey("method.id"), nullable=True)
|
|
109
|
+
method_state = mapped_column(Integer, ForeignKey("method_state.id"), nullable=True)
|
|
110
|
+
value = mapped_column(JSON, nullable=False) # Depends on the method
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SolutionArchive(Base):
|
|
114
|
+
"""A model to store a solution archive.
|
|
115
|
+
|
|
116
|
+
The archive can be used to store the results of a method run. Note that each entry must be a single,
|
|
117
|
+
complete solution. This is different from the Results table, which can store partial results.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
__tablename__ = "solution_archive"
|
|
121
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
122
|
+
user = mapped_column(Integer, ForeignKey("user.id"), nullable=False)
|
|
123
|
+
problem = mapped_column(Integer, ForeignKey("problem.id"), nullable=False)
|
|
124
|
+
method = mapped_column(Integer, ForeignKey("method.id"), nullable=False)
|
|
125
|
+
preference = mapped_column(Integer, ForeignKey("preference.id"), nullable=True)
|
|
126
|
+
# decision_variables = mapped_column(ARRAY(FLOAT), nullable=True)
|
|
127
|
+
decision_variables = mapped_column(JSON, nullable=True)
|
|
128
|
+
# objectives = mapped_column(ARRAY(FLOAT), nullable=False)
|
|
129
|
+
objectives = mapped_column(JSON, nullable=False)
|
|
130
|
+
# constraints = mapped_column(ARRAY(FLOAT), nullable=True)
|
|
131
|
+
constraints = mapped_column(JSON, nullable=True)
|
|
132
|
+
# extra_funcs = mapped_column(ARRAY(FLOAT), nullable=True)
|
|
133
|
+
extra_funcs = mapped_column(JSON, nullable=True)
|
|
134
|
+
other_info = mapped_column(
|
|
135
|
+
JSON,
|
|
136
|
+
nullable=True,
|
|
137
|
+
) # Depends on the method. May include things such as scalarization functions value, etc.
|
|
138
|
+
saved: Mapped[bool] = mapped_column(nullable=False)
|
|
139
|
+
current: Mapped[bool] = mapped_column(nullable=False)
|
|
140
|
+
chosen: Mapped[bool] = mapped_column(nullable=False)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Log(Base):
|
|
144
|
+
"""A model to store logs of user actions. I have no idea what to put in this table."""
|
|
145
|
+
|
|
146
|
+
__tablename__ = "log"
|
|
147
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
148
|
+
user = mapped_column(Integer, ForeignKey("user.id"), nullable=False)
|
|
149
|
+
action: Mapped[str] = mapped_column(nullable=False)
|
|
150
|
+
value = mapped_column(JSON, nullable=False)
|
|
151
|
+
timestamp: Mapped[str] = mapped_column(nullable=False)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Utopia(Base):
|
|
155
|
+
"""A model to store user specific information relating to Utopia problems."""
|
|
156
|
+
|
|
157
|
+
__tablename__ = "utopia"
|
|
158
|
+
id: Mapped[int] = mapped_column(primary_key=True, unique=True)
|
|
159
|
+
problem: Mapped[int] = mapped_column(Integer, ForeignKey("problem.id"), nullable=False)
|
|
160
|
+
map_json: Mapped[str] = mapped_column(nullable=False)
|
|
161
|
+
schedule_dict = mapped_column(JSONB, nullable=False)
|
|
162
|
+
years: Mapped[list[str]] = mapped_column(ARRAY(String), nullable=False)
|
|
163
|
+
stand_id_field: Mapped[str] = mapped_column(String, nullable=False)
|
|
164
|
+
stand_descriptor = mapped_column(JSONB, nullable=True)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import json # noqa: D100
|
|
2
|
+
|
|
3
|
+
from desdeo.api.db_init import * # noqa: F403
|
|
4
|
+
from desdeo.utopia_stuff.utopia_problem import utopia_problem
|
|
5
|
+
|
|
6
|
+
with open("C:/MyTemp/code/students_and_passwords.json") as file: # noqa: PTH123
|
|
7
|
+
userdict = json.load(file)
|
|
8
|
+
|
|
9
|
+
db = SessionLocal()
|
|
10
|
+
|
|
11
|
+
holding_num = 1
|
|
12
|
+
for name in userdict:
|
|
13
|
+
print(name)
|
|
14
|
+
user = db_models.User(
|
|
15
|
+
username=name,
|
|
16
|
+
password_hash=get_password_hash(userdict[name]),
|
|
17
|
+
role=UserRole.DM,
|
|
18
|
+
privilages=[],
|
|
19
|
+
user_group="",
|
|
20
|
+
)
|
|
21
|
+
db.add(user)
|
|
22
|
+
|
|
23
|
+
db.commit()
|
|
24
|
+
|
|
25
|
+
# Extra problem ends here
|
|
26
|
+
|
|
27
|
+
db.close()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Model exports."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"ArchiveEntryBase",
|
|
5
|
+
"ArchiveEntryDB",
|
|
6
|
+
"Bounds",
|
|
7
|
+
"ConstantDB",
|
|
8
|
+
"ConstraintDB",
|
|
9
|
+
"CreateSessionRequest",
|
|
10
|
+
"DiscreteRepresentationDB",
|
|
11
|
+
"ExtraFunctionDB",
|
|
12
|
+
"GetSessionRequest",
|
|
13
|
+
"User",
|
|
14
|
+
"UserBase",
|
|
15
|
+
"UserPublic",
|
|
16
|
+
"UserRole",
|
|
17
|
+
"ObjectiveDB",
|
|
18
|
+
"ScalarizationFunctionDB",
|
|
19
|
+
"TensorConstantDB",
|
|
20
|
+
"SimulatorDB",
|
|
21
|
+
"TensorVariableDB",
|
|
22
|
+
"PreferenceBase",
|
|
23
|
+
"PreferenceDB",
|
|
24
|
+
"ProblemGetRequest",
|
|
25
|
+
"ProblemDB",
|
|
26
|
+
"ProblemInfo",
|
|
27
|
+
"ProblemInfoSmall",
|
|
28
|
+
"ReferencePoint",
|
|
29
|
+
"RPMSolveRequest",
|
|
30
|
+
"VariableDB",
|
|
31
|
+
"InteractiveSessionBase",
|
|
32
|
+
"InteractiveSessionDB",
|
|
33
|
+
"InteractiveSessionInfo",
|
|
34
|
+
"RPMBaseState",
|
|
35
|
+
"RPMState",
|
|
36
|
+
"StateDB",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
from .archive import ArchiveEntryBase, ArchiveEntryDB
|
|
40
|
+
from .preference import Bounds, PreferenceBase, PreferenceDB, ReferencePoint
|
|
41
|
+
from .problem import (
|
|
42
|
+
ConstantDB,
|
|
43
|
+
ConstraintDB,
|
|
44
|
+
DiscreteRepresentationDB,
|
|
45
|
+
ExtraFunctionDB,
|
|
46
|
+
ObjectiveDB,
|
|
47
|
+
ProblemDB,
|
|
48
|
+
ProblemGetRequest,
|
|
49
|
+
ProblemInfo,
|
|
50
|
+
ProblemInfoSmall,
|
|
51
|
+
ScalarizationFunctionDB,
|
|
52
|
+
SimulatorDB,
|
|
53
|
+
TensorConstantDB,
|
|
54
|
+
TensorVariableDB,
|
|
55
|
+
VariableDB,
|
|
56
|
+
)
|
|
57
|
+
from .reference_point_method import RPMSolveRequest
|
|
58
|
+
from .session import (
|
|
59
|
+
CreateSessionRequest,
|
|
60
|
+
GetSessionRequest,
|
|
61
|
+
InteractiveSessionBase,
|
|
62
|
+
InteractiveSessionDB,
|
|
63
|
+
InteractiveSessionInfo,
|
|
64
|
+
)
|
|
65
|
+
from .state import RPMBaseState, RPMState, StateDB
|
|
66
|
+
from .user import User, UserBase, UserPublic, UserRole
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Defines models for archiving solutions."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
|
6
|
+
|
|
7
|
+
from .preference import PreferenceDB
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .problem import ProblemDB
|
|
11
|
+
from .user import User
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ArchiveEntryBase(SQLModel):
|
|
15
|
+
"""The base model of an archive entry."""
|
|
16
|
+
|
|
17
|
+
variable_values: dict[str, float | list] = Field(sa_column=Column(JSON, nullable=False))
|
|
18
|
+
objective_values: dict[str, float] = Field(sa_column=Column(JSON, nullable=False))
|
|
19
|
+
constraint_values: dict[str, float] | None = Field(sa_column=Column(JSON), default=None)
|
|
20
|
+
extra_func_values: dict[str, float] | None = Field(sa_column=Column(JSON), default=None)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ArchiveEntryDB(ArchiveEntryBase, table=True):
|
|
24
|
+
"""Database model of an archive entry."""
|
|
25
|
+
|
|
26
|
+
id: int | None = Field(primary_key=True, default=None)
|
|
27
|
+
user_id: int | None = Field(foreign_key="user.id", default=None)
|
|
28
|
+
problem_id: int | None = Field(foreign_key="problemdb.id", default=None)
|
|
29
|
+
preference_id: int | None = Field(foreign_key="preferencedb.id", default=None)
|
|
30
|
+
|
|
31
|
+
# Back populates
|
|
32
|
+
user: "User" = Relationship(back_populates="archive")
|
|
33
|
+
preference: "PreferenceDB" = Relationship(back_populates="solutions")
|
|
34
|
+
problem: "ProblemDB" = Relationship(back_populates="solutions")
|