Skip to content

Commit c2d6d4f

Browse files
committed
implementing changes suggested in PR pvlib#1251
1 parent f7ba1ad commit c2d6d4f

File tree

4 files changed

+69
-50
lines changed

4 files changed

+69
-50
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
.. _whatsnew_0910:
23

34
v0.9.1 (March 29, 2022)
@@ -25,8 +26,6 @@ Enhancements
2526
* Added ``map_variables`` option to :func:`~pvlib.iotools.read_crn` (:pull:`1368`)
2627
* Added :py:func:`pvlib.temperature.prilliman` for modeling cell temperature
2728
at short time steps (:issue:`1081`, :pull:`1391`)
28-
* Added Townsend Powers Snow loss model in :py:func:`pvlib.snow`
29-
(:issue:`1246`, :pull:`1251`)
3029

3130
Bug fixes
3231
~~~~~~~~~
@@ -85,7 +84,6 @@ Contributors
8584
* Jack Kelly (:ghuser:`JackKelly`)
8685
* Somasree Majumder(:ghuser:`soma2000-lang`)
8786
* Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`)
88-
* Abhishek Parikh (:ghuser:`abhisheksparikh`)
8987

9088
* Will Holmgren (:ghuser:`wholmgren`)
9189
* Mark Mikofksi (:ghuser:`mikofski`)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Bug fixes
1818
where passing localized timezones with large UTC offsets could return
1919
rise/set/transit times for the wrong day in recent versions of ``ephem``
2020
(:issue:`1449`, :pull:`1448`)
21+
* Added Townsend-Powers monthly snow loss model: :py:func:`pvlib.snow.loss_townsend`
22+
(:issue:`1246`, :pull:`1251`)
2123

2224

2325
Testing
@@ -40,3 +42,5 @@ Contributors
4042
* Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`)
4143
* Chencheng Luo (:ghuser:`roger-lcc`)
4244
* Prajwal Borkar (:ghuser:`PrajwalBorkar`)
45+
* Abhishek Parikh (:ghuser:`abhisheksparikh`)
46+
* Taos Transue (:ghuser:`reepoi`)

pvlib/snow.py

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,23 @@ def dc_loss_nrel(snow_coverage, num_strings):
187187
return np.ceil(snow_coverage * num_strings) / num_strings
188188

189189

190-
def _townsend_Se(S, N):
190+
def _townsend_effective_snow(snow_load, snow_events):
191191
'''
192-
Calculates effective snow for a given month based upon the total snowfall
193-
received in a month in inches and the number of events where snowfall is
194-
greater than 1 inch
192+
Calculates effective snow using the total snowfall in inches received
193+
each month and the number of snowfall events each month.
195194
196195
Parameters
197196
----------
198-
S : numeric
199-
Snowfall in inches received in a month [in]
197+
snow_load : array-like
198+
Inches of snow received each month. Referred to as S in the paper [in]
200199
201-
N: numeric
202-
Number of snowfall events with snowfall > 1" [-]
200+
snow_events : array-like
201+
Number of snowfall events each month. Referred to as N in the paper [-]
203202
204203
Returns
205204
-------
206-
effective_snowfall : numeric
207-
Effective snowfall as defined in the townsend model
205+
effective_snowfall : array-like
206+
Effective snowfall as defined in the Townsend model
208207
209208
References
210209
----------
@@ -215,52 +214,56 @@ def _townsend_Se(S, N):
215214
Available at https://www.researchgate.net/publication/261042016_Photovoltaics_and_snow_An_update_from_two_winters_of_measurements_in_the_SIERRA
216215
217216
''' # noqa: E501
218-
return(np.where(N > 0, 0.5 * S * (1 + 1/N), 0))
217+
snow_events_no_zeros = np.maximum(snow_events, 1)
218+
effective_snow = 0.5 * snow_load * (1 + 1 / snow_events_no_zeros)
219+
return np.where(snow_events > 0, effective_snow, 0)
219220

220221

