spotoptim 2.0.0__tar.gz → 2.1.1__tar.gz

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 (85) hide show
  1. {spotoptim-2.0.0 → spotoptim-2.1.1}/PKG-INFO +1 -1
  2. {spotoptim-2.0.0 → spotoptim-2.1.1}/pyproject.toml +1 -1
  3. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/SpotOptim.py +21 -41
  4. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/optimizer/acquisition.py +112 -5
  5. {spotoptim-2.0.0 → spotoptim-2.1.1}/README.md +0 -0
  6. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/__init__.py +0 -0
  7. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/core/__init__.py +0 -0
  8. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/core/data.py +0 -0
  9. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/core/experiment.py +0 -0
  10. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/core/protocol.py +0 -0
  11. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/core/storage.py +0 -0
  12. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/data/__init__.py +0 -0
  13. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/data/base.py +0 -0
  14. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/data/diabetes.py +0 -0
  15. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/datasets/__init__.py +0 -0
  16. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/datasets/py.typed +0 -0
  17. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/datasets/test01.csv +0 -0
  18. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/datasets/test02.csv +0 -0
  19. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/datasets/test11.csv +0 -0
  20. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/eda/__init__.py +0 -0
  21. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/eda/plots.py +0 -0
  22. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/factor_analyzer/__init__.py +0 -0
  23. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/factor_analyzer/confirmatory_factor_analyzer.py +0 -0
  24. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/factor_analyzer/factor_analyzer.py +0 -0
  25. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/factor_analyzer/factor_analyzer_rotator.py +0 -0
  26. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/factor_analyzer/factor_analyzer_utils.py +0 -0
  27. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/__init__.py +0 -0
  28. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/cd_data.csv +0 -0
  29. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/forr08a.py +0 -0
  30. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/mo.py +0 -0
  31. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/remote.py +0 -0
  32. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/so.py +0 -0
  33. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/function/torch_objective.py +0 -0
  34. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/hyperparameters/__init__.py +0 -0
  35. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/hyperparameters/parameters.py +0 -0
  36. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/hyperparameters/repr_helpers.py +0 -0
  37. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/inspection/__init__.py +0 -0
  38. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/inspection/importance.py +0 -0
  39. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/inspection/predictions.py +0 -0
  40. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/mo/__init__.py +0 -0
  41. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/mo/mo_mm.py +0 -0
  42. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/mo/pareto.py +0 -0
  43. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/nn/__init__.py +0 -0
  44. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/nn/linear_regressor.py +0 -0
  45. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/nn/mlp.py +0 -0
  46. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/optimizer/__init__.py +0 -0
  47. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/optimizer/schedule_free.py +0 -0
  48. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/optimizer/wrapper.py +0 -0
  49. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/plot/__init__.py +0 -0
  50. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/plot/contour.py +0 -0
  51. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/plot/mo.py +0 -0
  52. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/plot/visualization.py +0 -0
  53. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/py.typed +0 -0
  54. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/reporting/__init__.py +0 -0
  55. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/reporting/analysis.py +0 -0
  56. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/reporting/results.py +0 -0
  57. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/sampling/__init__.py +0 -0
  58. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/sampling/design.py +0 -0
  59. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/sampling/effects.py +0 -0
  60. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/sampling/lhs.py +0 -0
  61. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/sampling/mm.py +0 -0
  62. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/__init__.py +0 -0
  63. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/kernels.py +0 -0
  64. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/kriging.py +0 -0
  65. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/mlp_surrogate.py +0 -0
  66. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/nystroem.py +0 -0
  67. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/pipeline.py +0 -0
  68. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/surrogate/simple_kriging.py +0 -0
  69. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/tricands/__init__.py +0 -0
  70. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/tricands/tricands.py +0 -0
  71. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/__init__.py +0 -0
  72. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/boundaries.py +0 -0
  73. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/convert.py +0 -0
  74. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/dimreduction.py +0 -0
  75. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/eval.py +0 -0
  76. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/file.py +0 -0
  77. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/mapping.py +0 -0
  78. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/ocba.py +0 -0
  79. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/pca.py +0 -0
  80. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/scaler.py +0 -0
  81. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/serialization.py +0 -0
  82. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/stats.py +0 -0
  83. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/tensorboard.py +0 -0
  84. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/transform.py +0 -0
  85. {spotoptim-2.0.0 → spotoptim-2.1.1}/src/spotoptim/utils/variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: spotoptim
3
- Version: 2.0.0
3
+ Version: 2.1.1
4
4
  Summary: Sequential Parameter Optimization Toolbox
5
5
  Author: bartzbeielstein
6
6
  Author-email: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "spotoptim"
