Skip to content

Commit 1b31636

Browse files
nappaillavcwhanse
andcommitted
add Boyle/Coello (Humboldt State Univ) soiling model (#850)
* initial_commit_Boyle_Coello_soiling_model * stickler-ci_correction * E128_Error * E128_Error * E128_Error * format_corrections * updated soiling_hsu * updated soiling_hsu * added unit test * added unit test * added unit test * added unit test * added unit test * corrections_to_test_losses * corrections_to_test_losses * cleaning Test function * cleaning Test function * cleaning Test function * update api.rst, whatsnew * merge_correction * merge_corrections * updated_init Co-authored-by: Cliff Hansen <[email protected]>
1 parent bb081ed commit 1b31636

File tree

5 files changed

+193
-1
lines changed

5 files changed

+193
-1
lines changed

docs/sphinx/source/api.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ PVWatts model
298298
pvsystem.pvwatts_ac
299299
pvsystem.pvwatts_losses
300300

301-
Functions for fitting PV models
301+
Functions for fitting diode models
302302
-------------------------------
303303
.. autosummary::
304304
:toctree: generated/
@@ -307,6 +307,14 @@ Functions for fitting PV models
307307
ivtools.fit_sdm_cec_sam
308308
ivtools.fit_sdm_desoto
309309

310+
Losses
311+
------
312+
.. autosummary::
313+
:toctree: generated/
314+
315+
losses.soiling_hsu
316+
317+
310318
Other
311319
-----
312320

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ Enhancements
1717
* Added :py:func:`~pvlib.iotools.get_pvgis_tmy` to get PVGIS TMY datasets.
1818
* Added :py:func:`~pvlib.iotools.parse_epw` to parse a file-like buffer
1919
containing weather data in the EPW format.
20+
* Added a new module `pvlib.losses` for various loss models.
21+
* Added the Humboldt State University soiling model
22+
:py:func:`~pvlib.losses.soiling_hsu`. (:issue:`739`)
2023

2124
Bug fixes
2225
~~~~~~~~~
@@ -39,3 +42,4 @@ Contributors
3942
~~~~~~~~~~~~
4043
* Kevin Anderson (:ghuser:`kanderso-nrel`)
4144
* Mark Mikofski (:ghuser:`mikofski`)
45+
* Valliappan CA (:ghuser:`nappaillav`)

pvlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
from pvlib import modelchain # noqa: F401
1515
from pvlib import singlediode # noqa: F401
1616
from pvlib import bifacial # noqa: F401
17+
from pvlib import losses # noqa: F401

pvlib/losses.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
This module contains functions for losses of various types: soiling, mismatch,
4+
snow cover, etc.
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from pvlib.tools import cosd
10+
11+
12+
def soiling_hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
13+
depo_veloc={'2_5': 0.004, '10': 0.0009},
14+
rain_accum_period=pd.Timedelta('1h')):
15+
"""
16+
Calculates soiling ratio given particulate and rain data using the model
17+
from Humboldt State University [1]_.
18+
19+
Parameters
20+
----------
21+
22+
rainfall : Series
23+
Rain accumulated in each time period. [mm]
24+
25+
cleaning_threshold : float
26+
Amount of rain in an accumulation period needed to clean the PV
27+
modules. [mm]
28+
29+
tilt : float
30+
Tilt of the PV panels from horizontal. [degree]
31+
32+
pm2_5 : numeric
33+
Concentration of airborne particulate matter (PM) with
34+
aerodynamic diameter less than 2.5 microns. [g/m^3]
35+
36+
pm10 : numeric
37+
Concentration of airborne particulate matter (PM) with
38+
aerodynamicdiameter less than 10 microns. [g/m^3]
39+
40+
depo_veloc : dict, default {'2_5': 0.4, '10': 0.09}
41+
Deposition or settling velocity of particulates. [m/s]
42+
43+
rain_accum_period : Timedelta, default 1 hour
44+
Period for accumulating rainfall to check against `cleaning_threshold`
45+
It is recommended that `rain_accum_period` be between 1 hour and
46+
24 hours.
47+
48+
Returns
49+
-------
50+
soiling_ratio : Series
51+
Values between 0 and 1. Equal to 1 - transmission loss.
52+
53+
References
54+
-----------
55+
.. [1] M. Coello and L. Boyle, "Simple Model For Predicting Time Series
56+
Soiling of Photovoltaic Panels," in IEEE Journal of Photovoltaics.
57+
doi: 10.1109/JPHOTOV.2019.2919628
58+
.. [2] Atmospheric Chemistry and Physics: From Air Pollution to Climate
59+
Change. J. Seinfeld and S. Pandis. Wiley and Sons 2001.
60+
61+
"""
62+
try:
63+
from scipy.special import erf
64+
except ImportError:
65+
raise ImportError("The soiling_hsu function requires scipy.")
66+
67+
# accumulate rainfall into periods for comparison with threshold
68+
accum_rain = rainfall.rolling(rain_accum_period, closed='right').sum()
69+
# cleaning is True for intervals with rainfall greater than threshold
70+
cleaning_times = accum_rain.index[accum_rain >= cleaning_threshold]
71+
72+
horiz_mass_rate = pm2_5 * depo_veloc['2_5']\
73+
+ np.maximum(pm10 - pm2_5, 0.) * depo_veloc['10']
74+
tilted_mass_rate = horiz_mass_rate * cosd(tilt) # assuming no rain
75+
76+
# tms -> tilt_mass_rate
77+
tms_cumsum = np.cumsum(tilted_mass_rate * np.ones(rainfall.shape))
78+
79+
mass_no_cleaning = pd.Series(index=rainfall.index, data=tms_cumsum)
80+
mass_removed = pd.Series(index=rainfall.index)
81+
mass_removed[0] = 0.
82+
mass_removed[cleaning_times] = mass_no_cleaning[cleaning_times]
83+
accum_mass = mass_no_cleaning - mass_removed.ffill()
84+
85+
soiling_ratio = 1 - 0.3437 * erf(0.17 * accum_mass**0.8473)
86+
87+
return soiling_ratio

