gw/schedule_pulp.py

174 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
import pandas as pd
import pulp
from math import radians, sin, cos, sqrt, atan2
# 1. 读取数据
inventory_df = pd.read_excel('可调拨库存表.xlsx')
factory_loc_df = pd.read_excel('工厂位置.xlsx')
demand_df = pd.read_excel('需求表.xlsx') # 型号、市、需求
city_loc_df = pd.read_excel('地名经纬度.xlsx') # 市、经度、纬度
# 2. 数据合并
inventory_df['可调拨库存'] = pd.to_numeric(inventory_df['可调拨库存'], errors='coerce').fillna(0)
demand_df['需求'] = pd.to_numeric(demand_df['需求'], errors='coerce').fillna(0)
# 工厂库存合并
factory_capacity = {}
for _, row in inventory_df.groupby(['工厂', '型号'])['可调拨库存'].sum().reset_index().iterrows():
f, p, cap = row['工厂'], row['型号'], int(row['可调拨库存'])
if cap > 0:
factory_capacity.setdefault(f, {})[p] = cap
# 城市需求合并
city_demand = {}
for _, row in demand_df.iterrows():
city, p, dem = row[''], row['型号'], int(row['需求'])
if dem > 0:
city_demand.setdefault(city, {})
city_demand[city][p] = city_demand[city].get(p, 0) + dem
factory_lonlat = dict(zip(factory_loc_df['工厂'], zip(factory_loc_df['经度'], factory_loc_df['纬度'])))
city_lonlat = dict(zip(city_loc_df[''], zip(city_loc_df['经度'], city_loc_df['纬度'])))
# missing = set(city_demand) - set(city_lonlat)
# if missing:
# raise ValueError(f"缺少经纬度:{missing}")
# 3. 距离
dist = {}
for f in factory_capacity:
for c in city_demand:
lon1, lat1 = factory_lonlat[f]
lon2, lat2 = city_lonlat[c]
dlon = radians(lon2-lon1)
dlat = radians(lat2-lat1)
a = sin(dlat/2)**2 + cos(radians(lat1))*cos(radians(lat2))*sin(dlon/2)**2
dist[(f,c)] = 6371 * 2 * atan2(sqrt(a), sqrt(1-a))
# 4. 单位运费函数
def get_real_unit_cost(distance_km, product):
d = distance_km
p = str(product).strip()
if p in ['GWBD-A2', 'GWBD-A3', 'GWBD-B']:
if distance_km <= 100:
return 15802.31
elif distance_km <= 200:
return 18445.12
elif distance_km <= 300:
return 22391.35
elif distance_km <= 600:
return 62.29 * d
elif distance_km <= 900:
return 44.75 * d
elif distance_km <= 1200:
return 38.23 * d
elif distance_km <= 1500:
return 36.08 * d
elif distance_km <= 2000:
return 33.44 * d
elif distance_km <= 2500:
return 32.76 * d
elif distance_km <= 3500:
return 26.68 * d
else:
return 24.27
# 其他型号用第二套
else:
if distance_km <= 100:
return 12859.46
elif distance_km <= 200:
return 14946.73
elif distance_km <= 300:
return 18662.46
elif distance_km <= 600:
return 52.83 * d
elif distance_km <= 900:
return 38.36 * d
elif distance_km <= 1200:
return 32.89 * d
elif distance_km <= 1500:
return 30.69 * d
elif distance_km <= 2000:
return 28.49 * d
elif distance_km <= 2500:
return 27.76 * d
elif distance_km <= 3500:
return 22.87 * d
else:
return 21.02 * d
# 5. 规划模型
prob = pulp.LpProblem("最小化运费", pulp.LpMinimize)
x = {}
for f in factory_capacity:
for p in factory_capacity[f]:
for city in city_demand:
if city_demand[city].get(p, 0) > 0:
x[(f,city,p)] = pulp.LpVariable(f"ship_{f}_{city}_{p}", lowBound=0, cat="Integer")
# 缺货惩罚
shortage = {(city,p): pulp.LpVariable(f"short_{city}_{p}", lowBound=0, cat="Continuous")
for city in city_demand for p in city_demand[city]}
BIG_M = 1e10
prob += pulp.lpSum(
x[k] * get_real_unit_cost(dist[(k[0],k[1])], k[2]) # 真实单位运费
for k in x
) + pulp.lpSum(BIG_M * s for s in shortage.values())
# 库存硬约束
for f in factory_capacity:
for p in factory_capacity[f]:
prob += pulp.lpSum(x.get((f,c,p), 0) for c in city_demand) <= factory_capacity[f][p]
# 需求软约束
for city in city_demand:
for p, dem in city_demand[city].items():
prob += pulp.lpSum(x.get((f,city,p), 0) for f in factory_capacity) + shortage[(city,p)] >= dem
# 6. 求解
status = prob.solve(pulp.PULP_CBC_CMD(msg=True, timeLimit=600))
print(f"\n求解状态: {pulp.LpStatus[status]}")
if status != 1:
print("无解(请检查总库存)")
else:
print("有解")
# real_cost = pulp.value(prob.objective)
# print(f"实际运输总费用: {real_cost:,.2f} 元")
# ====================== 7. 输出 ======================
result = []
for (f,city,p), var in x.items():
q = int(pulp.value(var) or 0)
if q >= 1:
d = dist[(f,city)]
# unit = get_real_unit_cost(d, p)
result.append({
"工厂": f,
"型号": p,
"需求方": city,
"调拨数量": q,
# "距离_km": round(d, 1),
# "真实单位运费": round(unit, 3),
# "总运费": round(q * unit, 2)
})
df = pd.DataFrame(result)
if not df.empty:
df = df.sort_values(["工厂", "型号"]) # 按单位运费排序
df[["工厂","型号","需求方","调拨数量"]].to_excel("调拨方案.xlsx", index=False)
# print("\n调拨方案已保存真实单位运费最优优先满足单价更低的城市")
# print(df[["工厂","型号","需求方","调拨数量"]].to_string(index=False))
else:
print("\n无调拨记录")
# 校验报告
# print("\n各城市需求满足情况")
# for city in city_demand:
# for p, dem in city_demand[city].items():
# sup = sum(r["调拨数量"] for r in result if r["需求方"]==city and r["型号"]==p)
# print(f" {city} | {p}: 需求 {dem} → 调拨 {sup} → {'完美' if sup==dem else '异常'}")