Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAINT: DOC: rename singlediode module, and clean up docstring for singlediode function #525

Merged
merged 5 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,11 @@ Low-level functions for solving the single diode equation.
.. autosummary::
:toctree: generated/

singlediode_methods.estimate_voc
singlediode_methods.bishop88
singlediode_methods.bishop88_i_from_v
singlediode_methods.bishop88_v_from_i
singlediode_methods.bishop88_mpp
singlediode.estimate_voc
singlediode.bishop88
singlediode.bishop88_i_from_v
singlediode.bishop88_v_from_i
singlediode.bishop88_mpp

SAPM model
----------
Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Contents
api
comparison_pvlib_matlab
variables_style_rules
singlediode


Indices and tables
Expand Down
114 changes: 114 additions & 0 deletions docs/sphinx/source/singlediode.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
.. _singlediode:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this! Perhaps it deserves its own note in the whatsnew document.

Didn't you make something else (a wiki?) with more information that might belong here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you set up to get readthedocs to render this version? It's pretty easy to do if you're not. Always nice to see that it works when making bigger documentation changes. If you've rendered locally and say it looks good then ok with me.

Copy link
Member Author

@mikofski mikofski Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is a wiki on cell model solutions, but in order to close #518, I just need to move the section on bishop88() out of singlediode(), so I would like to open a separate documentation issue/pr to address moving the material appropriate from the wiki to the documents, and perhaps augmenting that with more detail as needed. Is this okay?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ added note under documentation in what's new:

Added section on single diode equation with some detail on solutions used in
pvlib-python (:issue:518)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's ok with me.


Single Diode Equation
=====================

This section reviews the solutions to the single diode equation used in
pvlib-python to generate an IV curve of a PV module.

pvlib-python supports two ways to solve the single diode equation:

1. Lambert W-Function
2. Bishop's Algorithm

The :func:`pvlib.pvsystem.singlediode` function allows the user to choose the
method using the ``method`` keyword.

Lambert W-Function
------------------
When ``method='lambertw'``, the Lambert W-function is used as previously shown
by Jain, Kapoor [1, 2] and Hansen [3]. The following algorithm can be found on
`Wikipedia: Theory of Solar Cells
<https://en.wikipedia.org/wiki/Theory_of_solar_cells>`_, given the basic single
diode model equation.

.. math::

I = I_L - I_0 \left(\exp \left(\frac{V + I R_s}{n Ns V_{th}} \right) - 1 \right)
- \frac{V + I R_s}{R_{sh}}

Lambert W-function is the inverse of the function
:math:`f \left( w \right) = w \exp \left( w \right)` or
:math:`w = f^{-1} \left( w \exp \left( w \right) \right)` also given as
:math:`w = W \left( w \exp \left( w \right) \right)`. Defining the following
parameter, :math:`z`, is necessary to transform the single diode equation into
a form that can be expressed as a Lambert W-function.

.. math::

z = \frac{R_s I_0}{n Ns V_{th} \left(1 + \frac{R_s}{R_{sh}} \right)} \exp \left(
\frac{R_s \left( I_L + I_0 \right) + V}{n Ns V_{th} \left(1 + \frac{R_s}{R_{sh}}\right)}
\right)

Then the module current can be solved using the Lambert W-function,
:math:`W \left(z \right)`.

.. math::

I = \frac{I_L + I_0 - \frac{V}{R_{sh}}}{1 + \frac{R_s}{R_{sh}}}
- \frac{n Ns V_{th}}{R_s} W \left(z \right)


Bishop's Algorithm
------------------
The function :func:`pvlib.singlediode.bishop88` uses an explicit solution [4]
that finds points on the IV curve by first solving for pairs :math:`(V_d, I)`
where :math:`V_d` is the diode voltage :math:`V_d = V + I*Rs`. Then the voltage
is backed out from :math:`V_d`. Points with specific voltage, such as open
circuit, are located using the bisection search method, ``brentq``, bounded
by a zero diode voltage and an estimate of open circuit voltage given by

.. math::

V_{oc, est} = n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1 \right)

We know that :math:`V_d = 0` corresponds to a voltage less than zero, and
we can also show that when :math:`V_d = V_{oc, est}`, the resulting
current is also negative, meaning that the corresponding voltage must be
in the 4th quadrant and therefore greater than the open circuit voltage
(see proof below). Therefore the entire forward-bias 1st quadrant IV-curve
is bounded because :math:`V_{oc} < V_{oc, est}`, and so a bisection search
between 0 and :math:`V_{oc, est}` will always find any desired condition in the
1st quadrant including :math:`V_{oc}`.

.. math::

I = I_L - I_0 \left(\exp \left(\frac{V_{oc, est}}{n Ns V_{th}} \right) - 1 \right)
- \frac{V_{oc, est}}{R_{sh}} \newline

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)
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline

I = I_L - I_0 \left(\exp \left(\log \left(\frac{I_L}{I_0} + 1 \right) \right) - 1 \right)
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline

I = I_L - I_0 \left(\frac{I_L}{I_0} + 1 - 1 \right)
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline

I = I_L - I_0 \left(\frac{I_L}{I_0} \right)
- \frac{n Ns V_{th} \log \left(\frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline

I = I_L - I_L - \frac{n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1 \right)}{R_{sh}} \newline

I = - \frac{n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1 \right)}{R_{sh}}

References
----------
[1] "Exact analytical solutions of the parameters of real solar cells using
Lambert W-function," A. Jain, A. Kapoor, Solar Energy Materials and Solar Cells,
81, (2004) pp 269-277.
:doi:`10.1016/j.solmat.2003.11.018`

[2] "A new method to determine the diode ideality factor of real solar cell
using Lambert W-function," A. Jain, A. Kapoor, Solar Energy Materials and Solar
Cells, 85, (2005) 391-396.
:doi:`10.1016/j.solmat.2004.05.022`

[3] "Parameter Estimation for Single Diode Models of Photovoltaic Modules,"
Clifford W. Hansen, Sandia `Report SAND2015-2065
<https://prod.sandia.gov/techlib-noauth/access-control.cgi/2015/152065.pdf>`_,
2015 :doi:`10.13140/RG.2.1.4336.7842`

[4] "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`
20 changes: 11 additions & 9 deletions docs/sphinx/source/whatsnew/v0.6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,32 @@ Enhancements
``method`` in ``('lambertw', 'newton', 'brentq')``, default is ``'lambertw'``,
to select a method to solve the single diode equation for points on the IV
curve. Selecting either ``'brentq'`` or ``'newton'`` as the method uses
:func:`~pvlib.singlediode_methods.bishop88` with the corresponding method.
:func:`~pvlib.singlediode.bishop88` with the corresponding method.
(:issue:`410`)
* Implement new methods ``'brentq'`` and ``'newton'`` for solving the single
diode equation for points on the IV curve. ``'brentq'`` uses a bisection
method (Brent, 1973) that may be slow but guarantees a solution. ``'newton'``
uses the Newton-Raphson method and may be faster but is not guaranteed to
converge. However, ``'newton'`` should be safe for well-behaved IV curves.
(:issue:`408`)
* Implement :func:`~pvlib.singlediode_methods.bishop88` for explicit calculation
* Implement :func:`~pvlib.singlediode.bishop88` for explicit calculation
of arbitrary IV curve points using diode voltage instead of cell voltage. If
``method`` is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts`` in
:func:`~pvlib.pvsystem.singlediode` is provided, the IV curve points will be
log spaced instead of linear.
* Implement :func:`~pvlib.singlediode_methods.estimate_voc` to estimate open
* Implement :func:`~pvlib.singlediode.estimate_voc` to estimate open
circuit voltage by assuming :math:`R_{sh} \to \infty` and :math:`R_s=0` as an
upper bound in bisection method for :func:`~pvlib.pvsystem.singlediode` when
method is either ``'newton'`` or ``'brentq'``.
* Add :func:`~pvlib.pvsystem.max_power_point` method to compute the max power
point using the new ``'brentq'`` method.
* Add new module ``pvlib.singlediode_methods`` with low-level functions for
* Add new module ``pvlib.singlediode`` with low-level functions for
solving the single diode equation such as:
:func:`~pvlib.singlediode_methods.bishop88`,
:func:`~pvlib.singlediode_methods.estimate_voc`,
:func:`~pvlib.singlediode_methods.bishop88_i_from_v`,
:func:`~pvlib.singlediode_methods.bishop88_v_from_i`, and
:func:`~pvlib.singlediode_methods.bishop88_mpp`.
:func:`~pvlib.singlediode.bishop88`,
:func:`~pvlib.singlediode.estimate_voc`,
:func:`~pvlib.singlediode.bishop88_i_from_v`,
:func:`~pvlib.singlediode.bishop88_v_from_i`, and
:func:`~pvlib.singlediode.bishop88_mpp`.
* Add PVSyst thin-film recombination losses for CdTe and a:Si (:issue:`163`)
* Python 3.7 officially supported. (:issue:`496`)

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


Testing
Expand Down
2 changes: 1 addition & 1 deletion pvlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
from pvlib import pvsystem
from pvlib import spa
from pvlib import modelchain
from pvlib import singlediode_methods
from pvlib import singlediode
74 changes: 17 additions & 57 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from pvlib.tools import _build_kwargs
from pvlib.location import Location
from pvlib import irradiance, atmosphere
from pvlib import singlediode_methods
import pvlib # use pvlib.singlediode to avoid clash with local method


# not sure if this belongs in the pvsystem module.
Expand Down Expand Up @@ -1963,52 +1963,12 @@ def singlediode(photocurrent, saturation_current, resistance_series,
open-circuit.

If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``
are indicated, then :func:`pvlib.singlediode_methods.bishop88` is used to
are indicated, then :func:`pvlib.singlediode.bishop88` [4] is used to
calculate the points on the IV curve points at diode voltages from zero to
open-circuit voltage with a log spacing that gets closer as voltage
increases. If the method is ``'lambertw'`` then the calculated points on
the IV curve are linearly spaced.

The ``bishop88`` method uses an explicit solution from [4] that finds
points on the IV curve by first solving for pairs :math:`(V_d, I)` where
:math:`V_d` is the diode voltage :math:`V_d = V + I*Rs`. Then the voltage
is backed out from :math:`V_d`. Points with specific voltage, such as open
circuit, are located using the bisection search method, ``brentq``, bounded
by a zero diode voltage and an estimate of open circuit voltage given by

.. math::

V_{oc, est} = n Ns V_{th} \\log \\left( \\frac{I_L}{I_0} + 1 \\right)

We know that :math:`V_d = 0` corresponds to a voltage less than zero, and
we can also show that when :math:`V_d = V_{oc, est}`, the resulting
current is also negative, meaning that the corresponding voltage must be
in the 4th quadrant and therefore greater than the open circuit voltage
(see proof below). Therefore the entire forward-bias 1st quadrant IV-curve
is bounded, and a bisection search within these points will always find
desired condition.

.. math::

I = I_L - I_0 \\left(\\exp \\left(\\frac{V_{oc, est}}{n Ns V_{th}} \\right) - 1 \\right)
- \\frac{V_{oc, est}}{R_{sh}} \\newline

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)
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline

I = I_L - I_0 \\left(\\exp \\left(\\log \\left(\\frac{I_L}{I_0} + 1 \\right) \\right) - 1 \\right)
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline

I = I_L - I_0 \\left(\\frac{I_L}{I_0} + 1 - 1 \\right)
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline

I = I_L - I_0 \\left(\\frac{I_L}{I_0} \\right)
- \\frac{n Ns V_{th} \\log \\left(\\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline

I = I_L - I_L - \\frac{n Ns V_{th} \log \\left( \\frac{I_L}{I_0} + 1 \\right)}{R_{sh}} \\newline

I = - \\frac{n Ns V_{th} \\log \\left( \\frac{I_L}{I_0} + 1 \\right)}{R_{sh}}

References
-----------
[1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN
Expand All @@ -2029,12 +1989,12 @@ def singlediode(photocurrent, saturation_current, resistance_series,
--------
sapm
calcparams_desoto
pvlib.singlediode_methods.bishop88
pvlib.singlediode.bishop88
"""
# Calculate points on the IV curve using the LambertW solution to the
# single diode equation
if method.lower() == 'lambertw':
out = singlediode_methods._lambertw(
out = pvlib.singlediode._lambertw(
photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, ivcurve_pnts
)
Expand All @@ -2047,19 +2007,19 @@ def singlediode(photocurrent, saturation_current, resistance_series,
# 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_methods.bishop88_v_from_i(
v_oc = pvlib.singlediode.bishop88_v_from_i(
0.0, *args, method=method.lower()
)
i_mp, v_mp, p_mp = singlediode_methods.bishop88_mpp(
i_mp, v_mp, p_mp = pvlib.singlediode.bishop88_mpp(
*args, method=method.lower()
)
i_sc = singlediode_methods.bishop88_i_from_v(
i_sc = pvlib.singlediode.bishop88_i_from_v(
0.0, *args, method=method.lower()
)
i_x = singlediode_methods.bishop88_i_from_v(
i_x = pvlib.singlediode.bishop88_i_from_v(
v_oc / 2.0, *args, method=method.lower()
)
i_xx = singlediode_methods.bishop88_i_from_v(
i_xx = pvlib.singlediode.bishop88_i_from_v(
(v_oc + v_mp) / 2.0, *args, method=method.lower()
)

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

out = OrderedDict()
out['i_sc'] = i_sc
Expand Down Expand Up @@ -2125,7 +2085,7 @@ def max_power_point(photocurrent, saturation_current, resistance_series,
curve. This function uses Brent's method by default because it is
guaranteed to converge.
"""
i_mp, v_mp, p_mp = singlediode_methods.bishop88_mpp(
i_mp, v_mp, p_mp = pvlib.singlediode.bishop88_mpp(
photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth, method=method.lower()
)
Expand Down Expand Up @@ -2205,7 +2165,7 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
Energy Materials and Solar Cells, 81 (2004) 269-277.
'''
if method.lower() == 'lambertw':
return singlediode_methods._lambertw_v_from_i(
return pvlib.singlediode._lambertw_v_from_i(
resistance_shunt, resistance_series, nNsVth, current,
saturation_current, photocurrent
)
Expand All @@ -2215,9 +2175,9 @@ def v_from_i(resistance_shunt, resistance_series, nNsVth, current,
# equation for the diode voltage V_d then backing out voltage
args = (current, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth)
V = singlediode_methods.bishop88_v_from_i(*args, method=method.lower())
V = pvlib.singlediode.bishop88_v_from_i(*args, method=method.lower())
# find the right size and shape for returns
size, shape = singlediode_methods._get_size_and_shape(args)
size, shape = pvlib.singlediode._get_size_and_shape(args)
if size <= 1:
if shape is not None:
V = np.tile(V, shape)
Expand Down Expand Up @@ -2293,7 +2253,7 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
Energy Materials and Solar Cells, 81 (2004) 269-277.
'''
if method.lower() == 'lambertw':
return singlediode_methods._lambertw_i_from_v(
return pvlib.singlediode._lambertw_i_from_v(
resistance_shunt, resistance_series, nNsVth, voltage,
saturation_current, photocurrent
)
Expand All @@ -2303,9 +2263,9 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
# equation for the diode voltage V_d then backing out voltage
args = (voltage, photocurrent, saturation_current, resistance_series,
resistance_shunt, nNsVth)
I = singlediode_methods.bishop88_i_from_v(*args, method=method.lower())
I = pvlib.singlediode.bishop88_i_from_v(*args, method=method.lower())
# find the right size and shape for returns
size, shape = singlediode_methods._get_size_and_shape(args)
size, shape = pvlib.singlediode._get_size_and_shape(args)
if size <= 1:
if shape is not None:
I = np.tile(I, shape)
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions pvlib/test/test_numerical_precision.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

This module can be executed from the command line to generate a high precision
dataset of I-V curve points to test the explicit single diode calculations
:func:`pvlib.singlediode_methods.bishop88`::
:func:`pvlib.singlediode.bishop88`::

$ python test_numeric_precision.py

This generates a file in the pvlib data folder, which is specified by the
constant ``DATA_PATH``. When the test is run using ``pytest`` it will compare
the values calculated by :func:`pvlib.singlediode_methods.bishop88` with the
the values calculated by :func:`pvlib.singlediode.bishop88` with the
high-precision values generated with SymPy.
"""

Expand All @@ -21,7 +21,7 @@
import numpy as np
import pandas as pd
from pvlib import pvsystem
from pvlib.singlediode_methods import bishop88, estimate_voc
from pvlib.singlediode import bishop88, estimate_voc

logging.basicConfig()
LOGGER = logging.getLogger(__name__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import numpy as np
from pvlib import pvsystem
from pvlib.singlediode_methods import bishop88, estimate_voc, VOLTAGE_BUILTIN
from pvlib.singlediode import bishop88, estimate_voc, VOLTAGE_BUILTIN
import pytest
from conftest import requires_scipy

Expand Down
Loading