Skip to content

Commit a26d8e1

Browse files
wholmgrencwhanse
authored andcommitted
Add option to control airmass/perez enhancement factor in clearsky.ineichen (#459)
* add perez_enhancement option * pass kwargs through location.get_clearsky to ineichen * hard code test values to avoid test failures * refactor test_irradiance.py input data into fixtures * refactor location tests to avoid tight cs dependence * mock/refactor all tests except losses * fix typo in test * update whatsnew * update test ineichen values for perez_enhancement=False * update test_clearsky values * add test for perez enhancement * update tests for py2.7-3.5 * add requires_scipy for min env test * update comment to point to correct refs
1 parent f1eff39 commit a26d8e1

File tree

7 files changed

+343
-274
lines changed

7 files changed

+343
-274
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ API Changes
1111
support from PVSystem.pvwatts_losses. Enables custom losses specification
1212
in ModelChain calculations. (:issue:`484`)
1313
* removed irradiance parameter from ModelChain.run_model and ModelChain.prepare_inputs
14+
* Add ``perez_enhancement`` keyword argument to clearsky.ineichen to control
15+
whether or not the "perez enhancement factor" is applied. The enhancement
16+
factor was always applied until now. Now it is turned off by default. The
17+
enhancement factor can yield unphysical results, especially for latitudes
18+
closer to the poles and especially in the winter months. It may yield
19+
improved results under other conditions. (:issue:`435`)
1420

1521

1622
Enhancements
@@ -81,8 +87,11 @@ Documentation
8187
Testing
8288
~~~~~~~
8389
* Add pytest-mock dependency
84-
* Use pytest-mock to ensure that PVSystem methods call corresponding functions
85-
correctly. Removes implicit dependence on precise return values of functions
90+
* Use pytest-mock to ensure that PVSystem and ModelChain methods call
91+
corresponding functions correctly. Removes implicit dependence on precise
92+
return values of some function/methods. (:issue:`394`)
93+
* Additional test refactoring to limit test result dependence to a single
94+
function per test. (:issue:`394`)
8695
* Use pytest-mock to ensure that ModelChain DC model is set up correctly.
8796

8897

pvlib/clearsky.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717

1818
def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
19-
altitude=0, dni_extra=1364.):
19+
altitude=0, dni_extra=1364., perez_enhancement=False):
2020
'''
2121
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model.
2222
@@ -47,6 +47,12 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
4747
Extraterrestrial irradiance. The units of ``dni_extra``
4848
determine the units of the output.
4949
50+
perez_enhancement : bool, default False
51+
Controls if the Perez enhancement factor should be applied.
52+
Setting to True may produce spurious results for times when
53+
the Sun is near the horizon and the airmass is high.
54+
See https://github.com/pvlib/pvlib-python/issues/435
55+
5056
Returns
5157
-------
5258
clearsky : DataFrame (if Series input) or OrderedDict of arrays
@@ -79,28 +85,9 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
7985
ISES Solar World Congress, June 2003. Goteborg, Sweden.
8086
'''
8187

82-
# Dan's note on the TL correction: By my reading of the publication
83-
# on pages 151-157, Ineichen and Perez introduce (among other
84-
# things) three things. 1) Beam model in eqn. 8, 2) new turbidity
85-
# factor in eqn 9 and appendix A, and 3) Global horizontal model in
86-
# eqn. 11. They do NOT appear to use the new turbidity factor (item
87-
# 2 above) in either the beam or GHI models. The phrasing of
88-
# appendix A seems as if there are two separate corrections, the
89-
# first correction is used to correct the beam/GHI models, and the
90-
# second correction is used to correct the revised turibidity
91-
# factor. In my estimation, there is no need to correct the
92-
# turbidity factor used in the beam/GHI models.
93-
94-
# Create the corrected TL for TL < 2
95-
# TLcorr = TL;
96-
# TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);
97-
98-
# This equation is found in Solar Energy 73, pg 311. Full ref: Perez
99-
# et. al., Vol. 73, pp. 307-317 (2002). It is slightly different
100-
# than the equation given in Solar Energy 73, pg 156. We used the
101-
# equation from pg 311 because of the existence of known typos in
102-
# the pg 156 publication (notably the fh2-(TL-1) should be fh2 *
103-
# (TL-1)).
88+
# ghi is calculated using either the equations in [1] by setting
89+
# perez_enhancement=False (default behavior) or using the model
90+
# in [2] by setting perez_enhancement=True.
10491

