Квантовый хэдж фонд: что интересного для IT специалиста?

Всем привет! Это моя первая статья на Хабр. Меня зовут Артем и я директор по разработке ПО в Luxoft. В IT больше 15 лет: начинал как Java разработчик, прошел долгий путь от тим лида и руководителя проектов до директора по разработке ПО с сотней людей в подчинении. В связи с последними событиями в ближайшее время собираюсь с семьей переезжать на остров Пенанг в Малайзию и присоединиться к Люксофт.Малайзия. Там продолжу развитие своих проектов в квантовых хэдж фондах, куда мы теперь нанимаем людей в Сербии, Мексике и Малайзии. Буду писать статьи про жизнь в Малайзии и про работу в Люксофт.

Что такое квантовые хедж-фонды? 

Квантовые хедж-фонды отличаются от классических с точки зрения трейдинга и финансового анализа. Такие компании используют количественный (или квантовый — от англ. quantitative) инвестиционный подход с помощью data science, статистических методов и machine learning. В классических хедж-фондах трейдер анализирует факты, оценивает показатели, общается с представителями компаний, изучает публичную информацию и ищет инсайды. Почти то же самое делают и алгоритмы, они ищут неэффективности рынков и используют их в торговле путем обработки больших массивов данных за короткое время. Например мониторят любые упоминания компаний в СМИ, соцсетях и даже в публичных выступлениях. Еще один из плюсов количественного подхода: автоматизированные системы не азартны в торговле — они выполняют то, что заложено алгоритмом, у них отсутствует поведенческий фактор, что снижает риски. В квантовых фондах основная работа лежит на плечах ученых, математиков и разработчиков, а не управленцев и трейдеров. 

Количественные фонды за последнее десятилетие двигались вверх, удерживая наивысшую долю объема торговли на фондовых биржах США.

По данным Barclay’s на 2017 год, поток капитала в количественные фонды составлял 29%, или 114 миллиардов долларов от общей суммы средств, переданных в хедж-фонды. Финансовые компании продолжают вкладываться в количественные способы инвестиций, некоторые создают отдельные квантовые подразделения. 

Как работают квантовые фонды? 

Компании создают так называемые «сигналы» на основе разных источников данных для принятия решений о покупке или продаже акций. 

Кроме классических рыночных данных: цен и объемов торгов, такими источниками могут быть:

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

  • Спутниковые изображения — наблюдение за количеством автомобилей, припаркованных возле сетевых универмагов или кафе позволяет прогнозировать объем продаж, соответственно и цены на акции. Еще один пример: поиск всех танкеров с нефтью на снимках, где с помощью длины тени на воде и времени снимка можно определить усадку корабля и соответственно объем нефти внутри танкера, что позволяет получить данные о перевозке нефти морским путем во всем мире. Сейчас эта история закрыта, так как пустые танкеры стали заливать рандомным количеством воды, но определенный промежуток времени данный сигнал активно работал.

  • Календарь национальных праздников в странах, где в конкретные дни традиционно дарят определенные драгоценные металлы — с учётом дат можно анализировать их стоимость.

  • Транзакции карт-провайдеров (VISA, MasterCard, etc.) — это триллионы операций (если не больше), которые парсятся с помощью machine learning для суммирования транзакций по всем компаниям, представленным на биржах. Благодаря этому фонд получает доступ к примерной аккумулированной выручке и может раньше публикации квартальных отчетов понимать ситуацию в каждой конкретной компании.

Таких сигналов для аналитики в фонде может быть более 5 миллионов и это терабайты входных данных ежедневно. Чаще всего такие сигналы разрабатывают ресечеры — специальные люди, квант-инженеры, которые экспертно разбираются в вопросе финансов и алгоритмов. 

А что же IT специалисты? 

IT специалисты нужны как для создания инфраструктуры для обработки, преобразования и загрузки этих данных, так и для разработки и тестирования приложений для непосредственно торговли. Поэтому в хедж-фонды ищут сениоров со знанием Python, Java, C++, обладающих опытом в data science, machine learning, cloud, data lakes, big data и т.д.. Стек технологий: Kafka, Cassandra, Spark, Django, K8S, AWS и др. Не последнее место занимает знание английского языка не ниже уровня upper intermediate. 

В фонды нанимают суперпрофи, экспертов. Здесь работает много математиков, поэтому нужно быть подкованным в алгоритмах, теории вероятности, теории чисел.

Опытный программист в количественном фонде занимает место опытного трейдера: фонд может получить преимущество на рынке благодаря превосходно написанному алгоритму. По данным Wall Street Journal, за лучших из лучших математиков и программистов идет борьба с Кремниевой долиной. Хедж-фонды ищут различные варианты привлечения кандидатов, предлагают льготы лучше, чем в технологических компаниях: дают столько отпусков, сколько нужно, предлагают мгновенные выплаты тем, чья работа отразилась на прибыли, устраивают хакатоны, открывают лаборатории количественных исследований для студентов Оксфорда.

Чем заниматься: исторические данные, NLP, репортинг 

Разные команды работают над разными проектами. К примеру, это может быть:

  • построение систем Data Lakes и Big Data Storages с использованием HDFS, Spark и Java;

  • написание Data Managers на С++, которые объединяют данные в один формат, а также разработка плагинов для преобразования данных из разных форматов в единый;

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

  • настройка инфраструктуры с использованием виртуализации и кластеризации AWS, K8S и Docker;

  • создание распределенных систем на основе Kafka и Cassandra;

  • решение задач Data Science c использованием NLP (natural language processing), numPy, Pandas, NLTK и Pyhton;

  • инфраструктурные проекты, связанные с подсчетом выручки, с репортингом в регулирующие органы, внутренним репортингом на Django и Flask + ReactJS;

Глобальность 

Хедж-фонды активно ищут программистов (кто сейчас не ищет), поэтому география не ограничена страной присутствия. Помимо США, где находятся штаб квартиры большинства фондов, таланты ищут и в Азии, и в Южной Америке, и в Европе.

Remote и почти remote 

Из-за особенностей информационной и финансовой безопасности, специалистов на удалёнке в проектах количественных фондов долгое время практически не было. Пандемия внесла свои коррективы: в вакансиях иногда можно найти remote или temporary remote предложения. В некоторых компаниях предпочитают гибридный формат flexible sharedesk (часть дней сотрудник работает из дома, часть — из офиса).

Оклады и бонусы 

Senior-инженер в квантовом фонде может заработать суммы, сопоставимые с европейскими зарплатами. Бывают дополнительные бонусы от самой компании. К примеру, мы работаем с одним из крупных американских хедж-фондов — они ввели призовой фонд для наших сотрудников и полностью его оплачивают. Это около 150 тыс. долларов в год для девяти чемпионов и девяти топ-перформеров, которые получат по 10 и по 5 тыс. долларов соответственно. 

Как попасть? 

Самый простой вариант — мониторить вакансии конкретных компаний, второй — устроиться в компанию-партнера квантового фонда, например к нам в Люксофт. Но нужно быть готовыми, что они отбирают минимальный процент от присланных CV.

