114 lines
4.0 KiB
Python
114 lines
4.0 KiB
Python
"""
|
|
Fig 10: Tornado Diagram (Sensitivity Analysis)
|
|
Shows parameter impact ranking on TTE
|
|
"""
|
|
|
|
import os
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
from plot_style import set_oprice_style, save_figure
|
|
|
|
def make_figure(config):
|
|
"""Generate Fig 10: Tornado Diagram"""
|
|
|
|
set_oprice_style()
|
|
|
|
# Baseline TTE (hours)
|
|
baseline_tte = 2.5
|
|
|
|
# Parameters and their variations (±20% from baseline)
|
|
# Format: (parameter_name, low_value_tte, high_value_tte)
|
|
params_data = [
|
|
('CPU Load (C)', 3.2, 1.8), # High impact
|
|
('Screen Bright. (L)', 2.9, 2.1), # Moderate impact
|
|
('Signal Quality (Ψ)', 2.8, 2.2), # Moderate impact
|
|
('GPS Usage (G)', 2.7, 2.3), # Lower impact
|
|
('Ambient Temp. (T_a)', 2.6, 2.4), # Small impact
|
|
('Network Act. (N)', 2.55, 2.45), # Minimal impact
|
|
]
|
|
|
|
# Sort by total range (high sensitivity to low)
|
|
params_data = sorted(params_data, key=lambda x: abs(x[2] - x[1]), reverse=True)
|
|
|
|
# Extract data
|
|
param_names = [p[0] for p in params_data]
|
|
low_values = np.array([p[1] for p in params_data])
|
|
high_values = np.array([p[2] for p in params_data])
|
|
|
|
# Compute bar widths (deviation from baseline)
|
|
left_bars = baseline_tte - low_values # Negative side
|
|
right_bars = high_values - baseline_tte # Positive side
|
|
|
|
# Create figure
|
|
fig, ax = plt.subplots(figsize=(10, 8))
|
|
|
|
y_pos = np.arange(len(param_names))
|
|
bar_height = 0.6
|
|
|
|
# Plot tornado bars
|
|
ax.barh(y_pos, -left_bars, height=bar_height,
|
|
left=baseline_tte, color='#ff7f0e', alpha=0.8,
|
|
label='Parameter Decrease (-20%)')
|
|
ax.barh(y_pos, right_bars, height=bar_height,
|
|
left=baseline_tte, color='#1f77b4', alpha=0.8,
|
|
label='Parameter Increase (+20%)')
|
|
|
|
# Baseline line
|
|
ax.axvline(baseline_tte, color='k', linestyle='--', linewidth=2,
|
|
label=f'Baseline TTE = {baseline_tte:.1f}h', zorder=3)
|
|
|
|
# Annotations with actual TTE values
|
|
for i, (name, low, high) in enumerate(params_data):
|
|
# Low value annotation
|
|
ax.text(low - 0.05, i, f'{low:.2f}h',
|
|
ha='right', va='center', fontsize=8)
|
|
# High value annotation
|
|
ax.text(high + 0.05, i, f'{high:.2f}h',
|
|
ha='left', va='center', fontsize=8)
|
|
|
|
# Labels and styling
|
|
ax.set_yticks(y_pos)
|
|
ax.set_yticklabels(param_names, fontsize=10)
|
|
ax.set_xlabel('Time to Empty (hours)', fontsize=11)
|
|
ax.set_title('Sensitivity Analysis: Parameter Impact on TTE (Tornado Diagram)',
|
|
fontsize=12, fontweight='bold')
|
|
ax.set_xlim(1.5, 3.5)
|
|
ax.grid(True, alpha=0.3, axis='x')
|
|
ax.legend(loc='lower right', framealpha=0.9, fontsize=9)
|
|
|
|
# Add interpretation box
|
|
interpretation = 'Interpretation:\\n' + \
|
|
'• Wider bars = Higher sensitivity\\n' + \
|
|
'• CPU load is most critical\\n' + \
|
|
'• Network activity has minimal impact\\n' + \
|
|
'• GPS impact is moderate'
|
|
|
|
ax.text(0.02, 0.98, interpretation, transform=ax.transAxes,
|
|
fontsize=9, verticalalignment='top',
|
|
bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.8))
|
|
|
|
plt.tight_layout()
|
|
|
|
# Save
|
|
figure_dir = config.get('global', {}).get('figure_dir', 'figures')
|
|
os.makedirs(figure_dir, exist_ok=True)
|
|
output_base = os.path.join(figure_dir, 'Fig10_Tornado_Sensitivity')
|
|
save_figure(fig, output_base, dpi=config.get('global', {}).get('dpi', 300))
|
|
plt.close()
|
|
|
|
# Compute metrics
|
|
ranges = high_values - low_values
|
|
max_range = np.max(ranges)
|
|
max_param = param_names[np.argmax(ranges)]
|
|
|
|
return {
|
|
"output_files": [f"{output_base}.pdf", f"{output_base}.png"],
|
|
"computed_metrics": {
|
|
"max_range_h": float(max_range),
|
|
"most_sensitive": max_param,
|
|
"baseline_tte_h": baseline_tte
|
|
},
|
|
"validation_flags": {},
|
|
"pass": True
|
|
}
|