资讯详情

资讯详情

矿石加工质量控制:配矿优化与线性规划实战解析

简介这是一份2022年五一杯数学建模竞赛B题“矿石加工质量控制问题”的完整参赛作品面向数学建模竞赛备赛学生及需要矿石加工预测建模参考的研究者。资源包为单个PDF文档共1个文件大小约1.49MB论文附录内嵌代码与数据便于对照复现。论文以XGBoost模型、贝叶斯优化模型与回归插补法为主线完整覆盖原矿参数与系统温度场景下的产品质量预测、系统设定温度估计、矿石合格率预测及灵敏度分析四个核心问题给出了关键公式、建模步骤与结果解读。目前已有4623人浏览学习。对于希望学习梯度提升集成建模、贝叶斯超参调优或数据处理插补技巧的读者这份作品提供了可落地的参考范例也可用于期末选课结业或同类赛题的拓展研究。1. 2022年五一赛B题在考什么矿石加工质量控制问题的本质拿到2022年五一赛B题“矿石加工质量控制问题”第一眼看到的不该是附录里那堆代码和数据而是一个被很多参赛队低估的事实这道题考的是“在波动中做决策”不是“预测”。矿石从破碎、磨矿到选别中间任何一道工序的品位波动都会传导到最终产品而质量控制的核心动作不是事后检测而是在源头把多种矿石、多个批次按比例搭配让混合后的品位稳定落在目标区间内。把这件事翻译成数学语言就是一个带品位约束的优化问题。本文会顺着这条线把建模思路、附录代码的复现方式、指标权衡和常见翻车点完整拆开。适合正在备赛的建模选手、刚接触过程优化的工程师以及想用数据驱动质量管理的从业者。2. 把“矿石质量控制”翻译成优化问题建模思路与决策变量2.1 从配矿到品位约束控制质量的完整逻辑链条矿石加工质量问题的现场逻辑可以压缩成三个环节来料、配比、选别。来料是多个矿点或多个批次的矿石每一批的铁品位、杂质含量、成本都不一样配比是决定每种矿石用多少这一步直接决定进入磨机的混合矿品位选别是后续工艺对混合矿品位波动非常敏感品位偏高浪费成本偏低废品率上升。所以赛题里真正能被建模拿住的是“配比”环节。它的数学结构很漂亮决策变量是配矿比例约束是混合后品位必须落在工艺允许的上下限之间目标是成本最小或综合效益最大。线性关系成立因为混合矿的品位是各组分品位的加权平均不需要引入复杂的动力学方程。这也是为什么我建议拿到题先做配矿优化而不是一上来就训练神经网络——赛题给了“代码数据在附录”如果附录数据里是各批次的品位与成本字段线性规划就是最稳、最快、最好解释的起点。2.2 目标函数与约束的写法把生产KPI变成数学表达式先约定记号。假设有 n 种可用的矿石决策变量 x_i 表示第 i 种矿石在混合料中的配比取值范围 0 到 1且所有 x_i 之和为 1。每种矿石有三个关键参数铁品位 Fe_i%、杂质品位 Imp_i%、单吨成本 c_i元/吨。工艺要求混合后的铁品位不低于 Fe_min、不高于 Fe_max杂质不高于 Imp_max。目标是最小化吨成本。模型写出来是这样决策变量x_ii 1, 2, ..., n目标函数min ∑ c_i · x_i约束条件∑ x_i 1Fe_min ≤ ∑ Fe_i · x_i ≤ Fe_max∑ Imp_i · x_i ≤ Imp_max0 ≤ x_i ≤ stock_i每组矿石有库存上限为什么目标不直接写“最大化回收率”因为回收率是选别环节的指标和配矿品位之间存在非线性关系在没有详细工艺参数的情况下强行建模容易失真。成本最小化是赛题里最稳健的落点品位约束天然嵌在里面后续如果附录数据里有回收率曲线再改成非线性目标也不迟。建模要素常见写法需要注意的问题决策变量配比 x_i必须归一化到和为 1否则解不唯一目标成本最小 / 综合效益最大回收率非线性时不要硬套线性目标品位约束上下限区间注意单位统一% 还是小数库存约束0 ≤ x_i ≤ stock_i缺库存上限时解会集中在最便宜矿种2.3 线性规划与启发式方案的选型什么场景用什么求解器配矿模型用线性规划LP就能解决这是教科书级别的标准应用。scipy.optimize.linprog 适合做快速验证PuLP 适合需要清晰表达约束和变量的场景。如果赛题追加了“选矿工艺参数也参与优化”比如磨矿细度影响回收率、回收率影响最终经济收益模型会变成非线性这时候再考虑遗传算法或粒子群。选型判断我一般看三点约束是否全部线性、变量是否连续、数据量是否在千级以内。满足就上 LP变量要求整数比如“至少使用 3 种矿石”这种离散约束就上整数规划目标函数带非线性项就换启发式。线性规划解出来的结果还能直接做灵敏度分析配矿比例对品位边界有多敏感这是后续章节要展开的重点。3. 复现附录代码数据清洗、配矿求解与结果输出的最小落地3.1 从“数据在附录”到内存DataFrame读取、清洗与列名约定附录里的数据文件通常以 csv 或 xlsx 形式提供第一步先把数据读进来并统一列名。我习惯把所有列名转成小写、去空格避免后面写代码时因为列名差异反复报错。读取时建议显式指定编码Windows 环境下最常见的坑是 UTF-8 读 GBK 文件直接抛异常。import pandas as pd # 读原始数据encoding 根据附录文件实际情况调整 df pd.read_csv(ore_data.csv, encodingutf-8-sig) # 统一列名小写 去空格 下划线分隔 df.columns df.columns.str.strip().str.lower().str.replace( , _) # 检查缺失值与基本统计量 print(df.info()) print(df.describe()) # 只保留建模需要的字段矿石编号、铁品位、杂质品位、成本、库存上限 keep_cols [ore_id, fe, imp, cost, stock] df df[keep_cols].dropna(subset[fe, imp, cost]) print(df.head())这段代码做的事情很直白读文件、清洗列名、看数据概况、筛选建模字段。keep_cols列表里的字段名是我常用的占位实际跑的时候要按附录 CSV 的真实表头改。dropna(subset...)只剔除关键字段为空的记录杂质字段缺失还可以用插补但成本缺失就必须丢掉不然目标函数算不出来。注意encodingutf-8-sig能自动处理带 BOM 的文件用utf-8读带 BOM 的 CSV 会在第一列列名前面多出一个隐藏字符\ufeff这是后续所有列名匹配失败的根源。3.2 用Python实现配矿线性规划目标函数、约束与求解完整代码数据清洗完成后直接用 PuLP 建模。PuLP 的好处是约束和变量的写法接近数学公式评委或同事看代码就能对应上模型。下面这段代码是我的标准模板可以直接替换数据跑通配矿问题。import pulp # 从 DataFrame 构造参数 ore_ids df[ore_id].tolist() fe dict(zip(df[ore_id], df[fe])) imp dict(zip(df[ore_id], df[imp])) cost dict(zip(df[ore_id], df[cost])) stock dict(zip(df[ore_id], df[stock])) # 工艺参数铁品位上下限、杂质上限单位 % fe_min, fe_max 58.0, 62.0 imp_max 8.0 # 创建模型最小化 prob pulp.LpProblem(Ore_Blending_Problem, pulp.LpMinimize) # 决策变量每种矿石的配比0 到库存上限 x pulp.LpVariable.dicts(x, ore_ids, lowBound0, upBound1) # 目标总成本最小 prob pulp.lpSum(cost[i] * x[i] for i in ore_ids), Total_Cost # 约束 1配比之和为 1 prob pulp.lpSum(x[i] for i in ore_ids) 1, Sum_Ratio # 约束 2铁品位加权平均落在区间内 prob pulp.lpSum(fe[i] * x[i] for i in ore_ids) fe_min, Fe_Lower prob pulp.lpSum(fe[i] * x[i] for i in ore_ids) fe_max, Fe_Upper # 约束 3杂质加权平均不超过上限 prob pulp.lpSum(imp[i] * x[i] for i in ore_ids) imp_max, Imp_Upper # 求解 solver pulp.PULP_CBC_CMD(msgTrue) prob.solve(solver) # 输出结果 print(Status:, pulp.LpStatus[prob.status]) for i in ore_ids: if pulp.value(x[i]) 1e-6: print(f矿石 {i}: 配比 {pulp.value(x[i]):.4f}, f铁品位 {fe[i]:.2f}%, 杂质 {imp[i]:.2f}%, 成本 {cost[i]:.2f}) print(f铁品位实际值: {sum(fe[i] * pulp.value(x[i]) for i in ore_ids):.2f}%) print(f杂质实际值: {sum(imp[i] * pulp.value(x[i]) for i in ore_ids):.2f}%) print(f总成本: {pulp.value(prob.objective):.2f})这段代码的关键点有三个。第一个是upBound1配合“配比之和为 1”约束限制每种矿石最多占 100%实际库存更紧的话把upBound改成stock[i] / total_stock用归一化库存比例。第二个是pulp.LpVariable.dicts返回的是字典后续所有表达式的索引方式必须和它一致混用列表和字典会报 KeyError。第三个是pulp.PULP_CBC_CMD(msgTrue)打开求解器日志无解或不可行时能从日志里看到是哪个约束出问题。1e-6的阈值用来过滤接近零的小数解浮点误差会让理论上的 0 变成 1e-10。3.3 把求解结果写回表格决策变量、品位与成本输出赛题要求提交的不只是配比数字还有各批次使用量、混合后实际品位、总成本。我习惯把结果整理成 CSV并单独生成一份“约束校验表”逐条列出每个约束的目标值、上下限、是否达标。这比只给一个总成本有说服力得多。# 整理配比结果 result_df pd.DataFrame({ ore_id: ore_ids, ratio: [pulp.value(x[i]) for i in ore_ids], fe: [fe[i] for i in ore_ids], imp: [imp[i] for i in ore_ids], cost: [cost[i] for i in ore_ids], }) # 计算混合后实际品位与成本 mix_fe sum(fe[i] * pulp.value(x[i]) for i in ore_ids) mix_imp sum(imp[i] * pulp.value(x[i]) for i in ore_ids) mix_cost pulp.value(prob.objective) # 写出配比明细 result_df.to_csv(blending_result.csv, indexFalse, encodingutf-8-sig) # 写出约束校验结果 check_df pd.DataFrame({ constraint: [Fe_Lower, Fe_Upper, Imp_Upper, Sum_Ratio], value: [mix_fe, mix_fe, mix_imp, sum(pulp.value(x[i]) for i in ore_ids)], lower_bound: [fe_min, None, None, 1.0], upper_bound: [None, fe_max, imp_max, 1.0], }) check_df.to_csv(constraint_check.csv, indexFalse, encodingutf-8-sig) print(结果已写出)输出文件用utf-8-sig编码Excel 双击打开不会乱码。constraint_check.csv的核心价值是让“数据在附录”这件事闭环评委能看到你的解不仅成本最低而且所有约束边界都清晰可查。我见过太多人只输出结果不输出校验过程一旦某条约束被松弛或遗漏整个模型的合理性就无从谈起。4. 质量控制指标的权衡合格率、回收率与成本的博弈4.1 核心指标计算方法品位偏差率、合格率与综合评分配矿模型求解完不等于质量控制做完赛题通常还有第二问评价当前方案的稳定性。这里常用两个指标品位偏差率和批次合格率。品位偏差率是混合后实际品位相对目标品位的偏离程度公式是abs(实际品位 - 目标品位) / 目标品位 * 100%。合格率是统计多个批次生产数据中品位落在工艺区间内的比例。有了配比方案后我可以模拟生产批次来评估稳定性。做法是用历史品位数据的波动标准差生成扰动叠加到混合矿品位上再统计超限比例。import numpy as np # 假设混合矿品位的标准差来自历史数据这里取 0.8% std_dev 0.8 target_fe (fe_min fe_max) / 2 # 生成 1000 个批次模拟品位 np.random.seed(42) simulated_fe np.random.normal(mix_fe, std_dev, 1000) # 计算合格率 pass_rate np.mean((simulated_fe fe_min) (simulated_fe fe_max)) deviation_rate np.mean(np.abs(simulated_fe - target_fe) / target_fe) * 100 print(f模拟批次合格率: {pass_rate * 100:.2f}%) print(f平均品位偏差率: {deviation_rate:.2f}%)std_dev是测试值正式比赛时应该从附录原始数据里按矿石种类分组求标准差再按配比加权合成。这里刻意不写死模拟次数1000 次是为了让均值和标准差的估计稳定。合格率算出来如果低于 95%原因要么是品位上下限太窄要么是配比方案本身没有考虑稳定性优化——这就引出下一节要讲的阈值敏感性。4.2 约束阈值的敏感性与工艺经验松紧怎么调品位上下限不是拍脑袋定的。工艺上给的是“铁品位不低于 58%、不高于 62%”但实际生产中目标值更接近 60%且波动越小越好。我一般会做一个敏感性测试把品位下限从 58 逐步放到 59、59.5、60看成本和合格率的变化曲线。fe_min_list [57.0, 57.5, 58.0, 58.5, 59.0, 59.5, 60.0] results [] for f_min in fe_min_list: prob pulp.LpProblem(Sensitivity, pulp.LpMinimize) x pulp.LpVariable.dicts(x, ore_ids, lowBound0, upBound1) prob pulp.lpSum(cost[i] * x[i] for i in ore_ids) prob pulp.lpSum(x[i] for i in ore_ids) 1 prob pulp.lpSum(fe[i] * x[i] for i in ore_ids) f_min prob pulp.lpSum(fe[i] * x[i] for i in ore_ids) fe_max prob pulp.lpSum(imp[i] * x[i] for i in ore_ids) imp_max prob.solve(pulp.PULP_CBC_CMD(msgFalse)) results.append({ fe_min: f_min, cost: pulp.value(prob.objective), status: pulp.LpStatus[prob.status], }) for r in results: print(f下限 {r[fe_min]}: 成本 {r[cost]:.2f}, 状态 {r[status]})这条循环跑完就能看到边界在哪品位下限越接近最便宜矿石的品位成本越低但合格率可能崩掉下限太高则直接无解。敏感性分析的价值不是找一个“最优阈值”而是搞清楚你提交的方案离无解边界有多远这决定了模型在评审眼里是稳健还是碰巧可行。4.3 把附录里的代码改造成可复用脚本函数化、参数化与日志附录代码往往是一次性的脚本变量名混乱、参数散落在各处。比赛只有几天但如果你在热身赛里把代码改造成函数化的版本正式赛就省一半时间。我推荐的改造方式是把所有工艺参数集中到一个字典或配置文件里把所有求解过程封装成一个函数。def solve_blending(df, config): 参数: df: DataFrame至少包含 ore_id, fe, imp, cost, stock config: dict包含 fe_min, fe_max, imp_max, weight_cost 返回: result_df, check_df, status ore_ids df[ore_id].tolist() fe dict(zip(df[ore_id], df[fe])) imp dict(zip(df[ore_id], df[imp])) cost dict(zip(df[ore_id], df[cost])) prob pulp.LpProblem(Ore_Blending, pulp.LpMinimize) x pulp.LpVariable.dicts(x, ore_ids, lowBound0, upBound1) prob pulp.lpSum(cost[i] * x[i] for i in ore_ids) prob pulp.lpSum(x[i] for i in ore_ids) 1 prob pulp.lpSum(fe[i] * x[i] for i in ore_ids) config[fe_min] prob pulp.lpSum(fe[i] * x[i] for i in ore_ids) config[fe_max] prob pulp.lpSum(imp[i] * x[i] for i in ore_ids) config[imp_max] prob.solve(pulp.PULP_CBC_CMD(msgFalse)) result_df pd.DataFrame({ ore_id: ore_ids, ratio: [pulp.value(x[i]) for i in ore_ids], fe: [fe[i] for i in ore_ids], imp: [imp[i] for i in ore_ids], cost: [cost[i] for i in ore_ids], }) return result_df, prob函数化的好处是后续做灵敏度分析、蒙特卡洛模拟、甚至多目标优化时只需要在config里改参数不用动核心求解逻辑。weight_cost这个字段我留了接口如果赛题第三问要求“成本与质量加权综合最优”可以直接把目标函数改成cost weight * abs(品位偏差)不用推翻重写。5. 避坑与排查附录代码复现与建模中的五类经典问题5.1 数据读取报错编码、路径与列名不匹配现象pd.read_csv报UnicodeDecodeError或者代码里写df[Fe]但 DataFrame 里根本没有这列报KeyError。原因附录 CSV 文件可能是 GBK 或 GB2312 编码Excel 打开正常但 pandas 默认按 UTF-8 读另一种情况是列名里有空格或不可见字符比如Fe(%)和Fe (%)看起来一样但实际不同。解决读取时先试encodinggbk再试encodingutf-8-sig读进来后立刻执行df.columns df.columns.str.strip().str.lower().str.replace( , _)把列名规范化。如果还有列名匹配不上用print(df.columns.tolist())看一下实际列名的原始表示。5.2 求解器提示无解可行域为空与约束冲突现象PuLP 返回Infeasible日志里没有任何变量的解LpStatus显示Infeasible。原因最常见的是品位上下限和杂质上限相互打架。例如库存里所有矿石杂质都高于 8%但杂质约束要求不超过 8%这时无论怎么配都不可能满足。另一种情况是“配比之和为 1”和“品位下限”组合后可行域变成了空集。解决先注释掉杂质约束跑一遍如果可行说明杂质约束是元凶再注释品位约束跑一遍逐步定位冲突组合。赛题里如果出现“某种矿石库存为零”检查upBound是否设成了 0这会直接导致解空间退化。调试时在模型里加两行列印约束的系数矩阵确认每个变量的系数没有被写反。5.3 结果异常但求解成功符号写反、单位不统一与归一化陷阱现象求解状态是Optimal但品位输出是 5700%成本是负数或者配比里某种矿石用了 -0.05。原因铁品位如果原始数据是小数0.58而约束写的是 58解出来的加权品位就会差 100 倍目标函数系数符号写反最小化成本会变成最大化成本求解器照样返回 Optimal。归一化陷阱更隐蔽x_i如果没有约束“和为 1”最便宜的矿石会把配比拉到接近 1但不能为 1 的库存上限又同时被触发这往往导致结果中有多个非零小数解。解决在求解前用assert abs(sum(fe[i] * x[i] ...) - 目标值) 1e-6打印关键约束的预估值先手工验算一组简单数据。单位问题我一般统一用百分比成本用元/吨所有参数进模型前查一遍最大值和最小值异常值一目了然。5.4 附录代码格式问题复制粘贴后跑不动的五个细节现象代码从 PDF 或 Word 里的附录复制到编辑器运行时第一个错误往往是TabError: inconsistent use of tabs and spaces in indentation或SyntaxError: invalid character in identifier。原因PDF 导出代码时经常把空格折叠、把全角括号替换成半角失败或者保留不可见字符Word 复制还会带上软换行。Python 对缩进和全角符号零容忍这是新人最容易翻车的地方。解决粘贴到 VS Code 或 PyCharm 后先用python -m py_compile检查语法编辑器里开启“显示空白字符”功能能看到所有空格和 Tab 的真实分布。全角符号搜索.并替换成半角。这个步骤应该在写任何代码前执行而不是等报错后一行行找。赛前准备一个脚本专门做代码清洗两分钟解决省下半小时和大量情绪损耗。5.5 原始数据缺失值处理不当导致品位偏差现象模型能跑通但输出品位比任何矿石的品位都低或者某些矿石的配比为零却显示有品位贡献。原因数据清洗时用dropna()把包含 NaN 的整行删掉导致参与计算的矿石集合变小更危险的是用fillna(0)填充缺失品位把一批原本杂质高的矿石变成“高纯度矿石”配比严重失真。缺失值填充成 0 在品位数据里几乎没有物理意义。解决如果是字段缺失先按矿石种类分组的众数或中位数填充并做填充标记如果同一矿石的品位字段缺失超过 30%直接舍弃该矿种并记录。填充后跑一次模型把结果和去掉缺失值前后的品位差异做对比如果混合品位变化超过 1%说明缺失值已经影响决策需要在报告里说明。6. 进阶技巧用灵敏度分析判断配矿方案的鲁棒性赛题评奖时最拉分的不是“解出来”而是“解为什么是这个、换个参数还成不成立”。最后一个技巧我建议做品位约束的批量扫描绘制“品位下限-成本”曲线。下面这段代码把刚才的敏感性测试结果可视化直接放到论文里当图。import matplotlib.pyplot as plt fe_min_list [57.0, 57.5, 58.0, 58.5, 59.0, 59.5, 60.0] cost_values [] for f_min in fe_min_list: config {fe_min: f_min, fe_max: fe_max, imp_max: imp_max} result_df, prob solve_blending(df, config) if prob.status 1: cost_values.append(pulp.value(prob.objective)) else: cost_values.append(None) plt.plot(fe_min_list, cost_values, markero, linestyle-) plt.xlabel(铁品位下限 (%)) plt.ylabel(最低成本 (元/吨)) plt.title(品位下限对成本的影响) plt.grid(True) plt.show()status 1对应Optimal无解时画成断点。曲线会呈现明显的阶梯或拐点拐点左边是“便宜矿石被品位约束逼退”的区域拐点右边接近无解。把拐点位置和赛题给出的工艺下限叠加分析就能判断原题参数是否故意把一个约束推到了可行域边缘。以前我做这类赛题总喜欢把参数定死、闷头跑最优解复盘的次数越多越觉得建模题的核心从来不是求解器多强而是你对“解的边界”心里有数——哪里会无解哪里成本骤降哪里合格率崩盘这些比最优解本身更值得花时间。先把这个小工具写进你的模板比赛时能少走一大段弯路。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →