Skip to content

Commit 6a11969

Browse files
committed
add function
1 parent 7e34a32 commit 6a11969

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

pvlib/ivtools/sdm/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515

1616
from pvlib.ivtools.sdm.pvsyst import ( # noqa: F401
1717
fit_pvsyst_sandia,
18+
fit_pvsyst_iec61853_sandia,
1819
pvsyst_temperature_coeff,
1920
)

pvlib/ivtools/sdm/pvsyst.py

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
_extract_sdm_params, _initial_iv_params, _update_iv_params
1515
)
1616

17+
from pvlib.pvsystem import (
18+
_pvsyst_Rsh, _pvsyst_IL, _pvsyst_Io, _pvsyst_nNsVth, _pvsyst_gamma
19+
)
1720

1821
CONSTANTS = {'E0': 1000.0, 'T0': 25.0, 'k': constants.k, 'q': constants.e}
1922

@@ -305,3 +308,310 @@ def maxp(temp_cell, irrad_ref, alpha_sc, gamma_ref, mu_gamma, I_L_ref,
305308
gamma_pdc = _first_order_centered_difference(maxp, x0=temp_ref, args=args)
306309

307310
return gamma_pdc / pmp
311+
312+
313+
def fit_pvsyst_iec61853_sandia(effective_irradiance, temp_cell,
314+
i_sc, v_oc, i_mp, v_mp,
315+
cells_in_series, EgRef=1.121,
316+
alpha_sc=None, beta_mp=None,
317+
r_sh_coeff=0.12, R_s=None,
318+
min_Rsh_irradiance=None):
319+
"""
320+
Estimate parameters for the PVsyst module performance model using
321+
IEC 61853-1 matrix measurements.
322+
323+
Parameters
324+
----------
325+
effective_irradiance : array
326+
Effective irradiance for each test condition [W/m²]
327+
temp_cell : array
328+
Cell temperature for each test condition [C]
329+
i_sc : array
330+
Short circuit current for each test condition [A]
331+
v_oc : array
332+
Open circuit voltage for each test condition [V]
333+
i_mp : array
334+
Current at maximum power point for each test condition [A]
335+
v_mp : array
336+
Voltage at maximum power point for each test condition [V]
337+
cells_in_series : int
338+
The number of cells connected in series.
339+
EgRef : float, optional
340+
The energy bandgap at reference temperature in units of eV.
341+
1.121 eV for crystalline silicon. EgRef must be >0.
342+
alpha_sc : float, optional
343+
Temperature coefficient of short circuit current. If not specified,
344+
it will be estimated using the ``i_sc`` values at irradiance of
345+
1000 W/m2. [A/K]
346+
beta_mp : float, optional
347+
Temperature coefficient of maximum power voltage. If not specified,
348+
it will be estimated using the ``v_mp`` values at irradiance of
349+
1000 W/m2. [1/K]
350+
r_sh_coeff : float, default 0.12
351+
Shunt resistance fitting coefficient. The default value is taken
352+
from [1]_.
353+
R_s : float, optional
354+
Series resistance value. If not provided, a value will be estimated
355+
from the input measurements. [ohm]
356+
min_Rsh_irradiance : float, optional
357+
Irradiance threshold below which values are excluded when estimating
358+
shunt resistance parameter values. May be useful for modules
359+
with problematic low-light measurements. [W/m²]
360+
361+
Returns
362+
-------
363+
dict
364+
alpha_sc : float
365+
short circuit current temperature coefficient [A/K]
366+
gamma_ref : float
367+
diode (ideality) factor at STC [unitless]
368+
mu_gamma : float
369+
temperature coefficient for diode (ideality) factor [1/K]
370+
I_L_ref : float
371+
light current at STC [A]
372+
I_o_ref : float
373+
dark current at STC [A]
374+
R_sh_ref : float
375+
shunt resistance at STC [ohm]
376+
R_sh_0 : float
377+
shunt resistance at zero irradiance [ohm]
378+
R_sh_exp : float
379+
exponential factor defining decrease in shunt resistance with
380+
increasing effective irradiance
381+
R_s : float
382+
series resistance at STC [ohm]
383+
cells_in_series : int
384+
number of cells in series
385+
EgRef : float
386+
effective band gap at STC [eV]
387+
388+
See also
389+
--------
390+
pvlib.pvsystem.calcparams_pvsyst
391+
pvlib.ivtools.sdm.fit_pvsyst_sandia
392+
393+
Notes
394+
-----
395+
This method is non-iterative. In some cases, it may be desirable to
396+
refine the estimated parameter values using a numerical optimizer like
397+
those provided in ``scipy.optimize``.
398+
399+
References
400+
----------
401+
.. [1] K. S. Anderson, C. W. Hansen, and M. Theristis, "A Noniterative
402+
Method of Estimating Parameter Values for the PVsyst Version 6
403+
Single-Diode Model From IEC 61853-1 Matrix Measurements," IEEE Journal
404+
of Photovoltaics, vol. 15, 3, 2025. :doi:`10.1109/JPHOTOV.2025.3554338`
405+
"""
406+
407+
try:
408+
import statsmodels.api as sm
409+
except ImportError:
410+
raise ImportError('fit_pvsyst_iec61853_sandia requires statsmodels')
411+
412+
if alpha_sc is None:
413+
mu_i_sc = _fit_tempco(i_sc[effective_irradiance==1000],
414+
temp_cell[effective_irradiance==1000])
415+
i_sc_ref = float(i_sc[(effective_irradiance==1000) & (temp_cell==25)])
416+
alpha_sc = mu_i_sc * i_sc_ref
417+
418+
if beta_mp is None:
419+
beta_mp = _fit_tempco(v_mp[effective_irradiance==1000],
420+
temp_cell[effective_irradiance==1000])
421+
422+
R_sh_ref, R_sh_0, R_sh_exp = _fit_shunt_resistances(
423+
i_sc, i_mp, v_mp, effective_irradiance, temp_cell, beta_mp,
424+
coeff=r_sh_coeff, min_irradiance=min_Rsh_irradiance
425+
)
426+
427+
if R_s is None:
428+
R_s = _fit_series_resistance(sm, v_oc, i_mp, v_mp)
429+
430+
filt = temp_cell == 25
431+
gamma_ref, mu_gamma = _fit_diode_ideality_factor(
432+
sm, i_sc[filt], v_oc[filt], i_mp[filt], v_mp[filt],
433+
effective_irradiance[filt], temp_cell[filt],
434+
R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series
435+
)
436+
437+
I_o_ref = _fit_saturation_current(
438+
i_sc, v_oc, effective_irradiance, temp_cell, gamma_ref, mu_gamma,
439+
R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef
440+
)
441+
442+
I_L_ref = _fit_photocurrent(
443+
sm, i_sc, effective_irradiance, temp_cell, alpha_sc,
444+
gamma_ref, mu_gamma,
445+
I_o_ref, R_sh_ref, R_sh_0, R_sh_exp, R_s, cells_in_series, EgRef
446+
)
447+
448+
gamma_ref, mu_gamma = \
449+
_fit_diode_ideality_factor_post(sm, i_mp, v_mp, effective_irradiance,
450+
temp_cell, alpha_sc, I_L_ref, I_o_ref,
451+
R_sh_ref, R_sh_0, R_sh_exp, R_s,
452+
cells_in_series, EgRef)
453+
454+
fitted_params = dict(
455+
alpha_sc=alpha_sc,
456+
gamma_ref=gamma_ref,
457+
mu_gamma=mu_gamma,
458+
I_L_ref=I_L_ref,
459+
I_o_ref=I_o_ref,
460+
R_sh_ref=R_sh_ref,
461+
R_sh_0=R_sh_0,
462+
R_sh_exp=R_sh_exp,
463+
R_s=R_s,
464+
cells_in_series=cells_in_series,
465+
EgRef=EgRef,
466+
)
467+
return fitted_params
468+
469+
470+
def _fit_tempco(values, temp_cell, temp_cell_ref=25):
471+
fit = np.polynomial.polynomial.Polynomial.fit(temp_cell, values, deg=1)
472+
intercept, slope = fit.convert().coef
473+
value_ref = intercept + slope*temp_cell_ref
474+
return slope / value_ref
475+
476+
477+
def _fit_shunt_resistances(i_sc, i_mp, v_mp, effective_irradiance, temp_cell,
478+
beta_v_mp, coeff=0.2, min_irradiance=None):
479+
if min_irradiance is None:
480+
min_irradiance = 0
481+
482+
mask = effective_irradiance >= min_irradiance
483+
i_sc = i_sc[mask]
484+
i_mp = i_mp[mask]
485+
v_mp = v_mp[mask]
486+
effective_irradiance = effective_irradiance[mask]
487+
temp_cell = temp_cell[mask]
488+
489+
# Equation 10
490+
Rsh_est = (
491+
(v_mp / (1 + beta_v_mp * (temp_cell - 25)))
492+
/ (coeff * (i_sc - i_mp))
493+
)
494+
Rshexp = 5.5
495+
496+
# Eq 11
497+
y = Rsh_est
498+
x = np.exp(-Rshexp * effective_irradiance / 1000)
499+
500+
fit = np.polynomial.polynomial.Polynomial.fit(x, y, deg=1)
501+
intercept, slope = fit.convert().coef
502+
Rshbase = intercept
503+
Rsh0 = slope + Rshbase
504+
505+
# Eq 12
506+
expRshexp = np.exp(-Rshexp)
507+
Rshref = Rshbase * (1 - expRshexp) + Rsh0 * expRshexp
508+
509+
return Rshref, Rsh0, Rshexp
510+
511+
512+
def _fit_series_resistance(sm, v_oc, i_mp, v_mp):
513+
# Stein et al 2014, https://doi.org/10.1109/PVSC.2014.6925326
514+
515+
# Eq 13
516+
x = np.array([i_mp, np.log(i_mp), v_mp]).T
517+
x = sm.add_constant(x)
518+
y = v_oc
519+
520+
results = sm.OLS(endog=y, exog=x).fit()
521+
R_s = results.params['x1']
522+
return R_s
523+
524+
525+
def _fit_diode_ideality_factor(sm, i_sc, v_oc, i_mp, v_mp,
526+
effective_irradiance, temp_cell,
527+
R_sh_ref, R_sh_0, R_sh_exp, R_s,
528+
cells_in_series):
529+
530+
NsVth = _pvsyst_nNsVth(temp_cell, gamma=1, cells_in_series=cells_in_series)
531+
Rsh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp)
532+
term1 = (i_sc * (1 + R_s/Rsh) - v_oc / Rsh) # Eq 15
533+
term2 = (i_sc - i_mp) * (1 + R_s/Rsh) - v_mp / Rsh # Eq 16
534+
535+
# Eq 14
536+
x1 = NsVth * np.log(term2 / term1)
537+
538+
x = np.array([x1]).T
539+
y = v_mp + i_mp*R_s - v_oc
540+
541+
results = sm.OLS(endog=y, exog=x).fit()
542+
gamma_ref = results.params[0]
543+
return gamma_ref, 0
544+
545+
546+
def _fit_saturation_current(i_sc, v_oc, effective_irradiance, temp_cell,
547+
gamma_ref, mu_gamma,
548+
R_sh_ref, R_sh_0, R_sh_exp, R_s,
549+
cells_in_series, EgRef):
550+
R_sh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp)
551+
gamma = _pvsyst_gamma(temp_cell, gamma_ref, mu_gamma)
552+
nNsVth = _pvsyst_nNsVth(temp_cell, gamma, cells_in_series)
553+
554+
# Eq 17
555+
I_o_est = (i_sc * (1 + R_s/R_sh) - v_oc/R_sh) / (np.expm1(v_oc / nNsVth))
556+
x = _pvsyst_Io(temp_cell, gamma, I_o_ref=1, EgRef=EgRef)
557+
558+
# Eq 18
559+
log_I_o_ref = np.mean(np.log(I_o_est) - np.log(x))
560+
I_o_ref = np.exp(log_I_o_ref)
561+
562+
return I_o_ref
563+
564+
565+
def _fit_photocurrent(sm, i_sc, effective_irradiance, temp_cell,
566+
alpha_sc, gamma_ref, mu_gamma, I_o_ref,
567+
R_sh_ref, R_sh_0, R_sh_exp, R_s,
568+
cells_in_series, EgRef):
569+
R_sh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp)
570+
gamma = _pvsyst_gamma(temp_cell, gamma_ref, mu_gamma)
571+
I_o = _pvsyst_Io(temp_cell, gamma, I_o_ref, EgRef)
572+
nNsVth = _pvsyst_nNsVth(temp_cell, gamma, cells_in_series)
573+
574+
# Eq 19
575+
I_L_est = i_sc + I_o * (np.expm1(i_sc * R_s / nNsVth)) + i_sc * R_s / R_sh
576+
577+
# Eq 20
578+
x = np.array([effective_irradiance / 1000]).T
579+
y = I_L_est - effective_irradiance / 1000 * alpha_sc * (temp_cell - 25)
580+
results = sm.OLS(endog=y, exog=x).fit()
581+
I_L_ref = results.params['x1']
582+
return I_L_ref
583+
584+
585+
def _fit_diode_ideality_factor_post(sm, i_mp, v_mp, effective_irradiance,
586+
temp_cell, alpha_sc, I_L_ref, I_o_ref,
587+
R_sh_ref, R_sh_0, R_sh_exp, R_s,
588+
cells_in_series, EgRef):
589+
590+
Rsh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp)
591+
I_L = _pvsyst_IL(effective_irradiance, temp_cell, I_L_ref, alpha_sc)
592+
NsVth = _pvsyst_nNsVth(temp_cell, gamma=1, cells_in_series=cells_in_series)
593+
594+
Tref_K = 25 + 273.15
595+
Tcell_K = temp_cell + 273.15
596+
597+
# Eq 21
598+
k = constants.k # Boltzmann constant in J/K
599+
q = constants.e # elementary charge in coulomb
600+
numerator = (
601+
(q * EgRef / k) * (1/Tref_K - 1/Tcell_K)
602+
+ (v_mp + i_mp*R_s) / NsVth
603+
)
604+
denominator = (
605+
np.log((I_L - i_mp - (v_mp+i_mp*R_s) / Rsh) / I_o_ref)
606+
- 3 * np.log(Tcell_K / Tref_K)
607+
)
608+
gamma_est = numerator / denominator
609+
610+
# Eq 22
611+
x = np.array([temp_cell - 25]).T
612+
x = sm.add_constant(x)
613+
y = gamma_est
614+
615+
results = sm.OLS(endog=y, exog=x).fit()
616+
gamma_ref, mu_gamma = results.params
617+
return gamma_ref, mu_gamma

0 commit comments

Comments
 (0)