Skip to content
31 changes: 17 additions & 14 deletions docs/examples/iv-modeling/plot_singlediode.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
# -----------------------
# This example uses :py:meth:`pvlib.pvsystem.calcparams_desoto` to calculate
# the 5 electrical parameters needed to solve the single-diode equation.
# :py:meth:`pvlib.pvsystem.singlediode` is then used to generate the IV curves.
# :py:meth:`pvlib.pvsystem.singlediode` and :py:meth:`pvlib.pvsystem.i_from_v`
# are used to generate the IV curves.

from pvlib import pvsystem
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

Expand Down Expand Up @@ -88,26 +90,27 @@
)

# plug the parameters into the SDE and solve for IV curves:
curve_info = pvsystem.singlediode(
photocurrent=IL,
saturation_current=I0,
resistance_series=Rs,
resistance_shunt=Rsh,
nNsVth=nNsVth,
ivcurve_pnts=100,
method='lambertw'
)
SDE_params = {
'photocurrent': IL,
'saturation_current': I0,
'resistance_series': Rs,
'resistance_shunt': Rsh,
'nNsVth': nNsVth
}
curve_info = pvsystem.singlediode(method='lambertw', **SDE_params)
v = pd.DataFrame(np.linspace(0., curve_info['v_oc'], 100))
i = pd.DataFrame(pvsystem.i_from_v(voltage=v, method='lambertw', **SDE_params))

# plot the calculated curves:
plt.figure()
for i, case in conditions.iterrows():
for idx, case in conditions.iterrows():
label = (
"$G_{eff}$ " + f"{case['Geff']} $W/m^2$\n"
"$T_{cell}$ " + f"{case['Tcell']} $\\degree C$"
)
plt.plot(curve_info['v'][i], curve_info['i'][i], label=label)
v_mp = curve_info['v_mp'][i]
i_mp = curve_info['i_mp'][i]
plt.plot(v[idx], i[idx], label=label)
v_mp = curve_info['v_mp'][idx]
i_mp = curve_info['i_mp'][idx]
# mark the MPP
plt.plot([v_mp], [i_mp], ls='', marker='o', c='k')

Expand Down
3 changes: 3 additions & 0 deletions docs/sphinx/source/whatsnew/v0.10.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Breaking changes

Deprecations
~~~~~~~~~~~~
* The ``ivcurve_pnts`` parameter of :py:func:`pvlib.pvsystem.singlediode` is
deprecated. Use :py:func:`pvlib.pvsystem.v_from_i` and
:py:func:`pvlib.pvsystem.i_from_v` instead. (:issue:`1626`, :pull:`1743`)


Enhancements
Expand Down
98 changes: 50 additions & 48 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from abc import ABC, abstractmethod
from typing import Optional

from pvlib._deprecation import deprecated
from pvlib._deprecation import deprecated, warn_deprecated

