Commit Graph

60 Commits

Author SHA1 Message Date
jze9
6b52ba4660 Топ скоростей снова обновляется: рекорд скорости острова тоже пишется в базу
Регрессия выборочной записи (7732577): полная строка писалась только при
улучшении КПД лучшего генома острова, а рекордсмены СКОРОСТИ — почти
всегда прожорливые геномы с низким КПД (57.8 м/с при 0.5%), которые
КПД-рекорд не улучшают. Топ «по скорости» на дашборде застыл на сутки,
хотя эволюция продолжала находить всё более быстрые конфигурации
(пользователь поймал: «даже случайно никто не стал быстрее лететь»).
Теперь на каждом острове отслеживаются ДВА записываемых рекорда — КПД и
скорость; это по-прежнему максимум пара строк на остров за поколение.
2026-07-13 17:03:14 +05:00
jze9
9a783ec8aa Дашборд: колонка «Когда найден» в топе конфигураций
Без времени находки обновление топа было невидимо: новая строка-рекорд
13.63% глазом неотличима от старой 13.44%, и пользователю казалось, что
таблица застыла («тир-лист не обновлялся давно» при живом конвейере).
Свежие записи (моложе часа) подсвечиваются зелёным.
2026-07-13 16:51:52 +05:00
jze9
7732577f31 Эволюция: в базу только улучшения лучших + счётчики по всем; выборка 10000
По требованию пользователя «не сохранять всё подряд, а только то, что
нужно для эволюции»: рядовые геномы поколения больше НЕ пишутся строками
в runs — писатель получает лёгкие StatsOnly-инкременты (счётчики,
гистограмма, причины отказов на дашборде остаются честными по ВСЕМ
оценкам), а полная строка пишется только когда лучший геном острова
улучшился (+ полировка). Вставка миллионов строк в HDD-базу с 6 индексами
была узким местом конвейера: GPU считал цикл ~23 мин, дозапись хвоста
очереди шла часами (~150 строк/с), реальная скорость была ~165 оценок/с
вместо ~1300/с. gpu_record_worker стал не нужен — удалён.

Выборка эволюции уменьшена до 10000 на поколение (8 островов x 1250,
было 8x4000=32000) — по просьбе пользователя, чтобы поколения сменялись
быстрее; поколений за цикл теперь 120 (было 60) — при освобождённом
писателе цикл углубляется вдвое дальше за то же время.
v0.1.0
2026-07-12 22:29:17 +05:00
jze9
c232f1d387 Ревью хранения: честная детализация прогона + общий rebuild + чистка мёртвого кода
По итогам код-ревью коммита 5c11999:
- run_detail: шапка и энергобаланс — СОХРАНЁННЫЕ при прогоне значения
  (пересчитанный на лету detail мог противоречить им: GPU-строки и старые
  model_version считались другим движком/моделью, а energy_breakdown
  прятался за feasible пересчёта). Пересчёт теперь явно помечен
  recomputed_with_model_version и обёрнут в try/except — /api/run был
  единственным эндпоинтом, где падение пересимуляции роняло ответ.
- web/rebuild.py: один общий путь «строка БД -> симуляция» для детализации
  и рендера (раньше два дубля; клик по прогону гонял одну и ту же
  симуляцию до 5 раз и перечитывал базу компонентов с диска на каждый
  запрос — теперь кэш компонентов + кэш последних симуляций по run_id).
- Мёртвый код после 5c11999: ветка build_details=True (единственный
  вызов передавал False; дефолт True возвращал бы как раз ту дорогую
  запись, от которой уходили), _FEASIBLE, параметр bounds у _record,
  поле EvaluationResult.detail и его мёртвая ветка в build_run_record.
- overview: n_stages=None (морда покажет «—»), а не 0, когда сводки нет.
- PLAN.md/schema.py: убраны утверждения, что полный build_detail хранится
  в каждой записи (устарело с 5c11999), отмечена старая форма записей.
