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.
Files changed (130) hide show
  1. desdeo/adm/ADMAfsar.py +551 -0
  2. desdeo/adm/ADMChen.py +414 -0
  3. desdeo/adm/BaseADM.py +119 -0
  4. desdeo/adm/__init__.py +11 -0
  5. desdeo/api/__init__.py +6 -6
  6. desdeo/api/app.py +38 -28
  7. desdeo/api/config.py +65 -44
  8. desdeo/api/config.toml +23 -12
  9. desdeo/api/db.py +10 -8
  10. desdeo/api/db_init.py +12 -6
  11. desdeo/api/models/__init__.py +220 -20
  12. desdeo/api/models/archive.py +16 -27
  13. desdeo/api/models/emo.py +128 -0
  14. desdeo/api/models/enautilus.py +69 -0
  15. desdeo/api/models/gdm/gdm_aggregate.py +139 -0
  16. desdeo/api/models/gdm/gdm_base.py +69 -0
  17. desdeo/api/models/gdm/gdm_score_bands.py +114 -0
  18. desdeo/api/models/gdm/gnimbus.py +138 -0
  19. desdeo/api/models/generic.py +104 -0
  20. desdeo/api/models/generic_states.py +401 -0
  21. desdeo/api/models/nimbus.py +158 -0
  22. desdeo/api/models/preference.py +44 -6
  23. desdeo/api/models/problem.py +274 -64
  24. desdeo/api/models/session.py +4 -1
  25. desdeo/api/models/state.py +419 -52
  26. desdeo/api/models/user.py +7 -6
  27. desdeo/api/models/utopia.py +25 -0
  28. desdeo/api/routers/_EMO.backup +309 -0
  29. desdeo/api/routers/_NIMBUS.py +6 -3
  30. desdeo/api/routers/emo.py +497 -0
  31. desdeo/api/routers/enautilus.py +237 -0
  32. desdeo/api/routers/gdm/gdm_aggregate.py +234 -0
  33. desdeo/api/routers/gdm/gdm_base.py +420 -0
  34. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_manager.py +398 -0
  35. desdeo/api/routers/gdm/gdm_score_bands/gdm_score_bands_routers.py +377 -0
  36. desdeo/api/routers/gdm/gnimbus/gnimbus_manager.py +698 -0
  37. desdeo/api/routers/gdm/gnimbus/gnimbus_routers.py +591 -0
  38. desdeo/api/routers/generic.py +233 -0
  39. desdeo/api/routers/nimbus.py +705 -0
  40. desdeo/api/routers/problem.py +201 -4
  41. desdeo/api/routers/reference_point_method.py +20 -44
  42. desdeo/api/routers/session.py +50 -26
  43. desdeo/api/routers/user_authentication.py +180 -26
  44. desdeo/api/routers/utils.py +187 -0
  45. desdeo/api/routers/utopia.py +230 -0
  46. desdeo/api/schema.py +10 -4
  47. desdeo/api/tests/conftest.py +94 -2
  48. desdeo/api/tests/test_enautilus.py +330 -0
  49. desdeo/api/tests/test_models.py +550 -72
  50. desdeo/api/tests/test_routes.py +902 -43
  51. desdeo/api/utils/_database.py +263 -0
  52. desdeo/api/utils/database.py +28 -266
  53. desdeo/api/utils/emo_database.py +40 -0
  54. desdeo/core.py +7 -0
  55. desdeo/emo/__init__.py +154 -24
  56. desdeo/emo/hooks/archivers.py +18 -2
  57. desdeo/emo/methods/EAs.py +128 -5
  58. desdeo/emo/methods/bases.py +9 -56
  59. desdeo/emo/methods/templates.py +111 -0
  60. desdeo/emo/operators/crossover.py +544 -42
  61. desdeo/emo/operators/evaluator.py +10 -14
  62. desdeo/emo/operators/generator.py +127 -24
  63. desdeo/emo/operators/mutation.py +212 -41
  64. desdeo/emo/operators/scalar_selection.py +202 -0
  65. desdeo/emo/operators/selection.py +956 -214
  66. desdeo/emo/operators/termination.py +124 -16
  67. desdeo/emo/options/__init__.py +108 -0
  68. desdeo/emo/options/algorithms.py +435 -0
  69. desdeo/emo/options/crossover.py +164 -0
  70. desdeo/emo/options/generator.py +131 -0
  71. desdeo/emo/options/mutation.py +260 -0
  72. desdeo/emo/options/repair.py +61 -0
  73. desdeo/emo/options/scalar_selection.py +66 -0
  74. desdeo/emo/options/selection.py +127 -0
  75. desdeo/emo/options/templates.py +383 -0
  76. desdeo/emo/options/termination.py +143 -0
  77. desdeo/gdm/__init__.py +22 -0
  78. desdeo/gdm/gdmtools.py +45 -0
  79. desdeo/gdm/score_bands.py +114 -0
  80. desdeo/gdm/voting_rules.py +50 -0
  81. desdeo/mcdm/__init__.py +23 -1
  82. desdeo/mcdm/enautilus.py +338 -0
  83. desdeo/mcdm/gnimbus.py +484 -0
  84. desdeo/mcdm/nautilus_navigator.py +7 -6
  85. desdeo/mcdm/reference_point_method.py +70 -0
  86. desdeo/problem/__init__.py +16 -11
  87. desdeo/problem/evaluator.py +4 -5
  88. desdeo/problem/external/__init__.py +18 -0
  89. desdeo/problem/external/core.py +356 -0
  90. desdeo/problem/external/pymoo_provider.py +266 -0
  91. desdeo/problem/external/runtime.py +44 -0
  92. desdeo/problem/gurobipy_evaluator.py +37 -12
  93. desdeo/problem/infix_parser.py +1 -16
  94. desdeo/problem/json_parser.py +7 -11
  95. desdeo/problem/pyomo_evaluator.py +25 -6
  96. desdeo/problem/schema.py +73 -55
  97. desdeo/problem/simulator_evaluator.py +65 -15
  98. desdeo/problem/testproblems/__init__.py +26 -11
  99. desdeo/problem/testproblems/benchmarks_server.py +120 -0
  100. desdeo/problem/testproblems/cake_problem.py +185 -0
  101. desdeo/problem/testproblems/dmitry_forest_problem_discrete.py +71 -0
  102. desdeo/problem/testproblems/forest_problem.py +77 -69
  103. desdeo/problem/testproblems/multi_valued_constraints.py +119 -0
  104. desdeo/problem/testproblems/{river_pollution_problem.py → river_pollution_problems.py} +28 -22
  105. desdeo/problem/testproblems/single_objective.py +289 -0
  106. desdeo/problem/testproblems/zdt_problem.py +4 -1
  107. desdeo/problem/utils.py +1 -1
  108. desdeo/tools/__init__.py +39 -21
  109. desdeo/tools/desc_gen.py +22 -0
  110. desdeo/tools/generics.py +22 -2
  111. desdeo/tools/group_scalarization.py +3090 -0
  112. desdeo/tools/indicators_binary.py +107 -1
  113. desdeo/tools/indicators_unary.py +3 -16
  114. desdeo/tools/message.py +33 -2
  115. desdeo/tools/non_dominated_sorting.py +4 -3
  116. desdeo/tools/patterns.py +9 -7
  117. desdeo/tools/pyomo_solver_interfaces.py +49 -36
  118. desdeo/tools/reference_vectors.py +118 -351
  119. desdeo/tools/scalarization.py +340 -1413
  120. desdeo/tools/score_bands.py +491 -328
  121. desdeo/tools/utils.py +117 -49
  122. desdeo/tools/visualizations.py +67 -0
  123. desdeo/utopia_stuff/utopia_problem.py +1 -1
  124. desdeo/utopia_stuff/utopia_problem_old.py +1 -1
  125. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/METADATA +47 -30
  126. desdeo-2.1.1.dist-info/RECORD +180 -0
  127. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info}/WHEEL +1 -1
  128. desdeo-2.0.0.dist-info/RECORD +0 -120
  129. /desdeo/api/utils/{logger.py → _logger.py} +0 -0
  130. {desdeo-2.0.0.dist-info → desdeo-2.1.1.dist-info/licenses}/LICENSE +0 -0
