-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ENH: add methods and tests for a explicit IV curve calculation of single-diode model #409
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
Changes from 29 commits
22d2e5c
bb7c49a
36d4f78
817a303
b17a8cd
0666d29
595e254
5ade588
7179096
4b66f87
664d7d8
09a9e14
06be522
1aba56a
f09be91
4905363
a3d2bb4
8ee1b94
c9a893a
c5248bb
68c65c3
41e0c83
d7b6e62
aa3d29a
9fc350d
54e8d18
16ad9b4
7aca302
086e73f
9f2b157
555e946
52a8e88
c0e18a5
09c6a75
ff8cc0b
446fa9e
f976f61
db88022
a509dd3
62010c2
5c939b8
66a801d
df1423b
eb20cf4
e3805ef
5f89578
ba84fcf
0f3893c
d6023b1
80b3352
ef20676
98d1c01
4ecd913
bf05d2d
edc445d
f542d15
cc6c976
22c53fc
442a3d4
6bcffe8
e6b60c6
0fc9c83
fc6cee1
28c8ffb
58361c1
5f9ed41
43475cc
c79ab97
1ad6031
f14ba04
8d86560
337d7b4
ce5b0f5
3e009f8
082dfd5
ceb69cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,10 @@ | |
from pvlib.tools import _build_kwargs | ||
from pvlib.location import Location | ||
from pvlib import irradiance, atmosphere | ||
from pvlib import way_faster | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a fan of this module name. Way faster what exactly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ changed it to |
||
|
||
bishop88 = way_faster.bishop88 | ||
est_voc = way_faster.est_voc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this use case, I view this more as an upper bound on Voc, rather than an estimate. |
||
|
||
|
||
# not sure if this belongs in the pvsystem module. | ||
|
@@ -1571,7 +1575,7 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi, | |
|
||
|
||
def singlediode(photocurrent, saturation_current, resistance_series, | ||
resistance_shunt, nNsVth, ivcurve_pnts=None): | ||
resistance_shunt, nNsVth, ivcurve_pnts=None, method=''): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the default method should be given a unique name. Something like 'gold' or 'robust' (which then refer to a particular method), or perhaps just the name of the default method. This method could change in the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ change it to "gold" but anything that is not "fast" or "lambertw" will be run using the default |
||
r''' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mikofski can you confirm that the equation still renders properly? If I remember correctly, I added r to some of the doc strings at one point because it was necessary for the equations to render properly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the check. That sounds familiar. Maybe I added "r" in a few places because I thought it was easier to do than escape the backslashes. One could argue that it improves readability too, but let's not fret over it. |
||
Solve the single-diode model to obtain a photovoltaic IV curve. | ||
|
||
|
@@ -1627,6 +1631,13 @@ def singlediode(photocurrent, saturation_current, resistance_series, | |
Number of points in the desired IV curve. If None or 0, no | ||
IV curves will be produced. | ||
|
||
method : str, default '' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In ivcurve_pnts above this line, we should specify that the points are between Isc and Voc, inclusive, and linear-spaced (or log-spaced, see further comments). |
||
Determines the method used to calculate IV curve and points. If | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'calculate points on the IV curve' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
'lambertw' then ``lambertw`` is used. If 'fast' then ``newton`` is | ||
used. Otherwise the problem is bounded between zero and open-circuit | ||
voltage and a bisection method, ``brentq``, is used, that guarantees | ||
convergence. | ||
|
||
Returns | ||
------- | ||
OrderedDict or DataFrame | ||
|
@@ -1655,9 +1666,48 @@ def singlediode(photocurrent, saturation_current, resistance_series, | |
|
||
Notes | ||
----- | ||
The solution employed to solve the implicit diode equation utilizes | ||
the Lambert W function to obtain an explicit function of V=f(i) and | ||
I=f(V) as shown in [2]. | ||
The default method employed is an explicit solution using [4] to find an | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More editing. Move this description of The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
arbitrary point on the IV curve as a function of the diode voltage | ||
:math:`V_d = V + I*Rs`. Then the voltage is backed out from :math:`V_d`. | ||
A specific desired point, such as short circuit current or max power, is | ||
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}} \\ | ||
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}} \\ | ||
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}} \\ | ||
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}} \\ | ||
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}} \\ | ||
I = I_L - I_L - \frac{n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1\right)}{R_{sh}} \\ | ||
I = - \frac{n Ns V_{th} \log \left( \frac{I_L}{I_0} + 1\right)}{R_{sh}} | ||
|
||
If ``method.lower() == 'fast'`` then a gradient descent method, ``newton``, | ||
is used to solve the implicit diode equation. It should be safe for well | ||
behaved IV-curves, but the default method is recommended for reliability, | ||
it is often just as fast. | ||
|
||
If either the "fast" or default methods are indicated, then | ||
:func:`pvlib.pvsystem.bishop88` is used to calculate the points at diode | ||
voltages from zero to open-circuit voltage with a log spacing so that | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider making it an option spacing='log' (with default spacing='linear'), or at the very least call it out this change in Equal spacing by arc length would be another cool option to explore later! |
||
points get closer as they approach the open-circuit voltage. | ||
|
||
If ``method.lower() == 'lambertw'`` then the solution employed to solve the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
implicit diode equation utilizes the Lambert W function to obtain an | ||
explicit function of V=f(i) and I=f(V) as shown in [2]. | ||
|
||
References | ||
----------- | ||
|
@@ -1671,63 +1721,145 @@ def singlediode(photocurrent, saturation_current, resistance_series, | |
[3] D. King et al, "Sandia Photovoltaic Array Performance Model", | ||
SAND2004-3535, Sandia National Laboratories, Albuquerque, NM | ||
|
||
[4] "Computer simulation of the effects of electrical mismatches in | ||
photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988) | ||
https://doi.org/10.1016/0379-6787(88)90059-2 | ||
|
||
See also | ||
-------- | ||
sapm | ||
calcparams_desoto | ||
''' | ||
|
||
# Compute short circuit current | ||
i_sc = i_from_v(resistance_shunt, resistance_series, nNsVth, 0., | ||
saturation_current, photocurrent) | ||
if method.lower() == 'lambertw': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a lot of lines to read within an if/else... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we'll handle this in another PR see #497 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ moved to private function in |
||
# Compute short circuit current | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment block here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ |
||
i_sc = i_from_v(resistance_shunt, resistance_series, nNsVth, 0., | ||
saturation_current, photocurrent) | ||
|
||
# Compute open circuit voltage | ||
v_oc = v_from_i(resistance_shunt, resistance_series, nNsVth, 0., | ||
saturation_current, photocurrent) | ||
|
||
# Compute open circuit voltage | ||
v_oc = v_from_i(resistance_shunt, resistance_series, nNsVth, 0., | ||
saturation_current, photocurrent) | ||
params = {'r_sh': resistance_shunt, | ||
'r_s': resistance_series, | ||
'nNsVth': nNsVth, | ||
'i_0': saturation_current, | ||
'i_l': photocurrent} | ||
|
||
params = {'r_sh': resistance_shunt, | ||
'r_s': resistance_series, | ||
'nNsVth': nNsVth, | ||
'i_0': saturation_current, | ||
'i_l': photocurrent} | ||
p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, _pwr_optfcn) | ||
|
||
p_mp, v_mp = _golden_sect_DataFrame(params, 0., v_oc * 1.14, _pwr_optfcn) | ||
# Invert the Power-Current curve. Find the current where the inverted power | ||
# is minimized. This is i_mp. Start the optimization at v_oc/2 | ||
i_mp = i_from_v(resistance_shunt, resistance_series, nNsVth, v_mp, | ||
saturation_current, photocurrent) | ||
|
||
# Invert the Power-Current curve. Find the current where the inverted power | ||
# is minimized. This is i_mp. Start the optimization at v_oc/2 | ||
i_mp = i_from_v(resistance_shunt, resistance_series, nNsVth, v_mp, | ||
saturation_current, photocurrent) | ||
# Find Ix and Ixx using Lambert W | ||
i_x = i_from_v(resistance_shunt, resistance_series, nNsVth, 0.5 * v_oc, | ||
saturation_current, photocurrent) | ||
|
||
# Find Ix and Ixx using Lambert W | ||
i_x = i_from_v(resistance_shunt, resistance_series, nNsVth, 0.5 * v_oc, | ||
saturation_current, photocurrent) | ||
i_xx = i_from_v(resistance_shunt, resistance_series, nNsVth, | ||
0.5 * (v_oc + v_mp), saturation_current, photocurrent) | ||
|
||
i_xx = i_from_v(resistance_shunt, resistance_series, nNsVth, | ||
0.5 * (v_oc + v_mp), saturation_current, photocurrent) | ||
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 | ||
|
||
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 | ||
# create ivcurve | ||
if ivcurve_pnts: | ||
ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] * | ||
np.linspace(0, 1, ivcurve_pnts)) | ||
|
||
# create ivcurve | ||
if ivcurve_pnts: | ||
ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] * | ||
np.linspace(0, 1, ivcurve_pnts)) | ||
ivcurve_i = i_from_v(resistance_shunt, resistance_series, nNsVth, | ||
ivcurve_v.T, saturation_current, photocurrent).T | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a problem from this PR, but I wish this expression and the previous were commented a bit better, esp. w.r.t. array manipulations and resulting dimensions. |
||
|
||
ivcurve_i = i_from_v(resistance_shunt, resistance_series, nNsVth, | ||
ivcurve_v.T, saturation_current, photocurrent).T | ||
out['v'] = ivcurve_v | ||
out['i'] = ivcurve_i | ||
|
||
out['v'] = ivcurve_v | ||
out['i'] = ivcurve_i | ||
if isinstance(photocurrent, pd.Series) and not ivcurve_pnts: | ||
out = pd.DataFrame(out, index=photocurrent.index) | ||
|
||
if isinstance(photocurrent, pd.Series) and not ivcurve_pnts: | ||
out = pd.DataFrame(out, index=photocurrent.index) | ||
else: | ||
if method.lower() == 'fast': | ||
sdm_fun = way_faster.faster_way | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps module name and function names should be made more representative of methods employed instead of relative performance to current implementation. |
||
else: | ||
sdm_fun = way_faster.slower_way | ||
try: | ||
len(photocurrent) | ||
except TypeError: | ||
out = sdm_fun( | ||
photocurrent, saturation_current, resistance_series, | ||
resistance_shunt, nNsVth, ivcurve_pnts | ||
) | ||
else: | ||
vecfun = np.vectorize(sdm_fun) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps numba can work wonders here in a followup. |
||
out = vecfun(photocurrent, saturation_current, resistance_series, | ||
resistance_shunt, nNsVth, ivcurve_pnts) | ||
if isinstance(photocurrent, pd.Series) and not ivcurve_pnts: | ||
out = pd.DataFrame(out.tolist(), index=photocurrent.index) | ||
else: | ||
out_array = pd.DataFrame(out.tolist()) | ||
out = OrderedDict() | ||
out['i_sc'] = out_array.i_sc.values | ||
out['v_oc'] = out_array.v_oc.values | ||
out['i_mp'] = out_array.i_mp.values | ||
out['v_mp'] = out_array.v_mp.values | ||
out['p_mp'] = out_array.p_mp.values | ||
out['i_x'] = out_array.i_x.values | ||
out['i_xx'] = out_array.i_xx.values | ||
if ivcurve_pnts: | ||
out['i'] = np.vstack(out_array.i.values) | ||
out['v'] = np.vstack(out_array.v.values) | ||
out['p'] = np.vstack(out_array.p.values) | ||
return out | ||
|
||
|
||
def mppt(photocurrent, saturation_current, resistance_series, resistance_shunt, | ||
nNsVth, method=''): | ||
""" | ||
Max power point tracker. Given the calculated DeSoto parameters calculates | ||
the maximum power point (MPP). | ||
|
||
:param numeric photocurrent: photo-generated current [A] | ||
:param numeric saturation_current: diode one reverse saturation current [A] | ||
:param numeric resistance_series: series resitance [ohms] | ||
:param numeric resistance_shunt: shunt resitance [ohms] | ||
:param numeric nNsVth: product of thermal voltage ``Vth`` [V], diode | ||
:param str method: if "fast" then use Newton, otherwise use bisection | ||
:returns: ``OrderedDict`` or ``pandas.Datafrane`` with ``i_mp``, ``v_mp``, | ||
and ``p_mp`` | ||
""" | ||
if method.lower() == 'fast': | ||
mppt_func = way_faster.fast_mppt | ||
else: | ||
mppt_func = way_faster.slow_mppt | ||
try: | ||
len(photocurrent) | ||
except TypeError: | ||
i_mp, v_mp, p_mp = mppt_func( | ||
photocurrent, saturation_current, resistance_series, | ||
resistance_shunt, nNsVth | ||
) | ||
out = OrderedDict() | ||
out['i_mp'] = i_mp | ||
out['v_mp'] = v_mp | ||
out['p_mp'] = p_mp | ||
else: | ||
vecfun = np.vectorize(mppt_func) | ||
ivp = vecfun(photocurrent, saturation_current, resistance_series, | ||
resistance_shunt, nNsVth) | ||
if isinstance(photocurrent, pd.Series): | ||
ivp = {k: v for k, v in zip(('i_mp', 'v_mp', 'p_mp'), ivp)} | ||
out = pd.DataFrame(ivp, index=photocurrent.index) | ||
else: | ||
out = OrderedDict() | ||
out['i_mp'] = ivp[0] | ||
out['v_mp'] = ivp[1] | ||
out['p_mp'] = ivp[2] | ||
return out | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
descent (but also decent!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️