import warnings import numpy as np from scipy.integrate import solve_ivp from gausse.physics.sensors import make_inductive_sensor_event, make_optical_sensor_event def _ballistic(t, state): return [state[1], 0.0] def test_optical_sensor_trigger_is_velocity_independent(): x_sensor = 0.02 trigger_xs = [] for v in (1.0, 5.0, 20.0): event = make_optical_sensor_event(x_sensor) sol = solve_ivp(_ballistic, (0, 1.0), [-0.05, v], events=event) assert len(sol.t_events[0]) == 1 trigger_xs.append(sol.y_events[0][0][0]) assert np.allclose(trigger_xs, x_sensor, atol=1e-9) def test_inductive_sensor_fires_earlier_at_higher_velocity(): x_sensor = 0.02 sensitivity = 0.05 threshold = 0.3 width = 0.005 trigger_xs = [] for v in (10.0, 20.0, 40.0): event = make_inductive_sensor_event(x_sensor, sensitivity, threshold, width) sol = solve_ivp(_ballistic, (0, 1.0), [-0.05, v], events=event) assert len(sol.t_events[0]) == 1 trigger_xs.append(sol.y_events[0][0][0]) # чем выше скорость, тем раньше (дальше от датчика, т.е. при меньшем x) # срабатывает индукционный датчик, т.к. sensitivity*v*bump(x) достигает # порога при меньшем bump(x), а значит при большем |x - x_sensor| assert trigger_xs[0] > trigger_xs[1] > trigger_xs[2] assert all(x < x_sensor for x in trigger_xs) def test_inductive_sensor_never_fires_below_threshold_speed(): x_sensor = 0.02 sensitivity = 0.05 threshold = 0.3 width = 0.005 # пиковый сигнал = sensitivity * v = 0.05 * 1.0 = 0.05 << порог 0.3 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 - порог