Skip to content

Commit 84bac97

Browse files
committed
Merge branch 'ivtools' of https://github.com/cwhanse/pvlib-python
2 parents 3e88458 + 6d6733d commit 84bac97

File tree

5 files changed

+306
-0
lines changed

5 files changed

+306
-0
lines changed

ci/requirements-py35.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ dependencies:
2020
- coveralls
2121
- pytest-mock
2222
- pytest-timeout
23+
- nrel-pysam
2324
- pvfactors==1.0.1

ci/requirements-py36.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ dependencies:
1919
- pip:
2020
- coveralls
2121
- pytest-mock
22+
- nrel-pysam==1.2.1
2223
- pytest-timeout
2324
- pvfactors==1.0.1

ci/requirements-py37.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ dependencies:
1919
- pip:
2020
- coveralls
2121
- pytest-mock
22+
- nrel-pysam
2223
- pytest-timeout
2324
- pvfactors==1.0.1

pvlib/ivtools.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Created on Fri Mar 29 10:34:10 2019
4+
5+
@author: cwhanse
6+
"""
7+
8+
import numpy as np
9+
10+
11+
def fit_cec_with_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc,
12+
gamma_pmp, cells_in_series, temp_ref=25):
13+
'''
14+
Estimates parameters for the CEC single diode model using the SAM SDK.
15+
16+
Parameters
17+
----------
18+
celltype : str
19+
Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte',
20+
'amorphous'
21+
v_mp : float
22+
Voltage at maximum power point at standard test condition (STC)
23+
i_mp : float
24+
Current at maximum power point at STC
25+
Voc : float
26+
Open circuit voltage at STC
27+
i_sc : float
28+
Short circuit current at STC
29+
alpha_sc : float
30+
Temperature coefficient of short circuit current at STC, A/C
31+
beta_voc : float
32+
Temperature coefficient of open circuit voltage at STC, V/C
33+
gamma_pmp : float
34+
Temperature coefficient of power at maximum point point at STC, %/C
35+
cells_in_series : int
36+
Number of cells in series
37+
temp_ref : float, default 25
38+
Reference temperature condition
39+
40+
Returns
41+
-------
42+
a_ref : float
43+
The product of the usual diode ideality factor (n, unitless),
44+
number of cells in series (Ns), and cell thermal voltage at reference
45+
conditions, in units of V.
46+
47+
I_L_ref : float
48+
The light-generated current (or photocurrent) at reference conditions,
49+
in amperes.
50+
51+
I_o_ref : float
52+
The dark or diode reverse saturation current at reference conditions,
53+
in amperes.
54+
55+
R_sh_ref : float
56+
The shunt resistance at reference conditions, in ohms.
57+
58+
R_s : float
59+
The series resistance at reference conditions, in ohms.
60+
61+
Adjust : float
62+
The adjustment to the temperature coefficient for short circuit
63+
current, in percent
64+
65+
Raises:
66+
ImportError if NREL-PySAM is not installed
67+
'''
68+
69+
try:
70+
from PySAM import PySSC
71+
except ImportError as e:
72+
raise(e)
73+
74+
datadict = {'tech_model': '6parsolve', 'financial_model': 'none',
75+
'celltype': celltype, 'Vmp': v_mp,
76+
'Imp': i_mp, 'Voc': v_oc, 'Isc': i_sc, 'alpha_isc': alpha_sc,
77+
'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp,
78+
'Nser': cells_in_series, 'Tref': temp_ref}
79+
80+
result = PySSC.ssc_sim_from_dict(datadict)
81+
a_ref = result['a']
82+
I_L_ref = result['Il']
83+
I_o_ref = result['Io']
84+
R_s = result['Rs']
85+
R_sh_ref = result['Rsh']
86+
Adjust = result['Adj']
87+
88+
return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust
89+
90+
91+
def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1):
92+
""" Fits the single diode equation to an IV curve.
93+
94+
If fitting fails, returns NaN in each parameter.
95+
96+
Parameters
97+
----------
98+
v : numeric
99+
Voltage at each point on the IV curve, from 0 to v_oc
100+
101+
i : numeric
102+
Current at each point on the IV curve, from i_sc to 0
103+
104+
v_oc : float
105+
Open circuit voltage
106+
107+
i_sc : float
108+
Short circuit current
109+
110+
v_mp : float
111+
Voltage at maximum power point
112+
113+
i_mp : float
114+
Current at maximum power point
115+
116+
vlim : float, default 0.2
117+
defines linear portion of IV curve i.e. V <= vlim * v_oc
118+
119+
ilim : float, default 0.1
120+
defines exponential portion of IV curve i.e. I > ilim * i_sc
121+
122+
Returns
123+
-------
124+
tuple of the following elements:
125+
126+
photocurrent : float
127+
photocurrent, A
128+
129+
saturation_current : float
130+
dark (saturation) current, A
131+
132+
resistance_shunt : float
133+
shunt (parallel) resistance, ohm
134+
135+
resistance_series : float
136+
series resistance, ohm
137+
138+
nNsVth : float
139+
product of diode (ideality) factor n (unitless) x number of
140+
cells in series Ns (unitless) x cell thermal voltage Vth (V), V
141+
142+
References
143+
----------
144+
[1] C. B. Jones, C. W. Hansen, Single Diode Parameter Extraction from
145+
In-Field Photovoltaic I-V Curves on a Single Board Computer, 46th IEEE
146+
Photovoltaic Specialist Conference, Chicago, IL, 2019
147+
"""
148+
# Find intercept and slope of linear portion of IV curve.
149+
# Start with V < vlim * v_oc, extend by adding points until slope is
150+
# acceptable
151+
beta = [np.nan for i in range(5)]
152+
# get index of largest voltage less than/equal to limit
153+
idx = _max_index(v, vlim * v_oc)
154+
while np.isnan(beta[1]) and (idx <= len(v)):
155+
try:
156+
coef = np.polyfit(v[:idx], i[:idx], deg=1)
157+
if coef[0] < 0:
158+
# intercept term
159+
beta[0] = coef[1].item()
160+
# sign change of slope to get positive parameter value
161+
beta[1] = -coef[0].item()
162+
except Exception as e:
163+
raise e
164+
if np.isnan(beta[1]):
165+
idx += 1
166+
167+
if not np.isnan(beta[0]):
168+
# Find parameters from exponential portion of IV curve
169+
y = beta[0] - beta[1] * v - i
170+
x = np.array([np.ones_like(v), v, i]).T
171+
idx = _min_index(y, ilim * i_sc)
172+
try:
173+
result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:]))
174+
coef = result[0]
175+
beta[3] = coef[1].item()
176+
beta[4] = coef[2].item()
177+
except Exception as e:
178+
raise e
179+
180+
if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]):
181+
# calculate parameters
182+
nNsVth = 1.0 / beta[3]
183+
Rs = beta[4] / beta[3]
184+
Gp = beta[1] / (1.0 - Rs * beta[1])
185+
Rsh = 1.0 / Gp
186+
IL = (1 + Gp * Rs) * beta[0]
187+
# calculate I0
188+
I0_v_mp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta[3])
189+
I0_v_oc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta[3])
190+
if (I0_v_mp > 0) and (I0_v_oc > 0):
191+
I0 = 0.5 * (I0_v_mp + I0_v_oc)
192+
elif (I0_v_mp > 0):
193+
I0 = I0_v_mp
194+
elif (I0_v_oc > 0):
195+
I0 = I0_v_oc
196+
else:
197+
I0 = np.nan
198+
else:
199+
IL = I0 = Rs = Rsh = nNsVth = np.nan
200+
201+
return IL, I0, Rsh, Rs, nNsVth
202+
203+
204+
def _calc_I0(IL, I, V, Gp, Rs, beta3):
205+
return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I))
206+
207+
208+
def _max_index(x, xlim):
209+
""" Finds maximum index of value of x <= xlim """
210+
return int(np.argwhere(x <= xlim)[-1])
211+
212+
213+
def _min_index(x, xlim):
214+
""" Finds minimum index of value of x > xlim """
215+
return int(np.argwhere(x > xlim)[0])

pvlib/test/test_ivtools.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Created on Thu May 9 10:51:15 2019
4+
5+
@author: cwhanse
6+
"""
7+
8+
import numpy as np
9+
import pandas as pd
10+
import pytest
11+
from pvlib import pvsystem
12+
from pvlib import ivtools
13+
from conftest import requires_scipy
14+
15+
16+
def get_test_iv_params():
17+
return {'IL': 8.0, 'I0': 5e-10, 'Rsh': 1000, 'Rs': 0.2, 'nNsVth': 1.61864}
18+
19+
20+
def get_cec_params_cansol_cs5p_220p():
21+
return {'V_mp_ref': 46.6, 'I_mp_ref': 4.73, 'V_oc_ref': 58.3,
22+
'I_sc_ref': 5.05, 'alpha_isc': 0.000495, 'beta_voc': -0.003372,
23+
'gamma_pmp': -0.43, 'cells_in_series': 96}
24+
25+
26+
@pytest.fixture()
27+
def sam_data():
28+
data = {}
29+
data['cecmod'] = pvsystem.retrieve_sam('cecmod')
30+
return data
31+
32+
33+
@pytest.fixture()
34+
def cec_module_parameters(sam_data):
35+
modules = sam_data['cecmod']
36+
module = "Canadian_Solar_CS5P_220P"
37+
module_parameters = modules[module]
38+
return module_parameters
39+
40+
41+
@requires_scipy
42+
def test_fit_sde_sandia():
43+
test_params = get_test_iv_params()
44+
testcurve = pvsystem.singlediode(photocurrent=test_params['IL'],
45+
saturation_current=test_params['I0'],
46+
resistance_shunt=test_params['Rsh'],
47+
resistance_series=test_params['Rs'],
48+
nNsVth=test_params['nNsVth'],
49+
ivcurve_pnts=300)
50+
expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs',
51+
'nNsVth'])
52+
result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'],
53+
v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], v_mp=testcurve['v_mp'],
54+
i_mp=testcurve['i_mp'])
55+
assert np.allclose(result, expected, rtol=5e-5)
56+
57+
58+
def test_fit_cec_with_SAM():
59+
sam_parameters = cec_module_parameters(sam_data)
60+
cec_list_data = get_cec_params_cansol_cs5p_220p()
61+
# convert from %/C to A/C and V/C
62+
alpha_sc = cec_list_data['alpha_isc'] * cec_list_data['I_sc_ref']
63+
beta_oc = cec_list_data['beta_voc'] * cec_list_data['V_oc_ref']
64+
65+
I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \
66+
ivtools.fit_cec_with_sam(celltype='polySi',
67+
v_mp=cec_list_data['V_mp_ref'], i_mp=cec_list_data['I_mp_ref'],
68+
v_oc=cec_list_data['V_oc_ref'], i_sc=cec_list_data['I_sc_ref'],
69+
alpha_sc=alpha_sc, beta_voc=beta_oc,
70+
gamma_pmp=cec_list_data['gamma_pmp'],
71+
cells_in_series=cec_list_data['cells_in_series'], temp_ref=25)
72+
modeled = pd.Series(index=sam_parameters.index, data=cec_list_data)
73+
modeled['a_ref'] = a_ref
74+
modeled['I_L_ref'] = I_L_ref
75+
modeled['I_o_ref'] = I_o_ref
76+
modeled['R_sh_ref'] = R_sh_ref
77+
modeled['R_s'] = R_s
78+
modeled['Adjust'] = Adjust
79+
modeled['alpha_sc'] = alpha_sc
80+
modeled['beta_oc'] = beta_oc
81+
modeled['gamma_r'] = cec_list_data['gamma_pmp']
82+
modeled['N_s'] = cec_list_data['cells_in_series']
83+
modeled = modeled.dropna()
84+
expected = pd.Series(index=modeled.index, data=np.nan)
85+
for k in modeled.keys():
86+
expected[k] = sam_parameters[k]
87+
assert np.isclose(modeled.values, expected.values, rtol=1e-2)
88+

0 commit comments

Comments
 (0)