Skip to content

Commit e2a764d

Browse files
author
Jan Pecinovsky
committed
Enhance energy simulation evaluation and modeling
- Added methods to retrieve the first and last timestamps in TimeSeriesBase. - Implemented a new function `apply_simulation` to apply simulation results to input data in the PV simulation module. - Updated the PVSimulator class to include a method for converting simulation results to a DataFrame. - Enhanced the evaluation module with a new function `compare_results` to compare two evaluation results and return differences. - Modified the `evaluate` method to handle missing columns for electricity delivered and exported. - Updated imports in various modules to include new functions and classes as needed. - Adjusted execution counts in the demo notebook to reflect the new evaluation and simulation processes.
1 parent ff6d183 commit e2a764d

7 files changed

Lines changed: 429 additions & 55 deletions

File tree

demo_simeval.ipynb

Lines changed: 361 additions & 49 deletions
Large diffs are not rendered by default.

openenergyid/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ def from_json(cls, string: str | None = None, path: str | None = None, **kwargs)
6060
return cls.model_validate_json(file.read(), **kwargs)
6161
raise ValueError("Either string or path must be provided.")
6262

63+
def first_timestamp(self) -> dt.datetime:
64+
"""Get the first timestamp in the index."""
65+
return min(self.index)
66+
67+
def last_timestamp(self) -> dt.datetime:
68+
"""Get the last timestamp in the index."""
69+
return max(self.index)
70+
6371

6472
class TimeSeries(TimeSeriesBase):
6573
"""

openenergyid/pvsim/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .main import PVSimulationInput, get_simulator
1+
from .main import PVSimulationInput, apply_simulation, get_simulator
22

3-
__all__ = ["PVSimulationInput", "get_simulator"]
3+
__all__ = ["PVSimulationInput", "get_simulator", "apply_simulation"]

openenergyid/pvsim/abstract.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
import datetime as dt
66
from abc import ABC, abstractmethod
7-
from typing import cast
7+
from typing import Self, cast
88

99
import pandas as pd
1010
from aiohttp import ClientSession
1111
from pydantic import BaseModel, Field
1212

1313
from openenergyid.models import TimeSeries
1414

15+
from ..const import ELECTRICITY_PRODUCED
16+
1517

1618
class PVSimulationInputAbstract(BaseModel):
1719
"""
@@ -59,8 +61,14 @@ def result_to_timeseries(self):
5961
result = self.simulation_results.resample(self.result_resolution).sum()
6062
return TimeSeries.from_pandas(result)
6163

64+
def result_as_frame(self) -> pd.DataFrame:
65+
"""
66+
Convert the simulation results to a DataFrame.
67+
"""
68+
return self.simulation_results.rename(ELECTRICITY_PRODUCED).to_frame()
69+
6270
@classmethod
63-
def from_pydantic(cls, input_: PVSimulationInputAbstract) -> "PVSimulator":
71+
def from_pydantic(cls, input_: PVSimulationInputAbstract) -> Self:
6472
"""
6573
Create an instance of the simulator from Pydantic input data.
6674
"""

openenergyid/pvsim/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Annotated, Union
22

3+
import pandas as pd
34
from pydantic import Field
45

56
from .elia import EliaPVSimulationInput, EliaPVSimulator
@@ -17,3 +18,24 @@ def get_simulator(input_: PVSimulationInput) -> PVLibSimulator | EliaPVSimulator
1718
if isinstance(input_, EliaPVSimulationInput):
1819
return EliaPVSimulator.from_pydantic(input_)
1920
raise ValueError(f"Unknown simulator type: {input_.type}")
21+
22+
23+
def apply_simulation(input_data: pd.DataFrame, simulation_results: pd.Series) -> pd.DataFrame:
24+
"""Apply simulation results to input data."""
25+
df = input_data.copy()
26+
27+
if "electricity_produced" not in df.columns:
28+
df["electricity_produced"] = 0.0
29+
df["electricity_produced"] = df["electricity_produced"] + simulation_results
30+
31+
new_delivered = (df["electricity_delivered"] - simulation_results).clip(lower=0.0)
32+
self_consumed = df["electricity_delivered"] - new_delivered
33+
df["electricity_delivered"] = new_delivered
34+
35+
exported = simulation_results - self_consumed
36+
37+
if "electricity_exported" not in df.columns:
38+
df["electricity_exported"] = 0.0
39+
df["electricity_exported"] = df["electricity_exported"] + exported
40+
41+
return df

openenergyid/simeval/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Module containing basic evaluation functions for energy systems."""
22

3-
from .main import evaluate
3+
from .main import compare_results, evaluate
44
from .models import EvaluationInput
55

6-
__all__ = ["EvaluationInput", "evaluate"]
6+
__all__ = ["EvaluationInput", "evaluate", "compare_results"]

openenergyid/simeval/main.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Module for evaluating energy simulation data."""
22

3+
import typing
4+
35
import numpy as np
46
import pandas as pd
57

@@ -39,6 +41,8 @@ def __init__(self, data: pd.DataFrame, return_frequencies: list[str] | None = No
3941

4042
def evaluate(self) -> dict[str, pd.DataFrame | pd.Series]:
4143
"""Evaluate the data and return resampled results."""
44+
if const.ELECTRICITY_DELIVERED not in self.data.columns:
45+
self.data[const.ELECTRICITY_DELIVERED] = float("NaN")
4246
if const.ELECTRICITY_EXPORTED not in self.data.columns:
4347
self.data[const.ELECTRICITY_EXPORTED] = float("NaN")
4448
if const.ELECTRICITY_PRODUCED not in self.data.columns:
@@ -122,3 +126,23 @@ def evaluate(self) -> dict[str, pd.DataFrame | pd.Series]:
122126
frame[const.ELECTRICITY_CONSUMED],
123127
)
124128
return results
129+
130+
131+
def compare_results(
132+
res_1: dict[str, pd.DataFrame | pd.Series], res_2: dict[str, pd.DataFrame | pd.Series]
133+
) -> dict[str, dict[str, pd.Series | pd.DataFrame]]:
134+
"""Compare two evaluation results and return the differences."""
135+
results = {}
136+
for key in res_1.keys():
137+
if key in res_2:
138+
df_1 = res_1[key]
139+
df_2 = res_2[key]
140+
if isinstance(df_1, pd.Series) and isinstance(df_2, pd.Series):
141+
df_1 = df_1.to_frame().T
142+
df_2 = df_2.to_frame().T
143+
df_1, df_2 = typing.cast(pd.DataFrame, df_1), typing.cast(pd.DataFrame, df_2)
144+
diff = df_2 - df_1
145+
results[key] = {}
146+
results[key]["diff"] = diff.dropna(how="all", axis=1).squeeze(axis=0)
147+
results[key]["ratio_diff"] = (diff / df_1).dropna(how="all", axis=1).squeeze(axis=0)
148+
return results

0 commit comments

Comments
 (0)