单纯形法的补充与代码实现

线性规划中,我们介绍了三种求解算法——单纯形法、对偶理论和内点法。

传送门:线性规划之单纯形法 线性规划的对偶理论 线性规划之内点法

其中单纯形法要建立在标准型上,并且开始迭代要求有一个基本可行解。如果系数矩阵A规模较大,有时候比较难找到初始可行解。这时候需要用人工手段增加变量,来找到初始可行解。具体方法为:

通过从每个约束行中选取系数在对应列向量中唯一非零,而且系数符号与右边项一致的变量作为基变量,可以构造单纯形法的初始基。如果标准型中没有满足条件的变量,可以引入人工变量。此时目标函数为求人工变量之和最小化的问题,即目标函数除人工变量系数为1外,其余变量系数均为0.

我们举一个例子,对于下面的标准型LP问题,引入人工变量找到初始可行集:

单纯形法的补充与代码实现_第1张图片

首先看第一个约束,变量x4系数在列向量中唯一非零,但其系数为负,而对应的右边项系数为正,因此需要引入人工变量x6;第二个约束,变量x5系数在列向量中唯一非零,且符号与右边项一致,因此x5可作为初始基。第三第四个约束均没有满足条件的向量,引入人工变量x7和x8。

此时要求解的目标函数变为:

单纯形法的补充与代码实现_第2张图片

最后四列即可新LP问题的初始可行解,通过单纯形法可以求解最优解。最后还需要检验该问题最优解是否为原模型的可行解。判断标准为:

如果人工变量之和最小值大于0,则原模型无可行解,计算到此为止,否则人工模型最后的基作为求解原模型的初始可行基

我们用python开发完整的单纯形法代码,包含人工模型的(阶段一)以及求解原模型(阶段二)的过程。考虑到有些场景可以直接找到原模型初始可行解,因此阶段一不一定用得上,因此用python的装饰器实现。

import numpy as np
import pandas as pd
from numpy.linalg import det,inv #求解矩阵的秩和逆矩阵


class Simplex(object):
    '''
    单纯形法求解最值问题,这里固化为求最小值,要是遇到求最大值,将目标系数向量取负号即可
    '''


    def __init__(self, A, b, c, B_x):


        '''


        :param A: 系数矩阵
        :param b: 常数向量
        :param c: 目标函数系数向量
        :param B_x: 初始基变量索引列表
        '''
        self.A = A
        self.b = b
        self.c = c
        self.B_x = B_x


    def __call__(self, *args, **kwargs):


        print('单纯形法第二阶段...')
        x = self.phaseII(self.A, self.b, self.c, self.B_x)
        return x


    @classmethod
    def phaseI(cls, manual_n, A_, b, c, B_x_):


        '''
        单纯形法第一阶段,确定初始可行基
        这里人工变量排在矩阵后面
        :param manual_n: 人工变量的个数
        :param A_: 添加人工变量后的系数矩阵
        :param b: 常数向量
        :param c: 目标函数系数向量
        :param B_x_: 初始可行基
        :return:
        '''


        print('单纯形法第一阶段......')
        c_ = [0 for i in range(len(c))] + [1 for i in range(manual_n)]  # 添加人工变量后的目标函数系数向量, 人工变量系数为1,其余均为0
        c_ = np.array(c_)
        x_ = cls.phaseII(A_, b, c_, B_x_)
        a = np.dot(c_, np.array(x_))


        if a > 0:
            print('原模型无可行解')
        else:
            B_x = []
            for n, i in enumerate(x_):
                if i != 0:
                    B_x.append(n)


            return cls(A_[:, :-manual_n], b, c, B_x)


    @staticmethod
    def phaseII(A, b, c, B_x):
        '''


        :param n: 列向量个数
        :param B_x: 初始基变量索引列表
        :return:
        '''


        n = A.shape[1]  # 列向量个数
        N_x = list(filter(lambda x: 0 if x in B_x else 1, [i for i in range(n)]))  # 初始非基变量索引
        init_B = A[:, B_x]  # 基矩阵
        x_0 = np.dot(inv(init_B), b)  # t=0的解
        for i in N_x:
            x_0 = np.insert(x_0, i, 0)


        t, dim = 0, n - len(N_x)
        while True:


            if t >= 100:
                break


            # print('t={}时刻**********'.format(t))
            print('t={}时刻目标函数取值{}:'.format(t, np.dot(c, x_0)))
            # print('入基变量索引:', B_x)
            # print('出基变量索引:', N_x)
            # print('基矩阵:', init_B)
            # print('唯一解:', x_0)


            v = np.dot(c[B_x], inv(init_B))  # 定价向量
            # print('定价向量:', v)
            delta_c = dict(zip([c[i] - np.dot(v, A[:, i]) for i in N_x], N_x))
            # print('非基变量目标函数变化值:', delta_c)


            if min(delta_c) < 0:
                p = delta_c[min(delta_c)]
            else:
                p = -1
                print('计算结束,最优解为:', x_0)
                break


            delta_x = np.dot(-inv(init_B), A[:, p])
            # print('入基变量索引p={},此时单纯性方向为:{}'.format(p, delta_x))


            if min(delta_x) < 0:
                di = {}
                for i in range(dim):
                    if delta_x[i] < 0:
                        di[B_x[i]] = -x_0[B_x[i]] / delta_x[i]
                lam = min(di.values())
                r = min(di, key=di.get)
            else:
                r = -1
                print('单纯性方向所有元素非负,模型无界')
                break
            # print('出基变量索引r={},此时lamda取值为{}'.format(r,lam))


            E = np.identity(dim)
            E[:, B_x.index(r)] = [
                -1 / delta_x[B_x.index(r)] if i == B_x.index(r) else -delta_x[i] / delta_x[B_x.index(r)]
                for i in range(dim)]
            init_B = inv(np.dot(E, inv(init_B)))


            d = dict(zip(B_x, delta_x))
            delta_x = np.array(list(dict(sorted(d.items(), key=lambda x: x[0])).values()))
            for i in N_x:
                delta_x = np.insert(delta_x, i, 0)
            delta_x[p] = 1


            B_x[B_x.index(r)] = p  # 更新入基变量索引
            N_x = list(filter(lambda x: 0 if x in B_x else 1, [i for i in range(n)]))  # 更新出基变量索引
            x_0 = [x_0[i] + lam * delta_x[i] for i in range(n)]  # 更新解
            t += 1
        #print('###########################################')


        return x_0

