8
8
from pvlib .tools import sind
9
9
10
10
11
- def _snow_slide_amount (surface_tilt , sliding_coefficient = 1.97 ,
12
- time_step = 1 ):
13
- '''
14
- Calculates the amount of snow that slides off in each time step.
15
-
16
- Parameters
17
- ----------
18
- surface_tilt : numeric
19
- Surface tilt angles in decimal degrees. Tilt must be >=0 and
20
- <=180. The tilt angle is defined as degrees from horizontal
21
- (e.g. surface facing up = 0, surface facing horizon = 90).
22
-
23
-
24
- sliding_coefficient : numeric
25
- An empirically determined coefficient used in [1] to determine
26
- how much snow slides off if a sliding event occurs.
27
-
28
- time_step: float
29
- Period of the data. [hour]
30
-
31
- Returns
32
- ----------
33
- slide_amount : numeric
34
- The fraction of panel slant height from which snow slides off
35
- at each time step, in tenths of the panel's slant height.
36
- '''
37
-
38
- return sliding_coefficient / 10 * sind (surface_tilt ) * time_step
39
-
40
-
41
- def _snow_slide_event (poa_irradiance , temperature ,
42
- m = - 80 ):
43
- '''
44
- Calculates when snow sliding events will occur.
45
-
46
- Parameters
47
- ----------
48
- poa_irradiance : numeric
49
- Total in-plane irradiance [W/m^2]
50
-
51
- temperature : numeric
52
- Ambient air temperature at the surface [C]
53
-
54
- m : numeric
55
- A coefficient used in the model described in [1]. [W/m^2 C]
56
-
57
- Returns
58
- ----------
59
- slide_event : boolean array
60
- True if the condiditions are suitable for a snow slide event.
61
- False elsewhere.
62
- '''
63
-
64
- return temperature > poa_irradiance / m
11
+ def _time_delta_in_hours (times ):
12
+ delta = times .to_series ().diff ()
13
+ return delta .dt .seconds .div (3600 )
65
14
66
15
67
- def fully_covered_panel (snow_data , time_step = 1 ,
68
- snow_data_type = "snowfall" ):
16
+ def fully_covered (snowfall , threshold = 1. ):
69
17
'''
70
- Calculates the timesteps when the panel is presumed to be fully covered
18
+ Calculates the timesteps when the row's slant height is fully covered
71
19
by snow.
72
20
73
21
Parameters
74
22
----------
75
- snow_data : numeric
76
- Time series data on either snowfall (cm/hr) or ground snow depth (cm).
77
- The type of data should be specified in snow_data_type.
23
+ snowfall : Series
24
+ Accumulated snowfall in each time period [cm]
78
25
79
- time_step: float
80
- Period of the data. [hour]
81
-
82
- snow_data_type : string
83
- Defines what type of data is being passed as snow_data. Acceptable
84
- values are "snowfall" and "snow_depth".
26
+ threshold : float, default 1.0
27
+ Minimum hourly snowfall to cover a row's slant height. [cm/hr]
85
28
86
29
Returns
87
30
----------
88
- fully_covered_mask : boolean array
31
+ boolean: Series
89
32
True where the snowfall exceeds the defined threshold to fully cover
90
- the panel. False elsewhere.
33
+ the panel.
91
34
92
35
Notes
93
36
-----
94
- Implements the model described in [1] with minor improvements in [2].
37
+ Implements the model described in [1]_ with minor improvements in [2]_ .
95
38
96
39
References
97
40
----------
@@ -101,60 +44,38 @@ def fully_covered_panel(snow_data, time_step=1,
101
44
.. [2] Ryberg, D; Freeman, J. "Integration, Validation, and Application
102
45
of a PV Snow Coverage Model in SAM" (2017) NREL Technical Report
103
46
'''
104
- if snow_data_type == "snow_depth" :
105
- prev_data = snow_data .shift (1 )
106
- prev_data [0 ] = 0
107
- snowfall = snow_data - prev_data
108
- elif snow_data_type == "snowfall" :
109
- snowfall = snow_data
110
- else :
111
- raise ValueError ('snow_data_type was not specified or was not set to a'
112
- 'valid option (snowfall, snow_depth).' )
113
-
114
- time_adjusted = snowfall / time_step
115
- fully_covered_mask = time_adjusted >= 1
116
- return fully_covered_mask
117
-
118
-
119
- def snow_coverage_model (snow_data , snow_data_type ,
120
- poa_irradiance , temperature , surface_tilt ,
121
- time_step = 1 , m = - 80 , sliding_coefficient = 1.97 ):
47
+ delta = snowfall .index .to_series ().diff () # [0] will be NaT
48
+ timestep = delta .dt .seconds .div (3600 ) # convert to hours
49
+ time_adjusted = snowfall / timestep
50
+ time_adjusted .iloc [0 ] = 0 # replace NaN from NaT / timestep
51
+ return time_adjusted >= threshold
52
+
53
+
54
+ def snow_coverage_nrel (snowfall , poa_irradiance , temperature , surface_tilt ,
55
+ threshold_snowfall = 1. , m = - 80 ,
56
+ sliding_coefficient = 0.197 ):
122
57
'''
123
58
Calculates the fraction of the slant height of a row of modules covered by
124
59
snow at every time step.
125
60
126
61
Parameters
127
62
----------
128
- snow_data : Series
129
- Time series data on either snowfall or ground snow depth. The type of
130
- data should be specified in snow_data_type. The original model was
131
- designed for ground snowdepth only. (cm/hr or cm)
132
-
133
- snow_data_type : string
134
- Defines what type of data is being passed as snow_data. Acceptable
135
- values are "snowfall" and "snow_depth". "snowfall" will be in units of
136
- cm/hr. "snow_depth" is in units of cm.
137
-
63
+ snowfall : Series
64
+ Accumulated snowfall at the end of each time period. [cm]
138
65
poa_irradiance : Series
139
- Total in-plane irradiance (W/m^2)
140
-
66
+ Total in-plane irradiance [W/m^2]
141
67
temperature : Series
142
- Ambient air temperature at the surface (C)
143
-
68
+ Ambient air temperature at the surface [C]
144
69
surface_tilt : numeric
145
- Surface tilt angles in decimal degrees. Tilt must be >=0 and
146
- <=180. The tilt angle is defined as degrees from horizontal
147
- (e.g. surface facing up = 0, surface facing horizon = 90).
148
-
149
- time_step: float
150
- Period of the data. [hour]
151
-
152
- sliding coefficient : numeric
153
- Empirically determined coefficient used in [1] to determine how much
154
- snow slides off if a sliding event occurs.
155
-
70
+ Tilt of module's from horizontal, e.g. surface facing up = 0,
71
+ surface facing horizon = 90. Must be between 0 and 180. [degrees]
72
+ threshold_snowfall : float, default 1.0
73
+ Minimum hourly snowfall to cover a row's slant height. [cm/hr]
156
74
m : numeric
157
- A coefficient used in the model described in [1]. [W/(m^2 C)]
75
+ A coefficient used in the model described in [1]_. [W/(m^2 C)]
76
+ sliding coefficient : numeric
77
+ Empirically determined coefficient used in [1]_ to determine how much
78
+ snow slides off in each time period. [unitless]
158
79
159
80
Returns
160
81
-------
@@ -164,50 +85,50 @@ def snow_coverage_model(snow_data, snow_data_type,
164
85
165
86
Notes
166
87
-----
167
- Implements the model described in [1] with minor improvements in [2].
168
- Currently only validated for fixed tilt systems.
88
+ Initial snow coverage is assumed to be zero. Implements the model described
89
+ in [1]_ with minor improvements in [2]_. Validated for fixed tilt systems.
169
90
170
91
References
171
92
----------
172
93
.. [1] Marion, B.; Schaefer, R.; Caine, H.; Sanchez, G. (2013).
173
94
“Measured and modeled photovoltaic system energy losses from snow for
174
95
Colorado and Wisconsin locations.” Solar Energy 97; pp.112-121.
175
- .. [2] Ryberg, D; Freeman, J. "Integration, Validation, and Application
176
- of a PV Snow Coverage Model in SAM" (2017) NREL Technical Report
96
+ .. [2] Ryberg, D; Freeman, J. (2017). "Integration, Validation, and
97
+ Application of a PV Snow Coverage Model in SAM" NREL Technical Report
98
+ NREL/TP-6A20-68705
177
99
'''
178
100
179
- full_coverage_events = fully_covered_panel (snow_data ,
180
- time_step = time_step ,
181
- snow_data_type = snow_data_type )
182
- snow_coverage = pd .Series (np .full (len (snow_data ), np .nan ))
183
- snow_coverage = snow_coverage .reindex (snow_data .index )
184
- snow_coverage [full_coverage_events ] = 1
185
- slide_events = _snow_slide_event (poa_irradiance , temperature )
186
- slide_amount = _snow_slide_amount (surface_tilt , sliding_coefficient ,
187
- time_step )
188
- slidable_snow = ~ np .isnan (snow_coverage )
189
- while (np .any (slidable_snow )):
190
- new_slides = np .logical_and (slide_events , slidable_snow )
191
- snow_coverage [new_slides ] -= slide_amount
192
- new_snow_coverage = snow_coverage .fillna (method = "ffill" , limit = 1 )
193
- slidable_snow = np .logical_and (~ np .isnan (new_snow_coverage ),
194
- np .isnan (snow_coverage ))
195
- slidable_snow = np .logical_and (slidable_snow , new_snow_coverage > 0 )
196
- snow_coverage = new_snow_coverage
197
-
198
- new_slides = np .logical_and (slide_events , slidable_snow )
199
- snow_coverage [new_slides ] -= slide_amount
200
-
201
- snow_coverage = snow_coverage .fillna (method = "ffill" )
202
- snow_coverage = snow_coverage .fillna (value = 0 )
101
+ # set up output Series
102
+ snow_coverage = pd .Series (index = poa_irradiance .index , data = np .nan )
103
+ snow_events = snowfall [fully_covered (snowfall , threshold_snowfall )]
104
+
105
+ can_slide = temperature > poa_irradiance / m
106
+ slide_amt = sliding_coefficient * sind (surface_tilt ) * \
107
+ _time_delta_in_hours (poa_irradiance .index )
108
+
109
+ uncovered = pd .Series (0.0 , index = poa_irradiance .index )
110
+ uncovered [can_slide ] = slide_amt [can_slide ]
111
+
112
+ windows = [(ev , ne ) for (ev , ne ) in
113
+ zip (snow_events .index [:- 1 ], snow_events .index [1 :])]
114
+ # add last time window
115
+ windows .append ((snow_events .index [- 1 ], snowfall .index [- 1 ]))
116
+
117
+ for (ev , ne ) in windows :
118
+ filt = (snow_coverage .index > ev ) & (snow_coverage .index <= ne )
119
+ snow_coverage [ev ] = 1.0
120
+ snow_coverage [filt ] = 1.0 - uncovered [filt ].cumsum ()
121
+
122
+ # clean up periods where row is completely uncovered
203
123
snow_coverage [snow_coverage < 0 ] = 0
124
+ snow_coverage = snow_coverage .fillna (value = 0. )
204
125
return snow_coverage
205
126
206
127
207
- def DC_loss_factor (snow_coverage , num_strings ):
128
+ def snow_loss_factor (snow_coverage , num_strings ):
208
129
'''
209
130
Calculates the DC loss due to snow coverage. Assumes that if a string is
210
- even partially covered by snow, it produces 0W.
131
+ partially covered by snow, it produces 0W.
211
132
212
133
Parameters
213
134
----------
@@ -220,6 +141,6 @@ def DC_loss_factor(snow_coverage, num_strings):
220
141
Returns
221
142
-------
222
143
loss : numeric
223
- DC loss due to snow coverage at each time step.
144
+ fraction of DC capacity loss due to snow coverage at each time step.
224
145
'''
225
146
return np .ceil (snow_coverage * num_strings ) / num_strings
0 commit comments