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>
This commit is contained in:
@@ -18,6 +18,15 @@ import numpy as np
|
||||
|
||||
SensorEvent = Callable[[float, np.ndarray], float]
|
||||
|
||||
# cosh(z)**2 переполняет float64 при |z| > ~355; клэмпим до этого аргумент
|
||||
# sech^2, а не сам bump -- при |z|>=20 бамп уже ~1e-17, дальнейший рост z
|
||||
# физически ничего не меняет (снаряд всё равно "не виден" датчику).
|
||||
_SECH2_ARG_CLIP = 20.0
|
||||
|
||||
|
||||
def _sech2(z: np.ndarray) -> np.ndarray:
|
||||
return 1.0 / np.cosh(np.clip(z, -_SECH2_ARG_CLIP, _SECH2_ARG_CLIP)) ** 2
|
||||
|
||||
|
||||
def make_optical_sensor_event(x_sensor_m: float) -> SensorEvent:
|
||||
def event(t: float, state: np.ndarray) -> float:
|
||||
@@ -36,7 +45,7 @@ def make_inductive_sensor_event(
|
||||
) -> SensorEvent:
|
||||
def event(t: float, state: np.ndarray) -> float:
|
||||
x, v = state
|
||||
bump = 1.0 / np.cosh((x - x_sensor_m) / width_m) ** 2
|
||||
bump = _sech2((x - x_sensor_m) / width_m)
|
||||
return sensitivity_v_per_mps * v * bump - threshold_v
|
||||
|
||||
event.terminal = True
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from scipy.integrate import solve_ivp
|
||||
|
||||
@@ -48,3 +50,17 @@ def test_inductive_sensor_never_fires_below_threshold_speed():
|
||||
event = make_inductive_sensor_event(x_sensor, sensitivity, threshold, width)
|
||||
sol = solve_ivp(_ballistic, (0, 1.0), [-0.05, 1.0], events=event)
|
||||
assert len(sol.t_events[0]) == 0
|
||||
|
||||
|
||||
def test_inductive_event_far_from_sensor_is_finite_and_does_not_warn():
|
||||
"""Регрессия: cosh(z)**2 переполнял float64 для больших |x - x_sensor| /
|
||||
width_m (типично при интегрировании на широком временном бюджете) —
|
||||
результат физически верный (bump -> 0), но с RuntimeWarning на каждый
|
||||
вызов, что при миллионах прогонов sweep превращается в лавину спама."""
|
||||
event = make_inductive_sensor_event(x_sensor_m=0.02, sensitivity_v_per_mps=0.05, threshold_v=0.3, width_m=0.005)
|
||||
far_state = np.array([-5.0, 20.0]) # 5 м от датчика при ширине чувствительности 5мм
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
value = event(0.0, far_state)
|
||||
assert np.isfinite(value)
|
||||
assert value == -0.3 # bump практически 0 -> событие = 0 - порог
|
||||
|
||||
Reference in New Issue
Block a user