From c0400ae6655004efdce83cc967439a5438004732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20H=C3=B6ning?= Date: Fri, 28 Oct 2022 13:45:09 +0200 Subject: [PATCH] various review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicolas Höning --- documentation/plugin/customisation.rst | 8 ++++-- flexmeasures/api/v3_0/sensors.py | 13 ++++----- flexmeasures/data/models/planning/utils.py | 31 +++++++++++++++++----- flexmeasures/data/services/scheduling.py | 4 +-- flexmeasures/data/tests/dummy_scheduler.py | 7 +++-- 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/documentation/plugin/customisation.rst b/documentation/plugin/customisation.rst index bbe51fafe..b694f2b6c 100644 --- a/documentation/plugin/customisation.rst +++ b/documentation/plugin/customisation.rst @@ -33,9 +33,13 @@ The following minimal example gives you an idea of the inputs and outputs: *args, **kwargs ): - """Just a dummy scheduler, advising to do nothing""" + """ + Just a dummy scheduler that always plans to consume at maximum capacity. + (Schedulers return positive values for consumption, and negative values for production) + """ return pd.Series( - 0, index=pd.date_range(start, end, freq=resolution, closed="left") + sensor.get_attribute("capacity_in_mw"), + index=pd.date_range(start, end, freq=resolution, closed="left"), ) diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index de1f80f9a..aedf09742 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -256,20 +256,21 @@ def trigger_schedule( # noqa: C901 .. :quickref: Schedule; Trigger scheduling job Trigger FlexMeasures to create a schedule for this sensor. - The assumption is that this sensor is the energy sensor on a flexible asset. + The assumption is that this sensor is the power sensor on a flexible asset. In this request, you can describe: - the schedule (start, unit, prior) - the flexibility model for the sensor (see below, only storage models are supported at the moment) - - the EMS the sensor operates in (inflexible device sensors, sensors which put a price on consumption and/or production) + - the EMS the sensor operates in (inflexible device sensors, and sensors that put a price on consumption and/or production) - Note: This endpoint does not support an EMS with multiple flexible sensors. This will happen in another endpoint. - See https://github.com/FlexMeasures/flexmeasures/issues/485 + Note: This endpoint does not support to schedule an EMS with multiple flexible sensors at once. This will happen in another endpoint. + See https://github.com/FlexMeasures/flexmeasures/issues/485. Until then, it is possible to call this endpoint for one flexible endpoint at a time + (considering already scheduled sensors as inflexible). Flexibility models apply to the sensor's asset type: - 1) For storage sensors (e.g. battery, charging stations), the schedule deals with the state of charge (SOC). + 1) For storage sensors (e.g. battery, charge points), the schedule deals with the state of charge (SOC). The possible flexibility parameters are: - soc-at-start (defaults to 0) @@ -365,7 +366,7 @@ def trigger_schedule( # noqa: C901 if unit == "kWh": start_value = start_value / 1000.0 - # Convert round-trip efficiency to dimensionless (to the [0,1] range) + # Convert round-trip efficiency to dimensionless (to the (0,1] range) if roundtrip_efficiency is not None: roundtrip_efficiency = roundtrip_efficiency.to( ur.Quantity("dimensionless") diff --git a/flexmeasures/data/models/planning/utils.py b/flexmeasures/data/models/planning/utils.py index 783033a6d..56fcda75a 100644 --- a/flexmeasures/data/models/planning/utils.py +++ b/flexmeasures/data/models/planning/utils.py @@ -17,9 +17,15 @@ def initialize_df( - columns: List[str], start: datetime, end: datetime, resolution: timedelta + columns: List[str], + start: datetime, + end: datetime, + resolution: timedelta, + inclusive: str, ) -> pd.DataFrame: - df = pd.DataFrame(index=initialize_index(start, end, resolution), columns=columns) + df = pd.DataFrame( + index=initialize_index(start, end, resolution, inclusive), columns=columns + ) return df @@ -28,16 +34,25 @@ def initialize_series( start: datetime, end: datetime, resolution: timedelta, + inclusive: str, ) -> pd.Series: - s = pd.Series(index=initialize_index(start, end, resolution), data=data) + s = pd.Series(index=initialize_index(start, end, resolution, inclusive), data=data) return s def initialize_index( - start: Union[date, datetime], end: Union[date, datetime], resolution: timedelta + start: Union[date, datetime], + end: Union[date, datetime], + resolution: timedelta, + inclusive: str = "left", ) -> pd.DatetimeIndex: + assert inclusive == "left" or inclusive == "right" i = pd.date_range( - start=start, end=end, freq=to_offset(resolution), closed="left", name="datetime" + start=start, + end=end, + freq=to_offset(resolution), + closed=inclusive, + name="datetime", ) return i @@ -101,7 +116,7 @@ def ensure_sensor_is_set(sensor) -> Sensor: # Check for min and max SOC, or get default from sensor if "soc_min" not in specs or specs["soc_min"] is None: sensor = ensure_sensor_is_set(sensor) - # Can't drain the EV battery by more than it contains + # Can't drain the storage by more than it contains specs["soc_min"] = sensor.get_attribute("min_soc_in_mwh", 0) if "soc_max" not in specs or specs["soc_max"] is None: sensor = ensure_sensor_is_set(sensor) @@ -234,6 +249,10 @@ def get_power_values( raise UnknownForecastException( f"Forecasts unknown for planning window. (sensor {sensor.id})" ) + if sensor.get_attribute( + "consumption_is_positive", False + ): # FlexMeasures default is to store consumption as negative power values + return df.values return -df.values diff --git a/flexmeasures/data/services/scheduling.py b/flexmeasures/data/services/scheduling.py index d5319cf9b..59ff663e3 100644 --- a/flexmeasures/data/services/scheduling.py +++ b/flexmeasures/data/services/scheduling.py @@ -109,7 +109,7 @@ def make_schedule( - Choose which scheduling function can be used - Compute schedule - - Turn schedukled values into beliefs and save them to db + - Turn scheduled values into beliefs and save them to db """ # https://docs.sqlalchemy.org/en/13/faq/connections.html#how-do-i-use-engines-connections-sessions-with-python-multiprocessing-or-os-fork db.engine.dispose() @@ -202,7 +202,7 @@ def load_custom_scheduler(scheduler_specs: dict) -> Tuple[Callable, str]: assert "function" in scheduler_specs, "scheduler specs have no 'function'" source_name = scheduler_specs.get( - "source", f"Custom scheduler - {scheduler_specs['function']}" + "source", f"custom scheduler - {scheduler_specs['function']}" ) scheduler_name = scheduler_specs["function"] diff --git a/flexmeasures/data/tests/dummy_scheduler.py b/flexmeasures/data/tests/dummy_scheduler.py index cf4f0ec89..207c98e52 100644 --- a/flexmeasures/data/tests/dummy_scheduler.py +++ b/flexmeasures/data/tests/dummy_scheduler.py @@ -12,10 +12,13 @@ def compute_a_schedule( *args, **kwargs ): - """Just a dummy scheduler.""" + """ + Just a dummy scheduler that always plans to consume at maximum capacity. + (Schedulers return positive values for consumption, and negative values for production) + """ return initialize_series( # simply creates a Pandas Series repeating one value data=sensor.get_attribute("capacity_in_mw"), start=start, end=end, resolution=resolution, - ) + )