- optim/worker_context.py: shared per-process DB/bounds init, extracted
from sweep.py so evolutionary.py doesn't reach into another module's
private state
- optim/objective.py: build_run_record() shared between sweep and
evolutionary so both write identically-shaped rows
- optim/evolutionary.py: tournament selection, whole-stage-swap crossover,
elitism, structural mutation (stage count itself evolves), then a serial
Nelder-Mead polish of the best genome's continuous parameters with
discrete component choices frozen
- Found and fixed a real crash: crossover() indexed into an empty
inter_stage_gaps_m list when both parents had only 1 stage (0 gaps),
raising IndexError. Fixed the fallback to only choose from gaps that
actually exist, defaulting to a neutral value (clipped later by
repair()) when neither parent has one. Added a regression test.
- Verified 59/59 tests pass both locally and inside the Docker image
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- storage/schema.py: `runs` table recording every evaluated configuration,
feasible or not, with an honest infeasible_reason instead of dropping
failures
- storage/database.py: WAL-mode SQLite, single-writer-over-a-queue pattern
for safe concurrent writes from many sweep worker processes
- Testing with real multiprocessing.Process (not mocks) caught a genuine
bug: a duplicate run_id raised inside the writer loop and killed the
writer process, silently halting queue drainage for the rest of a
sweep. Fixed by catching the insert error per-record and logging to
stderr instead of crashing; added a regression test for it.
- Verified locally and inside the Docker image (43/43 tests both ways)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dockerfile (non-root user, UID 1000 to match typical host users so bind
mounts don't end up root-owned) + docker-compose.yml with a ./results
volume for the SQLite database and reports that Stage 5/6 will write.
Verified by actually building the image and running the full test suite
and CLI inside the container, not just writing the files and assuming
they work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wire (Cu/Al ПЭТВ-2), capacitors (350-450V electrolytic bank), switches
(КУ202Н/BT151 SCR, IRFP250/IRFP4468 MOSFET, IRG4PC50F IGBT), sensors
(A3144E Hall, TCST2103 optical, LM393-based inductive pickup), and
projectile materials (Ст3, Сталь 10, армко-железо) sourced from
procontact74.ru/chipdip.ru/cable.ru research.
Every entry's `source` field states plainly whether the number is a
REAL scraped retail price/spec (with URL) or an ОЦЕНКА (estimate) with
its basis (e.g. aluminum magnet wire has no self-service retail price
in RU stores, so it's priced as a fraction of equivalent copper) — no
value is presented as real when it isn't. Added a smoke test proving
the real database runs end-to-end through the full physics + chaining
pipeline, not just against synthetic test fixtures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- physics/sensors.py: optical/Hall (velocity-independent) and inductive
(velocity-scaled, sech^2 spatial sensitivity) trigger events for solve_ivp
- sim/stage.py: flight-to-trigger -> fire delay -> discharge -> energy
accounting, returning StageResult(feasible=False, reason=...) instead of
raising when a sensor never fires or discharge never commutates
- Found and fixed a real bug caught by the energy-conservation test: the
saturation clamp was applied to the mechanical force but not the
electrical back-EMF term, silently breaking energy balance by ~15%.
Removed the dynamic clamp (documented as a deferred nonlinear-L(x,I)
limitation) and kept saturation as a diagnostic-only warning
(StageResult.saturation_warning) so numbers stay honest rather than
quietly wrong. Balance error is now ~0.02%.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- components/schema.py + database.py: typed loader for real retail parts
(wire, capacitors, switches, sensors, projectile materials)
- physics/inductance.py: multilayer solenoid inductance (Wheeler) with
ferromagnetic-slug coupling via smooth overlap model and analytic dL/dx
- physics/force.py: F = 0.5*I^2*dL/dx with a saturation clamp
- physics/circuit.py: coupled [Q,I,x,v] discharge ODE
- Validated against textbook RLC analytical solutions (under/over/critically
damped) and energy conservation, not just spot-checked by eye
- PLAN.md updated with animation (per-run field visualization) and
GPU-accelerated batch sweep as explicit later stages
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
src-layout Python package with CLI stub (sweep/evolve/simulate/report
subcommands), pyproject.toml pinned to numpy/scipy/matplotlib, and PLAN.md
tracking the implementation roadmap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>