spotoptim 2.1.0__tar.gz → 2.1.2__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.
- {spotoptim-2.1.0 → spotoptim-2.1.2}/PKG-INFO +6 -6
- {spotoptim-2.1.0 → spotoptim-2.1.2}/pyproject.toml +6 -6
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/optimizer/acquisition.py +112 -5
- {spotoptim-2.1.0 → spotoptim-2.1.2}/README.md +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/SpotOptim.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/core/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/core/data.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/core/experiment.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/core/protocol.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/core/storage.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/data/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/data/base.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/data/diabetes.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/datasets/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/datasets/py.typed +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/datasets/test01.csv +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/datasets/test02.csv +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/datasets/test11.csv +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/eda/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/eda/plots.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/confirmatory_factor_analyzer.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/factor_analyzer.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/factor_analyzer_rotator.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/factor_analyzer_utils.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/cd_data.csv +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/forr08a.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/mo.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/remote.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/so.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/function/torch_objective.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/hyperparameters/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/hyperparameters/parameters.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/hyperparameters/repr_helpers.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/inspection/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/inspection/importance.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/inspection/predictions.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/mo/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/mo/mo_mm.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/mo/pareto.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/nn/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/nn/linear_regressor.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/nn/mlp.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/optimizer/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/optimizer/schedule_free.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/optimizer/wrapper.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/plot/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/plot/contour.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/plot/mo.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/plot/visualization.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/py.typed +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/reporting/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/reporting/analysis.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/reporting/results.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/sampling/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/sampling/design.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/sampling/effects.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/sampling/lhs.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/sampling/mm.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/kernels.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/kriging.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/mlp_surrogate.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/nystroem.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/pipeline.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/surrogate/simple_kriging.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/tricands/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/tricands/tricands.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/__init__.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/boundaries.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/convert.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/dimreduction.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/eval.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/file.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/mapping.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/ocba.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/pca.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/scaler.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/serialization.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/stats.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/tensorboard.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/transform.py +0 -0
- {spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/utils/variables.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: spotoptim
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Sequential Parameter Optimization Toolbox
|
|
5
5
|
Author: bartzbeielstein
|
|
6
6
|
Author-email: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com>
|
|
@@ -8,11 +8,11 @@ License: AGPL-3.0-or-later
|
|
|
8
8
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.13
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
-
Requires-Dist: numpy>=
|
|
12
|
-
Requires-Dist: scipy>=1.
|
|
13
|
-
Requires-Dist: scikit-learn>=1.
|
|
14
|
-
Requires-Dist: pandas>=
|
|
15
|
-
Requires-Dist: tabulate>=0.
|
|
11
|
+
Requires-Dist: numpy>=2.4
|
|
12
|
+
Requires-Dist: scipy>=1.18
|
|
13
|
+
Requires-Dist: scikit-learn>=1.8
|
|
14
|
+
Requires-Dist: pandas>=3.0
|
|
15
|
+
Requires-Dist: tabulate>=0.10
|
|
16
16
|
Requires-Dist: dill>=0.4.1
|
|
17
17
|
Requires-Dist: spotoptim[torch,viz,stats,remote] ; extra == 'all'
|
|
18
18
|
Requires-Dist: pytest>=7.4.0 ; extra == 'dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spotoptim"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.2"
|
|
4
4
|
description = "Sequential Parameter Optimization Toolbox"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "AGPL-3.0-or-later" }
|
|
@@ -14,11 +14,11 @@ authors = [
|
|
|
14
14
|
]
|
|
15
15
|
requires-python = ">=3.13"
|
|
16
16
|
dependencies = [
|
|
17
|
-
"numpy>=
|
|
18
|
-
"scipy>=1.
|
|
19
|
-
"scikit-learn>=1.
|
|
20
|
-
"pandas>=
|
|
21
|
-
"tabulate>=0.
|
|
17
|
+
"numpy>=2.4",
|
|
18
|
+
"scipy>=1.18",
|
|
19
|
+
"scikit-learn>=1.8",
|
|
20
|
+
"pandas>=3.0",
|
|
21
|
+
"tabulate>=0.10",
|
|
22
22
|
"dill>=0.4.1",
|
|
23
23
|
]
|
|
24
24
|
|
|
@@ -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
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/confirmatory_factor_analyzer.py
RENAMED
|
File without changes
|
|
File without changes
|
{spotoptim-2.1.0 → spotoptim-2.1.2}/src/spotoptim/factor_analyzer/factor_analyzer_rotator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|