From 8d35f852d33772c9dee3a742824c385c5c963bb8 Mon Sep 17 00:00:00 2001 From: Jostein Solaas Date: Mon, 14 Oct 2024 14:15:13 +0200 Subject: [PATCH] refactor: exporter queries The exporter knew too much about the specifics. This commit implements an interface to get specific sets of results, which basically is the query. What's left in the queries is alignment of results and aggregation of attributes. --- src/ecalc_cli/io/output.py | 3 +- src/libecalc/common/temporal_model.py | 5 +- src/libecalc/common/time_utils.py | 2 +- src/libecalc/common/variables.py | 2 +- src/libecalc/examples/simple/output/model.csv | 13 + .../fixtures/cases/ltp_export/utilities.py | 3 +- .../presentation/exporter/aggregators.py | 14 +- .../presentation/exporter/appliers.py | 14 +- .../presentation/exporter/configs/configs.py | 18 +- .../exporter/domain/exportable.py | 74 ++ src/libecalc/presentation/exporter/filters.py | 4 +- .../presentation/exporter/infrastructure.py | 405 ++++++++++ src/libecalc/presentation/exporter/queries.py | 756 +++++------------- .../test.INST_A.ltp.tsv | 30 +- .../test.INST_A.ltp.tsv.json | 16 + .../test.POWER_FROM_SHORE_EVENT.ltp.tsv | 30 +- .../test.POWER_FROM_SHORE_EVENT.ltp.tsv.json | 16 + 17 files changed, 771 insertions(+), 634 deletions(-) create mode 100644 src/libecalc/examples/simple/output/model.csv create mode 100644 src/libecalc/presentation/exporter/domain/exportable.py create mode 100644 src/libecalc/presentation/exporter/infrastructure.py diff --git a/src/ecalc_cli/io/output.py b/src/ecalc_cli/io/output.py index 2d5860eb9d..964c0bea6e 100644 --- a/src/ecalc_cli/io/output.py +++ b/src/ecalc_cli/io/output.py @@ -14,6 +14,7 @@ from libecalc.presentation.exporter.exporter import Exporter from libecalc.presentation.exporter.formatters.formatter import CSVFormatter from libecalc.presentation.exporter.handlers.handler import MultiFileHandler +from libecalc.presentation.exporter.infrastructure import ExportableGraphResult from libecalc.presentation.flow_diagram.EcalcModelMapper import EcalcModelMapper from libecalc.presentation.json_result.result import ( EcalcModelResult as EcalcModelResultDTO, @@ -152,7 +153,7 @@ def export_tsv( ] # last step is always added as a STOP, and does infer the end of the time vector prognosis_filter = config.filter(frequency=frequency) - result = prognosis_filter.filter(results, resampled_timevector) + result = prognosis_filter.filter(ExportableGraphResult(results), resampled_timevector) row_based_data: Dict[str, List[str]] = CSVFormatter(separation_character="\t").format_groups(result) diff --git a/src/libecalc/common/temporal_model.py b/src/libecalc/common/temporal_model.py index 74f0aee8d3..05ec2d6ce3 100644 --- a/src/libecalc/common/temporal_model.py +++ b/src/libecalc/common/temporal_model.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from datetime import datetime -from typing import Dict, Generic, Iterator, Tuple, TypeVar +from typing import Dict, Generic, Iterable, Iterator, Tuple, TypeVar from libecalc.common.time_utils import Period @@ -26,6 +26,9 @@ def __init__(self, data: Dict[datetime, ModelType]): for start_time, end_time, model in zip(start_times, end_times, data.values()) ] + def get_periods(self) -> Iterable[Period]: + return [model.period for model in self.models] + def items(self) -> Iterator[Tuple[Period, ModelType]]: return ((model.period, model.model) for model in self.models) diff --git a/src/libecalc/common/time_utils.py b/src/libecalc/common/time_utils.py index 06e9effa38..c52aa1d0f3 100644 --- a/src/libecalc/common/time_utils.py +++ b/src/libecalc/common/time_utils.py @@ -86,7 +86,7 @@ class Periods: def create_periods(cls, times: List[datetime], include_before: bool = True, include_after: bool = True) -> Periods: """ Create periods from the provided datetimes - :param times: the datetimes to create periods from + :param times: the sorted times to create periods from :param include_before: whether to add a period that ends with the first provided datetime, i.e. define a period before the earliest provided datetime. :param include_after: whether to add a period that starts with the last provided datetime, i.e. define a period diff --git a/src/libecalc/common/variables.py b/src/libecalc/common/variables.py index 3a0d3963d5..012b226d2a 100644 --- a/src/libecalc/common/variables.py +++ b/src/libecalc/common/variables.py @@ -82,7 +82,7 @@ def evaluate(self, expression: Union[Expression, Dict[datetime, Expression], Tem if isinstance(expression, Expression): return expression.evaluate(variables=self.variables, fill_length=len(self.get_time_vector())) elif isinstance(expression, dict): - return self._evaluate_temporal(temporal_expression=TemporalModel[expression]) + return self._evaluate_temporal(temporal_expression=TemporalModel(expression)) elif isinstance(expression, TemporalModel): return self._evaluate_temporal(temporal_expression=expression) diff --git a/src/libecalc/examples/simple/output/model.csv b/src/libecalc/examples/simple/output/model.csv new file mode 100644 index 0000000000..6b49ebc8b9 --- /dev/null +++ b/src/libecalc/examples/simple/output/model.csv @@ -0,0 +1,13 @@ +timesteps,model.is_valid[N/A],model.energy_usage[Sm3/cd],model.energy_usage_cumulative[Sm3 (cd)],model.power[MW (sd)],model.power_cumulative[GWh (cd)],model.co2.rate[t/cd],model.co2.cumulative[t (cd)],model.hydrocarbon_export_rate[Sm3/cd],model.co2.intensity_sm3[kg/Sm3],model.co2.intensity_boe[kg/BOE],model.co2.intensity_yearly_sm3[kg/Sm3],model.co2.intensity_yearly_boe[kg/BOE],model.power_electrical[MW (sd)],model.power_electrical_cumulative[GWh (cd)],model.power_mechanical[MW (sd)],model.power_mechanical_cumulative[GWh (cd)],Installation A.is_valid[N/A],Installation A.energy_usage[Sm3/cd],Installation A.energy_usage_cumulative[Sm3 (cd)],Installation A.power[MW (sd)],Installation A.power_cumulative[GWh (cd)],Installation A.co2.rate[t/cd],Installation A.co2.cumulative[t (cd)],Installation A.hydrocarbon_export_rate[Sm3/cd],Installation A.co2.intensity_sm3[kg/Sm3],Installation A.co2.intensity_boe[kg/BOE],Installation A.co2.intensity_yearly_sm3[kg/Sm3],Installation A.co2.intensity_yearly_boe[kg/BOE],Installation A.power_electrical[MW (sd)],Installation A.power_electrical_cumulative[GWh (cd)],Installation A.power_mechanical[MW (sd)],Installation A.power_mechanical_cumulative[GWh (cd)],Installation A.regularity[N/A],Generator set A.is_valid[N/A],Generator set A.energy_usage[Sm3/cd],Generator set A.energy_usage_cumulative[Sm3 (cd)],Generator set A.power[MW (sd)],Generator set A.power_cumulative[GWh (cd)],Generator set A.co2.rate[t/cd],Generator set A.co2.cumulative[t (cd)],Generator set A.power_capacity_margin[MW (sd)],Gas export compressor.is_valid[N/A],Gas export compressor.energy_usage[Sm3/cd],Gas export compressor.energy_usage_cumulative[Sm3 (cd)],Gas export compressor.co2.rate[t/cd],Gas export compressor.co2.cumulative[t (cd)],Gas export compressor.recirculation_loss[MW (sd)],Gas export compressor.rate_exceeds_maximum[N/A],Gas injection compressor.is_valid[N/A],Gas injection compressor.energy_usage[MW (sd)],Gas injection compressor.energy_usage_cumulative[MWd (cd)],Gas injection compressor.power[MW (sd)],Gas injection compressor.power_cumulative[GWh (cd)],Gas injection compressor.recirculation_loss[MW (sd)],Gas injection compressor.rate_exceeds_maximum[N/A],Produced water reinjection pump.is_valid[N/A],Produced water reinjection pump.energy_usage[MW (sd)],Produced water reinjection pump.energy_usage_cumulative[MWd (cd)],Produced water reinjection pump.power[MW (sd)],Produced water reinjection pump.power_cumulative[GWh (cd)],Produced water reinjection pump.inlet_liquid_rate_m3_per_day[Sm3/sd],Produced water reinjection pump.inlet_pressure_bar[bara],Produced water reinjection pump.outlet_pressure_bar[bara],Produced water reinjection pump.operational_head[J/kg],Base production load.is_valid[N/A],Base production load.energy_usage[MW (sd)],Base production load.energy_usage_cumulative[MWd (cd)],Base production load.power[MW (sd)],Base production load.power_cumulative[GWh (cd)],Flare.is_valid[N/A],Flare.energy_usage[Sm3/cd],Flare.energy_usage_cumulative[Sm3 (cd)],Flare.co2.rate[t/cd],Flare.co2.cumulative[t (cd)],Sea water injection pump.is_valid[N/A],Sea water injection pump.energy_usage[MW (sd)],Sea water injection pump.energy_usage_cumulative[MWd (cd)],Sea water injection pump.power[MW (sd)],Sea water injection pump.power_cumulative[GWh (cd)] +2020-01-01 00:00:00,1, 310830.00000, 0.00000, 27.71452, 0.00000, 680.71770, 0.00000, 12500.00000,nan,nan,54.45742,8.657777, 27.71452, 0.00000, 0.00000, 0.00000,1, 310830.00000, 0.00000, 27.71452, 0.00000, 680.71770, 0.00000, 12500.00000,nan,nan,54.45742,8.657777, 27.71452, 0.00000, 0.00000, 0.00000, 1.00000,1, 173830.00000, 0.00000, 27.71452, 0.00000, 380.68770, 0.00000, 72.28548,1, 130000.00000, 0.00000, 284.70000, 0.00000, 0.00000,0,1, 4.89000, 0.00000, 4.89000, 0.00000, 0.00000,0,1, 5.02452, 0.00000, 5.02452, 0.00000, 17000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 0.00000, 11.80000, 0.00000,1, 7000.00000, 0.00000, 15.33000, 0.00000,1, 6.00000, 0.00000, 6.00000, 0.00000 +2021-01-01 00:00:00,1, 311343.90000, 113763780.00000, 27.61307, 243.44430, 681.84320, 249142.70000, 11600.00000,54.45742,8.657777,58.77959,9.344926, 27.61307, 243.44430, 0.00000, 0.00000,1, 311343.90000, 113763780.00000, 27.61307, 243.44430, 681.84320, 249142.70000, 11600.00000,54.45742,8.657777,58.77959,9.344926, 27.61307, 243.44430, 0.00000, 0.00000, 1.00000,1, 173201.00000, 63621780.00000, 27.61307, 243.44430, 379.31020, 139331.70000, 72.38693,1, 131142.90000, 47580000.00000, 287.20300, 104200.20000, 0.00000,0,1, 5.01000, 1789.74000, 5.01000, 42.95376, 0.00000,0,1, 5.05307, 1838.97300, 5.05307, 44.13536, 17200.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 4318.80000, 11.80000, 103.65120,1, 7000.00000, 2562000.00000, 15.33000, 5610.78000,1, 5.75000, 2196.00000, 5.75000, 52.70400 +2022-01-01 00:00:00,1, 309378.10000, 227404304.00000, 27.11168, 485.33480, 677.53810, 498015.40000, 10700.00000,56.53484,8.988052,63.32132,10.06698, 27.11168, 485.33480, 0.00000, 0.00000,1, 309378.10000, 227404304.00000, 27.11168, 485.33480, 677.53810, 498015.40000, 10700.00000,56.53484,8.988052,63.32132,10.06698, 27.11168, 485.33480, 0.00000, 0.00000, 1.00000,1, 170092.40000, 126840145.00000, 27.11168, 485.33480, 372.50240, 277779.90000, 72.88832,1, 132285.70000, 95447158.00000, 289.70570, 209029.30000, 0.00000,0,1, 5.13000, 3618.39000, 5.13000, 86.84136, 0.00000,0,1, 4.68168, 3683.34500, 4.68168, 88.40027, 15000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 8625.80000, 11.80000, 207.01920,1, 7000.00000, 5117000.00000, 15.33000, 11206.23000,1, 5.50000, 4294.75000, 5.50000, 103.07400 +2023-01-01 00:00:00,1, 317428.00000, 340327310.00000, 28.22570, 722.83320, 695.16730, 745316.90000, 9800.00000,58.61944,9.319466,70.93544,11.27749, 28.22570, 722.83320, 0.00000, 0.00000,1, 317428.00000, 340327310.00000, 28.22570, 722.83320, 695.16730, 745316.90000, 9800.00000,58.61944,9.319466,70.93544,11.27749, 28.22570, 722.83320, 0.00000, 0.00000, 1.00000,1, 176999.40000, 188923871.00000, 28.22570, 722.83320, 387.62870, 413743.30000, 71.77430,1, 133428.60000, 143731439.00000, 292.20860, 314771.90000, 0.00000,0,1, 5.56000, 5490.84000, 5.56000, 131.78020, 0.00000,0,1, 4.86570, 5392.15600, 4.86570, 129.41170, 16000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 12932.80000, 11.80000, 310.38720,1, 7000.00000, 7672000.00000, 15.33000, 16801.68000,1, 6.00000, 6302.25000, 6.00000, 151.25400 +2024-01-01 00:00:00,1, 340633.50000, 456188530.00000, 31.78422, 970.09030, 745.98740, 999052.90000, 9900.00000,61.32357,9.749375,75.37705,11.98363, 31.78422, 970.09030, 0.00000, 0.00000,1, 340633.50000, 456188530.00000, 31.78422, 970.09030, 745.98740, 999052.90000, 9900.00000,61.32357,9.749375,75.37705,11.98363, 31.78422, 970.09030, 0.00000, 0.00000, 1.00000,1, 199062.10000, 253528652.00000, 31.78422, 970.09030, 435.94600, 555227.80000, 68.21578,1, 134571.40000, 192432878.00000, 294.71140, 421428.00000, 0.00000,0,1, 5.72000, 7520.24000, 5.72000, 180.48580, 0.00000,0,1, 4.50746, 7168.13700, 4.50746, 172.03530, 14000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 17239.80000, 11.80000, 413.75520,1, 7000.00000, 10227000.00000, 15.33000, 22397.13000,1, 9.75676, 8492.25000, 9.75676, 203.81400 +2024-12-01 00:00:00,1, 345398.60000, 570300752.00000, 32.36843, 1225.63500, 756.42290, 1248959.00000, 10000.00000,63.69638,10.12661,75.37705,11.98363, 32.36843, 1225.63500, 0.00000, 0.00000,1, 345398.60000, 570300752.00000, 32.36843, 1225.63500, 756.42290, 1248959.00000, 10000.00000,63.69638,10.12661,75.37705,11.98363, 32.36843, 1225.63500, 0.00000, 0.00000, 1.00000,1, 202684.30000, 320214456.00000, 32.36843, 1225.63500, 443.87860, 701269.70000, 67.63157,1, 135714.30000, 237514297.00000, 297.21430, 520156.30000, 0.00000,0,1, 6.13000, 9436.44000, 6.13000, 226.47460, 0.00000,0,1, 4.68168, 8678.13600, 4.68168, 208.27530, 15000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 21192.80000, 11.80000, 508.62720,1, 7000.00000, 12572000.00000, 15.33000, 27532.68000,1, 9.75676, 11760.76000, 9.75676, 282.25830 +2026-01-01 00:00:00,1, 346729.90000, 707078598.00000, 32.39884, 1533.26500, 759.33840, 1548502.00000, 11100.00000,65.70359,10.44572,68.40886,10.87581, 32.39884, 1533.26500, 0.00000, 0.00000,1, 346729.90000, 707078598.00000, 32.39884, 1533.26500, 759.33840, 1548502.00000, 11100.00000,65.70359,10.44572,68.40886,10.87581, 32.39884, 1533.26500, 0.00000, 0.00000, 1.00000,1, 202872.80000, 400477438.00000, 32.39884, 1533.26500, 444.29140, 877045.60000, 67.60116,1, 136857.10000, 291257160.00000, 299.71700, 637853.20000, 0.00000,0,1, 6.25000, 11863.92000, 6.25000, 284.73410, 0.00000,0,1, 5.02452, 10532.08000, 5.02452, 252.76990, 17000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 25865.60000, 11.80000, 620.77440,1, 7000.00000, 15344000.00000, 15.33000, 33603.36000,1, 9.32432, 15624.44000, 9.32432, 374.98650 +2027-01-01 00:00:00,1, 355282.00000, 833635012.00000, 33.04086, 1817.07900, 778.06750, 1825661.00000, 10500.00000,66.10042,10.50881,74.10167,11.78087, 33.04086, 1817.07900, 0.00000, 0.00000,1, 355282.00000, 833635012.00000, 33.04086, 1817.07900, 778.06750, 1825661.00000, 10500.00000,66.10042,10.50881,74.10167,11.78087, 33.04086, 1817.07900, 0.00000, 0.00000, 1.00000,1, 206853.40000, 474526010.00000, 33.04086, 1817.07900, 453.00890, 1039212.00000, 66.95914,1, 141428.60000, 341210001.00000, 309.72860, 747249.90000, 0.00000,0,1, 6.37000, 14145.17000, 6.37000, 339.48410, 0.00000,0,1, 4.68168, 12366.03000, 4.68168, 296.78470, 15000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 30172.60000, 11.80000, 724.14240,1, 7000.00000, 17899000.00000, 15.33000, 39198.81000,1, 10.18919, 19027.82000, 10.18919, 456.66760 +2028-01-01 00:00:00,1, 339026.40000, 963312942.00000, 32.26232, 2106.51700, 742.46780, 2109655.00000, 9500.00000,67.07539,10.66381,78.15451,12.4252, 32.26232, 2106.51700, 0.00000, 0.00000,1, 339026.40000, 963312942.00000, 32.26232, 2106.51700, 742.46780, 2109655.00000, 9500.00000,67.07539,10.66381,78.15451,12.4252, 32.26232, 2106.51700, 0.00000, 0.00000, 1.00000,1, 202026.40000, 550027501.00000, 32.26232, 2106.51700, 442.43780, 1204560.00000, 67.73768,1, 130000.00000, 392831440.00000, 284.70000, 860300.80000, 0.00000,0,1, 6.52000, 16470.22000, 6.52000, 395.28530, 0.00000,0,1, 4.18557, 14074.84000, 4.18557, 337.79620, 12000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 34479.60000, 11.80000, 827.51040,1, 7000.00000, 20454000.00000, 15.33000, 44794.26000,1, 9.75676, 22746.87000, 9.75676, 545.92490 +2029-01-01 00:00:00,1, 263106.70000, 1087396604.00000, 32.92043, 2389.90900, 576.20370, 2381399.00000, 7500.00000,68.17826,10.83915,76.82716,12.21417, 32.92043, 2389.90900, 0.00000, 0.00000,1, 263106.70000, 1087396604.00000, 32.92043, 2389.90900, 576.20370, 2381399.00000, 7500.00000,68.17826,10.83915,76.82716,12.21417, 32.92043, 2389.90900, 0.00000, 0.00000, 1.00000,1, 206106.70000, 623969164.00000, 32.92043, 2389.90900, 451.37370, 1366492.00000, 67.07957,1, 50000.00000, 440411440.00000, 109.50000, 964501.00000, 0.00000,0,1, 6.64000, 18856.54000, 6.64000, 452.55700, 0.00000,0,1, 4.50746, 15606.76000, 4.50746, 374.56220, 14000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 38798.40000, 11.80000, 931.16160,1, 7000.00000, 23016000.00000, 15.33000, 50405.04000,1, 9.97297, 26317.85000, 9.97297, 631.62830 +2030-01-01 00:00:00,1, 262298.20000, 1183430550.00000, 32.79003, 2678.29200, 574.43310, 2591713.00000, 8000.00000,68.80684,10.93908,71.80414,11.4156, 32.79003, 2678.29200, 0.00000, 0.00000,1, 262298.20000, 1183430550.00000, 32.79003, 2678.29200, 574.43310, 2591713.00000, 8000.00000,68.80684,10.93908,71.80414,11.4156, 32.79003, 2678.29200, 0.00000, 0.00000, 1.00000,1, 205298.20000, 699198109.00000, 32.79003, 2678.29200, 449.60310, 1531244.00000, 67.20997,1, 50000.00000, 458661440.00000, 109.50000, 1004469.00000, 0.00000,0,1, 6.80000, 21280.14000, 6.80000, 510.72340, 0.00000,0,1, 4.86570, 17251.98000, 4.86570, 414.04750, 16000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 43105.40000, 11.80000, 1034.53000,1, 7000.00000, 25571000.00000, 15.33000, 56000.49000,1, 9.32432, 29957.98000, 9.32432, 718.99150 +2031-01-01 00:00:00,1, 260635.10000, 1279169392.00000, 32.52178, 2965.53300, 570.79090, 2801381.00000, 7000.00000,69.02248,10.97337,nan,nan, 32.52178, 2965.53300, 0.00000, 0.00000,1, 260635.10000, 1279169392.00000, 32.52178, 2965.53300, 570.79090, 2801381.00000, 7000.00000,69.02248,10.97337,nan,nan, 32.52178, 2965.53300, 0.00000, 0.00000, 1.00000,1, 203635.10000, 774131952.00000, 32.52178, 2965.53300, 445.96090, 1695349.00000, 67.47822,1, 50000.00000, 476911440.00000, 109.50000, 1044436.00000, 0.00000,0,1, 6.89000, 23762.14000, 6.89000, 570.29140, 0.00000,0,1, 4.50746, 19027.96000, 4.50746, 456.67100, 14000.00000, 10.00000, 200.00000, 18811.88000,1, 11.80000, 47412.40000, 11.80000, 1137.89800,1, 7000.00000, 28126000.00000, 15.33000, 61595.94000,1, 9.32432, 33361.36000, 9.32432, 800.67260 diff --git a/src/libecalc/fixtures/cases/ltp_export/utilities.py b/src/libecalc/fixtures/cases/ltp_export/utilities.py index ca1d3a1931..8070c0906c 100644 --- a/src/libecalc/fixtures/cases/ltp_export/utilities.py +++ b/src/libecalc/fixtures/cases/ltp_export/utilities.py @@ -8,6 +8,7 @@ from libecalc.dto import Asset, Installation from libecalc.presentation.exporter.configs.configs import LTPConfig from libecalc.presentation.exporter.dto.dtos import FilteredResult +from libecalc.presentation.exporter.infrastructure import ExportableGraphResult def get_consumption( @@ -28,7 +29,7 @@ def get_consumption( ) ltp_filter = LTPConfig.filter(frequency=Frequency.YEAR) - ltp_result = ltp_filter.filter(graph_result, time_vector) + ltp_result = ltp_filter.filter(ExportableGraphResult(graph_result), time_vector) return ltp_result diff --git a/src/libecalc/presentation/exporter/aggregators.py b/src/libecalc/presentation/exporter/aggregators.py index 73fbe90c6c..c0cc00389c 100644 --- a/src/libecalc/presentation/exporter/aggregators.py +++ b/src/libecalc/presentation/exporter/aggregators.py @@ -1,11 +1,10 @@ import abc from typing import List -from libecalc.application.graph_result import GraphResult -from libecalc.common.component_type import ComponentType from libecalc.common.logger import logger from libecalc.common.time_utils import Frequency from libecalc.presentation.exporter.appliers import Applier +from libecalc.presentation.exporter.domain.exportable import ExportableSet, ExportableType from libecalc.presentation.exporter.dto.dtos import GroupedQueryResult, QueryResult @@ -13,7 +12,7 @@ class Aggregator(abc.ABC): @abc.abstractmethod def aggregate( self, - energy_calculator_result: GraphResult, + energy_calculator_result: ExportableSet, ) -> List[GroupedQueryResult]: """Each entry in this list will be handled separately Should ideally only work on one level in the hierarchy, more than one level @@ -50,7 +49,7 @@ def __init__(self, frequency: Frequency, appliers: List[Applier]): def aggregate( self, - energy_calculator_result: GraphResult, + energy_calculator_result: ExportableSet, ) -> List[GroupedQueryResult]: """Aggregates data at installation level. @@ -71,12 +70,11 @@ def aggregate( :return: """ aggregated_installation_results: List[GroupedQueryResult] = [] - for installation_id in energy_calculator_result.graph.get_nodes_of_type(ComponentType.INSTALLATION): - installation_graph = energy_calculator_result.get_subgraph(installation_id) - installation_name = installation_graph.graph.get_node_info(installation_graph.graph.root).name + for installation in energy_calculator_result.get_from_type(ExportableType.INSTALLATION): + installation_name = installation.get_name() single_results: List[QueryResult] = [] for applier in self.appliers: - applier_result = applier.apply(installation_graph, self.frequency) + applier_result = applier.apply(installation, self.frequency) if applier_result: single_results.append(applier_result) diff --git a/src/libecalc/presentation/exporter/appliers.py b/src/libecalc/presentation/exporter/appliers.py index 249ecb2b38..0d451bf08f 100644 --- a/src/libecalc/presentation/exporter/appliers.py +++ b/src/libecalc/presentation/exporter/appliers.py @@ -2,23 +2,23 @@ from datetime import datetime from typing import Any, Dict, Optional -from libecalc.application.graph_result import GraphResult from libecalc.common.math.numbers import Numbers from libecalc.common.time_utils import Frequency from libecalc.common.units import Unit from libecalc.presentation.exporter.configs import configs +from libecalc.presentation.exporter.domain.exportable import Exportable from libecalc.presentation.exporter.dto.dtos import QueryResult from libecalc.presentation.exporter.queries import Query class Modifier(abc.ABC): - def __init__(self, modifier: Optional["Modifier"] = None): + def __init__(self, before_modifier: Optional["Modifier"] = None): """Modifiers can be chained, just make sure in/output of the modifiers are compatible. Modifier is completely private, to not be inherited by children - :param modifier: + :param before_modifier: """ - self.__modifier = modifier + self.__before_modifier = before_modifier def modify(self, data: Dict[datetime, float]) -> Dict[datetime, Any]: """Public modify() method that is used in order to be able @@ -26,8 +26,8 @@ def modify(self, data: Dict[datetime, float]) -> Dict[datetime, Any]: :param data: :return: """ - if self.__modifier is not None: - return self._modify(self.__modifier._modify(data)) + if self.__before_modifier is not None: + return self._modify(self.__before_modifier.modify(data)) return self._modify(data) @@ -88,7 +88,7 @@ def __init__( def apply( self, - installation_graph: GraphResult, + installation_graph: Exportable, frequency: Frequency, ) -> Optional[QueryResult]: values = self.query.query(installation_graph, self.unit, frequency) diff --git a/src/libecalc/presentation/exporter/configs/configs.py b/src/libecalc/presentation/exporter/configs/configs.py index 0c69189ada..9eccbb333c 100644 --- a/src/libecalc/presentation/exporter/configs/configs.py +++ b/src/libecalc/presentation/exporter/configs/configs.py @@ -15,14 +15,12 @@ from libecalc.presentation.exporter.filters import Filter from libecalc.presentation.exporter.generators import Generator, TimeIndexGenerator from libecalc.presentation.exporter.queries import ( - ElConsumerPowerConsumptionQuery, ElectricityGeneratedQuery, EmissionQuery, - FuelConsumerPowerConsumptionQuery, FuelQuery, MaxUsageFromShoreQuery, - PowerSupplyOnshoreQuery, - VolumeQuery, + PowerConsumptionQuery, + StorageVolumeQuery, ) """ @@ -605,7 +603,7 @@ def aggregator(frequency: Frequency) -> Aggregator: name="loadedAndStoredOil", # TODO: Get correct Centuries name here title="Total Oil Loaded/Stored", unit=Unit.STANDARD_CUBIC_METER, - query=VolumeQuery( + query=StorageVolumeQuery( installation_category="FIXED", consumer_categories=["LOADING"], ), @@ -623,7 +621,7 @@ def aggregator(frequency: Frequency) -> Aggregator: name="gasTurbineCompressorConsumption", title="Total Power Consumed By Gas-Turbine Driven Compressors", unit=Unit.GIGA_WATT_HOURS, - query=FuelConsumerPowerConsumptionQuery( + query=PowerConsumptionQuery( installation_category="FIXED", consumer_categories=["GAS-DRIVEN-COMPRESSOR"], ), @@ -632,7 +630,7 @@ def aggregator(frequency: Frequency) -> Aggregator: name="offshoreWindConsumption", title="Total Electricity Consumed From Offshore Wind", unit=Unit.GIGA_WATT_HOURS, - query=ElConsumerPowerConsumptionQuery( + query=PowerConsumptionQuery( installation_category="FIXED", consumer_categories=["OFFSHORE-WIND"], ), @@ -642,7 +640,7 @@ def aggregator(frequency: Frequency) -> Aggregator: name="fromShoreConsumption", title="Total Electricity Consumed From Power-From-Shore", unit=Unit.GIGA_WATT_HOURS, - query=ElectricityGeneratedQuery( + query=PowerConsumptionQuery( producer_categories=["POWER-FROM-SHORE"], ), ), @@ -650,7 +648,7 @@ def aggregator(frequency: Frequency) -> Aggregator: name="powerSupplyOnshore", title="Power Supply Onshore", unit=Unit.GIGA_WATT_HOURS, - query=PowerSupplyOnshoreQuery( + query=ElectricityGeneratedQuery( producer_categories=["POWER-FROM-SHORE"], ), ), @@ -666,7 +664,7 @@ def aggregator(frequency: Frequency) -> Aggregator: name="steamTurbineGeneratorConsumption", title="Total Electricity Consumed From Steam Turbine Generators", unit=Unit.GIGA_WATT_HOURS, - query=ElConsumerPowerConsumptionQuery( + query=PowerConsumptionQuery( installation_category="FIXED", consumer_categories=["STEAM-TURBINE-GENERATOR"], ), diff --git a/src/libecalc/presentation/exporter/domain/exportable.py b/src/libecalc/presentation/exporter/domain/exportable.py new file mode 100644 index 0000000000..7489d34146 --- /dev/null +++ b/src/libecalc/presentation/exporter/domain/exportable.py @@ -0,0 +1,74 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Iterable, Iterator, List, Optional, Tuple + +from libecalc.common.units import Unit + + +class ExportableType(str, Enum): + INSTALLATION = "INSTALLATION" + + +class ConsumptionType(str, Enum): + FUEL = "FUEL" + ELECTRICITY = "ELECTRICITY" + + +@dataclass +class AttributeMeta: + fuel_category: Optional[str] + consumer_category: Optional[str] + producer_category: Optional[str] = None + emission_type: Optional[str] = None + + +class Attribute(ABC): + @abstractmethod + def datapoints(self) -> Iterable[Tuple[datetime, float]]: ... + + @abstractmethod + def get_meta(self) -> AttributeMeta: ... + + +@dataclass +class AttributeSet(ABC): + attributes: List[Attribute] + + def __iter__(self) -> Iterator[Attribute]: + return self.attributes.__iter__() + + +class Exportable(ABC): + @abstractmethod + def get_name(self) -> str: ... + + @abstractmethod + def get_category(self) -> str: ... + + @abstractmethod + def get_timesteps(self) -> List[datetime]: ... + + @abstractmethod + def get_fuel_consumption(self) -> AttributeSet: ... + + @abstractmethod + def get_power_consumption(self, unit: Unit) -> AttributeSet: ... + + @abstractmethod + def get_emissions(self, unit: Unit) -> AttributeSet: ... + + @abstractmethod + def get_electricity_production(self, unit: Unit) -> AttributeSet: ... + + @abstractmethod + def get_maximum_electricity_production(self, unit: Unit) -> AttributeSet: ... + + @abstractmethod + def get_storage_volumes(self, unit: Unit) -> AttributeSet: ... + + +class ExportableSet(ABC): + @abstractmethod + def get_from_type(self, exportable_type: ExportableType) -> List[Exportable]: ... diff --git a/src/libecalc/presentation/exporter/filters.py b/src/libecalc/presentation/exporter/filters.py index 27ac95816b..441c138b25 100644 --- a/src/libecalc/presentation/exporter/filters.py +++ b/src/libecalc/presentation/exporter/filters.py @@ -2,8 +2,8 @@ from datetime import datetime from typing import List -from libecalc.application.graph_result import GraphResult from libecalc.presentation.exporter.aggregators import Aggregator +from libecalc.presentation.exporter.domain.exportable import ExportableSet from libecalc.presentation.exporter.dto.dtos import FilteredResult from libecalc.presentation.exporter.generators import Generator @@ -27,7 +27,7 @@ class Filter: def filter( self, - energy_calculator_result: GraphResult, + energy_calculator_result: ExportableSet, time_vector: List[datetime], ) -> FilteredResult: data_series_collection = [generator.generate(time_vector) for generator in self.generators] diff --git a/src/libecalc/presentation/exporter/infrastructure.py b/src/libecalc/presentation/exporter/infrastructure.py new file mode 100644 index 0000000000..662dc0501a --- /dev/null +++ b/src/libecalc/presentation/exporter/infrastructure.py @@ -0,0 +1,405 @@ +from datetime import datetime +from typing import Iterable, List, Optional, Tuple + +from typing_extensions import assert_never + +from libecalc.application.graph_result import GraphResult +from libecalc.common.component_type import ComponentType +from libecalc.common.errors.exceptions import ProgrammingError +from libecalc.common.logger import logger +from libecalc.common.temporal_model import TemporalModel +from libecalc.common.time_utils import Frequency, Period, Periods +from libecalc.common.units import Unit +from libecalc.common.utils.rates import TimeSeries, TimeSeriesFloat, TimeSeriesRate, TimeSeriesStreamDayRate +from libecalc.core.result import GeneratorSetResult +from libecalc.dto import FuelConsumer, GeneratorSet +from libecalc.dto.utils.validators import convert_expression +from libecalc.presentation.exporter.domain.exportable import ( + Attribute, + AttributeMeta, + AttributeSet, + Exportable, + ExportableSet, + ExportableType, +) +from libecalc.presentation.yaml.yaml_types.emitters.yaml_venting_emitter import ( + YamlDirectTypeEmitter, + YamlOilTypeEmitter, + YamlVentingType, +) + + +class TimeSeriesAttribute(Attribute): + def __init__(self, time_series: TimeSeries, attribute_meta: AttributeMeta): + self._attribute_meta = attribute_meta + self._time_series = time_series + + def datapoints(self) -> Iterable[Tuple[datetime, float]]: + return self._time_series.datapoints() + + def get_meta(self) -> AttributeMeta: + return self._attribute_meta + + +class InstallationExportable(Exportable): + def __init__(self, installation_graph: GraphResult): + self._installation_graph = installation_graph + self._frequency: Optional[Frequency] = None + self._installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) + + def get_electricity_production(self, unit: Unit) -> AttributeSet: + attributes = [] + for fuel_consumer in self._installation_dto.fuel_consumers: + if not isinstance(fuel_consumer, GeneratorSet): + continue + + fuel_consumer_result = self._installation_graph.get_energy_result(fuel_consumer.id) + assert isinstance(fuel_consumer_result, GeneratorSetResult) + consumer_category = TemporalModel(fuel_consumer.user_defined_category) + for period, category in consumer_category.items(): + if fuel_consumer.cable_loss is not None: + # TODO: Move this calculation into generator set + cable_loss = self._installation_graph.variables_map.evaluate( + convert_expression(fuel_consumer.cable_loss) + ) + + power_production_values = fuel_consumer_result.power.values * (1 + cable_loss) + power_production_rate = TimeSeriesStreamDayRate( + timesteps=fuel_consumer_result.power.timesteps, + values=power_production_values, + unit=fuel_consumer_result.power.unit, + ) + else: + power_production_rate = fuel_consumer_result.power + + electricity_production_volumes = ( + TimeSeriesRate.from_timeseries_stream_day_rate( + power_production_rate, + regularity=self._get_regularity(), + ) + .for_period(period) + .to_volumes() + .to_unit(unit) + ) + + attributes.append( + TimeSeriesAttribute( + time_series=electricity_production_volumes, + attribute_meta=AttributeMeta( + fuel_category=None, + consumer_category=None, + producer_category=category, + ), + ) + ) + return AttributeSet(attributes) + + def get_maximum_electricity_production(self, unit: Unit) -> AttributeSet: + attributes = [] + for fuel_consumer in self._installation_dto.fuel_consumers: + if not isinstance(fuel_consumer, GeneratorSet): + continue + + if fuel_consumer.max_usage_from_shore is None: + continue + + consumer_category = TemporalModel(fuel_consumer.user_defined_category) + for period, category in consumer_category.items(): + max_usage_from_shore_values = self._installation_graph.variables_map.evaluate( + convert_expression(fuel_consumer.max_usage_from_shore) + ).tolist() + max_usage_from_shore_rate = ( + TimeSeriesRate.from_timeseries_stream_day_rate( + TimeSeriesStreamDayRate( + timesteps=self._installation_graph.timesteps, + values=max_usage_from_shore_values, + unit=Unit.MEGA_WATT, + ), + regularity=self._get_regularity(), + ) + .for_period(period) + .to_unit(unit) + ) + + attributes.append( + TimeSeriesAttribute( + time_series=max_usage_from_shore_rate, + attribute_meta=AttributeMeta( + fuel_category=None, + consumer_category=None, + producer_category=category, + ), + ) + ) + return AttributeSet(attributes) + + def get_storage_volumes(self, unit: Unit) -> AttributeSet: + attributes = [] + for venting_emitter in self._installation_dto.venting_emitters: + if venting_emitter.type != YamlVentingType.OIL_VOLUME: + continue + + oil_rates = venting_emitter.get_oil_rates( + expression_evaluator=self._installation_graph.variables_map, + regularity=self._get_regularity(), + ) + oil_volumes = TimeSeriesRate.from_timeseries_stream_day_rate( + oil_rates, regularity=self._get_regularity() + ).to_volumes() + oil_volumes = oil_volumes.to_unit(unit) + attributes.append( + TimeSeriesAttribute( + time_series=oil_volumes, + attribute_meta=AttributeMeta( + fuel_category=None, + consumer_category=venting_emitter.user_defined_category, + ), + ) + ) + return AttributeSet(attributes) + + def get_category(self) -> str: + return self._installation_dto.user_defined_category + + def get_timesteps(self) -> List[datetime]: + return self._installation_graph.timesteps + + def _get_regularity(self) -> TimeSeriesFloat: + return TimeSeriesFloat( + timesteps=self.get_timesteps(), + values=self._installation_graph.variables_map.evaluate( + expression=TemporalModel(self._installation_dto.regularity) + ).tolist(), + unit=Unit.NONE, + ) + + @staticmethod + def _combine_categories( + fuel_category: TemporalModel[str] = None, + consumer_category: TemporalModel[str] = None, + producer_category: TemporalModel[str] = None, + ) -> List[Tuple[Period, AttributeMeta]]: + defined_temporal_categories = [ + temporal_category + for temporal_category in [fuel_category, consumer_category, producer_category] + if temporal_category is not None + ] + if len(defined_temporal_categories) < 2: + raise ProgrammingError("Should combine at least two temporal categories") + + timesteps = set() + for temporal_model in defined_temporal_categories: + for period in temporal_model.get_periods(): + timesteps.add(period.start) + timesteps.add(period.end) + + periods = Periods.create_periods(sorted(timesteps), include_before=False, include_after=False) + + def _get_category(temporal_category: Optional[TemporalModel[str]], timestep: datetime) -> Optional[str]: + """ + Get category for a timestep, returning None if temporal category is None or not defined for timestep + Args: + temporal_category: temporal category + timestep: the timestep to get category for + + Returns: category or None + """ + if temporal_category is None: + return None + + try: + return temporal_category.get_model(timestep) + except ValueError: + # category not defined for timestep + return None + + combined = [] + for period in periods: + combined.append( + ( + period, + AttributeMeta( + fuel_category=_get_category(fuel_category, period.start), + consumer_category=_get_category(consumer_category, period.start), + producer_category=_get_category(producer_category, period.start), + ), + ) + ) + return combined + + def get_fuel_consumption(self) -> AttributeSet: + attributes = [] + for fuel_consumer in self._installation_dto.fuel_consumers: + assert isinstance(fuel_consumer, (GeneratorSet, FuelConsumer)) + + fuel_consumer_result = self._installation_graph.get_energy_result(fuel_consumer.id) + consumer_category = TemporalModel(fuel_consumer.user_defined_category) + fuel_category = TemporalModel( + {fuel_period: fuel.user_defined_category for fuel_period, fuel in fuel_consumer.fuel.items()} + ) + for period, attribute_meta in self._combine_categories(fuel_category, consumer_category): + attributes.append( + TimeSeriesAttribute( + time_series=TimeSeriesRate.from_timeseries_stream_day_rate( + fuel_consumer_result.energy_usage, regularity=self._get_regularity() + ) + .for_period(period) + .to_volumes(), + attribute_meta=attribute_meta, + ) + ) + + return AttributeSet(attributes) + + def get_power_consumption(self, unit: Unit) -> AttributeSet: + attributes = [] + + # Get power consumption from electricity consumers + for fuel_consumer in self._installation_dto.fuel_consumers: + if not isinstance(fuel_consumer, GeneratorSet): + continue + + for electricity_consumer in fuel_consumer.consumers: + electricity_consumer_result = self._installation_graph.get_energy_result(electricity_consumer.id) + + if electricity_consumer_result.power is None: + continue + + temporal_consumer_category = TemporalModel(electricity_consumer.user_defined_category) + temporal_producer_category = TemporalModel(fuel_consumer.user_defined_category) + for period, attribute_meta in self._combine_categories( + consumer_category=temporal_consumer_category, producer_category=temporal_producer_category + ): + electricity_consumption_volumes = ( + TimeSeriesRate.from_timeseries_stream_day_rate( + electricity_consumer_result.power, + regularity=self._get_regularity(), + ) + .for_period(period) + .to_volumes() + .to_unit(unit) + ) + + attributes.append( + TimeSeriesAttribute( + time_series=electricity_consumption_volumes, + attribute_meta=AttributeMeta( + fuel_category=None, + consumer_category=attribute_meta.consumer_category, + producer_category=attribute_meta.producer_category, + ), + ) + ) + + # Get power consumption from fuel consumers + for fuel_consumer in self._installation_dto.fuel_consumers: + if not isinstance(fuel_consumer, FuelConsumer): + continue + + for period, consumer_category in TemporalModel(fuel_consumer.user_defined_category).items(): + fuel_consumer_result = self._installation_graph.get_energy_result(fuel_consumer.id) + time_vector = fuel_consumer_result.timesteps + shaft_power = fuel_consumer_result.power + if ( + shaft_power is not None + and 0 < len(shaft_power) == len(time_vector) + and len(fuel_consumer_result.timesteps) == len(self._installation_graph.timesteps) + ): + shaft_power_volumes = ( + TimeSeriesRate.from_timeseries_stream_day_rate(shaft_power, regularity=self._get_regularity()) + .for_period(period) + .to_volumes() + .to_unit(unit) + ) + + attributes.append( + TimeSeriesAttribute( + time_series=shaft_power_volumes, + attribute_meta=AttributeMeta( + fuel_category=None, # Not relevant for power consumer + consumer_category=consumer_category, + ), + ) + ) + + else: + # TODO: is this ok? + logger.warning( + f"A combination of one or more compressors that do not support fuel to power conversion was used." + f"We are therefore unable to calculate correct power usage. Please only use compressors which support POWER conversion" + f"for fuel consumer '{fuel_consumer.name}'" + ) + + return AttributeSet(attributes) + + def get_emissions(self, unit: Unit) -> AttributeSet: + attributes = [] + + for fuel_consumer in self._installation_dto.fuel_consumers: + assert isinstance(fuel_consumer, (GeneratorSet, FuelConsumer)) + + emissions = self._installation_graph.get_emissions(fuel_consumer.id) + consumer_category = TemporalModel(fuel_consumer.user_defined_category) + fuel_category = TemporalModel( + {fuel_period: fuel.user_defined_category for fuel_period, fuel in fuel_consumer.fuel.items()} + ) + for period, attribute_meta in self._combine_categories(fuel_category, consumer_category): + for emission in emissions.values(): + emission_volumes = ( + TimeSeriesRate.from_timeseries_stream_day_rate(emission.rate, regularity=self._get_regularity()) + .for_period(period) + .to_volumes() + .to_unit(unit) + ) + emission_attribute_meta = AttributeMeta( + fuel_category=attribute_meta.fuel_category, + consumer_category=attribute_meta.consumer_category, + emission_type=emission.name, + ) + attributes.append( + TimeSeriesAttribute( + time_series=emission_volumes, + attribute_meta=emission_attribute_meta, + ) + ) + + for venting_emitter in self._installation_dto.venting_emitters: + assert isinstance(venting_emitter, (YamlOilTypeEmitter, YamlDirectTypeEmitter)) + + emissions = self._installation_graph.get_emissions(venting_emitter.id) + for emission in emissions.values(): + attributes.append( + TimeSeriesAttribute( + time_series=TimeSeriesRate.from_timeseries_stream_day_rate( + emission.rate, regularity=self._get_regularity() + ) + .to_volumes() + .to_unit(unit), + attribute_meta=AttributeMeta( + fuel_category=None, + consumer_category=venting_emitter.user_defined_category, + emission_type=emission.name, + ), + ) + ) + + return AttributeSet(attributes) + + def get_name(self) -> str: + return self._installation_graph.graph.get_node_info(self._installation_graph.graph.root).name + + +class ExportableGraphResult(ExportableSet): + def __init__(self, graph_result: GraphResult): + self._graph_result = graph_result + + def get_from_type(self, exportable_type: ExportableType) -> List[Exportable]: + if exportable_type == ExportableType.INSTALLATION: + component_type = ComponentType.INSTALLATION + else: + assert_never(exportable_type) + + return [ + InstallationExportable(self._graph_result.get_subgraph(installation_id)) + for installation_id in self._graph_result.graph.get_nodes_of_type(component_type) + ] diff --git a/src/libecalc/presentation/exporter/queries.py b/src/libecalc/presentation/exporter/queries.py index 7b725b36d0..ff4d8debbe 100644 --- a/src/libecalc/presentation/exporter/queries.py +++ b/src/libecalc/presentation/exporter/queries.py @@ -3,24 +3,14 @@ from datetime import datetime from typing import DefaultDict, Dict, List, Optional -import libecalc.dto -from libecalc.application.graph_result import GraphResult from libecalc.common.decorators.feature_flags import Feature -from libecalc.common.list.list_utils import array_to_list -from libecalc.common.temporal_model import TemporalModel from libecalc.common.time_utils import Frequency, resample_time_steps from libecalc.common.units import Unit from libecalc.common.utils.rates import ( TimeSeriesFloat, - TimeSeriesRate, - TimeSeriesStreamDayRate, TimeSeriesVolumes, ) -from libecalc.core.result import GeneratorSetResult -from libecalc.expression import Expression -from libecalc.presentation.yaml.yaml_types.emitters.yaml_venting_emitter import ( - YamlVentingType, -) +from libecalc.presentation.exporter.domain.exportable import Exportable class Query(abc.ABC): @@ -32,7 +22,7 @@ class Query(abc.ABC): @abc.abstractmethod def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: @@ -68,148 +58,85 @@ def __init__( def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) + if self.installation_category is not None and self.installation_category != installation_graph.get_category(): + return None aggregated_result: DefaultDict[datetime, float] = defaultdict(float) - aggregated_result_volume = {} - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in installation_dto.fuel_consumers: - temporal_category = TemporalModel(fuel_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.consumer_categories is None or category in self.consumer_categories: - fuel_consumer_result = installation_graph.get_energy_result(fuel_consumer.id) - fuel_volumes = ( - TimeSeriesRate.from_timeseries_stream_day_rate( - fuel_consumer_result.energy_usage, regularity=regularity - ) - .for_period(period) - .to_volumes() - ) - - fuel_temporal_model = TemporalModel(fuel_consumer.fuel) - for timestep, fuel_volume in fuel_volumes.datapoints(): - fuel_model = fuel_temporal_model.get_model(timestep) - fuel_category = fuel_model.user_defined_category - - if fuel_volume is not None: - if self.fuel_type_category is None or fuel_category == self.fuel_type_category: - aggregated_result[timestep] += fuel_volume - - if aggregated_result: - sorted_result = dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - # Last timestep is removed in check above (fuel_volume is None). Needed back for re-indexing: - # date_keys.append(installation_time_steps[-1]) - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - return aggregated_result_volume if aggregated_result_volume else None - - -class VolumeQuery(Query): + for attribute in installation_graph.get_fuel_consumption(): + meta = attribute.get_meta() + if self.fuel_type_category is not None and meta.fuel_category != self.fuel_type_category: + continue + + if self.consumer_categories is not None and meta.consumer_category not in self.consumer_categories: + continue + + for timestep, fuel_volume in attribute.datapoints(): + aggregated_result[timestep] += fuel_volume + + if aggregated_result: + sorted_result = dict( + sorted(zip(aggregated_result.keys(), aggregated_result.values())) + ) # Sort tuple with datetime and values, basically means sort on date since dates are unique? + sorted_result = { + **dict.fromkeys(installation_graph.get_timesteps(), 0.0), + **sorted_result, + } # Fill missing timesteps with zeroes, also keep sort? + date_keys = list(sorted_result.keys()) + reindexed_result = ( + TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit) + .reindex(resample_time_steps(time_steps=installation_graph.get_timesteps(), frequency=frequency)) + .fill_nan(0) + ) + + return {reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result))} + return None + + +class StorageVolumeQuery(Query): def __init__( self, installation_category: Optional[str] = None, consumer_categories: Optional[List[str]] = None, - fuel_type_category: Optional[str] = None, - emission_type: Optional[str] = None, ): self.installation_category = installation_category self.consumer_categories = consumer_categories - self.fuel_type_category = fuel_type_category - self.emission_type = emission_type def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) - - aggregated_emission_volume_output_unit = {} - aggregated_emission_volume: Dict[datetime, float] = defaultdict(float) - unit_in = None - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - # Add loading and storage volumes related to venting emissions, but ensure that emissions are not counted twice. - # Venting emissions have no fuel, and should not count when asking for emissions for a given fuel - if self.fuel_type_category is None: - for venting_emitter in installation_dto.venting_emitters: - if ( - self.consumer_categories is None - or venting_emitter.user_defined_category in self.consumer_categories - ) and venting_emitter.type == YamlVentingType.OIL_VOLUME: - oil_volumes = venting_emitter.get_oil_rates( - expression_evaluator=installation_graph.variables_map, regularity=regularity - ) - emission_volumes = TimeSeriesRate.from_timeseries_stream_day_rate( - oil_volumes, regularity=regularity - ).to_volumes() - unit_in = emission_volumes.unit - for timestep, emission_volume in emission_volumes.datapoints(): - if self.emission_type is None: - aggregated_emission_volume[timestep] += emission_volume - - if aggregated_emission_volume: - sorted_result = dict( - dict(sorted(zip(aggregated_emission_volume.keys(), aggregated_emission_volume.values()))).items() - ) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit_in) - .to_unit(unit) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_emission_volume_output_unit = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - - return aggregated_emission_volume_output_unit if aggregated_emission_volume_output_unit else None + if self.installation_category is not None and self.installation_category != installation_graph.get_category(): + return None + + aggregated_result: DefaultDict[datetime, float] = defaultdict(float) + for attribute in installation_graph.get_storage_volumes(unit): + meta = attribute.get_meta() + + if self.consumer_categories is not None and meta.consumer_category not in self.consumer_categories: + continue + + for timestep, fuel_volume in attribute.datapoints(): + aggregated_result[timestep] += fuel_volume + + if aggregated_result: + sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) + sorted_result = {**dict.fromkeys(installation_graph.get_timesteps(), 0.0), **sorted_result} + date_keys = list(sorted_result.keys()) + + reindexed_result = ( + TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit) + .reindex(resample_time_steps(time_steps=installation_graph.get_timesteps(), frequency=frequency)) + .fill_nan(0) + ) + + return {reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result))} + return None @@ -228,93 +155,43 @@ def __init__( def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) - - aggregated_result_volume = {} - aggregated_result: Dict[datetime, float] = defaultdict(float) - unit_in = None - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in installation_dto.fuel_consumers: - temporal_category = TemporalModel(fuel_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.consumer_categories is None or category in self.consumer_categories: - fuel_temporal_model = TemporalModel(fuel_consumer.fuel) - - emissions = installation_graph.get_emissions(fuel_consumer.id) - - for emission in emissions.values(): - emission_volumes = ( - TimeSeriesRate.from_timeseries_stream_day_rate(emission.rate, regularity=regularity) - .for_period(period) - .to_volumes() - ) - unit_in = emission_volumes.unit - for timestep, emission_volume in emission_volumes.datapoints(): - fuel_model = fuel_temporal_model.get_model(timestep) - fuel_category = fuel_model.user_defined_category - - if self.fuel_type_category is None or fuel_category == self.fuel_type_category: - if self.emission_type is None or emission.name == self.emission_type: - aggregated_result[timestep] += emission_volume - - # Add emissions from direct emitters, but ensure that emissions are not counted twice. - # Direct emissions have no fuel, and should not count when asking for emissions for a given fuel - if self.fuel_type_category is None: - for venting_emitter in installation_dto.venting_emitters: - if ( - self.consumer_categories is None - or venting_emitter.user_defined_category in self.consumer_categories - ): - emissions = installation_graph.get_emissions(venting_emitter.id) - - for emission_name, emission in emissions.items(): - rate = emission.rate - - emission_volumes = TimeSeriesRate.from_timeseries_stream_day_rate( - rate, regularity=regularity - ).to_volumes() - unit_in = emission_volumes.unit - for timestep, emission_volume in emission_volumes.datapoints(): - if self.emission_type is None or emission_name == self.emission_type: - aggregated_result[timestep] += emission_volume - - if aggregated_result: - sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit_in) - .to_unit(Unit.KILO) - .to_unit(unit) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - - return aggregated_result_volume if aggregated_result_volume else None + if self.installation_category is not None and self.installation_category != installation_graph.get_category(): + return None + + aggregated_result: DefaultDict[datetime, float] = defaultdict(float) + for attribute in installation_graph.get_emissions(unit): + meta = attribute.get_meta() + if self.fuel_type_category is not None and meta.fuel_category != self.fuel_type_category: + continue + + if self.consumer_categories is not None and meta.consumer_category not in self.consumer_categories: + continue + + if self.emission_type is not None and meta.emission_type != self.emission_type: + continue + + for timestep, emission_volume in attribute.datapoints(): + aggregated_result[timestep] += emission_volume + + if aggregated_result: + sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) + sorted_result = {**dict.fromkeys(installation_graph.get_timesteps(), 0.0), **sorted_result} + date_keys = list(sorted_result.keys()) + + reindexed_result = ( + TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit) + .to_unit(Unit.KILO) + .to_unit(unit) + .reindex(resample_time_steps(time_steps=installation_graph.get_timesteps(), frequency=frequency)) + .fill_nan(0) + ) + + return {reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result))} + return None @@ -327,151 +204,36 @@ def __init__(self, installation_category: Optional[str] = None, producer_categor def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) + if self.installation_category is not None and self.installation_category != installation_graph.get_category(): + return None aggregated_result: DefaultDict[datetime, float] = defaultdict(float) - aggregated_result_volume = {} - unit_in = None - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in installation_dto.fuel_consumers: - if isinstance(fuel_consumer, libecalc.dto.GeneratorSet): - temporal_category = TemporalModel(fuel_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.producer_categories is None or category in self.producer_categories: - fuel_consumer_result: GeneratorSetResult = installation_graph.get_energy_result( - fuel_consumer.id - ) - - cumulative_volumes_gwh = ( - TimeSeriesRate.from_timeseries_stream_day_rate( - fuel_consumer_result.power, regularity=regularity - ) - .for_period(period) - .to_volumes() - ) - - unit_in = cumulative_volumes_gwh.unit - - for timestep, cumulative_volume_gwh in cumulative_volumes_gwh.datapoints(): - aggregated_result[timestep] += cumulative_volume_gwh - - if aggregated_result: - sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit_in) - .to_unit(Unit.GIGA_WATT_HOURS) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - return aggregated_result_volume if aggregated_result_volume else None - - -class PowerSupplyOnshoreQuery(Query): - """GenSet only (ie el producers).""" + for attribute in installation_graph.get_electricity_production(unit): + meta = attribute.get_meta() - def __init__(self, installation_category: Optional[str] = None, producer_categories: Optional[List[str]] = None): - self.installation_category = installation_category - self.producer_categories = producer_categories + if self.producer_categories is not None and meta.producer_category not in self.producer_categories: + continue - def query( - self, - installation_graph: GraphResult, - unit: Unit, - frequency: Frequency, - ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) + for timestep, production_volume in attribute.datapoints(): + aggregated_result[timestep] += production_volume - aggregated_result: DefaultDict[datetime, float] = defaultdict(float) - aggregated_result_volume = {} - unit_in = None - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in installation_dto.fuel_consumers: - if isinstance(fuel_consumer, libecalc.dto.GeneratorSet) and fuel_consumer.cable_loss is not None: - temporal_category = TemporalModel(fuel_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.producer_categories is None or category in self.producer_categories: - fuel_consumer_result: GeneratorSetResult = installation_graph.get_energy_result( - fuel_consumer.id - ) - - cable_loss = Expression.evaluate( - fuel_consumer.cable_loss, - variables=installation_graph.variables_map.variables, - fill_length=len(installation_graph.variables_map.time_vector), - ) - - fuel_consumer_result.power.values = fuel_consumer_result.power.values * (1 + cable_loss) - - cumulative_volumes_gwh = ( - TimeSeriesRate.from_timeseries_stream_day_rate( - fuel_consumer_result.power, regularity=regularity - ) - .for_period(period) - .to_volumes() - ) - - unit_in = cumulative_volumes_gwh.unit - - for timestep, cumulative_volume_gwh in cumulative_volumes_gwh.datapoints(): - aggregated_result[timestep] += cumulative_volume_gwh - - if aggregated_result: - sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit_in) - .to_unit(Unit.GIGA_WATT_HOURS) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - return aggregated_result_volume if aggregated_result_volume else None + if aggregated_result: + sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) + sorted_result = {**dict.fromkeys(installation_graph.get_timesteps(), 0.0), **sorted_result} + date_keys = list(sorted_result.keys()) + + reindexed_result = ( + TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit) + .reindex(resample_time_steps(time_steps=installation_graph.get_timesteps(), frequency=frequency)) + .fill_nan(0) + ) + + return {reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result))} + return None class MaxUsageFromShoreQuery(Query): @@ -483,236 +245,86 @@ def __init__(self, installation_category: Optional[str] = None, producer_categor def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) + if self.installation_category is not None and self.installation_category != installation_graph.get_category(): + return None aggregated_result: DefaultDict[datetime, float] = defaultdict(float) - aggregated_result_volume = {} - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in installation_dto.fuel_consumers: - if ( - isinstance(fuel_consumer, libecalc.dto.GeneratorSet) - and fuel_consumer.max_usage_from_shore is not None - ): - temporal_category = TemporalModel(fuel_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.producer_categories is None or category in self.producer_categories: - installation_graph.get_energy_result(fuel_consumer.id) - - max_usage_from_shore = TimeSeriesStreamDayRate( - values=array_to_list( - Expression.evaluate( - fuel_consumer.max_usage_from_shore, - variables=installation_graph.variables_map.variables, - fill_length=len(installation_graph.variables_map.time_vector), - ) - ), - unit=unit, - timesteps=installation_graph.variables_map.time_vector, - ) - - results = TimeSeriesRate.from_timeseries_stream_day_rate( - max_usage_from_shore, regularity=regularity - ).for_period(period) - - for timestep, result in results.datapoints(): - aggregated_result[timestep] += result - - if aggregated_result: - sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - # Max usage from shore is time series float (values), and contains one more item - # than time steps for volumes. Number of values for max usage from shore should - # be the same as number of volume-time steps, hence [:-1] - reindexed_result = ( - TimeSeriesFloat(timesteps=date_keys, values=list(sorted_result.values()), unit=unit) - .reindex(time_steps) - .fill_nan(0) - )[:-1] - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - return aggregated_result_volume if aggregated_result_volume else None - - -class FuelConsumerPowerConsumptionQuery(Query): - def __init__(self, consumer_categories: Optional[List[str]] = None, installation_category: Optional[str] = None): - self.consumer_categories = consumer_categories - self.installation_category = installation_category + for attribute in installation_graph.get_maximum_electricity_production(unit): + meta = attribute.get_meta() + + if self.producer_categories is not None and meta.producer_category not in self.producer_categories: + continue + + for timestep, production_volume in attribute.datapoints(): + aggregated_result[timestep] += production_volume + + if aggregated_result: + sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) + sorted_result = {**dict.fromkeys(installation_graph.get_timesteps(), 0.0), **sorted_result} + date_keys = list(sorted_result.keys()) + + # Max usage from shore is time series float (values), and contains one more item + # than time steps for volumes. Number of values for max usage from shore should + # be the same as number of volume-time steps, hence [:-1] + reindexed_result = ( + TimeSeriesFloat(timesteps=date_keys, values=list(sorted_result.values()), unit=unit) + .reindex(resample_time_steps(time_steps=installation_graph.get_timesteps(), frequency=frequency)) + .fill_nan(0) + )[:-1] + + return {reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result))} + return None - def query( + +class PowerConsumptionQuery(Query): + def __init__( self, - installation_graph: GraphResult, - unit: Unit, - frequency: Frequency, - ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) - - fuel_consumers = installation_dto.fuel_consumers - fuel_consumers = [ - fuel_consumer - for fuel_consumer in fuel_consumers - if not isinstance(fuel_consumer, libecalc.dto.GeneratorSet) - ] - - aggregated_result: DefaultDict[datetime, float] = defaultdict( - float - ) # aggregate together over all fuel consumers with given category - aggregated_result_volume = {} - unit_in = None - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in fuel_consumers: - temporal_category = TemporalModel(fuel_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.consumer_categories is None or category in self.consumer_categories: - fuel_consumer_result = installation_graph.get_energy_result(fuel_consumer.id) - time_vector = fuel_consumer_result.timesteps - shaft_power = fuel_consumer_result.power - - if ( - shaft_power is not None - and 0 < len(shaft_power) == len(time_vector) - and len(fuel_consumer_result.timesteps) == len(installation_graph.timesteps) - ): - cumulative_volumes_gwh = ( - TimeSeriesRate.from_timeseries_stream_day_rate(shaft_power, regularity=regularity) - .for_period(period) - .to_volumes() - ) - unit_in = cumulative_volumes_gwh.unit - - for timestep, cumulative_volume_gwh in cumulative_volumes_gwh.datapoints(): - aggregated_result[timestep] += cumulative_volume_gwh - else: - raise NotImplementedError( - f"A combination of one or more compressors that do not support fuel to power conversion was used." - f"We are therefore unable to calculate correct power usage. Please only use compressors which support POWER conversion" - f"for fuel consumer {fuel_consumer.name}" - ) - - if aggregated_result: - sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit_in) - .to_unit(Unit.GIGA_WATT_HOURS) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - - return aggregated_result_volume if aggregated_result_volume else None - - -class ElConsumerPowerConsumptionQuery(Query): - def __init__(self, consumer_categories: Optional[List[str]] = None, installation_category: Optional[str] = None): + consumer_categories: Optional[List[str]] = None, + installation_category: Optional[str] = None, + producer_categories: List[str] = None, + ): self.consumer_categories = consumer_categories + self.producer_categories = producer_categories self.installation_category = installation_category @Feature.experimental("New LTP power consumption calculation") def query( self, - installation_graph: GraphResult, + installation_graph: Exportable, unit: Unit, frequency: Frequency, ) -> Optional[Dict[datetime, float]]: - installation_dto = installation_graph.graph.get_node(installation_graph.graph.root) - - installation_time_steps = installation_graph.timesteps - time_steps = resample_time_steps( - frequency=frequency, - time_steps=installation_time_steps, - ) - - regularity = TimeSeriesFloat( - timesteps=installation_time_steps, - values=installation_graph.variables_map.evaluate( - expression=TemporalModel(installation_dto.regularity) - ).tolist(), - unit=Unit.NONE, - ) + if self.installation_category is not None and self.installation_category != installation_graph.get_category(): + return None aggregated_result: DefaultDict[datetime, float] = defaultdict(float) - aggregated_result_volume = {} - unit_in = None - - if self.installation_category is None or installation_dto.user_defined_category == self.installation_category: - for fuel_consumer in installation_dto.fuel_consumers: - if isinstance(fuel_consumer, libecalc.dto.GeneratorSet): - for electrical_consumer in fuel_consumer.consumers: - temporal_category = TemporalModel(electrical_consumer.user_defined_category) - for period, category in temporal_category.items(): - if self.consumer_categories is None or category in self.consumer_categories: - electrical_consumer_result = installation_graph.get_energy_result( - electrical_consumer.id - ) - power = electrical_consumer_result.power - if power is not None: - cumulative_volumes_gwh = ( - TimeSeriesRate.from_timeseries_stream_day_rate(power, regularity=regularity) - .for_period(period) - .to_volumes() - ) - unit_in = cumulative_volumes_gwh.unit - - for timestep, cumulative_volume_gwh in cumulative_volumes_gwh.datapoints(): - aggregated_result[timestep] += cumulative_volume_gwh - - if aggregated_result: - sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) - sorted_result = {**dict.fromkeys(installation_time_steps, 0.0), **sorted_result} - date_keys = list(sorted_result.keys()) - - reindexed_result = ( - TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit_in) - .to_unit(Unit.GIGA_WATT_HOURS) - .reindex(time_steps) - .fill_nan(0) - ) - - aggregated_result_volume = { - reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result)) - } - - return aggregated_result_volume if aggregated_result_volume else None + for attribute in installation_graph.get_power_consumption(unit): + meta = attribute.get_meta() + + if self.producer_categories is not None and meta.producer_category not in self.producer_categories: + continue + + if self.consumer_categories is not None and meta.consumer_category not in self.consumer_categories: + continue + + for timestep, consumption_volume in attribute.datapoints(): + aggregated_result[timestep] += consumption_volume + + if aggregated_result: + sorted_result = dict(dict(sorted(zip(aggregated_result.keys(), aggregated_result.values()))).items()) + sorted_result = {**dict.fromkeys(installation_graph.get_timesteps(), 0.0), **sorted_result} + date_keys = list(sorted_result.keys()) + + reindexed_result = ( + TimeSeriesVolumes(timesteps=date_keys, values=list(sorted_result.values())[:-1], unit=unit) + .reindex(resample_time_steps(time_steps=installation_graph.get_timesteps(), frequency=frequency)) + .fill_nan(0) + ) + + return {reindexed_result.timesteps[i]: reindexed_result.values[i] for i in range(len(reindexed_result))} + + return None diff --git a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv index f93707910c..8e8f42ea3d 100644 --- a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv +++ b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv @@ -1,15 +1,15 @@ -forecastYear forecastMonth turbineFuelGasConsumption flareGasConsumption engineDieselConsumption turbineFuelGasCo2Mass flareGasCo2Mass engineDieselCo2Mass co2VentingMass turbineFuelGasNoxMass flareGasNoxMass engineDieselNoxMass turbineFuelGasNmvocMass flareGasNmvocMass engineDieselNmvocMass coldVentAndFugitivesNmvocMass storageNmvocMass loadingNmvocMass turbineFuelGasCh4Mass flareGasCh4Mass engineDieselCh4Mass coldVentAndFugitivesCh4Mass storageCh4Mass loadingCh4Mass loadedAndStoredOil gasTurbineGeneratorConsumption gasTurbineCompressorConsumption offshoreWindConsumption fromShoreConsumption steamTurbineGeneratorConsumption -#Years Months Fuel Consumption[Sm3] Flare Gas[Sm3] Diesel Consumption[L] CO2 From Fuel Gas[t] CO2 From Flare[t] CO2 From Diesel[t] CO2 From Cold Venting Fugitives[t] NOX From Fuel Gas[t] NOX From Flare[t] NOX From Diesel[t] NMVOC From Fuel Gas[t] NMVOC From Flare[t] NMVOC From Diesel[t] NMVOC From Cold Venting Fugitives[t] NMVOC From Storage[t] NMVOC From Loading[t] CH4 From Fuel Gas[t] CH4 From Flare[t] CH4 From Diesel[t] CH4 From Cold Venting Fugitives[t] CH4 From Storage[t] CH4 From Loading[t] Total Oil Loaded/Stored[Sm3] Total Electricity Generated[GWh] Total Power Consumed By Gas-Turbine Driven Compressors[GWh] Total Electricity Consumed From Offshore Wind[GWh] Total Electricity Consumed From Power-From-Shore[GWh] Total Electricity Consumed From Steam Turbine Generators[GWh] -2021 01 33446253 5475000 3889000 70571.61 14235 10539.19 0 306.3675 7.665036 118.6144 8.027098 0.328555 8.633489 38.32487 0 229.9502 30.43615 1.313909 0 131.3999 0 21.89993 1095000 122.3545 0 0 74.46 7.446 -2022 01 693967871 3650000 3518600 1464272 9490 9535.405 0 6356.745 5.110128 107.3173 166.5526 0.218887 7.811172 43.06989 0 229.9501 631.5108 0.87588 0 153.3002 0 21.89998 1095000 130.9912 50.34694 0 79.716 7.9716 -2023 01 533925379 3650000 3518600 1126583 9490 9535.405 0 4890.757 5.110128 107.3173 128.1428 0.218887 7.811172 43.43492 0 214.6201 485.8723 0.87588 0 156.9498 0 20.43985 1022000 130.9912 66.29014 0 79.716 7.9716 -2024 01 311313735 5490000 3806400 656872 14274 10315.34 0 2851.634 7.686037 116.0951 74.71564 0.329455 8.450098 42.82198 0 192.15 283.2951 1.317509 0 151.8899 0 18.30015 914999.9 122.6897 410.5339 32.85216 41.81184 7.4664 -2025 01 411507899 3650001 3591600 868281.6 9490.002 9733.236 0 3769.412 5.109883 109.5438 98.76234 0.219 7.973352 44.895 0 306.6 374.4718 0.876 0 160.5999 0 29.19988 1460000 138.1886 681.5524 37.00224 47.09376 8.4096 -2026 01 292912595 3650001 3591600 618045.6 9490.002 9733.236 0 2683.08 5.109883 109.5438 70.29935 0.219 7.973352 45.26012 0 459.9 266.5507 0.876 0 163.155 0 43.8 2190000 138.1886 468.6232 37.00224 47.09376 8.4096 -2027 01 234469407 3650001 3650001 494730.5 9490.002 9891.505 0 2147.74 5.109883 111.3249 56.27249 0.219 8.103 45.62488 0 268.275 213.3673 0.876 0 163.8849 0 25.55012 1277500 138.1886 354.3396 37.00224 47.09376 8.4096 -2028 01 177817904 5489999 2562000 375195.8 14274 6943.021 0 1628.812 7.686117 78.14112 42.67624 0.329488 5.687611 43.55391 0 153.7201 161.8146 1.317629 0 155.916 0 14.64015 731999.9 125.3475 325.7012 34.01165 43.28755 7.72992 -2029 01 192263657 3650001 2190000 405676.3 9490.002 5934.9 0 1761.135 5.109883 66.795 46.14348 0.219 4.8618 45.26012 0 137.97 174.96 0.876 0 161.6949 0 13.14 657000 134.7266 354.3396 37.00224 47.09376 8.4096 -2030 01 191916726 3650001 2190000 404944.3 9490.002 5934.9 0 1757.958 5.109883 66.795 46.06008 0.219 4.8618 45.62488 0 124.173 174.6446 0.876 0 163.155 0 11.826 591300 133.0609 354.3396 37.00224 47.09376 8.4096 -2031 01 177606484 3650001 2190000 374749.7 9490.002 5934.9 0 1626.876 5.109883 66.795 42.62546 0.219 4.8618 45.62488 0 111.7555 161.622 0.876 0 163.155 0 10.6434 532170 131.5041 347.7193 37.00224 47.09376 8.4096 -2032 01 157104083 5489999 2562000 331489.6 14274 6943.021 0 1439.074 7.686117 78.14112 37.70494 0.329488 5.687611 43.55391 0 100.8403 142.9649 1.317629 0 155.916 0 9.603781 480192 119.4056 316.8373 34.01165 43.28755 7.72992 -2033 01 27668241 0 0 58380 0 0 0 253.441 0 0 6.640445 0 0 0 0 0 25.17807 0 0 0 0 0 0 43.8 0 0 87.6 8.76 +forecastYear forecastMonth turbineFuelGasConsumption flareGasConsumption engineDieselConsumption turbineFuelGasCo2Mass flareGasCo2Mass engineDieselCo2Mass co2VentingMass turbineFuelGasNoxMass flareGasNoxMass engineDieselNoxMass turbineFuelGasNmvocMass flareGasNmvocMass engineDieselNmvocMass coldVentAndFugitivesNmvocMass storageNmvocMass loadingNmvocMass turbineFuelGasCh4Mass flareGasCh4Mass engineDieselCh4Mass coldVentAndFugitivesCh4Mass storageCh4Mass loadingCh4Mass loadedAndStoredOil gasTurbineGeneratorConsumption gasTurbineCompressorConsumption offshoreWindConsumption fromShoreConsumption powerSupplyOnshore steamTurbineGeneratorConsumption +#Years Months Fuel Consumption[Sm3] Flare Gas[Sm3] Diesel Consumption[L] CO2 From Fuel Gas[t] CO2 From Flare[t] CO2 From Diesel[t] CO2 From Cold Venting Fugitives[t] NOX From Fuel Gas[t] NOX From Flare[t] NOX From Diesel[t] NMVOC From Fuel Gas[t] NMVOC From Flare[t] NMVOC From Diesel[t] NMVOC From Cold Venting Fugitives[t] NMVOC From Storage[t] NMVOC From Loading[t] CH4 From Fuel Gas[t] CH4 From Flare[t] CH4 From Diesel[t] CH4 From Cold Venting Fugitives[t] CH4 From Storage[t] CH4 From Loading[t] Total Oil Loaded/Stored[Sm3] Total Electricity Generated[GWh] Total Power Consumed By Gas-Turbine Driven Compressors[GWh] Total Electricity Consumed From Offshore Wind[GWh] Total Electricity Consumed From Power-From-Shore[GWh] Power Supply Onshore[GWh] Total Electricity Consumed From Steam Turbine Generators[GWh] +2021 01 33446253 5475000 3889000 70571.61 14235 10539.19 0 306.3675 7.665036 118.6144 8.027098 0.328555 8.633489 38.32487 0 229.9502 30.43615 1.313909 0 131.3999 0 21.89993 1095000 122.3545 0 0 74.46 74.46 7.446 +2022 01 693967871 3650000 3518600 1464272 9490 9535.405 0 6356.745 5.110128 107.3173 166.5526 0.218887 7.811172 43.06989 0 229.9501 631.5108 0.87588 0 153.3002 0 21.89998 1095000 130.9912 50.34694 0 79.716 79.716 7.9716 +2023 01 533925379 3650000 3518600 1126583 9490 9535.405 0 4890.757 5.110128 107.3173 128.1428 0.218887 7.811172 43.43492 0 214.6201 485.8723 0.87588 0 156.9498 0 20.43985 1022000 130.9912 66.29014 0 79.716 79.716 7.9716 +2024 01 311313735 5490000 3806400 656872 14274 10315.34 0 2851.634 7.686037 116.0951 74.71564 0.329455 8.450098 42.82198 0 192.15 283.2951 1.317509 0 151.8899 0 18.30015 914999.9 122.6897 410.5339 32.85216 41.81184 41.81184 7.4664 +2025 01 411507899 3650001 3591600 868281.6 9490.002 9733.236 0 3769.412 5.109883 109.5438 98.76234 0.219 7.973352 44.895 0 306.6 374.4718 0.876 0 160.5999 0 29.19988 1460000 138.1886 681.5524 37.00224 47.09376 47.09376 8.4096 +2026 01 292912595 3650001 3591600 618045.6 9490.002 9733.236 0 2683.08 5.109883 109.5438 70.29935 0.219 7.973352 45.26012 0 459.9 266.5507 0.876 0 163.155 0 43.8 2190000 138.1886 468.6232 37.00224 47.09376 47.09376 8.4096 +2027 01 234469407 3650001 3650001 494730.5 9490.002 9891.505 0 2147.74 5.109883 111.3249 56.27249 0.219 8.103 45.62488 0 268.275 213.3673 0.876 0 163.8849 0 25.55012 1277500 138.1886 354.3396 37.00224 47.09376 47.09376 8.4096 +2028 01 177817904 5489999 2562000 375195.8 14274 6943.021 0 1628.812 7.686117 78.14112 42.67624 0.329488 5.687611 43.55391 0 153.7201 161.8146 1.317629 0 155.916 0 14.64015 731999.9 125.3475 325.7012 34.01165 43.28755 43.28755 7.72992 +2029 01 192263657 3650001 2190000 405676.3 9490.002 5934.9 0 1761.135 5.109883 66.795 46.14348 0.219 4.8618 45.26012 0 137.97 174.96 0.876 0 161.6949 0 13.14 657000 134.7266 354.3396 37.00224 47.09376 47.09376 8.4096 +2030 01 191916726 3650001 2190000 404944.3 9490.002 5934.9 0 1757.958 5.109883 66.795 46.06008 0.219 4.8618 45.62488 0 124.173 174.6446 0.876 0 163.155 0 11.826 591300 133.0609 354.3396 37.00224 47.09376 47.09376 8.4096 +2031 01 177606484 3650001 2190000 374749.7 9490.002 5934.9 0 1626.876 5.109883 66.795 42.62546 0.219 4.8618 45.62488 0 111.7555 161.622 0.876 0 163.155 0 10.6434 532170 131.5041 347.7193 37.00224 47.09376 47.09376 8.4096 +2032 01 157104083 5489999 2562000 331489.6 14274 6943.021 0 1439.074 7.686117 78.14112 37.70494 0.329488 5.687611 43.55391 0 100.8403 142.9649 1.317629 0 155.916 0 9.603781 480192 119.4056 316.8373 34.01165 43.28755 43.28755 7.72992 +2033 01 27668241 0 0 58380 0 0 0 253.441 0 0 6.640445 0 0 0 0 0 25.17807 0 0 0 0 0 0 43.8 0 0 87.6 87.6 8.76 diff --git a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv.json b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv.json index b381d3fe1d..a30b6fa005 100644 --- a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv.json +++ b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.INST_A.ltp.tsv.json @@ -463,6 +463,22 @@ "12":"43.28755", "13":"87.6" }, + "powerSupplyOnshore":{ + "0":"Power Supply Onshore[GWh]", + "1":"74.46", + "2":"79.716", + "3":"79.716", + "4":"41.81184", + "5":"47.09376", + "6":"47.09376", + "7":"47.09376", + "8":"43.28755", + "9":"47.09376", + "10":"47.09376", + "11":"47.09376", + "12":"43.28755", + "13":"87.6" + }, "steamTurbineGeneratorConsumption":{ "0":"Total Electricity Consumed From Steam Turbine Generators[GWh]", "1":"7.446", diff --git a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv index fa54a9c830..b9349c2d57 100644 --- a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv +++ b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv @@ -1,15 +1,15 @@ -forecastYear forecastMonth turbineFuelGasConsumption turbineFuelGasCo2Mass turbineFuelGasNoxMass turbineFuelGasNmvocMass turbineFuelGasCh4Mass gasTurbineGeneratorConsumption fromShoreConsumption -#Years Months Fuel Consumption[Sm3] CO2 From Fuel Gas[t] NOX From Fuel Gas[t] NMVOC From Fuel Gas[t] CH4 From Fuel Gas[t] Total Electricity Generated[GWh] Total Electricity Consumed From Power-From-Shore[GWh] -2021 01 0 0 0 0 0 0 0 -2022 01 50156012 105829.2 459.4292 12.03745 45.64206 159.432 0 -2023 01 0 0 0 0 0 0 159.432 -2024 01 0 0 0 0 0 0 149.328 -2025 01 0 0 0 0 0 0 168.192 -2026 01 0 0 0 0 0 0 168.192 -2027 01 0 0 0 0 0 0 168.192 -2028 01 0 0 0 0 0 0 154.5984 -2029 01 0 0 0 0 0 0 168.192 -2030 01 0 0 0 0 0 0 168.192 -2031 01 0 0 0 0 0 0 168.192 -2032 01 0 0 0 0 0 0 154.5984 -2033 01 0 0 0 0 0 0 175.2 +forecastYear forecastMonth turbineFuelGasConsumption turbineFuelGasCo2Mass turbineFuelGasNoxMass turbineFuelGasNmvocMass turbineFuelGasCh4Mass gasTurbineGeneratorConsumption fromShoreConsumption powerSupplyOnshore +#Years Months Fuel Consumption[Sm3] CO2 From Fuel Gas[t] NOX From Fuel Gas[t] NMVOC From Fuel Gas[t] CH4 From Fuel Gas[t] Total Electricity Generated[GWh] Total Electricity Consumed From Power-From-Shore[GWh] Power Supply Onshore[GWh] +2021 01 0 0 0 0 0 0 0 0 +2022 01 50156012 105829.2 459.4292 12.03745 45.64206 159.432 0 0 +2023 01 0 0 0 0 0 0 159.432 159.432 +2024 01 0 0 0 0 0 0 149.328 149.328 +2025 01 0 0 0 0 0 0 168.192 168.192 +2026 01 0 0 0 0 0 0 168.192 168.192 +2027 01 0 0 0 0 0 0 168.192 168.192 +2028 01 0 0 0 0 0 0 154.5984 154.5984 +2029 01 0 0 0 0 0 0 168.192 168.192 +2030 01 0 0 0 0 0 0 168.192 168.192 +2031 01 0 0 0 0 0 0 168.192 168.192 +2032 01 0 0 0 0 0 0 154.5984 154.5984 +2033 01 0 0 0 0 0 0 175.2 175.2 diff --git a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv.json b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv.json index b185113b7a..2fe512a06e 100644 --- a/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv.json +++ b/src/tests/ecalc_cli/snapshots/test_app/test_new_ltp_export_properly/test.POWER_FROM_SHORE_EVENT.ltp.tsv.json @@ -142,5 +142,21 @@ "11":"168.192", "12":"154.5984", "13":"175.2" + }, + "powerSupplyOnshore":{ + "0":"Power Supply Onshore[GWh]", + "1":"0", + "2":"0", + "3":"159.432", + "4":"149.328", + "5":"168.192", + "6":"168.192", + "7":"168.192", + "8":"154.5984", + "9":"168.192", + "10":"168.192", + "11":"168.192", + "12":"154.5984", + "13":"175.2" } } \ No newline at end of file