Initial commit
This commit is contained in:
99
3_oaward.py
Normal file
99
3_oaward.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user