28 KiB
План: 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на хосте, чтобы переживать пересборку образа.
Чек-лист этапов
- Этап 0 — Скелет проекта:
pyproject.toml,README.md,.gitignore, пакетыsrc/gausse/*, этот файл, первый коммит. - Этап 1 — База реальных компонентов: провод Cu/Al, конденсаторы, тиристоры/MOSFET/IGBT, датчики (Hall/оптика реальные, индукционный — оценка), материалы снаряда →
components/data/*.json. Каждая запись честно помечена REAL (с URL источника) или ОЦЕНКА (с указанием основания); алюминиевый провод и ёмкости 1000/2200мкФ — низкая уверенность в цене, явно отмечено. - Этап 2 — Физическое ядро:
physics/constants.py,inductance.py(Уилер + ферромагнитный сердечник + размагничивание),force.py,circuit.py(ОДУ RLC). Юнит-тесты: аналитическое RLC-решение, согласованность dL/dx. - Этап 3 — Датчики и одна ступень:
physics/sensors.py(оба типа как события solve_ivp),sim/stage.py(полёт → триггер → разряд → энергобаланс). Тест на сохранение энергии + найден/исправлен баг насыщения (см. выше). - Этап 4 — Многоступенчатая цепочка:
sim/coilgun.py— сквозная координата, отбраковка нереализуемых конфигураций. Дымовой тест на реальной базе компонентов (test_real_components_smoke.py) подтверждает: полный путь реальные JSON → физика → цепочка работает (пример: 22.3 м/с, КПД 3.4% — честный неоптимизированный результат). - Этап 5 — Хранилище результатов:
storage/schema.py+database.py— SQLite (WAL), таблицаrunsсо всеми прогонами (успех/провал + честная причина), однопроцессный писатель поверх многопроцессной очереди. Тест с реальнымиmultiprocessing.Process(не моками) поймал реальную проблему: коллизияrun_idроняла writer и молча останавливала осушение очереди на весь sweep — писатель теперь переживает ошибку вставки одной записи (лог в stderr) и продолжает работу. - Этап 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дешёвой проверки порога индукционного датчика). Если миллионы прогонов на сервере окажутся слишком медленными, это первое место для ускорения.
- Не сделано: отдельный "дешёвый квазистатический предфильтр" перед полным ODE не реализован (кроме уже встроенной в
- Этап 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). - Этап 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/анимацией) работает и произвела не выдуманный, а посчитанный и перепроверенный результат.
- Найден и исправлен реальный баг именно на этом этапе: КПД лучшего генома после
- Обогащённая БД + лог процесса + веб-морда (по запросу пользователя при деплое): каждая запись
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/, только stdlibhttp.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).
-
Пуш на gitea (
gitea.jze9.ru/jze9/gausse.git) — рабочий процесс: пуш с локальной машины →git pullна VM. -
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. -
Веб-морда: http://192.168.20.47:8000 (Docker-сервис
web). -
Автономная эволюция — 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. -
Этап 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 раунд-за-раундом (подлёт аналитически, состояние снаряда переносится между раундами), CLIgausse sweep --gpu. Требование честности выполнено: GPU сверен с CPU-эталоном — расхождение exit_v 0.084% (tests/test_batch_sweep.py, test_batch_integrator.py). Стиффные конфиги, переполняющие фикс-шаг, честно бракуются (blew_up), а не записываются мусором.- cut-логика дослита: весь шаг = 2 fused-ядра (одно не влезает в лимит параметров CUDA 4096Б). Оценка 1000 геномов: 98.5с → 6.6с (15x), ~150 геномов/с на 1070, сверка с numpy-эталоном 0.001%.
- GPU-батч в эволюции:
run_evolution(use_gpu=True)оценивает всё поколение одним батчем (evaluate_genomes_gpu), та же формула фитнеса; полировка (Nelder-Mead) остаётся точной на CPU. Сервисgausse-evolveпереведён на venv+GPU: 120 поколений × 1000 особей, ~115/с стабильно.
Физика: найденные завышения КПД и их исправления (хронология честности)
Пользователь ловил нереальные цифры; каждое исправление РОНЯЛО КПД к
реальности. Версия модели пишется в каждую запись БД (model_version),
прогоны разных версий не смешиваются.
- Поле 13 Тл → насыщение железа через единое потокосцепление λ(x,I)=L_air·I+L_iron·overlap(x)·g(I), g=I_sat·tanh(I/I_sat); сила и ЭДС из одного λ (coenergy) → энергия сохраняется, поле упирается в B_sat.
- Ток сверх рейтинга ключа → жёсткая отбраковка по импульсному пределу.
- КПД 83.9%: не было потерь в железе → вихревые токи: снаряд = короткозамкнутый виток, отражённое R_eddy=(ωM)²/R_e × overlap(x) (physics/losses.py). Лучший упал 83.9% → ~47%, медиана ~0%.
- Три горба тока за выстрел (физически невозможно — конденсатор не перезарядить за мкс) → одиночный импульс: обрыв на первом нуле тока ИЛИ первом локальном минимуме; остаточная энергия катушки → freewheel-диод, считается точно через magnetic_energy (с насыщением). Энергобаланс сошёлся.
- 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% — столько стоили неучтённые скин и трение. - gausse-physics-v3: эволюция нашла «КПД 82%» → вскрытие генома: тонкий снаряд (⌀4мм) в толстой катушке (⌀29мм) — модель умножала ВСЮ L на (μ_eff−1), как будто железо заполняет всё сечение. Фикс: коэффициент заполнения A_снаряда/A_катушки (iron_fill_factor). Плюс лечение зависаний решателя: tanh-сглаживание сухого трения (разрывный sign(v) дробил шаг бесконечно), событие тока удержания ключа (передемпфированный хвост), полная формула отражения вихрей (ωM)²R₂/(R₂²+(ωL₂)²), гард вырожденных катушек (L/R<100нс). Бенч: 200 геномов 608с → 22с.
- gausse-physics-v4: эволюция нашла «30%» через МЕДЛЕННУЮ катушку (600+ витков, ~1мс, 36А — там слабы и Доуэлл, и вихревые, и трение). Неучтённая физика: за импульс поле проникает в сталь на глубину скин-слоя (~доли мм) — внутренность снаряда не намагничивается. Фикс: iron_penetration_fraction = 1−(1−δ/a)² множит fill factor. После v4 лучший КПД эволюции — единицы процентов, что соответствует реальным reluctance-пушкам. Урок зафиксирован: подозрительно красивый КПД = вскрывать лучший геном и искать обойдённый канал потерь.
Требования пользователя, закрытые после Этапа 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.
- Осторожно: не тюнить модель под «желаемую» цифру КПД — только физика.