Skip to content

Commit 9a120dd

Browse files
mikofskiwholmgren
authored andcommitted
add linke turbidity formulas module to atmosphere (#278)
* add linke turbidity formulas module * add kasten 1996 pyrheliometric formula for linke turbidity factor * move atmosphere into package, just for fun, why not? * make __init__ with same api as before Signed-off-by: Mark Mikofski <[email protected]> * ignore venv, sublime * revert api changes from 6e9aaf0 * append linke_turb_forms.py to atmosphere.py * rm -rf atmosphere/ Signed-off-by: Mark Mikofski <[email protected]> * move demo to tests * import xrange from six for python 3 compatibility * fix pyrheliometric formula is from 1980 * fix docstring * just use `range` instead of importing `six` * change function to `kasten80_lt` to match `gueymard94_pw` * start adding references * test kaste96_lt against linke turbidity using tmy3 at 700nm * add lots of references * cite text quoted directly from Ineichen * acknowledge Armel * fix docstrings for numpydocs * removing filters from kasten pyrhelometric formula * refactor kasten96_lt api simpler * remove alpha argument * update docstring to explain that if molineaux method, then aod is shape is only aod at 700[nm] but if bird-hulstrom, then aod should be both aod380 and aod500 * add see also to angstrom_aod_at_lambda * add warning for air mass or pwat out of range * check for bird-hulstrom, raise type error if neither molineaux nor * also simplify angstrom_aod_at_lambda() api, change aod to aod0, change lambda0 to lambda1, and add lambda0, update docstring * update test_atmosphere.test_kasten96_lt with new changes * add angstrom_alpha() method in atmosphere to calculate alpha * simplify kasten96_lt API * separate arguments so their shapes don't change depending on method, ie: before aod could be 1, 2, N, N x 1 or N x 2 depending on whether Molineaux or Bird-Hulstrom were used, now, there are 3 separate args, aod700, aod380 and aod500. * fix misspelling of Hulstrom, no "d", note several sources also make this error :( * add new methods to api.rst * add test_angstrom tests * add raises value error test for bad method * add lower() to compare only lower case of method name * add new linke and angstrom functions to api.rst * fix more hulstrom errors, no "d" * fix typos in referecnes * add doi to sphinx docs as extlink * add 'doi': ('http://dx/doi.org/%s', 'DOI: ') to extlink in conf.py * change doi links everywhere to use :doi:`doi number` instead of links * make landscape happy and use r"""docstring with :math:`\alpha`""" * instead of """docstring with :math:`\\alpha` * add Co-Action publishing for original Tellus A DOI before moving to Taylor & Francis * reformat long lines to make landscape happy :) * use broadband AOD arg instead of methods * change kasten96_lt AOD args to just a single arg called aod_bb for broadband AOD * remove "method" arg and if-then-else on method.lower() in kasten96_lt() * add note that according to Molineaux, aod at 700 nm is a good approximation for broadband aod * add new function bird_hulstrom80_aod_bb to atmosphere.py * add it to api.rst * add new test for bird_hulstrom80_aod_bb() to test_atmosphere.py * update test_kasten96_lt() to use aod_bb * use verbose names: - s/am/airmass_absolute/g - s/pwat/precipitable_water/g * fix angstrom aod test to use more realistic values * don't use brackets around units * remove test for raises ValueError in test_kasten * use hard coded test values instead of TMY3 * values span pwat range 0 - 5 cm, and airmass between 1 & 2.5 atm * remove tmy3 file * don't try Bird-Hulstrom, not necessary * make pylint happy, isclose(a, b) a & b should be array or iterable * remove unused imports * fix test kasten * fix assert np.isclose() should be scalar only * super simplify test_kasten96_lt() to ranges of inputs and expected outputs, don't need to compare to actual data, just test that method is working nominally * remove clearsky import from test_atmosphere * add missing assert statements for tests * update hulstrom expected value * remove old melbourne-fl test data
1 parent 6a41299 commit 9a120dd

File tree

6 files changed

+245
-6
lines changed

6 files changed

+245
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var/
2222
Drafts/
2323
*/build/
2424
build/
25+
venv/
2526

2627
*.egg-info/
2728
.installed.cfg
@@ -58,6 +59,8 @@ coverage.xml
5859
.pydevproject
5960
.spyderproject
6061
.idea/
62+
*.sublime-project
63+
*.sublime-workspace
6164

6265
# Rope
6366
.ropeproject

docs/sphinx/source/api.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ Airmass and atmospheric models
8787
atmosphere.alt2pres
8888
atmosphere.gueymard94_pw
8989
atmosphere.first_solar_spectral_correction
90+
atmosphere.bird_hulstrom80_aod_bb
91+
atmosphere.kasten96_lt
92+
atmosphere.angstrom_aod_at_lambda
93+
atmosphere.angstrom_alpha
9094

9195

9296
Irradiance

docs/sphinx/source/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ def setup(app):
270270
extlinks = {'issue': ('https://github.com/pvlib/pvlib-python/issues/%s',
271271
'GH'),
272272
'wiki': ('https://github.com/pvlib/pvlib-python/wiki/%s',
273-
'wiki ')}
273+
'wiki '),
274+
'doi': ('http://dx.doi.org/%s', 'DOI: ')}
274275

