def test_process_beep_cycle_data_for_initial_halfcell_analysis(self): ia = IntracellAnalysis( os.path.join(TEST_FILE_DIR, 'cathode_clean_cc_charge_exptl_aligned.csv'), os.path.join( TEST_FILE_DIR, 'anode_secondMeasure_clean_cc_charge_exptl_aligned.csv'), cycle_type='rpt_0.2C', step_type=0) real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile = \ ia.process_beep_cycle_data_for_initial_halfcell_analysis(self.cell_struct, step_type=0) self.assertAlmostEqual( real_cell_initial_charge_profile_aligned['Voltage_aligned'].min(), 2.742084, 5) self.assertAlmostEqual( real_cell_initial_charge_profile_aligned['Voltage_aligned'].max(), 4.196994, 5) self.assertAlmostEqual( real_cell_initial_charge_profile['Voltage'].min(), 2.703006, 5) self.assertAlmostEqual( real_cell_initial_charge_profile['Voltage'].max(), 4.196994, 5) self.assertAlmostEqual( real_cell_initial_charge_profile['charge_capacity'].min(), 0.0, 4) self.assertAlmostEqual( real_cell_initial_charge_profile['charge_capacity'].max(), 4.539547, 5)
def test_intracell_halfcell_matching_v2_mock(self): ia = IntracellAnalysis(self.cathode_file, self.anode_file, cycle_type='rpt_0.2C', step_type=0) # real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile = \ ia.process_beep_cycle_data_for_initial_halfcell_analysis( self.cell_struct) test_opt = np.array([0.999459, -4.1740795, 1.0, 0.1, 0.1]) (PE_pristine_matched, NE_pristine_matched, df_real_interped, emulated_full_cell_interped) = ia.halfcell_initial_matching_v2( test_opt, real_cell_initial_charge_profile_aligned, ia.pe_pristine, ia.ne_1_pristine, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg) self.assertAlmostEqual(PE_pristine_matched['SOC_aligned'].min(), -93.08943089430896, 5) self.assertAlmostEqual(PE_pristine_matched['SOC_aligned'].max(), 110.16260162601628, 5) self.assertAlmostEqual(PE_pristine_matched['Voltage_aligned'].min(), 2.865916, 5) self.assertAlmostEqual(PE_pristine_matched['Voltage_aligned'].max(), 4.299219386998656, 5)
def create_features(self): """ Args: processed_cycler_run (beep.structure.ProcessedCyclerRun) params_dict (dict): dictionary of parameters governing how the ProcessedCyclerRun object gets featurized. These could be filters for column or row operations parameters_path (str): Root directory storing project parameter files. cell_info_path (str): Root directory for cell half cell data Returns: (pd.DataFrame) containing the cell material parameters for the first and second diagnotics as a single row dataframe """ ia = IntracellAnalysis( self.hyperparameters["cathode_file"], self.hyperparameters["anode_file"], cycle_type=self.hyperparameters["diagnostic_cycle_type"], step_type=self.hyperparameters["step_type"] ) (cell_init_aligned, cell_init_profile, PE_matched, NE_matched) = ia.intracell_wrapper_init( self.datapath, ) eol_cycle_index_list = self.datapath.diagnostic_summary[ (self.datapath.diagnostic_summary.cycle_type == ia.cycle_type) & (self.datapath.diagnostic_summary.discharge_capacity > ia.THRESHOLD) ].cycle_index.to_list() # # initializations before for loop dataset_dict_of_cell_degradation_path = dict() real_cell_dict_of_profiles = dict() for i, cycle_index in enumerate(eol_cycle_index_list[0:2]): loss_dict, profiles_dict = ia.intracell_values_wrapper(cycle_index, self.datapath, cell_init_aligned, cell_init_profile, PE_matched, NE_matched, ) dataset_dict_of_cell_degradation_path.update(loss_dict) real_cell_dict_of_profiles.update(profiles_dict) degradation_df = pd.DataFrame(dataset_dict_of_cell_degradation_path, index=['LLI', 'LAM_PE', 'LAM_NE', 'x_NE_2', 'alpha_real', 'alpha_emulated', 'PE_upper_voltage', 'PE_lower_voltage', 'PE_upper_SOC', 'PE_lower_SOC', 'PE_mass', 'NE_upper_voltage', 'NE_lower_voltage', 'NE_upper_SOC', 'NE_lower_SOC', 'NE_mass', 'Li_mass' ]).T diag_0_names = ["diag_0_" + name for name in degradation_df.columns] diag_1_names = ["diag_1_" + name for name in degradation_df.columns] values = {0: degradation_df.iloc[0].tolist() + degradation_df.iloc[1].tolist()} features_df = pd.DataFrame(values, index=diag_0_names+diag_1_names).T self.features = features_df
def test_intracell_wrappers(self): ia = IntracellAnalysis(os.path.join(TEST_FILE_DIR, 'data-share', 'raw', 'cell_info', 'cathode_test.csv'), os.path.join(TEST_FILE_DIR, 'data-share', 'raw', 'cell_info', 'anode_test.csv'), cycle_type='rpt_0.2C', step_type=0) (cell_init_aligned, cell_init_profile, PE_matched, NE_matched) = ia.intracell_wrapper_init(self.cell_struct) eol_cycle_index_list = self.cell_struct.diagnostic_summary[ (self.cell_struct.diagnostic_summary.cycle_type == ia.cycle_type) & (self.cell_struct.diagnostic_summary.discharge_capacity > ia.THRESHOLD)].cycle_index.to_list() # # initializations before for loop dataset_dict_of_cell_degradation_path = dict() real_cell_dict_of_profiles = dict() for i, cycle_index in enumerate(eol_cycle_index_list): loss_dict, profiles_dict = ia.intracell_values_wrapper( cycle_index, self.cell_struct, cell_init_aligned, cell_init_profile, PE_matched, NE_matched, ) dataset_dict_of_cell_degradation_path.update(loss_dict) real_cell_dict_of_profiles.update(profiles_dict) degradation_df = pd.DataFrame( dataset_dict_of_cell_degradation_path, index=[ 'LLI', 'LAM_PE', 'LAM_NE', 'x_NE_2', 'alpha_real', 'alpha_emulated', 'PE_upper_voltage', 'PE_lower_voltage', 'PE_upper_SOC', 'PE_lower_SOC', 'PE_mass', 'NE_upper_voltage', 'NE_lower_voltage', 'NE_upper_SOC', 'NE_lower_SOC', 'NE_mass', 'Li_mass' ]).T print(degradation_df['LLI']) print(degradation_df['LAM_PE']) print(degradation_df['Li_mass']) self.assertAlmostEqual(degradation_df['LLI'].iloc[0], -9.999983, 5) self.assertAlmostEqual(degradation_df['LLI'].iloc[1], -9.999556, 5) self.assertAlmostEqual(degradation_df['LAM_PE'].iloc[0], 49.984768, 5) self.assertAlmostEqual(degradation_df['LAM_PE'].iloc[1], 49.984877, 5) self.assertAlmostEqual(degradation_df["Li_mass"].iloc[1], 12.312480, 3)
def test_process_beep_cycle_data_for_initial_halfcell_analysis_mock(self): ia = IntracellAnalysis('cathode_test.csv', 'anode_test.csv', cycle_type='rpt_0.2C', step_type=0) real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile = \ ia.process_beep_cycle_data_for_initial_halfcell_analysis(self.cell_struct, step_type=0) self.assertAlmostEqual( real_cell_initial_charge_profile_aligned['Voltage_aligned'].min(), 2.742084, 5) self.assertAlmostEqual( real_cell_initial_charge_profile_aligned['Voltage_aligned'].max(), 4.196994, 5) self.assertAlmostEqual( real_cell_initial_charge_profile['Voltage'].min(), 2.703006, 5) self.assertAlmostEqual( real_cell_initial_charge_profile['Voltage'].max(), 4.196994, 5) self.assertAlmostEqual( real_cell_initial_charge_profile['charge_capacity'].min(), 0.0, 4) self.assertAlmostEqual( real_cell_initial_charge_profile['charge_capacity'].max(), 4.539547, 5)
def test_intracell_get_dq_dv_mock(self): os.environ["BEEP_PROCESSING_DIR"] = TEST_FILE_DIR ia = IntracellAnalysis('cathode_test.csv', 'anode_test.csv', cycle_type='rpt_0.2C', step_type=0) # real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile = \ ia.process_beep_cycle_data_for_initial_halfcell_analysis(self.cell_struct) test_opt = np.array([0.999459, -4.1740795, 1.0, 0.1, 0.1]) (PE_p_m, NE_p_m, df_real_i, em_interped) = ia.halfcell_initial_matching_v2( test_opt, real_cell_initial_charge_profile_aligned, ia.pe_pristine, ia.ne_1_pristine, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg) real_cell_candidate_charge_profile_aligned = ia.process_beep_cycle_data_for_candidate_halfcell_analysis( self.cell_struct, real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile, 3) self.assertAlmostEqual( real_cell_candidate_charge_profile_aligned["Voltage_aligned"].min( ), 2.742084, 5) (PE_out_centered, NE_out_centered, dVdQ_over_SOC_real, dVdQ_over_SOC_emulated, df_real_interped, emulated_full_cell_interped ) = ia.get_dQdV_over_V_from_degradation_matching( test_opt, PE_p_m, NE_p_m, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg, real_cell_candidate_charge_profile_aligned) print(PE_out_centered) self.assertAlmostEqual(PE_out_centered["Voltage_aligned"].min(), 2.86591646, 5) self.assertAlmostEqual(NE_out_centered["Voltage_aligned"].min(), 0.011226739, 5) self.assertAlmostEqual(dVdQ_over_SOC_real["Voltage_aligned"].mean(), 3.45, 5) self.assertAlmostEqual(dVdQ_over_SOC_emulated["Voltage_aligned"].min(), 2.7, 5) self.assertAlmostEqual(dVdQ_over_SOC_emulated["Voltage_aligned"].max(), 4.2, 5) # (PE_upper_voltage, PE_lower_voltage, PE_upper_SOC, PE_lower_SOC, PE_mass, NE_upper_voltage, NE_lower_voltage, NE_upper_SOC, NE_lower_SOC, NE_mass, SOC_upper, SOC_lower, Li_mass) = get_halfcell_voltages(PE_out_centered, NE_out_centered) self.assertAlmostEqual(PE_upper_voltage, 4.28427357, 5) self.assertAlmostEqual(PE_lower_voltage, 3.56538024, 5) self.assertAlmostEqual(PE_upper_SOC, 99.0605427, 5) self.assertAlmostEqual(PE_lower_SOC, 48.851774, 5) self.assertAlmostEqual(PE_mass, 202.843024, 5) self.assertAlmostEqual(NE_upper_voltage, 0.0828635, 5) self.assertAlmostEqual(NE_lower_voltage, 0.86598357, 5) self.assertAlmostEqual(NE_upper_SOC, 95.1648351, 5) self.assertAlmostEqual(NE_lower_SOC, 42.30769230, 5) self.assertAlmostEqual(NE_mass, 192.6796998, 5) self.assertAlmostEqual(SOC_upper, 183.363318, 5) self.assertAlmostEqual(SOC_lower, 81.518334, 5) self.assertAlmostEqual(PE_upper_SOC, 99.06054279, 5) self.assertAlmostEqual(PE_lower_SOC, 48.8517745, 5) self.assertAlmostEqual(Li_mass, 185.2689422, 5)
def test_intracell(self): ia = IntracellAnalysis( os.path.join(TEST_FILE_DIR, 'cathode_clean_cc_charge_exptl_aligned.csv'), os.path.join( TEST_FILE_DIR, 'anode_secondMeasure_clean_cc_charge_exptl_aligned.csv'), cycle_type='rpt_0.2C', step_type=0) real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile = \ ia.process_beep_cycle_data_for_initial_halfcell_analysis(self.cell_struct) # Solving initial electrode matching to real full cell electrode_matching_bounds = ((0.8, 1.2), (-20.0, 20.0), (1, 1), (0.1, 0.1), (0.1, 0.1)) # (-1,1),(0.01,0.1),(0.01,0.1) opt_result_halfcell_initial_matching = differential_evolution( ia._get_error_from_halfcell_initial_matching, electrode_matching_bounds, args=(real_cell_initial_charge_profile_aligned, ia.pe_pristine, ia.ne_1_pristine, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg), strategy='best1bin', maxiter=1000, popsize=15, tol=0.001, mutation=0.5, recombination=0.7, seed=1, callback=None, disp=False, polish=True, init='latinhypercube', atol=0, updating='deferred', workers=-1, constraints=()) print(opt_result_halfcell_initial_matching) self.assertEqual(opt_result_halfcell_initial_matching.success, True) self.assertAlmostEqual(opt_result_halfcell_initial_matching.x[0], 0.999459, 5) self.assertAlmostEqual(opt_result_halfcell_initial_matching.x[1], -4.1740795, 6) # test_opt = np.array([0.999459, -4.1740795, 1.0, 0.1, 0.1]) (PE_pristine_matched, NE_pristine_matched, df_real_interped, emulated_full_cell_interped) = ia.halfcell_initial_matching_v2( opt_result_halfcell_initial_matching.x, real_cell_initial_charge_profile_aligned, ia.pe_pristine, ia.ne_1_pristine, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg) print(PE_pristine_matched) self.assertAlmostEqual(PE_pristine_matched['SOC_aligned'].min(), -4.675029, 5) self.assertAlmostEqual(PE_pristine_matched['SOC_aligned'].max(), 109.350057, 5) self.assertAlmostEqual(PE_pristine_matched['Voltage_aligned'].min(), 2.865916, 5) self.assertAlmostEqual(PE_pristine_matched['Voltage_aligned'].max(), 4.29917115, 5) eol_cycle_index_list = self.cell_struct.diagnostic_summary[ (self.cell_struct.diagnostic_summary.cycle_type == ia.cycle_type) & (self.cell_struct.diagnostic_summary.discharge_capacity > ia.threshold)].cycle_index.to_list() # # initial bounds (for first cycle); they expand as the cell degrades (see update further down). # allow negative degradation, as it may reflect gained capacity from electrolyte wetting or other phenomena degradation_bounds = ( (-10, 50), # LLI (-10, 50), # LAM_PE (-10, 50), # LAM_NE (1, 1), # (-1,1) x_NE_2 (0.1, 0.1), # (0.01,0.1) (0.1, 0.1), # (0.01,0.1) ) # # # initializations before for loop dataset_dict_of_cell_degradation_path = dict() real_cell_dict_of_profiles = dict() for i, cycle_index in enumerate(eol_cycle_index_list): real_cell_candidate_charge_profile_aligned = ia.process_beep_cycle_data_for_candidate_halfcell_analysis( self.cell_struct, real_cell_initial_charge_profile_aligned, real_cell_initial_charge_profile, cycle_index) degradation_optimization_result = differential_evolution( ia._get_error_from_degradation_matching, degradation_bounds, args=(PE_pristine_matched, NE_pristine_matched, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg, real_cell_candidate_charge_profile_aligned), strategy='best1bin', maxiter=100000, popsize=15, tol=0.001, mutation=0.5, recombination=0.7, seed=1, callback=None, disp=False, polish=True, init='latinhypercube', atol=0, updating='deferred', workers=-1, constraints=()) (PE_out_centered, NE_out_centered, dVdQ_over_SOC_real, dVdQ_over_SOC_emulated, df_real_interped, emulated_full_cell_interped ) = ia.get_dQdV_over_V_from_degradation_matching( degradation_optimization_result.x, PE_pristine_matched, NE_pristine_matched, ia.ne_2_pristine_pos, ia.ne_2_pristine_neg, real_cell_candidate_charge_profile_aligned) # (PE_upper_voltage, PE_lower_voltage, PE_upper_SOC, PE_lower_SOC, PE_mass, NE_upper_voltage, NE_lower_voltage, NE_upper_SOC, NE_lower_SOC, NE_mass, SOC_upper, SOC_lower, Li_mass) = get_halfcell_voltages(PE_out_centered, NE_out_centered) # LLI = degradation_optimization_result.x[0] LAM_PE = degradation_optimization_result.x[1] LAM_NE = degradation_optimization_result.x[2] x_NE_2 = degradation_optimization_result.x[3] alpha_real = degradation_optimization_result.x[4] alpha_emulated = degradation_optimization_result.x[5] tmp_dict = { cycle_index: [ LLI, LAM_PE, LAM_NE, x_NE_2, alpha_real, alpha_emulated, PE_upper_voltage, PE_lower_voltage, PE_upper_SOC, PE_lower_SOC, PE_mass, NE_upper_voltage, NE_lower_voltage, NE_upper_SOC, NE_lower_SOC, NE_mass, Li_mass ] } dataset_dict_of_cell_degradation_path.update(tmp_dict) real_cell_dict_of_profiles.update( {cycle_index: real_cell_candidate_charge_profile_aligned}) degradation_df = pd.DataFrame( dataset_dict_of_cell_degradation_path, index=[ 'LLI', 'LAM_PE', 'LAM_NE', 'x_NE_2', 'alpha_real', 'alpha_emulated', 'PE_upper_voltage', 'PE_lower_voltage', 'PE_upper_SOC', 'PE_lower_SOC', 'PE_mass', 'NE_upper_voltage', 'NE_lower_voltage', 'NE_upper_SOC', 'NE_lower_SOC', 'NE_mass', 'Li_mass' ]).T # print(degradation_df.iloc[0].to_list()) self.assertAlmostEqual(degradation_df['LLI'].iloc[0], -0.027076, 5) self.assertAlmostEqual(degradation_df['LAM_PE'].iloc[0], 0.06750165, 5) self.assertAlmostEqual(degradation_df['LAM_NE'].iloc[0], 0.3055425, 5) self.assertAlmostEqual(degradation_df['PE_upper_voltage'].iloc[0], 4.25095116, 5) self.assertAlmostEqual(degradation_df['PE_lower_voltage'].iloc[0], 3.62354913951, 5) self.assertAlmostEqual(degradation_df['PE_upper_SOC'].iloc[0], 95.7202505, 5) self.assertAlmostEqual(degradation_df['PE_mass'].iloc[0], 109.24224079, 5) self.assertAlmostEqual(degradation_df['NE_upper_voltage'].iloc[0], 0.050515360, 5) self.assertAlmostEqual(degradation_df['NE_lower_voltage'].iloc[0], 0.8564127166, 5) self.assertAlmostEqual(degradation_df['NE_upper_SOC'].iloc[0], 91.832460, 5) self.assertAlmostEqual(degradation_df['NE_mass'].iloc[0], 108.900146, 5) self.assertAlmostEqual(degradation_df['Li_mass'].iloc[0], 104.680978, 5)