С весны нынешнего года я разрабатываю статически типизированный встраиваемый скриптовый язык Umka, о концепции которого в своё время была статья на Хабре. При этом по своей основной профессии я занимаюсь алгоритмами систем автоматического руления тракторами — о некоторых подходах к комплексированию датчиков в этих системах я тоже писал. Теперь эти два направления деятельности причудливо пересеклись.

Для исследования поведения трактора в некоторых специфических сценариях (например, на склонах при наличии бокового проскальзывания) понадобился программный симулятор трактора, который верно моделировал бы не только кинематику, но и динамику машины. При этом алгоритм контроллера руления предполагалось постоянно видоизменять и немедленно наблюдать эффект этих изменений. Для такой задачи тандем C++ и Umka выглядел вполне органичным: основной код симулятора, требующий высокого быстродействия, был реализован на C++, а логика контроллера была вынесена в скрипт на Umka.

Вероятно, читатель уже заподозрил во мне нездоровую тягу к изобретению велосипедов. Попробую объясниться и заодно рассказать, что вышло из этой немного странной затеи.

Зачем свой симулятор?

Действительно, почему бы не взять готовый симулятор? Их масса: и Gazebo, и Project Chrono, да хоть игрушечный Farming Simulator со скриптами на Lua! Не стоит ожидать, что самостоятельно написанный симулятор сможет сравниться с ними по гибкости и мощи. Однако от инструмента для быстрого прототипирования контроллера руления вовсе не требовалось, например, достоверного моделирования нюансов работы трансмиссии трактора. Требовалось одно: быть готовым "уже вчера", и времени на освоение чрезвычайно громоздких проектов типа Project Chrono не оставалось.

Было и ещё одно обстоятельство, о котором часто забывают, рассуждая о достоверности моделирования. Нет никакого смысла вводить дополнительные степени свободы в модель, дополнительные члены в уравнения, если нет возможности экспериментально оценить входящие туда новые численные параметры. Хочется смоделировать качку кабины относительно рамы трактора? Похвальное стремление, однако оно обречено на провал, если мы не в состоянии измерить упругость и коэффициент демпфирования тех амортизаторов, на которых установлена кабина. Хочется максимально реалистично смоделировать контакт колеса с грунтом? Отлично, к нашим услугам "волшебная формула" Пацейки. Однако вправду ли мы знаем те десятки параметров, которые входят в эту формулу и которые тотчас же изменятся, как только твёрдый грунт поля размякнет под дождём? Так не стоит ли ограничиться моделированием лишь тех эффектов, которые действительно представляют интерес и для которых мы способны измерить все параметры?

Таким образом, оказался предпочтительным самый незамысловатый симулятор:

  • Уравнения динамики твёрдого тела с шестью степенями свободы;

  • Модель контакта шины с грунтом (4 параметра);

  • Модель круиз-контроля (2 параметра);

  • Модель рулевого привода (4 параметра);

  • Модели датчиков GNSS и IMU (5 параметров);

  • Простейшая графика на основе библиотеки raylib.

Скриншот симулятора
Скриншот симулятора

Зачем свой язык?

Сейчас в нише встраиваемых скриптовых языков почти безраздельно господствует Lua. Почему бы не взять её? Или, быть может, Python? Да и вообще, стоило ли выносить логику контроллера в скрипт, если сам симулятор настолько прост? Не следовало ли сделать контроллер частью симулятора? Что ж, даже с небольшим симулятором мы бы потеряли много времени, если бы вынуждены были перекомпилировать его всякий раз, когда вздумается попробовать ещё какой-нибудь алгоритмический трюк в контроллере. После десятка модификаций алгоритма выигрыш времени от использования внешнего скрипта ещё совершенно незаметен, после пары сотен — уже весьма ощутим.

Пусть так, но почему не Lua или Python? Полагаю, субъективный фактор здесь очевиден: если симулятор предстояло разрабатывать мне и под рукой был собственный язык, который я знал намного лучше, чем Lua и даже Python, то грех было не воспользоваться своими наработками. Одновременно проект симулятора послужил и отличной проверкой возможностей языка "в боевых условиях". Кроме того, скрипт, разумеется, никогда не предназначался для непосредственного запуска в реальном контроллере трактора: там весь код написан на C++, поэтому никаких рисков для пользователей эти эксперименты со скриптами заведомо не несли. Что же касается Python, то сам по себе он вряд ли годится в качестве встраиваемого языка. Взаимодействие с Python, скорее всего, потребовало бы инфраструктуры ROS — громоздкой и несовместимой с Windows 7, что сразу затруднило бы работу с симулятором.

Однако была и вполне объективная причина предпочесть Umka — его статическая типизация. Во-первых, не приходилось сначала запускать симулятор, прокладывать путь, включать контроллер — и лишь тогда обнаруживать несовместимость типов данных. Во-вторых, удалось легко и безо всяких дополнительных преобразований обмениваться структурами данных между кодом симулятора на C++ и скриптом на Umka. Вот, например, вызов функции расчёта управляющего воздействия — угла поворота рулевых колёс:

// C++
UmkaStackSlot param[] = {{.realVal = simOutput.was.steerAngle},
                         {.ptrVal  = (int64_t)&simOutput.omega},
                         {.ptrVal  = (int64_t)&simOutput.att},
                         {.ptrVal  = (int64_t)&simOutput.vel},
                         {.ptrVal  = (int64_t)&simOutput.pos},
                         {.realVal = t}};
UmkaStackSlot result = {0};

if (!umkaCall(umka, umkaGetFunc(umka, NULL, "update"), 
              sizeof(param) / sizeof(param[0]), param, &result))
{
    UmkaError error;
    umkaGetError(umka, &error);
    printf("\nUmka runtime error %s (%d): %s\n", 
           error.fileName, error.line, error.msg);
    break;
}

simInput.steerAngle = result.realVal;
// Umka
fn update(t: real, pos, vel, att, omega: ^mat.Vec, was: real): real {
   // ...
}

Какие впечатления?

Здесь читатель вправе усомниться в моей беспристрастности, однако первый практический опыт работы с Umka оказался в общем позитивным. Код на Umka оказывается чуть многословным из-за объявлений типов, но читается и пишется легко — во всяком случае для тех, кто испытывает хоть небольшую симпатию к Go. Впрочем, даже и тем моим коллегам, кто вовсе незнаком с Go, было нетрудно прочесть написанное. Полиморфизм (построенный на понятии интерфейса в духе того же Go) оказался полезен для унифицированного вычисления отклонения трактора от заданного пути, в качестве которого может выступать прямая, окружность, произвольная кривая. Сборщик мусора тоже не подвёл.

Главное неудобство работы заключалось в отсутствии необходимых библиотек, что было весьма ожидаемым. Специально для этого проекта была написана простейшая векторно-матричная библиотека, которая удовлетворила минимальные потребности геометрических расчётов.

В целом благодаря экспериментам с Umka удалось выявить и изучить два-три непредвиденных физических эффекта в контуре управления трактором — на сегодня это и есть основной полезный результат экспериментов с симулятором.