2026-07-12 12:31:37 +05:00
jze9
5c119990b7 Хранить только genome_json+скаляры на прогон, детали строить на лету
decoded_summary_json раньше писал полную build_detail() (геометрия, URL,
электрика по каждой ступени) на каждый из миллионов прогонов — это и
душило CPU при GPU-эволюции (пул процессов нужен был только чтобы успевать
за GPU), и разогнало базу до 70ГБ на 12.9М строк, пока диск не забился под
завязку. genome_json уже достаточен для полного воспроизведения — теперь
пишем дешёвую decoded_summary(), а run_detail() на веб-морде пересчитывает
полную физику по клику (как уже делает render.py для графиков/анимации).
2026-07-11 21:50:54 +05:00
jze9
e394fd6d59 Дашборд: раздельные карточки «лучший за всё время» и «лучший текущего цикла»
Лог показывает лучший ТЕКУЩЕГО цикла (свежая популяция карабкается заново),
дашборд — за всё время; без подписи числа выглядели несвязанными.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:42:43 +05:00
jze9
17323335e0 evolve_forever: 8 островов по 4000 (батч 32к на GPU)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:32:00 +05:00
jze9
51464b2139 Островная эволюция: K популяций одним GPU-батчем + миграция по кольцу
GPU на маленьких батчах упирается в накладные на запуск ядер (1070: батч
4000 = 306/с, 32000 = 1211/с при 99МБ видеопамяти из 8ГБ). Вместо одной
гигантской популяции — 8 независимых островов по 4000: их поколения
оцениваются одним жирным батчем 32к, а изоляция островов + миграция лучших
по кольцу раз в 10 поколений дают разнообразие против скатывания в один
локальный оптимум. На более мощной карте достаточно поднять islands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:31:45 +05:00
jze9
fe34bb3672 GPU не ждёт CPU: физика ступеней предвычисляется в пуле процессов
precompute_genome/StagePre: decode+build_stage_physics (тот же единый
источник истины) выполняется в воркерах пула и возвращает ПЛОСКИЕ числа;
главному потоку остаётся склейка массивов и запуск ядер. Раньше он строил
физобъекты 4000 геномов один и GPU ждал ~половину времени поколения.
_simulate_states/_prepare_stage переведены на StagePre (без объектов
компонентов в горячем цикле), run_gpu_sweep — на тот же путь.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:12:34 +05:00
jze9
005ffaf04b evolve_forever: лучший-за-всё-время через частичный индекс, а не max()-скан
MAX(efficiency) сканировал всю базу (6.7ГБ) на старте каждого цикла —
минуты мёртвого времени, растущие с базой.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:54:19 +05:00
jze9
e52026d2b2 Полировка: писать одну запись напрямую — убран вечный завис в join()
Редкая гонка spawn+mp.Queue вешала главный процесс НАВЕЧНО после полировки
(22.5 часа простоя; /proc/*/stack: главный в do_wait, polish-писатель в
pipe_read — данные из очереди не доехали). Для одной записи процесс-писатель
и очередь не нужны вовсе: конкурентных писателей в этот момент нет, пишем
insert_run'ом напрямую. Плюс возврат SIGTERM к SIG_DFL после дренажа —
иначе фаза полировки неубиваема мягко.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:51:21 +05:00
jze9
9b98dbe27b Убрать шум финализаторов очереди при завершении цикла
queue.close()+join_thread() после writer.join(): иначе семафоры очереди
финализируются в гонке с resource_tracker на выходе интерпретатора и после
каждого цикла в журнал сыпалось «Exception ignored … sem_unlink
FileNotFoundError». Проверено репродукцией: stderr пуст.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 05:30:25 +05:00
jze9
b2fc51bb72 SQLite: обуздать WAL (4.4ГБ!) + принудить частичные индексы топов
WAL раздувался до гигабайт (авто-чекпоинт голодал при 400 вставках/с и
постоянных читателях) и замедлял ВСЕ запросы: journal_size_limit=64МБ +
периодический wal_checkpoint(PASSIVE) в писателе. Планировщик игнорировал
частичные DESC-индексы (брал idx_runs_feasible + TEMP B-TREE сортировку
620к строк) — принуждаем INDEXED BY. ensure_stats при старте писателя —
закрыта гонка миграции счётчиков.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 05:14:44 +05:00
jze9
b43885c6e1 fetch_runs: feasible литералом, чтобы работали частичные индексы топов
С bound-параметром SQLite не может применить partial index (WHERE feasible=1)
на этапе подготовки запроса — топ-15 продолжал сканировать сотни тысяч строк.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 05:05:51 +05:00
jze9
4f0c38500b Морда O(1) + GPU без удушения одним потоком Python
Дашборд: /api/overview занимал 5.2с на 630к строк (сканы всей таблицы на
каждый рефреш при автообновлении 3с — морда хронически отставала, и с
ростом базы становилось бы хуже). Теперь:
- счётчики/гистограмма/причины/режимы — инкрементальные таблицы stats_*,
  обновляются В ТРАНЗАКЦИИ вставки (insert_runs), чтение O(1);
  ensure_stats() мигрирует старую базу одним пересчётом
- топы — частичные индексы (efficiency/exit_velocity DESC WHERE feasible=1)
- хвост лога — чтение последних 64КБ с конца, а не всего файла

GPU: сборка записей (decode+build_detail+JSON+стоимость) вынесена в пул
процессов (gpu_record_worker), GPU больше не ждёт один поток Python между
батчами.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:56:32 +05:00
jze9
a4219c7212 evolve_forever: не печатать трейсбек штатной остановки (KeyboardInterrupt)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:32:02 +05:00
jze9
185d7f4361 Фикс обрыва дренажа очереди повторным SIGTERM + model_version в API деталей
systemd шлёт TERM каждому процессу группы: повторный сигнал ловился внутри
writer.join() и обрывал дописывание базы (FileNotFoundError-агония очереди
в журнале, потерянный хвост). Теперь после первого TERM сигнал игнорируется,
join неубиваем. run_detail отдаёт model_version — прогоны разных версий
физики несравнимы, версия обязана быть видна.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:28:10 +05:00
jze9
2af292e094 deploy/: серверные скрипты и юниты в репозиторий + нумерация циклов в логе
evolve_forever.sh теперь пишет в лог явные маркеры конвейера: «ЦИКЛ #N
стартует (лучший КПД за всё время: X%)» и «ЦИКЛ #N завершён — НЕ остановка,
стартует #N+1». Раньше скрипты жили только на VM без версионирования.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:21:07 +05:00
jze9
e6b877bab7 Хранилище: писатель пачками + выживание при SIGTERM — конец потерь записей
GPU-эволюция (380+/с) обгоняла писателя (~150/с, коммит=fsync на каждую
запись): хвост очереди копился в RAM минутами и ТЕРЯЛСЯ при рестарте
сервиса (~80к записей за один рестарт) — нарушение «сохраняем всё».
- run_writer_process: вставка пачками до 500 в одной транзакции; при ошибке
  пачки — откат и поштучный поиск виновника; SIGTERM игнорирует (дописывает
  всё до None)
- run_evolution: SIGTERM -> исключение -> штатный finally досылает None и
  ждёт писателя; в лог явные строки про дописывание базы и полировку
  (чтобы «=== готово ===» не выглядело остановкой)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:18:11 +05:00
jze9
ac459d26a4 Дашборд: карточка загрузки GPU (util/Вт/°C из results/gpustat.json)
Веб в Docker без nvidia-smi: хост-сервис gausse-gpustat пишет JSON рядом с
базой, /api/overview отдаёт поле gpu (протухшее >30с — честно null).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:01:48 +05:00
jze9
6fba55f2d6 Доки: v3/v4 в хронологии честности, GPU-эволюция, актуальные ограничения
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:51:52 +05:00
jze9
83a3e72bd5 GPU: cut-логика двумя fused-ядрами (лимит параметров CUDA 4096 байт)
Одно ядро на весь шаг переполняло formal parameter space (4272>4096).
Разбито на step_phys (RK4+потери+ост.энергия) и step_cut (обрывы, без
физпараметров): 2 запуска ядра на шаг вместо ~20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:48:40 +05:00
jze9
965c18f327 GPU: вся cut-логика шага слита в одно cupy.fuse-ядро
Профиль на 1070: RK4 был слит, но пошаговая бухгалтерия (_magnetic_energy,
_overlap, ~15 where) запускала десятки мелких ядер и съедала >90% времени
(10 геномов/с). Теперь весь шаг — одно ядро; при несборке fuse — честный
fallback на прежний путь. numpy-путь не тронут (эталон для тестов).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:42:05 +05:00
jze9
1480f4ce1a Физика v4: диффузия поля в снаряд (намагничивается только скин-кольцо)
Эволюция на v3 нашла новый обход: «медленная» катушка (600+ витков, ~1мс
импульс, 36А) давала КПД 30% — на низкой частоте слабы и Доуэлл, и вихревые,
и трение. Неучтённая физика: за импульс поле проникает в сталь лишь на
глубину скин-слоя δ=√(2ρ/(ω·μ0·μr)) ~ доли мм; внутренность снаряда не
намагничивается и силы не даёт. iron_penetration_fraction = 1-(1-δ/a)²
умножает коэффициент заполнения. MODEL_VERSION -> gausse-physics-v4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:34:12 +05:00
jze9
ef83512601 GPU в эволюции: оценка поколения одним батчем (use_gpu=True)
evaluate_genomes_gpu в gpu/batch_sweep.py — общий раундовый симулятор
(_simulate_states) для sweep и эволюции, та же формула фитнеса, что в
objective.evaluate (КПД либо -1+доля пройденных ступеней). run_evolution
получил use_gpu: поколение целиком уходит в батч-интегратор (на сервере
cupy/GTX 1070), полировка остаётся точной на CPU. search_mode=evolve-gpu(...).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 03:22:47 +05:00
jze9
ecd6508f29 Физика v3: коэффициент заполнения железа + фикс зависаний решателя
ЭКСПЛОЙТ (нашёлся по подозрительным 75-82% КПД на дашборде): модель
умножала ВСЮ индуктивность катушки на (mu_eff-1)*overlap, как будто железо
заполняет всё сечение. Тонкий снаряд (4мм) в толстой катушке (29мм) получал
~50x нефизичной силы бесплатно, и эволюция сгрузилась именно туда.
Фикс: iron_fill_factor = A_снаряда/A_среднего витка (классическое
λ = n*(B_fe*A_fe + B_возд*(A-A_fe))).

ЗАВИСАНИЯ (сервер стоял 35+ мин на одной конфигурации):
- сглаживание сухого трения tanh(v/0.01) вместо разрывного sign(v)
- событие тока удержания ключа (SCR закрывается при I<0.1А после пика) --
  передемпфированный хвост больше не молотит весь 20мс бюджет
- полная формула отражения вихревого контура (ωM)²R2/(R2²+(ωL2)²) вместо
  резистивного приближения (114кОм -> 2.5кОм на вырожденных катушках)
- гард: L/R < 100нс = вне области применимости модели, мгновенный отказ
Бенч 200 геномов: 608с -> 22с, худшая оценка 540с -> 2.5с.

MODEL_VERSION -> gausse-physics-v3. 104 теста.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 02:22:29 +05:00
jze9
78af686e6d Фикс зависа решателя: сгладить сухое трение tanh'ом у v=0
Разрывный sign(v) в силе трения заставлял адаптивный solve_ivp бесконечно
дробить шаг у v≈0 (chattering): одна конфигурация считалась 35+ минут,
скорость эволюции упала с ~430/с до 3-5/с. Стандартная регуляризация
sign(v)->tanh(v/0.01) во всех путях (CPU-разряд, подлёт, GPU numpy, fused
cupy-ядро) + регрессионный тест на гладкость силы в нуле.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 01:41:59 +05:00
jze9
8708d0f922 Обновить README и PLAN до актуального состояния (физика v2, GPU, автономная эволюция)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 01:29:26 +05:00
jze9
a0f02e246a Анимация: равномерное время (ничего не вырезается) + результат эксперимента в кадре
- кадры на равномерной сетке по ФИЗИЧЕСКОМУ времени вместо индексов массива
  (раньше плотные точки разряда съедали все кадры, подлёт вырезался)
- множитель замедления написан в кадре, событие показано целиком
- шапка: выходная скорость, КПД, ступени, энергия банок, масса снаряда
- панель тока каждой катушки во времени + панель скорости снаряда,
  бегущий курсор синхронен с положением в трубе, окна разрядов подкрашены
- датчики каждой ступени отмечены на трубе

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 01:21:18 +05:00
jze9
d4affa9574 Физреализм v2: скин/близость (Доуэлл), трение+воздух, паспортные импульсные токи ключей
- physics/ac_resistance.py: R_AC обмотки по Доуэллу на частоте импульса
  1/sqrt(LC) — для толстого провода во многих слоях потери в разы выше DC
- трение о трубку (0.35·m·g) + аэродинамика (0.5·rho·Cd·A·v^2) во всей
  динамике: CPU ОДУ разряда, подлёт к датчику (с событием остановки),
  GPU numpy-путь, fused cupy-ядро, аналитический coast GPU-sweep'а
- SwitchSpec.pulse_current_a: паспортные ITSM/IDM/ICM из даташитов вместо
  generic-множителей; отчёт теперь различает превышение продолжительного
  рейтинга (норма для импульса) и импульсного предела (отбраковка) — фикс
  вводившего в заблуждение флага switch_current_over_limit
- КПД теперь может быть слегка отрицательным (трение съело больше, чем
  добавила слабая катушка) — это честно
- MODEL_VERSION -> gausse-physics-v2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:57:07 +05:00
jze9
83a4a7b805 GPU sweep now multi-stage (up to 10 coils); raise max_stages to 10
The GPU batch sweep processes multi-stage configs round-by-round: at round
s every still-alive config with a stage s runs its discharge in one batch,
carrying the slug's global position/velocity forward to the next round.
Configs with fewer stages drop out of later rounds. Analytic
constant-velocity sensor trigger per stage. Validated vs CPU run_coilgun
for multi-stage configs (<=3% exit velocity). Default max_stages 4 -> 10.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 23:44:22 +05:00
jze9
ae6481ceb0 GPU: fuse the whole RK4 discharge step into one cupy kernel
The naive elementwise port launched ~60 tiny kernels per step, so the
GTX 1070 was only ~1.3x over CPU (launch-bound). Fold the entire RK4 step
(4 derivative evals + combine + overlap) into a single cupy.fuse kernel;
numpy path unchanged and still validates vs scipy. GPU correctness
re-checked against CPU on deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 23:26:52 +05:00
jze9
fd4c9ad541 GPU: cut per-step device->host sync in batch integrator
The naive port synced every step (bool(any(active)) early-break), which
on GPU is a device->host copy per iteration that serializes the pipeline
and made cupy slower than numpy. Check the break condition only every 256
steps. Correctness unchanged (batch still validates vs scipy and CPU).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 23:08:37 +05:00
jze9
4cfa946315 Enforce per-stage sensor placement in the gap; report absolute sensor position
The model already gave every coil its own sensor, its own sensor->coil
distance, and searched inter-coil gaps (all of which set when each later
coil fires) -- confirmed on a 3-stage run. Two refinements from user
feedback:

- Each stage's sensor must sit in the gap BEFORE its coil, not overlap
  the previous coil: repair() now clamps sensor_to_coil_distance < gap for
  stages > 0. sample_genome now also routes through repair() (it didn't
  before, so freshly sampled genomes skipped every physical constraint).
  Verified: 161/780 previously-overlapping sensors -> 0.
- build_detail now records each sensor's absolute position along the tube
  (sensor_position_m), not just the relative distance, so it's clear where
  every stage's sensor physically sits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 22:19:32 +05:00
jze9
7803e147a7 Add GPU/CPU batch sweep for single-stage configs (gausse sweep --gpu)
Finishes the GPU path to a usable state: run_gpu_sweep vectorizes the
expensive discharge across N single-stage configs via the validated
batch integrator (numpy CPU / cupy GPU, same code). Setup (decode +
build_stage_physics) is a fast python loop; the ODE batch is one call.

- Extracted sim/stage.build_stage_physics so the CPU path (run_stage) and
  the GPU batch build IDENTICAL physics (coil model, eddy, saturation,
  circuit params) -- no divergence by construction.
- Batch integrator hardened: stiff configs that overflow fixed-step RK4
  are marked infeasible (blew_up), feasibility = actually-commutated
  (committed), warnings silenced via seterr. Single-pulse + eddy mirrored.
- Analytic sensor trigger (constant-velocity approach) for the batch;
  inductive threshold check preserved.

Honesty gate: test_batch_sweep validates GPU-path exit velocities against
the CPU run_coilgun -- 0.0% divergence on the checked configs. Limitation
stated plainly: single stage only (multi-stage stays on the CPU sweep);
cupy on the server GPU (1070 passthrough) is the remaining infra step.
91 tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 16:00:49 +05:00
jze9
13c1d417d6 Add shop links, full dimensions, and fix the flight animation
- Every component now has a `url` (schema + JSON) pointing to the shop it
  was priced from; surfaced in the run detail and the BOM (new "Купить"
  column). Wire/switch/capacitor entries also expose gauge, ratings,
  single-vs-bank values; detail reports coil inner/outer diameter (=tube
  outer), tube inner bore + wall, projectile mass in grams and bore fit.
- Animation was wrong: it stitched flight+discharge segments by sorting on
  time, which tore the trajectory into discontinuities. Rebuilt it
  sequentially -- coast (ballistic) between discharges + exact discharge
  trajectory, global x = coil center + local discharge_x -- so the slug
  moves continuously and coils sit at their real positions. Cleaner
  visuals: labeled coils that glow gold on discharge, blue slug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 15:44:26 +05:00
jze9
35cbbb04c6 Physical tube geometry, 200g projectiles, and single-pulse discharge
Three fixes from user feedback:

1. Single-pulse discharge (user spotted 3 current humps for one stage):
   a thyristor fires ONCE per shot -- you can't recharge the cap in
   microseconds. The discharge now terminates at the first current zero
   OR first local minimum (where the slug starts pumping current back),
   whichever comes first. Residual coil energy at cutoff is accounted as
   freewheel-diode dissipation using the exact saturating magnetic energy
   integral, so energy still balances. Verified: 3 humps -> 1.

2. Real tube geometry: the genome now carries tube INNER diameter (bore,
   the projectile flies through) and wall thickness; outer diameter =
   inner + 2*wall = the coil's inner diameter (which drives the field).
   The projectile must fit the bore (diameter < inner - clearance).

3. Projectiles up to 200 g: diameter to 28mm, length to 150mm, with mass
   capped at 200g (length clamped by density).

Detail/BOM now report inner/outer/wall tube diameters, projectile mass in
grams, and whether it fits the bore. Same single-pulse + eddy physics
mirrored into the GPU batch integrator. Evolutionary polish updated for
the new tube params. All test groups pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 15:33:47 +05:00
jze9
4d16825f7d Add eddy-current losses in the slug -- the missing loss channel
The user caught the evolution reporting 83.9% efficiency, which is
unphysical (real coilguns are single-digit %). Root cause: the model's
only loss channel was copper resistance; iron had no losses at all, so
the reluctance force came "for free" and the optimizer climbed into that
corner.

Adds eddy-current loss: the solid steel slug acts as a shorted secondary
(1-turn transformer), and its reflected resistance R_eddy = (wM)^2/R_e
is added to the circuit while the slug is inside the coil (x overlap(x)).
Energy now honestly goes to slug heating instead of kinetic. The formula
matches the classical solid-cylinder eddy loss (P ~ sigma*w^2*B^2*a^4),
so it's physically grounded, not tuned to a target. Wired through
schema/JSON (slug resistivity), losses.py, circuit.py, stage.py, and the
GPU batch integrator; energy conservation still holds (0.025%).

Effect: best efficiency 83.9% -> ~47%, and the distribution is now
realistic (median ~0%, most configs single-digit). 47% is still an
optimistic ceiling -- it's the optimizer's single best exploit, and the
model still omits tube friction, air drag, skin-effect field penetration
(skin depth ~0.44mm < slug radius), and timing imperfection. Hysteresis
computed but not in the dynamics (negligible, ~1e-4 J vs eddy). 89 tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 15:03:45 +05:00
jze9
4f94811b49 Stage 9 core: GPU-ready batched discharge integrator, validated vs scipy
The expensive part of the sim (the 4-state [Q,I,x,v] discharge ODE) now
has a vectorized fixed-step RK4 integrator that runs N configs at once as
arrays, with the same saturating-iron physics as the scipy path. One code
path runs on numpy (CPU) or cupy (GPU) via a backend shim
(gpu/backend.py) -- so it's developed and validated WITHOUT a GPU, then
runs on GPU unchanged.

Honesty gate (per the project's no-smoke-and-mirrors rule): validated
against the scipy solve_ivp reference on 4 configs -- exit velocity, peak
current, and dissipated energy all match within ~1-3%, plus a batched
energy-conservation check. GPU speed is worthless if the numbers differ,
so this test is the point.

Benchmark: 5000 configs in 6.8s = 733/s single-threaded numpy, already
faster than the 5-core scipy sweep (~430/s); cupy parallelizes the same
arithmetic for the real win at large N.

Not yet batched: flight-to-trigger, multi-stage chaining, genome decode
(so this isn't a drop-in sweep replacement yet -- it's the validated
core). Next: batch the full pipeline + run cupy on the server GPU.

89 tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 02:18:34 +05:00
jze9
85425e27bc Speed up dashboard and add exit-velocity ranking
- Web was slow because run_detail and the plot/GIF renderers loaded the
  ENTIRE runs table to find one row by id (O(n) on a 200k-row DB, seconds
  per request). Now query by PRIMARY KEY (run_id): run_detail drops from
  seconds to ~1ms. The KPD histogram is aggregated in SQL instead of
  streaming every feasible row into Python.
- Dashboard now toggles the top table between "по КПД" and "по скорости"
  (top_by_velocity, indexed on exit_velocity_mps); the best-of card shows
  both best efficiency and best exit velocity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 02:04:58 +05:00
jze9
b0a35e08d5 Set realistic pulse-current ratings for electrolytic capacitors
Grounded in the user's hands-on experience: 8 electrolytics soldered in
parallel drove a nail hard enough to dent a metal PSU wall (~few joules,
~30-60 m/s -- matching the model's output range). So cheap electrolytics
in parallel packs really do deliver high pulse current; the DB's old
values were ripple (continuous) ratings, not single-shot pulse capability.
Updated max_current_a to pulse estimates that scale with size/ESR (bigger,
lower-ESR cans sustain more), clearly labeled ESTIMATE for coilgun pulse
use in each source field. A parallel bank sums these (n_parallel), so an
8-pack models the user's real setup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:53:26 +05:00
jze9
7f040c3cf5 Fix two physics bugs the user caught: unbounded field and ignored current limits
1. Iron saturation now in the dynamics (was the "13 Tesla" bug). Flux
   linkage lambda(x,I) = L_air*I + L_iron*overlap(x)*g(I) with
   g(I)=I_sat*tanh(I/I_sat) saturating at the current where iron reaches
   B_sat. Both the circuit back-EMF (dlambda/dx) and the force (coenergy,
   dW'/dx) derive from the SAME lambda, so energy stays conserved (0.025%
   error) AND the field caps at B_sat instead of running to 13 T. Reduces
   to the old 0.5*I^2*dL/dx in the low-current limit. On the config that
   reported 60% efficiency / 11.6 T, it now gives 40% / 1.90 T.

2. Switch surge-current limit is now a hard feasibility constraint: a
   config whose peak discharge current exceeds the switch's pulse rating
   (continuous * surge factor per device kind) is infeasible -- otherwise
   the optimizer "wins" with configs that vaporize their own thyristor
   (e.g. 119 A through a 12 A BT151). Capacitor current stays a warning
   (electrolytics tolerate pulses; DB has continuous not pulse ratings).

Regression tests added for both (field cap near saturation, energy
conservation under strong discharge). 87 tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:45:26 +05:00
jze9
b3151e94a5 Document capacitor banks, dashboard graphics, and server deployment status
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:02:21 +05:00
jze9
1ca807dcbe Embed per-run plots and flight animation in the web dashboard
Clicking a config in the dashboard now shows its velocity/field/current
plots (rendered on the fly by /api/run/<id>/plot/<kind>.png) plus an
on-demand GIF of the slug flying through the coils
(/api/run/<id>/anim.gif). Matplotlib rendering is serialized behind a
lock since the Agg pyplot state machine isn't thread-safe and the server
is threaded. Verified end to end: endpoints return real PNG/GIF bytes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:57:02 +05:00
jze9
950be5fcf2 Model capacitor banks (series for voltage, parallel for current/capacitance)
Per user: each stage's energy source is now a bank of real capacitors,
n_series x n_parallel, not a single cap.

- components/schema.py CapacitorBank.from_spec: series multiplies voltage
  and ESR and divides capacitance; parallel multiplies capacitance and
  max current and divides ESR; price and count scale with total cap count.
  Duck-typed as a drop-in for CapacitorSpec in StageConfig.
- search_space: cap_series (1-6) and cap_parallel (1-8) per stage gene,
  sampled/mutated/repaired; decode builds the bank; charge voltage capped
  by min(bank voltage, switch rating)
- objective.build_detail records the full bank composition (base part,
  series/parallel, count, totals) and current-capability diagnostics:
  bank_current_over_limit / switch_current_over_limit vs peak discharge
  current (a warning, not a hard reject -- DB holds datasheet not pulse
  ratings; parallel caps already help for real via lower ESR)
- BOM shows "N шт (Sпосл × Pпар)" with per-unit and total price

Already supported and confirmed present (also user-requested): per-coil
independent wire gauge (wire_idx per stage) and computed wire length
(winding_geometry.total_wire_length_m, drives resistance/cost/DB detail).

83 tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:53:33 +05:00
jze9
70b732d170 Enrich results DB, add process log, and build a live web dashboard
Requested during deployment: make the DB maximally detailed, add an
experiment process log, and a web UI to watch runs live.

- optim/objective.py build_detail(): each stored run now records
  inter-coil distances, absolute coil positions along the tube, every
  component part number/spec, winding geometry, computed physics
  (resistance, air inductance, peak current, peak field), and per-stage
  outcome (entry/exit velocity, sensor timing, energy breakdown)
- optim/progress_log.py: append-only <db>.log with progress %, feasible
  rate, throughput, ETA, and a line on each new best efficiency; wired
  into both sweep and evolve
- src/gausse/web/: stdlib-only (http.server) dashboard `gausse serve` --
  self-contained HTML polling /api/overview every 3s: counters, KPD
  histogram, top-configs table with per-run drill-down, failure reasons,
  live log tail. No new dependencies.
- docker-compose.yml: `web` service on port 8000
- 77 tests pass locally and in Docker

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:27:50 +05:00
jze9
d487021c2f Document Stage 8 end-to-end verification findings in PLAN.md
Real 5000-run sweep + 600-genome evolution through docker compose run,
not a synthetic smoke test. Two concrete outcomes worth recording:

1. Quantitative confirmation of the sensor discussion from the original
   planning conversation: inductive pickup sensor reached only 0.2%
   feasibility (6/2965 configs using it), vs ~45-47% for optical/Hall,
   because its trigger threshold sits above the fixed launch velocity.
2. Best honest result after the efficiency-metric fix: single stage,
   80.97% efficiency, 26.4 m/s, ~1029 RUB in real parts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 21:06:42 +05:00
jze9
1029f40318 Fix efficiency metric that could exceed 100% (real bug found by Stage 8 sweep)
A real 5000-run sweep through Docker found the evolutionary optimizer's
best genome reporting efficiency=387%. Root cause: efficiency was
computed as exit_kinetic_energy / capacitor_energy_in, but exit kinetic
energy includes the fixed initial_v_mps launch push (a modeling
assumption, not something paid for by the capacitors) -- for a light
enough projectile, that free energy dominates and the ratio blows past 1.

Fixed by using kinetic_energy_delta (the energy the coils actually added)
instead of absolute exit KE. The per-stage energy identity (energy_in =
remaining_cap + dissipated + kinetic_delta) guarantees kinetic_delta <=
energy_in, so this metric can never exceed 1.0 -- provably, not by luck.
Added a regression test with a light projectile + large initial velocity
reproducing the exact failure mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 21:03:30 +05:00
jze9
57a83cd4f8 Fix float64 overflow in inductive sensor's sech^2 bump function
cosh(z)**2 overflows past |z|~355, which happens routinely once solve_ivp
evaluates the sensor event far from x_sensor over a wide time budget --
correct result (bump->0) but with a RuntimeWarning on every call, which
would flood stderr across a million-run sweep. Clamp the argument to
±20 (already ~1e-17, physically indistinguishable from further growth)
before computing cosh. Caught by running a real 500-run sweep through
Docker as part of Stage 8 verification, not by unit tests alone -- added
a regression test asserting no warning and a finite result far from the
sensor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:55:59 +05:00
jze9
65d8b633ad Wire up reporting (plots, BOM, summary, animation) and a working CLI (Stage 7)
- report/plots.py: current/field/velocity plots per stage, Agg backend so
  it works headless in Docker/on a server
- report/bom.py: itemized bill of materials with real component prices
- report/summary.py: honest text/JSON summary including a
  "model limitations" section and a saturation warning flag per stage
- report/animate.py: the visualization the user explicitly asked for --
  a GIF of the slug flying through the tube with each coil glowing by its
  instantaneous current, reconstructing the pre-trigger ballistic flight
  segment (not just the stored discharge phase) for a continuous timeline
- cli.py: sweep/evolve/simulate/report subcommands now actually call the
  underlying modules instead of being stubs
- Extended StageResult/StageOutcome with the coil/entry-state fields the
  report layer needed (mu_eff, turns, coil_length, entry_x/v) rather than
  recomputing them by other means

Verified end-to-end through `docker compose run`, not just pytest: a real
sweep (30 runs, 11 feasible) followed by `report --animate` produced a
correct GIF/plots/BOM/summary for the actual best result found (32.8%
efficiency, 982 RUB) -- not a synthetic fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:50:32 +05:00