p3: figfix

This commit is contained in:
2026-02-03 02:23:35 +08:00
parent 03110c6f66
commit 52fb527c0e
4 changed files with 117 additions and 78 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -806,7 +806,7 @@ def sensitivity_buffer_days(baseline: BaselineParameters, save_dir: str):
def tornado_analysis(baseline: BaselineParameters, save_dir: str): def tornado_analysis(baseline: BaselineParameters, save_dir: str):
""" """
Tornado图 - 多参数灵敏度排序分析 Tornado图 - 多参数灵敏度排序分析(马卡龙色系简约版)
""" """
print("生成Tornado图...") print("生成Tornado图...")
@@ -825,7 +825,7 @@ def tornado_analysis(baseline: BaselineParameters, save_dir: str):
baseline_annual = baseline_demand['annual_supply_tons'] baseline_annual = baseline_demand['annual_supply_tons']
baseline_initial = baseline_demand['initial_transport_tons'] baseline_initial = baseline_demand['initial_transport_tons']
# 定义参数范围 # 定义参数范围 - 根据论述调整删除Confidence Level
params = [ params = [
('Recycle Rate (η)', 'recycle_rate', 0.80, 0.95, baseline.recycle_rate), ('Recycle Rate (η)', 'recycle_rate', 0.80, 0.95, baseline.recycle_rate),
('Comfort Factor (α)', 'comfort_factor', 25, 100, baseline.comfort_factor), ('Comfort Factor (α)', 'comfort_factor', 25, 100, baseline.comfort_factor),
@@ -833,7 +833,6 @@ def tornado_analysis(baseline: BaselineParameters, save_dir: str):
('Sickness Rate', 'sickness_rate', 0.01, 0.04, baseline.sickness_rate), ('Sickness Rate', 'sickness_rate', 0.01, 0.04, baseline.sickness_rate),
('Medical Water/Event', 'medical_water', 3, 10, baseline.medical_water_per_event), ('Medical Water/Event', 'medical_water', 3, 10, baseline.medical_water_per_event),
('Buffer Days', 'buffer_days', 15, 45, baseline.safety_buffer_days), ('Buffer Days', 'buffer_days', 15, 45, baseline.safety_buffer_days),
('Confidence Level', 'confidence', 0.95, 0.999, baseline.confidence_level),
] ]
results_annual = [] results_annual = []
@@ -883,13 +882,6 @@ def tornado_analysis(baseline: BaselineParameters, save_dir: str):
demand_high = calculate_water_demand(baseline.population, baseline.survival_water, baseline.hygiene_water_base, demand_high = calculate_water_demand(baseline.population, baseline.survival_water, baseline.hygiene_water_base,
baseline.comfort_factor, baseline.medical_water_per_event, baseline.sickness_rate, baseline.comfort_factor, baseline.medical_water_per_event, baseline.sickness_rate,
baseline.confidence_level, baseline.recycle_rate, int(high)) baseline.confidence_level, baseline.recycle_rate, int(high))
elif param_type == 'confidence':
demand_low = calculate_water_demand(baseline.population, baseline.survival_water, baseline.hygiene_water_base,
baseline.comfort_factor, baseline.medical_water_per_event, baseline.sickness_rate,
low, baseline.recycle_rate, baseline.safety_buffer_days)
demand_high = calculate_water_demand(baseline.population, baseline.survival_water, baseline.hygiene_water_base,
baseline.comfort_factor, baseline.medical_water_per_event, baseline.sickness_rate,
high, baseline.recycle_rate, baseline.safety_buffer_days)
# 计算变化百分比 # 计算变化百分比
change_low_annual = (demand_low['annual_supply_tons'] - baseline_annual) / baseline_annual * 100 change_low_annual = (demand_low['annual_supply_tons'] - baseline_annual) / baseline_annual * 100
@@ -914,76 +906,109 @@ def tornado_analysis(baseline: BaselineParameters, save_dir: str):
'swing': swing_initial 'swing': swing_initial
}) })
# 按swing排序 # 按年度需求的swing排序(统一排序顺序)
results_annual = sorted(results_annual, key=lambda x: x['swing'], reverse=True) results_annual = sorted(results_annual, key=lambda x: x['swing'], reverse=True)
results_initial = sorted(results_initial, key=lambda x: x['swing'], reverse=True)
# 绘图 # 按相同顺序重排initial结果
fig, axes = plt.subplots(1, 2, figsize=(16, 8)) param_order = [r['param'] for r in results_annual]
results_initial_dict = {r['param']: r for r in results_initial}
results_initial = [results_initial_dict[p] for p in param_order]
# 图1: 年补充量Tornado # 马卡龙色系
ax = axes[0] color_decrease = '#A8D8B9' # 薄荷绿 - 减少
y_pos = np.arange(len(results_annual)) color_increase = '#F7C5A8' # 杏橙 - 增加
color_decrease_dark = '#7BC08F'
color_increase_dark = '#E8A07A'
# 绘图 - 简约风格(缩小尺寸)
fig, axes = plt.subplots(1, 2, figsize=(9, 3.5))
fig.patch.set_facecolor('#FAFAFA')
# 统一的参数标签
params_sorted = [r['param'] for r in results_annual] params_sorted = [r['param'] for r in results_annual]
y_pos = np.arange(len(params_sorted))
# ========== 图1: 年补充量Tornado ==========
ax = axes[0]
ax.set_facecolor('#FAFAFA')
lows = [r['low'] for r in results_annual] lows = [r['low'] for r in results_annual]
highs = [r['high'] for r in results_annual] highs = [r['high'] for r in results_annual]
# 绘制条形图 # 绘制条形图 - 分开绘制正负部分
for i, (l, h) in enumerate(zip(lows, highs)):
# 负值部分(减少)
if l < 0:
ax.barh(i, l, color=color_decrease, edgecolor=color_decrease_dark,
linewidth=1, height=0.65)
# 正值部分(增加)
if h > 0:
ax.barh(i, h, color=color_increase, edgecolor=color_increase_dark,
linewidth=1, height=0.65)
# 添加数值标注
for i, (l, h) in enumerate(zip(lows, highs)): for i, (l, h) in enumerate(zip(lows, highs)):
if l < 0: if l < 0:
ax.barh(i, l, color='green', alpha=0.7, height=0.6) ax.text(l - 2, i, f'{l:.0f}%', ha='right', va='center', fontsize=8, color='#555555')
if h > 0: if h > 0:
ax.barh(i, h, color='red', alpha=0.7, height=0.6) ax.text(h + 2, i, f'+{h:.0f}%', ha='left', va='center', fontsize=8, color='#555555')
if l >= 0:
ax.barh(i, l, left=0, color='green', alpha=0.7, height=0.6)
if h <= 0:
ax.barh(i, h, left=0, color='red', alpha=0.7, height=0.6)
# 如果跨越0绘制完整范围
if l < 0 < h:
pass # 已经绘制
elif l >= 0:
ax.barh(i, h - l, left=l, color='orange', alpha=0.7, height=0.6)
elif h <= 0:
ax.barh(i, h - l, left=l, color='orange', alpha=0.7, height=0.6)
ax.axvline(x=0, color='black', linewidth=1.5) ax.axvline(x=0, color='#333333', linewidth=1.2)
ax.set_yticks(y_pos) ax.set_yticks(y_pos)
ax.set_yticklabels(params_sorted) ax.set_yticklabels(params_sorted, fontsize=10)
ax.set_xlabel('Change in Annual Supply (%)', fontsize=12) ax.set_xlabel('Change in Annual Supply (%)', fontsize=11, color='#444444')
ax.set_title('Tornado Diagram: Annual Water Supply', fontsize=14) ax.spines['top'].set_visible(False)
ax.grid(True, alpha=0.3, axis='x') ax.spines['right'].set_visible(False)
ax.spines['left'].set_color('#CCCCCC')
ax.spines['bottom'].set_color('#CCCCCC')
ax.grid(True, alpha=0.15, axis='x', linestyle='--')
ax.set_xlim(-65, 115)
# 图2: 首批运输量Tornado # ========== 图2: 首批运输量Tornado ==========
ax = axes[1] ax = axes[1]
params_sorted = [r['param'] for r in results_initial] ax.set_facecolor('#FAFAFA')
lows = [r['low'] for r in results_initial] lows = [r['low'] for r in results_initial]
highs = [r['high'] for r in results_initial] highs = [r['high'] for r in results_initial]
for i, (l, h) in enumerate(zip(lows, highs)): for i, (l, h) in enumerate(zip(lows, highs)):
width = h - l # 负值部分(减少)
left = l if l < 0:
color = 'steelblue' if width > 0 else 'gray' ax.barh(i, l, color=color_decrease, edgecolor=color_decrease_dark,
ax.barh(i, width, left=left, color=color, alpha=0.7, height=0.6) linewidth=1, height=0.65)
# 正值部分(增加)
if h > 0:
ax.barh(i, h, color=color_increase, edgecolor=color_increase_dark,
linewidth=1, height=0.65)
ax.axvline(x=0, color='black', linewidth=1.5) # 添加数值标注
for i, (l, h) in enumerate(zip(lows, highs)):
if l < 0:
ax.text(l - 2, i, f'{l:.0f}%', ha='right', va='center', fontsize=8, color='#555555')
if h > 0:
ax.text(h + 2, i, f'+{h:.0f}%', ha='left', va='center', fontsize=8, color='#555555')
ax.axvline(x=0, color='#333333', linewidth=1.2)
ax.set_yticks(y_pos) ax.set_yticks(y_pos)
ax.set_yticklabels(params_sorted) ax.set_yticklabels([]) # 删除右图纵坐标标签
ax.set_xlabel('Change in Initial Transport (%)', fontsize=12) ax.set_xlabel('Change in Initial Transport (%)', fontsize=11, color='#444444')
ax.set_title('Tornado Diagram: Initial Transport Volume', fontsize=14) ax.spines['top'].set_visible(False)
ax.grid(True, alpha=0.3, axis='x') ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False) # 隐藏左边框
ax.spines['bottom'].set_color('#CCCCCC')
ax.grid(True, alpha=0.15, axis='x', linestyle='--')
ax.set_xlim(-65, 105)
# 添加图例说明 # 简化图例 - 放在图的下方中间
from matplotlib.patches import Patch from matplotlib.patches import Patch
legend_elements = [ legend_elements = [
Patch(facecolor='green', alpha=0.7, label='Decrease'), Patch(facecolor=color_decrease, edgecolor=color_decrease_dark, label='Decrease'),
Patch(facecolor='red', alpha=0.7, label='Increase'), Patch(facecolor=color_increase, edgecolor=color_increase_dark, label='Increase'),
] ]
axes[0].legend(handles=legend_elements, loc='lower right') fig.legend(handles=legend_elements, loc='lower center', ncol=2, fontsize=9,
framealpha=0.9, edgecolor='#CCCCCC', bbox_to_anchor=(0.55, -0.02))
plt.suptitle('Multi-Parameter Sensitivity Analysis (Tornado Diagram)\nBaseline: α=50, η=90%, N=100k',
fontsize=14, y=1.02)
plt.tight_layout() plt.tight_layout()
plt.savefig(f'{save_dir}/tornado_analysis.png', dpi=150, bbox_inches='tight') plt.subplots_adjust(bottom=0.15)
plt.savefig(f'{save_dir}/tornado_analysis.png', dpi=150, bbox_inches='tight', facecolor='#FAFAFA')
print(f" 保存: {save_dir}/tornado_analysis.png") print(f" 保存: {save_dir}/tornado_analysis.png")
return results_annual, results_initial return results_annual, results_initial
@@ -991,10 +1016,12 @@ def tornado_analysis(baseline: BaselineParameters, save_dir: str):
def interaction_heatmap(baseline: BaselineParameters, save_dir: str): def interaction_heatmap(baseline: BaselineParameters, save_dir: str):
""" """
双参数交互热力图 - α vs η 双参数交互热力图 - α vs η(马卡龙色系简约版)
""" """
print("生成双参数交互热力图...") print("生成双参数交互热力图...")
from matplotlib.colors import LinearSegmentedColormap
alphas = np.array([1, 25, 50, 100, 150, 200, 250]) alphas = np.array([1, 25, 50, 100, 150, 200, 250])
etas = np.array([0.70, 0.75, 0.80, 0.85, 0.90, 0.95]) etas = np.array([0.70, 0.75, 0.80, 0.85, 0.90, 0.95])
@@ -1019,52 +1046,64 @@ def interaction_heatmap(baseline: BaselineParameters, save_dir: str):
annual_matrix[i, j] = demand['annual_supply_tons'] / 1000 # 千吨 annual_matrix[i, j] = demand['annual_supply_tons'] / 1000 # 千吨
capacity_matrix[i, j] = demand['annual_supply_tons'] / baseline.total_elevator_capacity * 100 capacity_matrix[i, j] = demand['annual_supply_tons'] / baseline.total_elevator_capacity * 100
fig, axes = plt.subplots(1, 2, figsize=(16, 7)) # 马卡龙色系(中等饱和度)
# 图1: 淡蓝→蓝紫→玫粉(年补充量)
colors_annual = ['#C8E6F0', '#A8D0E8', '#98B8D8', '#B898C8', '#D098B0', '#D88898', '#C86878']
cmap_annual = LinearSegmentedColormap.from_list('macaron_annual', colors_annual)
# 图2: 薄荷绿→淡黄→杏橙→珊瑚红(运力占比)
colors_capacity = ['#A8D8B8', '#C8E0A0', '#E0D890', '#E8C078', '#E8A070', '#D88070', '#C86868']
cmap_capacity = LinearSegmentedColormap.from_list('macaron_capacity', colors_capacity)
# 缩小图片尺寸
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
fig.patch.set_facecolor('#FAFAFA')
# 图1: 年补充量热力图 # 图1: 年补充量热力图
ax = axes[0] ax = axes[0]
im = ax.imshow(annual_matrix, cmap='YlOrRd', aspect='auto') ax.set_facecolor('#FAFAFA')
im = ax.imshow(annual_matrix, cmap=cmap_annual, aspect='auto')
ax.set_xticks(range(len(alphas))) ax.set_xticks(range(len(alphas)))
ax.set_xticklabels([f'{a}' for a in alphas]) ax.set_xticklabels([f'{a}' for a in alphas], fontsize=9)
ax.set_yticks(range(len(etas))) ax.set_yticks(range(len(etas)))
ax.set_yticklabels([f'{e*100:.0f}%' for e in etas]) ax.set_yticklabels([f'{e*100:.0f}%' for e in etas], fontsize=9)
ax.set_xlabel('Comfort Factor (α)', fontsize=12) ax.set_xlabel('Comfort Factor (α)', fontsize=10, color='#444444')
ax.set_ylabel('Recycle Rate (η)', fontsize=12) ax.set_ylabel('Recycle Rate (η)', fontsize=10, color='#444444')
ax.set_title('Annual Water Supply (kt)', fontsize=13)
# 添加数值标注 # 添加数值标注
for i in range(len(etas)): for i in range(len(etas)):
for j in range(len(alphas)): for j in range(len(alphas)):
text_color = 'white' if annual_matrix[i, j] > annual_matrix.max() * 0.6 else 'black' ax.text(j, i, f'{annual_matrix[i, j]:.0f}', ha='center', va='center',
ax.text(j, i, f'{annual_matrix[i, j]:.0f}', ha='center', va='center', fontsize=9, color=text_color) fontsize=8, color='#333333', fontweight='medium')
plt.colorbar(im, ax=ax, label='Annual Supply (kt)') cbar1 = plt.colorbar(im, ax=ax, shrink=0.8)
cbar1.ax.tick_params(labelsize=8)
cbar1.set_label('Annual Supply (kt)', fontsize=9, color='#444444')
# 图2: 运力占比热力图 # 图2: 运力占比热力图
ax = axes[1] ax = axes[1]
im = ax.imshow(capacity_matrix, cmap='RdYlGn_r', aspect='auto', vmin=0, vmax=150) ax.set_facecolor('#FAFAFA')
im = ax.imshow(capacity_matrix, cmap=cmap_capacity, aspect='auto', vmin=0, vmax=150)
ax.set_xticks(range(len(alphas))) ax.set_xticks(range(len(alphas)))
ax.set_xticklabels([f'{a}' for a in alphas]) ax.set_xticklabels([f'{a}' for a in alphas], fontsize=9)
ax.set_yticks(range(len(etas))) ax.set_yticks(range(len(etas)))
ax.set_yticklabels([f'{e*100:.0f}%' for e in etas]) ax.set_yticklabels([f'{e*100:.0f}%' for e in etas], fontsize=9)
ax.set_xlabel('Comfort Factor (α)', fontsize=12) ax.set_xlabel('Comfort Factor (α)', fontsize=10, color='#444444')
ax.set_ylabel('Recycle Rate (η)', fontsize=12) ax.set_ylabel('Recycle Rate (η)', fontsize=10, color='#444444')
ax.set_title('Elevator Capacity Usage (%)', fontsize=13)
# 添加数值标注 # 添加数值标注
for i in range(len(etas)): for i in range(len(etas)):
for j in range(len(alphas)): for j in range(len(alphas)):
val = capacity_matrix[i, j] val = capacity_matrix[i, j]
text_color = 'white' if val > 60 else 'black' ax.text(j, i, f'{val:.0f}%', ha='center', va='center',
ax.text(j, i, f'{val:.0f}%', ha='center', va='center', fontsize=9, color=text_color) fontsize=8, color='#333333', fontweight='medium')
# 添加100%等高线 cbar2 = plt.colorbar(im, ax=ax, shrink=0.8)
cbar = plt.colorbar(im, ax=ax, label='Capacity Usage (%)') cbar2.ax.tick_params(labelsize=8)
cbar2.set_label('Capacity Usage (%)', fontsize=9, color='#444444')
plt.suptitle('Two-Parameter Interaction Analysis: α vs η\n(N=100,000, Buffer=30 days)',
fontsize=14, y=1.02)
plt.tight_layout() plt.tight_layout()
plt.savefig(f'{save_dir}/interaction_heatmap.png', dpi=150, bbox_inches='tight') plt.savefig(f'{save_dir}/interaction_heatmap.png', dpi=150, bbox_inches='tight', facecolor='#FAFAFA')
print(f" 保存: {save_dir}/interaction_heatmap.png") print(f" 保存: {save_dir}/interaction_heatmap.png")
return annual_matrix, capacity_matrix return annual_matrix, capacity_matrix