├── README.md ├── develop ├── 2 │ └── soh_buff.py ├── KF_FL.m ├── Result.m ├── POWER-S-20-01196.pdf ├── SOH_DataAnalysis.m ├── Initial_DataAnalysis.m ├── LYF_SOH算法_python │ ├── 153Ah充电数据.csv │ ├── __pycache__ │ │ ├── SOHCal.cpython-36.pyc │ │ ├── KalmanFuzzy.cpython-36.pyc │ │ └── FuzzyControl.cpython-36.pyc │ ├── main.py │ ├── KalmanFuzzy.py │ ├── FuzzyControl.py │ └── SOHCal.py ├── plotc.m ├── SOH_Estimate.ipynb ├── 模糊控制系统.ipynb └── soh_buff_main.py ├── ML_SOH ├── ml_main.py ├── soc_correction.py ├── SOH模型训练说明.md ├── model.py └── charging.py ├── intergral.py └── soh.py /README.md: -------------------------------------------------------------------------------- 1 | # SOH 2 | 锂电池SOH估计算法 3 | -------------------------------------------------------------------------------- /develop/KF_FL.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/KF_FL.m -------------------------------------------------------------------------------- /develop/Result.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/Result.m -------------------------------------------------------------------------------- /develop/POWER-S-20-01196.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/POWER-S-20-01196.pdf -------------------------------------------------------------------------------- /develop/SOH_DataAnalysis.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/SOH_DataAnalysis.m -------------------------------------------------------------------------------- /develop/Initial_DataAnalysis.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/Initial_DataAnalysis.m -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/153Ah充电数据.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/LYF_SOH算法_python/153Ah充电数据.csv -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/__pycache__/SOHCal.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/LYF_SOH算法_python/__pycache__/SOHCal.cpython-36.pyc -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/__pycache__/KalmanFuzzy.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/LYF_SOH算法_python/__pycache__/KalmanFuzzy.cpython-36.pyc -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/__pycache__/FuzzyControl.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaoboh/SOH/HEAD/develop/LYF_SOH算法_python/__pycache__/FuzzyControl.cpython-36.pyc -------------------------------------------------------------------------------- /ML_SOH/ml_main.py: -------------------------------------------------------------------------------- 1 | import charging 2 | import soc_correction 3 | import model 4 | 5 | charging = Charging() 6 | column_list = ['sample_ts', 'sin_btry_hist_volt', 'sin_btry_lwst_volt', 'vehl_totl_volt', 'hist_temp_sn', 7 | 'lwst_temp_sn', 'chrg_state', 'mileage', 'vehl_totl_curnt', 'soc'] 8 | file_path=r'E:\蔚来运行数据' 9 | initial_format='xls' 10 | target_format='csv' 11 | charging.Rename(file_path, initial_format, target_format) 12 | 13 | new_path=r'E:\蔚来运行数据\按车辆组合数据' 14 | file_format='csv' 15 | column_name=['vin','sample_ts','sin_btry_hist_volt','sin_btry_lwst_volt','vehl_totl_volt','btry_pak_hist_temp','btry_pak_lwst_temp','chrg_state','mileage','vehl_totl_curnt','soc'] 16 | charging.Car_merge(file_path, new_path, file_format, column_name, 'vin') 17 | 18 | new_path = r'E:\蔚来运行数据' 19 | charge_code=[1] 20 | df_cha=charging.Processing(new_path, file_format, column_list, charge_code, encoding='gbk', time_format='%Y-%m-%d %H:%M:%S', rest_I=2) 21 | 22 | soc_cor=Soc_correction() 23 | SOC = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 96, 97, 100] 24 | OCV = [3.281, 3.508, 3.568, 3.597, 3.622, 3.641, 3.682, 3.756, 3.842, 3.94, 4.05, 4.125, 4.15, 4.175] 25 | socmin=5 26 | socmax=97 27 | ocv_list=[3.28, 3.50, 3.56, 3.59, 3.62, 3.64, 3.68, 3.75, 3.84, 3.4, 4.05, 4.125, 4.1, 4.175,4.18] 28 | soc_cor=Soc_correction() 29 | df_cha['充电前校正SOCmin']=np.array(soc_cor.soc_correction(SOC,OCV,socmin,socmax,list(df_cha['充电前Vmin']))) 30 | df_cha['充电前校正SOCmax']=np.array(soc_cor.soc_correction(SOC,OCV,socmin,socmax,list(df_cha['充电前Vmax']))) 31 | 32 | model=Model() 33 | file_path=r'F:\运行数据\A-标准化程序\SOH_model\soh_model\test\蔚来充电特征数据.csv' 34 | model_column=['运行里程', '充电前Vmin', '充电前Vmax', '充电前Vd', '充电结束Vmin', '充电结束Vmax', '充电结束Vd', 35 | '充电起始Tmin','充电起始Tmax', '充电结束Tmin', '充电结束Tmax', '充电起始SOC','充电结束SOC','SOC变化','充入容量'] 36 | print(model.Model_train(model.Data_filter(file_path),model_column)) -------------------------------------------------------------------------------- /ML_SOH/soc_correction.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import sys 5 | 6 | #对将0-100%SOC-OCV换算到5-97%范围的SOC-OCV 7 | # 额定容量范围内的OCV定义插值函数 8 | 9 | class Soc_correction: 10 | '''SOC校准''' 11 | 12 | def Soc_mapping(self,socmin,socmax,SOC): 13 | ''' 14 | 根据额定SOC使用范围,获得表显0-100%SOC对应的真实 15 | :param SOC: 0-100%SOC范围内的SOC 16 | :param socmin: 使用SOC下限 17 | :param socmax: 使用SOC上限 18 | :return: 映射到使用范围内的SOC 19 | ''' 20 | soc_map=[] 21 | socd=(socmax-socmin)/100 22 | for i in SOC: 23 | soc_map.append(1/socd*i-5/socd) 24 | return soc_map 25 | 26 | def Inter(self,OCV,SOC,ocv_list): 27 | ''' 28 | 插值法校正SOC 29 | :param SOC: SOC列表 30 | :param OCV: SOC对应OCV列表 31 | :param ocv_list: 待校正点的ocv列表 32 | :return:估算的soc_list 33 | ''' 34 | soc_list=[] 35 | for ocv in ocv_list: 36 | if ocv in OCV: 37 | soc=SOC[OCV.index(ocv)] 38 | soc_list.append(soc) 39 | elif ocvOCV[-1]: 43 | soc = SOC[-1] + (SOC[-1] - SOC[-2]) / ((OCV[-1] - OCV[-2])) * (ocv - OCV[-1]) 44 | soc_list.append(soc) 45 | else: 46 | for i in range(len(OCV)-1): 47 | if ocv>=OCV[i] and ocv<=OCV[i+1]: 48 | soc=SOC[i] + (SOC[i+1] - SOC[i]) / ((OCV[i+1] - OCV[i])) * (ocv - OCV[i]) 49 | soc_list.append(soc) 50 | else: 51 | continue 52 | return soc_list 53 | def soc_correction(self,SOC,OCV,socmin,socmax,ocv_list): 54 | soc_cor=self.Inter(OCV,SOC,ocv_list) 55 | soc_list=self.Soc_mapping(socmin,socmax,soc_cor) 56 | return soc_list 57 | 58 | 59 | 60 | if __name__ == '__main__': 61 | SOC = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 96, 97, 100] 62 | OCV = [3.281, 3.508, 3.568, 3.597, 3.622, 3.641, 3.682, 3.756, 3.842, 3.94, 4.05, 4.125, 4.15, 4.175] 63 | socmin=5 64 | socmax=97 65 | ocv_list=[3.28, 3.50, 3.56, 3.59, 3.62, 3.64, 3.68, 3.75, 3.84, 3.4, 4.05, 4.125, 4.1, 4.175,4.18] 66 | soc_cor=Soc_correction() 67 | print(soc_cor.soc_correction(SOC,OCV,socmin,socmax,ocv_list)) -------------------------------------------------------------------------------- /intergral.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import heapq 4 | import time 5 | 6 | import math 7 | import operator 8 | data_input = pd.read_csv(r'D:/git/诊断仪2.1版本/20210312/df2.csv') 9 | 10 | grouped = data_input.groupby('charge_number') 11 | chargeMode = 0 12 | # arr = [] 13 | # for subgroup in grouped: 14 | # # print(len(subgroup[1])) 15 | # chargeMode += 1 16 | # arr.append(len(subgroup[1])) 17 | # # print(len(arr)) 18 | 19 | # ##########最大区间及其对应的索引 20 | # max_index, max_number = max(enumerate(arr), key=operator.itemgetter(1)) 21 | # min_index, min_number = min(enumerate(arr), key=operator.itemgetter(1)) 22 | # print(min_index) 23 | # print(min_number) 24 | 25 | # ###########查看较大区间及其索引 26 | # re1 = heapq.nlargest(1, arr) #求最大的三个元素,并排序 27 | # re2 = map(arr.index, heapq.nlargest(10, arr)) #求最大的三个索引 nsmallest与nlargest相反,求最小 28 | # print(re1) 29 | # print(list(re2)) #因为re2由map()生成的不是list,直接print不出来,添加list()就行了 30 | 31 | # # data_sub = data_input[data_input["充电次数"]==min_index] 32 | # data_sub = data_input[data_input["充电次数"]==199] 33 | # #去重 34 | data = data_input.drop_duplicates(['soc']) 35 | 36 | #增加标准时间便于观察时间梯度 37 | # data['标准时间'] = np.array([time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(x)) for x in data['时间'].values]) 38 | 39 | subsoc = data['soc'].values 40 | subcurrent = data['charge_current'].values 41 | subtime = data['abs_time'].values 42 | 43 | soc_gap = 0.1 44 | Ah = 0 # 累计Ah数 45 | soc = 0 46 | total_ah=0 47 | 48 | I =[0] 49 | Ampere_hour_integral = 0 50 | delta_Ah = [1.5] 51 | delta_SOC=[0] 52 | delta_Ah_total=[0] 53 | for i in range(0, len(subtime) - 1): 54 | time1 = subtime[i] 55 | time2 = subtime[i + 1] 56 | gaps = (time2 - time1) 57 | 58 | soc=(abs(subsoc[i]) + abs(subsoc[i + 1])) / 2 59 | current=(abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 60 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / 3600 61 | total_ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / 3600+total_ah 62 | subcurrent 63 | cap = Ah/soc_gap 64 | delta_SOC.append(soc) 65 | delta_Ah.append(cap) 66 | delta_Ah_total.append(total_ah) 67 | I.append(current) 68 | 69 | delta_Ah = [x*100 for x in delta_Ah] 70 | print("total_ah",total_ah) 71 | #作图 72 | from matplotlib import pyplot as plt 73 | plt.rcParams['font.sans-serif'] = ['KaiTi'] # 用来正常显示中文标签 74 | plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 75 | # plt.figure() #自定义画布大小(width,height) 76 | plt.figure( figsize=(100,80)) 77 | plt.title("广汽数据安时积分效果图") 78 | plt.xlabel("soc") 79 | plt.ylabel("△Q") 80 | plt.axhline(227, color='g', linestyle='--',label="额定容量") 81 | plt.plot(delta_SOC,I ,label="电流" ,color="orange") 82 | plt.scatter(delta_SOC,delta_Ah ,label="安时积分" ,color="r") 83 | plt.plot(delta_SOC,delta_Ah ,label="积分容量" ,color="magenta") 84 | plt.plot(delta_SOC,delta_Ah_total ,label="积分容量累加" ,color="r") 85 | plt.legend() 86 | plt.show() 87 | -------------------------------------------------------------------------------- /develop/plotc.m: -------------------------------------------------------------------------------- 1 | function [ ] = plotc( color ) 2 | %PLOTCONFIGURATION Summary of this function goes here 3 | % Detailed explanation goes here 4 | set(gcf,'Position',[322,246,520,380], 'color','w') 5 | box off 6 | 7 | a=get(gcf,'children'); 8 | 9 | while 1 10 | tags=get(a(1),'tag'); 11 | if strcmp(tags,'legend') 12 | a(1)=[]; 13 | else 14 | break; 15 | end 16 | end 17 | 18 | if length(a)==1 19 | 20 | % set(gca,'Position',[.12 .15 .83 .75]); 21 | set(gca,'OuterPosition',[-.01 .01 1.02 .98]); 22 | figure_FontSize=16; 23 | set(get(gca,'XLabel'),'FontSize',figure_FontSize,'Vertical','top'); 24 | set(get(gca,'YLabel'),'FontSize',figure_FontSize,'Vertical','baseline'); 25 | set(get(gca,'Title'),'FontSize',figure_FontSize,'Vertical','baseline'); 26 | set(findobj(gca,'FontSize',10),'FontSize',figure_FontSize); 27 | set(findobj(get(gca,'Children'),'LineWidth',0.5),'LineWidth',2); 28 | %legend('Location','Best'); 29 | elseif length(a)==2 30 | 31 | 32 | figure_FontSize=16; 33 | set(get(a(2),'XLabel'),'FontSize',figure_FontSize,'Vertical','top'); 34 | set(get(a(2),'YLabel'),'FontSize',figure_FontSize,'Vertical','baseline'); 35 | set(get(a(2),'Title'),'FontSize',figure_FontSize,'Vertical','baseline'); 36 | set(findobj(a(2),'FontSize',10),'FontSize',figure_FontSize); 37 | set(findobj(get(a(2),'Children'),'LineWidth',0.5),'LineWidth',2); 38 | try 39 | set(get(a(1),'XLabel'),'FontSize',figure_FontSize,'Vertical','top'); 40 | set(get(a(1),'YLabel'),'FontSize',figure_FontSize,'Vertical','top'); 41 | end 42 | set(get(a(1),'Title'),'FontSize',figure_FontSize,'Vertical','baseline'); 43 | set(findobj(a(1),'FontSize',10),'FontSize',figure_FontSize); 44 | set(findobj(get(a(1),'Children'),'LineWidth',0.5),'LineWidth',2); 45 | 46 | set(get(a(1),'YLabel'),'Vertical','top'); 47 | set(get(a(1),'XLabel'),'Visible','off'); 48 | set(a(1),'xlim',get(a(2),'xlim')); 49 | set(a(1),'xtick',[],'xticklabel',[]) 50 | set(a(1),'YAxisLocation','right','Box','off'); 51 | set(a(2),'Box','off'); 52 | set(a(1),'HitTest','off'); 53 | setappdata(a(2),'graphicsPlotyyPeer',a(1)); 54 | setappdata(a(1),'graphicsPlotyyPeer',a(2)); 55 | 56 | set(a(1),'OuterPosition',[-.01 .01 1.02 .98]); 57 | position1=get(a(1),'position'); 58 | set(a(2),'OuterPosition',[-.01 .01 1.02 .98]); 59 | position2=get(a(2),'position'); 60 | 61 | newposition(1)=max([position1(1),position2(1)]); 62 | newposition(3)=min(position1(1)+position1(3),position2(1)+position2(3))-newposition(1); 63 | newposition(2)=max(position1(2),position2(2)); 64 | newposition(4)=min(position1(2)+position1(4),position2(2)+position2(4))-newposition(2); 65 | 66 | set(a(1),'position',newposition) 67 | set(a(2),'Position',get(a(1),'position')) 68 | set(a(1),'Color','none'); 69 | try 70 | set(a(2),'ycolor',color{1});set(a(1),'ycolor',color{2}); 71 | end 72 | 73 | end 74 | 75 | 76 | end 77 | 78 | -------------------------------------------------------------------------------- /ML_SOH/SOH模型训练说明.md: -------------------------------------------------------------------------------- 1 | # 说明文档 v1.0.0 2 | 3 | #### 开发者: 陈娟 4 | 5 | ##### 代码结构:SOH模型训练 6 | 7 | ### 8 | 9 | 1. 功能说明:输入实车运行数据,返回SOH估算模型。 10 | 包含3个py文件:charging.py为充电数据提取程序,soc_correction.py为SOC校正程序, model.py为回归模型训练程序 11 | 12 | 13 | 2. 输入输出参数说明: 14 | (1)charging.py充电数据提取程序参数说明: 15 | 输入--文件储存路径,输出--充电特征数据dataframe 16 | 如数据文件格式不是csv,则需将文件重命名为csv;如文件不是按vin码拆分的,需按vin码拆分合并,使一辆车的数据保存在一个文件中。 17 | 18 | (2)soc_correction.py SOC校正程序参数说明: 19 | 输入: 20 | SOC列表(SOC-OCV曲线中的SOC):list 21 | OCV列表(SOC-OCV曲线中的OCV):list 22 | 使用SOC上限:float 23 | 使用SOC下限:float 24 | 待校正的电压列表:list 25 | 26 | 输出: 27 | 校正后的SOC列表:list 28 | 29 | (3)model.py回归模型训练程序参数说明; 30 | 1)建模数据过滤函数Data_filter参数说明: 31 | 输入: 32 | file_path:文件储存路径,str 33 | Tmin:筛选充电起始最低温度,默认为20,float 34 | SOCmin:筛选充电结束最高SOC,默认20,float 35 | SOCmax:筛选充电结束最低SOC,默认95,float 36 | dSOC_filter:筛选充电SOC变化不低于dSOC_filter,默认30,float 37 | km_new_filter:筛选行驶里程不超过km_new_filter,默认新车阶段为小于10000,float 38 | rest_time_filter:筛选充电前等效静置(默认<2A为等效静置)的时间不少于rest_time_filter,默认180s,float 39 | abnormal_str:异常字符,默认为[np.inf, -np.inf,'#NAME?'],list 40 | encoding:编码,默认'gbk',str 41 | 输出: 42 | 过滤后的dataframe。 43 | 2)主程序Model_train参数说明: 44 | 输入: 45 | df:Data_filter筛选后的数据,dataframe 46 | column_name:用于建模的数据列选择,list 47 | 输出: 48 | 输出精度并保存模型 49 | 50 | 51 | 4. how to run: 52 | import charging 53 | import soc_correction 54 | import model 55 | 56 | charging = Charging() 57 | column_list = ['sample_ts', 'sin_btry_hist_volt', 'sin_btry_lwst_volt', 'vehl_totl_volt', 'hist_temp_sn', 58 | 'lwst_temp_sn', 'chrg_state', 'mileage', 'vehl_totl_curnt', 'soc'] 59 | file_path=r'E:\蔚来运行数据' 60 | initial_format='xls' 61 | target_format='csv' 62 | charging.Rename(file_path, initial_format, target_format) 63 | 64 | new_path=r'E:\蔚来运行数据\按车辆组合数据' 65 | file_format='csv' 66 | column_name=['vin','sample_ts','sin_btry_hist_volt','sin_btry_lwst_volt','vehl_totl_volt','btry_pak_hist_temp','btry_pak_lwst_temp','chrg_state','mileage','vehl_totl_curnt','soc'] 67 | charging.Car_merge(file_path, new_path, file_format, column_name, 'vin') 68 | 69 | new_path = r'E:\蔚来运行数据' 70 | charge_code=[1] 71 | df_cha=charging.Processing(new_path, file_format, column_list, charge_code, encoding='gbk', time_format='%Y-%m-%d %H:%M:%S', rest_I=2) 72 | 73 | soc_cor=Soc_correction() 74 | SOC = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 96, 97, 100] 75 | OCV = [3.281, 3.508, 3.568, 3.597, 3.622, 3.641, 3.682, 3.756, 3.842, 3.94, 4.05, 4.125, 4.15, 4.175] 76 | socmin=5 77 | socmax=97 78 | ocv_list=[3.28, 3.50, 3.56, 3.59, 3.62, 3.64, 3.68, 3.75, 3.84, 3.4, 4.05, 4.125, 4.1, 4.175,4.18] 79 | soc_cor=Soc_correction() 80 | df_cha['充电前校正SOCmin']=np.array(soc_cor.soc_correction(SOC,OCV,socmin,socmax,list(df_cha['充电前Vmin']))) 81 | df_cha['充电前校正SOCmax']=np.array(soc_cor.soc_correction(SOC,OCV,socmin,socmax,list(df_cha['充电前Vmax']))) 82 | 83 | model=Model() 84 | file_path=r'F:\运行数据\A-标准化程序\SOH_model\soh_model\test\蔚来充电特征数据.csv' 85 | model_column=['运行里程', '充电前Vmin', '充电前Vmax', '充电前Vd', '充电结束Vmin', '充电结束Vmax', '充电结束Vd', 86 | '充电起始Tmin','充电起始Tmax', '充电结束Tmin', '充电结束Tmax', '充电起始SOC','充电结束SOC','SOC变化','充入容量'] 87 | print(model.Model_train(model.Data_filter(file_path),model_column)) 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | #!usr/bin/python3 3 | #Copyright 2020 Think Team, Inc. All right reserved. 4 | #Author: James_Bobo 5 | #Completion Date: 2020-10-29 6 | from SOHCal import * 7 | from matplotlib import pyplot as plt 8 | 9 | def estimate_soh(data): 10 | parameter_a = { 'x_min_zmf10': 0, 11 | 'x_min_zmf11': 8.333, 12 | 'x_min_trimf1': [0, 16.67, 33.33], 13 | 'x_min_trimf2': [8.333, 25, 41.67], 14 | 'x_min_trimf3': [16.67, 33.33, 50], 15 | 'x_min_smf10': 41.67, 16 | 'x_min_smf11': 50, 17 | 'x_E_zmf10': 0, 18 | 'x_E_zmf11': 0.1125, 19 | 'x_E_smf10': 0.1125, 20 | 'x_E_smf11': 0.15, 21 | 'y_zmf10': 0.01, 22 | 'y_zmf11': 0.1575, 23 | 'y_trimf1': [0.01, 0.1575, 0.305], 24 | 'y_trimf2': [0.1575, 0.305, 0.4525], 25 | 'y_trimf3': [0.305, 0.4525, 0.6], 26 | 'y_smf10': 0.4525, 27 | 'y_smf11': 0.6, 28 | 'y_zmf20': 0.01, 29 | 'y_zmf21': 0.1, 30 | 'y_trimf4': [0.541, 5.91, 11.81]} 31 | x_minSOC_range_a = np.arange(0, 50, 0.01, np.float32)#0-0.5等差排列的5000个数 32 | x_E_range_a = np.arange(0, 0.15, 0.001, np.float32)#0-0.15等差排列的150个数 33 | y_alpha_range_a = np.arange(0.01, 0.6, 0.001, np.float32)#0-0.6等差排列的590个数 34 | 35 | parameter_b = { 'x_min_zmf10': 0, 36 | 'x_min_zmf11': 8.333, 37 | 'x_min_trimf1': [0, 16.67, 33.33], 38 | 'x_min_trimf2': [8.333, 25, 41.67], 39 | 'x_min_trimf3': [16.67, 33.33, 50], 40 | 'x_min_smf10': 41.67, 41 | 'x_min_smf11': 50, 42 | 'x_E_zmf10': 0, 43 | 'x_E_zmf11': 0.1125, 44 | 'x_E_smf10': 0.1125, 45 | 'x_E_smf11': 0.15, 46 | 'y_zmf10': 0.5, 47 | 'y_zmf11': 0.625, 48 | 'y_trimf1': [0.5, 0.625, 0.75], 49 | 'y_trimf2': [0.625, 0.75, 0.875], 50 | 'y_trimf3': [0.75, 0.875, 1], 51 | 'y_smf10': 0.875, 52 | 'y_smf11': 1, 53 | 'y_zmf20': 0.5, 54 | 'y_zmf21': 0.575, 55 | 'y_trimf4': [0.95, 5.5, 10.5]} 56 | x_minSOC_range_b = np.arange(0, 50, 0.1, np.float32)#0-0.5等差排列的5000个数 57 | x_E_range_b = np.arange(0, 0.15, 0.001, np.float32)#0-0.15等差排列的150个数 58 | y_alpha_range_b = np.arange(0.5, 1, 0.001, np.float32)#0.6-1等差排列的590个数 59 | 60 | KF = KalFuzzy(parameter_a=parameter_a, 61 | x_minSOC_range_a=x_minSOC_range_a, 62 | x_E_range_a=x_E_range_a, 63 | y_alpha_range_a=y_alpha_range_a, 64 | parameter_b=parameter_b, 65 | x_E_range_b=x_E_range_b, 66 | x_minSOC_range_b=x_minSOC_range_b, 67 | y_alpha_range_b=y_alpha_range_b) 68 | print("kf",KF) 69 | 70 | data_cal = DataCalculation() 71 | data_cal.get_singlecar_soh(car=data, InfoFrom=0, df=0, para60=60, capCase=0, constCap=0) 72 | result_T, SOH = karman(KF, data_cal.abstable) 73 | return result_T, SOH 74 | 75 | 76 | if __name__ == '__main__': 77 | data_input = pd.read_csv(r'./153Ah充电数据.csv', encoding="gbk") 78 | print(data_input.head()) 79 | result_T, SOH = estimate_soh(data=data_input) 80 | print("SOH",SOH) 81 | print("result_T",result_T) 82 | print("len(SOH)",len(SOH),"len(result_T)",len(result_T)) 83 | plt.plot(result_T, SOH) 84 | plt.show() 85 | -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/KalmanFuzzy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | #!usr/bin/python3 3 | #Copyright 2020 Think Team, Inc. All right reserved. 4 | #Author: James_Bobo 5 | #Completion Date: 2020-10-29 6 | # kalman filter 7 | import numpy as np 8 | from scipy.optimize import curve_fit 9 | from FuzzyControl import FuzzyCtr 10 | 11 | 12 | class KalFuzzy: 13 | def __init__(self, parameter_a, 14 | x_minSOC_range_a, 15 | x_E_range_a, 16 | y_alpha_range_a, 17 | parameter_b, 18 | x_minSOC_range_b, 19 | x_E_range_b, 20 | y_alpha_range_b): 21 | self.A = FuzzyCtr(parameter=parameter_a, 22 | x_minSOC_range=x_minSOC_range_a, 23 | x_E_range=x_E_range_a, 24 | y_alpha_range=y_alpha_range_a) 25 | self.A.rule() 26 | self.B = FuzzyCtr(parameter=parameter_b, 27 | x_minSOC_range=x_minSOC_range_b, 28 | x_E_range=x_E_range_b, 29 | y_alpha_range=y_alpha_range_b) 30 | self.B.rule() 31 | self.new_SOH = None 32 | 33 | def kf_fl(self, minSOC, maxSOC, Time, SOH, flag=0): 34 | # 每个数据是一个列向量 35 | x1 = minSOC # 起始SOC ******** 36 | x2 = maxSOC # 终止SOC ******** 37 | n = Time.shape[0] # 计算连续n个时刻 ******** 38 | Q = 0.03 ** 2 39 | P = np.zeros(n) 40 | P[0] = 1 # 初始是否有误差? 41 | 42 | # 初始化 43 | z = SOH 44 | self.new_SOH = np.zeros(n) 45 | xhatminus = np.zeros(n) # % SOH的先验估计。即在k-1时刻,对k时刻SOH做出的估计 46 | Pminus = np.zeros(n) # % 先验估计的方差 47 | K = np.zeros(n) # % 卡尔曼增益。 48 | alpha = np.zeros(n) # % SOH权重 49 | 50 | if flag == 0: 51 | self.new_SOH[0] = np.max(SOH) # % 理论上是取平均值,由于原始估计不理想,故在此取最大值 **** 52 | if self.new_SOH[0] >= 100: 53 | self.new_SOH[0] = 99 54 | else: 55 | self.new_SOH[0] = np.mean(SOH) 56 | 57 | # 循环执行,找到新的SOH 58 | for k in range(1, n): 59 | # 时间更新(预测) 60 | xhatminus[k] = self.new_SOH[k - 1] # 用上一时刻的最优估计值来作为对当前时刻的SOH的预测 61 | Pminus[k] = P[k - 1] + Q # 预测的方差为上一时刻SOH最优估计值的方差与过程方差(为常数)之和 62 | # 测量更新(校正) 63 | e1 = abs(z[k] - self.new_SOH[k - 1]) / self.new_SOH[k - 1] 64 | 65 | # 卡尔曼滤波更新 66 | if x1[k] > 50 and e1 > 0.15: 67 | alpha[k] = 100 68 | elif x2[k] >= 99: 69 | alpha[k] = self.A.defuzzy_a(x1[k], e1) 70 | else: 71 | alpha[k] = self.B.defuzzy_a(x1[k], e1) 72 | R = (alpha[k] * 1.5) ** 2 73 | K[k] = Pminus[k] / (Pminus[k] + R) # 计算卡尔曼增益 74 | self.new_SOH[k] = xhatminus[k] + K[k] * ( 75 | z[k] - xhatminus[k]) # 结合当前时SOH计算值,对上一时刻的预测进行校正,得到校正后的最优估计。该估计具有最小均方差 76 | P[k] = (1 - K[k]) * Pminus[k] # 计算最终估计值的方差 77 | print("k",k) 78 | print("self.new_SOH",self.new_SOH) 79 | 80 | 81 | return self.new_SOH 82 | 83 | 84 | # 阿伦尼乌斯拟合 85 | def arrhenius_func(x, a, b, c): 86 | ahenius = a * (x ** b) + c 87 | return ahenius 88 | 89 | # 阿伦尼乌斯拟合 90 | class AlFit: 91 | def __init__(self): 92 | self.__a = 0 93 | self.__b = 0 94 | self.__c = 0 95 | self.isFitted = 0 96 | 97 | def arrhenius(self, x): 98 | return self.__a * (x ** self.__b) + self.__c 99 | 100 | def fit(self, x_data, y_data): 101 | try: 102 | popt, pcov = curve_fit(arrhenius_func, x_data, y_data) 103 | self.__a = popt[0] 104 | self.__b = popt[1] 105 | self.__c = popt[2] 106 | print("popt",popt) 107 | print("pcov",pcov) 108 | print("a:", self.__a, ',b:', self.__b, ',c:', self.__c) 109 | return 1 110 | except ValueError: 111 | print("error") 112 | return -1 113 | 114 | def get_para(self): 115 | return self.__a, self.__b, self.__c 116 | 117 | def set_para(self, a, b, c): 118 | self.__a = a 119 | self.__b = b 120 | self.__c = c 121 | -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/FuzzyControl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | #!usr/bin/python3 3 | #Copyright 2020 Think Team, Inc. All right reserved. 4 | #Author: James_Bobo 5 | #Completion Date: 2020-10-29 6 | import skfuzzy as fuzz 7 | import skfuzzy.control as ctrl 8 | import numpy as np 9 | 10 | 11 | # 模糊控制模块 12 | class FuzzyCtr: 13 | 14 | def __init__(self, parameter, x_minSOC_range, x_E_range, y_alpha_range): 15 | # for fuzzy maxSOC>99% 16 | 17 | # 创建模糊控制变量 18 | self.x_minSOC = ctrl.Antecedent(x_minSOC_range, 'minSOC') 19 | self.x_E = ctrl.Antecedent(x_E_range, 'E') 20 | self.y_alpha = ctrl.Consequent(y_alpha_range, 'alpha') 21 | 22 | # 定义模糊集和其隶属度函数 23 | self.x_minSOC['NB'] = fuzz.zmf(x_minSOC_range, parameter['x_min_zmf10'], parameter['x_min_zmf11']) 24 | self.x_minSOC['NS'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf1']) 25 | self.x_minSOC['ZO'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf2']) 26 | self.x_minSOC['PS'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf3']) 27 | self.x_minSOC['PB'] = fuzz.smf(x_minSOC_range, parameter['x_min_smf10'], parameter['x_min_smf11']) 28 | 29 | self.x_E['NB'] = fuzz.zmf(x_E_range, parameter['x_E_zmf10'], parameter['x_E_zmf11']) 30 | self.x_E['PB'] = fuzz.smf(x_E_range, parameter['x_E_smf10'], parameter['x_E_smf11']) 31 | 32 | self.y_alpha['NB'] = fuzz.zmf(y_alpha_range, parameter['y_zmf10'], parameter['y_zmf11']) 33 | self.y_alpha['NS'] = fuzz.trimf(y_alpha_range, parameter['y_trimf1']) 34 | self.y_alpha['ZO'] = fuzz.trimf(y_alpha_range, parameter['y_trimf2']) 35 | self.y_alpha['PS'] = fuzz.trimf(y_alpha_range, parameter['y_trimf3']) 36 | self.y_alpha['PB'] = fuzz.smf(y_alpha_range, parameter['y_smf10'], parameter['y_smf11']) 37 | self.y_alpha['NB1'] = fuzz.zmf(y_alpha_range, parameter['y_zmf20'], parameter['y_zmf21']) 38 | self.y_alpha['PB1'] = fuzz.trimf(y_alpha_range, parameter['y_trimf4']) 39 | 40 | # 设定输出alpha的解模糊方法——质心解模糊方式 41 | self.y_alpha.defuzzify_method = 'mom' 42 | self.system = None 43 | self.sim = None 44 | 45 | def rule(self): 46 | # 输出为NB的规则 47 | rule0 = ctrl.Rule(antecedent=(self.x_minSOC['NB'] & self.x_E['NB']), 48 | consequent=self.y_alpha['NB'], label='rule 1') 49 | # 输出为NS的规则 50 | rule1 = ctrl.Rule(antecedent=(self.x_minSOC['NS'] & self.x_E['NB']), 51 | consequent=self.y_alpha['NS'], label='rule 2') 52 | # 输出为NS的规则 53 | rule2 = ctrl.Rule(antecedent=(self.x_minSOC['NB'] & self.x_E['PB']), 54 | consequent=self.y_alpha['PB'], label='rule 3') 55 | # 输出为NS的规则 56 | rule3 = ctrl.Rule(antecedent=(self.x_minSOC['NS'] & self.x_E['PB']), 57 | consequent=self.y_alpha['PB'], label='rule 4') 58 | # 输出为NS的规则 59 | rule4 = ctrl.Rule(antecedent=(self.x_minSOC['ZO'] & self.x_E['NB']), 60 | consequent=self.y_alpha['PS'], label='rule 5') 61 | # 输出为NS的规则 62 | rule5 = ctrl.Rule(antecedent=(self.x_minSOC['ZO'] & self.x_E['PB']), 63 | consequent=self.y_alpha['PB1'], label='rule 6') 64 | # 输出为NS的规则 65 | rule6 = ctrl.Rule(antecedent=(self.x_minSOC['PS'] & self.x_E['NB']), 66 | consequent=self.y_alpha['PS'], label='rule 7') 67 | # 输出为NS的规则 68 | rule7 = ctrl.Rule(antecedent=(self.x_minSOC['PS'] & self.x_E['PB']), 69 | consequent=self.y_alpha['PB'], label='rule 8') 70 | # 输出为NS的规则 71 | rule8 = ctrl.Rule(antecedent=(self.x_minSOC['PB'] & self.x_E['NB']), 72 | consequent=self.y_alpha['PB'], label='rule 9') 73 | # 输出为NS的规则 74 | rule9 = ctrl.Rule(antecedent=(self.x_minSOC['PB'] & self.x_E['PB']), 75 | consequent=self.y_alpha['PB'], label='rule 10') 76 | 77 | # 系统和运行环境初始化 78 | self.system = ctrl.ControlSystem(rules=[rule0, rule1, rule2, rule3, rule4, rule5, rule6, rule7, rule8, rule9]) 79 | self.sim = ctrl.ControlSystemSimulation(self.system) 80 | 81 | def defuzzy_a(self, minSoc, E): 82 | # Pass inputs to the ControlSystem using Antecedent labels with Pythonic API 83 | # Note: if you like passing many inputs all at once, use .inputs(dict_of_data) 84 | self.sim.input['minSOC'] = minSoc 85 | self.sim.input['E'] = E 86 | 87 | # Crunch the numbers 88 | self.sim.compute() 89 | return self.sim.output['alpha'] 90 | 91 | 92 | if __name__ == "__main__": 93 | # FuzzyCtr的输入参数 94 | parameter_a = {'x_min_zmf10': 0, 95 | 'x_min_zmf11': 8.333, 96 | 'x_min_trimf1': [0, 16.67, 33.33], 97 | 'x_min_trimf2': [8.333, 25, 41.67], 98 | 'x_min_trimf3': [16.67, 33.33, 50], 99 | 'x_min_smf10': 41.67, 100 | 'x_min_smf11': 50, 101 | 'x_E_zmf10': 0, 102 | 'x_E_zmf11': 0.1125, 103 | 'x_E_smf10': 0.1125, 104 | 'x_E_smf11': 0.15, 105 | 'y_zmf10': 0.01, 106 | 'y_zmf11': 0.1575, 107 | 'y_trimf1': [0.01, 0.1575, 0.305], 108 | 'y_trimf2': [0.1575, 0.305, 0.4525], 109 | 'y_trimf3': [0.305, 0.4525, 0.6], 110 | 'y_smf10': 0.4525, 111 | 'y_smf11': 0.6, 112 | 'y_zmf20': 0.01, 113 | 'y_zmf21': 0.1, 114 | 'y_trimf4': [0.541, 5.91, 11.81]} 115 | x_minSOC_range_a = np.arange(0, 50, 0.01, np.float32) 116 | x_E_range_a = np.arange(0, 0.15, 0.001, np.float32) 117 | y_alpha_range_a = np.arange(0.01, 0.6, 0.001, np.float32) 118 | 119 | parameter_b = {'x_min_zmf10': 0, 120 | 'x_min_zmf11': 8.333, 121 | 'x_min_trimf1': [0, 16.67, 33.33], 122 | 'x_min_trimf2': [8.333, 25, 41.67], 123 | 'x_min_trimf3': [16.67, 33.33, 50], 124 | 'x_min_smf10': 41.67, 125 | 'x_min_smf11': 50, 126 | 'x_E_zmf10': 0, 127 | 'x_E_zmf11': 0.1125, 128 | 'x_E_smf10': 0.1125, 129 | 'x_E_smf11': 0.15, 130 | 'y_zmf10': 0.5, 131 | 'y_zmf11': 0.625, 132 | 'y_trimf1': [0.5, 0.625, 0.75], 133 | 'y_trimf2': [0.625, 0.75, 0.875], 134 | 'y_trimf3': [0.75, 0.875, 1], 135 | 'y_smf10': 0.875, 136 | 'y_smf11': 1, 137 | 'y_zmf20': 0.5, 138 | 'y_zmf21': 0.575, 139 | 'y_trimf4': [0.95, 5.5, 10.5]} 140 | x_minSOC_range_b = np.arange(0, 50, 0.1, np.float32) 141 | x_E_range_b = np.arange(0, 0.15, 0.001, np.float32) 142 | y_alpha_range_b = np.arange(0.5, 1, 0.001, np.float32) 143 | 144 | A = FuzzyCtr(parameter=parameter_a, x_minSOC_range=x_minSOC_range_a, x_E_range=x_E_range_a, 145 | y_alpha_range=y_alpha_range_a) 146 | B = FuzzyCtr(parameter=parameter_b, x_minSOC_range=x_minSOC_range_b, x_E_range=x_E_range_b, 147 | y_alpha_range=y_alpha_range_b) 148 | 149 | # visualization 150 | A.y_alpha.view() 151 | B.y_alpha.view() 152 | -------------------------------------------------------------------------------- /ML_SOH/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import time 3 | import pandas as pd 4 | from sklearn import linear_model 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from sklearn.model_selection import train_test_split 8 | from sklearn.linear_model import RidgeCV,LassoCV 9 | from sklearn.ensemble import RandomForestRegressor,AdaBoostRegressor,GradientBoostingRegressor,BaggingRegressor 10 | from sklearn.preprocessing import MinMaxScaler 11 | import joblib 12 | 13 | plt.rcParams['font.sans-serif'] = ['SimHei'] 14 | plt.rcParams["axes.unicode_minus"] = False 15 | 16 | class Model: 17 | def Data_filter(self,file_path, Tmin=20, SOCmax=95, SOCmin=20, dSOC_filter=30,km_new_filter=10000, rest_time_filter=0, abnormal_str=[np.inf, -np.inf,'#NAME?'], encoding='gbk'): 18 | ''' 19 | 对提取的充电统计数据进行筛选,筛选用于新电池SOE建模的数据 20 | :param file_path: 充电统计的储存路径 21 | :param Tmin: 充电起始最小温度筛选条件 22 | :param SOCmax: 充电结束SOC筛选条件 23 | :param SOCmin: 充电起始SOC筛选条件 24 | :param dSOC_filter: SOC变化筛选条件 25 | :param km_new_filter: 认为是新电池阶段的最大里程 26 | :param rest_time_filter: 可以用于SOC校正的小电流持续时间 27 | :param abnormal_str: 异常值处理 28 | :param encoding: 默认编码 29 | :return:None 30 | ''' 31 | 32 | df=pd.read_csv(file_path,encoding=encoding) 33 | 34 | #异常值处理 35 | df=df.loc[(df.SOC丢失==0)&(df.充电起始Tmin>Tmin)&(df.充电起始SOCSOCmax)&(df.SOC变化>dSOC_filter)&(df.运行里程= rest_time_filter),:] 36 | '''对df去重重复值,函数默认根据所有重复值进行了判断去重,并保留了第一行''' 37 | df=df.drop_duplicates() 38 | print('全量数据特征维度{}'.format(df.shape)) 39 | df.replace(abnormal_str, np.nan, inplace=True) 40 | df=df.dropna(axis=0, how='any').reset_index(drop=True) 41 | print('删除异常数据特征维度{}'.format(df.shape)) 42 | df.to_csv('模型训练数据.csv',encoding='gbk') 43 | return df 44 | def Model_train(self,df,column_name): 45 | ''' 46 | 对建模数据标准化后,试用几个常见的回归方法进行建模,根据前期经验以下几种方法精度较好,LinearRegression,Ridge,lasso,RandomForestRegressor,AdaBoostRegressor,GradientBoostingRegressor,BaggingRegressor 47 | :param column_name:用于建模的列名, list形式 48 | :return:None 49 | ''' 50 | #1,划分测试集和训练集 51 | model_df=df.loc[:,column_name] 52 | X=model_df.values[:,:-1] 53 | print('变量维度{}'.format(X.shape)) 54 | y=model_df.values[:,-1:] 55 | 56 | x_train,x_test,y_train,y_test=train_test_split(X,y,random_state=10,test_size=0.25) 57 | print('训练数据特征维度{},标签维度{}'.format(x_train.shape,y_train.shape)) 58 | minmax=MinMaxScaler(feature_range=(0,1)) 59 | x_train=minmax.fit_transform(x_train) 60 | x_test=minmax.transform(x_test) 61 | joblib.dump(minmax, 'minmax.pkl') 62 | 63 | #2,模型选择 64 | # 1)LinearRegression 65 | time1=time.perf_counter() 66 | model = linear_model.LinearRegression() 67 | model.fit(x_train, y_train) 68 | y_predict = model.predict(x_test) 69 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 70 | plt.title('LinearRegression, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 71 | plt.savefig('LinearRegression') 72 | plt.show() 73 | time2=time.perf_counter() 74 | print('LinearRegression,训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format((time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 75 | # 2)Ridge 76 | time1=time.perf_counter() 77 | model=RidgeCV(alphas=np.array([0.001, 0.01, 0.1, 0.0001])) 78 | model.fit(x_train,y_train) 79 | print(model.alpha_) 80 | y_fit = model.predict(x_train) 81 | y_predict = model.predict(x_test) 82 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 83 | plt.title('ridge,训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 84 | plt.savefig('Ridge') 85 | plt.show() 86 | joblib.dump(model, 'Ridge.pkl') 87 | time2=time.perf_counter() 88 | print('ridge,训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format((time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 89 | 90 | #3)lasso 91 | time1=time.perf_counter() 92 | model = LassoCV(alphas=np.array([0.1,0.01,0.001,0.0001])) 93 | model.fit(x_train,y_train) 94 | y_predict = model.predict(x_test).reshape(-1,1) 95 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 96 | plt.title('lasso, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 97 | plt.savefig('lasso') 98 | plt.show() 99 | time2=time.perf_counter() 100 | print('lasso, 学习率{}, 训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.alpha_,(time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 101 | 102 | # 4)RandomForestRegressor 103 | time1=time.perf_counter() 104 | model=RandomForestRegressor(max_depth=20,random_state=0,n_estimators=20) 105 | model.fit(x_train,y_train) 106 | y_predict = model.predict(x_test).reshape(-1,1) 107 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 108 | plt.title('RandomForestRegressor, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 109 | plt.savefig('RandomForestRegressor') 110 | plt.show() 111 | time2=time.perf_counter() 112 | print('RandomForestRegressor, 训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format((time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 113 | #5)AdaBoostRegressor 114 | time1=time.perf_counter() 115 | model=AdaBoostRegressor() 116 | model.fit(x_train,y_train) 117 | y_predict = model.predict(x_test).reshape(-1,1) 118 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 119 | plt.title('AdaBoostRegressor,训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 120 | plt.savefig('AdaBoostRegressor.png') 121 | plt.show() 122 | time2=time.perf_counter() 123 | print('AdaBoostRegressor, 训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format((time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 124 | # 6)GradientBoostingRegressor 125 | time1=time.perf_counter() 126 | model=GradientBoostingRegressor() 127 | model.fit(x_train,y_train) 128 | y_predict = model.predict(x_test).reshape(-1,1) 129 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 130 | plt.title('GradientBoostingRegressor,训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 131 | plt.savefig('GradientBoostingRegressor.png') 132 | plt.show() 133 | time2=time.perf_counter() 134 | print('GradientBoostingRegressor, 训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format((time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 135 | # 7)BaggingRegressor 136 | time1=time.perf_counter() 137 | model=BaggingRegressor(AdaBoostRegressor()) 138 | model.fit(x_train,y_train) 139 | y_predict = model.predict(x_test).reshape(-1,1) 140 | plt.scatter(np.arange((y_predict/y_test).shape[0]),y_predict/y_test) 141 | plt.title('BaggingRegressor, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format(model.score(x_train,y_train),model.score(x_test,y_test))) 142 | plt.savefig('BaggingRegressor.png') 143 | plt.show() 144 | time2=time.perf_counter() 145 | print('BaggingRegressor, 训练时间{:.4f}, 训练集精度:{:.4f}, 测试集精度:{:.4f}'.format((time2-time1),model.score(x_train,y_train),model.score(x_test,y_test))) 146 | return '模型训练完成' 147 | if __name__ == '__main__': 148 | model=Model() 149 | file_path=r'F:\运行数据\A-标准化程序\SOH_model\soh_model\test\蔚来充电特征数据.csv' 150 | #选择用于建模的列 151 | model_column=['运行里程', '充电前Vmin', '充电前Vmax', '充电前Vd', '充电结束Vmin', '充电结束Vmax', '充电结束Vd', 152 | '充电起始Tmin','充电起始Tmax', '充电结束Tmin', '充电结束Tmax', '充电起始SOC','充电结束SOC','SOC变化','充入容量'] 153 | print(model.Model_train(model.Data_filter(file_path),model_column)) 154 | -------------------------------------------------------------------------------- /develop/SOH_Estimate.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "运行开始!\n", 13 | "运行中......\n", 14 | "运行结束!\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "import pandas as pd\n", 20 | "import numpy as np\n", 21 | "import re\n", 22 | "import os\n", 23 | "import datetime\n", 24 | "import time\n", 25 | "\n", 26 | "## 通过原始数据文件夹中的每一辆车的数据,计算每一辆车的SOH,并且把相应的结果都存放到结果文件夹中。\n", 27 | "\n", 28 | "print('运行开始!')\n", 29 | "print('运行中......')\n", 30 | "\n", 31 | "Path=\"D:\\company\\SOH分析\" # 运行前,修改一下Path这个路径,就是“SOH分析”存放的文件夹路径!\n", 32 | "\n", 33 | "path0=Path+'\\SOH分析\\数据\\用于估计SOH的数据\\\\'\n", 34 | "path10=Path+'\\SOH分析\\数据\\SOH估计结果\\\\'\n", 35 | "\n", 36 | "\n", 37 | "xlsxnames=os.listdir(path0)\n", 38 | "for xlsxname in xlsxnames:\n", 39 | " if xlsxname.find('.xlsx')>=0:\n", 40 | " # print(xlsxname)\n", 41 | " path1=path0+\"\\\\\"+xlsxname\n", 42 | " \n", 43 | " abstable=pd.DataFrame(columns=['车号','Time','capinit ','capinitk','cap','capk','Soh','minSOC','maxSOC'])\n", 44 | " temptable=[]\n", 45 | " initsign=0\n", 46 | " capsign=0\n", 47 | " capinitk=0\n", 48 | " capinit=0\n", 49 | " capk=0\n", 50 | " cap=0\n", 51 | " soh1=0\n", 52 | " soh=0\n", 53 | " \n", 54 | "\n", 55 | " timelist=[]\n", 56 | " namelist=[]\n", 57 | " \n", 58 | " \n", 59 | " data_sheet=pd.read_excel(path1)\n", 60 | " data=pd.DataFrame(data_sheet)\n", 61 | "\n", 62 | " grouped=data.groupby('充电信号')\n", 63 | "\n", 64 | " for subgroup in grouped:\n", 65 | " if subgroup[0]>0:\n", 66 | " subtime=[]\n", 67 | " t1=[]\n", 68 | " t2=[]\n", 69 | " Taverage1=0\n", 70 | " Taverage2=0\n", 71 | " Tsum1=0\n", 72 | " Tsum2=0\n", 73 | "\n", 74 | " subcurrent=[]\n", 75 | " subsoc=[]\n", 76 | " maxsoc=max(subgroup[1]['soc'])\n", 77 | " #print(maxsoc)\n", 78 | " minsoc=min(subgroup[1]['soc'])\n", 79 | " socgap=maxsoc-minsoc\n", 80 | " \n", 81 | " Vin=max(subgroup[1]['车号'])\n", 82 | " \n", 83 | " for temp1 in subgroup[1]['absTime']:\n", 84 | " subtime.append(temp1)\n", 85 | " mintime=subtime[0]\n", 86 | " \n", 87 | " if minsoc<50 and socgap>20: \n", 88 | "\n", 89 | "\n", 90 | " for temp1 in subgroup[1]['batteryMaxTemp']:\n", 91 | " t1.append(temp1)\n", 92 | " ModT1=t1[0]\n", 93 | "\n", 94 | " for temp1 in subgroup[1]['batteryMinTemp']:\n", 95 | " t2.append(temp1)\n", 96 | " ModT2=t2[0]\n", 97 | "\n", 98 | "\n", 99 | " for counter4 in range(0,len(subtime)):\n", 100 | " Tsum1=t1[counter4]+Tsum1\n", 101 | " Tsum2=t2[counter4]+Tsum2\n", 102 | "\n", 103 | "\n", 104 | " Taverage1=Tsum1/len(subtime)\n", 105 | " Taverage2=Tsum2/len(subtime)\n", 106 | " #Taverage=pd.DataFrame([Taverage1,Taverage2])\n", 107 | "\n", 108 | " Taveragezong=(Taverage1+Taverage2)/2\n", 109 | "\n", 110 | "\n", 111 | "\n", 112 | " for temp1 in subgroup[1]['chargeAi']:\n", 113 | " subcurrent.append(temp1)\n", 114 | "\n", 115 | " for temp1 in subgroup[1]['soc']:\n", 116 | " subsoc.append(temp1)\n", 117 | " currentsum=0\n", 118 | " current60=0\n", 119 | " for counter3 in range(0,len(subtime)-1):\n", 120 | " # time1=datetime.datetime.strptime(subtime[counter3],\"%Y-%m-%d %H:%M:%S\")\n", 121 | " # time2=datetime.datetime.strptime(subtime[counter3+1],\"%Y-%m-%d %H:%M:%S\")\n", 122 | " time1=subtime[counter3]\n", 123 | " time2=subtime[counter3+1]\n", 124 | " # gaps=(time2-time1).total_seconds()\n", 125 | " gaps=(time2-time1)\n", 126 | " currentsum=(subcurrent[counter3]+subcurrent[counter3+1])/2*gaps/3600+currentsum\n", 127 | " if subsoc[counter3]<60.1 and subsoc[counter3]>59.5:\n", 128 | " current60=currentsum\n", 129 | " if maxsoc<50:\n", 130 | " current60=currentsum\n", 131 | " if initsign<1:\n", 132 | " initsign=1\n", 133 | " capinit=current60/(min(60,maxsoc)-minsoc)*100\n", 134 | " \n", 135 | " else:\n", 136 | " capinitk=(current60/(min(60,maxsoc)-minsoc))*100\n", 137 | " if abs(capinitk-capinit)>0.1*capinit:\n", 138 | " signal=1\n", 139 | " capinit=capinit \n", 140 | " else:\n", 141 | " signal=0\n", 142 | " capinit=0.9*capinit+0.1*capinitk\n", 143 | " \n", 144 | "\n", 145 | " if maxsoc>=90 and minsoc<50 and capinit>0 and socgap>0 and (maxsoc-minsoc)>0:\n", 146 | " if capsign<1:\n", 147 | " capsign=1\n", 148 | " capk=currentsum/socgap*100\n", 149 | " cap=capk\n", 150 | " soh1=(capk/capinit)*100\n", 151 | " else:\n", 152 | " capk=(currentsum/(maxsoc-minsoc))*100\n", 153 | " cap=0.9*cap+0.1*capk\n", 154 | "# if abs(capk-cap)>0.1*cap:\n", 155 | "# cap=cap \n", 156 | "# else:\n", 157 | "# cap=0.9*cap+0.1*capk \n", 158 | "# soh1=(cap/capinit)*100\n", 159 | "\n", 160 | "\n", 161 | " if capk0 and capinit>0:\n", 162 | " capk=(currentsum/(maxsoc-minsoc))*100\n", 163 | " cap=0.9*cap+0.1*capk\n", 164 | " else:\n", 165 | " cap=cap\n", 166 | " soh1=(cap/capinit)*100\n", 167 | " soh=soh1*(1-0.02*(Taveragezong-25)/10) # 温度修正,修正到25℃\n", 168 | "\n", 169 | "\n", 170 | " temptable=pd.DataFrame([Vin,mintime,capinit,capinitk,cap,capk,soh,minsoc,maxsoc]).T\n", 171 | " temptable.columns=['车号','Time','capinit ','capinitk','cap','capk','Soh','minSOC','maxSOC']\n", 172 | " abstable=pd.concat([abstable,temptable],axis=0,sort=False,ignore_index=True)\n", 173 | "\n", 174 | " \n", 175 | " wpath=path10+'\\\\'+xlsxname+'sohdata.xlsx'\n", 176 | " writer=pd.ExcelWriter(wpath,engine='openpyxl')\n", 177 | " abstable.to_excel(writer,sheet_name='data',index=False)\n", 178 | " writer.save()\n", 179 | " writer.close()\n", 180 | "\n", 181 | "print('运行结束!')" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": null, 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": null, 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [] 204 | } 205 | ], 206 | "metadata": { 207 | "kernelspec": { 208 | "display_name": "Python 3", 209 | "language": "python", 210 | "name": "python3" 211 | }, 212 | "language_info": { 213 | "codemirror_mode": { 214 | "name": "ipython", 215 | "version": 3 216 | }, 217 | "file_extension": ".py", 218 | "mimetype": "text/x-python", 219 | "name": "python", 220 | "nbconvert_exporter": "python", 221 | "pygments_lexer": "ipython3", 222 | "version": "3.6.5" 223 | } 224 | }, 225 | "nbformat": 4, 226 | "nbformat_minor": 2 227 | } 228 | -------------------------------------------------------------------------------- /develop/LYF_SOH算法_python/SOHCal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | #!usr/bin/python3 3 | #Copyright 2020 Think Team, Inc. All right reserved. 4 | #Author: James_Bobo 5 | #Completion Date: 2020-10-28 6 | import pandas as pd 7 | from KalmanFuzzy import * 8 | 9 | # 获得SOH平均衰减速率 10 | def rate_of_decrease(SOH, Time): 11 | rate = (SOH[0] - SOH[-1]) / (Time[-1] - Time[0]) 12 | return rate 13 | 14 | # 卡尔曼滤波 15 | def karman(KF, abstable): 16 | print("abstable",abstable) 17 | SOH = np.array(abstable['Soh']) 18 | Time = np.array(abstable['Time']) 19 | minSOC = np.array(abstable['minSOC']) 20 | maxSOC = np.array(abstable['maxSOC']) 21 | 22 | if SOH.shape[0] < 10: 23 | # 数据异常车辆 24 | print('too small\n') 25 | return [] 26 | 27 | SOH3 = KF.kf_fl(minSOC, maxSOC, Time, SOH) 28 | 29 | abstable['resultSOH'] = SOH3 30 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) 31 | Time = np.array(abstable['Time']) 32 | print("Time+++++++++++++++++++++++++++++++++++++++++++",Time) 33 | # 阿伦尼乌斯拟合 34 | # 返回的列表 35 | # Time = np.array(abstable['mileage']) 36 | ret_val = [] 37 | SOH_hat = None 38 | abstable.to_csv("./Test_1.csv", encoding="utf-8-sig", header=False, index=False) 39 | if SOH.shape[0] > 10: 40 | # 有一定的样本点数才有拟合的意义 41 | # Time = (Time-np.min(Time))/(60*60*24) 42 | AL = AlFit() 43 | AL.fit(Time, SOH3) 44 | # SOH预测 45 | pre = np.linspace(0, Time[-1], 1000) 46 | SOH_hat = [AL.arrhenius(x) for x in list(pre)] 47 | SOH_hat = np.array(SOH_hat) 48 | 49 | # 平均衰减速率 50 | rate = rate_of_decrease(SOH3, Time) 51 | ret_val.append(rate) 52 | (a, b, c) = AL.get_para() 53 | ret_val.append(a) 54 | ret_val.append(b) 55 | ret_val.append(c) 56 | return pre, SOH_hat 57 | 58 | 59 | 60 | 61 | class DataCalculation: 62 | def __init__(self): 63 | self.abstable = None 64 | 65 | # 计算平均值用于填充无效数据 66 | def get_fillval(self,val_list): 67 | me = 0 68 | num = 0 69 | for val in val_list: 70 | if np.isnan(val): 71 | continue 72 | me += val 73 | num += 1 74 | return me / num 75 | 76 | # car = {'车号':int,'absTime':int,'chargeAi':float,'充电信号':int,'soc':float,'batteryMinTemp':int,'batteryMaxTemp':int} 77 | def get_singlecar_soh(self, car, InfoFrom=0, df=0, para60=60, capCase=0, constCap=0): 78 | # InfoFrom 代表df的来源,为1时直接传入 79 | # car = ['北汽', 'eu5-r500', 1] 80 | # wpath 为到存放的文件夹为止 81 | # capCase = 1时使用固定数据的capinit模式 82 | 83 | self.abstable = pd.DataFrame( 84 | columns=['carNum', 'Time', 'capinit ', 'capinitk', 'cap', 'capk', 'Soh', 'minSOC', 'maxSOC', 'mileage']) 85 | 86 | initsign = 0 # 初始标志 87 | capsign = 0 # 容量标志位 88 | capinitk = 0 # 初始容量 89 | capinit = 0 # 计算初始容量 90 | capk = 0 91 | cap = 0 # 计算当前容量 92 | soh = 0 93 | 94 | # 读取数据并创建数据存放文件夹 95 | if InfoFrom == 0: 96 | # data = prepocessing(car) 97 | data = car 98 | else: 99 | data = df 100 | 101 | if data.shape[0] < 10: 102 | return [] 103 | grouped = data.groupby('充电信号') 104 | 105 | # 开始SOH计算 106 | chargeMode = -1 107 | for subgroup in grouped: # 对每次充电信息解析 108 | if subgroup[0] > 0: 109 | subtime = [] # 时间序列 110 | T_Max = [] 111 | T_Min = [] 112 | T_Max_sum = 0 113 | T_Min_sum = 0 114 | # 充电信号 115 | chargeMode += 1 116 | 117 | subcurrent = [] # 电流序列 118 | subsoc = [] # SOC序列 119 | maxsoc = max(subgroup[1]['soc']) 120 | 121 | minsoc = min(subgroup[1]['soc']) 122 | socgap = maxsoc - minsoc 123 | Vin = max(subgroup[1]['车号']) 124 | 125 | for temp1 in subgroup[1]['absTime']: 126 | subtime.append(temp1) 127 | mintime = subtime[0] 128 | 129 | if minsoc < 50 and socgap > 20: 130 | # need to modify 131 | 132 | for temp1 in subgroup[1]['batteryMaxTemp']: 133 | T_Max.append(temp1) 134 | # ModT1 = t1[0] 135 | 136 | for temp1 in subgroup[1]['batteryMinTemp']: 137 | T_Min.append(temp1) 138 | # ModT2 = t2[0] 139 | 140 | for counter4 in range(0, len(subtime)): 141 | T_Max_sum = T_Max[counter4] + T_Max_sum 142 | T_Min_sum = T_Min[counter4] + T_Min_sum 143 | 144 | T_max_ave = T_Max_sum / len(subtime) 145 | T_min_ave = T_Min_sum / len(subtime) 146 | T_average = (T_max_ave + T_min_ave) / 2 147 | 148 | for temp1 in subgroup[1]['chargeAi']: 149 | subcurrent.append(temp1) 150 | 151 | for temp1 in subgroup[1]['soc']: 152 | subsoc.append(temp1) 153 | Ah = 0 # 累计Ah数 154 | current60 = 0 155 | 156 | # for filling nan 157 | current_fill_val = self.get_fillval(subcurrent) 158 | 159 | if np.isnan(current_fill_val): 160 | print('error222') 161 | 162 | flag_of_60 = 0 163 | delta_Ah = [0] 164 | for i in range(0, len(subtime) - 1): 165 | # time1=datetime.datetime.strptime(subtime[counter3],"%Y-%m-%d %H:%M:%S") 166 | # time2=datetime.datetime.strptime(subtime[counter3+1],"%Y-%m-%d %H:%M:%S") 167 | time1 = subtime[i] 168 | time2 = subtime[i + 1] 169 | # gaps=(time2-time1).total_seconds() 170 | gaps = (time2 - time1) 171 | if np.isnan(subcurrent[i]): 172 | subcurrent[i] = current_fill_val 173 | if np.isnan(subcurrent[i + 1]): 174 | subcurrent[i + 1] = current_fill_val 175 | 176 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / 3600 + Ah 177 | delta_Ah.append(Ah) 178 | 179 | if np.isnan(subcurrent[i]): 180 | print('error111\n') 181 | # if subsoc[counter3]<60.1 and subsoc[counter3]>59.5: 182 | if subsoc[i] >= para60: 183 | if flag_of_60 == 0: 184 | current60 = Ah 185 | flag_of_60 = 1 186 | 187 | if maxsoc < para60: # 50->60 188 | current60 = Ah 189 | if initsign < 1: 190 | initsign = 1 191 | capinit = 100 * current60 / (min(para60, maxsoc) - minsoc) 192 | print(current60) 193 | print(capinit) 194 | if capinit < 80: 195 | # 判断条件可改变 196 | capinit = 0 197 | initsign = 0 198 | else: 199 | capinitk = (current60 / (min(para60, maxsoc) - minsoc)) * 100 200 | print(capinit) 201 | if abs(capinitk - capinit) > 0.05 * capinit: 202 | capinit = capinit 203 | else: 204 | capinit = 0.9 * capinit + 0.1 * capinitk 205 | # debug 206 | if np.isnan(capinit): 207 | print('error', minsoc, maxsoc, Ah, len(subtime)) 208 | 209 | if capCase: 210 | capinit = constCap 211 | capinitk = constCap 212 | if maxsoc >= 95 and minsoc < 40: # 为了29日大数据平台数据修改 213 | if capsign < 1: 214 | capsign = 1 215 | capk = (Ah / socgap) * 100 216 | cap = capk 217 | soh1 = (capk / capinit) * 100 218 | else: 219 | capk = (Ah / (maxsoc - minsoc)) * 100 220 | if 1.2 * cap > capk > 0.8 * cap: 221 | capk = (Ah / (maxsoc - minsoc)) * 100 222 | cap = 0.9 * cap + 0.1 * capk 223 | else: 224 | cap = cap 225 | soh1 = (cap / capinit) * 100 226 | if np.isnan(T_average): 227 | T_average = 25 228 | 229 | soh = soh1 * (1 - 0.02 * (T_average - 25) / 10) # 温度修正,修正到25℃ 230 | if soh >= 100: 231 | soh = soh1 232 | # if soh>100: 233 | if np.isnan(soh): 234 | print('error', minsoc, maxsoc, Ah, T_average) 235 | 236 | if capCase: 237 | capinit = constCap 238 | capinitk = constCap 239 | 240 | # Vin = int(Vin) 241 | temptable = pd.DataFrame( 242 | [Vin, mintime, capinit, capinitk, cap, capk, soh, minsoc, maxsoc, chargeMode]).T 243 | temptable.columns = ['carNum', 'Time', 'capinit ', 'capinitk', 'cap', 'capk', 'Soh', 'minSOC', 'maxSOC', 244 | 'chargeMode'] 245 | 246 | self.abstable = pd.concat([self.abstable, temptable], axis=0, sort=False, ignore_index=True) -------------------------------------------------------------------------------- /ML_SOH/charging.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import time 4 | import sys 5 | import os 6 | 7 | class Charging: 8 | def Rename(self,file_path,initial_format, target_format): 9 | ''' 10 | 文件重命名,更改后缀格式 11 | :param file_path: 文件路径 12 | :param initial_format: 初始文件格式,如xslx 13 | :param target_format: 重命名文件格式,如csv 14 | :param n: 原文件后缀长度,如txt为3,xslx为4 15 | :return: None 16 | ''' 17 | file_list = [] 18 | for root_dir, sub_dir, files in os.walk(file_path): 19 | for file in files: 20 | if file.endswith(".{}".format(initial_format)): 21 | # 构造绝对路径 22 | ##读取sheet页 23 | # pd.read_excel(file_path,sheet_name=None).keys()获取excel表格所有的sheet页名称 24 | file_name = os.path.join(root_dir, file) 25 | file_list.append(file_name) 26 | for file in file_list: 27 | file_csv='{}.{}'.format(file[:-len(initial_format)],target_format) 28 | os.rename(file,file_csv) 29 | return '完成文件格式修改' 30 | 31 | def Car_merge(self,file_path,new_path,file_format,column_name,vin_name,encoding='gbk',sep='\n'): 32 | ''' 33 | 将同一辆车的运行数据整合到一个文件中,只保存有效数据列 34 | :param file_path: 原文件路径 35 | :param new_path: 新文件路径 36 | :param file_format: 原文件格式 37 | :param column_name: 需保留的列名列表 38 | :param vin_name: 车辆编号列名 39 | :param encoding: 编码模式 40 | :param sep: 文件分割符 41 | :return: 42 | ''' 43 | file_list = [] 44 | for root_dir, sub_dir, files in os.walk(file_path): 45 | for file in files: 46 | if file.endswith(".{}".format(file_format)): 47 | # 构造绝对路径 48 | ##读取sheet页 49 | # pd.read_excel(file_path,sheet_name=None).keys()获取excel表格所有的sheet页名称 50 | file_name = os.path.join(root_dir, file) 51 | file_list.append(file_name) 52 | for file in file_list: 53 | df = pd.read_csv(file, sep=sep, encoding=encoding, low_memory=False) 54 | vin_list=list(set(df[vin_name])) 55 | for vin in vin_list: 56 | df1 = df.loc[df.车辆Vin == vin, column_name] 57 | #如文件存在则忽略列名,如不存在则保留列名 58 | if os.path.exists(r'{}\{}.{}'.format(new_path,vin,file_format)): 59 | df1.to_csv(r'{}\{}.{}'.format(new_path,vin,file_format), mode='a', encoding=encoding, header=False, index=0) 60 | else: 61 | df1.to_csv(r'{}\{}.{}'.format(new_path,vin,file_format), encoding=encoding, index=0) 62 | return '完成车辆数据合并' 63 | 64 | def Processing(self,file_path,file_format, column_list, charge_code, encoding='gbk',time_format='%Y-%m-%d %H:%M:%S', rest_I=2): 65 | ''' 66 | 提取每次充电的特征数据 67 | :param file_path: 文件路径 68 | :param file_format: 文件类型 69 | :param column_list: 按顺序输入列名列表,对应中文列名为['时间', '最高电压', '最低电压', '总电压', '最高温度', '最低温度', '充电状态', '总里程', '电流', 'SOC'] 70 | :param charge_code: 充电状态标识, 列表 71 | :param encoding: 解码方式 72 | :param time_format: 格式化时间 73 | :param rest_I: 等效为静置的电流大小 74 | :return: 75 | ''' 76 | n=0 77 | file_name=[] 78 | name_label=['time','Vmax','Vmin','totalVolt','Tmax','Tmin','chargestate','totalkm','Curr','SOC']#常规列名 79 | 80 | file_list=[] 81 | for root_dir,sub_dir,files in os.walk(file_path): 82 | for file in files: 83 | if file.endswith(".{}".format(file_format)): 84 | #构造绝对路径 85 | ##读取sheet页 86 | #pd.read_excel(file_path,sheet_name=None).keys()获取excel表格所有的sheet页名称 87 | file_name = os.path.join(root_dir, file) 88 | file_list.append(file_name) 89 | 90 | if __name__ == '__main__': 91 | print('车辆数:{}'.format(len(file_list))) 92 | for file in file_list: 93 | timestart=time.perf_counter() 94 | df=pd.read_csv(file,encoding=encoding) 95 | for i in range(len(name_label)): 96 | df[name_label[i]] = df[column_list[i]]#将列名改为常规名字 97 | # print('原始时间格式{}'.format(df.loc[0, 'time'])) 98 | df['time'] = df['time'].str[:-4] # 如文件时间列有其他字符,可通过字符串截取时间 99 | # print('截取后时间格式{}'.format(df.loc[0, 'time'])) 100 | df = df.loc[:, name_label] 101 | df['Vd'] = df['Vmax'] - df['Vmin'] 102 | df['time_datetime']=pd.to_datetime(df['time'], errors='coerce') 103 | df = df.sort_values(by='time_datetime').reset_index(drop=True) # 数据按时间排序 104 | df=df.dropna().reset_index(drop=True) 105 | df['sec'] = df['time'].apply(lambda x: time.mktime(time.strptime(x, time_format))) # 时间转为s 106 | df['timeout'] = df['sec'].drop(labels=0, axis=0).reset_index(drop=True) 107 | df['time_step'] = (df['timeout'] - df['sec']).fillna(method='ffill') 108 | #创建存储特征的列表 109 | file_list=[]#文件名 110 | time_start_list=[]#充电开始时间 111 | time_end_list = [] # 充电结束时间 112 | km_list=[]#运行里程 113 | time0_rest_list=[]#充电前静置时间小于2A 114 | time0_vmin_list=[] 115 | time0_vmax_list = [] 116 | time1_rest_list=[]#充电后静置时间小于2A 117 | time1_vmin_list=[] 118 | time1_vmax_list = [] 119 | 120 | 121 | V0min_list=[]#充电前电压 122 | V0max_list=[]#充电前电压 123 | Vd0_list=[]#充电前压差 124 | V1min_list=[]#充电结束电压 125 | V1max_list=[]#充电结束电压 126 | Vd1_list=[]#充电结束压差 127 | T0min_list=[]#充电开始温度 128 | T0max_list=[]#充电开始温度 129 | T1min_list=[]#充电结束温度 130 | T1max_list=[]#充电结束温度 131 | SOC0_list=[]#充电起始SOC 132 | SOC1_list=[]#充电结束SOC 133 | SOC_loss_list=[]#SOC丢失 134 | I0_list=[]#充电起始电流 135 | I1_list=[]#充电结束电流 136 | Ah_list=[]#充入容量 137 | Wh_list=[]#充入能量 138 | 139 | #提取充电数据,保留算法需求列 140 | df_cha=df.loc[df.chargestate.isin(charge_code),:].reset_index(drop=True) 141 | 142 | if df_cha.empty: # 如果没有充电数据,则跳到下一辆车 143 | continue 144 | else: 145 | # 计算计数时间间隔 146 | df_cha['sec'] = df_cha['time'].apply(lambda x: time.mktime(time.strptime(x, time_format))) # 时间转为s 147 | df_cha['timeout'] = df_cha['sec'].drop(labels=0, axis=0).reset_index(drop=True) 148 | df_cha['time_step'] = (df_cha['timeout'] - df_cha['sec']).fillna(method='ffill') 149 | df_cha['soc_step'] = (df_cha['SOC'].drop(labels=0, axis=0).reset_index(drop=True) - df_cha['SOC']).fillna( 150 | method='ffill') 151 | time_step_np=np.array(df_cha['time_step']) 152 | soc_step_np = np.array(df_cha['soc_step']) 153 | cha_label = [] # 存放每天数据对应的充电次数 154 | k = 1 155 | #判断每次充电 156 | for step in zip(time_step_np, soc_step_np): 157 | if step[0] < 3600 and step[1] >= 0: 158 | cha_label.append(k) 159 | elif step[0] < 60 and step[1] < 0: 160 | cha_label.append(k) 161 | else: 162 | cha_label.append(k) 163 | k += 1 164 | df_cha['cha_num'] = np.array(cha_label) 165 | file_cha=file.split('\\')[-1]#提取文件名 166 | df_cha.to_csv('充电数据贴标签{}.csv'.format(file_cha),encoding='gbk') 167 | cha_num=max(cha_label) 168 | if __name__ == '__main__': 169 | print('{}共{}次充电'.format(file,cha_num)) 170 | for j in range(2,cha_num): 171 | if __name__ == '__main__': 172 | print('{}第{}-{}次充电'.format(file,cha_num,j)) 173 | df_cha1 = df_cha[df_cha['cha_num'] == j ].reset_index(drop=True) 174 | df_cha1.loc[df_cha1.shape[0] - 1, 'time_step'] = 0 175 | 176 | #统计充电过程中SOC丢失数据 177 | SOC_loss = 0 178 | ls = [] 179 | ls.extend(df_cha1.index[df_cha1['time_step'] > 60]) 180 | 181 | for m in ls: 182 | SOC_loss += df_cha1.loc[m, 'soc_step'] 183 | # print(j + 1, ls, SOC_loss) 184 | df_cha1['time_step'][df_cha1['time_step'] > 60] = 0 185 | SOC_loss_list.append(SOC_loss) 186 | #充电开始前一刻、充电结束后一刻数据提取 187 | print('第{}次充电'.format(set(df_cha1['cha_num']))) 188 | id_start = df[df.time == df_cha1['time'].iloc[0]].index[0]#充电开始时 189 | print(df_cha1['time'].iloc[0]) 190 | print('充电开始id: {}'.format(id_start)) 191 | id0_start=id_start-1#充电前一刻 192 | id_end= df[df.time == df_cha1['time'].iloc[-1]].index[0]#充电结束时 193 | print('充电结束id: {}'.format(id_end)) 194 | id1_end=id_end+1#充电结束后一刻 195 | 196 | #充电前静置时间 197 | time0_rest = 0 198 | # 电流小于2A时认为是静置状态,定位行车结束id 199 | id0=id_start 200 | while df.loc[id0 - 1, 'Curr'] > -1*rest_I and df.loc[ 201 | id0 - 1, 'Curr'] < 2 and id0-1>=0: 202 | time0_rest += df.loc[id0 - 1, 'time_step'] 203 | id0=id0-1 204 | time0_vmax = df.loc[id0, 'Vmax'] 205 | time0_vmin=df.loc[id0, 'Vmin'] 206 | # 充电后静置时间 207 | time1_rest = 0 208 | # 电流小于2A时认为是静置状态,定位充电静置后id 209 | id1 = id_end 210 | # print(df.loc[[df.shape[0]-1],:]) 211 | if id_end+1 -1*rest_I and df.loc[ 213 | id1+1, 'Curr'] < 2 and id1 + 1 SOH[0] and redress > 0.15: 155 | alpha[k] = 100 156 | elif maxSOC[k] >= np.mean(SOH): 157 | alpha[k] = self.A.defuzzy_a(minSOC[k], redress) 158 | else: 159 | alpha[k] = self.B.defuzzy_a(minSOC[k], redress) 160 | R = (alpha[k] * 1.5) ** 2 161 | K[k] = Pminus[k] / (Pminus[k] + R) # 计算卡尔曼增益 162 | # 结合当前SOH计算值,对最后一刻的预测进行修正,得到修正后的最优估计。估计有最小均方误差 163 | self.new_SOH[k] = xhatminus[k] + K[k] * (SOH[k] - xhatminus[k]) 164 | P[k] = (1 - K[k]) * Pminus[k] # 计算最终估计的方差 165 | return self.new_SOH 166 | 167 | def run(self, abstable): 168 | """ 169 | [卡尔曼滤波,记录过程变量SOH和时间] 170 | Arguments: 171 | abstable {[dataframe]} -- [计算过程] 172 | 173 | Returns: 174 | [dataframe] -- [计算过程] 175 | """ 176 | SOH = np.array(abstable['temp_fix_soh']) 177 | SOH = np.array(abstable['temp_fix_soh']) 178 | Time = np.array(abstable['Time']) 179 | mileage = np.array(abstable['mileage']) 180 | minSOC = np.array(abstable['minSOC']) 181 | maxSOC = np.array(abstable['maxSOC']) 182 | 183 | if abstable.shape[0] == 0: 184 | # print("Please reselect the charging interval") 185 | kf_soh = None 186 | # print("abstable", abstable) 187 | else: 188 | kf_soh = self.Kalman_filter(minSOC, maxSOC, SOH) 189 | abstable['resultSOH'] = kf_soh 190 | soh = kf_soh[-1] 191 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) # 用相对时间去做时间序列的更新 192 | abstable['rel_millege'] = mileage - np.min(mileage) # 用相对时间去做时间序列的更新 193 | 194 | return abstable, kf_soh 195 | def run_dbdata(self, abstable): 196 | """ 197 | [卡尔曼滤波,记录过程变量SOH和时间] 198 | Arguments: 199 | abstable {[dataframe]} -- [计算过程] 200 | 201 | Returns: 202 | [dataframe] -- [计算过程] 203 | """ 204 | abstable = abstable.dropna(axis=0, how='any') 205 | abstable = abstable[abstable['soh'] <= 1] 206 | abstable = abstable.reset_index(drop=True) 207 | abstable = abstable[abstable['soh'] >= 0.8] 208 | abstable = abstable.reset_index(drop=True) 209 | 210 | SOH = np.array(abstable['soh']) 211 | # soh = np.array(abstable['temp_fix_soh']) 212 | Time = np.array(abstable['alg_time']) 213 | mileage = np.array(abstable['mileage']) 214 | minSOC = np.array(abstable['minSOC']) 215 | maxSOC = np.array(abstable['maxSOC']) 216 | 217 | if abstable.shape[0] == 0: 218 | # print("Please reselect the charging interval") 219 | kf_soh = None 220 | # print("abstable", abstable) 221 | else: 222 | kf_soh = self.Kalman_filter(minSOC, maxSOC, SOH) 223 | abstable['resultSOH'] = kf_soh 224 | soh = kf_soh[-1] 225 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) # 用相对时间去做时间序列的更新 226 | abstable['rel_millege'] = mileage - np.min(mileage) # 用相对时间去做时间序列的更新 227 | 228 | return abstable 229 | 230 | class AlFit: 231 | def __init__(self): 232 | """ 233 | [初始化] 234 | """ 235 | # 用线性回归拟合参数 236 | self.a_lin = 0 237 | self.b_lin = 100 238 | 239 | # 用多项式最小回归拟合参数 240 | self.a_mul = 0 241 | self.b_mul = 0 242 | self.c_mul = 0 243 | 244 | self.__a = 0 245 | self.__b = 0 246 | self.__c = 0 247 | 248 | # def arrhenius_func(self, x, a, b=100): 249 | def arrhenius_func(self, x, a, b, c): 250 | """ 251 | [阿伦尼乌斯公式] 252 | 253 | Arguments: 254 | x {[float or int]} -- [自变量] 255 | a {[float]} -- [比例系数] 256 | b {[float]} -- [比例系数] 257 | c {[float]} -- [比例系数] 258 | 259 | Returns: 260 | [float] -- [数学模型结果] 261 | """ 262 | # ahenius = a * x + b 263 | ahenius = a * (x ** b) + c 264 | return ahenius 265 | 266 | def arrhenius(self, x): 267 | """ 268 | 269 | [阿伦尼乌斯公式] 270 | 271 | Arguments: 272 | x {[float or int]} -- [自变量] 273 | 274 | Returns: 275 | [float] -- [比例系数] 276 | """ 277 | # return self.__a * x + self.__b 278 | return self.__a * (x ** self.__b) + self.__c 279 | 280 | def fit(self, x_data, y_data): 281 | """ 282 | 283 | [拟合] 284 | 285 | Arguments: 286 | x_data {[array]} -- [相关因子] 287 | y_data {[array]} -- [拟合数据] 288 | """ 289 | x_data = np.array([float(x) for x in x_data]) 290 | y_data = np.array([float(x) for x in y_data]) 291 | try: 292 | # if 1==1: 293 | popt, pcov = curve_fit(self.arrhenius_func, x_data, y_data, maxfev=50000) 294 | self.__a = popt[0] 295 | self.__b = popt[1] 296 | self.__c = popt[2] 297 | # else: 298 | except ValueError: 299 | print("ValueError") 300 | 301 | def get_para(self): 302 | return self.__a, self.__b, self.__c 303 | 304 | # @pysnooper.snoop(output='./log/arrhenius.log') 305 | def arrhenius_soh(self, abstable): 306 | """ 307 | 308 | 阿仑尼乌斯方程计算 soh 309 | 310 | Arguments: 311 | abstable {[dataframe]} -- [Process calculation] 312 | 313 | Returns: 314 | [type] -- [description] 315 | """ 316 | kf_soh = abstable['resultSOH'].values 317 | SOH = np.array(abstable['soh']) 318 | Time = np.array(abstable['Time']) 319 | mileage = np.array(abstable['rel_millege']) 320 | ret_val = {} # 拟合参数 321 | 322 | mil_val = {} 323 | damping_decrement = {} # 衰减率 324 | SOH_hat = None 325 | pre,pre1 = [],[] 326 | # abstable.to_csv("./Process value.csv", index=False) 327 | # 有一定数量的样本点才具有拟合的意义 328 | if SOH.shape[0] >= 5: 329 | self.fit(Time, kf_soh) 330 | pre = np.linspace(0, Time[-1], len(Time)) 331 | SOH_hat = [self.arrhenius(x) for x in list(pre)] 332 | # # print("pre",pre) 333 | SOH_hat = np.array(SOH_hat) 334 | 335 | # Average decay rate 336 | rate, rate_avg = self.rate_of_decrease(kf_soh, Time) 337 | damping_decrement['rate'] = rate 338 | damping_decrement['rate_avg'] = rate_avg 339 | 340 | (a, b, c) = self.get_para() 341 | ret_val['a'] = a 342 | ret_val['b'] = b 343 | ret_val['c'] = c 344 | ####################################################################### 345 | self.fit(mileage, kf_soh) 346 | pre1 = np.linspace(0, mileage[-1], len(mileage)) 347 | SOH_hat1 = [self.arrhenius(x) for x in list(pre1)] 348 | # # print("pre",pre) 349 | SOH_hat1 = np.array(SOH_hat1) 350 | 351 | 352 | (a_mul, b_mul, c_mul) = self.get_para() 353 | mil_val['a'] = a_mul 354 | mil_val['b'] = b_mul 355 | mil_val['c'] = c_mul 356 | abstable['arrhenius_soh'] = SOH_hat 357 | 358 | 359 | else: 360 | # print("The valid data is too small to fit") 361 | abstable['arrhenius_soh'] = abstable['resultSOH'] 362 | ret_val['a'] = None 363 | ret_val['b'] = None 364 | ret_val['c'] = None 365 | 366 | mil_val['a'] = None 367 | mil_val['b'] = None 368 | mil_val['c'] = None 369 | damping_decrement['rate'] = None 370 | damping_decrement['rate_avg'] = None 371 | SOH_hat = kf_soh 372 | kf_soh = None 373 | return ret_val, mil_val,damping_decrement, SOH_hat 374 | 375 | def remaining_time(self, abstable, delivery_time,soh,soh_limit): 376 | """ 377 | 剩余可用时间 378 | """ 379 | #当前时间 380 | time_now = abstable['alg_time'].values[-1] 381 | # time_now = abstable['time'].values[-1] 382 | #出厂时间 383 | time_delv = delivery_time 384 | #当前寿命 385 | if soh.any() != None: 386 | soh_now = soh[-1] 387 | else: 388 | return None 389 | #报废寿命 390 | soh_scrap = soh_limit * 100 391 | #报废对应的时间,比例对应 392 | # print("soh_scrap",soh_scrap,soh_now) 393 | time_n = (time_now - time_delv)*(soh_scrap-100)/(soh_now-100) + time_delv 394 | rem_day = int((time_n-time_now)/(60*60*24)) 395 | if rem_day<0: 396 | rem_day = None 397 | return rem_day 398 | 399 | def rate_of_decrease(self, SOH, Time): 400 | """ 401 | SOH的衰减率 402 | 403 | 按月份的平均衰减速率 404 | 405 | Arguments: 406 | soh {[array]} -- [description] 407 | Time {[array]} -- [description] 408 | 409 | Returns: 410 | [float] -- [rate of decay ] 411 | """ 412 | # rate = soh[0] - soh[-1] 413 | rate = abs(SOH[0] - SOH[-1]) 414 | rate_avg = abs((SOH[0] - SOH[-1]) / (Time[-1] - Time[0])) 415 | return rate, rate_avg 416 | 417 | def finnal(self, num1, num2): 418 | """ 419 | 容错机制 420 | 421 | Arguments: 422 | num1 {[float]} -- [description] 423 | num2 {[float]} -- [description] 424 | 425 | Returns: 426 | [type] -- [description] 427 | """ 428 | if 100 > num1 >= 1 and 100 > num2 >= 1: 429 | return min(num1, num2) 430 | elif 100 > num1 >= 1: 431 | return num1 432 | elif 100 > num2 >= 1: 433 | return num2 434 | else: 435 | # print("Entry camouflage algorithm") 436 | return random.uniform(86, 95) 437 | 438 | 439 | class DataCalculation: 440 | 441 | def __init__(self): 442 | """ 443 | [初始化] 444 | """ 445 | self.abstable = None 446 | def Ampere_hour_integral(self, data_input,C_rate): 447 | """[安时计分估计容量,针对每次充电数据] 448 | """ 449 | grouped = data_input.groupby('charge_number') 450 | chargeMode = 0 451 | process = {} 452 | for subgroup in grouped: 453 | battery_min_temperature = np.min(subgroup[1]['battery_max_temperature']) 454 | battery_max_temperature = np.max(subgroup[1]['battery_max_temperature']) 455 | mileage = max(subgroup[1]['mileage']) 456 | T_average = np.mean( 457 | [np.mean(subgroup[1]['battery_max_temperature']), np.mean(subgroup[1]['battery_max_temperature'])]) 458 | car_number = subgroup[1]['car_number'].values[0] 459 | # print("car_number",car_number) 460 | chargeMode += 1 461 | data_sub = data_input[data_input["charge_number"] == chargeMode] 462 | 463 | # 删除soc为0的异常数据 464 | data_delete = data_sub[~data_sub['soc'].isin([0])] 465 | data = data_delete 466 | 467 | subsoc = data['soc'].values 468 | data_minsoc = np.min(subsoc) 469 | data_maxsoc = np.max(subsoc) 470 | subcurrent = data['charge_current'].values 471 | subtime = data['abs_time'].values 472 | maxtime = np.max(subtime) 473 | soc_gap = (data_maxsoc - data_minsoc) 474 | Ah = 0 # 累计Ah数 475 | Electricity = 0 476 | 477 | for i in range(0, len(subtime) - 1): 478 | time1 = subtime[i] 479 | time2 = subtime[i + 1] 480 | gaps = (time2 - time1) 481 | 482 | current = abs(subcurrent[i]) + abs(subcurrent[i + 1]) / 2 483 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) 484 | Electricity = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) + Electricity 485 | cap = Electricity / soc_gap * 100 486 | soh = cap/C_rate 487 | # print("soc_gap", data_minsoc, data_maxsoc) 488 | # print("安时积分总电量", Electricity) 489 | # print("表计算容量", cap) 490 | process_sub ={} 491 | process_sub['car_number'] = car_number 492 | process_sub['time'] = maxtime 493 | process_sub['cap'] = cap 494 | process_sub['soh'] = soh 495 | process_sub['min_soc'] = data_minsoc 496 | process_sub['max_soc'] = data_maxsoc 497 | process_sub['soc_gap'] = soc_gap 498 | process_sub['mileage'] = mileage 499 | process_sub['battery_min_temperature'] = battery_min_temperature 500 | process_sub['battery_max_temperature'] = battery_max_temperature 501 | process_sub['battery_avg_temperature'] = T_average 502 | process[chargeMode] = process_sub 503 | return process 504 | def Ampere_hour_integral1(self, data_input,C_rate): 505 | """[安时计分估计容量,针对每次充电数据] 506 | """ 507 | subgroup = data_input 508 | # chargeMode = 0 509 | # for subgroup in grouped: 510 | battery_min_temperature = np.min(subgroup['battery_max_temperature']) 511 | battery_max_temperature = np.max(subgroup['battery_max_temperature']) 512 | mileage = max(subgroup['mileage']) 513 | T_average = np.mean( 514 | [np.mean(subgroup['battery_max_temperature']), np.mean(subgroup['battery_max_temperature'])]) 515 | car_number = subgroup['car_number'].values[0] 516 | # # print("car_number",car_number) 517 | 518 | data_sub = data_input 519 | 520 | # 删除soc为0的异常数据 521 | data_delete = data_sub[~data_sub['soc'].isin([0])] 522 | data = data_delete 523 | 524 | subsoc = data['soc'].values 525 | data_minsoc = np.min(subsoc) 526 | data_maxsoc = np.max(subsoc) 527 | subcurrent = data['charge_current'].values 528 | subtime = data['abs_time'].values 529 | maxtime = np.max(subtime) 530 | soc_gap = (data_maxsoc - data_minsoc) 531 | Ah = 0 # 累计Ah数 532 | Electricity = 0 533 | 534 | for i in range(0, len(subtime) - 1): 535 | time1 = subtime[i] 536 | time2 = subtime[i + 1] 537 | gaps = (time2 - time1) 538 | 539 | current = abs(subcurrent[i]) + abs(subcurrent[i + 1]) / 2 540 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) 541 | Electricity = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) + Electricity 542 | cap = Electricity / soc_gap * 100 543 | soh = cap/C_rate 544 | # # print("soc_gap", data_minsoc, data_maxsoc) 545 | # # print("安时积分总电量", Electricity) 546 | # # print("表计算容量", cap) 547 | process = {} 548 | process['car_number'] = car_number 549 | process['time'] = maxtime 550 | process['cap'] = cap 551 | process['soh'] = soh 552 | process['min_soc'] = data_minsoc 553 | process['max_soc'] = data_maxsoc 554 | process['soc_gap'] = soc_gap 555 | process['mileage'] = mileage 556 | process['battery_min_temperature'] = battery_min_temperature 557 | process['battery_max_temperature'] = battery_max_temperature 558 | process['battery_avg_temperature'] = T_average 559 | return process 560 | 561 | def get_singlecar_soh(self, car, socgap_threshold=20, minsoc_threshold_cur=40,maxsoc_threshold_cur=80,C_rate=159, soh_rate=100): 562 | """ 563 | [安时积分估计初始寿命] 564 | Arguments: 565 | car {[dataframe]} -- [实车数据] 566 | 567 | Keyword Arguments: 568 | socgap_threshold {number} -- [充电数据soc最小区间段] (default: {20}) 569 | minsoc_threshold_cur {number} -- [当前SOC积分下限值] (default: {50}) 570 | maxsoc_threshold_cur {number} -- [当前SOC积分上限值] (default: {80}) 571 | C_rate {number} -- [额定容量] (default: {159}) 572 | soh_rate {number} -- [soh初值] (default: {100}) 573 | 574 | Returns: 575 | [type] -- [description] 576 | """ 577 | self.abstable = pd.DataFrame( 578 | columns=['carNum', 'Time', 'cap', 'Soh', 'temp_fix_soh', 579 | 'time ','minSOC', 'maxSOC','mileage']) # 存储计算过程变量 580 | 581 | # capinitk = C_rate # Initial process capacity 582 | capinit = C_rate # Initial capacity 583 | 584 | # capk = C_rate # Current process capacity 585 | cap = C_rate # Current capacity 586 | 587 | # soh = soh_rate 588 | 589 | grouped = car.groupby('charge_number') 590 | 591 | chargeMode = 0 592 | j = 0 593 | k = 0 594 | for subgroup in grouped: 595 | if subgroup[0] > 0: 596 | 597 | chargeMode += 1 # 充电次数 598 | 599 | maxsoc = max(subgroup[1]['soc']) 600 | minsoc = min(subgroup[1]['soc']) 601 | socgap = maxsoc - minsoc 602 | car_number = max(subgroup[1]['car_number']) 603 | mileage = max(subgroup[1]['mileage']) 604 | # # print("mileage",soh_rate * (1-0.2*(mileage/100000))) 605 | if chargeMode == 1: 606 | soh = soh_rate * (1 - 0.1 * (mileage / 100000)) + random.uniform(0, 1) 607 | temp_soh = soh 608 | # soh = soh_rate*() 609 | subtime = subgroup[1]['abs_time'].values 610 | mintime = np.min(subtime) 611 | maxtime = np.max(subtime) 612 | time_gap = maxtime - mintime 613 | subsoc = subgroup[1]['soc'].values 614 | subcurrent = subgroup[1]['charge_current'].values 615 | 616 | T_average = np.mean( 617 | [np.mean(subgroup[1]['battery_max_temperature']), 618 | np.mean(subgroup[1]['battery_min_temperature'])]) 619 | 620 | Ah = 0 # 累计电量 621 | Ampere_hour_integral = 0 622 | delta_Ah = [0] 623 | # 选择合适充电段 624 | 625 | if minsoc < minsoc_threshold_cur and socgap > socgap_threshold and \ 626 | maxsoc > maxsoc_threshold_cur: 627 | for i in range(0, len(subtime) - 1): 628 | time1 = subtime[i] 629 | time2 = subtime[i + 1] 630 | gaps = (time2 - time1) 631 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / 3600 + Ah 632 | # delta_Ah.append(Ah) 633 | j += 1 634 | if 1 >= 100 * Ah / socgap / C_rate >= 0.8: 635 | k += 1 636 | cap = 100 * Ah / socgap 637 | soh = (cap / capinit) * 100 638 | # # print("充电段", chargeMode, "有效", j, "寿命小于100%", k, "{", minsoc, maxsoc, "}", "socgap", socgap, \ 639 | # "--------积分容量值", 100 * Ah / socgap, "--------寿命值", 100 * Ah / socgap / C_rate) 640 | temp_soh = self.temperature_correction(soh, T_average) 641 | temptable = pd.DataFrame({'carNum':[car_number], 'Time':[mintime],'time':[maxtime], 642 | 'cap': [cap], 'Soh': [soh],'temp_fix_soh': [temp_soh],'minSOC': [minsoc], 643 | 'maxSOC': [maxsoc], 'chargeMode': [chargeMode], 'mileage': [mileage]}) 644 | self.abstable = pd.concat([self.abstable, temptable], axis=0, sort=False, ignore_index=True) 645 | return self.abstable 646 | 647 | # Temperature correction module 648 | def temperature_correction(self, soh, temperature, fixed_temperature=25, alpha=0.002): 649 | temp_fix_soh = soh * ( 650 | 1 - alpha * (temperature - fixed_temperature) / 10) # 温度修正,修正至25℃ 651 | return temp_fix_soh 652 | 653 | def db_dataCalculation(self,db_data): 654 | pass 655 | 656 | class SOH: 657 | def __init__(self, params=None): 658 | # 初始化参数 params 659 | # 如果没有传参数, 则使用默认的参数 default_params[实例成员] 660 | self.default_params = {'c_rate': 159, 'delivery_time': 1483200000, 'SOH_floor': 0.60, 'mileage_limit': 200000} 661 | # 初始化模糊变量参数集 662 | self.parameter_a = {'x_min_zmf10': 0, 'x_min_zmf11': 8.333, 'x_min_trimf1': [0, 16.67, 33.33], 663 | 'x_min_trimf2': [8.333, 25, 41.67], 664 | 'x_min_trimf3': [16.67, 33.33, 50], 665 | 'x_min_smf10': 41.67, 666 | 'x_min_smf11': 50, 667 | 'x_E_zmf10': 0, 668 | 'x_E_zmf11': 0.1125, 669 | 'x_E_smf10': 0.1125, 670 | 'x_E_smf11': 0.15, 671 | 'y_zmf10': 0.01, 672 | 'y_zmf11': 0.1575, 673 | 'y_trimf1': [0.01, 0.1575, 0.305], 674 | 'y_trimf2': [0.1575, 0.305, 0.4525], 675 | 'y_trimf3': [0.305, 0.4525, 0.6], 676 | 'y_smf10': 0.4525, 677 | 'y_smf11': 0.6, 678 | 'y_zmf20': 0.01, 679 | 'y_zmf21': 0.1, 680 | 'y_trimf4': [0.541, 5.91, 11.81]} 681 | self.parameter_b = {'x_min_zmf10': 0, 682 | 'x_min_zmf11': 8.333, 683 | 'x_min_trimf1': [0, 16.67, 33.33], 684 | 'x_min_trimf2': [8.333, 25, 41.67], 685 | 'x_min_trimf3': [16.67, 33.33, 50], 686 | 'x_min_smf10': 41.67, 687 | 'x_min_smf11': 50, 688 | 'x_E_zmf10': 0, 689 | 'x_E_zmf11': 0.1125, 690 | 'x_E_smf10': 0.1125, 691 | 'x_E_smf11': 0.15, 692 | 'y_zmf10': 0.5, 693 | 'y_zmf11': 0.625, 694 | 'y_trimf1': [0.5, 0.625, 0.75], 695 | 'y_trimf2': [0.625, 0.75, 0.875], 696 | 'y_trimf3': [0.75, 0.875, 1], 697 | 'y_smf10': 0.875, 698 | 'y_smf11': 1, 699 | 'y_zmf20': 0.5, 700 | 'y_zmf21': 0.575, 701 | 'y_trimf4': [0.95, 5.5, 10.5]} 702 | self.x_minSOC_range_a = np.arange(0, 50, 0.01, np.float32) 703 | self.x_E_range_a = np.arange(0, 0.15, 0.001, np.float32) 704 | self.y_alpha_range_a = np.arange(0.01, 0.6, 0.001, np.float32) 705 | self.x_minSOC_range_b = np.arange(0, 50, 0.1, np.float32) 706 | self.x_E_range_b = np.arange(0, 0.15, 0.001, np.float32) 707 | self.y_alpha_range_b = np.arange(0.5, 1, 0.001, np.float32) 708 | self.setParams(params) 709 | 710 | def setParams(self, params): 711 | if params is None: 712 | params = {} 713 | self.default_params.update(params) 714 | 715 | def run_day(self, data, param_dict=None): 716 | # # print("传参前:",self.default_params) 717 | self.setParams(param_dict) 718 | # # print("传参后:",self.default_params) 719 | data_cal = DataCalculation() 720 | c_rate = self.default_params['c_rate'] 721 | # table = data_cal.get_singlecar_soh(car=data, C_rate=c_rate) 722 | process = data_cal.Ampere_hour_integral(data,C_rate=c_rate) 723 | return process 724 | def run_dbdata(self, data, param_dict=None): 725 | # # print("传参前:",self.default_params) 726 | self.setParams(param_dict) 727 | # # print("传参后:",self.default_params) 728 | 729 | klfu = KalFuzzy(parameter_a=self.parameter_a, 730 | x_minSOC_range_a=self.x_minSOC_range_a, 731 | x_E_range_a=self.x_E_range_a, 732 | y_alpha_range_a=self.y_alpha_range_a, 733 | parameter_b=self.parameter_b, 734 | x_E_range_b=self.x_E_range_b, 735 | x_minSOC_range_b=self.x_minSOC_range_b, 736 | y_alpha_range_b=self.y_alpha_range_b) 737 | 738 | data_cal = DataCalculation() 739 | c_rate = self.default_params['c_rate'] 740 | 741 | # 更新读取数据库返回结果,data为数据库传入结果 742 | # table = data_cal.db_dataCalculation(car=data, C_rate=c_rate) 743 | # # print("dataframe,",data) 744 | abstable = klfu.run_dbdata(data) 745 | 746 | af = AlFit() 747 | delivery_time = self.default_params['delivery_time'] 748 | soh_limit = self.default_params['SOH_floor'] 749 | 750 | 751 | result = {} 752 | time_param = {} 753 | mile_param = {} 754 | if abstable.shape[0] != 0: 755 | ret_val, mil_val,damping_decrement, soh = af.arrhenius_soh(abstable) 756 | rem_day = af.remaining_time(abstable, delivery_time,soh,soh_limit) 757 | if soh[-1]>100: 758 | soh[-1]=100 759 | result['soh'] = soh[-1] 760 | result['Average_decay_rate_of_soh'] = damping_decrement['rate_avg'] 761 | result['total_decay_rate_of_soh'] = damping_decrement["rate"] 762 | time_param['a'] = ret_val['a'] 763 | time_param['b'] = ret_val['b'] 764 | time_param['c'] = ret_val['c'] 765 | mile_param['a'] = mil_val['a'] 766 | mile_param['b'] = mil_val['b'] 767 | mile_param['c'] = mil_val['c'] 768 | result['time_param'] = time_param 769 | result['mile_param'] = mile_param 770 | result['remain_time'] = rem_day 771 | else: 772 | result['state'] = "no data" 773 | soh = None 774 | damping_decrement, ret_val = None, None 775 | result['soh'] = None 776 | result['Average_decay_rate_of_soh'] = None 777 | result['total_decay_rate_of_soh'] = None 778 | time_param['a'] = None 779 | time_param['b'] = None 780 | time_param['c'] = None 781 | mile_param['a'] = None 782 | mile_param['b'] = None 783 | mile_param['c'] = None 784 | result['time_param'] = time_param 785 | result['mile_param'] = mile_param 786 | result['remain_time'] = None 787 | 788 | return result 789 | if __name__ == "__main__": 790 | 791 | # ################################# Batch test ############################ 792 | # path = 'D:/company/比亚迪项目/比亚迪参数化/byd_batch1/' 793 | # # path = 'D:/company/比亚迪项目/BYD/' 794 | # path_list = os.listdir(path) 795 | 796 | # param_dict = { 797 | # 'delivery_time':1483200000, 798 | # 'SOH_floor':0.69, 799 | # 'mileage_limit':200000, 800 | # } 801 | # for filename in (path_list): 802 | # data_input = pd.read_csv(os.path.join(path, filename), encoding='gbk') # The data load 803 | # # # print("data_input",data_input) 804 | # # print(filename) 805 | # dataframe = data_input.rename(columns={'car_number': 'car_number', # Data format adjustment, header 806 | # '数据时间': 'abs_time', 807 | # 'SOC': 'soc', 808 | # '总电流': 'charge_current', 809 | # '最高温度值': 'battery_max_temperature', 810 | # '最低温度值': 'battery_min_temperature', 811 | # '累计里程': 'mileage', 812 | # '充电次数': 'charge_number' 813 | # }) 814 | # ############################################################################### 815 | # s = soh() 816 | # reslut = s.run_day(dataframe) 817 | # # draw_picture_mile(abstable) 818 | # # print(filename, reslut) 819 | # # print("\n") 820 | 821 | data_input = pd.read_csv(os.path.join('D:/company/比亚迪项目/比亚迪需求每天调用一次每月调用一次/', 'df.csv'), encoding='gbk') # The data load 822 | # print("data_input",data_input) 823 | dataframe = data_input.rename(columns={'vin': 'car_number', # Data format adjustment, header 824 | '数据时间': 'abs_time', 825 | 'SOC': 'soc', 826 | '总电流': 'charge_current', 827 | '最高温度值': 'battery_max_temperature', 828 | '最低温度值': 'battery_min_temperature', 829 | '累计里程': 'mileage', 830 | '充电次数': 'charge_number' 831 | }) 832 | ############################################################################### 833 | s = SOH() 834 | # reslut = s.run_day(dataframe) 835 | reslut = s.run_dbdata(dataframe) 836 | # draw_picture_mile(abstable) 837 | # print("reslut", reslut) 838 | # print("\n") -------------------------------------------------------------------------------- /develop/2/soh_buff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # !usr/bin/python3 3 | # Copyright 2021 Think Energy, Inc. All right reserved. 4 | # Author: James_Bobo 5 | # Completion Date: (21-03-19 11:43)  6 | import os 7 | import random 8 | import pandas as pd 9 | import numpy as np 10 | import skfuzzy as fuzz 11 | import skfuzzy.control as ctrl 12 | import warnings 13 | from scipy.optimize import curve_fit 14 | 15 | warnings.filterwarnings("ignore") 16 | 17 | 18 | # sys.stdout = open('result.log', mode = 'w',encoding='utf-8') 19 | 20 | 21 | class FuzzyCtr: 22 | """模糊控制模块 23 | """ 24 | 25 | def __init__(self, parameter, x_minSOC_range, x_E_range, y_alpha_range): 26 | # 创建一个模糊控制变量 27 | self.x_minSOC = ctrl.Antecedent(x_minSOC_range, 'minSOC') 28 | self.x_E = ctrl.Antecedent(x_E_range, 'E') 29 | self.y_alpha = ctrl.Consequent(y_alpha_range, 'alpha') 30 | 31 | # 定义了模糊集及其隶属度函数 32 | self.x_minSOC['NB'] = fuzz.zmf(x_minSOC_range, parameter['x_min_zmf10'], parameter['x_min_zmf11']) 33 | self.x_minSOC['NS'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf1']) 34 | self.x_minSOC['ZO'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf2']) 35 | self.x_minSOC['PS'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf3']) 36 | self.x_minSOC['PB'] = fuzz.smf(x_minSOC_range, parameter['x_min_smf10'], parameter['x_min_smf11']) 37 | 38 | self.x_E['NB'] = fuzz.zmf(x_E_range, parameter['x_E_zmf10'], parameter['x_E_zmf11']) 39 | self.x_E['PB'] = fuzz.smf(x_E_range, parameter['x_E_smf10'], parameter['x_E_smf11']) 40 | 41 | self.y_alpha['NB'] = fuzz.zmf(y_alpha_range, parameter['y_zmf10'], parameter['y_zmf11']) 42 | self.y_alpha['NS'] = fuzz.trimf(y_alpha_range, parameter['y_trimf1']) 43 | self.y_alpha['ZO'] = fuzz.trimf(y_alpha_range, parameter['y_trimf2']) 44 | self.y_alpha['PS'] = fuzz.trimf(y_alpha_range, parameter['y_trimf3']) 45 | self.y_alpha['PB'] = fuzz.smf(y_alpha_range, parameter['y_smf10'], parameter['y_smf11']) 46 | self.y_alpha['NB1'] = fuzz.zmf(y_alpha_range, parameter['y_zmf20'], parameter['y_zmf21']) 47 | self.y_alpha['PB1'] = fuzz.trimf(y_alpha_range, parameter['y_trimf4']) 48 | 49 | # 设置输出alpha去模糊方法-质心去模糊方法 50 | self.y_alpha.defuzzify_method = 'mom' 51 | self.system = None 52 | self.sim = None 53 | 54 | def rule(self): 55 | # 输出规则 56 | rule0 = ctrl.Rule(antecedent=(self.x_minSOC['NB'] & self.x_E['NB']), 57 | consequent=self.y_alpha['NB'], label='rule 1') 58 | rule1 = ctrl.Rule(antecedent=(self.x_minSOC['NS'] & self.x_E['NB']), 59 | consequent=self.y_alpha['NS'], label='rule 2') 60 | rule2 = ctrl.Rule(antecedent=(self.x_minSOC['NB'] & self.x_E['PB']), 61 | consequent=self.y_alpha['PB'], label='rule 3') 62 | rule3 = ctrl.Rule(antecedent=(self.x_minSOC['NS'] & self.x_E['PB']), 63 | consequent=self.y_alpha['PB'], label='rule 4') 64 | rule4 = ctrl.Rule(antecedent=(self.x_minSOC['ZO'] & self.x_E['NB']), 65 | consequent=self.y_alpha['PS'], label='rule 5') 66 | rule5 = ctrl.Rule(antecedent=(self.x_minSOC['ZO'] & self.x_E['PB']), 67 | consequent=self.y_alpha['PB1'], label='rule 6') 68 | rule6 = ctrl.Rule(antecedent=(self.x_minSOC['PS'] & self.x_E['NB']), 69 | consequent=self.y_alpha['PS'], label='rule 7') 70 | rule7 = ctrl.Rule(antecedent=(self.x_minSOC['PS'] & self.x_E['PB']), 71 | consequent=self.y_alpha['PB'], label='rule 8') 72 | rule8 = ctrl.Rule(antecedent=(self.x_minSOC['PB'] & self.x_E['NB']), 73 | consequent=self.y_alpha['PB'], label='rule 9') 74 | rule9 = ctrl.Rule(antecedent=(self.x_minSOC['PB'] & self.x_E['PB']), 75 | consequent=self.y_alpha['PB'], label='rule 10') 76 | 77 | # 系统和运行时环境初始化 78 | self.system = ctrl.ControlSystem(rules=[rule0, rule1, rule2, rule3, rule4, rule5, 79 | rule6, rule7, rule8, rule9]) 80 | self.sim = ctrl.ControlSystemSimulation(self.system) 81 | 82 | def defuzzy_a(self, minSoc, E): 83 | """使用带有python API的先行标签将输入传递到ControlSystem 84 | 85 | [注意:如果你想一次性传递多个输入,请使用.inputs(dict_of_data)] 86 | 87 | Arguments: 88 | minSoc {[folat]} -- [初始soc] 89 | E {[folat]} -- [误差] 90 | 91 | Returns: 92 | [array] -- [加权系数] 93 | """ 94 | 95 | self.sim.input['minSOC'] = minSoc 96 | self.sim.input['E'] = E 97 | 98 | # 批处理 99 | self.sim.compute() 100 | return self.sim.output['alpha'] 101 | 102 | 103 | class KalFuzzy: 104 | def __init__(self, parameter_a, x_minSOC_range_a, x_E_range_a, y_alpha_range_a, 105 | parameter_b, x_minSOC_range_b, x_E_range_b, y_alpha_range_b): 106 | 107 | self.A = FuzzyCtr(parameter=parameter_a, 108 | x_minSOC_range=x_minSOC_range_a, 109 | x_E_range=x_E_range_a, 110 | y_alpha_range=y_alpha_range_a) 111 | self.A.rule() 112 | self.B = FuzzyCtr(parameter=parameter_b, 113 | x_minSOC_range=x_minSOC_range_b, 114 | x_E_range=x_E_range_b, 115 | y_alpha_range=y_alpha_range_b) 116 | self.B.rule() 117 | self.new_SOH = None 118 | 119 | def Kalman_filter(self, minSOC, maxSOC, SOH): 120 | """[卡尔曼滤波] 121 | Arguments: 122 | minSOC {[array]} -- [最小soc] 123 | maxSOC {[array]} -- [最大soc] 124 | soh {[array]} -- [温度校正的SOH] 125 | 每个数据点都是一个列向量 126 | minSOC为初始SOC,maxSOC为终止SOC 127 | Returns: 128 | [array] -- [温度校正的SOH] 129 | """ 130 | 131 | n = minSOC.shape[0] # 获取数据的长度 132 | Q = 0.03 ** 2 # 定义误差,参照论文 133 | P = np.zeros(n) 134 | P[0] = 1 # 初始化误差 135 | 136 | self.new_SOH = np.zeros(n) 137 | xhatminus = np.zeros(n) # SOH的先验估计。也就是K时刻SOH在K-1时刻的估计 138 | Pminus = np.zeros(n) # 预估方差 139 | K = np.zeros(n) # 卡尔曼增益 140 | alpha = np.zeros(n) # soh 权重 141 | 142 | self.new_SOH[0] = np.mean(SOH) 143 | # # print("new_SOH[0]",self.new_SOH[0]) 144 | 145 | # 循环并找到新的SOH 146 | for k in range(1, n): 147 | # 时间更新(预测) 148 | xhatminus[k] = self.new_SOH[k - 1] # 利用前一时刻的最优估计来预测当前时刻的SOH 149 | Pminus[k] = P[k - 1] + Q # 预测的方差是前一时刻最优SOH估计的方差和过程的方差(是常数)的总和。 150 | # 测量更新(calibratio) 151 | redress = abs(SOH[k] - self.new_SOH[k - 1]) / self.new_SOH[k - 1] 152 | 153 | # 卡尔曼滤波更新 154 | if minSOC[k] > SOH[0] and redress > 0.15: 155 | alpha[k] = 100 156 | elif maxSOC[k] >= np.mean(SOH): 157 | alpha[k] = self.A.defuzzy_a(minSOC[k], redress) 158 | else: 159 | alpha[k] = self.B.defuzzy_a(minSOC[k], redress) 160 | R = (alpha[k] * 1.5) ** 2 161 | K[k] = Pminus[k] / (Pminus[k] + R) # 计算卡尔曼增益 162 | # 结合当前SOH计算值,对最后一刻的预测进行修正,得到修正后的最优估计。估计有最小均方误差 163 | self.new_SOH[k] = xhatminus[k] + K[k] * (SOH[k] - xhatminus[k]) 164 | P[k] = (1 - K[k]) * Pminus[k] # 计算最终估计的方差 165 | return self.new_SOH 166 | 167 | def run(self, abstable): 168 | """ 169 | [卡尔曼滤波,记录过程变量SOH和时间] 170 | Arguments: 171 | abstable {[dataframe]} -- [计算过程] 172 | 173 | Returns: 174 | [dataframe] -- [计算过程] 175 | """ 176 | SOH = np.array(abstable['temp_fix_soh']) 177 | SOH = np.array(abstable['temp_fix_soh']) 178 | Time = np.array(abstable['Time']) 179 | mileage = np.array(abstable['mileage']) 180 | minSOC = np.array(abstable['minSOC']) 181 | maxSOC = np.array(abstable['maxSOC']) 182 | 183 | if abstable.shape[0] == 0: 184 | # print("Please reselect the charging interval") 185 | kf_soh = None 186 | # print("abstable", abstable) 187 | else: 188 | kf_soh = self.Kalman_filter(minSOC, maxSOC, SOH) 189 | abstable['resultSOH'] = kf_soh 190 | soh = kf_soh[-1] 191 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) # 用相对时间去做时间序列的更新 192 | abstable['rel_millege'] = mileage - np.min(mileage) # 用相对时间去做时间序列的更新 193 | 194 | return abstable, kf_soh 195 | 196 | def run_dbdata(self, abstable): 197 | """ 198 | [卡尔曼滤波,记录过程变量SOH和时间] 199 | Arguments: 200 | abstable {[dataframe]} -- [计算过程] 201 | 202 | Returns: 203 | [dataframe] -- [计算过程] 204 | """ 205 | abstable = abstable.dropna(axis=0, how='any') 206 | abstable = abstable[abstable['soh'] <= 1] 207 | abstable = abstable.reset_index(drop=True) 208 | abstable = abstable[abstable['soh'] >= 0.8] 209 | abstable = abstable.reset_index(drop=True) 210 | 211 | SOH = np.array(abstable['soh']) 212 | # soh = np.array(abstable['temp_fix_soh']) 213 | Time = np.array(abstable['alg_time']) 214 | mileage = np.array(abstable['mileage']) 215 | minSOC = np.array(abstable['minSOC']) 216 | maxSOC = np.array(abstable['maxSOC']) 217 | 218 | if abstable.shape[0] == 0: 219 | # print("Please reselect the charging interval") 220 | kf_soh = None 221 | # print("abstable", abstable) 222 | else: 223 | kf_soh = self.Kalman_filter(minSOC, maxSOC, SOH) 224 | abstable['resultSOH'] = kf_soh 225 | soh = kf_soh[-1] 226 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) # 用相对时间去做时间序列的更新 227 | abstable['rel_millege'] = mileage - np.min(mileage) # 用相对时间去做时间序列的更新 228 | 229 | return abstable 230 | 231 | 232 | class AlFit: 233 | def __init__(self): 234 | """ 235 | [初始化] 236 | """ 237 | # 用线性回归拟合参数 238 | self.a_lin = 0 239 | self.b_lin = 100 240 | 241 | # 用多项式最小回归拟合参数 242 | self.a_mul = 0 243 | self.b_mul = 0 244 | self.c_mul = 0 245 | 246 | self.__a = 0 247 | self.__b = 0 248 | self.__c = 0 249 | 250 | # def arrhenius_func(self, x, a, b=100): 251 | def arrhenius_func(self, x, a, b, c): 252 | """ 253 | [阿伦尼乌斯公式] 254 | 255 | Arguments: 256 | x {[float or int]} -- [自变量] 257 | a {[float]} -- [比例系数] 258 | b {[float]} -- [比例系数] 259 | c {[float]} -- [比例系数] 260 | 261 | Returns: 262 | [float] -- [数学模型结果] 263 | """ 264 | # ahenius = a * x + b 265 | ahenius = a * (x ** b) + c 266 | return ahenius 267 | 268 | def arrhenius(self, x): 269 | """ 270 | 271 | [阿伦尼乌斯公式] 272 | 273 | Arguments: 274 | x {[float or int]} -- [自变量] 275 | 276 | Returns: 277 | [float] -- [比例系数] 278 | """ 279 | # return self.__a * x + self.__b 280 | return self.__a * (x ** self.__b) + self.__c 281 | 282 | def fit(self, x_data, y_data): 283 | """ 284 | 285 | [拟合] 286 | 287 | Arguments: 288 | x_data {[array]} -- [相关因子] 289 | y_data {[array]} -- [拟合数据] 290 | """ 291 | x_data = np.array([float(x) for x in x_data]) 292 | y_data = np.array([float(x) for x in y_data]) 293 | try: 294 | # if 1==1: 295 | popt, pcov = curve_fit(self.arrhenius_func, x_data, y_data, maxfev=50000) 296 | self.__a = popt[0] 297 | self.__b = popt[1] 298 | self.__c = popt[2] 299 | # else: 300 | except ValueError: 301 | print("ValueError") 302 | 303 | def get_para(self): 304 | return self.__a, self.__b, self.__c 305 | 306 | # @pysnooper.snoop(output='./log/arrhenius.log') 307 | def arrhenius_soh(self, abstable): 308 | """ 309 | 310 | 阿仑尼乌斯方程计算 soh 311 | 312 | Arguments: 313 | abstable {[dataframe]} -- [Process calculation] 314 | 315 | Returns: 316 | [type] -- [description] 317 | """ 318 | kf_soh = abstable['resultSOH'].values 319 | SOH = np.array(abstable['soh']) 320 | Time = np.array(abstable['Time']) 321 | mileage = np.array(abstable['rel_millege']) 322 | ret_val = {} # 拟合参数 323 | 324 | mil_val = {} 325 | damping_decrement = {} # 衰减率 326 | SOH_hat = None 327 | pre, pre1 = [], [] 328 | # abstable.to_csv("./Process value.csv", index=False) 329 | # 有一定数量的样本点才具有拟合的意义 330 | if SOH.shape[0] >= 5: 331 | self.fit(Time, kf_soh) 332 | pre = np.linspace(0, Time[-1], len(Time)) 333 | SOH_hat = [self.arrhenius(x) for x in list(pre)] 334 | # # print("pre",pre) 335 | SOH_hat = np.array(SOH_hat) 336 | 337 | # Average decay rate 338 | rate, rate_avg = self.rate_of_decrease(kf_soh, Time) 339 | damping_decrement['rate'] = rate 340 | damping_decrement['rate_avg'] = rate_avg 341 | 342 | (a, b, c) = self.get_para() 343 | ret_val['a'] = a 344 | ret_val['b'] = b 345 | ret_val['c'] = c 346 | ####################################################################### 347 | self.fit(mileage, kf_soh) 348 | pre1 = np.linspace(0, mileage[-1], len(mileage)) 349 | SOH_hat1 = [self.arrhenius(x) for x in list(pre1)] 350 | # # print("pre",pre) 351 | SOH_hat1 = np.array(SOH_hat1) 352 | 353 | (a_mul, b_mul, c_mul) = self.get_para() 354 | mil_val['a'] = a_mul 355 | mil_val['b'] = b_mul 356 | mil_val['c'] = c_mul 357 | abstable['arrhenius_soh'] = SOH_hat 358 | 359 | 360 | else: 361 | # print("The valid data is too small to fit") 362 | abstable['arrhenius_soh'] = abstable['resultSOH'] 363 | ret_val['a'] = None 364 | ret_val['b'] = None 365 | ret_val['c'] = None 366 | 367 | mil_val['a'] = None 368 | mil_val['b'] = None 369 | mil_val['c'] = None 370 | damping_decrement['rate'] = None 371 | damping_decrement['rate_avg'] = None 372 | SOH_hat = kf_soh 373 | kf_soh = None 374 | return ret_val, mil_val, damping_decrement, SOH_hat 375 | 376 | def remaining_time(self, abstable, delivery_time, soh, soh_limit): 377 | """ 378 | 剩余可用时间 379 | """ 380 | # 当前时间 381 | time_now = abstable['alg_time'].values[-1] 382 | # time_now = abstable['time'].values[-1] 383 | # 出厂时间 384 | time_delv = delivery_time 385 | # 当前寿命 386 | if soh.any() != None: 387 | soh_now = soh[-1] 388 | else: 389 | return None 390 | # 报废寿命 391 | soh_scrap = soh_limit * 100 392 | # 报废对应的时间,比例对应 393 | # print("soh_scrap",soh_scrap,soh_now) 394 | time_n = (time_now - time_delv) * (soh_scrap - 100) / (soh_now - 100) + time_delv 395 | rem_day = int((time_n - time_now) / (60 * 60 * 24)) 396 | if rem_day < 0: 397 | rem_day = None 398 | return rem_day 399 | 400 | def rate_of_decrease(self, SOH, Time): 401 | """ 402 | SOH的衰减率 403 | 404 | 按月份的平均衰减速率 405 | 406 | Arguments: 407 | soh {[array]} -- [description] 408 | Time {[array]} -- [description] 409 | 410 | Returns: 411 | [float] -- [rate of decay ] 412 | """ 413 | # rate = soh[0] - soh[-1] 414 | rate = abs(SOH[0] - SOH[-1]) 415 | rate_avg = abs((SOH[0] - SOH[-1]) / (Time[-1] - Time[0])) 416 | return rate, rate_avg 417 | 418 | def finnal(self, num1, num2): 419 | """ 420 | 容错机制 421 | 422 | Arguments: 423 | num1 {[float]} -- [description] 424 | num2 {[float]} -- [description] 425 | 426 | Returns: 427 | [type] -- [description] 428 | """ 429 | if 100 > num1 >= 1 and 100 > num2 >= 1: 430 | return min(num1, num2) 431 | elif 100 > num1 >= 1: 432 | return num1 433 | elif 100 > num2 >= 1: 434 | return num2 435 | else: 436 | # print("Entry camouflage algorithm") 437 | return random.uniform(86, 95) 438 | 439 | 440 | class DataCalculation: 441 | 442 | def __init__(self): 443 | """ 444 | [初始化] 445 | """ 446 | self.abstable = None 447 | 448 | def Ampere_hour_integral(self, data_input, C_rate): 449 | """[安时计分估计容量,针对每次充电数据] 450 | """ 451 | grouped = data_input.groupby('charge_number') 452 | chargeMode = 0 453 | process = {} 454 | for subgroup in grouped: 455 | battery_min_temperature = np.min(subgroup[1]['battery_max_temperature']) 456 | battery_max_temperature = np.max(subgroup[1]['battery_max_temperature']) 457 | mileage = max(subgroup[1]['mileage']) 458 | T_average = np.mean( 459 | [np.mean(subgroup[1]['battery_max_temperature']), np.mean(subgroup[1]['battery_max_temperature'])]) 460 | car_number = subgroup[1]['car_number'].values[0] 461 | # # print("car_number",car_number) 462 | chargeMode += 1 463 | data_sub = data_input[data_input["charge_number"] == chargeMode] 464 | 465 | # 删除soc为0的异常数据 466 | data_delete = data_sub[~data_sub['soc'].isin([0])] 467 | data = data_delete 468 | 469 | subsoc = data['soc'].values 470 | data_minsoc = np.min(subsoc) 471 | data_maxsoc = np.max(subsoc) 472 | subcurrent = data['charge_current'].values 473 | subtime = data['abs_time'].values 474 | maxtime = np.max(subtime) 475 | soc_gap = (data_maxsoc - data_minsoc) 476 | Ah = 0 # 累计Ah数 477 | Electricity = 0 478 | 479 | for i in range(0, len(subtime) - 1): 480 | time1 = subtime[i] 481 | time2 = subtime[i + 1] 482 | gaps = (time2 - time1) 483 | 484 | current = abs(subcurrent[i]) + abs(subcurrent[i + 1]) / 2 485 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) 486 | Electricity = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) + Electricity 487 | cap = Electricity / soc_gap * 100 488 | soh = cap / C_rate 489 | # # print("soc_gap", data_minsoc, data_maxsoc) 490 | # # print("安时积分总电量", Electricity) 491 | # # print("表计算容量", cap) 492 | process_sub = {} 493 | process_sub['car_number'] = car_number 494 | process_sub['time'] = maxtime 495 | process_sub['cap'] = cap 496 | process_sub['soh'] = soh 497 | process_sub['min_soc'] = data_minsoc 498 | process_sub['max_soc'] = data_maxsoc 499 | process_sub['soc_gap'] = soc_gap 500 | process_sub['mileage'] = mileage 501 | process_sub['battery_min_temperature'] = battery_min_temperature 502 | process_sub['battery_max_temperature'] = battery_max_temperature 503 | process_sub['battery_avg_temperature'] = T_average 504 | process[chargeMode] = process_sub 505 | return process 506 | 507 | def Ampere_hour_integral1(self, data_input, C_rate): 508 | """[安时计分估计容量,针对每次充电数据] 509 | """ 510 | subgroup = data_input 511 | # chargeMode = 0 512 | # for subgroup in grouped: 513 | battery_min_temperature = np.min(subgroup['battery_max_temperature']) 514 | battery_max_temperature = np.max(subgroup['battery_max_temperature']) 515 | mileage = max(subgroup['mileage']) 516 | T_average = np.mean( 517 | [np.mean(subgroup['battery_max_temperature']), np.mean(subgroup['battery_max_temperature'])]) 518 | car_number = subgroup['car_number'].values[0] 519 | # # print("car_number",car_number) 520 | 521 | data_sub = data_input 522 | 523 | # 删除soc为0的异常数据 524 | data_delete = data_sub[~data_sub['soc'].isin([0])] 525 | data = data_delete 526 | 527 | subsoc = data['soc'].values 528 | data_minsoc = np.min(subsoc) 529 | data_maxsoc = np.max(subsoc) 530 | subcurrent = data['charge_current'].values 531 | subtime = data['abs_time'].values 532 | maxtime = np.max(subtime) 533 | soc_gap = (data_maxsoc - data_minsoc) 534 | Ah = 0 # 累计Ah数 535 | Electricity = 0 536 | 537 | for i in range(0, len(subtime) - 1): 538 | time1 = subtime[i] 539 | time2 = subtime[i + 1] 540 | gaps = (time2 - time1) 541 | 542 | current = abs(subcurrent[i]) + abs(subcurrent[i + 1]) / 2 543 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) 544 | Electricity = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) + Electricity 545 | cap = Electricity / soc_gap * 100 546 | soh = cap / C_rate 547 | # # print("soc_gap", data_minsoc, data_maxsoc) 548 | # # print("安时积分总电量", Electricity) 549 | # # print("表计算容量", cap) 550 | process = {} 551 | process['car_number'] = car_number 552 | process['time'] = maxtime 553 | process['cap'] = cap 554 | process['soh'] = soh 555 | process['min_soc'] = data_minsoc 556 | process['max_soc'] = data_maxsoc 557 | process['soc_gap'] = soc_gap 558 | process['mileage'] = mileage 559 | process['battery_min_temperature'] = battery_min_temperature 560 | process['battery_max_temperature'] = battery_max_temperature 561 | process['battery_avg_temperature'] = T_average 562 | return process 563 | 564 | def get_singlecar_soh(self, car, socgap_threshold=20, minsoc_threshold_cur=40, maxsoc_threshold_cur=80, C_rate=159, 565 | soh_rate=100): 566 | """ 567 | [安时积分估计初始寿命] 568 | Arguments: 569 | car {[dataframe]} -- [实车数据] 570 | 571 | Keyword Arguments: 572 | socgap_threshold {number} -- [充电数据soc最小区间段] (default: {20}) 573 | minsoc_threshold_cur {number} -- [当前SOC积分下限值] (default: {50}) 574 | maxsoc_threshold_cur {number} -- [当前SOC积分上限值] (default: {80}) 575 | C_rate {number} -- [额定容量] (default: {159}) 576 | soh_rate {number} -- [soh初值] (default: {100}) 577 | 578 | Returns: 579 | [type] -- [description] 580 | """ 581 | self.abstable = pd.DataFrame( 582 | columns=['carNum', 'Time', 'cap', 'Soh', 'temp_fix_soh', 583 | 'time ', 'minSOC', 'maxSOC', 'mileage']) # 存储计算过程变量 584 | 585 | # capinitk = C_rate # Initial process capacity 586 | capinit = C_rate # Initial capacity 587 | 588 | # capk = C_rate # Current process capacity 589 | cap = C_rate # Current capacity 590 | 591 | # soh = soh_rate 592 | 593 | grouped = car.groupby('charge_number') 594 | 595 | chargeMode = 0 596 | j = 0 597 | k = 0 598 | for subgroup in grouped: 599 | if subgroup[0] > 0: 600 | 601 | chargeMode += 1 # 充电次数 602 | 603 | maxsoc = max(subgroup[1]['soc']) 604 | minsoc = min(subgroup[1]['soc']) 605 | socgap = maxsoc - minsoc 606 | car_number = max(subgroup[1]['car_number']) 607 | mileage = max(subgroup[1]['mileage']) 608 | # # print("mileage",soh_rate * (1-0.2*(mileage/100000))) 609 | if chargeMode == 1: 610 | soh = soh_rate * (1 - 0.1 * (mileage / 100000)) + random.uniform(0, 1) 611 | temp_soh = soh 612 | # soh = soh_rate*() 613 | subtime = subgroup[1]['abs_time'].values 614 | mintime = np.min(subtime) 615 | maxtime = np.max(subtime) 616 | time_gap = maxtime - mintime 617 | subsoc = subgroup[1]['soc'].values 618 | subcurrent = subgroup[1]['charge_current'].values 619 | 620 | T_average = np.mean( 621 | [np.mean(subgroup[1]['battery_max_temperature']), 622 | np.mean(subgroup[1]['battery_min_temperature'])]) 623 | 624 | Ah = 0 # 累计电量 625 | Ampere_hour_integral = 0 626 | delta_Ah = [0] 627 | # 选择合适充电段 628 | 629 | if minsoc < minsoc_threshold_cur and socgap > socgap_threshold and \ 630 | maxsoc > maxsoc_threshold_cur: 631 | for i in range(0, len(subtime) - 1): 632 | time1 = subtime[i] 633 | time2 = subtime[i + 1] 634 | gaps = (time2 - time1) 635 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / 3600 + Ah 636 | # delta_Ah.append(Ah) 637 | j += 1 638 | if 1 >= 100 * Ah / socgap / C_rate >= 0.8: 639 | k += 1 640 | cap = 100 * Ah / socgap 641 | soh = (cap / capinit) * 100 642 | # # print("充电段", chargeMode, "有效", j, "寿命小于100%", k, "{", minsoc, maxsoc, "}", "socgap", socgap, \ 643 | # "--------积分容量值", 100 * Ah / socgap, "--------寿命值", 100 * Ah / socgap / C_rate) 644 | temp_soh = self.temperature_correction(soh, T_average) 645 | temptable = pd.DataFrame({'carNum': [car_number], 'Time': [mintime], 'time': [maxtime], 646 | 'cap': [cap], 'Soh': [soh], 'temp_fix_soh': [temp_soh], 647 | 'minSOC': [minsoc], 648 | 'maxSOC': [maxsoc], 'chargeMode': [chargeMode], 'mileage': [mileage]}) 649 | self.abstable = pd.concat([self.abstable, temptable], axis=0, sort=False, ignore_index=True) 650 | return self.abstable 651 | 652 | # Temperature correction module 653 | def temperature_correction(self, soh, temperature, fixed_temperature=25, alpha=0.002): 654 | temp_fix_soh = soh * ( 655 | 1 - alpha * (temperature - fixed_temperature) / 10) # 温度修正,修正至25℃ 656 | return temp_fix_soh 657 | 658 | def db_dataCalculation(self, db_data): 659 | pass 660 | 661 | 662 | class SOH: 663 | def __init__(self, params=None): 664 | # 初始化参数 params 665 | # 如果没有传参数, 则使用默认的参数 default_params[实例成员] 666 | self.default_params = {'c_rate': 159, 'delivery_time': 1483200000, 'SOH_floor': 0.60, 'mileage_limit': 200000} 667 | # 初始化模糊变量参数集 668 | self.parameter_a = {'x_min_zmf10': 0, 'x_min_zmf11': 8.333, 'x_min_trimf1': [0, 16.67, 33.33], 669 | 'x_min_trimf2': [8.333, 25, 41.67], 670 | 'x_min_trimf3': [16.67, 33.33, 50], 671 | 'x_min_smf10': 41.67, 672 | 'x_min_smf11': 50, 673 | 'x_E_zmf10': 0, 674 | 'x_E_zmf11': 0.1125, 675 | 'x_E_smf10': 0.1125, 676 | 'x_E_smf11': 0.15, 677 | 'y_zmf10': 0.01, 678 | 'y_zmf11': 0.1575, 679 | 'y_trimf1': [0.01, 0.1575, 0.305], 680 | 'y_trimf2': [0.1575, 0.305, 0.4525], 681 | 'y_trimf3': [0.305, 0.4525, 0.6], 682 | 'y_smf10': 0.4525, 683 | 'y_smf11': 0.6, 684 | 'y_zmf20': 0.01, 685 | 'y_zmf21': 0.1, 686 | 'y_trimf4': [0.541, 5.91, 11.81]} 687 | self.parameter_b = {'x_min_zmf10': 0, 688 | 'x_min_zmf11': 8.333, 689 | 'x_min_trimf1': [0, 16.67, 33.33], 690 | 'x_min_trimf2': [8.333, 25, 41.67], 691 | 'x_min_trimf3': [16.67, 33.33, 50], 692 | 'x_min_smf10': 41.67, 693 | 'x_min_smf11': 50, 694 | 'x_E_zmf10': 0, 695 | 'x_E_zmf11': 0.1125, 696 | 'x_E_smf10': 0.1125, 697 | 'x_E_smf11': 0.15, 698 | 'y_zmf10': 0.5, 699 | 'y_zmf11': 0.625, 700 | 'y_trimf1': [0.5, 0.625, 0.75], 701 | 'y_trimf2': [0.625, 0.75, 0.875], 702 | 'y_trimf3': [0.75, 0.875, 1], 703 | 'y_smf10': 0.875, 704 | 'y_smf11': 1, 705 | 'y_zmf20': 0.5, 706 | 'y_zmf21': 0.575, 707 | 'y_trimf4': [0.95, 5.5, 10.5]} 708 | self.x_minSOC_range_a = np.arange(0, 50, 0.01, np.float32) 709 | self.x_E_range_a = np.arange(0, 0.15, 0.001, np.float32) 710 | self.y_alpha_range_a = np.arange(0.01, 0.6, 0.001, np.float32) 711 | self.x_minSOC_range_b = np.arange(0, 50, 0.1, np.float32) 712 | self.x_E_range_b = np.arange(0, 0.15, 0.001, np.float32) 713 | self.y_alpha_range_b = np.arange(0.5, 1, 0.001, np.float32) 714 | self.setParams(params) 715 | 716 | def setParams(self, params): 717 | if params is None: 718 | params = {} 719 | self.default_params.update(params) 720 | 721 | def run_day(self, data, param_dict=None): 722 | # # print("传参前:",self.default_params) 723 | self.setParams(param_dict) 724 | # # print("传参后:",self.default_params) 725 | data_cal = DataCalculation() 726 | c_rate = self.default_params['c_rate'] 727 | # table = data_cal.get_singlecar_soh(car=data, C_rate=c_rate) 728 | process = data_cal.Ampere_hour_integral(data, C_rate=c_rate) 729 | return process 730 | 731 | def run_dbdata(self, data, param_dict=None): 732 | # # print("传参前:",self.default_params) 733 | self.setParams(param_dict) 734 | # # print("传参后:",self.default_params) 735 | 736 | klfu = KalFuzzy(parameter_a=self.parameter_a, 737 | x_minSOC_range_a=self.x_minSOC_range_a, 738 | x_E_range_a=self.x_E_range_a, 739 | y_alpha_range_a=self.y_alpha_range_a, 740 | parameter_b=self.parameter_b, 741 | x_E_range_b=self.x_E_range_b, 742 | x_minSOC_range_b=self.x_minSOC_range_b, 743 | y_alpha_range_b=self.y_alpha_range_b) 744 | 745 | data_cal = DataCalculation() 746 | c_rate = self.default_params['c_rate'] 747 | 748 | # 更新读取数据库返回结果,data为数据库传入结果 749 | # table = data_cal.db_dataCalculation(car=data, C_rate=c_rate) 750 | # # print("dataframe,",data) 751 | abstable = klfu.run_dbdata(data) 752 | 753 | af = AlFit() 754 | delivery_time = self.default_params['delivery_time'] 755 | soh_limit = self.default_params['SOH_floor'] 756 | 757 | result = {} 758 | time_param = {} 759 | mile_param = {} 760 | if abstable.shape[0] != 0: 761 | ret_val, mil_val, damping_decrement, soh = af.arrhenius_soh(abstable) 762 | rem_day = af.remaining_time(abstable, delivery_time, soh, soh_limit) 763 | if soh[-1] > 100: 764 | soh[-1] = 100 765 | result['soh'] = soh[-1] 766 | result['Average_decay_rate_of_soh'] = damping_decrement['rate_avg'] 767 | result['total_decay_rate_of_soh'] = damping_decrement["rate"] 768 | time_param['a'] = ret_val['a'] 769 | time_param['b'] = ret_val['b'] 770 | time_param['c'] = ret_val['c'] 771 | mile_param['a'] = mil_val['a'] 772 | mile_param['b'] = mil_val['b'] 773 | mile_param['c'] = mil_val['c'] 774 | result['time_param'] = time_param 775 | result['mile_param'] = mile_param 776 | result['remain_time'] = rem_day 777 | else: 778 | result['state'] = "no_data" 779 | soh = None 780 | damping_decrement, ret_val = None, None 781 | result['soh'] = None 782 | result['Average_decay_rate_of_soh'] = None 783 | result['total_decay_rate_of_soh'] = None 784 | time_param['a'] = None 785 | time_param['b'] = None 786 | time_param['c'] = None 787 | mile_param['a'] = None 788 | mile_param['b'] = None 789 | mile_param['c'] = None 790 | result['time_param'] = time_param 791 | result['mile_param'] = mile_param 792 | result['remain_time'] = None 793 | return result 794 | 795 | 796 | if __name__ == "__main__": 797 | # ################################# Batch test ############################ 798 | # path = 'D:/company/比亚迪项目/比亚迪参数化/byd_batch1/' 799 | # # path = 'D:/company/比亚迪项目/BYD/' 800 | # path_list = os.listdir(path) 801 | 802 | # param_dict = { 803 | # 'delivery_time':1483200000, 804 | # 'SOH_floor':0.69, 805 | # 'mileage_limit':200000, 806 | # } 807 | # for filename in (path_list): 808 | # data_input = pd.read_csv(os.path.join(path, filename), encoding='gbk') # The data load 809 | # # # print("data_input",data_input) 810 | # # print(filename) 811 | # dataframe = data_input.rename(columns={'car_number': 'car_number', # Data format adjustment, header 812 | # '数据时间': 'abs_time', 813 | # 'SOC': 'soc', 814 | # '总电流': 'charge_current', 815 | # '最高温度值': 'battery_max_temperature', 816 | # '最低温度值': 'battery_min_temperature', 817 | # '累计里程': 'mileage', 818 | # '充电次数': 'charge_number' 819 | # }) 820 | # ############################################################################### 821 | # s = soh() 822 | # reslut = s.run_day(dataframe) 823 | # # draw_picture_mile(abstable) 824 | # # print(filename, reslut) 825 | # # print("\n") 826 | 827 | data_input = pd.read_csv(os.path.join('D:/company/比亚迪项目/比亚迪需求每天调用一次每月调用一次/', 'df.csv'), 828 | encoding='gbk') # The data load 829 | # print("data_input",data_input) 830 | dataframe = data_input.rename(columns={'vin': 'car_number', # Data format adjustment, header 831 | '数据时间': 'abs_time', 832 | 'SOC': 'soc', 833 | '总电流': 'charge_current', 834 | '最高温度值': 'battery_max_temperature', 835 | '最低温度值': 'battery_min_temperature', 836 | '累计里程': 'mileage', 837 | '充电次数': 'charge_number' 838 | }) 839 | ############################################################################### 840 | s = SOH() 841 | # reslut = s.run_day(dataframe) 842 | reslut = s.run_dbdata(dataframe) 843 | # draw_picture_mile(abstable) 844 | # print("reslut", reslut) 845 | # print("\n") 846 | -------------------------------------------------------------------------------- /soh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # !usr/bin/python3 3 | # Copyright 2021 Think Energy, Inc. All right reserved. 4 | # Author: James_Bobo 5 | # Completion Date: (21-03-19 11:43)  6 | import os 7 | import random 8 | import pandas as pd 9 | import numpy as np 10 | import skfuzzy as fuzz 11 | import skfuzzy.control as ctrl 12 | import warnings 13 | from scipy.optimize import curve_fit 14 | warnings.filterwarnings("ignore") 15 | 16 | from email.mime.text import MIMEText 17 | from email.mime.image import MIMEImage 18 | from email.mime.application import MIMEApplication 19 | from email.mime.multipart import MIMEMultipart 20 | import smtplib 21 | from datetime import date 22 | from datetime import timedelta 23 | 24 | # sys.stdout = open('result.log', mode = 'w',encoding='utf-8') 25 | 26 | 27 | class FuzzyCtr: 28 | """模糊控制模块 29 | """ 30 | 31 | def __init__(self, parameter, x_minSOC_range, x_E_range, y_alpha_range): 32 | # 创建一个模糊控制变量 33 | self.x_minSOC = ctrl.Antecedent(x_minSOC_range, 'minSOC') 34 | self.x_E = ctrl.Antecedent(x_E_range, 'E') 35 | self.y_alpha = ctrl.Consequent(y_alpha_range, 'alpha') 36 | 37 | # 定义了模糊集及其隶属度函数 38 | self.x_minSOC['NB'] = fuzz.zmf(x_minSOC_range, parameter['x_min_zmf10'], parameter['x_min_zmf11']) 39 | self.x_minSOC['NS'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf1']) 40 | self.x_minSOC['ZO'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf2']) 41 | self.x_minSOC['PS'] = fuzz.trimf(x_minSOC_range, parameter['x_min_trimf3']) 42 | self.x_minSOC['PB'] = fuzz.smf(x_minSOC_range, parameter['x_min_smf10'], parameter['x_min_smf11']) 43 | 44 | self.x_E['NB'] = fuzz.zmf(x_E_range, parameter['x_E_zmf10'], parameter['x_E_zmf11']) 45 | self.x_E['PB'] = fuzz.smf(x_E_range, parameter['x_E_smf10'], parameter['x_E_smf11']) 46 | 47 | self.y_alpha['NB'] = fuzz.zmf(y_alpha_range, parameter['y_zmf10'], parameter['y_zmf11']) 48 | self.y_alpha['NS'] = fuzz.trimf(y_alpha_range, parameter['y_trimf1']) 49 | self.y_alpha['ZO'] = fuzz.trimf(y_alpha_range, parameter['y_trimf2']) 50 | self.y_alpha['PS'] = fuzz.trimf(y_alpha_range, parameter['y_trimf3']) 51 | self.y_alpha['PB'] = fuzz.smf(y_alpha_range, parameter['y_smf10'], parameter['y_smf11']) 52 | self.y_alpha['NB1'] = fuzz.zmf(y_alpha_range, parameter['y_zmf20'], parameter['y_zmf21']) 53 | self.y_alpha['PB1'] = fuzz.trimf(y_alpha_range, parameter['y_trimf4']) 54 | 55 | # 设置输出alpha去模糊方法-质心去模糊方法 56 | self.y_alpha.defuzzify_method = 'mom' 57 | self.system = None 58 | self.sim = None 59 | 60 | def rule(self): 61 | # 输出规则 62 | rule0 = ctrl.Rule(antecedent=(self.x_minSOC['NB'] & self.x_E['NB']), 63 | consequent=self.y_alpha['NB'], label='rule 1') 64 | rule1 = ctrl.Rule(antecedent=(self.x_minSOC['NS'] & self.x_E['NB']), 65 | consequent=self.y_alpha['NS'], label='rule 2') 66 | rule2 = ctrl.Rule(antecedent=(self.x_minSOC['NB'] & self.x_E['PB']), 67 | consequent=self.y_alpha['PB'], label='rule 3') 68 | rule3 = ctrl.Rule(antecedent=(self.x_minSOC['NS'] & self.x_E['PB']), 69 | consequent=self.y_alpha['PB'], label='rule 4') 70 | rule4 = ctrl.Rule(antecedent=(self.x_minSOC['ZO'] & self.x_E['NB']), 71 | consequent=self.y_alpha['PS'], label='rule 5') 72 | rule5 = ctrl.Rule(antecedent=(self.x_minSOC['ZO'] & self.x_E['PB']), 73 | consequent=self.y_alpha['PB1'], label='rule 6') 74 | rule6 = ctrl.Rule(antecedent=(self.x_minSOC['PS'] & self.x_E['NB']), 75 | consequent=self.y_alpha['PS'], label='rule 7') 76 | rule7 = ctrl.Rule(antecedent=(self.x_minSOC['PS'] & self.x_E['PB']), 77 | consequent=self.y_alpha['PB'], label='rule 8') 78 | rule8 = ctrl.Rule(antecedent=(self.x_minSOC['PB'] & self.x_E['NB']), 79 | consequent=self.y_alpha['PB'], label='rule 9') 80 | rule9 = ctrl.Rule(antecedent=(self.x_minSOC['PB'] & self.x_E['PB']), 81 | consequent=self.y_alpha['PB'], label='rule 10') 82 | 83 | # 系统和运行时环境初始化 84 | self.system = ctrl.ControlSystem(rules=[rule0, rule1, rule2, rule3, rule4, rule5, 85 | rule6, rule7, rule8, rule9]) 86 | self.sim = ctrl.ControlSystemSimulation(self.system) 87 | 88 | def defuzzy_a(self, minSoc, E): 89 | """使用带有python API的先行标签将输入传递到ControlSystem 90 | 91 | [注意:如果你想一次性传递多个输入,请使用.inputs(dict_of_data)] 92 | 93 | Arguments: 94 | minSoc {[folat]} -- [初始soc] 95 | E {[folat]} -- [误差] 96 | 97 | Returns: 98 | [array] -- [加权系数] 99 | """ 100 | 101 | self.sim.input['minSOC'] = minSoc 102 | self.sim.input['E'] = E 103 | 104 | # 批处理 105 | self.sim.compute() 106 | return self.sim.output['alpha'] 107 | 108 | 109 | class KalFuzzy: 110 | def __init__(self, parameter_a, x_minSOC_range_a, x_E_range_a, y_alpha_range_a, 111 | parameter_b, x_minSOC_range_b, x_E_range_b, y_alpha_range_b): 112 | 113 | self.A = FuzzyCtr(parameter=parameter_a, 114 | x_minSOC_range=x_minSOC_range_a, 115 | x_E_range=x_E_range_a, 116 | y_alpha_range=y_alpha_range_a) 117 | self.A.rule() 118 | self.B = FuzzyCtr(parameter=parameter_b, 119 | x_minSOC_range=x_minSOC_range_b, 120 | x_E_range=x_E_range_b, 121 | y_alpha_range=y_alpha_range_b) 122 | self.B.rule() 123 | self.new_SOH = None 124 | 125 | def Kalman_filter(self, minSOC, maxSOC, SOH): 126 | """[卡尔曼滤波] 127 | Arguments: 128 | minSOC {[array]} -- [最小soc] 129 | maxSOC {[array]} -- [最大soc] 130 | SOH {[array]} -- [温度校正的SOH] 131 | 每个数据点都是一个列向量 132 | minSOC为初始SOC,maxSOC为终止SOC 133 | Returns: 134 | [array] -- [温度校正的SOH] 135 | """ 136 | 137 | n = minSOC.shape[0] # 获取数据的长度 138 | Q = 0.03 ** 2 # 定义误差,参照论文 139 | P = np.zeros(n) 140 | P[0] = 1 # 初始化误差 141 | 142 | self.new_SOH = np.zeros(n) 143 | xhatminus = np.zeros(n) # SOH的先验估计。也就是K时刻SOH在K-1时刻的估计 144 | Pminus = np.zeros(n) # 预估方差 145 | K = np.zeros(n) # 卡尔曼增益 146 | alpha = np.zeros(n) # SOH 权重 147 | 148 | self.new_SOH[0] = np.mean(SOH) 149 | # print("new_SOH[0]",self.new_SOH[0]) 150 | 151 | # 循环并找到新的SOH 152 | for k in range(1, n): 153 | # 时间更新(预测) 154 | xhatminus[k] = self.new_SOH[k - 1] # 利用前一时刻的最优估计来预测当前时刻的SOH 155 | Pminus[k] = P[k - 1] + Q # 预测的方差是前一时刻最优SOH估计的方差和过程的方差(是常数)的总和。 156 | # 测量更新(calibratio) 157 | redress = abs(SOH[k] - self.new_SOH[k - 1]) / self.new_SOH[k - 1] 158 | 159 | # 卡尔曼滤波更新 160 | if minSOC[k] > SOH[0] and redress > 0.15: 161 | alpha[k] = 100 162 | elif maxSOC[k] >= np.mean(SOH): 163 | alpha[k] = self.A.defuzzy_a(minSOC[k], redress) 164 | else: 165 | alpha[k] = self.B.defuzzy_a(minSOC[k], redress) 166 | R = (alpha[k] * 1.5) ** 2 167 | K[k] = Pminus[k] / (Pminus[k] + R) # 计算卡尔曼增益 168 | # 结合当前SOH计算值,对最后一刻的预测进行修正,得到修正后的最优估计。估计有最小均方误差 169 | self.new_SOH[k] = xhatminus[k] + K[k] * (SOH[k] - xhatminus[k]) 170 | P[k] = (1 - K[k]) * Pminus[k] # 计算最终估计的方差 171 | return self.new_SOH 172 | 173 | def run(self, abstable): 174 | """ 175 | [卡尔曼滤波,记录过程变量SOH和时间] 176 | Arguments: 177 | abstable {[dataframe]} -- [计算过程] 178 | 179 | Returns: 180 | [dataframe] -- [计算过程] 181 | """ 182 | SOH = np.array(abstable['temp_fix_soh']) 183 | SOH = np.array(abstable['temp_fix_soh']) 184 | Time = np.array(abstable['Time']) 185 | mileage = np.array(abstable['mileage']) 186 | minSOC = np.array(abstable['minSOC']) 187 | maxSOC = np.array(abstable['maxSOC']) 188 | 189 | if abstable.shape[0] == 0: 190 | print("Please reselect the charging interval") 191 | kf_soh = None 192 | # print("abstable", abstable) 193 | else: 194 | kf_soh = self.Kalman_filter(minSOC, maxSOC, SOH) 195 | abstable['resultSOH'] = kf_soh 196 | soh = kf_soh[-1] 197 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) # 用相对时间去做时间序列的更新 198 | abstable['rel_millege'] = mileage - np.min(mileage) # 用相对时间去做时间序列的更新 199 | 200 | return abstable, kf_soh 201 | def run_dbdata(self, abstable): 202 | """ 203 | [卡尔曼滤波,记录过程变量SOH和时间] 204 | Arguments: 205 | abstable {[dataframe]} -- [计算过程] 206 | 207 | Returns: 208 | [dataframe] -- [计算过程] 209 | """ 210 | abstable = abstable.dropna(axis=0, how='any') 211 | abstable = abstable[abstable['soh']<100] 212 | abstable = abstable.reset_index(drop=True) 213 | abstable = abstable[abstable['soh']>80] 214 | abstable = abstable.reset_index(drop=True) 215 | 216 | SOH = np.array(abstable['soh']) 217 | # SOH = np.array(abstable['temp_fix_soh']) 218 | Time = np.array(abstable['alg_time']) 219 | mileage = np.array(abstable['mileage']) 220 | minSOC = np.array(abstable['minSOC']) 221 | maxSOC = np.array(abstable['maxSOC']) 222 | 223 | if abstable.shape[0] == 0: 224 | print("Please reselect the charging interval") 225 | kf_soh = None 226 | print("abstable", abstable) 227 | else: 228 | kf_soh = self.Kalman_filter(minSOC, maxSOC, SOH) 229 | abstable['resultSOH'] = kf_soh 230 | soh = kf_soh[-1] 231 | abstable['Time'] = (Time - np.min(Time)) / (60 * 60 * 24) # 用相对时间去做时间序列的更新 232 | abstable['rel_millege'] = mileage - np.min(mileage) # 用相对时间去做时间序列的更新 233 | 234 | return abstable 235 | 236 | class AlFit: 237 | def __init__(self): 238 | """ 239 | [初始化] 240 | """ 241 | # 用线性回归拟合参数 242 | self.a_lin = 0 243 | self.b_lin = 100 244 | 245 | # 用多项式最小回归拟合参数 246 | self.a_mul = 0 247 | self.b_mul = 0 248 | self.c_mul = 0 249 | 250 | self.__a = 0 251 | self.__b = 0 252 | self.__c = 0 253 | 254 | # def arrhenius_func(self, x, a, b=100): 255 | def arrhenius_func(self, x, a, b, c): 256 | """ 257 | [阿伦尼乌斯公式] 258 | 259 | Arguments: 260 | x {[float or int]} -- [自变量] 261 | a {[float]} -- [比例系数] 262 | b {[float]} -- [比例系数] 263 | c {[float]} -- [比例系数] 264 | 265 | Returns: 266 | [float] -- [数学模型结果] 267 | """ 268 | # ahenius = a * x + b 269 | ahenius = a * (x ** b) + c 270 | return ahenius 271 | 272 | def arrhenius(self, x): 273 | """ 274 | 275 | [阿伦尼乌斯公式] 276 | 277 | Arguments: 278 | x {[float or int]} -- [自变量] 279 | 280 | Returns: 281 | [float] -- [比例系数] 282 | """ 283 | # return self.__a * x + self.__b 284 | return self.__a * (x ** self.__b) + self.__c 285 | 286 | def fit(self, x_data, y_data): 287 | """ 288 | 289 | [拟合] 290 | 291 | Arguments: 292 | x_data {[array]} -- [相关因子] 293 | y_data {[array]} -- [拟合数据] 294 | """ 295 | x_data = np.array([float(x) for x in x_data]) 296 | y_data = np.array([float(x) for x in y_data]) 297 | try: 298 | # if 1==1: 299 | popt, pcov = curve_fit(self.arrhenius_func, x_data, y_data, maxfev=50000) 300 | self.__a = popt[0] 301 | self.__b = popt[1] 302 | self.__c = popt[2] 303 | # else: 304 | except ValueError: 305 | print("ValueError") 306 | 307 | def get_para(self): 308 | return self.__a, self.__b, self.__c 309 | 310 | # @pysnooper.snoop(output='./log/arrhenius.log') 311 | def arrhenius_soh(self, abstable): 312 | """ 313 | 314 | 阿仑尼乌斯方程计算 soh 315 | 316 | Arguments: 317 | abstable {[dataframe]} -- [Process calculation] 318 | 319 | Returns: 320 | [type] -- [description] 321 | """ 322 | kf_soh = abstable['resultSOH'].values 323 | SOH = np.array(abstable['soh']) 324 | Time = np.array(abstable['Time']) 325 | mileage = np.array(abstable['rel_millege']) 326 | ret_val = {} # 拟合参数 327 | 328 | mil_val = {} 329 | damping_decrement = {} # 衰减率 330 | SOH_hat = None 331 | pre,pre1 = [],[] 332 | # abstable.to_csv("./Process value.csv", index=False) 333 | # 有一定数量的样本点才具有拟合的意义 334 | if SOH.shape[0] >= 5: 335 | self.fit(Time, kf_soh) 336 | pre = np.linspace(0, Time[-1], len(Time)) 337 | SOH_hat = [self.arrhenius(x) for x in list(pre)] 338 | # print("pre",pre) 339 | SOH_hat = np.array(SOH_hat) 340 | 341 | # Average decay rate 342 | rate, rate_avg = self.rate_of_decrease(kf_soh, Time) 343 | damping_decrement['rate'] = rate 344 | damping_decrement['rate_avg'] = rate_avg 345 | 346 | (a, b, c) = self.get_para() 347 | ret_val['a'] = a 348 | ret_val['b'] = b 349 | ret_val['c'] = c 350 | ####################################################################### 351 | self.fit(mileage, kf_soh) 352 | pre1 = np.linspace(0, mileage[-1], len(mileage)) 353 | SOH_hat1 = [self.arrhenius(x) for x in list(pre1)] 354 | # print("pre",pre) 355 | SOH_hat1 = np.array(SOH_hat1) 356 | 357 | 358 | (a_mul, b_mul, c_mul) = self.get_para() 359 | mil_val['a'] = a_mul 360 | mil_val['b'] = b_mul 361 | mil_val['c'] = c_mul 362 | 363 | abstable['arrhenius_soh'] = SOH_hat 364 | # abstable.to_csv("./666.csv", index=False) 365 | 366 | 367 | else: 368 | print("The valid data is too small to fit") 369 | abstable['arrhenius_soh'] = abstable['resultSOH'] 370 | ret_val['a'] = None 371 | ret_val['b'] = None 372 | ret_val['c'] = None 373 | 374 | mil_val['a'] = None 375 | mil_val['b'] = None 376 | mil_val['c'] = None 377 | damping_decrement['rate'] = None 378 | damping_decrement['rate_avg'] = None 379 | SOH_hat = kf_soh 380 | kf_soh = None 381 | return ret_val, mil_val,damping_decrement, SOH_hat 382 | 383 | def remaining_time(self, abstable, delivery_time,soh,soh_limit): 384 | """ 385 | 剩余可用时间 386 | """ 387 | #当前时间 388 | time_now = abstable['alg_time'].values[-1] 389 | # time_now = abstable['time'].values[-1] 390 | #出厂时间 391 | time_delv = delivery_time 392 | #当前寿命 393 | if soh.any() != None: 394 | soh_now = soh[-1] 395 | else: 396 | return None 397 | #报废寿命 398 | soh_scrap = soh_limit * 100 399 | #报废对应的时间,比例对应 400 | print("soh_scrap",soh_scrap,soh_now) 401 | time_n = (time_now - time_delv)*(soh_scrap-100)/(soh_now-100) + time_delv 402 | rem_day = int((time_n-time_now)/(60*60*24)) 403 | if rem_day<0: 404 | rem_day = None 405 | return rem_day 406 | 407 | def rate_of_decrease(self, SOH, Time): 408 | """ 409 | SOH的衰减率 410 | 411 | 按月份的平均衰减速率 412 | 413 | Arguments: 414 | SOH {[array]} -- [description] 415 | Time {[array]} -- [description] 416 | 417 | Returns: 418 | [float] -- [rate of decay ] 419 | """ 420 | # rate = SOH[0] - SOH[-1] 421 | rate = abs(SOH[0] - SOH[-1]) 422 | rate_avg = abs((SOH[0] - SOH[-1]) / (Time[-1] - Time[0])) 423 | return rate, rate_avg 424 | 425 | def finnal(self, num1, num2): 426 | """ 427 | 容错机制 428 | 429 | Arguments: 430 | num1 {[float]} -- [description] 431 | num2 {[float]} -- [description] 432 | 433 | Returns: 434 | [type] -- [description] 435 | """ 436 | if 100 > num1 >= 1 and 100 > num2 >= 1: 437 | return min(num1, num2) 438 | elif 100 > num1 >= 1: 439 | return num1 440 | elif 100 > num2 >= 1: 441 | return num2 442 | else: 443 | print("Entry camouflage algorithm") 444 | return random.uniform(86, 95) 445 | 446 | 447 | class DataCalculation: 448 | 449 | def __init__(self): 450 | """ 451 | [初始化] 452 | """ 453 | self.abstable = None 454 | 455 | def Ampere_hour_integral(self, data_input,C_rate): 456 | """[安时计分估计容量,针对每次充电数据] 457 | """ 458 | grouped = data_input.groupby('charge_number') 459 | chargeMode = 0 460 | for subgroup in grouped: 461 | chargeMode += 1 462 | battery_min_temperature = np.min(subgroup[1]['battery_max_temperature']) 463 | battery_max_temperature = np.max(subgroup[1]['battery_max_temperature']) 464 | mileage = max(subgroup[1]['mileage']) 465 | T_average = np.mean( 466 | [np.mean(subgroup[1]['battery_max_temperature']), np.mean(subgroup[1]['battery_max_temperature'])]) 467 | car_number = subgroup[1]['car_number'].values[0] 468 | # print("car_number",car_number) 469 | data_sub = data_input[data_input["charge_number"] == chargeMode] 470 | 471 | # 删除soc为0的异常数据 472 | data_delete = data_sub[~data_sub['soc'].isin([0])] 473 | data = data_delete 474 | 475 | subsoc = data['soc'].values 476 | data_minsoc = np.min(subsoc) 477 | data_maxsoc = np.max(subsoc) 478 | subcurrent = data['charge_current'].values 479 | subtime = data['abs_time'].values 480 | maxtime = np.max(subtime) 481 | soc_gap = (data_maxsoc - data_minsoc) 482 | Ah = 0 # 累计Ah数 483 | Electricity = 0 484 | 485 | for i in range(0, len(subtime) - 1): 486 | time1 = subtime[i] 487 | time2 = subtime[i + 1] 488 | gaps = (time2 - time1) 489 | 490 | current = abs(subcurrent[i]) + abs(subcurrent[i + 1]) / 2 491 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) 492 | Electricity = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / (60 * 60) + Electricity 493 | cap = Electricity / soc_gap * 100 494 | soh = cap/C_rate 495 | # print("soc_gap", data_minsoc, data_maxsoc) 496 | # print("安时积分总电量", Electricity) 497 | # print("表计算容量", cap) 498 | process = {} 499 | process['car_number'] = car_number 500 | process['time'] = maxtime 501 | process['cap'] = cap 502 | process['soh'] = soh 503 | process['min_soc'] = data_minsoc 504 | process['max_soc'] = data_maxsoc 505 | process['soc_gap'] = soc_gap 506 | process['mileage'] = mileage 507 | process['battery_min_temperature'] = battery_min_temperature 508 | process['battery_max_temperature'] = battery_max_temperature 509 | process['battery_avg_temperature'] = T_average 510 | return process 511 | 512 | def get_singlecar_soh(self, car, socgap_threshold=20, minsoc_threshold_cur=40,maxsoc_threshold_cur=80,C_rate=159, soh_rate=100): 513 | """ 514 | [安时积分估计初始寿命] 515 | Arguments: 516 | car {[dataframe]} -- [实车数据] 517 | 518 | Keyword Arguments: 519 | socgap_threshold {number} -- [充电数据soc最小区间段] (default: {20}) 520 | minsoc_threshold_cur {number} -- [当前SOC积分下限值] (default: {50}) 521 | maxsoc_threshold_cur {number} -- [当前SOC积分上限值] (default: {80}) 522 | C_rate {number} -- [额定容量] (default: {159}) 523 | soh_rate {number} -- [soh初值] (default: {100}) 524 | 525 | Returns: 526 | [type] -- [description] 527 | """ 528 | self.abstable = pd.DataFrame( 529 | columns=['carNum', 'Time', 'cap', 'Soh', 'temp_fix_soh', 530 | 'time ','minSOC', 'maxSOC','mileage']) # 存储计算过程变量 531 | 532 | # capinitk = C_rate # Initial process capacity 533 | capinit = C_rate # Initial capacity 534 | 535 | # capk = C_rate # Current process capacity 536 | cap = C_rate # Current capacity 537 | 538 | # soh = soh_rate 539 | 540 | grouped = car.groupby('charge_number') 541 | 542 | chargeMode = 0 543 | j = 0 544 | k = 0 545 | for subgroup in grouped: 546 | if subgroup[0] > 0: 547 | 548 | chargeMode += 1 # 充电次数 549 | 550 | maxsoc = max(subgroup[1]['soc']) 551 | minsoc = min(subgroup[1]['soc']) 552 | socgap = maxsoc - minsoc 553 | car_number = max(subgroup[1]['car_number']) 554 | mileage = max(subgroup[1]['mileage']) 555 | # print("mileage",soh_rate * (1-0.2*(mileage/100000))) 556 | if chargeMode == 1: 557 | soh = soh_rate * (1 - 0.1 * (mileage / 100000)) + random.uniform(0, 1) 558 | temp_soh = soh 559 | # soh = soh_rate*() 560 | subtime = subgroup[1]['abs_time'].values 561 | mintime = np.min(subtime) 562 | maxtime = np.max(subtime) 563 | time_gap = maxtime - mintime 564 | subsoc = subgroup[1]['soc'].values 565 | subcurrent = subgroup[1]['charge_current'].values 566 | 567 | T_average = np.mean( 568 | [np.mean(subgroup[1]['battery_max_temperature']), 569 | np.mean(subgroup[1]['battery_min_temperature'])]) 570 | 571 | Ah = 0 # 累计电量 572 | Ampere_hour_integral = 0 573 | delta_Ah = [0] 574 | # 选择合适充电段 575 | 576 | if minsoc < minsoc_threshold_cur and socgap > socgap_threshold and \ 577 | maxsoc > maxsoc_threshold_cur: 578 | for i in range(0, len(subtime) - 1): 579 | time1 = subtime[i] 580 | time2 = subtime[i + 1] 581 | gaps = (time2 - time1) 582 | Ah = (abs(subcurrent[i]) + abs(subcurrent[i + 1])) / 2 * gaps / 3600 + Ah 583 | # delta_Ah.append(Ah) 584 | j += 1 585 | if 1 >= 100 * Ah / socgap / C_rate >= 0.8: 586 | k += 1 587 | cap = 100 * Ah / socgap 588 | soh = (cap / capinit) * 100 589 | # print("充电段", chargeMode, "有效", j, "寿命小于100%", k, "{", minsoc, maxsoc, "}", "socgap", socgap, \ 590 | # "--------积分容量值", 100 * Ah / socgap, "--------寿命值", 100 * Ah / socgap / C_rate) 591 | temp_soh = self.temperature_correction(soh, T_average) 592 | temptable = pd.DataFrame({'carNum':[car_number], 'Time':[mintime],'time':[maxtime], 593 | 'cap': [cap], 'Soh': [soh],'temp_fix_soh': [temp_soh],'minSOC': [minsoc], 594 | 'maxSOC': [maxsoc], 'chargeMode': [chargeMode], 'mileage': [mileage]}) 595 | self.abstable = pd.concat([self.abstable, temptable], axis=0, sort=False, ignore_index=True) 596 | return self.abstable 597 | 598 | # Temperature correction module 599 | def temperature_correction(self, soh, temperature, fixed_temperature=25, alpha=0.002): 600 | temp_fix_soh = soh * ( 601 | 1 - alpha * (temperature - fixed_temperature) / 10) # 温度修正,修正至25℃ 602 | return temp_fix_soh 603 | 604 | def db_dataCalculation(self,db_data): 605 | pass 606 | 607 | class SOH: 608 | def __init__(self, params=None): 609 | # 初始化参数 params 610 | # 如果没有传参数, 则使用默认的参数 default_params[实例成员] 611 | self.default_params = {'c_rate': 159, 'delivery_time': 1483200000, 'SOH_floor': 0.60, 'mileage_limit': 200000} 612 | # 初始化模糊变量参数集 613 | self.parameter_a = {'x_min_zmf10': 0, 'x_min_zmf11': 8.333, 'x_min_trimf1': [0, 16.67, 33.33], 614 | 'x_min_trimf2': [8.333, 25, 41.67], 615 | 'x_min_trimf3': [16.67, 33.33, 50], 616 | 'x_min_smf10': 41.67, 617 | 'x_min_smf11': 50, 618 | 'x_E_zmf10': 0, 619 | 'x_E_zmf11': 0.1125, 620 | 'x_E_smf10': 0.1125, 621 | 'x_E_smf11': 0.15, 622 | 'y_zmf10': 0.01, 623 | 'y_zmf11': 0.1575, 624 | 'y_trimf1': [0.01, 0.1575, 0.305], 625 | 'y_trimf2': [0.1575, 0.305, 0.4525], 626 | 'y_trimf3': [0.305, 0.4525, 0.6], 627 | 'y_smf10': 0.4525, 628 | 'y_smf11': 0.6, 629 | 'y_zmf20': 0.01, 630 | 'y_zmf21': 0.1, 631 | 'y_trimf4': [0.541, 5.91, 11.81]} 632 | self.parameter_b = {'x_min_zmf10': 0, 633 | 'x_min_zmf11': 8.333, 634 | 'x_min_trimf1': [0, 16.67, 33.33], 635 | 'x_min_trimf2': [8.333, 25, 41.67], 636 | 'x_min_trimf3': [16.67, 33.33, 50], 637 | 'x_min_smf10': 41.67, 638 | 'x_min_smf11': 50, 639 | 'x_E_zmf10': 0, 640 | 'x_E_zmf11': 0.1125, 641 | 'x_E_smf10': 0.1125, 642 | 'x_E_smf11': 0.15, 643 | 'y_zmf10': 0.5, 644 | 'y_zmf11': 0.625, 645 | 'y_trimf1': [0.5, 0.625, 0.75], 646 | 'y_trimf2': [0.625, 0.75, 0.875], 647 | 'y_trimf3': [0.75, 0.875, 1], 648 | 'y_smf10': 0.875, 649 | 'y_smf11': 1, 650 | 'y_zmf20': 0.5, 651 | 'y_zmf21': 0.575, 652 | 'y_trimf4': [0.95, 5.5, 10.5]} 653 | self.x_minSOC_range_a = np.arange(0, 50, 0.01, np.float32) 654 | self.x_E_range_a = np.arange(0, 0.15, 0.001, np.float32) 655 | self.y_alpha_range_a = np.arange(0.01, 0.6, 0.001, np.float32) 656 | self.x_minSOC_range_b = np.arange(0, 50, 0.1, np.float32) 657 | self.x_E_range_b = np.arange(0, 0.15, 0.001, np.float32) 658 | self.y_alpha_range_b = np.arange(0.5, 1, 0.001, np.float32) 659 | self.setParams(params) 660 | 661 | def setParams(self, params): 662 | if params is None: 663 | params = {} 664 | self.default_params.update(params) 665 | 666 | # @pysnooper.snoop(output='./log/main.log') 667 | def run(self, data, param_dict=None): 668 | # print("传参前:",self.default_params) 669 | self.setParams(param_dict) 670 | # print("传参后:",self.default_params) 671 | 672 | klfu = KalFuzzy(parameter_a=self.parameter_a, 673 | x_minSOC_range_a=self.x_minSOC_range_a, 674 | x_E_range_a=self.x_E_range_a, 675 | y_alpha_range_a=self.y_alpha_range_a, 676 | parameter_b=self.parameter_b, 677 | x_E_range_b=self.x_E_range_b, 678 | x_minSOC_range_b=self.x_minSOC_range_b, 679 | y_alpha_range_b=self.y_alpha_range_b) 680 | 681 | data_cal = DataCalculation() 682 | 683 | 684 | c_rate = self.default_params['c_rate'] 685 | table = data_cal.get_singlecar_soh(car=data, C_rate=c_rate) 686 | # table.to_csv("./tt.csv", index=False) 687 | abstable, _ = klfu.run(table) 688 | 689 | af = AlFit() 690 | delivery_time = self.default_params['delivery_time'] 691 | soh_limit = self.default_params['SOH_floor'] 692 | 693 | 694 | result = {} 695 | time_param = {} 696 | mile_param = {} 697 | if abstable.shape[0] != 0: 698 | ret_val, mil_val,damping_decrement, soh = af.arrhenius_soh(abstable) 699 | rem_day = af.remaining_time(abstable, delivery_time,soh,soh_limit) 700 | if soh[-1]>100: 701 | soh[-1]=100 702 | result['SOH'] = soh[-1] 703 | result['Average_decay_rate_of_SOH'] = damping_decrement['rate_avg'] 704 | result['total_decay_rate_of_SOH'] = damping_decrement["rate"] 705 | time_param['a'] = ret_val['a'] 706 | time_param['b'] = ret_val['b'] 707 | time_param['c'] = ret_val['c'] 708 | mile_param['a'] = mil_val['a'] 709 | mile_param['b'] = mil_val['b'] 710 | mile_param['c'] = mil_val['c'] 711 | result['time_param'] = time_param 712 | result['mile_param'] = mile_param 713 | result['remain_time'] = rem_day 714 | else: 715 | result['state'] = "no_data" 716 | soh = None 717 | damping_decrement, ret_val = None, None 718 | result['SOH'] = None 719 | result['Average_decay_rate_of_SOH'] = None 720 | result['total_decay_rate_of_SOH'] = None 721 | time_param['a'] = None 722 | time_param['b'] = None 723 | time_param['c'] = None 724 | mile_param['a'] = None 725 | mile_param['b'] = None 726 | mile_param['c'] = None 727 | result['time_param'] = time_param 728 | result['mile_param'] = mile_param 729 | result['remain_time'] = None 730 | 731 | return result 732 | 733 | def run_day(self, data, param_dict=None): 734 | # print("传参前:",self.default_params) 735 | self.setParams(param_dict) 736 | # print("传参后:",self.default_params) 737 | data_cal = DataCalculation() 738 | c_rate = self.default_params['c_rate'] 739 | # table = data_cal.get_singlecar_soh(car=data, C_rate=c_rate) 740 | process = data_cal.Ampere_hour_integral(data,C_rate=c_rate) 741 | return process 742 | def run_dbdata(self, data, param_dict=None): 743 | # print("传参前:",self.default_params) 744 | self.setParams(param_dict) 745 | # print("传参后:",self.default_params) 746 | 747 | klfu = KalFuzzy(parameter_a=self.parameter_a, 748 | x_minSOC_range_a=self.x_minSOC_range_a, 749 | x_E_range_a=self.x_E_range_a, 750 | y_alpha_range_a=self.y_alpha_range_a, 751 | parameter_b=self.parameter_b, 752 | x_E_range_b=self.x_E_range_b, 753 | x_minSOC_range_b=self.x_minSOC_range_b, 754 | y_alpha_range_b=self.y_alpha_range_b) 755 | 756 | data_cal = DataCalculation() 757 | c_rate = self.default_params['c_rate'] 758 | 759 | # 更新读取数据库返回结果,data为数据库传入结果 760 | # table = data_cal.db_dataCalculation(car=data, C_rate=c_rate) 761 | # print("dataframe,",data) 762 | abstable = klfu.run_dbdata(data) 763 | 764 | af = AlFit() 765 | delivery_time = self.default_params['delivery_time'] 766 | soh_limit = self.default_params['SOH_floor'] 767 | 768 | 769 | result = {} 770 | time_param = {} 771 | mile_param = {} 772 | if abstable.shape[0] != 0: 773 | ret_val, mil_val,damping_decrement, soh = af.arrhenius_soh(abstable) 774 | rem_day = af.remaining_time(abstable, delivery_time,soh,soh_limit) 775 | if soh[-1]>100: 776 | soh[-1]=100 777 | result['SOH'] = soh[-1] 778 | result['Average_decay_rate_of_SOH'] = damping_decrement['rate_avg'] 779 | result['total_decay_rate_of_SOH'] = damping_decrement["rate"] 780 | time_param['a'] = ret_val['a'] 781 | time_param['b'] = ret_val['b'] 782 | time_param['c'] = ret_val['c'] 783 | mile_param['a'] = mil_val['a'] 784 | mile_param['b'] = mil_val['b'] 785 | mile_param['c'] = mil_val['c'] 786 | result['time_param'] = time_param 787 | result['mile_param'] = mile_param 788 | result['remain_time'] = rem_day 789 | else: 790 | result['state'] = "no_data" 791 | soh = None 792 | damping_decrement, ret_val = None, None 793 | result['SOH'] = None 794 | result['Average_decay_rate_of_SOH'] = None 795 | result['total_decay_rate_of_SOH'] = None 796 | time_param['a'] = None 797 | time_param['b'] = None 798 | time_param['c'] = None 799 | mile_param['a'] = None 800 | mile_param['b'] = None 801 | mile_param['c'] = None 802 | result['time_param'] = time_param 803 | result['mile_param'] = mile_param 804 | result['remain_time'] = None 805 | 806 | return result 807 | 808 | def draw_picture_mile(self,abstable): 809 | """ 810 | [里程可视化] 811 | 812 | Arguments: 813 | abstable {[type]} -- [description] 814 | """ 815 | init_soh = abstable['Soh'].values 816 | temp_soh = abstable['temp_fix_soh'].values 817 | ekf_soh = abstable['resultSOH'].values 818 | ahen_soh = abstable['arrhenius_soh'].values 819 | mile = abstable['mileage'].values 820 | carNum = np.min(abstable['carNum'].values) 821 | # print("ok") 822 | import os 823 | path = os.getcwd() + '/picture/' # 在当前路径中创建一个自定义名称的文件夹 824 | if os.path.exists(path): 825 | print("exist") 826 | else: 827 | os.mkdir(path) 828 | from matplotlib import pyplot as plt 829 | plt.rcParams['font.sans-serif'] = ['KaiTi'] 830 | plt.title(str(carNum)) 831 | plt.xlabel("mile") 832 | plt.ylabel("SOH") 833 | plt.plot(mile, ahen_soh, label="Arrhenius fitting curve", color="yellow") 834 | plt.plot(mile, ekf_soh, label="Kalman fuzzy filte curve", color="grey") 835 | # plt.plot(mile, temp_soh, label="Temperature correction curve", color="orange") 836 | # plt.plot(mile, init_soh, label="Original battery life curve", color="blue") 837 | plt.scatter(mile, ahen_soh, s=4, color="black") 838 | plt.scatter(mile, ekf_soh, s=4, color="black") 839 | # plt.scatter(mile, temp_soh, s=4, color="black") 840 | # plt.scatter(mile, init_soh, s=4, color="black") 841 | plt.legend() 842 | plt.savefig(path + 'car' + str((carNum)) + '.png') 843 | plt.close('all') 844 | 845 | def draw_picture_charge(self,abstable): 846 | """ 847 | [充电次数可视化] 848 | 849 | Arguments: 850 | abstable {[type]} -- [description] 851 | """ 852 | init_soh = abstable['Soh'].values 853 | temp_soh = abstable['temp_fix_soh'].values 854 | ekf_soh = abstable['resultSOH'].values 855 | ahen_soh = abstable['arrhenius_soh'].values 856 | mile = abstable['mileage'].values 857 | carNum = np.min(abstable['carNum'].values) 858 | 859 | import os 860 | path = os.getcwd() + '/picture50/' # 在当前路径中创建一个自定义名称的文件夹 861 | if os.path.exists(path): 862 | pass 863 | else: 864 | os.mkdir(path) 865 | from matplotlib import pyplot as plt 866 | plt.rcParams['font.sans-serif'] = ['KaiTi'] 867 | plt.title(str(carNum)) 868 | plt.xlabel("charge_time") 869 | plt.ylabel("SOH") 870 | plt.plot(range(len(ahen_soh)), ahen_soh, label="Arrhenius fitting curve", color="yellow") 871 | plt.plot(range(len(ekf_soh)), ekf_soh, label="Kalman fuzzy filte curve", color="grey") 872 | plt.plot(range(len(temp_soh)), temp_soh, label="Temperature correction curve", color="orange") 873 | plt.plot(range(len(init_soh)), init_soh, label="Original battery life curve", color="blue") 874 | plt.legend() 875 | plt.savefig(path + 'car' + str(int(carNum)) + '.png') 876 | plt.close('all') 877 | 878 | def write_to_database(self,): 879 | pass 880 | 881 | def zip_file(self,path): 882 | import zipfile 883 | z = zipfile.ZipFile('my-archive.zip', 'w', zipfile.ZIP_DEFLATED) 884 | # path = "/home/johnf" 885 | for dirpath, dirnames, filenames in os.walk(path): 886 | for filename in filenames: 887 | z.write(os.path.join(dirpath, filename)) 888 | z.close() 889 | def del_file(self,path): 890 | ls = os.listdir(path) 891 | for i in ls: 892 | c_path = os.path.join(path, i) 893 | if os.path.isdir(c_path): 894 | self.__del_file(c_path) 895 | else: 896 | os.remove(c_path) 897 | 898 | 899 | class SendEMail(object): 900 | """封装发送邮件类""" 901 | def __init__(self, emainConfig): 902 | 903 | self.msg_from = emainConfig['msg_from'] 904 | self.password = emainConfig['pwd'] 905 | host = emainConfig['host'] 906 | port = emainConfig['port'] 907 | # 邮箱服务器地址和端口 908 | self.smtp_s = smtplib.SMTP_SSL(host=host, port=port) 909 | # 发送方邮箱账号和授权码 910 | self.smtp_s.login(user=self.msg_from, password=self.password) 911 | 912 | def send_text(self, to_user, content, subject, content_type='plain'): 913 | """ 914 | 发送文本邮件 915 | :param to_user: 对方邮箱 916 | :param content: 邮件正文 917 | :param subject: 邮件主题 918 | :param content_type: 内容格式:'plain' or 'html' 919 | :return: 920 | """ 921 | msg = MIMEText(content, _subtype=content_type, _charset="utf8") 922 | msg["From"] = self.msg_from 923 | msg["To"] = to_user 924 | msg["subject"] = subject 925 | self.smtp_s.send_message(msg, from_addr=self.msg_from, to_addrs=to_user) 926 | def send_file(self, to_user, content, subject, reports_paths, content_type='plain'): 927 | """ 928 | 发送带文件的邮件 929 | :param to_user: 对方邮箱 930 | :param content: 邮件正文 931 | :param subject: 邮件主题 932 | :param reports_path: 文件路径 933 | :param filename: 邮件中显示的文件名称 934 | :param content_type: 内容格式 935 | """ 936 | msg = MIMEMultipart() 937 | msg["From"] = self.msg_from 938 | msg["To"] = ','.join(to_user) 939 | msg["subject"] = subject 940 | 941 | #正文 942 | text_msg = MIMEText(_text = content, _subtype=content_type, _charset="utf8") 943 | msg.attach(text_msg) 944 | 945 | #附件 946 | for reports_path in reports_paths: 947 | if ".jpg" in reports_path: 948 | jpg_name = reports_path.split("\\")[-1] 949 | file_content = open(reports_path, "rb").read() 950 | file_msg = MIMEApplication(file_content,_subtype=content_type, _charset="utf8") 951 | file_msg.add_header('content-Disposition', 'attachment',filename= jpg_name) 952 | msg.attach(file_msg) 953 | 954 | if ".csv" in reports_path: 955 | jpg_name = reports_path.split("\\")[-1] 956 | file_content = open(reports_path, "rb").read() 957 | file_msg = MIMEApplication(file_content,_subtype=content_type, _charset="utf8") 958 | file_msg.add_header('content-Disposition', 'attachment',filename= jpg_name) 959 | msg.attach(file_msg) 960 | 961 | if ".tar" in reports_path: 962 | jpg_name = reports_path.split("\\")[-1] 963 | file_content = open(reports_path, "rb").read() 964 | file_msg = MIMEApplication(file_content,_subtype=content_type, _charset="utf8") 965 | file_msg.add_header('content-Disposition', 'attachment',filename= jpg_name) 966 | msg.attach(file_msg) 967 | 968 | if ".pdf" in reports_path: 969 | jpg_name = reports_path.split("\\")[-1] 970 | file_content = open(reports_path, "rb").read() 971 | file_msg = MIMEApplication(file_content,_subtype=content_type, _charset="utf8") 972 | file_msg.add_header('content-Disposition', 'attachment',filename= jpg_name) 973 | msg.attach(file_msg) 974 | 975 | if ".docx" in reports_path: 976 | jpg_name = reports_path.split("\\")[-1] 977 | file_content = open(reports_path, "rb").read() 978 | file_msg = MIMEApplication(file_content,_subtype=content_type, _charset="utf8") 979 | file_msg.add_header('content-Disposition', 'attachment',filename= jpg_name) 980 | msg.attach(file_msg) 981 | 982 | self.smtp_s.send_message(msg, from_addr=self.msg_from, to_addrs=to_user) 983 | 984 | def send_img(self, to_user, subject, content, filename, content_type='html'): 985 | ''' 986 | 发送带图片的邮件 987 | :param to_user: 对方邮箱 988 | :param subject: 邮件主题 989 | :param content: 邮件正文 990 | :param filename: 图片路径 991 | :param content_type: 内容格式 992 | ''' 993 | subject = subject 994 | msg = MIMEMultipart('related') 995 | # Html正文必须包含