@@ -5,15 +5,14 @@ from collections.abc import Sequence
5
5
 
6
6
  import polars as pl
7
7
 
8
- from desdeo.problem import Evaluator, Problem
8
+ from desdeo.problem import Problem, SimulatorEvaluator
9
9
  from desdeo.tools.message import (
10
10
  EvaluatorMessageTopics,
11
- GenericMessage,
12
11
  IntMessage,
13
12
  Message,
14
13
  PolarsDataFrameMessage,
15
14
  )
16
- from desdeo.tools.patterns import Subscriber
15
+ from desdeo.tools.patterns import Publisher, Subscriber
17
16
 
18
17
 
19
18
  class EMOEvaluator(Subscriber):
@@ -41,23 +40,20 @@ class EMOEvaluator(Subscriber):
41
40
  """The topics that the Evaluator is interested in."""
42
41
  return []
43
42
 
44
- def __init__(
45
- self,
46
- problem: Problem,
47
- verbosity: int = 1,
48
- **kwargs,
49
- ):
43
+ def __init__(self, problem: Problem, verbosity: int, publisher: Publisher):
50
44
  """Initialize the EMOEvaluator class."""
51
- super().__init__(**kwargs)
45
+ super().__init__(
46
+ verbosity=verbosity,
47
+ publisher=publisher,
48
+ )
52
49
  self.problem = problem
