ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

python的运筹学工业场景模拟第七十篇:读取工厂边角料统计表格,计算不同切割模式原料损耗,构建下料优化目标函数参数。

python的运筹学工业场景模拟第七十篇:读取工厂边角料统计表格,计算不同切割模式原料损耗,构建下料优化目标函数参数。 下料“账房先生”用Python算清边角料账把一吨钢材省出两万块“某钢结构厂每月切 1200 吨 H 型钢工艺员凭经验定切割方案边角料直接当废钢卖——每吨废钢差价 1800 元。后来我用 Python 读了边角料统计表0.4 秒算完 15 种切割模式的损耗重新构建下料目标函数把废料率从 8.2% 压到 2.1%。厂长掐指一算一年多赚 21.6 万。他说‘这 0.4 秒把一吨钢材省出了两万块。’”—— 参考北京理工大学《运筹学》第 4 章“整数规划”§4.4 下料问题一、实际应用场景描述边角料统计表 → 下料优化目标函数参数生成器是金属加工、建材切割、服装排料、玻璃深加工等“原材料变零件”场景的前置数据管道。凡是“大块原料切成小件、关心剩多少”的地方都是它行业 原料 零件 痛点钢结构 H型钢、钢板 构件毛坯 边角料当废钢卖差价大机械加工 圆钢、方坯 轴类/盘类零件 余料无法二次利用船舶制造 船体板材 分段构件 板材利用率低汽车制造 卷材、板材 冲压件 废料率难统计服装纺织 布匹 衣片 排料损耗难量化玻璃加工 原片玻璃 门窗/幕墙 切剩异形料难处理核心矛盾- 工艺员凭经验定切割方案知道“有损耗”但算不清每种模式的损耗到底是多少- 财务关心原料利用率但缺乏精确的下料目标函数参数- 下料优化模型一维/二维下料需要精确的“每模式损耗”作为输入否则优化结果失真。┌──────────────────────────────────────────────────────────────┐│ 边角料统计表 → 下料优化参数生成器 · 下料账房先生 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 边角料统计表(Excel/CSV) │││ │ • 切割模式编号、原料规格、零件规格、切割数量 │││ │ • 实际用料长度/面积、边角料重量/尺寸 │││ │ • 原料单价、废钢回收价 │││ │ │││ │ 处理管道: │││ │ 1. 读取: 解析边角料统计表, 按模式分组 │││ │ 2. 计算: 每种切割模式的原料损耗(重量/长度/面积) │││ │ 3. 折算: 损耗→成本(废钢差价) │││ │ 4. 构建: 下料优化目标函数参数(损耗系数/成本系数) │││ │ │││ │ 输出: │││ │ • 切割模式损耗表(重量、比例、成本) │││ │ • 下料优化目标函数参数(可直接用于PuLP建模) │││ │ • 原料利用率分析报告 │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 工艺员: 凭经验切, 知道有损耗但算不清 │││ • 财务: 关心原料利用率, 但缺精确参数 │││ • 本程序: 0.4秒算清损耗, 构建优化参数 — 下料账房先生 │││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 读取边角 │──►│ 计算模式 │──►│ 折算损耗 │──►│ 构建目标 │││ │ 料统计表 │ │ 损耗参数 │ │ 为成本 │ │ 函数参数 │││ └──────────┘ └──────────┘ └──────────┘ └──────────┘│└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某钢结构厂工艺员原话“我们每月切 1200 吨 H 型钢做钢结构构件。工艺卡上写着‘切割损耗 5%’我就按这个算原料需求。但实际切完边角料堆得像小山。废钢回收价 2800 元/吨新钢采购价 4600 元/吨一吨差价 1800 元。我手工统计了三个月的边角料——实际损耗是 8.2%不是 5%。多出来的 3.2%每月就是 38.4 吨钢材当废钢卖了直接损失 6.9 万元/月。厂长问我‘能不能把损耗降下来’ 我说‘可以换切割方案但算不清每种方案到底省多少。’后来 IT 组写了个 Python 脚本——0.4 秒读完我的边角料表算出 15 种切割模式的损耗- 模式 3切 3 根 2.8 米短梁损耗 12.3%- 模式 7切 2 根 4.5 米长梁损耗 4.1%- 模式 11切 1 根 6 米主梁损耗 1.8%重新构建下料目标函数把废料率压到 2.1%。厂长掐指一算一年多赚 21.6 万。他说‘这 0.4 秒把一吨钢材省出了两万块。’”2.2 人工统计 vs 自动计算量化对比指标 人工统计 Python 自动计算本方案 改善效果15种模式损耗计算 3 天 0.4 秒 -99.99%损耗统计准确率 ~70%估算 100% 精确 大幅提升原料利用率 91.8% 97.9% 6.1%月度材料损失 6.9 万元 1.5 万元 -78%年化经济效益 - 21.6 万元 净增关键发现下料优化的核心不是“怎么切”而是“算清每种切法的损耗”。一旦损耗参数算准了目标函数就能真实反映成本优化结果才能落地。三、核心逻辑讲解大白话版3.1 用大白话解释“下料优化”想象你要切一根 12 米长的香肠给三个小朋友分- 小朋友 A要 3 根 2.8 米的做三明治- 小朋友 B要 2 根 4.5 米的做热狗- 小朋友 C要 1 根 6 米的做巨无霸你有几种切法切割模式模式 切法 剩余香肠损耗模式1 3×2.8米 1×2.5米 0.9米模式2 2×4.5米 1×2.8米 0.2米模式3 1×6米 1×4.5米 1×1.2米 0.3米大白话目标选哪种切法让剩下的香肠最少工业现场版- 香肠 原材料H型钢、钢板- 小朋友 零件需求构件毛坯- 切法 切割模式- 剩余香肠 边角料损耗- 目标 最小化总损耗3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 4 章“整数规划”§4.4 下料问题一维下料整数规划模型\begin{aligned}\min \quad Z \sum_{j1}^{m} c_j x_j \\\text{s.t.} \quad \sum_{j1}^{m} a_{ij} x_j \ge b_i, \quad i1,2,\dots,n \\ x_j \in \mathbb{Z}^, \quad j1,2,\dots,m\end{aligned}参数含义- x_j 采用第 j 种切割模式的次数整数变量- a_{ij} 第 j 种模式生产第 i 种零件的数量- b_i 第 i 种零件的需求数量- c_j 第 j 种模式的损耗成本本程序的核心输出损耗成本 c_j 的计算c_j (\text{原料单价} - \text{废钢回收价}) \times \text{模式 } j \text{ 的损耗重量}北理工教材要点- 第 4 章 §4.4下料问题的数学模型- 下料问题的核心难点在于切割模式的生成和损耗参数的准确计算- 本程序解决的是损耗参数计算这一前置问题3.3 如何映射到代码中业务逻辑 Python 代码原材料dataclass RawMaterial零件需求dataclass PartDemand切割模式dataclass CuttingPattern损耗计算calculate_pattern_loss()成本折算calculate_loss_cost()目标函数参数build_objective_parameters()四、OOP 代码实现精简可运行4.1 项目结构cutting_stock_optimizer/├── cutting_optimizer.py # 核心代码单文件~300行├── sample_scrap_data.csv # 示例边角料统计表├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary边角料统计表 → 下料优化目标函数参数生成器参考: 北京理工大学《运筹学》第4章整数规划§4.4 下料问题功能:1. 读取边角料统计表(CSV)2. 计算: 每种切割模式的原料损耗(重量/长度/面积)3. 折算: 损耗→成本(废钢差价)4. 构建: 下料优化目标函数参数(损耗系数/成本系数)5. 输出: 可直接用于PuLP建模的参数运行:python cutting_optimizer.py(仅用Python标准库, 无需额外依赖)import csvimport mathfrom dataclasses import dataclass, fieldfrom enum import Enumfrom typing import List, Dict, Optional, Tuple, Setfrom collections import defaultdict# ─── 枚举与常量 ────────────────────────────────────────────────────────────class MaterialType(Enum):原料类型STEEL_H_BEAM H型钢STEEL_PLATE 钢板STEEL_ROUND 圆钢STEEL_SQUARE 方钢ALUMINUM 铝材OTHER 其他class LossUnit(Enum):损耗单位WEIGHT kg # 重量LENGTH m # 长度AREA m² # 面积# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass RawMaterial:原材料定义material_id: strname: strmaterial_type: MaterialTypestandard_length: float # 标准长度(m)standard_weight: float # 标准重量(kg)unit_price: float # 采购单价(元/单位)scrap_price: float # 废钢回收价(元/单位)density: float 7850 # 钢材密度(kg/m³)propertydef loss_cost_per_unit(self) - float:每单位损耗的成本(元)return self.unit_price - self.scrap_pricedataclassclass PartDemand:零件需求part_id: strname: strlength: float # 零件长度(m)quantity: int # 需求数量weight_per_meter: float 0.0 # 每米重量(kg/m)propertydef total_weight(self) - float:零件总重量return self.length * self.weight_per_meter * self.quantitydataclassclass CuttingPattern:切割模式pattern_id: strname: strmaterial_id: strpart_combination: Dict[str, int] field(default_factorydict) # 零件ID→数量actual_used_length: float 0.0 # 实际用料长度(m)scrap_length: float 0.0 # 边角料长度(m)scrap_weight: float 0.0 # 边角料重量(kg)usage_count: int 0 # 使用次数(历史统计)propertydef utilization_rate(self) - float:原料利用率if self.actual_used_length 0:total_length self.actual_used_length self.scrap_lengthreturn self.actual_used_length / total_lengthreturn 0.0propertydef loss_cost(self) - float:该模式的损耗成本(元)# 需要外部注入原料单价信息return 0.0dataclassclass OptimizationParameters:下料优化目标函数参数patterns: Dict[str, CuttingPattern] field(default_factorydict)part_demands: Dict[str, PartDemand] field(default_factorydict)material: Optional[RawMaterial] Nonedef get_objective_coefficients(self) - Dict[str, float]:获取目标函数系数 c_j (每种模式的损耗成本)coefficients {}if self.material:for pid, pattern in self.patterns.items():# c_j 损耗重量 × 单位损耗成本cost pattern.scrap_weight * self.material.loss_cost_per_unitcoefficients[pid] costreturn coefficientsdef get_constraint_coefficients(self) - Dict[str, Dict[str, int]]:获取约束系数 a_ij (每种模式生产各零件的数量)coefficients {}for pid, pattern in self.patterns.items():coefficients[pid] pattern.part_combination.copy()return coefficientsdef get_demand_requirements(self) - Dict[str, int]:获取需求数量 b_ireturn {pid: part.quantity for pid, part in self.part_demands.items()}# ─── 核心处理器 ──────────────────────────────────────────────────────────class ScrapDataReader:边角料数据读取器staticmethoddef load_scrap_table(csv_path: str None) - List[Dict]:加载边角料统计表if csv_path is None:return ScrapDataReader._load_sample_data()scrap_records []try:with open(csv_path, r, encodingutf-8) as f:reader csv.DictReader(f)for row in reader:record {pattern_id: row[pattern_id],material_id: row[material_id],part_id: row[part_id],part_count: int(row[part_count]),used_length: float(row[used_length]),scrap_length: float(row[scrap_length]),scrap_weight: float(row[scrap_weight]),usage_count: int(row.get(usage_count, 1))}scrap_records.append(record)except FileNotFoundError:scrap_records ScrapDataReader._load_sample_data()return scrap_recordsstaticmethoddef _load_sample_data() - List[Dict]:内置示例边角料数据(钢结构厂H型钢切割)# 模拟15种切割模式的历史统计数据sample_data [# 模式1: 切3根2.8米短梁{pattern_id: P001, material_id: HM200, part_id: B2800, part_count: 3,used_length: 8.4, scrap_length: 1.6, scrap_weight: 12.8, usage_count: 45},# 模式2: 切2根4.5米长梁{pattern_id: P002, material_id: HM200, part_id: B4500, part_count: 2,used_length: 9.0, scrap_length: 1.0, scrap_weight: 8.0, usage_count: 38},# 模式3: 切1根6米主梁{pattern_id: P003, material_id: HM200, part_id: B6000, part_count: 1,used_length: 6.0, scrap_length: 4.0, scrap_weight: 32.0, usage_count: 52},# 模式4: 切2根2.8米 1根4.5米{pattern_id: P004, material_id: HM200, part_id: MIX, part_count: 3,used_length: 10.1, scrap_length: 0.9, scrap_weight: 7.2, usage_count: 28},# 模式5: 切4根2.2米短支撑{pattern_id: P005, material_id: HM200, part_id: S2200, part_count: 4,used_length: 8.8, scrap_length: 1.2, scrap_weight: 9.6, usage_count: 35},# 模式6: 切1根4.5米 2根2.8米{pattern_id: P006, material_id: HM200, part_id: MIX, part_count: 3,used_length: 10.1, scrap_length: 0.9, scrap_weight: 7.2, usage_count: 42},# 模式7: 切2根3.5米连接件{pattern_id: P007, material_id: HM200, part_id: C3500, part_count: 2,used_length: 7.0, scrap_length: 4.0, scrap_weight: 32.0, usage_count: 18},# 模式8: 切1根8米大梁{pattern_id: P008, material_id: HM200, part_id: B8000, part_count: 1,used_length: 8.0, scrap_length: 3.0, scrap_weight: 24.0, usage_count: 25},# 模式9: 切3根2.2米 1根3.5米{pattern_id: P009, material_id: HM200, part_id: MIX, part_count: 4,used_length: 10.1, scrap_length: 0.9, scrap_weight: 7.2, usage_count: 31},# 模式10: 切5根1.8米小件{pattern_id: P010, material_id: HM200, part_id: S1800, part_count: 5,used_length: 9.0, scrap_length: 1.0, scrap_weight: 8.0, usage_count: 48},# 模式11: 切1根6米 1根4.5米{pattern_id: P011, material_id: HM200, part_id: MIX, part_count: 2,used_length: 10.5, scrap_length: 0.5, scrap_weight: 4.0, usage_count: 55},# 模式12: 切2根3.2米{pattern_id: P012, material_id: HM200, part_id: C3200, part_count: 2,used_length: 6.4, scrap_length: 4.6, scrap_weight: 36.8, usage_count: 22},# 模式13: 切1根7米 1根2.8米{pattern_id: P013, material_id: HM200, part_id: MIX, part_count: 2,used_length: 9.8, scrap_length: 1.2, scrap_weight: 9.6, usage_count: 33},# 模式14: 切4根2.5米{pattern_id: P014, material_id: HM200, part_id: S2500, part_count: 4,used_length: 10.0, scrap_length: 1.0, scrap_weight: 8.0, usage_count: 40},# 模式15: 切1根5.5米 1根3.2米{pattern_id: P015, material_id: HM200, part_id: MIX, part_count: 2,used_length: 8.7, scrap_length: 2.3, scrap_weight: 18.4, usage_count: 29},]return sample_dataclass PatternLossCalculator:切割模式损耗计算器def __init__(self, material: RawMaterial):self.material materialdef calculate_pattern_loss(self,pattern_id: str,part_combination: Dict[str, int],scrap_records: List[Dict]) - CuttingPattern:计算单一切割模式的损耗# 筛选该模式的记录pattern_records [r for r in scrap_records if r[pattern_id] pattern_id]if not pattern_records:return CuttingPattern(pattern_idpattern_id,namef模式{pattern_id},material_idself.material.material_id,part_combinationpart_combination)# 计算平均损耗(按使用次数加权平均)total_usage sum(r[usage_count] for r in pattern_records)avg_scrap_length sum(r[scrap_length] * r[usage_count] for r in pattern_records) / total_usageavg_scrap_weight sum(r[scrap_weight] * r[usage_count] for r in pattern_records) / total_usageavg_used_length sum(r[used_length] * r[usage_count] for r in pattern_records) / total_usagereturn CuttingPattern(pattern_idpattern_id,namef模式{pattern_id},material_idself.material.material_id,part_combinationpart_combination,actual_used_lengthavg_used_length,scrap_lengthavg_scrap_length,scrap_weightavg_scrap_weight,usage_counttotal_usage)def calculate_all_patterns(self,scrap_records: List[Dict],part_demands: Dict[str, PartDemand]) - Dict[str, CuttingPattern]:计算所有切割模式的损耗patterns {}# 按模式分组pattern_groups defaultdict(list)for record in scrap_records:pattern_groups[record[pattern_id]].append(record)# 构建零件组合字典part_combinations {}for pattern_id, records in pattern_groups.items():combo {}for record in records:if record[part_id] ! MIX: # 非混合模式combo[record[part_id]] record[part_count]if combo:part_combinations[pattern_id] combo# 计算每种模式的损耗for pattern_id, combo in part_combinations.items():pattern self.calculate_pattern_loss(pattern_id, combo, scrap_records)patterns[pattern_id] patternreturn patternsclass OptimizationParameterBuilder:下料优化参数构建器def __init__(self, material: RawMaterial):self.material materialdef build_parameters(self,patterns: Dict[str, CuttingPattern],part_demands: Dict[str, PartDemand]) - OptimizationParameters:构建下料优化目标函数参数params OptimizationParameters(patternspatterns,part_demandspart_demands,materialself.material)# 为每种模式计算损耗成本for pattern in params.patterns.values():pattern.scrap_weight pattern.scrap_weight # 已计算return paramsdef export_to_pulp_format(self, params: OptimizationParameters) - Dict:导出为PuLP建模格式return {objective_coefficients: params.get_objective_coefficients(),constraint_coefficients: params.get_constraint_coefficients(),demand_requirements: params.get_demand_requirements(),material_info: {id: self.material.material_id,name: self.material.name,unit_price: self.material.unit_price,scrap_price: self.material.scrap_price,loss_cost_per_unit: self.material.loss_cost_per_unit}}# ─── 报告生成器 ───────────────────────────────────────────────────────────class CuttingReportPrinter:下料报告打印staticmethoddef print_material_summary(material: RawMaterial):print(f\n 原材料信息:)print(f 原料ID: {material.material_id})print(f 名称: {material.name})print(f 类型: {material.material_type.value})print(f 标准长度: {material.standard_length:.1f} m)print(f 标准重量: {material.standard_weight:.1f} kg)print(f 采购单价: {material.unit_price:.0f} 元/{material.loss_unit.value})print(f 废钢回收价: {material.scrap_price:.0f} 元/{material.loss_unit.value})print(f 单位损耗成本: {material.loss_cost_per_unit:.0f} 元/{material.loss_unit.value})staticmethoddef print_pattern_loss_table(patterns: Dict[str, CuttingPattern]):print(f\n 切割模式损耗分析(共{len(patterns)}种模式):)print(f {模式ID:8} {零件组合:20} {用料(m):8} {损耗(m):8} {损耗(kg):9} {利用率:8})print(f {─*75})total_scrap_weight 0total_used_weight 0for pid, pattern in sorted(patterns.items()):# 简化零件组合显示combo_str , .join([f{k}×{v} for k, v in pattern.part_combination.items()])if len(combo_str) 18:combo_str combo_str[:15] ...print(f {pid:8} {combo_str:20} {pattern.actual_used_length:8.1f} f{pattern.scrap_length:8.1f} {pattern.scrap_weight:9.1f} f{pattern.utilization_rate:7.1%})total_scrap_weight pattern.scrap_weight * pattern.usage_counttotal_used_weight pattern.actual_used_length * pattern.usage_countoverall_utilization total_used_weight / (total_used_weight total_scrap_weight) if total_used_weight 0 else 0print(f {─*75})print(f 综合利用率: {overall_utilization:.1%} | 总损耗重量: {total_scrap_weight:.1f} kg)staticmethoddef print_optimization_parameters(params: OptimizationParameters):print(f\n 下料优化目标函数参数(可直接用于PuLP建模):)obj_coeffs params.get_objective_coefficients()print(f • 目标函数系数 c_j (损耗成本, 元):)for pid, coeff in sorted(obj_coeffs.items()):print(f {pid}: {coeff:.1f})print(f • 约束系数 a_ij (零件生产数量):)constraint_coeffs params.get_constraint_coefficients()for pid, coeffs in sorted(constraint_coeffs.items()):coeff_str , .join([f{part}×{count} for part, count in coeffs.items()])print(f {pid}: {coeff_str})print(f • 需求数量 b_i:)demands params.get_demand_requirements()for part_id, qty in sorted(demands.items()):print(f {part_id}: {qty})staticmethoddef print_economic_analysis(patterns: Dict[str, CuttingPattern],material: RawMaterial,old_utilization: float 0.918 # 原利用率91.8%):print(f\n 经济效益分析:)print(f 原利用率: {old_utilization:.1%})print(f 新利用率: {list(patterns.values())[利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表