Python数学建模:线性规划求解方法与PuLP库实战指南
本文深入解析线性规划在数学建模中的应用,对比图解法、单纯形法等传统算法与启发式方法的差异。重点介绍Python中PuLP库的标准用法,涵盖变量定义、目标函数构建及约束条件添加,通过完整代码示例演示如何高效求解线性规划问题,适合初学者快速掌握建模编程技巧。
一、求解方法、算法和编程方案
线性规划(Linear Programming,LP)通常是数学建模培训中引入的第一个核心算法。虽然其基础概念看似简单,但背后蕴含的数学思想却十分深刻。
线性规划问题早在中学数学阶段便已出现,例如经典的“鸡兔同笼”问题本质上就是一个线性规划案例。在高考数学中,这类题目频繁出现,形式包括直接给出线性约束条件求目标函数极值、间接给出约束条件求极值,以及已知约束条件求解非线性目标函数的极值问题。
因此,在各类数学建模问题和算法体系中,线性规划属于相对基础且简单的范畴。接下来,我们将通过这一熟悉且相对简单的问题,深入剖析数学建模问题的处理方法、算法逻辑及学习路径,探讨那些容易混淆的概念,从而帮助读者更好地理解本系列教程的初衷与特色。
1.1、线性规划问题的求解方法
解决线性规划问题存在多种数学途径,主要包括以下几种:
- 图解法:这是一种通过几何作图寻找最优解的方法,在中学阶段已有涉及,在经济学研究领域应用广泛;
- 矩阵法:通过引入松弛变量将线性规划问题转化为增广矩阵形式,进而进行逐步求解,这是单纯形法出现前的典型处理方式;
- 单纯形法:该方法利用多面体在可行域内逐步构造新顶点以逼近最优解,被视为线性规划研究的里程碑,至今仍是该领域最重要的求解方法之一;
- 内点法:通过选取可行域内部的点,沿下降方向不断迭代以逼近最优解,目前在理论上被认为是求解线性规划问题的最佳方法;
- 启发式方法:基于经验准则通过不断迭代改进来搜索最优解,常见的包括贪心法、模拟退火算法、遗传算法以及神经网络等。
尽管不同的求解方法都旨在解决线性规划问题,最终结果殊途同归,但它们在核心思想上存在本质区别,导致具体的求解步骤和策略截然不同。
毫不夸张地说,对于许多初学者而言,是否学习过单纯形法,对其后续学习启发式方法几乎没有影响。
这揭示了什么?这意味着对于非数学专业的学生,或者旨在参加数学建模竞赛的学习者来说,针对每一类问题,完全没有必要掌握所有的解决方法。即便是数学专业的学生,在数学建模的学习周期内也不可能学会所有方法。
对于初学者,本文推荐选择较为通用且相对简单(思路清晰、代码简洁)的方法进行学习,无需盲目追求数量或新奇性。
1.2、线性规划的最快算法
那么,算法与方法有何不同?
算法被定义为“解题方案的准确而完整的描述”,是一系列清晰的问题解决指令,代表了用系统化方法描述解决问题策略的机制。
我对“方法”的理解偏向于思想层面的总体框架,而“算法”则是具体且明确的实现步骤,在计算机编程中相当于详细的流程图。
在提出每种方法的基本思想和方案后,往往会出现许多变形、改进和发展的算法。极少数经过改进的算法具有实质性贡献并成为主流经典算法,即便如此,它们通常也仅带来性能或效率上的提升,对解决数学建模竞赛中的问题影响不大。
而绝大多数改进算法仅针对某些特殊情况或特定问题(自称)有效,常被用于大量发表学术论文。对于数学建模而言,学习基本算法或当前经典算法已足够,无需轻信那些自称具有优势的改进算法,那往往类似于“莆田系”广告般的夸大宣传。
存在一种例外情况,即某些算法具有特定的适用范围和限制条件。例如,内点法的基本算法无法处理等式约束,最短路径问题中的Dijkstra算法不能处理负权边。在这种情况下,如果选错算法,问题将无法求解。因此,对我们而言,明确算法的适用范围比深入理解算法本身更为重要。
回到本节标题,对于线性规划问题,什么算法是最快的呢?答案是:猜。并非让你随意猜测,而是指在求解线性规划问题时,“猜”起来可能比较快。这并非玩笑,而是严肃的观点。
佐治亚理工学院的彭泱教授在2021年计算机理论顶级会议SODA 2021上获得最佳论文奖(Best paper award at ACM-SIAM symposium on discrete algorithms 2021),其研究正是关于线性规划问题的求解——“Solving sparse linear systems faster than matrix multiplication”。其所采用的全新思路是:猜,反复猜,迭代猜。