10592
# The NaN handling is a little subtle. The AM input is likely to
10693
# have NaNs that we'll want to map to 0s in the output. However, we
@@ -119,8 +106,12 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
119106
cg1 = 5.09e-05 * altitude + 0.868
120107
cg2 = 3.92e-05 * altitude + 0.0387
121108

122-
ghi = (np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1))) *
123-
np.exp(0.01*airmass_absolute**1.8))
109+
ghi = np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1)))
110+
111+
# https://github.com/pvlib/pvlib-python/issues/435
112+
if perez_enhancement:
113+
ghi *= np.exp(0.01*airmass_absolute**1.8)
114+
124115
# use fmax to map airmass nans to 0s. multiply and divide by tl to
125116
# reinsert tl nans
126117
ghi = cg1 * dni_extra * cos_zenith * tl / tl * np.fmax(ghi, 0)

pvlib/location.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def get_clearsky(self, times, model='ineichen', solar_position=None,
218218

219219
cs = clearsky.ineichen(apparent_zenith, airmass_absolute,
220220
linke_turbidity, altitude=self.altitude,
221-
dni_extra=dni_extra)
221+
dni_extra=dni_extra, **kwargs)
222222
elif model == 'haurwitz':
223223
cs = clearsky.haurwitz(apparent_zenith)
224224
elif model == 'simplified_solis':

pvlib/test/test_clearsky.py

Lines changed: 88 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections import OrderedDict
44

55
import numpy as np
6+
from numpy import nan
67
import pandas as pd
78
import pytz
89

@@ -20,35 +21,66 @@
2021

2122

2223
def test_ineichen_series():
23-
tus = Location(32.2, -111, 'US/Arizona', 700)
24-
times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h')
25-
times_localized = times.tz_localize(tus.tz)
26-
ephem_data = solarposition.get_solarposition(times_localized, tus.latitude,
27-
tus.longitude)
28-
am = atmosphere.relativeairmass(ephem_data['apparent_zenith'])
29-
am = atmosphere.absoluteairmass(am, atmosphere.alt2pres(tus.altitude))
24+
times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h',
25+
tz='America/Phoenix')
26+
apparent_zenith = pd.Series(np.array(
27+
[124.0390863, 113.38779941, 82.85457044, 46.0467599, 10.56413562,
28+
34.86074109, 72.41687122, 105.69538659, 124.05614124]),
29+
index=times)
30+
am = pd.Series(np.array(
31+
[nan, nan, 6.97935524, 1.32355476, 0.93527685,
32+
1.12008114, 3.01614096, nan, nan]),
33+
index=times)
3034
expected = pd.DataFrame(np.
31-
array([[ 0. , 0. , 0. ],
32-
[ 0. , 0. , 0. ],
33-
[ 91.12492792, 321.16092181, 51.17628184],
34-
[ 716.46580533, 888.90147035, 99.5050056 ],
35-
[ 1053.42066043, 953.24925854, 116.32868969],
36-
[ 863.54692781, 922.06124712, 106.95536561],
37-
[ 271.06382274, 655.44925241, 73.05968071],
38-
[ 0. , 0. , 0. ],
39-
[ 0. , 0. , 0. ]]),
35+
array([[ 0. , 0. , 0. ],
36+
[ 0. , 0. , 0. ],
37+
[ 65.49426624, 321.16092181, 25.54562017],
38+
[ 704.6968125 , 888.90147035, 87.73601277],
39+
[1044.1230677 , 953.24925854, 107.03109696],
40+
[ 853.02065704, 922.06124712, 96.42909484],
41+
[ 251.99427693, 655.44925241, 53.9901349 ],
42+
[ 0. , 0. , 0. ],
43+
[ 0. , 0. , 0. ]]),
4044
columns=['ghi', 'dni', 'dhi'],
41-
index=times_localized)
45+
index=times)
4246

