Skip to content

Commit

Permalink
Get solpos8760 - and version 0.3 (Carpenters) (#14)
Browse files Browse the repository at this point in the history
* ENH: loop inside solar_utils

* ENH:TST: test 8760 method

* ENH: return angles and airmass separately

* ENH: collapse reassignments, use inplace

* TST:ENH: test errors, output all error codes

* ENH: any size datetime array

* combine get_solposAM with get_solpos8760, add get specified year function
  • Loading branch information
mikofski authored and anomam committed May 23, 2019
1 parent e65f70e commit 6ea5aca
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Changes
=======

For more recent changes, please see
`GitHub Releases <https://github.com/SunPower/SolarUtils/releases>`_.

v0.2.1 - Bartenders (2016-07-11)
--------------------------------
* add documentation
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# content of setup.cfg
[pytest]
[tool:pytest]
norecursedirs = .git build dist venv
12 changes: 5 additions & 7 deletions solar_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
"""
This is the solar utilities package.
2013 SunPower Corp.
Confidential & Proprietary
Do Not Distribute
2013, 2019 SunPower Corp.
"""

from solar_utils.core import solposAM, spectrl2
from solar_utils.core import solposAM, spectrl2, get_solpos8760, get_solposAM

__version__ = '0.2.4'
__release__ = 'Beetroot'
__version__ = '0.3'
__release__ = 'Carpenters'
__author__ = 'Mark Mikofski'
__email__ = '[email protected]'
__url__ = 'https://github.com/SunPower/SolarUtils'
__all__ = ['solposAM', 'spectrl2']
__all__ = ['solposAM', 'spectrl2', 'get_solpos8760', 'get_solposAM']
92 changes: 91 additions & 1 deletion solar_utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
`NREL RREDC Solar Resource Models and Tools
<http://www.nrel.gov/rredc/models_tools.html>`_
2013 SunPower Corp.
2013, 2019 SunPower Corp.
"""

import ctypes
import datetime as pydatetime
import math
import os
import sys
Expand Down Expand Up @@ -43,6 +44,95 @@ def _int2bits(err_code):
return int(math.log(err_code, 2))


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)


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)


def solposAM(location, datetime, weather):
"""
Calculate solar position and air mass by calling functions exported by
Expand Down
4 changes: 4 additions & 0 deletions solar_utils/docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
# All configuration values have a default; values that are commented out
# serve to show the default.

from __future__ import unicode_literals, absolute_import, division
import sys
import os

if sys.version_info[0] >= 3:
unicode = str

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
Expand Down
8 changes: 8 additions & 0 deletions solar_utils/docs/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ that imports :data:`SOLPOSAMDLL` and exports functions called by

.. data:: SPECTRL2DLL

get_solpos8760
--------------
.. autofunction:: get_solpos8760

get_solposAM
------------
.. autofunction:: get_solposAM

solposAM
--------
.. autofunction:: solposAM
Expand Down
49 changes: 32 additions & 17 deletions solar_utils/src/solposAM.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// 2013 Sunpower Corp.
// Confidential & Proprietary
// Do Not Distribute
// 2013, 2019 Sunpower Corp.

// include C Standard Library headers
#include <math.h>
Expand All @@ -19,20 +17,24 @@
#define DllExport
#endif

// solposAM
// Inputs:
// location: (float*) [longitude, latitude, UTC-timezone]
// datetime: (int*) [year, month, day, hour, minute, second]
// weather: (float*) [ambient-pressure (mBar), ambient-temperature (C)]
// Outputs:
// angles: (float*) [refracted-zenith, azimuth]
// airmass: (float*) [airmass (atmos), pressure-adjusted-airmass (atmos)]
// settings: (int*): [daynum, interval]
// orientation: (float*) [tilt, aspect] (degrees)
// shadowband: (float*): [width, radiation, sky]
DllExport long solposAM( float *location, int *datetime, float *weather,
float *angles, float *airmass, int *settings, float *orientation,
float *shadowband )
/* NREL SOLPOS solar position algorithm
*
* Parameters
* ----------
* location: (float*) [longitude, latitude, UTC-timezone]
* datetime: (int*) [year, month, day, hour, minute, second]
* weather: (float*) [ambient-pressure (mBar), ambient-temperature (C)]
* Returns
* -------
* angles: (float*) [refracted-zenith, azimuth]
* airmass: (float*) [airmass (atmos), pressure-adjusted-airmass (atmos)]
* settings: (int*): [daynum, interval]
* orientation: (float*) [tilt, aspect] (degrees)
* shadowband: (float*): [width, radiation, sky]
*/
DllExport long solposAM( float location[3], int datetime[6], float weather[2],
float angles[2], float airmass[2], int settings[2], float orientation[2],
float shadowband[3] )
{

/* variable declarations */
Expand Down Expand Up @@ -102,3 +104,16 @@ DllExport long solposAM( float *location, int *datetime, float *weather,
return retval;

}

// get_solposAM
DllExport long get_solposAM( float location[3], int datetimes[][6],
float weather[2], int cnt, float angles[][2], float airmass[][2],
int settings[][2], float orientation[][2], float shadowband[][3],
long err_code[])
{
for (size_t i=0; i<cnt; i++){
err_code[i] = solposAM( location, datetimes[i], weather, angles[i],
airmass[i], settings[i], orientation[i], shadowband[i] );
}
return 0;
}
92 changes: 89 additions & 3 deletions solar_utils/tests/test_cdlls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
$ nosetests --verbose
2013 SunPower Corp.
Confidential & Proprietary
Do Not Distribute
2013, 2019 SunPower Corp.
"""

import datetime as pydatetime
import json
import numpy as np
import os
Expand All @@ -28,6 +27,93 @@
RELDIFF = lambda x, x0: np.abs(x - x0) / x0


def test_get_solpos8760():
location = [35.56836, -119.2022, -8.0]
weather = [1015.62055, 40.0]
x, y = get_solpos8760(location, 2017, weather)
z = np.array([
[ 98.96697 , 248.12735 , -1. , -1. ],
[ 98.96697 , 111.86818 , -1. , -1. ],
[ 98.96697 , 111.86365 , -1. , -1. ],
[ 98.96697 , 111.85951 , -1. , -1. ],
[ 98.96697 , 111.85494 , -1. , -1. ],
[ 98.96697 , 111.85041 , -1. , -1. ],
[ 98.96697 , 111.84623 , -1. , -1. ],
[ 91.8338 , 117.05424 , 64.716125 , 64.88354 ],
[ 81.50037 , 126.166016 , 6.489029 , 6.5058155],
[ 72.399506 , 136.78699 , 3.2762375, 3.2847128],
[ 65.06621 , 149.33855 , 2.3618126, 2.3679223],
[ 60.21105 , 163.89734 , 2.0070193, 2.0122113],
[ 58.47457 , 179.82529 , 1.9076174, 1.9125524],
[ 60.13257 , 195.77512 , 2.002265 , 2.0074446],
[ 64.921104 , 210.37862 , 2.3491564, 2.3552334],
[ 72.20489 , 222.98093 , 3.2422662, 3.2506535],
[ 81.272095 , 233.6459 , 6.333636 , 6.3500204],
[ 91.55735 , 242.79282 , 64.014565 , 64.18017 ],
[ 98.96697 , 248.20842 , -1. , -1. ],
[ 98.96697 , 248.21324 , -1. , -1. ],
[ 98.96697 , 248.21808 , -1. , -1. ],
[ 98.96697 , 248.22249 , -1. , -1. ],
[ 98.96697 , 248.22736 , -1. , -1. ],
[ 98.96697 , 248.23221 , -1. , -1. ]],
dtype=np.float32)
assert np.allclose(x[:24], z[:24,:2])
assert np.allclose(y[:24], z[:24,2:])


def test_get_solposAM():
location = [35.56836, -119.2022, -8.0]
weather = [1015.62055, 40.0]
times = [
(pydatetime.datetime(2017, 1, 1, 0, 0, 0)
+ pydatetime.timedelta(hours=h)).timetuple()[:6]
for h in range(1000)]
x, y = get_solposAM(location, times, weather)
z = np.array([
[ 98.96697 , 248.12735 , -1. , -1. ],
[ 98.96697 , 111.86818 , -1. , -1. ],
[ 98.96697 , 111.86365 , -1. , -1. ],
[ 98.96697 , 111.85951 , -1. , -1. ],
[ 98.96697 , 111.85494 , -1. , -1. ],
[ 98.96697 , 111.85041 , -1. , -1. ],
[ 98.96697 , 111.84623 , -1. , -1. ],
[ 91.8338 , 117.05424 , 64.716125 , 64.88354 ],
[ 81.50037 , 126.166016 , 6.489029 , 6.5058155],
[ 72.399506 , 136.78699 , 3.2762375, 3.2847128],
[ 65.06621 , 149.33855 , 2.3618126, 2.3679223],
[ 60.21105 , 163.89734 , 2.0070193, 2.0122113],
[ 58.47457 , 179.82529 , 1.9076174, 1.9125524],
[ 60.13257 , 195.77512 , 2.002265 , 2.0074446],
[ 64.921104 , 210.37862 , 2.3491564, 2.3552334],
[ 72.20489 , 222.98093 , 3.2422662, 3.2506535],
[ 81.272095 , 233.6459 , 6.333636 , 6.3500204],
[ 91.55735 , 242.79282 , 64.014565 , 64.18017 ],
[ 98.96697 , 248.20842 , -1. , -1. ],
[ 98.96697 , 248.21324 , -1. , -1. ],
[ 98.96697 , 248.21808 , -1. , -1. ],
[ 98.96697 , 248.22249 , -1. , -1. ],
[ 98.96697 , 248.22736 , -1. , -1. ],
[ 98.96697 , 248.23221 , -1. , -1. ]],
dtype=np.float32)
assert np.allclose(x[:24], z[:24,:2])
assert np.allclose(y[:24], z[:24,2:])
# test LOCATION
location = [1135.56836, -119.2022, -8.0]
try:
x, y = get_solposAM(location, times, weather)
except SOLPOS_Error as err:
assert err.args[0] == 'S_LAT_ERROR'
location = [35.56836, -119.2022, -8.0]
# test DATETIMES
times = [list(ts) for ts in times]
times[10][5] = 3248273
times = [tuple(ts) for ts in times]
try:
x, y = get_solposAM(location, times, weather)
except SOLPOS_Error as err:
assert err.args[0] == 'S_SECOND_ERROR'


def test_solposAM():
"""
test solposAM.dll
Expand Down

0 comments on commit 6ea5aca

Please sign in to comment.