| """Per-Phase-2 optimizer acceptance tests. |
| |
| For every standalone optimizer (DOA, CMA-ES, PSO, GWO, WOA, SCSO): |
| |
| * Sphere 5D, ~2000 evals: final f < 1e-2 (lenient because metaheuristics |
| are population-based and don't hit machine precision). |
| * Rastrigin 10D, ~2000 evals: final f < 50. |
| * Reproducibility: same seed -> identical history. |
| |
| CMA-ES gets a tighter sphere bound because it is locally-optimal on |
| quadratic problems. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from collections.abc import Callable |
| from typing import Any |
|
|
| import numpy as np |
| import pytest |
|
|
| from ahdcma.algorithms.base import OptimizationResult, Optimizer, SearchSpace |
| from ahdcma.algorithms.cmaes_wrapper import CMAES |
| from ahdcma.algorithms.doa import DOA |
| from ahdcma.algorithms.gwo import GWO |
| from ahdcma.algorithms.pso import PSO |
| from ahdcma.algorithms.scso import SCSO |
| from ahdcma.algorithms.woa import WOA |
| from tests.conftest import rastrigin_05, sphere_05 |
|
|
|
|
| def _run( |
| cls: type[Optimizer], |
| *, |
| dim: int, |
| fitness: Callable[[np.ndarray], float], |
| pop: int = 20, |
| gens: int = 100, |
| seed: int = 0, |
| extra: dict[str, Any] | None = None, |
| ) -> OptimizationResult: |
| config: dict[str, Any] = { |
| "population_size": pop, |
| "max_generations": gens, |
| "seed": seed, |
| } |
| if extra: |
| config.update(extra) |
| sp = SearchSpace.unit_cube(dim) |
| opt = cls(config, fitness, sp, run_id=f"{cls.__name__}_test") |
| return opt.optimize() |
|
|
|
|
| ALGOS: list[tuple[type[Optimizer], float]] = [ |
| (DOA, 1e-2), |
| (CMAES, 1e-6), |
| (PSO, 1e-2), |
| (GWO, 1e-2), |
| (WOA, 1e-2), |
| (SCSO, 1e-2), |
| ] |
|
|
|
|
| @pytest.mark.parametrize("cls,sphere_threshold", ALGOS) |
| def test_sphere_5d(cls: type[Optimizer], sphere_threshold: float) -> None: |
| res = _run(cls, dim=5, fitness=sphere_05, pop=20, gens=100, seed=0) |
| assert ( |
| res.best_f < sphere_threshold |
| ), f"{cls.__name__} sphere f={res.best_f:.3g} >= {sphere_threshold}" |
|
|
|
|
| @pytest.mark.parametrize("cls,_", ALGOS) |
| def test_rastrigin_10d(cls: type[Optimizer], _: float) -> None: |
| res = _run(cls, dim=10, fitness=rastrigin_05, pop=30, gens=100, seed=0) |
| assert res.best_f < 50.0, f"{cls.__name__} rastrigin f={res.best_f:.3g}" |
|
|
|
|
| @pytest.mark.parametrize("cls,_", ALGOS) |
| def test_reproducibility(cls: type[Optimizer], _: float) -> None: |
| a = _run(cls, dim=5, fitness=sphere_05, pop=10, gens=20, seed=7) |
| b = _run(cls, dim=5, fitness=sphere_05, pop=10, gens=20, seed=7) |
| assert a.best_f == b.best_f |
| np.testing.assert_array_equal(a.best_x, b.best_x) |
| assert len(a.history) == len(b.history) |
| for fa, fb in zip(a.history.fitnesses, b.history.fitnesses, strict=True): |
| np.testing.assert_array_equal(fa, fb) |
|
|
|
|
| def test_doa_history_modes_all_set() -> None: |
| res = _run(DOA, dim=4, fitness=sphere_05, pop=10, gens=15, seed=0) |
| assert all(m == "explore" for m in res.history.mode_per_gen) |
| assert len(res.history) == 16 |
|
|
|
|
| def test_cmaes_rejects_dim_one() -> None: |
| sp = SearchSpace.unit_cube(1) |
| with pytest.raises(ValueError): |
| CMAES( |
| {"population_size": 8, "max_generations": 5, "seed": 1}, |
| sphere_05, |
| sp, |
| ) |
|
|