Skip to content

Commit

Permalink
Deprecate pvlib.forecast (pvlib#1426)
Browse files Browse the repository at this point in the history
* deprecate pvlib.forecast classes

* catch warnings in tests

* add warning admonition to forecasts.rst

* whatsnew

* stickler

* pin pytest < 7.1.0

* pin pytest in the right place this time

* more warning suppression in tests

* unpin pytest

* Update docs/sphinx/source/whatsnew/v0.9.1.rst

* copy warning to reference/forecasting.rst
  • Loading branch information
kandersolar committed Mar 17, 2022
1 parent e3baa12 commit c243183
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 14 deletions.
6 changes: 6 additions & 0 deletions docs/sphinx/source/reference/forecasting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Forecasting
===========

.. warning::

All functionality in the ``pvlib.forecast`` module is deprecated as of
pvlib v0.9.1. For details, see :ref:`forecasts`.


Forecast models
---------------

Expand Down
19 changes: 19 additions & 0 deletions docs/sphinx/source/user_guide/forecasts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@
Forecasting
***********

.. warning::

The ``pvlib.forecast`` module is deprecated as of version ``0.9.1``.

Because none of the current pvlib team members are able to continue
maintaining it, the functionality in ``pvlib.forecast`` is deprecated
and will be removed without replacement in a future version. If you
are interested in maintaining this functionality, please let us know.

You can fetch forecast data yourself using ``siphon`` (see the
docs below this warning) and the code from pvlib v0.9.0 as a reference:
https://github.com/pvlib/pvlib-python/blob/v0.9.0/pvlib/forecast.py

The `Solar Forecast Arbiter Core
<https://solarforecastarbiter-core.readthedocs.io/en/stable/reference-forecasts.html>`_
offers similar (and more robust) forecast processing functionality
and may be a suitable replacement for some users.


pvlib python provides a set of functions and classes that make it easy
to obtain weather forecast data and convert that data into a PV power
forecast. Users can retrieve standardized weather forecast data relevant
Expand Down
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Deprecations
:py:meth:`pvlib.modelchain.ModelChain.with_sapm` for alternative simplified
:py:class:`~pvlib.modelchain.ModelChain` interfaces, although note that the
inputs do not directly translate. (:pull:`1401`)
* All functionality in the ``pvlib.forecast`` module is deprecated.
For details, see :ref:`forecasts`. (:issue:`1057`, :pull:`1426`)

Enhancements
~~~~~~~~~~~~
Expand Down
15 changes: 15 additions & 0 deletions pvlib/forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@
from siphon.ncss import NCSS

import warnings
from pvlib._deprecation import deprecated


warnings.warn(
'The forecast module algorithms and features are highly experimental. '
'The API may change, the functionality may be consolidated into an io '
'module, or the module may be separated into its own package.')

_forecast_deprecated = deprecated(
since='0.9.1',
removal='a future release',
addendum='For details, see https://pvlib-python.readthedocs.io/en/stable/user_guide/forecasts.html' # noqa: E501
)

# don't decorate the base class to prevent the subclasses from showing
# duplicate warnings:
# @_forecast_deprecated
class ForecastModel:
"""
An object for querying and holding forecast model information for
Expand Down Expand Up @@ -684,6 +693,7 @@ def gust_to_speed(self, data, scaling=1/1.4):
return wind_speed


@_forecast_deprecated
class GFS(ForecastModel):
"""
Subclass of the ForecastModel class representing GFS
Expand Down Expand Up @@ -785,6 +795,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs):
return data[self.output_variables]


@_forecast_deprecated
class HRRR_ESRL(ForecastModel): # noqa: N801
"""
Subclass of the ForecastModel class representing
Expand Down Expand Up @@ -875,6 +886,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs):
return data[self.output_variables]


@_forecast_deprecated
class NAM(ForecastModel):
"""
Subclass of the ForecastModel class representing NAM
Expand Down Expand Up @@ -956,6 +968,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs):
return data[self.output_variables]


@_forecast_deprecated
class HRRR(ForecastModel):
"""
Subclass of the ForecastModel class representing HRRR
Expand Down Expand Up @@ -1044,6 +1057,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs):
return data[self.output_variables]


@_forecast_deprecated
class NDFD(ForecastModel):
"""
Subclass of the ForecastModel class representing NDFD forecast
Expand Down Expand Up @@ -1112,6 +1126,7 @@ def process_data(self, data, **kwargs):
return data[self.output_variables]