275276
# -- Options for manual page output ---------------------------------------
276277

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ v0.4.4 (January xx, 2017)
66
Enhancements
77
~~~~~~~~~~~~
88

9+
* Added Kasten pyrheliometric formula to calculate Linke turbidity factors with
10+
improvements by Ineichen and Perez to extend range of air mass (:issue:`278`)
11+
912

1013
Documentation
1114
~~~~~~~~~~~~~
@@ -19,3 +22,4 @@ Contributors
1922
~~~~~~~~~~~~
2023

2124
* Will Holmgren
25+
* Mark Mikofski

pvlib/atmosphere.py

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ def gueymard94_pw(temp_air, relative_humidity):
292292
----------
293293
.. [1] W. M. Keogh and A. W. Blakers, Accurate Measurement, Using Natural
294294
Sunlight, of Silicon Solar Cells, Prog. in Photovoltaics: Res.
295-
and Appl. 2004, vol 12, pp. 1-19 (DOI: 10.1002/pip.517)
295+
and Appl. 2004, vol 12, pp. 1-19 (:doi:`10.1002/pip.517`)
296296
297297
.. [2] C. Gueymard, Analysis of Monthly Average Atmospheric Precipitable
298298
Water and Turbidity in Canada and Northern United States,
@@ -467,3 +467,193 @@ def first_solar_spectral_correction(pw, airmass_absolute, module_type=None,
467467
coeff[4]*np.sqrt(pw) + coeff[5]*ama/np.sqrt(pw))
468468

