diff --git a/task3/01_distance.xlsx b/task3/01_distance.xlsx
index 64afdc6..d1b6a81 100644
Binary files a/task3/01_distance.xlsx and b/task3/01_distance.xlsx differ
diff --git a/task3/02_pairing.py b/task3/02_pairing.py
index f5100e2..ae66bee 100644
--- a/task3/02_pairing.py
+++ b/task3/02_pairing.py
@@ -26,10 +26,10 @@ INPUT_FILE = '01_distance.xlsx'
OUTPUT_FILE = '02_pairing.xlsx'
# 约束参数
-L_MAX = 50 # 距离阈值 (英里)
-MU_SUM_MAX = 450 # 需求和上限
-CV_MAX = 0.5 # 变异系数上限
-Q = 400 # 卡车容量
+L_MAX = 30 # 距离阈值 (英里)
+MU_SUM_MAX = 350 # 需求和上限
+CV_MAX = 0.3 # 变异系数上限
+Q = 350 # 卡车容量
# 价值函数权重
ALPHA = 1.0 # 容量利用率权重
diff --git a/task3/02_pairing.xlsx b/task3/02_pairing.xlsx
index e8bd422..bea037a 100644
Binary files a/task3/02_pairing.xlsx and b/task3/02_pairing.xlsx differ
diff --git a/task3/03_allocation.xlsx b/task3/03_allocation.xlsx
index 216b5ea..acf68cc 100644
Binary files a/task3/03_allocation.xlsx and b/task3/03_allocation.xlsx differ
diff --git a/task3/04_reschedule.xlsx b/task3/04_reschedule.xlsx
index 4f15d64..c1708d7 100644
Binary files a/task3/04_reschedule.xlsx and b/task3/04_reschedule.xlsx differ
diff --git a/task3/05_calendar.xlsx b/task3/05_calendar.xlsx
index ca57067..23fa505 100644
Binary files a/task3/05_calendar.xlsx and b/task3/05_calendar.xlsx differ
diff --git a/task3/06_evaluate.xlsx b/task3/06_evaluate.xlsx
index 9528082..1a9057b 100644
Binary files a/task3/06_evaluate.xlsx and b/task3/06_evaluate.xlsx differ
diff --git a/task3/07_sensitivity.py b/task3/07_sensitivity.py
index b96f00e..cb524db 100644
--- a/task3/07_sensitivity.py
+++ b/task3/07_sensitivity.py
@@ -16,17 +16,66 @@ Task 3 - Step 7: 敏感性分析 (Refined)
import pandas as pd
import numpy as np
from scipy import stats
-import matplotlib.pyplot as plt
import warnings
import os
+import tempfile
from pathlib import Path
warnings.filterwarnings('ignore')
-# 设置绘图风格
-plt.style.use('seaborn-v0_8-whitegrid')
-plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] # 适配中文
-plt.rcParams['axes.unicode_minus'] = False
+# Matplotlib cache dirs(避免 home 目录不可写导致的警告)
+_mpl_config_dir = Path(tempfile.gettempdir()) / "mm_task3_mplconfig"
+_xdg_cache_dir = Path(tempfile.gettempdir()) / "mm_task3_xdg_cache"
+_mpl_config_dir.mkdir(parents=True, exist_ok=True)
+_xdg_cache_dir.mkdir(parents=True, exist_ok=True)
+os.environ.setdefault("MPLCONFIGDIR", str(_mpl_config_dir))
+os.environ.setdefault("XDG_CACHE_HOME", str(_xdg_cache_dir))
+
+import matplotlib.pyplot as plt
+from cycler import cycler
+
+# 设置绘图风格(低饱和度 Morandi 色系)
+MORANDI = {
+ "bg": "#F5F2ED",
+ "grid": "#D7D1C7",
+ "text": "#3F3F3F",
+ "muted_blue": "#8FA3A7",
+ "muted_blue_dark": "#6E858A",
+ "sage": "#AEB8A6",
+ "terracotta": "#B07A6A",
+ "warm_gray": "#8C857A",
+}
+
+plt.rcParams.update(
+ {
+ "figure.facecolor": MORANDI["bg"],
+ "axes.facecolor": MORANDI["bg"],
+ "savefig.facecolor": MORANDI["bg"],
+ "axes.edgecolor": MORANDI["grid"],
+ "axes.labelcolor": MORANDI["text"],
+ "xtick.color": MORANDI["text"],
+ "ytick.color": MORANDI["text"],
+ "text.color": MORANDI["text"],
+ "grid.color": MORANDI["grid"],
+ "grid.alpha": 0.55,
+ "axes.grid": True,
+ "axes.prop_cycle": cycler(
+ "color",
+ [
+ MORANDI["muted_blue"],
+ MORANDI["sage"],
+ MORANDI["terracotta"],
+ MORANDI["warm_gray"],
+ ],
+ ),
+ "legend.frameon": True,
+ "legend.framealpha": 0.9,
+ "legend.facecolor": MORANDI["bg"],
+ "legend.edgecolor": MORANDI["grid"],
+ "font.sans-serif": ["Arial Unicode MS", "SimHei", "DejaVu Sans"],
+ "axes.unicode_minus": False,
+ }
+)
# ============================================
# 基础参数和函数 (保持不变)
@@ -333,16 +382,38 @@ for i, param in enumerate(params_list):
data = df_results[df_results['param'] == param].sort_values('value')
# 绘制 E1/E2 - 左轴
- line1, = ax.plot(data['value'], data['E1'], 'b-', linewidth=1.6, label='Expected Service (E1)')
- line2, = ax.plot(data['value'], data['E2'], 'g-', linewidth=1.6, alpha=0.9, label='Quality-weighted (E2)')
+ line1, = ax.plot(
+ data['value'],
+ data['E1'],
+ linestyle='-',
+ color=MORANDI["muted_blue_dark"],
+ linewidth=1.8,
+ label='Expected Service (E1)',
+ )
+ line2, = ax.plot(
+ data['value'],
+ data['E2'],
+ linestyle='-',
+ color=MORANDI["sage"],
+ linewidth=1.8,
+ alpha=0.95,
+ label='Quality-weighted (E2)',
+ )
ax.set_xlabel(param_labels[param][0])
ax.set_ylabel('Service (E1/E2)')
# 绘制 R1 (风险) - 右轴
ax2 = ax.twinx()
- line3, = ax2.plot(data['value'], data['R1'], 'r--', linewidth=1.6, label='Shortfall Risk (R1)')
- ax2.set_ylabel('Risk Probability (R1)', color='r')
- ax2.tick_params(axis='y', labelcolor='r')
+ line3, = ax2.plot(
+ data['value'],
+ data['R1'],
+ linestyle='--',
+ color=MORANDI["terracotta"],
+ linewidth=1.8,
+ label='Shortfall Risk (R1)',
+ )
+ ax2.set_ylabel('Risk Probability (R1)', color=MORANDI["terracotta"])
+ ax2.tick_params(axis='y', labelcolor=MORANDI["terracotta"])
# 添加基准线
if param == 'merge_ratio':
@@ -354,7 +425,7 @@ for i, param in enumerate(params_list):
elif param == 'cv_max':
base_val = BASE_CV_MAX
- ax.axvline(x=base_val, color='gray', linestyle=':', alpha=0.5)
+ ax.axvline(x=base_val, color=MORANDI["warm_gray"], linestyle=':', alpha=0.65)
# 图例
lines = [line1, line2, line3]
@@ -362,7 +433,7 @@ for i, param in enumerate(params_list):
ax.legend(lines, labels, loc='best', frameon=True)
ax.set_title(f'Effect of {param_labels[param][0]}')
- ax.grid(True, alpha=0.3)
+ ax.grid(True, alpha=0.55)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.savefig(OUTPUT_FIG, dpi=300)
diff --git a/task3/07_sensitivity.xlsx b/task3/07_sensitivity.xlsx
index ad10fdd..463faba 100644
Binary files a/task3/07_sensitivity.xlsx and b/task3/07_sensitivity.xlsx differ
diff --git a/task3/08_visualize.py b/task3/08_visualize.py
index f113482..6a44d58 100644
--- a/task3/08_visualize.py
+++ b/task3/08_visualize.py
@@ -19,6 +19,7 @@ Task 3 - Step 8: 可视化(Fig.1/2/4/5)
from __future__ import annotations
from dataclasses import dataclass
+import json
from pathlib import Path
import os
@@ -38,11 +39,62 @@ import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
+from matplotlib import colors as mcolors
+from cycler import cycler
BASE_DIR = Path(__file__).resolve().parent
+MORANDI = {
+ "bg": "#F5F2ED",
+ "grid": "#D7D1C7",
+ "text": "#3F3F3F",
+ "muted_blue": "#8FA3A7",
+ "muted_blue_dark": "#6E858A",
+ "sage": "#AEB8A6",
+ "clay": "#C6AA9B",
+ "rose": "#D2B8B3",
+ "warm_gray": "#8C857A",
+ "terracotta": "#B07A6A",
+}
+
+
+def _apply_morandi_style() -> None:
+ plt.rcParams.update(
+ {
+ "figure.facecolor": MORANDI["bg"],
+ "axes.facecolor": MORANDI["bg"],
+ "savefig.facecolor": MORANDI["bg"],
+ "axes.edgecolor": MORANDI["grid"],
+ "axes.labelcolor": MORANDI["text"],
+ "xtick.color": MORANDI["text"],
+ "ytick.color": MORANDI["text"],
+ "text.color": MORANDI["text"],
+ "grid.color": MORANDI["grid"],
+ "grid.alpha": 0.55,
+ "axes.grid": True,
+ "axes.titleweight": "semibold",
+ "axes.titlesize": 11,
+ "axes.labelsize": 10,
+ "legend.frameon": True,
+ "legend.framealpha": 0.9,
+ "legend.facecolor": MORANDI["bg"],
+ "legend.edgecolor": MORANDI["grid"],
+ "axes.prop_cycle": cycler(
+ "color",
+ [
+ MORANDI["muted_blue"],
+ MORANDI["sage"],
+ MORANDI["clay"],
+ MORANDI["rose"],
+ MORANDI["warm_gray"],
+ ],
+ ),
+ }
+ )
+
+
@dataclass(frozen=True)
class Paths:
distance_xlsx: Path
@@ -76,6 +128,15 @@ def _pair_key(a: int, b: int) -> tuple[int, int]:
def fig1_pairing_map(paths: Paths) -> Path:
+ _apply_morandi_style()
+
+ # Warmer + higher contrast for Fig.1 specifically (clearer on paper/screens)
+ fig1_colors = {
+ "paired": "#e76f51",
+ "unpaired": "#6c757d",
+ "link": "#bc6c25",
+ }
+
sites = pd.read_excel(paths.distance_xlsx, sheet_name="sites")
pairs = pd.read_excel(paths.pairing_xlsx, sheet_name="selected_pairs")
@@ -97,9 +158,9 @@ def fig1_pairing_map(paths: Paths) -> Path:
ax.plot(
[si["lon"], sj["lon"]],
[si["lat"], sj["lat"]],
- color="#2c7fb8",
- alpha=0.35,
- linewidth=1.0,
+ color=fig1_colors["link"],
+ alpha=0.5,
+ linewidth=1.2,
zorder=1,
)
@@ -110,8 +171,8 @@ def fig1_pairing_map(paths: Paths) -> Path:
paired["lon"],
paired["lat"],
s=18,
- color="#d95f0e",
- alpha=0.85,
+ color=fig1_colors["paired"],
+ alpha=0.9,
label=f"Paired sites (n={len(paired)})",
zorder=2,
)
@@ -120,7 +181,7 @@ def fig1_pairing_map(paths: Paths) -> Path:
unpaired["lon"],
unpaired["lat"],
s=22,
- color="#636363",
+ color=fig1_colors["unpaired"],
alpha=0.9,
marker="x",
label=f"Unpaired sites (n={len(unpaired)})",
@@ -130,7 +191,7 @@ def fig1_pairing_map(paths: Paths) -> Path:
ax.set_title("Fig.1 Pairing map (sites + selected 34 links)")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
- ax.grid(True, alpha=0.2)
+ ax.grid(True, alpha=0.55)
ax.legend(loc="best", frameon=True)
out = paths.figures_dir / "fig1_pairing_map.png"
@@ -140,7 +201,58 @@ def fig1_pairing_map(paths: Paths) -> Path:
return out
+def export_fig1_points_js(paths: Paths) -> Path:
+ sites = pd.read_excel(paths.distance_xlsx, sheet_name="sites").copy()
+ pairs = pd.read_excel(paths.pairing_xlsx, sheet_name="selected_pairs").copy()
+
+ sites["site_id"] = sites["site_id"].astype(int)
+ sites["k"] = sites["k"].astype(int)
+
+ paired_ids = set(pairs["site_i_id"].astype(int)).union(
+ set(pairs["site_j_id"].astype(int))
+ )
+
+ points = []
+ for _, r in sites.iterrows():
+ points.append(
+ {
+ "site_id": int(r["site_id"]),
+ "site_name": str(r["site_name"]),
+ "lat": float(r["lat"]),
+ "lng": float(r["lon"]),
+ "mu": float(r["mu"]),
+ "sigma": float(r["sigma"]),
+ "k": int(r["k"]),
+ "is_paired": int(r["site_id"]) in paired_ids,
+ }
+ )
+
+ links = []
+ for _, r in pairs.iterrows():
+ links.append(
+ {
+ "site_i_id": int(r["site_i_id"]),
+ "site_j_id": int(r["site_j_id"]),
+ "site_i_name": str(r["site_i_name"]),
+ "site_j_name": str(r["site_j_name"]),
+ "distance": float(r["distance"]),
+ }
+ )
+
+ out = BASE_DIR / "fig1_points.js"
+ payload = (
+ "// Auto-generated from `task3/01_distance.xlsx` (sites) + `task3/02_pairing.xlsx` (selected_pairs)\n"
+ "// Usage: open `task3/fig1_carto.html` in a browser (same directory must contain this file).\n"
+ f"window.FIG1_POINTS = {json.dumps(points, ensure_ascii=False, separators=(',', ':'))};\n"
+ f"window.FIG1_LINKS = {json.dumps(links, ensure_ascii=False, separators=(',', ':'))};\n"
+ )
+ out.write_text(payload, encoding="utf-8")
+ return out
+
+
def fig2_allocation_scatter(paths: Paths) -> Path:
+ _apply_morandi_style()
+
df = pd.read_excel(paths.allocation_xlsx, sheet_name="allocation")
q_ratio = df["q_ratio"].to_numpy(dtype=float)
@@ -157,15 +269,22 @@ def fig2_allocation_scatter(paths: Paths) -> Path:
]
for ax, (title, x) in zip(axes, panels):
- ax.scatter(x, q_ratio, s=22, alpha=0.75, color="#3182bd", edgecolor="none")
+ ax.scatter(
+ x,
+ q_ratio,
+ s=22,
+ alpha=0.8,
+ color=MORANDI["muted_blue"],
+ edgecolor="none",
+ )
if np.isfinite(x).all() and np.isfinite(q_ratio).all() and len(x) >= 2:
coef = np.polyfit(x, q_ratio, 1)
xx = np.linspace(float(np.min(x)), float(np.max(x)), 100)
yy = coef[0] * xx + coef[1]
- ax.plot(xx, yy, color="#de2d26", linewidth=2.0, alpha=0.9)
+ ax.plot(xx, yy, color=MORANDI["terracotta"], linewidth=2.0, alpha=0.95)
ax.set_title(title)
ax.set_xlabel("x")
- ax.grid(True, alpha=0.2)
+ ax.grid(True, alpha=0.55)
axes[0].set_ylabel("$q^*/Q$")
fig.suptitle("Fig.2 Allocation strategy scatter (34 pairs)", y=1.02)
@@ -178,6 +297,8 @@ def fig2_allocation_scatter(paths: Paths) -> Path:
def fig4_calendar_heatmap(paths: Paths) -> Path:
+ _apply_morandi_style()
+
sites = pd.read_excel(paths.distance_xlsx, sheet_name="sites")[["site_id", "mu"]]
sites["site_id"] = sites["site_id"].astype(int)
mu_by_id = dict(zip(sites["site_id"], sites["mu"]))
@@ -212,7 +333,17 @@ def fig4_calendar_heatmap(paths: Paths) -> Path:
expected[day - 1, slot - 1] = pair_E.get(_pair_key(i_id, j_id), np.nan)
fig, ax = plt.subplots(figsize=(12, 2.6), dpi=200)
- im = ax.imshow(expected.T, aspect="auto", cmap="viridis")
+ cmap = mcolors.LinearSegmentedColormap.from_list(
+ "morandi",
+ [
+ MORANDI["bg"],
+ "#E7E1D7",
+ MORANDI["sage"],
+ MORANDI["muted_blue"],
+ "#5F7478",
+ ],
+ )
+ im = ax.imshow(expected.T, aspect="auto", cmap=cmap)
ax.set_yticks([0, 1], labels=["Slot 1", "Slot 2"])
ax.set_xlabel("Day of year")
ax.set_title("Fig.4 Calendar heatmap (expected service per slot)")
@@ -233,6 +364,8 @@ def fig4_calendar_heatmap(paths: Paths) -> Path:
def fig5_risk_distribution(paths: Paths) -> Path:
+ _apply_morandi_style()
+
risk = pd.read_excel(paths.evaluate_xlsx, sheet_name="pair_risk").copy()
p = risk["shortfall_prob_either"].to_numpy(dtype=float)
p = p[np.isfinite(p)]
@@ -240,21 +373,36 @@ def fig5_risk_distribution(paths: Paths) -> Path:
fig, axes = plt.subplots(1, 2, figsize=(11, 3.8), dpi=200)
ax = axes[0]
- ax.hist(p, bins=10, color="#3182bd", alpha=0.85, edgecolor="white")
- ax.axvline(float(np.mean(p)), color="#de2d26", linewidth=2.0, label=f"mean={np.mean(p):.3f}")
+ ax.hist(p, bins=10, color=MORANDI["sage"], alpha=0.9, edgecolor=MORANDI["bg"])
+ ax.axvline(
+ float(np.mean(p)),
+ color=MORANDI["terracotta"],
+ linewidth=2.2,
+ label=f"mean={np.mean(p):.3f}",
+ )
ax.set_title("Histogram")
ax.set_xlabel("Shortfall probability (either site)")
ax.set_ylabel("Count")
- ax.grid(True, alpha=0.2)
+ ax.grid(True, alpha=0.55)
ax.legend(loc="best", frameon=True)
ax = axes[1]
p_sorted = np.sort(p)[::-1]
- ax.plot(np.arange(1, len(p_sorted) + 1), p_sorted, marker="o", markersize=3.5, linewidth=1.5)
+ ax.plot(
+ np.arange(1, len(p_sorted) + 1),
+ p_sorted,
+ marker="o",
+ markersize=3.2,
+ linewidth=1.6,
+ color=MORANDI["muted_blue_dark"],
+ markerfacecolor=MORANDI["muted_blue"],
+ markeredgecolor=MORANDI["bg"],
+ markeredgewidth=0.6,
+ )
ax.set_title("Sorted by risk (descending)")
ax.set_xlabel("Pair rank")
ax.set_ylabel("Shortfall probability")
- ax.grid(True, alpha=0.2)
+ ax.grid(True, alpha=0.55)
fig.suptitle("Fig.5 Risk distribution across 34 pairs", y=1.02)
fig.tight_layout()
@@ -282,6 +430,8 @@ def main() -> None:
out1 = fig1_pairing_map(paths)
print(f"Saved: {out1.relative_to(BASE_DIR)}")
+ out1_js = export_fig1_points_js(paths)
+ print(f"Saved: {out1_js.relative_to(BASE_DIR)}")
out2 = fig2_allocation_scatter(paths)
print(f"Saved: {out2.relative_to(BASE_DIR)}")
out4 = fig4_calendar_heatmap(paths)
diff --git a/task3/README.md b/task3/README.md
index a2f1ada..7072d58 100644
--- a/task3/README.md
+++ b/task3/README.md
@@ -643,6 +643,8 @@ $$R_1 = P(S_i / D_i < 0.8 \text{ 或 } S_j / D_j < 0.8)$$
```
task3/
+├── fig1_carto.html # 交互地图(CartoDB 底图)
+├── fig1_points.js # ✅ 自动生成(08_visualize.py)
├── 08_visualize.py # 可视化脚本
└── figures/
├── fig1_pairing_map.png
@@ -656,6 +658,8 @@ task3/

+交互版:用浏览器打开 `task3/fig1_carto.html`(同目录需有 `task3/fig1_points.js`;需联网加载 CartoDB 底图)。
+


diff --git a/task3/__pycache__/07_sensitivity.cpython-313.pyc b/task3/__pycache__/07_sensitivity.cpython-313.pyc
index 17fa2fa..d7ab62d 100644
Binary files a/task3/__pycache__/07_sensitivity.cpython-313.pyc and b/task3/__pycache__/07_sensitivity.cpython-313.pyc differ
diff --git a/task3/__pycache__/08_visualize.cpython-313.pyc b/task3/__pycache__/08_visualize.cpython-313.pyc
index d623973..e1e59f0 100644
Binary files a/task3/__pycache__/08_visualize.cpython-313.pyc and b/task3/__pycache__/08_visualize.cpython-313.pyc differ
diff --git a/task3/fig1_carto.html b/task3/fig1_carto.html
new file mode 100644
index 0000000..e462d4a
--- /dev/null
+++ b/task3/fig1_carto.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+ Task 3 Fig.1 (CartoDB + Leaflet)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/task3/fig1_points.js b/task3/fig1_points.js
new file mode 100644
index 0000000..175f19c
--- /dev/null
+++ b/task3/fig1_points.js
@@ -0,0 +1,4 @@
+// Auto-generated from `task3/01_distance.xlsx` (sites) + `task3/02_pairing.xlsx` (selected_pairs)
+// Usage: open `task3/fig1_carto.html` in a browser (same directory must contain this file).
+window.FIG1_POINTS = [{"site_id":1,"site_name":"MFP American Legion - Binghamton","lat":42.108036,"lng":-75.887779,"mu":200.1818181818182,"sigma":46.0669473306365,"k":14,"is_paired":true},{"site_id":2,"site_name":"MFP Avoca","lat":42.4174468,"lng":-77.4358433,"mu":314.5833333333333,"sigma":57.3497377870416,"k":24,"is_paired":true},{"site_id":3,"site_name":"MFP Bath","lat":42.3362172,"lng":-77.3175047,"mu":279.4545454545454,"sigma":45.2534277958336,"k":19,"is_paired":true},{"site_id":4,"site_name":"MFP Beaver Dams","lat":42.2606415,"lng":-76.9559856,"mu":170.7,"sigma":28.7906234736241,"k":12,"is_paired":true},{"site_id":5,"site_name":"MFP Birnie Transportation Services","lat":42.172309,"lng":-77.1101217,"mu":213.375,"sigma":53.4039525021991,"k":15,"is_paired":true},{"site_id":6,"site_name":"MFP Boys and Girls Club","lat":42.1053841,"lng":-75.9213736,"mu":210.8,"sigma":52.3551971313896,"k":15,"is_paired":true},{"site_id":7,"site_name":"MFP Bradford","lat":42.3691309,"lng":-77.1081064,"mu":122.2727272727273,"sigma":29.7425315300864,"k":9,"is_paired":true},{"site_id":8,"site_name":"MFP Campbell","lat":42.2314129,"lng":-77.1948265,"mu":168.5,"sigma":40.968946111104,"k":12,"is_paired":true},{"site_id":9,"site_name":"MFP Canisteo","lat":42.272779,"lng":-77.606849,"mu":177.0,"sigma":51.3051870888531,"k":13,"is_paired":true},{"site_id":10,"site_name":"MFP Colesville","lat":42.1841905,"lng":-75.6329193,"mu":197.3333333333333,"sigma":66.2589044902074,"k":14,"is_paired":false},{"site_id":11,"site_name":"MFP College Corning Community College","lat":42.1172594,"lng":-77.0735445,"mu":251.0,"sigma":33.2505639049926,"k":17,"is_paired":true},{"site_id":12,"site_name":"MFP College Ithaca College","lat":42.4199351,"lng":-76.4969643,"mu":138.3333333333333,"sigma":66.1418929272515,"k":10,"is_paired":false},{"site_id":13,"site_name":"MFP College TC3 -College","lat":42.502126,"lng":-76.287671,"mu":261.5,"sigma":91.9929345112982,"k":19,"is_paired":false},{"site_id":14,"site_name":"MFP Conklin- Maines Community Center","lat":42.0870901,"lng":-75.8309647,"mu":153.1666666666667,"sigma":20.1125620325993,"k":11,"is_paired":true},{"site_id":15,"site_name":"MFP Danby","lat":42.3522565,"lng":-76.4800051,"mu":160.2222222222222,"sigma":34.5788149658782,"k":11,"is_paired":true},{"site_id":16,"site_name":"MFP Deposit","lat":42.0625771,"lng":-75.422248,"mu":156.5833333333333,"sigma":56.545168322378,"k":11,"is_paired":false},{"site_id":17,"site_name":"MFP Endwell United Methodist Church","lat":42.111296,"lng":-76.02203,"mu":285.25,"sigma":60.7814639806937,"k":21,"is_paired":true},{"site_id":18,"site_name":"MFP Erin","lat":42.1788978,"lng":-76.6922607,"mu":173.9090909090909,"sigma":27.3219858189501,"k":12,"is_paired":true},{"site_id":19,"site_name":"MFP First Assembly Of God Church","lat":42.1022814,"lng":-75.9129518,"mu":146.0,"sigma":21.6459108042479,"k":10,"is_paired":true},{"site_id":20,"site_name":"MFP Lamphear Court","lat":42.1598551,"lng":-77.0711389,"mu":126.0,"sigma":40.6803255750109,"k":9,"is_paired":false},{"site_id":21,"site_name":"MFP Lansing","lat":42.5183206,"lng":-76.5035538,"mu":181.0,"sigma":23.2937759927411,"k":13,"is_paired":true},{"site_id":22,"site_name":"MFP Lindley","lat":42.028142,"lng":-77.1392597,"mu":232.9,"sigma":49.6217918078562,"k":16,"is_paired":true},{"site_id":23,"site_name":"MFP Millport","lat":42.267172,"lng":-76.837356,"mu":166.0,"sigma":36.4996194805127,"k":12,"is_paired":true},{"site_id":24,"site_name":"MFP Montour Falls-Schuyler County Human Services Complex","lat":42.3409968,"lng":-76.8412703,"mu":149.3636363636364,"sigma":60.0387753493902,"k":11,"is_paired":false},{"site_id":25,"site_name":"MFP Nichols-The Creamery","lat":42.02302,"lng":-76.371793,"mu":122.4,"sigma":26.4253666010521,"k":9,"is_paired":true},{"site_id":26,"site_name":"MFP Owego VFW","lat":42.1043411,"lng":-76.2615965,"mu":176.25,"sigma":34.285234033433,"k":12,"is_paired":true},{"site_id":27,"site_name":"MFP Prattsburgh","lat":42.5272099,"lng":-77.2866,"mu":144.5,"sigma":50.8428952755446,"k":10,"is_paired":false},{"site_id":28,"site_name":"MFP Rathbone","lat":42.1306252,"lng":-77.3197915,"mu":269.0909090909091,"sigma":55.5687943821972,"k":19,"is_paired":true},{"site_id":29,"site_name":"MFP Reach for Christ Church Freeville","lat":42.4919905,"lng":-76.3443272,"mu":220.0,"sigma":22.6175939382498,"k":15,"is_paired":true},{"site_id":30,"site_name":"MFP Redeemer Lutheran Church","lat":42.1000047,"lng":-75.9224594,"mu":230.6,"sigma":93.4786963252412,"k":17,"is_paired":false},{"site_id":31,"site_name":"MFP Rehoboth Deliverance Ministry","lat":42.318273,"lng":-77.6493618,"mu":235.9,"sigma":56.9530996132393,"k":16,"is_paired":true},{"site_id":32,"site_name":"MFP Richford","lat":42.3551522,"lng":-76.2008963,"mu":265.9090909090909,"sigma":28.7974114998363,"k":18,"is_paired":true},{"site_id":33,"site_name":"MFP Saint Mary Recreation Center","lat":42.0988789,"lng":-75.9041391,"mu":148.3,"sigma":59.8313370140505,"k":11,"is_paired":false},{"site_id":34,"site_name":"MFP Salvation Army Ithaca","lat":42.4398066,"lng":-76.5019869,"mu":181.1818181818182,"sigma":39.5848915163808,"k":13,"is_paired":true},{"site_id":35,"site_name":"MFP Schuyler Outreach","lat":42.3770866,"lng":-76.8698425,"mu":138.8571428571429,"sigma":21.5362374571215,"k":10,"is_paired":true},{"site_id":36,"site_name":"MFP Senior - Addison Place Apartments","lat":42.1001876,"lng":-77.2371924,"mu":30.09090909090909,"sigma":5.02900676982135,"k":3,"is_paired":true},{"site_id":37,"site_name":"MFP Senior - Bragg","lat":42.0896598,"lng":-76.7977954,"mu":66.88888888888889,"sigma":6.03001750504186,"k":5,"is_paired":true},{"site_id":38,"site_name":"MFP Senior - Carpenter Apartments","lat":42.0930065,"lng":-76.7984317,"mu":31.09090909090909,"sigma":6.75950509215793,"k":3,"is_paired":true},{"site_id":39,"site_name":"MFP Senior - Cayuga Meadows","lat":42.464569,"lng":-76.5409393,"mu":25.88888888888889,"sigma":3.33333333333333,"k":3,"is_paired":true},{"site_id":40,"site_name":"MFP Senior - CFS Lakeview","lat":42.3488781,"lng":-77.3110763,"mu":112.0,"sigma":15.563490039905,"k":8,"is_paired":true},{"site_id":41,"site_name":"MFP Senior - Conifer Village","lat":42.4511294,"lng":-76.5323538,"mu":33.8,"sigma":10.3794669098819,"k":3,"is_paired":false},{"site_id":42,"site_name":"MFP Senior - Corning Senior Center","lat":42.1490255,"lng":-77.0619725,"mu":75.0,"sigma":30.9636884107821,"k":6,"is_paired":false},{"site_id":43,"site_name":"MFP Senior - Dayspring","lat":42.1420715,"lng":-77.0454685,"mu":77.18181818181819,"sigma":17.9489174148091,"k":6,"is_paired":true},{"site_id":44,"site_name":"MFP Senior - East Hill Senior Living","lat":42.1141777,"lng":-75.8720703,"mu":39.83333333333334,"sigma":2.16724933890169,"k":4,"is_paired":true},{"site_id":45,"site_name":"MFP Senior - Elizabeth Square, Waverly","lat":42.001546,"lng":-76.541203,"mu":29.0,"sigma":9.67658842962558,"k":3,"is_paired":false},{"site_id":46,"site_name":"MFP Senior - Ellis Hollow","lat":42.435955,"lng":-76.460374,"mu":24.72727272727273,"sigma":13.8064543536051,"k":3,"is_paired":false},{"site_id":47,"site_name":"MFP Senior - Flannery","lat":42.0819957,"lng":-76.8053269,"mu":61.72727272727273,"sigma":10.1103007778296,"k":5,"is_paired":true},{"site_id":48,"site_name":"MFP Senior - Harry L Apartments","lat":42.1237339,"lng":-75.9567565,"mu":32.83333333333334,"sigma":6.24232862533419,"k":3,"is_paired":true},{"site_id":49,"site_name":"MFP Senior - Jefferson Village","lat":42.3829923,"lng":-76.8713304,"mu":24.81818181818182,"sigma":2.78633026822672,"k":3,"is_paired":true},{"site_id":50,"site_name":"MFP Senior - Lincoln Court","lat":42.090217,"lng":-75.910198,"mu":26.0,"sigma":4.89897948556636,"k":3,"is_paired":true},{"site_id":51,"site_name":"MFP Senior - Long Meadow Senior Housing","lat":42.1135325,"lng":-76.2704045,"mu":34.75,"sigma":13.0043698949098,"k":3,"is_paired":false},{"site_id":52,"site_name":"MFP Senior - Metro Plaza Apartments","lat":42.1016977,"lng":-75.9085338,"mu":56.3,"sigma":19.9167712689024,"k":5,"is_paired":false},{"site_id":53,"site_name":"MFP Senior - North Shore Towers","lat":42.0966132,"lng":-75.9103884,"mu":58.33333333333334,"sigma":6.86051504383355,"k":5,"is_paired":true},{"site_id":54,"site_name":"MFP Senior - Northern Broome Senior Center, Whitney Point","lat":42.3269618,"lng":-75.9677778,"mu":50.90909090909091,"sigma":21.9565687002981,"k":4,"is_paired":false},{"site_id":55,"site_name":"MFP Senior - Park Terrace Congregate Apartments","lat":42.051364,"lng":-76.832954,"mu":24.36363636363636,"sigma":4.41073071662117,"k":3,"is_paired":true},{"site_id":56,"site_name":"MFP Senior - Springview Apartments","lat":42.0100399,"lng":-76.533895,"mu":27.58333333333333,"sigma":9.31722094084958,"k":3,"is_paired":false},{"site_id":57,"site_name":"MFP Senior - Titus Towers","lat":42.4317058,"lng":-76.504801,"mu":72.8,"sigma":4.31534728871525,"k":6,"is_paired":true},{"site_id":58,"site_name":"MFP Senior - Villa Serene","lat":42.1295916,"lng":-76.8158267,"mu":69.72727272727273,"sigma":5.0614406860282,"k":6,"is_paired":true},{"site_id":59,"site_name":"MFP Senior - Village Square/Manor","lat":42.159804,"lng":-77.09139,"mu":34.25,"sigma":6.48249390842192,"k":3,"is_paired":true},{"site_id":60,"site_name":"MFP Senior - Wells Apartments","lat":42.1076045,"lng":-75.9607932,"mu":23.5,"sigma":4.55216676124922,"k":3,"is_paired":false},{"site_id":61,"site_name":"MFP Senior - Woodsedge Apartments","lat":42.535151,"lng":-76.501086,"mu":17.2,"sigma":4.23739962188552,"k":2,"is_paired":true},{"site_id":62,"site_name":"MFP The Love Church","lat":42.1412225,"lng":-76.8137998,"mu":259.3333333333333,"sigma":59.8913157057824,"k":18,"is_paired":true},{"site_id":63,"site_name":"MFP Troupsburg","lat":42.0432505,"lng":-77.5456383,"mu":148.7142857142857,"sigma":50.2484304422015,"k":11,"is_paired":false},{"site_id":64,"site_name":"MFP Tuscarora","lat":42.0522655,"lng":-77.2737134,"mu":192.6363636363636,"sigma":29.8639338576576,"k":14,"is_paired":true},{"site_id":65,"site_name":"MFP Van Etten","lat":42.2080409,"lng":-76.579406,"mu":213.7,"sigma":19.9613515463145,"k":15,"is_paired":true},{"site_id":66,"site_name":"MFP Waverly","lat":42.0194466,"lng":-76.5236008,"mu":396.6363636363636,"sigma":51.8753751355548,"k":35,"is_paired":false},{"site_id":67,"site_name":"MFP Wayland","lat":42.5685797,"lng":-77.5957241,"mu":180.4166666666667,"sigma":35.2638324461179,"k":13,"is_paired":false},{"site_id":68,"site_name":"MFP Whitney Point","lat":42.3405329,"lng":-75.9765268,"mu":202.5454545454545,"sigma":30.4478690103713,"k":14,"is_paired":true},{"site_id":69,"site_name":"MFP Windsor","lat":42.0779615,"lng":-75.6427789,"mu":200.9090909090909,"sigma":34.8954281975578,"k":14,"is_paired":true},{"site_id":70,"site_name":"MFP Woodhull","lat":42.0798397,"lng":-77.4111295,"mu":176.0,"sigma":63.5438288256081,"k":12,"is_paired":false}];
+window.FIG1_LINKS = [{"site_i_id":1,"site_j_id":19,"site_i_name":"MFP American Legion - Binghamton","site_j_name":"MFP First Assembly Of God Church","distance":1.348436821527424},{"site_i_id":58,"site_j_id":62,"site_i_name":"MFP Senior - Villa Serene","site_j_name":"MFP The Love Church","distance":0.8092057380985171},{"site_i_id":11,"site_j_id":43,"site_i_name":"MFP College Corning Community College","site_j_name":"MFP Senior - Dayspring","distance":2.2349978091056},{"site_i_id":17,"site_j_id":53,"site_i_name":"MFP Endwell United Methodist Church","site_j_name":"MFP Senior - North Shore Towers","distance":5.804383712418112},{"site_i_id":15,"site_j_id":34,"site_i_name":"MFP Danby","site_j_name":"MFP Salvation Army Ithaca","distance":6.143925955556406},{"site_i_id":4,"site_j_id":23,"site_i_name":"MFP Beaver Dams","site_j_name":"MFP Millport","distance":6.074412981852127},{"site_i_id":5,"site_j_id":7,"site_i_name":"MFP Birnie Transportation Services","site_j_name":"MFP Bradford","distance":13.58110090990046},{"site_i_id":31,"site_j_id":40,"site_i_name":"MFP Rehoboth Deliverance Ministry","site_j_name":"MFP Senior - CFS Lakeview","distance":17.38378084014594},{"site_i_id":32,"site_j_id":57,"site_i_name":"MFP Richford","site_j_name":"MFP Senior - Titus Towers","distance":16.36265720561532},{"site_i_id":28,"site_j_id":36,"site_i_name":"MFP Rathbone","site_j_name":"MFP Senior - Addison Place Apartments","distance":4.720660032776843},{"site_i_id":25,"site_j_id":65,"site_i_name":"MFP Nichols-The Creamery","site_j_name":"MFP Van Etten","distance":16.61032388714411},{"site_i_id":8,"site_j_id":9,"site_i_name":"MFP Campbell","site_j_name":"MFP Canisteo","distance":21.23606041594513},{"site_i_id":2,"site_j_id":59,"site_i_name":"MFP Avoca","site_j_name":"MFP Senior - Village Square/Manor","distance":25.00335559264166},{"site_i_id":18,"site_j_id":35,"site_i_name":"MFP Erin","site_j_name":"MFP Schuyler Outreach","distance":16.40726185975893},{"site_i_id":14,"site_j_id":26,"site_i_name":"MFP Conklin- Maines Community Center","site_j_name":"MFP Owego VFW","distance":22.08036608633093},{"site_i_id":6,"site_j_id":44,"site_i_name":"MFP Boys and Girls Club","site_j_name":"MFP Senior - East Hill Senior Living","distance":2.595672199467672},{"site_i_id":22,"site_j_id":37,"site_i_name":"MFP Lindley","site_j_name":"MFP Senior - Bragg","distance":18.00067727329831},{"site_i_id":3,"site_j_id":49,"site_i_name":"MFP Bath","site_j_name":"MFP Senior - Jefferson Village","distance":22.97654515986252},{"site_i_id":29,"site_j_id":39,"site_i_name":"MFP Reach for Christ Church Freeville","site_j_name":"MFP Senior - Cayuga Meadows","distance":10.18287955207643},{"site_i_id":21,"site_j_id":61,"site_i_name":"MFP Lansing","site_j_name":"MFP Senior - Woodsedge Apartments","distance":1.168058001763275},{"site_i_id":48,"site_j_id":68,"site_i_name":"MFP Senior - Harry L Apartments","site_j_name":"MFP Whitney Point","distance":14.99319217055408},{"site_i_id":50,"site_j_id":69,"site_i_name":"MFP Senior - Lincoln Court","site_j_name":"MFP Windsor","distance":13.72039618876427},{"site_i_id":47,"site_j_id":64,"site_i_name":"MFP Senior - Flannery","site_j_name":"MFP Tuscarora","distance":24.07963686909654},{"site_i_id":38,"site_j_id":55,"site_i_name":"MFP Senior - Carpenter Apartments","site_j_name":"MFP Senior - Park Terrace Congregate Apartments","distance":3.373801467242223}];
diff --git a/task3/figures/fig1_pairing_map.png b/task3/figures/fig1_pairing_map.png
index a014b1b..1d7d6cc 100644
Binary files a/task3/figures/fig1_pairing_map.png and b/task3/figures/fig1_pairing_map.png differ
diff --git a/task3/figures/fig2_allocation_scatter.png b/task3/figures/fig2_allocation_scatter.png
index a5ee808..8e6ad1b 100644
Binary files a/task3/figures/fig2_allocation_scatter.png and b/task3/figures/fig2_allocation_scatter.png differ
diff --git a/task3/figures/fig3_sensitivity.png b/task3/figures/fig3_sensitivity.png
index 774dd75..54c15ed 100644
Binary files a/task3/figures/fig3_sensitivity.png and b/task3/figures/fig3_sensitivity.png differ
diff --git a/task3/figures/fig4_calendar_heatmap.png b/task3/figures/fig4_calendar_heatmap.png
index e43ed3e..02be6bd 100644
Binary files a/task3/figures/fig4_calendar_heatmap.png and b/task3/figures/fig4_calendar_heatmap.png differ
diff --git a/task3/figures/fig5_risk_distribution.png b/task3/figures/fig5_risk_distribution.png
index 3d5186a..0e82eae 100644
Binary files a/task3/figures/fig5_risk_distribution.png and b/task3/figures/fig5_risk_distribution.png differ