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>
This commit is contained in:
jze9
2026-07-07 15:33:47 +05:00
parent 4d16825f7d
commit 35cbbb04c6
8 changed files with 180 additions and 51 deletions

View File

@@ -4,7 +4,7 @@ import pytest
from gausse.components.database import ComponentDatabase
from gausse.optim.search_space import (
TUBE_WALL_CLEARANCE_M,
PROJECTILE_BORE_CLEARANCE_M,
SearchBounds,
crossover,
decode,
@@ -25,8 +25,15 @@ def test_sample_genome_respects_bounds():
genome = sample_genome(DB, BOUNDS, rng)
assert BOUNDS.min_stages <= len(genome.stages) <= BOUNDS.max_stages
assert len(genome.inter_stage_gaps_m) == len(genome.stages) - 1
assert BOUNDS.tube_od_m_min <= genome.tube_od_m <= BOUNDS.tube_od_m_max
assert genome.projectile.diameter_m <= genome.tube_od_m - TUBE_WALL_CLEARANCE_M
assert BOUNDS.tube_inner_d_m_min <= genome.tube_inner_d_m <= BOUNDS.tube_inner_d_m_max
assert genome.tube_od_m == pytest.approx(genome.tube_inner_d_m + 2 * genome.tube_wall_m)
# снаряд влезает в бор трубы
assert genome.projectile.diameter_m <= genome.tube_inner_d_m - PROJECTILE_BORE_CLEARANCE_M + 1e-9
# масса снаряда не больше 200 г
import math as _m
mat = DB.projectile_materials[genome.projectile.material_idx]
mass = _m.pi * (genome.projectile.diameter_m / 2) ** 2 * genome.projectile.length_m * mat.density_kg_m3
assert mass <= BOUNDS.projectile_mass_max_kg + 1e-6
for stage in genome.stages:
assert 0 <= stage.wire_idx < len(DB.wires)
assert BOUNDS.turns_per_layer_min <= stage.turns_per_layer <= BOUNDS.turns_per_layer_max
@@ -53,8 +60,8 @@ def test_mutate_keeps_genome_within_bounds():
genome = mutate(genome, DB, BOUNDS, rng, rate=0.5)
assert BOUNDS.min_stages <= len(genome.stages) <= BOUNDS.max_stages
assert len(genome.inter_stage_gaps_m) == len(genome.stages) - 1
assert BOUNDS.tube_od_m_min <= genome.tube_od_m <= BOUNDS.tube_od_m_max
assert genome.projectile.diameter_m <= genome.tube_od_m - TUBE_WALL_CLEARANCE_M + 1e-9
assert BOUNDS.tube_inner_d_m_min <= genome.tube_inner_d_m <= BOUNDS.tube_inner_d_m_max
assert genome.projectile.diameter_m <= genome.tube_inner_d_m - PROJECTILE_BORE_CLEARANCE_M + 1e-9
# decode должен всегда успевать без исключений после repair
decode(genome, DB, BOUNDS)