Files
MCM/3_oaward.py
2026-02-16 21:52:26 +08:00

100 lines
3.2 KiB
Python

"""
Figure: Battery SOC Trajectory under Different Usage Scenarios
MCM/ICM 2026 - Problem A
"""
import matplotlib.pyplot as plt
import numpy as np
# === MCM O-Award Style Configuration ===
plt.rcParams.update({
'font.family': 'serif',
'font.serif': ['Times New Roman'],
'mathtext.fontset': 'stix',
'axes.labelsize': 12,
'axes.titlesize': 14,
'xtick.labelsize': 11,
'ytick.labelsize': 11,
'legend.fontsize': 9,
'axes.linewidth': 1.0,
'axes.unicode_minus': False,
'figure.dpi': 300,
})
# === Data Configuration ===
scenarios = ['Gaming', 'Navigation', 'Movie', 'Chatting', 'Screen Off']
power_values = [3.551, 2.954, 2.235, 1.481, 0.517] # W
# Time-to-empty from different starting SOC [100%, 75%, 50%, 25%]
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]
z_min = 0.02 # SOC threshold (2%)
# Professional color palette (colorblind-friendly, Nature-style)
colors = ['#E64B35', '#4DBBD5', '#00A087', '#3C5488', '#F39B7F']
markers = ['o', 's', '^', 'D', 'v']
# === Figure Setup ===
fig, ax = plt.subplots(figsize=(8, 5))
# Mixed power-law model parameters
n1, n2, w1, w2 = 1.2, 5.0, 0.6, 0.4
for i, scenario in enumerate(scenarios):
t_end = data_matrix[i, 0] # Time from 100% to z_min
color = colors[i]
marker = markers[i]
# Mixed power-law SOC decay model
t = np.linspace(0, t_end, 200)
tau = t / t_end
shape_func = w1 * (tau ** n1) + w2 * (tau ** n2)
z = 100 - 98 * shape_func # 100% -> 2%
# Plot trajectory
label = f'{scenario} ({power_values[i]:.3f} W)'
ax.plot(t, z, color=color, linewidth=1.8, label=label, zorder=3)
# Add markers along curve
mark_indices = np.linspace(0, 199, 8, dtype=int)
ax.scatter(t[mark_indices], z[mark_indices], color=color, marker=marker,
s=35, edgecolors='white', linewidths=0.5, zorder=4)
# Threshold line
ax.axhline(y=z_min*100, color='#B71C1C', linestyle='--', linewidth=1.5,
label=f'Cutoff Threshold ({z_min*100:.0f}%)', zorder=2)
# === Axis Configuration ===
ax.set_xlabel('Time $t$ (hours)', fontweight='bold')
ax.set_ylabel('State of Charge $z(t)$ (%)', fontweight='bold')
ax.set_xlim(0, 32)
ax.set_ylim(0, 105)
ax.set_xticks(np.arange(0, 35, 5))
ax.set_yticks(np.arange(0, 120, 20))
# Grid styling
ax.grid(True, linestyle='-', alpha=0.3, linewidth=0.5, color='gray')
ax.set_axisbelow(True)
# Legend (outside plot area for clarity)
ax.legend(loc='upper right', frameon=True, fancybox=False,
edgecolor='black', framealpha=0.95, ncol=1)
# Minor ticks
ax.minorticks_on()
ax.tick_params(which='minor', length=2, width=0.5)
ax.tick_params(which='major', length=4, width=1.0)
# === Output ===
plt.tight_layout()
plt.savefig('combined_soc_trajectory.png', dpi=300, bbox_inches='tight',
facecolor='white', edgecolor='none')
plt.savefig('combined_soc_trajectory.pdf', bbox_inches='tight',
facecolor='white', edgecolor='none')
print("Figure saved: combined_soc_trajectory.png / .pdf")