From c6a8ae72ef94486d287477d6498e13e5443bbe82 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 11 Jul 2019 16:46:51 +0200 Subject: [PATCH 01/13] Insert three function placeholders in pvsystem.py --- pvlib/pvsystem.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 35701bd3a2..7854da8645 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1064,6 +1064,20 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): return iam +def iam_martin_ruiz(aoi, a_r): + ''' + Determine the incidence angle modifier using the Martin & Ruiz model. + ''' + raise NotImplementedError + + +def iam_interp(aoi, measurements): + ''' + Determine the incidence angle modifier by interpolating measured values. + ''' + raise NotImplementedError + + def calcparams_desoto(effective_irradiance, temp_cell, alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, EgRef=1.121, dEgdT=-0.0002677, @@ -1994,6 +2008,14 @@ def pvsyst_celltemp(poa_global, temp_air, wind_speed=1.0, eta_m=0.1, return temp_cell +def celltemp_faiman(poa_global, temp_air, wind_speed, u0, u1): + ''' + Calculate cell temperature using an emperical heat loss factor model + in the form proposed by Faiman. + ''' + raise NotImplementedError + + def sapm_spectral_loss(airmass_absolute, module): """ Calculates the SAPM spectral loss coefficient, F1. From 2177d78a111e287c86ee3d37f93ec0367dc30e7c Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Fri, 12 Jul 2019 17:39:56 +0200 Subject: [PATCH 02/13] transform docstring from matlab --- pvlib/pvsystem.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 7854da8645..f10dc0ff2c 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1066,7 +1066,61 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): def iam_martin_ruiz(aoi, a_r): ''' - Determine the incidence angle modifier using the Martin & Ruiz model. + Determine the incidence angle modifier using the Martin + and Ruiz incident angle model + + iam_martin_ruiz calculates the incidence angle modifier (angular + factor) as described by Martin and Ruiz in [1]. The information + required is the incident angle (theta) and the angular losses + coefficient (ar). Please note that [1] has a corrigendum which makes the + document much simpler to understand. + + The incident angle modifier is defined as + [1-exp(-cos(theta/ar))] / [1-exp(-1/ar)], which is + presented as AL(alpha) = 1 - IAM in equation 4 of [1]. Thus IAM is + equal to 1 at theta = 0, and equal to 0 at theta = 90. IAM is a + column vector with the same number of elements as the largest input + vector. + + Parameters + ---------- + aoi : numeric, degrees + The angle of incidence between the module normal vector and the + sun-beam vector in degrees. Theta must be a numeric scalar or vector. + For any values of theta where abs(theta)>90, IAM is set to 0. For any + values of theta where -90 < theta < 0, theta is set to abs(theta) and + evaluated. A warning will be generated if any(theta<0 or theta>90). + + ar : numeric + The angular losses coefficient described in equation 3 of [1]. + This is an empirical dimensionless parameter. Values of ar are + generally on the order of 0.08 to 0.25 for flat-plate PV modules. ar + must be a numeric scalar or vector with all values > 0. If ar + is a vector, it must be the same size as all other input vectors. + + Returns + ------- + iam : numeric + The incident angle modifier + + + + References + ---------- + [1] N. Martin and J. M. Ruiz, "Calculation of the PV modules angular + losses under field conditions by means of an analytical model", Solar + Energy Materials & Solar Cells, vol. 70, pp. 25-38, 2001. + + [2] N. Martin and J. M. Ruiz, "Corrigendum to 'Calculation of the PV + modules angular losses under field conditions by means of an + analytical model'", Solar Energy Materials & Solar Cells, vol. 110, + pp. 154, 2013. + + See Also + -------- + getaoi + physicaliam + ashraeiam ''' raise NotImplementedError From f036041f4e66dd19eee62ef138a6c8c7a8f09256 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Sat, 13 Jul 2019 18:43:46 +0200 Subject: [PATCH 03/13] Code for iam_martin and iam_interp using greek symbol for theta --- pvlib/pvsystem.py | 106 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 18 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f10dc0ff2c..b86aa0cc7b 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -17,7 +17,7 @@ import pandas as pd from pvlib import atmosphere, irradiance, tools, singlediode as _singlediode -from pvlib.tools import _build_kwargs +from pvlib.tools import _build_kwargs, cosd from pvlib.location import Location @@ -1064,7 +1064,7 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): return iam -def iam_martin_ruiz(aoi, a_r): +def iam_martin_ruiz(θ:'degrees', a_r=0.16): ''' Determine the incidence angle modifier using the Martin and Ruiz incident angle model @@ -1084,26 +1084,21 @@ def iam_martin_ruiz(aoi, a_r): Parameters ---------- - aoi : numeric, degrees + θ : numeric, degrees The angle of incidence between the module normal vector and the sun-beam vector in degrees. Theta must be a numeric scalar or vector. - For any values of theta where abs(theta)>90, IAM is set to 0. For any - values of theta where -90 < theta < 0, theta is set to abs(theta) and - evaluated. A warning will be generated if any(theta<0 or theta>90). + iam is 0 where |θ| > 90 - ar : numeric + a_r : numeric The angular losses coefficient described in equation 3 of [1]. This is an empirical dimensionless parameter. Values of ar are - generally on the order of 0.08 to 0.25 for flat-plate PV modules. ar - must be a numeric scalar or vector with all values > 0. If ar - is a vector, it must be the same size as all other input vectors. + generally on the order of 0.08 to 0.25 for flat-plate PV modules. a_r + must be a positive numeric scalar. Returns ------- iam : numeric - The incident angle modifier - - + The incident angle modifier(s) References ---------- @@ -1118,18 +1113,93 @@ def iam_martin_ruiz(aoi, a_r): See Also -------- - getaoi physicaliam ashraeiam + iam_interp ''' - raise NotImplementedError + # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019 + + θ = np.asanyarray(θ) + a_r = np.asanyarray(a_r) + + if not np.all(np.positive(a_r)): + raise RuntimeError("The parameter 'a_r' cannot be zero or negative.") + + θ = np.clip(θ, -90, 90) + iam = (1 - np.exp(-cosd(θ)/ a_r)) / (1 - np.exp(-1 / a_r)) + + return iam -def iam_interp(aoi, measurements): +def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): ''' - Determine the incidence angle modifier by interpolating measured values. + Determine the incidence angle modifier by interpolating a set of + reference values, which are usually measured values. + + Parameters + ---------- + θ : numeric, degrees + The angle of incidence between the module normal vector and the + sun-beam vector in degrees. + + θ_ref : numeric, degrees + Vector of angles at which the iam is known. + + iam_ref : + iam values for each angle in θ_ref. + + method : + Specifies the intrpolation method. + Useful options are: 'linear', 'quadratic','cubic'. + See scipy.interpolate.interp1d for more options. + + normalize : boolean + When true, the interpolated values are divided by the interpolated + value at zero degrees. This ensures that the iam and normal + incidence is equal to 1.0. + + Notes: + ------ + θ_ref must have two or more points and may span any range of angles, but + typically there will be a dozen or more points in the range 0-90 degrees. + iam beyond the range of θ_ref are extrapolated, but constrained to be + non-negative. + + The sign of θ is ignore, only the magnitude is used. + + Returns + ------- + iam : numeric + The incident angle modifier(s) + + See Also + -------- + physicaliam + ashraeiam + iam_martin_ruiz ''' - raise NotImplementedError + # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019 + + from scipy.interpolate import interp1d + + # Scipy doesn't give the clearest feedback, so check number of points here. + MIN_REF_VALS = {'linear':2, 'quadratic':3, 'cubic':4, 1:2, 2:3, 3:4} + + if len(θ_ref) < MIN_REF_VALS.get(method, 2): + raise ValueError("Too few reference points defined " + "for interpolation method '%s'." % method) + + interpolator = interp1d(θ_ref, iam_ref, kind=method, + fill_value='extrapolate') + θ = np.asanyarray(θ) + θ = np.abs(θ) + iam = interpolator(θ) + iam = np.clip(iam, 0, None) + + if normalize: + iam /= interpolator(0) + + return iam def calcparams_desoto(effective_irradiance, temp_cell, From faecf0cd0719e4f482581b70845aeddb974f36ec Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 17 Jul 2019 15:28:16 +0200 Subject: [PATCH 04/13] Minor improvements. --- pvlib/pvsystem.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index b86aa0cc7b..01259d39c4 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1125,15 +1125,15 @@ def iam_martin_ruiz(θ:'degrees', a_r=0.16): if not np.all(np.positive(a_r)): raise RuntimeError("The parameter 'a_r' cannot be zero or negative.") - θ = np.clip(θ, -90, 90) - iam = (1 - np.exp(-cosd(θ)/ a_r)) / (1 - np.exp(-1 / a_r)) + iam = (1 - np.exp(-cosd(θ) / a_r)) / (1 - np.exp(-1 / a_r)) + iam = np.where(np.abs(θ) >= 90.0, 0.0, iam) return iam def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): ''' - Determine the incidence angle modifier by interpolating a set of + Determine the incidence angle modifier (iam) by interpolating a set of reference values, which are usually measured values. Parameters @@ -1165,7 +1165,7 @@ def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): iam beyond the range of θ_ref are extrapolated, but constrained to be non-negative. - The sign of θ is ignore, only the magnitude is used. + The sign of θ is ignored; only the magnitude is used. Returns ------- @@ -1189,6 +1189,10 @@ def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): raise ValueError("Too few reference points defined " "for interpolation method '%s'." % method) + if np.any(np.less(iam_ref, 0)): + raise ValueError("Negative value(s) found in 'iam_ref'. " + "This is not physically possible.") + interpolator = interp1d(θ_ref, iam_ref, kind=method, fill_value='extrapolate') θ = np.asanyarray(θ) From 054981e3b29dfcd6a0ebe8eca4668bf85c0460dc Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 17 Jul 2019 15:28:47 +0200 Subject: [PATCH 05/13] Tests for iam_martin_ruiz() and iam_interp() --- pvlib/test/test_pvsystem.py | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 17f70e9d12..03817bebe8 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -148,6 +148,87 @@ def test_PVSystem_physicaliam(mocker): assert iam < 1. +def test_iam_martin_ruiz(): + + aoi = 45. + a_r = 0.16 + expected = 0.98986965 + + # will fail of default values change + iam = pvsystem.iam_martin_ruiz(aoi) + assert_allclose(iam, expected) + # will fail of parameter names change + iam = pvsystem.iam_martin_ruiz(θ=aoi, a_r=a_r) + assert_allclose(iam, expected) + + a_r = 0.18 + aoi = [-100, -60, 0, 60, 100, np.nan, np.inf] + expected = [0.0, 0.9414631, 1.0, 0.9414631, 0.0, np.nan, 0.0] + + with np.errstate(invalid='ignore'): + # check out of range of inputs as list + iam = pvsystem.iam_martin_ruiz(aoi, a_r) + assert_allclose(iam, expected, equal_nan=True) + + # check out of range of inputs as array + iam = pvsystem.iam_martin_ruiz(np.array(aoi), a_r) + assert_allclose(iam, expected, equal_nan=True) + + # check out of range of inputs as Series + iam = pvsystem.iam_martin_ruiz(pd.Series(aoi), a_r) + assert_allclose(iam, expected, equal_nan=True) + + # check exception clause + with pytest.raises(RuntimeError): + pvsystem.iam_martin_ruiz(0.0, a_r=0.0) + + +def test_iam_interp(): + + aoi_meas = [0.0, 45.0, 65.0, 75.0] + iam_meas = [1.0, 0.9, 0.8, 0.6] + + # simple default linear method + aoi = 55.0 + expected = 0.85 + iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) + assert_allclose(iam, expected) + + iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) + assert_allclose(iam, expected) + + # simple non-default method + aoi = 55.0 + expected = 0.8878062 + iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas, method='cubic') + assert_allclose(iam, expected) + + # check with all reference values + aoi = aoi_meas + expected = iam_meas + iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) + assert_allclose(iam, expected) + + # check normalization + iam_mult = np.multiply (0.9, [1.0, 0.9, 0.8, 0.6]) + iam = pvsystem.iam_interp(aoi, aoi_meas, iam_mult, normalize=True) + assert_allclose(iam, expected) + + # check beyond reference values + aoi = [-45, 0, 45, 85, 90, 95, 100, 105, 110] + expected = [0.9, 1. , 0.9, 0.4, 0.3, 0.2, 0.1, 0. , 0. ] + iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) + assert_allclose(iam, expected) + + # check exception clause + with pytest.raises(ValueError): + pvsystem.iam_interp(0.0, [0], [1]) + + # check exception clause + with pytest.raises(ValueError): + pvsystem.iam_interp(0.0, [0, 90], [1, -1]) + + # if this completes successfully we'll be able to do more tests below. @pytest.fixture(scope="session") def sam_data(): From cd04ad1ebc3f2ceac8d57e1d24439f121ce1933e Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 23 Jul 2019 10:26:36 +0200 Subject: [PATCH 06/13] Removed undesirables; polished docstrings and code here and there. --- pvlib/pvsystem.py | 92 ++++++++++++++++++------------------- pvlib/test/test_pvsystem.py | 6 +-- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 01259d39c4..6c9a9158de 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1064,42 +1064,42 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): return iam -def iam_martin_ruiz(θ:'degrees', a_r=0.16): +def iam_martin_ruiz(theta, a_r=0.16): ''' - Determine the incidence angle modifier using the Martin - and Ruiz incident angle model - - iam_martin_ruiz calculates the incidence angle modifier (angular - factor) as described by Martin and Ruiz in [1]. The information - required is the incident angle (theta) and the angular losses - coefficient (ar). Please note that [1] has a corrigendum which makes the - document much simpler to understand. - - The incident angle modifier is defined as - [1-exp(-cos(theta/ar))] / [1-exp(-1/ar)], which is - presented as AL(alpha) = 1 - IAM in equation 4 of [1]. Thus IAM is - equal to 1 at theta = 0, and equal to 0 at theta = 90. IAM is a - column vector with the same number of elements as the largest input - vector. + Determine the incidence angle modifier (iam) using the Martin + and Ruiz incident angle model. Parameters ---------- - θ : numeric, degrees + theta : numeric, degrees The angle of incidence between the module normal vector and the sun-beam vector in degrees. Theta must be a numeric scalar or vector. - iam is 0 where |θ| > 90 + iam is 0 where |theta| > 90. a_r : numeric The angular losses coefficient described in equation 3 of [1]. - This is an empirical dimensionless parameter. Values of ar are - generally on the order of 0.08 to 0.25 for flat-plate PV modules. a_r - must be a positive numeric scalar. + This is an empirical dimensionless parameter. Values of a_r are + generally on the order of 0.08 to 0.25 for flat-plate PV modules. + a_r must be a positive numeric scalar or vector (same length as theta). Returns ------- iam : numeric The incident angle modifier(s) + Notes + ----- + iam_martin_ruiz calculates the incidence angle modifier (iamangular + factor) as described by Martin and Ruiz in [1]. The information + required is the incident angle (theta) and the angular losses + coefficient (a_r). Please note that [1] has a corrigendum which makes + the document much simpler to understand. + + The incident angle modifier is defined as + [1-exp(-cos(theta/ar))] / [1-exp(-1/ar)], which is + presented as AL(alpha) = 1 - IAM in equation 4 of [1]. Thus IAM is + equal to 1 at theta = 0, and equal to 0 at theta = 90. + References ---------- [1] N. Martin and J. M. Ruiz, "Calculation of the PV modules angular @@ -1119,59 +1119,59 @@ def iam_martin_ruiz(θ:'degrees', a_r=0.16): ''' # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019 - θ = np.asanyarray(θ) + theta = np.asanyarray(theta) a_r = np.asanyarray(a_r) - if not np.all(np.positive(a_r)): + if np.any(np.less_equal(a_r, 0)): raise RuntimeError("The parameter 'a_r' cannot be zero or negative.") - iam = (1 - np.exp(-cosd(θ) / a_r)) / (1 - np.exp(-1 / a_r)) - iam = np.where(np.abs(θ) >= 90.0, 0.0, iam) + iam = (1 - np.exp(-cosd(theta) / a_r)) / (1 - np.exp(-1 / a_r)) + iam = np.where(np.abs(theta) >= 90.0, 0.0, iam) return iam -def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): +def iam_interp(theta, theta_ref, iam_ref, method='linear', normalize=True): ''' Determine the incidence angle modifier (iam) by interpolating a set of reference values, which are usually measured values. Parameters ---------- - θ : numeric, degrees + theta : numeric, degrees The angle of incidence between the module normal vector and the sun-beam vector in degrees. - θ_ref : numeric, degrees + theta_ref : numeric, degrees Vector of angles at which the iam is known. iam_ref : - iam values for each angle in θ_ref. + iam values for each angle in theta_ref. method : - Specifies the intrpolation method. + Specifies the interpolation method. Useful options are: 'linear', 'quadratic','cubic'. See scipy.interpolate.interp1d for more options. normalize : boolean When true, the interpolated values are divided by the interpolated - value at zero degrees. This ensures that the iam and normal + value at zero degrees. This ensures that the iam at normal incidence is equal to 1.0. - Notes: - ------ - θ_ref must have two or more points and may span any range of angles, but - typically there will be a dozen or more points in the range 0-90 degrees. - iam beyond the range of θ_ref are extrapolated, but constrained to be - non-negative. - - The sign of θ is ignored; only the magnitude is used. - Returns ------- iam : numeric The incident angle modifier(s) + Notes: + ------ + theta_ref must have two or more points and may span any range of angles. + Typically there will be a dozen or more points in the range 0-90 degrees. + iam beyond the range of theta_ref are extrapolated, but constrained to be + non-negative. + + The sign of theta is ignored; only the magnitude is used. + See Also -------- physicaliam @@ -1183,9 +1183,9 @@ def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): from scipy.interpolate import interp1d # Scipy doesn't give the clearest feedback, so check number of points here. - MIN_REF_VALS = {'linear':2, 'quadratic':3, 'cubic':4, 1:2, 2:3, 3:4} + MIN_REF_VALS = {'linear': 2, 'quadratic': 3, 'cubic': 4, 1: 2, 2: 3, 3: 4} - if len(θ_ref) < MIN_REF_VALS.get(method, 2): + if len(theta_ref) < MIN_REF_VALS.get(method, 2): raise ValueError("Too few reference points defined " "for interpolation method '%s'." % method) @@ -1193,11 +1193,11 @@ def iam_interp(θ:'degrees', θ_ref, iam_ref, method='linear', normalize=True): raise ValueError("Negative value(s) found in 'iam_ref'. " "This is not physically possible.") - interpolator = interp1d(θ_ref, iam_ref, kind=method, + interpolator = interp1d(theta_ref, iam_ref, kind=method, fill_value='extrapolate') - θ = np.asanyarray(θ) - θ = np.abs(θ) - iam = interpolator(θ) + theta = np.asanyarray(theta) + theta = np.abs(theta) + iam = interpolator(theta) iam = np.clip(iam, 0, None) if normalize: diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 03817bebe8..238f23d594 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -158,7 +158,7 @@ def test_iam_martin_ruiz(): iam = pvsystem.iam_martin_ruiz(aoi) assert_allclose(iam, expected) # will fail of parameter names change - iam = pvsystem.iam_martin_ruiz(θ=aoi, a_r=a_r) + iam = pvsystem.iam_martin_ruiz(theta=aoi, a_r=a_r) assert_allclose(iam, expected) a_r = 0.18 @@ -210,13 +210,13 @@ def test_iam_interp(): assert_allclose(iam, expected) # check normalization - iam_mult = np.multiply (0.9, [1.0, 0.9, 0.8, 0.6]) + iam_mult = np.multiply (0.9, iam_meas) iam = pvsystem.iam_interp(aoi, aoi_meas, iam_mult, normalize=True) assert_allclose(iam, expected) # check beyond reference values aoi = [-45, 0, 45, 85, 90, 95, 100, 105, 110] - expected = [0.9, 1. , 0.9, 0.4, 0.3, 0.2, 0.1, 0. , 0. ] + expected = [0.9, 1.0, 0.9, 0.4, 0.3, 0.2, 0.1, 0.0, 0.0] iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) assert_allclose(iam, expected) From defc07311a497bef6fc5b9c7a01f3404ecc55bd4 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 23 Jul 2019 10:32:35 +0200 Subject: [PATCH 07/13] Final stickler. --- pvlib/test/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 238f23d594..c36e7591c0 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -210,7 +210,7 @@ def test_iam_interp(): assert_allclose(iam, expected) # check normalization - iam_mult = np.multiply (0.9, iam_meas) + iam_mult = np.multiply(0.9, iam_meas) iam = pvsystem.iam_interp(aoi, aoi_meas, iam_mult, normalize=True) assert_allclose(iam, expected) From fcf267768e144b99aaca4b4aa2650f068fe7cf8b Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Tue, 23 Jul 2019 10:43:59 +0200 Subject: [PATCH 08/13] Update v0.7.0.rst --- docs/sphinx/source/whatsnew/v0.7.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index a8b2f21ebd..723c1a1717 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -10,6 +10,11 @@ compatibility notes. **Python 2.7 support ended on June 1, 2019**. (:issue:`501`) **Minimum numpy version is now 1.10.4. Minimum pandas version is now 0.18.1.** +Enhancements +~~~~~~~~~~~~ +* Created two new incidence angle modifier functions: :py:func:`pvlib.pvsystem.iam_martin_ruiz` + and :py:func:`pvlib.pvsystem.iam_interp`. (:issue:`751`) + Bug fixes ~~~~~~~~~ * Fix handling of keyword arguments in `forecasts.get_processed_data`. @@ -25,3 +30,4 @@ Contributors * Mark Campanellli (:ghuser:`markcampanelli`) * Will Holmgren (:ghuser:`wholmgren`) * Oscar Dowson (:ghuser:`odow`) +* Anton Driesse (:ghuser:`adriesse`) From 8a93f975d0510052d6916e0e5b794e9eb60e6174 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 24 Jul 2019 00:01:10 +0200 Subject: [PATCH 09/13] Fix many oversights (also an old one). --- pvlib/pvsystem.py | 60 +++++++++++++++++++------------------ pvlib/test/test_pvsystem.py | 2 +- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 6c9a9158de..f23c6a64aa 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -946,7 +946,7 @@ def ashraeiam(aoi, b=0.05): iam = np.where(aoi_gte_90, 0, iam) iam = np.maximum(0, iam) - if isinstance(iam, pd.Series): + if isinstance(aoi, pd.Series): iam = pd.Series(iam, index=aoi.index) return iam @@ -1064,23 +1064,23 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): return iam -def iam_martin_ruiz(theta, a_r=0.16): +def iam_martin_ruiz(aoi, a_r=0.16): ''' Determine the incidence angle modifier (iam) using the Martin and Ruiz incident angle model. Parameters ---------- - theta : numeric, degrees + aoi : numeric, degrees The angle of incidence between the module normal vector and the sun-beam vector in degrees. Theta must be a numeric scalar or vector. - iam is 0 where |theta| > 90. + iam is 0 where |aoi| > 90. a_r : numeric The angular losses coefficient described in equation 3 of [1]. This is an empirical dimensionless parameter. Values of a_r are generally on the order of 0.08 to 0.25 for flat-plate PV modules. - a_r must be a positive numeric scalar or vector (same length as theta). + a_r must be a positive numeric scalar or vector (same length as aoi). Returns ------- @@ -1089,16 +1089,16 @@ def iam_martin_ruiz(theta, a_r=0.16): Notes ----- - iam_martin_ruiz calculates the incidence angle modifier (iamangular - factor) as described by Martin and Ruiz in [1]. The information - required is the incident angle (theta) and the angular losses + iam_martin_ruiz calculates the incidence angle modifier (iam) + as described by Martin and Ruiz in [1]. The information + required is the incident angle (aoi) and the angular losses coefficient (a_r). Please note that [1] has a corrigendum which makes the document much simpler to understand. The incident angle modifier is defined as - [1-exp(-cos(theta/ar))] / [1-exp(-1/ar)], which is + [1-exp(-cos(aoi/ar))] / [1-exp(-1/ar)], which is presented as AL(alpha) = 1 - IAM in equation 4 of [1]. Thus IAM is - equal to 1 at theta = 0, and equal to 0 at theta = 90. + equal to 1 at aoi = 0, and equal to 0 at aoi = 90. References ---------- @@ -1119,36 +1119,41 @@ def iam_martin_ruiz(theta, a_r=0.16): ''' # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019 - theta = np.asanyarray(theta) + aoi_input = aoi + + aoi = np.asanyarray(aoi) a_r = np.asanyarray(a_r) if np.any(np.less_equal(a_r, 0)): raise RuntimeError("The parameter 'a_r' cannot be zero or negative.") - iam = (1 - np.exp(-cosd(theta) / a_r)) / (1 - np.exp(-1 / a_r)) - iam = np.where(np.abs(theta) >= 90.0, 0.0, iam) + iam = (1 - np.exp(-cosd(aoi) / a_r)) / (1 - np.exp(-1 / a_r)) + iam = np.where(np.abs(aoi) >= 90.0, 0.0, iam) + + if isinstance(aoi_input, pd.Series): + iam = pd.Series(iam, index=aoi_input.index) return iam -def iam_interp(theta, theta_ref, iam_ref, method='linear', normalize=True): +def iam_interp(aoi, theta_ref, iam_ref, method='linear', normalize=True): ''' Determine the incidence angle modifier (iam) by interpolating a set of reference values, which are usually measured values. Parameters ---------- - theta : numeric, degrees + aoi : numeric, degrees The angle of incidence between the module normal vector and the sun-beam vector in degrees. theta_ref : numeric, degrees Vector of angles at which the iam is known. - iam_ref : + iam_ref : numeric, unitless iam values for each angle in theta_ref. - method : + method : str, default 'linear' Specifies the interpolation method. Useful options are: 'linear', 'quadratic','cubic'. See scipy.interpolate.interp1d for more options. @@ -1170,7 +1175,7 @@ def iam_interp(theta, theta_ref, iam_ref, method='linear', normalize=True): iam beyond the range of theta_ref are extrapolated, but constrained to be non-negative. - The sign of theta is ignored; only the magnitude is used. + The sign of aoi is ignored; only the magnitude is used. See Also -------- @@ -1195,14 +1200,19 @@ def iam_interp(theta, theta_ref, iam_ref, method='linear', normalize=True): interpolator = interp1d(theta_ref, iam_ref, kind=method, fill_value='extrapolate') - theta = np.asanyarray(theta) - theta = np.abs(theta) - iam = interpolator(theta) + aoi_input = aoi + + aoi = np.asanyarray(aoi) + aoi = np.abs(aoi) + iam = interpolator(aoi) iam = np.clip(iam, 0, None) if normalize: iam /= interpolator(0) + if isinstance(aoi_input, pd.Series): + iam = pd.Series(iam, index=aoi_input.index) + return iam @@ -2136,14 +2146,6 @@ def pvsyst_celltemp(poa_global, temp_air, wind_speed=1.0, eta_m=0.1, return temp_cell -def celltemp_faiman(poa_global, temp_air, wind_speed, u0, u1): - ''' - Calculate cell temperature using an emperical heat loss factor model - in the form proposed by Faiman. - ''' - raise NotImplementedError - - def sapm_spectral_loss(airmass_absolute, module): """ Calculates the SAPM spectral loss coefficient, F1. diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index c36e7591c0..27aaf4ed5a 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -158,7 +158,7 @@ def test_iam_martin_ruiz(): iam = pvsystem.iam_martin_ruiz(aoi) assert_allclose(iam, expected) # will fail of parameter names change - iam = pvsystem.iam_martin_ruiz(theta=aoi, a_r=a_r) + iam = pvsystem.iam_martin_ruiz(aoi=aoi, a_r=a_r) assert_allclose(iam, expected) a_r = 0.18 From 396ab4cfe26c1b4585a254f935b09b21ba9c5dcc Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 24 Jul 2019 10:13:32 +0200 Subject: [PATCH 10/13] Final(?) documentation touch-ups. --- docs/sphinx/source/whatsnew/v0.7.0.rst | 1 + pvlib/pvsystem.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index 723c1a1717..5531712dad 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -19,6 +19,7 @@ Bug fixes ~~~~~~~~~ * Fix handling of keyword arguments in `forecasts.get_processed_data`. (:issue:`745`) +* Fix output as Series feature in :py:func:`pvlib.pvsystemashraeiam`. Testing ~~~~~~~ diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f23c6a64aa..fec0066499 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1098,7 +1098,9 @@ def iam_martin_ruiz(aoi, a_r=0.16): The incident angle modifier is defined as [1-exp(-cos(aoi/ar))] / [1-exp(-1/ar)], which is presented as AL(alpha) = 1 - IAM in equation 4 of [1]. Thus IAM is - equal to 1 at aoi = 0, and equal to 0 at aoi = 90. + equal to 1 at aoi = 0, and equal to 0 at aoi = 90. This equation is only + valid for -90 <= aoi <= 90, therefore iam must be constrained to 0.0 + beyond this range. References ---------- From f5d653f5c21e589ebff74baa11c92c31ab7040ad Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 25 Jul 2019 16:56:14 +0200 Subject: [PATCH 11/13] Fix tests and nan handling. --- pvlib/pvsystem.py | 5 +++-- pvlib/test/test_pvsystem.py | 27 ++++++++++++--------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index fec0066499..9000b9f611 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1129,8 +1129,9 @@ def iam_martin_ruiz(aoi, a_r=0.16): if np.any(np.less_equal(a_r, 0)): raise RuntimeError("The parameter 'a_r' cannot be zero or negative.") - iam = (1 - np.exp(-cosd(aoi) / a_r)) / (1 - np.exp(-1 / a_r)) - iam = np.where(np.abs(aoi) >= 90.0, 0.0, iam) + with np.errstate(invalid='ignore'): + iam = (1 - np.exp(-cosd(aoi) / a_r)) / (1 - np.exp(-1 / a_r)) + iam = np.where(np.abs(aoi) >= 90.0, 0.0, iam) if isinstance(aoi_input, pd.Series): iam = pd.Series(iam, index=aoi_input.index) diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 27aaf4ed5a..8373ca4e85 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -165,24 +165,24 @@ def test_iam_martin_ruiz(): aoi = [-100, -60, 0, 60, 100, np.nan, np.inf] expected = [0.0, 0.9414631, 1.0, 0.9414631, 0.0, np.nan, 0.0] - with np.errstate(invalid='ignore'): - # check out of range of inputs as list - iam = pvsystem.iam_martin_ruiz(aoi, a_r) - assert_allclose(iam, expected, equal_nan=True) + # check out of range of inputs as list + iam = pvsystem.iam_martin_ruiz(aoi, a_r) + assert_allclose(iam, expected, equal_nan=True) - # check out of range of inputs as array - iam = pvsystem.iam_martin_ruiz(np.array(aoi), a_r) - assert_allclose(iam, expected, equal_nan=True) + # check out of range of inputs as array + iam = pvsystem.iam_martin_ruiz(np.array(aoi), a_r) + assert_allclose(iam, expected, equal_nan=True) - # check out of range of inputs as Series - iam = pvsystem.iam_martin_ruiz(pd.Series(aoi), a_r) - assert_allclose(iam, expected, equal_nan=True) + # check out of range of inputs as Series + iam = pvsystem.iam_martin_ruiz(pd.Series(aoi), a_r) + assert_allclose(iam, expected, equal_nan=True) # check exception clause with pytest.raises(RuntimeError): pvsystem.iam_martin_ruiz(0.0, a_r=0.0) +@requires_scipy def test_iam_interp(): aoi_meas = [0.0, 45.0, 65.0, 75.0] @@ -194,9 +194,6 @@ def test_iam_interp(): iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) assert_allclose(iam, expected) - iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) - assert_allclose(iam, expected) - # simple non-default method aoi = 55.0 expected = 0.8878062 @@ -209,8 +206,8 @@ def test_iam_interp(): iam = pvsystem.iam_interp(aoi, aoi_meas, iam_meas) assert_allclose(iam, expected) - # check normalization - iam_mult = np.multiply(0.9, iam_meas) + # check normalization and Series + iam_mult = pd.Series(np.multiply(0.9, iam_meas)) iam = pvsystem.iam_interp(aoi, aoi_meas, iam_mult, normalize=True) assert_allclose(iam, expected) From 1de4228d1513944141c06128056ad65520249107 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Fri, 26 Jul 2019 09:59:42 +0200 Subject: [PATCH 12/13] Final(?) test fixes. --- pvlib/test/test_pvsystem.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 8373ca4e85..7de8930f73 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -174,8 +174,10 @@ def test_iam_martin_ruiz(): assert_allclose(iam, expected, equal_nan=True) # check out of range of inputs as Series - iam = pvsystem.iam_martin_ruiz(pd.Series(aoi), a_r) - assert_allclose(iam, expected, equal_nan=True) + aoi = pd.Series(aoi) + expected = pd.Series(expected) + iam = pvsystem.iam_martin_ruiz(aoi, a_r) + assert_series_equal(iam, expected) # check exception clause with pytest.raises(RuntimeError): @@ -207,9 +209,11 @@ def test_iam_interp(): assert_allclose(iam, expected) # check normalization and Series - iam_mult = pd.Series(np.multiply(0.9, iam_meas)) + aoi = pd.Series(aoi) + expected = pd.Series(expected) + iam_mult = np.multiply(0.9, iam_meas) iam = pvsystem.iam_interp(aoi, aoi_meas, iam_mult, normalize=True) - assert_allclose(iam, expected) + assert_series_equal(iam, expected) # check beyond reference values aoi = [-45, 0, 45, 85, 90, 95, 100, 105, 110] From b893e03b7a7215a595028ba12b0dfba00579601c Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 26 Jul 2019 11:05:30 -0600 Subject: [PATCH 13/13] Update v0.7.0.rst --- docs/sphinx/source/whatsnew/v0.7.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index fe2adf68ef..505bd00825 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -19,7 +19,7 @@ Bug fixes ~~~~~~~~~ * Fix handling of keyword arguments in `forecasts.get_processed_data`. (:issue:`745`) -* Fix output as Series feature in :py:func:`pvlib.pvsystemashraeiam`. +* Fix output as Series feature in :py:func:`pvlib.pvsystem.ashraeiam`. Testing ~~~~~~~