143 lines
5.0 KiB
Python
143 lines
5.0 KiB
Python
"""
|
||
Fig 11: Two-Parameter Heatmap (Temperature × Signal Quality)
|
||
Shows interaction effects on TTE
|
||
"""
|
||
|
||
import os
|
||
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
from plot_style import set_oprice_style, save_figure
|
||
|
||
def compute_tte_surface(T_a_range, psi_range):
|
||
"""Compute TTE for grid of (T_a, psi) values"""
|
||
|
||
T_grid, psi_grid = np.meshgrid(T_a_range, psi_range)
|
||
tte_grid = np.zeros_like(T_grid)
|
||
|
||
# Battery capacity
|
||
Q_full = 2.78 # Ah
|
||
V_avg = 3.8 # V
|
||
E_total = Q_full * V_avg # Wh
|
||
|
||
# Baseline power components
|
||
P_bg = 5.0
|
||
P_scr = 8.0 * 0.3 # L = 0.3
|
||
P_cpu = 35.0 * 0.4 # C = 0.4
|
||
P_gps = 0.015 # Minimal
|
||
|
||
for i in range(len(psi_range)):
|
||
for j in range(len(T_a_range)):
|
||
T_a = T_grid[i, j]
|
||
psi = psi_grid[i, j]
|
||
|
||
# Network power depends on signal quality (worse signal = more power)
|
||
k_net_base = 7.0
|
||
k_net = k_net_base * (1 + 2 * (1 - psi))
|
||
P_net = k_net * 0.05 # N = 0.05
|
||
|
||
# Temperature affects efficiency (cold reduces capacity, hot increases resistance)
|
||
temp_factor = 1.0
|
||
if T_a < 10:
|
||
temp_factor = 0.8 + 0.02 * T_a # Reduced capacity in cold
|
||
elif T_a > 30:
|
||
temp_factor = 1.0 + 0.01 * (T_a - 30) # Increased losses in heat
|
||
|
||
P_total = (P_bg + P_scr + P_cpu + P_net + P_gps) * temp_factor
|
||
|
||
tte_grid[i, j] = E_total / P_total
|
||
|
||
return T_grid, psi_grid, tte_grid
|
||
|
||
def make_figure(config):
|
||
"""Generate Fig 11: Two-Parameter Heatmap"""
|
||
|
||
set_oprice_style()
|
||
|
||
# Parameter ranges
|
||
T_a_range = np.linspace(-10, 40, 50) # °C
|
||
psi_range = np.linspace(0.1, 1.0, 50) # Signal quality (0=poor, 1=excellent)
|
||
|
||
# Compute TTE surface
|
||
T_grid, psi_grid, tte_grid = compute_tte_surface(T_a_range, psi_range)
|
||
|
||
# Create figure
|
||
fig, ax = plt.subplots(figsize=(12, 9))
|
||
|
||
# Heatmap
|
||
im = ax.contourf(T_grid, psi_grid, tte_grid, levels=20, cmap='RdYlGn', alpha=0.9)
|
||
|
||
# Contour lines
|
||
contours = ax.contour(T_grid, psi_grid, tte_grid, levels=10, colors='k',
|
||
linewidths=0.5, alpha=0.3)
|
||
ax.clabel(contours, inline=True, fontsize=8, fmt='%.1f h')
|
||
|
||
# Colorbar
|
||
cbar = fig.colorbar(im, ax=ax, label='Time to Empty (hours)')
|
||
cbar.ax.tick_params(labelsize=9)
|
||
|
||
# Mark baseline condition
|
||
T_baseline = 25
|
||
psi_baseline = 0.8
|
||
ax.plot(T_baseline, psi_baseline, 'r*', markersize=20,
|
||
markeredgecolor='white', markeredgewidth=1.5,
|
||
label='Baseline Condition', zorder=5)
|
||
|
||
# Mark danger zones
|
||
# Cold + poor signal
|
||
ax.add_patch(plt.Rectangle((-10, 0.1), 15, 0.3,
|
||
fill=False, edgecolor='red', linewidth=2,
|
||
linestyle='--', label='Critical Zone'))
|
||
ax.text(-5, 0.25, 'Battery Killer\\n(Cold + Poor Signal)',
|
||
fontsize=9, color='darkred', ha='center',
|
||
bbox=dict(boxstyle='round', facecolor='white', alpha=0.7))
|
||
|
||
# Optimal zone
|
||
ax.add_patch(plt.Rectangle((15, 0.7), 15, 0.25,
|
||
fill=False, edgecolor='green', linewidth=2,
|
||
linestyle='--', label='Optimal Zone'))
|
||
ax.text(22.5, 0.825, 'Optimal\\n(Mild + Good Signal)',
|
||
fontsize=9, color='darkgreen', ha='center',
|
||
bbox=dict(boxstyle='round', facecolor='white', alpha=0.7))
|
||
|
||
# Labels and styling
|
||
ax.set_xlabel('Ambient Temperature (°C)', fontsize=11)
|
||
ax.set_ylabel('Signal Quality (Ψ)', fontsize=11)
|
||
ax.set_title('TTE Sensitivity to Temperature and Signal Quality (Interaction Effect)',
|
||
fontsize=12, fontweight='bold')
|
||
ax.set_xlim(-10, 40)
|
||
ax.set_ylim(0.1, 1.0)
|
||
ax.legend(loc='upper left', framealpha=0.9, fontsize=9)
|
||
|
||
# Add statistics
|
||
tte_min = np.min(tte_grid)
|
||
tte_max = np.max(tte_grid)
|
||
tte_range = tte_max - tte_min
|
||
|
||
stats_text = f'TTE Range:\\n' + \
|
||
f'Min: {tte_min:.2f}h\\n' + \
|
||
f'Max: {tte_max:.2f}h\\n' + \
|
||
f'Spread: {tte_range:.2f}h ({tte_range/tte_max*100:.1f}%)'
|
||
|
||
ax.text(0.98, 0.02, stats_text, transform=ax.transAxes,
|
||
fontsize=9, verticalalignment='bottom', horizontalalignment='right',
|
||
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8),
|
||
family='monospace')
|
||
|
||
# Save
|
||
figure_dir = config.get('global', {}).get('figure_dir', 'figures')
|
||
os.makedirs(figure_dir, exist_ok=True)
|
||
output_base = os.path.join(figure_dir, 'Fig11_Heatmap_Temp_Signal')
|
||
save_figure(fig, output_base, dpi=config.get('global', {}).get('dpi', 300))
|
||
plt.close()
|
||
|
||
return {
|
||
"output_files": [f"{output_base}.pdf", f"{output_base}.png"],
|
||
"computed_metrics": {
|
||
"tte_min_h": float(tte_min),
|
||
"tte_max_h": float(tte_max),
|
||
"tte_range_h": float(tte_range)
|
||
},
|
||
"validation_flags": {},
|
||
"pass": True
|
||
}
|