From d03f97cf2dfa93759da6c0449fe334fffa0f4ec2 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 26 Sep 2023 20:01:45 +0200 Subject: [PATCH 01/18] Definitely not ready for review! --- pvlib/irradiance.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 7e66281974..2c2910db05 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1212,6 +1212,13 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, return sky_diffuse +def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, + solar_zenith, solar_azimuth, airmass, + model='allsitescomposite1990', return_components=False): + + return 0 + + def clearsky_index(ghi, clearsky_ghi, max_clearsky_index=2.0): """ Calculate the clearsky index. From 4a842186dc37147d55bcd23322fdced37ce91239 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 26 Sep 2023 23:52:49 +0200 Subject: [PATCH 02/18] Big step forward. --- pvlib/irradiance.py | 147 ++++++++++++++++++++++++++++++++- pvlib/tests/test_irradiance.py | 12 +++ 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 2c2910db05..ebdb29e344 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -10,8 +10,10 @@ import numpy as np import pandas as pd +from scipy.interpolate import splev from pvlib import atmosphere, solarposition, tools +from pvlib.atmosphere import get_relative_airmass import pvlib # used to avoid dni name collision in complete_irradiance @@ -1212,11 +1214,150 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, return sky_diffuse +def _calc_delta(dhi, dni_extra, zenith): + ''' + Helper function for perez_driesse transposition + + Compute the delta parameter, which represents sky dome "brightness". + ''' + # prevent nan airmass at night + zenith = np.minimum(zenith, 90) + + # use the same airmass model as in the original perez work + airmass = get_relative_airmass(zenith, 'kastenyoung1989') + delta = dhi / (dni_extra / airmass) + + return delta + + +def _calc_zeta(dhi, dni, zenith, use_kappa=True): + ''' + Helper function for perez_driesse transposition + + Compute the zeta parameter, which represents sky dome "clearness" + in the perez-driesse model. + + Zeta is a simple transpormation of the original Perez epsilon index: + zeta = 1 - 1 / epsilon + epsilon = 1 / (1 - zeta) + + Zeta has values between 0 and 1 rather than between 1 and infinity + for epsilon. + ''' + dhi = np.asarray(dhi) + dni = np.asarray(dni) + + with np.errstate(invalid='ignore'): + zeta = dni / (dhi + dni) + + zeta = np.where(dhi > 0, zeta, 0.0) + + if use_kappa: + kappa = 1.041 + kterm = kappa * np.radians(zenith) ** 3 + zeta = zeta / (1 - kterm * (zeta - 1)) + + return zeta + + +def _evaluate_f(i, j, zeta): + ''' + Helper function for perez_driesse transposition + ''' + knots = np.array( + [0.000, 0.000, 0.000, + 0.061, 0.187, 0.333, 0.487, 0.643, 0.778, 0.839, + 1.000, 1.000, 1.000]) + + # quadratic spline coefficients for 'allsitescomposite1990' + coefs = np.array( + [[-0.053, 0.529, -0.028, -0.071, 0.061, -0.019], + [-0.008, 0.588, -0.062, -0.06 , 0.072, -0.022], + [ 0.131, 0.77 , -0.167, -0.026, 0.106, -0.032], + [ 0.328, 0.471, -0.216, 0.069, -0.105, -0.028], + [ 0.557, 0.241, -0.3 , 0.086, -0.085, -0.012], + [ 0.861, -0.323, -0.355, 0.24 , -0.467, -0.008], + [ 1.212, -1.239, -0.444, 0.305, -0.797, 0.047], + [ 1.099, -1.847, -0.365, 0.275, -1.132, 0.124], + [ 0.544, 0.157, -0.213, 0.118, -1.455, 0.292], + [ 0.544, 0.157, -0.213, 0.118, -1.455, 0.292], + [ 0. , 0. , 0. , 0. , 0. , 0. ], + [ 0. , 0. , 0. , 0. , 0. , 0. ], + [ 0. , 0. , 0. , 0. , 0. , 0. ]]) + + coefs = coefs.T.reshape((2, 3, -1)) + + tck = (knots, coefs[i-1, j-1], 2) + + return splev(zeta, tck) + + def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, - solar_zenith, solar_azimuth, airmass, - model='allsitescomposite1990', return_components=False): + solar_zenith, solar_azimuth, airmass=None, + model='allsitescomposite1990', return_components=False): + ''' + Determine diffuse irradiance from the sky on a tilted surface using + the continuous Perez-Driesse model. + ''' + + if airmass is not None: + raise Warning("The 'airmass' parameter is ignored. " + 'Airmass is calculated internally. ') + + if model is not 'allsitescomposite1990': + raise Warning("The 'model' parameter is ignored. " + "Only 'allsitescomposite1990' is available.") + + delta = _calc_delta(dhi, dni_extra, solar_zenith) + zeta = _calc_zeta(dhi, dni, solar_zenith, use_kappa=True) - return 0 + f = _evaluate_f + z = np.radians(solar_zenith) + + F1 = f(1, 1, zeta) + f(1, 2, zeta) * delta + f(1, 3, zeta) * z + F2 = f(2, 1, zeta) + f(2, 2, zeta) * delta + f(2, 3, zeta) * z + + # from this point the code is identical to the original perez function + A = aoi_projection(surface_tilt, surface_azimuth, + solar_zenith, solar_azimuth) + A = np.maximum(A, 0) + + B = tools.cosd(solar_zenith) + B = np.maximum(B, tools.cosd(85)) + + # Calculate Diffuse POA from sky dome + term1 = 0.5 * (1 - F1) * (1 + tools.cosd(surface_tilt)) + term2 = F1 * A / B + term3 = F2 * tools.sind(surface_tilt) + + sky_diffuse = np.maximum(dhi * (term1 + term2 + term3), 0) + + # we've preserved the input type until now, so don't ruin it! + # if isinstance(sky_diffuse, pd.Series): + # sky_diffuse[np.isnan(airmass)] = 0 + # else: + # sky_diffuse = np.where(np.isnan(airmass), 0, sky_diffuse) + + if return_components: + diffuse_components = OrderedDict() + diffuse_components['sky_diffuse'] = sky_diffuse + + # Calculate the different components + diffuse_components['isotropic'] = dhi * term1 + diffuse_components['circumsolar'] = dhi * term2 + diffuse_components['horizon'] = dhi * term3 + + # Set values of components to 0 when sky_diffuse is 0 + mask = sky_diffuse == 0 + if isinstance(sky_diffuse, pd.Series): + diffuse_components = pd.DataFrame(diffuse_components) + diffuse_components.loc[mask] = 0 + else: + diffuse_components = {k: np.where(mask, 0, v) for k, v in + diffuse_components.items()} + return diffuse_components + else: + return sky_diffuse def clearsky_index(ghi, clearsky_ghi, max_clearsky_index=2.0): diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 2f660d5c97..1cebda6c88 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -273,6 +273,18 @@ def test_perez(irrad_data, ephem_data, dni_et, relative_airmass): assert_series_equal(out, expected, check_less_precise=2) +def test_perez_driesse(irrad_data, ephem_data, dni_et, relative_airmass): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, + dni_et, ephem_data['apparent_zenith'], + ephem_data['azimuth']) + expected = pd.Series(np.array( + [0., 30.00, np.nan, 47.40]), + index=irrad_data.index) + assert_series_equal(out, expected, check_less_precise=2) + + def test_perez_components(irrad_data, ephem_data, dni_et, relative_airmass): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan From 4d739de7eff2f543afc277cbc848b54ff4ae85f8 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 27 Sep 2023 00:03:24 +0200 Subject: [PATCH 03/18] Add entry in docs. --- docs/sphinx/source/reference/irradiance/transposition.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sphinx/source/reference/irradiance/transposition.rst b/docs/sphinx/source/reference/irradiance/transposition.rst index 4749b5d5b9..7b3624e692 100644 --- a/docs/sphinx/source/reference/irradiance/transposition.rst +++ b/docs/sphinx/source/reference/irradiance/transposition.rst @@ -10,6 +10,7 @@ Transposition models irradiance.get_sky_diffuse irradiance.isotropic irradiance.perez + irradiance.perez_driesse irradiance.haydavies irradiance.klucher irradiance.reindl From 0cbd42078e7e9d3a4edb9d47e119c224086123f5 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 27 Sep 2023 14:51:54 +0200 Subject: [PATCH 04/18] A working model but just one test sofar. --- pvlib/irradiance.py | 61 ++++++++++++++-------------------- pvlib/tests/test_irradiance.py | 2 +- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index ebdb29e344..6baf07ef13 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1214,35 +1214,25 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, return sky_diffuse -def _calc_delta(dhi, dni_extra, zenith): +def _calc_delta(dhi, dni_extra, airmass): ''' - Helper function for perez_driesse transposition - Compute the delta parameter, which represents sky dome "brightness". + + Helper function for perez_driesse transposition. ''' - # prevent nan airmass at night - zenith = np.minimum(zenith, 90) + max_airmass = get_relative_airmass(90, 'kastenyoung1989') - # use the same airmass model as in the original perez work - airmass = get_relative_airmass(zenith, 'kastenyoung1989') - delta = dhi / (dni_extra / airmass) + airmass = np.where(np.isnan(airmass), max_airmass, airmass) - return delta + return dhi / (dni_extra / airmass) def _calc_zeta(dhi, dni, zenith, use_kappa=True): ''' - Helper function for perez_driesse transposition - Compute the zeta parameter, which represents sky dome "clearness" in the perez-driesse model. - Zeta is a simple transpormation of the original Perez epsilon index: - zeta = 1 - 1 / epsilon - epsilon = 1 / (1 - zeta) - - Zeta has values between 0 and 1 rather than between 1 and infinity - for epsilon. + Helper function for perez_driesse transposition. ''' dhi = np.asarray(dhi) dni = np.asarray(dni) @@ -1250,7 +1240,7 @@ def _calc_zeta(dhi, dni, zenith, use_kappa=True): with np.errstate(invalid='ignore'): zeta = dni / (dhi + dni) - zeta = np.where(dhi > 0, zeta, 0.0) + zeta = np.where(dhi == 0, 0.0, zeta) if use_kappa: kappa = 1.041 @@ -1260,8 +1250,11 @@ def _calc_zeta(dhi, dni, zenith, use_kappa=True): return zeta -def _evaluate_f(i, j, zeta): +def _f(i, j, zeta): ''' + Evaluate the quadratic splines corresponding to the + allsitescomposite1990 perez look-up tables. + Helper function for perez_driesse transposition ''' knots = np.array( @@ -1269,7 +1262,6 @@ def _evaluate_f(i, j, zeta): 0.061, 0.187, 0.333, 0.487, 0.643, 0.778, 0.839, 1.000, 1.000, 1.000]) - # quadratic spline coefficients for 'allsitescomposite1990' coefs = np.array( [[-0.053, 0.529, -0.028, -0.071, 0.061, -0.019], [-0.008, 0.588, -0.062, -0.06 , 0.072, -0.022], @@ -1285,7 +1277,7 @@ def _evaluate_f(i, j, zeta): [ 0. , 0. , 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. , 0. , 0. ]]) - coefs = coefs.T.reshape((2, 3, -1)) + coefs = coefs.T.reshape((2, 3, 13)) tck = (knots, coefs[i-1, j-1], 2) @@ -1300,24 +1292,27 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, the continuous Perez-Driesse model. ''' - if airmass is not None: - raise Warning("The 'airmass' parameter is ignored. " - 'Airmass is calculated internally. ') - if model is not 'allsitescomposite1990': raise Warning("The 'model' parameter is ignored. " "Only 'allsitescomposite1990' is available.") - delta = _calc_delta(dhi, dni_extra, solar_zenith) + if airmass is None: + # use the same airmass model as in the original perez work + airmass = get_relative_airmass(solar_zenith, 'kastenyoung1989') + + delta = _calc_delta(dhi, dni_extra, airmass) zeta = _calc_zeta(dhi, dni, solar_zenith, use_kappa=True) - f = _evaluate_f z = np.radians(solar_zenith) - F1 = f(1, 1, zeta) + f(1, 2, zeta) * delta + f(1, 3, zeta) * z - F2 = f(2, 1, zeta) + f(2, 2, zeta) * delta + f(2, 3, zeta) * z + F1 = _f(1, 1, zeta) + _f(1, 2, zeta) * delta + _f(1, 3, zeta) * z + F2 = _f(2, 1, zeta) + _f(2, 2, zeta) * delta + _f(2, 3, zeta) * z + + # note the upper limit on F1 + F1 = np.clip(F1, 0, 0.9) + + # lines after this point are identical to the original perez function - # from this point the code is identical to the original perez function A = aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth) A = np.maximum(A, 0) @@ -1332,12 +1327,6 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, sky_diffuse = np.maximum(dhi * (term1 + term2 + term3), 0) - # we've preserved the input type until now, so don't ruin it! - # if isinstance(sky_diffuse, pd.Series): - # sky_diffuse[np.isnan(airmass)] = 0 - # else: - # sky_diffuse = np.where(np.isnan(airmass), 0, sky_diffuse) - if return_components: diffuse_components = OrderedDict() diffuse_components['sky_diffuse'] = sky_diffuse diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 1cebda6c88..fc47a43362 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -278,7 +278,7 @@ def test_perez_driesse(irrad_data, ephem_data, dni_et, relative_airmass): dni.iloc[2] = np.nan out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, dni_et, ephem_data['apparent_zenith'], - ephem_data['azimuth']) + ephem_data['azimuth'], relative_airmass) expected = pd.Series(np.array( [0., 30.00, np.nan, 47.40]), index=irrad_data.index) From 9f39dd52f660db298f72f1f123210cc17e29e558 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 27 Sep 2023 16:45:59 +0200 Subject: [PATCH 05/18] Add new model as option in get_sky_diffuse. Docstring edits pending. --- pvlib/irradiance.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 6baf07ef13..1f5e87cb90 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -453,7 +453,7 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, model = model.lower() - if (model in {'haydavies', 'reindl', 'perez'}) and (dni_extra is None): + if (model in {'haydavies', 'reindl', 'perez', 'perez-driesse'}) and (dni_extra is None): raise ValueError(f'dni_extra is required for model {model}') if model == 'isotropic': @@ -475,6 +475,12 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, sky = perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, solar_zenith, solar_azimuth, airmass, model=model_perez) + elif model == 'perez-driesse': + if airmass is None: + airmass = atmosphere.get_relative_airmass(solar_zenith) + sky = perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, + solar_zenith, solar_azimuth, airmass, + model=model_perez) else: raise ValueError(f'invalid model selection {model}') From 7848148112dd995a4f7a944eebe801a5474d3dd9 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 28 Sep 2023 13:18:25 +0200 Subject: [PATCH 06/18] Completed doc strings. Also a bit of fine-tuning code. --- pvlib/irradiance.py | 170 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 131 insertions(+), 39 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 1f5e87cb90..ad49051eea 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -325,6 +325,7 @@ def get_total_irradiance(surface_tilt, surface_azimuth, * reindl * king * perez + * perez-driesse Parameters ---------- @@ -353,7 +354,8 @@ def get_total_irradiance(surface_tilt, surface_azimuth, the list of accepted values. model : str, default 'isotropic' Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``, - ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``. + ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``, + ``'perez-driesse'``. model_perez : str, default 'allsitescomposite1990' Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`. @@ -365,13 +367,13 @@ def get_total_irradiance(surface_tilt, surface_azimuth, Notes ----- - Models ``'haydavies'``, ``'reindl'``, or ``'perez'`` require - ``'dni_extra'``. Values can be calculated using + Models ``'haydavies'``, ``'reindl'``, ``'perez'`` and ``'perez-driesse'`` + require ``'dni_extra'``. Values can be calculated using :py:func:`~pvlib.irradiance.get_extra_radiation`. - The ``'perez'`` model requires relative airmass (``airmass``) as input. If - ``airmass`` is not provided, it is calculated using the defaults in - :py:func:`~pvlib.atmosphere.get_relative_airmass`. + The ``'perez'`` and ``'perez-driesse'`` models require relative airmass + (``airmass``) as input. If ``airmass`` is not provided, it is calculated + using the defaults in :py:func:`~pvlib.atmosphere.get_relative_airmass`. """ poa_sky_diffuse = get_sky_diffuse( @@ -402,6 +404,7 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, * reindl * king * perez + * perez-driesse Parameters ---------- @@ -425,7 +428,8 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, Relative airmass (not adjusted for pressure). [unitless] model : str, default 'isotropic' Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``, - ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``. + ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``, + ``'perez-driesse'``. model_perez : str, default 'allsitescomposite1990' Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`. @@ -442,13 +446,13 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, Notes ----- - Models ``'haydavies'``, ``'reindl'``, and ``'perez``` require 'dni_extra'. - Values can be calculated using + Models ``'haydavies'``, ``'reindl'``, ``'perez'`` and ``'perez-driesse'`` + require ``'dni_extra'``. Values can be calculated using :py:func:`~pvlib.irradiance.get_extra_radiation`. - The ``'perez'`` model requires relative airmass (``airmass``) as input. If - ``airmass`` is not provided, it is calculated using the defaults in - :py:func:`~pvlib.atmosphere.get_relative_airmass`. + The ``'perez'`` and ``'perez-driesse'`` models require relative airmass + (``airmass``) as input. If ``airmass`` is not provided, it is calculated + using the defaults in :py:func:`~pvlib.atmosphere.get_relative_airmass`. """ model = model.lower() @@ -1220,15 +1224,19 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, return sky_diffuse -def _calc_delta(dhi, dni_extra, airmass): +def _calc_delta(dhi, dni_extra, solar_zenith, airmass=None): ''' - Compute the delta parameter, which represents sky dome "brightness". + Compute the delta parameter, which represents sky dome "brightness" + in the Perez and Perez-Driesse models. Helper function for perez_driesse transposition. ''' - max_airmass = get_relative_airmass(90, 'kastenyoung1989') + if airmass is None: + # use the same airmass model as in the original perez work + airmass = get_relative_airmass(solar_zenith, 'kastenyoung1989') - airmass = np.where(np.isnan(airmass), max_airmass, airmass) + max_airmass = get_relative_airmass(90, 'kastenyoung1989') + airmass = np.where(solar_zenith >= 90, max_airmass, airmass) return dhi / (dni_extra / airmass) @@ -1236,7 +1244,7 @@ def _calc_delta(dhi, dni_extra, airmass): def _calc_zeta(dhi, dni, zenith, use_kappa=True): ''' Compute the zeta parameter, which represents sky dome "clearness" - in the perez-driesse model. + in the Perez-Driesse model. Helper function for perez_driesse transposition. ''' @@ -1259,9 +1267,9 @@ def _calc_zeta(dhi, dni, zenith, use_kappa=True): def _f(i, j, zeta): ''' Evaluate the quadratic splines corresponding to the - allsitescomposite1990 perez look-up tables. + allsitescomposite1990 Perez model look-up table. - Helper function for perez_driesse transposition + Helper function for perez_driesse transposition. ''' knots = np.array( [0.000, 0.000, 0.000, @@ -1271,17 +1279,17 @@ def _f(i, j, zeta): coefs = np.array( [[-0.053, 0.529, -0.028, -0.071, 0.061, -0.019], [-0.008, 0.588, -0.062, -0.06 , 0.072, -0.022], - [ 0.131, 0.77 , -0.167, -0.026, 0.106, -0.032], + [ 0.131, 0.770, -0.167, -0.026, 0.106, -0.032], [ 0.328, 0.471, -0.216, 0.069, -0.105, -0.028], - [ 0.557, 0.241, -0.3 , 0.086, -0.085, -0.012], + [ 0.557, 0.241, -0.300, 0.086, -0.085, -0.012], [ 0.861, -0.323, -0.355, 0.24 , -0.467, -0.008], [ 1.212, -1.239, -0.444, 0.305, -0.797, 0.047], [ 1.099, -1.847, -0.365, 0.275, -1.132, 0.124], [ 0.544, 0.157, -0.213, 0.118, -1.455, 0.292], [ 0.544, 0.157, -0.213, 0.118, -1.455, 0.292], - [ 0. , 0. , 0. , 0. , 0. , 0. ], - [ 0. , 0. , 0. , 0. , 0. , 0. ], - [ 0. , 0. , 0. , 0. , 0. , 0. ]]) + [ 0.000, 0.000, 0.000, 0.000, 0.000, 0.000], + [ 0.000, 0.000, 0.000, 0.000, 0.000, 0.000], + [ 0.000, 0.000, 0.000, 0.000, 0.000, 0.000]]) coefs = coefs.T.reshape((2, 3, 13)) @@ -1296,17 +1304,108 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, ''' Determine diffuse irradiance from the sky on a tilted surface using the continuous Perez-Driesse model. - ''' + The Perez-Driesse model [1]_ is a reformulation of the 1990 Perez + model [2]_ that provides continuity of the function and of its first + derivatives. This is achieved by replacing the look-up table of coefficients + with quadratic splines. Seet Notes for more information. + + Parameters + ---------- + surface_tilt : numeric + Surface tilt angles in decimal degrees. surface_tilt must be >=0 + and <=180. The tilt angle is defined as degrees from horizontal + (e.g. surface facing up = 0, surface facing horizon = 90) + + surface_azimuth : numeric + Surface azimuth angles in decimal degrees. surface_azimuth must + be >=0 and <=360. The azimuth convention is defined as degrees + east of north (e.g. North = 0, South=180 East = 90, West = 270). + + dhi : numeric + Diffuse horizontal irradiance in W/m^2. DHI must be >=0. + + dni : numeric + Direct normal irradiance in W/m^2. DNI must be >=0. + + dni_extra : numeric + Extraterrestrial normal irradiance in W/m^2. + + solar_zenith : numeric + apparent (refraction-corrected) zenith angles in decimal + degrees. solar_zenith must be >=0 and <=180. + + solar_azimuth : numeric + Sun azimuth angles in decimal degrees. solar_azimuth must be >=0 + and <=360. The azimuth convention is defined as degrees east of + north (e.g. North = 0, East = 90, West = 270). + + airmass : numeric (optional, default None) + Relative (not pressure-corrected) airmass values. If airmass is a + DataFrame it must be of the same size as all other DataFrame + inputs. The kastenyoung1989 airmass calculation is used internally + and is also recommended when pre-calculating airmass because + it was used in the original model development. + + model : string (optional, default='allsitescomposite1990') + A string which selects the desired set of Perez coefficients. + This argument is only for compatibility with the `perez` function. + If a value other than the default value is provided, it is ignored. + + return_components: bool (optional, default=False) + Flag used to decide whether to return the calculated diffuse components + or not. + + Returns + -------- + numeric, OrderedDict, or DataFrame + Return type controlled by `return_components` argument. + If ``return_components=False``, `sky_diffuse` is returned. + If ``return_components=True``, `diffuse_components` is returned. + + sky_diffuse : numeric + The sky diffuse component of the solar radiation on a tilted + surface. + + diffuse_components : OrderedDict (array input) or DataFrame (Series input) + Keys/columns are: + * sky_diffuse: Total sky diffuse + * isotropic + * circumsolar + * horizon + + Notes + ----- + The Driesse-Perez model can be considered a plug-in replacement for the + 1990 Perez model using the ``'allsitescomposite1990'`` coefficient set. + Deviations between the two are very small, as demonstrated in [1]_. + Support for other coefficient sets is not provided because the 1990 + set is based on the largest and most diverse set of empirical data. + + References + ---------- + .. [1] A. Driesse, A. Jensen, R. Perez, A Continuous Form of the Perez + Diffuse Sky Model for Forward and Reverse Transposition, accepted + for publication in the Solar Energy Journal. + + .. [2] Perez, R., Ineichen, P., Seals, R., Michalsky, J., Stewart, R., + 1990. Modeling daylight availability and irradiance components from + direct and global irradiance. Solar Energy 44 (5), 271-289. + + See also + -------- + perez + isotropic + haydavies + klucher + reindl + king + ''' if model is not 'allsitescomposite1990': raise Warning("The 'model' parameter is ignored. " "Only 'allsitescomposite1990' is available.") - if airmass is None: - # use the same airmass model as in the original perez work - airmass = get_relative_airmass(solar_zenith, 'kastenyoung1989') - - delta = _calc_delta(dhi, dni_extra, airmass) + delta = _calc_delta(dhi, dni_extra, solar_zenith, airmass) zeta = _calc_zeta(dhi, dni, solar_zenith, use_kappa=True) z = np.radians(solar_zenith) @@ -1314,10 +1413,11 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, F1 = _f(1, 1, zeta) + _f(1, 2, zeta) * delta + _f(1, 3, zeta) * z F2 = _f(2, 1, zeta) + _f(2, 2, zeta) * delta + _f(2, 3, zeta) * z - # note the upper limit on F1 + # note the newly recommended upper limit on F1 F1 = np.clip(F1, 0, 0.9) # lines after this point are identical to the original perez function + # with some checks removed A = aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth) @@ -1342,14 +1442,6 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, diffuse_components['circumsolar'] = dhi * term2 diffuse_components['horizon'] = dhi * term3 - # Set values of components to 0 when sky_diffuse is 0 - mask = sky_diffuse == 0 - if isinstance(sky_diffuse, pd.Series): - diffuse_components = pd.DataFrame(diffuse_components) - diffuse_components.loc[mask] = 0 - else: - diffuse_components = {k: np.where(mask, 0, v) for k, v in - diffuse_components.items()} return diffuse_components else: return sky_diffuse From c00029bf7a75b6dbec85010a146f304a45fb96e3 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 28 Sep 2023 13:23:02 +0200 Subject: [PATCH 07/18] Updated whatsnew. --- docs/sphinx/source/whatsnew/v0.10.3.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index efa2fab075..c64e9c95f8 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -7,6 +7,8 @@ v0.10.3 (Anticipated December, 2023) Enhancements ~~~~~~~~~~~~ +* Added the continuous Perez-Driesse transposition model. + :py:func:`pvlib.irradiance.perez_driesse` (:issue:`1841`, :pull:`1876`) Bug fixes @@ -23,4 +25,4 @@ Documentation Contributors ~~~~~~~~~~~~ - +* Anton Driesse (:ghuser:`adriesse`) From f08bd2faa9308af834605e4164cfbb13e1695056 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Fri, 29 Sep 2023 11:37:06 +0200 Subject: [PATCH 08/18] Bugfix, formatting fix, and add all tests. --- pvlib/irradiance.py | 36 ++++++++-------- pvlib/tests/test_irradiance.py | 75 +++++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index ad49051eea..216d0f5f57 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -457,7 +457,8 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, model = model.lower() - if (model in {'haydavies', 'reindl', 'perez', 'perez-driesse'}) and (dni_extra is None): + if ((model in {'haydavies', 'reindl', 'perez', 'perez-driesse'}) and + (dni_extra is None)): raise ValueError(f'dni_extra is required for model {model}') if model == 'isotropic': @@ -1277,19 +1278,19 @@ def _f(i, j, zeta): 1.000, 1.000, 1.000]) coefs = np.array( - [[-0.053, 0.529, -0.028, -0.071, 0.061, -0.019], - [-0.008, 0.588, -0.062, -0.06 , 0.072, -0.022], - [ 0.131, 0.770, -0.167, -0.026, 0.106, -0.032], - [ 0.328, 0.471, -0.216, 0.069, -0.105, -0.028], - [ 0.557, 0.241, -0.300, 0.086, -0.085, -0.012], - [ 0.861, -0.323, -0.355, 0.24 , -0.467, -0.008], - [ 1.212, -1.239, -0.444, 0.305, -0.797, 0.047], - [ 1.099, -1.847, -0.365, 0.275, -1.132, 0.124], - [ 0.544, 0.157, -0.213, 0.118, -1.455, 0.292], - [ 0.544, 0.157, -0.213, 0.118, -1.455, 0.292], - [ 0.000, 0.000, 0.000, 0.000, 0.000, 0.000], - [ 0.000, 0.000, 0.000, 0.000, 0.000, 0.000], - [ 0.000, 0.000, 0.000, 0.000, 0.000, 0.000]]) + [[-0.053, +0.529, -0.028, -0.071, +0.061, -0.019], + [-0.008, +0.588, -0.062, -0.06 , +0.072, -0.022], + [+0.131, +0.770, -0.167, -0.026, +0.106, -0.032], + [+0.328, +0.471, -0.216, +0.069, -0.105, -0.028], + [+0.557, +0.241, -0.300, +0.086, -0.085, -0.012], + [+0.861, -0.323, -0.355, +0.24 , -0.467, -0.008], + [ 1.212, -1.239, -0.444, +0.305, -0.797, +0.047], + [ 1.099, -1.847, -0.365, +0.275, -1.132, +0.124], + [+0.544, +0.157, -0.213, +0.118, -1.455, +0.292], + [+0.544, +0.157, -0.213, +0.118, -1.455, +0.292], + [+0.000, +0.000, +0.000, +0.000, +0.000, +0.000], + [+0.000, +0.000, +0.000, +0.000, +0.000, +0.000], + [+0.000, +0.000, +0.000, +0.000, +0.000, +0.000]]) coefs = coefs.T.reshape((2, 3, 13)) @@ -1307,8 +1308,8 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, The Perez-Driesse model [1]_ is a reformulation of the 1990 Perez model [2]_ that provides continuity of the function and of its first - derivatives. This is achieved by replacing the look-up table of coefficients - with quadratic splines. Seet Notes for more information. + derivatives. This is achieved by replacing the look-up table of + coefficients with quadratic splines. Seet Notes for more information. Parameters ---------- @@ -1442,6 +1443,9 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, diffuse_components['circumsolar'] = dhi * term2 diffuse_components['horizon'] = dhi * term3 + if isinstance(sky_diffuse, pd.Series): + diffuse_components = pd.DataFrame(diffuse_components) + return diffuse_components else: return sky_diffuse diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index fc47a43362..8f24e7a605 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -280,7 +280,20 @@ def test_perez_driesse(irrad_data, ephem_data, dni_et, relative_airmass): dni_et, ephem_data['apparent_zenith'], ephem_data['azimuth'], relative_airmass) expected = pd.Series(np.array( - [0., 30.00, np.nan, 47.40]), + [0., 29.991, np.nan, 47.397]), + index=irrad_data.index) + assert_series_equal(out, expected, check_less_precise=2) + + +def test_perez_driesse_airmass(irrad_data, ephem_data, dni_et): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, + dni_et, ephem_data['apparent_zenith'], + ephem_data['azimuth'], airmass=None) + print(out) + expected = pd.Series(np.array( + [0., 29.991, np.nan, 47.397]), index=irrad_data.index) assert_series_equal(out, expected, check_less_precise=2) @@ -309,6 +322,32 @@ def test_perez_components(irrad_data, ephem_data, dni_et, relative_airmass): assert_series_equal(sum_components, expected_for_sum, check_less_precise=2) +def test_perez_driesse_components(irrad_data, ephem_data, dni_et, + relative_airmass): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, + dni_et, ephem_data['apparent_zenith'], + ephem_data['azimuth'], relative_airmass, + return_components=True) + + expected = pd.DataFrame(np.array( + [[0., 29.991, np.nan, 47.397], + [0., 25.806, np.nan, 33.181], + [0., 0.000, np.nan, 4.197], + [0., 4.184, np.nan, 10.018]]).T, + columns=['sky_diffuse', 'isotropic', 'circumsolar', 'horizon'], + index=irrad_data.index + ) + expected_for_sum = expected['sky_diffuse'].copy() + expected_for_sum.iloc[2] = 0 + sum_components = out.iloc[:, 1:].sum(axis=1) + sum_components.name = 'sky_diffuse' + + assert_frame_equal(out, expected, check_less_precise=2) + assert_series_equal(sum_components, expected_for_sum, check_less_precise=2) + + def test_perez_negative_horizon(): times = pd.date_range(start='20190101 11:30:00', freq='1H', periods=5, tz='US/Central') @@ -368,6 +407,21 @@ def test_perez_arrays(irrad_data, ephem_data, dni_et, relative_airmass): assert isinstance(out, np.ndarray) +def test_perez_driesse_arrays(irrad_data, ephem_data, dni_et, + relative_airmass): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'].values, + dni.values, dni_et, + ephem_data['apparent_zenith'].values, + ephem_data['azimuth'].values, + relative_airmass.values) + expected = np.array( + [0., 29.990, np.nan, 47.396]) + assert_allclose(out, expected, atol=1e-2) + assert isinstance(out, np.ndarray) + + def test_perez_scalar(): # copied values from fixtures out = irradiance.perez(40, 180, 118.45831879, 939.95469881, @@ -378,8 +432,17 @@ def test_perez_scalar(): assert_allclose(out, 109.084332) +def test_perez_driesse_scalar(): + # copied values from fixtures + out = irradiance.perez_driesse(40, 180, 118.458, 939.954, + 1321.165, 10.564, 144.765, 1.016) + # this will fail. out is ndarry with ndim == 0. fix in future version. + # assert np.isscalar(out) + assert_allclose(out, 110.341, atol=1e-2) + + @pytest.mark.parametrize('model', ['isotropic', 'klucher', 'haydavies', - 'reindl', 'king', 'perez']) + 'reindl', 'king', 'perez', 'perez-driesse']) def test_sky_diffuse_zenith_close_to_90(model): # GH 432 sky_diffuse = irradiance.get_sky_diffuse( @@ -431,7 +494,7 @@ def test_campbell_norman(): def test_get_total_irradiance(irrad_data, ephem_data, dni_et, relative_airmass): models = ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez'] + 'haydavies', 'reindl', 'king', 'perez', 'perez-driesse'] for model in models: total = irradiance.get_total_irradiance( @@ -449,7 +512,8 @@ def test_get_total_irradiance(irrad_data, ephem_data, dni_et, @pytest.mark.parametrize('model', ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez']) + 'haydavies', 'reindl', 'king', + 'perez', 'perez-driesse']) def test_get_total_irradiance_albedo( irrad_data, ephem_data, dni_et, relative_airmass, model): albedo = pd.Series(0.2, index=ephem_data.index) @@ -468,7 +532,8 @@ def test_get_total_irradiance_albedo( @pytest.mark.parametrize('model', ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez']) + 'haydavies', 'reindl', 'king', + 'perez', 'perez-driesse']) def test_get_total_irradiance_scalars(model): total = irradiance.get_total_irradiance( 32, 180, From 4382f8ab3694787b60a5e6e16460e92303655223 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Fri, 29 Sep 2023 13:05:07 +0200 Subject: [PATCH 09/18] Test warning plus some other small changes. --- pvlib/irradiance.py | 21 +++++++++++---------- pvlib/tests/test_irradiance.py | 6 ++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 216d0f5f57..fe32851659 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -7,6 +7,7 @@ import datetime from collections import OrderedDict from functools import partial +import warnings import numpy as np import pandas as pd @@ -459,7 +460,7 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, if ((model in {'haydavies', 'reindl', 'perez', 'perez-driesse'}) and (dni_extra is None)): - raise ValueError(f'dni_extra is required for model {model}') + raise ValueError(f'dni_extra is required for model {model}') if model == 'isotropic': sky = isotropic(surface_tilt, dhi) @@ -481,11 +482,10 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, airmass, model=model_perez) elif model == 'perez-driesse': - if airmass is None: - airmass = atmosphere.get_relative_airmass(solar_zenith) + # perez_driesse will calculate its own airmass if needed sky = perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, - solar_zenith, solar_azimuth, airmass, - model=model_perez) + solar_zenith, solar_azimuth, airmass, + model=model_perez) else: raise ValueError(f'invalid model selection {model}') @@ -1279,11 +1279,11 @@ def _f(i, j, zeta): coefs = np.array( [[-0.053, +0.529, -0.028, -0.071, +0.061, -0.019], - [-0.008, +0.588, -0.062, -0.06 , +0.072, -0.022], + [-0.008, +0.588, -0.062, -0.060, +0.072, -0.022], [+0.131, +0.770, -0.167, -0.026, +0.106, -0.032], [+0.328, +0.471, -0.216, +0.069, -0.105, -0.028], [+0.557, +0.241, -0.300, +0.086, -0.085, -0.012], - [+0.861, -0.323, -0.355, +0.24 , -0.467, -0.008], + [+0.861, -0.323, -0.355, +0.240, -0.467, -0.008], [ 1.212, -1.239, -0.444, +0.305, -0.797, +0.047], [ 1.099, -1.847, -0.365, +0.275, -1.132, +0.124], [+0.544, +0.157, -0.213, +0.118, -1.455, +0.292], @@ -1402,9 +1402,10 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, reindl king ''' - if model is not 'allsitescomposite1990': - raise Warning("The 'model' parameter is ignored. " - "Only 'allsitescomposite1990' is available.") + if model not in ('allsitescomposite1990', '1990'): + warnings.warn("The 'model' parameter is ignored. " + "Only 'allsitescomposite1990' is available.", + UserWarning) delta = _calc_delta(dhi, dni_extra, solar_zenith, airmass) zeta = _calc_zeta(dhi, dni, solar_zenith, use_kappa=True) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 8f24e7a605..e018efc0ea 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -285,6 +285,12 @@ def test_perez_driesse(irrad_data, ephem_data, dni_et, relative_airmass): assert_series_equal(out, expected, check_less_precise=2) +def test_perez_driesse_warning(): + with pytest.warns(UserWarning, match='allsitescomposite1990'): + irradiance.perez_driesse(48, 180, 0, 0, 0, 48, 180, 1.5, + model='allsitescomposite1988') + + def test_perez_driesse_airmass(irrad_data, ephem_data, dni_et): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan From 5af34e8644711d392438203fb23988db07cf6503 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Fri, 29 Sep 2023 13:18:16 +0200 Subject: [PATCH 10/18] Make flake happy. --- pvlib/irradiance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index fe32851659..64ea8d6d54 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -458,9 +458,9 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, model = model.lower() - if ((model in {'haydavies', 'reindl', 'perez', 'perez-driesse'}) and - (dni_extra is None)): - raise ValueError(f'dni_extra is required for model {model}') + if dni_extra is None and model in {'haydavies', 'reindl', + 'perez', 'perez-driesse'}: + raise ValueError(f'dni_extra is required for model {model}') if model == 'isotropic': sky = isotropic(surface_tilt, dhi) From 93a7c99c097367c45db028dbd742d4c42d4b6b58 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Sat, 30 Sep 2023 14:26:57 +0200 Subject: [PATCH 11/18] Update pvlib/irradiance.py Co-authored-by: Cliff Hansen --- pvlib/irradiance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 64ea8d6d54..5cda1f84b8 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1309,7 +1309,7 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, The Perez-Driesse model [1]_ is a reformulation of the 1990 Perez model [2]_ that provides continuity of the function and of its first derivatives. This is achieved by replacing the look-up table of - coefficients with quadratic splines. Seet Notes for more information. + coefficients with quadratic splines. Parameters ---------- From 48ec54221ed746d8de62376b2a1957e0f8695b1a Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Sat, 30 Sep 2023 15:49:48 +0200 Subject: [PATCH 12/18] Address comments. --- pvlib/irradiance.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 5cda1f84b8..14ae3d3acf 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1324,10 +1324,10 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, east of north (e.g. North = 0, South=180 East = 90, West = 270). dhi : numeric - Diffuse horizontal irradiance in W/m^2. DHI must be >=0. + Diffuse horizontal irradiance in W/m^2. dhi must be >=0. dni : numeric - Direct normal irradiance in W/m^2. DNI must be >=0. + Direct normal irradiance in W/m^2. dni must be >=0. dni_extra : numeric Extraterrestrial normal irradiance in W/m^2. @@ -1380,8 +1380,8 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, The Driesse-Perez model can be considered a plug-in replacement for the 1990 Perez model using the ``'allsitescomposite1990'`` coefficient set. Deviations between the two are very small, as demonstrated in [1]_. - Support for other coefficient sets is not provided because the 1990 - set is based on the largest and most diverse set of empirical data. + Other coefficient sets are not supported because the 1990 set is + based on the largest and most diverse set of empirical data. References ---------- From 702f088b63b45cd436c702b0419a5de827b70278 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Mon, 2 Oct 2023 22:40:33 +0200 Subject: [PATCH 13/18] Add contributor code comments. --- pvlib/irradiance.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 14ae3d3acf..b56888264c 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1402,6 +1402,8 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, reindl king ''' + # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Oct., 2023 + if model not in ('allsitescomposite1990', '1990'): warnings.warn("The 'model' parameter is ignored. " "Only 'allsitescomposite1990' is available.", @@ -2590,6 +2592,8 @@ def erbs_driesse(ghi, zenith, datetime_or_doy=None, dni_extra=None, orgill_hollands boland """ + # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Aug., 2023 + # central polynomial coefficients with float64 precision p = [+12.26911439571261000, -16.47050842469730700, From 177e637be4addfc65aa74782233ec3530f75ea80 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 5 Oct 2023 16:18:07 +0200 Subject: [PATCH 14/18] Update pvlib/irradiance.py Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- pvlib/irradiance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index b56888264c..8076672455 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1377,7 +1377,7 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, Notes ----- - The Driesse-Perez model can be considered a plug-in replacement for the + The Perez-Driesse model can be considered a plug-in replacement for the 1990 Perez model using the ``'allsitescomposite1990'`` coefficient set. Deviations between the two are very small, as demonstrated in [1]_. Other coefficient sets are not supported because the 1990 set is From 10e844c5474f9164d7f3d72ee60d52bda3576fd5 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 5 Oct 2023 17:06:18 +0200 Subject: [PATCH 15/18] Adapt to reviewer preferences. --- pvlib/irradiance.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 8076672455..cdb2d8dcb1 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -14,7 +14,6 @@ from scipy.interpolate import splev from pvlib import atmosphere, solarposition, tools -from pvlib.atmosphere import get_relative_airmass import pvlib # used to avoid dni name collision in complete_irradiance @@ -1234,15 +1233,16 @@ def _calc_delta(dhi, dni_extra, solar_zenith, airmass=None): ''' if airmass is None: # use the same airmass model as in the original perez work - airmass = get_relative_airmass(solar_zenith, 'kastenyoung1989') + airmass = atmosphere.get_relative_airmass(solar_zenith, + 'kastenyoung1989') - max_airmass = get_relative_airmass(90, 'kastenyoung1989') + max_airmass = atmosphere.get_relative_airmass(90, 'kastenyoung1989') airmass = np.where(solar_zenith >= 90, max_airmass, airmass) return dhi / (dni_extra / airmass) -def _calc_zeta(dhi, dni, zenith, use_kappa=True): +def _calc_zeta(dhi, dni, zenith): ''' Compute the zeta parameter, which represents sky dome "clearness" in the Perez-Driesse model. @@ -1252,15 +1252,17 @@ def _calc_zeta(dhi, dni, zenith, use_kappa=True): dhi = np.asarray(dhi) dni = np.asarray(dni) + # first calculate what zeta would be without the kappa correction + # using eq. 5 and eq. 13 with np.errstate(invalid='ignore'): zeta = dni / (dhi + dni) zeta = np.where(dhi == 0, 0.0, zeta) - if use_kappa: - kappa = 1.041 - kterm = kappa * np.radians(zenith) ** 3 - zeta = zeta / (1 - kterm * (zeta - 1)) + # then apply the kappa correction in a manner analogous to eq. 7 + kappa = 1.041 + kterm = kappa * np.radians(zenith) ** 3 + zeta = zeta / (1 - kterm * (zeta - 1)) return zeta @@ -1410,7 +1412,7 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, UserWarning) delta = _calc_delta(dhi, dni_extra, solar_zenith, airmass) - zeta = _calc_zeta(dhi, dni, solar_zenith, use_kappa=True) + zeta = _calc_zeta(dhi, dni, solar_zenith) z = np.radians(solar_zenith) From 2c702f02cb4bffc51f6c188a0f0fab4c61ecbd0a Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Sat, 7 Oct 2023 11:45:13 +0200 Subject: [PATCH 16/18] Adapt to flake preferences. --- pvlib/irradiance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index cdb2d8dcb1..a04ea36992 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1233,7 +1233,7 @@ def _calc_delta(dhi, dni_extra, solar_zenith, airmass=None): ''' if airmass is None: # use the same airmass model as in the original perez work - airmass = atmosphere.get_relative_airmass(solar_zenith, + airmass = atmosphere.get_relative_airmass(solar_zenith, 'kastenyoung1989') max_airmass = atmosphere.get_relative_airmass(90, 'kastenyoung1989') From d004387c01c5154252b3877401b44c2264c39f0f Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 10 Oct 2023 22:21:04 +0200 Subject: [PATCH 17/18] Remove model pseudo-option. --- pvlib/irradiance.py | 15 ++------------- pvlib/tests/test_irradiance.py | 6 ------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index a04ea36992..b349f221f9 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -483,8 +483,7 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, elif model == 'perez-driesse': # perez_driesse will calculate its own airmass if needed sky = perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, - solar_zenith, solar_azimuth, airmass, - model=model_perez) + solar_zenith, solar_azimuth, airmass) else: raise ValueError(f'invalid model selection {model}') @@ -1303,7 +1302,7 @@ def _f(i, j, zeta): def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, solar_zenith, solar_azimuth, airmass=None, - model='allsitescomposite1990', return_components=False): + return_components=False): ''' Determine diffuse irradiance from the sky on a tilted surface using the continuous Perez-Driesse model. @@ -1350,11 +1349,6 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, and is also recommended when pre-calculating airmass because it was used in the original model development. - model : string (optional, default='allsitescomposite1990') - A string which selects the desired set of Perez coefficients. - This argument is only for compatibility with the `perez` function. - If a value other than the default value is provided, it is ignored. - return_components: bool (optional, default=False) Flag used to decide whether to return the calculated diffuse components or not. @@ -1406,11 +1400,6 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, ''' # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Oct., 2023 - if model not in ('allsitescomposite1990', '1990'): - warnings.warn("The 'model' parameter is ignored. " - "Only 'allsitescomposite1990' is available.", - UserWarning) - delta = _calc_delta(dhi, dni_extra, solar_zenith, airmass) zeta = _calc_zeta(dhi, dni, solar_zenith) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index e018efc0ea..8f24e7a605 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -285,12 +285,6 @@ def test_perez_driesse(irrad_data, ephem_data, dni_et, relative_airmass): assert_series_equal(out, expected, check_less_precise=2) -def test_perez_driesse_warning(): - with pytest.warns(UserWarning, match='allsitescomposite1990'): - irradiance.perez_driesse(48, 180, 0, 0, 0, 48, 180, 1.5, - model='allsitescomposite1988') - - def test_perez_driesse_airmass(irrad_data, ephem_data, dni_et): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan From 90b84e2bfe25d91d484d1dbcb50360316741e404 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 10 Oct 2023 22:23:57 +0200 Subject: [PATCH 18/18] Flake --- pvlib/irradiance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index b349f221f9..efae7f6236 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -7,7 +7,6 @@ import datetime from collections import OrderedDict from functools import partial -import warnings import numpy as np import pandas as pd