From aa3e6e9b8f820651ac21fc75a17b023e4e80cd49 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 10 Oct 2025 08:26:50 -0700 Subject: [PATCH 1/7] restart from PR2567 --- docs/sphinx/source/whatsnew/v0.13.2.rst | 5 ++-- pvlib/singlediode.py | 32 +++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.2.rst b/docs/sphinx/source/whatsnew/v0.13.2.rst index e12f7277e5..573771b17a 100644 --- a/docs/sphinx/source/whatsnew/v0.13.2.rst +++ b/docs/sphinx/source/whatsnew/v0.13.2.rst @@ -27,7 +27,8 @@ Enhancements :py:func:`~pvlib.singlediode.bishop88_mpp`, :py:func:`~pvlib.singlediode.bishop88_v_from_i`, and :py:func:`~pvlib.singlediode.bishop88_i_from_v`. (:issue:`2497`, :pull:`2498`) - +* Accelerate :py:func:`~pvlib.pvsystem.singlediode` when scipy>=1.15 is + installed. (:issue:`2497`, :pull:`XXXX`) Documentation @@ -53,4 +54,4 @@ Maintenance Contributors ~~~~~~~~~~~~ - +* Cliff Hansen (:ghuser:`cwhanse`) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 43be522437..ad2d4b3243 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -913,11 +913,24 @@ def _lambertw(photocurrent, saturation_current, resistance_series, v_oc = 0. # 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) - - # Find Imp using Lambert W - i_mp = _lambertw_i_from_v(v_mp, **params) + # use scipy.elementwise if available + # remove try/except when scipy>=1.15, and golden mean is retired + try: + from scipy.optimize.elementwise import find_minimum + init = (0., 0.8*v_oc, v_oc) + res = find_minimum(_vmp_opt, init, + args=(params['photocurrent'], + params['saturation_current'], + params['resistance_series'], + params['resistance_shunt'], + params['nNsVth'],)) + v_mp = res.x + p_mp = -1.*res.f_x + except ModuleNotFoundError: + # switch to old golden section method + p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, + _pwr_optfcn) + i_mp = p_mp / v_mp # Find Ix and Ixx using Lambert W i_x = _lambertw_i_from_v(0.5 * v_oc, **params) @@ -938,6 +951,15 @@ def _lambertw(photocurrent, saturation_current, resistance_series, return out +def _vmp_opt(v, iph, io, rs, rsh, nNsVth): + ''' + Function to find negative of power from ``i_from_v``. + ''' + current = _lambertw_i_from_v(v, iph, io, rs, rsh, nNsVth) + + return -v * current + + def _pwr_optfcn(df, loc): ''' Function to find power from ``i_from_v``. From 6347c053d21c0746fb3e424b226dbe459d8a2d88 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 15 Oct 2025 10:45:54 -0700 Subject: [PATCH 2/7] left interval to neg., calculate i_mp --- pvlib/pvsystem.py | 19 ++++++++++++------- pvlib/singlediode.py | 5 +++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 2b703f3a52..d74537ae31 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2534,12 +2534,15 @@ def singlediode(photocurrent, saturation_current, resistance_series, explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_. If the method is ``'newton'`` then the root-finding Newton-Raphson method - is used. It should be safe for well behaved IV-curves, but the ``'brentq'`` - method is recommended for reliability. + is used. It should be safe for well-behaved IV curves, otherwise the + ``'chandralupta``` or ``'brentq'`` methods are recommended for reliability. If the method is ``'brentq'`` then Brent's bisection search method is used that guarantees convergence by bounding the voltage between zero and - open-circuit. + open-circuit. ``'brentq'`` is generally slower than the other options. + + If the method is ``'chandralupta'`` then Chandralupta's method is used + that guarantees convergence. References ---------- @@ -2553,8 +2556,9 @@ def singlediode(photocurrent, saturation_current, resistance_series, .. [3] D. King et al, "Sandia Photovoltaic Array Performance Model", SAND2004-3535, Sandia National Laboratories, Albuquerque, NM - .. [4] "Computer simulation of the effects of electrical mismatches in - photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988) + .. [4] J.W. Bishop, "Computer simulation of the effects of electrical + mismatches in photovoltaic cell interconnection circuits" Solar Cell + (1988) https://doi.org/10.1016/0379-6787(88)90059-2 """ args = (photocurrent, saturation_current, resistance_series, @@ -2565,8 +2569,9 @@ def singlediode(photocurrent, saturation_current, resistance_series, out = _singlediode._lambertw(*args) points = out[:7] else: - # Calculate points on the IV curve using either 'newton' or 'brentq' - # methods. Voltages are determined by first solving the single diode + # Calculate points on the IV curve using Bishop's algorithm and solving + # with 'newton', 'brentq' or 'chandralupta' method. + # Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage v_oc = _singlediode.bishop88_v_from_i( 0.0, *args, method=method.lower() diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index ad2d4b3243..4c278c86dd 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -917,7 +917,7 @@ def _lambertw(photocurrent, saturation_current, resistance_series, # remove try/except when scipy>=1.15, and golden mean is retired try: from scipy.optimize.elementwise import find_minimum - init = (0., 0.8*v_oc, v_oc) + init = (-1., 0.8*v_oc, v_oc) # left negative to insure strict inequality res = find_minimum(_vmp_opt, init, args=(params['photocurrent'], params['saturation_current'], @@ -930,7 +930,8 @@ def _lambertw(photocurrent, saturation_current, resistance_series, # switch to old golden section method p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, _pwr_optfcn) - i_mp = p_mp / v_mp + #i_mp = p_mp / v_mp + i_mp = _lambertw_i_from_v(v_mp, **params) # Find Ix and Ixx using Lambert W i_x = _lambertw_i_from_v(0.5 * v_oc, **params) From ae3c067f5aa48df8b9f994c042075dddfd78c175 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 15 Oct 2025 10:56:34 -0700 Subject: [PATCH 3/7] relax tolerance for i_mp --- tests/test_singlediode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_singlediode.py b/tests/test_singlediode.py index 8f3b4012c3..ca30bc0e2b 100644 --- a/tests/test_singlediode.py +++ b/tests/test_singlediode.py @@ -159,7 +159,7 @@ def test_singlediode_precision(method, precise_iv_curves): assert np.allclose(pc['i_sc'], outs['i_sc'], atol=1e-10, rtol=0) assert np.allclose(pc['v_oc'], outs['v_oc'], atol=1e-10, rtol=0) - assert np.allclose(pc['i_mp'], outs['i_mp'], atol=7e-8, rtol=0) + assert np.allclose(pc['i_mp'], outs['i_mp'], atol=1e-7, rtol=0) assert np.allclose(pc['v_mp'], outs['v_mp'], atol=1e-6, rtol=0) assert np.allclose(pc['p_mp'], outs['p_mp'], atol=1e-10, rtol=0) assert np.allclose(pc['i_x'], outs['i_x'], atol=1e-10, rtol=0) From 3e49d4a72b9acec5b6504b5d61f015259e0c0843 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 15 Oct 2025 12:55:57 -0700 Subject: [PATCH 4/7] docstring work --- pvlib/pvsystem.py | 19 +++++++++++-------- pvlib/singlediode.py | 29 ++++++++++++++++------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index d74537ae31..5b01d97877 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2546,20 +2546,23 @@ def singlediode(photocurrent, saturation_current, resistance_series, References ---------- - .. [1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN - 0 86758 909 4 + .. [1] S. R. Wenham, M. A. Green, M. E. Watt, "Applied Photovoltaics", + Centre for Photovoltaic Devices and Systems, 1995. ISBN + 0867589094 .. [2] A. Jain, A. Kapoor, "Exact analytical solutions of the parameters of real solar cells using Lambert W-function", Solar - Energy Materials and Solar Cells, 81 (2004) 269-277. + Energy Materials and Solar Cells, vol. 81 no. 2, pp. 269-277, Feb. 2004. + :doi:`10.1016/j.solmat.2003.11.018`. - .. [3] D. King et al, "Sandia Photovoltaic Array Performance Model", - SAND2004-3535, Sandia National Laboratories, Albuquerque, NM + .. [3] D. L. King, E. E. Boyson and J. A. Kratochvil "Photovoltaic Array + Performance Model", Sandia National Laboratories, Albuquerque, NM, USA. + Report SAND2004-3535, 2004. .. [4] J.W. Bishop, "Computer simulation of the effects of electrical - mismatches in photovoltaic cell interconnection circuits" Solar Cell - (1988) - https://doi.org/10.1016/0379-6787(88)90059-2 + mismatches in photovoltaic cell interconnection circuits" Solar Cells, + vol. 25 no. 1, pp. 73-89, Oct. 1988. + :doi:`doi.org/10.1016/0379-6787(88)90059-2` """ args = (photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth) # collect args diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 4c278c86dd..cb932a1838 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -141,18 +141,20 @@ def bishop88(diode_voltage, photocurrent, saturation_current, References ---------- - .. [1] "Computer simulation of the effects of electrical mismatches in - photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988) - :doi:`10.1016/0379-6787(88)90059-2` - - .. [2] "Improved equivalent circuit and Analytical Model for Amorphous - Silicon Solar Cells and Modules." J. Mertens, et al., IEEE Transactions - on Electron Devices, Vol 45, No 2, Feb 1998. + .. [1] J.W. Bishop, "Computer simulation of the effects of electrical + mismatches in photovoltaic cell interconnection circuits" Solar Cells, + vol. 25 no. 1, pp. 73-89, Oct. 1988. + :doi:`doi.org/10.1016/0379-6787(88)90059-2` + + .. [2] J. Merten, J. M. Asensi, C. Voz, A. V. Shah, R. Platz and J. Andreu, + "Improved equivalent circuit and Analytical Model for Amorphous + Silicon Solar Cells and Modules." , IEEE Transactions + on Electron Devices, vol. 45, no. 2, pp. 423-429, Feb 1998. :doi:`10.1109/16.658676` - .. [3] "Performance assessment of a simulation model for PV modules of any - available technology", André Mermoud and Thibault Lejeune, 25th EUPVSEC, - 2010 + .. [3] A. Mermoud and T. Lejeune, "Performance assessment of a simulation + model for PV modules of any available technology", In Proc. of the 25th + European PVSEC, Valencia, ES, 2010. :doi:`10.4229/25thEUPVSEC2010-4BV.1.114` """ # calculate recombination loss current where d2mutau > 0 @@ -917,7 +919,8 @@ def _lambertw(photocurrent, saturation_current, resistance_series, # remove try/except when scipy>=1.15, and golden mean is retired try: from scipy.optimize.elementwise import find_minimum - init = (-1., 0.8*v_oc, v_oc) # left negative to insure strict inequality + # left negative to insure strict inequality + init = (-1., 0.8*v_oc, v_oc) res = find_minimum(_vmp_opt, init, args=(params['photocurrent'], params['saturation_current'], @@ -930,10 +933,10 @@ def _lambertw(photocurrent, saturation_current, resistance_series, # switch to old golden section method p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, _pwr_optfcn) - #i_mp = p_mp / v_mp + i_mp = _lambertw_i_from_v(v_mp, **params) - # Find Ix and Ixx using Lambert W + # Find Ix and Ixx using Lambert W i_x = _lambertw_i_from_v(0.5 * v_oc, **params) i_xx = _lambertw_i_from_v(0.5 * (v_oc + v_mp), **params) From e48573855aef86d0d0bfaaffaa2e1bef8b918953 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 15 Oct 2025 13:04:38 -0700 Subject: [PATCH 5/7] indent --- pvlib/singlediode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index cb932a1838..779467729d 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -936,7 +936,7 @@ def _lambertw(photocurrent, saturation_current, resistance_series, i_mp = _lambertw_i_from_v(v_mp, **params) - # Find Ix and Ixx using Lambert W + # Find Ix and Ixx using Lambert W i_x = _lambertw_i_from_v(0.5 * v_oc, **params) i_xx = _lambertw_i_from_v(0.5 * (v_oc + v_mp), **params) From f0320cf3f5b1222ff16a43d31680fbf1edca9be1 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 15 Oct 2025 14:00:30 -0700 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: Kevin Anderson --- pvlib/pvsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 5b01d97877..edad38b412 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2535,13 +2535,13 @@ def singlediode(photocurrent, saturation_current, resistance_series, If the method is ``'newton'`` then the root-finding Newton-Raphson method is used. It should be safe for well-behaved IV curves, otherwise the - ``'chandralupta``` or ``'brentq'`` methods are recommended for reliability. + ``'chandrupatla``` or ``'brentq'`` methods are recommended for reliability. If the method is ``'brentq'`` then Brent's bisection search method is used that guarantees convergence by bounding the voltage between zero and open-circuit. ``'brentq'`` is generally slower than the other options. - If the method is ``'chandralupta'`` then Chandralupta's method is used + If the method is ``'chandrupatla'`` then Chandrupatla's method is used that guarantees convergence. References @@ -2573,7 +2573,7 @@ def singlediode(photocurrent, saturation_current, resistance_series, points = out[:7] else: # Calculate points on the IV curve using Bishop's algorithm and solving - # with 'newton', 'brentq' or 'chandralupta' method. + # with 'newton', 'brentq' or 'chandrupatla' method. # Voltages are determined by first solving the single diode # equation for the diode voltage V_d then backing out voltage v_oc = _singlediode.bishop88_v_from_i( From 2c8a92573bc6b8532569052b100287658c7179f7 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 22 Oct 2025 15:20:04 -0700 Subject: [PATCH 7/7] better inspection of values --- tests/test_modelchain.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index ecc2c41447..ea9b0a7555 100644 --- a/tests/test_modelchain.py +++ b/tests/test_modelchain.py @@ -1382,6 +1382,12 @@ def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, 'adr': 'adr', 'pvwatts': 'pvwatts', 'pvwatts_multi': 'pvwatts'} + inverter_to_ac_model_param = { + 'sandia': 'Paco', + 'sandia_multi': 'Paco', + 'adr': 'Pnom', + 'pvwatts': 'pdc0', + 'pvwatts_multi': 'pdc0'} ac_model = inverter_to_ac_model[inverter_model] system = ac_systems[inverter_model] @@ -1398,7 +1404,12 @@ def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, assert m.call_count == 1 assert isinstance(mc.results.ac, pd.Series) assert not mc.results.ac.empty - assert mc.results.ac.iloc[1] < 1 + # irradiance 800 W/m2 at 1st timestamp + inv_param = mc.system.inverter_parameters[ + inverter_to_ac_model_param[inverter_model]] + assert (mc.results.ac.iloc[0] > inv_param / 2.) + # irradiance 0 W/m2 at the 2nd timestamp + assert (np.isnan(mc.results.ac.iloc[1]) or (mc.results.ac.iloc[1] < 1)) def test_ac_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather,