123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- from datetime import datetime, timedelta
- import pytest
- import pytz
- from flexmeasures.data.models.planning.process import ProcessScheduler
- from flexmeasures.tests.utils import get_test_sensor
- tz = pytz.timezone("Europe/Amsterdam")
- start = tz.localize(datetime(2015, 1, 2))
- end = tz.localize(datetime(2015, 1, 3))
- resolution = timedelta(hours=1)
- @pytest.mark.parametrize(
- "process_type, optimal_start",
- [("INFLEXIBLE", datetime(2015, 1, 2, 0)), ("SHIFTABLE", datetime(2015, 1, 2, 8))],
- )
- def test_process_scheduler(
- add_battery_assets, process, process_type, optimal_start, db
- ):
- """
- Test scheduling a process of 4kW of power that last 4h using the ProcessScheduler
- without time restrictions.
- """
- # get the sensors from the database
- epex_da = get_test_sensor(db)
- flex_model = {
- "duration": "PT4H",
- "process-type": process_type,
- "power": 4,
- }
- flex_context = {
- "consumption-price": {"sensor": epex_da.id},
- }
- scheduler = ProcessScheduler(
- process,
- start,
- end,
- resolution,
- flex_model=flex_model,
- flex_context=flex_context,
- )
- schedule = scheduler.compute()
- optimal_start = tz.localize(optimal_start)
- mask = (optimal_start <= schedule.index) & (
- schedule.index < optimal_start + timedelta(hours=4)
- )
- assert (schedule[mask] == 4).all()
- assert (schedule[~mask] == 0).all()
- @pytest.mark.parametrize(
- "process_type, optimal_start",
- [("INFLEXIBLE", datetime(2015, 1, 2, 0)), ("SHIFTABLE", datetime(2015, 1, 2, 8))],
- )
- def test_duration_exceeds_planning_window(
- add_battery_assets, process, process_type, optimal_start, db
- ):
- """
- Test scheduling a process that last longer than the planning window.
- """
- # get the sensors from the database
- epex_da = get_test_sensor(db)
- flex_model = {
- "duration": "PT48H",
- "process-type": process_type,
- "power": 4,
- }
- flex_context = {
- "consumption-price": {"sensor": epex_da.id},
- }
- scheduler = ProcessScheduler(
- process,
- start,
- end,
- resolution,
- flex_model=flex_model,
- flex_context=flex_context,
- )
- schedule = scheduler.compute()
- optimal_start = tz.localize(optimal_start)
- assert (schedule == 4).all()
- def test_process_scheduler_time_restrictions(add_battery_assets, process, db):
- """
- Test ProcessScheduler with a time restrictions consisting of a block of 2h starting
- at 8am. The resulting schedules avoid the 8am-10am period and schedules for a valid period.
- """
- # get the sensors from the database
- epex_da = get_test_sensor(db)
- # time parameters
- flex_model = {
- "duration": "PT4H",
- "process-type": "SHIFTABLE",
- "power": 4,
- "time-restrictions": [
- {"start": "2015-01-02T08:00:00+01:00", "duration": "PT2H"}
- ],
- }
- flex_context = {
- "consumption-price": {"sensor": epex_da.id},
- }
- scheduler = ProcessScheduler(
- process,
- start,
- end,
- resolution,
- flex_model=flex_model,
- flex_context=flex_context,
- )
- schedule = scheduler.compute()
- optimal_start = tz.localize(datetime(2015, 1, 2, 10))
- mask = (optimal_start <= schedule.index) & (
- schedule.index < optimal_start + timedelta(hours=4)
- )
- assert (schedule[mask] == 4).all()
- assert (schedule[~mask] == 0).all()
- # check that the time restrictions are fulfilled
- time_restrictions = scheduler.flex_model["time_restrictions"]
- time_restrictions = time_restrictions.tz_convert(tz)
- assert (schedule[time_restrictions] == 0).all()
- def test_breakable_scheduler_time_restrictions(add_battery_assets, process, db):
- """
- Test BREAKABLE process_type of ProcessScheduler by introducing four 1-hour restrictions
- interspaced by 1 hour. The equivalent mask would be the following: [0,...,0,1,0,1,0,1,0,1,0, ...,0].
- Trying to get the best prices (between 9am and 4pm), his makes the schedule choose time periods between
- the time restrictions.
- """
- # get the sensors from the database
- epex_da = get_test_sensor(db)
- # time parameters
- flex_model = {
- "duration": "PT4H",
- "process-type": "BREAKABLE",
- "power": 4,
- "time-restrictions": [
- {"start": "2015-01-02T09:00:00+01:00", "duration": "PT1H"},
- {"start": "2015-01-02T11:00:00+01:00", "duration": "PT1H"},
- {"start": "2015-01-02T13:00:00+01:00", "duration": "PT1H"},
- {"start": "2015-01-02T15:00:00+01:00", "duration": "PT1H"},
- ],
- }
- flex_context = {
- "consumption-price": {"sensor": epex_da.id},
- }
- scheduler = ProcessScheduler(
- process,
- start,
- end,
- resolution,
- flex_model=flex_model,
- flex_context=flex_context,
- )
- schedule = scheduler.compute()
- expected_schedule = [0] * 8 + [4, 0, 4, 0, 4, 0, 4, 0] + [0] * 8
- assert (schedule == expected_schedule).all()
- # check that the time restrictions are fulfilled
- time_restrictions = scheduler.flex_model["time_restrictions"]
- time_restrictions = time_restrictions.tz_convert(tz)
- assert (schedule[time_restrictions] == 0).all()
- @pytest.mark.parametrize(
- "process_type, time_restrictions",
- [
- ("BREAKABLE", [{"start": "2015-01-02T00:00:00+01:00", "duration": "PT24H"}]),
- ("INFLEXIBLE", [{"start": "2015-01-02T03:00:00+01:00", "duration": "PT21H"}]),
- ("SHIFTABLE", [{"start": "2015-01-02T03:00:00+01:00", "duration": "PT21H"}]),
- ],
- )
- def test_impossible_schedules(
- add_battery_assets, process, process_type, time_restrictions, db
- ):
- """
- Test schedules with time restrictions that make a 4h block not fit anytime during the
- planned window.
- """
- # get the sensors from the database
- epex_da = get_test_sensor(db)
- flex_model = {
- "duration": "PT4H",
- "process-type": process_type,
- "power": 4,
- "time-restrictions": time_restrictions,
- }
- flex_context = {
- "consumption-price": {"sensor": epex_da.id},
- }
- scheduler = ProcessScheduler(
- process,
- start,
- end,
- resolution,
- flex_model=flex_model,
- flex_context=flex_context,
- )
- with pytest.raises(ValueError):
- scheduler.compute()
|