diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 9d79aab177..1306ed79d1 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -181,7 +181,7 @@ DNI estimation models irradiance.dirint irradiance.dirindex irradiance.erbs - irradiance.liujordan + irradiance.campbell_norman irradiance.gti_dirint Clearness index models @@ -520,7 +520,7 @@ Processing data forecast.ForecastModel.cloud_cover_to_ghi_linear forecast.ForecastModel.cloud_cover_to_irradiance_clearsky_scaling forecast.ForecastModel.cloud_cover_to_transmittance_linear - forecast.ForecastModel.cloud_cover_to_irradiance_liujordan + forecast.ForecastModel.cloud_cover_to_irradiance_campbell_norman forecast.ForecastModel.cloud_cover_to_irradiance forecast.ForecastModel.kelvin_to_celsius forecast.ForecastModel.isobaric_to_ambient_temperature diff --git a/docs/sphinx/source/whatsnew/v0.8.1.rst b/docs/sphinx/source/whatsnew/v0.8.1.rst index f7b4ec1782..8b2bec94b5 100644 --- a/docs/sphinx/source/whatsnew/v0.8.1.rst +++ b/docs/sphinx/source/whatsnew/v0.8.1.rst @@ -9,7 +9,7 @@ Breaking changes Deprecations ~~~~~~~~~~~~ - +* ``pvlib.irradiance.liujordan`` is deprecated. Enhancements ~~~~~~~~~~~~ @@ -21,6 +21,12 @@ Enhancements multiple MPPTs (:issue:`457`, :pull:`1085`) * Added optional ``attributes`` parameter to :py:func:`pvlib.iotools.get_psm3` and added the option of fetching 5- and 15-minute PSM3 data. (:pull:`1086`) +* Added :py:func:`pvlib.irradiance.campbell_norman` for estimating DNI, DHI and GHI + from extraterrestrial irradiance. This function replaces ``pvlib.irradiance.liujordan``; + users of ``pvlib.irradiance.liujordan`` should note that :py:func:`pvlib.irradiance.campbell_norman` + expects different parameters. +* :py:meth:`pvlib.forecast.Forecast.cloud_cover_to_irradiance_campbell_norman` + replaces ``pvlib.forecast.Forecast.cloud_cover_to_irradiance_liujordan``. Bug fixes ~~~~~~~~~ diff --git a/pvlib/forecast.py b/pvlib/forecast.py index 24e54e1015..4d4500c560 100644 --- a/pvlib/forecast.py +++ b/pvlib/forecast.py @@ -9,11 +9,14 @@ from xml.etree.ElementTree import ParseError from pvlib.location import Location -from pvlib.irradiance import liujordan, get_extra_radiation, disc +from pvlib.irradiance import campbell_norman, get_extra_radiation, disc +from pvlib.irradiance import _liujordan from siphon.catalog import TDSCatalog from siphon.ncss import NCSS import warnings +from pvlib._deprecation import deprecated + warnings.warn( 'The forecast module algorithms and features are highly experimental. ' @@ -526,8 +529,48 @@ def cloud_cover_to_transmittance_linear(self, cloud_cover, offset=0.75, return transmittance + def cloud_cover_to_irradiance_campbell_norman(self, cloud_cover, **kwargs): + """ + Estimates irradiance from cloud cover in the following steps: + + 1. Determine transmittance using a function of cloud cover e.g. + :py:meth:`~ForecastModel.cloud_cover_to_transmittance_linear` + 2. Calculate GHI, DNI, DHI using the + :py:func:`pvlib.irradiance.campbell_norman` model + + Parameters + ---------- + cloud_cover : Series + + Returns + ------- + irradiance : DataFrame + Columns include ghi, dni, dhi + """ + # in principle, get_solarposition could use the forecast + # pressure, temp, etc., but the cloud cover forecast is not + # accurate enough to justify using these minor corrections + solar_position = self.location.get_solarposition(cloud_cover.index) + dni_extra = get_extra_radiation(cloud_cover.index) + + transmittance = self.cloud_cover_to_transmittance_linear(cloud_cover, + **kwargs) + + irrads = campbell_norman(solar_position['apparent_zenith'], + transmittance, dni_extra=dni_extra) + irrads = irrads.fillna(0) + + return irrads + + @deprecated( + '0.8', + alternative='Forecast.cloud_cover_to_irradiance_campbell_norman', + name='Forecast.cloud_cover_to_irradiance_liujordan', + removal='0.9') def cloud_cover_to_irradiance_liujordan(self, cloud_cover, **kwargs): """ + Deprecated. Use cloud_cover_to_irradiance_campbell_norman instead. + Estimates irradiance from cloud cover in the following steps: 1. Determine transmittance using a function of cloud cover e.g. @@ -554,9 +597,9 @@ def cloud_cover_to_irradiance_liujordan(self, cloud_cover, **kwargs): transmittance = self.cloud_cover_to_transmittance_linear(cloud_cover, **kwargs) - irrads = liujordan(solar_position['apparent_zenith'], - transmittance, airmass['airmass_absolute'], - dni_extra=dni_extra) + irrads = _liujordan(solar_position['apparent_zenith'], + transmittance, airmass['airmass_absolute'], + dni_extra=dni_extra) irrads = irrads.fillna(0) return irrads @@ -571,7 +614,8 @@ def cloud_cover_to_irradiance(self, cloud_cover, how='clearsky_scaling', cloud_cover : Series how : str, default 'clearsky_scaling' Selects the method for conversion. Can be one of - clearsky_scaling or liujordan. + clearsky_scaling or campbell_norman. Method liujordan is + deprecated. **kwargs Passed to the selected method. @@ -585,6 +629,9 @@ def cloud_cover_to_irradiance(self, cloud_cover, how='clearsky_scaling', if how == 'clearsky_scaling': irrads = self.cloud_cover_to_irradiance_clearsky_scaling( cloud_cover, **kwargs) + elif how == 'campbell_norman': + irrads = self.cloud_cover_to_irradiance_campbell_norman( + cloud_cover, **kwargs) elif how == 'liujordan': irrads = self.cloud_cover_to_irradiance_liujordan( cloud_cover, **kwargs) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index e1d07699b2..67a327a090 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -13,6 +13,9 @@ from pvlib import atmosphere, solarposition, tools +from pvlib._deprecation import deprecated + + # see References section of grounddiffuse function SURFACE_ALBEDOS = {'urban': 0.18, 'grass': 0.20, @@ -2184,7 +2187,61 @@ def erbs(ghi, zenith, datetime_or_doy, min_cos_zenith=0.065, max_zenith=87): return data -def liujordan(zenith, transmittance, airmass, dni_extra=1367.0): +def campbell_norman(zenith, transmittance, pressure=101325.0, + dni_extra=1367.0): + ''' + Determine DNI, DHI, GHI from extraterrestrial flux, transmittance, + and atmospheric pressure. + + Parameters + ---------- + zenith: pd.Series + True (not refraction-corrected) zenith angles in decimal + degrees. If Z is a vector it must be of the same size as all + other vector inputs. Z must be >=0 and <=180. + + transmittance: float + Atmospheric transmittance between 0 and 1. + + pressure: float, default 101325.0 + Air pressure + + dni_extra: float, default 1367.0 + Direct irradiance incident at the top of the atmosphere. + + Returns + ------- + irradiance: DataFrame + Modeled direct normal irradiance, direct horizontal irradiance, + and global horizontal irradiance in W/m^2 + + References + ---------- + .. [1] Campbell, G. S., J. M. Norman (1998) An Introduction to + Environmental Biophysics. 2nd Ed. New York: Springer. + ''' + + tau = transmittance + + airmass = atmosphere.get_relative_airmass(zenith, model='simple') + airmass = atmosphere.get_absolute_airmass(airmass, pressure=pressure) + dni = dni_extra*tau**airmass + cos_zen = tools.cosd(zenith) + dhi = 0.3 * (1.0 - tau**airmass) * dni_extra * cos_zen + ghi = dhi + dni * cos_zen + + irrads = OrderedDict() + irrads['ghi'] = ghi + irrads['dni'] = dni + irrads['dhi'] = dhi + + if isinstance(ghi, pd.Series): + irrads = pd.DataFrame(irrads) + + return irrads + + +def _liujordan(zenith, transmittance, airmass, dni_extra=1367.0): ''' Determine DNI, DHI, GHI from extraterrestrial flux, transmittance, and optical air mass number. @@ -2242,6 +2299,10 @@ def liujordan(zenith, transmittance, airmass, dni_extra=1367.0): return irrads +liujordan = deprecated('0.8', alternative='campbellnormam', + name='liujordan', removal='0.9')(_liujordan) + + def _get_perez_coefficients(perezmodel): ''' Find coefficients for the Perez model diff --git a/pvlib/tests/test_forecast.py b/pvlib/tests/test_forecast.py index 75b3badb74..ff14de7629 100644 --- a/pvlib/tests/test_forecast.py +++ b/pvlib/tests/test_forecast.py @@ -10,7 +10,7 @@ requires_siphon, has_siphon, skip_windows, - requires_recent_cftime, + requires_recent_cftime ) from conftest import RERUNS, RERUNS_DELAY @@ -69,7 +69,7 @@ def model(request): @pytest.mark.remote_data @pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) def test_process_data(model): - for how in ['liujordan', 'clearsky_scaling']: + for how in ['campbell_norman', 'clearsky_scaling']: if model.raw_data.empty: warnings.warn('Could not test {} process_data with how={} ' 'because raw_data was empty'.format(model, how)) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 0aeec3b95e..995952648d 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -13,7 +13,8 @@ from pvlib import irradiance -from conftest import requires_ephem, requires_numba +from conftest import requires_ephem, requires_numba, fail_on_pvlib_version +from pvlib._deprecation import pvlibDeprecationWarning # fixtures create realistic test input data @@ -285,13 +286,27 @@ def test_get_sky_diffuse_invalid(): model='invalid') +@fail_on_pvlib_version('0.9') def test_liujordan(): expected = pd.DataFrame(np.array( [[863.859736967, 653.123094076, 220.65905025]]), columns=['ghi', 'dni', 'dhi'], index=[0]) - out = irradiance.liujordan( - pd.Series([10]), pd.Series([0.5]), pd.Series([1.1]), dni_extra=1400) + with pytest.warns(pvlibDeprecationWarning): + out = irradiance.liujordan( + pd.Series([10]), pd.Series([0.5]), pd.Series([1.1]), + dni_extra=1400) + assert_frame_equal(out, expected) + + +def test_campbell_norman(): + expected = pd.DataFrame(np.array( + [[863.859736967, 653.123094076, 220.65905025]]), + columns=['ghi', 'dni', 'dhi'], + index=[0]) + out = irradiance.campbell_norman( + pd.Series([10]), pd.Series([0.5]), pd.Series([109764.21013135818]), + dni_extra=1400) assert_frame_equal(out, expected)