当然,“猜起来比较快”仅在特定特殊条件下有效。至于在什么条件下猜、如何猜,这并非我们所能理解或需要关心的重点。此处举例旨在说明,简单的问题背后也可能存在复杂情况,每个问题都有多种求解思路、方法和算法。
1.3、选择适合自己的编程方案
“编程方案”是我杜撰的一个术语。我想表达的意思是,在选择了求解方法和算法之后,是依据算法步骤一步步自行编程实现,还是寻找例程进行调试使用,亦或是调用第三方工具包/库函数来完成?
首先,对于学习数学建模或参加数模竞赛而言,不建议按照算法步骤自行编程。我们在《01.新手必读》中讨论过此问题:对于既是数学小白又是计算机小白的学习者来说,这样做既不可行也无必要;即使你愿意挑战自我尝试,那实际上已经是在学习另一门计算机或算法课程的路上了。
其次,是否应该寻找例程自行调试和使用?许多数学建模培训正是如此宣传并操作的,甚至将这些收集的例程视为核心机密以吸引学员。我不反对这种做法,这种学习方法有助于理解算法和提高编程能力;但我不推荐这样做,原因如下:
(1)我认为学习数学建模、参加数模竞赛的重点应放在识别问题、分析问题和解决问题上,只要能使用算法和编程即可;
(2)第三方库与例程没有本质区别,第三方库就是经典的、规范的、标准化的例程。既然选择使用例程,为何不选择优秀的例程——即第三方库呢?
(3)大部分自行编写的例程存在诸多问题,即使调试通过仍有许多难以识别的陷阱,尤其是对于新手而言。
因此,我明确推荐优先直接使用第三方库来解决问题,这也符合Python语言“不要重复造轮子”的核心思想。
进一步地,许多工具包/库函数都能实现常用算法,应如何选择呢?
如果你对某个工具包非常熟悉,且能实现所需算法,这当然是理想的选择。如果你是初学者,请跟随我的建议。
本系列选择第三方工具包的原则如下:
(1)优先选择常用的工具包;
(2)优先选择具备通用功能的工具包和函数(例如,最好既能实现线性规划,又能实现整数规划、非线性规划);
(3)优先选择安装简单、使用便捷、配置灵活的工具包;
(4)优先选择兼具模型检验和图形绘制功能的工具包。
二、PuLP库求解线性规划问题
2.1、线性规划问题的描述
线性规划旨在研究在线性等式或不等式约束条件下,求解线性目标函数的极值问题,常用于解决资源分配、生产调度和混合配比等问题。
一般线性规划问题的标准形式如下:

满足所有约束条件的解,称为线性规划问题的可行解;所有可行解构成的集合,称为可行域。
使目标函数达到最小值的解,称为最优解。
线性规划问题的建模和求解,通常遵循以下步骤:
- 问题定义:确定决策变量、目标函数和约束条件;
- 模型构建:根据问题描述建立数学方程,并转化为标准形式的数学模型;
- 模型求解:使用标准模型的优化算法对模型进行求解,得到优化结果。
许多Python第三方包都提供了求解线性规划问题的算法,部分工具包还提供整数规划、非线性规划的算法。例如:
- Scipy库提供了解简单线性或非线性规划问题的功能,但不能求解如背包问题的0-1规划问题,或整数规划、混合整数规划问题。
- PuLP可以求解线性规划、整数规划、0-1规划、混合整数规划问题,并提供多种针对不同类型问题的求解器。
- Cvxpy是一种凸优化工具包,可以求解线性规划、整数规划、0-1规划、混合整数规划、二次规划和几何规划问题。
此外,SKlearn、DOcplex、Pymprog等许多第三方工具包也能求解线性规划问题。
2.2、PuLP 求解线性规划问题的步骤
例题 1:

