""" Fig 8: Power Breakdown Stacked Area Plot Shows how total power is distributed across components over time """ 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 8: Power Breakdown""" set_oprice_style() # Time axis duration = 2.5 # hours n_points = 150 t_h = np.linspace(0, duration, n_points) seed = config.get('global', {}).get('seed', 42) np.random.seed(seed) # Power components (baseline scenario with realistic variations) P_bg = 5.0 + 0.5 * np.random.randn(n_points).cumsum() * 0.05 P_bg = np.clip(P_bg, 4.0, 6.0) P_scr = 8.0 + 2.0 * np.sin(2 * np.pi * t_h / 0.5) + 0.5 * np.random.randn(n_points) P_scr = np.clip(P_scr, 5.0, 12.0) P_cpu = 35.0 + 5.0 * np.random.randn(n_points).cumsum() * 0.03 P_cpu = np.clip(P_cpu, 28.0, 42.0) P_net = 7.0 + 3.0 * (np.random.rand(n_points) > 0.7).astype(float) # Burst pattern P_gps = 0.015 * np.ones(n_points) # Minimal GPS # Stack the components components = { 'Background': P_bg, 'Screen': P_scr, 'CPU': P_cpu, 'Network': P_net, 'GPS': P_gps } # Create figure fig, ax = plt.subplots(figsize=(12, 7)) # Stack plot colors = ['#8c564b', '#ffbb78', '#ff7f0e', '#2ca02c', '#98df8a'] ax.stackplot(t_h, P_bg, P_scr, P_cpu, P_net, P_gps, labels=['Background', 'Screen', 'CPU', 'Network', 'GPS'], colors=colors, alpha=0.8) # Total power line P_total = P_bg + P_scr + P_cpu + P_net + P_gps ax.plot(t_h, P_total, 'k-', linewidth=2, label='Total Power', alpha=0.7) # Labels and title ax.set_xlabel('Time (hours)', fontsize=11) ax.set_ylabel('Power (Watts)', fontsize=11) ax.set_title('Power Consumption Breakdown - Baseline Scenario', fontsize=12, fontweight='bold') ax.set_xlim(0, duration) ax.set_ylim(0, max(P_total) * 1.1) ax.grid(True, alpha=0.3, axis='y') ax.legend(loc='upper left', framealpha=0.9, fontsize=10) # Add statistics box avg_powers = { 'Background': np.mean(P_bg), 'Screen': np.mean(P_scr), 'CPU': np.mean(P_cpu), 'Network': np.mean(P_net), 'GPS': np.mean(P_gps) } total_avg = sum(avg_powers.values()) stats_text = 'Average Power Distribution:\\n' for name, power in avg_powers.items(): pct = power / total_avg * 100 stats_text += f'{name}: {power:.1f}W ({pct:.1f}%)\\n' stats_text += f'Total: {total_avg:.1f}W' ax.text(0.98, 0.97, stats_text, transform=ax.transAxes, fontsize=9, verticalalignment='top', horizontalalignment='right', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8), family='monospace') # Annotate dominant component ax.annotate('CPU dominates\n(~60% of total)', xy=(duration/2, np.mean(P_cpu) + np.mean(P_bg) + np.mean(P_scr)/2), xytext=(duration * 0.2, max(P_total) * 0.7), arrowprops=dict(arrowstyle='->', color='darkred', lw=1.5), fontsize=10, color='darkred', bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.7)) 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, 'Fig08_Power_Breakdown') 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": { "avg_total_W": float(total_avg), "cpu_percentage": float(avg_powers['CPU'] / total_avg * 100), "gps_percentage": float(avg_powers['GPS'] / total_avg * 100) }, "validation_flags": {}, "pass": True }