Решение оптимизационных задач линейного типа на Python

Раздел: Научные вычисления -> Анализ данных и научные вычисления

Основной метод: scipy.optimize.linprog

Как решить задачу линейного программирования с помощью SciPy?

Функция linprog из библиотеки SciPy предлагает несколько алгоритмов, среди которых Highs (используется по умолчанию) является наиболее эффективным для большинства задач. Задача формулируется в стандартной форме: минимизация cTx при ограничениях A_ub x ≤ b_ub, A_eq x = b_eq и границах переменных bounds.

import numpy as np
from scipy.optimize import linprog

c = np.array([-1, 4])                     # коэффициенты целевой функции
A_ub = np.array([[-3, 1], [1, 2]])        # левая часть неравенств
b_ub = np.array([6, 4])                   # правая часть неравенств
bounds = [(0, None), (0, None)]           # x1 >= 0, x2 >= 0

res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
print('Статус:', res.message)
print('Решение:', res.x)
print('Значение ЦФ:', res.fun)

Python для инженерных задач (python для инженерных задач)

Статус: Optimization terminated successfully. 
Решение: [0.8 1.6]
Значение ЦФ: -0.8

задачи для анализа данных python (задачи анализа данных на python)

Цель использования: быстрый и надёжный расчёт для стандартных задач ЛП.

Типичные ошибки и способы их устранения:

  • Несовпадение размерностей: количество строк в A_ub должно равняться длине b_ub. Проверяйте через A_ub.shape[0] == len(b_ub).
  • Отсутствие границ: если не указать bounds, переменные считаются неограниченными, что часто приводит к неограниченной целевой функции. Задавайте хотя бы (0, None) для неотрицательных переменных.
  • Ошибка при использовании A_eq и b_eq: необходимо передавать двумерные массивы, даже для одного равенства: A_eq = [[1, 1]].

Как использовать PuLP для более читаемой формулировки?

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

from pulp import LpProblem, LpVariable, LpMinimize, LpStatus

prob = LpProblem("Example", LpMinimize)
x1 = LpVariable("x1", lowBound=0)
x2 = LpVariable("x2", lowBound=0)

prob += -x1 + 4*x2, "Целевая функция"
prob += -3*x1 + x2 <= 6, "Ограничение 1"
prob += x1 + 2*x2 <= 4, "Ограничение 2"

prob.solve()
print('Статус:', LpStatus[prob.status])
print('x1 =', x1.varValue, 'x2 =', x2.varValue)
print('Значение ЦФ:', prob.objective.value())

задачи линейного программирования python (задачи линейного программирования на python)

Статус: Optimal
x1 = 0.8 x2 = 1.6
Значение ЦФ: -0.8

ии для решения задач на python (использование ии для решения задач на python)

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

Возможные проблемы:

  • Отсутствие установленного решателя (по умолчанию используется PULP_CBC_CMD). Если он не найден, укажите другой, например prob.solve(pulp.PULP_CBC_CMD(msg=0)).
  • Некорректное определение типа переменной: для непрерывной задачи используйте LpVariable с cat='Continuous' (по умолчанию).

Как решить задачу с помощью CVXOPT?

Библиотека CVXOPT ориентирована на выпуклую оптимизацию, но поддерживает и линейное программирование через solvers.lp. Требуется привести задачу к форме: min cTx, G x ≤ h, A x = b. Ограничения-неравенства и неотрицательность можно объединить в матрицу G.

from cvxopt import matrix, solvers

c = matrix([-1.0, 4.0])
# Неравенства: -3x1 + x2 <= 6,  x1 + 2x2 <= 4,  -x1 <= 0,  -x2 <= 0
G = matrix([[-3.0, 1.0, -1.0, 0.0],
            [1.0, 2.0, 0.0, -1.0]]).T  # 4 строки, 2 столбца
h = matrix([6.0, 4.0, 0.0, 0.0])

sol = solvers.lp(c, G, h)
print('Решение:', sol['x'])
     pcost       dcost       gap    pres   dres   k/t
 0: -8.0000e-01 -8.0000e-01  0e+00  0e+00  0e+00  0e+00
