domainiac 8.0.10__tar.gz → 9.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {domainiac-8.0.10 → domainiac-9.0.1}/PKG-INFO +1 -1
  2. domainiac-9.0.1/domainiac/functions/__init__.py +3 -0
  3. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/functions/temperature.py +3 -1
  4. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/functions/wind.py +3 -1
  5. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/nwp_manager.py +40 -78
  6. {domainiac-8.0.10 → domainiac-9.0.1}/pyproject.toml +1 -1
  7. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/__init__.py +0 -0
  8. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/functions/conversions.py +0 -0
  9. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/functions/interpolation.py +0 -0
  10. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/functions/solar.py +0 -0
  11. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/functions/typing.py +0 -0
  12. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/__init__.py +0 -0
  13. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/masterdata_manager.py +0 -0
  14. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/metering_manager.py +0 -0
  15. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/outage_manager.py +0 -0
  16. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/plant_manager.py +0 -0
  17. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/resource_manager.py +0 -0
  18. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/managers/unit_manager.py +0 -0
  19. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/modeling/__init__.py +0 -0
  20. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/modeling/nwp.py +0 -0
  21. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/modeling/plant.py +0 -0
  22. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/wrappers/__init__.py +0 -0
  23. {domainiac-8.0.10 → domainiac-9.0.1}/domainiac/wrappers/cache_wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: domainiac
3
- Version: 8.0.10
3
+ Version: 9.0.1
4
4
  Summary: Package for working with Energinet data, but with specialized functions used for Enigma.
5
5
  Author: Team Enigma
6
6
  Author-email: gridop-enigma@energinet.dk
@@ -0,0 +1,3 @@
1
+ from .solar import interpolate_irradiance
2
+ from .temperature import interpolate_temperature
3
+ from .wind import interpolate_wind_components
@@ -1,4 +1,6 @@
1
+ import numpy as np
1
2
  import pandas as pd
3
+ from numpy.typing import NDArray
2
4
  from scipy import interpolate
3
5
 
4
6
  from domainiac.functions import conversions
@@ -21,7 +23,7 @@ def interpolate_temperature(times: pd.Series, temperature: pd.Series) -> RealFun
21
23
 
22
24
  f = interpolate.make_interp_spline(x, y, k=1)
23
25
 
24
- def estimate(times):
26
+ def estimate(times: pd.Series) -> NDArray[np.float64]:
25
27
  x = conversions.datetime_to_float(times)
26
28
  return f(x)
27
29
 
@@ -1,4 +1,6 @@
1
+ import numpy as np
1
2
  import pandas as pd
3
+ from numpy.typing import NDArray
2
4
  from scipy import interpolate
3
5
 
4
6
  from domainiac.functions import conversions
