Files
gausse/PLAN.md
2026-07-07 01:02:21 +05:00

79 lines
18 KiB
Markdown
Raw 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.
# План: Gauss-ускоритель — симулятор и оптимизатор
Многоступенчатый электромагнитный ускоритель ферромагнитного цилиндра (coilgun).
Каждая ступень: разгонная катушка (Cu/Al провод на пластиковой трубке) + батарея
конденсаторов через тиристор/MOSFET, и датчик прохода снаряда перед катушкой
(компенсация задержки включения ключа). Датчик — два конкурирующих варианта:
индукционная катушка (сигнал ~ скорости снаряда, слаб на медленных ступенях) и
оптопара/датчик Холла (не зависит от скорости) — оптимизатор сравнивает оба.
Цель поиска — КПД (кинетическая энергия снаряда на выходе / энергия во всех
конденсаторах). Скорость и стоимость — вторичные метрики. Число ступеней N,
геометрия/материал снаряда, все компоненты — часть пространства поиска, а не
фиксированные входы. Компоненты — реальные, розничные (Проконтакт/procontact74.ru,
ChipDip, Cable.ru и др.).
Требование: миллионы прогонов (Monte Carlo/LHS sweep + эволюционный поиск),
**все** результаты — успешные и неудачные, с честной причиной отказа — пишутся
в SQLite. Никаких приукрашенных цифр — если модель показывает низкий КПД или
нереализуемость, это тоже результат.
**Найденная и исправленная ошибка (Этап 3):** насыщение сердечника изначально
клэмпилось только в механическом уравнении (F=0.5·I²·dL/dx), но не в
электрическом (наведённая ЭДС всё ещё считалась по полной dL/dx) — это
незаметно ломало точный энергобаланс на ~15%. Тест на сохранение энергии
(этого же честного протокола, который просил пользователь) это поймал.
Решение: клэмп насыщения убран из динамики (F=0.5·I²·dL/dx без клэмпа,
энергобаланс теперь точен до ~0.02%), а `solenoid_field_estimate_tesla`/
`saturation_scale` оставлены как ДИАГНОСТИКА — `StageResult.saturation_warning`
честно предупреждает, когда конфигурация физически выходит за пределы
насыщения материала снаряда, не подменяя динамику. Полная нелинейная
L(x, I)-модель с coenergy-выводом силы — в разделе "ограничения модели"
как будущая работа, не как текущая гарантия точности.
Полный план архитектуры: см. историю обсуждения / `physics`, `sim`, `optim`,
`storage`, `report` модули ниже.
**Docker — основной способ запуска** (по требованию пользователя): `Dockerfile`
+ `docker-compose.yml` в корне репозитория, образ собран и провалидирован —
внутри контейнера запускаются все 37 тестов и CLI (`docker compose run --rm
--entrypoint pytest gausse -q`). Результаты (SQLite и отчёты, когда появятся)
монтируются в `./results` на хосте, чтобы переживать пересборку образа.
## Чек-лист этапов
- [x] **Этап 0 — Скелет проекта**: `pyproject.toml`, `README.md`, `.gitignore`, пакеты `src/gausse/*`, этот файл, первый коммит.
- [x] **Этап 1 — База реальных компонентов**: провод Cu/Al, конденсаторы, тиристоры/MOSFET/IGBT, датчики (Hall/оптика реальные, индукционный — оценка), материалы снаряда → `components/data/*.json`. Каждая запись честно помечена REAL (с URL источника) или ОЦЕНКА (с указанием основания); алюминиевый провод и ёмкости 1000/2200мкФ — низкая уверенность в цене, явно отмечено.
- [x] **Этап 2 — Физическое ядро**: `physics/constants.py`, `inductance.py` (Уилер + ферромагнитный сердечник + размагничивание), `force.py`, `circuit.py` (ОДУ RLC). Юнит-тесты: аналитическое RLC-решение, согласованность dL/dx.
- [x] **Этап 3 — Датчики и одна ступень**: `physics/sensors.py` (оба типа как события solve_ivp), `sim/stage.py` (полёт → триггер → разряд → энергобаланс). Тест на сохранение энергии + найден/исправлен баг насыщения (см. выше).
- [x] **Этап 4 — Многоступенчатая цепочка**: `sim/coilgun.py` — сквозная координата, отбраковка нереализуемых конфигураций. Дымовой тест на реальной базе компонентов (`test_real_components_smoke.py`) подтверждает: полный путь реальные JSON → физика → цепочка работает (пример: 22.3 м/с, КПД 3.4% — честный неоптимизированный результат).
- [x] **Этап 5 — Хранилище результатов**: `storage/schema.py` + `database.py` — SQLite (WAL), таблица `runs` со всеми прогонами (успех/провал + честная причина), однопроцессный писатель поверх многопроцессной очереди. Тест с реальными `multiprocessing.Process` (не моками) поймал реальную проблему: коллизия `run_id` роняла writer и молча останавливала осушение очереди на весь sweep — писатель теперь переживает ошибку вставки одной записи (лог в stderr) и продолжает работу.
- [x] **Этап 6 — Поиск и оптимизация**: `optim/search_space.py` (геном переменной длины — число ступеней тоже эволюционирует), `objective.py` (fitness=КПД, честный мягкий штраф за нереализуемость пропорционально пройденным ступеням), `optim/sweep.py` (параллельный Monte Carlo, каждый прогон в SQLite), `optim/evolutionary.py` ((μ+λ)-ГА + Nelder-Mead полировка непрерывных параметров лучшего генома). Тест поймал реальный баг в `crossover()` — IndexError при скрещивании двух одноступенчатых геномов (пустой список зазоров), исправлено и покрыто регрессией.
- [ ] **Не сделано**: отдельный "дешёвый квазистатический предфильтр" перед полным ODE не реализован (кроме уже встроенной в `sim/stage.py` дешёвой проверки порога индукционного датчика). Если миллионы прогонов на сервере окажутся слишком медленными, это первое место для ускорения.
- [x] **Этап 7 — Отчётность**: `report/plots.py` (ток/поле/скорость по ступеням), `summary.py` (честная сводка + раздел "ограничения модели"), `bom.py` (спецификация деталей с ценами), `animate.py` (GIF: снаряд летит по трубе, катушки светятся пропорционально току — по запросу пользователя), `cli.py` (`gausse sweep/evolve/simulate/report`, все 4 команды реально вызывают соответствующие модули, не заглушки). Проверено сквозным прогоном через `docker compose run`: sweep → report с анимацией на реальной базе компонентов, лучший найденный результат — КПД 32.8% за 982₽ (не выдумано, из реального SQLite).
- [x] **Этап 8 — Сквозная проверка**: реальный прогон через `docker compose run` — sweep на 5000 конфигураций (16 воркеров, ~59с, 30.5% реализуемо), затем `evolve` (15 поколений x 40 особей = 600 оценок, ~18с) поверх той же базы, затем `report --top 3 --animate`.
- **Найден и исправлен реальный баг именно на этом этапе**: КПД лучшего генома после `evolve` показал 387% — оказалось, `efficiency` считался как `exit_kinetic_energy / energy_in`, а `exit_kinetic_energy` включает фиксированный "бесплатный" толчок `initial_v_mps`, не учтённый в `energy_in`. Для лёгкого снаряда этот толчок доминировал и КПД пробивал 100%. Исправлено на `kinetic_energy_delta / energy_in` (энергия, реально добавленная катушками) — эта величина математически не может превысить 1 (следует из поэтапного энергобаланса). После исправления лучший честный результат: **1 ступень, КПД 80.97%, скорость 26.4 м/с, снаряд Ст3 ⌀4мм x 30мм, стоимость 1029₽** (провод cu-petv2-1.0mm, конденсатор 22мкФ/400В, ключ IRG4PC50F, оптический датчик).
- **Честное сравнение датчиков** (запрос из первого обсуждения плана) — на 5000 прогонах: индукционный датчик (`inductive-pickup-lm393`) дал **0.2% реализуемых** конфигураций (6 из 2965, где он стоял хотя бы на одной ступени), тогда как оптический (TCST2103) и Холла (A3144E) — **~45-47%** реализуемых каждый. Это количественно подтверждает опасение, высказанное в самом начале обсуждения плана: индукционная катушка-датчик ненадёжна именно потому, что требует скорости выше её порога (здесь — фиксированный старт 3 м/с как раз ниже порога срабатывания реального компонента), тогда как оптика/Холл не зависят от скорости.
- Итог: сквозная цепочка (реальные компоненты → физика → поиск → SQLite → отчёт с графиками/BOM/анимацией) работает и произвела не выдуманный, а посчитанный и перепроверенный результат.
- [x] **Обогащённая БД + лог процесса + веб-морда** (по запросу пользователя при деплое): каждая запись `runs.decoded_summary_json` теперь содержит расстояния между катушками (`inter_stage_gaps_m`), абсолютные позиции катушек вдоль трубы, все номиналы деталей, геометрию намотки, вычисленную физику (индуктивность/сопротивление/пиковый ток/поле) и результат каждой ступени (вход/выход скорость, тайминги датчика, энергобаланс) — см. `optim/objective.py::build_detail`. Ход эксперимента пишется в `<db>.log` (`optim/progress_log.py`): прогресс, доля реализуемых, скорость, ETA, отметки нового лучшего КПД. Веб-дашборд `gausse serve` (`src/gausse/web/`, только stdlib `http.server`, self-contained HTML) — счётчики, гистограмма КПД, топ конфигураций с drill-down, причины отказа, хвост лога, автообновление 3с. В `docker-compose.yml` сервис `web` на порту 8000. 77 тестов, проверено в Docker.
## Батареи конденсаторов + графики в веб-морде (по запросу пользователя)
- **Батареи конденсаторов** (`components/schema.py::CapacitorBank`): каждая ступень собирает банку из N одинаковых конденсаторов — `n_series` (1-6, складывает напряжение и ESR, делит ёмкость) × `n_parallel` (1-8, складывает ёмкость и макс.ток, делит ESR). Цена и количество масштабируются. Параметры банки — в геноме (`cap_series`, `cap_parallel` на ступень), в BOM ("N шт (Sпосл × Pпар)") и в подробной БД (`capacitor_bank`: состав, суммарные C/V/ESR/макс.ток). Диагностика по току: `bank_current_over_limit` / `switch_current_over_limit` (предупреждение, не жёсткий отказ — в базе паспортный, а не импульсный ток; параллельные конденсаторы дают реальный выигрыш через сниженный ESR).
- **Уже было (тоже запрашивалось)**: разная толщина провода на разных катушках (`wire_idx` на ступень) и длина провода (`winding_geometry.total_wire_length_m` → сопротивление/цена/БД).
- **Графики в дашборде**: клик по прогону → графики скорость/поле/ток строятся на лету (`web/render.py`, `/api/run/<id>/plot/<kind>.png`) + GIF пролёта по кнопке (`/api/run/<id>/anim.gif`). Рендер matplotlib сериализован локом (Agg не потокобезопасен). 86 тестов.
## Развёртывание на сервере (Proxmox 192.168.20.254 / VM 106 "test-math" = 192.168.20.45)
**СТАТУС: развёрнуто и работает на CPU.** VM 106: RAM 2→6ГБ, Docker+git, клон с gitea, образ собран, 86 тестов проходят в контейнере на сервере. Веб-морда живёт на **http://192.168.20.45:8000** (сервис `web`, restart unless-stopped), sweep пишет в `~/gausse/results/gausse.sqlite3` (+ лог `.log`). Дашборд доступен снаружи (Proxmox firewall порт 8000 не блокирует). Обновление кода: на VM `cd ~/gausse && git pull && docker compose build && docker compose up -d web`.
Обнаружено при осмотре: GTX 1070 (`10de:1b81`+аудио `10de:10f0`) стоит в Proxmox-хосте, **уже привязана к vfio-pci**, blacklist'ы nouveau/nvidia прописаны, IOMMU включён (`intel_iommu=on`), карта одна в IOMMU-группе 1. То есть хост заранее подготовлен под проброс — можно пробросить в VM **без перезагрузки хоста** (не заденет другие VM: nextcloud/minecraft/web-player). Целевая VM 106 = 5 ядер, 2ГБ RAM (мало для CUDA+Docker, поднять). GPU ей ещё не назначен.
- [ ] Пуш на gitea (`gitea.jze9.ru/jze9/gausse.git`) или rsync прямо в VM.
- [ ] `hostpci0: 0000:01:00,pcie=1` в конфиг VM 106, поднять RAM, перезагрузить VM 106.
- [ ] В VM: NVIDIA-драйвер + CUDA + Docker + nvidia-container-toolkit.
- [ ] Развернуть проект, запустить sweep (пока CPU!), поднять `gausse serve` (порт 8000).
- [ ] Дать доступ: веб-морда http://192.168.20.45:8000, SQLite `results/gausse.sqlite3`, лог `results/gausse.sqlite3.log`.
- [ ] **Этап 9 — GPU-ускорение массового sweep (сервер с GTX 1070)**: после того как CPU/`scipy.solve_ivp`-модель провалидирована тестами (Этап 2-4) — батч-версия интегратора с ФИКСИРОВАННЫМ шагом (RK4/полу-неявная схема), считающая сразу N траекторий параллельно как один тензор (`cupy`, если доступна CUDA, иначе векторизованный `numpy`/`numba`), для прогона по-настоящему миллионов конфигураций на сервере. Важно: сначала корректность на CPU, потом скорость на GPU — численные результаты GPU-пути должны сверяться с CPU-эталоном на контрольной выборке, чтобы ускорение не подменило точность честными числами "для галочки".