Skip to content
28 changes: 15 additions & 13 deletions docs/examples/iv-modeling/plot_singlediode.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# :py:meth:`pvlib.pvsystem.singlediode` is then 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 +89,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
3 changes: 3 additions & 0 deletions pvlib/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
df['max'] = 0.5 * (df['V1'] + df['V2'])
func_result = func(df, 'max')
x = np.where(np.isnan(func_result), np.nan, df['max'])
if np.isscalar(df['max']):
# np.where always returns an ndarray, converting scalars to 0d-arrays
x = x.item()

return func_result, x

Expand Down