Source code for src.datafev.algorithms.cluster.prioritization_llf

# The datafev framework

# Copyright (C) 2022,
# Institute for Automation of Complex Power Systems (ACS),
# E.ON Energy Research Center (E.ON ERC),
# RWTH Aachen University

# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
# Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


import pandas as pd


[docs]def leastlaxityfirst( inisoc, tarsoc, bcap, efficiency, p_socdep, p_chmax, p_re, leadtime, upperlimit ): """ This is a control algorithm that manages the real-time charging rates of the EV chargers in a cluster under given power limits. The control is based on the least-laxity-first rule prioritizing urgent demands. Parameters ---------- inisoc : dict of float Initial SOCs of EV batteries (0<inisoc[key]<1). tarsoc : dict of float Target SOCs of EVs (0<inisoc[key]<1). bcap : dict of float Battery capactiy of EVs (kWs). efficiency : dict of float Power conversion efficiencies of chargers. p_socdep : dict of dict SOC dependency of power capability of EV batteries. Dictionary values are dictionaries for a single EV in the cluster. In the EV dictionaries a key indicates a particular SOC range. An SOC range is defined by lower (SOC_LB) and upper bound of the SOC range(SOC_UB). The power that the EV battery can accept (kW) in a particular SOC range is indicated by the value of 'P_UB'. p_chmax : dict of float Maximum charge power capabilities of EVs (kW). p_re : dict of float Power requested by EVs (kW). leadtime : dict of int How long EVs are expected to stay connected (seconds). upperlimit : dict of float Upper limit of cluster power consumption (kW). Returns ------- p_charge : dict of float Dictionary containing the charge power (kW) to each EV connected in the cluster. """ # This will contain the laxity of EVs laxity = {} # Laxity is defined with the formula LAX=1-T_MIN/T_LEAD # T_MIN : Minimum time required to achieve target SOC (T_MIN>=0) # T_LEAD : Time until estimated departure (T_LEAD>0) for ev_id in inisoc.keys(): # Lead time until estimated departure T_LEAD = leadtime[ev_id] if inisoc[ev_id] >= tarsoc[ev_id]: # Minimum time required to acheive taget SOC is 0 if target SOC is already achieved T_MIN = 0 else: # Target SOC has not been achieved yet: T_MIN will have a postive value if p_socdep[ev_id] == None: # The EV battery does not have a specific charger power-SOC dependency limiting the power transfer # T_MIN is determined by p_chmax (max power that charger-EV pair can handle for whole SOC range T_MIN = (tarsoc[ev_id] - inisoc[ev_id]) * bcap[ev_id] / p_chmax[ev_id] else: # The EV battery has a specific charger power-SOC dependency limiting the power transfer table = pd.DataFrame(p_socdep[ev_id]).T # Current SOC range of the EV I = ( table[ (table["SOC_LB"] <= inisoc[ev_id]) & (inisoc[ev_id] < table["SOC_UB"]) ] ).index[0] # Target SOC range of the EV F = ( table[ (table["SOC_LB"] <= tarsoc[ev_id]) & (tarsoc[ev_id] < table["SOC_UB"]) ] ).index[0] if I == F: # The current SOC is in the same range as the target SOC # The EV can be charged with a constant power until target SOC is reached p_max_in_range = table.loc[I, "P_UB"] # Maximum feasible power input to EV can be smaller though (taking into account the charger rating) p_max_feassible = min(p_max_in_range, p_chmax[ev_id]) # T_MIN is determined by considering only p_max_feasible T_MIN = ( (tarsoc[ev_id] - inisoc[ev_id]) * bcap[ev_id] / p_max_feassible ) else: # T_MIN is determined by taking into account the variations in charge power capability with SOC change # T_MIN will be summation of the minimum time spent in each SOC range time_in_range = ( {} ) # Will contain the minimum time spent within a particular SOC range # Loop through all SOC ranges to calculate the minimum time spent in each SOC range for r in range(I, F + 1): # Parameters for the specific SOC range soc_ub_in_range = table.loc[r, "SOC_UB"] soc_lb_in_range = table.loc[r, "SOC_LB"] p_max_in_range = table.loc[r, "P_UB"] # Maximum feasible power input to EV in this SOC range (taking into account the charger rating) p_max_feassible = min(p_max_in_range, p_chmax[ev_id]) # Calculation of minimum time spent in specific SOC range through charging if r == I: # From initial SOC to upper bound of this particular range time_in_range[r] = ( (soc_ub_in_range - inisoc[ev_id]) * bcap[ev_id] / p_max_feassible ) elif r < F: # From lower to upper bound of this particular range time_in_range[r] = ( (soc_ub_in_range - soc_lb_in_range) * bcap[ev_id] / p_max_feassible ) else: # r==F # From lower bound of this particular range to target SOC time_in_range[r] = ( (tarsoc[ev_id] - soc_lb_in_range) * bcap[ev_id] / p_max_feassible ) T_MIN = sum(time_in_range[r] for r in range(I, F + 1)) # Laxity of this EV's charging demand LAX = 1 - T_MIN / T_LEAD # Store in the laxity dictionary laxity[ev_id] = LAX # Sorting EVs according to their laxity (least laxity first) vehicles_sorted = (pd.Series(laxity).sort_values(ascending=True)).index p_charge = {} # Will contain the charge power consumed by the EVs free_margin = upperlimit # Cluster level constraint for ev in vehicles_sorted: # The power that charger wants to withdraw from grid to meet EV's request p_max_to_cu = p_re[ev] / efficiency[ev] if p_max_to_cu <= free_margin: # The grid has enough margin to suppy the requested amount p_to_ev = p_max_to_cu * efficiency[ev] else: # The grid does not have enough margin to supply the requested amount p_to_ev = free_margin * efficiency[ev] # This EV will get p_to_ev amount of power in this control horizon p_charge[ev] = p_to_ev # Available margin is closed after dedicating certain capacity to the EVs free_margin -= p_to_ev / efficiency[ev] return p_charge
if __name__ == "__main__": import pandas as pd import numpy as np ########################################################################### # Input parameters PEV = 50 # Maximum charge power that the EV battery can accept ch_eff = 1.0 # Power conversion efficiency of the charger N = 16 # Number of connected EVs in the system CAP = 55 * 3600 # Battery capacity of EVs upperlimit = 0.5 * N * PEV # Aggregate power consumption constraint of the cluster sch_horizon = 300 # Decision taken for the next 300 seconds (5 minutes) # SOC dependency of the maximum charge power to the EV battery pow_soc_dep_table = { 0: {"SOC_LB": 0.0, "SOC_UB": 0.5, "P_UB": 50}, 1: {"SOC_LB": 0.5, "SOC_UB": 0.7, "P_UB": 40}, 2: {"SOC_LB": 0.7, "SOC_UB": 1.0, "P_UB": 30}, } # Demand specifications np.random.seed(0) p_re = {} p_chmax = {} efficiency = {} bcap = {} tarsoc = {} leadtime = {} inisoc = {} p_socdep = {} for n in range(1, N + 1): # EV IDs evid = "EV" + str(n) # Maximum charge power to EV p_chmax[evid] = PEV # Conversion efficiency efficiency[evid] = ch_eff # Battery capacity (given in kWs) bcap[evid] = CAP # Current SOCs are between 40%-60% inisoc[evid] = np.random.uniform(low=0.4, high=0.8) # Target SOCs are 90% tarsoc[evid] = 0.9 # EVs have 5-10 min till departure. # The values are given in number of time steps until departure times leadtime[evid] = int(np.random.uniform(low=sch_horizon, high=sch_horizon * 2)) # SOC dependency of power capability of EV batteries p_socdep[evid] = pow_soc_dep_table # EVs want to consume the maximum feasible power in their SOC range table = pd.DataFrame(pow_soc_dep_table).T inisoc_range = ( table[(table["SOC_LB"] <= inisoc[evid]) & (inisoc[evid] < table["SOC_UB"])] ).index[0] p_re[evid] = min(PEV, table.loc[inisoc_range, "P_UB"]) ########################################################################### print("The cluster with total installed capacity of:", N * PEV, "kW") print() print("...has a power limit of:", upperlimit, "kW") print() print( "...controlling the real-time chaging rates of the EVs with charging demands:" ) inputs = pd.DataFrame(columns=["Current SOC", "Target SOC", "Estimate Departure"]) inputs["Current SOC"] = pd.Series(inisoc) inputs["Target SOC"] = pd.Series(tarsoc) inputs["Estimate Departure"] = pd.Series(leadtime) print(inputs) print() print("Executing the control algorithm...") p_ref = leastlaxityfirst( inisoc, tarsoc, bcap, efficiency, p_socdep, p_chmax, p_re, leadtime, upperlimit ) print("Resulting profile:") result = inputs.copy() result["Controlled Consumption"] = pd.Series(p_ref) print(result) print()