469469
return modifier
470+
471+
472+
def bird_hulstrom80_aod_bb(aod380, aod500):
473+
"""
474+
Approximate broadband aerosol optical depth.
475+
476+
Bird and Hulstrom developed a correlation for broadband aerosol optical
477+
depth (AOD) using two wavelengths, 380 nm and 500 nm.
478+
479+
Parameters
480+
----------
481+
aod380 : numeric
482+
AOD measured at 380 nm
483+
aod500 : numeric
484+
AOD measured at 500 nm
485+
486+
Returns
487+
-------
488+
aod_bb : numeric
489+
broadband AOD
490+
491+
See also
492+
--------
493+
kasten96_lt
494+
495+
References
496+
----------
497+
[1] Bird and Hulstrom, "Direct Insolation Models" (1980)
498+
`SERI/TR-335-344 <http://www.nrel.gov/docs/legosti/old/344.pdf>`_
499+
500+
[2] R. E. Bird and R. L. Hulstrom, "Review, Evaluation, and Improvement of
501+
Direct Irradiance Models", Journal of Solar Energy Engineering 103(3),
502+
pp. 182-192 (1981)
503+
:doi:`10.1115/1.3266239`
504+
"""
505+
# approximate broadband AOD using (Bird-Hulstrom 1980)
506+
return 0.27583 * aod380 + 0.35 * aod500
507+
508+
509+
def kasten96_lt(airmass_absolute, precipitable_water, aod_bb):
510+
"""
511+
Calculate Linke turbidity factor using Kasten pyrheliometric formula.
512+
513+
Note that broadband aerosol optical depth (AOD) can be approximated by AOD
514+
measured at 700 nm according to Molineaux [4] . Bird and Hulstrom offer an
515+
alternate approximation using AOD measured at 380 nm and 500 nm.
516+
517+
Based on original implementation by Armel Oumbe.
518+
519+
.. warning::
520+
These calculations are only valid for air mass less than 5 atm and
521+
precipitable water less than 5 cm.
522+
523+
Parameters
524+
----------
525+
airmass_absolute : numeric
526+
airmass, pressure corrected in atmospheres
527+
precipitable_water : numeric
528+
precipitable water or total column water vapor in centimeters
529+
aod_bb : numeric
530+
broadband AOD
531+
532+
Returns
533+
-------
534+
lt : numeric
535+
Linke turbidity
536+
537+
See also
538+
--------
539+
bird_hulstrom80_aod_bb
540+
angstrom_aod_at_lambda
541+
542+
References
543+
----------
544+
[1] F. Linke, "Transmissions-Koeffizient und Trubungsfaktor", Beitrage
545+
zur Physik der Atmosphare, Vol 10, pp. 91-103 (1922)
546+
547+
[2] F. Kasten, "A simple parameterization of the pyrheliometric formula for
548+
determining the Linke turbidity factor", Meteorologische Rundschau 33,
549+
pp. 124-127 (1980)
550+
551+
[3] Kasten, "The Linke turbidity factor based on improved values of the
552+
integral Rayleigh optical thickness", Solar Energy, Vol. 56, No. 3,
553+
pp. 239-244 (1996)
554+
:doi:`10.1016/0038-092X(95)00114-7`
555+
556+
[4] B. Molineaux, P. Ineichen, N. O'Neill, "Equivalence of pyrheliometric
557+
and monochromatic aerosol optical depths at a single key wavelength",
558+
Applied Optics Vol. 37, issue 10, 7008-7018 (1998)
559+
:doi:`10.1364/AO.37.007008`
560+
561+
[5] P. Ineichen, "Conversion function between the Linke turbidity and the
562+
atmospheric water vapor and aerosol content", Solar Energy 82,
563+
pp. 1095-1097 (2008)
564+
:doi:`10.1016/j.solener.2008.04.010`
565+
566+
[6] P. Ineichen and R. Perez, "A new airmass independent formulation for
567+
the Linke Turbidity coefficient", Solar Energy, Vol. 73, no. 3, pp. 151-157
568+
(2002)
569+
:doi:`10.1016/S0038-092X(02)00045-2`
570+
"""
571+
# "From numerically integrated spectral simulations done with Modtran
572+
# (Berk, 1989), Molineaux (1998) obtained for the broadband optical depth
573+
# of a clean and dry atmospshere (fictitious atmosphere that comprises only
574+
# the effects of Rayleigh scattering and absorption by the atmosphere gases
575+
# other than the water vapor) the following expression"
576+
# - P. Ineichen (2008)
577+
delta_cda = -0.101 + 0.235 * airmass_absolute ** (-0.16)
578+
# "and the broadband water vapor optical depth where pwat is the integrated
579+
# precipitable water vapor content of the atmosphere expressed in cm and am
580+
# the optical air mass. The precision of these fits is better than 1% when
581+
# compared with Modtran simulations in the range 1 < am < 5 and
582+
# 0 < pwat < 5 cm at sea level" - P. Ineichen (2008)
583+
delta_w = 0.112 * airmass_absolute ** (-0.55) * precipitable_water ** 0.34
584+
# broadband AOD
585+
delta_a = aod_bb
586+
# "Then using the Kasten pyrheliometric formula (1980, 1996), the Linke
587+
# turbidity at am = 2 can be written. The extension of the Linke turbidity
588+
# coefficient to other values of air mass was published by Ineichen and
589+
# Perez (2002)" - P. Ineichen (2008)
590+
lt = -(9.4 + 0.9 * airmass_absolute) * np.log(
591+
np.exp(-airmass_absolute * (delta_cda + delta_w + delta_a))
592+
) / airmass_absolute
593+
# filter out of extrapolated values
594+
return lt
595+
596+
597+
def angstrom_aod_at_lambda(aod0, lambda0, alpha, lambda1=700.0):
598+
r"""
599+
Get AOD at specified wavelength using Angstrom turbidity model.
600+
601+
Parameters
602+
----------
603+
aod0 : numeric
604+
aerosol optical depth (AOD) measured at known wavelength
605+
lambda0 : numeric
606+
wavelength in nanometers corresponding to ``aod0``
607+
alpha : numeric
608+
Angstrom :math:`\alpha` exponent corresponding to ``aod0``
609+
lambda1 : numeric
610+
desired wavelength in nanometers, defaults to 700 nm
611+
612+
Returns
613+
-------
614+
aod1 : numeric
615+
AOD at desired wavelength, ``lambda1``
616+
617+
See also
618+
--------
619+
angstrom_alpha
620+
621+
References
622+
----------
623+
[1] Anders Angstrom, "On the Atmospheric Transmission of Sun Radiation and
624+
On Dust in the Air", Geografiska Annaler Vol. 11, pp. 156-166 (1929) JSTOR
625+
:doi:`10.2307/519399`
626+
627+
[2] Anders Angstrom, "Techniques of Determining the Turbidity of the
628+
Atmosphere", Tellus 13:2, pp. 214-223 (1961) Taylor & Francis
629+
:doi:`10.3402/tellusa.v13i2.9493` and Co-Action Publishing
630+
:doi:`10.1111/j.2153-3490.1961.tb00078.x`
631+
"""
632+
return aod0 * ((lambda1 / lambda0) ** (-alpha))
633+
634+
635+
def angstrom_alpha(aod1, lambda1, aod2, lambda2):
636+
r"""
637+
Calculate Angstrom alpha exponent.
638+
639+
Parameters
640+
----------
641+
aod1 : numeric
642+
first aerosol optical depth
643+
lambda1 : numeric
644+
wavelength in nanometers corresponding to ``aod1``
645+
aod2 : numeric
646+
second aerosol optical depth
647+
lambda2 : numeric
648+
wavelength in nanometers corresponding to ``aod2``
649+
650+
Returns
651+
-------
652+
alpha : numeric
653+
Angstrom :math:`\alpha` exponent for AOD in ``(lambda1, lambda2)``
654+
655+
See also
656+
--------
657+
angstrom_aod_at_lambda
658+
"""
659+
return - np.log(aod1 / aod2) / np.log(lambda1 / lambda2)

