pvlib 0.12.0__py3-none-any.whl → 0.13.0__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/bifacial/infinite_sheds.py +11 -16
- pvlib/iotools/__init__.py +5 -0
- pvlib/iotools/bsrn.py +16 -10
- pvlib/iotools/epw.py +14 -11
- pvlib/iotools/psm3.py +37 -71
- pvlib/iotools/psm4.py +766 -0
- pvlib/iotools/pvgis.py +116 -101
- pvlib/iotools/sodapro.py +54 -69
- pvlib/iotools/srml.py +1 -2
- pvlib/irradiance.py +28 -19
- 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 +130 -69
- pvlib/solarposition.py +1 -1
- pvlib/spectrum/irradiance.py +2 -1
- pvlib/temperature.py +13 -10
- pvlib/tools.py +27 -0
- {pvlib-0.12.0.dist-info → pvlib-0.13.0.dist-info}/METADATA +2 -3
- {pvlib-0.12.0.dist-info → pvlib-0.13.0.dist-info}/RECORD +28 -23
- {pvlib-0.12.0.dist-info → pvlib-0.13.0.dist-info}/WHEEL +1 -1
- pvlib/ivtools/sdm.py +0 -1379
- {pvlib-0.12.0.dist-info → pvlib-0.13.0.dist-info}/licenses/AUTHORS.md +0 -0
- {pvlib-0.12.0.dist-info → pvlib-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {pvlib-0.12.0.dist-info → pvlib-0.13.0.dist-info}/top_level.txt +0 -0
|
@@ -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 parameters 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
|
pvlib/ivtools/sdm/cec.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc,
|
|
3
|
+
gamma_pmp, cells_in_series, temp_ref=25):
|
|
4
|
+
"""
|
|
5
|
+
Estimates parameters for the CEC single diode model (SDM) using the SAM
|
|
6
|
+
SDK.
|
|
7
|
+
|
|
8
|
+
Parameters
|
|
9
|
+
----------
|
|
10
|
+
celltype : str
|
|
11
|
+
Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte',
|
|
12
|
+
'amorphous'
|
|
13
|
+
v_mp : float
|
|
14
|
+
Voltage at maximum power point [V]
|
|
15
|
+
i_mp : float
|
|
16
|
+
Current at maximum power point [A]
|
|
17
|
+
v_oc : float
|
|
18
|
+
Open circuit voltage [V]
|
|
19
|
+
i_sc : float
|
|
20
|
+
Short circuit current [A]
|
|
21
|
+
alpha_sc : float
|
|
22
|
+
Temperature coefficient of short circuit current [A/C]
|
|
23
|
+
beta_voc : float
|
|
24
|
+
Temperature coefficient of open circuit voltage [V/C]
|
|
25
|
+
gamma_pmp : float
|
|
26
|
+
Temperature coefficient of power at maximum power point [%/C]
|
|
27
|
+
cells_in_series : int
|
|
28
|
+
Number of cells in series
|
|
29
|
+
temp_ref : float, default 25
|
|
30
|
+
Reference temperature condition [C]
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
I_L_ref : float
|
|
35
|
+
The light-generated current (or photocurrent) at reference
|
|
36
|
+
conditions [A]
|
|
37
|
+
I_o_ref : float
|
|
38
|
+
The dark or diode reverse saturation current at reference
|
|
39
|
+
conditions [A]
|
|
40
|
+
R_s : float
|
|
41
|
+
The series resistance at reference conditions, in ohms.
|
|
42
|
+
R_sh_ref : float
|
|
43
|
+
The shunt resistance at reference conditions, in ohms.
|
|
44
|
+
a_ref : float
|
|
45
|
+
The product of the usual diode ideality factor ``n`` (unitless),
|
|
46
|
+
number of cells in series ``Ns``, and cell thermal voltage at
|
|
47
|
+
reference conditions [V]
|
|
48
|
+
Adjust : float
|
|
49
|
+
The adjustment to the temperature coefficient for short circuit
|
|
50
|
+
current, in percent.
|
|
51
|
+
|
|
52
|
+
Raises
|
|
53
|
+
------
|
|
54
|
+
ImportError
|
|
55
|
+
if NREL-PySAM is not installed.
|
|
56
|
+
RuntimeError
|
|
57
|
+
if parameter extraction is not successful.
|
|
58
|
+
|
|
59
|
+
Notes
|
|
60
|
+
-----
|
|
61
|
+
The CEC model and estimation method are described in [1]_.
|
|
62
|
+
Inputs ``v_mp``, ``i_mp``, ``v_oc`` and ``i_sc`` are assumed to be from a
|
|
63
|
+
single IV curve at constant irradiance and cell temperature. Irradiance is
|
|
64
|
+
not explicitly used by the fitting procedure. The irradiance level at which
|
|
65
|
+
the input IV curve is determined and the specified cell temperature
|
|
66
|
+
``temp_ref`` are the reference conditions for the output parameters
|
|
67
|
+
``I_L_ref``, ``I_o_ref``, ``R_s``, ``R_sh_ref``, ``a_ref`` and ``Adjust``.
|
|
68
|
+
|
|
69
|
+
References
|
|
70
|
+
----------
|
|
71
|
+
.. [1] A. Dobos, "An Improved Coefficient Calculator for the California
|
|
72
|
+
Energy Commission 6 Parameter Photovoltaic Module Model", Journal of
|
|
73
|
+
Solar Energy Engineering, vol 134, 2012. :doi:`10.1115/1.4005759`
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
from PySAM import PySSC
|
|
78
|
+
except ImportError:
|
|
79
|
+
raise ImportError("Requires NREL's PySAM package at "
|
|
80
|
+
"https://pypi.org/project/NREL-PySAM/.")
|
|
81
|
+
|
|
82
|
+
datadict = {'tech_model': '6parsolve', 'financial_model': None,
|
|
83
|
+
'celltype': celltype, 'Vmp': v_mp,
|
|
84
|
+
'Imp': i_mp, 'Voc': v_oc, 'Isc': i_sc, 'alpha_isc': alpha_sc,
|
|
85
|
+
'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp,
|
|
86
|
+
'Nser': cells_in_series, 'Tref': temp_ref}
|
|
87
|
+
|
|
88
|
+
result = PySSC.ssc_sim_from_dict(datadict)
|
|
89
|
+
if result['cmod_success'] == 1:
|
|
90
|
+
return tuple([result[k] for k in ['Il', 'Io', 'Rs', 'Rsh', 'a',
|
|
91
|
+
'Adj']])
|
|
92
|
+
else:
|
|
93
|
+
raise RuntimeError('Parameter estimation failed')
|