diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output.rst b/docs/sphinx/source/reference/effects_on_pv_system_output.rst index 92efa946b4..62c1035860 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output.rst @@ -21,6 +21,7 @@ Snow snow.coverage_nrel snow.fully_covered_nrel snow.dc_loss_nrel + snow.loss_townsend Soiling ------- diff --git a/docs/sphinx/source/whatsnew/v0.9.1.rst b/docs/sphinx/source/whatsnew/v0.9.1.rst index 4fac13dc8c..75d605e83d 100644 --- a/docs/sphinx/source/whatsnew/v0.9.1.rst +++ b/docs/sphinx/source/whatsnew/v0.9.1.rst @@ -28,6 +28,8 @@ Enhancements * Added ``map_variables`` option to :func:`~pvlib.iotools.read_crn` (:pull:`1368`) * Added :py:func:`pvlib.temperature.prilliman` for modeling cell temperature at short time steps (:issue:`1081`, :pull:`1391`) +* Added Townsend Powers Snow loss model in :py:func:`pvlib.snow` + (:issue:`1246`, :pull:`1251`) Bug fixes ~~~~~~~~~ @@ -83,3 +85,5 @@ Contributors * Jack Kelly (:ghuser:`JackKelly`) * Somasree Majumder(:ghuser:`soma2000-lang`) * Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`) +* Abhishek Parikh (:ghuser:`abhisheksparikh`) + diff --git a/pvlib/snow.py b/pvlib/snow.py index bf40c7b995..c003c5531a 100644 --- a/pvlib/snow.py +++ b/pvlib/snow.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd -from pvlib.tools import sind +from pvlib.tools import sind, cosd, tand def _time_delta_in_hours(times): @@ -185,3 +185,108 @@ def dc_loss_nrel(snow_coverage, num_strings): Available at https://www.nrel.gov/docs/fy18osti/67399.pdf ''' return np.ceil(snow_coverage * num_strings) / num_strings + + +def _townsend_Se(S, N): + ''' + Calculates effective snow for a given month based upon the total snowfall + received in a month in inches and the number of events where snowfall is + greater than 1 inch + + Parameters + ---------- + S : numeric + Snowfall in inches received in a month [in] + + N: numeric + Number of snowfall events with snowfall > 1" [-] + + Returns + ------- + effective_snowfall : numeric + Effective snowfall as defined in the townsend model + + References + ---------- + .. [1] Townsend, Tim & Powers, Loren. (2011). Photovoltaics and snow: An + update from two winters of measurements in the SIERRA. Conference + Record of the IEEE Photovoltaic Specialists Conference. + 003231-003236. :doi:`10.1109/PVSC.2011.6186627` + Available at https://www.researchgate.net/publication/261042016_Photovoltaics_and_snow_An_update_from_two_winters_of_measurements_in_the_SIERRA + + ''' # noqa: E501 + return(np.where(N > 0, 0.5 * S * (1 + 1/N), 0)) + + +def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity, + temp_air, poa_global, slant_height, lower_edge_drop_height, + angle_of_repose=40): + ''' + Calculates monthly snow loss based on a generalized monthly snow loss model + discussed in [1]_. + + Parameters + ---------- + snow_total : numeric + Inches of snow received in the current month. Referred as S in the + paper [in] + + snow_events : numeric + Number of snowfall events with snowfall > 1". Referred as N in the + paper [-] + + surface_tilt : numeric + Array surface_tilt [deg] + + relative_humidity : numeric + Relative humidity [%] + + temp_air : numeric + Ambient temperature [°C] + + poa_global : numeric + Plane of array insolation [kWh/m2/month] + + slant_height : float + Row length in the slanted plane of array dimension [in] + + lower_edge_drop_height : float + Drop height from array edge to ground [in] + + P : float + piled snow angle, assumed to stabilize at 40° , the midpoint of + 25°-55° avalanching slope angles [deg] + + Returns + ------- + loss : numeric + Average monthly DC capacity loss due to snow coverage [%] + + References + ---------- + .. [1] Townsend, Tim & Powers, Loren. (2011). Photovoltaics and snow: An + update from two winters of measurements in the SIERRA. Conference + Record of the IEEE Photovoltaic Specialists Conference. + 003231-003236. 10.1109/PVSC.2011.6186627. + Available at https://www.researchgate.net/publication/261042016_Photovoltaics_and_snow_An_update_from_two_winters_of_measurements_in_the_SIERRA + ''' # noqa: E501 + + C1 = 5.7e04 + C2 = 0.51 + + snow_total_prev = np.roll(snow_total, 1) + snow_events_prev = np.roll(snow_events, 1) + + Se = _townsend_Se(snow_total, snow_events) + Se_prev = _townsend_Se(snow_total_prev, snow_events_prev) + + Se_weighted = 1/3 * Se_prev + 2/3 * Se + gamma = (slant_height * Se_weighted * cosd(surface_tilt)) / \ + (np.clip((lower_edge_drop_height**2 - Se_weighted**2), a_min=0.01, + a_max=None) / 2 / tand(angle_of_repose)) + + GIT = 1 - C2 * np.exp(-gamma) + loss = (C1 * Se_weighted * (cosd(surface_tilt))**2 * GIT * + relative_humidity / (temp_air+273.15)**2 / poa_global**0.67) / 100 + + return loss diff --git a/pvlib/tests/test_snow.py b/pvlib/tests/test_snow.py index 3db56c8f61..bc71d5537a 100644 --- a/pvlib/tests/test_snow.py +++ b/pvlib/tests/test_snow.py @@ -95,3 +95,32 @@ def test_dc_loss_nrel(): expected = pd.Series([1, 1, .5, .625, .25, .5, 0]) actual = snow.dc_loss_nrel(snow_coverage, num_strings) assert_series_equal(expected, actual) + + +def test__townsend_Se(): + S = np.array([10, 10, 5, 1, 0, 0, 0, 0, 0, 0, 5, 10]) + N = np.array([2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3]) + expected = np.array([7.5, 7.5, 5, 0, 0, 0, 0, 0, 0, 0, 3.75, 6.66666667]) + actual = snow._townsend_Se(S, N) + np.testing.assert_allclose(expected, actual, rtol=1e-07) + + +def test_loss_townsend(): + snow_total = np.array([10, 10, 5, 1, 0, 0, 0, 0, 0, 0, 5, 10]) + snow_events = np.array([2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3]) + surface_tilt = 20 + relative_humidity = np.array([80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80]) + temp_air = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + poa_global = np.array([350, 350, 350, 350, 350, 350, 350, 350, 350, 350, + 350, 350]) + angle_of_repose = 40 + slant_height = 100 + lower_edge_drop_height = 10 + expected = np.array([0.07696253, 0.07992262, 0.06216201, 0.01715392, 0, 0, + 0, 0, 0, 0, 0.02643821, 0.06068194]) + actual = snow.loss_townsend(snow_total, snow_events, surface_tilt, + relative_humidity, temp_air, + poa_global, slant_height, + lower_edge_drop_height, angle_of_repose) + np.testing.assert_allclose(expected, actual, rtol=1e-05)