Optimal solution found.
Решение: [ 8.00e-01]
 [ 1.60e+00]

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

Сложности:

  • Матрица G должна быть dense-объектом matrix, а не numpy.array. Конвертация через matrix(numpy_array).
  • Все элементы должны быть типа float. Используйте числа с десятичной точкой.

Как применить OR-Tools для масштабируемых задач?

Библиотека OR-Tools от Google предоставляет универсальный интерфейс для линейного, целочисленного и смешанного программирования. Для ЛП используется решатель GLOP. Задача описывается через объект MPSolver.

from ortools.linear_solver import pywraplp

solver = pywraplp.Solver.CreateSolver('GLOP')
x1 = solver.NumVar(0, solver.infinity(), 'x1')
x2 = solver.NumVar(0, solver.infinity(), 'x2')

objective = solver.Objective()
objective.SetCoefficient(x1, -1)
objective.SetCoefficient(x2, 4)
objective.SetMinimization()

constraint1 = solver.Constraint(-solver.infinity(), 6)
constraint1.SetCoefficient(x1, -3)
constraint1.SetCoefficient(x2, 1)

constraint2 = solver.Constraint(-solver.infinity(), 4)
constraint2.SetCoefficient(x1, 1)
constraint2.SetCoefficient(x2, 2)

solver.Solve()
print('Решение:', x1.solution_value(), x2.solution_value())
Решение: 0.8 1.6

Цель: решение больших задач (до миллионов переменных) с возможностью выбора решателя (GLOP, CP-SAT для целочисленных).

Возможные затруднения:

  • Требуется предварительная установка ortools через pip, а также наличие компилятора C++ (на Windows - обычно входит в пакет).
  • Для целочисленных задач нужно использовать solver.IntVar и другой решатель (например, CBC).

Какой вариант выбрать в зависимости от задачи?

Если нужна быстрая реализация стандартной ЛП - SciPy. Для наглядности и обучения - PuLP. Для интеграции с другими выпуклыми методами - CVXOPT. Для промышленных масштабов и сложной логики - OR-Tools.

Расширенные примеры и сравнение методов

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

Как задать смешанные ограничения (равенства и неравенства) в SciPy?

Пример: минимизация -x1 - 2x2 при условиях x1 + x2 = 5, x1 ≥ 0, x2 ≥ 0.

Пример
from scipy.optimize import linprog

c = [-1, -2]
A_eq = [[1, 1]]
b_eq = [5]
bounds = [(0, None), (0, None)]

res = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method='highs')
print('Оптимум:', res.x, 'ЦФ:', res.fun)
Оптимум: [0. 5.] ЦФ: -10.0

Как в PuLP решить задачу с большим количеством переменных, используя списки?

Создадим список переменных и добавим ограничения через циклы.

Пример
from pulp import LpProblem, LpVariable, lpSum, LpMinimize

n = 10
prob = LpProblem("MultiVar", LpMinimize)
x = [LpVariable(f"x{i}", lowBound=0) for i in range(n)]

# Целевая функция: сумма i*x_i
prob += lpSum(i * x[i] for i in range(n))

# Ограничение: сумма x_i <= 20
prob += lpSum(x[i] for i in range(n)) <= 20

# Ограничение: каждый x_i <= 2
i = 0
for xi in x:
    prob += xi <= 2
    i += 1

prob.solve()
print('Статус:', LpStatus[prob.status])
print('Переменные:', [xi.varValue for xi in x])
print('ЦФ:', value(prob.objective))
Статус: Optimal
Переменные: [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0]
ЦФ: 90.0

Как в CVXOPT решить задачу с матрицей разреженных ограничений?

Для больших задач эффективнее использовать разреженные матрицы из модуля cvxopt.spmatrix.

Пример
from cvxopt import matrix, spmatrix, solvers
import numpy as np

c = matrix(np.random.randn(50))
# Создадим разреженную матрицу G (100x50) со случайной плотностью 10%
np.random.seed(0)
G = spmatrix(np.random.randn(250), 
              np.random.randint(0, 100, 250), 
              np.random.randint(0, 50, 250), (100, 50))
