☰
数学建模实战:用LINGO求解运输与投资优化问题
2026/10/31 5:26:43 网站建设 项目流程

1. 项目概述:当数学建模遇上LINGO

最近在带学生做数学建模竞赛的培训,发现很多同学一看到“运输方案优化”或者“连续投资决策”这类题目就有点发怵。题目描述往往是一大堆数据表格,要求你给出成本最低的运输计划,或者未来几年收益最大的投资策略。思路大家其实都有,列方程、设变量嘛,但真到了求解那一步,尤其是变量一多、约束一复杂,手工计算几乎不可能,用通用编程语言(比如Python)从头写算法又容易在调试上耗费大量时间。这时候,一个专业的工具就能让你事半功倍——它就是LINGO。

简单来说,LINGO是一款专门用于求解线性规划、非线性规划、整数规划等优化问题的软件。你可以把它理解为一个“数学规划模型的翻译器和求解器”。你不需要去深究背后的单纯形法、分支定界法是什么原理,你只需要用接近数学语言的格式,把目标函数、决策变量和约束条件“告诉”它,它就能快速给你算出最优解。对于“运输方案”和“连续投资”这类典型的运筹学问题,LINGO几乎是量身定做的解决方案。本文,我就结合这两个经典场景,带你从零开始,掌握用LINGO将数学模型转化为实际解决方案的全过程,并分享一些我多年辅导中总结的、在官方教程里未必会写的实战技巧和避坑指南。

2. 核心思路拆解:从问题到LINGO模型

在打开LINGO软件之前,最关键的一步是把现实问题抽象成一个清晰的数学模型。很多新手会直接跳进软件里敲代码,结果发现模型本身有逻辑漏洞,求解自然失败。我们分别看看这两个问题的建模核心。

2.1 运输问题:成本最小化的网络流

运输问题的背景非常直观:你有若干个工厂(产地),每个工厂有固定的产量(供给量);你也有若干个销售点(销地),每个销售点有固定的需求量。从每个工厂到每个销售点都有单位运输成本。目标是如何安排运输量,在满足所有供给和需求的前提下,让总运输成本最低。

这本质上是一个线性规划问题。它的核心结构非常规整:

  1. 决策变量:通常设为X_ij,表示从工厂i运到销售点j的货物量。
  2. 目标函数:总成本最小化。即,对所有可能的i, j,求单位成本_ij * X_ij的总和。
  3. 约束条件:
    • 供给约束:对于每个工厂i,运往所有销售点的货物总量不能超过其产量。
    • 需求约束:对于每个销售点j,从所有工厂运来的货物总量必须恰好满足其需求量(平衡型运输问题)。
    • 非负约束:运输量不能为负数。

这个模型的妙处在于,它虽然变量多(工厂数×销售点数),但约束和目标函数都是线性的,结构清晰,是LINGO最擅长处理的类型之一。在LINGO中,我们可以利用集合(SETS)功能来优雅地定义这种多维变量和约束,避免为每一个变量和约束都写一行代码。

2.2 连续投资问题:动态决策的跨期优化

连续投资问题,也叫多阶段投资问题,比静态的运输问题多了一个时间维度。典型描述是:你有一笔初始资金,需要在未来若干年(比如5年)内进行投资。市场上有多种投资项目,每种项目每年有特定的投资期限和收益率。每年你会有新的资金流入(如项目到期回收的本息),然后需要重新分配投资。目标是在规划期结束时,让总资金额最大。

这本质上是一个带有时序关系的线性(或非线性)规划问题。其核心在于“动态”和“决策链”:

  1. 决策变量:通常设为X_it,表示在第t年,投入到项目i的资金量。
  2. 目标函数:最终时刻(如第5年末)的总资金最大化。
  3. 约束条件(这是关键和难点):
    • 资金流平衡约束(最核心):每年可用于投资的资金总额,等于上年末回收的资金加上本年可能的外来资金。这建立起了年份之间的连接。例如,第2年的投资总额,等于第1年那些为期1年的项目回收的本息和。
    • 项目投资限额约束:每个项目每年可能有投资上限。
    • 非负约束:投资额不能为负数。
    • 逻辑约束:如果项目期限大于1年,那么一旦投资,在到期前资金是被锁定的,中间年份不能再对其追加或减少投资。这通常需要通过定义变量或附加约束来实现。

注意:连续投资问题的模型建立比运输问题更考验逻辑严密性。一个常见的错误是忽略了资金的时间锁定效应,或者写错了资金平衡方程的顺序,导致模型求解结果违背常识(比如第一年就把所有钱投到一个长期项目,后面几年无钱可投)。在LINGO中实现时,需要仔细定义集合(尤其是时间集),并利用@FOR循环来精确表达这些跨期约束。

