From 532d1979ad669e0c78fda2d1d4f3f0020717ecc4 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sat, 19 Sep 2020 18:58:54 -0600 Subject: [PATCH 01/26] spectrl2 implementation --- pvlib/spectrum.py | 249 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 pvlib/spectrum.py diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py new file mode 100644 index 0000000000..709319422a --- /dev/null +++ b/pvlib/spectrum.py @@ -0,0 +1,249 @@ +r""" +The ``spectrum`` module contains functions that implement models for the solar +irradiance spectrum. +""" + +import pvlib +from pvlib.tools import cosd +import numpy as np + +# SPECTRL2 extraterrestrial spectrum and atmospheric absorption coefficients +_SPECTRL2_COEFFS = np.zeros(122, dtype=np.dtype([ + ('wavelength', 'float64'), + ('spectral_irradiance_et', 'float64'), + ('water_vapor_absorption', 'float64'), + ('ozone_absorption', 'float64'), + ('mixed_absorption', 'float64'), +])) +_SPECTRL2_COEFFS['wavelength'] = [ # um + 0.3, 0.305, 0.31, 0.315, 0.32, 0.325, 0.33, 0.335, 0.34, 0.345, 0.35, + 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, + 0.48, 0.49, 0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.57, 0.593, 0.61, 0.63, + 0.656, 0.6676, 0.69, 0.71, 0.718, 0.7244, 0.74, 0.7525, 0.7575, 0.7625, + 0.7675, 0.78, 0.8, 0.816, 0.8237, 0.8315, 0.84, 0.86, 0.88, 0.905, 0.915, + 0.925, 0.93, 0.937, 0.948, 0.965, 0.98, 0.9935, 1.04, 1.07, 1.1, 1.12, + 1.13, 1.145, 1.161, 1.17, 1.2, 1.24, 1.27, 1.29, 1.32, 1.35, 1.395, 1.4425, + 1.4625, 1.477, 1.497, 1.52, 1.539, 1.558, 1.578, 1.592, 1.61, 1.63, 1.646, + 1.678, 1.74, 1.8, 1.86, 1.92, 1.96, 1.985, 2.005, 2.035, 2.065, 2.1, 2.148, + 2.198, 2.27, 2.36, 2.45, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, + 3.5, 3.6, 3.7, 3.8, 3.9, 4.0 +] +_SPECTRL2_COEFFS['spectral_irradiance_et'] = [ # W/m^2/um + 535.9, 558.3, 622.0, 692.7, 715.1, 832.9, 961.9, 931.9, 900.6, 911.3, + 975.5, 975.9, 1119.9, 1103.8, 1033.8, 1479.1, 1701.3, 1740.4, 1587.2, + 1837.0, 2005.0, 2043.0, 1987.0, 2027.0, 1896.0, 1909.0, 1927.0, 1831.0, + 1891.0, 1898.0, 1892.0, 1840.0, 1768.0, 1728.0, 1658.0, 1524.0, 1531.0, + 1420.0, 1399.0, 1374.0, 1373.0, 1298.0, 1269.0, 1245.0, 1223.0, 1205.0, + 1183.0, 1148.0, 1091.0, 1062.0, 1038.0, 1022.0, 998.7, 947.2, 893.2, 868.2, + 829.7, 830.3, 814.0, 786.9, 768.3, 767.0, 757.6, 688.1, 640.7, 606.2, + 585.9, 570.2, 564.1, 544.2, 533.4, 501.6, 477.5, 442.7, 440.0, 416.8, + 391.4, 358.9, 327.5, 317.5, 307.3, 300.4, 292.8, 275.5, 272.1, 259.3, + 246.9, 244.0, 243.5, 234.8, 220.5, 190.8, 171.1, 144.5, 135.7, 123.0, + 123.8, 113.0, 108.5, 97.5, 92.4, 82.4, 74.6, 68.3, 63.8, 49.5, 48.5, 38.6, + 36.6, 32.0, 28.1, 24.8, 22.1, 19.6, 17.5, 15.7, 14.1, 12.7, 11.5, 10.4, + 9.5, 8.6 +] +_SPECTRL2_COEFFS['water_vapor_absorption'] = [ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.075, 0.0, 0.0, 0.0, 0.0, 0.016, 0.0125, 1.8, 2.5, 0.061, + 0.0008, 0.0001, 1e-05, 1e-05, 0.0006, 0.036, 1.6, 2.5, 0.5, 0.155, 1e-05, + 0.0026, 7.0, 5.0, 5.0, 27.0, 55.0, 45.0, 4.0, 1.48, 0.1, 1e-05, 0.001, 3.2, + 115.0, 70.0, 75.0, 10.0, 5.0, 2.0, 0.002, 0.002, 0.1, 4.0, 200.0, 1000.0, + 185.0, 80.0, 80.0, 12.0, 0.16, 0.002, 0.0005, 0.0001, 1e-05, 0.0001, 0.001, + 0.01, 0.036, 1.1, 130.0, 1000.0, 500.0, 100.0, 4.0, 2.9, 1.0, 0.4, 0.22, + 0.25, 0.33, 0.5, 4.0, 80.0, 310.0, 15000.0, 22000.0, 8000.0, 650.0, 240.0, + 230.0, 100.0, 120.0, 19.5, 3.6, 3.1, 2.5, 1.4, 0.17, 0.0045 +] +_SPECTRL2_COEFFS['ozone_absorption'] = [ + 10.0, 4.8, 2.7, 1.35, 0.8, 0.38, 0.16, 0.075, 0.04, 0.019, 0.007, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.003, 0.006, 0.009, 0.014, 0.021, 0.03, + 0.04, 0.048, 0.063, 0.075, 0.085, 0.12, 0.119, 0.12, 0.09, 0.065, 0.051, + 0.028, 0.018, 0.015, 0.012, 0.01, 0.008, 0.007, 0.006, 0.005, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 +] +_SPECTRL2_COEFFS['mixed_absorption'] = [ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.15, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, + 0.35, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.05, 0.3, + 0.02, 0.0002, 0.00011, 1e-05, 0.05, 0.011, 0.005, 0.0006, 0.0, 0.005, 0.13, + 0.04, 0.06, 0.13, 0.001, 0.0014, 0.0001, 1e-05, 1e-05, 0.0001, 0.001, 4.3, + 0.2, 21.0, 0.13, 1.0, 0.08, 0.001, 0.00038, 0.001, 0.0005, 0.00015, + 0.00014, 0.00066, 100.0, 150.0, 0.13, 0.0095, 0.001, 0.8, 1.9, 1.3, 0.075, + 0.01, 0.00195, 0.004, 0.29, 0.025 +] + + +def _spectrl2_transmittances(apparent_zenith, relative_airmass, + surface_pressure, precipitable_water, ozone, + optical_thickness, scattering_albedo, dayofyear): + """ + Calculate transmittance factors from Section 2 of Bird and Riordan 1984. + """ + wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] + vapor_coeff = _SPECTRL2_COEFFS['water_vapor_absorption'][:, np.newaxis] + ozone_coeff = _SPECTRL2_COEFFS['ozone_absorption'][:, np.newaxis] + mixed_coeff = _SPECTRL2_COEFFS['mixed_absorption'][:, np.newaxis] + + # ET spectral irradiance correction for earth-sun distance seasonality + day_angle = 2 * np.pi * (dayofyear - 1) / 365.0 # Eq 2-3 + earth_sun_distance_correction = ( + 1.000110 + + 0.034221 * np.cos(day_angle) + + 0.001280 * np.sin(day_angle) + + 0.000719 * np.cos(2*day_angle) + + 0.000077 * np.sin(2*day_angle) + ) # Eq 2-2 + + # Rayleigh scattering + airmass = relative_airmass * surface_pressure / 101300 + rayleigh_transmittance = np.exp( + #-airmass / (wavelength**4 * (115.6406 - 1.335 / wavelength**2)) + -airmass / (wavelength**4 * (115.6406 - 1.3366 / wavelength**2)) + ) # Eq 2-4 + + # Aerosol scattering and absorption, Eq 2-6 + aerosol_transmittance = np.exp(-optical_thickness * relative_airmass) + + # Water vapor absorption, Eq 2-8 + aWM = vapor_coeff * precipitable_water * relative_airmass + vapor_transmittance = np.exp(-0.2385 * aWM / (1 + 20.07 * aWM)**0.45) + + # Ozone absorption + ozone_max_height = 22 + h0_norm = ozone_max_height / 6370 + ozone_mass_numerator = (1 + h0_norm) + ozone_mass_denominator = np.sqrt(cosd(apparent_zenith)**2 + 2 * h0_norm) + ozone_mass = ozone_mass_numerator / ozone_mass_denominator # Eq 2-10 + ozone_transmittance = np.exp(-ozone_coeff * ozone * ozone_mass) # Eq 2-9 + + # Mixed gas absorption, Eq 2-11 + aM = mixed_coeff * airmass + #mixed_transmittance = np.exp(-1.41 * aM / (1 + 118.93 * aM)**0.45) + mixed_transmittance = np.exp(-1.41 * aM / (1 + 118.3 * aM)**0.45) + + # split out aerosol components for diffuse irradiance calcs + aerosol_scattering = ( + np.exp(-scattering_albedo * optical_thickness * relative_airmass) + ) # Eq 3-9 + + aerosol_absorption = np.exp( + -(1 - scattering_albedo) * optical_thickness * relative_airmass + ) # Eq 3-10 + + return ( + earth_sun_distance_correction, + rayleigh_transmittance, + aerosol_transmittance, + vapor_transmittance, + ozone_transmittance, + mixed_transmittance, + aerosol_scattering, + aerosol_absorption, + locals() + ) + + +def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, + surface_pressure, precipitable_water, ozone, dayofyear, + alpha=1.14, scattering_albedo_04=0.945, + aerosol_thickness_500nm=0.5, wavelength_variation_factor=0.095, + aerosol_asymmetry_factor=0.65): + """ + Estimate the spectral irradiance using the Bird Simple Spectral Model. + + The Bird Simple Spectral Model produces terrestrial spectra between 0.3 + and 400 um with a resolution of approximately 10 nm. Direct and diffuse + spectral irradiance are modeled for horizontal and tilted surfaces under + cloudless skies. + + Parameters + ---------- + + Returns + ------- + + """ + relative_airmass = pvlib.atmosphere.get_relative_airmass( + apparent_zenith, model='kasten1966' + ) # Eq 2-5 + + wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] + spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] + + optical_thickness = ( + aerosol_thickness_500nm * (wavelength / 0.5)**-alpha + ) # Eq 2-7 + + # Eq 3-16 + scattering_albedo = scattering_albedo_04 * \ + np.exp(-wavelength_variation_factor * np.log(wavelength / 0.4)**2) + + spectrl2 = _spectrl2_transmittances(apparent_zenith, relative_airmass, + surface_pressure, precipitable_water, + ozone, optical_thickness, + scattering_albedo, dayofyear) + D, Tr, Ta, Tw, To, Tu, Tas, Taa, sub_extras = spectrl2 + + # spectrum of direct irradiance, Eq 2-1 + Id = spectrum_et * D * Tr * Ta * Tw * To * Tu + + cosZ = cosd(apparent_zenith) + Cs = np.where(wavelength <= 0.45, (wavelength + 0.55)**1.8, 1.0) # Eq 3-17 + ALG = np.log(1 - aerosol_asymmetry_factor) # Eq 3-14 + BFS = ALG * (0.0783 + ALG * (-0.3824 - ALG * 0.5874)) # Eq 3-13 + AFS = ALG * (1.459 + ALG * (0.1595 + ALG * 0.4129)) # Eq 3-12 + cosZ = cosd(apparent_zenith) + Fs = 1 - 0.5 * np.exp((AFS + BFS * cosZ) * cosZ) # Eq 3-11 + Fsp = 1 - 0.5 * np.exp((AFS + BFS / 1.8) / 1.8) # Eq 3.15 + + # evaluate the "primed terms" -- transmittances evaluated at airmass=1.8 + primes = _spectrl2_transmittances(apparent_zenith, 1.8, + surface_pressure, precipitable_water, + ozone, optical_thickness, + scattering_albedo, dayofyear) + _, Trp, Tap, Twp, Top, Tup, Tasp, Taap, sub_extras2 = primes + + # NOTE: not sure what the correct form of this equation is. + # The first coefficient is To' in Eq 3-8 but Tu' in the code appendix. + sky_reflectivity = ( + Tup * Twp * Taap * (0.5 * (1-Trp) + (1-Fsp) * Trp * (1-Tasp)) + ) # Eq 3-8 + + # a common factor for 3-5 and 3-6 + common_factor = spectrum_et * D * cosZ * To * Tu * Tw * Taa + Ir = common_factor * (1 - Tr**0.95) * 0.5 * Cs # Eq 3-5 + Ia = common_factor * Tr**1.5 * (1 - Tas) * Fs * Cs # Eq 3-6 + rs = sky_reflectivity + rg = ground_albedo + Ig = (Id * cosZ + Ir + Ia) * rs * rg * Cs / (1 - rs * rg) # Eq 3-7 + + # total scattered irradiance + Is = Ir + Ia + Ig # Eq 3-1 + + # calculate spectral irradiance on a tilted surface, Eq 3-18 + Ibeam = Id * cosd(aoi) + + # don't need surface_azimuth if we provide projection_ratio + projection_ratio = cosd(aoi) / cosZ + Isky = pvlib.irradiance.haydavies(surface_tilt=surface_tilt, + surface_azimuth=None, + dhi=Is, + dni=Id, + dni_extra=spectrum_et * D, + projection_ratio=projection_ratio) + + ghi = Id * cosZ + Is + Iground = pvlib.irradiance.get_ground_diffuse(surface_tilt, ghi, albedo=rg) + + Itilt = Ibeam + Isky + Iground + + extras = locals() + + return wavelength, Is, Id, spectrum_et * D, Itilt, extras From 87b3dfc67338b070c798a5aed7369f080d3b5a9c Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:04:10 -0600 Subject: [PATCH 02/26] sphinx --- docs/sphinx/source/api.rst | 7 +++++++ docs/sphinx/source/whatsnew.rst | 1 + docs/sphinx/source/whatsnew/v0.8.1.rst | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 077a5e121d..19d17027a1 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -413,6 +413,13 @@ Shading shading.masking_angle_passias shading.sky_diffuse_passias +Spectrum +-------- + +.. autosummary:: + :toctree: generated/ + + spectrum.spectrl2 Tracking ======== diff --git a/docs/sphinx/source/whatsnew.rst b/docs/sphinx/source/whatsnew.rst index 0e71fd788b..271c898af6 100644 --- a/docs/sphinx/source/whatsnew.rst +++ b/docs/sphinx/source/whatsnew.rst @@ -6,6 +6,7 @@ What's New These are new features and improvements of note in each release. +.. include:: whatsnew/v0.8.1.rst .. include:: whatsnew/v0.8.0.rst .. include:: whatsnew/v0.7.2.rst .. include:: whatsnew/v0.7.1.rst diff --git a/docs/sphinx/source/whatsnew/v0.8.1.rst b/docs/sphinx/source/whatsnew/v0.8.1.rst index 50e01401f5..0f4f6576d7 100644 --- a/docs/sphinx/source/whatsnew/v0.8.1.rst +++ b/docs/sphinx/source/whatsnew/v0.8.1.rst @@ -13,7 +13,8 @@ Deprecations Enhancements ~~~~~~~~~~~~ - +* Add a numpy-based implementation of the SPECTRL2 spectral irradiance model. + (:pull:`XX`) Bug fixes ~~~~~~~~~ From 0605e508fcbe023f20618b40678c40bf7466a87e Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:04:26 -0600 Subject: [PATCH 03/26] package updates --- pvlib/__init__.py | 1 + pvlib/spectrum.py | 129 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 100 insertions(+), 30 deletions(-) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index c2d645d169..09a18d7069 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -16,3 +16,4 @@ from pvlib import bifacial # noqa: F401 from pvlib import soiling # noqa: F401 from pvlib import snow # noqa: F401 +from pvlib import spectrum #noqa: F401 \ No newline at end of file diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 709319422a..ead872a3a6 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -104,7 +104,8 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, # Rayleigh scattering airmass = relative_airmass * surface_pressure / 101300 rayleigh_transmittance = np.exp( - #-airmass / (wavelength**4 * (115.6406 - 1.335 / wavelength**2)) + # Note: the report uses 1.335 but spectrl2_2.c uses 1.3366 + # -airmass / (wavelength**4 * (115.6406 - 1.335 / wavelength**2)) -airmass / (wavelength**4 * (115.6406 - 1.3366 / wavelength**2)) ) # Eq 2-4 @@ -125,7 +126,8 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, # Mixed gas absorption, Eq 2-11 aM = mixed_coeff * airmass - #mixed_transmittance = np.exp(-1.41 * aM / (1 + 118.93 * aM)**0.45) + # Note: the report uses 118.93, but spectrl2_2.c uses 118.3 + # mixed_transmittance = np.exp(-1.41 * aM / (1 + 118.93 * aM)**0.45) mixed_transmittance = np.exp(-1.41 * aM / (1 + 118.3 * aM)**0.45) # split out aerosol components for diffuse irradiance calcs @@ -146,53 +148,103 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, mixed_transmittance, aerosol_scattering, aerosol_absorption, - locals() ) def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, - surface_pressure, precipitable_water, ozone, dayofyear, - alpha=1.14, scattering_albedo_04=0.945, - aerosol_thickness_500nm=0.5, wavelength_variation_factor=0.095, + surface_pressure, relative_airmass, precipitable_water, ozone, + dayofyear, aerosol_turbidity_500nm, scattering_albedo_400nm=0.945, + alpha=1.14, wavelength_variation_factor=0.095, aerosol_asymmetry_factor=0.65): """ - Estimate the spectral irradiance using the Bird Simple Spectral Model. + Estimate spectral irradiance using the Bird Simple Spectral Model + (SPECTRL2). - The Bird Simple Spectral Model produces terrestrial spectra between 0.3 - and 400 um with a resolution of approximately 10 nm. Direct and diffuse + The Bird Simple Spectral Model [1]_ produces terrestrial spectra between + 0.3 and 4 um with a resolution of approximately 10 nm. Direct and diffuse spectral irradiance are modeled for horizontal and tilted surfaces under - cloudless skies. + cloudless skies. SPECTRL2 models radiative transmission, absorption, and + scattering due to atmospheric aerosol, water vapor, and ozone content. Parameters ---------- + surface_tilt : float or numpy array + Panel tilt from horizontal [degrees] + apparent_zenith : float or numpy array + Solar zenith angle [degrees] + aoi : float or numpy array + Angle of incidence of the solar vector on the panel [degrees] + ground_albedo : float or numpy array + Albedo [0-1] of the ground surface. Can be provided as a scalar value + if albedo is not spectrally-dependent, or as a 122xN matrix where + the first dimension spans the wavelength range and the second spans + the number of simulations. [unitless] + surface_pressure : float or numpy array + Surface pressure [Pa] + relative_airmass : float or numpy array + Relative airmass. The airmass model used in [1]_ is the `'kasten1966'` + model, while a later implementation by NREL uses the + `'kastenyoung1989'` model. [unitless] + precipitable_water : float or numpy array + Atmospheric water vapor content [cm] + ozone : float or numpy array + Atmospheric ozone content [atm-cm] + dayofyear : float or numpy array + The day of year [1-365] + aerosol_turbidity_500nm : float or numpy array + Aerosol turbidity at 500 nm [unitless] + scattering_albedo_400nm : float or numpy array, default 0.945 + Aerosol single scattering albedo at 400nm. The default value of 0.945 + is suggested in [1]_ for a rural aerosol model. [unitless] + alpha : float or numpy array, default 1.14 + Angstrom turbidity exponent. The default value of 1.14 is suggested + in [1]_ for a rural aerosol model. [unitless] + wavelength_variation_factor : float or numpy array, default 0.095 + Wavelength variation factor [unitless] + aerosol_asymmetry_factor : float or numpy array, default 0.65 + Aeorosol asymmetry factor (mean cosine of scattering angle) [unitless] Returns ------- - + spectra : dict + A dict of arrays of length 122. All values are spectral irradiance + with units W/m^2/um except for `wavelength`, which is in microns. + * wavelength + * et + * dhi + * dni + * poa_sky_diffuse + * poa_ground_diffuse + * poa_direct + * poa_global + + References + ---------- + .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for + direct and diffuse irradiance on horizontal and tilted planes at the + earth's surface for cloudless atmospheres", NREL Technical Report + TR-215-2436 doi:10.2172/5986936. """ - relative_airmass = pvlib.atmosphere.get_relative_airmass( - apparent_zenith, model='kasten1966' - ) # Eq 2-5 - wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] optical_thickness = ( - aerosol_thickness_500nm * (wavelength / 0.5)**-alpha + aerosol_turbidity_500nm * (wavelength / 0.5)**-alpha ) # Eq 2-7 # Eq 3-16 - scattering_albedo = scattering_albedo_04 * \ + scattering_albedo = scattering_albedo_400nm * \ np.exp(-wavelength_variation_factor * np.log(wavelength / 0.4)**2) spectrl2 = _spectrl2_transmittances(apparent_zenith, relative_airmass, surface_pressure, precipitable_water, ozone, optical_thickness, scattering_albedo, dayofyear) - D, Tr, Ta, Tw, To, Tu, Tas, Taa, sub_extras = spectrl2 + D, Tr, Ta, Tw, To, Tu, Tas, Taa = spectrl2 + spectrum_et_adj = spectrum_et * D # spectrum of direct irradiance, Eq 2-1 - Id = spectrum_et * D * Tr * Ta * Tw * To * Tu + Id = spectrum_et_adj * Tr * Ta * Tw * To * Tu cosZ = cosd(apparent_zenith) Cs = np.where(wavelength <= 0.45, (wavelength + 0.55)**1.8, 1.0) # Eq 3-17 @@ -208,24 +260,34 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, surface_pressure, precipitable_water, ozone, optical_thickness, scattering_albedo, dayofyear) - _, Trp, Tap, Twp, Top, Tup, Tasp, Taap, sub_extras2 = primes + _, Trp, Tap, Twp, Top, Tup, Tasp, Taap = primes - # NOTE: not sure what the correct form of this equation is. + # Note: not sure what the correct form of this equation is. # The first coefficient is To' in Eq 3-8 but Tu' in the code appendix. + # spectrl2_2.c uses Tu'. sky_reflectivity = ( + # Top * Twp * Taap * (0.5 * (1-Trp) + (1-Fsp) * Trp * (1-Tasp)) Tup * Twp * Taap * (0.5 * (1-Trp) + (1-Fsp) * Trp * (1-Tasp)) ) # Eq 3-8 # a common factor for 3-5 and 3-6 - common_factor = spectrum_et * D * cosZ * To * Tu * Tw * Taa - Ir = common_factor * (1 - Tr**0.95) * 0.5 * Cs # Eq 3-5 - Ia = common_factor * Tr**1.5 * (1 - Tas) * Fs * Cs # Eq 3-6 + common_factor = spectrum_et_adj * cosZ * To * Tu * Tw * Taa + # Note: spectrl2_2.c differs from the report in how the Cs value is used. + # The two commented out lines match the report, while the following match + # spectrl2_2.c: + # Ir = common_factor * (1 - Tr**0.95) * 0.5 * Cs # Eq 3-5 + # Ia = common_factor * Tr**1.5 * (1 - Tas) * Fs * Cs # Eq 3-6 + Ir = common_factor * (1 - Tr**0.95) * 0.5 # Eq 3-5 + Ia = common_factor * Tr**1.5 * (1 - Tas) * Fs # Eq 3-6 + rs = sky_reflectivity rg = ground_albedo - Ig = (Id * cosZ + Ir + Ia) * rs * rg * Cs / (1 - rs * rg) # Eq 3-7 + Ig = (Id * cosZ + Ir + Ia) * rs * rg / (1 - rs * rg) # Eq 3-7 # total scattered irradiance - Is = Ir + Ia + Ig # Eq 3-1 + # Note: see discussion about Cs above. + # Is = Ir + Ia + Ig # Eq 3-1 + Is = (Ir + Ia + Ig) * Cs # Eq 3-1 # calculate spectral irradiance on a tilted surface, Eq 3-18 Ibeam = Id * cosd(aoi) @@ -236,7 +298,7 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, surface_azimuth=None, dhi=Is, dni=Id, - dni_extra=spectrum_et * D, + dni_extra=spectrum_et_adj, projection_ratio=projection_ratio) ghi = Id * cosZ + Is @@ -244,6 +306,13 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, Itilt = Ibeam + Isky + Iground - extras = locals() - - return wavelength, Is, Id, spectrum_et * D, Itilt, extras + return { + 'wavelength': wavelength.ravel(), # This only ever needs 1 dimension + 'et': spectrum_et_adj, + 'dhi': Is, + 'dni': Id, + 'poa_sky_diffuse': Isky, + 'poa_ground_diffuse': Iground, + 'poa_direct': Ibeam, + 'poa_global': Itilt, + } From 779a047ade98826f1696f7a982fb62f84fc205c8 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:04:51 -0600 Subject: [PATCH 04/26] add tests --- pvlib/data/spectrl2_example_spectra.csv | 123 ++++++++++++++++++++++++ pvlib/tests/test_spectrum.py | 67 +++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 pvlib/data/spectrl2_example_spectra.csv create mode 100644 pvlib/tests/test_spectrum.py diff --git a/pvlib/data/spectrl2_example_spectra.csv b/pvlib/data/spectrl2_example_spectra.csv new file mode 100644 index 0000000000..7c1d8b19e9 --- /dev/null +++ b/pvlib/data/spectrl2_example_spectra.csv @@ -0,0 +1,123 @@ +,wavelength,specdif,specdir,specetr,specglo +0,0.30000001192092896,0.7665966153144836,0.40335652232170105,541.6846923828125,1.036954402923584 +1,0.3050000071525574,11.298118591308594,6.824652671813965,564.326416015625,15.872478485107422 +2,0.3100000023841858,36.487388610839844,25.05140495300293,628.7140502929688,53.278594970703125 +3,0.3149999976158142,80.22676849365234,62.04690170288086,700.1771850585938,121.81494903564453 +4,0.3199999928474426,108.57009887695312,93.82110595703125,722.8189697265625,171.45558166503906 +5,0.32499998807907104,155.0250701904297,148.59210205078125,841.8905639648438,254.62191772460938 +6,0.33000001311302185,198.05923461914062,209.17189025878906,972.2830200195312,338.2608947753906 +7,0.33500000834465027,198.03289794921875,229.05174255371094,941.959228515625,351.5594482421875 +8,0.3400000035762787,192.4607391357422,242.4566650390625,910.3212890625,354.97216796875 +9,0.3449999988079071,194.3870391845703,265.3816833496094,921.1368408203125,372.26446533203125 +10,0.3499999940395355,206.6869659423828,304.3901062011719,986.0298461914062,410.7105712890625 +11,0.36000001430511475,202.11509704589844,342.1354675292969,986.4341430664062,431.4382629394531 +12,0.3700000047683716,225.76776123046875,433.04852294921875,1131.988525390625,516.0272216796875 +13,0.3799999952316284,216.52438354492188,464.911865234375,1115.7147216796875,528.140869140625 +14,0.38999998569488525,197.29737854003906,469.2872619628906,1044.959228515625,511.84661865234375 +15,0.4000000059604645,274.6373291015625,717.1521606445312,1495.0657958984375,755.322998046875 +16,0.4099999964237213,307.39105224609375,874.312255859375,1719.664306640625,893.4163818359375 +17,0.41999998688697815,306.0761413574219,941.7630004882812,1759.1864013671875,937.311767578125 +18,0.4300000071525574,271.7977294921875,899.2123413085938,1604.3326416015625,874.5128784179688 +19,0.4399999976158142,306.446533203125,1084.28076171875,1856.8291015625,1033.20751953125 +20,0.44999998807907104,325.4994812011719,1225.8232421875,2026.642578125,1147.132080078125 +21,0.46000000834465027,317.2213134765625,1288.9774169921875,2065.052734375,1181.184326171875 +22,0.4699999988079071,295.31744384765625,1289.505615234375,2008.4482421875,1159.634521484375 +23,0.47999998927116394,288.3025817871094,1347.8497314453125,2048.880126953125,1191.7259521484375 +24,0.49000000953674316,258.0177917480469,1287.21337890625,1916.4659423828125,1120.7984619140625 +25,0.5,248.51402282714844,1318.9627685546875,1929.6063232421875,1132.5753173828125 +26,0.5099999904632568,240.04953002929688,1351.5888671875,1947.800537109375,1145.9791259765625 +27,0.5199999809265137,218.66915893554688,1302.779541015625,1850.7642822265625,1091.88330078125 +28,0.5299999713897705,215.91168212890625,1357.883544921875,1911.4119873046875,1126.0604248046875 +29,0.5400000214576721,207.6764678955078,1375.676513671875,1918.487548828125,1129.7513427734375 +30,0.550000011920929,198.75257873535156,1383.860595703125,1912.4227294921875,1126.31298828125 +31,0.5699999928474426,177.2172393798828,1355.662841796875,1859.8614501953125,1085.8775634765625 +32,0.5929999947547913,154.48672485351562,1309.30224609375,1787.0843505859375,1032.0728759765625 +33,0.6100000143051147,146.30194091796875,1325.1925048828125,1746.6524658203125,1034.538818359375 +34,0.6299999952316284,134.20530700683594,1312.0911865234375,1675.89697265625,1013.6607666015625 +35,0.656000018119812,115.96925354003906,1243.6280517578125,1540.450439453125,949.535888671875 +36,0.6675999760627747,113.64875793457031,1267.23583984375,1547.5260009765625,963.0390014648438 +37,0.6899999976158142,92.26598358154297,1114.307373046875,1435.327880859375,839.1528930664062 +38,0.7099999785423279,94.02474975585938,1197.5704345703125,1414.1011962890625,896.7203369140625 +39,0.7179999947547913,79.2312240600586,1045.2738037109375,1388.831298828125,779.8469848632812 +40,0.724399983882904,75.92426300048828,1022.4166870117188,1387.820556640625,761.2196044921875 +41,0.7400000095367432,80.37458038330078,1116.2152099609375,1312.010986328125,828.5402221679688 +42,0.7524999976158142,77.84077453613281,1116.2403564453125,1282.697998046875,826.0232543945312 +43,0.7574999928474426,75.56704711914062,1098.0296630859375,1258.4388427734375,811.54345703125 +44,0.762499988079071,45.746158599853516,696.9605102539062,1236.201416015625,512.89794921875 +45,0.7674999833106995,63.194129943847656,952.3276977539062,1218.007080078125,701.5109252929688 +46,0.7799999713897705,68.51187133789062,1054.7071533203125,1195.7696533203125,775.4505615234375 +47,0.800000011920929,62.87863540649414,1017.720458984375,1160.391845703125,745.0262451171875 +48,0.8159999847412109,51.39439010620117,871.8419799804688,1102.776611328125,635.7639770507812 +49,0.8237000107765198,47.48876953125,822.1177368164062,1073.4635009765625,598.5297241210938 +50,0.8314999938011169,50.44326400756836,882.2304077148438,1049.2044677734375,641.77587890625 +51,0.8399999737739563,50.50870895385742,897.87939453125,1033.03173828125,652.3304443359375 +52,0.8600000143051147,49.128204345703125,909.5646362304688,1009.4802856445312,658.7822265625 +53,0.8799999952316284,44.81117248535156,865.2081909179688,957.4243774414062,624.7343139648438 +54,0.9049999713897705,30.195653915405273,625.3776245117188,902.8414916992188,449.3675537109375 +55,0.9150000214576721,30.289173126220703,637.6793212890625,877.5715942382812,457.70654296875 +56,0.925000011920929,28.448226928710938,610.4505004882812,838.656005859375,437.61492919921875 +57,0.9300000071525574,19.592674255371094,432.5026550292969,839.262451171875,309.48626708984375 +58,0.9369999766349792,14.277915954589844,322.7142639160156,822.7865600585938,230.5836181640625 +59,0.9480000138282776,14.852182388305664,341.4164123535156,795.39404296875,243.69338989257812 +60,0.9649999737739563,25.331485748291016,583.9312744140625,776.59326171875,416.72314453125 +61,0.9800000190734863,27.016454696655273,635.7760009765625,775.2792358398438,453.1580810546875 +62,0.9934999942779541,28.801536560058594,689.1533203125,765.7777099609375,490.72039794921875 +63,1.0399999618530273,24.99117660522461,644.7619018554688,695.5275268554688,457.1557922363281 +64,1.0700000524520874,22.25499153137207,602.015625,647.6159057617188,425.76806640625 +65,1.100000023841858,16.722328186035156,479.10223388671875,612.7435302734375,337.8502197265625 +66,1.1200000047683716,5.06715202331543,155.4837188720703,592.224365234375,109.28324127197266 +67,1.1299999952316284,6.635468006134033,205.1408233642578,576.3549194335938,144.13522338867188 +68,1.1449999809265137,6.193835735321045,195.7523651123047,570.1890258789062,137.40078735351562 +69,1.1610000133514404,11.748560905456543,370.4906005859375,550.0742797851562,260.07733154296875 +70,1.1699999570846558,12.715644836425781,403.7008972167969,539.15771484375,283.30426025390625 +71,1.2000000476837158,12.658197402954102,416.2677001953125,507.014404296875,291.66998291015625 +72,1.2400000095367432,12.723058700561523,438.0545959472656,482.6542663574219,306.33795166015625 +73,1.2699999809265137,10.594919204711914,380.4599609375,447.4786376953125,265.6058349609375 +74,1.2899999618530273,10.970724105834961,402.2275085449219,444.7494812011719,280.5718078613281 +75,1.3200000524520874,8.522184371948242,326.9190979003906,421.2990417480469,227.64627075195312 +76,1.350000023841858,1.6226987838745117,67.14704132080078,395.6248779296875,46.62935256958008 +77,1.3949999809265137,0.12126490473747253,5.323389053344727,362.7740478515625,3.689373254776001 +78,1.4424999952316284,1.2762092351913452,58.43701171875,331.0351257324219,40.44478988647461 +79,1.462499976158142,2.304751396179199,106.74402618408203,320.92718505859375,73.8520736694336 +80,1.4769999980926514,2.212883234024048,104.03244018554688,310.6170654296875,71.94271850585938 +81,1.496999979019165,4.278558731079102,201.01097106933594,303.6426086425781,139.0102081298828 +82,1.5199999809265137,5.802607536315918,274.1861877441406,295.9605407714844,189.58140563964844 +83,1.5390000343322754,5.494048118591309,264.2200927734375,278.47381591796875,182.59288024902344 +84,1.5579999685287476,5.016831874847412,246.71206665039062,275.0371398925781,170.38055419921875 +85,1.5779999494552612,4.857605457305908,243.11094665527344,262.09893798828125,167.80760192871094 +86,1.5920000076293945,4.522202968597412,229.53021240234375,249.5651092529297,158.3694305419922 +87,1.6100000143051147,4.286829471588135,221.66986083984375,246.63380432128906,152.86550903320312 +88,1.6299999952316284,4.491518497467041,235.7653350830078,246.12840270996094,162.51797485351562 +89,1.6460000276565552,4.248502254486084,226.42987060546875,237.33450317382812,156.01766967773438 +90,1.6779999732971191,3.856564998626709,211.73875427246094,222.88014221191406,145.77871704101562 +91,1.7400000095367432,2.889831066131592,168.52455139160156,192.85955810546875,115.84679412841797 +92,1.7999999523162842,0.6722216010093689,42.90081787109375,172.94691467285156,29.427356719970703 +93,1.8600000143051147,0.03204738348722458,2.1758835315704346,146.0597686767578,1.4904770851135254 +94,1.9199999570846558,0.10780706256628036,7.664745330810547,137.16477966308594,5.2452569007873535 +95,1.9600000381469727,0.32299190759658813,23.525781631469727,124.32769775390625,16.09161949157715 +96,1.9850000143051147,1.270032286643982,91.32678985595703,125.13633728027344,62.483646392822266 +97,2.005000114440918,0.4182654023170471,31.37992286682129,114.21975708007812,21.451290130615234 +98,2.0350000858306885,1.2233593463897705,90.81049346923828,109.67118072509766,62.09091567993164 +99,2.065000057220459,0.9671005010604858,73.7654800415039,98.55244445800781,50.40989685058594 +100,2.0999999046325684,1.0698728561401367,83.00049591064453,93.39739227294922,56.70262145996094 +101,2.1480000019073486,0.966998815536499,77.45055389404297,83.2894515991211,52.87978744506836 +102,2.197999954223633,0.8404589295387268,69.74111938476562,75.4052505493164,47.58584213256836 +103,2.2699999809265137,0.7222429513931274,63.00203323364258,69.0372543334961,42.95062255859375 +104,2.359999895095825,0.5515680909156799,51.417625427246094,64.48867797851562,35.015262603759766 +105,2.450000047683716,0.17017853260040283,17.249914169311523,50.03431701660156,11.732279777526855 +106,2.5,0.05053719878196716,5.350593090057373,49.023521423339844,3.6368796825408936 +107,2.5999999046325684,3.1101667907762476e-09,3.513878539251891e-07,39.016658782958984,2.3863492515374674e-07 +108,2.700000047683716,2.7611552367440284e-12,3.305597739977628e-10,36.99506759643555,2.2432547486239685e-10 +109,2.799999952316284,2.0488089447212587e-08,2.5939837087207707e-06,32.34541702270508,1.7591577261555358e-06 +110,2.9000000953674316,0.007262714207172394,0.9694051146507263,28.4033203125,0.657025933265686 +111,3.0,0.02586463652551174,3.6192307472229004,25.067697525024414,2.4517266750335693 +112,3.0999999046325684,0.02316739596426487,3.4096922874450684,22.33855438232422,2.308582067489624 +113,3.200000047683716,0.032580118626356125,5.011773109436035,19.811569213867188,3.3918216228485107 +114,3.299999952316284,0.021878913044929504,3.5411267280578613,17.688899993896484,2.395390272140503 +115,3.4000000953674316,0.04572540521621704,7.63726282119751,15.86946964263916,5.1647539138793945 +116,3.5,0.0644257664680481,11.073113441467285,14.25220012664795,7.48640251159668 +117,3.5999999046325684,0.058482903987169266,10.483891487121582,12.837087631225586,7.085522174835205 +118,3.700000047683716,0.05231606587767601,9.779257774353027,11.624134063720703,6.607059955596924 +119,3.799999952316284,0.047291696071624756,9.203333854675293,10.512260437011719,6.216011047363281 +120,3.9000000953674316,0.04069847613573074,8.261123657226562,9.602545738220215,5.577882766723633 +121,4.0,0.039725467562675476,8.346587181091309,8.692831039428711,5.634192943572998 diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py new file mode 100644 index 0000000000..7808ba3197 --- /dev/null +++ b/pvlib/tests/test_spectrum.py @@ -0,0 +1,67 @@ +import pytest +from numpy.testing import assert_allclose +import pandas as pd +import numpy as np +from pvlib import spectrum +from conftest import DATA_DIR + +SPECTRL2_TEST_DATA = DATA_DIR / 'spectrl2_example_spectra.csv' + + +@pytest.fixture +def spectrl2_data(): + # reference spectra generated with solar_utils==0.3 + """ + expected = solar_utils.spectrl2( + units=1, + location=[40, -80, -5], + datetime=[2020, 3, 15, 10, 45, 59], + weather=[1013, 15], + orientation=[0, 180], + atmospheric_conditions=[1.14, 0.65, 0.344, 0.1, 1.42], + albedo=[0.3, 0.7, 0.8, 1.3, 2.5, 4.0] + [0.2]*6, + ) + """ + kwargs = { + 'surface_tilt': 0, + 'relative_airmass': 1.4899535986910446, + 'apparent_zenith': 47.912086486816406, + 'aoi': 47.91208648681641, + 'ground_albedo': 0.2, + 'surface_pressure': 101300, + 'ozone': 0.344, + 'precipitable_water': 1.42, + 'aerosol_turbidity_500nm': 0.1, + 'dayofyear': 75 + } + df = pd.read_csv(SPECTRL2_TEST_DATA) + return kwargs, df + + +def test_spectrl2(spectrl2_data): + # compare against output from solar_utils wrapper around NREL spectrl2_2.c + kwargs, expected = spectrl2_data + actual = spectrum.spectrl2(**kwargs) + assert_allclose(expected['wavelength'].values, actual['wavelength']) + assert_allclose(expected['specdif'].values, actual['dhi'].ravel(), + atol=7e-5) + assert_allclose(expected['specdir'].values, actual['dni'].ravel(), + atol=1.5e-4) + assert_allclose(expected['specetr'], actual['et'].ravel(), + atol=2e-4) + assert_allclose(expected['specglo'], actual['poa_global'].ravel(), + atol=1e-4) + + +def test_spectrl2_multiple(spectrl2_data): + # test that supplying arrays instead of scalars works + kwargs, expected = spectrl2_data + kwargs = {k: np.array([v, v, v]) for k, v in kwargs.items()} + actual = spectrum.spectrl2(**kwargs) + + assert actual['wavelength'].shape == (122,) + + keys = ['et', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse', + 'poa_direct', 'poa_global'] + for key in keys: + assert actual[key].shape == (122, 3) From 5b34c3a3a284f868f995a19aa0095f091528e502 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:05:07 -0600 Subject: [PATCH 05/26] add gallery example --- docs/examples/plot_spectrl2_fig51A.py | 105 ++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 docs/examples/plot_spectrl2_fig51A.py diff --git a/docs/examples/plot_spectrl2_fig51A.py b/docs/examples/plot_spectrl2_fig51A.py new file mode 100644 index 0000000000..010c255951 --- /dev/null +++ b/docs/examples/plot_spectrl2_fig51A.py @@ -0,0 +1,105 @@ +""" +Modeling Spectral Irradiance +============================ + +Recreating Figure 5-1A from the SPECTRL2 NREL Technical Report. +""" + +# %% +# This example shows how to model the spectral distribution of irradiance +# based on atmospheric conditions. The spectral distribution of irradiance is +# the power content at each wavelength band in the solar spectrum and is +# affected by various scattering and absorption mechanisms in the atmosphere. +# This example recreates an example figure from the SPECTRL2 NREL Technical +# Report [1]_. The figure shows modeled spectra at hourly intervals across +# a single morning. +# +# References +# ---------- +# .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for +# direct and diffuse irradiance on horizontal and tilted planes at the +# earth's surface for cloudless atmospheres", NREL Technical Report +# TR-215-2436 doi:10.2172/5986936. + +# %% +# The SPECTRL2 model has several inputs; some can be calculated with pvlib, +# but other must come from a weather dataset. In this case, these weather +# parameters are example assumptions taken from the technical report. + +from pvlib import spectrum, solarposition, irradiance, atmosphere +import pandas as pd +import matplotlib.pyplot as plt + +# assumptions from the technical report: +lat = 37 +lon = -100 +tilt = 37 +azimuth = 180 +pressure = 101300 # sea level, roughly +water_vapor_content = 0.5 # cm +tau500 = 0.1 +ozone = 0.31 # atm-cm +albedo = 0.2 + +times = pd.date_range('1984-03-20 06:17', freq='h', periods=6, tz='Etc/GMT+7') +solpos = solarposition.get_solarposition(times, lat, lon) +aoi = irradiance.aoi(tilt, azimuth, solpos.apparent_zenith, solpos.azimuth) + +# The technical report uses the 'kasten1966' airmass model, but later +# versions of SPECTRL2 use 'kastenyoung1989'. Here we use 'kasten1966' +# for consistency with the technical report. +relative_airmass = atmosphere.get_relative_airmass(solpos.apparent_zenith, + model='kasten1966') + +# %% +# With all the necessary inputs in hand we can model spectral irradiance using +# :py:func:`pvlib.spectrum.spectrl2`. Note that because we are calculating +# the spectra for more than one set of conditions, we will get back 2-D +# arrays (one dimension for wavelength, one for time). + +spectra = spectrum.spectrl2( + surface_tilt=tilt, + apparent_zenith=solpos.apparent_zenith.values, + aoi=aoi.values, + ground_albedo=albedo, + surface_pressure=pressure, + relative_airmass=relative_airmass.values, + precipitable_water=water_vapor_content, + ozone=ozone, + dayofyear=times.dayofyear.values, + aerosol_turbidity_500nm=tau500, +) + +# %% +# The SPECTRL2 model returns spectral irradiance for several irradiance +# components at 122 wavelength bands from 0.3 to 4 microns: + +print(spectra.keys()) +print(spectra['wavelength'].ravel()) + +# %% +# The ``poa_global`` array represents the total spectral irradiance on our +# hypothetical solar panel. Let's plot it against wavelength to recreate +# Figure 5-1A: + +plt.figure() +plt.plot(spectra['wavelength'], spectra['poa_global']) +plt.xlim(0.2, 2.7) +plt.ylim(0, 1800) +plt.title(r"Day 80 1984, $\tau=0.1$, Wv=0.5 cm") +plt.ylabel(r"Irradiance ($W m^{-2} \mu m^{-1}$)") +plt.xlabel(r"Wavelength ($\mu m$)") +time_labels = times.strftime("%H:%M %p") +labels = [ + "AM {:0.02f}, Z{:0.02f}, {}".format(*vals) + for vals in zip(relative_airmass, solpos.apparent_zenith, time_labels) +] +plt.legend(labels) +plt.show() + +# %% +# Note that the airmass and zenith values do not exactly match the values in +# the technical report; this is because airmass is estimated from solar +# position and the solar position calculation in the technical report does not +# exactly match the one used here. However, the differences are minor enough +# to not materially change the spectra. From 0d5655cbea41d873b2443a218f732fee7e73d8df Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:06:24 -0600 Subject: [PATCH 06/26] stickler --- pvlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index 09a18d7069..abaf108e21 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -16,4 +16,4 @@ from pvlib import bifacial # noqa: F401 from pvlib import soiling # noqa: F401 from pvlib import snow # noqa: F401 -from pvlib import spectrum #noqa: F401 \ No newline at end of file +from pvlib import spectrum # noqa: F401 \ No newline at end of file From 10793e7134d971607f25add02c9869066d186dc3 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:08:59 -0600 Subject: [PATCH 07/26] whatsnew --- docs/sphinx/source/whatsnew/v0.8.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.8.1.rst b/docs/sphinx/source/whatsnew/v0.8.1.rst index 0f4f6576d7..7db479b3fb 100644 --- a/docs/sphinx/source/whatsnew/v0.8.1.rst +++ b/docs/sphinx/source/whatsnew/v0.8.1.rst @@ -14,7 +14,7 @@ Deprecations Enhancements ~~~~~~~~~~~~ * Add a numpy-based implementation of the SPECTRL2 spectral irradiance model. - (:pull:`XX`) + (:pull:`1062`) Bug fixes ~~~~~~~~~ From f32663b44ee6cf15ea72b7108e80c7dfd63d25c0 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:12:08 -0600 Subject: [PATCH 08/26] more whatsnew --- docs/sphinx/source/whatsnew/v0.8.1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.8.1.rst b/docs/sphinx/source/whatsnew/v0.8.1.rst index 7db479b3fb..009b051379 100644 --- a/docs/sphinx/source/whatsnew/v0.8.1.rst +++ b/docs/sphinx/source/whatsnew/v0.8.1.rst @@ -13,8 +13,8 @@ Deprecations Enhancements ~~~~~~~~~~~~ -* Add a numpy-based implementation of the SPECTRL2 spectral irradiance model. - (:pull:`1062`) +* Add a numpy-based implementation of the SPECTRL2 spectral irradiance model + :py:func:`pvlib.spectrum.spectrl2` (:pull:`1062`) Bug fixes ~~~~~~~~~ From 83a584e87195686c9c81933e3dc3b97e429b96ba Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sun, 20 Sep 2020 15:17:36 -0600 Subject: [PATCH 09/26] here's your newline stickler --- pvlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index abaf108e21..62f753b5ae 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -16,4 +16,4 @@ from pvlib import bifacial # noqa: F401 from pvlib import soiling # noqa: F401 from pvlib import snow # noqa: F401 -from pvlib import spectrum # noqa: F401 \ No newline at end of file +from pvlib import spectrum # noqa: F401 From c0eaaa82a0395a63f462f1ddc13fa42fb02bdff3 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 9 Dec 2020 20:56:43 -0700 Subject: [PATCH 10/26] merge typo --- pvlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index 2ced1e0cab..ff6b375017 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -20,7 +20,7 @@ soiling, solarposition, spa, - spectrum + spectrum, temperature, tools, tracking, From 18d98b54f976ea96bd7bb0f26d9201611dba5cbf Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 11 Dec 2020 18:10:47 -0700 Subject: [PATCH 11/26] et -> dni_extra --- pvlib/spectrum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index ead872a3a6..f1072402b3 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -210,7 +210,7 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, A dict of arrays of length 122. All values are spectral irradiance with units W/m^2/um except for `wavelength`, which is in microns. * wavelength - * et + * dni_extra * dhi * dni * poa_sky_diffuse @@ -308,7 +308,7 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, return { 'wavelength': wavelength.ravel(), # This only ever needs 1 dimension - 'et': spectrum_et_adj, + 'dni_extra': spectrum_et_adj, 'dhi': Is, 'dni': Id, 'poa_sky_diffuse': Isky, From 5db6bd86978cda693411fc43d07b850a566a0e09 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 11 Dec 2020 18:22:24 -0700 Subject: [PATCH 12/26] use spencer for earth-sun distance; add airmass comment --- pvlib/spectrum.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index f1072402b3..061714a646 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -91,17 +91,15 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, ozone_coeff = _SPECTRL2_COEFFS['ozone_absorption'][:, np.newaxis] mixed_coeff = _SPECTRL2_COEFFS['mixed_absorption'][:, np.newaxis] - # ET spectral irradiance correction for earth-sun distance seasonality - day_angle = 2 * np.pi * (dayofyear - 1) / 365.0 # Eq 2-3 - earth_sun_distance_correction = ( - 1.000110 - + 0.034221 * np.cos(day_angle) - + 0.001280 * np.sin(day_angle) - + 0.000719 * np.cos(2*day_angle) - + 0.000077 * np.sin(2*day_angle) - ) # Eq 2-2 - + # ET spectral irradiance correction for earth-sun distance seasonality. + # Note that we only want the distance correction coefficient, so set + # solar_constant=1: + earth_sun_distance_correction = \ + pvlib.irradiance.get_extra_radiation(method='spencer', + solar_constant=1) # Eq 2-2, 2-3 # Rayleigh scattering + # note: 101300 is used for consistentcy with reference; can't use + # atmosphere.get_absolute_airmass because it uses 101325 airmass = relative_airmass * surface_pressure / 101300 rayleigh_transmittance = np.exp( # Note: the report uses 1.335 but spectrl2_2.c uses 1.3366 From 55054058067e616f5423f49cb4d900654537b705 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 11 Dec 2020 18:31:24 -0700 Subject: [PATCH 13/26] bugfix --- pvlib/spectrum.py | 2 +- pvlib/tests/test_spectrum.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 061714a646..69c8f8bea5 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -95,7 +95,7 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, # Note that we only want the distance correction coefficient, so set # solar_constant=1: earth_sun_distance_correction = \ - pvlib.irradiance.get_extra_radiation(method='spencer', + pvlib.irradiance.get_extra_radiation(dayofyear, method='spencer', solar_constant=1) # Eq 2-2, 2-3 # Rayleigh scattering # note: 101300 is used for consistentcy with reference; can't use diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 7808ba3197..7c24519cc8 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -47,7 +47,7 @@ def test_spectrl2(spectrl2_data): atol=7e-5) assert_allclose(expected['specdir'].values, actual['dni'].ravel(), atol=1.5e-4) - assert_allclose(expected['specetr'], actual['et'].ravel(), + assert_allclose(expected['specetr'], actual['dni_extra'].ravel(), atol=2e-4) assert_allclose(expected['specglo'], actual['poa_global'].ravel(), atol=1e-4) @@ -61,7 +61,7 @@ def test_spectrl2_multiple(spectrl2_data): assert actual['wavelength'].shape == (122,) - keys = ['et', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse', + keys = ['dni_extra', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse', 'poa_direct', 'poa_global'] for key in keys: assert actual[key].shape == (122, 3) From a07866bab1f8d2d200409198aea650a928dbfc0c Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 14 Dec 2020 20:19:44 -0700 Subject: [PATCH 14/26] um to nm --- docs/examples/plot_spectrl2_fig51A.py | 15 ++--- pvlib/spectrum.py | 83 ++++++++++++++------------- pvlib/tests/test_spectrum.py | 3 + 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/docs/examples/plot_spectrl2_fig51A.py b/docs/examples/plot_spectrl2_fig51A.py index 010c255951..eb4e0491b4 100644 --- a/docs/examples/plot_spectrl2_fig51A.py +++ b/docs/examples/plot_spectrl2_fig51A.py @@ -70,13 +70,6 @@ aerosol_turbidity_500nm=tau500, ) -# %% -# The SPECTRL2 model returns spectral irradiance for several irradiance -# components at 122 wavelength bands from 0.3 to 4 microns: - -print(spectra.keys()) -print(spectra['wavelength'].ravel()) - # %% # The ``poa_global`` array represents the total spectral irradiance on our # hypothetical solar panel. Let's plot it against wavelength to recreate @@ -84,11 +77,11 @@ plt.figure() plt.plot(spectra['wavelength'], spectra['poa_global']) -plt.xlim(0.2, 2.7) -plt.ylim(0, 1800) +plt.xlim(200, 2700) +plt.ylim(0, 1.8) plt.title(r"Day 80 1984, $\tau=0.1$, Wv=0.5 cm") -plt.ylabel(r"Irradiance ($W m^{-2} \mu m^{-1}$)") -plt.xlabel(r"Wavelength ($\mu m$)") +plt.ylabel(r"Irradiance ($W m^{-2} nm^{-1}$)") +plt.xlabel(r"Wavelength ($nm$)") time_labels = times.strftime("%H:%M %p") labels = [ "AM {:0.02f}, Z{:0.02f}, {}".format(*vals) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 69c8f8bea5..c33c80864f 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -15,33 +15,36 @@ ('ozone_absorption', 'float64'), ('mixed_absorption', 'float64'), ])) -_SPECTRL2_COEFFS['wavelength'] = [ # um - 0.3, 0.305, 0.31, 0.315, 0.32, 0.325, 0.33, 0.335, 0.34, 0.345, 0.35, - 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, - 0.48, 0.49, 0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.57, 0.593, 0.61, 0.63, - 0.656, 0.6676, 0.69, 0.71, 0.718, 0.7244, 0.74, 0.7525, 0.7575, 0.7625, - 0.7675, 0.78, 0.8, 0.816, 0.8237, 0.8315, 0.84, 0.86, 0.88, 0.905, 0.915, - 0.925, 0.93, 0.937, 0.948, 0.965, 0.98, 0.9935, 1.04, 1.07, 1.1, 1.12, - 1.13, 1.145, 1.161, 1.17, 1.2, 1.24, 1.27, 1.29, 1.32, 1.35, 1.395, 1.4425, - 1.4625, 1.477, 1.497, 1.52, 1.539, 1.558, 1.578, 1.592, 1.61, 1.63, 1.646, - 1.678, 1.74, 1.8, 1.86, 1.92, 1.96, 1.985, 2.005, 2.035, 2.065, 2.1, 2.148, - 2.198, 2.27, 2.36, 2.45, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, - 3.5, 3.6, 3.7, 3.8, 3.9, 4.0 +_SPECTRL2_COEFFS['wavelength'] = [ # nm + 300.0, 305.0, 310.0, 315.0, 320.0, 325.0, 330.0, 335.0, 340.0, 345.0, + 350.0, 360.0, 370.0, 380.0, 390.0, 400.0, 410.0, 420.0, 430.0, 440.0, + 450.0, 460.0, 470.0, 480.0, 490.0, 500.0, 510.0, 520.0, 530.0, 540.0, + 550.0, 570.0, 593.0, 610.0, 630.0, 656.0, 667.6, 690.0, 710.0, 718.0, + 724.4, 740.0, 752.5, 757.5, 762.5, 767.5, 780.0, 800.0, 816.0, 823.7, + 831.5, 840.0, 860.0, 880.0, 905.0, 915.0, 925.0, 930.0, 937.0, 948.0, + 965.0, 980.0, 993.5, 1040.0, 1070.0, 1100.0, 1120.0, 1130.0, 1145.0, + 1161.0, 1170.0, 1200.0, 1240.0, 1270.0, 1290.0, 1320.0, 1350.0, 1395.0, + 1442.5, 1462.5, 1477.0, 1497.0, 1520.0, 1539.0, 1558.0, 1578.0, 1592.0, + 1610.0, 1630.0, 1646.0, 1678.0, 1740.0, 1800.0, 1860.0, 1920.0, 1960.0, + 1985.0, 2005.0, 2035.0, 2065.0, 2100.0, 2148.0, 2198.0, 2270.0, 2360.0, + 2450.0, 2500.0, 2600.0, 2700.0, 2800.0, 2900.0, 3000.0, 3100.0, 3200.0, + 3300.0, 3400.0, 3500.0, 3600.0, 3700.0, 3800.0, 3900.0, 4000.0 ] -_SPECTRL2_COEFFS['spectral_irradiance_et'] = [ # W/m^2/um - 535.9, 558.3, 622.0, 692.7, 715.1, 832.9, 961.9, 931.9, 900.6, 911.3, - 975.5, 975.9, 1119.9, 1103.8, 1033.8, 1479.1, 1701.3, 1740.4, 1587.2, - 1837.0, 2005.0, 2043.0, 1987.0, 2027.0, 1896.0, 1909.0, 1927.0, 1831.0, - 1891.0, 1898.0, 1892.0, 1840.0, 1768.0, 1728.0, 1658.0, 1524.0, 1531.0, - 1420.0, 1399.0, 1374.0, 1373.0, 1298.0, 1269.0, 1245.0, 1223.0, 1205.0, - 1183.0, 1148.0, 1091.0, 1062.0, 1038.0, 1022.0, 998.7, 947.2, 893.2, 868.2, - 829.7, 830.3, 814.0, 786.9, 768.3, 767.0, 757.6, 688.1, 640.7, 606.2, - 585.9, 570.2, 564.1, 544.2, 533.4, 501.6, 477.5, 442.7, 440.0, 416.8, - 391.4, 358.9, 327.5, 317.5, 307.3, 300.4, 292.8, 275.5, 272.1, 259.3, - 246.9, 244.0, 243.5, 234.8, 220.5, 190.8, 171.1, 144.5, 135.7, 123.0, - 123.8, 113.0, 108.5, 97.5, 92.4, 82.4, 74.6, 68.3, 63.8, 49.5, 48.5, 38.6, - 36.6, 32.0, 28.1, 24.8, 22.1, 19.6, 17.5, 15.7, 14.1, 12.7, 11.5, 10.4, - 9.5, 8.6 +_SPECTRL2_COEFFS['spectral_irradiance_et'] = [ # W/m^2/nm + 0.5359, 0.5583, 0.622, 0.6927, 0.7151, 0.8329, 0.9619, 0.9319, 0.9006, + 0.9113, 0.9755, 0.9759, 1.1199, 1.1038, 1.0338, 1.4791, 1.7013, 1.7404, + 1.5872, 1.837, 2.005, 2.043, 1.987, 2.027, 1.896, 1.909, 1.927, 1.831, + 1.891, 1.898, 1.892, 1.84, 1.768, 1.728, 1.658, 1.524, 1.531, 1.42, + 1.399, 1.374, 1.373, 1.298, 1.269, 1.245, 1.223, 1.205, 1.183, 1.148, + 1.091, 1.062, 1.038, 1.022, 0.9987, 0.9472, 0.8932, 0.8682, 0.8297, + 0.8303, 0.814, 0.7869, 0.7683, 0.767, 0.7576, 0.6881, 0.6407, 0.6062, + 0.5859, 0.5702, 0.5641, 0.5442, 0.5334, 0.5016, 0.4775, 0.4427, 0.44, + 0.4168, 0.3914, 0.3589, 0.3275, 0.3175, 0.3073, 0.3004, 0.2928, 0.2755, + 0.2721, 0.2593, 0.2469, 0.244, 0.2435, 0.2348, 0.2205, 0.1908, 0.1711, + 0.1445, 0.1357, 0.123, 0.1238, 0.113, 0.1085, 0.0975, 0.0924, 0.0824, + 0.0746, 0.0683, 0.0638, 0.0495, 0.0485, 0.0386, 0.0366, 0.032, 0.0281, + 0.0248, 0.0221, 0.0196, 0.0175, 0.0157, 0.0141, 0.0127, 0.0115, 0.0104, + 0.0095, 0.0086 ] _SPECTRL2_COEFFS['water_vapor_absorption'] = [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, @@ -96,15 +99,16 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, # solar_constant=1: earth_sun_distance_correction = \ pvlib.irradiance.get_extra_radiation(dayofyear, method='spencer', - solar_constant=1) # Eq 2-2, 2-3 + solar_constant=1) # Eq 2-2, 2-3 # Rayleigh scattering # note: 101300 is used for consistentcy with reference; can't use # atmosphere.get_absolute_airmass because it uses 101325 airmass = relative_airmass * surface_pressure / 101300 + wavelength_um = wavelength / 1000 rayleigh_transmittance = np.exp( # Note: the report uses 1.335 but spectrl2_2.c uses 1.3366 - # -airmass / (wavelength**4 * (115.6406 - 1.335 / wavelength**2)) - -airmass / (wavelength**4 * (115.6406 - 1.3366 / wavelength**2)) + # -airmass / (wavelength_um**4 * (115.6406 - 1.335 / wavelength_um**2)) + -airmass / (wavelength_um**4 * (115.6406 - 1.3366 / wavelength_um**2)) ) # Eq 2-4 # Aerosol scattering and absorption, Eq 2-6 @@ -129,8 +133,8 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, mixed_transmittance = np.exp(-1.41 * aM / (1 + 118.3 * aM)**0.45) # split out aerosol components for diffuse irradiance calcs - aerosol_scattering = ( - np.exp(-scattering_albedo * optical_thickness * relative_airmass) + aerosol_scattering = np.exp( + -scattering_albedo * optical_thickness * relative_airmass ) # Eq 3-9 aerosol_absorption = np.exp( @@ -159,10 +163,10 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, (SPECTRL2). The Bird Simple Spectral Model [1]_ produces terrestrial spectra between - 0.3 and 4 um with a resolution of approximately 10 nm. Direct and diffuse - spectral irradiance are modeled for horizontal and tilted surfaces under - cloudless skies. SPECTRL2 models radiative transmission, absorption, and - scattering due to atmospheric aerosol, water vapor, and ozone content. + 300 and 4000 nm with a resolution of approximately 10 nm. Direct and + diffuse spectral irradiance are modeled for horizontal and tilted surfaces + under cloudless skies. SPECTRL2 models radiative transmission, absorption, + and scattering due to atmospheric aerosol, water vapor, and ozone content. Parameters ---------- @@ -206,7 +210,7 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, ------- spectra : dict A dict of arrays of length 122. All values are spectral irradiance - with units W/m^2/um except for `wavelength`, which is in microns. + with units W/m^2/nm except for `wavelength`, which is in nanometers. * wavelength * dni_extra * dhi @@ -227,12 +231,12 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] optical_thickness = ( - aerosol_turbidity_500nm * (wavelength / 0.5)**-alpha + aerosol_turbidity_500nm * (wavelength / 500)**-alpha ) # Eq 2-7 # Eq 3-16 scattering_albedo = scattering_albedo_400nm * \ - np.exp(-wavelength_variation_factor * np.log(wavelength / 0.4)**2) + np.exp(-wavelength_variation_factor * np.log(wavelength / 400)**2) spectrl2 = _spectrl2_transmittances(apparent_zenith, relative_airmass, surface_pressure, precipitable_water, @@ -245,7 +249,8 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, Id = spectrum_et_adj * Tr * Ta * Tw * To * Tu cosZ = cosd(apparent_zenith) - Cs = np.where(wavelength <= 0.45, (wavelength + 0.55)**1.8, 1.0) # Eq 3-17 + # Eq 3-17 + Cs = np.where(wavelength <= 450, ((wavelength + 550)/1000)**1.8, 1.0) ALG = np.log(1 - aerosol_asymmetry_factor) # Eq 3-14 BFS = ALG * (0.0783 + ALG * (-0.3824 - ALG * 0.5874)) # Eq 3-13 AFS = ALG * (1.459 + ALG * (0.1595 + ALG * 0.4129)) # Eq 3-12 diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 7c24519cc8..9ccbe9bbc7 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -35,6 +35,9 @@ def spectrl2_data(): 'dayofyear': 75 } df = pd.read_csv(SPECTRL2_TEST_DATA) + # convert um to nm + df['wavelength'] *= 1000 + df[['specdif', 'specdir', 'specetr', 'specglo']] /= 1000 return kwargs, df From 415f636e5877005c8fed50cdf9c94626b31320b4 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 14 Dec 2020 20:59:59 -0700 Subject: [PATCH 15/26] add docstring notes --- pvlib/spectrum.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index c33c80864f..11c0017e11 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -220,12 +220,32 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, * poa_direct * poa_global + Notes + ----- + NREL's C implementation ``spectrl2_2.c`` [2]_ of the model differs in + several ways from the original report [1]_. The report itself also has + a few differences between the in-text equations and the code appendix. + The list of known differences is shown below. Note that this + implementation follows ``spectrl2_2.c``. + + =================== ========== ========== =============== + Equation Report Appendix spectrl2_2.c + =================== ========== ========== =============== + 2-4 1.335 1.335 1.3366 + 2-11 118.93 118.93 118.3 + 3-8 To' Tu' Tu' + 3-5, 3-6, 3-7, 3-1 double Cs single Cs sigle Cs + 2-5 kasten1966 kasten1966 kastenyoung1989 + =================== ========== ========== =============== + References ---------- .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for direct and diffuse irradiance on horizontal and tilted planes at the earth's surface for cloudless atmospheres", NREL Technical Report TR-215-2436 doi:10.2172/5986936. + .. [2] Bird Simple Spectral Model: spectrl2_2.c. + https://www.nrel.gov/grid/solar-resource/spectral.html """ wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] @@ -308,9 +328,9 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, Iground = pvlib.irradiance.get_ground_diffuse(surface_tilt, ghi, albedo=rg) Itilt = Ibeam + Isky + Iground - + wavelength_1d = wavelength.ravel() # return value only needs 1 dimension return { - 'wavelength': wavelength.ravel(), # This only ever needs 1 dimension + 'wavelength': wavelength_1d, 'dni_extra': spectrum_et_adj, 'dhi': Is, 'dni': Id, From c2709b2bf1617dade26d7ccb6f0296abfc42d5fd Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Dec 2020 16:44:24 -0700 Subject: [PATCH 16/26] reorder kwargs --- docs/examples/plot_spectrl2_fig51A.py | 2 +- pvlib/spectrum.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples/plot_spectrl2_fig51A.py b/docs/examples/plot_spectrl2_fig51A.py index eb4e0491b4..085f26390b 100644 --- a/docs/examples/plot_spectrl2_fig51A.py +++ b/docs/examples/plot_spectrl2_fig51A.py @@ -58,9 +58,9 @@ # arrays (one dimension for wavelength, one for time). spectra = spectrum.spectrl2( - surface_tilt=tilt, apparent_zenith=solpos.apparent_zenith.values, aoi=aoi.values, + surface_tilt=tilt, ground_albedo=albedo, surface_pressure=pressure, relative_airmass=relative_airmass.values, diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 11c0017e11..913e57a20e 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -153,7 +153,7 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, ) -def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, +def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, surface_pressure, relative_airmass, precipitable_water, ozone, dayofyear, aerosol_turbidity_500nm, scattering_albedo_400nm=0.945, alpha=1.14, wavelength_variation_factor=0.095, @@ -170,12 +170,12 @@ def spectrl2(surface_tilt, apparent_zenith, aoi, ground_albedo, Parameters ---------- - surface_tilt : float or numpy array - Panel tilt from horizontal [degrees] apparent_zenith : float or numpy array Solar zenith angle [degrees] aoi : float or numpy array Angle of incidence of the solar vector on the panel [degrees] + surface_tilt : float or numpy array + Panel tilt from horizontal [degrees] ground_albedo : float or numpy array Albedo [0-1] of the ground surface. Can be provided as a scalar value if albedo is not spectrally-dependent, or as a 122xN matrix where From 01f695641060ccdd65e6a4e4034ef5ca5489f90f Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Dec 2020 21:02:55 -0700 Subject: [PATCH 17/26] allow Series inputs; infer dayofyear if not provided --- docs/examples/plot_spectrl2_fig51A.py | 7 ++-- pvlib/spectrum.py | 54 ++++++++++++++++++--------- pvlib/tests/test_spectrum.py | 18 ++++++++- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/docs/examples/plot_spectrl2_fig51A.py b/docs/examples/plot_spectrl2_fig51A.py index 085f26390b..6f3470a246 100644 --- a/docs/examples/plot_spectrl2_fig51A.py +++ b/docs/examples/plot_spectrl2_fig51A.py @@ -58,15 +58,14 @@ # arrays (one dimension for wavelength, one for time). spectra = spectrum.spectrl2( - apparent_zenith=solpos.apparent_zenith.values, - aoi=aoi.values, + apparent_zenith=solpos.apparent_zenith, + aoi=aoi, surface_tilt=tilt, ground_albedo=albedo, surface_pressure=pressure, - relative_airmass=relative_airmass.values, + relative_airmass=relative_airmass, precipitable_water=water_vapor_content, ozone=ozone, - dayofyear=times.dayofyear.values, aerosol_turbidity_500nm=tau500, ) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 913e57a20e..bd96bee92b 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -6,6 +6,7 @@ import pvlib from pvlib.tools import cosd import numpy as np +import pandas as pd # SPECTRL2 extraterrestrial spectrum and atmospheric absorption coefficients _SPECTRL2_COEFFS = np.zeros(122, dtype=np.dtype([ @@ -155,9 +156,9 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, surface_pressure, relative_airmass, precipitable_water, ozone, - dayofyear, aerosol_turbidity_500nm, scattering_albedo_400nm=0.945, - alpha=1.14, wavelength_variation_factor=0.095, - aerosol_asymmetry_factor=0.65): + aerosol_turbidity_500nm, dayofyear=None, + scattering_albedo_400nm=0.945, alpha=1.14, + wavelength_variation_factor=0.095, aerosol_asymmetry_factor=0.65): """ Estimate spectral irradiance using the Bird Simple Spectral Model (SPECTRL2). @@ -170,40 +171,41 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, Parameters ---------- - apparent_zenith : float or numpy array + apparent_zenith : numeric Solar zenith angle [degrees] - aoi : float or numpy array + aoi : numeric Angle of incidence of the solar vector on the panel [degrees] - surface_tilt : float or numpy array + surface_tilt : numeric Panel tilt from horizontal [degrees] - ground_albedo : float or numpy array + ground_albedo : numeric Albedo [0-1] of the ground surface. Can be provided as a scalar value if albedo is not spectrally-dependent, or as a 122xN matrix where the first dimension spans the wavelength range and the second spans the number of simulations. [unitless] - surface_pressure : float or numpy array + surface_pressure : numeric Surface pressure [Pa] - relative_airmass : float or numpy array + relative_airmass : numeric Relative airmass. The airmass model used in [1]_ is the `'kasten1966'` model, while a later implementation by NREL uses the `'kastenyoung1989'` model. [unitless] - precipitable_water : float or numpy array + precipitable_water : numeric Atmospheric water vapor content [cm] - ozone : float or numpy array + ozone : numeric Atmospheric ozone content [atm-cm] - dayofyear : float or numpy array - The day of year [1-365] - aerosol_turbidity_500nm : float or numpy array + aerosol_turbidity_500nm : numeric Aerosol turbidity at 500 nm [unitless] - scattering_albedo_400nm : float or numpy array, default 0.945 + dayofyear : numeric, optional + The day of year [1-365]. Must be provided if ``apparent_zenith`` is + not a pandas Series. + scattering_albedo_400nm : numeric, default 0.945 Aerosol single scattering albedo at 400nm. The default value of 0.945 is suggested in [1]_ for a rural aerosol model. [unitless] - alpha : float or numpy array, default 1.14 + alpha : numeric, default 1.14 Angstrom turbidity exponent. The default value of 1.14 is suggested in [1]_ for a rural aerosol model. [unitless] - wavelength_variation_factor : float or numpy array, default 0.095 + wavelength_variation_factor : numeric, default 0.095 Wavelength variation factor [unitless] - aerosol_asymmetry_factor : float or numpy array, default 0.65 + aerosol_asymmetry_factor : numeric, default 0.65 Aeorosol asymmetry factor (mean cosine of scattering angle) [unitless] Returns @@ -247,6 +249,22 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, .. [2] Bird Simple Spectral Model: spectrl2_2.c. https://www.nrel.gov/grid/solar-resource/spectral.html """ + # values need to be np arrays for broadcasting, so unwrap Series if needed: + is_pandas = isinstance(apparent_zenith, pd.Series) + if is_pandas: + original_index = apparent_zenith.index + (apparent_zenith, aoi, surface_tilt, ground_albedo, surface_pressure, + relative_airmass, precipitable_water, ozone, aerosol_turbidity_500nm, + scattering_albedo_400nm, alpha, wavelength_variation_factor, + aerosol_asymmetry_factor) = \ + tuple(map(np.array, [ + apparent_zenith, aoi, surface_tilt, ground_albedo, + surface_pressure, relative_airmass, precipitable_water, ozone, + aerosol_turbidity_500nm, scattering_albedo_400nm, alpha, + wavelength_variation_factor, aerosol_asymmetry_factor])) + + dayofyear = original_index.dayofyear.values + wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 9ccbe9bbc7..d38e0ed381 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -56,7 +56,7 @@ def test_spectrl2(spectrl2_data): atol=1e-4) -def test_spectrl2_multiple(spectrl2_data): +def test_spectrl2_array(spectrl2_data): # test that supplying arrays instead of scalars works kwargs, expected = spectrl2_data kwargs = {k: np.array([v, v, v]) for k, v in kwargs.items()} @@ -68,3 +68,19 @@ def test_spectrl2_multiple(spectrl2_data): 'poa_direct', 'poa_global'] for key in keys: assert actual[key].shape == (122, 3) + + +def test_spectrl2_series(spectrl2_data): + # test that supplying Series instead of scalars works + kwargs, expected = spectrl2_data + kwargs.pop('dayofyear') + index = pd.to_datetime(['2020-03-15 10:45:59']*3) + kwargs = {k: pd.Series([v, v, v], index=index) for k, v in kwargs.items()} + actual = spectrum.spectrl2(**kwargs) + + assert actual['wavelength'].shape == (122,) + + keys = ['dni_extra', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse', + 'poa_direct', 'poa_global'] + for key in keys: + assert actual[key].shape == (122, 3) From b1d8ca906b04a5e9061709d015142a52a1cf153c Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Dec 2020 21:03:56 -0700 Subject: [PATCH 18/26] stickler --- pvlib/spectrum.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index bd96bee92b..799b3eadc3 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -258,10 +258,10 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, scattering_albedo_400nm, alpha, wavelength_variation_factor, aerosol_asymmetry_factor) = \ tuple(map(np.array, [ - apparent_zenith, aoi, surface_tilt, ground_albedo, - surface_pressure, relative_airmass, precipitable_water, ozone, - aerosol_turbidity_500nm, scattering_albedo_400nm, alpha, - wavelength_variation_factor, aerosol_asymmetry_factor])) + apparent_zenith, aoi, surface_tilt, ground_albedo, + surface_pressure, relative_airmass, precipitable_water, ozone, + aerosol_turbidity_500nm, scattering_albedo_400nm, alpha, + wavelength_variation_factor, aerosol_asymmetry_factor])) dayofyear = original_index.dayofyear.values From ff3b9812aa8960ab2e277082ba8e98453a3a1415 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Dec 2020 21:08:56 -0700 Subject: [PATCH 19/26] raise error if not pandas and dayofyear not specified --- pvlib/spectrum.py | 4 ++++ pvlib/tests/test_spectrum.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 799b3eadc3..6963e75a53 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -265,6 +265,10 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, dayofyear = original_index.dayofyear.values + if not is_pandas and dayofyear is None: + raise ValueError('dayofyear must be specified if not using pandas ' + 'Series inputs') + wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index d38e0ed381..a28ceca06c 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -84,3 +84,11 @@ def test_spectrl2_series(spectrl2_data): 'poa_direct', 'poa_global'] for key in keys: assert actual[key].shape == (122, 3) + + +def test_dayofyear_missing(spectrl2_data): + # test that not specifying dayofyear with non-pandas inputs raises error + kwargs, expected = spectrl2_data + kwargs.pop('dayofyear') + with pytest.raises(ValueError, match='dayofyear must be specified'): + _ = spectrum.spectrl2(**kwargs) From 06914cfb1873a1dc56f3e1457a5824eedc6a3dbc Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 16 Dec 2020 09:18:28 -0700 Subject: [PATCH 20/26] clarify shape of return arrays --- pvlib/spectrum.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 6963e75a53..90988b552a 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -211,7 +211,9 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, Returns ------- spectra : dict - A dict of arrays of length 122. All values are spectral irradiance + A dict of arrays. With the exception of `wavelength`, which has length + 122, each array has shape (122, N) where N is the length of the + input ``apparent_zenith``. All values are spectral irradiance with units W/m^2/nm except for `wavelength`, which is in nanometers. * wavelength * dni_extra From 53f119951da584947f0feeb8ce73b0a4e0b579c4 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 16 Dec 2020 09:18:43 -0700 Subject: [PATCH 21/26] typo and sphinx warning --- pvlib/spectrum.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/spectrum.py b/pvlib/spectrum.py index 90988b552a..e83fe7a4c4 100644 --- a/pvlib/spectrum.py +++ b/pvlib/spectrum.py @@ -215,6 +215,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, 122, each array has shape (122, N) where N is the length of the input ``apparent_zenith``. All values are spectral irradiance with units W/m^2/nm except for `wavelength`, which is in nanometers. + * wavelength * dni_extra * dhi @@ -238,7 +239,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, 2-4 1.335 1.335 1.3366 2-11 118.93 118.93 118.3 3-8 To' Tu' Tu' - 3-5, 3-6, 3-7, 3-1 double Cs single Cs sigle Cs + 3-5, 3-6, 3-7, 3-1 double Cs single Cs single Cs 2-5 kasten1966 kasten1966 kastenyoung1989 =================== ========== ========== =============== From 10397977d6cb3b2545376bd08dc04d272f30ab90 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 17 Dec 2020 18:44:39 -0700 Subject: [PATCH 22/26] create spectrum folder --- pvlib/spectrum/__init__.py | 1 + pvlib/{spectrum.py => spectrum/spectrl2.py} | 0 2 files changed, 1 insertion(+) create mode 100644 pvlib/spectrum/__init__.py rename pvlib/{spectrum.py => spectrum/spectrl2.py} (100%) diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py new file mode 100644 index 0000000000..36b3503d13 --- /dev/null +++ b/pvlib/spectrum/__init__.py @@ -0,0 +1 @@ +from pvlib.spectrum.spectrl2 import spectrl2 # noqa: F401 diff --git a/pvlib/spectrum.py b/pvlib/spectrum/spectrl2.py similarity index 100% rename from pvlib/spectrum.py rename to pvlib/spectrum/spectrl2.py From 7ef67c444441ea0280a82f94264f430b7c58c01f Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 17 Dec 2020 19:15:29 -0700 Subject: [PATCH 23/26] changes from review --- pvlib/spectrum/spectrl2.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/pvlib/spectrum/spectrl2.py b/pvlib/spectrum/spectrl2.py index e83fe7a4c4..79576fb4a5 100644 --- a/pvlib/spectrum/spectrl2.py +++ b/pvlib/spectrum/spectrl2.py @@ -1,6 +1,5 @@ r""" -The ``spectrum`` module contains functions that implement models for the solar -irradiance spectrum. +The ``spectrl2`` module implements the Bird Simple Spectral Model. """ import pvlib @@ -89,7 +88,25 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, optical_thickness, scattering_albedo, dayofyear): """ Calculate transmittance factors from Section 2 of Bird and Riordan 1984. + + Parameters + ---------- + apparent_zenith, relative_airmass, surface_pressure, precipitable_water, + ozone, dayofyear: float or 1d np.array + One value per timestamp + optical_thickness, scattering_albedo: np.ndarray + Array with shape (122, N) where N is either 1 or len(apparent_zenith) + + Returns + ------- + earth_sun_distance_correction: float or 1d np.array + Same shape/type as apparent_zenith + rayleigh_transmittance, aerosol_transmittance, vapor_transmittance, + ozone_transmittance, mixed_transmittance, aerosol_scattering, + aerosol_absorption: np.ndarray + Array with shape (122, N) where N is len(apparent_zenith) """ + # add a dimension so that each ndarray is 2d with shape (122, 1) wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] vapor_coeff = _SPECTRL2_COEFFS['water_vapor_absorption'][:, np.newaxis] ozone_coeff = _SPECTRL2_COEFFS['ozone_absorption'][:, np.newaxis] @@ -142,6 +159,9 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, -(1 - scattering_albedo) * optical_thickness * relative_airmass ) # Eq 3-10 + import pdb + pdb.set_trace() + return ( earth_sun_distance_correction, rayleigh_transmittance, @@ -206,7 +226,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, wavelength_variation_factor : numeric, default 0.095 Wavelength variation factor [unitless] aerosol_asymmetry_factor : numeric, default 0.65 - Aeorosol asymmetry factor (mean cosine of scattering angle) [unitless] + Aerosol asymmetry factor (mean cosine of scattering angle) [unitless] Returns ------- @@ -260,7 +280,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, relative_airmass, precipitable_water, ozone, aerosol_turbidity_500nm, scattering_albedo_400nm, alpha, wavelength_variation_factor, aerosol_asymmetry_factor) = \ - tuple(map(np.array, [ + tuple(map(np.asanyarray, [ apparent_zenith, aoi, surface_tilt, ground_albedo, surface_pressure, relative_airmass, precipitable_water, ozone, aerosol_turbidity_500nm, scattering_albedo_400nm, alpha, @@ -272,6 +292,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, raise ValueError('dayofyear must be specified if not using pandas ' 'Series inputs') + # add a dimension so that each ndarray is 2d with shape (122, 1) wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] @@ -353,7 +374,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, Iground = pvlib.irradiance.get_ground_diffuse(surface_tilt, ghi, albedo=rg) Itilt = Ibeam + Isky + Iground - wavelength_1d = wavelength.ravel() # return value only needs 1 dimension + wavelength_1d = wavelength.reshape(-1) # only needs 1 dimension return { 'wavelength': wavelength_1d, 'dni_extra': spectrum_et_adj, From a2a840234e94f220de2f0cac632e4d2943924158 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 17 Dec 2020 19:29:02 -0700 Subject: [PATCH 24/26] remove pdb call --- pvlib/spectrum/spectrl2.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pvlib/spectrum/spectrl2.py b/pvlib/spectrum/spectrl2.py index 79576fb4a5..8f9d73b73a 100644 --- a/pvlib/spectrum/spectrl2.py +++ b/pvlib/spectrum/spectrl2.py @@ -159,9 +159,6 @@ def _spectrl2_transmittances(apparent_zenith, relative_airmass, -(1 - scattering_albedo) * optical_thickness * relative_airmass ) # Eq 3-10 - import pdb - pdb.set_trace() - return ( earth_sun_distance_correction, rayleigh_transmittance, From 27406d53bba89fb77e566233120437aa1b10ce5d Mon Sep 17 00:00:00 2001 From: Kevin Anderson <57452607+kanderso-nrel@users.noreply.github.com> Date: Tue, 22 Dec 2020 15:32:45 -0700 Subject: [PATCH 25/26] Update pvlib/spectrum/spectrl2.py Co-authored-by: Cliff Hansen --- pvlib/spectrum/spectrl2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/spectrum/spectrl2.py b/pvlib/spectrum/spectrl2.py index 8f9d73b73a..eb6c08589b 100644 --- a/pvlib/spectrum/spectrl2.py +++ b/pvlib/spectrum/spectrl2.py @@ -340,7 +340,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, common_factor = spectrum_et_adj * cosZ * To * Tu * Tw * Taa # Note: spectrl2_2.c differs from the report in how the Cs value is used. # The two commented out lines match the report, while the following match - # spectrl2_2.c: + # spectrl2_2.c. With regard to Cs, the equations in the report and spectrl12_2.c are algebraically equivalent. # Ir = common_factor * (1 - Tr**0.95) * 0.5 * Cs # Eq 3-5 # Ia = common_factor * Tr**1.5 * (1 - Tas) * Fs * Cs # Eq 3-6 Ir = common_factor * (1 - Tr**0.95) * 0.5 # Eq 3-5 From 9fc9ff4d731bcd08541e58a74a8a12c91566a004 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 22 Dec 2020 15:33:38 -0700 Subject: [PATCH 26/26] changes from review --- pvlib/spectrum/spectrl2.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pvlib/spectrum/spectrl2.py b/pvlib/spectrum/spectrl2.py index eb6c08589b..15f006b288 100644 --- a/pvlib/spectrum/spectrl2.py +++ b/pvlib/spectrum/spectrl2.py @@ -293,9 +293,10 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, wavelength = _SPECTRL2_COEFFS['wavelength'][:, np.newaxis] spectrum_et = _SPECTRL2_COEFFS['spectral_irradiance_et'][:, np.newaxis] - optical_thickness = ( - aerosol_turbidity_500nm * (wavelength / 500)**-alpha - ) # Eq 2-7 + optical_thickness = \ + pvlib.atmosphere.angstrom_aod_at_lambda(aod0=aerosol_turbidity_500nm, + lambda0=500, alpha=alpha, + lambda1=wavelength) # Eq 2-7 # Eq 3-16 scattering_albedo = scattering_albedo_400nm * \ @@ -317,7 +318,6 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, ALG = np.log(1 - aerosol_asymmetry_factor) # Eq 3-14 BFS = ALG * (0.0783 + ALG * (-0.3824 - ALG * 0.5874)) # Eq 3-13 AFS = ALG * (1.459 + ALG * (0.1595 + ALG * 0.4129)) # Eq 3-12 - cosZ = cosd(apparent_zenith) Fs = 1 - 0.5 * np.exp((AFS + BFS * cosZ) * cosZ) # Eq 3-11 Fsp = 1 - 0.5 * np.exp((AFS + BFS / 1.8) / 1.8) # Eq 3.15 @@ -340,7 +340,8 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, common_factor = spectrum_et_adj * cosZ * To * Tu * Tw * Taa # Note: spectrl2_2.c differs from the report in how the Cs value is used. # The two commented out lines match the report, while the following match - # spectrl2_2.c. With regard to Cs, the equations in the report and spectrl12_2.c are algebraically equivalent. + # spectrl2_2.c. With regard to Cs, the equations in the report and + # spectrl12_2.c are algebraically equivalent. # Ir = common_factor * (1 - Tr**0.95) * 0.5 * Cs # Eq 3-5 # Ia = common_factor * Tr**1.5 * (1 - Tas) * Fs * Cs # Eq 3-6 Ir = common_factor * (1 - Tr**0.95) * 0.5 # Eq 3-5