用Python实现单纯形法:从数学原理到代码落地
线性规划作为运筹优化的核心工具,单纯形法无疑是其中最经典的求解算法。但很多学习者止步于表格操作步骤的记忆,对算法背后的迭代逻辑一知半解。本文将带您用Python从头构建一个单纯形法求解器,通过代码实现穿透数学抽象,真正掌握这一优化利器的运作机制。
1. 单纯形法的核心思想与实现框架
单纯形法的本质是在可行解空间的顶点间智能跳跃,逐步逼近最优解。想象一个多面体的每个顶点代表一个基本可行解,算法的工作就是沿着边线从一个顶点移动到更优的相邻顶点。这种几何直观转化为代数操作,就形成了我们熟悉的单纯形表变换。
算法核心流程:
- 初始化:将线性规划问题转化为标准型,构造初始单纯形表
- 最优性检验:计算检验数判断当前解是否最优
- 基变换:若非最优,确定入基和出基变量
- 迭代更新:通过高斯-约当消元更新单纯形表
- 终止条件:直到满足最优或发现无界解
让我们先定义问题标准形式:
import numpy as np class LinearProgram: def __init__(self, c, A, b): """ 线性规划标准型: max c^T x s.t. Ax = b x ≥ 0 """ self.c = c # 目标函数系数 self.A = A # 约束矩阵 self.b = b # 约束右端项2. 单纯形表数据结构设计与初始化
单纯形表是算法运行的载体,我们需要将其数学表示转化为程序数据结构。关键是将约束方程组和目标函数整合为一个增广矩阵。
def initialize_simplex_table(lp): # 添加松弛变量后的扩展矩阵 m, n = lp.A.shape tableau = np.zeros((m+1, n+m+1)) # 填充约束部分 tableau[:-1, :n] = lp.A tableau[:-1, n:n+m] = np.eye(m) # 松弛变量 tableau[:-1, -1] = lp.b # 填充目标函数行 tableau[-1, :n] = -lp.c # 原始变量检验数 tableau[-1, n:n+m] = 0 # 松弛变量检验数 return tableau, list(range(n, n+m)) # 返回初始表和基变量索引表格结构示例:
| 基变量 | x₁ | x₂ | ... | s₁ | s₂ | ... | 解 |
|---|---|---|---|---|---|---|---|
| s₁ | a₁₁ | a₁₂ | ... | 1 | 0 | ... | b₁ |
| s₂ | a₂₁ | a₂₂ | ... | 0 | 1 | ... | b₂ |
| ... | ... | ... | ... | ... | ... | ... | ... |
| -z | -c₁ | -c₂ | ... | 0 | 0 | ... | 0 |
3. 迭代逻辑的代码实现
3.1 最优性检验与入基选择
检验数决定是否达到最优解,也指导着改进方向。我们通过扫描目标函数行来寻找可能的改进空间。
def optimality_test(tableau): # 所有检验数非负时达到最优 return all(x >= 0 for x in tableau[-1, :-1]) def select_entering_variable(tableau): # 选择最负检验数对应的变量入基(Bland规则避免循环) last_row = tableau[-1, :-1] return np.argmin(last_row)3.2 出基变量选择与比值测试
确定入基变量后,需要找出限制改进幅度的约束,这通过比值测试实现。
def select_leaving_variable(tableau, entering_idx): m = tableau.shape[0] - 1 ratios = [] for i in range(m): if tableau[i, entering_idx] > 0: ratio = tableau[i, -1] / tableau[i, entering_idx] ratios.append((ratio, i)) if not ratios: # 无限制,问题无界 return None # 选择最小非负比值对应的行 return min(ratios, key=lambda x: x[0])[1]3.3 基变换与表格更新
高斯-约当消元是单纯形法的核心运算,它通过行变换将入基变量转化为单位向量。
def pivot(tableau, entering_idx, leaving_idx): m, n = tableau.shape # 归一化主元行 pivot_val = tableau[leaving_idx, entering_idx] tableau[leaving_idx] /= pivot_val # 消去其他行的入基变量 for i in range(m): if i != leaving_idx: multiplier = tableau[i, entering_idx] tableau[i] -= multiplier * tableau[leaving_idx]4. 完整算法实现与特殊处理
将上述模块组合起来,并添加退化处理和无界解检测,我们得到完整的单纯形法实现。
def simplex(lp, max_iter=100): tableau, basis = initialize_simplex_table(lp) for _ in range(max_iter): if optimality_test(tableau): break entering = select_entering_variable(tableau) leaving = select_leaving_variable(tableau, entering) if leaving is None: raise ValueError("问题无界") basis[leaving] = entering pivot(tableau, entering, leaving) else: raise ValueError("超过最大迭代次数") # 提取最优解 solution = np.zeros(tableau.shape[1] - 1) for i, var in enumerate(basis): solution[var] = tableau[i, -1] return solution, -tableau[-1, -1] # 解和最优值退化处理技巧:
- 当比值测试出现平局时,采用Bland规则选择下标最小的变量
- 记录迭代历史检测循环,必要时使用扰动法打破僵局
5. 可视化迭代过程与调试技巧
理解算法的最佳方式是观察其运行过程。我们可以通过添加打印语句和可视化来跟踪单纯形表的变换。
def print_tableau(tableau, basis): print("\n当前单纯形表:") headers = [f"x{i+1}" for i in range(tableau.shape[1]-1)] + ["RHS"] print("\t".join(["基"] + headers)) for i in range(tableau.shape[0]-1): row = [f"s{basis[i]+1}"] + [f"{x:.2f}" for x in tableau[i]] print("\t".join(row)) print("\t".join(["-z"] + [f"{x:.2f}" for x in tableau[-1]]))调试常见问题:
- 初始基不可行:需要两阶段法处理
- 数值不稳定:使用部分主元法或尺度变换
- 循环问题:实现Bland规则或随机扰动
6. 性能优化与工业级实现
上述教学实现侧重清晰度,实际应用中还需考虑:
def revised_simplex(lp): """修正单纯形法,适合大规模稀疏问题""" # 仅存储基矩阵的逆,而非整个表格 # 使用LU分解更新基逆 # 采用高级数据结构处理稀疏矩阵 pass性能对比:
| 方法 | 存储需求 | 计算复杂度 | 适用场景 |
|---|---|---|---|
| 标准单纯形法 | O(mn) | O(m²n) | 中小规模稠密问题 |
| 修正单纯形法 | O(m²) | O(mn) | 大规模稀疏问题 |
| 内点法 | O(n²) | O(n³L) | 超大规模问题 |
实现时还可考虑:
- 使用稀疏矩阵库(如scipy.sparse)
- 并行化检验数计算
- 增量式更新技术
7. 从单纯形法到现代优化求解器
单纯形法虽经典,但现代优化库如PuLP、CVXPY等已提供更友好的接口。理解底层实现却能让你:
- 更准确地解释求解结果
- 自定义算法应对特殊问题结构
- 有效处理求解失败情况
- 进行高级的灵敏度分析
# 使用实现的单纯形法求解实例 c = np.array([3, 5]) # max 3x1 + 5x2 A = np.array([[1, 0], [0, 2], [3, 2]]) # 约束系数 b = np.array([4, 12, 18]) # 约束右端项 lp = LinearProgram(c, A, b) solution, optimal_value = simplex(lp) print(f"最优解:{solution},最优值:{optimal_value}")通过这个从零实现的旅程,单纯形法不再是一套机械的表格操作,而成为您可以自由驾驭的优化工具。当您下次调用现成的求解器时,将能更深入地理解其运作机制,并在出现问题时知道如何诊断和调整。