@@ -23,7 +25,7 @@ def interpolate_wind_components(
23
25
 
24
26
  f = interpolate.make_interp_spline(x, y, k=1)
25
27
 
26
- def estimate(times):
28
+ def estimate(times: pd.Series) -> NDArray[np.float64]:
27
29
  x = conversions.datetime_to_float(times)
28
30
  return f(x)
29
31
 
@@ -3,6 +3,8 @@ import numpy as np
3
3
  import pandas as pd
4
4
  from sklearn.neighbors import KDTree
5
5
 
6
+ from domainiac import functions
7
+
6
8
  from ..modeling.nwp import Coordinate, Neighborhood, NWPParameter, NWPProvider
7
9
 
8
10
 
@@ -15,32 +17,6 @@ class NWPManager:
15
17
  - Finding closest NWP coordinates
16
18
  """
17
19
 
18
- NWP_INTERPOLATION_METHODS = {
19
- # Wind and temperature are estimates
20
- # of the instantaneous value at time t.
21
- # We use linear interpolation.
22
- NWPParameter.WIND: ("linear", "instantanous"),
23
- NWPParameter.TEMPERATURE: ("linear", "instantanous"),
24
- # solar is an estimate of the average value
25
- # in the interval [t-1, t].
26
- # We use PCHIP interpolation to ensure
27
- # a smooth shape and non-negative values
28
- # (https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PchipInterpolator.html)
29
- NWPParameter.SOLAR: ("pchip", "interval_average"),
30
- }
31
-
32
- NWP_RESOLUTIONS = {
33
- (NWPProvider.ECMWF, NWPParameter.WIND): pd.Timedelta("PT3H"),
34
- (NWPProvider.ECMWF, NWPParameter.TEMPERATURE): pd.Timedelta("PT3H"),
35
- (NWPProvider.ECMWF, NWPParameter.SOLAR): pd.Timedelta("PT1H"),
36
- (NWPProvider.DMI, NWPParameter.WIND): pd.Timedelta("PT1H"),
37
- (NWPProvider.DMI, NWPParameter.TEMPERATURE): pd.Timedelta("PT1H"),
38
- (NWPProvider.DMI, NWPParameter.SOLAR): pd.Timedelta("PT1H"),
39
- (NWPProvider.CONWX, NWPParameter.WIND): pd.Timedelta("PT1H"),
40
- (NWPProvider.CONWX, NWPParameter.TEMPERATURE): pd.Timedelta("PT1H"),
41
- (NWPProvider.CONWX, NWPParameter.SOLAR): pd.Timedelta("PT1H"),
42
- }
43
-
44
20
  def __init__(
45
21
  self,
46
22
  db: pdz.Database,
@@ -54,22 +30,6 @@ class NWPManager:
54
30
  self._nwp_coordinates_kd_tree = dict()
55
31
  self._nwp_table_name_prefix = "forecastNwp"
56
32
 
57
- @classmethod
58
- def get_nwp_resolution(
59
- cls, provider: NWPProvider, parameter: NWPParameter
60
- ) -> pd.Timedelta:
61
- """
62
- Get the resolution of the NWP parameter.
63
- """
64
- return cls.NWP_RESOLUTIONS[(provider, parameter)]
65
-
66
- @classmethod
67
- def get_nwp_interpolation_method(cls, parameter: NWPParameter) -> tuple[str, str]:
68
- """
69
- Get the interpolation method and temporal aggregation for the NWP parameter.
70
- """
71
- return cls.NWP_INTERPOLATION_METHODS[parameter]
72
-
73
33
  @staticmethod
74
34
  def calculate_wind_speed_from_vectors(u: pd.Series, v: pd.Series) -> pd.Series:
75
35
  return np.sqrt(u**2 + v**2)
@@ -167,14 +127,14 @@ class NWPManager:
167
127
  "altitude_m": coordinate.altitude,
168
128
  }
169
129
 
170
- # Pad time interval with one resolution
171
- # This is needed for the interpolation to work as we shift back in time
172
- nwp_resolution = self.get_nwp_resolution(provider, parameter)
173
-
174
- # Pad time interval to make sure interpolation is possible
130
+ # Since we need to interpolate along the time dimension, we need to pad
131
+ # the desired time interval when querying data, so we ensure we have data
132
+ # points to interpolate between. Padding with 6 hours should be enough
133
+ # for all our current NWP providers
134
+ query_margin = pd.Timedelta("P6H")
175
135
  padded_time_interval = pdz.TimeInterval(
176
- self.time_interval.left - (nwp_resolution / 2),
177
- self.time_interval.right + (nwp_resolution * 1.5),
136
+ self.time_interval.left - query_margin,
137
+ self.time_interval.right + query_margin,
178
138
  )
179
139
 
180
140
  df = self.db.query(table, padded_time_interval, filters=filters)
@@ -205,41 +165,43 @@ class NWPManager:
205
165
  """
206
166
  df = self._get_nwp_parameter(provider, parameter, coordinate)
207
167
 
208
- nwp_resolution = self.get_nwp_resolution(provider, parameter)
209
- interpolation_method, temporal_aggregation = self.get_nwp_interpolation_method(
210
- parameter
168
+ df = df.sort_values(by="time_utc")
169
+
170
+ if parameter == NWPParameter.TEMPERATURE:
171
+ column_names = "temperature_K"
172
+ interpolated_function = functions.interpolate_temperature(
173
+ times=df["time_utc"], temperature=df[column_names]
174
+ )
175
+ elif parameter == NWPParameter.SOLAR:
176
+ column_names = "global_radiation_W_m2"
177
+ interpolated_function = functions.interpolate_irradiance(
178
+ coordinate=coordinate,
179
+ times=df["time_utc"],
180
+ radiation_avg=df[column_names].iloc[1:],
181
+ )
182
+ elif parameter == NWPParameter.WIND:
183
+ column_names = ["wind_u_m_s", "wind_v_m_s"]
184
+ interpolated_function = functions.interpolate_wind_components(
185
+ times=df["time_utc"],
186
+ wind_components=df[column_names],
187
+ )
188
+
189
+ df_interpolated = self.time_interval.to_range(freq=self.resolution).to_frame(
190
+ name="time_utc"
211
191
  )
212
192
 
213
- match temporal_aggregation:
214
- # if the parameter is an interval average,
215
- # it makes best sense to interpolate from
216
- # midpoint to midpoint. Otherwise, we just
217
- # interpolate regularly.
218
- case "interval_average":
219
- shift = -1 / 2
220
- case "instantanous":
221
- shift = 0
222
-
223
- df_shifted = pdz.shift_time(df, on="time_utc", period=shift * nwp_resolution)
224
-
225
- # Interpolate
226
- df_resampled = pdz.resample(
227
- df_shifted, on="time_utc", resolution=self.resolution / 2
228
- ).interpolate(interpolation_method)
229
-
230
- # Shift back time, so that the output time series again represents
231
- # the value by the endpoint.
232
- df_re_shifted = pdz.shift_time(
233
- df_resampled, on="time_utc", period=-shift * self.resolution
193
+ df_interpolated[column_names] = interpolated_function(
194
+ df_interpolated["time_utc"]
234
195
  )
235
196
 
236
- # Make sure times is divisible by the resolution
237
- df_resolution = df_re_shifted[
238
- df_re_shifted["time_utc"].dt.round(self.resolution)
239
- == df_re_shifted["time_utc"]
197
+ # disallow extrapolation
198
+ df_interpolated = df_interpolated[
199
+ df_interpolated["time_utc"].between(
200
+ df["time_utc"].min(), df["time_utc"].max()
201
+ )
240
202
  ]
241
203
 
242
- return df_resolution
204
+ return df_interpolated
243
205
 
244
206
  def get_nwp_parameter(
245
207
  self,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "domainiac"
3
- version = "8.0.10"
3
+ version = "9.0.1"
4
4
  description = "Package for working with Energinet data, but with specialized functions used for Enigma."
5
5
  authors = ["Team Enigma <gridop-enigma@energinet.dk>"]
6
6
  packages = [