@_forecast_deprecated
class RAP(ForecastModel):
"""
Subclass of the ForecastModel class representing RAP forecast model.
Expand Down
44 changes: 30 additions & 14 deletions pvlib/tests/test_forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
)
from .conftest import RERUNS, RERUNS_DELAY

from pvlib._deprecation import pvlibDeprecationWarning

pytestmark = pytest.mark.skipif(not has_siphon, reason='requires siphon')


Expand Down Expand Up @@ -52,7 +54,8 @@
@requires_siphon
@pytest.fixture(scope='module', params=_modelclasses)
def model(request):
amodel = request.param()
with pytest.warns(pvlibDeprecationWarning):
amodel = request.param()
try:
raw_data = amodel.get_data(_latitude, _longitude, _start, _end)
except Exception as e:
Expand Down Expand Up @@ -90,7 +93,8 @@ def test_process_data(model):
def test_bad_kwarg_get_data():
# For more information on why you would want to pass an unknown keyword
# argument, see Github issue #745.
amodel = NAM()
with pytest.warns(pvlibDeprecationWarning):
amodel = NAM()
data = amodel.get_data(_latitude, _longitude, _start, _end,
bad_kwarg=False)
assert not data.empty
Expand All @@ -103,7 +107,8 @@ def test_bad_kwarg_get_data():
def test_bad_kwarg_get_processed_data():
# For more information on why you would want to pass an unknown keyword
# argument, see Github issue #745.
amodel = NAM()
with pytest.warns(pvlibDeprecationWarning):
amodel = NAM()
data = amodel.get_processed_data(_latitude, _longitude, _start, _end,
bad_kwarg=False)
assert not data.empty
Expand All @@ -114,7 +119,8 @@ def test_bad_kwarg_get_processed_data():
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_how_kwarg_get_processed_data():
amodel = NAM()
with pytest.warns(pvlibDeprecationWarning):
amodel = NAM()
data = amodel.get_processed_data(_latitude, _longitude, _start, _end,
how='clearsky_scaling')
assert not data.empty
Expand All @@ -125,7 +131,8 @@ def test_how_kwarg_get_processed_data():
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_vert_level():
amodel = NAM()
with pytest.warns(pvlibDeprecationWarning):
amodel = NAM()
vert_level = 5000
amodel.get_processed_data(_latitude, _longitude, _start, _end,
vert_level=vert_level)
Expand All @@ -136,7 +143,8 @@ def test_vert_level():
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_datetime():
amodel = NAM()
with pytest.warns(pvlibDeprecationWarning):
amodel = NAM()
start = datetime.now(tz=timezone.utc)
end = start + timedelta(days=1)
amodel.get_processed_data(_latitude, _longitude, start, end)
Expand All @@ -147,7 +155,8 @@ def test_datetime():
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_queryvariables():
amodel = GFS()
with pytest.warns(pvlibDeprecationWarning):
amodel = GFS()
new_variables = ['u-component_of_wind_height_above_ground']
data = amodel.get_data(_latitude, _longitude, _start, _end,
query_variables=new_variables)
Expand All @@ -156,16 +165,19 @@ def test_queryvariables():

@requires_siphon
def test_latest():
GFS(set_type='latest')
with pytest.warns(pvlibDeprecationWarning):
GFS(set_type='latest')


@requires_siphon
def test_full():
GFS(set_type='full')
with pytest.warns(pvlibDeprecationWarning):
GFS(set_type='full')


def test_temp_convert():
amodel = GFS()
with pytest.warns(pvlibDeprecationWarning):
amodel = GFS()
data = pd.DataFrame({'temp_air': [273.15]})
data['temp_air'] = amodel.kelvin_to_celsius(data['temp_air'])

Expand All @@ -183,27 +195,31 @@ def test_temp_convert():


def test_set_location():
amodel = GFS()
with pytest.warns(pvlibDeprecationWarning):
amodel = GFS()
latitude, longitude = 32.2, -110.9
time = 'UTC'
amodel.set_location(time, latitude, longitude)


def test_set_query_time_range_tzfail():
amodel = GFS()
with pytest.warns(pvlibDeprecationWarning):
amodel = GFS()
with pytest.raises(TypeError):
amodel.set_query_time_range(datetime.now(), datetime.now())


def test_cloud_cover_to_transmittance_linear():
amodel = GFS()
with pytest.warns(pvlibDeprecationWarning):
amodel = GFS()
assert_allclose(amodel.cloud_cover_to_transmittance_linear(0), 0.75)
assert_allclose(amodel.cloud_cover_to_transmittance_linear(100), 0.0)
assert_allclose(amodel.cloud_cover_to_transmittance_linear(0, 0.5), 0.5)


def test_cloud_cover_to_ghi_linear():
amodel = GFS()
with pytest.warns(pvlibDeprecationWarning):
amodel = GFS()
ghi_clear = 1000
offset = 25
out = amodel.cloud_cover_to_ghi_linear(0, ghi_clear, offset=offset)
Expand Down

0 comments on commit c243183

Please sign in to comment.