Pull to refresh

Трудности поиска ошибок в научных приложениях

Reading time6 min
Views1.7K
Это продолжение заметки о том, почему юнит-тесты плохо работают в научных приложениях; и в этой статье я хочу рассказать о трудностях поиска ошибок и дебага (научных приложений), с которыми в свое время столкнулся, и многие из которых были удивительными для меня как веб-разработчика.


Статья будет состоять из нескольких разделов:
  1. Введение, для порядка
  2. Трудности поиска ошибок
    • Параллелизм
    • Нелокальность ошибок
    • Неочевидность опечаток
    • Влияние сеттинга на результат
    • Идентификация ошибок: ошибка или нет?

  3. Заключение

Введение


Основная цель этой статьи—это описание собственного опыта в надежде, что кому-то он будет интересен и необычен (особенно, как мне кажется, промышленным программистам); возможно, кто-то сможет лучше подготовиться к написанию дипломов/курсовых/лабораторных. Преамбулу, постановку научной проблемы, краткое описание алгоритма можно прочитать в упомянутой статье [1]. Поэтому сразу перейду к изложению тех необычных (например, для веб-разработчиков) трудностей поиска ошибок, дабы расширить кругозор читателей и сознание в целом.

Трудности поиска ошибок


Параллелизм

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

Нелокальность ошибок

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

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

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

Кроме того, ошибки в научных приложениях могут долгое время не проявляться. Например, если при добавлении нового типа граничных условий вдруг перестал правильно моделироваться поток Пуазейля [2], приводимый в движение силой, а не градиентом давления, то может оказаться, что дело не в новом алгоритме граничных условий, а в логике учета внешней силы, просто до этого ошибка не была критична. (см также пункт «Малая скорость возрастания ошибок» в [1]).

Неочевидность опечаток

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

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

Если вы дебажите бизнес-приложение и наблюдаете строчку типа
bool categoryIsVisible = categoryIsEnabled || productsCount > 0;
то вы сразу заметите опечатку, потому что в условии должно стоять логическое «И».

Но представьте, что вам попалась на глаза строчка (из реального проекта)
double probability = latticeVectorWeight * density * (1.0 + 3.0 * dotProduct + 9.0 / 2.0 * dotProduct * dotProduct — 3.0 / 2.0 * velocitySquare);
Вряд ли вы определите, что перепутали где-то плюс с минусом. И, кстати, имена переменных здесь осмысленные.

Влияние сеттинга на результат

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

Основные источники зависимостей от сеттинга следующие.

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

2. Меньшая область устойчивости алгоритмов по входным данным. В бизнес-приложениях основное ограничение на данные—это отсутствие ошибок переполнения (да и кто на него обращает внимание?!). В научных алгоритмах (одно из отличий которых заключается в работе с множествами большей мощности) нужно вспоминать об устойчивости (а вслед за этим и о жесткости дифференциальных уравнений, теории устойчивости, показателях Ляпунова и т.д.) и следить за ней. Кроме того, в бизнес-приложениях все ограничения детерминированы (мол, имя при регистрации не может быть длиннее 100 символов, email должен соответствовать определенному регулярному выражению), в научных же задачах для определения рабочего диапазона входных данных часто приходится пользоваться методом проб и ошибок.

3. Все остальное (трудноформализуемое пока для меня). В частности, это перевод единиц измерения из физических в единицы измерения для программы.

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

Внимание! Он несколько далек от тематики хабра и интересов большинства читателей, так что можно его при желании пропустить и перейти к следующему пункту.
Итак, checklist:
  1. Проверить несжимаемость
  2. Проверить числа Рейнольдса
  3. Проверить перевод величин

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

Второй из пунктов эквивалентен проверке области устойчивости алгоритма. Дело в том, что число Рейнольдса определяет, насколько движение жидкости турбулентно и неустойчиво [3]. Чем оно больше, тем течение неустойчивей, чем меньше--тем более «вязкое». Оказывается, что даже если движение никогда не будет турбулентным физически (опять же, в потоке Пуазейля), то расчеты начинают расходиться при достаточно больших числах Рейнольдса. Разумеется, пока я не наступил и на эти грабли (и не походил по ним неделю), то не задумывался о слежении за областью устойчивости.

Третий пункт специфичен только для физических расчетов и некоторых алгоритмов. Использовавшийся метод принимает входные физические величины в специальных единицах решетки (когда единица длины—это шаг равномерной пространственной решетки, и ей же пропорциональна единица времени). До тех пор, пока не наткнулся на специальную статью [4], посвященную переводу величин в этом методе, то безуспешно пытался в течение нескольких недель понять, почему программа ведет себя не совсем верно.

Стоит заметить, что второй и третий пункты едва допускают автоматическую проверку.

Идентификация ошибок: ошибка или нет?

Это проблема совершенно невообразима в бизнес-приложениях; и заключается она в том, что часто невозможно наверняка сказать, является ли ошибкой отклонение в поведении программы (от ожидаемого).

Например, известно, что профиль скоростей при течении вязкой жидкости по цилиндрической трубе будет параболическим [2]. Тем не менее, предположим, что при моделировании жидкость у стенок трубы течет чуть быстрее, чем должна. Варианты обычно рассматриваются следующие:
  1. это на самом деле ошибка
  2. это особенность алгоритма
  3. это следствие неправильных или неподходящих для алгоритма входных данных (начальных условий, физических параметров) (см. «Влияние сеттинга на результат»)

Проверка через модульное тестирование первого пункта осложняется трудностями написания юнит-тестов в таких приложениях [1].

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

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

Заключение


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

Спасибо за прочтение!

Ссылки:

[1] Почему юнит-тесты не работают в научных приложениях
[2] Поток Пуазейля
[3] Число Рейнольдса
[4] Перевод величин в LBM
Tags:
Hubs:
Total votes 49: ↑43 and ↓6+37
Comments59

Articles