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.
Files changed (122) hide show
  1. desdeo/__init__.py +8 -8
  2. desdeo/api/README.md +73 -0
  3. desdeo/api/__init__.py +15 -0
  4. desdeo/api/app.py +40 -0
  5. desdeo/api/config.py +69 -0
  6. desdeo/api/config.toml +53 -0
  7. desdeo/api/db.py +25 -0
  8. desdeo/api/db_init.py +79 -0
  9. desdeo/api/db_models.py +164 -0
  10. desdeo/api/malaga_db_init.py +27 -0
  11. desdeo/api/models/__init__.py +66 -0
  12. desdeo/api/models/archive.py +34 -0
  13. desdeo/api/models/preference.py +90 -0
  14. desdeo/api/models/problem.py +507 -0
  15. desdeo/api/models/reference_point_method.py +18 -0
  16. desdeo/api/models/session.py +46 -0
  17. desdeo/api/models/state.py +96 -0
  18. desdeo/api/models/user.py +51 -0
  19. desdeo/api/routers/_NAUTILUS.py +245 -0
  20. desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
  21. desdeo/api/routers/_NIMBUS.py +762 -0
  22. desdeo/api/routers/__init__.py +5 -0
  23. desdeo/api/routers/problem.py +110 -0
  24. desdeo/api/routers/reference_point_method.py +117 -0
  25. desdeo/api/routers/session.py +76 -0
  26. desdeo/api/routers/test.py +16 -0
  27. desdeo/api/routers/user_authentication.py +366 -0
  28. desdeo/api/schema.py +94 -0
  29. desdeo/api/tests/__init__.py +0 -0
  30. desdeo/api/tests/conftest.py +59 -0
  31. desdeo/api/tests/test_models.py +701 -0
  32. desdeo/api/tests/test_routes.py +216 -0
  33. desdeo/api/utils/database.py +274 -0
  34. desdeo/api/utils/logger.py +29 -0
  35. desdeo/core.py +27 -0
  36. desdeo/emo/__init__.py +29 -0
  37. desdeo/emo/hooks/archivers.py +172 -0
  38. desdeo/emo/methods/EAs.py +418 -0
  39. desdeo/emo/methods/__init__.py +0 -0
  40. desdeo/emo/methods/bases.py +59 -0
  41. desdeo/emo/operators/__init__.py +1 -0
  42. desdeo/emo/operators/crossover.py +780 -0
  43. desdeo/emo/operators/evaluator.py +118 -0
  44. desdeo/emo/operators/generator.py +356 -0
  45. desdeo/emo/operators/mutation.py +1053 -0
  46. desdeo/emo/operators/selection.py +1036 -0
  47. desdeo/emo/operators/termination.py +178 -0
  48. desdeo/explanations/__init__.py +6 -0
  49. desdeo/explanations/explainer.py +100 -0
  50. desdeo/explanations/utils.py +90 -0
  51. desdeo/mcdm/__init__.py +19 -0
  52. desdeo/mcdm/nautili.py +345 -0
  53. desdeo/mcdm/nautilus.py +477 -0
  54. desdeo/mcdm/nautilus_navigator.py +655 -0
  55. desdeo/mcdm/nimbus.py +417 -0
  56. desdeo/mcdm/pareto_navigator.py +269 -0
  57. desdeo/mcdm/reference_point_method.py +116 -0
  58. desdeo/problem/__init__.py +79 -0
  59. desdeo/problem/evaluator.py +561 -0
  60. desdeo/problem/gurobipy_evaluator.py +562 -0
  61. desdeo/problem/infix_parser.py +341 -0
  62. desdeo/problem/json_parser.py +944 -0
  63. desdeo/problem/pyomo_evaluator.py +468 -0
  64. desdeo/problem/schema.py +1808 -0
  65. desdeo/problem/simulator_evaluator.py +298 -0
  66. desdeo/problem/sympy_evaluator.py +244 -0
  67. desdeo/problem/testproblems/__init__.py +73 -0
  68. desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
  69. desdeo/problem/testproblems/dtlz2_problem.py +102 -0
  70. desdeo/problem/testproblems/forest_problem.py +275 -0
  71. desdeo/problem/testproblems/knapsack_problem.py +163 -0
  72. desdeo/problem/testproblems/mcwb_problem.py +831 -0
  73. desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
  74. desdeo/problem/testproblems/momip_problem.py +172 -0
  75. desdeo/problem/testproblems/nimbus_problem.py +143 -0
  76. desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
  77. desdeo/problem/testproblems/re_problem.py +492 -0
  78. desdeo/problem/testproblems/river_pollution_problem.py +434 -0
  79. desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
  80. desdeo/problem/testproblems/simple_problem.py +351 -0
  81. desdeo/problem/testproblems/simulator_problem.py +92 -0
  82. desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
  83. desdeo/problem/testproblems/zdt_problem.py +271 -0
  84. desdeo/problem/utils.py +245 -0
  85. desdeo/tools/GenerateReferencePoints.py +181 -0
  86. desdeo/tools/__init__.py +102 -0
  87. desdeo/tools/generics.py +145 -0
  88. desdeo/tools/gurobipy_solver_interfaces.py +258 -0
  89. desdeo/tools/indicators_binary.py +11 -0
  90. desdeo/tools/indicators_unary.py +375 -0
  91. desdeo/tools/interaction_schema.py +38 -0
  92. desdeo/tools/intersection.py +54 -0
  93. desdeo/tools/iterative_pareto_representer.py +99 -0
  94. desdeo/tools/message.py +234 -0
  95. desdeo/tools/ng_solver_interfaces.py +199 -0
  96. desdeo/tools/non_dominated_sorting.py +133 -0
  97. desdeo/tools/patterns.py +281 -0
  98. desdeo/tools/proximal_solver.py +99 -0
  99. desdeo/tools/pyomo_solver_interfaces.py +464 -0
  100. desdeo/tools/reference_vectors.py +462 -0
  101. desdeo/tools/scalarization.py +3138 -0
  102. desdeo/tools/scipy_solver_interfaces.py +454 -0
  103. desdeo/tools/score_bands.py +464 -0
  104. desdeo/tools/utils.py +320 -0
  105. desdeo/utopia_stuff/__init__.py +0 -0
  106. desdeo/utopia_stuff/data/1.json +15 -0
  107. desdeo/utopia_stuff/data/2.json +13 -0
  108. desdeo/utopia_stuff/data/3.json +15 -0
  109. desdeo/utopia_stuff/data/4.json +17 -0
  110. desdeo/utopia_stuff/data/5.json +15 -0
  111. desdeo/utopia_stuff/from_json.py +40 -0
  112. desdeo/utopia_stuff/reinit_user.py +38 -0
  113. desdeo/utopia_stuff/utopia_db_init.py +212 -0
  114. desdeo/utopia_stuff/utopia_problem.py +403 -0
  115. desdeo/utopia_stuff/utopia_problem_old.py +415 -0
  116. desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
  117. desdeo-2.0.0.dist-info/LICENSE +21 -0
  118. desdeo-2.0.0.dist-info/METADATA +168 -0
  119. desdeo-2.0.0.dist-info/RECORD +120 -0
  120. {desdeo-1.1.3.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
  121. desdeo-1.1.3.dist-info/METADATA +0 -18
  122. desdeo-1.1.3.dist-info/RECORD +0 -4
desdeo/__init__.py CHANGED
@@ -1,9 +1,9 @@
1
- __all__ = [
2
- "desdeo_problem",
3
- "desdeo_tools",
4
- "desdeo_mcdm",
5
- ]
1
+ """Configuration stuff of the DESDEO framework."""
6
2
 
7
- import desdeo_mcdm
8
- import desdeo_problem
9
- import desdeo_tools
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
@@ -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")