601 線形計画法

\begin{align} \text{minimize } c^Tx & \\ \text{subject to } Ax &\le b \\ \text{where } & A \in R^{n \times m}_+, \\ & x \in R^m_{++}, \\ & b \in R^n_+, \\ & c \in R^m_+. \end{align}

アプリケーション:トンコケ丼

  • 決定変数:トンコケ丼の数\(x_1\)杯,コケトン丼の数\(x_2\)杯,ミックス丼の数\(x_3\)
  • 目的関数:利益最大化 トンコケ丼 1500円/杯,コケトン丼 1800円/杯,ミックス丼 3000円/杯
  • 制約式:材料 豚肉60 \(\times 10^2\)g/day,鶏肉60 \(\times 10^2\)g/day,牛肉20 \(\times 10^2\)g/day,
\begin{align} \text{maximize } 15x_1 + 18x_2 + 30x_3,& \\ \text{subject to } 2x_1 +x_2 + x_3 &\le 60, \\ x_1 +2x_2 + x_3 &\le 60, \\ x_3 &\le 30, \\ x_1,x_2,x_3 &\ge 0, \\ \text{where } A =\begin{bmatrix} 2 & 1 & 1 \\ 1 & 2 & 1 \\ 0 & 0 & 1 \end{bmatrix}, \\ x = \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix}, \\ b = \begin{bmatrix} 60 \\ 60 \\ 30 \end{bmatrix}, \\ c = \begin{bmatrix} 15 \\ 18 \\ 30 \end{bmatrix}. \end{align}

読み込み

In [1]:
import sympy as sy
import numpy as np
import multiprocessing as mp

import gurobipy as gp

import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
% matplotlib inline

import warnings
warnings.filterwarnings("ignore")

LP不等式標準形のテンプレートを用意

In [2]:
n = 3
m = 3
A = sy.Matrix(n, m, sy.symbols('A:{}:{}'.format(n, m)))
x = sy.Matrix(m, 1, sy.symbols('x:{}:1'.format(m)))
b = sy.Matrix(n, 1, sy.symbols('b:{}:1'.format(n)))
c = sy.Matrix(m, 1, sy.symbols('c:{}:1'.format(m)))

objective_function = c.T*x
constraints = A*x-b

display(A, x, b, c, objective_function, constraints)
Matrix([
[A00, A01, A02],
[A10, A11, A12],
[A20, A21, A22]])
Matrix([
[x00],
[x10],
[x20]])
Matrix([
[b00],
[b10],
[b20]])
Matrix([
[c00],
[c10],
[c20]])
Matrix([[c00*x00 + c10*x10 + c20*x20]])
Matrix([
[A00*x00 + A01*x10 + A02*x20 - b00],
[A10*x00 + A11*x10 + A12*x20 - b10],
[A20*x00 + A21*x10 + A22*x20 - b20]])

具体的なパラメータの挿入

In [3]:
def assign_parameter(arg):
    nn, mm, name = arg
    return ('{}{}{}'.format(name, nn, mm), param_dict[name][nn][mm])

def make_matrix_parallel(func, position_lists):
    pool = mp.Pool(processes=mp.cpu_count() - 1)
    res_list = pool.map(func, position_lists)
    pool.close()
    pool.join()
    return res_list

param_dict = {}
param_dict['A'] = [[2, 1, 1],
                   [1, 2, 1],
                   [0, 0, 1]]
param_dict['b'] = [[60],
                   [60],
                   [30]]
param_dict['c'] = [[15],
                   [18],
                   [30]]

position_lists = [(nn, mm, 'A') for nn in range(n) for mm in range(m)]+[(nn, 0, 'b') for nn in range(n)]
constr_params = make_matrix_parallel(assign_parameter, position_lists)
obj_params = make_matrix_parallel(assign_parameter, [(mm, 0, 'c') for mm in range(m)])

constraints_with_param = constraints.subs(constr_params)
objective_with_param = objective_function.subs(obj_params)

display(objective_with_param, constraints_with_param)
Matrix([[15*x00 + 18*x10 + 30*x20]])
Matrix([
[2*x00 + x10 + x20 - 60],
[x00 + 2*x10 + x20 - 60],
[              x20 - 30]])

LPモデル構築

In [4]:
model = gp.Model('Tonkoke')

