Skip to content

Commit a4adaf3

Browse files
committed
Merge branch 'dev'
2 parents a580efe + 3b9783c commit a4adaf3

5 files changed

Lines changed: 487 additions & 26 deletions

File tree

‎.github/workflows/test.yml‎

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,38 @@ jobs:
146146
echo "No changed files to check."
147147
fi
148148
149+
# ===========================================================================
150+
# STAGE 1.5: API smoke tests (gate — blocks all further tests on failure)
151+
# ===========================================================================
152+
api-smoke-tests:
153+
name: api-smoke-tests
154+
needs: [code-quality]
155+
runs-on: ubuntu-latest
156+
timeout-minutes: 5
157+
158+
steps:
159+
- uses: actions/checkout@v4
160+
161+
- name: Set up Python 3.11
162+
uses: actions/setup-python@v5
163+
with:
164+
python-version: "3.11"
165+
166+
- name: Install dependencies
167+
run: |
168+
python -m pip install --no-cache-dir --upgrade pip
169+
python -m pip install --no-cache-dir -e .[test]
170+
171+
- name: Run API smoke tests
172+
run: |
173+
python -m pytest tests/test_smoke_api.py -v -p no:warnings
174+
149175
# ===========================================================================
150176
# STAGE 2: Targeted tests (only for changed areas, fast feedback)
151177
# ===========================================================================
152178
targeted-core:
153179
name: targeted-core
154-
needs: [code-quality, detect-changes]
180+
needs: [api-smoke-tests, detect-changes]
155181
if: needs.detect-changes.outputs.core == 'true' || needs.detect-changes.outputs.any_src == 'false'
156182
runs-on: ubuntu-latest
157183
timeout-minutes: 10
@@ -175,7 +201,7 @@ jobs:
175201
176202
targeted-sklearn:
177203
name: targeted-sklearn
178-
needs: [code-quality, detect-changes]
204+
needs: [api-smoke-tests, detect-changes]
179205
if: needs.detect-changes.outputs.sklearn == 'true'
180206
runs-on: ubuntu-latest
181207
timeout-minutes: 10
@@ -199,7 +225,7 @@ jobs:
199225
200226
targeted-sktime:
201227
name: targeted-sktime
202-
needs: [code-quality, detect-changes]
228+
needs: [api-smoke-tests, detect-changes]
203229
if: needs.detect-changes.outputs.sktime == 'true'
204230
runs-on: ubuntu-latest
205231
timeout-minutes: 10
@@ -223,7 +249,7 @@ jobs:
223249
224250
targeted-skpro:
225251
name: targeted-skpro
226-
needs: [code-quality, detect-changes]
252+
needs: [api-smoke-tests, detect-changes]
227253
if: needs.detect-changes.outputs.skpro == 'true'
228254
runs-on: ubuntu-latest
229255
timeout-minutes: 10
@@ -247,7 +273,7 @@ jobs:
247273
248274
targeted-utils:
249275
name: targeted-utils
250-
needs: [code-quality, detect-changes]
276+
needs: [api-smoke-tests, detect-changes]
251277
if: needs.detect-changes.outputs.utils == 'true'
252278
runs-on: ubuntu-latest
253279
timeout-minutes: 10
@@ -272,18 +298,25 @@ jobs:
272298
# Gate job: passes if all targeted tests pass OR are skipped (not failed)
273299
targeted-tests-gate:
274300
name: targeted-tests-gate
275-
needs: [targeted-core, targeted-sklearn, targeted-sktime, targeted-skpro, targeted-utils]
301+
needs: [api-smoke-tests, targeted-core, targeted-sklearn, targeted-sktime, targeted-skpro, targeted-utils]
276302
if: always()
277303
runs-on: ubuntu-latest
278304
steps:
279-
- name: Check targeted test results
305+
- name: Check results
280306
run: |
307+
echo "API smoke tests: ${{ needs.api-smoke-tests.result }}"
281308
echo "Core: ${{ needs.targeted-core.result }}"
282309
echo "Sklearn: ${{ needs.targeted-sklearn.result }}"
283310
echo "Sktime: ${{ needs.targeted-sktime.result }}"
284311
echo "Skpro: ${{ needs.targeted-skpro.result }}"
285312
echo "Utils: ${{ needs.targeted-utils.result }}"
286313
314+
# Fail if API smoke tests did not succeed
315+
if [[ "${{ needs.api-smoke-tests.result }}" != "success" ]]; then
316+
echo "API smoke tests failed — blocking pipeline"
317+
exit 1
318+
fi
319+
287320
# Fail if any targeted test failed (not skipped)
288321
if [[ "${{ needs.targeted-core.result }}" == "failure" ]] || \
289322
[[ "${{ needs.targeted-sklearn.result }}" == "failure" ]] || \
@@ -293,7 +326,7 @@ jobs:
293326
echo "One or more targeted tests failed"
294327
exit 1
295328
fi
296-
echo "All targeted tests passed or were skipped"
329+
echo "All gates passed"
297330
298331
# ===========================================================================
299332
# STAGE 3: Full test matrix (only after targeted tests pass)
@@ -433,6 +466,7 @@ jobs:
433466
- name: Upload coverage to Codecov
434467
uses: codecov/codecov-action@v5
435468
with:
469+
token: ${{ secrets.CODECOV_TOKEN }}
436470
files: ./coverage.xml
437471
fail_ci_if_error: false
438472
verbose: true

