diff --git a/src/gausse/physics/sensors.py b/src/gausse/physics/sensors.py index 6405dd3..8298e24 100644 --- a/src/gausse/physics/sensors.py +++ b/src/gausse/physics/sensors.py @@ -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 diff --git a/tests/test_sensors.py b/tests/test_sensors.py index b7e6c52..8db2a76 100644 --- a/tests/test_sensors.py +++ b/tests/test_sensors.py @@ -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 - порог