From f41432de89c395eefc246839bcafb68673c402af Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 27 Apr 2023 08:03:36 -0600 Subject: [PATCH 1/8] reorder i_from_v, v_from_i args and refactor tests --- pvlib/pvsystem.py | 84 ++++++++++++++++----------------- pvlib/tests/test_pvsystem.py | 61 ++++++++++++------------ pvlib/tests/test_singlediode.py | 45 +++++++----------- 3 files changed, 90 insertions(+), 100 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 48371ca961..f0835953f0 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -944,14 +944,14 @@ def singlediode(self, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, ivcurve_pnts=ivcurve_pnts) - def i_from_v(self, resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent): + def i_from_v(self, voltage, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth): """Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function. See :py:func:`pvsystem.i_from_v` for details """ - return i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent) + return i_from_v(voltage, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth) def get_ac(self, model, p_dc, v_dc=None): r"""Calculates AC power from p_dc using the inverter model indicated @@ -2961,8 +2961,8 @@ def max_power_point(photocurrent, saturation_current, resistance_series, return out -def v_from_i(resistance_shunt, resistance_series, nNsVth, current, - saturation_current, photocurrent, method='lambertw'): +def v_from_i(current, photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth, method='lambertw'): ''' Device voltage at the given device current for the single diode model. @@ -2978,16 +2978,29 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current, Parameters ---------- - resistance_shunt : numeric - Shunt resistance in ohms under desired IV curve conditions. - Often abbreviated ``Rsh``. - 0 < resistance_shunt <= numpy.inf + current : numeric + The current in amperes under desired IV curve conditions. + + photocurrent : numeric + Light-generated current (photocurrent) in amperes under desired + IV curve conditions. Often abbreviated ``I_L``. + 0 <= photocurrent + + saturation_current : numeric + Diode saturation current in amperes under desired IV curve + conditions. Often abbreviated ``I_0``. + 0 < saturation_current resistance_series : numeric Series resistance in ohms under desired IV curve conditions. Often abbreviated ``Rs``. 0 <= resistance_series < numpy.inf + resistance_shunt : numeric + Shunt resistance in ohms under desired IV curve conditions. + Often abbreviated ``Rsh``. + 0 < resistance_shunt <= numpy.inf + nNsVth : numeric The product of three components. 1) The usual diode ideal factor (n), 2) the number of cells in series (Ns), and 3) the cell @@ -2998,19 +3011,6 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current, q is the charge of an electron (coulombs). 0 < nNsVth - current : numeric - The current in amperes under desired IV curve conditions. - - saturation_current : numeric - Diode saturation current in amperes under desired IV curve - conditions. Often abbreviated ``I_0``. - 0 < saturation_current - - photocurrent : numeric - Light-generated current (photocurrent) in amperes under desired - IV curve conditions. Often abbreviated ``I_L``. - 0 <= photocurrent - method : str Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*: ``'brentq'`` is limited to 1st quadrant only. @@ -3049,8 +3049,8 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current, return V -def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent, method='lambertw'): +def i_from_v(voltage, photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth, method='lambertw'): ''' Device current at the given device voltage for the single diode model. @@ -3066,16 +3066,29 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, Parameters ---------- - resistance_shunt : numeric - Shunt resistance in ohms under desired IV curve conditions. - Often abbreviated ``Rsh``. - 0 < resistance_shunt <= numpy.inf + voltage : numeric + The voltage in Volts under desired IV curve conditions. + + photocurrent : numeric + Light-generated current (photocurrent) in amperes under desired + IV curve conditions. Often abbreviated ``I_L``. + 0 <= photocurrent + + saturation_current : numeric + Diode saturation current in amperes under desired IV curve + conditions. Often abbreviated ``I_0``. + 0 < saturation_current resistance_series : numeric Series resistance in ohms under desired IV curve conditions. Often abbreviated ``Rs``. 0 <= resistance_series < numpy.inf + resistance_shunt : numeric + Shunt resistance in ohms under desired IV curve conditions. + Often abbreviated ``Rsh``. + 0 < resistance_shunt <= numpy.inf + nNsVth : numeric The product of three components. 1) The usual diode ideal factor (n), 2) the number of cells in series (Ns), and 3) the cell @@ -3086,19 +3099,6 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, q is the charge of an electron (coulombs). 0 < nNsVth - voltage : numeric - The voltage in Volts under desired IV curve conditions. - - saturation_current : numeric - Diode saturation current in amperes under desired IV curve - conditions. Often abbreviated ``I_0``. - 0 < saturation_current - - photocurrent : numeric - Light-generated current (photocurrent) in amperes under desired - IV curve conditions. Often abbreviated ``I_L``. - 0 <= photocurrent - method : str Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*: ``'brentq'`` is limited to 1st quadrant only. diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 7fa013d0dc..d945b3b955 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1079,11 +1079,12 @@ def test_v_from_i(fixture_v_from_i, method, atol): IL = fixture_v_from_i['IL'] V_expected = fixture_v_from_i['V_expected'] - V = pvsystem.v_from_i(Rsh, Rs, nNsVth, I, I0, IL, method=method) - assert(isinstance(V, type(V_expected))) + V = pvsystem.v_from_i(I, IL, I0, Rs, Rsh, nNsVth, method=method) + + assert isinstance(V, type(V_expected)) if isinstance(V, type(np.ndarray)): - assert(isinstance(V.dtype, type(V_expected.dtype))) - assert(V.shape == V_expected.shape) + assert isinstance(V.dtype, type(V_expected.dtype)) + assert V.shape == V_expected.shape assert_allclose(V, V_expected, atol=atol) @@ -1100,14 +1101,16 @@ def test_i_from_v_from_i(fixture_v_from_i): # Convergence criteria atol = 1.e-11 - I_expected = pvsystem.i_from_v(Rsh, Rs, nNsVth, V, I0, IL, + I_expected = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth, method='lambertw') assert_allclose(I, I_expected, atol=atol) - I = pvsystem.i_from_v(Rsh, Rs, nNsVth, V, I0, IL) - assert(isinstance(I, type(I_expected))) + + I = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth) + + assert isinstance(I, type(I_expected)) if isinstance(I, type(np.ndarray)): - assert(isinstance(I.dtype, type(I_expected.dtype))) - assert(I.shape == I_expected.shape) + assert isinstance(I.dtype, type(I_expected.dtype)) + assert I.shape == I_expected.shape assert_allclose(I, I_expected, atol=atol) @@ -1197,41 +1200,42 @@ def test_i_from_v(fixture_i_from_v, method, atol): IL = fixture_i_from_v['IL'] I_expected = fixture_i_from_v['I_expected'] - I = pvsystem.i_from_v(Rsh, Rs, nNsVth, V, I0, IL, method=method) - assert(isinstance(I, type(I_expected))) + I = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth, method=method) + + assert isinstance(I, type(I_expected)) if isinstance(I, type(np.ndarray)): - assert(isinstance(I.dtype, type(I_expected.dtype))) - assert(I.shape == I_expected.shape) + assert isinstance(I.dtype, type(I_expected.dtype)) + assert I.shape == I_expected.shape assert_allclose(I, I_expected, atol=atol) def test_PVSystem_i_from_v(mocker): system = pvsystem.PVSystem() m = mocker.patch('pvlib.pvsystem.i_from_v', autospec=True) - args = (20, 0.1, 0.5, 7.5049875193450521, 6e-7, 7) + args = (7.5049875193450521, 7, 6e-7, 0.1, 20, 0.5) system.i_from_v(*args) m.assert_called_once_with(*args) def test_i_from_v_size(): with pytest.raises(ValueError): - pvsystem.i_from_v(20, [0.1] * 2, 0.5, [7.5] * 3, 6.0e-7, 7.0) + pvsystem.i_from_v([7.5] * 3, 7., 6e-7, [0.1] * 2, 20, 0.5) with pytest.raises(ValueError): - pvsystem.i_from_v(20, [0.1] * 2, 0.5, [7.5] * 3, 6.0e-7, 7.0, + pvsystem.i_from_v([7.5] * 3, 7., 6e-7, [0.1] * 2, 20, 0.5, method='brentq') with pytest.raises(ValueError): - pvsystem.i_from_v(20, 0.1, 0.5, [7.5] * 3, 6.0e-7, np.array([7., 7.]), + pvsystem.i_from_v([7.5] * 3, np.array([7., 7.]), 6e-7, 0.1, 20, 0.5, method='newton') def test_v_from_i_size(): with pytest.raises(ValueError): - pvsystem.v_from_i(20, [0.1] * 2, 0.5, [3.0] * 3, 6.0e-7, 7.0) + pvsystem.v_from_i([3.] * 3, 7., 6e-7, [0.1] * 2, 20, 0.5) with pytest.raises(ValueError): - pvsystem.v_from_i(20, [0.1] * 2, 0.5, [3.0] * 3, 6.0e-7, 7.0, + pvsystem.v_from_i([3.] * 3, 7., 6e-7, [0.1] * 2, 20, 0.5, method='brentq') with pytest.raises(ValueError): - pvsystem.v_from_i(20, [0.1], 0.5, [3.0] * 3, 6.0e-7, np.array([7., 7.]), + pvsystem.v_from_i([3.] * 3, np.array([7., 7.]), 6e-7, [0.1], 20, 0.5, method='newton') @@ -1328,8 +1332,8 @@ def test_singlediode_array(): sd = pvsystem.singlediode(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth) - expected = pvsystem.i_from_v(resistance_shunt, resistance_series, nNsVth, - sd['v_mp'], saturation_current, photocurrent, + expected = pvsystem.i_from_v(sd['v_mp'], photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth, method='lambertw') assert_allclose(sd['i_mp'], expected, atol=1e-8) @@ -1404,20 +1408,19 @@ def test_singlediode_series_ivcurve(cec_module_params): [3.0107985972, 2.8841320056, 0.], [6.0072629615, 5.7462022810, 0.]]))]) - for k, v in out.items(): assert_allclose(v, expected[k], atol=1e-2) out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3) - expected['i_mp'] = pvsystem.i_from_v(Rsh, Rs, nNsVth, out['v_mp'], I0, IL, + expected['i_mp'] = pvsystem.i_from_v(out['v_mp'], IL, I0, Rs, Rsh, nNsVth, method='lambertw') - expected['v_mp'] = pvsystem.v_from_i(Rsh, Rs, nNsVth, out['i_mp'], I0, IL, + expected['v_mp'] = pvsystem.v_from_i(out['i_mp'], IL, I0, Rs, Rsh, nNsVth, method='lambertw') - expected['i'] = pvsystem.i_from_v(Rsh, Rs, nNsVth, out['v'].T, I0, IL, - method='lambertw').T - expected['v'] = pvsystem.v_from_i(Rsh, Rs, nNsVth, out['i'].T, I0, IL, - method='lambertw').T + expected['i'] = pvsystem.i_from_v(out['v'].T, IL, I0, Rs, Rsh, nNsVth, + method='lambertw').T + expected['v'] = pvsystem.v_from_i(out['i'].T, IL, I0, Rs, Rsh, nNsVth, + method='lambertw').T for k, v in out.items(): assert_allclose(v, expected[k], atol=1e-6) diff --git a/pvlib/tests/test_singlediode.py b/pvlib/tests/test_singlediode.py index ee20e4885e..8e4a92001e 100644 --- a/pvlib/tests/test_singlediode.py +++ b/pvlib/tests/test_singlediode.py @@ -22,22 +22,16 @@ def test_method_spr_e20_327(method, cec_module_spr_e20_327): I_L_ref=spr_e20_327['I_L_ref'], I_o_ref=spr_e20_327['I_o_ref'], R_sh_ref=spr_e20_327['R_sh_ref'], R_s=spr_e20_327['R_s'], EgRef=1.121, dEgdT=-0.0002677) - il, io, rs, rsh, nnsvt = x pvs = pvsystem.singlediode(*x, method='lambertw') out = pvsystem.singlediode(*x, method=method) - isc, voc, imp, vmp, pmp, ix, ixx = out.values() - assert np.isclose(pvs['i_sc'], isc) - assert np.isclose(pvs['v_oc'], voc) - # the singlediode method doesn't actually get the MPP correct - pvs_imp = pvsystem.i_from_v(rsh, rs, nnsvt, vmp, io, il, method='lambertw') - pvs_vmp = pvsystem.v_from_i(rsh, rs, nnsvt, imp, io, il, method='lambertw') - assert np.isclose(pvs_imp, imp) - assert np.isclose(pvs_vmp, vmp) - assert np.isclose(pvs['p_mp'], pmp) - assert np.isclose(pvs['i_x'], ix) - pvs_ixx = pvsystem.i_from_v(rsh, rs, nnsvt, (voc + vmp)/2, io, il, - method='lambertw') - assert np.isclose(pvs_ixx, ixx) + + assert np.isclose(pvs['i_sc'], out['i_sc']) + assert np.isclose(pvs['v_oc'], out['v_oc']) + assert np.isclose(pvs['i_mp'], out['i_mp']) + assert np.isclose(pvs['v_mp'], out['v_mp']) + assert np.isclose(pvs['p_mp'], out['p_mp']) + assert np.isclose(pvs['i_x'], out['i_x']) + assert np.isclose(pvs['i_xx'], out['i_xx']) @pytest.mark.parametrize('method', ['brentq', 'newton']) @@ -50,23 +44,16 @@ def test_newton_fs_495(method, cec_module_fs_495): I_L_ref=fs_495['I_L_ref'], I_o_ref=fs_495['I_o_ref'], R_sh_ref=fs_495['R_sh_ref'], R_s=fs_495['R_s'], EgRef=1.475, dEgdT=-0.0003) - il, io, rs, rsh, nnsvt = x - x += (101, ) pvs = pvsystem.singlediode(*x, method='lambertw') out = pvsystem.singlediode(*x, method=method) - isc, voc, imp, vmp, pmp, ix, ixx, i, v = out.values() - assert np.isclose(pvs['i_sc'], isc) - assert np.isclose(pvs['v_oc'], voc) - # the singlediode method doesn't actually get the MPP correct - pvs_imp = pvsystem.i_from_v(rsh, rs, nnsvt, vmp, io, il, method='lambertw') - pvs_vmp = pvsystem.v_from_i(rsh, rs, nnsvt, imp, io, il, method='lambertw') - assert np.isclose(pvs_imp, imp) - assert np.isclose(pvs_vmp, vmp) - assert np.isclose(pvs['p_mp'], pmp) - assert np.isclose(pvs['i_x'], ix) - pvs_ixx = pvsystem.i_from_v(rsh, rs, nnsvt, (voc + vmp)/2, io, il, - method='lambertw') - assert np.isclose(pvs_ixx, ixx) + + assert np.isclose(pvs['i_sc'], out['i_sc']) + assert np.isclose(pvs['v_oc'], out['v_oc']) + assert np.isclose(pvs['i_mp'], out['i_mp']) + assert np.isclose(pvs['v_mp'], out['v_mp']) + assert np.isclose(pvs['p_mp'], out['p_mp']) + assert np.isclose(pvs['i_x'], out['i_x']) + assert np.isclose(pvs['i_xx'], out['i_xx']) def get_pvsyst_fs_495(): From 2901dffd67d1ddd83a40f0eefba1bcbbc361bc03 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 27 Apr 2023 08:40:10 -0600 Subject: [PATCH 2/8] reorder args to _update_io v_from_i call --- pvlib/ivtools/sdm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/ivtools/sdm.py b/pvlib/ivtools/sdm.py index 34f5c66c0b..082c0552c6 100644 --- a/pvlib/ivtools/sdm.py +++ b/pvlib/ivtools/sdm.py @@ -942,7 +942,7 @@ def _update_io(voc, iph, io, rs, rsh, nnsvth): while maxerr > eps and k < niter: # Predict Voc - pvoc = v_from_i(rsh, rs, nnsvth, 0., tio, iph) + pvoc = v_from_i(0., iph, tio, rs, rsh, nnsvth) # Difference in Voc dvoc = pvoc - voc From 086b80dabc76f20b60440e28abc178e03827fac2 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Mon, 1 May 2023 07:23:49 -0600 Subject: [PATCH 3/8] fix condition in type checks for i_from_v tests --- pvlib/tests/test_pvsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index d945b3b955..d5c2ac6313 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1082,7 +1082,7 @@ def test_v_from_i(fixture_v_from_i, method, atol): V = pvsystem.v_from_i(I, IL, I0, Rs, Rsh, nNsVth, method=method) assert isinstance(V, type(V_expected)) - if isinstance(V, type(np.ndarray)): + if isinstance(V, np.ndarray): assert isinstance(V.dtype, type(V_expected.dtype)) assert V.shape == V_expected.shape assert_allclose(V, V_expected, atol=atol) @@ -1108,7 +1108,7 @@ def test_i_from_v_from_i(fixture_v_from_i): I = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth) assert isinstance(I, type(I_expected)) - if isinstance(I, type(np.ndarray)): + if isinstance(I, np.ndarray): assert isinstance(I.dtype, type(I_expected.dtype)) assert I.shape == I_expected.shape assert_allclose(I, I_expected, atol=atol) @@ -1203,7 +1203,7 @@ def test_i_from_v(fixture_i_from_v, method, atol): I = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth, method=method) assert isinstance(I, type(I_expected)) - if isinstance(I, type(np.ndarray)): + if isinstance(I, np.ndarray): assert isinstance(I.dtype, type(I_expected.dtype)) assert I.shape == I_expected.shape assert_allclose(I, I_expected, atol=atol) From ea866ef721b681ee2b80170d523ba4e7cf4378f7 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Sun, 7 May 2023 10:59:38 -0600 Subject: [PATCH 4/8] reorder arguments for _lambertw methods --- pvlib/pvsystem.py | 8 +++--- pvlib/singlediode.py | 68 +++++++++++++++++++------------------------- pvlib/tools.py | 2 +- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 3597519702..394f83c3dd 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -3028,8 +3028,8 @@ def v_from_i(current, photocurrent, saturation_current, resistance_series, ''' if method.lower() == 'lambertw': return _singlediode._lambertw_v_from_i( - resistance_shunt, resistance_series, nNsVth, current, - saturation_current, photocurrent + current, photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth ) else: # Calculate points on the IV curve using either 'newton' or 'brentq' @@ -3116,8 +3116,8 @@ def i_from_v(voltage, photocurrent, saturation_current, resistance_series, ''' if method.lower() == 'lambertw': return _singlediode._lambertw_i_from_v( - resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent + voltage, photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth ) else: # Calculate points on the IV curve using either 'newton' or 'brentq' diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 9f5fd336ef..d861d201ec 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -495,12 +495,12 @@ def _prepare_newton_inputs(i_or_v_tup, args, v0): return args, v0 -def _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, current, - saturation_current, photocurrent): +def _lambertw_v_from_i(current, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth): # Record if inputs were all scalar output_is_scalar = all(map(np.isscalar, - [resistance_shunt, resistance_series, nNsVth, - current, saturation_current, photocurrent])) + (current, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth))) # This transforms Gsh=1/Rsh, including ideal Rsh=np.inf into Gsh=0., which # is generally more numerically stable @@ -509,9 +509,9 @@ def _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, current, # Ensure that we are working with read-only views of numpy arrays # Turns Series into arrays so that we don't have to worry about # multidimensional broadcasting failing - Gsh, Rs, a, I, I0, IL = \ - np.broadcast_arrays(conductance_shunt, resistance_series, nNsVth, - current, saturation_current, photocurrent) + I, IL, I0, Rs, Gsh, a = \ + np.broadcast_arrays(current, photocurrent, saturation_current, + resistance_series, conductance_shunt, nNsVth) # Intitalize output V (I might not be float64) V = np.full_like(I, np.nan, dtype=np.float64) @@ -572,12 +572,12 @@ def _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, current, return V -def _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent): +def _lambertw_i_from_v(voltage, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth): # Record if inputs were all scalar output_is_scalar = all(map(np.isscalar, - [resistance_shunt, resistance_series, nNsVth, - voltage, saturation_current, photocurrent])) + (voltage, photocurrent, saturation_current, + resistance_series, resistance_shunt, nNsVth))) # This transforms Gsh=1/Rsh, including ideal Rsh=np.inf into Gsh=0., which # is generally more numerically stable @@ -586,9 +586,9 @@ def _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, # Ensure that we are working with read-only views of numpy arrays # Turns Series into arrays so that we don't have to worry about # multidimensional broadcasting failing - Gsh, Rs, a, V, I0, IL = \ - np.broadcast_arrays(conductance_shunt, resistance_series, nNsVth, - voltage, saturation_current, photocurrent) + V, IL, I0, Rs, Gsh, a = \ + np.broadcast_arrays(voltage, photocurrent, saturation_current, + resistance_series, conductance_shunt, nNsVth) # Intitalize output I (V might not be float64) I = np.full_like(V, np.nan, dtype=np.float64) # noqa: E741, N806 @@ -632,36 +632,29 @@ def _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, def _lambertw(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, ivcurve_pnts=None): + # collect args + params = {'photocurrent': photocurrent, + 'saturation_current': saturation_current, + 'resistance_series': resistance_series, + 'resistance_shunt': resistance_shunt, 'nNsVth': nNsVth} + # Compute short circuit current - i_sc = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, 0., - saturation_current, photocurrent) + i_sc = _lambertw_i_from_v(0., **params) # Compute open circuit voltage - v_oc = _lambertw_v_from_i(resistance_shunt, resistance_series, nNsVth, 0., - saturation_current, photocurrent) - - params = {'r_sh': resistance_shunt, - 'r_s': resistance_series, - 'nNsVth': nNsVth, - 'i_0': saturation_current, - 'i_l': photocurrent} + v_oc = _lambertw_v_from_i(0., **params) # Find the voltage, v_mp, where the power is maximized. # Start the golden section search at v_oc * 1.14 - p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, - _pwr_optfcn) + p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, _pwr_optfcn) # Find Imp using Lambert W - i_mp = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, - v_mp, saturation_current, photocurrent) + i_mp = _lambertw_i_from_v(v_mp, **params) # Find Ix and Ixx using Lambert W - i_x = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, - 0.5 * v_oc, saturation_current, photocurrent) + i_x = _lambertw_i_from_v(0.5 * v_oc, **params) - i_xx = _lambertw_i_from_v(resistance_shunt, resistance_series, nNsVth, - 0.5 * (v_oc + v_mp), saturation_current, - photocurrent) + i_xx = _lambertw_i_from_v(0.5 * (v_oc + v_mp), **params) out = (i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx) @@ -670,9 +663,7 @@ def _lambertw(photocurrent, saturation_current, resistance_series, ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] * np.linspace(0, 1, ivcurve_pnts)) - ivcurve_i = _lambertw_i_from_v(resistance_shunt, resistance_series, - nNsVth, ivcurve_v.T, saturation_current, - photocurrent).T + ivcurve_i = _lambertw_i_from_v(ivcurve_v.T, **params).T out += (ivcurve_i, ivcurve_v) @@ -684,7 +675,8 @@ def _pwr_optfcn(df, loc): Function to find power from ``i_from_v``. ''' - I = _lambertw_i_from_v(df['r_sh'], df['r_s'], # noqa: E741, N806 - df['nNsVth'], df[loc], df['i_0'], df['i_l']) + I = _lambertw_i_from_v(df[loc], df['photocurrent'], + df['saturation_current'], df['resistance_series'], + df['resistance_shunt'], df['nNsVth']) return I * df[loc] diff --git a/pvlib/tools.py b/pvlib/tools.py index f1a569bb1f..f6974cf3d3 100644 --- a/pvlib/tools.py +++ b/pvlib/tools.py @@ -346,7 +346,7 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8): phim1 = (np.sqrt(5) - 1) / 2 - df = params + df = params.copy() # shallow copy to avoid modifying caller's dict df['VH'] = upper df['VL'] = lower From d026172408b8a0f122fbe67d4df7dc07a7f3ebde Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Sun, 7 May 2023 12:03:32 -0600 Subject: [PATCH 5/8] adding versionchanged directives --- docs/sphinx/source/whatsnew/v0.10.0.rst | 41 +++++++++++++++++++++++++ pvlib/pvsystem.py | 29 ++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 docs/sphinx/source/whatsnew/v0.10.0.rst diff --git a/docs/sphinx/source/whatsnew/v0.10.0.rst b/docs/sphinx/source/whatsnew/v0.10.0.rst new file mode 100644 index 0000000000..7d5f4b8a50 --- /dev/null +++ b/docs/sphinx/source/whatsnew/v0.10.0.rst @@ -0,0 +1,41 @@ +.. _whatsnew_01000: + + +v0.10.0 +------- + + +Deprecations +~~~~~~~~~~~~ + + +Enhancements +~~~~~~~~~~~~ +* Reorder arguments of ``pvsystem.PVSystem.i_from_v``, ``pvsystem.i_from_v``, + ``pvsystem.v_from_i``, and corresponding ``singlediode._lambertw_*`` + functions to match ``pvsystem.singlediode``. + (:issue:`1718`, :pull:`1719`) + + +Bug fixes +~~~~~~~~~ + + +Testing +~~~~~~~ + + +Documentation +~~~~~~~~~~~~~ + +Benchmarking +~~~~~~~~~~~~~ + + +Requirements +~~~~~~~~~~~~ + + +Contributors +~~~~~~~~~~~~ +* Taos Transue (:ghuser:`reepoi`) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 394f83c3dd..e43db6c8e8 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -948,7 +948,16 @@ def i_from_v(self, voltage, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth): """Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function. - See :py:func:`pvsystem.i_from_v` for details + See :py:func:`pvlib.pvsystem.i_from_v` for details. + + .. versionchanged:: 0.10.0 + The function's arguments have been reordered. In earlier versions, + the argument order is + + .. code-block:: python + + i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, + saturation_current, photocurrent, method='lambertw') """ return i_from_v(voltage, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth) @@ -2977,6 +2986,15 @@ def v_from_i(current, photocurrent, saturation_current, resistance_series, the caller's responsibility to ensure that the arguments are all float64 and within the proper ranges. + .. versionchanged:: 0.10.0 + The function's arguments have been reordered. In earlier versions, + the argument order is + + .. code-block:: python + + v_from_i(resistance_shunt, resistance_series, nNsVth, current, + saturation_current, photocurrent, method='lambertw') + Parameters ---------- current : numeric @@ -3065,6 +3083,15 @@ def i_from_v(voltage, photocurrent, saturation_current, resistance_series, the caller's responsibility to ensure that the arguments are all float64 and within the proper ranges. + .. versionchanged:: 0.10.0 + The function's arguments have been reordered. In earlier versions, + the argument order is + + .. code-block:: python + + i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, + saturation_current, photocurrent, method='lambertw') + Parameters ---------- voltage : numeric From 673bcdadd4c46aa84ed8a3980150db70ff8ad340 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 18 May 2023 08:52:13 -0700 Subject: [PATCH 6/8] breaking changes section in whatsnew; edit v_from_i, i_from_v docstrings --- docs/sphinx/source/whatsnew/v0.10.0.rst | 14 +++++++--- pvlib/pvsystem.py | 34 ++++++------------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.0.rst b/docs/sphinx/source/whatsnew/v0.10.0.rst index 7d5f4b8a50..c6f19e2ff8 100644 --- a/docs/sphinx/source/whatsnew/v0.10.0.rst +++ b/docs/sphinx/source/whatsnew/v0.10.0.rst @@ -5,16 +5,22 @@ v0.10.0 ------- +Breaking Changes +~~~~~~~~~~~~~~~~ +* Reorder arguments of :py:func:`pvlib.pvsystem.PVSystem.i_from_v`, + :py:func:`pvlib.pvsystem.i_from_v`, :py:func:`pvlib.pvsystem.v_from_i`, + :py:func:`pvlib.singlediode._lambertw_i_from_v`, and + :py:func:`pvlib.singlediode._lambertw_v_from_i` to match + :py:func:`pvlib.pvsystem.singlediode`. + (:issue:`1718`, :pull:`1719`) + + Deprecations ~~~~~~~~~~~~ Enhancements ~~~~~~~~~~~~ -* Reorder arguments of ``pvsystem.PVSystem.i_from_v``, ``pvsystem.i_from_v``, - ``pvsystem.v_from_i``, and corresponding ``singlediode._lambertw_*`` - functions to match ``pvsystem.singlediode``. - (:issue:`1718`, :pull:`1719`) Bug fixes diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index e43db6c8e8..d136089b10 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -951,13 +951,7 @@ def i_from_v(self, voltage, photocurrent, saturation_current, See :py:func:`pvlib.pvsystem.i_from_v` for details. .. versionchanged:: 0.10.0 - The function's arguments have been reordered. In earlier versions, - the argument order is - - .. code-block:: python - - i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent, method='lambertw') + The function's arguments have been reordered. """ return i_from_v(voltage, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth) @@ -2987,13 +2981,7 @@ def v_from_i(current, photocurrent, saturation_current, resistance_series, and within the proper ranges. .. versionchanged:: 0.10.0 - The function's arguments have been reordered. In earlier versions, - the argument order is - - .. code-block:: python - - v_from_i(resistance_shunt, resistance_series, nNsVth, current, - saturation_current, photocurrent, method='lambertw') + The function's arguments have been reordered. Parameters ---------- @@ -3074,23 +3062,17 @@ def i_from_v(voltage, photocurrent, saturation_current, resistance_series, Device current at the given device voltage for the single diode model. Uses the single diode model (SDM) as described in, e.g., - Jain and Kapoor 2004 [1]_. + Jain and Kapoor 2004 [1]_. The solution is per Eq 2 of [1] except when resistance_series=0, - in which case the explict solution for current is used. + in which case the explict solution for current is used. Ideal device parameters are specified by resistance_shunt=np.inf and - resistance_series=0. + resistance_series=0. Inputs to this function can include scalars and pandas.Series, but it is - the caller's responsibility to ensure that the arguments are all float64 - and within the proper ranges. + the caller's responsibility to ensure that the arguments are all float64 + and within the proper ranges. .. versionchanged:: 0.10.0 - The function's arguments have been reordered. In earlier versions, - the argument order is - - .. code-block:: python - - i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, - saturation_current, photocurrent, method='lambertw') + The function's arguments have been reordered. Parameters ---------- From 9710b01366ef59b3effe2e84158588e40a9521f9 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Thu, 18 May 2023 08:54:36 -0700 Subject: [PATCH 7/8] "Breaking Changes" to "Breaking changes" to match casing of "Bug fixes" --- docs/sphinx/source/whatsnew/v0.10.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.0.rst b/docs/sphinx/source/whatsnew/v0.10.0.rst index c6f19e2ff8..a4406072f8 100644 --- a/docs/sphinx/source/whatsnew/v0.10.0.rst +++ b/docs/sphinx/source/whatsnew/v0.10.0.rst @@ -5,7 +5,7 @@ v0.10.0 ------- -Breaking Changes +Breaking changes ~~~~~~~~~~~~~~~~ * Reorder arguments of :py:func:`pvlib.pvsystem.PVSystem.i_from_v`, :py:func:`pvlib.pvsystem.i_from_v`, :py:func:`pvlib.pvsystem.v_from_i`, From cbadd89510e7fbcc5e031a9ae6e1ed25195d5825 Mon Sep 17 00:00:00 2001 From: Taos Transue Date: Tue, 23 May 2023 11:20:17 -0700 Subject: [PATCH 8/8] rename I to current --- pvlib/singlediode.py | 9 +++++---- pvlib/tests/test_pvsystem.py | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index d861d201ec..81d6ce3761 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -675,8 +675,9 @@ def _pwr_optfcn(df, loc): Function to find power from ``i_from_v``. ''' - I = _lambertw_i_from_v(df[loc], df['photocurrent'], - df['saturation_current'], df['resistance_series'], - df['resistance_shunt'], df['nNsVth']) + current = _lambertw_i_from_v(df[loc], df['photocurrent'], + df['saturation_current'], + df['resistance_series'], + df['resistance_shunt'], df['nNsVth']) - return I * df[loc] + return current * df[loc] diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index d5c2ac6313..2966aa55d6 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1093,7 +1093,7 @@ def test_i_from_v_from_i(fixture_v_from_i): Rsh = fixture_v_from_i['Rsh'] Rs = fixture_v_from_i['Rs'] nNsVth = fixture_v_from_i['nNsVth'] - I = fixture_v_from_i['I'] + current = fixture_v_from_i['I'] I0 = fixture_v_from_i['I0'] IL = fixture_v_from_i['IL'] V = fixture_v_from_i['V_expected'] @@ -1103,15 +1103,15 @@ def test_i_from_v_from_i(fixture_v_from_i): I_expected = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth, method='lambertw') - assert_allclose(I, I_expected, atol=atol) + assert_allclose(current, I_expected, atol=atol) - I = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth) + current = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth) - assert isinstance(I, type(I_expected)) - if isinstance(I, np.ndarray): - assert isinstance(I.dtype, type(I_expected.dtype)) - assert I.shape == I_expected.shape - assert_allclose(I, I_expected, atol=atol) + assert isinstance(current, type(I_expected)) + if isinstance(current, np.ndarray): + assert isinstance(current.dtype, type(I_expected.dtype)) + assert current.shape == I_expected.shape + assert_allclose(current, I_expected, atol=atol) @pytest.fixture(params=[ @@ -1200,13 +1200,13 @@ def test_i_from_v(fixture_i_from_v, method, atol): IL = fixture_i_from_v['IL'] I_expected = fixture_i_from_v['I_expected'] - I = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth, method=method) + current = pvsystem.i_from_v(V, IL, I0, Rs, Rsh, nNsVth, method=method) - assert isinstance(I, type(I_expected)) - if isinstance(I, np.ndarray): - assert isinstance(I.dtype, type(I_expected.dtype)) - assert I.shape == I_expected.shape - assert_allclose(I, I_expected, atol=atol) + assert isinstance(current, type(I_expected)) + if isinstance(current, np.ndarray): + assert isinstance(current.dtype, type(I_expected.dtype)) + assert current.shape == I_expected.shape + assert_allclose(current, I_expected, atol=atol) def test_PVSystem_i_from_v(mocker):