├── .gitignore ├── .gitmodules ├── .travis.yml ├── HOW_TO_ADD_KERNEL.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── demo.png ├── docs ├── .gitignore ├── Makefile ├── _code │ ├── .gitignore │ ├── benchmark.py │ ├── hyper.py │ ├── hyper_sample.py │ ├── hyper_sample_results.py │ ├── model.py │ └── quickstart.py ├── _static │ ├── hyper │ │ ├── data.png │ │ ├── figure.png │ │ ├── mcmc.png │ │ └── results.txt │ ├── js │ │ └── analytics.js │ ├── model │ │ ├── data.png │ │ ├── gp-corner.png │ │ ├── gp-results.png │ │ ├── ind-corner.png │ │ └── ind-results.png │ └── quickstart │ │ └── conditional.png ├── conf.py ├── index.rst └── user │ ├── gp.rst │ ├── hyper.rst │ ├── kernels.rst │ ├── model.rst │ ├── quickstart.rst │ └── solvers.rst ├── document ├── .gitignore ├── Makefile ├── code │ └── exo_demo_1 │ │ ├── 2301306-injection-gp.pkl │ │ ├── 2301306-injection-median.pkl │ │ ├── 2301306-injection.fits │ │ ├── 2973073-injection-gp.pkl │ │ ├── 2973073-injection-median.pkl │ │ ├── 2973073-injection.fits │ │ ├── fit_detrended.py │ │ ├── fit_gp.py │ │ ├── inject.py │ │ ├── load_data.py │ │ ├── models.py │ │ └── results.py └── ms.tex ├── examples ├── 2d.py ├── simple.py ├── test_learning_curve_kernel.py ├── test_polynomial_noise_kernel.py └── test_task_kernel.py ├── george ├── __init__.py ├── _kernels.pyx ├── basic.py ├── generate_kernel_defs.py ├── gp.py ├── hodlr.pyx ├── kernels.pxd ├── kernels.py ├── testing │ ├── __init__.py │ ├── test_gp.py │ ├── test_kernels.py │ ├── test_pickle.py │ ├── test_solvers.py │ └── test_tutorial.py └── utils.py ├── george_extension.py ├── images ├── cosine.png ├── demo.png ├── exp.png └── expsquared.png ├── include ├── constants.h ├── george.h ├── kernels.h ├── metrics.h └── solver.h └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.o 4 | *.so 5 | .syntastic_cpp_config 6 | *.png 7 | *.gif 8 | dist 9 | *.egg-info 10 | .syntastic_c_config 11 | build 12 | python/george/__init__.py 13 | .coverage 14 | george/*.cpp 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hodlr"] 2 | path = hodlr 3 | url = https://github.com/dfm/HODLR_Solver.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Much of this script was adapted from astropy/astropy. 2 | 3 | language: python 4 | 5 | env: 6 | global: 7 | - NUMPY_VERSION=1.8 8 | 9 | matrix: 10 | include: 11 | # All the versions of Python. 12 | - python: 2.6 13 | - python: 2.7 14 | - python: 3.3 15 | - python: 3.4 16 | 17 | # Old versions of NumPy. 18 | - python: 2.6 19 | env: NUMPY_VERSION=1.7 20 | - python: 2.7 21 | env: NUMPY_VERSION=1.7 22 | - python: 3.3 23 | env: NUMPY_VERSION=1.7 24 | 25 | - python: 2.6 26 | env: NUMPY_VERSION=1.6 27 | - python: 2.7 28 | env: NUMPY_VERSION=1.6 29 | 30 | before_install: 31 | - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh 32 | - chmod +x miniconda.sh 33 | - ./miniconda.sh -b 34 | - export PATH=/home/travis/miniconda/bin:$PATH 35 | - conda update --yes conda 36 | 37 | install: 38 | - sudo apt-get install -qq libeigen3-dev 39 | 40 | - conda create --yes -n test python=$TRAVIS_PYTHON_VERSION 41 | - source activate test 42 | 43 | - conda install --yes numpy=$NUMPY_VERSION scipy nose pip Cython 44 | 45 | - CXX=g++ python setup.py build_ext --inplace 46 | 47 | script: 48 | - nosetests -v 49 | -------------------------------------------------------------------------------- /HOW_TO_ADD_KERNEL.rst: -------------------------------------------------------------------------------- 1 | Adding a new kernel requires a few steps: 2 | 3 | 1. Implement the kernel in the C++ header file 'include/kernels.h' 4 | 2. Add the class to the Cython definition file 'george/kernels.pxd' 5 | 3. Define a corresponding Python Class in 'george/kernels.py' with a unique kernel_type 6 | 4. add this kernel_type to the function 'parse_kernel(kernel_spec)' in 'george/kernels.pxd' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Daniel Foreman-Mackey and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE include/*.h hodlr/header/*.hpp 2 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | George 2 | ====== 3 | 4 | **Fast and flexible Gaussian Process regression in Python.** 5 | 6 | .. image:: http://img.shields.io/travis/dfm/george/master.svg?style=flat 7 | :target: http://travis-ci.org/dfm/george 8 | .. image:: http://img.shields.io/pypi/dm/george.svg?style=flat 9 | :target: https://pypi.python.org/pypi/george/ 10 | .. image:: http://img.shields.io/pypi/v/george.svg?style=flat 11 | :target: https://pypi.python.org/pypi/george/ 12 | .. image:: http://img.shields.io/badge/license-MIT-blue.svg?style=flat 13 | :target: https://github.com/dfm/george/blob/master/LICENSE 14 | .. image:: http://img.shields.io/badge/DOI-10.5281/zenodo.11989-blue.svg?style=flat 15 | :target: http://dx.doi.org/10.5281/zenodo.11989 16 | 17 | Read the documentation at: `dan.iel.fm/george `_. 18 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/demo.png -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | !*.png 3 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | 3 | default: dirhtml 4 | 5 | # You can set these variables from the command line. 6 | SPHINXOPTS = 7 | SPHINXBUILD = sphinx-build 8 | PAPER = 9 | BUILDDIR = _build 10 | 11 | # User-friendly check for sphinx-build 12 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 13 | $(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/) 14 | endif 15 | 16 | # Internal variables. 17 | PAPEROPT_a4 = -D latex_paper_size=a4 18 | PAPEROPT_letter = -D latex_paper_size=letter 19 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 20 | # the i18n builder cannot share the environment and doctrees with the others 21 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 22 | 23 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 24 | 25 | help: 26 | @echo "Please use \`make ' where is one of" 27 | @echo " html to make standalone HTML files" 28 | @echo " dirhtml to make HTML files named index.html in directories" 29 | @echo " singlehtml to make a single large HTML file" 30 | @echo " pickle to make pickle files" 31 | @echo " json to make JSON files" 32 | @echo " htmlhelp to make HTML files and a HTML help project" 33 | @echo " qthelp to make HTML files and a qthelp project" 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 | 50 | clean: 51 | rm -rf $(BUILDDIR)/* 52 | 53 | html: 54 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 55 | @echo 56 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 57 | 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | singlehtml: 64 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 65 | @echo 66 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 67 | 68 | pickle: 69 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 70 | @echo 71 | @echo "Build finished; now you can process the pickle files." 72 | 73 | json: 74 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 75 | @echo 76 | @echo "Build finished; now you can process the JSON files." 77 | 78 | htmlhelp: 79 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 80 | @echo 81 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 82 | ".hhp project file in $(BUILDDIR)/htmlhelp." 83 | 84 | qthelp: 85 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 86 | @echo 87 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 88 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 89 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/George.qhcp" 90 | @echo "To view the help file:" 91 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/George.qhc" 92 | 93 | devhelp: 94 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 95 | @echo 96 | @echo "Build finished." 97 | @echo "To view the help file:" 98 | @echo "# mkdir -p $$HOME/.local/share/devhelp/George" 99 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/George" 100 | @echo "# devhelp" 101 | 102 | epub: 103 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 104 | @echo 105 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 106 | 107 | latex: 108 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 109 | @echo 110 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 111 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 112 | "(use \`make latexpdf' here to do that automatically)." 113 | 114 | latexpdf: 115 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 116 | @echo "Running LaTeX files through pdflatex..." 117 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 118 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 119 | 120 | latexpdfja: 121 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 122 | @echo "Running LaTeX files through platex and dvipdfmx..." 123 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 124 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 125 | 126 | text: 127 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 128 | @echo 129 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 130 | 131 | man: 132 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 133 | @echo 134 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 135 | 136 | texinfo: 137 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 138 | @echo 139 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 140 | @echo "Run \`make' in that directory to run these through makeinfo" \ 141 | "(use \`make info' here to do that automatically)." 142 | 143 | info: 144 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 145 | @echo "Running Texinfo files through makeinfo..." 146 | make -C $(BUILDDIR)/texinfo info 147 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 148 | 149 | gettext: 150 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 151 | @echo 152 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 153 | 154 | changes: 155 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 156 | @echo 157 | @echo "The overview file is in $(BUILDDIR)/changes." 158 | 159 | linkcheck: 160 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 161 | @echo 162 | @echo "Link check complete; look for any errors in the above output " \ 163 | "or in $(BUILDDIR)/linkcheck/output.txt." 164 | 165 | doctest: 166 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 167 | @echo "Testing of doctests in the sources finished, look at the " \ 168 | "results in $(BUILDDIR)/doctest/output.txt." 169 | 170 | xml: 171 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 172 | @echo 173 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 174 | 175 | pseudoxml: 176 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 177 | @echo 178 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 179 | -------------------------------------------------------------------------------- /docs/_code/.gitignore: -------------------------------------------------------------------------------- 1 | *.pkl 2 | *.png 3 | -------------------------------------------------------------------------------- /docs/_code/benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | import time 7 | import numpy as np 8 | import matplotlib.pyplot as pl 9 | 10 | import george 11 | from george import kernels 12 | 13 | N = 2000 14 | dt = 0.5 / 24. 15 | x = np.linspace(0, N*dt, N) 16 | y = np.sin(x) 17 | 18 | kernel = 1.0 * kernels.Matern32Kernel(2.0) 19 | gp = george.GP(kernel) 20 | 21 | strt = time.time() 22 | gp.compute(x, 0.1) 23 | ll = gp.lnlikelihood(y) 24 | print(time.time() - strt) 25 | print(ll) 26 | 27 | gp = george.HODLRGP(kernel) 28 | strt = time.time() 29 | gp.compute(x, 0.1) 30 | ll = gp.lnlikelihood(y) 31 | print(time.time() - strt) 32 | print(ll) 33 | -------------------------------------------------------------------------------- /docs/_code/hyper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | A reproduction of Figure 5.6 from Rasmussen & Williams (2006). 5 | http://www.gaussianprocess.org/gpml/ 6 | 7 | """ 8 | 9 | from __future__ import division, print_function 10 | 11 | import numpy as np 12 | import scipy.optimize as op 13 | import statsmodels.api as sm 14 | import matplotlib.pyplot as pl 15 | 16 | import george 17 | from george import kernels 18 | 19 | # Load the dataset. 20 | data = sm.datasets.get_rdataset("co2").data 21 | t = np.array(data.time) 22 | y = np.array(data.co2) 23 | base = np.mean(y) 24 | 25 | # Plot the data. 26 | fig = pl.figure(figsize=(6, 3.5)) 27 | ax = fig.add_subplot(111) 28 | ax.plot(t, y, ".k", ms=2) 29 | ax.set_xlim(min(t), 1999) 30 | ax.set_ylim(min(y), 369) 31 | ax.set_xlabel("year") 32 | ax.set_ylabel("CO$_2$ in ppm") 33 | fig.subplots_adjust(left=0.15, bottom=0.2, right=0.99, top=0.95) 34 | fig.savefig("../_static/hyper/data.png", dpi=150) 35 | fig.savefig("hyper-data.pdf") 36 | 37 | # Initialize the kernel. 38 | k1 = 66.0**2 * kernels.ExpSquaredKernel(67.0**2) 39 | k2 = 2.4**2 * kernels.ExpSquaredKernel(90**2) \ 40 | * kernels.ExpSine2Kernel(2.0 / 1.3**2, 1.0) 41 | k3 = 0.66**2 * kernels.RationalQuadraticKernel(0.78, 1.2**2) 42 | k4 = 0.18**2 * kernels.ExpSquaredKernel(1.6**2) + kernels.WhiteKernel(0.19) 43 | kernel = k1 + k2 + k3 + k4 44 | 45 | # Set up the Gaussian process and maximize the marginalized likelihood. 46 | gp = george.GP(kernel, mean=np.mean(y)) 47 | 48 | # Define the objective function (negative log-likelihood in this case). 49 | def nll(p): 50 | # Update the kernel parameters and compute the likelihood. 51 | gp.kernel[:] = p 52 | ll = gp.lnlikelihood(y, quiet=True) 53 | 54 | # The scipy optimizer doesn't play well with infinities. 55 | return -ll if np.isfinite(ll) else 1e25 56 | 57 | # And the gradient of the objective function. 58 | def grad_nll(p): 59 | # Update the kernel parameters and compute the likelihood. 60 | gp.kernel[:] = p 61 | return -gp.grad_lnlikelihood(y, quiet=True) 62 | 63 | # You need to compute the GP once before starting the optimization. 64 | gp.compute(t) 65 | 66 | # Print the initial ln-likelihood. 67 | print(gp.lnlikelihood(y)) 68 | 69 | # Run the optimization routine. 70 | p0 = gp.kernel.vector 71 | results = op.minimize(nll, p0, jac=grad_nll, method="L-BFGS-B") 72 | 73 | # Update the kernel and print the final log-likelihood. 74 | gp.kernel[:] = results.x 75 | print(results) 76 | p = gp.kernel.pars 77 | print("Final parameters: \n", p) 78 | print("Final marginalized ln-likelihood: {0}".format(gp.lnlikelihood(y))) 79 | 80 | # Build the results table. 81 | rw = [66**2, 67**2, 2.4**2, 90**2, 2.0 / 1.3**2, 1.0, 0.66**2, 0.78, 1.2**2, 82 | 0.18**2, 1.6**2, 0.19] 83 | labels = map(r":math:`\theta_{{{0}}}`".format, range(1, len(p)+1)) 84 | l = max(map(len, labels)) 85 | rf = "| {{0[0]:{0}s}} | {{0[1]:10.2f}} | {{0[2]:10.2f}} |".format(l).format 86 | header = "| {{0:{0}s}} | {{1:10s}} | {{2:10s}} |".format(l).format( 87 | "", "result", "R&W") 88 | sep = "+" + "+".join(["-" * (l+2), "-" * 12, "-" * 12]) + "+" 89 | rows = ("\n"+sep+"\n").join(map(rf, zip(labels, p, rw))) 90 | table = "\n".join([sep, header, sep.replace("-", "="), rows, sep]) 91 | with open("../_static/hyper/results.txt", "w") as f: 92 | f.write(table) 93 | 94 | # Compute the prediction into the future. 95 | x = np.linspace(max(t), 2025, 2000) 96 | mu, cov = gp.predict(y, x) 97 | std = np.sqrt(np.diag(cov)) 98 | 99 | # Plot the prediction. 100 | ax.fill_between(x, mu+std, mu-std, color="k", alpha=0.4) 101 | ax.set_xlim(min(t), 2025.0) 102 | ax.set_ylim(min(y), 400.0) 103 | fig.savefig("../_static/hyper/figure.png", dpi=150) 104 | fig.savefig("hyper-figure.pdf", dpi=150) 105 | -------------------------------------------------------------------------------- /docs/_code/hyper_sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | A reproduction of Figure 5.6 from Rasmussen & Williams (2006). 5 | http://www.gaussianprocess.org/gpml/ 6 | 7 | """ 8 | 9 | from __future__ import division, print_function 10 | 11 | import sys 12 | import time 13 | import emcee 14 | import numpy as np 15 | import cPickle as pickle 16 | import statsmodels.api as sm 17 | import matplotlib.pyplot as pl 18 | from multiprocessing import Pool 19 | 20 | import george 21 | from george import kernels 22 | 23 | # Load the dataset. 24 | data = sm.datasets.get_rdataset("co2").data 25 | t = np.array(data.time) 26 | y = np.array(data.co2) 27 | 28 | # Initialize the kernel. 29 | k1 = 66.0**2 * kernels.ExpSquaredKernel(67.0**2) 30 | k2 = 2.4**2 * kernels.ExpSquaredKernel(90**2) \ 31 | * kernels.ExpSine2Kernel(2.0 / 1.3**2, 1.0) 32 | k3 = 0.66**2 * kernels.RationalQuadraticKernel(0.78, 1.2**2) 33 | k4 = 0.18**2 * kernels.ExpSquaredKernel(1.6**2) + kernels.WhiteKernel(0.19) 34 | kernel = k1 + k2 + k3 + k4 35 | 36 | # Set up the Gaussian process. 37 | gp = george.GP(kernel, mean=np.mean(y)) 38 | 39 | s = time.time() 40 | gp.compute(t) 41 | gp.lnlikelihood(y) 42 | print(time.time() - s) 43 | 44 | 45 | # Define the probabilistic model. 46 | def lnprob(p): 47 | # Trivial prior: uniform in the log. 48 | if np.any((-10 > p) + (p > 10)): 49 | return -np.inf 50 | lnprior = 0.0 51 | 52 | # Update the kernel and compute the lnlikelihood. 53 | kernel.pars = np.exp(p) 54 | return lnprior + gp.lnlikelihood(y, quiet=True) 55 | 56 | 57 | # Set up the sampler and initialize the walkers. 58 | nwalkers, ndim = 36, len(kernel) 59 | 60 | if len(sys.argv) > 1: 61 | chain, _, _ = pickle.load(open(sys.argv[1], "rb")) 62 | p0 = chain[:, -1, :] 63 | else: 64 | p0 = [np.log(kernel.pars) + 1e-4 * np.random.randn(ndim) 65 | for i in range(nwalkers)] 66 | 67 | sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob, pool=Pool()) 68 | print("Sampling...") 69 | s = time.time() 70 | for i in range(20): 71 | print(i+1) 72 | p0, _, _ = sampler.run_mcmc(p0, 200) 73 | pickle.dump((sampler.chain, sampler.lnprobability, gp), 74 | open("hyper-results-{0}.pkl".format(i), "wb"), -1) 75 | 76 | print("Finished {0}".format(time.time() - s)) 77 | -------------------------------------------------------------------------------- /docs/_code/hyper_sample_results.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | A reproduction of Figure 5.6 from Rasmussen & Williams (2006). 5 | http://www.gaussianprocess.org/gpml/ 6 | 7 | """ 8 | 9 | from __future__ import division, print_function 10 | 11 | import sys 12 | import numpy as np 13 | import cPickle as pickle 14 | import statsmodels.api as sm 15 | import matplotlib.pyplot as pl 16 | 17 | # Load the dataset. 18 | data = sm.datasets.get_rdataset("co2").data 19 | t = np.array(data.time) 20 | y = np.array(data.co2) 21 | 22 | # Load the results. 23 | chain, _, gp = pickle.load(open(sys.argv[1], "rb")) 24 | 25 | # Set up the figure. 26 | fig = pl.figure(figsize=(6, 3.5)) 27 | ax = fig.add_subplot(111) 28 | ax.plot(t, y, ".k", ms=2) 29 | ax.set_xlabel("year") 30 | ax.set_ylabel("CO$_2$ in ppm") 31 | fig.subplots_adjust(left=0.15, bottom=0.2, right=0.99, top=0.95) 32 | 33 | # Plot the predictions. 34 | x = np.linspace(max(t), 2025, 250) 35 | for i in range(50): 36 | # Choose a random walker and step. 37 | w = np.random.randint(chain.shape[0]) 38 | n = np.random.randint(2000, chain.shape[1]) 39 | gp.kernel.pars = np.exp(chain[w, n]) 40 | 41 | # Plot a single sample. 42 | ax.plot(x, gp.sample_conditional(y, x), "k", alpha=0.3) 43 | 44 | ax.set_xlim(min(t), 2025.0) 45 | ax.set_ylim(min(y), 420.0) 46 | fig.savefig("../_static/hyper/mcmc.png", dpi=150) 47 | -------------------------------------------------------------------------------- /docs/_code/model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | import emcee 7 | import triangle 8 | import numpy as np 9 | import matplotlib.pyplot as pl 10 | 11 | import george 12 | from george import kernels 13 | 14 | 15 | def model(params, t): 16 | amp, loc, sig2 = params 17 | return amp * np.exp(-0.5 * (t - loc) ** 2 / sig2) 18 | 19 | 20 | def lnprior_base(p): 21 | amp, loc, sig2 = p 22 | if not -10 < amp < 10: 23 | return -np.inf 24 | if not -5 < loc < 5: 25 | return -np.inf 26 | if not 0 < sig2 < 3.0: 27 | return -np.inf 28 | return 0.0 29 | 30 | 31 | def lnlike_ind(p, t, y, invar): 32 | m = model(p[2:], t) + p[0] * t + p[1] 33 | return -0.5 * np.sum((y - m) ** 2 * invar) 34 | 35 | 36 | def lnprior_ind(p): 37 | m, b = p[:2] 38 | if not -10 < m < 10: 39 | return -np.inf 40 | if not -10 < b < 10: 41 | return -np.inf 42 | return lnprior_base(p[2:]) 43 | 44 | 45 | def lnprob_ind(p, t, y, invar): 46 | lp = lnprior_ind(p) 47 | if not np.isfinite(lp): 48 | return -np.inf 49 | return lp + lnlike_ind(p, t, y, invar) 50 | 51 | 52 | def fit_ind(initial, data, nwalkers=32): 53 | ndim = len(initial) 54 | p0 = [np.array(initial) + 1e-8 * np.random.randn(ndim) 55 | for i in xrange(nwalkers)] 56 | sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob_ind, args=data) 57 | 58 | print("Running burn-in") 59 | p0, _, _ = sampler.run_mcmc(p0, 500) 60 | sampler.reset() 61 | 62 | print("Running production") 63 | p0, _, _ = sampler.run_mcmc(p0, 1000) 64 | 65 | return sampler 66 | 67 | 68 | def lnlike_gp(p, t, y, yerr): 69 | a, tau = np.exp(p[:2]) 70 | gp = george.GP(a * kernels.Matern32Kernel(tau)) 71 | gp.compute(t, yerr) 72 | return gp.lnlikelihood(y - model(p[2:], t)) 73 | 74 | 75 | def lnprior_gp(p): 76 | lna, lntau = p[:2] 77 | if not -5 < lna < 5: 78 | return -np.inf 79 | if not -5 < lntau < 5: 80 | return -np.inf 81 | return lnprior_base(p[2:]) 82 | 83 | 84 | def lnprob_gp(p, t, y, yerr): 85 | lp = lnprior_gp(p) 86 | if not np.isfinite(lp): 87 | return -np.inf 88 | return lp + lnlike_gp(p, t, y, yerr) 89 | 90 | 91 | def fit_gp(initial, data, nwalkers=32): 92 | ndim = len(initial) 93 | p0 = [np.array(initial) + 1e-8 * np.random.randn(ndim) 94 | for i in xrange(nwalkers)] 95 | sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob_gp, args=data) 96 | 97 | print("Running burn-in") 98 | p0, lnp, _ = sampler.run_mcmc(p0, 500) 99 | sampler.reset() 100 | 101 | print("Running second burn-in") 102 | p = p0[np.argmax(lnp)] 103 | p0 = [p + 1e-8 * np.random.randn(ndim) for i in xrange(nwalkers)] 104 | p0, _, _ = sampler.run_mcmc(p0, 500) 105 | sampler.reset() 106 | 107 | print("Running production") 108 | p0, _, _ = sampler.run_mcmc(p0, 1000) 109 | 110 | return sampler 111 | 112 | 113 | def generate_data(params, N, rng=(-5, 5)): 114 | gp = george.GP(0.1 * kernels.ExpSquaredKernel(3.3)) 115 | t = rng[0] + np.diff(rng) * np.sort(np.random.rand(N)) 116 | y = gp.sample(t) 117 | y += model(params, t) 118 | yerr = 0.05 + 0.05 * np.random.rand(N) 119 | y += yerr * np.random.randn(N) 120 | return t, y, yerr 121 | 122 | 123 | if __name__ == "__main__": 124 | np.random.seed(1234) 125 | truth = [-1.0, 0.1, 0.4] 126 | t, y, yerr = generate_data(truth, 50) 127 | pl.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0) 128 | pl.ylabel(r"$y$") 129 | pl.xlabel(r"$t$") 130 | pl.xlim(-5, 5) 131 | pl.title("simulated data") 132 | pl.savefig("../_static/model/data.png", dpi=150) 133 | 134 | # Fit assuming independent. 135 | print("Fitting independent") 136 | data = (t, y, 1.0 / yerr ** 2) 137 | truth_ind = [0.0, 0.0] + truth 138 | sampler = fit_ind(truth_ind, data) 139 | 140 | # Plot the samples in data space. 141 | print("Making plots") 142 | samples = sampler.flatchain 143 | x = np.linspace(-5, 5, 500) 144 | for s in samples[np.random.randint(len(samples), size=24)]: 145 | pl.plot(x, model(s[2:], x)+s[0]*x+s[1], color="#4682b4", alpha=0.3) 146 | pl.title("results assuming uncorrelated noise") 147 | pl.savefig("../_static/model/ind-results.png", dpi=150) 148 | 149 | # Make the corner plot. 150 | labels = [r"$\alpha$", r"$\ell$", r"$\sigma^2$"] 151 | fig = triangle.corner(samples[:, 2:], truths=truth, labels=labels) 152 | fig.savefig("../_static/model/ind-corner.png", dpi=150) 153 | 154 | # Fit assuming GP. 155 | print("Fitting GP") 156 | data = (t, y, yerr) 157 | truth_gp = [0.0, 0.0] + truth 158 | sampler = fit_gp(truth_gp, data) 159 | 160 | # Plot the samples in data space. 161 | print("Making plots") 162 | samples = sampler.flatchain 163 | x = np.linspace(-5, 5, 500) 164 | pl.figure() 165 | pl.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0) 166 | for s in samples[np.random.randint(len(samples), size=24)]: 167 | gp = george.GP(np.exp(s[0]) * kernels.Matern32Kernel(np.exp(s[1]))) 168 | gp.compute(t, yerr) 169 | m = gp.sample_conditional(y - model(s[2:], t), x) + model(s[2:], x) 170 | pl.plot(x, m, color="#4682b4", alpha=0.3) 171 | pl.ylabel(r"$y$") 172 | pl.xlabel(r"$t$") 173 | pl.xlim(-5, 5) 174 | pl.title("results with Gaussian process noise model") 175 | pl.savefig("../_static/model/gp-results.png", dpi=150) 176 | 177 | # Make the corner plot. 178 | fig = triangle.corner(samples[:, 2:], truths=truth, labels=labels) 179 | fig.savefig("../_static/model/gp-corner.png", dpi=150) 180 | -------------------------------------------------------------------------------- /docs/_code/quickstart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import numpy as np 5 | import matplotlib.pyplot as pl 6 | 7 | import george 8 | from george.kernels import ExpSquaredKernel 9 | 10 | np.random.seed(1234) 11 | 12 | # Generate some fake noisy data. 13 | x = 10 * np.sort(np.random.rand(10)) 14 | yerr = 0.2 * np.ones_like(x) 15 | y = np.sin(x) + yerr * np.random.randn(len(x)) 16 | 17 | # Set up the Gaussian process. 18 | kernel = ExpSquaredKernel(1.0) 19 | gp = george.GP(kernel) 20 | 21 | # Pre-compute the factorization of the matrix. 22 | gp.compute(x, yerr) 23 | 24 | # Compute the log likelihood. 25 | print(gp.lnlikelihood(y)) 26 | 27 | # Compute the predictive conditional distribution. 28 | t = np.linspace(0, 10, 500) 29 | mu, cov = gp.predict(y, t) 30 | std = np.sqrt(np.diag(cov)) 31 | 32 | pl.fill_between(t, mu+std, mu-std, color="k", alpha=0.1) 33 | pl.plot(t, mu+std, color="k", alpha=1, lw=0.25) 34 | pl.plot(t, mu-std, color="k", alpha=1, lw=0.25) 35 | pl.plot(t, mu, color="k", alpha=1, lw=0.5) 36 | pl.errorbar(x, y, yerr=yerr, fmt=".k", capsize=0) 37 | pl.xlabel("$x$") 38 | pl.ylabel("$y$") 39 | pl.savefig("../_static/quickstart/conditional.png", dpi=150) 40 | -------------------------------------------------------------------------------- /docs/_static/hyper/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/hyper/data.png -------------------------------------------------------------------------------- /docs/_static/hyper/figure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/hyper/figure.png -------------------------------------------------------------------------------- /docs/_static/hyper/mcmc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/hyper/mcmc.png -------------------------------------------------------------------------------- /docs/_static/hyper/results.txt: -------------------------------------------------------------------------------- 1 | +---------------------+------------+------------+ 2 | | | result | R&W | 3 | +=====================+============+============+ 4 | | :math:`\theta_{1}` | 1549.86 | 4356.00 | 5 | +---------------------+------------+------------+ 6 | | :math:`\theta_{2}` | 1871.10 | 4489.00 | 7 | +---------------------+------------+------------+ 8 | | :math:`\theta_{3}` | 12.37 | 5.76 | 9 | +---------------------+------------+------------+ 10 | | :math:`\theta_{4}` | 45860.04 | 8100.00 | 11 | +---------------------+------------+------------+ 12 | | :math:`\theta_{5}` | 0.93 | 1.18 | 13 | +---------------------+------------+------------+ 14 | | :math:`\theta_{6}` | 1.00 | 1.00 | 15 | +---------------------+------------+------------+ 16 | | :math:`\theta_{7}` | 0.35 | 0.44 | 17 | +---------------------+------------+------------+ 18 | | :math:`\theta_{8}` | 0.05 | 0.78 | 19 | +---------------------+------------+------------+ 20 | | :math:`\theta_{9}` | 0.07 | 1.44 | 21 | +---------------------+------------+------------+ 22 | | :math:`\theta_{10}` | 0.15 | 0.03 | 23 | +---------------------+------------+------------+ 24 | | :math:`\theta_{11}` | 1.12 | 2.56 | 25 | +---------------------+------------+------------+ 26 | | :math:`\theta_{12}` | 0.03 | 0.19 | 27 | +---------------------+------------+------------+ -------------------------------------------------------------------------------- /docs/_static/js/analytics.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 5 | 6 | ga('create', 'UA-22909046-1', 'auto'); 7 | ga('require', 'displayfeatures'); 8 | ga('send', 'pageview'); 9 | -------------------------------------------------------------------------------- /docs/_static/model/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/model/data.png -------------------------------------------------------------------------------- /docs/_static/model/gp-corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/model/gp-corner.png -------------------------------------------------------------------------------- /docs/_static/model/gp-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/model/gp-results.png -------------------------------------------------------------------------------- /docs/_static/model/ind-corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/model/ind-corner.png -------------------------------------------------------------------------------- /docs/_static/model/ind-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/model/ind-results.png -------------------------------------------------------------------------------- /docs/_static/quickstart/conditional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/docs/_static/quickstart/conditional.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | 6 | d = os.path.dirname 7 | sys.path.insert(0, d(d(os.path.abspath(__file__)))) 8 | import george 9 | 10 | extensions = [ 11 | "sphinx.ext.autodoc", 12 | "sphinx.ext.mathjax", 13 | ] 14 | templates_path = ["_templates"] 15 | source_suffix = ".rst" 16 | master_doc = "index" 17 | 18 | # # FIXME 19 | # mathjax_path = "MathJax/MathJax.js" 20 | 21 | # General information about the project. 22 | project = u"George" 23 | copyright = u"2013-2014 Dan Foreman-Mackey" 24 | 25 | version = george.__version__ 26 | release = george.__version__ 27 | 28 | exclude_patterns = ["_build"] 29 | pygments_style = "sphinx" 30 | 31 | # Readthedocs. 32 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 33 | if not on_rtd: 34 | import sphinx_rtd_theme 35 | html_theme = "sphinx_rtd_theme" 36 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 37 | 38 | htmp_theme_options = dict( 39 | analytics_id="analytics_id", 40 | ) 41 | html_context = dict( 42 | display_github=True, 43 | github_user="dfm", 44 | github_repo="george", 45 | github_version="master", 46 | conf_py_path="/docs/", 47 | script_files=[ 48 | "_static/jquery.js", 49 | "_static/underscore.js", 50 | "_static/doctools.js", 51 | "//cdn.mathjax.org/mathjax/latest/MathJax.js" 52 | "?config=TeX-AMS-MML_HTMLorMML", 53 | "_static/js/analytics.js", 54 | ], 55 | ) 56 | html_static_path = ["_static"] 57 | html_show_sourcelink = False 58 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | George 2 | ====== 3 | 4 | George is a fast and flexible Python library for Gaussian Process Regression. 5 | A full introduction to the theory of Gaussian Processes is beyond the scope of 6 | this documentation but the best resource is available for free online: 7 | `Rasmussen & Williams (2006) `_. 8 | 9 | George is being actively developed in `a public repository on GitHub 10 | `_ so if you have any trouble, `open an issue 11 | `_ there. 12 | 13 | 14 | User Guide 15 | ---------- 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | user/quickstart 21 | user/model 22 | user/hyper 23 | user/gp 24 | user/kernels 25 | user/solvers 26 | 27 | 28 | License & Attribution 29 | --------------------- 30 | 31 | Copyright 2014 Daniel Foreman-Mackey and contributors. 32 | 33 | George is being developed by `Dan Foreman-Mackey `_ in a 34 | `public GitHub repository `_. 35 | The source code is made available under the terms of the MIT license. 36 | 37 | If you make use of this code, please cite `the paper which is currently on the 38 | ArXiv `_: 39 | 40 | .. code-block:: tex 41 | 42 | @article{hodlr, 43 | author = {{Ambikasaran}, S. and {Foreman-Mackey}, D. and 44 | {Greengard}, L. and {Hogg}, D.~W. and {O'Neil}, M.}, 45 | title = "{Fast Direct Methods for Gaussian Processes and the Analysis 46 | of NASA Kepler Mission Data}", 47 | year = 2014, 48 | month = mar, 49 | url = http://arxiv.org/abs/1403.6015 50 | } 51 | -------------------------------------------------------------------------------- /docs/user/gp.rst: -------------------------------------------------------------------------------- 1 | .. module:: george 2 | 3 | .. _gp: 4 | 5 | The GP object 6 | ============= 7 | 8 | The core element of George is the :class:`GP` object. 9 | All of the available methods and properties are documented here: 10 | 11 | .. autoclass:: george.GP 12 | :inherited-members: 13 | -------------------------------------------------------------------------------- /docs/user/hyper.rst: -------------------------------------------------------------------------------- 1 | .. module:: george 2 | 3 | .. _hyper: 4 | 5 | Tutorial: setting the hyperparameters 6 | ===================================== 7 | 8 | In this demo, we'll reproduce the analysis for Figure 5.6 in `Chapter 5 of 9 | Rasmussen & Williams (R&W) 10 | `_. 11 | The data are measurements of the atmospheric CO2 concentration made at Mauna 12 | Loa, Hawaii (Keeling & Whorf 2004). 13 | The dataset is said to be available online but I couldn't seem to download it 14 | from the original source. 15 | Luckily the `statsmodels `_ package 16 | `includes a copy 17 | `_ that 18 | we can load as follows: 19 | 20 | .. code-block:: python 21 | 22 | import numpy as np 23 | import statsmodels.api as sm 24 | 25 | data = sm.datasets.get_rdataset("co2").data 26 | t = np.array(data.time) 27 | y = np.array(data.co2) 28 | 29 | These data are plotted in the figure below: 30 | 31 | .. image:: ../_static/hyper/data.png 32 | 33 | In this figure, you can see that there is periodic (or quasi-periodic) signal 34 | with a year-long period superimposed on a long term trend. 35 | We will follow R&W and model these effects non-parametrically using a 36 | complicated covariance function. 37 | The covariance function that we'll use is: 38 | 39 | .. math:: 40 | 41 | k(r) = k_1(r) + k_2(r) + k_3(r) + k_4(r) 42 | 43 | where 44 | 45 | .. math:: 46 | 47 | \begin{eqnarray} 48 | k_1(r) &=& \theta_1^2 \, \exp \left(-\frac{r^2}{2\,\theta_2} \right) \\ 49 | k_2(r) &=& \theta_3^2 \, \exp \left(-\frac{r^2}{2\,\theta_4} 50 | -\theta_5\,\sin^2\left( 51 | \frac{\pi\,r}{\theta_6}\right) 52 | \right) \\ 53 | k_3(r) &=& \theta_7^2 \, \left [ 1 + \frac{r^2}{2\,\theta_8\,\theta_9} 54 | \right ]^{-\theta_8} \\ 55 | k_4(r) &=& \theta_{10}^2 \, \exp \left(-\frac{r^2}{2\,\theta_{11}} \right) 56 | + \theta_{12}^2\,\delta_{ij} 57 | \end{eqnarray} 58 | 59 | We can implement this kernel in George as follows (we'll use the R&W results 60 | as the hyperparameters for now): 61 | 62 | .. code-block:: python 63 | 64 | from george import kernels 65 | 66 | k1 = 66.0**2 * kernels.ExpSquaredKernel(67.0**2) 67 | k2 = 2.4**2 * kernels.ExpSquaredKernel(90**2) * kernels.ExpSine2Kernel(2.0 / 1.3**2, 1.0) 68 | k3 = 0.66**2 * kernels.RationalQuadraticKernel(0.78, 1.2**2) 69 | k4 = 0.18**2 * kernels.ExpSquaredKernel(1.6**2) + kernels.WhiteKernel(0.19) 70 | kernel = k1 + k2 + k3 + k4 71 | 72 | 73 | Optimization 74 | ------------ 75 | 76 | If we want to find the "best-fit" hyperparameters, we should *optimize* an 77 | objective function. 78 | The two standard functions (as described in Chapter 5 of R&W) are the 79 | marginalized ln-likelihood and the cross validation likelihood. 80 | George implements the former in the :func:`GP.lnlikelihood` function and the 81 | gradient with respect to the hyperparameters in the 82 | :func:`GP.grad_lnlikelihood` function: 83 | 84 | .. code-block:: python 85 | 86 | import george 87 | gp = george.GP(kernel, mean=np.mean(y)) 88 | gp.compute(t) 89 | print(gp.lnlikelihood(y)) 90 | print(gp.grad_lnlikelihood(y)) 91 | 92 | The gradient is taken with respect to the ``vector`` property of the kernel so 93 | this is what you want to fit for. 94 | For most kernels the vector is actually the *logarithm* of the hyperparameters 95 | (see the discussion in :ref:`implementation`). 96 | We'll use a gradient based optimization routine from SciPy to fit this model 97 | as follows: 98 | 99 | .. code-block:: python 100 | 101 | import scipy.optimize as op 102 | 103 | # Define the objective function (negative log-likelihood in this case). 104 | def nll(p): 105 | # Update the kernel parameters and compute the likelihood. 106 | gp.kernel[:] = p 107 | ll = gp.lnlikelihood(y, quiet=True) 108 | 109 | # The scipy optimizer doesn't play well with infinities. 110 | return -ll if np.isfinite(ll) else 1e25 111 | 112 | # And the gradient of the objective function. 113 | def grad_nll(p): 114 | # Update the kernel parameters and compute the likelihood. 115 | gp.kernel[:] = p 116 | return -gp.grad_lnlikelihood(y, quiet=True) 117 | 118 | # You need to compute the GP once before starting the optimization. 119 | gp.compute(t) 120 | 121 | # Print the initial ln-likelihood. 122 | print(gp.lnlikelihood(y)) 123 | 124 | # Run the optimization routine. 125 | p0 = gp.kernel.vector 126 | results = op.minimize(nll, p0, jac=grad_nll, method="L-BFGS-B") 127 | 128 | # Update the kernel and print the final log-likelihood. 129 | gp.kernel[:] = results.x 130 | print(gp.lnlikelihood(y)) 131 | 132 | .. warning:: An optimization code something like this should work on most 133 | problems but the results can be very sensitive to your choice of 134 | initialization and algorithm. If the results are nonsense, try choosing a 135 | better initial guess or try a different value of the ``method`` parameter 136 | in ``op.minimize``. 137 | 138 | After running this optimization, we find a final ln-likelihood of -82.46 139 | (slightly better than the result in R&W) and the following parameter values: 140 | 141 | .. include:: ../_static/hyper/results.txt 142 | 143 | We can plot our prediction of the CO2 concentration into the future using our 144 | optimized Gaussian process model by running: 145 | 146 | .. code-block:: python 147 | 148 | x = np.linspace(max(t), 2025, 2000) 149 | mu, cov = gp.predict(y, x) 150 | std = np.sqrt(np.diag(cov)) 151 | 152 | and this gives a result just like Figure 5.6 from R&W: 153 | 154 | .. image:: ../_static/hyper/figure.png 155 | 156 | 157 | Sampling & Marginalization 158 | -------------------------- 159 | 160 | The prediction made in the previous section take into account uncertainties 161 | due to the fact that a Gaussian process is stochastic but it doesn't take into 162 | account any uncertainties in the *values of the hyperparameters*. 163 | This won't matter if the hyperparameters are very well constrained by the data 164 | but in this case, many of the parameters are actually poorly constrained. 165 | To take this effect into account, we can apply prior probability functions to 166 | the hyperparameters and marginalize using `Markov chain Monte Carlo (MCMC) 167 | `_. 168 | To do this, we'll use the `emcee `_ package. 169 | 170 | First, we define the probabilistic model: 171 | 172 | .. code-block:: python 173 | 174 | def lnprob(p): 175 | # Trivial improper prior: uniform in the log. 176 | if np.any((-10 > p) + (p > 10)): 177 | return -np.inf 178 | lnprior = 0.0 179 | 180 | # Update the kernel and compute the lnlikelihood. 181 | kernel.pars = np.exp(p) 182 | return lnprior + gp.lnlikelihood(y, quiet=True) 183 | 184 | In this function, we've applied a prior on every parameter that is uniform in 185 | the natural log between -10 and 10. 186 | The ``quiet`` argument in the call to :func:`GP.lnlikelihood` means that that 187 | function will return ``-numpy.inf`` if the kernel is invalid or if there are 188 | any linear algebra errors (otherwise it would raise an exception). 189 | 190 | Then, we run the sampler (this will probably take a while to run if you want 191 | to repeat this analysis): 192 | 193 | .. code-block:: python 194 | 195 | import emcee 196 | 197 | # You need to compute the GP once before starting. Then the sample list 198 | # will be saved. 199 | gp.compute(t) 200 | 201 | # Set up the sampler. 202 | nwalkers, ndim = 36, len(kernel) 203 | sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob) 204 | 205 | # Initialize the walkers. 206 | p0 = [np.log(kernel.pars) + 1e-4 * np.random.randn(ndim) 207 | for i in range(nwalkers)] 208 | 209 | print("Running burn-in") 210 | p0, _, _ = sampler.run_mcmc(p0, 2000) 211 | 212 | print("Running production chain") 213 | sampler.run_mcmc(p0, 2000) 214 | 215 | After this run, you can plot 50 samples from the marginalized predictive 216 | probability distribution: 217 | 218 | .. code-block:: python 219 | 220 | import matplotlib.pyplot as pl 221 | 222 | x = np.linspace(max(t), 2025, 250) 223 | for i in range(50): 224 | # Choose a random walker and step. 225 | w = np.random.randint(chain.shape[0]) 226 | n = np.random.randint(2000, chain.shape[1]) 227 | gp.kernel.pars = np.exp(chain[w, n]) 228 | 229 | # Plot a single sample. 230 | pl.plot(x, gp.sample_conditional(y, x), "k", alpha=0.3) 231 | 232 | This should give you a figure similar to this one: 233 | 234 | .. image:: ../_static/hyper/mcmc.png 235 | 236 | Comparing this to the same figure in the previous section, you'll notice that 237 | the error bars on the prediction are now substantially larger than before. 238 | This is because we are now considering all the predictions that are consistent 239 | with the data, not just the "best" prediction. 240 | In general, even though it requires much more computation, it is more 241 | conservative (and honest) to take all these sources of uncertainty into 242 | account. 243 | -------------------------------------------------------------------------------- /docs/user/kernels.rst: -------------------------------------------------------------------------------- 1 | .. module:: george.kernels 2 | 3 | .. _kernels: 4 | 5 | Kernels 6 | ======= 7 | 8 | George comes equipped with a suite of standard covariance functions or 9 | kernels that can be combined to build more complex models. 10 | The standard kernels fall into the following categories: 11 | 12 | 1. :ref:`basic-kernels` — trivial (constant or parameterless) functions, 13 | 2. :ref:`radial-kernels` — functions that depend only on the radial distance 14 | between points in some user-defined metric, and 15 | 3. :ref:`periodic-kernels` — exactly period functions that, when combined with 16 | a radial kernel, can model quasi-periodic signals. 17 | 18 | :ref:`combining-kernels` describes how to combine kernels to build more 19 | sophisticated models and :ref:`new-kernels` explains how you would go about 20 | incorporating a custom kernel. 21 | 22 | **Note:** every kernel takes an optional ``ndim`` keyword that must be set to 23 | the number of input dimensions for your problem. 24 | 25 | .. _implementation: 26 | 27 | Implementation Details 28 | ---------------------- 29 | 30 | It's worth understanding how these kernels are implemented. 31 | Most of the hard work is done at a low level (in C++) and the Python is only a 32 | thin wrapper to this functionality. 33 | This makes the code fast and consistent across interfaces but it also means 34 | that it isn't currently possible to implement new kernel functions efficiently 35 | without recompiling the code. 36 | Almost every kernel has hyperparameters that you can set to control its 37 | behavior and these can be accessed via the ``pars`` property. 38 | The values in this array are in the same order as you specified them when 39 | initializing the kernel and, in the case of composite kernels (see 40 | :ref:`combining-kernels`) the order goes from left to right. 41 | For example, 42 | 43 | .. code-block:: python 44 | 45 | from george import kernels 46 | 47 | k = 2.0 * kernels.Matern32Kernel(5.0) 48 | print(k.pars) 49 | # array([ 2., 5.]) 50 | 51 | 52 | In general, kernel functions have some—possibly different—natural 53 | parameterization that can be useful for parameter inference. 54 | This can be accessed via the ``vector`` property and for most kernels, this 55 | will be—unless otherwise specified—the natural logarithm of the ``pars`` 56 | array. 57 | So, for our previous example, 58 | 59 | .. code-block:: python 60 | 61 | k = 2.0 * kernels.Matern32Kernel(5.0) 62 | print(k.vector) 63 | # array([ 0.69314718, 1.60943791]) 64 | 65 | George is smart about when it recomputes the kernel and it will only do this 66 | if you change the parameters. 67 | Therefore, the best way to make changes is by *subscripting* the kernel. 68 | It's worth noting that subscripting changes the ``vector`` array (not 69 | ``pars``) so following up our previous example, we can do something like 70 | 71 | .. code-block:: python 72 | 73 | import numpy as np 74 | k = 2.0 * kernels.Matern32Kernel(5.0) 75 | 76 | k[0] = np.log(4.0) 77 | print(k.pars) 78 | # array([ 4., 5.]) 79 | 80 | k[:] = np.log([6.0, 10.0]) 81 | print(k.pars) 82 | # array([ 6., 10.]) 83 | 84 | .. note:: The gradient of each kernel is given with respect to ``vector`` not 85 | ``pars``. This means that in most cases the gradient taken in terms of the 86 | *logarithm* of the hyperparameters. 87 | 88 | 89 | 90 | .. _basic-kernels: 91 | 92 | Basic Kernels 93 | ------------- 94 | 95 | .. autoclass:: george.kernels.Kernel 96 | :special-members: __call__ 97 | :members: 98 | 99 | .. autoclass:: george.kernels.ConstantKernel 100 | .. autoclass:: george.kernels.WhiteKernel 101 | .. autoclass:: george.kernels.DotProductKernel 102 | 103 | 104 | .. _radial-kernels: 105 | 106 | Radial Kernels 107 | -------------- 108 | 109 | .. autoclass:: george.kernels.RadialKernel 110 | .. autoclass:: george.kernels.ExpKernel 111 | .. autoclass:: george.kernels.ExpSquaredKernel 112 | .. autoclass:: george.kernels.Matern32Kernel 113 | .. autoclass:: george.kernels.Matern52Kernel 114 | 115 | 116 | .. _periodic-kernels: 117 | 118 | Periodic Kernels 119 | ---------------- 120 | 121 | .. autoclass:: george.kernels.CosineKernel 122 | .. autoclass:: george.kernels.ExpSine2Kernel 123 | 124 | 125 | .. _combining-kernels: 126 | 127 | Combining Kernels 128 | ----------------- 129 | 130 | More complicated kernels can be constructed by algebraically combining the 131 | basic kernels listed in the previous sections. 132 | In particular, all the kernels support addition and multiplication. 133 | For example, an exponential-squared kernel with a non-trivial variance can be 134 | constructed as follows: 135 | 136 | .. code-block:: python 137 | 138 | from george import kernels 139 | kernel = 1e-3 * kernels.ExpSquaredKernel(3.4) 140 | 141 | This is equivalent to: 142 | 143 | .. code-block:: python 144 | 145 | from math import sqrt 146 | kernel = kernels.Product(kernels.ConstantKernel(sqrt(1e-3)), 147 | kernels.ExpSquaredKernel(3.4)) 148 | 149 | As demonstrated in :ref:`hyper`, a mixture of kernels can be implemented with 150 | addition: 151 | 152 | .. code-block:: python 153 | 154 | k1 = 1e-3 * kernels.ExpSquaredKernel(3.4) 155 | k2 = 1e-4 * kernels.Matern32Kernel(14.53) 156 | kernel = k1 + k2 157 | 158 | 159 | .. _new-kernels: 160 | 161 | Implementing New Kernels 162 | ------------------------ 163 | 164 | Implementing custom kernels in George is a bit of a pain in the current 165 | version. For now, the only way to do it is with the :class:`PythonKernel` 166 | where you provide a Python function that computes the value of the kernel 167 | function at *a single pair of training points*. 168 | 169 | .. autoclass:: george.kernels.PythonKernel 170 | :members: 171 | -------------------------------------------------------------------------------- /docs/user/model.rst: -------------------------------------------------------------------------------- 1 | .. _model: 2 | 3 | Tutorial: model fitting with correlated noise 4 | ============================================= 5 | 6 | In this example, we're going to simulate a common data analysis situation 7 | where our dataset exhibits unknown correlations in the noise. 8 | When taking data, it is often possible to estimate the independent measurement 9 | uncertainty on a single point (due to, for example, Poisson counting 10 | statistics) but there are often residual systematics that correlate data 11 | points. 12 | The effect of this correlated noise can often be hard to estimate but ignoring 13 | it can introduce substantial biases into your inferences. 14 | In the following sections, we will consider a synthetic dataset with 15 | correlated noise and a simple non-linear model. 16 | We will start by fitting the model assuming that the noise is uncorrelated and 17 | then improve on this model by modeling the covariance structure in the data 18 | using a Gaussian process. 19 | 20 | All the code used in this tutorial is available `here 21 | `_. 22 | 23 | 24 | A Simple Mean Model 25 | ------------------- 26 | 27 | The model that we'll fit in this demo is a single Gaussian feature with three 28 | parameters: amplitude :math:`\alpha`, location :math:`\ell`, and width 29 | :math:`\sigma^2`. 30 | I've chosen this model because is is the simplest non-linear model that I 31 | could think of, and it is qualitatively similar to a few problems in astronomy 32 | (fitting spectral features, measuring transit times, *etc.*). 33 | 34 | 35 | Simulated Dataset 36 | ----------------- 37 | 38 | I simulated a dataset of 50 points with known correlated noise. 39 | In fact, this example is somewhat artificial since the data *were* drawn from 40 | a Gaussian process but in everything that follows, we'll use a different 41 | kernel function for our inferences in an attempt to make the situation 42 | slightly more realistic. 43 | A known white variance was also added to each data point and the resulting 44 | dataset is: 45 | 46 | .. image:: ../_static/model/data.png 47 | 48 | The true model parameters used to simulate this dataset are: 49 | 50 | .. math:: 51 | 52 | \alpha = -1\quad, \quad\quad 53 | \ell = 0.1\quad, \quad\quad 54 | \sigma^2 = 0.4\quad. 55 | 56 | 57 | Assuming White Noise 58 | -------------------- 59 | 60 | Let's start by doing the standard thing and assuming that the noise is 61 | uncorrelated. 62 | In this case, the ln-likelihood function of the data :math:`\{y_n\}` given the 63 | parameters :math:`\theta` is 64 | 65 | .. math:: 66 | 67 | \ln p(\{y_n\}\,|\,\{t_n\},\,\{\sigma_n^2\},\,\theta) = 68 | -\frac{1}{2}\,\sum_{n=1}^N \frac{[y_n - f_\theta(t_n)]^2}{\sigma_n^2} 69 | + A 70 | 71 | where :math:`A` doesn't depend on :math:`\theta` so it is irrelevant for our 72 | purposes and :math:`f_\theta(t)` is our model function. 73 | 74 | It is clear that there is some sort of systematic trend in the data and we 75 | don't want to ignore that so we'll simultaneously model a linear trend and the 76 | Gaussian feature described in the previous section. 77 | Therefore, our model is 78 | 79 | .. math:: 80 | 81 | f_\theta (t) = m\,t + b + 82 | \alpha\,\exp\left(-\frac{[t-\ell]^2}{2\,\sigma^2} \right) 83 | 84 | where :math:`\theta` is the 5-dimensional parameter vector 85 | 86 | .. math:: 87 | 88 | \theta = \{ m,\,b,\,\alpha,\,\ell,\,\sigma^2 \} \quad. 89 | 90 | 91 | The following code snippet is a simple implementation of this model in Python 92 | 93 | .. code-block:: python 94 | 95 | import numpy as np 96 | 97 | def model1(params, t): 98 | m, b, amp, loc, sig2 = params 99 | return m*t + b + amp * np.exp(-0.5 * (t - loc) ** 2 / sig2) 100 | 101 | def lnlike1(p, t, y, yerr): 102 | return -0.5 * np.sum(((y - model1(p, t))/yerr) ** 2) 103 | 104 | To fit this model using MCMC (using `emcee `_), we 105 | need to first choose priors---in this case we'll just use a simple uniform 106 | prior on each parameter---and then combine these with our likelihood function 107 | to compute the ln-probability (up to a normalization constant). 108 | In code, this will be: 109 | 110 | .. code-block:: python 111 | 112 | def lnprior1(p): 113 | m, b, amp, loc, sig2 = p 114 | if (-10 < m < 10 and -10 < b < 10 and -10 < amp < 10 and 115 | -5 < loc < 5 and 0 < sig2 < 3): 116 | return 0.0 117 | return -np.inf 118 | 119 | def lnprob1(p, x, y, yerr): 120 | lp = lnprior1(p) 121 | return lp + lnlike1(p, x, y, yerr) if np.isfinite(lp) else -np.inf 122 | 123 | Now that we have our model implemented, we'll initialize the walkers and run 124 | both a burn-in and production chain: 125 | 126 | .. code-block:: python 127 | 128 | # We'll assume that the data are stored in a tuple: 129 | # data = (t, y, yerr) 130 | 131 | import emcee 132 | 133 | initial = np.array([0, 0, -1.0, 0.1, 0.4]) 134 | ndim = len(initial) 135 | nwalkers = 32 136 | p0 = [np.array(initial) + 1e-8 * np.random.randn(ndim) 137 | for i in xrange(nwalkers)] 138 | sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob1, args=data) 139 | 140 | print("Running burn-in...") 141 | p0, _, _ = sampler.run_mcmc(p0, 500) 142 | sampler.reset() 143 | 144 | print("Running production...") 145 | sampler.run_mcmc(p0, 1000) 146 | 147 | After running the chain, we can plot the results using the ``flatchain`` 148 | property of the sampler. 149 | It is often useful to plot the results on top of the data as well. 150 | To do this, we can over plot 24 posterior samples on top of the data: 151 | 152 | .. code-block:: python 153 | 154 | import matplotlib.pyplot as pl 155 | 156 | # Plot the data. 157 | pl.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0) 158 | 159 | # The positions where the prediction should be computed. 160 | x = np.linspace(-5, 5, 500) 161 | 162 | # Plot 24 posterior samples. 163 | samples = sampler.flatchain 164 | for s in samples[np.random.randint(len(samples), size=24)]: 165 | pl.plot(x, model1(s, x), color="#4682b4", alpha=0.3) 166 | 167 | Running this code should make a figure like: 168 | 169 | .. image:: ../_static/model/ind-results.png 170 | 171 | In this figure, the data are shown as black points with error bars and the 172 | posterior samples are shown as translucent blue lines. 173 | These results seem, at face value, pretty satisfying. 174 | But, since we know the true model parameters that were used to simulate the 175 | data, we can assess our original assumption of uncorrelated noise. 176 | To do this, we'll plot all the projections of our posterior samples using 177 | `triangle.py `_ and over plot the true 178 | values: 179 | 180 | .. image:: ../_static/model/ind-corner.png 181 | 182 | In this figure, the blue lines are the true values used to simulate the data 183 | and the black contours and histograms show the posterior constraints. 184 | The constraints on the amplitude :math:`\alpha` and the width :math:`\sigma^2` 185 | are consistent with the truth but the location of the feature :math:`\ell` is 186 | *almost completely inconsistent with the truth!* 187 | This would matter a lot if we were trying to precisely measure radial 188 | velocities or transit times. 189 | 190 | 191 | Modeling the Noise 192 | ------------------ 193 | 194 | .. note:: A full discussion of the theory of Gaussian processes is beyond the 195 | scope of this demo---you should probably check out `Rasmussen & Williams 196 | (2006) `_---but I'll try to give a 197 | quick qualitative motivation for our model. 198 | 199 | In this section, instead of assuming that the noise is white, we'll generalize 200 | the likelihood function to include covariances between data points. 201 | To do this, let's start by re-writing the likelihood function from the 202 | previous section as a matrix equation (if you squint, you'll be able to work 203 | out that we haven't changed it at all): 204 | 205 | .. math:: 206 | 207 | \ln p(\{y_n\}\,|\,\{t_n\},\,\{\sigma_n^2\},\,\theta) = 208 | -\frac{1}{2}\,\boldsymbol{r}^\mathrm{T}\,K^{-1}\,\boldsymbol{r} 209 | -\frac{1}{2}\,\ln\det K - \frac{N}{2}\,\ln 2\pi 210 | 211 | where 212 | 213 | .. math:: 214 | 215 | \boldsymbol{r} = \left ( \begin{array}{c} 216 | y_1 - f_\theta(t_1) \\ 217 | y_2 - f_\theta(t_2) \\ 218 | \vdots \\ 219 | y_N - f_\theta(t_N) \\ 220 | \end{array}\right) 221 | 222 | is the residual vector and 223 | 224 | .. math:: 225 | 226 | K = \left ( \begin{array}{cccc} 227 | \sigma_1^2 & 0 & & 0 \\ 228 | 0 & \sigma_2^2 & & 0 \\ 229 | & & \ddots & \\ 230 | 0 & 0 & & \sigma_N^2 \\ 231 | \end{array}\right) 232 | 233 | is the :math:`N \times N` data covariance matrix (where :math:`N` is the 234 | number of data points). 235 | 236 | The fact that :math:`K` is diagonal is the result of our earlier assumption 237 | that the noise was white. 238 | If we want to relax this assumption, we just need to start populating the 239 | off-diagonal elements of this covariance matrix. 240 | If we wanted to make every off-diagonal element of the matrix a free 241 | parameter, there would be too many parameters to actually do any inference. 242 | Instead, we can simply *model* the elements of this array as 243 | 244 | .. math:: 245 | 246 | K_{ij} = \sigma_i^2\,\delta_{ij} + k(t_i,\,t_j) 247 | 248 | where :math:`\delta_{ij}` is the `Kronecker_delta 249 | `_ and :math:`k(\cdot,\,\cdot)` 250 | is a covariance function that we get to choose. 251 | `Chapter 4 `_ of 252 | Rasmussen & Williams discusses various choices for :math:`k` but for this 253 | demo, we'll just use the `Matérn-3/2 function 254 | `_: 255 | 256 | .. math:: 257 | 258 | k(r) = a^2 \, \left( 1+\frac{\sqrt{3}\,r}{\tau} \right)\, 259 | \exp \left (-\frac{\sqrt{3}\,r}{\tau} \right ) 260 | 261 | where :math:`r = |t_i - t_j|`, and :math:`a^2` and :math:`\tau` are the 262 | parameters of the model. 263 | 264 | 265 | The Final Fit 266 | ------------- 267 | 268 | Now we could go ahead and implement the ln-likelihood function that we came up 269 | with in the previous section but that's what George is for, after all! 270 | To implement the model from the previous section using George, we can write 271 | the following ln-likelihood function in Python: 272 | 273 | .. code-block:: python 274 | 275 | import george 276 | from george import kernels 277 | 278 | def model2(params, t): 279 | _, _, amp, loc, sig2 = params 280 | return amp * np.exp(-0.5 * (t - loc) ** 2 / sig2) 281 | 282 | def lnlike2(p, t, y, yerr): 283 | a, tau = np.exp(p[:2]) 284 | gp = george.GP(a * kernels.Matern32Kernel(tau)) 285 | gp.compute(t, yerr) 286 | return gp.lnlikelihood(y - model2(p, t)) 287 | 288 | def lnprior2(p): 289 | lna, lntau, amp, loc, sig2 = p 290 | if (-5 < lna < 5 and -5 < lntau < 5 and -10 < amp < 10 and 291 | -5 < loc < 5 and 0 < sig2 < 3): 292 | return 0.0 293 | return -np.inf 294 | 295 | def lnprob2(p, x, y, yerr): 296 | lp = lnprior2(p) 297 | return lp + lnlike2(p, x, y, yerr) if np.isfinite(lp) else -np.inf 298 | 299 | As before, let's run MCMC on this model: 300 | 301 | .. code-block:: python 302 | 303 | initial = np.array([0, 0, -1.0, 0.1, 0.4]) 304 | ndim = len(initial) 305 | p0 = [np.array(initial) + 1e-8 * np.random.randn(ndim) 306 | for i in xrange(nwalkers)] 307 | sampler = emcee.EnsembleSampler(nwalkers, ndim, lnprob2, args=data) 308 | 309 | print("Running first burn-in...") 310 | p0, lnp, _ = sampler.run_mcmc(p0, 250) 311 | p = p0[np.argmax(lnp)] 312 | sampler.reset() 313 | 314 | # Re-sample the walkers near the best walker from the previous burn-in. 315 | p0 = [p + 1e-8 * np.random.randn(ndim) for i in xrange(nwalkers)] 316 | 317 | print("Running second burn-in...") 318 | p0, _, _ = sampler.run_mcmc(p0, 250) 319 | sampler.reset() 320 | 321 | print("Running production...") 322 | sampler.run_mcmc(p0, 1000) 323 | 324 | You'll notice that this time I've run two burn-in phases where each one is 325 | half the length of the burn-in from the previous example. 326 | Before the second burn-in, I re-sample the positions of the walkers in a tiny 327 | ball around the position of the best walker in the previous run. 328 | I found that this re-sampling step was useful because otherwise some of the 329 | walkers started in a bad part of parameter space and took a while to converge 330 | to something reasonable. 331 | 332 | The plotting code for the results for this model is similar to the code in the 333 | previous section. 334 | First, we can plot the posterior samples on top of the data: 335 | 336 | .. code-block:: python 337 | 338 | # Plot the data. 339 | pl.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0) 340 | 341 | # The positions where the prediction should be computed. 342 | x = np.linspace(-5, 5, 500) 343 | 344 | # Plot 24 posterior samples. 345 | samples = sampler.flatchain 346 | for s in samples[np.random.randint(len(samples), size=24)]: 347 | # Set up the GP for this sample. 348 | a, tau = np.exp(s[:2]) 349 | gp = george.GP(a * kernels.Matern32Kernel(tau)) 350 | gp.compute(t, yerr) 351 | 352 | # Compute the prediction conditioned on the observations and plot it. 353 | m = gp.sample_conditional(y - model2(s, t), x) + model2(s, x) 354 | pl.plot(x, m, color="#4682b4", alpha=0.3) 355 | 356 | This code should produce a figure like: 357 | 358 | .. image:: ../_static/model/gp-results.png 359 | 360 | The code for the corner plot is identical to the previous one. 361 | Running that should give the following marginalized constraints: 362 | 363 | .. image:: ../_static/model/gp-corner.png 364 | 365 | It is clear from this figure that the constraints obtained when modeling the 366 | noise are less precise (the error bars are larger) but more accurate (less 367 | biased). 368 | -------------------------------------------------------------------------------- /docs/user/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart: 2 | 3 | Getting started 4 | =============== 5 | 6 | Installation 7 | ------------ 8 | 9 | You can install the most recent stable version of George using `PyPI 10 | <#stable>`_ or the development version from `GitHub 11 | `_. 12 | 13 | Prerequisites 14 | +++++++++++++ 15 | 16 | Whichever method you choose, you'll need to make sure that you first have 17 | `Eigen `_ installed. 18 | On Linux: 19 | 20 | .. code-block:: bash 21 | 22 | sudo apt-get install libeigen3-dev 23 | 24 | On Mac: 25 | 26 | .. code-block:: bash 27 | 28 | brew install eigen 29 | 30 | .. note:: Chances are high that George won't work on Windows right now because 31 | it hasn't been tested at all but feel free to try it out at your own risk! 32 | 33 | You'll also need a working scientific Python installation (including `NumPy 34 | `_ and `SciPy `_). 35 | I recommend the `Anaconda distribution `_ if 36 | you don't already have your own opinions. 37 | 38 | .. _stable: 39 | 40 | Stable Version 41 | ++++++++++++++ 42 | 43 | The simplest way to install the `most recent stable version of George 44 | `_ is using `pip 45 | `_: 46 | 47 | .. code-block:: bash 48 | 49 | pip install george 50 | 51 | If you installed Eigen in a strange place, specify that location by running 52 | (sorry to say that it's pretty freaking ugly): 53 | 54 | .. code-block:: bash 55 | 56 | pip install george \ 57 | --global-option=build_ext \ 58 | --global-option=-I/path/to/eigen3 59 | 60 | 61 | .. _dev: 62 | 63 | Development Version 64 | +++++++++++++++++++ 65 | 66 | To get the source for the development version, clone the git repository and 67 | checkout the required HODLR submodule: 68 | 69 | .. code-block:: bash 70 | 71 | git clone https://github.com/dfm/george.git 72 | cd george 73 | git submodule init 74 | git submodule update 75 | 76 | Then, install the package by running the following command: 77 | 78 | .. code-block:: bash 79 | 80 | python setup.py install 81 | 82 | If installed Eigen in a non-standard location, you can specify the correct 83 | path using the install command: 84 | 85 | .. code-block:: bash 86 | 87 | python setup.py build_ext -I/path/to/eigen3 install 88 | 89 | Testing 90 | +++++++ 91 | 92 | To run the unit tests, install `nose `_ and then 93 | execute: 94 | 95 | .. code-block:: bash 96 | 97 | nosetests -v george.testing 98 | 99 | All of the tests should (of course) pass. 100 | If any of the tests don't pass and if you can't sort out why, `open an issue 101 | on GitHub `_. 102 | 103 | 104 | A Simple Example 105 | ---------------- 106 | 107 | The following code generates some fake data (from a sinusoidal model) with 108 | error bars: 109 | 110 | .. code-block:: python 111 | 112 | import numpy as np 113 | 114 | # Generate some fake noisy data. 115 | x = 10 * np.sort(np.random.rand(10)) 116 | yerr = 0.2 * np.ones_like(x) 117 | y = np.sin(x) + yerr * np.random.randn(len(x)) 118 | 119 | Then, we'll choose a simple kernel function (see :ref:`kernels` for some other 120 | choices) and compute the log-likelihood of the fake data under a Gaussian 121 | process model with this kernel: 122 | 123 | .. code-block:: python 124 | 125 | import george 126 | from george.kernels import ExpSquaredKernel 127 | 128 | # Set up the Gaussian process. 129 | kernel = ExpSquaredKernel(1.0) 130 | gp = george.GP(kernel) 131 | 132 | # Pre-compute the factorization of the matrix. 133 | gp.compute(x, yerr) 134 | 135 | # Compute the log likelihood. 136 | print(gp.lnlikelihood(y)) 137 | 138 | Finally, we can compute the predicted values of the function at a fine grid of 139 | points conditioned on the observed data. 140 | This prediction will be an :math:`N_\mathrm{test} \times N_\mathrm{test}` 141 | multivariate Gaussian (where :math:`N_\mathrm{test}` is the number of points 142 | in the grid) with mean ``mu`` and covariance ``cov``: 143 | 144 | .. code-block:: python 145 | 146 | t = np.linspace(0, 10, 500) 147 | mu, cov = gp.predict(y, t) 148 | std = np.sqrt(np.diag(cov)) 149 | 150 | This should result in a constraint that looks something like: 151 | 152 | .. image:: ../_static/quickstart/conditional.png 153 | 154 | where the points with error bars are the simulated data and the filled gray 155 | patch is the mean and standard deviation of the prediction. 156 | -------------------------------------------------------------------------------- /docs/user/solvers.rst: -------------------------------------------------------------------------------- 1 | .. module:: george 2 | 3 | .. _solvers: 4 | 5 | Solvers 6 | ======= 7 | 8 | There are currently two different GP solvers included with George using 9 | different libraries for doing linear algebra. 10 | Both of the solvers implement the same API and should (up to some tolerance) 11 | give the same answers on the same datasets. 12 | A solver is just a class that takes a :class:`Kernel` and that exposes 3 13 | methods: 14 | 15 | 1. ``compute`` --- to compute and factorize the kernel matrix, 16 | 2. ``apply_inverse`` --- to left-multiply the input by the covariance matrix 17 | :math:`C^{-1}\,b` (actually implemented by solving the system 18 | :math:`C\,x = b`), and 19 | 3. ``apply_sqrt`` --- to apply the (Cholesky) square root of the covariance. 20 | 21 | The solvers also provide the properties ``computed`` and ``log_determinant``. 22 | 23 | The simplest solver provided by George (:class:`BasicSolver`) uses `scipy's 24 | Cholesky implementation 25 | `_ 26 | and the second implementation (:class:`HODLRSolver`) uses Sivaram 27 | Amambikasaran's `HODLR library `_. 28 | The HODLR algorithm implements a :math:`\mathcal{O}(N\,\log^2 N)` direct 29 | solver for dense matrices as described `here `_. 30 | 31 | By default, George uses the :class:`BasicSolver` but the :class:`HODLRSolver` 32 | can be used as follows: 33 | 34 | .. code-block:: python 35 | 36 | import george 37 | kernel = ... 38 | gp = george.GP(kernel, solver=george.HODLRSolver) 39 | 40 | The :class:`HODLRSolver` is probably best for most one-dimensional problems 41 | and some large multi-dimensional problems but it doesn't (in general) scale 42 | well with the number of input dimensions. 43 | In practice, it's worth trying both solvers on your specific problem to see 44 | which runs faster. 45 | 46 | 47 | Basic Solver 48 | ------------ 49 | 50 | .. autoclass:: george.BasicSolver 51 | :inherited-members: 52 | 53 | 54 | HODLR Solver 55 | ------------ 56 | 57 | .. autoclass:: george.HODLRSolver 58 | :inherited-members: 59 | -------------------------------------------------------------------------------- /document/.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.brf 3 | *.log 4 | *.out 5 | *.pdf 6 | *.bbl 7 | *.blg 8 | *.dvi 9 | *.ps 10 | *.synctex.gz 11 | -------------------------------------------------------------------------------- /document/Makefile: -------------------------------------------------------------------------------- 1 | LATEX = pdflatex 2 | BASH = bash -c 3 | ECHO = echo 4 | RM = rm -rf 5 | TMP_SUFFS = pdf aux bbl blg log dvi ps eps out 6 | CHECK_RERUN = grep Rerun $*.log 7 | 8 | NAME = ms 9 | 10 | all: ${NAME}.pdf 11 | 12 | %.pdf: %.tex 13 | ${LATEX} $< 14 | ( ${CHECK_RERUN} && ${LATEX} $< ) || echo "Done." 15 | ( ${CHECK_RERUN} && ${LATEX} $< ) || echo "Done." 16 | 17 | clean: 18 | ${RM} $(foreach suff, ${TMP_SUFFS}, ${NAME}.${suff}) 19 | -------------------------------------------------------------------------------- /document/code/exo_demo_1/2301306-injection-gp.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/document/code/exo_demo_1/2301306-injection-gp.pkl -------------------------------------------------------------------------------- /document/code/exo_demo_1/2301306-injection-median.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/document/code/exo_demo_1/2301306-injection-median.pkl -------------------------------------------------------------------------------- /document/code/exo_demo_1/2301306-injection.fits: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/document/code/exo_demo_1/2301306-injection.fits -------------------------------------------------------------------------------- /document/code/exo_demo_1/2973073-injection-gp.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/document/code/exo_demo_1/2973073-injection-gp.pkl -------------------------------------------------------------------------------- /document/code/exo_demo_1/2973073-injection-median.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/document/code/exo_demo_1/2973073-injection-median.pkl -------------------------------------------------------------------------------- /document/code/exo_demo_1/2973073-injection.fits: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/document/code/exo_demo_1/2973073-injection.fits -------------------------------------------------------------------------------- /document/code/exo_demo_1/fit_detrended.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = ["fit_detrended"] 7 | 8 | import os 9 | import emcee 10 | import numpy as np 11 | import cPickle as pickle 12 | 13 | from models import MedianModel 14 | 15 | 16 | def fit_detrended(fn): 17 | # Load the data and set up the model. 18 | model = MedianModel(fn) 19 | 20 | # Initialize the walkers. 21 | v = model.vector 22 | ndim, nwalkers = len(v), 32 23 | p0 = [v + 1e-5*np.random.randn(ndim) for i in range(nwalkers)] 24 | sampler = emcee.EnsembleSampler(nwalkers, ndim, model) 25 | 26 | print("Running burn-in...") 27 | p0, _, _ = sampler.run_mcmc(p0, 500) 28 | sampler.reset() 29 | 30 | print("Running production chain...") 31 | sampler.run_mcmc(p0, 2000) 32 | 33 | with open(os.path.splitext(fn)[0] + "-median.pkl", "wb") as f: 34 | pickle.dump((model, sampler), f, -1) 35 | 36 | 37 | if __name__ == "__main__": 38 | import sys 39 | fit_detrended(sys.argv[1]) 40 | -------------------------------------------------------------------------------- /document/code/exo_demo_1/fit_gp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = ["fit_gp"] 7 | 8 | import os 9 | import emcee 10 | import numpy as np 11 | import cPickle as pickle 12 | 13 | from models import GPModel 14 | 15 | 16 | def fit_gp(fn): 17 | # Load the data and set up the model. 18 | model = GPModel(fn) 19 | 20 | # Initialize the walkers. 21 | v = model.vector 22 | ndim, nwalkers = len(v), 32 23 | p0 = [v + 1e-3*np.random.randn(ndim) for i in range(nwalkers)] 24 | sampler = emcee.EnsembleSampler(nwalkers, ndim, model) 25 | 26 | print("Running burn-in...") 27 | p0, _, _ = sampler.run_mcmc(p0, 1000) 28 | sampler.reset() 29 | 30 | print("Running production chain...") 31 | sampler.run_mcmc(p0, 4000) 32 | 33 | with open(os.path.splitext(fn)[0] + "-gp.pkl", "wb") as f: 34 | pickle.dump((model, sampler), f, -1) 35 | 36 | 37 | if __name__ == "__main__": 38 | import sys 39 | fit_gp(sys.argv[1]) 40 | -------------------------------------------------------------------------------- /document/code/exo_demo_1/inject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = ["inject"] 7 | 8 | import kplr 9 | import fitsio 10 | import transit 11 | import numpy as np 12 | import matplotlib.pyplot as pl 13 | 14 | 15 | def inject(kicid, rng=6): 16 | # Download the data. 17 | client = kplr.API() 18 | kic = client.star(kicid) 19 | lcs = kic.get_light_curves(short_cadence=False) 20 | lc = lcs[np.random.randint(len(lcs))] 21 | 22 | # Read the data. 23 | data = lc.read() 24 | t = data["TIME"] 25 | f = data["SAP_FLUX"] 26 | fe = data["SAP_FLUX_ERR"] 27 | q = data["SAP_QUALITY"] 28 | 29 | # Remove missing points. 30 | m = np.isfinite(t) * np.isfinite(f) * np.isfinite(fe) * (q == 0) 31 | t, f, fe = t[m], f[m], fe[m] 32 | t -= t.min() 33 | 34 | # Build the transit system. 35 | s = transit.System(transit.Central(q1=np.random.rand(), 36 | q2=np.random.rand())) 37 | body = transit.Body(period=365.25, b=np.random.rand(), r=0.04, 38 | t0=np.random.uniform(t.max())) 39 | s.add_body(body) 40 | 41 | # Compute the transit model. 42 | texp = kplr.EXPOSURE_TIMES[1] / 86400.0 # Long cadence exposure time. 43 | model = s.light_curve(t, texp=texp) 44 | f *= model 45 | 46 | # Trim the dataset to include data only near the transit. 47 | m = np.abs(t - body.t0) < rng 48 | t, f, fe = t[m], f[m], fe[m] 49 | t -= body.t0 50 | 51 | # Save the injection as a FITS light curve. 52 | dt = [("TIME", float), ("SAP_FLUX", float), ("SAP_FLUX_ERR", float)] 53 | data = np.array(zip(t, f, fe), dtype=dt) 54 | hdr = dict(b=body.b, period=body.period, r=body.r, t0=0.0, 55 | q1=s.central.q1, q2=s.central.q2) 56 | fitsio.write("{0}-injection.fits".format(kicid), data, header=hdr, 57 | clobber=True) 58 | 59 | # Plot the light curve. 60 | ppm = (f / np.median(f) - 1) * 1e6 61 | fig = pl.figure(figsize=(6, 6)) 62 | ax = fig.add_subplot(111) 63 | ax.plot(t, ppm, ".k") 64 | ax.set_xlim(-rng, rng) 65 | ax.set_xlabel("time since transit [days]") 66 | ax.set_ylabel("relative flux [ppm]") 67 | ax.set_title("raw light curve") 68 | fig.subplots_adjust(left=0.2, bottom=0.2, top=0.9, right=0.9) 69 | fig.savefig("{0}-raw.pdf".format(kicid)) 70 | 71 | 72 | if __name__ == "__main__": 73 | import sys 74 | 75 | if len(sys.argv) > 1: 76 | inject(sys.argv[1]) 77 | else: 78 | np.random.seed(12345) 79 | inject(2301306) 80 | # inject(2973073) 81 | -------------------------------------------------------------------------------- /document/code/exo_demo_1/load_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = ["load_data"] 7 | 8 | import fitsio 9 | import numpy as np 10 | 11 | 12 | def median_trend(x, y, dt): 13 | x, y = np.atleast_1d(x), np.atleast_1d(y) 14 | assert len(x) == len(y) 15 | r = np.empty(len(y)) 16 | for i, t in enumerate(x): 17 | inds = np.abs(x-t) < 0.5 * dt 18 | r[i] = np.median(y[inds]) 19 | return r 20 | 21 | 22 | def load_data(fn, median=False, dt=3.0): 23 | data, hdr = fitsio.read(fn, ext=1, header=True) 24 | t, f, fe = data["TIME"], data["SAP_FLUX"], data["SAP_FLUX_ERR"] 25 | 26 | # Median detrend if requested. 27 | if median: 28 | trend = median_trend(t, f, dt) 29 | f /= trend 30 | fe /= trend 31 | 32 | # Parse the true values. 33 | truth = dict((k.lower(), hdr[k]) for k in ["Q1", "Q2", "R", "B", "PERIOD", 34 | "T0"]) 35 | 36 | return t, f, fe, truth 37 | 38 | 39 | if __name__ == "__main__": 40 | import os 41 | import sys 42 | import matplotlib.pyplot as pl 43 | 44 | # Load and detrend the data. 45 | t, f, fe, truth = load_data(sys.argv[1], True) 46 | 47 | # Plot the detrended light curve. 48 | ppm = (f / np.median(f) - 1) * 1e6 49 | fig = pl.figure(figsize=(6, 6)) 50 | ax = fig.add_subplot(111) 51 | ax.plot(t, ppm, ".k") 52 | ax.set_xlim(np.min(t), np.max(t)) 53 | ax.set_xlabel("time since transit [days]") 54 | ax.set_ylabel("relative flux [ppm]") 55 | ax.set_title("median de-trended light curve") 56 | fig.subplots_adjust(left=0.2, bottom=0.2, top=0.9, right=0.9) 57 | fig.savefig(os.path.splitext(sys.argv[1])[0] + "-median.pdf") 58 | -------------------------------------------------------------------------------- /document/code/exo_demo_1/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = ["MedianModel", "GPModel"] 7 | 8 | import kplr 9 | import transit 10 | import numpy as np 11 | 12 | import george 13 | from george import kernels 14 | 15 | from load_data import load_data 16 | 17 | texp = kplr.EXPOSURE_TIMES[1] / 86400.0 # Long cadence exposure time. 18 | 19 | 20 | class MedianModel(object): 21 | 22 | def __init__(self, fn, median=True): 23 | self.t, self.f, self.fe, self.truth = load_data(fn, median) 24 | self.ivar = 1.0 / self.fe ** 2 25 | self.central = transit.Central(q1=self.truth["q1"], 26 | q2=self.truth["q2"]) 27 | self.system = transit.System(self.central) 28 | self.body = transit.Body(period=self.truth["period"], 29 | r=self.truth["r"], 30 | b=self.truth["b"], 31 | t0=self.truth["t0"]) 32 | self.system.add_body(self.body) 33 | 34 | @property 35 | def true_vector(self): 36 | return np.array([ 37 | 1.0, self.truth["q1"], self.truth["q2"], 38 | np.log(self.truth["period"]), self.truth["t0"], self.truth["b"], 39 | np.log(self.truth["r"]), 40 | ]) 41 | 42 | @property 43 | def vector(self): 44 | return np.array([ 45 | self.central.flux, self.central.q1, self.central.q2, 46 | np.log(self.body.period), self.body.t0, self.body.b, 47 | np.log(self.body.r), 48 | ]) 49 | 50 | @vector.setter 51 | def vector(self, v): 52 | self.central.flux, self.central.q1, self.central.q2 = v[:3] 53 | lnp, self.body.t0, self.body.b, lnr = v[3:] 54 | self.body.period = np.exp(lnp) 55 | self.body.r = np.exp(lnr) 56 | 57 | @property 58 | def labels(self): 59 | return [r"$f_\star$", r"$q_1$", r"$q_2$", r"$\ln P$", 60 | r"$t_0$", r"$b$", r"$\ln r$"] 61 | 62 | def lnlike(self, p): 63 | self.vector = p 64 | model = self.system.light_curve(self.t, texp=texp) 65 | return -0.5 * np.sum((self.f - model) ** 2 * self.ivar) 66 | 67 | def predict(self, t): 68 | return self.system.light_curve(t, texp=texp) 69 | 70 | def __call__(self, p): 71 | try: 72 | self.vector = p 73 | if np.log(self.body.r) < -4: 74 | return -np.inf 75 | if not (-0.5 < self.body.t0 < 0.5): 76 | return -np.inf 77 | if not self.body.b < 1.5: 78 | return -np.inf 79 | return self.lnlike(p) 80 | except ValueError: 81 | return -np.inf 82 | 83 | 84 | class _mean_function(object): 85 | 86 | def __init__(self, s): 87 | self.s = s 88 | 89 | def __call__(self, t): 90 | return self.s.light_curve(t, texp=texp) 91 | 92 | 93 | class GPModel(MedianModel): 94 | 95 | def __init__(self, *args, **kwargs): 96 | kwargs["median"] = False 97 | super(GPModel, self).__init__(*args, **kwargs) 98 | 99 | # Normalize the fluxes. 100 | mu = np.median(self.f) 101 | self.f /= mu 102 | self.fe /= mu 103 | 104 | # Set up the GP model. 105 | self.kernel = 1e-6 * kernels.Matern32Kernel(3.0) 106 | self.gp = george.GP(self.kernel, mean=_mean_function(self.system)) 107 | self.gp.compute(self.t, self.fe) 108 | 109 | @property 110 | def true_vector(self): 111 | return np.append([-np.inf for i in range(len(self.kernel))], 112 | super(GPModel, self).true_vector) 113 | 114 | @property 115 | def vector(self): 116 | return np.append(np.log(self.kernel.pars), super(GPModel, self).vector) 117 | 118 | @vector.setter 119 | def vector(self, v): 120 | self.kernel.pars = np.exp(v[:len(self.kernel)]) 121 | MedianModel.vector.fset(self, v[len(self.kernel):]) 122 | 123 | @property 124 | def labels(self): 125 | return (map(r"$\ln\theta_{{{0}}}$".format, 126 | range(1, len(self.kernel)+1)) 127 | + super(GPModel, self).labels) 128 | 129 | def lnlike(self, p): 130 | self.vector = p 131 | return self.gp.lnlikelihood(self.f, quiet=True) 132 | 133 | def predict(self, t): 134 | return self.gp.sample_conditional(self.f, t) 135 | -------------------------------------------------------------------------------- /document/code/exo_demo_1/results.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = ["results"] 7 | 8 | import os 9 | import triangle 10 | import numpy as np 11 | import cPickle as pickle 12 | import matplotlib.pyplot as pl 13 | 14 | 15 | def results(fn): 16 | model, sampler = pickle.load(open(fn, "rb")) 17 | mu = np.median(model.f) 18 | ppm = lambda f: (f / mu - 1) * 1e6 19 | 20 | # Plot the data. 21 | fig = pl.figure(figsize=(6, 6)) 22 | ax = fig.add_subplot(111) 23 | ax.plot(model.t, ppm(model.f), ".k") 24 | ax.set_xlim(np.min(model.t), np.max(model.t)) 25 | ax.set_xlabel("time since transit [days]") 26 | ax.set_ylabel("relative flux [ppm]") 27 | fig.subplots_adjust(left=0.2, bottom=0.2, top=0.9, right=0.9) 28 | 29 | # Plot the predictions. 30 | samples = sampler.flatchain 31 | t = np.linspace(model.t.min(), model.t.max(), 1000) 32 | for i in np.random.randint(len(samples), size=10): 33 | model.vector = samples[i] 34 | ax.plot(t, ppm(model.predict(t)), color="#4682b4", alpha=0.5) 35 | 36 | fig.savefig(os.path.splitext(fn)[0] + "-results.pdf") 37 | 38 | # Plot the corner plot. 39 | fig = triangle.corner(samples, labels=model.labels, 40 | truths=model.true_vector) 41 | fig.savefig(os.path.splitext(fn)[0] + "-triangle.png") 42 | 43 | 44 | if __name__ == "__main__": 45 | import sys 46 | results(sys.argv[1]) 47 | -------------------------------------------------------------------------------- /examples/2d.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | import os 7 | import sys 8 | import numpy as np 9 | import matplotlib.pyplot as pl 10 | 11 | d = os.path.dirname 12 | sys.path.insert(0, d(d(os.path.abspath(__file__)))) 13 | import george 14 | from george.kernels import ExpSquaredKernel 15 | 16 | np.random.seed(12345) 17 | 18 | kernel = ExpSquaredKernel([3, 0.5], ndim=2) 19 | gp = george.HODLRGP(kernel, tol=1e-10) 20 | 21 | x, y = np.linspace(-5, 5, 62), np.linspace(-5, 5, 60) 22 | x, y = np.meshgrid(x, y, indexing="ij") 23 | shape = x.shape 24 | samples = np.vstack((x.flatten(), y.flatten())).T 25 | gp.compute(samples, 1e-4*np.ones(len(samples)), sort=False) 26 | 27 | print(len(samples)) 28 | i = george.utils.nd_sort_samples(samples) 29 | 30 | img = gp.get_matrix(samples[i]) 31 | pl.imshow(img, cmap="gray", interpolation="nearest") 32 | pl.gca().set_xticklabels([]) 33 | pl.gca().set_yticklabels([]) 34 | pl.colorbar() 35 | pl.savefig("2d-cov.png") 36 | 37 | pl.clf() 38 | z = np.empty(len(samples)) 39 | z[i] = gp.sample(samples[i]) 40 | pl.pcolor(x, y, z.reshape(shape), cmap="gray") 41 | pl.colorbar() 42 | pl.savefig("2d.png") 43 | 44 | import time 45 | 46 | s = time.time() 47 | gp.compute(samples, 1e-4*np.ones_like(z), sort=False) 48 | print(time.time() - s) 49 | s = time.time() 50 | print(gp.lnlikelihood(z)) 51 | print(time.time() - s) 52 | 53 | s = time.time() 54 | gp.compute(samples, 1e-4*np.ones_like(z)) 55 | print(gp.lnlikelihood(z)) 56 | print(time.time() - s) 57 | 58 | gp.kernel = ExpSquaredKernel([3.1, 0.6], ndim=2) 59 | 60 | s = time.time() 61 | gp.compute(samples, 1e-4*np.ones_like(z)) 62 | print(gp.lnlikelihood(z)) 63 | print(time.time() - s) 64 | 65 | s = time.time() 66 | gp.compute(samples[i], 1e-4*np.ones_like(z), sort=False) 67 | print(gp.lnlikelihood(z[i])) 68 | print(time.time() - s) 69 | -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | import os 7 | import sys 8 | import numpy as np 9 | import matplotlib.pyplot as pl 10 | 11 | d = os.path.dirname 12 | sys.path.insert(0, d(d(os.path.abspath(__file__)))) 13 | import george 14 | from george.kernels import ExpSquaredKernel, Matern32Kernel, CosineKernel 15 | 16 | np.random.seed(12345) 17 | 18 | experiments = [ 19 | ("exponential squared", [ 20 | ("-k", "$l=0.5$", ExpSquaredKernel(0.5)), 21 | ("--k", "$l=1$", ExpSquaredKernel(1.0)), 22 | (":k", "$l=2$", ExpSquaredKernel(2.0)), 23 | ]), 24 | ("quasi-periodic", [ 25 | ("-k", "$l=2,\,P=3$", Matern32Kernel(2.0) * CosineKernel(3.0)), 26 | ("--k", "$l=3,\,P=3$", Matern32Kernel(3.0) * CosineKernel(3.0)), 27 | (":k", "$l=3,\,P=1$", Matern32Kernel(3.0) * CosineKernel(1.0)), 28 | ]) 29 | ] 30 | 31 | t = np.linspace(0, 10, 500) 32 | h, w = len(experiments) * 4, 6 33 | fig, axes = pl.subplots(len(experiments), 1, figsize=(w, h), sharex=True) 34 | fig.subplots_adjust(left=0.1, bottom=0.1, right=0.96, top=0.98, 35 | wspace=0.0, hspace=0.05) 36 | for ax, (name, runs) in zip(axes, experiments): 37 | for style, label, kernel in runs: 38 | gp = george.GP(kernel) 39 | f = gp.sample(t) 40 | ax.plot(t, f, style, lw=1.5, label=label) 41 | ax.legend(prop={"size": 12}) 42 | ax.set_ylim(-2.8, 3.8) 43 | ax.annotate(name, xy=(0, 1), xycoords="axes fraction", xytext=(5, -5), 44 | textcoords="offset points", ha="left", va="top") 45 | axes[-1].set_xlabel("$t$") 46 | fig.savefig("simple.png") 47 | fig.savefig("simple.pdf") 48 | 49 | # Plot the covariance matrix images. 50 | fig = pl.figure(figsize=(8, 8)) 51 | for i, (name, runs) in enumerate(experiments): 52 | fig.clf() 53 | gp = george.GP(runs[0][2]) 54 | img = gp.get_matrix(t) 55 | ax = fig.add_subplot(111) 56 | ax.set_title(name) 57 | ax.imshow(img, cmap="gray", interpolation="nearest") 58 | ax.set_xticklabels([]) 59 | ax.set_yticklabels([]) 60 | fig.savefig("simple-cov-{0}.pdf".format(i+1)) 61 | -------------------------------------------------------------------------------- /examples/test_learning_curve_kernel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import numpy as np 7 | import random 8 | import itertools 9 | import matplotlib.pyplot as plt 10 | sys.path.append("..") 11 | 12 | import george 13 | from george.kernels import LearningCurveKernel 14 | 15 | 16 | kernel = LearningCurveKernel(ndim=1,dim=0) 17 | kernel.vector=np.array([1.0, 0.5]) 18 | 19 | n_curves = 10 20 | 21 | gp = george.GP(kernel, mean=0) 22 | 23 | t = np.linspace(0, 9, 100) 24 | 25 | for i in range(20): 26 | plt.plot(gp.sample(t)) 27 | plt.show() 28 | 29 | for i in range(n_curves): 30 | x = np.array(random.sample(range(10), 3)) 31 | 32 | alpha = np.random.rand() 33 | print(alpha) 34 | y = np.exp(-x * alpha) + np.random.randn() * 0.001 35 | gp.compute(x[:, None]) 36 | 37 | f = gp.predict(y, t[:, None])[0] 38 | plt.plot(f) 39 | plt.show() 40 | -------------------------------------------------------------------------------- /examples/test_polynomial_noise_kernel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import numpy as np 7 | import itertools 8 | 9 | import matplotlib.pyplot as plt 10 | 11 | sys.path.append("..") 12 | import george 13 | from george.kernels import HeteroscedasticNoisePolynomialKernel 14 | 15 | 16 | kernel = HeteroscedasticNoisePolynomialKernel(2,0) 17 | 18 | 19 | 20 | 21 | 22 | num_plot_points=100 23 | x = np.linspace(0,1, num_plot_points, endpoint=False) 24 | K = np.zeros(num_plot_points,dtype=np.double) 25 | 26 | for foo in range(7): 27 | kernel.vector=np.random.randn(len(kernel.vector)) 28 | 29 | for i,s in enumerate(x): 30 | K[i] = kernel.value(np.array([[s,0]]),np.array([[s,0]]))[0,0] 31 | 32 | plt.plot(x,K, label="{} -> c={:.2f}, alpha = {:.2f}".format(kernel.vector, np.exp(kernel.vector[0]), np.exp(kernel.vector[1])), linewidth=3) 33 | plt.legend() 34 | 35 | plt.xlabel('input value') 36 | plt.ylabel('kernel value') 37 | plt.title("Note how the parameters of the kernel live in log space!") 38 | plt.show() 39 | -------------------------------------------------------------------------------- /examples/test_task_kernel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import numpy as np 7 | import itertools 8 | 9 | sys.path.append("..") 10 | 11 | import george 12 | from george.kernels import TaskKernel 13 | 14 | 15 | num_tasks = 10 16 | 17 | 18 | kernel = TaskKernel(1,0,num_tasks) 19 | 20 | print(kernel.vector) 21 | kernel.vector=range(1, len(kernel.vector)+1) 22 | print(kernel.vector) 23 | 24 | 25 | K = np.zeros([num_tasks, num_tasks]) 26 | 27 | for (i,j) in itertools.product(range(num_tasks), repeat=2): 28 | K[i,j] = (kernel.value(np.array([[i]]),np.array([[j]]))[0,0]) 29 | 30 | print(K) 31 | 32 | 33 | print(np.linalg.cholesky(K)) 34 | -------------------------------------------------------------------------------- /george/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __version__ = "0.2.1" 4 | 5 | try: 6 | __GEORGE_SETUP__ 7 | except NameError: 8 | __GEORGE_SETUP__ = False 9 | 10 | if not __GEORGE_SETUP__: 11 | __all__ = ["kernels", "GP", "BasicSolver", "HODLRSolver"] 12 | 13 | from . import kernels 14 | from .gp import GP 15 | from .basic import BasicSolver 16 | from .hodlr import HODLRSolver 17 | -------------------------------------------------------------------------------- /george/_kernels.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | from __future__ import division 3 | 4 | cimport cython 5 | cimport kernels 6 | 7 | import numpy as np 8 | cimport numpy as np 9 | np.import_array() 10 | 11 | DTYPE = np.float64 12 | ctypedef np.float64_t DTYPE_t 13 | 14 | 15 | def _rebuild(kernel_spec): 16 | return CythonKernel(kernel_spec) 17 | 18 | 19 | cdef class CythonKernel: 20 | 21 | cdef kernels.Kernel* kernel 22 | cdef object kernel_spec 23 | 24 | def __cinit__(self, kernel_spec): 25 | self.kernel_spec = kernel_spec 26 | self.kernel = kernels.parse_kernel(kernel_spec) 27 | 28 | def __reduce__(self): 29 | return _rebuild, (self.kernel_spec, ) 30 | 31 | def __dealloc__(self): 32 | del self.kernel 33 | 34 | @cython.boundscheck(False) 35 | def value_symmetric(self, np.ndarray[DTYPE_t, ndim=2] x): 36 | cdef unsigned int n = x.shape[0], ndim = x.shape[1] 37 | if self.kernel.get_ndim() != ndim: 38 | raise ValueError("Dimension mismatch") 39 | 40 | # Build the kernel matrix. 41 | cdef double value 42 | cdef unsigned int i, j, delta = x.strides[0] 43 | cdef np.ndarray[DTYPE_t, ndim=2] k = np.empty((n, n), dtype=DTYPE) 44 | for i in range(n): 45 | k[i, i] = self.kernel.value((x.data + i*delta), 46 | (x.data + i*delta)) 47 | for j in range(i + 1, n): 48 | value = self.kernel.value((x.data + i*delta), 49 | (x.data + j*delta)) 50 | k[i, j] = value 51 | k[j, i] = value 52 | 53 | return k 54 | 55 | @cython.boundscheck(False) 56 | def value_general(self, np.ndarray[DTYPE_t, ndim=2] x1, 57 | np.ndarray[DTYPE_t, ndim=2] x2): 58 | # Parse the input kernel spec. 59 | cdef unsigned int n1 = x1.shape[0], ndim = x1.shape[1], n2 = x2.shape[0] 60 | if self.kernel.get_ndim() != ndim or x2.shape[1] != ndim: 61 | raise ValueError("Dimension mismatch") 62 | 63 | # Build the kernel matrix. 64 | cdef double value 65 | cdef unsigned int i, j, d1 = x1.strides[0], d2 = x2.strides[0] 66 | cdef np.ndarray[DTYPE_t, ndim=2] k = np.empty((n1, n2), dtype=DTYPE) 67 | for i in range(n1): 68 | for j in range(n2): 69 | k[i, j] = self.kernel.value((x1.data + i*d1), 70 | (x2.data + j*d2)) 71 | 72 | return k 73 | 74 | @cython.boundscheck(False) 75 | def gradient_symmetric(self, np.ndarray[DTYPE_t, ndim=2] x): 76 | # Check the input dimensions. 77 | cdef unsigned int n = x.shape[0], ndim = x.shape[1] 78 | if self.kernel.get_ndim() != ndim: 79 | raise ValueError("Dimension mismatch") 80 | 81 | # Get the number of parameters. 82 | cdef unsigned int size = self.kernel.size() 83 | 84 | # Build the gradient matrix. 85 | cdef double value 86 | cdef np.ndarray[DTYPE_t, ndim=3] g = np.empty((n, n, size), dtype=DTYPE) 87 | cdef unsigned int i, j, k, delta = x.strides[0] 88 | cdef unsigned int dx = g.strides[0], dy = g.strides[1] 89 | for i in range(n): 90 | self.kernel.gradient((x.data + i*delta), 91 | (x.data + i*delta), 92 | (g.data + i*dx + i*dy)) 93 | for j in range(i + 1, n): 94 | self.kernel.gradient((x.data + i*delta), 95 | (x.data + j*delta), 96 | (g.data + i*dx + j*dy)) 97 | for k in range(size): 98 | g[j, i, k] = g[i, j, k] 99 | 100 | return g 101 | 102 | @cython.boundscheck(False) 103 | def gradient_general(self, np.ndarray[DTYPE_t, ndim=2] x1, 104 | np.ndarray[DTYPE_t, ndim=2] x2): 105 | cdef unsigned int n1 = x1.shape[0], ndim = x1.shape[1], n2 = x2.shape[0] 106 | if self.kernel.get_ndim() != ndim or x2.shape[1] != ndim: 107 | raise ValueError("Dimension mismatch") 108 | 109 | # Get the number of parameters. 110 | cdef unsigned int size = self.kernel.size() 111 | 112 | # Build the gradient matrix. 113 | cdef double value 114 | cdef np.ndarray[DTYPE_t, ndim=3] g = np.empty((n1, n2, size), dtype=DTYPE) 115 | cdef unsigned int i, j, k, d1 = x1.strides[0], d2 = x2.strides[0] 116 | cdef unsigned int dx = g.strides[0], dy = g.strides[1] 117 | for i in range(n1): 118 | for j in range(n2): 119 | self.kernel.gradient((x1.data + i*d1), 120 | (x2.data + j*d2), 121 | (g.data + i*dx + j*dy)) 122 | 123 | return g 124 | -------------------------------------------------------------------------------- /george/basic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function 4 | 5 | __all__ = ["BasicSolver"] 6 | 7 | import numpy as np 8 | from scipy.linalg import cholesky, cho_solve 9 | 10 | 11 | class BasicSolver(object): 12 | """ 13 | This is the most basic solver built using :func:`scipy.linalg.cholesky`. 14 | 15 | :param kernel: 16 | A subclass of :class:`Kernel` implementing a kernel function. 17 | 18 | """ 19 | 20 | def __init__(self, kernel): 21 | self.kernel = kernel 22 | self._computed = False 23 | self._log_det = None 24 | 25 | @property 26 | def computed(self): 27 | """ 28 | A flag indicating whether or not the covariance matrix was computed 29 | and factorized (using the :func:`compute` method). 30 | 31 | """ 32 | return self._computed 33 | 34 | @computed.setter 35 | def computed(self, v): 36 | self._computed = v 37 | 38 | @property 39 | def log_determinant(self): 40 | """ 41 | The log-determinant of the covariance matrix. This will only be 42 | non-``None`` after calling the :func:`compute` method. 43 | 44 | """ 45 | return self._log_det 46 | 47 | @log_determinant.setter 48 | def log_determinant(self, v): 49 | self._log_det = v 50 | 51 | def compute(self, x, yerr): 52 | """ 53 | Compute and factorize the covariance matrix. 54 | 55 | :param x: ``(nsamples, ndim)`` 56 | The independent coordinates of the data points. 57 | 58 | :param yerr: (optional) ``(nsamples,)`` or scalar 59 | The Gaussian uncertainties on the data points at coordinates 60 | ``x``. These values will be added in quadrature to the diagonal of 61 | the covariance matrix. 62 | 63 | """ 64 | # Compute the kernel matrix. 65 | K = self.kernel.value(x) 66 | K[np.diag_indices_from(K)] += yerr ** 2 67 | 68 | # Factor the matrix and compute the log-determinant. 69 | self._factor = (cholesky(K, overwrite_a=True, lower=False), False) 70 | self.log_determinant = 2 * np.sum(np.log(np.diag(self._factor[0]))) 71 | self.computed = True 72 | 73 | def apply_inverse(self, y, in_place=False): 74 | r""" 75 | Apply the inverse of the covariance matrix to the input by solving 76 | 77 | .. math:: 78 | 79 | C\,x = b 80 | 81 | :param y: ``(nsamples,)`` or ``(nsamples, nrhs)`` 82 | The vector or matrix :math:`b`. 83 | 84 | :param in_place: (optional) 85 | Should the data in ``y`` be overwritten with the result :math:`x`? 86 | 87 | """ 88 | return cho_solve(self._factor, y, overwrite_b=in_place) 89 | 90 | def apply_sqrt(self, r): 91 | """ 92 | Apply the Cholesky square root of the covariance matrix to the input 93 | vector or matrix. 94 | 95 | :param r: ``(nsamples,)`` or ``(nsamples, nrhs)`` 96 | The input vector or matrix. 97 | 98 | """ 99 | return np.dot(r, self._factor[0]) 100 | -------------------------------------------------------------------------------- /george/generate_kernel_defs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import division, print_function 5 | 6 | __all__ = [] 7 | 8 | import os 9 | import sys 10 | import inspect 11 | 12 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 13 | from george import kernels 14 | 15 | rad_temp = """ elif kernel_spec.kernel_type == {kernel_type}: 16 | if kernel_spec.dim >= 0: 17 | kernel = new {name}[OneDMetric](ndim, 18 | new OneDMetric(ndim, kernel_spec.dim)) 19 | elif kernel_spec.isotropic: 20 | kernel = new {name}[IsotropicMetric](ndim, 21 | new IsotropicMetric(ndim)) 22 | elif kernel_spec.axis_aligned: 23 | kernel = new {name}[AxisAlignedMetric](ndim, 24 | new AxisAlignedMetric(ndim)) 25 | else: 26 | raise NotImplementedError("The general metric isn't implemented") 27 | """ 28 | 29 | for name, value in inspect.getmembers(kernels): 30 | if not hasattr(value, "__bases__"): 31 | continue 32 | if kernels.RadialKernel in value.__bases__: 33 | print(rad_temp.format(name=name, kernel_type=value.kernel_type)) 34 | -------------------------------------------------------------------------------- /george/gp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function 4 | 5 | __all__ = ["GP"] 6 | 7 | import numpy as np 8 | import scipy.optimize as op 9 | from scipy.linalg import LinAlgError 10 | 11 | from .basic import BasicSolver 12 | from .utils import multivariate_gaussian_samples, nd_sort_samples 13 | 14 | 15 | # MAGIC: tiny epsilon to add on the diagonal of the matrices in the absence 16 | # of observational uncertainties. Needed for computational stability. 17 | TINY = 1.25e-12 18 | 19 | 20 | class GP(object): 21 | """ 22 | The basic Gaussian Process object. 23 | 24 | :param kernel: 25 | An instance of a subclass of :class:`kernels.Kernel`. 26 | 27 | :param mean: (optional) 28 | A description of the mean function; can be a callable or a scalar. If 29 | scalar, the mean is assumed constant. Otherwise, the function will be 30 | called with the array of independent coordinates as the only argument. 31 | (default: ``0.0``) 32 | 33 | :param solver: (optional) 34 | The solver to use for linear algebra as documented in :ref:`solvers`. 35 | 36 | :param kwargs: (optional) 37 | Any additional arguments are passed directly to the solver's init 38 | function. 39 | 40 | """ 41 | 42 | def __init__(self, kernel, mean=None, solver=BasicSolver, **kwargs): 43 | self.kernel = kernel 44 | self._computed = False 45 | self._alpha = None 46 | self._y = None 47 | self.mean = mean 48 | 49 | self.solver_type = solver 50 | self.solver_kwargs = kwargs 51 | self.solver = None 52 | 53 | @property 54 | def mean(self): 55 | return self._mean 56 | 57 | @mean.setter 58 | def mean(self, mean): 59 | if mean is None: 60 | self._mean = _default_mean(0.) 61 | else: 62 | try: 63 | val = float(mean) 64 | except TypeError: 65 | self._mean = mean 66 | else: 67 | self._mean = _default_mean(val) 68 | 69 | @property 70 | def computed(self): 71 | """ 72 | Has the processes been computed since the last update of the kernel? 73 | 74 | """ 75 | return ( 76 | self._computed 77 | and self.solver.computed 78 | and not self.kernel.dirty 79 | ) 80 | 81 | @computed.setter 82 | def computed(self, v): 83 | self._computed = v 84 | if v: 85 | self.kernel.dirty = False 86 | 87 | def parse_samples(self, t, sort=False): 88 | """ 89 | Parse a list of samples to make sure that it has the correct 90 | dimensions and optionally sort it. In one dimension, the samples will 91 | be sorted in the logical order. In higher dimensions, a kd-tree is 92 | built and the samples are sorted in increasing distance from the 93 | *first* sample. 94 | 95 | :param t: ``(nsamples,)`` or ``(nsamples, ndim)`` 96 | The list of samples. If 1-D, this is assumed to be a list of 97 | one-dimensional samples otherwise, the size of the second 98 | dimension is assumed to be the dimension of the input space. 99 | 100 | :param sort: 101 | A boolean flag indicating whether or not the samples should be 102 | sorted. 103 | 104 | Returns a tuple ``(samples, inds)`` where 105 | 106 | * **samples** is an array with shape ``(nsamples, ndim)`` and if 107 | ``sort`` was ``True``, it will also be sorted, and 108 | * **inds** is an ``(nsamples,)`` list of integer permutations used to 109 | sort the list of samples. 110 | 111 | Raises a ``RuntimeError`` if the input dimension doesn't match the 112 | dimension of the kernel. 113 | 114 | """ 115 | t = np.atleast_1d(t) 116 | if len(t.shape) == 1: 117 | # Deal with one-dimensional data. 118 | if sort: 119 | inds = np.argsort(t) 120 | else: 121 | inds = np.arange(len(t), dtype=int) 122 | t = np.atleast_2d(t).T 123 | elif sort: 124 | # Sort the data using a KD-tree. 125 | inds = nd_sort_samples(t) 126 | else: 127 | # Otherwise, assume that the samples are sorted. 128 | inds = np.arange(t.shape[0], dtype=int) 129 | 130 | # Double check the dimensions against the kernel. 131 | if len(t.shape) != 2 or t.shape[1] != self.kernel.ndim: 132 | raise ValueError("Dimension mismatch") 133 | 134 | return t[inds], inds 135 | 136 | def _check_dimensions(self, y): 137 | n, ndim = self._x.shape 138 | y = np.atleast_1d(y) 139 | if len(y.shape) > 1: 140 | raise ValueError("The predicted dimension must be 1-D") 141 | if len(y) != n: 142 | raise ValueError("Dimension mismatch") 143 | return y 144 | 145 | def _compute_alpha(self, y): 146 | # Recalculate alpha only if y is not the same as the previous y. 147 | if self._alpha is None or not np.array_equiv(y, self._y): 148 | self._y = y 149 | r = np.ascontiguousarray(self._check_dimensions(y)[self.inds] 150 | - self.mean(self._x), dtype=np.float64) 151 | self._alpha = self.solver.apply_inverse(r, in_place=True) 152 | 153 | def compute(self, x, yerr=TINY, sort=True, **kwargs): 154 | """ 155 | Pre-compute the covariance matrix and factorize it for a set of times 156 | and uncertainties. 157 | 158 | :param x: ``(nsamples,)`` or ``(nsamples, ndim)`` 159 | The independent coordinates of the data points. 160 | 161 | :param yerr: (optional) ``(nsamples,)`` or scalar 162 | The Gaussian uncertainties on the data points at coordinates 163 | ``x``. These values will be added in quadrature to the diagonal of 164 | the covariance matrix. 165 | 166 | :param sort: (optional) 167 | Should the samples be sorted before computing the covariance 168 | matrix? This can lead to more numerically stable results and with 169 | some linear algebra libraries this can more computationally 170 | efficient. Either way, this flag is passed directly to 171 | :func:`parse_samples`. (default: ``True``) 172 | 173 | """ 174 | # Parse the input coordinates and ensure the right memory layout. 175 | self._x, self.inds = self.parse_samples(x, sort) 176 | self._x = np.ascontiguousarray(self._x, dtype=np.float64) 177 | try: 178 | self._yerr = float(yerr) * np.ones(len(x)) 179 | except TypeError: 180 | self._yerr = self._check_dimensions(yerr)[self.inds] 181 | self._yerr = np.ascontiguousarray(self._yerr, dtype=np.float64) 182 | 183 | # Set up and pre-compute the solver. 184 | self.solver = self.solver_type(self.kernel, **(self.solver_kwargs)) 185 | self.solver.compute(self._x, self._yerr, **kwargs) 186 | 187 | self._const = -0.5 * (len(self._x) * np.log(2 * np.pi) 188 | + self.solver.log_determinant) 189 | self.computed = True 190 | 191 | self._alpha = None 192 | 193 | def recompute(self, quiet=False, **kwargs): 194 | """ 195 | Re-compute a previously computed model. You might want to do this if 196 | the kernel parameters change and the kernel is labeled as ``dirty``. 197 | 198 | """ 199 | if self.kernel.dirty or not self.computed: 200 | if not (hasattr(self, "_x") and hasattr(self, "_yerr")): 201 | raise RuntimeError("You need to compute the model first") 202 | try: 203 | # Update the model making sure that we store the original 204 | # ordering of the points. 205 | initial_order = np.array(self.inds) 206 | self.compute(self._x, self._yerr, sort=False, **kwargs) 207 | self.inds = initial_order 208 | except (ValueError, LinAlgError): 209 | if quiet: 210 | return False 211 | raise 212 | return True 213 | 214 | def lnlikelihood(self, y, quiet=False): 215 | """ 216 | Compute the ln-likelihood of a set of observations under the Gaussian 217 | process model. You must call ``compute`` before this function. 218 | 219 | :param y: ``(nsamples, )`` 220 | The observations at the coordinates provided in the ``compute`` 221 | step. 222 | 223 | :param quiet: 224 | If ``True`` return negative infinity instead of raising an 225 | exception when there is an invalid kernel or linear algebra 226 | failure. (default: ``False``) 227 | 228 | """ 229 | r = np.ascontiguousarray(self._check_dimensions(y)[self.inds] 230 | - self.mean(self._x), dtype=np.float64) 231 | if not self.recompute(quiet=quiet): 232 | return -np.inf 233 | ll = self._const - 0.5 * np.dot(r, self.solver.apply_inverse(r)) 234 | return ll if np.isfinite(ll) else -np.inf 235 | 236 | def grad_lnlikelihood(self, y, quiet=False): 237 | """ 238 | Compute the gradient of the ln-likelihood function as a function of 239 | the kernel parameters. 240 | 241 | :param y: ``(nsamples,)`` 242 | The list of observations at coordinates ``x`` provided to the 243 | :func:`compute` function. 244 | 245 | :param quiet: 246 | If ``True`` return a gradient of zero instead of raising an 247 | exception when there is an invalid kernel or linear algebra 248 | failure. (default: ``False``) 249 | 250 | """ 251 | # Make sure that the model is computed and try to recompute it if it's 252 | # dirty. 253 | if not self.recompute(quiet=quiet): 254 | return np.zeros(len(self.kernel), dtype=float) 255 | 256 | # Pre-compute some factors. 257 | self._compute_alpha(y) 258 | K_inv = self.solver.apply_inverse(np.eye(self._alpha.size), 259 | in_place=True) 260 | Kg = self.kernel.gradient(self._x) 261 | 262 | # Calculate the gradient. 263 | A = np.outer(self._alpha, self._alpha) - K_inv 264 | g = 0.5 * np.einsum('ijk,ij', Kg, A) 265 | 266 | return g 267 | 268 | def predict(self, y, t, mean_only=False): 269 | """ 270 | Compute the conditional predictive distribution of the model. 271 | 272 | :param y: ``(nsamples,)`` 273 | The observations to condition the model on. 274 | 275 | :param t: ``(ntest,)`` or ``(ntest, ndim)`` 276 | The coordinates where the predictive distribution should be 277 | computed. 278 | 279 | Returns a tuple ``(mu, cov)`` where 280 | 281 | * **mu** ``(ntest,)`` is the mean of the predictive distribution, and 282 | * **cov** ``(ntest, ntest)`` is the predictive covariance. 283 | 284 | """ 285 | self.recompute() 286 | self._compute_alpha(y) 287 | xs, i = self.parse_samples(t, False) 288 | 289 | # Compute the predictive mean. 290 | Kxs = self.kernel.value(xs, self._x) 291 | mu = np.dot(Kxs, self._alpha) + self.mean(xs) 292 | if mean_only: 293 | return mu 294 | 295 | # Compute the predictive covariance. 296 | KxsT = np.ascontiguousarray(Kxs.T, dtype=np.float64) 297 | cov = self.kernel.value(xs) 298 | cov -= np.dot(Kxs, self.solver.apply_inverse(KxsT, in_place=False)) 299 | 300 | return mu, cov 301 | 302 | def sample_conditional(self, y, t, size=1): 303 | """ 304 | Draw samples from the predictive conditional distribution. 305 | 306 | :param y: ``(nsamples, )`` 307 | The observations to condition the model on. 308 | 309 | :param t: ``(ntest, )`` or ``(ntest, ndim)`` 310 | The coordinates where the predictive distribution should be 311 | computed. 312 | 313 | :param size: (optional) 314 | The number of samples to draw. (default: ``1``) 315 | 316 | Returns **samples** ``(N, ntest)``, a list of predictions at 317 | coordinates given by ``t``. 318 | 319 | """ 320 | mu, cov = self.predict(y, t) 321 | return multivariate_gaussian_samples(cov, size, mean=mu) 322 | 323 | def sample(self, t=None, size=1): 324 | """ 325 | Draw samples from the prior distribution. 326 | 327 | :param t: ``(ntest, )`` or ``(ntest, ndim)`` (optional) 328 | The coordinates where the model should be sampled. If no 329 | coordinates are given, the precomputed coordinates and 330 | factorization are used. 331 | 332 | :param size: (optional) 333 | The number of samples to draw. (default: ``1``) 334 | 335 | Returns **samples** ``(size, ntest)``, a list of predictions at 336 | coordinates given by ``t``. If ``size == 1``, the result is a single 337 | sample with shape ``(ntest,)``. 338 | 339 | """ 340 | if t is None: 341 | self.recompute() 342 | n, _ = self._x.shape 343 | 344 | # Generate samples using the precomputed factorization. 345 | samples = self.solver.apply_sqrt(np.random.randn(size, n)) 346 | samples += self.mean(self._x) 347 | 348 | # Reorder the samples correctly. 349 | results = np.empty_like(samples) 350 | results[:, self.inds] = samples 351 | return results[0] if size == 1 else results 352 | 353 | x, _ = self.parse_samples(t, False) 354 | cov = self.get_matrix(x) 355 | return multivariate_gaussian_samples(cov, size, mean=self.mean(x)) 356 | 357 | def get_matrix(self, t): 358 | """ 359 | Get the covariance matrix at a given set of independent coordinates. 360 | 361 | :param t: ``(nsamples,)`` or ``(nsamples, ndim)`` 362 | The list of samples. 363 | 364 | """ 365 | r, _ = self.parse_samples(t, False) 366 | return self.kernel.value(r) 367 | 368 | def optimize(self, x, y, yerr=TINY, sort=True, dims=None, verbose=True, 369 | **kwargs): 370 | """ 371 | A simple and not terribly robust non-linear optimization algorithm for 372 | the kernel hyperpararmeters. 373 | 374 | :param x: ``(nsamples,)`` or ``(nsamples, ndim)`` 375 | The independent coordinates of the data points. 376 | 377 | :param y: ``(nsamples, )`` 378 | The observations at the coordinates ``x``. 379 | 380 | :param yerr: (optional) ``(nsamples,)`` or scalar 381 | The Gaussian uncertainties on the data points at coordinates 382 | ``x``. These values will be added in quadrature to the diagonal of 383 | the covariance matrix. 384 | 385 | :param sort: (optional) 386 | Should the samples be sorted before computing the covariance 387 | matrix? 388 | 389 | :param dims: (optional) 390 | If you only want to optimize over some parameters, list their 391 | indices here. 392 | 393 | :param verbose: (optional) 394 | Display the results of the call to :func:`scipy.optimize.minimize`? 395 | (default: ``True``) 396 | 397 | Returns ``(pars, results)`` where ``pars`` is the list of optimized 398 | parameters and ``results`` is the results object returned by 399 | :func:`scipy.optimize.minimize`. 400 | 401 | """ 402 | self.compute(x, yerr, sort=sort) 403 | 404 | # By default, optimize all the hyperparameters. 405 | if dims is None: 406 | dims = np.ones(len(self.kernel), dtype=bool) 407 | dims = np.arange(len(self.kernel))[dims] 408 | 409 | # Define the objective function and gradient. 410 | def nll(pars): 411 | self.kernel[dims] = pars 412 | ll = self.lnlikelihood(y, quiet=True) 413 | if not np.isfinite(ll): 414 | return 1e25 # The optimizers can't deal with infinities. 415 | return -ll 416 | 417 | def grad_nll(pars): 418 | self.kernel[dims] = pars 419 | return -self.grad_lnlikelihood(y, quiet=True)[dims] 420 | 421 | # Run the optimization. 422 | p0 = self.kernel.vector[dims] 423 | results = op.minimize(nll, p0, jac=grad_nll, **kwargs) 424 | 425 | if verbose: 426 | print(results.message) 427 | 428 | return self.kernel.vector[dims], results 429 | 430 | 431 | class _default_mean(object): 432 | 433 | def __init__(self, value): 434 | self.value = value 435 | 436 | def __call__(self, t): 437 | return self.value + np.zeros(len(t), dtype=float) 438 | 439 | def __len__(self): 440 | return 1 441 | 442 | @property 443 | def vector(self): 444 | return np.array([self.value]) 445 | 446 | @vector.setter 447 | def vector(self, value): 448 | self.value = float(value) 449 | 450 | def lnprior(self): 451 | return 0.0 452 | -------------------------------------------------------------------------------- /george/hodlr.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | from __future__ import division 3 | 4 | cimport cython 5 | cimport kernels 6 | 7 | import time 8 | import numpy as np 9 | cimport numpy as np 10 | 11 | DTYPE = np.float64 12 | ctypedef np.float64_t DTYPE_t 13 | 14 | cdef extern from "solver.h" namespace "george": 15 | 16 | cdef cppclass Solver: 17 | Solver(kernels.Kernel*, unsigned int, double) 18 | int compute (const unsigned int, const double*, const double*, unsigned int) 19 | int get_computed () const 20 | double get_log_determinant () const 21 | void apply_inverse (const unsigned int, const unsigned int, double*, double*) 22 | 23 | 24 | def _rebuild(kernel_spec, nleaf, tol): 25 | return HODLRSolver(kernel_spec, nleaf=nleaf, tol=tol) 26 | 27 | 28 | cdef class HODLRSolver: 29 | """ 30 | A solver using Sivaram Amambikasaran's `HODLR library 31 | `_ that implements a 32 | :math:`\mathcal{O}(N\,\log^2 N)` direct solver for dense matrices as 33 | described `here `_. 34 | 35 | :param kernel: 36 | A subclass of :class:`Kernel` implementing a kernel function. 37 | 38 | :param nleaf: (optional) 39 | The size of the smallest matrix blocks. When the solver reaches this 40 | level in the tree, it directly solves these systems using Eigen's 41 | Cholesky implementation. (default: ``100``) 42 | 43 | :param tol: (optional) 44 | A tuning parameter used when factorizing the matrix. The conversion 45 | between this parameter and the precision of the results is problem 46 | specific but if you need more precision, try deceasing this number 47 | (at the cost of a longer runtime). (default: ``1e-12``) 48 | 49 | """ 50 | 51 | cdef object kernel_spec 52 | cdef kernels.Kernel* kernel 53 | cdef Solver* solver 54 | cdef unsigned int nleaf 55 | cdef double tol 56 | 57 | def __cinit__(self, kernel_spec, unsigned int nleaf=100, double tol=1e-12): 58 | self.kernel_spec = kernel_spec 59 | self.kernel = kernels.parse_kernel(kernel_spec) 60 | self.solver = new Solver(self.kernel, nleaf, tol) 61 | self.nleaf = nleaf 62 | self.tol = tol 63 | 64 | def __reduce__(self): 65 | return _rebuild, (self.kernel_spec, self.nleaf, self.tol) 66 | 67 | def __dealloc__(self): 68 | del self.solver 69 | del self.kernel 70 | 71 | def compute(self, np.ndarray[DTYPE_t, ndim=2] x, 72 | np.ndarray[DTYPE_t, ndim=1] yerr, seed=None): 73 | """ 74 | compute(x, yerr, seed=None) 75 | Compute and factorize the covariance matrix. 76 | 77 | :param x: ``(nsamples, ndim)`` 78 | The independent coordinates of the data points. 79 | 80 | :param yerr: (optional) ``(nsamples,)`` or scalar 81 | The Gaussian uncertainties on the data points at coordinates 82 | ``x``. These values will be added in quadrature to the diagonal of 83 | the covariance matrix. 84 | 85 | :param seed: (optional) 86 | There is a stochastic component in the HODLR factorization step. 87 | Use this parameter (it should be an integer) to seed this random 88 | step and ensure deterministic results. Normally the randomization 89 | shouldn't make a big difference but as the matrix becomes poorly 90 | conditioned, it will have a larger effect. 91 | 92 | """ 93 | # Check the input dimensions. 94 | cdef unsigned int n = x.shape[0] 95 | cdef unsigned int ndim = x.shape[1] 96 | if yerr.shape[0] != n or ndim != self.kernel.get_ndim(): 97 | raise ValueError("Dimension mismatch") 98 | 99 | # Seed with the time if no seed is provided. 100 | if seed is None: 101 | seed = time.time() 102 | 103 | # Compute the matrix. 104 | cdef int info 105 | info = self.solver.compute(n, x.data, yerr.data, seed) 106 | if info != 0: 107 | raise np.linalg.LinAlgError(info) 108 | 109 | property log_determinant: 110 | """ 111 | The log-determinant of the covariance matrix. This will only be 112 | non-``None`` after calling the :func:`compute` method. 113 | 114 | """ 115 | def __get__(self): 116 | return self.solver.get_log_determinant() 117 | 118 | property computed: 119 | """ 120 | A flag indicating whether or not the covariance matrix was computed 121 | and factorized (using the :func:`compute` method). 122 | 123 | """ 124 | def __get__(self): 125 | return bool(self.solver.get_computed()) 126 | 127 | def apply_inverse(self, y0, in_place=False): 128 | """ 129 | apply_inverse(y, in_place=False) 130 | Apply the inverse of the covariance matrix to the input by solving 131 | 132 | .. math:: 133 | 134 | C\,x = b 135 | 136 | :param y: ``(nsamples,)`` or ``(nsamples, nrhs)`` 137 | The vector or matrix :math:`b`. 138 | 139 | :param in_place: (optional) 140 | Should the data in ``y`` be overwritten with the result :math:`x`? 141 | 142 | """ 143 | # Coerce the input array into the correct format. 144 | cdef np.ndarray[DTYPE_t, ndim=2] y 145 | if len(y0.shape) == 1: 146 | y = np.atleast_2d(y0).T 147 | else: 148 | y = y0 149 | 150 | # Get the problem dimensions. 151 | cdef unsigned int n = y.shape[0], nrhs = y.shape[1] 152 | 153 | # Do an in-place solve if requested. 154 | if in_place: 155 | self.solver.apply_inverse(n, nrhs, y.data, y.data) 156 | return y.reshape(y0.shape) 157 | 158 | # Do the standard solve. 159 | cdef np.ndarray[DTYPE_t, ndim=2] alpha = np.empty_like(y, dtype=DTYPE) 160 | self.solver.apply_inverse(n, nrhs, y.data, alpha.data) 161 | return alpha.reshape(y0.shape) 162 | 163 | def apply_sqrt(self, y0): 164 | """ 165 | apply_sqrt(r) 166 | This method is not implemented by this solver yet. 167 | 168 | """ 169 | raise NotImplementedError("The sqrt function isn't available in " 170 | "the HODLR solver yet") 171 | -------------------------------------------------------------------------------- /george/kernels.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | from __future__ import division 3 | 4 | import numpy as np 5 | cimport numpy as np 6 | 7 | DTYPE = np.float64 8 | ctypedef np.float64_t DTYPE_t 9 | 10 | 11 | cdef extern from "metrics.h" namespace "george::metrics": 12 | 13 | cdef cppclass Metric: 14 | pass 15 | 16 | cdef cppclass OneDMetric(Metric): 17 | OneDMetric(const unsigned int ndim, const unsigned int dim) 18 | 19 | cdef cppclass IsotropicMetric(Metric): 20 | IsotropicMetric(const unsigned int ndim) 21 | 22 | cdef cppclass AxisAlignedMetric(Metric): 23 | AxisAlignedMetric(const unsigned int ndim) 24 | 25 | cdef extern from "kernels.h" namespace "george::kernels": 26 | 27 | cdef cppclass Kernel: 28 | double value (const double* x1, const double* x2) const 29 | void gradient (const double* x1, const double* x2, double* grad) const 30 | unsigned int get_ndim () const 31 | unsigned int size () const 32 | void set_vector (const double*) 33 | 34 | cdef cppclass CustomKernel(Kernel): 35 | CustomKernel(const unsigned int ndim, const unsigned int size, 36 | void* meta, 37 | double (*f) (const double* pars, const unsigned int size, 38 | void* meta, 39 | const double* x1, const double* x2, 40 | const unsigned int ndim), 41 | void (*g) (const double* pars, const unsigned int size, 42 | void* meta, 43 | const double* x1, const double* x2, 44 | const unsigned int ndim, double* grad)) 45 | 46 | # Operators. 47 | cdef cppclass Operator(Kernel): 48 | pass 49 | 50 | cdef cppclass Sum(Operator): 51 | Sum(const unsigned int ndim, Kernel* k1, Kernel* k2) 52 | 53 | cdef cppclass Product(Operator): 54 | Product(const unsigned int ndim, Kernel* k1, Kernel* k2) 55 | 56 | # Basic kernels. 57 | cdef cppclass ConstantKernel(Kernel): 58 | ConstantKernel(const unsigned int ndim) 59 | 60 | cdef cppclass WhiteKernel(Kernel): 61 | WhiteKernel(const unsigned int ndim) 62 | 63 | cdef cppclass DotProductKernel(Kernel): 64 | DotProductKernel(const unsigned int ndim) 65 | 66 | cdef cppclass BayesianLinearRegressionKernel(Kernel): 67 | BayesianLinearRegressionKernel(const unsigned int ndim, const unsigned int dim, const unsigned int degree) 68 | 69 | cdef cppclass HeteroscedasticNoisePolynomialKernel(Kernel): 70 | HeteroscedasticNoisePolynomialKernel(const unsigned int ndim, const unsigned int dim) 71 | 72 | 73 | cdef cppclass LearningCurveKernel(Kernel): 74 | LearningCurveKernel(const unsigned int ndim, const unsigned int dim) 75 | 76 | 77 | # discrete kernels 78 | cdef cppclass TaskKernel(Kernel): 79 | TaskKernel( const unsigned int ndim, const unsigned int dim, const unsigned int num_tasks) 80 | 81 | # Radial kernels. 82 | cdef cppclass ExpKernel[M](Kernel): 83 | ExpKernel(const unsigned int ndim, M* metric) 84 | 85 | cdef cppclass ExpSquaredKernel[M](Kernel): 86 | ExpSquaredKernel(const unsigned int ndim, M* metric) 87 | 88 | cdef cppclass Matern32Kernel[M](Kernel): 89 | Matern32Kernel(const unsigned int ndim, M* metric) 90 | 91 | cdef cppclass Matern52Kernel[M](Kernel): 92 | Matern52Kernel(const unsigned int ndim, M* metric) 93 | 94 | cdef cppclass RationalQuadraticKernel[M](Kernel): 95 | RationalQuadraticKernel(const unsigned int ndim, M* metric) 96 | 97 | # Periodic kernels. 98 | cdef cppclass CosineKernel(Kernel): 99 | CosineKernel(const unsigned int ndim, const unsigned int dim) 100 | 101 | cdef cppclass ExpSine2Kernel(Kernel): 102 | ExpSine2Kernel(const unsigned int ndim, const unsigned int dim) 103 | 104 | 105 | cdef inline double eval_python_kernel (const double* pars, 106 | const unsigned int size, void* meta, 107 | const double* x1, const double* x2, 108 | const unsigned int ndim): 109 | # Build the arguments for calling the function. 110 | cdef np.npy_intp shape[1] 111 | shape[0] = ndim 112 | x1_arr = np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, x1) 113 | x2_arr = np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, x2) 114 | 115 | shape[0] = size 116 | pars_arr = np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, pars) 117 | 118 | # Call the Python function and return the value. 119 | cdef object self = meta 120 | return self.f(x1_arr, x2_arr, pars_arr) 121 | 122 | 123 | cdef inline void eval_python_kernel_grad (const double* pars, 124 | const unsigned int size, 125 | void* meta, 126 | const double* x1, const double* x2, 127 | const unsigned int ndim, double* grad): 128 | # Build the arguments for calling the function. 129 | cdef np.npy_intp shape[1] 130 | shape[0] = ndim 131 | x1_arr = np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, x1) 132 | x2_arr = np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, x2) 133 | 134 | shape[0] = size 135 | pars_arr = np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, pars) 136 | 137 | # Call the Python function and update the gradient values in place. 138 | cdef object self = meta 139 | cdef np.ndarray[DTYPE_t, ndim=1] grad_arr = \ 140 | np.PyArray_SimpleNewFromData(1, shape, np.NPY_FLOAT64, grad) 141 | grad_arr[:] = self.g(x1_arr, x2_arr, pars_arr) 142 | 143 | 144 | cdef inline Kernel* parse_kernel(kernel_spec) except *: 145 | if not hasattr(kernel_spec, "is_kernel"): 146 | raise TypeError("Invalid kernel") 147 | 148 | # Deal with operators first. 149 | cdef Kernel* k1 150 | cdef Kernel* k2 151 | cdef unsigned int n1, n2 152 | if not kernel_spec.is_kernel: 153 | k1 = parse_kernel(kernel_spec.k1) 154 | n1 = k1.get_ndim() 155 | k2 = parse_kernel(kernel_spec.k2) 156 | n2 = k2.get_ndim() 157 | if n1 != n2: 158 | raise ValueError("Dimension mismatch") 159 | 160 | if kernel_spec.operator_type == 0: 161 | return new Sum(n1, k1, k2) 162 | elif kernel_spec.operator_type == 1: 163 | return new Product(n1, k1, k2) 164 | else: 165 | raise TypeError("Unknown operator: {0}".format( 166 | kernel_spec.__class__.__name__)) 167 | 168 | # Get the kernel parameters. 169 | cdef unsigned int ndim = kernel_spec.ndim 170 | cdef np.ndarray[DTYPE_t, ndim=1] pars = kernel_spec.pars 171 | 172 | cdef Kernel* kernel 173 | 174 | if kernel_spec.kernel_type == -2: 175 | kernel = new CustomKernel(ndim, kernel_spec.size, kernel_spec, 176 | &eval_python_kernel, &eval_python_kernel_grad) 177 | 178 | elif kernel_spec.kernel_type == 0: 179 | kernel = new ConstantKernel(ndim) 180 | 181 | elif kernel_spec.kernel_type == 1: 182 | kernel = new WhiteKernel(ndim) 183 | 184 | elif kernel_spec.kernel_type == 2: 185 | kernel = new DotProductKernel(ndim) 186 | 187 | elif kernel_spec.kernel_type == 3: 188 | if kernel_spec.dim >= 0: 189 | kernel = new ExpKernel[OneDMetric](ndim, 190 | new OneDMetric(ndim, kernel_spec.dim)) 191 | elif kernel_spec.isotropic: 192 | kernel = new ExpKernel[IsotropicMetric](ndim, 193 | new IsotropicMetric(ndim)) 194 | elif kernel_spec.axis_aligned: 195 | kernel = new ExpKernel[AxisAlignedMetric](ndim, 196 | new AxisAlignedMetric(ndim)) 197 | else: 198 | raise NotImplementedError("The general metric isn't implemented") 199 | 200 | elif kernel_spec.kernel_type == 4: 201 | if kernel_spec.dim >= 0: 202 | kernel = new ExpSquaredKernel[OneDMetric](ndim, 203 | new OneDMetric(ndim, kernel_spec.dim)) 204 | elif kernel_spec.isotropic: 205 | kernel = new ExpSquaredKernel[IsotropicMetric](ndim, 206 | new IsotropicMetric(ndim)) 207 | elif kernel_spec.axis_aligned: 208 | kernel = new ExpSquaredKernel[AxisAlignedMetric](ndim, 209 | new AxisAlignedMetric(ndim)) 210 | else: 211 | raise NotImplementedError("The general metric isn't implemented") 212 | 213 | elif kernel_spec.kernel_type == 5: 214 | if kernel_spec.dim >= 0: 215 | kernel = new Matern32Kernel[OneDMetric](ndim, 216 | new OneDMetric(ndim, kernel_spec.dim)) 217 | elif kernel_spec.isotropic: 218 | kernel = new Matern32Kernel[IsotropicMetric](ndim, 219 | new IsotropicMetric(ndim)) 220 | elif kernel_spec.axis_aligned: 221 | kernel = new Matern32Kernel[AxisAlignedMetric](ndim, 222 | new AxisAlignedMetric(ndim)) 223 | else: 224 | raise NotImplementedError("The general metric isn't implemented") 225 | 226 | elif kernel_spec.kernel_type == 6: 227 | if kernel_spec.dim >= 0: 228 | kernel = new Matern52Kernel[OneDMetric](ndim, 229 | new OneDMetric(ndim, kernel_spec.dim)) 230 | elif kernel_spec.isotropic: 231 | kernel = new Matern52Kernel[IsotropicMetric](ndim, 232 | new IsotropicMetric(ndim)) 233 | elif kernel_spec.axis_aligned: 234 | kernel = new Matern52Kernel[AxisAlignedMetric](ndim, 235 | new AxisAlignedMetric(ndim)) 236 | else: 237 | raise NotImplementedError("The general metric isn't implemented") 238 | 239 | elif kernel_spec.kernel_type == 7: 240 | if kernel_spec.dim >= 0: 241 | kernel = new RationalQuadraticKernel[OneDMetric](ndim, 242 | new OneDMetric(ndim, kernel_spec.dim)) 243 | elif kernel_spec.isotropic: 244 | kernel = new RationalQuadraticKernel[IsotropicMetric](ndim, 245 | new IsotropicMetric(ndim)) 246 | elif kernel_spec.axis_aligned: 247 | kernel = new RationalQuadraticKernel[AxisAlignedMetric](ndim, 248 | new AxisAlignedMetric(ndim)) 249 | else: 250 | raise NotImplementedError("The general metric isn't implemented") 251 | 252 | elif kernel_spec.kernel_type == 8: 253 | kernel = new CosineKernel(ndim, kernel_spec.dim) 254 | 255 | elif kernel_spec.kernel_type == 9: 256 | kernel = new ExpSine2Kernel(ndim, kernel_spec.dim) 257 | 258 | elif kernel_spec.kernel_type == 10: 259 | kernel = new BayesianLinearRegressionKernel(ndim, kernel_spec.dim, kernel_spec.degree) 260 | 261 | elif kernel_spec.kernel_type == 11: 262 | kernel = new TaskKernel(ndim, kernel_spec.dim, kernel_spec.num_tasks) 263 | 264 | elif kernel_spec.kernel_type == 12: 265 | kernel = new HeteroscedasticNoisePolynomialKernel(ndim, kernel_spec.dim) 266 | elif kernel_spec.kernel_type == 13: 267 | kernel = new LearningCurveKernel(ndim, kernel_spec.dim) 268 | 269 | else: 270 | raise TypeError("Unknown kernel: {0}".format( 271 | kernel_spec.__class__.__name__)) 272 | 273 | kernel.set_vector(pars.data) 274 | return kernel 275 | -------------------------------------------------------------------------------- /george/kernels.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function 4 | 5 | __all__ = [ 6 | "Sum", "Product", "Kernel", 7 | "ConstantKernel", "WhiteKernel", "DotProductKernel", 8 | "RadialKernel", "ExpKernel", "ExpSquaredKernel", 9 | "CosineKernel", "ExpSine2Kernel", 10 | "Matern32Kernel", "Matern52Kernel", 11 | 12 | "PythonKernel", 13 | ] 14 | 15 | import numpy as np 16 | from functools import partial 17 | 18 | from ._kernels import CythonKernel 19 | from .utils import numerical_gradient 20 | 21 | 22 | class Kernel(object): 23 | """ 24 | The abstract kernel type. Every kernel implemented in George should be 25 | a subclass of this object. 26 | 27 | :param pars: 28 | The hyper-parameters of the kernel. 29 | 30 | :param ndim: (optional) 31 | The number of input dimensions of the kernel. (default: ``1``) 32 | 33 | """ 34 | 35 | is_kernel = True 36 | is_radial = False 37 | kernel_type = -1 38 | 39 | # This function deals with weird behavior when performing arithmetic 40 | # operations with numpy scalars. 41 | def __array_wrap__(self, array, context=None): 42 | if context is None: 43 | raise TypeError("Invalid operation") 44 | ufunc, args, _ = context 45 | if ufunc.__name__ == "multiply": 46 | return float(args[0]) * args[1] 47 | elif ufunc.__name__ == "add": 48 | return float(args[0]) + args[1] 49 | raise TypeError("Invalid operation") 50 | __array_priority__ = np.inf 51 | 52 | def __init__(self, *pars, **kwargs): 53 | self.ndim = kwargs.get("ndim", 1) 54 | self.pars = np.array(pars) 55 | self.dirty = True 56 | self._kernel = None 57 | 58 | def __getstate__(self): 59 | odict = self.__dict__.copy() 60 | odict["_kernel"] = None 61 | return odict 62 | 63 | @property 64 | def kernel(self): 65 | if self.dirty or self._kernel is None: 66 | self._kernel = CythonKernel(self) 67 | self.dirty = False 68 | return self._kernel 69 | 70 | def __repr__(self): 71 | return "{0}({1})".format(self.__class__.__name__, 72 | ", ".join(map("{0}".format, 73 | self.pars) + 74 | ["ndim={0}".format(self.ndim)])) 75 | 76 | def lnprior(self): 77 | return 0.0 78 | 79 | @property 80 | def vector(self): 81 | return np.log(self.pars) 82 | 83 | @vector.setter 84 | def vector(self, v): 85 | self.pars = np.exp(v) 86 | 87 | @property 88 | def pars(self): 89 | return self._pars 90 | 91 | @pars.setter 92 | def pars(self, v): 93 | self._pars = np.array(v, dtype=np.float64, order="C") 94 | self.dirty = True 95 | 96 | def __getitem__(self, i): 97 | return self.vector[i] 98 | 99 | def __setitem__(self, i, v): 100 | vec = self.vector 101 | vec[i] = v 102 | self.vector = vec 103 | 104 | def __len__(self): 105 | return len(self.pars) 106 | 107 | def __add__(self, b): 108 | if not hasattr(b, "is_kernel"): 109 | return Sum(ConstantKernel(float(b), ndim=self.ndim), self) 110 | return Sum(self, b) 111 | 112 | def __radd__(self, b): 113 | return self.__add__(b) 114 | 115 | def __mul__(self, b): 116 | if not hasattr(b, "is_kernel"): 117 | return Product(ConstantKernel(float(b), ndim=self.ndim), self) 118 | return Product(self, b) 119 | 120 | def __rmul__(self, b): 121 | return self.__mul__(b) 122 | 123 | def value(self, x1, x2=None): 124 | x1 = np.ascontiguousarray(x1, dtype=np.float64) 125 | if x2 is None: 126 | return self.kernel.value_symmetric(x1) 127 | x2 = np.ascontiguousarray(x2, dtype=np.float64) 128 | return self.kernel.value_general(x1, x2) 129 | 130 | def gradient(self, x1, x2=None): 131 | x1 = np.ascontiguousarray(x1, dtype=np.float64) 132 | if x2 is None: 133 | g = self.kernel.gradient_symmetric(x1) 134 | else: 135 | x2 = np.ascontiguousarray(x2, dtype=np.float64) 136 | g = self.kernel.gradient_general(x1, x2) 137 | return g * self.vector_gradient[None, None, :] 138 | 139 | @property 140 | def vector_gradient(self): 141 | return self.pars 142 | 143 | 144 | class _operator(Kernel): 145 | is_kernel = False 146 | operator_type = -1 147 | 148 | def __init__(self, k1, k2): 149 | if k1.ndim != k2.ndim: 150 | raise ValueError("Dimension mismatch") 151 | self.k1 = k1 152 | self.k2 = k2 153 | self.ndim = k1.ndim 154 | self._dirty = True 155 | self._kernel = None 156 | 157 | def lnprior(self): 158 | return self.k1.lnprior() + self.k2.lnprior() 159 | 160 | @property 161 | def dirty(self): 162 | return self._dirty or self.k1.dirty or self.k2.dirty 163 | 164 | @dirty.setter 165 | def dirty(self, v): 166 | self._dirty = v 167 | self.k1.dirty = False 168 | self.k2.dirty = False 169 | 170 | @property 171 | def pars(self): 172 | return np.append(self.k1.pars, self.k2.pars) 173 | 174 | @pars.setter 175 | def pars(self, v): 176 | self._dirty = True 177 | i = len(self.k1) 178 | self.k1.pars = v[:i] 179 | self.k2.pars = v[i:] 180 | 181 | @property 182 | def vector(self): 183 | return np.append(self.k1.vector, self.k2.vector) 184 | 185 | @vector.setter 186 | def vector(self, v): 187 | self._dirty = True 188 | i = len(self.k1) 189 | self.k1.vector = v[:i] 190 | self.k2.vector = v[i:] 191 | 192 | 193 | class Sum(_operator): 194 | is_kernel = False 195 | operator_type = 0 196 | 197 | def __repr__(self): 198 | return "{0} + {1}".format(self.k1, self.k2) 199 | 200 | 201 | class Product(_operator): 202 | is_kernel = False 203 | operator_type = 1 204 | 205 | def __repr__(self): 206 | return "{0} * {1}".format(self.k1, self.k2) 207 | 208 | 209 | class ConstantKernel(Kernel): 210 | r""" 211 | This kernel returns the constant 212 | 213 | .. math:: 214 | 215 | k(\mathbf{x}_i,\,\mathbf{x}_j) = c 216 | 217 | where :math:`c` is a parameter. 218 | 219 | :param value: 220 | The constant value :math:`c` in the above equation. 221 | 222 | """ 223 | kernel_type = 0 224 | 225 | def __init__(self, value, ndim=1): 226 | super(ConstantKernel, self).__init__(value, ndim=ndim) 227 | 228 | 229 | class WhiteKernel(Kernel): 230 | r""" 231 | This kernel returns constant along the diagonal. 232 | 233 | .. math:: 234 | 235 | k(\mathbf{x}_i,\,\mathbf{x}_j) = c \, \delta_{ij} 236 | 237 | where :math:`c` is the parameter. 238 | 239 | :param value: 240 | The constant value :math:`c` in the above equation. 241 | 242 | """ 243 | kernel_type = 1 244 | 245 | def __init__(self, value, ndim=1): 246 | super(WhiteKernel, self).__init__(value, ndim=ndim) 247 | 248 | 249 | class DotProductKernel(Kernel): 250 | r""" 251 | The dot-product kernel takes the form 252 | 253 | .. math:: 254 | 255 | k(\mathbf{x}_i,\,\mathbf{x}_j) = \mathbf{x}_i^{\mathrm{T}} \cdot 256 | \mathbf{x}_j 257 | 258 | """ 259 | kernel_type = 2 260 | 261 | def __init__(self, ndim=1): 262 | super(DotProductKernel, self).__init__(ndim=ndim) 263 | 264 | 265 | class RadialKernel(Kernel): 266 | r""" 267 | This kernel (and more importantly its subclasses) computes the distance 268 | between two samples in an arbitrary metric and applies a radial function 269 | to this distance. 270 | 271 | :param metric: 272 | The specification of the metric. This can be a ``float``, in which 273 | case the metric is considered isotropic with the variance in each 274 | dimension given by the value of ``metric``. Alternatively, ``metric`` 275 | can be a list of variances for each dimension. In this case, it should 276 | have length ``ndim``. The fully general (not axis-aligned) metric 277 | hasn't been implemented yet but it's on the to do list! 278 | 279 | :param dim: (optional) 280 | If provided, this will apply the kernel in only the specified 281 | dimension. 282 | 283 | """ 284 | is_radial = True 285 | 286 | def __init__(self, metric, ndim=1, dim=-1, extra=[]): 287 | self.isotropic = False 288 | self.axis_aligned = False 289 | try: 290 | float(metric) 291 | 292 | except TypeError: 293 | metric = np.atleast_1d(metric) 294 | if len(metric) == ndim: 295 | # The metric is axis aligned. 296 | self.axis_aligned = True 297 | 298 | else: 299 | raise NotImplementedError("The general metric isn't " 300 | "implemented") 301 | 302 | else: 303 | # If we get here then the kernel is isotropic. 304 | self.isotropic = True 305 | 306 | if dim >= 0: 307 | assert self.isotropic, "A 1-D kernel should also be isotropic" 308 | self.dim = dim 309 | 310 | super(RadialKernel, self).__init__(*(np.append(extra, metric)), 311 | ndim=ndim) 312 | 313 | 314 | class ExpKernel(RadialKernel): 315 | r""" 316 | The exponential kernel is a :class:`RadialKernel` where the value at a 317 | given radius :math:`r^2` is given by: 318 | 319 | .. math:: 320 | 321 | k({r_{ij}}) = \exp \left ( -|r| \right ) 322 | 323 | :param metric: 324 | The custom metric specified as described in the :class:`RadialKernel` 325 | description. 326 | 327 | """ 328 | kernel_type = 3 329 | 330 | 331 | class ExpSquaredKernel(RadialKernel): 332 | r""" 333 | The exponential-squared kernel is a :class:`RadialKernel` where the value 334 | at a given radius :math:`r^2` is given by: 335 | 336 | .. math:: 337 | 338 | k(r^2) = \exp \left ( -\frac{r^2}{2} \right ) 339 | 340 | :param metric: 341 | The custom metric specified as described in the :class:`RadialKernel` 342 | description. 343 | 344 | """ 345 | kernel_type = 4 346 | 347 | 348 | class Matern32Kernel(RadialKernel): 349 | r""" 350 | The Matern-3/2 kernel is a :class:`RadialKernel` where the value at a 351 | given radius :math:`r^2` is given by: 352 | 353 | .. math:: 354 | 355 | k(r^2) = \left( 1+\sqrt{3\,r^2} \right)\, 356 | \exp \left (-\sqrt{3\,r^2} \right ) 357 | 358 | :param metric: 359 | The custom metric specified as described in the :class:`RadialKernel` 360 | description. 361 | 362 | """ 363 | kernel_type = 5 364 | 365 | 366 | class Matern52Kernel(RadialKernel): 367 | r""" 368 | The Matern-5/2 kernel is a :class:`RadialKernel` where the value at a 369 | given radius :math:`r^2` is given by: 370 | 371 | .. math:: 372 | 373 | k(r^2) = \left( 1+\sqrt{5\,r^2} + \frac{5\,r^2}{3} \right)\, 374 | \exp \left (-\sqrt{5\,r^2} \right ) 375 | 376 | :param metric: 377 | The custom metric specified as described in the :class:`RadialKernel` 378 | description. 379 | 380 | """ 381 | kernel_type = 6 382 | 383 | 384 | class RationalQuadraticKernel(RadialKernel): 385 | r""" 386 | TODO: document this kernel. 387 | 388 | """ 389 | kernel_type = 7 390 | 391 | def __init__(self, alpha, metric, ndim=1, **kwargs): 392 | super(RationalQuadraticKernel, self).__init__(metric, extra=[alpha], 393 | ndim=ndim, **kwargs) 394 | 395 | 396 | class CosineKernel(Kernel): 397 | r""" 398 | The cosine kernel is given by: 399 | 400 | .. math:: 401 | 402 | k(\mathbf{x}_i,\,\mathbf{x}_j) = 403 | \cos\left(\frac{2\,\pi}{P}\,\left|x_i-x_j\right| \right) 404 | 405 | where :math:`P` is the period. 406 | 407 | :param period: 408 | The period :math:`P` of the oscillation (in the same units as 409 | :math:`\mathbf{x}`). 410 | 411 | **Note:** 412 | A shortcoming of this kernel is that it currently only accepts a single 413 | period so it's not very applicable to problems with input dimension larger 414 | than one. 415 | 416 | """ 417 | kernel_type = 8 418 | 419 | def __init__(self, period, ndim=1, dim=0): 420 | super(CosineKernel, self).__init__(period, ndim=ndim) 421 | assert dim < self.ndim, "Invalid dimension" 422 | self.dim = dim 423 | 424 | 425 | class ExpSine2Kernel(Kernel): 426 | r""" 427 | The exp-sine-squared kernel is used to model stellar rotation and *might* 428 | be applicable in some other contexts. It is given by the equation: 429 | 430 | .. math:: 431 | 432 | k(\mathbf{x}_i,\,\mathbf{x}_j) = 433 | \exp \left( -\Gamma\,\sin^2\left[ 434 | \frac{\pi}{P}\,\left|x_i-x_j\right| 435 | \right] \right) 436 | 437 | where :math:`\Gamma` is the "scale" of the correlation and :math:`P` is 438 | the period of the oscillation measured in the same units as 439 | :math:`\mathbf{x}`. 440 | 441 | :param gamma: 442 | The scale :math:`\Gamma` of the correlations. 443 | 444 | :param period: 445 | The period :math:`P` of the oscillation (in the same units as 446 | :math:`\mathbf{x}`). 447 | 448 | :param dim: (optional) 449 | The dimension along which this kernel should apply. By default, this 450 | will be the zero-th axis. 451 | 452 | """ 453 | kernel_type = 9 454 | 455 | def __init__(self, gamma, period, ndim=1, dim=0): 456 | super(ExpSine2Kernel, self).__init__(gamma, period, ndim=ndim) 457 | assert dim < self.ndim, "Invalid dimension" 458 | self.dim = dim 459 | 460 | class BayesianLinearRegressionKernel(Kernel): 461 | r""" 462 | A kernel to perform Bayesian linear regression for the basis functions (1, x, x^2,...) 463 | 464 | :param ndim: 465 | Number of input dimensions. 466 | 467 | :param dim: 468 | The dimension along which this kernel should apply. 469 | 470 | :param degree: 471 | Degree of the basis polynoms. The resulting kernel has degree + 1 hyperparameters. 472 | 473 | """ 474 | kernel_type = 10 475 | 476 | def __init__(self, ndim, dim, degree): 477 | super(BayesianLinearRegressionKernel, self).__init__(*([0]*(degree+1)), ndim=ndim) 478 | assert dim < self.ndim, "Invalid dimension" 479 | self.dim = dim 480 | self.degree = degree 481 | 482 | 483 | class LearningCurveKernel(Kernel): 484 | r""" 485 | 486 | 487 | :param ndim: 488 | Number of input dimensions. 489 | 490 | :param dim: 491 | The dimension along which this kernel should apply. 492 | 493 | """ 494 | kernel_type = 13 495 | 496 | def __init__(self, ndim, dim): 497 | super(LearningCurveKernel, self).__init__(*[1, 1], ndim=ndim) 498 | assert dim < self.ndim, "Invalid dimension" 499 | self.dim = dim 500 | 501 | class TaskKernel(Kernel): 502 | r""" 503 | A kernel for discrete variables, labeled tasks. The Kernel's parameters 504 | correspond to the kernel values. Note that only the offdiagonal elements 505 | are stored internally. Check the source code to see how the vector elements 506 | map to the Kernel's Gram Matrix elements. 507 | 508 | :param ndim: 509 | Number of input dimensions. 510 | 511 | :param dim: 512 | The dimension along which this kernel should apply. 513 | 514 | :param num_tasks: 515 | Number of tasks (represented by 0, 1, ... num_tasks-1) 516 | 517 | """ 518 | kernel_type = 11 519 | 520 | def __init__(self, ndim, dim, num_tasks): 521 | super(TaskKernel, self).__init__(*([0]*(num_tasks * (num_tasks+1) // 2 )), ndim=ndim) 522 | assert dim < self.ndim, "Invalid dimension" 523 | self.dim = dim 524 | self.num_tasks = num_tasks 525 | 526 | # overload the vector to make them live on the linear scale 527 | @property 528 | def vector(self): 529 | return self.pars 530 | 531 | @vector.setter 532 | def vector(self, v): 533 | self.pars = v 534 | 535 | class HeteroscedasticNoisePolynomialKernel(Kernel): 536 | r""" 537 | A kernel to model heteroscedastic noise depending on one dimension. 538 | 539 | This kernel adds 540 | 541 | K_{HSNP} = c ( 1-s)^{\alpha} 542 | 543 | to the diagonal of the kernel's Gram matrix where $s$ is the input value 544 | in the specified dimension (see below). The parameers $c$ and $\alpha$ are 545 | the kernel's hyperparameter. 546 | 547 | :param ndim: 548 | Number of input dimensions. 549 | 550 | :param dim: 551 | The dimension along which this kernel should apply. 552 | 553 | """ 554 | kernel_type = 12 555 | 556 | def __init__(self, ndim, dim): 557 | super(HeteroscedasticNoisePolynomialKernel, self).__init__(*([1]*(2)), ndim=ndim) 558 | assert dim < self.ndim, "Invalid dimension" 559 | self.dim = dim 560 | 561 | 562 | class PythonKernel(Kernel): 563 | r""" 564 | A custom kernel evaluated in Python. The gradient is optionally evaluated 565 | numerically. For big problems, this type of kernel will probably be 566 | unbearably slow because each evaluation is done point-wise. Unfortunately, 567 | this is the only way to implement custom kernels without re-compiling 568 | George. Hopefully we can solve this in the future! 569 | 570 | :param f: 571 | A callable that evaluates the kernel function given arguments 572 | ``(x1, x2, p)`` where ``x1`` and ``x2`` are numpy array defining the 573 | coordinates of the samples and ``p`` is the numpy array giving the 574 | current settings of the parameters. 575 | 576 | :param g: (optional) 577 | A function with the same calling parameters as ``f`` but it should 578 | return the numpy array with the gradient of the kernel function. If 579 | this function isn't given then the gradient is evaluated using 580 | centered finite difference. 581 | 582 | :param pars: (optional) 583 | The initial list of parameter values. If this isn't provided then the 584 | kernel is assumed to have no parameters. 585 | 586 | :param dx: (optional) 587 | The step size used for the gradient computation when using finite 588 | difference. 589 | 590 | """ 591 | 592 | kernel_type = -2 593 | 594 | def __init__(self, f, g=None, pars=(), dx=1.234e-6, ndim=1): 595 | super(PythonKernel, self).__init__(*pars, ndim=ndim) 596 | self.size = len(self.pars) 597 | self.f = f 598 | self.g = self._wrap_grad(f, g, dx=dx) 599 | 600 | def _wrap_grad(self, f, g, dx=1.234e-6): 601 | if g is not None: 602 | grad = g 603 | else: 604 | def grad(x1, x2, p): 605 | g = numerical_gradient(partial(f, x1, x2), p, dx=dx) 606 | return g 607 | return grad 608 | -------------------------------------------------------------------------------- /george/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ["test_kernels", "test_solvers", "test_tutorial", "test_gp"] 4 | 5 | from . import test_kernels, test_solvers, test_tutorial, test_gp 6 | -------------------------------------------------------------------------------- /george/testing/test_gp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function 4 | 5 | __all__ = [ 6 | "test_gradient", "test_prediction", "test_repeated_prediction_cache" 7 | ] 8 | 9 | import numpy as np 10 | 11 | from .. import kernels, GP, BasicSolver, HODLRSolver 12 | 13 | 14 | def _test_gradient(seed=123, N=100, ndim=3, eps=1.32e-4, solver=BasicSolver): 15 | np.random.seed(seed) 16 | 17 | # Set up the solver. 18 | kernel = (0.1 * kernels.ExpSquaredKernel(0.5, ndim) 19 | + kernels.WhiteKernel(1e-8, ndim)) 20 | gp = GP(kernel, solver=solver) 21 | 22 | # Sample some data. 23 | x = np.random.rand(N, ndim) 24 | y = gp.sample(x) 25 | gp.compute(x) 26 | 27 | # Compute the initial gradient. 28 | grad0 = gp.grad_lnlikelihood(y) 29 | 30 | for i in range(len(kernel)): 31 | # Compute the centered finite difference approximation to the gradient. 32 | kernel[i] += eps 33 | lp = gp.lnlikelihood(y) 34 | kernel[i] -= 2*eps 35 | lm = gp.lnlikelihood(y) 36 | kernel[i] += eps 37 | grad = 0.5 * (lp - lm) / eps 38 | assert np.abs(grad - grad0[i]) < 5 * eps, \ 39 | "Gradient computation failed in dimension {0} ({1})\n{2}" \ 40 | .format(i, solver.__name__, np.abs(grad - grad0[i])) 41 | 42 | 43 | def test_gradient(**kwargs): 44 | _test_gradient(solver=BasicSolver, **kwargs) 45 | _test_gradient(solver=HODLRSolver, **kwargs) 46 | 47 | 48 | def _test_prediction(solver=BasicSolver): 49 | """Basic sanity checks for GP regression.""" 50 | 51 | kernel = kernels.ExpSquaredKernel(1.0) 52 | gp = GP(kernel, solver=solver) 53 | 54 | x = np.array((-1, 0, 1)) 55 | gp.compute(x) 56 | 57 | y = x/x.std() 58 | mu, cov = gp.predict(y, x) 59 | 60 | assert np.allclose(y, mu), \ 61 | "GP must predict noise-free training data exactly ({0}).\n({1})" \ 62 | .format(solver.__name__, y - mu) 63 | 64 | assert np.all(cov > -1e-15), \ 65 | "Covariance matrix must be nonnegative ({0}).\n{1}" \ 66 | .format(solver.__name__, cov) 67 | 68 | var = np.diag(cov) 69 | assert np.allclose(var, 0), \ 70 | "Variance must vanish at noise-free training points ({0}).\n{1}" \ 71 | .format(solver.__name__, var) 72 | 73 | t = np.array((-.5, .3, 1.2)) 74 | var = np.diag(gp.predict(y, t)[1]) 75 | assert np.all(var > 0), \ 76 | "Variance must be positive away from training points ({0}).\n{1}" \ 77 | .format(solver.__name__, var) 78 | 79 | 80 | def test_prediction(**kwargs): 81 | _test_prediction(solver=BasicSolver, **kwargs) 82 | _test_prediction(solver=HODLRSolver, **kwargs) 83 | 84 | 85 | def test_repeated_prediction_cache(): 86 | kernel = kernels.ExpSquaredKernel(1.0) 87 | gp = GP(kernel) 88 | 89 | x = np.array((-1, 0, 1)) 90 | gp.compute(x) 91 | 92 | t = np.array((-.5, .3, 1.2)) 93 | 94 | y = x/x.std() 95 | mu0, mu1 = (gp.predict(y, t, mean_only=True) for _ in range(2)) 96 | assert np.array_equal(mu0, mu1), \ 97 | "Identical training data must give identical predictions " \ 98 | "(problem with GP cache)." 99 | 100 | y2 = 2*y 101 | mu2 = gp.predict(y2, t, mean_only=True) 102 | assert not np.array_equal(mu0, mu2), \ 103 | "Different training data must give different predictions " \ 104 | "(problem with GP cache)." 105 | 106 | a0 = gp._alpha 107 | gp.kernel[0] += 0.1 108 | gp.recompute() 109 | gp._compute_alpha(y2) 110 | a1 = gp._alpha 111 | assert not np.allclose(a0, a1), \ 112 | "Different kernel parameters must give different alphas " \ 113 | "(problem with GP cache)." 114 | -------------------------------------------------------------------------------- /george/testing/test_kernels.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function, absolute_import 4 | 5 | __all__ = [ 6 | "test_dtype", 7 | 8 | "test_constant", "test_white", "test_dot_prod", 9 | 10 | "test_exp", "test_exp_squared", "test_matern32", 11 | "test_matern52", "test_rational_quadratic", 12 | 13 | "test_cosine", "test_exp_sine2", 14 | 15 | "test_combine", 16 | 17 | "test_custom", "test_custom_numerical", 18 | ] 19 | 20 | import numpy as np 21 | 22 | from .. import kernels 23 | from ..gp import GP 24 | 25 | 26 | def test_dtype(seed=123): 27 | np.random.seed(seed) 28 | kernel = 0.1 * kernels.ExpSquaredKernel(1.5) 29 | kernel.pars = [1, 2] 30 | gp = GP(kernel) 31 | x = np.random.rand(100) 32 | gp.compute(x, 1e-2) 33 | 34 | 35 | def do_kernel_t(kernel, N=20, seed=123, eps=1.32e-7): 36 | """ 37 | Test that both the Python and C++ kernels return the same matrices. 38 | 39 | """ 40 | np.random.seed(seed) 41 | t1 = np.random.randn(N, kernel.ndim) 42 | 43 | # Check the symmetric gradients. 44 | g1 = kernel.gradient(t1) 45 | for i in range(len(kernel)): 46 | # Compute the centered finite difference approximation to the gradient. 47 | kernel[i] += eps 48 | kp = kernel.value(t1) 49 | kernel[i] -= 2*eps 50 | km = kernel.value(t1) 51 | kernel[i] += eps 52 | g0 = 0.5 * (kp - km) / eps 53 | assert np.allclose(g1[:, :, i], g0), \ 54 | "Gradient computation failed in dimension {0}".format(i) 55 | 56 | 57 | # 58 | # BASIC KERNELS 59 | # 60 | 61 | def test_custom(): 62 | def f(x1, x2, p): 63 | return np.exp(-0.5 * np.dot(x1, x2) / p[0]) 64 | 65 | def g(x1, x2, p): 66 | arg = 0.5 * np.dot(x1, x2) / p[0] 67 | return np.exp(-arg) * arg / p[0] 68 | 69 | def wrong_g(x1, x2, p): 70 | arg = 0.5 * np.dot(x1, x2) / p[0] 71 | return 10 * np.exp(-arg) * arg / p[0] 72 | 73 | do_kernel_t(kernels.PythonKernel(f, g, pars=[0.5])) 74 | do_kernel_t(kernels.PythonKernel(f, g, pars=[0.1])) 75 | 76 | try: 77 | do_kernel_t(kernels.PythonKernel(f, wrong_g, pars=[0.5])) 78 | except AssertionError: 79 | pass 80 | else: 81 | assert False, "This test should fail" 82 | 83 | 84 | def test_custom_numerical(): 85 | def f(x1, x2, p): 86 | return np.exp(-0.5 * np.dot(x1, x2) / p[0]) 87 | do_kernel_t(kernels.PythonKernel(f, pars=[0.5])) 88 | do_kernel_t(kernels.PythonKernel(f, pars=[10.0])) 89 | 90 | 91 | def test_constant(): 92 | do_kernel_t(kernels.ConstantKernel(0.1)) 93 | do_kernel_t(kernels.ConstantKernel(10.0, 2)) 94 | do_kernel_t(kernels.ConstantKernel(5.0, 5)) 95 | 96 | 97 | def test_white(): 98 | do_kernel_t(kernels.WhiteKernel(0.1)) 99 | do_kernel_t(kernels.WhiteKernel(10.0, 2)) 100 | do_kernel_t(kernels.WhiteKernel(5.0, 5)) 101 | 102 | 103 | def test_dot_prod(): 104 | do_kernel_t(kernels.DotProductKernel()) 105 | do_kernel_t(kernels.DotProductKernel(2)) 106 | do_kernel_t(kernels.DotProductKernel(5)) 107 | 108 | 109 | # 110 | # COVARIANCE KERNELS 111 | # 112 | 113 | def do_cov_t(kernel_type, extra=None): 114 | def build_kernel(metric, **kwargs): 115 | if extra is None: 116 | return kernel_type(metric, **kwargs) 117 | return kernel_type(*(extra + [metric]), **kwargs) 118 | 119 | kernel = build_kernel(0.1) 120 | do_kernel_t(kernel) 121 | 122 | kernel = build_kernel(1.0) 123 | do_kernel_t(kernel) 124 | 125 | kernel = build_kernel(10.0) 126 | do_kernel_t(kernel) 127 | 128 | kernel = build_kernel([1.0, 0.1, 10.0], ndim=3) 129 | do_kernel_t(kernel) 130 | 131 | kernel = build_kernel(1.0, ndim=3) 132 | do_kernel_t(kernel) 133 | 134 | try: 135 | kernel = build_kernel([1.0, 0.1, 10.0, 500], ndim=3) 136 | except: 137 | pass 138 | else: 139 | assert False, "This test should fail" 140 | 141 | kernel = build_kernel(1.0, ndim=3, dim=2) 142 | do_kernel_t(kernel) 143 | 144 | 145 | def test_exp(): 146 | do_cov_t(kernels.ExpKernel) 147 | 148 | 149 | def test_exp_squared(): 150 | do_cov_t(kernels.ExpSquaredKernel) 151 | 152 | 153 | def test_matern32(): 154 | do_cov_t(kernels.Matern32Kernel) 155 | 156 | 157 | def test_matern52(): 158 | do_cov_t(kernels.Matern52Kernel) 159 | 160 | 161 | def test_rational_quadratic(): 162 | do_cov_t(kernels.RationalQuadraticKernel, [1.0]) 163 | do_cov_t(kernels.RationalQuadraticKernel, [0.1]) 164 | do_cov_t(kernels.RationalQuadraticKernel, [10.0]) 165 | 166 | 167 | # 168 | # PERIODIC KERNELS 169 | # 170 | 171 | def test_cosine(): 172 | do_kernel_t(kernels.CosineKernel(1.0)) 173 | do_kernel_t(kernels.CosineKernel(0.5, ndim=2)) 174 | do_kernel_t(kernels.CosineKernel(0.5, ndim=2, dim=1)) 175 | do_kernel_t(kernels.CosineKernel(0.75, ndim=5, dim=3)) 176 | 177 | 178 | def test_exp_sine2(): 179 | do_kernel_t(kernels.ExpSine2Kernel(0.4, 1.0)) 180 | do_kernel_t(kernels.ExpSine2Kernel(12., 0.5, ndim=2)) 181 | do_kernel_t(kernels.ExpSine2Kernel(17., 0.5, ndim=2, dim=1)) 182 | do_kernel_t(kernels.ExpSine2Kernel(13.7, 0.75, ndim=5, dim=3)) 183 | 184 | 185 | # 186 | # COMBINING KERNELS 187 | # 188 | 189 | def test_combine(): 190 | do_kernel_t(12 * kernels.ExpSine2Kernel(0.4, 1.0, ndim=5) + 0.1) 191 | do_kernel_t(12 * kernels.ExpSquaredKernel(0.4, ndim=3) + 0.1) 192 | -------------------------------------------------------------------------------- /george/testing/test_pickle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function, absolute_import 4 | 5 | __all__ = [ 6 | "test_basic_pickle", "test_hodlr_pickle", 7 | ] 8 | 9 | import pickle 10 | import numpy as np 11 | 12 | from .. import GP, kernels, BasicSolver, HODLRSolver 13 | 14 | 15 | def _fake_compute(arg, *args, **kwargs): 16 | assert 0, "Unpickled GP shouldn't need to be computed" 17 | 18 | 19 | def _test_pickle(solver, success, N=50, seed=123): 20 | np.random.seed(seed) 21 | kernel = 0.1 * kernels.ExpSquaredKernel(1.5) 22 | kernel.pars = [1, 2] 23 | gp = GP(kernel, solver=solver) 24 | x = np.random.rand(100) 25 | gp.compute(x, 1e-2) 26 | 27 | s = pickle.dumps(gp, -1) 28 | gp = pickle.loads(s) 29 | if success: 30 | gp.compute = _fake_compute 31 | gp.lnlikelihood(np.sin(x)) 32 | 33 | 34 | def test_basic_pickle(**kwargs): 35 | _test_pickle(BasicSolver, True, **kwargs) 36 | 37 | 38 | def test_hodlr_pickle(**kwargs): 39 | _test_pickle(HODLRSolver, False, **kwargs) 40 | -------------------------------------------------------------------------------- /george/testing/test_solvers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function 4 | 5 | __all__ = ["test_basic_solver"] 6 | 7 | import numpy as np 8 | 9 | from .. import kernels 10 | from ..basic import BasicSolver 11 | from ..hodlr import HODLRSolver 12 | 13 | 14 | def _test_solver(Solver, N=100, seed=1234): 15 | # Set up the solver. 16 | kernel = 1e-4 * kernels.ExpSquaredKernel(1.0) 17 | solver = Solver(kernel) 18 | 19 | # Sample some data. 20 | np.random.seed(seed) 21 | x = np.random.randn(N, kernel.ndim) 22 | yerr = 1e-3 * np.ones(N) 23 | solver.compute(x, yerr) 24 | 25 | # Build the matrix. 26 | K = kernel.value(x) 27 | K[np.diag_indices_from(K)] += yerr ** 2 28 | 29 | # Check the determinant. 30 | sgn, lndet = np.linalg.slogdet(K) 31 | assert sgn == 1.0, "Invalid determinant" 32 | assert np.allclose(solver.log_determinant, lndet), "Incorrect determinant" 33 | 34 | # Check the inverse. 35 | assert np.allclose(solver.apply_inverse(K), np.eye(N)), "Incorrect inverse" 36 | 37 | 38 | def test_basic_solver(**kwargs): 39 | _test_solver(BasicSolver, **kwargs) 40 | 41 | 42 | def test_hodlr_solver(**kwargs): 43 | _test_solver(HODLRSolver, **kwargs) 44 | -------------------------------------------------------------------------------- /george/testing/test_tutorial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function, absolute_import 4 | 5 | __all__ = ["test_tutorial"] 6 | 7 | import numpy as np 8 | 9 | from .. import kernels 10 | from ..gp import GP 11 | from ..basic import BasicSolver 12 | from ..hodlr import HODLRSolver 13 | 14 | 15 | def test_tutorial(): 16 | def model(params, t): 17 | _, _, amp, loc, sig2 = params 18 | return amp * np.exp(-0.5 * (t - loc) ** 2 / sig2) 19 | 20 | def lnlike(p, t, y, yerr, solver=BasicSolver): 21 | a, tau = np.exp(p[:2]) 22 | gp = GP(a * kernels.Matern32Kernel(tau) + 0.001, solver=solver) 23 | gp.compute(t, yerr) 24 | return gp.lnlikelihood(y - model(p, t)) 25 | 26 | def lnprior(p): 27 | lna, lntau, amp, loc, sig2 = p 28 | if (-5 < lna < 5 and -5 < lntau < 5 and -10 < amp < 10 and 29 | -5 < loc < 5 and 0 < sig2 < 3): 30 | return 0.0 31 | return -np.inf 32 | 33 | def lnprob(p, x, y, yerr, **kwargs): 34 | lp = lnprior(p) 35 | return lp + lnlike(p, x, y, yerr, **kwargs) \ 36 | if np.isfinite(lp) else -np.inf 37 | 38 | np.random.seed(1234) 39 | x = np.sort(np.random.rand(50)) 40 | yerr = 0.05 + 0.01 * np.random.rand(len(x)) 41 | y = np.sin(x) + yerr * np.random.randn(len(x)) 42 | p = [0, 0, -1.0, 0.1, 0.4] 43 | assert np.isfinite(lnprob(p, x, y, yerr)), "Incorrect result" 44 | assert np.allclose(lnprob(p, x, y, yerr), 45 | lnprob(p, x, y, yerr, solver=HODLRSolver)), \ 46 | "Inconsistent results" 47 | -------------------------------------------------------------------------------- /george/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division, print_function 4 | 5 | __all__ = ["multivariate_gaussian_samples", "nd_sort_samples"] 6 | 7 | import numpy as np 8 | from scipy.spatial import cKDTree 9 | 10 | 11 | def multivariate_gaussian_samples(matrix, N, mean=None): 12 | """ 13 | Generate samples from a multidimensional Gaussian with a given covariance. 14 | 15 | :param matrix: ``(k, k)`` 16 | The covariance matrix. 17 | 18 | :param N: 19 | The number of samples to generate. 20 | 21 | :param mean: ``(k,)`` (optional) 22 | The mean of the Gaussian. Assumed to be zero if not given. 23 | 24 | :returns samples: ``(k,)`` or ``(N, k)`` 25 | Samples from the given multivariate normal. 26 | 27 | """ 28 | if mean is None: 29 | mean = np.zeros(len(matrix)) 30 | samples = np.random.multivariate_normal(mean, matrix, N) 31 | if N == 1: 32 | return samples[0] 33 | return samples 34 | 35 | 36 | def nd_sort_samples(samples): 37 | """ 38 | Sort an N-dimensional list of samples using a KDTree. 39 | 40 | :param samples: ``(nsamples, ndim)`` 41 | The list of samples. This must be a two-dimensional array. 42 | 43 | :returns i: ``(nsamples,)`` 44 | The list of indices into the original array that return the correctly 45 | sorted version. 46 | 47 | """ 48 | # Check the shape of the sample list. 49 | assert len(samples.shape) == 2 50 | 51 | # Build a KD-tree on the samples. 52 | tree = cKDTree(samples) 53 | 54 | # Compute the distances. 55 | d, i = tree.query(samples[0], k=len(samples)) 56 | return i 57 | 58 | 59 | def numerical_gradient(f, x, dx=1.234e-6): 60 | g = np.empty_like(x, dtype=float) 61 | for i in range(len(g)): 62 | x[i] += dx 63 | fp = f(x) 64 | x[i] -= 2*dx 65 | fm = f(x) 66 | x[i] += dx 67 | g[i] = 0.5 * (fp - fm) / dx 68 | return g 69 | -------------------------------------------------------------------------------- /george_extension.py: -------------------------------------------------------------------------------- 1 | import george 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import emcee 5 | 6 | 7 | 8 | ndims = 5 # total number of dimensions 9 | which_dimension=3 10 | degree=2 # if the degree is to low, it gets numerically unstable as many configurations have essentially zero probability 11 | 12 | sigma_noise = 0.1 13 | 14 | 15 | kernel = george.kernels.BayesianLinearRegressionKernel(ndims,which_dimension,degree) 16 | gp = george.GP(kernel) 17 | 18 | 19 | 20 | def f(x): 21 | return(x**3 - 20*x**2 + 10*x+0.2) 22 | 23 | 24 | 25 | # just to check that you can form product and sum kernels 26 | kernel2 = george.kernels.ExpSquaredKernel(0.5,ndims) * kernel 27 | kernel3 = george.kernels.ExpSquaredKernel(0.5,ndims) + kernel 28 | 29 | num_points = 10 30 | X = np.array([np.sort(np.random.rand(num_points)*0.5) for d in range(ndims)]).T 31 | Y = np.sort(np.random.rand(num_points))[::-1] 32 | Y = f(X[:,which_dimension]) + sigma_noise**2*np.random.randn(num_points) 33 | 34 | # kernel parameter live on a log scale 35 | gp.kernel[:] = np.array([0.1]*(degree+1)) 36 | # initialize kernel computation 37 | gp.compute(X, sigma_noise) 38 | 39 | 40 | plt.scatter(X[:,which_dimension],Y) 41 | plt.show(block=False) 42 | 43 | 44 | 45 | def lnprob(p): 46 | # Update the kernel and compute the lnlikelihood. 47 | if np.any(p>10) or np.any (p<-10): 48 | return(-np.inf) 49 | logprior = 0 50 | gp.kernel.pars = np.exp(p) 51 | lh = logprior + gp.lnlikelihood(Y, quiet=True) 52 | print(p, lh) 53 | return(lh) 54 | 55 | 56 | # Set up the sampler. 57 | nwalkers = 2*(degree+1) 58 | sampler = emcee.EnsembleSampler(nwalkers, degree+1, lnprob) 59 | 60 | 61 | # if you want to do the kernel with (1, (1-x)^2) as basis functions, 62 | # set degree = 2 and initialize the walkers such that the parameters obey 63 | # gp.kernel[1] = -2 gp.kernel[2] 64 | p0 = [5*np.random.rand(degree+1)-5 for i in range(nwalkers)] 65 | 66 | 67 | 68 | p0, _, _ = sampler.run_mcmc(p0, 200) 69 | chain, _, _=sampler.run_mcmc(p0, 50) 70 | 71 | 72 | t = np.array([np.linspace(0,1,20) for d in range(ndims)]).T 73 | mu, cov = gp.predict(Y, t) 74 | std = np.sqrt(np.diag(cov)) 75 | 76 | 77 | plt.fill_between(t[:,which_dimension], mu-std, mu+std, alpha=0.3) 78 | plt.scatter(X[:,which_dimension],Y) 79 | plt.plot(t[:,which_dimension],mu) 80 | 81 | 82 | for confs in sampler.chain[:,::10]: 83 | for conf in confs: 84 | gp.kernel.pars = np.exp(conf) 85 | gp.compute(X,sigma_noise) 86 | mu, cov = gp.predict(Y, t) 87 | plt.plot(t[:,which_dimension],mu, alpha=0.3) 88 | 89 | plt.plot(t[:,which_dimension], f(t[:,which_dimension]), color='red') 90 | 91 | plt.show() 92 | -------------------------------------------------------------------------------- /images/cosine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/images/cosine.png -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/images/demo.png -------------------------------------------------------------------------------- /images/exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/images/exp.png -------------------------------------------------------------------------------- /images/expsquared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automl/george/14c4c89906c770528dad2b80973aab0320141fe5/images/expsquared.png -------------------------------------------------------------------------------- /include/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef _GEORGE_CONSTANTS_H_ 2 | #define _GEORGE_CONSTANTS_H_ 3 | 4 | namespace george { 5 | 6 | // 7 | // Error values. 8 | // 9 | const int SOLVER_OK = 0; 10 | const int SETUP_FAILURE = 1; 11 | const int DIMENSION_MISMATCH = 2; 12 | const int CHOLMOD_ERROR = 3; 13 | const int MEMORY_ERROR = 4; 14 | const int USAGE_ERROR = 5; 15 | 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/george.h: -------------------------------------------------------------------------------- 1 | #ifndef _GEORGE_H_ 2 | #define _GEORGE_H_ 3 | 4 | #include "kernels.h" 5 | #include "constants.h" 6 | #include "solver.h" 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /include/kernels.h: -------------------------------------------------------------------------------- 1 | #ifndef _GEORGE_KERNELS_H_ 2 | #define _GEORGE_KERNELS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using std::vector; 11 | 12 | namespace george { 13 | namespace kernels { 14 | 15 | 16 | class Kernel { 17 | public: 18 | Kernel (const unsigned int ndim) : ndim_(ndim) {}; 19 | virtual ~Kernel () {}; 20 | virtual double value (const double* x1, const double* x2) const { 21 | return 0.0; 22 | }; 23 | virtual void gradient (const double* x1, const double* x2, double* grad) const { 24 | unsigned int i; 25 | for (i = 0; i < this->size(); ++i) grad[i] = 0.0; 26 | }; 27 | 28 | // Input dimension. 29 | void set_ndim (const unsigned int ndim) { ndim_ = ndim; }; 30 | unsigned int get_ndim () const { return ndim_; }; 31 | 32 | // Parameter vector spec. 33 | virtual unsigned int size () const { return 0; } 34 | virtual void set_vector (const double* vector) { 35 | int i, n = this->size(); 36 | for (i = 0; i < n; ++i) this->set_parameter(i, vector[i]); 37 | }; 38 | virtual void set_parameter (const unsigned int i, const double value) {}; 39 | virtual double get_parameter (const unsigned int i) const { return 0.0; }; 40 | 41 | protected: 42 | unsigned int ndim_; 43 | }; 44 | 45 | 46 | class CustomKernel : public Kernel { 47 | public: 48 | CustomKernel (const unsigned int ndim, const unsigned int size, void* meta, 49 | double (*f) (const double* pars, const unsigned int size, 50 | void* meta, 51 | const double* x1, const double* x2, 52 | const unsigned int ndim), 53 | void (*g) (const double* pars, const unsigned int size, 54 | void* meta, 55 | const double* x1, const double* x2, 56 | const unsigned int ndim, double* grad)) 57 | : Kernel(ndim), size_(size), meta_(meta), f_(f), g_(g) 58 | { 59 | parameters_ = new double[size]; 60 | }; 61 | ~CustomKernel () { 62 | delete parameters_; 63 | }; 64 | 65 | // Call the external functions. 66 | double value (const double* x1, const double* x2) const { 67 | return f_(parameters_, size_, meta_, x1, x2, this->get_ndim()); 68 | }; 69 | void gradient (const double* x1, const double* x2, double* grad) const { 70 | g_(parameters_, size_, meta_, x1, x2, this->get_ndim(), grad); 71 | }; 72 | 73 | // Parameters. 74 | unsigned int size () const { return size_; } 75 | void set_parameter (const unsigned int i, const double value) { 76 | parameters_[i] = value; 77 | }; 78 | double get_parameter (const unsigned int i) const { 79 | return parameters_[i]; 80 | }; 81 | 82 | protected: 83 | double* parameters_; 84 | unsigned int ndim_, size_; 85 | 86 | // Metadata needed for this function. 87 | void* meta_; 88 | 89 | // The function and gradient pointers. 90 | double (*f_) (const double* pars, const unsigned int size, void* meta, 91 | const double* x1, const double* x2, 92 | const unsigned int ndim); 93 | void (*g_) (const double* pars, const unsigned int size, void* meta, 94 | const double* x1, const double* x2, 95 | const unsigned int ndim, double* grad); 96 | }; 97 | 98 | 99 | // 100 | // OPERATORS 101 | // 102 | 103 | class Operator : public Kernel { 104 | public: 105 | Operator (const unsigned int ndim, Kernel* k1, Kernel* k2) 106 | : Kernel(ndim), kernel1_(k1), kernel2_(k2) {}; 107 | ~Operator () { 108 | delete kernel1_; 109 | delete kernel2_; 110 | }; 111 | Kernel* get_kernel1 () const { return kernel1_; }; 112 | Kernel* get_kernel2 () const { return kernel2_; }; 113 | 114 | // Parameter vector spec. 115 | unsigned int size () const { return kernel1_->size() + kernel2_->size(); }; 116 | void set_parameter (const unsigned int i, const double value) { 117 | unsigned int n = kernel1_->size(); 118 | if (i < n) kernel1_->set_parameter(i, value); 119 | else kernel2_->set_parameter(i-n, value); 120 | }; 121 | double get_parameter (const unsigned int i) const { 122 | unsigned int n = kernel1_->size(); 123 | if (i < n) return kernel1_->get_parameter(i); 124 | return kernel2_->get_parameter(i-n); 125 | }; 126 | 127 | protected: 128 | Kernel* kernel1_, * kernel2_; 129 | }; 130 | 131 | class Sum : public Operator { 132 | public: 133 | Sum (const unsigned int ndim, Kernel* k1, Kernel* k2) 134 | : Operator(ndim, k1, k2) {}; 135 | 136 | double value (const double* x1, const double* x2) const { 137 | return this->kernel1_->value(x1, x2) + this->kernel2_->value(x1, x2); 138 | }; 139 | 140 | void gradient (const double* x1, const double* x2, double* grad) const { 141 | unsigned int n = this->kernel1_->size(); 142 | this->kernel1_->gradient(x1, x2, grad); 143 | this->kernel2_->gradient(x1, x2, &(grad[n])); 144 | }; 145 | }; 146 | 147 | class Product : public Operator { 148 | public: 149 | Product (const unsigned int ndim, Kernel* k1, Kernel* k2) 150 | : Operator(ndim, k1, k2) {}; 151 | 152 | double value (const double* x1, const double* x2) const { 153 | return this->kernel1_->value(x1, x2) * this->kernel2_->value(x1, x2); 154 | }; 155 | 156 | void gradient (const double* x1, const double* x2, double* grad) const { 157 | unsigned int i, n1 = this->kernel1_->size(), n2 = this->size(); 158 | 159 | this->kernel1_->gradient(x1, x2, grad); 160 | this->kernel2_->gradient(x1, x2, &(grad[n1])); 161 | 162 | double k1 = this->kernel1_->value(x1, x2), 163 | k2 = this->kernel2_->value(x1, x2); 164 | for (i = 0; i < n1; ++i) grad[i] *= k2; 165 | for (i = n1; i < n2; ++i) grad[i] *= k1; 166 | }; 167 | }; 168 | 169 | 170 | // 171 | // BASIC KERNELS 172 | // 173 | 174 | class ConstantKernel : public Kernel { 175 | public: 176 | ConstantKernel (const unsigned int ndim) : Kernel(ndim) {}; 177 | ConstantKernel (const unsigned int ndim, const double value) 178 | : Kernel(ndim), value_(value) {}; 179 | 180 | double value (const double* x1, const double* x2) const { 181 | return value_; 182 | }; 183 | void gradient (const double* x1, const double* x2, double* grad) const { 184 | grad[0] = 1.0; 185 | }; 186 | 187 | unsigned int size () const { return 1; } 188 | void set_parameter (const unsigned int i, const double value) { 189 | value_ = value; 190 | }; 191 | double get_parameter (const unsigned int i) const { return value_; }; 192 | 193 | private: 194 | double value_; 195 | }; 196 | 197 | class WhiteKernel : public Kernel { 198 | public: 199 | WhiteKernel (const unsigned int ndim) : Kernel(ndim) {}; 200 | 201 | double _switch (const double* x1, const double* x2) const { 202 | unsigned int i, n = this->get_ndim(); 203 | double d, r2 = 0.0; 204 | for (i = 0; i < n; ++i) { 205 | d = x1[i] - x2[i]; 206 | r2 += d * d; 207 | } 208 | if (r2 < DBL_EPSILON) return 1.0; 209 | return 0.0; 210 | }; 211 | 212 | double value (const double* x1, const double* x2) const { 213 | return value_ * _switch(x1, x2); 214 | }; 215 | 216 | void gradient (const double* x1, const double* x2, double* grad) const { 217 | grad[0] = _switch(x1, x2); 218 | }; 219 | 220 | unsigned int size () const { return 1; } 221 | void set_parameter (const unsigned int i, const double value) { 222 | value_ = value; 223 | }; 224 | double get_parameter (const unsigned int i) const { return value_; }; 225 | 226 | private: 227 | double value_; 228 | }; 229 | 230 | class DotProductKernel : public Kernel { 231 | public: 232 | DotProductKernel (const unsigned int ndim) : Kernel(ndim) {}; 233 | 234 | double value (const double* x1, const double* x2) const { 235 | unsigned int i, ndim = this->get_ndim(); 236 | double val = 0.0; 237 | for (i = 0; i < ndim; ++i) val += x1[i] * x2[i]; 238 | return val; 239 | }; 240 | void gradient (const double* x1, const double* x2, double* grad) const {}; 241 | unsigned int size () const { return 0; } 242 | }; 243 | 244 | class BayesianLinearRegressionKernel : public Kernel{ 245 | public: 246 | BayesianLinearRegressionKernel( const unsigned int ndim, const unsigned int dim, const unsigned int degree): Kernel(ndim), dim_(dim), vector_(degree+1) {}; 247 | 248 | double value (const double* x1, const double *x2) const{ 249 | double ret = vector_[0]; 250 | for (unsigned int i=1; i vector_; 269 | }; 270 | 271 | 272 | class LearningCurveKernel : public Kernel{ 273 | public: 274 | LearningCurveKernel( const unsigned int ndim, const unsigned int dim): Kernel(ndim), dim_(dim), vector_(2) {}; 275 | 276 | double value (const double* x1, const double *x2) const{ 277 | double alpha = vector_[0]; 278 | double beta = vector_[1]; 279 | double ret = pow(beta, alpha) / pow(x1[dim_] + x2[dim_] + beta, alpha); 280 | return(ret); 281 | }; 282 | 283 | // Parameter vector spec. 284 | unsigned int size () const { return vector_.size(); }; 285 | void set_parameter (const unsigned int i, const double value) { 286 | vector_[i] = value; 287 | }; 288 | double get_parameter (const unsigned int i) const { 289 | return vector_[i]; 290 | }; 291 | 292 | 293 | private: 294 | unsigned int dim_; 295 | vector vector_; 296 | }; 297 | 298 | 299 | 300 | class HeteroscedasticNoisePolynomialKernel : public Kernel { 301 | public: 302 | HeteroscedasticNoisePolynomialKernel (const unsigned int ndim, const unsigned int dim) : Kernel(ndim), dim_(dim){}; 303 | 304 | double _switch (const double* x1, const double* x2) const { 305 | unsigned int i, n = this->get_ndim(); 306 | double d, r2 = 0.0; 307 | for (i = 0; i < n; ++i) { 308 | d = x1[i] - x2[i]; 309 | r2 += d * d; 310 | } 311 | if (r2 < DBL_EPSILON) return 1.0; 312 | return 0.0; 313 | }; 314 | 315 | double value (const double* x1, const double* x2) const { 316 | return vector_[0]*pow(x1[dim_], vector_[1]) * _switch(x1, x2); 317 | }; 318 | 319 | void gradient (const double* x1, const double* x2, double* grad) const { 320 | grad[0] = _switch(x1, x2) * pow(x1[dim_], vector_[1]); 321 | grad[1] = _switch(x1, x2) * vector_[0] * log(x1[dim_]) * pow(x1[dim_], vector_[1]); 322 | }; 323 | 324 | unsigned int size () const { return 2; } 325 | void set_parameter (const unsigned int i, const double value) { 326 | vector_[i] = value; 327 | }; 328 | double get_parameter (const unsigned int i) const { return vector_[i]; }; 329 | 330 | private: 331 | unsigned int dim_; 332 | double vector_[2]; 333 | }; 334 | 335 | 336 | 337 | // discrete kernels 338 | class TaskKernel : public Kernel{ 339 | public: 340 | TaskKernel( const unsigned int ndim, const unsigned int dim, const unsigned int num_tasks): Kernel(ndim), dim_(dim), num_tasks_(num_tasks), vector_((num_tasks*(num_tasks+1))/2, 0), cov_(num_tasks, num_tasks) {}; 341 | 342 | double value (const double* x1, const double *x2) const{ 343 | 344 | return(cov_(int(x1[dim_]), int(x2[dim_]))); 345 | }; 346 | 347 | void update_cov_(){ 348 | Eigen::MatrixXd L = Eigen::MatrixXd::Zero(num_tasks_, num_tasks_); 349 | 350 | int row=0, col=0; 351 | 352 | for (int i = 0; i < vector_.size(); ++i){ 353 | L(row,col) = vector_[i]; 354 | ++row; 355 | 356 | if (row == num_tasks_){ 357 | ++col; 358 | row = col; 359 | } 360 | } 361 | cov_ = (L * (L.transpose())); 362 | } 363 | 364 | // Parameter vector spec. 365 | unsigned int size () const { return vector_.size(); }; 366 | void set_parameter (const unsigned int i, const double value) { 367 | vector_[i] = value; 368 | update_cov_(); 369 | }; 370 | double get_parameter (const unsigned int i) const { 371 | return vector_[i]; 372 | }; 373 | 374 | 375 | private: 376 | unsigned int dim_; 377 | unsigned int num_tasks_; 378 | vector vector_; 379 | Eigen::MatrixXd cov_; 380 | }; 381 | 382 | // 383 | // RADIAL KERNELS 384 | // 385 | 386 | template 387 | class RadialKernel : public Kernel { 388 | public: 389 | RadialKernel (const long ndim, M* metric) 390 | : Kernel(ndim), metric_(metric) {}; 391 | ~RadialKernel () { 392 | delete metric_; 393 | }; 394 | 395 | // Interface to the metric. 396 | double get_squared_distance (const double* x1, const double* x2) const { 397 | return metric_->get_squared_distance(x1, x2); 398 | }; 399 | virtual double get_radial_gradient (double r2) const { 400 | return 0.0; 401 | }; 402 | 403 | virtual void gradient (const double* x1, const double* x2, double* grad) const { 404 | metric_gradient(x1, x2, grad); 405 | }; 406 | 407 | double metric_gradient (const double* x1, const double* x2, double* grad) const { 408 | int i, n = metric_->size(); 409 | double r2 = metric_->gradient(x1, x2, grad), 410 | kg = this->get_radial_gradient(r2); 411 | for (i = 0; i < n; ++i) grad[i] *= kg; 412 | return r2; 413 | }; 414 | 415 | 416 | // Parameter vector spec. 417 | unsigned int size () const { return metric_->size(); }; 418 | void set_parameter (const unsigned int i, const double value) { 419 | metric_->set_parameter(i, value); 420 | }; 421 | double get_parameter (const unsigned int i) const { 422 | return metric_->get_parameter(i); 423 | }; 424 | 425 | protected: 426 | M* metric_; 427 | 428 | }; 429 | 430 | template 431 | class ExpKernel : public RadialKernel { 432 | public: 433 | ExpKernel (const long ndim, M* metric) : RadialKernel(ndim, metric) {}; 434 | double value (const double* x1, const double* x2) const { 435 | return exp(-sqrt(this->get_squared_distance(x1, x2))); 436 | }; 437 | double get_radial_gradient (double r2) const { 438 | double r = sqrt(r2); 439 | if (r < DBL_EPSILON) 440 | return 0.0; 441 | return -0.5 * exp(-r) / r; 442 | }; 443 | }; 444 | 445 | template 446 | class ExpSquaredKernel : public RadialKernel { 447 | public: 448 | ExpSquaredKernel (const long ndim, M* metric) 449 | : RadialKernel(ndim, metric) {}; 450 | double value (const double* x1, const double* x2) const { 451 | return exp(-0.5 * this->get_squared_distance(x1, x2)); 452 | }; 453 | double get_radial_gradient (double r2) const { 454 | return -0.5 * exp(-0.5 * r2); 455 | }; 456 | }; 457 | 458 | template 459 | class Matern32Kernel : public RadialKernel { 460 | public: 461 | Matern32Kernel (const long ndim, M* metric) 462 | : RadialKernel(ndim, metric) {}; 463 | double value (const double* x1, const double* x2) const { 464 | double r = sqrt(3 * this->get_squared_distance(x1, x2)); 465 | return (1 + r) * exp(-r); 466 | }; 467 | double get_radial_gradient (double r2) const { 468 | double r = sqrt(3 * r2); 469 | return -3 * 0.5 * exp(-r); 470 | }; 471 | }; 472 | 473 | template 474 | class Matern52Kernel : public RadialKernel { 475 | public: 476 | Matern52Kernel (const long ndim, M* metric) 477 | : RadialKernel(ndim, metric) {}; 478 | double value (const double* x1, const double* x2) const { 479 | double r2 = 5 * this->get_squared_distance(x1, x2), 480 | r = sqrt(r2); 481 | return (1 + r + r2 / 3.0) * exp(-r); 482 | }; 483 | double get_radial_gradient (double r2) const { 484 | double r = sqrt(5 * r2); 485 | return -5 * (1 + r) * exp(-r) / 6.0; 486 | }; 487 | }; 488 | 489 | template 490 | class RationalQuadraticKernel : public RadialKernel { 491 | public: 492 | RationalQuadraticKernel (const long ndim, M* metric) 493 | : RadialKernel(ndim, metric) {}; 494 | 495 | double value (const double* x1, const double* x2) const { 496 | double r2 = this->get_squared_distance(x1, x2); 497 | return pow(1 + 0.5 * r2 / alpha_, -alpha_); 498 | }; 499 | double get_radial_gradient (double r2) const { 500 | return -0.5 * pow(1 + 0.5 * r2 / alpha_, -alpha_-1); 501 | }; 502 | 503 | void gradient (const double* x1, const double* x2, double* grad) const { 504 | double r2 = this->metric_gradient(x1, x2, &(grad[1])), 505 | t1 = 1.0 + 0.5 * r2 / alpha_, 506 | t2 = 2.0 * alpha_ * t1; 507 | grad[0] = pow(t1, -alpha_) * (r2 / t2 - log(t1)); 508 | }; 509 | 510 | unsigned int size () const { return this->metric_->size() + 1; }; 511 | void set_parameter (const unsigned int i, const double value) { 512 | if (i == 0) alpha_ = value; 513 | else this->metric_->set_parameter(i - 1, value); 514 | }; 515 | double get_parameter (const unsigned int i) const { 516 | if (i == 0) return alpha_; 517 | return this->metric_->get_parameter(i - 1); 518 | }; 519 | 520 | private: 521 | double alpha_; 522 | }; 523 | 524 | 525 | // 526 | // PERIODIC KERNELS 527 | // 528 | 529 | class CosineKernel : public Kernel { 530 | public: 531 | CosineKernel (const unsigned int ndim, const unsigned int dim) 532 | : Kernel(ndim), dim_(dim) {}; 533 | 534 | double value (const double* x1, const double* x2) const { 535 | return cos(2 * M_PI * (x1[dim_] - x2[dim_]) / period_); 536 | }; 537 | void gradient (const double* x1, const double* x2, double* grad) const { 538 | double dx = 2 * M_PI * (x1[dim_] - x2[dim_]) / period_; 539 | grad[0] = dx * sin(dx) / period_; 540 | }; 541 | 542 | unsigned int size () const { return 1; } 543 | void set_parameter (const unsigned int i, const double value) { 544 | period_ = value; 545 | }; 546 | double get_parameter (const unsigned int i) const { return period_; }; 547 | 548 | private: 549 | unsigned int dim_; 550 | double period_; 551 | }; 552 | 553 | class ExpSine2Kernel : public Kernel { 554 | public: 555 | ExpSine2Kernel (const unsigned int ndim, const unsigned int dim) 556 | : Kernel(ndim), dim_(dim) {}; 557 | 558 | double value (const double* x1, const double* x2) const { 559 | double s = sin(M_PI * (x1[dim_] - x2[dim_]) / period_); 560 | return exp(-gamma_ * s * s); 561 | }; 562 | void gradient (const double* x1, const double* x2, double* grad) const { 563 | double arg = M_PI * (x1[dim_] - x2[dim_]) / period_, 564 | s = sin(arg), c = cos(arg), s2 = s * s, A = exp(-gamma_ * s2); 565 | 566 | grad[0] = -s2 * A; 567 | grad[1] = 2 * gamma_ * arg * c * s * A / period_; 568 | }; 569 | 570 | unsigned int size () const { return 2; } 571 | void set_parameter (const unsigned int i, const double value) { 572 | if (i == 0) gamma_ = value; 573 | else period_ = value; 574 | }; 575 | double get_parameter (const unsigned int i) const { 576 | if (i == 0) return gamma_; 577 | return period_; 578 | } 579 | 580 | private: 581 | unsigned int dim_; 582 | double gamma_, period_; 583 | }; 584 | 585 | }; // namespace kernels 586 | }; // namespace george 587 | 588 | #endif 589 | -------------------------------------------------------------------------------- /include/metrics.h: -------------------------------------------------------------------------------- 1 | #ifndef _GEORGE_METRICS_H_ 2 | #define _GEORGE_METRICS_H_ 3 | 4 | #include 5 | #include 6 | 7 | using std::vector; 8 | 9 | namespace george { 10 | namespace metrics { 11 | 12 | class Metric { 13 | public: 14 | Metric (const unsigned int ndim, const unsigned int size) 15 | : ndim_(ndim), vector_(size) {}; 16 | virtual ~Metric () {}; 17 | virtual double get_squared_distance (const double* x1, const double* x2) const { 18 | return 0.0; 19 | }; 20 | virtual double gradient (const double* x1, const double* x2, double* grad) const { 21 | return 0.0; 22 | }; 23 | 24 | // Parameter vector spec. 25 | virtual unsigned int size () const { return vector_.size(); }; 26 | void set_parameter (const unsigned int i, const double value) { 27 | vector_[i] = value; 28 | }; 29 | double get_parameter (const unsigned int i) const { 30 | return vector_[i]; 31 | }; 32 | 33 | protected: 34 | unsigned int ndim_; 35 | vector vector_; 36 | }; 37 | 38 | class OneDMetric : public Metric { 39 | public: 40 | 41 | OneDMetric (const unsigned int ndim, const unsigned int dim) 42 | : Metric(ndim, 1), dim_(dim) {}; 43 | 44 | double get_squared_distance (const double* x1, const double* x2) const { 45 | double d = x1[dim_] - x2[dim_]; 46 | return d * d / this->vector_[0]; 47 | }; 48 | 49 | double gradient (const double* x1, const double* x2, double* grad) const { 50 | double r2 = get_squared_distance(x1, x2); 51 | grad[0] = -r2 / this->vector_[0]; 52 | return r2; 53 | }; 54 | 55 | private: 56 | unsigned int dim_; 57 | }; 58 | 59 | class IsotropicMetric : public Metric { 60 | public: 61 | 62 | IsotropicMetric (const unsigned int ndim) : Metric(ndim, 1) {}; 63 | 64 | double get_squared_distance (const double* x1, const double* x2) const { 65 | unsigned int i; 66 | double d, r2 = 0.0; 67 | for (i = 0; i < ndim_; ++i) { 68 | d = x1[i] - x2[i]; 69 | r2 += d*d; 70 | } 71 | return r2 / this->vector_[0]; 72 | }; 73 | 74 | double gradient (const double* x1, const double* x2, double* grad) const { 75 | double r2 = get_squared_distance(x1, x2); 76 | grad[0] = -r2 / this->vector_[0]; 77 | return r2; 78 | }; 79 | }; 80 | 81 | class AxisAlignedMetric : public Metric { 82 | public: 83 | 84 | AxisAlignedMetric (const unsigned int ndim) : Metric(ndim, ndim) {}; 85 | 86 | double get_squared_distance (const double* x1, const double* x2) const { 87 | unsigned int i; 88 | double d, r2 = 0.0; 89 | for (i = 0; i < ndim_; ++i) { 90 | d = x1[i] - x2[i]; 91 | r2 += d * d / this->vector_[i]; 92 | } 93 | return r2; 94 | }; 95 | 96 | double gradient (const double* x1, const double* x2, double* grad) const { 97 | unsigned int i; 98 | double d, r2 = 0.0; 99 | for (i = 0; i < ndim_; ++i) { 100 | d = x1[i] - x2[i]; 101 | d = d * d / this->vector_[i]; 102 | r2 += d; 103 | grad[i] = -d / this->vector_[i]; 104 | } 105 | return r2; 106 | }; 107 | }; 108 | 109 | }; // namespace metrics 110 | }; // namespace george 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /include/solver.h: -------------------------------------------------------------------------------- 1 | #ifndef _GEORGE_SOLVER_H_ 2 | #define _GEORGE_SOLVER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "constants.h" 10 | #include "kernels.h" 11 | 12 | using Eigen::VectorXd; 13 | using Eigen::MatrixXd; 14 | using george::kernels::Kernel; 15 | 16 | namespace george { 17 | 18 | // Eigen is column major and numpy is row major. Barf. 19 | typedef Map > RowMajorMap; 21 | 22 | class HODLRSolverMatrix : public HODLR_Matrix { 23 | public: 24 | HODLRSolverMatrix (Kernel* kernel) 25 | : kernel_(kernel) 26 | { 27 | stride_ = kernel_->get_ndim(); 28 | }; 29 | void set_values (const double* v) { 30 | t_ = v; 31 | }; 32 | double get_Matrix_Entry (const unsigned i, const unsigned j) { 33 | return kernel_->value(&(t_[i*stride_]), &(t_[j*stride_])); 34 | }; 35 | 36 | private: 37 | Kernel* kernel_; 38 | unsigned int stride_; 39 | const double* t_; 40 | }; 41 | 42 | class Solver { 43 | 44 | public: 45 | 46 | // 47 | // Allocation and deallocation. 48 | // 49 | Solver (Kernel* kernel, unsigned nLeaf = 10, double tol = 1e-10) 50 | : tol_(tol), nleaf_(nLeaf), kernel_(kernel) 51 | { 52 | matrix_ = new HODLRSolverMatrix(kernel_); 53 | solver_ = NULL; 54 | status_ = SOLVER_OK; 55 | computed_ = 0; 56 | }; 57 | ~Solver () { 58 | if (solver_ != NULL) delete solver_; 59 | delete matrix_; 60 | }; 61 | 62 | // 63 | // Accessors. 64 | // 65 | int get_status () const { return status_; }; 66 | int get_computed () const { return computed_; }; 67 | double get_log_determinant () const { return logdet_; }; 68 | 69 | // 70 | // Pre-compute and factorize the kernel matrix. 71 | // 72 | int compute (const unsigned int n, const double* x, const double* yerr, 73 | unsigned int seed) { 74 | unsigned int ndim = kernel_->get_ndim(); 75 | 76 | // It's not computed until it's computed... 77 | computed_ = 0; 78 | 79 | // Compute the diagonal elements. 80 | VectorXd diag(n); 81 | for (unsigned int i = 0; i < n; ++i) { 82 | diag[i] = yerr[i]*yerr[i]; 83 | diag[i] += kernel_->value(&(x[i*ndim]), &(x[i*ndim])); 84 | } 85 | 86 | // Set the time points for the kernel. 87 | matrix_->set_values(x); 88 | 89 | // Set up the solver object. 90 | if (solver_ != NULL) delete solver_; 91 | solver_ = new HODLR_Tree (matrix_, n, nleaf_); 92 | solver_->assemble_Matrix(diag, tol_, 's', seed); 93 | 94 | // Factorize the matrix. 95 | solver_->compute_Factor(); 96 | 97 | // Extract the log-determinant. 98 | solver_->compute_Determinant(logdet_); 99 | 100 | // Update the bookkeeping flags. 101 | computed_ = 1; 102 | status_ = SOLVER_OK; 103 | return status_; 104 | }; 105 | 106 | void apply_inverse (const unsigned int n, const unsigned int nrhs, 107 | double* b, double* out) { 108 | unsigned int i, j; 109 | MatrixXd b_vec = RowMajorMap(b, n, nrhs), alpha(n, nrhs); 110 | solver_->solve(b_vec, alpha); 111 | for (i = 0; i < n; ++i) 112 | for (j = 0; j < nrhs; ++j) 113 | out[i*nrhs+j] = alpha(i, j); 114 | }; 115 | 116 | private: 117 | double logdet_, tol_; 118 | unsigned nleaf_; 119 | int status_, computed_; 120 | Kernel* kernel_; 121 | HODLRSolverMatrix* matrix_; 122 | HODLR_Tree* solver_; 123 | MatrixXd x_; 124 | }; 125 | 126 | }; 127 | 128 | #endif 129 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | 6 | try: 7 | from setuptools import setup, Extension 8 | from setuptools.command.build_ext import build_ext as _build_ext 9 | except ImportError: 10 | from distutils.core import setup, Extension 11 | from distutils.command.build_ext import build_ext as _build_ext 12 | 13 | 14 | def find_eigen(hint=None): 15 | """ 16 | Find the location of the Eigen 3 include directory. This will return 17 | ``None`` on failure. 18 | 19 | """ 20 | # List the standard locations including a user supplied hint. 21 | search_dirs = [] if hint is None else hint 22 | search_dirs += [ 23 | "/usr/local/include/eigen3", 24 | "/usr/local/homebrew/include/eigen3", 25 | "/opt/local/var/macports/software/eigen3", 26 | "/opt/local/include/eigen3", 27 | "/usr/include/eigen3", 28 | "/usr/include/local", 29 | ] 30 | 31 | # Loop over search paths and check for the existence of the Eigen/Dense 32 | # header. 33 | for d in search_dirs: 34 | path = os.path.join(d, "Eigen", "Dense") 35 | if os.path.exists(path): 36 | # Determine the version. 37 | vf = os.path.join(d, "Eigen", "src", "Core", "util", "Macros.h") 38 | if not os.path.exists(vf): 39 | continue 40 | src = open(vf, "r").read() 41 | v1 = re.findall("#define EIGEN_WORLD_VERSION (.+)", src) 42 | v2 = re.findall("#define EIGEN_MAJOR_VERSION (.+)", src) 43 | v3 = re.findall("#define EIGEN_MINOR_VERSION (.+)", src) 44 | if not len(v1) or not len(v2) or not len(v3): 45 | continue 46 | v = "{0}.{1}.{2}".format(v1[0], v2[0], v3[0]) 47 | print("Found Eigen version {0} in: {1}".format(v, d)) 48 | return d 49 | return None 50 | 51 | 52 | def find_hodlr(hint=None): 53 | """ 54 | Find the location of the HODLR include directory. This will return 55 | ``None`` on failure. 56 | 57 | """ 58 | # List the standard locations including a user supplied hint. 59 | search_dirs = [] if hint is None else hint 60 | search_dirs += [ 61 | "./hodlr/header", 62 | "/usr/local/include", 63 | "/usr/include/local", 64 | ] 65 | 66 | # Loop over search paths and check for the existence of the Eigen/Dense 67 | # header. 68 | for d in search_dirs: 69 | paths = [ 70 | os.path.join(d, "HODLR_Matrix.hpp"), 71 | os.path.join(d, "HODLR_Tree.hpp"), 72 | os.path.join(d, "HODLR_Node.hpp"), 73 | ] 74 | if all(map(os.path.exists, paths)): 75 | print("Found HODLR headers in: {0}".format(d)) 76 | return d 77 | return None 78 | 79 | 80 | class build_ext(_build_ext): 81 | """ 82 | A custom extension builder that finds the include directories for Eigen 83 | and HODLR before compiling. 84 | 85 | """ 86 | 87 | def build_extension(self, ext): 88 | dirs = ext.include_dirs + self.compiler.include_dirs 89 | 90 | # Look for the Eigen headers and make sure that we can find them. 91 | eigen_include = find_eigen(hint=dirs) 92 | if eigen_include is None: 93 | raise RuntimeError("Required library Eigen 3 not found. " 94 | "Check the documentation for solutions.") 95 | 96 | # Look for the HODLR headers and make sure that we can find them. 97 | hodlr_include = find_hodlr(hint=dirs) 98 | if hodlr_include is None: 99 | raise RuntimeError("Required library HODLR not found. " 100 | "Check the documentation for solutions.") 101 | 102 | # Update the extension's include directories. 103 | ext.include_dirs += [eigen_include, hodlr_include] 104 | ext.extra_compile_args += ["-Wno-unused-function", 105 | "-Wno-uninitialized"] 106 | 107 | # Run the standard build procedure. 108 | _build_ext.build_extension(self, ext) 109 | 110 | 111 | if __name__ == "__main__": 112 | import sys 113 | import numpy 114 | 115 | # Publish the library to PyPI. 116 | if "publish" in sys.argv[-1]: 117 | os.system("python setup.py sdist upload") 118 | sys.exit() 119 | 120 | # Set up the C++-extension. 121 | libraries = [] 122 | if os.name == "posix": 123 | libraries.append("m") 124 | include_dirs = [ 125 | "include", 126 | numpy.get_include(), 127 | ] 128 | 129 | kern_fn = os.path.join("george", "_kernels") 130 | hodlr_fn = os.path.join("george", "hodlr") 131 | if (os.path.exists(kern_fn + ".pyx") and os.path.exists(hodlr_fn + ".pyx") 132 | and os.path.exists(os.path.join("george", "kernels.pxd"))): 133 | from Cython.Build import cythonize 134 | kern_fn += ".pyx" 135 | hodlr_fn += ".pyx" 136 | else: 137 | kern_fn += ".cpp" 138 | hodlr_fn += ".cpp" 139 | cythonize = lambda x: x 140 | 141 | kern_ext = Extension("george._kernels", sources=[kern_fn], 142 | libraries=libraries, include_dirs=include_dirs) 143 | hodlr_ext = Extension("george.hodlr", sources=[hodlr_fn], 144 | libraries=libraries, include_dirs=include_dirs) 145 | extensions = cythonize([kern_ext, hodlr_ext]) 146 | 147 | # Hackishly inject a constant into builtins to enable importing of the 148 | # package before the library is built. 149 | if sys.version_info[0] < 3: 150 | import __builtin__ as builtins 151 | else: 152 | import builtins 153 | builtins.__GEORGE_SETUP__ = True 154 | import george 155 | 156 | setup( 157 | name="george", 158 | version=george.__version__, 159 | author="Daniel Foreman-Mackey", 160 | author_email="danfm@nyu.edu", 161 | url="https://github.com/dfm/george", 162 | license="MIT", 163 | packages=["george", "george.testing"], 164 | ext_modules=extensions, 165 | description="Blazingly fast Gaussian Processes for regression.", 166 | long_description=open("README.rst").read(), 167 | package_data={"": ["README.rst", "LICENSE", 168 | "include/*.h", "hodlr/header/*.hpp", ]}, 169 | include_package_data=True, 170 | cmdclass=dict(build_ext=build_ext), 171 | classifiers=[ 172 | "Development Status :: 5 - Production/Stable", 173 | "Intended Audience :: Developers", 174 | "Intended Audience :: Science/Research", 175 | "License :: OSI Approved :: MIT License", 176 | "Operating System :: OS Independent", 177 | "Programming Language :: Python", 178 | ], 179 | ) 180 | --------------------------------------------------------------------------------