Skip to content

Commit 0b147e8

Browse files
committed
functions and tests
1 parent 5aa9ba1 commit 0b147e8

File tree

2 files changed

+335
-2
lines changed

2 files changed

+335
-2
lines changed

pvlib/ivtools/sdm.py

Lines changed: 289 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
"""
88

99
import numpy as np
10+
import pandas as pd
1011

1112
from scipy import constants
1213
from scipy import optimize
1314
from scipy.special import lambertw
1415

15-
from pvlib.pvsystem import calcparams_pvsyst, singlediode, v_from_i
16+
from pvlib.pvsystem import (calcparams_pvsyst, calcparams_cec, singlediode,
17+
v_from_i)
1618
from pvlib.singlediode import bishop88_mpp
1719

1820
from pvlib.ivtools.utils import rectify_iv_curve, _numdiff
@@ -24,6 +26,17 @@
2426
CONSTANTS = {'E0': 1000.0, 'T0': 25.0, 'k': constants.k, 'q': constants.e}
2527

2628

29+
IEC61853 = pd.DataFrame(
30+
columns=['effective_irradiance', 'temp_cell'],
31+
data = np.array(
32+
[[100, 100, 100, 100, 200, 200, 200, 200, 400, 400, 400, 400,
33+
600, 600, 600, 600, 800, 800, 800, 800, 1000, 1000, 1000, 1000,
34+
1100, 1100, 1100, 1100],
35+
[15, 25, 50, 75, 15, 25, 50, 75, 15, 25, 50, 75, 15, 25, 50, 75,
36+
15, 25, 50, 75, 15, 25, 50, 75, 15, 25, 50, 75]]).T,
37+
dtype=np.float64)
38+
39+
2740
def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc,
2841
gamma_pmp, cells_in_series, temp_ref=25):
2942
"""
@@ -1354,3 +1367,278 @@ def maxp(temp_cell, irrad_ref, alpha_sc, gamma_ref, mu_gamma, I_L_ref,
13541367
gamma_pdc = _first_order_centered_difference(maxp, x0=temp_ref, args=args)
13551368

13561369
return gamma_pdc / pmp
1370+
1371+
1372+
def _pvsyst_objfun(pvs_mod, cec_ivs, ee, tc, cs):
1373+
1374+
# translate the guess into named args that are used in the functions
1375+
# order : [alpha_sc, gamma_ref, mu_gamma, I_L_ref, I_o_ref,
1376+
# R_sh_mult, R_sh_ref, R_s]
1377+
# cec_ivs : DataFrame with columns i_sc, v_oc, i_mp, v_mp, p_mp
1378+
# ee : effective irradiance
1379+
# tc : cell temperature
1380+
# cs : cells in series
1381+
alpha_sc = pvs_mod[0]
1382+
gamma_ref = pvs_mod[1]
1383+
mu_gamma = pvs_mod[2]
1384+
I_L_ref = pvs_mod[3]
1385+
I_o_ref = pvs_mod[4]
1386+
R_sh_mult = pvs_mod[5]
1387+
R_sh_ref = pvs_mod[6]
1388+
R_s = pvs_mod[7]
1389+
1390+
R_sh_0 = R_sh_ref * R_sh_mult
1391+
1392+
pvs_params = calcparams_pvsyst(
1393+
ee, tc, alpha_sc, gamma_ref, mu_gamma, I_L_ref, I_o_ref, R_sh_ref,
1394+
R_sh_0, R_s, cs)
1395+
1396+
pvsyst_ivs = singlediode(*pvs_params)
1397+
1398+
isc_diff = np.abs((pvsyst_ivs['i_sc'] - cec_ivs['i_sc']) /
1399+
cec_ivs['i_sc']).mean()
1400+
imp_diff = np.abs((pvsyst_ivs['i_mp'] - cec_ivs['i_mp']) /
1401+
cec_ivs['i_mp']).mean()
1402+
voc_diff = np.abs((pvsyst_ivs['v_oc'] - cec_ivs['v_oc']) /
1403+
cec_ivs['v_oc']).mean()
1404+
vmp_diff = np.abs((pvsyst_ivs['v_mp'] - cec_ivs['v_mp']) /
1405+
cec_ivs['v_mp']).mean()
1406+
pmp_diff = np.abs((pvsyst_ivs['p_mp'] - cec_ivs['p_mp']) /
1407+
cec_ivs['p_mp']).mean()
1408+
1409+
mean_abs_diff = (isc_diff + imp_diff + voc_diff + vmp_diff + pmp_diff) / 5
1410+
1411+
return mean_abs_diff
1412+
1413+
1414+
def convert_cec_pvsyst(cec_model, cells_in_series):
1415+
r"""
1416+
Convert a CEC model to a PVsyst model.
1417+
1418+
Uses optimization to fit the PVsyst model to :math:`I_{sc}`,
1419+
:math:`V_{oc}`, :math:`V_{mp}`, :math:`I_{mp}`, and :math:`P_{mp}`,
1420+
calculated using the input CEC model at the IEC 61853-3 conditions [2]_.
1421+
1422+
Parameters
1423+
----------
1424+
cec_model : dict or DataFrame
1425+
Must include keys: 'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
1426+
'R_sh_ref', 'R_s', 'Adjust'
1427+
cell_in_series : int
1428+
Number of cells in series.
1429+
1430+
Returns
1431+
-------
1432+
dict with the following elements:
1433+
alpha_sc : float
1434+
Short-circuit current temperature coefficient [A/C] .
1435+
I_L_ref : float
1436+
The light-generated current (or photocurrent) at reference
1437+
conditions [A].
1438+
I_o_ref : float
1439+
The dark or diode reverse saturation current at reference
1440+
conditions [A].
1441+
EgRef : float
1442+
The energy bandgap at reference temperature [eV].
1443+
R_s : float
1444+
The series resistance at reference conditions [ohm].
1445+
R_sh_ref : float
1446+
The shunt resistance at reference conditions [ohm].
1447+
R_sh_0 : float
1448+
Shunt resistance at zero irradiance [ohm].
1449+
R_sh_exp : float
1450+
Exponential factor defining decrease in shunt resistance with
1451+
increasing effective irradiance [unitless].
1452+
gamma_ref : float
1453+
Diode (ideality) factor at reference conditions [unitless].
1454+
mu_gamma : float
1455+
Temperature coefficient for diode (ideality) factor at reference
1456+
conditions [1/K].
1457+
cells_in_series : int
1458+
Number of cells in series.
1459+
1460+
Notes
1461+
-----
1462+
Reference conditions are irradiance of 1000 W/m⁻² and cell temperature of
1463+
25 °C.
1464+
1465+
References
1466+
----------
1467+
.. [1] L. Deville et al., "Parameter Translation for Photovoltaic Single
1468+
Diode Models", submitted. 2024
1469+
1470+
.. [2] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
1471+
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.
1472+
"""
1473+
1474+
# calculate target IV curve values
1475+
cec_params = calcparams_cec(
1476+
IEC61853['effective_irradiance'],
1477+
IEC61853['temp_cell'],
1478+
**cec_model)
1479+
cec_ivs = singlediode(*cec_params)
1480+
1481+
# initial guess at PVsyst parameters
1482+
# Order in list is alpha_sc, gamma_ref, mu_gamma, I_L_ref, I_o_ref,
1483+
# Rsh_mult = R_sh_0 / R_sh_ref, R_sh_ref, R_s
1484+
initial = [0, 1.2, 0.001, cec_model['I_L_ref'], cec_model['I_o_ref'],
1485+
12, 1000, cec_model['R_s']]
1486+
1487+
# bounds for PVsyst parameters
1488+
b_alpha = (-1, 1)
1489+
b_gamma = (1, 2)
1490+
b_mu = (-1, 1)
1491+
b_IL = (1e-12, 100)
1492+
b_Io = (1e-24, 0.1)
1493+
b_Rmult = (1, 20)
1494+
b_Rsh = (100, 1e6)
1495+
b_Rs = (1e-12, 10)
1496+
bounds = [b_alpha, b_gamma, b_mu, b_IL, b_Io, b_Rmult, b_Rsh, b_Rs]
1497+
1498+
# optimization to find PVsyst parameters
1499+
result = optimize.minimize(
1500+
_pvsyst_objfun, initial,
1501+
args=(cec_ivs, IEC61853['effective_irradiance'],
1502+
IEC61853['temp_cell'], cells_in_series),
1503+
method='Nelder-Mead', bounds=bounds,
1504+
options={'maxiter': 5000, 'maxfev': 5000, 'xatol': 0.001})
1505+
alpha_sc, gamma, mu_gamma, I_L_ref, I_o_ref, Rsh_mult, R_sh_ref, R_s = \
1506+
result.x
1507+
1508+
R_sh_0 = Rsh_mult * R_sh_ref
1509+
R_sh_exp = 5.5
1510+
EgRef = 1.121 # default for all modules in the CEC model
1511+
return {'alpha_sc': alpha_sc,
1512+
'I_L_ref': I_L_ref, 'I_o_ref': I_o_ref, 'EgRef': EgRef, 'R_s': R_s,
1513+
'R_sh_ref': R_sh_ref, 'R_sh_0': R_sh_0, 'R_sh_exp': R_sh_exp,
1514+
'gamma_ref': gamma, 'mu_gamma': mu_gamma,
1515+
'cells_in_series': cells_in_series,
1516+
}
1517+
1518+
1519+
def _cec_objfun(cec_mod, pvs_ivs, ee, tc, alpha_sc):
1520+
# translate the guess into named args that are used in the functions
1521+
# order : [I_L_ref, I_o_ref, a_ref, R_sh_ref, R_s, alpha_sc, Adjust]
1522+
# pvs_ivs : DataFrame with columns i_sc, v_oc, i_mp, v_mp, p_mp
1523+
# ee : effective irradiance
1524+
# tc : cell temperature
1525+
# alpha_sc : temperature coefficient for Isc
1526+
I_L_ref = cec_mod[0]
1527+
I_o_ref = cec_mod[1]
1528+
a_ref = cec_mod[2]
1529+
R_sh_ref = cec_mod[3]
1530+
R_s = cec_mod[4]
1531+
Adjust = cec_mod[5]
1532+
alpha_sc = alpha_sc
1533+
1534+
cec_params = calcparams_cec(
1535+
ee, tc, alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust)
1536+
cec_ivs = singlediode(*cec_params)
1537+
1538+
isc_rss = np.sqrt(sum((cec_ivs['i_sc'] - pvs_ivs['i_sc'])**2))
1539+
imp_rss = np.sqrt(sum((cec_ivs['i_mp'] - pvs_ivs['i_mp'])**2))
1540+
voc_rss = np.sqrt(sum((cec_ivs['v_oc'] - pvs_ivs['v_oc'])**2))
1541+
vmp_rss = np.sqrt(sum((cec_ivs['v_mp'] - pvs_ivs['v_mp'])**2))
1542+
pmp_rss = np.sqrt(sum((cec_ivs['p_mp'] - pvs_ivs['p_mp'])**2))
1543+
1544+
mean_diff = (isc_rss+imp_rss+voc_rss+vmp_rss+pmp_rss) / 5
1545+
1546+
return mean_diff
1547+
1548+
1549+
def convert_pvsyst_cec(pvsyst_model):
1550+
r"""
1551+
Convert a PVsyst model to a CEC model.
1552+
1553+
Uses optimization to fit the CEC model to :math:`I_{sc}`,
1554+
:math:`V_{oc}`, :math:`V_{mp}`, :math:`I_{mp}`, and :math:`P_{mp}`,
1555+
calculated using the input PVsyst model at the IEC 61853-3 conditions [2]_.
1556+
1557+
Parameters
1558+
----------
1559+
cec_model : dict or DataFrame
1560+
Must include keys: 'alpha_sc', 'I_L_ref', 'I_o_ref', 'EgRef', 'R_s',
1561+
'R_sh_ref', 'R_sh_0', 'R_sh_exp', 'gamma_ref', 'mu_gamma',
1562+
'cells_in_series'
1563+
1564+
Returns
1565+
-------
1566+
dict with the following elements:
1567+
I_L_ref : float
1568+
The light-generated current (or photocurrent) at reference
1569+
conditions [A].
1570+
I_o_ref : float
1571+
The dark or diode reverse saturation current at reference
1572+
conditions [A].
1573+
R_s : float
1574+
The series resistance at reference conditions [ohm].
1575+
R_sh_ref : float
1576+
The shunt resistance at reference conditions [ohm].
1577+
a_ref : float
1578+
The product of the usual diode ideality factor ``n`` (unitless),
1579+
number of cells in series ``Ns``, and cell thermal voltage at
1580+
reference conditions [V].
1581+
Adjust : float
1582+
The adjustment to the temperature coefficient for short circuit
1583+
current, in percent.
1584+
EgRef : float
1585+
The energy bandgap at reference temperature [eV].
1586+
dEgdT : float
1587+
The temperature dependence of the energy bandgap at reference
1588+
conditions [1/K].
1589+
1590+
Notes
1591+
-----
1592+
Reference conditions are irradiance of 1000 W/m⁻² and cell temperature of
1593+
25 °C.
1594+
1595+
References
1596+
----------
1597+
.. [1] L. Deville et al., "Parameter Translation for Photovoltaic Single
1598+
Diode Models", submitted. 2024.
1599+
1600+
.. [2] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
1601+
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.
1602+
"""
1603+
# calculate target IV curve values
1604+
pvs_params = calcparams_pvsyst(
1605+
IEC61853['effective_irradiance'],
1606+
IEC61853['temp_cell'],
1607+
**pvsyst_model)
1608+
pvsyst_ivs = singlediode(*pvs_params)
1609+
1610+
# set EgRef and dEgdT to CEC defaults
1611+
EgRef = 1.121
1612+
dEgdT = -0.0002677
1613+
1614+
# initial guess
1615+
# order must match _pvsyst_objfun
1616+
# order : [I_L_ref, I_o_ref, a_ref, R_sh_ref, R_s, alpha_sc, Adjust]
1617+
nNsVth = pvsyst_model['gamma_ref'] * pvsyst_model['cells_in_series'] \
1618+
* 0.025
1619+
initial = [pvsyst_model['I_L_ref'], pvsyst_model['I_o_ref'],
1620+
nNsVth, pvsyst_model['R_sh_ref'], pvsyst_model['R_s'],
1621+
0]
1622+
1623+
# bounds for PVsyst parameters
1624+
b_IL = (1e-12, 100)
1625+
b_Io = (1e-24, 0.1)
1626+
b_aref = (1e-12, 1000)
1627+
b_Rsh = (100, 1e6)
1628+
b_Rs = (1e-12, 10)
1629+
b_Adjust = (-100, 100)
1630+
bounds = [b_IL, b_Io, b_aref, b_Rsh, b_Rs, b_Adjust]
1631+
1632+
result = optimize.minimize(
1633+
_cec_objfun, initial,
1634+
args=(pvsyst_ivs, IEC61853['effective_irradiance'],
1635+
IEC61853['temp_cell'], pvsyst_model['alpha_sc']),
1636+
method='Nelder-Mead', bounds=bounds,
1637+
options={'maxiter': 5000, 'maxfev': 5000, 'xatol': 0.001})
1638+
I_L_ref, I_o_ref, a_ref, R_sh_ref, R_s, Adjust = result.x
1639+
1640+
return {'alpha_sc': pvsyst_model['alpha_sc'],
1641+
'a_ref': a_ref, 'I_L_ref': I_L_ref, 'I_o_ref': I_o_ref,
1642+
'R_sh_ref': R_sh_ref, 'R_s': R_s, 'Adjust': Adjust,
1643+
'EgRef': EgRef, 'dEgdT': dEgdT
1644+
}

pvlib/tests/ivtools/test_sdm.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from pvlib.ivtools import sdm
88
from pvlib import pvsystem
9-
from pvlib._deprecation import pvlibDeprecationWarning
109

1110
from pvlib.tests.conftest import requires_pysam, requires_statsmodels
1211

@@ -405,3 +404,49 @@ def test_pvsyst_temperature_coeff():
405404
params['I_L_ref'], params['I_o_ref'], params['R_sh_ref'],
406405
params['R_sh_0'], params['R_s'], params['cells_in_series'])
407406
assert_allclose(gamma_pdc, expected, rtol=0.0005)
407+
408+
409+
def test_convert_cec_pvsyst():
410+
cells_in_series = 66
411+
trina660_cec = {'I_L_ref': 18.4759, 'I_o_ref': 5.31e-12,
412+
'EgRef': 1.121, 'dEgdT': -0.0002677,
413+
'R_s': 0.159916, 'R_sh_ref': 113.991, 'a_ref': 1.59068,
414+
'Adjust': 6.42247, 'alpha_sc': 0.00629}
415+
trina660_pvsyst_est = sdm.convert_cec_pvsyst(trina660_cec,
416+
cells_in_series)
417+
pvsyst_expected = {'alpha_sc': 0.007478218748188788,
418+
'I_L_ref': 18.227679597516214,
419+
'I_o_ref': 2.7418999402908e-11,
420+
'EgRef': 1.121,
421+
'R_s': 0.16331908293164496,
422+
'R_sh_ref': 5267.928954454954,
423+
'R_sh_0': 60171.206687871425,
424+
'R_sh_exp': 5.5,
425+
'gamma_ref': 1.0,
426+
'mu_gamma': -6.349173477135307e-05,
427+
'cells_in_series': 66}
428+
429+
assert np.all([np.isclose(trina660_pvsyst_est[k], pvsyst_expected[k],
430+
rtol=1e-3)
431+
for k in pvsyst_expected])
432+
433+
434+
def test_convert_pvsyst_cec():
435+
trina660_pvsyst = {'alpha_sc': 0.0074, 'I_o_ref': 3.3e-11, 'EgRef': 1.121,
436+
'R_s': 0.156, 'R_sh_ref': 200, 'R_sh_0': 800,
437+
'R_sh_exp': 5.5, 'gamma_ref': 1.002, 'mu_gamma': 1e-3,
438+
'cells_in_series': 66}
439+
trina660_cec_est = sdm.convert_pvsyst_cec(trina660_pvsyst)
440+
cec_expected = {'alpha_sc': 0.0074,
441+
'I_L_ref': 18.05154226834071,
442+
'I_o_ref': 2.6863417875143392e-14,
443+
'EgRef': 1.121,
444+
'dEgdT': -0.0002677,
445+
'R_s': 0.09436341848926795,
446+
'a_ref': 1.2954800250731866,
447+
'Adjust': 0.0011675969492410047,
448+
'cells_in_series': 66}
449+
450+
assert np.all([np.isclose(trina660_cec_est[k], cec_expected[k],
451+
rtol=1e-3)
452+
for k in cec_expected])

0 commit comments

Comments
 (0)