From 967d860fa9a7cd3cafcdff90cd81190f9c77172b Mon Sep 17 00:00:00 2001 From: Jakub Krasuski Date: Tue, 22 Sep 2026 16:35:26 +0200 Subject: [PATCH 1/3] Fix RuntimeWarning in singlediode when v_oc is zero With v_oc = 0 the bracket passed to find_minimum collapsed to (-1, 0, 0), breaking its x1 < x2 < x3 requirement. Where v_oc is 0, use 1 as the right end: current falls with voltage, so the bracket still holds the minimum at 0. Closes #2671 Assisted-by: Claude Opus 5 --- docs/sphinx/source/whatsnew/v0.16.0.rst | 4 ++++ pvlib/singlediode.py | 6 ++++-- tests/test_singlediode.py | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.16.0.rst b/docs/sphinx/source/whatsnew/v0.16.0.rst index 537521069f..af07ed4086 100644 --- a/docs/sphinx/source/whatsnew/v0.16.0.rst +++ b/docs/sphinx/source/whatsnew/v0.16.0.rst @@ -83,6 +83,9 @@ Bug fixes :py:func:`~pvlib.atmosphere.get_relative_airmass` since v0.9.5 but was not added to ``pvlib.atmosphere.APPARENT_ZENITH_MODELS``, so passing it raised ``ValueError``. (:pull:`2857`) +* :py:func:`~pvlib.pvsystem.singlediode` with ``method='lambertw'`` no longer + emits ``RuntimeWarning: invalid value encountered in divide`` when the open + circuit voltage is zero, e.g. with zero photocurrent. (:issue:`2671`) Enhancements @@ -137,3 +140,4 @@ Contributors * Kevin Anderson (:ghuser:`kandersolar`) * Johann Loux (:ghuser:`JoLo90`) * Dylan Pulver (:ghuser:`dylanpulver`) +* Jakub Krasuski (:ghuser:`Blizzeq`) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index 974d59dff8..d4d9c81370 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -911,8 +911,10 @@ 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 - # left negative to insure strict inequality - init = (-1., 0.8*v_oc, v_oc) + # left negative to insure strict inequality. Where v_oc is 0 the + # bracket would collapse to x2 == x3 (GH 2671); any positive right + # end still brackets the minimum at 0, since current falls with voltage + init = (-1., 0.8*v_oc, np.where(v_oc > 0, v_oc, 1.)) res = find_minimum(_vmp_opt, init, args=(params['photocurrent'], params['saturation_current'], diff --git a/tests/test_singlediode.py b/tests/test_singlediode.py index 10b589b1b1..afd1db6050 100644 --- a/tests/test_singlediode.py +++ b/tests/test_singlediode.py @@ -2,6 +2,8 @@ testing single-diode methods using JW Bishop 1988 """ +import warnings + import numpy as np import pandas as pd import scipy @@ -191,6 +193,25 @@ def test_singlediode_lambert_negative_voc(mocker): assert_array_equal(outs["v_oc"], [0, 0]) +def test_singlediode_lambert_zero_voc(): + """With v_oc = 0 the bracket passed to find_minimum collapsed to + x2 == x3 and scipy raised a RuntimeWarning. See issue #2671. + """ + pytest.importorskip("scipy", minversion="1.15") + with warnings.catch_warnings(): + warnings.simplefilter("error") + out = pvsystem.singlediode(0., 1e-12, 0.5, np.inf, 1.8, + method="lambertw") + outs = pvsystem.singlediode(np.array([0., 5.]), 1e-12, 0.5, np.inf, + 1.8, method="lambertw") + assert out["v_oc"] == 0 + assert np.isclose(out["v_mp"], 0, atol=1e-12) + assert out["p_mp"] == 0 + assert np.isclose(outs["v_mp"][0], 0, atol=1e-12) + assert outs["p_mp"][0] == 0 + assert outs["p_mp"][1] > 0 + + @pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton', chandrupatla]) def test_v_from_i_i_from_v_precision(method, precise_iv_curves): From b8cb2c727ec276f3a1bfd360a291fd992e05a405 Mon Sep 17 00:00:00 2001 From: Jakub Krasuski Date: Tue, 22 Sep 2026 22:06:27 +0200 Subject: [PATCH 2/3] Set v_mp and p_mp to exactly 0 where v_oc is 0 Keeps the find_minimum bracket valid without letting v_mp exceed v_oc. Assisted-by: Claude Opus 5.5 --- pvlib/singlediode.py | 12 +++++++----- tests/test_singlediode.py | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pvlib/singlediode.py b/pvlib/singlediode.py index d4d9c81370..4bc58f5f53 100644 --- a/pvlib/singlediode.py +++ b/pvlib/singlediode.py @@ -912,17 +912,19 @@ def _lambertw(photocurrent, saturation_current, resistance_series, try: from scipy.optimize.elementwise import find_minimum # left negative to insure strict inequality. Where v_oc is 0 the - # bracket would collapse to x2 == x3 (GH 2671); any positive right - # end still brackets the minimum at 0, since current falls with voltage - init = (-1., 0.8*v_oc, np.where(v_oc > 0, v_oc, 1.)) + # bracket would collapse to x2 == x3 (GH 2671), and 0 is the only + # voltage in [0, v_oc], so v_mp and p_mp are 0 there. Those elements + # get a placeholder bracket whose result is discarded. + voc_zero = v_oc == 0 + init = (-1., 0.8*v_oc, np.where(voc_zero, 1., 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 + v_mp = np.where(voc_zero, 0., res.x)[()] + p_mp = np.where(voc_zero, 0., -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, diff --git a/tests/test_singlediode.py b/tests/test_singlediode.py index afd1db6050..afd13b97f7 100644 --- a/tests/test_singlediode.py +++ b/tests/test_singlediode.py @@ -205,10 +205,11 @@ def test_singlediode_lambert_zero_voc(): outs = pvsystem.singlediode(np.array([0., 5.]), 1e-12, 0.5, np.inf, 1.8, method="lambertw") assert out["v_oc"] == 0 - assert np.isclose(out["v_mp"], 0, atol=1e-12) + assert out["v_mp"] == 0 assert out["p_mp"] == 0 - assert np.isclose(outs["v_mp"][0], 0, atol=1e-12) + assert outs["v_mp"][0] == 0 assert outs["p_mp"][0] == 0 + assert 0 < outs["v_mp"][1] < outs["v_oc"][1] assert outs["p_mp"][1] > 0 From 14406a80fd544d377ccc5592cf81d81bf1700636 Mon Sep 17 00:00:00 2001 From: Jakub Krasuski Date: Sun, 27 Sep 2026 20:46:04 +0200 Subject: [PATCH 3/3] Move whatsnew entry to v0.16.2 v0.16.0 is released, so the entry for #2671 belongs to the next version. Assisted-by: Claude Opus 5.5 --- docs/sphinx/source/whatsnew/v0.16.0.rst | 10 ---------- docs/sphinx/source/whatsnew/v0.16.2.rst | 4 ++++ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.16.0.rst b/docs/sphinx/source/whatsnew/v0.16.0.rst index 0785561a43..bbb6ed0b32 100644 --- a/docs/sphinx/source/whatsnew/v0.16.0.rst +++ b/docs/sphinx/source/whatsnew/v0.16.0.rst @@ -83,15 +83,6 @@ Bug fixes * Fix :py:func:`~pvlib.iotools.get_merra2` to work with a new API endpoint now that the previous one is retired. Format of returned data and metadata is slightly different. (:pull:`2839`) -* ``model='gueymard2003'`` is now accepted by - :py:meth:`~pvlib.location.Location.get_airmass` and, through it, by - :py:class:`~pvlib.modelchain.ModelChain`. The model has been available in - :py:func:`~pvlib.atmosphere.get_relative_airmass` since v0.9.5 but was not - added to ``pvlib.atmosphere.APPARENT_ZENITH_MODELS``, so passing it raised - ``ValueError``. (:pull:`2857`) -* :py:func:`~pvlib.pvsystem.singlediode` with ``method='lambertw'`` no longer - emits ``RuntimeWarning: invalid value encountered in divide`` when the open - circuit voltage is zero, e.g. with zero photocurrent. (:issue:`2671`) * Fix ``model='gueymard2003'`` not being available in :py:meth:`~pvlib.location.Location.get_airmass` and :py:class:`~pvlib.modelchain.ModelChain`. (:pull:`2857`) @@ -198,7 +189,6 @@ Contributors * Kevin Anderson (:ghuser:`kandersolar`) * Johann Loux (:ghuser:`JoLo90`) * Dylan Pulver (:ghuser:`dylanpulver`) -* Jakub Krasuski (:ghuser:`Blizzeq`) * Adam R. Jensen (:ghuser:`AdamRJensen`) * Erin Tonita (:ghuser:`etoni044`) * Eesh Saxena (:ghuser:`eeshsaxena`) diff --git a/docs/sphinx/source/whatsnew/v0.16.2.rst b/docs/sphinx/source/whatsnew/v0.16.2.rst index f704525a12..29d0c8acd6 100644 --- a/docs/sphinx/source/whatsnew/v0.16.2.rst +++ b/docs/sphinx/source/whatsnew/v0.16.2.rst @@ -14,6 +14,9 @@ Deprecations Bug fixes ~~~~~~~~~ +* :py:func:`~pvlib.pvsystem.singlediode` with ``method='lambertw'`` no longer + emits ``RuntimeWarning: invalid value encountered in divide`` when the open + circuit voltage is zero, e.g. with zero photocurrent. (:issue:`2671`) Enhancements @@ -42,4 +45,5 @@ Maintenance Contributors ~~~~~~~~~~~~ +* Jakub Krasuski (:ghuser:`Blizzeq`)