Skip to content

Commit 4a897d9

Browse files
authored
Change units on SAPM effective irradiance from suns to W/m2 (#815)
* change effective_irradiance units in sapm, change sapm_effective_irradiance function * whatsnew * docstring additions * lint * remove /1000 from ModelChain.sapm, fix pvsystem tests * test fix, docstring edits * change irrad_ref to suns, with deprecation message * remove suns option, new kwargs * remove reference_irradiance from tests * fix method test * update warning * separate API breaking changes
1 parent c699575 commit 4a897d9

File tree

4 files changed

+110
-63
lines changed

4 files changed

+110
-63
lines changed

docs/sphinx/source/whatsnew/v0.7.0.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ compatibility notes.
1010
**Python 2.7 support ended on June 1, 2019.** (:issue:`501`)
1111
**Minimum numpy version is now 1.10.4. Minimum pandas version is now 0.18.1.**
1212

13-
API Changes
14-
~~~~~~~~~~~
13+
API Breaking Changes
14+
~~~~~~~~~~~~~~~~~~~~
15+
* The `effective_irradiance` argument for :py:func:`pvsystem.sapm` now requires
16+
units of W/m^2. Previously, units for this input were suns. A RuntimeWarning
17+
warning is raised if all `effective_irradiance < 2.0`.
18+
* The output of :py:func:`pvsystem.sapm_effective_irradiance` is now in units
19+
of W/m2 rather than suns.
20+
21+
API Changes with Deprecations
22+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1523
* Changes related to cell temperature models (:issue:`678`):
1624
* Changes to functions
1725
- Moved functions for cell temperature from `pvsystem.py` to `temperature.py`.

pvlib/modelchain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ def infer_dc_model(self):
440440
'set the model with the dc_model kwarg.')
441441

