
主副产品“搭着卖”用线性规划把产线收益拉高 18%“某炼油厂有 3 条生产线主产汽油副产沥青和硫磺。以前调度只盯着汽油沥青堆成山、硫磺当废料烧年损失 1200 万。后来我用 Python 写了个主副产品协同生产规划器0.2 秒算完最优生产比例把沥青和硫磺全转成高附加值产品年收益从 1.1 亿涨到 1.298 亿多赚 1980 万。生产部长说‘原来不是油不值钱是没算明白搭着卖。’”—— 参考北京理工大学《运筹学》第 3 章“线性规划”、第 6 章“对偶理论”一、实际应用场景描述主副产品协同生产规划器是任何涉及“单一原料、多产品、固定产出比例、收益差异大”场景的“生产大脑”。凡是“主产品要保、副产品要卖、比例改不了”的地方都是它行业 典型场景 主产品 副产品 比例约束炼油 常减压蒸馏 汽油 沥青、硫磺 原油性质决定化工 乙烯裂解 乙烯 丙烯、丁二烯 裂解深度决定钢铁 高炉炼铁 铁水 炉渣、煤气 原料配比决定造纸 制浆造纸 纸浆 黑液、松节油 木材种类决定食品 榨油精炼 食用油 豆粕、磷脂 油料品种决定能源 煤气化 合成气 焦油、硫磺 煤质决定核心矛盾- 运筹学教科书教“线性规划目标函数最大化、比例约束”- 生产拿到的是“装置能力、产品比例、市场价格”- 调度员凭经验“主产品优先、副产品凑数”- 结果要么主产品过剩、副产品浪费要么整体收益没算满。┌──────────────────────────────────────────────────────────────┐│ 主副产品协同生产规划器 · 生产大脑 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 生产装置参数 │││ │ • 装置A: 最大处理量1000吨/天, 主产品比例60%, 副产品40%│││ │ • 主产品: 汽油, 售价5000元/吨, 成本3000元/吨 │││ │ • 副产品: 沥青, 售价2000元/吨, 成本1500元/吨 │││ │ • 副产品: 硫磺, 售价800元/吨, 成本600元/吨 │││ │ │││ │ 线性规划逻辑: │││ │ 1. 决策变量: 装置A的生产量x_A │││ │ 2. 目标函数: 总收益最大(主产品收益副产品收益-总成本) │││ │ 3. 约束条件: 装置能力、产品比例、市场需求 │││ │ 4. 求解: 单纯形法/内点法 │││ │ │││ │ 输出: │││ │ • 最优生产计划(各装置开多少) │││ │ • 产品产出结构(主副产品各多少) │││ │ • 收益贡献分析(哪个产品最赚钱) │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 生产部长: 想知道怎么开装置收益最高 │││ • 教科书: 线性规划输出决策变量、约束矩阵、影子价格 │││ • 现场: 3套装置、5种产品、10多个比例约束 │││ • 本程序: 把数学规划变成生产执行表 │││ ││ 【本程序处理流程】 │││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐│││ │ 读取装置 │──►│ 构建线性 │──►│ 求解最优 │──►│ 生成生产 ││││ │ 产品参数 │ │ 规划模型 │ │ 生产计划 │ │ 执行表 ││││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某炼油厂生产部长的原话“我们厂有 3 套主要装置常减压、催化裂化、重整。主产汽油、柴油但每套装置都副产沥青、硫磺、液化气。以前我们调度有个死规矩- ‘主产品优先’汽油不够就拼命开装置- ‘副产品凑数’沥青、硫磺能卖就卖卖不掉就堆- ‘比例改不了’原油性质定了产品比例就定了。结果就是- 汽油一年卖 1.1 亿看起来不错- 沥青堆了 3 万吨占了半个罐区- 硫磺没人要一年烧了 2000 吨还被环保罚了 50 万- 算总账一年少赚 1200 万。厂长问我‘3 套装置5 种产品怎么就赚不到钱’我也很委屈装置比例改不了、市场需求天天变人工调度根本算不过来。不是油不值钱是没算明白搭着卖。后来我研究北理工《运筹学》第 3 章‘线性规划’才发现这是个标准的“多产品线性规划问题”。- 决策变量各装置的生产负荷 x_i - 目标函数总收益最大主产品收益 副产品收益 - 总成本- 约束条件装置能力、产品比例、市场需求、原料供应。我写了个 Python 主副产品协同生产规划器——0.2 秒算完最优生产计划- 常减压装置从 80% 负荷提到 95%- 催化裂化多产丙烯高附加值- 重整装置优化芳烃产出- 沥青从堆存变成按订单生产- 硫磺全部卖给化肥厂- 年收益从 1.1 亿涨到 1.298 亿多赚 1980 万。生产部长看完说‘原来不是油不值钱是没算明白搭着卖。这 0.2 秒的计算值 2000 万。’”2.2 经验调度 vs 线性规划协同优化量化对比指标 经验调度主产品优先 线性规划协同优化 改善效果年总收益 1.10 亿元 1.298 亿元 18%主产品收益 1.10 亿元 1.05 亿元 -4.5%副产品收益 0 亿元浪费 0.248 亿元 ∞装置平均负荷 78% 92% 14%沥青堆存 3 万吨 0 吨 消除硫磺焚烧 2000 吨/年 0 吨/年 消除环保罚款 50 万/年 0 万/年 消除决策耗时 4 小时人工核算 0.2 秒 -99.99%关键发现生产优化的瓶颈不在“主产品多少”而在“副产品怎么卖”。线性规划把“单产品调度”变成“多产品协同”让每一吨原料都榨出最大价值。三、核心逻辑讲解大白话版3.1 用大白话解释“主副产品协同生产问题”想象你开了一家榨油厂用花生榨油- 1 吨花生 → 能榨 400 公斤花生油主产品 600 公斤花生粕副产品- 花生油卖 20 元/公斤大家都抢着要- 花生粕卖 3 元/公斤饲料厂偶尔要- 如果你只盯着花生油- 花生油卖光了花生粕堆成山- 最后花生粕卖不掉只能当垃圾扔- 一年少赚好几万。- 如果你“搭着卖”- 花生油按订单卖花生粕卖给饲料厂- 甚至把花生粕做成宠物零食卖 10 元/公斤- 一年多赚好几万。问题是怎么安排榨油计划让总收益最高线性规划就是帮你算这个的“智能榨油工”1. 先想“原料能出什么”产品比例- 1 吨花生 → 400 公斤花生油 600 公斤花生粕比例固定- 这个比例改不了是花生品种决定的。2. 再想“东西值多少钱”收益计算- 花生油20 元/公斤- 花生粕3 元/公斤普通、10 元/公斤零食级- 榨油成本5 元/公斤花生。3. 最后想“怎么榨最赚钱”目标函数- 目标是总收益最大花生油收益 花生粕收益 - 榨油成本- 约束是花生有限、设备能力有限、市场需求有限。大白话逻辑- “原料能出什么” → 产品产出比例技术约束- “东西值多少钱” → 产品售价与成本经济参数- “怎么榨最赚钱” → 最大化总收益优化目标- “智能榨油工” → 线性规划算法。工业现场版- 花生 原油/原料- 花生油 主产品汽油- 花生粕 副产品沥青、硫磺- 智能榨油工 线性规划协同生产规划器。3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 3 章“线性规划”、第 6 章“对偶理论”主副产品协同生产线性规划模型决策变量- x_i 装置 i 的生产负荷吨/天- y_{ij} 装置 i 生产产品 j 的数量吨/天。参数- a_{ij} 装置 i 生产产品 j 的产出比例技术约束- p_j 产品 j 的售价元/吨- c_i 装置 i 的单位加工成本元/吨原料- C_i^{max} 装置 i 的最大处理能力吨/天- D_j^{max} 产品 j 的最大市场需求吨/天- R_i 装置 i 的原料供应量吨/天。目标函数最大化总收益\max Z \sum_{i} \sum_{j} p_j \cdot y_{ij} - \sum_{i} c_i \cdot x_i约束条件1. 产出比例约束技术限制改不了y_{ij} a_{ij} \cdot x_i, \quad \forall i,j2. 装置能力约束不能超设计负荷x_i \le C_i^{max}, \quad \forall i3. 原料供应约束原料有限x_i \le R_i, \quad \forall i4. 市场需求约束产品不能卖不掉\sum_{i} y_{ij} \le D_j^{max}, \quad \forall j5. 非负约束x_i \ge 0, \quad y_{ij} \ge 0, \quad \forall i,j北理工教材要点- 第 3 章 §3.1线性规划问题的一般形式- 第 3 章 §3.2线性规划的图解法与单纯形法- 第 6 章 §6.1对偶理论影子价格解释- 本程序使用 PuLP 库调用 CBC 求解器求解线性规划问题。3.3 如何映射到代码中业务逻辑 Python 代码装置定义ProcessUnit 数据类产品定义Product 数据类产出比例YieldRatio 数据类生产模型构建ProductionPlanner.build_model()求解最优计划ProductionPlanner.solve()结果分析ProductionAnalyzer 类四、OOP 代码实现精简可运行4.1 项目结构production_planner/├── production_planner.py # 核心代码单文件~380行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary主副产品协同生产规划器 · 生产大脑参考: 北理工《运筹学》第3章线性规划、第6章对偶理论功能:1. 定义生产装置、产品、产出比例2. 构建线性规划协同生产模型3. 求解最优生产计划(最大化总收益)4. 统计产品贡献、装置负荷、收益分析运行:python production_planner.py(需要安装pulp, numpy, pandas)注意:本程序解决主副产品协同生产优化问题, 属于线性规划的典型应用。对于大规模问题(装置50, 产品100), 建议使用专业求解器(Gurobi/CPLEX)。import numpy as npimport pandas as pdfrom dataclasses import dataclass, fieldfrom typing import List, Dict, Tuple, Optional, Setfrom enum import Enumimport pulpimport mathfrom collections import defaultdictimport time# ─── 枚举与常量 ────────────────────────────────────────────────────────────class ProductType(Enum):产品类型MAIN 主产品BYPRODUCT 副产品INTERMEDIATE 中间品class UnitStatus(Enum):装置状态ACTIVE 运行INACTIVE 停运MAINTENANCE 检修# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass Product:产品product_id: strname: strproduct_type: ProductTypeselling_price: float # 售价(元/吨)production_cost: float 0.0 # 生产成本(元/吨, 不含原料)max_demand: float 1e9 # 最大市场需求(吨/天)propertydef profit_margin(self) - float:单位毛利return self.selling_price - self.production_costdef __str__(self):type_str 主 if self.product_type ProductType.MAIN else 副return f{type_str}{self.name}({self.product_id}): 售价{self.selling_price}元/吨, 毛利{self.profit_margin:.1f}元dataclassclass ProcessUnit:生产装置unit_id: strname: strmax_capacity: float # 最大处理能力(吨/天)unit_cost: float # 单位加工成本(元/吨原料)raw_material_cost: float 0.0 # 原料成本(元/吨)status: UnitStatus UnitStatus.ACTIVEmin_load: float 0.0 # 最小负荷(吨/天)propertydef total_unit_cost(self) - float:总单位成本(加工原料)return self.unit_cost self.raw_material_costdef __str__(self):status_str if self.status UnitStatus.ACTIVE else return f{status_str}{self.name}({self.unit_id}): 能力{self.max_capacity}吨/天, 成本{self.total_unit_cost:.1f}元/吨dataclassclass YieldRatio:产出比例unit_id: strproduct_id: strratio: float # 产出比例(吨产品/吨原料)def __str__(self):return f装置{self.unit_id}→产品{self.product_id}: {self.ratio:.2f}吨/吨原料dataclassclass ProductionPlan:生产计划结果success: booltotal_profit: float # 总收益(元/天)unit_loads: Dict[str, float] # 装置负荷product_outputs: Dict[str, float] # 产品产出量product_profits: Dict[str, float] # 产品收益贡献unit_utilization: Dict[str, float] # 装置利用率solve_time: floatsolver_status: strpropertydef total_revenue(self) - float:总营收return sum(self.product_profits.values())propertydef average_utilization(self) - float:平均装置利用率if not self.unit_utilization:return 0.0return sum(self.unit_utilization.values()) / len(self.unit_utilization)propertydef main_product_ratio(self) - float:主产品占比main_output sum(output for (pid, output) in self.product_outputs.items()if pid.startswith(P1) # 假设P1开头为主产品)total_output sum(self.product_outputs.values())return main_output / total_output if total_output 0 else 0.0# ─── 线性规划生产规划器 ───────────────────────────────────────────────────────class ProductionPlanner:主副产品协同生产规划器def __init__(self,units: List[ProcessUnit],products: List[Product],yield_ratios: List[YieldRatio],planning_horizon: int 1): # 计划周期(天)Args:units: 生产装置列表products: 产品列表yield_ratios: 产出比例列表planning_horizon: 计划周期(天)self.units unitsself.products productsself.yield_ratios yield_ratiosself.planning_horizon planning_horizon# 建立索引self.unit_dict {u.unit_id: u for u in units}self.product_dict {p.product_id: p for p in products}self.yield_dict defaultdict(list)for yr in yield_ratios:self.yield_dict[yr.unit_id].append(yr)# 有效装置(运行状态)self.active_units [u for u in units if u.status UnitStatus.ACTIVE]# 求解器self.model Noneself.variables {}def build_model(self):构建线性规划模型print( 构建线性规划协同生产模型...)print(f • 装置数量: {len(self.active_units)})print(f • 产品数量: {len(self.products)})print(f • 产出比例: {len(self.yield_ratios)})print(f • 计划周期: {self.planning_horizon}天)# 创建模型self.model pulp.LpProblem(Production_Planning, pulp.LpMaximize)# 决策变量: x[i] 装置i的生产负荷(吨/天)x {}for unit in self.active_units:var_name fx_{unit.unit_id}x[unit.unit_id] pulp.LpVariable(var_name, lowBoundunit.min_load, upBoundunit.max_capacity, catContinuous)self.variables[x] x# 决策变量: y[i,j] 装置i生产产品j的数量(吨/天)y {}for unit in self.active_units:for product in self.products:# 检查该装置是否生产该产品if any(yr.unit_id unit.unit_id and yr.product_id product.product_idfor yr in self.yield_ratios):var_name fy_{unit.unit_id}_{product.product_id}y[(unit.unit_id, product.product_id)] pulp.LpVariable(var_name, lowBound0, catContinuous)self.variables[y] y# 目标函数: 最大化总收益print( 添加目标函数: 最大化总收益...)total_revenue pulp.lpSum([product.selling_price * y[(unit.unit_id, product.product_id)]for unit in self.active_unitsfor product in self.productsif (unit.unit_id, product.product_id) in y])total_cost pulp.lpSum([unit.total_unit_cost * x[unit.unit_id]for unit in self.active_units])total_profit total_revenue - total_costself.model total_profit# 约束1: 产出比例约束(技术限制, 改不了)print( 添加约束: 产出比例(技术约束)...)for unit in self.active_units:for product in self.products:if (unit.unit_id, product.product_id) in y:# 找到对应的产出比例ratio next((yr.ratio for yr in self.yield_ratiosif yr.unit_id unit.unit_id and yr.product_id product.product_id),0.0)if ratio 0:self.model y[(unit.unit_id, product.product_id)] ratio * x[unit.unit_id], \fYield_{unit.unit_id}_{product.product_id}# 约束2: 装置能力约束print( 添加约束: 装置能力限制...)for unit in self.active_units:self.model x[unit.unit_id] unit.max_capacity, \fCapacity_{unit.unit_id}# 约束3: 市场需求约束print( 添加约束: 市场需求限制...)for product in self.products:total_output pulp.lpSum([y[(unit.unit_id, product.product_id)]for unit in self.active_unitsif (unit.unit_id, product.product_id) in y])self.model total_output product.max_demand, \fDemand_{product.product_id}# 约束4: 原料供应约束(简化: 假设原料充足)# 实际可扩展为: x[i] R[i] (原料供应量)print(f ✅ 模型构建完成! 变量数: {len(self.model.variables())})def solve(self, time_limit: int 30) - ProductionPlan:求解线性规划问题if self.model is None:self.build_model()print(\n 启动线性规划求解器...)start_time time.perf_counter()# 求解solver pulp.PULP_CBC_CMD(msgFalse, timeLimittime_limit)self.model.solve(solver)end_time time.perf_counter()solve_time end_time - start_time# 检查结果status pulp.LpStatus[self.model.status]if self.model.status ! pulp.LpOptimal:print(f ❌ 求解失败! 状态: {status})return ProductionPlan(successFalse,total_profit0,unit_loads{},product_outputs{},product_profits{},unit_utilization{},solve_timesolve_time,solver_statusstatus)print(f ✅ 求解成功! 耗时: {solve_time:.3f}秒)print(f 最优总收益: {pulp.value(self.model.objective)/10000:.1f}万元/天)# 提取结果unit_loads {}product_outputs {}product_profits {}unit_utilization {}x self.variables[x]y self.variables[y]# 提取装置负荷for unit in self.active_units:load x[unit.unit_id].varValueunit_loads[unit.unit_id] loadunit_utilization[unit.unit_id] load / unit.max_capacity * 100 if unit.max_capacity 0 else 0# 提取产品产出量for product in self.products:total_output 0.0for unit in self.active_units:if (unit.unit_id, product.product_id) in y:output y[(unit.unit_id, product.product_id)].varValuetotal_output outputproduct_outputs[product.product_id] total_output# 计算产品收益贡献for product in self.products:revenue product.selling_price * product_outputs.get(product.product_id, 0)cost product.production_cost * product_outputs.get(product.product_id, 0)product_profits[product.product_id] revenue - cost# 计算总收益total_profit pulp.value(self.model.objective)# 打印摘要self._print_summary(total_profit, unit_loads, product_outputs, product_profits, unit_utilization)return ProductionPlan(successTrue,total_profittotal_profit,unit_loadsunit_loads,product_outputsproduct_outputs,product_profitsproduct_profits,unit_utilizationunit_utilization,solve_timesolve_time,solver_statusstatus)def _print_summary(self,total_profit: float,unit_loads: Dict[str, float],product_outputs: Dict[str, float],product_profits: Dict[str, float],unit_utilization: Dict[str, float]):打印求解摘要print(f\n 生产优化结果摘要:)print(f • 总收益: {total_profit/10000:.1f}万元/天)print(f • 总营收: {sum(product_profits.values())/10000:.1f}万元/天)print(f • 平均装置利用率: {sum(unit_utilization.values())/len(unit_utilization):.1f}%)print(f\n 装置运行情况:)for unit in self.active_units:load unit_loads.get(unit.unit_id, 0)util unit_utilization.get(unit.unit_id, 0)status if util 95 else if util 80 else print(f {status} {unit.name}: 负荷{load:.1f}吨/天, 利用率{util:.1f}%)print(f\n 产品产出情况:)for product in self.products:output product_outputs.get(product.product_id, 0)profit product_profits.get(product.product_id, 0)type_str 主 if product.product_type ProductType.MAIN else 副print(f {type_str} {product.name}: 产出{output:.1f}吨/天, 贡献{profit/10000:.1f}万元/天)# 找出最大贡献产品if product_profits:best_product_id max(product_profits, keyproduct_profits.get)best_product self.product_dict[best_product_id]print(f\n 最大收益贡献: {best_product.name} ({product_profits[best_product_id]/10000:.1f}万元/天))# ─── 结果分析器 ───────────────────────────────────────────────────────────class ProductionAnalyzer:生产结果分析器def __init__(self):passdef generate_production_table(self, plan: ProductionPlan,units: List[ProcessUnit],products: List[Product]) - pd.DataFrame:生成生产计划表data []for unit in units:if unit.unit_id in plan.unit_loads:load plan.unit_loads[unit.unit_id]util plan.unit_utilization.get(unit.unit_id, 0)data.append({装置ID: unit.unit_id,装置名称: unit.name,负荷(吨/天): round(load, 1),利用率(%): round(util, 1),状态: unit.status.value})return pd.DataFrame(data)def generate_product_table(self, plan: ProductionPlan,products: List[Product]) - pd.DataFrame:生成产品产出表data []for product in products:output plan.product_outputs.get(product.product_id, 0)profit plan.product_profits.get(product.product_id, 0)type_str 主产品 if product.product_type ProductType.MAIN else 副产品data.append({产品ID: product.product_id,产品名称: product.name,类型: type_str,产出量(吨/天): round(output, 1),售价(元/吨): product.selling_price,毛利(元/吨): product.profit_margin,收益贡献(万元/天): round(profit/10000, 2)})return pd.DataFrame(data)def print_detailed_report(self, plan: ProductionPlan,units: List[ProcessUnit],products: List[Product]):打印详细报告print(\n *80)print(主副产品协同生产优化详细报告)print(*80)print(f\n 总体指标:)print(f • 优化状态: {成功 if plan.success else 失败})print(f • 总收益: {plan.total_profit/10000:.1f}万元/天)prin利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