Source code for fleetrl.utils.battery_degradation.empirical_degradation

import numpy as np

from fleetrl.fleet_env.config.time_config import TimeConfig
from fleetrl.utils.battery_degradation.batt_deg import BatteryDegradation
[docs] class EmpiricalDegradation(BatteryDegradation): def __init__(self, init_soh: float, num_cars: int): """ Initialising the Degradation instance - http://queenbattery.com.cn/our-products/677-lg-e63-376v-63ah-li-po-li-polymer-battery-cell.html?search_query=lg+e63&results=1 - read off the graphs in section 4: cycle and calendar aging :param init_soh: Initial state of health, assumed same for each EV :param num_cars: How many EVs are being optimized """ self.cycle_loss_11 = 0.000125 # Cycle loss per full cycle (100% DoD discharge and charge) at 11 kW self.cycle_loss_22 = 0.000125 # Cycle loss per full cycle (100% DoD discharge and charge) at 11 kW self.cycle_loss_43 = 0.000167 # Cycle loss per full cycle (100% DoD discharge and charge) at 43 kW self.calendar_aging_0 = 0.0065 # Calendar aging per year if battery at 0% SoC self.calendar_aging_40 = 0.0293 # Calendar aging per year if battery at 40% SoC self.calendar_aging_90 = 0.065 # Calendar aging per year if battery at 90% SoC self.init_soh = init_soh self.num_cars = num_cars self.soh = np.ones(self.num_cars) * self.init_soh
[docs] def calculate_degradation(self, soc_log: list, charging_power: float, time_conf: TimeConfig, temp: float) -> np.array: """ Similar to non-linear SEI, the most recent event is taken, and the linear-based degradation is calculated. No rainflow counting, thus degradation is computed for each time step. - find out the most recent entries in the soc list - get old and new soc - get average soc - compute cycle and calendar based on avg soc and charging power :param soc_log: Historical log of SOC :param charging_power: EVSE power in kW :param time_conf: time config object :param temp: temperature :return: Degradation, float """ # compute sorted soc list based on the log records of the episode so far # go from: t1:[soc_car1, soc_car2, ...], t2:[soc_car1, soc_car2,...] # to this: car 1: [soc_t1, soc_t2, ...], car 2: [soc_t1, soc_t2, ...] sorted_soc_list = [] # range(len(soc_log[0])) gives the number of cars for j in range(len(soc_log[0])): # range(len(soc_log)) gives the number of time steps that the cars go through sorted_soc_list.append([soc_log[i][j] for i in range(len(soc_log))]) # empty list, appends a degradation kWh value for each car degradation = [] for i in range(len(sorted_soc_list)): # get old and new soc old_soc = sorted_soc_list[i][-2] new_soc = sorted_soc_list[i][-1] # compute average for calendar aging avg_soc = (old_soc + new_soc) / 2 # find the closest avg soc for calendar aging cal_soc = np.asarray([0, 40, 90]) closest_index = np.abs(cal_soc - avg_soc).argmin() closest = cal_soc[closest_index] if closest == 0: cal_aging = self.calendar_aging_0 * time_conf.dt / 8760 elif closest == 40: cal_aging = self.calendar_aging_40 * time_conf.dt / 8760 elif closest == 90: cal_aging = self.calendar_aging_90 * time_conf.dt / 8760 else: cal_aging = None raise RuntimeError("Closest calendar aging SoC not recognised.") # calculate DoD of timestep dod = abs(new_soc - old_soc) # distinguish between high and low power charging according to input graph data if charging_power <= 22.0: cycle_loss = dod * self.cycle_loss_11 / 2 # convert to equivalent full cycles, that's why divided by 2 else: cycle_loss = dod * self.cycle_loss_43 / 2 # convert to equivalent full cycles, that's why divided by 2 # aggregate calendar and cyclic aging and append to degradation list degradation.append(cal_aging + cycle_loss) self.soh -= degradation # print(f"emp soh: {self.soh}") return np.array(degradation)