53
50
  # TODO(@light-weaver, @gialmisi): This can be so much more efficient.
54
- self.evaluator = lambda x: Evaluator(problem).evaluate(
51
+ self.evaluator = lambda x: SimulatorEvaluator(problem).evaluate(
55
52
  {name.symbol: x[name.symbol].to_list() for name in problem.get_flattened_variables()}, flat=True
56
53
  )
57
54
  self.variable_symbols = [name.symbol for name in problem.variables]
58
55
  self.population: pl.DataFrame
59
- self.outs: pl.DataFrame
60
- self.verbosity: int = verbosity
56
+ self.out: pl.DataFrame
61
57
  self.new_evals: int = 0
62
58
 
63
59
  def evaluate(self, population: pl.DataFrame) -> pl.DataFrame:
@@ -72,7 +68,7 @@ class EMOEvaluator(Subscriber):
72
68
  self.population = population
73
69
  out = self.evaluator(population)
74
70
  # remove variable_symbols from the output
75
- self.out = out.drop(self.variable_symbols)
71
+ self.out = out.drop(self.variable_symbols, strict=False)
76
72
  self.new_evals = len(population)
77
73
  # merge the objectives and targets
78
74
 
@@ -10,7 +10,7 @@ from scipy.stats.qmc import LatinHypercube
10
10
  from desdeo.emo.operators.evaluator import EMOEvaluator
11
11
  from desdeo.problem import Problem, VariableTypeEnum
12
12
  from desdeo.tools.message import GeneratorMessageTopics, IntMessage, Message, PolarsDataFrameMessage
13
- from desdeo.tools.patterns import Subscriber
13
+ from desdeo.tools.patterns import Publisher, Subscriber
14
14
 
15
15
 
16
16
  class BaseGenerator(Subscriber):
@@ -42,9 +42,9 @@ class BaseGenerator(Subscriber):
42
42
  """Return the message topics that the generator is interested in."""
43
43
  return []
44
44
 
45
- def __init__(self, problem: Problem, **kwargs):
45
+ def __init__(self, problem: Problem, publisher: Publisher, verbosity: int):
46
46
  """Initialize the BaseGenerator class."""
47
- super().__init__(**kwargs)
47
+ super().__init__(publisher=publisher, verbosity=verbosity)
48
48
  self.problem = problem
49
49
  self.variable_symbols = [var.symbol for var in problem.get_flattened_variables()]
50
50
  self.bounds = np.array([[var.lowerbound, var.upperbound] for var in problem.get_flattened_variables()])
@@ -103,7 +103,9 @@ class RandomGenerator(BaseGenerator):
103
103
  distribution of the points is uniform. If the seed is not provided, the seed is set to 0.
104
104
  """
105
105
 
106
- def __init__(self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, **kwargs):
106
+ def __init__(
107
+ self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, verbosity: int, publisher: Publisher
108
+ ):
107
109
  """Initialize the RandomGenerator class.
108
110
 
109
111
  Args:
@@ -111,10 +113,11 @@ class RandomGenerator(BaseGenerator):
111
113
  evaluator (BaseEvaluator): The evaluator to evaluate the population.
112
114
  n_points (int): The number of points to generate for the initial population.
113
115
  seed (int): The seed for the random number generator.
114
- kwargs: Additional keyword arguments. Check the Subscriber class for more information.
115
- At the very least, the publisher argument should be provided.
116
+ verbosity (int): The verbosity level of the generator. A verbosity of 2 is needed if you want to maintain
117
+ an external archive. Otherwise, a verbosity of 1 is sufficient.
118
+ publisher (Publisher): The publisher to publish the messages.
116
119
  """
117
- super().__init__(problem, **kwargs)
120
+ super().__init__(problem, verbosity=verbosity, publisher=publisher)
118
121
  self.n_points = n_points
119
122
  self.evaluator = evaluator
120
123
  self.rng = np.random.default_rng(seed)
@@ -144,14 +147,16 @@ class RandomGenerator(BaseGenerator):
144
147
  """Update the generator based on the message."""
145
148
 
146
149
 
147
- class LHSGenerator(RandomGenerator):
150
+ class LHSGenerator(BaseGenerator):
148
151
  """Class for generating Latin Hypercube Sampling (LHS) initial population for the MOEAs.
149
152
 
150
153
  This class generates the initial population by using the Latin Hypercube Sampling (LHS) method.
151
154
  If the seed is not provided, the seed is set to 0.
152
155
  """
153
156
 
154
- def __init__(self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, **kwargs):
157
+ def __init__(
158
+ self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, verbosity: int, publisher: Publisher
159
+ ):
155
160
  """Initialize the LHSGenerator class.
156
161
 
157
162
  Args:
@@ -159,10 +164,13 @@ class LHSGenerator(RandomGenerator):
159
164
  evaluator (BaseEvaluator): The evaluator to evaluate the population.
160
165
  n_points (int): The number of points to generate for the initial population.
161
166
  seed (int): The seed for the random number generator.
162
- kwargs: Additional keyword arguments. Check the Subscriber class for more information.
163
- At the very least, the publisher argument should be provided.
167
+ verbosity (int): The verbosity level of the generator. A verbosity of 2 is needed if you want to maintain
168
+ an external archive. Otherwise, a verbosity of 1 is sufficient.
169
+ publisher (Publisher): The publisher to publish the messages.
164
170
  """
165
- super().__init__(problem, evaluator, n_points, seed, **kwargs)
171
+ super().__init__(problem, verbosity=verbosity, publisher=publisher)
172
+ self.n_points = n_points
173
+ self.evaluator = evaluator
166
174
  self.lhsrng = LatinHypercube(d=len(self.variable_symbols), seed=seed)
167
175
  self.seed = seed
168
176
 
@@ -196,7 +204,9 @@ class RandomBinaryGenerator(BaseGenerator):
196
204
  This class generates an initial population by randomly setting variable values to be either 0 or 1.
197
205
  """
198
206
 
199
- def __init__(self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, **kwargs):
207
+ def __init__(
208
+ self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, verbosity: int, publisher: Publisher
209
+ ):
200
210
  """Initialize the RandomBinaryGenerator class.
201
211
 
202
212
  Args:
@@ -204,10 +214,11 @@ class RandomBinaryGenerator(BaseGenerator):
204
214
  evaluator (BaseEvaluator): The evaluator to evaluate the population.
205
215
  n_points (int): The number of points to generate for the initial population.
206
216
  seed (int): The seed for the random number generator.
207
- kwargs: Additional keyword arguments. Check the Subscriber class for more information.
208
- At the very least, the publisher argument should be provided.
217
+ verbosity (int): The verbosity level of the generator. A verbosity of 2 is needed if you want to maintain
218
+ an external archive. Otherwise, a verbosity of 1 is sufficient.
219
+ publisher (Publisher): The publisher to publish the messages.
209
220
  """
210
- super().__init__(problem, **kwargs)
221
+ super().__init__(problem, verbosity=verbosity, publisher=publisher)
211
222
  self.n_points = n_points
212
223
  self.evaluator = evaluator
213
224
  self.rng = np.random.default_rng(seed)
@@ -244,7 +255,9 @@ class RandomIntegerGenerator(BaseGenerator):
244
255
  the variables.
245
256
  """
246
257
 
247
- def __init__(self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, **kwargs):
258
+ def __init__(
259
+ self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, verbosity: int, publisher: Publisher
260
+ ):
248
261
  """Initialize the RandomIntegerGenerator class.
249
262
 
250
263
  Args:
@@ -252,10 +265,11 @@ class RandomIntegerGenerator(BaseGenerator):
252
265
  evaluator (BaseEvaluator): The evaluator to evaluate the population.
253
266
  n_points (int): The number of points to generate for the initial population.
254
267
  seed (int): The seed for the random number generator.
255
- kwargs: Additional keyword arguments. Check the Subscriber class for more information.
256
- At the very least, the publisher argument should be provided.
268
+ verbosity (int): The verbosity level of the generator. A verbosity of 2 is needed if you want to maintain
269
+ an external archive. Otherwise, a verbosity of 1 is sufficient.
270
+ publisher (Publisher): The publisher to publish the messages.
257
271
  """
258
- super().__init__(problem, **kwargs)
272
+ super().__init__(problem, verbosity=verbosity, publisher=publisher)
259
273
  self.n_points = n_points
260
274
  self.evaluator = evaluator
261
275
  self.rng = np.random.default_rng(seed)
@@ -297,7 +311,9 @@ class RandomMixedIntegerGenerator(BaseGenerator):
297
311
  values to be integers or floats between the bounds of the variables.
298
312
  """
299
313
 
300
- def __init__(self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, **kwargs):
314
+ def __init__(
315
+ self, problem: Problem, evaluator: EMOEvaluator, n_points: int, seed: int, verbosity: int, publisher: Publisher
316
+ ):
301
317
  """Initialize the RandomMixedIntegerGenerator class.
302
318
 
303
319
  Args:
@@ -305,10 +321,11 @@ class RandomMixedIntegerGenerator(BaseGenerator):
305
321
  evaluator (BaseEvaluator): The evaluator to evaluate the population.
306
322
  n_points (int): The number of points to generate for the initial population.
307
323
  seed (int): The seed for the random number generator.
308
- kwargs: Additional keyword arguments. Check the Subscriber class for more information.
309
- At the very least, the publisher argument should be provided.
324
+ verbosity (int): The verbosity level of the generator. A verbosity of 2 is needed if you want to maintain
325
+ an external archive. Otherwise, a verbosity of 1 is sufficient.
326
+ publisher (Publisher): The publisher to publish the messages.
310
327
  """
311
- super().__init__(problem, **kwargs)
328
+ super().__init__(problem, verbosity=verbosity, publisher=publisher)
312
329
  self.var_symbol_types = {
313
330
  VariableTypeEnum.real: [
314
331
  var.symbol for var in problem.variables if var.variable_type == VariableTypeEnum.real
@@ -354,3 +371,89 @@ class RandomMixedIntegerGenerator(BaseGenerator):
354
371
 
355
372
  def update(self, message) -> None:
356
373
  """Update the generator based on the message."""
374
+
375
+
376
+ class ArchiveGenerator(BaseGenerator):
377
+ """Class for getting initial population from an archive."""
378
+
379
+ def __init__(
380
+ self,
381
+ problem: Problem,
382
+ evaluator: EMOEvaluator,
383
+ publisher: Publisher,
384
+ verbosity: int,
385
+ solutions: pl.DataFrame,
386
+ **kwargs, # just to dump seed
387
+ ):
388
+ """Initialize the ArchiveGenerator class.
389
+
390
+ Args:
391
+ problem (Problem): The problem to solve.
392
+ evaluator (BaseEvaluator): The evaluator to evaluate the population. Only used to check that the outputs
393
+ have the correct variables.
394
+ publisher (Publisher): The publisher to publish the messages.
395
+ verbosity (int): The verbosity level of the generator. A verbosity of 2 is needed if you want to maintain
396
+ an external archive. Otherwise, a verbosity of 1 is sufficient.
397
+ solutions (pl.DataFrame): The decision variable vectors to use as the initial population.
398
+ """
399
+ super().__init__(problem, verbosity=verbosity, publisher=publisher)
400
+ if not isinstance(solutions, pl.DataFrame):
401
+ raise ValueError("The solutions must be a polars DataFrame.")
402
+ if solutions.shape[0] == 0:
403
+ raise ValueError("The solutions DataFrame is empty.")
404
+ self.solutions = solutions
405
+ # self.outputs = outputs
406
+ if not set(self.solutions.columns) == set(self.variable_symbols):
407
+ raise ValueError("The solutions DataFrame must have the same columns as the problem variables.")
408
+ # TODO: Check that the outputs have the correct columns
409
+ self.evaluator = evaluator
410
+
411
+ def do(self) -> tuple[pl.DataFrame, pl.DataFrame]:
412
+ """Get the initial population from the archive.
413
+
414
+ Returns:
415
+ tuple[pl.DataFrame, pl.DataFrame]: The initial population as the first element,
416
+ the corresponding objectives, the constraint violations, and the targets as the second element.
417
+ """
418
+ self.outputs = self.evaluator.evaluate(self.solutions)
419
+ self.notify()
420
+ return self.solutions, self.outputs
421
+
422
+ def state(self) -> Sequence[Message]:
423
+ """Return the state of the generator.
424
+
425
+ This method overrides the state method of the BaseGenerator class, because the solutions and outputs are
426
+ already provided and not generated by the generator.
427
+
428
+ Returns:
429
+ dict: The state of the generator.
430
+ """
431
+ # TODO: Should we do it like this? Or just do super().state()?
432
+ # Maybe saying that zero evaluations have been done is misleading?
433
+ # idk
434
+ if self.verbosity == 0:
435
+ return []
436
+ if self.verbosity == 1:
437
+ return [
438
+ IntMessage(
439
+ topic=GeneratorMessageTopics.NEW_EVALUATIONS,
440
+ value=0,
441
+ source=self.__class__.__name__,
442
+ ),
443
+ ]
444
+ # verbosity == 2
445
+ return [
446
+ PolarsDataFrameMessage(
447
+ topic=GeneratorMessageTopics.VERBOSE_OUTPUTS,
448
+ value=pl.concat([self.solutions, self.outputs], how="horizontal"),
449
+ source=self.__class__.__name__,
450
+ ),
451
+ IntMessage(
452
+ topic=GeneratorMessageTopics.NEW_EVALUATIONS,
453
+ value=0,
454
+ source=self.__class__.__name__,
455
+ ),
456
+ ]
457
+
458
+ def update(self, message) -> None:
459
+ """Update the generator based on the message."""