hefty 0.1.6__tar.gz → 0.2.0__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.
- {hefty-0.1.6/src/hefty.egg-info → hefty-0.2.0}/PKG-INFO +5 -4
- {hefty-0.1.6 → hefty-0.2.0}/README.md +4 -3
- {hefty-0.1.6 → hefty-0.2.0}/pyproject.toml +1 -1
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty/solar.py +387 -107
- {hefty-0.1.6 → hefty-0.2.0/src/hefty.egg-info}/PKG-INFO +5 -4
- {hefty-0.1.6 → hefty-0.2.0}/LICENSE +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/setup.cfg +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty/__init__.py +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty/custom.py +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty/pv_model.py +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty/utilities.py +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty/wind.py +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty.egg-info/SOURCES.txt +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty.egg-info/dependency_links.txt +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty.egg-info/requires.txt +0 -0
- {hefty-0.1.6 → hefty-0.2.0}/src/hefty.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hefty
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Some (relatively) lightweight short-term energy forecasting tools for solar, wind, and load.
|
|
5
5
|
Author: Will Hobbs
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -29,13 +29,13 @@ This repository currently includes solar and wind tools, but may expand one day
|
|
|
29
29
|
- NOAA HRRR
|
|
30
30
|
- ECMWF IFS (open data version)
|
|
31
31
|
- ECMWF AIFS
|
|
32
|
-
- ECMWF CAMS
|
|
32
|
+
- ECMWF CAMS version of IFS* (a.k.a. "IFS-COMPO")
|
|
33
33
|
|
|
34
34
|
A detailed pvlib-based PV weather-to-power model is included in [pv_model.py](src/hefty/pv_model.py), `model_pv_power()`.
|
|
35
35
|
|
|
36
36
|
The [custom.py](src/hefty/custom.py) module is intended to help with getting forecasts of "custom" weather parameters, not necessarily specific to wind or solar, which migh be useful for load forecasting.
|
|
37
37
|
|
|
38
|
-
_*CAMS IFS is only available via `hefty.solar.get_solar_forecast()`, and it requires `cdsapi` to be installed and the user needs an API key (see https://ads.atmosphere.copernicus.eu/how-to-api)._
|
|
38
|
+
_*CAMS IFS is only available via `hefty.solar.get_solar_forecast()`, and it requires `cdsapi` to be installed and the user needs an API key (see https://ads.atmosphere.copernicus.eu/how-to-api and the [CAMS example notebook](examples/cams_example.ipynb))._
|
|
39
39
|
|
|
40
40
|
## Contents
|
|
41
41
|
- [Installation](#installation) - getting started
|
|
@@ -130,7 +130,8 @@ The [examples](examples) folder contains several example Jupyter notebooks.
|
|
|
130
130
|
- [more_solar_examples.ipynb](examples/more_solar_examples.ipynb): Expanded examples, including multiple sites and multiple forecast initialization times.
|
|
131
131
|
- [cams_example.ipynb](examples/cams_example.ipynb): Solar resource forecasting with CAMS IFS.
|
|
132
132
|
- [ensemble_example.ipynb](examples/ensemble_example.ipynb): Examples of multi-member ensembles with IFS, AIFS, and GEFS.
|
|
133
|
-
- [wind_example.ipynb](examples/wind_example.ipynb): A basic wind resource forecast example using a few models, including converting the data to a format compatible with `windpowerlib
|
|
133
|
+
- [wind_example.ipynb](examples/wind_example.ipynb): A basic wind resource forecast example using a few models, including converting the data to a format compatible with `windpowerlib`*.
|
|
134
|
+
- [erbs_vs_dirindex.ipynb](examples/erbs_vs_dirindex.ipynb): A comparison of Erbs and DIRINDEX irradiance decomposition models using SURFRAD measurements.
|
|
134
135
|
|
|
135
136
|
\* https://github.com/wind-python/windpowerlib/
|
|
136
137
|
|
|
@@ -7,13 +7,13 @@ This repository currently includes solar and wind tools, but may expand one day
|
|
|
7
7
|
- NOAA HRRR
|
|
8
8
|
- ECMWF IFS (open data version)
|
|
9
9
|
- ECMWF AIFS
|
|
10
|
-
- ECMWF CAMS
|
|
10
|
+
- ECMWF CAMS version of IFS* (a.k.a. "IFS-COMPO")
|
|
11
11
|
|
|
12
12
|
A detailed pvlib-based PV weather-to-power model is included in [pv_model.py](src/hefty/pv_model.py), `model_pv_power()`.
|
|
13
13
|
|
|
14
14
|
The [custom.py](src/hefty/custom.py) module is intended to help with getting forecasts of "custom" weather parameters, not necessarily specific to wind or solar, which migh be useful for load forecasting.
|
|
15
15
|
|
|
16
|
-
_*CAMS IFS is only available via `hefty.solar.get_solar_forecast()`, and it requires `cdsapi` to be installed and the user needs an API key (see https://ads.atmosphere.copernicus.eu/how-to-api)._
|
|
16
|
+
_*CAMS IFS is only available via `hefty.solar.get_solar_forecast()`, and it requires `cdsapi` to be installed and the user needs an API key (see https://ads.atmosphere.copernicus.eu/how-to-api and the [CAMS example notebook](examples/cams_example.ipynb))._
|
|
17
17
|
|
|
18
18
|
## Contents
|
|
19
19
|
- [Installation](#installation) - getting started
|
|
@@ -108,7 +108,8 @@ The [examples](examples) folder contains several example Jupyter notebooks.
|
|
|
108
108
|
- [more_solar_examples.ipynb](examples/more_solar_examples.ipynb): Expanded examples, including multiple sites and multiple forecast initialization times.
|
|
109
109
|
- [cams_example.ipynb](examples/cams_example.ipynb): Solar resource forecasting with CAMS IFS.
|
|
110
110
|
- [ensemble_example.ipynb](examples/ensemble_example.ipynb): Examples of multi-member ensembles with IFS, AIFS, and GEFS.
|
|
111
|
-
- [wind_example.ipynb](examples/wind_example.ipynb): A basic wind resource forecast example using a few models, including converting the data to a format compatible with `windpowerlib
|
|
111
|
+
- [wind_example.ipynb](examples/wind_example.ipynb): A basic wind resource forecast example using a few models, including converting the data to a format compatible with `windpowerlib`*.
|
|
112
|
+
- [erbs_vs_dirindex.ipynb](examples/erbs_vs_dirindex.ipynb): A comparison of Erbs and DIRINDEX irradiance decomposition models using SURFRAD measurements.
|
|
112
113
|
|
|
113
114
|
\* https://github.com/wind-python/windpowerlib/
|
|
114
115
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hefty"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Some (relatively) lightweight short-term energy forecasting tools for solar, wind, and load."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -20,7 +20,8 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
20
20
|
lead_time_to_start=0, model='gfs', member='avg',
|
|
21
21
|
attempts=2, hrrr_hour_middle=True,
|
|
22
22
|
hrrr_coursen_window=None, priority=None,
|
|
23
|
-
cams_api_key=None, cams_area=None
|
|
23
|
+
cams_api_key=None, cams_area=None,
|
|
24
|
+
decomp_model='dirindex'):
|
|
24
25
|
"""
|
|
25
26
|
Get a solar resource forecast for one or several sites from one of several
|
|
26
27
|
NWPs. This function uses Herbie [1]_ and pvlib [2]_.
|
|
@@ -96,6 +97,11 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
96
97
|
South, and East corners of the area to be covered when using 'cams'.
|
|
97
98
|
For example, [50, -125, 20, -65] approximately covers CONUS.
|
|
98
99
|
|
|
100
|
+
decomp_model : string, optional
|
|
101
|
+
Decomposition model to use for forecast models that don't include DNI.
|
|
102
|
+
Options include 'dirindex' and 'erbs'. Note that HRRR and CAMS include
|
|
103
|
+
DNI, so this parameter does not apply to those models.
|
|
104
|
+
|
|
99
105
|
Returns
|
|
100
106
|
-------
|
|
101
107
|
data : pandas.DataFrane
|
|
@@ -113,7 +119,7 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
113
119
|
"""
|
|
114
120
|
|
|
115
121
|
# set clear sky model. could be an input variable at some point
|
|
116
|
-
model_cs = 'simplified_solis'
|
|
122
|
+
# model_cs = 'simplified_solis'
|
|
117
123
|
model_cs_kwargs = {
|
|
118
124
|
'aod700': 0.05,
|
|
119
125
|
'precipitable_water': 0.5,
|
|
@@ -144,6 +150,11 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
144
150
|
|
|
145
151
|
# get NWP data as dataframe
|
|
146
152
|
if model != 'cams':
|
|
153
|
+
delimiter = '|'
|
|
154
|
+
search_string_list = search_str.split(delimiter)
|
|
155
|
+
num_datasets = len(search_string_list)
|
|
156
|
+
if model == 'hrrr':
|
|
157
|
+
num_datasets -= 1 # DNI and GHI will show up in a single dataset
|
|
147
158
|
i = []
|
|
148
159
|
for fxx in fxx_range:
|
|
149
160
|
# get solar, 10m wind, and 2m temp data
|
|
@@ -162,6 +173,13 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
162
173
|
member=member,
|
|
163
174
|
priority=priority
|
|
164
175
|
).xarray(search_str)
|
|
176
|
+
# address GH#77
|
|
177
|
+
if len(ds) < num_datasets:
|
|
178
|
+
msg = ('Parameters appear to be '
|
|
179
|
+
'missing. Another download'
|
|
180
|
+
' will be attempted if there are attempts'
|
|
181
|
+
' remaining.')
|
|
182
|
+
raise ValueError(msg)
|
|
165
183
|
# merge - override avoids height conflict between 2m
|
|
166
184
|
# temp and 10m wind
|
|
167
185
|
ds = xr.merge(ds, compat='override')
|
|
@@ -176,6 +194,13 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
176
194
|
member=member,
|
|
177
195
|
priority=priority
|
|
178
196
|
).xarray(search_str, overwrite=True)
|
|
197
|
+
# address GH#77
|
|
198
|
+
if len(ds) < num_datasets:
|
|
199
|
+
msg = ('Parameters appear to be '
|
|
200
|
+
'missing. Another download'
|
|
201
|
+
' will be attempted if there are attempts'
|
|
202
|
+
' remaining.')
|
|
203
|
+
raise ValueError(msg)
|
|
179
204
|
# merge - override avoids height conflict between 2m
|
|
180
205
|
# temp and 10m wind
|
|
181
206
|
ds = xr.merge(ds, compat='override')
|
|
@@ -407,7 +432,16 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
407
432
|
tz='UTC')
|
|
408
433
|
|
|
409
434
|
# calculate clear sky ghi with pvlib
|
|
410
|
-
|
|
435
|
+
sp = pvlib.solarposition.ephemeris(
|
|
436
|
+
times,
|
|
437
|
+
loc.latitude,
|
|
438
|
+
loc.longitude
|
|
439
|
+
)
|
|
440
|
+
apparent_elevation = sp['apparent_elevation']
|
|
441
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
442
|
+
apparent_elevation,
|
|
443
|
+
**model_cs_kwargs
|
|
444
|
+
)
|
|
411
445
|
|
|
412
446
|
# calculate average CS ghi over the intervals from the forecast
|
|
413
447
|
# based on list comprehension example in
|
|
@@ -458,7 +492,18 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
458
492
|
end=df.index[-1]-pd.Timedelta('30m'),
|
|
459
493
|
freq='60min',
|
|
460
494
|
tz='UTC')
|
|
461
|
-
|
|
495
|
+
|
|
496
|
+
# calculate clear sky ghi with pvlib
|
|
497
|
+
sp = pvlib.solarposition.ephemeris(
|
|
498
|
+
times,
|
|
499
|
+
loc.latitude,
|
|
500
|
+
loc.longitude
|
|
501
|
+
)
|
|
502
|
+
apparent_elevation = sp['apparent_elevation']
|
|
503
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
504
|
+
apparent_elevation,
|
|
505
|
+
**model_cs_kwargs
|
|
506
|
+
)
|
|
462
507
|
|
|
463
508
|
# calculate ghi from clear sky and backfilled forecasted clear sky
|
|
464
509
|
# index
|
|
@@ -467,16 +512,31 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
467
512
|
# clip to avoid occasional small negative ghi in GEFS, see GH #35
|
|
468
513
|
df_60min['ghi'] = df_60min['ghi'].clip(lower=0)
|
|
469
514
|
|
|
470
|
-
#
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
515
|
+
# decompose
|
|
516
|
+
if decomp_model == 'erbs':
|
|
517
|
+
out_erbs = pvlib.irradiance.erbs(
|
|
518
|
+
df_60min['ghi'],
|
|
519
|
+
sp['zenith'],
|
|
520
|
+
df_60min.index,
|
|
521
|
+
)
|
|
522
|
+
df_60min['dni'] = out_erbs['dni']
|
|
523
|
+
df_60min['dhi'] = out_erbs['dhi']
|
|
524
|
+
elif decomp_model == 'dirindex':
|
|
525
|
+
cs_decomp = cs # could later use a different cs model...
|
|
526
|
+
decomp = pvlib.irradiance.dirindex(
|
|
527
|
+
ghi=df_60min['ghi'],
|
|
528
|
+
ghi_clear=cs_decomp['ghi'],
|
|
529
|
+
dni_clear=cs_decomp['dni'],
|
|
530
|
+
zenith=sp['zenith'],
|
|
531
|
+
times=times,
|
|
532
|
+
)
|
|
533
|
+
df_60min['dni'] = decomp.fillna(0)
|
|
534
|
+
cos_zenith = np.maximum(
|
|
535
|
+
np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
536
|
+
min_cos_zenith
|
|
537
|
+
)
|
|
538
|
+
df_60min['dhi'] = (df_60min['ghi'] -
|
|
539
|
+
(df_60min['dni'] * cos_zenith))
|
|
480
540
|
|
|
481
541
|
# add clearsky ghi
|
|
482
542
|
df_60min['ghi_clear'] = df_60min['ghi'] / df_60min['ghi_csi']
|
|
@@ -500,7 +560,11 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
500
560
|
df.index = df.index - pd.Timedelta('30min')
|
|
501
561
|
|
|
502
562
|
# direct horiz clear to dni_clear
|
|
503
|
-
sp =
|
|
563
|
+
sp = pvlib.solarposition.ephemeris(
|
|
564
|
+
df.index,
|
|
565
|
+
loc.latitude,
|
|
566
|
+
loc.longitude
|
|
567
|
+
)
|
|
504
568
|
cos_zenith = np.maximum(np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
505
569
|
min_cos_zenith)
|
|
506
570
|
df['dni_clear'] = (df['direct_horiz_clear'] / cos_zenith)
|
|
@@ -523,8 +587,16 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
523
587
|
if hrrr_hour_middle is True:
|
|
524
588
|
# clear sky index
|
|
525
589
|
times = df.index
|
|
526
|
-
|
|
527
|
-
|
|
590
|
+
sp = pvlib.solarposition.ephemeris(
|
|
591
|
+
times,
|
|
592
|
+
loc.latitude,
|
|
593
|
+
loc.longitude
|
|
594
|
+
)
|
|
595
|
+
apparent_elevation = sp['apparent_elevation']
|
|
596
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
597
|
+
apparent_elevation,
|
|
598
|
+
**model_cs_kwargs
|
|
599
|
+
)
|
|
528
600
|
df['csi_ghi'] = df['ghi'] / cs['ghi']
|
|
529
601
|
df['csi_dni'] = df['dni'] / cs['dni']
|
|
530
602
|
# avoid divide by zero issues
|
|
@@ -538,8 +610,18 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
538
610
|
freq='1min',
|
|
539
611
|
tz='UTC')
|
|
540
612
|
|
|
541
|
-
|
|
542
|
-
|
|
613
|
+
# calculate clear sky ghi with pvlib
|
|
614
|
+
sp = pvlib.solarposition.ephemeris(
|
|
615
|
+
times,
|
|
616
|
+
loc.latitude,
|
|
617
|
+
loc.longitude
|
|
618
|
+
)
|
|
619
|
+
apparent_elevation = sp['apparent_elevation']
|
|
620
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
621
|
+
apparent_elevation,
|
|
622
|
+
**model_cs_kwargs
|
|
623
|
+
)
|
|
624
|
+
|
|
543
625
|
# calculate 1min interpolated temp_air, wind_speed, csi
|
|
544
626
|
df_01min = (
|
|
545
627
|
df[['temp_air', 'wind_speed', 'csi_ghi', 'csi_dni']]
|
|
@@ -561,15 +643,22 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
561
643
|
df_60min = df.copy()
|
|
562
644
|
|
|
563
645
|
# calculate dhi from ghi, dni, solar position
|
|
564
|
-
sp =
|
|
646
|
+
sp = pvlib.solarposition.ephemeris(
|
|
647
|
+
df_60min.index,
|
|
648
|
+
loc.latitude,
|
|
649
|
+
loc.longitude
|
|
650
|
+
)
|
|
565
651
|
cos_zenith = np.maximum(np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
566
652
|
min_cos_zenith)
|
|
567
653
|
df_60min['dhi'] = (df_60min['ghi'] -
|
|
568
654
|
(df_60min['dni'] * cos_zenith))
|
|
569
655
|
|
|
570
656
|
# add clearsky ghi
|
|
571
|
-
|
|
572
|
-
|
|
657
|
+
apparent_elevation = sp['apparent_elevation']
|
|
658
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
659
|
+
apparent_elevation,
|
|
660
|
+
**model_cs_kwargs
|
|
661
|
+
)
|
|
573
662
|
df_60min['ghi_clear'] = cs['ghi']
|
|
574
663
|
df_60min['dni_clear'] = cs['dni']
|
|
575
664
|
|
|
@@ -595,7 +684,8 @@ def get_solar_forecast(latitude, longitude, init_date, run_length,
|
|
|
595
684
|
def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
596
685
|
lead_time_to_start=0, model='gfs', member='avg',
|
|
597
686
|
attempts=2, hrrr_hour_middle=True,
|
|
598
|
-
hrrr_coursen_window=None, priority=None
|
|
687
|
+
hrrr_coursen_window=None, priority=None,
|
|
688
|
+
decomp_model='dirindex'):
|
|
599
689
|
"""
|
|
600
690
|
Get a solar resource forecast for one or several sites from one of several
|
|
601
691
|
NWPs. This function uses Herbie [1]_ and pvlib [2]_. This version
|
|
@@ -665,6 +755,11 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
665
755
|
priority, or string for a single source. See Herbie docs.
|
|
666
756
|
Typical values would be 'aws' or 'google'.
|
|
667
757
|
|
|
758
|
+
decomp_model : string, optional
|
|
759
|
+
Decomposition model to use for forecast models that don't include DNI.
|
|
760
|
+
Options include 'dirindex' and 'erbs'. Note that HRRR and CAMS include
|
|
761
|
+
DNI, so this parameter does not apply to those models.
|
|
762
|
+
|
|
668
763
|
Returns
|
|
669
764
|
-------
|
|
670
765
|
data : pandas.DataFrane
|
|
@@ -730,6 +825,10 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
730
825
|
FH.download(search_string_list[j])
|
|
731
826
|
ds_dict[j] = FH.xarray(search_string_list[j],
|
|
732
827
|
remove_grib=True)
|
|
828
|
+
# calculate wind speed from u and v components if relevant
|
|
829
|
+
if ('uv' in search_string_list[j] or
|
|
830
|
+
'UV' in search_string_list[j]):
|
|
831
|
+
ds_dict[j] = ds_dict[j].herbie.with_wind('speed')
|
|
733
832
|
# merge - override avoids height conflict between 2m temp
|
|
734
833
|
# and 10m wind
|
|
735
834
|
ds = xr.merge(ds_dict.values(), compat='override')
|
|
@@ -740,6 +839,10 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
740
839
|
ds_dict[j] = FH.xarray(search_string_list[j],
|
|
741
840
|
remove_grib=True,
|
|
742
841
|
overwrite=True)
|
|
842
|
+
# calculate wind speed from u and v components if relevant
|
|
843
|
+
if ('uv' in search_string_list[j] or
|
|
844
|
+
'UV' in search_string_list[j]):
|
|
845
|
+
ds_dict[j] = ds_dict[j].herbie.with_wind('speed')
|
|
743
846
|
# merge - override avoids height conflict between 2m temp
|
|
744
847
|
# and 10m wind
|
|
745
848
|
ds = xr.merge(ds_dict.values(), compat='override')
|
|
@@ -755,9 +858,6 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
755
858
|
else:
|
|
756
859
|
break
|
|
757
860
|
|
|
758
|
-
# calculate wind speed from u and v components
|
|
759
|
-
ds = ds.herbie.with_wind('speed')
|
|
760
|
-
|
|
761
861
|
if model == 'hrrr' and hrrr_coursen_window is not None:
|
|
762
862
|
ds = ds.coarsen(x=hrrr_coursen_window,
|
|
763
863
|
y=hrrr_coursen_window,
|
|
@@ -849,7 +949,17 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
849
949
|
freq='1min',
|
|
850
950
|
tz='UTC')
|
|
851
951
|
|
|
852
|
-
|
|
952
|
+
# calculate clear sky ghi with pvlib
|
|
953
|
+
sp = pvlib.solarposition.ephemeris(
|
|
954
|
+
times,
|
|
955
|
+
loc.latitude,
|
|
956
|
+
loc.longitude
|
|
957
|
+
)
|
|
958
|
+
apparent_elevation = sp['apparent_elevation']
|
|
959
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
960
|
+
apparent_elevation,
|
|
961
|
+
**model_cs_kwargs
|
|
962
|
+
)
|
|
853
963
|
|
|
854
964
|
# calculate average CS ghi over the intervals from the forecast
|
|
855
965
|
# based on list comprehension example in
|
|
@@ -898,7 +1008,18 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
898
1008
|
end=df.index[-1]-pd.Timedelta('30m'),
|
|
899
1009
|
freq='60min',
|
|
900
1010
|
tz='UTC')
|
|
901
|
-
|
|
1011
|
+
|
|
1012
|
+
# calculate clear sky ghi with pvlib
|
|
1013
|
+
sp = pvlib.solarposition.ephemeris(
|
|
1014
|
+
times,
|
|
1015
|
+
loc.latitude,
|
|
1016
|
+
loc.longitude
|
|
1017
|
+
)
|
|
1018
|
+
apparent_elevation = sp['apparent_elevation']
|
|
1019
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
1020
|
+
apparent_elevation,
|
|
1021
|
+
**model_cs_kwargs
|
|
1022
|
+
)
|
|
902
1023
|
|
|
903
1024
|
# calculate ghi from clear sky and backfilled forecasted clear sky
|
|
904
1025
|
# index
|
|
@@ -907,15 +1028,31 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
907
1028
|
# clip to avoid occasional small negative ghi in GEFS, see GH #35
|
|
908
1029
|
df_60min['ghi'] = df_60min['ghi'].clip(lower=0)
|
|
909
1030
|
|
|
910
|
-
#
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1031
|
+
# decompose
|
|
1032
|
+
if decomp_model == 'erbs':
|
|
1033
|
+
out_erbs = pvlib.irradiance.erbs(
|
|
1034
|
+
df_60min['ghi'],
|
|
1035
|
+
sp['zenith'],
|
|
1036
|
+
df_60min.index,
|
|
1037
|
+
)
|
|
1038
|
+
df_60min['dni'] = out_erbs['dni']
|
|
1039
|
+
df_60min['dhi'] = out_erbs['dhi']
|
|
1040
|
+
elif decomp_model == 'dirindex':
|
|
1041
|
+
cs_decomp = cs # could later use a different cs model...
|
|
1042
|
+
decomp = pvlib.irradiance.dirindex(
|
|
1043
|
+
ghi=df_60min['ghi'],
|
|
1044
|
+
ghi_clear=cs_decomp['ghi'],
|
|
1045
|
+
dni_clear=cs_decomp['dni'],
|
|
1046
|
+
zenith=sp['zenith'],
|
|
1047
|
+
times=times,
|
|
1048
|
+
)
|
|
1049
|
+
df_60min['dni'] = decomp.fillna(0)
|
|
1050
|
+
cos_zenith = np.maximum(
|
|
1051
|
+
np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
1052
|
+
min_cos_zenith
|
|
1053
|
+
)
|
|
1054
|
+
df_60min['dhi'] = (df_60min['ghi'] -
|
|
1055
|
+
(df_60min['dni'] * cos_zenith))
|
|
919
1056
|
|
|
920
1057
|
# add clearsky ghi
|
|
921
1058
|
df_60min['ghi_clear'] = df_60min['ghi'] / df_60min['ghi_csi']
|
|
@@ -941,8 +1078,18 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
941
1078
|
freq='1min',
|
|
942
1079
|
tz='UTC')
|
|
943
1080
|
|
|
944
|
-
|
|
945
|
-
|
|
1081
|
+
# calculate clear sky ghi with pvlib
|
|
1082
|
+
sp = pvlib.solarposition.ephemeris(
|
|
1083
|
+
times,
|
|
1084
|
+
loc.latitude,
|
|
1085
|
+
loc.longitude
|
|
1086
|
+
)
|
|
1087
|
+
apparent_elevation = sp['apparent_elevation']
|
|
1088
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
1089
|
+
apparent_elevation,
|
|
1090
|
+
**model_cs_kwargs
|
|
1091
|
+
)
|
|
1092
|
+
|
|
946
1093
|
# calculate 1min interpolated temp_air, wind_speed, csi
|
|
947
1094
|
df_01min = (
|
|
948
1095
|
df[['temp_air', 'wind_speed', 'csi_ghi', 'csi_dni']]
|
|
@@ -964,15 +1111,22 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
964
1111
|
df_60min = df.copy()
|
|
965
1112
|
|
|
966
1113
|
# calculate dhi from ghi, dni, solar position
|
|
967
|
-
sp =
|
|
1114
|
+
sp = pvlib.solarposition.ephemeris(
|
|
1115
|
+
df_60min.index,
|
|
1116
|
+
loc.latitude,
|
|
1117
|
+
loc.longitude
|
|
1118
|
+
)
|
|
968
1119
|
cos_zenith = np.maximum(np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
969
1120
|
min_cos_zenith)
|
|
970
1121
|
df_60min['dhi'] = (df_60min['ghi'] -
|
|
971
1122
|
(df_60min['dni'] * cos_zenith))
|
|
972
1123
|
|
|
973
1124
|
# add clearsky ghi
|
|
974
|
-
|
|
975
|
-
|
|
1125
|
+
apparent_elevation = sp['apparent_elevation']
|
|
1126
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
1127
|
+
apparent_elevation,
|
|
1128
|
+
**model_cs_kwargs
|
|
1129
|
+
)
|
|
976
1130
|
df_60min['ghi_clear'] = cs['ghi']
|
|
977
1131
|
df_60min['dni_clear'] = cs['dni']
|
|
978
1132
|
|
|
@@ -997,7 +1151,8 @@ def get_solar_forecast_fast(latitude, longitude, init_date, run_length,
|
|
|
997
1151
|
|
|
998
1152
|
def get_solar_forecast_ensemble_subset(
|
|
999
1153
|
latitude, longitude, init_date, run_length, lead_time_to_start=0,
|
|
1000
|
-
model='ifs_ens', attempts=2, num_members=3, priority=None
|
|
1154
|
+
model='ifs_ens', attempts=2, num_members=3, priority=None,
|
|
1155
|
+
decomp_model='dirindex'):
|
|
1001
1156
|
"""
|
|
1002
1157
|
Get solar resource forecasts for one or several sites using a subset of
|
|
1003
1158
|
ensemble members. Use `get_solar_forecast_ensemble` for all ensemble
|
|
@@ -1048,6 +1203,9 @@ def get_solar_forecast_ensemble_subset(
|
|
|
1048
1203
|
priority, or string for a single source. See Herbie docs.
|
|
1049
1204
|
Typical values would be 'aws' or 'google'.
|
|
1050
1205
|
|
|
1206
|
+
decomp_model : string, optional
|
|
1207
|
+
Decomposition model to use. Options include 'dirindex' and 'erbs'.
|
|
1208
|
+
|
|
1051
1209
|
Returns
|
|
1052
1210
|
-------
|
|
1053
1211
|
data : pandas.DataFrane
|
|
@@ -1065,11 +1223,14 @@ def get_solar_forecast_ensemble_subset(
|
|
|
1065
1223
|
"""
|
|
1066
1224
|
|
|
1067
1225
|
# set clear sky model. could be an input variable at some point
|
|
1068
|
-
model_cs = 'simplified_solis'
|
|
1226
|
+
# model_cs = 'simplified_solis'
|
|
1069
1227
|
model_cs_kwargs = {
|
|
1070
1228
|
'aod700': 0.05,
|
|
1071
1229
|
'precipitable_water': 0.5,
|
|
1072
1230
|
}
|
|
1231
|
+
# minimum cosine of zenith, same default used in pvlib.irradiance
|
|
1232
|
+
# functions. Could be an input variable at some point
|
|
1233
|
+
min_cos_zenith = 0.065
|
|
1073
1234
|
|
|
1074
1235
|
# check model
|
|
1075
1236
|
if model.casefold() != ('ifs_ens').casefold():
|
|
@@ -1170,7 +1331,18 @@ def get_solar_forecast_ensemble_subset(
|
|
|
1170
1331
|
end=df.index[-1],
|
|
1171
1332
|
freq='1min',
|
|
1172
1333
|
tz='UTC')
|
|
1173
|
-
|
|
1334
|
+
|
|
1335
|
+
# calculate clear sky ghi with pvlib
|
|
1336
|
+
sp = pvlib.solarposition.ephemeris(
|
|
1337
|
+
times,
|
|
1338
|
+
loc.latitude,
|
|
1339
|
+
loc.longitude
|
|
1340
|
+
)
|
|
1341
|
+
apparent_elevation = sp['apparent_elevation']
|
|
1342
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
1343
|
+
apparent_elevation,
|
|
1344
|
+
**model_cs_kwargs
|
|
1345
|
+
)
|
|
1174
1346
|
|
|
1175
1347
|
# calculate average CS ghi over the intervals from the forecast
|
|
1176
1348
|
# based on list comprehension example in
|
|
@@ -1222,7 +1394,18 @@ def get_solar_forecast_ensemble_subset(
|
|
|
1222
1394
|
end=df.index[-1]-pd.Timedelta('30m'),
|
|
1223
1395
|
freq='60min',
|
|
1224
1396
|
tz='UTC')
|
|
1225
|
-
|
|
1397
|
+
|
|
1398
|
+
# calculate clear sky ghi with pvlib
|
|
1399
|
+
sp = pvlib.solarposition.ephemeris(
|
|
1400
|
+
times,
|
|
1401
|
+
loc.latitude,
|
|
1402
|
+
loc.longitude
|
|
1403
|
+
)
|
|
1404
|
+
apparent_elevation = sp['apparent_elevation']
|
|
1405
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
1406
|
+
apparent_elevation,
|
|
1407
|
+
**model_cs_kwargs
|
|
1408
|
+
)
|
|
1226
1409
|
|
|
1227
1410
|
# calculate ghi from clear sky and backfilled forecasted clear sky
|
|
1228
1411
|
# index
|
|
@@ -1231,15 +1414,31 @@ def get_solar_forecast_ensemble_subset(
|
|
|
1231
1414
|
# clip to avoid occasional small negative ghi in GEFS, see GH #35
|
|
1232
1415
|
df_60min['ghi'] = df_60min['ghi'].clip(lower=0)
|
|
1233
1416
|
|
|
1234
|
-
#
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1417
|
+
# decompose
|
|
1418
|
+
if decomp_model == 'erbs':
|
|
1419
|
+
out_erbs = pvlib.irradiance.erbs(
|
|
1420
|
+
df_60min['ghi'],
|
|
1421
|
+
sp['zenith'],
|
|
1422
|
+
df_60min.index,
|
|
1423
|
+
)
|
|
1424
|
+
df_60min['dni'] = out_erbs['dni']
|
|
1425
|
+
df_60min['dhi'] = out_erbs['dhi']
|
|
1426
|
+
elif decomp_model == 'dirindex':
|
|
1427
|
+
cs_decomp = cs # could later use a different cs model...
|
|
1428
|
+
decomp = pvlib.irradiance.dirindex(
|
|
1429
|
+
ghi=df_60min['ghi'],
|
|
1430
|
+
ghi_clear=cs_decomp['ghi'],
|
|
1431
|
+
dni_clear=cs_decomp['dni'],
|
|
1432
|
+
zenith=sp['zenith'],
|
|
1433
|
+
times=times,
|
|
1434
|
+
)
|
|
1435
|
+
df_60min['dni'] = decomp.fillna(0)
|
|
1436
|
+
cos_zenith = np.maximum(
|
|
1437
|
+
np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
1438
|
+
min_cos_zenith
|
|
1439
|
+
)
|
|
1440
|
+
df_60min['dhi'] = (df_60min['ghi'] -
|
|
1441
|
+
(df_60min['dni'] * cos_zenith))
|
|
1243
1442
|
|
|
1244
1443
|
# add clearsky ghi
|
|
1245
1444
|
df_60min['ghi_clear'] = df_60min['ghi'] / df_60min['ghi_csi']
|
|
@@ -1368,7 +1567,8 @@ def get_solar_forecast_ensemble_subset(
|
|
|
1368
1567
|
def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
1369
1568
|
lead_time_to_start=0, model='ifs_ens',
|
|
1370
1569
|
attempts=2, priority=None,
|
|
1371
|
-
get_ens_temp=False, get_ens_wind=False
|
|
1570
|
+
get_ens_temp=False, get_ens_wind=False,
|
|
1571
|
+
decomp_model='dirindex'):
|
|
1372
1572
|
"""
|
|
1373
1573
|
Get solar resource forecasts for one or several sites using all ensemble
|
|
1374
1574
|
members. Using `get_solar_forecast_ensemble_subset` may be fast for a
|
|
@@ -1433,6 +1633,9 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
1433
1633
|
Get wind speed from each ensemble member if `True`. Otherwise, if
|
|
1434
1634
|
`False` (default), wind speed is a generic 2 m/s value to save time.
|
|
1435
1635
|
|
|
1636
|
+
decomp_model : string, optional
|
|
1637
|
+
Decomposition model to use. Options include 'dirindex' and 'erbs'.
|
|
1638
|
+
|
|
1436
1639
|
Returns
|
|
1437
1640
|
-------
|
|
1438
1641
|
data : pandas.DataFrane
|
|
@@ -1450,11 +1653,14 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
1450
1653
|
"""
|
|
1451
1654
|
|
|
1452
1655
|
# set clear sky model. could be an input variable at some point
|
|
1453
|
-
model_cs = 'simplified_solis'
|
|
1656
|
+
# model_cs = 'simplified_solis'
|
|
1454
1657
|
model_cs_kwargs = {
|
|
1455
1658
|
'aod700': 0.05,
|
|
1456
1659
|
'precipitable_water': 0.5,
|
|
1457
1660
|
}
|
|
1661
|
+
# minimum cosine of zenith, same default used in pvlib.irradiance
|
|
1662
|
+
# functions. Could be an input variable at some point
|
|
1663
|
+
min_cos_zenith = 0.065
|
|
1458
1664
|
|
|
1459
1665
|
# check model
|
|
1460
1666
|
if (
|
|
@@ -1640,34 +1846,68 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
1640
1846
|
# work through sites (points) and members
|
|
1641
1847
|
member_list = df_temp['number'].unique()
|
|
1642
1848
|
dfs = []
|
|
1643
|
-
for
|
|
1644
|
-
for
|
|
1645
|
-
|
|
1646
|
-
|
|
1849
|
+
for point in range(num_sites):
|
|
1850
|
+
# set of timestamps for solar position and clear sky
|
|
1851
|
+
df = df_temp[(df_temp['point'] == point) &
|
|
1852
|
+
(df_temp['number'] == member_list[0])].copy()
|
|
1647
1853
|
|
|
1648
|
-
|
|
1854
|
+
# location
|
|
1855
|
+
loc = pvlib.location.Location(
|
|
1649
1856
|
latitude=latitude[point],
|
|
1650
1857
|
longitude=longitude[point],
|
|
1651
1858
|
tz=df.index.tz
|
|
1652
1859
|
)
|
|
1653
1860
|
|
|
1861
|
+
# make 1min interval clear sky data covering our time range
|
|
1862
|
+
times_1min = pd.date_range(
|
|
1863
|
+
start=df.index[0],
|
|
1864
|
+
end=df.index[-1],
|
|
1865
|
+
freq='1min',
|
|
1866
|
+
tz='UTC')
|
|
1867
|
+
|
|
1868
|
+
# calculate clear sky ghi with pvlib
|
|
1869
|
+
sp_1min = pvlib.solarposition.ephemeris(
|
|
1870
|
+
times_1min,
|
|
1871
|
+
loc.latitude,
|
|
1872
|
+
loc.longitude
|
|
1873
|
+
)
|
|
1874
|
+
apparent_elevation_1min = sp_1min['apparent_elevation']
|
|
1875
|
+
cs_1min = pvlib.clearsky.simplified_solis(
|
|
1876
|
+
apparent_elevation_1min,
|
|
1877
|
+
**model_cs_kwargs
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1880
|
+
# make 60min interval clear sky, centered at bottom of the hour
|
|
1881
|
+
times_60min = pd.date_range(
|
|
1882
|
+
start=df.index[0]+pd.Timedelta('30m'),
|
|
1883
|
+
end=df.index[-1]-pd.Timedelta('30m'),
|
|
1884
|
+
freq='60min',
|
|
1885
|
+
tz='UTC')
|
|
1886
|
+
|
|
1887
|
+
# calculate clear sky ghi with pvlib
|
|
1888
|
+
sp_60min = pvlib.solarposition.ephemeris(
|
|
1889
|
+
times_60min,
|
|
1890
|
+
loc.latitude,
|
|
1891
|
+
loc.longitude
|
|
1892
|
+
)
|
|
1893
|
+
apparent_elevation_60min = sp_60min['apparent_elevation']
|
|
1894
|
+
cs_60min = pvlib.clearsky.simplified_solis(
|
|
1895
|
+
apparent_elevation_60min,
|
|
1896
|
+
**model_cs_kwargs
|
|
1897
|
+
)
|
|
1898
|
+
|
|
1899
|
+
for number in member_list:
|
|
1900
|
+
df = df_temp[(df_temp['point'] == point) &
|
|
1901
|
+
(df_temp['number'] == number)].copy()
|
|
1902
|
+
|
|
1654
1903
|
# convert cumulative J/m^s to average W/m^2
|
|
1655
1904
|
df['ghi'] = (df['sdswrf'].diff() /
|
|
1656
1905
|
df.index.diff().seconds.values)
|
|
1657
1906
|
|
|
1658
|
-
# make 1min interval clear sky data covering our time range
|
|
1659
|
-
times = pd.date_range(
|
|
1660
|
-
start=df.index[0],
|
|
1661
|
-
end=df.index[-1],
|
|
1662
|
-
freq='1min',
|
|
1663
|
-
tz='UTC')
|
|
1664
|
-
cs = loc.get_clearsky(times, model=model_cs,
|
|
1665
|
-
**model_cs_kwargs)
|
|
1666
|
-
|
|
1667
1907
|
# calculate average CS ghi over the intervals from the forecast
|
|
1668
1908
|
# based on list comprehension example in
|
|
1669
1909
|
# https://stackoverflow.com/a/55724134/27574852
|
|
1670
|
-
ghi =
|
|
1910
|
+
ghi = cs_1min['ghi']
|
|
1671
1911
|
dates = df.index
|
|
1672
1912
|
ghi_clear = [
|
|
1673
1913
|
ghi.loc[(ghi.index > dates[i]) & (ghi.index <= dates[i+1])]
|
|
@@ -1722,29 +1962,35 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
1722
1962
|
direction='forward'
|
|
1723
1963
|
).set_index('valid_time')
|
|
1724
1964
|
|
|
1725
|
-
# make 60min interval clear sky, centered at bottom of the hour
|
|
1726
|
-
times = pd.date_range(
|
|
1727
|
-
start=df.index[0]+pd.Timedelta('30m'),
|
|
1728
|
-
end=df.index[-1]-pd.Timedelta('30m'),
|
|
1729
|
-
freq='60min',
|
|
1730
|
-
tz='UTC')
|
|
1731
|
-
cs = loc.get_clearsky(times, model=model_cs,
|
|
1732
|
-
**model_cs_kwargs)
|
|
1733
|
-
|
|
1734
1965
|
# calculate ghi from clear sky and backfilled forecasted clear
|
|
1735
1966
|
# sky index
|
|
1736
|
-
df_60min['ghi'] =
|
|
1737
|
-
|
|
1738
|
-
#
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1967
|
+
df_60min['ghi'] = cs_60min['ghi'] * df_60min['ghi_csi']
|
|
1968
|
+
|
|
1969
|
+
# decompose
|
|
1970
|
+
if decomp_model == 'erbs':
|
|
1971
|
+
out_erbs = pvlib.irradiance.erbs(
|
|
1972
|
+
df_60min['ghi'],
|
|
1973
|
+
sp_60min['zenith'],
|
|
1974
|
+
df_60min.index,
|
|
1975
|
+
)
|
|
1976
|
+
df_60min['dni'] = out_erbs['dni']
|
|
1977
|
+
df_60min['dhi'] = out_erbs['dhi']
|
|
1978
|
+
elif decomp_model == 'dirindex':
|
|
1979
|
+
cs_decomp = cs_60min # could later use another cs model
|
|
1980
|
+
decomp = pvlib.irradiance.dirindex(
|
|
1981
|
+
ghi=df_60min['ghi'],
|
|
1982
|
+
ghi_clear=cs_decomp['ghi'],
|
|
1983
|
+
dni_clear=cs_decomp['dni'],
|
|
1984
|
+
zenith=sp_60min['zenith'],
|
|
1985
|
+
times=times_60min,
|
|
1986
|
+
)
|
|
1987
|
+
df_60min['dni'] = decomp.fillna(0)
|
|
1988
|
+
cos_zenith = np.maximum(
|
|
1989
|
+
np.cos(np.deg2rad(sp_60min['apparent_zenith'])),
|
|
1990
|
+
min_cos_zenith
|
|
1991
|
+
)
|
|
1992
|
+
df_60min['dhi'] = (df_60min['ghi'] -
|
|
1993
|
+
(df_60min['dni'] * cos_zenith))
|
|
1748
1994
|
|
|
1749
1995
|
# add clearsky ghi
|
|
1750
1996
|
df_60min['ghi_clear'] = df_60min['ghi'] / df_60min['ghi_csi']
|
|
@@ -2026,8 +2272,17 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
2026
2272
|
freq='1min',
|
|
2027
2273
|
tz='UTC')
|
|
2028
2274
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2275
|
+
# calculate clear sky ghi with pvlib
|
|
2276
|
+
sp = pvlib.solarposition.ephemeris(
|
|
2277
|
+
times,
|
|
2278
|
+
loc.latitude,
|
|
2279
|
+
loc.longitude
|
|
2280
|
+
)
|
|
2281
|
+
apparent_elevation = sp['apparent_elevation']
|
|
2282
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
2283
|
+
apparent_elevation,
|
|
2284
|
+
**model_cs_kwargs
|
|
2285
|
+
)
|
|
2031
2286
|
|
|
2032
2287
|
# calculate average CS ghi over the intervals from the forecast
|
|
2033
2288
|
# based on list comprehension example in
|
|
@@ -2093,8 +2348,18 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
2093
2348
|
end=df.index[-1]-pd.Timedelta('30m'),
|
|
2094
2349
|
freq='60min',
|
|
2095
2350
|
tz='UTC')
|
|
2096
|
-
|
|
2097
|
-
|
|
2351
|
+
|
|
2352
|
+
# calculate clear sky ghi with pvlib
|
|
2353
|
+
sp = pvlib.solarposition.ephemeris(
|
|
2354
|
+
times,
|
|
2355
|
+
loc.latitude,
|
|
2356
|
+
loc.longitude
|
|
2357
|
+
)
|
|
2358
|
+
apparent_elevation = sp['apparent_elevation']
|
|
2359
|
+
cs = pvlib.clearsky.simplified_solis(
|
|
2360
|
+
apparent_elevation,
|
|
2361
|
+
**model_cs_kwargs
|
|
2362
|
+
)
|
|
2098
2363
|
|
|
2099
2364
|
# calculate ghi from clear sky and backfilled forecasted clear
|
|
2100
2365
|
# sky index
|
|
@@ -2103,16 +2368,31 @@ def get_solar_forecast_ensemble(latitude, longitude, init_date, run_length,
|
|
|
2103
2368
|
# clip to avoid occasional small negative ghi in GEFS, GH #35
|
|
2104
2369
|
df_60min['ghi'] = df_60min['ghi'].clip(lower=0)
|
|
2105
2370
|
|
|
2106
|
-
#
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2371
|
+
# decompose
|
|
2372
|
+
if decomp_model == 'erbs':
|
|
2373
|
+
out_erbs = pvlib.irradiance.erbs(
|
|
2374
|
+
df_60min['ghi'],
|
|
2375
|
+
sp['zenith'],
|
|
2376
|
+
df_60min.index,
|
|
2377
|
+
)
|
|
2378
|
+
df_60min['dni'] = out_erbs['dni']
|
|
2379
|
+
df_60min['dhi'] = out_erbs['dhi']
|
|
2380
|
+
elif decomp_model == 'dirindex':
|
|
2381
|
+
cs_decomp = cs # could later use a different cs model...
|
|
2382
|
+
decomp = pvlib.irradiance.dirindex(
|
|
2383
|
+
ghi=df_60min['ghi'],
|
|
2384
|
+
ghi_clear=cs_decomp['ghi'],
|
|
2385
|
+
dni_clear=cs_decomp['dni'],
|
|
2386
|
+
zenith=sp['zenith'],
|
|
2387
|
+
times=times,
|
|
2388
|
+
)
|
|
2389
|
+
df_60min['dni'] = decomp.fillna(0)
|
|
2390
|
+
cos_zenith = np.maximum(
|
|
2391
|
+
np.cos(np.deg2rad(sp['apparent_zenith'])),
|
|
2392
|
+
min_cos_zenith
|
|
2393
|
+
)
|
|
2394
|
+
df_60min['dhi'] = (df_60min['ghi'] -
|
|
2395
|
+
(df_60min['dni'] * cos_zenith))
|
|
2116
2396
|
|
|
2117
2397
|
# add clearsky ghi
|
|
2118
2398
|
df_60min['ghi_clear'] = df_60min['ghi'] / df_60min['ghi_csi']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hefty
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Some (relatively) lightweight short-term energy forecasting tools for solar, wind, and load.
|
|
5
5
|
Author: Will Hobbs
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -29,13 +29,13 @@ This repository currently includes solar and wind tools, but may expand one day
|
|
|
29
29
|
- NOAA HRRR
|
|
30
30
|
- ECMWF IFS (open data version)
|
|
31
31
|
- ECMWF AIFS
|
|
32
|
-
- ECMWF CAMS
|
|
32
|
+
- ECMWF CAMS version of IFS* (a.k.a. "IFS-COMPO")
|
|
33
33
|
|
|
34
34
|
A detailed pvlib-based PV weather-to-power model is included in [pv_model.py](src/hefty/pv_model.py), `model_pv_power()`.
|
|
35
35
|
|
|
36
36
|
The [custom.py](src/hefty/custom.py) module is intended to help with getting forecasts of "custom" weather parameters, not necessarily specific to wind or solar, which migh be useful for load forecasting.
|
|
37
37
|
|
|
38
|
-
_*CAMS IFS is only available via `hefty.solar.get_solar_forecast()`, and it requires `cdsapi` to be installed and the user needs an API key (see https://ads.atmosphere.copernicus.eu/how-to-api)._
|
|
38
|
+
_*CAMS IFS is only available via `hefty.solar.get_solar_forecast()`, and it requires `cdsapi` to be installed and the user needs an API key (see https://ads.atmosphere.copernicus.eu/how-to-api and the [CAMS example notebook](examples/cams_example.ipynb))._
|
|
39
39
|
|
|
40
40
|
## Contents
|
|
41
41
|
- [Installation](#installation) - getting started
|
|
@@ -130,7 +130,8 @@ The [examples](examples) folder contains several example Jupyter notebooks.
|
|
|
130
130
|
- [more_solar_examples.ipynb](examples/more_solar_examples.ipynb): Expanded examples, including multiple sites and multiple forecast initialization times.
|
|
131
131
|
- [cams_example.ipynb](examples/cams_example.ipynb): Solar resource forecasting with CAMS IFS.
|
|
132
132
|
- [ensemble_example.ipynb](examples/ensemble_example.ipynb): Examples of multi-member ensembles with IFS, AIFS, and GEFS.
|
|
133
|
-
- [wind_example.ipynb](examples/wind_example.ipynb): A basic wind resource forecast example using a few models, including converting the data to a format compatible with `windpowerlib
|
|
133
|
+
- [wind_example.ipynb](examples/wind_example.ipynb): A basic wind resource forecast example using a few models, including converting the data to a format compatible with `windpowerlib`*.
|
|
134
|
+
- [erbs_vs_dirindex.ipynb](examples/erbs_vs_dirindex.ipynb): A comparison of Erbs and DIRINDEX irradiance decomposition models using SURFRAD measurements.
|
|
134
135
|
|
|
135
136
|
\* https://github.com/wind-python/windpowerlib/
|
|
136
137
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|