├── .gitignore ├── ICA.m ├── IDCA.m ├── MyModel.mat ├── README.md ├── VQlinspace.m ├── VQlinspace2.m ├── VQlinspace3.m ├── apply_model.m ├── apply_model2.m ├── apply_model3.m ├── auto_analysis.m ├── batch1_summary_plots.m ├── batch2_summary_plots.m ├── batch2_summary_plots2.m ├── batch3_summary_plots.m ├── batch_analysis.m ├── batch_analysis2.m ├── batch_analysis_old.m ├── batch_visualization.m ├── build_battery_features.m ├── build_battery_features95.m ├── build_battery_features98.m ├── cell_analysis.m ├── cell_analysis_batch0.m ├── columnlegend.m ├── delete_bad_csvs.m ├── distinguishable_colors.m ├── email_results.m ├── get_batch_date_from_batch_name.m ├── hline.m ├── linspecer.m ├── make_images.m ├── make_result_tables.m ├── make_summary_gifs.m ├── make_summary_images.m ├── oed_model.mat ├── oed_model_batch1.mat ├── oed_plots.py ├── python.m ├── random_color.m ├── reportgenerator.py ├── sendemail.m ├── taskscheduler.bat └── vline.m /.gitignore: -------------------------------------------------------------------------------- 1 | *.m~ 2 | *.csv 3 | *.xlsx 4 | *.png 5 | *.asv 6 | *.gif 7 | .DS_Store 8 | path.mat 9 | -------------------------------------------------------------------------------- /ICA.m: -------------------------------------------------------------------------------- 1 | function [ Capacitance, xVoltage] = ICA(Capacity,Voltage) 2 | %Performs and Tidies ICA 3 | % Detailed explanation goes here 4 | Capacitance= zeros(length(Voltage),1); 5 | %Calculate Approximate dQdV 6 | for j=2:length(Voltage) 7 | Capacitance(j)=(Capacity(j)-Capacity(j-1))/(Voltage(j)-Voltage(j-1)); 8 | end 9 | 10 | %Tidy dQdV data for all battery cycles. 11 | for j=2:length(Capacitance) 12 | if isnan(Capacitance(j)) 13 | Capacitance(j)=0; 14 | elseif Capacitance(j)<0 15 | Capacitance(j)=Capacitance(j-1); 16 | elseif abs(Capacitance(j)) >= 25 17 | Capacitance(j)=0; 18 | %Ignore CV location, or shorted battery 19 | elseif Voltage(j) >= max(Voltage)-.001 20 | Capacitance(j)=0; 21 | elseif Capacitance(j)== 0 22 | Capacitance(j)=Capacitance(j-1); 23 | elseif Voltage(j) <= 2.5 24 | Capacitance(j)=0; 25 | else 26 | Capacitance(j)=Capacitance(j); 27 | end 28 | end 29 | 30 | Capacitance=smooth(Capacitance,10);%,0.05,'rloess'); 31 | Capacitance=smooth(Capacitance,0.01,'rloess'); 32 | xVoltage=Voltage; 33 | end 34 | 35 | -------------------------------------------------------------------------------- /IDCA.m: -------------------------------------------------------------------------------- 1 | function [ Capacitance, xVoltage] = IDCA(Capacity,Voltage) 2 | %Performs incremental capacity analysis (ICA) and cleans data 3 | % IDCA = incremental differential capaciy analysis 4 | % Nick Perkins, May 2017; Updated by Peter Attia, October 2017 5 | 6 | Capacitance= zeros(1,length(Voltage)); 7 | % Calculate approximate dQdV 8 | for j=2:length(Voltage) 9 | Capacitance(j)=(Capacity(j)-Capacity(j-1))/(Voltage(j)-Voltage(j-1)); 10 | end 11 | 12 | % Clean dQdV data for all battery cycles 13 | for j=2:length(Capacitance) 14 | if isnan(Capacitance(j)) 15 | Capacitance(j)=0; 16 | elseif abs(Capacitance(j)) >= 50 17 | Capacitance(j)=0; 18 | %Ignore CV location, or shorted battery 19 | elseif Capacitance(j) > 0 20 | Capacitance(j)= Capacitance(j-1); 21 | elseif Capacitance(j)== 0 22 | Capacitance(j)=Capacitance(j-1); 23 | elseif Voltage(j) <= 2 24 | Capacitance(j)=0; 25 | else 26 | Capacitance(j)=Capacitance(j); 27 | end 28 | end 29 | 30 | %% Interpolate voltage and dQdV 31 | %Define Voltage Range. 32 | xVoltage=linspace(3.5,2.000,1000); 33 | % Create Array for Initial Voltage Values and empty dQdV 34 | interp_Voltages=3.6; 35 | first_Voltage=Voltage(1)+.0001; 36 | interp_ICA1=0; 37 | interp_ICA2=-.0001; 38 | 39 | % Add initial values 40 | continuosVoltage=horzcat(interp_Voltages,first_Voltage,transpose(Voltage)); 41 | continuosICA=horzcat(interp_ICA1,interp_ICA2,Capacitance); 42 | % Interpret 43 | VoltageCurve=continuosVoltage; 44 | ICA_Curve=continuosICA; 45 | [VoltageCurve, index] = unique(VoltageCurve); 46 | Capacitance=interp1(VoltageCurve,ICA_Curve(index),xVoltage); 47 | Capacitance=transpose(smooth(Capacitance,10)); 48 | %Capacitance=transpose(smooth(Capacitance,0.01,'rloess')); 49 | end 50 | -------------------------------------------------------------------------------- /MyModel.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chueh-ermon/BMS-autoanalysis/c82ab7704211a6aee75bd926714b82d1f95e1a0e/MyModel.mat -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BMS-autoanalysis 2 | 3 | This MATLAB repository processes cycling data, generates early predictions (if applicable), and emails reports with cycling visualizations. It runs daily from an AWS Workspace. The main script is `auto_analysis.m`. 4 | 5 | Primary author: [Peter Attia](https://github.com/petermattia) 6 | -------------------------------------------------------------------------------- /VQlinspace.m: -------------------------------------------------------------------------------- 1 | function [ Qlin, Vlin ] = VQlinspace( cycle ) 2 | %VQlinspace returns a linearly-spaced V vs Q curve 3 | % Inputs: cycle (i.e. batch(i).cycles(j)) 4 | % Outputs: Qlin, Vlin = linearly spaced Qdis vs Vdis 5 | 6 | % 1. Get the indices of all currents ~ -4 C, i.e. discharge indices. 7 | % For all policies, we discharge at 4C (= -4C) 8 | indices = find(abs(cycle.I+4) < 0.05); 9 | 10 | % 2. Extract Q_dis and V_dis: 11 | Q_dis_raw = cycle.Q(indices(1))-cycle.Q(indices); 12 | V_dis_raw = cycle.V(indices); 13 | 14 | % 3. Fit to function. Ensure data is nearly untransformed 15 | VQfit = fit(V_dis_raw,Q_dis_raw, 'smoothingspline'); 16 | 17 | % 4. Linearly interpolate 18 | n_points = 1000; % number of points to linearly interpolate between 19 | V1 = 2.0; 20 | V2 = 3.5; 21 | 22 | spacing = (V2 - V1) / n_points; 23 | Vlin = V1:spacing:V2; % voltage range for interpolation 24 | Qlin = VQfit(Vlin); 25 | 26 | end -------------------------------------------------------------------------------- /VQlinspace2.m: -------------------------------------------------------------------------------- 1 | function [ Qlin, Vlin ] = VQlinspace2( cycle ) 2 | %VQlinspace2 returns a linearly-spaced V vs Q curve 3 | % Inputs: cycle (i.e. batch(i).cycles(j)) 4 | % Outputs: Qlin, Vlin = linearly spaced Qdis vs Vdis 5 | %VQlinspace2 uses time/current to generate discharge capacity due to 6 | %discrepancy for Qdischarge. This produces a "smoother" and more 7 | %physically meaningful discharge curve 8 | % Last modified October 24, 2017 9 | 10 | %% 1. Create Vlin 11 | n_points = 1000; % number of points to linearly interpolate between 12 | V1 = 2.0; 13 | V2 = 3.5; 14 | 15 | % Old code below - use Vlin to keep consistent with IDCA (only one vector) 16 | % spacing = (V2 - V1) / (n_points - 1); 17 | % Vlin = V1:spacing:V2; % voltage range for interpolation 18 | Vlin=linspace(V2,V1,n_points); 19 | 20 | %% 2. Get the indices of all currents ~ -4 C, i.e. discharge indices. 21 | % For all policies, we discharge at 4C (= -4.4A) 22 | indices = find(abs(cycle.I+4) < 0.05); 23 | % Remove trailing data points 24 | [~, index2] = min(cycle.V(indices(1:end-2))); 25 | indices = indices(1:index2); 26 | 27 | %% 3. Extract Q_dis (from t_dis) and V_dis: 28 | V_dis_raw = cycle.V(indices); 29 | try % Q_dis_raw occasionally gives errors 30 | Q_dis_raw = -(cycle.t(indices)-cycle.t(indices(1)))./60.*cycle.I(indices).*1.1; 31 | 32 | %% 4. Fit to function. Ensure data is nearly untransformed 33 | VQfit = fit(V_dis_raw,Q_dis_raw, 'smoothingspline'); 34 | 35 | %% 5. Linearly interpolate 36 | Qlin = VQfit(Vlin); 37 | catch 38 | warning('VQlinspace2 failed') 39 | Qlin = zeros(length(Vlin),1); 40 | end 41 | 42 | end -------------------------------------------------------------------------------- /VQlinspace3.m: -------------------------------------------------------------------------------- 1 | function [ Qlin, Vlin, Tlin ] = VQlinspace3( cycle ) 2 | %VQlinspace2 returns a linearly-spaced V vs Q curve 3 | % Inputs: cycle (i.e. batch(i).cycles(j)) 4 | % Outputs: Qlin, Vlin = linearly spaced Qdis vs Vdis 5 | %VQlinspace2 uses time/current to generate discharge capacity due to 6 | %discrepancy for Qdischarge. This produces a "smoother" and more 7 | %physically meaningful discharge curve 8 | % Last modified October 31, 2017 9 | 10 | %% 1. Create Vlin 11 | n_points = 1000; % number of points to linearly interpolate between 12 | V1 = 2.0; 13 | V2 = 3.5; 14 | 15 | % Old code below - use Vlin to keep consistent with IDCA (only one vector) 16 | % spacing = (V2 - V1) / (n_points - 1); 17 | % Vlin = V1:spacing:V2; % voltage range for interpolation 18 | Vlin=linspace(V2,V1,n_points); 19 | 20 | %% 2. Get the indices of all currents ~ -4 C, i.e. discharge indices. 21 | % OLD: For all policies, we discharge at 4C (= -4.4A) 22 | Irounded = round(cycle.I); 23 | Idischarge = mode(Irounded(cycle.I<0)); 24 | indices = find(abs(cycle.I+(-Idischarge)) < 0.05); 25 | % Remove trailing data points 26 | [~, index2] = min(cycle.V(indices(1:end-2))); 27 | indices = indices(1:index2); 28 | 29 | %% 3. Extract Q_dis: 30 | V_dis_raw = cycle.V(indices); 31 | try % Q_dis_raw occasionally gives errors 32 | Q_dis_raw = -(cycle.t(indices)-cycle.t(indices(1)))./60.*cycle.I(indices).*1.1; 33 | 34 | %% 4. Fit to function. Ensure data is nearly untransformed 35 | VQfit = fit(V_dis_raw,Q_dis_raw, 'smoothingspline'); 36 | 37 | %% 5. Linearly interpolate 38 | Qlin = VQfit(Vlin); 39 | catch 40 | warning('VQlinspace2 failed - Qlin') 41 | Qlin = zeros(length(Vlin),1); 42 | end 43 | 44 | %% 6. Extract T_dis 45 | T_dis_raw = cycle.T(indices); 46 | try 47 | %% 7. Fit to function. Ensure data is nearly untransformed 48 | VTfit = fit(V_dis_raw,T_dis_raw, 'smoothingspline'); 49 | 50 | %% 8. Linearly interpolate 51 | Tlin = VTfit(Vlin); 52 | catch 53 | warning('VQlinspace2 failed - Tlin') 54 | Tlin = zeros(length(Vlin),1); 55 | end 56 | end -------------------------------------------------------------------------------- /apply_model.m: -------------------------------------------------------------------------------- 1 | function apply_model(batch, batch_name, path) 2 | % This function applies the predictions based on the model stored in 3 | % MyModel.mat 4 | % Peter Attia, Kristen Severson 5 | % Last updated May 4, 2018 6 | 7 | %% TO DO: 8 | % - if cell < 100 cycles 9 | 10 | disp('Starting apply_model'),tic 11 | 12 | % Set colormap 13 | CM = colormap('jet'); 14 | 15 | % Load the data you would like to apply the model to 16 | numBat = size(batch,2); 17 | 18 | % Load the model you would like to use 19 | load('MyModel.mat') 20 | 21 | %% Features 22 | feat = build_battery_features(batch); 23 | 24 | %% Error correction would go here 25 | 26 | %% Make predictions 27 | feat_scaled = bsxfun(@minus,feat,mu); 28 | feat_scaled = bsxfun(@rdivide,feat_scaled,sigma); 29 | feat_scaled = feat_scaled(:,feat_ind); 30 | 31 | se = zeros(numBat,1); 32 | for i = 1:numBat 33 | se(i) = t_val*sqrt(MSE + MSE*[feat_scaled(i,:),1]/des_mat*[feat_scaled(i,:),1]'); 34 | end 35 | 36 | ypred = feat_scaled*B1 + y_mu; 37 | ypred_l = 10.^(ypred - se); 38 | ypred_h = 10.^(ypred + se); 39 | ypred = 10.^ypred; 40 | 41 | figure(), hold on 42 | plot(ypred,'s') 43 | for i = 1:numBat 44 | plot(i*ones(100,1), linspace(ypred_l(i), ypred_h(i)),'k') 45 | end 46 | xlabel('Battery Index') 47 | ylabel('Cycle Life') 48 | 49 | %export the result to a csv 50 | barcode = zeros(numBat,1); 51 | channels = zeros(numBat,1); 52 | lifetimes = zeros(numBat,1); 53 | policies = cell(numBat,1); 54 | for i = 1:numBat 55 | barcode(i) = str2num(batch(i).barcode{1}(3:end)); 56 | channels(i) = str2num(batch(i).channel_id{1}); 57 | lifetimes(i) = batch(i).cycle_life; 58 | policies{i} = batch(i).policy_readable; 59 | end 60 | 61 | M = [channels, barcode, lifetimes, round(ypred), round(ypred_l), round(ypred_h)]; 62 | T = array2table(M,'VariableNames', ... 63 | {'Channel','Barcode','Lifetime','Prediction','CI_Lo','CI_Hi'}); 64 | T.Policy = policies; 65 | T = sortrows(T,7); 66 | filename = [date '_' batch_name '_predictions.csv']; 67 | cd(path.result_tables) 68 | writetable(T, filename); 69 | cd(path.code) 70 | 71 | %% Make contour plot 72 | % Note that this code assumes 2-step, 10 minute policies 73 | 74 | p1 = zeros(numBat,1); 75 | p2 = zeros(numBat,1); 76 | skip_ind = []; 77 | 78 | for i = 1:numBat 79 | try 80 | k = strfind(batch(i).policy_readable,'C'); 81 | j = strfind(batch(i).policy_readable,'-'); 82 | p1(i) = str2num(batch(i).policy_readable(1:k(1)-1)) - 0.1 + 0.2*rand; 83 | if isempty(j+1:length(batch(i).policy_readable) - 1) 84 | p2(i) = str2num(batch(i).policy_readable(j+1)) -0.1 + 0.2*rand; 85 | else 86 | p2(i) = str2num(batch(i).policy_readable(j(1)+1:k(2)-1)) -0.1 + 0.2*rand; 87 | end 88 | catch 89 | skip_ind = [skip_ind, i]; 90 | end 91 | 92 | 93 | end 94 | 95 | %for contour lines 96 | time = 10; % target time of policies, in minutes 97 | CC1 = 2.9:0.01:6.1; 98 | CC2 = 2.9:0.01:6.1; 99 | [X,Y] = meshgrid(CC1,CC2); 100 | Q1 = (100).*(time - ((60*0.8)./Y))./((60./X)-(60./Y)); 101 | Q1(Q1<0) = NaN; 102 | Q1(Q1>80) = NaN; 103 | Q1_values = 5:10:75; 104 | 105 | plot_ind = 1:numBat; 106 | plot_ind(skip_ind) = []; 107 | max_Q = max(ypred(plot_ind)) + 1; 108 | min_Q = min(ypred(plot_ind)) - 1; 109 | 110 | figure('units','normalized','outerposition',[0 0 1 1]); 111 | hold on, box on, axis square 112 | for i = 1:numBat 113 | if i == 1 114 | colormap 'jet' 115 | contour(X,Y,Q1,Q1_values,'k','LineWidth',2,'ShowText','on') 116 | axis([2.9 6.1 2.9 6.1]) 117 | end 118 | if sum(i == skip_ind) 119 | else 120 | color_ind = ceil((ypred(i) - min_Q)./(max_Q - min_Q)*64); 121 | scatter(p1(i),p2(i),'ro','filled','CData',CM(color_ind,:),'SizeData',170) 122 | end 123 | 124 | end 125 | xlabel('C1','FontSize',20), ylabel('C2','FontSize',20) 126 | title('Predictions (points jittered)') 127 | h = colorbar; 128 | title(h, {'Predicted','cycle life'},'FontWeight','bold') 129 | tl = linspace(round(min_Q),round(max_Q),71); 130 | h.TickLabels = tl(10:10:70); 131 | set(gca,'fontsize',18) 132 | 133 | cd(path.images), cd(batch_name) 134 | print('summary6_predictions', '-dpng') 135 | savefig(gcf,'summary6_predictions.fig') 136 | cd(path.code) 137 | 138 | close all 139 | 140 | disp('Completed apply_model'),toc 141 | 142 | end -------------------------------------------------------------------------------- /apply_model2.m: -------------------------------------------------------------------------------- 1 | function apply_model2(batch, batch_name, path) 2 | % This function applies the predictions based on the model stored in 3 | % MyModel.mat 4 | % Peter Attia, Kristen Severson 5 | % Last updated June 28, 2018 6 | 7 | %% TO DO: 8 | % Update model/build_battery_features 9 | 10 | disp('Starting apply_model'),tic 11 | 12 | %% Initialize 13 | 14 | % Load the data you would like to apply the model to 15 | numBat = size(batch,2); 16 | 17 | % Load the model you would like to use 18 | if strcmp(batch_name,'oed1') 19 | load('oed_model_batch1.mat') 20 | else 21 | load('oed_model.mat') 22 | end 23 | 24 | %% Remove cells that did not reach 100 cycles 25 | idx_running_cells = zeros(numBat,1); 26 | if strcmp(batch_name,'oed1') 27 | cycle_cutoff = 98; 28 | else 29 | cycle_cutoff = 100; 30 | end 31 | for k = 1:numBat 32 | if length(batch(k).cycles) < cycle_cutoff 33 | idx_running_cells(k) = 1; 34 | end 35 | end 36 | batch_running = batch(~idx_running_cells); % batch2 only contains running cells 37 | 38 | %% Initialize 39 | ypred = NaN(numBat,1); 40 | ypred_l = NaN(numBat,1); 41 | ypred_h = NaN(numBat,1); 42 | 43 | if ~isempty(batch_running) 44 | %% Build features 45 | if strcmp(batch_name,'oed1') 46 | feat = build_battery_features98(batch_running); 47 | elseif strcmp(batch_name,'oed4') 48 | feat = build_battery_features95(batch_running); 49 | else 50 | feat = build_battery_features(batch_running); 51 | end 52 | 53 | %% Make predictions 54 | feat_scaled = bsxfun(@minus,feat,mu); 55 | feat_scaled = bsxfun(@rdivide,feat_scaled,sigma); 56 | feat_scaled = feat_scaled(:,feat_ind); 57 | 58 | se = zeros(numBat,1); 59 | for i = 1:numBat 60 | se(i) = t_val*sqrt(MSE + MSE*[feat_scaled(i,:),1]/des_mat*[feat_scaled(i,:),1]'); 61 | end 62 | 63 | ypred2 = feat_scaled*B1 + y_mu; 64 | ypred_l2 = 10.^(ypred2 - se); 65 | ypred_h2 = 10.^(ypred2 + se); 66 | ypred2 = 10.^ypred2; 67 | 68 | % Detect outliers 69 | for k = 1:length(feat_scaled) 70 | %if sum(abs(feat_scaled(k,:)) > 4) 71 | if (ypred_h2(k) - ypred_l2(k)) > 2000 72 | ypred2(k) = -1; 73 | end 74 | end 75 | 76 | % Combine pred arrays for cells that have completed with those that 77 | % haven't 78 | ypred(idx_running_cells==0) = ypred2; 79 | ypred_l(idx_running_cells==0) = ypred_l2; 80 | ypred_h(idx_running_cells==0) = ypred_h2; 81 | 82 | figure(), hold on 83 | CM = colormap('jet'); % Set colormap 84 | plot(ypred,'s') 85 | for i = 1:numBat 86 | plot(i*ones(100,1), linspace(ypred_l(i), ypred_h(i)),'k') 87 | end 88 | xlabel('Battery Index') 89 | ylabel('Cycle Life') 90 | end 91 | 92 | %% Export the result to a csv 93 | % Preinitialization 94 | barcode = zeros(numBat,1); 95 | channels = zeros(numBat,1); 96 | lifetimes = zeros(numBat,1); 97 | policies = cell(numBat,1); 98 | 99 | for i = 1:numBat 100 | try % Missing barcode 101 | barcode(i) = str2num(batch(i).barcode{1}(3:end)); 102 | end 103 | channels(i) = str2num(batch(i).channel_id{1}); 104 | lifetimes(i) = batch(i).cycle_life; 105 | policies{i} = batch(i).policy_readable; 106 | end 107 | 108 | if contains(batch_name,'oed') 109 | % preinitialization 110 | C1 = zeros(numBat,1); 111 | C2 = zeros(numBat,1); 112 | C3 = zeros(numBat,1); 113 | C4 = zeros(numBat,1); 114 | 115 | for i = 1:numBat 116 | idx = strfind(batch(i).policy_readable,'-'); 117 | C1(i) = str2double(batch(i).policy_readable(1:idx(1)-1)); 118 | C2(i) = str2double(batch(i).policy_readable(idx(1)+1:idx(2)-1)); 119 | C3(i) = str2double(batch(i).policy_readable(idx(2)+1:idx(3)-1)); 120 | C4(i) = str2double(batch(i).policy_readable(idx(3)+1:end)); 121 | end 122 | 123 | M = [C1, C2, C3, C4, round(ypred), round(ypred_l), round(ypred_h), ... 124 | lifetimes, channels, barcode]; 125 | T = array2table(M,'VariableNames', {'C1','C2','C3','C4'... 126 | 'Prediction','CI_Lo','CI_Hi','Lifetime','Channel','Barcode'}); 127 | else 128 | % Not OED 129 | M = [channels, barcode, lifetimes, round(ypred), round(ypred_l), round(ypred_h)]; 130 | T = array2table(M,'VariableNames', ... 131 | {'Channel','Barcode','Lifetime','Prediction','CI_Lo','CI_Hi'}); 132 | T.Policy = policies; 133 | T = sortrows(T,7); 134 | end 135 | filename = [date '_' batch_name '_predictions.csv']; 136 | cd(path.result_tables) 137 | writetable(T, filename); 138 | cd(path.code) 139 | 140 | disp('Completed apply_model'),toc 141 | 142 | end -------------------------------------------------------------------------------- /apply_model3.m: -------------------------------------------------------------------------------- 1 | function apply_model3(batch, batch_name, path) 2 | % This function applies the predictions based on the model stored in 3 | % MyModel.mat 4 | % Peter Attia, Kristen Severson 5 | % Last updated August 28, 2018 6 | 7 | %% TO DO: 8 | % Update model/build_battery_features 9 | 10 | disp('Starting apply_model'),tic 11 | 12 | %% Initialize 13 | 14 | % Load the data you would like to apply the model to 15 | numBat = size(batch,2); 16 | 17 | % Load the model 18 | load('oed_model.mat') 19 | 20 | %% Remove cells that did not reach 100 cycles 21 | idx_running_cells = zeros(numBat,1); 22 | cycle_cutoff = 100; 23 | 24 | for k = 1:numBat 25 | if length(batch(k).cycles) < cycle_cutoff 26 | idx_running_cells(k) = 1; 27 | end 28 | end 29 | batch_running = batch(~idx_running_cells); % batch2 only contains running cells 30 | 31 | %% Initialize 32 | ypred = NaN(numBat,1); 33 | ypred_l = NaN(numBat,1); 34 | ypred_h = NaN(numBat,1); 35 | 36 | if ~isempty(batch_running) 37 | %% Build features 38 | feat = build_battery_features(batch_running); 39 | 40 | %% Make predictions 41 | feat_scaled = bsxfun(@minus,feat,mu); 42 | feat_scaled = bsxfun(@rdivide,feat_scaled,sigma); 43 | feat_scaled = feat_scaled(:,feat_ind); 44 | 45 | se = zeros(numBat,1); 46 | for i = 1:numBat 47 | se(i) = t_val*sqrt(MSE + MSE*[feat_scaled(i,:),1]/des_mat*[feat_scaled(i,:),1]'); 48 | end 49 | 50 | ypred2 = feat_scaled*B1 + y_mu; 51 | ypred_l2 = 10.^(ypred2 - se); 52 | ypred_h2 = 10.^(ypred2 + se); 53 | ypred2 = 10.^ypred2; 54 | 55 | % Detect outliers 56 | for k = 1:length(ypred2) 57 | %if sum(abs(feat_scaled(k,:)) > 4) 58 | if (ypred_h2(k) - ypred_l2(k)) > 2000 59 | ypred2(k) = -1; 60 | end 61 | end 62 | 63 | % Combine pred arrays for cells that have completed with those that 64 | % haven't 65 | ypred(idx_running_cells==0) = ypred2; 66 | ypred_l(idx_running_cells==0) = ypred_l2; 67 | ypred_h(idx_running_cells==0) = ypred_h2; 68 | 69 | % figure(), hold on 70 | % CM = colormap('jet'); % Set colormap 71 | % plot(ypred,'s') 72 | % for i = 1:numBat 73 | % plot(i*ones(100,1), linspace(ypred_l(i), ypred_h(i)),'k') 74 | % end 75 | % xlabel('Battery Index') 76 | % ylabel('Cycle Life') 77 | end 78 | 79 | %% Export the result to a csv 80 | % Preinitialization 81 | barcode = zeros(numBat,1); 82 | channels = zeros(numBat,1); 83 | lifetimes = zeros(numBat,1); 84 | policies = cell(numBat,1); 85 | 86 | for i = 1:numBat 87 | try % Missing barcode 88 | barcode(i) = str2num(batch(i).barcode{1}(3:end)); 89 | end 90 | channels(i) = str2num(batch(i).channel_id{1}); 91 | lifetimes(i) = batch(i).cycle_life; 92 | policies{i} = batch(i).policy_readable; 93 | end 94 | 95 | if contains(batch_name,'oed') 96 | % preinitialization 97 | C1 = zeros(numBat,1); 98 | C2 = zeros(numBat,1); 99 | C3 = zeros(numBat,1); 100 | C4 = zeros(numBat,1); 101 | 102 | for i = 1:numBat 103 | idx = strfind(batch(i).policy_readable,'-'); 104 | C1(i) = str2double(batch(i).policy_readable(1:idx(1)-1)); 105 | C2(i) = str2double(batch(i).policy_readable(idx(1)+1:idx(2)-1)); 106 | C3(i) = str2double(batch(i).policy_readable(idx(2)+1:idx(3)-1)); 107 | C4(i) = str2double(batch(i).policy_readable(idx(3)+1:end)); 108 | end 109 | 110 | M = [C1, C2, C3, C4, round(ypred), round(ypred_l), round(ypred_h), ... 111 | lifetimes, channels, barcode]; 112 | T = array2table(M,'VariableNames', {'C1','C2','C3','C4'... 113 | 'Prediction','CI_Lo','CI_Hi','Lifetime','Channel','Barcode'}); 114 | else 115 | % Not OED 116 | M = [channels, barcode, lifetimes, round(ypred), round(ypred_l), round(ypred_h)]; 117 | T = array2table(M,'VariableNames', ... 118 | {'Channel','Barcode','Lifetime','Prediction','CI_Lo','CI_Hi'}); 119 | T.Policy = policies; 120 | T = sortrows(T,7); 121 | end 122 | filename = [date '_' batch_name '_predictions.csv']; 123 | cd(path.result_tables) 124 | writetable(T, filename); 125 | cd(path.code) 126 | 127 | disp('Completed apply_model'),toc 128 | 129 | end -------------------------------------------------------------------------------- /auto_analysis.m: -------------------------------------------------------------------------------- 1 | %% auto_analysis.m... 2 | % - Pulls latest CSVs from AWS S3 bucket to this Workspace 3 | % - Runs batch_analysis (converts to struct and saves data to .mat) 4 | % - Runs apply_model (generates predictions) 5 | % - Runs makes images (cell summary info) 6 | % - Runs make_result_tables and make_summary_images (batch summary info) 7 | % - Runs reportgenerator.py (creates PDF report) 8 | % - Emails results 9 | % - Syncs results to AWS 10 | % Peter Attia, Nick Perkins, Zi Yang, Michael Chen, Norman Jin 11 | 12 | % For this file to successfully run, you must do the following: 13 | % - Ensure 'python.m' is in the same folder 14 | % - Also, ensure the required Python libraries are installed (see 15 | % reportgenerator.py) 16 | 17 | %clear, close all 18 | init_tic = tic; % time entire script 19 | 20 | %% Load path names 21 | load path.mat 22 | cd(path.code) 23 | 24 | %%%%%%% CHANGE THESE SETTINGS %%%%%%% 25 | email_list = {'pattia@stanford.edu'}; 26 | batch_name = 'batch9'; 27 | % IF ADDING A NEW BATCH... 28 | % - ADD batch_date TO get_batch_date_from_batch_name 29 | % - CREATE batchx_summary_plots.m AND MODIFY make_summary_images AS NEEDED 30 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 31 | 32 | %% Get batch date from batch name 33 | get_batch_date_from_batch_name 34 | 35 | %% Pull CSVs if program is running on Amazon Workspace 36 | if path.whichcomp == 'amazonws' 37 | aws_pulldata = ['aws s3 sync s3://matr.io/experiment/d3batt D:\Data --exclude "*" --include "' batch_date '*"']; 38 | system(aws_pulldata) 39 | end 40 | 41 | %% Delete bad csvs 42 | delete_bad_csvs 43 | 44 | %% Run batch_analysis for all cells 45 | batch = batch_analysis2(batch_date); 46 | 47 | %% Run predictions 48 | try 49 | apply_model3(batch, batch_name, path) 50 | catch 51 | end 52 | 53 | %% Generate images & results for all cells 54 | make_images(batch, batch_name, batch_date, path.images); 55 | try 56 | [T_cells, T_policies] = make_result_tables(batch, batch_name, ... 57 | path.result_tables, path.code); 58 | catch 59 | end 60 | try 61 | make_summary_images(batch, batch_name, T_cells, T_policies); 62 | catch 63 | end 64 | 65 | %% Run the report generator (in Python) 66 | % This will create the PPT and convert to PDF. It saves in the Box Sync 67 | % folder 68 | python('reportgenerator.py', path.images, path.reports, batch_name); % run python code 69 | 70 | %% Send email 71 | email_results 72 | 73 | %% Sync Data_Matlab folder to AWS 74 | if path.whichcomp == 'amazonws' 75 | disp('Syncing Data_Matlab from Amazon WS to Amazon S3') 76 | system('aws s3 sync D:\Data_Matlab s3://matr.io/experiment/d3batt_matlab') 77 | disp('Sync complete!') 78 | end 79 | 80 | %% Clear contents of D:\Data folder 81 | if path.whichcomp == 'amazonws' 82 | disp('Deleting D:\Data') 83 | cd('D:\') 84 | if exist('Data','dir') 85 | rmdir('Data','s') 86 | end 87 | mkdir('Data') 88 | cd(path.code) 89 | disp('Delete complete!') 90 | end 91 | toc(init_tic) -------------------------------------------------------------------------------- /batch1_summary_plots.m: -------------------------------------------------------------------------------- 1 | function batch1_summary_plots(batch, batch_name, T_cells, T_policies) 2 | %% Function: takes in tabular data to generate summary plots of batch 3 | % Usage: batch1_summary_plots(batteries [struct],'batch_2' [str], T_cells [table],T_policies [table]) 4 | % July 2017 Michael Chen and Peter Attia 5 | % Plotting adapted from Nick Perkins' plot_spread function 6 | 7 | 8 | %% Initialization and inputs 9 | 10 | % pull names from policies 11 | cell_names = cell(height(T_cells),1); 12 | for k = 1:numel(batch) 13 | cell_names{k}=batch(k).policy_readable; 14 | end 15 | 16 | T_policies_array = table2array(T_policies); % convert to array 17 | T_cells_array = table2array(T_cells); % convert to arrayT 18 | T_size = size(T_policies); 19 | colormap jet; 20 | scalefactor = 1e6; % factor to scale degradation rates by 21 | maxvalue = max(T_policies_array(:,8))*scalefactor; % scale degradation rate 22 | 23 | CC1 = T_cells_array(:,1); % CC1 24 | Q1 = T_cells_array(:,2); % Q1 25 | CC2 = T_cells_array(:,3); % CC2 26 | degradation_rate = T_cells_array(:,7); % Deg rate 27 | tt_80 = T_cells_array(:,5); % Time to 80% 28 | cycle_num = T_cells_array(:,6); % Cycle numbers 29 | num_cells = length(cycle_num); % Number of indiv. cells 30 | capacities = zeros(num_cells,1); % Initialize capacities array 31 | 32 | % Init figures 33 | figure_capacity = figure('units','normalized','outerposition',[0 0 1 1]);hold on; box on; 34 | figure_degradation = figure('units','normalized','outerposition',[0 0 1 1]);hold on; box on; 35 | 36 | % Find Capacities 37 | for i = 1:num_cells 38 | cycle = cycle_num(i); 39 | capacities(i,1) = batch(i).summary.QDischarge(cycle); % pulls the most recent capacity 40 | end 41 | 42 | %% Capacity vs charging time 43 | [col, mark]=random_color('y','y'); 44 | figure(figure_capacity); hold on; 45 | for i = 1:num_cells 46 | scatter(tt_80(i),capacities(i,1),100,col,mark,'LineWidth',2) 47 | hold on 48 | end 49 | leg = columnlegend(2,cell_names,'Location','NortheastOutside','boxoff'); 50 | xlabel('Time to 80% SOC (minutes)') 51 | ylabel('Remaining discharge capacity (Ah)') 52 | print('summary3_Q_vs_t80','-dpng') 53 | 54 | %% Average degradation vs charging time 55 | figure(figure_degradation); hold on; 56 | for i = 1:num_cells 57 | scatter(tt_80,degradation_rate,200,col,mark,'LineWidth',2); hold on; 58 | end 59 | leg = columnlegend(2,cell_names,'Location','NortheastOutside','boxoff'); 60 | xlabel('Time to 80% SOC (minutes)') 61 | ylabel('Average degradation rate (Ah/cycle)') 62 | print('summary4_deg_vs_t80','-dpng') 63 | 64 | %% Contour plot 65 | % adapted from Peter Attia 66 | 67 | Q1=5:5:80; 68 | CC1=3:0.5:10; 69 | CC2=[3 3.6]; 70 | time = ones(length(CC2),length(CC1),length(Q1)); 71 | figure, set(gcf, 'units','normalized','outerposition',[0 0 1 1]); 72 | for i=1:length(CC2) 73 | subplot(1,length(CC2),i), hold on, box on 74 | xlabel('CC1'),ylabel('Q1 (%)') 75 | title(['CC2 = ' num2str(CC2(i))]) 76 | axis([min(CC1) max(CC1) min(Q1) max(Q1)]) 77 | 78 | [X,Y] = meshgrid(CC1,Q1); 79 | time = (60.*Y./X./100)+(60.*(80-Y)./CC2(i)./100); 80 | v = [13, 12, 11, 10, 9, 8]; 81 | contour(X,Y,time,v,'LineWidth',2,'ShowText','on') 82 | hold on; 83 | end 84 | 85 | % prepare for degradation plotting 86 | % extract data for scatter plot 87 | CC1_data = table2array(T_policies(:,1)); % CC1 88 | Q1_data = table2array(T_policies(:,2)); % Q1 89 | CC2_data = table2array(T_policies(:,3)); % CC2 90 | degradation_rate = table2array(T_policies(:,8)); % Deg rate 91 | 92 | colormap jet; 93 | scalefactor = 1e6; % Factor to scale 94 | maxvalue = max(degradation_rate*scalefactor); 95 | 96 | for i = 1:length(CC1_data) 97 | if CC2_data(i) == 3.6 && CC1_data(i) ==3.6 98 | subplot(1, length(CC2), 2) 99 | scatter(CC1_data(i),Q1_data(i),'square','CData',degradation_rate(i)*scalefactor,'SizeData',200,'LineWidth',5) 100 | elseif CC2_data(i) == 3.0 101 | subplot(1, length(CC2), 1) 102 | scatter(CC1_data(i),Q1_data(i),'o','CData',degradation_rate(i)*scalefactor,'SizeData',200,'LineWidth',5) 103 | elseif CC2_data(i) == 3.6000 104 | subplot(1, length(CC2), 2) 105 | scatter(CC1_data(i),Q1_data(i),'o','CData',degradation_rate(i)*scalefactor,'SizeData',200,'LineWidth',5) 106 | else 107 | subplot(1, length(CC2), 1) 108 | scatter(CC1_data(i),Q1_data(i),'square','CData',degradation_rate(i)*scalefactor,'SizeData',200,'LineWidth',5) 109 | caxis([0 maxvalue]) 110 | subplot(1, length(CC2), 2) 111 | scatter(CC1_data(i),Q1_data(i),'square','CData',degradation_rate(i)*scalefactor,'SizeData',200,'LineWidth',5) 112 | caxis([0 maxvalue]) 113 | end 114 | end 115 | 116 | 117 | print('summary5_contour','-dpng') 118 | 119 | 120 | end 121 | 122 | -------------------------------------------------------------------------------- /batch2_summary_plots.m: -------------------------------------------------------------------------------- 1 | function batch2_summary_plots(batch, batch_name, T_cells, T_policies) 2 | %% Function: takes in tabular data to generate contour plots of results 3 | % Usage: batch2_summary_plots(batteries [struct],'batch_2' [str], T_cells [table],T_policies [table]) 4 | % July 2017 Michael Chen 5 | % 6 | % Plot needs fixing because of artifacts 7 | 8 | %% Initialization and inputs 9 | T_policies = table2array(T_policies); % convert to array 10 | % T_cells = table2array(T_cells); % convert to array (not really needed) 11 | T_size = size(T_policies); 12 | time = 10; % target time of policies, in minutes 13 | scalefactor = 5e5; % factor to scale degradation rates by 14 | maxvalue = max(T_policies(:,8))*scalefactor/2; % scale degradation rate 15 | 16 | 17 | %% Initialize plot 1 18 | contour1 = figure; % x = CC1, y = Q1, contours = CC2 19 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 20 | set(gcf,'color','w') % make figures white 21 | hold on, box on 22 | Q1=0.5:0.1:79.5; 23 | CC1=1:0.02:6; 24 | [X,Y] = meshgrid(CC1,Q1); 25 | CC2 = ((time - (Y./100).*(60./X))./(60.*(0.8-(Y./100)))).^(-1); 26 | CC2_values = T_policies(:,3); % creates vector of CC2 values 27 | 28 | % plot contour values for plot 1 29 | contour(X,Y,CC2,CC2_values,'LineWidth',2,'ShowText','on') 30 | % scatter plot policies with performance data 31 | for i = 1:T_size(1) 32 | if T_policies(i,2) == 80 33 | if T_policies(i,1) == 4.8 34 | figure(contour1) 35 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 36 | 'CData',[0 0 0],'SizeData',250) 37 | end 38 | else 39 | figure(contour1) 40 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 41 | 'CData',[0 0 0],'SizeData',250) 42 | end 43 | caxis([0 maxvalue]) 44 | end 45 | xlabel('CC1'),ylabel('Q1 (%)') 46 | hcb = colorbar; set(hcb,'YTick',[]) % colorbar with no axis 47 | 48 | % Cover bad region 49 | line([1.02 4.75],[16.5 79], 'LineWidth',20,'color','w'); 50 | % add explanation of color 51 | dim = [.2 .5 .3 .3]; 52 | str = {'Color = degradation rate',' (yellow = higher degradation rate)'}; 53 | annotation('textbox',dim,'String',str,'FitBoxToText','on','LineStyle','none','FontSize',16) 54 | 55 | %% Save file 56 | saveas(contour1, 'summary3_contour1.png') 57 | savefig(contour1,'summary3_contour1') 58 | 59 | %% Initialize plot 2 60 | contour2 = figure; % x = CC1, y = CC2 contours = Q1 61 | colormap jet; 62 | colormap(flipud(colormap)); 63 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 64 | set(gcf,'color','w') % make figures white 65 | hold on, box on, axis square 66 | CC1 = 1:0.1:8; 67 | CC2 = 1:0.1:8; 68 | [X,Y] = meshgrid(CC1,CC2); 69 | Q1 = (100).*(time - ((60*0.8)./Y))./((60./X)-(60./Y)); 70 | Q1_values = 5:10:75; 71 | axis([0.9 8.1 0.9 8.1]) 72 | 73 | % plot contour values for plot 2 74 | contour(X,Y,Q1,Q1_values,'LineWidth',2,'ShowText','on') 75 | 76 | for i = 1:T_size(1) 77 | if T_policies(i,2) == 80 78 | if T_policies(i,1) == 4.8 79 | figure(contour2) 80 | scatter(T_policies(i,1),T_policies(i,3),'rsquare','CData',T_policies(i,8)*scalefactor,'SizeData',250,'LineWidth',5) 81 | end 82 | else 83 | figure(contour2) 84 | scatter(T_policies(i,1),T_policies(i,3),'ro','CData',T_policies(i,8)*scalefactor,'SizeData',250,'LineWidth',5) 85 | end 86 | caxis([0 maxvalue]) 87 | end 88 | xlabel('CC1'),ylabel('CC2') 89 | hcb = colorbar; set(hcb,'YTick',[]) % colorbar with no axis 90 | 91 | %% Cover bad region 92 | line([1.01 4.72],[1.01 4.72], 'LineWidth',20,'color','w') 93 | line([4.9 7.99],[4.9 7.99], 'LineWidth',20,'color','w'); 94 | 95 | % add explanation of color 96 | dim = [0.3 0.1 0.3 0.2]; 97 | str = {'Color = degradation rate',' (blue = higher degradation rate)'}; 98 | annotation('textbox',dim,'String',str,'FitBoxToText','on','LineStyle','none','FontSize',16) 99 | 100 | %% Save file 101 | saveas(contour2, 'summary4_contour2.png') 102 | savefig(gcf,'summary4_contour2.fig') 103 | 104 | %% Close all figure windows 105 | close all 106 | 107 | end 108 | -------------------------------------------------------------------------------- /batch2_summary_plots2.m: -------------------------------------------------------------------------------- 1 | function batch2_summary_plots2(T_policies) 2 | %% Function: takes in tabular data to generate contour plots of results 3 | % Usage: batch3_summary_plots(batteries [struct],'batch_2' [str], T_cells [table],T_policies [table]) 4 | % August 2017 Michael Chen & Peter Attia 5 | % 6 | % Plot needs fixing because of artifacts 7 | 8 | %% Initialization and inputs 9 | T_size = height(T_policies); 10 | time = 10; % target time of policies, in minutes 11 | T_policies.Properties.VariableNames{1} = 'CC1'; 12 | T_policies.Properties.VariableNames{2} = 'Q1'; 13 | T_policies.Properties.VariableNames{3} = 'CC2'; 14 | T_policies.Properties.VariableNames{7} = 'cycles'; 15 | T_policies.Properties.VariableNames{8} = 'degrate'; 16 | 17 | %% Initialize plot 1: color = Q1 18 | contour1 = figure; % x = CC1, y = CC2, contours = Q1 19 | colormap parula; 20 | colormap(flipud(colormap)); 21 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 22 | set(gcf,'color','w') % make figures white 23 | hold on, box on, axis square 24 | CC1 = 2.9:0.01:6.1; 25 | CC2 = 2.9:0.01:6.1; 26 | [X,Y] = meshgrid(CC1,CC2); 27 | Q1 = (100).*(time - ((60*0.8)./Y))./((60./X)-(60./Y)); 28 | Q1(Q1<0) = NaN; 29 | Q1(Q1>80) = NaN; 30 | Q1_values = 5:10:75; 31 | axis([2.9 6.1 2.9 6.1]) 32 | 33 | % plot contour values for plot 1 34 | contour(X,Y,Q1,Q1_values,'LineWidth',2,'ShowText','on') 35 | 36 | title(['Time to 80% = ' num2str(time) ' minutes'],'FontSize',20) 37 | set(gca,'FontSize',18) 38 | xlabel('C1','FontSize',20),ylabel('C2','FontSize',20) 39 | h = colorbar; title(h, 'Q1 (%)','FontWeight','bold') 40 | caxis([0 80]) 41 | 42 | for i = 1:T_size 43 | if T_policies.Q1(i) == 80 44 | if T_policies.CC1(i) == 4.8 45 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 46 | 'CData',[0 0 0],'SizeData',250) 47 | end 48 | else 49 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 50 | 'CData',[0 0 0],'SizeData',250) 51 | end 52 | end 53 | 54 | 55 | 56 | %% Initialize plot 2 - color = cycle life 57 | contour2 = figure; % x = CC1, y = CC2, contours = Q1 58 | colormap jet; 59 | colormap(flipud(colormap)); 60 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 61 | set(gcf,'color','w') % make figures white 62 | hold on, box on, axis square 63 | axis([2.9 6.1 2.9 6.1]) 64 | 65 | % plot contour values for plot 2 66 | contour(X,Y,Q1,Q1_values,'k','LineWidth',2,'ShowText','on') 67 | 68 | title(['Time to 80% = ' num2str(time) ' minutes'],'FontSize',20) 69 | set(gca,'FontSize',18) 70 | xlabel('C1','FontSize',20),ylabel('C2','FontSize',20) 71 | h = colorbar; title(h, 'Cycle Life','FontWeight','bold') 72 | 73 | for i = 1:T_size 74 | if T_policies.Q1(i) == 80 75 | if T_policies.CC1(i) == 4.8 76 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 77 | 'CData',T_policies.cycles(i),'SizeData',250) 78 | end 79 | else 80 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 81 | 'CData',T_policies.cycles(i),'SizeData',250) 82 | end 83 | end 84 | 85 | %% Initialize plot 3 - color = deg rate 86 | contour3 = figure; % x = CC1, y = CC2, contours = Q1 87 | colormap jet; 88 | colormap(flipud(colormap)); 89 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 90 | set(gcf,'color','w') % make figures white 91 | hold on, box on, axis square 92 | axis([2.9 6.1 2.9 6.1]) 93 | 94 | % plot contour values for plot 2 95 | contour(X,Y,Q1,Q1_values,'k','LineWidth',2,'ShowText','on') 96 | 97 | title(['Time to 80% = ' num2str(time) ' minutes'],'FontSize',20) 98 | set(gca,'FontSize',18) 99 | xlabel('C1','FontSize',20),ylabel('C2','FontSize',20) 100 | h = colorbar; title(h, 'Degradation rate (Ah/cycle)','FontWeight','bold') 101 | caxis([0 max(T_policies.degrate)]) 102 | 103 | for i = 1:T_size 104 | if T_policies.Q1(i) == 80 105 | if T_policies.CC1(i) == 4.8 106 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 107 | 'CData',T_policies.degrate(i),'SizeData',250) 108 | end 109 | else 110 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 111 | 'CData',T_policies.degrate(i),'SizeData',250) 112 | end 113 | end 114 | 115 | %% Save files 116 | saveas(contour1, 'summary3_contour1.png') 117 | savefig(contour1,'summary3_contour1.fig') 118 | 119 | saveas(contour2, 'summary4_contour2.png') 120 | savefig(contour2,'summary4_contour2.fig') 121 | 122 | saveas(contour3, 'summary5_contour3.png') 123 | savefig(contour3,'summary5_contour3.fig') 124 | 125 | % %% Close all figure windows 126 | % close all 127 | 128 | end 129 | -------------------------------------------------------------------------------- /batch3_summary_plots.m: -------------------------------------------------------------------------------- 1 | function batch3_summary_plots(T_policies) 2 | %% Function: takes in tabular data to generate contour plots of results 3 | % Usage: batch3_summary_plots(batteries [struct],'batch_2' [str], T_cells [table],T_policies [table]) 4 | % August 2017 Michael Chen & Peter Attia 5 | % 6 | % Plot needs fixing because of artifacts 7 | 8 | %% Initialization and inputs 9 | T_size = height(T_policies); 10 | time = 13.3333; % target time of policies, in minutes 11 | T_policies.Properties.VariableNames{1} = 'CC1'; 12 | T_policies.Properties.VariableNames{2} = 'Q1'; 13 | T_policies.Properties.VariableNames{3} = 'CC2'; 14 | T_policies.Properties.VariableNames{7} = 'cycles'; 15 | T_policies.Properties.VariableNames{8} = 'degrate'; 16 | 17 | %% Initialize plot 1: color = Q1 18 | contour1 = figure; % x = CC1, y = CC2, contours = Q1 19 | colormap parula; 20 | colormap(flipud(colormap)); 21 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 22 | set(gcf,'color','w') % make figures white 23 | hold on, box on, axis square 24 | CC1 = 1:0.05:8; 25 | CC2 = 1:0.05:8; 26 | [X,Y] = meshgrid(CC1,CC2); 27 | Q1 = (100).*(time - ((60*0.8)./Y))./((60./X)-(60./Y)); 28 | Q1_values = 5:10:75; 29 | axis([0.9 8.1 0.9 8.1]) 30 | 31 | % plot contour values for plot 1 32 | contour(X,Y,Q1,Q1_values,'LineWidth',2,'ShowText','on') 33 | 34 | title(['Time to 80% = ' num2str(time) ' minutes'],'FontSize',20) 35 | set(gca,'FontSize',18) 36 | xlabel('CC1','FontSize',20),ylabel('CC2','FontSize',20) 37 | h = colorbar; title(h, 'Q1 (%)','FontWeight','bold') 38 | caxis([0 80]) 39 | 40 | %% Cover bad region 41 | line([8 3.6],[7.9 3.6], 'LineWidth',22.5,'color','w'); 42 | line([3.6 1],[3.6 1], 'LineWidth',22.5,'color','w'); 43 | 44 | for i = 1:T_size 45 | if T_policies.Q1(i) == 80 46 | if T_policies.CC1(i) == 3.6 47 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 48 | 'CData',[0 0 0],'SizeData',250) 49 | end 50 | else 51 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 52 | 'CData',[0 0 0],'SizeData',250) 53 | end 54 | end 55 | 56 | 57 | 58 | %% Initialize plot 2 - color = cycle life 59 | contour2 = figure; % x = CC1, y = CC2, contours = Q1 60 | colormap jet; 61 | colormap(flipud(colormap)); 62 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 63 | set(gcf,'color','w') % make figures white 64 | hold on, box on, axis square 65 | axis([0.9 8.1 0.9 8.1]) 66 | 67 | % plot contour values for plot 2 68 | contour(X,Y,Q1,Q1_values,'k','LineWidth',2,'ShowText','on') 69 | 70 | title(['Time to 80% = ' num2str(time) ' minutes'],'FontSize',20) 71 | set(gca,'FontSize',18) 72 | xlabel('C1','FontSize',20),ylabel('C2','FontSize',20) 73 | h = colorbar; title(h, 'Cycle Life','FontWeight','bold') 74 | 75 | %% Cover bad region 76 | line([8 3.6],[7.9 3.6], 'LineWidth',22.5,'color','w'); 77 | line([3.6 1],[3.6 1], 'LineWidth',22.5,'color','w'); 78 | 79 | for i = 1:T_size 80 | if T_policies.Q1(i) == 80 81 | if T_policies.CC1(i) == 3.6 82 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 83 | 'CData',T_policies.cycles(i),'SizeData',250) 84 | end 85 | else 86 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 87 | 'CData',T_policies.cycles(i),'SizeData',250) 88 | end 89 | end 90 | 91 | %% Initialize plot 3 - color = deg rate 92 | contour3 = figure; % x = CC1, y = CC2, contours = Q1 93 | colormap jet; 94 | colormap(flipud(colormap)); 95 | set(gcf, 'units','normalized','outerposition',[0 0 1 1]) % resize for screen 96 | set(gcf,'color','w') % make figures white 97 | hold on, box on, axis square 98 | axis([0.9 8.1 0.9 8.1]) 99 | 100 | % plot contour values for plot 2 101 | contour(X,Y,Q1,Q1_values,'k','LineWidth',2,'ShowText','on') 102 | 103 | title(['Time to 80% = ' num2str(time) ' minutes'],'FontSize',20) 104 | set(gca,'FontSize',18) 105 | xlabel('C1','FontSize',20),ylabel('C2','FontSize',20) 106 | h = colorbar; title(h, 'Degradation rate (Ah/cycle)','FontWeight','bold') 107 | caxis([0 max(T_policies.degrate)]) 108 | 109 | %% Cover bad region 110 | line([8 3.6],[7.9 3.6], 'LineWidth',22.5,'color','w'); 111 | line([3.6 1],[3.6 1], 'LineWidth',22.5,'color','w'); 112 | 113 | for i = 1:T_size 114 | if T_policies.Q1(i) == 80 115 | if T_policies.CC1(i) == 3.6 116 | scatter(T_policies.CC1(i),T_policies.CC2(i),'rsquare', 'filled', ... 117 | 'CData',T_policies.degrate(i),'SizeData',250) 118 | end 119 | else 120 | scatter(T_policies.CC1(i),T_policies.CC2(i),'ro', 'filled', ... 121 | 'CData',T_policies.degrate(i),'SizeData',250) 122 | end 123 | end 124 | 125 | %% Save files 126 | saveas(contour1, 'summary3_contour1.png') 127 | savefig(contour1,'summary3_contour1.fig') 128 | 129 | saveas(contour2, 'summary4_contour2.png') 130 | savefig(contour2,'summary4_contour2.fig') 131 | 132 | saveas(contour3, 'summary5_contour3.png') 133 | savefig(contour3,'summary5_contour3.fig') 134 | 135 | % %% Close all figure windows 136 | % close all 137 | 138 | end 139 | -------------------------------------------------------------------------------- /batch_analysis.m: -------------------------------------------------------------------------------- 1 | function batch = batch_analysis(batch_date) 2 | 3 | disp('Starting batch_analysis'), batch_tic = tic; 4 | 5 | %% Initialize batch struct 6 | batch = struct('policy', ' ', 'policy_readable', ' ', 'barcode', ... 7 | ' ', 'channel_id', ' ','cycle_life', NaN,... 8 | 'cycles', struct('discharge_dQdV', [], 't', [], 'Qc', [], 'I', [], ... 9 | 'V', [], 'T', [], 'Qd', [], 'Qdlin', [],'Tdlin',[]), ... 10 | 'summary', struct('cycle', [], 'QDischarge', [], 'QCharge', [], ... 11 | 'IR', [], 'Tmax', [], 'Tavg', [], 'Tmin', [], 'chargetime', []), ... 12 | 'Vdlin',[]); 13 | 14 | %% Load path names 15 | load path.mat 16 | 17 | %% Initialize arrays 18 | % array of charging algorithm names 19 | CA_array = {}; 20 | % List of all file names including metadata 21 | test_files = {}; 22 | % An array of barcodes for each cell pulled from metadata 23 | barcodes = {}; 24 | channel_ids = {}; 25 | 26 | %% Find CSVs from this batch 27 | cd(path.csv_data) 28 | 29 | batch_file_name = strcat('*', batch_date, '*.csv'); 30 | dir_info = dir(char(batch_file_name)); 31 | filenames = {dir_info.name}; 32 | 33 | % Remove deleted filenames from list 34 | deletedcount = 0; 35 | for i = 1:numel(filenames) 36 | if filenames{i}(1) == '~' 37 | deletedcount = deletedcount + 1; 38 | end 39 | end 40 | filenames = filenames(1:numel(filenames) - deletedcount); 41 | 42 | % If no files are found, display error and exit 43 | if numel(filenames) == 0 44 | disp('No files match query') 45 | return 46 | end 47 | 48 | keySet = {}; 49 | valueSet = {}; 50 | 51 | %% Extract metadata and then remove from filename array 52 | for i = 1:numel(filenames) 53 | % Finds if .csv is a metadata csv 54 | if contains(filenames{i}, 'Meta') 55 | % If so, read the cell barcode from the metadata 56 | [~, ~, text_data] = xlsread(filenames{i}); 57 | cell_ID = string(text_data{2, 11}); 58 | channel_id = string((text_data{2, 4} + 1)); 59 | % Extract metadata 60 | barcodes = [barcodes, cell_ID]; 61 | channel_ids = [channel_ids, channel_id]; 62 | 63 | % Extract charging algorithm 64 | schedule_file = upper(text_data{2, 9}); 65 | % Find hyphen before charging algorithm 66 | % Example schedule file: 67 | % '2017-12-04_tests\20170630-3_6C_30per_6C.sdu' 68 | % We want the '-' between 0630 and 3_6, or the last hyphen 69 | underscore_i = strfind(schedule_file, '-'); 70 | underscore_i = underscore_i(end); 71 | % We also want the '.' in '.sdu' 72 | dot_i = strfind(schedule_file, '.'); 73 | charging_algorithm = schedule_file(underscore_i + 1:dot_i - 1); 74 | 75 | % Store charging algorithm name 76 | CA_array = [CA_array, charging_algorithm]; 77 | 78 | % Create 'container' (aka dictionary) to map charging algorithms to 79 | % channels 80 | keySet{end+1} = char(channel_id); 81 | valueSet{end+1} = charging_algorithm; 82 | 83 | continue 84 | else 85 | % File is a result csv 86 | test_files = [test_files, filenames{i}]; 87 | end 88 | end 89 | 90 | % Remove any duplicates 91 | CA_array = unique(CA_array); 92 | 93 | % Create map object 94 | mapObj = containers.Map(keySet,valueSet); 95 | 96 | if strcmp(batch_date,'20170412') 97 | test_files = test_files([1:29 42:end]); 98 | end 99 | 100 | k = 1; 101 | 102 | %% Load each file sequentially and save data into struct 103 | for j = 1:numel(CA_array) 104 | charging_algorithm = CA_array{j}; 105 | 106 | for i = 1:numel(test_files) 107 | % Find tests that are within that charging algorithm 108 | filename = test_files{i}; 109 | 110 | underscore_i = strfind(filename, '_'); 111 | underscore_i = underscore_i(end); 112 | % We also want the '.' in '.sdu' 113 | dot_i = strfind(filename, '.'); 114 | channel = char(filename(underscore_i + 3:dot_i - 1)); 115 | 116 | CA = mapObj(channel); 117 | 118 | if strcmp(CA,charging_algorithm) 119 | % Update user on progress 120 | tic 121 | disp(['Starting processing of file ' num2str(k) ' of ' ... 122 | num2str(numel(test_files)) ': ' filename]) 123 | k = k + 1; 124 | 125 | %% Run cell_analysis for this file 126 | result_data = csvread(strcat(path.csv_data, '\', test_files{i}),1,1); 127 | cd(path.code) 128 | 129 | if strcmp(batch_date,'20170412') 130 | battery = cell_analysis_batch0(result_data, charging_algorithm, ... 131 | batch_date, path.csv_data); 132 | battery.barcode = barcodes(i); 133 | battery.channel_id = channel_ids(i); 134 | batch(i) = battery; 135 | else 136 | battery = cell_analysis(result_data, charging_algorithm, ... 137 | batch_date, path.csv_data); 138 | battery.barcode = barcodes(i); %% THIS IS LIKELY WRONG 139 | battery.channel_id = channel; 140 | batch(i) = battery; 141 | end 142 | cd(path.csv_data) 143 | else 144 | continue 145 | end 146 | toc 147 | end 148 | end 149 | 150 | %% Save batch as struct 151 | cd(path.batch_struct) 152 | disp(['Saving batch information to directory ', cd]) 153 | tic 154 | save(strcat(batch_date, '_batchdata','_updated_struct_errorcorrect'), 'batch_date', 'batch') 155 | toc 156 | cd(path.code) 157 | 158 | disp('Completed batch_analysis'), toc(batch_tic) 159 | 160 | end 161 | -------------------------------------------------------------------------------- /batch_analysis2.m: -------------------------------------------------------------------------------- 1 | function batch = batch_analysis2(batch_date) 2 | 3 | disp('Starting batch_analysis'), batch_tic = tic; 4 | 5 | %% Initialize batch struct 6 | batch = struct('policy', ' ', 'policy_readable', ' ', 'barcode', ... 7 | ' ', 'channel_id', ' ','cycle_life', NaN,... 8 | 'cycles', struct('discharge_dQdV', [], 't', [], 'Qc', [], 'I', [], ... 9 | 'V', [], 'T', [], 'Qd', [], 'Qdlin', [],'Tdlin',[]), ... 10 | 'summary', struct('cycle', [], 'QDischarge', [], 'QCharge', [], ... 11 | 'IR', [], 'Tmax', [], 'Tavg', [], 'Tmin', [], 'chargetime', []), ... 12 | 'Vdlin',[]); 13 | 14 | %% Load path names 15 | load path.mat 16 | 17 | %% Initialize arrays 18 | % array of charging algorithm names 19 | CA_array = {}; 20 | % List of all file names including metadata 21 | test_files = {}; 22 | % An array of barcodes for each cell pulled from metadata 23 | barcodes = {}; 24 | channel_ids = {}; 25 | 26 | %% Find CSVs from this batch 27 | cd(path.csv_data) 28 | 29 | batch_file_name = strcat('*', batch_date, '*.csv'); 30 | dir_info = dir(char(batch_file_name)); 31 | filenames = {dir_info.name}; 32 | 33 | % Remove deleted filenames from list 34 | deletedcount = 0; 35 | for i = 1:numel(filenames) 36 | if filenames{i}(1) == '~' 37 | deletedcount = deletedcount + 1; 38 | end 39 | end 40 | filenames = filenames(1:numel(filenames) - deletedcount); 41 | 42 | % If no files are found, display error and exit 43 | if numel(filenames) == 0 44 | disp('No files match query') 45 | return 46 | end 47 | 48 | keySet = {}; 49 | valueSet = {}; 50 | 51 | %% Extract metadata and then remove from filename array 52 | for i = 1:numel(filenames) 53 | % Finds if .csv is a metadata csv 54 | if contains(filenames{i}, 'Meta') 55 | % If so, read the cell barcode from the metadata 56 | [~, ~, text_data] = xlsread(filenames{i}); 57 | cell_ID = string(text_data{2, 11}); 58 | channel_id = string((text_data{2, 4} + 1)); 59 | % Extract metadata 60 | barcodes = [barcodes, cell_ID]; 61 | channel_ids = [channel_ids, channel_id]; 62 | 63 | % Extract charging algorithm 64 | schedule_file = upper(text_data{2, 9}); 65 | % Find hyphen before charging algorithm 66 | % Example schedule file: 67 | % '2017-12-04_tests\20170630-3_6C_30per_6C.sdu' 68 | % We want the '-' between 0630 and 3_6, or the last hyphen 69 | underscore_i = strfind(schedule_file, '-'); 70 | underscore_i = underscore_i(end); 71 | if strcmp(batch_date, '2018-10-02') || strcmp(batch_date, '2018-11-02') 72 | underscore_i = strfind(schedule_file, '\'); 73 | underscore_i = underscore_i(end); 74 | end 75 | % We also want the '.' in '.sdu' 76 | dot_i = strfind(schedule_file, '.'); 77 | charging_algorithm = schedule_file(underscore_i + 1:dot_i - 1); 78 | 79 | % Store charging algorithm name 80 | CA_array = [CA_array, charging_algorithm]; 81 | 82 | test_files = [test_files, filenames{i}([1:end-13 end-3:end])]; 83 | end 84 | end 85 | 86 | if strcmp(batch_date,'20170412') 87 | test_files = test_files([1:29 42:end]); 88 | end 89 | 90 | k = 1; 91 | 92 | %% Load each file sequentially and save data into struct 93 | n_cells = numel(test_files); 94 | for i = 1:n_cells 95 | % Find metadata 96 | filename = test_files{i}; 97 | CA = CA_array{i}; 98 | 99 | % Update user on progress 100 | tic 101 | disp(['Starting processing of file ' num2str(k) ' of ' ... 102 | num2str(n_cells) ': ' filename]) 103 | k = k + 1; 104 | 105 | %% Run cell_analysis for this file 106 | result_data = csvread(strcat(path.csv_data, '\', filename),1,1); 107 | cd(path.code) 108 | 109 | battery = cell_analysis(result_data, CA, batch_date, path.csv_data); 110 | battery.barcode = barcodes(i); 111 | battery.channel_id = channel_ids(i); 112 | batch(i) = battery; 113 | 114 | cd(path.csv_data) 115 | toc 116 | end 117 | 118 | %% Save batch as struct 119 | cd(path.batch_struct) 120 | disp(['Saving batch information to directory ', cd]) 121 | tic 122 | save(strcat(batch_date, '_batchdata','_updated_struct_errorcorrect'), 'batch_date', 'batch') 123 | toc 124 | cd(path.code) 125 | 126 | disp('Completed batch_analysis'), toc(batch_tic) 127 | 128 | end 129 | -------------------------------------------------------------------------------- /batch_analysis_old.m: -------------------------------------------------------------------------------- 1 | function batch = batch_analysis(batch_date) 2 | 3 | disp('Starting batch_analysis'), batch_tic = tic; 4 | 5 | %% Initialize batch struct 6 | batch = struct('policy', ' ', 'policy_readable', ' ', 'barcode', ... 7 | ' ', 'channel_id', ' ','cycle_life', NaN,... 8 | 'cycles', struct('discharge_dQdV', [], 't', [], 'Qc', [], 'I', [], ... 9 | 'V', [], 'T', [], 'Qd', [], 'Qdlin', [],'Tdlin',[]), ... 10 | 'summary', struct('cycle', [], 'QDischarge', [], 'QCharge', [], ... 11 | 'IR', [], 'Tmax', [], 'Tavg', [], 'Tmin', [], 'chargetime', []), ... 12 | 'Vdlin',[]); 13 | 14 | %% Load path names 15 | load path.mat 16 | 17 | %% Initialize arrays 18 | % array of charging algorithm names 19 | CA_array = {}; 20 | % List of all file names including metadata 21 | test_files = {}; 22 | % An array of barcodes for each cell pulled from metadata 23 | barcodes = {}; 24 | channel_ids = {}; 25 | 26 | %% Find CSVs from this batch 27 | cd(path.csv_data) 28 | 29 | batch_file_name = strcat('*', batch_date, '*.csv') 30 | dir_info = dir(char(batch_file_name)) 31 | filenames = {dir_info.name} 32 | 33 | % Remove deleted filenames from list 34 | deletedcount = 0; 35 | for i = 1:numel(filenames) 36 | if filenames{i}(1) == '~' 37 | deletedcount = deletedcount + 1; 38 | end 39 | end 40 | filenames = filenames(1:numel(filenames) - deletedcount); 41 | 42 | % If no files are found, display error and exit 43 | if numel(filenames) == 0 44 | disp('No files match query') 45 | return 46 | end 47 | 48 | %% Extract metadata and then remove from filename array 49 | for i = 1:numel(filenames) 50 | % Finds if .csv is a metadata csv 51 | if contains(filenames{i}, 'Meta') 52 | % If so, read the cell barcode from the metadata 53 | [~, ~, text_data] = xlsread(filenames{i}); 54 | cell_ID = string(text_data{2, 11}); 55 | channel_id = string((text_data{2, 4} + 1)); 56 | % Extract metadata 57 | barcodes = [barcodes, cell_ID]; 58 | channel_ids = [channel_ids, channel_id]; 59 | 60 | continue 61 | else 62 | % File is a result csv 63 | test_files = [test_files, filenames{i}]; 64 | test_name = filenames{i}; 65 | 66 | if strcmp(batch_date,'20170412') 67 | underscore_i = strfind(test_name, '_'); 68 | charging_algorithm = test_name(10:underscore_i - 1); 69 | else 70 | % Find underscore before and after charging algorithm 71 | underscore_i = strfind(test_name, '_'); 72 | charging_algorithm = test_name(underscore_i(1) ... 73 | + 1:underscore_i(end) - 1); 74 | end 75 | 76 | % Store charging algorithm name 77 | CA_array = [CA_array, charging_algorithm]; 78 | end 79 | end 80 | % Remove any duplicates 81 | CA_array = unique(CA_array); 82 | 83 | if strcmp(batch_date,'20170412') 84 | test_files = test_files([1:29 42:end]); 85 | end 86 | 87 | %% Load each file sequentially and save data into struct 88 | for j = 1:numel(CA_array) 89 | charging_algorithm = CA_array{j}; 90 | 91 | for i = 1:numel(test_files) 92 | % Find tests that are within that charging algorithm 93 | filename = test_files{i}; 94 | if contains(filename, charging_algorithm) 95 | % Update user on progress 96 | tic 97 | disp(['Starting processing of file ' num2str(i) ' of ' ... 98 | num2str(numel(test_files)) ': ' filename]) 99 | 100 | %% Run cell_analysis for this file 101 | result_data = csvread(strcat(path.csv_data, '\', test_files{i}),1,1); 102 | cd(path.code) 103 | 104 | if strcmp(batch_date,'20170412') 105 | battery = cell_analysis_batch0(result_data, charging_algorithm, ... 106 | batch_date, path.csv_data); 107 | battery.barcode = barcodes(i); 108 | battery.channel_id = channel_ids(i); 109 | batch(i) = battery; 110 | else 111 | battery = cell_analysis(result_data, charging_algorithm, ... 112 | batch_date, path.csv_data); 113 | battery.barcode = barcodes(i); 114 | battery.channel_id = channel_ids(i); 115 | batch(i) = battery; 116 | end 117 | cd(path.csv_data) 118 | else 119 | continue 120 | end 121 | toc 122 | end 123 | end 124 | 125 | %% Save batch as struct 126 | cd(path.batch_struct) 127 | disp(['Saving batch information to directory ', cd]) 128 | tic 129 | save(strcat(batch_date, '_batchdata','_updated_struct'), 'batch_date', 'batch') 130 | toc 131 | cd(path.code) 132 | 133 | disp('Completed batch_analysis'), toc(batch_tic) 134 | 135 | end 136 | -------------------------------------------------------------------------------- /batch_visualization.m: -------------------------------------------------------------------------------- 1 | %% batch_visualization 2 | % This script creates plots to visualize the features from a model. 3 | % Not run in auto_analysis data processing script 4 | 5 | %clear; close all; clc 6 | 7 | %%% PUT THE PATH TO THE DATA HERE %%% 8 | if ~exist('batch','var') 9 | load('D:\Data_Matlab\Batch_data\2018-04-12_batchdata_updated_struct_errorcorrect.mat') 10 | end 11 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 12 | 13 | numBat = size(batch,2); 14 | 15 | %% Features 16 | % Load the model you would like to use 17 | load('MyModel.mat') 18 | feat = build_battery_features(batch); 19 | feat_scaled = bsxfun(@minus,feat,mu); 20 | feat_scaled = bsxfun(@rdivide,feat_scaled,sigma); 21 | feat_scaled = feat_scaled(:,feat_ind); 22 | 23 | for k = 1:length(feat_scaled(1,:)) 24 | figure, plot(feat_scaled(:,k)) 25 | title(['Feature ' num2str(k)]) 26 | end 27 | 28 | %% Q v. N plots 29 | figure, hold on 30 | for i = 1:numBat 31 | plot(batch(i).summary.QDischarge,'.-') 32 | end 33 | xlabel('Cycle Number') 34 | ylabel('Discharge (Ah)') 35 | 36 | figure() 37 | hold on 38 | for i = 1:numBat 39 | plot(batch(i).summary.QCharge,'.-') 40 | end 41 | xlabel('Cycle Number') 42 | ylabel('Charge (Ah)') 43 | 44 | %% Temperature plots 45 | figure() 46 | for i = 1:numBat 47 | subplot(6,8,i) 48 | hold on 49 | for j = 1:5:length(batch(i).summary.QDischarge) 50 | if any(batch(i).cycles(j).t < 0) 51 | fix_ind = find(batch(i).cycles(j).t < 0,1); 52 | batch(i).cycles(j).t(fix_ind:end) = batch(i).cycles(j).t(fix_ind:end) + ... 53 | batch(i).cycles(j).t(fix_ind-1) - batch(i).cycles(j).t(fix_ind); 54 | 55 | end 56 | if any(batch(i).cycles(j).t > 100) 57 | fix_ind = find(batch(i).cycles(j).t >100,1); 58 | batch(i).cycles(j).t(fix_ind:end) = batch(i).cycles(j).t(fix_ind:end) + ... 59 | batch(i).cycles(j).t(fix_ind-1) - batch(i).cycles(j).t(fix_ind); 60 | 61 | end 62 | plot(batch(i).cycles(j).t,batch(i).cycles(j).T) 63 | end 64 | xlabel('Time') 65 | ylabel('Temperature') 66 | title(batch(i).policy_readable) 67 | end 68 | 69 | %% IR plots 70 | figure() 71 | hold on 72 | for i = 1:numBat 73 | plot(batch(i).summary.IR,'.-') 74 | end 75 | xlabel('Cycle Number') 76 | ylabel('IR') 77 | -------------------------------------------------------------------------------- /build_battery_features.m: -------------------------------------------------------------------------------- 1 | function [ X ] = build_battery_features( batch ) 2 | %This function builds the possible feature set for the model 3 | 4 | %If you add candidate features, update this number for better memory 5 | %management 6 | numFeat = 15; 7 | 8 | numBat = size(batch,2); 9 | X = zeros(numBat,numFeat); 10 | 11 | for i = 1:numBat 12 | %Capacity features 13 | %Initial capacity 14 | X(i,1) = batch(i).summary.QDischarge(2); 15 | %Max change in capacity 16 | X(i,2) = max(batch(i).summary.QDischarge(1:100))... 17 | - batch(i).summary.QDischarge(2); 18 | %capacity at cycle 100 19 | X(i,3) = batch(i).summary.QDischarge(100); 20 | 21 | %Linear fit of Q v. N 22 | R3 = regress(batch(i).summary.QDischarge(2:100),[2:100;ones(1,length(2:100))]'); 23 | X(i,4) = R3(1); 24 | X(i,5) = R3(2); 25 | 26 | %Linear fit of Q v. N, only last 10 cycles 27 | R1 = regress(batch(i).summary.QDischarge(91:100),[91:100;ones(1,length(91:100))]'); 28 | X(i,6) = R1(1); 29 | X(i,7) = R1(2); 30 | 31 | %Q features 32 | QDiff = batch(i).cycles(100).Qdlin - batch(i).cycles(10).Qdlin; 33 | 34 | X(i,8) = log10(abs(min(QDiff))); 35 | X(i,9) = log10(abs(mean(QDiff))); 36 | X(i,10) = log10(abs(var(QDiff))); 37 | X(i,11) = log10(abs(skewness(QDiff))); 38 | X(i,12) = log10(abs(QDiff(1))); 39 | 40 | % Peter's proposed features 41 | 42 | % Sum of Qdiff 43 | X(i,13) = log10(sum(abs(QDiff))); 44 | 45 | % Sum of Qdiff^2 46 | X(i,14) = log10(sum(QDiff.^2)); 47 | 48 | % Energy difference 49 | E10 = trapz(batch(i).cycles(10).Qdlin,batch(i).Vdlin); 50 | E100 = trapz(batch(i).cycles(100).Qdlin,batch(i).Vdlin); 51 | X(i,15) = log10(E10-E100); 52 | 53 | end %end loop through batteries 54 | 55 | 56 | end -------------------------------------------------------------------------------- /build_battery_features95.m: -------------------------------------------------------------------------------- 1 | function [ X ] = build_battery_features95( batch ) 2 | %This function builds the possible feature set for the model 3 | 4 | %If you add candidate features, update this number for better memory 5 | %management 6 | numFeat = 15; 7 | 8 | numBat = size(batch,2); 9 | X = zeros(numBat,numFeat); 10 | 11 | for i = 1:numBat 12 | %Capacity features 13 | %Initial capacity 14 | X(i,1) = batch(i).summary.QDischarge(2); 15 | %Max change in capacity 16 | X(i,2) = max(batch(i).summary.QDischarge(1:95))... 17 | - batch(i).summary.QDischarge(2); 18 | %capacity at cycle 100 19 | X(i,3) = batch(i).summary.QDischarge(95); 20 | 21 | %Linear fit of Q v. N 22 | R3 = regress(batch(i).summary.QDischarge(2:95),[2:95;ones(1,length(2:95))]'); 23 | X(i,4) = R3(1); 24 | X(i,5) = R3(2); 25 | 26 | %Linear fit of Q v. N, only last 10 cycles 27 | R1 = regress(batch(i).summary.QDischarge(91:95),[91:95;ones(1,length(91:95))]'); 28 | X(i,6) = R1(1); 29 | X(i,7) = R1(2); 30 | 31 | %Q features 32 | QDiff = batch(i).cycles(95).Qdlin - batch(i).cycles(10).Qdlin; 33 | 34 | X(i,8) = log10(abs(min(QDiff))); 35 | X(i,9) = log10(abs(mean(QDiff))); 36 | X(i,10) = log10(abs(var(QDiff))); 37 | X(i,11) = log10(abs(skewness(QDiff))); 38 | X(i,12) = log10(abs(QDiff(1))); 39 | 40 | % Peter's proposed features 41 | 42 | % Sum of Qdiff 43 | X(i,13) = log10(sum(abs(QDiff))); 44 | 45 | % Sum of Qdiff^2 46 | X(i,14) = log10(sum(QDiff.^2)); 47 | 48 | % Energy difference 49 | E10 = trapz(batch(i).cycles(10).Qdlin,batch(i).Vdlin); 50 | E95 = trapz(batch(i).cycles(95).Qdlin,batch(i).Vdlin); 51 | X(i,15) = log10(E10-E95); 52 | 53 | end %end loop through batteries 54 | 55 | 56 | end -------------------------------------------------------------------------------- /build_battery_features98.m: -------------------------------------------------------------------------------- 1 | function [ X ] = build_battery_features98( batch ) 2 | %This function builds the possible feature set for the model 3 | %**This model is built for batch oed1, which was only cycled to 98 cycles 4 | %If you add candidate features, update this number for better memory 5 | %management 6 | numFeat = 15; 7 | 8 | numBat = size(batch,2); 9 | X = zeros(numBat,numFeat); 10 | 11 | for i = 1:numBat 12 | %Capacity features 13 | %Initial capacity 14 | X(i,1) = batch(i).summary.QDischarge(2); 15 | %Max change in capacity 16 | X(i,2) = max(batch(i).summary.QDischarge(1:98))... 17 | - batch(i).summary.QDischarge(2); 18 | %capacity at cycle 98 19 | X(i,3) = batch(i).summary.QDischarge(98); 20 | 21 | %Linear fit of Q v. N 22 | R3 = regress(batch(i).summary.QDischarge(2:98),[2:98;ones(1,length(2:98))]'); 23 | X(i,4) = R3(1); 24 | X(i,5) = R3(2); 25 | 26 | %Linear fit of Q v. N, only last 10 cycles 27 | R1 = regress(batch(i).summary.QDischarge(91:98),[91:98;ones(1,length(91:98))]'); 28 | X(i,6) = R1(1); 29 | X(i,7) = R1(2); 30 | 31 | %Q features 32 | QDiff = batch(i).cycles(98).Qdlin - batch(i).cycles(10).Qdlin; 33 | 34 | X(i,8) = log10(abs(min(QDiff))); 35 | X(i,9) = log10(abs(mean(QDiff))); 36 | X(i,10) = log10(abs(var(QDiff))); 37 | X(i,11) = log10(abs(skewness(QDiff))); 38 | X(i,12) = log10(abs(QDiff(1))); 39 | 40 | % Peter's proposed features 41 | 42 | % Sum of Qdiff 43 | X(i,13) = log10(sum(abs(QDiff))); 44 | 45 | % Sum of Qdiff^2 46 | X(i,14) = log10(sum(QDiff.^2)); 47 | 48 | % Energy difference 49 | E10 = trapz(batch(i).cycles(10).Qdlin,batch(i).Vdlin); 50 | E98 = trapz(batch(i).cycles(98).Qdlin,batch(i).Vdlin); 51 | X(i,15) = log10(E10-E98); 52 | 53 | end %end loop through batteries 54 | 55 | end -------------------------------------------------------------------------------- /cell_analysis.m: -------------------------------------------------------------------------------- 1 | function battery = cell_analysis(result_data, charging_algorithm, ... 2 | batch_date, csvpath) 3 | %% Initialize battery struct 4 | battery = struct('policy', ' ', 'policy_readable', ' ', 'barcode', ... 5 | ' ', 'channel_id', ' ','cycle_life', NaN,... 6 | 'cycles', struct('discharge_dQdV', [], 't', [], 'Qc', [], 'I', [], ... 7 | 'V', [], 'T', [], 'Qd', [], 'Qdlin', [], 'Tdlin',[]), ... 8 | 'summary', struct('cycle', [], 'QDischarge', [], 'QCharge', [], ... 9 | 'IR', [], 'Tmax', [], 'Tavg', [], 'Tmin', [], 'chargetime', []), ... 10 | 'Vdlin',[]); 11 | 12 | cd(csvpath) 13 | 14 | %% Fix error in oed_2 where csvs have data from oed_1 15 | if strcmp(batch_date, '2018-09-06') 16 | if result_data(1,5) ~= 0 % if the first entry of cycle index ~= 0 17 | idx = find(result_data(:,5)==0,1); 18 | result_data = result_data(idx:end,:); % trim data 19 | end 20 | end 21 | 22 | %% Extract columns of interest 23 | 24 | % Total test time 25 | Total_time = result_data(:,1); 26 | % Unix date time (currently unused) 27 | %Date_time = result_data(:,2); 28 | 29 | % Cycle index, 0 is first discharge cycle 30 | Cycle_Index = result_data(:,5); 31 | % Extract all columns of interest 32 | Voltage = result_data(:,7); 33 | Current = result_data(:,6); 34 | Charge_Capacity = result_data(:,8); 35 | Discharge_Capacity = result_data(:,9); 36 | Internal_Resistance = result_data(:,13); 37 | Temperature = result_data(:,14); 38 | % Cell temp is 14, Shelf is 15 and 16 39 | 40 | % if batch1 or batch4, skip cycle 1 data 41 | if strcmp(batch_date, '2017-05-12') || strcmp(batch_date, '2017-12-04') || strcmp(batch_date, '2019-01-24') 42 | start = 2; 43 | else 44 | start = 1; 45 | end 46 | 47 | % Pre-initialize vectors 48 | C_in = zeros(max(Cycle_Index) - start - 1,1); 49 | C_out = zeros(max(Cycle_Index) - start - 1,1); 50 | T_max = zeros(max(Cycle_Index) - start - 1,1); 51 | T_min = zeros(max(Cycle_Index) - start - 1,1); 52 | T_avg = zeros(max(Cycle_Index) - start - 1,1); 53 | DQ = zeros(max(Cycle_Index) - start - 1,1); 54 | CQ = zeros(max(Cycle_Index) - start - 1,1); 55 | IR_CC1 = zeros(max(Cycle_Index) - start - 1,1); 56 | tt_80 = zeros(max(Cycle_Index) - start - 1,1); 57 | 58 | % Parse battery.policy into a "readable" policy, battery.policy_readable 59 | t = charging_algorithm; 60 | battery.policy = t; 61 | if datetime(batch_date) < datetime('2018-06-04') % pre-oed 62 | t = strrep(t, 'C_','C('); 63 | t = strrep(t, '_' , '.' ); 64 | t = strrep(t, 'PER.' , '%)-' ); 65 | t = strrep(t, '(NEWSTRUCTURE','-newstructure'); % new to batch6 66 | t = strrep(t, 'VARCHARGE.','VarCharge-'); % new to batch6 67 | t = strrep(t, '(100CYCLES','-100cycles'); % new to batch6 68 | % For 3-step policies: 69 | % Replace '6C(20%)-0.1C20.1%)-5C' with '6C(20%)-0.1C(20.1%)-5C' 70 | matchStr = regexp(t,'C\d','match'); 71 | if ~isempty(matchStr) 72 | matchStr = matchStr{1}; 73 | c2 = matchStr(2:end); 74 | t = regexprep(t,'C\d',['C(' c2]); 75 | end 76 | else 77 | t = strrep(t, '_' , '-' ); 78 | t = strrep(t, 'pt' , '.' ); 79 | t = strrep(t, 'PT' , '.' ); 80 | end 81 | battery.policy_readable = t; 82 | 83 | thisdir = cd; 84 | 85 | %% Go through every cycle except current running one 86 | cycle_indices2 = unique(Cycle_Index); 87 | cycle_indices2 = cycle_indices2(2:end-1); 88 | for k = 1:length(cycle_indices2) 89 | j = cycle_indices2(k); 90 | cycle_indices = find(Cycle_Index == j); 91 | cycle_start = cycle_indices(1); 92 | cycle_end = cycle_indices(end); 93 | 94 | %% Add full per-cycle information 95 | battery.cycles(k).Qd = Discharge_Capacity(cycle_indices); 96 | battery.cycles(k).Qc = Charge_Capacity(cycle_indices); 97 | battery.cycles(k).V = Voltage(cycle_indices); 98 | battery.cycles(k).T = Temperature(cycle_indices); 99 | battery.cycles(k).t = (Total_time(cycle_indices) - Total_time(cycle_start))./60; 100 | battery.cycles(k).I = Current(cycle_indices)/1.1; 101 | 102 | %% Correct for negative times from Patrick's script 103 | if battery.cycles(k).t(end) < 0 104 | negidx = find(battery.cycles(k).t < 0, 1); 105 | constant = battery.cycles(k).t(negidx - 1) - battery.cycles(k).t(negidx); 106 | battery.cycles(k).t(negidx:end) = battery.cycles(k).t(negidx:end) + constant; 107 | end 108 | 109 | %% dQdV vs V for discharge 110 | % Indices of discharging portion of the cycle 111 | discharge_indices = find(battery.cycles(k).I < 0); 112 | % In case i3 is empty 113 | if isempty(discharge_indices) 114 | discharge_start = 1; discharge_end = 2; 115 | else 116 | discharge_start = discharge_indices(1); 117 | discharge_end = discharge_indices(end); 118 | end 119 | 120 | [IDC,~] = IDCA( battery.cycles(k).Qd(discharge_start:discharge_end), ... 121 | battery.cycles(k).V(discharge_start:discharge_end) ); 122 | battery.cycles(k).discharge_dQdV = IDC'; 123 | 124 | %% Apply VQlinspace3 function to obtain Qdlin, Vdlin, and Tdlin 125 | [Qdlin,Vdlin,Tdlin] = VQlinspace3(battery.cycles(k)); 126 | battery.cycles(k).Qdlin = Qdlin; 127 | battery.cycles(k).Tdlin = Tdlin; 128 | 129 | %% Update summary information 130 | C_in(k) = max(battery.cycles(k).Qc); 131 | C_out(k) = max(battery.cycles(k).Qd); 132 | T_max(k) = max(battery.cycles(k).T); 133 | T_min(k) = min(battery.cycles(k).T); 134 | T_avg(k) = mean(battery.cycles(k).T); 135 | IR_CC1(k) = Internal_Resistance(cycle_end); 136 | 137 | %% Find time to 80% 138 | chargetime_indices = find(Charge_Capacity(cycle_start:cycle_end) >= 0.88,2); 139 | if isempty(chargetime_indices) || length(chargetime_indices) == 1 140 | tt_80(k) = 1200; 141 | else 142 | tt_80(k) = Total_time(chargetime_indices(2)+cycle_start)-Total_time(cycle_start); 143 | Total_time(chargetime_indices + cycle_start); 144 | Total_time(cycle_start); 145 | end 146 | % In case of an incomplete charge 147 | if tt_80(k)<300 148 | tt_80(k) = tt_80(k-1); 149 | end 150 | end 151 | 152 | % Update static voltage variables 153 | battery.Vdlin = Vdlin'; 154 | 155 | % Export charge capacity and correct if errant charge 156 | %if j > 5 157 | %[~, ind] = sort(C_in,'descend'); 158 | %maxValueIndices = ind(1:5); 159 | %median(C_in(maxValueIndices)); 160 | CQ = C_in; 161 | DQ = C_out; 162 | %end 163 | 164 | % Add vectors to battery.summary 165 | battery.summary.cycle = (1:k)'; 166 | battery.summary.QDischarge = DQ; 167 | battery.summary.QCharge = CQ; 168 | battery.summary.IR = IR_CC1; 169 | battery.summary.Tmax = T_max; 170 | battery.summary.Tavg = T_avg; 171 | battery.summary.Tmin = T_min; 172 | battery.summary.chargetime = tt_80./60; % Convert to minutes 173 | 174 | % Update cycle life, if applicable 175 | batches_cycleto80 = {'2017-05-12', '2017-06-30', '2018-04-12', '2019-01-24'}; 176 | if battery.summary.QDischarge(end) < 0.88 177 | % Confirm point is not a fluke 178 | lower_than_threshold = find(battery.summary.QDischarge<0.88); 179 | for idx = 1:length(lower_than_threshold) 180 | % test to see if next discharge point is lower than this point 181 | if battery.summary.QDischarge(lower_than_threshold(idx)+1) < ... 182 | battery.summary.QDischarge(lower_than_threshold(idx)) 183 | battery.cycle_life = lower_than_threshold(idx); 184 | break; 185 | end 186 | end 187 | elseif sum(strcmp(batch_date,batches_cycleto80)) && ... 188 | battery.summary.QDischarge(end) - 0.88 < 0.01 189 | % Special case for batches 1 and 8, where we don't cycle past failure 190 | battery.cycle_life = j + 1; 191 | end 192 | 193 | cd(thisdir) 194 | end 195 | -------------------------------------------------------------------------------- /cell_analysis_batch0.m: -------------------------------------------------------------------------------- 1 | function battery = cell_analysis(result_data, charging_algorithm, ... 2 | batch_date, csvpath) 3 | 4 | %% Initialize battery struct 5 | battery = struct('policy', ' ', 'policy_readable', ' ', 'barcode', ... 6 | ' ', 'channel_id', ' ', 'cycles', struct('discharge_dQdVvsV', ... 7 | struct('V', [], 'dQdV', []), 't', [], 'Qc', [], 'I', [],'V', [], ... 8 | 'T', [], 'Qd', [], 'Q', []), 'summary', struct('cycle', [], ... 9 | 'QDischarge', [], 'QCharge', [], 'IR', [], 'Tmax', [], 'Tavg', ... 10 | [], 'Tmin', [], 'chargetime', [])); 11 | 12 | cd(csvpath) 13 | 14 | % Total Test time 15 | Total_time = result_data(:,1); 16 | % Unix Date Time 17 | Date_time = result_data(:,2); 18 | 19 | % Cycle index, 0 is formation cycle 20 | Cycle_Index = result_data(:,5); 21 | % All Voltage, current, charge capacity, internal resistance, 22 | % and temperature variables 23 | VoltageV = result_data(:,7); 24 | Current = result_data(:,6); 25 | Charge_CapacityAh = result_data(:,8); 26 | Discharge_CapacityAh = result_data(:,9); 27 | % Internal_Resistance = result_data(:,13); 28 | % TemperatureT1 = result_data(:,14); 29 | % batch0 attempts 30 | Internal_Resistance = ones(length(result_data(:,1)),1); 31 | TemperatureT1 = ones(length(result_data(:,1)),1); 32 | 33 | % Cell temp is 14, Shelf is 15 and 16 34 | % Initialize Vector of capacity in and out, maximum temperature, 35 | % and discharge dQdV 36 | C_in = []; 37 | C_out = []; 38 | tmax = []; 39 | dDQdV = []; 40 | 41 | % Init whats needed for saving struct 42 | DQ = []; 43 | CQ = []; 44 | IR_CC1 = []; 45 | tmax = []; 46 | t_avg = []; 47 | tmin = []; 48 | tt_80 = []; 49 | 50 | % Translate charging algorithm to something we can put in a legend. 51 | t = charging_algorithm; 52 | battery.policy = t; 53 | t = strrep(t, '_' , '.' ); 54 | t = strrep(t, '-' , '(' ); 55 | t = strrep(t, 'per.' , '%)-' ); 56 | battery.policy_readable = t; 57 | 58 | thisdir = cd; 59 | 60 | % if batch1, skip cycle 1 data, and add all cycles, including last to 61 | % struct 62 | if strcmp(batch_date, '2017-05-12') 63 | %x = 0; 64 | start = 2; 65 | else 66 | %x = 1; 67 | start = 1; 68 | end 69 | 70 | %% Go through every cycle except current running one 71 | for j = start:max(Cycle_Index) - 1 72 | cycle_indices = find(Cycle_Index == j); 73 | cycle_start = cycle_indices(1); 74 | cycle_end = cycle_indices(end); 75 | % Time in the cycle 76 | cycle_time = Total_time(cycle_start:cycle_end) - ... 77 | Total_time(cycle_start); 78 | % Voltage of Cycle J 79 | Voltage = VoltageV(cycle_start:cycle_end); 80 | % Current values for cycle J 81 | Current_J = Current(cycle_start:cycle_end); 82 | % Charge Capacity for the cycle 83 | Charge_cap = Charge_CapacityAh(cycle_start:cycle_end); 84 | % Discharge Capacity for the cycle 85 | Discharge_cap = Discharge_CapacityAh(cycle_start:cycle_end); 86 | % Temperature of the cycle. 87 | temp = TemperatureT1(cycle_start:cycle_end); 88 | 89 | %{ 90 | % Index of any charging portion 91 | charge_indices = find(Current(cycle_start:cycle_end) >= 0); % todo: > or >= ? 92 | charge_start = charge_indices(1); charge_end = charge_indices(end); 93 | %} 94 | % Index of discharging portion of the cycle 95 | discharge_indices = find(Current(cycle_start:cycle_end) < 0); 96 | % In case i3 is empty 97 | if isempty(discharge_indices) 98 | discharge_start = 1; discharge_end = 2; 99 | else 100 | discharge_start = discharge_indices(1); 101 | discharge_end = discharge_indices(end); 102 | end 103 | 104 | 105 | 106 | % record discharge dQdV vs V 107 | [IDC,xVoltage2] = IDCA(Discharge_cap(discharge_start:discharge_end), ... 108 | Voltage(discharge_start:discharge_end)); 109 | battery.cycles(j).discharge_dQdVvsV.V = xVoltage2; 110 | battery.cycles(j).discharge_dQdVvsV.dQdV = IDC; 111 | 112 | % record Qd 113 | battery.cycles(j).Qd = Discharge_cap; 114 | 115 | % add Qc and V to batch 116 | battery.cycles(j).Qc = Charge_cap; 117 | battery.cycles(j).V = Voltage; 118 | 119 | % add T to batch 120 | battery.cycles(j).T = temp; 121 | 122 | % add t and C to batch 123 | battery.cycles(j).t = cycle_time./60; 124 | battery.cycles(j).I = Current_J/1.1; 125 | 126 | 127 | battery.cycles(j).Q = Charge_cap - Discharge_cap; 128 | 129 | %% Add Cycle Legend 130 | C_in(j) = max(Charge_cap); 131 | C_out(j) = max(Discharge_cap); 132 | tmax(j) = max(temp); 133 | tmin(j) = min(temp); 134 | t_avg(j) = mean(temp); 135 | IR_CC1(j) = Internal_Resistance(cycle_end); 136 | 137 | %% Find Time to 80% 138 | discharge_indices = find(Charge_CapacityAh(cycle_start:cycle_end) >= .88,2); 139 | if isempty(discharge_indices) || length(discharge_indices) == 1 140 | tt_80(j) = 1200; 141 | else 142 | tt_80(j) = Total_time(discharge_indices(2)+cycle_start)-Total_time(cycle_start); 143 | Total_time(discharge_indices + cycle_start); 144 | Total_time(cycle_start); 145 | end 146 | % In case an incomplete charge 147 | if tt_80(j)<300 148 | tt_80(j) = tt_80(j-1); 149 | end 150 | end 151 | 152 | % Export Charge Capacity and correct if errant charge 153 | if j > 5 154 | [~, ind] = sort(C_in,'descend'); 155 | maxValueIndices = ind(1:5); 156 | median(C_in(maxValueIndices)); 157 | CQ = C_in; 158 | DQ = C_out; 159 | end 160 | 161 | % ADDED 162 | battery.summary.cycle = 1:j; 163 | battery.summary.QDischarge = DQ; 164 | battery.summary.QCharge = CQ; 165 | battery.summary.IR = IR_CC1; 166 | battery.summary.Tmax = tmax; 167 | battery.summary.Tavg = t_avg; 168 | battery.summary.Tmin = tmin; 169 | battery.summary.chargetime = tt_80./60; 170 | % ADDED 171 | 172 | cd(thisdir) 173 | end -------------------------------------------------------------------------------- /columnlegend.m: -------------------------------------------------------------------------------- 1 | function [legend_h,object_h,plot_h,text_strings] = columnlegend(numcolumns, str, varargin) 2 | % 3 | % columnlegend creates a legend with a specified number of columns. 4 | % 5 | % columnlegend(numcolumns, str, varargin) 6 | % numcolumns - number of columns in the legend 7 | % str - cell array of strings for the legend 8 | % 9 | % Additional Input Options: 10 | % columnlegend(..., 'Location', loc) 11 | % loc - location variable for legend, default is 'NorthEast' 12 | % possible values: 'NorthWest', 'NorthEast', 'SouthEast', 'SouthWest', 13 | % 'NorthOutside', 'SouthOutside', 14 | % 'NortheastOutside', 'SoutheastOutside' 15 | % 16 | % columnlegend(numcolumns, str, 'padding', 0.5); % add 50% vertical padding between legend entries (relative to original height) 17 | % 18 | % columnlegend(..., 'boxon') 19 | % columnlegend(..., 'boxoff') 20 | % set legend bounding box on/off 21 | % 22 | % example: 23 | % plot(bsxfun(@times, [0:9]',[1:10])); 24 | % columnlegend(3, cellstr(num2str([1:10]')), 'location','northwest'); 25 | % 26 | % 27 | % Author: Simon Henin 28 | % 29 | % 4/09/2013 - Fixed bug with 3 entries / 3 columns 30 | % 4/09/2013 - Added bounding box option as per @Durga Lal Shrestha (fileexchage) 31 | % 11 May 2010 - 1.2 Add instructions for printing figure with columns 32 | % 08 Feb 2011 - 1.4 Added functionality when using markers. 33 | % 31 Oct 2015 - Updates for compatibility with 2015a, Adds minor improvements as per user suggestions 34 | % 07 Nov 2016 - Bug fixes, added functionality for bar plots, added all valid legend locations 35 | % 07 Jun 2017 - Added a quick padding feature that creates additional 36 | % vertical space between legend entries. 37 | 38 | 39 | location = 'NorthEast'; 40 | boxon = false; legend_h = false; padding = 0; 41 | for i=1:2:length(varargin), 42 | switch lower(varargin{i}) 43 | case 'location' 44 | location = varargin{i+1}; 45 | i=i+2; 46 | case 'boxon' 47 | boxon = true; 48 | case 'boxoff' 49 | boxon = false; 50 | case 'legend' 51 | legend_h = varargin{i+1}; 52 | i=i+2; 53 | case 'object' 54 | object_h = varargin{i+1}; 55 | i=i+2; 56 | case 'padding' 57 | padding = varargin{i+1}; 58 | i=i+2; 59 | end 60 | end 61 | 62 | if legend_h == false, 63 | %create the legend 64 | [legend_h,object_h,plot_h,text_strings] = legend(str); 65 | end 66 | 67 | %some variables 68 | numlines = length(str); 69 | numpercolumn = ceil(numlines/numcolumns); 70 | 71 | %get old width, new width and scale factor 72 | set(legend_h, 'units', 'normalized'); 73 | set(gca, 'units', 'normalized'); 74 | 75 | pos = get(legend_h, 'position'); 76 | width = numcolumns*pos(3); 77 | newheight = (pos(4)/numlines)*numpercolumn; 78 | rescale = pos(3)/width; 79 | 80 | 81 | %get some old values so we can scale everything later 82 | type = get(object_h(numlines+1), 'type'); 83 | switch type, 84 | case {'line'} 85 | xdata = get(object_h(numlines+1), 'xdata'); 86 | ydata1 = get(object_h(numlines+1), 'ydata'); 87 | ydata2 = get(object_h(numlines+3), 'ydata'); 88 | 89 | %we'll use these later to align things appropriately 90 | sheight = ydata1(1)-ydata2(1) + (ydata1(1)-ydata2(1))*padding; % height between data lines 91 | height = ydata1(1) + (ydata1(1)-ydata2(1))*padding*2; % height of the box. Used to top margin offset 92 | line_width = (xdata(2)-xdata(1))*rescale; % rescaled linewidth to match original 93 | spacer = xdata(1)*rescale; % rescaled spacer used for margins 94 | case {'hggroup'} 95 | text_pos = get(object_h(1), 'position'); 96 | child = get(object_h(numlines+1), 'children'); 97 | vertices_1 = get(child, 'vertices'); 98 | child = get(object_h(numlines+2), 'children'); 99 | vertices_2 = get(child, 'vertices'); 100 | sheight = vertices_1(2,2)-vertices_1(1,2); 101 | height = vertices_1(2,2); 102 | line_width = (vertices_1(3,1)-vertices_1(1,1))*rescale; % rescaled linewidth to match original 103 | spacer = vertices_1(1,2)-vertices_2(2,2); % rescaled spacer used for margins 104 | text_space = (text_pos(1)-vertices_1(4,1))./numcolumns; 105 | end 106 | 107 | 108 | %put the legend on the upper left corner to make initial adjustments easier 109 | % set(gca, 'units', 'pixels'); 110 | loci = get(gca, 'position'); 111 | set(legend_h, 'position', [loci(1) pos(2) width pos(4)]); 112 | 113 | col = -1; 114 | for i=1:numlines, 115 | if (mod(i,numpercolumn)==1 || (numpercolumn == 1)), 116 | col = col+1; 117 | end 118 | 119 | if i==1 120 | linenum = i+numlines; 121 | else 122 | if strcmp(type, 'line'), 123 | linenum = linenum+2; 124 | else 125 | linenum = linenum+1; 126 | end 127 | end 128 | labelnum = i; 129 | 130 | position = mod(i,numpercolumn); 131 | if position == 0, 132 | position = numpercolumn; 133 | end 134 | 135 | switch type, 136 | case {'line'} 137 | %realign the labels 138 | set(object_h(linenum), 'ydata', [(height-(position-1)*sheight) (height-(position-1)*sheight)]); 139 | set(object_h(linenum), 'xdata', [col/numcolumns+spacer col/numcolumns+spacer+line_width]); 140 | 141 | set(object_h(linenum+1), 'ydata', [height-(position-1)*sheight height-(position-1)*sheight]); 142 | set(object_h(linenum+1), 'xdata', [col/numcolumns+spacer*3.5 col/numcolumns+spacer*3.5]); 143 | 144 | set(object_h(labelnum), 'position', [col/numcolumns+spacer*2+line_width height-(position-1)*sheight]); 145 | case {'hggroup'}, 146 | child = get(object_h(linenum), 'children'); 147 | v = get(child, 'vertices'); 148 | %x-positions 149 | v([1:2 5],1) = col/numcolumns+spacer; 150 | v(3:4,1) = col/numcolumns+spacer+line_width; 151 | % y-positions 152 | v([1 4 5],2) = (height-(position-1)*sheight-(position-1)*spacer); 153 | v([2 3], 2) = v(1,2)+sheight; 154 | set(child, 'vertices', v); 155 | set(object_h(labelnum), 'position', [v(3,1)+text_space v(1,2)+(v(2,2)-v(1,2))/2 v(3,1)-v(1,1)]); 156 | end 157 | 158 | end 159 | 160 | %unfortunately, it is not possible to force the box to be smaller than the 161 | %original height, therefore, turn it off and set background color to none 162 | %so that it no longer appears 163 | set(legend_h, 'Color', 'None', 'Box', 'off'); 164 | 165 | %let's put it where you want it 166 | fig_pos = get(gca, 'position'); 167 | pos = get(legend_h, 'position'); 168 | padding = 0.01; % padding, in normalized units 169 | % if location is some variation on south, then we need to take into account 170 | % the new height 171 | if strfind(location, 'south'), 172 | h_diff = pos(4)-newheight; 173 | pos(4) = newheight; 174 | end 175 | switch lower(location), 176 | case {'northeast'} 177 | set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3)-padding pos(2) pos(3) pos(4)]); 178 | case {'northwest'} 179 | set(legend_h, 'position', [pos(1)+padding pos(2) pos(3) pos(4)]); 180 | case {'southeast'} 181 | pos(4) = newheight; 182 | set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3)-padding fig_pos(2)-pos(4)/2+pos(4)/4 pos(3) pos(4)]); 183 | case {'southwest'} 184 | set(legend_h, 'position', [fig_pos(1)+padding fig_pos(2)-pos(4)/2+pos(4)/4 pos(3) pos(4)]); 185 | case {'northeastoutside'} 186 | % need to resize axes to allow legend to fit in figure window 187 | set(gca, 'position', [fig_pos]-[0 0 pos(3) 0]); 188 | set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3) pos(2) pos(3) pos(4)]); 189 | case {'northwestoutside'} 190 | % need to resize axes to allow legend to fit in figure window 191 | set(gca, 'position', [fig_pos]+[pos(3) 0 -pos(3) 0]); 192 | set(legend_h, 'position', [fig_pos(1)-fig_pos(3)*.1 pos(2) pos(3) pos(4)]); % -10% figurewidth to account for axis labels 193 | case {'north'} 194 | % need to resize axes to allow legend to fit in figure window 195 | set(legend_h, 'position', [fig_pos(1)+fig_pos(3)/2-pos(3)/2 fig_pos(2)+(fig_pos(4)-pos(4))-padding pos(3) pos(4)]); 196 | case {'northoutside'} 197 | % need to resize axes to allow legend to fit in figure window 198 | set(gca, 'position', [fig_pos]-[0 0 0 pos(4)]); 199 | set(legend_h, 'position', [fig_pos(1)+fig_pos(3)/2-pos(3)/2 fig_pos(2)+(fig_pos(4)-pos(4)) pos(3) pos(4)]); 200 | case {'south'} 201 | y_pos = fig_pos(2)-h_diff+pos(4); 202 | set(legend_h, 'position', [fig_pos(1)+fig_pos(3)/2-pos(3)/2 y_pos pos(3) pos(4)]); 203 | case {'southoutside'} 204 | % need to resize axes to allow legend to fit in figure window 205 | set(gca, 'position', [fig_pos]-[0 -pos(4) 0 pos(4)]); 206 | set(legend_h, 'position', [fig_pos(1)+fig_pos(3)/2-pos(3)/2 fig_pos(2)-pos(4)-pos(3)*0.1 pos(3) pos(4)]); 207 | case {'eastoutside'} 208 | % need to resize axes to allow legend to fit in figure window 209 | set(gca, 'position', [fig_pos]-[0 0 pos(3) 0]); 210 | set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3) fig_pos(2)+fig_pos(4)/2-pos(4)/2 pos(3) pos(4)]); 211 | case {'southeastoutside'} 212 | % need to resize axes to allow legend to fit in figure window 213 | set(gca, 'position', [fig_pos]-[0 0 pos(3) 0]); 214 | set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3) fig_pos(2)-pos(4)/4 pos(3) pos(4)]); 215 | case {'westoutside'} 216 | % need to resize axes to allow legend to fit in figure window 217 | set(gca, 'position', [fig_pos]+[pos(3) 0 -pos(3) 0]); 218 | set(legend_h, 'position', [fig_pos(1)-fig_pos(3)*.1 fig_pos(2)+fig_pos(4)/2-pos(4)/2 pos(3) pos(4)]); % -10% figurewidth to account for axis labels 219 | case {'southwestoutside'} 220 | % need to resize axes to allow legend to fit in figure window 221 | set(gca, 'position', [fig_pos]+[pos(3) 0 -pos(3) 0]); 222 | set(legend_h, 'position', [fig_pos(1)-fig_pos(3)*.1 fig_pos(2)-pos(4)/4 pos(3) pos(4)]); % -10% figurewidth to account for axis labels 223 | end 224 | 225 | % display box around legend 226 | if boxon, 227 | drawnow; % make sure everyhting is drawn in place first. 228 | % set(legend_h, 'units', 'normalized'); 229 | pos = get(legend_h, 'position'); 230 | orgHeight = pos(4); 231 | pos(4) = (orgHeight/numlines)*numpercolumn; 232 | pos(2)=pos(2) + orgHeight-pos(4) - pos(4)*0.05; 233 | pos(1) = pos(1)+pos(1)*0.01; 234 | annotation('rectangle',pos, 'linewidth', 1) 235 | end 236 | 237 | % re-set to normalized so that things scale properly 238 | set(legend_h, 'units', 'normalized'); 239 | set(gca, 'units', 'normalized'); -------------------------------------------------------------------------------- /delete_bad_csvs.m: -------------------------------------------------------------------------------- 1 | %% Delete bad csvs %% 2 | % Removes tests with major experimental issues (i.e. didn't start). 3 | % Manually added 4 | if strcmp(batch_name, 'batch1') 5 | delete([path.csv_data '\' '2017-05-12_3_6C-80per_3_6C_CH4.csv']) 6 | delete([path.csv_data '\' '2017-05-12_3_6C-80per_3_6C_CH4_Metadata.csv']') 7 | delete([path.csv_data '\' '2017-05-12_4_4C-80per_4_4C_CH8.csv']) 8 | delete([path.csv_data '\' '2017-05-12_4_4C-80per_4_4C_CH8_Metadata.csv']') 9 | elseif strcmp(batch_name, 'batch2') 10 | delete([path.csv_data '\' '2017-06-30_CH14.csv']) 11 | delete([path.csv_data '\' '2017-06-30_CH14_Metadata.csv']') 12 | elseif strcmp(batch_name, 'batch3') 13 | delete([path.csv_data '\' '2017-08-14_2C-5per_3_8C_CH4.csv']) 14 | delete([path.csv_data '\' '2017-08-14_2C-5per_3_8C_CH4_Metadata.csv']') 15 | elseif strcmp(batch_name, 'batch4') 16 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH4.csv']); 17 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH4_Metadata.csv']); 18 | delete([path.csv_data '\' '2017-12-04_6C-10per_5c-76_7per_2C_CH25.csv']); 19 | delete([path.csv_data '\' '2017-12-04_6C-10per_5c-76_7per_2C_CH25_Metadata.csv']); 20 | % Error with sql2csv converter 21 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH14.csv']); 22 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH14_Metadata.csv']); 23 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH24.csv']); 24 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH24_Metadata.csv']); 25 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH36.csv']); 26 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH36_Metadata.csv']); 27 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH42.csv']); 28 | delete([path.csv_data '\' '2017-12-04_4_65C-44per_5C_CH42_Metadata.csv']); 29 | elseif strcmp(batch_name, 'batch5') 30 | delete([path.csv_data '\' '2018-01-18_batch5_CH41.csv']); 31 | delete([path.csv_data '\' '2018-01-18_batch5_CH41_Metadata.csv']); 32 | elseif strcmp(batch_name, 'batch7') 33 | delete([path.csv_data '\' '2018-02-20_batch7_CH26.csv']); 34 | delete([path.csv_data '\' '2018-02-20_batch7_CH26_Metadata.csv']); 35 | elseif strcmp(batch_name, 'batch8') 36 | delete([path.csv_data '\' '2018-04-12_batch8_CH26.csv']); 37 | delete([path.csv_data '\' '2018-04-12_batch8_CH26_Metadata.csv']); 38 | elseif strcmp(batch_name, 'oed4') 39 | delete([path.csv_data '\' '2018-07-29_OED4_CH17.csv']); 40 | delete([path.csv_data '\' '2018-07-29_OED4_CH17_Metadata.csv']); 41 | delete([path.csv_data '\' '2018-07-29_OED4_CH27.csv']); 42 | delete([path.csv_data '\' '2018-07-29_OED4_CH27_Metadata.csv']); 43 | elseif strcmp(batch_name, 'oed_0') 44 | delete([path.csv_data '\' '2018-08-28_oed_0_CH17.csv']); 45 | delete([path.csv_data '\' '2018-08-28_oed_0_CH17_Metadata.csv']); 46 | delete([path.csv_data '\' '2018-08-28_oed_0_CH27.csv']); 47 | delete([path.csv_data '\' '2018-08-28_oed_0_CH27_Metadata.csv']); 48 | elseif strcmp(batch_name, 'disassembly_batch') 49 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH5.csv']); 50 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH5_Metadata.csv']); 51 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH39.csv']); 52 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH39_Metadata.csv']); 53 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH45.csv']); 54 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH45_Metadata.csv']); 55 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH46.csv']); 56 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH46_Metadata.csv']); 57 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH47.csv']); 58 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH47_Metadata.csv']); 59 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH48.csv']); 60 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH48_Metadata.csv']); 61 | 62 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv2_CH42.csv']); 63 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv2_CH42_Metadata.csv']); 64 | 65 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv2_CH43.csv']); 66 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv2_CH43_Metadata.csv']); 67 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv2_CH44.csv']); 68 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv2_CH44_Metadata.csv']); 69 | 70 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH38.csv']); 71 | delete([path.csv_data '\' '2018-10-02_disassembly_batch_CH38_Metadata.csv']); 72 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv3_CH43.csv']); 73 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv3_CH43_Metadata.csv']); 74 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv3_CH44.csv']); 75 | delete([path.csv_data '\' '2018-10-02_disassembly_batchv3_CH44_Metadata.csv']); 76 | elseif strcmp(batch_name, 'disassembly_batch2') 77 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH27.csv']); 78 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH27_Metadata.csv']); 79 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH28.csv']); 80 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH28_Metadata.csv']); 81 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH29.csv']); 82 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH29_Metadata.csv']); 83 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH30.csv']); 84 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH30_Metadata.csv']); 85 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH31.csv']); 86 | delete([path.csv_data '\' '2018-12-05_M1B_rate_lifetime_CH31_Metadata.csv']); 87 | elseif strcmp(batch_name, 'batch9') 88 | delete([path.csv_data '\' '2019-01-24_batch9_CH46.csv']); 89 | delete([path.csv_data '\' '2019-01-24_batch9_CH46_Metadata.csv']); 90 | 91 | % Pull in batch9pt2 and rename ch48 92 | batch_date = '2019-01-29'; 93 | aws_pulldata = ['aws s3 sync s3://matr.io/experiment/d3batt D:\Data --exclude "*" --include "' batch_date '*"']; 94 | system(aws_pulldata) 95 | delete([path.csv_data '\' '2019-01-29_batch9pt2_CH5.csv']); 96 | delete([path.csv_data '\' '2019-01-29_batch9pt2_CH5_Metadata.csv']); 97 | cd(path.csv_data) 98 | movefile '2019-01-29_batch9pt2_CH48.csv' '2019-01-24_batch9_CH48.csv' 99 | movefile '2019-01-29_batch9pt2_CH48_Metadata.csv' '2019-01-24_batch9_CH48_Metadata.csv' 100 | batch_date = '2019-01-24'; 101 | cd(path.code) 102 | elseif strcmp(batch_name, 'batch9pt2') 103 | delete([path.csv_data '\' '2019-01-29_batch9pt2_CH5.csv']); 104 | delete([path.csv_data '\' '2019-01-29_batch9pt2_CH5_Metadata.csv']); 105 | end -------------------------------------------------------------------------------- /distinguishable_colors.m: -------------------------------------------------------------------------------- 1 | function colors = distinguishable_colors(n_colors,bg,func) 2 | % DISTINGUISHABLE_COLORS: pick colors that are maximally perceptually distinct 3 | % 4 | % When plotting a set of lines, you may want to distinguish them by color. 5 | % By default, Matlab chooses a small set of colors and cycles among them, 6 | % and so if you have more than a few lines there will be confusion about 7 | % which line is which. To fix this problem, one would want to be able to 8 | % pick a much larger set of distinct colors, where the number of colors 9 | % equals or exceeds the number of lines you want to plot. Because our 10 | % ability to distinguish among colors has limits, one should choose these 11 | % colors to be "maximally perceptually distinguishable." 12 | % 13 | % This function generates a set of colors which are distinguishable 14 | % by reference to the "Lab" color space, which more closely matches 15 | % human color perception than RGB. Given an initial large list of possible 16 | % colors, it iteratively chooses the entry in the list that is farthest (in 17 | % Lab space) from all previously-chosen entries. While this "greedy" 18 | % algorithm does not yield a global maximum, it is simple and efficient. 19 | % Moreover, the sequence of colors is consistent no matter how many you 20 | % request, which facilitates the users' ability to learn the color order 21 | % and avoids major changes in the appearance of plots when adding or 22 | % removing lines. 23 | % 24 | % Syntax: 25 | % colors = distinguishable_colors(n_colors) 26 | % Specify the number of colors you want as a scalar, n_colors. This will 27 | % generate an n_colors-by-3 matrix, each row representing an RGB 28 | % color triple. If you don't precisely know how many you will need in 29 | % advance, there is no harm (other than execution time) in specifying 30 | % slightly more than you think you will need. 31 | % 32 | % colors = distinguishable_colors(n_colors,bg) 33 | % This syntax allows you to specify the background color, to make sure that 34 | % your colors are also distinguishable from the background. Default value 35 | % is white. bg may be specified as an RGB triple or as one of the standard 36 | % "ColorSpec" strings. You can even specify multiple colors: 37 | % bg = {'w','k'} 38 | % or 39 | % bg = [1 1 1; 0 0 0] 40 | % will only produce colors that are distinguishable from both white and 41 | % black. 42 | % 43 | % colors = distinguishable_colors(n_colors,bg,rgb2labfunc) 44 | % By default, distinguishable_colors uses the image processing toolbox's 45 | % color conversion functions makecform and applycform. Alternatively, you 46 | % can supply your own color conversion function. 47 | % 48 | % Example: 49 | % c = distinguishable_colors(25); 50 | % figure 51 | % image(reshape(c,[1 size(c)])) 52 | % 53 | % Example using the file exchange's 'colorspace': 54 | % func = @(x) colorspace('RGB->Lab',x); 55 | % c = distinguishable_colors(25,'w',func); 56 | 57 | % Copyright 2010-2011 by Timothy E. Holy 58 | 59 | % Parse the inputs 60 | if (nargin < 2) 61 | bg = [1 1 1]; % default white background 62 | else 63 | if iscell(bg) 64 | % User specified a list of colors as a cell aray 65 | bgc = bg; 66 | for i = 1:length(bgc) 67 | bgc{i} = parsecolor(bgc{i}); 68 | end 69 | bg = cat(1,bgc{:}); 70 | else 71 | % User specified a numeric array of colors (n-by-3) 72 | bg = parsecolor(bg); 73 | end 74 | end 75 | 76 | % Generate a sizable number of RGB triples. This represents our space of 77 | % possible choices. By starting in RGB space, we ensure that all of the 78 | % colors can be generated by the monitor. 79 | n_grid = 30; % number of grid divisions along each axis in RGB space 80 | x = linspace(0,1,n_grid); 81 | [R,G,B] = ndgrid(x,x,x); 82 | rgb = [R(:) G(:) B(:)]; 83 | if (n_colors > size(rgb,1)/3) 84 | error('You can''t readily distinguish that many colors'); 85 | end 86 | 87 | % Convert to Lab color space, which more closely represents human 88 | % perception 89 | if (nargin > 2) 90 | lab = func(rgb); 91 | bglab = func(bg); 92 | else 93 | C = makecform('srgb2lab'); 94 | lab = applycform(rgb,C); 95 | bglab = applycform(bg,C); 96 | end 97 | 98 | % If the user specified multiple background colors, compute distances 99 | % from the candidate colors to the background colors 100 | mindist2 = inf(size(rgb,1),1); 101 | for i = 1:size(bglab,1)-1 102 | dX = bsxfun(@minus,lab,bglab(i,:)); % displacement all colors from bg 103 | dist2 = sum(dX.^2,2); % square distance 104 | mindist2 = min(dist2,mindist2); % dist2 to closest previously-chosen color 105 | end 106 | 107 | % Iteratively pick the color that maximizes the distance to the nearest 108 | % already-picked color 109 | colors = zeros(n_colors,3); 110 | lastlab = bglab(end,:); % initialize by making the "previous" color equal to background 111 | for i = 1:n_colors 112 | dX = bsxfun(@minus,lab,lastlab); % displacement of last from all colors on list 113 | dist2 = sum(dX.^2,2); % square distance 114 | mindist2 = min(dist2,mindist2); % dist2 to closest previously-chosen color 115 | [~,index] = max(mindist2); % find the entry farthest from all previously-chosen colors 116 | colors(i,:) = rgb(index,:); % save for output 117 | lastlab = lab(index,:); % prepare for next iteration 118 | end 119 | end 120 | 121 | function c = parsecolor(s) 122 | if ischar(s) 123 | c = colorstr2rgb(s); 124 | elseif isnumeric(s) && size(s,2) == 3 125 | c = s; 126 | else 127 | error('MATLAB:InvalidColorSpec','Color specification cannot be parsed.'); 128 | end 129 | end 130 | 131 | function c = colorstr2rgb(c) 132 | % Convert a color string to an RGB value. 133 | % This is cribbed from Matlab's whitebg function. 134 | % Why don't they make this a stand-alone function? 135 | rgbspec = [1 0 0;0 1 0;0 0 1;1 1 1;0 1 1;1 0 1;1 1 0;0 0 0]; 136 | cspec = 'rgbwcmyk'; 137 | k = find(cspec==c(1)); 138 | if isempty(k) 139 | error('MATLAB:InvalidColorString','Unknown color string.'); 140 | end 141 | if k~=3 || length(c)==1, 142 | c = rgbspec(k,:); 143 | elseif length(c)>2, 144 | if strcmpi(c(1:3),'bla') 145 | c = [0 0 0]; 146 | elseif strcmpi(c(1:3),'blu') 147 | c = [0 0 1]; 148 | else 149 | error('MATLAB:UnknownColorString', 'Unknown color string.'); 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /email_results.m: -------------------------------------------------------------------------------- 1 | %% email_results sends the report and the predictions to the mailing list 2 | % Peter Attia, last updated June 25, 2018 3 | 4 | batch_size = length(batch); 5 | num_complete = 0; 6 | if strcmp(batch_name,'oed1') 7 | cycles_completed = 97; 8 | else 9 | cycles_completed = 99; 10 | end 11 | for k = 1:batch_size 12 | if length(batch(k).cycles) > cycles_completed 13 | num_complete = num_complete + 1; 14 | end 15 | end 16 | 17 | %attachments = [path.reports '\' date '_report.pdf']; 18 | if contains(batch_name,'disassembly_batch') 19 | attachments = [path.reports '\' date '_report.pdf']; 20 | else 21 | attachments = cell(2,1); 22 | attachments{1} = [path.reports '\' date '_report.pdf']; 23 | attachments{2} = [path.result_tables '\' date '_' batch_name '_predictions.csv']; 24 | end 25 | message_body = {['Hot off the press: Check out the latest ' batch_name ... 26 | ' results, now including predictions!']; path.message; 27 | [num2str(num_complete) ' out of ' num2str(batch_size) ' cells complete']; 28 | ''; ''; ''}; 29 | sendemail(email_list,'BMS project: Updated results', ... 30 | message_body, attachments); 31 | disp('Email sent - success!') -------------------------------------------------------------------------------- /get_batch_date_from_batch_name.m: -------------------------------------------------------------------------------- 1 | % Get batch_date from batch_name 2 | switch batch_name % Format as 'yyyy-mm-dd' 3 | case 'batch0' 4 | batch_date = '20170412'; 5 | case 'batch1' 6 | batch_date = '2017-05-12'; 7 | case 'batch2' 8 | batch_date = '2017-06-30'; 9 | case 'batch3' 10 | batch_date = '2017-08-14'; 11 | case 'batch4' 12 | batch_date = '2017-12-04'; 13 | case 'batch5' 14 | batch_date = '2018-01-18'; 15 | case 'batch6' 16 | batch_date = '2018-02-01'; 17 | case 'batch7' 18 | batch_date = '2018-02-20'; 19 | case 'batch7pt5' 20 | batch_date = '2018-04-03_varcharge'; 21 | case 'batch8' 22 | batch_date = '2018-04-12'; 23 | case 'oed1' 24 | batch_date = '2018-06-21'; 25 | case 'oed2' 26 | batch_date = '2018-07-18'; 27 | case 'oed3' 28 | batch_date = '2018-07-23'; 29 | case 'oed4' 30 | batch_date = '2018-07-29'; 31 | case 'oed_0' 32 | batch_date = '2018-08-28'; 33 | case 'oed_1' 34 | batch_date = '2018-09-02'; 35 | case 'oed_2' 36 | batch_date = '2018-09-06'; 37 | case 'oed_3' 38 | batch_date = '2018-09-10'; 39 | case 'oed_4' 40 | batch_date = '2018-09-18'; 41 | case 'disassembly_batch' 42 | batch_date = '2018-10-02'; 43 | case 'disassembly_batch1pt5' 44 | batch_date = '2018-11-02'; 45 | case 'disassembly_batch2' 46 | batch_date = '2018-12-05'; 47 | case 'disassembly_batch3' 48 | batch_date = '2018-12-21'; 49 | case 'batch9' 50 | batch_date = '2019-01-24'; 51 | case 'batch9pt2' 52 | batch_date = '2019-01-29'; 53 | otherwise 54 | warning('batch_date not recognized') 55 | end -------------------------------------------------------------------------------- /hline.m: -------------------------------------------------------------------------------- 1 | function hhh=hline(y,in1,in2) 2 | % function h=hline(y, linetype, label) 3 | % 4 | % Draws a horizontal line on the current axes at the location specified by 'y'. Optional arguments are 5 | % 'linetype' (default is 'r:') and 'label', which applies a text label to the graph near the line. The 6 | % label appears in the same color as the line. 7 | % 8 | % The line is held on the current axes, and after plotting the line, the function returns the axes to 9 | % its prior hold state. 10 | % 11 | % The HandleVisibility property of the line object is set to "off", so not only does it not appear on 12 | % legends, but it is not findable by using findobj. Specifying an output argument causes the function to 13 | % return a handle to the line, so it can be manipulated or deleted. Also, the HandleVisibility can be 14 | % overridden by setting the root's ShowHiddenHandles property to on. 15 | % 16 | % h = hline(42,'g','The Answer') 17 | % 18 | % returns a handle to a green horizontal line on the current axes at y=42, and creates a text object on 19 | % the current axes, close to the line, which reads "The Answer". 20 | % 21 | % hline also supports vector inputs to draw multiple lines at once. For example, 22 | % 23 | % hline([4 8 12],{'g','r','b'},{'l1','lab2','LABELC'}) 24 | % 25 | % draws three lines with the appropriate labels and colors. 26 | % 27 | % By Brandon Kuczenski for Kensington Labs. 28 | % brandon_kuczenski@kensingtonlabs.com 29 | % 8 November 2001 30 | 31 | if length(y)>1 % vector input 32 | for I=1:length(y) 33 | switch nargin 34 | case 1 35 | linetype='r:'; 36 | label=''; 37 | case 2 38 | if ~iscell(in1) 39 | in1={in1}; 40 | end 41 | if I>length(in1) 42 | linetype=in1{end}; 43 | else 44 | linetype=in1{I}; 45 | end 46 | label=''; 47 | case 3 48 | if ~iscell(in1) 49 | in1={in1}; 50 | end 51 | if ~iscell(in2) 52 | in2={in2}; 53 | end 54 | if I>length(in1) 55 | linetype=in1{end}; 56 | else 57 | linetype=in1{I}; 58 | end 59 | if I>length(in2) 60 | label=in2{end}; 61 | else 62 | label=in2{I}; 63 | end 64 | end 65 | h(I)=hline(y(I),linetype,label); 66 | end 67 | else 68 | switch nargin 69 | case 1 70 | linetype='r:'; 71 | label=''; 72 | case 2 73 | linetype=in1; 74 | label=''; 75 | case 3 76 | linetype=in1; 77 | label=in2; 78 | end 79 | 80 | 81 | 82 | 83 | g=ishold(gca); 84 | hold on 85 | 86 | x=get(gca,'xlim'); 87 | h=plot(x,[y y],linetype,'linewidth',1.5); 88 | if ~isempty(label) 89 | yy=get(gca,'ylim'); 90 | yrange=yy(2)-yy(1); 91 | yunit=(y-yy(1))/yrange; 92 | if yunit<0.2 93 | text(x(1)+0.02*(x(2)-x(1)),y+0.02*yrange,label,'color',get(h,'color')) 94 | else 95 | text(x(1)+0.02*(x(2)-x(1)),y-0.02*yrange,label,'color',get(h,'color')) 96 | end 97 | end 98 | 99 | if g==0 100 | hold off 101 | end 102 | set(h,'tag','hline','handlevisibility','off') % this last part is so that it doesn't show up on legends 103 | end % else 104 | 105 | if nargout 106 | hhh=h; 107 | end 108 | -------------------------------------------------------------------------------- /linspecer.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chueh-ermon/BMS-autoanalysis/c82ab7704211a6aee75bd926714b82d1f95e1a0e/linspecer.m -------------------------------------------------------------------------------- /make_images.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chueh-ermon/BMS-autoanalysis/c82ab7704211a6aee75bd926714b82d1f95e1a0e/make_images.m -------------------------------------------------------------------------------- /make_result_tables.m: -------------------------------------------------------------------------------- 1 | function [T_cells, T_policies] = make_result_tables( batch, batch_name, ... 2 | tablespath, codepath ) 3 | %make_result_table makes tables of results for OED. 4 | % Parses policy parameters (e.g. CC1, CC2, Q1) and 5 | % calculates charging time and average degradation rate. 6 | % In this function, we make two result tables - one for each cell (T), 7 | % and one for each policy (in which results for policies with multiple 8 | % cells are averaged). 9 | % It then writes the results to a CSV. 10 | % Peter Attia, July 2017 11 | 12 | disp('Starting make_result_tables'),tic 13 | 14 | n = numel(batch); % number of batteries in the batch 15 | 16 | %% Preinitialize variables 17 | policies = cell(n,1); 18 | CC1 = zeros(n,1); 19 | CC2 = zeros(n,1); 20 | Q1 = zeros(n,1); 21 | t80calc = zeros(n,1); % time to 80% (calculated from policy parameters) 22 | t80meas100 = zeros(n,1); % time to 80% (measured - median of first 100 cycles) 23 | cycles = zeros(n,1); % number of cycles completed 24 | degrate = zeros(n,1); % average deg rate (Ah/cycles) 25 | initdegrate = zeros(n,1); % initial deg rate (Ah/cycles) 26 | finaldegrate = zeros(n,1); % final deg rate (Ah/cycles) 27 | 28 | %% Loops through each battery 29 | for i = 1:numel(batch) 30 | % Parses file name. Two-step policy names are in this format: 31 | % 8C(35%)-3.6C 32 | policy = batch(i).policy_readable; 33 | policies{i} = policy; 34 | experimental = ['discharge','dod','restattop','ratetest']; 35 | TF = contains(policy,experimental); % logical to check if the tests are experimental 36 | if TF || count(policy,'%') == 2 37 | continue % skip if test is experimental 38 | else 39 | %% Identify CC1, CC2, Q1 40 | try 41 | % CC1 is the number before the first 'C' 42 | C_indices = strfind(policy,'C'); 43 | CC1(i) = str2double(policy(1:C_indices(1)-1)); 44 | % CC2 is the number after '-' but before the second 'C' 45 | dash_index = strfind(policy,'-'); 46 | CC2(i) = str2double(policy(dash_index+1:C_indices(2)-1)); 47 | % Q1 is the number between '(' and '%' 48 | paren_index = strfind(policy,'('); 49 | percent_index = strfind(policy,'%'); 50 | Q1(i) = str2double(policy(paren_index+1:percent_index-1)); 51 | catch 52 | warning(['Policy names cannot be parsed by MATLAB. Ensure the ' ... 53 | 'policy names follow the format 8C(35%)-3.6C']) 54 | end 55 | 56 | %% Charging time - calculated and measured 57 | t80calc(i) = 60./CC1(i) .* Q1(i)./100 + 60./CC2(i) .* (80-Q1(i))./100; 58 | if length(batch(i).summary.chargetime) > 100 59 | t80meas100(i) = mean(batch(i).summary.chargetime(1:100)); 60 | else 61 | t80meas100(i) = mean(batch(i).summary.chargetime); 62 | end 63 | 64 | %% Cycles. Cycle life, or number of cycles completed 65 | cutoff_Q = 0.8*1.1; 66 | if batch(i).summary.QDischarge(end) < cutoff_Q 67 | cycles(i) = find(batch(i).summary.QDischarge < cutoff_Q,1); 68 | else 69 | cycles(i) = max(batch(i).summary.cycle); 70 | end 71 | 72 | %% Degradation rate. Defined as (max(capacity) - min(capacity))/cycles 73 | degrate(i) = (max(batch(i).summary.QDischarge) - ... 74 | min(batch(i).summary.QDischarge))/ ... 75 | cycles(i); 76 | 77 | if length(batch(i).summary.chargetime) > 100 78 | initdegrate(i) = (max(batch(i).summary.QDischarge(1:100)) - ... 79 | min(batch(i).summary.QDischarge(1:100)))/ 100; 80 | 81 | finaldegrate(i) = (max(batch(i).summary.QDischarge(end-100:end)) - ... 82 | min(batch(i).summary.QDischarge(end-100:end)))/ 100; 83 | else 84 | initdegrate(i) = degrate(i); 85 | 86 | finaldegrate(i) = degrate(i); 87 | end 88 | end 89 | end 90 | 91 | %% Creates table (for each cell) 92 | T_cells = table(CC1, Q1, CC2, t80calc, t80meas100, cycles, degrate, ... 93 | initdegrate,finaldegrate); 94 | 95 | %% Saves files on a cell basis 96 | cd(tablespath) 97 | results_table_file = [date '_' batch_name '_results_table_allcells.xlsx']; 98 | writetable(T_cells,results_table_file) % Save to CSV 99 | % Re-writes column headers 100 | col_headers = {'CC1' 'Q1' 'CC2' ... 101 | 'Time to 80% - calculated (min)' ... 102 | 'Time to 80% - measured, median of first 100 cycles (min)', ... 103 | 'Cycles completed', 'Average degradation rate (Ah/cycle)', ... 104 | 'Initial degradation rate (Ah/cycle)', ... 105 | 'Final degradation rate (Ah/cycle)'}; 106 | try 107 | xlswrite(results_table_file,col_headers,'Sheet1','A1') 108 | catch 109 | warning('xlswrite error') 110 | end 111 | cd(codepath) 112 | 113 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 114 | %%%% POLICY-SPECIFIC TABLES %%%%% 115 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 116 | 117 | %% Creates table for each policy 118 | % Identify all unique policy names 119 | unique_policies = unique(policies); 120 | num_policies = length(unique_policies); 121 | 122 | % Preinitialize vectors. Same as before, but for the policies 123 | numcells = zeros(num_policies,1); % number of cells for a given policy 124 | CC1_policies = zeros(num_policies,1); 125 | CC2_policies = zeros(num_policies,1); 126 | Q1_policies = zeros(num_policies,1); 127 | t80calc_policies = zeros(num_policies,1); % time to 80% (calculated from policy parameters) 128 | t80meas100_policies = zeros(num_policies,1); % time to 80% (measured - median of first 100 cycles) 129 | cycles_policies = zeros(num_policies,1); % number of cycles completed 130 | degrate_policies = zeros(num_policies,1); % average deg rate (Ah/cycles) 131 | initdegrate_policies = zeros(num_policies,1); % initial deg rate (Ah/cycles) 132 | finaldegrate_policies = zeros(num_policies,1); % final deg rate (Ah/cycles) 133 | 134 | % Loop through each policy, find all cells with that policy, and then 135 | % compute the parameters 136 | for i = 1:num_policies 137 | battery_index = []; 138 | for j = 1:n 139 | if strcmp(unique_policies{i}, batch(j).policy_readable) 140 | numcells(i) = numcells(i) + 1; 141 | battery_index = [battery_index j]; 142 | end 143 | end 144 | 145 | CC1_policies(i) = CC1(battery_index(1)); 146 | CC2_policies(i) = CC2(battery_index(1)); 147 | Q1_policies(i) = Q1(battery_index(1)); 148 | t80calc_policies(i) = t80calc(battery_index(1)); 149 | t80meas100_policies(i) = mean(t80meas100(battery_index)); % time to 80% (measured - median of first 100 cycles) 150 | cycles_policies(i) = mean(cycles(battery_index)); % number of cycles completed 151 | degrate_policies(i) = mean(degrate(battery_index)); % average deg rate (Ah/cycles) 152 | initdegrate_policies(i) = mean(initdegrate(battery_index)); % initial deg rate (Ah/cycles) 153 | finaldegrate_policies(i) = mean(finaldegrate(battery_index)); % final deg rate (Ah/cycles) 154 | end 155 | 156 | %% Create table (for each policy) 157 | T_policies = table(CC1_policies, Q1_policies, CC2_policies, numcells, ... 158 | t80calc_policies, t80meas100_policies, cycles_policies, ... 159 | degrate_policies, initdegrate_policies, finaldegrate_policies); 160 | 161 | %% Saves files on a policy basis 162 | cd(tablespath) 163 | results_table_file2 = [date '_' batch_name '_results_table_allpolicies.xlsx']; 164 | writetable(T_policies,results_table_file2) % Save to CSV 165 | % Re-writes column headers 166 | col_headers = {'CC1' 'Q1' 'CC2','Number of cells' ... 167 | 'Time to 80% - calculated (min)' ... 168 | 'Time to 80% - measured, median of first 100 cycles (min)', ... 169 | 'Cycles completed', 'Average degradation rate (Ah/cycle)', ... 170 | 'Initial degradation rate (Ah/cycle)', ... 171 | 'Final degradation rate (Ah/cycle)'}; 172 | try 173 | xlswrite(results_table_file2,col_headers,'Sheet1','A1') 174 | catch 175 | warning('xlswrite error') 176 | end 177 | 178 | %% Save both result tables as .mat 179 | save([date '_' batch_name '_result_tables'],'T_cells', 'T_policies') 180 | cd(codepath) 181 | 182 | disp('Completed make_result_tables'),toc 183 | end -------------------------------------------------------------------------------- /make_summary_gifs.m: -------------------------------------------------------------------------------- 1 | function make_summary_gifs(batch, batch_name) 2 | % make_summary_gifs makes the summary gifs for each batch. Since each 3 | % batch will have different 'best' ways of presenting data, we use 4 | % conditional statements to identify which to use 5 | 6 | disp('Starting make_summary_gifs'), tic 7 | 8 | %% CHANGE THESE SETTINGS - development mode 9 | filename = ['Qnplot_' batch_name '.gif']; % Specify the output file name 10 | endcycle = 1000; % Last cycle plotted 11 | 12 | %% Move to GIF directory 13 | % cd 'C:\Users\Arbin\Box Sync\Data\Batch GIFS\' 14 | 15 | %% Find all unique policies 16 | n_cells = numel(batch); 17 | policies = cell(n_cells,1); 18 | readable_policies = cell(n_cells,1); 19 | for i = 1:n_cells 20 | policies{i} = batch(i).policy; 21 | readable_policies{i}=batch(i).policy_readable; 22 | end 23 | unique_policies = unique(policies); 24 | unique_readable_policies = unique(readable_policies); 25 | n_policies = numel(unique_policies); 26 | 27 | %% Preinitialize random colors and markers. 28 | % % Random colors: Updated to use 'pretty colors' (linspecer.m) 29 | % cols = linspecer(n_policies); 30 | % ordering = randperm(n_policies); 31 | % cols = cols(ordering, :); % shuffle 32 | % Distinguishable colors: Colors with maximum contrast 33 | % (distinguishable_colors.m) 34 | cols = distinguishable_colors(n_policies); 35 | 36 | % Random markers 37 | marks = cell(1,n_policies); 38 | for i = 1:n_policies 39 | [~, mark]=random_color('y','y'); 40 | marks{i} = mark; 41 | end 42 | 43 | %% Create 'Q' array - cell array of cell arrays 44 | % Q = 1xn cell, where n = number of policies 45 | % Q{1,1} = 1xm cell, where m = number of cells tested for this policy 46 | Q = cell(1,n_policies); 47 | for i = 1:n_policies % loop through all policies 48 | numcells = 0; 49 | for j = 1:n_cells % cell index 50 | if strcmp(unique_policies{i}, batch(j).policy) 51 | numcells = numcells + 1; 52 | end 53 | end 54 | Q{i} = cell(1,numcells); 55 | 56 | j = 1; % cell index 57 | k = 1; % cell = policy index 58 | while k < numcells + 1 59 | if strcmp(unique_policies{i}, batch(j).policy) 60 | Q{i}(k) = {batch(j).summary.QDischarge}; 61 | k = k + 1; 62 | end 63 | j = j + 1; 64 | end 65 | end 66 | 67 | %% Make full screen figure 68 | figure('units','normalized','outerposition',[0 0 1 1]), box on 69 | xlabel('Cycle number','FontSize',16) 70 | ylabel('Remaining capacity (normalized)','FontSize',16) 71 | set(gca,'FontSize',16) 72 | % ylabel('Remaining discharge capacity (Ah)') 73 | axis([0 1000 0.79 1.0]) % y = 0.85 -> 1.15 74 | set(gcf, 'Color' ,'w') 75 | hline(0.8) 76 | 77 | %% Begin looping. j = cycle index 78 | for j=1:endcycle 79 | % i = policy index 80 | for i=1:n_policies 81 | %% Plot each policy 82 | hold on 83 | 84 | cycles = j.*ones(1,length(Q{i})); 85 | Qn = zeros(1,length(Q{i})); % preinitialize capacity at cycle n (Qn) 86 | % k = index of cells within a policy 87 | for k = 1:length(Q{i}) 88 | % If cell has died, we won't have data at this cycle number. 89 | % Just plot the last cycle 90 | if length(Q{i}{k}) < j 91 | Qn(k) = Q{i}{k}(end); 92 | else 93 | Qn(k) = Q{i}{k}(j); 94 | end 95 | end 96 | 97 | % Plot points for this policy 98 | scatter(cycles,Qn./1.1,100,cols(i,:),marks{i},'LineWidth',1.5); 99 | end 100 | % Misc plotting stuff 101 | title(['Cycle ' num2str(j)],'FontSize',16) 102 | %leg = columnlegend(2,unique_readable_policies,'Location','NortheastOutside','boxoff'); 103 | leg = legend(unique_readable_policies','Location','EastOutside','FontSize',14); 104 | hold off 105 | 106 | % Create GIF 107 | drawnow 108 | frame = getframe(1); 109 | im = frame2im(frame); 110 | [imind,cm] = rgb2ind(im,256); 111 | if j == 1 112 | imwrite(imind,cm,filename,'gif','Loopcount',1); 113 | else 114 | imwrite(imind,cm,filename,'gif','WriteMode','append','DelayTime',0.03); 115 | end 116 | end 117 | 118 | % %% Make different summary plots for each batch 119 | % % Batch 1 (2017-05-12) 120 | % if batch_name == 'batch1' 121 | % batch1_summary_plots(batch, batch_name, T_cells, T_policies) 122 | % % Batch 2 (2017-06-30) 123 | % elseif batch_name == 'batch2' 124 | % batch2_summary_plots(batch, batch_name, T_cells, T_policies) 125 | % else 126 | % warning('Batch name not recognized. No summary figures generated') 127 | % end 128 | 129 | close all 130 | %cd 'C:/Users/Arbin/Documents/GitHub/BMS-autoanalysis' 131 | 132 | disp('Completed make_summary_gifs'),toc 133 | 134 | end -------------------------------------------------------------------------------- /make_summary_images.m: -------------------------------------------------------------------------------- 1 | function make_summary_images(batch, batch_name, T_cells, T_policies) 2 | % make_summary images makes the summary images for each batch. Since each 3 | % batch will have different 'best' ways of presenting data, have 4 | % conditional statements to identify which to use 5 | 6 | disp('Starting make_summary_images'), tic 7 | 8 | load path.mat 9 | 10 | %% Move to image directory 11 | cd (strcat(path.images, '\', batch_name)) 12 | 13 | %% Q vs n for each policy 14 | policies = cell(height(T_cells),1); 15 | readable_policies = cell(height(T_cells),1); 16 | for i = 1:numel(batch) 17 | policies{i} = batch(i).policy; 18 | readable_policies{i}=batch(i).policy_readable; 19 | end 20 | disp(policies) 21 | unique_policies = unique(policies); 22 | unique_readable_policies = unique(readable_policies); 23 | 24 | %Two figures, absolute and normalized. We switch between the two as we 25 | %plot and format the images 26 | figAbsolute = figure('units','normalized','outerposition',[0 0 1 1]); hold on, box on 27 | set(gca, 'FontSize', 16) 28 | figNormalized = figure('units','normalized','outerposition',[0 0 1 1]); hold on, box on 29 | set(gca, 'FontSize', 16) 30 | 31 | % Loop through 32 | for i = 1:length(unique_policies) 33 | % Keep consistent color 34 | [col, ~] = random_color('y','y'); 35 | %All the markers we want to use 36 | markers = {'+','o','*','.','x','s','d','^','v','>','<','p','h'}; 37 | % Find all cells with policy i, generate combined x,y 38 | x=cell(0); 39 | y=cell(0); 40 | 41 | k = 1; 42 | for j = 1:numel(batch) 43 | if strcmp(unique_policies{i}, batch(j).policy) 44 | x{k} = 1:length(batch(j).summary.QDischarge); 45 | y{k} = batch(j).summary.QDischarge; 46 | 47 | figure(figAbsolute); 48 | plot(x{k},y{k},markers{mod(i,numel(markers))+1},'color',col); 49 | figure(figNormalized); 50 | plot(x{k},y{k}./y{k}(1),markers{mod(i,numel(markers))+1},'color',col); 51 | k = k + 1; 52 | end 53 | end 54 | end 55 | 56 | %Formatting of figures 57 | figure(figAbsolute); 58 | xlabel('Cycle number') 59 | ylabel('Remaining discharge capacity (Ah)') 60 | if strcmp(batch_name, 'batch1') || strcmp(batch_name, 'batch2') || strcmp(batch_name, 'batch4') || strcmp(batch_name, 'batch9') 61 | ylim([0.85 1.1]) 62 | elseif contains(batch_name,'oed') 63 | ylim([1.0 1.1]) 64 | else 65 | ylim([0.85 1.25]) 66 | end 67 | %2-column legend via custom function. Not perfect but workable 68 | columnlegend(2,unique_readable_policies,'Location','NortheastOutside','boxoff'); 69 | print('summary1_Q_vs_n','-dpng') 70 | %savefig(gcf,'summary1_Q_vs_n') 71 | 72 | figure(figNormalized); 73 | xlabel('Cycle number') 74 | ylabel('Remaining discharge capacity (normalized by initial capacity)') 75 | if strcmp(batch_name, 'batch1') || strcmp(batch_name, 'batch2') || strcmp(batch_name, 'batch4') 76 | ylim([0.8 .011]) 77 | elseif contains(batch_name,'oed') 78 | ylim([0.99 1.01]) 79 | end 80 | %2-column legend via custom function. Not perfect but workable 81 | columnlegend(2,unique_readable_policies,'Location','NortheastOutside','boxoff'); 82 | print('summary2_Q_vs_n_norm','-dpng') 83 | %savefig(gcf,'summary2_Q_vs_n_norm') 84 | 85 | % Delta Q plot 86 | fig_deltaQ = figure('units','normalized','outerposition',[0 0 1 1]); hold on, box on 87 | min_cycles_completed = 1000; 88 | for k = 1:length(batch) 89 | min_cycles_completed = min(length(batch(k).cycles),min_cycles_completed); 90 | end 91 | for k = 1:length(batch) 92 | if min_cycles_completed < 10 93 | try 94 | plot(batch(k).cycles(min_cycles_completed).Qdlin - batch(k).cycles(2).Qdlin, batch(k).Vdlin); 95 | xlabel(['Q_{',num2str(min_cycles_completed),'} - Q_{2} (Ah)']) 96 | catch 97 | end 98 | else 99 | plot(batch(k).cycles(min_cycles_completed).Qdlin - batch(k).cycles(10).Qdlin, batch(k).Vdlin); 100 | xlabel(['Q_{',num2str(min_cycles_completed),'} - Q_{10} (Ah)']) 101 | end 102 | end 103 | set(gca, 'FontSize', 16) 104 | ylabel('Voltage (V)') 105 | %2-column legend via custom function. Not perfect but workable 106 | columnlegend(2,unique_readable_policies,'Location','NortheastOutside','boxoff'); 107 | print('summary3_DeltaQ','-dpng') 108 | savefig(gcf,'summary3_DeltaQ') 109 | 110 | %% Make different summary plots for each batch 111 | batches_likebatch2 = {'batch2','batch4','batch5','batch6','batch7','batch8'}; 112 | % Batch 1 (2017-05-12) 113 | if strcmp(batch_name, 'batch1') 114 | batch1_summary_plots(batch, batch_name, T_cells, T_policies) 115 | % Batch 2 and similar 116 | elseif sum(strcmp(batch_name, batches_likebatch2)) 117 | batch2_summary_plots2(T_policies) 118 | % Batch 3 (2017-08-14) 119 | elseif strcmp(batch_name,'batch3') 120 | batch3_summary_plots(T_policies) 121 | % OED batches 122 | elseif contains(batch_name,'oed') || strcmp(batch_name,'batch9') 123 | table_path = [path.result_tables '\' date '_' batch_name '_predictions.csv']; 124 | python('oed_plots.py',table_path,path.images,batch_name); 125 | else 126 | warning('Batch name not recognized. Additional summary figures not generated') 127 | end 128 | 129 | close all 130 | cd(path.code) 131 | disp('Completed make_summary_images'),toc 132 | 133 | end -------------------------------------------------------------------------------- /oed_model.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chueh-ermon/BMS-autoanalysis/c82ab7704211a6aee75bd926714b82d1f95e1a0e/oed_model.mat -------------------------------------------------------------------------------- /oed_model_batch1.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chueh-ermon/BMS-autoanalysis/c82ab7704211a6aee75bd926714b82d1f95e1a0e/oed_model_batch1.mat -------------------------------------------------------------------------------- /oed_plots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Creates map of 4-step policy space with associated lifetimes 5 | 6 | Peter Attia 7 | Last modified June 25, 2018 8 | """ 9 | 10 | import numpy as np 11 | import matplotlib 12 | import matplotlib.pyplot as plt 13 | import sys 14 | 15 | ############################################################################## 16 | 17 | # PARAMETERS TO CREATE POLICY SPACE 18 | C1list = [3.6, 4.0, 4.4, 4.8, 5.2, 5.6, 6, 8] 19 | C2list = [3.6, 4.0, 4.4, 4.8, 5.2, 5.6, 6, 7] 20 | C3list = [3.6, 4.0, 4.4, 4.8, 5.2, 5.6] 21 | 22 | C4_LIMITS = [0.1, 4.81] # Lower and upper limits specifying valid C4s 23 | #FILENAME = 'D:\Data_Matlab\Result_tables\\25-Jun-2018_oed1_predictions.csv' 24 | 25 | ############################################################################## 26 | plt.close('all') 27 | colormap = 'plasma_r' 28 | 29 | path_table = sys.argv[1] 30 | path_images = sys.argv[2] 31 | batch_name = sys.argv[3] 32 | 33 | one_step = 4.8 34 | margin = 0.2 # plotting margin 35 | 36 | ## IMPORT POLICIES 37 | data = np.genfromtxt(path_table, delimiter=',',skip_header=1) 38 | policies = data[:,:4] 39 | lifetime = data[:,4] 40 | 41 | ## CREATE CONTOUR PLOT 42 | # Calculate C4(CC1, CC2) values for contour lines 43 | C1_grid = np.arange(min(C1list)-margin,max(C1list) + margin,0.01) 44 | C2_grid = np.arange(min(C1list)-margin,max(C1list) + margin,0.01) 45 | [X,Y] = np.meshgrid(C1_grid,C2_grid) 46 | 47 | # Initialize plot 48 | fig = plt.figure() # x = C1, y = C2, cuts = C3, contours = C4 49 | plt.style.use('classic') 50 | plt.rcParams.update({'font.size': 16}) 51 | plt.set_cmap(colormap) 52 | manager = plt.get_current_fig_manager() # Make full screen 53 | manager.window.showMaximized() 54 | minn, maxx = min(lifetime), max(lifetime) 55 | 56 | ## MAKE PLOT 57 | for k, c3 in enumerate(C3list): 58 | plt.subplot(2,3,k+1) 59 | plt.axis('square') 60 | 61 | C4 = 0.2/(1/6 - (0.2/X + 0.2/Y + 0.2/c3)) 62 | C4[np.where(C4C4_LIMITS[1])] = float('NaN') 64 | 65 | ## PLOT CONTOURS 66 | levels = np.arange(2.5,4.8,0.25) 67 | C = plt.contour(X,Y,C4,levels,zorder=1,colors='k') 68 | plt.clabel(C,fmt='%1.1f') 69 | 70 | ## PLOT POLICIES 71 | idx_subset = np.where(policies[:,2]==c3) 72 | policy_subset = policies[idx_subset,:][0] 73 | lifetime_subset = lifetime[idx_subset] 74 | plt.scatter(policy_subset[:,0],policy_subset[:,1],vmin=minn,vmax=maxx, 75 | c=lifetime_subset.ravel(),zorder=2,s=100) 76 | 77 | if c3 == 4.8: 78 | plt.scatter(4.8,4.8,c='k',zorder=2,s=100, marker='s') 79 | 80 | plt.title('C3=' + str(c3) + ': ' + str(len(policy_subset)) + ' policies',fontsize=16) 81 | plt.xlabel('C1') 82 | plt.ylabel('C2') 83 | plt.xlim((min(C1list)-margin, max(C1list)+margin)) 84 | plt.ylim((min(C1list)-margin, max(C1list)+margin)) 85 | 86 | # Add colorbar 87 | fig.set_size_inches((15,8.91), forward=False) 88 | plt.tight_layout() 89 | 90 | fig.subplots_adjust(right=0.8) 91 | cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) 92 | norm = matplotlib.colors.Normalize(minn, maxx) 93 | m = plt.cm.ScalarMappable(norm=norm, cmap=colormap) 94 | m.set_array([]) 95 | cbar = fig.colorbar(m, cax=cbar_ax) 96 | #plt.clim(min(lifetime),max(lifetime)) 97 | cbar.ax.set_title('Cycle life') 98 | 99 | ## SAVE FIGURE 100 | plt.savefig(path_images + '\\' + batch_name + '\\' + 'summary4_predictions.png', bbox_inches='tight') -------------------------------------------------------------------------------- /python.m: -------------------------------------------------------------------------------- 1 | function [result, status] = python(varargin) 2 | %python Execute python command and return the result. 3 | % python(pythonFILE) calls python script specified by the file pythonFILE 4 | % using appropriate python executable. 5 | % 6 | % python(pythonFILE,ARG1,ARG2,...) passes the arguments ARG1,ARG2,... 7 | % to the python script file pythonFILE, and calls it by using appropriate 8 | % python executable. 9 | % 10 | % RESULT=python(...) outputs the result of attempted python call. If the 11 | % exit status of python is not zero, an error will be returned. 12 | % 13 | % [RESULT,STATUS] = python(...) outputs the result of the python call, and 14 | % also saves its exit status into variable STATUS. 15 | % 16 | % If the python executable is not available, it can be downloaded from: 17 | % http://www.cpan.org 18 | % 19 | % See also SYSTEM, JAVA, MEX. 20 | 21 | % Copyright 1990-2012 The MathWorks, Inc. 22 | 23 | cmdString = ''; 24 | 25 | % Add input to arguments to operating system command to be executed. 26 | % (If an argument refers to a file on the MATLAB path, use full file path.) 27 | for i = 1:nargin 28 | thisArg = varargin{i}; 29 | if ~ischar(thisArg) 30 | error(message('MATLAB:python:InputsMustBeStrings')); 31 | end 32 | if i==1 33 | if exist(thisArg, 'file')==2 34 | % This is a valid file on the MATLAB path 35 | if isempty(dir(thisArg)) 36 | % Not complete file specification 37 | % - file is not in current directory 38 | % - OR filename specified without extension 39 | % ==> get full file path 40 | thisArg = which(thisArg); 41 | end 42 | else 43 | % First input argument is pythonFile - it must be a valid file 44 | error(message('MATLAB:python:FileNotFound', thisArg)); 45 | end 46 | end 47 | 48 | % Wrap thisArg in double quotes if it contains spaces 49 | if isempty(thisArg) || any(thisArg == ' ') 50 | thisArg = ['"', thisArg, '"']; %#ok 51 | end 52 | 53 | % Add argument to command string 54 | cmdString = [cmdString, ' ', thisArg]; %#ok 55 | end 56 | 57 | % Check that the command string is not empty 58 | if isempty(cmdString) 59 | error(message('MATLAB:python:NopythonCommand')); 60 | end 61 | 62 | % Check that python is available if this is not a PC or isdeployed 63 | if ~ispc || isdeployed 64 | if ispc 65 | checkCMDString = 'python -v'; 66 | else 67 | checkCMDString = 'which python'; 68 | end 69 | [cmdStatus, ~] = system(checkCMDString); 70 | if cmdStatus ~=0 71 | error(message('MATLAB:python:NoExecutable')); 72 | end 73 | end 74 | 75 | % Execute python script 76 | cmdString = ['python' cmdString]; 77 | if ispc && ~isdeployed 78 | % Add python to the path 79 | pythonInst = fullfile(matlabroot, 'sys\python\win32\bin\'); 80 | cmdString = ['set PATH=',pythonInst, ';%PATH%&' cmdString]; 81 | end 82 | [status, result] = system(cmdString); 83 | 84 | % Check for errors in shell command 85 | if nargout < 2 && status~=0 86 | error(message('MATLAB:python:ExecutionError', result, cmdString)); 87 | end 88 | 89 | 90 | -------------------------------------------------------------------------------- /random_color.m: -------------------------------------------------------------------------------- 1 | function [ color, marker ] = random_color( color, marker ) 2 | %This function takes in either a yes for a random color, or a number 3 | % to give a random export. Nick Perkins 4 | 5 | % All markers and colors 6 | mark_array={'o','+','*','x','s','d','^','v','>','<','p','h'}; 7 | col_array={'y','m','c','r','g','b','w','k'}; 8 | 9 | % Random color 10 | if color == 'y' 11 | i =randi(100); 12 | j =randi(100); 13 | k =randi(100); 14 | color=[i*.01, j*.01, k*.01]; 15 | elseif ischar(color) == 1 16 | display('Choose yes or give number corresponding to desired color') 17 | elseif color <= 8 18 | color=col_array{color}; 19 | else 20 | i =randi(255); 21 | color=rand_color{i}; 22 | end 23 | 24 | % Random marker 25 | if marker == 'y' 26 | i =randi(12); 27 | marker=mark_array{i}; 28 | elseif ischar(marker) ==1 29 | display('Choose yes or give number corresponding to desired marker') 30 | elseif marker <= 12 31 | marker=mark_array{marker}; 32 | else 33 | i =randi(12); 34 | marker=mark_array{i}; 35 | end 36 | 37 | end 38 | 39 | -------------------------------------------------------------------------------- /reportgenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Fri Jul 7 12:10:30 2017 5 | 6 | @author: peter 7 | """ 8 | 9 | # Imports 10 | import os # file i/o operations 11 | import glob # file i/o operations 12 | from datetime import date# finding today's date 13 | from pptx import Presentation # creating the PPT 14 | #from pptx.util import Inches 15 | import comtypes.client # for opening PowerPoint from python 16 | import sys 17 | 18 | """ Assign variable names to arguments from MATLAB """ 19 | path_images = sys.argv[1] 20 | path_reports = sys.argv[2] 21 | batch_name = sys.argv[3] 22 | 23 | """ Use lines 26-28 for debugging """ 24 | #path_images = 'D:\Data_Matlab\Batch_images' 25 | #path_reports = 'D:\Data_Matlab\Reports' 26 | #batch_name = 'batch8' 27 | 28 | def PPTtoPDF(inputFileName, outputFileName, formatType = 32): 29 | """ 30 | Converts a PPT file to a PDF by opening PowerPoint, opening the file, and 31 | then saving as a PowerPoint. Requires Windows. 32 | """ 33 | powerpoint = comtypes.client.CreateObject("Powerpoint.Application") 34 | powerpoint.Visible = 1 35 | 36 | if outputFileName[-3:] != 'pdf': 37 | outputFileName = outputFileName + ".pdf" 38 | deck = powerpoint.Presentations.Open(inputFileName) 39 | deck.SaveAs(outputFileName, formatType) # formatType = 32 for ppt to pdf 40 | deck.Close() 41 | powerpoint.Quit() 42 | 43 | def addImageSlide(image_file_name): 44 | """ 45 | Adds a full-screen image to a blank full-screen slide. 46 | """ 47 | blank_slide_layout = prs.slide_layouts[6] 48 | slide = prs.slides.add_slide(blank_slide_layout) 49 | slide.shapes.add_picture(image_file_name, 0, 0, height=prs.slide_height, width=prs.slide_width) 50 | 51 | # Get today's date, formatted to MATLAB's default (e.g. 2017-Jul-09) 52 | today = date.today().strftime('%d-%b-%Y') 53 | 54 | # make filename 55 | reportFile = today + '_report.pptx' 56 | 57 | # Initialize presentation 58 | prs = Presentation() 59 | prs.slide_height = 5143500 # Widescreen aspect ratio 60 | title_slide_layout = prs.slide_layouts[0] # add title slide 61 | slide = prs.slides.add_slide(title_slide_layout) 62 | title = slide.shapes.title 63 | subtitle = slide.placeholders[1] 64 | 65 | # Create title slide 66 | title.text = "Current Cycling Progress" 67 | subtitle.text = today 68 | 69 | # CD to directory with most recent images 70 | os.chdir(path_images + '\\' + batch_name + '\\') 71 | 72 | # Add .png files in this directory. Start with summary figures 73 | all_images = glob.glob('*.png') 74 | for file in all_images: 75 | if "summary" in file: 76 | addImageSlide(file) 77 | 78 | # Cell "spec sheets" 79 | for file in all_images: 80 | if "summary" not in file: 81 | addImageSlide(file) 82 | 83 | # Change to directory for saving reports 84 | os.chdir(path_reports) 85 | 86 | # Create file names 87 | reportFileFull = path_reports + '\\' + reportFile 88 | reportFileFullPDF = path_reports + '\\' + reportFile.replace('pptx','pdf') 89 | 90 | # Save powerpoint 91 | prs.save(reportFileFull) 92 | # Convert to PDF 93 | PPTtoPDF(reportFileFull,reportFileFullPDF) -------------------------------------------------------------------------------- /sendemail.m: -------------------------------------------------------------------------------- 1 | function props = sendemail(email_list,subject,message,attachment) 2 | %% sendemail: send email from gmail account 3 | 4 | % Pradyumna 5 | % June 2008 6 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 7 | %Your gmail ID and password 8 | %(from which email ID you would like to send the mail) 9 | mail = 'chuehbatteries@gmail.com'; %Your GMail email address 10 | password = 'fake_password'; %Your GMail password 11 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 12 | 13 | if nargin == 1 14 | message = subject; 15 | subject = ''; 16 | elseif nargin == 2 17 | message = ''; 18 | attachment = ''; 19 | elseif nargin == 3 20 | attachment = ''; 21 | end 22 | 23 | %% Set up Gmail SMTP service. 24 | % Then this code will set up the preferences properly: 25 | setpref('Internet','E_mail',mail); 26 | setpref('Internet','SMTP_Server','smtp.gmail.com'); 27 | setpref('Internet','SMTP_Username',mail); 28 | setpref('Internet','SMTP_Password',password); 29 | 30 | % Gmail server. 31 | props = java.lang.System.getProperties; 32 | props.setProperty('mail.smtp.auth','true'); 33 | props.setProperty('mail.smtp.socketFactory.class', 'javax.net.ssl.SSLSocketFactory'); 34 | props.setProperty('mail.smtp.socketFactory.port','465'); 35 | props.setProperty('mail.smtp.starttls.enable', 'true'); 36 | 37 | %% Send the email 38 | for i=1:numel(email_list) 39 | if nargin == 4 40 | sendmail(email_list{i},subject,message,attachment) 41 | else 42 | sendmail(email_list{i},subject,message) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /taskscheduler.bat: -------------------------------------------------------------------------------- 1 | "C:\Program Files\MATLAB\R2017a\bin\matlab.exe" -r "run('D:\Users\chueh\Documents\GitHub\BMS-autoanalysis\auto_analysis.m');quit" -------------------------------------------------------------------------------- /vline.m: -------------------------------------------------------------------------------- 1 | function hhh=vline(x,in1,in2) 2 | 3 | % function h=vline(x, linetype, label) 4 | 5 | % 6 | 7 | % Draws a vertical line on the current axes at the location specified by 'x'. Optional arguments are 8 | 9 | % 'linetype' (default is 'r:') and 'label', which applies a text label to the graph near the line. The 10 | 11 | % label appears in the same color as the line. 12 | 13 | % 14 | 15 | % The line is held on the current axes, and after plotting the line, the function returns the axes to 16 | 17 | % its prior hold state. 18 | 19 | % 20 | 21 | % The HandleVisibility property of the line object is set to "off", so not only does it not appear on 22 | 23 | % legends, but it is not findable by using findobj. Specifying an output argument causes the function to 24 | 25 | % return a handle to the line, so it can be manipulated or deleted. Also, the HandleVisibility can be 26 | 27 | % overridden by setting the root's ShowHiddenHandles property to on. 28 | 29 | % 30 | 31 | % h = vline(42,'g','The Answer') 32 | 33 | % 34 | 35 | % returns a handle to a green vertical line on the current axes at x=42, and creates a text object on 36 | 37 | % the current axes, close to the line, which reads "The Answer". 38 | 39 | % 40 | 41 | % vline also supports vector inputs to draw multiple lines at once. For example, 42 | 43 | % 44 | 45 | % vline([4 8 12],{'g','r','b'},{'l1','lab2','LABELC'}) 46 | 47 | % 48 | 49 | % draws three lines with the appropriate labels and colors. 50 | 51 | % 52 | 53 | % By Brandon Kuczenski for Kensington Labs. 54 | 55 | % brandon_kuczenski@kensingtonlabs.com 56 | 57 | % 8 November 2001 58 | 59 | 60 | 61 | if length(x)>1 % vector input 62 | 63 | for I=1:length(x) 64 | 65 | switch nargin 66 | 67 | case 1 68 | 69 | linetype='r:'; 70 | 71 | label=''; 72 | 73 | case 2 74 | 75 | if ~iscell(in1) 76 | 77 | in1={in1}; 78 | 79 | end 80 | 81 | if I>length(in1) 82 | 83 | linetype=in1{end}; 84 | 85 | else 86 | 87 | linetype=in1{I}; 88 | 89 | end 90 | 91 | label=''; 92 | 93 | case 3 94 | 95 | if ~iscell(in1) 96 | 97 | in1={in1}; 98 | 99 | end 100 | 101 | if ~iscell(in2) 102 | 103 | in2={in2}; 104 | 105 | end 106 | 107 | if I>length(in1) 108 | 109 | linetype=in1{end}; 110 | 111 | else 112 | 113 | linetype=in1{I}; 114 | 115 | end 116 | 117 | if I>length(in2) 118 | 119 | label=in2{end}; 120 | 121 | else 122 | 123 | label=in2{I}; 124 | 125 | end 126 | 127 | end 128 | 129 | h(I)=vline(x(I),linetype,label); 130 | 131 | end 132 | 133 | else 134 | 135 | switch nargin 136 | 137 | case 1 138 | 139 | linetype='r:'; 140 | 141 | label=''; 142 | 143 | case 2 144 | 145 | linetype=in1; 146 | 147 | label=''; 148 | 149 | case 3 150 | 151 | linetype=in1; 152 | 153 | label=in2; 154 | 155 | end 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | g=ishold(gca); 166 | 167 | hold on 168 | 169 | 170 | 171 | y=get(gca,'ylim'); 172 | 173 | h=plot([x x],y,linetype); 174 | 175 | if length(label) 176 | 177 | xx=get(gca,'xlim'); 178 | 179 | xrange=xx(2)-xx(1); 180 | 181 | xunit=(x-xx(1))/xrange; 182 | 183 | if xunit<0.8 184 | 185 | text(x+0.01*xrange,y(1)+0.1*(y(2)-y(1)),label,'color',get(h,'color')) 186 | 187 | else 188 | 189 | text(x-.05*xrange,y(1)+0.1*(y(2)-y(1)),label,'color',get(h,'color')) 190 | 191 | end 192 | 193 | end 194 | 195 | 196 | 197 | if g==0 198 | 199 | hold off 200 | 201 | end 202 | 203 | set(h,'tag','vline','handlevisibility','off') 204 | 205 | end % else 206 | 207 | 208 | 209 | if nargout 210 | 211 | hhh=h; 212 | 213 | end 214 | 215 | --------------------------------------------------------------------------------