3. LINGO实战:运输问题建模与求解

我们用一个具体例子来上手。假设有3个工厂A1, A2, A3,产量分别是7吨、4吨、9吨;4个销售点B1, B2, B3, B4,需求量分别是3吨、6吨、5吨、6吨。单位运价表(元/吨)如下:

从\到B1B2B3B4
A1311310
A21928
A374105

3.1 LINGO模型代码详解

打开LINGO,在模型窗口输入以下代码。我会逐段解释:

MODEL: ! 1. 定义集合; SETS: factory /A1, A2, A3/: capacity; ! 工厂集合,属性为产能capacity; market /B1, B2, B3, B4/: demand; ! 市场集合,属性为需求demand; link(factory, market): cost, volume; ! 派生集合,表示工厂到市场的路线,属性为单位成本cost和运量volume; ENDSETS ! 2. 输入数据; DATA: capacity = 7 4 9; ! 工厂产能; demand = 3 6 5 6; ! 市场需求; cost = 3 11 3 10 ! 成本矩阵,按行排列: A1->B1,B2,B3,B4; 1 9 2 8 ! A2->B1,B2,B3,B4; 7 4 10 5; ! A3->B1,B2,B3,B4; ENDDATA ! 3. 定义目标函数:最小化总运输成本; MIN = @SUM(link(i, j): cost(i, j) * volume(i, j)); ! 4. 定义约束条件; ! 供给约束:每个工厂运出的总量不超过其产能; @FOR(factory(i): @SUM(market(j): volume(i, j)) <= capacity(i) ); ! 需求约束:每个市场运入的总量等于其需求; @FOR(market(j): @SUM(factory(i): volume(i, j)) = demand(j) ); ! 5. 变量范围约束(非负)已在LINGO中默认,如需特殊限制可在此添加; END

关键点解析:

  • 集合定义:这是LINGO建模的精髓。link(factory, market)自动生成了所有工厂和市场的组合(3x4=12条路线),我们无需手动定义12个变量。cost和volume是附着在这些路线上的属性。
  • 数据输入:DATA部分用于赋值。注意cost矩阵的输入是按行排列的,顺序必须与link集合中元素的生成顺序一致(通常是先遍历第一个集合的所有元素,再遍历第二个)。这是新手最容易出错的地方之一。
  • 目标与约束:@SUM和@FOR是LINGO的循环求和与循环定义函数。@FOR(factory(i): ...)意味着为factory集合中的每一个成员i(即A1, A2, A3)都生成一个后面的约束不等式。这样,三行供给约束就通过一行代码生成了。

3.2 求解与结果分析

输入代码后,点击工具栏的“求解”按钮(靶心图标)。LINGO会调用内置求解器进行计算。

  1. 求解状态报告:求解完成后,会弹出对话框。你应该看到“Global optimal solution found.”(找到全局最优解),这表明求解成功。同时会显示目标函数值(最小总成本)和迭代次数。
  2. 查看结果:关闭报告窗口,回到模型界面。选择菜单LINGO -> Solution,或直接查看状态窗口下方的“Solution Report”标签页。这里会列出所有变量的最优值。 对于本例,最优解(volume变量)大致如下:
    • A1->B3: 5吨, A1->B4: 2吨
    • A2->B1: 3吨, A2->B3: 0吨, A2->B4: 1吨
    • A3->B2: 6吨, A3->B4: 3吨 (其他路线运量为0) 最小总成本为86元。
  3. 灵敏度分析(可选但重要):对于线性规划,LINGO还可以提供灵敏度分析报告(LINGO -> Range)。这份报告会告诉你每个约束条件的“影子价格”和每个变量目标函数系数的“允许变化范围”。例如,某个工厂产能约束的影子价格是5,意味着该工厂产能每增加1单位,总成本最多能减少5元。这在商业决策中极具价值。

实操心得:第一次运行时,如果遇到“No feasible solution found”(无可行解),大概率是约束条件互相矛盾。比如总产能(7+4+9=20)小于总需求(3+6+5+6=20),本例是平衡的。如果不平衡(产能>需求),供给约束应改为<=;如果产能<需求,则问题无解,或需求约束需改为<=并修改目标。务必先手算验证一下总量关系。

4. LINGO进阶:连续投资问题建模

