# 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]class ChargingUnit(object):
"""
Simulation model of EV chargers.
"""
def __init__(self, cu_id, p_max_ch, p_max_ds, efficiency):
"""
EV chargers are defined by power capability parameters.
Parameters
----------
cu_id : str
Identifier of the charger.
p_max_ch : float
Maximum charge power that can be supplied to EV battery (kW).
p_max_ds : float
Maximum discharge power that can be withdrawn from EV battery (kW).
efficiency : float
Power conversion efficiency (0<efficiency<1).
This affects the power that the charger withdraw/injects from/to
grid.
Returns
-------
None.
"""
self.type = "CU"
self.id = cu_id
self.p_max_ch = p_max_ch
self.p_max_ds = p_max_ds
self.eff = efficiency
self.connected_ev = None
self.connection_dataset = pd.DataFrame(
columns=["EV ID", "Connection", "Disconnection"]
)
self.supplied_power = pd.Series(dtype=float)
self.consumed_power = pd.Series(dtype=float)
self.schedule_pow = {}
self.schedule_soc = {}
[docs] def connect(self, ts, ev):
"""
This method connects an EV to the charger. It is called in
execution of arrival routines.
Parameters
----------
ts : datetime.datetime
Connection time.
ev : ElectricVehicle
Connected EV object.
Returns
-------
None.
"""
self.connected_ev = ev
ev.connected_cu = self
dataset_ind = len(self.connection_dataset) + 1
self.connection_dataset.loc[dataset_ind, "EV ID"] = ev.vehicle_id
self.connection_dataset.loc[dataset_ind, "Connection"] = ts
[docs] def disconnect(self, ts):
"""
This method disconnects the connected EV from the charger. It is called
in execution of departure routines.
Parameters
----------
ts : datetime.datetime
Disconnection time.
Returns
-------
None.
"""
self.connected_ev.connected_cu = None
self.connected_ev = None
dataset_ind = max(self.connection_dataset.index)
self.connection_dataset.loc[dataset_ind, "Disconnection"] = ts
[docs] def supply(self, ts, tdelta, p):
"""
This method triggers 'charge' method of the connected EV.
The connected EV charges with 'p' power during current time step.
The charger consumes (for p>0) or inject (for p<0) from/to grid.
Parameters
----------
ts : datetime.datetime
Current time.
tdelta : datetime.timedelta
Length of time step.
p : float
Charge power (kW).
p>0 indicates EV charging and power consumption from the grid.
p<0 indicates EV discharging and power injection to the grid.
Returns
-------
None.
"""
self.connected_ev.charge(ts, tdelta, p)
self.supplied_power[ts] = p
self.consumed_power[ts] = p / self.eff if p > 0 else p * self.eff
[docs] def set_schedule(self, ts, schedule_pow, schedule_soc):
"""
This method assigns a charging schedule to the charger.
Parameters
----------
ts : datetime.datetime
Current time.
schedule_pow : pandas.Series
Time indexed power schedule.
Each index indicates a time step in the scheduling horizon.
Each value indicates how much power the charger should supply
(kW) during a particular time step (i.e. index value).
schedule_soc : pandas.Series
Time indexed SOC schedule.
Each index indicates a time step in the scheduling horizon.
Each value indicates the SOC (0<soc<1) that the connected EV
battery should achieve by a partiuclar time step (i.e. index).
Returns
-------
None.
"""
self.schedule_pow[ts] = schedule_pow
self.schedule_soc[ts] = schedule_soc
self.set_active_schedule(ts)
[docs] def set_active_schedule(self, ts):
"""
A single charger may be assigned with multiple schedules. For instance,
it may have a schedule for the connected vehicle and another schedule
for an EV that has reservation in the future. This method marks the one
that has been set at 'ts' as the 'active schedule' so that it is
considered to be 'the schedule' for the current charging event.
Parameters
----------
ts : datetime.datetime
Time at which the schedule that should be activated was set.
Returns
-------
None.
"""
self.active_schedule_instance = ts
[docs] def uncontrolled_supply(self, ts, step):
"""
This method is run to execute the uncontrolled charging behavior.
Parameters
----------
ts : datetime.datetime
Current time.
step : datetime.timedelta
Length of time step.
Returns
-------
None.
"""
# Current SOC of the EV
ev_soc = self.connected_ev.soc[ts]
# If the current SOC is smaller than 1
# Then EV would charge with max feasible power
if ev_soc < 1:
# EV battery capacity
ev_bcap = self.connected_ev.bCapacity
# Limit due to the battery capacity of EV
lim_ev_batcap = (1 - ev_soc) * ev_bcap
# Limit due to the charger power capability
lim_ch_pow = self.p_max_ch * step.seconds
if type(self.connected_ev.pow_soc_table) != type(None):
# The EV battery has a specific charger power-SOC dependency
# limiting the power transfer
table = self.connected_ev.pow_soc_table
soc_range = (
table[(table["SOC_LB"] <= ev_soc) & (ev_soc < table["SOC_UB"])]
).index[0]
# Limit due to the SOC dependency of charge power
p_max = table.loc[soc_range, "P_UB"]
lim_ev_socdep = p_max * step.seconds
e_max = min(lim_ev_batcap, lim_ch_pow, lim_ev_socdep)
else:
# The power transfer is only limited by the charger's power
# and battery capacity
e_max = min(lim_ev_batcap, lim_ch_pow)
# Average charge power during the simulation step
p_avr = e_max / step.seconds
# If the current SOC is 1
# Then the EV would not charge
else:
p_avr = 0
# Execution of the uncontrolled behavior in the simulation
self.supply(ts, step, p_avr)
[docs] def occupation_record(self, start, end, step):
"""
This method is run after simulation to analyze the occupation profile
of the charger.
Parameters
----------
start : datetime
Start of the period of investigation.
end : datetime
End of the period of investigation.
step : timedelta
Time resolution of the period of investiation.
Returns
-------
record : pandas.Series
Time indexed occupation record.
Each index indicates a time step in the investigated period.
Each value indicates the number of connected EVs (0 or 1) during
a particular time step (i.e. index value).
"""
period = pd.date_range(start=start, end=end, freq=step)
connections = pd.DataFrame(index=period)
connections.loc[:, :] = 0
for _id, con in self.connection_dataset.iterrows():
con_start = con["Connection"]
con_end = con["Disconnection"]
connections.loc[con_start:con_end, _id] = 1
record = connections.sum(axis=1)
return record