线性规划问题是运筹学当中重要的一个话题,参考教材《简明运筹学》,由姚奕荣,韩伯顺编写。重点基于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 的最大值,转化为求 -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
令 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行当中
到此一轮处理完成
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)
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())