diff --git a/src/gausse/sim/coilgun.py b/src/gausse/sim/coilgun.py index dc47a21..b318414 100644 --- a/src/gausse/sim/coilgun.py +++ b/src/gausse/sim/coilgun.py @@ -109,7 +109,13 @@ def run_coilgun(config: CoilgunConfig) -> CoilgunResult: entry_x_m, entry_v_mps = result.exit_x_m, result.exit_v_mps exit_kinetic_energy_j = 0.5 * config.projectile.mass_kg * entry_v_mps**2 - efficiency = exit_kinetic_energy_j / total_energy_in_j if total_energy_in_j > 0 else None + # КПД = энергия, добавленная катушками (kinetic_energy_delta), а не вся + # exit-кинетическая энергия -- иначе фиксированный внешний "толчок" + # initial_v_mps (не учтённый в energy_in) может дать КПД > 100%, что + # физически бессмысленно. per-stage энергобаланс (energy_in = + # remaining_cap + dissipated + kinetic_delta) гарантирует + # kinetic_delta <= energy_in, так что этот КПД всегда <= 1. + efficiency = total_kinetic_energy_delta_j / total_energy_in_j if total_energy_in_j > 0 else None return CoilgunResult( feasible=True, diff --git a/tests/test_coilgun_chain.py b/tests/test_coilgun_chain.py index ac719ef..2ae9d3a 100644 --- a/tests/test_coilgun_chain.py +++ b/tests/test_coilgun_chain.py @@ -96,6 +96,27 @@ def test_infeasible_stage_stops_chain_with_honest_reason(): assert len(result.stage_outcomes) == 2 +def test_efficiency_never_exceeds_one_even_with_large_initial_velocity(): + """Регрессия: КПД считался как exit_KE/energy_in, а не как ДОБАВЛЕННАЯ + катушками энергия/energy_in. Для лёгкого снаряда с большой заданной + initial_v_mps (внешний "толчок", не учтённый в energy_in) это давало + КПД > 100% -- физически бессмысленно, найдено реальным sweep на Этапе 8.""" + light_projectile = ProjectileConfig(material=STEEL, diameter_m=0.003, length_m=0.005) + config = CoilgunConfig( + stages=[_stage()], + inter_stage_gaps_m=[], + projectile=light_projectile, + initial_x_m=-0.05, + initial_v_mps=50.0, # заведомо большой "бесплатный" толчок относительно массы снаряда + ) + result = run_coilgun(config) + assert result.feasible, result.reason + assert result.efficiency <= 1.0 + 1e-9 + # и КПД действительно равен добавленной энергии, а не абсолютной exit-КЭ + expected = result.total_kinetic_energy_delta_j / result.total_energy_in_j + assert result.efficiency == pytest.approx(expected) + + def test_mismatched_gap_count_raises(): with pytest.raises(ValueError): CoilgunConfig(