123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- import pytest
- import numpy as np
- import pandas as pd
- from flexmeasures.data.services.scheduling import create_simultaneous_scheduling_job
- from flexmeasures.data.tests.utils import work_on_rq
- from flexmeasures.data.models.time_series import Sensor
- @pytest.mark.parametrize("use_heterogeneous_resolutions", [True, False])
- def test_create_simultaneous_jobs(
- db, app, flex_description_sequential, smart_building, use_heterogeneous_resolutions
- ):
- assets, sensors, _ = smart_building
- queue = app.queues["scheduling"]
- start = pd.Timestamp("2015-01-03").tz_localize("Europe/Amsterdam")
- end = pd.Timestamp("2015-01-04").tz_localize("Europe/Amsterdam")
- scheduler_specs = {
- "module": "flexmeasures.data.models.planning.storage",
- "class": "StorageScheduler",
- }
- flex_description_sequential["start"] = start
- flex_description_sequential["end"] = end
- if use_heterogeneous_resolutions:
- flex_description_sequential["flex_model"][1]["sensor"] = sensors[
- "Test Battery 1h"
- ]
- job = create_simultaneous_scheduling_job(
- asset=assets["Test Site"],
- scheduler_specs=scheduler_specs,
- enqueue=True,
- **flex_description_sequential,
- )
- # The EV is scheduled firstly.
- assert job.kwargs["asset_or_sensor"] == {
- "id": assets["Test Site"].id,
- "class": "GenericAsset",
- }
- # It uses the inflexible-device-sensors that are defined in the flex-context, exclusively.
- assert job.kwargs["flex_context"]["inflexible-device-sensors"] == [
- sensors["Test Solar"].id,
- sensors["Test Building"].id,
- ]
- ev_power = sensors["Test EV"].search_beliefs()
- battery_power = sensors["Test Battery"].search_beliefs()
- assert ev_power.empty
- assert battery_power.empty
- # work tasks
- work_on_rq(queue)
- # check that the jobs complete successfully
- job.perform()
- assert job.get_status() == "finished"
- # Get power values
- ev_power = sensors["Test EV"].search_beliefs()
- assert ev_power.sources.unique()[0].model == "StorageScheduler"
- ev_power = ev_power.droplevel([1, 2, 3])
- if use_heterogeneous_resolutions:
- battery_power = sensors["Test Battery 1h"].search_beliefs()
- assert len(battery_power) == 24
- else:
- battery_power = sensors["Test Battery"].search_beliefs()
- assert len(battery_power) == 96
- assert battery_power.sources.unique()[0].model == "StorageScheduler"
- battery_power = battery_power.droplevel([1, 2, 3])
- start_charging = start + pd.Timedelta(hours=8)
- end_charging = start + pd.Timedelta(hours=10) - sensors["Test EV"].event_resolution
- # Check schedules
- assert (
- ev_power.loc[start_charging:end_charging] != -0.005
- ).values.any(), "no charging at full device power capacity (5 kW) expected"
- for target_no in (1, 2, 3):
- non_zero_target = flex_description_sequential["flex_model"][0][
- "sensor_flex_model"
- ]["soc-targets"][target_no]
- # NB: assumes perfect conversion and storage efficiencies
- np.testing.assert_approx_equal(
- # head(-1) because ev_power is indexed by event start and target datetime corresponds to event end
- # minus ev_power because ev_power uses negative values for consumption
- -ev_power[: non_zero_target["datetime"]].head(-1).sum()[0] / 4,
- non_zero_target["value"],
- )
- # Get price data
- price_sensor_id = flex_description_sequential["flex_context"][
- "consumption-price-sensor"
- ]
- price_sensor = db.session.get(Sensor, price_sensor_id)
- prices = price_sensor.search_beliefs(
- event_starts_after=start - pd.Timedelta(hours=1), event_ends_before=end
- )
- prices = prices.droplevel([1, 2, 3])
- prices.index = prices.index.tz_convert("Europe/Amsterdam")
- # Calculate costs
- ev_costs = (-ev_power.resample("1h").mean() * prices).sum().item()
- battery_costs = (-battery_power.resample("1h").mean() * prices).sum().item()
- total_cost = ev_costs + battery_costs
- # Define expected costs based on resolution
- expected_ev_costs = 2.2375
- expected_battery_costs = -5.515
- expected_total_cost = -3.2775
- # Check costs
- assert (
- round(total_cost, 4) == expected_total_cost
- ), f"Total costs should be €{expected_total_cost}, got €{total_cost}"
- assert (
- round(ev_costs, 4) == expected_ev_costs
- ), f"EV costs should be €{expected_ev_costs}, got €{ev_costs}"
- assert (
- round(battery_costs, 4) == expected_battery_costs
- ), f"Battery costs should be €{expected_battery_costs}, got €{battery_costs}"
|