from pvlib import (atmosphere, iam, inverter, irradiance,
singlediode as _singlediode, temperature)
Expand Down Expand Up @@ -2714,7 +2714,7 @@ def singlediode(photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, ivcurve_pnts=None,
method='lambertw'):
r"""
Solve the single-diode equation to obtain a photovoltaic IV curve.
Solve the single diode equation to obtain a photovoltaic IV curve.

Solves the single diode equation [1]_

Expand All @@ -2727,11 +2727,10 @@ def singlediode(photocurrent, saturation_current, resistance_series,
\frac{V + I R_s}{R_{sh}}

for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and
:math:`n N_s V_{th}` which are described later. Returns a DataFrame
which contains the 5 points on the I-V curve specified in
[3]_. If all :math:`I_L, I_0, R_s, R_{sh},` and
:math:`n N_s V_{th}` are scalar, a single curve is returned, if any
are Series (of the same length), multiple IV curves are calculated.
:math:`n N_s V_{th}` which are described later. The five points on the I-V
curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and
:math:`n N_s V_{th}` are all scalars, a single curve is returned. If any
are array-like (of the same length), multiple IV curves are calculated.

The input parameters can be calculated from meteorological data using a
function for a single diode model, e.g.,
Expand Down Expand Up @@ -2769,35 +2768,33 @@ def singlediode(photocurrent, saturation_current, resistance_series,
Number of points in the desired IV curve. If None or 0, no points on
the IV curves will be produced.

.. deprecated:: 0.10.0
Use :py:func:`pvlib.pvsystem.v_from_i` and
:py:func:`pvlib.pvsystem.i_from_v` instead.

method : str, default 'lambertw'
Determines the method used to calculate points on the IV curve. The
options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.

Returns
-------
OrderedDict or DataFrame

The returned dict-like object always contains the keys/columns:

* i_sc - short circuit current in amperes.
* v_oc - open circuit voltage in volts.
* i_mp - current at maximum power point in amperes.
* v_mp - voltage at maximum power point in volts.
* p_mp - power at maximum power point in watts.
* i_x - current, in amperes, at ``v = 0.5*v_oc``.
* i_xx - current, in amperes, at ``V = 0.5*(v_oc+v_mp)``.
dict or pandas.DataFrame
The returned dict-like object always contains the keys/columns:

If ivcurve_pnts is greater than 0, the output dictionary will also
include the keys:
* i_sc - short circuit current in amperes.
* v_oc - open circuit voltage in volts.
* i_mp - current at maximum power point in amperes.
* v_mp - voltage at maximum power point in volts.
* p_mp - power at maximum power point in watts.
* i_x - current, in amperes, at ``v = 0.5*v_oc``.
* i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.

* i - IV curve current in amperes.
* v - IV curve voltage in volts.
A dict is returned when the input parameters are scalars or
``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary
will also include the keys:

The output will be an OrderedDict if photocurrent is a scalar,
array, or ivcurve_pnts is not None.

The output will be a DataFrame if photocurrent is a Series and
ivcurve_pnts is None.
* i - IV curve current in amperes.
* v - IV curve voltage in volts.

See also
--------
Expand Down Expand Up @@ -2844,22 +2841,25 @@ def singlediode(photocurrent, saturation_current, resistance_series,
photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
https://doi.org/10.1016/0379-6787(88)90059-2
"""
if ivcurve_pnts:
warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',
alternative=('pvlib.pvsystem.v_from_i and '
'pvlib.pvsystem.i_from_v'),
obj_type='parameter ivcurve_pnts',
removal='0.11.0')
args = (photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth) # collect args
# Calculate points on the IV curve using the LambertW solution to the
# single diode equation
if method.lower() == 'lambertw':
out = _singlediode._lambertw(
photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, ivcurve_pnts
)
i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx = out[:7]
out = _singlediode._lambertw(*args, ivcurve_pnts)
points = out[:7]
if ivcurve_pnts:
ivcurve_i, ivcurve_v = out[7:]
else:
# Calculate points on the IV curve using either 'newton' or 'brentq'
# 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
v_oc = _singlediode.bishop88_v_from_i(
0.0, *args, method=method.lower()
)
Expand All @@ -2875,6 +2875,7 @@ def singlediode(photocurrent, saturation_current, resistance_series,
i_xx = _singlediode.bishop88_i_from_v(
(v_oc + v_mp) / 2.0, *args, method=method.lower()
)
points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx

# calculate the IV curve if requested using bishop88
if ivcurve_pnts:
Expand All @@ -2883,22 +2884,23 @@ def singlediode(photocurrent, saturation_current, resistance_series,
)
ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)

out = OrderedDict()
out['i_sc'] = i_sc
out['v_oc'] = v_oc
out['i_mp'] = i_mp
out['v_mp'] = v_mp
out['p_mp'] = p_mp
out['i_x'] = i_x
out['i_xx'] = i_xx
columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')

if ivcurve_pnts:
if all(map(np.isscalar, args)) or ivcurve_pnts:
out = {c: p for c, p in zip(columns, points)}

