Skip to content

Commit

Permalink
Merge pull request #332 from openego/features/resample-edisgo-simbev
Browse files Browse the repository at this point in the history
Automatically resample time series when applying charging strategies
  • Loading branch information
birgits authored Jan 10, 2023
2 parents f6d80b0 + 9a7e267 commit a01d0e5
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 21 deletions.
13 changes: 12 additions & 1 deletion edisgo/edisgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,15 @@ def apply_charging_strategy(self, strategy="dumb", **kwargs):
capacity of 22 kW and a minimum_charging_capacity_factor of 0.1 this would
result in a minimum charging power of 2.2 kW. Default: 0.1.
Notes
------
If the frequency of time series data in :class:`~.network.timeseries.TimeSeries`
(checked using :attr:`~.network.timeseries.TimeSeries.timeindex`) differs from
the frequency of SimBEV data, then the time series in
:class:`~.network.timeseries.TimeSeries` is first automatically resampled to
match the SimBEV data frequency and after determining the charging demand time
series resampled back to the original frequency.
"""
charging_strategy(self, strategy=strategy, **kwargs)

Expand Down Expand Up @@ -2256,7 +2265,9 @@ def check_integrity(self):

logging.info("Integrity check finished. Please pay attention to warnings.")

def resample_timeseries(self, method: str = "ffill", freq: str = "15min"):
def resample_timeseries(
self, method: str = "ffill", freq: str | pd.Timedelta = "15min"
):
"""
Resamples all generator, load and storage time series to a desired resolution.
Expand Down
39 changes: 29 additions & 10 deletions edisgo/flex_opt/charging_strategies.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from __future__ import annotations

import logging

from numbers import Number
from typing import TYPE_CHECKING

import numpy as np
import pandas as pd

if TYPE_CHECKING:
from edisgo import EDisGo

