Skip to content

Commit 3b0a86b

Browse files
jkfmwholmgren
authored andcommitted
Incidence Angle Modifier zero instead of np.nan (#338) (#339)
* changed np.nan to 0 in functions ashraeiam, physicaliam, sapm_aoi_loss * updated docstrings * updated tests to account for changes in functions * replace 0-degree aoi in physicaliam with 1e-06, analogous to the calculation of tau0 * updated tests to account for input angles of np.nan * updated test for ashraeiam to check for possible negative values for input angles close to 90° * updated docstring with info about nan-inputs resulting in nan. * updated variables-and-symbols-page with allowable range of aoi * changed whatsnew for v0.5 * applied @adriesse suggestion to split calculation of tau * forgot to delete unused line * typo :-( * typo in variables_styles_rules * implemented code suggestion by @adriesse
1 parent 289e473 commit 3b0a86b

File tree

4 files changed

+77
-34
lines changed

4 files changed

+77
-34
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. _whatsnew_0500:
2+
3+
v0.5.0 ()
4+
---------
5+
6+
7+
Bug fixes
8+
~~~~~~~~~
9+
10+
11+
Enhancements
12+
~~~~~~~~~~~~
13+
14+
15+
API Changes
16+
~~~~~~~~~~~
17+
18+
* Changes calculation of the Incidence Angle Modifier to return 0 instead of np.nan for angles >= 90°.
19+
This improves the calculation of effective irradiance close to sunrise and sunset. (:issue:`338`)
20+
21+
22+
Documentation
23+
~~~~~~~~~~~~~
24+
25+
26+
Contributors
27+
~~~~~~~~~~~~
28+
29+
* Johannes Kaufmann

pvlib/data/variables_style_rules.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dni;direct normal irradiance
66
dni_extra;direct normal irradiance at top of atmosphere (extraterrestrial)
77
dhi;diffuse horizontal irradiance
88
ghi;global horizontal irradiance
9-
aoi;angle of incidence
9+
aoi;angle of incidence between :math:`90\deg` and :math:`90\deg`
1010
aoi_projection;cos(aoi)
1111
airmass;airmass
1212
airmass_relative;relative airmass

pvlib/pvsystem.py

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ def ashraeiam(aoi, b=0.05):
700700
----------
701701
aoi : numeric
702702
The angle of incidence between the module normal vector and the
703-
sun-beam vector in degrees.
703+
sun-beam vector in degrees. Angles of nan will result in nan.
704704
705705
b : float, default 0.05
706706
A parameter to adjust the modifier as a function of angle of
@@ -712,7 +712,7 @@ def ashraeiam(aoi, b=0.05):
712712
The incident angle modifier calculated as 1-b*(sec(aoi)-1) as
713713
described in [2,3].
714714
715-
Returns nan for all abs(aoi) >= 90 and for all IAM values that
715+
Returns zeros for all abs(aoi) >= 90 and for all IAM values that
716716
would be less than 0.
717717
718718
References
@@ -735,7 +735,7 @@ def ashraeiam(aoi, b=0.05):
735735

736736
iam = 1 - b*((1/np.cos(np.radians(aoi)) - 1))
737737

738-
iam = np.where(np.abs(aoi) >= 90, np.nan, iam)
738+
iam = np.where(np.abs(aoi) >= 90, 0, iam)
739739
iam = np.maximum(0, iam)
740740

741741
if isinstance(iam, pd.Series):
@@ -764,7 +764,8 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002):
764764
----------
765765
aoi : numeric
766766
The angle of incidence between the module normal vector and the
767-
sun-beam vector in degrees.
767+
sun-beam vector in degrees. Angles of 0 are replaced with 1e-06
768+
to ensure non-nan results. Angles of nan will result in nan.
768769
769770
n : numeric, default 1.526
770771
The effective index of refraction (unitless). Reference [1]
@@ -814,27 +815,40 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002):
814815
spa
815816
ashraeiam
816817
'''
818+
zeroang = 1e-06
819+
820+
aoi = np.where(aoi == 0, zeroang, aoi)
821+
822+
# angle of reflection
817823
thetar_deg = tools.asind(1.0 / n*(tools.sind(aoi)))
818824

819-
tau = (np.exp(- 1.0 * (K*L / tools.cosd(thetar_deg))) *
820-
((1 - 0.5*((((tools.sind(thetar_deg - aoi)) ** 2) /
821-
((tools.sind(thetar_deg + aoi)) ** 2) +
822-
((tools.tand(thetar_deg - aoi)) ** 2) /
823-
((tools.tand(thetar_deg + aoi)) ** 2))))))
825+
# reflectance and transmittance for normal incidence light
826+
rho_zero = ((1-n) / (1+n)) ** 2
827+
tau_zero = np.exp(-K*L)
824828

825-
zeroang = 1e-06
829+
# reflectance for parallel and perpendicular polarized light
830+
rho_para = (tools.tand(thetar_deg - aoi) /
831+
tools.tand(thetar_deg + aoi)) ** 2
832+
rho_perp = (tools.sind(thetar_deg - aoi) /
833+
tools.sind(thetar_deg + aoi)) ** 2
834+
835+
# transmittance for non-normal light
836+
tau = np.exp(-K*L / tools.cosd(thetar_deg))
826837

827-
thetar_deg0 = tools.asind(1.0 / n*(tools.sind(zeroang)))
838+
# iam is ratio of non-normal to normal incidence transmitted light
839+
# after deducting the reflected portion of each
840+
iam = ((1 - (rho_para + rho_perp) / 2) / (1 - rho_zero) * tau / tau_zero)
828841

829-
tau0 = (np.exp(- 1.0 * (K*L / tools.cosd(thetar_deg0))) *
830-
((1 - 0.5*((((tools.sind(thetar_deg0 - zeroang)) ** 2) /
831-
((tools.sind(thetar_deg0 + zeroang)) ** 2) +
832-
((tools.tand(thetar_deg0 - zeroang)) ** 2) /
833-
((tools.tand(thetar_deg0 + zeroang)) ** 2))))))
842+
# angles near zero produce nan, but iam is defined as one
843+
small_angle = 1e-06
844+
iam = np.where(np.abs(aoi) < small_angle, 1.0, iam)
834845

835-
iam = tau / tau0
846+
# angles at 90 degrees can produce tiny negative values, which should be zero
847+
# this is a result of calculation precision rather than the physical model
848+
iam = np.where(iam < 0, 0, iam)
836849

837-
iam = np.where((np.abs(aoi) >= 90) | (iam < 0), np.nan, iam)
850+
# for light coming from behind the plane, none can enter the module
851+
iam = np.where(aoi > 90, 0, iam)
838852

839853
if isinstance(aoi, pd.Series):
840854
iam = pd.Series(iam, index=aoi.index)
@@ -1465,7 +1479,7 @@ def sapm_aoi_loss(aoi, module, upper=None):
14651479
----------
14661480
aoi : numeric
14671481
Angle of incidence in degrees. Negative input angles will return
1468-
nan values.
1482+
zeros.
14691483
14701484
module : dict-like
14711485
A dict, Series, or DataFrame defining the SAPM performance
@@ -1507,7 +1521,7 @@ def sapm_aoi_loss(aoi, module, upper=None):
15071521

15081522
aoi_loss = np.polyval(aoi_coeff, aoi)
15091523
aoi_loss = np.clip(aoi_loss, 0, upper)
1510-
aoi_loss = np.where(aoi < 0, np.nan, aoi_loss)
1524+
aoi_loss = np.where(aoi < 0, 0, aoi_loss)
15111525

15121526
if isinstance(aoi, pd.Series):
15131527
aoi_loss = pd.Series(aoi_loss, aoi.index)

pvlib/test/test_pvsystem.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,41 +94,41 @@ def test_systemdef_dict():
9494

9595
@needs_numpy_1_10
9696
def test_ashraeiam():
97-
thetas = np.linspace(-90, 90, 9)
97+
thetas = np.array([-90. , -67.5, -45. , -22.5, 0. , 22.5, 45. , 67.5, 89., 90. , np.nan])
9898
iam = pvsystem.ashraeiam(thetas, .05)
99-
expected = np.array([ nan, 0.9193437 , 0.97928932, 0.99588039, 1. ,
100-
0.99588039, 0.97928932, 0.9193437 , nan])
99+
expected = np.array([ 0, 0.9193437 , 0.97928932, 0.99588039, 1. ,
100+
0.99588039, 0.97928932, 0.9193437 , 0, 0, np.nan])
101101
assert_allclose(iam, expected, equal_nan=True)
102102

103103

104104
@needs_numpy_1_10
105105
def test_PVSystem_ashraeiam():
106106
module_parameters = pd.Series({'b': 0.05})
107107
system = pvsystem.PVSystem(module_parameters=module_parameters)
108-
thetas = np.linspace(-90, 90, 9)
108+
thetas = np.array([-90. , -67.5, -45. , -22.5, 0. , 22.5, 45. , 67.5, 89., 90. , np.nan])
109109
iam = system.ashraeiam(thetas)
110-
expected = np.array([ nan, 0.9193437 , 0.97928932, 0.99588039, 1. ,
111-
0.99588039, 0.97928932, 0.9193437 , nan])
110+
expected = np.array([ 0, 0.9193437 , 0.97928932, 0.99588039, 1. ,
111+
0.99588039, 0.97928932, 0.9193437 , 0, 0, np.nan])
112112
assert_allclose(iam, expected, equal_nan=True)
113113

114114

115115
@needs_numpy_1_10
116116
def test_physicaliam():
117-
thetas = np.linspace(-90, 90, 9)
117+
thetas = np.array([-90. , -67.5, -45. , -22.5, 0. , 22.5, 45. , 67.5, 90. , np.nan])
118118
iam = pvsystem.physicaliam(thetas, 1.526, 0.002, 4)
119-
expected = np.array([ nan, 0.8893998 , 0.98797788, 0.99926198, nan,
120-
0.99926198, 0.98797788, 0.8893998 , nan])
119+
expected = np.array([ 0, 0.8893998, 0.98797788, 0.99926198, 1,
120+
0.99926198, 0.98797788, 0.8893998, 0, np.nan])
121121
assert_allclose(iam, expected, equal_nan=True)
122122

123123

124124
@needs_numpy_1_10
125125
def test_PVSystem_physicaliam():
126126
module_parameters = pd.Series({'K': 4, 'L': 0.002, 'n': 1.526})
127127
system = pvsystem.PVSystem(module_parameters=module_parameters)
128-
thetas = np.linspace(-90, 90, 9)
128+
thetas = np.array([-90. , -67.5, -45. , -22.5, 0. , 22.5, 45. , 67.5, 90. , np.nan])
129129
iam = system.physicaliam(thetas)
130-
expected = np.array([ nan, 0.8893998 , 0.98797788, 0.99926198, nan,
131-
0.99926198, 0.98797788, 0.8893998 , nan])
130+
expected = np.array([ 0, 0.8893998 , 0.98797788, 0.99926198, 1,
131+
0.99926198, 0.98797788, 0.8893998 , 0, np.nan])
132132
assert_allclose(iam, expected, equal_nan=True)
133133

134134

@@ -239,7 +239,7 @@ def test_PVSystem_sapm_spectral_loss(sapm_module_params):
239239
@pytest.mark.parametrize('aoi,expected', [
240240
(45, 0.9975036250000002),
241241
(np.array([[-30, 30, 100, np.nan]]),
242-
np.array([[np.nan, 1.007572, 0, np.nan]])),
242+
np.array([[0, 1.007572, 0, np.nan]])),
243243
(pd.Series([80]), pd.Series([0.597472]))
244244
])
245245
def test_sapm_aoi_loss(sapm_module_params, aoi, expected):

0 commit comments

Comments
 (0)