for var_name in x:
    model.addVar(lb=0, ub=gp.GRB.INFINITY, name=str(var_name))
model.update()

for const in constraints_with_param:
    lhs = gp.LinExpr(0)
    #Only work for linear expression
    for key, value in const.as_coefficients_dict().items():
        if key not in x:
            lhs += value
        else:
            lhs.add(model.getVarByName(str(key)), value)
    model.addConstr(lhs, gp.GRB.LESS_EQUAL, 0, name='')

obj = gp.LinExpr(0)
for key, value in objective_with_param[0].as_coefficients_dict().items():
    if key not in x:
        obj += value
    else:
        obj.add(model.getVarByName(str(key)), value)
model.setObjective(obj, gp.GRB.MAXIMIZE)

model.update()

output_file = 'tonkoke.lp'
model.write(output_file)
print(open(output_file).read())
\ Model Tonkoke
\ LP format - for model browsing. Use MPS format to capture full model detail.
Maximize
  15 x00 + 18 x10 + 30 x20
Subject To
 R0: 2 x00 + x10 + x20 <= 60
 R1: x00 + 2 x10 + x20 <= 60
 R2: x20 <= 30
Bounds
End

最適化計算の実行

In [5]:
model.optimize()
Optimize a model with 3 rows, 3 columns and 7 nonzeros
Coefficient statistics:
  Matrix range     [1e+00, 2e+00]
  Objective range  [2e+01, 3e+01]
  Bounds range     [0e+00, 0e+00]
  RHS range        [3e+01, 6e+01]
Presolve removed 1 rows and 0 columns
Presolve time: 0.03s
Presolved: 2 rows, 3 columns, 6 nonzeros

Iteration    Objective       Primal Inf.    Dual Inf.      Time
       0    1.8000000e+03   3.750000e+00   0.000000e+00      0s
       2    1.2300000e+03   0.000000e+00   0.000000e+00      0s

Solved in 2 iterations and 0.04 seconds
Optimal objective  1.230000000e+03

最適化結果

In [6]:
for var in model.getVars():
    print(var.VarName, var.X)
x00 10.0
x10 10.0
x20 30.0

可視化

並列化して値を入手

In [7]:
func_const_list = []
for const in  constraints_with_param:
    func_const_list.append(sy.lambdify(x, const, modules='numpy'))

func_obj = sy.lambdify(x, objective_with_param[0], modules='numpy')

def calc_const(arg):
    x1, x2, x3 = arg
    res = [x1, x2, x3]
    for const in func_const_list:
        res.append(const(x1, x2, x3))
    res.append(None)
    return res

def calc_obj(arg):
    x1, x2, x3 = arg
    res = [x1, x2, x3]
    res.append(func_obj(x1, x2, x3))
    return res

def calc_parallel(func, x_lists, column_list):
    df = pd.DataFrame(columns=column_list)
    pool = mp.Pool(processes=mp.cpu_count() - 1)
    tmp_df = pd.DataFrame(pool.map(func, x_lists), columns=column_list)
    pool.close()
    pool.join()
    return df.append(tmp_df)

x_min  =  0
x_max  =  50
x_delta = 1
x_lists = [ (float(x1), float(x2), float(x3))
                 for x1 in list(range(x_min, x_max, x_delta))
                 for x2 in list(range(x_min, x_max, x_delta))
                 for x3 in list(range(x_min, x_max, x_delta))
          ]
const_df = calc_parallel(calc_const, x_lists, ['x1', 'x2', 'x3', 'eq1', 'eq2', 'eq3', 'obj'])
obj_df = calc_parallel(calc_obj, x_lists, ['x1', 'x2', 'x3', 'obj'])

Eq.1

In [8]:
eq1 = const_df[(2*const_df.x1+const_df.x2+const_df.x3 -60 <= 0)]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
p = ax.scatter(eq1['x1'], eq1['x2'], eq1['x3'], c=eq1['eq1'], cmap=plt.hot())
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$x_3$', rotation=90)
fig.colorbar(p, label='Eq.1')
plt.show()
../_images/materials_601_Linear_programming_17_0.png

Eq.2