if ivcurve_pnts:
out.update(i=ivcurve_i, v=ivcurve_v)

return out

points = np.atleast_1d(*points) # convert scalars to 1d-arrays
points = np.vstack(points).T # collect rows into DataFrame columns

out['v'] = ivcurve_v
out['i'] = ivcurve_i
# save the first available pd.Series index, otherwise set to None
index = next((a.index for a in args if isinstance(a, pd.Series)), None)

if isinstance(photocurrent, pd.Series) and not ivcurve_pnts:
out = pd.DataFrame(out, index=photocurrent.index)
out = pd.DataFrame(points, columns=columns, index=index)

return out

Expand Down Expand Up @@ -2939,7 +2941,7 @@ def max_power_point(photocurrent, saturation_current, resistance_series,

Returns
-------
OrderedDict or pandas.Datafrane
OrderedDict or pandas.DataFrame
``(i_mp, v_mp, p_mp)``

Notes
Expand Down
36 changes: 19 additions & 17 deletions pvlib/tests/ivtools/test_sde.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest
from pvlib import pvsystem
from pvlib.ivtools import sde
from pvlib._deprecation import pvlibDeprecationWarning


@pytest.fixture
Expand All @@ -11,31 +12,32 @@ def get_test_iv_params():

def test_fit_sandia_simple(get_test_iv_params, get_bad_iv_curves):
test_params = get_test_iv_params
testcurve = pvsystem.singlediode(photocurrent=test_params['IL'],
saturation_current=test_params['I0'],
resistance_shunt=test_params['Rsh'],
resistance_series=test_params['Rs'],
nNsVth=test_params['nNsVth'],
ivcurve_pnts=300)
expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rs', 'Rsh',
'nNsVth'])
result = sde.fit_sandia_simple(voltage=testcurve['v'],
current=testcurve['i'])
test_params = dict(photocurrent=test_params['IL'],
saturation_current=test_params['I0'],
resistance_series=test_params['Rs'],
resistance_shunt=test_params['Rsh'],
nNsVth=test_params['nNsVth'])
testcurve = pvsystem.singlediode(**test_params)
v = np.linspace(0., testcurve['v_oc'], 300)
i = pvsystem.i_from_v(voltage=v, **test_params)
expected = tuple(test_params.values())

result = sde.fit_sandia_simple(voltage=v, current=i)
assert np.allclose(result, expected, rtol=5e-5)
result = sde.fit_sandia_simple(voltage=testcurve['v'],
current=testcurve['i'],

result = sde.fit_sandia_simple(voltage=v, current=i,
v_oc=testcurve['v_oc'],
i_sc=testcurve['i_sc'])
assert np.allclose(result, expected, rtol=5e-5)
result = sde.fit_sandia_simple(voltage=testcurve['v'],
current=testcurve['i'],

result = sde.fit_sandia_simple(voltage=v, current=i,
v_oc=testcurve['v_oc'],
i_sc=testcurve['i_sc'],
v_mp_i_mp=(testcurve['v_mp'],
testcurve['i_mp']))
testcurve['i_mp']))
assert np.allclose(result, expected, rtol=5e-5)
result = sde.fit_sandia_simple(voltage=testcurve['v'],
current=testcurve['i'], vlim=0.1)

result = sde.fit_sandia_simple(voltage=v, current=i, vlim=0.1)
assert np.allclose(result, expected, rtol=5e-5)


Expand Down
13 changes: 9 additions & 4 deletions pvlib/tests/ivtools/test_sdm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from pvlib.ivtools import sdm
from pvlib import pvsystem
from pvlib._deprecation import pvlibDeprecationWarning

from pvlib.tests.conftest import requires_pysam, requires_statsmodels

