ANOMS便签


大江歌罢掉头东
邃密群科济世穷
面壁十年图破壁
难酬蹈海亦英雄

用户工具

站点工具


post:20220906_1

求解线性规划问题

#!/usr/bin/python3
#coding=utf-8
# 使用图解法(穷举法)寻找线性规划问题的最优解
# 其中,线性方程组使用高斯消元法求解

import numpy as np

## ============================================
## ======== 以下为高斯消元法的算法部分 ========
# 参考代码:https://blog.csdn.net/lzyws739307453/article/details/89816311

# 打印矩阵的内容
def printM(a):
    m = len(a)
    n = len(a[0])
    for i in range(0,m):
        for j in range(0,n):
            print("%10f,\t"%a[i][j],end="")
        print()

# 选择列主元并消元
def SelectColE(a): #a:2-dimension matrix; n:int 
    n = len(a) # 参数n应该代表的是矩阵的行数,也就是线性方程组的方程个数
    for i in range(0,n): # 循环处理每一行的信息
        r = i
        for j in range(i,n):
            if(abs(a[j][i])>abs(a[r][i])): r=j
        if(r!=i): # 当r!=i时,交换两行的内容。这里的操作基于以下逻辑:
            # 由于单纯的高斯消元法会将矩阵化为梯形矩阵,靠下方的行中系数为0的项更多
            # 而非0项也会有很多分母,在除法运算中会导致精度降低
            # 因此将系数大的项尽可能的往前移动,可以提高精度
            tmp  = a[r]
            a[r] = a[i]
            a[i] = tmp
        for j in range(i+1,n): # 消元
            temp = a[j][i]/a[i][i]
            for k in range(i,n+1):
                a[j][k] -= a[i][k]*temp
        #print("第{}列消元后:".format(i))
        #printM(a)

# 高斯消元法
def Gauss(a): 
    n = len(a) # 参数n应该代表的是矩阵的行数,也就是线性方程组的方程个数
    SelectColE(a)
    #print("上三角矩阵的结果:")
    #printM(a)
    for i in range(n-1,-1,-1): # 回代求解
        # i 从 n-1递减循环到0,可以取到0
        # 从第n-1行循环到第0行。由于前面已经对矩阵进行了变换,得到了一个倒三角矩阵
        # 所以n-1行可以直接求得第n-1个未知数的值(未知数的下标从0开始)
        # 而前面的行则需要经过一些迭代
        for j in range(i+1,n):  # 对行矩阵进行回代
            # 在此行的最后一个元素处存储回代后的值
            # a[i][n]是行矩阵的最后一个元素,是存储回代后的数值的地方
            # a[i][i]是本行对应的未知数的系数。一共i行,那么就是i个未知数
            # 例如一个3*4的矩阵,有3个变量分别是x0,x1,x2,那么当i=1时,
            # 对应位置就是变量x1的系数
            # 同样的,a[i][j]是本行第j个未知数的系数,
            # 而第j个未知数的值刚刚我们已经求过了,是a[j][n],
            # 所以a[i][n]减去他俩相乘的值就行
            a[i][n] -= a[i][j]*a[j][n]
        # 回代结束后,对系数进行化简,使得第i个未知数的系数变为1,即可
        # 于是第i个未知数的值就等于a[i][n]/a[i][i]。我们把它存储在a[i][n]的位置上
        a[i][n] /= a[i][i]

## ======== 以上为高斯消元法的算法部分 ========
## ============================================



# 回溯法求解组合情况枚举问题
# 参考自B站
# https://www.bilibili.com/video/BV1Lg411P7k3
def dfs(a,step,n,r,res):
    # a: 一个数组,用于存储组合方案。需要预先对数组大小初始化
    # step: 当前进行到了第几步
    # n: 元素的总体个数
    # r: 选取几个元素。注意,r<=n
    # res: 一个用于存储所有结果的公共变量
    for i in range(a[step-1]+1,n+1):
        a[step] = i
        if(step==r): #到达目的地
            res.append(a.copy())
        else:
            dfs(a,step+1,n,r,res)
def run_dfs(n,r):
    # n: 元素的总体个数
    # r: 选取几个元素。注意,r<=n
    a = np.zeros(r+1,dtype=int)
    res = []
    dfs(a,1,n,r,res)
    return res

# 判断一个解是否为可行解
def feasible_solution(A,b,X):
    # 传入参数:
    #  - A:m*n的矩阵,参考graph_method(A,b,C)的定义
    #  - b:长度为m的向量,参考graph_method(A,b,C)的定义
    #  - X:长度为n的向量,是决策变量向量的一组基解
    # 我们的任务是判断这一组基解是否为可行解
    print('子程序:判断一组基解是否可行')
    #print('A=',A)
    #print('X=',X)
    for x in X:
        if(x<0):return False
    b1 = np.dot(A,X)
    for i in range(len(b)):
        if(b1[i]!=b[i]):return False
    return True