In [9]:
eq2 = const_df[(const_df.x1+2*const_df.x2+const_df.x3 -60 <= 0)]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
p = ax.scatter(eq2['x1'], eq2['x2'], eq2['x3'], c=eq2['eq2'], cmap=plt.hot())
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$x_3$', rotation=90)
fig.colorbar(p, label='Eq.2')
plt.show()
../_images/materials_601_Linear_programming_19_0.png

Eq.3

In [10]:
eq3 = const_df[(const_df.x3 -30 <= 0)]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
p = ax.scatter(eq3['x1'], eq3['x2'], eq3['x3'], c=eq3['eq3'], cmap=plt.hot())
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$x_3$', rotation=90)
fig.colorbar(p, label='Eq.3')
plt.show()
../_images/materials_601_Linear_programming_21_0.png

Feasible region and value of objective funciton

In [11]:
obj_value = obj_df[(2*const_df.x1+const_df.x2+const_df.x3 -60 <= 0) &
                     (const_df.x1+2*const_df.x2+const_df.x3 -60 <= 0) &
                     (const_df.x3 -30 <= 0)]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
p = ax.scatter(obj_value['x1'], obj_value['x2'], obj_value['x3'], c=obj_value['obj'], cmap=plt.hot())
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$x_3$', rotation=90)
fig.colorbar(p, label='f(x)')
plt.show()
../_images/materials_601_Linear_programming_23_0.png
In [12]:
obj_value.sort_values(by='obj', ascending=False)
Out[12]:
x1 x2 x3 obj
25530 10.0 10.0 30.0 1230.0
20580 8.0 11.0 30.0 1218.0
23030 9.0 10.0 30.0 1215.0
25480 10.0 9.0 30.0 1212.0
27930 11.0 8.0 30.0 1209.0
15630 6.0 12.0 30.0 1206.0
18080 7.0 11.0 30.0 1203.0
23079 9.0 11.0 29.0 1203.0
25529 10.0 10.0 29.0 1200.0
20530 8.0 10.0 30.0 1200.0
27979 11.0 9.0 29.0 1197.0
22980 9.0 9.0 30.0 1197.0
10680 4.0 13.0 30.0 1194.0
25430 10.0 8.0 30.0 1194.0
27880 11.0 7.0 30.0 1191.0
13130 5.0 12.0 30.0 1191.0
18129 7.0 12.0 29.0 1191.0
30330 12.0 6.0 30.0 1188.0
25578 10.0 11.0 28.0 1188.0
20579 8.0 11.0 29.0 1188.0
15580 6.0 11.0 30.0 1188.0
18030 7.0 10.0 30.0 1185.0
28028 11.0 10.0 28.0 1185.0
23029 9.0 10.0 29.0 1185.0
5730 2.0 14.0 30.0 1182.0
20480 8.0 9.0 30.0 1182.0
25479 10.0 9.0 29.0 1182.0
22930 9.0 8.0 30.0 1179.0
13179 5.0 13.0 29.0 1179.0
8180 3.0 13.0 30.0 1179.0
... ... ... ... ...
2601 1.0 2.0 1.0 81.0
7600 3.0 2.0 0.0 81.0
52 0.0 1.0 2.0 78.0
5051 2.0 1.0 1.0 78.0
10050 4.0 1.0 0.0 78.0
12500 5.0 0.0 0.0 75.0
7501 3.0 0.0 1.0 75.0
2502 1.0 0.0 2.0 75.0
200 0.0 4.0 0.0 72.0
2650 1.0 3.0 0.0 69.0
101 0.0 2.0 1.0 66.0
5100 2.0 2.0 0.0 66.0
2551 1.0 1.0 1.0 63.0
7550 3.0 1.0 0.0 63.0
5001 2.0 0.0 1.0 60.0
2 0.0 0.0 2.0 60.0
10000 4.0 0.0 0.0 60.0
150 0.0 3.0 0.0 54.0
2600 1.0 2.0 0.0 51.0
5050 2.0 1.0 0.0 48.0
51 0.0 1.0 1.0 48.0
7500 3.0 0.0 0.0 45.0
2501 1.0 0.0 1.0 45.0
100 0.0 2.0 0.0 36.0
2550 1.0 1.0 0.0 33.0
1 0.0 0.0 1.0 30.0
5000 2.0 0.0 0.0 30.0
50 0.0 1.0 0.0 18.0
2500 1.0 0.0 0.0 15.0
0 0.0 0.0 0.0 0.0

11826 rows × 4 columns