442442
def sapm(self):
443-
self.dc = self.system.sapm(self.effective_irradiance/1000.,
443+
self.dc = self.system.sapm(self.effective_irradiance,
444444
self.cell_temperature)
445445

446446
self.dc = self.system.scale_voltage_current_power(self.dc)

pvlib/pvsystem.py

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -563,28 +563,25 @@ def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
563563
Parameters
564564
----------
565565
poa_direct : numeric
566-
The direct irradiance incident upon the module.
566+
The direct irradiance incident upon the module. [W/m2]
567567
568568
poa_diffuse : numeric
569-
The diffuse irradiance incident on module.
569+
The diffuse irradiance incident on module. [W/m2]
570570
571571
airmass_absolute : numeric
572-
Absolute airmass.
572+
Absolute airmass. [unitless]
573573
574574
aoi : numeric
575-
Angle of incidence in degrees.
576-
577-
reference_irradiance : numeric, default 1000
578-
Reference irradiance by which to divide the input irradiance.
575+
Angle of incidence. [degrees]
579576
580577
Returns
581578
-------
582579
effective_irradiance : numeric
583-
The SAPM effective irradiance.
580+
The SAPM effective irradiance. [W/m2]
584581
"""
585582
return sapm_effective_irradiance(
586583
poa_direct, poa_diffuse, airmass_absolute, aoi,
587-
self.module_parameters, reference_irradiance=reference_irradiance)
584+
self.module_parameters)
588585

589586
def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0):
590587
"""Uses :py:func:`temperature.pvsyst_cell` to calculate cell
@@ -1580,10 +1577,11 @@ def sapm(effective_irradiance, temp_cell, module):
15801577
Parameters
15811578
----------
15821579
effective_irradiance : numeric
1583-
Effective irradiance (suns).
1580+
Irradiance reaching the module's cells, after reflections and
1581+
adjustment for spectrum. [W/m2]
15841582
15851583
temp_cell : numeric
1586-
The cell temperature (degrees C).
1584+
Cell temperature [C].
15871585
15881586
module : dict-like
15891587
A dict or Series defining the SAPM parameters. See the notes section
@@ -1659,12 +1657,23 @@ def sapm(effective_irradiance, temp_cell, module):
16591657
temperature.sapm_module
16601658
'''
16611659

1662-
T0 = 25
1660+
# TODO: someday, change temp_ref and irrad_ref to reference_temperature and
1661+
# reference_irradiance and expose
1662+
temp_ref = 25
1663+
irrad_ref = 1000
1664+
# TODO: remove this warning in v0.8 after deprecation period for change in
1665+
# effective irradiance units, made in v0.7
1666+
if np.all(effective_irradiance) < 2.0:
1667+
import warnings
1668+
warnings.warn('effective_irradiance inputs appear to be in suns.'
1669+
' Units changed in v0.7 from suns to W/m2',
1670+
RuntimeWarning)
1671+
16631672
q = 1.60218e-19 # Elementary charge in units of coulombs
16641673
kb = 1.38066e-23 # Boltzmann's constant in units of J/K
16651674

16661675
# avoid problem with integer input
1667-
Ee = np.array(effective_irradiance, dtype='float64')
1676+
Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref
16681677

16691678
# set up masking for 0, positive, and nan inputs
16701679
Ee_gt_0 = np.full_like(Ee, False, dtype='bool')
@@ -1687,32 +1696,32 @@ def sapm(effective_irradiance, temp_cell, module):
16871696
out = OrderedDict()
16881697

16891698
out['i_sc'] = (
1690-
module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - T0)))
1699+
module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))
16911700

16921701
out['i_mp'] = (
16931702
module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *
1694-
(1 + module['Aimp']*(temp_cell - T0)))
1703+
(1 + module['Aimp']*(temp_cell - temp_ref)))
16951704

16961705
out['v_oc'] = np.maximum(0, (
16971706
module['Voco'] + cells_in_series * delta * logEe +
1698-
Bvoco*(temp_cell - T0)))
1707+
Bvoco*(temp_cell - temp_ref)))
16991708

17001709
out['v_mp'] = np.maximum(0, (
17011710
module['Vmpo'] +
17021711
module['C2'] * cells_in_series * delta * logEe +
17031712
module['C3'] * cells_in_series * ((delta * logEe) ** 2) +
1704-
Bvmpo*(temp_cell - T0)))
1713+
Bvmpo*(temp_cell - temp_ref)))
17051714

17061715
out['p_mp'] = out['i_mp'] * out['v_mp']
17071716

17081717
out['i_x'] = (
17091718
module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *
1710-
(1 + module['Aisc']*(temp_cell - T0)))
1719+
(1 + module['Aisc']*(temp_cell - temp_ref)))
17111720

17121721
# the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)
17131722
out['i_xx'] = (
17141723
module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *
1715-
(1 + module['Aisc']*(temp_cell - T0)))
1724+
(1 + module['Aisc']*(temp_cell - temp_ref)))
17161725

17171726
if isinstance(out['i_sc'], pd.Series):
17181727
out = pd.DataFrame(out)
@@ -1839,45 +1848,70 @@ def sapm_spectral_loss(airmass_absolute, module):
18391848

18401849

18411850
def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
1842-
module, reference_irradiance=1000):
1843-
"""
1851+
module):
1852+
r"""
18441853
Calculates the SAPM effective irradiance using the SAPM spectral
18451854
loss and SAPM angle of incidence loss functions.
18461855
18471856
Parameters
18481857
----------
18491858
poa_direct : numeric
1850-
The direct irradiance incident upon the module.
1859+
The direct irradiance incident upon the module. [W/m2]
18511860
18521861
poa_diffuse : numeric
1853-
The diffuse irradiance incident on module.
1862+
The diffuse irradiance incident on module. [W/m2]
18541863
18551864
airmass_absolute : numeric
1856-
Absolute airmass.
1865+
Absolute airmass. [unitless]
18571866
18581867
aoi : numeric
1859-
Angle of incidence in degrees.
1868+
Angle of incidence. [degrees]
18601869
18611870
module : dict-like
18621871
A dict, Series, or DataFrame defining the SAPM performance
18631872
parameters. See the :py:func:`sapm` notes section for more
18641873
details.
18651874
1866-
reference_irradiance : numeric, default 1000
1867-
Reference irradiance by which to divide the input irradiance.
1868-
18691875
Returns
18701876
-------
18711877
effective_irradiance : numeric
1872-
The SAPM effective irradiance.
1878+
Effective irradiance accounting for reflections and spectral content.
1879+
[W/m2]
1880+
1881+
Notes
1882+
-----
1883+
The SAPM model for effective irradiance [1] translates broadband direct and
1884+
diffuse irradiance on the plane of array to the irradiance absorbed by a
1885+
module's cells.
1886+
1887+
The model is
1888+
.. math::
1889+
1890+
`Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`
1891+
1892+
where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth
1893+
degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)
1894+
irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the
1895+
plane of array, :math:`f_2` is a fifth degree polynomial in the angle of
1896+
incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse
1897+
irradiance on the plane of array that is not reflected away.
1898+
1899+
References
1900+
----------
1901+
[1] D. King et al, "Sandia Photovoltaic Array Performance Model",
1902+
SAND2004-3535, Sandia National Laboratories, Albuquerque, NM
1903+
1904+
See also
1905+
--------
1906+
pvlib.iam.sapm
1907+
pvlib.pvsystem.sapm_spectral_loss
1908+
pvlib.pvsystem.sapm
18731909
"""
18741910

18751911
F1 = sapm_spectral_loss(airmass_absolute, module)
18761912
F2 = iam.sapm(aoi, module)
18771913

1878-
E0 = reference_irradiance
1879-
1880-
Ee = F1 * (poa_direct*F2 + module['FD']*poa_diffuse) / E0
1914+
Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)
18811915

18821916
return Ee
18831917

@@ -1992,7 +2026,7 @@ def singlediode(photocurrent, saturation_current, resistance_series,
19922026
the IV curve are linearly spaced.
19932027
19942028
References
1995-
-----------
2029+
----------
19962030
[1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN
19972031
0 86758 909 4
19982032

pvlib/test/test_pvsystem.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ def test_retrieve_sam_cecinverter():
195195
def test_sapm(sapm_module_params):
196196

197197
times = pd.date_range(start='2015-01-01', periods=5, freq='12H')
198-
effective_irradiance = pd.Series([-1, 0.5, 1.1, np.nan, 1], index=times)
198+
effective_irradiance = pd.Series([-1000, 500, 1100, np.nan, 1000],
199+
index=times)
199200
temp_cell = pd.Series([10, 25, 50, 25, np.nan], index=times)
200201

201202
out = pvsystem.sapm(effective_irradiance, temp_cell, sapm_module_params)
@@ -216,7 +217,7 @@ def test_sapm(sapm_module_params):
216217

217218
assert_frame_equal(out, expected, check_less_precise=4)
218219

219-
out = pvsystem.sapm(1, 25, sapm_module_params)
220+
out = pvsystem.sapm(1000, 25, sapm_module_params)
220221

221222
expected = OrderedDict()
222223
expected['i_sc'] = 5.09115
@@ -235,10 +236,21 @@ def test_sapm(sapm_module_params):
235236
pd.Series(sapm_module_params))
236237

237238

239+
def test_pvsystem_sapm_warning(sapm_module_params):
240+
# deprecation warning for change in effective_irradiance units in
241+
# pvsystem.sapm
242+
# TODO: remove after deprecation period (v0.8)
243+
effective_irradiance = np.array([0.1, 0.2, 1.3])
244+
temp_cell = np.array([25, 25, 50])
245+
warn_txt = 'effective_irradiance inputs appear to be in suns'
246+
with pytest.warns(RuntimeWarning, match=warn_txt):
247+
pvsystem.sapm(effective_irradiance, temp_cell, sapm_module_params)
248+
249+
238250
def test_PVSystem_sapm(sapm_module_params, mocker):
239251
mocker.spy(pvsystem, 'sapm')
240252
system = pvsystem.PVSystem(module_parameters=sapm_module_params)
241-
effective_irradiance = 0.5
253+
effective_irradiance = 500
242254
temp_cell = 25
243255
out = system.sapm(effective_irradiance, temp_cell)
244256
pvsystem.sapm.assert_called_once_with(effective_irradiance, temp_cell,
@@ -295,33 +307,23 @@ def test_PVSystem_first_solar_spectral_loss(module_parameters, module_type,
295307

296308

297309
@pytest.mark.parametrize('test_input,expected', [
298-
([1000, 100, 5, 45, 1000], 1.1400510967821877),
310+
([1000, 100, 5, 45], 1140.0510967821877),
299311
([np.array([np.nan, 1000, 1000]),
300312
np.array([100, np.nan, 100]),
301313
np.array([1.1, 1.1, 1.1]),
302-
np.array([10, 10, 10]),
303-
1000],
304-
np.array([np.nan, np.nan, 1.081157])),
314+
np.array([10, 10, 10])],
315+
np.array([np.nan, np.nan, 1081.1574])),
305316
([pd.Series([1000]), pd.Series([100]), pd.Series([1.1]),
306-
pd.Series([10]), 1370],
307-
pd.Series([0.789166]))
317+
pd.Series([10])],
318+
pd.Series([1081.1574]))
308319
])
309320
def test_sapm_effective_irradiance(sapm_module_params, test_input, expected):
310-
311-
try:
312-
kwargs = {'reference_irradiance': test_input[4]}
313-
test_input = test_input[:-1]
314-
except IndexError:
315-
kwargs = {}
316-
317321
test_input.append(sapm_module_params)
318-
319-
out = pvsystem.sapm_effective_irradiance(*test_input, **kwargs)
320-
322+
out = pvsystem.sapm_effective_irradiance(*test_input)
321323
if isinstance(test_input, pd.Series):
322324
assert_series_equal(out, expected, check_less_precise=4)
323325
else:
324-
assert_allclose(out, expected, atol=1e-4)
326+
assert_allclose(out, expected, atol=1e-1)
325327

326328

327329
def test_PVSystem_sapm_effective_irradiance(sapm_module_params, mocker):
@@ -332,15 +334,16 @@ def test_PVSystem_sapm_effective_irradiance(sapm_module_params, mocker):
332334
poa_diffuse = 100
333335
airmass_absolute = 1.5
334336
aoi = 0
335-
reference_irradiance = 1000
336-
337+
p = (sapm_module_params['A4'], sapm_module_params['A3'],
338+
sapm_module_params['A2'], sapm_module_params['A1'],
339+
sapm_module_params['A0'])
340+
f1 = np.polyval(p, airmass_absolute)
341+
expected = f1 * (poa_direct + sapm_module_params['FD'] * poa_diffuse)
337342
out = system.sapm_effective_irradiance(
338-
poa_direct, poa_diffuse, airmass_absolute,
339-
aoi, reference_irradiance=reference_irradiance)
343+
poa_direct, poa_diffuse, airmass_absolute, aoi)
340344
pvsystem.sapm_effective_irradiance.assert_called_once_with(
341-
poa_direct, poa_diffuse, airmass_absolute, aoi, sapm_module_params,
342-
reference_irradiance=reference_irradiance)
343-
assert_allclose(out, 1, atol=0.1)
345+
poa_direct, poa_diffuse, airmass_absolute, aoi, sapm_module_params)
346+
assert_allclose(out, expected, atol=0.1)
344347

345348

346349
def test_PVSystem_sapm_celltemp(mocker):
@@ -1464,8 +1467,10 @@ def test_PVSystem_pvwatts_ac_kwargs(mocker):
14641467

14651468
@fail_on_pvlib_version('0.8')
14661469
def test_deprecated_08():
1470+
# deprecated function pvsystem.sapm_celltemp
14671471
with pytest.warns(pvlibDeprecationWarning):
14681472
pvsystem.sapm_celltemp(1000, 25, 1)
1473+
# deprecated function pvsystem.pvsyst_celltemp
14691474
with pytest.warns(pvlibDeprecationWarning):
14701475
pvsystem.pvsyst_celltemp(1000, 25)
14711476
module_parameters = {'R_sh_ref': 1, 'a_ref': 1, 'I_o_ref': 1,

0 commit comments

Comments
 (0)