diff --git a/task3/07_sensitivity.py b/task3/07_sensitivity.py
new file mode 100644
index 0000000..9e25e28
--- /dev/null
+++ b/task3/07_sensitivity.py
@@ -0,0 +1,400 @@
+"""
+Task 3 - Step 7: 敏感性分析
+============================
+
+分析以下参数对模型输出的影响:
+1. 合并比例 r_merge: [1/3, 1/2, 2/3]
+2. 距离阈值 l_max: [30, 40, 50, 60, 70]
+3. 容量上限 μ_sum_max: [400, 425, 450, 475, 500]
+4. CV阈值: [0.3, 0.4, 0.5, 0.6]
+
+输出: 07_sensitivity.xlsx (各参数对E1', E2', F1', R1的影响)
+"""
+
+import pandas as pd
+import numpy as np
+from scipy import stats
+import warnings
+warnings.filterwarnings('ignore')
+
+# ============================================
+# 基础参数和函数
+# ============================================
+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):
+ 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):
+ 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):
+ if sigma == 0:
+ return 0 if q >= mu * threshold else 1
+ critical_demand = q / threshold
+ return 1 - stats.norm.cdf((critical_demand - mu) / sigma)
+
+def optimal_allocation(mu_i, sigma_i, mu_j, sigma_j, Q=400):
+ if sigma_i + sigma_j == 0:
+ return mu_i
+ return (sigma_j * mu_i + sigma_i * (Q - mu_j)) / (sigma_i + sigma_j)
+
+def calc_distance(lat1, lon1, lat2, lon2):
+ lat_avg = (lat1 + lat2) / 2
+ lat_avg_rad = np.radians(lat_avg)
+ delta_lat = lat1 - lat2
+ delta_lon = lon1 - lon2
+ return 69.0 * np.sqrt(delta_lat**2 + (np.cos(lat_avg_rad) * delta_lon)**2)
+
+# ============================================
+# 完整流水线函数
+# ============================================
+def run_pipeline(sites_df, l_max=50, mu_sum_max=450, cv_max=0.5, merge_ratio=0.5):
+ """
+ 运行完整的Task 3流水线,返回评估指标
+ """
+ sites = sites_df.copy()
+ sites['cv'] = sites['sigma'] / sites['mu']
+ n = len(sites)
+
+ # Step 1: 计算距离矩阵
+ distance_matrix = np.zeros((n, n))
+ for i in range(n):
+ for j in range(n):
+ if i != j:
+ distance_matrix[i, j] = calc_distance(
+ sites.iloc[i]['lat'], sites.iloc[i]['lon'],
+ sites.iloc[j]['lat'], sites.iloc[j]['lon']
+ )
+
+ # Step 2: 配对筛选
+ candidates = []
+ for i in range(n):
+ for j in range(i + 1, n):
+ site_i = sites.iloc[i]
+ site_j = sites.iloc[j]
+ dist = distance_matrix[i, j]
+
+ if dist > l_max:
+ continue
+ mu_sum = site_i['mu'] + site_j['mu']
+ if mu_sum > mu_sum_max:
+ continue
+ 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 = (1.0 * mu_sum / Q - 0.3 * dist / l_max - 0.5 * sigma_sq_sum / mu_sum**2)
+
+ candidates.append({
+ 'idx_i': i, 'idx_j': j,
+ 'site_i_id': site_i['site_id'], 'site_j_id': site_j['site_id'],
+ 'distance': dist,
+ 'mu_i': site_i['mu'], 'mu_j': site_j['mu'],
+ 'sigma_i': site_i['sigma'], 'sigma_j': site_j['sigma'],
+ 'k_i': site_i['k'], 'k_j': site_j['k'],
+ 'mu_tilde_i': site_i['mu_tilde'], 'mu_tilde_j': site_j['mu_tilde'],
+ 'value': value
+ })
+
+ if len(candidates) == 0:
+ # 无可行配对,返回Task 1的指标
+ E1 = (sites['k'] * sites['mu']).sum()
+ E2 = sum(sites['k'] * sites['mu'].apply(quality_factor) * sites['mu'])
+ rates = [row['k'] * row['mu'] / row['mu_tilde'] for _, row in sites.iterrows()]
+ F1 = gini_coefficient(rates)
+ F2 = min(rates)
+ return {
+ 'num_pairs': 0, 'num_dual_visits': 0,
+ 'E1': E1, 'E2': E2, 'F1': F1, 'F2': F2, 'R1': 0, 'RS': 0
+ }
+
+ # 贪心配对选择
+ df_cand = pd.DataFrame(candidates).sort_values('value', ascending=False)
+ selected = []
+ used = set()
+ for _, row in df_cand.iterrows():
+ if row['idx_i'] not in used and row['idx_j'] not in used:
+ selected.append(row.to_dict())
+ used.add(row['idx_i'])
+ used.add(row['idx_j'])
+
+ # Step 3: 计算最优分配
+ for pair in selected:
+ q_star = optimal_allocation(pair['mu_i'], pair['sigma_i'],
+ pair['mu_j'], pair['sigma_j'], Q)
+ pair['q_final'] = q_star
+ pair['E_Si'] = expected_service(q_star, pair['mu_i'], pair['sigma_i'])
+ pair['E_Sj'] = expected_service(Q - q_star, pair['mu_j'], pair['sigma_j'])
+ pair['E_total'] = pair['E_Si'] + pair['E_Sj']
+
+ # Step 4: 重分配访问次数
+ sites['k_single'] = sites['k'].copy()
+ sites['k_dual'] = 0
+
+ pair_k = {}
+ for pair in selected:
+ k_i, k_j = pair['k_i'], pair['k_j']
+ k_ij = int(min(k_i, k_j) * merge_ratio)
+ if k_ij >= min(k_i, k_j):
+ k_ij = min(k_i, k_j) - 1
+ if k_ij < 1:
+ k_ij = 0
+
+ idx_i, idx_j = pair['idx_i'], pair['idx_j']
+ sites.loc[idx_i, 'k_single'] = k_i - k_ij
+ sites.loc[idx_i, 'k_dual'] = k_ij
+ sites.loc[idx_j, 'k_single'] = k_j - k_ij
+ sites.loc[idx_j, 'k_dual'] = k_ij
+ pair_k[(pair['site_i_id'], pair['site_j_id'])] = k_ij
+
+ # 计算释放槽位并重分配
+ total_single = sites['k_single'].sum()
+ total_dual = sum(pair_k.values())
+ delta_N = 730 - (total_single + total_dual)
+
+ if delta_N > 0:
+ total_demand = sites['mu_tilde'].sum()
+ sites['k_extra'] = (delta_N * sites['mu_tilde'] / total_demand).apply(np.floor).astype(int)
+ remainder = delta_N - sites['k_extra'].sum()
+ if remainder > 0:
+ fractional = delta_N * sites['mu_tilde'] / total_demand - sites['k_extra']
+ top_idx = fractional.nlargest(int(remainder)).index
+ sites.loc[top_idx, 'k_extra'] += 1
+ sites['k_single_final'] = sites['k_single'] + sites['k_extra']
+ else:
+ sites['k_single_final'] = sites['k_single']
+
+ # Step 5: 计算指标
+ # E1'
+ E1 = (sites['k_single_final'] * sites['mu']).sum()
+ for pair in selected:
+ k_ij = pair_k.get((pair['site_i_id'], pair['site_j_id']), 0)
+ E1 += k_ij * pair['E_total']
+
+ # E2'
+ E2 = sum(sites['k_single_final'] * sites['mu'].apply(quality_factor) * sites['mu'])
+ for pair in selected:
+ k_ij = pair_k.get((pair['site_i_id'], pair['site_j_id']), 0)
+ mu_sum = pair['mu_i'] + pair['mu_j']
+ q_factor = quality_factor(mu_sum)
+ E2 += k_ij * q_factor * pair['E_total']
+
+ # 满足率
+ site_satisfaction = {}
+ for idx, row in sites.iterrows():
+ r = row['k_single_final'] * row['mu'] / row['mu_tilde'] if row['mu_tilde'] > 0 else 0
+ site_satisfaction[row['site_id']] = r
+
+ for pair in selected:
+ k_ij = pair_k.get((pair['site_i_id'], pair['site_j_id']), 0)
+ r_i = k_ij * pair['E_Si'] / pair['mu_tilde_i'] if pair['mu_tilde_i'] > 0 else 0
+ r_j = k_ij * pair['E_Sj'] / pair['mu_tilde_j'] if pair['mu_tilde_j'] > 0 else 0
+ site_satisfaction[pair['site_i_id']] += r_i
+ site_satisfaction[pair['site_j_id']] += r_j
+
+ rates = list(site_satisfaction.values())
+ F1 = gini_coefficient(rates)
+ F2 = min(rates) if rates else 0
+
+ # R1: 缺口风险
+ shortfall_probs = []
+ for pair in selected:
+ q = pair['q_final']
+ p_i = shortfall_probability(q, pair['mu_i'], pair['sigma_i'])
+ p_j = shortfall_probability(Q - q, pair['mu_j'], pair['sigma_j'])
+ shortfall_probs.append(1 - (1 - p_i) * (1 - p_j))
+ R1 = np.mean(shortfall_probs) if shortfall_probs else 0
+
+ # RS: 资源节省率
+ RS = total_dual / 730
+
+ return {
+ 'num_pairs': len(selected),
+ 'num_dual_visits': total_dual,
+ 'E1': E1, 'E2': E2, 'F1': F1, 'F2': F2, 'R1': R1, 'RS': RS
+ }
+
+# ============================================
+# 主程序
+# ============================================
+print("=" * 60)
+print("Task 3 - Step 7: 敏感性分析")
+print("=" * 60)
+
+# 读取基础数据
+sites_df = pd.read_excel('../task1/03_allocate.xlsx')
+print(f"\n读取站点数据: {len(sites_df)} 个站点")
+
+# 基准参数
+BASE_L_MAX = 50
+BASE_MU_SUM_MAX = 450
+BASE_CV_MAX = 0.5
+BASE_MERGE_RATIO = 0.5
+
+# 计算基准结果
+print(f"\n计算基准结果...")
+base_result = run_pipeline(sites_df, BASE_L_MAX, BASE_MU_SUM_MAX, BASE_CV_MAX, BASE_MERGE_RATIO)
+print(f"基准: E1={base_result['E1']:.0f}, E2={base_result['E2']:.0f}, F1={base_result['F1']:.4f}, R1={base_result['R1']:.4f}")
+
+# ============================================
+# 敏感性分析
+# ============================================
+all_results = []
+
+# 1. 合并比例敏感性
+print(f"\n" + "-" * 40)
+print("1. 合并比例敏感性 (merge_ratio)")
+print("-" * 40)
+
+merge_ratios = [1/3, 0.5, 2/3]
+for mr in merge_ratios:
+ result = run_pipeline(sites_df, BASE_L_MAX, BASE_MU_SUM_MAX, BASE_CV_MAX, mr)
+ result['param'] = 'merge_ratio'
+ result['param_value'] = mr
+ all_results.append(result)
+ print(f" merge_ratio={mr:.3f}: pairs={result['num_pairs']}, dual={result['num_dual_visits']}, "
+ f"E1={result['E1']:.0f}, E2={result['E2']:.0f}, F1={result['F1']:.4f}, R1={result['R1']:.4f}")
+
+# 2. 距离阈值敏感性
+print(f"\n" + "-" * 40)
+print("2. 距离阈值敏感性 (l_max)")
+print("-" * 40)
+
+l_max_values = [30, 40, 50, 60, 70]
+for lm in l_max_values:
+ result = run_pipeline(sites_df, lm, BASE_MU_SUM_MAX, BASE_CV_MAX, BASE_MERGE_RATIO)
+ result['param'] = 'l_max'
+ result['param_value'] = lm
+ all_results.append(result)
+ print(f" l_max={lm}: pairs={result['num_pairs']}, dual={result['num_dual_visits']}, "
+ f"E1={result['E1']:.0f}, E2={result['E2']:.0f}, F1={result['F1']:.4f}, R1={result['R1']:.4f}")
+
+# 3. 容量上限敏感性
+print(f"\n" + "-" * 40)
+print("3. 容量上限敏感性 (mu_sum_max)")
+print("-" * 40)
+
+mu_sum_values = [400, 425, 450, 475, 500]
+for ms in mu_sum_values:
+ result = run_pipeline(sites_df, BASE_L_MAX, ms, BASE_CV_MAX, BASE_MERGE_RATIO)
+ result['param'] = 'mu_sum_max'
+ result['param_value'] = ms
+ all_results.append(result)
+ print(f" mu_sum_max={ms}: pairs={result['num_pairs']}, dual={result['num_dual_visits']}, "
+ f"E1={result['E1']:.0f}, E2={result['E2']:.0f}, F1={result['F1']:.4f}, R1={result['R1']:.4f}")
+
+# 4. CV阈值敏感性
+print(f"\n" + "-" * 40)
+print("4. CV阈值敏感性 (cv_max)")
+print("-" * 40)
+
+cv_max_values = [0.3, 0.4, 0.5, 0.6]
+for cv in cv_max_values:
+ result = run_pipeline(sites_df, BASE_L_MAX, BASE_MU_SUM_MAX, cv, BASE_MERGE_RATIO)
+ result['param'] = 'cv_max'
+ result['param_value'] = cv
+ all_results.append(result)
+ print(f" cv_max={cv}: pairs={result['num_pairs']}, dual={result['num_dual_visits']}, "
+ f"E1={result['E1']:.0f}, E2={result['E2']:.0f}, F1={result['F1']:.4f}, R1={result['R1']:.4f}")
+
+# ============================================
+# 汇总分析
+# ============================================
+df_results = pd.DataFrame(all_results)
+
+print(f"\n" + "=" * 60)
+print("敏感性分析汇总")
+print("=" * 60)
+
+# 计算各参数的影响范围
+for param in ['merge_ratio', 'l_max', 'mu_sum_max', 'cv_max']:
+ subset = df_results[df_results['param'] == param]
+ print(f"\n{param}:")
+ print(f" E1 变化范围: [{subset['E1'].min():.0f}, {subset['E1'].max():.0f}], "
+ f"变化幅度: {(subset['E1'].max() - subset['E1'].min()) / base_result['E1'] * 100:.2f}%")
+ print(f" E2 变化范围: [{subset['E2'].min():.0f}, {subset['E2'].max():.0f}], "
+ f"变化幅度: {(subset['E2'].max() - subset['E2'].min()) / base_result['E2'] * 100:.2f}%")
+ print(f" F1 变化范围: [{subset['F1'].min():.4f}, {subset['F1'].max():.4f}]")
+ print(f" R1 变化范围: [{subset['R1'].min():.4f}, {subset['R1'].max():.4f}]")
+
+# ============================================
+# 保存结果
+# ============================================
+OUTPUT_FILE = '07_sensitivity.xlsx'
+
+with pd.ExcelWriter(OUTPUT_FILE, engine='openpyxl') as writer:
+ # Sheet 1: 所有结果
+ df_results.to_excel(writer, sheet_name='all_results', index=False)
+
+ # Sheet 2: 合并比例敏感性
+ df_merge = df_results[df_results['param'] == 'merge_ratio'].copy()
+ df_merge.to_excel(writer, sheet_name='merge_ratio', index=False)
+
+ # Sheet 3: 距离阈值敏感性
+ df_lmax = df_results[df_results['param'] == 'l_max'].copy()
+ df_lmax.to_excel(writer, sheet_name='l_max', index=False)
+
+ # Sheet 4: 容量上限敏感性
+ df_musum = df_results[df_results['param'] == 'mu_sum_max'].copy()
+ df_musum.to_excel(writer, sheet_name='mu_sum_max', index=False)
+
+ # Sheet 5: CV阈值敏感性
+ df_cv = df_results[df_results['param'] == 'cv_max'].copy()
+ df_cv.to_excel(writer, sheet_name='cv_max', index=False)
+
+ # Sheet 6: 基准结果
+ base_df = pd.DataFrame([{
+ 'param': 'baseline',
+ 'l_max': BASE_L_MAX,
+ 'mu_sum_max': BASE_MU_SUM_MAX,
+ 'cv_max': BASE_CV_MAX,
+ 'merge_ratio': BASE_MERGE_RATIO,
+ **base_result
+ }])
+ base_df.to_excel(writer, sheet_name='baseline', index=False)
+
+ # Sheet 7: 汇总统计
+ summary_rows = []
+ for param in ['merge_ratio', 'l_max', 'mu_sum_max', 'cv_max']:
+ subset = df_results[df_results['param'] == param]
+ summary_rows.append({
+ 'param': param,
+ 'E1_min': subset['E1'].min(),
+ 'E1_max': subset['E1'].max(),
+ 'E1_range_pct': (subset['E1'].max() - subset['E1'].min()) / base_result['E1'] * 100,
+ 'E2_min': subset['E2'].min(),
+ 'E2_max': subset['E2'].max(),
+ 'E2_range_pct': (subset['E2'].max() - subset['E2'].min()) / base_result['E2'] * 100,
+ 'F1_min': subset['F1'].min(),
+ 'F1_max': subset['F1'].max(),
+ 'R1_min': subset['R1'].min(),
+ 'R1_max': subset['R1'].max()
+ })
+ df_summary = pd.DataFrame(summary_rows)
+ df_summary.to_excel(writer, sheet_name='summary', index=False)
+
+print(f"\n结果已保存至: {OUTPUT_FILE}")
+print(" - Sheet 'all_results': 所有结果")
+print(" - Sheet 'merge_ratio': 合并比例敏感性")
+print(" - Sheet 'l_max': 距离阈值敏感性")
+print(" - Sheet 'mu_sum_max': 容量上限敏感性")
+print(" - Sheet 'cv_max': CV阈值敏感性")
+print(" - Sheet 'baseline': 基准结果")
+print(" - Sheet 'summary': 汇总统计")
+print("\n" + "=" * 60)
diff --git a/task3/07_sensitivity.xlsx b/task3/07_sensitivity.xlsx
new file mode 100644
index 0000000..efcfccf
Binary files /dev/null and b/task3/07_sensitivity.xlsx differ
diff --git a/task3/README.md b/task3/README.md
index 89a0351..0d3560b 100644
--- a/task3/README.md
+++ b/task3/README.md
@@ -9,128 +9,324 @@
3. **解析最优分配策略**:推导出闭式解 $q^* = \frac{\sigma_j \mu_i + \sigma_i (Q - \mu_j)}{\sigma_i + \sigma_j}$
4. **风险可控的全局调度**:在提升效率的同时,将服务缺口风险控制在可接受范围
+**实际运行结果**:
+- 总服务量提升 **16.9%**(140,121 → 163,777)
+- 质量加权服务量提升 **5.3%**(131,673 → 138,699)
+- 资源节省 **19.5%**(142次双站点访问)
+- 服务缺口风险 **17.1%**(可控范围)
+
---
-## 1. 问题本质重析
+## 完整流程图
+
+### Mermaid版本(GitHub可渲染)
+
+```mermaid
+flowchart TB
+ subgraph INPUT["数据输入"]
+ A[Task 1 结果
70站点 + 730次分配
task1/03_allocate.xlsx]
+ end
+
+ subgraph TASK3["TASK 3: 双站点同车优化"]
+ direction TB
+
+ subgraph CORE["核心流程 ✅ 已完成"]
+ subgraph PAIRING["阶段1: 配对选择"]
+ B1[01_distance.py
距离矩阵计算]
+ B2[02_pairing.py
可行性筛选+贪心选择]
+ B1 --> B2
+ end
+
+ subgraph ALLOCATION["阶段2: 分配优化"]
+ C1[03_allocation.py
最优分配q*计算]
+ end
+
+ subgraph SCHEDULING["阶段3: 全局调度"]
+ D1[04_reschedule.py
访问次数重分配]
+ D2[05_calendar.py
日历排程生成]
+ D1 --> D2
+ end
+
+ subgraph EVALUATION["阶段4: 效果评估"]
+ E1[06_evaluate.py
E1',E2',F1',F2',R1,RS]
+ end
+
+ B2 --> C1 --> D1
+ D2 --> E1
+ end
+
+ subgraph VALIDATE["结果验证 ✅ 已完成"]
+ V1[约束满足检验
每日2事件、总730次]
+ V2[与Task 1对比
指标改进验证]
+ V3[分配合理性
q*边界检查]
+ end
+
+ subgraph SENSITIVITY["敏感性分析 ✅ 已完成"]
+ S1[07_sensitivity.py
4参数扫描]
+ S2[合并比例: 1/3,1/2,2/3]
+ S3[距离阈值: 30-70mi]
+ S4[容量上限: 400-500]
+ S5[CV阈值: 0.3-0.6]
+ S1 --> S2
+ S1 --> S3
+ S1 --> S4
+ S1 --> S5
+ end
+
+ subgraph VISUAL["可视化 ⏳ 待实现"]
+ P1[Fig.1 站点配对地图]
+ P2[Fig.2 分配策略散点图]
+ P3[Fig.3 敏感性曲线]
+ P4[Fig.4 日历热力图]
+ P5[Fig.5 风险分布图]
+ end
+
+ CORE --> VALIDATE
+ CORE --> SENSITIVITY
+ VALIDATE --> VISUAL
+ SENSITIVITY --> VISUAL
+ end
+
+ subgraph OUTPUT["输出文件"]
+ F1[01_distance.xlsx
距离矩阵]
+ F2[02_pairing.xlsx
34对配对]
+ F3[03_allocation.xlsx
最优分配]
+ F4[04_reschedule.xlsx
访问次数]
+ F5[05_calendar.xlsx
365天排程]
+ F6[06_evaluate.xlsx
效果对比]
+ F7[07_sensitivity.xlsx
敏感性结果]
+ end
+
+ A --> B1
+ E1 --> F1
+ E1 --> F2
+ E1 --> F3
+ E1 --> F4
+ E1 --> F5
+ E1 --> F6
+ S1 --> F7
+
+ style CORE fill:#90EE90
+ style VALIDATE fill:#90EE90
+ style SENSITIVITY fill:#90EE90
+ style VISUAL fill:#FFE4B5
+```
+
+### ASCII版本(详细)
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────────────────┐
+│ TASK 3 完整流程 │
+├─────────────────────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────────────────┐ │
+│ │ 核心流程 [已完成 ✓] │ │
+│ │ │ │
+│ │ task1/03_allocate.xlsx │ │
+│ │ │ │ │
+│ │ ▼ │ │
+│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │
+│ │ │ 01_distance.py │ ──▶ │ 02_pairing.py │ ──▶ │ 03_allocation.py │ │ │
+│ │ │ 距离矩阵70×70 │ │ 34对配对选择 │ │ 最优分配q* │ │ │
+│ │ └──────────────────┘ └──────────────────┘ └────────┬─────────┘ │ │
+│ │ │ │ │
+│ │ ▼ │ │
+│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │
+│ │ │ 06_evaluate.py │ ◀── │ 05_calendar.py │ ◀── │ 04_reschedule.py │ │ │
+│ │ │ 效果评估对比 │ │ 365天日历排程 │ │ 访问次数重分配 │ │ │
+│ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │
+│ └─────────────────────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ┌─────────────┼─────────────┬───────────────────────────┐ │
+│ │ │ │ │ │
+│ ▼ ▼ ▼ ▼ │
+│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
+│ │ 结果验证 [✓] │ │ 敏感性分析 [✓] │ │ 可视化 [待实现] │ │ 输出文件 │ │
+│ │ │ │ │ │ │ │ │ │
+│ │ ✓ 每日2事件 │ │ 07_sensitivity │ │ □ 配对地图 │ │ 01_distance.xlsx │ │
+│ │ ✓ 总730次访问 │ │ ┌──────────────┐ │ │ □ 分配散点图 │ │ 02_pairing.xlsx │ │
+│ │ ✓ q*边界检查 │ │ │合并比例 │ │ │ □ 敏感性曲线 │ │ 03_allocation.xlsx│ │
+│ │ ✓ Task 1对比 │ │ │ 1/3,1/2,2/3 │ │ │ □ 日历热力图 │ │ 04_reschedule.xlsx│ │
+│ │ │ │ ├──────────────┤ │ │ □ 风险分布图 │ │ 05_calendar.xlsx │ │
+│ │ 结论: │ │ │距离阈值 │ │ │ │ │ 06_evaluate.xlsx │ │
+│ │ E1↑16.9% │ │ │ 30-70 mi │ │ │ 图表清单: │ │ 07_sensitivity.xlsx│ │
+│ │ E2↑5.3% │ │ ├──────────────┤ │ │ Fig.1-5 │ │ │ │
+│ │ RS=19.5% │ │ │容量上限 │ │ │ │ │ │ │
+│ │ R1=17.1% │ │ │ 400-500 │ │ │ │ │ │ │
+│ │ │ │ ├──────────────┤ │ │ │ │ │ │
+│ │ │ │ │CV阈值 │ │ │ │ │ │ │
+│ │ │ │ │ 0.3-0.6 │ │ │ │ │ │ │
+│ │ │ │ └──────────────┘ │ │ │ │ │ │
+│ └──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 1. 问题本质与底层逻辑
### 1.1 题目核心要求
题目原文关键句:
> "decide on the amount of food to dispense at the first site **(since without pre-registration the demand at the next site is not known for sure)**"
+**为什么这是核心难点?**
+
+传统的单站点访问:需求已知(或可估计),分配是确定性问题。
+
+双站点访问的困境:
+```
+卡车出发时 ──▶ 到达站点i ──▶ 服务完成 ──▶ 前往站点j ──▶ 服务站点j
+ │ │ │ │
+ │ 需求D_i实现 │ 需求D_j实现
+ │ │ │ │
+ └── 决策点:预留多少给i? └── 剩余Q-S_i给j
+```
+
**问题本质**:这是一个**两阶段随机优化问题(Two-Stage Stochastic Optimization)**
+- **第一阶段决策**:在出发前决定 $q$(给站点i预留多少)
+- **第二阶段决策**:观测到 $D_i$ 后,$S_i = \min(D_i, q)$,剩余 $Q - S_i$ 给站点j
-- **第一阶段决策**:在出发前/到达第一站点时,决定分配量 $q$
-- **第二阶段决策**:观测到第一站点实际需求 $D_1$ 后,将剩余食物分配给第二站点
+### 1.2 为什么采用双站点模式?
-### 1.2 双站点模式的战略价值
+**底层逻辑**:资源效率优化
-| 收益 | 成本/风险 |
-|------|----------|
-| 释放卡车次数用于其他站点 | 单次服务两站点可能均不充分 |
-| 低需求站点合并提升效率 | 需求波动导致分配失衡 |
-| 减少志愿者调度复杂度 | 行程时间增加 |
+| 单站点模式 | 双站点模式 |
+|-----------|-----------|
+| 每天2次独立访问 | 每天可能1次双站点+1次单站点 |
+| 730次/年 | 释放部分槽位 |
+| 灵活但资源密集 | 效率更高但有风险 |
-**核心权衡**:资源节省 vs 服务质量风险
+**核心权衡**:
+- **收益**:释放访问槽位 → 可服务更多需求
+- **成本**:需求不确定性 → 可能导致某站点服务不足
---
-## 2. 符号定义与数据准备
+## 2. 符号定义与参数估计
### 2.1 符号体系
-| 符号 | 定义 | 单位/范围 |
+| 符号 | 定义 | 来源/估计 |
|------|------|----------|
-| $\mu_i, \sigma_i$ | 站点 $i$ 的需求均值和标准差 | 户 |
-| $\tilde{\mu}_i$ | 截断修正后的真实需求(来自Task 1) | 户 |
+| $\mu_i, \sigma_i$ | 站点 $i$ 的需求均值和标准差 | 2019年历史数据 |
+| $\tilde{\mu}_i$ | 截断修正后的真实需求 | Task 1截断回归 |
| $D_i$ | 站点 $i$ 的随机需求 | $D_i \sim N(\mu_i, \sigma_i^2)$ |
-| $Q$ | 卡车单次总服务能力 | 户(≈400,基于15000 lbs) |
-| $q$ | 第一站点预分配量 | 户 |
-| $l_{ij}$ | 站点 $i, j$ 间行驶距离 | 英里 |
-| $T_{travel}$ | 站点间转场时间 | 小时 |
-| $T_{service}$ | 单站点服务时间(≈2小时) | 小时 |
+| $Q$ | 卡车单次总服务能力 | 400户 |
+| $q$ | 第一站点预分配量 | **决策变量** |
+| $l_{ij}$ | 站点间距离 | Haversine公式计算 |
+| $k_i$ | 站点 $i$ 的年度访问次数 | Task 1 Hamilton分配 |
-### 2.2 关键参数估计
+### 2.2 关键参数推导
-**卡车容量转换**:
-$$Q = \frac{15000 \text{ lbs}}{w} \approx \frac{15000}{40} = 375 \text{ 户}$$
+**为什么 $Q = 400$?**
-其中 $w \approx 40$ lbs/户(基于典型服务225户 × 15000/225 ≈ 67 lbs,取保守估计)。
+题面信息:
+- 卡车运力 = 15,000 lbs
+- 典型服务 = 200-250户
-考虑到 $\mu_{max} = 396.6$,取 $Q = 400$ 作为软上限。
+推导:
+$$\text{每户食物量} = \frac{15000}{225} \approx 67 \text{ lbs}$$
+
+但数据显示 $\mu_{max} = 396.6$,说明实际每户分配可能更少:
+$$\text{实际每户} \approx \frac{15000}{400} = 37.5 \text{ lbs}$$
+
+取 $Q = 400$ 作为容量上限,与数据一致。
---
-## 3. 共生站点配对选择模型
+## 3. 共生站点配对模型
-### 3.1 配对可行性约束
+### 3.1 配对约束的底层逻辑
**约束1:同日可达性**
-单日时间预算:$T_{day} = 8$ 小时(工作日)
+**为什么需要距离约束?**
-可行条件:
-$$2 \times T_{service} + T_{travel}(l_{ij}) \leq T_{day}$$
-$$T_{travel}(l_{ij}) = \frac{l_{ij}}{v_{avg}} + T_{setup}$$
+单日时间预算分析:
+```
+工作日时间 = 8小时
+单站点服务 = 2小时 × 2 = 4小时
+转场+准备 = 0.5 + 行驶时间
+可用行驶时间 = 8 - 4 - 0.5 = 3.5小时
+```
-其中 $v_{avg} \approx 30$ mph(含路况因素),$T_{setup} = 0.5$ 小时(卸货准备)。
+假设平均车速30 mph(含路况):
+$$l_{max} = 3.5 \times 30 = 105 \text{ miles}$$
-**距离约束**:
-$$l_{ij} \leq (T_{day} - 2 \times T_{service} - T_{setup}) \times v_{avg} = (8 - 4 - 0.5) \times 30 = 105 \text{ miles}$$
+**为什么取50英里而非105英里?**
+- 保守设计:留有余量应对交通延误
+- 敏感性分析显示:50英里已覆盖足够多的配对
-取保守阈值 $l_{max} = 50$ 英里,确保充裕的服务时间。
+**距离计算公式**(Haversine简化):
+$$l_{ij} = 69.0 \times \sqrt{(\Delta lat)^2 + \cos^2(lat_{avg})(\Delta lon)^2}$$
-**距离计算**(Haversine公式简化):
-$$l_{ij} = 69.0 \times \sqrt{(lat_i - lat_j)^2 + \cos^2\left(\frac{lat_i + lat_j}{2} \times \frac{\pi}{180}\right)(lon_i - lon_j)^2}$$
+- 69.0 = 1度纬度对应的英里数
+- $\cos^2(lat_{avg})$ = 经度修正因子(高纬度地区经度圈变小)
**约束2:容量可行性**
-$$\mu_i + \mu_j \leq Q + \delta$$
+**为什么 $\mu_i + \mu_j \leq 450$?**
-取 $\delta = 50$(允许10%溢出,由需求波动吸收),即:
-$$\mu_i + \mu_j \leq 450$$
+- 卡车容量 $Q = 400$
+- 允许10%溢出($\delta = 50$):因为需求是随机的,平均值之和略超容量时,实际服务可能仍在容量内
+
+$$\mu_i + \mu_j \leq Q + \delta = 450$$
**约束3:需求稳定性**
-变异系数约束(排除高波动配对):
-$$CV_i = \frac{\sigma_i}{\mu_i} \leq 0.5 \quad \text{且} \quad CV_j = \frac{\sigma_j}{\mu_j} \leq 0.5$$
+**为什么限制CV(变异系数)?**
-### 3.2 配对价值评估
+变异系数 $CV = \sigma / \mu$ 衡量需求的相对波动性。
-对于满足可行性约束的配对 $(i, j)$,定义**配对价值函数**:
+高CV站点的问题:
+- 需求高度不确定
+- 分配策略难以优化
+- 服务缺口风险高
-$$V_{ij} = \underbrace{\alpha \cdot \frac{\mu_i + \mu_j}{Q}}_{\text{容量利用率}} - \underbrace{\beta \cdot \frac{l_{ij}}{l_{max}}}_{\text{距离惩罚}} - \underbrace{\gamma \cdot \frac{\sigma_i^2 + \sigma_j^2}{(\mu_i + \mu_j)^2}}_{\text{风险惩罚}} + \underbrace{\delta \cdot \mathbb{1}[\rho_{ij} < 0]}_{\text{对冲奖励}}$$
+经验阈值:$CV_{max} = 0.5$(即标准差不超过均值的一半)
-其中:
-- $\alpha = 1.0$:容量利用权重
-- $\beta = 0.3$:距离惩罚权重
-- $\gamma = 0.5$:风险惩罚权重
-- $\delta = 0.2$:需求负相关奖励
-- $\rho_{ij}$:需求相关系数(若数据可得)
+### 3.2 配对价值函数的设计逻辑
-### 3.3 配对选择算法
+**为什么需要价值函数?**
-```
-Algorithm 1: Symbiotic Site Pairing
-────────────────────────────────────
-Input: 70 sites with (μᵢ, σᵢ, latᵢ, lonᵢ)
-Output: Set of paired sites P
+满足约束的配对可能有上千个(实际1568对),需要选择最优的子集。
-1. Compute pairwise distance matrix L[i,j]
-2. Initialize candidate set C = ∅
-3. For each pair (i,j) where i < j:
- a. If l_ij ≤ l_max AND μᵢ + μⱼ ≤ 450 AND CVᵢ ≤ 0.5 AND CVⱼ ≤ 0.5:
- Add (i,j) to C with value V_ij
-4. Sort C by V_ij descending
-5. Initialize P = ∅, used = ∅
-6. For each (i,j) in C:
- If i ∉ used AND j ∉ used:
- Add (i,j) to P
- Add i, j to used
-7. Return P
-```
+**价值函数设计**:
-**贪心策略的合理性**:高价值配对优先,避免一个站点被多次配对。
+$$V_{ij} = \underbrace{\alpha \cdot \frac{\mu_i + \mu_j}{Q}}_{\text{容量利用率}} - \underbrace{\beta \cdot \frac{l_{ij}}{l_{max}}}_{\text{距离惩罚}} - \underbrace{\gamma \cdot \frac{\sigma_i^2 + \sigma_j^2}{(\mu_i + \mu_j)^2}}_{\text{风险惩罚}}$$
+
+**各项解释**:
+
+| 项 | 逻辑 | 权重 |
+|----|------|------|
+| 容量利用率 | 需求和接近Q的配对更有价值 | $\alpha = 1.0$ |
+| 距离惩罚 | 距离远的配对效率低 | $\beta = 0.3$ |
+| 风险惩罚 | 波动大的配对风险高 | $\gamma = 0.5$ |
+
+**为什么用贪心算法而非整数规划?**
+
+- 贪心算法:O(n²),实现简单,结果接近最优
+- 整数规划:精确但复杂度高,收益有限
+- 约束"每站点最多配对一次"使贪心算法非常有效
+
+### 3.3 实际运行结果
+
+**配对筛选**:
+- 候选配对数:1568对(满足3个约束)
+- 最终选择:**34对**(覆盖68个站点,97%)
+- 未配对站点:2个
+
+**Top 10 高价值配对**:
+
+| 配对 | 站点i | 站点j | 距离 | 需求和 | 价值 |
+|------|-------|-------|------|--------|------|
+| 1 | Boys and Girls Club | Redeemer Lutheran | 0.4 mi | 441 | 1.07 |
+| 2 | Rathbone | Woodhull | 5.8 mi | 445 | 1.06 |
+| 3 | Bath | Campbell | 9.6 mi | 448 | 1.05 |
+| 4 | Springview Apts | Waverly | 0.8 mi | 424 | 1.05 |
+| 5 | Birnie Transport | Lindley | 10.1 mi | 446 | 1.04 |
---
@@ -138,350 +334,331 @@ Output: Set of paired sites P
### 4.1 问题形式化
-设卡车访问配对 $(i, j)$,先访问站点 $i$,后访问站点 $j$。
+**为什么建模为随机优化?**
-**随机变量**:
-- $D_i \sim N(\mu_i, \sigma_i^2)$:站点 $i$ 的实际需求
-- $D_j \sim N(\mu_j, \sigma_j^2)$:站点 $j$ 的实际需求
+确定性思维的错误:
+> "给站点i分配 $\mu_i$,给站点j分配 $Q - \mu_i$"
-**决策变量**:$q$ = 为站点 $i$ 预留的食物量(在出发时决定)
+问题:实际需求 $D_i$ 可能大于或小于 $\mu_i$:
+- 若 $D_i > \mu_i$:站点i服务不足
+- 若 $D_i < \mu_i$:多余食物浪费在站点i,站点j也可能不足
-**服务量**:
-- 站点 $i$ 实际服务量:$S_i = \min(D_i, q)$
-- 站点 $j$ 实际服务量:$S_j = \min(D_j, Q - S_i) = \min(D_j, Q - \min(D_i, q))$
+**正确的建模**:
-### 4.2 目标函数设计
+决策变量:$q$ = 为站点i预留的食物量
-**目标1:最大化期望总服务量**
+服务量:
+$$S_i = \min(D_i, q), \quad S_j = \min(D_j, Q - S_i)$$
-$$\max_q \quad E[S_i + S_j] = E[\min(D_i, q)] + E[\min(D_j, Q - \min(D_i, q))]$$
+目标:最大化期望总服务量
+$$\max_q \quad E[S_i + S_j]$$
-**目标2:考虑公平性的惩罚项**
+### 4.2 解析解的推导过程
-定义未满足需求:
-$$U_i = (D_i - q)^+ = \max(0, D_i - q)$$
-$$U_j = (D_j - (Q - S_i))^+$$
+**引理:截断正态期望**
-**综合目标函数**:
-$$\max_q \quad E[S_i + S_j] - \lambda \cdot E[U_i + U_j] - \eta \cdot E[\max(U_i, U_j)]$$
+对于 $X \sim N(\mu, \sigma^2)$:
+$$E[\min(X, c)] = \mu \cdot \Phi(z) - \sigma \cdot \phi(z) + c \cdot (1 - \Phi(z))$$
-其中:
-- $\lambda$:未满足需求的边际惩罚(取 $\lambda = 0.5$)
-- $\eta$:最大缺口惩罚,体现公平性(取 $\eta = 0.3$)
+其中 $z = \frac{c - \mu}{\sigma}$。
-### 4.3 解析解推导
+**推导**:
-**引理(截断正态期望)**:若 $X \sim N(\mu, \sigma^2)$,则:
-$$E[\min(X, c)] = \mu \cdot \Phi(z) + \sigma \cdot \phi(z) - c \cdot (1 - \Phi(z))$$
-其中 $z = \frac{c - \mu}{\sigma}$,$\Phi, \phi$ 为标准正态CDF和PDF。
+$$E[\min(X, c)] = \int_{-\infty}^{c} x \cdot f(x) dx + c \cdot P(X > c)$$
-**简化情形**:假设 $D_i, D_j$ 独立,忽略 $\min(D_i, q)$ 的随机性对第二项的影响。
+第一项通过分部积分,第二项直接计算,得到上述公式。
-**一阶近似**:
-$$E[S_i] \approx \mu_i \cdot \Phi\left(\frac{q - \mu_i}{\sigma_i}\right) + \sigma_i \cdot \phi\left(\frac{q - \mu_i}{\sigma_i}\right)$$
+**最优条件推导**:
-$$E[S_j] \approx \mu_j \cdot \Phi\left(\frac{Q - q - \mu_j}{\sigma_j}\right) + \sigma_j \cdot \phi\left(\frac{Q - q - \mu_j}{\sigma_j}\right)$$
+简化假设:$D_i, D_j$ 独立,忽略 $S_i$ 的随机性对 $E[S_j]$ 的影响。
-**最优条件**(对 $q$ 求导并令其为零):
+$$E[S_i] \approx g(q; \mu_i, \sigma_i)$$
+$$E[S_j] \approx g(Q - q; \mu_j, \sigma_j)$$
-$$\frac{\partial E[S_i]}{\partial q} = \frac{\partial E[S_j]}{\partial q}$$
+对 $q$ 求导并令其为零:
+$$\frac{\partial E[S_i]}{\partial q} = \frac{\partial E[S_j]}{\partial (-q)}$$
-化简得:
-$$\Phi\left(\frac{q^* - \mu_i}{\sigma_i}\right) = \Phi\left(\frac{Q - q^* - \mu_j}{\sigma_j}\right)$$
+利用截断正态的导数性质:
+$$\frac{\partial E[\min(X, c)]}{\partial c} = 1 - \Phi\left(\frac{c - \mu}{\sigma}\right)$$
-**最优分配公式**:
+得到:
+$$1 - \Phi\left(\frac{q - \mu_i}{\sigma_i}\right) = 1 - \Phi\left(\frac{Q - q - \mu_j}{\sigma_j}\right)$$
-$$q^* = \frac{\sigma_j \mu_i + \sigma_i \mu_j + \sigma_i (Q - \mu_j) - \sigma_j \mu_i}{\sigma_i + \sigma_j} = \frac{\sigma_j \mu_i + \sigma_i (Q - \mu_j)}{\sigma_i + \sigma_j}$$
+$$\Phi\left(\frac{q - \mu_i}{\sigma_i}\right) = \Phi\left(\frac{Q - q - \mu_j}{\sigma_j}\right)$$
-整理得:
-$$\boxed{q^* = \frac{\sigma_j}{\sigma_i + \sigma_j} \mu_i + \frac{\sigma_i}{\sigma_i + \sigma_j} (Q - \mu_j)}$$
+由 $\Phi$ 的单调性:
+$$\frac{q - \mu_i}{\sigma_i} = \frac{Q - q - \mu_j}{\sigma_j}$$
-**物理解释**:
-- 当 $\sigma_j \gg \sigma_i$(站点 $j$ 波动大):$q^* \to \mu_i$,为站点 $i$ 精确分配
-- 当 $\sigma_i \gg \sigma_j$(站点 $i$ 波动大):$q^* \to Q - \mu_j$,为站点 $j$ 预留更多
-- 波动大的站点需要更多"缓冲"
+解得:
+$$\boxed{q^* = \frac{\sigma_j \mu_i + \sigma_i (Q - \mu_j)}{\sigma_i + \sigma_j}}$$
-### 4.4 鲁棒性考虑
+### 4.3 最优分配公式的物理解释
-**最坏情况约束**:确保在高需求情景下两站点均获得最低服务
+改写公式:
+$$q^* = \frac{\sigma_j}{\sigma_i + \sigma_j} \cdot \mu_i + \frac{\sigma_i}{\sigma_i + \sigma_j} \cdot (Q - \mu_j)$$
-$$q \geq \mu_i - k \sigma_i \quad \text{(站点 $i$ 的下界保护)}$$
-$$Q - q \geq \mu_j - k \sigma_j \quad \text{(站点 $j$ 的下界保护)}$$
+**解释**:
+- 当 $\sigma_j \gg \sigma_i$:$q^* \to \mu_i$(为站点i精确分配,因为j波动大需要更多缓冲)
+- 当 $\sigma_i \gg \sigma_j$:$q^* \to Q - \mu_j$(为站点j预留更多,因为i波动大)
+- 当 $\sigma_i = \sigma_j$:$q^* = \frac{\mu_i + Q - \mu_j}{2}$(平均分配)
-取 $k = 1$(约84%保护水平),则:
-$$\mu_i - \sigma_i \leq q \leq Q - \mu_j + \sigma_j$$
+**核心洞察**:波动大的站点需要更多"缓冲"空间。
-**最终分配策略**:
-$$q^{final} = \text{clip}(q^*, \mu_i - \sigma_i, Q - \mu_j + \sigma_j)$$
+### 4.4 鲁棒性约束
+
+**为什么需要约束?**
+
+极端情况下,$q^*$ 可能导致某站点获得很少的预留:
+- 若 $q^* < \mu_i - \sigma_i$:站点i很可能服务不足
+- 若 $Q - q^* < \mu_j - \sigma_j$:站点j很可能服务不足
+
+**约束设计**:
+$$\mu_i - k\sigma_i \leq q \leq Q - \mu_j + k\sigma_j$$
+
+取 $k = 1$(约84%保护水平)。
+
+**实际效果**:34对配对中,无一触及边界约束——说明最优解本身已经是鲁棒的。
+
+### 4.5 实际分配结果
+
+| 统计量 | 值 |
+|--------|-----|
+| $q^*$ 范围 | [23.9, 315.6] |
+| 分配比例范围 | [6.0%, 78.9%] |
+| 平均分配比例 | 50.6% |
+| 平均效率 | 94.2% |
+
+**Top 5 配对的分配方案**:
+
+| 配对 | $\mu_i$ | $\mu_j$ | $\sigma_i$ | $\sigma_j$ | $q^*$ | 比例 |
+|------|---------|---------|------------|------------|-------|------|
+| Boys & Girls + Redeemer | 210.8 | 230.6 | 28.9 | 93.5 | 195.9 | 49.0% |
+| Rathbone + Woodhull | 269.1 | 176.0 | 38.2 | 35.9 | 248.1 | 62.0% |
+| Bath + Campbell | 279.5 | 168.5 | 67.9 | 33.9 | 254.3 | 63.6% |
+| Springview + Waverly | 27.6 | 396.6 | 5.6 | 51.9 | 23.9 | 6.0% |
+| Birnie + Lindley | 213.4 | 232.9 | 27.8 | 56.9 | 189.4 | 47.3% |
---
## 5. 全局调度优化
-### 5.1 双站点访问的引入策略
+### 5.1 合并比例的设计逻辑
-**问题**:在Task 1的730次单站点访问中,哪些应改为双站点访问?
+**为什么不全部合并?**
-**决策逻辑**:
-1. 对于配对 $(i, j) \in P$,合并其部分访问
-2. 设原频次为 $k_i, k_j$,合并后:
- - 双站点访问次数:$k_{ij} = \min(k_i, k_j, k_{max})$
- - 站点 $i$ 剩余单独访问:$k_i' = k_i - k_{ij}$
- - 站点 $j$ 剩余单独访问:$k_j' = k_j - k_{ij}$
+风险考虑:
+- 双站点访问有服务缺口风险
+- 保留部分单独访问作为"保险"
-取 $k_{max} = \lfloor \min(k_i, k_j) / 2 \rfloor$,确保每个站点仍保留独立访问。
+**合并比例公式**:
+$$k_{ij} = \lfloor \min(k_i, k_j) / 2 \rfloor$$
-**释放的访问次数**:
-$$\Delta N = \sum_{(i,j) \in P} k_{ij}$$
+**示例**:站点i有10次访问,站点j有6次
+- 双站点访问次数:$\lfloor \min(10, 6) / 2 \rfloor = 3$
+- 站点i剩余单独访问:$10 - 3 = 7$
+- 站点j剩余单独访问:$6 - 3 = 3$
-这些次数可重新分配给其他高需求站点。
+### 5.2 释放槽位的重分配
-### 5.2 频次重分配模型
+**为什么双站点访问算1次事件?**
-设释放 $\Delta N$ 次访问,按需求比例分配给非配对站点:
+题目说"sending the same truck to visit two sites on **some of the trips**"——双站点是一次"行程"。
-$$k_i^{new} = k_i + \Delta N \cdot \frac{\tilde{\mu}_i}{\sum_{l \notin P} \tilde{\mu}_l} \quad \text{for } i \notin P$$
+**重分配逻辑**:
+1. 计算释放的槽位:$\Delta N = \sum k_{ij} = 142$
+2. 按需求比例分配给所有站点
+3. 使用Hamilton方法取整
-使用Hamilton方法取整(与Task 1一致)。
+### 5.3 实际运行结果
-### 5.3 日历排程调整
-
-**约束修改**:
-- 原约束:每日恰好2个单站点访问
-- 新约束:每日恰好2个"访问事件"(单站点或双站点)
-
-**双站点排程优先级**:
-- 双站点访问消耗更多时间,优先安排在周中(避免周末交通)
-- 同一配对的多次访问应均匀分布
-
-**排程算法**:沿用Task 1的贪心算法,将双站点访问视为单一事件。
+| 指标 | 值 |
+|------|-----|
+| 配对数 | 34 |
+| 双站点访问次数 | 142 |
+| 释放槽位 | 142 |
+| 最终单站点访问 | 588 |
+| 最终总事件 | 730(符合约束)|
---
-## 6. 效果评估体系
+## 6. 效果评估
-### 6.1 有效性指标扩展
+### 6.1 指标定义与逻辑
**E1':期望总服务量**
-$$E_1' = \sum_{i \notin P} k_i' \cdot \mu_i + \sum_{(i,j) \in P} k_{ij} \cdot E[S_i + S_j] + \sum_{(i,j) \in P} (k_i' \cdot \mu_i + k_j' \cdot \mu_j)$$
+$$E_1' = \sum_{\text{单站点}} k_i \cdot \mu_i + \sum_{\text{双站点}} k_{ij} \cdot E[S_i + S_j]$$
-**E2':质量加权服务量**(考虑双站点服务质量折扣)
+**E2':质量加权服务量**
-双站点访问的质量因子:
-$$q_{ij} = \frac{Q}{E[S_i + S_j]} \cdot q(\mu_i) \cdot q(\mu_j)$$
+**为什么需要质量加权?**
-### 6.2 公平性指标扩展
+当服务户数超过250时,每户分得的食物减少:
+$$q(\mu) = \min\left(1, \frac{250}{\mu}\right)$$
-**F1':满足率Gini系数**
+**双站点的质量计算(总量法)**:
+$$q_{ij} = \min\left(1, \frac{250}{\mu_i + \mu_j}\right)$$
-$$r_i = \frac{\text{年度期望服务量}_i}{\tilde{\mu}_i \times (\text{年度需求发生次数})}$$
+**为什么用总量而非分站点计算?**
+- 双站点共享同一卡车的食物
+- 总负载决定每户分得的量
+- 更符合物理实际
-**F2':最低满足率**
+**满足率 $r_i$**(与Task 1定义一致):
+$$r_i = \frac{k_i \cdot \mu_i}{\tilde{\mu}_i}$$
-$$F_2' = \min_i r_i$$
+对于参与配对的站点:
+$$r_i = \frac{k_i^{single} \cdot \mu_i + k_{ij} \cdot E[S_i]}{\tilde{\mu}_i}$$
-### 6.3 风险指标(新增)
+**R1:服务缺口风险**
-**R1:服务缺口概率**
+$$R_1 = P(S_i / D_i < 0.8 \text{ 或 } S_j / D_j < 0.8)$$
-$$R_1 = P(\text{至少一个站点服务量} < 0.8 \times \text{需求})$$
+### 6.2 实际结果对比
-**R2:期望最大缺口**
+| 指标 | Task 1 | Task 3 | 变化 | 变化% |
+|------|--------|--------|------|-------|
+| **E1 (总服务量)** | 140,121 | 163,777 | +23,656 | **+16.9%** |
+| **E2 (质量加权)** | 131,673 | 138,699 | +7,026 | **+5.3%** |
+| F1 (Gini系数) | 0.314 | 0.320 | +0.006 | +1.9% |
+| F2 (最低满足率) | 2.0 | 2.0 | 0 | 0% |
+| **R1 (缺口风险)** | 0 | 0.171 | +0.171 | 新增 |
+| **RS (资源节省)** | 0% | 19.5% | +19.5% | 新增 |
-$$R_2 = E[\max(U_i, U_j)]$$
-
-### 6.4 资源效率指标(新增)
-
-**Efficiency Gain**:
-$$\text{EG} = \frac{E_1' - E_1^{Task1}}{E_1^{Task1}} \times 100\%$$
-
-**Resource Saving**:
-$$\text{RS} = \frac{\Delta N}{730} \times 100\%$$
+**核心发现**:
+1. 通过双站点模式,释放19.5%的访问槽位
+2. 总服务量提升16.9%
+3. 公平性几乎不变(Gini仅增加1.9%)
+4. 代价是引入17.1%的服务缺口风险
---
-## 7. 数值算例与结果
+## 7. 敏感性分析
-### 7.1 配对筛选结果
+### 7.1 分析参数
-基于70个站点数据,应用Algorithm 1:
+| 参数 | 符号 | 基准值 | 扫描范围 |
+|------|------|--------|---------|
+| 合并比例 | $r_{merge}$ | 1/2 | [1/3, 1/2, 2/3] |
+| 距离阈值 | $l_{max}$ | 50 mi | [30, 40, 50, 60, 70] |
+| 容量上限 | $\mu_{sum,max}$ | 450 | [400, 425, 450, 475, 500] |
+| CV阈值 | $CV_{max}$ | 0.5 | [0.3, 0.4, 0.5, 0.6] |
-| 配对编号 | 站点 $i$ | 站点 $j$ | $l_{ij}$ (mi) | $\mu_i + \mu_j$ | $V_{ij}$ |
-|---------|---------|---------|--------------|-----------------|----------|
-| 1 | MFP Owego | MFP Candor | 12.3 | 287 | 0.82 |
-| 2 | MFP Elmira | MFP Horseheads | 8.7 | 312 | 0.79 |
-| 3 | MFP Norwich | MFP Oxford | 15.1 | 245 | 0.71 |
-| ... | ... | ... | ... | ... | ... |
+### 7.2 敏感性结果
-**预计配对数量**:10-15对(具体取决于数据)
+**1. 合并比例 — 最敏感参数**
-### 7.2 分配策略示例
+| merge_ratio | 双站点次数 | E1 | E2 | F1 | R1 |
+|-------------|-----------|-----|-----|------|------|
+| **1/3 (0.333)** | 93 | 155,724 | 136,556 | 0.3200 | 0.1708 |
+| **1/2 (0.500)** | 142 | 163,777 | 138,699 | 0.3199 | 0.1708 |
+| **2/3 (0.667)** | 196 | 171,574 | 140,891 | 0.3122 | 0.1708 |
-以配对 (MFP Owego, MFP Candor) 为例:
-- $\mu_i = 156, \sigma_i = 42$
-- $\mu_j = 131, \sigma_j = 38$
-- $Q = 400$
+- **E1变化幅度:9.68%** — 最敏感
+- 提高合并比例可显著增加服务量,但可能略微降低公平性(F1)。
-$$q^* = \frac{38 \times 156 + 42 \times (400 - 131)}{42 + 38} = \frac{5928 + 11298}{80} = 215$$
+**2. 距离阈值 — 稳健**
-**分配方案**:第一站点预留215户食物量,剩余185户给第二站点。
+| l_max | 配对数 | E1 | E2 | F1 | R1 |
+|-------|--------|-----|-----|------|------|
+| 30 mi | 33 | 162,180 | 137,436 | 0.3173 | 0.1757 |
+| 40 mi | 34 | 163,252 | 138,368 | 0.3201 | 0.1714 |
+| 50 mi | 34 | 163,777 | 138,699 | 0.3199 | 0.1708 |
+| 60 mi | 34 | 163,763 | 138,587 | 0.3200 | 0.1714 |
+| 70 mi | 34 | 163,588 | 138,484 | 0.3201 | 0.1714 |
-### 7.3 效果对比
+- **E1变化幅度:0.98%** — 模型极其稳健
+- 50英里是平衡效率与计算复杂度的理想选择。
-| 指标 | Task 1方案 | Task 3方案 | 变化 |
-|------|-----------|-----------|------|
-| E1 (总服务量) | 140,121 | 144,500 | +3.1% |
-| E2 (质量加权) | 131,673 | 134,200 | +1.9% |
-| F1 (Gini) | 0.314 | 0.298 | -5.1% (改善) |
-| F2 (min满足率) | 2.00 | 2.15 | +7.5% |
-| R1 (缺口概率) | 0 | 0.12 | 新增风险 |
-| RS (资源节省) | 0% | 4.1% | 30次访问 |
+**3. 容量上限 — 风险敏感**
+
+| mu_sum_max | E1 | E2 | F1 | R1 |
+|------------|-----|-----|------|------|
+| 400 | 162,858 | 140,271 | 0.3227 | 0.0937 |
+| 425 | 163,146 | 139,306 | 0.3213 | 0.1514 |
+| 450 | 163,777 | 138,699 | 0.3199 | 0.1708 |
+| 475 | 163,153 | 138,113 | 0.3185 | 0.1894 |
+| 500 | 162,688 | 136,835 | 0.3123 | 0.2036 |
+
+- **R1变化幅度:10.99%** — 对风险影响极大
+- 较低容量上限(400)可将风险降低近一半,同时维持较高的质量加权服务量(E2)。
+
+**4. CV阈值 — 准入敏感**
+
+| cv_max | 配对数 | E1 | E2 | F1 | R1 |
+|--------|--------|-----|-----|------|------|
+| 0.3 | 25 | 157,907 | 135,824 | 0.3190 | 0.1810 |
+| 0.4 | 31 | 161,248 | 137,282 | 0.3188 | 0.1748 |
+| 0.5 | 34 | 163,777 | 138,699 | 0.3199 | 0.1708 |
+| 0.6 | 35 | 163,951 | 138,873 | 0.3197 | 0.1659 |
+
+- **E1变化幅度:3.69%**
+- 增加CV阈值允许更多高波动性站点配对,略微提升总量但需谨慎对待。
+
+### 7.3 敏感性结论与汇总
+
+| 参数 | E1影响范围 | E2影响范围 | R1影响范围 | 结论 |
+|------|-----------|-----------|-----------|------|
+| **merge_ratio** | 9.68% | 3.13% | 0.00% | ⚠️ 关键决策参数 |
+| **l_max** | 0.98% | 0.91% | 0.49% | ✅ 参数稳健 |
+| **mu_sum_max** | 0.66% | 2.48% | 10.99% | ⚠️ 风险控制参数 |
+| **cv_max** | 3.69% | 2.20% | 1.51% | ✅ 较稳健 |
+
+**建议**:
+1. **合并比例**是最敏感参数,需根据风险承受能力选择
+2. **距离阈值**和**CV阈值**影响有限,模型稳健
+3. **容量上限**影响风险水平,保守选择400可降低风险50%
---
-## 8. 敏感性与鲁棒性分析
+## 8. 结果验证
-### 8.1 参数敏感性
+### 8.1 约束满足检验
-| 参数 | 基准值 | 扫描范围 | 对E1'的影响 |
-|------|--------|---------|------------|
-| $l_{max}$ | 50 mi | [30, 70] | ±1.2% |
-| $\lambda$ (惩罚) | 0.5 | [0.3, 0.7] | ±0.8% |
-| $k_{max}$ | $\min(k_i,k_j)/2$ | [1/3, 2/3] | ±2.1% |
+| 检验项 | 要求 | 实际 | 结果 |
+|--------|------|------|------|
+| 每日访问事件数 | 2 | min=2, max=2 | ✅ 通过 |
+| 年度总事件数 | 730 | 730 | ✅ 通过 |
+| 站点覆盖 | 全覆盖 | 70/70 | ✅ 通过 |
+| $q^*$边界检查 | 在[q_lower, q_upper]内 | 34/34在边界内 | ✅ 通过 |
-### 8.2 场景分析
+### 8.2 模型有效性验证
-**场景1:需求普遍上升10%**
-- 配对约束收紧,可行配对数减少
-- 建议:降低配对比例,保留更多单站点访问
+**与Task 1对比**:
+- E1提升16.9%:释放的槽位被有效利用
+- F1几乎不变:公平性未受损
+- R1可控:17.1%的缺口风险在合理范围
-**场景2:需求波动增大(σ增加20%)**
-- 风险指标R1显著上升
-- 建议:收紧稳定性约束 $CV_{max} = 0.4$
+**物理合理性**:
+- 高价值配对的需求和接近容量(平均413)
+- 最优分配比例接近50%(平均50.6%)
+- 低需求配对的双站点次数较少(合理)
---
-## 9. 算法实现
+## 9. 待实现:可视化图表
-### 9.1 Algorithm 2: First-Site Allocation Strategy
+### 9.1 图表清单
-```python
-def optimal_allocation(mu_i, sigma_i, mu_j, sigma_j, Q=400):
- """
- 计算双站点访问时第一站点的最优分配量
+| 图编号 | 图名 | 内容 | 目的 |
+|--------|------|------|------|
+| Fig.1 | 站点配对地图 | 70站点+34条配对连线 | 展示空间分布 |
+| Fig.2 | 分配策略散点图 | q* vs (μ_i, σ_i, σ_j) | 验证分配逻辑 |
+| Fig.3 | 敏感性曲线 | 4参数对E1,E2,R1的影响 | 参数选择依据 |
+| Fig.4 | 日历热力图 | 365天×2槽位 | 排程可视化 |
+| Fig.5 | 风险分布图 | 34对的缺口概率分布 | 风险识别 |
- Parameters:
- -----------
- mu_i, sigma_i : float
- 第一站点的需求均值和标准差
- mu_j, sigma_j : float
- 第二站点的需求均值和标准差
- Q : float
- 卡车总容量(以户数计)
+### 9.2 可视化脚本(待实现)
- Returns:
- --------
- q_final : float
- 第一站点的最优分配量
- """
- # 计算最优分配
- q_star = (sigma_j * mu_i + sigma_i * (Q - mu_j)) / (sigma_i + sigma_j)
-
- # 应用鲁棒性边界
- q_lower = max(0, mu_i - sigma_i)
- q_upper = min(Q, Q - mu_j + sigma_j)
-
- # 裁剪到可行范围
- q_final = max(q_lower, min(q_star, q_upper))
-
- return q_final
```
-
-### 9.2 Algorithm 3: Integrated Scheduling
-
-```python
-def integrated_scheduling(sites, pairings, original_k, Q=400):
- """
- 整合双站点访问的全局调度
-
- Parameters:
- -----------
- sites : DataFrame
- 站点数据,包含 mu, sigma, lat, lon
- pairings : list of tuples
- 配对站点列表 [(i, j), ...]
- original_k : dict
- 原始访问次数分配 {site_id: k}
-
- Returns:
- --------
- schedule : DataFrame
- 完整的年度调度表
- """
- paired_sites = set()
- for i, j in pairings:
- paired_sites.add(i)
- paired_sites.add(j)
-
- # 步骤1:计算每个配对的合并访问次数
- dual_visits = {}
- single_visits = original_k.copy()
-
- for i, j in pairings:
- k_ij = min(original_k[i], original_k[j]) // 2
- dual_visits[(i, j)] = k_ij
- single_visits[i] = original_k[i] - k_ij
- single_visits[j] = original_k[j] - k_ij
-
- # 步骤2:计算释放的访问次数
- delta_N = sum(dual_visits.values())
-
- # 步骤3:重分配给非配对站点
- non_paired_demand = sum(sites.loc[i, 'mu_corrected']
- for i in sites.index if i not in paired_sites)
-
- for i in sites.index:
- if i not in paired_sites:
- extra = int(delta_N * sites.loc[i, 'mu_corrected'] / non_paired_demand)
- single_visits[i] += extra
-
- # 步骤4:生成访问事件列表
- events = []
-
- # 单站点访问
- for site_id, k in single_visits.items():
- for visit_num in range(k):
- ideal_day = (visit_num + 0.5) * 365 / k
- events.append({
- 'type': 'single',
- 'site_i': site_id,
- 'site_j': None,
- 'ideal_day': ideal_day,
- 'q': None
- })
-
- # 双站点访问
- for (i, j), k_ij in dual_visits.items():
- mu_i, sigma_i = sites.loc[i, ['mu', 'sigma']]
- mu_j, sigma_j = sites.loc[j, ['mu', 'sigma']]
- q_opt = optimal_allocation(mu_i, sigma_i, mu_j, sigma_j, Q)
-
- for visit_num in range(k_ij):
- ideal_day = (visit_num + 0.5) * 365 / k_ij
- events.append({
- 'type': 'dual',
- 'site_i': i,
- 'site_j': j,
- 'ideal_day': ideal_day,
- 'q': q_opt
- })
-
- # 步骤5:贪心排程(与Task 1相同)
- events.sort(key=lambda x: x['ideal_day'])
- schedule = greedy_schedule(events, days=365, slots_per_day=2)
-
- return schedule
+task3/
+├── 08_visualize.py # 可视化脚本
+└── figures/
+ ├── fig1_pairing_map.png
+ ├── fig2_allocation_scatter.png
+ ├── fig3_sensitivity.png
+ ├── fig4_calendar_heatmap.png
+ └── fig5_risk_distribution.png
```
---
@@ -490,253 +667,95 @@ def integrated_scheduling(sites, pairings, original_k, Q=400):
### 10.1 主要发现
-1. **配对收益**:双站点模式可节省约4%的访问次数,释放资源服务更多需求
+1. **配对收益**:双站点模式可节省19.5%的访问槽位,服务量提升16.9%
2. **分配策略**:最优分配量 $q^*$ 与两站点需求波动性成反比——波动大的站点需要更多"缓冲"
-3. **风险可控**:通过鲁棒性约束,服务缺口概率控制在12%以内
+3. **风险可控**:通过合理的配对选择和鲁棒性约束,服务缺口风险控制在17%以内
+4. **参数稳健**:除合并比例外,其他参数对结果影响有限
### 10.2 对FBST的建议
| 建议 | 理由 |
|------|------|
+| 采用双站点模式 | 可释放19.5%资源服务更多需求 |
| 优先配对低需求、地理相近站点 | 容量利用率高,风险低 |
| 保留高需求站点的单独访问 | 避免服务不足 |
| 记录双站点访问的实际服务量 | 校准模型参数 |
-| 建立动态调整机制 | 季度复盘,调整配对策略 |
+| 合并比例取1/2 | 平衡效率与风险 |
### 10.3 模型局限性
1. **需求独立性假设**:实际中相邻站点需求可能相关
2. **服务时间固定**:大需求站点可能需要更长服务时间
3. **天气因素未纳入**:可与Task 2结合考虑
-
----
-
-## 11. 流程图
-
-### 11.1 Mermaid版本
-
-```mermaid
-flowchart TB
- subgraph INPUT["数据输入"]
- A[Task 1 结果
70站点 + 730次分配]
- end
-
- subgraph TASK3["TASK 3: 双站点同车优化"]
- direction TB
-
- subgraph PAIRING["阶段1: 配对选择"]
- B1[距离矩阵计算]
- B2[可行性筛选
距离/容量/稳定性]
- B3[配对价值评估
V_ij计算]
- B4[贪心配对选择]
- B1 --> B2 --> B3 --> B4
- end
-
- subgraph ALLOCATION["阶段2: 分配优化"]
- C1[随机优化建模]
- C2[解析解推导
q* 公式]
- C3[鲁棒性约束]
- C1 --> C2 --> C3
- end
-
- subgraph SCHEDULING["阶段3: 全局调度"]
- D1[访问次数重分配]
- D2[日历排程调整]
- D1 --> D2
- end
-
- subgraph EVALUATION["阶段4: 效果评估"]
- E1[有效性 E1', E2']
- E2[公平性 F1', F2']
- E3[风险 R1, R2]
- E4[资源节省 RS]
- end
-
- PAIRING --> ALLOCATION
- ALLOCATION --> SCHEDULING
- SCHEDULING --> EVALUATION
- end
-
- subgraph OUTPUT["输出"]
- F1[共生站点配对表]
- F2[最优分配策略]
- F3[2021年双站点调度表]
- F4[效果对比报告]
- end
-
- A --> B1
- EVALUATION --> F1
- EVALUATION --> F2
- EVALUATION --> F3
- EVALUATION --> F4
-
- style PAIRING fill:#87CEEB
- style ALLOCATION fill:#90EE90
- style SCHEDULING fill:#FFB6C1
- style EVALUATION fill:#DDA0DD
-```
-
-### 11.2 ASCII版本
-
-```
-┌─────────────────────────────────────────────────────────────────────────────┐
-│ TASK 3: 双站点同车优化 │
-├─────────────────────────────────────────────────────────────────────────────┤
-│ │
-│ Task 1 结果 ──▶ ┌─────────────────────────────────────────────────────┐ │
-│ (70站点,730次) │ 阶段1: 配对选择 │ │
-│ │ 距离矩阵 → 可行性筛选 → 价值评估 → 贪心选择 │ │
-│ │ (l_ij) (3约束) (V_ij) (Algorithm 1) │ │
-│ └─────────────────────────┬───────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ ┌─────────────────────────────────────────────────────┐ │
-│ │ 阶段2: 分配优化 │ │
-│ │ │ │
-│ │ max E[S_i + S_j] - λE[U_i + U_j] │ │
-│ │ ↓ │ │
-│ │ q* = (σⱼμᵢ + σᵢ(Q-μⱼ)) / (σᵢ+σⱼ) │ │
-│ │ ↓ │ │
-│ │ q_final = clip(q*, μᵢ-σᵢ, Q-μⱼ+σⱼ) │ │
-│ └─────────────────────────┬───────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ ┌─────────────────────────────────────────────────────┐ │
-│ │ 阶段3: 全局调度 │ │
-│ │ │ │
-│ │ ┌──────────────┐ ┌──────────────┐ │ │
-│ │ │ 释放 ΔN 次数 │ → │ 重分配给 │ │ │
-│ │ │ (约30次) │ │ 高需求站点 │ │ │
-│ │ └──────────────┘ └──────────────┘ │ │
-│ │ ↓ │ │
-│ │ 日历排程:双站点事件 + 单站点事件 │ │
-│ └─────────────────────────┬───────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ ┌─────────────────────────────────────────────────────┐ │
-│ │ 阶段4: 效果评估 │ │
-│ │ │ │
-│ │ ┌────────────┬────────────┬────────────┐ │ │
-│ │ │ 有效性 │ 公平性 │ 风险 │ │ │
-│ │ │ E1': +3.1% │ F1: -5.1% │ R1: 0.12 │ │ │
-│ │ │ E2': +1.9% │ F2: +7.5% │ RS: 4.1% │ │ │
-│ │ └────────────┴────────────┴────────────┘ │ │
-│ └─────────────────────────────────────────────────────┘ │
-│ │
-└─────────────────────────────────────────────────────────────────────────────┘
-```
+4. **简化的不确定性传播**:忽略了 $S_i$ 对 $S_j$ 的随机影响
---
## 附录A:关键公式速查
-| 公式 | 用途 |
-|------|------|
-| $q^* = \frac{\sigma_j \mu_i + \sigma_i (Q - \mu_j)}{\sigma_i + \sigma_j}$ | 最优第一站点分配 |
-| $V_{ij} = \alpha \frac{\mu_i + \mu_j}{Q} - \beta \frac{l_{ij}}{l_{max}} - \gamma \frac{\sigma_i^2 + \sigma_j^2}{(\mu_i + \mu_j)^2}$ | 配对价值函数 |
-| $l_{ij} = 69.0 \times \sqrt{(\Delta lat)^2 + \cos^2(lat_{avg})(\Delta lon)^2}$ | 距离计算 |
-| $E[\min(X, c)] = \mu \Phi(z) + \sigma \phi(z) - c(1-\Phi(z))$ | 截断期望 |
-| $R_1 = P(\min(S_i/D_i, S_j/D_j) < 0.8)$ | 服务缺口风险 |
+| 公式 | 用途 | 来源 |
+|------|------|------|
+| $q^* = \frac{\sigma_j \mu_i + \sigma_i (Q - \mu_j)}{\sigma_i + \sigma_j}$ | 最优第一站点分配 | 4.2节推导 |
+| $V_{ij} = \alpha \frac{\mu_i + \mu_j}{Q} - \beta \frac{l_{ij}}{l_{max}} - \gamma \frac{\sigma_i^2 + \sigma_j^2}{(\mu_i + \mu_j)^2}$ | 配对价值函数 | 3.2节设计 |
+| $E[\min(X, c)] = \mu \Phi(z) - \sigma \phi(z) + c(1-\Phi(z))$ | 截断正态期望 | 统计引理 |
+| $q(\mu) = \min(1, 250/\mu)$ | 质量折扣因子 | Task 1定义 |
+| $k_{ij} = \lfloor \min(k_i, k_j) / 2 \rfloor$ | 合并次数 | 5.1节设计 |
---
-## 附录B:与Task 1的衔接
-
-| 维度 | Task 1 | Task 3 |
-|------|--------|--------|
-| 访问模式 | 单站点 | 单站点 + 双站点 |
-| 决策变量 | $k_i$ (访问次数) | $k_i$, $k_{ij}$, $q^*$ |
-| 不确定性 | 未考虑 | 核心建模对象 |
-| 约束 | 每日2站点 | 每日2"事件" |
-| 评估指标 | E1, E2, F1, F2 | E1', E2', F1', F2', R1, R2, RS |
-
----
-
-## 附录C:参数设置汇总
-
-| 参数 | 符号 | 基准值 | 依据 |
-|------|------|--------|------|
-| 卡车容量 | $Q$ | 400户 | 15000lbs ÷ 40lbs/户 |
-| 距离阈值 | $l_{max}$ | 50 mi | 时间预算推导 |
-| 容量溢出容忍 | $\delta$ | 50户 | 10%缓冲 |
-| CV上限 | $CV_{max}$ | 0.5 | 经验值 |
-| 未满足惩罚 | $\lambda$ | 0.5 | 敏感性分析 |
-| 最大缺口惩罚 | $\eta$ | 0.3 | 敏感性分析 |
-| 鲁棒性水平 | $k$ | 1 | 84%保护 |
-| **合并比例** | $r_{merge}$ | 1/2 | 保留50%独立访问 |
-
----
-
-## 附录D:实现决策记录
-
-### D.1 关键设计决策
-
-| 决策项 | 选择 | 理由 |
-|--------|------|------|
-| 数据来源 | Task 1结果 (`task1/03_allocate.xlsx`) | 复用已验证的需求修正和频次分配 |
-| 有效性衰减计算 | **总量计算** $q(\mu_i + \mu_j)$ | 双站点共享同一卡车,总负载决定服务质量 |
-| 合并比例 | $k_{max} = \lfloor \min(k_i, k_j) / 2 \rfloor$ | 保留50%独立访问,平衡效率与风险 |
-| 配对策略 | 每站点最多配对一次 | 简化实现,足以说明方法论 |
-| 双站点访问计数 | 算1次访问事件 | 释放槽位给其他站点 |
-
-### D.2 有效性衰减公式(总量计算)
-
-双站点访问时,质量折扣因子按总服务量计算:
-
-$$q_{ij} = \min\left(1, \frac{250}{\mu_i + \mu_j}\right)$$
-
-**E2'的计算**:
-$$E_2' = \sum_{\text{单站点}} k_i \cdot q(\mu_i) \cdot \mu_i + \sum_{\text{双站点}} k_{ij} \cdot q(\mu_i + \mu_j) \cdot E[S_i + S_j]$$
-
-### D.3 合并比例详述
-
-对于配对 $(i, j)$,原频次为 $k_i, k_j$:
-
-| 项目 | 公式 |
-|------|------|
-| 双站点访问次数 | $k_{ij} = \lfloor \min(k_i, k_j) / 2 \rfloor$ |
-| 站点i剩余单独访问 | $k_i' = k_i - k_{ij}$ |
-| 站点j剩余单独访问 | $k_j' = k_j - k_{ij}$ |
-| 释放的访问槽位 | $\Delta N = \sum k_{ij}$ |
-
----
-
-## 附录E:敏感性分析计划
-
-### E.1 待分析参数
-
-| 参数 | 基准值 | 扫描范围 | 预期影响 |
-|------|--------|---------|---------|
-| **合并比例** $r_{merge}$ | 1/2 | [1/3, 1/2, 2/3] | 配对数量、资源节省 |
-| 距离阈值 $l_{max}$ | 50 mi | [30, 40, 50, 60, 70] | 可行配对数 |
-| 容量上限 $\mu_i + \mu_j$ | 450 | [400, 425, 450, 475, 500] | 配对选择范围 |
-| CV阈值 | 0.5 | [0.3, 0.4, 0.5, 0.6] | 配对稳定性 |
-
-### E.2 敏感性分析输出
-
-- 各参数对 E1', E2', F1', R1 的影响曲线
-- 参数交互效应热力图
-- 稳健性结论
-
----
-
-## 附录F:程序流水线
+## 附录B:程序流水线
```
task3/
-├── 01_distance.py # 距离矩阵计算
-│ └── 01_distance.xlsx
-├── 02_pairing.py # 配对筛选与选择
-│ └── 02_pairing.xlsx
-├── 03_allocation.py # 最优分配计算
-│ └── 03_allocation.xlsx
-├── 04_reschedule.py # 访问次数重分配
-│ └── 04_reschedule.xlsx
-├── 05_calendar.py # 日历排程生成
-│ └── 05_calendar.xlsx
-├── 06_evaluate.py # 效果评估
-│ └── 06_evaluate.xlsx
-├── 07_sensitivity.py # 敏感性分析(待实现)
-│ └── 07_sensitivity.xlsx
-└── figures/ # 可视化输出
+├── 01_distance.py ✅ 距离矩阵计算
+│ └── 01_distance.xlsx (70×70矩阵)
+├── 02_pairing.py ✅ 配对筛选与选择
+│ └── 02_pairing.xlsx (34对配对)
+├── 03_allocation.py ✅ 最优分配计算
+│ └── 03_allocation.xlsx (q*值)
+├── 04_reschedule.py ✅ 访问次数重分配
+│ └── 04_reschedule.xlsx (k'值)
+├── 05_calendar.py ✅ 日历排程生成
+│ └── 05_calendar.xlsx (365天)
+├── 06_evaluate.py ✅ 效果评估
+│ └── 06_evaluate.xlsx (指标对比)
+├── 07_sensitivity.py ✅ 敏感性分析
+│ └── 07_sensitivity.xlsx (4参数)
+├── 08_visualize.py ⏳ 可视化(待实现)
+│ └── figures/ (5张图)
+└── README.md ✅ 本文档
```
+
+---
+
+## 附录C:运行命令
+
+```bash
+cd task3
+
+# 完整流程
+python 01_distance.py
+python 02_pairing.py
+python 03_allocation.py
+python 04_reschedule.py
+python 05_calendar.py
+python 06_evaluate.py
+python 07_sensitivity.py
+
+# 一键运行(可选)
+for i in 01 02 03 04 05 06 07; do python ${i}_*.py; done
+```
+
+---
+
+## 附录D:参数设置汇总
+
+| 参数 | 符号 | 基准值 | 敏感性 | 依据 |
+|------|------|--------|--------|------|
+| 卡车容量 | $Q$ | 400户 | - | 数据推断 |
+| 距离阈值 | $l_{max}$ | 50 mi | 低 | 时间预算 |
+| 容量上限 | $\mu_{sum,max}$ | 450 | 中(风险) | Q+10% |
+| CV上限 | $CV_{max}$ | 0.5 | 低 | 经验值 |
+| 合并比例 | $r_{merge}$ | 1/2 | **高** | 效率-风险平衡 |
+| 鲁棒性水平 | $k$ | 1 | - | 84%保护 |
+| 质量阈值 | $\bar{C}$ | 250 | - | Task 1定义 |