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
@@ -0,0 +1,216 @@
1
+ """Tests related to routes and routers."""
2
+
3
+ from fastapi import status
4
+ from fastapi.testclient import TestClient
5
+
6
+ from desdeo.api.models import (
7
+ CreateSessionRequest,
8
+ GetSessionRequest,
9
+ InteractiveSessionDB,
10
+ ProblemGetRequest,
11
+ ProblemInfo,
12
+ ReferencePoint,
13
+ RPMSolveRequest,
14
+ User,
15
+ )
16
+ from desdeo.api.routers.user_authentication import create_access_token
17
+ from desdeo.problem.testproblems import simple_knapsack_vectors
18
+
19
+
20
+ def login(client: TestClient, username="analyst", password="analyst") -> str: # noqa: S107
21
+ """Login, returns the access token."""
22
+ response_login = client.post(
23
+ "/login",
24
+ data={"username": username, "password": password, "grant_type": "password"},
25
+ headers={"content-type": "application/x-www-form-urlencoded"},
26
+ ).json()
27
+
28
+ return response_login["access_token"]
29
+
30
+
31
+ def post_json(client: TestClient, endpoint: str, json: dict, access_token: str):
32
+ """Makes a post request and returns the response."""
33
+ return client.post(
34
+ endpoint,
35
+ json=json,
36
+ headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
37
+ )
38
+
39
+
40
+ def get_json(client: TestClient, endpoint: str, access_token: str):
41
+ """Makes a get request and returns the response."""
42
+ return client.get(
43
+ endpoint,
44
+ headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
45
+ )
46
+
47
+
48
+ def test_user_login(client: TestClient):
49
+ """Test that login works."""
50
+ response = client.post(
51
+ "/login",
52
+ data={"username": "analyst", "password": "analyst", "grant_type": "password"},
53
+ headers={"content-type": "application/x-www-form-urlencoded"},
54
+ )
55
+
56
+ assert response.status_code == 200
57
+ assert "access_token" in response.json()
58
+
59
+ # wrong login
60
+ response = client.post(
61
+ "/login",
62
+ data={"username": "analyst", "password": "anallyst", "grant_type": "password"},
63
+ headers={"content-type": "application/x-www-form-urlencoded"},
64
+ )
65
+
66
+ assert response.status_code == 401
67
+
68
+
69
+ def test_tokens():
70
+ """Test token generation."""
71
+ token_1 = create_access_token({"id": 1, "sub": "analyst"})
72
+ token_2 = create_access_token({"id": 1, "sub": "analyst"})
73
+
74
+ assert token_1 != token_2
75
+
76
+
77
+ def test_refresh(client: TestClient):
78
+ """Test that refreshing the access token works."""
79
+ # check that no previous cookies exist
80
+ assert len(client.cookies) == 0
81
+
82
+ # no cookie
83
+ response_bad = client.post("/refresh")
84
+
85
+ response_good = client.post(
86
+ "/login",
87
+ data={"username": "analyst", "password": "analyst", "grant_type": "password"},
88
+ headers={"content-type": "application/x-www-form-urlencoded"},
89
+ )
90
+
91
+ assert response_bad.status_code == 401
92
+ assert response_good.status_code == 200
93
+
94
+ assert "access_token" in response_good.json()
95
+ assert len(client.cookies) == 1
96
+ assert "refresh_token" in client.cookies
97
+
98
+ response_refresh = client.post("/refresh")
99
+
100
+ assert "access_token" in response_refresh.json()
101
+
102
+ assert response_good.json()["access_token"] != response_refresh.json()["access_token"]
103
+
104
+
105
+ def test_get_problem(client: TestClient):
106
+ """Test fetching specific problems based on their id."""
107
+ access_token = login(client)
108
+
109
+ response = post_json(client, "/problem/get", ProblemGetRequest(problem_id=1).model_dump(), access_token)
110
+
111
+ assert response.status_code == 200
112
+
113
+ info = ProblemInfo.model_validate(response.json())
114
+
115
+ assert info.id == 1
116
+ assert info.name == "dtlz2"
117
+
118
+ response = post_json(client, "problem/get", ProblemGetRequest(problem_id=2).model_dump(), access_token)
119
+
120
+ assert response.status_code == 200
121
+
122
+ info = ProblemInfo.model_validate(response.json())
123
+
124
+ assert info.id == 2
125
+ assert info.name == "The river pollution problem"
126
+
127
+
128
+ def test_add_problem(client: TestClient):
129
+ """Test that adding a problem to the database works."""
130
+ access_token = login(client)
131
+
132
+ problem = simple_knapsack_vectors()
133
+
134
+ response = post_json(client, "/problem/add", problem.model_dump(), access_token)
135
+
136
+ assert response.status_code == status.HTTP_200_OK
137
+
138
+ problem_info: ProblemInfo = ProblemInfo.model_validate(response.json())
139
+
140
+ assert problem_info.name == "Simple two-objective Knapsack problem"
141
+
142
+ response = get_json(client, "problem/all_info", access_token)
143
+
144
+ assert response.status_code == status.HTTP_200_OK
145
+
146
+ problems = response.json()
147
+
148
+ assert len(problems) == 3
149
+
150
+
151
+ def test_new_session(client: TestClient, session_and_user: dict):
152
+ """Test that creating a new session works as expected."""
153
+ user: User = session_and_user["user"]
154
+ session = session_and_user["session"]
155
+
156
+ assert user.active_session_id is None
157
+
158
+ access_token = login(client)
159
+
160
+ request = CreateSessionRequest(info="My session")
161
+
162
+ response = post_json(client, "/session/new", request.model_dump(), access_token)
163
+
164
+ assert response.status_code == status.HTTP_200_OK
165
+
166
+ assert user.active_session_id == 1
167
+ isession = session.get(InteractiveSessionDB, 1)
168
+
169
+ assert isession.info == "My session"
170
+
171
+
172
+ def test_get_session(client: TestClient, session_and_user: dict):
173
+ """Test that getting a session works as intended."""
174
+ user: User = session_and_user["user"]
175
+
176
+ access_token = login(client)
177
+
178
+ # no sessions
179
+ request = GetSessionRequest(session_id=1)
180
+ response = post_json(client, "/session/get", request.model_dump(), access_token)
181
+ assert response.status_code == status.HTTP_404_NOT_FOUND
182
+
183
+ # add some sessions
184
+ request = CreateSessionRequest(info="My session")
185
+ response = post_json(client, "/session/new", request.model_dump(), access_token)
186
+ assert response.status_code == status.HTTP_200_OK
187
+
188
+ assert user.active_session_id == 1
189
+
190
+ request = CreateSessionRequest(info="My session")
191
+ response = post_json(client, "/session/new", request.model_dump(), access_token)
192
+ assert response.status_code == status.HTTP_200_OK
193
+
194
+ assert user.active_session_id == 2
195
+
196
+ # sessions with id 1 and 2 should exist
197
+ request = GetSessionRequest(session_id=1)
198
+ response = post_json(client, "/session/get", request.model_dump(), access_token)
199
+ assert response.status_code == status.HTTP_200_OK
200
+
201
+ request = GetSessionRequest(session_id=2)
202
+ response = post_json(client, "/session/get", request.model_dump(), access_token)
203
+ assert response.status_code == status.HTTP_200_OK
204
+
205
+
206
+ def test_rpm_solve(client: TestClient):
207
+ """Test that using the reference point method works as expected."""
208
+ access_token = login(client)
209
+
210
+ request = RPMSolveRequest(
211
+ problem_id=1, preference=ReferencePoint(aspiration_levels={"f_1": 0.5, "f_2": 0.3, "f_3": 0.4})
212
+ )
213
+
214
+ response = post_json(client, "/method/rpm/solve", request.model_dump(), access_token)
215
+
216
+ assert response.status_code == status.HTTP_200_OK
@@ -0,0 +1,274 @@
1
+ """Database utils for the API."""
2
+
3
+ from os import getenv
4
+ from typing import TypeVar
5
+
6
+ from sqlalchemy.engine import URL, Result
7
+ from sqlalchemy.ext.asyncio import (
8
+ AsyncEngine,
9
+ AsyncScalarResult,
10
+ AsyncSession,
11
+ create_async_engine,
12
+ )
13
+ from sqlalchemy.future import select as sa_select
14
+ from sqlalchemy.orm import DeclarativeMeta, declarative_base, sessionmaker
15
+ from sqlalchemy.pool import NullPool
16
+ from sqlalchemy.sql import Executable
17
+ from sqlalchemy.sql.expression import Delete
18
+ from sqlalchemy.sql.expression import delete as sa_delete
19
+ from sqlalchemy.sql.expression import exists as sa_exists
20
+ from sqlalchemy.sql.functions import count
21
+ from sqlalchemy.sql.selectable import Exists, Select
22
+
23
+ from desdeo.api.config import SettingsConfig
24
+
25
+ from .logger import get_logger
26
+
27
+ if SettingsConfig.debug:
28
+ from desdeo.api.config import DatabaseDebugConfig
29
+
30
+ DBConfig = DatabaseDebugConfig
31
+ else:
32
+ # set development setting, e.g., DBConfig = DatabaseDeployConfig
33
+ pass
34
+
35
+ T = TypeVar("T")
36
+
37
+
38
+ def select(entity) -> Select:
39
+ """Shortcut for :meth:`sqlalchemy.future.select`.
40
+
41
+ Args:
42
+ entity (sqlalchemy.sql.expression.FromClause): Entities / DB model to SELECT from.
43
+
44
+ Returns:
45
+ Select: A Select class object
46
+ """
47
+ return sa_select(entity)
48
+
49
+
50
+ def filter_by(cls, *args, **kwargs) -> Select:
51
+ """Shortcut for :meth:`sqlalchemy.future.Select.filter_by`.
52
+
53
+ Args:
54
+ cls (sqlalchemy.sql.expression.FromClause): Entities / DB model to SELECT from.
55
+ *args (tuple): Positional arguments.
56
+ **kwargs (dict): Keyword arguments.
57
+
58
+ Returns:
59
+ Select: A Select class object with a WHERE clause
60
+ """
61
+ return select(cls, *args).filter_by(**kwargs)
62
+
63
+
64
+ def exists(*entities, **kwargs) -> Exists:
65
+ """Shortcut for :meth:`sqlalchemy.sql.expression.exists`.
66
+
67
+ Args:
68
+ *entities (tuple): Positional arguments.
69
+ **kwargs (dict): Keyword arguments.
70
+
71
+ Returns:
72
+ Exists: A new Exists construct
73
+ """
74
+ return sa_exists(*entities, **kwargs)
75
+
76
+
77
+ def delete(table) -> Delete:
78
+ """Shortcut for :meth:`sqlalchemy.sql.expression.delete`.
79
+
80
+ Args:
81
+ table (sqlalchemy.sql.expression.FromClause): Entities / DB model.
82
+
83
+ Returns:
84
+ Delete: A Delete object
85
+ """
86
+ return sa_delete(table)
87
+
88
+
89
+ class DB:
90
+ """An async SQLAlchemy ORM wrapper."""
91
+
92
+ Base: DeclarativeMeta
93
+ _engine: AsyncEngine
94
+ _session: AsyncSession
95
+
96
+ def __init__(self, driver: str, options: dict | None = None, **kwargs):
97
+ """Init the class.
98
+
99
+ Args:
100
+ driver (str): Drivername for db url.
101
+ options (dict, optional): Options for AsyncEngine instance.
102
+ **kwargs (dict): Keyword arguments.
103
+ """
104
+ if options is None:
105
+ options = {"pool_size": 20, "max_overflow": 20}
106
+
107
+ url: str = URL.create(drivername=driver, **kwargs)
108
+ self._engine = create_async_engine(url, echo=True, pool_pre_ping=True, pool_recycle=300, **options)
109
+ self.Base = declarative_base()
110
+ self._session: AsyncSession = sessionmaker(self._engine, expire_on_commit=False, class_=AsyncSession)()
111
+
112
+ async def create_tables(self):
113
+ """Creates all Model Tables."""
114
+ async with self._engine.begin() as conn:
115
+ await conn.run_sync(self.Base.metadata.create_all)
116
+
117
+ async def add(self, obj: T) -> T:
118
+ """Adds an Row to the Database.
119
+
120
+ Args:
121
+ obj (T): Object to add.
122
+
123
+ Returns:
124
+ T: Added object.
125
+ """
126
+ self._session.add(obj)
127
+ return obj
128
+
129
+ async def delete(self, obj: T) -> T:
130
+ """Deletes a Row from the Database.
131
+
132
+ Args:
133
+ obj (T): Object to delete.
134
+
135
+ Returns:
136
+ T: Deleted object.
137
+ """
138
+ await self._session.delete(obj)
139
+ return obj
140
+
141
+ async def exec(self, statement: Executable, *args, **kwargs) -> Result:
142
+ """Executes a SQL Statement.
143
+
144
+ Args:
145
+ statement (Executable): SQL statement.
146
+ *args (tuple): Positional arguments.
147
+ **kwargs (dict): Keyword arguments.
148
+
149
+ Returns:
150
+ Result: A buffered Result object.
151
+ """
152
+ return await self._session.execute(statement, *args, **kwargs)
153
+
154
+ async def stream(self, statement: Executable, *args, **kwargs) -> AsyncScalarResult:
155
+ """Returns an Stream of Query Results.
156
+
157
+ Args:
158
+ statement (Executable): SQL statement.
159
+ *args (tuple): Positional arguments.
160
+ **kwargs (dict): Keyword arguments.
161
+
162
+ Returns:
163
+ AsyncScalarResult: An AsyncScalarResult filtering object.
164
+ """
165
+ return (await self._session.stream(statement, *args, **kwargs)).scalars()
166
+
167
+ async def all(self, statement: Executable, *args, **kwargs) -> list[T]:
168
+ """Returns all matches for a Query.
169
+
170
+ Args:
171
+ statement (Executable): SQL statement.
172
+ *args (tuple): Positional arguments.
173
+ **kwargs (dict): Keyword arguments.
174
+
175
+ Returns:
176
+ list[T]: List of rows.
177
+ """
178
+ return [x async for x in await self.stream(statement, *args, **kwargs)]
179
+
180
+ async def first(self, *args, **kwargs) -> dict | None:
181
+ """Returns first match for a Query.
182
+
183
+ Args:
184
+ *args (tuple): Positional arguments.
185
+ **kwargs (dict): Keyword arguments.
186
+
187
+ Returns:
188
+ dict | None: First match for the Query, or None if there is no match.
189
+ """
190
+ return (await self.exec(*args, **kwargs)).scalar()
191
+
192
+ async def exists(self, *args, **kwargs) -> bool:
193
+ """Checks if there is a match for this Query.
194
+
195
+ Args:
196
+ *args (tuple): Positional arguments.
197
+ **kwargs (dict): Keyword arguments.
198
+
199
+ Returns:
200
+ bool: Whether there is a match for this Query
201
+ """
202
+ return await self.first(exists(*args, **kwargs).select())
203
+
204
+ async def count(self, *args, **kwargs) -> int:
205
+ """Counts matches for a Query.
206
+
207
+ Args:
208
+ *args (tuple): Positional arguments.
209
+ **kwargs (dict): Keyword arguments.
210
+
211
+ Returns:
212
+ int: Number of matches for a Query
213
+ """
214
+ return await self.first(select(count()).select_from(*args, **kwargs))
215
+
216
+ async def commit(self):
217
+ """Commits/Saves changes to Database."""
218
+ await self._session.commit()
219
+
220
+
221
+ logger = get_logger(__name__)
222
+
223
+
224
+ class DatabaseDependency:
225
+ """A class to represent a database dependency."""
226
+
227
+ db: DB
228
+ database_url_options: dict
229
+ engine_options: dict
230
+ initialized: bool = False
231
+
232
+ def __init__(self):
233
+ """Initialize the class."""
234
+ self.database_url_options = {
235
+ "host": DBConfig.db_host,
236
+ "port": DBConfig.db_port,
237
+ "database": DBConfig.db_database,
238
+ "username": DBConfig.db_username,
239
+ "password": DBConfig.db_password,
240
+ }
241
+ self.database_url_options = {k: v for k, v in self.database_url_options.items() if v != ""}
242
+ self.engine_options: dict = {
243
+ # "pool_size": DB_POOL_SIZE, ## not used by sqlite
244
+ # "max_overflow": DB_MAX_OVERFLOW, ## not used by sqlite
245
+ "poolclass": DBConfig.db_pool,
246
+ }
247
+ self.engine_options = {k: int(v) for k, v in self.engine_options.items() if v != ""}
248
+ if self.engine_options["poolclass"] == 0:
249
+ self.engine_options["poolclass"] = NullPool
250
+ else:
251
+ del self.engine_options["poolclass"]
252
+ self.db = DB(
253
+ getenv("DB_DRIVER", "postgresql+asyncpg"), options=self.engine_options, **self.database_url_options
254
+ )
255
+ logger.info("Connected to Database")
256
+
257
+ async def init(self) -> None:
258
+ """Set whether the dependency is initialized or not."""
259
+ if self.initialized:
260
+ return
261
+
262
+ # logger.info("Create Tables")
263
+ self.initialized = True
264
+ # await self.db.create_tables()
265
+
266
+ async def __call__(self) -> DB:
267
+ """Define __call__."""
268
+ if not self.initialized:
269
+ await self.init()
270
+ return self.db
271
+
272
+
273
+ database_dependency: DatabaseDependency = DatabaseDependency()
274
+ Base: DeclarativeMeta = database_dependency.db.Base
@@ -0,0 +1,29 @@
1
+ import logging
2
+ import sys
3
+ from dotenv import load_dotenv
4
+ from os import getenv
5
+
6
+ load_dotenv()
7
+ LOG_LEVEL = getenv("LOG_LEVEL", "INFO")
8
+
9
+ logging_formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
10
+
11
+ logging_handler = logging.StreamHandler(sys.stdout)
12
+ logging_handler.setFormatter(logging_formatter)
13
+
14
+
15
+ def get_logger(name: str) -> logging.Logger:
16
+ """Get a logger with a given name.
17
+
18
+ Args:
19
+ name (str): Logger name.
20
+
21
+ Returns:
22
+ logging.Logger: logging.Logger object.
23
+ """
24
+
25
+ logger: logging.Logger = logging.getLogger(name)
26
+ logger.addHandler(logging_handler)
27
+ logger.setLevel(LOG_LEVEL.upper())
28
+
29
+ return logger
desdeo/core.py ADDED
@@ -0,0 +1,27 @@
1
+ """Functionalities related to managing and building the project."""
2
+
3
+ import shutil
4
+ import warnings
5
+
6
+
7
+ def _check_executables(executable_list):
8
+ missing_executables = []
9
+ for executable in executable_list:
10
+ if shutil.which(executable) is None:
11
+ missing_executables.append(executable)
12
+
13
+ if missing_executables:
14
+ warnings.warn(
15
+ f"""
16
+ \nThe following required highly recommended executables cannot be found: {", ".join(missing_executables)}\n
17
+ DESDEO relies on powerful 3rd party solvers to solve multiobjective
18
+ optimization problems. Without these solvers, sub-optimal defaults
19
+ will be used instead, which can lead to not optimal, or even wrong,
20
+ results.\n
21
+ It is highly recommended to have these solvers available
22
+ in the environment DESDEO is utilized!\n
23
+ For more information, see DESDEO's documentation: https://desdeo.readthedocs.io/en/latest/howtoguides/installing/#third-party-optimizers
24
+ """,
25
+ UserWarning,
26
+ stacklevel=2,
27
+ )
desdeo/emo/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """Imports available from the desdeo emo."""
2
+
3
+ __all__ = [
4
+ "rvea",
5
+ "nsga3",
6
+ "template1",
7
+ "NSGAIII_select",
8
+ "RVEASelector",
9
+ "SimulatedBinaryCrossover",
10
+ "BoundedPolynomialMutation",
11
+ "LHSGenerator",
12
+ "RandomGenerator",
13
+ "EMOEvaluator",
14
+ "MaxEvaluationsTerminator",
15
+ "MaxGenerationsTerminator",
16
+ "Archive",
17
+ "FeasibleArchive",
18
+ "NonDominatedArchive",
19
+ ]
20
+
21
+ from .hooks.archivers import Archive, FeasibleArchive, NonDominatedArchive
22
+ from .methods.bases import template1
23
+ from .methods.EAs import nsga3, rvea
24
+ from .operators.crossover import SimulatedBinaryCrossover
25
+ from .operators.evaluator import EMOEvaluator
26
+ from .operators.generator import LHSGenerator, RandomGenerator
27
+ from .operators.mutation import BoundedPolynomialMutation
28
+ from .operators.selection import NSGAIII_select, RVEASelector
29
+ from .operators.termination import MaxEvaluationsTerminator, MaxGenerationsTerminator