Expand Down Expand Up @@ -100,11 +101,15 @@ def test_fit_desoto_sandia(cec_params_cansol_cs5p_220p):
temp_cell = np.array([15., 25., 35., 45.])
ee = np.tile(effective_irradiance, len(temp_cell))
tc = np.repeat(temp_cell, len(effective_irradiance))
iph, io, rs, rsh, nnsvth = pvsystem.calcparams_desoto(
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
ee, tc, alpha_sc=specs['alpha_sc'], **params)
sim_ivcurves = pvsystem.singlediode(iph, io, rs, rsh, nnsvth, 300)
sim_ivcurves['ee'] = ee
sim_ivcurves['tc'] = tc
ivcurve_params = dict(photocurrent=IL, saturation_current=I0,
resistance_series=Rs, resistance_shunt=Rsh,
nNsVth=nNsVth)
sim_ivcurves = pvsystem.singlediode(**ivcurve_params).to_dict('series')
v = np.linspace(0., sim_ivcurves['v_oc'], 300)
i = pvsystem.i_from_v(voltage=v, **ivcurve_params)
sim_ivcurves.update(v=v.T, i=i.T, ee=ee, tc=tc)

result = sdm.fit_desoto_sandia(sim_ivcurves, specs)
modeled = pd.Series(index=params.keys(), data=np.nan)
Expand Down
20 changes: 15 additions & 5 deletions pvlib/tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,8 +1358,9 @@ def test_singlediode_floats():


def test_singlediode_floats_ivcurve():
out = pvsystem.singlediode(7., 6e-7, .1, 20., .5, ivcurve_pnts=3,
method='lambertw')
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
out = pvsystem.singlediode(7., 6e-7, .1, 20., .5, ivcurve_pnts=3,
method='lambertw')
expected = {'i_xx': 4.264060478,
'i_mp': 6.136267360,
'v_oc': 8.106300147,
Expand Down Expand Up @@ -1391,8 +1392,9 @@ def test_singlediode_series_ivcurve(cec_module_params):
EgRef=1.121,
dEgdT=-0.0002677)

out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3,
method='lambertw')
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3,
method='lambertw')

expected = OrderedDict([('i_sc', array([0., 3.01079860, 6.00726296])),
('v_oc', array([0., 9.96959733, 10.29603253])),
Expand All @@ -1411,7 +1413,8 @@ def test_singlediode_series_ivcurve(cec_module_params):
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)
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3)

expected['i_mp'] = pvsystem.i_from_v(out['v_mp'], IL, I0, Rs, Rsh, nNsVth,
method='lambertw')
Expand All @@ -1426,6 +1429,13 @@ def test_singlediode_series_ivcurve(cec_module_params):
assert_allclose(v, expected[k], atol=1e-6)


@pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
def test_singlediode_ivcurvepnts_deprecation_warning(method):
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
pvsystem.singlediode(7., 6e-7, .1, 20., .5, ivcurve_pnts=3,
method=method)


def test_scale_voltage_current_power():
data = pd.DataFrame(
np.array([[2, 1.5, 10, 8, 12, 0.5, 1.5]]),
Expand Down
6 changes: 5 additions & 1 deletion pvlib/tests/test_singlediode.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pvlib import pvsystem
from pvlib.singlediode import (bishop88_mpp, estimate_voc, VOLTAGE_BUILTIN,
bishop88, bishop88_i_from_v, bishop88_v_from_i)
from pvlib._deprecation import pvlibDeprecationWarning
import pytest
from .conftest import DATA_DIR

Expand Down Expand Up @@ -176,7 +177,10 @@ def test_ivcurve_pnts_precision(method, precise_iv_curves):
x, pc = precise_iv_curves
pc_i, pc_v = np.stack(pc['Currents']), np.stack(pc['Voltages'])
ivcurve_pnts = len(pc['Currents'][0])
outs = pvsystem.singlediode(method=method, ivcurve_pnts=ivcurve_pnts, **x)

with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
outs = pvsystem.singlediode(method=method, ivcurve_pnts=ivcurve_pnts,
**x)

assert np.allclose(pc_i, outs['i'], atol=1e-10, rtol=0)
assert np.allclose(pc_v, outs['v'], atol=1e-10, rtol=0)
Expand Down
Loading