Degradation-pattern-informed Non-destructive Verification Enables Sustainable Manufacturing and Recycling of Battery Prototypes
Battery research and development (R&D) faces fierce technology path competition for future energy use scenarios, generating endless battery prototypes. Performance mismatches between theoretical designs and as-manufactured battery prototypes remain a significant bottleneck, due to time-consuming, costly, and often destructive verification. Although machine learning has been well-documented in battery R&D, it still fails to interpret electrochemistry degradation patterns and, is hardly supportive of prototype verification. Here, distinct from purely statistical modeling, we explore thermodynamic and kinetic degradation patterns decoupling from initial manufacturing differences among normally identical batteries, and inferring multidimensional chemical process signals for the entire lifetime performance verification at minimal physical sensory data retrieval cost. This physics-informed method shows great promise in integrating material science priors into machine learning models, demonstrating sustainable and economic superiorities by rationally managing massive prototype batteries based on statistical degradation status.
- Python (Jupyter notebook)
- python=3.11.5
- numpy=1.26.4
- tensorflow=2.15.0
- keras=2.15.0
- matplotlib=3.9.0
- scipy=1.13.1
- scikit-learn=1.3.1
- pandas=2.2.2
- Raw and processed datasets have been deposited in TBSI-Sunwoda-Battery-Dataset, which can be accessed at TBSI-Sunwoda-Battery-Dataset.
- Please refer to Data generation part of the paper for detailed dataset explanation.
The entire experiment consists of three steps as well as three models:
- ChemicalProcessModel
- DomainAdaptModel
- DegradationTrajectoryModel
First, we model multi-dimensional chemical processes using early cycle and guiding sample data; second, we adapt these predictions to specific temperatures; and third, we use adapted chemical processes to avoid the need for physical measures in later cycles. The extent of early data used is tailored to meet the desired accuracy, assessed by mean absolute percentage error for consistent cross-stage comparisons.
3.2 Chemical process prediction model considering initial manufacturing variability (ChemicalProcessModel)
The ChemicalProcessModel predicts chemical process variations by using input voltage matrix
where
Here is the implementation:
class ChemicalProcessModel(nn.Module):
def __init__(self):
super(ChemicalProcessModel, self).__init__()
self.fc1 = nn.Linear(10, 32)
self.fc2 = nn.Linear(32, 64)
self.fc3 = nn.Linear(64, 32)
self.fc4 = nn.Linear(32, 42)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = torch.relu(self.fc3(x))
x = self.fc4(x)
return x
In each selected temperature, we split the data into 75% and 25% for training and testing, respectively. We train the chemical process prediction model using the Adam optimizer, with 30 epochs and a learning rate of
criterion = nn.MSELoss().to(device)
def loss_fn(outputs, labels, model, l1_strength):
loss = criterion(outputs, labels)
l1_regularization = add_l1_regularization(model, l1_strength)
loss += l1_regularization
return loss
See the Methods section of the paper for more details.
- In the code of Chemical Process Model, there are options to change parameters at the very beginning. The following parameters can be modified to adjust the training process.
# List of learning rates to be used for training.
learning_rates = [3e-4, 1e-4]
lr_losses = {}
best_lr = None
best_loss = float('inf')
best_model_state = None
# Total number of training epochs.
train_epochs = 100
# Read raw data from csv file.
raw_data = pd.read_csv("./raw_data_0920.csv")
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Create training dataset and its data loader with batch size 1 and shuffle enabled.
train_dataset = BattDataset(raw_data, train=True)
train_loader = DataLoader(train_dataset, batch_size=1, shuffle=True)
# Create validation dataset and its data loader
valid_dataset = BattDataset(raw_data, train=True)
valid_loader = DataLoader(valid_dataset, batch_size=1, shuffle=False)
# Create test dataset and its data loader
test_dataset = BattDataset(raw_data, train=False)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)
# Define MSE loss function
criterion = nn.MSELoss().to(device)
- After changing the experiment settings, run
ChemicalProcessModel.py
directly for training and testing.
After predicting the chemical process of source domain, it is necessary to transfer it to the target domain. This section will specifically explain how to perform multi-domain adaptation by utilizing our proposed physics-informed transferability metric.
It is time-consuming and cost-intensive to enumerate continuous temperature verifications, we therefore formulate a knowledge transfer from existing measured data (source domain) to arbitrary intermediate temperatures (target domain). The transfer is compatible with multi- and uni-source domain adaptation cases for tailored verification purposes. Here we use a multi-source domain adaptation to elucidate the core idea. For instance, we take 25, 55℃ as source domains and 35, 45℃ as target domains.
We propose a physics-informed transferability metric to quantitatively evaluate the effort in the knowledge transfer. The proposed transferability metric integrates prior physics knowledge inspired by the Arrhenius equation:
where:
The Arrhenius equation provides us with important information that the aging rate of batteries is directly related to the temperature. Therefore, the Arrhenius equation offers valuable insights into translating the aging rate between different temperatures. We observe the domain-invariant representation of the aging rate ratio, consequently, the proposed Arrhenius equation-based transferability metric
where
The closer the
where
where
This calculation mitigates the noise-induced errors, resulting in a more robust aging rate computation. For domains where the aging mechanism is already known (different domains share the same ( E_a )), the
where
The multi-source transfer based on
- First, we calculate aging rates
$r$ for all target and source domains by using early-stage data, i.e., we set$\text{start} = 100$ ,$\text{end} = 200$ ,$n = 50$ . After calculating aging rates for all features or aging curves, we obtain a target domain aging rate vector$r_{\text{target } 1\times N}$ and a source domain aging rate matrix$r_{\text{source } K\times N}$ , where$K$ and$N$ are the number of source domains and the number of features, respectively.
def gradient(feature_list,start,end,sample_size):
mean_start = 0
mean_end = 0
for i in range(sample_size):
mean_start = mean_start + feature_list[start+i]
for i in range(sample_size):
mean_end = mean_end + feature_list[end+i]
mean_start = mean_start/sample_size
mean_end = mean_end/sample_size
grad = mean_end - mean_start
rang = end-start
return np.log(abs(grad/rang))
start45 = early_cycle_start#100
end45 = early_cycle_end#200
g25.append(gradient(pre25[i][:],start,end,sample_size))
g55.append(gradient(pre55[i][:],start,end,sample_size))
g35.append(gradient(real35[i][:],start,end,sample_size))
g45.append(gradient(real45[i][:],start45,end45,sample_size))
- Second, we calculate the transferability metric
$AT_{\text{score}}$ and weight vector$W_{1\times K} = {W_i}$ .
#ATscore calculation
at25.append(gradient(real45[i][:],start45,end45,sample_size)/gradient(pre25[i][:],start,end,sample_size))
at55.append(gradient(real45[i][:],start45,end45,sample_size)/gradient(pre55[i][:],start,end,sample_size))
#weight calculation
w25 = abs(step25-1)+abs(step55-1)
w25 = abs(step55-1)/w25
w_at_25.append(w25)
- Third, we predict the late stage (cycles after 200) aging rate of the target domain (
$r_{\text{target}}$ ) (shown in 4.2.3). Note that$AT_{\text{score}}^{\text{source } i \to \text{target}}$ and$W_i$ are obtained by both target and source domain early-stage data, which are used to measure the transferability from source domain to target domain based on their aging rate similarity.$r_{\text{source } i}$ is obtained from all accessible data in the source domain, consistent with our definition of the early-stage estimate problem.
Using the physics-informed transferability metric, we assign a weight vector
where,
See the Methods section of the paper for more details.
Battery chemical process degradation is continuous, which we call the "Chain of Degradation". We have predicted the
where
We concatenate the
#################################################################################################
# AT method:
# Input: at25_to_45, at55_to_45, w_at_25, pre25, pre55 (these are from this round), last45, last55, last25 (these are from the previous round).
# Output: pre45, stored in feature_1 (replaces the original model1 output).
# Renew: last45 = pre45, last25 = pre25, last55 = pre55
#################################################################################################
if(batch>early_cycle_end):
if(batch==early_cycle_end):
pred_feature.append(last45)
for i in range(42):
step1 = w_at_25[i]*(pre25[i][0]-last25[i][0])*at25[i]
step2 = (1-w_at_25[i])*(pre55[i][0]-last55[i][0])*at55[i]
feature_45[i] = last45[i] + w_at_25[i]*(pre25[i][0]-last25[i][0])*at25[i] + (1-w_at_25[i])*(pre55[i][0]-last55[i][0])*at55[i]
last45[i] = feature_1[i]
last25[i][0] = pre25[i][0]
last55[i][0] = pre55[i][0]
pred_feature.append(feature_45)
- In the code of DomainAdaptModel, there are options to change parameters at the very beginning. The following parameters can be modified to adjust the training process.
#load raw data from csv
raw_data = pd.read_csv("./raw_data_0920.csv")
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#load chemical process model under different temperatures
model_1_25 = MyNetwork1()
model_path_1 = "./0921_best_model_1_T25.pt"
model_1_25.load_state_dict(torch.load(model_path_1, map_location=torch.device('cpu')))
model_1_55 = MyNetwork1()
model_path_1 = "./0921_best_model_1_T55.pt"
model_1_55.load_state_dict(torch.load(model_path_1, map_location=torch.device('cpu')))
# load the previous chemical process model for compare。
model_1 = MyNetwork3()
model_path_1 = "./best_model_1.pt"
model_1.load_state_dict(torch.load(model_path_1, map_location=torch.device('cpu')))
# load different types of degradation trajectory model
model_2 = MyNetwork2()
model_2_old = MyNetwork2()
model_path_2_old = "./best_model_2_previous.pt"
model_path_2 = "./best_model_2_now.pt"
model_2.load_state_dict(torch.load(model_path_2, map_location=torch.device('cpu')))
model_2_old.load_state_dict(torch.load(model_path_2_old, map_location=torch.device('cpu')))
# the battery_dict is used to filter the specific battery data from the raw data
battery_dict = {
"T25": [1.362,1.336,1.3,1.351,1.318,1.329,1.286,1.442,1.478],
"T35": [-0.0816,-0.121,-0.157,-0.092,-0.15,-0.128,-0.244],
"T45": [-0.854,-0.941,-0.912,-0.937,-0.988,-0.865,-1.006],
"T55": [-1.101,-1.304,-1.242,-1.220,-1.144,-1.224,-1.090],
}
# set the target temperature: T25/T35/T45/T55
test_tmp = "T35"
# set the early cycle numbers, for now, it means using 0-200 cycles of target domain to train the DomainAdaptModel
early_cycle_start = 0
early_cycle_end = 200
sample_size= 20
# the lifetime of battery under different temperature is not equal, which causes the test size is different
test_size = 1295
if test_tmp == "T45":
test_size = 1095
if test_tmp == "T55":
test_size = 895
- After changing the experiment settings, run
DomainAdaptModel.py
directly for training and testing.
We have successfully predicted the battery chemical process. It is assumed that the chemical process of the battery deterministically affects the aging process, we therefore use the predicted chemical process to predict the battery degradation curve. The battery degradation trajectory model learns a composition of
where
Here is the implementation of DegradationTrajectoryModel:
class DegradationTrajectoryModel(nn.Module):
def __init__(self):
super(DegradationTrajectoryModel, self).__init__()
self.fc1 = nn.Linear(53, 32)
self.fc2 = nn.Linear(32, 64)
self.fc3 = nn.Linear(64, 32)
self.fc4 = nn.Linear(32, 1)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = torch.relu(self.fc3(x))
x = self.fc4(x)
return x
- In the code of Degradation Trajectory Modell, there are options to change parameters at the very beginning. The following parameters can be modified to adjust the training process.
# a set of learning rate
learning_rates = [1e-3, 2e-3, 3e-3]
lr_losses = {}
# The information of the best model
best_lr = None
best_loss = float('inf')
best_model_state = None
train_epochs = 100
raw_data = pd.read_csv("./raw_data_0920.csv")
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Create training dataset and its data loader with batch size 1 and shuffle enabled.
train_dataset = BattDataset(raw_data, train=True)
train_loader = DataLoader(train_dataset, batch_size=1, shuffle=True)
# Create validation dataset and its data loader with batch size 1 and no shuffle.
valid_dataset = BattDataset(raw_data, train=True)
valid_loader = DataLoader(valid_dataset, batch_size=1, shuffle=False)
# Create test dataset and its data loader with batch size 1 and no shuffle.
test_dataset = BattDataset(raw_data, train=False)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)
# Define MSE loss function and move it to the device.
criterion = nn.MSELoss().to(device)
- After changing the experiment settings, run
DegradationTrajectoryModel.py
directly for training and testing.
Access the raw data and processed features here under the MIT licence. Correspondence to Terence (Shengyu) Tao and CC Prof. Xuan Zhang, Guangmin Zhou and Yang Li when you use, or have any inquiries.
Terence (Shengyu) Tao and Zixi Zhao at Tsinghua Berkeley Shenzhen Institute designed the model and algorithms, developed and tested the experiments, uploaded the model and experimental code, revised the testing experiment plan, and wrote this instruction document based on supplementary materials.