Русский | English
Задача. Беспилотный трамвай маршрута № 10 в Москве определяет положение сравнением с лидарной картой (сообщение мэрии). Кейс хакатона «Резервная одометрия по модели» просит второй канал на штатных сигналах вагона: двух скоростях тележек, положении ручки контроллера и известной геометрии пути. GNSS в городской застройке пропадает и прыгает, поэтому резервный канал берёт его только на старте и в редкие окна RTK на оси пути.
Что делает RailBreak. Превращает эти три сигнала в продольную скорость, 3D-положение точки base_link в MGRS и карточку достоверности — с задержкой 0.20 мс, без IMU, лидара и камер. Пакет railbreak_backup_odometry — ROS 2 Humble, C++17, собирается без сети.
| Звено | Что держит | RailBreak |
|---|---|---|
| Основная локализация | Лидарная карта | — |
| Резервный канал | Скорость, положение и доверие, когда основной канал деградирует | Фильтр вдоль пути на тележках, ручке и карте |
| Потребитель | Решение о движении | Получает оценку, её интервал и статус use_position |
- Лучшая скорость среди опубликованных решений трека: 0.024691 м/с на официальном checker организатора после дедлайна; другие команды в своих отчётах дают 0.026–0.055 м/с (C-01, C-02).
- Положение 1.157702 м по 3D RMSE с максимумом 3.67 м; после отбора ошибка снижена с 2.151 м на 46 % (C-01, C-03).
- Поперечная ошибка 7 см: состояние живёт на оси пути, и оценка не сходит с рельса (C-04).
- Задержка 0.20 мс по медиане, p99 1.09 мс, максимум 100.7 мс при пороге ТЗ 100 мс и пике 250 мс; дольше 100 мс — 2 пары из 24 388. 24 МБ памяти и меньше 1 % одного ядра (C-05, C-06).
- Точность на отложенных рейсах: медиана 1.47 м вдоль пути и дрейф 0.024 % пройденного на 21 рейсе
val(C-08). - Отказ одной тележки почти не виден в метриках: пропуск 20 с — 1.60 м против 1.47 м без сбоя; ни один прогон сбоев не дал NaN или Inf (C-10).
- Общая мода обнаруживается: синхронный юз обеих тележек помечен в 95 % рейсов, доверие к положению снимается до якоря станции (C-11).
- Объяснимая карточка состояния
advisory_class, раздельное доверие к скорости и положению, эмпирическая граница вдоль пути — 97.2 % фиксовvalвнутри (C-13). - Воспроизводимо: приёмка одной командой с кодом возврата; C++-ядро и Python-двойник совпадают до 0.37 мм; ядро чисто под AddressSanitizer + UndefinedBehaviorSanitizer (C-12, C-15).
- Аудит 2026-10-25: привязка GNSS к кольцу точна до долей сантиметра, некорректная конфигурация и повреждённые assets отклоняются до первого шага (C-16, C-17, отчёт).
Каждое число — в реестре утверждений с командой воспроизведения; границы — в LIMITS.md. Числа официального checker сняты 2026-10-03, до исправления привязки GNSS 2026-10-25 и до сетки положения 0.04 с; повтор на текущем дереве — первый пункт плана.
- Запуск для жюри
- Как работает решение
- Устойчивость к сбоям
- Эволюция точности
- Сравнение с треком и литературой 2025–2026
- Границы и план
- Документация
- Сценарии запуска
- Официальный checker и замер задержки
- Контракт топиков
- Сборка без Docker
- CI и права
Нужен Docker (Docker Desktop на Windows и macOS, Docker Engine с Compose на Linux). Из корня клона:
scripts/jury.sh play --bag <каталог rosbag2> --msgs <tram_vehicle_msgs>
.\scripts\jury.ps1 play -Bag <каталог rosbag2> -Msgs <tram_vehicle_msgs>
Контейнер собирает пакет вместе с пакетом сообщений организатора и проигрывает запись; после загрузки базового образа Humble сеть не нужна. Записи rosbag2 и tram_vehicle_msgs приходят с датасетом, карта маршрута (assets/) встроена в пакет.
Используйте полный tram_vehicle_msgs из датасета. В архиве check-code-with-bag.zip нет DriverControllerCommand.msg; с таким пакетом нода сама собирается в режиме двух тележек при нулевой ручке (notch_input = type_absent) и даёт 1.325 м и 0.027 м/с на той же записи (C-14).
На выходе:
/result/velocity(tram_vehicle_msgs/VelocitySensor) — с первого сообщения тележки, продольная скорость в м/с;/result/position(nav_msgs/Odometry) — после 3-секундного стартового окна GNSS:frame_id = map,child_frame_id = base_link,xв квадрате MGRS 37U CB около99–103 км;/result/diagnostics(diagnostic_msgs/DiagnosticArray) — режим, целостность, доверие каналов; после стартового окнаgnss = correcting(по умолчанию) илиgnss = closedприgnss_correction: false.
Автоматическая приёмка с кодом возврата (0 — все проверки пройдены):
scripts/jury.sh acceptance --bag <каталог rosbag2> --msgs <tram_vehicle_msgs>
.\scripts\jury.ps1 acceptance -Bag <каталог rosbag2> -Msgs <tram_vehicle_msgs>
acceptance записывает выходные топики, считает метрики score_ros.py и завершается ненулевым кодом, если нода упала, нет скорости или положения, штамп шагнул назад, частота уникальных штампов ниже 10 Гц, разрыв больше 0.25 с, кадры отличаются от map / base_link или стартовое окно GNSS не закрылось. На учебной записи 30618_e9a34502: код 0, 3D 0.773 м, 29.2 Гц, 10 якорей станций.
flowchart TB
bf[Front bogie] --> q[Stamp queue]
br[Rear bogie] --> q
cmd[Driver notch] --> q
q --> nf[Notch guard]
nf --> ekf[Schmidt-Kalman]
ring[Route 10 ring] --> ekf
gnss[GNSS] --> ekf
ekf --> out[Speed, pose, diagnostics]
- Одна координата вдоль рельсов. Трамвай связан колеёй, поэтому фильтр оценивает дугу
sвдоль геодезического кольца маршрута № 10 (assets/ring.csv,10 945.6099 мв WGS84), аx, y, zи курс берёт из карты с пересчётом от антенны master кbase_link(+9.873 мвдоль пути,−3.0 мпо высоте). Ковариация положения проецируется касательнойt = (p(s+2) − p(s−2)) / 4:Σ_xyz = Var(s_pub) · t tᵀ. - Фильтр Шмидта — Калмана
[s, v, k, b_a]. Между отсчётами тележек скорость интегрируется по таблице тяги и торможенияa_tab(n, v)(assets/notch.csv, снята сtrain) за вычетом уклонаg · i(s)со смещениемb_a. По двум колёсам наблюдаемо только отношениеv / k, поэтому на колёсном шаге усиление по масштабу обнулено (K_k = 0, форма Джозефа): погрешность таблицы тяги не утекает в радиус колеса. Масштабkуточняют однозначные стоянки у станций (assets/stops.csv, разброс≤ 3 м, ворота3σ,ZUPT) через ковариациюP_sk. - Мягкое снижение веса вместо отключения датчика. Тележка, разошедшаяся с моделью (
ν² / S > 16) или с соседней, остаётся в фильтре сR = 25 (м/с)²; траекторию ведут здоровая тележка и модель тяги. - Защита знака ручки (
notch_fault). Если согласованные тележки быстрее1 м/сдольше0.5 сускоряются при тормозной ручке (notch ≤ −4) или замедляются сильнее1.0 м/с²при ходовой (notch ≥ +4), модель переходит на выбег. - Выход на сетке
0.04 с. Пока обе тележки свежие и согласны,/result/velocityпубликует их среднее на сетке0.04 ссо сдвигом штампаoutput_stamp_offset_s = +0.105 с(измеренный лаг эталона/localization/kinematic_stateотносительно тележек; параметр). При расхождении или обрыве выход за0.5 спереходит на скорость фильтра. Положение выходит со штампом тележки и между штампами — по сохранённой дуге с шагом0.04 с. - Контроль общей моды. Две тележки на одном рельсе ошибаются вместе: при синхронном юзе они согласны друг с другом и расходятся с моделью. Через
3 сфильтр переходит вCOMMON_MODE_UNOBSERVABLE, замораживаетkиb_aи ждёт станцию или пачку RTK на оси; через5 сили100 мбез якоря —LOST, положение публикуется сσ = 1 кмиintegrity_use_position = false. Карточкаadvisory_class(input_quality,common_mode,one_bogie_residual,both_residual,nominal) отделяет плохой вход от физической невязки. - GNSS: старт и редкие якоря. Окно
3 сставит вагон на кольцо (среднее дуг двух антенн и азимут master→rover). Затем приgnss_correction: trueкаждое окно master RTK один раз корректируетs, не трогаяk, если фикс не дальше1.5 мпоперёк оси и продольная невязка не большеmax(5 м, 4σ_s).gnss_correction: falseоставляет после старта колёса, модель тяги и карту.
Подробно: модель, допущения и параметры, архитектура, граница целостности, карточка модели.
Отложенная выборка val, 21 рейс; сбой накладывается на ходу (скорость > 20 км/ч, после 25 % рейса).
| Сбой | RMSE вдоль пути, медиана (p95) | RMSE скорости | Поведение |
|---|---|---|---|
| Без сбоя | 1.47 м (5.83) | 0.032 м/с | — |
| Передняя тележка молчит 20 с | 1.60 м (5.7) | 0.032 м/с | Путь ведёт задняя тележка |
| Обе тележки молчат 20 с | 1.76 м (6.6) | 0.061 м/с | Путь ведёт модель тяги по ручке и уклону |
| Передняя тележка NaN 10 с | 1.60 м (5.8) | 0.032 м/с | Отсев на входе |
| Передняя +10 % на 30 с (боксование) | 1.94 м (10.5) | 0.035 м/с | Флаг в 95 % рейсов, R = 25 (м/с)² |
| Обе −20 % на 8 с (юз) | 6.46 м (868.5) | 0.156 м/с | Флаг в 95 % рейсов; хвост p95 — рейсы без однозначной станции до конца, там LOST |
Якоря станций — главный источник точности: без них медиана 12.6 м (C-09). Полная матрица — в results.md §4.
Официальный hackathon_solution_checker (metrics.py, ApproximateTimeSynchronizer, очередь 100, допуск 0.05 с), запись 30618_88aea4d9, --rate 1:
| Этап | Что изменилось | Положение, м (макс.) | Скорость, м/с |
|---|---|---|---|
Сдача к дедлайну, checker на 5cd35fa |
Выход на каждый вход, сферическая дуга, скорость фильтра | 2.151 (5.649) | 0.051 |
| Pathgraph и окна RTK | Ось pathgraph в кольце, медиана каждой пачки master RTK на оси | 1.615 (4.899) | 0.035 |
| Среднее согласных тележек | wheels_mean, штамп +0.105 с, лаг дуги 0.05 с |
1.372506 (4.922128) | 0.026633 |
| Геодезическое кольцо WGS84 | Точная длина ring.csv, k0 = 1.001378 по train, сетка скорости 0.04 с |
1.163029 (3.682215) | 0.024695 |
| 2026-10-03, поза на штампе тележки | Ручка больше не публикует позу | 1.157702 (3.674567) | 0.024691 |
Тот же бинарник при --rate 10 — 1.159811 м и 0.024686 м/с. Полный журнал из 18 прогонов и разложение по 120-секундным окнам — в results.md §9. Карта и k0 строились только по train.
Числа других команд на записи 30618_88aea4d9 — из их README и отчётов (references.md).
| Решение | Подход | Скорость, м/с | Положение 3D, м |
|---|---|---|---|
| RailBreak | Шмидт — Калман [s, v, k, b_a], геодезическое кольцо, среднее тележек, notch_fault, монитор целостности |
0.024691 | 1.157702 (макс. 3.67) |
67-time-to-win-hack |
GPB1 на пяти режимах, CUSUM, западный тупик в карте | 0.026 | 0.60 |
ekimlichenko-mt |
Одномерный оцениватель с GNSS по ходу рейса | 0.0324 | 1.111 (макс. 14.02) |
VECTRA |
UKF [s, v, d, k_т, k_б], сопротивление Дэвиса |
0.042 | 1.43 |
tram-backup-odometry |
Эхо-сеть и Калман [v, b] |
0.051 | 1.209 |
dub-otrezkov |
Проекция на путь | 0.055 | 1.053 (макс. 13.17) |
FunnyRepo |
Таблица 31×16, задержка тяги 0.4 с | 0.033 | 5.13 |
Среди опубликованных решений у RailBreak самая низкая ошибка скорости и наименьший максимум ошибки положения: 3.67 м против 13–14 м у двух команд, опубликовавших максимум. Лучшее опубликованное среднее положение — 0.60 м у 67-time-to-win-hack (M-03); резерв RailBreak виден в разложении ошибки: 0.07 м поперёк и 0.80 м вдоль пути между якорями.
Место в литературе.
- Одномерное состояние вдоль пути — магистральное направление рельсовой навигации. Одномерный фильтр INS/одометр ION GNSS+ 2026 получает 1.335 м пробега и 0.0132 м/с на 30 км — с IMU (M-01); метропоезд в IEEE T-ITS 2025 уточняет положение по геометрии пути. RailBreak выходит на метровую точность без IMU: второй канал — модель тяги и якоря станций.
- Раздувание дисперсии по невязке следует Palmer, Nourani-Vatani (IROS 2018) и residual-aware DR-EKF (arXiv:2604.02749, 2026) в виде O(1)-правила по NIS для цикла 30 Гц.
- Целостность. Точность и целостность разделены, как в обзоре Sensors 2025; следующий шаг — онлайн- и многоисточниковая конформная калибровка (2026).
- Сцепление. Обученные предикторы юза (Urban Rail Transit 2026, M-04) и наблюдатели μ (ETAE 2026) опираются на тормозное усилие или момент; RailBreak публикует объяснимую карточку невязок.
Практики, перенесённые из соседних репозиториев автора, — docs/PORTING.md.
| Область | Сейчас | Следующий шаг |
|---|---|---|
| Синхронный юз обеих тележек (L-01) | Обнаружение, снятие доверия к положению до якоря, LOST по слепому бюджету |
Третий канал скорости: радар, IMU или магнитная подпись пути (M-02) |
| Коэффициент сцепления (L-02) | Карточка невязок, mu_estimate = null |
Момент тяги или IMU во входном контракте |
| Граница целостности (L-03) | Эмпирическая по train, 97.2 % val внутри |
Онлайн-конформная калибровка по вагонам |
| Топология (L-04) | Одно кольцо регулярного маршрута | Банк гипотез пути (прототип) |
| Масштаб вагона (L-05) | k0 вагона 30618 |
Калибровка по вагону |
Первый пункт плана — повтор официального checker на текущем дереве с сеткой положения 0.04 с и исправленной привязкой GNSS. Сводный план — tz-audit.md.
| Документ | Содержание |
|---|---|
Пакет railbreak_backup_odometry |
Входы и выходы, сборка без интернета, параметры, диагностика, поведение при сбоях |
| Математическая модель | Состояние [s, v, k, b_a], таблица тяги, фильтр, наблюдаемость, якоря |
| Допущения и параметры | Единицы, геометрия base_link, MGRS 37U CB, полная таблица параметров |
| Методика и результаты | Официальный checker, val, абляции, сбои, реальное время, журнал прогонов |
| Соответствие ТЗ и план | Построчная сверка с кейсом и план развития |
| Реестр утверждений, границы, карточка модели | Каждое число с источником; границы и что их снимает |
| Аудит 2026-10-25 | Находки в коде и документах, исправления, регрессионные тесты |
| Источники | Литература 2012–2026 и что взято из каждой работы |
| Инженерный журнал | Изменения после отбора: что вошло, что проверено и отклонено |
| Демо и питч | Три акта живой демонстрации и пятиминутный доклад |
| Карта документации, правила для участников | Все документы; проверки перед PR |
scripts/jury.sh <сценарий> --bag <каталог rosbag2> --msgs <tram_vehicle_msgs>
scripts/jury.ps1 <сценарий> -Bag <каталог rosbag2> -Msgs <tram_vehicle_msgs>
| Сценарий | Что проверяет |
|---|---|
play |
Полный прогон записи в кадре mgrs, без /clock |
smoke |
Быстрая проверка первых 25 секунд записи |
acceptance |
Запись выхода, метрики score_ros.py и fail-closed проверки (код 0 — всё прошло) |
record |
Запись выхода и расчёт метрик в интерактивном режиме |
clock |
Запуск с use_sim_time:=true и ros2 bag play --clock |
fast |
Ускоренное воспроизведение --rate 10 (логика, не замер задержки) |
frame |
Локальная сетка от старта (output_frame:=mkrs_start) |
no-gnss |
Прогон без топиков GNSS (относительная одометрия от точки старта) |
no-assets |
Без каталога карт (assets_dir:=/nonexistent): нода жива, выдаёт пройденный путь по x, y = z = 0 |
arc |
Старт без GNSS с заданной дуги (--initial-s / -InitialS) |
core |
Сборка CMake и модульные тесты ядра C++17 без Docker, ROS и rosbag2 |
Пакет hackathon_solution_checker из архива check-code-with-bag.zip собирается в одном рабочем пространстве с нодой (tram_vehicle_msgs для полного прогона — из датасета):
colcon build --packages-select tram_vehicle_msgs railbreak_backup_odometry hackathon_solution_checker
ros2 run railbreak_backup_odometry backup_odometry_node # терминал 1
ros2 run hackathon_solution_checker metrics # терминал 2, итог после Ctrl+C
ros2 bag play <каталог rosbag2> --rate 1 # терминал 3
Задержка вход→выход: запишите три входных топика, /result/position и /result/velocity через ros2 bag record при --rate 1, затем:
python3 tools/organizer/latency_probe.py --bag <записанный_bag> --output-topic /result/position
Скрипт считает разность времени приёма выхода и входа с тем же header.stamp. Поле callback_max_us в /result/diagnostics — длительность одного колбэка внутри процесса.
| Направление | Топик | Тип | Описание |
|---|---|---|---|
| Вход | /vehicle/front_bogie_velocity, /vehicle/rear_bogie_velocity |
tram_vehicle_msgs/msg/VelocitySensor |
Скорости тележек (в записях — км/ч; нода делит на 3.6) |
| Вход | /vehicle/driver_position_cmd |
tram_vehicle_msgs/msg/DriverControllerCommand |
Позиция ручки −15..+15 (отрицательная — торможение, положительная — тяга) |
| Вход | /sensing/gnss/master/fix, /sensing/gnss/rover/fix |
sensor_msgs/msg/NavSatFix |
Стартовое окно 3 с и редкие окна master RTK на оси пути |
| Выход | /result/velocity |
tram_vehicle_msgs/msg/VelocitySensor |
Продольная скорость, м/с (сетка 0.04 с, штамп +0.105 с) |
| Выход | /result/position |
nav_msgs/msg/Odometry |
Точка base_link в MGRS 37U CB (map → base_link), скорость в twist.twist.linear.x; при запрете целостности σ = 1 км |
| Выход | /result/diagnostics |
diagnostic_msgs/msg/DiagnosticArray |
Режим, целостность, доверие каналов, advisory_class, notch_fault, GNSS, очередь штампов |
Нужен установленный ROS 2 Humble (rclcpp, nav_msgs, sensor_msgs, diagnostic_msgs, ament_index_cpp, launch_ros, ros2 bag). Интернет и rosdep для сборки пакета не нужны.
mkdir -p ~/ws/src
cp -a railbreak_backup_odometry ~/ws/src/
cp -a <датасет>/tram_vehicle_msgs ~/ws/src/
# В выданном package.xml нет тега <maintainer>, обязательного для catkin_pkg в Humble
source scripts/ensure_maintainer.sh
ensure_maintainer ~/ws/src/tram_vehicle_msgs/package.xml
cd ~/ws
source /opt/ros/humble/setup.bash
colcon build --packages-select tram_vehicle_msgs railbreak_backup_odometry \
--cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash
ros2 pkg executables railbreak_backup_odometry # backup_odometry_node, replay_eventsЗапускайте ноду до проигрывателя, чтобы не пропустить стартовое окно GNSS (если окно пропущено, нода захватит абсолютную дугу на первой пачке master RTK на оси):
# Терминал 1
ros2 launch railbreak_backup_odometry backup_odometry.launch.py
# Терминал 2 — после строки "assets ...: ring ... m" в логе
ros2 bag play /путь/к/каталогу_bag --rate 1
# Терминал 3
ros2 topic hz /result/velocity
ros2 topic echo /result/position --field pose.pose.position
ros2 topic echo /result/diagnostics --field status[0].valuesПо умолчанию use_sim_time выключен: интегратор работает по header.stamp входов. С /clock:
ros2 launch railbreak_backup_odometry backup_odometry.launch.py use_sim_time:=true
ros2 bag play /путь/к/каталогу_bag --clock --rate 1--rate 10 подходит для проверки логики (очередь подписки 500); задержку по ТЗ меряют при --rate 1.
В учебных записях нет /localization/kinematic_state, поэтому score_ros.py восстанавливает base_link по отрезку антенн master→rover (база 12.436 ± 2 м, разность высот ≤ 1 м):
ros2 bag record -o /tmp/result_bag /result/velocity /result/position /result/diagnostics
python3 tools/organizer/score_ros.py /tmp/result_bag /путь/к/исходному_bag --frame mgrs- Локальная сетка от старта:
output_frame:=mkrs_start(такжеmgrs,mkrs,grid_start,enu). - Свой каталог карт без пересборки:
assets_dir:=/путь/к/assets. - Старт без GNSS:
-p initial_lat_deg:=55.810417 -p initial_lon_deg:=37.462308или-p initial_s_m:=1234.0. Если за 10 с нет ни GNSS, ни начальной точки, нода выдаёт относительный путь от нуля. - Без карты (
assets_dir:=/nonexistent): нода пишетassets not loadedи выдаёт скорость и пройденный путь поx.
Математическое ядро в include/railbreak_backup_odometry/ не зависит от ROS 2 и собирается на любой ОС с CMake 3.14+:
cmake -S railbreak_backup_odometry/tools -B build/rbo -DCMAKE_BUILD_TYPE=Release
cmake --build build/rbo --config Release --parallel
ctest --test-dir build/rbo -C Release --output-on-failure- Публичный CI (
.github/workflows/ci.yml): ядро пакета и исследовательский стенд в Release и подAddressSanitizer+UndefinedBehaviorSanitizer; Python-тесты двойника, скореров, монитора целостности, кампании сбоев, документации и реестра утверждений (lint_claims.py); сборка иcolcon testпакетаrailbreak_backup_odometryв контейнере ROS 2 Humble с полным стабом ci/tram_vehicle_msgs и безDriverControllerCommand.msg. - Исследовательский стенд
tramDR-0.0.11: каталогиtram_dr_localizationиstandalone— 12-мерный UKF на синтетике (docs/verification.md); нода сдачи их не использует. - Права: код передаётся организаторам по условиям хакатона; сторонние компоненты и данные — NOTICE и THIRD_PARTY_NOTICES.md; происхождение модулей — PROVENANCE.md; как цитировать — CITATION.cff.