‎pyproject.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ version = "5.0.4"
1111
description = "An optimization and data collection toolbox for convenient and fast prototyping of computationally expensive models."
1212
readme = "README.md"
1313
requires-python = ">=3.10"
14-
license = {file = "LICENSE"}
14+
license = "MIT"
15+
license-files = ["LICENSE"]
1516
keywords = ["optimization",
1617
"hyperparameter-tuning",
1718
"bayesian-optimization",
@@ -38,7 +39,6 @@ classifiers=[
3839
"Programming Language :: Python :: 3.12",
3940
"Programming Language :: Python :: 3.13",
4041
"Programming Language :: Python :: 3.14",
41-
"License :: OSI Approved :: MIT License",
4242
"Operating System :: OS Independent",
4343
"Topic :: Scientific/Engineering :: Information Analysis",
4444
"Topic :: Scientific/Engineering :: Mathematics",

‎src/hyperactive/integrations/sklearn/opt_cv.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,4 @@ def score(self, X, y=None, **params):
201201
@property
202202
def fit_successful(self):
203203
"""Fit Successful function."""
204-
self._fit_successful
204+
return self._fit_successful

‎src/hyperactive/opt/_adapters/_base_optuna_adapter.py‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ def _suggest_params(self, trial, param_space):
7878
dict
7979
The suggested parameters
8080
"""
81+
from optuna.distributions import BaseDistribution
82+
8183
params = {}
8284
for key, space in param_space.items():
83-
if hasattr(space, "suggest"): # optuna distribution object
84-
params[key] = trial._suggest(space, key)
85+
if isinstance(space, BaseDistribution):
86+
params[key] = trial._suggest(key, space)
8587
elif isinstance(space, tuple) and len(space) == 2:
8688
# Tuples are treated as ranges (low, high)
8789
low, high = space
@@ -110,7 +112,7 @@ def _objective(self, trial):
110112
The objective value
111113
"""
112114
params = self._suggest_params(trial, self.param_space)
113-
score = self.experiment(params)
115+
score = self._experiment_wrapped(params)
114116

115117
# Handle early stopping based on max_score
116118
if self.max_score is not None and score >= self.max_score:
@@ -130,19 +132,17 @@ def _setup_initial_positions(self, study):
130132
if isinstance(self.initialize, dict) and "warm_start" in self.initialize:
131133
warm_start_points = self.initialize["warm_start"]
132134
if isinstance(warm_start_points, list):
133-
# For warm start, we manually add trials to the study history
134-
# instead of using suggest methods to avoid distribution conflicts
135135
for point in warm_start_points:
136-
self.experiment(point)
137136
study.enqueue_trial(point)
138137

139138
def _solve(self, experiment, param_space, n_trials, **kwargs):
140139
"""Run the Optuna optimization.
141140
142141
Parameters
143142
----------
144-
experiment : callable
145-
The experiment to optimize
143+
experiment : BaseExperiment
144+
The experiment to optimize, already wrapped by
145+
``BaseOptimizer.get_experiment()``.
146146
param_space : dict
147147
The parameter space
148148
n_trials : int
@@ -157,29 +157,29 @@ def _solve(self, experiment, param_space, n_trials, **kwargs):
157157
"""
158158
import optuna
159159

160-
# Create optimizer with random state if provided
160+
self._experiment_wrapped = experiment
161+
161162
optimizer = self._get_optimizer()
162163

163-
# Create study
164164
study = optuna.create_study(
165-
direction="maximize", # Assuming we want to maximize scores
165+
direction="maximize",
166166
sampler=optimizer,
167167
)
168168

169-
# Setup initial positions
170169
self._setup_initial_positions(study)
171170

172-
# Setup early stopping callback
173171
callbacks = []
174172
if self.early_stopping is not None:
173+
patience = self.early_stopping
175174

176175
def early_stopping_callback(study, trial):
177-
if len(study.trials) >= self.early_stopping:
176+
if trial.number < patience:
177+
return
178+
if trial.number - study.best_trial.number >= patience:
178179
study.stop()
179180

180181
callbacks.append(early_stopping_callback)
181182

182-
# Run optimization
183183
study.optimize(
184184
self._objective,
185185
n_trials=n_trials,

0 commit comments

Comments
 (0)