43-
out = clearsky.ineichen(ephem_data['apparent_zenith'], am, 3)
47+
out = clearsky.ineichen(apparent_zenith, am, 3)
48+
assert_frame_equal(expected, out)
49+
50+
51+
def test_ineichen_series_perez_enhancement():
52+
times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h',
53+
tz='America/Phoenix')
54+
apparent_zenith = pd.Series(np.array(
55+
[124.0390863, 113.38779941, 82.85457044, 46.0467599, 10.56413562,
56+
34.86074109, 72.41687122, 105.69538659, 124.05614124]),
57+
index=times)
58+
am = pd.Series(np.array(
59+
[nan, nan, 6.97935524, 1.32355476, 0.93527685,
60+
1.12008114, 3.01614096, nan, nan]),
61+
index=times)
62+
expected = pd.DataFrame(np.
63+
array([[ 0. , 0. , 0. ],
64+
[ 0. , 0. , 0. ],
65+
[ 91.1249279 , 321.16092171, 51.17628184],
66+
[ 716.46580547, 888.9014706 , 99.50500553],
67+
[1053.42066073, 953.24925905, 116.3286895 ],
68+
[ 863.54692748, 922.06124652, 106.9553658 ],
69+
[ 271.06382275, 655.44925213, 73.05968076],
70+
[ 0. , 0. , 0. ],
71+
[ 0. , 0. , 0. ]]),
72+
columns=['ghi', 'dni', 'dhi'],
73+
index=times)
74+
75+
out = clearsky.ineichen(apparent_zenith, am, 3, perez_enhancement=True)
4476
assert_frame_equal(expected, out)
4577

4678

4779
def test_ineichen_scalar_input():
4880
expected = OrderedDict()
49-
expected['ghi'] = 1048.592893113678
81+
expected['ghi'] = 1038.159219
5082
expected['dni'] = 942.2081860378344
51-
expected['dhi'] = 120.6989665520498
83+
expected['dhi'] = 110.26529293612793
5284

5385
out = clearsky.ineichen(10., 1., 3.)
5486
for k, v in expected.items():
@@ -74,9 +106,9 @@ def test_ineichen_nans():
74106
expected['dni'] = np.full(length, np.nan)
75107
expected['dhi'] = np.full(length, np.nan)
76108

77-
expected['ghi'][length-1] = 1053.205472
78-
expected['dni'][length-1] = 946.352797
79-
expected['dhi'][length-1] = 121.2299
109+
expected['ghi'][length-1] = 1042.72590228
110+
expected['dni'][length-1] = 946.35279683
111+
expected['dhi'][length-1] = 110.75033088
80112

81113
out = clearsky.ineichen(apparent_zenith, airmass_absolute,
82114
linke_turbidity, dni_extra=dni_extra)
@@ -89,43 +121,43 @@ def test_ineichen_arrays():
89121
expected = OrderedDict()
90122

91123
expected['ghi'] = (np.
92-
array([[[ 1106.78342709, 1064.7691287 , 1024.34972343],
93-
[ 847.84529406, 815.66047425, 784.69741345],
94-
[ 192.19092519, 184.89521884, 177.87646277]],
124+
array([[[1095.77074798, 1054.17449885, 1014.15727338],
125+
[ 839.40909243, 807.54451692, 776.88954373],
126+
[ 190.27859353, 183.05548067, 176.10656239]],
95127

96-
[[ 959.12310134, 775.2374976 , 626.60692548],
97-
[ 734.73092205, 593.86637713, 480.00875328],
98-
[ 166.54997871, 134.61857872, 108.80915072]],
128+
[[ 773.49041181, 625.19479557, 505.33080493],
129+
[ 592.52803177, 478.92699901, 387.10585505],
130+
[ 134.31520045, 108.56393694, 87.74977339]],
99131

100-
[[ 1026.15144142, 696.85030591, 473.22483724],
101-
[ 786.0776095 , 533.81830453, 362.51125692],
102-
[ 178.18932781, 121.00678573, 82.17463061]]]))
132+
[[ 545.9968869 , 370.78162375, 251.79449885],
133+
[ 418.25788117, 284.03520249, 192.88577665],
134+
[ 94.81136442, 64.38555328, 43.72365587]]]))
103135

104136
expected['dni'] = (np.
105-
array([[[ 1024.58284359, 942.20818604, 861.11344424],
106-
[ 1024.58284359, 942.20818604, 861.11344424],
107-
[ 1024.58284359, 942.20818604, 861.11344424]],
137+
array([[[1014.38807396, 942.20818604, 861.11344424],
138+
[1014.38807396, 942.20818604, 861.11344424],
139+
[1014.38807396, 942.20818604, 861.11344424]],
108140

109-
[[ 687.61305142, 419.14891162, 255.50098235],
110-
[ 687.61305142, 419.14891162, 255.50098235],
111-
[ 687.61305142, 419.14891162, 255.50098235]],
141+
[[ 687.61305142, 419.14891162, 255.50098235],
142+
[ 687.61305142, 419.14891162, 255.50098235],
143+
[ 687.61305142, 419.14891162, 255.50098235]],
112144

113-
[[ 458.62196014, 186.46177428, 75.80970012],
114-
[ 458.62196014, 186.46177428, 75.80970012],
115-
[ 458.62196014, 186.46177428, 75.80970012]]]))
145+
[[ 458.62196014, 186.46177428, 75.80970012],
146+
[ 458.62196014, 186.46177428, 75.80970012],
147+
[ 458.62196014, 186.46177428, 75.80970012]]]))
116148

