Регрессия выборочной записи (7732577): полная строка писалась только при
улучшении КПД лучшего генома острова, а рекордсмены СКОРОСТИ — почти
всегда прожорливые геномы с низким КПД (57.8 м/с при 0.5%), которые
КПД-рекорд не улучшают. Топ «по скорости» на дашборде застыл на сутки,
хотя эволюция продолжала находить всё более быстрые конфигурации
(пользователь поймал: «даже случайно никто не стал быстрее лететь»).
Теперь на каждом острове отслеживаются ДВА записываемых рекорда — КПД и
скорость; это по-прежнему максимум пара строк на остров за поколение.
118 lines
5.1 KiB
Python
118 lines
5.1 KiB
Python
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)
|
||
# строк меньше, чем оценок: максимум 2 улучшения (КПД+скорость) на
|
||
# остров на поколение
|
||
assert 1 <= n_rows <= 4
|
||
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
|
||
# 1 остров x 3 поколения, максимум 2 улучшения (КПД+скорость) за поколение
|
||
assert 1 <= count_runs(conn) <= 6
|
||
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
|
||
# максимум 2 улучшения (КПД+скорость) на остров на поколение
|
||
assert 3 <= count_runs(conn) <= 4 * 3 * 2
|