pvlib/tests/test_losses.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import pandas as pd
2+
from pandas.util.testing import assert_series_equal
3+
from pvlib.losses import soiling_hsu
4+
from conftest import requires_scipy
5+
import pytest
6+
7+
8+
@pytest.fixture
9+
def expected_output():
10+
# Sample output (calculated manually)
11+
dt = pd.date_range(start=pd.datetime(2019, 1, 1, 0, 0, 0),
12+
end=pd.datetime(2019, 1, 1, 23, 59, 0), freq='1h')
13+
14+
expected_no_cleaning = pd.Series(
15+
data=[0.884980357535360, 0.806308930084762, 0.749974647038078,
16+
0.711804155175089, 0.687489866078621, 0.672927554408964,
17+
0.664714899337491, 0.660345851212099, 0.658149551658860,
18+
0.657104593968981, 0.656633344364056, 0.656431630729954,
19+
0.656349579062171, 0.656317825078228, 0.656306121502393,
20+
0.656302009396500, 0.656300630853678, 0.656300189543417,
21+
0.656300054532516, 0.656300015031680, 0.656300003971846,
22+
0.656300001006533, 0.656300000244750, 0.656300000057132],
23+
index=dt)
24+
25+
return expected_no_cleaning
26+
27+
28+
@pytest.fixture
29+
def expected_output_2(expected_output):
30+
# Sample output (calculated manually)
31+
dt = pd.date_range(start=pd.datetime(2019, 1, 1, 0, 0, 0),
32+
end=pd.datetime(2019, 1, 1, 23, 59, 0), freq='1h')
33+
34+
expected_no_cleaning = expected_output
35+
36+
expected = pd.Series(index=dt)
37+
expected[dt[:4]] = expected_no_cleaning[dt[:4]]
38+
expected[dt[4:7]] = 1.
39+
expected[dt[7]] = expected_no_cleaning[dt[0]]
40+
expected[dt[8:12]] = 1.
41+
expected[dt[12:17]] = expected_no_cleaning[dt[:5]]
42+
expected[dt[17:21]] = 1.
43+
expected[dt[21:]] = expected_no_cleaning[:3]
44+
45+
return expected
46+
47+
48+
@pytest.fixture
49+
def rainfall_input():
50+
51+
dt = pd.date_range(start=pd.datetime(2019, 1, 1, 0, 0, 0),
52+
end=pd.datetime(2019, 1, 1, 23, 59, 0), freq='1h')
53+
rainfall = pd.Series(
54+
data=[0., 0., 0., 0., 1., 0., 0., 0., 0.5, 0.5, 0., 0., 0., 0., 0.,
55+
0., 0.3, 0.3, 0.3, 0.3, 0., 0., 0., 0.], index=dt)
56+
return rainfall
57+
58+
59+
@requires_scipy
60+
def test_soiling_hsu_no_cleaning(rainfall_input, expected_output):
61+
"""Test Soiling HSU function"""
62+
63+
rainfall = rainfall_input
64+
pm2_5 = 1.0
65+
pm10 = 2.0
66+
depo_veloc = {'2_5': 1.0, '10': 1.0}
67+
tilt = 0.
68+
expected_no_cleaning = expected_output
69+
70+
result = soiling_hsu(rainfall=rainfall, cleaning_threshold=10., tilt=tilt,
71+
pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
72+
rain_accum_period=pd.Timedelta('1h'))
73+
assert_series_equal(result, expected_no_cleaning)
74+
75+
76+
@requires_scipy
77+
def test_soiling_hsu(rainfall_input, expected_output_2):
78+
"""Test Soiling HSU function"""
79+
80+
rainfall = rainfall_input
81+
pm2_5 = 1.0
82+
pm10 = 2.0
83+
depo_veloc = {'2_5': 1.0, '10': 1.0}
84+
tilt = 0.
85+
expected = expected_output_2
86+
87+
# three cleaning events at 4:00-6:00, 8:00-11:00, and 17:00-20:00
88+
result = soiling_hsu(rainfall=rainfall, cleaning_threshold=0.5, tilt=tilt,
89+
pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
90+
rain_accum_period=pd.Timedelta('3h'))
91+
92+
assert_series_equal(result, expected)

0 commit comments

Comments
 (0)