我们看一个简化版的5年投资问题。假设有3个项目可供投资:

  • 项目A:每年初投资,年末回收本利,年利率10%。
  • 项目B:每年初投资,需要两年(跨两个年度),到期后回收本利,年利率40%。
  • 项目C:每年初投资,需要三年(跨三个年度),到期后回收本利,年利率80%。 期初有10万元资金,每年末的项目回收款可以用于下一年初再投资。如何分配每年的投资,使第5年末的本利总额最大?

建模思路:我们需要定义5个时间点(年初)。决策变量Xij表示在第i年年初,投入到项目j的资金量(j=1,2,3对应A,B,C)。关键在于理清每年年初的可投资资金流。

4.1 LINGO模型代码

MODEL: SETS: year /1..5/:; ! 定义5个年份; project /A, B, C/:; ! 定义3个项目; invest(year, project): X; ! 决策变量:X(i,j)表示第i年初投给项目j的钱; ENDSETS DATA: ! 利率参数; rate_A = 0.10; rate_B = 0.40; rate_C = 0.80; ! 项目期限(年); term_A = 1; term_B = 2; term_C = 3; ENDDATA ! 初始资金; INIT: capital = 100000; ENDINIT ! 目标函数:最大化第五年末的总资金; MAX = F; ! 定义每年的资金流平衡; ! 第1年初:可投资资金就是初始资金; @SUM(project(j): X(1, j)) = capital; ! 第2年初:可投资资金 = 第1年项目A到期回收的本利; @SUM(project(j): X(2, j)) = (1+rate_A) * X(1, 'A'); ! 第3年初:可投资资金 = 第2年项目A到期回收的本利 + 第1年项目B到期回收的本利; @SUM(project(j): X(3, j)) = (1+rate_A) * X(2, 'A') + (1+rate_B) * X(1, 'B'); ! 第4年初:可投资资金 = 第3年项目A到期回收的本利 + 第2年项目B到期回收的本利 + 第1年项目C到期回收的本利; @SUM(project(j): X(4, j)) = (1+rate_A) * X(3, 'A') + (1+rate_B) * X(2, 'B') + (1+rate_C) * X(1, 'C'); ! 第5年初:可投资资金 = 第4年项目A到期回收的本利 + 第3年项目B到期回收的本利 + 第2年项目C到期回收的本利; @SUM(project(j): X(5, j)) = (1+rate_A) * X(4, 'A') + (1+rate_B) * X(3, 'B') + (1+rate_C) * X(2, 'C'); ! 第5年末的总资金F:等于第5年所有投资到期回收的本利和; F = (1+rate_A) * X(5, 'A') + (1+rate_B) * X(4, 'B') + (1+rate_C) * X(3, 'C'); ! 非负约束; @FOR(invest(i, j): X(i, j) >= 0); END

4.2 模型难点与技巧

这个模型比运输问题复杂,体现在:

  1. 资金流方程的构建:每年初的投资总额必须等于上年末回收的资金总额。回收的资金来自之前年份投资的、且刚好在本年年初到期的项目。必须仔细追踪每个项目的投资年份和到期年份。我上面用注释详细列出了每笔钱的来源,建议在纸上画一个时间线图来辅助理解。
  2. 目标函数的定义:最终财富F不是简单地加总所有投资,而是第五年末时,所有已到期投资的本利和。即,第五年初的投资中,只有项目A能在当年末收回;第四年初投的B项目、第三年初投的C项目也会在第五年末到期。
  3. 使用INIT部分:对于初始资金这种在模型开始前就确定的常量,可以用INIT部分来初始化,使模型更清晰。

求解结果分析:求解此模型,你会得到每年在各个项目上的最优投资分配X(i,j),以及最终的最大本利和F。你会发现,尽管项目C的利率最高,但因为它锁定期长(3年),所以并不会把所有钱都投给它。最优策略是在前期利用C和B的高收益进行积累,后期则更注重资金的流动性(多投A),以确保最后一年有足够的到期资金来贡献给最终收益F。这个动态权衡的过程,正是连续投资问题的精髓。

踩坑提醒:在写资金平衡方程时,极易出现“时间错配”。例如,把X(1,'B')的回收算到第2年初(实际上它需要2年,应在第3年初回收)。一个有效的检查方法是:对于任意X(i, j),其回收年份一定是i + term_j。在LINGO中,你可以尝试用更复杂的集合和@SUM函数来生成这些约束,以增强模型的通用性(比如年份数变化时无需重写方程),但这需要更熟练的集合操作。初学时,像上面这样“笨拙”地明确写出每个方程,反而更不容易出错,也便于调试。

5. 常见问题、调试技巧与模型优化

即使模型逻辑正确,在LINGO中也可能遇到各种问题。以下是一些常见情况及处理办法。

5.1 常见错误与排查

