Skip to content

Commit 1dc4266

Browse files
committed
update singlediode etc for var names. add tests
1 parent 36871d2 commit 1dc4266

File tree

2 files changed

+181
-119
lines changed

2 files changed

+181
-119
lines changed

pvlib/pvsystem.py

Lines changed: 158 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,8 @@ def sapm_celltemp(irrad, wind, temp, model='open_rack_cell_glassback'):
807807
return pd.DataFrame({'temp_cell': temp_cell, 'temp_module': temp_module})
808808

809809

810-
def singlediode(module, IL, I0, Rs, Rsh, nNsVth, **kwargs):
810+
def singlediode(module, photocurrent, saturation_current,
811+
resistance_series, resistance_shunt, nNsVth):
811812
'''
812813
Solve the single-diode model to obtain a photovoltaic IV curve.
813814
@@ -822,65 +823,56 @@ def singlediode(module, IL, I0, Rs, Rsh, nNsVth, **kwargs):
822823
are described later. Returns a DataFrame which contains
823824
the 5 points on the I-V curve specified in SAND2004-3535 [3].
824825
If all IL, I0, Rs, Rsh, and nNsVth are scalar, a single curve
825-
will be returned, if any are DataFrames (of the same length), multiple IV
826+
will be returned, if any are Series (of the same length), multiple IV
826827
curves will be calculated.
827828
828829
The input parameters can be calculated using calcparams_desoto from
829-
meterological data.
830+
meteorological data.
830831
831832
Parameters
832833
----------
833834
module : DataFrame
834835
A DataFrame defining the SAPM performance parameters.
835836
836-
IL : float or DataFrame
837+
photocurrent : float or Series
837838
Light-generated current (photocurrent) in amperes under desired IV
838-
curve conditions.
839+
curve conditions. Often abbreviated ``I_L``.
839840
840-
I0 : float or DataFrame
841+
saturation_current : float or Series
841842
Diode saturation current in amperes under desired IV curve
842-
conditions.
843+
conditions. Often abbreviated ``I_0``.
843844
844-
Rs : float or DataFrame
845-
Series resistance in ohms under desired IV curve conditions.
845+
resistance_series : float or Series
846+
Series resistance in ohms under desired IV curve conditions.
847+
Often abbreviated ``Rs``.
846848
847-
Rsh : float or DataFrame
848-
Shunt resistance in ohms under desired IV curve conditions. May
849-
be a scalar or DataFrame, but DataFrames must be of same length as all
850-
other input DataFrames.
849+
resistance_shunt : float or Series
850+
Shunt resistance in ohms under desired IV curve conditions.
851+
Often abbreviated ``Rsh``.
851852
852-
nNsVth : float or DataFrame
853+
nNsVth : float or Series
853854
The product of three components. 1) The usual diode ideal
854855
factor (n), 2) the number of cells in series (Ns), and 3) the cell
855856
thermal voltage under the desired IV curve conditions (Vth).
856857
The thermal voltage of the cell (in volts) may be calculated as
857-
k*Tcell/q, where k is Boltzmann's constant (J/K), Tcell is the
858-
temperature of the p-n junction in Kelvin, and q is the elementary
859-
charge of an electron (coulombs).
860-
861-
Other Parameters
862-
----------------
863-
NumPoints : integer
864-
Number of points in the desired IV curve (optional). Must be a finite
865-
scalar value. Non-integer values will be rounded to the next highest
866-
integer (ceil). If ceil(NumPoints) is < 2, no IV curves will be produced
867-
(i.e. Result.V and Result.I will not be generated). The default
868-
value is 0, resulting in no calculation of IV points other than
869-
those specified in [3].
858+
``k*temp_cell/q``, where k is Boltzmann's constant (J/K),
859+
temp_cell is the temperature of the p-n junction in Kelvin,
860+
and q is the charge of an electron (coulombs).
870861
871862
Returns
872863
-------
873-
A DataFrame with the following fields. All fields have the
874-
same number of rows as the largest input DataFrame:
864+
If ``photocurrent`` is a Series, a DataFrame with the following columns.
865+
All columns have the same number of rows as the largest input DataFrame.
875866
876-
* Result.Isc - short circuit current in amperes.
877-
* Result.Voc - open circuit voltage in volts.
878-
* Result.Imp - current at maximum power point in amperes.
879-
* Result.Vmp - voltage at maximum power point in volts.
880-
* Result.Pmp - power at maximum power point in watts.
881-
* Result.Ix - current, in amperes, at V = 0.5*Voc.
882-
* Result.Ixx - current, in amperes, at V = 0.5*(Voc+Vmp).
883-
867+
If ``photocurrent`` is a scalar, a dict with the following keys.
868+
869+
* i_sc - short circuit current in amperes.
870+
* v_oc - open circuit voltage in volts.
871+
* i_mp - current at maximum power point in amperes.
872+
* v_mp - voltage at maximum power point in volts.
873+
* p_mp - power at maximum power point in watts.
874+
* i_x - current, in amperes, at ``v = 0.5*v_oc``.
875+
* i_xx - current, in amperes, at ``V = 0.5*(v_oc+v_mp)``.
884876
885877
Notes
886878
-----
@@ -905,39 +897,37 @@ def singlediode(module, IL, I0, Rs, Rsh, nNsVth, **kwargs):
905897
sapm
906898
calcparams_desoto
907899
'''
900+
pvl_logger.debug('pvsystem.singlediode')
901+
902+
# Find short circuit current using Lambert W
903+
i_sc = i_from_v(resistance_shunt, resistance_series, nNsVth, 0.01,
904+
saturation_current, photocurrent)
905+
906+
params = {'r_sh': resistance_shunt,
907+
'r_s': resistance_series,
908+
'nNsVth': nNsVth,
909+
'i_0': saturation_current,
910+
'i_l': photocurrent}
911+
912+
__, v_oc = _golden_sect_DataFrame(params, 0, module['V_oc_ref']*1.6,
913+
_v_oc_optfcn)
908914

