Source code for solar_utils.core

# -*- 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)