diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 4855187ea4..5ad1a06c1f 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1563,9 +1563,13 @@ def _delta_kt_prime_dirint(kt_prime, use_delta_kt_prime, times): """ if use_delta_kt_prime: # Perez eqn 2 - delta_kt_prime = 0.5*((kt_prime - kt_prime.shift(1)).abs().add( - (kt_prime - kt_prime.shift(-1)).abs(), - fill_value=0)) + kt_next = kt_prime.shift(-1) + kt_previous = kt_prime.shift(1) + kt_next.iloc[-1] = kt_previous.iloc[-1] + kt_previous.iloc[0] = kt_next.iloc[0] + delta_kt_prime = 0.5 * ((kt_prime - kt_next).abs().add( + (kt_prime - kt_previous).abs(), + fill_value=0)) else: # do not change unless also modifying _dirint_bins delta_kt_prime = pd.Series(-1, index=times) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 6215c1a73d..65ef3007f5 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -600,7 +600,7 @@ def _infer_cell_type(self): def singlediode(self, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, - ivcurve_pnts=None): + d2mutau=0, NsVbi=np.Inf, ivcurve_pnts=None): """Wrapper around the :py:func:`singlediode` function. Parameters @@ -613,7 +613,7 @@ def singlediode(self, photocurrent, saturation_current, """ return singlediode(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, - ivcurve_pnts=ivcurve_pnts) + d2mutau, NsVbi, ivcurve_pnts=ivcurve_pnts) def i_from_v(self, resistance_shunt, resistance_series, nNsVth, voltage, saturation_current, photocurrent): @@ -2113,8 +2113,8 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi, def singlediode(photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, ivcurve_pnts=None, - method='lambertw'): + resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf, + ivcurve_pnts=None, method='lambertw'): """ Solve the single-diode model to obtain a photovoltaic IV curve. @@ -2166,6 +2166,16 @@ def singlediode(photocurrent, saturation_current, resistance_series, q is the charge of an electron (coulombs). 0 < nNsVth + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` + ivcurve_pnts : None or int, default None Number of points in the desired IV curve. If None or 0, no IV curves will be produced. @@ -2258,7 +2268,7 @@ def singlediode(photocurrent, saturation_current, resistance_series, # methods. Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth) # collect args + resistance_shunt, nNsVth, d2mutau, NsVbi) # collect args v_oc = _singlediode.bishop88_v_from_i( 0.0, *args, method=method.lower() ) @@ -2304,7 +2314,8 @@ def singlediode(photocurrent, saturation_current, resistance_series, def max_power_point(photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, method='brentq'): + resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf, + method='brentq'): """ Given the single diode equation coefficients, calculates the maximum power point (MPP). @@ -2322,6 +2333,14 @@ def max_power_point(photocurrent, saturation_current, resistance_series, nNsVth : numeric product of thermal voltage ``Vth`` [V], diode ideality factor ``n``, and number of serices cells ``Ns`` + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` method : str either ``'newton'`` or ``'brentq'`` @@ -2339,7 +2358,7 @@ def max_power_point(photocurrent, saturation_current, resistance_series, """ i_mp, v_mp, p_mp = _singlediode.bishop88_mpp( photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, method=method.lower() + resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower() ) if isinstance(photocurrent, pd.Series): ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp} @@ -2353,7 +2372,8 @@ def max_power_point(photocurrent, saturation_current, resistance_series, def v_from_i(resistance_shunt, resistance_series, nNsVth, current, - saturation_current, photocurrent, method='lambertw'): + saturation_current, photocurrent, d2mutau=0, NsVbi=np.Inf, + method='lambertw'): ''' Device voltage at the given device current for the single diode model. @@ -2402,6 +2422,16 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current, IV curve conditions. Often abbreviated ``I_L``. 0 <= photocurrent + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` + method : str Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*: ``'brentq'`` is limited to 1st quadrant only. @@ -2426,7 +2456,7 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current, # methods. Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage args = (current, photocurrent, saturation_current, - resistance_series, resistance_shunt, nNsVth) + resistance_series, resistance_shunt, nNsVth, d2mutau, NsVbi) V = _singlediode.bishop88_v_from_i(*args, method=method.lower()) # find the right size and shape for returns size, shape = _singlediode._get_size_and_shape(args) @@ -2441,7 +2471,8 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current, def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent, method='lambertw'): + saturation_current, photocurrent, d2mutau=0, + NsVbi=np.Inf, method='lambertw'): ''' Device current at the given device voltage for the single diode model. @@ -2490,6 +2521,16 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, IV curve conditions. Often abbreviated ``I_L``. 0 <= photocurrent + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` + method : str Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*: ``'brentq'`` is limited to 1st quadrant only. @@ -2514,7 +2555,7 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, # methods. Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage args = (voltage, photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth) + resistance_shunt, nNsVth, d2mutau, NsVbi) I = _singlediode.bishop88_i_from_v(*args, method=method.lower()) # find the right size and shape for returns size, shape = _singlediode._get_size_and_shape(args) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 361e53882f..ef88f757ab 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -139,6 +139,11 @@ def bishop88(diode_voltage, photocurrent, saturation_current, available technology", André Mermoud and Thibault Lejeune, 25th EUPVSEC, 2010 :doi:`10.4229/25thEUPVSEC2010-4BV.1.114` + + [4] K. J. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance + and Temperature Dependence of Photovoltaic Modules in PVsyst, J. of + Photovoltaics 5(1), Jan. 2015. + :doi:`10.1109/JPHOTOV.2014.2364133` """ # calculate recombination loss current where d2mutau > 0 is_recomb = d2mutau > 0 # True where there is thin-film recombination loss @@ -172,8 +177,8 @@ def bishop88(diode_voltage, photocurrent, saturation_current, def bishop88_i_from_v(voltage, photocurrent, saturation_current, - resistance_series, resistance_shunt, nNsVth, - method='newton'): + resistance_series, resistance_shunt, nNsVth, d2mutau=0, + NsVbi=np.Inf, method='newton'): """ Find current given any voltage. @@ -192,6 +197,14 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current, nNsVth : numeric product of diode ideality factor (n), number of series cells (Ns), and thermal voltage (Vth = k_b * T / q_e) in volts [V] + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` method : str one of two optional search methods: either ``'brentq'``, a reliable and bounded method or ``'newton'`` which is the default. @@ -203,7 +216,7 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current, """ # collect args args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth) + resistance_shunt, nNsVth, d2mutau, NsVbi) def fv(x, v, *a): # calculate voltage residual given diode voltage "x" @@ -234,8 +247,8 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma): def bishop88_v_from_i(current, photocurrent, saturation_current, - resistance_series, resistance_shunt, nNsVth, - method='newton'): + resistance_series, resistance_shunt, nNsVth, d2mutau=0, + NsVbi=np.Inf, method='newton'): """ Find voltage given any current. @@ -251,6 +264,14 @@ def bishop88_v_from_i(current, photocurrent, saturation_current, series resistance (Rs) in ohms resistance_shunt : numeric shunt resistance (Rsh) in ohms + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` nNsVth : numeric product of diode ideality factor (n), number of series cells (Ns), and thermal voltage (Vth = k_b * T / q_e) in volts [V] @@ -265,7 +286,7 @@ def bishop88_v_from_i(current, photocurrent, saturation_current, """ # collect args args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth) + resistance_shunt, nNsVth, d2mutau, NsVbi) # first bound the search using voc voc_est = estimate_voc(photocurrent, saturation_current, nNsVth) @@ -295,7 +316,8 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma): def bishop88_mpp(photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth, method='newton'): + resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf, + method='newton'): """ Find max power point. @@ -312,6 +334,14 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series, nNsVth : numeric product of diode ideality factor (n), number of series cells (Ns), and thermal voltage (Vth = k_b * T / q_e) in volts [V] + d2mutau : numeric + PVSyst thin-film recombination parameter that is the ratio of thickness + of the intrinsic layer squared :math:`d^2` and the diffusion length of + charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V] + NsVbi : numeric + PVSyst thin-film recombination parameter that is the product of the PV + module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of + the intrinsic layer, in volts [V], defaults to ``np.inf`` method : str one of two optional search methods: either ``'brentq'``, a reliable and bounded method or ``'newton'`` which is the default. @@ -324,7 +354,7 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series, """ # collect args args = (photocurrent, saturation_current, resistance_series, - resistance_shunt, nNsVth) + resistance_shunt, nNsVth, d2mutau, NsVbi) # first bound the search using voc voc_est = estimate_voc(photocurrent, saturation_current, nNsVth) diff --git a/pvlib/test/test_irradiance.py b/pvlib/test/test_irradiance.py index cd1c5b168d..30bb2c95e6 100644 --- a/pvlib/test/test_irradiance.py +++ b/pvlib/test/test_irradiance.py @@ -466,13 +466,13 @@ def test_dirint_value(): pressure = 93193. dirint_data = irradiance.dirint(ghi, zenith, times, pressure=pressure) assert_almost_equal(dirint_data.values, - np.array([ 888. , 683.7]), 1) + np.array([868.8, 699.7]), 1) def test_dirint_nans(): times = pd.DatetimeIndex(start='2014-06-24T12-0700', periods=5, freq='6H') ghi = pd.Series([np.nan, 1038.62, 1038.62, 1038.62, 1038.62], index=times) - zenith = pd.Series([10.567, np.nan, 10.567, 10.567, 10.567,], index=times) + zenith = pd.Series([10.567, np.nan, 10.567, 10.567, 10.567], index=times) pressure = pd.Series([93193., 93193., np.nan, 93193., 93193.], index=times) temp_dew = pd.Series([10, 10, 10, np.nan, 10], index=times) dirint_data = irradiance.dirint(ghi, zenith, times, pressure=pressure, @@ -489,7 +489,7 @@ def test_dirint_tdew(): dirint_data = irradiance.dirint(ghi, zenith, times, pressure=pressure, temp_dew=10) assert_almost_equal(dirint_data.values, - np.array([892.9, 636.5]), 1) + np.array([882.1, 672.6]), 1) def test_dirint_no_delta_kt(): @@ -559,7 +559,7 @@ def test_gti_dirint(): expected = pd.DataFrame(array( [[ 21.05796198, 0. , 21.05796198], [ 288.22574368, 60.59964218, 245.37532576], - [ 930.85454521, 695.8504884 , 276.96897609]]), + [ 931.04078010, 695.94965324, 277.06172442]]), columns=expected_col_order, index=times) assert_frame_equal(output, expected) @@ -583,7 +583,7 @@ def test_gti_dirint(): expected = pd.DataFrame(array( [[ 21.05796198, 0. , 21.05796198], [ 289.81109139, 60.52460392, 247.01373353], - [ 932.22047435, 647.68716072, 323.59362885]]), + [ 932.46756378, 647.05001357, 323.49974813]]), columns=expected_col_order, index=times) assert_frame_equal(output, expected) @@ -595,9 +595,9 @@ def test_gti_dirint(): albedo=albedo) expected = pd.DataFrame(array( - [[ 21.3592591 , 0. , 21.3592591 ], - [ 292.5162373 , 64.42628826, 246.95997198], - [ 941.47847463, 727.07261187, 258.25370648]]), + [[ 21.3592591, 0. , 21.3592591 ], + [ 292.5162373, 64.42628826, 246.95997198], + [ 941.6753031, 727.16311901, 258.36548605]]), columns=expected_col_order, index=times) assert_frame_equal(output, expected) @@ -611,7 +611,7 @@ def test_gti_dirint(): expected = pd.DataFrame(array( [[ 21.05796198, 0. , 21.05796198], [ 292.40468994, 36.79559287, 266.3862767 ], - [ 930.72198876, 712.36063132, 261.32196017]]), + [ 931.79627208, 689.81549269, 283.5817439]]), columns=expected_col_order, index=times) assert_frame_equal(output, expected)