Skip to content

Commit 2c0cb3c

Browse files
committed
add optional ivcurve calculation to singlediode
1 parent 6a43bcd commit 2c0cb3c

File tree

3 files changed

+135
-74
lines changed

3 files changed

+135
-74
lines changed

docs/sphinx/source/whatsnew/v0.4.0.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Enhancements
4343
* Add solarposition.nrel_earthsun_distance function and option to
4444
calculate extraterrestrial radiation using the NREL solar position
4545
algorithm. (:issue:`211`, :issue:`215`)
46+
* pvsystem.singlediode can now calculate IV curves if a user supplies
47+
an ivcurve_pnts keyword argument. (:issue:`83`)
4648

4749

4850
Bug fixes

pvlib/pvsystem.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
406406
reference_irradiance)
407407

408408
def singlediode(self, photocurrent, saturation_current,
409-
resistance_series, resistance_shunt, nNsVth):
409+
resistance_series, resistance_shunt, nNsVth,
410+
ivcurve_pnts=None):
410411
"""Wrapper around the :py:func:`singlediode` function.
411412
412413
Parameters
@@ -418,7 +419,8 @@ def singlediode(self, photocurrent, saturation_current,
418419
See pvsystem.singlediode for details
419420
"""
420421
return singlediode(photocurrent, saturation_current,
421-
resistance_series, resistance_shunt, nNsVth)
422+
resistance_series, resistance_shunt, nNsVth,
423+
ivcurve_pnts=ivcurve_pnts)
422424