117149
expected['dhi'] = (np.
118-
array([[[ 82.20058349, 122.56094266, 163.23627919],
119-
[ 62.96930021, 93.88712907, 125.04624459],
120-
[ 14.27398153, 21.28248435, 28.34568241]],
150+
array([[[ 81.38267402, 111.96631281, 153.04382915],
151+
[ 62.3427452 , 85.77117175, 117.23837487],
152+
[ 14.13195304, 19.44274618, 26.57578203]],
121153

122-
[[ 271.51004993, 356.08858598, 371.10594313],
123-
[ 207.988765 , 272.77968255, 284.28364554],
124-
[ 47.14722539, 61.83413404, 64.44187075]],
154+
[[ 85.87736039, 206.04588395, 249.82982258],
155+
[ 65.78587472, 157.84030442, 191.38074731],
156+
[ 14.91244713, 35.77949226, 43.38249342]],
125157

126-
[[ 567.52948128, 510.38853163, 397.41513712],
127-
[ 434.75280544, 390.98029849, 304.4376574 ],
128-
[ 98.5504602 , 88.62803842, 69.01041434]]]))
158+
[[ 87.37492676, 184.31984947, 175.98479873],
159+
[ 66.93307711, 141.19719644, 134.81217714],
160+
[ 15.17249681, 32.00680597, 30.5594396 ]]]))
129161

130162
apparent_zenith = np.linspace(0, 80, 3)
131163
airmass_absolute = np.linspace(1, 10, 3)
@@ -142,7 +174,7 @@ def test_ineichen_arrays():
142174

143175
def test_ineichen_dni_extra():
144176
expected = pd.DataFrame(
145-
np.array([[ 1053.20547182, 946.35279683, 121.22990042]]),
177+
np.array([[1042.72590228, 946.35279683, 110.75033088]]),
146178
columns=['ghi', 'dni', 'dhi'])
147179

148180
out = clearsky.ineichen(10, 1, 3, dni_extra=pd.Series(1370))
@@ -151,7 +183,7 @@ def test_ineichen_dni_extra():
151183

152184
def test_ineichen_altitude():
153185
expected = pd.DataFrame(
154-
np.array([[ 1145.64245696, 994.95377835, 165.80426215]]),
186+
np.array([[1134.24312405, 994.95377835, 154.40492924]]),
155187
columns=['ghi', 'dni', 'dhi'])
156188

157189
out = clearsky.ineichen(10, 1, 3, altitude=pd.Series(2000))
@@ -526,18 +558,18 @@ def test_detect_clearsky_components(detect_clearsky_data):
526558
assert_series_equal(expected['Clear or not'], clear_samples,
527559
check_dtype=False, check_names=False)
528560
assert isinstance(components, OrderedDict)
529-
assert np.allclose(alpha, 0.95345573579557108)
561+
assert np.allclose(alpha, 0.9633903181941296)
530562

531563

532564
@requires_scipy
533565
def test_detect_clearsky_iterations(detect_clearsky_data):
534566
expected, cs = detect_clearsky_data
535-
alpha = 1.0348
567+
alpha = 1.0448
536568
with pytest.warns(RuntimeWarning):
537569
clear_samples = clearsky.detect_clearsky(
538570
expected['GHI'], cs['ghi']*alpha, cs.index, 10, max_iterations=1)
539-
assert (clear_samples[:'2012-04-01 10:39:00'] == True).all()
540-
assert (clear_samples['2012-04-01 10:40:00':] == False).all()
571+
assert (clear_samples[:'2012-04-01 10:41:00'] == True).all()
572+
assert (clear_samples['2012-04-01 10:42:00':] == False).all()
541573
clear_samples = clearsky.detect_clearsky(
542574
expected['GHI'], cs['ghi']*alpha, cs.index, 10, max_iterations=20)
543575
assert_series_equal(expected['Clear or not'], clear_samples,

0 commit comments

Comments
 (0)