@@ -13,30 +13,38 @@ def _time_delta_in_hours(times):
13
13
return delta .dt .total_seconds ().div (3600 )
14
14
15
15
16
- def fully_covered_nrel (snowfall , threshold_snowfall = 1. ):
16
+ def fully_covered_nrel (snowfall , snow_depth = None , threshold_snowfall = 1. ,
17
+ threshold_depth = 1. ):
17
18
'''
18
- Calculates the timesteps when the row's slant height is fully covered
19
- by snow.
19
+ Calculates the timesteps when modules are fully covered by snow.
20
20
21
21
Parameters
22
22
----------
23
- snowfall : Series
24
- Accumulated snowfall in each time period [cm]
25
-
26
- threshold_snowfall : float, default 1.0
27
- Hourly snowfall above which snow coverage is set to the row's slant
28
- height. [cm/hr]
23
+ snowfall: Series
24
+ Snowfall in each time period. [cm]
25
+ snow_depth: Series, optional
26
+ Snow depth on the ground at the beginning of each time period.
27
+ Must have the same index as ``snowfall``. [cm]
28
+ threshold_snowfall: float, default 1.0
29
+ Hourly snowfall above which the row is fully covered for that hour.
30
+ [cm/hr]
31
+ threshold_depth: float, default 1.0
32
+ Snow depth on the ground, above which snow can affect the modules. [cm]
29
33
30
34
Returns
31
35
----------
32
- boolean : Series
33
- True where the snowfall exceeds the defined threshold to fully cover
34
- the panel.
36
+ covered : Series
37
+ A Series of boolean, True where the snowfall exceeds the defined
38
+ threshold to fully cover the panel.
35
39
36
40
Notes
37
41
-----
38
42
Implements the model described in [1]_ with minor improvements in [2]_.
39
43
44
+ ``snow_depth`` is used to return `False` (not fully covered) when snow
45
+ is less than ``threshold_depth``. This check is described in [2]_ as needed
46
+ for systems with low tilt angle.
47
+
40
48
References
41
49
----------
42
50
.. [1] Marion, B.; Schaefer, R.; Caine, H.; Sanchez, G. (2013).
@@ -56,15 +64,20 @@ def fully_covered_nrel(snowfall, threshold_snowfall=1.):
56
64
hourly_snow_rate .iloc [0 ] = snowfall .iloc [0 ] / timedelta
57
65
else : # can't infer frequency from index
58
66
hourly_snow_rate .iloc [0 ] = 0 # replaces NaN
59
- return hourly_snow_rate > threshold_snowfall
67
+ covered = (hourly_snow_rate > threshold_snowfall )
68
+ # no coverage when no snow on the ground
69
+ if snow_depth is not None :
70
+ covered = covered & (snow_depth >= threshold_depth )
71
+ return covered
60
72
61
73
62
74
def coverage_nrel (snowfall , poa_irradiance , temp_air , surface_tilt ,
63
- initial_coverage = 0 , threshold_snowfall = 1. ,
64
- can_slide_coefficient = - 80. , slide_amount_coefficient = 0.197 ):
75
+ snow_depth = None , initial_coverage = 0 , threshold_snowfall = 1. ,
76
+ threshold_depth = 1. , can_slide_coefficient = - 80. ,
77
+ slide_amount_coefficient = 0.197 ):
65
78
'''
66
- Calculates the fraction of the slant height of a row of modules covered by
67
- snow at every time step.
79
+ Calculates the fraction of the slant height of a row of modules that is
80
+ covered by snow at every time step.
68
81
69
82
Implements the model described in [1]_ with minor improvements in [2]_,
70
83
with the change that the output is in fraction of the row's slant height
@@ -74,20 +87,25 @@ def coverage_nrel(snowfall, poa_irradiance, temp_air, surface_tilt,
74
87
Parameters
75
88
----------
76
89
snowfall : Series
77
- Accumulated snowfall within each time period. [cm]
90
+ Snowfall within each time period. [cm]
78
91
poa_irradiance : Series
79
92
Total in-plane irradiance [W/m^2]
80
93
temp_air : Series
81
94
Ambient air temperature [C]
82
95
surface_tilt : numeric
83
96
Tilt of module's from horizontal, e.g. surface facing up = 0,
84
97
surface facing horizon = 90. [degrees]
98
+ snow_depth : Series, optional
99
+ Snow depth on the ground at the beginning of each time period.
100
+ Must have the same index as ``snowfall``. [cm]
85
101
initial_coverage : float, default 0
86
102
Fraction of row's slant height that is covered with snow at the
87
103
beginning of the simulation. [unitless]
88
- threshold_snowfall : float, default 1.0
89
- Hourly snowfall above which snow coverage is set to the row's slant
90
- height. [cm/hr]
104
+ threshold_snowfall: float, default 1.0
105
+ Hourly snowfall above which the row is fully covered for that hour.
106
+ [cm/hr]
107
+ threshold_depth: float, default 1.0
108
+ Snow depth on the ground, above which snow can affect the modules. [cm]
91
109
can_slide_coefficient : float, default -80.
92
110
Coefficient to determine if snow can slide given irradiance and air
93
111
temperature. [W/(m^2 C)]
@@ -103,8 +121,12 @@ def coverage_nrel(snowfall, poa_irradiance, temp_air, surface_tilt,
103
121
104
122
Notes
105
123
-----
106
- In [1]_, `can_slide_coefficient` is termed `m`, and the value of
107
- `slide_amount_coefficient` is given in tenths of a module's slant height.
124
+ In [1]_, ``can_slide_coefficient`` is termed `m`, and the value of
125
+ ``slide_amount_coefficient`` is given in tenths of a module's slant height.
126
+
127
+ ``snow_depth`` is used to set ``snow_coverage`` to 0 (not fully covered)
128
+ when snow is less than ``threshold_depth``. This check is described in
129
+ [2]_ as needed for systems with low tilt angle.
108
130
109
131
References
110
132
----------
@@ -117,7 +139,8 @@ def coverage_nrel(snowfall, poa_irradiance, temp_air, surface_tilt,
117
139
'''
118
140
119
141
# find times with new snowfall
120
- new_snowfall = fully_covered_nrel (snowfall , threshold_snowfall )
142
+ new_snowfall = fully_covered_nrel (snowfall , snow_depth , threshold_snowfall ,
143
+ threshold_depth )
121
144
122
145
# set up output Series
123
146
snow_coverage = pd .Series (np .nan , index = poa_irradiance .index )
@@ -132,6 +155,13 @@ def coverage_nrel(snowfall, poa_irradiance, temp_air, surface_tilt,
132
155
# don't slide in the interval preceding the snowfall data
133
156
slide_amt .iloc [0 ] = 0
134
157
158
+ if snow_depth is not None :
159
+ # All slides off if snow on the ground is less than threshold_depth.
160
+ # Described in [2] to avoid non-sliding snow for low-tilt systems.
161
+ # Default threshold_depth of 1cm is from [2[ and SAM's implementation.
162
+ # https://github.com/NREL/ssc/issues/1265
163
+ slide_amt [snow_depth < threshold_depth ] = 1.
164
+
135
165
# build time series of cumulative slide amounts
136
166
sliding_period_ID = new_snowfall .cumsum ()
137
167
cumulative_sliding = slide_amt .groupby (sliding_period_ID ).cumsum ()
@@ -143,7 +173,6 @@ def coverage_nrel(snowfall, poa_irradiance, temp_air, surface_tilt,
143
173
snow_coverage .ffill (inplace = True )
144
174
snow_coverage -= cumulative_sliding
145
175
146
- # clean up periods where row is completely uncovered
147
176
return snow_coverage .clip (lower = 0 )
148
177
149
178
0 commit comments