Repository: pvlib/pvlib-python
Pull request #1395 (2022-01-26 20:24:16)

Adding PVGIS horizon data retrieval method
 - [x] Closes #1295, #758
 - [x] I am familiar with the [contributing guidelines](https://pvlib-python.readthedocs.io/en/latest/contributing.html)
 - [x] Tests added
 - [x] Updates entries to [`docs/sphinx/source/api.rst`](https://github.com/pvlib/pvlib-python/blob/master/docs/sphinx/source/api.rst) for API changes.
 - [x] Adds description and name entries in the appropriate "what's new" file in [`docs/sphinx/source/whatsnew`](https://github.com/pvlib/pvlib-python/tree/master/docs/sphinx/source/whatsnew) for all changes. Includes link to the GitHub Issue with `` :issue:`num` `` or this Pull Request with `` :pull:`num` ``. Includes contributor name and/or GitHub username (link with `` :ghuser:`user` ``).
 - [x] New code is fully documented. Includes [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) compliant docstrings, examples, and comments where necessary.
 - [x] Pull request is nearly complete and ready for detailed review.
 - [x] Maintainer: Appropriate GitHub Labels and Milestone are assigned to the Pull Request and linked Issue.

This PR adds a function to fetch a horizon profile from PVGIS.

~~In this request, I've added two main functions: one in a new module horizon.py that, given an SRTM DEM, calculates a horizon profile in terms of (azimuth, elevation) manually, and an addition in iotools that fetches the same from pvgis (#1295 ). I also incorporated a few functions from abandoned #758. I've tested that they're compatible, but have not verified the results.~~

~~Along with #1295, this gives pvlib 3 potential ways of retrieving the horizon profile. I'm going to be testing them against each other and collected field data like @mikofski suggested in #1295 . I'll add more functionality as I go along but I wanted to check in with @cwhanse and @jsstein with what we have now. I'm currently working on retrieving raw SRTM data automatically, as well.~~

cwhanse: @bgpierc before we start polishing in earnest, could you clear up the stickler items? Makes it much easier to review. My first reactions are 1) that many of the utility functions in `horizon.py` would be better as private, and 2) several added dependencies should be optional.
bgpierc: @cwhanse fixed stickler errors, and added underscores for private functions. How should I specify optional dependencies?
kanderso-nrel: Thanks @bgpierc!

> How should I specify optional dependencies?

Add an entry for each new dependency here: https://github.com/pvlib/pvlib-python/blob/master/setup.py#L56-L58

And then, rather than importing the optional dependencies at the top of the module like normal (which, when the new functions are eventually added to an `__init__.py` somewhere, will cause `import pvlib` to fail if the optional deps are not available), import them inside the functions that use them like this: https://github.com/pvlib/pvlib-python/blob/master/pvlib/bifacial.py#L95
bgpierc: Thanks @cwhanse , went through and made the changes. I did have a question about attributing the code from #758 ; is there a permalink to that pull request if it's closed? Is there a better way to reference that?

I moved those functions to shading.py, I agree they're better there. I have kept the functions in horizon.py public, as I think they're either needed or useful to expose to the end user. 
kanderso-nrel: > I did have a question about attributing the code from https://github.com/pvlib/pvlib-python/pull/758 ; is there a permalink to that pull request if it's closed? Is there a better way to reference that?

Pull request links are valid even after closing, but in the source code we typically just refer to `GH #758` or similar rather than the full url.

I think the ideal solution to retain commit authorship would have been to preserve the original commits themselves using git rather than copy/pasting their content and making new commits.  Then the original author would show up in in this PR's commit history.  Might be too late for that now unless you basically start over.  

Regardless, we should be sure to include the original author in the eventual "what's new" entry!
adriesse: > This didn't effect the overall shape of the output elevation, just the size of the elevation angle. It seems to match up reasonably? closely to pvgis & the method provided in #1295 (see figure); maybe due to using different versions/interpolations of the SRTM?

If the green line in the diagram above is supposed to be close to the others, then I would say it does not match up reasonably.  A possible reason for this could be the horizon limit.  The default 500m was used it is likely to be too small.  10 km might be a better default, but even that will be too small in many locations.
cwhanse: > I think the ideal solution to retain commit authorship would have been to preserve the original commits themselves using git rather than copy/pasting their content and making new commits.

I agree in principle, but in this case #758 contains a lot of other code that isn't going forward in this PR.

bgpierc: > The default 500m was used it is likely to be too small. 10 km might be a better default, but even that will be too small in many locations.

I'm working on adding larger ranges; this involves stitching DEM tiles together as an individual tile may be too small if the point of interest in the a corner or edge of the DEM. 
bgpierc: Ok, I added a function to download the DEM tiles. I have it accept a latitude and longitude and download which ever DEM tile that contains it. I made a new module in iotools because I wasn't sure where else to put it since it's introducing a new data source, is that good? 

I choose to use [CGIAR ](https://srtm.csi.cgiar.org/srtmdata/) as a source instead of [USGS EarthExplorer](https://earthexplorer.usgs.gov/) because CGIAR doesn't require a login and I didn't think we wanted to have to deal with authentication. 
mikofski: @bgpierc  sorry if you already answered this, but I do use USGS Earth Explorer - so if I have already downloaded a DEM file or four, can I still use `horizon.py` to calculate the horizon with the DEM file on my local disk? Or does this only retrieve data from CGIAR? Thanks!
bgpierc: @mikofski yep, as long as the `elevation` argument to horizon_map is a 2D array with values being elevation in meters, it'll work. You'll have to be careful to select the correct resolution, usually 30m or 90m. I'm also looking into differences between various SRTM releases/versions as well, like void-filled vs non-void filled, different interpolation methods, etc. 
bgpierc: Sorry, meant to get back to this sooner. Publication on the DEM method to calculate the horizon map manually is coming along slower then expected, mostly due to difficulties getting validation data, so I took that out for now.

There are now three things added in this commit:

1.  horizon.py , which only contains the direct and diffuse adjustments from #758 for now
2.  a method get_pvgis_horizon in iotools which uses requests to fetch from pvgis's API
3.  iotools/cgiar.py, which adds a method to programmatically download a DEM for a given lat/lon

I also put in a test for pvgis, hopefully I did it right. I think it makes sense to start with this and then add the manual horizon calculation in later to avoid adding too much at once. 
kanderso-nrel: Thanks @bgpierc, is this PR ready for a close review?  All the new code will need tests to verify that they execute without error, work with all the advertised types, etc. 

Also it seems like the new functions in `shading.py` and `horizon.py` are duplicates of each other, unless I'm missing something. 
bgpierc: Hi @kanderso-nrel oh, I forgot that those are in `shading.py` already. That probably makes more sense; I can add a new module `topography.py` or something like that later on for all the code with the DEMs since you can do other things with them that are not related to the horizon. 

I added a test for the pvgis function, but I am unsure how to do one for `iotools/cgiar.py`. The DEM files are rather large (~70MB) so it may not be ideal to distribute one for testing. 
kanderso-nrel: > I added a test for the pvgis function, but I am unsure how to do one for iotools/cgiar.py. The DEM files are rather large (~70MB) so it may not be ideal to distribute one for testing.

I agree a 70MB file is quite large for pvlib.  I'm not familiar with DEMs but it looks like it's more or less a 2d array of elevation data. Instead of testing against the full grid, would it be reasonable to determine the expected elevations for just a few locations and hardcode those values in the tests?
jsstein: Is there a way to have the test include the download the file as part of the test?

From: Kevin Anderson ***@***.***>
Reply-To: pvlib/pvlib-python ***@***.***>
Date: Monday, October 31, 2022 at 12:06 PM
To: pvlib/pvlib-python ***@***.***>
Cc: Joshua Stein ***@***.***>, Mention ***@***.***>
Subject: [EXTERNAL] Re: [pvlib/pvlib-python] Adding horizon data retrieval methods (PR #1395)


I added a test for the pvgis function, but I am unsure how to do one for iotools/cgiar.py. The DEM files are rather large (~70MB) so it may not be ideal to distribute one for testing.

I agree a 70MB file is quite large for pvlib. I'm not familiar with DEMs but it looks like it's more or less a 2d array of elevation data. Instead of testing against the full grid, would it be reasonable to determine the expected elevations for just a few locations and hardcode those values in the tests?

—
Reply to this email directly, view it on GitHub<https://gcc02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fpvlib%2Fpvlib-python%2Fpull%2F1395%23issuecomment-1297471779&data=05%7C01%7Cjsstein%40sandia.gov%7C173ab1500e214aa7d95208dabb6a9601%7C7ccb5a20a303498cb0c129007381b574%7C1%7C0%7C638028363714631122%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=YoYeN6Y01nIBTU2VfXGCFVFMQjuAG5JaNgOzOkS8KFg%3D&reserved=0>, or unsubscribe<https://gcc02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FABJES5BQGPW2JWZEBKP3KOTWGADAPANCNFSM5M4AUZCQ&data=05%7C01%7Cjsstein%40sandia.gov%7C173ab1500e214aa7d95208dabb6a9601%7C7ccb5a20a303498cb0c129007381b574%7C1%7C0%7C638028363714631122%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=6s2UlGURek7g%2BEk98iimZmIAkecKMCh7zkQB%2B1hFGLM%3D&reserved=0>.
You are receiving this because you were mentioned.Message ID: ***@***.***>

bgpierc: @kanderso-nrel @jsstein OK, I added a test that downloads the DEM and then verifies that three hardcoded points are the same elevation. I had the function place the downloaded file in the `./pvlib-python/pvlib/data` directory.

The function to download the data raises an HTTP error if it fails to fetch as well, does that need to be integrated into pytest with a  `with pytest.raises(HTTPError):` context block? sorry, I'm not very familiar with pytest.

bgpierc: @kandersolar @jsstein The horizon functions in this PR have been updated and appear to have no conflicts. I think I did the tests look right, but they could use a second look. 

Anyway, to recap, this PR primarily contains a function to download the horizon for a given lat/lon from PVGIS, as well as a DEM downloading function. Do we want to merge this?
cwhanse: @bgpierc can you update the PR checklist?
bgpierc: @cwhanse done, it seems like [docs/sphinx/source/api.rst](https://github.com/pvlib/pvlib-python/blob/master/docs/sphinx/source/api.rst) no longer exists, is there a replacement or somewhere else to modify?
cwhanse: @bgpierc https://github.com/pvlib/pvlib-python/blob/main/docs/sphinx/source/reference/iotools.rst
bgpierc: @cwhanse done, thanks! The checklist should be up to date, then. 
kandersolar: @bgpierc I'm not sure what's going on with the three windows test failures, but it seems like some transient PyPI issue that isn't relevant to this PR, so OK to ignore
bgpierc: @kandersolar ok, removed all the DEM and shading stuff, now only the pvgis horizon method is added here ([diff](https://github.com/pvlib/pvlib-python/compare/main...bgpierc:pvlib-python:add_horizon)). I think we should be good to go now.
AdamRJensen: Since there are only two columns, I would vote for setting the azimuth as the index.
bgpierc: @AdamRJensen Setting `horizon_azimuth` to the index makes sense to me, done
AdamRJensen: Given that this is an iotools function, it could be considered following the convention of returning a tuple of (data, meta). For this function, the meta object would just be an empty dictionary. 

If we can imagine that other horizon fetching functions could return metadata then I would vote for doing this.
kandersolar: > For this function, the meta object would just be an empty dictionary.

Well, the PVGIS API does actually return metadata, we just aren't doing anything with it right now.  I'm in favor of having this function follow the `data, metadata` convention and passing through the metadata returned by the API.  Good idea!
bgpierc: Ok, I had it return the meta data from PVGIS as well. I'm not sure if we want to make the function return a `Series` rather the a `DataFrame` as the other pvgis functions all return a `DataFrame`. Also, I think having the `horizon_azimuth` be explicit as the index column is helpful for clarity. 

Patch:
diff --git a/docs/sphinx/source/reference/iotools.rst b/docs/sphinx/source/reference/iotools.rst
index 514aeac..14271cf 100644
--- a/docs/sphinx/source/reference/iotools.rst
+++ b/docs/sphinx/source/reference/iotools.rst
@@ -31,6 +31,7 @@ of sources and file formats relevant to solar energy modeling.
    iotools.read_pvgis_tmy
    iotools.get_pvgis_hourly
    iotools.read_pvgis_hourly
+   iotools.get_pvgis_horizon
    iotools.get_bsrn
    iotools.read_bsrn
    iotools.parse_bsrn
diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst
index 0950002..501115c 100644
--- a/docs/sphinx/source/whatsnew/v0.9.6.rst
+++ b/docs/sphinx/source/whatsnew/v0.9.6.rst
@@ -5,6 +5,12 @@ v0.9.6 (Anticipated June 2023)
 ------------------------------
 
 
+Breaking Changes
+~~~~~~~~~~~~~~~~
+* For consistency with the rest of pvlib, the ``tilt`` parameter is renamed
+  to ``surface_tilt`` in :py:func:`pvlib.soiling.hsu`. (:issue:`1717`, :pull:`1738`)
+  
+
 Deprecations
 ~~~~~~~~~~~~
 
@@ -15,6 +21,8 @@ Deprecations
 
 Enhancements
 ~~~~~~~~~~~~
+* Added function to retrieve horizon data from PVGIS 
+  :py:func:`pvlib.iotools.get_pvgis_horizon`. (:issue:`1290`, :pull:`1395`)
 * Added ``map_variables`` argument to the :py:func:`pvlib.iotools.read_tmy3` in
   order to offer the option of mapping column names to standard pvlib names.
   (:issue:`1517`, :pull:`1623`)
@@ -25,7 +33,6 @@ Enhancements
 * :py:func:`pvlib.iotools.get_psm3` now uses the new NSRDB 3.2.2 endpoint for
   hourly and half-hourly single-year datasets. (:issue:`1591`, :pull:`1736`)
 
-
 Bug fixes
 ~~~~~~~~~
 * `data` can no longer be left unspecified in
@@ -53,9 +60,17 @@ Contributors
 ~~~~~~~~~~~~
 * Lakshya Garg (:ghuser:`Lakshyadevelops`)
 * Adam R. Jensen (:ghuser:`adamrjensen`)
+* Ben Pierce (:ghuser:`bgpierc`)
+* Joseph Palakapilly (:ghuser:`JPalakapillyKWH`)
+* Cliff Hansen (:ghuser:`cwhanse`)
+* Anton Driesse (:ghuser:`adriesse`)
+* Will Holmgren (:ghuser:`wholmgren`)
+* Mark Mikofski (:ghuser:`mikofski`)
+* Karel De Brabandere (:ghuser:`kdebrab`)
+* Josh Stein (:ghuser:`jsstein`)
+* Kevin Anderson (:ghuser:`kandersolar`)
 * Siddharth Kaul (:ghuser:`k10blogger`)
 * Kshitiz Gupta (:ghuser:`kshitiz305`)
 * Stefan de Lange (:ghuser:`langestefan`)
 * :ghuser:`ooprathamm`
 * Kevin Anderson (:ghuser:`kandersolar`)
-
diff --git a/pvlib/iotools/__init__.py b/pvlib/iotools/__init__.py
index b02ce24..6f6a254 100644
--- a/pvlib/iotools/__init__.py
+++ b/pvlib/iotools/__init__.py
@@ -15,6 +15,7 @@ from pvlib.iotools.psm3 import parse_psm3  # noqa: F401
 from pvlib.iotools.pvgis import get_pvgis_tmy, read_pvgis_tmy  # noqa: F401
 from pvlib.iotools.pvgis import read_pvgis_hourly  # noqa: F401
 from pvlib.iotools.pvgis import get_pvgis_hourly  # noqa: F401
+from pvlib.iotools.pvgis import get_pvgis_horizon  # noqa: F401
 from pvlib.iotools.bsrn import get_bsrn  # noqa: F401
 from pvlib.iotools.bsrn import read_bsrn  # noqa: F401
 from pvlib.iotools.bsrn import parse_bsrn  # noqa: F401
diff --git a/pvlib/iotools/pvgis.py b/pvlib/iotools/pvgis.py
index edfb28c..16bfee7 100644
--- a/pvlib/iotools/pvgis.py
+++ b/pvlib/iotools/pvgis.py
@@ -665,3 +665,57 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=None):
         data = data.rename(columns=VARIABLE_MAP)
 
     return data, months_selected, inputs, meta
+
+
+def get_pvgis_horizon(latitude, longitude, url=URL, **kwargs):
+    """Get horizon data from PVGIS.
+
+    Parameters
+    ----------
+    latitude : float
+        Latitude in degrees north
+    longitude : float
+        Longitude in degrees east
+    url: str, default: :const:`pvlib.iotools.pvgis.URL`
+        Base URL for PVGIS
+    kwargs:
+        Passed to requests.get
+
+    Returns
+    -------
+    data : pd.Series
+        Pandas Series of the retrived horizon elevation angles. Index is the
+        corresponding horizon azimuth angles.
+    metadata : dict
+        Metadata returned by PVGIS.
+
+    Notes
+    -----
+    The horizon azimuths are specified clockwise from north, e.g., south=180.
+    This is the standard pvlib convention, although the PVGIS website specifies
+    south=0.
+
+    References
+    ----------
+    .. [1] `PVGIS horizon profile tool
+       <https://ec.europa.eu/jrc/en/PVGIS/tools/horizon>`_
+    """
+    params = {'lat': latitude, 'lon': longitude, 'outputformat': 'json'}
+    res = requests.get(url + 'printhorizon', params=params, **kwargs)
+    if not res.ok:
+        try:
+            err_msg = res.json()
+        except Exception:
+            res.raise_for_status()
+        else:
+            raise requests.HTTPError(err_msg['message'])
+    json_output = res.json()
+    metadata = json_output['meta']
+    data = pd.DataFrame(json_output['outputs']['horizon_profile'])
+    data.columns = ['horizon_azimuth', 'horizon_elevation']
+    # Convert azimuth to pvlib convention (north=0, south=180)
+    data['horizon_azimuth'] += 180
+    data.set_index('horizon_azimuth', inplace=True)
+    data = data['horizon_elevation']  # convert to pd.Series
+    data = data[data.index < 360]  # remove duplicate north point (0 and 360)
+    return data, metadata
diff --git a/pvlib/soiling.py b/pvlib/soiling.py
index 811b414..bbad486 100644
--- a/pvlib/soiling.py
+++ b/pvlib/soiling.py
@@ -10,7 +10,7 @@ from scipy.special import erf
 from pvlib.tools import cosd
 
 
-def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
+def hsu(rainfall, cleaning_threshold, surface_tilt, pm2_5, pm10,
         depo_veloc=None, rain_accum_period=pd.Timedelta('1h')):
     """
     Calculates soiling ratio given particulate and rain data using the
@@ -30,7 +30,7 @@ def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
         Amount of rain in an accumulation period needed to clean the PV
         modules. [mm]
 
-    tilt : float
+    surface_tilt : numeric
         Tilt of the PV panels from horizontal. [degree]
 
     pm2_5 : numeric
@@ -83,7 +83,7 @@ def hsu(rainfall, cleaning_threshold, tilt, pm2_5, pm10,
     horiz_mass_rate = (
         pm2_5 * depo_veloc['2_5'] + np.maximum(pm10 - pm2_5, 0.)
         * depo_veloc['10']) * dt_sec
-    tilted_mass_rate = horiz_mass_rate * cosd(tilt)  # assuming no rain
+    tilted_mass_rate = horiz_mass_rate * cosd(surface_tilt)  # assuming no rain
 
     # tms -> tilt_mass_rate
     tms_cumsum = np.cumsum(tilted_mass_rate * np.ones(rainfall.shape))


Test patch:
diff --git a/pvlib/data/test_read_pvgis_horizon.csv b/pvlib/data/test_read_pvgis_horizon.csv
new file mode 100644
index 0000000..0b3ed27
--- /dev/null
+++ b/pvlib/data/test_read_pvgis_horizon.csv
@@ -0,0 +1,49 @@
+horizon_azimuth,horizon_elevation
+0,9.9
+7.5,13
+15,14.5
+22.5,15.7
+30,14.9
+37.5,15.3
+45,15.7
+52.5,15.7
+60,13
+67.5,11.5
+75,11.1
+82.5,11.5
+90,10.3
+97.5,11.5
+105,10.3
+112.5,9.5
+120,10.7
+127.5,11.8
+135,11.8
+142.5,8.8
+150,8.4
+157.5,7.3
+165,5.7
+172.5,5.7
+180,4.6
+187.5,3.4
+195,0.8
+202.5,0
+210,0
+217.5,0
+225,0
+232.5,0
+240,0
+247.5,0
+255,0
+262.5,0
+270,0
+277.5,0
+285,0
+292.5,0
+300,0
+307.5,0
+315,1.1
+322.5,1.9
+330,3.8
+337.5,5
+345,6.5
+352.5,9.2
diff --git a/pvlib/tests/iotools/test_pvgis.py b/pvlib/tests/iotools/test_pvgis.py
index 579c269..a5a5e3f 100644
--- a/pvlib/tests/iotools/test_pvgis.py
+++ b/pvlib/tests/iotools/test_pvgis.py
@@ -9,8 +9,9 @@ import pytest
 import requests
 from pvlib.iotools import get_pvgis_tmy, read_pvgis_tmy
 from pvlib.iotools import get_pvgis_hourly, read_pvgis_hourly
+from pvlib.iotools import get_pvgis_horizon
 from ..conftest import (DATA_DIR, RERUNS, RERUNS_DELAY, assert_frame_equal,
-                        fail_on_pvlib_version)
+                        fail_on_pvlib_version, assert_series_equal)
 from pvlib._deprecation import pvlibDeprecationWarning
 
 
@@ -509,6 +510,23 @@ def test_get_pvgis_map_variables(pvgis_tmy_mapped_columns):
     assert all([c in pvgis_tmy_mapped_columns for c in actual.columns])
 
 
+@pytest.mark.remote_data
+@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
+def test_read_pvgis_horizon():
+    pvgis_data, _ = get_pvgis_horizon(35.171051, -106.465158)
+    horizon_data = pd.read_csv(DATA_DIR / 'test_read_pvgis_horizon.csv',
+                               index_col=0)
+    horizon_data = horizon_data['horizon_elevation']
+    assert_series_equal(pvgis_data, horizon_data)
+
+
+@pytest.mark.remote_data
+@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
+def test_read_pvgis_horizon_invalid_coords():
+    with pytest.raises(requests.HTTPError, match='lat: Incorrect value'):
+        _, _ = get_pvgis_horizon(100, 50)  # unfeasible latitude
+
+
 def test_read_pvgis_tmy_map_variables(pvgis_tmy_mapped_columns):
     fn = DATA_DIR / 'tmy_45.000_8.000_2005_2016.json'
     actual, _, _, _ = read_pvgis_tmy(fn, map_variables=True)
diff --git a/pvlib/tests/test_soiling.py b/pvlib/tests/test_soiling.py
index 0385728..e774e62 100644
--- a/pvlib/tests/test_soiling.py
+++ b/pvlib/tests/test_soiling.py
@@ -92,7 +92,7 @@ def test_hsu_no_cleaning(rainfall_input, expected_output):
     tilt = 0.
     expected_no_cleaning = expected_output
 
-    result = hsu(rainfall=rainfall, cleaning_threshold=10., tilt=tilt,
+    result = hsu(rainfall=rainfall, cleaning_threshold=10., surface_tilt=tilt,
                  pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
                  rain_accum_period=pd.Timedelta('1h'))
     assert_series_equal(result, expected_no_cleaning)
@@ -108,7 +108,7 @@ def test_hsu(rainfall_input, expected_output_2):
     tilt = 0.
 
     # three cleaning events at 4:00-6:00, 8:00-11:00, and 17:00-20:00
-    result = hsu(rainfall=rainfall, cleaning_threshold=0.5, tilt=tilt,
+    result = hsu(rainfall=rainfall, cleaning_threshold=0.5, surface_tilt=tilt,
                  pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
                  rain_accum_period=pd.Timedelta('3h'))
 
@@ -120,8 +120,8 @@ def test_hsu_defaults(rainfall_input, expected_output_1):
     Test Soiling HSU function with default deposition velocity and default rain
     accumulation period.
     """
-    result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5, tilt=0.0,
-                 pm2_5=1.0e-2, pm10=2.0e-2)
+    result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5,
+                 surface_tilt=0.0, pm2_5=1.0e-2, pm10=2.0e-2)
     assert np.allclose(result.values, expected_output_1)
 
 
@@ -138,7 +138,7 @@ def test_hsu_variable_time_intervals(rainfall_input, expected_output_3):
     rain['new_time'] = rain.index + rain['mins_added']
     rain_var_times = rain.set_index('new_time').iloc[:, 0]
     result = hsu(
-        rainfall=rain_var_times, cleaning_threshold=0.5, tilt=50.0,
+        rainfall=rain_var_times, cleaning_threshold=0.5, surface_tilt=50.0,
         pm2_5=1, pm10=2, depo_veloc=depo_veloc,
         rain_accum_period=pd.Timedelta('2h'))
     assert np.allclose(result, expected_output_3)