909-
# Find Isc using Lambert W
910-
Isc = I_from_V(Rsh=Rsh, Rs=Rs, nNsVth=nNsVth, V=0.01, I0=I0, IL=IL)
911-
912-
# If passed a dataframe, output a dataframe, if passed a list or scalar,
913-
# return a dict
914-
if isinstance(Rsh, pd.Series):
915-
DFOut = pd.DataFrame({'Isc': Isc})
916-
DFOut.index = Rsh.index
917-
else:
918-
DFOut = {'Isc': Isc}
919-
920-
DFOut['Rsh'] = Rsh
921-
DFOut['Rs'] = Rs
922-
DFOut['nNsVth'] = nNsVth
923-
DFOut['I0'] = I0
924-
DFOut['IL'] = IL
925-
926-
__, Voc_return = _golden_sect_DataFrame(DFOut, 0, module.V_oc_ref*1.6,
927-
_Voc_optfcn)
928-
Voc = Voc_return.copy()
929-
930-
Pmp, Vmax = _golden_sect_DataFrame(DFOut, 0, module.V_oc_ref*1.14,
931-
_pwr_optfcn)
932-
Imax = I_from_V(Rsh=Rsh, Rs=Rs, nNsVth=nNsVth, V=Vmax, I0=I0, IL=IL)
915+
p_mp, v_mp = _golden_sect_DataFrame(params, 0, module['V_oc_ref']*1.14,
916+
_pwr_optfcn)
917+
933918
# Invert the Power-Current curve. Find the current where the inverted power
934-
# is minimized. This is Imax. Start the optimization at Voc/2
919+
# is minimized. This is i_mp. Start the optimization at v_oc/2
920+
i_mp = i_from_v(resistance_shunt, resistance_series, nNsVth, v_mp,
921+
saturation_current, photocurrent)
935922

936923
# Find Ix and Ixx using Lambert W
937-
Ix = I_from_V(Rsh=Rsh, Rs=Rs, nNsVth=nNsVth, V=.5*Voc, I0=I0, IL=IL)
938-
Ixx = I_from_V(Rsh=Rsh, Rs=Rs, nNsVth=nNsVth, V=0.5*(Voc+Vmax), I0=I0,
939-
IL=IL)
924+
i_x = i_from_v(resistance_shunt, resistance_series, nNsVth,
925+
0.5*v_oc, saturation_current, photocurrent)
940926

927+
i_xx = i_from_v(resistance_shunt, resistance_series, nNsVth,
928+
0.5*(v_oc+v_mp), saturation_current, photocurrent)
929+
930+
# @wholmgren: need to move this stuff to a different function
941931
# If the user says they want a curve of with number of points equal to
942932
# NumPoints (must be >=2), then create a voltage array where voltage is
943933
# zero in the first column, and Voc in the last column. Number of columns
@@ -952,30 +942,37 @@ def singlediode(module, IL, I0, Rs, Rsh, nNsVth, **kwargs):
952942
# Result.I = I_from_V(Rsh*s, Rs*s, nNsVth*s, Result.V, I0*s, IL*s);
953943
# end
954944

