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
|
@@ -8,4 +8,110 @@ If these conditions are not met, the results of the indicators will not be meani
|
|
|
8
8
|
Additionally, the set may be assumed to only contain mutually non-dominated solutions, depending on the indicator.
|
|
9
9
|
|
|
10
10
|
For now, we rely on pymoo for the implementation of many of the indicators.
|
|
11
|
-
"""
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from moocore import epsilon_additive, epsilon_mult
|
|
17
|
+
from numba import njit
|
|
18
|
+
from desdeo.tools.non_dominated_sorting import dominates
|
|
19
|
+
from desdeo.tools.indicators_unary import hv
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
Note that the moocore package includes a more complex implementation for calculating the epsilon_indicator for two
|
|
23
|
+
solution *sets*.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@njit()
|
|
28
|
+
def epsilon_component(solution1: np.ndarray, solution2: np.ndarray) -> float:
|
|
29
|
+
"""Computes the additive epsilon-indicator between two solutions.
|
|
30
|
+
|
|
31
|
+
Basically, returns the minimum amount by which the values in solution1 must be translated (minimization assumed)
|
|
32
|
+
such that it (weakly) dominates solution2. If solution1 already dominates solution2, returns 0.0.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
solution1 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
36
|
+
solution2 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
float: The maximum distance between the values in s1 and s2.
|
|
40
|
+
"""
|
|
41
|
+
return max(0.0, max(solution1 - solution2))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@njit()
|
|
45
|
+
def self_epsilon(solution_set: np.ndarray) -> np.ndarray:
|
|
46
|
+
"""Computes the pairwise additive epsilon-indicator for a solution set.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
solution_set (np.ndarray): Should be a two-dimensional array, where each row is a
|
|
50
|
+
solution normalized between [0, 1].
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
np.ndarray: A two-dimensional array where the entry at (i, j) is the
|
|
54
|
+
additive epsilon-indicator between the i-th and j-th solution in the set.
|
|
55
|
+
"""
|
|
56
|
+
n_solutions = solution_set.shape[0]
|
|
57
|
+
eps_matrix = np.zeros((n_solutions, n_solutions), dtype=np.float64)
|
|
58
|
+
for i in range(n_solutions):
|
|
59
|
+
for j in range(n_solutions):
|
|
60
|
+
eps_matrix[i, j] = epsilon_component(solution_set[i], solution_set[j])
|
|
61
|
+
return eps_matrix
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def epsilon_indicator(
|
|
65
|
+
set1: np.ndarray, set2: np.ndarray, kind: Literal["additive", "multiplicative"] = "additive"
|
|
66
|
+
) -> float:
|
|
67
|
+
"""Computes the additive epsilon-indicator between two solution sets.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
set1 (np.ndarray): Should be a two-dimensional array, where each row is a solution normalized between [0, 1]
|
|
71
|
+
set2 (np.ndarray): Should be a two-dimensional array, where each row is a solution normalized between [0, 1]
|
|
72
|
+
kind (Literal["additive", "multiplicative"]): The kind of epsilon-indicator to compute. Defaults to "additive".
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
float: the epsilon-indicator between the two sets.
|
|
76
|
+
"""
|
|
77
|
+
if kind == "additive":
|
|
78
|
+
return epsilon_additive(set1, ref=set2)
|
|
79
|
+
if kind == "multiplicative":
|
|
80
|
+
return epsilon_mult(set1, ref=set2)
|
|
81
|
+
raise ValueError(f"Unknown kind: {kind}. Use 'additive' or 'multiplicative'.")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def hv_component(solution1: np.ndarray, solution2: np.ndarray, ref: float = 2.0) -> float:
|
|
85
|
+
"""Computes the hypervolume contribution of solution1 with respect to solution2.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
solution1 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
89
|
+
solution2 (np.ndarray): Should be an one-dimensional array, where each value is normalized between [0, 1]
|
|
90
|
+
ref (float): The reference point for the hypervolume calculation. Defaults to 2.0.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
float: The hypervolume contribution of solution1 with respect to solution2.
|
|
94
|
+
"""
|
|
95
|
+
if dominates(solution1, solution2):
|
|
96
|
+
return np.prod(ref - solution2) - np.prod(ref - solution1)
|
|
97
|
+
return hv(solution_set=np.array([solution1, solution2]), reference_point_component=ref)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def self_hv(solution_set: np.ndarray, ref: float = 2.0) -> np.ndarray:
|
|
101
|
+
"""Computes the pairwise hypervolume contribution for a solution set.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
solution_set (np.ndarray): Should be a two-dimensional array, where each row is a
|
|
105
|
+
solution normalized between [0, 1].
|
|
106
|
+
ref (float): The reference point for the hypervolume calculation. Defaults to 2.0.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
np.ndarray: A two-dimensional array where the entry at (i, j) is the
|
|
110
|
+
hypervolume contribution of the i-th solution with respect to the j-th solution in the set.
|
|
111
|
+
"""
|
|
112
|
+
n_solutions = solution_set.shape[0]
|
|
113
|
+
hv_matrix = np.zeros((n_solutions, n_solutions), dtype=np.float64)
|
|
114
|
+
for i in range(n_solutions):
|
|
115
|
+
for j in range(n_solutions):
|
|
116
|
+
hv_matrix[i, j] = hv_component(solution_set[i], solution_set[j], ref=ref)
|
|
117
|
+
return hv_matrix
|
desdeo/tools/indicators_unary.py
CHANGED
|
@@ -18,7 +18,7 @@ from warnings import warn
|
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
from pydantic import BaseModel, Field
|
|
21
|
-
from
|
|
21
|
+
from moocore import Hypervolume
|
|
22
22
|
from pymoo.indicators.rmetric import RMetric
|
|
23
23
|
from scipy.spatial.distance import cdist
|
|
24
24
|
from typing import Dict
|
|
@@ -37,15 +37,8 @@ def hv(solution_set: np.ndarray, reference_point_component: float) -> float:
|
|
|
37
37
|
Returns:
|
|
38
38
|
float: The hypervolume indicator value.
|
|
39
39
|
"""
|
|
40
|
-
rp = np.full(solution_set.shape[1], reference_point_component, dtype=np.float64)
|
|
41
|
-
ideal = np.zeros(solution_set.shape[1], dtype=np.float64)
|
|
42
|
-
nadir = np.ones(solution_set.shape[1], dtype=np.float64)
|
|
43
|
-
|
|
44
|
-
# Sets the ideal and nadir to (0, 0, ..., 0) and (1, 1, ..., 1) respectively.
|
|
45
|
-
# Turns of non-domination checks.
|
|
46
|
-
# Turns of normalization of the reference point
|
|
47
|
-
hv = Hypervolume(ref_point=rp, ideal=ideal, nadir=nadir, nds=False, norm_ref_point=False)
|
|
48
40
|
|
|
41
|
+
hv = Hypervolume(reference_point_component)
|
|
49
42
|
ind = hv(solution_set)
|
|
50
43
|
|
|
51
44
|
if ind is None:
|
|
@@ -80,13 +73,7 @@ def hv_batch(
|
|
|
80
73
|
num_objs = solution_sets[next(iter(solution_sets.keys()))].shape[1]
|
|
81
74
|
|
|
82
75
|
for rp in reference_points_component:
|
|
83
|
-
hv = Hypervolume(
|
|
84
|
-
ref_point=np.full(num_objs, rp, dtype=np.float64),
|
|
85
|
-
ideal=np.zeros(num_objs, dtype=np.float64),
|
|
86
|
-
nadir=np.ones(num_objs, dtype=np.float64),
|
|
87
|
-
nds=False,
|
|
88
|
-
norm_ref_point=False,
|
|
89
|
-
)
|
|
76
|
+
hv = Hypervolume(rp)
|
|
90
77
|
for set_name in solution_sets:
|
|
91
78
|
ind = hv(solution_sets[set_name])
|
|
92
79
|
if ind is None:
|
desdeo/tools/message.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Any, Literal
|
|
5
5
|
|
|
6
|
+
import numpy as np
|
|
6
7
|
from polars import DataFrame
|
|
7
8
|
from pydantic import BaseModel, ConfigDict, Field, field_serializer
|
|
8
9
|
|
|
@@ -22,6 +23,8 @@ class CrossoverMessageTopics(Enum):
|
|
|
22
23
|
""" The offsprings generated from the crossover. """
|
|
23
24
|
ALPHA = "ALPHA"
|
|
24
25
|
""" Alpha parameter used in crossover. """
|
|
26
|
+
LAMBDA = "LAMBDA"
|
|
27
|
+
""" Lambda parameter used in crossover. Primarily used in the bounded exponential xover. """
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
class MutationMessageTopics(Enum):
|
|
@@ -100,6 +103,8 @@ class SelectorMessageTopics(Enum):
|
|
|
100
103
|
""" The individuals selected by the selector. """
|
|
101
104
|
SELECTED_OUTPUTS = "SELECTED_OUTPUTS"
|
|
102
105
|
""" The targets of the selected individuals. """
|
|
106
|
+
SELECTED_FITNESS = "SELECTED_FITNESS"
|
|
107
|
+
""" The fitness of the selected individuals. This is the fitness calculated by the selector, not the objectives."""
|
|
103
108
|
SELECTED_VERBOSE_OUTPUTS = "SELECTED_VERBOSE_OUTPUTS"
|
|
104
109
|
""" Same as SELECTED_OUTPUTS + SELECTED_INDIVIDUALS"""
|
|
105
110
|
REFERENCE_VECTORS = "REFERENCE_VECTORS"
|
|
@@ -125,6 +130,12 @@ class TerminatorMessageTopics(Enum):
|
|
|
125
130
|
""" The maximum number of evaluations. """
|
|
126
131
|
|
|
127
132
|
|
|
133
|
+
class ReferenceVectorMessageTopics(Enum):
|
|
134
|
+
"""Topics for messages related to the reference vectors."""
|
|
135
|
+
|
|
136
|
+
TEST = "TEST"
|
|
137
|
+
|
|
138
|
+
|
|
128
139
|
MessageTopics = (
|
|
129
140
|
CrossoverMessageTopics
|
|
130
141
|
| MutationMessageTopics
|
|
@@ -132,7 +143,10 @@ MessageTopics = (
|
|
|
132
143
|
| GeneratorMessageTopics
|
|
133
144
|
| SelectorMessageTopics
|
|
134
145
|
| TerminatorMessageTopics
|
|
135
|
-
|
|
|
146
|
+
| ReferenceVectorMessageTopics
|
|
147
|
+
| Literal[
|
|
148
|
+
"ALL"
|
|
149
|
+
] # Used to indicate that all topics are of interest to a subscriber.
|
|
136
150
|
)
|
|
137
151
|
|
|
138
152
|
|
|
@@ -176,7 +190,9 @@ class BoolMessage(BaseMessage):
|
|
|
176
190
|
class DictMessage(BaseMessage):
|
|
177
191
|
"""A message containing a dictionary value."""
|
|
178
192
|
|
|
179
|
-
value: dict[str, Any] = Field(
|
|
193
|
+
value: dict[str, Any] = Field(
|
|
194
|
+
..., description="The dictionary value of the message."
|
|
195
|
+
)
|
|
180
196
|
""" The dictionary value of the message. """
|
|
181
197
|
|
|
182
198
|
|
|
@@ -200,6 +216,19 @@ class PolarsDataFrameMessage(BaseMessage):
|
|
|
200
216
|
return value.to_dict(as_series=False)
|
|
201
217
|
|
|
202
218
|
|
|
219
|
+
class NumpyArrayMessage(BaseMessage):
|
|
220
|
+
"""A message containing a numpy array value."""
|
|
221
|
+
|
|
222
|
+
value: np.ndarray = Field(..., description="The numpy array value of the message.")
|
|
223
|
+
""" The numpy array value of the message. """
|
|
224
|
+
|
|
225
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
226
|
+
|
|
227
|
+
@field_serializer("value")
|
|
228
|
+
def _serialize_value(self, value: np.ndarray) -> list[list[float]]:
|
|
229
|
+
return value.tolist()
|
|
230
|
+
|
|
231
|
+
|
|
203
232
|
class GenericMessage(BaseMessage):
|
|
204
233
|
"""A message containing a generic value."""
|
|
205
234
|
|
|
@@ -216,6 +245,7 @@ Message = (
|
|
|
216
245
|
| StringMessage
|
|
217
246
|
| BoolMessage
|
|
218
247
|
| PolarsDataFrameMessage
|
|
248
|
+
| NumpyArrayMessage
|
|
219
249
|
)
|
|
220
250
|
|
|
221
251
|
AllowedMessagesAtVerbosity: dict[int, tuple[type[Message], ...]] = {
|
|
@@ -230,5 +260,6 @@ AllowedMessagesAtVerbosity: dict[int, tuple[type[Message], ...]] = {
|
|
|
230
260
|
Array2DMessage,
|
|
231
261
|
GenericMessage,
|
|
232
262
|
PolarsDataFrameMessage,
|
|
263
|
+
NumpyArrayMessage,
|
|
233
264
|
),
|
|
234
265
|
}
|
|
@@ -76,10 +76,11 @@ def fast_non_dominated_sort(data: np.ndarray) -> np.ndarray:
|
|
|
76
76
|
fronts[i] = current_front_all
|
|
77
77
|
|
|
78
78
|
taken = taken + fronts[i]
|
|
79
|
-
if
|
|
80
|
-
# if
|
|
79
|
+
if taken.all():
|
|
80
|
+
# if all the solutions have been sorted, stop
|
|
81
81
|
break
|
|
82
|
-
|
|
82
|
+
|
|
83
|
+
return fronts[: i + 1]
|
|
83
84
|
|
|
84
85
|
|
|
85
86
|
def fast_non_dominated_sort_indices(data: np.ndarray) -> list[np.ndarray]:
|
desdeo/tools/patterns.py
CHANGED
|
@@ -63,17 +63,18 @@ class Subscriber(ABC):
|
|
|
63
63
|
def __init__(
|
|
64
64
|
self,
|
|
65
65
|
publisher: "Publisher",
|
|
66
|
-
verbosity: int
|
|
66
|
+
verbosity: int,
|
|
67
67
|
) -> None:
|
|
68
68
|
"""Initialize a subscriber.
|
|
69
69
|
|
|
70
70
|
Args:
|
|
71
71
|
publisher (Callable): the publisher to send messages to.
|
|
72
|
-
verbosity (int, optional): the verbosity level of the messages.
|
|
73
|
-
amounts of information depending on the message sender. A value of 0 means no messages at all.
|
|
72
|
+
verbosity (int, optional): the verbosity level of the messages. A value of 0 means no messages at all.
|
|
74
73
|
"""
|
|
75
74
|
if not isinstance(verbosity, int):
|
|
76
75
|
raise TypeError("Verbosity must be an integer.")
|
|
76
|
+
if verbosity < 0:
|
|
77
|
+
raise ValueError("Verbosity must be a non-negative integer.")
|
|
77
78
|
self.publisher = publisher
|
|
78
79
|
self.verbosity: int = verbosity
|
|
79
80
|
|
|
@@ -196,12 +197,13 @@ class Publisher:
|
|
|
196
197
|
else:
|
|
197
198
|
self.registered_topics[topic].append(source)
|
|
198
199
|
|
|
199
|
-
def check_consistency(self) ->
|
|
200
|
+
def check_consistency(self) -> tuple[bool, dict[MessageTopics, list[str]]]:
|
|
200
201
|
"""Check if all subscribed topics have also been registered by a source.
|
|
201
202
|
|
|
202
203
|
Returns:
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
tuple[bool, dict[MessageTopics, list[str]]]: Returns a tuple. The first element is a bool. True if all
|
|
205
|
+
subscribed topics have been registered by a source. False otherwise. The second element is a dictionary
|
|
206
|
+
of unregistered topics that have been subscribed to.
|
|
205
207
|
"""
|
|
206
208
|
unregistered_topics = {}
|
|
207
209
|
for topic in self.subscribers:
|
|
@@ -209,7 +211,7 @@ class Publisher:
|
|
|
209
211
|
unregistered_topics[topic] = [x.__class__.__name__ for x in self.subscribers[topic]]
|
|
210
212
|
if unregistered_topics:
|
|
211
213
|
return False, unregistered_topics
|
|
212
|
-
return True
|
|
214
|
+
return True, {}
|
|
213
215
|
|
|
214
216
|
def relationship_map(self):
|
|
215
217
|
"""Make a diagram connecting sources to subscribers based on topics."""
|
|
@@ -80,7 +80,7 @@ class IpoptOptions(BaseModel):
|
|
|
80
80
|
max_iter: int = Field(description="Maximum number of iterations. Must be >1. Defaults to 3000.", default=3000)
|
|
81
81
|
"""Maximum number of iterations. Must be >1. Defaults to 3000."""
|
|
82
82
|
|
|
83
|
-
print_level:
|
|
83
|
+
print_level: int = Field(
|
|
84
84
|
description="The verbosity level of the solver's output. Ranges between 0 and 12. Defaults to 5.", default=5
|
|
85
85
|
)
|
|
86
86
|
"""The verbosity level of the solver's output. Ranges between 0 and 12."""
|
|
@@ -99,62 +99,62 @@ class CbcOptions(BaseModel):
|
|
|
99
99
|
|
|
100
100
|
model_config = ConfigDict(frozen=True, populate_by_name=True)
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
description="The maximum amount of time (in seconds) the solver should run. Defaults to
|
|
102
|
+
seconds: int = Field(
|
|
103
|
+
description="The maximum amount of time (in seconds) the solver should run. Defaults to 600.", default=600
|
|
104
104
|
)
|
|
105
|
-
"""The maximum amount of time (in seconds) the solver should run. Defaults to
|
|
105
|
+
"""The maximum amount of time (in seconds) the solver should run. Defaults to 600."""
|
|
106
106
|
|
|
107
107
|
threads: int = Field(
|
|
108
|
-
description="Number of threads (cores) to use for solving the problem. Defaults to
|
|
108
|
+
description="Number of threads (cores) to use for solving the problem. Defaults to 4.", default=4
|
|
109
109
|
)
|
|
110
|
-
"""Number of threads (cores) to use for solving the problem. Defaults to
|
|
110
|
+
"""Number of threads (cores) to use for solving the problem. Defaults to 4."""
|
|
111
111
|
|
|
112
112
|
log_level: int = Field(
|
|
113
113
|
alias="logLevel",
|
|
114
114
|
description=(
|
|
115
115
|
"Controls the level of logging output. Values range from 0 (no output) to 5 (very detailed output)."
|
|
116
|
-
" Defaults to
|
|
116
|
+
" Defaults to 2."
|
|
117
117
|
),
|
|
118
|
-
default=
|
|
118
|
+
default=2,
|
|
119
119
|
)
|
|
120
120
|
"""Controls the level of logging output. Values range from 0 (no output) to 5 (very detailed output).
|
|
121
|
-
Defaults to
|
|
121
|
+
Defaults to 2.
|
|
122
122
|
"""
|
|
123
123
|
|
|
124
124
|
max_solutions: int = Field(
|
|
125
125
|
alias="maxSolutions",
|
|
126
|
-
description="Limits the number of feasible solutions found by the solver. Defaults to
|
|
127
|
-
default=
|
|
126
|
+
description="Limits the number of feasible solutions found by the solver. Defaults to 10.",
|
|
127
|
+
default=10,
|
|
128
128
|
)
|
|
129
|
-
"""Limits the number of feasible solutions found by the solver. Defaults to
|
|
129
|
+
"""Limits the number of feasible solutions found by the solver. Defaults to 10."""
|
|
130
130
|
|
|
131
131
|
max_nodes: int = Field(
|
|
132
132
|
alias="maxNodes",
|
|
133
|
-
description="Sets the maximum number of branch-and-bound nodes to explore. Defaults to
|
|
134
|
-
default=
|
|
133
|
+
description="Sets the maximum number of branch-and-bound nodes to explore. Defaults to 1000.",
|
|
134
|
+
default=1000,
|
|
135
135
|
)
|
|
136
|
-
"""Sets the maximum number of branch-and-bound nodes to explore. Defaults to
|
|
136
|
+
"""Sets the maximum number of branch-and-bound nodes to explore. Defaults to 1000."""
|
|
137
137
|
|
|
138
138
|
ratio_gap: float = Field(
|
|
139
139
|
alias="ratioGap",
|
|
140
140
|
description=(
|
|
141
141
|
"Sets the relative MIP gap (as a fraction of the optimal solution value) at which the solver will"
|
|
142
|
-
" terminate. Defaults to
|
|
142
|
+
" terminate. Defaults to 0.01."
|
|
143
143
|
),
|
|
144
|
-
default=
|
|
144
|
+
default=0.01,
|
|
145
145
|
)
|
|
146
146
|
"""Sets the relative MIP gap (as a fraction of the optimal solution value) at which the solver will terminate.
|
|
147
|
-
Defaults to
|
|
147
|
+
Defaults to 0.01.
|
|
148
148
|
"""
|
|
149
149
|
|
|
150
150
|
absolute_gap: float = Field(
|
|
151
151
|
alias="absoluteGap",
|
|
152
152
|
description=(
|
|
153
|
-
"Sets the absolute MIP gap (an absolute value) at which the solver will terminate. Defaults to
|
|
153
|
+
"Sets the absolute MIP gap (an absolute value) at which the solver will terminate. Defaults to 1.0."
|
|
154
154
|
),
|
|
155
|
-
default=
|
|
155
|
+
default=1.0,
|
|
156
156
|
)
|
|
157
|
-
"""Sets the absolute MIP gap (an absolute value) at which the solver will terminate. Defaults to
|
|
157
|
+
"""Sets the absolute MIP gap (an absolute value) at which the solver will terminate. Defaults to 1.0."""
|
|
158
158
|
|
|
159
159
|
solve: str = Field(
|
|
160
160
|
description=(
|
|
@@ -168,9 +168,9 @@ class CbcOptions(BaseModel):
|
|
|
168
168
|
"""
|
|
169
169
|
|
|
170
170
|
presolve: int = Field(
|
|
171
|
-
description="Controls the presolve level (0: no presolve, 1: default, 2: aggressive). Defaults to
|
|
171
|
+
description="Controls the presolve level (0: no presolve, 1: default, 2: aggressive). Defaults to 2.", default=2
|
|
172
172
|
)
|
|
173
|
-
"""Controls the presolve level (0: no presolve, 1: default, 2: aggressive). Defaults to
|
|
173
|
+
"""Controls the presolve level (0: no presolve, 1: default, 2: aggressive). Defaults to 2."""
|
|
174
174
|
|
|
175
175
|
feasibility_tolerance: float = Field(
|
|
176
176
|
alias="feasibilityTolerance",
|
|
@@ -187,19 +187,7 @@ class CbcOptions(BaseModel):
|
|
|
187
187
|
"""Sets the tolerance for integrality of integer variables. Defaults to 1e-5."""
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
_default_cbc_options = CbcOptions(
|
|
191
|
-
sec=600,
|
|
192
|
-
threads=4,
|
|
193
|
-
logLevel=2,
|
|
194
|
-
maxSolutions=10,
|
|
195
|
-
maxNodes=1000,
|
|
196
|
-
ratioGap=0.01,
|
|
197
|
-
absoluteGap=1.0,
|
|
198
|
-
solve="branchAndCut",
|
|
199
|
-
presolve=2,
|
|
200
|
-
feasibilityTolerance=1e-6,
|
|
201
|
-
integerTolerance=1e-5,
|
|
202
|
-
)
|
|
190
|
+
_default_cbc_options = CbcOptions()
|
|
203
191
|
"""Defines CBC options with default values."""
|
|
204
192
|
|
|
205
193
|
_default_bonmin_options = BonminOptions()
|
|
@@ -243,6 +231,31 @@ def parse_pyomo_optimizer_results(
|
|
|
243
231
|
constraint_values = (
|
|
244
232
|
{con.symbol: results[con.symbol] for con in problem.constraints} if problem.constraints else None
|
|
245
233
|
)
|
|
234
|
+
|
|
235
|
+
# handle constraint, which might be multi-valued
|
|
236
|
+
if problem.constraints is not None:
|
|
237
|
+
constraint_values = {}
|
|
238
|
+
|
|
239
|
+
for con in problem.constraints:
|
|
240
|
+
result = results[con.symbol]
|
|
241
|
+
|
|
242
|
+
if isinstance(result, dict):
|
|
243
|
+
# multi-valued
|
|
244
|
+
indices = list(getattr(evaluator.model, con.symbol).keys())
|
|
245
|
+
shape = tuple(len({idx[k] for idx in indices}) for k in range(len(indices[0])))
|
|
246
|
+
values_list = np.zeros(shape)
|
|
247
|
+
|
|
248
|
+
for idx in indices:
|
|
249
|
+
values_list[*[i - 1 for i in idx]] = result[idx]
|
|
250
|
+
|
|
251
|
+
constraint_values[con.symbol] = values_list.tolist()
|
|
252
|
+
|
|
253
|
+
else:
|
|
254
|
+
# scalar-valued
|
|
255
|
+
constraint_values[con.symbol] = result
|
|
256
|
+
else:
|
|
257
|
+
constraint_values = None
|
|
258
|
+
|
|
246
259
|
extra_func_values = (
|
|
247
260
|
{extra.symbol: results[extra.symbol] for extra in problem.extra_funcs}
|
|
248
261
|
if problem.extra_funcs is not None
|