Skip to content

Commit

Permalink
bump to v0.3 Proterozoic Eon
Browse files Browse the repository at this point in the history
* update documents with new tests, fix unc_wrapper_args docs
* add docs for unc_wrapper manually
* fix test_solpos for list of datetimes
* change some names:
  wrapper->wrapped_function
  unc_wrapper->wrapper
  g->f_
  y->x_
  gkwargs->kwargs_
* update history
  • Loading branch information
mikofski committed Apr 16, 2016
1 parent 50bd714 commit d6a2763
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 30 deletions.
16 changes: 15 additions & 1 deletion README.rst
Expand Up @@ -25,8 +25,22 @@ History
Releases are named after
`geological eons, periods and epochs <https://en.wikipedia.org/wiki/Geologic_time_scale>`_.

`v0.3 <https://github.com/SunPower/UncertaintyWrapper/releases/tag/v0.3>`_ `Proterozoic Eon <https://en.wikipedia.org/wiki/Proterozoic>`_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* new ``unc_wrapper_args()`` allows selection of independent variables that the
partial derivatives are with respect to and also grouping those arguments
together so that in the original function they can stay unpacked.
* return values are grouped correctly so that they can remain unpacked in
original function. These allow Uncertainty Wrapper to be used with
`Pint's wrapper <http://pint.readthedocs.org/en/0.6/wrapping.html>`_
* covariance now specified as dimensionaless fraction of square of arguments
* more complex tests: IV curve and solar position (requires
`NREL's solpos <http://rredc.nrel.gov/solar/codesandalgorithms/solpos/>`_)


`v0.2.1 <https://github.com/SunPower/UncertaintyWrapper/releases/tag/v0.2>`_ `Eoarchean Era <https://en.wikipedia.org/wiki/Eoarchean>`_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* update documentation

Expand Down
42 changes: 26 additions & 16 deletions uncertainty_wrapper/__init__.py
Expand Up @@ -4,7 +4,7 @@
.. math::
dF_{ij} = J_{ij} * S_{x_i}{x_j} * J_{ij}^{T}
dF_{ij} = J_{ij} * S_{x_i, x_j} * J_{ij}^{T}
Diagonals of :math:`dF_{ij}` are standard deviations squared.
Expand All @@ -19,8 +19,8 @@
logging.basicConfig()
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
__VERSION__ = '0.2.1'
__RELEASE__ = u"Eoarchean Era"
__VERSION__ = '0.3'
__RELEASE__ = u"Proterozoic Eon"
__URL__ = u'https://github.com/SunPower/UncertaintyWrapper'
__AUTHOR__ = u"Mark Mikofski"
__EMAIL__ = u'mark.mikofski@sunpowercorp.com'
Expand Down Expand Up @@ -109,16 +109,25 @@ def jflatten(j):


def unc_wrapper_args(*covariance_keys):
def unc_wrapper(f):
"""
Wrap function, pop ``__covariance__`` argument from keyword arguments,
propagate uncertainty given covariance using Jacobian estimate and append
calculated covariance and Jacobian matrices to return values. User supplied
covariance keys can be any of the argument names used in the function. If
empty then assume the arguments are already grouped. If ``None`` then use
all of the arguments. See tests for examples.
:param covariance_keys: names of arguments that correspond to covariance
:return: function value, covariance and Jacobian
"""
def wrapper(f):
"""
Wrap function, pop ``__covariance__`` argument from keyword arguments,
propagate uncertainty given covariance using Jacobian estimate. and append
calculated covariance and Jacobian matrices to return values.
"""
argspec = inspect.getargspec(f)

@wraps(f)
def wrapper(*args, **kwargs):
def wrapped_function(*args, **kwargs):
cov_keys = covariance_keys
cov = kwargs.pop('__covariance__', None) # pop covariance
if argspec.defaults is not None:
Expand All @@ -133,15 +142,15 @@ def wrapper(*args, **kwargs):
else:
x = kwargs.pop(argspec.args[0])

def g(y, **gkwargs):
def f_(x_, **kwargs_):
if cov_keys:
gkwargs.update(zip(cov_keys, y))
return np.array(f(**gkwargs))
kwargs_.update(zip(cov_keys, x_))
return np.array(f(**kwargs_))
# assumes independent and dependent vars already grouped
return f(y, **gkwargs)
return f(x_, **kwargs_)

