desdeo 1.1.3__py3-none-any.whl → 2.0.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 (122) hide show
  1. desdeo/__init__.py +8 -8
  2. desdeo/api/README.md +73 -0
  3. desdeo/api/__init__.py +15 -0
  4. desdeo/api/app.py +40 -0
  5. desdeo/api/config.py +69 -0
  6. desdeo/api/config.toml +53 -0
  7. desdeo/api/db.py +25 -0
  8. desdeo/api/db_init.py +79 -0
  9. desdeo/api/db_models.py +164 -0
  10. desdeo/api/malaga_db_init.py +27 -0
  11. desdeo/api/models/__init__.py +66 -0
  12. desdeo/api/models/archive.py +34 -0
  13. desdeo/api/models/preference.py +90 -0
  14. desdeo/api/models/problem.py +507 -0
  15. desdeo/api/models/reference_point_method.py +18 -0
  16. desdeo/api/models/session.py +46 -0
  17. desdeo/api/models/state.py +96 -0
  18. desdeo/api/models/user.py +51 -0
  19. desdeo/api/routers/_NAUTILUS.py +245 -0
  20. desdeo/api/routers/_NAUTILUS_navigator.py +233 -0
  21. desdeo/api/routers/_NIMBUS.py +762 -0
  22. desdeo/api/routers/__init__.py +5 -0
  23. desdeo/api/routers/problem.py +110 -0
  24. desdeo/api/routers/reference_point_method.py +117 -0
  25. desdeo/api/routers/session.py +76 -0
  26. desdeo/api/routers/test.py +16 -0
  27. desdeo/api/routers/user_authentication.py +366 -0
  28. desdeo/api/schema.py +94 -0
  29. desdeo/api/tests/__init__.py +0 -0
  30. desdeo/api/tests/conftest.py +59 -0
  31. desdeo/api/tests/test_models.py +701 -0
  32. desdeo/api/tests/test_routes.py +216 -0
  33. desdeo/api/utils/database.py +274 -0
  34. desdeo/api/utils/logger.py +29 -0
  35. desdeo/core.py +27 -0
  36. desdeo/emo/__init__.py +29 -0
  37. desdeo/emo/hooks/archivers.py +172 -0
  38. desdeo/emo/methods/EAs.py +418 -0
  39. desdeo/emo/methods/__init__.py +0 -0
  40. desdeo/emo/methods/bases.py +59 -0
  41. desdeo/emo/operators/__init__.py +1 -0
  42. desdeo/emo/operators/crossover.py +780 -0
  43. desdeo/emo/operators/evaluator.py +118 -0
  44. desdeo/emo/operators/generator.py +356 -0
  45. desdeo/emo/operators/mutation.py +1053 -0
  46. desdeo/emo/operators/selection.py +1036 -0
  47. desdeo/emo/operators/termination.py +178 -0
  48. desdeo/explanations/__init__.py +6 -0
  49. desdeo/explanations/explainer.py +100 -0
  50. desdeo/explanations/utils.py +90 -0
  51. desdeo/mcdm/__init__.py +19 -0
  52. desdeo/mcdm/nautili.py +345 -0
  53. desdeo/mcdm/nautilus.py +477 -0
  54. desdeo/mcdm/nautilus_navigator.py +655 -0
  55. desdeo/mcdm/nimbus.py +417 -0
  56. desdeo/mcdm/pareto_navigator.py +269 -0
  57. desdeo/mcdm/reference_point_method.py +116 -0
  58. desdeo/problem/__init__.py +79 -0
  59. desdeo/problem/evaluator.py +561 -0
  60. desdeo/problem/gurobipy_evaluator.py +562 -0
  61. desdeo/problem/infix_parser.py +341 -0
  62. desdeo/problem/json_parser.py +944 -0
  63. desdeo/problem/pyomo_evaluator.py +468 -0
  64. desdeo/problem/schema.py +1808 -0
  65. desdeo/problem/simulator_evaluator.py +298 -0
  66. desdeo/problem/sympy_evaluator.py +244 -0
  67. desdeo/problem/testproblems/__init__.py +73 -0
  68. desdeo/problem/testproblems/binh_and_korn_problem.py +88 -0
  69. desdeo/problem/testproblems/dtlz2_problem.py +102 -0
  70. desdeo/problem/testproblems/forest_problem.py +275 -0
  71. desdeo/problem/testproblems/knapsack_problem.py +163 -0
  72. desdeo/problem/testproblems/mcwb_problem.py +831 -0
  73. desdeo/problem/testproblems/mixed_variable_dimenrions_problem.py +83 -0
  74. desdeo/problem/testproblems/momip_problem.py +172 -0
  75. desdeo/problem/testproblems/nimbus_problem.py +143 -0
  76. desdeo/problem/testproblems/pareto_navigator_problem.py +89 -0
  77. desdeo/problem/testproblems/re_problem.py +492 -0
  78. desdeo/problem/testproblems/river_pollution_problem.py +434 -0
  79. desdeo/problem/testproblems/rocket_injector_design_problem.py +140 -0
  80. desdeo/problem/testproblems/simple_problem.py +351 -0
  81. desdeo/problem/testproblems/simulator_problem.py +92 -0
  82. desdeo/problem/testproblems/spanish_sustainability_problem.py +945 -0
  83. desdeo/problem/testproblems/zdt_problem.py +271 -0
  84. desdeo/problem/utils.py +245 -0
  85. desdeo/tools/GenerateReferencePoints.py +181 -0
  86. desdeo/tools/__init__.py +102 -0
  87. desdeo/tools/generics.py +145 -0
  88. desdeo/tools/gurobipy_solver_interfaces.py +258 -0
  89. desdeo/tools/indicators_binary.py +11 -0
  90. desdeo/tools/indicators_unary.py +375 -0
  91. desdeo/tools/interaction_schema.py +38 -0
  92. desdeo/tools/intersection.py +54 -0
  93. desdeo/tools/iterative_pareto_representer.py +99 -0
  94. desdeo/tools/message.py +234 -0
  95. desdeo/tools/ng_solver_interfaces.py +199 -0
  96. desdeo/tools/non_dominated_sorting.py +133 -0
  97. desdeo/tools/patterns.py +281 -0
  98. desdeo/tools/proximal_solver.py +99 -0
  99. desdeo/tools/pyomo_solver_interfaces.py +464 -0
  100. desdeo/tools/reference_vectors.py +462 -0
  101. desdeo/tools/scalarization.py +3138 -0
  102. desdeo/tools/scipy_solver_interfaces.py +454 -0
  103. desdeo/tools/score_bands.py +464 -0
  104. desdeo/tools/utils.py +320 -0
  105. desdeo/utopia_stuff/__init__.py +0 -0
  106. desdeo/utopia_stuff/data/1.json +15 -0
  107. desdeo/utopia_stuff/data/2.json +13 -0
  108. desdeo/utopia_stuff/data/3.json +15 -0
  109. desdeo/utopia_stuff/data/4.json +17 -0
  110. desdeo/utopia_stuff/data/5.json +15 -0
  111. desdeo/utopia_stuff/from_json.py +40 -0
  112. desdeo/utopia_stuff/reinit_user.py +38 -0
  113. desdeo/utopia_stuff/utopia_db_init.py +212 -0
  114. desdeo/utopia_stuff/utopia_problem.py +403 -0
  115. desdeo/utopia_stuff/utopia_problem_old.py +415 -0
  116. desdeo/utopia_stuff/utopia_reference_solutions.py +79 -0
  117. desdeo-2.0.0.dist-info/LICENSE +21 -0
  118. desdeo-2.0.0.dist-info/METADATA +168 -0
  119. desdeo-2.0.0.dist-info/RECORD +120 -0
  120. {desdeo-1.1.3.dist-info → desdeo-2.0.0.dist-info}/WHEEL +1 -1
  121. desdeo-1.1.3.dist-info/METADATA +0 -18
  122. desdeo-1.1.3.dist-info/RECORD +0 -4