3
- version = "2.0.0"
3
+ version = "2.1.1"
4
4
  description = "Sequential Parameter Optimization Toolbox"
5
5
  readme = "README.md"
6
6
  license = { text = "AGPL-3.0-or-later" }
@@ -89,10 +89,6 @@ class SpotOptimConfig:
89
89
  acquisition_optimizer_kwargs (Optional[Dict[str, Any]]): Keyword arguments for the acquisition function optimizer.
90
90
  args (Tuple): Arguments for the objective function.
91
91
  kwargs (Optional[Dict[str, Any]]): Keyword arguments for the objective function.
92
- metric_factorial (str): Distance metric used by a factor-aware Kriging surrogate for
93
- nominal (categorical) variables. ``"hamming"`` (default) treats all distinct
94
- factor levels as equidistant, which is correct for unordered factors.
95
- Any scipy pairwise distance metric string is accepted.
96
92
 
97
93
 
98
94
  Examples:
@@ -193,7 +189,6 @@ class SpotOptimConfig:
193
189
  acquisition_optimizer_kwargs: Optional[Dict[str, Any]] = None
194
190
  args: Tuple = ()
195
191
  kwargs: Optional[Dict[str, Any]] = None
196
- metric_factorial: str = "hamming"
197
192
 
198
193
  def __post_init__(self):
199
194
  if self.kwargs is None:
@@ -442,13 +437,6 @@ class SpotOptim(BaseEstimator):
442
437
  * "cosine": Cosine distance.
443
438
  * "correlation": Correlation distance.
444
439
  * "canberra", "braycurtis", "sqeuclidean", etc.
445
- metric_factorial (str, optional):
446
- Distance metric used for nominal (factor) variables by a factor-aware
447
- Kriging surrogate. ``"hamming"`` (default) treats all distinct factor
448
- levels as equidistant, which is correct for unordered factors; any
449
- scipy pairwise distance metric string is accepted. Only takes effect
450
- when factor variables are present and ``surrogate`` is left as ``None``
451
- (an order-agnostic Kriging is then built automatically).
452
440
 
453
441
  Attributes:
454
442
  X_ (ndarray): All evaluated points, shape (n_samples, n_features).
@@ -774,7 +762,6 @@ class SpotOptim(BaseEstimator):
774
762
  acquisition_optimizer_kwargs: Optional[Dict[str, Any]] = None,
775
763
  args: Tuple = (),
776
764
  kwargs: Optional[Dict[str, Any]] = None,
777
- metric_factorial: str = "hamming",
778
765
  ):
779
766
  warnings.filterwarnings(warnings_filter)
780
767
 
@@ -853,7 +840,6 @@ class SpotOptim(BaseEstimator):
853
840
  acquisition_optimizer_kwargs=acquisition_optimizer_kwargs,
854
841
  args=args,
855
842
  kwargs=kwargs,
856
- metric_factorial=metric_factorial,
857
843
  )
858
844
 
859
845
  # Initialize State
@@ -1662,36 +1648,30 @@ class SpotOptim(BaseEstimator):
1662
1648
  self._max_surrogate_points_list = None
1663
1649
  self._active_max_surrogate_points = self.config.max_surrogate_points
1664
1650
 
1665
- if self._factor_maps:
1666
- # Factor variables are present: use a nominal (order-agnostic) Kriging
1667
- # surrogate so the kernel treats factor levels as equidistant.
1668
- # var_type is already the reduced var_type (after setup_dimension_reduction).
1669
- self.surrogate = Kriging(
1670
- var_type=list(self.var_type),
1671
- metric_factorial=self.config.metric_factorial,
1672
- seed=self.seed if self.seed is not None else 124,
1673
- )
1674
- else:
1675
- # No factor variables: use the standard GaussianProcessRegressor.
1676
- kernel = ConstantKernel(1.0, (1e-2, 1e12)) * Matern(
1677
- length_scale=1.0, length_scale_bounds=(1e-4, 1e2), nu=2.5
1678
- )
1679
-
1680
- # Determine optimizer for GPR
1681
- optimizer = "fmin_l_bfgs_b" # Default used by sklearn
1682
- if self.config.acquisition_optimizer_kwargs is not None:
1683
- optimizer = partial(
1684
- gpr_minimize_wrapper, **self.config.acquisition_optimizer_kwargs
1685
- )
1651
+ # Default surrogate is the GaussianProcessRegressor for ALL problem
1652
+ # types. Factor variables are NOT auto-switched to a Kriging: the user
1653
+ # keeps full control over the surrogate (and is informed by
1654
+ # _validate_factor_surrogate_compat that a factor-aware
1655
+ # Kriging(metric_factorial="hamming") is available for nominal factors).
1656
+ kernel = ConstantKernel(1.0, (1e-2, 1e12)) * Matern(
1657
+ length_scale=1.0, length_scale_bounds=(1e-4, 1e2), nu=2.5
1658
+ )
1686
1659
 