423425
def i_from_v(self, resistance_shunt, resistance_series, nNsVth, voltage,
424426
saturation_current, photocurrent):
@@ -1517,7 +1519,7 @@ def sapm_effective_irradiance(module, poa_direct, poa_diffuse,
15171519

15181520

15191521
def singlediode(photocurrent, saturation_current, resistance_series,
1520-
resistance_shunt, nNsVth):
1522+
resistance_shunt, nNsVth, ivcurve_pnts=None):
15211523
r'''
15221524
Solve the single-diode model to obtain a photovoltaic IV curve.
15231525
@@ -1564,13 +1566,18 @@ def singlediode(photocurrent, saturation_current, resistance_series,
15641566
temp_cell is the temperature of the p-n junction in Kelvin, and
15651567
q is the charge of an electron (coulombs).
15661568
1569+
ivcurve_pnts : None or int
1570+
Number of points in the desired IV curve. If None or 0, no
1571+
IV curves will be produced.
1572+
15671573
Returns
15681574
-------
1569-
If ``photocurrent`` is a Series, a DataFrame with the following
1570-
columns. All columns have the same number of rows as the largest
1571-
input DataFrame.
1575+
If photocurrent is a Series and ivcurve_pnts is None, a DataFrame
1576+
with the columns described below. All columns have the same number
1577+
of rows as the largest input DataFrame.
15721578
1573-
If ``photocurrent`` is a scalar, a dict with the following keys.
1579+
If photocurrent is a scalar or ivcurve_pnts is not None, an
1580+
OrderedDict with the following keys.
15741581
15751582
* i_sc - short circuit current in amperes.
15761583
* v_oc - open circuit voltage in volts.
@@ -1579,6 +1586,8 @@ def singlediode(photocurrent, saturation_current, resistance_series,
15791586
* p_mp - power at maximum power point in watts.
15801587
* i_x - current, in amperes, at ``v = 0.5*v_oc``.
15811588
* i_xx - current, in amperes, at ``V = 0.5*(v_oc+v_mp)``.
1589+
* i - None or iv curve current.
1590+
* v - None or iv curve voltage.
15821591
15831592
Notes
15841593
-----
@@ -1632,37 +1641,32 @@ def singlediode(photocurrent, saturation_current, resistance_series,
16321641
i_xx = i_from_v(resistance_shunt, resistance_series, nNsVth,
16331642
0.5*(v_oc+v_mp), saturation_current, photocurrent)
16341643

1635-
# @wholmgren: need to move this stuff to a different function
1636-
# If the user says they want a curve of with number of points equal to
1637-
# NumPoints (must be >=2), then create a voltage array where voltage is
1638-
# zero in the first column, and Voc in the last column. Number of columns
1639-
# must equal NumPoints. Each row represents the voltage for one IV curve.
1640-
# Then create a current array where current is Isc in the first column, and
1641-
# zero in the last column, and each row represents the current in one IV
1642-
# curve. Thus the nth (V,I) point of curve m would be found as follows:
1643-
# (Result.V(m,n),Result.I(m,n)).
1644-
# if NumPoints >= 2
1645-
# s = ones(1,NumPoints); # shaping DataFrame to shape the column
1646-
# # DataFrame parameters into 2-D matrices
1647-
# Result.V = (Voc)*(0:1/(NumPoints-1):1);
1648-
# Result.I = I_from_V(Rsh*s, Rs*s, nNsVth*s, Result.V, I0*s, IL*s);
1649-
# end
1650-
1651-
dfout = {}
1652-
dfout['i_sc'] = i_sc
1653-
dfout['i_mp'] = i_mp
1654-
dfout['v_oc'] = v_oc
1655-
dfout['v_mp'] = v_mp
1656-
dfout['p_mp'] = p_mp
1657-
dfout['i_x'] = i_x
1658-
dfout['i_xx'] = i_xx
1644+
# create ivcurve
1645+
if ivcurve_pnts:
1646+
ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] *
1647+
np.linspace(0, 1, ivcurve_pnts))
1648+
ivcurve_i = i_from_v(
1649+
resistance_shunt, resistance_series, nNsVth, ivcurve_v.T,
1650+
saturation_current, photocurrent).T
1651+
else:
1652+
ivcurve_v = None
1653+
ivcurve_i = None
16591654

1660-
try:
1661-
dfout = pd.DataFrame(dfout, index=photocurrent.index)
1662-
except AttributeError:
1663-
pass
1655+
out = OrderedDict()
1656+
out['i_sc'] = i_sc
1657+
out['i_mp'] = i_mp
1658+
out['v_oc'] = v_oc
1659+
out['v_mp'] = v_mp
1660+
out['p_mp'] = p_mp
1661+
out['i_x'] = i_x
1662+
out['i_xx'] = i_xx
1663+
out['i'] = ivcurve_i
1664+
out['v'] = ivcurve_v
1665+
1666+
if isinstance(photocurrent, pd.Series) and not ivcurve_pnts:
1667+
out = pd.DataFrame(out, index=photocurrent.index)
16641668

1665-
return dfout
1669+
return out
16661670

16671671

16681672
# Created April,2014
@@ -1863,11 +1867,13 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
18631867
except ImportError:
18641868
raise ImportError('This function requires scipy')
18651869

1866-
Rsh = resistance_shunt
1867-
Rs = resistance_series
1868-
I0 = saturation_current
1869-
IL = photocurrent
1870-
V = voltage
1870+
# asarray turns Series into arrays so that we don't have to worry
1871+
# about multidimensional broadcasting failing
1872+
Rsh = np.asarray(resistance_shunt)
1873+
Rs = np.asarray(resistance_series)
1874+
I0 = np.asarray(saturation_current)
1875+
IL = np.asarray(photocurrent)
1876+
V = np.asarray(voltage)
18711877

18721878
argW = (Rs*I0*Rsh *
18731879
np.exp(Rsh*(Rs*(IL+I0)+V) / (nNsVth*(Rs+Rsh))) /

pvlib/test/test_pvsystem.py

Lines changed: 86 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections import OrderedDict
55

66
import numpy as np
7-
from numpy import nan
7+
from numpy import nan, array
88
import pandas as pd
99

1010
import pytest
@@ -148,6 +148,14 @@ def sapm_module_params(sam_data):
148148
return module_parameters
149149

150150

151+
@pytest.fixture(scope="session")
152+
def cec_module_params(sam_data):
153+
modules = sam_data['cecmod']
154+
module = 'Example_Module'
155+
module_parameters = modules[module]
156+
return module_parameters
157+
158+
151159
def test_sapm(sapm_module_params):
152160

153161
times = pd.DatetimeIndex(start='2015-01-01', periods=5, freq='12H')
@@ -297,17 +305,15 @@ def test_PVSystem_sapm_effective_irradiance(sapm_module_params):
297305
aoi, reference_irradiance=reference_irradiance)
298306

299307

300-
def test_calcparams_desoto(sam_data):
301-
module = 'Example_Module'
302-
module_parameters = sam_data['cecmod'][module]
308+
def test_calcparams_desoto(cec_module_params):
303309
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
304310
poa_data = pd.Series([0, 800], index=times)
305311

306312
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
307313
poa_data,
308314
temp_cell=25,
309-
alpha_isc=module_parameters['alpha_sc'],
310-
module_parameters=module_parameters,
315+
alpha_isc=cec_module_params['alpha_sc'],
316+
module_parameters=cec_module_params,
311317
EgRef=1.121,
312318
dEgdT=-0.0002677)
313319

@@ -318,13 +324,11 @@ def test_calcparams_desoto(sam_data):
318324
assert_allclose(nNsVth, 0.473)
319325

320326

321-
def test_PVSystem_calcparams_desoto(sam_data):
322-
module = 'Example_Module'
323-
module_parameters = sam_data['cecmod'][module].copy()
327+
def test_PVSystem_calcparams_desoto(cec_module_params):
328+
module_parameters = cec_module_params.copy()
324329
module_parameters['EgRef'] = 1.121
325330
module_parameters['dEgdT'] = -0.0002677
326-
system = pvsystem.PVSystem(module=module,
327-
module_parameters=module_parameters)
331+
system = pvsystem.PVSystem(module_parameters=module_parameters)
328332
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
329333
poa_data = pd.Series([0, 800], index=times)
330334
temp_cell = 25
@@ -357,69 +361,118 @@ def test_i_from_v():
357361

358362

359363
@requires_scipy
360-
def test_PVSystem_i_from_v(sam_data):
361-
module = 'Example_Module'
362-
module_parameters = sam_data['cecmod'][module]
363-
system = pvsystem.PVSystem(module=module,
364-
module_parameters=module_parameters)
364+
def test_PVSystem_i_from_v():
365+
system = pvsystem.PVSystem()
365366
output = system.i_from_v(20, .1, .5, 40, 6e-7, 7)
366367
assert_allclose(-299.746389916, output, 5)
367368

368369

369370
@requires_scipy
370-
def test_singlediode_series(sam_data):
371-
module = 'Example_Module'
372-
module_parameters = sam_data['cecmod'][module]
371+
def test_singlediode_series(cec_module_params):
373372
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
374373
poa_data = pd.Series([0, 800], index=times)
375374
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
376375
poa_data,
377376
temp_cell=25,
378-
alpha_isc=module_parameters['alpha_sc'],
379-
module_parameters=module_parameters,
377+
alpha_isc=cec_module_params['alpha_sc'],
378+
module_parameters=cec_module_params,
380379
EgRef=1.121,
381380
dEgdT=-0.0002677)
382381
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth)
383382
assert isinstance(out, pd.DataFrame)
384383

385384

386385
@requires_scipy
387-
def test_singlediode_floats(sam_data):
388-
module = 'Example_Module'
389-
module_parameters = sam_data['cecmod'][module]
386+
def test_singlediode_floats():
390387
out = pvsystem.singlediode(7, 6e-7, .1, 20, .5)
391388
expected = {'i_xx': 4.2685798754011426,
392389
'i_mp': 6.1390251797935704,
393390
'v_oc': 8.1063001465863085,
394391
'p_mp': 38.194165464983037,
395392
'i_x': 6.7556075876880621,
396393
'i_sc': 6.9646747613963198,
397-
'v_mp': 6.221535886625464}
394+
'v_mp': 6.221535886625464,
395+
'i': None,
396+
'v': None}
398397
assert isinstance(out, dict)
399398
for k, v in out.items():
400-
assert_allclose(expected[k], v, atol=3)
399+
if k in ['i', 'v']:
400+
assert v is None
401+
else:
402+
assert_allclose(expected[k], v, atol=3)
401403

402404

403405
@requires_scipy
404-
def test_PVSystem_singlediode_floats(sam_data):
405-
module = 'Example_Module'
406-
module_parameters = sam_data['cecmod'][module]
407-
system = pvsystem.PVSystem(module=module,
408-
module_parameters=module_parameters)
406+
def test_PVSystem_singlediode_floats():
407+
system = pvsystem.PVSystem()
409408
out = system.singlediode(7, 6e-7, .1, 20, .5)
410409
expected = {'i_xx': 4.2685798754011426,
411410
'i_mp': 6.1390251797935704,
412411
'v_oc': 8.1063001465863085,
413412
'p_mp': 38.194165464983037,
414413
'i_x': 6.7556075876880621,
415414
'i_sc': 6.9646747613963198,
416-
'v_mp': 6.221535886625464}
415+
'v_mp': 6.221535886625464,
416+
'i': None,
417+
'v': None}
417418
assert isinstance(out, dict)
419+
for k, v in out.items():
420+
if k in ['i', 'v']:
421+
assert v is None
422+
else:
423+
assert_allclose(expected[k], v, atol=3)
424+
425+
426+
@requires_scipy
427+
def test_singlediode_floats_ivcurve():
428+
out = pvsystem.singlediode(7, 6e-7, .1, 20, .5, ivcurve_pnts=3)
429+
expected = {'i_xx': 4.2685798754011426,
430+
'i_mp': 6.1390251797935704,
431+
'v_oc': 8.1063001465863085,
432+
'p_mp': 38.194165464983037,
433+
'i_x': 6.7556075876880621,
434+
'i_sc': 6.9646747613963198,
435+
'v_mp': 6.221535886625464,
436+
'i': np.array([6.965172e+00, 6.755882e+00, 2.575717e-14]),
437+
'v': np.array([0. , 4.05315, 8.1063])}
438+
assert isinstance(out, dict)
439+
for k, v in out.items():
440+
assert_allclose(expected[k], v, atol=3)
441+
442+
443+
@requires_scipy
444+
def test_singlediode_series_ivcurve(cec_module_params):
445+
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
446+
poa_data = pd.Series([0, 800], index=times)
447+
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
448+
poa_data,
449+
temp_cell=25,
450+
alpha_isc=cec_module_params['alpha_sc'],
451+
module_parameters=cec_module_params,
452+
EgRef=1.121,
453+
dEgdT=-0.0002677)
454+
455+
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3)
456+
457+
expected = OrderedDict([('i_sc', array([ nan, 6.00675648])),
458+
('i_mp', array([ nan, 5.6129056])),
459+
('v_oc', array([ nan, 10.29530483])),
460+
('v_mp', array([ nan, 7.25364707])),
461+
('p_mp', array([ nan, 40.71403625])),
462+
('i_x', array([ nan, 5.74622046])),
463+
('i_xx', array([ nan, 4.97138154])),
464+
('i',
465+
array([[ nan, nan, nan],
466+
[ 6.00726296, 5.74622046, 0. ]])),
467+
('v',
468+
array([[ nan, nan, nan],
469+
[ 0. , 5.14765242, 10.29530483]]))])
470+
418471
for k, v in out.items():
419472
assert_allclose(expected[k], v, atol=3)
420473

421474

422-
def test_scale_voltage_current_power(sam_data):
475+
def test_scale_voltage_current_power():
423476
data = pd.DataFrame(
424477
np.array([[2, 1.5, 10, 8, 12, 0.5, 1.5]]),
425478
columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'],

0 commit comments

Comments
 (0)