Files
gausse/PLAN.md

171 lines
26 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, оптический датчик). *(Историческая запись: это физика v1 БЕЗ потерь в железе, скина и трения — после их добавления такие КПД невозможны, см. «хронологию честности» ниже.)*
- **Честное сравнение датчиков** (запрос из первого обсуждения плана) — на 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.47**)
**СТАТУС: развёрнуто, работает АВТОНОМНО (CPU-эволюция под systemd), GPU проброшен и рабочий.**
- VM 106: Ubuntu 24.04, 5 ядер, 12ГБ RAM. После перевода на q35 (нужен для
PCIe-проброса) интерфейс переименовался и IP сменился **.45 → .47**
(netplan починен привязкой по MAC).
- [x] Пуш на gitea (`gitea.jze9.ru/jze9/gausse.git`) — рабочий процесс: пуш с
локальной машины → `git pull` на VM.
- [x] GPU: `qm set 106 --machine q35 --hostpci0 0000:01:00,pcie=1`; в VM
драйвер NVIDIA 580 + CUDA, `cupy-cuda12x[ctk]` в `~/gausse/.venv`.
`nvidia-smi` видит GTX 1070 без Code 43.
- [x] Веб-морда: **http://192.168.20.47:8000** (Docker-сервис `web`).
- [x] **Автономная эволюция — systemd-сервис `gausse-evolve`** (пользователь
явно потребовал: «алгоритм должен считать сам, годами, без тебя»):
`/etc/systemd/system/gausse-evolve.service` крутит `evolve_forever.sh`
вечный цикл `run_evolution(80 поколений × 300 особей, polish=True)` с новым
случайным семенем на каждом заходе (свежая популяция = «революция» против
застревания в локальном оптимуме). `Restart=always` + `enabled` → переживает
и падение процесса, и перезагрузку VM. Всё пишет в одну базу
`~/gausse/results/gausse.sqlite3`; «запланировано N» на дашборде — план
ТЕКУЩЕГО цикла, сам поиск бесконечен. Управление: `sudo systemctl
status|stop|restart gausse-evolve`, лог `journalctl -u gausse-evolve`.
- Обновление кода на VM: `cd ~/gausse && git pull && docker compose build web
&& docker compose up -d web && sudo systemctl restart gausse-evolve`.
- [x] **Этап 9 — GPU-ускорение массового sweep — СДЕЛАН**:
`gpu/batch_integrator.py` — батч-RK4 фиксированного шага [Q,I,x,v] на N
конфигураций одним тензором (numpy/cupy через `xp`), та же физика, что
CPU-путь (общий `sim/stage.build_stage_physics` — один источник истины).
Весь RK4-шаг слит в ОДНО `cupy.fuse`-ядро → **GTX 1070: 740 конф/с vs 193
на numpy = 3.8x** (наивный порт был 1.3x — упирался в запуск ядер);
SYNC_EVERY=256 убирает device→host синхронизацию на каждом шаге.
`gpu/batch_sweep.py` — МНОГОСТУПЕНЧАТЫЙ sweep раунд-за-раундом (подлёт
аналитически, состояние снаряда переносится между раундами), CLI
`gausse sweep --gpu`. Требование честности выполнено: GPU сверен с
CPU-эталоном — расхождение exit_v **0.084%** (tests/test_batch_sweep.py,
test_batch_integrator.py). Стиффные конфиги, переполняющие фикс-шаг,
честно бракуются (blew_up), а не записываются мусором.
- [ ] (опция) дослить cut-логику в ядро (>3.8x); GPU-батч внутрь эволюции.
## Физика: найденные завышения КПД и их исправления (хронология честности)
Пользователь ловил нереальные цифры; каждое исправление РОНЯЛО КПД к
реальности. Версия модели пишется в каждую запись БД (`model_version`),
прогоны разных версий не смешиваются.
1. **Поле 13 Тл** → насыщение железа через единое потокосцепление
λ(x,I)=L_air·I+L_iron·overlap(x)·g(I), g=I_sat·tanh(I/I_sat); сила и ЭДС
из одного λ (coenergy) → энергия сохраняется, поле упирается в B_sat.
2. **Ток сверх рейтинга ключа** → жёсткая отбраковка по импульсному пределу.
3. **КПД 83.9%: не было потерь в железе** → вихревые токи: снаряд =
короткозамкнутый виток, отражённое R_eddy=(ωM)²/R_e × overlap(x)
(physics/losses.py). Лучший упал 83.9% → ~47%, медиана ~0%.
4. **Три горба тока за выстрел** (физически невозможно — конденсатор не
перезарядить за мкс) → одиночный импульс: обрыв на первом нуле тока ИЛИ
первом локальном минимуме; остаточная энергия катушки → freewheel-диод,
считается точно через magnetic_energy (с насыщением). Энергобаланс сошёлся.
5. **gausse-physics-v2 (2026-07-08)**: скин-эффект + эффект близости обмотки
(метод Доуэлла на ω=1/√(LC), `physics/ac_resistance.py`) — для толстого
провода во многих слоях R_AC в разы выше DC; **трение о трубку**
(0.35·m·g) и **сопротивление воздуха** (½ρ·Cd·A·v²) во всей динамике
(разряд, подлёт с событием «снаряд остановлен трением», GPU-ядро,
аналитический межступенчатый пролёт — точная квадратура); паспортные
импульсные токи ключей из даташитов (`pulse_current_a`: ITSM/IDM/ICM)
вместо generic-множителей + починен вводивший в заблуждение флаг
(`switch_current_over_continuous_rating` — норма для импульса,
`switch_current_over_pulse_limit` — брак). КПД ступени теперь может быть
слегка отрицательным (трение съело больше слабой катушки) — это честно.
Для сравнения на одной и той же эволюции: v1 давал «38%» там, где v2 даёт
~8% — столько стоили неучтённые скин и трение.
## Требования пользователя, закрытые после Этапа 8
- **Снаряд до 200 г** (⌀ до 28мм, длина до 150мм, длина зажата по плотности).
- **Труба — часть поиска**: геном несёт внутренний диаметр (бор) + толщину
стенки; внешний диаметр трубы = внутренний диаметр катушки (влияет на
поле); снаряд обязан влезать в бор с зазором.
- **До 10 ступеней**; у каждой катушки СВОЙ датчик на своём расстоянии
(sensor_to_coil_distance_m на ступень; datчик не может попасть в предыдущую
катушку — repair()).
- **Ссылки на магазины** (`url`) у каждого компонента: в JSON-базе, BOM
(«Купить») и в детализации прогона.
- **Максимально детальная запись** в БД (`build_detail`): труба
внутр/стенка/внеш, масса снаряда в граммах и влезает-ли-в-бор,
внутр/внеш диаметры катушки, состав банки конденсаторов, DC/AC
сопротивление обмотки, все позиции датчиков и катушек вдоль трубы,
энергобаланс каждой ступени.
- **Анимация переделана** (была «время вырезается, результата не видно»):
кадры на равномерной сетке ФИЗИЧЕСКОГО времени (а не по индексам массива,
из-за чего плотные точки разряда съедали все кадры и подлёт выпадал),
множитель замедления в кадре; в кадре сам результат эксперимента — шапка
(выход м/с, КПД, ступени, энергия, масса), панель тока каждой катушки во
времени, панель скорости снаряда, бегущий курсор, датчики на трубе.
## Ограничения модели / следующие шаги по реализму (если потребуются)
- Поршневой эффект воздуха в трубе (учтено только лобовое сопротивление).
- Диффузия поля в снаряде (наш r_eddy — сосредоточенная оценка 1-го порядка).
- Спектр импульса для Доуэлла (взята одна характерная частота).
- Нагрев провода за импульс/серию; коэффициент трения не измерен (0.35).
- Гистерезис посчитан (losses.hysteresis_energy_j) и СОЗНАТЕЛЬНО не в
динамике: <0.1% энергии выстрела включение изображало бы ложную точность.
- Индуктивность Уилер+размагничивание, не МКЭ/FEA.
- Осторожно: не тюнить модель под «желаемую» цифру КПД только физика.