Files
gausse/tests/test_evolutionary.py
jze9 6b52ba4660 Топ скоростей снова обновляется: рекорд скорости острова тоже пишется в базу
Регрессия выборочной записи (7732577): полная строка писалась только при
улучшении КПД лучшего генома острова, а рекордсмены СКОРОСТИ — почти
всегда прожорливые геномы с низким КПД (57.8 м/с при 0.5%), которые
КПД-рекорд не улучшают. Топ «по скорости» на дашборде застыл на сутки,
хотя эволюция продолжала находить всё более быстрые конфигурации
(пользователь поймал: «даже случайно никто не стал быстрее лететь»).
Теперь на каждом острове отслеживаются ДВА записываемых рекорда — КПД и
скорость; это по-прежнему максимум пара строк на остров за поколение.
2026-07-13 17:03:14 +05:00

118 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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