From 0276764105494e312913efa763e685ed16bd444a Mon Sep 17 00:00:00 2001 From: rajath-09 Date: Wed, 26 Apr 2023 18:22:47 +0530 Subject: [PATCH] Issue #618 Step1-3 done --- flexmeasures/cli/data_add.py | 18 +++ .../models/planning/linear_optimization.py | 4 +- flexmeasures/data/models/planning/storage.py | 119 +++++++++++++----- .../data/schemas/scheduling/__init__.py | 6 + flyway/postgres/sql/V0001__.sql | 4 +- 5 files changed, 113 insertions(+), 38 deletions(-) diff --git a/flexmeasures/cli/data_add.py b/flexmeasures/cli/data_add.py index 662f910d8..d5bc61ac7 100755 --- a/flexmeasures/cli/data_add.py +++ b/flexmeasures/cli/data_add.py @@ -941,6 +941,20 @@ def create_schedule(ctx): required=False, help="Optimize production against this sensor. Defaults to the consumption price sensor. The sensor typically records an electricity price (e.g. in EUR/kWh), but this field can also be used to optimize against some emission intensity factor (e.g. in kg CO₂ eq./kWh). Follow up with the sensor's ID.", ) +@click.option( + "--consumption-price-sensor-per-device", + "consumption_price_sensor_per_device", + type=dict, + required=False, + # help="Optimize consumption against this sensor. The sensor typically records an electricity price (e.g. in EUR/kWh), but this field can also be used to optimize against some emission intensity factor (e.g. in kg CO₂ eq./kWh). Follow up with the sensor's ID.", +) +@click.option( + "--production-price-sensor-per-device", + "production_price_sensor_per_device", + type=dict, + required=False, + # help="Optimize production against this sensor. Defaults to the consumption price sensor. The sensor typically records an electricity price (e.g. in EUR/kWh), but this field can also be used to optimize against some emission intensity factor (e.g. in kg CO₂ eq./kWh). Follow up with the sensor's ID.", +) @click.option( "--optimization-context-id", "optimization_context_sensor", @@ -1023,6 +1037,8 @@ def add_schedule_for_storage( production_price_sensor: Sensor, optimization_context_sensor: Sensor, inflexible_device_sensors: list[Sensor], + consumption_price_sensors_per_device: dict(Sensor, Sensor), + production_price_sensors_per_device: dict(Sensor, Sensor), start: datetime, duration: timedelta, soc_at_start: ur.Quantity, @@ -1103,6 +1119,8 @@ def add_schedule_for_storage( "consumption-price-sensor": consumption_price_sensor.id, "production-price-sensor": production_price_sensor.id, "inflexible-device-sensors": [s.id for s in inflexible_device_sensors], + "consumption-price-sensors-per-device": {(power.id, price.id) for (power, price) in consumption_price_sensors_per_device.items()}, + "production-price-sensors-per-device": {(power.id, price.id) for (power, price) in production_price_sensors_per_device.items()}, }, ) if as_job: diff --git a/flexmeasures/data/models/planning/linear_optimization.py b/flexmeasures/data/models/planning/linear_optimization.py index 32d8b7456..464ef7ed7 100644 --- a/flexmeasures/data/models/planning/linear_optimization.py +++ b/flexmeasures/data/models/planning/linear_optimization.py @@ -29,8 +29,8 @@ def device_scheduler( # noqa C901 device_constraints: List[pd.DataFrame], ems_constraints: pd.DataFrame, commitment_quantities: List[pd.Series], - commitment_downwards_deviation_price: Union[List[pd.Series], List[float]], - commitment_upwards_deviation_price: Union[List[pd.Series], List[float]], + commitment_downwards_deviation_price_array: List[Union[List[pd.Series], List[float]]], + commitment_upwards_deviation_price_array: List[Union[List[pd.Series], List[float]]], ) -> Tuple[List[pd.Series], float, SolverResults]: """This generic device scheduler is able to handle an EMS with multiple devices, with various types of constraints on the EMS level and on the device level, diff --git a/flexmeasures/data/models/planning/storage.py b/flexmeasures/data/models/planning/storage.py index 9afe5c2fb..de7267f27 100644 --- a/flexmeasures/data/models/planning/storage.py +++ b/flexmeasures/data/models/planning/storage.py @@ -48,56 +48,107 @@ def compute_schedule( roundtrip_efficiency = self.flex_model.get("roundtrip_efficiency") prefer_charging_sooner = self.flex_model.get("prefer_charging_sooner", True) - consumption_price_sensor = self.flex_context.get("consumption_price_sensor") - production_price_sensor = self.flex_context.get("production_price_sensor") + # consumption_price_sensor = self.flex_context.get("consumption_price_sensor") + # production_price_sensor = self.flex_context.get("production_price_sensor") inflexible_device_sensors = self.flex_context.get( "inflexible_device_sensors", [] ) - + consumption_price_sensor_per_device = self.flex_context.get("consumption_price_sensor_per_device", {}) + production_price_sensor_per_device = self.flex_context.get("production_price_sensor_per_device", {}) + # Check for required Sensor attributes self.sensor.check_required_attributes([("capacity_in_mw", (float, int))]) # Check for known prices or price forecasts, trimming planning window accordingly - up_deviation_prices, (start, end) = get_prices( - (start, end), - resolution, - beliefs_before=belief_time, - price_sensor=consumption_price_sensor, - sensor=sensor, - allow_trimmed_query_window=False, - ) - down_deviation_prices, (start, end) = get_prices( - (start, end), - resolution, - beliefs_before=belief_time, - price_sensor=production_price_sensor, - sensor=sensor, - allow_trimmed_query_window=False, - ) + # up_deviation_prices, (start, end) = get_prices( + # (start, end), + # resolution, + # beliefs_before=belief_time, + # price_sensor=consumption_price_sensor, + # sensor=sensor, + # allow_trimmed_query_window=False, + # ) + # down_deviation_prices, (start, end) = get_prices( + # (start, end), + # resolution, + # beliefs_before=belief_time, + # price_sensor=production_price_sensor, + # sensor=sensor, + # allow_trimmed_query_window=False, + # ) + + up_deviation_prices_array = [] + for power_sensor, price_sensor in consumption_price_sensor_per_device.items(): + + # Check for known prices or price forecasts, trimming planning window accordingly + up_deviation_prices, (start, end) = get_prices( + (start, end), + resolution, + beliefs_before=belief_time, + price_sensor=price_sensor, + sensor=power_sensor, + allow_trimmed_query_window=False, + ) + up_deviation_prices_array.append(up_deviation_prices) + + down_deviation_prices_array = [] + for power_sensor, price_sensor in production_price_sensor_per_device.items(): + + down_deviation_prices, (start, end) = get_prices( + (start, end), + resolution, + beliefs_before=belief_time, + price_sensor=price_sensor, + sensor=power_sensor, + allow_trimmed_query_window=False, + ) + down_deviation_prices_array.append(down_deviation_prices) start = pd.Timestamp(start).tz_convert("UTC") end = pd.Timestamp(end).tz_convert("UTC") # Add tiny price slope to prefer charging now rather than later, and discharging later rather than now. # We penalise the future with at most 1 per thousand times the price spread. + # if prefer_charging_sooner: + # up_deviation_prices = add_tiny_price_slope( + # up_deviation_prices, "event_value" + # ) + # down_deviation_prices = add_tiny_price_slope( + # down_deviation_prices, "event_value" + # ) if prefer_charging_sooner: - up_deviation_prices = add_tiny_price_slope( - up_deviation_prices, "event_value" - ) - down_deviation_prices = add_tiny_price_slope( - down_deviation_prices, "event_value" - ) - + for i in range(0, len(up_deviation_prices_array)): + up_deviation_prices[i] = add_tiny_price_slope( + up_deviation_prices[i], "event_value" + ) + for i in range(0, len(down_deviation_prices_array)): + down_deviation_prices[i] = add_tiny_price_slope( + down_deviation_prices[i], "event_value" + ) # Set up commitments to optimise for commitment_quantities = [initialize_series(0, start, end, self.resolution)] # Todo: convert to EUR/(deviation of commitment, which is in MW) - commitment_upwards_deviation_price = [ - up_deviation_prices.loc[start : end - resolution]["event_value"] - ] - commitment_downwards_deviation_price = [ - down_deviation_prices.loc[start : end - resolution]["event_value"] - ] + # commitment_upwards_deviation_price = [ + # up_deviation_prices.loc[start : end - resolution]["event_value"] + # ] + # commitment_downwards_deviation_price = [ + # down_deviation_prices.loc[start : end - resolution]["event_value"] + # ] + + commitment_upwards_deviation_price_array = [] + for up_deviation_price in up_deviation_prices_array: + commitment_upwards_deviation_price = [ + up_deviation_price.loc[start : end - resolution]["event_value"] + ] + commitment_upwards_deviation_price_array.append(commitment_upwards_deviation_price) + + commitment_downwards_deviation_price_array = [] + for down_deviation_price in down_deviation_prices_array: + commitment_downwards_deviation_price = [ + down_deviation_price.loc[start : end - resolution]["event_value"] + ] + commitment_downwards_deviation_price_array.append(commitment_downwards_deviation_price) # Set up device constraints: only one scheduled flexible device for this EMS (at index 0), plus the forecasted inflexible devices (at indices 1 to n). columns = [ @@ -169,8 +220,8 @@ def compute_schedule( device_constraints, ems_constraints, commitment_quantities, - commitment_downwards_deviation_price, - commitment_upwards_deviation_price, + commitment_downwards_deviation_price_array, + commitment_upwards_deviation_price_array, ) if scheduler_results.solver.termination_condition == "infeasible": # Fallback policy if the problem was unsolvable diff --git a/flexmeasures/data/schemas/scheduling/__init__.py b/flexmeasures/data/schemas/scheduling/__init__.py index 382f4430a..6867a1989 100644 --- a/flexmeasures/data/schemas/scheduling/__init__.py +++ b/flexmeasures/data/schemas/scheduling/__init__.py @@ -13,3 +13,9 @@ class FlexContextSchema(Schema): inflexible_device_sensors = fields.List( SensorIdField(), data_key="inflexible-device-sensors" ) + consumption_price_sensors_per_device = fields.Dict( + SensorIdField(), SensorIdField(), data_key="consumption-price-sensors-per-device" + ) + production_price_sensors_per_device = fields.Dict( + SensorIdField(), SensorIdField(), data_key="production-price-sensors-per-device" + ) \ No newline at end of file diff --git a/flyway/postgres/sql/V0001__.sql b/flyway/postgres/sql/V0001__.sql index 369e60fe4..c2fcbc4c9 100644 --- a/flyway/postgres/sql/V0001__.sql +++ b/flyway/postgres/sql/V0001__.sql @@ -1011,9 +1011,9 @@ INSERT INTO "public"."roles_users" ("id", "user_id", "role_id") VALUES (1, 1, 1) INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (13, 'power-1', 'MW', 'Europe/Amsterdam', '00:15:00', 'ex_post', '{"ex_post_horizon": "P0D"}', 1, '{}'); INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (14, 'power-2', 'MW', 'Europe/Amsterdam', '00:15:00', 'ex_post', '{"ex_post_horizon": "P0D"}', 5, '{}'); INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (15, 'power-3', 'MW', 'Europe/Amsterdam', '00:15:00', 'ex_post', '{"ex_post_horizon": "P0D"}', 2, '{}'); -INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (11, 'price-solar-1', 'EUR/MWh', 'Europe/Amsterdam', '01:00:00', 'x_days_ago_at_y_oclock', '{"x": 1, "y": 12, "z": "Europe/Paris"}',1, '{}'); +INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (9, 'price-solar-1', 'EUR/MWh', 'Europe/Amsterdam', '01:00:00', 'x_days_ago_at_y_oclock', '{"x": 1, "y": 12, "z": "Europe/Paris"}',1, '{}'); INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (10, 'price-solar-2', 'EUR/MWh', 'Europe/Amsterdam', '01:00:00', 'x_days_ago_at_y_oclock', '{"x": 1, "y": 12, "z": "Europe/Paris"}', 5, '{}'); -INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (9, 'battery-cprice', 'EUR/MWh', 'Europe/Amsterdam', '01:00:00', 'x_days_ago_at_y_oclock', '{"x": 1, "y": 12, "z": "Europe/Paris"}', 3, '{}'); +INSERT INTO "public"."sensor" ("id", "name", "unit", "timezone", "event_resolution", "knowledge_horizon_fnc", "knowledge_horizon_par", "generic_asset_id", "attributes") VALUES (11, 'battery-cprice', 'EUR/MWh', 'Europe/Amsterdam', '01:00:00', 'x_days_ago_at_y_oclock', '{"x": 1, "y": 12, "z": "Europe/Paris"}', 3, '{}'); --