Skip to content

Commit 37fba6b

Browse files
jssteinkandersolar
andauthored
Adds ability for soiling.Hsu to accept arbitrary time intervals (other than 1 hour) (#980)
* bug fixes to hsu soiling function. Addresses (#970) * Made changes to the soiling tests Addresses (#970) * update what's new documentation * Fixed stickler-ci issues in test_soiling.py * Fixed errant character in test * allowed function to accept aribtrary time intervals * updated v0.8.0.rst file * Add variable time interval functionality and testing to soiling.hsu(), also corrected horiz_mass_rate equation * fixed typo in test_soiling.py * fixed stickler-ci errors * fixed more formatting errors * more stickler-ci issues fixed * fixed more stickler-ci issues * formatting issues * formatting issues * formatting... * formatting..... * changed variable time interval calculation in hsu model and test * formatting changes * formatting * formatting * formatting * whatsnew PR links * remove needs_pandas_22 from test_soiling * move time interval note from bug fixes to enhancements * stickler * review improvements Co-authored-by: Kevin Anderson <[email protected]>
1 parent 8ca32c9 commit 37fba6b

File tree

3 files changed

+69
-25
lines changed

3 files changed

+69
-25
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ Enhancements
7878
* Added *racking_model*, *module_type*, and *temperature_model_parameters* to
7979
PVSystem, LocalizedPVSystem, SingleAxisTracker, and
8080
LocalizedSingleAxisTracker repr methods. (:issue:`1027`)
81+
* Added ability for :py:func:`pvlib.soiling.hsu` to accept arbitrary time intervals. (:pull:`980`)
8182

8283
Bug fixes
8384
~~~~~~~~~
84-
* Fixed unit and default value errors in :py:func:`pvlib.soiling.hsu`. (:pull:`XXX`)
85+
* Fixed unit and default value errors in :py:func:`pvlib.soiling.hsu`. (:pull:`977`, :pull:`980`)
8586
* Handle NUL characters and fix version column dtype in
8687
:py:func:`~pvlib.iotools.crn.read_crn`. (:issue:`1025`)
8788

pvlib/soiling.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
1313
depo_veloc=None, rain_accum_period=pd.Timedelta('1h')):
1414
"""
15-
Calculates soiling ratio given particulate and rain data using the model
16-
from Humboldt State University (HSU).
15+
Calculates soiling ratio given particulate and rain data using the
16+
Fixed Velocity model from Humboldt State University (HSU).
1717
1818
The HSU soiling model [1]_ returns the soiling ratio, a value between zero
1919
and one which is equivalent to (1 - transmission loss). Therefore a soiling
@@ -76,8 +76,17 @@ def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
7676
# cleaning is True for intervals with rainfall greater than threshold
7777
cleaning_times = accum_rain.index[accum_rain >= cleaning_threshold]
7878

79-
horiz_mass_rate = pm2_5 * depo_veloc['2_5']\
80-
+ np.maximum(pm10 - pm2_5, 0.) * depo_veloc['10'] * 3600
79+
# determine the time intervals in seconds (dt_sec)
80+
dt = rainfall.index
81+
# subtract shifted values from original and convert to seconds
82+
dt_diff = (dt[1:] - dt[:-1]).total_seconds()
83+
# ensure same number of elements in the array, assuming that the interval
84+
# prior to the first value is equal in length to the first interval
85+
dt_sec = np.append(dt_diff[0], dt_diff).astype('float64')
86+
87+
horiz_mass_rate = (
88+
pm2_5 * depo_veloc['2_5'] + np.maximum(pm10 - pm2_5, 0.)
89+
* depo_veloc['10']) * dt_sec
8190
tilted_mass_rate = horiz_mass_rate * cosd(tilt) # assuming no rain
8291

8392
# tms -> tilt_mass_rate

pvlib/tests/test_soiling.py

+54-20
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,24 @@ def expected_output():
1818
end=pd.Timestamp(2019, 1, 1, 23, 59, 0), freq='1h')
1919

2020
expected_no_cleaning = pd.Series(
21-
data=[0.97230454, 0.95036146, 0.93039061, 0.91177978, 0.89427556,
22-
0.8777455 , 0.86211038, 0.84731759, 0.83332881, 0.82011354,
23-
0.80764549, 0.79590056, 0.78485556, 0.77448749, 0.76477312,
24-
0.75568883, 0.74721046, 0.73931338, 0.73197253, 0.72516253,
25-
0.7188578 , 0.71303268, 0.7076616 , 0.70271919],
21+
data=[0.96998483, 0.94623958, 0.92468139, 0.90465654, 0.88589707,
22+
0.86826366, 0.85167258, 0.83606715, 0.82140458, 0.80764919,
23+
0.79476875, 0.78273241, 0.77150951, 0.76106905, 0.75137932,
24+
0.74240789, 0.73412165, 0.72648695, 0.71946981, 0.7130361,
25+
0.70715176, 0.70178307, 0.69689677, 0.69246034],
2626
index=dt)
2727
return expected_no_cleaning
2828

2929
@pytest.fixture
3030
def expected_output_1():
3131
dt = pd.date_range(start=pd.Timestamp(2019, 1, 1, 0, 0, 0),
32-
end=pd.Timestamp(2019, 1, 1, 23, 59, 0), freq='1h')
32+
end=pd.Timestamp(2019, 1, 1, 23, 59, 0), freq='1h')
3333
expected_output_1 = pd.Series(
34-
data=[0.9872406 , 0.97706269, 0.96769693, 0.95884032, 1.,
35-
0.9872406 , 0.97706269, 0.96769693, 1. , 1. ,
36-
0.9872406 , 0.97706269, 0.96769693, 0.95884032, 0.95036001,
37-
0.94218263, 0.93426236, 0.92656836, 0.91907873, 0.91177728,
38-
0.9046517 , 0.89769238, 0.89089165, 0.88424329],
34+
data=[0.98484972, 0.97277367, 0.96167471, 0.95119603, 1.,
35+
0.98484972, 0.97277367, 0.96167471, 1., 1.,
36+
0.98484972, 0.97277367, 0.96167471, 0.95119603, 0.94118234,
37+
0.93154854, 0.922242, 0.91322759, 0.90448058, 0.89598283,
38+
0.88772062, 0.87968325, 0.8718622, 0.86425049],
3939
index=dt)
4040
return expected_output_1
4141

@@ -44,15 +44,31 @@ def expected_output_2():
4444
dt = pd.date_range(start=pd.Timestamp(2019, 1, 1, 0, 0, 0),
4545
end=pd.Timestamp(2019, 1, 1, 23, 59, 0), freq='1h')
4646
expected_output_2 = pd.Series(
47-
data=[0.97229869, 0.95035106, 0.93037619, 0.91176175, 1.,
48-
1. , 1. , 0.97229869, 1. , 1. ,
49-
1. , 1. , 0.97229869, 0.95035106, 0.93037619,
50-
0.91176175, 0.89425431, 1. , 1. , 1. ,
51-
1. , 0.97229869, 0.95035106, 0.93037619],
47+
data=[0.95036261, 0.91178179, 0.87774818, 0.84732079, 1.,
48+
1., 1., 0.95036261, 1., 1.,
49+
1., 1., 0.95036261, 0.91178179, 0.87774818,
50+
0.84732079, 0.8201171, 1., 1., 1.,
51+
1., 0.95036261, 0.91178179, 0.87774818],
5252
index=dt)
53-
5453
return expected_output_2
5554

55+
56+
@pytest.fixture
57+
def expected_output_3():
58+
dt = pd.date_range(start=pd.Timestamp(2019, 1, 1, 0, 0, 0),
59+
end=pd.Timestamp(2019, 1, 1, 23, 59, 0), freq='1h')
60+
timedelta = [0, 0, 0, 0, 0, 30, 0, 30, 0, 30, 0, -30,
61+
-30, -30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
62+
dt_new = dt + pd.to_timedelta(timedelta, 'm')
63+
expected_output_3 = pd.Series(
64+
data=[0.96576705, 0.9387675, 0.91437615, 0.89186852, 1.,
65+
1., 0.98093819, 0.9387675, 1., 1.,
66+
1., 1., 0.96576705, 0.9387675, 0.90291005,
67+
0.88122293, 0.86104089, 1., 1., 1.,
68+
0.96576705, 0.9387675, 0.91437615, 0.89186852],
69+
index=dt_new)
70+
return expected_output_3
71+
5672
@pytest.fixture
5773
def rainfall_input():
5874

@@ -105,12 +121,30 @@ def test_hsu_defaults(rainfall_input, expected_output_1):
105121
Test Soiling HSU function with default deposition velocity and default rain
106122
accumulation period.
107123
"""
108-
result = hsu(
109-
rainfall=rainfall_input, cleaning_threshold=0.5, tilt=0.0,
110-
pm2_5=1.0e-2,pm10=2.0e-2)
124+
result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5, tilt=0.0,
125+
pm2_5=1.0e-2, pm10=2.0e-2)
111126
assert np.allclose(result.values, expected_output_1)
112127

113128

129+
@requires_scipy
130+
def test_hsu_variable_time_intervals(rainfall_input, expected_output_3):
131+
"""
132+
Test Soiling HSU function with variable time intervals.
133+
"""
134+
depo_veloc = {'2_5': 1.0e-4, '10': 1.0e-4}
135+
rain = pd.DataFrame(data=rainfall_input)
136+
# define time deltas in minutes
137+
timedelta = [0, 0, 0, 0, 0, 30, 0, 30, 0, 30, 0, -30,
138+
-30, -30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
139+
rain['mins_added'] = pd.to_timedelta(timedelta, 'm')
140+
rain['new_time'] = rain.index + rain['mins_added']
141+
rain_var_times = rain.set_index('new_time').iloc[:, 0]
142+
result = hsu(
143+
rainfall=rain_var_times, cleaning_threshold=0.5, tilt=50.0,
144+
pm2_5=1, pm10=2, depo_veloc=depo_veloc,
145+
rain_accum_period=pd.Timedelta('2h'))
146+
assert np.allclose(result, expected_output_3)
147+
114148
@pytest.fixture
115149
def greensboro_rain():
116150
# get TMY3 data with rain

0 commit comments

Comments
 (0)