955-
DFOut['Imp'] = Imax
956-
DFOut['Voc'] = Voc
957-
DFOut['Vmp'] = Vmax
958-
DFOut['Pmp'] = Pmp
959-
DFOut['Ix'] = Ix
960-
DFOut['Ixx'] = Ixx
945+
dfout = {}
946+
dfout['i_sc'] = i_sc
947+
dfout['i_mp'] = i_mp
948+
dfout['v_oc'] = v_oc
949+
dfout['v_mp'] = v_mp
950+
dfout['p_mp'] = p_mp
951+
dfout['i_x'] = i_x
952+
dfout['i_xx'] = i_xx
953+
954+
try:
955+
dfout = pd.DataFrame(dfout, index=photocurrent.index)
956+
except AttributeError:
957+
pass
961958

962-
return DFOut
959+
return dfout
963960

964961

965962
# Created April,2014
966963
# Author: Rob Andrews, Calama Consulting
967964

968-
def _golden_sect_DataFrame(df, VL, VH, func):
965+
def _golden_sect_DataFrame(params, VL, VH, func):
969966
'''
970967
Vectorized golden section search for finding MPPT
971968
from a dataframe timeseries.
972969
973970
Parameters
974971
----------
975-
df : DataFrame
976-
Dataframe containing a timeseries of inputs to the function
977-
to be optimized.
978-
Each row should represent an independant optimization
972+
params : dict
973+
Dictionary containing scalars or arrays
974+
of inputs to the function to be optimized.
975+
Each row should represent an independent optimization.
979976
980977
VL: float
981978
Lower bound of the optimization
@@ -984,7 +981,7 @@ def _golden_sect_DataFrame(df, VL, VH, func):
984981
Upper bound of the optimization
985982
986983
func: function
987-
Function to be optimized must be in the form f(dataframe, x)
984+
Function to be optimized must be in the form f(array-like, x)
988985
989986
Returns
990987
-------
@@ -998,77 +995,122 @@ def _golden_sect_DataFrame(df, VL, VH, func):
998995
-----
999996
This funtion will find the MAXIMUM of a function
1000997
'''
1001-
1002-
df['VH']=VH
1003-
df['VL']=VL
998+
999+
df = params
1000+
df['VH'] = VH
1001+
df['VL'] = VL
10041002

1005-
err=df['VH']-df['VL']
1006-
errflag=True
1007-
iterations=0
1003+
err = df['VH'] - df['VL']
1004+
errflag = True
1005+
iterations = 0
1006+
10081007
while errflag:
10091008

1010-
phi=(np.sqrt(5)-1)/2*(df['VH']-df['VL'])
1011-
df['V1']=df['VL']+phi
1012-
df['V2']=df['VH']-phi
1009+
phi = (np.sqrt(5)-1)/2*(df['VH']-df['VL'])
1010+
df['V1'] = df['VL'] + phi
1011+
df['V2'] = df['VH'] - phi
10131012

1014-
df['f1']=func(df,'V1')
1015-
df['f2']=func(df,'V2')
1016-
df['SW_Flag']=df['f1']>df['f2']
1013+
df['f1'] = func(df, 'V1')
1014+
df['f2'] = func(df, 'V2')
1015+
df['SW_Flag'] = df['f1'] > df['f2']
10171016

1018-
df['VL']=df['V2']*df['SW_Flag']+df['VL']*(~df['SW_Flag'])
1019-
df['VH']=df['V1']*~df['SW_Flag']+df['VH']*(df['SW_Flag'])
1017+
df['VL'] = df['V2']*df['SW_Flag'] + df['VL']*(~df['SW_Flag'])
1018+
df['VH'] = df['V1']*~df['SW_Flag'] + df['VH']*(df['SW_Flag'])
10201019

1021-
err=(df['V1']-df['V2'])
1022-
if isinstance(df,pd.DataFrame):
1023-
errflag=all(abs(err)>.01)
1024-
else:
1025-
errflag=(abs(err)>.01)
1020+
err = df['V1'] - df['V2']
1021+
try:
1022+
errflag = (abs(err)>.01).all()
1023+
except ValueError:
1024+
errflag = (abs(err)>.01)
10261025

1027-
iterations=iterations+1
1026+
iterations += 1
10281027

1029-
if iterations >50:
1028+
if iterations > 50:
10301029
raise Exception("EXCEPTION:iterations exeeded maximum (50)")
10311030

1032-
return func(df,'V1') , df['V1']
1031+
return func(df, 'V1'), df['V1']
10331032

10341033

10351034
def _pwr_optfcn(df, loc):
10361035
'''
1037-
Function to find power from I_from_V.
1036+
Function to find power from ``i_from_v``.
10381037
'''
10391038

