# This file is part of Py6S.
#
# Copyright 2012 Robin Wilson and contributors listed in the CONTRIBUTORS file.
#
# Py6S is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Py6S is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Py6S. If not, see <http://www.gnu.org/licenses/>.
import sys
from collections import defaultdict
# from sixs_exceptions import *
from Py6S.sixs_exceptions import ParameterError
[docs]class AeroProfile:
"""Class representing options for Aerosol Profiles"""
NoAerosols = 0
Continental = 1
Maritime = 2
Urban = 3
Desert = 5
BiomassBurning = 6
Stratospheric = 7
[docs] @classmethod
def PredefinedType(cls, type):
"""Set 6S to use a predefined aerosol type, one of the constants defined in this class.
Arguments:
* ``type`` -- the predefined aerosol type, one of the constants defined in this class
Example usage::
s.aero_profile = AeroProfile.PredefinedType(AeroProfile.Urban)
"""
return "%d" % type
[docs] @classmethod
def FromMieFile(cls, mie_file):
"""Set 6S to use pre-computed outputs of the MIE subroutine written to a text file.
This corresponds to using option iaer=12 in sixs.
Arguments:
* ``mie_file`` -- the name of the text file containing the outputs of the MIE subroutine.
Example usage::
s.aero_profile = AeroProfile.FromMieFile(mie_file = 'FILE.mie')
"""
if not mie_file.endswith('.mie'):
mie_file = mie_file + '.mie'
return "12 from pre-computed mie file\n%s" % mie_file
[docs] @classmethod
def User(cls, **kwargs):
"""Set 6S to use a user-defined aerosol profile based on proportions of standard aerosol components.
The profile is set as a mixture of pre-defined components, each given as an optional keyword.
Not all keywords need to be given, but the values for the keywords given must sum to 1, or a
ParameterError will be raised.
Optional keywords:
* ``dust`` -- The proportion of dust-like aerosols
* ``water`` -- The proportion of water-like aerosols
* ``oceanic`` -- The proportion of oceanic aerosols
* ``soot`` -- The proportion of soot-like aerosols
Example usage::
s.aero_profile = AeroProfile.User(dust=0.3, oceanic=0.7)
s.aero_profile = AeroProfile.User(soot = 0.1, water = 0.3, oceanic = 0.05, dust = 0.55)
"""
d = defaultdict(lambda: 0, kwargs)
dust = d["dust"]
water = d["water"]
oceanic = d["oceanic"]
soot = d["soot"]
if ((dust + water + oceanic + soot) - 1) > 0.01:
raise ParameterError("Aerosol Profile", "User aerosol components don't sum to 1.0")
return "4 (User's Components)\n%f, %f, %f, %f" % (dust, water, oceanic, soot)
[docs] @classmethod
def MultimodalLogNormalDistribution(cls, rmin, rmax):
"""Set 6S to use a Multimodal Log-Normal distribution.
Arguments:
* ``rmin`` -- The minimum aerosol radius
* ``rmax`` -- The maximum aerosol radius
This returns an :class:`.AerosolDistribution` object. Components can then be added to this distribution using the :meth:`.add_component`
method of the returned class.
Example usage::
s.aero_profile = AeroProfile.MultimodalLogNormalDistribution(0.1, 0.3)
s.aero_profile.add_component(...)
"""
return cls.AerosolDistribution(rmin, rmax, 8)
[docs] @classmethod
def ModifiedGammaDistribution(cls, rmin, rmax):
"""Set 6S to use a Modified Gamma distribution.
Arguments:
* ``rmin`` -- The minimum aerosol radius
* ``rmax`` -- The maximum aerosol radius
This returns an :class:`.AerosolDistribution` object. Components can then be added to this distribution using the :meth:`.add_component`
method of the returned class.
Example usage::
s.aero_profile = AeroProfile.ModifiedGammaDistribution(0.1, 0.3)
s.aero_profile.add_component(...)
"""
return cls.AerosolDistribution(rmin, rmax, 9)
[docs] @classmethod
def JungePowerLawDistribution(cls, rmin, rmax):
"""Set 6S to use a Junge Power Law distribution.
Arguments:
* ``rmin`` -- The minimum aerosol radius
* ``rmax`` -- The maximum aerosol radius
This returns an :class:`.AerosolDistribution` object. Components can then be added to this distribution using the :meth:`.add_component`
method of the returned class.
Example usage::
s.aero_profile = AeroProfile.JungePowerLawDistribution(0.1, 0.3)
s.aero_profile.add_component(...)
"""
return cls.AerosolDistribution(rmin, rmax, 10)
[docs] @classmethod
def SunPhotometerDistribution(cls, r, dvdlogr, refr_real, refr_imag):
"""Set 6S to use an aerosol parameterisation from Sun Photometer measurements.
The real and imaginary parts of the refractive indices must be input at the following wavelengths
(given in micrometers):
0.350, 0.400, 0.412, 0.443, 0.470, 0.488, 0.515, 0.550, 0.590, 0.633, 0.670, 0.694, 0.760,
0.860, 1.240, 1.536, 1.650, 1.950, 2.250, 3.750
Arguments:
* ``r`` -- A list of radius measurements from a sun photometer (microns)
* ``dvdlogr`` -- A list of dV/d(logr) measurements from a sun photometer, for the radiuses as above (cm^3/cm^2/micron)
* ``refr_real`` -- A list containing the real part of the refractive indices for each of the 20 wavelengths (above). If a single
float value is given then the value is treated as constant for all wavelengths.
* ``refr_imag`` -- A list containing the imaginary part of the refractive indices for each of the 20 wavelengths (above). If a single
float value is given then the value is treated as constant for all wavelengths.
"""
header = "11 (Sun Photometric Distribution)\n"
# Check lengths of r and dvdlorg are the same
if len(r) != len(dvdlogr):
raise ParameterError("R and dV/d(log r)", "These must be the same length")
num = "%d\n" % len(r)
ds = ""
comp = ""
for i in range(len(r)):
ds += "%f %f\n" % (r[i], dvdlogr[i])
try:
if type(refr_real) is float:
refr_real = [refr_real] * 20
elif len(refr_real) != 20:
raise ParameterError(
"Aerosol Distribution Real Refractive Index",
"You must specify the real part of the Refractive Index at 20 wavelengths.",
)
except TypeError:
raise ParameterError(
"Aerosol Distribution Imaginary Refractive Index",
"You must specify the imaginary part of the Refractive Index at 20 wavelengths.",
)
try:
if type(refr_imag) is float:
refr_imag = [refr_imag] * 20
elif len(refr_imag) != 20:
raise ParameterError(
"Aerosol Distribution Imaginary Refractive Index",
"You must specify the imaginary part of the Refractive Index at 20 wavelengths.",
)
except TypeError:
raise ParameterError(
"Aerosol Distribution Imaginary Refractive Index",
"You must specify the imaginary part of the Refractive Index at 20 wavelengths.",
)
real = map(str, refr_real)
imag = map(str, refr_imag)
if sys.version_info[0] >= 3:
real = list(real)
imag = list(imag)
comp += " ".join(real) + "\n"
comp += " ".join(imag) + "\n"
return header + num + ds + comp #+ "0 no results saved"
[docs] class AerosolDistribution:
"""Stores data regarding a specific Aerosol Distribution.
Used by the following methods:
* :meth:`.MultimodalLogNormalDistribution`
* :meth:`.ModifiedGammaDistribution`
* :meth:`.JungePowerLawDistribution`
"""
def __init__(self, rmin, rmax, numtype):
"""Initialise an Aerosol Distribution with various parameters.
Should not be called directly - use one of the methods like AeroProfile.MultimodalLogNormalDistribution() instead.
Arguments:
* ``rmin`` -- The minimum aerosol radius
* ``rmax`` -- The maximum aerosol radius
* ``numtype`` -- The type of aerosol distribution (eg. 8 for Multimodal Log Normal)
"""
self.rmin = rmin
self.rmax = rmax
self.numtype = numtype
self.values = []
[docs] def add_component(self, rmean, sigma, percentage_density, refr_real, refr_imag):
"""Adds a component to the aerosol distribution.
Wavelength dependent values must be input at the following wavelengths (given in micrometers):
0.350, 0.400, 0.412, 0.443, 0.470, 0.488, 0.515, 0.550, 0.590, 0.633, 0.670, 0.694, 0.760,
0.860, 1.240, 1.536, 1.650, 1.950, 2.250, 3.750
Arguments:
* ``rmean`` -- The mean radius of the aerosols
* ``sigma`` -- Sigma, as defined by the distribution (Log Normal etc)
* ``percentage_density`` -- The percentage density of the aerosol
* ``refr_real`` -- A 20-element iterable giving the real part of the refractive indices at the specified wavelengths (see above)
* ``refr_imag`` -- A 20-element iterable giving the imaginary part of the refractive indices at the specified wavelengths (see above)
"""
if len(self.values) >= 4:
raise ParameterError(
"Aerosol Distribution components",
"You can only add a maximum of 4 components",
)
if len(refr_real) != 20:
raise ParameterError(
"Aerosol Distribution Real Refractive Index",
"You must specify the real part of the Refractive Index at 20 wavelengths.",
)
if len(refr_imag) != 20:
raise ParameterError(
"Aerosol Distribution Imaginary Refractive Index",
"You must specify the imaginary part of the Refractive Index at 20 wavelengths.",
)
comp = "%f %f %f\n" % (rmean, sigma, percentage_density)
real = map(str, refr_real)
imag = map(str, refr_imag)
if sys.version_info[0] >= 3:
real = list(real)
imag = list(imag)
comp += " ".join(real) + "\n"
comp += " ".join(imag) + "\n"
self.values.append(comp)
def __str__(self):
result = "%d\n%f %f %d\n" % (
self.numtype,
self.rmin,
self.rmax,
len(self.values),
)
components = "".join(self.values)
return result + components #+ "0 no results saved"
[docs] class UserProfile:
"""Set 6S to use a user-defined aerosol profile, with differing AOTs over the height of the profile.
Arguments:
* ``atype`` -- Aerosol type to be used for all layers. Must be one of the pre-defined types defined in this class.
Methods:
* :meth:`.add_layer` -- Adds a layer to the user-defined aerosol profile, with the specified height and aerosol optical thickness.
Example usage::
s.aero_profile = AeroProfile.UserProfile(AeroProfile.Maritime)
s.aero_profile.add_layer(5, 0.34) # Add a 5km-thick layer with an AOT of 0.34
s.aero_profile.add_layer(10, 0.7) # Add a 10km-thick layer with an AOT of 0.7
s.aero_profile.add_layer(100, 0.01) # Add a 100km-thick layer with an AOT of 0.01
"""
def __init__(self, atype):
"""Initialises the user-defined aerosol profile to a specific aerosol type.
Arguments:
* ``atype`` -- Aerosol type to be used for all layers. Must be one of the pre-defined types defined in this class.
"""
self.aerotype = atype
self.values = []
[docs] def add_layer(self, height, optical_thickness):
"""Adds a layer to the user-defined profile.
Arguments:
* ``height`` -- Height of the layer (in km)
* ``optical_thickness`` -- Optical thickness of the layer
Example usage::
s.aero_profile.add_layer(5, 0.34) # Add a 5km-thick layer with an AOT of 0.34
"""
self.values.append((height, optical_thickness))
def __str__(self):
res = "-1 Aerosol model (type) and profile\n%d\n" % len(self.values)
for val in self.values:
res += "%f %f %d\n" % (val[0], val[1], self.aerotype)
return res