Files
gausse/tests/test_coilgun_chain.py
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

108 lines
3.7 KiB
Python
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.
import pytest
from gausse.components.schema import (
CapacitorSpec,
ProjectileMaterialSpec,
SensorSpec,
SwitchSpec,
WireSpec,
)
from gausse.sim.coilgun import CoilgunConfig, run_coilgun
from gausse.sim.stage import ProjectileConfig, StageConfig
STEEL = ProjectileMaterialSpec(
name="steel", density_kg_m3=7850.0, mu_r=200.0, b_sat_tesla=1.8, price_per_kg=100.0, source="test"
)
PROJECTILE = ProjectileConfig(material=STEEL, diameter_m=0.008, length_m=0.02)
CAPACITOR = CapacitorSpec(
part_number="c1", capacitance_uf=100.0, voltage_v=400.0, esr_ohm=0.05, max_current_a=300.0,
price=300.0, source="test",
)
WIRE = WireSpec(
part_id="w1", material="copper", gauge_mm=0.8, insulation_od_mm=0.85,
resistivity_ohm_m=1.68e-8, max_current_a=10.0, price_per_m=3.0, source="test",
)
SWITCH = SwitchSpec(
part_number="sw1", kind="MOSFET", max_current_a=200.0, max_voltage_v=500.0,
on_resistance_ohm=0.02, on_voltage_drop_v=None, turn_on_time_ns=50.0, price=50.0, source="test",
)
OPTICAL_SENSOR = SensorSpec(
part_number="s1", kind="optical", propagation_delay_ns=500.0, price=20.0, source="test"
)
WEAK_INDUCTIVE_SENSOR = SensorSpec(
part_number="s2", kind="inductive", propagation_delay_ns=0.0, price=5.0, source="test",
sensitivity_v_per_mps=0.001, threshold_v=1.0,
)
def _stage(sensor=OPTICAL_SENSOR, sensor_to_coil_distance_m=0.02):
return StageConfig(
wire=WIRE,
capacitor=CAPACITOR,
switch=SWITCH,
sensor=sensor,
tube_od_m=0.01,
turns_per_layer=20,
layers=4,
sensor_to_coil_distance_m=sensor_to_coil_distance_m,
charge_voltage_v=350.0,
)
def test_three_stage_chain_is_feasible_and_accelerates():
config = CoilgunConfig(
stages=[_stage(), _stage(), _stage()],
inter_stage_gaps_m=[0.05, 0.05],
projectile=PROJECTILE,
initial_x_m=-0.05,
initial_v_mps=5.0,
)
result = run_coilgun(config)
assert result.feasible, result.reason
assert len(result.stage_outcomes) == 3
assert result.exit_v_mps > 5.0
assert 0.0 < result.efficiency < 1.0
def test_global_coil_centers_accumulate_gaps():
config = CoilgunConfig(
stages=[_stage(), _stage(), _stage()],
inter_stage_gaps_m=[0.05, 0.07],
projectile=PROJECTILE,
initial_x_m=-0.05,
initial_v_mps=5.0,
)
result = run_coilgun(config)
assert result.feasible, result.reason
centers = [o.global_coil_center_m for o in result.stage_outcomes]
assert centers == pytest.approx([0.0, 0.05, 0.12])
def test_infeasible_stage_stops_chain_with_honest_reason():
# у слабого индукционного датчика порог 1В при чувствительности 0.001В/(м/с)
# требует скорости 1000 м/с, которой у снаряда не будет -> ступень 2 обязана провалиться
config = CoilgunConfig(
stages=[_stage(), _stage(sensor=WEAK_INDUCTIVE_SENSOR), _stage()],
inter_stage_gaps_m=[0.05, 0.05],
projectile=PROJECTILE,
initial_x_m=-0.05,
initial_v_mps=5.0,
)
result = run_coilgun(config)
assert not result.feasible
assert result.failed_stage_index == 1
assert "индукционный датчик" in result.reason
# ступень 2 (индекс 2) не должна была даже запуститься
assert len(result.stage_outcomes) == 2
def test_mismatched_gap_count_raises():
with pytest.raises(ValueError):
CoilgunConfig(
stages=[_stage(), _stage()],
inter_stage_gaps_m=[0.05, 0.05],
projectile=PROJECTILE,
initial_x_m=-0.05,
initial_v_mps=5.0,
)