import random from gausse.components.database import ComponentDatabase from gausse.optim.evolutionary import polish_best, run_evolution from gausse.optim.search_space import SearchBounds, sample_genome from gausse.storage.database import count_runs, fetch_runs, open_connection DB = ComponentDatabase.load() def _stats_total(conn) -> int: row = conn.execute("SELECT value FROM stats_counters WHERE key='total'").fetchone() return row[0] if row else 0 def test_run_evolution_counts_all_evaluations_but_stores_only_best_rows(tmp_path): """Контракт хранения evolve: агрегаты честно считают ВСЕ оценки, а полные строки — только улучшения лучшего генома острова.""" db_path = tmp_path / "runs.sqlite3" bounds = SearchBounds(max_stages=2) summary = run_evolution( db_path, n_generations=2, population_size=6, bounds=bounds, n_workers=2, seed=7, polish=False, ) assert summary["n_evaluated"] == 12 # 2 поколения x 6 особей assert "best_fitness" in summary conn = open_connection(db_path) assert _stats_total(conn) == 12 # счётчики дашборда видят все оценки n_rows = count_runs(conn) # строк меньше, чем оценок: максимум 1 улучшение на остров на поколение assert 1 <= n_rows <= 2 rows = fetch_runs(conn) assert all(r.search_mode == "evolve" for r in rows) # лучший найденный обязан лежать в базе полной строкой best_row_fitness = max((r.efficiency if r.feasible else -1.0) for r in rows) if summary["best_feasible"]: assert best_row_fitness == summary["best_efficiency"] conn.close() def test_evolution_is_reproducible_given_same_seed(tmp_path): bounds = SearchBounds(max_stages=2) summary_a = run_evolution( tmp_path / "a.sqlite3", n_generations=2, population_size=6, bounds=bounds, n_workers=1, seed=99, polish=False, ) summary_b = run_evolution( tmp_path / "b.sqlite3", n_generations=2, population_size=6, bounds=bounds, n_workers=1, seed=99, polish=False, ) assert summary_a["best_fitness"] == summary_b["best_fitness"] assert summary_a["best_efficiency"] == summary_b["best_efficiency"] def test_polish_does_not_make_the_best_genome_worse(): rng = random.Random(15) bounds = SearchBounds(max_stages=2) # ищем реализуемый геном как стартовую точку доводки genome = None for _ in range(30): candidate = sample_genome(DB, bounds, rng) from gausse.optim.objective import evaluate result = evaluate(candidate, DB, bounds) if result.feasible: genome = candidate baseline_fitness = result.fitness break assert genome is not None, "не нашли реализуемый геном за 30 попыток" _, polished_result = polish_best(genome, DB, bounds) assert polished_result.fitness >= baseline_fitness - 1e-9 def test_run_evolution_gpu_batch_mode(tmp_path): """GPU-режим эволюции (батч-оценка поколения; здесь numpy-бэкенд): агрегаты считают все оценки, строки — только улучшения лучшего.""" from gausse.storage.database import count_runs, fetch_runs, open_connection db_path = tmp_path / "evo_gpu.sqlite3" summary = run_evolution( db_path, n_generations=3, population_size=40, seed=5, polish=False, use_gpu=True ) assert summary["n_evaluated"] == 120 conn = open_connection(db_path) assert _stats_total(conn) == 120 assert 1 <= count_runs(conn) <= 3 # 1 остров x 3 поколения, только улучшения rows = fetch_runs(conn, limit=5) assert all(r.search_mode.startswith("evolve-gpu") for r in rows) # у сохранённых лучших должна быть посчитана стоимость (GPU-путь # не считает её для рядовых, но дочитывает для избранных) assert all(r.cost_rub is not None for r in rows) def test_run_evolution_islands(tmp_path): """Островная модель: K популяций одним батчем + миграция по кольцу.""" from gausse.storage.database import count_runs, open_connection db_path = tmp_path / "evo_isl.sqlite3" summary = run_evolution( db_path, n_generations=4, population_size=15, seed=7, polish=False, use_gpu=True, islands=3, migrate_every=2, ) assert summary["n_evaluated"] == 4 * 15 * 3 conn = open_connection(db_path) assert _stats_total(conn) == 4 * 15 * 3 # максимум одно улучшение на остров на поколение assert 3 <= count_runs(conn) <= 4 * 3