pvlib 0.12.0__py3-none-any.whl → 0.13.0a1__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.
@@ -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
@@ -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')