Если вы заинтересованы в работе в квантовых фондах, но еще не слишком опытны — можно попробовать себя в формате стажировок. Они чаще всего оплачиваемы, но для этого нужно приехать в другую страну. Такие предложения, например, есть у фондов Two Sigma и Citadel с обучением в США и Китае. Можно пройти обучение и в онлайн-формате на data science модуле.

Плюсы:

  • достойная оплата, на 20-25% выше средней по рынку;

  • реальная big data — до 10 петабайт в новых данных в день;

  • современные технологии — фонды сразу же применяют новые стандарты языков;

Минусы:

  • иногда монотонная и сложная работа с данными;

  • не самая интересная предметная область — математика и финансы;

  • не стоит забывать, что фондовый рынок — это риск.


ссылка на оригинал статьи https://habr.com/ru/post/663846/

Частное солнечное затмение 30 апреля 2022 года

Для начала, видеоролик об этом. А потом — для закрепления материала — можно и почитать 😉

В ночь с 30 апреля на 1 мая произойдет первое в этом году солнечное затмение. В России и сопредельных странах это затмение не будет видимым, хотя можно и просто отшутиться, что российские астрономы, дабы уберечь зрение от яркого дневного светила, решили наблюдать солнечное затмение ночью — совсем, как космонавты. Но если говорить серьезно, это затмение не представляет интереса, поскольку будет частным — нигде на Земле полного затмения не случиться.

Конус лунной тени пройдет вдали от южного полюса планеты, и Землю лишь немного накроет лунная полутень — расходящийся в пространстве конус, в пределах которого Солнце будет прикрыто Луной лишь частично.

Где будет видимо предстоящее затмение?

Прежде всего, это — акватория Тихого океана. И еще Чили, Аргентина, Перу и Боливия. И самый краешек Антарктиды. Во всех обозначенных странах затмение будет наблюдаться низко над горизонтом — буквально, во время захода Солнца. И фаза затмения будет невелика.

Так, например, в Сантьяго — столице Чили — максимальная фаза затмения составит 40%, причем, Солнце будет находиться на высоте всего 2 градуса над горизонтом. И это — типичные условия для наблюдения затмения на западном побережье Южной Америке.

Максимальная фаза затмения составит 64%, и теоретически она могла бы быть наблюдаема в Тихом Океане — 500 километров к западу от пролива Дрейка — между Антарктидой и архипелагом Огненная земля, ровно в момент захода Солнца.

Научного интереса наблюдения данного затмения не представляют ввиду незначительной максимальной фазы и неудовлетворительных условий. Но наверняка любители астрономии в Чили и Аргентине отметятся фотографиями заходящего Солнца, чуть прикрытого Луной. Если им повезет с погодой.

Чем может быть интересно это затмение само по себе?

Оно напоминает нам о том, что затмения случаются, и — случаются с определенной периодичностью.

По словам астрологов, частное солнечное затмение 30 апреля 2022 года открывает собой так называемый “коридор затмений”, который будет “закрыт” 16 мая полным лунным затмением, которое тоже не будет видимо в России, так как произойдет днем — уже после захода Луны за горизонт.

Понятие “коридор затмений” — сугубо астрологическое, и к астрономии отношения не имеет. Но любителям астрономии важно понимать, что затмения никогда не случаются поодиночке, и чаще всего “ходят парами”. Например, солнечное, а через две недели лунное. Или — наоборот — сначала лунное, и через две недели солнечное. Бывают редкие исключения, когда случаются три затмения подряд. Интересно, как бы такой паровозик из затмений назвали бы астрологи? Но это действительно крайне редкое стечение небесных обстоятельств, а затмения при этом имеют крайне незначительные фазы, чаще всего остаются незамеченными.

Следующая пара затмений происходит через полгода. И в 2022 году затмения вновь напомнят о себе осенью: 25 октября 2022 года — частное солнечное (в этот раз уже видимое на территории России и сопредельных государств), и 8 ноября 2022 года — полное лунное затмение (видимое в восточных регионах страны).


ссылка на оригинал статьи https://habr.com/ru/post/663848/

Как я протестировала интерфейс голосового ассистента Сбер Салют

Голосовые ассистенты незаметно вошли в нашу жизнь с появлением умной колонки Алисы. Многие известные финтех компании внедряют голосовых помощников в свои продукты, так что теперь при использовании мобильных приложений пользователь вынужден привыкать к мультимодальным интерфейсам. Но несмотря на их возрастающую популярность голосовые помощники требуют серьёзных доработок.

У меня как UX-адепта возникает череда вопросов, требующих изучения. Насколько просто использовать голосового помощника в мультимодальном интерфейсе? По какому пути проходит типичный пользователь, используя приложение с голосовым ассистентом? Насколько ему комфортно и просто общаться с голосовым ассистентом, решая свою задачу? Чтобы найти ответы на эти вопросы, я провела модерируемое юзабилити тестирование мобильного приложения Салют.

Приложение Сбер Салют. Начальный экран
Приложение Сбер Салют. Начальный экран

Исходные данные

Для быстрого рекрута респондентов я запустила анкету-скриннер в нескольких телеграм-чатах и попросила друзей пошарить анкету через их каналы. Для тестирования были подобраны респонденты по следующим критериям:

— возраст: 20-55 лет

— люди, имеющие опыт взаимодействия с любым голосовым помощником или люди, интересующиеся виртуальными ассистентами

— пользователи Android (т.к. проводим тестирование на этой платформе, человеку должны быть привычны визуальные паттерны платформы)

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

 

Подготовка к тестированию

Следуя канонам юзабилити тестирования я составила список заданий и гипотезы для тестирования. Предметом моего исследования в приложении Салют стал раздел «Заказ продуктов». Основная цель исследования состояла в том, чтобы выявить проблемы в основном и краевых сценариях при заказе продуктов и предложить рекомендации по улучшению мультимодального интерфейса. Дополнительная задача — фиксация работающих и неработающих вариаций голосовых команд, озвученных респондентами для решения поставленной задачи, чтобы научить ассистента понимать частотные команды.

Тестирование. Основной сценарий

Узнав минимальные исходные данные от респондентов (опыт взаимодействия с голосовыми ассистентами, контекст использования) я перешла к проверке гипотез, озвучивая задания и описывая контекст. При проведении модерируемых юзабилити тестов крайне важно «поместить» пользователя в соответствующий контекст, как если бы человек взаимодействовал с приложением в реальных условиях, решая реальную задачу. В нашем случае первое задание звучало так: «Вы решили купить яблоки для шарлотки. Как вы выполните эту задачу в приложении Салют, пользуясь голосовым ассистентом?». На начальном этапе есть интересные инсайты. 2 из 6 респондентов не выполнили задание с первого раза, т.к. они проговаривали голосовую команду без нажатия на светящийся шар Салют. Гипотеза подтвердилась, пользователь не сразу привыкает к постоянному нажатию на кнопку для вызова ассистента. Далее пользователи переходили на экран с ассортиментом яблок через голосовые команды

«яблоки» ✓

«выбери отсюда яблоки» ✓

«мне нужны яблоки» ✓

 

Затем пользователи выбирали товар понравившегося бренда. 4 из 6 человек выполнили задачу с помощью голосового ассистента, используя одну из этих голосовых команд

«яблоки голден нфк в лотке 500 г» ✓

