尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

python的运筹学工业场景模拟第七十一篇:批量读取5组不同市场需求报表,批量转换为模型输入,方便批量对比多场景计算。

python的运筹学工业场景模拟第七十一篇:批量读取5组不同市场需求报表,批量转换为模型输入,方便批量对比多场景计算。 多场景“翻译官”用Python把5份需求报表批量转成模型输入一次跑完所有方案“某饲料厂有 3 条制粒线、12 种配方的原料。每月要接 5 个不同区域的市场需求报表格式还不一样华北是 Excel、华东是 CSV、华南是 JSON、华中是数据库导出的 TXT、西北是 ERP 网页下载的 XLSX。计划员要把这 5 份报表手动抄进排产模型算 5 遍再对比哪个方案总能耗最低。光‘抄表 建模’就要 2 天还经常抄错。后来我用 Python 写了个批量读取 统一转换程序0.8 秒读完 5 份报表0.2 秒生成 5 套模型输入PuLP 一次算完直接给出最优方案。厂长说‘这 1 秒把 2 天和 3 万能耗费都省了。’”—— 参考北京理工大学《运筹学》第 1 章“线性规划”、第 3 章“运输问题”一、实际应用场景描述多市场需求报表 → 批量模型输入转换器是多区域、多场景、多约束排产/调度/配货场景的前置数据管道。凡是“一个模型多份需求”的地方都是它行业 模型对象 多场景来源 痛点饲料/食品 配方排产 区域需求报表 格式不一、人工录入慢化工 生产计划 客户订单 多版本对比难钢铁 轧制计划 区域销售预测 场景切换成本高水泥 发运调度 工地需求 多目标权衡难医药 生产排程 医院/渠道订单 合规性与成本冲突快消 配送优化 城市需求 多场景仿真慢核心矛盾- 市场部给的是“业务视角的需求报表”Excel/CSV/JSON/数据库- 计划部需要的是“模型视角的输入参数”决策变量、约束右端项、目标系数- 两者之间是“翻译鸿沟”人工翻译既慢又容易错。┌──────────────────────────────────────────────────────────────┐│ 多市场需求报表 → 批量模型输入转换器 · 多场景翻译官 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 5组不同市场需求报表(不同格式/结构) │││ │ • 华北: Excel(.xlsx) - 产品需求、产能约束 │││ │ • 华东: CSV(.csv) - 客户订单、运输成本 │││ │ • 华南: JSON(.json) - 配方需求、原料限制 │││ │ • 华中: TXT(制表符) - 生产计划、能耗约束 │││ │ • 西北: XLSX(ERP导出) - 综合需求、库存约束 │││ │ │││ │ 处理管道: │││ │ 1. 批量读取: 自动识别格式, 统一解析 │││ │ 2. 标准化: 映射到统一数据模型(需求、约束、目标) │││ │ 3. 批量转换: 生成5套模型输入参数 │││ │ 4. 批量计算: 调用PuLP/Scipy批量求解 │││ │ 5. 对比输出: 多场景结果对比表 │││ │ │││ │ 输出: │││ │ • 5套标准化模型输入(可直接用于PuLP建模) │││ │ • 5套优化结果(目标值、决策变量、约束余量) │││ │ • 多场景对比报告(最优方案、敏感度分析) │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 市场部: 业务报表(格式五花八门) │││ • 计划部: 模型输入(结构高度统一) │││ • 本程序: 批量翻译转换 — 多场景翻译官 │││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 批量读取 │──►│ 标准化映射│──►│ 批量生成 │──►│ 批量求解 │││ │ 多格式报表│ │ 统一数据 │ │ 模型输入 │ │ 多场景 │││ └──────────┘ └──────────┘ └──────────┘ └──────────┘│└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某饲料厂计划员原话“我们厂有 3 条制粒线、12 种配方的原料。每月 25 号我要接 5 个区域的市场需求- 华北Excel12 种产品需求 2 条线产能上限- 华东CSV18 个客户订单 运输成本- 华南JSON 接口8 种配方需求 原料库存- 华中TXT 文本10 种产品需求 能耗约束- 西北ERP 导出的 XLSX综合需求 库存约束。我得把 5 份报表手动抄进排产模型算 5 遍再对比哪个方案总能耗最低。光‘抄表 建模’就要 2 天还经常抄错。上个月我把华北的‘玉米需求 1200 吨’抄成‘2100 吨’结果多买了 900 吨玉米多花了 3 万元还占仓库。后来 IT 组写了个 Python 脚本——0.8 秒读完 5 份报表0.2 秒生成 5 套模型输入PuLP 一次算完直接给出最优方案。厂长说‘这 1 秒把 2 天和 3 万能耗费都省了。’”2.2 人工处理 vs 批量转换量化对比指标 人工处理 Python 批量转换本方案 改善效果5 份报表录入 2 天 1 秒 -99.99%录入错误率 ~5%抄错/漏项 0% 消除多场景建模 逐个手工建模 批量自动生成 效率飞跃方案对比 手动整理对比 自动生成对比表 直观隐性成本 3 万元/月多买原料 0 元 消除决策时效 3 天后出方案 1 秒后出方案 实时关键发现多场景优化的瓶颈不在“求解”而在“输入准备”。一旦输入能批量生成求解只是“顺便的事”。三、核心逻辑讲解大白话版3.1 用大白话解释“多场景批量建模”想象你要给 5 个朋友分别做旅行计划- 朋友 A想去北京预算 50003 天- 朋友 B想去上海预算 40004 天- 朋友 C想去成都预算 60005 天- 朋友 D想去西安预算 45003 天- 朋友 E想去杭州预算 55004 天。你手里有同一个“旅行规划模型”- 决策变量住哪、吃啥、玩啥- 约束预算、时间- 目标玩得最爽满意度最高。问题来了- 5 个朋友的需求格式不一样有的写在微信里有的发在邮件里有的口述- 你得把 5 份需求“翻译”成模型输入- 然后跑 5 遍模型再对比哪个方案最划算。大白话流程1. 批量读需求把 5 份需求都读进来2. 统一格式不管原来啥格式都转成“预算、时间、目的地”3. 批量建模用同一个模型换 5 套输入4. 批量求解一次跑完 5 个方案5. 对比结果看哪个朋友玩得最爽。工业现场版- 朋友 区域市场- 旅行计划 生产/排产/配货方案- 需求格式不一 报表格式五花八门- 统一模型 同一个运筹学模型- 批量转换 把业务需求翻译成模型输入3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 1 章“线性规划”、第 3 章“运输问题”多场景线性规划模型族\begin{aligned}\min / \max \quad Z^k c^{kT} x^k, \quad k1,2,\dots,K \\\text{s.t.} \quad A^k x^k \le b^k \\ x^k \ge 0\end{aligned}核心思想- 模型结构相同都是线性规划- 输入参数不同目标系数 c^k 、约束右端项 b^k - 批量求解对 k1,\dots,K 循环求解。北理工教材要点- 第 1 章 §1.2线性规划的三个要素决策变量、目标函数、约束条件- 第 3 章 §3.1运输问题是多场景优化的典型代表- 第 3 章 §3.2运输表的标准化表示- 本程序解决的是“多场景输入参数的批量生成”问题3.3 如何映射到代码中业务逻辑 Python 代码市场需求报表dataclass MarketDemandReport报表读取器BaseReportReader 各格式子类数据标准化DataStandardizer.standardize()模型输入生成器ModelInputBuilder.build_inputs()批量求解器BatchSolver.solve_all_scenarios()结果对比器ResultComparator.compare()四、OOP 代码实现精简可运行4.1 项目结构multi_scenario_converter/├── multi_scenario_converter.py # 核心代码单文件~350行├── sample_reports/ # 示例报表目录│ ├── north_china.xlsx│ ├── east_china.csv│ ├── south_china.json│ ├── central_china.txt│ └── northwest.xlsx├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary多市场需求报表 → 批量模型输入转换器参考: 北京理工大学《运筹学》第1章线性规划、第3章运输问题功能:1. 批量读取5种不同格式的市场需求报表2. 标准化: 映射到统一数据模型(需求、约束、目标)3. 批量转换: 生成5套模型输入参数4. 批量计算: 调用PuLP批量求解5. 对比输出: 多场景结果对比表运行:python multi_scenario_converter.py(需要安装pandas, openpyxl, pulp)import jsonimport csvimport pandas as pdfrom pathlib import Pathfrom dataclasses import dataclass, fieldfrom typing import List, Dict, Optional, Any, Unionfrom abc import ABC, abstractmethodfrom enum import Enumimport pulpimport time# ─── 枚举与常量 ────────────────────────────────────────────────────────────class ReportFormat(Enum):报表格式EXCEL excelCSV csvJSON jsonTXT txtXLSX xlsxclass ScenarioRegion(Enum):场景区域NORTH 华北EAST 华东SOUTH 华南CENTRAL 华中NORTHWEST 西北# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass ProductDemand:产品需求product_id: strproduct_name: strdemand_quantity: floatunit: str 吨def __str__(self):return f{self.product_name}: {self.demand_quantity}{self.unit}dataclassclass CapacityConstraint:产能约束resource_id: strresource_name: strmax_capacity: floatunit: str 吨/天def __str__(self):return f{self.resource_name} ≤ {self.max_capacity}{self.unit}dataclassclass CostCoefficient:成本系数product_id: strvariable_cost: float # 变动成本(元/吨)fixed_cost: float 0.0 # 固定成本(元)propertydef total_unit_cost(self) - float:return self.variable_cost self.fixed_costdataclassclass MarketDemandReport:市场需求报表(标准化后)scenario_id: strregion: ScenarioRegionreport_format: ReportFormatproduct_demands: List[ProductDemand] field(default_factorylist)capacity_constraints: List[CapacityConstraint] field(default_factorylist)cost_coefficients: List[CostCoefficient] field(default_factorylist)raw_data: Optional[Dict] Nonedef get_total_demand(self) - float:总需求量return sum(d.demand_quantity for d in self.product_demands)def get_demand_dict(self) - Dict[str, float]:需求字典(产品ID→需求量)return {d.product_id: d.demand_quantity for d in self.product_demands}def get_cost_dict(self) - Dict[str, float]:成本字典(产品ID→单位成本)return {c.product_id: c.total_unit_cost for c in self.cost_coefficients}dataclassclass ModelInput:模型输入参数(可直接用于PuLP)scenario_id: strregion: ScenarioRegiondecision_variables: List[str]objective_coefficients: Dict[str, float]constraints: Dict[str, Dict[str, float]] # 约束名→{变量名: 系数}rhs: Dict[str, float] # 约束名→右端项variable_bounds: Dict[str, Tuple[float, float]] field(default_factorydict)def to_pulp_problem(self) - pulp.LpProblem:转换为PuLP问题对象prob pulp.LpProblem(fProduction_Plan_{self.scenario_id}, pulp.LpMinimize)# 决策变量x pulp.LpVariable.dicts(x,self.decision_variables,lowBound0,catContinuous)# 目标函数prob pulp.lpSum(self.objective_coefficients[var] * x[var]for var in self.decision_variables)# 约束条件for constraint_name, coefficients in self.constraints.items():constraint_expr pulp.lpSum(coefficients[var] * x[var]for var in coefficientsif var in x)prob constraint_expr self.rhs[constraint_name], constraint_namereturn probdataclassclass ScenarioResult:场景求解结果scenario_id: strregion: ScenarioRegionobjective_value: floatvariable_values: Dict[str, float]solve_time: floatstatus: strdef summary(self) - str:return (f{self.region.value}({self.scenario_id}): f目标值{self.objective_value:.2f}元, f状态{self.status}, 耗时{self.solve_time:.3f}s)# ─── 报表读取器策略模式──────────────────────────────────────────────────class BaseReportReader(ABC):报表读取器基类abstractmethoddef read(self, file_path: Path) - MarketDemandReport:passclass ExcelReportReader(BaseReportReader):Excel报表读取器def read(self, file_path: Path) - MarketDemandReport:# 读取需求表demand_df pd.read_excel(file_path, sheet_name需求)capacity_df pd.read_excel(file_path, sheet_name产能)cost_df pd.read_excel(file_path, sheet_name成本)report MarketDemandReport(scenario_idfile_path.stem,regionScenarioRegion.NORTH,report_formatReportFormat.EXCEL)# 解析需求for _, row in demand_df.iterrows():report.product_demands.append(ProductDemand(product_idrow[产品ID],product_namerow[产品名称],demand_quantityrow[需求量],unitrow.get(单位, 吨)))# 解析产能for _, row in capacity_df.iterrows():report.capacity_constraints.append(CapacityConstraint(resource_idrow[资源ID],resource_namerow[资源名称],max_capacityrow[最大产能],unitrow.get(单位, 吨/天)))# 解析成本for _, row in cost_df.iterrows():report.cost_coefficients.append(CostCoefficient(product_idrow[产品ID],variable_costrow[变动成本],fixed_costrow.get(固定成本, 0.0)))return reportclass CsvReportReader(BaseReportReader):CSV报表读取器def read(self, file_path: Path) - MarketDemandReport:report MarketDemandReport(scenario_idfile_path.stem,regionScenarioRegion.EAST,report_formatReportFormat.CSV)with open(file_path, r, encodingutf-8) as f:reader csv.DictReader(f)for row in reader:# CSV格式: 产品ID,产品名称,需求量,单位,变动成本,固定成本,产能上限report.product_demands.append(ProductDemand(product_idrow[产品ID],product_namerow[产品名称],demand_quantityfloat(row[需求量]),unitrow.get(单位, 吨)))report.cost_coefficients.append(CostCoefficient(product_idrow[产品ID],variable_costfloat(row[变动成本]),fixed_costfloat(row.get(固定成本, 0.0))))if 产能上限 in row and row[产能上限]:report.capacity_constraints.append(CapacityConstraint(resource_idLINE001,resource_name生产线1,max_capacityfloat(row[产能上限]),unit吨/天))return reportclass JsonReportReader(BaseReportReader):JSON报表读取器def read(self, file_path: Path) - MarketDemandReport:with open(file_path, r, encodingutf-8) as f:data json.load(f)report MarketDemandReport(scenario_idfile_path.stem,regionScenarioRegion.SOUTH,report_formatReportFormat.JSON,raw_datadata)# 解析需求for item in data.get(demands, []):report.product_demands.append(ProductDemand(product_iditem[product_id],product_nameitem[product_name],demand_quantityitem[quantity],unititem.get(unit, 吨)))# 解析成本for item in data.get(costs, []):report.cost_coefficients.append(CostCoefficient(product_iditem[product_id],variable_costitem[variable_cost],fixed_costitem.get(fixed_cost, 0.0)))# 解析产能for item in data.get(capacities, []):report.capacity_constraints.append(CapacityConstraint(resource_iditem[resource_id],resource_nameitem[resource_name],max_capacityitem[max_capacity],unititem.get(unit, 吨/天)))return reportclass TxtReportReader(BaseReportReader):TXT报表读取器(制表符分隔)def read(self, file_path: Path) - MarketDemandReport:report MarketDemandReport(scenario_idfile_path.stem,regionScenarioRegion.CENTRAL,report_formatReportFormat.TXT)with open(file_path, r, encodingutf-8) as f:lines f.readlines()# 解析表头headers lines[0].strip().split(\t)for line in lines[1:]:if not line.strip():continuevalues line.strip().split(\t)if len(values) 3:report.product_demands.append(ProductDemand(product_idvalues[0],product_namevalues[1],demand_quantityfloat(values[2]),unitvalues[3] if len(values) 3 else 吨))# TXT格式中成本信息在后续列if len(values) 5:report.cost_coefficients.append(CostCoefficient(product_idvalues[0],variable_costfloat(values[4]),fixed_costfloat(values[5]) if len(values) 5 else 0.0))return reportclass ReportReaderFactory:报表读取器工厂_readers {ReportFormat.EXCEL: ExcelReportReader,ReportFormat.CSV: CsvReportReader,ReportFormat.JSON: JsonReportReader,ReportFormat.TXT: TxtReportReader,ReportFormat.XLSX: ExcelReportReader, # XLSX用Excel读取器}classmethoddef get_reader(cls, format: ReportFormat) - BaseReportReader:reader_class cls._readers.get(format)if not reader_class:raise ValueError(f不支持的报表格式: {format})return reader_class()# ─── 数据标准化器 ──────────────────────────────────────────────────────────class DataStandardizer:数据标准化器staticmethoddef standardize(report: MarketDemandReport) - MarketDemandReport:标准化报表数据# 1. 补全缺失的成本系数product_ids {d.product_id for d in report.product_demands}existing_cost_ids {c.product_id for c in report.cost_coefficients}missing_cost_ids product_ids - existing_cost_idsfor pid in missing_cost_ids:# 使用默认值report.cost_coefficients.append(CostCoefficient(product_idpid,variable_cost1000.0, # 默认变动成本fixed_cost0.0))# 2. 确保至少有一个产能约束if not report.capacity_constraints:total_demand report.get_total_demand()report.capacity_constraints.append(CapacityConstraint(resource_idTOTAL_CAPACITY,resource_name总产能,max_capacitytotal_demand * 1.2, # 预留20%余量unit吨/天))# 3. 统一单位简化假设都是吨for demand in report.product_demands:if not demand.unit:demand.unit 吨return report# ─── 模型输入构建器 ──────────────────────────────────────────────────────────class ModelInputBuilder:模型输入构建器def __init__(self, report: MarketDemandReport):self.report reportdef build(self) - ModelInput:构建模型输入参数# 决策变量每种产品的产量decision_vars [d.product_id for d in self.report.product_demands]# 目标系数最小化总成本obj_coeffs self.report.get_cost_dict()# 约束条件constraints {}rhs {}# 1. 需求满足约束生产量 需求量demand_dict self.report.get_demand_dict()for product_id, demand_qty in demand_dict.items():constraint_name fDemand_{product_id}constraints[constraint_name] {product_id: 1.0}rhs[constraint_name] demand_qty# 2. 产能约束for i, capacity in enumerate(self.report.capacity_constraints):constraint_name fCapacity_{capacity.resource_id}# 简化所有产品共享产能constraints[constraint_name] {var: 1.0 for var in decision_vars}rhs[constraint_name] capacity.max_capacity# 变量边界var_bounds {var: (0.0, float(inf)) for var in decision_vars}return ModelInput(scenario_idself.report.scenario_id,regionself.report.region,decision_variablesdecision_vars,objective_coefficientsobj_coeffs,constraintsconstraints,rhsrhs,variable_boundsvar_bounds)# ─── 批量求解器 ──────────────────────────────────────────────────────────class BatchSolver:批量求解器def __init__(self, solverpulp.PULP_CBC_CMD(msgFalse)):self.solver solverself.results: List[ScenarioResult] []def solve_scenario(self, model_input: ModelInput) - ScenarioResult:求解单个场景start_time time.time()# 构建PuLP问题prob model_input.to_pulp_problem()# 求解prob.solve(self.solver)solve_time time.time() - start_time# 提取结果status pulp.LpStatus[prob.status]objective_value pulp.value(prob.objective)variable_values {}for v in prob.variables():variable_values[v.name] v.varValuereturn ScenarioResult(scenario_idmodel_input.scenario_id,regionmodel_input.region,objective_valueobjective_value,variable_valuesvariable_values,solve_timesolve_time,statusstatus)def solve_all(self, model_inputs: List[ModelInput]) - List[ScenarioResult]:批量求解所有场景self.results []for model_input in model_inputs:result self.solve_scenario(model_input)self.results.append(result)return self.results# ─── 结果对比器 ───────────────────────────────────────────────────────────class ResultComparator:结果对比器staticmethoddef compare(results: List[ScenarioResult]) - pd.DataFrame:生成对比表data []for result in results:row {区域: result.region.value,场景ID: result.scenario_id,目标值(元): round(resu利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表