# Buggy code
def soiling_hsu(
    rainfall,
    cleaning_threshold,
    tilt,
    pm2_5,
    pm10,
    depo_veloc={"2_5": 0.004, "10": 0.0009},
    rain_accum_period=pd.Timedelta("1h"),
):
    """
    Calculates soiling ratio given particulate and rain data using the model
    from Humboldt State University [1]_.

    Parameters
    ----------

    rainfall : Series
        Rain accumulated in each time period. [mm]

    cleaning_threshold : float
        Amount of rain in an accumulation period needed to clean the PV
        modules. [mm]

    tilt : float
        Tilt of the PV panels from horizontal. [degree]

    pm2_5 : numeric
        Concentration of airborne particulate matter (PM) with
        aerodynamic diameter less than 2.5 microns. [g/m^3]

    pm10 : numeric
        Concentration of airborne particulate matter (PM) with
        aerodynamicdiameter less than 10 microns. [g/m^3]

    depo_veloc : dict, default {'2_5': 0.4, '10': 0.09}
        Deposition or settling velocity of particulates. [m/s]

    rain_accum_period : Timedelta, default 1 hour
        Period for accumulating rainfall to check against `cleaning_threshold`
        It is recommended that `rain_accum_period` be between 1 hour and
        24 hours.

    Returns
    -------
    soiling_ratio : Series
        Values between 0 and 1. Equal to 1 - transmission loss.

    References
    -----------
    .. [1] M. Coello and L. Boyle, "Simple Model For Predicting Time Series
       Soiling of Photovoltaic Panels," in IEEE Journal of Photovoltaics.
       doi: 10.1109/JPHOTOV.2019.2919628
    .. [2] Atmospheric Chemistry and Physics: From Air Pollution to Climate
       Change. J. Seinfeld and S. Pandis. Wiley and Sons 2001.

    """
    try:
        from scipy.special import erf
    except ImportError:
        raise ImportError("The soiling_hsu function requires scipy.")

    # accumulate rainfall into periods for comparison with threshold
    accum_rain = rainfall.rolling(rain_accum_period, closed="right").sum()
    # cleaning is True for intervals with rainfall greater than threshold
    cleaning_times = accum_rain.index[accum_rain >= cleaning_threshold]

    horiz_mass_rate = (
        pm2_5 * depo_veloc["2_5"] + np.maximum(pm10 - pm2_5, 0.0) * depo_veloc["10"]
    )
    tilted_mass_rate = horiz_mass_rate * cosd(tilt)  # assuming no rain

    # tms -> tilt_mass_rate
    tms_cumsum = np.cumsum(tilted_mass_rate * np.ones(rainfall.shape))

    mass_no_cleaning = pd.Series(index=rainfall.index, data=tms_cumsum)
    # specify dtype so pandas doesn't assume object
    mass_removed = pd.Series(index=rainfall.index, dtype="float64")
    mass_removed[0] = 0.0
    mass_removed[cleaning_times] = mass_no_cleaning[cleaning_times]
    accum_mass = mass_no_cleaning - mass_removed.ffill()

    soiling_ratio = 1 - 0.3437 * erf(0.17 * accum_mass**0.8473)

    return soiling_ratio


# Traceback (ValueError)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-95-ff36a88e4e3a> in <module>
----> 1 soiling_hsu(greensboro_rain, cleaning_threshold=6, tilt=10, pm2_5=1e-5, pm10=1e-5)

~\Projects\pvlib-python\pvlib\losses.py in soiling_hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10, depo_veloc, rain_accum_period)
67
68     # accumulate rainfall into periods for comparison with threshold
---> 69     accum_rain = rainfall.rolling(rain_accum_period, closed='right').sum()
70     # cleaning is True for intervals with rainfall greater than threshold
71     cleaning_times = accum_rain.index[accum_rain >= cleaning_threshold]

~\projects\pvlib-python\venv\lib\site-packages\pandas\core\generic.py in rolling(self, window, min_periods, center, win_type, on, axis, closed)
10145                                    min_periods=min_periods,
10146                                    center=center, win_type=win_type,
10147                                    on=on, axis=axis, closed=closed)
10148
10149         cls.rolling = rolling

~\projects\pvlib-python\venv\lib\site-packages\pandas\core\window.py in rolling(obj, win_type, **kwds)
2624         return Window(obj, win_type=win_type, **kwds)
2625
-> 2626     return Rolling(obj, **kwds)
2627
2628