@@ -0,0 +1,454 @@
1
+ """Solver interfaces to the optimization routines found in scipy.
2
+
3
+ These solvers can solve various scalarized problems of multiobjective optimization problems.
4
+ """
5
+
6
+ from collections.abc import Callable
7
+ from enum import Enum
8
+
9
+ import numpy as np
10
+ from scipy.optimize import NonlinearConstraint
11
+ from scipy.optimize import OptimizeResult as _ScipyOptimizeResult
12
+ from scipy.optimize import differential_evolution as _scipy_de
13
+ from scipy.optimize import minimize as _scipy_minimize
14
+
15
+ from pydantic import BaseModel, Field
16
+
17
+ from desdeo.problem import ConstraintTypeEnum, PolarsEvaluator, Problem, variable_dimension_enumerate
18
+ from desdeo.tools.generics import BaseSolver, SolverError, SolverResults
19
+
20
+ SUPPORTED_VAR_DIMENSIONS = ["scalar"]
21
+
22
+
23
+ class ScipyDeOptions(BaseModel):
24
+ """
25
+ Defines a pydantic model to store and pass options to the Scipy differential evolution solver.
26
+ """
27
+
28
+ initial_guess: dict[str, float | None] | None = Field(
29
+ description="The initial guess to be utilized in the solver. For variables with a None as their initial "
30
+ "guess, the mid-point of the variable's lower and upper bound is utilized as the initial"
31
+ "guess. If None, it is assumed that there are no initial guesses for any of the variables.",
32
+ default=None
33
+ )
34
+ de_kwargs: dict | None = Field(
35
+ description="Custom keyword arguments to be forwarded to `scipy.optimize.differential_evolution`.",
36
+ default=None
37
+ )
38
+
39
+
40
+ class ScipyMinimizeOptions(BaseModel):
41
+ """
42
+ Defines a pydantic model to store and pass options to the Scipy Minimize solver.
43
+ """
44
+
45
+ initial_guess: dict[str, float | None] | None = Field(
46
+ description="The initial guess to be utilized in the solver. For variables with a None as their"
47
+ "initial guess, the mid-point of the variable's lower and upper bound is utilized as the "
48
+ "initial guess. If None, it is assumed that there are no initial guesses for any of the variables.",
49
+ default=None
50
+ )
51
+ method: str | None = Field(
52
+ description="The scipy.optimize.minimize method to beused. If None, a method is selected "
53
+ "automatically based on the properties of the objective (does it have constraints?).",
54
+ default=None
55
+ )
56
+ method_kwargs: dict | None = Field(
57
+ description="The keyword arguments passed to the scipy.optimize.minimize method.",
58
+ default=None
59
+ )
60
+ tol: float | None = Field(
61
+ description="Tolerance for termination.",
62
+ default=None
63
+ )
64
+ additional_options: dict | None = Field(
65
+ description="Additional solver options.",
66
+ default=None
67
+ )
68
+
69
+
70
+ class EvalTargetEnum(str, Enum):
71
+ """An enum that describe whether the evaluator target is an objective or a constraint."""
72
+
73
+ objective = "objective"
74
+ constraint = "constraint"
75
+
76
+
77
+ def get_variable_bounds_pairs(problem: Problem) -> list[tuple[float | int, float | int]]:
78
+ """Returns the variable bounds defined in a Problem as a list of tuples.
79
+
80
+ Args:
81
+ problem (Problem): the problem with the variables of interest.
82
+
83
+ Returns:
84
+ list[tuple[float | int, float | int]]: a list of tuples, the first
85
+ element of each tuple is the lower bound of a variable and the second
86
+ its upper bound. Each tuple corresponds to a variable.
87
+ """
88
+ return [(variable.lowerbound, variable.upperbound) for variable in problem.variables]
89
+
90
+
91
+ def set_initial_guess(problem: Problem) -> list[float | int]:
92
+ """Sets or gets the initial guess for each variable defined in a Problem.
93
+
94
+ For variables without initial guess, the initial guess is set to be the middle point of said
95
+ variable's lower and upper bound.
96
+
97
+ Args:
98
+ problem (Problem): the problem with the variables of which the initial values are of interest.
99
+
100
+ Returns:
101
+ list[float | int]: a list of numbers, each number represents the initial guess of each variable in the problem.
102
+ """
103
+ return [
104
+ variable.initial_value
105
+ if variable.initial_value is not None
106
+ else ((variable.upperbound - variable.lowerbound) / 2 + variable.lowerbound)
107
+ for variable in problem.variables
108
+ ]
109
+
110
+
111
+ def create_scipy_dict_constraints(problem: Problem, evaluator: PolarsEvaluator) -> dict:
112
+ """Creates a dict with scipy compatible constraints.
113
+
114
+ It is assumed that there are constraints defined in problem.
115
+
116
+ Args:
117
+ problem (Problem): the Problem with the constraints.
118
+ evaluator (GenericEvaluator): the evaluator utilized to evaluate problem.
119
+
120
+ Returns:
121
+ dict: a dict with scipy compatible constraints.
122
+ """
123
+ return [
124
+ {
125
+ "type": "ineq" if constraint.cons_type == ConstraintTypeEnum.LTE else "eq",
126
+ "fun": get_scipy_eval(problem, evaluator, constraint.symbol, eval_target=EvalTargetEnum.constraint),
127
+ }
128
+ for constraint in problem.constraints
129
+ ]
130
+
131
+
132
+ def create_scipy_object_constraints(problem: Problem, evaluator: PolarsEvaluator) -> list[NonlinearConstraint]:
133
+ """Creates a list with scipy constraint object `NonLinearConstraints` used by some scipy routines.
134
+
135
+ For more infor, see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.NonlinearConstraint.html#scipy-optimize-nonlinearconstraint
136
+
137
+ Args:
138
+ problem (Problem): the problem with the original constraint to be utilized in creating the list of constraints.
139
+ evaluator (GenericEvaluator): the evaluator corresponding to problem that can be used to evaluate
140
+ the constraints.
141
+
142
+ Returns:
143
+ list[NonlinearConstraint]: a list of scipy's NonLinearConstraint objects.
144
+ """
145
+ return NonlinearConstraint(
146
+ fun=get_scipy_eval(problem, evaluator, "", eval_target=EvalTargetEnum.constraint),
147
+ lb=0, # constraint value must be between 0 and inf, e.g., positive.
148
+ ub=float("inf"), # since in scipy, a constraint is respected when its value is positive. See scipy_eval.
149
+ )
150
+
151
+
152
+ def get_scipy_eval(
153
+ problem: Problem,
154
+ evaluator: PolarsEvaluator,
155
+ target: str,
156
+ eval_target: EvalTargetEnum,
157
+ ) -> Callable[[list[float | int]], list[float | int]]:
158
+ """Wraps the problem and evaluator into a callable function that can be used by scipy routines.
159
+
160
+ The returned function expects an array-like argument, such as a numpy array or list.
161
+
162
+ Args:
163
+ problem (Problem): the problem being solved.
164
+ evaluator (GenericEvaluator): the evaluator to evaluate the problem being solved.
165
+ target (str): the symbol of the objective to of the optimization, defined in problem.
166
+ eval_target (EvalTargetEnum): either objective or constraints. If objective,
167
+ it is assumed that the evalution is about evaluating the objective function
168
+ of the single-objective optimization problem being solved, e.g., a scalarization function
169
+ defined in problem. If constraint, then the evalution is assumed to be about evaluating
170
+ the constraints defined in problem.
171
+
172
+ Returns:
173
+ Callable[[list[float | int]], list[float | int]]: a function that takes as its argument
174
+ an array like object.
175
+
176
+ Note:
177
+ Constraints in scipy are defined such that a positive number means the constraint
178
+ is respected. In DESDEO, this is the opposite, e.g., a positive number means
179
+ a constraint is breached. We take this into account when returning the
180
+ constraint values, but this does not affect the constraint values computed
181
+ for the true constraints.
182
+ """
183
+
184
+ def scipy_eval(x: list[float | int]) -> list[float | int]:
185
+ """An evaluator to be used in scipy routines.
186
+
187
+ Args:
188
+ x (list[float | int]): an array like, such as a numpy array or list.append
189
+
190
+ Raises:
191
+ SolverError: when an invalid evaluator target is specified.
192
+
193
+ Returns:
194
+ list[float | int]: an array like.
195
+ """
196
+ # TODO: Consider caching the results of evaluator.evaluate
197
+ evalutor_args = {
198
+ problem.variables[i].symbol: [x[i]] if isinstance(x[i], float | int) else x[i]
199
+ for i in range(len(problem.variables))
200
+ }
201
+
202
+ if eval_target == EvalTargetEnum.objective:
203
+ evaluator_res = evaluator.evaluate(evalutor_args)
204
+
205
+ # evaluata objective (scalarized)
206
+ return evaluator_res.to_dict(as_series=False)[target]
207
+
208
+ if eval_target == EvalTargetEnum.constraint:
209
+ evaluator_df = evaluator.evaluate(evalutor_args)
210
+ # evaluate constraint
211
+ # put the minus here because scipy expect positive constraints values when the constraint
212
+ # is respected. But in DESDEO, we define constraints s.t., a negative value means the constraint
213
+ # is recpected, therefore, it needs to be flipped here.
214
+ con_symbols = [constraint.symbol for constraint in problem.constraints]
215
+ res_dict = evaluator_df[con_symbols].to_dict(as_series=False)
216
+
217
+ res = np.array([np.array(res_dict[symbol]) for symbol in con_symbols])
218
+
219
+ # squeeze important for minimization routines
220
+ return -np.squeeze(res, axis=-1) if res.shape[-1] == 1 else -res
221
+
222
+ # non-existing eval_target
223
+ msg = f"'eval_target' = '{eval_target} not supported. Must be one of {list(EvalTargetEnum)}."
224
+ raise SolverError(msg)
225
+
226
+ return scipy_eval
227
+
228
+
229
+ def parse_scipy_optimization_result(
230
+ optimization_result: _ScipyOptimizeResult, problem: Problem, evaluator: PolarsEvaluator
231
+ ) -> SolverResults:
232
+ """Parses the optimization results returned by various scipy methods.
233
+
234
+ For documentation, see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.OptimizeResult.html#scipy.optimize.OptimizeResult
235
+
236
+ Args:
237
+ optimization_result (_ScipyOptimizeResult): the optimization results.
238
+ problem (Problem): the problem to which the optimization results correspond to.
239
+ evaluator (GenericEvaluator): the evaluator that has been used in computing the optimization results.
240
+
241
+ Returns:
242
+ SolverResults: a pydantic dataclass with the relevant optimization results.
243
+ """
244
+ x_opt = optimization_result.x
245
+ success_opt = optimization_result.success
246
+ msg_opt = optimization_result.message
247
+
248
+ results = evaluator.evaluate({problem.variables[i].symbol: [x_opt[i]] for i in range(len(problem.variables))})
249
+
250
+ optimal_objectives = {obj.symbol: results[obj.symbol][0] for obj in problem.objectives}
251
+ constraint_values = (
252
+ {con.symbol: results[con.symbol][0] for con in problem.constraints} if problem.constraints is not None else None
253
+ )
254
+
255
+ """
256
+ objective_symbols = [obj.symbol for obj in problem.objectives]
257
+ f_res = results[objective_symbols]
258
+ optimal_objectives = {symbol: f_res[symbol][0] for symbol in objective_symbols}
259
+
260
+ if problem.constraints is not None:
261
+ const_symbols = [const.symbol for const in problem.constraints]
262
+ const_res = results[const_symbols]
263
+ constraint_values = {symbol: const_res[symbol][0] for symbol in const_symbols}
264
+ else:
265
+ constraint_values = None
266
+ """
267
+
268
+ extra_func_values = (
269
+ {extra.symbol: results[extra.symbol][0] for extra in problem.extra_funcs}
270
+ if problem.extra_funcs is not None
271
+ else None
272
+ )
273
+ scalarization_values = (
274
+ {scal.symbol: results[scal.symbol][0] for scal in problem.scalarization_funcs}
275
+ if problem.scalarization_funcs is not None
276
+ else None
277
+ )
278
+
279
+ return SolverResults(
280
+ optimal_variables={problem.variables[i].symbol: x_opt[i] for i in range(len(problem.variables))},
281
+ optimal_objectives=optimal_objectives,
282
+ constraint_values=constraint_values,
283
+ extra_func_values=extra_func_values,
284
+ scalarization_values=scalarization_values,
285
+ success=success_opt,
286
+ message=msg_opt,
287
+ )
288
+
289
+
290
+ class ScipyMinimizeSolver(BaseSolver):
291
+ """Creates a scipy solver that utilizes the `minimization` routine."""
292
+
293
+ def __init__(
294
+ self,
295
+ problem: Problem,
296
+ options: ScipyMinimizeOptions = ScipyMinimizeOptions()
297
+ ):
298
+ """Initializes a solver that utilizes the `scipy.optimize.minimize` routine.
299
+
300
+ The `scipy.optimize.minimze` routine is fully accessible through this function.
301
+ For additional details and explanation of some of the argumetns, see
302
+ https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize
303
+
304
+ Args:
305
+ problem (Problem): the multiobjective optimization problem to be solved.
306
+ options: (ScipyMinimizeOptions): Pydantic model containing args for scipy minimize solver.
307
+ subscriber (str | None, optional): not used right now. WIP. Defaults to None.
308
+
309
+ """
310
+ if variable_dimension_enumerate(problem) not in SUPPORTED_VAR_DIMENSIONS:
311
+ msg = "ScipyMinimizeSolver only supports scalar variables."
312
+ raise SolverError(msg)
313
+
314
+ initial_guess = options.initial_guess
315
+ self.problem = problem
316
+ self.method = options.method
317
+ self.method_kwargs = options.method_kwargs
318
+ self.tol = options.tol
319
+ self.additional_options = options.additional_options
320
+
321
+ # variables bounds as (min, max pairs)
322
+ self.bounds = get_variable_bounds_pairs(problem)
323
+
324
+ # the initial guess as a simple sequence. If no initial value is set for some variable,
325
+ # then the initial value defaults to middle of the upper and lower bounds.
326
+ if initial_guess is not None:
327
+ self.initial_guess = [initial_guess[var.symbol] for var in self.problem.variables]
328
+ else:
329
+ self.initial_guess = set_initial_guess(problem)
330
+
331
+ self.evaluator = PolarsEvaluator(problem)
332
+
333
+ self.constraints = (
334
+ create_scipy_dict_constraints(self.problem, self.evaluator)
335
+ if self.problem.constraints is not None
336
+ else None
337
+ )
338
+
339
+ def solve(self, target: str) -> SolverResults:
340
+ """Solves the problem for a given target.
341
+
342
+ Args:
343
+ target (str): the sumbol of the objective function to be optimized.
344
+
345
+ Returns:
346
+ SolverResults: results of the optimization.
347
+ """
348
+ # add constraints if there are any
349
+
350
+ optimization_result: _ScipyOptimizeResult = _scipy_minimize(
351
+ get_scipy_eval(self.problem, self.evaluator, target, EvalTargetEnum.objective),
352
+ self.initial_guess,
353
+ method=self.method,
354
+ bounds=self.bounds,
355
+ constraints=self.constraints,
356
+ options=self.additional_options,
357
+ tol=self.tol,
358
+ )
359
+
360
+ # pare and return the results
361
+ return parse_scipy_optimization_result(optimization_result, self.problem, self.evaluator)
362
+
363
+
364
+ class ScipyDeSolver(BaseSolver):
365
+ """Creates a scipy solver that utilizes differential evolution."""
366
+
367
+ def __init__(
368
+ self,
369
+ problem: Problem,
370
+ options: ScipyDeOptions = ScipyDeOptions(),
371
+ ):
372
+ """Creates a solver that utilizes the `scipy.optimize.differential_evolution` routine.
373
+
374
+ The `scipy.optimize.differential_evolution` routine is fully accessible through this function.
375
+ For additional details and explanation of some of the argumetns, see
376
+ https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html
377
+
378
+ Args:
379
+ problem (Problem): the multiobjective optimization problem to be solved.
380
+ initial_guess (dict[str, float, None] | None, optional): The initial
381
+ guess to be utilized in the solver. For variables with a None as their
382
+ initial guess, the mid-point of the variable's lower and upper bound is
383
+ utilzied as the initial guess. If None, it is assumed that there are
384
+ no initial guesses for any of the variables. Defaults to None.
385
+ options (ScipyDeOptions): Pydantic model containing arguments used by scipy DE solver.
386
+ """
387
+
388
+ initial_guess = options.initial_guess
389
+ de_kwargs = options.de_kwargs
390
+
391
+ if variable_dimension_enumerate(problem) not in SUPPORTED_VAR_DIMENSIONS:
392
+ msg = "ScipyDeSolver only supports scalar variables."
393
+ raise SolverError(msg)
394
+
395
+ self.problem = problem
396
+ if de_kwargs is None:
397
+ de_kwargs = {
398
+ "strategy": "best1bin",
399
+ "maxiter": 1000,
400
+ "popsize": 15,
401
+ "tol": 0.01,
402
+ "mutation": (0.5, 1),
403
+ "recombination": 0.7,
404
+ "seed": None,
405
+ "callback": None,
406
+ "disp": False,
407
+ "polish": True,
408
+ "init": "latinhypercube",
409
+ "atol": 0,
410
+ "updating": "deferred",
411
+ "workers": 1,
412
+ "integrality": None,
413
+ "vectorized": True, # the constraints for scipy_de need to be fixed first for this to work
414
+ }
415
+ self.de_kwargs = de_kwargs
416
+
417
+ # variable bounds
418
+ self.bounds = get_variable_bounds_pairs(problem)
419
+
420
+ # initial guess. If no guess is present for a variable, said variable's mid point of its
421
+ # lower abd upper bound is used instead
422
+ if initial_guess is None:
423
+ self.initial_guess = set_initial_guess(problem)
424
+ else:
425
+ self.initial_guess = initial_guess
426
+
427
+ self.evaluator = PolarsEvaluator(problem)
428
+ self.constraints = (
429
+ create_scipy_object_constraints(self.problem, self.evaluator)
430
+ if self.problem.constraints is not None
431
+ else ()
432
+ )
433
+
434
+ def solve(self, target: str) -> SolverResults:
435
+ """Solve the problem for a given target.
436
+
437
+ Args:
438
+ target (str): the symbol of the objective function to be optimized.
439
+
440
+ Returns:
441
+ SolverResults: results of the optimization.
442
+ """
443
+ # add constraints if there are any
444
+
445
+ optimization_result: _ScipyOptimizeResult = _scipy_de(
446
+ get_scipy_eval(self.problem, self.evaluator, target, EvalTargetEnum.objective),
447
+ bounds=self.bounds,
448
+ x0=self.initial_guess,
449
+ constraints=self.constraints,
450
+ **self.de_kwargs,
451
+ )
452
+
453
+ # parse the results
454
+ return parse_scipy_optimization_result(optimization_result, self.problem, self.evaluator)