Skip to content

Commit e643dc3

Browse files
kdebrabkandersolar
andauthored
[FIX] iam.physical returns nan for aoi > 90° when n = 1 (#1706) (#1707)
* [FIX] iam.physical returns nan for aoi > 90° when n = 1 (#1706) * address stickler-ci and codecov alerts * suppress obnoxious numpy warnings * whatsnew --------- Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>
1 parent 81598e4 commit e643dc3

File tree

3 files changed

+32
-4
lines changed

3 files changed

+32
-4
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ Bug fixes
5858
:py:meth:`pvlib.modelchain.ModelChain.run_model_from_effective_irradiance`. (:issue:`1713`, :pull:`1720`)
5959
* ``d2mutau`` and ``NsVbi`` were hardcoded in :py:func:`pvlib.pvsystem.max_power_point` instead of
6060
passing through the arguments to the function. (:pull:`1733`)
61+
* :py:func:`pvlib.iam.physical` no longer returns NaN when ``n=1`` and ``aoi>90``.
62+
This bug was introduced in v0.9.5. (:issue:`1706`, :pull:`1707`)
63+
6164

6265
Testing
6366
~~~~~~~

pvlib/iam.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,12 @@ def physical(aoi, n=1.526, K=4.0, L=0.002, *, n_ar=None):
175175
n2costheta2 = n2 * costheta
176176

177177
# reflectance of s-, p-polarized, and normal light by the first interface
178-
rho12_s = ((n1costheta1 - n2costheta2) / (n1costheta1 + n2costheta2)) ** 2
179-
rho12_p = ((n1costheta2 - n2costheta1) / (n1costheta2 + n2costheta1)) ** 2
178+
with np.errstate(divide='ignore', invalid='ignore'):
179+
rho12_s = \
180+
((n1costheta1 - n2costheta2) / (n1costheta1 + n2costheta2)) ** 2
181+
rho12_p = \
182+
((n1costheta2 - n2costheta1) / (n1costheta2 + n2costheta1)) ** 2
183+
180184
rho12_0 = ((n1 - n2) / (n1 + n2)) ** 2
181185

182186
# transmittance through the first interface
@@ -208,13 +212,22 @@ def physical(aoi, n=1.526, K=4.0, L=0.002, *, n_ar=None):
208212
tau_0 *= (1 - rho23_0) / (1 - rho23_0 * rho12_0)
209213

210214
# transmittance after absorption in the glass
211-
tau_s *= np.exp(-K * L / costheta)
212-
tau_p *= np.exp(-K * L / costheta)
215+
with np.errstate(divide='ignore', invalid='ignore'):
216+
tau_s *= np.exp(-K * L / costheta)
217+
tau_p *= np.exp(-K * L / costheta)
218+
213219
tau_0 *= np.exp(-K * L)
214220

215221
# incidence angle modifier
216222
iam = (tau_s + tau_p) / 2 / tau_0
217223

224+
# for light coming from behind the plane, none can enter the module
225+
# when n2 > 1, this is already the case
226+
if np.isclose(n2, 1).any():
227+
iam = np.where(aoi >= 90, 0, iam)
228+
if isinstance(aoi, pd.Series):
229+
iam = pd.Series(iam, index=aoi.index)
230+
218231
return iam
219232

220233

pvlib/tests/test_iam.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ def test_physical():
5151
assert_series_equal(iam, expected)
5252

5353

54+
def test_physical_n1_L0():
55+
aoi = np.array([0, 22.5, 45, 67.5, 90, 100, np.nan])
56+
expected = np.array([1, 1, 1, 1, 0, 0, np.nan])
57+
iam = _iam.physical(aoi, n=1, L=0)
58+
assert_allclose(iam, expected, equal_nan=True)
59+
60+
aoi = pd.Series(aoi)
61+
expected = pd.Series(expected)
62+
iam = _iam.physical(aoi, n=1, L=0)
63+
assert_series_equal(iam, expected)
64+
65+
5466
def test_physical_ar():
5567
aoi = np.array([0, 22.5, 45, 67.5, 90, 100, np.nan])
5668
expected = np.array([1, 0.99944171, 0.9917463, 0.91506158, 0, 0, np.nan])

0 commit comments

Comments
 (0)