├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── get_spa.py ├── requirements.txt ├── setup.cfg ├── setup.py └── uncertainty_wrapper ├── __init__.py ├── core.py ├── docs ├── Makefile ├── _static │ ├── IV-PV-jac-errors.png │ ├── IV_and_PV_plots_with_uncertainty.png │ └── sp_2014_logo_black_orange_rgb.png ├── api.rst ├── conf.py ├── favicon.ico ├── getting_started.rst ├── index.rst ├── make.bat └── more_examples │ ├── complex_example.rst │ └── python_extension_example_with_units.rst └── tests ├── __init__.py ├── test_algopy.py └── test_uncertainty_wrapper.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | venv/ 27 | pvlib*/ 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | */docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | #Ipython Notebook 64 | .ipynb_checkpoints 65 | 66 | # IDE 67 | UncertaintyWrapper.sublime-project 68 | UncertaintyWrapper.sublime-workspace 69 | .idea/ 70 | .settings/ 71 | .project 72 | .pydevproject 73 | .spyderproject 74 | .vscode 75 | 76 | 77 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: required 3 | before_install: 4 | - sudo apt-get -qq update 5 | - sudo apt-get install -y libatlas-dev liblapack-dev libblas-dev gfortran 6 | before_script: 7 | - pip install requests cython 8 | - export PVLIB_PATH=/home/travis/build/pvlib/pvlib-python 9 | - echo $PVLIB_PATH 10 | - git clone https://github.com/pvlib/pvlib-python.git $PVLIB_PATH 11 | - python /home/travis/build/BreakingBytes/UncertaintyWrapper/get_spa.py 12 | - BUILD_DIR=$PWD 13 | - echo leaving $BUILD_DIR 14 | - cd $PVLIB_PATH/pvlib/spa_c_files/ 15 | - echo entered $PWD 16 | - python setup.py build_ext --inplace 17 | - cd $PVLIB_PATH/ 18 | - echo entered $PWD 19 | - python setup.py bdist_wheel 20 | - pip install -U dist/pvlib-*.whl 21 | - cd $BUILD_DIR 22 | - echo entered $PWD 23 | # command to run tests 24 | script: py.test 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, SunPower 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of UncertaintyWrapper nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/BreakingBytes/UncertaintyWrapper.svg?branch=master 2 | :target: https://travis-ci.org/BreakingBytes/UncertaintyWrapper 3 | 4 | UncertaintyWrapper 5 | ================== 6 | 7 | Use ``@unc_wrapper`` decorator to wrap any Python callable to append the 8 | covariance and Jacobian matrices to the return values. See documentation and 9 | tests for usage and examples. 10 | 11 | Installation 12 | ------------ 13 | 14 | Use ``pip install UncertaintyWrapper`` to install from 15 | `PyPI `_ or download a source 16 | distribution, extract and use ``python setup.py install``. 17 | 18 | Requirements 19 | ------------ 20 | 21 | * `NumPy `_ 22 | 23 | Optional Requirements 24 | ~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | * `Nose `_ for testing. 27 | * `Sphinx `_ to build documentation. 28 | * `NREL SOLPOS `_ for testing 29 | * `AlgoPy `_ for testing 30 | 31 | Usage 32 | ----- 33 | 34 | Example:: 35 | 36 | from uncertainty_wrapper import unc_wraper 37 | import numpy as np 38 | 39 | @unc_wrapper 40 | def f(x): 41 | return np.exp(x) 42 | 43 | x, cov = np.array([[1.0]]), np.array([[0.1]]) 44 | f(x, __covariance__=cov) 45 | 46 | Returns:: 47 | 48 | (array([[ 2.71828183]]), # exp(1.0) 49 | array([[[ 0.73890561]]]), # (delta-f)^2 = (df/dx)^2 * (delta-x)^2 50 | array([[[ 2.71828183]]])) # df/dx = exp(x) 51 | 52 | 53 | History 54 | ------- 55 | Releases are named after 56 | `geological eons, periods and epochs `_. 57 | 58 | v0.4.2 Terreneuvian Series 59 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 60 | * futurize 61 | * remove pint 62 | 63 | `v0.4.1 `_ `Paleozoic Era `_ 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 | * Jagged arrays of covariance keys work now. 66 | * simplify 67 | 68 | `v0.4 `_ `Phanerozoic Era `_ 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | * Fixes #5, ``ValueError`` if covariance keys have multiple observations 71 | * fix covariance cross terms not scaled correctly 72 | 73 | `v0.3.3 `_ `Neoproterozoic Era `_ 74 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 75 | * Fixes #4, ``ValueError`` if just one observation 76 | 77 | `v0.3.2 `_ `Mesoproterozoic Era `_ 78 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 | * Fixes #2, don't need to tile scalar x for multiple observations 80 | * Fixes #3, use sparse matrices for dot product instead of dense 81 | * uses pvlib example instead of proprietary solar_utils 82 | 83 | 84 | `v0.3.1 `_ `Paleoproterozoic Era `_ 85 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86 | 87 | * Fixes #1 works with Pint's @ureg.wraps() 88 | * Use indices for positional arguments. Don't use inspect.argspec since not 89 | guaranteed to be the same for wrapped or decorated functions 90 | * Test Jacobian estimate for IV with `AlgoPy `_ 91 | * Show Jacobian errors plot in getting started docs. 92 | 93 | 94 | `v0.3 `_ `Proterozoic Eon `_ 95 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 96 | 97 | * new ``unc_wrapper_args()`` allows selection of independent variables that the 98 | partial derivatives are with respect to and also grouping those arguments 99 | together so that in the original function they can stay unpacked. 100 | * return values are grouped correctly so that they can remain unpacked in 101 | original function. These allow Uncertainty Wrapper to be used with 102 | `Pint's wrapper `_ 103 | * covariance now specified as dimensionaless fraction of square of arguments 104 | * more complex tests: IV curve and solar position (requires 105 | `NREL's solpos `_) 106 | 107 | 108 | `v0.2.1 `_ `Eoarchean Era `_ 109 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 110 | 111 | * update documentation 112 | 113 | 114 | `v0.2 `_ `Archean Eon `_ 115 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 116 | 117 | * Fix nargs and nf order mixup in Jacobian 118 | * add more complex test 119 | * fix tile cov by nobs 120 | * move partial derivative to subfunction 121 | * try threading, but same speed, and would only work with NumPy anyway 122 | 123 | 124 | `v0.1 `_ `Hadean Eon `_ 125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 126 | 127 | * adds covariance to output 128 | * allows __covariance__ to be passed as input 129 | * uses estimate Jacobian based on central finite difference method 130 | -------------------------------------------------------------------------------- /get_spa.py: -------------------------------------------------------------------------------- 1 | #! /usr/env/python 2 | 3 | """ 4 | get spa c source from NREL for travis 5 | """ 6 | 7 | import requests 8 | import os 9 | import logging 10 | 11 | # logger 12 | logging.basicConfig(level=logging.DEBUG) 13 | LOGGER = logging.getLogger(__file__) 14 | # constants 15 | SPAC_URL = r'https://midcdmz.nrel.gov/apps/download.pl' # spa.c source url 16 | # register with NREL to download spa.c source 17 | PAYLOAD = { 18 | 'name': 'uncertainty wrapper', 19 | 'country': 'US', 20 | 'company': 'SunPower', 21 | 'software': 'SPA' 22 | } 23 | SPAH_URL = r'http://midcdmz.nrel.gov/spa/spa.h' # spa.h source url 24 | PVLIB_PATH = os.environ['PVLIB_PATH'] # path to PVLIB on travis 25 | SPA_C_FILES = os.path.join(PVLIB_PATH, 'pvlib', 'spa_c_files') 26 | 27 | if __name__ == "__main__": 28 | # get spa.c source 29 | LOGGER.debug('post payload to url: %s\n\tpayload:\n%s', SPAC_URL, PAYLOAD) 30 | r = requests.post(SPAC_URL, data=PAYLOAD) 31 | LOGGER.debug('post response: %r', r) 32 | # save spa.c source to PVLIB/SPA_C_FILES folder 33 | with open(os.path.join(SPA_C_FILES, 'spa.c'), 'wb') as f: 34 | f.write(r.content) 35 | LOGGER.debug('saved file: %r', f.name) 36 | # get spa.c source 37 | LOGGER.debug('get url: %s', SPAH_URL) 38 | r = requests.get(SPAH_URL) 39 | LOGGER.debug('get response: %r', r) 40 | # save spa.c source to PVLIB/SPA_C_FILES folder 41 | with open(os.path.join(SPA_C_FILES, 'spa.h'), 'wb') as f: 42 | f.write(r.content) 43 | LOGGER.debug('saved file: %r', f.name) 44 | LOGGER.debug('exiting %s', __file__) # exit 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.18.2 2 | pandas==1.0.3 3 | pvlib==0.7.1 4 | pytz==2019.3 5 | matplotlib==3.2.1 6 | nose==1.3.7 7 | scipy==1.4.1 8 | pytest==5.4.1 9 | sphinx==3.0.1 10 | algopy==0.5.7 11 | numdifftools==0.9.39 12 | six==1.14.0 13 | future==0.18.2 14 | pylint==2.4.4 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # content of setup.cfg 2 | [tool:pytest] 3 | norecursedirs = .git build dist 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | To install UncertaintyWrapper from source, a cloned repository or an archive, 3 | use ``python setup.py install``. 4 | 5 | Use ``python setup.py bdist_wheel`` to make distribute as a wheel.bdist_wheel. 6 | """ 7 | 8 | # try to use setuptools if available, otherwise fall back on distutils 9 | try: 10 | from setuptools import setup 11 | except ImportError: 12 | from distutils.core import setup 13 | from uncertainty_wrapper import ( 14 | __VERSION__, __AUTHOR__, __EMAIL__, __URL__ 15 | ) 16 | 17 | import os 18 | 19 | README = 'README.rst' 20 | try: 21 | with open(os.path.join(os.path.dirname(__file__), README), 'r') as readme: 22 | README = readme.read() 23 | except IOError: 24 | pass 25 | 26 | setup(name='UncertaintyWrapper', 27 | version=__VERSION__, 28 | description='Uncertainty wrapper using estimated Jacobian', 29 | long_description=README, 30 | author=__AUTHOR__, 31 | author_email=__EMAIL__, 32 | url=__URL__, 33 | packages=['uncertainty_wrapper', 'uncertainty_wrapper.tests'], 34 | requires=['numpy (>=1.8)'], 35 | install_requires=['numpy>=1.8'], 36 | package_data={'uncertainty_wrapper': [ 37 | 'docs/conf.py', 'docs/*.rst', 'docs/Makefile', 'docs/make.bat' 38 | ]}) 39 | -------------------------------------------------------------------------------- /uncertainty_wrapper/__init__.py: -------------------------------------------------------------------------------- 1 | """UncertaintyWrapper""" 2 | 3 | __VERSION__ = '0.4.2' 4 | __RELEASE__ = u"Terreneuvian Series" 5 | __URL__ = u'https://github.com/BreakingBytes/UncertaintyWrapper' 6 | __AUTHOR__ = u"Mark Mikofski" 7 | __EMAIL__ = u'bwana.marko@yahoo.com' 8 | 9 | try: 10 | from uncertainty_wrapper.core import unc_wrapper, unc_wrapper_args, jflatten 11 | except (ImportError, ModuleNotFoundError): 12 | pass 13 | else: 14 | __all__ = ['unc_wrapper', 'unc_wrapper_args', 'jflatten'] 15 | -------------------------------------------------------------------------------- /uncertainty_wrapper/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Uncertainty wrapper calculates uncertainties of wrapped functions using 3 | central finite difference approximation of the Jacobian matrix. 4 | 5 | .. math:: 6 | 7 | \\frac{\\partial f_i}{\\partial x_{j,k}} 8 | 9 | Uncertainty of the output is propagated using first order terms of a Taylor 10 | series expansion around :math:`x`. 11 | 12 | 13 | .. math:: 14 | 15 | dF_{ij} = J_{ij} * S_{x_i, x_j} * J_{ij}^{T} 16 | 17 | Diagonals of :math:`dF_{ij}` are standard deviations squared. 18 | 19 | SunPower Corp. (c) 2016 20 | """ 21 | from __future__ import division 22 | 23 | from builtins import zip 24 | from builtins import range 25 | from past.builtins import basestring 26 | from past.utils import old_div 27 | from functools import wraps 28 | import numpy as np 29 | import logging 30 | from multiprocessing import Pool 31 | from scipy.sparse import csr_matrix 32 | 33 | logging.basicConfig() 34 | LOGGER = logging.getLogger(__name__) 35 | DELTA = np.finfo(float).eps ** (1.0 / 3.0) / 2.0 36 | 37 | 38 | def prop_unc(jc): 39 | """ 40 | Propagate uncertainty. 41 | 42 | :param jc: the Jacobian and covariance matrix 43 | :type jc: sequence 44 | 45 | This method is mainly designed to be used as the target for a 46 | multiprocessing pool. 47 | """ 48 | j, c = jc 49 | return np.dot(np.dot(j, c), j.T) 50 | 51 | 52 | def partial_derivative(f, x, n, nargs, delta=DELTA): 53 | """ 54 | Calculate partial derivative using central finite difference approximation. 55 | 56 | :param f: function 57 | :param x: sequence of arguments 58 | :param n: index of argument derivateve is with respect to 59 | :param nargs: number of arguments 60 | :param delta: optional step size, default is :math:`\\epsilon^{1/3}` where 61 | :math:`\\epsilon` is machine precision 62 | """ 63 | dx = np.zeros((nargs, len(x[n]))) 64 | # scale delta by (|x| + 1.0) to avoid noise from machine precision 65 | dx[n] += np.where(x[n], x[n] * delta, delta) 66 | # apply central difference approximation 67 | x_dx = list(zip(*[xi + (dxi, -dxi) for xi, dxi in zip(x, dx)])) 68 | return old_div((f(x_dx[0]) - f(x_dx[1])), dx[n]) / 2.0 69 | 70 | 71 | # TODO: make this a class, add DELTA as class variable and flatten as method 72 | def jacobian(func, x, nf, nobs, *args, **kwargs): 73 | """ 74 | Estimate Jacobian matrices :math:`\\frac{\\partial f_i}{\\partial x_{j,k}}` 75 | where :math:`k` are independent observations of :math:`x`. 76 | 77 | The independent variable, :math:`x`, must be a numpy array with exactly 2 78 | dimensions. The first dimension is the number of independent arguments, 79 | and the second dimensions is the number of observations. 80 | 81 | The function must return a Numpy array with exactly 2 dimensions. The first 82 | is the number of returns and the second dimension corresponds to the number 83 | of observations. If the input argument is 2-D then the output should also 84 | be 2-D 85 | 86 | Constant arguments can be passed as additional positional arguments or 87 | keyword arguments. If any constant argument increases the number of 88 | observations of the return value, tile the input arguments to match. 89 | 90 | Use :func:`numpy.atleast_2d` or :func:`numpy.reshape` to get the 91 | correct dimensions for scalars. 92 | 93 | :param func: function 94 | :param x: independent variables grouped by observation 95 | :param nf: number of return in output (1st dimension) 96 | :param nobs: number of observations in output (2nd dimension) 97 | :return: Jacobian matrices for each observation 98 | """ 99 | nargs = len(x) # degrees of freedom 100 | f = lambda x_: func(x_, *args, **kwargs) 101 | j = np.zeros((nargs, nf, nobs)) # matrix of zeros 102 | for n in range(nargs): 103 | j[n] = partial_derivative(f, x, n, nargs) 104 | # better to transpose J once than transpose partial derivative each time 105 | # j[:,:,n] = df.T 106 | return j.T 107 | 108 | 109 | def jflatten(j): 110 | """ 111 | Flatten 3_D Jacobian into 2-D. 112 | """ 113 | nobs, nf, nargs = j.shape 114 | nrows, ncols = nf * nobs, nargs * nobs 115 | jflat = np.zeros((nrows, ncols)) 116 | for n in range(nobs): 117 | r, c = n * nf, n * nargs 118 | jflat[r:(r + nf), c:(c + nargs)] = j[n] 119 | return jflat 120 | 121 | 122 | def jtosparse(j): 123 | """ 124 | Generate sparse matrix coordinates from 3-D Jacobian. 125 | """ 126 | data = j.flatten().tolist() 127 | nobs, nf, nargs = j.shape 128 | indices = list(zip(*[(r, c) for n in range(nobs) 129 | for r in range(n * nf, (n + 1) * nf) 130 | for c in range(n * nargs, (n + 1) * nargs)])) 131 | return csr_matrix((data, indices), shape=(nobs * nf, nobs * nargs)) 132 | 133 | 134 | # TODO: allow user to supply analytical Jacobian, only fall back on Jacob 135 | # estimate if jac is None 136 | 137 | 138 | def unc_wrapper_args(*covariance_keys): 139 | """ 140 | Wrap function, calculate its Jacobian and calculate the covariance of the 141 | outputs given the covariance of the specified inputs. 142 | 143 | :param covariance_keys: indices and names of arguments corresponding to 144 | covariance 145 | :return: wrapped function bound to specified covariance keys 146 | 147 | This is the outer uncertainty wrapper that allows you to specify the 148 | arguments in the original function that correspond to the covariance. The 149 | inner wrapper takes the original function to be wrapped. :: 150 | 151 | def f(a, b, c, d, kw1='foo', *args, **kwargs): 152 | pass 153 | 154 | # arguments a, c, d and kw1 correspond to the covariance matrix 155 | f_wrapped = unc_wrapper_args(0, 2, 3, 'kw1')(f) 156 | 157 | cov = np.array([[0.0001, 0., 0., 0.], [0., 0.0001, 0., 0.], 158 | [0., 0., 0.0001, 0.], [0., 0., 0., 0.0001]) 159 | y, cov, jac = f_wrapped(a, b, c, d, kw1='bar', __covariance__=cov) 160 | 161 | The covariance keys can be indices of positional arguments or the names of 162 | keywords argument used in calling the function. If no covariance keys are 163 | specified then the arguments that correspond to the covariance shoud be 164 | grouped into a sequence. If ``None`` is anywhere in ``covariance_keys`` then 165 | all of the arguments will be used to calculate the Jacobian. 166 | 167 | The covariance matrix must be a symmetrical matrix with positive numbers on 168 | the diagonal that correspond to the square of the standard deviation, second 169 | moment around the mean or root-mean-square(RMS) of the function with respect 170 | to the arguments specified as covariance keys. The other elements are the 171 | covariances corresponding to the arguments intersecting at that element. 172 | Pass the covariance matrix with the keyword ``__covariance__`` and it will 173 | be popped from the dictionary of keyword arguments provided to the wrapped 174 | function. 175 | 176 | The wrapped function will return the evaluation of the original function, 177 | its Jacobian, which is the sensitivity of the return output to each 178 | argument specified as a covariance key and the covariance propagated using 179 | the first order terms of a Taylor series expansion around the arguments. 180 | 181 | An optional keyword argument ``__method__`` can also be passed to the 182 | wrapped function (not the wrapper) that specifies the method used to 183 | calculate the dot product. The default method is ``'loop'``. The other 184 | methods are ``'dense'``, ``'sparse'`` and ``'pool'``. 185 | 186 | If the arguments specified as covariance keys are arrays, they should all be 187 | the same size. These dimensions will be considered as separate observations. 188 | Another argument, not in the covariance keys, may also create observations. 189 | The resulting Jacobian will have dimensions of number of observations (nobs) 190 | by number of return output (nf) by number of covariance keys (nargs). The 191 | resulting covariance will be nobs x nf x nf. 192 | """ 193 | def wrapper(f): 194 | @wraps(f) 195 | def wrapped_function(*args, **kwargs): 196 | cov = kwargs.pop('__covariance__', None) # pop covariance 197 | method = kwargs.pop('__method__', 'loop') # pop covariance 198 | # covariance keys cannot be defaults, they must be in args or kwargs 199 | cov_keys = covariance_keys 200 | # convert args to kwargs by index 201 | kwargs.update({n: v for n, v in enumerate(args)}) 202 | args = () # empty args 203 | if None in cov_keys: 204 | # use all keys 205 | cov_keys = list(kwargs.keys()) 206 | # group covariance keys 207 | if len(cov_keys) > 0: 208 | # uses specified keys 209 | x = [np.atleast_1d(kwargs.pop(k)) for k in cov_keys] 210 | else: 211 | # arguments already grouped 212 | x = kwargs.pop(0) # use first argument 213 | # remaining args 214 | args_dict = {} 215 | 216 | def args_from_kwargs(kwargs_): 217 | """unpack positional arguments from keyword arguments""" 218 | # create mapping of positional arguments by index 219 | args_ = [(n, v) for n, v in kwargs_.items() 220 | if not isinstance(n, basestring)] 221 | # sort positional arguments by index 222 | idx, args_ = list(zip(*sorted(args_, key=lambda m: m[0]))) 223 | # remove args_ and their indices from kwargs_ 224 | args_dict_ = {n: kwargs_.pop(n) for n in idx} 225 | return args_, args_dict_ 226 | 227 | if kwargs: 228 | args, args_dict = args_from_kwargs(kwargs) 229 | 230 | def f_(x_, *args_, **kwargs_): 231 | """call original function with independent variables grouped""" 232 | args_dict_ = args_dict 233 | if cov_keys: 234 | kwargs_.update(args_dict_) 235 | kwargs_.update(list(zip(cov_keys, x_))) 236 | if kwargs_: 237 | args_, _ = args_from_kwargs(kwargs_) 238 | return np.array(f(*args_, **kwargs_)) 239 | # assumes independent variables already grouped 240 | return f(x_, *args_, **kwargs_) 241 | 242 | # evaluate function and Jacobian 243 | avg = f_(x, *args, **kwargs) 244 | # number of returns and observations 245 | if avg.ndim > 1: 246 | nf, nobs = avg.shape 247 | else: 248 | nf, nobs = avg.size, 1 249 | jac = jacobian(f_, x, nf, nobs, *args, **kwargs) 250 | # calculate covariance 251 | if cov is not None: 252 | # covariance must account for all observations 253 | # scale covariances by x squared in each direction 254 | if cov.ndim == 3: 255 | x = np.array([np.repeat(y, nobs) if len(y)==1 256 | else y for y in x]) 257 | LOGGER.debug('x:\n%r', x) 258 | cov = np.array([c * y * np.row_stack(y) 259 | for c, y in zip(cov, x.T)]) 260 | else: # x are all only one dimension 261 | x = np.asarray(x) 262 | cov = cov * x * x.T 263 | assert old_div(old_div(jac.size, nf), nobs) == old_div(cov.size, len(x)) 264 | cov = np.tile(cov, (nobs, 1, 1)) 265 | # propagate uncertainty using different methods 266 | if method.lower() == 'dense': 267 | j, c = jflatten(jac), jflatten(cov) 268 | cov = prop_unc((j, c)) 269 | # sparse 270 | elif method.lower() == 'sparse': 271 | j, c = jtosparse(jac), jtosparse(cov) 272 | cov = j.dot(c).dot(j.transpose()) 273 | cov = cov.todense() 274 | # pool 275 | elif method.lower() == 'pool': 276 | try: 277 | p = Pool() 278 | cov = np.array(p.map(prop_unc, list(zip(jac, cov)))) 279 | finally: 280 | p.terminate() 281 | # loop is the default 282 | else: 283 | cov = np.array([prop_unc((jac[o], cov[o])) 284 | for o in range(nobs)]) 285 | # dense and spares are flattened, unravel them into 3-D list of 286 | # observations 287 | if method.lower() in ['dense', 'sparse']: 288 | cov = np.array([ 289 | cov[(nf * o):(nf * (o + 1)), (nf * o):(nf * (o + 1))] 290 | for o in range(nobs) 291 | ]) 292 | # unpack returns for original function with ungrouped arguments 293 | if None in cov_keys or len(cov_keys) > 0: 294 | return tuple(avg.tolist() + [cov, jac]) 295 | # independent variables were already grouped 296 | return avg, cov, jac 297 | return wrapped_function 298 | return wrapper 299 | 300 | # short cut for functions with arguments already grouped 301 | unc_wrapper = unc_wrapper_args() 302 | unc_wrapper.__doc__ = "equivalent to unc_wrapper_args() with no arguments" 303 | unc_wrapper.__name__ = "unc_wrapper" 304 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/UncertaintyWrapper.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/UncertaintyWrapper.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/UncertaintyWrapper" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/UncertaintyWrapper" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/_static/IV-PV-jac-errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BreakingBytes/UncertaintyWrapper/633151dc76c1a3c8ccb38f35f30283f756e1193f/uncertainty_wrapper/docs/_static/IV-PV-jac-errors.png -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/_static/IV_and_PV_plots_with_uncertainty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BreakingBytes/UncertaintyWrapper/633151dc76c1a3c8ccb38f35f30283f756e1193f/uncertainty_wrapper/docs/_static/IV_and_PV_plots_with_uncertainty.png -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/_static/sp_2014_logo_black_orange_rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BreakingBytes/UncertaintyWrapper/633151dc76c1a3c8ccb38f35f30283f756e1193f/uncertainty_wrapper/docs/_static/sp_2014_logo_black_orange_rgb.png -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | 6 | Uncertainty Wrapper 7 | ------------------- 8 | 9 | .. automodule:: uncertainty_wrapper.core 10 | 11 | Step Size 12 | ~~~~~~~~~ 13 | 14 | .. autodata:: DELTA 15 | 16 | Partial Derivative 17 | ~~~~~~~~~~~~~~~~~~ 18 | 19 | .. autofunction:: partial_derivative 20 | 21 | Jacobian 22 | ~~~~~~~~ 23 | 24 | .. autofunction:: jacobian 25 | 26 | Flatten Jacobian 27 | ~~~~~~~~~~~~~~~~ 28 | 29 | .. autofunction:: jflatten 30 | 31 | Wrapper 32 | ~~~~~~~ 33 | 34 | .. autofunction:: unc_wrapper_args 35 | 36 | 37 | Wrapper Shortcut 38 | ```````````````` 39 | 40 | .. function:: unc_wrapper 41 | 42 | This is basically :func:`unc_wrapper_args()` with no argument which assumes that 43 | all independent arguments are already grouped together. 44 | 45 | Tests 46 | ----- 47 | 48 | .. automodule:: uncertainty_wrapper.tests.test_uncertainty_wrapper 49 | 50 | Test Uncertainty Wrapper 51 | ~~~~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | .. autofunction:: test_unc_wrapper 54 | 55 | Test simple exponential function with known derivative. Assert derivative is 56 | correct and that uncertainty is propagated correctly. 57 | 58 | Test IV curve 59 | ~~~~~~~~~~~~~ 60 | 61 | .. autofunction:: test_IV 62 | 63 | Test complex function with several operations, including :math:`exp`, :math:`log` 64 | and powers, with several input arguments and with several return values. Check 65 | Jacobian calculated with central finite difference approximation with automatic 66 | differentiation using AlgoPy. 67 | 68 | Test Solar Position 69 | ~~~~~~~~~~~~~~~~~~~ 70 | 71 | .. autofunction:: test_solpos 72 | 73 | Test function from a Python C/C++ extension. Check calcuated Jacobian with John 74 | D'Errico's ``numdifftools`` Python package (MATLAB ``derivest``). 75 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Uncertainty Wrapper documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Apr 05 13:02:16 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import alabaster 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) 23 | 24 | from uncertainty_wrapper import __VERSION__, __RELEASE__, __AUTHOR__ 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.intersphinx', 37 | 'sphinx.ext.coverage', 38 | 'sphinx.ext.mathjax', 39 | 'sphinx.ext.viewcode', 40 | 'alabaster' 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | #source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # General information about the project. 58 | project = u'Uncertainty Wrapper' 59 | copyright = u'2016, SunPower' 60 | author = __AUTHOR__ 61 | 62 | # The version info for the project you're documenting, acts as replacement for 63 | # |version| and |release|, also used in various other places throughout the 64 | # built documents. 65 | # 66 | # The short X.Y version. 67 | version = __VERSION__ 68 | # The full version, including alpha/beta/rc tags. 69 | release = __RELEASE__ 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | # 74 | # This is also used if you do content translation via gettext catalogs. 75 | # Usually you set "language" from the command line for these cases. 76 | language = None 77 | 78 | # There are two options for replacing |today|: either, you set today to some 79 | # non-false value, then it is used: 80 | #today = '' 81 | # Else, today_fmt is used as the format for a strftime call. 82 | #today_fmt = '%B %d, %Y' 83 | 84 | # List of patterns, relative to source directory, that match files and 85 | # directories to ignore when looking for source files. 86 | exclude_patterns = ['_build'] 87 | 88 | # The reST default role (used for this markup: `text`) to use for all 89 | # documents. 90 | #default_role = None 91 | 92 | # If true, '()' will be appended to :func: etc. cross-reference text. 93 | #add_function_parentheses = True 94 | 95 | # If true, the current module name will be prepended to all description 96 | # unit titles (such as .. function::). 97 | #add_module_names = True 98 | 99 | # If true, sectionauthor and moduleauthor directives will be shown in the 100 | # output. They are ignored by default. 101 | #show_authors = False 102 | 103 | # The name of the Pygments (syntax highlighting) style to use. 104 | pygments_style = 'sphinx' 105 | 106 | # A list of ignored prefixes for module index sorting. 107 | #modindex_common_prefix = [] 108 | 109 | # If true, keep warnings as "system message" paragraphs in the built documents. 110 | #keep_warnings = False 111 | 112 | # If true, `todo` and `todoList` produce output, else they produce nothing. 113 | todo_include_todos = False 114 | 115 | 116 | # -- Options for HTML output ---------------------------------------------- 117 | 118 | # The theme to use for HTML and HTML Help pages. See the documentation for 119 | # a list of builtin themes. 120 | html_theme = 'alabaster' 121 | 122 | # Theme options are theme-specific and customize the look and feel of a theme 123 | # further. For a list of options available for each theme, see the 124 | # documentation. 125 | html_theme_options = { 126 | 'logo': 'sp_2014_logo_black_orange_rgb.png', 127 | 'logo_name': True, 128 | 'description': 'Calculate uncertainty and sensitivity of any function', 129 | 'github_user': 'BreakingBytes', 130 | 'github_repo': 'UncertaintyWrapper', 131 | 'github_banner': True, 132 | 'travis_button': True, 133 | 'show_related': True 134 | } 135 | 136 | # Add any paths that contain custom themes here, relative to this directory. 137 | html_theme_path = [alabaster.get_path()] 138 | 139 | # The name for this set of Sphinx documents. If None, it defaults to 140 | # " v documentation". 141 | #html_title = None 142 | 143 | # A shorter title for the navigation bar. Default is the same as html_title. 144 | #html_short_title = None 145 | 146 | # The name of an image file (relative to this directory) to place at the top 147 | # of the sidebar. 148 | #html_logo = None 149 | 150 | # The name of an image file (within the static path) to use as favicon of the 151 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 152 | # pixels large. 153 | html_favicon = 'favicon.ico' 154 | 155 | # Add any paths that contain custom static files (such as style sheets) here, 156 | # relative to this directory. They are copied after the builtin static files, 157 | # so a file named "default.css" will overwrite the builtin "default.css". 158 | html_static_path = ['_static'] 159 | 160 | # Add any extra paths that contain custom files (such as robots.txt or 161 | # .htaccess) here, relative to this directory. These files are copied 162 | # directly to the root of the documentation. 163 | #html_extra_path = [] 164 | 165 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 166 | # using the given strftime format. 167 | #html_last_updated_fmt = '%b %d, %Y' 168 | 169 | # If true, SmartyPants will be used to convert quotes and dashes to 170 | # typographically correct entities. 171 | #html_use_smartypants = True 172 | 173 | # Custom sidebar templates, maps document names to template names. 174 | html_sidebars = { 175 | '**': [ 176 | 'about.html', 177 | 'navigation.html', 178 | 'searchbox.html', 179 | 'relations.html' 180 | ] 181 | } 182 | 183 | # Additional templates that should be rendered to pages, maps page names to 184 | # template names. 185 | #html_additional_pages = {} 186 | 187 | # If false, no module index is generated. 188 | #html_domain_indices = True 189 | 190 | # If false, no index is generated. 191 | #html_use_index = True 192 | 193 | # If true, the index is split into individual pages for each letter. 194 | #html_split_index = False 195 | 196 | # If true, links to the reST sources are added to the pages. 197 | #html_show_sourcelink = True 198 | 199 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 200 | #html_show_sphinx = True 201 | 202 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 203 | #html_show_copyright = True 204 | 205 | # If true, an OpenSearch description file will be output, and all pages will 206 | # contain a tag referring to it. The value of this option must be the 207 | # base URL from which the finished HTML is served. 208 | #html_use_opensearch = '' 209 | 210 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 211 | #html_file_suffix = None 212 | 213 | # Language to be used for generating the HTML full-text search index. 214 | # Sphinx supports the following languages: 215 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 216 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 217 | #html_search_language = 'en' 218 | 219 | # A dictionary with options for the search language support, empty by default. 220 | # Now only 'ja' uses this config value 221 | #html_search_options = {'type': 'default'} 222 | 223 | # The name of a javascript file (relative to the configuration directory) that 224 | # implements a search results scorer. If empty, the default will be used. 225 | #html_search_scorer = 'scorer.js' 226 | 227 | # Output file base name for HTML help builder. 228 | htmlhelp_basename = 'UncertaintyWrapperdoc' 229 | 230 | # -- Options for LaTeX output --------------------------------------------- 231 | 232 | latex_elements = { 233 | # The paper size ('letterpaper' or 'a4paper'). 234 | #'papersize': 'letterpaper', 235 | 236 | # The font size ('10pt', '11pt' or '12pt'). 237 | #'pointsize': '10pt', 238 | 239 | # Additional stuff for the LaTeX preamble. 240 | #'preamble': '', 241 | 242 | # Latex figure (float) alignment 243 | #'figure_align': 'htbp', 244 | } 245 | 246 | # Grouping the document tree into LaTeX files. List of tuples 247 | # (source start file, target name, title, 248 | # author, documentclass [howto, manual, or own class]). 249 | latex_documents = [ 250 | (master_doc, 'UncertaintyWrapper.tex', u'Uncertainty Wrapper Documentation', 251 | u'Mark Mikofski', 'manual'), 252 | ] 253 | 254 | # The name of an image file (relative to this directory) to place at the top of 255 | # the title page. 256 | #latex_logo = None 257 | 258 | # For "manual" documents, if this is true, then toplevel headings are parts, 259 | # not chapters. 260 | #latex_use_parts = False 261 | 262 | # If true, show page references after internal links. 263 | #latex_show_pagerefs = False 264 | 265 | # If true, show URL addresses after external links. 266 | #latex_show_urls = False 267 | 268 | # Documents to append as an appendix to all manuals. 269 | #latex_appendices = [] 270 | 271 | # If false, no module index is generated. 272 | #latex_domain_indices = True 273 | 274 | 275 | # -- Options for manual page output --------------------------------------- 276 | 277 | # One entry per manual page. List of tuples 278 | # (source start file, name, description, authors, manual section). 279 | man_pages = [ 280 | (master_doc, 'uncertaintywrapper', u'Uncertainty Wrapper Documentation', 281 | [author], 1) 282 | ] 283 | 284 | # If true, show URL addresses after external links. 285 | #man_show_urls = False 286 | 287 | 288 | # -- Options for Texinfo output ------------------------------------------- 289 | 290 | # Grouping the document tree into Texinfo files. List of tuples 291 | # (source start file, target name, title, author, 292 | # dir menu entry, description, category) 293 | texinfo_documents = [ 294 | (master_doc, 'UncertaintyWrapper', u'Uncertainty Wrapper Documentation', 295 | author, 'UncertaintyWrapper', 'One line description of project.', 296 | 'Miscellaneous'), 297 | ] 298 | 299 | # Documents to append as an appendix to all manuals. 300 | #texinfo_appendices = [] 301 | 302 | # If false, no module index is generated. 303 | #texinfo_domain_indices = True 304 | 305 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 306 | #texinfo_show_urls = 'footnote' 307 | 308 | # If true, do not generate a @detailmenu in the "Top" node's menu. 309 | #texinfo_no_detailmenu = False 310 | 311 | 312 | # Example configuration for intersphinx: refer to the Python standard library. 313 | intersphinx_mapping = { 314 | 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 315 | 'python': ('https://docs.python.org/', None) 316 | } 317 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BreakingBytes/UncertaintyWrapper/633151dc76c1a3c8ccb38f35f30283f756e1193f/uncertainty_wrapper/docs/favicon.ico -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | Getting Started 4 | =============== 5 | You can wrap any Python callable using :func:`~uncertainty_wrapper.core.unc_wrapper` 6 | or :func:`~uncertainty_wrapper.core.unc_wrapper_args`, that does the following: 7 | 8 | * looks for ``__covariance__`` as a keyword argument 9 | * calculates the Jacobian and covariance matrices 10 | * appends the Jacobian and covariance matrices to the return values. 11 | 12 | However you may need to manipulate the input arguments to match the expected 13 | :ref:`API`. 14 | 15 | Simple Example 16 | -------------- 17 | 18 | This simple example using two input arguments and two return values is from 19 | `Uncertainty Benchmarks `_:: 20 | 21 | from uncertainty_wrapper import unc_wrapper 22 | # unc_wrapper expects input args to be 2-D NumPy arrays 23 | import numpy as np 24 | 25 | # simple test functions with multiple input arguments and return 26 | # values and whose derivatives are easily derived. 27 | NARGS = 2 # number of input arguments 28 | F = lambda x: np.array([(x[0] + x[1]) ** 2, x[1] ** 2 - x[0] ** 2]) 29 | G = lambda x: np.array([(2 * (x[0] + x[1]), 2 * (x[1] + x[0])), 30 | (-2 * x[0], 2 * x[1])]) 31 | AVG = np.random.rand(NARGS) * 10. # some test input arguments 32 | # randomly generated symmetrical covariance matrix 33 | COV = np.random.rand(NARGS, NARGS) / 10. 34 | COV = (COV + COV.T) / 2.0 # must be symmetrical 35 | 36 | @unc_wrapper 37 | def example(avg=AVG, f=F): 38 | """Example of unc_wrapper usage""" 39 | avg = f(avg) 40 | return avg 41 | 42 | # uses @wraps from functools so docstrings should work 43 | print example.__doc__ 44 | # Example of unc_wrapper usage 45 | 46 | # reshape args as row stack since there is only one observation and 47 | # unc_wrapper expects there to be multiple observations 48 | AVG = AVG.reshape((NARGS, 1)) 49 | print AVG 50 | # [[ 1.80222955] 51 | # [ 5.62897685]] 52 | 53 | # the wrapped example now takes a second argument called 54 | # __covariance__ 55 | print COV 56 | # [[ 0.06798386 0.05971218] 57 | # [ 0.05971218 0.09359305]] 58 | 59 | retval = example(AVG, F, __covariance__=COV) 60 | # and appends covariance and Jacobian matrices to the return values 61 | avg, cov, jac = retval 62 | 63 | # squeeze out extra dimension we added since there's only one 64 | # observation and display results 65 | avg = avg.squeeze() 66 | print avg 67 | # [ 55.22282851 28.43734901] 68 | 69 | print cov 70 | # [[ 1164.60425675 790.5452895 ] 71 | # [ 415.45944116 294.07938566]] 72 | 73 | print jac 74 | # [[ 14.86241279 14.86241279] 75 | # [ -3.6044591 11.2579537 ]] 76 | 77 | # compare to analytical derivatives 78 | print G(AVG).squeeze() 79 | # [[ 14.86241279 14.86241279] 80 | # [ -3.6044591 11.2579537 ]] 81 | 82 | More Examples 83 | ------------- 84 | 85 | * :ref:`complex-example` 86 | * :ref:`python-extension-example-with-units` 87 | 88 | The next sections contain more examples cover more advanced usage. Uncertanty 89 | Wrapper can consider multiple inputs arguments and return values. It can also be 90 | used with Python extensions written in c/c++. Finally 91 | :func:`~uncertainty_wrapper.core.unc_wrapper_args` can be used to specify which 92 | args are indepndent to include in the covariance and Jaconbian. 93 | 94 | Announcement 95 | ------------ 96 | Previous versions of Uncertainty Wrapper have worked with Pint's units wrapper 97 | to automatically check units, but unfortunately this is no longer supported. 98 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Uncertainty Wrapper documentation master file, created by 2 | sphinx-quickstart on Tue Apr 05 13:02:16 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Uncertainty Wrapper's documentation! 7 | =============================================== 8 | 9 | Version: |version| (|release|) 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | getting_started 17 | more_examples/complex_example 18 | more_examples/python_extension_example_with_units 19 | api 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | 28 | .. include:: ../../README.rst 29 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\UncertaintyWrapper.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\UncertaintyWrapper.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/more_examples/complex_example.rst: -------------------------------------------------------------------------------- 1 | .. _complex-example: 2 | 3 | Complex Example 4 | --------------- 5 | 6 | A more complex example from the :mod:`~uncertainty_wrapper.tests.test_uncertainty_wrapper` 7 | module called :func:`~uncertainty_wrapper.tests.test_uncertainty_wrapper.test_IV`, 8 | includes combinations of several exponential and power operations. It contains 9 | 9 input arguments, there 126 observations of each corresponding to different 10 | voltages and there are 3 return values. The calculated uncertainty using a 1% 11 | standard deviation (square root of variance) for all 9 inputs is shown below. 12 | 13 | .. image:: /_static/IV_and_PV_plots_with_uncertainty.png 14 | 15 | The test compares the derivatives calculated using central finite difference 16 | approximation with an analytical calculation from 0.3[V] to 0.6[V]. Below 0.3[V] 17 | the approximations deviate from the analytical for 18 | :math:`\frac{\partial I_{sc}}{\partial I_{sat_{1,0}}}`, 19 | :math:`\frac{\partial I_{sc}}{\partial I_{sat_2}}` and 20 | :math:`\frac{\partial I_{sc}}{\partial E_g}` while all other independent 21 | variables are consistently below 10e-7. The analytical derivatives are propagated 22 | using `AlgoPy `_, an automatic differentiation 23 | package, which requires rewriting all NumPy operations like :math:`exp` using 24 | AlgoPy. This makes it impractical for use in most models, but still useful for 25 | testing. 26 | 27 | .. image:: /_static/IV-PV-jac-errors.png 28 | -------------------------------------------------------------------------------- /uncertainty_wrapper/docs/more_examples/python_extension_example_with_units.rst: -------------------------------------------------------------------------------- 1 | .. _python-extension-example-with-units: 2 | 3 | Python Extension Example with Units 4 | ----------------------------------- 5 | 6 | Often Python packages contain extensions in C/C++ which can't be tested using 7 | automatic differentiation. The Numdidfftools is an alternative package that can 8 | calculate derivatives more accurately than the central finite difference 9 | approximation. 10 | 11 | An example of using :func:`~uncertainty_wrapper.core.unc_wrapper_args` with a 12 | Python extension written in C/C++ is in the tests called 13 | :func:`~uncertainty_wrapper.tests.test_uncertainty_wrapper.test_solpos`. This 14 | test using C/C++ code from NREL that is called using Python ``ctypes`` module. 15 | 16 | This example also demonstrates calculating covariance for multiple 17 | observations using :func:`~uncertainty_wrapper.core.unc_wrapper_args` to 18 | specify the indices of the positional arguments which corresond to the 19 | covariance matrix.:: 20 | 21 | @unc_wrapper_args(1, 2, 3, 4, 5) 22 | # indices specify positions of independent variables: 23 | # 1: latitude, 2: longitude, 3: pressure, 4: altitude, 5: temperature 24 | def spa(times, latitude, longitude, pressure, altitude, temperature): 25 | dataframe = pvlib.solarposition.spa_c(times, latitude, longitude, pressure, 26 | temperature) 27 | retvals = dataframe.to_records() 28 | zenith = retvals['apparent_zenith'] 29 | zenith = np.where(zenith < 90, zenith, np.nan) 30 | azimuth = retvals['azimuth'] 31 | return zenith, azimuth 32 | 33 | Then test it out. :: 34 | 35 | times = pd.DatetimeIndex(pd.date_range( 36 | start='2015/1/1', end='2015/1/2', freq='1h', tz=PST)).tz_convert(UTC) 37 | latitude, longitude = 37.0, -122.0 # degrees 38 | pressure, temperature = 101325.0, 22.0 # Pa, degC 39 | altitude = 0.0 40 | # standard deviation of 1% assuming normal distribution 41 | covariance = np.diag([0.0001] * 5) 42 | ze, az, cov, jac = spa(times, latitude, longitude, pressure, altitude, 43 | temperature, __covariance__=covariance) 44 | 45 | The results are:: 46 | 47 | >>> ze 48 | 54 | 55 | >>> az 56 | 62 | 63 | Note: previous versions of uncertainty wrapper worked with Pint to check 64 | units, but unfortunatley this is no longer supported. :: 65 | 66 | >>> idx = 8 # covariance at 8AM 67 | >>> times[idx] 68 | Timestamp('2015-01-01 08:00:00-0800', tz='US/Pacific', offset='H') 69 | 70 | >>> nf = 2 # number of dependent variables: [ze, az] 71 | >>> print cov[(nf * idx):(nf * (idx + 1)), (nf * idx):(nf * (idx + 1))] 72 | [[ 0.66082282, -0.61487518], 73 | [-0.61487518, 0.62483904]] 74 | 75 | >>> print np.sqrt(cov[(nf * idx), (nf * idx)]) / ze[idx] # standard deviation 76 | 0.0096710802029002577 77 | 78 | This tells us that the standard deviation of the zenith is 1% if the input has a standard deviation 79 | of 1%. That's reasonable. 80 | 81 | >>> nargs = 5 # number of independent args 82 | >>> jac[nf*(idx-1):nf*idx, nargs*(idx-1):nargs*idx] # Jacobian at 8AM 83 | [[ 5.56075143e-01, -6.44623321e-01, -1.42364184e-06, 0.00000000e+00, 1.06672083e-10], 84 | [ 8.29163154e-02, 6.47436098e-01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]] 85 | 86 | This also tells that zenith is more sensitive to latitude and longitude than pressure or temperature 87 | and more sensitive to latitude than azimuth is. 88 | 89 | Perhaps the most interesting outcome is the negative covariance between Zenith and Azimuth. From 90 | `Wikipedia `_ 91 | 92 | ... when the greater values of one variable mainly correspond to the lesser values of the other, 93 | the covariance is negative. 94 | 95 | In other words when the error in Zenith increases, the error in Azimuth decreases. This is not 96 | uncommon but it's not always intuitively obvious; we generally think that to get the largest error 97 | we should choose the largest errors for all independent variables. 98 | 99 | -------------------------------------------------------------------------------- /uncertainty_wrapper/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests 3 | 4 | SunPower Corp. (c) 2016 5 | """ 6 | 7 | from nose.tools import ok_ 8 | import numpy as np 9 | from uncertainty_wrapper.core import ( 10 | unc_wrapper, unc_wrapper_args, jflatten, logging 11 | ) 12 | from scipy.constants import Boltzmann as KB, elementary_charge as QE 13 | import pytz 14 | #import pint 15 | from matplotlib import pyplot as plt 16 | import pandas as pd 17 | import pvlib 18 | 19 | 20 | PST = pytz.timezone('US/Pacific') 21 | UTC = pytz.UTC 22 | T0 = 298.15 # [K] reference temperature 23 | __all__ = ['ok_', 'np', 'pd', 'unc_wrapper', 'unc_wrapper_args', 'jflatten', 24 | 'logging', 'KB', 'QE', 'plt', 'pvlib', 'PST', 'UTC', 'T0'] 25 | -------------------------------------------------------------------------------- /uncertainty_wrapper/tests/test_algopy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test AlgoPy and numdifftools 3 | """ 4 | from __future__ import division 5 | 6 | from past.utils import old_div 7 | from algopy import UTPM, exp, log, sqrt, zeros 8 | import numdifftools as nd 9 | from uncertainty_wrapper.tests import KB, QE, np, pvlib, T0 10 | 11 | 12 | def IV_algopy(x, Vd): 13 | """ 14 | IV curve implemented using algopy instead of numpy 15 | """ 16 | nobs = x.shape[1] 17 | out = zeros((3, nobs), dtype=x) 18 | Ee, Tc, Rs, Rsh, Isat1_0, Isat2, Isc0, alpha_Isc, Eg = x 19 | Vt = old_div(Tc * KB, QE) 20 | Isc = Ee * Isc0 * (1.0 + (Tc - T0) * alpha_Isc) 21 | Isat1 = ( 22 | Isat1_0 * (old_div(Tc ** 3.0, T0 ** 3.0)) * 23 | exp(old_div(Eg * QE, KB) * (1.0 / T0 - 1.0 / Tc)) 24 | ) 25 | Vd_sc = Isc * Rs # at short circuit Vc = 0 26 | Id1_sc = Isat1 * (exp(old_div(Vd_sc, Vt)) - 1.0) 27 | Id2_sc = Isat2 * (exp(Vd_sc / 2.0 / Vt) - 1.0) 28 | Ish_sc = old_div(Vd_sc, Rsh) 29 | Iph = Isc + Id1_sc + Id2_sc + Ish_sc 30 | Id1 = Isat1 * (exp(old_div(Vd, Vt)) - 1.0) 31 | Id2 = Isat2 * (exp(Vd / 2.0 / Vt) - 1.0) 32 | Ish = old_div(Vd, Rsh) 33 | Ic = Iph - Id1 - Id2 - Ish 34 | Vc = Vd - Ic * Rs 35 | out[0] = Ic 36 | out[1] = Vc 37 | out[2] = Ic * Vc 38 | return out 39 | 40 | 41 | def IV_algopy_jac (Ee, Tc, Rs, Rsh, Isat1_0, Isat2, Isc0, alpha_Isc, Eg, Vd): 42 | """ 43 | Calculate Jacobian of IV curve using AlgoPy 44 | 45 | :param Ee: [suns] effective irradiance 46 | :param Tc: [C] cell temperature 47 | :param Rs: [ohms] series resistance 48 | :param Rsh: [ohms] shunt resistance 49 | :param Isat1_0: [A] saturation current of first diode at STC 50 | :param Isat2: [A] saturation current of second diode 51 | :param Isc0: [A] short circuit current at STC 52 | :param alpha_Isc: [1/K] short circuit current temperature coefficient 53 | :param Eg: [eV] band gap 54 | :param Vd: [V] diode voltages 55 | :return: Jacobian :math:`\\frac{\\partial f_i}{\\partial x_{j,k}}` 56 | where :math:`k` are independent observations of :math:`x` 57 | """ 58 | x = UTPM.init_jacobian([ 59 | Ee, Tc, Rs, Rsh, Isat1_0, Isat2, Isc0, alpha_Isc, Eg 60 | ]) 61 | return UTPM.extract_jacobian(IV_algopy(x, Vd)) 62 | 63 | 64 | 65 | def spa(times, latitude, longitude, pressure, altitude, temperature): 66 | dataframe = pvlib.solarposition.spa_c(times, latitude, longitude, pressure, 67 | temperature) 68 | retvals = dataframe.to_records() 69 | zenith = retvals['apparent_zenith'] 70 | zenith = np.where(zenith < 90, zenith, np.nan) 71 | azimuth = retvals['azimuth'] 72 | return zenith, azimuth 73 | 74 | 75 | def solpos_nd_jac(times, latitude, longitude, pressure, altitude, temperature): 76 | """ 77 | 78 | :param times: timestamps 79 | :param latitude: [deg] latitude 80 | :param longitude: [deg] longitude 81 | :param pressure: [mbar] pressure 82 | :param altitude: [m] elevation above sea level 83 | :param temperature: [C] ambient temperature 84 | :return: Jacobian estimated using ``numdifftools`` 85 | """ 86 | 87 | def f(x, times): 88 | latitude, longitude, pressure, altitude, temperature = x 89 | return np.array(spa(times, latitude, longitude, pressure, altitude, 90 | temperature)) 91 | 92 | j = nd.Jacobian(f) 93 | x = np.array([latitude, longitude, pressure, altitude, temperature]) 94 | return j(x, times).squeeze() 95 | -------------------------------------------------------------------------------- /uncertainty_wrapper/tests/test_uncertainty_wrapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for :func:`~uncertainty_wrapper.unc_wrapper` and 3 | :func:`~uncertainty_wrapper.unc_wrapper_args` 4 | """ 5 | from __future__ import division 6 | 7 | # import ok_, np, pd,unc_wrapper, unc_wrapper_args, KB, QE, 8 | # pvlib, plt, UREG, PST and LOGGER from .tests 9 | from builtins import range 10 | from past.utils import old_div 11 | from uncertainty_wrapper.tests import * 12 | from uncertainty_wrapper.tests.test_algopy import IV_algopy_jac, solpos_nd_jac 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | LOGGER.setLevel(logging.DEBUG) 16 | 17 | 18 | def test_unc_wrapper(): 19 | """ 20 | Test uncertainty wrapper with grouped arguments. 21 | """ 22 | x, cov = np.array([[1.0]]), np.array([[0.1]]) 23 | 24 | @unc_wrapper 25 | def f(y): 26 | return np.exp(y) 27 | 28 | avg, var, jac = f(x, __covariance__=cov, __method__='dense') 29 | LOGGER.debug("average = %g", avg) 30 | LOGGER.debug("variance = %g", var) 31 | ok_(np.isclose(avg, np.exp(x))) 32 | ok_(np.isclose(var, cov * np.exp(x) ** 2)) 33 | ok_(np.isclose(jac, np.exp(x))) 34 | return avg, var, jac 35 | 36 | 37 | def test_unc_wrapper_args(): 38 | """ 39 | Test uncertainty wrapper with ungrouped arguments. 40 | """ 41 | x, cov = 1.0, np.array([[0.1]]) 42 | 43 | @unc_wrapper_args(None) 44 | def f(y): 45 | return np.exp(y) 46 | 47 | avg, var, jac = f(x, __covariance__=cov, __method__='dense') 48 | LOGGER.debug("average = %g", avg) 49 | LOGGER.debug("variance = %g", var) 50 | ok_(np.isclose(avg, np.exp(x))) 51 | ok_(np.isclose(var, cov * np.exp(x) ** 2)) 52 | ok_(np.isclose(jac, np.exp(x))) 53 | return avg, var, jac 54 | 55 | 56 | def test_multiple_observations(): 57 | """ 58 | Test multiple observations. 59 | """ 60 | x, cov = [1.0, 1.0], np.array([[[0.1]], [[0.1]]]) 61 | 62 | @unc_wrapper_args(None) 63 | def f(y): 64 | return np.exp(y).reshape(1, -1) 65 | 66 | avg, var, jac = f(x, __covariance__=cov, __method__='dense') 67 | LOGGER.debug("average = %r", avg) 68 | LOGGER.debug("variance = %r", var) 69 | ok_(np.allclose(avg, np.exp(x))) 70 | ok_(np.allclose(var, cov * np.exp(x) ** 2)) 71 | ok_(np.allclose(jac, np.exp(x))) 72 | return avg, var, jac 73 | 74 | 75 | def test_jagged(): 76 | """ 77 | Test jagged array. 78 | """ 79 | w, x = 1.0, [1.0, 1.0] 80 | cov = np.array([[[0.1, 0.], [0., 0.1]], 81 | [[0.1, 0.], [0., 0.1]]]) 82 | 83 | @unc_wrapper_args(None) 84 | def f(y, z): 85 | return (y * np.exp(z)).reshape(1, -1) 86 | 87 | avg, var, jac = f(w, x, __covariance__=cov, __method__='dense') 88 | LOGGER.debug("average = %r", avg) 89 | LOGGER.debug("jacobian = %r", jac) 90 | LOGGER.debug("variance = %r", var) 91 | ok_(np.allclose(avg, w * np.exp(x))) 92 | ok_(np.allclose(jac, [np.exp(x), np.exp(x)])) 93 | var_calc = np.concatenate( 94 | [[np.exp(x) * cov[:, 0, 0] + np.exp(x) * cov[:, 1, 0]], 95 | [np.exp(x) * cov[:, 0, 1] + np.exp(x) * cov[:, 1, 1]]], 0 96 | ).reshape(2, 1, 2) 97 | var_calc = var_calc[:, 0, 0] * np.exp(x) + var_calc[:, 0, 1] * np.exp(x) 98 | ok_(np.allclose(var, var_calc)) 99 | return avg, var, jac 100 | 101 | 102 | def IV(x, Vd): 103 | """ 104 | Calculate IV curve using 2-diode model. 105 | 106 | :param x: independent variables: 107 | :type x: sequence 108 | :param Vd: diode voltages 109 | :type Vd: :class:`numpy.ndarray` 110 | :returns: current [A], voltage [V] and power [W] 111 | :rtype: :class:`numpy.ndarray` 112 | 113 | The sequence of independent variables must contain the following in the 114 | specified order:: 115 | 116 | [Ee, Tc, Rs, Rsh, Isat1_0, Isat2, Isc0, alpha_Isc, Eg] 117 | 118 | This function is an example of grouping the independent variables together 119 | so that :class:~`uncertianty_wrapper.core.unc_wrapper` can be used. 120 | """ 121 | Ee, Tc, Rs, Rsh, Isat1_0, Isat2, Isc0, alpha_Isc, Eg = x 122 | Vt = old_div(Tc * KB, QE) 123 | Isc = Ee * Isc0 * (1.0 + (Tc - T0) * alpha_Isc) 124 | Isat1 = ( 125 | Isat1_0 * (old_div(Tc ** 3.0, T0 ** 3.0)) * 126 | np.exp(old_div(Eg * QE, KB) * (1.0 / T0 - 1.0 / Tc)) 127 | ) 128 | Vd_sc = Isc * Rs # at short circuit Vc = 0 129 | Id1_sc = Isat1 * (np.exp(old_div(Vd_sc, Vt)) - 1.0) 130 | Id2_sc = Isat2 * (np.exp(Vd_sc / 2.0 / Vt) - 1.0) 131 | Ish_sc = old_div(Vd_sc, Rsh) 132 | Iph = Isc + Id1_sc + Id2_sc + Ish_sc 133 | Id1 = Isat1 * (np.exp(old_div(Vd, Vt)) - 1.0) 134 | Id2 = Isat2 * (np.exp(Vd / 2.0 / Vt) - 1.0) 135 | Ish = old_div(Vd, Rsh) 136 | Ic = Iph - Id1 - Id2 - Ish 137 | Vc = Vd - Ic * Rs 138 | return np.array([Ic, Vc, Ic * Vc]) 139 | 140 | 141 | def Voc(x): 142 | """ 143 | Estimate open circuit voltage (Voc). 144 | """ 145 | Ee, Tc, Rs, Rsh, Isat1_0, Isat2, Isc0, alpha_Isc, Eg = x 146 | msg = ['Ee=%g[suns]','Tc=%g[K]','Rs=%g[ohms]','Rsh=%g[ohms]', 147 | 'Isat1_0=%g[A]','Isat2=%g[A]','Isc0=%g[A]','alpha_Isc=%g[]', 148 | 'Eg=%g[eV]'] 149 | LOGGER.debug('\n' + '\n'.join(msg) + '\n', *x) 150 | Vt = old_div(Tc * KB, QE) 151 | LOGGER.debug('Vt=%g[V]', Vt) 152 | Isc = Ee * Isc0 * (1.0 + (Tc - T0) * alpha_Isc) 153 | LOGGER.debug('Isc=%g[A]', Isc) 154 | Isat1 = ( 155 | Isat1_0 * (old_div(Tc ** 3.0, T0 ** 3.0)) * 156 | np.exp(old_div(Eg * QE, KB) * (1.0 / T0 - 1.0 / Tc)) 157 | ) 158 | LOGGER.debug('Isat1=%g[A]', Isat1) 159 | Vd_sc = Isc * Rs # at short circuit Vc = 0 160 | Id1_sc = Isat1 * (np.exp(old_div(Vd_sc, Vt)) - 1.0) 161 | Id2_sc = Isat2 * (np.exp(Vd_sc / 2.0 / Vt) - 1.0) 162 | Ish_sc = old_div(Vd_sc, Rsh) 163 | Iph = Isc + Id1_sc + Id2_sc + Ish_sc 164 | # estimate Voc 165 | delta = Isat2 ** 2.0 + 4.0 * Isat1 * (Iph + Isat1 + Isat2) 166 | return Vt * np.log(((-Isat2 + np.sqrt(delta)) / 2.0 / Isat1) ** 2.0) 167 | 168 | 169 | # constants for IV test 170 | RS = 0.004267236774264931 # [ohm] series resistance 171 | RSH = 10.01226369025448 # [ohm] shunt resistance 172 | ISAT1_0 = 2.286188161253440E-11 # [A] diode one saturation current 173 | ISAT2 = 1.117455042372326E-6 # [A] diode two saturation current 174 | ISC0 = 6.3056 # [A] reference short circuit current 175 | EE = 0.8 # [suns] effective irradiance 176 | TC = 323.15 # [K] cell temperature 177 | EG = 1.1 # [eV] c-Si band gap 178 | ALPHA_ISC = 0.0003551 # [1/degC] short circuit current temp co 179 | # [V] open circuit voltage 180 | VOC = Voc((EE, TC, RS, RSH, ISAT1_0, ISAT2, ISC0, ALPHA_ISC, EG)) 181 | assert np.isclose(VOC, 0.62816490891656673) 182 | LOGGER.debug('Voc = %g[V]', VOC) 183 | VD = np.arange(0, VOC, 0.005) # [V] diode voltages 184 | X = np.array([EE, TC, RS, RSH, ISAT1_0, ISAT2, ISC0, ALPHA_ISC, EG]) 185 | X = X.reshape(-1, 1) 186 | # covariance equivalent to standard deviation of 1.0 [%] 187 | COV = np.diag([1e-4] * X.size) 188 | X_algopy = X.repeat(VD.size, axis=1) 189 | 190 | 191 | def test_IV(method='sparse'): 192 | """ 193 | Test calculation of photovoltaic cell IV curve using 2-diode model and 194 | and compare Jacobian estimated by finite central difference to AlgoPy 195 | automatic differentiation. 196 | """ 197 | f = unc_wrapper(IV) 198 | pv, pv_cov, pv_jac = f(X, VD, __covariance__=COV, __method__=method) 199 | pv_cov = jflatten(pv_cov) 200 | pv_jac = jflatten(pv_jac) 201 | pv_jac_algopy = IV_algopy_jac(*X_algopy, Vd=VD) 202 | nVd = pv_jac_algopy.shape[1] 203 | for n in range(nVd // 2, nVd): 204 | irow, icol = 3 * n, 9 * n 205 | jrow, jcol = 3 + irow, 9 +icol 206 | pv_jac_n = pv_jac[irow:jrow, icol:jcol] 207 | pv_jac_algopy_n = pv_jac_algopy[:, n, n::VD.size] 208 | LOGGER.debug('pv jac at Vd = %g[V]:\n%r', VD[n], pv_jac_n) 209 | LOGGER.debug('pv jac AlgoPy at Vd = %g[V]:\n%r', VD[n], pv_jac_algopy_n) 210 | reldiff = old_div(pv_jac_n, pv_jac_algopy_n) - 1.0 211 | LOGGER.debug('reldiff at Vd = %g[V]:\n%r', VD[n], reldiff) 212 | resnorm = np.linalg.norm(reldiff) 213 | LOGGER.debug('resnorm at Vd = %g[V]: %r', VD[n], resnorm) 214 | rms = np.sqrt(np.sum(reldiff ** 2.0) / 9.0/ 3.0) 215 | LOGGER.debug('rms at Vd = %g[V]: %r', VD[n], rms) 216 | ok_(np.allclose(pv_jac_n, pv_jac_algopy_n, rtol=1e-3, atol=1e-3)) 217 | return pv, pv_cov, pv_jac, pv_jac_algopy 218 | 219 | 220 | def plot_pv(pv, pv_cov): 221 | """ 222 | IV and PV 2-axis plot with errorbars 223 | """ 224 | i_pv, v_pv, p_pv = pv 225 | i_stdev = np.sqrt(pv_cov.diagonal()[::3]) 226 | v_stdev = np.sqrt(pv_cov.diagonal()[1::3]) 227 | p_stdev = np.sqrt(pv_cov.diagonal()[2::3]) 228 | fig, ax1 = plt.subplots() 229 | ax1.errorbar(v_pv, i_pv, i_stdev, v_stdev) 230 | ax1.grid() 231 | ax1.set_xlabel('voltage [V]') 232 | ax1.set_ylabel('current [A]', color='b') 233 | ax1.set_ylim([0, 6.0]) 234 | ax2 = ax1.twinx() 235 | ax2.errorbar(v_pv, p_pv, p_stdev, v_stdev, fmt='r') 236 | ax2.grid() 237 | ax2.set_ylabel('power [W]', color='r') 238 | ax2.set_ylim([0, 3.0]) 239 | ax1.set_title('IV and PV curves') 240 | return fig 241 | 242 | 243 | def plot_pv_jac(pv_jac, pv_jac_algopy, Vd=VD): 244 | """ 245 | Log plot of relative difference between AlgoPy and central finite difference 246 | approximations 247 | 248 | :param pv_jac: central finite approximations 249 | :param pv_jac_algopy: automatic differentiation 250 | :param Vd: voltages 251 | :return: fig 252 | """ 253 | fn = ['Cell Current, Ic [A]', 'Cell Voltage, Vc [V]', 'Cell Power, Pc [W]'] 254 | fig, ax = plt.subplots(3, 1, **{'figsize': (8.0, 18.0)}) 255 | colorcycle = [ 256 | 'firebrick', 'goldenrod', 'sage', 'lime', 'seagreen', 'turquoise', 257 | 'royalblue', 'indigo', 'fuchsia' 258 | ] 259 | for m in range(3): 260 | for n in range(9): 261 | pv_jac_n = pv_jac[m::3, n::9].diagonal() 262 | pv_jac_algopy_n = pv_jac_algopy[ 263 | m, :, n * 126:(n + 1) * 126 264 | ].diagonal() 265 | reldiff = np.abs(old_div(pv_jac_n, pv_jac_algopy_n) - 1.0) 266 | ax[m].semilogy(Vd, reldiff, colorcycle[n]) 267 | ax[m].grid() 268 | ax[m].legend( 269 | ['Ee', 'Tc', 'Rs', 'Rsh', 'Isat1_0', 'Isat2', 'Isc0', 'alpha_Isc', 270 | 'Eg'], fancybox=True, framealpha=0.5 271 | ) 272 | ax[m].set_xlabel('Diode Voltage, Vd [V]') 273 | ax[m].set_ylabel('Relative Difference') 274 | ax[m].set_title(fn[m]) 275 | plt.tight_layout() 276 | return fig 277 | 278 | 279 | @unc_wrapper_args(1, 2, 3, 4, 5) 280 | # indices specify positions of independent variables: 281 | # 1: latitude, 2: longitude, 3: pressure, 4: altitude, 5: temperature 282 | def spa(times, latitude, longitude, pressure, altitude, temperature): 283 | """ 284 | Calculate solar position using PVLIB Cython wrapper around NREL SPA. 285 | 286 | :param times: list of times, must be localized as UTC 287 | :type times: :class:`pandas.DatetimeIndex` 288 | :param latitude: latitude [deg] 289 | :param latitude: longitude [deg] 290 | :param pressure: pressure [Pa] 291 | :param latitude: altitude [m] 292 | :param temperature: temperature [degC] 293 | :returns: zenith, azimuth 294 | """ 295 | dataframe = pvlib.solarposition.spa_c(times, latitude, longitude, pressure, 296 | temperature) 297 | retvals = dataframe.to_records() 298 | zenith = retvals['apparent_zenith'] 299 | zenith = np.where(zenith < 90, zenith, np.nan) 300 | azimuth = retvals['azimuth'] 301 | return zenith, azimuth 302 | 303 | 304 | def test_solpos(method='loop'): 305 | """ 306 | Test solar position calculation using NREL's SOLPOS. 307 | """ 308 | times = pd.DatetimeIndex( 309 | pd.date_range(start='2015/1/1', end='2015/1/2', freq='1h', 310 | tz=PST)).tz_convert(UTC) 311 | latitude, longitude = 37.0, -122.0 312 | pressure, temperature = 101325.0, 22.0 313 | altitude = 0.0 314 | 315 | # standard deviation of 1% assuming normal distribution 316 | covariance = np.diag([0.0001] * 5) 317 | ze, az, cov, jac = spa(times, latitude, longitude, pressure, altitude, 318 | temperature, __covariance__=covariance, 319 | __method__=method) 320 | cov = jflatten(cov) 321 | jac = jflatten(jac) 322 | jac_nd = solpos_nd_jac(times, latitude, longitude, pressure, altitude, 323 | temperature) 324 | for n in range(times.size): 325 | r, c = 2 * n, 5 * n 326 | # some rows which numdifftools returned nan 327 | if n in [0, 8, 17, 24]: 328 | continue 329 | ok_(np.allclose(jac[r:(r + 2), c:(c + 5)], jac_nd[:,:,n], equal_nan=True)) 330 | return ze, az, cov, jac, jac_nd 331 | 332 | 333 | if __name__ == '__main__': 334 | test_unc_wrapper() 335 | pv, pv_cov, pv_jac, pv_jac_algopy = test_IV() 336 | test_solpos() 337 | fig1 = plot_pv(pv, pv_cov) 338 | fig1.show() 339 | fig2 = plot_pv_jac(pv_jac, pv_jac_algopy) 340 | fig2.savefig('IV-PV-jac-errors.png') 341 | fig2.show() 342 | --------------------------------------------------------------------------------