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.
- ocf_data_sampler/config/__init__.py +5 -0
- ocf_data_sampler/config/load.py +33 -0
- ocf_data_sampler/config/model.py +249 -0
- ocf_data_sampler/config/save.py +36 -0
- ocf_data_sampler/select/dropout.py +2 -2
- ocf_data_sampler/select/geospatial.py +118 -0
- ocf_data_sampler/select/location.py +62 -0
- ocf_data_sampler/select/select_spatial_slice.py +5 -14
- ocf_data_sampler/torch_datasets/pvnet_uk_regional.py +10 -5
- ocf_data_sampler-0.0.21.dist-info/METADATA +83 -0
- ocf_data_sampler-0.0.21.dist-info/RECORD +53 -0
- {ocf_data_sampler-0.0.18.dist-info → ocf_data_sampler-0.0.21.dist-info}/WHEEL +1 -1
- tests/config/test_config.py +152 -0
- tests/conftest.py +6 -1
- tests/load/test_load_gsp.py +15 -0
- tests/load/test_load_nwp.py +21 -0
- tests/load/test_load_satellite.py +17 -0
- tests/numpy_batch/test_gsp.py +23 -0
- tests/numpy_batch/test_nwp.py +54 -0
- tests/numpy_batch/test_satellite.py +42 -0
- tests/numpy_batch/test_sun_position.py +81 -0
- tests/select/test_dropout.py +75 -0
- tests/select/test_fill_time_periods.py +28 -0
- tests/select/test_find_contiguous_time_periods.py +202 -0
- tests/select/test_location.py +67 -0
- tests/select/test_select_spatial_slice.py +154 -0
- tests/select/test_select_time_slice.py +284 -0
- tests/torch_datasets/test_pvnet_uk_regional.py +72 -0
- ocf_data_sampler-0.0.18.dist-info/METADATA +0 -22
- ocf_data_sampler-0.0.18.dist-info/RECORD +0 -32
- {ocf_data_sampler-0.0.18.dist-info → ocf_data_sampler-0.0.21.dist-info}/LICENSE +0 -0
- {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
|
+
[](#contributors-)
|
|
54
|
+
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
55
|
+
[](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,,
|
|
@@ -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()
|