desdeo 1.2__py3-none-any.whl → 2.1.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/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/README.md +73 -0
- desdeo/api/__init__.py +15 -0
- desdeo/api/app.py +50 -0
- desdeo/api/config.py +90 -0
- desdeo/api/config.toml +64 -0
- desdeo/api/db.py +27 -0
- desdeo/api/db_init.py +85 -0
- desdeo/api/db_models.py +164 -0
- desdeo/api/malaga_db_init.py +27 -0
- desdeo/api/models/__init__.py +266 -0
- desdeo/api/models/archive.py +23 -0
- 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 +128 -0
- desdeo/api/models/problem.py +717 -0
- desdeo/api/models/reference_point_method.py +18 -0
- desdeo/api/models/session.py +49 -0
- desdeo/api/models/state.py +463 -0
- desdeo/api/models/user.py +52 -0
- desdeo/api/models/utopia.py +25 -0
- desdeo/api/routers/_EMO.backup +309 -0
- desdeo/api/routers/_NAUTILUS.py +245 -0
- desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
- desdeo/api/routers/_NIMBUS.py +765 -0
- desdeo/api/routers/__init__.py +5 -0
- 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 +307 -0
- desdeo/api/routers/reference_point_method.py +93 -0
- desdeo/api/routers/session.py +100 -0
- desdeo/api/routers/test.py +16 -0
- desdeo/api/routers/user_authentication.py +520 -0
- desdeo/api/routers/utils.py +187 -0
- desdeo/api/routers/utopia.py +230 -0
- desdeo/api/schema.py +100 -0
- desdeo/api/tests/__init__.py +0 -0
- desdeo/api/tests/conftest.py +151 -0
- desdeo/api/tests/test_enautilus.py +330 -0
- desdeo/api/tests/test_models.py +1179 -0
- desdeo/api/tests/test_routes.py +1075 -0
- desdeo/api/utils/_database.py +263 -0
- desdeo/api/utils/_logger.py +29 -0
- desdeo/api/utils/database.py +36 -0
- desdeo/api/utils/emo_database.py +40 -0
- desdeo/core.py +34 -0
- desdeo/emo/__init__.py +159 -0
- desdeo/emo/hooks/archivers.py +188 -0
- desdeo/emo/methods/EAs.py +541 -0
- desdeo/emo/methods/__init__.py +0 -0
- desdeo/emo/methods/bases.py +12 -0
- desdeo/emo/methods/templates.py +111 -0
- desdeo/emo/operators/__init__.py +1 -0
- desdeo/emo/operators/crossover.py +1282 -0
- desdeo/emo/operators/evaluator.py +114 -0
- desdeo/emo/operators/generator.py +459 -0
- desdeo/emo/operators/mutation.py +1224 -0
- desdeo/emo/operators/scalar_selection.py +202 -0
- desdeo/emo/operators/selection.py +1778 -0
- desdeo/emo/operators/termination.py +286 -0
- 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/explanations/__init__.py +6 -0
- desdeo/explanations/explainer.py +100 -0
- desdeo/explanations/utils.py +90 -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 +41 -0
- desdeo/mcdm/enautilus.py +338 -0
- desdeo/mcdm/gnimbus.py +484 -0
- desdeo/mcdm/nautili.py +345 -0
- desdeo/mcdm/nautilus.py +477 -0
- desdeo/mcdm/nautilus_navigator.py +656 -0
- desdeo/mcdm/nimbus.py +417 -0
- desdeo/mcdm/pareto_navigator.py +269 -0
- desdeo/mcdm/reference_point_method.py +186 -0
- desdeo/problem/__init__.py +83 -0
- desdeo/problem/evaluator.py +561 -0
- 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 +562 -0
- desdeo/problem/infix_parser.py +341 -0
- desdeo/problem/json_parser.py +944 -0
- desdeo/problem/pyomo_evaluator.py +487 -0
- desdeo/problem/schema.py +1829 -0
- desdeo/problem/simulator_evaluator.py +348 -0
- desdeo/problem/sympy_evaluator.py +244 -0
- desdeo/problem/testproblems/__init__.py +88 -0
- desdeo/problem/testproblems/benchmarks_server.py +120 -0
- desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
- desdeo/problem/testproblems/cake_problem.py +185 -0
- desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
- desdeo/problem/testproblems/dtlz2_problem.py +102 -0
- desdeo/problem/testproblems/forest_problem.py +283 -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/multi_valued_constraints.py +119 -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_problems.py +440 -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/single_objective.py +289 -0
- desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
- desdeo/problem/testproblems/zdt_problem.py +274 -0
- desdeo/problem/utils.py +245 -0
- desdeo/tools/GenerateReferencePoints.py +181 -0
- desdeo/tools/__init__.py +120 -0
- desdeo/tools/desc_gen.py +22 -0
- desdeo/tools/generics.py +165 -0
- desdeo/tools/group_scalarization.py +3090 -0
- desdeo/tools/gurobipy_solver_interfaces.py +258 -0
- desdeo/tools/indicators_binary.py +117 -0
- desdeo/tools/indicators_unary.py +362 -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 +265 -0
- desdeo/tools/ng_solver_interfaces.py +199 -0
- desdeo/tools/non_dominated_sorting.py +134 -0
- desdeo/tools/patterns.py +283 -0
- desdeo/tools/proximal_solver.py +99 -0
- desdeo/tools/pyomo_solver_interfaces.py +477 -0
- desdeo/tools/reference_vectors.py +229 -0
- desdeo/tools/scalarization.py +2065 -0
- desdeo/tools/scipy_solver_interfaces.py +454 -0
- desdeo/tools/score_bands.py +627 -0
- desdeo/tools/utils.py +388 -0
- desdeo/tools/visualizations.py +67 -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.1.0.dist-info/METADATA +186 -0
- desdeo-2.1.0.dist-info/RECORD +180 -0
- {desdeo-1.2.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
- desdeo-2.1.0.dist-info/licenses/LICENSE +21 -0
- desdeo-1.2.dist-info/METADATA +0 -16
- desdeo-1.2.dist-info/RECORD +0 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Utilities specific to explainable multiobjective optimization."""
|
|
2
|
+
|
|
3
|
+
import cvxpy as cp
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_biased_mean_data(
|
|
8
|
+
data: np.ndarray, target_means: np.ndarray, min_size: int = 2, max_size: int | None = None, solver: str = "SCIP"
|
|
9
|
+
) -> list | None:
|
|
10
|
+
r"""Finds a subset of the provided data that has a mean value close to provided target values.
|
|
11
|
+
|
|
12
|
+
Finds a subset of the provided data that has a mean value close to the
|
|
13
|
+
provided target values. Formulates a mixed-integer quadratic programming problem to
|
|
14
|
+
find a subset of `data` with a mean as close as possible to `target_values`
|
|
15
|
+
and a size between `min_size` and `max_size`. In other words, the following problems is solved:
|
|
16
|
+
|
|
17
|
+
\begin{align}
|
|
18
|
+
&\min_{\mathbf{x}} & f(\mathbf{x}) & = \sum_{i=1}^m \left[ \left(\frac{1}{k}
|
|
19
|
+
\sum_{j=1}^n x_j \times \text{data}_j\right)_i - \text{target}_i \right]^2 \\
|
|
20
|
+
&\text{s.t.,} & k & = \sum_{i=1}^n x_i,\\
|
|
21
|
+
& & k & \leq \text{max_size},\\
|
|
22
|
+
& & k & \geq \text{min_size},\\
|
|
23
|
+
\end{align},
|
|
24
|
+
where $n$ is the number of rows in `data`, $m$ is the number of columns in
|
|
25
|
+
`data`, and $k$ is the size of the subset. Notice that the closeness to the
|
|
26
|
+
target means is based on the Euclidean distance.
|
|
27
|
+
|
|
28
|
+
Note:
|
|
29
|
+
Be mindful that this functions can take a long time with a very large
|
|
30
|
+
data set and large upper bound for the desired subset.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data (np.ndarray): the data from which to generate the subset with a
|
|
34
|
+
biased mean. Should be a 2D array with each row representing a sample
|
|
35
|
+
and each column the value of the variables in the sample.
|
|
36
|
+
target_means (np.ndarray): the target mean values for each column the
|
|
37
|
+
generated subset should have values close to.
|
|
38
|
+
min_size (int, optional): the minimum size of the generated subset. Defaults to 2.
|
|
39
|
+
max_size (int | None, optional): the maximum size of the generated
|
|
40
|
+
subset. If None, then the maximum size is bound by the numbers of rows
|
|
41
|
+
in `data`. Defaults to None.
|
|
42
|
+
solver (str, optional): the selected solver to be used by cvxpy. The
|
|
43
|
+
solver should support mixed-integer quadratic programming. Defaults to
|
|
44
|
+
"SCIP".
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
list | None: the indices of the samples of the generated subset respect to
|
|
48
|
+
`data`, i.e., the generated subset is `data[indices]`. If optimization is not successful,
|
|
49
|
+
returns None instead.
|
|
50
|
+
"""
|
|
51
|
+
# Number of rows and columns
|
|
52
|
+
n_rows, n_cols = data.shape
|
|
53
|
+
max_size = n_rows if max_size is None else max_size
|
|
54
|
+
|
|
55
|
+
# Big M used to penalize the auxiliary variable z
|
|
56
|
+
big_m = data.max(axis=0)
|
|
57
|
+
|
|
58
|
+
# Binary variables to select rows from the data
|
|
59
|
+
x = cp.Variable(n_rows, boolean=True)
|
|
60
|
+
|
|
61
|
+
# Auxiliary variables, z represents the mean. phi is the weighted sum of the data (weighted by x)
|
|
62
|
+
z = cp.Variable(n_cols)
|
|
63
|
+
phi = cp.Variable((n_rows, n_cols))
|
|
64
|
+
|
|
65
|
+
# The objective function, squared values of the difference between the mean
|
|
66
|
+
# of the currently selected subset and the target values.
|
|
67
|
+
objective = cp.sum_squares(z - target_means)
|
|
68
|
+
|
|
69
|
+
# Define the constraints
|
|
70
|
+
constraints = [
|
|
71
|
+
# Sets the value of phi
|
|
72
|
+
*[cp.sum(phi[:, col]) == cp.sum(cp.multiply(x, data[:, col])) for col in range(n_cols)],
|
|
73
|
+
# Constraints the values of phi using big M, in practice setting z to be the mean values
|
|
74
|
+
*[phi[:, col] <= cp.multiply(big_m[col], x) for col in range(n_cols)],
|
|
75
|
+
*[phi[:, col] <= z[col] for col in range(n_cols)],
|
|
76
|
+
*[phi[:, col] >= z[col] - cp.multiply(big_m[col], 1 - x) for col in range(n_cols)],
|
|
77
|
+
phi >= 0,
|
|
78
|
+
# Bounds the size of the set: min_size <= k <= max_size
|
|
79
|
+
cp.sum(x) >= min_size,
|
|
80
|
+
cp.sum(x) <= max_size,
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
# Create the problem model
|
|
84
|
+
problem = cp.Problem(cp.Minimize(objective), constraints)
|
|
85
|
+
|
|
86
|
+
# Solve the problem
|
|
87
|
+
problem.solve(solver=solver)
|
|
88
|
+
|
|
89
|
+
# Return the indices of the found subset
|
|
90
|
+
return [i for i in range(n_rows) if x.value[i] == 1]
|
desdeo/gdm/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Imports available from the desdeo-gdm package."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"dict_of_rps_to_list_of_rps",
|
|
5
|
+
"list_of_rps_to_dict_of_rps",
|
|
6
|
+
"majority_rule",
|
|
7
|
+
"plurality_rule",
|
|
8
|
+
"agg_aspbounds",
|
|
9
|
+
"scale_delta",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
from .gdmtools import (
|
|
13
|
+
dict_of_rps_to_list_of_rps,
|
|
14
|
+
list_of_rps_to_dict_of_rps,
|
|
15
|
+
agg_aspbounds,
|
|
16
|
+
scale_delta,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .voting_rules import (
|
|
20
|
+
majority_rule,
|
|
21
|
+
plurality_rule,
|
|
22
|
+
)
|
desdeo/gdm/gdmtools.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
""" This module contains tools for group decision making such as manipulating set of preferences. """
|
|
2
|
+
|
|
3
|
+
from desdeo.problem import Problem
|
|
4
|
+
|
|
5
|
+
# Below two are tools for GDM, have needed them in both projects
|
|
6
|
+
def dict_of_rps_to_list_of_rps(reference_points: dict[str, dict[str, float]]) -> list[dict[str, float]]:
|
|
7
|
+
"""
|
|
8
|
+
Convert dict containing the DM key to an ordered list.
|
|
9
|
+
"""
|
|
10
|
+
return list(reference_points.values())
|
|
11
|
+
|
|
12
|
+
def list_of_rps_to_dict_of_rps(reference_points: list[dict[str, float]]) -> dict[str, dict[str, float]]:
|
|
13
|
+
"""
|
|
14
|
+
Convert the ordered list to a dict contain the DM keys. TODO: Later maybe get these keys from somewhere.
|
|
15
|
+
"""
|
|
16
|
+
return {f"DM{i+1}": rp for i, rp in enumerate(reference_points)}
|
|
17
|
+
|
|
18
|
+
# TODO:(@jpajasmaa) document
|
|
19
|
+
def agg_aspbounds(po_list: list[dict[str, float]], problem: Problem):
|
|
20
|
+
agg_aspirations = {}
|
|
21
|
+
agg_bounds = {}
|
|
22
|
+
|
|
23
|
+
for obj in problem.objectives:
|
|
24
|
+
if obj.maximize:
|
|
25
|
+
agg_aspirations.update({obj.symbol: max(s[obj.symbol] for s in po_list)})
|
|
26
|
+
agg_bounds.update({obj.symbol: min(s[obj.symbol] for s in po_list)})
|
|
27
|
+
else:
|
|
28
|
+
agg_aspirations.update({obj.symbol: min(s[obj.symbol] for s in po_list)})
|
|
29
|
+
agg_bounds.update({obj.symbol: max(s[obj.symbol] for s in po_list)})
|
|
30
|
+
|
|
31
|
+
return agg_aspirations, agg_bounds
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# TODO:(@jpajasmaa) comments
|
|
35
|
+
def scale_delta(problem: Problem, d: float):
|
|
36
|
+
delta = {}
|
|
37
|
+
ideal = problem.get_ideal_point()
|
|
38
|
+
nadir = problem.get_nadir_point()
|
|
39
|
+
|
|
40
|
+
for obj in problem.objectives:
|
|
41
|
+
if obj.maximize:
|
|
42
|
+
delta.update({obj.symbol: d*(ideal[obj.symbol] - nadir[obj.symbol])})
|
|
43
|
+
else:
|
|
44
|
+
delta.update({obj.symbol: d*(nadir[obj.symbol] - ideal[obj.symbol])})
|
|
45
|
+
return delta
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Implements a interactive SCORE bands based GDM."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
from desdeo.gdm.voting_rules import consensus_rule
|
|
9
|
+
from desdeo.tools.score_bands import SCOREBandsConfig, SCOREBandsResult, score_json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SCOREBandsGDMConfig(BaseModel):
|
|
13
|
+
"""Configuration for the SCORE bands based GDM."""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
16
|
+
|
|
17
|
+
score_bands_config: SCOREBandsConfig = Field(default_factory=lambda: SCOREBandsConfig())
|
|
18
|
+
"""Configuration for the SCORE bands method."""
|
|
19
|
+
minimum_votes: int = Field(default=1, gt=0)
|
|
20
|
+
"""Minimum number of votes required to select a cluster."""
|
|
21
|
+
from_iteration: int | None
|
|
22
|
+
"""The iteration number from which to consider the clusters. Set to None if method is initializing."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SCOREBandsGDMResult(BaseModel):
|
|
26
|
+
"""Result of the SCORE bands based GDM."""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
29
|
+
|
|
30
|
+
votes: dict[str, int] | None = Field(default=None)
|
|
31
|
+
"""The votes given by the decision makers."""
|
|
32
|
+
score_bands_result: SCOREBandsResult
|
|
33
|
+
"""Result of the SCORE bands method."""
|
|
34
|
+
relevant_ids: list[int]
|
|
35
|
+
"""IDs of the relevant solutions in the current iteration. Assumes that data is not modified between iterations."""
|
|
36
|
+
# If the data keeps changing, we need to store the actual data instead of just the IDs.
|
|
37
|
+
iteration: int
|
|
38
|
+
previous_iteration: int | None
|
|
39
|
+
"""The previous iteration number, if any."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def score_bands_gdm(
|
|
43
|
+
data: pl.DataFrame,
|
|
44
|
+
config: SCOREBandsGDMConfig,
|
|
45
|
+
state: list[SCOREBandsGDMResult],
|
|
46
|
+
votes: dict[str, int] | None = None,
|
|
47
|
+
) -> list[SCOREBandsGDMResult]:
|
|
48
|
+
"""Run the SCORE bands based interactive GDM.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
data (pl.DataFrame): The data to run the GDM on.
|
|
52
|
+
config (SCOREBandsGDMConfig): Configuration for the GDM.
|
|
53
|
+
state (list[SCOREBandsGDMResult]): List of previous state of the GDM. Empty list if first iteration.
|
|
54
|
+
votes (dict[str, int] | None, optional): Votes from the decision makers. Defaults to None.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: Both state and votes must be provided or neither.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
list[SCOREBandsGDMResult]: The updated state of the GDM.
|
|
61
|
+
"""
|
|
62
|
+
if bool(state) != bool(votes):
|
|
63
|
+
raise ValueError("Both state and votes must be provided or neither.")
|
|
64
|
+
if votes is None:
|
|
65
|
+
# First iteration. No votes yet.
|
|
66
|
+
score_bands_result = score_json(data, config.score_bands_config)
|
|
67
|
+
return [
|
|
68
|
+
SCOREBandsGDMResult(
|
|
69
|
+
score_bands_result=score_bands_result,
|
|
70
|
+
relevant_ids=list(range(len(data))),
|
|
71
|
+
iteration=1,
|
|
72
|
+
previous_iteration=None,
|
|
73
|
+
)
|
|
74
|
+
]
|
|
75
|
+
if not state:
|
|
76
|
+
raise ValueError("State must be provided if votes are provided.")
|
|
77
|
+
elif config.from_iteration is None:
|
|
78
|
+
raise ValueError("from_iteration must be set in the config for subsequent iterations.")
|
|
79
|
+
|
|
80
|
+
winning_clusters = consensus_rule(votes, config.minimum_votes)
|
|
81
|
+
|
|
82
|
+
index_column_name = "index"
|
|
83
|
+
if index_column_name in data.columns:
|
|
84
|
+
index_column_name = "index_"
|
|
85
|
+
cluster_column_name = "cluster"
|
|
86
|
+
if cluster_column_name in data.columns:
|
|
87
|
+
cluster_column_name = "cluster_"
|
|
88
|
+
|
|
89
|
+
current_iteration = state[-1].iteration + 1
|
|
90
|
+
|
|
91
|
+
clusters = state[config.from_iteration - 1].score_bands_result.clusters
|
|
92
|
+
relevant_data = (
|
|
93
|
+
data.with_row_index(name=index_column_name) # Add index column
|
|
94
|
+
.filter(
|
|
95
|
+
pl.col(index_column_name).is_in(state[config.from_iteration - 1].relevant_ids)
|
|
96
|
+
) # Get the solutions from previous iteration
|
|
97
|
+
.with_columns(pl.Series(cluster_column_name, clusters)) # Add clustering information from last iteration
|
|
98
|
+
.filter(pl.col(cluster_column_name).is_in(winning_clusters)) # Keep only winning clusters
|
|
99
|
+
.drop(cluster_column_name) # Drop cluster column
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
relevant_ids = relevant_data[index_column_name].to_list()
|
|
103
|
+
relevant_data = relevant_data.drop(index_column_name) # Drop index column
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
*state,
|
|
107
|
+
SCOREBandsGDMResult(
|
|
108
|
+
votes=votes,
|
|
109
|
+
score_bands_result=score_json(relevant_data, config.score_bands_config),
|
|
110
|
+
relevant_ids=relevant_ids,
|
|
111
|
+
iteration=current_iteration,
|
|
112
|
+
previous_iteration=config.from_iteration,
|
|
113
|
+
),
|
|
114
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""This module contains voting rules for group decision making such as majority rule."""
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def majority_rule(votes: dict[str, int]) -> int | None:
|
|
7
|
+
"""Choose the option that has more than half of the votes.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
votes (dict[str, int]): A dictionary mapping voter IDs to their votes.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
int | None: The option that has more than half of the votes, or None if no such option exists.
|
|
14
|
+
"""
|
|
15
|
+
counts = Counter(votes.values())
|
|
16
|
+
all_votes = sum(counts.values())
|
|
17
|
+
for vote, c in counts.items():
|
|
18
|
+
if c > all_votes // 2:
|
|
19
|
+
return vote
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def plurality_rule(votes: dict[str, int]) -> list[int]:
|
|
24
|
+
"""Choose the option that has the most votes.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
votes (dict[str, int]): A dictionary mapping voter IDs to their votes.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
list[int]: A list of options that have the most votes (in case of a tie).
|
|
31
|
+
"""
|
|
32
|
+
counts = Counter(votes.values())
|
|
33
|
+
max_votes = max(counts.values())
|
|
34
|
+
return [vote for vote, c in counts.items() if c == max_votes]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def consensus_rule(votes: dict[str, int], min_votes: int) -> list[int]:
|
|
38
|
+
"""Choose all options that have at least min_votes votes.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
votes (dict[str, int]): A dictionary mapping voter IDs to their votes.
|
|
42
|
+
min_votes (int): The minimum number of votes required for an option to be selected.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
if min_votes <= 0:
|
|
46
|
+
raise ValueError("min_votes must be greater than 0.")
|
|
47
|
+
if min_votes > len(votes):
|
|
48
|
+
raise ValueError("min_votes cannot be greater than the number of voters.")
|
|
49
|
+
counts = Counter(votes.values())
|
|
50
|
+
return [vote for vote, c in counts.items() if c >= min_votes]
|
desdeo/mcdm/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Imports available from the desdeo-mcdm package."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"ENautilusResult",
|
|
5
|
+
"NimbusError",
|
|
6
|
+
"enautilus_get_representative_solutions",
|
|
7
|
+
"enautilus_step",
|
|
8
|
+
"calculate_closeness",
|
|
9
|
+
"calculate_intermediate_points",
|
|
10
|
+
"calculate_lower_bounds",
|
|
11
|
+
"calculate_reachable_subset",
|
|
12
|
+
"generate_starting_point",
|
|
13
|
+
"infer_classifications",
|
|
14
|
+
"prune_by_average_linkage",
|
|
15
|
+
"solve_intermediate_solutions",
|
|
16
|
+
"solve_sub_problems",
|
|
17
|
+
"solve_group_sub_problems",
|
|
18
|
+
"voting_procedure",
|
|
19
|
+
"rpm_solve_solutions",
|
|
20
|
+
"rpm_intermediate_solutions",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
from .enautilus import (
|
|
24
|
+
ENautilusResult,
|
|
25
|
+
calculate_closeness,
|
|
26
|
+
calculate_intermediate_points,
|
|
27
|
+
calculate_lower_bounds,
|
|
28
|
+
calculate_reachable_subset,
|
|
29
|
+
enautilus_get_representative_solutions,
|
|
30
|
+
enautilus_step,
|
|
31
|
+
prune_by_average_linkage,
|
|
32
|
+
)
|
|
33
|
+
from .nimbus import (
|
|
34
|
+
NimbusError,
|
|
35
|
+
generate_starting_point,
|
|
36
|
+
infer_classifications,
|
|
37
|
+
solve_intermediate_solutions,
|
|
38
|
+
solve_sub_problems,
|
|
39
|
+
)
|
|
40
|
+
from .gnimbus import solve_group_sub_problems, voting_procedure
|
|
41
|
+
from .reference_point_method import rpm_solve_solutions, rpm_intermediate_solutions
|