test_process.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. from datetime import datetime, timedelta
  2. import pytest
  3. import pytz
  4. from flexmeasures.data.models.planning.process import ProcessScheduler
  5. from flexmeasures.tests.utils import get_test_sensor
  6. tz = pytz.timezone("Europe/Amsterdam")
  7. start = tz.localize(datetime(2015, 1, 2))
  8. end = tz.localize(datetime(2015, 1, 3))
  9. resolution = timedelta(hours=1)
  10. @pytest.mark.parametrize(
  11. "process_type, optimal_start",
  12. [("INFLEXIBLE", datetime(2015, 1, 2, 0)), ("SHIFTABLE", datetime(2015, 1, 2, 8))],
  13. )
  14. def test_process_scheduler(
  15. add_battery_assets, process, process_type, optimal_start, db
  16. ):
  17. """
  18. Test scheduling a process of 4kW of power that last 4h using the ProcessScheduler
  19. without time restrictions.
  20. """
  21. # get the sensors from the database
  22. epex_da = get_test_sensor(db)
  23. flex_model = {
  24. "duration": "PT4H",
  25. "process-type": process_type,
  26. "power": 4,
  27. }
  28. flex_context = {
  29. "consumption-price": {"sensor": epex_da.id},
  30. }
  31. scheduler = ProcessScheduler(
  32. process,
  33. start,
  34. end,
  35. resolution,
  36. flex_model=flex_model,
  37. flex_context=flex_context,
  38. )
  39. schedule = scheduler.compute()
  40. optimal_start = tz.localize(optimal_start)
  41. mask = (optimal_start <= schedule.index) & (
  42. schedule.index < optimal_start + timedelta(hours=4)
  43. )
  44. assert (schedule[mask] == 4).all()
  45. assert (schedule[~mask] == 0).all()
  46. @pytest.mark.parametrize(
  47. "process_type, optimal_start",
  48. [("INFLEXIBLE", datetime(2015, 1, 2, 0)), ("SHIFTABLE", datetime(2015, 1, 2, 8))],
  49. )
  50. def test_duration_exceeds_planning_window(
  51. add_battery_assets, process, process_type, optimal_start, db
  52. ):
  53. """
  54. Test scheduling a process that last longer than the planning window.
  55. """
  56. # get the sensors from the database
  57. epex_da = get_test_sensor(db)
  58. flex_model = {
  59. "duration": "PT48H",
  60. "process-type": process_type,
  61. "power": 4,
  62. }
  63. flex_context = {
  64. "consumption-price": {"sensor": epex_da.id},
  65. }
  66. scheduler = ProcessScheduler(
  67. process,
  68. start,
  69. end,
  70. resolution,
  71. flex_model=flex_model,
  72. flex_context=flex_context,
  73. )
  74. schedule = scheduler.compute()
  75. optimal_start = tz.localize(optimal_start)
  76. assert (schedule == 4).all()
  77. def test_process_scheduler_time_restrictions(add_battery_assets, process, db):
  78. """
  79. Test ProcessScheduler with a time restrictions consisting of a block of 2h starting
  80. at 8am. The resulting schedules avoid the 8am-10am period and schedules for a valid period.
  81. """
  82. # get the sensors from the database
  83. epex_da = get_test_sensor(db)
  84. # time parameters
  85. flex_model = {
  86. "duration": "PT4H",
  87. "process-type": "SHIFTABLE",
  88. "power": 4,
  89. "time-restrictions": [
  90. {"start": "2015-01-02T08:00:00+01:00", "duration": "PT2H"}
  91. ],
  92. }
  93. flex_context = {
  94. "consumption-price": {"sensor": epex_da.id},
  95. }
  96. scheduler = ProcessScheduler(
  97. process,
  98. start,
  99. end,
  100. resolution,
  101. flex_model=flex_model,
  102. flex_context=flex_context,
  103. )
  104. schedule = scheduler.compute()
  105. optimal_start = tz.localize(datetime(2015, 1, 2, 10))
  106. mask = (optimal_start <= schedule.index) & (
  107. schedule.index < optimal_start + timedelta(hours=4)
  108. )
  109. assert (schedule[mask] == 4).all()
  110. assert (schedule[~mask] == 0).all()
  111. # check that the time restrictions are fulfilled
  112. time_restrictions = scheduler.flex_model["time_restrictions"]
  113. time_restrictions = time_restrictions.tz_convert(tz)
  114. assert (schedule[time_restrictions] == 0).all()
  115. def test_breakable_scheduler_time_restrictions(add_battery_assets, process, db):
  116. """
  117. Test BREAKABLE process_type of ProcessScheduler by introducing four 1-hour restrictions
  118. interspaced by 1 hour. The equivalent mask would be the following: [0,...,0,1,0,1,0,1,0,1,0, ...,0].
  119. Trying to get the best prices (between 9am and 4pm), his makes the schedule choose time periods between
  120. the time restrictions.
  121. """
  122. # get the sensors from the database
  123. epex_da = get_test_sensor(db)
  124. # time parameters
  125. flex_model = {
  126. "duration": "PT4H",
  127. "process-type": "BREAKABLE",
  128. "power": 4,
  129. "time-restrictions": [
  130. {"start": "2015-01-02T09:00:00+01:00", "duration": "PT1H"},
  131. {"start": "2015-01-02T11:00:00+01:00", "duration": "PT1H"},
  132. {"start": "2015-01-02T13:00:00+01:00", "duration": "PT1H"},
  133. {"start": "2015-01-02T15:00:00+01:00", "duration": "PT1H"},
  134. ],
  135. }
  136. flex_context = {
  137. "consumption-price": {"sensor": epex_da.id},
  138. }
  139. scheduler = ProcessScheduler(
  140. process,
  141. start,
  142. end,
  143. resolution,
  144. flex_model=flex_model,
  145. flex_context=flex_context,
  146. )
  147. schedule = scheduler.compute()
  148. expected_schedule = [0] * 8 + [4, 0, 4, 0, 4, 0, 4, 0] + [0] * 8
  149. assert (schedule == expected_schedule).all()
  150. # check that the time restrictions are fulfilled
  151. time_restrictions = scheduler.flex_model["time_restrictions"]
  152. time_restrictions = time_restrictions.tz_convert(tz)
  153. assert (schedule[time_restrictions] == 0).all()
  154. @pytest.mark.parametrize(
  155. "process_type, time_restrictions",
  156. [
  157. ("BREAKABLE", [{"start": "2015-01-02T00:00:00+01:00", "duration": "PT24H"}]),
  158. ("INFLEXIBLE", [{"start": "2015-01-02T03:00:00+01:00", "duration": "PT21H"}]),
  159. ("SHIFTABLE", [{"start": "2015-01-02T03:00:00+01:00", "duration": "PT21H"}]),
  160. ],
  161. )
  162. def test_impossible_schedules(
  163. add_battery_assets, process, process_type, time_restrictions, db
  164. ):
  165. """
  166. Test schedules with time restrictions that make a 4h block not fit anytime during the
  167. planned window.
  168. """
  169. # get the sensors from the database
  170. epex_da = get_test_sensor(db)
  171. flex_model = {
  172. "duration": "PT4H",
  173. "process-type": process_type,
  174. "power": 4,
  175. "time-restrictions": time_restrictions,
  176. }
  177. flex_context = {
  178. "consumption-price": {"sensor": epex_da.id},
  179. }
  180. scheduler = ProcessScheduler(
  181. process,
  182. start,
  183. end,
  184. resolution,
  185. flex_model=flex_model,
  186. flex_context=flex_context,
  187. )
  188. with pytest.raises(ValueError):
  189. scheduler.compute()