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.
Files changed (182) hide show
  1. desdeo/__init__.py +8 -8
  2. desdeo/adm/ADMAfsar.py +551 -0
  3. desdeo/adm/ADMChen.py +414 -0
  4. desdeo/adm/BaseADM.py +119 -0
  5. desdeo/adm/__init__.py +11 -0
  6. desdeo/api/README.md +73 -0
  7. desdeo/api/__init__.py +15 -0
  8. desdeo/api/app.py +50 -0
  9. desdeo/api/config.py +90 -0
  10. desdeo/api/config.toml +64 -0
  11. desdeo/api/db.py +27 -0
  12. desdeo/api/db_init.py +85 -0
  13. desdeo/api/db_models.py +164 -0
  14. desdeo/api/malaga_db_init.py +27 -0
  15. desdeo/api/models/__init__.py +266 -0
  16. desdeo/api/models/archive.py +23 -0
  17. desdeo/api/models/emo.py +128 -0
  18. desdeo/api/models/enautilus.py +69 -0
  19. desdeo/api/models/gdm/gdm_aggregate.py +139 -0
  20. desdeo/api/models/gdm/gdm_base.py +69 -0
  21. desdeo/api/models/gdm/gdm_score_bands.py +114 -0
  22. desdeo/api/models/gdm/gnimbus.py +138 -0
  23. desdeo/api/models/generic.py +104 -0
  24. desdeo/api/models/generic_states.py +401 -0
  25. desdeo/api/models/nimbus.py +158 -0
  26. desdeo/api/models/preference.py +128 -0
  27. desdeo/api/models/problem.py +717 -0
  28. desdeo/api/models/reference_point_method.py +18 -0
  29. desdeo/api/models/session.py +49 -0
  30. desdeo/api/models/state.py +463 -0
  31. desdeo/api/models/user.py +52 -0
  32. desdeo/api/models/utopia.py +25 -0
  33. desdeo/api/routers/_EMO.backup +309 -0
  34. desdeo/api/routers/_NAUTILUS.py +245 -0
  35. desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
  36. desdeo/api/routers/_NIMBUS.py +765 -0
  37. desdeo/api/routers/__init__.py +5 -0
  38. desdeo/api/routers/emo.py +497 -0
  39. desdeo/api/routers/enautilus.py +237 -0
  40. desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
  41. desdeo/api/routers/gdm/gdm_base.py +420 -0
  42. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
  43. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
  44. desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
  45. desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
  46. desdeo/api/routers/generic.py +233 -0
  47. desdeo/api/routers/nimbus.py +705 -0
  48. desdeo/api/routers/problem.py +307 -0
  49. desdeo/api/routers/reference_point_method.py +93 -0
  50. desdeo/api/routers/session.py +100 -0
  51. desdeo/api/routers/test.py +16 -0
  52. desdeo/api/routers/user_authentication.py +520 -0
  53. desdeo/api/routers/utils.py +187 -0
  54. desdeo/api/routers/utopia.py +230 -0
  55. desdeo/api/schema.py +100 -0
  56. desdeo/api/tests/__init__.py +0 -0
  57. desdeo/api/tests/conftest.py +151 -0
  58. desdeo/api/tests/test_enautilus.py +330 -0
  59. desdeo/api/tests/test_models.py +1179 -0
  60. desdeo/api/tests/test_routes.py +1075 -0
  61. desdeo/api/utils/_database.py +263 -0
  62. desdeo/api/utils/_logger.py +29 -0
  63. desdeo/api/utils/database.py +36 -0
  64. desdeo/api/utils/emo_database.py +40 -0
  65. desdeo/core.py +34 -0
  66. desdeo/emo/__init__.py +159 -0
  67. desdeo/emo/hooks/archivers.py +188 -0
  68. desdeo/emo/methods/EAs.py +541 -0
  69. desdeo/emo/methods/__init__.py +0 -0
  70. desdeo/emo/methods/bases.py +12 -0
  71. desdeo/emo/methods/templates.py +111 -0
  72. desdeo/emo/operators/__init__.py +1 -0
  73. desdeo/emo/operators/crossover.py +1282 -0
  74. desdeo/emo/operators/evaluator.py +114 -0
  75. desdeo/emo/operators/generator.py +459 -0
  76. desdeo/emo/operators/mutation.py +1224 -0
  77. desdeo/emo/operators/scalar_selection.py +202 -0
  78. desdeo/emo/operators/selection.py +1778 -0
  79. desdeo/emo/operators/termination.py +286 -0
  80. desdeo/emo/options/__init__.py +108 -0
  81. desdeo/emo/options/algorithms.py +435 -0
  82. desdeo/emo/options/crossover.py +164 -0
  83. desdeo/emo/options/generator.py +131 -0
  84. desdeo/emo/options/mutation.py +260 -0
  85. desdeo/emo/options/repair.py +61 -0
  86. desdeo/emo/options/scalar_selection.py +66 -0
  87. desdeo/emo/options/selection.py +127 -0
  88. desdeo/emo/options/templates.py +383 -0
  89. desdeo/emo/options/termination.py +143 -0
  90. desdeo/explanations/__init__.py +6 -0
  91. desdeo/explanations/explainer.py +100 -0
  92. desdeo/explanations/utils.py +90 -0
  93. desdeo/gdm/__init__.py +22 -0
  94. desdeo/gdm/gdmtools.py +45 -0
  95. desdeo/gdm/score_bands.py +114 -0
  96. desdeo/gdm/voting_rules.py +50 -0
  97. desdeo/mcdm/__init__.py +41 -0
  98. desdeo/mcdm/enautilus.py +338 -0
  99. desdeo/mcdm/gnimbus.py +484 -0
  100. desdeo/mcdm/nautili.py +345 -0
  101. desdeo/mcdm/nautilus.py +477 -0
  102. desdeo/mcdm/nautilus_navigator.py +656 -0
  103. desdeo/mcdm/nimbus.py +417 -0
  104. desdeo/mcdm/pareto_navigator.py +269 -0
  105. desdeo/mcdm/reference_point_method.py +186 -0
  106. desdeo/problem/__init__.py +83 -0
  107. desdeo/problem/evaluator.py +561 -0
  108. desdeo/problem/external/__init__.py +18 -0
  109. desdeo/problem/external/core.py +356 -0
  110. desdeo/problem/external/pymoo_provider.py +266 -0
  111. desdeo/problem/external/runtime.py +44 -0
  112. desdeo/problem/gurobipy_evaluator.py +562 -0
  113. desdeo/problem/infix_parser.py +341 -0
  114. desdeo/problem/json_parser.py +944 -0
  115. desdeo/problem/pyomo_evaluator.py +487 -0
  116. desdeo/problem/schema.py +1829 -0
  117. desdeo/problem/simulator_evaluator.py +348 -0
  118. desdeo/problem/sympy_evaluator.py +244 -0
  119. desdeo/problem/testproblems/__init__.py +88 -0
  120. desdeo/problem/testproblems/benchmarks_server.py +120 -0
  121. desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
  122. desdeo/problem/testproblems/cake_problem.py +185 -0
  123. desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
  124. desdeo/problem/testproblems/dtlz2_problem.py +102 -0
  125. desdeo/problem/testproblems/forest_problem.py +283 -0
  126. desdeo/problem/testproblems/knapsack_problem.py +163 -0
  127. desdeo/problem/testproblems/mcwb_problem.py +831 -0
  128. desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
  129. desdeo/problem/testproblems/momip_problem.py +172 -0
  130. desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
  131. desdeo/problem/testproblems/nimbus_problem.py +143 -0
  132. desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
  133. desdeo/problem/testproblems/re_problem.py +492 -0
  134. desdeo/problem/testproblems/river_pollution_problems.py +440 -0
  135. desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
  136. desdeo/problem/testproblems/simple_problem.py +351 -0
  137. desdeo/problem/testproblems/simulator_problem.py +92 -0
  138. desdeo/problem/testproblems/single_objective.py +289 -0
  139. desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
  140. desdeo/problem/testproblems/zdt_problem.py +274 -0
  141. desdeo/problem/utils.py +245 -0
  142. desdeo/tools/GenerateReferencePoints.py +181 -0
  143. desdeo/tools/__init__.py +120 -0
  144. desdeo/tools/desc_gen.py +22 -0
  145. desdeo/tools/generics.py +165 -0
  146. desdeo/tools/group_scalarization.py +3090 -0
  147. desdeo/tools/gurobipy_solver_interfaces.py +258 -0
  148. desdeo/tools/indicators_binary.py +117 -0
  149. desdeo/tools/indicators_unary.py +362 -0
  150. desdeo/tools/interaction_schema.py +38 -0
  151. desdeo/tools/intersection.py +54 -0
  152. desdeo/tools/iterative_pareto_representer.py +99 -0
  153. desdeo/tools/message.py +265 -0
  154. desdeo/tools/ng_solver_interfaces.py +199 -0
  155. desdeo/tools/non_dominated_sorting.py +134 -0
  156. desdeo/tools/patterns.py +283 -0
  157. desdeo/tools/proximal_solver.py +99 -0
  158. desdeo/tools/pyomo_solver_interfaces.py +477 -0
  159. desdeo/tools/reference_vectors.py +229 -0
  160. desdeo/tools/scalarization.py +2065 -0
  161. desdeo/tools/scipy_solver_interfaces.py +454 -0
  162. desdeo/tools/score_bands.py +627 -0
  163. desdeo/tools/utils.py +388 -0
  164. desdeo/tools/visualizations.py +67 -0
  165. desdeo/utopia_stuff/__init__.py +0 -0
  166. desdeo/utopia_stuff/data/1.json +15 -0
  167. desdeo/utopia_stuff/data/2.json +13 -0
  168. desdeo/utopia_stuff/data/3.json +15 -0
  169. desdeo/utopia_stuff/data/4.json +17 -0
  170. desdeo/utopia_stuff/data/5.json +15 -0
  171. desdeo/utopia_stuff/from_json.py +40 -0
  172. desdeo/utopia_stuff/reinit_user.py +38 -0
  173. desdeo/utopia_stuff/utopia_db_init.py +212 -0
  174. desdeo/utopia_stuff/utopia_problem.py +403 -0
  175. desdeo/utopia_stuff/utopia_problem_old.py +415 -0
  176. desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
  177. desdeo-2.1.0.dist-info/METADATA +186 -0
  178. desdeo-2.1.0.dist-info/RECORD +180 -0
  179. {desdeo-1.2.dist-info → desdeo-2.1.0.dist-info}/WHEEL +1 -1
  180. desdeo-2.1.0.dist-info/licenses/LICENSE +21 -0
  181. desdeo-1.2.dist-info/METADATA +0 -16
  182. 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]
@@ -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