import matplotlib.pyplot as plt import numpy as np import pandas as pd # ============================================================ # Problem 2 Complete Analysis - MCM O-Award Standard # ============================================================ plt.rcParams['font.family'] = 'Times New Roman' plt.rcParams['mathtext.fontset'] = 'stix' plt.rcParams['axes.unicode_minus'] = False # ========================= # 1. Data Preparation # ========================= scenarios = ['Gaming', 'Navigation', 'Movie', 'Chatting', 'Screen Off'] power_W = [3.551, 2.954, 2.235, 1.481, 0.517] # Average power consumption # Time-to-empty data (hours) for different start SOC data_matrix = np.array([ [4.11, 3.05, 2.01, 0.97], # Gaming [5.01, 3.72, 2.45, 1.18], # Navigation [6.63, 4.92, 3.24, 1.56], # Movie [10.02, 7.43, 4.89, 2.36], # Chatting [29.45, 21.85, 14.39, 6.95] # Screen Off ]) start_soc = [100, 75, 50, 25] # ========================= # 2. Model Prediction vs Observation # ========================= # Assuming linear discharge model: T = (SOC - z_min) * C / P # where C = battery capacity (Wh), z_min = 2% # Estimate battery capacity from Screen Off data (most stable) # T_screen_off_100 = (100 - 2) * C / 0.517 => C = T * P / 98 C_estimated = 29.45 * 0.517 / 0.98 # ≈ 15.53 Wh # Predicted time-to-empty def predict_tte(start_soc, power, capacity=15.53, z_min=2): """Predict time-to-empty based on linear model""" return (start_soc - z_min) * capacity / power / 100 predicted_matrix = np.zeros_like(data_matrix) for i, p in enumerate(power_W): for j, soc in enumerate(start_soc): predicted_matrix[i, j] = predict_tte(soc, p) # ========================= # 3. Error Analysis & Uncertainty Quantification # ========================= error_matrix = data_matrix - predicted_matrix relative_error = error_matrix / data_matrix * 100 # Percentage error # Calculate RMSE and MAE for each scenario rmse_per_scenario = np.sqrt(np.mean(error_matrix**2, axis=1)) mae_per_scenario = np.mean(np.abs(error_matrix), axis=1) print("=" * 60) print("PROBLEM 2: TIME-TO-EMPTY PREDICTION ANALYSIS") print("=" * 60) # ========================= # Table 1: Prediction vs Observation # ========================= print("\n[Table 1] Time-to-Empty Predictions vs Observations (hours)") print("-" * 70) print(f"{'Scenario':<12} | {'SOC=100%':>10} | {'SOC=75%':>10} | {'SOC=50%':>10} | {'SOC=25%':>10}") print("-" * 70) for i, s in enumerate(scenarios): obs = ' / '.join([f"{data_matrix[i,j]:.2f}" for j in range(4)]) pred = ' / '.join([f"{predicted_matrix[i,j]:.2f}" for j in range(4)]) print(f"{s:<12} | Obs: {data_matrix[i,0]:>5.2f} | {data_matrix[i,1]:>10.2f} | {data_matrix[i,2]:>10.2f} | {data_matrix[i,3]:>10.2f}") print(f"{'':12} | Pred: {predicted_matrix[i,0]:>5.2f} | {predicted_matrix[i,1]:>10.2f} | {predicted_matrix[i,2]:>10.2f} | {predicted_matrix[i,3]:>10.2f}") print("-" * 70) # ========================= # Table 2: Model Performance Metrics # ========================= print("\n[Table 2] Model Performance by Scenario") print("-" * 55) print(f"{'Scenario':<12} | {'Power(W)':>8} | {'RMSE(h)':>8} | {'MAE(h)':>8} | {'Status':>10}") print("-" * 55) for i, s in enumerate(scenarios): status = "Good" if rmse_per_scenario[i] < 0.5 else ("Fair" if rmse_per_scenario[i] < 1.0 else "Poor") print(f"{s:<12} | {power_W[i]:>8.3f} | {rmse_per_scenario[i]:>8.3f} | {mae_per_scenario[i]:>8.3f} | {status:>10}") print("-" * 55) # ========================= # 4. Uncertainty Quantification (95% CI) # ========================= print("\n[Table 3] Uncertainty Quantification (95% Confidence Interval)") print("-" * 60) # Assume 5% measurement uncertainty in power + 3% in capacity power_uncertainty = 0.05 capacity_uncertainty = 0.03 total_uncertainty = np.sqrt(power_uncertainty**2 + capacity_uncertainty**2) # ~5.83% for i, s in enumerate(scenarios): t_100 = data_matrix[i, 0] ci_lower = t_100 * (1 - 1.96 * total_uncertainty) ci_upper = t_100 * (1 + 1.96 * total_uncertainty) print(f"{s:<12}: T_100% = {t_100:.2f}h, 95% CI = [{ci_lower:.2f}, {ci_upper:.2f}]h") print("-" * 60) # ========================= # 5. Drivers of Rapid Battery Drain # ========================= print("\n[Analysis 1] Drivers of Rapid Battery Drain") print("=" * 60) # Rank by power consumption sorted_idx = np.argsort(power_W)[::-1] print("\nRanking by Power Consumption (Greatest to Least Impact):") print("-" * 50) for rank, i in enumerate(sorted_idx, 1): drain_rate = power_W[i] / C_estimated * 100 # %/hour time_factor = data_matrix[4, 0] / data_matrix[i, 0] # Relative to Screen Off print(f" {rank}. {scenarios[i]:<12}: {power_W[i]:.3f}W ({drain_rate:.1f}%/h)") print(f" → {time_factor:.1f}x faster drain than Screen Off") print("-" * 50) # Key drivers identification print("\nKey Drivers of Rapid Drain:") print(" • Gaming: GPU rendering + high CPU usage + screen brightness") print(" • Navigation: GPS module + continuous screen + network") print(" • Movie: Video decoding + screen backlight + audio") # ========================= # 6. Activities with Surprisingly Little Impact # ========================= print("\n[Analysis 2] Activities with Surprisingly Little Model Change") print("=" * 60) # Compare model sensitivity base_time = data_matrix[4, 0] # Screen Off as baseline for i, s in enumerate(scenarios): reduction = (base_time - data_matrix[i, 0]) / base_time * 100 expected = (power_W[i] - power_W[4]) / power_W[4] * 100 surprise = abs(reduction - expected) / expected * 100 if expected > 0 else 0 if i < 4: # Not Screen Off itself if surprise < 20: verdict = "As Expected" elif reduction < expected * 0.8: verdict = "Surprisingly Small Impact" else: verdict = "Surprisingly Large Impact" print(f"{s:<12}: {reduction:>5.1f}% reduction (Expected ~{expected:.0f}% based on power)") print(f" → {verdict}") print("\n" + "-" * 60) print("Conclusion on 'Surprisingly Little' Impact:") print(" • Chatting: Low active screen time → power dominated by idle") print(" • The OLED display scaling means text-based apps consume") print(" surprisingly little extra power compared to screen off") print(" • Background tasks (OS overhead) create a 'floor' effect") print("-" * 60) # ========================= # 7. Visualization: Error Analysis Figure # ========================= fig, axes = plt.subplots(1, 3, figsize=(14, 4.5), dpi=300) # Nature-style colors colors = ['#E64B35', '#4DBBD5', '#00A087', '#3C5488', '#F39B7F'] # (a) Prediction vs Observation scatter ax1 = axes[0] for i, s in enumerate(scenarios): ax1.scatter(data_matrix[i, :], predicted_matrix[i, :], c=colors[i], s=60, label=s, alpha=0.8, edgecolors='black', linewidth=0.5) max_val = max(data_matrix.max(), predicted_matrix.max()) ax1.plot([0, max_val*1.05], [0, max_val*1.05], 'k--', linewidth=1.5, label='Perfect Fit') ax1.set_xlabel("Observed Time-to-Empty (h)", fontsize=11, fontweight='bold') ax1.set_ylabel("Predicted Time-to-Empty (h)", fontsize=11, fontweight='bold') ax1.legend(fontsize=8, loc='lower right') ax1.set_xlim(0, max_val*1.05) ax1.set_ylim(0, max_val*1.05) ax1.text(0.05, 0.95, '(a)', transform=ax1.transAxes, fontsize=12, fontweight='bold', va='top') ax1.grid(True, alpha=0.3) # (b) Relative Error by Scenario ax2 = axes[1] x_pos = np.arange(len(scenarios)) mean_rel_error = np.mean(np.abs(relative_error), axis=1) std_rel_error = np.std(np.abs(relative_error), axis=1) bars = ax2.bar(x_pos, mean_rel_error, yerr=std_rel_error, capsize=4, color=colors, edgecolor='black', linewidth=1) ax2.set_xticks(x_pos) ax2.set_xticklabels(scenarios, rotation=30, ha='right', fontsize=9) ax2.set_ylabel("Mean Absolute Relative Error (%)", fontsize=11, fontweight='bold') ax2.axhline(y=10, color='red', linestyle='--', linewidth=1.5, label='10% Threshold') ax2.legend(fontsize=9) ax2.text(0.05, 0.95, '(b)', transform=ax2.transAxes, fontsize=12, fontweight='bold', va='top') ax2.grid(True, alpha=0.3, axis='y') # (c) Power vs Battery Life (inverse relationship) ax3 = axes[2] ax3.scatter(power_W, data_matrix[:, 0], c=colors, s=100, edgecolors='black', linewidth=1, zorder=5) for i, s in enumerate(scenarios): ax3.annotate(s, (power_W[i], data_matrix[i, 0]), textcoords="offset points", xytext=(5, 5), fontsize=8) # Fit inverse curve: T = k / P k_fit = np.mean(np.array(power_W) * data_matrix[:, 0]) p_range = np.linspace(0.3, 4, 100) t_fit = k_fit / p_range ax3.plot(p_range, t_fit, 'k--', linewidth=1.5, label=f'$T = {k_fit:.1f}/P$') ax3.set_xlabel("Power Consumption (W)", fontsize=11, fontweight='bold') ax3.set_ylabel("Time-to-Empty from 100% (h)", fontsize=11, fontweight='bold') ax3.legend(fontsize=9) ax3.text(0.05, 0.95, '(c)', transform=ax3.transAxes, fontsize=12, fontweight='bold', va='top') ax3.grid(True, alpha=0.3) ax3.set_xlim(0, 4.5) ax3.set_ylim(0, 35) plt.tight_layout() plt.savefig('problem2_analysis.png', dpi=300, bbox_inches='tight') plt.savefig('problem2_analysis.pdf', bbox_inches='tight') print("\n[Figure saved: problem2_analysis.png/pdf]") print("\n" + "=" * 60) print("ANALYSIS COMPLETE") print("=" * 60)