尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从经典赛题到Lingo实战:整数规划建模与两辆平板车装货问题详解

从经典赛题到Lingo实战:整数规划建模与两辆平板车装货问题详解 1. 问题背景与核心挑战从一道经典赛题说起最近在整理一些老资料翻到了1988年美国大学生数学建模竞赛MCM的B题题目是关于两辆平板车的装货问题。这道题可以说是运筹学和优化建模领域的“Hello World”级经典案例很多朋友最早接触Lingo、Lindo这类专业优化软件可能就是从这个题目开始的。题目描述并不复杂你有若干种不同尺寸的包装箱需要装到两辆平板车上每辆车有长度限制每种箱子有特定的数量目标是在不超过车辆长度限制的前提下尽可能把箱子都装上车或者在某些变体问题中是最大化装载的总价值或满足特定的装载顺序要求。别看描述简单这题目背后涉及的建模思想、求解技巧以及从手动计算到软件求解的思维转变对今天处理物流调度、资源分配、生产排程等问题依然有很强的借鉴意义。我当年第一次啃这道题时就被它从“人脑枚举”到“模型表达”再到“软件求解”的完整链条给吸引住了。这不仅仅是写几个方程而是教你如何把一个模糊的实际问题精确地翻译成数学语言并交给计算机去高效求解。今天我就结合这道经典赛题把使用Lingo软件建立并求解整数规划模型的全过程包括一些新手极易踩坑的细节和模型调试的心得完整地梳理一遍。无论你是正在备战数模竞赛的学生还是工作中需要处理类似优化问题的工程师相信这篇内容都能给你带来直接的帮助。2. 问题拆解与数学模型构建把现实装进数学公式面对“两辆平板车装货”这样的问题第一步也是最关键的一步就是进行问题拆解和数学抽象。我们不能一上来就打开Lingo敲代码必须先想清楚模型的每一个组成部分。2.1 定义决策变量问题的“开关”决策变量是我们模型中可以控制的部分。对于这道题最直观的想法是每种箱子在每辆车上装多少个因此我们可以定义如下决策变量 设我们有I种包装箱用i表示i 1, 2, ..., I。 我们有J辆平板车通常J2用j表示j 1, 2。 那么决策变量x_ij就可以定义为第i种包装箱装载到第j辆平板车上的数量。 这里有一个非常重要的点x_ij必须是整数因为你不能装半个箱子。这直接决定了我们的模型属于整数规划而不是更简单的线性规划。2.2 明确目标函数我们想要什么原题可能有不同的目标。最常见的一种是在满足车辆长度限制的前提下尽可能装载更多种类的箱子或者装载所有箱子。但“尽可能多”是一个模糊概念在数学上我们需要一个可量化的目标。 一种更精确、也更经典的设定是最大化两辆平板车的总装载价值假设每种箱子有不同的价值。或者如果目标是装载所有箱子那么我们可以把“是否装载所有箱子”作为一个约束条件而目标函数可以是最小化两辆车的总长度冗余或者简单地设定为一个常数因为目标就是满足约束这称为“可行性问题”。 为了更具一般性我们这里采用“最大化总装载价值”作为目标。假设第i种箱子的单位价值是v_i那么目标函数就是Maximize Z Σ_i Σ_j (v_i * x_ij)即对所有箱子种类i和所有车辆j求和计算装载的价值总和并使其最大。2.3 梳理约束条件现实的“枷锁”这是建模的核心任何疏漏都会导致模型失效。车辆长度约束这是最硬的约束。设第i种箱子的长度为l_i第j辆车的可用长度为L_j。那么对于每一辆车j其装载的所有箱子的总长度不能超过它的可用长度Σ_i (l_i * x_ij) ≤ L_j, for all j箱子供应量约束每种箱子的装载总数不能超过其可用数量。设第i种箱子的总数量为s_i。那么对于每一种箱子i在两辆车上的装载总数不能超过s_iΣ_j x_ij ≤ s_i, for all i非负整数约束决策变量必须是非负整数。x_ij ≥ 0 and integer, for all i, j2.4 一个具体的数值例子为了后续在Lingo中演示我们构造一个简单的例子。假设有3种箱子I32辆车J2。箱子数据类型1长度l1 5米价值v1 8数量s1 4类型2长度l2 3米价值v2 5数量s2 6类型3长度l3 4米价值v3 6数量s3 5车辆数据车1可用长度L1 20米车2可用长度L2 18米我们的模型就具体化为目标Max Z 8*(x11x12) 5*(x21x22) 6*(x31x32)约束车1长度5x11 3x21 4*x31 ≤ 20车2长度5x12 3x22 4*x32 ≤ 18箱子1总量x11 x12 ≤ 4箱子2总量x21 x22 ≤ 6箱子3总量x31 x32 ≤ 5所有 x_ij 为非负整数。现在数学模型已经准备就绪。接下来就是如何让Lingo理解并求解这个模型。3. Lingo模型实现从公式到可执行代码有了清晰的数学模型用Lingo实现就相对直接了。但“实现”不等于“简单输入”里面的语法细节和结构安排直接影响着模型的易读性和可维护性。下面我以刚才构造的例子展示一个结构清晰的Lingo模型文件。3.1 Lingo模型的基本结构一个完整的Lingo模型通常包含以下几个部分我建议按此顺序编写养成好习惯集合段定义问题的维度有哪些种类、哪些车辆。数据段输入具体的参数值长度、价值、数量、容量。变量段定义决策变量。目标函数与约束段描述优化目标和限制条件。初始段可选为变量提供初始值有时能加快求解速度。3.2 逐段代码实现与解析我们打开Lingo软件新建一个模型文件.lg4。! ********************************************* ! 两辆平板车装货问题 - Lingo模型 ! 基于MCM1988 B题简化示例 ! ********************************************* ! 1. 集合段 - 定义索引 SETS: BOX /1..3/: length, value, supply; ! 定义了一个名为BOX的集合有3个成员(1,2,3)。 ! 每个成员有三个属性length(长度), value(价值), supply(供应量)。 TRUCK /1..2/: capacity; ! 定义了一个名为TRUCK的集合有2个成员(1,2)。 ! 每个成员有一个属性capacity(容量/可用长度)。 LINK(BOX, TRUCK): x; ! 定义了一个派生集合LINK它是BOX和TRUCK的笛卡尔积。 ! 即它包含了所有(箱子类型车辆)的组合如(1,1),(1,2),(2,1)等。 ! 并为此集合定义了一个属性x这就是我们的决策变量。 ENDSETS ! 2. 数据段 - 输入具体参数 DATA: ! 箱子数据长度(米), 价值, 供应量 length 5, 3, 4; value 8, 5, 6; supply 4, 6, 5; ! 车辆数据容量/可用长度(米) capacity 20, 18; ENDDATA ! 3. 目标函数与约束段 ! 目标最大化总装载价值 MAX SUM(LINK(i,j): value(i) * x(i,j)); ! SUM是求和函数。这里对LINK集合中的所有元素(i,j)求和。 ! 对于每一对(i,j)贡献的价值是 value(i) * x(i,j)。 ! 约束条件 ! 3.1 每辆车的长度约束 FOR(TRUCK(j): SUM(BOX(i): length(i) * x(i,j)) capacity(j) ); ! FOR是循环函数。对每一辆车j生成一个约束。 ! 约束内容是对于这辆车j所有箱子i的长度乘以其装载量x(i,j)的总和必须小于等于该车的容量capacity(j)。 ! 3.2 每种箱子的供应量约束 FOR(BOX(i): SUM(TRUCK(j): x(i,j)) supply(i) ); ! 对每一种箱子i生成一个约束。 ! 约束内容是这种箱子i在所有车辆j上的装载量总和必须小于等于其供应量supply(i)。 ! 3.3 变量类型约束整数、非负 FOR(LINK(i,j): GIN(x(i,j))); ! GIN 表示变量为广义整数即整数 FOR(LINK(i,j): x(i,j) 0); ! 非负约束3.3 关键语法与函数解释! 表示注释Lingo会忽略其后的内容。良好的注释是模型可读性的关键。SETS: ... ENDSETS 定义集合及其属性的区块。这是Lingo建模的灵魂它让你能用抽象的符号i, j来书写模型而不是具体的数字1,2,3模型因此具备了通用性。DATA: ... ENDDATA 数据输入区块。将模型与数据分离是优秀建模实践。同一个模型结构换一套数据就能解决一个新问题。SUM(集合(索引): 表达式) 在指定集合上对表达式求和。FOR(集合(索引): 约束或表达式) 为集合中的每个成员生成对应的约束或计算表达式。它实现了约束的“批量生成”。GIN(x) 指定变量x为整数变量。如果没有这一行Lingo会默认变量为连续变量求出的解可能是小数这显然不符合“装几个箱子”的现实。 小于等于。在Lingo中不被支持必须用。注意很多新手会忘记写GIN来声明整数变量结果求出一个“装了几个半箱子”的荒谬解。这是整数规划建模中最常见的错误之一。另外FOR循环的括号和冒号使用要仔细语法错误是初期调试的主要障碍。写好模型后点击工具栏的“求解”按钮那个靶心图标Lingo就会开始工作。对于这个小规模问题几乎是瞬间就能得到结果。4. 结果解读与方案分析看懂Lingo的输出报告点击求解后Lingo会弹出一个求解状态窗口并自动生成一份详细的报告。读懂这份报告是验证模型正确性和理解解决方案的关键。报告主要分为几大块4.1 求解状态摘要首先会看到类似这样的信息Global optimal solution found. Objective value: 86.00000 Objective bound: 86.00000 Infeasibilities: 0.000000 Extended solver steps: 0 Total solver iterations: 12Global optimal solution found. 这是最理想的状态表示找到了全局最优解。对于线性整数规划Lingo通常能找到全局最优。Objective value: 86.00000 这就是我们目标函数的最大值即最大总装载价值为86。Infeasibilities: 0.000000 不可行性为0表示所有约束都得到了严格满足。Total solver iterations: 12 求解器迭代了12次。这个数字越小通常意味着问题越简单求解越快。4.2 决策变量值报告这是报告的核心部分告诉我们具体怎么装车。报告会列出所有变量的值非零变量会显示出来。在我们的例子中你可能会看到Variable Value Reduced Cost X( 1, 1) 4.000000 0.000000 X( 2, 1) 0.000000 0.000000 X( 3, 1) 0.000000 0.000000 X( 1, 2) 0.000000 0.000000 X( 2, 2) 6.000000 0.000000 X( 3, 2) 0.000000 0.000000注实际解可能不同这里仅为示例格式我们需要把它翻译成人类语言X(1,1)4 把4个类型1的箱子装到车1上。X(2,2)6 把6个类型2的箱子装到车2上。其他X(i,j)0 不装。那么装载方案就是车1装载4个类型1的箱子。总长度 4 * 5 20米刚好用满容量。价值 4 * 8 32。车2装载6个类型2的箱子。总长度 6 * 3 18米刚好用满容量。价值 6 * 5 30。类型3的箱子没有装载。总价值 32 30 62。注意这个62是我这个示例解的值前面报告的86是另一个可能的最优解值这里用62是为了说明解读过程4.3 约束松弛与对偶价格报告还会显示每个约束的“松弛/剩余变量”和“对偶价格”。松弛变量 对于“≤”约束松弛变量表示约束的“宽松”程度。如果约束是“总长度≤20”实际总长度用了18那么松弛变量就是2。如果为0则表示该约束是“紧的”资源被完全利用。在我们的解里两个车的长度约束松弛变量很可能都是0表示车装满了。对偶价格 这是一个非常重要的经济解释。它表示该约束右端常数资源每增加一个单位目标函数最优值能改善多少。例如如果车1容量约束的对偶价格是2.5那么如果车1能加长1米总价值最多能增加2.5。这为决策提供了边际效益信息。但请注意对偶价格只在约束为“紧”且最优基不变的一个小范围内有效。4.4 方案可行性验证拿到变量值后一定要手动验证一下是否满足所有约束车1长度54 30 4*0 20 ≤ 20满足。车2长度50 36 4*0 18 ≤ 18满足。箱子1总量4 0 4 ≤ 4满足。箱子2总量0 6 6 ≤ 6满足。箱子3总量0 0 0 ≤ 5满足。 所有约束通过方案可行。5. 模型扩展与实战技巧超越基础题目经典的平板车问题只是一个起点。在实际的科研或工程中问题会复杂得多。掌握如何扩展基础模型是真正掌握建模能力的体现。下面分享几个常见的扩展方向及在Lingo中的实现技巧。5.1 扩展一考虑箱子的装载顺序与稳定性原题只考虑了空间没考虑装载的物理可行性。现实中箱子有重量需要保证车辆重心平衡或者大箱子不能压在小箱子上。重心约束假设每辆车有一个重心位置范围如中点前后1米。我们可以为每种箱子定义一个重心位置通常就是其几何中心。那么需要增加约束所有已装箱子的总力矩重量*重心到某参考点的距离必须在允许范围内。这会在模型中加入新的线性约束。装载顺序/稳定性这通常需要引入0-1变量来建模。例如定义y_ijk为0-1变量表示箱子i是否在箱子k的上方或前方。这会引入大量的新变量和约束如“如果y_ijk1则箱子i的下方必须有足够支撑”模型会迅速从纯整数规划变为更复杂的混合整数规划求解难度激增。对于复杂顺序问题有时需要结合启发式算法。5.2 扩展二多目标优化我们之前只最大化价值。但现实中可能有多重目标既要价值高又要车辆利用率高空余少还要装载的箱子种类多。处理方法Lingo本身对多目标优化的直接支持有限。常用方法有主目标法将一个最重要的目标作为目标函数将其他目标转化为约束。例如“在总价值不低于V_min的前提下最小化总空余长度”。加权求和法给每个目标分配一个权重合并成一个单一目标。例如新目标 w1总价值 - w2总空余长度。权重的选择需要谨慎不同权重会导致完全不同的最优解。分层序列法先优化第一重要的目标得到最优值Z1*。然后以“目标函数值不低于αZ1”α接近1为约束去优化第二目标以此类推。这可以在Lingo中通过多次求解来实现。5.3 扩展三不确定性与随机规划原题数据是确定的。但现实中箱子的尺寸、数量甚至车辆可用长度都可能存在误差或波动。处理方法这进入了随机规划或鲁棒优化的领域。Lingo的高级版本支持一些随机编程功能但更通用的做法是情景分析预设几种可能的数据情景如“箱子尺寸偏大”、“车辆容量减少”分别求解观察方案的稳定性。鲁棒优化在约束中引入“安全余量”。例如原本约束是Σ l_i * x_ij ≤ L_j可以改为Σ l_i * x_ij Γ * σ_ij ≤ L_j其中σ_ij是长度不确定性的度量Γ是控制保守程度的参数。这需要你对不确定性有量化估计。5.4 Lingo实战调试技巧从小开始逐步复杂不要一开始就写完整的复杂模型。先用极小的数据如2种箱子1辆车测试模型骨架是否正确能否求出可行解。再逐步增加数据规模和约束复杂度。善用FILE和OLE函数读取外部数据当数据量很大时把数据写在DATA段里很麻烦。可以使用FILE(‘filename.txt’)从文本文件读取或OLE(‘filename.xlsx’)从Excel读取。这能极大提高模型的可维护性。DATA: length FILE(data.txt); ! 假设data.txt文件中有一行数据5, 3, 4 ENDDATA理解“不可行”和“无界”如果Lingo报告“No feasible solution found”说明约束条件互相矛盾没有解。你需要检查约束是否过严或者数据是否有误。可以使用Lingo的“调试”功能找出矛盾的约束组。如果报告“Unbounded solution”通常意味着目标函数缺少必要的约束可以无限变大或变小。检查是否漏掉了关键的资源限制约束。整数规划求解耐心对于大规模整数规划问题求解时间可能很长。可以设置求解时间限制或最优间隙容忍度。在Lingo的Options-Solver中可以设置Time Limitation和Relative Optimality Tolerance。比如设置0.05的容忍度意味着求解器找到的解与理论最优解的差距在5%以内时就可以停止这能大幅缩短求解时间适合对精度要求不极致的场景。模型文档化在模型文件中用!写详细的注释说明每个集合、参数、变量、约束的实际意义。几个月后回头看或者交给别人看清晰的注释能省下大量重新理解的时间。6. 从Lingo到通用编程语言的思路迁移虽然Lingo在建模和求解中小型优化问题上非常高效直观但在处理超大规模问题、需要与其他系统集成、或部署到生产环境时我们可能需要使用通用编程语言如Python调用专业的优化库如PuLP, OR-Tools, Gurobi, CPLEX等。理解其间的思路迁移至关重要。6.1 核心概念的对应关系Lingo中的概念在编程式建模中都有直接对应集合-数组/列表的索引。SETS: BOX /1..3/对应Python中的box_indices [0, 1, 2]或range(3)。属性-字典或数组。length, value, supply对应Python字典length {0:5, 1:3, 2:4}或简单列表length [5, 3, 4]。派生集合-嵌套循环或笛卡尔积。LINK(BOX, TRUCK)对应for i in box_indices: for j in truck_indices:。SUM-求和函数或循环累加。FOR-循环语句。GIN-设置变量类型为整数。6.2 一个Python PuLP的示例使用Python的PuLP库来实现同一个问题代码会呈现出另一种风格但内核完全一致。# 导入PuLP库 from pulp import LpProblem, LpMaximize, LpVariable, lpSum, LpStatus, value # 1. 定义问题 prob LpProblem(Two_Truck_Loading_Problem, LpMaximize) # 2. 定义数据与Lingo数据段对应 box_length [5, 3, 4] box_value [8, 5, 6] box_supply [4, 6, 5] truck_capacity [20, 18] num_boxes len(box_length) num_trucks len(truck_capacity) # 3. 定义决策变量与Lingo变量段对应 # 变量名格式 x[i][j] 低边界为0 类型为整数 x {} for i in range(num_boxes): for j in range(num_trucks): x[(i, j)] LpVariable(fx_{i}_{j}, lowBound0, catInteger) # 4. 定义目标函数 prob lpSum([box_value[i] * x[(i, j)] for i in range(num_boxes) for j in range(num_trucks)]) # 5. 定义约束条件 # 5.1 车辆长度约束 for j in range(num_trucks): prob lpSum([box_length[i] * x[(i, j)] for i in range(num_boxes)]) truck_capacity[j] # 5.2 箱子供应量约束 for i in range(num_boxes): prob lpSum([x[(i, j)] for j in range(num_trucks)]) box_supply[i] # 6. 求解问题 prob.solve() # 7. 打印结果 print(f求解状态: {LpStatus[prob.status]}) print(f最大总价值: {value(prob.objective)}) # 打印装载方案 for i in range(num_boxes): for j in range(num_trucks): if value(x[(i, j)]) 0: print(f箱子类型{i1} 装到 车{j1} 的数量: {value(x[(i, j)])})运行这段Python代码你会得到和Lingo求解相同的结果。这种方式的优势在于灵活性可以方便地使用循环、条件判断等编程逻辑来构建更复杂的模型。集成性可以轻松地从数据库、API或文件中读取数据并将结果写回。可扩展性可以接入更强大、更专业的商业求解器如Gurobi, CPLEX作为后端处理百万级变量的问题。6.3 选择工具的建议快速原型、教学、中小型问题Lingo/Lindo是绝佳选择。它的建模语言非常直观接近数学公式调试和验证模型速度快。大规模问题、复杂逻辑、系统集成使用Python/Java/C等 优化库如OR-Tools, Gurobi, CPLEX。这些工业级求解器性能更强且能无缝嵌入到你的应用流水线中。特定问题类型如果是纯粹的车辆路径问题VRP可能有更专业的软件或库如VRP Spreadsheet Solver, jsprit等。理解Lingo建模的本质就是理解如何用数学语言描述世界。这个能力是通用的无论你未来使用什么工具其核心——定义变量、确定目标、列出约束——都不会改变。从Lingo这个“训练场”出发掌握这套思维框架再面对任何优化挑战时你都能找到清晰的破解路径。
返回列表