线性规划问题是运筹学当中重要的一个话题,参考教材《简明运筹学》,由姚奕荣,韩伯顺编写。重点基于python实现的单纯形法以求解线性规划问题。
线性规划问题标准形式
原始形式
当使用线性规划方法求解问题时,通常包含两个组成部分:
- 待优化的函数
- 约束条件
希望达到的目的是:在满足约束条件的情况下,最大化或者最小化目标函数;
标准形式
m i n z = f ( x ) s . t . A x = b x > = 0 min\ z=f(x)\\ s.t. Ax=b\\ x>=0 min z=f(x)s.t.Ax=bx>=0
将一般的线性规划问题化为标准形式
以如下问题作为示例
m
a
x
z
=
x
1
+
2
x
2
+
3
x
3
s
.
t
.
−
2
x
1
+
x
2
+
x
3
⩽
9
−
3
x
1
+
x
2
+
2
x
3
⩾
4
4
x
1
−
2
x
2
−
3
x
3
=
−
6
x
1
⩽
0
,
x
2
⩾
0
max\ z=x_1+2x_2+3x_3\\ \begin{aligned} s.t.-2x_1+x_2+x_3&\leqslant9\\ -3x_1+x_2+2x_3&\geqslant4\\ 4x_1-2x_2-3x_3&=-6\\ x_1\leqslant0,x_2\geqslant0 \end{aligned}
max z=x1+2x2+3x3s.t.−2x1+x2+x3−3x1+x2+2x34x1−2x2−3x3x1⩽0,x2⩾0⩽9⩾4=−6
- 针对优化函数 z 和变量 x
求 z 的最大值,转化为求 -z 的最小值;当x大于等于0的时候,转化为-x<=0
z
,
=
−
z
x
1
,
=
−
x
1
z^,=-z\\ {x_1}^,=-x_1
z,=−zx1,=−x1
- 对于约束条件中存在的小于或者大于情况,通过加入自由变量将约束条件转化为等于
对 于 − 2 x 1 + x 2 + x 3 ⩽ 9 引 入 自 由 变 量 x 4 , 将 其 转 化 为 − 2 x 1 + x 2 + x 3 + x 4 = 9 其 中 x 4 ⩾ 0 对 于 − 3 x 1 + x 2 + 2 x 3 ⩾ 4 引 入 自 由 变 量 x 4 , 将 其 转 化 为 − 3 x 1 + x 2 + 2 x 3 − x 5 = 4 , 其 中 x 5 ⩾ 0 对于-2x_1+x_2+x_3\leqslant9 引入自由变量x_4,将其转化为-2x_1+x_2+x_3+x_4=9 其中x_4\geqslant0\\ 对于-3x_1+x_2+2x_3\geqslant4 引入自由变量x_4, 将其转化为-3x_1+x_2+2x_3-x_5=4,其中x_5\geqslant0 对于−2x1+x2+x3⩽9引入自由变量x4,将其转化为−2x1+x2+x3+x4=9其中x4⩾0对于−3x1+x2+2x3⩾4引入自由变量x4,将其转化为−3x1+x2+2x3−x5=4,其中x5⩾0
- 对于没有给出约束范围的变量,例如本例中的x3,没有在约束条件中给出明确的范围,也需要进行如下转化
令 x 3 = x 3 , − x 3 , , 其 中 x 3 , ⩾ 0 , x 3 , , ⩾ 0 令x_3=x_3^,-x_3^{,,}其中x_3^,\geqslant0,x_3^{,,}\geqslant0 令x3=x3,−x3,,其中x3,⩾0,x3,,⩾0
- 将上面新引入的变量
z , , x 1 , , x 2 , x 3 , , x 3 , , , x 4 , x 5 z^,,x_1^,,x_2,x_3^{,},x_3^{,,},x_4,x_5 z,,x1,,x2,x3,,x3,,,x4,x5
代入到原来的线性规划问题,即可得到标准形式如下
m
i
n
z
=
x
1
,
−
2
x
2
−
3
x
3
,
+
3
x
3
,
,
s
.
t
.
2
x
1
,
+
x
2
+
x
3
,
−
x
3
,
,
+
x
4
=
9
3
x
1
,
+
x
2
+
2
x
3
,
−
2
x
3
,
,
−
x
5
=
4
4
x
1
,
+
2
x
2
−
3
x
3
,
−
3
x
3
,
,
=
6
z
,
⩾
0
,
x
1
,
⩾
0
,
x
2
⩾
0
,
x
3
,
⩾
0
,
x
3
,
,
⩾
0
,
x
4
⩾
0
,
x
5
⩾
0
\begin{aligned} min\ z&=x_1^,-2x_2-3x_3^,+3x_3^{,,}\\ s.t.\ &2x_1^,+x_2+x_3^,-x_3^{,,}+x_4=9\\ &3x_1^,+x_2+2x_3^,-2x_3^{,,}-x_5=4\\ &4x_1^,+2x_2-3x_3^,-3x_3^{,,}=6\\ &z^,\geqslant0,x_1^,\geqslant0,x_2\geqslant0,x_3^{,}\geqslant0,x_3^{,,}\geqslant0,x_4\geqslant0,x_5\geqslant0 \end{aligned}
min zs.t. =x1,−2x2−3x3,+3x3,,2x1,+x2+x3,−x3,,+x4=93x1,+x2+2x3,−2x3,,−x5=44x1,+2x2−3x3,−3x3,,=6z,⩾0,x1,⩾0,x2⩾0,x3,⩾0,x3,,⩾0,x4⩾0,x5⩾0
后面使用单纯形表法的时候,需要首先将一般的线性规划问题转化为标准形式
单纯形法
算法实现思路
创建单纯形表:(m+2)*(m+2)
初始化单纯形表:在对应的位置上填入A,b;cj行,检验数(check)行常数列(constrant)以及cb列
计算检验数:cb列和xi列的元素对应相乘之后减去对应cj行的元素
判断检验数是否全大于等于0,是结束,返回最后一列否则进行如下操作
找到检验数最大的哪一列:j
计算常数列上的元素和j列上对应元素的比值,从里面找到最小的元素其位置记为(min_i,min_j)
将该元素化为1
非min_i行上的元素经过初等变换全部变为0
改变基变量类型:将min_j列对应的变量写入到min_i行当中
到此一轮处理完成
代码
lenear_programing.py文件
import numpy as np
import pandas as pd
# 梳理思路
"""
1. 接收的输入:
化为标准形式的系数矩阵(Ax=b)中的A,b
基可行解,其中A的维度为m*n,则及可行解theta为n*1
待优化函数f(x),改写成矩阵形式:1 x1 x2 x3 ....
2. 输出:
线性规划的最优解及其变量取值
3. 实现思路
创建单纯形表:(m+2)*(m+2)
初始化单纯形表:在对应的位置上填入A,b;cj行,检验数(check)行常数列(constrant)以及cb列
计算检验数:cb列和xi列的元素对应相乘之后减去对应cj行的元素
判断检验数是否全大于等于0,是结束,返回最后一列否则进行如下操作
找到检验数最大的哪一列:j
计算常数列上的元素和j列上对应元素的比值,从里面找到最小的元素其位置记为(min_i,min_j)
将该元素化为1
非min_i行上的元素经过初等变换全部变为0
改变基变量类型:将min_j列对应的变量写入到min_i行当中
到此一轮处理完成
"""
class LenarPrograming():
"""
A = [
[1, -2, 1, 1, 0],
[-4, 1, 2, 0, -1],
[-2, 0, 1, 0, 0]
]
b=[11,3,1]
f=[0,-3,1,1]
"""
def __init__(self, A, b, func):
"""
构造方法,根据初始输入的经过标准化的系数系数矩阵,常数项,以及优化函数
:param A: 系数矩阵
:param b: 常数项
:param func: 待优化函数
"""
self.f = np.array(func).reshape(len(func), )
self.A = np.array(A)
self.b = np.array(b).reshape(len(b), )
def basic_feasible_variable(self):
"""
求解基变量,基变量的求解是使用单纯形表的第一步
:return:基变量
说明:对于一个5元方程假设基变量为x3=3,x4=4,x5=5,则最终返回结果为
x1 x2 x3 x4 x5
[0, 0, 3, 4, 5]
"""
A, f, b = self.A, self.f, self.b
m, n = A.shape
# 求解基变量
# 下面这段代码的作用是:给定一个系数矩阵,判定当前矩阵是否需要重新构建基变量
# 对于一个含有n元的方程,考虑每一个变量xi,如果xi在某个方程上面的系数为1在其他方程上面的系数都是0时
# 即某个变量只在某一个方程存在,且其系数为1
# 实现思路:
fset = set()# 1.构建集合,储存当前遍历到的基变量所属的方程
theta = [] # 储存最终生成的基变量
for i in range(n): # 2.依次遍历每个变量,寻找满足基变量条件的变量
temp = A[:, i] # 3.获取该变量在所有方程上面的系数temp
if np.count_nonzero(temp) == 1 and max(temp) == 1.0:# 4.temp上面最大值为1,且其他元素都是0
# 找到基变量
location = np.argmax(temp) # 系数为1的元素的位置
fset.add(location) # 5.将基变量所属的方程添加到fset集合
theta.append(b[location]) # 6. 添加基变量
else:
theta.append(0) #当前变量不是基变量,在theta中加0
if len(fset) == m:# fset的大小等于方程的个数m,说明每个方程都有一个基变量,此时可以直接返回
return A, f, np.array(theta)
else:
# 需要重新构建基变量
theta = []
total = set([i for i in range(m)])
total = total.difference(fset)
# 新加变量
temp = np.zeros((m, len(total)))
length = len(total)
f = [0 for i in range(n + 1)] + [1 for i in range(length)]
for i in range(length):
j = total.pop()
temp[j, i] = 1
# 两者连接
A_temp = np.concatenate([A, temp], axis=1)
m, n = A_temp.shape
for i in range(n):
temp = A_temp[:, i]
if np.count_nonzero(temp) == 1 and max(temp) == 1.0:
# 找到基变量
location = np.argmax(temp) # 系数为1的元素的位置
theta.append(b[location])
else:
theta.append(0)
return A_temp, np.array(f), np.array(theta)
def init_table(self, A, theta, f):
"""
初始化单纯形表
:param A: 系数矩阵
:param theta: 基变量矩阵
:param f: 待优化函数
:return: 单纯形表 pd.DataFrame类型
"""
m, n = A.shape
table = pd.DataFrame(np.zeros((m + 2, n + 2)), dtype=float)
table.columns = ['cb'] + ['x' + str(i)
for i in range(1, n + 1)] + ['constrant']
indexs = np.arange(1, n + 1, 1)[theta > 0]
table.index = ['cj'] + ['x' + str(i) for i in indexs] + ['check']
# 填充元素
table.iloc[0, 1:len(f)] = f[1:]
table.iloc[1:m + 1, 1:n + 1] = A
table.iloc[1:m + 1, -1] = theta[indexs - 1]
return table
def __change_table(self, table):
"""
根据初始化后的单纯形表进行后续的变换
:param table:
:param A:
:return:
"""
m, n = table.shape
m, n = m - 2, n - 2 # 对应系数矩阵的行和列的数量
for ele in table.index:
if ele in table.columns and table[ele][0] == 1:
table['cb'][ele] = 1
while True:
# 计算检验数
for i in range(1, n + 1):
table.loc['check'][i] = sum(
table['cb'][1:m + 1] * table['x' + str(i)][1:m + 1]) - table.loc['cj'][i]
table.loc['check'][n +
1] = sum(table['cb'][1:m + 1] * table['constrant'][1:m + 1])
# 找到检验数最大值所在的列
max_column = np.argmax(table.loc['check'][1:-1]) + 1
target = table['x' + str(max_column)][1:m + 1]
temp = []
for i in range(len(target)):
if target[i] < 0:
temp.append(np.inf)
else:
temp.append(table['constrant'][i + 1] /
table['x' + str(max_column)][i + 1])
min_i = np.argmin(temp) + 1
min_j = max_column
if table.iloc[min_i, min_j] != 1.0:
table.iloc[min_i][:] /= table.iloc[min_i, min_j]
for x in range(1, m + 2):
if x != min_i:
rate = table['x' + str(min_j)][x] / \
table.iloc[min_i, min_j]
table.iloc[x, 1:] -= rate * table.iloc[min_i, 1:]
# 更换标签
list = []
for i in range(len(table.index)):
if i == min_i:
list.append('x' + str(min_j))
table['cb'][i] = table.loc['cj'][min_j]
else:
list.append(table.index[i])
table.index = list
if all(table.loc['check'][:-1] <= 0):
return table
def simple_method1(self, A, theta, f):
"""
根据初始输入的系数矩阵,基变量,待优化函数进行单纯形变换
:param A: 系数矩阵
:param theta: 基变量矩阵
:param f: 待优化函数
:return: 单纯形表
"""
# 初始化单纯形表
table = self.init_table(A, theta, f)
return self.__change_table(table)
def simple_method2(self, table, f):
"""
根据初始输入的单纯形表,优化函数,需要对单纯形表首先进行优化函数替换,即把原来的优化函数替换为f
:param table: 单纯性表
:param f: 新的优化函数
:return:
"""
# 优化函数替换
table.iloc[0, 1:len(f)] = f[1:]
return self.__change_table(table)
def solve(self):
"""
求解函数
:return:
"""
A, f, theta = self.basic_feasible_variable()
table = self.simple_method1(A, theta,f)
# 经过第一阶段处理,如果最优值不是0,则此规划问题无解
om,on=self.A.shape
if on!=len(theta): # 基变量需要重新构建
if table.loc['check']['constrant']!=0:
return '此线性规划问题无解'
# 裁剪
# 删除掉多余的列
m, n = self.A.shape
del_columns = len(table.columns) - n - 2
for _ in range(del_columns):
del table[table.columns[n + 1]]
return self.simple_method2(table, self.f)
测试
输入说明
矩阵A:化为标准形式后的系数矩阵(使用列表存储)
矩阵b:化为标准形式后的常数项
优化函数f:其输入需要注意,从左往右依次是常数项,x1,x2,x3……,举例说明如下:
f
=
2
+
3
x
1
+
4
x
2
则
输
入
的
f
为
[
0
,
3
,
4
]
f=2+3x_1+4x_2\\ 则输入的f为[0,3,4]
f=2+3x1+4x2则输入的f为[0,3,4]
test.py文件
from lenear_programing import LenarPrograming
f=[0,-3,-4] # f函数
A=[
[1,2,1,0,0],
[3,2,0,1,0],
[0,1,0,0,1]
]
b=[6,12,2]
test_bfv=LenarPrograming(A,b,f)
# print(test_bfv.basic_feasible_variable())
print(test_bfv.solve())
A = [
[1, -2, 1, 1, 0],
[-4, 1, 2, 0, -1],
[-2, 0, 1, 0, 0]
]
b=[11,3,1]
f=[0,-3,1,1]
test_bfv=LenarPrograming(A,b,f)
# print(test_bfv.basic_feasible_variable())
print(test_bfv.solve())