Skip to content

Commit dc6bdbf

Browse files
mikofskiwholmgren
authored andcommitted
MAINT: DOC: rename singlediode module, and clean up docstring for singlediode function (#525)
* MAINT: rename singlediode_methods.py to just singlediode.py * closes #515 * import pvlib in pvsystem and use pvlib.singlediode to differentiate from existing pvsystem.singldiode() method already there! * rename test_singlediode_methods.py to test_singlediode.py * update documentation and comments * DOC: move proof that estimated Voc is in q4 * closes #518 * add singlediode.rst and add to index * DOC: add DOI to Jain and Kapoor * proofed the docs and they are ok * DOC: MAINT: improve wording in singlediode.rst * add to what's new documentation section that there is now some detail on pvlib-python solutions to single diode equation * change single diode _model_ to single diode _equation_ everywhere to distinguish between the equation I = IL - I0*(exp(Vd/nNsVt)-1) - Vd/Rsh and implementation like PVSyst and DeSoto that derive coefficients * add reference to Cliff's Sandia Report on Lambert W-function solution of single diode equation * remove fixme in pvsystem * DOC: MAINT: fix citation for Sandia Report, remove extra parentheses
1 parent 5e6ba7e commit dc6bdbf

File tree

10 files changed

+155
-78
lines changed

10 files changed

+155
-78
lines changed

docs/sphinx/source/api.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,11 @@ Low-level functions for solving the single diode equation.
213213
.. autosummary::
214214
:toctree: generated/
215215

216-
singlediode_methods.estimate_voc
217-
singlediode_methods.bishop88
218-
singlediode_methods.bishop88_i_from_v
219-
singlediode_methods.bishop88_v_from_i
220-
singlediode_methods.bishop88_mpp
216+
singlediode.estimate_voc
217+
singlediode.bishop88
218+
singlediode.bishop88_i_from_v
219+
singlediode.bishop88_v_from_i
220+
singlediode.bishop88_mpp
221221

222222
SAPM model
223223
----------

docs/sphinx/source/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Contents
8181
api
8282
comparison_pvlib_matlab
8383
variables_style_rules
84+
singlediode
8485

8586

8687
Indices and tables

docs/sphinx/source/singlediode.rst

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
.. _singlediode:
2+
3+
Single Diode Equation
4+
=====================
5+
6+
This section reviews the solutions to the single diode equation used in
7+
pvlib-python to generate an IV curve of a PV module.
8+
9+
pvlib-python supports two ways to solve the single diode equation:
10+
11+
1. Lambert W-Function
12+
2. Bishop's Algorithm
13+
14+
The :func:`pvlib.pvsystem.singlediode` function allows the user to choose the
15+
method using the ``method`` keyword.
16+
17+
Lambert W-Function
18+
------------------
19+
When ``method='lambertw'``, the Lambert W-function is used as previously shown
20+
by Jain, Kapoor [1, 2] and Hansen [3]. The following algorithm can be found on
21+
`Wikipedia: Theory of Solar Cells
22+
<https://en.wikipedia.org/wiki/Theory_of_solar_cells>`_, given the basic single
23+
diode model equation.
24+
25+
.. math::
26+
27+
I = I_L - I_0 \left(\exp \left(\frac{V + I R_s}{n Ns V_{th}} \right) - 1 \right)
28+
- \frac{V + I R_s}{R_{sh}}
29+
30+
Lambert W-function is the inverse of the function
31+
:math:`f \left( w \right) = w \exp \left( w \right)` or
32+
:math:`w = f^{-1} \left( w \exp \left( w \right) \right)` also given as
33+
:math:`w = W \left( w \exp \left( w \right) \right)`. Defining the following
34+
parameter, :math:`z`, is necessary to transform the single diode equation into
35+
a form that can be expressed as a Lambert W-function.
36+
37+
.. math::
38+
39+
z = \frac{R_s I_0}{n Ns V_{th} \left(1 + \frac{R_s}{R_{sh}} \right)} \exp \left(
40+
\frac{R_s \left( I_L + I_0 \right) + V}{n Ns V_{th} \left(1 + \frac{R_s}{R_{sh}}\right)}
41+
\right)
42+
43+
Then the module current can be solved using the Lambert W-function,
44+
:math:`W \left(z \right)`.
45+
46+
.. math::
47+
48+
I = \frac{I_L + I_0 - \frac{V}{R_{sh}}}{1 + \frac{R_s}{R_{sh}}}
49+
- \frac{n Ns V_{th}}{R_s} W \left(z \right)
50+
51+
52+
Bishop's Algorithm
53+
------------------
54+
The function :func:`pvlib.singlediode.bishop88` uses an explicit solution [4]
55+
that finds points on the IV curve by first solving for pairs :math:`(V_d, I)`
56+
where :math:`V_d` is the diode voltage :math:`V_d = V + I*Rs`. Then the voltage
57+
is backed out from :math:`V_d`. Points with specific voltage, such as open
58+
circuit, are located using the bisection search method, ``brentq``, bounded
59+
by a zero diode voltage and an estimate of open circuit voltage given by
60+
61+
.. math::
62+
63+
V_{oc, est} = n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1 \right)
64+
65+
We know that :math:`V_d = 0` corresponds to a voltage less than zero, and
66+
we can also show that when :math:`V_d = V_{oc, est}`, the resulting
67+
current is also negative, meaning that the corresponding voltage must be
68+
in the 4th quadrant and therefore greater than the open circuit voltage
69+
(see proof below). Therefore the entire forward-bias 1st quadrant IV-curve
70+
is bounded because :math:`V_{oc} < V_{oc, est}`, and so a bisection search
71+
between 0 and :math:`V_{oc, est}` will always find any desired condition in the
72+
1st quadrant including :math:`V_{oc}`.
73+
74+
.. math::
75+
76+
I = I_L - I_0 \left(\exp \left(\frac{V_{oc, est}}{n Ns V_{th}} \right) - 1 \right)
77+
- \frac{V_{oc, est}}{R_{sh}} \newline
78+
79+
I = I_L - I_0 \left(\exp \left(\frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{n Ns V_{th}} \right) - 1 \right)
80+
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline
81+
82+
I = I_L - I_0 \left(\exp \left(\log \left(\frac{I_L}{I_0} + 1 \right) \right) - 1 \right)
83+
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline
84+
85+
I = I_L - I_0 \left(\frac{I_L}{I_0} + 1 - 1 \right)
86+
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline
87+
88+
I = I_L - I_0 \left(\frac{I_L}{I_0} \right)
89+
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline
90+
91+
I = I_L - I_L - \frac{n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline
92+
93+
I = - \frac{n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1 \right)}{R_{sh}}
94+
95+
References
96+
----------
97+
[1] "Exact analytical solutions of the parameters of real solar cells using
98+
Lambert W-function," A. Jain, A. Kapoor, Solar Energy Materials and Solar Cells,
99+
81, (2004) pp 269-277.
100+
:doi:`10.1016/j.solmat.2003.11.018`
101+
102+
[2] "A new method to determine the diode ideality factor of real solar cell
103+
using Lambert W-function," A. Jain, A. Kapoor, Solar Energy Materials and Solar
104+
Cells, 85, (2005) 391-396.
105+
:doi:`10.1016/j.solmat.2004.05.022`
106+
107+
[3] "Parameter Estimation for Single Diode Models of Photovoltaic Modules,"
108+
Clifford W. Hansen, Sandia `Report SAND2015-2065
109+
<https://prod.sandia.gov/techlib-noauth/access-control.cgi/2015/152065.pdf>`_,
110+
2015 :doi:`10.13140/RG.2.1.4336.7842`
111+
112+
[4] "Computer simulation of the effects of electrical mismatches in
113+
photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
114+
:doi:`10.1016/0379-6787(88)90059-2`

docs/sphinx/source/whatsnew/v0.6.0.rst

+11-9
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,32 @@ Enhancements
3232
``method`` in ``('lambertw', 'newton', 'brentq')``, default is ``'lambertw'``,
3333
to select a method to solve the single diode equation for points on the IV
3434
curve. Selecting either ``'brentq'`` or ``'newton'`` as the method uses
35-
:func:`~pvlib.singlediode_methods.bishop88` with the corresponding method.
35+
:func:`~pvlib.singlediode.bishop88` with the corresponding method.
3636
(:issue:`410`)
3737
* Implement new methods ``'brentq'`` and ``'newton'`` for solving the single
3838
diode equation for points on the IV curve. ``'brentq'`` uses a bisection
3939
method (Brent, 1973) that may be slow but guarantees a solution. ``'newton'``
4040
uses the Newton-Raphson method and may be faster but is not guaranteed to
4141
converge. However, ``'newton'`` should be safe for well-behaved IV curves.
4242
(:issue:`408`)
43-
* Implement :func:`~pvlib.singlediode_methods.bishop88` for explicit calculation
43+
* Implement :func:`~pvlib.singlediode.bishop88` for explicit calculation
4444
of arbitrary IV curve points using diode voltage instead of cell voltage. If
4545
``method`` is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts`` in
4646
:func:`~pvlib.pvsystem.singlediode` is provided, the IV curve points will be
4747
log spaced instead of linear.
48-
* Implement :func:`~pvlib.singlediode_methods.estimate_voc` to estimate open
48+
* Implement :func:`~pvlib.singlediode.estimate_voc` to estimate open
4949
circuit voltage by assuming :math:`R_{sh} \to \infty` and :math:`R_s=0` as an
5050
upper bound in bisection method for :func:`~pvlib.pvsystem.singlediode` when
5151
method is either ``'newton'`` or ``'brentq'``.
5252
* Add :func:`~pvlib.pvsystem.max_power_point` method to compute the max power
5353
point using the new ``'brentq'`` method.
54-
* Add new module ``pvlib.singlediode_methods`` with low-level functions for
54+
* Add new module ``pvlib.singlediode`` with low-level functions for
5555
solving the single diode equation such as:
56-
:func:`~pvlib.singlediode_methods.bishop88`,
57-
:func:`~pvlib.singlediode_methods.estimate_voc`,
58-
:func:`~pvlib.singlediode_methods.bishop88_i_from_v`,
59-
:func:`~pvlib.singlediode_methods.bishop88_v_from_i`, and
60-
:func:`~pvlib.singlediode_methods.bishop88_mpp`.
56+
:func:`~pvlib.singlediode.bishop88`,
57+
:func:`~pvlib.singlediode.estimate_voc`,
58+
:func:`~pvlib.singlediode.bishop88_i_from_v`,
59+
:func:`~pvlib.singlediode.bishop88_v_from_i`, and
60+
:func:`~pvlib.singlediode.bishop88_mpp`.
6161
* Add PVSyst thin-film recombination losses for CdTe and a:Si (:issue:`163`)
6262
* Python 3.7 officially supported. (:issue:`496`)
6363

@@ -85,6 +85,8 @@ Documentation
8585
* Updated several incorrect statements in ModelChain documentation regarding
8686
implementation status and default values. (:issue:`480`)
8787
* Expanded general contributing and pull request guidelines.
88+
* Added section on single diode equation with some detail on solutions used in
89+
pvlib-python (:issue:`518`)
8890

8991

9092
Testing

pvlib/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
from pvlib import pvsystem
1212
from pvlib import spa
1313
from pvlib import modelchain
14-
from pvlib import singlediode_methods
14+
from pvlib import singlediode

pvlib/pvsystem.py

+17-57
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from pvlib.tools import _build_kwargs
2121
from pvlib.location import Location
2222
from pvlib import irradiance, atmosphere
23-
from pvlib import singlediode_methods
23+
import pvlib # use pvlib.singlediode to avoid clash with local method
2424

2525

2626
# not sure if this belongs in the pvsystem module.
@@ -1963,52 +1963,12 @@ def singlediode(photocurrent, saturation_current, resistance_series,
19631963
open-circuit.
19641964
19651965
If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``
1966-
are indicated, then :func:`pvlib.singlediode_methods.bishop88` is used to
1966+
are indicated, then :func:`pvlib.singlediode.bishop88` [4] is used to
19671967
calculate the points on the IV curve points at diode voltages from zero to
19681968
open-circuit voltage with a log spacing that gets closer as voltage
19691969
increases. If the method is ``'lambertw'`` then the calculated points on
19701970
the IV curve are linearly spaced.
19711971
1972-
The ``bishop88`` method uses an explicit solution from [4] that finds
1973-
points on the IV curve by first solving for pairs :math:`(V_d, I)` where
1974-
:math:`V_d` is the diode voltage :math:`V_d = V + I*Rs`. Then the voltage
1975-
is backed out from :math:`V_d`. Points with specific voltage, such as open
1976-
circuit, are located using the bisection search method, ``brentq``, bounded
1977-
by a zero diode voltage and an estimate of open circuit voltage given by
1978-
1979-
.. math::
1980-
1981-
V_{oc, est} = n Ns V_{th} \\log \\left( \\frac{I_L}{I_0} + 1 \\right)
1982-
1983-
We know that :math:`V_d = 0` corresponds to a voltage less than zero, and
1984-
we can also show that when :math:`V_d = V_{oc, est}`, the resulting
1985-
current is also negative, meaning that the corresponding voltage must be
1986-
in the 4th quadrant and therefore greater than the open circuit voltage
1987-
(see proof below). Therefore the entire forward-bias 1st quadrant IV-curve
1988-
is bounded, and a bisection search within these points will always find
1989-
desired condition.
1990-
1991-
.. math::
1992-
1993-
I = I_L - I_0 \\left(\\exp \\left(\\frac{V_{oc, est}}{n Ns V_{th}} \\right) - 1 \\right)
1994-
- \\frac{V_{oc, est}}{R_{sh}} \\newline
1995-
1996-
I = I_L - I_0 \\left(\\exp \\left(\\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{n Ns V_{th}} \\right) - 1 \\right)
1997-
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline
1998-
1999-
I = I_L - I_0 \\left(\\exp \\left(\\log \\left(\\frac{I_L}{I_0} + 1 \\right) \\right) - 1 \\right)
2000-
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline
2001-
2002-
I = I_L - I_0 \\left(\\frac{I_L}{I_0} + 1 - 1 \\right)
2003-
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline
2004-
2005-
I = I_L - I_0 \\left(\\frac{I_L}{I_0} \\right)
2006-
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline
2007-
2008-
I = I_L - I_L - \\frac{n Ns V_{th} \log \\left( \\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline
2009-
2010-
I = - \\frac{n Ns V_{th} \\log \\left( \\frac{I_L}{I_0} + 1 \\right)}{R_{sh}}
2011-
20121972
References
20131973
-----------
20141974
[1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN
@@ -2029,12 +1989,12 @@ def singlediode(photocurrent, saturation_current, resistance_series,
20291989
--------
20301990
sapm
20311991
calcparams_desoto
2032-
pvlib.singlediode_methods.bishop88
1992+
pvlib.singlediode.bishop88
20331993
"""
20341994
# Calculate points on the IV curve using the LambertW solution to the
20351995
# single diode equation
20361996
if method.lower() == 'lambertw':
2037-
out = singlediode_methods._lambertw(
1997+
out = pvlib.singlediode._lambertw(
20381998
photocurrent, saturation_current, resistance_series,
20391999
resistance_shunt, nNsVth, ivcurve_pnts
20402000
)
@@ -2047,19 +2007,19 @@ def singlediode(photocurrent, saturation_current, resistance_series,
20472007
# equation for the diode voltage V_d then backing out voltage
20482008
args = (photocurrent, saturation_current, resistance_series,
20492009
resistance_shunt, nNsVth) # collect args
2050-
v_oc = singlediode_methods.bishop88_v_from_i(
2010+
v_oc = pvlib.singlediode.bishop88_v_from_i(
20512011
0.0, *args, method=method.lower()
20522012
)
2053-
i_mp, v_mp, p_mp = singlediode_methods.bishop88_mpp(
2013+
i_mp, v_mp, p_mp = pvlib.singlediode.bishop88_mpp(
20542014
*args, method=method.lower()
20552015
)
2056-
i_sc = singlediode_methods.bishop88_i_from_v(
2016+
i_sc = pvlib.singlediode.bishop88_i_from_v(
20572017
0.0, *args, method=method.lower()
20582018
)
2059-
i_x = singlediode_methods.bishop88_i_from_v(
2019+
i_x = pvlib.singlediode.bishop88_i_from_v(
20602020
v_oc / 2.0, *args, method=method.lower()
20612021
)
2062-
i_xx = singlediode_methods.bishop88_i_from_v(
2022+
i_xx = pvlib.singlediode.bishop88_i_from_v(
20632023
(v_oc + v_mp) / 2.0, *args, method=method.lower()
20642024
)
20652025

@@ -2069,7 +2029,7 @@ def singlediode(photocurrent, saturation_current, resistance_series,
20692029
(11.0 - np.logspace(np.log10(11.0), 0.0,
20702030
ivcurve_pnts)) / 10.0
20712031
)
2072-
ivcurve_i, ivcurve_v, _ = singlediode_methods.bishop88(vd, *args)
2032+
ivcurve_i, ivcurve_v, _ = pvlib.singlediode.bishop88(vd, *args)
20732033

20742034
out = OrderedDict()
20752035
out['i_sc'] = i_sc
@@ -2125,7 +2085,7 @@ def max_power_point(photocurrent, saturation_current, resistance_series,
21252085
curve. This function uses Brent's method by default because it is
21262086
guaranteed to converge.
21272087
"""
2128-
i_mp, v_mp, p_mp = singlediode_methods.bishop88_mpp(
2088+
i_mp, v_mp, p_mp = pvlib.singlediode.bishop88_mpp(
21292089
photocurrent, saturation_current, resistance_series,
21302090
resistance_shunt, nNsVth, method=method.lower()
21312091
)
@@ -2205,7 +2165,7 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
22052165
Energy Materials and Solar Cells, 81 (2004) 269-277.
22062166
'''
22072167
if method.lower() == 'lambertw':
2208-
return singlediode_methods._lambertw_v_from_i(
2168+
return pvlib.singlediode._lambertw_v_from_i(
22092169
resistance_shunt, resistance_series, nNsVth, current,
22102170
saturation_current, photocurrent
22112171
)
@@ -2215,9 +2175,9 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
22152175
# equation for the diode voltage V_d then backing out voltage
22162176
args = (current, photocurrent, saturation_current,
22172177
resistance_series, resistance_shunt, nNsVth)
2218-
V = singlediode_methods.bishop88_v_from_i(*args, method=method.lower())
2178+
V = pvlib.singlediode.bishop88_v_from_i(*args, method=method.lower())
22192179
# find the right size and shape for returns
2220-
size, shape = singlediode_methods._get_size_and_shape(args)
2180+
size, shape = pvlib.singlediode._get_size_and_shape(args)
22212181
if size <= 1:
22222182
if shape is not None:
22232183
V = np.tile(V, shape)
@@ -2293,7 +2253,7 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
22932253
Energy Materials and Solar Cells, 81 (2004) 269-277.
22942254
'''
22952255
if method.lower() == 'lambertw':
2296-
return singlediode_methods._lambertw_i_from_v(
2256+
return pvlib.singlediode._lambertw_i_from_v(
22972257
resistance_shunt, resistance_series, nNsVth, voltage,
22982258
saturation_current, photocurrent
22992259
)
@@ -2303,9 +2263,9 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
23032263
# equation for the diode voltage V_d then backing out voltage
23042264
args = (voltage, photocurrent, saturation_current, resistance_series,
23052265
resistance_shunt, nNsVth)
2306-
I = singlediode_methods.bishop88_i_from_v(*args, method=method.lower())
2266+
I = pvlib.singlediode.bishop88_i_from_v(*args, method=method.lower())
23072267
# find the right size and shape for returns
2308-
size, shape = singlediode_methods._get_size_and_shape(args)
2268+
size, shape = pvlib.singlediode._get_size_and_shape(args)
23092269
if size <= 1:
23102270
if shape is not None:
23112271
I = np.tile(I, shape)
File renamed without changes.

pvlib/test/test_numerical_precision.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
77
This module can be executed from the command line to generate a high precision
88
dataset of I-V curve points to test the explicit single diode calculations
9-
:func:`pvlib.singlediode_methods.bishop88`::
9+
:func:`pvlib.singlediode.bishop88`::
1010
1111
$ python test_numeric_precision.py
1212
1313
This generates a file in the pvlib data folder, which is specified by the
1414
constant ``DATA_PATH``. When the test is run using ``pytest`` it will compare
15-
the values calculated by :func:`pvlib.singlediode_methods.bishop88` with the
15+
the values calculated by :func:`pvlib.singlediode.bishop88` with the
1616
high-precision values generated with SymPy.
1717
"""
1818

@@ -21,7 +21,7 @@
2121
import numpy as np
2222
import pandas as pd
2323
from pvlib import pvsystem
24-
from pvlib.singlediode_methods import bishop88, estimate_voc
24+
from pvlib.singlediode import bishop88, estimate_voc
2525

2626
logging.basicConfig()
2727
LOGGER = logging.getLogger(__name__)

pvlib/test/test_singlediode_methods.py renamed to pvlib/test/test_singlediode.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import numpy as np
66
from pvlib import pvsystem
7-
from pvlib.singlediode_methods import bishop88, estimate_voc, VOLTAGE_BUILTIN
7+
from pvlib.singlediode import bishop88, estimate_voc, VOLTAGE_BUILTIN
88
import pytest
99
from conftest import requires_scipy
1010

0 commit comments

Comments
 (0)