Yuantongxue と申します。この記事は、Si Shoukui 氏によって書かれた本「Mathematical Modeling Algorithms and Programs」の内容に基づいており、本の中の例を使用しています。本の中のプログラミング言語は MATLAB と Lingo です。 Python を使用して問題を解決します。来月は、Python を使用して数学的モデリングの問題を解決する方法を学びます。研究中のメモといくつかのケースが記録されます。
1.線形計画法
1.1 線形計画法の例と定義
例 1ある工作機械工場で 2 種類の工作機械 A と B が生産され、各販売後の利益はそれぞれ 4,000 元と 3,000 元です。工作機械 A の生産は、機械 A と機械 B で処理する必要があり、処理時間は各機械で 2 時間と 1 時間です。1 日あたりの加工時間数が機械 A で 10 時間、機械 B で 8 時間、機械 C で 7 時間の場合、合計利益を最大化するには、工場で工作機械 A と B を何台生産する必要がありますか?
上記の問題の数学的モデル: 工場が機械 A と B を生産するときに最大の総利益を生み出すと仮定すると、次の条件を満たす必要があります。
ここでの変数は決定変数と呼ばれ、(1) は問題の目的関数と呼ばれ、(2) のいくつかの不等式は問題の制約であり、st (つまり、対象) として示されます。上記の目的関数と制約条件はともに線形関数であるため、線形計画問題と呼ばれます。要するに、線形計画問題は、一連の線形制約の制約の下で線形目的関数の最大値または最小値を見つける問題です。
1.2 Python で scipy ライブラリを使用して、列 1 の線形計画法の問題を解決します。
線形計画法の標準形式は次のとおりです。
(3)
このうち、c は値ベクトル、A と b は線形不等式制約に対応し、Aeq と beq は線形等式制約に対応し、bounds は式の lb と ub に対応し、決定ベクトルの下限と上限です。
scipy.optimize.
linprog
( c , A_ub=None , b_ub=None , A_eq=None , b_eq=None , bounds=None , method='simplex' , callback=None , options=None )
入力パラメーターの解析:
- c: 線形目的関数の係数
- A_ub (オプションのパラメーター): 不等式制約行列。各行は、x の線形不等式制約の係数を指定します。
- b_ub (オプションのパラメーター): 不等式制約のベクトル、各要素は x の上限を表します
- A_eq (オプションのパラメーター): 等式制約行列。各行は x xx の線形等式制約の係数を指定します
- b_eq (オプションのパラメーター): 各要素が対応する要素と等しくなければならない等式制約のベクトル
- 境界 (オプションのパラメーター): 決定変数 x の最小値と最大値を定義します
- データ型: (最小、最大) シーケンス ペア
- None: 境界がない場合は None を使用します。デフォルトでは、境界は (0, None) です (すべての決定変数は負ではありません)。
- タプル (最小、最大) が指定されている場合、最小値と最大値がすべての決定変数の境界として使用されます。
出力パラメーターの解析:
- x: 制約を満たしながら目的関数を最小化する決定変数の値
- fun: 目的関数の最適値 ( )
- slack: 不等式制約のスラック値 (名目上は正)
- con: 等式制約の残差 (通常はゼロ)
- success: アルゴリズムが最適解を見つけることに成功した場合は true
- status: アルゴリズムの終了ステータスを表す整数
- 0 : 最適化は正常に終了しました
- 1 : 反復制限に達しました
- 2 : 問題は実行可能ではないようです
- 3 : 問題は非収束のようです
- 4 : 数値上の問題が発生しました
- nit: すべての段階で実行された反復の合計数
- message: アルゴリズムの終了ステータスの文字列記述子
scipy ライブラリを使用して例 1 を解決します。
from scipy.optimize import linprog
c = [-4, -3] #目标函数的系数
A= [[2, 1], [1, 1],[0,1]] #<=不等式左侧未知数系数矩阵
b = [10,8,7] # <=不等式右侧常数矩阵
#A_eq = [[]] #等式左侧未知数系数矩阵
#B_eq = [] #等式右侧常数矩阵
x1 = [0, None] #未知数取值范围
x2 = [0, None] #未知数取值范围
res =linprog(c, A, b, bounds=(x1, x2))#默认求解最小值,求解最大值使用-c并取结果相反数
print(res)
出力結果:
con: array([], dtype=float64)
fun: -25.999999999841208
message: 'Optimization terminated successfully.'
nit: 5
slack: array([8.02629074e-11, 3.92663679e-11, 1.00000000e+00])
status: 0
success: True
x: array([2., 6.])
fun は目的関数の最適値、slack はスラック変数、status は最適化結果の状態、x は最適解です。
このモデルの解の結果は次のとおりです。x1=2、x2=6 の場合、関数は 25.999999999841208 の最適値を取得します。
例 2 次の線形計画問題を解きます。
最初に標準化するように注意してください。
別のより標準的な書き方を次に示します。
from scipy import optimize
import numpy as np
c = np.array([2,3,-5])
A = np.array([[-2,5,-1],[1,3,1]])
B = np.array([-10,12])
Aeq = np.array([[1,1,1]])
Beq = np.array([7])
x1 = [0, None] #未知数取值范围
x2 = [0, None] #未知数取值范围
x3 = [0, None] #未知数取值范围
res = optimize.linprog(-c,A,B,Aeq,Beq,bounds=(x1, x2, x3))
print(res)
出力結果:
con: array([1.80713489e-09])
fun: -14.571428565645054
message: 'Optimization terminated successfully.'
nit: 5
slack: array([-2.24579466e-10, 3.85714286e+00])
status: 0
success: True
x: array([6.42857143e+00, 5.71428571e-01, 2.35900788e-10])
このモデルの解の結果: x1=6.4、x2=5.7、x3=2.3 の場合、関数は最適値 14.571428565645054 を取得します。
例 3 線形計画法の問題を解く:
Python で書かれています:
from scipy import optimize
import numpy as np
c = np.array([2,3,1])
A = np.array([[1,4,2],[3,2,0]])
B = np.array([8,6])
x1 = [0, None] #未知数取值范围
x2 = [0, None] #未知数取值范围
x3 = [0, None] #未知数取值范围
res = optimize.linprog(c,-A,-B,bounds=(x1, x2, x3))
print(res)
出力結果:
con: array([], dtype=float64)
fun: 6.999999994872993
message: 'Optimization terminated successfully.'
nit: 3
slack: array([ 3.85261245e-09, -1.41066261e-08])
status: 0
success: True
x: array([1.17949641, 1.23075538, 0.94874104])
このモデルの解の結果: x1=1.1、x2=1.2、x3=0.9 の場合、関数は 6.999999994872993 の最適値を取得します。
2.輸送の問題
2.1 生産と販売のバランスの数学的モデリングプロセス:
例 6 商品には原産地と販売地があり、各生産地の産出量はであり、各販売地の需要は です 。 原産地から 販売 地までの製品の輸送単価が である場合 、総輸送コストを最も経済的にするには、輸送をどのように調整する必要がありますか?
解決策:原産地から販売地に出荷され た商品の数量を値 と する変数を導入します . 数学的モデルは次のとおりです.
もちろんシンプレックス法で解ける線形計画問題です。
生産と販売のバランスの輸送問題には、次の関係があります。
その制約の係数行列は非常に特殊であり、比較的単純な計算方法を使用できます。これは、通常、テーブルの宿題法と呼ばれます (Kantorovich と Hitchcock によって個別に提案され、Kang-Hitch テーブル作業法と呼ばれます) 、以下では、例を使用して説明します。
2.2 生産と販売のバランスの輸送問題
1. 問題の説明
ある企業に工場 A、B、C があり、それぞれ A、B、C、D に製品を供給する工場から販売までの生産高、需要、運賃 (単位: 元/トン)図のように、コストを最小化する最適な輸送計画を見つけます。
工場\販売 | あ | B | C | D | 生産量(トン) |
初め | 8 | 6 | 10 | 9 | 18 |
2番 | 9 | 12 | 13 | 1 | 18 |
ハ | 14 | 12 | 16 | 5 | 19 |
売上高(トン) | 16 | 15 | 7 | 17 | 55 |
2. 問題分析
総生産量=18+18+19=55
売上合計=16+15+7+17=55
産出は売上に等しい、つまり生産と売上のバランスの輸送問題です。テーブルの work メソッドを使用して直接解決します。
3. Python を使用して以下を解決します。
# 运输问题求解:使用Vogel逼近法寻找初始基本可行解
import numpy as np
import copy
import pandas as pd
def main():
# mat = pd.read_csv('表上作业法求解运输问题.csv', header=None).values
# mat = pd.read_excel('表上作业法求解运输问题.xlsx', header=None).values
a = np.array([18, 18, 19]) # 产量
b = np.array([16, 15, 7, 17]) # 销量
c = np.array([[8, 6, 10, 9], [9, 12, 13, 7], [14, 12, 16, 5]])
# [c, x] = TP_vogel(mat)
[c,x]=TP_vogel([c,a,b])
def TP_split_matrix(mat): # 运输分割矩阵
c = mat[:-1, :-1]
a = mat[:-1, -1]
b = mat[-1, :-1]
return (c, a, b)
def TP_vogel(var): # Vogel法代码,变量var可以是以numpy.ndarray保存的运输表,或以tuple或list保存的(成本矩阵,供给向量,需求向量)
import numpy
typevar1 = type(var) == numpy.ndarray
typevar2 = type(var) == tuple
typevar3 = type(var) == list
if typevar1 == False and typevar2 == False and typevar3 == False:
print('>>>非法变量<<<')
(cost, x) = (None, None)
else:
if typevar1 == True:
[c, a, b] = TP_split_matrix(var)
elif typevar2 == True or typevar3 == True:
[c, a, b] = var
cost = copy.deepcopy(c)
x = np.zeros(c.shape)
M = pow(10, 9)
for factor in c.reshape(1, -1)[0]:
p = 1
while int(factor) != M:
if np.all(c == M):
break
else:
print('第1次迭代!')
print('c:\n', c)
# 获取行/列最小值数组
row_mini1 = []
row_mini2 = []
for row in range(c.shape[0]):
Row = list(c[row, :])
row_min = min(Row)
row_mini1.append(row_min)
Row.remove(row_min)
row_2nd_min = min(Row)
row_mini2.append(row_2nd_min)
# print(row_mini1,'\n',row_mini2)
r_pun = [row_mini2[i] - row_mini1[i] for i in range(len(row_mini1))]
print('行罚数:', r_pun)
# 计算列罚数
col_mini1 = []
col_mini2 = []
for col in range(c.shape[1]):
Col = list(c[:, col])
col_min = min(Col)
col_mini1.append(col_min)
Col.remove(col_min)
col_2nd_min = min(Col)
col_mini2.append(col_2nd_min)
c_pun = [col_mini2[i] - col_mini1[i] for i in range(len(col_mini1))]
print('列罚数:', c_pun)
pun = copy.deepcopy(r_pun)
pun.extend(c_pun)
print('罚数向量:', pun)
max_pun = max(pun)
max_pun_index = pun.index(max(pun))
max_pun_num = max_pun_index + 1
print('最大罚数:', max_pun, '元素序号:', max_pun_num)
if max_pun_num <= len(r_pun):
row_num = max_pun_num
print('对第', row_num, '行进行操作:')
row_index = row_num - 1
catch_row = c[row_index, :]
print(catch_row)
min_cost_colindex = int(np.argwhere(catch_row == min(catch_row)))
print('最小成本所在列索引:', min_cost_colindex)
if a[row_index] <= b[min_cost_colindex]:
x[row_index, min_cost_colindex] = a[row_index]
c1 = copy.deepcopy(c)
c1[row_index, :] = [M] * c1.shape[1]
b[min_cost_colindex] -= a[row_index]
a[row_index] -= a[row_index]
else:
x[row_index, min_cost_colindex] = b[min_cost_colindex]
c1 = copy.deepcopy(c)
c1[:, min_cost_colindex] = [M] * c1.shape[0]
a[row_index] -= b[min_cost_colindex]
b[min_cost_colindex] -= b[min_cost_colindex]
else:
col_num = max_pun_num - len(r_pun)
col_index = col_num - 1
print('对第', col_num, '列进行操作:')
catch_col = c[:, col_index]
print(catch_col)
# 寻找最大罚数所在行/列的最小成本系数
min_cost_rowindex = int(np.argwhere(catch_col == min(catch_col)))
print('最小成本所在行索引:', min_cost_rowindex)
# 计算将该位置应填入x矩阵的数值(a,b中较小值)
if a[min_cost_rowindex] <= b[col_index]:
x[min_cost_rowindex, col_index] = a[min_cost_rowindex]
c1 = copy.deepcopy(c)
c1[min_cost_rowindex, :] = [M] * c1.shape[1]
b[col_index] -= a[min_cost_rowindex]
a[min_cost_rowindex] -= a[min_cost_rowindex]
else:
x[min_cost_rowindex, col_index] = b[col_index]
# 填入后删除已满足/耗尽资源系数的行/列,得到剩余的成本矩阵,并改写资源系数
c1 = copy.deepcopy(c)
c1[:, col_index] = [M] * c1.shape[0]
a[min_cost_rowindex] -= b[col_index]
b[col_index] -= b[col_index]
c = c1
print('本次迭代后的x矩阵:\n', x)
print('a:', a)
print('b:', b)
print('c:\n', c)
if np.all(c == M):
print('【迭代完成】')
print('-' * 60)
else:
print('【迭代未完成】')
print('-' * 60)
p += 1
print(f"第{p}次迭代!")
total_cost = np.sum(np.multiply(x, cost))
if np.all(a == 0):
if np.all(b == 0):
print('>>>供求平衡<<<')
else:
print('>>>供不应求,需求方有余量<<<')
elif np.all(b == 0):
print('>>>供大于求,供给方有余量<<<')
else:
print('>>>无法找到初始基可行解<<<')
print('>>>初始基本可行解x*:\n', x)
print('>>>当前总成本:', total_cost)
[m, n] = x.shape
varnum = np.array(np.nonzero(x)).shape[1]
if varnum != m + n - 1:
print('【注意:问题含有退化解】')
return (cost, x)
if __name__ == '__main__':
main()
出力結果:
D:\Anaconda\python.exe D:/PyCharm/数学建模/线性规划/www.py
第1次迭代!
c:
[[ 8 6 10 9]
[ 9 12 13 7]
[14 12 16 5]]
行罚数: [2, 2, 7]
列罚数: [1, 6, 3, 2]
罚数向量: [2, 2, 7, 1, 6, 3, 2]
最大罚数: 7 元素序号: 3
对第 3 行进行操作:
[14 12 16 5]
最小成本所在列索引: 3
本次迭代后的x矩阵:
[[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 17.]]
a: [18 18 2]
b: [16 15 7 0]
c:
[[ 8 6 10 1000000000]
[ 9 12 13 1000000000]
[ 14 12 16 1000000000]]
【迭代未完成】
------------------------------------------------------------
第2次迭代!
第1次迭代!
c:
[[ 8 6 10 1000000000]
[ 9 12 13 1000000000]
[ 14 12 16 1000000000]]
行罚数: [2, 3, 2]
列罚数: [1, 6, 3, 0]
罚数向量: [2, 3, 2, 1, 6, 3, 0]
最大罚数: 6 元素序号: 5
对第 2 列进行操作:
[ 6 12 12]
最小成本所在行索引: 0
本次迭代后的x矩阵:
[[ 0. 15. 0. 0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 17.]]
a: [ 3 18 2]
b: [16 0 7 0]
c:
[[ 8 1000000000 10 1000000000]
[ 9 1000000000 13 1000000000]
[ 14 1000000000 16 1000000000]]
【迭代未完成】
------------------------------------------------------------
第3次迭代!
第1次迭代!
c:
[[ 8 1000000000 10 1000000000]
[ 9 1000000000 13 1000000000]
[ 14 1000000000 16 1000000000]]
行罚数: [2, 4, 2]
列罚数: [1, 0, 3, 0]
罚数向量: [2, 4, 2, 1, 0, 3, 0]
最大罚数: 4 元素序号: 2
对第 2 行进行操作:
[ 9 1000000000 13 1000000000]
最小成本所在列索引: 0
本次迭代后的x矩阵:
[[ 0. 15. 0. 0.]
[16. 0. 0. 0.]
[ 0. 0. 0. 17.]]
a: [3 2 2]
b: [0 0 7 0]
c:
[[1000000000 1000000000 10 1000000000]
[1000000000 1000000000 13 1000000000]
[1000000000 1000000000 16 1000000000]]
【迭代未完成】
------------------------------------------------------------
第4次迭代!
第1次迭代!
c:
[[1000000000 1000000000 10 1000000000]
[1000000000 1000000000 13 1000000000]
[1000000000 1000000000 16 1000000000]]
行罚数: [999999990, 999999987, 999999984]
列罚数: [0, 0, 3, 0]
罚数向量: [999999990, 999999987, 999999984, 0, 0, 3, 0]
最大罚数: 999999990 元素序号: 1
对第 1 行进行操作:
[1000000000 1000000000 10 1000000000]
最小成本所在列索引: 2
本次迭代后的x矩阵:
[[ 0. 15. 3. 0.]
[16. 0. 0. 0.]
[ 0. 0. 0. 17.]]
a: [0 2 2]
b: [0 0 4 0]
c:
[[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 13 1000000000]
[1000000000 1000000000 16 1000000000]]
【迭代未完成】
------------------------------------------------------------
第5次迭代!
第1次迭代!
c:
[[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 13 1000000000]
[1000000000 1000000000 16 1000000000]]
行罚数: [0, 999999987, 999999984]
列罚数: [0, 0, 3, 0]
罚数向量: [0, 999999987, 999999984, 0, 0, 3, 0]
最大罚数: 999999987 元素序号: 2
对第 2 行进行操作:
[1000000000 1000000000 13 1000000000]
最小成本所在列索引: 2
本次迭代后的x矩阵:
[[ 0. 15. 3. 0.]
[16. 0. 2. 0.]
[ 0. 0. 0. 17.]]
a: [0 0 2]
b: [0 0 2 0]
c:
[[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 16 1000000000]]
【迭代未完成】
------------------------------------------------------------
第6次迭代!
第1次迭代!
c:
[[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 16 1000000000]]
行罚数: [0, 0, 999999984]
列罚数: [0, 0, 999999984, 0]
罚数向量: [0, 0, 999999984, 0, 0, 999999984, 0]
最大罚数: 999999984 元素序号: 3
对第 3 行进行操作:
[1000000000 1000000000 16 1000000000]
最小成本所在列索引: 2
本次迭代后的x矩阵:
[[ 0. 15. 3. 0.]
[16. 0. 2. 0.]
[ 0. 0. 2. 17.]]
a: [0 0 0]
b: [0 0 0 0]
c:
[[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 1000000000 1000000000]
[1000000000 1000000000 1000000000 1000000000]]
【迭代完成】
------------------------------------------------------------
>>>供求平衡<<<
>>>初始基本可行解x*:
[[ 0. 15. 3. 0.]
[16. 0. 2. 0.]
[ 0. 0. 2. 17.]]
>>>当前总成本: 407.0
进程已结束,退出代码为 0
上で使用した方法は、以下に示す表の作業方法です。
最終的な最適総コスト: 407.0
2.3 生産が販売を上回る輸送の問題
1. 問題の説明
4 都市に生乳を供給する 3 つの畜産拠点があり、4 都市の生乳の 1 日需要、3 拠点の生乳の 1 日供給量、1 キロリットルあたりの生乳の配送コストは下の表のとおりです。最も経済的な生乳輸送ソリューションを決定してみてください。
都市\ベース | あ | B | c | D | 供給 |
初め | 3 | 2 | 4 | 5 | 30 |
2番 | 2 | 3 | 5 | 3 | 40 |
ハ | 1 | 4 | 2 | 4 | 50 |
要求する | 16 | 30 | 24 | 30 |
2. 問題分析
総供給量=30+40+50=120
総需要=16+30+24+30=100
供給過剰、つまり、生産が販売を上回っています。これは、生産が販売を上回っている輸送の問題です。この問題をバランスの取れた生産と販売の輸送問題に変換するには、次の調整を行う必要があります。
(1) 総需要が120-100=20となるように、仮想売場Eを追加する。
(2) 販売場所が仮想化されているため、実際には生産過剰の材料は原産地でその場で保管されるため、実際の輸送は発生しません。つまり、運賃は発生しません。
よって、新たな需給・単価表は以下の通りとなります。
都市\ベース | あ | B | c | D | と | 供給 |
初め | 3 | 2 | 4 | 5 | 0 | 30 |
2番 | 2 | 3 | 5 | 3 | 0 | 40 |
ハ | 1 | 4 | 2 | 4 | 0 | 50 |
要求する | 16 | 30 | 24 | 30 | 20 | 120 |
3. 問題解決
Python を使用して達成すること:
これは、(最小要素法) を使用して解決することもできます。
'----------------------------------------最小元素法------------------------------------' \
'最小元素法是指有限满足单位运价最小的供销服务'
import numpy as np
import copy
supplyNum = 3 # 供应商数量
demandNum = 5 # 需求商数量
A = np.array([30,40,50]) # 产量
B = np.array([16,30,24,30,20]) # 销量
C = np.array([[3,2,4,5,0], [2,3,5,3,0], [1,4,2,4,0]]) # 成本
X = np.zeros((supplyNum, demandNum)) # 初始解
c = copy.deepcopy(C)
maxNumber = 10000 # 极大数
def pivot(A, B, c, X):
index = np.where(c == np.min(c)) # 寻找最小值的索引
minIndex = (index[0][0], index[1][0]) # 确定最小值的索引
# 确定应该优先分配的量,并且改变价格
if A[index[0][0]] > B[index[1][0]]:
X[minIndex] = B[index[1][0]]
c[:, index[1][0]] = maxNumber # 改变价格
A[index[0][0]] = A[index[0][0]] - B[index[1][0]]
B[index[1][0]] = 0 # 改变销量
else:
X[minIndex] = A[index[0][0]]
c[index[0][0], :] = maxNumber
B[index[1][0]] = B[index[1][0]] - A[index[0][0]]
A[index[0][0]] = 0
return A, B, c, X
# 最小元素法
def minimumElementMethod(A, B, c, X):
while (c < maxNumber).any():
A, B, c, X = pivot(A, B, c, X)
return X
solutionMin = minimumElementMethod(A, B, c, X)
print('最小元素法得到的初始解为:')
print(solutionMin)
print('最小元素法得到的总运费为:', ((solutionMin * C).sum()))
出力結果:
最小元素法得到的初始解为:
[[ 0. 10. 0. 0. 20.]
[ 0. 20. 0. 20. 0.]
[16. 0. 24. 10. 0.]]
最小元素法得到的总运费为: 244.0
2.4 生産量が販売量を下回る輸送問題
1. 問題の説明
ある 3 つの石炭火力発電所が 4 つの地域に供給し、同じ量の石炭がこれらの地域で同じ効果をもたらすと仮定して、各石炭火力発電所の年間生産量、各地域の需要、および各石炭火力発電所からの貨物単価各地域への対応は次のとおりです。 示して、最適な展開計画を決定してみてください。
販売場所\発送元 | B1 | B2 | B3 | B4 | 収率 |
A1 | 5 | 3 | 10 | 4 | 90 |
A2 | 2 | 6 | 9 | 6 | 40 |
A3 | 14 | 10 | 5 | 7 | 70 |
販売 | 30 | 50 | 100 | 40 |
2. 問題分析
総供給量=30+40+50=120
総需要=16+30+24+30=100
供給過剰、つまり、生産が販売を上回っています。これは、生産が販売を上回っている輸送の問題です。この問題をバランスの取れた生産と販売の輸送問題に変換するには、次の調整を行う必要があります。
(1) 総需要が120-100=20となるように、仮想売場Eを追加する。
(2) 販売場所が仮想化されているため、実際には生産過剰の材料は原産地でその場で保管されるため、実際の輸送は発生しません。つまり、運賃は発生しません。
よって、新たな需給・単価表は以下の通りとなります。
販売場所\発送元 | B | B | B3 | B4 | 収率 |
あ、 | 5 | 3 | 10 | 4 | 90 |
A2 | 2 | 6 | 9 | 6 | 40 |
A3 | 14 | 10 | 5 | 7 | 70 |
A4 | 0 | 0 | 0 | 0 | 20 |
販売 | 30 | 50 | 100 | 40 | 220 |
3. 問題解決
Python を使用して解決します。
ここで (フォーゲル近似法) を使用して、初期の基本的な実行可能解を見つけることができます。
'-----------------------------------Vogel法---------------------------------'
supplyNum = 4 # 供应商数量
demandNum = 4 # 需求商数量
A = np.array([90,40,70,20]) # 产量
B = np.array([30,50,100,40]) # 销量
C = np.array([[5,3,10,4], [2,6,9,6], [14,10,5,7],[0,0,0,0]]) # 成本
X = np.zeros((supplyNum, demandNum)) # 初始解
c = copy.deepcopy(C)
numPunish = [0] * (supplyNum + demandNum) # 整体罚数
def pivot(X, A, B, C):
# 求解罚数,其中列罚数排在行罚数后面
for i in range(supplyNum):
sortList = np.sort(C[i, :])
numPunish[i] = sortList[1] - sortList[0]
for i in range(demandNum):
sortList = np.sort(C[:, i])
numPunish[i + supplyNum] = sortList[1] - sortList[0]
maxIndex = np.argmax(numPunish) # 寻找最大罚数的索引
# 若最大的罚数属于行罚数
if maxIndex < supplyNum:
minIndex = np.argmin(C[maxIndex, :])
index = (maxIndex, minIndex) # 得到最大罚数的一行中最小的运价
# 若产量大于销量
if A[maxIndex] > B[minIndex]:
X[index] = B[minIndex] # 更新解
C[:, minIndex] = maxNumber # 将已经满足的一列中的运价增大替代删除操作
A[maxIndex] -= B[minIndex] # 更新剩余产量
B[minIndex] = 0 # 更新已经满足的销量
# 若销量大于产量
else:
X[index] = A[maxIndex]
C[maxIndex, :] = maxNumber
B[minIndex] -= A[maxIndex] # 更新销量
A[maxIndex] = 0
# 若最大的罚数为列罚数
else:
minIndex = np.argmin(C[:, maxIndex - supplyNum]) # 这时maxIndex-supplyNum是罚数最大的列
index = (minIndex, maxIndex - supplyNum)
if A[minIndex] > B[maxIndex - supplyNum]: # 这里是产量大于销量,因此有限满足销量
X[index] = B[maxIndex - supplyNum]
C[:, maxIndex - supplyNum] = maxNumber
A[minIndex] -= B[maxIndex - supplyNum]
B[maxIndex - supplyNum] = 0
else:
X[index] = A[minIndex]
C[minIndex, :] = maxNumber
B[maxIndex - supplyNum] -= A[minIndex]
A[minIndex] = 0
return X, A, B, C
# 沃格尔法得到初始解
def Vogel(A, B, C):
X = np.zeros((supplyNum, demandNum)) # 初始解
numPunish = [0] * (supplyNum + demandNum) # 整体罚数
# 迭代,直到所有的产量和销量全部满足
while (A != 0).any() and (B != 0).any():
X, A, B, C = pivot(X, A, B, C)
return X
# 上面对于条件的判断,还需要对销量和需求量进行改变
solutionVogel = Vogel(A, B, c)
print('Vogel法得到的初始解为:')
print(solutionVogel)
print('Vogel法得到的总运费为:', (C * solutionVogel).sum())
出力結果:
Vogel法得到的初始解为:
[[ 0. 50. 0. 40.]
[30. 0. 10. 0.]
[ 0. 0. 70. 0.]
[ 0. 0. 20. 0.]]
Vogel法得到的总运费为: 810.0
上記の3つの生産・販売問題は共通しており、生産・販売問題の他に、弾力的な需要の輸送問題や中間積み替えの輸送問題があります。
余計なことは言わないので、自分で情報をチェックしてください!
3. 割り当ての問題
3.1 割り当て問題の数学的モデル
例 7 ある仕事を人 に割り当てることが提案されて おり、各人は 1 つの仕事だけを行います.2 番目の 人が最初の 仕事をするために割り当てられた場合、時間単位がかかります.どのように仕事を割り当てるか.労働者が費やす合計時間を最小限に抑えるには?
割り当て問題のインスタンスを与えるには、割り当て問題の係数行列と呼ばれる行列 を与えるだけでよいことが簡単にわかります。仕事が割り当てられ ている場合は= 1 、それ以外の場合は = 0 を 取る変数を導入します。上記の代入問題の数学的モデルは次のとおりです。
上記の代入問題の実行可能な解は行列で表すことができ、各行と各列には 1 つの要素があり、1 つだけが 1 で、他の要素は 0 であり、 の順列で表すことができます。
この問題の変数は 0 または 1 しか取りません。これは 0-1 プログラミングの問題です。一般的な 0-1 プログラミングの問題は、解くのが非常に困難です。しかし、代入問題を解くのは難しくありません. 制約方程式系の係数行列は非常に特殊です (完全単位モジュラス行列と呼ばれ、そのすべての次数の非ゼロ部分式は ±1 です)。 -負の実行可能解は 0 または 1 しか取り得ないため、制約= 0 または 1 は、その解を変更せずに ≥ 0 として書き換えることができます。この時点で、割り当て問題は特別な輸送問題に変換されます。
3.2 代入問題を解くハンガリーのアルゴリズム
このアルゴリズムは主に、係数行列の行 (または列) の各要素に同じ数を加算または減算すると、係数行列として C または B を使用する割り当て問題が同じ最適な割り当てを持つという事実にます。新しいマトリックス。
例 8 割り当て問題を解くと、係数行列は次のようになります。
宿題の解決策:
この行の最小の要素である最初の行の要素から 15 を引き、同様に、2 行目の要素から 17 を引き、3 行目の要素から 17 を引き、最後の行の要素から 16 を引きます。我々が得る:
次に、3 列目の各要素から 1 を引いて、
係数行列の割り当て問題には最適な割り当てがあります
等価で、C の最適な割り当てでもあります。
python ソリューション:
from scipy.optimize import linear_sum_assignment
import numpy as np
cost = np.array([[16,15,19,22], [17,21,19,18], [24,22,18,17],[17,19,22,16]])
row_ind, col_ind = linear_sum_assignment(cost)
print('row_ind:', row_ind) # 开销矩阵对应的行索引
print('col_ind:', col_ind) # 对应行索引的最优指派的列索引
print('cost:', cost[row_ind, col_ind]) # 提取每个行索引的最优指派列索引所在的元素,形成数组
print('cost_sum:', cost[row_ind, col_ind].sum()) # 最小开销
出力結果:
row_ind: [0 1 2 3]
col_ind: [1 0 2 3]
cost: [15 17 18 16]
cost_sum: 66
例 9 係数行列 C の割り当て問題を解く
Python の実装:
from scipy.optimize import linear_sum_assignment
import numpy as np
cost = np.array([[12, 7, 9 ,7, 9],
[8, 9, 6, 6, 6],
[7, 17, 12, 14, 12],
[15,14, 6 ,6, 10],
[4,10 ,7 ,10 ,6]])
row_ind, col_ind = linear_sum_assignment(cost)
print('row_ind:', row_ind) # 开销矩阵对应的行索引
print('col_ind:', col_ind) # 对应行索引的最优指派的列索引
print('cost:', cost[row_ind, col_ind]) # 提取每个行索引的最优指派列索引所在的元素,形成数组
print('cost_sum:', cost[row_ind, col_ind].sum()) # 最小开销
出力結果:
row_ind: [0 1 2 3 4]
col_ind: [1 2 0 3 4]
cost: [7 6 7 6 6]
cost_sum: 32
4. 双対性理論と感度分析
4.1 原始問題と双対問題
次の線形計画法モデルのペアを検討してください。
(中)
と
(ニ)
(P) を主問題、(D) を双対問題と呼びます。
厳密には、双対問題は元の問題の「行と列の転置」と見なすことができます。
(1) 元の問題の j 列の係数は、双対問題の j 行の係数と同じです。
(2) 元の目的関数の各係数行は、その双対問題の右側の各定数列と同じです。
(3) 元の問題の右側の各定数列は、その双対目的関数の各係数行と同じです。
(4) このペアの問題では、不等式の方向と最適化の方向が逆です。
線形計画法を考えてみましょう:
等式制約を不等式制約に変えると、次のようになります。
その二重の問題は
ここで、 とは、それぞれ制約 sum に対応する 双対変数グループを表します。の場合、上記の式は次のように記述できます。
主問題と双対の双対制約との関係:
式:
不等号は同じ方向にある: 最大変数の不等号は最小制約の不等号と同じ方向にある
不等号の方向は反対です: 最大制約の不等号は最小変数の不等号と反対です. 等号は制約なしに対応します:
制約条件の「=」は変数「unconstrained」に対応
例 1:
次の線形計画法の双対問題を解いてみてください。
解決策: 3 つの制約に対応する双対変数をそれぞれ y1、y2、y3 とすると、元の問題の双対問題は次のようになります。
4.2 双対問題の基本的な性質
主問題と双対問題:
(1) 対称性
双対問題の双対が元の問題
(2) 弱い双対元の問題の実行可能解 (max)
が双対問題の実行可能解 (min) である場合、 を証明します。
最大の問題: 左の乗算
最小の問題: 右の乗算
(3) Unbounded
元の問題 (双対問題) に無限解がある場合、その双対問題 (元の問題) には実行可能解はありません。
(4) 最適性元問題の実行可能解
と双対問題の実行可能解とし、 = のとき最適解
(5) 双対性定理
元の問題に最適解がある場合、双対問題にも最適解があり、毎日のスカラー関数の値は等しい
(6) 相補的スラックネス
主問題と双対問題の実行可能解をそれぞれ、スラック変数の実行可能解とすると、と
例 10 既知の線形計画問題:
その双対問題の最適解は であることが知られています。元の問題の最適解を見つけるために双対性理論を試す
解決策は、その二重問題を最初に書くことです
スラック変数 を最適解に 追加する
補完緩和 解法
元の問題を再分析し
ます 。
解を代入して最適解 を 得る :
4.3 感度分析
これらの係数の 1 つ以上が変化すると、得られた線形計画問題の最適解はどうなるか、またはこれらの係数が変化すると、線形計画問題の最適解はどうなるかという 2 つの問題が提起されます。または最適な基底は変化しません。ここでは説明しません。
4.4 パラメトリック線形計画法
パラメトリック線形計画法は 、これらのパラメーターのいずれかが連続的に変化するときに最適解が変化する各臨界点の値を調べることです。すなわち、パラメータとしてパラメータを用い、目的関数はこのパラメータのある区間における一次関数であり、このパラメータを含む制約条件は一次方程式または不等式である。したがって、シンプレックス法と双対シンプレックス法を使用して、パラメトリック線形計画問題を解析することができます。
4.4.1 シンプレックス法:
例: シンプレックス法を使用して線形計画問題を解く
(1) 標準化
(2) Python プログラミングを使用します。
import numpy as np
class Simplex:
def __init__(self) -> None:
self.solutionStatus = 0
def set_param(self, A, b, c, baseInd):
self.A = A
self.m, self.n = self.A.shape
self.b = b
self.c = c
self.baseInd = baseInd
self.nonbaseInd = [i for i in range(self.n) if i not in self.baseInd]
self.B = self.A[:, self.baseInd]
self.N = self.A[:, self.nonbaseInd]
self.B_inv = self.B.I
def main(self):
# 计算非基变量的检验数,基变量检验数为0
reducedCost = [0] * self.n
X_B = None
simplexTable = SimplexTable()
while (self.solutionStatus == 0):
for i in range(self.n):
reducedCost[i] = float(self.c[:, i] - np.dot(self.c[:, self.baseInd], self.A[:, i]))
# p_j已经全部左乘B_inv,因此这里c_B可以直接与更新后的p_j相乘
self.solutionStatus = self.check_solutionStatus(reducedCost)
simplexTable.show(self.A, self.b, self.c, reducedCost, self.baseInd, self.solutionStatus)
if self.solutionStatus == 0:
inVarInd = reducedCost.index(max(reducedCost))
outVarInd = self.get_outVar(inVarInd)
self.baseInd[self.baseInd.index(outVarInd)] = inVarInd
self.nonbaseInd[self.nonbaseInd.index(inVarInd)] = outVarInd
# 得到新基
self.B = self.A[:, self.baseInd]
self.B_inv = self.B.I
self.b = np.dot(self.B_inv, self.b)
X_B = self.b.T.tolist()[0]
self.A[:, inVarInd] = self.A[:, outVarInd]
self.A[:, self.nonbaseInd] = np.dot(self.B_inv, self.A[:, self.nonbaseInd])
else:
break
if self.solutionStatus == 1:
solution = {}
for i in range(self.n):
if i in self.baseInd:
solution[i] = X_B[self.baseInd.index(i)]
if i in self.nonbaseInd:
solution[i] = 0
objctive = float(np.dot(np.dot(self.c[:, self.baseInd], self.B_inv), self.b))
return solution, objctive, self.solutionStatus
else:
solution = None
objctive = None
return solution, objctive, self.solutionStatus
def check_solutionStatus(self, reducedCost):
if all(x < 0 or x == 0 for x in reducedCost):
# 所有检验数<=0
if any(reducedCost[i] == 0 for i in self.nonbaseInd):
# 存在非基变量的检验数为0
return 2 # 无穷多最优解
else:
return 1 # 唯一最优解
else:
if all(all(x < 0 for x in self.A[:, i]) for i in self.nonbaseInd if reducedCost[i] > 0):
return 3 # 无界解
else:
# 选择最大检验数对应的非基变量入基
return 0
def get_outVar(self, inVarInd):
inVarCoef = self.A[:, inVarInd]
ratio = [np.inf] * self.m
for i in range(len(inVarCoef)):
if float(inVarCoef[i, :]) > 0:
ratio[i] = float(self.b[i, :]) / float(inVarCoef[i, :])
outVarInd = self.baseInd[ratio.index(min(ratio))]
return outVarInd
class SimplexTable:
def __init__(self):
# 左端表头格式设置
self.setting_left = '{:^8}'+'{:^8}'+'{:^8}|'
# 右端变量格式设置
self.setting_var = '{:^8}|'
# 求解状态格式设置
self.setting_status = '{:^24}|'+'{:^44}|'
def show(self,A,b,c,reducedCost,baseInd,solutionStatus):
var_num = A.shape[1]
# 打印系数行
c_j_list = c.flatten().tolist()[0]
print('='*80)
print(self.setting_left.format('','c_j',''),end = '')
for i in c_j_list:
print(self.setting_var.format(i),end = '')
print()
# 打印变量行
print(self.setting_left.format('c_B','x_B','b'),end = '')
for i in range(var_num):
print(self.setting_var.format('x'+str(i)),end = '')
print()
# 打印变量与矩阵
for i in range(len(baseInd)):
varInd = baseInd[i]
varCeof = round(float(c[:,varInd]),2)
varValue = round(float(b[i,:]),2)
row = A[i,:].flatten().tolist()[0]
print(self.setting_left.format(str(varCeof),'x'+str(varInd),str(varValue)),end='')
for i in range(var_num):
print(self.setting_var.format(round(row[i],2)),end = '')
print()
# 打印检验数
print(self.setting_left.format('','c_j-z_j',''),end='')
for i in reducedCost:
print(self.setting_var.format(round(i,2)),end = '')
print()
# 显示求解状态
if solutionStatus == 0:
print(self.setting_status.format('status','...Iteration continues...'))
if solutionStatus == 1:
print(self.setting_status.format('status','Unique Optimal Solution'))
B = A[:,baseInd]
B_inv = B.I
objctive = float(np.dot(np.dot(c[:, baseInd], B_inv), b))
print(self.setting_status.format('objctive', round(objctive,2)))
solution = [0]*var_num
X_B = b.T.tolist()[0]
for i in range(var_num):
if i in baseInd:
solution[i] = X_B[baseInd.index(i)]
print(self.setting_left.format('', 'solution', ''), end='')
for i in solution:
print(self.setting_var.format(round(i, 2)), end='')
print()
if solutionStatus == 2:
print(self.setting_status.format('status','Multiple Optimal Solutions'))
B = A[:, baseInd]
B_inv = B.I
objctive = float(np.dot(np.dot(c[:, baseInd], B_inv), b))
print(self.setting_status.format('objctive', round(objctive, 2)))
if solutionStatus == 3:
print(self.setting_status.format('status','Unboundedness'))
'''=============================================================================='''
A = np.matrix([[0,5,1,0,0],
[6,2,0,1,0],
[1,1,0,0,1]], dtype=np.float64)
# 必须设置精度,否则在替换矩阵列时会被自动圆整
b = np.matrix([[15], [24], [5]], dtype=np.float64)
c = np.matrix([2,1,0,0,0], dtype=np.float64)
baseInd = [2,3,4]
s=Simplex()
s.set_param( A, b, c, baseInd)
s.main()
次のように、シンプレックス法の基本条件を満たすこの形式のリストを解きます。
================================================================================
c_j | 2.0 | 1.0 | 0.0 | 0.0 | 0.0 |
c_B x_B b | x0 | x1 | x2 | x3 | x4 |
0.0 x2 15.0 | 0.0 | 5.0 | 1.0 | 0.0 | 0.0 |
0.0 x3 24.0 | 6.0 | 2.0 | 0.0 | 1.0 | 0.0 |
0.0 x4 5.0 | 1.0 | 1.0 | 0.0 | 0.0 | 1.0 |
c_j-z_j | 2.0 | 1.0 | 0.0 | 0.0 | 0.0 |
status | ...Iteration continues... |
================================================================================
c_j | 2.0 | 1.0 | 0.0 | 0.0 | 0.0 |
c_B x_B b | x0 | x1 | x2 | x3 | x4 |
0.0 x2 15.0 | 0.0 | 5.0 | 1.0 | 0.0 | 0.0 |
2.0 x0 4.0 | 1.0 | 0.33 | 0.0 | 0.17 | 0.0 |
0.0 x4 1.0 | 0.0 | 0.67 | 0.0 | -0.17 | 1.0 |
c_j-z_j | 0.0 | 0.33 | 0.0 | -0.33 | 0.0 |
status | ...Iteration continues... |
================================================================================
c_j | 2.0 | 1.0 | 0.0 | 0.0 | 0.0 |
c_B x_B b | x0 | x1 | x2 | x3 | x4 |
0.0 x2 7.5 | 0.0 | 0.0 | 1.0 | 1.25 | -7.5 |
2.0 x0 3.5 | 1.0 | 0.0 | 0.0 | 0.25 | -0.5 |
1.0 x1 1.5 | 0.0 | 1.0 | 0.0 | -0.25 | 1.5 |
c_j-z_j | 0.0 | 0.0 | 0.0 | -0.25 | -0.5 |
status | Unique Optimal Solution |
objctive | 8.5 |
solution | 3.5 | 1.5 | 7.5 | 0 | 0 |
最適解:
最適値:
シンプレックス法の詳細な説明:
1.人工変数法(ビッグM法)
いくつかの線形計画問題が正準形式に変換された後、制約係数行列には恒等行列が存在しないため、新しい線形計画問題を構築するために制約に人工変数を追加する必要があります。目的関数の人為変数の係数を任意に大きな負の数にすることで、解法プロセス中に人為変数が最適解に現れるのを防ぐことができます。最終的なシンプレックス テーブルのすべてのテスト数がゼロ以下であるが、ベース変数にゼロではない人為変数がまだある場合、問題は未解決です。次に、人為変数法の計算手順を示します。
2. 二段法
人工変数を扱うにはビッグ M 法を使用します. 電子計算機で解く場合、M は機械の最大語長の数しか入力できません. 線形計画問題のパラメータ値が M を表す数に近い場合、またはこの数よりもはるかに小さい場合、計算中のコンピューターの値の誤差により、計算結果が間違っている可能性があります。
この困難を克服するために、人為変数を追加した後の線形計画問題を 2 段階で計算することができます。これを 2 段階法と呼びます。
詳細:オペレーションズ リサーチ第 16 号 | 線形計画法のハードコア知識ポイントの組み合わせ - シンプレックス法 - Zhihu
4.4.2 デュアル シンプレックス方式:
例: 双対シンプレックス法を使用して LP を解きます。
(1) 標準化:
2 つの等式制約の両辺に -1 を掛けると、次のようになります。
(2) Python プログラミングを使用します。
import re
import pandas as pd
from fractions import Fraction
# data = pd.read_csv("data2.csv", header=0, index_col=0)
# data=pd.DataFrame({"x1":[-2,-2,-1,-9],"x2":[-2,-3,-1,-12],"x3":[-1,-1,-5,-15],"x4":[1,0,0,0],"x5":[0,1,0,0],"x6":[0,0,1,0],"b":[-10,-12,-14,0]},index = ['x4', 'x5', 'x6',"sigma"])
data=pd.DataFrame({"x1":[-1,-2,-2],"x2":[-2,1,-3],"x3":[-1,-3,-4],"x4":[1,0,0],"x5":[0,1,0],"b":[-3,-4,0]},index = ['x4', 'x5' ,"sigma"])
print("=======原始表=======")
print(data)
def do_it(data):
in_base_index = data.loc["sigma", :][data.loc['sigma', :] < 0].index
rec = {}
res_val = []
for i in in_base_index:
out_base_index = data.loc[:, i][data.loc[:, i] < 0].index.drop("sigma")
for j in out_base_index:
res = Fraction(data.loc["sigma", i], data.loc[j, i])
res_val.append(res)
rec[str(j) + "," + str(i)] = res
def get_key(dic, value):
return [k for k, v in dic.items() if v == value]
s = get_key(rec, min(res_val))
s = re.split(r"\,", s[0])
# 这里是将本身变成1
param1 = Fraction(1, data.loc[s[0], s[1]])
data.loc[s[0], :] = data.loc[s[0], :] * param1
# 将其他变为0
for row in data.index.drop(s[0]):
target = data.loc[row, s[1]]
param2 = Fraction(-target, 1)
data.loc[row, :] = data.loc[s[0], :] * param2 + data.loc[row, :]
data = data.rename(index={s[0]: s[1]})
print("================================")
print(data)
if (data["b"].drop("sigma") < 0).any():
print("需要继续迭代!")
do_it(data)
else:
print("迭代结束!")
return data
# 如何b中的任何一个数小于零,都需要进一步操作
if (data["b"].drop("sigma") < 0).any():
print("需要继续迭代!")
do_it(data)
else:
print("迭代结束!")
次のように、双対シンプレックス法の基本条件を満たすこの形式のリストを解きます。
=======原始表=======
x1 x2 x3 x4 x5 b
x4 -1 -2 -1 1 0 -3
x5 -2 1 -3 0 1 -4
sigma -2 -3 -4 0 0 0
需要继续迭代!
================================
x1 x2 x3 x4 x5 b
x4 0 -5/2 1/2 1 -1/2 -1
x1 1 -1/2 3/2 0 -1/2 2
sigma 0 -4 -1 0 -1 4
需要继续迭代!
================================
x1 x2 x3 x4 x5 b
x2 0 1 -1/5 -2/5 1/5 2/5
x1 1 0 7/5 -1/5 -2/5 11/5
sigma 0 0 -9/5 -8/5 -1/5 28/5
迭代结束!
最適解:
最適値: