Skip to content

Commit 8e0b724

Browse files
kandersolarcwhanseadriesse
authored
Implement iam.schlick, iam.schlick_diffuse (#1562)
* initial code from Yu Xie * edits for pvlib style * API and whatsnew entries * tests * add a comment * add notes section * Apply suggestions from code review Co-authored-by: Cliff Hansen <[email protected]> * other changes from review * add iam.schlick * revise iam.fedis * edits * Update pv_modeling.rst * clean up * better default behavior for n_ref * Apply suggestions from code review Co-authored-by: Cliff Hansen <[email protected]> * split up schlick/schlick_diffuse/fedis/fedis_diffuse Co-Authored-By: Anton Driesse <[email protected]> * Apply suggestions from code review Co-authored-by: Cliff Hansen <[email protected]> * follow up * use iam.physical in iam.fedis Co-Authored-By: Anton Driesse <[email protected]> * Apply suggestions from code review Co-authored-by: Cliff Hansen <[email protected]> * better comments, variable name * revise docstrings * clean up 0.9.4 whatsnew, include in whatsnew index * fix typo * Apply suggestions from code review Co-authored-by: Anton Driesse <[email protected]> * stickler * fix missed variable rename * remove fedis functions Co-authored-by: Cliff Hansen <[email protected]> Co-authored-by: Anton Driesse <[email protected]> Co-authored-by: Anton Driesse <[email protected]>
1 parent 582b956 commit 8e0b724

File tree

5 files changed

+179
-3
lines changed

5 files changed

+179
-3
lines changed

docs/sphinx/source/reference/pv_modeling.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Incident angle modifiers
2828
iam.interp
2929
iam.marion_diffuse
3030
iam.marion_integrate
31+
iam.schlick
32+
iam.schlick_diffuse
3133

3234
PV temperature models
3335
---------------------

docs/sphinx/source/whatsnew.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ What's New
66

77
These are new features and improvements of note in each release.
88

9+
.. include:: whatsnew/v0.9.4.rst
910
.. include:: whatsnew/v0.9.3.rst
1011
.. include:: whatsnew/v0.9.2.rst
1112
.. include:: whatsnew/v0.9.1.rst

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.. _whatsnew_0940:
22

3-
v0.9.4 (TBD)
4-
------------------------
3+
v0.9.4 (anticipated December 2022)
4+
----------------------------------
55

66
Deprecations
77
~~~~~~~~~~~~
@@ -10,6 +10,9 @@ Deprecations
1010
Enhancements
1111
~~~~~~~~~~~~
1212
* Multiple code style issues fixed that were reported by LGTM analysis. (:issue:`1275`, :pull:`1559`)
13+
* Added a direct IAM model :py:func:`pvlib.iam.schlick` which can be used with
14+
:py:func:`~pvlib.iam.marion_diffuse`, and a diffuse IAM model
15+
:py:func:`pvlib.iam.schlick_diffuse` (:pull:`1562`, :issue:`1564`)
1316
* Added a function to calculate one of GHI, DHI, and DNI from values of the other two.
1417
:py:func:`~pvlib.irradiance.complete_irradiance`
1518
(:issue:`1565`, :pull:`1567`)
@@ -47,6 +50,10 @@ Contributors
4750
* Christian Orner (:ghuser:`chrisorner`)
4851
* Saurabh Aneja (:ghuser:`spaneja`)
4952
* Marcus Boumans (:ghuser:`bowie2211`)
53+
* Yu Xie (:ghuser:`xieyupku`)
54+
* Anton Driesse (:ghuser:`adriesse`)
55+
* Cliff Hansen (:ghuser:`cwhanse`)
56+
* Kevin Anderson (:ghuser:`kanderso-nrel`)
5057
* Karel De Brabandere (:ghuser:`kdebrab`)
5158
* Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`)
5259
* Echedey Luis (:ghuser:`echedey-ls`)

pvlib/iam.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ def marion_diffuse(model, surface_tilt, **kwargs):
540540
----------
541541
model : str
542542
The IAM function to evaluate across solid angle. Must be one of
543-
`'ashrae', 'physical', 'martin_ruiz', 'sapm'`.
543+
`'ashrae', 'physical', 'martin_ruiz', 'sapm', 'schlick'`.
544544
545545
surface_tilt : numeric
546546
Surface tilt angles in decimal degrees.
@@ -591,6 +591,7 @@ def marion_diffuse(model, surface_tilt, **kwargs):
591591
'ashrae': ashrae,
592592
'sapm': sapm,
593593
'martin_ruiz': martin_ruiz,
594+
'schlick': schlick,
594595
}
595596

596597
try:
@@ -747,3 +748,123 @@ def marion_integrate(function, surface_tilt, region, num=None):
747748
Fd = pd.Series(Fd, surface_tilt.index)
748749

749750
return Fd
751+
752+
753+
def schlick(aoi):
754+
"""
755+
Determine incidence angle modifier (IAM) for direct irradiance using the
756+
Schlick approximation to the Fresnel equations.
757+
758+
The Schlick approximation was proposed in [1]_ as a computationally
759+
efficient alternative to computing the Fresnel factor in computer
760+
graphics contexts. This implementation is a normalized form of the
761+
equation in [1]_ so that it can be used as a PV IAM model.
762+
Unlike other IAM models, this model has no ability to describe
763+
different reflection profiles.
764+
765+
In PV contexts, the Schlick approximation has been used as an analytically
766+
integrable alternative to the Fresnel equations for estimating IAM
767+
for diffuse irradiance [2]_.
768+
769+
Parameters
770+
----------
771+
aoi : numeric
772+
The angle of incidence (AOI) between the module normal vector and the
773+
sun-beam vector. Angles of nan will result in nan. [degrees]
774+
775+
Returns
776+
-------
777+
iam : numeric
778+
The incident angle modifier.
779+
780+
References
781+
----------
782+
.. [1] Schlick, C. An inexpensive BRDF model for physically-based
783+
rendering. Computer graphics forum 13 (1994).
784+
785+
.. [2] Xie, Y., M. Sengupta, A. Habte, A. Andreas, "The 'Fresnel Equations'
786+
for Diffuse radiation on Inclined photovoltaic Surfaces (FEDIS)",
787+
Renewable and Sustainable Energy Reviews, vol. 161, 112362. June 2022.
788+
:doi:`10.1016/j.rser.2022.112362`
789+
790+
See Also
791+
--------
792+
pvlib.iam.schlick_diffuse
793+
"""
794+
iam = 1 - (1 - cosd(aoi)) ** 5
795+
iam = np.where(np.abs(aoi) >= 90.0, 0.0, iam)
796+
797+
# preserve input type
798+
if np.isscalar(aoi):
799+
iam = iam.item()
800+
elif isinstance(aoi, pd.Series):
801+
iam = pd.Series(iam, aoi.index)
802+
803+
return iam
804+
805+
806+
def schlick_diffuse(surface_tilt):
807+
"""
808+
Determine the incidence angle modifiers (IAM) for diffuse sky and
809+
ground-reflected irradiance on a tilted surface using the Schlick
810+
incident angle model.
811+
812+
The diffuse iam values are calculated using an analytical integration
813+
of the Schlick equation [1]_ over the portion of an isotropic sky and
814+
isotropic foreground that is visible from the tilted surface [2]_.
815+
816+
Parameters
817+
----------
818+
surface_tilt : numeric
819+
Surface tilt angle measured from horizontal (e.g. surface facing
820+
up = 0, surface facing horizon = 90). [degrees]
821+
822+
Returns
823+
-------
824+
iam_sky : numeric
825+
The incident angle modifier for sky diffuse.
826+
827+
iam_ground : numeric
828+
The incident angle modifier for ground-reflected diffuse.
829+
830+
References
831+
----------
832+
.. [1] Schlick, C. An inexpensive BRDF model for physically-based
833+
rendering. Computer graphics forum 13 (1994).
834+
835+
.. [2] Xie, Y., M. Sengupta, A. Habte, A. Andreas, "The 'Fresnel Equations'
836+
for Diffuse radiation on Inclined photovoltaic Surfaces (FEDIS)",
837+
Renewable and Sustainable Energy Reviews, vol. 161, 112362. June 2022.
838+
:doi:`10.1016/j.rser.2022.112362`
839+
840+
See Also
841+
--------
842+
pvlib.iam.schlick
843+
"""
844+
# these calculations are as in [2]_, but with the refractive index
845+
# weighting coefficient w set to 1.0 (so it is omitted)
846+
847+
# relative transmittance of sky diffuse radiation by PV cover:
848+
cosB = cosd(surface_tilt)
849+
sinB = sind(surface_tilt)
850+
cuk = (2 / (np.pi * (1 + cosB))) * (
851+
(30/7)*np.pi - (160/21)*np.radians(surface_tilt) - (10/3)*np.pi*cosB
852+
+ (160/21)*cosB*sinB - (5/3)*np.pi*cosB*sinB**2 + (20/7)*cosB*sinB**3
853+
- (5/16)*np.pi*cosB*sinB**4 + (16/105)*cosB*sinB**5
854+
) # Eq 4 in [2]
855+
856+
# relative transmittance of ground-reflected radiation by PV cover:
857+
with np.errstate(divide='ignore', invalid='ignore'): # Eq 6 in [2]
858+
cug = 40 / (21 * (1 - cosB)) - (1 + cosB) / (1 - cosB) * cuk
859+
860+
cug = np.where(surface_tilt < 1e-6, 0, cug)
861+
862+
# respect input types:
863+
if np.isscalar(surface_tilt):
864+
cuk = cuk.item()
865+
cug = cug.item()
866+
elif isinstance(surface_tilt, pd.Series):
867+
cuk = pd.Series(cuk, surface_tilt.index)
868+
cug = pd.Series(cug, surface_tilt.index)
869+
870+
return cuk, cug

pvlib/tests/test_iam.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,48 @@ def test_marion_integrate_invalid():
322322

323323
with pytest.raises(ValueError):
324324
_iam.marion_integrate(_iam.ashrae, 0, 'bad', 180)
325+
326+
327+
def test_schlick():
328+
idx = pd.date_range('2019-01-01', freq='h', periods=9)
329+
aoi = pd.Series([-180, -135, -90, -45, 0, 45, 90, 135, 180], idx)
330+
expected = pd.Series([0, 0, 0, 0.99784451, 1.0, 0.99784451, 0, 0, 0], idx)
331+
332+
# scalars
333+
for aoi_scalar, expected_scalar in zip(aoi, expected):
334+
actual = _iam.schlick(aoi_scalar)
335+
assert_allclose(expected_scalar, actual)
336+
337+
# numpy arrays
338+
actual = _iam.schlick(aoi.values)
339+
assert_allclose(expected.values, actual)
340+
341+
# pandas Series
342+
actual = _iam.schlick(aoi)
343+
assert_series_equal(expected, actual)
344+
345+
346+
def test_schlick_diffuse():
347+
surface_tilt = np.array([0, 20, 70, 90])
348+
# expected values calculated with marion_integrate and schlick
349+
expected_sky = np.array([0.95238092, 0.96249934, 0.96228167, 0.95238094])
350+
expected_ground = np.array([0, 0.62693858, 0.93218737, 0.95238094])
351+
352+
# numpy arrays
353+
actual_sky, actual_ground = _iam.schlick_diffuse(surface_tilt)
354+
assert_allclose(expected_sky, actual_sky)
355+
assert_allclose(expected_ground, actual_ground, rtol=1e-6)
356+
357+
# scalars
358+
for i in range(len(surface_tilt)):
359+
actual_sky, actual_ground = _iam.schlick_diffuse(surface_tilt[i])
360+
assert_allclose(expected_sky[i], actual_sky)
361+
assert_allclose(expected_ground[i], actual_ground, rtol=1e-6)
362+
363+
# pandas Series
364+
idx = pd.date_range('2019-01-01', freq='h', periods=len(surface_tilt))
365+
actual_sky, actual_ground = _iam.schlick_diffuse(pd.Series(surface_tilt,
366+
idx))
367+
assert_series_equal(pd.Series(expected_sky, idx), actual_sky)
368+
assert_series_equal(pd.Series(expected_ground, idx), actual_ground,
369+
rtol=1e-6)

0 commit comments

Comments
 (0)