# 图解法求解线性规划问题
def graph_method(A,b,C): 
    # 传入参数包括一个矩阵A和一个向量b,以及一个向量C
    #  - 矩阵A=(P_1,P_2,...,P_n),这个矩阵大小为m*n,其中m为约束条件方程的个数,
    #    n为决策变量的变量个数(包括松弛变量)。注意m<n
    #  - 向量b是资源向量,其长度为m,与约束条件方程的个数一致
    #  - 向量C是价值向量,其长度为n,与决策变量个数一致
    #
    # 注意,在调用此函数前,必须将线性规划问题化为带有松弛变量的标准形式!
    # 线性规划问题的标准形式的矩阵表达:
    # max z=CX, 
    #        其中:
    #           C为目标函数的系数向量,或者叫做价值向量;
    #           X是未知数向量,或者决策变量向量
    # s.t. sum(P_j*x_j)=b
    #      x_j >= 0, j=1,2,...,n
    #        其中:
    #           P_j是第j个未知数(决策变量)在所有约束条件中的系数向量
    #           x_j是第j个未知数(决策变量)
    #           b是资源向量,可以理解为约束条件不等式中的常数项
    #
    # 我们的求解思路是,从矩阵A中得到所有可能的基矩阵B,后者是一个m*m的矩阵
    # 并利用这个基和资源向量b求出一个基解
    # 之后,使用约束条件判断这个基解是否是可行解,这样我们得到了一个基可行解的集合
    # 我们在这个可行解的集合中找到最大的那个解,返回这个解的决策变量向量和解的数值
    print("\n\n##############\n要求解的约束向量A=\n",A)
    m = len(b) # 约束条件个数
    n = len(C) # 决策变量个数
    r = run_dfs(n,m) # 通过回溯法,寻找所有可能的基矩阵的组合方式
    s = []     # 一个存储基可行解的列表 
    # 下面这个循环从所有基中获得基解,然后判断基解是否是可行解
    for ri in r:
        print("\n\n>>> 尝试计算一个基解")
        tmp = [] # 一个临时变量,用于存储基向量
        for i in ri[1:]: # 注意,上述dfs函数的返回值中,每个组合是以数组的[1:]存储的
            tmp.append(A[:,i-1].copy().T) # 用这个方式获得基向量.i要记得减1
        B  = np.array(tmp).T # 这就获得了一个基
        # np.column_stack((B,b)) 是构建一个增广向量
        Bb = np.column_stack((B,b)).astype(float).tolist()
        print('Bb=',np.array(Bb))
        try: # 如果线性方程组无解:
        #if(True):
            Gauss(Bb)  
            print('Bb=',Bb)
            X = np.zeros(n)
            for j in range(m):
                X[ri[j+1]-1] = Bb[j][-1]
            #X  = np.array(Bb)[:,-1]
            #print('X=',X)
            if(feasible_solution(A,b,X)):
                print("一个可行解为:\n{}".format(X))
                s.append(X)
            else:
                print("这个基解不是可行解:\n{}".format(X))
        except: # 线性方程组无解:
            print("这个基没有解:\n{}".format(B))
        
        # TODO:
        # 1. 基可行解的形式是X=(x_1,x_2,...,x_m,0,0,...0),这是因为使用一个基(m*m矩阵)能够求出来的解的个数只有m个,然而约束条件共n个(n>m),所以剩下的要在对应位置填0。代码中这一部分的逻辑有问题,请重新编写【update:已完成】
        # 2. 高斯消元法在之前的测试中确实成功求出来了一些解,不知道为什么现在总是出现NaN的情况,请检查相关的逻辑【update:是numpy和list不兼容的问题。已完成】
        # 3. 在判断一个基解是可行解之后,应该再去进行比较,以获得目标函数z最大的可行解。请完善这一部分的逻辑【这是下一步的目标】






if(__name__=='__main__'):
    # 下面的内容是高斯消元法求解线性方程组的测试
    # 最终的方程组的解为a[:,-1]
    """
    a =[[ 1,-1,-1, 2 ],
        [ 2,-1,-3, 1 ],
        [ 3, 2,-5, 0 ]]
    Gauss(a.copy())
    print("求解结果:")
    printM(a)
    print("数据结构:")
    printM(a)
    for i in range(0,len(a)):
        print("X%d = %9f"%(i,a[i][3]))
    print(np.array(a)[:,-1])
    """

    # 书上的例题1
    A =[[1, 2, 1, 0, 0],
        [4, 0, 0, 1, 0],
        [0, 4, 0, 0, 1]]
    b = [8, 16,12]
    C = [2, 3, 0, 0, 0]
    # 本题的解为 X=[4,2,*,*,*], z_max=14
    graph_method(np.array(A),b,C) # 图解法求解线性规划问题
post/20220906_1.txt · 最后更改: 2022/09/06 23:24 由 cyclin