충전 최적화와 제어
전기차 사용자가 원하는 것은 짧은 충전 시간이고, 제조사가 보증해야 하는 것은 긴 수명과 안전이다. 충전 전류를 키우면 시간은 줄지만 셀 온도가 오르고, 음극 표면에 금속 리튬이 석출(lithium plating)될 위험이 커지며, 열화가 빨라진다. 충전 프로토콜 최적화는 이 세 가지를 동시에 다루는 문제다. 그리고 열화를 재려면 셀을 수백 사이클 돌려야 하므로, 평가 한 번이 몇 주씩 걸리는 비싼 최적화 문제이기도 하다.
이 장은 충전 프로토콜을 최적화 문제로 정의하고, 다목적 trade-off를 Pareto front로 보고, 안전 제약 아래에서 베이지안 최적화로 다단계 프로토콜을 찾는다. 계산은 30.5장의 1-RC 등가회로 모델에 집중 열 모델을 붙인 가상의 시뮬레이터로 하고, 열화는 설명을 위해 만든 가상의 대리 지표로 잰다. 그래서 이 장의 숫자는 방법을 보이기 위한 것이지 실제 셀의 최적 프로토콜이 아니다. 시뮬레이터의 최적해를 실제 셀에서 검증하는 일이 왜 따로 필요한지가 이 장의 마지막 주제다.
학습 목표
- 충전 프로토콜 최적화 문제를 결정변수·목적·제약으로 정의한다
- 안전 제약을 포함한 다목적·베이지안 최적화를 수행하고 Pareto front로 보고한다
- 시뮬레이터 최적해를 실제 셀에서 검증하는 실험을 설계한다
문제 정의
CC-CV. 리튬이온 셀의 표준 충전 방식은 CC-CV(constant current–constant voltage)다. 정해진 전류로 충전하다가(CC) 단자 전압이 상한(예: 4.2 V)에 닿으면, 전압을 상한에 고정하고 전류가 저절로 줄어들게 둔다(CV). 전류가 정해진 값(예: C/20)까지 줄면 끝낸다. 전류의 단위로 쓰는 C-rate는 정격 용량을 1시간에 채우는 전류를 1C로 두는 단위다. 3 Ah 셀에서 1C는 3 A, 2C는 6 A다.
단자 전압은 개방 전압에 과전압 을 더한 값이므로, 전류가 클수록 같은 SOC에서 전압이 높다. 그래서 전류를 키우면 CC가 더 낮은 SOC에서 끝나고, 나머지는 느린 CV로 채워야 한다. 30.5장의 가상 셀(3 Ah)에 집중 열 모델(열용량 45 J/K, 열전달 계수 0.1 W/K, 주변 25 °C)을 붙여 10%에서 충전한다.
import numpy as np
Q_AH, R0_OHM, R1_OHM, C1_F = 3.0, 0.020, 0.015, 2000.0 # 30.5장의 가상 셀 (충전 전류를 양수로 둔다)
MCP_JPK, HA_WPK, T_AMB_C, V_MAX_V = 45.0, 0.10, 25.0, 4.2 # 열용량, 열전달 계수, 주변 온도, 상한 전압
def ocv_v(soc):
return 3.5 + 0.55 * soc + 0.04 * np.log((soc + 0.02) / (1.02 - soc))
def cc_cv(c_rate, soc0=0.1, dt_s=1.0, i_cut_a=0.15): # CC로 V_MAX까지, 이후 CV로 전류가 C/20이 될 때까지
soc, v1, temp, t, cv = soc0, 0.0, T_AMB_C, 0.0, False
a = np.exp(-dt_s / (R1_OHM * C1_F))
t80, soc_cv, temp_max = None, None, temp
while True:
i = c_rate * Q_AH
if cv or ocv_v(soc) + i * R0_OHM + v1 > V_MAX_V: # 전압 상한에 닿으면 CV: 단자 전압 = V_MAX가 되게 전류를 줄인다
cv, soc_cv = True, soc_cv or soc
i = (V_MAX_V - ocv_v(soc) - v1) / R0_OHM
if i < i_cut_a:
return soc_cv, t80, t, temp_max
temp += dt_s * (i * (i * R0_OHM + v1) - HA_WPK * (temp - T_AMB_C)) / MCP_JPK # 줄 발열 - 냉각
v1 = a * v1 + R1_OHM * (1 - a) * i
soc += i * dt_s / (3600 * Q_AH); t += dt_s
temp_max = max(temp_max, temp)
if t80 is None and soc >= 0.8:
t80 = t
for c in [0.5, 1.0, 2.0, 3.0]:
soc_cv, t80, t_end, tmax = cc_cv(c)
print(f"{c:3.1f}C CV 시작 SOC {soc_cv:.2f} 80%까지 {t80/60:5.1f} min 종료까지 {t_end/60:5.1f} min 최고 {tmax:4.1f} °C")0.5C CV 시작 SOC 0.97 80%까지 84.0 min 종료까지 112.2 min 최고 25.8 °C 1.0C CV 시작 SOC 0.92 80%까지 42.0 min 종료까지 61.5 min 최고 28.1 °C 2.0C CV 시작 SOC 0.80 80%까지 21.0 min 종료까지 38.4 min 최고 36.8 °C 3.0C CV 시작 SOC 0.66 80%까지 14.7 min 종료까지 32.1 min 최고 47.1 °C
0.5C에서 3C로 전류를 6배 올리면 80%까지의 시간은 84.0분에서 14.7분으로 5.7분의 1이 되어 전류에 거의 반비례한다(3C에서는 SOC 66%에서 CV가 시작되지만, CV 초기에는 전류가 아직 커서 80%까지의 시간은 조금만 늘어난다). 그러나 충전 종료까지의 시간은 112.2분에서 32.1분으로 3.5분의 1에 그친다. CV 시작 SOC가 0.97에서 0.66으로 내려가 CV 구간이 길어지기 때문이다. 최고 온도는 25.8 °C에서 47.1 °C로 오른다. 발열이 전류의 제곱에 비례하기 때문이다. 충전 시간 지표로 흔히 "80%까지의 시간"을 쓰는 이유가 여기 있다. 마지막 20%는 어떤 프로토콜이든 느리다.
결정변수, 목적, 제약. 충전 최적화를 수식으로 쓰면
이다. 결정변수 는 프로토콜을 정하는 숫자들이다. 가장 흔한 꼴은 다단계 CC(multi-step constant current)로, SOC 구간마다 C-rate를 하나씩 정한다. 목적은 충전 시간 와 열화 다. 둘은 충돌하므로 답은 점 하나가 아니라 Pareto front, 즉 어느 목적을 더 좋게 하려면 다른 목적을 희생해야 하는 프로토콜들의 집합이다(10.7장). 제약은 안전에서 온다(아래 "안전 제약" 절).
가상의 열화 지표. 실제 열화는 셀을 반복 충방전해 용량 감소를 재야 알 수 있다. 여기서는 방법을 보이기 위해 가상의 대리 지표를 쓴다.
는 C-rate다. 큰 전류, 높은 SOC, 높은 온도에서 열화가 빨라진다는 정성적 경향(높은 SOC의 고율 충전에서 리튬 석출 위험이 크고, 고온에서 부반응이 빨라진다)만 흉내 낸 식이다. 계수는 지어낸 것이고 어떤 셀의 측정에서도 오지 않았다.
이 시뮬레이터에서 SOC 10–50% 구간과 50–80% 구간의 C-rate를 각각 1.0–4.0C(0.1C 간격)에서 고르는 2단계 프로토콜 961개를 모두 평가하고, 전압 4.2 V와 온도 45 °C 제약을 만족하는 것 중 Pareto front를 찾는다.
def two_stage(c1, c2, soc0=0.1, soc_sw=0.5, soc_end=0.8, dt_s=2.0): # c1, c2: (N,) 단계별 C-rate
soc, v1, temp = np.full(c1.shape, soc0), np.zeros(c1.shape), np.full(c1.shape, T_AMB_C)
t, wear, v_peak, t_peak = np.zeros(c1.shape), np.zeros(c1.shape), np.zeros(c1.shape), temp.copy()
a = np.exp(-dt_s / (R1_OHM * C1_F))
while np.any(soc < soc_end):
i = np.where(soc < soc_end, np.where(soc < soc_sw, c1, c2) * Q_AH, 0.0) # (N,) 끝난 후보는 0 A
v_peak = np.maximum(v_peak, np.where(i > 0, ocv_v(soc) + i * R0_OHM + v1, 0.0))
wear += (i / Q_AH) ** 2 * np.exp(3 * (soc - 0.5) + (temp - T_AMB_C) / 15) * dt_s / 3600 # 가상 열화 지표
temp += dt_s * (i * (i * R0_OHM + v1) - HA_WPK * (temp - T_AMB_C)) / MCP_JPK
v1 = a * v1 + R1_OHM * (1 - a) * i
t += np.where(i > 0, dt_s, 0.0); soc += i * dt_s / (3600 * Q_AH)
t_peak = np.maximum(t_peak, temp)
return t / 60, wear, v_peak, t_peak # 충전 시간 (min), 열화 지표, 최고 전압 (V), 최고 온도 (°C)
grid = np.linspace(1.0, 4.0, 31) # 단계별 C-rate 후보
C1g, C2g = [g.ravel() for g in np.meshgrid(grid, grid, indexing="ij")] # (961,)
t_min, wear, v_pk, temp_pk = two_stage(C1g, C2g)
feasible = (v_pk <= V_MAX_V) & (temp_pk <= 45.0) # 안전 제약: 전압 4.2 V, 온도 45 °C
def pareto_mask(F): # F: (N, 2) 최소화할 목적. 지배당하지 않는 점 True
dominated = np.any(np.all(F[:, None, :] >= F[None, :, :], axis=2) & np.any(F[:, None, :] > F[None, :, :], axis=2), axis=1)
return ~dominated
front = np.flatnonzero(feasible)[pareto_mask(np.column_stack([t_min, wear])[feasible])]
print(f"후보 {len(C1g)}개 중 제약을 만족하는 것 {feasible.sum()}개, 그중 Pareto 최적 {len(front)}개")
for k in front[np.argsort(t_min[front])][::10]:
print(f" {C1g[k]:.1f}C → {C2g[k]:.1f}C 시간 {t_min[k]:5.1f} min 열화 {wear[k]:.3f} 최고 {v_pk[k]:.3f} V, {temp_pk[k]:4.1f} °C")
same = np.flatnonzero(np.isclose(C1g, C2g) & feasible)
print("1단계(같은 C-rate) 중 Pareto 최적:", int(np.isin(same, front).sum()), "/", len(same))후보 961개 중 제약을 만족하는 것 220개, 그중 Pareto 최적 80개 3.1C → 1.9C 시간 17.2 min 열화 4.401 최고 4.191 V, 44.2 °C 2.7C → 1.8C 시간 18.9 min 열화 3.425 최고 4.181 V, 40.8 °C 2.3C → 1.8C 시간 20.4 min 열화 2.839 최고 4.181 V, 37.4 °C 2.1C → 1.7C 시간 22.0 min 열화 2.430 최고 4.171 V, 35.8 °C 1.9C → 1.6C 시간 23.9 min 열화 2.077 최고 4.160 V, 34.2 °C 1.7C → 1.5C 시간 26.1 min 열화 1.773 최고 4.150 V, 32.7 °C 1.7C → 1.2C 시간 29.1 min 열화 1.437 최고 4.118 V, 32.7 °C 1.6C → 1.0C 시간 33.0 min 열화 1.181 최고 4.097 V, 31.9 °C 1단계(같은 C-rate) 중 Pareto 최적: 1 / 10
- 제약이 후보를 크게 줄인다. 961개 중 220개만 실현 가능하다. 둘째 단계의 C-rate가 1.9C를 넘으면 80% 근처에서 전압이 4.2 V를 넘고, 첫째 단계가 3.1C를 넘으면 둘째 단계를 1.0C로 낮춰도 온도가 45 °C를 넘는다.
- front 위에서 시간과 열화가 교환된다. 17.2분 프로토콜의 열화 지표는 4.401이고, 33.0분 프로토콜은 1.181이다. 어디를 고를지는 최적화가 아니라 사용 목적(급속 충전소, 야간 완속 충전)이 정한다.
- 단계를 나누면 front가 넓어진다. 같은 C-rate 하나로 충전하는 1단계 프로토콜 10개(제약 만족) 중 front에 남은 것은 가장 느린 1.0C 하나뿐이다. 나머지는 모두 2단계 프로토콜에 지배당한다. 낮은 SOC에서 빨리, 높은 SOC에서 천천히 충전하면 같은 시간에 열화가 적다. 또 1단계로는 22.1분(1.9C)보다 빨리 충전할 수 없지만(2.0C부터 전압 제약 위반), 2단계로는 17.2분까지 가능하다.
연구에서의 의미. 충전 최적화 결과를 "최적 프로토콜 하나"로 보고하면 그 점이 front의 어디에 있는지, 어떤 가중치나 시간 한계가 그것을 골랐는지가 숨는다. front 전체와 각 점의 제약 여유(최고 전압·온도가 한계에서 얼마나 떨어져 있는지)를 함께 보고한다.
방법
충전 최적화에 쓰이는 방법은 "프로토콜을 미리 정하느냐, 충전 중에 실시간으로 정하느냐"로 나뉜다.
베이지안 최적화. 다단계 CC의 C-rate 몇 개처럼 결정변수가 적고 평가가 비싸면 베이지안 최적화(BO)가 맞다. GP 대리모델과 획득함수의 기본은 10.7장에서 다뤘다. 충전 문제에서 새로 필요한 것은 제약이다. 전압·온도는 프로토콜을 평가해야 알 수 있는 양이므로 제약 함수도 블랙박스다. 널리 쓰는 처리는 제약에도 GP를 하나 두고, expected improvement에 실현 가능 확률(probability of feasibility)을 곱하는 것이다(Gelbart, Snoek, Adams, 2014).
이 제약 만족이고, 는 제약 GP의 예측 평균과 표준편차다. EI의 기준값은 지금까지 찾은 실현 가능한 점 중 최선이고, 실현 가능한 점을 아직 못 찾았으면 실현 가능 확률만 최대화한다. 아래에서는 전압·온도 제약과 "충전 시간 20분 이내"를 정규화해 최댓값 하나 로 묶었다.
from scipy.stats import norm
X_all = np.column_stack([C1g, C2g]) / 4.0 # (961, 2) 입력을 [0.25, 1]로
g_all = np.maximum.reduce([(v_pk - V_MAX_V) / 0.05, (temp_pk - 45.0) / 5.0, (t_min - 20.0) / 2.0]) # ≤ 0이면 제약 만족
f_all = np.log(wear) # 목적: 가상 열화 지표(로그)를 최소화
def gp(X, y, Xs, ell=0.15, noise=1e-6): # RBF 커널 GP의 사후 평균·표준편차 (y 표준화)
k = lambda A, B: np.exp(-0.5 * np.sum((A[:, None] - B[None]) ** 2, -1) / ell**2)
m, s = y.mean(), y.std() + 1e-12
L = np.linalg.cholesky(k(X, X) + noise * np.eye(len(X)))
v = np.linalg.solve(L, k(X, Xs)) # (n, C)
return m + s * (v.T @ np.linalg.solve(L, (y - m) / s)), s * np.sqrt(np.clip(1 - np.sum(v**2, 0), 1e-12, None))
def next_point(idx): # idx: 지금까지 평가한 후보 번호
X, ok = X_all[idx], g_all[idx] <= 0
mu_g, sd_g = gp(X, g_all[idx], X_all)
pof = norm.cdf(-mu_g / sd_g) # 제약을 만족할 확률
if not ok.any():
score = pof # 실현 가능한 점을 아직 못 찾았으면 그것부터
else:
mu, sd = gp(X, f_all[idx], X_all)
z = (f_all[idx][ok].min() - mu) / sd
score = sd * (z * norm.cdf(z) + norm.pdf(z)) * pof # EI × 실현 가능 확률
score[idx] = -np.inf # 이미 평가한 점 제외
return int(np.argmax(score))10.7장과 같은 RBF 커널 GP를 두 번 쓴다. 하나는 열화 지표(로그)를, 하나는 제약 를 배운다. 평가하지 않은 후보 961개 중 획득함수가 가장 큰 것을 다음에 평가한다. 같은 평가 예산 20번으로 random search와 시드 20개에서 비교한다.
best_true = f_all[g_all <= 0].min()
def run(method, seed, budget=20):
rng = np.random.default_rng(seed)
idx = list(rng.choice(len(X_all), 4, replace=False)) # 처음 4개는 무작위
while len(idx) < budget:
idx.append(next_point(np.array(idx)) if method == "BO" else
int(rng.choice(np.setdiff1d(np.arange(len(X_all)), idx))))
f = np.where(g_all[idx] <= 0, f_all[idx], np.inf)
return np.minimum.accumulate(f) - best_true # 평가 횟수별 regret (실현 가능한 점이 없으면 inf)
for method in ["BO", "random"]:
R = np.array([run(method, s) for s in range(20)]) # (시드 20, 예산 20)
for n in [10, 20]:
print(f"{method:6s} 평가 {n}번: 실현 가능한 점을 찾은 비율 {np.mean(np.isfinite(R[:, n - 1])):.2f}, "
f"최적의 5% 이내에 도달한 비율 {np.mean(R[:, n - 1] < np.log(1.05)):.2f}")
k = np.flatnonzero(f_all == best_true)[0]
print(f"참 최적: {C1g[k]:.1f}C → {C2g[k]:.1f}C, 시간 {t_min[k]:.1f} min, 열화 {wear[k]:.3f}, 최고 {temp_pk[k]:.1f} °C")BO 평가 10번: 실현 가능한 점을 찾은 비율 0.90, 최적의 5% 이내에 도달한 비율 0.50 BO 평가 20번: 실현 가능한 점을 찾은 비율 1.00, 최적의 5% 이내에 도달한 비율 0.90 random 평가 10번: 실현 가능한 점을 찾은 비율 0.20, 최적의 5% 이내에 도달한 비율 0.00 random 평가 20번: 실현 가능한 점을 찾은 비율 0.45, 최적의 5% 이내에 도달한 비율 0.00 참 최적: 2.4C → 1.8C, 시간 20.0 min, 열화 2.968, 최고 38.2 °C
20분 이내·전압·온도를 모두 만족하는 프로토콜은 961개 중 29개뿐이다. random search는 평가 20번 중 실현 가능한 점을 하나라도 찾은 비율이 45%이고, 참 최적의 5% 이내에는 한 번도 가지 못했다. 제약 BO는 평가 10번에 절반, 20번에 90%의 시드에서 참 최적의 5% 이내에 도달했다. 참 최적(2.4C → 1.8C)의 충전 시간은 정확히 20.0분이다. 제약이 있는 최적화의 해는 대개 제약 경계 위에 있다. 시간 한계를 조금만 풀면 열화가 줄어든다는 뜻이고, 동시에 경계 위의 해는 모델 오차에 가장 민감하다는 뜻이다(sim-to-real 절).
MPC. 프로토콜을 미리 정하는 대신, 충전 중에 측정한 전압·온도와 모델로 앞으로 몇십 초를 예측하며 매 순간 전류를 정할 수도 있다. 이것이 20.3장의 모델 예측 제어(MPC)다. "충전 시간을 줄이되 예측한 온도와 음극 전위가 한계를 넘지 않게" 전류를 고르는 문제를 매 step 푼다. MPC는 셀마다 다른 저항과 주변 온도에 되먹임으로 대응할 수 있다는 것이 큰 장점이다. 대신 실시간으로 풀 수 있을 만큼 빠른 모델과, 모델의 내부 상태(SOC, 음극 전위)를 추정하는 관측기가 필요하다(24.6장의 필터). 리튬 석출 제약을 쓰려면 등가회로 모델로는 부족하고 전기화학 모델이나 그 대리모델(30.5장)이 필요하다.
강화학습. 정책(상태 → 전류)을 시뮬레이터 안에서 강화학습으로 학습하는 접근도 있다. 모델이 미분 불가능하거나 목적이 긴 지평에 걸쳐 있을 때 매력적이지만, 시뮬레이터 안에서 학습한 정책은 시뮬레이터의 결함을 파고들 수 있다(20.3장의 모델 오차 누적). 실제 셀에서 탐색하는 것은 안전 때문에 거의 불가능하므로, 오프라인 데이터와 안전 제약을 다루는 20.4장의 방법이 함께 필요하다.
실험 설계
왜 순차적인가. 실제 충전 최적화에서 평가 한 번은 셀 몇 개를 그 프로토콜로 수백에서 천 사이클 이상 돌려 수명을 재는 일이다. 사이클러의 채널 수가 한정되어 있으므로 후보를 한꺼번에 다 시험할 수 없다. 그래서 몇 개를 시험하고, 결과로 대리모델을 갱신하고, 다음 후보를 고르는 순차적 실험 설계가 된다(25.3장). 채널이 여러 개이면 한 번에 여러 후보를 고르는 batch BO를 쓴다. 이때 비슷한 후보만 고르지 않도록 다양성을 강제하는 장치가 필요하다.
조기 예측과의 결합. 수명이 끝날 때까지 기다리면 한 라운드가 몇 달이다. 초기 사이클의 데이터(전압 곡선의 변화 등)로 최종 수명을 예측하는 모델(30.4장)을 쓰면 라운드를 크게 줄일 수 있다. Severson 외(2019)는 고속 충전 조건으로 돌린 LFP/흑연 상용 셀 124개의 데이터에서, 처음 100 사이클의 데이터로 사이클 수명을 테스트 오차 9.1%로 예측했다고 보고했다. 이 결합에는 고유한 위험이 있다.
- 조기 예측의 오차가 BO의 관측 잡음이 된다. 잡음이 있는 관측으로 BO를 돌리면 "지금까지의 최선"이 잡음으로 부풀려진다. 추천은 관측값이 아니라 대리모델의 예측 평균으로 하고, 최종 후보는 수명을 끝까지 재서 확인한다.
- 조기 예측 모델은 학습한 프로토콜 범위 안에서만 믿을 수 있다. BO는 정의상 지금까지 시험하지 않은 프로토콜로 간다. 조기 예측 모델이 그 프로토콜의 열화 메커니즘을 본 적이 없으면 예측이 체계적으로 틀릴 수 있고, BO는 그 오류가 유리한 쪽으로 난 후보를 골라낸다. 탐색이 진행될수록 일부 후보는 끝까지 재서 조기 예측 모델을 다시 검증한다.
sim-to-real
시뮬레이터 최적해의 한계. 시뮬레이터에서 찾은 최적 프로토콜은 시뮬레이터의 파라미터에 대한 최적이다. 실제 셀은 제조 편차로 저항이 다르고, 노화하면 저항이 커지며, 차량 안의 냉각 조건과 주변 온도도 다르다. 앞에서 본 것처럼 제약 최적화의 해는 제약 경계 위에 있으므로, 실제 셀의 파라미터가 조금만 불리하게 달라도 제약을 넘는다. 실습에서 공칭 셀로 찾은 최적 프로토콜을 파라미터가 다른 가상 셀 200개에 적용하면 절반 넘게 전압이나 온도 한계를 넘는다.
검증 절차. 시뮬레이터의 결과를 실제 셀에 대한 주장으로 바꾸려면 다음이 필요하다.
- 파라미터 불확실성을 넣은 평가. 30.5장처럼 추정한 파라미터의 불확실성과 셀 간 편차, 운전 환경(주변 온도, 냉각)의 범위를 정하고, 후보 프로토콜을 그 범위 전체에서 평가한다. 제약은 공칭 셀이 아니라 최악 조건(또는 높은 분위수)에서 만족시킨다.
- 실제 셀 시험. 최종 후보와 기준 프로토콜(예: 표준 CC-CV)을 같은 배치의 셀 여러 개로 같은 조건에서 시험한다. 셀 간 편차가 크므로 프로토콜당 셀 하나로는 비교할 수 없다. 반복 셀 수와 비교 방법(12.5장)을 미리 정한다.
- 시뮬레이터 예측과 측정의 비교. 시뮬레이터가 예측한 최고 온도·최고 전압과 실제 측정을 비교해 모델의 편향을 보고한다. 편향이 크면 시뮬레이터를 다시 보정하고(30.5장) 최적화를 반복한다.
- 열화 지표의 검증. 시뮬레이터 안의 열화 대리 지표가 실제 수명 순위를 맞추는지 확인한다. 이 장의 가상 지표처럼 정성적 경향만 담은 지표로 고른 최적해는, 실제 열화 메커니즘이 다르면 의미가 없다.
안전 제약
전압. 상한 전압을 넘으면 양극이 불안정해지고 전해질 산화가 빨라진다. 단자 전압은 측정할 수 있으므로 가장 다루기 쉬운 제약이다. 그러나 단자 전압 은 과전압을 포함하므로, 같은 전압 한계도 전류가 클수록 낮은 SOC에서 걸린다(셀 1의 CV 시작 SOC).
온도. 셀 온도는 반응 속도와 부반응을 모두 바꾼다. 상한은 열 폭주와 빠른 열화를 막기 위한 것이고, 저온에서는 반대로 리튬 석출 위험이 커져 충전 전류를 줄여야 한다. 표면 온도는 측정할 수 있지만 셀 내부 온도는 더 높을 수 있으므로, 표면 온도 한계에는 내부–표면 차이만큼의 여유를 둔다.
리튬 석출. 고율·저온·높은 SOC에서 음극 전위가 리튬 금속 기준 0 V 아래로 내려가면, 리튬이온이 흑연 안으로 들어가는 대신 표면에 금속으로 석출될 수 있다. 석출된 리튬은 용량 손실을 일으키고, 자라면 내부 단락의 위험이 된다. 문제는 음극 전위를 단자에서 직접 잴 수 없다는 것이다. 단자 전압은 양극과 음극 전위의 차이일 뿐이다. 그래서 이 제약은 전기화학 모델(또는 기준 전극을 넣은 실험 셀로 보정한 모델)이 예측한 음극 전위로만 다룰 수 있고, 그 예측의 불확실성만큼 여유를 둬야 한다. 등가회로 모델만 쓰는 최적화에서는 이 제약이 아예 빠져 있다는 사실을 보고에 명시한다.
제약을 다루는 방식. 안전 제약은 목적함수에 벌점으로 섞지 않는다. 벌점 계수에 따라 제약을 조금 어긴 해가 "최적"이 될 수 있기 때문이다(10.2장). 최적화에서는 명시적 제약으로 다루고, 결과에는 각 제약의 여유를 따로 보고한다. 실제 충전기·BMS에서는 최적화와 별개로, 측정값이 한계에 닿으면 전류를 즉시 줄이는 하드웨어·펌웨어 보호가 마지막 방어선으로 남는다.
실습
시뮬레이터에서 3단계 충전 프로토콜(SOC 10–40%, 40–60%, 60–80% 구간의 C-rate, 각각 1.0–4.0C에서 0.25C 간격, 후보 2197개)을 제약 BO로 최적화하고, 찾은 프로토콜을 파라미터가 다른 가상의 "실제 셀" 200개(저항 0.9–1.4배, 열전달 0.7–1.1배, 주변 20–35 °C)에 적용해 제약 위반을 센다. 코드는 labs/battery-ai/charging-optimization/sim_to_real.py다. 제약을 공칭 셀에서 확인한 경우, 여유를 둔 경우, 최악 조건 셀에서 확인한 경우를 비교한다. 집필 시점의 실행 결과는 다음과 같다.
후보 2197개, 시뮬레이터에서 4.2 V·45 °C·20 min을 모두 만족하는 것 52개 공칭 셀, 공칭 제약 4.2 V·45 °C, 20 min 시뮬레이터 최적: 3.00C → 1.75C → 1.75C, 시간 19.7 min, 열화 3.084, 최고 4.176 V, 40.3 °C 제약 BO(평가 30번, 시드 10개)가 최적의 5% 이내를 찾은 비율 0.9 실제 셀 200개: 4.2 V 초과 56%, 45 °C 초과 55%, 20 min 초과 0%, 열화 지표 중앙값 4.438 (5–95% 2.670–7.388) 공칭 셀, 여유 제약 4.15 V·40 °C, 20 min 시뮬레이터 최적: 2.75C → 2.25C → 1.50C, 시간 19.9 min, 열화 3.140, 최고 4.150 V, 39.9 °C 제약 BO(평가 30번, 시드 10개)가 최적의 5% 이내를 찾은 비율 0.8 실제 셀 200개: 4.2 V 초과 4%, 45 °C 초과 56%, 20 min 초과 0%, 열화 지표 중앙값 4.554 (5–95% 2.734–7.584) 최악 조건 셀(R ×1.4, h ×0.7, 35 °C), 4.2 V·45 °C, 20 min 제약을 모두 만족하는 프로토콜이 없다 최악 조건 셀(R ×1.4, h ×0.7, 35 °C), 4.2 V·45 °C, 35 min 시뮬레이터 최적: 1.50C → 1.25C → 1.00C, 시간 33.6 min, 열화 1.133, 최고 4.098 V, 30.6 °C 제약 BO(평가 30번, 시드 10개)가 최적의 5% 이내를 찾은 비율 0.9 실제 셀 200개: 4.2 V 초과 0%, 45 °C 초과 0%, 35 min 초과 0%, 열화 지표 중앙값 1.458 (5–95% 0.918–2.325)
(최고 전압·온도는 공칭 셀 시뮬레이터의 값이다.) 결과를 읽는 법은 세 가지다.
- 공칭 셀의 최적해는 실제 셀의 절반 이상에서 제약을 넘는다. 시뮬레이터 안에서는 4.176 V, 40.3 °C로 한계 안이었지만, 저항이 크거나 냉각이 약하거나 주변이 더운 셀에서는 넘는다. BO가 최적을 잘 찾는 것(90%)과 그 최적이 실제로 안전한 것은 별개의 질문이다.
- 여유는 불확실성의 크기에 맞춰야 한다. 전압 여유 0.05 V는 위반을 56%에서 4%로 줄였지만, 온도 여유 5 °C는 주변 온도 범위(최대 +10 °C)보다 작아 효과가 없었다. 여유를 감으로 정하지 말고 파라미터 범위에서 계산한다.
- 최악 조건에서 제약을 지키면 요구 사항 자체가 바뀐다. 최악 조건 셀까지 지키는 20분 프로토콜은 없다. 35분으로 풀면 모든 셀에서 안전하다. "20분 충전"과 "모든 조건에서 안전"을 함께 원하면 고정 프로토콜이 아니라 온도·저항에 반응하는 제어(MPC)가 필요하다는 결론이 이 비교에서 나온다.
이어서 두 연습문제를 푼다. 하나는 CC-CV 충전 시간을 닫힌 꼴로 계산하고, 다른 하나는 제약을 지키는 후보만으로 Pareto front를 뽑는다.
실패 사례
- 시뮬레이터의 최적해를 실제 셀에서 검증 없이 주장하는 것. 시뮬레이터 최적해는 제약 경계에 붙어 있어 모델 오차에 가장 민감하다. 파라미터 불확실성 아래의 제약 만족과 실제 셀 시험(같은 배치, 반복 셀, 기준 프로토콜과의 비교) 없이 "이 프로토콜이 수명을 늘린다"고 쓰지 않는다.
- 가상의 열화 지표로 고른 프로토콜을 실제 열화에 대한 결론으로 쓰는 것. 열화 대리 지표가 실제 수명 순위를 맞추는지는 따로 검증해야 한다.
- 다목적 문제를 가중합 하나로 풀고 front를 숨기는 것. 가중치나 시간 한계가 답을 정한다. front와 선택 기준을 함께 보고한다.
- 리튬 석출 제약을 단자 전압 제약으로 대신하는 것. 단자 전압이 한계 안이어도 음극 전위는 0 V 아래로 내려갈 수 있다. 석출 제약을 다루지 않았다면 그렇다고 명시한다.
- 조기 예측 모델의 오차를 무시하고 BO의 관측으로 쓰는 것. BO는 예측이 유리하게 틀린 후보를 골라낸다. 최종 후보는 끝까지 측정해 확인한다.
통과 기준
- 충전 최적화 문제를 결정변수(단계별 C-rate)·목적(시간, 열화 지표)·제약(전압, 온도, 리튬 석출 여부)으로 쓰고, 다목적 결과를 제약을 만족하는 Pareto front와 각 점의 제약 여유로 보고한다.
- 제약 BO를 같은 평가 예산의 random search와 여러 시드에서 비교하고, 최적해가 제약 경계 위에 있는지 확인한다.
- 시뮬레이터 최적해를 파라미터·환경 범위에서 다시 평가해 제약 위반 비율을 보고하고, 실제 셀 검증 계획(반복 셀 수, 기준 프로토콜)을 쓴다.
- 연습문제
cc-cv-charge-time과constrained-pareto의 테스트를 통과한다.
참고 문헌
- Attia, Grover, Jin, Severson, Markov, Liao, Chen, Cheong, Perkins, Yang, Herring, Aykol, Harris, Braatz, Ermon, Chueh (2020). Closed-loop optimization of fast-charging protocols for batteries with machine learning. Nature, 578, 397–402. — 조기 수명 예측과 BO를 결합한 급속 충전 프로토콜 탐색
- Severson, Attia, Jin, Perkins, Jiang, Yang, Chen, Aykol, Herring, Fraggedakis, Bazant, Harris, Chueh, Braatz (2019). Data-driven prediction of battery cycle life before capacity degradation. Nature Energy, 4, 383–391. — 초기 사이클 데이터로 사이클 수명 예측
- Gelbart, Snoek, Adams (2014). Bayesian optimization with unknown constraints. Proceedings of the 30th Conference on Uncertainty in Artificial Intelligence (UAI), 250–259. — 블랙박스 제약이 있는 베이지안 최적화