avg = g(x, **kwargs)
jac = jacobian(g, x, **kwargs)
avg = f_(x, **kwargs)
jac = jacobian(f_, x, **kwargs)
# covariance must account for all observations
if cov is not None and cov.ndim == 3:
# if covariance is an array of covariances, flatten it.
Expand All @@ -151,7 +160,8 @@ def g(y, **gkwargs):
cov *= x.T.flatten() ** 2
cov = np.dot(np.dot(jac, cov), jac.T)
return avg, cov, jac
return wrapper
return unc_wrapper
return wrapped_function
return wrapper

# short cut for functions with arguments already grouped
unc_wrapper = unc_wrapper_args()
21 changes: 20 additions & 1 deletion uncertainty_wrapper/docs/api.rst
Expand Up @@ -31,7 +31,16 @@ Flatten Jacobian
Wrapper
~~~~~~~

.. autofunction:: unc_wrapper
.. autofunction:: unc_wrapper_args


Wrapper Shortcut
````````````````

.. function:: unc_wrapper

This is basically :func:`unc_wrapper_args()` with no argument which assumes that
all independent arguments are already grouped together.

Tests
-----
Expand All @@ -42,3 +51,13 @@ Test Uncertainty Wrapper
~~~~~~~~~~~~~~~~~~~~~~~~

.. autofunction:: test_unc_wrapper

Test IV curve
~~~~~~~~~~~~~

.. autofunction:: test_IV

Test Solar Position
~~~~~~~~~~~~~~~~~~~

.. autofunction:: test_solpos
33 changes: 21 additions & 12 deletions uncertainty_wrapper/tests/__init__.py
Expand Up @@ -9,7 +9,7 @@
from uncertainty_wrapper import unc_wrapper, unc_wrapper_args
import logging
from scipy.constants import Boltzmann as KB, elementary_charge as QE
from datetime import datetime
from datetime import datetime, timedelta
from solar_utils import *
import pytz

Expand Down Expand Up @@ -103,35 +103,44 @@ def Voc(x, E0=1000, T0=298.15, kB=KB, qe=QE):


def test_IV():
"""
Test calculation of photovoltaic cell IV curve using 2-diode model.
"""
f = unc_wrapper(IV)
return f(X, VD, __covariance__=COV)


@unc_wrapper_args('lat', 'lon', 'press', 'tamb', 'seconds')
def solar_position(lat, lon, press, tamb, dt, seconds=0):
def solar_position(lat, lon, press, tamb, timestamps, seconds=0):
"""
calculate solar position
"""
seconds = np.sign(seconds) * np.ceil(np.abs(seconds))
# seconds = np.where(x >0 0, np.ceil(seconds), np.floor(seconds))
utcoffset = dt.utcoffset() or 0.0
dst = dt.dst() or 0.0
loc = [lat, lon, (utcoffset.total_seconds() - dst.total_seconds()) / 3600.0]
naive = dt.replace(tzinfo=None)
timestamps = np.datetime64(naive) + np.timedelta64(int(seconds * 1e6), 'us')
timestamps = timestamps.reshape((-1,))
ntimestamps = timestamps.size
# seconds = np.where(x > 0, np.ceil(seconds), np.floor(seconds))
try:
ntimestamps = len(timestamps)
except TypeError:
ntimestamps = 1
timestamps = [timestamps]
an, am = np.zeros((ntimestamps, 2)), np.zeros((ntimestamps, 2))
for n, ts in enumerate(timestamps):
dt = ts.item().timetuple()[:6]
LOGGER.debug('datetime: %r', datetime(*dt).strftime('%Y/%m/%d-%H:%M:%S.%f'))
utcoffset = ts.utcoffset() or 0.0
dst = ts.dst() or 0.0
tz = (utcoffset.total_seconds() - dst.total_seconds()) / 3600.0
loc = [lat, lon, tz]
dt = ts + timedelta(seconds=seconds.item())
dt = dt.timetuple()[:6]
LOGGER.debug('datetime: %r', datetime(*dt).strftime('%Y/%m/%d-%H:%M:%S'))
LOGGER.debug('lat: %f, lon: %f, tz: %d', *loc)
LOGGER.debug('p = %f[mbar], T = %f[C]', press, tamb)
an[n], am[n] = solposAM(loc, dt, [press, tamb])
return an[:, 0], an[:, 1], am[:, 0], am[:, 1]


def test_solpos():
"""
Test solar position calculation using NREL's SOLPOS.
"""
dt = PST.localize(datetime(2016, 4, 13, 12, 30, 0))
return solar_position(37.405, -121.95, 1013.25, 20.0, dt,
__covariance__=np.diag([0.0001] * 5))
Expand Down

0 comments on commit d6a2763

Please sign in to comment.