下面是应用单纯形法求灵敏度分析的过程:

import itertools as it
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False


def plot(di):


    li1 = list(di.keys())
    li2 = list(di.values())
    plt.scatter([li1[i] for i in [0,80,84,100,199,291,318,355]],[li2[i] for i in [0,80,84,100,199,291,318,355]],c='r')
    # plt.text(5, 9900, s='斜率8.57', verticalalignment='bottom')
    # plt.text(11, 9970, s='斜率36.73', verticalalignment='bottom')
    # plt.text(10, 10020, s='斜率50.11', verticalalignment='bottom')
    plt.plot(di.keys(),di.values())
    # plt.xlim(-1,13)
    plt.xlabel('目标函数系数')
    plt.ylabel('最优值')
    plt.title('废料4成本对目标最优值影响')
    plt.show()




if __name__ == '__main__':
    # 系数矩阵
    A = np.array(
        [[1.000, 1.000, 1.0000, 1.000, 1, 1, 1,  0, 0,  0, 0,  0, 0,  0, 0, 0, 0],
         [0.008, 0.007, 0.0085, 0.004, 0, 0, 0, -1, 0,  0, 0,  0, 0,  0, 0, 0, 0],
         [0.008, 0.007, 0.0085, 0.004, 0, 0, 0,  0, 1,  0, 0,  0, 0,  0, 0, 0, 0],
         [0.180, 0.032, 0.0000, 0.000, 1, 0, 0,  0, 0, -1, 0,  0, 0,  0, 0, 0, 0],
         [0.180, 0.032, 0.0000, 0.000, 1, 0, 0,  0, 0,  0, 1,  0, 0,  0, 0, 0, 0],
         [0.120, 0.011, 0.0000, 0.000, 0, 1, 0,  0, 0,  0, 0, -1, 0,  0, 0, 0, 0],
         [0.120, 0.011, 0.0000, 0.000, 0, 1, 0,  0, 0,  0, 0,  0, 1,  0, 0, 0, 0],
         [0.000, 0.001, 0.0000, 0.000, 0, 0, 1,  0, 0,  0, 0,  0, 0, -1, 0, 0, 0],
         [0.000, 0.001, 0.0000, 0.000, 0, 0, 1,  0, 0,  0, 0,  0, 0,  0, 1, 0, 0],
         [1.000, 0.000, 0.0000, 0.000, 0, 0, 0,  0, 0,  0, 0,  0, 0,  0, 0, 1, 0],
         [0.000, 1.000, 0.0000, 0.000, 0, 0, 0,  0, 0,  0, 0,  0, 0,  0, 0, 0, 1],
         ])


    di = dict()
    for coef in np.linspace(0, 50, 501):
        b = np.array([1000, 6.5, 7.5, 30, 35, 10, 12, 11, 13, 75, 250])  # 常数项
        c = np.array([16, 10, 8, coef, 48, 60, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])  # 目标函数系数
        B_x = [2, 3, 4, 5, 6, 7, 10, 12, 14, 15, 16]
        # B_x = [1,7,8,9,10,11,12,13,14,15,16]
        # print(det(A[:,B_x]))
        # print(A.shape,b.shape,c.shape)
        model = Simplex(A,b,c,B_x)
        x_0 = model()
        di[coef] = np.dot(c,np.array(x_0))


    pd.DataFrame([di.keys(),di.values()]).to_csv(r'RHS.csv',index=False)
    plot(di)

你可能感兴趣的:(单纯形法的补充与代码实现)