From f2742af437dc1bb4e60b9af8f8eadb06ebd84fe2 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 10 Nov 2022 09:28:52 +0100 Subject: [PATCH 1/4] Prevent scheduler from emptying the battery at the end of the scheduling horizon, using naive forecasting (forward filling the last known price); note that in case of a negative price, this would still result in emptying the battery; switching to naive forecasting with a seasonal periodicity of 1 day or 1 week would give inconsistent results, because they will then depend on how long a perice period has been loaded in the price bdf. Signed-off-by: F.N. Claessen --- flexmeasures/data/models/planning/storage.py | 4 ++-- flexmeasures/data/models/planning/utils.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/flexmeasures/data/models/planning/storage.py b/flexmeasures/data/models/planning/storage.py index ddc5ce793..030c81d44 100644 --- a/flexmeasures/data/models/planning/storage.py +++ b/flexmeasures/data/models/planning/storage.py @@ -55,7 +55,7 @@ def schedule( beliefs_before=belief_time, price_sensor=consumption_price_sensor, sensor=sensor, - allow_trimmed_query_window=True, + allow_trimmed_query_window=False, ) down_deviation_prices, (start, end) = get_prices( (start, end), @@ -63,7 +63,7 @@ def schedule( beliefs_before=belief_time, price_sensor=production_price_sensor, sensor=sensor, - allow_trimmed_query_window=True, + allow_trimmed_query_window=False, ) start = pd.Timestamp(start).tz_convert("UTC") diff --git a/flexmeasures/data/models/planning/utils.py b/flexmeasures/data/models/planning/utils.py index d4442583d..da91c500f 100644 --- a/flexmeasures/data/models/planning/utils.py +++ b/flexmeasures/data/models/planning/utils.py @@ -203,9 +203,22 @@ def get_prices( ) query_window = (first_event_start, last_event_end) else: - raise UnknownPricesException( - f"Prices partially unknown for planning window (sensor {price_sensor.id})." + index = initialize_index( + start=query_window[0], + end=query_window[1], + resolution=resolution, ) + price_df = price_df.reindex(index) + # or to also forward fill intermediate NaN values, use: price_df = price_df.ffill().bfill() + price_df[: price_df.first_valid_index()] = price_df[ + price_df.index == price_df.first_valid_index() + ].values[0] + price_df[price_df.last_valid_index() :] = price_df[ + price_df.index == price_df.last_valid_index() + ].values[0] + # raise UnknownPricesException( + # f"Prices partially unknown for planning window (sensor {price_sensor.id})." + # ) return price_df, query_window From 3335d3427a258081348350f1b860abd8190d29ee Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 24 Nov 2022 23:31:42 +0100 Subject: [PATCH 2/4] Log a warning in case of extending prices to the edges of the planning window Signed-off-by: F.N. Claessen --- flexmeasures/data/models/planning/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/flexmeasures/data/models/planning/utils.py b/flexmeasures/data/models/planning/utils.py index da91c500f..60893086f 100644 --- a/flexmeasures/data/models/planning/utils.py +++ b/flexmeasures/data/models/planning/utils.py @@ -199,10 +199,16 @@ def get_prices( first_event_start = price_df.first_valid_index() last_event_end = price_df.last_valid_index() + resolution current_app.logger.warning( - f"Prices partially unknown for planning window (sensor {price_sensor.id}). Trimming planning window (from {query_window[0]} until {query_window[-1]}) to {first_event_start} until {last_event_end}." + f"Prices partially unknown for planning window (sensor {price_sensor.id}). " + f"Trimming planning window (from {query_window[0]} until {query_window[-1]}) to {first_event_start} until {last_event_end}." ) query_window = (first_event_start, last_event_end) else: + current_app.logger.warning( + f"Prices partially unknown for planning window (sensor {price_sensor.id}). " + f"Assuming the first price is valid from the start of the planning window ({query_window[0]}), " + f"and the last price is valid until the end of the planning window ({query_window[-1]})." + ) index = initialize_index( start=query_window[0], end=query_window[1], @@ -216,9 +222,6 @@ def get_prices( price_df[price_df.last_valid_index() :] = price_df[ price_df.index == price_df.last_valid_index() ].values[0] - # raise UnknownPricesException( - # f"Prices partially unknown for planning window (sensor {price_sensor.id})." - # ) return price_df, query_window From da3e304d9ff3154c170b3d706a33f54157cb9fc9 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 1 Dec 2022 22:04:26 +0100 Subject: [PATCH 3/4] Update docstring with description of new behaviour Signed-off-by: F.N. Claessen --- flexmeasures/data/models/planning/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/flexmeasures/data/models/planning/utils.py b/flexmeasures/data/models/planning/utils.py index 60893086f..c8a6624ff 100644 --- a/flexmeasures/data/models/planning/utils.py +++ b/flexmeasures/data/models/planning/utils.py @@ -163,7 +163,13 @@ def get_prices( sensor: Optional[Sensor] = None, allow_trimmed_query_window: bool = True, ) -> Tuple[pd.DataFrame, Tuple[datetime, datetime]]: - """Check for known prices or price forecasts, trimming query window accordingly if allowed.""" + """Check for known prices or price forecasts. + + If so allowed, the query window is trimmed according to the available data. + If not allowed, prices are extended to the edges of the query window: + - The first available price serves as a naive backcast. + - The last available price serves as a naive forecast. + """ # Look for the applicable price sensor if price_sensor is None: From 18020b3e1ad17fc47a6c874e38588a8277961e9b Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Thu, 1 Dec 2022 22:34:06 +0100 Subject: [PATCH 4/4] Changelog entry Signed-off-by: F.N. Claessen --- documentation/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 8c8dcbaa8..2ee1007c4 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -21,6 +21,7 @@ Bugfixes ----------- * The CLI command ``flexmeasures show beliefs`` now supports plotting time series data that includes NaN values, and provides better support for plotting multiple sensors that do not share the same unit [see `PR #516 `_] * Consistent CLI/UI support for asset lat/lng positions up to 7 decimal places (previously the UI rounded to 4 decimal places, whereas the CLI allowed more than 4) [see `PR #522 `_] +* Stop trimming the planning window in response to price availability, which is a problem when SoC targets occur outside of the available price window, by making a simplistic assumption about future prices [see `PR #538 `_] Infrastructure / Support ----------------------