# -*- coding: utf-8 -*-
"""
Solar utilities based on `SOLPOS
<http://rredc.nrel.gov/solar/codesandalgorithms/solpos/>`_ and `SPECTRL2
<http://rredc.nrel.gov/solar/models/spectral/>`_ from
`NREL RREDC Solar Resource Models and Tools
<http://www.nrel.gov/rredc/models_tools.html>`_
2013, 2019 SunPower Corp.
"""
import ctypes
import datetime as pydatetime
import math
import os
import sys
from solar_utils.exceptions import SOLPOS_Error, SPECTRL2_Error
_DIRNAME = os.path.dirname(__file__)
PLATFORM = sys.platform
if PLATFORM == 'win32':
SOLPOSAM = 'solposAM.dll'
SPECTRL2 = 'spectrl2.dll'
elif PLATFORM in ['linux2', 'linux']:
PLATFORM = 'linux'
SOLPOSAM = 'libsolposAM.so'
SPECTRL2 = 'libspectrl2.so'
elif PLATFORM == 'darwin':
SOLPOSAM = 'libsolposAM.dylib'
SPECTRL2 = 'libspectrl2.dylib'
else:
raise OSError('Platform "%s" is unknown or unsupported.' % PLATFORM)
SOLPOSAMDLL = os.path.join(_DIRNAME, SOLPOSAM)
SPECTRL2DLL = os.path.join(_DIRNAME, SPECTRL2)
[docs]def _int2bits(err_code):
"""
Convert integer to bits.
:param err_code: integer to convert
:returns: log(err_code, 2)
"""
return int(math.log(err_code, 2))
[docs]def get_solpos8760(location, year, weather):
"""
Get SOLPOS hourly calculation for specified non-leap year.
:param location: [latitude, longitude, UTC-timezone]
:type location: float
:param year: a non-leap year
:type year: int
:param weather: [ambient-pressure (mB), ambient-temperature (C)]
:type weather: float
:returns: angles [degrees], airmass [atm]
:rtype: float
:raises: :exc:`~solar_utils.exceptions.SOLPOS_Error`
**Example:**
>>> location = [35.56836, -119.2022, -8.0]
>>> weather = [1015.62055, 40.0]
>>> angles, airmass = get_solpos8760(location, 2013, weather)
"""
datetimes = [
(pydatetime.datetime(year, 1, 1, 0, 0, 0)
+ pydatetime.timedelta(hours=h)).timetuple()[:6]
for h in range(8760)]
return get_solposAM(location, datetimes, weather)
[docs]def get_solposAM(location, datetimes, weather):
"""
Get SOLPOS hourly calculation for sequence of datetimes.
:param location: [latitude, longitude, UTC-timezone]
:type location: float
:param datetimes: [year, month, day, hour, minute, second]
:type datetimes: int
:param weather: [ambient-pressure (mB), ambient-temperature (C)]
:type weather: float
:returns: angles [degrees], airmass [atm]
:rtype: float
:raises: :exc:`~solar_utils.exceptions.SOLPOS_Error`
**Example:**
>>> location = [35.56836, -119.2022, -8.0]
>>> datetimes = [
... (datetime.datetime(2013, 1, 1, 0, 0, 0)
... + datetime.timedelta(hours=h)).timetuple()[:6]
... for h in range(1000)]
>>> weather = [1015.62055, 40.0]
>>> angles, airmass = get_solposAM(location, datetimes, weather)
"""
count = len(datetimes)
# load the DLL
solposAM_dll = ctypes.cdll.LoadLibrary(SOLPOSAMDLL)
_get_solposAM = solposAM_dll.get_solposAM
# cast Python types as ctypes
_location = (ctypes.c_float * 3)(*location)
_datetime = ((ctypes.c_int * 6) * count)(*datetimes)
_weather = (ctypes.c_float * 2)(*weather)
# allocate space for results
angles = ((ctypes.c_float * 2) * count)()
airmass = ((ctypes.c_float * 2) * count)()
settings = ((ctypes.c_int * 2) * count)()
orientation = ((ctypes.c_float * 2) * count)()
shadowband = ((ctypes.c_float * 3) * count)()
err_code = (ctypes.c_long * count)()
# call
retval = _get_solposAM(
_location, _datetime, _weather, count, angles, airmass, settings,
orientation, shadowband, err_code)
if (retval != 0): raise RuntimeError('solposAM did not execute')
if all(ec == 0 for ec in err_code):
return angles, airmass
else:
for n, ec in enumerate(err_code):
if ec == 0: continue
# convert err_code to bits
_code = _int2bits(ec)
data = {'location': location,
'datetime': datetimes[n],
'weather': weather,
'angles': angles[n],
'airmass': airmass[n],
'settings': settings[n],
'orientation': orientation[n],
'shadowband': shadowband[n]}
raise SOLPOS_Error(_code, data)
[docs]def solposAM(location, datetime, weather):
"""
Calculate solar position and air mass by calling functions exported by
:data:`SOLPOSAMDLL`.
:param location: [latitude, longitude, UTC-timezone]
:type location: float
:param datetime: [year, month, day, hour, minute, second]
:type datetime: int
:param weather: [ambient-pressure (mB), ambient-temperature (C)]
:type weather: float
:returns: angles [degrees], airmass [atm]
:rtype: float
:raises: :exc:`~solar_utils.exceptions.SOLPOS_Error`
Returns the solar zenith and azimuth angles in degrees, as well as the
relative and absolute (or pressure corrected) air mass.
**Examples:**
>>> location = [35.56836, -119.2022, -8.0]
>>> datetime = [2013, 6, 5, 12, 31, 0]
>>> weather = [1015.62055, 40.0]
>>> (angles, airmass) = solposAM(location, datetime, weather)
>>> list(angles)
[15.074043273925781, 213.29042053222656]
>>> list(airmass)
[1.0352272987365723, 1.0379053354263306]
"""
# load the DLL
solposAM_dll = ctypes.cdll.LoadLibrary(SOLPOSAMDLL)
_solposAM = solposAM_dll.solposAM
# cast Python types as ctypes
_location = (ctypes.c_float * 3)(*location)
_datetime = (ctypes.c_int * 6)(*datetime)
_weather = (ctypes.c_float * 2)(*weather)
# allocate space for results
angles = (ctypes.c_float * 2)()
airmass = (ctypes.c_float * 2)()
settings = (ctypes.c_int * 2)()
orientation = (ctypes.c_float * 2)()
shadowband = (ctypes.c_float * 3)()
# call DLL
err_code = _solposAM(_location, _datetime, _weather, angles, airmass,
settings, orientation, shadowband)
# return results if successful, otherwise raise SOLPOS_Error
if err_code == 0:
return angles, airmass
else:
# convert err_code to bits
_code = _int2bits(err_code)
data = {'location': location,
'datetime': datetime,
'weather': weather,
'angles': angles,
'airmass': airmass,
'settings': settings,
'orientation': orientation,
'shadowband': shadowband}
raise SOLPOS_Error(_code, data)
[docs]def spectrl2(units, location, datetime, weather, orientation,
atmospheric_conditions, albedo):
"""
Calculate solar spectrum by calling functions exported by
:data:`SPECTRL2DLL`.
:param units: set ``units`` = 1 for W/m\ :sup:`2`/micron
:type units: int
:param location: latitude, longitude and UTC-timezone
:type location: float
:param datetime: year, month, day, hour, minute and second
:type datetime: int
:param weather: ambient-pressure [mB] and ambient-temperature [C]
:type weather: float
:param orientation: tilt and aspect [degrees]
:type orientation: float
:param atmospheric_conditions: alpha, assym, ozone, tau500 and watvap
:type atmospheric_conditions: float
:param albedo: 6 wavelengths and 6 reflectivities
:type albedo: float
:returns: spectral decomposition, x-coordinate
:rtype: float
:raises: :exc:`~solar_utils.exceptions.SPECTRL2_Error`,
:exc:`~solar_utils.exceptions.SOLPOS_Error`
Returns the diffuse, direct, extraterrestrial and global spectral components
on the tilted surface in as a function of x-coordinate specified by units.
===== ===============================================================
units output units
===== ===============================================================
1 irradiance (W/sq m/micron) per wavelength (microns)
2 photon flux (10.0E+16 /sq cm/s/micron) per wavelength (microns)
3 photon flux density (10.0E+16 /sq cm/s/eV) per energy (eV)
===== ===============================================================
See
`NREL SPECTRL2 Documentation <http://rredc.nrel.gov/solar/models/spectral/spectrl2/documentation.html>`_
for more detail.
.. seealso::
:func:`solposAM`
**Examples:**
>>> units = 1
>>> location = [33.65, -84.43, -5.0]
>>> datetime = [1999, 7, 22, 9, 45, 37]
>>> weather = [1006.0, 27.0]
>>> orientation = [33.65, 135.0]
>>> atmospheric_conditions = [1.14, 0.65, -1.0, 0.2, 1.36]
>>> albedo = [0.3, 0.7, 0.8, 1.3, 2.5, 4.0] + ([0.2] * 6)
>>> (specdif, specdir, specetr, specglo,
specx) = spectrl2(units, location, datetime, weather, orientation,
atmospheric_conditions, albedo)
"""
# load the DLL
ctypes.cdll.LoadLibrary(SOLPOSAMDLL) # requires 'solpos.dll'
spectrl2_dll = ctypes.cdll.LoadLibrary(SPECTRL2DLL)
_spectrl2 = spectrl2_dll.spectrl2
# cast Python types as ctypes
_location = (ctypes.c_float * 3)(*location)
_datetime = (ctypes.c_int * 6)(*datetime)
_weather = (ctypes.c_float * 2)(*weather)
_orientation = (ctypes.c_float * 2)(*orientation)
_atmospheric_conditions = (ctypes.c_float * 5)(*atmospheric_conditions)
_albedo = (ctypes.c_float * 12)(*albedo)
# allocate space for results
specdif = (ctypes.c_float * 122)()
specdir = (ctypes.c_float * 122)()
specetr = (ctypes.c_float * 122)()
specglo = (ctypes.c_float * 122)()
specx = (ctypes.c_float * 122)()
angles = (ctypes.c_float * 2)()
airmass = (ctypes.c_float * 2)()
settings = (ctypes.c_int * 2)()
shadowband = (ctypes.c_float * 3)()
# call DLL
err_code = _spectrl2(
units, _location, _datetime, _weather, _orientation,
_atmospheric_conditions, _albedo, specdif, specdir, specetr, specglo,
specx, angles, airmass, settings, shadowband
)
# return results if successful, otherwise raise exception
if err_code == 0:
return specdif, specdir, specetr, specglo, specx
elif err_code < 0:
data = {'units': units,
'tau500': atmospheric_conditions[3],
'watvap': atmospheric_conditions[4],
'assym': atmospheric_conditions[1]}
raise SPECTRL2_Error(err_code, data)
else:
# convert err_code to bits
_code = _int2bits(err_code)
data = {'location': location,
'datetime': datetime,
'weather': weather,
'angles': angles,
'airmass': airmass,
'settings': settings,
'orientation': orientation,
'shadowband': shadowband}
raise SOLPOS_Error(_code, data)