Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion flexmeasures/data/models/planning/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ def _prepare(self, skip_validation: bool = False) -> tuple: # noqa: C901
if not self.config_deserialized:
self.deserialize_config()

# todo: look for the reason why flex_model has an object(dict) without a sensor, and fix the root cause if possible, instead of filtering it out here
if isinstance(self.flex_model, list):
self.flex_model = [
model for model in self.flex_model if model["sensor"] is not None
]

start = self.start
end = self.end
resolution = self.resolution
Expand Down Expand Up @@ -935,6 +941,7 @@ def _prepare(self, skip_validation: bool = False) -> tuple: # noqa: C901
device_constraints,
ems_constraints,
commitments,
inflexible_device_sensors,
)

def convert_to_commitments(
Expand Down Expand Up @@ -1247,6 +1254,7 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
device_constraints,
ems_constraints,
commitments,
inflexible_device_sensors,
) = self._prepare(skip_validation=skip_validation)

# Fallback policy if the problem was unsolvable
Expand Down Expand Up @@ -1315,6 +1323,7 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
device_constraints,
ems_constraints,
commitments,
inflexible_device_sensors,
) = self._prepare(skip_validation=skip_validation)

ems_schedule, expected_costs, scheduler_results, model = device_scheduler(
Expand Down Expand Up @@ -1342,6 +1351,16 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
elif sensor is not None and sensor in storage_schedule:
storage_schedule[sensor] += ems_schedule[d]

# Obtain the inflexible device schedules
num_flexible_devices = len(sensors)
inflexible_schedules = dict()
for i, inflexible_sensor in enumerate(inflexible_device_sensors):
device_index = num_flexible_devices + i
if inflexible_sensor not in inflexible_schedules:
inflexible_schedules[inflexible_sensor] = ems_schedule[device_index]
else:
inflexible_schedules[inflexible_sensor] += ems_schedule[device_index]

# Obtain the aggregate power schedule, too, if the flex-context states the associated sensor. Fill with the sum of schedules made here.
aggregate_power_sensor = self.flex_context.get("aggregate_power", None)
if isinstance(aggregate_power_sensor, Sensor):
Expand All @@ -1361,6 +1380,18 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
if sensor is not None
}

# Convert each inflexible device schedule to the unit of the device's power sensor
inflexible_schedules = {
sensor: convert_units(
inflexible_schedules[sensor],
"MW",
sensor.unit,
event_resolution=sensor.event_resolution,
)
for sensor in inflexible_schedules.keys()
if sensor is not None
}

flex_model = self.flex_model.copy()

if not isinstance(self.flex_model, list):
Expand Down Expand Up @@ -1397,6 +1428,13 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
for sensor in storage_schedule.keys()
if sensor is not None
}
inflexible_schedules = {
sensor: inflexible_schedules[sensor]
.resample(sensor.event_resolution)
.mean()
for sensor in inflexible_schedules.keys()
if sensor is not None
}

# Round schedule
if self.round_to_decimals:
Expand All @@ -1405,6 +1443,11 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
for sensor in storage_schedule.keys()
if sensor is not None
}
inflexible_schedules = {
sensor: inflexible_schedules[sensor].round(self.round_to_decimals)
for sensor in inflexible_schedules.keys()
if sensor is not None
}
soc_schedule = {
sensor: soc_schedule[sensor].round(self.round_to_decimals)
for sensor in soc_schedule.keys()
Expand All @@ -1421,6 +1464,16 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
for sensor in storage_schedule.keys()
if sensor is not None
]
inflexible_device_schedules = [
{
"name": "inflexible_device_schedule",
"sensor": sensor,
"data": inflexible_schedules[sensor],
"unit": sensor.unit,
}
for sensor in inflexible_schedules.keys()
if sensor is not None
]
commitment_costs = [
{
"name": "commitment_costs",
Expand All @@ -1442,7 +1495,12 @@ def compute(self, skip_validation: bool = False) -> SchedulerOutputType:
}
for sensor, soc in soc_schedule.items()
]
return storage_schedules + commitment_costs + soc_schedules
return (
storage_schedules
+ inflexible_device_schedules
+ commitment_costs
+ soc_schedules
)
else:
return storage_schedule[sensors[0]]

Expand Down
3 changes: 3 additions & 0 deletions flexmeasures/data/models/planning/tests/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ def run_test_charge_discharge_sign(
device_constraints,
ems_constraints,
commitments,
inflexible_device_sensors,
) = scheduler._prepare(skip_validation=True)

planned_power_per_device, planned_costs, results, model = device_scheduler(
Expand Down Expand Up @@ -1188,6 +1189,7 @@ def test_numerical_errors(app_with_each_solver, setup_planning_test_data, db):
device_constraints,
ems_constraints,
commitments,
inflexible_device_sensors,
) = scheduler._prepare(skip_validation=True)

_, _, results, model = device_scheduler(
Expand Down Expand Up @@ -1370,6 +1372,7 @@ def set_if_not_none(dictionary, key, value):
device_constraints,
ems_constraints,
commitments,
inflexible_device_sensors,
) = scheduler._prepare(skip_validation=True)

assert all(device_constraints[0]["derivative min"] == -expected_capacity)
Expand Down
Loading