RELEVANT_CHARGING_STRATEGIES_COLUMNS = {
"dumb": [
"park_start_timesteps",
Expand Down Expand Up @@ -40,10 +48,10 @@
# wrong results if the timeindex of the edisgo object is not continuously
# (e.g. 2 weeks of the year)
def charging_strategy(
edisgo_obj,
strategy="dumb",
timestamp_share_threshold=0.2,
minimum_charging_capacity_factor=0.1,
edisgo_obj: EDisGo,
strategy: str = "dumb",
timestamp_share_threshold: Number = 0.2,
minimum_charging_capacity_factor: Number = 0.1,
):
"""
Applies charging strategy to set EV charging time series at charging parks.
Expand Down Expand Up @@ -107,12 +115,20 @@ def charging_strategy(
)
simbev_timedelta = timeindex[1] - timeindex[0]

assert edisgo_timedelta == simbev_timedelta, (
"The step size of the time series of the edisgo object differs from the"
f"simbev step size. The edisgo time delta is {edisgo_timedelta}, while"
f" the simbev time delta is {simbev_timedelta}. Make sure to use a "
f"matching step size."
)
resample = edisgo_timedelta != simbev_timedelta

if resample:
logger.warning(
f"The frequency of the time series data of the edisgo object differs from "
f"the simbev time series frequency. The edisgo frequency is "
f"{edisgo_timedelta}, while the simbev frequency is {simbev_timedelta}. "
f"The edisgo time series data "
f"will be resampled accordingly before applying the charging strategy. "
f"After applying the charging strategy all time series will be resampled "
f"to the original frequency of the edisgo time series data."
)

edisgo_obj.resample_timeseries(freq=simbev_timedelta)

if strategy == "dumb":
# "dumb" charging
Expand Down Expand Up @@ -292,6 +308,9 @@ def charging_strategy(
else:
raise ValueError(f"Strategy {strategy} has not yet been implemented.")

if resample:
edisgo_obj.resample_timeseries(freq=edisgo_timedelta)

# set reactive power time series to 0 Mvar
# fmt: off
edisgo_obj.timeseries.add_component_time_series(
Expand Down
4 changes: 3 additions & 1 deletion edisgo/network/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2135,7 +2135,9 @@ def _check_if_components_exist(
return set(component_names) - set(comps_not_in_network)
return component_names

def resample_timeseries(self, method: str = "ffill", freq: str = "15min"):
def resample_timeseries(
self, method: str = "ffill", freq: str | pd.Timedelta = "15min"
):
"""
Resamples all generator, load and storage time series to a desired resolution.
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def read(fname):
dev_requirements = [
"pytest",
"pytest-notebook",
"sphinx_rtd_theme",
"sphinx >= 4.3.0, < 5.1.0",
"sphinx_rtd_theme >=0.5.2",
"sphinx-autodoc-typehints",
"pre-commit",
"black",
Expand Down
22 changes: 15 additions & 7 deletions tests/flex_opt/test_charging_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,16 @@ def setup_class(cls):
timeindex = pd.date_range("1/1/2011", periods=24 * 7, freq="H")
cls.edisgo_obj.set_timeindex(timeindex)

cls.edisgo_obj.resample_timeseries()
cls.edisgo_obj.import_electromobility(cls.simbev_path, cls.tracbev_path)

def test_charging_strategy(self):
def test_charging_strategy(self, caplog):
charging_demand_lst = []

ts = self.edisgo_obj.timeseries

for strategy in self.charging_strategies:
charging_strategy(self.edisgo_obj, strategy=strategy)

ts = self.edisgo_obj.timeseries

# Check if all charging points have a valid chargingdemand_kWh > 0
df = ts.charging_points_active_power(self.edisgo_obj).loc[
:, (ts.charging_points_active_power(self.edisgo_obj) <= 0).any(axis=0)
Expand All @@ -50,7 +49,8 @@ def test_charging_strategy(self):
self.edisgo_obj, strategy="dumb", timestamp_share_threshold=0.5
)

ts = self.edisgo_obj.timeseries
# Check if resampling warning is raised
assert "The frequency of the time series data of the edisgo object differs" in caplog.text

# Check if all charging points have a valid chargingdemand_kWh > 0
df = ts.charging_points_active_power(self.edisgo_obj).loc[
Expand All @@ -64,8 +64,6 @@ def test_charging_strategy(self):
self.edisgo_obj, strategy="reduced", minimum_charging_capacity_factor=0.5
)

ts = self.edisgo_obj.timeseries

# Check if all charging points have a valid chargingdemand_kWh > 0
df = ts.charging_points_active_power(self.edisgo_obj).loc[
:, (ts.charging_points_active_power(self.edisgo_obj) <= 0).any(axis=0)
Expand All @@ -83,3 +81,13 @@ def test_charging_strategy(self):
(_.round(4) == charging_demand_lst[0].round(4)).all()
for _ in charging_demand_lst
)

# ##################### check time index #####################
assert ts._loads_active_power.index.freqstr == "H"
# change time index to quarter-hourly
timeindex = pd.date_range("1/1/2011", periods=24 * 7, freq="0.25H")
self.edisgo_obj.set_timeindex(timeindex)
charging_strategy(
self.edisgo_obj, strategy="dumb"
)
assert ts._loads_active_power.index.freqstr == "15T"
10 changes: 9 additions & 1 deletion tests/network/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import pandas as pd
import pytest

from pandas.util.testing import assert_frame_equal, assert_series_equal
from pandas.util.testing import (
assert_frame_equal,
assert_index_equal,
assert_series_equal,
)

from edisgo import EDisGo
from edisgo.network import timeseries
Expand Down Expand Up @@ -2360,6 +2364,7 @@ def test_resample_timeseries(self):

len_timeindex_orig = len(self.edisgo.timeseries.timeindex)
mean_value_orig = self.edisgo.timeseries.generators_active_power.mean()
index_orig = self.edisgo.timeseries.timeindex.copy()

# test up-sampling
self.edisgo.timeseries.resample_timeseries()
Expand All @@ -2375,6 +2380,9 @@ def test_resample_timeseries(self):
atol=1e-5,
)
).all()
# check if index is the same after resampled back
self.edisgo.timeseries.resample_timeseries(freq="1h")
assert_index_equal(self.edisgo.timeseries.timeindex, index_orig)

# same tests for down-sampling
self.edisgo.timeseries.resample_timeseries(freq="2h")
Expand Down

0 comments on commit a01d0e5

Please sign in to comment.