Commit Graph

29 Commits

Author SHA1 Message Date
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
jze9
d73341d3a2 Add (mu+lambda) evolutionary optimizer with Nelder-Mead polish (Stage 6 complete)
- 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>
2026-07-06 20:36:10 +05:00
jze9
891ac8fee0 Add parallel Monte Carlo sweep engine writing every run to SQLite
optim/sweep.py: ProcessPoolExecutor workers sample+evaluate genomes in
parallel; each result (feasible or not) is pushed through the
queue-backed single writer built in Stage 5. Verified with real
multiprocessing (not mocks): 12-run sweep across 2 worker processes
lands all 12 rows in SQLite with parseable genome/summary JSON, and two
independent sweeps with the same seed produce byte-identical results.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:27:43 +05:00
jze9
56c7ab9c1d Add variable-length genome search space and objective function (Stage 6 pt.1)
- optim/search_space.py: Genome encodes only indices into the real
  component database plus continuous/discrete geometry parameters; number
  of stages is itself mutable via add/remove/duplicate-stage operators.
  repair() clips everything back into bounds after mutation/crossover so
  decode() never sees an invalid genome.
- optim/objective.py: evaluate() decodes a genome, computes real BOM cost
  from wire length/price and component prices, runs the full chain, and
  scores fitness = efficiency for feasible runs or a soft penalty scaled
  by how many stages it got through for infeasible ones (so the GA gets
  gradient instead of a wall).

Tested against the real component database (not synthetic fixtures) —
including an explicit infeasible case using the real inductive sensor's
actual sensitivity/threshold values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:26:16 +05:00
jze9
069ea848e2 Add SQLite results storage with a resilient multiprocess writer (Stage 5)
- 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>
2026-07-06 20:21:21 +05:00
jze9
acf846ab29 Add Docker as the primary way to build and run gausse
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>
2026-07-06 20:17:12 +05:00
jze9
f22fd89612 Populate real retail component database (Stage 1 complete)
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>
2026-07-06 20:06:27 +05:00
jze9
f8f6eaa765 Chain stages into a multi-stage coilgun; document an over-pull failure mode
- sim/coilgun.py: chains StageConfig results through a global coordinate,
  stops honestly on the first infeasible stage (with which stage and why),
  and computes overall efficiency = exit KE / total capacitor energy in
- Discovered while wiring up the chain: a high-capacitance single-stage test
  configuration let current stay high past the coil center, decelerating and
  reversing the slug (exit velocity negative). This is a real coilgun tuning
  failure mode (not a bug) since KE ~ v^2 doesn't care about direction and
  the energy balance still holds to ~1%. Kept it as an explicit regression
  test (test_overpowered_discharge_can_fling_slug_backward) instead of
  quietly picking parameters that hide it, and tightened the "kinetic energy
  increases" test to also require forward (exit_v > 0) motion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 20:02:05 +05:00
jze9
f9b77b3756 Add dual sensor models and single-stage simulator; fix energy-conservation bug
- 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>
2026-07-06 19:55:26 +05:00
jze9
2046dcba10 Add component database layer and validated physics core
- 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>
2026-07-06 19:45:32 +05:00
jze9
04233aefd7 Scaffold gausse coilgun simulator project
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>
2026-07-06 19:38:06 +05:00