~\projects\pvlib-python\venv\lib\site-packages\pandas\core\window.py in __init__(self, obj, window, min_periods, center, win_type, axis, on, closed, **kwargs)
63         self.win_freq = None
64         self.axis = obj._get_axis_number(axis) if axis is not None else None
---> 65         self.validate()
66
67     @property

~\projects\pvlib-python\venv\lib\site-packages\pandas\core\window.py in validate(self)
1576                                          timedelta))):
1577
-> 1578             self._validate_monotonic()
1579             freq = self._validate_freq()
1580

~\projects\pvlib-python\venv\lib\site-packages\pandas\core\window.py in _validate_monotonic(self)
1610             formatted = self.on or 'index'
1611             raise ValueError("{0} must be "
-> 1612                              "monotonic".format(formatted))
1613
1614     def _validate_freq(self):

# Fixed code
def soiling_hsu(
    rainfall,
    cleaning_threshold,
    tilt,
    pm2_5,
    pm10,
    depo_veloc=None,
    rain_accum_period=pd.Timedelta("1h"),
):
    """
    Calculates soiling ratio given particulate and rain data using the model
    from Humboldt State University (HSU).

    The HSU soiling model [1]_ returns the soiling ratio, a value between zero
    and one which is equivalent to (1 - transmission loss). Therefore a soiling
    ratio of 1.0 is equivalent to zero transmission loss.

    Parameters
    ----------

    rainfall : Series
        Rain accumulated in each time period. [mm]

    cleaning_threshold : float
        Amount of rain in an accumulation period needed to clean the PV
        modules. [mm]

    tilt : float
        Tilt of the PV panels from horizontal. [degree]

    pm2_5 : numeric
        Concentration of airborne particulate matter (PM) with
        aerodynamic diameter less than 2.5 microns. [g/m^3]

    pm10 : numeric
        Concentration of airborne particulate matter (PM) with
        aerodynamicdiameter less than 10 microns. [g/m^3]

    depo_veloc : dict, default {'2_5': 0.4, '10': 0.09}
        Deposition or settling velocity of particulates. [m/s]

    rain_accum_period : Timedelta, default 1 hour
        Period for accumulating rainfall to check against `cleaning_threshold`
        It is recommended that `rain_accum_period` be between 1 hour and
        24 hours.

    Returns
    -------
    soiling_ratio : Series
        Values between 0 and 1. Equal to 1 - transmission loss.

    References
    -----------
    .. [1] M. Coello and L. Boyle, "Simple Model For Predicting Time Series
       Soiling of Photovoltaic Panels," in IEEE Journal of Photovoltaics.
       doi: 10.1109/JPHOTOV.2019.2919628
    .. [2] Atmospheric Chemistry and Physics: From Air Pollution to Climate
       Change. J. Seinfeld and S. Pandis. Wiley and Sons 2001.

    """
    try:
        from scipy.special import erf
    except ImportError:
        raise ImportError("The soiling_hsu function requires scipy.")

    # never use mutable input arguments
    if depo_veloc is None:
        depo_veloc = {"2_5": 0.004, "10": 0.0009}

    # accumulate rainfall into periods for comparison with threshold
    accum_rain = rainfall.rolling(rain_accum_period, closed="right").sum()
    # cleaning is True for intervals with rainfall greater than threshold
    cleaning_times = accum_rain.index[accum_rain >= cleaning_threshold]

    horiz_mass_rate = (
        pm2_5 * depo_veloc["2_5"] + np.maximum(pm10 - pm2_5, 0.0) * depo_veloc["10"]
    )
    tilted_mass_rate = horiz_mass_rate * cosd(tilt)  # assuming no rain

    # tms -> tilt_mass_rate
    tms_cumsum = np.cumsum(tilted_mass_rate * np.ones(rainfall.shape))

    mass_no_cleaning = pd.Series(index=rainfall.index, data=tms_cumsum)
    # specify dtype so pandas doesn't assume object
    mass_removed = pd.Series(index=rainfall.index, dtype="float64")
    mass_removed[0] = 0.0
    mass_removed[cleaning_times] = mass_no_cleaning[cleaning_times]
    accum_mass = mass_no_cleaning - mass_removed.ffill()

    soiling_ratio = 1 - 0.3437 * erf(0.17 * accum_mass**0.8473)

    return soiling_ratio