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,29 +5,38 @@ import subprocess
5
5
  import sys
6
6
  from inspect import getfullargspec
7
7
  from pathlib import Path
8
+ from urllib.parse import urlparse
8
9
 
9
10
  import joblib
10
11
  import numpy as np
11
12
  import polars as pl
12
- # import skops.io as sio
13
+ import requests
13
14
 
14
15
  from desdeo.problem import (
16
+ MathParser,
15
17
  ObjectiveTypeEnum,
16
18
  PolarsEvaluator,
17
19
  PolarsEvaluatorModesEnum,
18
20
  Problem,
19
21
  )
22
+ from desdeo.problem.external import ProviderParams, get_resolver, supported_schemes
23
+
24
+ # external resolver to resolve providers for problems defined externally of DESDEO
25
+ _external_resolver = get_resolver()
20
26
 
21
27
 
22
28
  class EvaluatorError(Exception):
23
29
  """Error raised when exceptions are encountered in an Evaluator."""
24
30
 
25
31
 
26
- class Evaluator:
32
+ class SimulatorEvaluator:
27
33
  """A class for creating evaluators for simulator based and surrogate based objectives, constraints and extras."""
28
34
 
29
- def __init__(
30
- self, problem: Problem, params: dict[str, dict] | None = None, surrogate_paths: dict[str, Path] | None = None
35
+ def __init__( # noqa: PLR0912
36
+ self,
37
+ problem: Problem,
38
+ params: dict[str, dict] | ProviderParams | None = None,
39
+ surrogate_paths: dict[str, Path] | None = None,
31
40
  ):
32
41
  """Creating an evaluator for simulator based and surrogate based objectives, constraints and extras.
33
42
 
@@ -62,6 +71,15 @@ class Evaluator:
62
71
  obj.symbol
63
72
  for obj in list(filter(lambda x: x.objective_type == ObjectiveTypeEnum.surrogate, problem.objectives))
64
73
  ]
74
+ if problem.scalarization_funcs is not None:
75
+ parser = MathParser()
76
+ self.scalarization_funcs = [
77
+ (func.symbol, parser.parse(func.func))
78
+ for func in problem.scalarization_funcs
79
+ if func.symbol is not None
80
+ ]
81
+ else:
82
+ self.scalarization_funcs = []
65
83
  # Gather any constraints' symbols
66
84
  if problem.constraints is not None:
67
85
  self.analytical_symbols = self.analytical_symbols + [
@@ -93,7 +111,10 @@ class Evaluator:
93
111
 
94
112
  # Gather the possible simulators
95
113
  self.simulators = problem.simulators if problem.simulators is not None else []
114
+
96
115
  # Gather the possibly given parameters
116
+ if params and not isinstance(params, dict):
117
+ params = params.model_dump()
97
118
  self.params = {}
98
119
  for sim in self.simulators:
99
120
  sim_params = params.get(sim.name, {}) if params is not None else {}
@@ -136,15 +157,37 @@ class Evaluator:
136
157
  for sim in self.simulators:
137
158
  # gather the possible parameters for the simulator
138
159
  params = self.params.get(sim.name, {})
139
- # call the simulator with the decision variable values and parameters as dicts
140
- res = subprocess.run(
141
- [sys.executable, sim.file, "-d", str(xs), "-p", str(params)], capture_output=True, text=True
142
- )
143
- if res.returncode == 0:
144
- # gather the simulation results (a dict) into the results dataframe
145
- res_df = res_df.hstack(pl.DataFrame(json.loads(res.stdout)))
146
- else:
147
- raise EvaluatorError(res.stderr)
160
+ if sim.file is not None:
161
+ # call the simulator with the decision variable values and parameters as dicts
162
+ res = subprocess.run(
163
+ [sys.executable, sim.file, "-d", str(xs), "-p", str(params)], capture_output=True, text=True
164
+ )
165
+ if res.returncode == 0:
166
+ # gather the simulation results (a dict) into the results dataframe
167
+ res_df = res_df.hstack(pl.DataFrame(json.loads(res.stdout)))
168
+ else:
169
+ raise EvaluatorError(res.stderr)
170
+ elif sim.url is not None:
171
+ # call the endpoint
172
+ try:
173
+ if isinstance(xs, pl.DataFrame):
174
+ # if xs is a polars dataframe, convert it to a dict
175
+ xs = xs.to_dict(as_series=False)
176
+ scheme = urlparse(sim.url.url).scheme
177
+ if scheme in supported_schemes:
178
+ # desdeo
179
+ res = _external_resolver.evaluate(sim.url.url, params, xs)
180
+ res_df = res_df.hstack(pl.DataFrame(res))
181
+ # parse res
182
+ else:
183
+ # http, https, etc...
184
+ res = requests.get(sim.url.url, auth=sim.url.auth, json={"d": xs, "p": params})
185
+ res.raise_for_status() # raise an error if the request failed
186
+ res_df = res_df.hstack(pl.DataFrame(res.json()))
187
+ except requests.RequestException as e:
188
+ raise EvaluatorError(
189
+ f"Failed to call the simulator at {sim.url}. Is the simulator server running?"
190
+ ) from e
148
191
 
149
192
  # Evaluate the minimization form of the objective functions
150
193
  min_obj_columns = pl.DataFrame()
@@ -153,7 +196,11 @@ class Evaluator:
153
196
  min_obj_columns = min_obj_columns.hstack(
154
197
  res_df.select((min_max_mult * pl.col(f"{symbol}")).alias(f"{symbol}_min"))
155
198
  )
156
- return res_df.hstack(min_obj_columns)
199
+
200
+ res_df = res_df.hstack(min_obj_columns)
201
+ # If there are scalarization functions, evaluate them as well
202
+ scalarization_columns = res_df.select(*[expr.alias(symbol) for symbol, expr in self.scalarization_funcs])
203
+ return res_df.hstack(scalarization_columns)
157
204
 
158
205
  def _evaluate_surrogates(self, xs: dict[str, list[int | float]]) -> pl.DataFrame:
159
206
  """Evaluate the problem for the given decision variables using the surrogate models.
@@ -194,7 +241,10 @@ class Evaluator:
194
241
  min_obj_columns = min_obj_columns.hstack(
195
242
  res.select((min_max_mult * pl.col(f"{symbol}")).alias(f"{symbol}_min"))
196
243
  )
197
- return res.hstack(min_obj_columns)
244
+ res_df = res.hstack(min_obj_columns)
245
+ # If there are scalarization functions, evaluate them as well
246
+ scalarization_columns = res_df.select(*[expr.alias(symbol) for symbol, expr in self.scalarization_funcs])
247
+ return res_df.hstack(scalarization_columns)
198
248
 
199
249
  def _load_surrogates(self, surrogate_paths: dict[str, Path] | None = None):
200
250
  """Load the surrogate models from disk and store them within the evaluator.
@@ -4,15 +4,21 @@ Pre-defined problems for, e.g.,
4
4
  testing and illustration purposed are defined here.
5
5
  """
6
6
 
7
- __all__ = [
7
+ __all__ = [ # noqa: RUF022
8
8
  "binh_and_korn",
9
9
  "dtlz2",
10
10
  "forest_problem",
11
11
  "forest_problem_discrete",
12
- "simple_knapsack",
13
- "simple_knapsack_vectors",
12
+ "mcwb_equilateral_tbeam_problem",
13
+ "mcwb_hollow_rectangular_problem",
14
+ "mcwb_ragsdell1976_problem",
15
+ "mcwb_solid_rectangular_problem",
16
+ "mcwb_square_channel_problem",
17
+ "mcwb_tapered_channel_problem",
18
+ "mixed_variable_dimensions_problem",
14
19
  "momip_ti2",
15
20
  "momip_ti7",
21
+ "multi_valued_constraint_problem",
16
22
  "nimbus_test_problem",
17
23
  "pareto_navigator_test_problem",
18
24
  "re21",
@@ -22,8 +28,11 @@ __all__ = [
22
28
  "river_pollution_problem",
23
29
  "river_pollution_problem_discrete",
24
30
  "river_pollution_scenario",
31
+ "rocket_injector_design",
25
32
  "simple_data_problem",
26
33
  "simple_integer_test_problem",
34
+ "simple_knapsack",
35
+ "simple_knapsack_vectors",
27
36
  "simple_linear_test_problem",
28
37
  "simple_scenario_test_problem",
29
38
  "simple_test_problem",
@@ -33,22 +42,32 @@ __all__ = [
33
42
  "zdt1",
34
43
  "zdt2",
35
44
  "zdt3",
36
- "rocket_injector_design",
37
- "mixed_variable_dimensions_problem",
38
- "mcwb_solid_rectangular_problem"
45
+ "best_cake_problem",
46
+ "dmitry_forest_problem_disc",
39
47
  ]
40
48
 
41
49
 
42
50
  from .binh_and_korn_problem import binh_and_korn
51
+ from .cake_problem import best_cake_problem
52
+ from .dmitry_forest_problem_discrete import dmitry_forest_problem_disc
43
53
  from .dtlz2_problem import dtlz2
44
54
  from .forest_problem import forest_problem, forest_problem_discrete
45
55
  from .knapsack_problem import simple_knapsack, simple_knapsack_vectors
56
+ from .mcwb_problem import (
57
+ mcwb_equilateral_tbeam_problem,
58
+ mcwb_hollow_rectangular_problem,
59
+ mcwb_ragsdell1976_problem,
60
+ mcwb_solid_rectangular_problem,
61
+ mcwb_square_channel_problem,
62
+ mcwb_tapered_channel_problem,
63
+ )
46
64
  from .mixed_variable_dimenrions_problem import mixed_variable_dimensions_problem
47
65
  from .momip_problem import momip_ti2, momip_ti7
66
+ from .multi_valued_constraints import multi_valued_constraint_problem
48
67
  from .nimbus_problem import nimbus_test_problem
49
68
  from .pareto_navigator_problem import pareto_navigator_test_problem
50
69
  from .re_problem import re21, re22, re23, re24
51
- from .river_pollution_problem import (
70
+ from .river_pollution_problems import (
52
71
  river_pollution_problem,
53
72
  river_pollution_problem_discrete,
54
73
  river_pollution_scenario,
@@ -67,7 +86,3 @@ from .spanish_sustainability_problem import (
67
86
  spanish_sustainability_problem_discrete,
68
87
  )
69
88
  from .zdt_problem import zdt1, zdt2, zdt3
70
-
71
- from .mcwb_problem import (mcwb_solid_rectangular_problem, mcwb_hollow_rectangular_problem,
72
- mcwb_equilateral_tbeam_problem, mcwb_square_channel_problem, mcwb_tapered_channel_problem,
73
- mcwb_ragsdell1976_problem)
@@ -0,0 +1,120 @@
1
+ # A FastAPI server to expose pymoo benchmark problems
2
+ from typing import Any
3
+
4
+ import polars as pl
5
+ import requests
6
+ from fastapi import FastAPI
7
+ from pydantic import BaseModel
8
+ from pymoo.problems import get_problem
9
+
10
+ from desdeo.problem.schema import Objective, Problem, Simulator, Url, Variable
11
+
12
+
13
+ class PymooParameters(BaseModel):
14
+ """Parameters for a pymoo problem instance."""
15
+
16
+ name: str
17
+ n_var: int
18
+ n_obj: int
19
+ minus: bool = False
20
+
21
+
22
+ class ProblemInfo(BaseModel):
23
+ """Information about a pymoo problem instance."""
24
+
25
+ lower_bounds: dict[str, float]
26
+ """Lower bounds of the decision variables. Keys are the names of the decision variables, e.g. "x_1", "x_2", etc."""
27
+ upper_bounds: dict[str, float]
28
+ """Upper bounds of the decision variables."""
29
+ objective_names: list[str]
30
+
31
+
32
+ app = FastAPI()
33
+
34
+
35
+ def get_pymoo_problem(p: PymooParameters):
36
+ """Get a pymoo problem instance by name, number of variables, and number of objectives."""
37
+ params = p.model_dump()
38
+ params.pop("minus")
39
+ return get_problem(**params)
40
+
41
+
42
+ @app.get("/evaluate")
43
+ def evaluate(d: dict[str, list[float]], p: PymooParameters) -> dict[str, Any]:
44
+ """Evaluate a pymoo problem instance with given parameters and input values."""
45
+ problem = get_pymoo_problem(p)
46
+
47
+ xs_df = pl.DataFrame(d)
48
+
49
+ output = problem.evaluate(xs_df.to_numpy())
50
+ output_df = pl.DataFrame(output, schema=[f"f_{i + 1}" for i in range(problem.n_obj)])
51
+
52
+ return d | output_df.to_dict(as_series=False)
53
+
54
+
55
+ @app.get("/info")
56
+ def info(p: PymooParameters) -> ProblemInfo:
57
+ """Get information about a pymoo problem instance, including bounds and objective names."""
58
+ problem = get_pymoo_problem(p)
59
+ bounds = problem.bounds()
60
+
61
+ return ProblemInfo(
62
+ lower_bounds={f"x_{i + 1}": bounds[0][i] for i in range(problem.n_var)},
63
+ upper_bounds={f"x_{i + 1}": bounds[1][i] for i in range(problem.n_var)},
64
+ objective_names=[f"f_{i + 1}" for i in range(problem.n_obj)],
65
+ )
66
+
67
+
68
+ url = "http://127.0.0.1"
69
+ port = 8000
70
+
71
+
72
+ def server_problem(parameters: PymooParameters) -> Problem:
73
+ """Create a Problem instance from pymoo parameters."""
74
+ try:
75
+ info = requests.get(url + f":{port}/info", json=parameters.model_dump())
76
+ info.raise_for_status()
77
+ except requests.RequestException as e:
78
+ raise RuntimeError("Failed to fetch problem info. Is the server running?") from e
79
+ info: ProblemInfo = ProblemInfo.model_validate(info.json())
80
+
81
+ simulator_url = Url(url=f"{url}:{port}/evaluate")
82
+
83
+ return Problem(
84
+ name=parameters.name,
85
+ description=f"Problem {parameters.name} with {parameters.n_var} variables and {parameters.n_obj} objectives.",
86
+ variables=[
87
+ Variable(
88
+ name=f"x_{i + 1}",
89
+ symbol=f"x_{i + 1}",
90
+ lowerbound=info.lower_bounds[f"x_{i + 1}"],
91
+ upperbound=info.upper_bounds[f"x_{i + 1}"],
92
+ variable_type="real",
93
+ )
94
+ for i in range(parameters.n_var)
95
+ ],
96
+ objectives=[
97
+ Objective(
98
+ name=f"f_{i + 1}",
99
+ symbol=f"f_{i + 1}",
100
+ simulator_path=simulator_url,
101
+ objective_type="simulator",
102
+ maximize=parameters.minus,
103
+ )
104
+ for i in range(parameters.n_obj)
105
+ ],
106
+ simulators=[
107
+ Simulator(
108
+ name="s1",
109
+ symbol="s1",
110
+ url=simulator_url,
111
+ parameter_options=parameters.model_dump(),
112
+ )
113
+ ],
114
+ )
115
+
116
+
117
+ if __name__ == "__main__":
118
+ import uvicorn
119
+
120
+ uvicorn.run(app)
@@ -0,0 +1,185 @@
1
+ """Defines the 'best cake problem'."""
2
+
3
+ from desdeo.problem.schema import (
4
+ Constant,
5
+ Objective,
6
+ ObjectiveTypeEnum,
7
+ Problem,
8
+ Variable,
9
+ VariableTypeEnum,
10
+ )
11
+
12
+ PI = 3.14159265358979323846
13
+
14
+
15
+ ## Helper func
16
+ def U(z: float):
17
+ return 4.0 * z * (1.0 - z)
18
+
19
+
20
+ ## Helper funcs to return string representations
21
+
22
+
23
+ def bowl_str(z: str, a: str, invD: str) -> str:
24
+ tmp: str = f"({z} - {a})*{invD}"
25
+ return f"({tmp}*{tmp})"
26
+ # return f"{clamp01_str(f"{tmp}*{tmp}")}"
27
+
28
+
29
+ def U_str(z: str) -> str:
30
+ tmp: str = f"(4*{z}*(1.0 - {z}))"
31
+ return f"({tmp}*{tmp})"
32
+
33
+
34
+ def ripple_str(t: str) -> str:
35
+ tmp: str = f"Sin({PI} * {t})"
36
+ return f"({tmp}*{tmp})"
37
+
38
+
39
+ # Objective function string representations
40
+ def f0_str() -> str:
41
+ yliq: str = "(0.5*x5 + 0.3*x4 + 0.2*x3)"
42
+ v: str = (
43
+ f"(0.4 * {bowl_str('x1', 'T1', 'INV_D1')}) + "
44
+ f"(0.4 * {bowl_str(yliq, 'Y_LIQ_STAR', 'INV_D_YLIQ')}) + "
45
+ f" (0.2 * {ripple_str('((x1 + x6) - (T1 + T6))')})"
46
+ )
47
+ return f"14*({v})"
48
+
49
+
50
+ def f1_str() -> str:
51
+ sbar: str = "((x2 + 0.5*x3)/1.5)"
52
+ w25: str = f"({U_str('x2')}*{U_str('x5')})"
53
+ d25: str = f"(({w25} - W25_STAR)*INV_DW25)"
54
+ v: str = f"(0.4*{bowl_str('x2', 'T2', 'INV_D2')}) + (0.3*{ripple_str(f'{sbar} - SBAR_STAR')}) +(0.3*{d25}*{d25})"
55
+ return f"14*({v})"
56
+
57
+
58
+ def f2_str() -> str:
59
+ v: str = (
60
+ f"(0.35*{bowl_str('x6', 'T6', 'INV_D6')}) + "
61
+ f"(0.25*{bowl_str('x4', 'T4', 'INV_D4')}) + "
62
+ f"(0.4*{ripple_str('((x6 - 0.5*x4) - (T6 - 0.5*T4))')})"
63
+ )
64
+ return f"14*({v})"
65
+
66
+
67
+ def f3_str() -> str:
68
+ w35: str = f"({U_str('x3')}*{U_str('x5')})"
69
+ d35: str = f"(({w35} - W35_STAR) * INV_DW35)"
70
+ v: str = f"(0.3*{bowl_str('x3', 'T3', 'INV_D3')}) + (0.3*{bowl_str('x5', 'T5', 'INV_D5')}) + (0.4*({d35}*{d35}))"
71
+ return f"14*({v})"
72
+
73
+
74
+ def f4_str() -> str:
75
+ v: str = (
76
+ f"(0.25*{bowl_str('x2', 'T2', 'INV_D2')}) + "
77
+ f"(0.25*{bowl_str('x3', 'T3', 'INV_D3')}) + "
78
+ f"(0.20*{ripple_str('(x4 - T4)')}) + "
79
+ f"(0.30*{ripple_str('((x2 - x5) - (T2 - T5))')})"
80
+ )
81
+ return f"14*({v})"
82
+
83
+
84
+ ## The cake problem
85
+ def best_cake_problem() -> Problem:
86
+ """Defines the best cake problem."""
87
+ variable_inits = [
88
+ ("Flour", 0.70),
89
+ ("Sugar", 0.10),
90
+ ("Butter", 0.40),
91
+ ("Eggs", 0.50),
92
+ ("Milk", 0.20),
93
+ ("Baking powder", 0.80),
94
+ ]
95
+ variables = [
96
+ Variable(
97
+ name=var[0],
98
+ symbol=f"x{i + 1}",
99
+ variable_type=VariableTypeEnum.real,
100
+ lowerbound=0.0,
101
+ upperbound=1.0,
102
+ initial_value=var[1],
103
+ )
104
+ for i, var in enumerate(variable_inits)
105
+ ]
106
+
107
+ constants_init = [
108
+ ("T1", 0.60),
109
+ ("T2", 0.35),
110
+ ("T3", 0.25),
111
+ ("T4", 0.30),
112
+ ("T5", 0.35),
113
+ ("T6", 0.40),
114
+ ("INV_D1", 1.0 / 0.60),
115
+ ("INV_D2", 1.0 / 0.65),
116
+ ("INV_D3", 1.0 / 0.75),
117
+ ("INV_D4", 1.0 / 0.70),
118
+ ("INV_D5", 1.0 / 0.65),
119
+ ("INV_D6", 1.0 / 0.60),
120
+ ("Y_LIQ_STAR", 0.5 * 0.35 + 0.3 * 0.30 + 0.2 * 0.25),
121
+ ("INV_D_YLIQ", 1.0 / 0.685),
122
+ ("SBAR_STAR", (0.35 + 0.5 * 0.25) / 1.5),
123
+ ("W25_STAR", U(0.35) * U(0.35)),
124
+ ("INV_DW25", 1.0 / 0.8281),
125
+ ("W35_STAR", U(0.25) * U(0.35)),
126
+ ("INV_DW35", 1.0 / 0.6825),
127
+ ]
128
+
129
+ constants = [Constant(name=const[0], symbol=const[0], value=const[1]) for const in constants_init]
130
+
131
+ objectives = [
132
+ Objective(
133
+ name="Dry/crumb error",
134
+ symbol="dry_crumb",
135
+ func=f0_str(),
136
+ ideal=0.0,
137
+ nadir=14.0,
138
+ objective_type=ObjectiveTypeEnum.analytical,
139
+ is_twice_differentiable=True, # right?
140
+ ),
141
+ Objective(
142
+ name="Sweetness/texture off-target",
143
+ symbol="sweet_texture",
144
+ func=f1_str(),
145
+ ideal=0.0,
146
+ nadir=14.0,
147
+ objective_type=ObjectiveTypeEnum.analytical,
148
+ is_twice_differentiable=True,
149
+ ),
150
+ Objective(
151
+ name="Rise/collapse risk",
152
+ symbol="rise_collapse",
153
+ func=f2_str(),
154
+ ideal=0.0,
155
+ nadir=14.0,
156
+ objective_type=ObjectiveTypeEnum.analytical,
157
+ is_twice_differentiable=True,
158
+ ),
159
+ Objective(
160
+ name="Moistness/grease imbalance",
161
+ symbol="moistness_grease",
162
+ func=f3_str(),
163
+ ideal=0.0,
164
+ nadir=14.0,
165
+ objective_type=ObjectiveTypeEnum.analytical,
166
+ is_twice_differentiable=True,
167
+ ),
168
+ Objective(
169
+ name="Browning/burn risk",
170
+ symbol="browning_burn",
171
+ func=f4_str(),
172
+ ideal=0.0,
173
+ nadir=14.0,
174
+ objective_type=ObjectiveTypeEnum.analytical,
175
+ is_twice_differentiable=True,
176
+ ),
177
+ ]
178
+
179
+ return Problem(
180
+ name="Cake problem",
181
+ description="Try to find the most delicious cake!",
182
+ constants=constants,
183
+ variables=variables,
184
+ objectives=objectives,
185
+ )
@@ -0,0 +1,71 @@
1
+ """A forest problem with discrete representation."""
2
+ from pathlib import Path
3
+
4
+ import polars as pl
5
+
6
+ from desdeo.problem.schema import (
7
+ DiscreteRepresentation,
8
+ Objective,
9
+ ObjectiveTypeEnum,
10
+ Problem,
11
+ Variable,
12
+ VariableTypeEnum,
13
+ )
14
+
15
+
16
+ def dmitry_forest_problem_disc() -> Problem:
17
+ """Implements the dmitry forest problem using Pareto front representation.
18
+
19
+ Returns:
20
+ Problem: A problem instance representing the forest problem.
21
+ """
22
+ path = Path(__file__)
23
+ while not str(path).endswith("/DESDEO"):
24
+ path = path.parent
25
+
26
+ path = path / "tests/data/dmitry_discrete_repr/dmitry_forest_problem_non_dom_solns.csv"
27
+
28
+ obj_names = ["Rev", "HA", "Carb", "DW"]
29
+
30
+ var_name = "index"
31
+
32
+ data = pl.read_csv(
33
+ path, has_header=True, columns=["Rev", "HA", "Carb", "DW"], separator=",", #decimal_comma=True
34
+ )
35
+
36
+ variables = [
37
+ Variable(
38
+ name=var_name,
39
+ symbol=var_name,
40
+ variable_type=VariableTypeEnum.integer,
41
+ lowerbound=0,
42
+ upperbound=len(data) - 1,
43
+ initial_value=0,
44
+ )
45
+ ]
46
+
47
+ objectives = [
48
+ Objective(
49
+ name=obj_name,
50
+ symbol=obj_name,
51
+ objective_type=ObjectiveTypeEnum.data_based,
52
+ ideal=data[obj_name].max(),
53
+ nadir=data[obj_name].min(),
54
+ maximize=True,
55
+ )
56
+ for obj_name in obj_names
57
+ ]
58
+
59
+ discrete_def = DiscreteRepresentation(
60
+ variable_values={"index": list(range(len(data)))},
61
+ objective_values=data[[obj.symbol for obj in objectives]].to_dict(),
62
+ )
63
+
64
+ return Problem(
65
+ name="Dmitry Forest Problem (Discrete)",
66
+ description="Defines a forest problem with four objectives: revenue, habitat availability, carbon storage, and deadwood.",
67
+ variables=variables,
68
+ objectives=objectives,
69
+ discrete_representation=discrete_def,
70
+ is_twice_differentiable=False,
71
+ )