ocf-data-sampler 0.0.18__py3-none-any.whl → 0.0.21__py3-none-any.whl

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.

Potentially problematic release.


This version of ocf-data-sampler might be problematic. Click here for more details.

Files changed (32) hide show
  1. ocf_data_sampler/config/__init__.py +5 -0
  2. ocf_data_sampler/config/load.py +33 -0
  3. ocf_data_sampler/config/model.py +249 -0
  4. ocf_data_sampler/config/save.py +36 -0
  5. ocf_data_sampler/select/dropout.py +2 -2
  6. ocf_data_sampler/select/geospatial.py +118 -0
  7. ocf_data_sampler/select/location.py +62 -0
  8. ocf_data_sampler/select/select_spatial_slice.py +5 -14
  9. ocf_data_sampler/torch_datasets/pvnet_uk_regional.py +10 -5
  10. ocf_data_sampler-0.0.21.dist-info/METADATA +83 -0
  11. ocf_data_sampler-0.0.21.dist-info/RECORD +53 -0
  12. {ocf_data_sampler-0.0.18.dist-info → ocf_data_sampler-0.0.21.dist-info}/WHEEL +1 -1
  13. tests/config/test_config.py +152 -0
  14. tests/conftest.py +6 -1
  15. tests/load/test_load_gsp.py +15 -0
  16. tests/load/test_load_nwp.py +21 -0
  17. tests/load/test_load_satellite.py +17 -0
  18. tests/numpy_batch/test_gsp.py +23 -0
  19. tests/numpy_batch/test_nwp.py +54 -0
  20. tests/numpy_batch/test_satellite.py +42 -0
  21. tests/numpy_batch/test_sun_position.py +81 -0
  22. tests/select/test_dropout.py +75 -0
  23. tests/select/test_fill_time_periods.py +28 -0
  24. tests/select/test_find_contiguous_time_periods.py +202 -0
  25. tests/select/test_location.py +67 -0
  26. tests/select/test_select_spatial_slice.py +154 -0
  27. tests/select/test_select_time_slice.py +284 -0
  28. tests/torch_datasets/test_pvnet_uk_regional.py +72 -0
  29. ocf_data_sampler-0.0.18.dist-info/METADATA +0 -22
  30. ocf_data_sampler-0.0.18.dist-info/RECORD +0 -32
  31. {ocf_data_sampler-0.0.18.dist-info → ocf_data_sampler-0.0.21.dist-info}/LICENSE +0 -0
  32. {ocf_data_sampler-0.0.18.dist-info → ocf_data_sampler-0.0.21.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.1
2
+ Name: ocf_data_sampler
3
+ Version: 0.0.21
4
+ Summary: Sample from weather data for renewable energy prediction
5
+ Author: James Fulton, Peter Dudfield, and the Open Climate Fix team
6
+ Author-email: info@openclimatefix.org
7
+ Maintainer: Open Climate Fix Ltd
8
+ License: MIT License
9
+
10
+ Copyright (c) 2023 Open Climate Fix
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+
30
+ Project-URL: homepage, https://github.com/openclimatefix
31
+ Project-URL: repository, https://github.com/openclimatefix/ocf-data-sampler
32
+ Keywords: weather data,renewable energy prediction,sample weather data
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3.8
35
+ Classifier: Operating System :: POSIX :: Linux
36
+ Requires-Python: >=3.8
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Requires-Dist: numpy
40
+ Requires-Dist: pandas
41
+ Requires-Dist: xarray
42
+ Requires-Dist: zarr
43
+ Requires-Dist: dask
44
+ Requires-Dist: ocf-blosc2
45
+ Requires-Dist: ocf-datapipes==3.3.39
46
+ Requires-Dist: pvlib
47
+ Provides-Extra: docs
48
+ Requires-Dist: mkdocs>=1.2; extra == "docs"
49
+ Requires-Dist: mkdocs-material>=8.0; extra == "docs"
50
+
51
+ # OCF Data Sampler
52
+ <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
53
+ [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
54
+ <!-- ALL-CONTRIBUTORS-BADGE:END -->
55
+ [![ease of contribution: easy](https://img.shields.io/badge/ease%20of%20contribution:%20easy-32bd50)](https://github.com/openclimatefix/ocf-meta-repo?tab=readme-ov-file#overview-of-ocfs-nowcasting-repositories)
56
+
57
+ A repo for sampling from weather data for renewable energy prediction
58
+
59
+ ## Contributors ✨
60
+
61
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
62
+
63
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
64
+ <!-- prettier-ignore-start -->
65
+ <!-- markdownlint-disable -->
66
+ <table>
67
+ <tbody>
68
+ <tr>
69
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/dfulu"><img src="https://avatars.githubusercontent.com/u/41546094?v=4?s=100" width="100px;" alt="James Fulton"/><br /><sub><b>James Fulton</b></sub></a><br /><a href="https://github.com/openclimatefix/ocf-data-sampler/commits?author=dfulu" title="Code">💻</a></td>
70
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/AUdaltsova"><img src="https://avatars.githubusercontent.com/u/43303448?v=4?s=100" width="100px;" alt="Alexandra Udaltsova"/><br /><sub><b>Alexandra Udaltsova</b></sub></a><br /><a href="https://github.com/openclimatefix/ocf-data-sampler/commits?author=AUdaltsova" title="Code">💻</a></td>
71
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Sukh-P"><img src="https://avatars.githubusercontent.com/u/42407101?v=4?s=100" width="100px;" alt="Sukhil Patel"/><br /><sub><b>Sukhil Patel</b></sub></a><br /><a href="https://github.com/openclimatefix/ocf-data-sampler/commits?author=Sukh-P" title="Code">💻</a></td>
72
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/peterdudfield"><img src="https://avatars.githubusercontent.com/u/34686298?v=4?s=100" width="100px;" alt="Peter Dudfield"/><br /><sub><b>Peter Dudfield</b></sub></a><br /><a href="https://github.com/openclimatefix/ocf-data-sampler/commits?author=peterdudfield" title="Code">💻</a></td>
73
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/VikramsDataScience"><img src="https://avatars.githubusercontent.com/u/45002417?v=4?s=100" width="100px;" alt="Vikram Pande"/><br /><sub><b>Vikram Pande</b></sub></a><br /><a href="https://github.com/openclimatefix/ocf-data-sampler/commits?author=VikramsDataScience" title="Code">💻</a></td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+
78
+ <!-- markdownlint-restore -->
79
+ <!-- prettier-ignore-end -->
80
+
81
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
82
+
83
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -0,0 +1,53 @@
1
+ ocf_data_sampler/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ ocf_data_sampler/config/__init__.py,sha256=YXnAkgHViHB26hSsjiv32b6EbpG-A1kKTkARJf0_RkY,212
3
+ ocf_data_sampler/config/load.py,sha256=4f7vPHAIAmd-55tPxoIzn7F_TI_ue4NxkDcLPoVWl0g,943
4
+ ocf_data_sampler/config/model.py,sha256=lAfryeJnwkFOaeuC74aYju38QrCPugSt5K2XDw1WZYI,8128
5
+ ocf_data_sampler/config/save.py,sha256=wKdctbv0dxIIiQtcRHLRxpWQVhEFQ_FCWg-oNaRLIps,1093
6
+ ocf_data_sampler/data/uk_gsp_locations.csv,sha256=RSh7DRh55E3n8lVAaWXGTaXXHevZZtI58td4d4DhGos,10415772
7
+ ocf_data_sampler/load/__init__.py,sha256=MjgfxilTzyz1RYFoBEeAXmE9hyjknLvdmlHPmlAoiQY,44
8
+ ocf_data_sampler/load/gsp.py,sha256=Gcr1JVUOPKhFRDCSHtfPDjxx0BtyyEhXrZvGEKLPJ5I,759
9
+ ocf_data_sampler/load/satellite.py,sha256=3KlA1fx4SwxdzM-jC1WRaONXO0D6m0WxORnEnwUnZrA,2967
10
+ ocf_data_sampler/load/utils.py,sha256=EQGvVWlGMoSOdbDYuMfVAa0v6wmAOPmHIAemdrTB5v4,1406
11
+ ocf_data_sampler/load/nwp/__init__.py,sha256=SmcrnbygO5xtCKmGR4wtHrj-HI7nOAvnAtfuvRufBGQ,25
12
+ ocf_data_sampler/load/nwp/nwp.py,sha256=O4QnajEZem8BvBgTcYYDBhRhgqPYuJkolHmpMRmrXEA,610
13
+ ocf_data_sampler/load/nwp/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ ocf_data_sampler/load/nwp/providers/ecmwf.py,sha256=vW-p3vCyQ-CofKo555-gE7VDi5hlpjtjTLfHqWF0HEE,1175
15
+ ocf_data_sampler/load/nwp/providers/ukv.py,sha256=79Bm7q-K_GJPYMy62SUIZbRWRF4-tIaB1dYPEgLD9vo,1207
16
+ ocf_data_sampler/load/nwp/providers/utils.py,sha256=Sy2exG1wpXLLhMXYdsfR-DZMR3txG1_bBmBdchlc-yA,848
17
+ ocf_data_sampler/numpy_batch/__init__.py,sha256=mrtqwbGik5Zc9MYP5byfCTBm08wMtS2XnTsypC4fPMo,245
18
+ ocf_data_sampler/numpy_batch/gsp.py,sha256=3gwSj0k29JyA8_09zovB8f8Pr-dVhCuMSO1-k4QKAOg,668
19
+ ocf_data_sampler/numpy_batch/nwp.py,sha256=Rv0yfDj902Z2oCwdlRjOs3Kh-F5Fgxjjylh99-lQ9ws,1105
20
+ ocf_data_sampler/numpy_batch/satellite.py,sha256=e6eoNmiiHtzZbDVtBolFzDuE3qwhHN6bL9H86emAUsk,732
21
+ ocf_data_sampler/numpy_batch/sun_position.py,sha256=UW6-WtjrKdCkcguolHUDSLhYFfarknQzzjlCX8YdEOM,1700
22
+ ocf_data_sampler/select/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
23
+ ocf_data_sampler/select/dropout.py,sha256=zDpVLMjGb70RRyYKN-WI2Kp3x9SznstT4cMcZ4dsvJg,1066
24
+ ocf_data_sampler/select/fill_time_periods.py,sha256=iTtMjIPFYG5xtUYYedAFBLjTWWUa7t7WQ0-yksWf0-E,440
25
+ ocf_data_sampler/select/find_contiguous_time_periods.py,sha256=6ioB8LeFpFNBMgKDxrgG3zqzNjkBF_jlV9yye2ZYT2E,11925
26
+ ocf_data_sampler/select/geospatial.py,sha256=oHJoKEKubn3v3yKCVeuiPxuGroVA4RyrpNi6ARq5woE,3558
27
+ ocf_data_sampler/select/location.py,sha256=26Y5ZjfFngShBwXieuWSoOA-RLaRzci4TTmcDk3Wg7U,2015
28
+ ocf_data_sampler/select/select_spatial_slice.py,sha256=hWIJe4_VzuQ2iiiQh7V17AXwTILT5kIkUvzG458J_Gw,11220
29
+ ocf_data_sampler/select/select_time_slice.py,sha256=41cch1fQr59fZgv7UHsNGc3OvoynrixT3bmr3_1d7cU,6628
30
+ ocf_data_sampler/torch_datasets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
31
+ ocf_data_sampler/torch_datasets/pvnet_uk_regional.py,sha256=3b44y-cgIa0Nsa4RqFCKUyhs8cIXc04_j0JhMQZFIn8,19127
32
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ tests/conftest.py,sha256=O77dmow8mGpGPbZ6Pz7ma7cLaiV1k8mxW1eYg37Avrw,5585
34
+ tests/config/test_config.py,sha256=G_PD_pXib0zdRBPUIn0jjwJ9VyoKaO_TanLN1Mh5Ca4,5055
35
+ tests/load/test_load_gsp.py,sha256=aT_nqaSXmUTcdHzuTT7AmXJr3R31k4OEN-Fv3eLxlQE,424
36
+ tests/load/test_load_nwp.py,sha256=3qyyDkB1q9t3tyAwogfotNrxqUOpXXimco1CImoEWGg,753
37
+ tests/load/test_load_satellite.py,sha256=STX5AqqmOAgUgE9R1xyq_sM3P1b8NKdGjO-hDhayfxM,524
38
+ tests/numpy_batch/test_gsp.py,sha256=uAnDmJKMp8BVm8olTN8d499VGd4dKSZbZbhZq9LSV4M,574
39
+ tests/numpy_batch/test_nwp.py,sha256=2dCmsLztzqPCpUoUv-d3F8sXWzYaHk_9PxqJz99SuzI,1477
40
+ tests/numpy_batch/test_satellite.py,sha256=Ysf0RUAgtxebaJ8F7oiR9ujucZ4ZkvBbGrCSOSPsgHE,1193
41
+ tests/numpy_batch/test_sun_position.py,sha256=oH6aSjsKhHPJ-Hy-G5Z_BMcQclvBuDOEVmPq9c-TVyc,2492
42
+ tests/select/test_dropout.py,sha256=kiycl7RxAQYMCZJlokmx6Da5h_oBpSs8Is8pmSW4gOU,2413
43
+ tests/select/test_fill_time_periods.py,sha256=o59f2YRe5b0vJrG3B0aYZkYeHnpNk4s6EJxdXZluNQg,907
44
+ tests/select/test_find_contiguous_time_periods.py,sha256=G6tJRJd0DMfH9EdfzlKWsmfTbtMwOf3w-2filjJzuIQ,5998
45
+ tests/select/test_location.py,sha256=_WZk2FPYeJ-nIfCJS6Sp_yaVEEo7m31DmMFoZzgyCts,2712
46
+ tests/select/test_select_spatial_slice.py,sha256=m_R0wS5ApoBfE9h28FgdwYqT3gFxle6d3CBtmoqdPmo,5134
47
+ tests/select/test_select_time_slice.py,sha256=rH4h90HdQCoWE7vV7ivMEKhiCStQDEcMBCPamiDuO0k,10147
48
+ tests/torch_datasets/test_pvnet_uk_regional.py,sha256=R_b8wbAYHqW5KQDmLLNIN8Y8uz5vslG2LPYvHHHSvvg,2615
49
+ ocf_data_sampler-0.0.21.dist-info/LICENSE,sha256=F-Q3UFCR-BECSocV55BFDpn4YKxve9PKrm-lTt6o_Tg,1073
50
+ ocf_data_sampler-0.0.21.dist-info/METADATA,sha256=_8fQPhtqm7tXX2_JBrkbDlUKnGY4QhG5q2uy89ag5cs,5167
51
+ ocf_data_sampler-0.0.21.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
52
+ ocf_data_sampler-0.0.21.dist-info/top_level.txt,sha256=KaQn5qzkJGJP6hKWqsVAc9t0cMLjVvSTk8-kTrW79SA,23
53
+ ocf_data_sampler-0.0.21.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.0.0)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,152 @@
1
+ import tempfile
2
+
3
+ import pytest
4
+ from pydantic import ValidationError
5
+
6
+ from ocf_data_sampler.config import (
7
+ load_yaml_configuration,
8
+ Configuration,
9
+ save_yaml_configuration
10
+ )
11
+
12
+
13
+ def test_default():
14
+ """Test default pydantic class"""
15
+
16
+ _ = Configuration()
17
+
18
+
19
+ def test_yaml_load_test_config(test_config_filename):
20
+ """
21
+ Test that yaml loading works for 'test_config.yaml'
22
+ and fails for an empty .yaml file
23
+ """
24
+
25
+ # check we get an error if loading a file with no config
26
+ with tempfile.NamedTemporaryFile(suffix=".yaml") as fp:
27
+ filename = fp.name
28
+
29
+ # check that temp file can't be loaded
30
+ with pytest.raises(TypeError):
31
+ _ = load_yaml_configuration(filename)
32
+
33
+ # test can load test_config.yaml
34
+ config = load_yaml_configuration(test_config_filename)
35
+
36
+ assert isinstance(config, Configuration)
37
+
38
+
39
+ def test_yaml_save(test_config_filename):
40
+ """
41
+ Check configuration can be saved to a .yaml file
42
+ """
43
+
44
+ test_config = load_yaml_configuration(test_config_filename)
45
+
46
+ with tempfile.NamedTemporaryFile(suffix=".yaml") as fp:
47
+ filename = fp.name
48
+
49
+ # save default config to file
50
+ save_yaml_configuration(test_config, filename)
51
+
52
+ # check the file can be loaded back
53
+ tmp_config = load_yaml_configuration(filename)
54
+
55
+ # check loaded configuration is the same as the one passed to save
56
+ assert test_config == tmp_config
57
+
58
+
59
+ def test_extra_field():
60
+ """
61
+ Check an extra parameters in config causes error
62
+ """
63
+
64
+ configuration = Configuration()
65
+ configuration_dict = configuration.model_dump()
66
+ configuration_dict["extra_field"] = "extra_value"
67
+ with pytest.raises(ValidationError, match="Extra inputs are not permitted"):
68
+ _ = Configuration(**configuration_dict)
69
+
70
+
71
+ def test_incorrect_forecast_minutes(test_config_filename):
72
+ """
73
+ Check a forecast length not divisible by time resolution causes error
74
+ """
75
+
76
+ configuration = load_yaml_configuration(test_config_filename)
77
+
78
+ configuration.input_data.nwp['ukv'].forecast_minutes = 1111
79
+ with pytest.raises(Exception, match="duration must be divisible by time resolution"):
80
+ _ = Configuration(**configuration.model_dump())
81
+
82
+
83
+ def test_incorrect_history_minutes(test_config_filename):
84
+ """
85
+ Check a history length not divisible by time resolution causes error
86
+ """
87
+
88
+ configuration = load_yaml_configuration(test_config_filename)
89
+
90
+ configuration.input_data.nwp['ukv'].history_minutes = 1111
91
+ with pytest.raises(Exception, match="duration must be divisible by time resolution"):
92
+ _ = Configuration(**configuration.model_dump())
93
+
94
+
95
+ def test_incorrect_nwp_provider(test_config_filename):
96
+ """
97
+ Check an unexpected nwp provider causes error
98
+ """
99
+
100
+ configuration = load_yaml_configuration(test_config_filename)
101
+
102
+ configuration.input_data.nwp['ukv'].nwp_provider = "unexpected_provider"
103
+ with pytest.raises(Exception, match="NWP provider"):
104
+ _ = Configuration(**configuration.model_dump())
105
+
106
+ def test_incorrect_dropout(test_config_filename):
107
+ """
108
+ Check a dropout timedelta over 0 causes error and 0 doesn't
109
+ """
110
+
111
+ configuration = load_yaml_configuration(test_config_filename)
112
+
113
+ # check a positive number is not allowed
114
+ configuration.input_data.nwp['ukv'].dropout_timedeltas_minutes = [120]
115
+ with pytest.raises(Exception, match="Dropout timedeltas must be negative"):
116
+ _ = Configuration(**configuration.model_dump())
117
+
118
+ # check 0 is allowed
119
+ configuration.input_data.nwp['ukv'].dropout_timedeltas_minutes = [0]
120
+ _ = Configuration(**configuration.model_dump())
121
+
122
+ def test_incorrect_dropout_fraction(test_config_filename):
123
+ """
124
+ Check dropout fraction outside of range causes error
125
+ """
126
+
127
+ configuration = load_yaml_configuration(test_config_filename)
128
+
129
+ configuration.input_data.nwp['ukv'].dropout_fraction= 1.1
130
+ with pytest.raises(Exception, match="Dropout fraction must be between 0 and 1"):
131
+ _ = Configuration(**configuration.model_dump())
132
+
133
+ configuration.input_data.nwp['ukv'].dropout_fraction= -0.1
134
+ with pytest.raises(Exception, match="Dropout fraction must be between 0 and 1"):
135
+ _ = Configuration(**configuration.model_dump())
136
+
137
+
138
+ def test_inconsistent_dropout_use(test_config_filename):
139
+ """
140
+ Check dropout fraction outside of range causes error
141
+ """
142
+
143
+ configuration = load_yaml_configuration(test_config_filename)
144
+ configuration.input_data.satellite.dropout_fraction= 1.0
145
+ configuration.input_data.satellite.dropout_timedeltas_minutes = None
146
+
147
+ with pytest.raises(ValueError, match="To dropout fraction > 0 requires a list of dropout timedeltas"):
148
+ _ = Configuration(**configuration.model_dump())
149
+ configuration.input_data.satellite.dropout_fraction= 0.0
150
+ configuration.input_data.satellite.dropout_timedeltas_minutes = [-120, -60]
151
+ with pytest.raises(ValueError, match="To use dropout timedeltas dropout fraction should be > 0"):
152
+ _ = Configuration(**configuration.model_dump())
tests/conftest.py CHANGED
@@ -6,11 +6,16 @@ import pytest
6
6
  import xarray as xr
7
7
  import tempfile
8
8
 
9
+ _top_test_directory = os.path.dirname(os.path.realpath(__file__))
10
+
11
+ @pytest.fixture()
12
+ def test_config_filename():
13
+ return f"{_top_test_directory}/test_data/configs/test_config.yaml"
9
14
 
10
15
 
11
16
  @pytest.fixture(scope="session")
12
17
  def config_filename():
13
- return f"{os.path.dirname(os.path.abspath(__file__))}/test_data/pvnet_test_config.yaml"
18
+ return f"{os.path.dirname(os.path.abspath(__file__))}/test_data/configs/pvnet_test_config.yaml"
14
19
 
15
20
 
16
21
  @pytest.fixture(scope="session")
@@ -0,0 +1,15 @@
1
+ from ocf_data_sampler.load.gsp import open_gsp
2
+ import xarray as xr
3
+
4
+
5
+ def test_open_gsp(uk_gsp_zarr_path):
6
+ da = open_gsp(uk_gsp_zarr_path)
7
+
8
+ assert isinstance(da, xr.DataArray)
9
+ assert da.dims == ("time_utc", "gsp_id")
10
+
11
+ assert "nominal_capacity_mwp" in da.coords
12
+ assert "effective_capacity_mwp" in da.coords
13
+ assert "x_osgb" in da.coords
14
+ assert "y_osgb" in da.coords
15
+ assert da.shape == (49, 318)
@@ -0,0 +1,21 @@
1
+ import pandas as pd
2
+ from xarray import DataArray
3
+ import numpy as np
4
+
5
+ from ocf_data_sampler.load.nwp import open_nwp
6
+
7
+
8
+ def test_load_ukv(nwp_ukv_zarr_path):
9
+ da = open_nwp(zarr_path=nwp_ukv_zarr_path, provider="ukv")
10
+ assert isinstance(da, DataArray)
11
+ assert da.dims == ("init_time_utc", "step", "channel", "x_osgb", "y_osgb")
12
+ assert da.shape == (24 * 7, 11, 4, 50, 100)
13
+ assert np.issubdtype(da.dtype, np.number)
14
+
15
+
16
+ def test_load_ecmwf(nwp_ecmwf_zarr_path):
17
+ da = open_nwp(zarr_path=nwp_ecmwf_zarr_path, provider="ecmwf")
18
+ assert isinstance(da, DataArray)
19
+ assert da.dims == ("init_time_utc", "step", "channel", "longitude", "latitude")
20
+ assert da.shape == (24 * 7, 15, 3, 15, 12)
21
+ assert np.issubdtype(da.dtype, np.number)
@@ -0,0 +1,17 @@
1
+ from ocf_data_sampler.load.satellite import open_sat_data
2
+ import xarray as xr
3
+ import numpy as np
4
+
5
+
6
+ def test_open_satellite(sat_zarr_path):
7
+ da = open_sat_data(zarr_path=sat_zarr_path)
8
+
9
+ assert isinstance(da, xr.DataArray)
10
+ assert da.dims == ("time_utc", "channel", "x_geostationary", "y_geostationary")
11
+ # 576 is 2 days of data at 5 minutes intervals, 12 * 24 * 2
12
+ # There are 11 channels
13
+ # There are 49 x 20 pixels
14
+ assert da.shape == (576, 11, 49, 20)
15
+ assert np.issubdtype(da.dtype, np.number)
16
+
17
+
@@ -0,0 +1,23 @@
1
+ from ocf_datapipes.batch import BatchKey
2
+ from ocf_data_sampler.load.gsp import open_gsp
3
+
4
+ from ocf_data_sampler.numpy_batch import convert_gsp_to_numpy_batch
5
+
6
+
7
+ def test_convert_gsp_to_numpy_batch(uk_gsp_zarr_path):
8
+
9
+ da = (
10
+ open_gsp(uk_gsp_zarr_path)
11
+ .isel(time_utc=slice(0, 10))
12
+ .sel(gsp_id=1)
13
+ )
14
+
15
+ # Call the function
16
+ numpy_batch = convert_gsp_to_numpy_batch(da)
17
+
18
+ # Assert the output type
19
+ assert isinstance(numpy_batch, dict)
20
+
21
+ # Assert the shape of the numpy batch
22
+ assert (numpy_batch[BatchKey.gsp] == da.values).all()
23
+
@@ -0,0 +1,54 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import xarray as xr
4
+
5
+ import pytest
6
+
7
+ from ocf_data_sampler.numpy_batch import convert_nwp_to_numpy_batch
8
+
9
+ from ocf_datapipes.batch import NWPBatchKey
10
+
11
+ @pytest.fixture(scope="module")
12
+ def da_nwp_like():
13
+ """Create dummy data which looks like time-sliced NWP data"""
14
+
15
+ t0 = pd.to_datetime("2024-01-02 00:00")
16
+
17
+ x = np.arange(-100, 100, 10)
18
+ y = np.arange(-100, 100, 10)
19
+ steps = pd.timedelta_range("0H", "8H", freq="1H")
20
+ target_times = t0 + steps
21
+
22
+ channels = ["t", "dswrf"]
23
+ init_times = pd.to_datetime([t0]*len(steps))
24
+
25
+ # Create dummy time-sliced NWP data
26
+ da_nwp = xr.DataArray(
27
+ np.random.normal(size=(len(target_times), len(channels), len(x), len(y))),
28
+ coords=dict(
29
+ target_times_utc=(["target_times_utc"], target_times),
30
+ channel=(["channel"], channels),
31
+ x_osgb=(["x_osgb"], x),
32
+ y_osgb=(["y_osgb"], y),
33
+ )
34
+ )
35
+
36
+ # Add extra non-coordinate dimensions
37
+ da_nwp = da_nwp.assign_coords(
38
+ init_time_utc=("target_times_utc", init_times),
39
+ step=("target_times_utc", steps),
40
+ )
41
+
42
+ return da_nwp
43
+
44
+
45
+ def test_convert_nwp_to_numpy_batch(da_nwp_like):
46
+
47
+ # Call the function
48
+ numpy_batch = convert_nwp_to_numpy_batch(da_nwp_like)
49
+
50
+ # Assert the output type
51
+ assert isinstance(numpy_batch, dict)
52
+
53
+ # Assert the shape of the numpy batch
54
+ assert (numpy_batch[NWPBatchKey.nwp] == da_nwp_like.values).all()
@@ -0,0 +1,42 @@
1
+
2
+ import numpy as np
3
+ import pandas as pd
4
+ import xarray as xr
5
+
6
+ import pytest
7
+
8
+ from ocf_data_sampler.numpy_batch import convert_satellite_to_numpy_batch
9
+
10
+ from ocf_datapipes.batch import BatchKey
11
+
12
+
13
+ @pytest.fixture(scope="module")
14
+ def da_sat_like():
15
+ """Create dummy data which looks like satellite data"""
16
+ x = np.arange(-100, 100, 10)
17
+ y = np.arange(-100, 100, 10)
18
+ datetimes = pd.date_range("2024-01-01 12:00", "2024-01-01 12:30", freq="5min")
19
+ channels = ["VIS008", "IR016"]
20
+
21
+ da_sat = xr.DataArray(
22
+ np.random.normal(size=(len(datetimes), len(channels), len(x), len(y))),
23
+ coords=dict(
24
+ time_utc=(["time_utc"], datetimes),
25
+ channel=(["channel"], channels),
26
+ x_geostationary=(["x_geostationary"], x),
27
+ y_geostationary=(["y_geostationary"], y),
28
+ )
29
+ )
30
+ return da_sat
31
+
32
+
33
+ def test_convert_satellite_to_numpy_batch(da_sat_like):
34
+
35
+ # Call the function
36
+ numpy_batch = convert_satellite_to_numpy_batch(da_sat_like)
37
+
38
+ # Assert the output type
39
+ assert isinstance(numpy_batch, dict)
40
+
41
+ # Assert the shape of the numpy batch
42
+ assert (numpy_batch[BatchKey.satellite_actual] == da_sat_like.values).all()
@@ -0,0 +1,81 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import pytest
4
+
5
+ from ocf_data_sampler.numpy_batch.sun_position import (
6
+ calculate_azimuth_and_elevation, make_sun_position_numpy_batch
7
+ )
8
+
9
+ from ocf_datapipes.batch import NumpyBatch, BatchKey
10
+
11
+
12
+ @pytest.mark.parametrize("lat", [0, 5, 10, 23.5])
13
+ def test_calculate_azimuth_and_elevation(lat):
14
+
15
+ # Pick the day of the summer solstice
16
+ datetimes = pd.to_datetime(["2024-06-20 12:00"])
17
+
18
+ # Calculate sun angles
19
+ azimuth, elevation = calculate_azimuth_and_elevation(datetimes, lon=0, lat=lat)
20
+
21
+ assert len(azimuth)==len(datetimes)
22
+ assert len(elevation)==len(datetimes)
23
+
24
+ # elevation should be close to (90 - (23.5-lat) degrees
25
+ assert np.abs(elevation - (90-23.5+lat)) < 1
26
+
27
+
28
+ def test_calculate_azimuth_and_elevation_random():
29
+ """Test that the function produces the expected range of azimuths and elevations"""
30
+
31
+ # Set seed so we know the test should pass
32
+ np.random.seed(0)
33
+
34
+ # Pick the day of the summer solstice
35
+ datetimes = pd.to_datetime(["2024-06-20 12:00"])
36
+
37
+ # Pick 100 random locations and measure their azimuth and elevations
38
+ azimuths = []
39
+ elevations = []
40
+
41
+ for _ in range(100):
42
+
43
+ lon = np.random.uniform(low=0, high=360)
44
+ lat = np.random.uniform(low=-90, high=90)
45
+
46
+ # Calculate sun angles
47
+ azimuth, elevation = calculate_azimuth_and_elevation(datetimes, lon=lon, lat=lat)
48
+
49
+ azimuths.append(azimuth.item())
50
+ elevations.append(elevation.item())
51
+
52
+ azimuths = np.array(azimuths)
53
+ elevations = np.array(elevations)
54
+
55
+ assert (0<=azimuths).all() and (azimuths<=360).all()
56
+ assert (-90<=elevations).all() and (elevations<=90).all()
57
+
58
+ # Azimuth range is [0, 360]
59
+ assert azimuths.min() < 30
60
+ assert azimuths.max() > 330
61
+
62
+ # Elevation range is [-90, 90]
63
+ assert elevations.min() < -70
64
+ assert elevations.max() > 70
65
+
66
+
67
+ def test_make_sun_position_numpy_batch():
68
+
69
+ datetimes = pd.date_range("2024-06-20 12:00", "2024-06-20 16:00", freq="30min")
70
+ lon, lat = 0, 51.5
71
+
72
+ batch = make_sun_position_numpy_batch(datetimes, lon, lat, key_preffix="gsp")
73
+
74
+ assert BatchKey.gsp_solar_elevation in batch
75
+ assert BatchKey.gsp_solar_azimuth in batch
76
+
77
+ # The solar coords are normalised in the function
78
+ assert (batch[BatchKey.gsp_solar_elevation]>=0).all()
79
+ assert (batch[BatchKey.gsp_solar_elevation]<=1).all()
80
+ assert (batch[BatchKey.gsp_solar_azimuth]>=0).all()
81
+ assert (batch[BatchKey.gsp_solar_azimuth]<=1).all()
@@ -0,0 +1,75 @@
1
+ from ocf_data_sampler.select.dropout import draw_dropout_time, apply_dropout_time
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import xarray as xr
6
+
7
+ import pytest
8
+
9
+
10
+ @pytest.fixture(scope="module")
11
+ def da_sample():
12
+ """Create dummy data which looks like satellite data"""
13
+
14
+ datetimes = pd.date_range("2024-01-01 12:00", "2024-01-01 13:00", freq="5min")
15
+
16
+ da_sat = xr.DataArray(
17
+ np.random.normal(size=(len(datetimes),)),
18
+ coords=dict(
19
+ time_utc=(["time_utc"], datetimes),
20
+ )
21
+ )
22
+ return da_sat
23
+
24
+
25
+ def test_draw_dropout_time():
26
+ t0 = pd.Timestamp("2021-01-01 04:00:00")
27
+
28
+ dropout_timedeltas = pd.to_timedelta([-30, -60], unit="min")
29
+ dropout_time = draw_dropout_time(t0, dropout_timedeltas, dropout_frac=1)
30
+
31
+ assert isinstance(dropout_time, pd.Timestamp)
32
+ assert dropout_time-t0 in dropout_timedeltas
33
+
34
+
35
+ def test_draw_dropout_time_partial():
36
+ t0 = pd.Timestamp("2021-01-01 04:00:00")
37
+
38
+ dropout_timedeltas = pd.to_timedelta([-30, -60], unit="min")
39
+
40
+ dropouts = set()
41
+
42
+ # Loop over 1000 to have very high probability of seeing all dropouts
43
+ # The chances of this failing by chance are approx ((2/3)^100)*3 = 7e-18
44
+ for _ in range(100):
45
+ dropouts.add(draw_dropout_time(t0, dropout_timedeltas, dropout_frac=2/3))
46
+
47
+ # Check all expected dropouts are present
48
+ dropouts == {None} | set(t0 + dt for dt in dropout_timedeltas)
49
+
50
+
51
+ def test_draw_dropout_time_none():
52
+ t0 = pd.Timestamp("2021-01-01 04:00:00")
53
+
54
+ # No dropout timedeltas
55
+ dropout_time = draw_dropout_time(t0, dropout_timedeltas=None, dropout_frac=1)
56
+ assert dropout_time is None
57
+
58
+ # Dropout fraction is 0
59
+ dropout_timedeltas = [pd.Timedelta(-30, "min")]
60
+ dropout_time = draw_dropout_time(t0, dropout_timedeltas=dropout_timedeltas, dropout_frac=0)
61
+ assert dropout_time is None
62
+
63
+ # No dropout timedeltas and dropout fraction is 0
64
+ dropout_time = draw_dropout_time(t0, dropout_timedeltas=None, dropout_frac=0)
65
+ assert dropout_time is None
66
+
67
+
68
+ @pytest.mark.parametrize("t0_str", ["12:00", "12:30", "13:00"])
69
+ def test_apply_dropout_time(da_sample, t0_str):
70
+ dropout_time = pd.Timestamp(f"2024-01-01 {t0_str}")
71
+
72
+ da_dropout = apply_dropout_time(da_sample, dropout_time)
73
+
74
+ assert da_dropout.sel(time_utc=slice(None, dropout_time)).notnull().all()
75
+ assert da_dropout.sel(time_utc=slice(dropout_time+pd.Timedelta(5, "min"), None)).isnull().all()