rencal 0.1.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.
- rencal-0.1.1/LICENSE +21 -0
- rencal-0.1.1/PKG-INFO +193 -0
- rencal-0.1.1/README.md +149 -0
- rencal-0.1.1/pyproject.toml +190 -0
- rencal-0.1.1/rencal/__init__.py +13 -0
- rencal-0.1.1/rencal/calibration/__init__.py +0 -0
- rencal-0.1.1/rencal/calibration/calibrator.py +99 -0
- rencal-0.1.1/rencal/calibration/wind/__init__.py +0 -0
- rencal-0.1.1/rencal/calibration/wind/wind_calibrator.py +605 -0
- rencal-0.1.1/rencal/core/__init__.py +0 -0
- rencal-0.1.1/rencal/core/bucketed_data.py +658 -0
- rencal-0.1.1/rencal/core/bucketer.py +241 -0
- rencal-0.1.1/rencal/core/data_downloader.py +820 -0
- rencal-0.1.1/rencal/core/data_loader.py +337 -0
- rencal-0.1.1/rencal/core/short_date.py +277 -0
- rencal-0.1.1/rencal/main.py +67 -0
- rencal-0.1.1/rencal/models/__init__.py +11 -0
- rencal-0.1.1/rencal/models/base.py +188 -0
- rencal-0.1.1/rencal/models/era5_model.py +147 -0
- rencal-0.1.1/rencal/models/generation_model.py +115 -0
- rencal-0.1.1/rencal/models/plant_model.py +106 -0
- rencal-0.1.1/rencal/simulation/__init__.py +0 -0
- rencal-0.1.1/rencal/simulation/intermittent_bucketer.py +28 -0
- rencal-0.1.1/rencal/simulation/weather_data.py +317 -0
- rencal-0.1.1/rencal/utils/__init__.py +28 -0
- rencal-0.1.1/rencal/utils/config.yml +10 -0
- rencal-0.1.1/rencal/utils/constants.py +128 -0
- rencal-0.1.1/rencal/utils/logger.py +72 -0
- rencal-0.1.1/rencal/utils/types.py +18 -0
rencal-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Low Carbon Contracts Company Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rencal-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rencal
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Weather-to-power calibration and Monte Carlo load factor simulation for renewable energy applications
|
|
5
|
+
Keywords: weather,forecasting,renewable energy,wind power,solar power,era5,monte carlo,calibration,climate
|
|
6
|
+
Author: Low Carbon Contracts Company Ltd
|
|
7
|
+
Author-email: Low Carbon Contracts Company Ltd <analytics@lowcarboncontracts.uk>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Dist: numpy>=1.20.0
|
|
18
|
+
Requires-Dist: pandas>=1.3.0
|
|
19
|
+
Requires-Dist: scipy>=1.7.0
|
|
20
|
+
Requires-Dist: matplotlib>=3.4.0
|
|
21
|
+
Requires-Dist: seaborn>=0.12.0
|
|
22
|
+
Requires-Dist: scikit-learn>=1.0.0
|
|
23
|
+
Requires-Dist: netcdf4>=1.5.0
|
|
24
|
+
Requires-Dist: xarray>=0.19.0
|
|
25
|
+
Requires-Dist: pytz>=2021.1
|
|
26
|
+
Requires-Dist: requests>=2.25.0
|
|
27
|
+
Requires-Dist: polars>=1.8.2
|
|
28
|
+
Requires-Dist: pydantic>=2.10.6
|
|
29
|
+
Requires-Dist: pyodbc>=4.0.0
|
|
30
|
+
Requires-Dist: cdsapi>=0.6.0
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
32
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
33
|
+
Requires-Dist: zarr>=2.16.1
|
|
34
|
+
Requires-Dist: tqdm>=4.62.0
|
|
35
|
+
Requires-Dist: pyarrow>=22.0.0
|
|
36
|
+
Maintainer: Low Carbon Contracts Company Ltd
|
|
37
|
+
Maintainer-email: Low Carbon Contracts Company Ltd <analytics@lowcarboncontracts.uk>
|
|
38
|
+
Requires-Python: >=3.12, <3.13
|
|
39
|
+
Project-URL: Homepage, https://github.com/LCCC-Tech/rencal
|
|
40
|
+
Project-URL: Repository, https://github.com/LCCC-Tech/rencal.git
|
|
41
|
+
Project-URL: Bug Tracker, https://github.com/LCCC-Tech/rencal/issues
|
|
42
|
+
Project-URL: Changelog, https://github.com/LCCC-Tech/rencal/blob/main/CHANGELOG.md
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# RenCal
|
|
46
|
+
|
|
47
|
+
RenCal (Renewable Calibration) is a Python library for calibrating renewable
|
|
48
|
+
energy power curves and generating probabilistic load-factor time series for
|
|
49
|
+
wind and solar plants.
|
|
50
|
+
|
|
51
|
+
It supports Monte Carlo-based forecasting using weather, generation, and plant
|
|
52
|
+
characteristic data. RenCal is intended for energy analysts, researchers, and
|
|
53
|
+
developers working on renewable-energy modelling.
|
|
54
|
+
|
|
55
|
+
## Status
|
|
56
|
+
|
|
57
|
+
RenCal is an experimental pre-1.0 public package. The API and modelling
|
|
58
|
+
approach may change as the project develops. It is not currently a guarantee of
|
|
59
|
+
production suitability or a substitute for independent validation.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
The first public release will be installed from PyPI with:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -m pip install rencal
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Until that release is published, clone the repository and follow the
|
|
70
|
+
[development setup](CONTRIBUTING.md#development-setup) instead.
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
The primary workflow uses data supplied by the user. RenCal does not distribute
|
|
75
|
+
operational ERA5, generation, or plant datasets.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
|
|
80
|
+
from rencal.core.data_loader import LocalDataLoader
|
|
81
|
+
|
|
82
|
+
loader = LocalDataLoader(data_path=Path("data"))
|
|
83
|
+
plants = loader.load_plant_data()
|
|
84
|
+
generation = loader.load_generation_data()
|
|
85
|
+
|
|
86
|
+
print(f"Loaded {len(plants.data)} plants")
|
|
87
|
+
print(f"Loaded {len(generation.data)} generation records")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For the full wind-calibration workflow, provide the expected input structure:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
data/
|
|
94
|
+
├── plant/plant_data.csv
|
|
95
|
+
├── generation/generation_data.parquet
|
|
96
|
+
└── era5/*.nc
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The ERA5 loader expects suitable NetCDF weather data. Users are responsible for
|
|
100
|
+
obtaining data, checking its provenance and licence, and preparing it for the
|
|
101
|
+
documented schema.
|
|
102
|
+
|
|
103
|
+
### Calibrate wind power curves
|
|
104
|
+
|
|
105
|
+
With the plant, generation, and ERA5 inputs in place, run the wind calibration
|
|
106
|
+
workflow and write its outputs to a separate directory:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
|
|
111
|
+
from rencal.calibration.wind.wind_calibrator import WindCalibrator
|
|
112
|
+
|
|
113
|
+
calibrator = WindCalibrator(
|
|
114
|
+
data_path="data",
|
|
115
|
+
output_path=Path("outputs/wind-calibration"),
|
|
116
|
+
visual_output=True,
|
|
117
|
+
stream_npy_output=True,
|
|
118
|
+
)
|
|
119
|
+
calibrator.calibrate()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The workflow writes the calibration summary, Weibull parameters, extracted wind
|
|
123
|
+
speeds, calibrated wind streams, and optional power-curve plots to the output
|
|
124
|
+
directory. With `stream_npy_output=True`, the generated `Wind Streams.npy` can
|
|
125
|
+
also be used by the weather sampler after its manifest and optional histogram
|
|
126
|
+
artefacts have been prepared.
|
|
127
|
+
|
|
128
|
+
### Sample calibrated wind streams
|
|
129
|
+
|
|
130
|
+
`WeatherData` samples future hourly paths from calibrated historical streams
|
|
131
|
+
while preserving the configured time-bucket structure. The local loader expects
|
|
132
|
+
the calibrated NPY file and its manifest under `data/calibrated/`.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import datetime
|
|
136
|
+
import random
|
|
137
|
+
|
|
138
|
+
import numpy as np
|
|
139
|
+
|
|
140
|
+
from rencal.core.data_loader import LocalDataLoader
|
|
141
|
+
from rencal.simulation.weather_data import HistoricalMetadata, WeatherData
|
|
142
|
+
|
|
143
|
+
loader = LocalDataLoader(data_path="data")
|
|
144
|
+
manifest = loader.check_historical_weather()
|
|
145
|
+
metadata = HistoricalMetadata.from_manifest(
|
|
146
|
+
manifest,
|
|
147
|
+
loader.path_resolver_weather_data,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
wind_sampler = WeatherData(
|
|
151
|
+
metadata=metadata,
|
|
152
|
+
prefix_histograms=loader.get_prefix_histograms(),
|
|
153
|
+
historical_data=loader.get_historical_weather(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
sample = wind_sampler.random_sample(
|
|
157
|
+
datetime.datetime(2027, 1, 1),
|
|
158
|
+
datetime.datetime(2027, 1, 7),
|
|
159
|
+
python_rng=random.Random(4),
|
|
160
|
+
numpy_rng=np.random.default_rng(32),
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Pass `desired_averages` to `WeatherData` when inverse-distribution resampling is
|
|
165
|
+
required; this also requires historical data or precomputed prefix histograms.
|
|
166
|
+
|
|
167
|
+
## Main capabilities
|
|
168
|
+
|
|
169
|
+
- Wind and solar power-curve calibration foundations
|
|
170
|
+
- Probabilistic load-factor forecasting
|
|
171
|
+
- Weather and generation data loading and validation
|
|
172
|
+
- Time-bucketed sampling with geographical correlation support
|
|
173
|
+
- Extensible interfaces for local and external data sources
|
|
174
|
+
|
|
175
|
+
## Documentation
|
|
176
|
+
|
|
177
|
+
- [Wind calibration tutorial](docs/tutorials/wind_calibration_tutorial.ipynb)
|
|
178
|
+
- [Contributing](CONTRIBUTING.md)
|
|
179
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
180
|
+
- [Issue tracker](https://github.com/LCCC-Tech/rencal/issues)
|
|
181
|
+
|
|
182
|
+
Hosted documentation and versioned examples will be linked here once the public
|
|
183
|
+
documentation site is verified.
|
|
184
|
+
|
|
185
|
+
## Support
|
|
186
|
+
|
|
187
|
+
Use [GitHub Issues](https://github.com/LCCC-Tech/rencal/issues) for public,
|
|
188
|
+
reproducible bugs and feature requests. Please do not include credentials,
|
|
189
|
+
internal data, or confidential information in issues.
|
|
190
|
+
|
|
191
|
+
## Licence
|
|
192
|
+
|
|
193
|
+
RenCal is released under the [MIT Licence](LICENSE).
|
rencal-0.1.1/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# RenCal
|
|
2
|
+
|
|
3
|
+
RenCal (Renewable Calibration) is a Python library for calibrating renewable
|
|
4
|
+
energy power curves and generating probabilistic load-factor time series for
|
|
5
|
+
wind and solar plants.
|
|
6
|
+
|
|
7
|
+
It supports Monte Carlo-based forecasting using weather, generation, and plant
|
|
8
|
+
characteristic data. RenCal is intended for energy analysts, researchers, and
|
|
9
|
+
developers working on renewable-energy modelling.
|
|
10
|
+
|
|
11
|
+
## Status
|
|
12
|
+
|
|
13
|
+
RenCal is an experimental pre-1.0 public package. The API and modelling
|
|
14
|
+
approach may change as the project develops. It is not currently a guarantee of
|
|
15
|
+
production suitability or a substitute for independent validation.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
The first public release will be installed from PyPI with:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m pip install rencal
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Until that release is published, clone the repository and follow the
|
|
26
|
+
[development setup](CONTRIBUTING.md#development-setup) instead.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
The primary workflow uses data supplied by the user. RenCal does not distribute
|
|
31
|
+
operational ERA5, generation, or plant datasets.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
from rencal.core.data_loader import LocalDataLoader
|
|
37
|
+
|
|
38
|
+
loader = LocalDataLoader(data_path=Path("data"))
|
|
39
|
+
plants = loader.load_plant_data()
|
|
40
|
+
generation = loader.load_generation_data()
|
|
41
|
+
|
|
42
|
+
print(f"Loaded {len(plants.data)} plants")
|
|
43
|
+
print(f"Loaded {len(generation.data)} generation records")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For the full wind-calibration workflow, provide the expected input structure:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
data/
|
|
50
|
+
├── plant/plant_data.csv
|
|
51
|
+
├── generation/generation_data.parquet
|
|
52
|
+
└── era5/*.nc
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The ERA5 loader expects suitable NetCDF weather data. Users are responsible for
|
|
56
|
+
obtaining data, checking its provenance and licence, and preparing it for the
|
|
57
|
+
documented schema.
|
|
58
|
+
|
|
59
|
+
### Calibrate wind power curves
|
|
60
|
+
|
|
61
|
+
With the plant, generation, and ERA5 inputs in place, run the wind calibration
|
|
62
|
+
workflow and write its outputs to a separate directory:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
from rencal.calibration.wind.wind_calibrator import WindCalibrator
|
|
68
|
+
|
|
69
|
+
calibrator = WindCalibrator(
|
|
70
|
+
data_path="data",
|
|
71
|
+
output_path=Path("outputs/wind-calibration"),
|
|
72
|
+
visual_output=True,
|
|
73
|
+
stream_npy_output=True,
|
|
74
|
+
)
|
|
75
|
+
calibrator.calibrate()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The workflow writes the calibration summary, Weibull parameters, extracted wind
|
|
79
|
+
speeds, calibrated wind streams, and optional power-curve plots to the output
|
|
80
|
+
directory. With `stream_npy_output=True`, the generated `Wind Streams.npy` can
|
|
81
|
+
also be used by the weather sampler after its manifest and optional histogram
|
|
82
|
+
artefacts have been prepared.
|
|
83
|
+
|
|
84
|
+
### Sample calibrated wind streams
|
|
85
|
+
|
|
86
|
+
`WeatherData` samples future hourly paths from calibrated historical streams
|
|
87
|
+
while preserving the configured time-bucket structure. The local loader expects
|
|
88
|
+
the calibrated NPY file and its manifest under `data/calibrated/`.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import datetime
|
|
92
|
+
import random
|
|
93
|
+
|
|
94
|
+
import numpy as np
|
|
95
|
+
|
|
96
|
+
from rencal.core.data_loader import LocalDataLoader
|
|
97
|
+
from rencal.simulation.weather_data import HistoricalMetadata, WeatherData
|
|
98
|
+
|
|
99
|
+
loader = LocalDataLoader(data_path="data")
|
|
100
|
+
manifest = loader.check_historical_weather()
|
|
101
|
+
metadata = HistoricalMetadata.from_manifest(
|
|
102
|
+
manifest,
|
|
103
|
+
loader.path_resolver_weather_data,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
wind_sampler = WeatherData(
|
|
107
|
+
metadata=metadata,
|
|
108
|
+
prefix_histograms=loader.get_prefix_histograms(),
|
|
109
|
+
historical_data=loader.get_historical_weather(),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
sample = wind_sampler.random_sample(
|
|
113
|
+
datetime.datetime(2027, 1, 1),
|
|
114
|
+
datetime.datetime(2027, 1, 7),
|
|
115
|
+
python_rng=random.Random(4),
|
|
116
|
+
numpy_rng=np.random.default_rng(32),
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pass `desired_averages` to `WeatherData` when inverse-distribution resampling is
|
|
121
|
+
required; this also requires historical data or precomputed prefix histograms.
|
|
122
|
+
|
|
123
|
+
## Main capabilities
|
|
124
|
+
|
|
125
|
+
- Wind and solar power-curve calibration foundations
|
|
126
|
+
- Probabilistic load-factor forecasting
|
|
127
|
+
- Weather and generation data loading and validation
|
|
128
|
+
- Time-bucketed sampling with geographical correlation support
|
|
129
|
+
- Extensible interfaces for local and external data sources
|
|
130
|
+
|
|
131
|
+
## Documentation
|
|
132
|
+
|
|
133
|
+
- [Wind calibration tutorial](docs/tutorials/wind_calibration_tutorial.ipynb)
|
|
134
|
+
- [Contributing](CONTRIBUTING.md)
|
|
135
|
+
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
136
|
+
- [Issue tracker](https://github.com/LCCC-Tech/rencal/issues)
|
|
137
|
+
|
|
138
|
+
Hosted documentation and versioned examples will be linked here once the public
|
|
139
|
+
documentation site is verified.
|
|
140
|
+
|
|
141
|
+
## Support
|
|
142
|
+
|
|
143
|
+
Use [GitHub Issues](https://github.com/LCCC-Tech/rencal/issues) for public,
|
|
144
|
+
reproducible bugs and feature requests. Please do not include credentials,
|
|
145
|
+
internal data, or confidential information in issues.
|
|
146
|
+
|
|
147
|
+
## Licence
|
|
148
|
+
|
|
149
|
+
RenCal is released under the [MIT Licence](LICENSE).
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.10.8,<0.11.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rencal"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Weather-to-power calibration and Monte Carlo load factor simulation for renewable energy applications"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12,<3.13"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Low Carbon Contracts Company Ltd", email = "analytics@lowcarboncontracts.uk"}
|
|
15
|
+
]
|
|
16
|
+
maintainers = [
|
|
17
|
+
{name = "Low Carbon Contracts Company Ltd", email = "analytics@lowcarboncontracts.uk"}
|
|
18
|
+
]
|
|
19
|
+
keywords = [
|
|
20
|
+
"weather",
|
|
21
|
+
"forecasting",
|
|
22
|
+
"renewable energy",
|
|
23
|
+
"wind power",
|
|
24
|
+
"solar power",
|
|
25
|
+
"era5",
|
|
26
|
+
"monte carlo",
|
|
27
|
+
"calibration",
|
|
28
|
+
"climate"
|
|
29
|
+
]
|
|
30
|
+
classifiers = [
|
|
31
|
+
"Intended Audience :: Science/Research",
|
|
32
|
+
"Intended Audience :: Developers",
|
|
33
|
+
"License :: OSI Approved :: MIT License",
|
|
34
|
+
"Operating System :: OS Independent",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
37
|
+
"Topic :: Utilities"
|
|
38
|
+
]
|
|
39
|
+
dependencies = [
|
|
40
|
+
"numpy>=1.20.0",
|
|
41
|
+
"pandas>=1.3.0",
|
|
42
|
+
"scipy>=1.7.0",
|
|
43
|
+
"matplotlib>=3.4.0",
|
|
44
|
+
"seaborn>=0.12.0",
|
|
45
|
+
"scikit-learn>=1.0.0",
|
|
46
|
+
"netCDF4>=1.5.0",
|
|
47
|
+
"xarray>=0.19.0",
|
|
48
|
+
"pytz>=2021.1",
|
|
49
|
+
"requests>=2.25.0",
|
|
50
|
+
"polars>=1.8.2",
|
|
51
|
+
"pydantic>=2.10.6",
|
|
52
|
+
"pyodbc>=4.0.0",
|
|
53
|
+
"cdsapi>=0.6.0",
|
|
54
|
+
"python-dotenv>=1.0.0",
|
|
55
|
+
"openpyxl>=3.1.0",
|
|
56
|
+
"zarr>=2.16.1",
|
|
57
|
+
"tqdm>=4.62.0",
|
|
58
|
+
"pyarrow>=22.0.0",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[dependency-groups]
|
|
62
|
+
dev = [
|
|
63
|
+
"pytest>=6.0",
|
|
64
|
+
"pytest-cov>=2.0",
|
|
65
|
+
"ruff>=0.8.0",
|
|
66
|
+
"basedpyright>=1.0.0",
|
|
67
|
+
"pre-commit>=2.15.0",
|
|
68
|
+
"build>=1.0.0"
|
|
69
|
+
]
|
|
70
|
+
docs = [
|
|
71
|
+
"sphinx>=4.0",
|
|
72
|
+
"sphinx-rtd-theme>=1.0",
|
|
73
|
+
"myst-parser>=0.15.0",
|
|
74
|
+
"sphinx-autoapi>=1.8.0"
|
|
75
|
+
]
|
|
76
|
+
azure = [
|
|
77
|
+
"azure-storage-blob>=12.0.0",
|
|
78
|
+
"azure-identity>=1.7.0"
|
|
79
|
+
]
|
|
80
|
+
notebook = [
|
|
81
|
+
"jupyter>=1.0.0",
|
|
82
|
+
"ipykernel>=6.0.0",
|
|
83
|
+
"plotly>=5.0.0"
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[project.urls]
|
|
87
|
+
Homepage = "https://github.com/LCCC-Tech/rencal"
|
|
88
|
+
Repository = "https://github.com/LCCC-Tech/rencal.git"
|
|
89
|
+
"Bug Tracker" = "https://github.com/LCCC-Tech/rencal/issues"
|
|
90
|
+
Changelog = "https://github.com/LCCC-Tech/rencal/blob/main/CHANGELOG.md"
|
|
91
|
+
|
|
92
|
+
[tool.uv.build-backend]
|
|
93
|
+
module-name = "rencal"
|
|
94
|
+
module-root = ""
|
|
95
|
+
|
|
96
|
+
[tool.basedpyright]
|
|
97
|
+
include = ["rencal"]
|
|
98
|
+
exclude = [".venv", "build", "dist", "rencal/simulation/intermittent_bucketer.py", "rencal/core/bucketed_data.py",]
|
|
99
|
+
venvPath = "."
|
|
100
|
+
venv = ".venv"
|
|
101
|
+
pythonVersion = "3.12"
|
|
102
|
+
typeCheckingMode = "basic" # Changed from strict to basic to reduce false positives
|
|
103
|
+
reportMissingImports = true
|
|
104
|
+
reportMissingTypeStubs = false
|
|
105
|
+
reportUnusedImport = true
|
|
106
|
+
reportUnusedClass = false # Reduce noise for now
|
|
107
|
+
reportUnusedFunction = false # Reduce noise for now
|
|
108
|
+
reportUnusedVariable = true
|
|
109
|
+
# Less strict about third-party library types
|
|
110
|
+
reportUnknownMemberType = false
|
|
111
|
+
reportUnknownVariableType = false
|
|
112
|
+
reportUnknownArgumentType = false
|
|
113
|
+
# Disable overly strict argument type checking for third-party libraries
|
|
114
|
+
reportArgumentType = false
|
|
115
|
+
# Additional settings to help with import resolution
|
|
116
|
+
extraPaths = ["."]
|
|
117
|
+
|
|
118
|
+
[tool.ruff]
|
|
119
|
+
line-length = 100
|
|
120
|
+
indent-width = 4
|
|
121
|
+
target-version = "py312"
|
|
122
|
+
|
|
123
|
+
[tool.ruff.lint]
|
|
124
|
+
select = [
|
|
125
|
+
"E", # pycodestyle errors
|
|
126
|
+
"W", # pycodestyle warnings
|
|
127
|
+
"F", # pyflakes
|
|
128
|
+
"I", # isort
|
|
129
|
+
"B", # flake8-bugbear
|
|
130
|
+
"C4", # flake8-comprehensions
|
|
131
|
+
"UP", # pyupgrade
|
|
132
|
+
]
|
|
133
|
+
ignore = [
|
|
134
|
+
"E501", # Line too long (handled by formatter)
|
|
135
|
+
"B008", # Do not perform function calls in argument defaults
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[tool.ruff.format]
|
|
139
|
+
quote-style = "double"
|
|
140
|
+
indent-style = "space"
|
|
141
|
+
skip-magic-trailing-comma = false
|
|
142
|
+
line-ending = "auto"
|
|
143
|
+
docstring-code-format = true
|
|
144
|
+
docstring-code-line-length = "dynamic"
|
|
145
|
+
|
|
146
|
+
[tool.ruff.lint.isort]
|
|
147
|
+
known-first-party = ["rencal"]
|
|
148
|
+
|
|
149
|
+
# pytest configuration
|
|
150
|
+
[tool.pytest.ini_options]
|
|
151
|
+
minversion = "6.0"
|
|
152
|
+
addopts = "-ra -q --strict-markers --strict-config"
|
|
153
|
+
testpaths = ["tests"]
|
|
154
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
155
|
+
python_classes = ["Test*"]
|
|
156
|
+
python_functions = ["test_*"]
|
|
157
|
+
filterwarnings = [
|
|
158
|
+
"error",
|
|
159
|
+
"ignore::UserWarning",
|
|
160
|
+
"ignore::DeprecationWarning",
|
|
161
|
+
"ignore:numpy.*:RuntimeWarning",
|
|
162
|
+
]
|
|
163
|
+
markers = [
|
|
164
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
165
|
+
"integration: marks tests as integration tests",
|
|
166
|
+
"unit: marks tests as unit tests",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
# Coverage configuration
|
|
170
|
+
[tool.coverage.run]
|
|
171
|
+
source = ["rencal"]
|
|
172
|
+
omit = [
|
|
173
|
+
"*/tests/*",
|
|
174
|
+
"*/test_*",
|
|
175
|
+
"setup.py",
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
[tool.coverage.report]
|
|
179
|
+
exclude_lines = [
|
|
180
|
+
"pragma: no cover",
|
|
181
|
+
"def __repr__",
|
|
182
|
+
"if self.debug:",
|
|
183
|
+
"if settings.DEBUG",
|
|
184
|
+
"raise AssertionError",
|
|
185
|
+
"raise NotImplementedError",
|
|
186
|
+
"if 0:",
|
|
187
|
+
"if __name__ == .__main__.:",
|
|
188
|
+
"class .*\\bProtocol\\):",
|
|
189
|
+
"@(abc\\.)?abstractmethod",
|
|
190
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Renewable Calibration (rencal) for renwable energy forecasting.
|
|
3
|
+
|
|
4
|
+
A Python library for calibrating renewable plant power curves using generalised logistic function
|
|
5
|
+
modules for generating probabilistic load factor time series forecasts using resampling from
|
|
6
|
+
historical ERA5 weather data.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import version
|
|
10
|
+
|
|
11
|
+
__version__ = version("rencal")
|
|
12
|
+
__author__ = "Low Carbon Contracts Company Ltd"
|
|
13
|
+
__email__ = "analytics@lowcarboncontracts.uk"
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Abstract base class for the calibration of intermittent generator power curves."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from ..core.data_loader import DataLoader, LocalDataLoader
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Calibrator(ABC):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
data_path: str = None,
|
|
12
|
+
plant_id_col: str = None,
|
|
13
|
+
loader: DataLoader | None = None,
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
Initialise a calibrator instance.
|
|
17
|
+
|
|
18
|
+
The Calibrator is an abstract class that specifies the methods any intermittent
|
|
19
|
+
generator's calibration should include, as well as setting up and loading input
|
|
20
|
+
datasets from the configuration file or user-specified paths.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data_path (str): Path to the directory containing the input data files.
|
|
24
|
+
plant_id_col (str): Name of the ID column designating a power plant in the input.
|
|
25
|
+
loader (DataLoader | None): Optional pre-configured data loader. When supplied it
|
|
26
|
+
is used as-is (overriding ``data_path``), allowing inputs to be read from
|
|
27
|
+
non-default locations or sources (e.g. a cloud loader reading directly from
|
|
28
|
+
Blob storage) instead of the default ``LocalDataLoader`` layout.
|
|
29
|
+
"""
|
|
30
|
+
if loader is not None:
|
|
31
|
+
self.loader = loader
|
|
32
|
+
else:
|
|
33
|
+
self.loader = LocalDataLoader(data_path) if data_path else LocalDataLoader()
|
|
34
|
+
self.plants = (
|
|
35
|
+
self.loader.load_plant_data(plant_id_col)
|
|
36
|
+
if plant_id_col
|
|
37
|
+
else self.loader.load_plant_data()
|
|
38
|
+
)
|
|
39
|
+
self.generation = (
|
|
40
|
+
self.loader.load_generation_data(plant_id_col)
|
|
41
|
+
if plant_id_col
|
|
42
|
+
else self.loader.load_generation_data()
|
|
43
|
+
)
|
|
44
|
+
self.resource = self.loader.load_era5_data()
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def calibrate(self) -> None:
|
|
48
|
+
"""Triggers calibration workflow."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def extract_resource_timeseries_for_plants(self) -> None:
|
|
53
|
+
"""Extracts resource time series for plants."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def calculate_historical_load_factors(self) -> None:
|
|
58
|
+
"""Calculates historical load factors."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def fit_historical_load_factor_distribution(self) -> None:
|
|
63
|
+
"""Fits a distribution to historical load factors."""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def estimate_load_factors_for_resource(self) -> None:
|
|
68
|
+
"""Estimates load factors based on historical distribution and full resource availability."""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def generate_resource_streams(self) -> None:
|
|
73
|
+
"""Generates load factor streams for available resource time series for each generator."""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def output_historical_load_factor_distribution_parameters(self) -> None:
|
|
78
|
+
"""Writes historical load factor parameters to a CSV file."""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def output_resource_per_plant(self) -> None:
|
|
83
|
+
"""Writes resource time series for each plant to a CSV file."""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def output_resource_streams(self) -> None:
|
|
88
|
+
"""Writes resource streams to a parquet file."""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def output_estimated_load_factors_tabular(self) -> None:
|
|
93
|
+
"""Outputs table of estimated load factors."""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def output_estimated_load_factors_visual(self) -> None:
|
|
98
|
+
"""Outputs a series of plots of estimated load factors and calibrated curves."""
|
|
99
|
+
pass
|
|
File without changes
|