«надо заказать яблоки богатырь 500 г» ✓

«купить яблоки нфк 500 г» ✓

 

Один респондент не выполнил задание с первого раза, т.к. голосовой ассистент не распознал его команду «положи в корзину яблоки голден нфк в лотке 500 г» ×

Теперь задача пользователя — убедиться, что все товары успешно добавлены и завершить оформление заказа. 3 из 6 человек перешли в корзину с заказом с первого раза с помощью голосового ассистента, используя команды

«в корзину» ✓ «корзина» ✓

«оформить заказ» ✓

2 респондента не смогли перейти в корзину с первого раза, используя неработающую команду «заказ» ×, которая перенаправляет пользователя в историю заказов, а не в существующий в корзине заказ.

Здесь ошибка средней критичности! Пользователь отклоняется от сценария. Приходится выполнять лишние клики, чтобы вернуться к собранному заказу. Рекомендация: проверить ожидания пользователей от голосовой команды «заказ». Если статистически значимое число пользователей ассоциирует команду «заказ» с текущим заказом, необходимо поменять сценарий реагирования Салюта на эту команду.

 

Приложение Сбер Салют. Карточка оформления заказа
Приложение Сбер Салют. Карточка оформления заказа

Далее задача пользователя: завершить оформление заказа. При этом, я описала нетипичный контекст оформления заказа. «Сейчас вы покупаете яблоки не из Питера, а из Москвы (вы уехали к родственникам на майские), т.е. сохранённый в приложении адрес сейчас не актуален для нового заказа. Итак, выполните задание.» На данном этапе 6 из 6 пользователей пытались определить свой актуальный адрес геолокации с помощью голосовой команды «адрес доставки». Но голосовой ассистент не реагирует на эту команду на экране с заказом, а только на экране с карточками товаров. Наблюдаются сложности с изменением геолокации при смене города.

Ошибка высокой критичности!  Пользователь не может выполнить задачу с первого раза. Невозможность оформления заказа при смене города может привести к жалобам пользователей и обращениям в контактный центр, это критично.

Необходимо добавить возможность изменения адреса доставки из карточки заказа.

 

Тестирование. Краевые сценарии

 

            В приложении Салют в рамках раздела «заказ продуктов» я также протестировала краевые сценарии.

Краевой сценарий 1. Возвращаем пользователя в контекст: «Итак, представим, что вы выбрали уже яблоки для шарлотки в этом заказе. Но теперь вы вспомнили, что к вам обещали зайти друзья, а значит вам нужно ещё три упаковки яблок того же сорта. Выполните задачу, используя голосовой ассистент». Моя гипотеза о том, что пользователь не сможет добавить сразу несколько упаковок товара подтверждается. Только 2 из 6 пользователей успешно выполняют задание с первого раза, используя голосовую команду

«мне нужно 3 упаковки яблок нфк россия» ✓

 

Неработающие голосовые команды:

«повторить заказ яблоки голден нфк 500 г» ×

«положить в корзину яблоки голден нфк 500 г 3 шт» ×

Краевой сценарий 2. Описываем контекст для пользователя: «Вы ожидали, что к вам приедут сегодня друзья, поэтому вы добавили в заказ много яблок для шарлотки. Но только что вам позвонили друзья и предупредили, что отменяют встречу. Вы решили оставить в корзине только 3 упаковки, а не 4. Выполните задачу.» 4 из 6 человек выполнили задание с первого раза, произнеся команду

 «удалить одну упаковку яблок нфк россия» ✓

Гипотеза в отношении используемой голосовой команды подтвердилась. 2 респондента не выполнили задачу с первого раза, используя команду

«яблок нфк россия минус 1» ×

Ошибка средней критичности! На фразе «(название товара) минус 1» ассистент добавляет, а не удаляет товар. Выполнение сценария замедляется, пользователь отклоняется от сценария, т.к. ему приходится вручную удалять лишний товар из корзины.

Рекомендация: проверить ожидания пользователей от голосовой команды «(название товара) минус 1». Если статистически значимое число пользователей ассоциирует фразу «минус 1» с удалением товара из корзины, необходимо поменять сценарий реагирования Салюта на эту команду.

 ____________________________________

Резюмируем выводы и инсайты. В ходе юзабилити тестирования была обнаружена одна критическая ошибка в виде невозможности смены города в карточке заказа, а также две ошибки средней критичности при несовпадении ожиданий пользователя с реакциями ассистента на голосовые команды «заказ», «(наименование товара) минус 1».

Были зафиксированы различные вариации голосовых команд от респондентов: не распознанные ассистентом команды, а также команды, которые вызывали неожиданную реакцию ассистента. Такие команды необходимо проверить на дополнительном исследовании и при необходимости обучить голосовой ассистент правильно распознавать их.

Кроме того, общее впечатление по поведению всех респондентов говорит о том, что сейчас приложение может работать полноценно только в мультимодальном режиме. Пользователь не может взаимодействовать с некоторыми отдельными экранами исключительно с помощью голосового ассистента.

 

UX-исследование приложения Салют от Сбера


ссылка на оригинал статьи https://habr.com/ru/post/663850/

Интеллектуальный брутфорс: пишем головоломку и солвер для неё

Небольшое предисловие

В колледже я много играл в головоломки. В статье под головоломками я буду подразумевать очень узкое подмножество таких игр. Вот некоторые из примеров:

Также мне посчастливилось изучать структуры данных в Политехническом институте Ренсселера, где в то время студенты профессора Катлера (привет, Барб!) ежегодно участвовали в соревновании по написанию солвера головоломок. Каждый год игра менялась, и в мой год это была Ricochet Robots, которая по сути является головоломкой со скольжением по льду для нескольких игроков. Мне очень понравилось это задание (и я победил в соревновании!), после чего я продолжил участвовать в соревнованиях в качестве ассистента преподавателя.

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

Аннотация

В этой статье я представлю новую игру-головоломку и продемонстрирую техники, использованные мной для написания быстрого практичного солвера для неё. Рассматриваются следующие темы: поиск в ширину/поиск A*, мемоизация, оптимизация и стратегии, относящиеся к NP-трудным и NP-полным играм-головоломкам. Если вы найдёте какие-то проблемы или захотите предложить улучшения, то составьте issue или отправьте PR на GitHub. Я представлю бенчмарки, подтверждающие мои результаты. Процентные изменения могут быть относительно точными, а абсолютное время может варьироваться, потому что берётся в разные моменты с разной величиной базового шума.

Бенчмарки до и после изменений всегда выполняются один за другим, чтобы гарантировать, что они работают в одном окружении и демонстрируют точное процентное изменение.

Давайте сыграем в игру

Игра, в которую мы будем играть, называется «Anima». В ней используется сетка из тайлов, каждый из которых проходим или не проходим. Некоторые тайлы помечены маленьким цветным ромбом; эти тайлы являются конечными точками и чтобы решить головоломку, нам нужно одновременно закрыть все эти тайлы акторами того же цвета. Акторы — это блоки, на одном тайле может быть только один блок, который можно перемещать по сетке через проходимые тайлы. В каждый ход можно перемещать акторов в одном из четырёх направлений и все они движутся одновременно. Давайте посмотрим на пару примеров, чтобы понять принцип:

Управление

[Все примеры интерактивны в оригинале статьи.]

  • Перемещение: WASD/клавиши со стрелками (десктоп), свайпы (мобильные устройства)
  • Отмена: Space (десктоп), кнопка слева вверху
  • Сброс: Shift + Space (десктоп), кнопка слева внизу
  • Снять фокус: Escape (десктоп), щелчок или касание вне поля (мобильные устройства)

Эти два примера довольно просты, однако вы могли заметить пару неявных правил, из-за которых решение головоломок становится нетривиальным:

  • Если актор пытается переместиться на непроходимый тайл, то он не движется.
  • Если актор должен наложиться на другого актора, то он не движется.

Из-за этих побочных эффектов очень сложно (или невозможно?) предсказать, как будет вести себя система даже через один-два хода, что является отличительной особенностью задач с высокой NP-сложностью. Мы добавим ещё один неожиданный поворот, чтобы сделать игру немного интереснее:

В отличие от красных акторов, синие акторы движутся в направлении, противоположном выбранному вами. Если вы нажмёте влево, синие акторы будут двигаться вправо, и наоборот. Это приводит к последнему неявному правилу:

Когда они находятся рядом друг с другом, красные и синие акторы могут проходить проходить друг друга насквозь, меняясь местами. Однако если они разделены одним пустым тайлом, то они попытаются переместиться в одно пространство, а поэтому не вообще не позволят друг другу двигаться. Эта дополнительная трудность может сильно повысить сложность решения головоломок.

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

Попрактиковаться в решении уровней можно здесь: Puzzles

А теперь мы приступим к написанию солвера. Вы можете следить за процессом при помощи метки start в репозитории GitHub, которая познакомит вас со всем бойлерплейтом:

git clone --branch start https://github.com/djkoloski/anima_solver

Простейший солвер

Наша стратегия для простейшего солвера будет заключаться в том, что он, начав из исходного состояния, по одному ходу за раз станет исследование состояний, пока не найдёт решение. Я реализую это при помощи простого поиска в ширину, дополненного отслеживанием родителей:

pub fn solve(initial_state: State, data: &Data) -> Option<Vec<Direction>> {     let mut parents = Vec::new();     let mut queue = VecDeque::new();      // Add transitions from initial state     for (action, transition) in initial_state.transitions(data) {         match transition {             Transition::Indeterminate(state) => {                 parents.push((0, action));                 queue.push_back(state);             }             Transition::Success => return Some(vec![action]),         }     }      // Pop states in order     let mut index = 0;     while let Some(parent_node) = queue.pop_front() {         index += 1;          for (action, transition) in parent_node.transitions(data) {             match transition {                 Transition::Indeterminate(state) => {                     parents.push((index, action));                     queue.push_back(state);                 }                 Transition::Success => {                     let mut result_actions = vec![action];                     let mut current_index = index;                     while current_index != 0 {                         let (next_index, action) = parents.swap_remove(current_index - 1);                         result_actions.push(action);                         current_index = next_index;                     }                     result_actions.reverse();                     return Some(result_actions);                 }             }         }     }      None }

Я убрал большую часть бойлерплейта, так что вот самое важное:

  • У нас есть структуры State и Data; в State находится вся информация, которая может меняться, а в Data — вся статичная информация. State — это позиции акторов, а Data — схема поля и позиции конечных точек. Они разделены, чтобы минимизировать объём памяти, используемый queue.
  • Метод transitions() обрабатывает State, получает данные из Data и возвращает новые состояния, которых мы достигаем, выполняя каждый возможный ход. Переход Indeterminate означает, что достигнутое нами состояние нерешено, а переход Success — что решено.

parents здесь — это список из (usize, Direction), отслеживающий, из какого состояния он получился и в каком направлении перемещался для перехода. Добравшись до успешного перехода, мы вернёмся через вектор parents, чтобы пересобрать решение и вернуть его. Давайте проверим нашу программу!

Возьмём для этого простую головоломку:

$ cargo run -- puzzles/line_dance.txt puzzles/line_dance.txt: Parse: 0.000201900s Solve: 0.000017600s Found solution of length 2: Right, Right

Здорово, и она нашла решение! Оно выглядит правильным. Давайте посмотрим, насколько оно быстрое в режиме релиза:

$ cargo run --release -- puzzles/line_dance.txt puzzles/line_dance.txt: Parse: 0.000122700s Solve: 0.000009100s Found solution of length 2: Right, Right

Примерно вдвое быстрее, отлично! В дальнейшем мы будем запускать программу только в режиме релиза. Теперь давайте шаг вперёд и попробуем головоломку посложнее:

$ cargo run --release -- puzzles/u_turn.txt puzzles/u_turn.txt: Parse: 0.005942000s Solve: 0.001093200s Found solution of length 6: Down, Down, Left, Left, Up, Up

Потрясающе, и её программа решила! Ещё одну, она практически такая же:

$ cargo run --release -- puzzles/spiral.txt ...

Ой-ёй, похоже, быстро её мы не решим. А если программу не прибить, то и память может закончиться! Посмотрев на тайминги, мы поймём, почему:

puzzles/line_dance.txt Solve: 0.000009100s Found solution of length 2:  puzzles/u_turn.txt Solve: 0.001093200s Found solution of length 6:

После добавления всего четырёх ходов к длине решения время выполнения увеличилось в 120 раз! Давайте посмотрим, сколько состояний мы исследуем:

println!("Explored {} states", parents.len());

$ cargo run --release -- puzzles/line_dance.txt Explored 3 states  $ cargo run --release -- puzzles/u_turn.txt Explored 5364 states

Ого, вот и проблема. И это логично — каждое исследуемое нами состояние приводит к ещё четырём, поэтому следует ожидать, что для решения длиной n нужно будет исследовать не менее (4^n - 1) / 3 состояний. При решении в 6 ходов получается от 1365 до 5461 состояния, то есть мы попали как раз в этот интервал. Но что это означает для нашей пока нерешённой головоломки? Для её решения требуется 16 ходов, то есть стоит ожидать, что придётся исследовать от 1431655765 до 5726623061 состояния.

Ой-ёй.

Пришло время переработать и усовершенствовать наш солвер. Мы будем измерять улучшение длительностью решений уже имеющихся головоломок, а в дальнейшем будем создавать новые, более сложные. Наша цель — быстро решать все головоломки, которые можно создать.

Отслеживание состояний

Во-первых, мы должны заметить, что несмотря на то, что для U-Turn мы исследовали 5364, функционально существует всего 7 уникальных состояний: по одному для каждого из тайлов, на которых может находиться актор. То есть, получается, мы множество раз исследуем одно и то же состояние. Мы можем избежать этого, сохраняя исследованные состояния во множество хэшей и исследуя только дочернее состояние, если родительское состояние ещё не было посещено:

let mut states = HashSet::new(); ...  while let Some(parent_node) = queue.pop_front() {     index += 1;      if !states.contains(&parent_node) {         ...          states.insert(parent_node);     } }

Давайте посмотрим, как это повлияет на уже рассмотренные нами случаи:

$ cargo run --release -- puzzles/u_turn.txt Explored 20 states Solve: 0.000895100s Found solution of length 6:    Down, Down, Left, Left, Up, Up

Выглядит намного лучше! Может быть, теперь мы сможем решить нашу головоломку?

$ cargo run --release -- puzzles/spiral.txt Explored 51 states puzzles/spiral.txt: Parse: 0.000212300s Solve: 0.001049000s Found solution of length 16: Left, Left, Up, Up, Right, Right, Right, Right, Down, Down, Down, Down, Left, Left, Left, Left

Так гораздо лучше — количество исследуемых состояний снизилось с миллиарда с лишним до всего 51. Одной этой оптимизации оказалось достаточно, чтобы все наши примеры головоломок стало можно решать! Теперь мы можем проводить более комплексные бенчмарки солвера. В качестве бенчмарков выберем несколько представителей из примеров головоломок:

$ cargo bench solve_square_dance      time:   [16.658 ms 16.888 ms 17.125 ms] solve_fractal           time:   [44.606 ms 45.193 ms 45.806 ms] solve_antiparticle      time:   [1.8001 s 1.8308 s 1.8640 s]

Снижение энтропии

Простая оптимизация заключается в снижении энтропии состояний. Сейчас наше состояние головоломки выглядит так:

enum Color {     Red,     Blue, }  pub struct Actor {     position: Vec2,     color: Color, }  pub struct State {     actors: Vec<Actor>, }

Представьте поле с несколькими красными акторами на нём. Допустим, их четыре, пометим их как A, B, C и D:

При наличии этих четырёх акторов существует 4! способов представления состояния, поскольку в векторе 4! перестановок акторов. Мы можем исправить это, отсортировав массив actors. При этом все перестановки акторов перестроятся в один канонический порядок. Мы можем довольно легко реализовать это при выполнении перехода между состояниями:

pub fn transition(&self, data: &Data, direction: &Direction) -> State {     let mut result = self.clone();      ...      result.actors.sort();     result }

Можно рассматривать это как устранение из состояния ненужной энтропии. Давайте посмотрим, как это повлияет на бенчмарки:

$ cargo bench  solve_square_dance      time:   [4.4875 ms 4.5667 ms 4.6525 ms]                         change: [-73.527% -72.960% -72.308%] (p = 0.00 < 0.05)  solve_fractal           time:   [19.518 ms 19.647 ms 19.776 ms]                         change: [-57.178% -56.527% -55.885%] (p = 0.00 < 0.05)  solve_antiparticle      time:   [187.49 ms 190.53 ms 193.91 ms]                         change: [-89.835% -89.593% -89.343%] (p = 0.00 < 0.05)

Посмотрим, как при этом изменилось количество исследуемых состояний (и коэффициент ветвления BF):

Головоломка Длина решения До (BF) После (BF) Изменение
Square Dance 12 37268 (2.292) 11401 (2.061) -61.408%
Fractal 13 86277 (2.294) 46253 (2.179) -46.390%
Antiparticle 22 2514936 (1.888) 315100 (1.708) -87.471%

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

Поиск A*

Помня об этом, мы можем сказать, что простым способом дальнейшего снижения коэффициента ветвления может стать использование более сложного алгоритма поиска. A* является очень распространённым усовершенствованием поиска в ширину, отдающим приоритет исследованию более многообещающих узлов. На практике это означает, что нам нужно задать эвристическую функцию, оценивающую минимальное количество ходов до завершения. Мы хотим, чтобы наша эвристическая функция как можно точнее оценивала оставшееся расстояние, но никогда не преувеличивала его. Преуменьшение нас устроит. Имея эту оценку количества оставшихся ходов, мы можем отдавать приоритет исследованию состояний, имеющих минимальный оценочный размер решения (сделанные ходы + оставшиеся ходы). Это позволит не исследовать состояния, которые очевидно являются тупиковыми, и отдавать приоритет состояниям, которые выглядят многообещающе.

Основная сложность с реализацией A* заключается в выборе хорошей эвристической функции. Хорошая эвристическая функция должна быть легко вычисляемой и возвращать максимально высокую оценку, но без преувеличения. Нам нужно полностью осознавать этот компромисс. В большинстве случаев результат будет положительным. Давайте подумаем об эвристике, которую можно вычислить для нашей головоломки:

Чтобы решить головоломку, на каждой конечной точке должен находиться актор соответствующего цвета. Так как все акторы движутся одновременно, мы можем использовать простой maxmin: находить ближайшего к каждой конечной точке актора и вычислять расстояние между ними, а затем брать максимальное значение от всех расстояний до конечных целей. Логически это можно воспринимать так, что мы вычисляем минимальное количество ходов, необходимое для перемещения выбранного актора к любой из конечных точек. Это крайне грубая оценка, но нам она подойдёт. При желании мы могли бы вместо этого вычислить назначения в узких местах, но это может потребовать много времени и отрицательно сказаться на производительности.

pub fn heuristic(&self, data: &Data) -> usize {     let mut max_distance = 0;      for goal in data.goals.iter() {         let mut min_distance = usize::MAX;         for actor in self.actors.iter().filter(|a| a.color == goal.color) {             let d = (goal.position - actor.position).abs();             min_distance = usize::min(min_distance, (d.x + d.y) as usize);         }         max_distance = usize::max(max_distance, min_distance);     }      max_distance }

Код делает всё то, о чём я рассказал. Самым сложным было адаптировать написанную ранее функцию solve под приоритеты состояний и вычисление эвристики. Первым делом мы объединим в новую структуру всю информацию, необходимую при исследовании узла:

#[derive(Eq, PartialEq)] struct Node {     state: State,     distance: usize,     estimate: usize,     index: usize, }

Именно это мы теперь будем вставлять в очередь. К слову о ней:

let mut queue = BinaryHeap::new();

BinaryHeap — это простая структура данных, которую можно использовать как очередь с приоритетом. Мы можем вставлять элементы и удалять максимальное значение за время log(N). Для этого достаточно задать упорядочивание узлов, присваивающее самым многообещающим узлам (с наименьшим расстоянием + эвристикой) наибольший приоритет:

impl PartialOrd for Node {     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {         Some(self.cmp(other))     } }  impl Ord for Node {     fn cmp(&self, other: &Self) -> Ordering {         other.estimate.cmp(&self.estimate)     } }

Немного непонятно, но в целом этот код просто сообщает, что мы хотим выполнить сортировку в порядке, обратном estimate. Далее нам нужно изменить операцию push, чтобы учесть новые поля, которые необходимо заполнять в fill out в Node:

let estimate = state.heuristic(data) + (parent_node.distance + 1); queue.push(Node {     state: state,     distance: parent_node.distance + 1,     estimate,     index: parents.len(), });

Вместо подсчёта состояний сразу после их извлечения нам теперь нужно отслеживать, по какому индексу текущее состояние находится в списке parents. И на этом всё! У нас есть полнофункциональная реализация A*, готовая сделать наш солвер сверхбыстрым!

$ cargo bench  solve_square_dance      time:   [4.4051 ms 4.4276 ms 4.4524 ms]                         change: [+3.7025% +4.6095% +5.4779%] (p = 0.00 < 0.05)  solve_fractal           time:   [7.7314 ms 7.7718 ms 7.8183 ms]                         change: [-57.412% -57.044% -56.682%] (p = 0.00 < 0.05)  solve_antiparticle      time:   [201.26 ms 202.26 ms 203.36 ms]                         change: [+14.570% +15.351% +16.121%] (p = 0.00 < 0.05)

Хм, результат немного разочаровывает, по крайней мере, когда смотришь только на отдельные бенчмарки. Результат двух увеличился на 5-15%, а одного — снизился более чем на 50%. То есть хотя выигрыш оказался и ассиметричным, в целом он был нам на пользу. А если теперь нам каким-то образом удастся улучшить эвристическую функцию, то мы получим выигрыш и от неё. Давайте взглянем на исследуемые состояния (и на коэффициент ветвления):

Головоломка Длина решения До (BF) После (BF) Разница
Square Dance 12 11401 (2.061) 9737 (2.031) -14.595%
Fractal 13 46253 (2.179) 16593 (2.002) -64.126%
Antiparticle 22 315100 (1.708) 269211 (1.695) -14.563%

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

Снижаем количество распределений

Первое правило высокопроизводительной оптимизации — снижение количества распределений. Давайте найдём места, в которых это можно сделать:

pub fn transitions(&self, data: &Data) -> Vec<(Direction, Transition<Self>)> {     ... }

Функция переходов возвращает Vec с данными, распределёнными в кучу, но мы знаем, что всегда будем возвращать четыре перехода. Поэтому мы можем сделать так, чтобы она возвращала массив:

pub fn transitions(&self, data: &Data) -> [(Direction, Transition<Self>); 4] {     ... }

Давайте посмотрим, что это нам даст:

$ cargo bench  solve_square_dance      time:   [4.4052 ms 4.4415 ms 4.4867 ms]                         change: [-7.0971% -6.1140% -4.9385%] (p = 0.00 < 0.05)  solve_fractal           time:   [7.6836 ms 7.7682 ms 7.8720 ms]                         change: [-8.5312% -7.0938% -5.5943%] (p = 0.00 < 0.05)  solve_antiparticle      time:   [207.00 ms 207.71 ms 208.44 ms]                         change: [-4.9600% -4.2495% -3.5907%] (p = 0.00 < 0.05)

Довольно скромный выигрыш, поэтому давайте продолжим. Далее мы можем использоват крейт arrayvec для снижения количества распределений в наших состояниях. По сути, это структура массива со встроенным распределением, поэтому мы не будем постоянно распределять память кучи, если можно ограничить сверху количество акторов в головоломках. Чтобы оставить пространство для манёвра, давайте ограничимся восемью:

pub struct State {     actors: ArrayVec<Actor, 8>, }

Посмотрим на результаты:

$ cargo bench  solve_square_dance      time:   [4.0299 ms 4.0567 ms 4.0854 ms]                         change: [-9.7690% -8.6635% -7.6778%] (p = 0.00 < 0.05)  solve_fractal           time:   [7.4610 ms 7.5152 ms 7.5743 ms]                         change: [-4.6656% -3.2571% -1.9086%] (p = 0.00 < 0.05)  solve_antiparticle      time:   [187.27 ms 188.29 ms 189.38 ms]                         change: [-9.9536% -9.3494% -8.7499%] (p = 0.00 < 0.05)

Отлично! Ещё немного скромных улучшений, но пригодится любая мелочь. Наконец, настало время выполнить…

Профилирование

Простые возможности для оптимизации исчерпаны, поэтому давайте перейдём к микрооптимизациям. Мы по-прежнему многое можем выиграть, и выяснится это при помощи профилирования. Запустив бенчмарки в Visual Studio, мы получим интересные наблюдения:

Вот они в текстовом виде:

Функция Общий % CPU
anima_solver::puzzle::State::transitions 24.40%
hashbrown::map::HashMap::contains_key 20.72%
hashbrown::map::HashMap::insert 19.83%
alloc::collections::binary_heap::BinaryHeap::pop 15.91%

Сразу становится заметно, что мы тратим почти столько же времени в HashMap::contains_key, сколько и в HashMap::insert. Но ведь в нашем солвере нет hash maps. Или есть?

На самом деле есть. Мы используем HashSet для посещённых состояний, а он является замаскированным HashMap<K, ()>. Давайте взглянем на код:

let mut states = HashSet::new();  ...  while let Some(parent_node) = queue.pop() {     if !states.contains(&parent_node.state) {         for (action, transition) in parent_node.state.transitions(data) {             ...         }         states.insert(parent_node.state);     } }

Вот в чём проблема, мы проверяем — содержит ли множество состояние, а затем получаем переходы и после этого вставляем состояние. Наверно, мы сможем использовать Entry API для проверки того, содержит ли множество состояние и для вставки его за одну операцию поиска, так? Ну-у-у… У HashSet пока нет entry API наподобие того, что есть у HashMap.

Ну, тогда мы можем использовать HashMap со значением (), так?

let mut states = HashMap::new();  ...  while let Some(parent_node) = queue.pop() {     if let Entry::Vacant(entry) = states.entry(parent_node.state) {         for (action, transition) in entry.key().transitions(data) {             ...         }         entry.insert(());     } }

Должно сработать. Давайте посмотрим, как это изменило время выполнения:

$ cargo bench  solve_square_dance      time:   [3.8785 ms 3.9364 ms 3.9998 ms]                         change: [+0.9796% +2.8698% +4.8065%] (p = 0.00 < 0.05)  solve_fractal           time:   [6.7915 ms 6.8446 ms 6.9022 ms]                         change: [-2.2994% -1.2597% -0.0914%] (p = 0.03 < 0.05)  solve_antiparticle      time:   [172.04 ms 172.76 ms 173.54 ms]                         change: [-2.4655% -1.8303% -1.1884%] (p = 0.00 < 0.05)

Печально, статистически изменения во времени выполнения отсутствуют. Предполагаю, что у entry API есть какие-то издержки, столь же затратные, как и поиск со вставкой. Если кто-то знает, почему суммарное время выполнения не изменилось, мне бы хотелось знать. Но в то же время это демонстрирует важность бенчмаркинга каждого шага. Даже самые очевидные улучшения в конечном итоге могут никак не повлиять на общую производительность, или даже повлиять отрицательно.

Предварительное задание размеров контейнеров

Мы почти добрались до дна бочки. Мы можем реализовать быструю и простую оптимизацию — заранее задать размер контейнеров. Это поможет нам не выполняют любую потенциальную работу по изменению размера контейнеров под рабочие нагрузки ниже некого минимального размера. Я выбрал относительно произвольные заданные размеры:

let mut states = HashMap::with_capacity(4 * 1024); let mut parents = Vec::with_capacity(4 * 1024); let mut queue = BinaryHeap::with_capacity(1024);

Стоит ожидать, что они повлияют только на головоломки, не использующие память выше порога заданного размера. Так ли это?

$ cargo bench  solve_square_dance      time:   [3.7025 ms 3.7311 ms 3.7652 ms]                         change: [-9.7612% -8.3409% -6.9754%] (p = 0.00 < 0.05)  solve_fractal           time:   [6.0512 ms 6.1010 ms 6.1584 ms]                         change: [-15.633% -14.231% -12.778%] (p = 0.00 < 0.05)  solve_antiparticle      time:   [177.28 ms 178.40 ms 179.59 ms]                         change: [-0.7913% +0.0458% +0.8882%] (p = 0.92 > 0.05)

Именно это мы и видим. В головоломках попроще мы получаем очень хорошие дельты, но на бенчмарки с большими головоломками это совершенно не повлияло. Однако это всё равно снижает время среднестатистического случая, и это для нас важно. Наконец, мы можем перейти к последней стратегии оптимизации.

Встраивание

Один из последних инструментов в инструментарии увеличения производительности — это встраивание функций. Воздействие встраивания функций сильно разнится, нам нужно быть аккуратными и постоянно проверять код бенчмарками. Давайте посмотрим, есть ли подходящие кандидаты на встраивание. В идеале подходящая функция для встраивания должна быть или маленькой, или средней, и использоваться один или два раза. В нашем векторе есть много простых функций, а также присутствуют примитивы директив, которые мы можем встроить, и это немного нам поможет. Встраивание особенно активно использующейся функции transition для состояния головоломки также позволит устранить часть издержек.

Давайте посмотрим, как это влияет на производительность:

$ cargo bench  solve_square_dance      time:   [3.7629 ms 3.8005 ms 3.8413 ms]                         change: [-10.293% -8.6516% -6.9645%] (p = 0.00 < 0.05)  solve_fractal           time:   [6.7174 ms 6.8059 ms 6.9033 ms]                         change: [-10.243% -8.5148% -6.7086%] (p = 0.00 < 0.05)  solve_antiparticle      time:   [175.97 ms 176.63 ms 177.38 ms]                         change: [-6.8481% -5.9110% -5.0767%] (p = 0.00 < 0.05)

Наш надёжный набор улучшений готов! Всегда радостно видеть такое.

Подведём итоги

Мы успешно создали солвер головоломок, способный решать задачу при всех возможных входных данных, и оптимизировали его при помощи специализированных методик брутфорса, а также общих методик оптимизации.

Мы рассмотрели влияние различных методик на производительность и знаем теперь, чего ждать в будущем.

Наконец, мы научились не предполагать, что какая-то конкретная оптимизация равномерно повлияет на все случаи, или что она хотя бы повлияет положительно.

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

При написании солвера для Stephen’s Sausage Roll, позволявшей получить более точную эвристику, выигрыш от эвристической функции был гораздо больше.

Кстати, вы прямо сейчас можете использовать написанный в статье солвер! Его можно включить на любой странице с головоломкой, добавив к URL строку &controls=true (например, здесь).


ссылка на оригинал статьи https://habr.com/ru/company/beeline/blog/663852/

Робот на все руки. Как современная робототехника помогает людям восстанавливаться после инсультов

Реальная угроза

Малоподвижный образ жизни, повышенное давление, лишний вес, стресс, некачественный сон, курение, переработки — все это может привести к инсульту. И не надо думать, что инсульт случается только с пожилыми. Треть больных — это люди трудоспособного возраста. При этом лишь 10–20% (по разным данным) пациентов после болезни возвращаются к труду.

Посмотрим на данные официальной российской статистики: в стране ежегодно происходит более 400 тысяч случаев инсульта, причем лишь 8–10% из них оказываются относительно легкими и заканчиваются восстановлением нарушенных функций в первые три недели заболевания.

Инсульт стоит на первом месте среди всех причин инвалидности в России. 31% перенесших инсульт нуждаются в посторонней помощи для ухода за собой, а 20% не могут самостоятельно ходить.

Такой урон человеческому организму наносит острое нарушение мозгового кровообращения, которое приводит к гибели клеток мозга. Так как за движение отвечает значительная часть нервной системы, различные двигательные нарушения являются чрезвычайно распространенным симптомом при инсульте. В острой стадии заболевания они выявляются у 70–90% пациентов, спустя год какие-то нарушения сохраняются не менее чем у половины выживших пациентов, отмечают учёные. Особенно часто при инсульте страдает пирамидная система, отвечающая за произвольные движения (и да — там есть что ломать!). 

На сцену выходят роботы

Конечно, реабилитация после инсульта — важная и непростая задача. И тут на помощь врачам могут прийти роботы. В целом можно выделить два типа реабилитационных роботов — вспомогательный робот, который заменяет потерянные движения конечностей, и терапевтический робот, который позволяет пациентам выполнять упражнения. Эти упражнения опираются на нейропластичность мозга, в частности на способность восстанавливать разрушенные нейронные связи. Обычно устройства помогают в реабилитации рук и ног, поддерживая повторяющиеся движения, которые позволяют создавать неврологические пути для работы мышц.

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

Интересно, что идея использования машин для реабилитации возникла очень давно. В патенте 1910 года Теодора Бюдингена описан «аппарат для лечения движением». Это электрическая машина, которая поддерживала шаговые движения у пациентов с заболеваниями сердца. В 1930-х годах Ричард Шерб разработал «меридиан» — тросовый аппарат для перемещения суставов в ортопедической терапии. Он уже поддерживал несколько режимов взаимодействия: пассивный, движения с активной помощью и с активным сопротивлением. 

Первые электрические экзоскелеты для терапевтического применения были придуманы для пациентов с травмами спинного мозга в 1970-х годах. В этих системах использовались пневматические, гидравлические или электромагнитные приводы. Вскоре экзоскелеты хорошо себя показали и в реабилитации после инсульта.

Последние десятилетия ознаменовались бурным развитием новых реабилитационных роботов как для верхних, так и для нижних конечностей, которые некоторые исследователи делят на заземленные экзоскелеты, заземленные исполнительные устройства и носимые экзоскелеты.

Основные типы реабилитационных роботов: 

Схема из исследования Rehabilitation robots for the treatment of sensorimotor deficits: a neurophysiological perspective, вышедшего в журнале Journal of NeuroEngineering and Rehabilitation
Схема из исследования Rehabilitation robots for the treatment of sensorimotor deficits: a neurophysiological perspective, вышедшего в журнале Journal of NeuroEngineering and Rehabilitation

По данным Global Rehabilitation Robots Market Report 2022–2026, мировой рынок реабилитационных роботов к 2026 году достигнет 1,8 млрд долларов. В 2020 году он оценивался в 566,5 млн долларов. Ключевыми игроками мирового рынка реабилитационных роботов считаются компании ReWalk Robotics, Ekso Bionics, AlterG Inc., Bionik Labs, Technologies Ltd., Motus Nova, Kinova Inc., Meditouch, Wearable Robotics Srl, Hocoma. Все более актуальным становится создание реабилитационных роботов для дома. Это происходит в том числе и потому, что стоимость реабилитации остается очень высокой.

Юрий Потанцев, участник рынка НТИ HealthNet, руководитель проекта VIBRAINT RehUp (реабилитационный робот, управляемый нейроинтерфейсом), рассказал мне, что на российском рынке представлены несколько десятков производителей робототехники для применения в разных областях медицины. 

«Благодаря развитию технологий качество и количество робототехнических устройств растут, как и количество производителей. Продажи у каждого могут варьировать от единиц до сотен экземпляров в зависимости от потребностей рынка, конкурентоспособных характеристик продукции и цены, — говорит он. — Среди лидеров можно назвать „Экзоатлета“ (реабилитационные экзоскелеты) и „Моторику“ (высокотехнологичные протезы конечностей). Появляется много стартапов в этой области, но далеко не все доходят до коммерческой стадии развития из-за технологических трудностей и большой конкуренции. Кроме того, разработки должны соответствовать стандартам, распространяемым на медицинские изделия, что тоже создает свои сложности».

Рукопожатие робота

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

Реабилитация усложняется еще и из-за того, что пациенты обычно пытаются компенсировать сенсомоторный дефицит за счет вовлечения здоровой руки. Поэтому один из важных подходов к восстановлению функций руки после инсульта — двигательная терапия, вызванная ограничением (CIMT). При иммобилизации здоровой руки пациент вынужден использовать руку с парезом для выполнения движений.

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

Эволюция роботов для реабилитации верхних конечностей. От жестких промышленных манипуляторов до специализированных реабилитационных роботов:

Схема из исследования Rehabilitation robots for the treatment of sensorimotor deficits: a neurophysiological perspective, вышедшего в журнале Journal of NeuroEngineering and Rehabilitation
Схема из исследования Rehabilitation robots for the treatment of sensorimotor deficits: a neurophysiological perspective, вышедшего в журнале Journal of NeuroEngineering and Rehabilitation

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

Большие компании создают целые системы, которые можно использовать для разработки разнообразных движений рук пациентов на разных этапах восстановления. Так, американская сеть клиник Kindred Hospital Rehabilitation Services установила роботизированную систему для реабилитации верхних конечностей InMotion Arm от Bionik Laboratories в своих центрах по всей территории Соединенных Штатов. Аппарат использует искусственный интеллект с системой анализа данных для настройки индивидуальной терапии и формирования отчетов. 

InMotion ARM точно оценивает движения рук, позволяя врачу лучше измерять и количественно оценивать прогресс пациента и реакцию на терапию. В отчетах фиксируются результаты лечения пациентов для четкого улавливания прогресса на протяжении всего реабилитационного периода. При этом настройка робота занимает две-три минуты. Распространение таких систем сдерживает только один фактор — они очень дорогие, даже для США.

Система InMotion Arm от Bionik Laboratories: 

Лечение в виртуальной реальности

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

Любопытно, что иногда реабилитационные роботы с VR приходят из игровой индустрии. Такова история российской перчатки-тренажера Senso Rehab, которая восстанавливает мелкую моторику кистей рук после инсульта. Сперва перчатку создали для компьютерных игр с технологией виртуальной реальности, а не для медицинских целей. Благодаря датчикам и вибромоторам перчатка должна была создавать иллюзию осязания в виртуальном мире. Однако о ней узнал доктор медицинских наук, профессор, завкафедрой нервных болезней Красноярского государственного медуниверситета, главный нейрореабилитолог Сибирского федерального округа Семен Прокопенко и побудил разработчиков сделать на основе изобретения медицинский тренажер. 

Сделано в Иннополисе

Российский разработчик Валерия Скворцова из Центра компетенций НТИ по направлению «Технологии компонентов робототехники и мехатроники» на базе Университета Иннополис пошла своим путем и предложила необычное решение реабилитационного робота для восстановления кистей рук после инсульта, сфокусировавшись на задаче разработки движения в лучезапястном суставе.

Два года назад, будучи аспиранткой Университета Иннополис, Валерия заинтересовалась темой параллельно-сферических роботов. Ее она и выбрала для защиты магистерской диссертации. 

«Я подала заявку на получение гранта „Умник“ от Фонда содействия инновациям и выиграла его. Так я начала проект робота-манипулятора, который помогает восстанавливать подвижность кистей рук после инсульта. Финансирование проекта шло в рамках выигранного конкурса, который предполагал выделение 500 тысяч рублей в течение года на развитие идеи. В общей сложности от идеи до ее воплощения прошло два года. Я надеюсь, что вскоре мой прототип испытают на пациентах, и проект масштабируют. Очень важно помогать людям реабилитироваться, а также совершенствовать робототехническую промышленность в России. Я пока не планирую представить аппарат на мировом рынке, но, если получится, будет здорово», — поделилась Валерия Скворцова.

Проект Валерии Скворцовой находится на стадии оформления интеллектуальной собственности. Сотрудница лаборатории робототехники уже получила свидетельство Роспатента о регистрации «Программного обеспечения для расчета кинематики параллельного сферического манипулятора». Чтобы отладить прототип и поставить первичные эксперименты, потребуется время. Для полноценного запуска проекта при наличии заказчиков, по оценке специалистов по робототехнике, необходимо 1–2 года.

Руководитель лаборатории робототехники Центра компетенций НТИ по направлению «Технологии компонентов робототехники и мехатроники» на базе Университета Иннополис Артур Шимановский рассказал мне о машине. «В мире есть много примеров реабилитационных тренажеров, но параллельно-сферических роботов нет. Преимущество нашего тренажера в том, что его можно настроить под индивидуальную программу реабилитации», — подчеркнул он.

Медицинский робот для реабилитации кисти руки, разработанный Валерией Скворцовой, создан на базе параллельного сферического манипулятора с силомоментной обратной связью. Робот обладает тремя вращательными степенями свободы с фиксированным центром вращения. Центр вращения робота совпадает с центром вращения запястья человека. 

Основная конструкция аппарата состоит из базовой и мобильной платформ и трех ног, образованных двумя дугообразными плечами. Пациент просовывает руку в аппарат и держится за ручку на верхней мобильной платформе, его локтевой сустав при этом фиксируется на специальной подставке. Подвижные элементы робота разрабатывают лучезапястный сустав руки: плавно двигают кисть вниз-вверх, вправо-влево и скручивают в обе стороны. Конструкция устройства рассчитана с учетом воздействия среднестатистической массы руки (3 кг) и имеет силу стандартного движения здорового человека — 30 N.

В отличие от иных подобных механизмов подвижные части этого робота расположены асимметрично. Это увеличивает полезное рабочее пространство машины, что позволяет приблизить траектории перемещения кисти к естественным движениям пациента и делает реабилитацию больных с инсультом более эффективной. Рабочее пространство робота составляет от −85 до 84 градусов вращения вокруг оси X, от −83 до 83 градусов вращения вокруг оси Y и от −180 до 180 градусов вращения вокруг оси Z.

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

Для этого проекта Валерия разработала математическую модель и написала программный код. Модель вычисляет движения робота вниз-вверх, вправо-влево и скручивания, то есть прямую, обратную и дифференциальную кинематику. Решение этих задач — верификация кинематики — проводилась на первых прототипах аппарата. Есть отдельные скрипты для общения с моторами — они сделаны на открытом исходном коде.

Юрий Потанцев считает, что разработка Валерии Скворцовой технологически интересна. Он отметил, что рынок тренажеров для разработки рук после травм и инсультов довольно насыщен. Наиболее сложная и долгая часть в разработке медицинских изделий, по его словам, — тестирование и валидация, организация клинических исследований и поиск надежных ответственных партнеров для них. 

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


ссылка на оригинал статьи https://habr.com/ru/company/leader-id/blog/663854/