单纯形法求解线性规划问题

线性规划问题是运筹学当中重要的一个话题,参考教材《简明运筹学》,由姚奕荣,韩伯顺编写。重点基于python实现的单纯形法以求解线性规划问题。

线性规划问题标准形式

原始形式

当使用线性规划方法求解问题时,通常包含两个组成部分:

  1. 待优化的函数
  2. 约束条件

希望达到的目的是:在满足约束条件的情况下,最大化或者最小化目标函数;

标准形式

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+x33x1+x2+2x34x12x23x3x10,x2094=6

  1. 针对优化函数 z 和变量 x

求 z 的最大值,转化为求 -z 的最小值;当x大于等于0的时候,转化为-x<=0
z , = − z x 1 , = − x 1 z^,=-z\\ {x_1}^,=-x_1 z,=zx1,=x1

  1. 对于约束条件中存在的小于或者大于情况,通过加入自由变量将约束条件转化为等于

对 于 − 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+x39x42x1+x2+x3+x4=9x403x1+x2+2x34x43x1+x2+2x3x5=4x50

  1. 对于没有给出约束范围的变量,例如本例中的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,0x3,,0

  1. 将上面新引入的变量

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,2x23x3,+3x3,,2x1,+x2+x3,x3,,+x4=93x1,+x2+2x3,2x3,,x5=44x1,+2x23x3,3x3,,=6z,0,x1,0,x20,x3,0,x3,,0,x40,x50

后面使用单纯形表法的时候,需要首先将一般的线性规划问题转化为标准形式
单纯形法求解线性规划问题_第1张图片

单纯形法

算法实现思路

创建单纯形表:(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)

测试

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())

测试结果

单纯形法求解线性规划问题_第2张图片

你可能感兴趣的:(算法,python,python,开发语言)