1040-
I = I_from_V(Rsh=df['Rsh'], Rs=df['Rs'], nNsVth=df['nNsVth'], V=df[loc],
1041-
I0=df['I0'], IL=df['IL'])
1039+
I = i_from_v(df['r_sh'], df['r_s'], df['nNsVth'],
1040+
df[loc], df['i_0'], df['i_l'])
10421041
return I*df[loc]
10431042

10441043

1045-
def _Voc_optfcn(df, loc):
1044+
def _v_oc_optfcn(df, loc):
10461045
'''
1047-
Function to find V_oc from I_from_V.
1046+
Function to find the open circuit voltage from ``i_from_v``.
10481047
'''
1049-
I = -abs(I_from_V(Rsh=df['Rsh'], Rs=df['Rs'], nNsVth=df['nNsVth'],
1050-
V=df[loc], I0=df['I0'], IL=df['IL']))
1048+
I = -abs(i_from_v(df['r_sh'], df['r_s'], df['nNsVth'],
1049+
df[loc], df['i_0'], df['i_l']))
10511050
return I
10521051

10531052

1054-
def I_from_V(Rsh, Rs, nNsVth, V, I0, IL):
1053+
def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
1054+
saturation_current, photocurrent):
10551055
'''
1056-
Calculates I from V per Eq 2 Jain and Kapoor 2004
1057-
uses Lambert W implemented in wapr_vec.m
1058-
Rsh, nVth, V, I0, IL can all be DataFrames
1059-
Rs can be a DataFrame, but should be a scalar.
1056+
Calculates current from voltage per Eq 2 Jain and Kapoor 2004 [1].
1057+
1058+
Parameters
1059+
----------
1060+
resistance_series : float or Series
1061+
Series resistance in ohms under desired IV curve conditions.
1062+
Often abbreviated ``Rs``.
1063+
1064+
resistance_shunt : float or Series
1065+
Shunt resistance in ohms under desired IV curve conditions.
1066+
Often abbreviated ``Rsh``.
1067+
1068+
saturation_current : float or Series
1069+
Diode saturation current in amperes under desired IV curve
1070+
conditions. Often abbreviated ``I_0``.
1071+
1072+
nNsVth : float or Series
1073+
The product of three components. 1) The usual diode ideal
1074+
factor (n), 2) the number of cells in series (Ns), and 3) the cell
1075+
thermal voltage under the desired IV curve conditions (Vth).
1076+
The thermal voltage of the cell (in volts) may be calculated as
1077+
``k*temp_cell/q``, where k is Boltzmann's constant (J/K),
1078+
temp_cell is the temperature of the p-n junction in Kelvin,
1079+
and q is the charge of an electron (coulombs).
1080+
1081+
photocurrent : float or Series
1082+
Light-generated current (photocurrent) in amperes under desired IV
1083+
curve conditions. Often abbreviated ``I_L``.
1084+
1085+
Returns
1086+
-------
1087+
current : np.array
1088+
1089+
References
1090+
----------
1091+
[1] A. Jain, A. Kapoor, "Exact analytical solutions of the parameters of
1092+
real solar cells using Lambert W-function", Solar Energy Materials
1093+
and Solar Cells, 81 (2004) 269-277.
10601094
'''
10611095
try:
10621096
from scipy.special import lambertw
10631097
except ImportError:
10641098
raise ImportError('This function requires scipy')
10651099

1066-
argW = (Rs*I0*Rsh * np.exp(Rsh*(Rs*(IL+I0)+V) /
1067-
(nNsVth*(Rs+Rsh))) / (nNsVth*(Rs + Rsh)) )
1068-
inputterm = lambertw(argW)
1100+
Rsh = resistance_shunt
1101+
Rs = resistance_series
1102+
I0 = saturation_current
1103+
IL = photocurrent
1104+
V = voltage
1105+
1106+
argW = (Rs*I0*Rsh *
1107+
np.exp( Rsh*(Rs*(IL+I0)+V) / (nNsVth*(Rs+Rsh)) ) /
1108+
(nNsVth*(Rs + Rsh)) )
1109+
lambertwterm = lambertw(argW)
1110+
pvl_logger.debug('argW: {}, lambertwterm{}'.format(argW, lambertwterm))
10691111

10701112
# Eqn. 4 in Jain and Kapoor, 2004
1071-
I = -V/(Rs + Rsh) - (nNsVth/Rs) * inputterm + Rsh*(IL + I0)/(Rs + Rsh)
1113+
I = -V/(Rs + Rsh) - (nNsVth/Rs)*lambertwterm + Rsh*(IL + I0)/(Rs + Rsh)
10721114

10731115
return I.real
10741116

0 commit comments

Comments
 (0)