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>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user