Skip to content

Commit 0c66e09

Browse files
adriessecwhansekandersolar
authored
Implement C1 continuous version of the Erbs diffuse-fraction/decomposition model (pvlib#1834)
* First draft of new erbs_driesse function together with test. * Update reference and whatsnew for docs. Minor improvements. * Update docs/sphinx/source/whatsnew/v0.10.2.rst Co-authored-by: Cliff Hansen <[email protected]> * Update pvlib/irradiance.py Co-authored-by: Cliff Hansen <[email protected]> * Deal with minor grievances from Kevin and Flake8. * Delete "new" and update "whatsnew". --------- Co-authored-by: Cliff Hansen <[email protected]> Co-authored-by: Kevin Anderson <[email protected]>
1 parent 2d04aeb commit 0c66e09

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

docs/sphinx/source/reference/irradiance/decomposition.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ DNI estimation models
1212
irradiance.dirint
1313
irradiance.dirindex
1414
irradiance.erbs
15+
irradiance.erbs_driesse
1516
irradiance.orgill_hollands
1617
irradiance.boland
1718
irradiance.campbell_norman

docs/sphinx/source/whatsnew/v0.10.2.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ Enhancements
1515
:py:func:`pvlib.iotools.get_pvgis_hourly`, :py:func:`pvlib.iotools.get_cams`,
1616
:py:func:`pvlib.iotools.get_bsrn`, and :py:func:`pvlib.iotools.read_midc_raw_data_from_nrel`.
1717
(:pull:`1800`)
18-
* Added option to infer threshold values for
18+
* Added option to infer threshold values for
1919
:py:func:`pvlib.clearsky.detect_clearsky` (:issue:`1808`, :pull:`1784`)
20+
* Added a continuous version of the Erbs diffuse-fraction/decomposition model.
21+
:py:func:`pvlib.irradiance.erbs_driesse` (:issue:`1755`, :pull:`1834`)
2022

2123

2224
Bug fixes
@@ -52,5 +54,6 @@ Contributors
5254
* Abigail Jones (:ghuser:`ajonesr`)
5355
* Taos Transue (:ghuser:`reepoi`)
5456
* NativeSci (:ghuser:`nativesci`)
57+
* Anton Driesse (:ghuser:`adriesse`)
5558
* Lukas Grossar (:ghuser:`tongpu`)
5659
* Areeba Turabi (:ghuser:`aturabi`)

pvlib/irradiance.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,136 @@ def erbs(ghi, zenith, datetime_or_doy, min_cos_zenith=0.065, max_zenith=87):
22672267
return data
22682268

22692269

2270+
def erbs_driesse(ghi, zenith, datetime_or_doy=None, dni_extra=None,
2271+
min_cos_zenith=0.065, max_zenith=87):
2272+
r"""
2273+
Estimate DNI and DHI from GHI using the continuous Erbs-Driesse model.
2274+
2275+
The Erbs-Driesse model [1]_ is a reformulation of the original Erbs
2276+
model [2]_ that provides continuity of the function and its first
2277+
derivative at the two transition points.
2278+
2279+
.. math::
2280+
2281+
DHI = DF \times GHI
2282+
2283+
DNI is then estimated as
2284+
2285+
.. math::
2286+
2287+
DNI = (GHI - DHI)/\cos(Z)
2288+
2289+
where Z is the zenith angle.
2290+
2291+
Parameters
2292+
----------
2293+
ghi: numeric
2294+
Global horizontal irradiance in W/m^2.
2295+
zenith: numeric
2296+
True (not refraction-corrected) zenith angles in decimal degrees.
2297+
datetime_or_doy : int, float, array, pd.DatetimeIndex, default None
2298+
Day of year or array of days of year e.g.
2299+
pd.DatetimeIndex.dayofyear, or pd.DatetimeIndex.
2300+
Either datetime_or_doy or dni_extra must be provided.
2301+
dni_extra : numeric, default None
2302+
Extraterrestrial normal irradiance.
2303+
dni_extra can be provided if available to avoid recalculating it
2304+
inside this function. In this case datetime_or_doy is not required.
2305+
min_cos_zenith : numeric, default 0.065
2306+
Minimum value of cos(zenith) to allow when calculating global
2307+
clearness index `kt`. Equivalent to zenith = 86.273 degrees.
2308+
max_zenith : numeric, default 87
2309+
Maximum value of zenith to allow in DNI calculation. DNI will be
2310+
set to 0 for times with zenith values greater than `max_zenith`.
2311+
2312+
Returns
2313+
-------
2314+
data : OrderedDict or DataFrame
2315+
Contains the following keys/columns:
2316+
2317+
* ``dni``: the modeled direct normal irradiance in W/m^2.
2318+
* ``dhi``: the modeled diffuse horizontal irradiance in
2319+
W/m^2.
2320+
* ``kt``: Ratio of global to extraterrestrial irradiance
2321+
on a horizontal plane.
2322+
2323+
Raises
2324+
------
2325+
ValueError
2326+
If neither datetime_or_doy nor dni_extra is provided.
2327+
2328+
Notes
2329+
-----
2330+
The diffuse fraction DHI/GHI of the Erbs-Driesse model deviates from the
2331+
original Erbs model by less than 0.0005.
2332+
2333+
References
2334+
----------
2335+
.. [1] A. Driesse, A. Jensen, R. Perez, A Continuous Form of the Perez
2336+
Diffuse Sky Model for Forward and Reverse Transposition, forthcoming.
2337+
2338+
.. [2] D. G. Erbs, S. A. Klein and J. A. Duffie, Estimation of the
2339+
diffuse radiation fraction for hourly, daily and monthly-average
2340+
global radiation, Solar Energy 28(4), pp 293-302, 1982. Eq. 1
2341+
2342+
See also
2343+
--------
2344+
erbs
2345+
dirint
2346+
disc
2347+
orgill_hollands
2348+
boland
2349+
"""
2350+
# central polynomial coefficients with float64 precision
2351+
p = [+12.26911439571261000,
2352+
-16.47050842469730700,
2353+
+04.24692671521831700,
2354+
-00.11390583806313881,
2355+
+00.94629663357100100]
2356+
2357+
if datetime_or_doy is None and dni_extra is None:
2358+
raise ValueError('Either datetime_or_doy or dni_extra '
2359+
'must be provided.')
2360+
2361+
if dni_extra is None:
2362+
dni_extra = get_extra_radiation(datetime_or_doy)
2363+
2364+
# negative ghi should not reach this point, but just in case
2365+
ghi = np.maximum(0, ghi)
2366+
2367+
kt = clearness_index(ghi, zenith, dni_extra, min_cos_zenith=min_cos_zenith,
2368+
max_clearness_index=1)
2369+
2370+
# For all Kt, set the default diffuse fraction
2371+
df = 1 - 0.09 * kt
2372+
2373+
# For Kt > 0.216, update the diffuse fraction
2374+
df = np.where(kt > 0.216, np.polyval(p, kt), df)
2375+
2376+
# For Kt > 0.792, update the diffuse fraction again
2377+
df = np.where(kt > 0.792, 0.165, df)
2378+
2379+
dhi = df * ghi
2380+
2381+
dni = (ghi - dhi) / tools.cosd(zenith)
2382+
bad_values = (zenith > max_zenith) | (ghi < 0) | (dni < 0)
2383+
dni = np.where(bad_values, 0, dni)
2384+
# ensure that closure relationship remains valid
2385+
dhi = np.where(bad_values, ghi, dhi)
2386+
2387+
data = OrderedDict()
2388+
data['dni'] = dni
2389+
data['dhi'] = dhi
2390+
data['kt'] = kt
2391+
2392+
if isinstance(datetime_or_doy, pd.DatetimeIndex):
2393+
data = pd.DataFrame(data, index=datetime_or_doy)
2394+
elif isinstance(ghi, pd.Series):
2395+
data = pd.DataFrame(data, index=ghi.index)
2396+
2397+
return data
2398+
2399+
22702400
def orgill_hollands(ghi, zenith, datetime_or_doy, dni_extra=None,
22712401
min_cos_zenith=0.065, max_zenith=87):
22722402
"""Estimate DNI and DHI from GHI using the Orgill and Hollands model.

pvlib/tests/test_irradiance.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,34 @@ def test_erbs():
805805
assert_frame_equal(np.round(out, 0), np.round(expected, 0))
806806

807807

808+
def test_erbs_driesse():
809+
index = pd.DatetimeIndex(['20190101']*3 + ['20190620'])
810+
ghi = pd.Series([0, 50, 1000, 1000], index=index)
811+
zenith = pd.Series([120, 85, 10, 10], index=index)
812+
# expected values are the same as for erbs original test
813+
expected = pd.DataFrame(np.array(
814+
[[0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
815+
[9.67192672e+01, 4.15703604e+01, 4.05723511e-01],
816+
[7.94205651e+02, 2.17860117e+02, 7.18132729e-01],
817+
[8.42001578e+02, 1.70790318e+02, 7.68214312e-01]]),
818+
columns=['dni', 'dhi', 'kt'], index=index)
819+
820+
out = irradiance.erbs_driesse(ghi, zenith, index)
821+
822+
assert_frame_equal(np.round(out, 0), np.round(expected, 0))
823+
824+
# test with the new optional dni_extra argument
825+
dni_extra = irradiance.get_extra_radiation(index)
826+
827+
out = irradiance.erbs_driesse(ghi, zenith, dni_extra=dni_extra)
828+
829+
assert_frame_equal(np.round(out, 0), np.round(expected, 0))
830+
831+
# test for required inputs
832+
with pytest.raises(ValueError):
833+
irradiance.erbs_driesse(ghi, zenith)
834+
835+
808836
def test_boland():
809837
index = pd.DatetimeIndex(['20190101']*3 + ['20190620'])
810838
ghi = pd.Series([0, 50, 1000, 1000], index=index)

0 commit comments

Comments
 (0)