Skip to content

Commit 85e53af

Browse files
authored
add optional ivcurve calculation to singlediode (#223)
* add optional ivcurve calculation to singlediode * only include i v keys if needed * update test for singlediode p_mp fix * add a moderate point test sd series
1 parent 86ef706 commit 85e53af

File tree

3 files changed

+132
-84
lines changed

3 files changed

+132
-84
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ Enhancements
5151
* Add solarposition.nrel_earthsun_distance function and option to
5252
calculate extraterrestrial radiation using the NREL solar position
5353
algorithm. (:issue:`211`, :issue:`215`)
54+
* pvsystem.singlediode can now calculate IV curves if a user supplies
55+
an ivcurve_pnts keyword argument. (:issue:`83`)
5456
* Includes SAM data files in the distribution. (:issue:`52`)
5557

5658

pvlib/pvsystem.py

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
413413
self.module_parameters, reference_irradiance=reference_irradiance)
414414

415415
def singlediode(self, photocurrent, saturation_current,
416-
resistance_series, resistance_shunt, nNsVth):
416+
resistance_series, resistance_shunt, nNsVth,
417+
ivcurve_pnts=None):
417418
"""Wrapper around the :py:func:`singlediode` function.
418419
419420
Parameters
@@ -425,7 +426,8 @@ def singlediode(self, photocurrent, saturation_current,
425426
See pvsystem.singlediode for details
426427
"""
427428
return singlediode(photocurrent, saturation_current,
428-
resistance_series, resistance_shunt, nNsVth)
429+
resistance_series, resistance_shunt, nNsVth,
430+
ivcurve_pnts=ivcurve_pnts)
429431

430432
def i_from_v(self, resistance_shunt, resistance_series, nNsVth, voltage,
431433
saturation_current, photocurrent):
@@ -1533,7 +1535,7 @@ def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
15331535

15341536

15351537
def singlediode(photocurrent, saturation_current, resistance_series,
1536-
resistance_shunt, nNsVth):
1538+
resistance_shunt, nNsVth, ivcurve_pnts=None):
15371539
r'''
15381540
Solve the single-diode model to obtain a photovoltaic IV curve.
15391541
@@ -1555,23 +1557,23 @@ def singlediode(photocurrent, saturation_current, resistance_series,
15551557
15561558
Parameters
15571559
----------
1558-
photocurrent : float or Series
1560+
photocurrent : numeric
15591561
Light-generated current (photocurrent) in amperes under desired
15601562
IV curve conditions. Often abbreviated ``I_L``.
15611563
1562-
saturation_current : float or Series
1564+
saturation_current : numeric
15631565
Diode saturation current in amperes under desired IV curve
15641566
conditions. Often abbreviated ``I_0``.
15651567
1566-
resistance_series : float or Series
1568+
resistance_series : numeric
15671569
Series resistance in ohms under desired IV curve conditions.
15681570
Often abbreviated ``Rs``.
15691571
1570-
resistance_shunt : float or Series
1572+
resistance_shunt : numeric
15711573
Shunt resistance in ohms under desired IV curve conditions.
15721574
Often abbreviated ``Rsh``.
15731575
1574-
nNsVth : float or Series
1576+
nNsVth : numeric
15751577
The product of three components. 1) The usual diode ideal factor
15761578
(n), 2) the number of cells in series (Ns), and 3) the cell
15771579
thermal voltage under the desired IV curve conditions (Vth). The
@@ -1580,21 +1582,35 @@ def singlediode(photocurrent, saturation_current, resistance_series,
15801582
temp_cell is the temperature of the p-n junction in Kelvin, and
15811583
q is the charge of an electron (coulombs).
15821584
1585+
ivcurve_pnts : None or int
1586+
Number of points in the desired IV curve. If None or 0, no
1587+
IV curves will be produced.
1588+
15831589
Returns
15841590
-------
1585-
If ``photocurrent`` is a Series, a DataFrame with the following
1586-
columns. All columns have the same number of rows as the largest
1587-
input DataFrame.
1591+
OrderedDict or DataFrame
1592+
1593+
The returned dict-like object always contains the keys/columns:
1594+
1595+
* i_sc - short circuit current in amperes.
1596+
* v_oc - open circuit voltage in volts.
1597+
* i_mp - current at maximum power point in amperes.
1598+
* v_mp - voltage at maximum power point in volts.
1599+
* p_mp - power at maximum power point in watts.
1600+
* i_x - current, in amperes, at ``v = 0.5*v_oc``.
1601+
* i_xx - current, in amperes, at ``V = 0.5*(v_oc+v_mp)``.
1602+
1603+
If ivcurve_pnts is greater than 0, the output dictionary will also
1604+
include the keys:
15881605
1589-
If ``photocurrent`` is a scalar, a dict with the following keys.
1606+
* i - IV curve current in amperes.
1607+
* v - IV curve voltage in volts.
15901608
1591-
* i_sc - short circuit current in amperes.
1592-
* v_oc - open circuit voltage in volts.
1593-
* i_mp - current at maximum power point in amperes.
1594-
* v_mp - voltage at maximum power point in volts.
1595-
* p_mp - power at maximum power point in watts.
1596-
* i_x - current, in amperes, at ``v = 0.5*v_oc``.
1597-
* i_xx - current, in amperes, at ``V = 0.5*(v_oc+v_mp)``.
1609+
The output will be an OrderedDict if photocurrent is a scalar,
1610+
array, or ivcurve_pnts is not None.
1611+
1612+
The output will be a DataFrame if photocurrent is a Series and
1613+
ivcurve_pnts is None.
15981614
15991615
Notes
16001616
-----
@@ -1648,37 +1664,29 @@ def singlediode(photocurrent, saturation_current, resistance_series,
16481664
i_xx = i_from_v(resistance_shunt, resistance_series, nNsVth,
16491665
0.5*(v_oc+v_mp), saturation_current, photocurrent)
16501666

1651-
# @wholmgren: need to move this stuff to a different function
1652-
# If the user says they want a curve of with number of points equal to
1653-
# NumPoints (must be >=2), then create a voltage array where voltage is
1654-
# zero in the first column, and Voc in the last column. Number of columns
1655-
# must equal NumPoints. Each row represents the voltage for one IV curve.
1656-
# Then create a current array where current is Isc in the first column, and
1657-
# zero in the last column, and each row represents the current in one IV
1658-
# curve. Thus the nth (V,I) point of curve m would be found as follows:
1659-
# (Result.V(m,n),Result.I(m,n)).
1660-
# if NumPoints >= 2
1661-
# s = ones(1,NumPoints); # shaping DataFrame to shape the column
1662-
# # DataFrame parameters into 2-D matrices
1663-
# Result.V = (Voc)*(0:1/(NumPoints-1):1);
1664-
# Result.I = I_from_V(Rsh*s, Rs*s, nNsVth*s, Result.V, I0*s, IL*s);
1665-
# end
1666-
1667-
dfout = {}
1668-
dfout['i_sc'] = i_sc
1669-
dfout['i_mp'] = i_mp
1670-
dfout['v_oc'] = v_oc
1671-
dfout['v_mp'] = v_mp
1672-
dfout['p_mp'] = p_mp
1673-
dfout['i_x'] = i_x
1674-
dfout['i_xx'] = i_xx
1675-
1676-
try:
1677-
dfout = pd.DataFrame(dfout, index=photocurrent.index)
1678-
except AttributeError:
1679-
pass
1667+
out = OrderedDict()
1668+
out['i_sc'] = i_sc
1669+
out['v_oc'] = v_oc
1670+
out['i_mp'] = i_mp
1671+
out['v_mp'] = v_mp
1672+
out['p_mp'] = p_mp
1673+
out['i_x'] = i_x
1674+
out['i_xx'] = i_xx
1675+
1676+
# create ivcurve
1677+
if ivcurve_pnts:
1678+
ivcurve_v = (np.asarray(v_oc)[..., np.newaxis] *
1679+
np.linspace(0, 1, ivcurve_pnts))
1680+
ivcurve_i = i_from_v(
1681+
resistance_shunt, resistance_series, nNsVth, ivcurve_v.T,
1682+
saturation_current, photocurrent).T
1683+
out['v'] = ivcurve_v
1684+
out['i'] = ivcurve_i
1685+
1686+
if isinstance(photocurrent, pd.Series) and not ivcurve_pnts:
1687+
out = pd.DataFrame(out, index=photocurrent.index)
16801688

1681-
return dfout
1689+
return out
16821690

16831691

16841692
# Created April,2014
@@ -1879,11 +1887,13 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
18791887
except ImportError:
18801888
raise ImportError('This function requires scipy')
18811889

1882-
Rsh = resistance_shunt
1883-
Rs = resistance_series
1884-
I0 = saturation_current
1885-
IL = photocurrent
1886-
V = voltage
1890+
# asarray turns Series into arrays so that we don't have to worry
1891+
# about multidimensional broadcasting failing
1892+
Rsh = np.asarray(resistance_shunt)
1893+
Rs = np.asarray(resistance_series)
1894+
I0 = np.asarray(saturation_current)
1895+
IL = np.asarray(photocurrent)
1896+
V = np.asarray(voltage)
18871897

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

pvlib/test/test_pvsystem.py

Lines changed: 66 additions & 30 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
@@ -150,6 +150,14 @@ def sapm_module_params(sam_data):
150150
return module_parameters
151151

152152

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

155163
times = pd.DatetimeIndex(start='2015-01-01', periods=5, freq='12H')
@@ -307,17 +315,15 @@ def test_PVSystem_sapm_effective_irradiance(sapm_module_params):
307315
aoi, reference_irradiance=reference_irradiance)
308316

309317

310-
def test_calcparams_desoto(sam_data):
311-
module = 'Example_Module'
312-
module_parameters = sam_data['cecmod'][module]
318+
def test_calcparams_desoto(cec_module_params):
313319
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
314320
poa_data = pd.Series([0, 800], index=times)
315321

316322
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
317323
poa_data,
318324
temp_cell=25,
319-
alpha_isc=module_parameters['alpha_sc'],
320-
module_parameters=module_parameters,
325+
alpha_isc=cec_module_params['alpha_sc'],
326+
module_parameters=cec_module_params,
321327
EgRef=1.121,
322328
dEgdT=-0.0002677)
323329

@@ -328,13 +334,11 @@ def test_calcparams_desoto(sam_data):
328334
assert_allclose(nNsVth, 0.473)
329335

330336

331-
def test_PVSystem_calcparams_desoto(sam_data):
332-
module = 'Example_Module'
333-
module_parameters = sam_data['cecmod'][module].copy()
337+
def test_PVSystem_calcparams_desoto(cec_module_params):
338+
module_parameters = cec_module_params.copy()
334339
module_parameters['EgRef'] = 1.121
335340
module_parameters['dEgdT'] = -0.0002677
336-
system = pvsystem.PVSystem(module=module,
337-
module_parameters=module_parameters)
341+
system = pvsystem.PVSystem(module_parameters=module_parameters)
338342
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
339343
poa_data = pd.Series([0, 800], index=times)
340344
temp_cell = 25
@@ -367,26 +371,21 @@ def test_i_from_v():
367371

368372

369373
@requires_scipy
370-
def test_PVSystem_i_from_v(sam_data):
371-
module = 'Example_Module'
372-
module_parameters = sam_data['cecmod'][module]
373-
system = pvsystem.PVSystem(module=module,
374-
module_parameters=module_parameters)
374+
def test_PVSystem_i_from_v():
375+
system = pvsystem.PVSystem()
375376
output = system.i_from_v(20, .1, .5, 40, 6e-7, 7)
376377
assert_allclose(-299.746389916, output, 5)
377378

378379

379380
@requires_scipy
380-
def test_singlediode_series(sam_data):
381-
module = 'Example_Module'
382-
module_parameters = sam_data['cecmod'][module]
381+
def test_singlediode_series(cec_module_params):
383382
times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
384383
poa_data = pd.Series([0, 800], index=times)
385384
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
386385
poa_data,
387386
temp_cell=25,
388-
alpha_isc=module_parameters['alpha_sc'],
389-
module_parameters=module_parameters,
387+
alpha_isc=cec_module_params['alpha_sc'],
388+
module_parameters=cec_module_params,
390389
EgRef=1.121,
391390
dEgdT=-0.0002677)
392391
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth)
@@ -424,31 +423,68 @@ def test_singlediode_floats(sam_data):
424423
'p_mp': 38.194165464983037,
425424
'i_x': 6.7556075876880621,
426425
'i_sc': 6.9646747613963198,
427-
'v_mp': 6.221535886625464}
426+
'v_mp': 6.221535886625464,
427+
'i': None,
428+
'v': None}
428429
assert isinstance(out, dict)
429430
for k, v in out.items():
430-
assert_allclose(expected[k], v, atol=3)
431+
if k in ['i', 'v']:
432+
assert v is None
433+
else:
434+
assert_allclose(expected[k], v, atol=3)
431435

432436

433437
@requires_scipy
434-
def test_PVSystem_singlediode_floats(sam_data):
435-
module = 'Example_Module'
436-
module_parameters = sam_data['cecmod'][module]
437-
system = pvsystem.PVSystem(module=module,
438-
module_parameters=module_parameters)
439-
out = system.singlediode(7, 6e-7, .1, 20, .5)
438+
def test_singlediode_floats_ivcurve():
439+
out = pvsystem.singlediode(7, 6e-7, .1, 20, .5, ivcurve_pnts=3)
440440
expected = {'i_xx': 4.2685798754011426,
441441
'i_mp': 6.1390251797935704,
442442
'v_oc': 8.1063001465863085,
443443
'p_mp': 38.194165464983037,
444444
'i_x': 6.7556075876880621,
445445
'i_sc': 6.9646747613963198,
446-
'v_mp': 6.221535886625464}
446+
'v_mp': 6.221535886625464,
447+
'i': np.array([6.965172e+00, 6.755882e+00, 2.575717e-14]),
448+
'v': np.array([0. , 4.05315, 8.1063])}
447449
assert isinstance(out, dict)
448450
for k, v in out.items():
449451
assert_allclose(expected[k], v, atol=3)
450452

451453

454+
@requires_scipy
455+
def test_singlediode_series_ivcurve(cec_module_params):
456+
times = pd.DatetimeIndex(start='2015-06-01', periods=3, freq='6H')
457+
poa_data = pd.Series([0, 400, 800], index=times)
458+
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
459+
poa_data,
460+
temp_cell=25,
461+
alpha_isc=cec_module_params['alpha_sc'],
462+
module_parameters=cec_module_params,
463+
EgRef=1.121,
464+
dEgdT=-0.0002677)
465+
466+
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3)
467+
468+
expected = OrderedDict([('i_sc', array([ nan, 3.01054475, 6.00675648])),
469+
('v_oc', array([ nan, 9.96886962, 10.29530483])),
470+
('i_mp', array([ nan, 2.65191983, 5.28594672])),
471+
('v_mp', array([ nan, 8.33392491, 8.4159707 ])),
472+
('p_mp', array([ nan, 22.10090078, 44.48637274])),
473+
('i_x', array([ nan, 2.88414114, 5.74622046])),
474+
('i_xx', array([ nan, 2.04340914, 3.90007956])),
475+
('v',
476+
array([[ nan, nan, nan],
477+
[ 0. , 4.98443481, 9.96886962],
478+
[ 0. , 5.14765242, 10.29530483]])),
479+
('i',
480+
array([[ nan, nan, nan],
481+
[ 3.01079860e+00, 2.88414114e+00, 3.10862447e-14],
482+
[ 6.00726296e+00, 5.74622046e+00, 0.00000000e+00]]))])
483+
484+
for k, v in out.items():
485+
assert_allclose(expected[k], v, atol=1e-2)
486+
487+
452488
def test_scale_voltage_current_power(sam_data):
453489
data = pd.DataFrame(
454490
np.array([[2, 1.5, 10, 8, 12, 0.5, 1.5]]),

0 commit comments

Comments
 (0)