pvlib 0.12.0__py3-none-any.whl → 0.12.1a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pvlib/atmosphere.py +40 -31
- pvlib/iotools/__init__.py +6 -0
- pvlib/iotools/psm4.py +819 -0
- pvlib/irradiance.py +24 -15
- pvlib/ivtools/sdm/__init__.py +20 -0
- pvlib/ivtools/sdm/_fit_desoto_pvsyst_sandia.py +585 -0
- pvlib/ivtools/sdm/cec.py +93 -0
- pvlib/ivtools/sdm/desoto.py +401 -0
- pvlib/ivtools/sdm/pvsyst.py +630 -0
- pvlib/modelchain.py +1 -1
- pvlib/pvsystem.py +109 -57
- pvlib/spectrum/irradiance.py +2 -1
- {pvlib-0.12.0.dist-info → pvlib-0.12.1a1.dist-info}/METADATA +2 -3
- {pvlib-0.12.0.dist-info → pvlib-0.12.1a1.dist-info}/RECORD +18 -13
- {pvlib-0.12.0.dist-info → pvlib-0.12.1a1.dist-info}/WHEEL +1 -1
- pvlib/ivtools/sdm.py +0 -1379
- {pvlib-0.12.0.dist-info → pvlib-0.12.1a1.dist-info}/licenses/AUTHORS.md +0 -0
- {pvlib-0.12.0.dist-info → pvlib-0.12.1a1.dist-info}/licenses/LICENSE +0 -0
- {pvlib-0.12.0.dist-info → pvlib-0.12.1a1.dist-info}/top_level.txt +0 -0
pvlib/irradiance.py
CHANGED
|
@@ -35,25 +35,30 @@ def get_extra_radiation(datetime_or_doy, solar_constant=1366.1,
|
|
|
35
35
|
"""
|
|
36
36
|
Determine extraterrestrial radiation from day of year.
|
|
37
37
|
|
|
38
|
+
Specific references for each method are cited in the parameter descriptions
|
|
39
|
+
below, while a more general discussion of the different models may also be
|
|
40
|
+
found in [1]_ and [2]_.
|
|
41
|
+
|
|
38
42
|
Parameters
|
|
39
43
|
----------
|
|
40
44
|
datetime_or_doy : numeric, array, date, datetime, Timestamp, DatetimeIndex
|
|
41
45
|
Day of year, array of days of year, or datetime-like object
|
|
42
46
|
|
|
43
47
|
solar_constant : float, default 1366.1
|
|
44
|
-
The solar constant.
|
|
48
|
+
The solar constant. [Wm⁻²]
|
|
45
49
|
|
|
46
|
-
method : string, default
|
|
47
|
-
The method by which the
|
|
48
|
-
Options include
|
|
50
|
+
method : string, default `spencer`
|
|
51
|
+
The method by which the extraterrestrial radiation should be
|
|
52
|
+
calculated. Options include: `pyephem`, `spencer` [3]_, `asce` [4]_,
|
|
53
|
+
'nrel' [6]_.
|
|
49
54
|
|
|
50
55
|
epoch_year : int, default 2014
|
|
51
56
|
The year in which a day of year input will be calculated. Only
|
|
52
|
-
applies to day of year input used with the pyephem or nrel
|
|
57
|
+
applies to day of year input used with the `pyephem` or `nrel`
|
|
53
58
|
methods.
|
|
54
59
|
|
|
55
60
|
kwargs :
|
|
56
|
-
Passed to solarposition.nrel_earthsun_distance
|
|
61
|
+
Passed to :py:func:`~pvlib.solarposition.nrel_earthsun_distance`.
|
|
57
62
|
|
|
58
63
|
Returns
|
|
59
64
|
-------
|
|
@@ -68,19 +73,23 @@ def get_extra_radiation(datetime_or_doy, solar_constant=1366.1,
|
|
|
68
73
|
.. [1] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance
|
|
69
74
|
Clear Sky Models: Implementation and Analysis", Sandia National
|
|
70
75
|
Laboratories, SAND2012-2389, 2012.
|
|
76
|
+
:doi:`10.2172/1039404`
|
|
71
77
|
|
|
72
|
-
.. [2]
|
|
73
|
-
|
|
78
|
+
.. [2] J. A. Duffie, W. A. Beckman, N. Blair, "Solar Radiation", in Solar
|
|
79
|
+
Engineering of Thermal Processes, Photovoltaics and Wind, 5th ed,
|
|
80
|
+
New York, USA: J. Wiley and Sons, 2020, pp. 3-44.
|
|
81
|
+
:doi:`10.1002/9781119540328`
|
|
74
82
|
|
|
75
|
-
.. [3]
|
|
76
|
-
|
|
83
|
+
.. [3] J. W. Spencer, "Fourier series representation of the sun," Search,
|
|
84
|
+
vol. 2, p. 172, 1971.
|
|
77
85
|
|
|
78
|
-
.. [4]
|
|
79
|
-
|
|
86
|
+
.. [4] R. G. Allen et al., Eds. The ASCE standardized reference
|
|
87
|
+
evapotranspiration equation. Reston, Va.: American Society of Civil
|
|
88
|
+
Engineers, 2005. :doi:`10.1061/9780784408056`
|
|
80
89
|
|
|
81
|
-
.. [
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
.. [6] I. Reda, A. Andreas, "Solar position algorithm for solar
|
|
91
|
+
radiation applications" NREL Golden, USA. NREL/TP-560-34302,
|
|
92
|
+
Revised 2008. :doi:`10.2172/15003974`
|
|
84
93
|
"""
|
|
85
94
|
|
|
86
95
|
to_doy, to_datetimeindex, to_output = \
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The ``sdm`` package contains functions to fit single diode models.
|
|
3
|
+
Function names should follow the pattern "fit_" + name of model + "_" +
|
|
4
|
+
fitting method.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pvlib.ivtools.sdm.cec import ( # noqa: F401
|
|
8
|
+
fit_cec_sam,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from pvlib.ivtools.sdm.desoto import ( # noqa: F401
|
|
12
|
+
fit_desoto,
|
|
13
|
+
fit_desoto_sandia
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from pvlib.ivtools.sdm.pvsyst import ( # noqa: F401
|
|
17
|
+
fit_pvsyst_sandia,
|
|
18
|
+
fit_pvsyst_iec61853_sandia_2025,
|
|
19
|
+
pvsyst_temperature_coeff,
|
|
20
|
+
)
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
"""
|
|
2
|
+
helper functions used in desoto.fit_desoto_sandia and pvsyst.fit_pvsyst_sandia
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from scipy import optimize
|
|
8
|
+
from scipy.special import lambertw
|
|
9
|
+
|
|
10
|
+
from pvlib.pvsystem import singlediode, v_from_i
|
|
11
|
+
from pvlib.ivtools.utils import rectify_iv_curve, _numdiff
|
|
12
|
+
from pvlib.pvsystem import _pvsyst_Rsh
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _initial_iv_params(ivcurves, ee, voc, isc, rsh, nnsvth):
|
|
16
|
+
# sets initial values for iph, io, rs and quality filter u.
|
|
17
|
+
# Helper function for fit_<model>_sandia.
|
|
18
|
+
n = len(ivcurves['v_oc'])
|
|
19
|
+
io = np.ones(n)
|
|
20
|
+
iph = np.ones(n)
|
|
21
|
+
rs = np.ones(n)
|
|
22
|
+
|
|
23
|
+
for j in range(n):
|
|
24
|
+
|
|
25
|
+
if rsh[j] > 0:
|
|
26
|
+
volt, curr = rectify_iv_curve(ivcurves['v'][j],
|
|
27
|
+
ivcurves['i'][j])
|
|
28
|
+
# Initial estimate of Io, evaluate the single diode model at
|
|
29
|
+
# voc and approximate Iph + Io = Isc [5] Step 3a; [6] Step 3b
|
|
30
|
+
io[j] = (isc[j] - voc[j] / rsh[j]) * np.exp(-voc[j] /
|
|
31
|
+
nnsvth[j])
|
|
32
|
+
|
|
33
|
+
# initial estimate of rs from dI/dV near Voc
|
|
34
|
+
# [5] Step 3a; [6] Step 3c
|
|
35
|
+
[didv, d2id2v] = _numdiff(volt, curr)
|
|
36
|
+
t3 = volt > .5 * voc[j]
|
|
37
|
+
t4 = volt < .9 * voc[j]
|
|
38
|
+
tmp = -rsh[j] * didv - 1.
|
|
39
|
+
with np.errstate(invalid="ignore"): # expect nan in didv
|
|
40
|
+
v = np.logical_and.reduce(np.array([t3, t4, ~np.isnan(tmp),
|
|
41
|
+
np.greater(tmp, 0)]))
|
|
42
|
+
if np.any(v):
|
|
43
|
+
vtrs = (nnsvth[j] / isc[j] * (
|
|
44
|
+
np.log(tmp[v] * nnsvth[j] / (rsh[j] * io[j]))
|
|
45
|
+
- volt[v] / nnsvth[j]))
|
|
46
|
+
rs[j] = np.mean(vtrs[vtrs > 0], axis=0)
|
|
47
|
+
else:
|
|
48
|
+
rs[j] = 0.
|
|
49
|
+
|
|
50
|
+
# Initial estimate of Iph, evaluate the single diode model at
|
|
51
|
+
# Isc [5] Step 3a; [6] Step 3d
|
|
52
|
+
iph[j] = isc[j] + io[j] * np.expm1(isc[j] / nnsvth[j]) \
|
|
53
|
+
+ isc[j] * rs[j] / rsh[j]
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
io[j] = np.nan
|
|
57
|
+
rs[j] = np.nan
|
|
58
|
+
iph[j] = np.nan
|
|
59
|
+
|
|
60
|
+
# Filter IV curves for good initial values
|
|
61
|
+
# [5] Step 3b
|
|
62
|
+
u = _filter_params(ee, isc, io, rs, rsh)
|
|
63
|
+
|
|
64
|
+
# [5] Step 3c
|
|
65
|
+
# Refine Io to match Voc
|
|
66
|
+
io[u] = _update_io(voc[u], iph[u], io[u], rs[u], rsh[u], nnsvth[u])
|
|
67
|
+
|
|
68
|
+
# parameters [6], Step 3c
|
|
69
|
+
# Calculate Iph to be consistent with Isc and current values of other
|
|
70
|
+
iph = isc + io * np.expm1(rs * isc / nnsvth) + isc * rs / rsh
|
|
71
|
+
|
|
72
|
+
return iph, io, rs, u
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _update_iv_params(voc, isc, vmp, imp, ee, iph, io, rs, rsh, nnsvth, u,
|
|
76
|
+
maxiter, eps1):
|
|
77
|
+
# Refine Rsh, Rs, Io and Iph in that order.
|
|
78
|
+
# Helper function for fit_<model>_sandia.
|
|
79
|
+
counter = 1. # counter variable for parameter updating while loop,
|
|
80
|
+
# counts iterations
|
|
81
|
+
prevconvergeparams = {}
|
|
82
|
+
prevconvergeparams['state'] = 0.0
|
|
83
|
+
|
|
84
|
+
not_converged = np.array([True])
|
|
85
|
+
|
|
86
|
+
while not_converged.any() and counter <= maxiter:
|
|
87
|
+
# update rsh to match max power point using a fixed point method.
|
|
88
|
+
rsh[u] = _update_rsh_fixed_pt(vmp[u], imp[u], iph[u], io[u], rs[u],
|
|
89
|
+
rsh[u], nnsvth[u])
|
|
90
|
+
|
|
91
|
+
# Calculate Rs to be consistent with Rsh and maximum power point
|
|
92
|
+
_, phi = _calc_theta_phi_exact(vmp[u], imp[u], iph[u], io[u],
|
|
93
|
+
rs[u], rsh[u], nnsvth[u])
|
|
94
|
+
rs[u] = (iph[u] + io[u] - imp[u]) * rsh[u] / imp[u] - \
|
|
95
|
+
nnsvth[u] * phi / imp[u] - vmp[u] / imp[u]
|
|
96
|
+
|
|
97
|
+
# Update filter for good parameters
|
|
98
|
+
u = _filter_params(ee, isc, io, rs, rsh)
|
|
99
|
+
|
|
100
|
+
# Update value for io to match voc
|
|
101
|
+
io[u] = _update_io(voc[u], iph[u], io[u], rs[u], rsh[u], nnsvth[u])
|
|
102
|
+
|
|
103
|
+
# Calculate Iph to be consistent with Isc and other parameters
|
|
104
|
+
iph = isc + io * np.expm1(rs * isc / nnsvth) + isc * rs / rsh
|
|
105
|
+
|
|
106
|
+
# update filter for good parameters
|
|
107
|
+
u = _filter_params(ee, isc, io, rs, rsh)
|
|
108
|
+
|
|
109
|
+
# compute the IV curve from the current parameter values
|
|
110
|
+
result = singlediode(iph[u], io[u], rs[u], rsh[u], nnsvth[u])
|
|
111
|
+
|
|
112
|
+
# check convergence criteria
|
|
113
|
+
# [5] Step 3d
|
|
114
|
+
convergeparams = _check_converge(
|
|
115
|
+
prevconvergeparams, result, vmp[u], imp[u], counter)
|
|
116
|
+
|
|
117
|
+
prevconvergeparams = convergeparams
|
|
118
|
+
counter += 1.
|
|
119
|
+
t5 = prevconvergeparams['vmperrmeanchange'] >= eps1
|
|
120
|
+
t6 = prevconvergeparams['imperrmeanchange'] >= eps1
|
|
121
|
+
t7 = prevconvergeparams['pmperrmeanchange'] >= eps1
|
|
122
|
+
t8 = prevconvergeparams['vmperrstdchange'] >= eps1
|
|
123
|
+
t9 = prevconvergeparams['imperrstdchange'] >= eps1
|
|
124
|
+
t10 = prevconvergeparams['pmperrstdchange'] >= eps1
|
|
125
|
+
t11 = prevconvergeparams['vmperrabsmaxchange'] >= eps1
|
|
126
|
+
t12 = prevconvergeparams['imperrabsmaxchange'] >= eps1
|
|
127
|
+
t13 = prevconvergeparams['pmperrabsmaxchange'] >= eps1
|
|
128
|
+
not_converged = np.logical_or.reduce(np.array([t5, t6, t7, t8, t9,
|
|
129
|
+
t10, t11, t12, t13]))
|
|
130
|
+
|
|
131
|
+
return iph, io, rs, rsh, u
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _extract_sdm_params(ee, tc, iph, io, rs, rsh, n, u, specs, const,
|
|
135
|
+
model):
|
|
136
|
+
# Get single diode model parameters from five parameters iph, io, rs, rsh
|
|
137
|
+
# and n vs. effective irradiance and temperature
|
|
138
|
+
try:
|
|
139
|
+
import statsmodels.api as sm
|
|
140
|
+
except ImportError:
|
|
141
|
+
raise ImportError(
|
|
142
|
+
'Parameter extraction using Sandia method requires statsmodels')
|
|
143
|
+
|
|
144
|
+
tck = tc + 273.15
|
|
145
|
+
tok = const['T0'] + 273.15 # convert to to K
|
|
146
|
+
|
|
147
|
+
params = {}
|
|
148
|
+
|
|
149
|
+
if model == 'pvsyst':
|
|
150
|
+
# Estimate I_o_ref and EgRef
|
|
151
|
+
x_for_io = const['q'] / const['k'] * (1. / tok - 1. / tck[u]) / n[u]
|
|
152
|
+
|
|
153
|
+
# Estimate R_sh_0, R_sh_ref and R_sh_exp
|
|
154
|
+
# Initial guesses. R_sh_0 is value at ee=0.
|
|
155
|
+
nans = np.isnan(rsh)
|
|
156
|
+
if any(ee < 400):
|
|
157
|
+
grsh0 = np.mean(rsh[np.logical_and(~nans, ee < 400)])
|
|
158
|
+
else:
|
|
159
|
+
grsh0 = np.max(rsh)
|
|
160
|
+
# Rsh_ref is value at Ee = 1000
|
|
161
|
+
if any(ee > 400):
|
|
162
|
+
grshref = np.mean(rsh[np.logical_and(~nans, ee > 400)])
|
|
163
|
+
else:
|
|
164
|
+
grshref = np.min(rsh)
|
|
165
|
+
# PVsyst default for Rshexp is 5.5
|
|
166
|
+
R_sh_exp = 5.5
|
|
167
|
+
|
|
168
|
+
# Find parameters for Rsh equation
|
|
169
|
+
|
|
170
|
+
def fun_rsh(x, rshexp, ee, e0, rsh):
|
|
171
|
+
tf = (
|
|
172
|
+
np.log10(_pvsyst_Rsh(ee, x[1], x[0], R_sh_exp, e0))
|
|
173
|
+
- np.log10(rsh)
|
|
174
|
+
)
|
|
175
|
+
return tf
|
|
176
|
+
|
|
177
|
+
x0 = np.array([grsh0, grshref])
|
|
178
|
+
beta = optimize.least_squares(
|
|
179
|
+
fun_rsh, x0, args=(R_sh_exp, ee[u], const['E0'], rsh[u]),
|
|
180
|
+
bounds=np.array([[1., 1.], [1.e7, 1.e6]]), verbose=2)
|
|
181
|
+
# Extract PVsyst parameter values
|
|
182
|
+
R_sh_0 = beta.x[0]
|
|
183
|
+
R_sh_ref = beta.x[1]
|
|
184
|
+
|
|
185
|
+
# parameters unique to PVsyst
|
|
186
|
+
params['R_sh_0'] = R_sh_0
|
|
187
|
+
params['R_sh_exp'] = R_sh_exp
|
|
188
|
+
|
|
189
|
+
elif model == 'desoto':
|
|
190
|
+
dEgdT = -0.0002677
|
|
191
|
+
x_for_io = const['q'] / const['k'] * (
|
|
192
|
+
1. / tok - 1. / tck[u] + dEgdT * (tc[u] - const['T0']) / tck[u])
|
|
193
|
+
|
|
194
|
+
# Estimate R_sh_ref
|
|
195
|
+
nans = np.isnan(rsh)
|
|
196
|
+
x = const['E0'] / ee[np.logical_and(u, ee > 400, ~nans)]
|
|
197
|
+
y = rsh[np.logical_and(u, ee > 400, ~nans)]
|
|
198
|
+
new_x = sm.add_constant(x)
|
|
199
|
+
beta = sm.RLM(y, new_x).fit()
|
|
200
|
+
R_sh_ref = beta.params[1]
|
|
201
|
+
|
|
202
|
+
params['dEgdT'] = dEgdT
|
|
203
|
+
|
|
204
|
+
# Estimate I_o_ref and EgRef
|
|
205
|
+
y = np.log(io[u]) - 3. * np.log(tck[u] / tok)
|
|
206
|
+
new_x = sm.add_constant(x_for_io)
|
|
207
|
+
res = sm.RLM(y, new_x).fit()
|
|
208
|
+
beta = res.params
|
|
209
|
+
I_o_ref = np.exp(beta[0])
|
|
210
|
+
EgRef = beta[1]
|
|
211
|
+
|
|
212
|
+
# Estimate I_L_ref
|
|
213
|
+
x = tc[u] - const['T0']
|
|
214
|
+
y = iph[u] * (const['E0'] / ee[u])
|
|
215
|
+
# average over non-NaN values of Y and X
|
|
216
|
+
nans = np.isnan(y - specs['alpha_sc'] * x)
|
|
217
|
+
I_L_ref = np.mean(y[~nans] - specs['alpha_sc'] * x[~nans])
|
|
218
|
+
|
|
219
|
+
# Estimate R_s
|
|
220
|
+
nans = np.isnan(rs)
|
|
221
|
+
R_s = np.mean(rs[np.logical_and(u, ee > 400, ~nans)])
|
|
222
|
+
|
|
223
|
+
params['I_L_ref'] = I_L_ref
|
|
224
|
+
params['I_o_ref'] = I_o_ref
|
|
225
|
+
params['EgRef'] = EgRef
|
|
226
|
+
params['R_sh_ref'] = R_sh_ref
|
|
227
|
+
params['R_s'] = R_s
|
|
228
|
+
# save values for each IV curve
|
|
229
|
+
params['iph'] = iph
|
|
230
|
+
params['io'] = io
|
|
231
|
+
params['rsh'] = rsh
|
|
232
|
+
params['rs'] = rs
|
|
233
|
+
params['u'] = u
|
|
234
|
+
|
|
235
|
+
return params
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _update_io(voc, iph, io, rs, rsh, nnsvth):
|
|
239
|
+
"""
|
|
240
|
+
Adjusts Io to match Voc using other parameter values.
|
|
241
|
+
|
|
242
|
+
Helper function for fit_pvsyst_sandia, fit_desoto_sandia
|
|
243
|
+
|
|
244
|
+
Description
|
|
245
|
+
-----------
|
|
246
|
+
Io is updated iteratively 10 times or until successive
|
|
247
|
+
values are less than 0.000001 % different. The updating is similar to
|
|
248
|
+
Newton's method.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
voc: a numpy array of length N of values for Voc (V)
|
|
253
|
+
iph: a numpy array of length N of values for lighbt current IL (A)
|
|
254
|
+
io: a numpy array of length N of initial values for Io (A)
|
|
255
|
+
rs: a numpy array of length N of values for the series resistance (ohm)
|
|
256
|
+
rsh: a numpy array of length N of values for the shunt resistance (ohm)
|
|
257
|
+
nnsvth: a numpy array of length N of values for the diode factor x thermal
|
|
258
|
+
voltage for the module, equal to Ns (number of cells in series) x
|
|
259
|
+
Vth (thermal voltage per cell).
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
new_io - a numpy array of length N of updated values for io
|
|
264
|
+
|
|
265
|
+
References
|
|
266
|
+
----------
|
|
267
|
+
.. [1] PVLib MATLAB https://github.com/sandialabs/MATLAB_PV_LIB
|
|
268
|
+
.. [2] C. Hansen, Parameter Estimation for Single Diode Models of
|
|
269
|
+
Photovoltaic Modules, Sandia National Laboratories Report SAND2015-2065
|
|
270
|
+
.. [3] C. Hansen, Estimation of Parameteres for Single Diode Models using
|
|
271
|
+
Measured IV Curves, Proc. of the 39th IEEE PVSC, June 2013.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
eps = 1e-6
|
|
275
|
+
niter = 10
|
|
276
|
+
k = 1
|
|
277
|
+
maxerr = 1
|
|
278
|
+
|
|
279
|
+
tio = io # Current Estimate of Io
|
|
280
|
+
|
|
281
|
+
while maxerr > eps and k < niter:
|
|
282
|
+
# Predict Voc
|
|
283
|
+
pvoc = v_from_i(0., iph, tio, rs, rsh, nnsvth)
|
|
284
|
+
|
|
285
|
+
# Difference in Voc
|
|
286
|
+
dvoc = pvoc - voc
|
|
287
|
+
|
|
288
|
+
# Update Io
|
|
289
|
+
with np.errstate(invalid="ignore", divide="ignore"):
|
|
290
|
+
new_io = tio * (1. + (2. * dvoc) / (2. * nnsvth - dvoc))
|
|
291
|
+
# Calculate Maximum Percent Difference
|
|
292
|
+
maxerr = np.max(np.abs(new_io - tio) / tio) * 100.
|
|
293
|
+
|
|
294
|
+
tio = new_io
|
|
295
|
+
k += 1.
|
|
296
|
+
|
|
297
|
+
return new_io
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _filter_params(ee, isc, io, rs, rsh):
|
|
301
|
+
# Function _filter_params identifies bad parameter sets. A bad set contains
|
|
302
|
+
# Nan, non-positive or imaginary values for parameters; Rs > Rsh; or data
|
|
303
|
+
# where effective irradiance Ee differs by more than 5% from a linear fit
|
|
304
|
+
# to Isc vs. Ee
|
|
305
|
+
|
|
306
|
+
badrsh = np.logical_or(rsh < 0., np.isnan(rsh))
|
|
307
|
+
negrs = rs < 0.
|
|
308
|
+
badrs = np.logical_or(rs > rsh, np.isnan(rs))
|
|
309
|
+
imagrs = ~(np.isreal(rs))
|
|
310
|
+
badio = np.logical_or(np.logical_or(~(np.isreal(rs)), io <= 0),
|
|
311
|
+
np.isnan(io))
|
|
312
|
+
goodr = np.logical_and(~badrsh, ~imagrs)
|
|
313
|
+
goodr = np.logical_and(goodr, ~negrs)
|
|
314
|
+
goodr = np.logical_and(goodr, ~badrs)
|
|
315
|
+
goodr = np.logical_and(goodr, ~badio)
|
|
316
|
+
|
|
317
|
+
matrix = np.vstack((ee / 1000., np.zeros(len(ee)))).T
|
|
318
|
+
eff = np.linalg.lstsq(matrix, isc, rcond=None)[0][0]
|
|
319
|
+
pisc = eff * ee / 1000
|
|
320
|
+
pisc_error = np.abs(pisc - isc) / isc
|
|
321
|
+
# check for departure from linear relation between Isc and Ee
|
|
322
|
+
badiph = pisc_error > .05
|
|
323
|
+
|
|
324
|
+
u = np.logical_and(goodr, ~badiph)
|
|
325
|
+
return u
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _check_converge(prevparams, result, vmp, imp, i):
|
|
329
|
+
"""
|
|
330
|
+
Function _check_converge computes convergence metrics for all IV curves.
|
|
331
|
+
|
|
332
|
+
Helper function for fit_pvsyst_sandia, fit_desoto_sandia
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
prevparams: Convergence Parameters from the previous Iteration (used to
|
|
337
|
+
determine Percent Change in values between iterations)
|
|
338
|
+
result: performacne paramters of the (predicted) single diode fitting,
|
|
339
|
+
which includes Voc, Vmp, Imp, Pmp and Isc
|
|
340
|
+
vmp: measured values for each IV curve
|
|
341
|
+
imp: measured values for each IV curve
|
|
342
|
+
i: Index of current iteration in cec_parameter_estimation
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
convergeparam: dict containing the following for Imp, Vmp and Pmp:
|
|
347
|
+
- maximum percent difference between measured and modeled values
|
|
348
|
+
- minimum percent difference between measured and modeled values
|
|
349
|
+
- maximum absolute percent difference between measured and modeled
|
|
350
|
+
values
|
|
351
|
+
- mean percent difference between measured and modeled values
|
|
352
|
+
- standard deviation of percent difference between measured and modeled
|
|
353
|
+
values
|
|
354
|
+
- absolute difference for previous and current values of maximum
|
|
355
|
+
absolute percent difference (measured vs. modeled)
|
|
356
|
+
- absolute difference for previous and current values of mean percent
|
|
357
|
+
difference (measured vs. modeled)
|
|
358
|
+
- absolute difference for previous and current values of standard
|
|
359
|
+
deviation of percent difference (measured vs. modeled)
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
convergeparam = {}
|
|
363
|
+
|
|
364
|
+
imperror = (result['i_mp'] - imp) / imp * 100.
|
|
365
|
+
vmperror = (result['v_mp'] - vmp) / vmp * 100.
|
|
366
|
+
pmperror = (result['p_mp'] - (imp * vmp)) / (imp * vmp) * 100.
|
|
367
|
+
|
|
368
|
+
convergeparam['imperrmax'] = max(imperror) # max of the error in Imp
|
|
369
|
+
convergeparam['imperrmin'] = min(imperror) # min of the error in Imp
|
|
370
|
+
# max of the absolute error in Imp
|
|
371
|
+
convergeparam['imperrabsmax'] = max(abs(imperror))
|
|
372
|
+
# mean of the error in Imp
|
|
373
|
+
convergeparam['imperrmean'] = np.mean(imperror, axis=0)
|
|
374
|
+
# std of the error in Imp
|
|
375
|
+
convergeparam['imperrstd'] = np.std(imperror, axis=0, ddof=1)
|
|
376
|
+
|
|
377
|
+
convergeparam['vmperrmax'] = max(vmperror) # max of the error in Vmp
|
|
378
|
+
convergeparam['vmperrmin'] = min(vmperror) # min of the error in Vmp
|
|
379
|
+
# max of the absolute error in Vmp
|
|
380
|
+
convergeparam['vmperrabsmax'] = max(abs(vmperror))
|
|
381
|
+
# mean of the error in Vmp
|
|
382
|
+
convergeparam['vmperrmean'] = np.mean(vmperror, axis=0)
|
|
383
|
+
# std of the error in Vmp
|
|
384
|
+
convergeparam['vmperrstd'] = np.std(vmperror, axis=0, ddof=1)
|
|
385
|
+
|
|
386
|
+
convergeparam['pmperrmax'] = max(pmperror) # max of the error in Pmp
|
|
387
|
+
convergeparam['pmperrmin'] = min(pmperror) # min of the error in Pmp
|
|
388
|
+
# max of the abs err. in Pmp
|
|
389
|
+
convergeparam['pmperrabsmax'] = max(abs(pmperror))
|
|
390
|
+
# mean error in Pmp
|
|
391
|
+
convergeparam['pmperrmean'] = np.mean(pmperror, axis=0)
|
|
392
|
+
# std error Pmp
|
|
393
|
+
convergeparam['pmperrstd'] = np.std(pmperror, axis=0, ddof=1)
|
|
394
|
+
|
|
395
|
+
if prevparams['state'] != 0.0:
|
|
396
|
+
convergeparam['imperrstdchange'] = np.abs(
|
|
397
|
+
convergeparam['imperrstd'] / prevparams['imperrstd'] - 1.)
|
|
398
|
+
convergeparam['vmperrstdchange'] = np.abs(
|
|
399
|
+
convergeparam['vmperrstd'] / prevparams['vmperrstd'] - 1.)
|
|
400
|
+
convergeparam['pmperrstdchange'] = np.abs(
|
|
401
|
+
convergeparam['pmperrstd'] / prevparams['pmperrstd'] - 1.)
|
|
402
|
+
convergeparam['imperrmeanchange'] = np.abs(
|
|
403
|
+
convergeparam['imperrmean'] / prevparams['imperrmean'] - 1.)
|
|
404
|
+
convergeparam['vmperrmeanchange'] = np.abs(
|
|
405
|
+
convergeparam['vmperrmean'] / prevparams['vmperrmean'] - 1.)
|
|
406
|
+
convergeparam['pmperrmeanchange'] = np.abs(
|
|
407
|
+
convergeparam['pmperrmean'] / prevparams['pmperrmean'] - 1.)
|
|
408
|
+
convergeparam['imperrabsmaxchange'] = np.abs(
|
|
409
|
+
convergeparam['imperrabsmax'] / prevparams['imperrabsmax'] - 1.)
|
|
410
|
+
convergeparam['vmperrabsmaxchange'] = np.abs(
|
|
411
|
+
convergeparam['vmperrabsmax'] / prevparams['vmperrabsmax'] - 1.)
|
|
412
|
+
convergeparam['pmperrabsmaxchange'] = np.abs(
|
|
413
|
+
convergeparam['pmperrabsmax'] / prevparams['pmperrabsmax'] - 1.)
|
|
414
|
+
convergeparam['state'] = 1.0
|
|
415
|
+
else:
|
|
416
|
+
convergeparam['imperrstdchange'] = float("Inf")
|
|
417
|
+
convergeparam['vmperrstdchange'] = float("Inf")
|
|
418
|
+
convergeparam['pmperrstdchange'] = float("Inf")
|
|
419
|
+
convergeparam['imperrmeanchange'] = float("Inf")
|
|
420
|
+
convergeparam['vmperrmeanchange'] = float("Inf")
|
|
421
|
+
convergeparam['pmperrmeanchange'] = float("Inf")
|
|
422
|
+
convergeparam['imperrabsmaxchange'] = float("Inf")
|
|
423
|
+
convergeparam['vmperrabsmaxchange'] = float("Inf")
|
|
424
|
+
convergeparam['pmperrabsmaxchange'] = float("Inf")
|
|
425
|
+
convergeparam['state'] = 1.
|
|
426
|
+
return convergeparam
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _update_rsh_fixed_pt(vmp, imp, iph, io, rs, rsh, nnsvth):
|
|
430
|
+
"""
|
|
431
|
+
Adjust Rsh to match Vmp using other parameter values
|
|
432
|
+
|
|
433
|
+
Helper function for fit_pvsyst_sandia, fit_desoto_sandia
|
|
434
|
+
|
|
435
|
+
Description
|
|
436
|
+
-----------
|
|
437
|
+
Rsh is updated iteratively using a fixed point expression
|
|
438
|
+
obtained from combining Vmp = Vmp(Imp) (using the analytic solution to the
|
|
439
|
+
single diode equation) and dP / dI = 0 at Imp. 500 iterations are performed
|
|
440
|
+
because convergence can be very slow.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
vmp: a numpy array of length N of values for Vmp (V)
|
|
445
|
+
imp: a numpy array of length N of values for Imp (A)
|
|
446
|
+
iph: a numpy array of length N of values for light current IL (A)
|
|
447
|
+
io: a numpy array of length N of values for Io (A)
|
|
448
|
+
rs: a numpy array of length N of values for series resistance (ohm)
|
|
449
|
+
rsh: a numpy array of length N of initial values for shunt resistance (ohm)
|
|
450
|
+
nnsvth: a numpy array length N of values for the diode factor x thermal
|
|
451
|
+
voltage for the module, equal to Ns (number of cells in series) x
|
|
452
|
+
Vth (thermal voltage per cell).
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
numpy array of length N of updated values for Rsh
|
|
457
|
+
|
|
458
|
+
References
|
|
459
|
+
----------
|
|
460
|
+
.. [1] PVLib for MATLAB https://github.com/sandialabs/MATLAB_PV_LIB
|
|
461
|
+
.. [2] C. Hansen, Parameter Estimation for Single Diode Models of
|
|
462
|
+
Photovoltaic Modules, Sandia National Laboratories Report SAND2015-2065
|
|
463
|
+
"""
|
|
464
|
+
niter = 500
|
|
465
|
+
x1 = rsh
|
|
466
|
+
|
|
467
|
+
for i in range(niter):
|
|
468
|
+
_, z = _calc_theta_phi_exact(vmp, imp, iph, io, rs, x1, nnsvth)
|
|
469
|
+
with np.errstate(divide="ignore"):
|
|
470
|
+
next_x1 = (1 + z) / z * ((iph + io) * x1 / imp - nnsvth * z / imp
|
|
471
|
+
- 2 * vmp / imp)
|
|
472
|
+
x1 = next_x1
|
|
473
|
+
|
|
474
|
+
return x1
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _calc_theta_phi_exact(vmp, imp, iph, io, rs, rsh, nnsvth):
|
|
478
|
+
"""
|
|
479
|
+
_calc_theta_phi_exact computes Lambert W values appearing in the analytic
|
|
480
|
+
solutions to the single diode equation for the max power point.
|
|
481
|
+
|
|
482
|
+
Helper function for fit_pvsyst_sandia
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
vmp: a numpy array of length N of values for Vmp (V)
|
|
487
|
+
imp: a numpy array of length N of values for Imp (A)
|
|
488
|
+
iph: a numpy array of length N of values for the light current IL (A)
|
|
489
|
+
io: a numpy array of length N of values for Io (A)
|
|
490
|
+
rs: a numpy array of length N of values for the series resistance (ohm)
|
|
491
|
+
rsh: a numpy array of length N of values for the shunt resistance (ohm)
|
|
492
|
+
nnsvth: a numpy array of length N of values for the diode factor x
|
|
493
|
+
thermal voltage for the module, equal to Ns
|
|
494
|
+
(number of cells in series) x Vth
|
|
495
|
+
(thermal voltage per cell).
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
theta: a numpy array of values for the Lamber W function for solving
|
|
500
|
+
I = I(V)
|
|
501
|
+
phi: a numpy array of values for the Lambert W function for solving
|
|
502
|
+
V = V(I)
|
|
503
|
+
|
|
504
|
+
Notes
|
|
505
|
+
-----
|
|
506
|
+
_calc_theta_phi_exact calculates values for the Lambert W function which
|
|
507
|
+
are used in the analytic solutions for the single diode equation at the
|
|
508
|
+
maximum power point. For V=V(I),
|
|
509
|
+
phi = W(Io*Rsh/n*Vth * exp((IL + Io - Imp)*Rsh/n*Vth)). For I=I(V),
|
|
510
|
+
theta = W(Rs*Io/n*Vth *
|
|
511
|
+
Rsh/ (Rsh+Rs) * exp(Rsh/ (Rsh+Rs)*((Rs(IL+Io) + V)/n*Vth))
|
|
512
|
+
|
|
513
|
+
References
|
|
514
|
+
----------
|
|
515
|
+
.. [1] PVL MATLAB 2065 https://github.com/sandialabs/MATLAB_PV_LIB
|
|
516
|
+
.. [2] C. Hansen, Parameter Estimation for Single Diode Models of
|
|
517
|
+
Photovoltaic Modules, Sandia National Laboratories Report SAND2015-2065
|
|
518
|
+
.. [3] A. Jain, A. Kapoor, "Exact analytical solutions of the parameters of
|
|
519
|
+
real solar cells using Lambert W-function", Solar Energy Materials and
|
|
520
|
+
Solar Cells, 81 (2004) 269-277.
|
|
521
|
+
"""
|
|
522
|
+
# handle singleton inputs
|
|
523
|
+
vmp = np.asarray(vmp)
|
|
524
|
+
imp = np.asarray(imp)
|
|
525
|
+
iph = np.asarray(iph)
|
|
526
|
+
io = np.asarray(io)
|
|
527
|
+
rs = np.asarray(rs)
|
|
528
|
+
rsh = np.asarray(rsh)
|
|
529
|
+
nnsvth = np.asarray(nnsvth)
|
|
530
|
+
|
|
531
|
+
# Argument for Lambert W function involved in V = V(I) [2] Eq. 12; [3]
|
|
532
|
+
# Eq. 3
|
|
533
|
+
with np.errstate(over="ignore", divide="ignore", invalid="ignore"):
|
|
534
|
+
argw = np.where(
|
|
535
|
+
nnsvth == 0,
|
|
536
|
+
np.nan,
|
|
537
|
+
rsh * io / nnsvth * np.exp(rsh * (iph + io - imp) / nnsvth))
|
|
538
|
+
phi = np.where(argw > 0, lambertw(argw).real, np.nan)
|
|
539
|
+
|
|
540
|
+
# NaN where argw overflows. Switch to log space to evaluate
|
|
541
|
+
u = np.isinf(argw)
|
|
542
|
+
if np.any(u):
|
|
543
|
+
logargw = (
|
|
544
|
+
np.log(rsh[u]) + np.log(io[u]) - np.log(nnsvth[u])
|
|
545
|
+
+ rsh[u] * (iph[u] + io[u] - imp[u]) / nnsvth[u])
|
|
546
|
+
# Three iterations of Newton-Raphson method to solve w+log(w)=logargW.
|
|
547
|
+
# The initial guess is w=logargW. Where direct evaluation (above)
|
|
548
|
+
# results in NaN from overflow, 3 iterations of Newton's method gives
|
|
549
|
+
# approximately 8 digits of precision.
|
|
550
|
+
x = logargw
|
|
551
|
+
for i in range(3):
|
|
552
|
+
x *= ((1. - np.log(x) + logargw) / (1. + x))
|
|
553
|
+
phi[u] = x
|
|
554
|
+
phi = np.transpose(phi)
|
|
555
|
+
|
|
556
|
+
# Argument for Lambert W function involved in I = I(V) [2] Eq. 11; [3]
|
|
557
|
+
# E1. 2
|
|
558
|
+
with np.errstate(over="ignore", divide="ignore", invalid="ignore"):
|
|
559
|
+
argw = np.where(
|
|
560
|
+
nnsvth == 0,
|
|
561
|
+
np.nan,
|
|
562
|
+
rsh / (rsh + rs) * rs * io / nnsvth * np.exp(
|
|
563
|
+
rsh / (rsh + rs) * (rs * (iph + io) + vmp) / nnsvth))
|
|
564
|
+
theta = np.where(argw > 0, lambertw(argw).real, np.nan)
|
|
565
|
+
|
|
566
|
+
# NaN where argw overflows. Switch to log space to evaluate
|
|
567
|
+
u = np.isinf(argw)
|
|
568
|
+
if np.any(u):
|
|
569
|
+
with np.errstate(divide="ignore"):
|
|
570
|
+
logargw = (
|
|
571
|
+
np.log(rsh[u]) - np.log(rsh[u] + rs[u]) + np.log(rs[u])
|
|
572
|
+
+ np.log(io[u]) - np.log(nnsvth[u])
|
|
573
|
+
+ (rsh[u] / (rsh[u] + rs[u]))
|
|
574
|
+
* (rs[u] * (iph[u] + io[u]) + vmp[u]) / nnsvth[u])
|
|
575
|
+
# Three iterations of Newton-Raphson method to solve w+log(w)=logargW.
|
|
576
|
+
# The initial guess is w=logargW. Where direct evaluation (above)
|
|
577
|
+
# results in NaN from overflow, 3 iterations of Newton's method gives
|
|
578
|
+
# approximately 8 digits of precision.
|
|
579
|
+
x = logargw
|
|
580
|
+
for i in range(3):
|
|
581
|
+
x *= ((1. - np.log(x) + logargw) / (1. + x))
|
|
582
|
+
theta[u] = x
|
|
583
|
+
theta = np.transpose(theta)
|
|
584
|
+
|
|
585
|
+
return theta, phi
|