├── .gitignore ├── .travis.yml ├── JOSS_Paper ├── paper.bib └── paper.md ├── LICENSE.rst ├── MANIFEST.in ├── README.md ├── active_subspaces ├── __init__.py ├── domains.py ├── gradients.py ├── integrals.py ├── optimizers.py ├── response_surfaces.py ├── subspaces.py └── utils │ ├── __init__.py │ ├── designs.py │ ├── misc.py │ ├── plotters.py │ ├── qp_solver.py │ ├── quadrature.py │ ├── response_surfaces.py │ └── simrunners.py ├── docs ├── Makefile ├── make.bat └── source │ ├── Contact.rst │ ├── LICENSE.rst │ ├── code.rst │ ├── conf.py │ ├── domains.rst │ ├── gradients.rst │ ├── index.rst │ ├── integrals.rst │ ├── optimizers.rst │ ├── response_surfaces.rst │ ├── subspaces.rst │ ├── utils.rst │ ├── utils_designs.rst │ ├── utils_misc.rst │ ├── utils_plotters.rst │ ├── utils_qp_solver.rst │ ├── utils_quadrature.rst │ ├── utils_response_surfaces.rst │ └── utils_simrunners.rst ├── requirements.txt ├── setup.py ├── test.py ├── tests ├── test_as_integrals.py ├── test_as_optimizers.py ├── test_as_response_surfaces.py ├── test_designs.py ├── test_domains.py ├── test_gradients.py ├── test_misc.py ├── test_plotters.py ├── test_qp_solver.py ├── test_quadrature.py ├── test_response_surfaces.py ├── test_simrunners.py └── test_subspaces.py └── tutorials ├── basic.ipynb ├── test_functions ├── borehole │ ├── borehole_example.ipynb │ └── borehole_functions.py ├── otl_circuit │ ├── otlcircuit_example.ipynb │ └── otlcircuit_functions.py ├── piston │ ├── piston_example.ipynb │ └── piston_functions.py ├── robot │ ├── robot_example.ipynb │ └── robot_functions.py └── wing_weight │ ├── wing_example.ipynb │ └── wing_functions.py └── wing_functions.py /.gitignore: -------------------------------------------------------------------------------- 1 | # matlab outsave viles 2 | *.m~ 3 | # this weird thing for mac file systems 4 | .DS_Store 5 | 6 | # compiled python codes 7 | *.pyc 8 | 9 | # figs directory 10 | figs/ 11 | 12 | # test data 13 | 14 | # *.npz 15 | 16 | # the etc directory 17 | etc/ 18 | 19 | # logs 20 | gurobi.log 21 | 22 | # Python Packages 23 | *.egg 24 | *.egg-info 25 | dist 26 | build 27 | eggs 28 | parts 29 | bin 30 | var 31 | sdist 32 | develop-eggs 33 | .installed.cfg 34 | lib 35 | lib64 36 | __pycache__ 37 | 38 | # Installer logs 39 | pip-log.txt 40 | 41 | # Unit test / coverage reports 42 | .coverage 43 | .tox 44 | nosetests.xml 45 | 46 | # Translations 47 | *.mo 48 | .eggs 49 | 50 | # Jupyter Checkpoints 51 | *.ipynb_checkpoints* 52 | *checkpoint.ipynb 53 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: apt 2 | sudo: false 3 | 4 | language: python 5 | python: 6 | - "2.7" 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - libatlas-dev 12 | - libatlas-base-dev 13 | - liblapack-dev 14 | 15 | before_install: 16 | - travis_retry wget http://repo.continuum.io/miniconda/Miniconda-3.8.3-Linux-x86_64.sh -O miniconda.sh 17 | - chmod +x miniconda.sh 18 | - bash miniconda.sh -b -p $HOME/miniconda 19 | - export PATH=/home/travis/miniconda/bin:$PATH 20 | - conda update --yes conda 21 | 22 | install: 23 | - conda create --yes -n test python=$TRAVIS_PYTHON_VERSION 24 | - source activate test 25 | - conda install --yes numpy scipy matplotlib pip nose 26 | - pip install setuptools 27 | - python setup.py install 28 | 29 | script: python test.py 30 | -------------------------------------------------------------------------------- /JOSS_Paper/paper.bib: -------------------------------------------------------------------------------- 1 | % Encoding: UTF-8 2 | 3 | 4 | @Article{CDW2014, 5 | Title = {Active subspace methods in theory and practice: Applications to kriging surfaces}, 6 | Author = {Paul G. Constantine and Eric Dow and Qiqi Wang}, 7 | Journal = {SIAM Journal on Scientific Computing}, 8 | Year = {2014}, 9 | Number = {4}, 10 | Pages = {A1500--A1524}, 11 | Volume = {36}, 12 | URL = {http://dx.doi.org/10.1137/130916138} 13 | } 14 | 15 | @Book{Const2015, 16 | Title = {Active Subspaces: Emerging Ideas for Dimension Reduction in Parameter Studies}, 17 | Author = {Paul G. Constantine}, 18 | Publisher = {SIAM, Philadelphia}, 19 | Year = {2015}, 20 | URL = {http://dx.doi.org/10.1137/1.9781611973860} 21 | } 22 | 23 | @Misc{PAUL, 24 | Title = {Python Active-subspaces Utility Library}, 25 | Year = {2016}, 26 | URL = {https://github.com/paulcon/active_subspaces} 27 | } 28 | 29 | -------------------------------------------------------------------------------- /JOSS_Paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Python Active-subspaces Utility Library' 3 | tags: 4 | - python 5 | - active subspaces 6 | - dimension reduction 7 | - uncertainty quantification 8 | - sensitivity analysis 9 | - surrogate modeling 10 | authors: 11 | - name: Paul Constantine 12 | orcid: 0000-0003-3726-6307 13 | affiliation: 1 14 | - name: Ryan Howard 15 | affiliation: 1 16 | - name: Andrew Glaws 17 | affiliation: 1 18 | - name: Zachary Grey 19 | affiliation: 1 20 | - name: Paul Diaz 21 | affiliation: 2 22 | - name: Leslie Fletcher 23 | affiliations: 24 | - name: Colorado School of Mines, Golden, CO 25 | index: 1 26 | - name: University of Colorado Boulder, Boulder, CO 27 | index: 2 28 | date: 29 September 2016 29 | bibliography: paper.bib 30 | repository: https://github.com/paulcon/active_subspaces 31 | archive_doi: https://doi.org/10.5281/zenodo.158941 32 | --- 33 | 34 | # Summary 35 | 36 | Active subspaces are part of an emerging set of tools for discovering and exploiting low-dimensional structure in a function of several variables [@Const2015; @CDW2015]. The active subspace for a given function is the span of a set of important directions in the function's domain, where importance is defined by the eigenvalues of a symmetric, positive semidefinite matrix derived from the function's partial derivatives. Perturbing the inputs within the active subspace changes the output more, on average, than perturbing the inputs orthogonally to the active subspace. The functions of interest arise in complex computer simulation models in science and engineering, where the inputs are the model's physical parameters and the output is the scientific or engineering quantity of interest. Identifying an active subspace in a given model enables one to reduce the input dimension for essential parameter studies---such as optimization or uncertainty quantification. When the simulation model is computationally expensive, this dimension reduction may enable otherwise infeasible parameter studies. 37 | 38 | The Python Active-subspaces Utility Library [@PAUL] contains Python utilities for working with active subspaces in a given model. Given either (i) a function handle to the model's output and gradients as a function of the model's inputs or (ii) a set of previously generated input/output pairs, the Utility Library provides several methods for estimating the active subspace---along with several diagnostics for the quality of the estimated active subspace. With an active subspace in hand, the Utility Library contains tools for (i) constructing a polynomial or Gaussian radial basis response surface, (ii) estimating the minimum value of the function, and (iii) estimating the average of the function---all of which exploit the low-dimensional active subspace for increased efficiency. The Library also contains several convenience functions for plotting that may reveal simple relationships between the model's active variables (linear combinations of the original variables) and its output. 39 | 40 | # References 41 | 42 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | LICENSE 2 | ======= 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 Paul Constantine 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE.txt 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.158941.svg)](https://doi.org/10.5281/zenodo.158941) 4 | 5 | [Active subspaces](http://activesubspaces.org) are part of an emerging set of tools for discovering low-dimensional structure in a given function of several variables. Interesting applications arise in deterministic computer simulations of complex physical systems, where the function is the map from the physical model's input parameters to its output quantity of interest. The active subspace is the span of particular directions in the input parameter space; perturbing the inputs along these *active* directions changes the output more, on average, than perturbing the inputs orthogonally to the active directions. By focusing on the model's response along active directions and ignoring the relatively inactive directions, we *reduce the dimension* for parameter studies---such as optimization and integration---that are essential to engineering tasks such as design and uncertainty quantification. 6 | 7 | This library contains Python tools for discovering and exploiting a given model's active subspace. The user may provide a function handle to a complex model or its gradient with respect to the input parameters. Alternatively, the user may provide a set of input/output pairs from a previously executed set of runs (e.g., a Monte Carlo or Latin hypercube study). Essential classes and methods are documented; see documentation below. We also provide a set of [Jupyter](http://jupyter.org/) notebooks that demonstrate how to apply the code to a given model. 8 | 9 | To see active subspace in action on real science and engineering applications, see the [Active Subspaces Data Sets](https://github.com/paulcon/as-data-sets) repository, which contains several Jupyter notebooks applying the methods to study input/output relationships in complex models. 10 | 11 | # Testing 12 | 13 | [![Build Status](https://travis-ci.org/paulcon/active_subspaces.svg?branch=master)](https://travis-ci.org/paulcon/active_subspaces) 14 | 15 | We are using Travis CI for continuous integration testing. You can check out the current status [here](https://travis-ci.org/paulcon/active_subspaces). 16 | 17 | To run tests locally: 18 | 19 | ```bash 20 | > python test.py 21 | ``` 22 | 23 | # Requirements and Dependencies 24 | 25 | * [numpy](http://www.numpy.org/) 26 | * [scipy](http://www.scipy.org/), >= 0.15.0 27 | * [matplotlib](http://matplotlib.org/) 28 | * [Gurobi](http://www.gurobi.com/) is an _optional_ dependency. The same functionality is accomplished with SciPy's [optimize](http://docs.scipy.org/doc/scipy/reference/optimize.html) package, albeit less accurately (particularly in the quadratic programs). 29 | 30 | If you wish to use Gurobi, you will need to install it separately by following the instructions contained in their quick-start guides for [Windows](http://www.gurobi.com/documentation/6.5/quickstart_windows.pdf), [Linux](http://www.gurobi.com/documentation/6.5/quickstart_linux.pdf), or [Mac](http://www.gurobi.com/documentation/6.5/quickstart_mac.pdf). To test your installation of Gurobi, start a python interpreter and execute the command `import gurobipy`. If there is no import exception, the active subspaces library will be able to use Gurobi. 31 | 32 | We had some initial trouble getting the Gurobi Python interface working with Enthought Python; Gurobi formally supports as subset of Python distributions, and Enthought is not one of them. However, we were able to get the interface working by following instructions in this [thread](https://groups.google.com/forum/#!searchin/gurobi/canopy/gurobi/ArCkf4a40uU/R9U1XFuMJEkJ). 33 | 34 | # Installation 35 | 36 | To install the active subspaces package, open the terminal/command line and clone the repository with the command 37 | 38 | ```bash 39 | git clone https://github.com/paulcon/active_subspaces.git 40 | ``` 41 | 42 | Navigate into the `active_subspaces` folder (where the `setup.py` file is located) and run the command 43 | 44 | ```bash 45 | python setup.py install 46 | ``` 47 | 48 | You should now be able to import the active subspaces library in Python scripts and interpreters with the command `import active_subspaces`. 49 | 50 | This method was tested on Windows 7 Professional, and Ubuntu 14.04 LTS, and Mac OSX El Capitan with the [Enthought](https://www.enthought.com/) Python distribution (Python 2.7.11, NumPy 1.10.4, SciPy 0.17.0, and matplotlib 1.5.1). 51 | 52 | # Usage 53 | 54 | For detailed examples of usage and results, see the Jupyter notebooks contained in the `tutorials` directory, the [active subspaces website] 55 | (http://activesubspaces.org/applications/), and the Active Subspaces Data Sets [repo](https://github.com/paulcon/as-data-sets). 56 | 57 | The core class is the Subspaces class contained in the `subspaces.py` file. An instance of this class can compute the active subspace with a variety of methods that take either an array of gradients or input/output pairs. It contains the estimated eigenvalues (and bootstrap ranges), subspace errors (and bootstrap ranges), eigenvalues, and an array of the eigenvectors defining the active subspace. The `utils/plotters.py` file contains functions to plot these quantities and produce summary plots that show model output against the first 1 or 2 active variables. The `utils/response_surfaces.py` file contains classes for polynomial and radial-basis approximations that can be trained with input/output pairs. Both classes can predict the value and gradient at input points and have a coefficient of determination (R-squared) value that measures goodness-of-fit. The `integrals.py` and `optimizers.py` files contain functions for integrating and optimizing functions of the active variables; these rely on classes from the `domains.py` file. 58 | 59 | # Documentation 60 | 61 | [![Documentation Status](https://readthedocs.org/projects/active-subspaces/badge/?version=latest)](http://active-subspaces.readthedocs.io/en/latest/?badge=latest) 62 | 63 | Documentation can be found on [ReadTheDocs](http://active-subspaces.readthedocs.io/en/latest/). 64 | 65 | # Community Guidelines 66 | 67 | To contribute to this project, please follow these steps. Thanks to [Marco Tezzele](https://github.com/mtezzele) for providing this helpful template. 68 | 69 | ## Submitting a patch 70 | 71 | 1. Open a new issue describing the bug to fix or feature to add. Even if you think it's relatively minor, it's helpful to know what people are working on. 72 | 2. Follow the normal process of [forking][] the project, and set up a new branch to work in. It's important that each group of changes be done in separate branches to ensure that a pull request only includes the commits related to that bug or feature. 73 | 3. Significant changes should be accompanied by tests. The project already has good test coverage, so look at some of the existing tests if you're unsure how to go about it. 74 | 4. Push the commits to your fork and submit a [pull request][]. Please, remember to rebase properly in order to maintain a clean, linear git history. 75 | 76 | [forking]: https://help.github.com/articles/fork-a-repo 77 | [pull request]: https://help.github.com/articles/creating-a-pull-request 78 | 79 | If you have questions or feedback, contact [*Paul Constantine*](http://inside.mines.edu/~pconstan/). 80 | 81 | # Acknowledgments 82 | 83 | This material is based upon work supported by the U.S. Department of Energy Office of Science, Office of Advanced Scientific Computing Research, Applied Mathematics program under Award Number DE-SC-0011077. -------------------------------------------------------------------------------- /active_subspaces/__init__.py: -------------------------------------------------------------------------------- 1 | ''' This is the init file.''' 2 | import utils, domains, gradients, integrals, optimizers, response_surfaces, subspaces 3 | -------------------------------------------------------------------------------- /active_subspaces/gradients.py: -------------------------------------------------------------------------------- 1 | """Utilities for approximating gradients.""" 2 | import numpy as np 3 | from utils.misc import process_inputs 4 | from utils.simrunners import SimulationRunner 5 | 6 | def local_linear_gradients(X, f, p=None, weights=None): 7 | """Estimate a collection of gradients from input/output pairs. 8 | 9 | Given a set of input/output pairs, choose subsets of neighboring points and 10 | build a local linear model for each subset. The gradients of these local 11 | linear models comprise estimates of sampled gradients. 12 | 13 | Parameters 14 | ---------- 15 | X : ndarray 16 | M-by-m matrix that contains the m-dimensional inputs 17 | f : ndarray 18 | M-by-1 matrix that contains scalar outputs 19 | p : int, optional 20 | how many nearest neighbors to use when constructing the local linear 21 | model (default 1) 22 | weights : ndarray, optional 23 | M-by-1 matrix that contains the weights for each observation (default 24 | None) 25 | 26 | Returns 27 | ------- 28 | df : ndarray 29 | M-by-m matrix that contains estimated partial derivatives approximated 30 | by the local linear models 31 | 32 | Notes 33 | ----- 34 | If `p` is not specified, the default value is floor(1.7*m). 35 | """ 36 | 37 | X, M, m = process_inputs(X) 38 | if M<=m: raise Exception('Not enough samples for local linear models.') 39 | 40 | if p is None: 41 | p = int(np.minimum(np.floor(1.7*m), M)) 42 | elif not isinstance(p, int): 43 | raise TypeError('p must be an integer.') 44 | 45 | if p < m+1 or p > M: 46 | raise Exception('p must be between m+1 and M') 47 | 48 | if weights is None: 49 | weights = np.ones((M, 1)) / M 50 | 51 | MM = np.minimum(int(np.ceil(10*m*np.log(m))), M-1) 52 | df = np.zeros((MM, m)) 53 | for i in range(MM): 54 | ii = np.random.randint(M) 55 | x = X[ii,:] 56 | D2 = np.sum((X - x)**2, axis=1) 57 | ind = np.argsort(D2) 58 | ind = ind[D2 != 0] 59 | A = np.hstack((np.ones((p,1)), X[ind[:p],:])) * np.sqrt(weights[ii]) 60 | b = f[ind[:p]] * np.sqrt(weights[ii]) 61 | u = np.linalg.lstsq(A, b)[0] 62 | df[i,:] = u[1:].T 63 | return df 64 | 65 | def finite_difference_gradients(X, fun, h=1e-6): 66 | """Compute finite difference gradients with a given interface. 67 | 68 | Parameters 69 | ---------- 70 | X : ndarray 71 | M-by-m matrix that contains the points to estimate the gradients with 72 | finite differences 73 | fun : function 74 | function that returns the simulation's quantity of interest given inputs 75 | h : float, optional 76 | the finite difference step size (default 1e-6) 77 | 78 | Returns 79 | ------- 80 | df : ndarray 81 | M-by-m matrix that contains estimated partial derivatives approximated 82 | by finite differences 83 | """ 84 | X, M, m = process_inputs(X) 85 | 86 | # points to run simulations including the perturbed inputs 87 | XX = np.kron(np.ones((m+1, 1)),X) + \ 88 | h*np.kron(np.vstack((np.zeros((1, m)), np.eye(m))), np.ones((M, 1))) 89 | 90 | # run the simulation 91 | if isinstance(fun, SimulationRunner): 92 | F = fun.run(XX) 93 | else: 94 | F = SimulationRunner(fun).run(XX) 95 | 96 | df = (F[M:].reshape((m, M)).transpose() - F[:M]) / h 97 | return df.reshape((M,m)) 98 | -------------------------------------------------------------------------------- /active_subspaces/integrals.py: -------------------------------------------------------------------------------- 1 | """Utilities for exploiting active subspaces when estimating integrals.""" 2 | 3 | import numpy as np 4 | import utils.quadrature as gq 5 | from utils.misc import conditional_expectations 6 | from utils.designs import maximin_design 7 | from utils.simrunners import SimulationRunner 8 | from domains import UnboundedActiveVariableDomain, BoundedActiveVariableDomain, \ 9 | ActiveVariableMap 10 | from response_surfaces import ActiveSubspaceResponseSurface 11 | from scipy.spatial import Delaunay 12 | 13 | def integrate(fun, avmap, N, NMC=10): 14 | """Approximate the integral of a function of m variables. 15 | 16 | Parameters 17 | ---------- 18 | fun : function 19 | an interface to the simulation that returns the quantity of interest 20 | given inputs as an 1-by-m ndarray 21 | avmap : ActiveVariableMap 22 | a domains.ActiveVariableMap 23 | N : int 24 | the number of points in the quadrature rule 25 | NMC : int, optional 26 | the number of points in the Monte Carlo estimates of the conditional 27 | expectation and conditional variance (default 10) 28 | 29 | Returns 30 | ------- 31 | mu : float 32 | an estimate of the integral of the function computed against the weight 33 | function on the simulation inputs 34 | lb : float 35 | a central-limit-theorem 95% lower confidence from the Monte Carlo part 36 | of the integration 37 | ub : float 38 | a central-limit-theorem 95% upper confidence from the Monte Carlo part 39 | of the integration 40 | 41 | See Also 42 | -------- 43 | integrals.quadrature_rule 44 | 45 | Notes 46 | ----- 47 | The CLT-based bounds `lb` and `ub` are likely poor estimators of the error. 48 | They only account for the variance from the Monte Carlo portion. They do 49 | not include any error from the integration rule on the active variables. 50 | """ 51 | if not isinstance(avmap, ActiveVariableMap): 52 | raise TypeError('avmap should be an ActiveVariableMap.') 53 | 54 | if not isinstance(N, int): 55 | raise TypeError('N should be an integer') 56 | 57 | # get the quadrature rule 58 | Xp, Xw, ind = quadrature_rule(avmap, N, NMC=NMC) 59 | 60 | # compute the simulation output at each quadrature node 61 | if isinstance(fun, SimulationRunner): 62 | f = fun.run(Xp) 63 | else: 64 | f = SimulationRunner(fun).run(Xp) 65 | 66 | # estimate conditional expectations and variances 67 | Ef, Vf = conditional_expectations(f, ind) 68 | 69 | # get weights for the conditional expectations 70 | w = conditional_expectations(Xw*NMC, ind)[0] 71 | 72 | # estimate the average 73 | mu = np.dot(Ef.T, w) 74 | 75 | # estimate the variance due to Monte Carlo 76 | sig2 = np.dot(Vf.T, w*w) / NMC 77 | 78 | # compute 95% confidence bounds from the Monte Carlo 79 | lb, ub = mu - 1.96*np.sqrt(sig2), mu + 1.96*np.sqrt(sig2) 80 | return mu[0,0], lb[0,0], ub[0,0] 81 | 82 | def av_integrate(avfun, avmap, N): 83 | """Approximate the integral of a function of active variables. 84 | 85 | Parameters 86 | ---------- 87 | avfun : function 88 | a function of the active variables 89 | avmap : ActiveVariableMap 90 | a domains.ActiveVariableMap 91 | N : int 92 | the number of points in the quadrature rule 93 | 94 | Returns 95 | ------- 96 | mu : float 97 | an estimate of the integral 98 | 99 | Notes 100 | ----- 101 | This function is usually used when one has already constructed a response 102 | surface on the active variables and wants to estimate its integral. 103 | """ 104 | if not isinstance(avmap, ActiveVariableMap): 105 | raise TypeError('avmap should be an ActiveVariableMap.') 106 | 107 | if not isinstance(N, int): 108 | raise TypeError('N should be an integer.') 109 | 110 | Yp, Yw = av_quadrature_rule(avmap, N) 111 | if isinstance(avfun, ActiveSubspaceResponseSurface): 112 | avf = avfun.predict_av(Yp)[0] 113 | else: 114 | avf = SimulationRunner(avfun).run(Yp) 115 | mu = np.dot(Yw.T, avf)[0,0] 116 | return mu 117 | 118 | def quadrature_rule(avmap, N, NMC=10): 119 | """Get a quadrature rule on the space of simulation inputs. 120 | 121 | Parameters 122 | ---------- 123 | avmap : ActiveVariableMap 124 | a domains.ActiveVariableMap 125 | N : int 126 | the number of quadrature nodes in the active variables 127 | NMC : int, optional 128 | the number of samples in the simple Monte Carlo over the inactive 129 | variables (default 10) 130 | 131 | Returns 132 | ------- 133 | Xp : ndarray 134 | (N*NMC)-by-m matrix containing the quadrature nodes on the simulation 135 | input space 136 | Xw : ndarray 137 | (N*NMC)-by-1 matrix containing the quadrature weights on the simulation 138 | input space 139 | ind : ndarray 140 | array of indices identifies which rows of `Xp` correspond to the same 141 | fixed value of the active variables 142 | 143 | See Also 144 | -------- 145 | integrals.av_quadrature_rule 146 | 147 | Notes 148 | ----- 149 | This quadrature rule uses an integration rule on the active variables and 150 | simple Monte Carlo on the inactive variables. 151 | 152 | If the simulation inputs are bounded, then the quadrature nodes on the 153 | active variables is constructed with a Delaunay triangulation of a 154 | maximin design. The weights are computed by sampling the original variables, 155 | mapping them to the active variables, and determining which triangle the 156 | active variables fall in. These samples are used to estimate quadrature 157 | weights. Note that when the dimension of the active subspace is 158 | one-dimensional, this reduces to operations on an interval. 159 | 160 | If the simulation inputs are unbounded, the quadrature rule on the active 161 | variables is given by a tensor product Gauss-Hermite quadrature rule. 162 | """ 163 | if not isinstance(avmap, ActiveVariableMap): 164 | raise TypeError('avmap should be an ActiveVariableMap.') 165 | 166 | if not isinstance(N, int): 167 | raise TypeError('N should be an integer.') 168 | 169 | if not isinstance(NMC, int): 170 | raise TypeError('NMC should be an integer.') 171 | 172 | # get quadrature rule on active variables 173 | Yp, Yw = av_quadrature_rule(avmap, N) 174 | 175 | # get points on x space with MC 176 | Xp, ind = avmap.inverse(Yp, NMC) 177 | Xw = np.kron(Yw, np.ones((NMC,1)))/float(NMC) 178 | return Xp, Xw, ind 179 | 180 | def av_quadrature_rule(avmap, N): 181 | """Get a quadrature rule on the space of active variables. 182 | 183 | Parameters 184 | ---------- 185 | avmap : ActiveVariableMap 186 | a domains.ActiveVariableMap 187 | N : int 188 | the number of quadrature nodes in the active variables 189 | 190 | Returns 191 | ------- 192 | Yp : ndarray 193 | quadrature nodes on the active variables 194 | Yw : ndarray 195 | quadrature weights on the active variables 196 | 197 | See Also 198 | -------- 199 | integrals.quadrature_rule 200 | """ 201 | m, n = avmap.domain.subspaces.W1.shape 202 | 203 | if isinstance(avmap.domain, UnboundedActiveVariableDomain): 204 | NN = [int(np.floor(np.power(N, 1.0/n))) for i in range(n)] 205 | Yp, Yw = gq.gauss_hermite(NN) 206 | 207 | elif isinstance(avmap.domain, BoundedActiveVariableDomain): 208 | if n == 1: 209 | Yp, Yw = interval_quadrature_rule(avmap, N) 210 | else: 211 | Yp, Yw = zonotope_quadrature_rule(avmap, N) 212 | else: 213 | raise Exception('There is a problem with the avmap.domain.') 214 | return Yp, Yw 215 | 216 | def interval_quadrature_rule(avmap, N, NX=10000): 217 | """Quadrature rule on a one-dimensional interval. 218 | 219 | Quadrature when the dimension of the active subspace is 1 and the 220 | simulation parameter space is bounded. 221 | 222 | Parameters 223 | ---------- 224 | avmap : ActiveVariableMap 225 | a domains.ActiveVariableMap 226 | N : int 227 | the number of quadrature nodes in the active variables 228 | NX : int, optional 229 | the number of samples to use to estimate the quadrature weights (default 230 | 10000) 231 | 232 | Returns 233 | ------- 234 | Yp : ndarray 235 | quadrature nodes on the active variables 236 | Yw : ndarray 237 | quadrature weights on the active variables 238 | 239 | See Also 240 | -------- 241 | integrals.quadrature_rule 242 | """ 243 | W1 = avmap.domain.subspaces.W1 244 | a, b = avmap.domain.vertY[0,0], avmap.domain.vertY[1,0] 245 | 246 | # number of dimensions 247 | m = W1.shape[0] 248 | 249 | # points 250 | y = np.linspace(a, b, N+1).reshape((N+1, 1)) 251 | points = 0.5*(y[1:] + y[:-1]) 252 | 253 | # weights 254 | Y_samples = np.dot(np.random.uniform(-1.0, 1.0, size=(NX, m)), W1) 255 | weights = np.histogram(Y_samples.reshape((NX, )), bins=y.reshape((N+1, )), \ 256 | range=(np.amin(y), np.amax(y)))[0] 257 | weights = weights / float(NX) 258 | 259 | Yp, Yw = points.reshape((N, 1)), weights.reshape((N, 1)) 260 | return Yp, Yw 261 | 262 | def zonotope_quadrature_rule(avmap, N, NX=10000): 263 | """Quadrature rule on a zonotope. 264 | 265 | Quadrature when the dimension of the active subspace is greater than 1 and 266 | the simulation parameter space is bounded. 267 | 268 | Parameters 269 | ---------- 270 | avmap : ActiveVariableMap 271 | a domains.ActiveVariableMap 272 | N : int 273 | the number of quadrature nodes in the active variables 274 | NX : int, optional 275 | the number of samples to use to estimate the quadrature weights (default 276 | 10000) 277 | 278 | Returns 279 | ------- 280 | Yp : ndarray 281 | quadrature nodes on the active variables 282 | Yw : ndarray 283 | quadrature weights on the active variables 284 | 285 | See Also 286 | -------- 287 | integrals.quadrature_rule 288 | """ 289 | 290 | vert = avmap.domain.vertY 291 | W1 = avmap.domain.subspaces.W1 292 | 293 | # number of dimensions 294 | m, n = W1.shape 295 | 296 | # points 297 | y = np.vstack((vert, maximin_design(vert, N))) 298 | T = Delaunay(y) 299 | c = [] 300 | for t in T.simplices: 301 | c.append(np.mean(T.points[t], axis=0)) 302 | points = np.array(c) 303 | 304 | # approximate weights 305 | Y_samples = np.dot(np.random.uniform(-1.0, 1.0, size=(NX,m)), W1) 306 | I = T.find_simplex(Y_samples) 307 | weights = np.zeros((T.nsimplex, 1)) 308 | for i in range(T.nsimplex): 309 | weights[i] = np.sum(I==i) / float(NX) 310 | 311 | Yp, Yw = points.reshape((T.nsimplex,n)), weights.reshape((T.nsimplex,1)) 312 | return Yp, Yw 313 | 314 | 315 | -------------------------------------------------------------------------------- /active_subspaces/optimizers.py: -------------------------------------------------------------------------------- 1 | """Utilities for exploiting active subspaces when optimizing.""" 2 | import numpy as np 3 | from domains import UnboundedActiveVariableDomain, BoundedActiveVariableDomain, \ 4 | ActiveVariableMap 5 | import scipy.optimize as scopt 6 | from utils.response_surfaces import PolynomialApproximation 7 | from utils.qp_solver import QPSolver 8 | from utils.misc import process_inputs_outputs 9 | 10 | class MinVariableMap(ActiveVariableMap): 11 | """ActiveVariableMap for optimization 12 | 13 | This subclass is an domains.ActiveVariableMap specifically for optimization. 14 | 15 | See Also 16 | -------- 17 | optimizers.BoundedMinVariableMap 18 | optimizers.UnboundedMinVariableMap 19 | 20 | Notes 21 | ----- 22 | This class's train function fits a global quadratic surrogate model to the 23 | n+2 active variables---two more than the dimension of the active subspace. 24 | This quadratic surrogate is used to map points in the space of active 25 | variables back to the simulation parameter space for minimization. 26 | """ 27 | 28 | def train(self, X, f): 29 | """Train the global quadratic for the regularization. 30 | 31 | Parameters 32 | ---------- 33 | X : ndarray 34 | input points used to train a global quadratic used in the 35 | `regularize_z` function 36 | f : ndarray 37 | simulation outputs used to train a global quadratic in the 38 | `regularize_z` function 39 | """ 40 | 41 | X, f, M, m = process_inputs_outputs(X, f) 42 | 43 | W1, W2 = self.domain.subspaces.W1, self.domain.subspaces.W2 44 | m, n = W1.shape 45 | W = self.domain.subspaces.eigenvecs 46 | 47 | # train quadratic surface on p>n active vars 48 | if m-n>2: 49 | p = n+2 50 | else: 51 | p = n+1 52 | 53 | Yp = np.dot(X, W[:,:p]) 54 | pr = PolynomialApproximation(N=2) 55 | pr.train(Yp, f) 56 | br, Ar = pr.g, pr.H 57 | 58 | # get coefficients 59 | b = np.dot(W[:,:p], br) 60 | A = np.dot(W[:,:p], np.dot(Ar, W[:,:p].T)) 61 | 62 | # some private attributes used in the regularize_z function 63 | self._bz = np.dot(W2.T, b) 64 | self._zAy = np.dot(W2.T, np.dot(A, W1)) 65 | self._zAz = np.dot(W2.T, np.dot(A, W2)) + 0.01*np.eye(m-n) 66 | 67 | class BoundedMinVariableMap(MinVariableMap): 68 | """This subclass is a MinVariableMap for bounded simulation inputs. 69 | 70 | See Also 71 | -------- 72 | optimizers.MinVariableMap 73 | optimizers.UnboundedMinVariableMap 74 | """ 75 | 76 | def regularize_z(self, Y, N=1): 77 | """Train the global quadratic for the regularization. 78 | 79 | Parameters 80 | ---------- 81 | Y : ndarray 82 | N-by-n matrix of points in the space of active variables 83 | N : int, optional 84 | merely there satisfy the interface of `regularize_z`. It should not 85 | be anything other than 1 86 | 87 | Returns 88 | ------- 89 | Z : ndarray 90 | N-by-(m-n)-by-1 matrix that contains a value of the inactive 91 | variables for each value of the inactive variables 92 | 93 | Notes 94 | ----- 95 | In contrast to the `regularize_z` in BoundedActiveVariableMap and 96 | UnboundedActiveVariableMap, this implementation of `regularize_z` uses 97 | a quadratic program to find a single value of the inactive variables 98 | for each value of the active variables. 99 | """ 100 | if N != 1: 101 | raise Exception('MinVariableMap needs N=1.') 102 | 103 | W1, W2 = self.domain.subspaces.W1, self.domain.subspaces.W2 104 | m, n = W1.shape 105 | NY = Y.shape[0] 106 | qps = QPSolver() 107 | 108 | Zlist = [] 109 | A_ineq = np.vstack((W2, -W2)) 110 | for y in Y: 111 | c = self._bz.reshape((m-n, 1)) + np.dot(self._zAy, y).reshape((m-n, 1)) 112 | b_ineq = np.vstack(( 113 | -1-np.dot(W1, y).reshape((m, 1)), 114 | -1+np.dot(W1, y).reshape((m, 1)) 115 | )) 116 | z = qps.quadratic_program_ineq(c, self._zAz, A_ineq, b_ineq) 117 | Zlist.append(z) 118 | Z = np.array(Zlist).reshape((NY, m-n, N)) 119 | return Z 120 | 121 | class UnboundedMinVariableMap(MinVariableMap): 122 | """This subclass is a MinVariableMap for unbounded simulation inputs. 123 | 124 | See Also 125 | -------- 126 | optimizers.MinVariableMap 127 | optimizers.BoundedMinVariableMap 128 | """ 129 | 130 | def regularize_z(self, Y, N=1): 131 | """Train the global quadratic for the regularization. 132 | 133 | Parameters 134 | ---------- 135 | Y : ndarray 136 | N-by-n matrix of points in the space of active variables 137 | N : int, optional 138 | merely there satisfy the interface of `regularize_z`. It should not 139 | be anything other than 1 140 | 141 | Returns 142 | ------- 143 | Z : ndarray 144 | N-by-(m-n)-by-1 matrix that contains a value of the inactive 145 | variables for each value of the inactive variables 146 | 147 | Notes 148 | ----- 149 | In contrast to the `regularize_z` in BoundedActiveVariableMap and 150 | UnboundedActiveVariableMap, this implementation of `regularize_z` uses 151 | a quadratic program to find a single value of the inactive variables 152 | for each value of the active variables. 153 | """ 154 | if N != 1: 155 | raise Exception('MinVariableMap needs N=1.') 156 | 157 | m, n = self.domain.subspaces.W1.shape 158 | NY = Y.shape[0] 159 | 160 | Zlist = [] 161 | for y in Y: 162 | c = self._bz.reshape((m-n, 1)) + np.dot(self._zAy, y).reshape((m-n, 1)) 163 | z = np.linalg.solve(self._zAz, c) 164 | Zlist.append(z) 165 | return np.array(Zlist).reshape((NY, m-n, N)) 166 | 167 | def minimize(asrs, X, f): 168 | """Minimize a response surface constructed with the active subspace. 169 | 170 | Parameters 171 | ---------- 172 | asrs : ActiveSubspaceResponseSurface 173 | a trained response_surfaces.ActiveSubspaceResponseSurface 174 | X : ndarray 175 | input points used to train the MinVariableMap 176 | f : ndarray 177 | simulation outputs used to train the MinVariableMap 178 | 179 | Returns 180 | ------- 181 | xstar : ndarray 182 | the estimated minimizer of the function modeled by the 183 | ActiveSubspaceResponseSurface `asrs` 184 | fstar : float 185 | the estimated minimum of the function modeled by `asrs` 186 | 187 | Notes 188 | ----- 189 | This function has two stages. First it uses the scipy.optimize package to 190 | minimize the response surface of the active variables. Then it trains 191 | a MinVariableMap with the given input/output pairs, which it uses to map 192 | the minimizer back to the space of simulation inputs. 193 | 194 | This is very heuristic. 195 | """ 196 | X, f, M, m = process_inputs_outputs(X, f) 197 | 198 | # ActiveVariableDomain 199 | avdom = asrs.avmap.domain 200 | 201 | # wrappers 202 | def avfun(y): 203 | f = asrs.predict_av(y.reshape((1,y.size)))[0] 204 | return f[0,0] 205 | def avdfun(y): 206 | df = asrs.gradient_av(y.reshape((1,y.size))) 207 | return df.reshape((y.size,)) 208 | 209 | if isinstance(avdom, UnboundedActiveVariableDomain): 210 | mvm = UnboundedMinVariableMap(avdom) 211 | elif isinstance(avdom, BoundedActiveVariableDomain): 212 | mvm = BoundedMinVariableMap(avdom) 213 | else: 214 | raise Exception('There is a problem with the avmap.domain.') 215 | 216 | ystar, fstar = av_minimize(avfun, avdom, avdfun=avdfun) 217 | mvm.train(X, f) 218 | xstar = mvm.inverse(ystar)[0] 219 | return xstar, fstar 220 | 221 | def av_minimize(avfun, avdom, avdfun=None): 222 | """Minimize a response surface on the active variables. 223 | 224 | Parameters 225 | ---------- 226 | avfun : function 227 | a function of the active variables 228 | avdom : ActiveVariableDomain 229 | information about the domain of `avfun` 230 | avdfun : function 231 | returns the gradient of `avfun` 232 | 233 | Returns 234 | ------- 235 | ystar : ndarray 236 | the estimated minimizer of `avfun` 237 | fstar : float 238 | the estimated minimum of `avfun` 239 | 240 | See Also 241 | -------- 242 | optimizers.interval_minimize 243 | optimizers.zonotope_minimize 244 | optimizers.unbounded_minimize 245 | """ 246 | if isinstance(avdom, UnboundedActiveVariableDomain): 247 | ystar, fstar = unbounded_minimize(avfun, avdom, avdfun) 248 | 249 | elif isinstance(avdom, BoundedActiveVariableDomain): 250 | n = avdom.subspaces.W1.shape[1] 251 | if n==1: 252 | ystar, fstar = interval_minimize(avfun, avdom) 253 | else: 254 | ystar, fstar = zonotope_minimize(avfun, avdom, avdfun) 255 | else: 256 | raise Exception('There is a problem with the avdom.') 257 | 258 | return ystar.reshape((1,ystar.size)), fstar 259 | 260 | def interval_minimize(avfun, avdom): 261 | """Minimize a response surface defined on an interval. 262 | 263 | Parameters 264 | ---------- 265 | avfun : function 266 | a function of the active variables 267 | avdom : ActiveVariableDomain 268 | contains information about the domain of `avfun` 269 | 270 | Returns 271 | ------- 272 | ystar : ndarray 273 | the estimated minimizer of `avfun` 274 | fstar : float 275 | the estimated minimum of `avfun` 276 | 277 | See Also 278 | -------- 279 | optimizers.av_minimize 280 | 281 | Notes 282 | ----- 283 | This function wraps the scipy.optimize function fminbound. 284 | """ 285 | 286 | yl, yu = avdom.vertY[0,0], avdom.vertY[1,0] 287 | result = scopt.fminbound(avfun, yl, yu, xtol=1e-9, maxfun=1e4, full_output=1) 288 | if result[2]: 289 | raise Exception('Max function values used in fminbound.') 290 | ystar, fstar = None, None 291 | else: 292 | ystar, fstar = np.array([[result[0]]]), result[1] 293 | return ystar, fstar 294 | 295 | def zonotope_minimize(avfun, avdom, avdfun): 296 | """Minimize a response surface defined on a zonotope. 297 | 298 | Parameters 299 | ---------- 300 | avfun : function 301 | a function of the active variables 302 | avdom : ActiveVariableDomain 303 | contains information about the domain of `avfun` 304 | avdfun : function 305 | returns the gradient of `avfun` 306 | 307 | Returns 308 | ------- 309 | ystar : ndarray 310 | the estimated minimizer of `avfun` 311 | fstar : float 312 | the estimated minimum of `avfun` 313 | 314 | See Also 315 | -------- 316 | optimizers.av_minimize 317 | 318 | Notes 319 | ----- 320 | This function wraps the scipy.optimize implementation of SLSQP with linear 321 | inequality constraints derived from the zonotope. 322 | """ 323 | 324 | n = avdom.subspaces.W1.shape[1] 325 | 326 | opts = {'disp':False, 'maxiter':1e4, 'ftol':1e-9} 327 | 328 | # a bit of globalization 329 | curr_state = np.random.get_state() 330 | np.random.seed(42) 331 | minf = 1e100 332 | minres = [] 333 | for i in range(10): 334 | y0 = np.random.normal(size=(1, n)) 335 | cons = avdom.constraints 336 | result = scopt.minimize(avfun, y0, constraints=cons, method='SLSQP', \ 337 | jac=avdfun, options=opts) 338 | if not result.success: 339 | raise Exception('SLSQP failed with message: {}.'.format(result.message)) 340 | if result.fun < minf: 341 | minf = result.fun 342 | minres = result 343 | 344 | np.random.set_state(curr_state) 345 | ystar, fstar = minres.x, minres.fun 346 | return ystar, fstar 347 | 348 | def unbounded_minimize(avfun, avdom, avdfun): 349 | """Minimize a response surface defined on an unbounded domain. 350 | 351 | Parameters 352 | ---------- 353 | avfun : function 354 | a function of the active variables 355 | avdom : ActiveVariableDomain 356 | contains information about the domain of `avfun` 357 | avdfun : function 358 | returns the gradient of `avfun` 359 | 360 | Returns 361 | ------- 362 | ystar : ndarray 363 | the estimated minimizer of `avfun` 364 | fstar : float 365 | the estimated minimum of `avfun` 366 | 367 | See Also 368 | -------- 369 | optimizers.av_minimize 370 | 371 | Notes 372 | ----- 373 | If the gradient `avdfun` is None, this function wraps the scipy.optimize 374 | implementation of SLSQP. Otherwise, it wraps BFGS. 375 | """ 376 | n = avdom.subspaces.W1.shape[1] 377 | opts = {'disp':False, 'maxiter':1e4} 378 | 379 | if avdfun == None: 380 | method = 'SLSQP' 381 | else: 382 | method = 'BFGS' 383 | 384 | # some tricks for globalization 385 | curr_state = np.random.get_state() 386 | np.random.seed(42) 387 | minf = 1e100 388 | minres = [] 389 | for i in range(10): 390 | y0 = np.random.normal(size=(1, n)) 391 | result = scopt.minimize(avfun, y0, method=method, jac=avdfun, options=opts) 392 | if not result.success: 393 | raise Exception('{} failed with message: {}.'.format(method, result.message)) 394 | if result.fun < minf: 395 | minf = result.fun 396 | minres = result 397 | np.random.set_state(curr_state) 398 | ystar, fstar = minres.x, minres.fun 399 | return ystar, fstar 400 | -------------------------------------------------------------------------------- /active_subspaces/response_surfaces.py: -------------------------------------------------------------------------------- 1 | """Utilities for exploiting active subspaces in response surfaces.""" 2 | import numpy as np 3 | import utils.designs as dn 4 | from utils.simrunners import SimulationRunner 5 | from utils.misc import conditional_expectations 6 | from utils.response_surfaces import RadialBasisApproximation 7 | from domains import UnboundedActiveVariableDomain, BoundedActiveVariableDomain, \ 8 | ActiveVariableMap 9 | 10 | class ActiveSubspaceResponseSurface(): 11 | """A class for using active subspace with response surfaces. 12 | 13 | Attributes 14 | ---------- 15 | respsurf : ResponseSurface 16 | `respsurf` is a utils.response_surfaces.ResponseSurface 17 | avmap : ActiveVariableMap 18 | a domains.ActiveVariableMap 19 | 20 | Notes 21 | ----- 22 | This class has several convenient functions for training and using a 23 | response surface with active subspaces. Note that the `avmap` must be 24 | given. This means that the active subspace must be computed already. 25 | """ 26 | respsurf = None 27 | avmap = None 28 | 29 | def __init__(self, avmap, respsurf=None): 30 | """Initialize an ActiveSubspaceResponseSurface. 31 | 32 | Parameters 33 | ---------- 34 | avmap : ActiveVariableMap 35 | a domains.ActiveVariable map that includes the active variable 36 | domain, which includes the active and inactive subspaces 37 | respsurf : ResponseSurface, optional 38 | a utils.response_surfaces.ResponseSurface object. If a 39 | ResponseSurface is not given, a default RadialBasisApproximation is 40 | used. 41 | """ 42 | if not isinstance(avmap, ActiveVariableMap): 43 | raise TypeError('avmap should be an ActiveVariableMap.') 44 | 45 | if respsurf == None: 46 | self.respsurf = RadialBasisApproximation() 47 | else: 48 | self.respsurf = respsurf 49 | self.avmap = avmap 50 | 51 | def _train(self, Y, f, v=None): 52 | """Train the radial basis function approximation. 53 | 54 | A private function for training the response surface with a set of 55 | active variable and function evaluations. 56 | """ 57 | if isinstance(self.respsurf, RadialBasisApproximation): 58 | evals = self.avmap.domain.subspaces.eigenvals 59 | self.respsurf.train(Y, f, v=v, e=evals) 60 | else: 61 | self.respsurf.train(Y, f) 62 | 63 | def train_with_data(self, X, f, v=None): 64 | """Train the response surface with input/output pairs. 65 | 66 | Parameters 67 | ---------- 68 | X : ndarray 69 | M-by-m matrix with evaluations of the simulation inputs 70 | f : ndarray 71 | M-by-1 matrix with corresponding simulation quantities of interest 72 | v : ndarray, optional 73 | M-by-1 matrix that contains the regularization (i.e., errors) 74 | associated with `f` (default None) 75 | 76 | Notes 77 | ----- 78 | The training methods exploit the eigenvalues from the active subspace 79 | analysis to determine length scales for each variable when tuning 80 | the parameters of the radial bases. 81 | 82 | The method sets attributes of the object for further use. 83 | """ 84 | Y = self.avmap.forward(X)[0] 85 | self._train(Y, f, v=v) 86 | 87 | def train_with_interface(self, fun, N, NMC=10): 88 | """Train the response surface with input/output pairs. 89 | 90 | Parameters 91 | ---------- 92 | fun : function 93 | a function that returns the simulation quantity of interest given a 94 | point in the input space as an 1-by-m ndarray 95 | N : int 96 | the number of points used in the design-of-experiments for 97 | constructing the response surface 98 | NMC : int, optional 99 | the number of points used to estimate the conditional expectation 100 | and conditional variance of the function given a value of the active 101 | variables 102 | 103 | Notes 104 | ----- 105 | The training methods exploit the eigenvalues from the active subspace 106 | analysis to determine length scales for each variable when tuning 107 | the parameters of the radial bases. 108 | 109 | The method sets attributes of the object for further use. 110 | 111 | The method uses the response_surfaces.av_design function to get the 112 | design for the appropriate `avmap`. 113 | """ 114 | Y, X, ind = av_design(self.avmap, N, NMC=NMC) 115 | 116 | if isinstance(self.avmap.domain, BoundedActiveVariableDomain): 117 | X = np.vstack((X, self.avmap.domain.vertX)) 118 | Y = np.vstack((Y, self.avmap.domain.vertY)) 119 | il = np.amax(ind) + 1 120 | iu = np.amax(ind) + self.avmap.domain.vertX.shape[0] + 1 121 | iind = np.arange(il, iu) 122 | ind = np.vstack(( ind, iind.reshape((iind.size,1)) )) 123 | 124 | # run simulation interface at all design points 125 | if isinstance(fun, SimulationRunner): 126 | f = fun.run(X) 127 | else: 128 | f = SimulationRunner(fun).run(X) 129 | 130 | Ef, Vf = conditional_expectations(f, ind) 131 | self._train(Y, Ef, v=Vf) 132 | 133 | def predict_av(self, Y, compgrad=False): 134 | """Evaluate response surface at active variable. 135 | 136 | Compute the value of the response surface given values of the active 137 | variables. 138 | 139 | Parameters 140 | ---------- 141 | Y : ndarray 142 | M-by-n matrix containing points in the range of active variables to 143 | evaluate the response surface 144 | compgrad : bool, optional 145 | determines if the gradient of the response surface with respect to 146 | the active variables is computed and returned (default False) 147 | 148 | Returns 149 | ------- 150 | f : ndarray 151 | contains the response surface values at the given `Y` 152 | df : ndarray 153 | contains the response surface gradients at the given `Y`. If 154 | `compgrad` is False, then `df` is None. 155 | """ 156 | f, df = self.respsurf.predict(Y, compgrad) 157 | return f, df 158 | 159 | def gradient_av(self, Y): 160 | """Compute the gradient with respect to the active variables. 161 | 162 | A convenience function for computing the gradient of the response 163 | surface with respect to the active variables. 164 | 165 | Parameters 166 | ---------- 167 | Y : ndarray 168 | M-by-n matrix containing points in the range of active variables to 169 | evaluate the response surface gradient 170 | 171 | Returns 172 | ------- 173 | df : ndarray 174 | contains the response surface gradient at the given `Y` 175 | """ 176 | df = self.respsurf.predict(Y, compgrad=True)[1] 177 | return df 178 | 179 | def predict(self, X, compgrad=False): 180 | """Evaluate the response surface at full space points. 181 | 182 | Compute the value of the response surface given values of the simulation 183 | variables. 184 | 185 | Parameters 186 | ---------- 187 | X : ndarray 188 | M-by-m matrix containing points in simulation's parameter space 189 | compgrad : bool, optional 190 | determines if the gradient of the response surface is computed and 191 | returned (default False) 192 | 193 | Returns 194 | ------- 195 | f : ndarray 196 | contains the response surface values at the given `X` 197 | dfdx : ndarray 198 | an ndarray of shape M-by-m that contains the estimated gradient at 199 | the given `X`. If `compgrad` is False, then `dfdx` is None. 200 | """ 201 | Y = self.avmap.forward(X)[0] 202 | f, dfdy = self.predict_av(Y, compgrad) 203 | if compgrad: 204 | W1 = self.avmap.domain.subspaces.W1 205 | dfdx = np.dot(dfdy, W1.T) 206 | else: 207 | dfdx = None 208 | return f, dfdx 209 | 210 | def gradient(self, X): 211 | """Gradient of the response surface. 212 | 213 | A convenience function for computing the gradient of the response 214 | surface with respect to the simulation inputs. 215 | 216 | Parameters 217 | ---------- 218 | X : ndarray 219 | M-by-m matrix containing points in the space of simulation inputs 220 | 221 | Returns 222 | ------- 223 | df : ndarray 224 | contains the response surface gradient at the given `X` 225 | """ 226 | return self.predict(X, compgrad=True)[1] 227 | 228 | def __call__(self, X): 229 | return self.predict(X)[0] 230 | 231 | def av_design(avmap, N, NMC=10): 232 | """Design on active variable space. 233 | 234 | A wrapper that returns the design for the response surface in the space of 235 | the active variables. 236 | 237 | Parameters 238 | ---------- 239 | avmap : ActiveVariableMap 240 | a domains.ActiveVariable map that includes the active variable domain, 241 | which includes the active and inactive subspaces 242 | N : int 243 | the number of points used in the design-of-experiments for constructing 244 | the response surface 245 | NMC : int, optional 246 | the number of points used to estimate the conditional expectation and 247 | conditional variance of the function given a value of the active 248 | variables (Default is 10) 249 | 250 | Returns 251 | ------- 252 | Y : ndarray 253 | N-by-n matrix that contains the design points in the space of active 254 | variables 255 | X : ndarray 256 | (N*NMC)-by-m matrix that contains points in the simulation input space 257 | to run the simulation 258 | ind : ndarray 259 | indices that map points in `X` to points in `Y` 260 | 261 | See Also 262 | -------- 263 | utils.designs.gauss_hermite_design 264 | utils.designs.interval_design 265 | utils.designs.maximin_design 266 | """ 267 | 268 | 269 | if not isinstance(avmap, ActiveVariableMap): 270 | raise TypeError('avmap should be an ActiveVariableMap.') 271 | 272 | # interpret N as total number of points in the design 273 | if not isinstance(N, int): 274 | raise Exception('N should be an integer.') 275 | 276 | if not isinstance(NMC, int): 277 | raise Exception('NMC should be an integer.') 278 | 279 | m, n = avmap.domain.subspaces.W1.shape 280 | 281 | if isinstance(avmap.domain, UnboundedActiveVariableDomain): 282 | NN = [int(np.floor(np.power(N, 1.0/n))) for i in range(n)] 283 | Y = dn.gauss_hermite_design(NN) 284 | 285 | elif isinstance(avmap.domain, BoundedActiveVariableDomain): 286 | 287 | if n==1: 288 | a, b = avmap.domain.vertY[0,0], avmap.domain.vertY[1,0] 289 | Y = dn.interval_design(a, b, N) 290 | else: 291 | vertices = avmap.domain.vertY 292 | Y = dn.maximin_design(vertices, N) 293 | else: 294 | raise Exception('There is a problem with the avmap.domain.') 295 | 296 | X, ind = avmap.inverse(Y, NMC) 297 | return Y, X, ind 298 | 299 | -------------------------------------------------------------------------------- /active_subspaces/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import designs, misc, plotters, qp_solver, quadrature, response_surfaces, simrunners 2 | -------------------------------------------------------------------------------- /active_subspaces/utils/designs.py: -------------------------------------------------------------------------------- 1 | """Utilities for constructing design-of-experiments.""" 2 | import numpy as np 3 | import misc as mi 4 | from quadrature import gauss_hermite 5 | from scipy.spatial import ConvexHull, distance_matrix 6 | from scipy.optimize import minimize 7 | 8 | def interval_design(a, b, N): 9 | """Equally spaced points on an interval. 10 | 11 | Parameters 12 | ---------- 13 | a : float 14 | the left endpoint of the interval 15 | b : float 16 | the right endpoint of the interval 17 | N : int 18 | the number of points in the design 19 | 20 | Returns 21 | ------- 22 | design, ndarray 23 | N-by-1 matrix that contains the design points in the interval. It does 24 | not contain the endpoints. 25 | """ 26 | y = np.linspace(a, b, N+2) 27 | design = mi.atleast_2d_col(y[1:-1]) 28 | return design 29 | 30 | def maximin_design(vert, N): 31 | """Multivariate maximin design constrained by a polytope. 32 | 33 | Parameters 34 | ---------- 35 | vert : ndarray 36 | the vertices that define the m-dimensional polytope. The shape of `vert` 37 | is M-by-m, where M is the number of vertices. 38 | N : int 39 | the number of points in the design 40 | 41 | Returns 42 | ------- 43 | design : ndarray 44 | N-by-m matrix that contains the design points in the polytope. It does 45 | not contain the vertices. 46 | 47 | Notes 48 | ----- 49 | The objective function used to find the design is the negative of the 50 | minimum distance between points in the design and the given vertices. The 51 | routine uses the scipy.minimize function with the SLSQP method to minimize 52 | the function. The constraints are given by the polytope defined by the 53 | vertices. The scipy.spatial packages turns the vertices into a set of 54 | linear inequality constraints. 55 | 56 | The optimization is nonlinear and nonconvex with many local minima. Any 57 | reasonable local minima is likely to give a good design. However, to 58 | increase robustness, we use three random starting points in the 59 | minimization and use the design with the lowest objective value. 60 | """ 61 | n = vert.shape[1] 62 | C = ConvexHull(vert) 63 | A = np.kron(np.eye(N), C.equations[:,:n]) 64 | b = np.kron(np.ones(N), C.equations[:,n]) 65 | cons = ({'type':'ineq', 66 | 'fun' : lambda x: np.dot(A, x) - b, 67 | 'jac' : lambda x: A}) 68 | 69 | # some tricks for the globalization 70 | curr_state = np.random.get_state() 71 | 72 | np.random.seed(42) 73 | minf = 1e10 74 | minres = [] 75 | 76 | for i in range(3): 77 | y0 = np.random.normal(size=(N, n)) 78 | res = minimize(_maximin_design_obj, y0, args=(vert, ), jac=_maximin_design_grad, constraints=cons, 79 | method='SLSQP', options={'disp':False, 'maxiter':1e2, 'ftol':1e-4}) 80 | if not res.success: 81 | raise Exception('SLSQP failed with message: {}.'.format(res.message)) 82 | if res.fun < minf: 83 | minf = res.fun 84 | minres = res 85 | 86 | np.random.set_state(curr_state) 87 | design = minres.x.reshape((N, n)) 88 | return design 89 | 90 | def gauss_hermite_design(N): 91 | """Tensor product Gauss-Hermite quadrature points. 92 | 93 | Parameters 94 | ---------- 95 | N : int[] 96 | contains the number of points per dimension in the tensor product design 97 | 98 | Returns 99 | ------- 100 | design : ndarray 101 | N-by-m matrix that contains the design points 102 | """ 103 | design = gauss_hermite(N)[0] 104 | return design 105 | 106 | def _maximin_design_obj(y, vert=None): 107 | """Objective function for the maximin design optimization. 108 | 109 | Parameters 110 | ---------- 111 | y : ndarray 112 | contains the coordinates of the points in the design. If there are N 113 | points in n dimensions then `y` is shape ((Nn, )). 114 | vert : ndarray, optional 115 | contains the fixed vertices defining the zonotope 116 | 117 | Notes 118 | ----- 119 | This function returns the minimum squared distance between all points in 120 | the design and between points and vertices. 121 | """ 122 | Ny, n = vert.shape 123 | N = y.size / n 124 | Y = y.reshape((N, n)) 125 | 126 | # get minimum distance among points 127 | D0 = distance_matrix(Y, Y) + 1e5*np.eye(N) 128 | d0 = np.power(D0.flatten(), 2) 129 | d0star = np.amin(d0) 130 | 131 | # get minimum distance between points and vertices 132 | D1 = distance_matrix(Y, vert) 133 | d1 = np.power(D1.flatten(), 2) 134 | d1star = np.amin(d1) 135 | dstar = np.amin([d0star, d1star]) 136 | return -dstar 137 | 138 | def _maximin_design_grad(y, vert=None): 139 | """Gradient of objective function for the maximin design optimization. 140 | 141 | Parameters 142 | ---------- 143 | y : ndarray 144 | contains the coordinates of the points in the design. If there are N 145 | points in n dimensions then `y` is shape ((Nn, )). 146 | vert : ndarray 147 | contains the fixed vertices defining the zonotope 148 | """ 149 | Ny, n = vert.shape 150 | v = vert.reshape((Ny*n, )) 151 | 152 | N = y.size / n 153 | Y = y.reshape((N, n)) 154 | 155 | # get minimum distance among points 156 | D0 = distance_matrix(Y, Y) + 1e5*np.eye(N) 157 | d0 = np.power(D0.flatten(), 2) 158 | d0star, k0star = np.amin(d0), np.argmin(d0) 159 | 160 | # get minimum distance between points and vertices 161 | D1 = distance_matrix(Y, vert) 162 | d1 = np.power(D1.flatten(), 2) 163 | d1star, k1star = np.amin(d1), np.argmin(d1) 164 | 165 | g = np.zeros((N*n, )) 166 | if d0star < d1star: 167 | dstar, kstar = d0star, k0star 168 | istar = kstar/N 169 | jstar = np.mod(kstar, N) 170 | 171 | for k in range(n): 172 | g[istar*n + k] = 2*(y[istar*n + k] - y[jstar*n + k]) 173 | g[jstar*n + k] = 2*(y[jstar*n + k] - y[istar*n + k]) 174 | 175 | else: 176 | dstar, kstar = d1star, k1star 177 | istar = kstar/Ny 178 | jstar = np.mod(kstar, Ny) 179 | 180 | for k in range(n): 181 | g[istar*n + k] = 2*(y[istar*n + k] - v[jstar*n + k]) 182 | 183 | return -g 184 | -------------------------------------------------------------------------------- /active_subspaces/utils/misc.py: -------------------------------------------------------------------------------- 1 | """Miscellaneous utilities.""" 2 | import numpy as np 3 | 4 | class Normalizer(): 5 | """An abstract class for normalizing inputs. 6 | 7 | """ 8 | def normalize(self, X): 9 | """Return corresponding points in normalized domain. 10 | 11 | Parameters 12 | ---------- 13 | X : ndarray 14 | contains all input points one wishes to normalize 15 | 16 | Returns 17 | ------- 18 | X_norm : ndarray 19 | contains the normalized inputs corresponding to `X` 20 | 21 | Notes 22 | ----- 23 | Points in `X` should be oriented as an m-by-n ndarray, where each row 24 | corresponds to an m-dimensional point in the problem domain. 25 | """ 26 | raise NotImplementedError() 27 | 28 | def unnormalize(self, X): 29 | """Return corresponding points shifted and scaled to [-1,1]^m. 30 | 31 | Parameters 32 | ---------- 33 | X : ndarray 34 | contains all input points one wishes to unnormalize 35 | 36 | Returns 37 | ------- 38 | X_unnorm : ndarray 39 | contains the unnormalized inputs corresponding to `X` 40 | 41 | Notes 42 | ----- 43 | Points in `X` should be oriented as an m-by-n ndarray, where each row 44 | corresponds to an m-dimensional point in the normalized domain. 45 | """ 46 | raise NotImplementedError() 47 | 48 | class BoundedNormalizer(Normalizer): 49 | """A class for normalizing bounded inputs. 50 | 51 | Attributes 52 | ---------- 53 | lb : ndarray 54 | a matrix of size m-by-1 that contains lower bounds on the simulation 55 | inputs 56 | ub : ndarray 57 | a matrix of size m-by-1 that contains upper bounds on the simulation 58 | inputs 59 | 60 | See Also 61 | -------- 62 | utils.misc.UnboundedNormalizer 63 | """ 64 | lb, ub = None, None 65 | 66 | def __init__(self, lb, ub): 67 | """Initialize a BoundedNormalizer. 68 | 69 | Parameters 70 | ---------- 71 | lb : ndarray 72 | a matrix of size m-by-1 that contains lower bounds on the simulation 73 | inputs 74 | ub : ndarray 75 | a matrix of size m-by-1 that contains upper bounds on the simulation 76 | inputs 77 | """ 78 | m = lb.size 79 | self.lb = lb.reshape((1, m)) 80 | self.ub = ub.reshape((1, m)) 81 | 82 | def normalize(self, X): 83 | """Return corresponding points shifted and scaled to [-1,1]^m. 84 | 85 | Parameters 86 | ---------- 87 | X : ndarray 88 | contains all input points one wishes to normalize. The shape of `X` 89 | is M-by-m. The components of each row of `X` should be between `lb` 90 | and `ub`. 91 | 92 | Returns 93 | ------- 94 | X_norm : ndarray 95 | contains the normalized inputs corresponding to `X`. The components 96 | of each row of `X_norm` should be between -1 and 1. 97 | """ 98 | X, M, m = process_inputs(X) 99 | X_norm = 2.0 * (X - self.lb) / (self.ub - self.lb) - 1.0 100 | return X_norm 101 | 102 | def unnormalize(self, X): 103 | """Return corresponding points shifted and scaled to `[lb, ub]`. 104 | 105 | Parameters 106 | ---------- 107 | X : ndarray 108 | contains all input points one wishes to unnormalize. The shape of 109 | `X` is M-by-m. The components of each row of `X` should be between 110 | -1 and 1. 111 | 112 | Returns 113 | ------- 114 | X_unnorm : ndarray 115 | contains the unnormalized inputs corresponding to `X`. The 116 | components of each row of `X_unnorm` should be between `lb` and 117 | `ub`. 118 | """ 119 | X, M, m = process_inputs(X) 120 | X_unnorm = (self.ub - self.lb) * (X + 1.0) / 2.0 + self.lb 121 | return X_unnorm 122 | 123 | class UnboundedNormalizer(Normalizer): 124 | """A class for normalizing unbounded, Gaussian inputs to standard normals. 125 | 126 | Attributes 127 | ---------- 128 | mu : ndarray 129 | a matrix of size m-by-1 that contains the mean of the Gaussian 130 | simulation inputs 131 | L : ndarray 132 | a matrix size m-by-m that contains the Cholesky factor of the covariance 133 | matrix of the Gaussian simulation inputs. 134 | 135 | See Also 136 | -------- 137 | utils.misc.BoundedNormalizer 138 | 139 | Notes 140 | ----- 141 | A simulation with unbounded inputs is assumed to have a Gaussian weight 142 | function associated with the inputs. The covariance of the Gaussian weight 143 | function should be full rank. 144 | """ 145 | mu, L = None, None 146 | 147 | def __init__(self, mu, C): 148 | """Initialize an UnboundedNormalizer. 149 | 150 | Parameters 151 | ---------- 152 | mu : ndarray 153 | a matrix of size m-by-1 that contains the mean of the Gaussian 154 | simulation inputs 155 | C : ndarray 156 | a matrix of size m-by-m that contains the covariance matrix of the 157 | Gaussian simulation inputs 158 | """ 159 | self.mu = mu.reshape((1, mu.size)) 160 | self.L = np.linalg.cholesky(C) 161 | 162 | def normalize(self, X): 163 | """Return points transformed to a standard normal distribution. 164 | 165 | Parameters 166 | ---------- 167 | X : ndarray 168 | contains all input points one wishes to normalize. The shape of `X` 169 | is M-by-m. The components of each row of `X` should be a draw from a 170 | Gaussian with mean `mu` and covariance `C`. 171 | 172 | Returns 173 | ------- 174 | X_norm : ndarray 175 | contains the normalized inputs corresponding to `X`. The components 176 | of each row of `X_norm` should be draws from a standard multivariate 177 | normal distribution. 178 | """ 179 | X, M, m = process_inputs(X) 180 | X0 = X - self.mu 181 | X_norm = np.linalg.solve(self.L,X0.T).T 182 | return X_norm 183 | 184 | def unnormalize(self, X): 185 | """Transform points to original Gaussian. 186 | 187 | Return corresponding points transformed to draws from a Gaussian 188 | distribution with mean `mu` and covariance `C`. 189 | 190 | Parameters 191 | ---------- 192 | X : ndarray 193 | contains all input points one wishes to unnormalize. The shape of 194 | `X` is M-by-m. The components of each row of `X` should be draws 195 | from a standard multivariate normal. 196 | 197 | Returns 198 | ------- 199 | X_unnorm : ndarray 200 | contains the unnormalized inputs corresponding to `X`. The 201 | components of each row of `X_unnorm` should represent draws from a 202 | multivariate normal with mean `mu` and covariance `C`. 203 | """ 204 | X, M, m = process_inputs(X) 205 | X0 = np.dot(X,self.L.T) 206 | X_unnorm = X0 + self.mu 207 | return X_unnorm 208 | 209 | def process_inputs(X): 210 | """Check a matrix of input values for the right shape. 211 | 212 | Parameters 213 | ---------- 214 | X : ndarray 215 | contains input points. The shape of `X` should be M-by-m. 216 | 217 | Returns 218 | ------- 219 | X : ndarray 220 | the same as the input 221 | M : int 222 | number of rows in `X` 223 | m : int 224 | number of columns in `X` 225 | """ 226 | if len(X.shape) == 2: 227 | M, m = X.shape 228 | else: 229 | raise ValueError('The inputs X should be a two-d numpy array.') 230 | 231 | X = X.reshape((M, m)) 232 | return X, M, m 233 | 234 | def process_inputs_outputs(X, f): 235 | """Check matrix of input values and a vector of outputs for correct shapes. 236 | 237 | Parameters 238 | ---------- 239 | X : ndarray 240 | contains input points. The shape of `X` should be M-by-m. 241 | f : ndarray 242 | M-by-1 matrix 243 | 244 | Returns 245 | ------- 246 | X : ndarray 247 | the same as the input 248 | f : ndarray 249 | the same as the output 250 | M : int 251 | number of rows in `X` 252 | m : int 253 | number of columns in `X` 254 | """ 255 | X, M, m = process_inputs(X) 256 | 257 | if len(f.shape) == 2: 258 | Mf, mf = f.shape 259 | else: 260 | raise ValueError('The outputs f should be a two-d numpy array.') 261 | 262 | if Mf != M: 263 | raise Exception('Different number of inputs and outputs.') 264 | 265 | if mf != 1: 266 | raise Exception('Only scalar-valued functions.') 267 | 268 | f = f.reshape((M, 1)) 269 | 270 | return X, f, M, m 271 | 272 | def conditional_expectations(f, ind): 273 | """Compute conditional expectations and variances for given function values. 274 | 275 | Parameters 276 | ---------- 277 | f : ndarray 278 | an ndarry of function evaluations 279 | ind : ndarray[int] 280 | index array that tells which values of `f` correspond to the same value 281 | for the active variable. 282 | 283 | Returns 284 | ------- 285 | Ef : ndarray 286 | an ndarray containing the conditional expectations 287 | Vf : ndarray 288 | an ndarray containing the conditional variances 289 | 290 | Notes 291 | ----- 292 | This function computes the mean and variance for all values in the ndarray 293 | `f` that have the same index in `ind`. The indices in `ind` correspond to 294 | values of the active variables. 295 | """ 296 | 297 | n = int(np.amax(ind)) + 1 298 | 299 | Ef, Vf = np.zeros((n, 1)), np.zeros((n, 1)) 300 | for i in range(n): 301 | fi = f[ind == i] 302 | Ef[i] = np.mean(fi) 303 | Vf[i] = np.var(fi) 304 | return Ef, Vf 305 | 306 | # thanks to Trent for these functions!!! 307 | def atleast_2d_col(A): 308 | """Wrapper for `atleast_2d(A, 'col')` 309 | 310 | Notes 311 | ----- 312 | Thanks to Trent Lukaczyk for these functions! 313 | """ 314 | return atleast_2d(A,'col') 315 | 316 | def atleast_2d_row(A): 317 | """Wrapper for `atleast_2d(A, 'row')` 318 | 319 | Notes 320 | ----- 321 | Thanks to Trent Lukaczyk for these functions! 322 | """ 323 | return atleast_2d(A,'row') 324 | 325 | def atleast_2d(A, oned_as='row'): 326 | """Ensures the array `A` is at least two dimensions. 327 | 328 | Parameters 329 | ---------- 330 | A : ndarray 331 | matrix 332 | oned_as : str, optional 333 | should be either 'row' or 'col'. It determines whether the array `A` 334 | should be expanded as a 2d row or 2d column (default 'row') 335 | """ 336 | 337 | # not an array yet 338 | if not isinstance(A,(np.ndarray,np.matrixlib.defmatrix.matrix)): 339 | if not isinstance(A,(list,tuple)): 340 | A = [A] 341 | A = np.array(A) 342 | 343 | # check rank 344 | if np.ndim(A) < 2: 345 | # expand row or col 346 | if oned_as == 'row': 347 | A = A[None,:] 348 | elif oned_as == 'col': 349 | A = A[:,None] 350 | else: 351 | raise Exception , "oned_as must be 'row' or 'col' " 352 | 353 | return A 354 | 355 | 356 | 357 | 358 | 359 | -------------------------------------------------------------------------------- /active_subspaces/utils/qp_solver.py: -------------------------------------------------------------------------------- 1 | """Solvers for the linear and quadratic programs in active subspaces.""" 2 | import numpy as np 3 | from scipy.optimize import linprog, minimize 4 | 5 | # checking to see if system has gurobi 6 | try: 7 | HAS_GUROBI = True 8 | import gurobipy as gpy 9 | except ImportError, e: 10 | HAS_GUROBI = False 11 | pass 12 | 13 | # string constants for QP solver names 14 | solver_SCIPY = 'SCIPY' 15 | solver_GUROBI = 'GUROBI' 16 | 17 | class QPSolver(): 18 | """A class for solving linear and quadratic programs. 19 | 20 | Attributes 21 | ---------- 22 | solver : str 23 | identifies which linear program software to use 24 | 25 | Notes 26 | ----- 27 | The class checks to see if Gurobi is present. If it is, it uses Gurobi to 28 | solve the linear and quadratic programs. Otherwise, it uses scipy 29 | implementations to solve the linear and quadratic programs. 30 | """ 31 | solver = None 32 | 33 | def __init__(self, solver='GUROBI'): 34 | """Initialize a QPSolver. 35 | 36 | Parameters 37 | ---------- 38 | solver : str, optional 39 | identifies which linear program software to use. Options are 40 | 'GUROBI' and 'SCIPY'. (default 'GUROBI') 41 | """ 42 | 43 | if solver==solver_GUROBI and HAS_GUROBI: 44 | self.solver = solver_GUROBI 45 | elif solver=='SCIPY': 46 | self.solver = solver_SCIPY 47 | else: 48 | self.solver = solver_SCIPY 49 | 50 | 51 | def linear_program_eq(self, c, A, b, lb, ub): 52 | """Solves an equality constrained linear program with variable bounds. 53 | 54 | This method returns the minimizer of the following linear program. 55 | 56 | minimize c^T x 57 | subject to A x = b 58 | lb <= x <= ub 59 | 60 | Parameters 61 | ---------- 62 | c : ndarray 63 | m-by-1 matrix for the linear objective function 64 | A : ndarray 65 | M-by-m matrix that contains the coefficients of the linear equality 66 | constraints 67 | b : ndarray 68 | M-by-1 matrix that is the right hand side of the equality 69 | constraints 70 | lb : ndarray 71 | m-by-1 matrix that contains the lower bounds on the variables 72 | ub : ndarray 73 | m-by-1 matrix that contains the upper bounds on the variables 74 | 75 | Returns 76 | ------- 77 | x : ndarray 78 | m-by-1 matrix that is the minimizer of the linear program 79 | """ 80 | if self.solver == solver_SCIPY: 81 | return _scipy_linear_program_eq(c, A, b, lb, ub) 82 | elif self.solver == solver_GUROBI: 83 | return _gurobi_linear_program_eq(c, A, b, lb, ub) 84 | else: 85 | raise ValueError('QP solver {} not available'.format(self.solver)) 86 | 87 | def linear_program_ineq(self, c, A, b): 88 | """Solves an inequality constrained linear program. 89 | 90 | This method returns the minimizer of the following linear program. 91 | 92 | minimize c^T x 93 | subject to A x >= b 94 | 95 | Parameters 96 | ---------- 97 | c : ndarray 98 | m-by-1 matrix for the linear objective function 99 | A : ndarray 100 | M-by-m matrix that contains the coefficients of the linear equality 101 | constraints 102 | b : ndarray 103 | size M-by-1 matrix that is the right hand side of the equality 104 | constraints 105 | 106 | Returns 107 | ------- 108 | x : ndarray 109 | m-by-1 matrix that is the minimizer of the linear program 110 | 111 | """ 112 | if self.solver == solver_SCIPY: 113 | return _scipy_linear_program_ineq(c, A, b) 114 | elif self.solver == solver_GUROBI: 115 | return _gurobi_linear_program_ineq(c, A, b) 116 | else: 117 | raise ValueError('QP solver {} not available'.format(self.solver)) 118 | 119 | def quadratic_program_bnd(self, c, Q, lb, ub): 120 | """Solves a quadratic program with variable bounds. 121 | 122 | This method returns the minimizer of the following linear program. 123 | 124 | minimize c^T x + x^T Q x 125 | subject to lb <= x <= ub 126 | 127 | Parameters 128 | ---------- 129 | c : ndarray 130 | m-by-1 matrix that contains the coefficients of the linear term in 131 | the objective function 132 | Q : ndarray 133 | m-by-m matrix that contains the coefficients of the quadratic term 134 | in the objective function 135 | lb : ndarray 136 | m-by-1 matrix that contains the lower bounds on the variables 137 | ub : ndarray 138 | m-by-1 matrix that contains the upper bounds on the variables 139 | 140 | Returns 141 | ------- 142 | x : ndarray 143 | m-by-1 matrix that is the minimizer of the quadratic program 144 | 145 | """ 146 | if self.solver == solver_SCIPY: 147 | return _scipy_quadratic_program_bnd(c, Q, lb, ub) 148 | elif self.solver == solver_GUROBI: 149 | return _gurobi_quadratic_program_bnd(c, Q, lb, ub) 150 | else: 151 | raise ValueError('QP solver {} not available'.format(self.solver)) 152 | 153 | def quadratic_program_ineq(self, c, Q, A, b): 154 | """Solves an inequality constrained quadratic program. 155 | 156 | 157 | This method returns the minimizer of the following quadratic program. 158 | 159 | minimize c^T x + x^T Q x 160 | subject to A x >= b 161 | 162 | Parameters 163 | ---------- 164 | c : ndarray 165 | m-by-1 matrix that contains the coefficients of the linear term in 166 | the objective function 167 | Q : ndarray 168 | m-by-m matrix that contains the coefficients of the quadratic term 169 | in the objective function 170 | A : ndarray 171 | M-by-m matrix that contains the coefficients of the linear equality 172 | constraints 173 | b : ndarray 174 | M-by-1 matrix that is the right hand side of the equality 175 | constraints 176 | 177 | Returns 178 | ------- 179 | x : ndarray 180 | m-by-1 matrix that is the minimizer of the quadratic program. 181 | 182 | """ 183 | if self.solver == solver_SCIPY: 184 | return _scipy_quadratic_program_ineq(c, Q, A, b) 185 | elif self.solver == solver_GUROBI: 186 | return _gurobi_quadratic_program_ineq(c, Q, A, b) 187 | else: 188 | raise ValueError('QP solver {} not available'.format(self.solver)) 189 | 190 | def _scipy_linear_program_eq(c, A, b, lb, ub): 191 | 192 | c = c.reshape((c.size,)) 193 | b = b.reshape((b.size,)) 194 | 195 | # make bounds 196 | bounds = [] 197 | for i in range(lb.size): 198 | bounds.append((lb[i,0], ub[i,0])) 199 | 200 | res = linprog(c, A_eq=A, b_eq=b, bounds=bounds, options={"disp": False}) 201 | if res.success: 202 | return res.x.reshape((c.size,1)) 203 | else: 204 | np.savez('bad_scipy_lp_eq_{:010d}'.format(np.random.randint(int(1e9))), 205 | c=c, A=A, b=b, lb=lb, ub=ub, res=res) 206 | raise Exception('Scipy did not solve the LP. Blame Scipy.') 207 | return None 208 | 209 | def _scipy_linear_program_ineq(c, A, b): 210 | 211 | c = c.reshape((c.size,)) 212 | b = b.reshape((b.size,)) 213 | 214 | # make unbounded bounds 215 | bounds = [] 216 | for i in range(c.size): 217 | bounds.append((None, None)) 218 | 219 | A_ub, b_ub = -A, -b 220 | res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, options={"disp": False}) 221 | if res.success: 222 | return res.x.reshape((c.size,1)) 223 | else: 224 | np.savez('bad_scipy_lp_ineq_{:010d}'.format(np.random.randint(int(1e9))), 225 | c=c, A=A, b=b, res=res) 226 | raise Exception('Scipy did not solve the LP. Blame Scipy.') 227 | return None 228 | 229 | def _scipy_quadratic_program_bnd(c, Q, lb, ub): 230 | 231 | # define the objective and gradient 232 | def fun(x): 233 | f = np.dot(x, c) + np.dot(x, np.dot(Q, x.T)) 234 | return f[0] 235 | 236 | def jac(x): 237 | j = c.T + 2.0*np.dot(x, Q) 238 | return j[0] 239 | 240 | # make bounds 241 | bounds = [] 242 | for i in range(lb.size): 243 | bounds.append((lb[i,0],ub[i,0])) 244 | 245 | x0 = np.zeros((c.size,)) 246 | res = minimize(fun, x0, method='L-BFGS-B', jac=jac, 247 | bounds=bounds, options={"disp": False}) 248 | 249 | if res.success: 250 | xstar = res.x 251 | if isinstance(xstar, float): 252 | xstar = np.array([[xstar]]) 253 | return xstar.reshape((c.size,1)) 254 | else: 255 | np.savez('bad_scipy_qp_bnd_{:010d}'.format(np.random.randint(int(1e9))), 256 | c=c, Q=Q, lb=lb, ub=ub, res=res) 257 | raise Exception('Scipy did not solve the LP. Blame Scipy.') 258 | return None 259 | 260 | def _scipy_quadratic_program_ineq(c, Q, A, b): 261 | 262 | b = b.reshape((b.size,)) 263 | 264 | # define the objective and gradient 265 | def fun(x): 266 | f = np.dot(x, c) + np.dot(x, np.dot(Q, x.T)) 267 | return f[0] 268 | 269 | def jac(x): 270 | j = c.T + 2.0*np.dot(x, Q) 271 | return j[0] 272 | 273 | # inequality constraints 274 | cons = ({'type':'ineq', 275 | 'fun' : lambda x: np.dot(A, x) - b, 276 | 'jac' : lambda x: A}) 277 | 278 | x0 = np.zeros((c.size,)) 279 | res = minimize(fun, x0, method='SLSQP', jac=jac, 280 | constraints=cons, options={"disp": False}) 281 | 282 | if res.success: 283 | xstar = res.x 284 | if isinstance(xstar, float): 285 | xstar = np.array([[xstar]]) 286 | return xstar.reshape((c.size,1)) 287 | else: 288 | np.savez('bad_scipy_qp_ineq_{:010d}'.format(np.random.randint(int(1e9))), 289 | c=c, Q=Q, A=A, b=b, res=res) 290 | raise Exception('Scipy did not solve the LP. Blame Scipy.') 291 | return None 292 | 293 | def _gurobi_linear_program_eq(c, A, b, lb, ub): 294 | 295 | m,n = A.shape 296 | model = gpy.Model() 297 | model.setParam('OutputFlag', 0) 298 | 299 | # Add variables to model 300 | vars = [] 301 | for j in range(n): 302 | vars.append(model.addVar(lb=lb[j,0], ub=ub[j,0], vtype=gpy.GRB.CONTINUOUS)) 303 | model.update() 304 | 305 | # Populate linear constraints 306 | for i in range(m): 307 | expr = gpy.LinExpr() 308 | for j in range(n): 309 | expr += A[i,j]*vars[j] 310 | model.addConstr(expr, gpy.GRB.EQUAL, b[i,0]) 311 | 312 | # Populate objective 313 | obj = gpy.LinExpr() 314 | for j in range(n): 315 | obj += c[j,0]*vars[j] 316 | model.setObjective(obj) 317 | model.update() 318 | 319 | # Solve 320 | model.optimize() 321 | 322 | if model.status == gpy.GRB.OPTIMAL: 323 | return np.array(model.getAttr('x', vars)).reshape((n,1)) 324 | else: 325 | np.savez('bad_gurobi_lp_eq_{:010d}'.format(np.random.randint(int(1e9))), 326 | c=c, A=A, b=b, lb=lb, ub=ub, model=model) 327 | raise Exception('Gurobi did not solve the LP. Blame Gurobi.') 328 | return None 329 | 330 | 331 | def _gurobi_linear_program_ineq(c, A, b): 332 | 333 | m,n = A.shape 334 | model = gpy.Model() 335 | model.setParam('OutputFlag', 0) 336 | 337 | # Add variables to model 338 | vars = [] 339 | for j in range(n): 340 | vars.append(model.addVar(lb=-gpy.GRB.INFINITY, 341 | ub=gpy.GRB.INFINITY, vtype=gpy.GRB.CONTINUOUS)) 342 | model.update() 343 | 344 | # Populate linear constraints 345 | for i in range(m): 346 | expr = gpy.LinExpr() 347 | for j in range(n): 348 | expr += A[i,j]*vars[j] 349 | model.addConstr(expr, gpy.GRB.GREATER_EQUAL, b[i,0]) 350 | 351 | # Populate objective 352 | obj = gpy.LinExpr() 353 | for j in range(n): 354 | obj += c[j,0]*vars[j] 355 | model.setObjective(obj) 356 | model.update() 357 | 358 | # Solve 359 | model.optimize() 360 | 361 | if model.status == gpy.GRB.OPTIMAL: 362 | return np.array(model.getAttr('x', vars)).reshape((n,1)) 363 | else: 364 | np.savez('bad_gurobi_lp_ineq_{:010d}'.format(np.random.randint(int(1e9))), 365 | c=c, A=A, b=b, model=model) 366 | raise Exception('Gurobi did not solve the LP. Blame Gurobi.') 367 | return None 368 | 369 | def _gurobi_quadratic_program_bnd(c, Q, lb, ub): 370 | 371 | n = Q.shape[0] 372 | model = gpy.Model() 373 | model.setParam('OutputFlag', 0) 374 | 375 | # Add variables to model 376 | vars = [] 377 | for j in range(n): 378 | vars.append(model.addVar(lb=lb[j,0], ub=ub[j,0], vtype=gpy.GRB.CONTINUOUS)) 379 | model.update() 380 | 381 | # Populate objective 382 | obj = gpy.QuadExpr() 383 | for i in range(n): 384 | for j in range(n): 385 | obj += Q[i,j]*vars[i]*vars[j] 386 | 387 | for j in range(n): 388 | obj += c[j,0]*vars[j] 389 | model.setObjective(obj) 390 | model.update() 391 | 392 | # Solve 393 | model.optimize() 394 | 395 | if model.status == gpy.GRB.OPTIMAL: 396 | return np.array(model.getAttr('x', vars)).reshape((n,1)) 397 | else: 398 | np.savez('bad_gurobi_qp_bnd_{:010d}'.format(np.random.randint(int(1e9))), 399 | c=c, Q=Q, lb=lb, ub=ub, model=model) 400 | raise Exception('Gurobi did not solve the QP. Blame Gurobi.') 401 | return None 402 | 403 | def _gurobi_quadratic_program_ineq(c, Q, A, b): 404 | 405 | m,n = A.shape 406 | model = gpy.Model() 407 | model.setParam('OutputFlag', 0) 408 | 409 | # Add variables to model 410 | vars = [] 411 | for j in range(n): 412 | vars.append(model.addVar(lb=-gpy.GRB.INFINITY, 413 | ub=gpy.GRB.INFINITY, vtype=gpy.GRB.CONTINUOUS)) 414 | model.update() 415 | 416 | # Populate linear constraints 417 | for i in range(m): 418 | expr = gpy.LinExpr() 419 | for j in range(n): 420 | expr += A[i,j]*vars[j] 421 | model.addConstr(expr, gpy.GRB.GREATER_EQUAL, b[i,0]) 422 | 423 | # Populate objective 424 | obj = gpy.QuadExpr() 425 | for i in range(n): 426 | for j in range(n): 427 | obj += Q[i,j]*vars[i]*vars[j] 428 | 429 | for j in range(n): 430 | obj += c[j,0]*vars[j] 431 | model.setObjective(obj) 432 | model.update() 433 | 434 | # Solve 435 | model.optimize() 436 | 437 | if model.status == gpy.GRB.OPTIMAL: 438 | return np.array(model.getAttr('x', vars)).reshape((n,1)) 439 | else: 440 | np.savez('bad_gurobi_qp_ineq_{:010d}'.format(np.random.randint(int(1e9))), 441 | c=c, Q=Q, A=A, b=b, model=model) 442 | raise Exception('Gurobi did not solve the QP. Blame Gurobi.') 443 | return None 444 | -------------------------------------------------------------------------------- /active_subspaces/utils/quadrature.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gaussian quadrature utilities for use with the Python Active-subspaces Utility 3 | Library. 4 | """ 5 | 6 | import numpy as np 7 | import misc as mi 8 | 9 | def r_hermite(N): 10 | """Recurrence coefficients for the Hermite orthogonal polynomials. 11 | 12 | Parameters 13 | ---------- 14 | N : int 15 | the number of recurrence coefficients 16 | 17 | Returns 18 | ------- 19 | ab : ndarray 20 | an `N`-by-2 array of the recurrence coefficients 21 | 22 | See Also 23 | -------- 24 | utils.quadrature.jacobi_matrix 25 | utils.quadrature.gauss_hermite 26 | 27 | Notes 28 | ----- 29 | This computation is inspired by Walter Gautschi's code at 30 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 31 | """ 32 | 33 | if not isinstance(N, int): 34 | raise TypeError('N must be an int') 35 | 36 | if N <= 0: 37 | raise ValueError('Parameters out of range.') 38 | 39 | if N == 1: 40 | return np.array([[0.0, 1.0]]) 41 | else: 42 | n = np.array(range(1, N+1)) 43 | b = np.vstack((1.0, n.reshape((N, 1)))) 44 | a = np.zeros(b.shape) 45 | ab = np.hstack((a, b)) 46 | return ab 47 | 48 | def r_jacobi(N,l,r,a,b): 49 | """Recurrence coefficients for the Legendre orthogonal polynomials. 50 | 51 | Parameters 52 | ---------- 53 | N : int 54 | the number of recurrence coefficients 55 | l : float 56 | the left endpoint of the interval 57 | r : float 58 | the right endpoint of the interval 59 | a : float 60 | Jacobi weight parameter 61 | b : float 62 | Jacobi weight parameter 63 | 64 | Returns 65 | ------- 66 | ab : ndarray 67 | an `N`-by-2 array of the recurrence coefficients 68 | 69 | See Also 70 | -------- 71 | utils.quadrature.jacobi_matrix 72 | utils.quadrature.gauss_legendre 73 | 74 | Notes 75 | ----- 76 | This computation is inspired by Walter Gautschi's code at 77 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 78 | """ 79 | 80 | if not isinstance(N, int): 81 | raise TypeError('N must be an int') 82 | 83 | if N <= 0: 84 | raise ValueError('Parameters out of range.') 85 | 86 | a0 = (b-a)/(a+b+2.0) 87 | ab = np.zeros((N+1,2)) 88 | b2a2 = b**2 - a**2 89 | s, o = (r-l)/2.0, l + (r-l)/2.0 90 | 91 | # first row 92 | ab[0,0] = s*a0 + o 93 | ab[0,1] = 1 94 | 95 | for k in range(1,N+1): 96 | ab[k,0] = s*b2a2/((2*(k)+a+b)*(2*(k+1) + a+b)) + o 97 | if k==1: 98 | ab[k,1] = ((r-l)**2*(k)*(k+a)*(k+b)) / ((2.0*(k)+a+b)**2*(2.0*(k)+a+b+1)) 99 | else: 100 | ab[k,1] = ((r-l)**2*(k)*(k+a)*(k+b)*(k+a+b)) / ((2.0*(k)+a+b)**2*(2.0*(k)+a+b+1)*(2.0*(k)+a+b-1)) 101 | return ab 102 | 103 | def jacobi_matrix(ab): 104 | """Tri-diagonal Jacobi matrix of recurrence coefficients. 105 | 106 | Parameters 107 | ---------- 108 | ab : ndarray 109 | N-by-2 array of recurrence coefficients 110 | 111 | Returns 112 | ------- 113 | J : ndarray 114 | (N-1)-by-(N-1) symmetric, tridiagonal Jacobi matrix associated with the 115 | orthogonal polynomials 116 | 117 | See Also 118 | -------- 119 | utils.quadrature.r_hermite 120 | utils.quadrature.gauss_hermite 121 | 122 | Notes 123 | ----- 124 | This computation is inspired by Walter Gautschi's code at 125 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 126 | """ 127 | 128 | if len(ab.shape) != 2: 129 | raise ValueError('ab must be 2 dimensional') 130 | 131 | if ab.shape[1] != 2: 132 | raise ValueError('ab must have two columns') 133 | 134 | n = ab.shape[0] - 1 135 | if n == 0: 136 | return ab[0,0] 137 | else: 138 | J = np.zeros((n, n)) 139 | J[0,0] = ab[0,0] 140 | J[0,1] = np.sqrt(ab[1,1]) 141 | for i in range(1,n-1): 142 | J[i,i] = ab[i,0] 143 | J[i,i-1] = np.sqrt(ab[i,1]) 144 | J[i,i+1] = np.sqrt(ab[i+1,1]) 145 | J[n-1,n-1] = ab[n-1,0] 146 | J[n-1,n-2] = np.sqrt(ab[n-1,1]) 147 | return J 148 | 149 | def gl1d(N): 150 | """One-dimensional Gauss-Legendre quadrature rule. 151 | 152 | Parameters 153 | ---------- 154 | N : int 155 | number of nodes in the quadrature rule 156 | 157 | Returns 158 | ------- 159 | x : ndarray 160 | N-by-1 array of quadrature nodes 161 | w : ndarray 162 | N-by-1 array of quadrature weights 163 | 164 | See Also 165 | -------- 166 | utils.quadrature.gauss_legendre 167 | 168 | Notes 169 | ----- 170 | This computation is inspired by Walter Gautschi's code at 171 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 172 | """ 173 | 174 | return g1d(N, 'Legendre') 175 | 176 | def gh1d(N): 177 | """One-dimensional Gauss-Hermite quadrature rule. 178 | 179 | Parameters 180 | ---------- 181 | N : int 182 | number of nodes in the quadrature rule 183 | 184 | Returns 185 | ------- 186 | x : ndarray 187 | N-by-1 array of quadrature nodes 188 | w : ndarray 189 | N-by-1 array of quadrature weights 190 | 191 | See Also 192 | -------- 193 | utils.quadrature.gauss_hermite 194 | 195 | Notes 196 | ----- 197 | This computation is inspired by Walter Gautschi's code at 198 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 199 | """ 200 | 201 | return g1d(N, 'Hermite') 202 | 203 | def g1d(N, quadtype): 204 | """One-dimensional Gaussian quadrature rule. 205 | 206 | Parameters 207 | ---------- 208 | N : int 209 | number of nodes in the quadrature rule 210 | quadtype : str 211 | type of quadrature rule {'Legendre', 'Hermite'} 212 | 213 | Returns 214 | ------- 215 | x : ndarray 216 | N-by-1 array of quadrature nodes 217 | w : ndarray 218 | N-by-1 array of quadrature weights 219 | 220 | See Also 221 | -------- 222 | utils.quadrature.gauss_hermite 223 | 224 | Notes 225 | ----- 226 | This computation is inspired by Walter Gautschi's code at 227 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 228 | """ 229 | 230 | if N > 1: 231 | if quadtype == 'Hermite': 232 | ab = r_hermite(N) 233 | elif quadtype == 'Legendre': 234 | ab = r_jacobi(N, -1, 1, 0, 0) 235 | else: 236 | raise ValueError('quadtype must be Legendre or Hermite') 237 | 238 | J = jacobi_matrix(ab) 239 | e, V = np.linalg.eig(J) 240 | ind = np.argsort(e) 241 | x = e[ind].reshape((N, 1)) 242 | x[np.fabs(x) < 1e-12] = 0.0 243 | w = (V[0,ind]*V[0,ind]).reshape((N, 1)) 244 | else: 245 | x, w = np.array([[0.0]]),np.array([[1.0]]) 246 | return x, w 247 | 248 | def gauss_hermite(N): 249 | """Tensor product Gauss-Hermite quadrature rule. 250 | 251 | Parameters 252 | ---------- 253 | N : int[] 254 | number of nodes in each dimension of the quadrature rule 255 | 256 | Returns 257 | ------- 258 | x : ndarray 259 | N-by-1 array of quadrature nodes 260 | w : ndarray 261 | N-by-1 array of quadrature weights 262 | 263 | Notes 264 | ----- 265 | This computation is inspired by Walter Gautschi's code at 266 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 267 | """ 268 | 269 | if isinstance(N, int): 270 | N = [N] 271 | 272 | if type(N) is not list: 273 | raise TypeError('N must be a list.') 274 | 275 | if len(N) == 1: 276 | x, w = gh1d(N[0]) 277 | else: 278 | x = np.array([[1.0]]) 279 | w = np.array([[1.0]]) 280 | for n in N: 281 | xi, wi = gh1d(n) 282 | 283 | xL = np.kron(x.copy(), np.ones(xi.shape)) 284 | xU = np.kron(np.ones((x.shape[0],1)), xi) 285 | x = np.hstack((xL, xU)) 286 | w = np.kron(w.copy(), wi) 287 | x, w = np.atleast_2d(x[:,1:]), mi.atleast_2d_col(w) 288 | 289 | return x, w 290 | 291 | def gauss_legendre(N): 292 | """Tensor product Gauss-Legendre quadrature rule. 293 | 294 | Parameters 295 | ---------- 296 | N : int[] 297 | number of nodes in each dimension of the quadrature rule 298 | 299 | Returns 300 | ------- 301 | x : ndarray 302 | N-by-1 array of quadrature nodes 303 | w : ndarray 304 | N-by-1 array of quadrature weights 305 | 306 | Notes 307 | ----- 308 | This computation is inspired by Walter Gautschi's code at 309 | https://www.cs.purdue.edu/archives/2002/wxg/codes/OPQ.html. 310 | """ 311 | 312 | if isinstance(N, int): 313 | N = [N] 314 | 315 | if type(N) is not list: 316 | raise TypeError('N must be a list.') 317 | 318 | if len(N) == 1: 319 | x, w = gl1d(N[0]) 320 | else: 321 | x = np.array([[1.0]]) 322 | w = np.array([[1.0]]) 323 | for n in N: 324 | xi, wi = gl1d(n) 325 | 326 | xL = np.kron(x.copy(), np.ones(xi.shape)) 327 | xU = np.kron(np.ones((x.shape[0],1)), xi) 328 | x = np.hstack((xL, xU)) 329 | w = np.kron(w.copy(), wi) 330 | x, w = np.atleast_2d(x[:,1:]), mi.atleast_2d_col(w) 331 | 332 | return x, w 333 | -------------------------------------------------------------------------------- /active_subspaces/utils/simrunners.py: -------------------------------------------------------------------------------- 1 | """Utilities for running several simulations at different inputs.""" 2 | 3 | import numpy as np 4 | import time 5 | from misc import process_inputs 6 | 7 | class SimulationRunner(): 8 | """A class for running several simulations at different input values. 9 | 10 | Attributes 11 | ---------- 12 | fun : function 13 | runs the simulation for a fixed value of the input parameters, given as 14 | an ndarray 15 | 16 | See Also 17 | -------- 18 | utils.simrunners.SimulationGradientRunner 19 | 20 | Notes 21 | ----- 22 | The function fun should take an ndarray of size 1-by-m and return a float. 23 | This float is the quantity of interest from the simulation. Often, the 24 | function is a wrapper to a larger simulation code. 25 | """ 26 | fun = None 27 | 28 | def __init__(self, fun): 29 | """Initialize a SimulationRunner. 30 | 31 | Parameters 32 | ---------- 33 | fun : function 34 | a function that runs the simulation for a fixed value of the input 35 | parameters, given as an ndarray. This function returns the quantity 36 | of interest from the model. Often, this function is a wrapper to a 37 | larger simulation code. 38 | """ 39 | if not hasattr(fun, '__call__'): 40 | raise TypeError('fun should be a callable function.') 41 | 42 | self.fun = fun 43 | 44 | def run(self, X): 45 | """Run the simulation at several input values. 46 | 47 | Parameters 48 | ---------- 49 | X : ndarray 50 | contains all input points where one wishes to run the simulation. If 51 | the simulation takes m inputs, then `X` must have shape M-by-m, 52 | where M is the number of simulations to run. 53 | 54 | Returns 55 | ------- 56 | F : ndarray 57 | contains the simulation output at each given input point. The shape 58 | of `F` is M-by-1. 59 | 60 | Notes 61 | ----- 62 | In principle, the simulation calls can be executed independently and in 63 | parallel. Right now this function uses a sequential for-loop. Future 64 | development will take advantage of multicore architectures to 65 | parallelize this for-loop. 66 | """ 67 | 68 | # right now this just wraps a sequential for-loop. 69 | # should be parallelized 70 | 71 | X, M, m = process_inputs(X) 72 | F = np.zeros((M, 1)) 73 | 74 | # TODO: provide some timing information 75 | # start = time.time() 76 | 77 | for i in range(M): 78 | F[i] = self.fun(X[i,:].reshape((1,m))) 79 | 80 | # TODO: provide some timing information 81 | # end = time.time() - start 82 | 83 | return F 84 | 85 | class SimulationGradientRunner(): 86 | """Evaluates gradients at several input values. 87 | 88 | 89 | A class for running several simulations at different input values that 90 | return the gradients of the quantity of interest. 91 | 92 | Attributes 93 | ---------- 94 | dfun : function 95 | a function that runs the simulation for a fixed value of the input 96 | parameters, given as an ndarray. It returns the gradient of the quantity 97 | of interest at the given input. 98 | 99 | See Also 100 | -------- 101 | utils.simrunners.SimulationRunner 102 | 103 | Notes 104 | ----- 105 | The function dfun should take an ndarray of size 1-by-m and return an 106 | ndarray of shape 1-by-m. This ndarray is the gradient of the quantity of 107 | interest from the simulation. Often, the function is a wrapper to a larger 108 | simulation code. 109 | """ 110 | dfun = None 111 | 112 | def __init__(self, dfun): 113 | """Initialize a SimulationGradientRunner. 114 | 115 | Parameters 116 | ---------- 117 | dfun : function 118 | a function that runs the simulation for a fixed value of the input 119 | parameters, given as an ndarray. It returns the gradient of the 120 | quantity of interest at the given input. 121 | """ 122 | if not hasattr(dfun, '__call__'): 123 | raise TypeError('fun should be a callable function.') 124 | 125 | self.dfun = dfun 126 | 127 | def run(self, X): 128 | """Run at several input values. 129 | 130 | Run the simulation at several input values and return the gradients of 131 | the quantity of interest. 132 | 133 | Parameters 134 | ---------- 135 | X : ndarray 136 | contains all input points where one wishes to run the simulation. 137 | If the simulation takes m inputs, then `X` must have shape M-by-m, 138 | where M is the number of simulations to run. 139 | 140 | Returns 141 | ------- 142 | dF : ndarray 143 | contains the gradient of the quantity of interest at each given 144 | input point. The shape of `dF` is M-by-m. 145 | 146 | Notes 147 | ----- 148 | In principle, the simulation calls can be executed independently and in 149 | parallel. Right now this function uses a sequential for-loop. Future 150 | development will take advantage of multicore architectures to 151 | parallelize this for-loop. 152 | """ 153 | 154 | # right now this just wraps a sequential for-loop. 155 | # should be parallelized 156 | 157 | X, M, m = process_inputs(X) 158 | dF = np.zeros((M, m)) 159 | 160 | # TODO: provide some timing information 161 | # start = time.time() 162 | 163 | for i in range(M): 164 | df = self.dfun(X[i,:].reshape((1,m))) 165 | dF[i,:] = df.reshape((1,m)) 166 | 167 | # TODO: provide some timing information 168 | # end = time.time() - start 169 | 170 | return dF 171 | -------------------------------------------------------------------------------- /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) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/active_subspaces.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/active_subspaces.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/active_subspaces" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/active_subspaces" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /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% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 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% 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\active_subspaces.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\active_subspaces.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 | -------------------------------------------------------------------------------- /docs/source/Contact.rst: -------------------------------------------------------------------------------- 1 | Contact 2 | ======= 3 | 4 | Questions or comments? Send an email to Paul Constantine (paul.constantine@mines.edu) 5 | -------------------------------------------------------------------------------- /docs/source/LICENSE.rst: -------------------------------------------------------------------------------- 1 | ../../LICENSE.rst -------------------------------------------------------------------------------- /docs/source/code.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | domains 8 | gradients 9 | integrals 10 | optimizers 11 | response_surfaces 12 | subspaces 13 | utils 14 | 15 | .. automodule:: active_subspaces 16 | :members: 17 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # active_subspaces documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Jun 22 16:09:40 2015. 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 shlex 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('../..')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.napoleon', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The encoding of source files. 46 | #source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = u'active_subspaces' 53 | copyright = u'2016, Paul Constantine' 54 | author = u'Paul Constantine' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = '0.1' 62 | # The full version, including alpha/beta/rc tags. 63 | release = version 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = None 71 | 72 | # There are two options for replacing |today|: either, you set today to some 73 | # non-false value, then it is used: 74 | #today = '' 75 | # Else, today_fmt is used as the format for a strftime call. 76 | #today_fmt = '%B %d, %Y' 77 | 78 | # List of patterns, relative to source directory, that match files and 79 | # directories to ignore when looking for source files. 80 | exclude_patterns = [] 81 | 82 | # The reST default role (used for this markup: `text`) to use for all 83 | # documents. 84 | #default_role = None 85 | 86 | # If true, '()' will be appended to :func: etc. cross-reference text. 87 | #add_function_parentheses = True 88 | 89 | # If true, the current module name will be prepended to all description 90 | # unit titles (such as .. function::). 91 | #add_module_names = True 92 | 93 | # If true, sectionauthor and moduleauthor directives will be shown in the 94 | # output. They are ignored by default. 95 | #show_authors = False 96 | 97 | # The name of the Pygments (syntax highlighting) style to use. 98 | pygments_style = 'sphinx' 99 | 100 | # A list of ignored prefixes for module index sorting. 101 | #modindex_common_prefix = [] 102 | 103 | # If true, keep warnings as "system message" paragraphs in the built documents. 104 | #keep_warnings = False 105 | 106 | # If true, `todo` and `todoList` produce output, else they produce nothing. 107 | todo_include_todos = False 108 | 109 | 110 | # -- Options for HTML output ---------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'sphinx_rtd_theme' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a theme 117 | # further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. 129 | #html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the top 132 | # of the sidebar. 133 | #html_logo = None 134 | 135 | # The name of an image file (within the static path) to use as favicon of the 136 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 137 | # pixels large. 138 | #html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) here, 141 | # relative to this directory. They are copied after the builtin static files, 142 | # so a file named "default.css" will overwrite the builtin "default.css". 143 | html_static_path = ['_static'] 144 | 145 | # Add any extra paths that contain custom files (such as robots.txt or 146 | # .htaccess) here, relative to this directory. These files are copied 147 | # directly to the root of the documentation. 148 | #html_extra_path = [] 149 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 151 | # using the given strftime format. 152 | #html_last_updated_fmt = '%b %d, %Y' 153 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to 155 | # typographically correct entities. 156 | #html_use_smartypants = True 157 | 158 | # Custom sidebar templates, maps document names to template names. 159 | #html_sidebars = {} 160 | 161 | # Additional templates that should be rendered to pages, maps page names to 162 | # template names. 163 | #html_additional_pages = {} 164 | 165 | # If false, no module index is generated. 166 | #html_domain_indices = True 167 | 168 | # If false, no index is generated. 169 | #html_use_index = True 170 | 171 | # If true, the index is split into individual pages for each letter. 172 | #html_split_index = False 173 | 174 | # If true, links to the reST sources are added to the pages. 175 | #html_show_sourcelink = True 176 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 178 | #html_show_sphinx = True 179 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages will 184 | # contain a tag referring to it. The value of this option must be the 185 | # base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Language to be used for generating the HTML full-text search index. 192 | # Sphinx supports the following languages: 193 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 194 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 195 | #html_search_language = 'en' 196 | 197 | # A dictionary with options for the search language support, empty by default. 198 | # Now only 'ja' uses this config value 199 | #html_search_options = {'type': 'default'} 200 | 201 | # The name of a javascript file (relative to the configuration directory) that 202 | # implements a search results scorer. If empty, the default will be used. 203 | #html_search_scorer = 'scorer.js' 204 | 205 | # Output file base name for HTML help builder. 206 | htmlhelp_basename = 'active_subspacesdoc' 207 | 208 | # -- Options for LaTeX output --------------------------------------------- 209 | 210 | latex_elements = { 211 | # The paper size ('letterpaper' or 'a4paper'). 212 | #'papersize': 'letterpaper', 213 | 214 | # The font size ('10pt', '11pt' or '12pt'). 215 | #'pointsize': '10pt', 216 | 217 | # Additional stuff for the LaTeX preamble. 218 | #'preamble': '', 219 | 220 | # Latex figure (float) alignment 221 | #'figure_align': 'htbp', 222 | } 223 | 224 | # Grouping the document tree into LaTeX files. List of tuples 225 | # (source start file, target name, title, 226 | # author, documentclass [howto, manual, or own class]). 227 | latex_documents = [ 228 | (master_doc, 'active_subspaces.tex', u'active\\_subspaces Documentation', 229 | u'Paul Constantine', 'manual'), 230 | ] 231 | 232 | # The name of an image file (relative to this directory) to place at the top of 233 | # the title page. 234 | #latex_logo = None 235 | 236 | # For "manual" documents, if this is true, then toplevel headings are parts, 237 | # not chapters. 238 | #latex_use_parts = False 239 | 240 | # If true, show page references after internal links. 241 | #latex_show_pagerefs = False 242 | 243 | # If true, show URL addresses after external links. 244 | #latex_show_urls = False 245 | 246 | # Documents to append as an appendix to all manuals. 247 | #latex_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | #latex_domain_indices = True 251 | 252 | 253 | # -- Options for manual page output --------------------------------------- 254 | 255 | # One entry per manual page. List of tuples 256 | # (source start file, name, description, authors, manual section). 257 | man_pages = [ 258 | (master_doc, 'active_subspaces', u'active_subspaces Documentation', 259 | [author], 1) 260 | ] 261 | 262 | # If true, show URL addresses after external links. 263 | #man_show_urls = False 264 | 265 | 266 | # -- Options for Texinfo output ------------------------------------------- 267 | 268 | # Grouping the document tree into Texinfo files. List of tuples 269 | # (source start file, target name, title, author, 270 | # dir menu entry, description, category) 271 | texinfo_documents = [ 272 | (master_doc, 'active_subspaces', u'active_subspaces Documentation', 273 | author, 'active_subspaces', 'One line description of project.', 274 | 'Miscellaneous'), 275 | ] 276 | 277 | # Documents to append as an appendix to all manuals. 278 | #texinfo_appendices = [] 279 | 280 | # If false, no module index is generated. 281 | #texinfo_domain_indices = True 282 | 283 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 284 | #texinfo_show_urls = 'footnote' 285 | 286 | # If true, do not generate a @detailmenu in the "Top" node's menu. 287 | #texinfo_no_detailmenu = False 288 | -------------------------------------------------------------------------------- /docs/source/domains.rst: -------------------------------------------------------------------------------- 1 | Domains 2 | ======= 3 | 4 | .. automodule:: active_subspaces.domains 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/gradients.rst: -------------------------------------------------------------------------------- 1 | Gradients 2 | ========= 3 | 4 | .. automodule:: active_subspaces.gradients 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Python Active subspaces Utility Library 2 | ======================================= 3 | 4 | `Active subspaces `_ are part of an emerging set of tools for discovering low-dimensional structure in a given function of several variables. Interesting applications arise in deterministic computer simulations of complex physical systems, where the function is the map from the physical model's input parameters to its output quantity of interest. The active subspace is the span of particular directions in the input parameter space; perturbing the inputs along these *active* directions changes the output more, on average, than perturbing the inputs orthogonally to the active directions. By focusing on the model's response along active directions and ignoring the relatively inactive directions, we *reduce the dimension* for parameter studies---such as optimization and integration---that are essential to engineering tasks such as design and uncertainty quantification. 5 | 6 | For more information on active subspaces, visit http://activesubspaces.org/ or purchase the book `Active Subspaces: Emerging Ideas in Dimension Reduction for Parameter Studies `_ published by `SIAM `_. 7 | 8 | This library contains Python tools for discovering and exploiting a given model's active subspace. The user may provide a function handle to a complex model or its gradient with respect to the input parameters. Alternatively, the user may provide a set of input/output pairs from a previously executed set of runs (e.g., a Monte Carlo or Latin hypercube study). 9 | 10 | Installation 11 | ^^^^^^^^^^^^ 12 | 13 | The github repository is https://github.com/paulcon/active_subspaces.git 14 | 15 | To install the active subspaces package, open the terminal/command line and clone the repository with the command 16 | 17 | :: 18 | 19 | git clone https://github.com/paulcon/active_subspaces.git 20 | 21 | Navigate into the ``active_subspaces`` folder (where the ``setup.py`` file is located) and run the command 22 | 23 | :: 24 | 25 | python setup.py install 26 | 27 | You should now be able to import the active subspaces library in Python scripts and interpreters with the command ``import active_subspaces``. 28 | 29 | Examples 30 | ^^^^^^^^ 31 | 32 | The ``tutorials`` directory contains several Jupyter notebooks with examples of the code usage. You may also visit the `Active Subspaces Data Sets `_ repository for examples of applying active subspaces to real science and engineering models. 33 | 34 | For a quickstart, consider a bivariate quadratic function 35 | 36 | :: 37 | 38 | import numpy as np 39 | 40 | def fun(x): 41 | A = np.array([[4., 2.], [2., 1.1]]) 42 | return 0.5*np.dot(x.ravel(), np.dot(A, x.ravel())) 43 | 44 | with gradient function 45 | 46 | :: 47 | 48 | def dfun(x): 49 | A = np.array([[4., 2.], [2., 1.1]]) 50 | return np.dot(A, x.ravel()).reshape((2, 1)) 51 | 52 | Draw 50 samples from the function's domain, assumed to be the [-1,1]^2 box equipped with a uniform probability density function, 53 | 54 | :: 55 | 56 | X = np.random.uniform(-1., 1., size=(50, 2)) 57 | 58 | For each sample, compute the function and its gradient using the ``SimulationRunner`` and ``SimulationGradientRunner`` classes. 59 | 60 | :: 61 | 62 | import active_subspaces as acs 63 | 64 | # evaluate the function 65 | sr = acs.utils.simrunners.SimulationRunner(fun) 66 | f = sr.run(X) 67 | 68 | # evaluate the gradient 69 | sgr = acs.utils.simrunners.SimulationGradientRunner(dfun) 70 | df = sgr.run(X) 71 | 72 | Compute the active subspace with the gradients. 73 | 74 | :: 75 | 76 | ss = acs.subspaces.Subspaces() 77 | ss.compute(df=df, sstype='AS') 78 | 79 | See the documentation for ``Subspaces.compute()`` for more details. Use the plotting routines to examine the estimated eigenvalues. 80 | 81 | :: 82 | 83 | acs.utils.plotters.eigenvalues(ss.eigenvals) 84 | 85 | Make a one-dimensional summary plot 86 | 87 | :: 88 | 89 | y = np.dot(X, ss.W1) 90 | acs.utils.plotters.sufficient_summary(y, f) 91 | 92 | To exploit the one-dimensional active subspace, first set up the active variable domain and the map between the active variables and the full variables, 93 | 94 | :: 95 | 96 | # set up the active variable domain 97 | avd = acs.domains.BoundedActiveVariableDomain(ss) 98 | 99 | # set up the maps between active and full variables 100 | avm = acs.domains.BoundedActiveVariableMap(avd) 101 | 102 | To estimate an integral, 103 | 104 | :: 105 | 106 | N = 10 # number of active variable quadrature points 107 | mu = acs.integrals.integrate(fun, avm, N)[0] 108 | print 'Estimated integral: {:6.4f}'.format(mu) 109 | 110 | To train and test a low-dimensional response surface, 111 | 112 | :: 113 | 114 | rs = acs.response_surfaces.ActiveSubspaceResponseSurface(avm) 115 | 116 | # train with the interface 117 | N = 10 # number of active variable training points 118 | rs.train_with_interface(fun, N) 119 | 120 | # or train with the existing runs 121 | rs.train_with_data(X, f) 122 | 123 | # test 124 | XX = np.random.uniform(-1., 1., size=(100, 2)) 125 | fXX = rs.predict(XX) 126 | 127 | Explore the documentation and Jupyter notebooks to see the code's full range of capabilities. 128 | 129 | 130 | Developer documentation 131 | ^^^^^^^^^^^^^^^^^^^^^^^ 132 | 133 | .. toctree:: 134 | :maxdepth: 3 135 | 136 | code 137 | Contact 138 | LICENSE 139 | 140 | 141 | Indexes 142 | ======================= 143 | 144 | * :ref:`genindex` 145 | * :ref:`modindex` 146 | * :ref:`search` 147 | 148 | -------------------------------------------------------------------------------- /docs/source/integrals.rst: -------------------------------------------------------------------------------- 1 | Integrals 2 | ========= 3 | 4 | .. automodule:: active_subspaces.integrals 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/optimizers.rst: -------------------------------------------------------------------------------- 1 | Optimizers 2 | ========== 3 | 4 | .. automodule:: active_subspaces.optimizers 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/response_surfaces.rst: -------------------------------------------------------------------------------- 1 | Response Surfaces 2 | ================= 3 | 4 | .. automodule:: active_subspaces.response_surfaces 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/subspaces.rst: -------------------------------------------------------------------------------- 1 | Subspaces 2 | ========= 3 | 4 | .. automodule:: active_subspaces.subspaces 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils.rst: -------------------------------------------------------------------------------- 1 | Utils 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | utils_designs 8 | utils_misc 9 | utils_plotters 10 | utils_qp_solver 11 | utils_quadrature 12 | utils_response_surfaces 13 | utils_simrunners 14 | -------------------------------------------------------------------------------- /docs/source/utils_designs.rst: -------------------------------------------------------------------------------- 1 | Designs 2 | ======= 3 | 4 | .. automodule:: active_subspaces.utils.designs 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils_misc.rst: -------------------------------------------------------------------------------- 1 | Misc 2 | ==== 3 | 4 | .. automodule:: active_subspaces.utils.misc 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils_plotters.rst: -------------------------------------------------------------------------------- 1 | Plotters 2 | ======== 3 | 4 | .. automodule:: active_subspaces.utils.plotters 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils_qp_solver.rst: -------------------------------------------------------------------------------- 1 | QP Solver 2 | ========= 3 | 4 | .. automodule:: active_subspaces.utils.qp_solver 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils_quadrature.rst: -------------------------------------------------------------------------------- 1 | Quadrature 2 | ========== 3 | 4 | .. automodule:: active_subspaces.utils.quadrature 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils_response_surfaces.rst: -------------------------------------------------------------------------------- 1 | Response Surfaces (Utils) 2 | ========================= 3 | 4 | .. automodule:: active_subspaces.utils.response_surfaces 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/utils_simrunners.rst: -------------------------------------------------------------------------------- 1 | Simrunners 2 | ========== 3 | 4 | .. automodule:: active_subspaces.utils.simrunners 5 | :members: 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy>=0.15.0 3 | matplotlib 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | def readme(): 4 | with open('README.md') as f: 5 | return f.read() 6 | 7 | setup(name='active_subspaces', 8 | version='0.1', 9 | description='Tools to apply active subspaces to analyze models and data.', 10 | long_description=readme(), 11 | classifiers=[ 12 | 'Development Status :: 3 - Alpha', 13 | 'License :: OSI Approved :: MIT License', 14 | 'Programming Language :: Python :: 2.7', 15 | 'Intended Audience :: Science/Research', 16 | 'Topic :: Scientific/Engineering :: Mathematics' 17 | ], 18 | keywords='dimension reduction mathematics active subspaces uncertainty quantification uq', 19 | url='https://github.com/paulcon/active_subspaces', 20 | author='Paul Constantine', 21 | author_email='paul.constantine@mines.edu', 22 | license='MIT', 23 | packages=['active_subspaces', 'active_subspaces.utils'], 24 | install_requires=[ 25 | 'numpy', 26 | 'scipy >= 0.15.0', 27 | 'matplotlib' 28 | ], 29 | test_suite='nose.collector', 30 | tests_require=['nose'], 31 | include_package_data=True, 32 | zip_safe=False) 33 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib 4 | import nose 5 | matplotlib.use('agg') 6 | 7 | nose.main() 8 | -------------------------------------------------------------------------------- /tests/test_as_integrals.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.response_surfaces as asm 4 | import active_subspaces.integrals as asi 5 | import active_subspaces.subspaces as ss 6 | import active_subspaces.domains as dom 7 | import numpy as np 8 | 9 | class TestASIntegrals(TestCase): 10 | 11 | 12 | def quad_fun(self, x): 13 | A = np.array([[ 0.2406659045776698, -0.3159904335007421, -0.1746908591702878], 14 | [-0.3159904335007421, 0.5532215729009683, 0.3777995408101305], 15 | [-0.1746908591702878, 0.3777995408101305, 0.3161125225213613]]) 16 | x = x.reshape((3,1)) 17 | return 0.5*np.dot(x.T,np.dot(A,x)) 18 | 19 | def quad_dfun(self, x): 20 | A = np.array([[ 0.2406659045776698, -0.3159904335007421, -0.1746908591702878], 21 | [-0.3159904335007421, 0.5532215729009683, 0.3777995408101305], 22 | [-0.1746908591702878, 0.3777995408101305, 0.3161125225213613]]) 23 | return np.dot(A,x.reshape((3,1))) 24 | 25 | def test_rs_ubnd_int(self): 26 | np.random.seed(42) 27 | X0 = np.random.normal(size=(50,3)) 28 | f0 = np.random.normal(size=(50,1)) 29 | df0 = np.random.normal(size=(50,3)) 30 | 31 | sub = ss.Subspaces() 32 | sub.compute(df=df0) 33 | sub.partition(1) 34 | 35 | avd = dom.UnboundedActiveVariableDomain(sub) 36 | avm = dom.UnboundedActiveVariableMap(avd) 37 | asrs = asm.ActiveSubspaceResponseSurface(avm) 38 | asrs.train_with_data(X0, f0) 39 | 40 | I = asi.av_integrate(asrs, avm, 10) 41 | 42 | def test_rs_bnd_int(self): 43 | np.random.seed(42) 44 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 45 | f0 = np.random.normal(size=(50,1)) 46 | df0 = np.random.normal(size=(50,3)) 47 | 48 | sub = ss.Subspaces() 49 | sub.compute(df=df0) 50 | sub.partition(1) 51 | 52 | avd = dom.BoundedActiveVariableDomain(sub) 53 | avm = dom.BoundedActiveVariableMap(avd) 54 | asrs = asm.ActiveSubspaceResponseSurface(avm) 55 | asrs.train_with_data(X0, f0) 56 | 57 | I = asi.av_integrate(asrs, avm, 10) 58 | 59 | def test_rs_ubnd_2d_int(self): 60 | np.random.seed(42) 61 | X0 = np.random.normal(size=(50,3)) 62 | f0 = np.random.normal(size=(50,1)) 63 | df0 = np.random.normal(size=(50,3)) 64 | 65 | sub = ss.Subspaces() 66 | sub.compute(df=df0) 67 | sub.partition(2) 68 | 69 | avd = dom.UnboundedActiveVariableDomain(sub) 70 | avm = dom.UnboundedActiveVariableMap(avd) 71 | asrs = asm.ActiveSubspaceResponseSurface(avm) 72 | asrs.train_with_data(X0, f0) 73 | 74 | I = asi.av_integrate(asrs, avm, 10) 75 | 76 | def test_rs_bnd_2d_int(self): 77 | np.random.seed(42) 78 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 79 | f0 = np.random.normal(size=(50,1)) 80 | df0 = np.random.normal(size=(50,3)) 81 | 82 | sub = ss.Subspaces() 83 | sub.compute(df=df0) 84 | sub.partition(2) 85 | 86 | avd = dom.BoundedActiveVariableDomain(sub) 87 | avm = dom.BoundedActiveVariableMap(avd) 88 | asrs = asm.ActiveSubspaceResponseSurface(avm) 89 | asrs.train_with_data(X0, f0) 90 | 91 | I = asi.av_integrate(asrs, avm, 10) 92 | 93 | def test_fun_ubnd_int(self): 94 | np.random.seed(42) 95 | X0 = np.random.normal(size=(50,3)) 96 | f0 = np.random.normal(size=(50,1)) 97 | df0 = np.random.normal(size=(50,3)) 98 | 99 | sub = ss.Subspaces() 100 | sub.compute(df=df0) 101 | sub.partition(1) 102 | 103 | avd = dom.UnboundedActiveVariableDomain(sub) 104 | avm = dom.UnboundedActiveVariableMap(avd) 105 | 106 | mu, lb, ub = asi.integrate(self.quad_fun, avm, 10) 107 | 108 | def test_fun_bnd_int(self): 109 | np.random.seed(42) 110 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 111 | f0 = np.random.normal(size=(50,1)) 112 | df0 = np.random.normal(size=(50,3)) 113 | 114 | sub = ss.Subspaces() 115 | sub.compute(df=df0) 116 | sub.partition(1) 117 | 118 | avd = dom.BoundedActiveVariableDomain(sub) 119 | avm = dom.BoundedActiveVariableMap(avd) 120 | 121 | mu, lb, ub = asi.integrate(self.quad_fun, avm, 10) 122 | 123 | def test_fun_ubnd_2d_int(self): 124 | np.random.seed(42) 125 | X0 = np.random.normal(size=(50,3)) 126 | f0 = np.random.normal(size=(50,1)) 127 | df0 = np.random.normal(size=(50,3)) 128 | 129 | sub = ss.Subspaces() 130 | sub.compute(df=df0) 131 | sub.partition(2) 132 | 133 | avd = dom.UnboundedActiveVariableDomain(sub) 134 | avm = dom.UnboundedActiveVariableMap(avd) 135 | 136 | mu, lb, ub = asi.integrate(self.quad_fun, avm, 10) 137 | 138 | def test_fun_bnd_2d_int(self): 139 | np.random.seed(42) 140 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 141 | f0 = np.random.normal(size=(50,1)) 142 | df0 = np.random.normal(size=(50,3)) 143 | 144 | sub = ss.Subspaces() 145 | sub.compute(df=df0) 146 | sub.partition(2) 147 | 148 | avd = dom.BoundedActiveVariableDomain(sub) 149 | avm = dom.BoundedActiveVariableMap(avd) 150 | 151 | mu, lb, ub = asi.integrate(self.quad_fun, avm, 10) 152 | 153 | if __name__ == '__main__': 154 | unittest.main() 155 | -------------------------------------------------------------------------------- /tests/test_as_optimizers.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.response_surfaces as asm 4 | import active_subspaces.optimizers as aso 5 | import active_subspaces.subspaces as ss 6 | import active_subspaces.domains as dom 7 | import numpy as np 8 | 9 | class TestASOptimizers(TestCase): 10 | 11 | def quad_fun(self, x): 12 | A = np.array([[ 0.2406659045776698, -0.3159904335007421, -0.1746908591702878], 13 | [-0.3159904335007421, 0.5532215729009683, 0.3777995408101305], 14 | [-0.1746908591702878, 0.3777995408101305, 0.3161125225213613]]) 15 | x = x.reshape((3,1)) 16 | return 0.5*np.dot(x.T,np.dot(A,x)) 17 | 18 | def quad_dfun(self, x): 19 | A = np.array([[ 0.2406659045776698, -0.3159904335007421, -0.1746908591702878], 20 | [-0.3159904335007421, 0.5532215729009683, 0.3777995408101305], 21 | [-0.1746908591702878, 0.3777995408101305, 0.3161125225213613]]) 22 | return np.dot(A,x.reshape((3,1))) 23 | 24 | def test_rs_ubnd_int(self): 25 | np.random.seed(42) 26 | X0 = np.random.normal(size=(50,3)) 27 | f0 = np.zeros((50,1)) 28 | df0 = np.zeros((50,3)) 29 | for i in range(50): 30 | x = X0[i,:] 31 | f0[i,0] = self.quad_fun(x) 32 | df0[i,:] = self.quad_dfun(x).reshape((3, )) 33 | 34 | sub = ss.Subspaces() 35 | sub.compute(df=df0) 36 | sub.partition(1) 37 | 38 | avd = dom.UnboundedActiveVariableDomain(sub) 39 | avm = dom.UnboundedActiveVariableMap(avd) 40 | asrs = asm.ActiveSubspaceResponseSurface(avm) 41 | asrs.train_with_data(X0, f0) 42 | 43 | xstar, fstar = aso.minimize(asrs, X0, f0) 44 | 45 | def test_rs_bnd_int(self): 46 | np.random.seed(42) 47 | X0 = np.random.uniform(-1.,1.,size=(50,3)) 48 | f0 = np.zeros((50,1)) 49 | df0 = np.zeros((50,3)) 50 | for i in range(50): 51 | x = X0[i,:] 52 | f0[i,0] = self.quad_fun(x) 53 | df0[i,:] = self.quad_dfun(x).reshape((3, )) 54 | 55 | sub = ss.Subspaces() 56 | sub.compute(df=df0) 57 | sub.partition(1) 58 | 59 | avd = dom.BoundedActiveVariableDomain(sub) 60 | avm = dom.BoundedActiveVariableMap(avd) 61 | asrs = asm.ActiveSubspaceResponseSurface(avm) 62 | asrs.train_with_data(X0, f0) 63 | 64 | xstar, fstar = aso.minimize(asrs, X0, f0) 65 | 66 | def test_rs_ubnd_2d_int(self): 67 | np.random.seed(42) 68 | X0 = np.random.normal(size=(50,3)) 69 | f0 = np.zeros((50,1)) 70 | df0 = np.zeros((50,3)) 71 | for i in range(50): 72 | x = X0[i,:] 73 | f0[i,0] = self.quad_fun(x) 74 | df0[i,:] = self.quad_dfun(x).reshape((3, )) 75 | 76 | sub = ss.Subspaces() 77 | sub.compute(df=df0) 78 | sub.partition(2) 79 | 80 | avd = dom.UnboundedActiveVariableDomain(sub) 81 | avm = dom.UnboundedActiveVariableMap(avd) 82 | asrs = asm.ActiveSubspaceResponseSurface(avm) 83 | asrs.train_with_data(X0, f0) 84 | 85 | xstar, fstar = aso.minimize(asrs, X0, f0) 86 | 87 | def test_rs_bnd_2d_int(self): 88 | np.random.seed(42) 89 | X0 = np.random.uniform(-1.,1.,size=(50,3)) 90 | f0 = np.zeros((50,1)) 91 | df0 = np.zeros((50,3)) 92 | for i in range(50): 93 | x = X0[i,:] 94 | f0[i,0] = self.quad_fun(x) 95 | df0[i,:] = self.quad_dfun(x).reshape((3, )) 96 | 97 | sub = ss.Subspaces() 98 | sub.compute(df=df0) 99 | sub.partition(2) 100 | 101 | avd = dom.BoundedActiveVariableDomain(sub) 102 | avm = dom.BoundedActiveVariableMap(avd) 103 | asrs = asm.ActiveSubspaceResponseSurface(avm) 104 | asrs.train_with_data(X0, f0) 105 | 106 | xstar, fstar = aso.minimize(asrs, X0, f0) 107 | 108 | if __name__ == '__main__': 109 | unittest.main() 110 | -------------------------------------------------------------------------------- /tests/test_as_response_surfaces.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.response_surfaces as asm 4 | import active_subspaces.subspaces as ss 5 | import active_subspaces.domains as dom 6 | import active_subspaces.utils.simrunners as srun 7 | import active_subspaces.utils.response_surfaces as rs 8 | import numpy as np 9 | 10 | class TestASResponseSurfaces(TestCase): 11 | 12 | def quad_fun(self, x): 13 | A = np.array([[ 0.2406659045776698, -0.3159904335007421, -0.1746908591702878], 14 | [-0.3159904335007421, 0.5532215729009683, 0.3777995408101305], 15 | [-0.1746908591702878, 0.3777995408101305, 0.3161125225213613]]) 16 | x = x.reshape((3,1)) 17 | return 0.5*np.dot(x.T,np.dot(A,x)) 18 | 19 | def quad_dfun(self, x): 20 | A = np.array([[ 0.2406659045776698, -0.3159904335007421, -0.1746908591702878], 21 | [-0.3159904335007421, 0.5532215729009683, 0.3777995408101305], 22 | [-0.1746908591702878, 0.3777995408101305, 0.3161125225213613]]) 23 | return np.dot(A,x.reshape((3,1))) 24 | 25 | def test_rs_0(self): 26 | np.random.seed(42) 27 | df0 = np.random.normal(size=(10,2)) 28 | 29 | sub = ss.Subspaces() 30 | sub.compute(df=df0) 31 | avd = dom.UnboundedActiveVariableDomain(sub) 32 | avm = dom.UnboundedActiveVariableMap(avd) 33 | asm.ActiveSubspaceResponseSurface(avm) 34 | 35 | def test_rs_data_train_gp_ubnd(self): 36 | np.random.seed(42) 37 | X0 = np.random.normal(size=(50,3)) 38 | f0 = np.random.normal(size=(50,1)) 39 | df0 = np.random.normal(size=(50,3)) 40 | 41 | sub = ss.Subspaces() 42 | sub.compute(df=df0) 43 | 44 | avd = dom.UnboundedActiveVariableDomain(sub) 45 | avm = dom.UnboundedActiveVariableMap(avd) 46 | asrs = asm.ActiveSubspaceResponseSurface(avm) 47 | asrs.train_with_data(X0, f0) 48 | 49 | np.random.seed(43) 50 | XX = np.random.normal(size=(10, 3)) 51 | ff, dff = asrs.predict(XX, compgrad=True) 52 | 53 | def test_rs_data_train_pr_ubnd(self): 54 | np.random.seed(42) 55 | X0 = np.random.normal(size=(50,3)) 56 | f0 = np.random.normal(size=(50,1)) 57 | df0 = np.random.normal(size=(50,3)) 58 | 59 | sub = ss.Subspaces() 60 | sub.compute(df=df0) 61 | 62 | avd = dom.UnboundedActiveVariableDomain(sub) 63 | avm = dom.UnboundedActiveVariableMap(avd) 64 | pr = rs.PolynomialApproximation() 65 | asrs = asm.ActiveSubspaceResponseSurface(avm, respsurf=pr) 66 | asrs.train_with_data(X0, f0) 67 | 68 | XX = np.random.normal(size=(10, 3)) 69 | ff, dff = asrs.predict(XX, compgrad=True) 70 | 71 | def test_rs_data_train_gp_bnd(self): 72 | np.random.seed(42) 73 | X0 = np.random.uniform(-1.0,1.0,size=(10,3)) 74 | f0 = np.random.normal(size=(10,1)) 75 | df0 = np.random.normal(size=(10,3)) 76 | 77 | sub = ss.Subspaces() 78 | sub.compute(df=df0) 79 | 80 | avd = dom.BoundedActiveVariableDomain(sub) 81 | avm = dom.BoundedActiveVariableMap(avd) 82 | asrs = asm.ActiveSubspaceResponseSurface(avm) 83 | asrs.train_with_data(X0, f0) 84 | 85 | np.random.seed(43) 86 | XX = np.random.uniform(-1.0,1.0,size=(10, 3)) 87 | ff, dff = asrs.predict(XX, compgrad=True) 88 | 89 | def test_rs_data_train_pr_bnd(self): 90 | np.random.seed(42) 91 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 92 | f0 = np.random.normal(size=(50,1)) 93 | df0 = np.random.normal(size=(50,3)) 94 | 95 | sub = ss.Subspaces() 96 | sub.compute(df=df0) 97 | 98 | avd = dom.BoundedActiveVariableDomain(sub) 99 | avm = dom.BoundedActiveVariableMap(avd) 100 | pr = rs.PolynomialApproximation() 101 | asrs = asm.ActiveSubspaceResponseSurface(avm, pr) 102 | asrs.train_with_data(X0, f0) 103 | 104 | XX = np.random.uniform(-1.0,1.0,size=(10, 3)) 105 | ff, dff = asrs.predict(XX, compgrad=True) 106 | 107 | def test_rs_data_train_gp_ubnd_2d(self): 108 | np.random.seed(42) 109 | X0 = np.random.normal(size=(50,3)) 110 | f0 = np.random.normal(size=(50,1)) 111 | df0 = np.random.normal(size=(50,3)) 112 | 113 | sub = ss.Subspaces() 114 | sub.compute(df=df0) 115 | sub.partition(2) 116 | 117 | avd = dom.UnboundedActiveVariableDomain(sub) 118 | avm = dom.UnboundedActiveVariableMap(avd) 119 | asrs = asm.ActiveSubspaceResponseSurface(avm) 120 | asrs.train_with_data(X0, f0) 121 | 122 | XX = np.random.normal(size=(10, 3)) 123 | ff, dff = asrs.predict(XX, compgrad=True) 124 | 125 | def test_rs_data_train_pr_ubnd_2d(self): 126 | np.random.seed(42) 127 | X0 = np.random.normal(size=(50,3)) 128 | f0 = np.random.normal(size=(50,1)) 129 | df0 = np.random.normal(size=(50,3)) 130 | 131 | sub = ss.Subspaces() 132 | sub.compute(df=df0) 133 | sub.partition(2) 134 | 135 | avd = dom.UnboundedActiveVariableDomain(sub) 136 | avm = dom.UnboundedActiveVariableMap(avd) 137 | pr = rs.PolynomialApproximation() 138 | asrs = asm.ActiveSubspaceResponseSurface(avm, pr) 139 | asrs.train_with_data(X0, f0) 140 | 141 | XX = np.random.normal(size=(10, 3)) 142 | ff, dff = asrs.predict(XX, compgrad=True) 143 | 144 | def test_rs_data_train_gp_bnd_2d(self): 145 | np.random.seed(42) 146 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 147 | f0 = np.random.normal(size=(50,1)) 148 | df0 = np.random.normal(size=(50,3)) 149 | 150 | sub = ss.Subspaces() 151 | sub.compute(df=df0) 152 | sub.partition(2) 153 | 154 | avd = dom.BoundedActiveVariableDomain(sub) 155 | avm = dom.BoundedActiveVariableMap(avd) 156 | asrs = asm.ActiveSubspaceResponseSurface(avm) 157 | asrs.train_with_data(X0, f0) 158 | 159 | XX = np.random.normal(size=(10, 3)) 160 | ff, dff = asrs.predict(XX, compgrad=True) 161 | 162 | def test_rs_data_train_pr_bnd_2d(self): 163 | np.random.seed(42) 164 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 165 | f0 = np.random.normal(size=(50,1)) 166 | df0 = np.random.normal(size=(50,3)) 167 | 168 | sub = ss.Subspaces() 169 | sub.compute(df=df0) 170 | sub.partition(2) 171 | 172 | avd = dom.BoundedActiveVariableDomain(sub) 173 | avm = dom.BoundedActiveVariableMap(avd) 174 | pr = rs.PolynomialApproximation() 175 | asrs = asm.ActiveSubspaceResponseSurface(avm, pr) 176 | asrs.train_with_data(X0, f0) 177 | 178 | XX = np.random.normal(size=(10, 3)) 179 | ff, dff = asrs.predict(XX, compgrad=True) 180 | 181 | def test_rs_fun_train_gp_ubnd(self): 182 | np.random.seed(42) 183 | X0 = np.random.normal(size=(50,3)) 184 | f0 = np.random.normal(size=(50,1)) 185 | df0 = np.random.normal(size=(50,3)) 186 | 187 | sub = ss.Subspaces() 188 | sub.compute(df=df0) 189 | 190 | avd = dom.UnboundedActiveVariableDomain(sub) 191 | avm = dom.UnboundedActiveVariableMap(avd) 192 | asrs = asm.ActiveSubspaceResponseSurface(avm) 193 | 194 | asrs.train_with_interface(self.quad_fun, 10) 195 | 196 | XX = np.random.normal(size=(10, 3)) 197 | ff, dff = asrs.predict(XX, compgrad=True) 198 | 199 | def test_rs_fun_train_pr_ubnd(self): 200 | np.random.seed(42) 201 | X0 = np.random.normal(size=(50,3)) 202 | f0 = np.random.normal(size=(50,1)) 203 | df0 = np.random.normal(size=(50,3)) 204 | 205 | sub = ss.Subspaces() 206 | sub.compute(df=df0) 207 | 208 | avd = dom.UnboundedActiveVariableDomain(sub) 209 | avm = dom.UnboundedActiveVariableMap(avd) 210 | asrs = asm.ActiveSubspaceResponseSurface(avm) 211 | 212 | asrs.train_with_interface(self.quad_fun, 10) 213 | 214 | XX = np.random.normal(size=(10, 3)) 215 | ff, dff = asrs.predict(XX, compgrad=True) 216 | 217 | def test_rs_fun_train_gp_bnd(self): 218 | np.random.seed(42) 219 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 220 | f0 = np.random.normal(size=(50,1)) 221 | df0 = np.random.normal(size=(50,3)) 222 | 223 | sub = ss.Subspaces() 224 | sub.compute(df=df0) 225 | 226 | avd = dom.BoundedActiveVariableDomain(sub) 227 | avm = dom.BoundedActiveVariableMap(avd) 228 | asrs = asm.ActiveSubspaceResponseSurface(avm) 229 | 230 | asrs.train_with_interface(self.quad_fun, 10) 231 | 232 | XX = np.random.uniform(-1.0,1.0,size=(10, 3)) 233 | ff, dff = asrs.predict(XX, compgrad=True) 234 | 235 | def test_rs_fun_train_pr_bnd(self): 236 | np.random.seed(42) 237 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 238 | f0 = np.random.normal(size=(50,1)) 239 | df0 = np.random.normal(size=(50,3)) 240 | 241 | sub = ss.Subspaces() 242 | sub.compute(df=df0) 243 | 244 | avd = dom.BoundedActiveVariableDomain(sub) 245 | avm = dom.BoundedActiveVariableMap(avd) 246 | pr = rs.PolynomialApproximation() 247 | asrs = asm.ActiveSubspaceResponseSurface(avm, pr) 248 | 249 | asrs.train_with_interface(self.quad_fun, 10) 250 | 251 | XX = np.random.uniform(-1.0,1.0,size=(10, 3)) 252 | ff, dff = asrs.predict(XX, compgrad=True) 253 | 254 | def test_rs_fun_train_gp_ubnd_2d(self): 255 | np.random.seed(42) 256 | X0 = np.random.normal(size=(50,3)) 257 | f0 = np.random.normal(size=(50,1)) 258 | df0 = np.random.normal(size=(50,3)) 259 | 260 | sub = ss.Subspaces() 261 | sub.compute(df=df0) 262 | sub.partition(2) 263 | 264 | avd = dom.UnboundedActiveVariableDomain(sub) 265 | avm = dom.UnboundedActiveVariableMap(avd) 266 | asrs = asm.ActiveSubspaceResponseSurface(avm) 267 | 268 | asrs.train_with_interface(self.quad_fun, 10) 269 | 270 | XX = np.random.normal(size=(10, 3)) 271 | ff, dff = asrs.predict(XX, compgrad=True) 272 | 273 | def test_rs_fun_train_pr_ubnd_2d(self): 274 | np.random.seed(42) 275 | X0 = np.random.normal(size=(50,3)) 276 | f0 = np.random.normal(size=(50,1)) 277 | df0 = np.random.normal(size=(50,3)) 278 | 279 | sub = ss.Subspaces() 280 | sub.compute(df=df0) 281 | sub.partition(2) 282 | 283 | avd = dom.UnboundedActiveVariableDomain(sub) 284 | avm = dom.UnboundedActiveVariableMap(avd) 285 | pr = rs.PolynomialApproximation() 286 | asrs = asm.ActiveSubspaceResponseSurface(avm, pr) 287 | 288 | asrs.train_with_interface(self.quad_fun, 10) 289 | 290 | XX = np.random.normal(size=(10, 3)) 291 | ff, dff = asrs.predict(XX, compgrad=True) 292 | 293 | def test_rs_fun_train_gp_bnd_2d(self): 294 | np.random.seed(42) 295 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 296 | f0 = np.random.normal(size=(50,1)) 297 | df0 = np.random.normal(size=(50,3)) 298 | 299 | sub = ss.Subspaces() 300 | sub.compute(df=df0) 301 | sub.partition(2) 302 | 303 | avd = dom.BoundedActiveVariableDomain(sub) 304 | avm = dom.BoundedActiveVariableMap(avd) 305 | asrs = asm.ActiveSubspaceResponseSurface(avm) 306 | 307 | asrs.train_with_interface(self.quad_fun, 10) 308 | 309 | XX = np.random.uniform(-1.0,1.0,size=(10, 3)) 310 | ff, dff = asrs.predict(XX, compgrad=True) 311 | 312 | def test_rs_fun_train_pr_bnd_2d(self): 313 | np.random.seed(42) 314 | X0 = np.random.uniform(-1.0,1.0,size=(50,3)) 315 | f0 = np.random.normal(size=(50,1)) 316 | df0 = np.random.normal(size=(50,3)) 317 | 318 | sub = ss.Subspaces() 319 | sub.compute(df=df0) 320 | sub.partition(2) 321 | 322 | avd = dom.BoundedActiveVariableDomain(sub) 323 | avm = dom.BoundedActiveVariableMap(avd) 324 | pr = rs.PolynomialApproximation() 325 | asrs = asm.ActiveSubspaceResponseSurface(avm, pr) 326 | 327 | asrs.train_with_interface(self.quad_fun, 10) 328 | 329 | XX = np.random.uniform(-1.0,1.0,size=(10, 3)) 330 | ff, dff = asrs.predict(XX, compgrad=True) 331 | 332 | 333 | if __name__ == '__main__': 334 | unittest.main() 335 | -------------------------------------------------------------------------------- /tests/test_designs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from unittest import TestCase 3 | import unittest 4 | import active_subspaces.utils.designs as dn 5 | 6 | class TestDesigns(TestCase): 7 | 8 | def test_interval_design(self): 9 | y = dn.interval_design(-1.0, 1.0, 3) 10 | ytrue = np.array([[-0.5], [0.], [0.5]]) 11 | np.testing.assert_almost_equal(y, ytrue) 12 | 13 | def test_maximin_design(self): 14 | vert = np.array([[-1.0,-1.0], [1.0,-1.0], [-1.0,1.0], [1.0,1.0]]) 15 | Y = dn.maximin_design(vert, 1) 16 | np.testing.assert_almost_equal(Y, np.zeros((1,2)), decimal=4) 17 | 18 | def test_maximin_design_random_state(self): 19 | vert = np.array([[-1.0,-1.0], [1.0,-1.0], [-1.0,1.0], [1.0,1.0]]) 20 | np.random.seed(99) 21 | Y1 = dn.maximin_design(vert, 3) 22 | np.random.seed(999) 23 | Y2 = dn.maximin_design(vert, 3) 24 | np.testing.assert_almost_equal(Y1, Y2, decimal=6) 25 | 26 | def test_maximin_design_repeatable(self): 27 | vert = np.array([[-1.0,-1.0], [1.0,-1.0], [-1.0,1.0], [1.0,1.0]]) 28 | Y1 = dn.maximin_design(vert, 10) 29 | Y2 = dn.maximin_design(vert, 10) 30 | np.testing.assert_almost_equal(Y1, Y2) 31 | 32 | def test_gauss_hermite_design_1(self): 33 | Y = dn.gauss_hermite_design([1]) 34 | Ytrue = np.array([[0.0]]) 35 | np.testing.assert_almost_equal(Y, Ytrue) 36 | 37 | def test_gauss_hermite_design_2(self): 38 | Y = dn.gauss_hermite_design([1,1]) 39 | Ytrue = np.array([[0.0, 0.0]]) 40 | np.testing.assert_almost_equal(Y, Ytrue) 41 | 42 | def test_gradient_with_finite_difference(self): 43 | vert = np.array([[-1.0,-1.0], [1.0,-1.0], [-1.0,1.0], [1.0,1.0]]) 44 | N, n, h = 5, 2, 1e-6 45 | for i in range(5): 46 | y0 = np.random.normal(size=(N*n, )) 47 | f0 = dn._maximin_design_obj(y0, vert) 48 | df0 = dn._maximin_design_grad(y0, vert) 49 | for j in range(N*n): 50 | y0p = y0.copy() 51 | y0p[j] += h 52 | f0p = dn._maximin_design_obj(y0p, vert) 53 | df0_fd = (f0p - f0)/h 54 | np.testing.assert_almost_equal(df0[j], df0_fd, decimal=5) 55 | 56 | 57 | if __name__ == '__main__': 58 | unittest.main() 59 | -------------------------------------------------------------------------------- /tests/test_domains.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.subspaces as ss 4 | import active_subspaces.domains as dom 5 | import numpy as np 6 | import pdb 7 | 8 | class TestDomains(TestCase): 9 | 10 | def test_unbounded_active_variable_domain(self): 11 | np.random.seed(42) 12 | df = np.random.normal(size=(10,3)) 13 | 14 | sub = ss.Subspaces() 15 | sub.compute(df=df) 16 | uavd = dom.UnboundedActiveVariableDomain(sub) 17 | 18 | def test_bounded_active_variable_domain_0(self): 19 | np.random.seed(42) 20 | df = np.random.normal(size=(10,3)) 21 | 22 | sub = ss.Subspaces() 23 | sub.compute(df=df) 24 | 25 | bavd = dom.BoundedActiveVariableDomain(sub) 26 | 27 | def test_bounded_active_variable_domain_1(self): 28 | np.random.seed(42) 29 | df0 = np.random.normal(size=(10,3)) 30 | 31 | sub = ss.Subspaces() 32 | sub.compute(df=df0) 33 | 34 | bavd = dom.BoundedActiveVariableDomain(sub) 35 | 36 | def test_unbounded_active_variable_map_0(self): 37 | np.random.seed(42) 38 | df0 = np.random.normal(size=(10,3)) 39 | 40 | sub = ss.Subspaces() 41 | sub.compute(df=df0) 42 | m, n = sub.W1.shape 43 | 44 | uavd = dom.UnboundedActiveVariableDomain(sub) 45 | uavm = dom.UnboundedActiveVariableMap(uavd) 46 | 47 | X = np.random.normal(size=(100,m)) 48 | Y,Z = uavm.forward(X) 49 | X0 = np.dot(Y, sub.W1.T) + np.dot(Z, sub.W2.T) 50 | np.testing.assert_almost_equal(X0, X) 51 | 52 | def test_unbounded_active_variable_map_1(self): 53 | np.random.seed(42) 54 | df0 = np.random.normal(size=(10,3)) 55 | 56 | sub = ss.Subspaces() 57 | sub.compute(df=df0) 58 | m, n = sub.W1.shape 59 | 60 | uavd = dom.UnboundedActiveVariableDomain(sub) 61 | uavm = dom.UnboundedActiveVariableMap(uavd) 62 | 63 | X = np.random.normal(size=(100,m)) 64 | Y,Z = uavm.forward(X) 65 | X0 = np.dot(Y, sub.W1.T) + np.dot(Z, sub.W2.T) 66 | np.testing.assert_almost_equal(X0, X) 67 | 68 | def test_unbounded_active_variable_map_2(self): 69 | np.random.seed(42) 70 | df0 = np.random.normal(size=(10,3)) 71 | 72 | sub = ss.Subspaces() 73 | sub.compute(df=df0) 74 | m, n = sub.W1.shape 75 | 76 | uavd = dom.UnboundedActiveVariableDomain(sub) 77 | uavm = dom.UnboundedActiveVariableMap(uavd) 78 | 79 | X = np.random.normal(size=(100,m)) 80 | Y,Z = uavm.forward(X) 81 | X0 = uavm.inverse(Y, N=10)[0] 82 | np.testing.assert_almost_equal(np.dot(X0, sub.W1), np.kron(Y, np.ones((10,1))) ) 83 | 84 | def test_unbounded_active_variable_map_3(self): 85 | np.random.seed(42) 86 | df0 = np.random.normal(size=(10,3)) 87 | 88 | sub = ss.Subspaces() 89 | sub.compute(df=df0) 90 | m, n = sub.W1.shape 91 | 92 | uavd = dom.UnboundedActiveVariableDomain(sub) 93 | uavm = dom.UnboundedActiveVariableMap(uavd) 94 | 95 | X = np.random.normal(size=(100,m)) 96 | Y,Z = uavm.forward(X) 97 | X0 = uavm.inverse(Y, N=10)[0] 98 | np.testing.assert_almost_equal(np.dot(X0, sub.W1), np.kron(Y, np.ones((10,1))) ) 99 | 100 | def test_bounded_active_variable_map_0(self): 101 | np.random.seed(42) 102 | df0 = np.random.normal(size=(10,3)) 103 | 104 | sub = ss.Subspaces() 105 | sub.compute(df=df0) 106 | m, n = sub.W1.shape 107 | 108 | bavd = dom.BoundedActiveVariableDomain(sub) 109 | bavm = dom.BoundedActiveVariableMap(bavd) 110 | 111 | X = np.random.uniform(-1.0,1.0,size=(100,m)) 112 | Y,Z = bavm.forward(X) 113 | X0 = np.dot(Y, sub.W1.T) + np.dot(Z, sub.W2.T) 114 | np.testing.assert_almost_equal(X0, X) 115 | 116 | def test_bounded_active_variable_map_1(self): 117 | np.random.seed(42) 118 | df0 = np.random.normal(size=(10,3)) 119 | 120 | sub = ss.Subspaces() 121 | sub.compute(df=df0) 122 | m, n = sub.W1.shape 123 | 124 | bavd = dom.BoundedActiveVariableDomain(sub) 125 | bavm = dom.BoundedActiveVariableMap(bavd) 126 | 127 | X = np.random.uniform(-1.0,1.0,size=(100,m)) 128 | Y,Z = bavm.forward(X) 129 | X0 = np.dot(Y, sub.W1.T) + np.dot(Z, sub.W2.T) 130 | np.testing.assert_almost_equal(X0, X) 131 | 132 | def test_bounded_active_variable_map_2(self): 133 | np.random.seed(42) 134 | df0 = np.random.normal(size=(10,3)) 135 | 136 | sub = ss.Subspaces() 137 | sub.compute(df=df0) 138 | m, n = sub.W1.shape 139 | 140 | bavd = dom.BoundedActiveVariableDomain(sub) 141 | bavm = dom.BoundedActiveVariableMap(bavd) 142 | 143 | X = np.random.uniform(-1.0,1.0,size=(10,m)) 144 | Y,Z = bavm.forward(X) 145 | X0 = bavm.inverse(Y, N=10)[0] 146 | np.testing.assert_almost_equal(np.dot(X0, sub.W1), np.kron(Y, np.ones((10,1))) ) 147 | np.testing.assert_equal(np.floor(np.abs(X0)), np.zeros(X0.shape)) 148 | 149 | def test_bounded_active_variable_map_3(self): 150 | np.random.seed(42) 151 | df0 = np.random.normal(size=(10,3)) 152 | 153 | sub = ss.Subspaces() 154 | sub.compute(df=df0) 155 | m, n = sub.W1.shape 156 | 157 | bavd = dom.BoundedActiveVariableDomain(sub) 158 | bavm = dom.BoundedActiveVariableMap(bavd) 159 | 160 | X = np.random.uniform(-1.0,1.0,size=(10,m)) 161 | Y,Z = bavm.forward(X) 162 | X0 = bavm.inverse(Y, N=10)[0] 163 | np.testing.assert_almost_equal(np.dot(X0, sub.W1), np.kron(Y, np.ones((10,1))) ) 164 | np.testing.assert_equal(np.floor(np.abs(X0)), np.zeros(X0.shape)) 165 | 166 | def test_rejection_sample_z(self): 167 | np.random.seed(42) 168 | df0 = np.random.normal(size=(10,3)) 169 | 170 | sub = ss.Subspaces() 171 | sub.compute(df=df0) 172 | W1, W2 = sub.W1, sub.W2 173 | m, n = W1.shape 174 | 175 | np.random.seed(43) 176 | x = np.random.uniform(-1.0,1.0,size=(1,m)) 177 | y = np.dot(x, W1).reshape((n, )) 178 | N = 10 179 | np.random.seed(42) 180 | Z = dom.rejection_sampling_z(N, y, W1, W2) 181 | 182 | 183 | 184 | def test_hit_and_run_z(self): 185 | np.random.seed(42) 186 | df0 = np.random.normal(size=(10,3)) 187 | 188 | sub = ss.Subspaces() 189 | sub.compute(df=df0) 190 | W1, W2 = sub.W1, sub.W2 191 | m, n = W1.shape 192 | 193 | np.random.seed(43) 194 | x = np.random.uniform(-1.0,1.0,size=(1,m)) 195 | y = np.dot(x, W1).reshape((n, )) 196 | N = 10 197 | np.random.seed(42) 198 | Z = dom.hit_and_run_z(N, y, W1, W2) 199 | 200 | def test_random_walk_z(self): 201 | np.random.seed(42) 202 | df0 = np.random.normal(size=(10,3)) 203 | 204 | sub = ss.Subspaces() 205 | sub.compute(df=df0) 206 | W1, W2 = sub.W1, sub.W2 207 | m, n = W1.shape 208 | 209 | np.random.seed(43) 210 | x = np.random.uniform(-1.0,1.0,size=(1,m)) 211 | y = np.dot(x, W1).reshape((n, )) 212 | N = 10 213 | np.random.seed(42) 214 | Z = dom.random_walk_z(N, y, W1, W2) 215 | 216 | 217 | def test_sample_z(self): 218 | np.random.seed(42) 219 | df0 = np.random.normal(size=(10,3)) 220 | 221 | sub = ss.Subspaces() 222 | sub.compute(df=df0) 223 | W1, W2 = sub.W1, sub.W2 224 | m, n = W1.shape 225 | 226 | np.random.seed(43) 227 | x = np.random.uniform(-1.0,1.0,size=(1,m)) 228 | y = np.dot(x, W1).reshape((n, )) 229 | N = 10 230 | np.random.seed(42) 231 | Z = dom.sample_z(N, y, W1, W2) 232 | 233 | 234 | 235 | if __name__ == '__main__': 236 | unittest.main() 237 | -------------------------------------------------------------------------------- /tests/test_gradients.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import numpy as np 4 | import active_subspaces.gradients as gr 5 | 6 | class TestGradients(TestCase): 7 | writeData = False 8 | 9 | def test_local_linear_gradients(self): 10 | 11 | np.random.seed(42) 12 | X = np.random.uniform(-1.0,1.0,size=(200,2)) 13 | f = 2 - 5*X[:,0] + 4*X[:,1] 14 | 15 | df = gr.local_linear_gradients(X, f) 16 | M = df.shape[0] 17 | np.testing.assert_array_almost_equal(df, np.tile(np.array([-5.0, 4.0]), (M,1)), decimal=9) 18 | 19 | df = gr.local_linear_gradients(X, f, p=8) 20 | M = df.shape[0] 21 | np.testing.assert_array_almost_equal(df, np.tile(np.array([-5.0, 4.0]), (M,1)), decimal=9) 22 | 23 | f = 2 - np.sin(X[:,0]) + np.cos(X[:,1]) 24 | np.random.seed(1234) 25 | df = gr.local_linear_gradients(X, f) 26 | 27 | def test_finite_difference_gradients(self): 28 | def myfun(x): 29 | return 2 - 5*x[0,0] + 4*x[0,1] 30 | 31 | np.random.seed(42) 32 | X = np.random.uniform(-1.0,1.0,size=(10,2)) 33 | 34 | df = gr.finite_difference_gradients(X, myfun) 35 | M = df.shape[0] 36 | df_test = np.tile(np.array([-5.0, 4.0]), (M,1)) 37 | np.testing.assert_array_almost_equal(df, df_test, decimal=6) 38 | 39 | if __name__ == '__main__': 40 | unittest.main() -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.utils.misc as ut 4 | import numpy as np 5 | 6 | class TestUtils(TestCase): 7 | def test_bounded_normalizer(self): 8 | M, m = 10, 3 9 | XX = np.hstack((np.random.uniform(-4.0,7.0,size=(M,1)), 10 | np.random.uniform(4.0,6.0,size=(M,1)), 11 | np.random.uniform(2.0,3.0,size=(M,1)))) 12 | lb = np.array([-4.0,4.0,2.0]) 13 | ub = np.array([7.0,6.0,3.0]) 14 | bn = ut.BoundedNormalizer(lb, ub) 15 | X0 = bn.normalize(XX) 16 | X1 = bn.unnormalize(X0) 17 | np.testing.assert_almost_equal(XX, X1) 18 | np.testing.assert_array_less(X0, 1.0) 19 | np.testing.assert_array_less(-X0, 1.0) 20 | 21 | def test_unbounded_normalizer(self): 22 | M, m = 100, 3 23 | XX = np.hstack((np.random.normal(-4.0,7.0,size=(M,1)), 24 | np.random.normal(4.0,6.0,size=(M,1)), 25 | np.random.normal(2.0,3.0,size=(M,1)))) 26 | 27 | C = np.diag([7.0**2, 6.0**2, 3.0**2]) 28 | mu = np.array([-4.0, 4.0, 2.0]).reshape((3,1)) 29 | 30 | un = ut.UnboundedNormalizer(mu, C) 31 | X0 = un.normalize(XX) 32 | X1 = un.unnormalize(X0) 33 | np.testing.assert_almost_equal(XX, X1) 34 | np.testing.assert_allclose(np.mean(X0,axis=0).reshape((m,1)), np.zeros((m,1)), atol=1.0) 35 | 36 | def test_process_inputs_bad_inputs(self): 37 | self.assertRaises(ValueError, ut.process_inputs, np.array([1.0,1.0,-1.0])) 38 | 39 | def test_process_inputs(self): 40 | X0 = np.random.uniform(-1.0,1.0,size=(10,3)) 41 | X,M,m = ut.process_inputs(X0) 42 | np.testing.assert_almost_equal(X, X0) 43 | np.testing.assert_almost_equal(M, 10) 44 | np.testing.assert_almost_equal(m, 3) 45 | 46 | def test_conditional_expectations(self): 47 | f = np.array([0.2,0.2,1.0,2.0]).reshape((4,1)) 48 | ind = np.array([0,0,1,1]).reshape((4,1)) 49 | E, V = ut.conditional_expectations(f, ind) 50 | np.testing.assert_almost_equal(E,np.array([[0.2],[1.5]])) 51 | np.testing.assert_almost_equal(V,np.array([[0.0],[0.25]])) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /tests/test_plotters.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.utils.plotters as plt 4 | import numpy as np 5 | import matplotlib.pyplot as mplt 6 | 7 | class TestPlotters(TestCase): 8 | 9 | def tearDown(self): 10 | mplt.close("all") 11 | 12 | def test_eigenvalues(self): 13 | e = np.power(10*np.ones(6),-np.arange(1,7)).reshape((6,1)) 14 | plt.eigenvalues(e) 15 | 16 | def test_eigenvalues_br(self): 17 | e = np.power(10*np.ones(6),-np.arange(1,7)).reshape((6,1)) 18 | e_br = np.hstack((0.5*e,1.3*e)) 19 | plt.eigenvalues(e, e_br=e_br) 20 | 21 | def test_eigenvalues_br_label(self): 22 | e = np.power(10*np.ones(6),-np.arange(1,7)).reshape((6,1)) 23 | e_br = np.hstack((0.5*e,1.3*e)) 24 | plt.eigenvalues(e, e_br=e_br, out_label='testing') 25 | 26 | def test_subspace_errors(self): 27 | sub_br = np.array([[0.01,0.05,0.1],[0.1,0.25,0.5],[0.2,0.4,0.8]]) 28 | plt.subspace_errors(sub_br, out_label='testing') 29 | 30 | def test_eigenvectors_0(self): 31 | W = np.eye(4) 32 | plt.eigenvectors(W[:,0].reshape((4,1))) 33 | 34 | def test_eigenvectors_1(self): 35 | W = np.eye(4) 36 | plt.eigenvectors(W[:,:2].reshape((4,2))) 37 | 38 | def test_eigenvectors_0_labels(self): 39 | W = np.eye(4) 40 | in_labels = ['a','b','c','d'] 41 | plt.eigenvectors(W[:,0].reshape((4,1)), in_labels=in_labels, out_label='data') 42 | 43 | def test_eigenvectors_1_labels(self): 44 | W = np.eye(4) 45 | in_labels = ['a','b','c','d'] 46 | plt.eigenvectors(W[:,:2].reshape((4,2)), in_labels=in_labels, out_label='data') 47 | 48 | def test_eigenvectors_2(self): 49 | W = np.eye(4) 50 | plt.eigenvectors(W[:,:3].reshape((4,3))) 51 | 52 | def test_eigenvectors_3(self): 53 | W = np.eye(4) 54 | plt.eigenvectors(W) 55 | 56 | def test_eigenvectors_2_labels(self): 57 | W = np.eye(4) 58 | in_labels = ['a','b','c','d'] 59 | plt.eigenvectors(W[:,:3].reshape((4,3)), in_labels=in_labels, out_label='data') 60 | 61 | def test_eigenvectors_3_labels(self): 62 | W = np.eye(4) 63 | in_labels = ['a','b','c','d'] 64 | plt.eigenvectors(W, in_labels=in_labels, out_label='data') 65 | 66 | def test_eigenvectors_0_br(self): 67 | W = np.eye(4) 68 | in_labels = ['a','b','c','d'] 69 | W_br = np.array([[0.9,1.0],[-0.1,0.1],[-0.1,0.1],[-0.1,0.1]]) 70 | plt.eigenvectors(W[:,0].reshape((4,1)), W_br=W_br, in_labels=in_labels, out_label='data') 71 | 72 | def test_eigenvectors_1_br(self): 73 | W = np.eye(4) 74 | in_labels = ['a','b','c','d'] 75 | W_br = np.array([[0.9,1.0,-0.1,0.1],[-0.1,0.1,0.9,1.0],[-0.1,0.1,-0.1,0.1],[-0.1,0.1,-0.1,0.1]]) 76 | plt.eigenvectors(W[:,:2].reshape((4,2)), W_br=W_br, in_labels=in_labels, out_label='data') 77 | 78 | def test_eigenvectors_2_br(self): 79 | W = np.eye(4) 80 | in_labels = ['a','b','c','d'] 81 | W_br = np.array([[0.9,1.0,-0.1,0.1,-0.1,0.1], 82 | [-0.1,0.1,0.9,1.0,-0.1,0.1], 83 | [-0.1,0.1,-0.1,0.1,0.9,1.0], 84 | [-0.1,0.1,-0.1,0.1,-0.1,0.1]]) 85 | plt.eigenvectors(W[:,:3].reshape((4,3)), W_br=W_br, in_labels=in_labels, out_label='data') 86 | 87 | def test_eigenvectors_3_br(self): 88 | W = np.eye(4) 89 | in_labels = ['a','b','c','d'] 90 | W_br = np.array([[0.9,1.0,-0.1,0.1,-0.1,0.1,-0.1,0.1], 91 | [-0.1,0.1,0.9,1.0,-0.1,0.1,-0.1,0.1], 92 | [-0.1,0.1,-0.1,0.1,0.9,1.0,-0.1,0.1], 93 | [-0.1,0.1,-0.1,0.1,-0.1,0.1,0.9,1.0]]) 94 | plt.eigenvectors(W, W_br=W_br, in_labels=in_labels, out_label='data') 95 | 96 | def test_ssp1(self): 97 | y = np.random.uniform(-1.0,1.0,size=(10,1)) 98 | f = np.sin(y) 99 | plt.sufficient_summary(y, f) 100 | 101 | def test_ssp2(self): 102 | y = np.random.uniform(-1.0,1.0,size=(20,2)) 103 | f = np.sin(y[:,0])*np.sin(y[:,1]) 104 | plt.sufficient_summary(y, f) 105 | """ 106 | def test_zonotope_0(self): 107 | data = helper.load_test_npz('test_spec_decomp_1.npz') 108 | df0 = data['df'] 109 | 110 | sub = ss.Subspaces() 111 | sub.compute(df0) 112 | sub.partition(2) 113 | 114 | np.random.seed(42) 115 | bavd = dom.BoundedActiveVariableDomain(sub) 116 | vertices = bavd.vertY 117 | plt.zonotope_2d_plot(vertices) 118 | 119 | def test_zonotope_1(self): 120 | data = helper.load_test_npz('test_spec_decomp_1.npz') 121 | df0 = data['df'] 122 | 123 | sub = ss.Subspaces() 124 | sub.compute(df0) 125 | sub.partition(2) 126 | 127 | np.random.seed(42) 128 | bavd = dom.BoundedActiveVariableDomain(sub) 129 | bavm = dom.BoundedActiveVariableMap(bavd) 130 | Y = av_design(bavm, 8, NMC=1)[0] 131 | 132 | vertices = bavd.vertY 133 | plt.zonotope_2d_plot(vertices, design=Y) 134 | 135 | def test_zonotope_2(self): 136 | data = helper.load_test_npz('test_spec_decomp_1.npz') 137 | df0 = data['df'] 138 | 139 | sub = ss.Subspaces() 140 | sub.compute(df0) 141 | sub.partition(2) 142 | 143 | np.random.seed(42) 144 | bavd = dom.BoundedActiveVariableDomain(sub) 145 | bavm = dom.BoundedActiveVariableMap(bavd) 146 | Y = av_design(bavm, 8, NMC=1)[0] 147 | 148 | vertices = bavd.vertY 149 | 150 | Xp = np.random.uniform(-1.0,1.0,size=(20, sub.W1.shape[0])) 151 | Yp = np.dot(Xp, sub.W1) 152 | fp = np.sum(Yp, axis=1) 153 | 154 | plt.zonotope_2d_plot(vertices, design=Y, y=Yp, f=fp) 155 | 156 | def test_zonotope_3(self): 157 | data = helper.load_test_npz('test_spec_decomp_1.npz') 158 | df0 = data['df'] 159 | 160 | sub = ss.Subspaces() 161 | sub.compute(df0) 162 | sub.partition(2) 163 | 164 | np.random.seed(42) 165 | bavd = dom.BoundedActiveVariableDomain(sub) 166 | bavm = dom.BoundedActiveVariableMap(bavd) 167 | Y = av_design(bavm, 8, NMC=1)[0] 168 | 169 | vertices = bavd.vertY 170 | 171 | Yp, Yw = av_quadrature_rule(bavm, 8) 172 | 173 | plt.zonotope_2d_plot(vertices, design=Y, y=Yp, f=Yw, out_label='quadrature rule') 174 | """ 175 | 176 | if __name__ == '__main__': 177 | mplt.close('all') 178 | unittest.main() 179 | -------------------------------------------------------------------------------- /tests/test_qp_solver.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import numpy as np 4 | import active_subspaces.utils.qp_solver as qp 5 | 6 | class TestGurobi(TestCase): 7 | def test_gurobi_linear_program_ineq(self): 8 | c = np.ones((2,1)) 9 | A = np.array([[1.0,0.0],[0.0,1.0],[1.0,1.0]]) 10 | b = np.array([[0.1],[0.1],[0.1]]) 11 | 12 | gs = qp.QPSolver() 13 | x = gs.linear_program_ineq(c, A, b) 14 | xtrue = np.array([0.1,0.1]).reshape((2,1)) 15 | np.testing.assert_almost_equal(x,xtrue) 16 | 17 | def test_gurobi_linear_program_eq(self): 18 | c = np.ones((5,1)) 19 | A = np.array([[2.0,1.0,0.,0.,0.],[0.,0.,2.0,1.0,0.]]) 20 | b = np.array([[0.5],[0.5]]) 21 | lb, ub = -np.ones((5,1)), np.ones((5,1)) 22 | 23 | gs = qp.QPSolver() 24 | x = gs.linear_program_eq(c, A, b, lb, ub) 25 | xtrue = np.array([0.75,-1.0,0.75,-1.0,-1.0]).reshape((5,1)) 26 | np.testing.assert_almost_equal(x,xtrue) 27 | 28 | def test_gurobi_quadratic_program_bnd(self): 29 | c = np.ones((5,1)) 30 | Q = np.eye(5) 31 | lb, ub = -np.ones((5,1)), np.ones((5,1)) 32 | 33 | gs = qp.QPSolver() 34 | x = gs.quadratic_program_bnd(c, Q, lb, ub) 35 | xtrue = -0.5*np.ones((5,1)) 36 | np.testing.assert_almost_equal(x,xtrue) 37 | 38 | def test_gurobi_quadratic_program_ineq(self): 39 | c = np.ones((5,1)) 40 | Q = np.eye(5) 41 | A = np.array([[1.,0.,0.,0.,0.],[0.,1.,0.,0.,0.]]) 42 | b = np.array([[-1.0],[-1.0]]) 43 | 44 | gs = qp.QPSolver() 45 | x = gs.quadratic_program_ineq(c, Q, A, b) 46 | xtrue = -0.5*np.ones((5,1)) 47 | np.testing.assert_almost_equal(x,xtrue) 48 | 49 | def test_scipy_linear_program_ineq(self): 50 | c = np.ones((2,1)) 51 | A = np.array([[1.0,0.0],[0.0,1.0],[1.0,1.0]]) 52 | b = np.array([[0.1],[0.1],[0.1]]) 53 | 54 | gs = qp.QPSolver(solver='SCIPY') 55 | x = gs.linear_program_ineq(c, A, b) 56 | xtrue = np.array([0.1,0.1]).reshape((2,1)) 57 | np.testing.assert_almost_equal(x,xtrue) 58 | 59 | def test_scipy_linear_program_eq(self): 60 | c = np.ones((5,1)) 61 | A = np.array([[2.0,1.0,0.,0.,0.],[0.,0.,2.0,1.0,0.]]) 62 | b = np.array([[0.5],[0.5]]) 63 | lb, ub = -np.ones((5,1)), np.ones((5,1)) 64 | 65 | gs = qp.QPSolver(solver='SCIPY') 66 | x = gs.linear_program_eq(c, A, b, lb, ub) 67 | xtrue = np.array([0.75,-1.0,0.75,-1.0,-1.0]).reshape((5,1)) 68 | np.testing.assert_almost_equal(x,xtrue) 69 | 70 | def test_scipy_quadratic_program_bnd(self): 71 | c = np.ones((5,1)) 72 | Q = np.eye(5) 73 | lb, ub = -np.ones((5,1)), np.ones((5,1)) 74 | 75 | gs = qp.QPSolver(solver='SCIPY') 76 | x = gs.quadratic_program_bnd(c, Q, lb, ub) 77 | xtrue = -0.5*np.ones((5,1)) 78 | np.testing.assert_almost_equal(x,xtrue) 79 | 80 | def test_scipy_quadratic_program_ineq(self): 81 | c = np.ones((5,1)) 82 | Q = np.eye(5) 83 | A = np.array([[1.,0.,0.,0.,0.],[0.,1.,0.,0.,0.]]) 84 | b = np.array([[-1.0],[-1.0]]) 85 | 86 | gs = qp.QPSolver(solver='SCIPY') 87 | x = gs.quadratic_program_ineq(c, Q, A, b) 88 | xtrue = -0.5*np.ones((5,1)) 89 | np.testing.assert_almost_equal(x,xtrue) 90 | 91 | def test_bad_solver(self): 92 | gs = qp.QPSolver(solver='CVXOPT') 93 | 94 | if __name__ == '__main__': 95 | unittest.main() 96 | 97 | -------------------------------------------------------------------------------- /tests/test_quadrature.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.utils.quadrature as gq 4 | import numpy as np 5 | 6 | class TestQuadrature(TestCase): 7 | 8 | def test_r_hermite_type_error(self): 9 | self.assertRaises(TypeError, gq.r_hermite, 'string') 10 | 11 | def test_r_hermite_0(self): 12 | self.assertRaises(ValueError, gq.r_hermite, 0) 13 | 14 | def test_r_hermite_1(self): 15 | v = gq.r_hermite(1) 16 | self.assertIsInstance(v, np.ndarray) 17 | np.testing.assert_almost_equal(v, np.array([[0.0, 1.0]])) 18 | 19 | def test_r_hermite_2(self): 20 | v = gq.r_hermite(2) 21 | self.assertIsInstance(v, np.ndarray) 22 | np.testing.assert_almost_equal(v, np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 2.0]])) 23 | 24 | def test_r_hermite_5(self): 25 | v = gq.r_hermite(5) 26 | self.assertIsInstance(v, np.ndarray) 27 | np.testing.assert_almost_equal(v, np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 2.0], [0.0, 3.0], [0.0, 4.0], [0.0, 5.0]])) 28 | 29 | def test_jacobi_matrix_multi_dimension_1(self): 30 | self.assertRaises(ValueError, gq.jacobi_matrix, np.array([0.0])) 31 | 32 | def test_jacobi_matrix_multi_dimension_num_columns_1(self): 33 | self.assertRaises(ValueError, gq.jacobi_matrix, np.array([[0.0], [0.1]])) 34 | 35 | def test_jacobi_matrix_multi_dimension_num_columns_2(self): 36 | self.assertRaises(ValueError, gq.jacobi_matrix, np.array([[0.0, 2.4, 5.2], [0.1, 21.5, 0.3]])) 37 | 38 | def test_jacobi_matrix_one_row_1(self): 39 | a = np.array([[0.0, 1.0]]) 40 | np.testing.assert_almost_equal(gq.jacobi_matrix(a), 0.0) 41 | 42 | def test_jacobi_matrix_one_row_2(self): 43 | a = np.array([[2.0, 1.0]]) 44 | np.testing.assert_almost_equal(gq.jacobi_matrix(a), 2.0) 45 | 46 | def test_jacobi_matrix_5(self): 47 | gq.jacobi_matrix(gq.r_hermite(5)) 48 | 49 | def test_gh1d_7pts(self): 50 | p,w = gq.gh1d(7) 51 | 52 | def test_gauss_hermite_1d_array_arg(self): 53 | p,w = gq.gauss_hermite([7]) 54 | 55 | def test_gauss_hermite_1d_int_arg(self): 56 | p,w = gq.gauss_hermite(7) 57 | 58 | def test_gauss_hermite_2d(self): 59 | p,w = gq.gauss_hermite([3,3]) 60 | 61 | def test_gauss_hermite_3d(self): 62 | p,w = gq.gauss_hermite([3,3,4]) 63 | 64 | def test_gauss_hermite_type_error(self): 65 | self.assertRaises(TypeError, gq.gauss_hermite, 'sting') 66 | 67 | if __name__ == '__main__': 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /tests/test_simrunners.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from unittest import TestCase 3 | import unittest 4 | import active_subspaces.utils.simrunners as sruns 5 | 6 | def fun(x): 7 | return 0.5*np.dot(x, x.T) 8 | 9 | def dfun1(x): 10 | m = x.size 11 | return x.reshape((m,1)) 12 | 13 | def dfun2(x): 14 | m = x.size 15 | return x.reshape((1,m)) 16 | 17 | def dfun3(x): 18 | m = x.size 19 | return x.reshape(m) 20 | 21 | class TestSimrunners(TestCase): 22 | 23 | def test_simulation_runner(self): 24 | X = np.array([[0.0, 1.0], [1.0, 0.0], [0.0, 0.0]]) 25 | sr = sruns.SimulationRunner(fun) 26 | ftrue = np.array([[0.5], [0.5], [0.0]]) 27 | f = sr.run(X) 28 | np.testing.assert_almost_equal(f, ftrue) 29 | 30 | def test_simulation_grad_runner1(self): 31 | X = np.array([[0.0, 1.0], [1.0, 0.0], [0.0, 0.0]]) 32 | sgr = sruns.SimulationGradientRunner(dfun1) 33 | df = sgr.run(X) 34 | np.testing.assert_almost_equal(df, X) 35 | 36 | def test_simulation_grad_runner2(self): 37 | X = np.array([[0.0, 1.0], [1.0, 0.0], [0.0, 0.0]]) 38 | sgr = sruns.SimulationGradientRunner(dfun2) 39 | df = sgr.run(X) 40 | np.testing.assert_almost_equal(df, X) 41 | 42 | def test_simulation_grad_runner3(self): 43 | X = np.array([[0.0, 1.0], [1.0, 0.0], [0.0, 0.0]]) 44 | sgr = sruns.SimulationGradientRunner(dfun3) 45 | df = sgr.run(X) 46 | np.testing.assert_almost_equal(df, X) 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/test_subspaces.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import unittest 3 | import active_subspaces.subspaces as ss 4 | import numpy as np 5 | 6 | class TestSubspaces(TestCase): 7 | 8 | def test_sorted_eigh(self): 9 | np.random.seed(42) 10 | X = np.random.normal(size=(3,3)) 11 | C = np.dot(X.transpose(),X) 12 | e, W = ss.sorted_eigh(C) 13 | np.testing.assert_array_less(e[1], e[0]) 14 | 15 | def test_active_subspace(self): 16 | np.random.seed(42) 17 | df = np.random.normal(size=(10,3)) 18 | weights = np.ones((10,1)) / 10 19 | e, W = ss.active_subspace(df, weights) 20 | np.testing.assert_array_less(e[1], e[0]) 21 | 22 | def test_ols_subspace(self): 23 | np.random.seed(42) 24 | X = np.random.normal(size=(20,3)) 25 | f = np.random.normal(size=(20,1)) 26 | weights = np.ones((20,1)) / 20 27 | e, W = ss.ols_subspace(X, f, weights) 28 | np.testing.assert_array_less(e[1], e[0]) 29 | 30 | def test_qphd_subspace(self): 31 | np.random.seed(42) 32 | X = np.random.normal(size=(50,3)) 33 | f = np.random.normal(size=(50,1)) 34 | weights = np.ones((50,1)) / 50 35 | e, W = ss.qphd_subspace(X, f, weights) 36 | np.testing.assert_array_less(e[1], e[0]) 37 | 38 | def test_opg_subspace(self): 39 | np.random.seed(42) 40 | X = np.random.normal(size=(50,3)) 41 | f = np.random.normal(size=(50,1)) 42 | weights = np.ones((50,1)) / 50 43 | e, W = ss.opg_subspace(X, f, weights) 44 | np.testing.assert_array_less(e[1], e[0]) 45 | 46 | def test_bootstrap_replicate(self): 47 | np.random.seed(42) 48 | X = np.random.normal(size=(10,3)) 49 | f = np.random.normal(size=(10,1)) 50 | df = np.random.normal(size=(10,3)) 51 | weights = np.ones((10,1)) / 10 52 | X0, f0, df0, w0 = ss._bootstrap_replicate(X, f, df, weights) 53 | assert(np.any(weights==w0[0])) 54 | assert(np.any(f==f0[1])) 55 | 56 | def test_bootstrap_ranges(self): 57 | np.random.seed(42) 58 | X = np.random.normal(size=(50,3)) 59 | f = np.random.normal(size=(50,1)) 60 | df = np.random.normal(size=(50,3)) 61 | weights = np.ones((50,1)) / 50 62 | 63 | e, W = ss.active_subspace(df, weights) 64 | ssmethod = lambda X, f, df, weights: ss.active_subspace(df, weights) 65 | d = ss._bootstrap_ranges(e, W, None, None, df, weights, ssmethod, nboot=10) 66 | 67 | e, W = ss.ols_subspace(X, f, weights) 68 | ssmethod = lambda X, f, df, weights: ss.ols_subspace(X, f, weights) 69 | d = ss._bootstrap_ranges(e, W, X, f, None, weights, ssmethod, nboot=10) 70 | 71 | e, W = ss.qphd_subspace(X, f, weights) 72 | ssmethod = lambda X, f, df, weights: ss.qphd_subspace(X, f, weights) 73 | d = ss._bootstrap_ranges(e, W, X, f, None, weights, ssmethod, nboot=10) 74 | 75 | e, W = ss.opg_subspace(X, f, weights) 76 | ssmethod = lambda X, f, df, weights: ss.opg_subspace(X, f, weights) 77 | d = ss._bootstrap_ranges(e, W, X, f, None, weights, ssmethod, nboot=10) 78 | 79 | def test_eig_partition(self): 80 | np.random.seed(42) 81 | df = np.random.normal(size=(10,3)) 82 | weights = np.ones((10,1)) / 10 83 | e, W = ss.active_subspace(df, weights) 84 | d = ss.eig_partition(e) 85 | 86 | def test_errbnd_partition(self): 87 | np.random.seed(42) 88 | df = np.random.normal(size=(10,3)) 89 | weights = np.ones((10,1)) / 10 90 | e, W = ss.active_subspace(df, weights) 91 | ssmethod = lambda X, f, df, weights: ss.active_subspace(df, weights) 92 | e_br, sub_br, li_F = ss._bootstrap_ranges(e, W, None, None, df, weights, ssmethod, nboot=10) 93 | sub_err = sub_br[:,1].reshape((2, 1)) 94 | d = ss.errbnd_partition(e, sub_err) 95 | 96 | def test_ladle_partition(self): 97 | np.random.seed(42) 98 | df = np.random.normal(size=(10,3)) 99 | weights = np.ones((10,1)) / 10 100 | e, W = ss.active_subspace(df, weights) 101 | ssmethod = lambda X, f, df, weights: ss.active_subspace(df, weights) 102 | e_br, sub_br, li_F = ss._bootstrap_ranges(e, W, None, None, df, weights, ssmethod, nboot=10) 103 | d = ss.ladle_partition(e, li_F) 104 | 105 | def test_subspace_class(self): 106 | np.random.seed(42) 107 | X = np.random.normal(size=(50,3)) 108 | f = np.random.normal(size=(50,1)) 109 | df = np.random.normal(size=(50,3)) 110 | weights = np.ones((50,1)) / 50 111 | 112 | sub = ss.Subspaces() 113 | sub.compute(X, f, df, weights) 114 | sub.compute(X, f, df, weights, sstype='AS') 115 | sub.compute(X, f, df, weights, sstype='OLS') 116 | sub.compute(X, f, df, weights, sstype='QPHD') 117 | sub.compute(X, f, df, weights, sstype='OPG') 118 | 119 | sub.compute(X, f, df, weights, sstype='AS', nboot=10) 120 | sub.compute(X, f, df, weights, sstype='OLS', nboot=10) 121 | sub.compute(X, f, df, weights, sstype='QPHD', nboot=10) 122 | sub.compute(X, f, df, weights, sstype='OPG', nboot=10) 123 | 124 | sub.compute(X, f, df, weights, sstype='AS', ptype='EVG', nboot=100) 125 | sub.compute(X, f, df, weights, sstype='AS', ptype='RS', nboot=100) 126 | sub.compute(X, f, df, weights, sstype='AS', ptype='LI', nboot=100) 127 | 128 | 129 | if __name__ == '__main__': 130 | unittest.main() 131 | -------------------------------------------------------------------------------- /tutorials/test_functions/borehole/borehole_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import active_subspaces as ac 3 | 4 | def borehole(xx): 5 | #each row of xx should be [rw, r, Tu, Hu, Tl, Hl, L, Kw] in the normalized space 6 | #returns column vector of borehole function at each row of inputs 7 | 8 | x = xx.copy() 9 | x = np.atleast_2d(x) 10 | M = x.shape[0] 11 | 12 | #unnormalize inpus 13 | xl = np.array([63070, 990, 63.1, 700, 1120, 9855]) 14 | xu = np.array([115600, 1110, 116, 820, 1680, 12045]) 15 | x[:,2:] = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x[:,2:]) 16 | x[:,0] = .0161812*x[:,0] + .1 17 | x[:,1] = np.exp(1.0056*x[:,1] + 7.71) 18 | 19 | rw = x[:,0]; r = x[:,1]; Tu = x[:,2]; Hu = x[:,3] 20 | Tl = x[:,4]; Hl = x[:,5]; L = x[:,6]; Kw = x[:,7] 21 | 22 | pi = np.pi 23 | 24 | return (2*pi*Tu*(Hu - Hl)/(np.log(r/rw)*(1 + 2*L*Tu/(np.log(r/rw)*rw**2*Kw) + Tu/Tl))).reshape(M, 1) 25 | 26 | def borehole_grad(xx): 27 | #each row of xx should be [rw, r, Tu, Hu, Tl, Hl, L, Kw] in the normalized space 28 | #returns matrix whose ith row is gradient of borehole function at ith row of inputs 29 | 30 | x = xx.copy() 31 | x = np.atleast_2d(x) 32 | M = x.shape[0] 33 | 34 | #unnormalize inpus 35 | xl = np.array([63070, 990, 63.1, 700, 1120, 9855]) 36 | xu = np.array([115600, 1110, 116, 820, 1680, 12045]) 37 | x[:,2:] = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x[:,2:]) 38 | x[:,0] = .0161812*x[:,0] + .1 39 | x[:,1] = np.exp(1.0056*x[:,1] + 7.71) 40 | 41 | rw = x[:,0]; r = x[:,1]; Tu = x[:,2]; Hu = x[:,3] 42 | Tl = x[:,4]; Hl = x[:,5]; L = x[:,6]; Kw = x[:,7] 43 | 44 | pi = np.pi 45 | Q = 1 + 2*L*Tu/(np.log(r/rw)*rw**2*Kw) + Tu/Tl #Convenience variable 46 | l = np.log(r/rw) #Convenience variable 47 | 48 | dfdrw = (-2*pi*Tu*(Hu - Hl)*(Q*l)**-2*(-Q/rw - l*2*L*Tu/Kw*(l*rw**2)**-2*(-rw + 2*rw*l)))[:,None] 49 | dfdr = (-2*pi*Tu*(Hu - Hl)*(l*Q)**-2*(Q/r - 2*L*Tu/(r*rw**2*Kw*l)))[:,None] 50 | dfdTu = (2*pi*(Hu - Hl)/(l*Q) - 2*pi*Tu*(Hu - Hl)*(l*Q)**-2*(l*(2*L/(l*rw**2*Kw)+1./Tl)))[:,None] 51 | dfdHu = (2*pi*Tu/(l*Q))[:,None] 52 | dfdTl = (2*pi*Tu*(Hu - Hl)*(l*Q)**-2*l*Tu/Tl**2)[:,None] 53 | dfdHl = (-2*pi*Tu/(l*Q))[:,None] 54 | dfdL = (-2*pi*Tu*(Hu - Hl)*(l*Q)**-2*2*Tu/(rw**2*Kw))[:,None] 55 | dfdKw = (2*pi*Tu*(Hu - Hl)*(l*Q)**-2*2*L*Tu/(rw**2*Kw**2))[:,None] 56 | 57 | #The gradient components must be scaled in accordance with the chain rule: df/dx = df/dy*dy/dx 58 | r = np.log(r); r = ((r - 7.71)/1.0056).reshape(M, 1) 59 | return np.hstack((dfdrw*.0161812, dfdr*1.0056*np.exp(1.0056*r + 7.71), dfdTu*(115600 - 63070)/2., dfdHu*(1110 - 990)/2.,\ 60 | dfdTl*(116 - 63.1)/2., dfdHl*(820 - 700)/2., dfdL*(1680 - 1120)/2., dfdKw*(12045 - 9855)/2.)) 61 | -------------------------------------------------------------------------------- /tutorials/test_functions/otl_circuit/otlcircuit_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import active_subspaces as ac 3 | def circuit(xx): 4 | #each row of xx should be [Rb1, Rb2, Rf, Rc1, Rc2, beta] in the normalized input space 5 | #returns column vector of circuit function at each row of inputs 6 | 7 | x = xx.copy() 8 | x = np.atleast_2d(x) 9 | M = x.shape[0] 10 | 11 | #Unnormalize inputs 12 | xl = np.array([50, 25, .5, 1.2, .25, 50]) 13 | xu = np.array([150, 70, 3, 2.5, 1.2, 300]) 14 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 15 | 16 | Rb1 = x[:,0]; Rb2 = x[:,1]; Rf = x[:,2] 17 | Rc1 = x[:,3]; Rc2 = x[:,4]; beta = x[:,5] 18 | 19 | Vb1 = 12*Rb2/(Rb1 + Rb2) 20 | denom = beta*(Rc2 + 9) + Rf #Convenience variable 21 | 22 | return ((Vb1 + .74)*beta*(Rc2 + 9)/denom + 11.35*Rf/denom + .74*Rf*beta*(Rc2 + 9)/(Rc1*denom)).reshape(M, 1) 23 | 24 | def circuit_grad(xx): 25 | #each row of xx should be [Rb1, Rb2, Rf, Rc1, Rc2, beta] in the normalized input space 26 | #returns matrix whose ith row is gradient of circuit function at ith row of inputs 27 | 28 | x = xx.copy() 29 | x = np.atleast_2d(x) 30 | 31 | #Unnormalize inputs 32 | xl = np.array([50, 25, .5, 1.2, .25, 50]) 33 | xu = np.array([150, 70, 3, 2.5, 1.2, 300]) 34 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 35 | 36 | Rb1 = x[:,0]; Rb2 = x[:,1]; Rf = x[:,2] 37 | Rc1 = x[:,3]; Rc2 = x[:,4]; beta = x[:,5] 38 | 39 | Vb1 = 12*Rb2/(Rb1 + Rb2) 40 | denom = beta*(Rc2 + 9) + Rf #Convenience variable 41 | 42 | dvdRb1 = (-12.*Rb2*beta*(Rc2 + 9.)/(denom*(Rb1 + Rb2)**2))[:,None] 43 | dvdRb2 = (beta*(Rc2 + 9.)/denom*(12./(Rb1 + Rb2) - 12.*Rb2/(Rb1 + Rb2)**2))[:,None] 44 | dvdRf = (-(Vb1 + .74)*beta*(Rc2 + 9.)/denom**2 + 11.35/denom - 11.35*Rf/denom**2 + .74*beta*(Rc2 + 9.)/(Rc1*denom)-\ 45 | .74*Rf*beta*(Rc2 + 9.)/(Rc1*denom**2))[:,None] 46 | dvdRc1 = (-.74*Rf*beta*(Rc2 + 9.)/(Rc1**2*denom))[:,None] 47 | dvdRc2 = (beta*(Vb1+.74)/denom - (Rc2 + 9.)*beta**2*(Vb1 + .74)/denom**2 - 11.35*Rf*beta/denom**2 +\ 48 | .74*Rf*beta/(Rc1*denom) - .74*Rf*beta**2*(Rc2 + 9)/(Rc1*denom**2))[:,None] 49 | dvdbeta = ((Vb1 + .74)*(Rc2 + 9.)/denom - (Vb1 + .74)*beta*(Rc2 + 9.)**2/denom**2 - 11.35*Rf*(Rc2 + 9.)/denom**2 +\ 50 | .74*Rf*(Rc2 + 9.)/(Rc1*denom) - .74*Rf*beta*(Rc2 + 9.)**2/(Rc1*denom**2))[:,None] 51 | 52 | #The gradient components must be scaled in accordance with the chain rule: df/dx = df/dy*dy/dx 53 | return np.hstack((dvdRb1*(150 - 50)/2., dvdRb2*(70 - 25)/2., dvdRf*(3 - .5)/2., dvdRc1*(2.5 - 1.2)/2.,\ 54 | dvdRc2*(1.2 - .25)/2., dvdbeta*(300 - 50)/2.)) 55 | 56 | -------------------------------------------------------------------------------- /tutorials/test_functions/piston/piston_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import active_subspaces as ac 3 | def piston(xx): 4 | #each row of xx should be [M, S, V0, k, P0, Ta, T0] in the normalized input space 5 | #returns a column vector of the piston function at each row of inputs 6 | 7 | x = xx.copy() 8 | x = np.atleast_2d(x) 9 | M0 = x.shape[0] 10 | 11 | #Unnormalize inputs 12 | xl = np.array([30, .005, .002, 1000, 90000, 290, 340]) 13 | xu = np.array([60, .02, .01, 5000, 110000, 296, 360]) 14 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 15 | 16 | M = x[:,0]; S = x[:,1]; V0 = x[:,2]; k = x[:,3] 17 | P0 = x[:,4]; Ta = x[:,5]; T0 = x[:,6] 18 | 19 | A = P0*S + 19.62*M - k*V0/S 20 | V = S/(2*k)*(-A + np.sqrt(A**2 + 4*k*P0*V0*Ta/T0)) 21 | pi = np.pi 22 | 23 | return (2*pi*np.sqrt(M/(k + S**2*P0*V0*Ta/(T0*V**2)))).reshape(M0, 1) 24 | 25 | def piston_grad(xx): 26 | #each row of xx should be [M, S, V0, k, P0, Ta, T0] in the normalized input space 27 | #returns a matrix whose ith row is gradient of piston function at ith row of inputs 28 | 29 | x = xx.copy() 30 | x = np.atleast_2d(x) 31 | 32 | #Unnormalize inputs 33 | xl = np.array([30, .005, .002, 1000, 90000, 290, 340]) 34 | xu = np.array([60, .02, .01, 5000, 110000, 296, 360]) 35 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 36 | 37 | M = x[:,0]; S = x[:,1]; V0 = x[:,2]; k = x[:,3] 38 | P0 = x[:,4]; Ta = x[:,5]; T0 = x[:,6] 39 | 40 | A = P0*S + 19.62*M - k*V0/S 41 | V = S/(2*k)*(-A + np.sqrt(A**2 + 4*k*P0*V0*Ta/T0)) 42 | pi = np.pi 43 | Q = k + S**2*P0*V0*Ta/(T0*V**2) #Convenience variables 44 | R = A**2 + 4*k*P0*V0*Ta/T0 45 | 46 | dCdM = (pi*(M*Q)**-.5 + 2*pi*M**.5*Q**-1.5*S**3*P0*V0*Ta/(2*k*T0*V**3)*(R**-.5*A*19.62 - 19.62))[:,None] 47 | dCdS = (-pi*M**.5*Q**-1.5*(2*S*P0*V0*Ta/(T0*V**2) - 2*S**2*P0*V0*Ta/(T0*V**3)*(V/S + S/(2*k)*(R**-.5*A*(P0 + k*V0/S**2) - P0 - k*V0/S**2))))[:,None] 48 | dCdV0 = (-pi*M**.5*Q**-1.5*(S**2*P0*Ta/(T0*V**2) - 2*S**3*P0*V0*Ta/(2*k*T0*V**3)*(R**-.5/2*(4*k*P0*Ta/T0 - 2*A*k/S) + k/S)))[:,None] 49 | dCdk = (-pi*M**.5*Q**-1.5*(1 - 2*S**2*P0*V0*Ta/(T0*V**3)*(-V/k + S/(2*k)*(R**-.5/2*(4*P0*V0*Ta/T0 - 2*A*V0/S) + V0/S))))[:,None] 50 | dCdP0 = (-pi*M**.5*Q**-1.5*(S**2*V0*Ta/(T0*V**2) - 2*S**3*P0*V0*Ta/(2*k*T0*V**3)*(R**-.5/2*(4*k*V0*Ta/T0 + 2*A*S) - S)))[:,None] 51 | dCdTa = (-pi*M**.5*Q**-1.5*(S**2*P0*V0/(T0*V**2) - 2*S**3*P0*V0*Ta/(2*k*T0*V**3)*(R**-.5/2*4*k*P0*V0/T0)))[:,None] 52 | dCdT0 = (pi*M**.5*Q**-1.5*(S**2*P0*V0*Ta/(T0**2*V**2) + 2*S**3*P0*V0*Ta/(2*k*T0*V**3)*(-R**-.5/2*4*k*P0*V0*Ta/T0**2)))[:,None] 53 | 54 | #The gradient components must be scaled in accordance with the chain rule: df/dx = df/dy*dy/dx 55 | return np.hstack((dCdM*(60 - 30)/2., dCdS*(.02 - .005)/2., dCdV0*(.01 - .002)/2., dCdk*(5000 - 1000)/2., dCdP0*(110000 - 90000)/2.,\ 56 | dCdTa*(296 - 290)/2., dCdT0*(360 - 340)/2.)) 57 | 58 | -------------------------------------------------------------------------------- /tutorials/test_functions/robot/robot_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import active_subspaces as ac 3 | def robot(xx): 4 | #each row of xx should be [t1, t2, t3, t4, L1, L2, L3, L4] in the normalized input space 5 | #returns a column vector of the piston function at each row of inputs 6 | 7 | x = xx.copy() 8 | x = np.atleast_2d(x) 9 | M = x.shape[0] 10 | 11 | #Unnormalize inputs 12 | xl = np.array([0, 0, 0, 0, 0, 0, 0, 0]) 13 | xu = np.array([2*np.pi, 2*np.pi, 2*np.pi, 2*np.pi, 1, 1, 1, 1]) 14 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 15 | 16 | t1 = x[:,0]; t2 = x[:,1]; t3 = x[:,2]; t4 = x[:,3] 17 | L1 = x[:,4]; L2 = x[:,5]; L3 = x[:,6]; L4 = x[:,7] 18 | 19 | cos = np.cos 20 | sin = np.sin 21 | 22 | u = L1*cos(t1) + L2*cos(t1 + t2) + L3*cos(t1 + t2 + t3) + L4*cos(t1 + t2 + t3 + t4) 23 | v = L1*sin(t1) + L2*sin(t1 + t2) + L3*sin(t1 + t2 + t3) + L4*sin(t1 + t2 + t3 + t4) 24 | 25 | return ((u**2 + v**2)**.5).reshape(M, 1) 26 | 27 | def robot_grad(xx): 28 | #each row of xx should be [t1, t2, t3, t4, L1, L2, L3, L4] in the normalized input space 29 | #returns a matrix whose ith row is gradient of robot function at ith row of inputs 30 | 31 | x = xx.copy() 32 | x = np.atleast_2d(x) 33 | M = x.shape[0] 34 | 35 | #Unnormalize inputs 36 | xl = np.array([0, 0, 0, 0, 0, 0, 0, 0]) 37 | xu = np.array([2*np.pi, 2*np.pi, 2*np.pi, 2*np.pi, 1, 1, 1, 1]) 38 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 39 | 40 | t1 = x[:,0]; t2 = x[:,1]; t3 = x[:,2]; t4 = x[:,3] 41 | L1 = x[:,4]; L2 = x[:,5]; L3 = x[:,6]; L4 = x[:,7] 42 | 43 | cos = np.cos 44 | sin = np.sin 45 | 46 | u = L1*cos(t1) + L2*cos(t1 + t2) + L3*cos(t1 + t2 + t3) + L4*cos(t1 + t2 + t3 + t4) 47 | v = L1*sin(t1) + L2*sin(t1 + t2) + L3*sin(t1 + t2 + t3) + L4*sin(t1 + t2 + t3 + t4) 48 | 49 | dfdt1 = np.zeros((M, 1)) 50 | dfdt2 = -((u**2 + v**2)**-.5*(L1*(v*cos(t1) - u*sin(t1))))[:,None] 51 | dfdt3 = -((u**2 + v**2)**-.5*(L1*(v*cos(t1) - u*sin(t1)) + L2*(v*cos(t1 + t2) - u*sin(t1 + t2))))[:,None] 52 | dfdt4 = -((u**2 + v**2)**-.5*(L1*(v*cos(t1) - u*sin(t1)) + L2*(v*cos(t1 + t2) - u*sin(t1 + t2)) + \ 53 | L3*(v*cos(t1 + t2 + t3) - u*sin(t1 + t2 + t3))))[:,None] 54 | dfdL1 = (.5*(u**2 + v**2)**-.5*(2*u*cos(t1) + 2*v*sin(t1)))[:,None] 55 | dfdL2 = (.5*(u**2 + v**2)**-.5*(2*u*cos(t1 + t2) + 2*v*sin(t1 + t2)))[:,None] 56 | dfdL3 = (.5*(u**2 + v**2)**-.5*(2*u*cos(t1 + t2 + t3) + 2*v*sin(t1 + t2 + t3)))[:,None] 57 | dfdL4 = (.5*(u**2 + v**2)**-.5*(2*u*cos(t1 + t2 + t3 + t4) + 2*v*sin(t1 + t2 + t3 + t4)))[:,None] 58 | 59 | #The gradient components must be scaled in accordance with the chain rule: df/dx = df/dy*dy/dx 60 | return np.hstack((dfdt1*(2*np.pi)/2., dfdt2*(2*np.pi)/2., dfdt3*(2*np.pi)/2., dfdt4*(2*np.pi)/2., dfdL1*(1)/2.,\ 61 | dfdL2*(1)/2., dfdL3*(1)/2., dfdL4*(1)/2.)) 62 | 63 | -------------------------------------------------------------------------------- /tutorials/test_functions/wing_weight/wing_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import active_subspaces as ac 3 | def wing(xx): 4 | #each row of xx should be [Sw. Wfw, A, Lambda, q, lambda, tc, Nz, Wdg, Wp] in the normalized input space 5 | #returns column vector of wing function at each row of inputs 6 | 7 | x = xx.copy() 8 | x = np.atleast_2d(x) 9 | M = x.shape[0] 10 | 11 | #Unnormalize inputs 12 | xl = np.array([150, 220, 6, -10, 16, .5, .08, 2.5, 1700, .025]) 13 | xu = np.array([200, 300, 10, 10, 45, 1, .18, 6, 2500, .08]) 14 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 15 | 16 | Sw = x[:,0]; Wfw = x[:,1]; A = x[:,2]; L = x[:,3]*np.pi/180.; q = x[:,4] 17 | l = x[:,5]; tc = x[:,6]; Nz = x[:,7]; Wdg = x[:,8]; Wp = x[:,9] 18 | 19 | return (.036*Sw**.758*Wfw**.0035*A**.6*np.cos(L)**-.9*q**.006*l**.04*100**-.3*tc**-.3*Nz**.49*Wdg**.49 + Sw*Wp).reshape(M, 1) 20 | 21 | def wing_grad(xx): 22 | #each row of xx should be [Sw. Wfw, A, Lambda, q, lambda, tc, Nz, Wdg, Wp] in the normalized input space 23 | #returns matrix whose ith row is gradient of wing function at ith row of inputs 24 | 25 | x = xx.copy() 26 | x = np.atleast_2d(x) 27 | 28 | #Unnormalize inputs 29 | xl = np.array([150, 220, 6, -10, 16, .5, .08, 2.5, 1700, .025]) 30 | xu = np.array([200, 300, 10, 10, 45, 1, .18, 6, 2500, .08]) 31 | x = ac.utils.misc.BoundedNormalizer(xl, xu).unnormalize(x) 32 | 33 | Sw = x[:,0]; Wfw = x[:,1]; A = x[:,2]; L = x[:,3]*np.pi/180.; q = x[:,4] 34 | l = x[:,5]; tc = x[:,6]; Nz = x[:,7]; Wdg = x[:,8]; Wp = x[:,9] 35 | 36 | Q = .036*Sw**.758*Wfw**.0035*A**.6*np.cos(L)**-.9*q**.006*l**.04*100**-.3*tc**-.3*Nz**.49*Wdg**.49 #Convenience variable 37 | 38 | dfdSw = (.758*Q/Sw + Wp)[:,None] 39 | dfdWfw = (.0035*Q/Wfw)[:,None] 40 | dfdA = (.6*Q/A)[:,None] 41 | dfdL = (.9*Q*np.sin(L)/np.cos(L))[:,None] 42 | dfdq = (.006*Q/q)[:,None] 43 | dfdl = (.04*Q/l)[:,None] 44 | dfdtc = (-.3*Q/tc)[:,None] 45 | dfdNz = (.49*Q/Nz)[:,None] 46 | dfdWdg = (.49*Q/Wdg)[:,None] 47 | dfdWp = (Sw)[:,None] 48 | 49 | #The gradient components must be scaled in accordance with the chain rule: df/dx = df/dy*dy/dx 50 | return np.hstack((dfdSw*(200 - 150)/2., dfdWfw*(300 - 220)/2., dfdA*(10 - 6)/2., dfdL*(10 + 10)*np.pi/(2.*180), dfdq*(45 - 16)/2.,\ 51 | dfdl*(1 - .5)/2., dfdtc*(.18 - .08)/2., dfdNz*(6 - 2.5)/2., dfdWdg*(2500 - 1700)/2., dfdWp*(.08 - .025)/2.)) 52 | 53 | -------------------------------------------------------------------------------- /tutorials/wing_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import active_subspaces as ac 3 | def wing(xx): 4 | #each row of xx should be [Sw. Wfw, A, Lambda, q, lambda, tc, Nz, Wdg, Wp] in the normalized input space 5 | #returns column vector of wing function at each row of inputs 6 | 7 | x = xx.copy() 8 | x = np.atleast_2d(x) 9 | M = x.shape[0] 10 | 11 | Sw = x[:,0]; Wfw = x[:,1]; A = x[:,2]; L = x[:,3]*np.pi/180.; q = x[:,4] 12 | l = x[:,5]; tc = x[:,6]; Nz = x[:,7]; Wdg = x[:,8]; Wp = x[:,9] 13 | 14 | return (.036*Sw**.758*Wfw**.0035*A**.6*np.cos(L)**-.9*q**.006*l**.04*100**-.3*tc**-.3*Nz**.49*Wdg**.49 + Sw*Wp).reshape(M, 1) 15 | 16 | def wing_grad(xx): 17 | #each row of xx should be [Sw. Wfw, A, Lambda, q, lambda, tc, Nz, Wdg, Wp] in the normalized input space 18 | #returns matrix whose ith row is gradient of wing function at ith row of inputs 19 | 20 | x = xx.copy() 21 | x = np.atleast_2d(x) 22 | 23 | Sw = x[:,0]; Wfw = x[:,1]; A = x[:,2]; L = x[:,3]*np.pi/180.; q = x[:,4] 24 | l = x[:,5]; tc = x[:,6]; Nz = x[:,7]; Wdg = x[:,8]; Wp = x[:,9] 25 | 26 | Q = .036*Sw**.758*Wfw**.0035*A**.6*np.cos(L)**-.9*q**.006*l**.04*100**-.3*tc**-.3*Nz**.49*Wdg**.49 #Convenience variable 27 | 28 | dfdSw = (.758*Q/Sw + Wp)[:,None] 29 | dfdWfw = (.0035*Q/Wfw)[:,None] 30 | dfdA = (.6*Q/A)[:,None] 31 | dfdL = (.9*Q*np.sin(L)/np.cos(L))[:,None] 32 | dfdq = (.006*Q/q)[:,None] 33 | dfdl = (.04*Q/l)[:,None] 34 | dfdtc = (-.3*Q/tc)[:,None] 35 | dfdNz = (.49*Q/Nz)[:,None] 36 | dfdWdg = (.49*Q/Wdg)[:,None] 37 | dfdWp = (Sw)[:,None] 38 | 39 | return np.hstack((dfdSw, dfdWfw, dfdA, dfdL, dfdq, dfdl, dfdtc, dfdNz, dfdWdg, dfdWp)) --------------------------------------------------------------------------------