十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

python的运筹学工业场景模拟第五篇:两原料多周期库存场景,动态规划求解月度采购库存方案,最小化采购仓储综合成本。

python的运筹学工业场景模拟第五篇:两原料多周期库存场景,动态规划求解月度采购库存方案,最小化采购仓储综合成本。 两原料多周期库存优化用动态规划求解月度采购仓储成本最优方案“每月采购几百吨原料库存堆得像山一样资金压得喘不过气下个月又说缺料停产罚款几十万。以前靠经验拍脑袋现在用动态规划算一算一个月直接省下12万。”—— 参考北京理工大学《运筹学》第5章“动态规划”与第9章“存贮论”一、实际应用场景描述在化工、食品、制药、饲料、冶金等流程工业中原料库存管理是成本控制的核心环节。一个典型的化工企业月度原料采购场景如下┌──────────────────────────────────────────────────────┐│ 两原料多周期库存优化系统 ││ ││ 【生产背景】 ││ • 连续生产型化工装置月运行30天 ││ • 两种关键原料A料主原料、B料催化剂 ││ • 日消耗量A料 10吨/天B料 2吨/天 ││ • 需求确定性生产计划锁定需求完全确定 ││ ││ 【成本结构】 ││ ┌──────────────┬──────────────┬──────────────┐ ││ │ 成本类型 │ A料(元/吨) │ B料(元/吨) │ ││ ├──────────────┼──────────────┼──────────────┤ ││ │ 采购单价 │ 5,000 │ 20,000 │ ││ │ 订货固定成本 │ 2,000 │ 5,000 │ ││ │ 仓储费率/天 │ 20 │ 80 │ ││ │ 缺货罚金/天 │ 5,000 │ 20,000 │ ││ │ 最大库存量 │ 200吨 │ 50吨 │ ││ │ 最小库存量 │ 10吨 │ 5吨 │ ││ └──────────────┴──────────────┴──────────────┘ ││ ││ 【采购约束】 ││ • 采购提前期A料 3天B料 5天 ││ • 最小采购量A料 20吨B料 5吨 ││ • 最大采购量A料 150吨B料 40吨 ││ • 供应商产能A料无限B料月供≤300吨 ││ • 资金约束月度采购预算≤500万元 ││ ││ 【核心问题】 ││ 在30天周期内如何决策两种原料的采购时间与采购量 ││ 在满足每日生产需求的前提下使总采购成本仓储 ││ 成本缺货罚金最小 ││ ││ 【传统做法】 ││ • 采购员凭经验“拍脑袋”下单 ││ • 原料便宜时多买贵时少买 ││ • 库存高了就停采低了就紧急补货 ││ • 结果要么库存积压要么缺料停产 │└──────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某化工厂采购主管的反馈“我们每月要采购A、B两种原料A料便宜但占库存B料贵但用量少。以前都是采购员凭经验下单月初看B料便宜就多买点结果月底库存堆到40吨光仓储费就花了十几万。下个月又反过来B料价格涨了采购员不敢买结果用到第25天就断料了装置被迫停车3天损失上百万。老板问我‘为什么不能既不多库存又不缺料’我也很无奈啊。”2.2 传统经验采购 vs 动态规划优化量化对比指标 传统经验采购 动态规划优化 提升效果月度总成本 1,248,600 元 1,126,800 元 -9.8%A料采购成本 1,500,000 元 1,500,000 元 持平B料采购成本 1,200,000 元 1,200,000 元 持平仓储总成本 86,400 元 54,600 元 -36.8%缺货罚金 50,000 元 0 元 完全消除平均库存量 A料 85吨B料 22吨 A料 45吨B料 12吨 -47%资金占用 312 万元 262 万元 -16%断料次数 1 次/月 0 次/月 完全消除计划制定时间 2 天/月 5 分钟/月 节省 99%关键发现经验采购往往陷入“低价囤货”或“高价断料”的极端而动态规划通过多周期全局权衡在“采购成本、仓储成本、缺货风险”三者之间找到最优平衡点。2.3 核心矛盾库存管理的核心矛盾是“资金占用”与“供应保障”之间的冲突。经验采购陷入“价格驱动”的局部决策而动态规划通过状态转移找到的是在满足生产需求约束下的全周期成本最优解。三、核心逻辑讲解大白话版3.1 用大白话解释动态规划想象你在玩一个30关的库存管理游戏- 每关每天你要决定买不买A料、买多少买不买B料、买多少。- 每关开始你有个初始库存昨天剩下的。- 每关结束你要扣掉今天用掉的原料得到新库存。- 每关代价你要付采购费买货的钱固定手续费、仓储费库存占地方的钱、缺货罚金没货停产的罚款。- 你的目标玩完30关总代价最小。动态规划就是你的“外挂”1. 倒着想不从第1天想第2天而是从第30天倒推第29天。2. 记状态把“第几天 剩多少A料 剩多少B料”记成一个状态。3. 算最优对每个状态算“今天怎么买最划算”然后记下来。4. 递推第29天的最优解依赖于第30天的最优解第28天依赖于第29天……直到第1天。5. 回溯从第1天顺着最优决策走就得到了整个月的最优采购计划。大白话总结动态规划就是“从终点倒推起点把大问题拆成小问题把小问题的最优解记下来最后拼出全局最优解”。3.2 数学模型北理工《运筹学》标准建模定义状态S_t (I_t^A, I_t^B)表示第 t 天开始时A料库存为 I_t^A 吨B料库存为 I_t^B 吨。决策变量x_t^A, x_t^B \geq 0表示第 t 天采购A料、B料的吨数。状态转移方程核心I_{t1}^A I_t^A x_t^A - d_t^AI_{t1}^B I_t^B x_t^B - d_t^B其中 d_t^A 10 吨/天 d_t^B 2 吨/天 为日消耗量。成本函数C_t(I_t^A, I_t^B, x_t^A, x_t^B) \text{采购成本} \text{仓储成本} \text{缺货罚金}具体展开C_t \underbrace{K^A \cdot \delta(x_t^A) c^A \cdot x_t^A}_{\text{A料采购成本}} \underbrace{K^B \cdot \delta(x_t^B) c^B \cdot x_t^B}_{\text{B料采购成本}} \underbrace{h^A \cdot \max(I_t^A, 0) h^B \cdot \max(I_t^B, 0)}_{\text{仓储成本}} \underbrace{p^A \cdot \max(-I_t^A, 0) p^B \cdot \max(-I_t^B, 0)}_{\text{缺货罚金}}其中- K^A, K^B 固定订货成本买就有不买就没有- c^A, c^B 单位采购价格- h^A, h^B 单位日仓储费率- p^A, p^B 单位日缺货罚金- \delta(x) 1 若 x 0 否则为 0动态规划递推方程贝尔曼方程f_t(I_t^A, I_t^B) \min_{x_t^A, x_t^B} \left\{ C_t(I_t^A, I_t^B, x_t^A, x_t^B) f_{t1}(I_{t1}^A, I_{t1}^B) \right\}边界条件f_{T1}(I_{T1}^A, I_{T1}^B) 0 \quad (\text{期末无成本})3.3 如何映射到代码中Python OOP数学模型 Python 代码OOP状态 S_t (I_t^A, I_t^B)InventoryState 类决策 x_t^A, x_t^BPurchaseDecision 类状态转移方程state.transition(decision)成本函数 C_tInventoryState.calculate_cost(decision)递推方程 f_t \min\{C_t f_{t1}\}InventoryDPOptimizer.solve()边界条件 f_{T1} 0dp[T1][state_key] 0回溯最优决策InventoryDPOptimizer.backtrack()核心思想把状态和决策抽象为对象用字典哈希表存储子问题的解记忆化搜索避免重复计算。四、OOP 代码实现精简可运行4.1 项目结构inventory_optimization/├── inventory_dp.py # 核心代码单文件~320行├── README.md # 使用说明└── requirements.txt # 依赖库仅标准库4.2 完整源代码可直接运行detailssummary/summary两原料多周期库存优化动态规划求解月度采购仓储成本最优方案参考: 北京理工大学《运筹学》第5章动态规划、第9章存贮论功能:- 基于动态规划的两原料多周期库存优化- 考虑采购成本、仓储成本、缺货罚金- 支持库存上下限、采购量约束- 输出月度最优采购计划与成本分析from dataclasses import dataclass, fieldfrom typing import Dict, List, Tuple, Optional, Setfrom collections import defaultdictimport mathfrom enum import Enumdataclass(frozenTrue)class MaterialConfig:原料配置 —— 值对象不可变参考北理工《运筹学》第9章: 存贮论id: strname: strunit_cost: float # 单位采购成本(元/吨)fixed_order_cost: float # 固定订货成本(元/次)holding_cost: float # 单位日仓储成本(元/吨·天)shortage_cost: float # 单位日缺货罚金(元/吨·天)daily_demand: float # 日消耗量(吨/天)min_order: float 0.0 # 最小采购量(吨)max_order: float float(inf) # 最大采购量(吨)min_inventory: float 0.0 # 最小库存(吨)max_inventory: float float(inf) # 最大库存(吨)lead_time: int 0 # 采购提前期(天)dataclass(frozenTrue)class PlanningHorizon:计划周期配置 —— 值对象不可变periods: int # 计划周期数天initial_inventory: Dict[str, float] # 初始库存final_inventory: Dict[str, float] # 期末目标库存dataclassclass InventoryState:库存状态 —— 值对象表示某一周期开始时的库存水平period: intinventories: Dict[str, float] # 各原料库存def __hash__(self):用于字典键状态作为DP的键return hash((self.period, tuple(sorted(self.inventories.items()))))def __eq__(self, other):if not isinstance(other, InventoryState):return Falsereturn (self.period other.period andself.inventories other.inventories)def get_key(self) - Tuple[int, ...]:生成状态键用于DP表# 将库存离散化到0.5吨精度减少状态空间precision 0.5keys [self.period]for material_id in sorted(self.inventories.keys()):inv self.inventories[material_id]# 离散化并限制范围discrete_inv round(inv / precision) * precisiondiscrete_inv max(-100, min(200, discrete_inv)) # 限制范围keys.append(discrete_inv)return tuple(keys)dataclassclass PurchaseDecision:采购决策 —— 值对象表示某一周期内的采购量period: intpurchases: Dict[str, float] # 各原料采购量total_cost: float 0.0def __post_init__(self):计算决策总成本self.total_cost sum(self.purchases.values())dataclassclass OptimizationResult:优化结果 —— 值对象status: strtotal_cost: floatpurchase_plan: List[PurchaseDecision] field(default_factorylist)inventory_trajectory: List[InventoryState] field(default_factorylist)cost_breakdown: Dict[str, float] field(default_factorydict)period_costs: List[float] field(default_factorylist)class InventoryDPOptimizer:库存动态规划优化器核心类设计模式: 策略模式 外观模式参考: 北理工《运筹学》§5.2 动态规划的基本概念def __init__(self, materials: List[MaterialConfig],horizon: PlanningHorizon):初始化优化器Args:materials: 原料配置列表horizon: 计划周期配置self.materials: Dict[str, MaterialConfig] {m.id: m for m in materials}self.horizon horizonself.material_ids sorted([m.id for m in materials])# DP表: dp[period][state_key] (min_cost, optimal_decision)self.dp: Dict[int, Dict[Tuple, Tuple[float, Optional[PurchaseDecision]]]] {}# 离散化精度减少状态空间self.precision 0.5self._validate_config()def _validate_config(self) - None:验证配置有效性if len(self.materials) ! 2:raise ValueError(本示例仅支持两原料场景)for material in self.materials.values():if material.daily_demand 0:raise ValueError(f原料{material.name}日消耗量必须大于0)if material.min_inventory material.max_inventory:raise ValueError(f原料{material.name}库存上下限配置错误)def _discretize_inventory(self, inventory: float) - float:库存离散化减少状态空间return round(inventory / self.precision) * self.precisiondef _get_next_state(self, state: InventoryState,decision: PurchaseDecision) - InventoryState:状态转移函数参考: 北理工《运筹学》§5.3 动态规划的基本方程公式: I_{t1} I_t x_t - d_tnew_inventories {}for material_id in self.material_ids:material self.materials[material_id]current_inv state.inventories.get(material_id, 0.0)purchase decision.purchases.get(material_id, 0.0)demand material.daily_demand# 状态转移考虑提前期采购不会立即到货if material.lead_time 0:new_inv current_inv purchase - demandelse:# 简化假设提前期内不消耗本次采购new_inv current_inv - demand# 应用库存约束new_inv max(material.min_inventory,min(material.max_inventory, new_inv))new_inventories[material_id] self._discretize_inventory(new_inv)return InventoryState(state.period 1, new_inventories)def _calculate_period_cost(self, state: InventoryState,decision: PurchaseDecision) - float:计算单周期成本成本构成: 采购成本 仓储成本 缺货罚金total_cost 0.0for material_id in self.material_ids:material self.materials[material_id]purchase decision.purchases.get(material_id, 0.0)inventory state.inventories.get(material_id, 0.0)# 1. 采购成本固定成本 变动成本if purchase 0.001: # 考虑浮点误差total_cost material.fixed_order_costtotal_cost material.unit_cost * purchase# 2. 仓储成本库存为正if inventory 0.001:total_cost material.holding_cost * inventory# 3. 缺货罚金库存为负elif inventory -0.001:total_cost material.shortage_cost * abs(inventory)return total_costdef _generate_decisions(self, state: InventoryState) - List[PurchaseDecision]:生成可行决策集合剪枝优化为减少计算量对采购量进行离散化和约束decisions []# 为每种原料生成采购选项material_options {}for material_id in self.material_ids:material self.materials[material_id]inventory state.inventories.get(material_id, 0.0)demand material.daily_demand# 采购选项0, min_order, 以及满足未来几日需求的量options [0.0]if material.min_order 0:options.append(material.min_order)# 添加满足1-5天需求的采购量for days in range(1, 6):order_qty demand * daysif order_qty material.min_order and order_qty material.max_order:# 离散化order_qty self._discretize_inventory(order_qty)if order_qty not in options:options.append(order_qty)# 添加补充到最大库存的量max_order material.max_inventory - inventoryif max_order material.min_order and max_order material.max_order:max_order self._discretize_inventory(max_order)if max_order 0 and max_order not in options:options.append(max_order)material_options[material_id] sorted(set(options))# 生成组合决策笛卡尔积# 优化只考虑合理的组合避免状态爆炸import itertools# 限制决策数量每周期最多25种组合all_combinations list(itertools.product(*material_options.values()))if len(all_combinations) 25:# 剪枝优先选择接近需求的组合filtered_combinations []for combo in all_combinations:# 计算总采购量total_purchase sum(combo)# 计算满足需求的程度demand_satisfaction 0for i, material_id in enumerate(self.material_ids):material self.materials[material_id]inventory state.inventories.get(material_id, 0.0)purchase combo[i]# 满足未来3天需求为理想状态ideal_inv material.daily_demand * 3if inventory purchase ideal_inv:demand_satisfaction 1filtered_combinations.append((combo, demand_satisfaction))# 按需求满足度排序取前25个filtered_combinations.sort(keylambda x: x[1], reverseTrue)all_combinations [c[0] for c in filtered_combinations[:25]]for combo in all_combinations:purchases {}for i, material_id in enumerate(self.material_ids):purchases[material_id] combo[i]decisions.append(PurchaseDecision(state.period, purchases))return decisionsdef solve(self) - OptimizationResult:执行动态规划求解参考: 北理工《运筹学》§5.4 动态规划的应用使用逆序法从期末向期初递推print(\n 正在执行动态规划求解...)# 初始化DP表self.dp {}# 边界条件期末状态final_state InventoryState(self.horizon.periods,self.horizon.final_inventory)final_key final_state.get_key()self.dp[self.horizon.periods] {final_key: (0.0, None)}# 逆序递推从T到1for t in range(self.horizon.periods, 0, -1):self.dp[t-1] {}# 获取t时刻可能的状态从前一周期的决策推导# 简化从t时刻的状态反向推导t-1时刻的状态if t not in self.dp:continuefor state_key, (future_cost, _) in self.dp[t].items():# 重建t时刻的状态period tinventories {}for i, material_id in enumerate(self.material_ids):inventories[material_id] state_key[i1]state InventoryState(period, inventories)# 生成t-1时刻的决策decisions self._generate_decisions(state)for decision in decisions:# 计算t-1时刻的成本period_cost self._calculate_period_cost(state, decision)# 状态转移得到t时刻的状态next_state self._get_next_state(state, decision)next_key next_state.get_key()# 检查下一状态是否在DP表中if next_key not in self.dp[t]:continue# 计算总成本total_cost period_cost self.dp[t][next_key][0]# 更新DP表prev_state_key self._get_state_key_at_period(t-1, decision)if (prev_state_key not in self.dp[t-1] ortotal_cost self.dp[t-1][prev_state_key][0]):self.dp[t-1][prev_state_key] (total_cost, decision)# 回溯最优解return self._backtrack()def _get_state_key_at_period(self, period: int,decision: PurchaseDecision) - Tuple:根据决策推导前一周期的状态键逆向推导I_t I_{t1} - x_t d_tkeys [period]for material_id in self.material_ids:material self.materials[material_id]# 从下一状态反推当前状态# 简化假设下一状态已知当前库存 下一库存 需求 - 采购# 这里需要更复杂的逻辑简化版本next_inv 0 # 简化keys.append(next_inv)return tuple(keys)def _backtrack(self) - OptimizationResult:回溯最优采购计划print( 正在回溯最优采购计划...)# 初始状态initial_state InventoryState(0,self.horizon.initial_inventory)initial_key initial_state.get_key()if initial_key not in self.dp[0]:return OptimizationResult(status求解失败无可行解,total_costfloat(inf))# 回溯purchase_plan []inventory_trajectory [initial_state]period_costs []total_cost self.dp[0][initial_key][0]current_state initial_statefor t in range(1, self.horizon.periods 1):state_key current_state.get_key()if state_key not in self.dp[t-1]:breakcost, decision self.dp[t-1][state_key]if decision:purchase_plan.append(decision)period_cost self._calculate_period_cost(current_state, decision)period_costs.append(period_cost)# 状态转移current_state self._get_next_state(current_state, decision)inventory_trajectory.append(current_state)else:period_costs.append(0.0)# 成本分解cost_breakdown self._calculate_cost_breakdown(purchase_plan, inventory_trajectory)return OptimizationResult(statusOptimal,total_costtotal_cost,purchase_planpurchase_plan,inventory_trajectoryinventory_trajectory,cost_breakdowncost_breakdown,period_costsperiod_costs)def _calculate_cost_breakdown(self, purchase_plan: List[PurchaseDecision],inventory_trajectory: List[InventoryState]) - Dict[str, float]:计算成本分解breakdown {purchase_fixed_cost: 0.0,purchase_variable_cost: 0.0,holding_cost: 0.0,shortage_cost: 0.0}# 采购成本for decision in purchase_plan:for material_id, qty in decision.purchases.items():if qty 0.001:material self.materials[material_id]breakdown[purchase_fixed_cost] material.fixed_order_costbreakdown[purchase_variable_cost] material.unit_cost * qty# 仓储与缺货成本for state in inventory_trajectory:for material_id, inventory in state.inventories.items():material self.materials[material_id]if inventory 0.001:利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表