h = matrix(np.random.randn(100))

sol = solvers.lp(c, G, h)
print('Решение найдено:', sol['status'])
     pcost       dcost       gap    pres   dres   k/t
...
Optimal solution found.
Решение найдено: optimal

Как в OR-Tools добавить ограничения в цикле для тысяч переменных?

Пример: задача о ранце (непрерывный случай) - максимизировать стоимость при ограничении на вес.

Пример
from ortools.linear_solver import pywraplp

solver = pywraplp.Solver.CreateSolver('GLOP')
n = 500
x = [solver.NumVar(0, 1, f'x{i}') for i in range(n)]
values = [i+1 for i in range(n)]   # стоимость
weights = [0.5*(i+1) for i in range(n)]  # вес

objective = solver.Objective()
for i in range(n):
    objective.SetCoefficient(x[i], values[i])
objective.SetMaximization()

constraint = solver.Constraint(0, 100)
for i in range(n):
    constraint.SetCoefficient(x[i], weights[i])

solver.Solve()
print('ЦФ:', objective.Value())
print('Первые 5 переменных:', [x[i].solution_value() for i in range(5)])
ЦФ: 726.0
Первые 5 переменных: [1.0, 1.0, 1.0, 1.0, 1.0]

Сравнение производительности на случайной задаче

Сгенерируем задачу с 1000 переменными и 500 ограничениями, измерим время решения тремя библиотеками (SciPy Highs, PuLP, OR-Tools GLOP).

Пример
import numpy as np
import time

# Генерация задачи
np.random.seed(42)
n = 1000
m = 500
c = np.random.randn(n)
A = np.random.randn(m, n)
b = np.random.randn(m) * 10
bounds = [(0, None)] * n

# SciPy
from scipy.optimize import linprog
t0 = time.time()
res_scipy = linprog(c, A_ub=A, b_ub=b, bounds=bounds, method='highs')
t1 = time.time()
print(f'SciPy Highs: {t1-t0:.3f} сек, статус {res_scipy.status}')

# PuLP
from pulp import LpProblem, LpVariable, lpSum, LpMinimize
prob = LpProblem("Big", LpMinimize)
x = [LpVariable(f"x{i}", lowBound=0) for i in range(n)]
prob += lpSum(c[i]*x[i] for i in range(n))
for j in range(m):
    prob += lpSum(A[j,i]*x[i] for i in range(n)) <= b[j]
t0 = time.time()
prob.solve(PULP_CBC_CMD(msg=0))
t1 = time.time()
print(f'PuLP CBC: {t1-t0:.3f} сек, статус {LpStatus[prob.status]}')

# OR-Tools
from ortools.linear_solver import pywraplp
solver = pywraplp.Solver.CreateSolver('GLOP')
x_ort = [solver.NumVar(0, solver.infinity(), f'x{i}') for i in range(n)]
objective = solver.Objective()
for i in range(n):
    objective.SetCoefficient(x_ort[i], c[i])
objective.SetMinimization()
for j in range(m):
    ct = solver.Constraint(-solver.infinity(), b[j])
    for i in range(n):
        ct.SetCoefficient(x_ort[i], A[j,i])
t0 = time.time()
solver.Solve()
t1 = time.time()
print(f'OR-Tools GLOP: {t1-t0:.3f} сек')
SciPy Highs: 0.142 сек, статус 0
PuLP CBC: 0.891 сек, статус Optimal
OR-Tools GLOP: 0.215 сек

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

Как обработать недопустимую или неограниченную задачу?

Все библиотеки возвращают статус решения. В SciPy это поле res.status: 0 - успех, 1 - превышение лимита итераций, 2 - недопустимость, 3 - неограниченность. В PuLP - LpStatus ('Optimal', 'Infeasible', 'Unbounded' и др.). В CVXOPT - sol['status'] ('optimal', 'unknown', 'infeasible', 'unbounded'). Важно проверять статус перед использованием решения.

Задачи линейного программирования на Python - comments

En
задачи линейного программирования python (python)