下面以该题为例讲解 PuLP 求解线性规划问题的步骤:
(0)导入 PuLP库函数
import pulp
(1)定义一个规划问题
MyProbLP = pulp.LpProblem("LPProbDemo1", sense=pulp.LpMaximize)
pulp.LpProblem 是定义问题的构造函数。
"LPProbDemo1"是用户定义的问题名(用于输出信息)。
参数 sense 用来指定求最小值/最大值问题,可选参数值:LpMinimize、LpMaximize 。本例 “sense=pulp.LpMaximize” 表示求目标函数的最大值。
(2)定义决策变量
x1 = pulp.LpVariable('x1', lowBound=0, upBound=7, cat='Continuous')
x2 = pulp.LpVariable('x2', lowBound=0, upBound=7, cat='Continuous')
x3 = pulp.LpVariable('x3', lowBound=0, upBound=7, cat='Continuous')
pulp.LpVariable 是定义决策变量的函数。 'x1' 是用户定义的变量名。
参数 lowBound、upBound 用来设定决策变量的下界、上界;可以不定义下界/上界,默认的下界/上界是负无穷/正无穷。本例中 x1,x2,x3 的取值区间为 [0,7]。
参数 cat 用来设定变量类型,可选参数值:'Continuous' 表示连续变量(默认值)、' Integer ' 表示离散变量(用于整数规划问题)、' Binary ' 表示0/1变量(用于0/1规划问题)。
(3)添加目标函数
MyProbLP += 2*x1 + 3*x2 - 5*x3 # 设置目标函数
添加目标函数使用 "问题名 += 目标函数式" 格式。
(4)添加约束条件
MyProbLP += (2*x1 - 5*x2 + x3 >= 10) # 不等式约束
MyProbLP += (x1 + 3*x2 + x3 <= 12) # 不等式约束
MyProbLP += (x1 + x2 + x3 == 7) # 等式约束
添加约束条件使用 "问题名 += 约束条件表达式" 格式。
约束条件可以是等式约束或不等式约束,不等式约束可以是 小于等于 或 大于等于,分别使用关键字">="、"<="和"=="。
(5)求解
MyProbLP.solve()
print("Status:", pulp.LpStatus[MyProbLP.status]) # 输出求解状态
for v in MyProbLP.variables():
print(v.name, "=", v.varValue) # 输出每个变量的最优值
print("F(x) = ", pulp.value(MyProbLP.objective)) #输出最优解的目标函数值
solve() 是求解函数。PuLP默认采用 CBC 求解器来求解优化问题,也可以调用其它的优化器来求解,如:GLPK,COIN CLP/CBC,CPLEX,和GUROBI,但需要另外安装。
2.3、Python例程:线性规划问题
例程 1:求解线性规划问题
import pulp
MyProbLP = pulp.LpProblem("LPProbDemo1", sense=pulp.LpMaximize) # 求最大值
x1 = pulp.LpVariable('x1', lowBound=0, upBound=7, cat='Continuous')
x2 = pulp.LpVariable('x2', lowBound=0, upBound=7, cat='Continuous')
x3 = pulp.LpVariable('x3', lowBound=0, upBound=7, cat='Continuous')
MyProbLP += 2*x1 + 3*x2 - 5*x3 # 设置目标函数
MyProbLP += (2*x1 - 5*x2 + x3 >= 10) # 不等式约束
MyProbLP += (x1 + 3*x2 + x3 <= 12) # 不等式约束
MyProbLP += (x1 + x2 + x3 == 7) # 等式约束
MyProbLP.solve() # youcans@xupt
print("Status:", pulp.LpStatus[MyProbLP.status]) # 输出求解状态
for v in MyProbLP.variables(): # youcans
print(v.name, "=", v.varValue) # 输出每个变量的最优值
print("Max F(x) = ", pulp.value(MyProbLP.objective)) #输出最优解的目标函数值
例程 1 运行结果:
Welcome to the CBC MILP Solver
Version: 2.9.0
Build Date: Feb 12 2015
Status: Optimal
x1 = 6.4285714
x2 = 0.57142857
x3 = 0.0
Max F(x) = 14.57142851
例程01 程序说明:
- 用 PuLP 库求解线性规划问题,可以选择求最大值或最小值,可以按照问题的数学描述,直接输入目标函数、等式约束和不等式约束,不等式约束可以选择 <= 或 >=,不需要进行转换。这中方式简单直观,非常适合初学者掌握。
- 对于较大规模线性规划问题, PuLP 库支持用字典类型(dict)建立多个变量,设置目标函数和约束条件。
三、小结
求解线性规划问题的方法非常简单,本文实际上并未讲解具体的算法。
希望通过对求解方法、算法和编程方案的讲解,阐明作者对于数学建模学什么、怎么学的理解,也使读者能了解本系列教程的特点:本教程不打算详细讲解各种算法的具体方法,重点介绍如何使用第三方包实现算法、解决问题。
以上就是浅谈Python数学建模之线性规划的详细内容,更多关于Python 线性规划的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









