""" Task 3 - Step 2: 共生站点配对筛选与选择 ======================================= 输入: 01_distance.xlsx (距离矩阵 + 站点信息) 输出: 02_pairing.xlsx (筛选后的配对列表 + 最终选择) 配对筛选条件: 1. 距离约束: l_ij ≤ l_max (50英里) 2. 容量约束: μ_i + μ_j ≤ 450 3. 稳定性约束: CV_i ≤ 0.5 且 CV_j ≤ 0.5 配对价值函数: V_ij = α * (μ_i + μ_j) / Q - β * l_ij / l_max - γ * (σ_i² + σ_j²) / (μ_i + μ_j)² 配对选择: 贪心算法,每个站点最多配对一次 """ import pandas as pd import numpy as np # ============================================ # 参数设置 # ============================================ INPUT_FILE = '01_distance.xlsx' OUTPUT_FILE = '02_pairing.xlsx' # 约束参数 L_MAX = 30 # 距离阈值 (英里) MU_SUM_MAX = 350 # 需求和上限 CV_MAX = 0.3 # 变异系数上限 Q = 350 # 卡车容量 # 价值函数权重 ALPHA = 1.0 # 容量利用率权重 BETA = 0.3 # 距离惩罚权重 GAMMA = 0.5 # 风险惩罚权重 # ============================================ # 读取数据 # ============================================ print("=" * 60) print("Task 3 - Step 2: 共生站点配对筛选与选择") print("=" * 60) # 读取站点信息 sites = pd.read_excel(INPUT_FILE, sheet_name='sites') print(f"\n读取站点数据: {len(sites)} 个站点") # 读取距离矩阵 dist_matrix = pd.read_excel(INPUT_FILE, sheet_name='distance_matrix', index_col=0) print(f"读取距离矩阵: {dist_matrix.shape}") # 计算变异系数 sites['cv'] = sites['sigma'] / sites['mu'] print(f"\nCV范围: [{sites['cv'].min():.3f}, {sites['cv'].max():.3f}]") print(f"CV > {CV_MAX} 的站点数: {(sites['cv'] > CV_MAX).sum()}") # ============================================ # 配对筛选 # ============================================ print(f"\n" + "-" * 40) print("配对筛选") print("-" * 40) candidates = [] n = len(sites) for i in range(n): for j in range(i + 1, n): # 只考虑上三角 site_i = sites.iloc[i] site_j = sites.iloc[j] # 获取距离 dist = dist_matrix.iloc[i, j] # 约束检查 # 1. 距离约束 if dist > L_MAX: continue # 2. 容量约束 mu_sum = site_i['mu'] + site_j['mu'] if mu_sum > MU_SUM_MAX: continue # 3. 稳定性约束 if site_i['cv'] > CV_MAX or site_j['cv'] > CV_MAX: continue # 通过所有约束,计算配对价值 sigma_sq_sum = site_i['sigma']**2 + site_j['sigma']**2 # 价值函数 value = (ALPHA * mu_sum / Q - BETA * dist / L_MAX - GAMMA * sigma_sq_sum / mu_sum**2) candidates.append({ 'site_i_id': site_i['site_id'], 'site_j_id': site_j['site_id'], 'site_i_name': site_i['site_name'], 'site_j_name': site_j['site_name'], 'distance': dist, 'mu_i': site_i['mu'], 'mu_j': site_j['mu'], 'mu_sum': mu_sum, 'sigma_i': site_i['sigma'], 'sigma_j': site_j['sigma'], 'cv_i': site_i['cv'], 'cv_j': site_j['cv'], 'k_i': site_i['k'], 'k_j': site_j['k'], 'value': value }) df_candidates = pd.DataFrame(candidates) print(f"通过约束的候选配对数: {len(df_candidates)}") if len(df_candidates) > 0: print(f"配对价值范围: [{df_candidates['value'].min():.3f}, {df_candidates['value'].max():.3f}]") print(f"平均距离: {df_candidates['distance'].mean():.2f} 英里") print(f"平均需求和: {df_candidates['mu_sum'].mean():.1f}") # ============================================ # 贪心配对选择 # ============================================ print(f"\n" + "-" * 40) print("贪心配对选择") print("-" * 40) # 按价值降序排序 df_candidates_sorted = df_candidates.sort_values('value', ascending=False).reset_index(drop=True) # 贪心选择 selected_pairs = [] used_sites = set() for _, row in df_candidates_sorted.iterrows(): site_i = row['site_i_id'] site_j = row['site_j_id'] # 检查是否已被使用 if site_i not in used_sites and site_j not in used_sites: selected_pairs.append(row.to_dict()) used_sites.add(site_i) used_sites.add(site_j) df_selected = pd.DataFrame(selected_pairs) print(f"最终选择配对数: {len(df_selected)}") print(f"涉及站点数: {len(used_sites)} (占总站点 {len(used_sites)/n*100:.1f}%)") print(f"未配对站点数: {n - len(used_sites)}") if len(df_selected) > 0: print(f"\n选中配对的价值范围: [{df_selected['value'].min():.3f}, {df_selected['value'].max():.3f}]") print(f"选中配对的距离范围: [{df_selected['distance'].min():.2f}, {df_selected['distance'].max():.2f}] 英里") print(f"选中配对的需求和范围: [{df_selected['mu_sum'].min():.1f}, {df_selected['mu_sum'].max():.1f}]") # ============================================ # 显示选中的配对 # ============================================ print(f"\n" + "-" * 40) print("选中的配对列表") print("-" * 40) if len(df_selected) > 0: display_cols = ['site_i_name', 'site_j_name', 'distance', 'mu_sum', 'k_i', 'k_j', 'value'] print(df_selected[display_cols].to_string(index=False)) # ============================================ # 保存结果 # ============================================ with pd.ExcelWriter(OUTPUT_FILE, engine='openpyxl') as writer: # Sheet 1: 站点信息(含CV) sites.to_excel(writer, sheet_name='sites', index=False) # Sheet 2: 所有候选配对 df_candidates_sorted.to_excel(writer, sheet_name='all_candidates', index=False) # Sheet 3: 最终选择的配对 df_selected.to_excel(writer, sheet_name='selected_pairs', index=False) # Sheet 4: 参数记录 params = pd.DataFrame({ 'parameter': ['L_MAX', 'MU_SUM_MAX', 'CV_MAX', 'Q', 'ALPHA', 'BETA', 'GAMMA'], 'value': [L_MAX, MU_SUM_MAX, CV_MAX, Q, ALPHA, BETA, GAMMA], 'description': ['距离阈值(英里)', '需求和上限', 'CV上限', '卡车容量', '容量利用率权重', '距离惩罚权重', '风险惩罚权重'] }) params.to_excel(writer, sheet_name='parameters', index=False) # Sheet 5: 汇总统计 summary = pd.DataFrame({ 'metric': ['total_sites', 'candidate_pairs', 'selected_pairs', 'paired_sites', 'unpaired_sites', 'avg_distance', 'avg_mu_sum'], 'value': [n, len(df_candidates), len(df_selected), len(used_sites), n - len(used_sites), df_selected['distance'].mean() if len(df_selected) > 0 else 0, df_selected['mu_sum'].mean() if len(df_selected) > 0 else 0] }) summary.to_excel(writer, sheet_name='summary', index=False) print(f"\n结果已保存至: {OUTPUT_FILE}") print(" - Sheet 'sites': 站点信息(含CV)") print(" - Sheet 'all_candidates': 所有候选配对") print(" - Sheet 'selected_pairs': 最终选择的配对") print(" - Sheet 'parameters': 参数记录") print(" - Sheet 'summary': 汇总统计") print("\n" + "=" * 60)