1687
- self.surrogate = GaussianProcessRegressor(
1688
- kernel=kernel,
1689
- n_restarts_optimizer=100,
1690
- normalize_y=True,
1691
- random_state=self.seed,
1692
- optimizer=optimizer,
1660
+ # Determine optimizer for GPR
1661
+ optimizer = "fmin_l_bfgs_b" # Default used by sklearn
1662
+ if self.config.acquisition_optimizer_kwargs is not None:
1663
+ optimizer = partial(
1664
+ gpr_minimize_wrapper, **self.config.acquisition_optimizer_kwargs
1693
1665
  )
1694
1666
 
1667
+ self.surrogate = GaussianProcessRegressor(
1668
+ kernel=kernel,
1669
+ n_restarts_optimizer=100,
1670
+ normalize_y=True,
1671
+ random_state=self.seed,
1672
+ optimizer=optimizer,
1673
+ )
1674
+
1695
1675
  def _validate_factor_surrogate_compat(self) -> None:
1696
1676
  """Warn when factor variables are present but the surrogate is factor-blind.
1697
1677
 
@@ -117,6 +117,107 @@ def prepare_de_kwargs(optimizer: SpotOptimProtocol, x0=None):
117
117
  return filtered_kwargs
118
118
 
119
119
 
120
+ # Normalized-space margin used to pull a warm-start x0 strictly inside the unit
121
+ # interval that scipy's DifferentialEvolutionSolver validates against. It must
122
+ # exceed the ~1e-16 rounding granularity of that rescaling by several orders of
123
+ # magnitude (robust) while staying negligible in parameter space (faithful).
124
+ _DE_X0_NORM_MARGIN = 1e-12
125
+
126
+
127
+ def sanitize_de_x0(
128
+ x0: np.ndarray, bounds: Optional[List[Tuple[float, float]]]
129
+ ) -> Optional[np.ndarray]:
130
+ """Make a warm-start ``x0`` acceptable to scipy ``differential_evolution``.
131
+
132
+ ``DifferentialEvolutionSolver`` does not validate ``x0`` against the raw box
133
+ ``[lower, upper]``. It rescales every coordinate into a unit interval using
134
+ ``scaled = (x0 - mid) * recip + 0.5`` with ``mid = (lower + upper) / 2`` and
135
+ ``recip = 1 / |upper - lower|`` (``0`` when ``lower == upper``), then rejects
136
+ the whole run if any ``scaled`` falls outside ``[0, 1]``.
137
+
138
+ An incumbent that legitimately sits *on* a box bound (optimizers converge to
139
+ corners; clipped warm-starts and estimator defaults land exactly on a bound)
140
+ can rescale to a hair outside ``[0, 1]`` purely from floating-point rounding
141
+ — e.g. ``0.01`` on the bound ``(0.01, 100.0)`` normalizes to ``-1.1e-16``.
142
+ A raw-space nudge such as ``np.nextafter`` cannot repair this: one ULP at a
143
+ small bound (``~1.7e-18`` at ``0.01``) is far below the rounding granularity
144
+ of the rescaling at the span's magnitude, so the nudged value normalizes to
145
+ the same out-of-range number. The nudge is simply in the wrong metric.
146
+
147
+ This helper therefore clamps in scipy's *own* normalized metric and maps the
148
+ result back with scipy's exact inverse, leaving an already-valid ``x0``
149
+ untouched (bit-for-bit) and returning ``None`` only if a coordinate cannot be
150
+ made interior — in which case the caller drops the optional warm-start.
151
+
152
+ Args:
153
+ x0 (ndarray): Candidate warm-start point in internal (transformed) scale,
154
+ shape ``(n_features,)``.
155
+ bounds (list of tuple): Per-dimension ``(lower, upper)`` bounds in
156
+ internal scale, exactly as passed to ``differential_evolution``.
157
+
158
+ Returns:
159
+ Optional[ndarray]: ``x0`` unchanged when scipy already accepts it; a
160
+ minimally adjusted copy guaranteed to pass scipy's check; or ``None``
161
+ when no acceptable point exists — ``bounds`` is ``None``, ``x0`` is not
162
+ all finite, or a coordinate cannot be made interior. ``None`` simply
163
+ drops the optional warm-start for that infill.
164
+
165
+ Examples:
166
+ ```{python}
167
+ import numpy as np
168
+ from spotoptim.optimizer.acquisition import sanitize_de_x0
169
+
170
+ # An incumbent exactly on the lower bound of a wide span: the raw value
171
+ # normalizes just below 0 and scipy would reject it.
172
+ bounds = [(0.01, 100.0)]
173
+ x0 = np.array([0.01])
174
+ fixed = sanitize_de_x0(x0, bounds)
175
+ lb, ub = 0.01, 100.0
176
+ mid, span = 0.5 * (lb + ub), abs(ub - lb)
177
+ scaled = (fixed[0] - mid) / span + 0.5
178
+ print(f"accepted by scipy: {0.0 <= scaled <= 1.0}")
179
+ print(f"distance from incumbent: {fixed[0] - 0.01:.2e}")
180
+ ```
181
+ """
182
+ if bounds is None:
183
+ return None
184
+ x0 = np.asarray(x0, dtype=float).ravel()
185
+ # A non-finite incumbent is pathological; never use it as a warm-start.
186
+ if not np.all(np.isfinite(x0)):
187
+ return None
188
+ lower = np.array([b[0] for b in bounds], dtype=float)
189
+ upper = np.array([b[1] for b in bounds], dtype=float)
190
+
191
+ # scipy's exact rescaling constants (see DifferentialEvolutionSolver.__init__
192
+ # and _unscale_parameters): note span uses |upper - lower|, so reversed
193
+ # bounds are handled identically to scipy.
194
+ mid = 0.5 * (lower + upper)
195
+ span = np.abs(lower - upper)
196
+ with np.errstate(divide="ignore", invalid="ignore"):
197
+ recip = np.where(span > 0.0, 1.0 / span, 0.0)
198
+
199
+ scaled = (x0 - mid) * recip + 0.5
200
+
201
+ # Fast path: scipy already accepts every coordinate -> leave x0 untouched so
202
+ # currently-working runs stay bit-for-bit identical.
203
+ if not np.any((scaled < 0.0) | (scaled > 1.0)):
204
+ return x0
205
+
206
+ # Clamp in scipy's normalized metric, then map back with its exact inverse
207
+ # (_scale_parameters: x = mid + (scaled - 0.5) * span).
208
+ scaled = np.clip(scaled, _DE_X0_NORM_MARGIN, 1.0 - _DE_X0_NORM_MARGIN)
209
+ x0_fixed = mid + (scaled - 0.5) * span
210
+
211
+ # Defense in depth: re-run scipy's exact check and drop x0 if any residual
212
+ # rounding still leaves a coordinate outside the unit interval. With finite
213
+ # x0 and finite bounds this is algebraically unreachable (the margin is far
214
+ # above the round-trip rounding error); it guards against future drift.
215
+ check = (x0_fixed - mid) * recip + 0.5
216
+ if not np.all(np.isfinite(x0_fixed)) or np.any((check < 0.0) | (check > 1.0)):
217
+ return None
218
+ return x0_fixed
219
+
220
+
120
221
  def optimize_acquisition_de(optimizer: SpotOptimProtocol) -> np.ndarray:
121
222
  """Optimize using differential evolution.
122
223
 
@@ -140,11 +241,17 @@ def optimize_acquisition_de(optimizer: SpotOptimProtocol) -> np.ndarray:
140
241
 
141
242
  if best_x is not None:
142
243
  best_x = optimizer.transform_X(best_x)
143
- # Nudge x0 strictly inside DE bounds using nextafter
144
- _lb = np.array([b[0] for b in optimizer.bounds])
145
- _ub = np.array([b[1] for b in optimizer.bounds])
146
- best_x = np.clip(best_x, np.nextafter(_lb, _ub), np.nextafter(_ub, _lb))
147
- best_X = best_x if optimizer.rng.rand() < optimizer.de_x0_prob else None
244
+ # Make x0 acceptable to scipy's differential_evolution, which validates
245
+ # x0 in a NORMALIZED metric rather than the raw box. An incumbent on a
246
+ # box bound can otherwise be rejected by floating-point rounding; a
247
+ # raw-space nudge (np.nextafter) is in the wrong metric and too small to
248
+ # survive scipy's rescaling when |bound| << span (see sanitize_de_x0).
249
+ best_x = sanitize_de_x0(best_x, optimizer.bounds)
250
+ # Draw unconditionally within this branch so the RNG stream is identical
251
+ # whether or not sanitization succeeds; drop the optional warm-start if
252
+ # it could not be sanitized.
253
+ use_x0 = optimizer.rng.rand() < optimizer.de_x0_prob
254
+ best_X = best_x if (best_x is not None and use_x0) else None
148
255
  else:
149
256
  best_X = None
150
257
 
File without changes