错误类型可能原因排查与解决方法
No feasible solution found1. 约束条件相互矛盾。
2. 变量上下限设置过紧,无解空间。
3. 初始值设置不当,导致求解器误判。
1.检查总量平衡:如运输问题中总供给与总需求关系。
2.简化模型:先注释掉部分约束,看是否能得到可行解,逐步添加以定位矛盾点。
3. 检查@BND或变量赋值是否合理。
Unbounded solution目标函数可以在不违反约束的情况下无限优化(如利润无限大)。这通常是模型构建错误。检查是否遗漏了关键的资源限制约束。例如在投资问题中,是否忘记了每年投资总额不能超过可用资金这一根本约束。
求解时间过长或内存不足1. 问题规模太大(变量/约束过多)。
2. 模型是非线性或整数规划,本身复杂。
1. 尝试使用LINGO -> Options中的求解器选项,如调整迭代次数、收敛精度。
2. 对于整数规划,可以设置启发式搜索或调整分支策略。
3. 考虑是否能用线性规划近似,或者分解问题。
结果与预期不符1. 目标函数系数(如成本、利率)输入错误。
2. 约束条件符号(<=,=,>=)用错。
3. 单位不统一。
1.仔细核对DATA部分,特别是矩阵数据的排列顺序。
2.逐行检查约束,理解其经济/物理意义。
3. 用LINGO -> Picture功能可视化模型结构,检查行、列对应关系。

5.2 LINGO编程与调试技巧

  1. 多用注释:使用!添加详细注释,说明每个集合、变量、约束的含义。这对于复杂的、几天后再回顾的模型至关重要。
  2. 模块化建模:对于大型模型,可以分块编写。先定义集合和数据,再写目标函数,然后分模块写约束(如供给约束块、需求约束块、资金平衡块)。每写完一个模块,可以暂时注释掉其他部分,单独测试其语法是否正确。
  3. 利用@FOR和@SUM简化代码:这是LINGO的核心优势。对于规律性强的约束,一定要用循环语句,避免手动罗列。例如,如果我们的投资问题有10年、10个项目,手动写资金平衡方程将是灾难。此时应该定义“项目期限”属性,然后利用@SUM函数和条件判断来动态生成回收项。
    ! 假设years是年份集合,projects是项目集合,term(p)是项目p的期限; @FOR(years(t) | t #GT# 1: ! 从第2年开始; @SUM(projects(p): X(t, p)) = @SUM(projects(p): (1+rate(p)) * X(t - term(p), p)) ! 注意:这里需要确保 t-term(p) >=1,否则会引用不存在的变量,需用@IF函数处理; );
  4. 查看模型概况:求解前,点击LINGO -> Generate -> Display model,可以查看LINGO将你的集合语言展开后的完整线性规划模型。这是验证模型是否按你设想的方式构建的终极手段。
  5. 从简单案例开始:不要一开始就构建复杂的模型。先用2个工厂、2个市场,或者2年、2个项目的小数据测试模型。用手算能验证结果,确认模型逻辑无误后,再替换成真实的大规模数据。

5.3 模型扩展与优化思路

掌握了基础模型后,你可以应对更复杂的情况:

  • 运输问题的变种:
    • 产销不平衡:修改供给或需求约束的等号为不等号。
    • 带有中转站:引入新的节点集合(中转站),变量变为X_ik(工厂到中转站) 和Y_kj(中转站到市场),并增加中转站的流量平衡约束。
    • 多商品运输:为每种商品增加一个维度,使用三维数组X_ijk(工厂i, 市场j, 商品k)。
  • 投资问题的变种:
    • 风险约束:引入风险系数,增加组合风险不超过某阈值的约束。
    • 借贷可能:引入“借款”变量,利率不同,在资金平衡方程中增加借款流入和还款流出项。
    • 非线性收益:如果收益率与投资额有关(如规模效应),则目标函数变为非线性,需要在LINGO中选用非线性求解器(LINGO -> Options -> Solver中选择Global或Multistart)。

最后,我个人在数学建模竞赛培训和实际项目中最深的体会是:LINGO这类工具的强大,永远建立在你对问题本质的深刻理解之上。软件只是帮你做计算,而建模才是真正的思考。花在清晰定义变量、梳理约束关系上的时间,远比调试错误代码的时间更有价值。当你拿到一个复杂问题,不妨先问自己:决策是什么?目标是什么?限制条件有哪些?它们之间如何用数学语言关联?把这些想明白了,翻译成LINGO代码就是水到渠成的事。开始的时候可能会觉得语法生疏,但就像学任何语言一样,多写几个完整的模型,你就能越来越熟练地用它来表达你的优化思想了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询