221222
def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity,
222223
temp_air, poa_global, slant_height, lower_edge_drop_height,
223224
angle_of_repose=40):
224225
'''
225-
Calculates monthly snow loss based on a generalized monthly snow loss model
226-
discussed in [1]_.
226+
Calculates monthly snow loss based on the Townsend monthly snow loss model [1]_.
227227
228228
Parameters
229229
----------
230-
snow_total : numeric
231-
Inches of snow received in the current month. Referred as S in the
232-
paper [in]
230+
snow_total : array-like
231+
Inches of snow received each month. Referred to as S in the paper [in]
233232
234-
snow_events : numeric
235-
Number of snowfall events with snowfall > 1". Referred as N in the
236-
paper [-]
233+
snow_events : array-like
234+
Number of snowfall events each month. Referred to as N in the paper [-]
237235
238-
surface_tilt : numeric
239-
Array surface_tilt [deg]
236+
surface_tilt : float
237+
Tilt angle of the array [deg]
240238
241-
relative_humidity : numeric
242-
Relative humidity [%]
239+
relative_humidity : array-like
240+
Monthly average relative humidity [%]
243241
244-
temp_air : numeric
245-
Ambient temperature [°C]
242+
temp_air : array-like
243+
Monthly average ambient temperature [C]
246244
247-
poa_global : numeric
248-
Plane of array insolation [kWh/m2/month]
245+
poa_global : array-like
246+
Monthly plane of array insolation [kWh/m2/month]
249247
250248
slant_height : float
251249
Row length in the slanted plane of array dimension [in]
252250
253251
lower_edge_drop_height : float
254252
Drop height from array edge to ground [in]
255253
256-
P : float
257-
piled snow angle, assumed to stabilize at 40° , the midpoint of
254+
angle_of_repose : float, default 40
255+
piled snow angle, assumed to stabilize at 40°, the midpoint of
258256
25°-55° avalanching slope angles [deg]
259257
260258
Returns
261259
-------
262-
loss : numeric
263-
Average monthly DC capacity loss due to snow coverage [%]
260+
loss : array-like
261+
Monthly average DC capacity loss fraction due to snow coverage
262+
263+
Notes
264+
-----
265+
This model has not been validated for tracking arrays; however, for tracking
266+
arrays [1]_ suggests using the maximum rotation angle in place of surface_tilt.
264267
265268
References
266269
----------
@@ -277,16 +280,30 @@ def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity,
277280
snow_total_prev = np.roll(snow_total, 1)
278281
snow_events_prev = np.roll(snow_events, 1)
279282

280-
Se = _townsend_Se(snow_total, snow_events)
281-
Se_prev = _townsend_Se(snow_total_prev, snow_events_prev)
282-
283-
Se_weighted = 1/3 * Se_prev + 2/3 * Se
284-
gamma = (slant_height * Se_weighted * cosd(surface_tilt)) / \
285-
(np.clip((lower_edge_drop_height**2 - Se_weighted**2), a_min=0.01,
286-
a_max=None) / 2 / tand(angle_of_repose))
287-
288-
GIT = 1 - C2 * np.exp(-gamma)
289-
loss = (C1 * Se_weighted * (cosd(surface_tilt))**2 * GIT *
290-
relative_humidity / (temp_air+273.15)**2 / poa_global**0.67) / 100
291-
292-
return loss
283+
effective_snow = _townsend_effective_snow(snow_total, snow_events)
284+
effective_snow_prev = _townsend_effective_snow(snow_total_prev, snow_events_prev)
285+
effective_snow_weighted = 1 / 3 * effective_snow_prev + 2 / 3 * effective_snow
286+
287+
drop_height_clipped = np.maximum(lower_edge_drop_height, 0.01)
288+
gamma = (
289+
slant_height
290+
* effective_snow_weighted
291+
* cosd(surface_tilt)
292+
/ (drop_height_clipped**2 - effective_snow_weighted**2)
293+
* 2
294+
* tand(angle_of_repose)
295+
)
296+
297+
ground_interference_term = 1 - C2 * np.exp(-gamma)
298+
temp_air_kelvin = temp_air + 273.15
299+
loss_percentage = (
300+
C1
301+
* effective_snow_weighted
302+
* cosd(surface_tilt)**2
303+
* ground_interference_term
304+
* relative_humidity
305+
/ temp_air_kelvin**2
306+
/ poa_global**0.67
307+
)
308+
309+
return loss_percentage / 100

pvlib/tests/test_snow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ def test_dc_loss_nrel():
9898

9999

100100
def test__townsend_Se():
101-
S = np.array([10, 10, 5, 1, 0, 0, 0, 0, 0, 0, 5, 10])
102-
N = np.array([2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3])
101+
snow_load = np.array([10, 10, 5, 1, 0, 0, 0, 0, 0, 0, 5, 10])
102+
snow_events = np.array([2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3])
103103
expected = np.array([7.5, 7.5, 5, 0, 0, 0, 0, 0, 0, 0, 3.75, 6.66666667])
104-
actual = snow._townsend_Se(S, N)
104+
actual = snow._townsend_effective_snow(snow_load, snow_events)
105105
np.testing.assert_allclose(expected, actual, rtol=1e-07)
106106

107107

0 commit comments

Comments
 (0)