LINGO实战:从运输优化到投资决策的线性规划建模指南
1. 从实际问题到数学模型运输与投资的共通逻辑最近在整理一些数学建模的案例时我发现“运输方案”和“连续投资”这两个看似风马牛不相及的问题在建模和求解思路上有着惊人的相似性。它们都属于典型的优化问题核心目标都是在满足一系列约束条件的前提下寻找一个最优的决策方案以实现成本最低、利润最大或效益最高。很多初学者在面对这类问题时常常感到无从下手要么是变量关系理不清要么是约束条件列不全最后模型建得七零八落。今天我就结合自己多年使用LINGO软件的经验把这两个经典案例掰开揉碎了讲清楚让你不仅知道怎么用LINGO求解更明白模型背后的数学逻辑和建模技巧。无论是运输方案还是连续投资其本质都是资源的最优配置。运输问题要解决的是如何将货物从多个产地供应点以最小的总运费运往多个销地需求点同时满足供需平衡。连续投资问题则是在多个时间周期内如何分配初始资金到不同的投资项目上使得在最终时刻获得的总收益最大。这两个问题都可以抽象为线性规划Linear Programming, LP或整数规划Integer Programming, IP模型这正是LINGO这类优化软件的“主战场”。对于数学建模的实践者来说掌握LINGO这类工具的意义远不止于得到一个数字答案。更重要的是它迫使你将一个模糊的实际问题转化为严谨的数学语言目标函数和约束条件。这个过程才是建模能力的核心。接下来我将分别深入这两个案例从问题分析、模型建立到LINGO求解和结果解读一步步带你走完整个流程。2. 运输方案问题从产销平衡到模型构建我们先从更直观的运输问题开始。假设你是一家物流公司的调度员公司有三个仓库产地A1, A2, A3向四个超市销地B1, B2, B3, B4供应某种商品。每个仓库的库存供应量、每个超市的需求量以及从每个仓库到每个超市的单位运输成本都是已知的。你的任务就是制定一个运输计划明确每个仓库应该给每个超市运送多少货物使得总运输成本最低。2.1 问题数据化与决策变量定义建模的第一步是把所有文字描述转化为数据。这是最基础也最容易出错的一步。假设我们有以下数据表表1供应量、需求量与单位运价表单位吨元/吨产地\销地B1B2B3B4供应量 (ai)A13113107A219284A3741059需求量 (bj)3656总供应20总需求20这张表包含了全部信息交叉处的数字是单位运价Cij如从A1到B1的运费是3元/吨最后一行是每个销地的需求量bj最后一列是每个产地的供应量ai。注意这里总供应等于总需求749 3656 20这是一个产销平衡的运输问题。如果供需不等则需要先通过设立虚拟产地或销地将其转化为平衡问题这是后话。接下来定义决策变量。决策变量就是我们要求解的那些未知数。在这个问题里我们需要决定从每个产地i运往每个销地j的货物量。因此我们定义Xij从产地i运往销地j的货物量i1,2,3; j1,2,3,4。 例如X12就表示从A1仓库运往B2超市的货物量。所有Xij都必须是非负的。2.2 目标函数与约束条件的形式化有了变量我们就可以用数学公式来描述“总成本最低”这个目标。目标函数总成本Min Z 3*X11 11*X12 3*X13 10*X14 1*X21 9*X22 2*X23 8*X24 7*X31 4*X32 10*X33 5*X34简单说总成本就是所有可能的运输路径上的运量乘以单位运价之和。我们的目标就是找到一组Xij的值让这个Z最小。仅有目标不行运输计划必须可行。这就引出了约束条件供应约束从每个仓库运出的货物总量不能超过其供应量。对于A1X11 X12 X13 X14 7注意这里是等于因为产销平衡通常我们会把供应量全部运出对于A2X21 X22 X23 X24 4对于A3X31 X32 X33 X34 9需求约束运到每个超市的货物总量必须满足其需求量。对于B1X11 X21 X31 3对于B2X12 X22 X32 6对于B3X13 X23 X33 5对于B4X14 X24 X34 6非负约束所有运量不能为负数。Xij 0。至此一个完整的线性规划模型就建立起来了。它包含1个目标函数7个等式约束3个供应4个需求和12个非负约束对应12个决策变量。2.3 LINGO求解与代码解析打开LINGO我们可以用两种方式输入模型一种是类似于数学公式的“建模语言”另一种是更简洁的“集合循环”方式。对于初学者我建议先从公式方式入手理解每个部分的意义。方式一直接公式输入MODEL: ! 运输问题; MIN 3*X11 11*X12 3*X13 10*X14 1*X21 9*X22 2*X23 8*X24 7*X31 4*X32 10*X33 5*X34; ! 供应约束; X11 X12 X13 X14 7; X21 X22 X23 X24 4; X31 X32 X33 X34 9; ! 需求约束; X11 X21 X31 3; X12 X22 X32 6; X13 X23 X33 5; X14 X24 X34 6; END输入后点击菜单栏的“Solve”按钮或按CtrlULINGO会快速求解并弹出报告窗口。方式二使用集合与循环推荐对于变量多的问题这种方式更高效也更接近编程思维。MODEL: SETS: WAREHOUSE /A1, A2, A3/: SUPPLY; MARKET /B1, B2, B3, B4/: DEMAND; LINKS(WAREHOUSE, MARKET): COST, VOLUME; ENDSETS DATA: SUPPLY 7, 4, 9; DEMAND 3, 6, 5, 6; COST 3, 11, 3, 10, 1, 9, 2, 8, 7, 4, 10, 5; ENDDATA ! 目标函数最小化总成本; MIN SUM(LINKS(I, J): COST(I, J) * VOLUME(I, J)); ! 供应约束每个仓库运出的总量等于其供应量; FOR(WAREHOUSE(I): SUM(MARKET(J): VOLUME(I, J)) SUPPLY(I); ); ! 需求约束运到每个市场的总量等于其需求量; FOR(MARKET(J): SUM(WAREHOUSE(I): VOLUME(I, J)) DEMAND(J); ); ENDSETS定义了集合。WAREHOUSE是产地集合有个属性叫SUPPLY供应量。MARKET是销地集合有个属性叫DEMAND需求量。LINKS是派生集合由前两个集合派生而来表示所有可能的运输路线它有COST运价和VOLUME运量即决策变量两个属性。DATA给集合的属性赋值。注意COST的赋值顺序是按行展开的A1-B1, B2, B3, B4然后A2-B1...SUM和FOR是LINGO的集合循环函数可以简洁地表达求和与循环约束避免了手动列出所有项。注意在LINGO中默认所有变量都是非负的所以不需要显式写出VOLUME 0。如果变量可以取负值或需要整数约束则需要特别声明。2.4 结果解读与灵敏度分析求解后查看报告。你会看到最优解Global optimal solution found和最优目标值Objective value。对于本例最小总成本为85元。报告里会列出所有变量的值VOLUME。最优运输方案可能是A1 - B1: 2吨 A1 - B3: 5吨A2 - B1: 1吨 A2 - B4: 3吨A3 - B2: 6吨 A3 - B4: 3吨 具体数值可能因求解路径不同而有微小差异但总成本85是唯一的除了最优解LINGO还会提供灵敏度分析报告需在LINGO - Options - General Solver - Dual Computations中选择Prices Ranges。这份报告极其重要Reduced Cost缩减成本对于取值为0的变量如A1-B2这条路线运量为0它的Reduced Cost表示该路线的单位运价要降低多少它才会被纳入最优方案。例如如果X12的Reduced Cost是5意味着只有当A1到B2的运价从11降到6以下时这条路线才可能被使用。Dual Price对偶价格对应每个约束条件。对于供应约束它表示该产地供应量增加1个单位总成本会减少多少对于最小化问题通常为负值其绝对值有经济意义。对于需求约束它表示该销地需求量增加1个单位总成本会增加多少。这为管理者进行资源调配提供了量化依据。3. 连续投资问题动态视角下的资金优化现在我们转向连续投资问题。这个问题引入了时间维度决策变得动态和序列化但建模的核心思想依然是线性规划。假设某公司现有100万元资金计划在今后五年内用于项目投资。已知有四个投资项目可供选择项目A从第一年到第四年每年年初都可以投资次年年末回收本金和利润115%。投资额不限。项目B需要在第三年年初投资到第五年年末回收本金和利润125%。投资额不超过40万元。项目C需要在第二年年初投资到第五年年末回收本金和利润140%。投资额不超过30万元。项目D需要在每年年初投资当年年末回收本金和利润106%。投资额不限。公司希望到第五年年末手上拥有的资金总额最大。该如何制定每年的投资计划3.1 时间线梳理与现金流分析面对多期问题画一个时间线并分析每年的现金流收入与支出是至关重要的。我们以“年”为时间点分析每年年初的可投资金和投资决策。决策变量定义 我们需要定义每年年初在各个项目上的投资额。设XA_t: 第t年年初对项目A的投资额 (t1,2,3,4)XB: 第三年年初对项目B的投资额XC: 第二年年初对项目C的投资额XD_t: 第t年年初对项目D的投资额 (t1,2,3,4,5)注意项目B和C在整个规划期内只投资一次所以不需要时间下标。资金流分析核心 我们追踪每年年初的可用资金。设M_t为第t年年初在做出投资决策前拥有的资金总额t1,2,3,4,5。M1 100万元。第一年年初 (t1)资金M1100。可以投资A(XA1)、D(XD1)。投资后资金被占用。第一年年末项目D当年回收回收金额为1.06*XD1。这笔钱将进入第二年年初的可用资金池。项目A、B、C未到期。第二年年初 (t2)可用资金M2来自两部分1) 上年末项目D的回收款1.06*XD12) 可能有的其他到期项目此时没有。所以M2 1.06*XD1。这些资金可以用于投资A(XA2)、C(XC)、D(XD2)。第二年年末项目D回收1.06*XD2。项目A第一年投的到期回收1.15*XA1。第三年年初 (t3)可用资金M3 1.15*XA1 1.06*XD2。可以投资A(XA3)、B(XB)、D(XD3)。第三年年末项目D回收1.06*XD3。项目A第二年投的到期回收1.15*XA2。第四年年初 (t4)可用资金M4 1.15*XA2 1.06*XD3。可以投资A(XA4)、D(XD4)。第四年年末项目D回收1.06*XD4。项目A第三年投的到期回收1.15*XA3。第五年年初 (t5)可用资金M5 1.15*XA3 1.06*XD4。只能投资D(XD5)因为其他项目要么不能投要么投了在第五年末无法回收。第五年年末这是我们的目标时刻。资金总额来自项目D第五年投的回收1.06*XD5项目A第四年投的到期1.15*XA4项目B第三年投的到期1.25*XB项目C第二年投的到期1.40*XC注意第五年年初投资D之后没有其他现金流了我们的目标就是让第五年年末的这个总金额最大。3.2 建立线性规划模型根据上面的分析我们可以列出完整的模型。目标函数最大化第五年年末的总资金。Max Z 1.06*XD5 1.15*XA4 1.25*XB 1.40*XC约束条件每年年初的资金平衡约束最重要每年年初的投资总额不能超过该年年初的可用资金。第一年XA1 XD1 100(M1)第二年XA2 XC XD2 1.06*XD1(M2)第三年XA3 XB XD3 1.15*XA1 1.06*XD2(M3)第四年XA4 XD4 1.15*XA2 1.06*XD3(M4)第五年XD5 1.15*XA3 1.06*XD4(M5)注意这里用了“小于等于”意味着允许资金闲置。如果题目隐含“资金必须全部利用”则应为“等于”。通常投资问题允许闲置因为可能存在投资额度限制。项目投资额上限约束XB 40项目B上限XC 30项目C上限非负约束所有投资额XA_t, XB, XC, XD_t 0。3.3 LINGO实现与求解策略在LINGO中实现这个模型使用集合循环可以写得非常清晰。我们将“年”和“项目”都视为集合。MODEL: SETS: YEAR /1..5/: M; ! M(t)表示第t年年初可用资金; PROJECT /A, B, C, D/; ! 定义投资变量。项目A只在1-4年投B只在第3年C只在第2年D每年都可投。 我们用LINK1表示有投资发生的(年项目)组合; LINK1(YEAR, PROJECT): X, RATE, LIMIT; ENDSETS DATA: ! 初始化第一年资金; M(1) 100; ! 定义投资收益率年末回收/年初投资; ! 格式对于LINK1中的每个(年项目)给出RATE; ! 我们通过初始化全部为0再对特定项赋值的方式; RATE 0; ! 先全部赋0; ! 项目A: 投资后第二年回收收益率为1.15; ! 所以对于第1-4年投资的A其回收发生在下一年末; ! 我们在约束中处理时间关系这里RATE可理解为“项目特性”在目标函数中不一定直接使用; ! 更清晰的做法是直接在目标函数和约束中写系数。这里为了演示集合我们换种方式定义参数; ! 重新设计定义参数 RETURN_A, RETURN_B等; RETURN_A 1.15; RETURN_B 1.25; RETURN_C 1.40; RETURN_D 1.06; ! 投资上限; LIMIT 10000; ! 设一个很大的数表示默认无上限; LIMIT(3, ‘B‘) 40; ! 第三年对B的投资上限40; LIMIT(2, ‘C‘) 30; ! 第二年对C的投资上限30; ENDDATA ! 为了方便我们直接显式定义变量; ! XA1, XA2, XA3, XA4, XB, XC, XD1, XD2, XD3, XD4, XD5; XA1 X(1, ‘A‘); XA2 X(2, ‘A‘); XA3 X(3, ‘A‘); XA4 X(4, ‘A‘); XB X(3, ‘B‘); XC X(2, ‘C‘); XD1 X(1, ‘D‘); XD2 X(2, ‘D‘); XD3 X(3, ‘D‘); XD4 X(4, ‘D‘); XD5 X(5, ‘D‘); ! 目标函数第五年末总资金最大; MAX RETURN_D * XD5 RETURN_A * XA4 RETURN_B * XB RETURN_C * XC; ! 资金流约束每年年初的投资总额 可用资金M(t); ! 第一年; XA1 XD1 M(1); ! 第二年; XA2 XC XD2 RETURN_D * XD1; ! M(2)来自第一年D的回收; ! 第三年; XA3 XB XD3 RETURN_A * XA1 RETURN_D * XD2; ! M(3); ! 第四年; XA4 XD4 RETURN_A * XA2 RETURN_D * XD3; ! M(4); ! 第五年; XD5 RETURN_A * XA3 RETURN_D * XD4; ! M(5); ! 投资上限约束已通过LIMIT在DATA中定义这里直接使用; FOR(LINK1(I, J): X(I, J) LIMIT(I, J)); ! 非负约束LINGO默认; END由于连续投资问题的约束是逐年递推的用集合写循环反而不如直接列出清晰。上面的模型是一种混合写法先定义集合和参数再显式写出约束逻辑更直白。求解这个模型得到最优解。一个可能的解是第一年全部100万投资于项目D (XD1100)。因为项目D当年回报6%可以快速回笼资金为后续投资高收益项目如C和B做准备。第二年年初收到106万。投资C项目30万满额投资A项目0万剩余76万投资D项目 (XD276)。第三年年初收到来自第一年A的回报0万和第二年D的回报1.06*76≈80.56万。投资B项目40万满额投资A项目0万剩余约40.56万投资D项目 (XD3≈40.56)。依此类推... 最终第五年年末的最大总资金约为143.75万元。实操心得解连续投资问题最关键的是画出现金流时间图并准确写出每一期的资金平衡方程。一个常见的错误是忽略资金的时间价值或搞错回收期。建议用M_t表示期初资金然后写出M_t的递推公式再写出投资额不超过M_t的约束这样逻辑最清晰不易出错。4. 模型对比、扩展与LINGO实战技巧通过以上两个案例我们可以看到线性规划模型的强大与通用性。运输问题是典型的静态网络流问题而连续投资问题是多阶段动态决策问题。前者约束矩阵非常稀疏每个变量只出现在两个约束中有特殊的求解算法表上作业法。后者则是一个标准的线性规划但约束条件具有清晰的链式结构。4.1 两类问题的联系与建模思想升华它们的共同点在于都用决策变量描述方案用线性等式或不等式描述限制条件资源、需求、平衡关系用线性函数描述目标成本、收益。这正是线性规划的核心特征。建模的精髓在于“抽象”和“转化”定义好决策变量变量要能完整描述一个可行方案。运输问题中的Xij投资问题中的XA_t, XB...都是如此。抓住核心约束运输问题的核心是“供应运出”、“需求运入”投资问题的核心是“期初资金 当期投资总额”以及资金在时间上的递推关系。准确表达目标目标函数是所有决策变量的线性组合系数就是“价格”或“收益率”。4.2 常见变体与扩展实际问题的约束往往更复杂但模型可以轻松扩展运输问题变体产销不平衡当总供应大于总需求时在需求约束中改为“小于等于”小于时在供应约束中改为“小于等于”。或者在模型中增加虚拟销地/产地将不等式化为等式。有转运点将转运点同时视为“产地”和“销地”增加相应的变量和约束运入量 运出量。有运力限制对某些路线Xij增加上限约束Xij Uij。需求是区间需求在一定范围内波动如D_min 总运入量 D_max。投资问题变体风险约束加入不同项目的风险系数要求总投资风险低于某个阈值。追加投资允许在项目中途追加或撤资。非线性收益收益率可能与投资额有关分段线性或非线性这时可能需要引入整数变量或使用非线性规划。4.3 LINGO高级功能与调试技巧对于更复杂的问题LINGO提供了强大功能整数变量在变量定义后加上GIN(X)表示X为整数BIN(Y)表示Y为0-1变量。例如如果要求运输方案中某条路线必须使用如果使用则运量大于一个下限可以引入0-1变量。分段线性函数使用SLE、SLL等函数处理。查看模型概况求解前使用LINGO - Generate - Display model可以查看LINGO展开后的完整模型检查是否有语法错误或逻辑错误。调试建议从简到繁先建立一个最简单的、已知答案的模型确保代码正确。检查数据DATA部分的数据输入最容易出错特别是多维数组的赋值顺序。理解错误信息LINGO的错误提示通常很直接如“未定义的变量”或“集合索引越界”。利用报告除了最优解一定要看灵敏度报告和求解状态报告是否找到全局最优迭代次数等。4.4 从求解到决策结果的应用与解读得到最优解只是第一步。一个优秀的建模者必须能解读结果背后的含义。对于运输问题除了最优调度方案还应关注哪些路线的运价为0未被使用它们的缩减成本是多少哪些仓库或超市的供应/需求约束的对偶价格最高这能指导你扩大哪个仓库的容量或优先满足哪个超市的需求对降低总成本最有效。对于投资问题最优方案往往具有“脉冲式”投资的特点即资金快速周转到收益率最高的项目。你需要检查在哪些时间点资金是闲置的约束是松的这可能是由于投资额度限制造成的。如果放松项目B或C的投资上限总收益能提升多少这可以通过查看对应约束的对偶价格或直接修改上限重新求解来获得。我个人在多次建模竞赛和实际项目中使用LINGO的体会是它最强大的地方在于将你从繁琐的算法实现中解放出来让你能更专注于问题本身的逻辑建模。但切记“垃圾进垃圾出”。如果模型本身建错了再强大的软件也给不出正确的答案。因此在把模型丢给LINGO之前一定要用简单的小例子或逻辑推理手动验证一下模型的基本正确性。例如在投资问题中你可以假设只投资项目D看看模型计算出的第五年末资金是否等于100 * 1.06^5这是一个很好的完整性检验。最后无论是运输还是投资其建模思想——在约束下优化目标——是运筹学的核心。掌握这种思想并熟练运用LINGO这样的工具将其实现你就能解决一大类现实中的资源分配与优化问题。
