Skip to content

Commit 01a0840

Browse files
authored
Better handling of missing inputs in get_total_irradiance, get_sky_diffuse (#1225)
* edits to get_total_irradiance and get_sky_diffuse * add tests * whatsnew * improvements from review * spell Tucson * overlooked comments
1 parent 5bdad64 commit 01a0840

File tree

3 files changed

+123
-35
lines changed

3 files changed

+123
-35
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ Enhancements
125125
* Added :py:func:`~pvlib.ivtools.sdm.pvsyst_temperature_coeff` to calculate
126126
the temperature coefficient of power for the pvsyst module model.
127127
(:pull:`1190`)
128+
* :py:func:`~pvlib.irradiance.get_total_irradiance` and
129+
:py:func:`~pvlib.irradiance.get_sky_diffuse` now fill in ``airmass``
130+
if required and not provided. These functions now raise a ``ValueError``
131+
if ``dni_extra`` is required and not provided. (:issue:`949`, :pull:`1225`)
128132

129133
Bug fixes
130134
~~~~~~~~~

pvlib/irradiance.py

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from pvlib import atmosphere, solarposition, tools
1515

1616

17-
# see References section of grounddiffuse function
17+
# see References section of get_ground_diffuse function
1818
SURFACE_ALBEDOS = {'urban': 0.18,
1919
'grass': 0.20,
2020
'fresh grass': 0.26,
@@ -326,38 +326,51 @@ def get_total_irradiance(surface_tilt, surface_azimuth,
326326
Parameters
327327
----------
328328
surface_tilt : numeric
329-
Panel tilt from horizontal.
329+
Panel tilt from horizontal. [degree]
330330
surface_azimuth : numeric
331-
Panel azimuth from north.
331+
Panel azimuth from north. [degree]
332332
solar_zenith : numeric
333-
Solar zenith angle.
333+
Solar zenith angle. [degree]
334334
solar_azimuth : numeric
335-
Solar azimuth angle.
335+
Solar azimuth angle. [degree]
336336
dni : numeric
337-
Direct Normal Irradiance
337+
Direct Normal Irradiance. [W/m2]
338338
ghi : numeric
339-
Global horizontal irradiance
339+
Global horizontal irradiance. [W/m2]
340340
dhi : numeric
341-
Diffuse horizontal irradiance
341+
Diffuse horizontal irradiance. [W/m2]
342342
dni_extra : None or numeric, default None
343-
Extraterrestrial direct normal irradiance
343+
Extraterrestrial direct normal irradiance. [W/m2]
344344
airmass : None or numeric, default None
345-
Airmass
345+
Relative airmass (not adjusted for pressure). [unitless]
346346
albedo : numeric, default 0.25
347-
Surface albedo
348-
surface_type : None or String, default None
349-
Surface type. See grounddiffuse.
350-
model : String, default 'isotropic'
351-
Irradiance model.
352-
model_perez : String, default 'allsitescomposite1990'
353-
Used only if model='perez'. See :py:func:`perez`.
347+
Surface albedo. [unitless]
348+
surface_type : None or str, default None
349+
Surface type. See :py:func:`~pvlib.irradiance.get_ground_diffuse` for
350+
the list of accepted values.
351+
model : str, default 'isotropic'
352+
Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``,
353+
``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``.
354+
model_perez : str, default 'allsitescomposite1990'
355+
Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`.
354356
355357
Returns
356358
-------
357359
total_irrad : OrderedDict or DataFrame
358360
Contains keys/columns ``'poa_global', 'poa_direct', 'poa_diffuse',
359361
'poa_sky_diffuse', 'poa_ground_diffuse'``.
362+
363+
Notes
364+
-----
365+
Models ``'haydavies'``, ``'reindl'``, or ``'perez'`` require
366+
``'dni_extra'``. Values can be calculated using
367+
:py:func:`~pvlib.irradiance.get_extra_radiation`.
368+
369+
The ``'perez'`` model requires relative airmass (``airmass``) as input. If
370+
``airmass`` is not provided, it is calculated using the defaults in
371+
:py:func:`~pvlib.atmosphere.get_relative_airmass`.
360372
"""
373+
361374
poa_sky_diffuse = get_sky_diffuse(
362375
surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
363376
dni, ghi, dhi, dni_extra=dni_extra, airmass=airmass, model=model,
@@ -390,34 +403,56 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
390403
Parameters
391404
----------
392405
surface_tilt : numeric
393-
Panel tilt from horizontal.
406+
Panel tilt from horizontal. [degree]
394407
surface_azimuth : numeric
395-
Panel azimuth from north.
408+
Panel azimuth from north. [degree]
396409
solar_zenith : numeric
397-
Solar zenith angle.
410+
Solar zenith angle. [degree]
398411
solar_azimuth : numeric
399-
Solar azimuth angle.
412+
Solar azimuth angle. [degree]
400413
dni : numeric
401-
Direct Normal Irradiance
414+
Direct Normal Irradiance. [W/m2]
402415
ghi : numeric
403-
Global horizontal irradiance
416+
Global horizontal irradiance. [W/m2]
404417
dhi : numeric
405-
Diffuse horizontal irradiance
418+
Diffuse horizontal irradiance. [W/m2]
406419
dni_extra : None or numeric, default None
407-
Extraterrestrial direct normal irradiance
420+
Extraterrestrial direct normal irradiance. [W/m2]
408421
airmass : None or numeric, default None
409-
Airmass
410-
model : String, default 'isotropic'
411-
Irradiance model.
412-
model_perez : String, default 'allsitescomposite1990'
413-
See perez.
422+
Relative airmass (not adjusted for pressure). [unitless]
423+
model : str, default 'isotropic'
424+
Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``,
425+
``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``.
426+
model_perez : str, default 'allsitescomposite1990'
427+
Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`.
414428
415429
Returns
416430
-------
417431
poa_sky_diffuse : numeric
432+
Sky diffuse irradiance in the plane of array. [W/m2]
433+
434+
Raises
435+
------
436+
ValueError
437+
If model is one of ``'haydavies'``, ``'reindl'``, or ``'perez'`` and
438+
``dni_extra`` is ``None``.
439+
440+
Notes
441+
-----
442+
Models ``'haydavies'``, ``'reindl'``, and ``'perez``` require 'dni_extra'.
443+
Values can be calculated using
444+
:py:func:`~pvlib.irradiance.get_extra_radiation`.
445+
446+
The ``'perez'`` model requires relative airmass (``airmass``) as input. If
447+
``airmass`` is not provided, it is calculated using the defaults in
448+
:py:func:`~pvlib.atmosphere.get_relative_airmass`.
418449
"""
419450

420451
model = model.lower()
452+
453+
if (model in {'haydavies', 'reindl', 'perez'}) and (dni_extra is None):
454+
raise ValueError(f'dni_extra is required for model {model}')
455+
421456
if model == 'isotropic':
422457
sky = isotropic(surface_tilt, dhi)
423458
elif model == 'klucher':
@@ -432,6 +467,8 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
432467
elif model == 'king':
433468
sky = king(surface_tilt, dhi, ghi, solar_zenith)
434469
elif model == 'perez':
470+
if airmass is None:
471+
airmass = atmosphere.get_relative_airmass(solar_zenith)
435472
sky = perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
436473
solar_zenith, solar_azimuth, airmass,
437474
model=model_perez)
@@ -504,7 +541,7 @@ def poa_components(aoi, dni, poa_sky_diffuse, poa_ground_diffuse):
504541
def get_ground_diffuse(surface_tilt, ghi, albedo=.25, surface_type=None):
505542
'''
506543
Estimate diffuse irradiance from ground reflections given
507-
irradiance, albedo, and surface tilt
544+
irradiance, albedo, and surface tilt.
508545
509546
Function to determine the portion of irradiance on a tilted surface
510547
due to ground reflections. Any of the inputs may be DataFrames or
@@ -518,7 +555,7 @@ def get_ground_diffuse(surface_tilt, ghi, albedo=.25, surface_type=None):
518555
(e.g. surface facing up = 0, surface facing horizon = 90).
519556
520557
ghi : numeric
521-
Global horizontal irradiance in W/m^2.
558+
Global horizontal irradiance. [W/m^2]
522559
523560
albedo : numeric, default 0.25
524561
Ground reflectance, typically 0.1-0.4 for surfaces on Earth
@@ -534,7 +571,7 @@ def get_ground_diffuse(surface_tilt, ghi, albedo=.25, surface_type=None):
534571
Returns
535572
-------
536573
grounddiffuse : numeric
537-
Ground reflected irradiances in W/m^2.
574+
Ground reflected irradiance. [W/m^2]
538575
539576
540577
References

pvlib/tests/test_irradiance.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,35 @@ def test_sky_diffuse_zenith_close_to_90(model):
281281
assert sky_diffuse < 100
282282

283283

284-
def test_get_sky_diffuse_invalid():
284+
def test_get_sky_diffuse_model_invalid():
285285
with pytest.raises(ValueError):
286286
irradiance.get_sky_diffuse(
287287
30, 180, 0, 180, 1000, 1100, 100, dni_extra=1360, airmass=1,
288288
model='invalid')
289289

290290

291+
def test_get_sky_diffuse_missing_dni_extra():
292+
msg = 'dni_extra is required'
293+
with pytest.raises(ValueError, match=msg):
294+
irradiance.get_sky_diffuse(
295+
30, 180, 0, 180, 1000, 1100, 100, airmass=1,
296+
model='haydavies')
297+
298+
299+
def test_get_sky_diffuse_missing_airmass(irrad_data, ephem_data, dni_et):
300+
# test assumes location is Tucson, AZ
301+
# calculated airmass should be the equivalent to fixture airmass
302+
dni = irrad_data['dni'].copy()
303+
dni.iloc[2] = np.nan
304+
out = irradiance.get_sky_diffuse(
305+
40, 180, ephem_data['apparent_zenith'], ephem_data['azimuth'], dni,
306+
irrad_data['ghi'], irrad_data['dhi'], dni_et, model='perez')
307+
expected = pd.Series(np.array(
308+
[0., 31.46046871, np.nan, 45.45539877]),
309+
index=irrad_data.index)
310+
assert_series_equal(out, expected, check_less_precise=2)
311+
312+
291313
def test_campbell_norman():
292314
expected = pd.DataFrame(np.array(
293315
[[863.859736967, 653.123094076, 220.65905025]]),
@@ -299,7 +321,8 @@ def test_campbell_norman():
299321
assert_frame_equal(out, expected)
300322

301323

302-
def test_get_total_irradiance(irrad_data, ephem_data, dni_et, relative_airmass):
324+
def test_get_total_irradiance(irrad_data, ephem_data, dni_et,
325+
relative_airmass):
303326
models = ['isotropic', 'klucher',
304327
'haydavies', 'reindl', 'king', 'perez']
305328

@@ -337,6 +360,30 @@ def test_get_total_irradiance_scalars(model):
337360
assert np.isnan(np.array(list(total.values()))).sum() == 0
338361

339362

363+
def test_get_total_irradiance_missing_dni_extra():
364+
msg = 'dni_extra is required'
365+
with pytest.raises(ValueError, match=msg):
366+
irradiance.get_total_irradiance(
367+
32, 180,
368+
10, 180,
369+
dni=1000, ghi=1100,
370+
dhi=100,
371+
model='haydavies')
372+
373+
374+
def test_get_total_irradiance_missing_airmass():
375+
total = irradiance.get_total_irradiance(
376+
32, 180,
377+
10, 180,
378+
dni=1000, ghi=1100,
379+
dhi=100,
380+
dni_extra=1400,
381+
model='perez')
382+
assert list(total.keys()) == ['poa_global', 'poa_direct',
383+
'poa_diffuse', 'poa_sky_diffuse',
384+
'poa_ground_diffuse']
385+
386+
340387
def test_poa_components(irrad_data, ephem_data, dni_et, relative_airmass):
341388
aoi = irradiance.aoi(40, 180, ephem_data['apparent_zenith'],
342389
ephem_data['azimuth'])

0 commit comments

Comments
 (0)