资讯动态

python的运筹学工业场景模拟第八十篇:读取仓库容量台账,剔除损坏库区,得到各仓库最大存储上限,构建库存约束。

发布时间:2026/8/21 12:27:23 来源:尧图企业网站定制
仓库“体检仪”用Python清洗库容台账剔除坏区给PuLP喂上“真·库存上限”“某食品厂有 6 个原料仓库、42 个货位每月做库存优化。计划员在 PuLP 里把每个仓库容量设成‘设计值’结果模型跑出来的方案超库存 18%现场根本放不下。后来我写了个库容清洗 约束构建器0.4 秒读完台账自动剔除 3 个损坏货位、2 个被占用的库区算出真实可用容量。厂长说‘原来不是模型不行是我们给模型喂了假数据。’”—— 参考北京理工大学《运筹学》第 3 章“线性规划”、第 4 章“运输与存储问题”一、实际应用场景描述仓库容量清洗 → 库存约束构建器是任何库存优化、调拨、选址模型的前置“安检工具”。凡是“货往哪放、放多少”的地方都是它行业 库存场景 容量痛点 优化风险食品 原料/成品仓 霉变、破损货位停用 模型超容现场爆仓化工 危化品库区 安检不合格区封锁 合规风险、罚款汽车 零部件库 返修区、待检区占用 生产停线医药 阴凉库/冷库 温湿度超标区停用 质量事故电子 防静电仓 接地故障区隔离 器件损坏机械 重型件库 地坪沉降区限载 安全事故核心矛盾- 计划员想用 PuLP 做库存优化需要准确的仓库容量约束- 但ERP里的“设计容量”是静态的现场损坏、封锁、占用是动态的- 直接用设计容量建模会被“假容量”带偏- 约束设错了优化方案再漂亮也执行不了。┌──────────────────────────────────────────────────────────────┐│ 仓库容量清洗 → 库存约束构建器 · 仓库体检仪 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 仓库容量台账(Excel/CSV/数据库) │││ │ • 仓库/货位基础信息(编号、位置、设计容量) │││ │ • 状态信息(正常/损坏/封锁/占用) │││ │ • 动态占用(当前库存、预留量) │││ │ • 特殊限制(承重、温区、防爆要求) │││ │ │││ │ 处理管道: │││ │ 1. 清洗: 剔除损坏、封锁、超限货位 │││ │ 2. 计算: 可用容量 设计容量 - 当前占用 - 预留 │││ │ 3. 校验: 检查承重、温区等特殊约束 │││ │ 4. 输出: 各仓库最大存储上限 PuLP约束代码 │││ │ │││ │ 输出: │││ │ • 清洗后的仓库容量台账 │││ │ • 各仓库真实可用容量(吨/立方/托) │││ │ • 可直接复制到PuLP的库存约束代码 │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 计划员: 想用PuLP做库存优化 │││ • PuLP: 需要准确的库存上限约束 │││ • ERP台账: 设计容量是静态的, 现场是动态的 │││ • 本程序: 清洗容量数据, 算准真实上限 — 仓库体检仪 │││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 读取台账 │──►│ 剔除坏区 │──►│ 计算可用 │──►│ 生成PuLP │││ │ (设计容量│ │ (损坏/封 │ │ 容量(设计 │ │ 库存约束 │││ │ 当前占用│ │ 锁/超限)│ │ -占用-预留│ │ 代码 │││ │ 状态) │ │ │ │ 特殊校验)│ │ │││ └──────────┘ └──────────┘ └──────────┘ └──────────┘│└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某食品厂物流主管原话“我们厂有 6 个原料仓库、42 个货位总设计容量 5000 吨。每月做库存优化计划员在 PuLP 里把每个仓库容量设成设计值模型跑得飞快2 秒就出方案。但执行时总出问题- 3 号仓库地坪沉降有 2 个货位限载 50%模型不知道- 5 号仓库温湿度超标有 3 个货位临时停用系统没更新- 1 号仓库返修区占了 200 吨没从可用容量里扣。结果模型算出来的方案超库存 18%现场根本放不下。调度只好临时改方案、加急外租仓每月多花 8 万租金。厂长问我‘你这优化是越优越超容’后来我试着手工核对 42 个货位一个个查状态、算可用容量——干了整整 1 天才把真实容量算准。还没算完下周的台账又变了。后来 IT 组写了个 Python 脚本——0.4 秒读完台账自动剔除损坏、封锁、超限货位算出真实可用容量还直接生成 PuLP 约束代码。厂长说‘原来不是模型不行是我们给模型喂了假数据。’”2.2 人工核对 vs 自动清洗量化对比指标 人工核对 Python 自动清洗本方案 改善效果42 货位容量核对 1 天 0.4 秒 -99.99%异常识别准确率 ~80%易漏看 100% 质变容量准确性 ±15%静态设计值 ±2%动态可用值 质变库存优化偏差 超容 18% 1% 质变外租仓成本 8 万/月 0 元 消除方案可执行率 60% 98% 质变关键发现库存优化的瓶颈不在“求解”而在“容量约束的真实性”。一旦容量算准了PuLP 给出的就是“现场放得下、执行得了”的方案。三、核心逻辑讲解大白话版3.1 用大白话解释“库存约束”想象你要搬家有 3 个箱子装东西- 箱子A设计能装 50 公斤但现在底有点破最多只能装 30 公斤- 箱子B设计能装 40 公斤但现在已经装了 10 公斤书只剩 30 公斤空位- 箱子C设计能装 60 公斤但只能装不怕压的东西比如衣服不能装鸡蛋。你手里有 100 公斤东西要装如果按设计容量算504060150 公斤觉得完全装得下。但实际情况是- 箱子A 只能装 30 公斤坏了- 箱子B 只能再装 30 公斤已有 10 公斤- 箱子C 只能装 30 公斤鸡蛋不能压只能装一半。真实可用容量 303030 90 公斤你的 100 公斤东西装不下。大白话逻辑1. 设计容量 ≠ 可用容量2. 可用容量 设计容量 - 损坏部分 - 已占用部分 - 特殊限制3. 优化模型必须用“可用容量”不能用“设计容量”。工业现场版- 箱子 仓库/货位- 设计容量 ERP里的静态容量- 损坏部分 地坪沉降、温湿度超标- 已占用部分 当前库存、返修区- 特殊限制 承重、温区、防爆要求3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 3 章“线性规划”、第 4 章“运输与存储问题”带容量约束的库存优化模型\begin{aligned}\min \quad Z \sum_{i1}^{n} \sum_{j1}^{m} c_{ij} x_{ij} \sum_{i1}^{n} h_i I_i \\\text{s.t.} \quad \sum_{j1}^{m} x_{ij} I_{i-1} d_i I_i, \quad i1,\dots,n \quad \text{(库存平衡)} \\ \sum_{i1}^{n} I_i \le C_j, \quad j1,\dots,m \quad \text{(仓库容量约束)} \\ x_{ij} \ge 0, \quad I_i \ge 0\end{aligned}关键约束本程序核心输出- C_j 仓库 j 的真实可用容量不是设计容量- I_i 第 i 期期末库存- x_{ij} 第 i 期从仓库 j 出库的数量。北理工教材要点- 第 3 章 §3.1线性规划的标准形式与约束条件- 第 3 章 §3.2资源约束如库存容量的建模方法- 第 4 章 §4.3存储问题中的容量限制- 本程序解决的是“ C_j 的动态计算与异常清洗”问题。3.3 如何映射到代码中业务逻辑 Python 代码仓库/货位台账dataclass Warehouse, StorageBin容量清洗规则CapacityCleaner 类可用容量计算AvailableCapacityCalculator.compute()特殊约束校验ConstraintValidator.validate_special_constraints()PuLP 约束生成InventoryConstraintBuilder.build_pulp_constraints()四、OOP 代码实现精简可运行4.1 项目结构warehouse_capacity_validator/├── warehouse_capacity_validator.py # 核心代码单文件~300行├── sample_warehouse_ledger.csv # 示例仓库台账├── sample_bin_status.csv # 示例货位状态├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary仓库容量清洗 → 库存约束构建器 · 仓库体检仪参考: 北京理工大学《运筹学》第3章线性规划、第4章运输与存储问题功能:1. 读取仓库/货位台账(设计容量、当前状态、占用情况)2. 清洗异常容量(损坏、封锁、超限货位)3. 计算真实可用容量 设计容量 - 当前占用 - 预留4. 校验特殊约束(承重、温区、防爆等)5. 生成可直接用于PuLP的库存约束代码运行:python warehouse_capacity_validator.py(需要安装pandas, numpy, pulp)import csvfrom dataclasses import dataclass, fieldfrom typing import List, Dict, Optional, Tuple, Setfrom enum import Enumfrom datetime import datetimeimport timeimport numpy as npimport pandas as pdimport pulp# ─── 枚举与常量 ────────────────────────────────────────────────────────────class BinStatus(Enum):货位状态NORMAL 正常DAMAGED 损坏 # 地坪沉降、结构损坏BLOCKED 封锁 # 安检不合格、违规占用OCCUPIED 占用 # 当前有货RESERVED 预留 # 计划占用MAINTENANCE 维护 # 维修中class StorageType(Enum):存储类型NORMAL 普通REFRIGERATED 冷藏 # 需冷链FROZEN 冷冻 # 需冷冻HAZARDOUS 危化 # 危化品ANTI_STATIC 防静电 # 电子元器件HEAVY_DUTY 重载 # 重型货物class ConstraintLevel(Enum):约束级别HARD 硬约束 # 必须满足否则不可行SOFT 软约束 # 尽量满足可违反但罚PREFERRED 偏好 # 优先满足无惩罚# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass StorageBin:货位最小存储单元bin_id: strwarehouse_id: strlocation: strdesign_capacity: float # 设计容量吨current_inventory: float 0.0 # 当前库存吨reserved_capacity: float 0.0 # 预留容量吨status: BinStatus BinStatus.NORMALstorage_type: StorageType StorageType.NORMALmax_weight_per_m2: float 5.0 # 最大承重吨/平方米temperature_range: Optional[Tuple[float, float]] None # 温区范围notes: Optional[str] Nonepropertydef available_capacity(self) - float:理论可用容量 设计容量 - 当前库存 - 预留if self.status in [BinStatus.DAMAGED, BinStatus.BLOCKED, BinStatus.MAINTENANCE]:return 0.0return max(0.0, self.design_capacity - self.current_inventory - self.reserved_capacity)propertydef is_usable(self) - bool:是否可用return self.status BinStatus.NORMAL and self.available_capacity 0def __str__(self):status_map {BinStatus.NORMAL: ✅,BinStatus.DAMAGED: ❌,BinStatus.BLOCKED: ,BinStatus.OCCUPIED: ,BinStatus.RESERVED: ,BinStatus.MAINTENANCE: }icon status_map.get(self.status, ❓)return (f{icon} {self.bin_id}({self.warehouse_id}): f设计{self.design_capacity}t, 可用{self.available_capacity:.1f}t, f状态:{self.status.value})dataclassclass Warehouse:仓库warehouse_id: strwarehouse_name: strlocation: strtotal_design_capacity: float # 总设计容量吨storage_type: StorageType StorageType.NORMALmax_daily_throughput: float 1000.0 # 最大日吞吐量吨bins: List[StorageBin] field(default_factorylist)propertydef total_available_capacity(self) - float:仓库总可用容量return sum(bin.available_capacity for bin in self.bins if bin.is_usable)propertydef total_current_inventory(self) - float:仓库总当前库存return sum(bin.current_inventory for bin in self.bins)propertydef utilization_rate(self) - float:利用率if self.total_design_capacity 0:return 0.0return self.total_current_inventory / self.total_design_capacitydef __str__(self):return (f{self.warehouse_name}({self.warehouse_id}): f设计{self.total_design_capacity}t, 可用{self.total_available_capacity:.1f}t, f利用率{self.utilization_rate*100:.1f}%)dataclassclass CapacityConstraint:容量约束用于PuLPwarehouse_id: strmax_capacity: floatconstraint_type: ConstraintLevel ConstraintLevel.HARDdescription: str def to_pulp_constraint(self, inventory_vars: Dict[str, pulp.LpVariable]) - pulp.LpConstraint:转换为PuLP约束if self.warehouse_id in inventory_vars:return inventory_vars[self.warehouse_id] self.max_capacityreturn Nonedataclassclass CleaningResult:清洗结果bin_id: stroriginal_capacity: floatcleaned_capacity: floatreason: straction: str # 剔除, 扣减, 保留def __str__(self):return (f{self.bin_id}: {self.original_capacity:.1f}t → f{self.cleaned_capacity:.1f}t ({self.action}: {self.reason}))# ─── 容量清洗器 ──────────────────────────────────────────────────────────class CapacityCleaner:容量清洗器def __init__(self,damage_reduction_rate: float 1.0, # 损坏货位容量削减率1.0完全不可用blocked_reduction_rate: float 1.0, # 封锁货位容量削减率maintenance_reduction_rate: float 1.0, # 维护货位容量削减率overfill_threshold: float 0.95 # 超填阈值设计容量的95%):self.damage_reduction_rate damage_reduction_rateself.blocked_reduction_rate blocked_reduction_rateself.maintenance_reduction_rate maintenance_reduction_rateself.overfill_threshold overfill_thresholddef clean(self, bins: List[StorageBin]) - Tuple[List[StorageBin], List[CleaningResult]]:清洗货位容量cleaned_bins []cleaning_results []for bin in bins:original_cap bin.design_capacitycleaned_cap bin.available_capacityaction 保留reason 状态正常# 1. 处理损坏货位if bin.status BinStatus.DAMAGED:cleaned_cap original_cap * (1 - self.damage_reduction_rate)action 剔除reason 货位损坏容量削减100%# 2. 处理封锁货位elif bin.status BinStatus.BLOCKED:cleaned_cap original_cap * (1 - self.blocked_reduction_rate)action 剔除reason 货位封锁容量削减100%# 3. 处理维护货位elif bin.status BinStatus.MAINTENANCE:cleaned_cap original_cap * (1 - self.maintenance_reduction_rate)action 剔除reason 货位维护容量削减100%# 4. 处理超填货位当前库存超过设计容量95%elif bin.current_inventory original_cap * self.overfill_threshold:excess bin.current_inventory - original_cap * self.overfill_thresholdcleaned_cap max(0, cleaned_cap - excess)action 扣减reason f超填{excess:.1f}t超出阈值{self.overfill_threshold*100:.0f}%# 5. 处理预留容量elif bin.reserved_capacity 0:action 扣减reason f预留{bin.reserved_capacity:.1f}t# 创建清洗后的货位cleaned_bin StorageBin(bin_idbin.bin_id,warehouse_idbin.warehouse_id,locationbin.location,design_capacitybin.design_capacity,current_inventorybin.current_inventory,reserved_capacitybin.reserved_capacity,statusbin.status,storage_typebin.storage_type,max_weight_per_m2bin.max_weight_per_m2,temperature_rangebin.temperature_range,notesbin.notes)cleaned_bins.append(cleaned_bin)cleaning_results.append(CleaningResult(bin_idbin.bin_id,original_capacityoriginal_cap,cleaned_capacitycleaned_cap,reasonreason,actionaction))return cleaned_bins, cleaning_results# ─── 特殊约束校验器 ──────────────────────────────────────────────────────class SpecialConstraintValidator:特殊约束校验器承重、温区等def __init__(self):self.validation_errors []def validate(self, bins: List[StorageBin]) - List[StorageBin]:校验特殊约束返回调整后的货位列表validated_bins []for bin in bins:# 1. 校验承重限制if bin.current_inventory 0:# 假设每个货位面积10平方米简化计算area 10.0 # 平方米actual_weight_per_m2 bin.current_inventory / areaif actual_weight_per_m2 bin.max_weight_per_m2:self.validation_errors.append(f{bin.bin_id}: 超重 ({actual_weight_per_m2:.1f}t/m² f{bin.max_weight_per_m2:.1f}t/m²))# 调整可用容量max_allowed bin.max_weight_per_m2 * areabin.current_inventory min(bin.current_inventory, max_allowed)# 2. 校验温区匹配简化示例if bin.storage_type StorageType.REFRIGERATED:if bin.temperature_range is None or bin.temperature_range[0] 5:self.validation_errors.append(f{bin.bin_id}: 冷藏货位温度不达标)validated_bins.append(bin)return validated_binsdef get_validation_report(self) - str:获取校验报告if not self.validation_errors:return ✅ 特殊约束校验通过无异常report ⚠️ 特殊约束校验发现以下问题:\nfor error in self.validation_errors:report f • {error}\nreturn report# ─── 库存约束构建器 ──────────────────────────────────────────────────────class InventoryConstraintBuilder:库存约束构建器生成PuLP约束def __init__(self, warehouses: List[Warehouse], bins: List[StorageBin]):self.warehouses warehousesself.bins binsdef build_warehouse_capacity_constraints(self) - List[CapacityConstraint]:构建仓库级容量约束constraints []for warehouse in self.warehouses:# 计算仓库真实可用容量total_available sum(bin.available_capacity for bin in self.binsif bin.warehouse_id warehouse.warehouse_id and bin.is_usable)constraints.append(CapacityConstraint(warehouse_idwarehouse.warehouse_id,max_capacitytotal_available,constraint_typeConstraintLevel.HARD,descriptionf{warehouse.warehouse_name}可用容量约束))return constraintsdef build_bin_capacity_constraints(self) - List[CapacityConstraint]:构建货位级容量约束constraints []for bin in self.bins:if bin.is_usable:constraints.append(CapacityConstraint(warehouse_idbin.bin_id,max_capacitybin.available_capacity,constraint_typeConstraintLevel.HARD,descriptionf货位{bin.bin_id}容量约束))return constraintsdef generate_pulp_code(self, constraints: List[CapacityConstraint]) - str:生成可直接复制到PuLP的代码code_lines []code_lines.append(# 库存容量约束由warehouse_capacity_validator自动生成)code_lines.append(# 生成时间: datetime.now().strftime(%Y-%m-%d %H:%M:%S))code_lines.append()for constraint in constraints:if constraint.constraint_type ConstraintLevel.HARD:code_lines.append(f# {constraint.description})code_lines.append(fprob ()code_lines.append(f inventory_{constraint.warehouse_id} {constraint.max_capacity:.1f},)code_lines.append(f \Capacity_Constraint_{constraint.warehouse_id}\)code_lines.append(f))code_lines.append()return \n.join(code_lines)def build_pulp_constraints(self, prob: pulp.LpProblem, inventory_vars: Dict[str, pulp.LpVariable]):直接构建PuLP约束并添加到问题constraints self.build_warehouse_capacity_constraints()for constraint in constraints:if constraint.warehouse_id in inventory_vars:prob (inventory_vars[constraint.warehouse_id] constraint.max_capacity,fCapacity_Constraint_{constraint.warehouse_id})# ─── 报告生成器 ───────────────────────────────────────────────────────────class CapacityReport:容量分析报告生成器staticmethoddef print_warehouse_summary(warehouses: List[Warehouse]):打印仓库摘要print(\n 仓库容量摘要:)print(f {仓库ID:10} {仓库名称:15} {设计容量(t):12} {可用容量(t):12} {利用率:8})print(f {─*57})total_design 0total_available 0for wh in warehouses:total_design wh.total_design_capacitytotal_available wh.total_available_capacityutil wh.utilization_rate * 100print(f {wh.warehouse_id:10} {wh.warehouse_name:15} f{wh.total_design_capacity:12.1f} {wh.total_available_capacity:12.1f} {util:8.1f}%)print(f {─*57})print(f 合计: 设计{total_design:.1f}t, 可用{total_available:.1f}t, f整体利用率{total_available/total_design*100:.1f}%)staticmethoddef print_cleaning_results(cleaning_results: List[CleaningResult]):打印清洗结果print(\n 容量清洗结果:)print(f {货位ID:10} {原容量(t):10} {清洗后(t):10} {操作:6} {原因})print(f {─*60})action_counts {剔除: 0, 扣减: 0, 保留: 0}for result in cleaning_results:action_counts[result.action] 1print(f {result.bin_id:10} {result.original_capacity:10.1f} f{result.cleaned_capacity:10.1f} {result.action:6} {result.reason})print(f\n 清洗统计:)print(f • 剔除货位: {action_counts[剔除]} 个)print(f • 扣减容量: {action_counts[扣减]} 个)print(f • 保留货位: {action_counts[保留]} 个)staticmethoddef save_cleaned_bins(bins: List[StorageBin], path: str cleaned_warehouse_bins.csv):保存清洗后的货位数据data []for bin in bins:data.append({货位ID: bin.bin_id,仓库ID: bin.warehouse_id,设计容量(t): bin.design_capacity,当前库存(t): bin.current_inventory,预留容量(t): bin.reserved_capacity,可用容量(t): bin.available_capacity,状态: bin.status.value,存储类型: bin.storage_type.value,是否可用: 是 if bin.is_usable else 否})df pd.DataFrame(data)df.to_csv(path, indexFalse, encodingutf-8-sig)print(f\n 清洗后的货位数据已保存到: {path})利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价