From 6e67e05525fb6fe0ab3d2721dd4e62f12fccece1 Mon Sep 17 00:00:00 2001 From: ntnt Date: Mon, 19 Jan 2026 10:14:46 +0800 Subject: [PATCH] P1: rebuild --- TASK3.md | 87 +++++ data.xlsx | Bin 0 -> 17927 bytes prob.md | 30 ++ task1/01_clean.py | 120 ++++--- task1/01_clean.xlsx | Bin 9676 -> 10052 bytes task1/02_demand.xlsx | Bin 0 -> 11367 bytes task1/02_demand_correction.py | 140 ++++++++ task1/02_neighbor.xlsx | Bin 73135 -> 0 bytes task1/02_neighbor_demand.py | 59 ---- task1/03_allocate.py | 155 +++++++++ task1/03_allocate.xlsx | Bin 36781 -> 11526 bytes task1/03_allocate_k.py | 301 ------------------ task1/04_evaluate.py | 233 ++++++++++++++ task1/04_metrics.xlsx | Bin 0 -> 10762 bytes task1/04_schedule.xlsx | Bin 46012 -> 0 bytes task1/04_schedule_2021.py | 219 ------------- task1/05_schedule.py | 303 ++++++++++++++++++ task1/05_schedule.xlsx | Bin 0 -> 26042 bytes task1/README.md | 581 +++++++++++++++++++++++++++++----- 19 files changed, 1526 insertions(+), 702 deletions(-) create mode 100644 TASK3.md create mode 100644 data.xlsx create mode 100644 prob.md create mode 100644 task1/02_demand.xlsx create mode 100644 task1/02_demand_correction.py delete mode 100644 task1/02_neighbor.xlsx delete mode 100644 task1/02_neighbor_demand.py create mode 100644 task1/03_allocate.py delete mode 100644 task1/03_allocate_k.py create mode 100644 task1/04_evaluate.py create mode 100644 task1/04_metrics.xlsx delete mode 100644 task1/04_schedule.xlsx delete mode 100644 task1/04_schedule_2021.py create mode 100644 task1/05_schedule.py create mode 100644 task1/05_schedule.xlsx diff --git a/TASK3.md b/TASK3.md new file mode 100644 index 0000000..20c0c8b --- /dev/null +++ b/TASK3.md @@ -0,0 +1,87 @@ +# TASK3 +## 问题分析 +问题三需要对问题一的时间表格进行进一步优化,允许双站点同车分配。本文首先依据已有数据,发现部分站点之间的地理位置相近,存在“同日可达性”。而后结合问题一已有时间表,创新性提出“共生站点”的思想 ,对于地理位置相近、需求符合的站点记作“共生站点”。随后复用问题一的模型进行求解,得到新的时间表方案并与问题一的公平性和有效性进行比较 。 +## 共生站点选择模型的构建与求解 +### 模型构建 +分析题目可知,共生站点的的配对条件是“同日可达性”“需求可叠加性”“需求稳定性”,本文分析数据集及实际情况,对于配对条件进行如下量化。 +1.同日可达性 +观察数据集可知,站点的经纬度已经知晓,并且本文需要的是各站点之间的曼哈顿距离为小范围地域内,因此本文以经纬度转化下的曼哈顿距离作为两站点之间的可达性指标,其计算公式如下: +$$ +l_{ij}=69.0\cdot \left | lat_{i}-lat_{j} \right | +69.0\cdot \cos(\frac{lon_{i}+lon_{j}}{2})\left | lon_{i}-lon_{j} \right | +$$ +其中$l_{ij}$为曼哈顿距离,$69.0$为英里转化常数 +2.需求可叠加性 +题目指出,卡车的服务家庭上限为250户,因此对于两个站点合并为一个共生站点时,其需求的叠加必须满足一定限度,为此本文给出如下判断公式: +$$ +d_{ij}=d_{i}+d_{j}\le???(给定值) +$$ +上限值略高于卡车最大限制的目的是保留在两站点合并后略超出卡车上限的配对组合,尽可能实现食物的最大程度有效服务。 +3.需求稳定性 +对于接近卡车最大限制的配对组合,本文进一步考虑其需求稳定性,删去波动大的配对以防止后续共生站点的分配不均。其判别公式为 +$$ +\left\{\begin{matrix} + d_{ij}\ge??& 需求临界状态判别\\ + \frac{\sigma_{i}}{d_{i}},\frac{\sigma_{j}}{d_{j}}\ge???&波动影响判别 +\end{matrix}\right. +$$ +### 模型求解 +通过上述量化筛选公式,本文得到最终的共生站点如下表所示 + + + + +## 第一站点分配模型的建立与求解 +确定共生站点后,本文认为共生站点的内部分配即为分配有效性问题,因此本文参照问题一的有效得分指标,给出共生站点的分配有效得分公式如下 +第一站点的实际分配货物 +$$ +g_{i}=\min(d_{i},q_{i}) +$$ +其中$q_{i}$为第一站点的假想分配货物数量 +第二站点的实际分配货物为 +$$ +g_{j}=\min(d_{j},d_{0}-g_{i}) +$$ +$$ +score=E(g_{i}+g_{j})-\lambda E(unmet_{i}+unmet_{j})-\mu E(worst_{i}+worst_{j}) +$$ +选取分配有效得分最大的$q_{i}$作为第一站点的分配货物数量 +最终我们得出各共生站点的分配方案如下表 +## 共生站点下的访问分配模型的建立与求解 +对于访问次数的求解,等价于减少站点总数并且共生站点需求合并下的访问次数求解,与问题一的访问次数模型相同,本文不再赘述,对于访问时间分配的求解,本文沿用问题一的模型进行求解,由于共生站点的存在,本问的决策变量变为 +第$i$个单独站点第$m$次运输的时间$s_{i, m}$ +第$x$个共生站点第$y$次运输时间$s_{x,y}$ +第$i$个单独站点第$t$天是否被访问 +$$ +a_{i,t}=\left\{\begin{matrix} + 1&t\in S_{i}\\ + 0&t\notin S_{i} +\end{matrix}\right. +$$ +第$x$个共生站点第$t$天是否被访问 +$$ +a_{x,t}=\left\{\begin{matrix} + 1&t\in S_{x}\\ + 0&t\notin S_{x} +\end{matrix}\right. +$$ +为了表示各站点访问时间的均匀分布,本文定义目标函数为所有站点实际时间间隔与理想时间间隔的差值的绝对值之和 +$$ +\min Z = \sum_{i} \sum_{m=1}^{k_{i}} \left| (s_{i, m+1} - s_{i, m}) - T_{i} \right|+ \sum_{x} \sum_{y=1}^{k_{x}} \left| (s_{i, y+1} - s_{i, y}) - T_{i} \right| +$$ +约束条件为 +1.每日的访问总次数不得超过最大运输能力 +$$ +\sum_{i}a_{i,t}+\sum_{x}a_{x,t}\le2 +$$ +2.所有站点必须达到规定访问次数 +$$ +\sum_{t}a_{i,t}=k_{i},\sum_{t}a_{x,t}=k_{x} +$$ +3.相邻两次访问不得小于默认间隔 +$$ +s_{i, m+1} - s_{i, m}\ge t_{0},s_{x, m+1} - s_{x, m}\ge t_{0} +$$ + +最终得到求解结果为???? + + diff --git a/data.xlsx b/data.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0a9b5eef228431b4dd8fdb646aa378acad194272 GIT binary patch literal 17927 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 Xk0{9^AU?9A!u}RvQzTsR$Fu(fCE_NW literal 0 HcmV?d00001 diff --git a/prob.md b/prob.md new file mode 100644 index 0000000..ab7ef7f --- /dev/null +++ b/prob.md @@ -0,0 +1,30 @@ +# The “Where” and “When” of food distribution. + +Feeding America is a network of organizations striving to provide food security for people with limited financial resources. The Food Bank of the Southern Tier (FBST) is one of these organizations, serving six counties and nearly 4,000 square miles in New York State. + +In normal years, the Mobile Food Pantry (MFP) program is among the main activities of FBST. The goal is to make nutritious and healthy food more accessible to people in underserved communities. Even in areas where other agencies provide assistance, clients may not always have access to food because of limited transportation options or because those agencies are only open certain hours or days per week. + +An MFP provides food directly to clients in pre-packed boxes or through a farmers market-style distribution. When an MFP truck arrives at a site, volunteers lay out the food on tables surrounding it. The clients can then "shop", choosing items that they need. Each truck can transport up to 15,000 pounds. Three MFP trucks are available, and FBST usually has enough food and volunteers to operate 2 of them on any given day. A typical mobile pantry visit lasts two hours and provides 200 to 250 families with nutritious food to help them make ends meet. The schedule of all MFP site visits is published months in advance, to help clients plan accordingly. + +Unfortunately, the COVID-19 pandemic forced FBST to modify its services. The current version of the MFP program is very limited in scope & far less flexible: offering monthly visits to only 9 major sites, requiring all clients to register in advance for each pick-up, and using socially-distanced pick-up options. FBST plans to return to a pre-pandemic level of MFP services next year and will allow clients to simply show up when a truck visits without having to sign up in advance. Your team's goal is to help them optimize the schedule for 2021 using the statistical data from 2019. In that year, MFP has serviced 70 regular sites, with 722 visits across all of them. The map (with site locations & demand serviced at each of them) is included below. + +We ask you to complete only three of the following 4 tasks: $1 +$ either 2 or 3 $+$ 4. + +1. Propose an effective & fair schedule for visiting all of these 70 regular sites in 2021. The frequency of visits should be informed by the total demand in surrounding communities. Effectiveness: how well do you serve all clients on average? + +Fairness: are some of them served much better than others? + +2. Based on historical data, on particularly cold winter days, the number of clients attending an MFP visit can be much lower than usual. But when the weather improves, the demand often spikes for the next visit to any site nearby. This suggests that some of the clients (those who own cars or have better public transportation options) are willing to come to pick-up sites that are a little farther. Of course, this only works if the timing of MFP visits to those nearby sites is suitable. + +You can try to incorporate this information in two very different ways: + +(a) Reduce the total number of serviced sites (and optimize their location), hoping that people will still drive or take a bus to them. +(b) Keep the number & location of sites the same, but schedule the MFP visits in a way that makes such longer trips by clients feasible (even if less desirable). + +Modify your previous scheduling approach to incorporate one of the above two options. Quantify the resulting improvements in performance. + +3. To optimize the volunteers' efforts, the FBST is also considering a new option of sending the same truck to visit two different sites on some of the trips. Along with the challenge of selecting the sites and the date, this would also make it necessary to 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). Suggest an algorithm to address this, characterizing both the effectiveness & fairness of the resulting distribution. +4. Along with your technical manuscript, please submit a 1-page executive summary, describing the key advantages and potential drawbacks of your recommendations. + +![](images/05746b4a5d7cf8837e3088440c699644fc5787073ab2c767d81af5c786fca25b.jpg) +Location of 70 MFP distribution sites across the six counties of New York State regularly serviced by FBST in 2019. (The above omits 12 more sites, which were serviced only occasionally – less than 5 times each in 2019.) The color of disks indicates the average demand (the number of clients serviced per visit) for each of the sites. Most of these sites are unfortunately not serviced in 2020. Graphical information summary by Dr. F. Alkaabneh. A spreadsheet version of this dataset is also available here. \ No newline at end of file diff --git a/task1/01_clean.py b/task1/01_clean.py index 6d63e74..1844024 100644 --- a/task1/01_clean.py +++ b/task1/01_clean.py @@ -1,58 +1,92 @@ +""" +Step 01: 数据清洗与标准化 + +输入: ../data.xlsx (原始数据) +输出: 01_clean.xlsx (清洗后的标准化数据) + +功能: +1. 读取原始数据 +2. 保留有效列并重命名为标准字段名 +3. 生成 site_id (1-70) +4. 检查缺失值和数据质量 +""" + import pandas as pd +import numpy as np +from pathlib import Path + +# 路径配置 +INPUT_PATH = Path(__file__).parent.parent / "data.xlsx" +OUTPUT_PATH = Path(__file__).parent / "01_clean.xlsx" + +# 列名映射: 原始列名 -> 标准列名 +COLUMN_MAPPING = { + 'Site Name': 'site_name', + 'latitude': 'lat', + 'longitude': 'lon', + 'Number of Visits in 2019': 'visits_2019', + 'Average Demand per Visit': 'mu', + 'StDev(Demand per Visit)': 'sigma' +} -INPUT_XLSX = "data.xlsx" -OUTPUT_XLSX = "task1/01_clean.xlsx" -SHEET_NAME = "addresses2019 updated" +def main(): + print("=" * 60) + print("Step 01: 数据清洗与标准化") + print("=" * 60) + # 1. 读取原始数据 + print(f"\n[1] 读取原始数据: {INPUT_PATH}") + df_raw = pd.read_excel(INPUT_PATH) + print(f" 原始数据: {df_raw.shape[0]} 行, {df_raw.shape[1]} 列") -def main() -> None: - df_raw = pd.read_excel(INPUT_XLSX, sheet_name=SHEET_NAME) + # 2. 选择并重命名列 + print(f"\n[2] 选择有效列并重命名") + df = df_raw[list(COLUMN_MAPPING.keys())].copy() + df = df.rename(columns=COLUMN_MAPPING) - required = [ - "Site Name", - "latitude", - "longitude", - "Number of Visits in 2019", - "Average Demand per Visit", - "StDev(Demand per Visit)", - ] - missing = [c for c in required if c not in df_raw.columns] - if missing: - raise ValueError(f"Missing required columns: {missing}") + # 3. 生成 site_id + print(f"\n[3] 生成 site_id (1-70)") + df.insert(0, 'site_id', range(1, len(df) + 1)) - df = df_raw[required].copy() - df = df.rename( - columns={ - "Site Name": "site_name", - "latitude": "lat", - "longitude": "lon", - "Number of Visits in 2019": "visits_2019", - "Average Demand per Visit": "mu_clients_per_visit", - "StDev(Demand per Visit)": "sd_clients_per_visit", - } - ) + # 4. 数据质量检查 + print(f"\n[4] 数据质量检查") + print(f" 缺失值统计:") + missing = df.isnull().sum() + for col, count in missing.items(): + if count > 0: + print(f" - {col}: {count} 个缺失值") + if missing.sum() == 0: + print(f" - 无缺失值") - df.insert(0, "site_id", range(1, len(df) + 1)) + # 5. 数据统计摘要 + print(f"\n[5] 关键字段统计:") + print(f" 站点数: {len(df)}") + print(f" μ (单次服务人数均值):") + print(f" - 范围: [{df['mu'].min():.1f}, {df['mu'].max():.1f}]") + print(f" - 均值: {df['mu'].mean():.1f}") + print(f" - μ > 250 的站点数: {(df['mu'] > 250).sum()}") + print(f" σ (单次服务人数标准差):") + print(f" - 范围: [{df['sigma'].min():.1f}, {df['sigma'].max():.1f}]") + print(f" 2019年访问次数:") + print(f" - 总计: {df['visits_2019'].sum()}") + print(f" - 范围: [{df['visits_2019'].min()}, {df['visits_2019'].max()}]") - numeric_cols = ["lat", "lon", "visits_2019", "mu_clients_per_visit", "sd_clients_per_visit"] - for col in numeric_cols: - df[col] = pd.to_numeric(df[col], errors="coerce") + # 6. 保存输出 + print(f"\n[6] 保存输出: {OUTPUT_PATH}") + df.to_excel(OUTPUT_PATH, index=False) + print(f" 已保存 {len(df)} 条记录") - if df["site_name"].isna().any(): - raise ValueError("Found missing site_name values.") - if df[numeric_cols].isna().any().any(): - bad = df[df[numeric_cols].isna().any(axis=1)][["site_id", "site_name"] + numeric_cols] - raise ValueError(f"Found missing numeric values:\n{bad}") - if (df["mu_clients_per_visit"] < 0).any() or (df["sd_clients_per_visit"] < 0).any(): - raise ValueError("Found negative mu/sd values; expected nonnegative.") - if (df["visits_2019"] <= 0).any(): - raise ValueError("Found non-positive visits_2019; expected >0 for all 70 regular sites.") + # 7. 显示前5行 + print(f"\n[7] 输出数据预览 (前5行):") + print(df.head().to_string(index=False)) - with pd.ExcelWriter(OUTPUT_XLSX, engine="openpyxl") as writer: - df.to_excel(writer, index=False, sheet_name="sites") + print("\n" + "=" * 60) + print("Step 01 完成") + print("=" * 60) + + return df if __name__ == "__main__": main() - diff --git a/task1/01_clean.xlsx b/task1/01_clean.xlsx index accd722f32798c41bc6d36bf411ef7af314a5fbd..baea299fde22267c952fc131e2a15bbefc0668ac 100644 GIT binary patch literal 10052 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 Kf_qT?b@xB}+~OPn diff --git a/task1/02_demand.xlsx b/task1/02_demand.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4bcc446230d512c3c839c00cdbc955a67b3cb733 GIT binary patch literal 11367 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 H|Ni!0%(5FC literal 0 HcmV?d00001 diff --git a/task1/02_demand_correction.py b/task1/02_demand_correction.py new file mode 100644 index 0000000..e3b15f8 --- /dev/null +++ b/task1/02_demand_correction.py @@ -0,0 +1,140 @@ +""" +Step 02: 截断回归修正 - 估计真实需求 + +输入: 01_clean.xlsx +输出: 02_demand.xlsx + +功能: +1. 识别被容量截断的高需求站点 +2. 使用截断正态模型修正,估计真实需求 μ̃ +3. 输出修正前后的对比 + +核心假设: +- 观测到的 μ_i 是真实需求在容量 C 下的截断观测 +- 真实需求服从正态分布 N(μ̃_i, σ̃_i²) +""" + +import pandas as pd +import numpy as np +from scipy.stats import norm +from pathlib import Path + +# 路径配置 +INPUT_PATH = Path(__file__).parent / "01_clean.xlsx" +OUTPUT_PATH = Path(__file__).parent / "02_demand.xlsx" + +# 模型参数 +C = 400 # 有效容量上限 (基于 μ_max = 396.6) +P_TRUNC_THRESHOLD = 0.02 # 截断概率阈值 (调低以捕获更多潜在截断站点) + + +def truncation_correction(mu: float, sigma: float, C: float = 400) -> tuple: + """ + 截断回归修正 + + Args: + mu: 观测均值 + sigma: 观测标准差 + C: 有效容量上限 + + Returns: + (mu_tilde, p_trunc, is_corrected) + - mu_tilde: 修正后的真实需求估计 + - p_trunc: 截断概率 + - is_corrected: 是否进行了修正 + """ + if sigma <= 0 or np.isnan(sigma): + return mu, 0.0, False + + # 计算截断概率: P(D > C) + z = (C - mu) / sigma + p_trunc = 1 - norm.cdf(z) + + if p_trunc < P_TRUNC_THRESHOLD: + # 截断概率低于阈值,视为未截断 + return mu, p_trunc, False + else: + # 截断修正: 使用 Mills ratio 近似 + # E[D | D > C] = μ + σ * φ(z) / (1 - Φ(z)) + # 修正后: μ̃ ≈ μ * (1 + α * p_trunc) + # 这里使用简化的线性修正 + correction_factor = 1 + 0.4 * p_trunc + mu_tilde = mu * correction_factor + return mu_tilde, p_trunc, True + + +def main(): + print("=" * 60) + print("Step 02: 截断回归修正 - 估计真实需求") + print("=" * 60) + + # 1. 读取清洗后的数据 + print(f"\n[1] 读取输入: {INPUT_PATH}") + df = pd.read_excel(INPUT_PATH) + print(f" 读取 {len(df)} 条记录") + + # 2. 显示参数 + print(f"\n[2] 模型参数:") + print(f" 有效容量上限 C = {C}") + print(f" 截断概率阈值 = {P_TRUNC_THRESHOLD}") + + # 3. 应用截断修正 + print(f"\n[3] 应用截断修正...") + results = [] + for _, row in df.iterrows(): + mu_tilde, p_trunc, is_corrected = truncation_correction( + row['mu'], row['sigma'], C + ) + results.append({ + 'mu_tilde': mu_tilde, + 'p_trunc': p_trunc, + 'is_corrected': is_corrected + }) + + df_result = pd.DataFrame(results) + df['mu_tilde'] = df_result['mu_tilde'] + df['p_trunc'] = df_result['p_trunc'] + df['is_corrected'] = df_result['is_corrected'] + + # 4. 修正统计 + n_corrected = df['is_corrected'].sum() + print(f"\n[4] 修正统计:") + print(f" 被修正的站点数: {n_corrected} / {len(df)}") + + if n_corrected > 0: + corrected_sites = df[df['is_corrected']] + print(f"\n 被修正站点详情:") + print(f" {'site_id':<8} {'site_name':<35} {'μ':>8} {'σ':>8} {'p_trunc':>8} {'μ̃':>10} {'修正幅度':>10}") + print(f" {'-'*8} {'-'*35} {'-'*8} {'-'*8} {'-'*8} {'-'*10} {'-'*10}") + for _, row in corrected_sites.iterrows(): + correction_pct = (row['mu_tilde'] - row['mu']) / row['mu'] * 100 + print(f" {row['site_id']:<8} {row['site_name'][:35]:<35} {row['mu']:>8.1f} {row['sigma']:>8.1f} {row['p_trunc']:>8.3f} {row['mu_tilde']:>10.1f} {correction_pct:>9.1f}%") + + # 5. 修正前后对比 + print(f"\n[5] 修正前后对比:") + print(f" 修正前 μ 总和: {df['mu'].sum():.1f}") + print(f" 修正后 μ̃ 总和: {df['mu_tilde'].sum():.1f}") + print(f" 增幅: {(df['mu_tilde'].sum() / df['mu'].sum() - 1) * 100:.2f}%") + + # 6. 保存输出 + print(f"\n[6] 保存输出: {OUTPUT_PATH}") + # 选择输出列 + output_cols = ['site_id', 'site_name', 'lat', 'lon', 'visits_2019', + 'mu', 'sigma', 'mu_tilde', 'p_trunc', 'is_corrected'] + df[output_cols].to_excel(OUTPUT_PATH, index=False) + print(f" 已保存 {len(df)} 条记录") + + # 7. 输出预览 + print(f"\n[7] 输出数据预览 (μ 最高的10个站点):") + top10 = df.nlargest(10, 'mu')[['site_id', 'site_name', 'mu', 'sigma', 'p_trunc', 'mu_tilde', 'is_corrected']] + print(top10.to_string(index=False)) + + print("\n" + "=" * 60) + print("Step 02 完成") + print("=" * 60) + + return df + + +if __name__ == "__main__": + main() diff --git a/task1/02_neighbor.xlsx b/task1/02_neighbor.xlsx deleted file mode 100644 index 126638e9d03d92e8da205a027185b782a91d35f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73135 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 e 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 float: - r_miles = 3958.7613 - phi1 = math.radians(lat1) - phi2 = math.radians(lat2) - dphi = math.radians(lat2 - lat1) - dlambda = math.radians(lon2 - lon1) - - a = math.sin(dphi / 2.0) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda / 2.0) ** 2 - c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a)) - return r_miles * c - - -def main() -> None: - sites = pd.read_excel(INPUT_XLSX, sheet_name="sites") - - lat = sites["lat"].to_numpy(float) - lon = sites["lon"].to_numpy(float) - mu = sites["mu_clients_per_visit"].to_numpy(float) - - n = len(sites) - dist = np.zeros((n, n), dtype=float) - for i in range(n): - for j in range(i + 1, n): - d = haversine_miles(lat[i], lon[i], lat[j], lon[j]) - dist[i, j] = d - dist[j, i] = d - - out = sites.copy() - out["neighbor_demand_mu_self"] = mu.copy() - for rho in RHO_MILES_LIST: - w = np.exp(-(dist**2) / (2.0 * rho * rho)) - # D_i(rho) = sum_j mu_j * exp(-dist(i,j)^2 / (2 rho^2)) - out[f"neighbor_demand_mu_rho_{int(rho)}mi"] = w @ mu - - dist_df = pd.DataFrame(dist, columns=sites["site_id"].tolist()) - dist_df.insert(0, "site_id", sites["site_id"]) - - with pd.ExcelWriter(OUTPUT_XLSX, engine="openpyxl") as writer: - out.to_excel(writer, index=False, sheet_name="sites") - dist_df.to_excel(writer, index=False, sheet_name="dist_miles") - - -if __name__ == "__main__": - main() - diff --git a/task1/03_allocate.py b/task1/03_allocate.py new file mode 100644 index 0000000..6985460 --- /dev/null +++ b/task1/03_allocate.py @@ -0,0 +1,155 @@ +""" +Step 03: 频次分配 - Hamilton最大余数法 + +输入: 02_demand.xlsx +输出: 03_allocate.xlsx + +功能: +1. 按真实需求 μ̃ 比例分配年度访问次数 k_i +2. 使用 Hamilton 方法保证整数分配且总和 = N +3. 满足覆盖约束: k_i >= 1 + +分配原则: +- 先给每个站点分配1次 (覆盖约束) +- 剩余 N-70 次按 μ̃ 比例分配 +""" + +import pandas as pd +import numpy as np +from pathlib import Path + +# 路径配置 +INPUT_PATH = Path(__file__).parent / "02_demand.xlsx" +OUTPUT_PATH = Path(__file__).parent / "03_allocate.xlsx" + +# 分配参数 +N_TOTAL = 730 # 年度总访问次数 (365天 × 2站点/天) +MIN_VISITS = 1 # 每站点最少访问次数 (覆盖约束) + + +def hamilton_allocation(total: int, weights: list) -> list: + """ + Hamilton最大余数法整数分配 + + Args: + total: 待分配总数 + weights: 各项权重 + + Returns: + 整数分配结果列表 + """ + n = len(weights) + w_sum = sum(weights) + + # 连续配额 + quotas = [total * w / w_sum for w in weights] + + # 下取整 + floors = [int(q) for q in quotas] + remainders = [q - f for q, f in zip(quotas, floors)] + + # 剩余席位按余数从大到小分配 + leftover = total - sum(floors) + indices = sorted(range(n), key=lambda i: -remainders[i]) + for i in indices[:leftover]: + floors[i] += 1 + + return floors + + +def main(): + print("=" * 60) + print("Step 03: 频次分配 - Hamilton最大余数法") + print("=" * 60) + + # 1. 读取需求修正后的数据 + print(f"\n[1] 读取输入: {INPUT_PATH}") + df = pd.read_excel(INPUT_PATH) + print(f" 读取 {len(df)} 条记录") + + n_sites = len(df) + + # 2. 显示参数 + print(f"\n[2] 分配参数:") + print(f" 年度总访问次数 N = {N_TOTAL}") + print(f" 站点数 = {n_sites}") + print(f" 覆盖约束: 每站点至少 {MIN_VISITS} 次") + print(f" 剩余可分配次数 = {N_TOTAL} - {n_sites} × {MIN_VISITS} = {N_TOTAL - n_sites * MIN_VISITS}") + + # 3. Hamilton分配 + print(f"\n[3] 执行Hamilton分配...") + + # 权重 = 修正后的真实需求 μ̃ + weights = df['mu_tilde'].tolist() + + # 分配剩余次数 + extra_visits = N_TOTAL - n_sites * MIN_VISITS + k_extra = hamilton_allocation(extra_visits, weights) + + # 总访问次数 = 基础 + 额外 + df['k'] = [MIN_VISITS + ke for ke in k_extra] + + # 4. 验证分配结果 + print(f"\n[4] 分配结果验证:") + print(f" 总访问次数: Σk_i = {df['k'].sum()} (应为 {N_TOTAL})") + print(f" 最小访问次数: min(k_i) = {df['k'].min()} (应 >= {MIN_VISITS})") + print(f" 最大访问次数: max(k_i) = {df['k'].max()}") + print(f" 访问次数范围: [{df['k'].min()}, {df['k'].max()}]") + + assert df['k'].sum() == N_TOTAL, f"总访问次数不等于{N_TOTAL}" + assert df['k'].min() >= MIN_VISITS, f"存在站点访问次数少于{MIN_VISITS}" + + # 5. 计算满足率 + # r_i = k_i * μ_i / μ̃_i (年度服务量 / 真实需求) + df['annual_service'] = df['k'] * df['mu'] # 年度预期服务量 + df['r'] = df['annual_service'] / df['mu_tilde'] # 满足率代理 + + print(f"\n[5] 满足率统计:") + print(f" 满足率 r = k × μ / μ̃") + print(f" r 均值: {df['r'].mean():.2f}") + print(f" r 标准差: {df['r'].std():.2f}") + print(f" r 范围: [{df['r'].min():.2f}, {df['r'].max():.2f}]") + print(f" r 变异系数: {df['r'].std() / df['r'].mean():.4f}") + + # 6. 分配结果分布 + print(f"\n[6] 访问次数分布:") + k_counts = df['k'].value_counts().sort_index() + for k_val, count in k_counts.items(): + print(f" k = {k_val:2d}: {count:2d} 个站点 {'█' * count}") + + # 7. 与2019年对比 + print(f"\n[7] 与2019年访问次数对比:") + df['k_2019_scaled'] = df['visits_2019'] * N_TOTAL / df['visits_2019'].sum() + df['k_diff'] = df['k'] - df['k_2019_scaled'] + print(f" 2019年总访问次数: {df['visits_2019'].sum()}") + print(f" 2019年缩放后总次数: {df['k_2019_scaled'].sum():.1f}") + print(f" 新方案 vs 2019缩放:") + print(f" - 增加访问的站点: {(df['k_diff'] > 0.5).sum()} 个") + print(f" - 减少访问的站点: {(df['k_diff'] < -0.5).sum()} 个") + print(f" - 基本不变的站点: {((df['k_diff'] >= -0.5) & (df['k_diff'] <= 0.5)).sum()} 个") + + # 8. 保存输出 + print(f"\n[8] 保存输出: {OUTPUT_PATH}") + output_cols = ['site_id', 'site_name', 'lat', 'lon', 'visits_2019', + 'mu', 'sigma', 'mu_tilde', 'k', 'annual_service', 'r'] + df[output_cols].to_excel(OUTPUT_PATH, index=False) + print(f" 已保存 {len(df)} 条记录") + + # 9. 输出预览 + print(f"\n[9] 分配结果预览 (k 最高的10个站点):") + top10 = df.nlargest(10, 'k')[['site_id', 'site_name', 'mu', 'mu_tilde', 'k', 'annual_service', 'r']] + print(top10.to_string(index=False)) + + print(f"\n 分配结果预览 (k 最低的10个站点):") + bottom10 = df.nsmallest(10, 'k')[['site_id', 'site_name', 'mu', 'mu_tilde', 'k', 'annual_service', 'r']] + print(bottom10.to_string(index=False)) + + print("\n" + "=" * 60) + print("Step 03 完成") + print("=" * 60) + + return df + + +if __name__ == "__main__": + main() diff --git a/task1/03_allocate.xlsx b/task1/03_allocate.xlsx index e586260a1def285b5333317eb874534f45199283..5995d4e8085c7088c140357304dc6f6ab7b9c56c 100644 GIT binary patch literal 11526 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 literal 36781 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 float: - x = np.asarray(x, dtype=float) - if np.any(x < 0): - raise ValueError("Gini expects nonnegative values.") - if np.allclose(x.sum(), 0.0): - return 0.0 - n = len(x) - diff_sum = np.abs(x.reshape(-1, 1) - x.reshape(1, -1)).sum() - return diff_sum / (2.0 * n * n * x.mean()) - - -def largest_remainder_allocation(weights: np.ndarray, total: int, k_min: int) -> np.ndarray: - if total < k_min * len(weights): - raise ValueError("total too small for k_min constraint.") - w = np.asarray(weights, dtype=float) - if np.any(w < 0): - raise ValueError("weights must be nonnegative.") - if np.allclose(w.sum(), 0.0): - # Purely equal split - base = np.full(len(w), total // len(w), dtype=int) - base[: total - base.sum()] += 1 - return np.maximum(base, k_min) - - remaining = total - k_min * len(w) - w_norm = w / w.sum() - raw = remaining * w_norm - flo = np.floor(raw).astype(int) - k = flo + k_min - - need = total - k.sum() - if need > 0: - frac = raw - flo - order = np.argsort(-frac, kind="stable") - k[order[:need]] += 1 - elif need < 0: - frac = raw - flo - order = np.argsort(frac, kind="stable") - to_remove = -need - for idx in order: - if to_remove == 0: - break - if k[idx] > k_min: - k[idx] -= 1 - to_remove -= 1 - if k.sum() != total: - raise RuntimeError("Failed to adjust allocation to exact total.") - - if k.sum() != total: - raise RuntimeError("Allocation did not match total.") - if (k < k_min).any(): - raise RuntimeError("Allocation violated k_min.") - return k - - -def feasibility_sum_for_t(t: float, d: np.ndarray, mu: np.ndarray, k_min: int) -> int: - # Constraints: k_i >= max(k_min, ceil(t * D_i / mu_i)) - if t < 0: - return math.inf - req = np.ceil((t * d) / mu).astype(int) - req = np.maximum(req, k_min) - return int(req.sum()) - - -def max_min_service_level(d: np.ndarray, mu: np.ndarray, n_total: int, k_min: int) -> tuple[float, np.ndarray]: - if (mu <= 0).any(): - raise ValueError("mu must be positive to define service level.") - if (d <= 0).any(): - raise ValueError("neighbor demand proxy D must be positive.") - - # Upper bound for t: if all visits assigned to best site, service level there is (n_total*mu_i/d_i). - # For max-min, t cannot exceed min_i (n_total*mu_i/d_i) but k_min can also bind; use conservative. - t_hi = float(np.min((n_total * mu) / d)) - t_lo = 0.0 - - # Binary search for max feasible t - for _ in range(60): - t_mid = (t_lo + t_hi) / 2.0 - s = feasibility_sum_for_t(t_mid, d=d, mu=mu, k_min=k_min) - if s <= n_total: - t_lo = t_mid - else: - t_hi = t_mid - - t_star = t_lo - k_base = np.ceil((t_star * d) / mu).astype(int) - k_base = np.maximum(k_base, k_min) - - if k_base.sum() > n_total: - # Numerical edge case: back off to ensure feasibility - t_star *= 0.999 - k_base = np.ceil((t_star * d) / mu).astype(int) - k_base = np.maximum(k_base, k_min) - if k_base.sum() > n_total: - raise RuntimeError("Failed to construct feasible k at t*.") - - return t_star, k_base - - -def distribute_remaining_fair(k: np.ndarray, mu: np.ndarray, d: np.ndarray, remaining: int) -> np.ndarray: - # Greedy: repeatedly allocate to smallest current s_i = k_i*mu_i/d_i - k_out = k.copy() - for _ in range(remaining): - s = (k_out * mu) / d - idx = int(np.argmin(s)) - k_out[idx] += 1 - return k_out - - -def distribute_remaining_efficient(k: np.ndarray, mu: np.ndarray, remaining: int) -> np.ndarray: - # Greedy: allocate to highest mu_i (maximizes sum k_i*mu_i) - k_out = k.copy() - order = np.argsort(-mu, kind="stable") - for t in range(remaining): - k_out[order[t % len(order)]] += 1 - return k_out - - -@dataclass(frozen=True) -class AllocationResult: - method: str - scenario: str - k: np.ndarray - t_star: float | None - - -def compute_metrics(k: np.ndarray, mu: np.ndarray, d: np.ndarray) -> dict[str, float]: - s = (k * mu) / d - return { - "k_sum": float(k.sum()), - "total_expected_clients": float(np.dot(k, mu)), - "service_level_min": float(s.min()), - "service_level_mean": float(s.mean()), - "service_level_gini": float(gini(s)), - "service_level_cv": float(s.std(ddof=0) / (s.mean() + 1e-12)), - "k_gini": float(gini(k.astype(float))), - } - - -def improve_efficiency_under_gini_cap( - *, - k_start: np.ndarray, - mu: np.ndarray, - d: np.ndarray, - n_total: int, - k_min: int, - gini_cap: float, - max_iters: int = 20000, -) -> np.ndarray: - if k_start.sum() != n_total: - raise ValueError("k_start must sum to n_total.") - if (k_start < k_min).any(): - raise ValueError("k_start violates k_min.") - - k = k_start.copy() - s = (k * mu) / d - g = gini(s) - if g <= gini_cap: - return k - - for _ in range(max_iters): - s = (k * mu) / d - g = gini(s) - if g <= gini_cap: - break - - donor_candidates = np.argsort(-s, kind="stable")[:10] - receiver_candidates = np.argsort(s, kind="stable")[:10] - - best_move = None - best_score = None - - for donor in donor_candidates: - if k[donor] <= k_min: - continue - for receiver in receiver_candidates: - if donor == receiver: - continue - - k2 = k.copy() - k2[donor] -= 1 - k2[receiver] += 1 - s2 = (k2 * mu) / d - g2 = gini(s2) - if g2 >= g: - continue - - eff_loss = mu[donor] - mu[receiver] - if eff_loss < 0: - # This move increases effectiveness and improves fairness; prefer strongly. - score = (g - g2) * 1e9 + (-eff_loss) - else: - score = (g - g2) / (eff_loss + 1e-9) - - if best_score is None or score > best_score: - best_score = score - best_move = (donor, receiver, k2) - - if best_move is None: - # No improving local swap found; stop. - break - k = best_move[2] - - if k.sum() != n_total or (k < k_min).any(): - raise RuntimeError("Post-optimization allocation violated constraints.") - return k - - -def main() -> None: - sites = pd.read_excel(INPUT_XLSX, sheet_name="sites") - mu = sites["mu_clients_per_visit"].to_numpy(float) - visits_2019 = sites["visits_2019"].to_numpy(int) - - results: list[AllocationResult] = [] - metrics_rows: list[dict[str, object]] = [] - - for scenario, dcol in RHO_COLS: - d = sites[dcol].to_numpy(float) - - # Baseline: scaled 2019 to sum to N_TOTAL_2021 - k_2019_scaled = largest_remainder_allocation( - weights=visits_2019.astype(float), total=N_TOTAL_2021, k_min=K_MIN - ) - results.append(AllocationResult(method="baseline_2019_scaled", scenario=scenario, k=k_2019_scaled, t_star=None)) - - # Max-min baseline k achieving best possible min service level under sum constraint. - t_star, k_base = max_min_service_level(d=d, mu=mu, n_total=N_TOTAL_2021, k_min=K_MIN) - remaining = N_TOTAL_2021 - int(k_base.sum()) - - k_fair = distribute_remaining_fair(k_base, mu=mu, d=d, remaining=remaining) - results.append(AllocationResult(method="fairness_waterfill", scenario=scenario, k=k_fair, t_star=t_star)) - - k_eff = distribute_remaining_efficient(k_base, mu=mu, remaining=remaining) - results.append(AllocationResult(method="efficiency_post_fair", scenario=scenario, k=k_eff, t_star=t_star)) - - # Proportional to surrounding demand proxy D (uses "surrounding communities demand" directly) - k_D = largest_remainder_allocation(weights=d, total=N_TOTAL_2021, k_min=K_MIN) - results.append(AllocationResult(method="proportional_D", scenario=scenario, k=k_D, t_star=None)) - - # Simple proportional fairness: k proportional to D/mu gives near-constant s in continuous relaxation. - k_prop = largest_remainder_allocation(weights=d / mu, total=N_TOTAL_2021, k_min=K_MIN) - results.append(AllocationResult(method="proportional_D_over_mu", scenario=scenario, k=k_prop, t_star=None)) - - # Pure efficiency: k proportional to mu (with k_min) - k_mu = largest_remainder_allocation(weights=mu, total=N_TOTAL_2021, k_min=K_MIN) - results.append(AllocationResult(method="proportional_mu", scenario=scenario, k=k_mu, t_star=None)) - - # Efficiency-maximizing subject to "no worse fairness than baseline_2019_scaled" (auditable cap) - gini_cap = compute_metrics(k_2019_scaled, mu=mu, d=d)["service_level_gini"] - k_mu_capped = improve_efficiency_under_gini_cap( - k_start=k_mu, mu=mu, d=d, n_total=N_TOTAL_2021, k_min=K_MIN, gini_cap=float(gini_cap) - ) - results.append(AllocationResult(method="proportional_mu_gini_capped_to_2019", scenario=scenario, k=k_mu_capped, t_star=None)) - - # Assemble per-site output - out_sites = sites[["site_id", "site_name", "lat", "lon", "visits_2019", "mu_clients_per_visit", "sd_clients_per_visit"]].copy() - - for res in results: - col_k = f"k_{res.method}_{res.scenario}" - out_sites[col_k] = res.k - - d = sites[[c for s, c in RHO_COLS if s == res.scenario][0]].to_numpy(float) - s_vals = (res.k * mu) / d - out_sites[f"s_{res.method}_{res.scenario}"] = s_vals - - m = compute_metrics(res.k, mu=mu, d=d) - row = {"scenario": res.scenario, "method": res.method, "t_star": res.t_star} - row.update(m) - metrics_rows.append(row) - - metrics_df = pd.DataFrame(metrics_rows).sort_values(["scenario", "method"]).reset_index(drop=True) - - with pd.ExcelWriter(OUTPUT_XLSX, engine="openpyxl") as writer: - out_sites.to_excel(writer, index=False, sheet_name="allocations") - metrics_df.to_excel(writer, index=False, sheet_name="metrics") - - -if __name__ == "__main__": - main() diff --git a/task1/04_evaluate.py b/task1/04_evaluate.py new file mode 100644 index 0000000..3fa6334 --- /dev/null +++ b/task1/04_evaluate.py @@ -0,0 +1,233 @@ +""" +Step 04: 评估指标计算 + +输入: 03_allocate.xlsx +输出: 04_metrics.xlsx + +功能: +1. 计算有效性指标 (E1, E2) +2. 计算公平性指标 (F1, F2, F3) +3. 与基线方案对比 +4. 输出综合评估报告 + +指标定义: +- E1: 原始总服务量 = Σ k_i × μ_i +- E2: 质量加权服务量 = Σ k_i × μ_i × q(μ_i), q(μ) = min(1, 250/μ) +- F1: 满足率基尼系数 Gini(r) +- F2: 最差站点满足率 min(r) +- F3: 满足率变异系数 CV(r) +""" + +import pandas as pd +import numpy as np +from pathlib import Path + +# 路径配置 +INPUT_PATH = Path(__file__).parent / "03_allocate.xlsx" +OUTPUT_PATH = Path(__file__).parent / "04_metrics.xlsx" + +# 评估参数 +C_BAR = 250 # 典型服务量 (质量折扣阈值) + + +def quality_discount(mu: float, c_bar: float = C_BAR) -> float: + """ + 质量折扣因子: 当 μ > c_bar 时,服务质量打折 + + q(μ) = min(1, c_bar / μ) + """ + return min(1.0, c_bar / mu) if mu > 0 else 1.0 + + +def gini_coefficient(values: list) -> float: + """ + 计算基尼系数 + + Gini = Σ_i Σ_j |x_i - x_j| / (2n Σ_i x_i) + """ + values = np.array(values) + n = len(values) + if n == 0 or values.sum() == 0: + return 0.0 + + # 排序后计算 + sorted_values = np.sort(values) + cumsum = np.cumsum(sorted_values) + return (2 * np.sum((np.arange(1, n + 1) * sorted_values)) - (n + 1) * cumsum[-1]) / (n * cumsum[-1]) + + +def compute_metrics(df: pd.DataFrame, method_name: str) -> dict: + """计算单个方案的所有指标""" + # 有效性指标 + E1 = (df['k'] * df['mu']).sum() + E2 = (df['k'] * df['mu'] * df['mu'].apply(quality_discount)).sum() + + # 满足率 + r = df['k'] * df['mu'] / df['mu_tilde'] + + # 公平性指标 + F1 = gini_coefficient(r.tolist()) + F2 = r.min() + F3 = r.std() / r.mean() + + return { + 'method': method_name, + 'E1_total_service': E1, + 'E2_quality_weighted': E2, + 'F1_gini': F1, + 'F2_min_satisfaction': F2, + 'F3_cv_satisfaction': F3 + } + + +def compute_baseline_uniform(df: pd.DataFrame, n_total: int = 730) -> pd.DataFrame: + """基线1: 均匀分配""" + df_baseline = df.copy() + k_uniform = n_total // len(df) + remainder = n_total - k_uniform * len(df) + df_baseline['k'] = k_uniform + # 将余数分配给前 remainder 个站点 + df_baseline.iloc[:remainder, df_baseline.columns.get_loc('k')] += 1 + return df_baseline + + +def compute_baseline_2019_scaled(df: pd.DataFrame, n_total: int = 730) -> pd.DataFrame: + """基线2: 2019年访问次数等比例缩放""" + df_baseline = df.copy() + total_2019 = df['visits_2019'].sum() + # 按比例缩放 + k_float = df['visits_2019'] * n_total / total_2019 + k_floor = k_float.astype(int) + remainder = n_total - k_floor.sum() + # 按余数分配 + remainders = k_float - k_floor + indices = remainders.nlargest(remainder).index + k_floor.loc[indices] += 1 + df_baseline['k'] = k_floor + return df_baseline + + +def compute_baseline_mu_raw(df: pd.DataFrame, n_total: int = 730) -> pd.DataFrame: + """基线3: 按原始μ比例分配 (无截断修正)""" + df_baseline = df.copy() + # 覆盖约束 + k_base = 1 + extra = n_total - len(df) * k_base + # 按μ比例分配额外次数 + weights = df['mu'].tolist() + w_sum = sum(weights) + quotas = [extra * w / w_sum for w in weights] + floors = [int(q) for q in quotas] + remainders = [q - f for q, f in zip(quotas, floors)] + leftover = extra - sum(floors) + indices = sorted(range(len(weights)), key=lambda i: -remainders[i]) + for i in indices[:leftover]: + floors[i] += 1 + df_baseline['k'] = [k_base + f for f in floors] + return df_baseline + + +def main(): + print("=" * 60) + print("Step 04: 评估指标计算") + print("=" * 60) + + # 1. 读取分配结果 + print(f"\n[1] 读取输入: {INPUT_PATH}") + df = pd.read_excel(INPUT_PATH) + print(f" 读取 {len(df)} 条记录") + + # 2. 计算推荐方案指标 + print(f"\n[2] 计算推荐方案指标 (按μ̃比例分配)") + metrics_recommended = compute_metrics(df, 'Recommended (μ̃ proportional)') + for key, value in metrics_recommended.items(): + if key != 'method': + print(f" {key}: {value:.4f}") + + # 3. 计算基线方案指标 + print(f"\n[3] 计算基线方案指标") + + # 基线1: 均匀分配 + df_b1 = compute_baseline_uniform(df) + metrics_b1 = compute_metrics(df_b1, 'Baseline 1: Uniform') + print(f"\n 基线1 (均匀分配, k={df_b1['k'].iloc[0]}~{df_b1['k'].max()}):") + for key, value in metrics_b1.items(): + if key != 'method': + print(f" {key}: {value:.4f}") + + # 基线2: 2019年缩放 + df_b2 = compute_baseline_2019_scaled(df) + metrics_b2 = compute_metrics(df_b2, 'Baseline 2: 2019 Scaled') + print(f"\n 基线2 (2019年缩放):") + for key, value in metrics_b2.items(): + if key != 'method': + print(f" {key}: {value:.4f}") + + # 基线3: 按原始μ比例 + df_b3 = compute_baseline_mu_raw(df) + metrics_b3 = compute_metrics(df_b3, 'Baseline 3: μ proportional (no correction)') + print(f"\n 基线3 (按原始μ比例, 无截断修正):") + for key, value in metrics_b3.items(): + if key != 'method': + print(f" {key}: {value:.4f}") + + # 4. 汇总对比 + print(f"\n[4] 方案对比汇总") + all_metrics = [metrics_recommended, metrics_b1, metrics_b2, metrics_b3] + df_metrics = pd.DataFrame(all_metrics) + + # 计算相对于推荐方案的变化 + for col in ['E1_total_service', 'E2_quality_weighted']: + base_val = metrics_recommended[col] + df_metrics[f'{col}_pct'] = (df_metrics[col] / base_val - 1) * 100 + + print("\n " + "-" * 90) + print(f" {'方案':<45} {'E1':>12} {'E2':>12} {'F1(Gini)':>10} {'F2(min r)':>10} {'F3(CV)':>10}") + print(" " + "-" * 90) + for _, row in df_metrics.iterrows(): + print(f" {row['method']:<45} {row['E1_total_service']:>12.1f} {row['E2_quality_weighted']:>12.1f} {row['F1_gini']:>10.4f} {row['F2_min_satisfaction']:>10.2f} {row['F3_cv_satisfaction']:>10.4f}") + print(" " + "-" * 90) + + # 5. 推荐方案优势分析 + print(f"\n[5] 推荐方案优势分析") + print(f" 相对于均匀分配 (基线1):") + print(f" - E1 提升: {(metrics_recommended['E1_total_service']/metrics_b1['E1_total_service']-1)*100:+.2f}%") + print(f" - E2 提升: {(metrics_recommended['E2_quality_weighted']/metrics_b1['E2_quality_weighted']-1)*100:+.2f}%") + + print(f"\n 相对于2019缩放 (基线2):") + print(f" - E1 变化: {(metrics_recommended['E1_total_service']/metrics_b2['E1_total_service']-1)*100:+.2f}%") + print(f" - E2 变化: {(metrics_recommended['E2_quality_weighted']/metrics_b2['E2_quality_weighted']-1)*100:+.2f}%") + print(f" - F1(Gini)变化: {(metrics_recommended['F1_gini']-metrics_b2['F1_gini']):+.4f}") + + # 6. 保存输出 + print(f"\n[6] 保存输出: {OUTPUT_PATH}") + + with pd.ExcelWriter(OUTPUT_PATH, engine='openpyxl') as writer: + # Sheet 1: 指标汇总 + df_metrics.to_excel(writer, sheet_name='metrics_summary', index=False) + + # Sheet 2: 站点级详情 (推荐方案) + df_detail = df[['site_id', 'site_name', 'mu', 'mu_tilde', 'k', 'annual_service', 'r']].copy() + df_detail['quality_factor'] = df['mu'].apply(quality_discount) + df_detail['service_quality_weighted'] = df_detail['k'] * df_detail['mu'] * df_detail['quality_factor'] + df_detail.to_excel(writer, sheet_name='site_details', index=False) + + # Sheet 3: 参数记录 + params = pd.DataFrame([ + {'parameter': 'C_BAR (quality threshold)', 'value': C_BAR}, + {'parameter': 'N_TOTAL', 'value': 730}, + {'parameter': 'n_sites', 'value': len(df)}, + ]) + params.to_excel(writer, sheet_name='parameters', index=False) + + print(f" 已保存3个工作表: metrics_summary, site_details, parameters") + + print("\n" + "=" * 60) + print("Step 04 完成") + print("=" * 60) + + return df_metrics + + +if __name__ == "__main__": + main() diff --git a/task1/04_metrics.xlsx b/task1/04_metrics.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..dbedeeb659221fe8f3e2661d9a4ce76b6aec5351 GIT binary patch literal 10762 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 list[Event]: - if k <= 0: - return [] - targets: list[Event] = [] - for j in range(k): - # Even spacing: place j-th visit at (j+0.5)*DAYS/k - t = int(round((j + 0.5) * DAYS / k)) - t = max(1, min(DAYS, t)) - targets.append(Event(site_id=site_id, site_name=site_name, target_day=t)) - return targets - - -def assign_events_to_days(events: list[Event]) -> dict[int, list[Event]]: - # Initial binning by rounded target day - day_to_events: dict[int, list[Event]] = {d: [] for d in range(1, DAYS + 1)} - overflow: list[Event] = [] - - # Put into bins - for ev in sorted(events, key=lambda e: (e.target_day, e.site_id)): - day_to_events[ev.target_day].append(ev) - - # Enforce per-day capacity and per-day unique site_id - for day in range(1, DAYS + 1): - bucket = day_to_events[day] - if not bucket: - continue - seen: set[int] = set() - kept: list[Event] = [] - for ev in bucket: - if ev.site_id in seen: - overflow.append(ev) - else: - seen.add(ev.site_id) - kept.append(ev) - # If still over capacity, keep earliest (already sorted) and overflow rest - day_to_events[day] = kept[:SLOTS_PER_DAY] - overflow.extend(kept[SLOTS_PER_DAY:]) - - # Underfull days list - underfull_days: list[int] = [] - for day in range(1, DAYS + 1): - cap = SLOTS_PER_DAY - len(day_to_events[day]) - underfull_days.extend([day] * cap) - underfull_days.sort() - - # Fill underfull days with closest assignment to target_day - def day_has_site(day: int, site_id: int) -> bool: - return any(ev.site_id == site_id for ev in day_to_events[day]) - - for ev in sorted(overflow, key=lambda e: (e.target_day, e.site_id)): - if not underfull_days: - raise RuntimeError("No remaining capacity but overflow events remain.") - pos = bisect.bisect_left(underfull_days, ev.target_day) - candidate_positions = [] - for delta in range(0, len(underfull_days)): - # Check outward from the insertion point - for p in (pos - delta, pos + delta): - if 0 <= p < len(underfull_days): - candidate_positions.append(p) - if candidate_positions: - # We gathered some; break after first ring to keep cost small - break - - assigned_idx = None - for p in candidate_positions: - day = underfull_days[p] - if not day_has_site(day, ev.site_id): - assigned_idx = p - break - - if assigned_idx is None: - # Fallback: scan until we find any feasible slot - for p, day in enumerate(underfull_days): - if not day_has_site(day, ev.site_id): - assigned_idx = p - break - - if assigned_idx is None: - raise RuntimeError(f"Unable to place event for site_id={ev.site_id}; per-day uniqueness too strict.") - - day = underfull_days.pop(assigned_idx) - day_to_events[day].append(ev) - - # Final sanity: every day filled, and no day has duplicate site_id - for day in range(1, DAYS + 1): - if len(day_to_events[day]) != SLOTS_PER_DAY: - raise RuntimeError(f"Day {day} not filled: {len(day_to_events[day])} events.") - ids = [e.site_id for e in day_to_events[day]] - if len(set(ids)) != len(ids): - raise RuntimeError(f"Day {day} has duplicate site assignments.") - return day_to_events - - -def main() -> None: - alloc = pd.read_excel(ALLOC_XLSX, sheet_name="allocations") - - k_col = f"k_{DEFAULT_METHOD}_{DEFAULT_SCENARIO}" - if k_col not in alloc.columns: - raise ValueError(f"Allocation column not found: {k_col}") - - alloc = alloc[["site_id", "site_name", k_col]].copy() - alloc = alloc.rename(columns={k_col: "k_2021"}) - alloc["k_2021"] = pd.to_numeric(alloc["k_2021"], errors="raise").astype(int) - - if int(alloc["k_2021"].sum()) != DAYS * SLOTS_PER_DAY: - raise ValueError("k_2021 does not match total required slots.") - if (alloc["k_2021"] < 1).any(): - raise ValueError("k_2021 violates coverage constraint k_i >= 1.") - - events: list[Event] = [] - for row in alloc.itertuples(index=False): - events.extend(build_targets(site_id=int(row.site_id), site_name=str(row.site_name), k=int(row.k_2021))) - - if len(events) != DAYS * SLOTS_PER_DAY: - raise RuntimeError("Generated events mismatch total required slots.") - - day_to_events = assign_events_to_days(events) - - start = dt.date(YEAR, 1, 1) - calendar_rows: list[dict[str, object]] = [] - per_site_rows: list[dict[str, object]] = [] - - for day in range(1, DAYS + 1): - date = start + dt.timedelta(days=day - 1) - evs = sorted(day_to_events[day], key=lambda e: e.site_id) - calendar_rows.append( - { - "date": date.isoformat(), - "day_of_year": day, - "site1_id": evs[0].site_id, - "site1_name": evs[0].site_name, - "site2_id": evs[1].site_id, - "site2_name": evs[1].site_name, - } - ) - for slot, ev in enumerate(evs, start=1): - per_site_rows.append( - { - "site_id": ev.site_id, - "site_name": ev.site_name, - "date": date.isoformat(), - "day_of_year": day, - "slot": slot, - "target_day": ev.target_day, - } - ) - - calendar_df = pd.DataFrame(calendar_rows) - site_dates_df = pd.DataFrame(per_site_rows).sort_values(["site_id", "day_of_year"]).reset_index(drop=True) - - # Schedule quality metrics: gaps between visits for each site - gap_rows: list[dict[str, object]] = [] - for site_id, group in site_dates_df.groupby("site_id"): - days = group["day_of_year"].to_numpy(int) - gaps = np.diff(days) - if len(gaps) == 0: - gap_rows.append({"site_id": int(site_id), "k": 1, "gap_max": None, "gap_mean": None, "gap_std": None}) - else: - gap_rows.append( - { - "site_id": int(site_id), - "k": int(len(days)), - "gap_max": int(gaps.max()), - "gap_mean": float(gaps.mean()), - "gap_std": float(gaps.std(ddof=0)), - } - ) - gap_df = pd.DataFrame(gap_rows).merge(alloc[["site_id", "site_name"]], on="site_id", how="left") - - meta_df = pd.DataFrame( - [ - {"key": "year", "value": YEAR}, - {"key": "days", "value": DAYS}, - {"key": "slots_per_day", "value": SLOTS_PER_DAY}, - {"key": "total_visits", "value": int(DAYS * SLOTS_PER_DAY)}, - {"key": "allocation_scenario", "value": DEFAULT_SCENARIO}, - {"key": "allocation_method", "value": DEFAULT_METHOD}, - {"key": "k_column", "value": k_col}, - ] - ) - - with pd.ExcelWriter(OUTPUT_XLSX, engine="openpyxl") as writer: - meta_df.to_excel(writer, index=False, sheet_name="meta") - calendar_df.to_excel(writer, index=False, sheet_name="calendar") - site_dates_df.to_excel(writer, index=False, sheet_name="site_dates") - gap_df.to_excel(writer, index=False, sheet_name="gap_metrics") - - -if __name__ == "__main__": - main() diff --git a/task1/05_schedule.py b/task1/05_schedule.py new file mode 100644 index 0000000..3674378 --- /dev/null +++ b/task1/05_schedule.py @@ -0,0 +1,303 @@ +""" +Step 05: 日历排程 - 贪心装箱算法 + +输入: 03_allocate.xlsx +输出: 05_schedule.xlsx + +功能: +1. 将年度频次 k_i 转化为具体日期 +2. 保证每天恰好2个站点 +3. 优化访问间隔的均匀性 +4. 输出完整的365天日历 + +约束: +- 每天恰好2个站点 +- 每站点出现次数 = k_i +- 同一站点相邻访问间隔尽量均匀 +""" + +import pandas as pd +import numpy as np +from pathlib import Path +from collections import defaultdict +import random + +# 路径配置 +INPUT_PATH = Path(__file__).parent / "03_allocate.xlsx" +OUTPUT_PATH = Path(__file__).parent / "05_schedule.xlsx" + +# 排程参数 +T = 365 # 全年天数 +CAPACITY = 2 # 每天站点数 +RANDOM_SEED = 42 # 随机种子 (用于局部优化) + + +def generate_ideal_dates(k: int, T: int = 365) -> list: + """ + 生成站点的理想访问日期 + + 将 k 次访问均匀分布在 [1, T] 内 + t_j = round((j + 0.5) * T / k), j = 0, 1, ..., k-1 + """ + dates = [] + for j in range(k): + ideal_day = round((j + 0.5) * T / k) + ideal_day = max(1, min(T, ideal_day)) + dates.append(ideal_day) + return dates + + +def greedy_schedule(site_visits: dict, T: int = 365, capacity: int = 2) -> dict: + """ + 贪心装箱算法 + + Args: + site_visits: {site_id: k} 各站点的年度访问次数 + T: 全年天数 + capacity: 每天站点容量 + + Returns: + calendar: {day: [site_id, ...]} 日历排程 + """ + # 生成所有访问事件: (理想日期, 站点ID) + events = [] + for site_id, k in site_visits.items(): + ideal_dates = generate_ideal_dates(k, T) + for ideal_day in ideal_dates: + events.append((ideal_day, site_id)) + + # 按理想日期排序 + events.sort(key=lambda x: (x[0], x[1])) + + # 初始化日历 + calendar = {day: [] for day in range(1, T + 1)} + + # 贪心分配 + for ideal_day, site_id in events: + assigned = False + # 从理想日期向两侧搜索可用槽位 + for offset in range(T): + for day in [ideal_day + offset, ideal_day - offset]: + if 1 <= day <= T: + # 检查容量和重复 + if len(calendar[day]) < capacity and site_id not in calendar[day]: + calendar[day].append(site_id) + assigned = True + break + if assigned: + break + + if not assigned: + print(f"警告: 无法分配站点 {site_id} (理想日期 {ideal_day})") + + return calendar + + +def compute_gap_stats(calendar: dict, site_id: int) -> dict: + """计算单个站点的访问间隔统计""" + days = sorted([day for day, sites in calendar.items() if site_id in sites]) + + if len(days) < 2: + return { + 'n_visits': len(days), + 'gaps': [], + 'gap_mean': None, + 'gap_std': None, + 'gap_min': None, + 'gap_max': None, + 'gap_cv': None + } + + gaps = [days[i + 1] - days[i] for i in range(len(days) - 1)] + + return { + 'n_visits': len(days), + 'gaps': gaps, + 'gap_mean': np.mean(gaps), + 'gap_std': np.std(gaps), + 'gap_min': min(gaps), + 'gap_max': max(gaps), + 'gap_cv': np.std(gaps) / np.mean(gaps) if np.mean(gaps) > 0 else 0 + } + + +def local_optimization(calendar: dict, site_ids: list, max_iter: int = 5000, seed: int = 42) -> dict: + """ + 局部搜索优化间隔均匀性 + + 通过随机交换两天的站点,若改善总间隔方差则接受 + """ + random.seed(seed) + calendar = {day: list(sites) for day, sites in calendar.items()} # 深拷贝 + + def total_gap_variance(): + """计算所有站点间隔方差之和""" + total_var = 0 + for site_id in site_ids: + stats = compute_gap_stats(calendar, site_id) + if stats['gap_std'] is not None: + total_var += stats['gap_std'] ** 2 + return total_var + + current_var = total_gap_variance() + improved = 0 + + for iteration in range(max_iter): + # 随机选两天 + t1, t2 = random.sample(range(1, 366), 2) + + if len(calendar[t1]) == 2 and len(calendar[t2]) == 2: + # 随机选择交换位置 + pos1, pos2 = random.randint(0, 1), random.randint(0, 1) + s1, s2 = calendar[t1][pos1], calendar[t2][pos2] + + # 检查交换可行性 (不能产生重复) + if s1 != s2: + other1 = calendar[t1][1 - pos1] + other2 = calendar[t2][1 - pos2] + if s2 != other1 and s1 != other2: + # 尝试交换 + calendar[t1][pos1], calendar[t2][pos2] = s2, s1 + + new_var = total_gap_variance() + + if new_var < current_var: + current_var = new_var + improved += 1 + else: + # 撤销 + calendar[t1][pos1], calendar[t2][pos2] = s1, s2 + + return calendar, improved + + +def main(): + print("=" * 60) + print("Step 05: 日历排程 - 贪心装箱算法") + print("=" * 60) + + # 1. 读取分配结果 + print(f"\n[1] 读取输入: {INPUT_PATH}") + df = pd.read_excel(INPUT_PATH) + print(f" 读取 {len(df)} 条记录") + + # 构建 site_visits 字典 + site_visits = dict(zip(df['site_id'], df['k'])) + total_visits = sum(site_visits.values()) + print(f" 总访问次数: {total_visits}") + print(f" 期望日历天数: {total_visits // CAPACITY} 天") + + # 2. 执行贪心排程 + print(f"\n[2] 执行贪心装箱排程...") + calendar = greedy_schedule(site_visits, T, CAPACITY) + + # 验证 + total_assigned = sum(len(sites) for sites in calendar.values()) + print(f" 已分配访问事件: {total_assigned} / {total_visits}") + + empty_days = sum(1 for sites in calendar.values() if len(sites) == 0) + partial_days = sum(1 for sites in calendar.values() if len(sites) == 1) + full_days = sum(1 for sites in calendar.values() if len(sites) == 2) + print(f" 日历统计: {full_days} 满载 + {partial_days} 部分 + {empty_days} 空闲") + + # 3. 局部优化 + print(f"\n[3] 局部优化 (改善间隔均匀性)...") + site_ids = list(site_visits.keys()) + calendar_opt, n_improved = local_optimization(calendar, site_ids, max_iter=5000, seed=RANDOM_SEED) + print(f" 优化迭代: 5000 次") + print(f" 接受的改进: {n_improved} 次") + + # 4. 间隔统计 + print(f"\n[4] 访问间隔统计") + gap_stats_list = [] + for site_id in site_ids: + stats = compute_gap_stats(calendar_opt, site_id) + stats['site_id'] = site_id + gap_stats_list.append(stats) + + df_gaps = pd.DataFrame(gap_stats_list) + df_gaps = df_gaps.merge(df[['site_id', 'site_name', 'k']], on='site_id') + + # 全局统计 + valid_gaps = df_gaps[df_gaps['gap_mean'].notna()] + print(f" 平均间隔均值: {valid_gaps['gap_mean'].mean():.2f} 天") + print(f" 平均间隔标准差: {valid_gaps['gap_std'].mean():.2f} 天") + print(f" 最大单次间隔: {valid_gaps['gap_max'].max():.0f} 天") + print(f" 平均间隔CV: {valid_gaps['gap_cv'].mean():.4f}") + + # 5. 生成日历输出 + print(f"\n[5] 生成日历输出...") + + # 日历表: date, site_1, site_2 + calendar_rows = [] + for day in range(1, T + 1): + sites = calendar_opt.get(day, []) + site_1 = sites[0] if len(sites) > 0 else None + site_2 = sites[1] if len(sites) > 1 else None + calendar_rows.append({ + 'day': day, + 'site_1_id': site_1, + 'site_2_id': site_2 + }) + df_calendar = pd.DataFrame(calendar_rows) + + # 添加站点名称 + site_name_map = dict(zip(df['site_id'], df['site_name'])) + df_calendar['site_1_name'] = df_calendar['site_1_id'].map(site_name_map) + df_calendar['site_2_name'] = df_calendar['site_2_id'].map(site_name_map) + + # 6. 站点日期列表 + site_dates = [] + for site_id in site_ids: + days = sorted([day for day, sites in calendar_opt.items() if site_id in sites]) + site_dates.append({ + 'site_id': site_id, + 'site_name': site_name_map[site_id], + 'k': len(days), + 'dates': ','.join(map(str, days)) + }) + df_site_dates = pd.DataFrame(site_dates) + + # 7. 保存输出 + print(f"\n[6] 保存输出: {OUTPUT_PATH}") + + with pd.ExcelWriter(OUTPUT_PATH, engine='openpyxl') as writer: + # Sheet 1: 日历 (365天) + df_calendar.to_excel(writer, sheet_name='calendar', index=False) + + # Sheet 2: 站点日期列表 + df_site_dates.to_excel(writer, sheet_name='site_dates', index=False) + + # Sheet 3: 间隔统计 + df_gaps_out = df_gaps[['site_id', 'site_name', 'k', 'n_visits', 'gap_mean', 'gap_std', 'gap_min', 'gap_max', 'gap_cv']] + df_gaps_out.to_excel(writer, sheet_name='gap_statistics', index=False) + + # Sheet 4: 排程参数 + params = pd.DataFrame([ + {'parameter': 'T (days)', 'value': T}, + {'parameter': 'CAPACITY (sites/day)', 'value': CAPACITY}, + {'parameter': 'total_visits', 'value': total_visits}, + {'parameter': 'optimization_iterations', 'value': 5000}, + {'parameter': 'improvements_accepted', 'value': n_improved}, + ]) + params.to_excel(writer, sheet_name='parameters', index=False) + + print(f" 已保存4个工作表: calendar, site_dates, gap_statistics, parameters") + + # 8. 输出预览 + print(f"\n[7] 日历预览 (前10天):") + print(df_calendar.head(10).to_string(index=False)) + + print(f"\n 间隔最大的5个站点:") + top5_gap = df_gaps.nlargest(5, 'gap_max')[['site_id', 'site_name', 'k', 'gap_mean', 'gap_max', 'gap_cv']] + print(top5_gap.to_string(index=False)) + + print("\n" + "=" * 60) + print("Step 05 完成") + print("=" * 60) + + return df_calendar, df_gaps + + +if __name__ == "__main__": + main() diff --git a/task1/05_schedule.xlsx b/task1/05_schedule.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2649c8a5a79e80467a41a311055b1892b681e8a3 GIT binary patch literal 26042 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%0D^1ayIeJoiruO_-)gkSWecD?z) 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 1` -- 总资源约束(情景 B):`Σ_i k_i = N_total = 730`(= 365 天 × 2 站点/天) +1. **截断回归修正**:识别到9个站点历史服务量 $\mu_i > 250$(最高396.6),说明高需求站点的观测数据被容量截断,采用截断正态模型恢复真实需求 $\tilde{\mu}_i$ +2. **质量加权有效性**:考虑高服务量下每户分配食物减少的质量折扣 +3. **满足率公平性**:以"年度服务量/真实需求"的均等程度衡量公平,而非简单的访问次数均等 -## 2. “周边社区总需求”的可审计代理 -题面要求“频次由周边社区总需求指导”,但数据没有社区人口/贫困率等外部字段,因此我们将其定义为**可审计的空间平滑代理**。 +方案遵循四阶段结构:需求估计 → 频次分配 → 效果评估 → 日历排程。 -### 2.1 距离 -使用 haversine 距离 `dist(i,j)`(英里)。 +--- -### 2.2 高斯核平滑(核心) -给定尺度参数 `ρ`(英里),定义站点 i 的邻域需求代理: +## 整体流程图 -`D_i(ρ) = Σ_j μ_j · exp( - dist(i,j)^2 / (2ρ^2) )` +``` +┌─────────────────────────────────────────────────────────────────────────────────────────┐ +│ 数据输入层 │ +│ │ +│ ┌─────────────┐ │ +│ │ data.xlsx │ 70站点: 位置、2019访问次数、μ、σ │ +│ └──────┬──────┘ │ +└───────────┼─────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────────────────┐ +│ TASK 1: 基础排程 [已完成 ✓] │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 01_clean.py │────▶│02_demand_ │────▶│03_allocate.py│────▶│04_evaluate.py│ │ +│ │ │ │correction.py │ │ │ │ │ │ +│ │ 数据清洗 │ │ 截断回归修正 │ │ Hamilton分配 │ │ 指标计算 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ └──────┬───────┘ │ +│ │ │ │ │ │ +│ ▼ ▼ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │01_clean.xlsx │ │02_demand.xlsx│ │03_allocate. │ │04_metrics. │ │ +│ │ │ │ μ̃ (5站点修正)│ │xlsx (k分配) │ │xlsx │ │ +│ └──────────────┘ └──────────────┘ └──────┬───────┘ └──────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │05_schedule.py│ │ +│ │ 日历排程生成 │ │ +│ └──────┬───────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │05_schedule. │ │ +│ │xlsx (365天) │ │ +│ └──────┬───────┘ │ +└─────────────────────────────────────────────────────┼───────────────────────────────────┘ + │ + ┌─────────────────────────────────────────┴─────────────────────────────────┐ + │ │ + ▼ ▼ +┌───────────────────────────────────────────┐ ┌───────────────────────────────────────────┐ +│ TASK 2: 天气响应调度 [待完成] │ │ TASK 3: 双站点同车 [待完成] │ +│ │ │ │ +│ 选项 2a: 减少站点数 + 优化位置 │ │ ┌─────────────────────────────────────┐ │ +│ ┌─────────────────────────────────────┐ │ │ │ 1. 共生站点筛选 │ │ +│ │ • 站点聚类 (K-means/层次聚类) │ │ │ │ • 距离约束: dist(i,j) < D_max │ │ +│ │ • 确定最优站点数 K < 70 │ │ │ │ • 需求约束: μ_i + μ_j ≤ 400 │ │ +│ │ • 客户愿意多走的距离建模 │ │ │ │ • 稳定性约束: CV_i, CV_j < 阈值 │ │ +│ │ • 重新分配频次 │ │ │ └─────────────────────────────────────┘ │ +│ └─────────────────────────────────────┘ │ │ │ │ +│ 或 │ │ ▼ │ +│ 选项 2b: 保持70站点 + 优化时间协调 │ │ ┌─────────────────────────────────────┐ │ +│ ┌─────────────────────────────────────┐ │ │ │ 2. 第一站点分配模型 │ │ +│ │ • 相邻站点访问时间协调 │ │ │ │ • 两阶段随机规划 │ │ +│ │ • 天气预测 → 需求预测 │ │ │ │ • q_i* = argmax E[服务量-缺货惩罚]│ │ +│ │ • 动态调度规则 │ │ │ └─────────────────────────────────────┘ │ +│ └─────────────────────────────────────┘ │ │ │ │ +│ │ │ ▼ │ +│ 输入: │ │ ┌─────────────────────────────────────┐ │ +│ • Task 1 排程结果 │ │ │ 3. 频次重分配 │ │ +│ • 历史天气数据 (如有) │ │ │ • 共生站点合并需求 │ │ +│ • 站点间距离矩阵 │ │ │ • Hamilton方法重新分配 │ │ +│ │ │ └─────────────────────────────────────┘ │ +│ 输出: │ │ │ │ +│ • 改进后的排程方案 │ │ ▼ │ +│ • 性能提升量化 │ │ ┌─────────────────────────────────────┐ │ +│ │ │ │ 4. 日历排程 + 对比评估 │ │ +└───────────────────────────────────────────┘ │ │ • 与 Task 1 对比 E1, E2, F1, F2 │ │ + │ └─────────────────────────────────────┘ │ + │ │ + │ 输入: │ + │ • Task 1 分配结果 (03_allocate.xlsx) │ + │ • 站点距离矩阵 │ + │ │ + │ 输出: │ + │ • 共生站点配对表 │ + │ • 第一站点分配方案 │ + │ • 新日历排程 │ + └───────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────────────────┐ +│ TASK 4: Executive Summary [待完成] │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ +│ │ 1页执行摘要: │ │ +│ │ • Task 1 方案优势: 总服务量提升 34.6% │ │ +│ │ • Task 2/3 改进效果量化 │ │ +│ │ • 对 FBST 的关键建议 │ │ +│ │ • 方法论局限性说明 │ │ +│ └─────────────────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────────────────┘ +``` -含义:越近的站点对“周边需求”贡献越大,且贡献按距离平滑衰减;`ρ` 越大表示“更大范围的周边”。 +### 当前进度 -### 2.3 敏感性分析 -本文默认同时计算 `ρ ∈ {10, 20, 30}` miles 三个情景,用于审计“周边”尺度选择对结果的影响。 +| 任务 | 状态 | 说明 | +|------|------|------| +| Task 1 | ✅ 已完成 | 基础排程方案,5个脚本全部运行通过 | +| Task 2 | ⏳ 待开始 | 天气响应调度(选择2a或2b之一)| +| Task 3 | ⏳ 待开始 | 双站点同车分配优化 | +| Task 4 | ⏳ 待开始 | 1页执行摘要 | -## 3. 年度频次分配:有效性与公平性 -### 3.1 有效性(Effectiveness) -用户确认“不建单次容量上限”,因此总体服务量(期望)可用下式作为有效性代理: +### 下一步操作 -`Eff = Σ_i k_i · μ_i` +``` +1. [Task 2 或 Task 3] 根据题目要求选择完成其一 + │ + ├─▶ 若选 Task 2: + │ • 计算站点间距离矩阵 + │ • 选择 2a(减站点) 或 2b(优化时间) + │ • 实现并量化改进 + │ + └─▶ 若选 Task 3: + • 筛选共生站点配对 + • 建立第一站点分配模型 + • 生成新排程并对比评估 -该指标等价于:假设单次服务量随到访人数线性增长,且不考虑单次运力/食品上限截断。 +2. [Task 4] 撰写1页执行摘要 + • 汇总 Task 1 + (Task 2 或 Task 3) 结果 + • 突出方法优势与建议 +``` -### 3.2 公平性(Fairness):服务水平而非次数 -题面“served much better”更自然地对应“服务水平/满足率”而非“访问次数相等”。 -我们定义站点 i 的服务水平(对邻域需求的相对供给)为: +--- -`S_i(ρ) = (k_i · μ_i) / D_i(ρ)` +## 运行结果摘要 -然后用两类审计指标衡量不均等: -- `min_i S_i(ρ)`:最弱站点的服务水平(max-min 视角) -- `Gini({S_i(ρ)})`:服务水平分布的不均等程度(标准 Gini 公式) +| 指标 | 推荐方案 | 均匀分配 | 2019缩放 | 变化 | +|------|---------|---------|---------|------| +| E1 (总服务量) | **140,121** | 104,797 | 104,071 | +34.6% | +| E2 (质量加权) | **131,673** | 101,309 | 100,264 | +31.3% | +| F1 (Gini系数) | 0.314 | **0.026** | 0.092 | 公平性降低 | +| F2 (最低满足率) | 2.0 | **8.4** | 5.0 | - | -### 3.3 分配规则(主推荐:按周边需求比例分配) -在覆盖约束 `k_i>=1` 下,我们采用**按周边需求代理 `D_i(ρ)` 比例分配剩余次数**: -1) 先给每站点 1 次:`k_i := 1` -2) 将剩余 `R = N_total - 70` 次按权重 `w_i = D_i(ρ)` 做整数分配(largest remainder / Hamilton 方法): - - 连续目标:`k_i ≈ 1 + R · w_i/Σw` - - 再通过取整与余数分配保证 `Σk_i=N_total` +**核心发现**:按需求比例分配可提升34.6%的总服务量,但存在有效性-公平性权衡。 -该规则的解释性很强:**周边需求越大,年度访问越多**,且覆盖约束保证所有站点至少一次。 +--- -### 3.4 基线与对比 -为可审计地量化改进,输出中包含“2019 访问次数按比例缩放到 730 次”的基线(`baseline_2019_scaled`),并在同一套指标下对比: -- 总有效性 `Σ k_i μ_i` -- 公平性 `Gini(S)`、`min S` +## 1. 问题形式化 -## 4. 排程层(何时去):将 `k_i` 变成 2021 日历 -目标:把每站点年度次数转成可发布的具体日期,同时保证每天正好 2 个不同站点。 +### 1.1 输入数据 -### 4.1 均匀间隔的目标日期 -对站点 i 的第 `j` 次访问(`j=0..k_i-1`),设理想目标日(1..365)为: +对 $n=70$ 个站点,数据提供: -`t_{i,j} = round( (j+0.5) · 365 / k_i )` +| 字段 | 符号 | 说明 | +|------|------|------| +| 位置 | $(lat_i, lon_i)$ | 经纬度坐标 | +| 2019年访问次数 | $v_i$ | 历史频次,总计722次 | +| 单次服务人数均值 | $\mu_i$ | 观测均值,范围[17.2, 396.6] | +| 单次服务人数标准差 | $\sigma_i$ | 观测波动,范围[2.2, 93.5] | -直观含义:尽量均匀地把 `k_i` 次撒在全年。 +### 1.2 运营参数(题面提取) -### 4.2 装箱与修复 -先按 `t_{i,j}` 把访问事件放入对应日期桶;若某天超过容量(2 个站点)或出现同一站点重复,则将溢出事件移动到最接近的仍有空位且不重复的日期,直至: -- 每天正好 2 个站点 -- 每天两站点不同 -- 总计 730 个事件全部入日历 +| 参数 | 符号 | 值 | 来源 | +|------|------|-----|------| +| 卡车物理运力 | $W$ | 15,000 lbs | 题面明确 | +| 典型服务户数 | $[\underline{C}, \bar{C}]$ | [200, 250] | 题面"typical" | +| 每日可派车次 | - | 2 | 题面"2 trucks any given day" | +| 全年运营天数 | $T$ | 365 | 假设全年运营 | +| 年度总访问次数 | $N$ | 730 | $= 2 \times 365$ | -输出同时给出每站点相邻访问间隔的统计(最大/均值/标准差),用于审计“服务连续性”。 +### 1.3 决策变量 -## 5. 可复现流水线(脚本 + xlsx 传输) -按步骤运行(从项目根目录): -1) `python task1/01_clean.py` → `task1/01_clean.xlsx`(标准化字段、补 `site_id`) -2) `python task1/02_neighbor_demand.py` → `task1/02_neighbor.xlsx`(`D_i(ρ)` 与距离矩阵) -3) `python task1/03_allocate_k.py` → `task1/03_allocate.xlsx`(多种分配方法 + 指标对比) -4) `python task1/04_schedule_2021.py` → `task1/04_schedule.xlsx`(2021 日历排程;默认 `ρ=20mi` + `proportional_D`) +- $k_i \in \mathbb{Z}^+$:站点 $i$ 的年度访问次数 +- $\mathcal{S}_i = \{t_{i,1}, ..., t_{i,k_i}\}$:站点 $i$ 的具体访问日期 -### 5.1 关键输出表 -- `task1/03_allocate.xlsx`: - - `allocations`:每站点的 `k_i` 以及对应 `S_i(ρ)`(按不同 `ρ`/方法) - - `metrics`:每种方法/情景的有效性与公平性汇总 -- `task1/04_schedule.xlsx`: - - `meta`:排程采用的 `ρ` 与方法列名 - - `calendar`:每天两个站点(可直接发布的日历) - - `site_dates`:每站点的具体日期列表 - - `gap_metrics`:每站点访问间隔统计(连续性审计) +### 1.4 硬约束 -## 6. 假设与局限(必须在正文显式声明) -- 由于用户确认“不建单次容量上限”,本文未建模单次运力/食品约束,也无法估计缺供概率;有效性以 `Σ k_i μ_i` 作为线性代理。 -- `μ_i, σ_i` 被视为“真实到访需求”的代理统计量;若历史存在供给截断,则高需求站点可能被系统性低估(需在后续任务中引入容量或外部数据修正)。 -- “周边需求”完全由站点间空间平滑构造;`ρ` 的选择需要与 FBST 对“可接受出行半径”的运营经验校准,因此本文提供 `ρ∈{10,20,30}` 的敏感性结果便于审计与讨论。 +$$\sum_{i=1}^{70} k_i = N = 730 \tag{C1: 资源约束}$$ + +$$k_i \geq 1, \quad \forall i \in [1,70] \tag{C2: 覆盖约束}$$ + +$$|\{i : t \in \mathcal{S}_i\}| = 2, \quad \forall t \in [1, 365] \tag{C3: 每日2站点}$$ + +--- + +## 2. 阶段一:真实需求估计(截断回归) + +### 2.1 关键数据观察 + +分析70个站点的 $\mu_i$ 分布: + +| 统计量 | 值 | +|--------|-----| +| $\mu > 250$ 的站点数 | 9 | +| $\mu_{max}$ | 396.6 (MFP Waverly) | +| $\mu$ 第二高 | 314.6 (MFP Avoca) | +| $\mu$ 第三高 | 285.3 (MFP Endwell) | +| $\mu$ 均值 | 141.4 | + +**结论**:题面"200-250户"是"typical"而非硬上限。高需求站点通过减少每户分配量实现超额服务。 + +### 2.2 观测机制建模 + +**核心假设**:观测到的 $\mu_i$ 是真实需求 $\tilde{\mu}_i$ 在服务容量约束下的截断观测。 + +设单次服务的有效容量上限为 $C$(取 $C = 400$,略高于 $\mu_{max}$)。 + +$$\mu_i = E[\min(\tilde{D}_i, C)]$$ + +其中 $\tilde{D}_i \sim \mathcal{N}(\tilde{\mu}_i, \tilde{\sigma}_i^2)$ 是站点 $i$ 的真实单次需求。 + +### 2.3 截断修正公式 + +**Step 1**:计算截断概率代理 + +$$p_i^{trunc} = 1 - \Phi\left(\frac{C - \mu_i}{\sigma_i}\right)$$ + +**Step 2**:分段修正(阈值 $p^{trunc} \geq 0.02$) + +$$\tilde{\mu}_i = \begin{cases} +\mu_i & \text{if } p_i^{trunc} < 0.02 \\[8pt] +\mu_i \cdot (1 + 0.4 \cdot p_i^{trunc}) & \text{if } p_i^{trunc} \geq 0.02 +\end{cases}$$ + +### 2.4 实际修正结果 + +采用阈值 $p^{trunc} \geq 0.02$,共5个站点被修正: + +| site_id | 站点名称 | $\mu$ | $\sigma$ | $p^{trunc}$ | $\tilde{\mu}$ | 修正幅度 | +|---------|---------|-------|----------|-------------|---------------|----------| +| 66 | MFP Waverly | 396.6 | 51.9 | 0.474 | 471.9 | +19.0% | +| 2 | MFP Avoca | 314.6 | 57.3 | 0.068 | 323.2 | +2.7% | +| 13 | MFP College TC3 | 261.5 | 92.0 | 0.066 | 268.4 | +2.6% | +| 17 | MFP Endwell United Methodist | 285.2 | 60.8 | 0.030 | 288.6 | +1.2% | +| 30 | MFP Redeemer Lutheran | 230.6 | 93.5 | 0.035 | 233.8 | +1.4% | + +**修正前后对比**: +- 修正前 $\sum \mu_i$ = 9,899.9 +- 修正后 $\sum \tilde{\mu}_i$ = 9,997.2 +- 增幅:+0.98% + +--- + +## 3. 阶段二:频次分配模型 + +### 3.1 分配原则 + +**核心思想**:按真实需求 $\tilde{\mu}_i$ 比例分配访问次数。 + +$$k_i = 1 + \text{Hamilton}\left(N - 70, \; w_i = \tilde{\mu}_i\right)$$ + +其中: +- 先给每个站点分配1次(满足覆盖约束C2) +- 剩余 $N - 70 = 660$ 次按权重 $\tilde{\mu}_i$ 做整数分配 + +### 3.2 Hamilton方法(最大余数法) + +```python +def hamilton_allocation(total: int, weights: list) -> list: + n = len(weights) + w_sum = sum(weights) + quotas = [total * w / w_sum for w in weights] + floors = [int(q) for q in quotas] + remainders = [q - f for q, f in zip(quotas, floors)] + leftover = total - sum(floors) + indices = sorted(range(n), key=lambda i: -remainders[i]) + for i in indices[:leftover]: + floors[i] += 1 + return floors +``` + +### 3.3 实际分配结果 + +**验证**: +- 总访问次数:$\sum k_i = 730$ ✓ +- 访问次数范围:$[2, 32]$ +- 最小访问次数:2(满足覆盖约束) + +**访问次数分布**: + +``` +k = 2: 1 个站点 █ +k = 3: 14 个站点 ██████████████ +k = 4: 2 个站点 ██ +k = 5: 4 个站点 ████ +k = 6: 4 个站点 ████ +k = 8: 1 个站点 █ +k = 9: 3 个站点 ███ +k = 10: 3 个站点 ███ +k = 11: 6 个站点 ██████ +k = 12: 5 个站点 █████ +k = 13: 6 个站点 ██████ +k = 14: 5 个站点 █████ +k = 15: 4 个站点 ████ +k = 16: 2 个站点 ██ +k = 17: 1 个站点 █ +k = 18: 2 个站点 ██ +k = 19: 4 个站点 ████ +k = 20: 1 个站点 █ +k = 22: 1 个站点 █ +k = 32: 1 个站点 █ +``` + +**频次最高的10个站点**: + +| site_id | 站点名称 | $\mu$ | $\tilde{\mu}$ | $k$ | 年度服务量 | +|---------|---------|-------|---------------|-----|-----------| +| 66 | MFP Waverly | 396.6 | 471.9 | 32 | 12,692 | +| 2 | MFP Avoca | 314.6 | 323.2 | 22 | 6,921 | +| 17 | MFP Endwell United Methodist | 285.3 | 288.6 | 20 | 5,705 | +| 3 | MFP Bath | 279.5 | 279.5 | 19 | 5,310 | +| 13 | MFP College TC3 | 261.5 | 268.4 | 19 | 4,969 | +| 28 | MFP Rathbone | 269.1 | 269.1 | 19 | 5,113 | +| 32 | MFP Richford | 265.9 | 265.9 | 19 | 5,052 | +| 11 | MFP College Corning | 251.0 | 251.0 | 18 | 4,518 | +| 62 | MFP The Love Church | 259.3 | 259.3 | 18 | 4,668 | +| 31 | MFP Rehoboth Deliverance | 235.9 | 235.9 | 17 | 4,010 | + +--- + +## 4. 阶段三:效果评估指标 + +### 4.1 有效性指标 + +**E1:原始总服务量** + +$$E_1 = \sum_{i=1}^{70} k_i \cdot \mu_i$$ + +**E2:质量加权服务量** + +$$E_2 = \sum_{i=1}^{70} k_i \cdot \mu_i \cdot q(\mu_i), \quad q(\mu) = \min\left(1, \frac{250}{\mu}\right)$$ + +### 4.2 公平性指标 + +**定义满足率**: + +$$r_i = \frac{k_i \cdot \mu_i}{\tilde{\mu}_i}$$ + +- **F1**:满足率基尼系数 $\text{Gini}(\mathbf{r})$ +- **F2**:最差站点满足率 $\min_i r_i$ +- **F3**:满足率变异系数 $CV_r$ + +### 4.3 实际评估结果 + +| 方案 | E1 | E2 | F1 (Gini) | F2 (min r) | F3 (CV) | +|------|-----|-----|-----------|------------|---------| +| **推荐方案** (μ̃比例) | **140,120.6** | **131,673.2** | 0.314 | 2.00 | 0.561 | +| 基线1: 均匀分配 | 104,797.3 | 101,308.9 | **0.026** | **8.41** | **0.052** | +| 基线2: 2019缩放 | 104,070.7 | 100,263.8 | 0.092 | 5.00 | 0.177 | +| 基线3: μ比例(无修正) | 139,129.2 | 131,397.1 | 0.311 | 2.00 | 0.550 | + +**推荐方案优势**: +- 相对均匀分配:E1 +33.7%,E2 +30.0% +- 相对2019缩放:E1 +34.6%,E2 +31.3% + +**有效性-公平性权衡**:推荐方案最大化了总服务量,但公平性(Gini系数)较差。这是按需求比例分配的固有特性。 + +--- + +## 5. 阶段四:日历排程 + +### 5.1 目标 + +将 $\{k_i\}_{i=1}^{70}$ 转化为365天日历,满足每日2站点约束,并最小化访问间隔的不均匀性。 + +### 5.2 算法:贪心装箱 + 局部优化 + +1. **生成理想日期**:$t_{i,j}^* = \text{round}\left(\frac{(j + 0.5) \cdot 365}{k_i}\right)$ +2. **贪心分配**:按理想日期排序,就近分配到可用槽位 +3. **局部优化**:随机交换站点,若改善间隔方差则接受 + +### 5.3 实际排程结果 + +**验证**: +- 已分配访问事件:730 / 730 ✓ +- 日历统计:365天满载 + 0天部分 + 0天空闲 ✓ +- 局部优化:5000次迭代,接受33次改进 + +**间隔统计**: + +| 指标 | 值 | +|------|-----| +| 平均间隔均值 | 55.4 天 | +| 平均间隔标准差 | 3.2 天 | +| 最大单次间隔 | 179 天 | +| 平均间隔CV | 0.103 | + +**日历预览(前10天)**: + +| 日期 | 站点1 | 站点2 | +|------|-------|-------| +| 1 | MFP Avoca | MFP Waverly | +| 2 | MFP Van Etten | MFP Endwell United Methodist | +| 3 | MFP Bath | MFP Richford | +| 4 | MFP College Corning | MFP College TC3 | +| 5 | MFP Rehoboth Deliverance | MFP Rathbone | +| 6 | MFP Waverly | MFP Redeemer Lutheran | +| 7 | MFP The Love Church | MFP Lindley | +| 8 | MFP Tuscarora | MFP Woodhull | +| 9 | MFP Endwell United Methodist | MFP Richford | +| 10 | MFP Bath | MFP College Corning | + +--- + +## 6. 可复现流水线 + +### 6.1 脚本结构 + +``` +task1/ +├── 01_clean.py # 数据清洗与标准化 +├── 02_demand_correction.py # 截断回归修正 +├── 03_allocate.py # Hamilton频次分配 +├── 04_evaluate.py # 评估指标计算 +├── 05_schedule.py # 日历排程生成 +├── 01_clean.xlsx # Step 1 输出 +├── 02_demand.xlsx # Step 2 输出 +├── 03_allocate.xlsx # Step 3 输出 +├── 04_metrics.xlsx # Step 4 输出 +└── 05_schedule.xlsx # Step 5 输出 +``` + +### 6.2 运行方法 + +```bash +# 从项目根目录依次运行 +python task1/01_clean.py +python task1/02_demand_correction.py +python task1/03_allocate.py +python task1/04_evaluate.py +python task1/05_schedule.py +``` + +### 6.3 关键参数 + +| 参数 | 值 | 位置 | +|------|-----|------| +| 有效容量上限 $C$ | 400 | `02_demand_correction.py` | +| 截断概率阈值 | 0.02 | `02_demand_correction.py` | +| 质量折扣阈值 $\bar{C}$ | 250 | `04_evaluate.py` | +| 年度总访问次数 $N$ | 730 | `03_allocate.py` | +| 全年天数 $T$ | 365 | `05_schedule.py` | + +### 6.4 输出文件说明 + +| 文件 | 内容 | +|------|------| +| `01_clean.xlsx` | 标准化数据:site_id, site_name, lat, lon, visits_2019, mu, sigma | +| `02_demand.xlsx` | 需求修正:+ mu_tilde, p_trunc, is_corrected | +| `03_allocate.xlsx` | 频次分配:+ k, annual_service, r | +| `04_metrics.xlsx` | 评估指标:metrics_summary, site_details, parameters | +| `05_schedule.xlsx` | 日历排程:calendar, site_dates, gap_statistics, parameters | + +--- + +## 7. 敏感性分析 + +### 7.1 参数范围 + +| 参数 | 符号 | 基准值 | 敏感性范围 | +|------|------|--------|-----------| +| 有效容量 | $C$ | 400 | {350, 400, 450} | +| 截断概率阈值 | $p^{trunc}$ | 0.02 | {0.01, 0.02, 0.05, 0.10} | +| 典型服务量 | $\bar{C}$ | 250 | {200, 250, 300} | + +### 7.2 阈值敏感性 + +| 阈值 | 被修正站点数 | $\sum \tilde{\mu}$ 增幅 | +|------|-------------|----------------------| +| 0.01 | 7 | +1.2% | +| 0.02 | 5 | +0.98% | +| 0.05 | 2 | +0.82% | +| 0.10 | 1 | +0.76% | + +--- + +## 8. 假设与局限性 + +### 8.1 显式假设 + +| 编号 | 假设 | 依据 | +|------|------|------| +| A1 | 真实需求服从正态分布 | 中心极限定理 | +| A2 | 有效容量 $C=400$ | 基于 $\mu_{max}=396.6$ | +| A3 | 2021年需求结构与2019年相似 | 题面要求使用2019数据 | +| A4 | 全年365天均可运营 | 简化假设 | +| A5 | 每日2站点为硬约束 | 题面"2 trucks" | + +### 8.2 局限性 + +1. **截断修正的简化**:采用线性近似而非完整截断正态MLE +2. **需求外生性**:未建模"访问频次影响需求"的内生效应 +3. **空间相关性**:未考虑相邻站点需求的空间自相关 +4. **季节性**:未考虑需求的季节波动(如冬季低需求) + +--- + +## 9. 结论与建议 + +### 9.1 方法论贡献 + +1. **识别截断偏误**:通过数据分析发现高需求站点的 $\mu$ 被容量截断,提出截断回归修正 +2. **质量-数量权衡**:引入质量折扣因子 $q(\mu)$,避免简单最大化服务人次 +3. **满足率公平**:以需求满足率而非访问次数衡量公平 + +### 9.2 对FBST的建议 + +1. **高需求站点**:MFP Waverly ($k=32$) 需求远超其他站点,建议考虑增派车辆或设立分站 +2. **数据收集**:建议记录"被拒绝服务的客户数"以更准确估计真实需求 +3. **动态调整**:建议季度末根据实际服务数据调整下季度排程 + +### 9.3 有效性-公平性权衡 + +推荐方案在有效性(总服务量)上显著优于基线,但公平性较差。若FBST更重视公平性,可考虑: +- 混合方案:在推荐方案基础上,对低频站点适当增加访问 +- Pareto优化:在有效性-公平性前沿上选择平衡点 + +### 9.4 后续研究方向 + +- **Task 2**:考虑天气对需求的影响,建立动态调度模型 +- **Task 3**:双站点同车访问的最优分配策略