Привет, Habr! На связи отдел аналитики данных X5 Tech. Сегодня мы поговорим об очень интересном разделе прикладной математики — оптимизации. Цели данной статьи: рассказать про задачи в ритейле, которые могут решаться методами оптимизации, продемонстрировать, как модельная задача ценообразования решается пакетами Pyomo и SciPy, сравнить производительность солверов* Pyomo и SciPy на примере поставленной задачи. Прим. Солвер (от англ. Solver) - программа, скомпилированная под выбранную платформу, для решения математической задачи. Так как данная тема достаточно обширна, то помимо данной статьи (статья 1) мы планируем написать ещё две: Статья 2: Обзор open-source солверов на примере задачи ритейла. Статья 3: Решение модельной задачи ценообразования оптимизаторами в различных постановках. Практически каждый человек ежедневно решает оптимизационные задачи даже не задумываясь об этом. Пара примеров: Закупка. Как правило, мы хотим минимизировать наши расходы для приобретения необходимых товаров, но при условии, чтобы эти товары были максимально полезны. Полезность здесь у каждого своя: для одних она определяется количеством растительных жиров, для других — минимальной суммарной стоимостью корзины, для третьих — наличием привычных товаров, и тд. Отпуск. Во время ограниченного отпуска мы хотим распределить свой маршрут так, чтобы посетить всё, что запланировали с минимальными временными затратами на дорогу, и при этом не забыть позагорать на пляже. Бизнес-требования часто приводят к задаче многокритериальной оптимизации и задаче оптимального управления. В таких постановках решения, найденные методами оптимизации, чрезвычайно полезны для принятия решений. Для иллюстрации приведём несколько задач из области ритейла, которые могут быть сформулированы в виде задачи оптимизации. Ценообразование. Поиск оптимальной конфигурации цен с учетом ценового позиционирования, допустимых ценовых диапазонов для каждого товара и набора бизнес-ограничений. Цены должны максимизировать суммарный доход, а прибыль быть не ниже заданного уровня. Оптимальное распределение маркетингового бюджета. Реализовать максимально эффективно выделенный на маркетинговые активности бюджет. Есть несколько каналов для рекламных акций, цель — инвестировать бюджет так, чтобы суммарный доход со всех коммуникаций был максимален. Необходимо учесть ограничения на предельную нагрузку на канал, допустимое количество коммуникаций для каждого клиента и пр. Планирование ассортимента. Подобрать полочный ассортимент товаров так, чтобы максимизировать оборот с учетом полочного пространства и других характеристик магазина. Закупка товаров. Задача — распределить бюджет, выделенный на закупки так, чтобы поддерживать товарооборот, заданный уровень доступности товаров и при этом достигнуть определенных финансовых показателей. Логистика. Задача — найти оптимальный график доставки продуктов с учётом вместимости грузовиков, затрат на логистику и тд. Прежде всего рассмотрим постановку задачи в общем виде: - вектор размерности  - допустимое множество значений этих переменных.  - целевая функция; - ограничения вида неравенств; - ограничения вида равенств; Исходя из практики можно разложить данную постановку на несколько классов в зависимости от вида целевой функции, ограничений и : Безусловная оптимизация - отсутствуют,  LP (linear programming) - линейное программирование. - линейные функции,  MILP (mixed integer linear programming) - смешанное целочисленное линейное программирование, это задача LP в которой только часть переменных являются целочисленными; NLP (nonlinear programming) - нелинейное программирование, возникает когда хотя бы одна из функций нелинейна; MINLP (mixed integer nonlinear programming) - смешанное целочисленное нелинейное программирование, возникает как и в MILP, когда часть переменных принимает целочисленные значения; NLP в свою очередь можно подробить еще на множество разных классов в зависимости от вида нелинейности и выпуклости. Подробнее о том, как получаются различные виды задачи ценообразования исходя из бизнес-формулировки, мы будем говорить в статье 3. Рассмотрев основные классы оптимизационных задач, перейдём к построению модели ценообразования. Предположим, что для товара известно значение эластичности , а спрос задаётся следующей зависимостью: Введём обозначения: - количество товаров, - себестоимость i-го товара, - спрос по новой и текущей ценам, соответственно, . В качестве иллюстративного примера рассмотрим задачу с одним ограничением сложного вида и ограничения на переменные. Задача — найти такой набор новых цен, чтобы: максимизировать оборот со всех товаров, общая прибыль осталась на прежнем уровне, цены лежали в заданных границах (индекс l и u - для нижней и верхней, соответственно). Тогда оптимизационную задачу можно записать следующей системой: Данная модель принадлежит к классу NLP, как нетрудно заметить из данных выше определений. Для демонстрации решения необходимы данные. Реальные данные мы использовать не можем из-за NDA, поэтому будем генерировать их из случайных распределений (функция generate_data), исходя из наших представлений о возможных значениях величин в фуд-ритейле.  SciPy и Pyomo имеют разные интерфейсы, чтобы как-то унифицировать работу с ними, будем наследоваться от базового класса, код класса. Методы, которые необходимо реализовать для каждого солвера: init_objective - задание целевой функции, init_constraints - добавление ограничений, solve - поиск оптимального решения. Опишем далее отличия в реализации этих методов в SciPy и Pyomo. Другие примеры из официальной документации можно найти здесь, для Pyomo и здесь, для SciPy. В SciPy оптимизатор работает в режиме минимизации, поэтому суммарный оборот берём со знаком "-". В Pyomo функцию пересчёта суммарного оборота необходимо передать в переменную expr объекта pyo.Objective. Ограничение на суммарную прибыль задаётся в методе init_constraints. Для SciPy ограничения передаются через NonlinearConstraint или LinearConstraint(). Для Pyomo ограничения передаются через pyo.Constraint(). В SciPy задача оптимизации запускается через метод minimize, а в Pyomo через проинициализированный объект SolverFactory методом solve. Так как оптимизаторы устанавливаются отдельно от python, то для получения аналогичных результатов можно воспользоваться Dockerfile, настроив контейнер, как описано в README проекта. Для сравнения расчётов достаточно выполнить команду (осторожно, в режиме compare расчёт на макбуке 2019 года занимает > 3 часов, для ускорения можно уменьшить GRID_SIZE): Зависимость длительности поиска оптимального решения от количества переменных представлена на графике ниже. Из графика можно сделать вывод, что Pyomo с солвером ipopt значительно превосходит SciPy с cobyla при N > 20. Отметим, что Pyomo затрачивает больше времени на подготовку данных во внутренний формат, что при небольших значениях N занимает больше времени, чем поиск решения. Более детально об этом поговорим в следующих статьях. Также затронем вопрос зависимости времени поиска решения от числа ограничений. В данной статье мы: Привели типичные примеры постановок задач оптимизации в области ритейла. Рассчитываем, что читателю станет легче распознавать подобные задачи в своей области. Показали, как можно решать задачу ценообразования с помощью Pyomo (код для статьи). Будем рады, если новичкам в моделировании данный материал упростит ознакомительный этап. Сравнили производительность солверов Pyomo.ipopt и Scipy.cobyla. Можно заметить существенную разницу во времени работы солверов и в результатах, о чем не следует забывать на практике. В следующих статьях более детально обсудим другие open-source солверы для python, их различия в реализации и производительности. Над статьей работали Антон Денисов, Михаил Будылин.