1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 1 из 6

Линейное программирование: задача, у которой ответ в углу

Коротко

Линейное программирование - задача «максимизировать линейную цель при линейных ограничениях». Никакого программирования в нынешнем смысле: слово programming тут значит «планирование». Допустимые точки образуют выпуклый многогранник, а линейная цель на выпуклом многограннике всегда достигает максимума В ВЕРШИНЕ - если максимум вообще есть. Отсюда и вся вычислительная стратегия: не искать по всей области, а перебирать вершины умно. В эту форму укладываются десятки практических задач - от раскроя материала до подбора рациона, - и туда же, как выяснится через два урока, сводятся поток и паросочетание.

Если ты умеешь записать задачу в стандартной форме, знаешь, почему оптимум достигается в вершине, и понимаешь разницу между линейным и целочисленным программированием, листай до «Теперь сам».

Как думаешь, где искать максимум

Цех делает два изделия. На первое уходит 2 часа станка и 3 часа сборки, на второе - 4 часа станка и 1 час сборки. Станок доступен 12 часов, сборка - 10. Прибыль: 5 за первое изделие, 4 за второе. Сколько чего делать?

Соблазн - перебрать целые количества. Но сначала ответь на другой вопрос: где вообще может стоять ответ? Внутри области или на границе? И если на границе, то в любой её точке или в особенной?

На что это похоже

Наклонная плоскость над многоугольной площадкой. Цель 5x + 4y - это высота: чем дальше в направлении «роста», тем выше. Поставь шарик в любую точку площадки и отпусти - он покатится в сторону роста и остановится, упёршись в край. Покатится вдоль края - и остановится в углу. Именно поэтому оптимум ищут в углах: из любой внутренней точки всегда есть куда идти, а из угла - уже нет.

Механизм: стандартная форма и геометрия

стандартная форма:
    максимизировать   c₁x₁ + c₂x₂ + ... + cₙxₙ
    при условиях      a₁₁x₁ + ... + a₁ₙxₙ <= b₁
                      ...
                      aₘ₁x₁ + ... + aₘₙxₙ <= bₘ
                      x₁ >= 0, ..., xₙ >= 0

Каждое неравенство отсекает полуплоскость (в общем случае полупространство). Пересечение полупространств - выпуклый многогранник: у него плоские грани и нет вмятин. Цель cᵀx растёт в одном фиксированном направлении, поэтому её линии уровня - параллельные прямые (плоскости), и двигать их можно только до касания многогранника.

три исхода линейной задачи:
    допустимых точек нет вовсе          - задача НЕСОВМЕСТНА
    цель растёт неограниченно           - оптимум НЕ ОГРАНИЧЕН
    оптимум конечен                     - он достигается в вершине
                                          (иногда на целой грани, но вершина среди них есть)

Задача минимизации сводится к максимизации сменой знака цели, ограничение >= - сменой знака обеих частей, равенство - парой неравенств. Поэтому одной формы хватает на все случаи.

Правило

Линейное программирование: максимизировать линейную цель при линейных ограничениях-неравенствах и неотрицательности переменных. Допустимая область - выпуклый многогранник. Если оптимум конечен, он достигается в ВЕРШИНЕ - точке пересечения n ограничений, обратившихся в равенства. Задача бывает несовместной или неограниченной, и оба случая надо различать в ответе. Целочисленное программирование - это то же самое плюс требование целых значений, и оно NP-трудно: вершины перестают помогать.

Разбор: считаем цех по вершинам

Запишем задачу из вступления: x - первых изделий, y - вторых.

максимизировать  5x + 4y
    2x + 3y <= 12        станок (2 часа и 3 часа)
    4x +  y <= 10        сборка (4 часа и 1 час)
    x >= 0,  y >= 0

Вершины - это пересечения пар ограничений, обратившихся в равенства. Перебираем:

(0, 0)                     цель 0
(0, 4)    из 2x + 3y = 12  цель 16
(2.5, 0)  из 4x +  y = 10  цель 12.5
(1.8, 2.8) пересечение обоих ограничений:
           2x + 3y = 12 и 4x + y = 10  →  x = 1.8, y = 2.8
           проверка: 3.6 + 8.4 = 12 <= 12 ✓,  7.2 + 2.8 = 10 <= 10 ✓
           цель 5·1.8 + 4·2.8 = 9 + 11.2 = 20.2

Максимум - 20.2 в дробной точке. Теперь сравни с перебором целых точек: лучшая целая - (2, 2) с целью 18. Разница не в точности вычислений, а в том, что ответ линейной задачи ПРИНЦИПИАЛЬНО может стоять между узлами сетки. Уменьшение шага не помогает - оно лишь делает перебор дороже.

Разбор: почему оптимум обязан быть в вершине

Возьми любую допустимую точку, которая не является вершиной. Тогда у неё есть направление, в котором можно сдвинуться в обе стороны, не выходя из области, - скажем, d и −d. Значение цели изменится на cᵀd и −cᵀd: одно из двух неотрицательно. Значит, сдвинувшись в подходящую сторону, цель не ухудшишь.

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

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

Сколько всего вершин? Каждая задаётся набором из n ограничений, обратившихся в равенства, значит их не больше C(m, n), где m - число ВСЕХ ограничений, включая x >= 0: они такие же прямые, и вершины на них лежат наравне с остальными. Для двух переменных и десяти ограничений (восемь содержательных плюс две неотрицательности) это C(10, 2) = 45 - перебрать легко. Для двадцати переменных и сотни ограничений - астрономическое число, и нужен алгоритм, который ходит по вершинам, а не перебирает их. Это симплекс, и он в следующем уроке.

Что ломается без этого

Первое: искать оптимум перебором целых точек. Ответ будет занижен, а работы - в разы больше. Второе: забыть ограничения неотрицательности. Без них область другая, и «оптимум» может уехать в минус. Третье: не различать несовместную и неограниченную задачу: первая означает «ограничения противоречат друг другу», вторая - «в модели что-то забыли, прибыль не может расти вечно». Четвёртое: считать, что раз ответ дробный, надо просто округлить его до ближайшего целого - округлённая точка может оказаться недопустимой, а честное целочисленное решение может лежать совсем в другом углу. Пятое: путать линейное программирование с целочисленным: первое решается за полиномиальное время, второе NP-трудно, и внешне задачи отличаются одной строчкой условия.

Зачем это в работе

Линейное программирование - редкий случай, когда «есть готовый решатель» - это правда. Задачу описывают, а решает её библиотека (scipy.optimize.linprog, PuLP, OR-Tools, коммерческие Gurobi и CPLEX), причём с десятками тысяч переменных. Выглядит описание так:

from scipy.optimize import linprog
# linprog МИНИМИЗИРУЕТ, поэтому цель 5x + 4y берётся со знаком минус
res = linprog(c=[-5, -4],
              A_ub=[[2, 3], [4, 1]], b_ub=[12, 10],   # ограничения-неравенства
              bounds=[(0, None), (0, None)])          # x >= 0, y >= 0
print(-res.fun, res.x)        # 20.2  [1.8 2.8]
20.2 [1.8 2.8]

Вся работа - в трёх списках: коэффициенты цели, матрица ограничений и правые части. Практическая работа сводится к тому, чтобы ПРАВИЛЬНО ЗАПИСАТЬ задачу: что за переменные, какая цель, какие ограничения. Типовые сюжеты: раскрой материала с минимумом отходов, составление рациона или смеси заданного состава, загрузка производства, распределение рекламного бюджета, планирование смен, логистика перевозок. А ещё линейная форма нужна как ЯЗЫК: многие задачи проще сперва записать как ЛП, а потом заметить, что у них есть специальная быстрая структура - например, что это поток.

Теперь сам

Запиши задачу и найди максимум: столяр делает табуретки и полки. Табуретка требует 3 доски и 2 часа, полка - 1 доску и 4 часа. Досок 24, часов 32. Прибыль: 6 за табуретку, 5 за полку.

Ответ. Пусть x - табуретки, y - полки.

максимизировать  6x + 5y
    3x + y  <= 24      доски
    2x + 4y <= 32      часы
    x >= 0,  y >= 0

Вершины:

(0, 0)                          цель 0
(0, 8)    из 2x + 4y = 32       цель 40
(8, 0)    из 3x + y = 24        цель 48
пересечение 3x + y = 24 и 2x + 4y = 32:
    из первого y = 24 − 3x, подставляем: 2x + 96 − 12x = 32 → −10x = −64 → x = 6.4, y = 4.8
    проверка: 19.2 + 4.8 = 24 ✓,  12.8 + 19.2 = 32 ✓
    цель 6·6.4 + 5·4.8 = 38.4 + 24 = 62.4

Максимум 62.4 при x = 6.4, y = 4.8. Столяру, конечно, нужны целые табуретки - и вот тут начинается целочисленное программирование: лучшая целая точка здесь (6, 5) с целью 36 + 25 = 61 (проверь: 18 + 5 = 23 <= 24, 12 + 20 = 32 <= 32). Заметь, что она НЕ получается округлением 6.4 и 4.8 - округление вверх дало бы (7, 5), а это уже 21 + 5 = 26 > 24 досок, то есть недопустимо.

Главное

Линейное программирование - максимизация линейной цели при линейных ограничениях; допустимая область получается пересечением полупространств и всегда выпукла. Если оптимум конечен, он достигается в вершине - точке, где n ограничений обратились в равенства; поэтому искать надо по вершинам, а не по сетке, и дробный оптимум - обычное дело. Три исхода задачи: несовместна, неограниченна или имеет конечный оптимум. Вершин не больше C(m, n) - для двух переменных их перебирают руками, для двадцати нужен симплекс. Требование целых значений превращает задачу в целочисленное программирование, а его сложность - совсем другая: NP-трудная, и округлять дробный ответ нельзя.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

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