pvlib/test/test_atmosphere.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import datetime
21
import itertools
32

43
import numpy as np
@@ -7,10 +6,8 @@
76
import pytest
87
from numpy.testing import assert_allclose
98

10-
from pvlib.location import Location
11-
from pvlib import solarposition
129
from pvlib import atmosphere
13-
10+
from pvlib import solarposition
1411

1512
latitude, longitude, tz, altitude = 32.2, -111, 'US/Arizona', 700
1613

@@ -113,3 +110,43 @@ def test_first_solar_spectral_correction_supplied():
113110
def test_first_solar_spectral_correction_ambiguous():
114111
with pytest.raises(TypeError):
115112
atmosphere.first_solar_spectral_correction(1, 1)
113+
114+
115+
def test_kasten96_lt():
116+
"""Test Linke turbidity factor calculated from AOD, Pwat and AM"""
117+
amp = np.array([1, 3, 5])
118+
pwat = np.array([0, 2.5, 5])
119+
aod_bb = np.array([0, 0.1, 1])
120+
lt_expected = np.array(
121+
[[[1.3802, 2.4102, 11.6802],
122+
[1.16303976, 2.37303976, 13.26303976],
123+
[1.12101907, 2.51101907, 15.02101907]],
124+
125+
[[2.95546945, 3.98546945, 13.25546945],
126+
[2.17435443, 3.38435443, 14.27435443],
127+
[1.99821967, 3.38821967, 15.89821967]],
128+
129+
[[3.37410769, 4.40410769, 13.67410769],
130+
[2.44311797, 3.65311797, 14.54311797],
131+
[2.23134152, 3.62134152, 16.13134152]]]
132+
)
133+
lt = atmosphere.kasten96_lt(*np.meshgrid(amp, pwat, aod_bb))
134+
assert np.allclose(lt, lt_expected, 1e-3)
135+
return lt
136+
137+
138+
def test_angstrom_aod():
139+
"""Test Angstrom turbidity model functions."""
140+
aod550 = 0.15
141+
aod1240 = 0.05
142+
alpha = atmosphere.angstrom_alpha(aod550, 550, aod1240, 1240)
143+
assert np.isclose(alpha, 1.3513924317859232)
144+
aod700 = atmosphere.angstrom_aod_at_lambda(aod550, 550, alpha)
145+
assert np.isclose(aod700, 0.10828110997681031)
146+
147+
148+
def test_bird_hulstrom80_aod_bb():
149+
"""Test Bird_Hulstrom broadband AOD."""
150+
aod380, aod500 = 0.22072480948195175, 0.1614279181106312
151+
bird_hulstrom = atmosphere.bird_hulstrom80_aod_bb(aod380, aod500)
152+
assert np.isclose(0.11738229553812768, bird_hulstrom)

0 commit comments

Comments
 (0)