""" Task 3 - Step 6: 效果评估 ========================= 输入: - 04_reschedule.xlsx (访问次数) - 03_allocation.xlsx (配对分配方案) - ../task1/03_allocate.xlsx (Task 1结果,用于对比) - ../task1/04_metrics.xlsx (Task 1指标,用于对比) 输出: 06_evaluate.xlsx (评估指标对比) 评估指标: - E1': 期望总服务量 - E2': 质量加权服务量(总量计算衰减) - F1': 满足率Gini系数 - F2': 最低满足率 - R1: 服务缺口风险 - RS: 资源节省率 有效性衰减公式(总量计算): q(μ_i + μ_j) = min(1, 250 / (μ_i + μ_j)) """ import pandas as pd import numpy as np from scipy import stats # ============================================ # 参数设置 # ============================================ RESCHEDULE_FILE = '04_reschedule.xlsx' ALLOCATION_FILE = '03_allocation.xlsx' TASK1_ALLOC_FILE = '../task1/03_allocate.xlsx' TASK1_METRIC_FILE = '../task1/04_metrics.xlsx' OUTPUT_FILE = '06_evaluate.xlsx' Q = 400 # 卡车容量 QUALITY_THRESHOLD = 250 # 质量衰减阈值 SHORTFALL_THRESHOLD = 0.8 # 服务缺口阈值 # ============================================ # 辅助函数 # ============================================ def quality_factor(mu_total): """质量折扣因子(总量计算)""" return min(1.0, QUALITY_THRESHOLD / mu_total) if mu_total > 0 else 1.0 def expected_service(q, mu, sigma): """E[min(D, q)]""" if sigma == 0: return min(mu, q) z = (q - mu) / sigma return mu * stats.norm.cdf(z) - sigma * stats.norm.pdf(z) + q * (1 - stats.norm.cdf(z)) def gini_coefficient(values): """计算Gini系数""" values = np.array(values) values = values[~np.isnan(values)] if len(values) == 0: return 0 values = np.sort(values) n = len(values) cumsum = np.cumsum(values) return (2 * np.sum((np.arange(1, n + 1) * values)) - (n + 1) * cumsum[-1]) / (n * cumsum[-1]) if cumsum[-1] > 0 else 0 def shortfall_probability(q, mu, sigma, threshold=0.8): """P(S/D < threshold),即服务不足的概率""" # 简化计算:P(min(D,q) / D < 0.8) ≈ P(D > q/0.8) if sigma == 0: return 0 if q >= mu * threshold else 1 critical_demand = q / threshold return 1 - stats.norm.cdf((critical_demand - mu) / sigma) # ============================================ # 读取数据 # ============================================ print("=" * 60) print("Task 3 - Step 6: 效果评估") print("=" * 60) # Task 3 数据 df_sites = pd.read_excel(RESCHEDULE_FILE, sheet_name='sites_schedule') df_pairs = pd.read_excel(ALLOCATION_FILE, sheet_name='allocation') df_pair_visits = pd.read_excel(RESCHEDULE_FILE, sheet_name='pair_visits') print(f"\n读取Task 3数据:") print(f" - 站点数: {len(df_sites)}") print(f" - 配对数: {len(df_pairs)}") # Task 1 数据(用于对比) df_task1 = pd.read_excel(TASK1_ALLOC_FILE) try: df_task1_metrics = pd.read_excel(TASK1_METRIC_FILE) has_task1_metrics = True except: has_task1_metrics = False print(" - 未找到Task 1指标文件,将重新计算") # ============================================ # 计算 Task 3 指标 # ============================================ print(f"\n" + "-" * 40) print("计算 Task 3 指标") print("-" * 40) # 合并配对访问信息 pair_k = {(row['site_i_id'], row['site_j_id']): row['k_dual'] for _, row in df_pair_visits.iterrows()} # === E1': 期望总服务量 === E1_prime = 0 # 单站点贡献 for _, row in df_sites.iterrows(): k_single = row['k_single_final'] mu = row['mu'] E1_prime += k_single * mu # 双站点贡献 for _, row in df_pairs.iterrows(): pair_key = (row['site_i_id'], row['site_j_id']) k_dual = pair_k.get(pair_key, 0) E_total = row['E_total'] E1_prime += k_dual * E_total print(f"E1' (期望总服务量): {E1_prime:.0f}") # === E2': 质量加权服务量 === E2_prime = 0 # 单站点贡献 for _, row in df_sites.iterrows(): k_single = row['k_single_final'] mu = row['mu'] q_factor = quality_factor(mu) E2_prime += k_single * q_factor * mu # 双站点贡献(总量计算衰减) for _, row in df_pairs.iterrows(): pair_key = (row['site_i_id'], row['site_j_id']) k_dual = pair_k.get(pair_key, 0) mu_sum = row['mu_i'] + row['mu_j'] E_total = row['E_total'] q_factor = quality_factor(mu_sum) # 总量计算 E2_prime += k_dual * q_factor * E_total print(f"E2' (质量加权服务量): {E2_prime:.0f}") # === 满足率计算 === # Task 1定义: r_i = k_i * μ_i / μ̃_i (等效服务次数) # Task 3: 需要分别计算单站点和双站点的贡献 site_satisfaction = {} for _, row in df_sites.iterrows(): site_id = row['site_id'] mu = row['mu'] mu_tilde = row['mu_tilde'] k_single = row['k_single_final'] # 单站点贡献: k_single * μ / μ̃ r_single = k_single * mu / mu_tilde if mu_tilde > 0 else 0 site_satisfaction[site_id] = r_single # 加上双站点贡献 for _, row in df_pairs.iterrows(): pair_key = (row['site_i_id'], row['site_j_id']) k_dual = pair_k.get(pair_key, 0) # 双站点贡献: k_dual * E[S_i] / μ̃_i mu_tilde_i = df_sites[df_sites['site_id'] == row['site_i_id']]['mu_tilde'].values[0] mu_tilde_j = df_sites[df_sites['site_id'] == row['site_j_id']]['mu_tilde'].values[0] r_dual_i = k_dual * row['E_Si'] / mu_tilde_i if mu_tilde_i > 0 else 0 r_dual_j = k_dual * row['E_Sj'] / mu_tilde_j if mu_tilde_j > 0 else 0 site_satisfaction[row['site_i_id']] += r_dual_i site_satisfaction[row['site_j_id']] += r_dual_j # 计算满足率列表 satisfaction_rates = list(site_satisfaction.values()) # === F1': Gini系数 === F1_prime = gini_coefficient(satisfaction_rates) print(f"F1' (满足率Gini): {F1_prime:.4f}") # === F2': 最低满足率 === F2_prime = min(satisfaction_rates) if satisfaction_rates else 0 print(f"F2' (最低满足率): {F2_prime:.4f}") # === R1: 服务缺口风险 === # 双站点访问时,任一站点服务不足的概率 shortfall_probs = [] for _, row in df_pairs.iterrows(): q_final = row['q_final'] mu_i, sigma_i = row['mu_i'], row['sigma_i'] mu_j, sigma_j = row['mu_j'], row['sigma_j'] # 站点i的缺口概率 p_i = shortfall_probability(q_final, mu_i, sigma_i, SHORTFALL_THRESHOLD) # 站点j的缺口概率 p_j = shortfall_probability(Q - q_final, mu_j, sigma_j, SHORTFALL_THRESHOLD) # 至少一个站点缺口的概率 p_either = 1 - (1 - p_i) * (1 - p_j) shortfall_probs.append(p_either) R1 = np.mean(shortfall_probs) if shortfall_probs else 0 print(f"R1 (平均缺口风险): {R1:.4f}") # === RS: 资源节省率 === total_dual = df_pair_visits['k_dual'].sum() RS = total_dual / 730 * 100 print(f"RS (资源节省率): {RS:.2f}%") # ============================================ # 计算 Task 1 指标(用于对比) # ============================================ print(f"\n" + "-" * 40) print("计算 Task 1 指标(对比基准)") print("-" * 40) # E1: 总服务量 E1_task1 = (df_task1['k'] * df_task1['mu']).sum() print(f"E1 (Task 1 总服务量): {E1_task1:.0f}") # E2: 质量加权 E2_task1 = 0 for _, row in df_task1.iterrows(): q_factor = quality_factor(row['mu']) E2_task1 += row['k'] * q_factor * row['mu'] print(f"E2 (Task 1 质量加权): {E2_task1:.0f}") # F1: Gini系数 - 使用与Task 3相同的r定义 # r_i = k_i * μ_i / μ̃_i task1_rates = [] for _, row in df_task1.iterrows(): r = row['k'] * row['mu'] / row['mu_tilde'] if row['mu_tilde'] > 0 else 0 task1_rates.append(r) F1_task1 = gini_coefficient(task1_rates) print(f"F1 (Task 1 Gini): {F1_task1:.4f}") # F2: 最低满足率 F2_task1 = min(task1_rates) if task1_rates else 0 print(f"F2 (Task 1 最低满足率): {F2_task1:.4f}") # ============================================ # 对比分析 # ============================================ print(f"\n" + "-" * 40) print("Task 3 vs Task 1 对比") print("-" * 40) comparison = pd.DataFrame({ 'Metric': ['E1 (总服务量)', 'E2 (质量加权)', 'F1 (Gini系数)', 'F2 (最低满足率)'], 'Task 1': [E1_task1, E2_task1, F1_task1, F2_task1], 'Task 3': [E1_prime, E2_prime, F1_prime, F2_prime], 'Change': [E1_prime - E1_task1, E2_prime - E2_task1, F1_prime - F1_task1, F2_prime - F2_task1], 'Change %': [(E1_prime - E1_task1) / E1_task1 * 100, (E2_prime - E2_task1) / E2_task1 * 100, (F1_prime - F1_task1) / F1_task1 * 100 if F1_task1 != 0 else 0, (F2_prime - F2_task1) / F2_task1 * 100 if F2_task1 != 0 else 0] }) print(comparison.to_string(index=False)) # ============================================ # 保存结果 # ============================================ with pd.ExcelWriter(OUTPUT_FILE, engine='openpyxl') as writer: # Sheet 1: 指标对比 comparison.to_excel(writer, sheet_name='comparison', index=False) # Sheet 2: Task 3 详细指标 task3_metrics = pd.DataFrame({ 'metric': ['E1_prime', 'E2_prime', 'F1_prime', 'F2_prime', 'R1', 'RS', 'total_pairs', 'total_dual_visits'], 'value': [E1_prime, E2_prime, F1_prime, F2_prime, R1, RS/100, len(df_pairs), total_dual], 'description': ['期望总服务量', '质量加权服务量', '满足率Gini系数', '最低满足率', '平均缺口风险', '资源节省率', '配对数', '双站点访问次数'] }) task3_metrics.to_excel(writer, sheet_name='task3_metrics', index=False) # Sheet 3: 站点级别满足率 site_rates = [] for site_id, r in site_satisfaction.items(): site_row = df_sites[df_sites['site_id'] == site_id] if len(site_row) > 0: site_name = site_row['site_name'].values[0] mu = site_row['mu'].values[0] mu_tilde = site_row['mu_tilde'].values[0] k_single = site_row['k_single_final'].values[0] k_dual = site_row['k_dual'].values[0] else: site_name = f"Site_{site_id}" mu, mu_tilde, k_single, k_dual = 0, 0, 0, 0 site_rates.append({ 'site_id': site_id, 'site_name': site_name, 'mu': mu, 'mu_tilde': mu_tilde, 'k_single': k_single, 'k_dual': k_dual, 'satisfaction_rate_r': r }) df_site_rates = pd.DataFrame(site_rates) df_site_rates = df_site_rates.sort_values('satisfaction_rate_r') df_site_rates.to_excel(writer, sheet_name='site_satisfaction', index=False) # Sheet 4: 配对风险分析 pair_risk = [] for idx, row in df_pairs.iterrows(): pair_key = (row['site_i_id'], row['site_j_id']) k_dual = pair_k.get(pair_key, 0) q_final = row['q_final'] mu_i, sigma_i = row['mu_i'], row['sigma_i'] mu_j, sigma_j = row['mu_j'], row['sigma_j'] p_i = shortfall_probability(q_final, mu_i, sigma_i, SHORTFALL_THRESHOLD) p_j = shortfall_probability(Q - q_final, mu_j, sigma_j, SHORTFALL_THRESHOLD) pair_risk.append({ 'site_i_name': row['site_i_name'], 'site_j_name': row['site_j_name'], 'k_dual': k_dual, 'q_final': q_final, 'shortfall_prob_i': p_i, 'shortfall_prob_j': p_j, 'shortfall_prob_either': 1 - (1 - p_i) * (1 - p_j) }) df_pair_risk = pd.DataFrame(pair_risk) df_pair_risk = df_pair_risk.sort_values('shortfall_prob_either', ascending=False) df_pair_risk.to_excel(writer, sheet_name='pair_risk', index=False) print(f"\n结果已保存至: {OUTPUT_FILE}") print(" - Sheet 'comparison': Task 1 vs Task 3 对比") print(" - Sheet 'task3_metrics': Task 3 详细指标") print(" - Sheet 'site_satisfaction': 站点满足率") print(" - Sheet 'pair_risk': 配对风险分析") print("\n" + "=" * 60)