├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── examples ├── CompareImplementations.ipynb ├── GeneratedData.ipynb ├── Index.ipynb ├── LSDerivation.ipynb ├── MultibandExample.ipynb ├── MultibandInteractive.ipynb ├── MultibandLS_Math.ipynb ├── RRLyraePartial.ipynb ├── RRLyraeTemplates.ipynb ├── RegularizedLS.ipynb ├── SingleBandExample.ipynb ├── SuperSmoother.ipynb └── TestWidths.ipynb ├── figures ├── LSSTsims │ ├── LSST.specWindow.tar.gz │ ├── LSSTsims.py │ ├── compute_results.py │ ├── mapcache.py │ ├── notes.txt │ ├── plot_olusei.py │ ├── plot_results.py │ ├── resultsLSST.npy │ ├── resultsLSST01.npy │ ├── resultsLSST_ssm_g.npy │ ├── resultsLSST_ssm_i.npy │ ├── resultsLSST_ssm_r.npy │ ├── resultsLSST_ssm_u.npy │ ├── resultsLSST_ssm_y.npy │ └── resultsLSST_ssm_z.npy ├── S82sims │ ├── compute_results.py │ ├── mapcache.py │ ├── res_multiband_0_1.npy │ ├── res_multiband_1_0.npy │ ├── res_partial_multiband_0_1.npy │ ├── res_partial_multiband_1_0.npy │ ├── res_partial_supersmoother_g.npy │ └── res_supersmoother_g.npy ├── fig01_basic_example.py ├── fig02_floating_mean.py ├── fig03_multiterm_example.py ├── fig04_regularization_example.py ├── fig05_multiband_sim.py ├── fig06_multiband_models.py ├── fig07_compare_periods.py ├── fig08_compare_periods_reduced.py ├── fig09_LSST_sims.py ├── fig_multiband_supersmoother.py └── fig_num_observations.py ├── gh-publisher-scripts ├── README.md ├── build.sh ├── copy.sh ├── example.travis.yml ├── front-matter.yml ├── gh-publisher.sh ├── index.html ├── javascripts │ └── scale.fix.js └── stylesheets │ ├── pygment_trac.css │ └── styles.css ├── talk_figures ├── band_by_band.py ├── multi_fourier.py ├── multiterm_buildup.py └── naive_methods.py └── writeup ├── .gitignore ├── Makefile ├── aastex.cls ├── apj.bst ├── emulateapj.cls ├── fig01.pdf ├── fig02.pdf ├── fig03.pdf ├── fig04.pdf ├── fig05a.pdf ├── fig05b.pdf ├── fig06.pdf ├── fig07a.pdf ├── fig07b.pdf ├── fig08a.pdf ├── fig08b.pdf ├── fig09.pdf ├── paper.bib └── paper.tex /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | 57 | # emacs 58 | *~ 59 | 60 | # data files 61 | *.gz 62 | *.tgz 63 | *.npy 64 | *.npz 65 | 66 | #IPython 67 | .ipynb_checkpoints 68 | 69 | # pdf outputs 70 | *.pdf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | script: /bin/bash gh-publisher-scripts/gh-publisher.sh 5 | before_install: 6 | - yes "" | sudo apt-add-repository ppa:texlive-backports/ppa 7 | - sudo apt-get update -y 8 | - sudo apt-get install -y 9 | inkscape 10 | texlive-fonts-recommended 11 | texlive-latex-extra 12 | texlive-latex-recommended 13 | texlive-xetex 14 | texlive-publishers 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Jake Vanderplas 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | tar: 2 | tar -czvf paper.tgz writeup 3 | 4 | clean: 5 | rm writeup/*~ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mutiband Lomb-Scargle Periodograms 2 | ================================== 3 | This repository contains the source for our multiband periodogram paper. 4 | It makes use of the [gatspy](http://github.com/jakevdp/gatspy/) package, 5 | which has been developed concurrently. 6 | The paper has been submitted to the Astrophysical Journal, and a preprint is available on [arXiv](http://arxiv.org/abs/1502.01344). 7 | To see a current build of the paper from the master branch of this repository, 8 | refer to http://jakevdp.github.io/multiband_LS (powered by [gh-publisher](https://github.com/ewanmellor/gh-publisher)). 9 | 10 | Feel free to submit comments or feedback via the Issues tab on this repository. 11 | 12 | 13 | Reproducing the Paper 14 | --------------------- 15 | The LaTeX source of the paper, including all figure pdfs, is in the ``writeup`` directory. The code to reproduce the analysis and figures in the paper is in the ``figures`` directory. 16 | 17 | To reproduce the figures, first install the following packages (Python 2 or 3): 18 | 19 | - Standard Python scientific stack: ([IPython](http://ipython.org), [numpy](http://numpy.org), [scipy](http://scipy.org), [matplotlib](http://matplotlib.org), [scikit-learn](http://scikit-learn.org), [pandas](http://pandas.pydata.org/)) 20 | - [seaborn](http://stanford.edu/~mwaskom/software/seaborn/) for plot styles. 21 | - [astroML](http://astroML.org) for general astronomy machine learning tools. 22 | - [gatspy](http://github.com/astroML/gatspy) for astronomical time-series analysis. 23 | - [supersmoother](http://github.com/jakevdp/supersmoother) for the supersmoother algorithm used by ``gatspy``. 24 | 25 | With [conda](http://conda.pydata.org/miniconda.html), a new environment meeting these requirements can be set up as follows: 26 | 27 | ``` 28 | $ conda create -n multibandLS python=3.4 ipython-notebook numpy scipy matplotlib scikit-learn pandas seaborn pip 29 | $ source activate multibandLS 30 | $ pip install astroML gatspy supersmoother 31 | ``` 32 | 33 | Once these packages are installed, navigate to the ``figures`` directory and run any of the ``fig*.py`` scripts. For example, to create figure 1, type 34 | ``` 35 | $ cd figures 36 | $ python fig01_basic_example.py 37 | ``` 38 | 39 | Several of the figures require the results of long computations. These results are cached as numpy binary files in ``figures/LSSTsims/`` and ``figures/S82sims/``. Code to recompute these results in parallel is in the ``compute_results.py`` script in each of these directories. Note that the full computation for these takes several dozen CPU hours, but is trivially parallelizable with IPython parallel (see scripts for details). 40 | -------------------------------------------------------------------------------- /examples/Index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:5048b1ac7e12188b3091b12d4e3c5ed7c05bab6af1feab2c583ca0b5bd0225f6" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "# Index of Periodogram Examples\n", 16 | "\n", 17 | "- [Example of Standard Lomb-Scargle](SingleBandExample.ipynb): this example shows how to compute periodicity on from the LINEAR survey, using a standard Lomb-Scargle periodogram and multi-term generalizations.\n", 18 | "- [Example of Multi-band Lomb-Scargle](MultibandExample.ipynb): this example shows how to compute periodicity on multiband data from the SDSS survey, using several variants of the multiband approach from VanderPlas & Ivezic 2015.\n", 19 | "- [Comparing Implementations](CompareImplementations.ipynb): comparing the implementations in ``multiband_LS`` with those in ``astroML``.\n", 20 | "- [Interactive Multiband](MultibandInteractive.ipynb): using IPython's interact to explore multiband fitting.\n", 21 | "- [Fits on Generated Data](GeneratedData.ipynb): Fitting periodograms to simulated RR Lyrae light curves." 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | " " 29 | ] 30 | } 31 | ], 32 | "metadata": {} 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /examples/LSDerivation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:602fee0a2842d1d585733252a2bd1113f170870cc74329e6e757996fcfbdd421" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "# Deriving Generalized Lomb-Scargle" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## Standard Lomb-Scargle" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "In standard Lomb-Scargle, we start with values\n", 30 | "\n", 31 | "$$\n", 32 | "\\{t,y,dy\\}\n", 33 | "$$" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "We then compute the mean of the $y$ values using the maximum likelihood:\n", 41 | "\n", 42 | "$$\n", 43 | "\\mu_y = \\frac{\\sum_i y_i/dy_i^2}{\\sum_i 1/dy_i^2}\n", 44 | "$$" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "Then we compute the centered $y$:\n", 52 | "\n", 53 | "$$\n", 54 | "\\bar{y} \\equiv y - \\mu_y\n", 55 | "$$" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "The model is a simple 1-term sinusoid given by\n", 63 | "\n", 64 | "$$\n", 65 | "M(t,\\omega~|~\\theta) = \\theta_0 \\sin(\\omega t) + \\theta_1\\cos(\\omega t)\n", 66 | "$$" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "The likelihood for the dataset is\n", 74 | "\n", 75 | "$$\n", 76 | "L(\\{t,y,dy\\},\\omega~|~\\theta) = \\sum_i \\frac{1}{\\sqrt{2\\pi dy_i^2}}\n", 77 | "\\exp\\left[\n", 78 | "\\frac{-(y_i - M(t_i,\\omega~|~\\theta)^2}{2dy_i^2}\n", 79 | "\\right]\n", 80 | "$$\n", 81 | "\n", 82 | "Which leads to the chi-squared function (derived from the log-likelihood)\n", 83 | "\n", 84 | "$$\n", 85 | "\\chi^2(\\omega, \\theta) = \\sum_i\\frac{(\\bar{y}_i - M(t_i,\\omega~|~\\theta)^2}{2dy_i^2}\n", 86 | "$$" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "If we re-express the model by defining\n", 94 | "\n", 95 | "$$\n", 96 | "X_\\omega = \\left[\n", 97 | "\\begin{array}{ll}\n", 98 | "\\sin(\\omega t_1) & \\cos(\\omega t_1)\\\\\n", 99 | "\\sin(\\omega t_2) & \\cos(\\omega t_2)\\\\\n", 100 | "\\vdots & \\vdots \\\\\n", 101 | "\\sin(\\omega t_N) & \\cos(\\omega t_N)\\\\\n", 102 | "\\end{array}\n", 103 | "\\right],~~~~\n", 104 | "y = \\left[\n", 105 | "\\begin{array}{l}\n", 106 | "y_1 \\\\\n", 107 | "y_2\\\\\n", 108 | "\\vdots \\\\\n", 109 | "y_N\\\\\n", 110 | "\\end{array}\n", 111 | "\\right]\n", 112 | "$$" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "And create the error matrix\n", 120 | "\n", 121 | "$$\n", 122 | "\\Sigma_y = \\left[\n", 123 | "\\begin{array}{lllll}\n", 124 | "dy_1^2 & 0 & 0 & \\cdots & 0\\\\\n", 125 | "0 & dy_2^2 & 0 & \\cdots & 0\\\\\n", 126 | "0 & 0 & dy_3^2 & \\cdots & 0\\\\\n", 127 | "\\vdots & \\vdots & \\vdots & \\ddots & \\vdots\\\\\n", 128 | "0 & 0 & 0 & \\cdots & dy_N^2\n", 129 | "\\end{array}\n", 130 | "\\right]\n", 131 | "$$" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "metadata": {}, 137 | "source": [ 138 | "then our model is given by\n", 139 | "\n", 140 | "$$\n", 141 | "M(\\omega, \\theta) = X_\\omega\\theta\n", 142 | "$$" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "and our $\\chi^2$ can be written\n", 150 | "\n", 151 | "$$\n", 152 | "\\chi^2(\\omega, \\theta) = (\\bar{y} - X_\\omega\\theta)^T\\Sigma_y^{-1}(\\bar{y} - X_\\omega\\theta)\n", 153 | "$$" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "Minimizing this cost funciton with respect to $\\theta$ gives the maximum likelihood parameters:\n", 161 | "\n", 162 | "$$\n", 163 | "\\hat{\\theta} = (X_\\omega^T\\Sigma_y^{-1}X_\\omega)^{-1}X_\\omega^T\\Sigma_y^{-1}\\bar{y}\n", 164 | "$$" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "We can simplify this a bit by defining\n", 172 | "\n", 173 | "$$\n", 174 | "X_{\\omega,\\ast} = \\Sigma_y^{-1/2}X_\\omega \\\\\n", 175 | "\\bar{y}_\\ast = \\Sigma_y^{-1/2}\\bar{y}\n", 176 | "$$\n", 177 | "\n", 178 | "And the above becomes\n", 179 | "\n", 180 | "$$\n", 181 | "\\hat{\\theta} = (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^T\\bar{y}_\\ast\n", 182 | "$$" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "Now the $\\chi^2$ of the model fit is given by\n", 190 | "\n", 191 | "$$\n", 192 | "\\chi^2(\\omega,\\hat{\\theta}) = \\left[\n", 193 | "\\bar{y}_\\ast^T\\bar{y}_\\ast\n", 194 | "- \\bar{y}_\\ast^TX_{\\omega,\\ast} (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^T\\bar{y}_\\ast\n", 195 | "\\right]\n", 196 | "$$" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "The reference $\\chi^2$ is\n", 204 | "\n", 205 | "$$\n", 206 | "\\chi_0^2 = \\bar{y}_\\ast^T\\bar{y}_\\ast\n", 207 | "$$\n", 208 | "\n", 209 | "So the power $P_{LS} = 1 - \\chi^2/\\chi_0^2$ is given by\n", 210 | "\n", 211 | "$$\n", 212 | "P_{LS}(\\omega) = \\frac{\\bar{y}_\\ast^TX_{\\omega,\\ast} (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^T\\bar{y}_\\ast}{\\bar{y}_\\ast^T\\bar{y}_\\ast}\n", 213 | "$$" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "## Generalized Lomb-Scargle\n", 221 | "\n", 222 | "The *generalized lomb-scargle* fits for the mean of $y$ as part of the model, rather than as a separete step.\n", 223 | "\n", 224 | "So what changes is that the $X_\\omega$ becomes:\n", 225 | "\n", 226 | "$$\n", 227 | "X_\\omega = \\left[\n", 228 | "\\begin{array}{lll}\n", 229 | "1 & \\sin(\\omega t_1) & \\cos(\\omega t_1)\\\\\n", 230 | "1 & \\sin(\\omega t_2) & \\cos(\\omega t_2)\\\\\n", 231 | "\\vdots & \\vdots & \\vdots \\\\\n", 232 | "1 & \\sin(\\omega t_N) & \\cos(\\omega t_N)\\\\\n", 233 | "\\end{array}\n", 234 | "\\right]\n", 235 | "$$" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "metadata": {}, 241 | "source": [ 242 | "and $\\bar{y}$ becomes\n", 243 | "\n", 244 | "$$\n", 245 | "\\bar{y} = y - X_\\omega \\mathbf{1}_1 \\hat{\\theta}\n", 246 | "$$\n", 247 | "\n", 248 | "where the best-fit parameters $\\hat{\\theta}$ satisfy\n", 249 | "\n", 250 | "$$\n", 251 | "\\hat{\\theta} = (X_\\omega^T\\Sigma_y^{-1}X_\\omega)^{-1}X_\\omega^T\\Sigma_y^{-1} y\n", 252 | "$$" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "metadata": {}, 258 | "source": [ 259 | "and we have defined,\n", 260 | "\n", 261 | "$$\n", 262 | "\\mathbf{1}_1 = \\left[\n", 263 | "\\begin{array}{lll}\n", 264 | "1 & 0 & 0\\\\\n", 265 | "0 & 0 & 0\\\\\n", 266 | "0 & 0 & 0\n", 267 | "\\end{array}\n", 268 | "\\right]\n", 269 | "$$" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "With this change, we write the maximum $\\chi^2$ as\n", 277 | "\n", 278 | "$$\n", 279 | "\\chi^2(\\omega) = (y - X_\\omega\\hat{\\theta})^T\\Sigma_y^{-1}(y - X_\\omega\\hat{\\theta})\n", 280 | "$$" 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "Let's re-express this in terms of $\\bar{y}$:\n", 288 | "\n", 289 | "$$\n", 290 | "\\chi^2(\\omega) = (\\bar{y} - X_\\omega\\tilde{\\mathbf{1}}_1\\hat{\\theta})^T\\Sigma_y^{-1}(y - X_\\omega\\tilde{\\mathbf{1}}_1\\hat{\\theta})\n", 291 | "$$" 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "metadata": {}, 297 | "source": [ 298 | "Where we have defined\n", 299 | "\n", 300 | "$$\n", 301 | "\\tilde{\\mathbf{1}}_1 = I - \\mathbf{1}_1\n", 302 | "$$" 303 | ] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "collapsed": false, 308 | "input": [], 309 | "language": "python", 310 | "metadata": {}, 311 | "outputs": [] 312 | } 313 | ], 314 | "metadata": {} 315 | } 316 | ] 317 | } -------------------------------------------------------------------------------- /examples/MultibandLS_Math.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:b66f9b4be29e2767af91a1d64dfcfeda22532ec375a35aa92efccd6e6f7471c6" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "# Multi-band Lomb-Scargle" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## Standard Lomb-Scargle Algorithm" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "In standard Lomb-Scargle, we start with our data\n", 30 | "\n", 31 | "$$\n", 32 | "D = \\{t_i,y_i,\\sigma_i\\}_{i=1}^N\n", 33 | "$$\n", 34 | "\n", 35 | "We assume here that the true values of $y$ are centered at zero (we'll relax this momentarily)" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "The model is a simple 1-term sinusoid given by\n", 43 | "\n", 44 | "$$\n", 45 | "M(t,\\omega~|~\\theta) = \\theta_0 \\sin(\\omega t) + \\theta_1\\cos(\\omega t)\n", 46 | "$$" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "The likelihood for the dataset is\n", 54 | "\n", 55 | "$$\n", 56 | "L(\\{t,y,dy\\},\\omega~|~\\theta) = \\sum_i \\frac{1}{\\sqrt{2\\pi \\sigma_i^2}}\n", 57 | "\\exp\\left[\n", 58 | "\\frac{-(y_i - M(t_i,\\omega~|~\\theta)^2}{2\\sigma_i^2}\n", 59 | "\\right]\n", 60 | "$$\n", 61 | "\n", 62 | "Which leads to the chi-squared function (derived from the log-likelihood)\n", 63 | "\n", 64 | "$$\n", 65 | "\\chi^2(\\omega, \\theta) = \\sum_i\\frac{[y_i - M(t_i,\\omega~|~\\theta)]^2}{2\\sigma_i^2}\n", 66 | "$$" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "If we re-express the model by defining\n", 74 | "\n", 75 | "$$\n", 76 | "X_\\omega = \\left[\n", 77 | "\\begin{array}{ll}\n", 78 | "\\sin(\\omega t_1) & \\cos(\\omega t_1)\\\\\n", 79 | "\\sin(\\omega t_2) & \\cos(\\omega t_2)\\\\\n", 80 | "\\vdots & \\vdots \\\\\n", 81 | "\\sin(\\omega t_N) & \\cos(\\omega t_N)\\\\\n", 82 | "\\end{array}\n", 83 | "\\right],~~~~\n", 84 | "y = \\left[\n", 85 | "\\begin{array}{l}\n", 86 | "y_1 \\\\\n", 87 | "y_2\\\\\n", 88 | "\\vdots \\\\\n", 89 | "y_N\\\\\n", 90 | "\\end{array}\n", 91 | "\\right]\n", 92 | "$$" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "And create the error matrix\n", 100 | "\n", 101 | "$$\n", 102 | "\\Sigma_y = \\left[\n", 103 | "\\begin{array}{lllll}\n", 104 | "\\sigma_1^2 & 0 & 0 & \\cdots & 0\\\\\n", 105 | "0 & \\sigma_2^2 & 0 & \\cdots & 0\\\\\n", 106 | "0 & 0 & \\sigma_3^2 & \\cdots & 0\\\\\n", 107 | "\\vdots & \\vdots & \\vdots & \\ddots & \\vdots\\\\\n", 108 | "0 & 0 & 0 & \\cdots & \\sigma_N^2\n", 109 | "\\end{array}\n", 110 | "\\right]\n", 111 | "$$" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "then our model is given by\n", 119 | "\n", 120 | "$$\n", 121 | "M(\\omega, \\theta) = X_\\omega\\theta\n", 122 | "$$" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "and our $\\chi^2$ can be written\n", 130 | "\n", 131 | "$$\n", 132 | "\\chi^2(\\omega, \\theta) = (y - X_\\omega\\theta)^T\\Sigma_y^{-1}(y - X_\\omega\\theta)\n", 133 | "$$" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "Minimizing this cost funciton with respect to $\\theta$ gives the maximum likelihood parameters:\n", 141 | "\n", 142 | "$$\n", 143 | "\\hat{\\theta} = (X_\\omega^T\\Sigma_y^{-1}X_\\omega)^{-1}X_\\omega^T\\Sigma_y^{-1}y\n", 144 | "$$" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "We can simplify this a bit by defining\n", 152 | "\n", 153 | "$$\n", 154 | "X_{\\omega,\\ast} = \\Sigma_y^{-1/2}X_\\omega \\\\\n", 155 | "y_\\ast = \\Sigma_y^{-1/2}y\n", 156 | "$$\n", 157 | "\n", 158 | "And the above becomes\n", 159 | "\n", 160 | "$$\n", 161 | "\\hat{\\theta} = (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^Ty_\\ast\n", 162 | "$$" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "Now the $\\chi^2$ of the model fit is given by\n", 170 | "\n", 171 | "$$\n", 172 | "\\chi^2(\\omega,\\hat{\\theta}) = \\left[\n", 173 | "y_\\ast^Ty_\\ast\n", 174 | "- y_\\ast^TX_{\\omega,\\ast} (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^Ty_\\ast\n", 175 | "\\right]\n", 176 | "$$" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "The reference $\\chi^2$ is\n", 184 | "\n", 185 | "$$\n", 186 | "\\chi_0^2 = \\bar{y}_\\ast^T\\bar{y}_\\ast\n", 187 | "$$\n", 188 | "\n", 189 | "So the power $P_{LS} = 1 - \\chi^2/\\chi_0^2$ is given by\n", 190 | "\n", 191 | "$$\n", 192 | "P_{LS}(\\omega) = \\frac{y_\\ast^TX_{\\omega,\\ast} (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^T\\bar{y}_\\ast}{\\bar{y}_\\ast^T\\bar{y}_\\ast}\n", 193 | "$$" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "### Generalized Lomb-Scargle\n", 201 | "\n", 202 | "The *generalized lomb-scargle* fits for the mean of $y$ as part of the model, rather than as a separete step.\n", 203 | "\n", 204 | "So what changes is that the $X_\\omega$ becomes:\n", 205 | "\n", 206 | "$$\n", 207 | "X_\\omega = \\left[\n", 208 | "\\begin{array}{lll}\n", 209 | "1 & \\sin(\\omega t_1) & \\cos(\\omega t_1)\\\\\n", 210 | "1 & \\sin(\\omega t_2) & \\cos(\\omega t_2)\\\\\n", 211 | "\\vdots & \\vdots & \\vdots \\\\\n", 212 | "1 & \\sin(\\omega t_N) & \\cos(\\omega t_N)\\\\\n", 213 | "\\end{array}\n", 214 | "\\right]\n", 215 | "$$\n", 216 | "\n", 217 | "With this, we can relax the requirement that $y$ is centered: it will be centered as part of the model. Everything else carries through, and we have \n", 218 | "\n", 219 | "$$\n", 220 | "P_{LS}(\\omega) = \\frac{y_\\ast^TX_{\\omega,\\ast} (X_{\\omega,\\ast}^TX_{\\omega,\\ast})^{-1}X_{\\omega,\\ast}^T\\bar{y}_\\ast}{\\bar{y}_\\ast^T\\bar{y}_\\ast}\n", 221 | "$$\n", 222 | "\n", 223 | "Where the quantities $y_\\ast$ and $X_{\\omega,\\ast}$ are the noise-corrected matrices as used above." 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "### Best-fit Parameters\n", 231 | "\n", 232 | "Note that for either of these models, the best-fit parameters are given by\n", 233 | "\n", 234 | "$$\n", 235 | "\\hat{\\theta} = (X_{\\omega,\\ast}^T X_{\\omega,\\ast})^{-1}X_{\\omega, \\ast}^T y_\\ast\n", 236 | "$$\n", 237 | "\n", 238 | "and that these best-fit values consist of a step within the computation of $P_{LS}$." 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "## Generalizing to Multiple Bands" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "For multiple bands, we'll assume that there exists some fundamental model\n", 253 | "\n", 254 | "$$\n", 255 | "\\theta_{0} = \\{\\omega, y_0, A_0, B_0\\}\n", 256 | "$$\n", 257 | "\n", 258 | "which defines an underlying oscillation\n", 259 | "\n", 260 | "$$\n", 261 | "M_0(t~|~\\theta_0) = y_0 + A_0\\sin(\\omega t) + B_0\\cos(\\omega_0 t)\n", 262 | "$$\n", 263 | "\n", 264 | "We'll assume that each band indexed by $b \\in \\{1, 2, 3...\\}$ has a periodic offset function $Q_b$ parametrized by some $\\theta_b$, such that the model for that band is\n", 265 | "\n", 266 | "$$\n", 267 | "M_b(t~|~\\theta_0,\\omega_0) = M_0(t~|~\\theta_0) + Q_b(t~|~\\omega, \\theta_b)\n", 268 | "$$\n", 269 | "\n", 270 | "Where, in general, $Q_b$ can be any periodic function." 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "metadata": {}, 276 | "source": [ 277 | "This will give us a single global underlying model $\\theta_0$, plus a $\\theta_f^\\ast$ for each of the have $N_f$ filters.\n", 278 | "\n", 279 | "But this problem is over-specified: one set of parameters here is redundant; note that we can easily reparametrize the model for each filter as\n", 280 | "\n", 281 | "$$\n", 282 | "\\theta_f = \\{y_f, A_f, B_f\\}\n", 283 | "$$\n", 284 | "\n", 285 | "where we've defined\n", 286 | "\n", 287 | "$$\n", 288 | "y_f = y_f^\\ast + y_0\\\\\n", 289 | "A_f = A_f^\\ast + A_0\\\\\n", 290 | "B_f = B_f^\\ast + B_0\n", 291 | "$$\n", 292 | "\n", 293 | "and we've thus eliminated the ability to solve explicitly for $\\{y_0, A_0, B_0\\}$." 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "### Wave hands a bit\n", 301 | "\n", 302 | "The result is that we can peform a very efficient lomb-scargle algorithm for **each band independently**, and then manipulate the results into a single global power $P$ which takes into account all the bands!" 303 | ] 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "metadata": {}, 308 | "source": [ 309 | "### What about regularization?" 310 | ] 311 | }, 312 | { 313 | "cell_type": "markdown", 314 | "metadata": {}, 315 | "source": [ 316 | "Say we go back to our $\\chi^2$ expression:\n", 317 | "\n", 318 | "$$\n", 319 | "\\chi^2(\\omega, \\theta) = (y - X_\\omega\\theta)^T\\Sigma_y^{-1}(y - X_\\omega\\theta)\n", 320 | "$$" 321 | ] 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "metadata": {}, 326 | "source": [ 327 | "We using a Tikhonov regularization, we can penalize the $\\theta$ values:\n", 328 | "\n", 329 | "$$\n", 330 | "\\chi^2(\\omega, \\theta) = (y - X_\\omega\\theta)^T\\Sigma_y^{-1}(y - X_\\omega\\theta) + \\theta^T \\Gamma^T\\Gamma \\theta\n", 331 | "$$\n", 332 | "\n", 333 | "(Note that while this form is often assumed within a frequentist context, it can be derived within a Bayesian context where the priors on $\\theta$ are Gaussian, centered at zero, with a covariance matrix $\\Sigma_\\theta = [\\Gamma^T\\Gamma]^{-1}$)" 334 | ] 335 | }, 336 | { 337 | "cell_type": "markdown", 338 | "metadata": {}, 339 | "source": [ 340 | "Minimizing this with respect to theta gives:\n", 341 | "\n", 342 | "\n", 343 | "$$\n", 344 | "X_\\omega^T\\Sigma_y^{-1}(X_\\omega\\theta - y) + \\Gamma^T\\Gamma\\theta = 0\n", 345 | "$$" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "metadata": {}, 351 | "source": [ 352 | "Or\n", 353 | "\n", 354 | "$$\n", 355 | "\\hat{\\theta} = \\left(X_\\omega^T\\Sigma_y^{-1}X_\\omega + \\Gamma^T\\Gamma\\right)^{-1}\\left(X_\\omega^T\\Sigma_y^{-1}y\\right)\n", 356 | "$$" 357 | ] 358 | }, 359 | { 360 | "cell_type": "markdown", 361 | "metadata": {}, 362 | "source": [ 363 | "Plugging this in to the expression for $\\chi^2$, we get the following result:\n", 364 | "\n", 365 | "$$\n", 366 | "\\chi^2 = y^Ty - y^TX\\left(X^TX + \\Gamma^T\\Gamma\\right)^{-1}X^Ty\n", 367 | "$$\n", 368 | "\n", 369 | "\n", 370 | "(TODO: fill in the missing sigma in the above expression)\n", 371 | "\n", 372 | "That is, it's the same expression as above with the regularization term added to the pseudoinverse!!" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "metadata": {}, 378 | "source": [ 379 | "So the regularized lomb-scargle power is given by:\n", 380 | "\n", 381 | "\n", 382 | "$$\n", 383 | "P_{LS} = \\frac{y^TX\\left(X^TX + \\Gamma^T\\Gamma\\right)^{-1}X^Ty}{y^Ty}\n", 384 | "$$\n" 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "collapsed": false, 390 | "input": [], 391 | "language": "python", 392 | "metadata": {}, 393 | "outputs": [] 394 | } 395 | ], 396 | "metadata": {} 397 | } 398 | ] 399 | } -------------------------------------------------------------------------------- /figures/LSSTsims/LSST.specWindow.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/LSST.specWindow.tar.gz -------------------------------------------------------------------------------- /figures/LSSTsims/LSSTsims.py: -------------------------------------------------------------------------------- 1 | import tarfile 2 | 3 | import numpy as np 4 | 5 | from gatspy.datasets import RRLyraeGenerated, fetch_rrlyrae 6 | 7 | LSST_FILE = 'LSST.specWindow.tar.gz' 8 | 9 | 10 | class LSSTsims(object): 11 | def __init__(self, lsst_file=LSST_FILE): 12 | self.pointings = self._load_sims(lsst_file) 13 | self.S82data = fetch_rrlyrae() 14 | 15 | def _load_sims(self, lsst_file): 16 | files = tarfile.open(lsst_file, 'r') 17 | dtype = np.dtype([('ra', 'float32'), ('dec', 'float32'), 18 | ('mjd', 'float64'), ('filter', 'int'), 19 | ('m5', 'float32')]) 20 | return [np.loadtxt(files.extractfile(member), dtype=dtype) 21 | for member in files] 22 | 23 | def generate_lc(self, pointing_index, n_days, gmag, S82index, 24 | random_state=None): 25 | gen = RRLyraeGenerated(self.S82data.ids[S82index], 26 | random_state=random_state) 27 | 28 | pointing = self.pointings[pointing_index] 29 | pointing = pointing[pointing['mjd'] <= pointing['mjd'].min() + n_days] 30 | t = pointing['mjd'] 31 | filts = pointing['filter'] 32 | m5 = pointing['m5'] 33 | 34 | # generate magnitudes; no errors 35 | mag = np.zeros_like(t) 36 | for i, filt in enumerate('ugrizy'): 37 | mask = (filts == i) 38 | # HACK: for y-band, use z-band template 39 | if filt == 'y': filt = 'z' 40 | mag[mask] = gen.generated(filt, t[mask]) 41 | 42 | # adjust mags to desired r-band mean 43 | gmag_mean = mag[filts == 1].mean() 44 | mag += (gmag - gmag_mean) 45 | 46 | # compute magnitude error from m5 (eq 5 of Ivezic 2014 LSST paper) 47 | gamma = np.array([0.037, 0.038, 0.039, 0.039, 0.040, 0.040]) 48 | x = 10 ** (0.4 * (mag - m5)) 49 | sig2_rand = (0.04 - gamma[filts]) * x + gamma[filts] * x ** 2 50 | sig2_sys = 0.005 ** 2 51 | dmag = np.sqrt(sig2_sys + sig2_rand) 52 | 53 | rng = np.random.RandomState(random_state) 54 | mag += dmag * rng.randn(len(mag)) 55 | 56 | return t, mag, dmag, filts 57 | 58 | 59 | if __name__ == '__main__': 60 | F = LSSTsims() 61 | t, mag, dmag, filts = F.generate_lc(0, n_days=3650, gmag=24.0, S82index=0) 62 | print(len(t)) 63 | 64 | import matplotlib.pyplot as plt 65 | import seaborn; seaborn.set() 66 | 67 | # Note: each night has multiple obs. 68 | for f in np.unique(filts): 69 | mask = (filts == f) 70 | plt.errorbar(t[mask], mag[mask], dmag[mask], fmt='.', label='ugrizy'[f]) 71 | plt.legend() 72 | plt.show() 73 | -------------------------------------------------------------------------------- /figures/LSSTsims/compute_results.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | import numpy as np 5 | 6 | from mapcache import NumpyCache, compute_parallel 7 | from LSSTsims import LSSTsims 8 | from gatspy.periodic import (LombScargleMultiband, 9 | SuperSmootherMultiband, 10 | LombScargleMultibandFast) 11 | 12 | 13 | class SuperSmoother1Band(SuperSmootherMultiband): 14 | """ 15 | Convenience class to fit a single band of data with supersmoother 16 | 17 | This class ignores all data not associated with the given band. 18 | 19 | The main reason for this is that it can then be used as a stand-in for 20 | any multiband class. 21 | """ 22 | def __init__(self, optimizer=None, band='g'): 23 | self.band = band 24 | SuperSmootherMultiband.__init__(self, optimizer) 25 | 26 | def _fit(self, t, y, dy, filts): 27 | import numpy as np 28 | mask = (filts == self.band) 29 | self.t, self.y, self.dy, self.filts = (t[mask], y[mask], 30 | dy[mask], filts[mask]) 31 | self.unique_filts_ = np.unique(self.filts) 32 | return SuperSmootherMultiband._fit(self, self.t, self.y, 33 | self.dy, self.filts) 34 | 35 | 36 | def compute_and_save_periods(Model, outfile, 37 | pointing_indices, ndays, 38 | gmags, template_indices, 39 | model_args=None, model_kwds=None, 40 | Nperiods=5, save_every=5, 41 | parallel=True, client=None, 42 | num_results=None): 43 | """Function to compute periods and save the results""" 44 | cache = NumpyCache(outfile) 45 | keys = list(np.broadcast(pointing_indices, ndays, gmags, template_indices)) 46 | 47 | if num_results is not None: 48 | keys = keys[:num_results] 49 | 50 | # Define a function which, given a key, computes the desired periods. 51 | def find_periods(key, Nperiods=Nperiods, Model=Model, LSSTsims=LSSTsims, 52 | model_args=model_args, model_kwds=model_kwds): 53 | import numpy as np 54 | lsstsim = LSSTsims() 55 | t, y, dy, filts = lsstsim.generate_lc(*key, random_state=0) 56 | 57 | model = Model(*(model_args or ()), **(model_kwds or {})) 58 | model.optimizer.period_range = (0.2, 1.2) 59 | model.optimizer.verbose = 0 60 | model.fit(t, y, dy, filts) 61 | 62 | try: 63 | periods = model.find_best_periods(Nperiods) 64 | except np.linalg.LinAlgError: 65 | periods = np.nan + np.zeros(Nperiods) 66 | except ValueError: 67 | periods = np.nan + np.zeros(Nperiods) 68 | return key, periods 69 | 70 | results = compute_parallel(cache, find_periods, keys, 71 | save_every=save_every, 72 | parallel=parallel, client=client) 73 | 74 | if num_results is not None: 75 | return results 76 | else: 77 | return gather_results(outfile, pointing_indices, ndays, 78 | gmags, template_indices) 79 | 80 | 81 | def gather_results(outfile, pointing_indices, ndays, gmags, template_indices): 82 | if not os.path.exists(outfile): 83 | raise ValueError("Cannot gather results from {0}".format(outfile)) 84 | results = NumpyCache(outfile) 85 | brd = np.broadcast(pointing_indices, ndays, gmags, template_indices) 86 | results = np.array([results.get_row(key) for key in brd]) 87 | return results.reshape(brd.shape + results.shape[-1:]) 88 | 89 | 90 | if __name__ == '__main__': 91 | parallel = True 92 | 93 | if parallel: 94 | # Need some imports on the engine 95 | from IPython.parallel import Client 96 | client = Client() 97 | 98 | dview = client.direct_view() 99 | with dview.sync_imports(): 100 | from gatspy.periodic import (LombScargleMultiband, 101 | LombScargleMultibandFast, 102 | SuperSmootherMultiband) 103 | else: 104 | client = None 105 | 106 | template_indices = np.arange(2 * 23).reshape(2, 23).T 107 | pointing_indices = np.arange(1, 24)[:, None] 108 | ndays = np.array([90, 180, 365, 2*365, 5*365])[:, None, None] 109 | gmags = np.array([20, 21, 22, 23, 24.5])[:, None, None, None] 110 | 111 | kwargs = dict(pointing_indices=pointing_indices, 112 | ndays=ndays, 113 | gmags=gmags, 114 | template_indices=template_indices, 115 | parallel=parallel, client=client, 116 | save_every=4) 117 | 118 | compute_and_save_periods(LombScargleMultiband, 'resultsLSST.npy', 119 | model_kwds=dict(Nterms_base=1, Nterms_band=0), 120 | **kwargs) 121 | 122 | compute_and_save_periods(LombScargleMultiband, 'resultsLSST01.npy', 123 | model_kwds=dict(Nterms_base=0, Nterms_band=1), 124 | **kwargs) 125 | 126 | for i, band in enumerate('ugrizy'): 127 | filename = 'resultsLSST_ssm_{0}.npy'.format(band) 128 | compute_and_save_periods(SuperSmoother1Band, filename, 129 | model_kwds=dict(band=i), 130 | **kwargs) 131 | -------------------------------------------------------------------------------- /figures/LSSTsims/mapcache.py: -------------------------------------------------------------------------------- 1 | """ 2 | mapcache.py: a simple mapped object persistency model, with various backends. 3 | """ 4 | from __future__ import division, print_function 5 | 6 | import pickle 7 | from datetime import datetime 8 | import os 9 | import numpy as np 10 | 11 | try: 12 | # Python 2 13 | from itertools import imap 14 | except ImportError: 15 | imap = map 16 | 17 | 18 | class MapCache(object): 19 | """Base Class for mapped object persistency""" 20 | def __init__(self, filename): 21 | raise NotImplementedError() 22 | 23 | def key_exists(self, key): 24 | raise NotImplementedError() 25 | 26 | def add_row(self, key, val, save=True): 27 | raise NotImplementedError() 28 | 29 | def get_row(self, key): 30 | raise NotImplementedError() 31 | 32 | def save(self): 33 | raise NotImplementedError() 34 | 35 | def load(self): 36 | raise NotImplementedError() 37 | 38 | def compute(self, func, key, *args, **kwargs): 39 | if self.key_exists(key): 40 | return self.get_row(key) 41 | else: 42 | return self.add_row(key, func(key, *args, **kwargs)) 43 | 44 | def compute_iter(self, func, keys, *args, **kwargs): 45 | return [self.compute(func, key, *args, **kwargs) 46 | for key in keys] 47 | 48 | 49 | class PickleCache(MapCache): 50 | """Pickle-backed Persistency Helper""" 51 | def __init__(self, filename): 52 | self.filename = filename 53 | self.dct = self.load() if os.path.exists(filename) else {} 54 | 55 | def __iter__(self): 56 | return iter(self.dct) 57 | 58 | def keys(self): 59 | return self.dct.keys() 60 | 61 | def values(self): 62 | return self.dct.values() 63 | 64 | def items(self): 65 | return self.dct.items() 66 | 67 | def key_exists(self, key): 68 | return key in self.dct 69 | 70 | def add_row(self, key, val, save=True): 71 | try: 72 | self.dct[key] = val 73 | return val 74 | finally: 75 | if save: self.save() 76 | 77 | def get_row(self, key): 78 | return self.dct[key] 79 | 80 | def save(self): 81 | with open(self.filename, 'wb') as f: 82 | pickle.dump(self.dct, f) 83 | 84 | def load(self): 85 | with open(self.filename, 'rb') as f: 86 | return pickle.load(f) 87 | 88 | 89 | class NumpyCache(PickleCache): 90 | """Numpy storage-backed Persistency Helper""" 91 | def __init__(self, filename): 92 | if not filename.endswith('.npy'): 93 | filename += '.npy' 94 | PickleCache.__init__(self, filename) 95 | 96 | def save(self): 97 | np.save(self.filename, _dict_to_array(self.dct)) 98 | 99 | def load(self): 100 | return _array_to_dict(np.load(self.filename)) 101 | 102 | 103 | #---------------------------------------------------------------------- 104 | # Utilities: 105 | 106 | def _array_to_dict(arr): 107 | """Convert mapping array to a dictionary""" 108 | keys = arr['key'] 109 | vals = arr['val'] 110 | 111 | if keys.dtype.names is not None: 112 | names = keys.dtype.names 113 | keys = (tuple(key[name] for name in names) for key in keys) 114 | return dict(zip(keys, vals)) 115 | 116 | 117 | def _dtype_of_key(key): 118 | """Find dtype associated with the object or tuple""" 119 | dtypes = ['i8', 'f8', 'c16', '= 2) 38 | frac = matches.mean(-1).mean(-1) 39 | 40 | print(matches.shape) 41 | print(frac.shape) 42 | 43 | for i, t in enumerate(ndays.ravel()): 44 | plt.plot(gmags.ravel(), frac[:, i], '-s', label='{0:.1f} yrs'.format(t / 365)) 45 | plt.legend() 46 | plt.xlim(20, 27) 47 | plt.ylim(0, 1) 48 | plt.xlabel('g-band mag') 49 | plt.ylabel('fraction of correct periods') 50 | 51 | plt.show() 52 | -------------------------------------------------------------------------------- /figures/LSSTsims/plot_results.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import seaborn; seaborn.set() 4 | 5 | from compute_results import gather_results 6 | from gatspy.datasets import fetch_rrlyrae 7 | 8 | 9 | def olusei_period_criterion(Pobs, Ptrue, Tdays, dphimax = 0.037): 10 | factor = dphimax / Tdays 11 | return abs(Pobs - Ptrue) <= (Ptrue ** 2) * factor 12 | 13 | 14 | def plot_LSST_sims(outfile, pointing_indices, ndays, gmags, template_indices): 15 | results_multi = 'resultsLSST.npy' 16 | results_ssm = 'resultsLSST_ssm_{0}.npy' 17 | 18 | # Get measured periods 19 | Pobs_multi = gather_results(results_multi, 20 | pointing_indices=pointing_indices, 21 | ndays=ndays, 22 | gmags=gmags, 23 | template_indices=template_indices) 24 | Pobs_multi[np.isnan(Pobs_multi)] = 0 25 | 26 | Pobs_ssm = np.array([gather_results(results_ssm.format(band), 27 | pointing_indices=pointing_indices, 28 | ndays=ndays, 29 | gmags=gmags, 30 | template_indices=template_indices) 31 | for band in 'ugriz']) 32 | Pobs_ssm = Pobs_ssm[:, :, :, :, :, 0].transpose(1, 2, 3, 4, 0) 33 | Pobs_ssm[np.isnan(Pobs_ssm)] = 0 34 | 35 | # Get true periods 36 | rrlyrae = fetch_rrlyrae() 37 | Ptrue = np.reshape([rrlyrae.get_metadata(rrlyrae.ids[i])['P'] 38 | for i in template_indices.ravel()], 39 | template_indices.shape) 40 | 41 | # Check for matches 42 | dphimax = 0.37 43 | matches_multi = olusei_period_criterion(Pobs_multi, 44 | Ptrue.reshape(Ptrue.shape + (1,)), 45 | ndays.reshape(ndays.shape + (1,)), 46 | dphimax=dphimax) 47 | results_multi = np.any(matches_multi, -1).mean(-1).mean(-1) 48 | 49 | matches_ssm = olusei_period_criterion(Pobs_ssm, 50 | Ptrue.reshape(Ptrue.shape + (1,)), 51 | ndays.reshape(ndays.shape + (1,)), 52 | dphimax=dphimax) 53 | results_ssm = np.any(matches_ssm, -1).mean(-1).mean(-1) 54 | 55 | fig, ax = plt.subplots() 56 | for t, frac_multi, frac_ssm in reversed(list(zip(ndays.ravel(), 57 | results_multi.T, 58 | results_ssm.T))): 59 | line = ax.plot(gmags.ravel(), frac_multi, 60 | label='{0:.1f} years'.format(t / 365)) 61 | ax.fill_between(gmags.ravel(), frac_ssm, frac_multi, 62 | edgecolor='none', 63 | facecolor=line[0].get_color(), alpha=0.3) 64 | ax.legend(loc='lower left') 65 | ax.set(xlabel='g-band magnitude', 66 | ylabel='Fraction of Periods among Top-5', 67 | title='Multiband Improvement over SuperSmoother for LSST', 68 | xlim=(20, 24.5), ylim=(0, 1)) 69 | 70 | 71 | 72 | 73 | template_indices = np.arange(2 * 23).reshape(2, 23).T 74 | pointing_indices = np.arange(1, 24)[:, None] 75 | ndays = np.array([180, 365, 2*365])[:, None, None] 76 | gmags = np.array([20, 22, 24.5])[:, None, None, None] 77 | 78 | plot_LSST_sims('resultsLSST.npy', 79 | pointing_indices=pointing_indices, 80 | ndays=ndays, 81 | gmags=gmags, 82 | template_indices=template_indices) 83 | plt.show() 84 | -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST01.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST01.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST_ssm_g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST_ssm_g.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST_ssm_i.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST_ssm_i.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST_ssm_r.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST_ssm_r.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST_ssm_u.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST_ssm_u.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST_ssm_y.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST_ssm_y.npy -------------------------------------------------------------------------------- /figures/LSSTsims/resultsLSST_ssm_z.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakevdp/multiband_LS/75aba85c3c7d47643deeabcfb15a6214cd086d61/figures/LSSTsims/resultsLSST_ssm_z.npy -------------------------------------------------------------------------------- /figures/S82sims/compute_results.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the code used to compute various things used in the paper. 3 | """ 4 | import numpy as np 5 | import os 6 | from datetime import datetime 7 | 8 | from mapcache import NumpyCache, compute_parallel 9 | from gatspy.datasets import fetch_rrlyrae 10 | from gatspy.periodic import (LombScargleMultiband, SuperSmoother, 11 | SuperSmootherMultiband) 12 | 13 | 14 | class SuperSmoother1Band(SuperSmootherMultiband): 15 | """ 16 | Convenience class to fit a single band of data with supersmoother 17 | 18 | This class ignores all data not associated with the given band. 19 | 20 | The main reason for this is that it can then be used as a stand-in for 21 | any multiband class. 22 | """ 23 | def __init__(self, optimizer=None, band='g'): 24 | self.band = band 25 | SuperSmootherMultiband.__init__(self, optimizer) 26 | 27 | def _fit(self, t, y, dy, filts): 28 | import numpy as np 29 | mask = (filts == self.band) 30 | self.t, self.y, self.dy, self.filts = (t[mask], y[mask], 31 | dy[mask], filts[mask]) 32 | self.unique_filts_ = np.unique(self.filts) 33 | return SuperSmootherMultiband._fit(self, self.t, self.y, 34 | self.dy, self.filts) 35 | 36 | 37 | def compute_and_save_periods(rrlyrae, Model, outfile, 38 | model_args=None, model_kwds=None, 39 | Nperiods=5, save_every=5, 40 | parallel=True, client=None, 41 | num_results=None): 42 | """Function to compute periods and save the results""" 43 | cache = NumpyCache(outfile) 44 | lcids = rrlyrae.ids 45 | if num_results is not None: 46 | lcids = lcids[:num_results] 47 | 48 | # Define a function which, given an lcid, computes the desired periods. 49 | def find_periods(lcid, Nperiods=Nperiods, 50 | rrlyrae=rrlyrae, Model=Model, 51 | model_args=model_args, model_kwds=model_kwds): 52 | t, y, dy, filts = rrlyrae.get_lightcurve(lcid) 53 | model = Model(*(model_args or ()), **(model_kwds or {})) 54 | model.optimizer.period_range = (0.2, 1.2) 55 | model.optimizer.verbose = 0 56 | model.fit(t, y, dy, filts) 57 | return lcid, model.find_best_periods(Nperiods) 58 | 59 | return compute_parallel(cache, find_periods, lcids, 60 | save_every=save_every, 61 | parallel=parallel, client=client) 62 | 63 | 64 | def gather_results(outfile, ids): 65 | if not os.path.exists(outfile): 66 | raise ValueError("Cannot gather results from {0}".format(outfile)) 67 | results = NumpyCache(outfile) 68 | return np.array([results.get_row(lcid) for lcid in ids]) 69 | 70 | 71 | if __name__ == '__main__': 72 | rrlyrae = fetch_rrlyrae() 73 | rrlyrae_partial = fetch_rrlyrae(partial=True) 74 | 75 | parallel = True 76 | ssm_bands = ['g'] 77 | 78 | if parallel: 79 | # Need some imports on the engine 80 | from IPython.parallel import Client 81 | client = Client() 82 | dview = client.direct_view() 83 | with dview.sync_imports(): 84 | from gatspy.periodic import (LombScargleMultiband, SuperSmoother, 85 | SuperSmootherMultiband) 86 | 87 | # Make sure necessary data is fetched on all clients. 88 | # Otherwise, there can be cross-talk which results in an error. 89 | for c in client: 90 | c.block = True 91 | c.execute('from gatspy.datasets import fetch_rrlyrae;' 92 | 'fetch_rrlyrae()') 93 | else: 94 | client = None 95 | 96 | # Now time to compute the results. Here are the keywords used throughout: 97 | kwargs = dict(Nperiods=5, save_every=4, parallel=parallel, client=client) 98 | 99 | # Full Dataset 100 | compute_and_save_periods(rrlyrae, LombScargleMultiband, 101 | model_kwds=dict(Nterms_base=1, Nterms_band=0), 102 | outfile='res_multiband_1_0', **kwargs) 103 | compute_and_save_periods(rrlyrae, LombScargleMultiband, 104 | model_kwds=dict(Nterms_base=0, Nterms_band=1), 105 | outfile='res_multiband_0_1', **kwargs) 106 | for band in ssm_bands: 107 | compute_and_save_periods(rrlyrae, SuperSmoother1Band, 108 | model_kwds=dict(band=band), 109 | outfile='res_supersmoother_{0}'.format(band), 110 | **kwargs) 111 | 112 | # Partial Dataset 113 | compute_and_save_periods(rrlyrae_partial, LombScargleMultiband, 114 | model_kwds=dict(Nterms_base=1, Nterms_band=0), 115 | outfile='res_partial_multiband_1_0', **kwargs) 116 | compute_and_save_periods(rrlyrae_partial, LombScargleMultiband, 117 | model_kwds=dict(Nterms_base=0, Nterms_band=1), 118 | outfile='res_partial_multiband_0_1', **kwargs) 119 | for band in ssm_bands: 120 | compute_and_save_periods(rrlyrae_partial, SuperSmoother1Band, 121 | model_kwds=dict(band=band), 122 | outfile=('res_partial_supersmoother_' 123 | '{0}'.format(band)), **kwargs) 124 | -------------------------------------------------------------------------------- /figures/S82sims/mapcache.py: -------------------------------------------------------------------------------- 1 | """ 2 | mapcache.py: a simple mapped object persistency model, with various backends. 3 | """ 4 | from __future__ import division, print_function 5 | 6 | import pickle 7 | from datetime import datetime 8 | import os 9 | import numpy as np 10 | 11 | try: 12 | # Python 2 13 | from itertools import imap 14 | except ImportError: 15 | imap = map 16 | 17 | 18 | class MapCache(object): 19 | """Base Class for mapped object persistency""" 20 | def __init__(self, filename): 21 | raise NotImplementedError() 22 | 23 | def key_exists(self, key): 24 | raise NotImplementedError() 25 | 26 | def add_row(self, key, val, save=True): 27 | raise NotImplementedError() 28 | 29 | def get_row(self, key): 30 | raise NotImplementedError() 31 | 32 | def save(self): 33 | raise NotImplementedError() 34 | 35 | def load(self): 36 | raise NotImplementedError() 37 | 38 | def compute(self, func, key, *args, **kwargs): 39 | if self.key_exists(key): 40 | return self.get_row(key) 41 | else: 42 | return self.add_row(key, func(key, *args, **kwargs)) 43 | 44 | def compute_iter(self, func, keys, *args, **kwargs): 45 | return [self.compute(func, key, *args, **kwargs) 46 | for key in keys] 47 | 48 | 49 | class PickleCache(MapCache): 50 | """Pickle-backed Persistency Helper""" 51 | def __init__(self, filename): 52 | self.filename = filename 53 | self.dct = self.load() if os.path.exists(filename) else {} 54 | 55 | def __iter__(self): 56 | return iter(self.dct) 57 | 58 | def keys(self): 59 | return self.dct.keys() 60 | 61 | def values(self): 62 | return self.dct.values() 63 | 64 | def items(self): 65 | return self.dct.items() 66 | 67 | def key_exists(self, key): 68 | return key in self.dct 69 | 70 | def add_row(self, key, val, save=True): 71 | try: 72 | self.dct[key] = val 73 | return val 74 | finally: 75 | if save: self.save() 76 | 77 | def get_row(self, key): 78 | return self.dct[key] 79 | 80 | def save(self): 81 | with open(self.filename, 'wb') as f: 82 | pickle.dump(self.dct, f) 83 | 84 | def load(self): 85 | with open(self.filename, 'rb') as f: 86 | return pickle.load(f) 87 | 88 | 89 | class NumpyCache(PickleCache): 90 | """Numpy storage-backed Persistency Helper""" 91 | def __init__(self, filename): 92 | if not filename.endswith('.npy'): 93 | filename += '.npy' 94 | PickleCache.__init__(self, filename) 95 | 96 | def save(self): 97 | np.save(self.filename, _dict_to_array(self.dct)) 98 | 99 | def load(self): 100 | return _array_to_dict(np.load(self.filename)) 101 | 102 | 103 | #---------------------------------------------------------------------- 104 | # Utilities: 105 | 106 | def _array_to_dict(arr): 107 | """Convert mapping array to a dictionary""" 108 | keys = arr['key'] 109 | vals = arr['val'] 110 | 111 | if keys.dtype.names is not None: 112 | names = keys.dtype.names 113 | keys = (tuple(key[name] for name in names) for key in keys) 114 | return dict(zip(keys, vals)) 115 | 116 | 117 | def _dtype_of_key(key): 118 | """Find dtype associated with the object or tuple""" 119 | dtypes = ['i8', 'f8', 'c16', ' 0: 43 | ax.plot(P1, fn(P1), ':k', alpha=0.7, lw=1, zorder=1) 44 | ax.text(1.21, fn(1.2), str(matches(fn(Px), Py)), 45 | size=10, va='center', ha='left', color='gray') 46 | else: 47 | ax.plot(fn(P1), P1, ':k', alpha=0.7, lw=1, zorder=1) 48 | ax.text(fn(1.2), 1.21, str(matches(Px, fn(Py))), 49 | size=10, va='bottom', ha='center', color='gray') 50 | 51 | for n in aliases: 52 | fn = lambda P, n=n: P / n 53 | ax.plot(P1, fn(P1), '--', color='gray', alpha=0.7, lw=1, zorder=1) 54 | if n < 1: 55 | ax.text(fn(1.2), 1.21, str(matches(Px, fn(Py))), 56 | size=10, va='bottom', ha='center', color='gray') 57 | else: 58 | ax.text(1.21, fn(1.2), str(matches(fn(Px), Py)), 59 | size=10, va='center', ha='left', color='gray') 60 | 61 | ax.set_xlim(0.1, 1.2) 62 | ax.set_ylim(0.1, 1.2) 63 | 64 | 65 | top_matches = np.any((abs(Px_all.T - Py) < 0.01), 0).sum() 66 | ax.text(1.17, 0.11, "Top 5 Matches: {0}/{1}".format(top_matches, len(Px)), 67 | size=10, ha='right', va='bottom') 68 | 69 | 70 | def plot_filter(t, y, verbose=True): 71 | """Simple filter which removes unnecessary points for plotting""" 72 | dy = y[1:] - y[:-1] 73 | mask = (dy[1:] * dy[:-1] <= 0) 74 | mask = np.concatenate([[True], mask, [True]]) 75 | 76 | if verbose: 77 | print("Reducing from {0} to {1} points for plotting" 78 | "".format(len(t), mask.sum())) 79 | return t[mask], y[mask] 80 | 81 | 82 | def plot_example_lightcurve(rrlyrae, lcid): 83 | fig = plt.figure(figsize=(10, 4)) 84 | 85 | gs = plt.GridSpec(2, 2, 86 | left=0.07, right=0.95, wspace=0.1, 87 | bottom=0.15, top=0.9) 88 | ax = [fig.add_subplot(gs[:, 0]), 89 | fig.add_subplot(gs[0, 1]), 90 | fig.add_subplot(gs[1, 1])] 91 | 92 | t, y, dy, filts = rrlyrae.get_lightcurve(lcid) 93 | 94 | # don't plot data with huge errorbars 95 | mask = (dy < 1) 96 | t, y, dy, filts = t[mask], y[mask], dy[mask], filts[mask] 97 | period = rrlyrae.get_metadata(lcid)['P'] 98 | phase = (t % period) / period 99 | 100 | for band in 'ugriz': 101 | mask = (filts == band) 102 | ax[0].errorbar(phase[mask], y[mask], dy[mask], 103 | fmt='.', label=band) 104 | ax[0].legend(loc='upper left', ncol=3) 105 | ax[0].set(xlabel='phase', ylabel='magnitude', 106 | title='Folded Data (P={0:.3f} days)'.format(period)) 107 | ylim = ax[0].get_ylim() 108 | ax[0].set_ylim(ylim[1], ylim[0] - 0.2 * (ylim[1] - ylim[0])) 109 | 110 | periods = np.linspace(0.2, 1.0, 4000) 111 | 112 | models = [SuperSmoother1Band(band='g'), 113 | LombScargleMultiband(Nterms_base=1, Nterms_band=0)] 114 | 115 | colors = seaborn.color_palette() 116 | 117 | for axi, model, color in zip(ax[1:], models, colors): 118 | model.fit(t, y, dy, filts) 119 | per, scr = plot_filter(periods, model.score(periods)) 120 | axi.plot(per, scr, lw=1, color=color) 121 | axi.set_ylim(0, 1) 122 | 123 | ax[1].xaxis.set_major_formatter(plt.NullFormatter()) 124 | ax[1].set_title("SuperSmoother on g-band") 125 | ax[2].set_title("Shared-phase Multiband") 126 | 127 | return fig, ax 128 | 129 | 130 | 131 | def plot_periods(ssm_file, mbls_file, rrlyrae): 132 | ids = list(rrlyrae.ids) 133 | sesar_periods = np.array([rrlyrae.get_metadata(lcid)['P'] 134 | for lcid in ids]) 135 | ssm_periods = gather_results(ssm_file, ids) 136 | mbls_periods = gather_results(mbls_file, ids) 137 | 138 | fig, ax = plt.subplots(1, 2, figsize=(10, 4), sharex=True, sharey=True) 139 | fig.subplots_adjust(left=0.07, right=0.95, wspace=0.1, 140 | bottom=0.15, top=0.9) 141 | 142 | colors = seaborn.color_palette() 143 | 144 | plot_period_comparison(ax[0], ssm_periods, sesar_periods, 145 | color=colors[0]) 146 | plot_period_comparison(ax[1], mbls_periods, sesar_periods, 147 | color=colors[1]) 148 | 149 | ax[0].set_xlabel('supersmoother period (days)') 150 | ax[0].set_ylabel('Sesar 2010 period (days)') 151 | ax[1].set_xlabel('multiband period (days)') 152 | 153 | ax[0].set_title("SuperSmoother (g-band)", y=1.04) 154 | ax[1].set_title("Shared-phase Multiband", y=1.04) 155 | 156 | return fig, ax 157 | 158 | 159 | if __name__ == '__main__': 160 | rrlyrae = fetch_rrlyrae() 161 | lcid = list(rrlyrae.ids)[482] 162 | 163 | fig, ax = plot_example_lightcurve(rrlyrae, lcid) 164 | fig.savefig('fig07a.pdf') 165 | 166 | fig, ax = plot_periods(ssm_file='S82sims/res_supersmoother_g.npy', 167 | mbls_file='S82sims/res_multiband_1_0.npy', 168 | rrlyrae=rrlyrae) 169 | fig.savefig('fig07b.pdf') 170 | plt.show() 171 | 172 | -------------------------------------------------------------------------------- /figures/fig08_compare_periods_reduced.py: -------------------------------------------------------------------------------- 1 | """ 2 | Plot figures comparing periods between multiband lomb scargle, 3 | supersmoother, and Sesar 2010 for reduced dataset. 4 | """ 5 | import matplotlib.pyplot as plt 6 | from fig07_compare_periods import plot_periods, plot_example_lightcurve 7 | from gatspy.datasets import fetch_rrlyrae 8 | 9 | rrlyrae = fetch_rrlyrae(partial=True) 10 | lcid = list(rrlyrae.ids)[482] 11 | 12 | fig, ax = plot_example_lightcurve(rrlyrae, lcid) 13 | fig.savefig('fig08a.pdf') 14 | 15 | fig, ax = plot_periods(ssm_file='S82sims/res_partial_supersmoother_g.npy', 16 | mbls_file='S82sims/res_partial_multiband_1_0.npy', 17 | rrlyrae=rrlyrae) 18 | fig.savefig('fig08b.pdf') 19 | plt.show() 20 | -------------------------------------------------------------------------------- /figures/fig09_LSST_sims.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import seaborn; seaborn.set() 6 | 7 | from gatspy.datasets import fetch_rrlyrae 8 | 9 | import os, sys 10 | sys.path.insert(0, 'LSSTsims') 11 | from compute_results import gather_results 12 | 13 | 14 | def olusei_period_criterion(Pobs, Ptrue, Tdays, dphimax = 0.037): 15 | factor = dphimax / Tdays 16 | return abs(Pobs - Ptrue) <= (Ptrue ** 2) * factor 17 | 18 | 19 | template_indices = np.arange(2 * 23).reshape(2, 23).T 20 | pointing_indices = np.arange(1, 24)[:, None] 21 | ndays = np.array([180, 365, 2*365, 5*365])[:, None, None] 22 | gmags = np.array([20, 21, 22, 23, 24.5])[:, None, None, None] 23 | 24 | results_multi = 'LSSTsims/resultsLSST.npy' 25 | results_ssm = 'LSSTsims/resultsLSST_ssm_{0}.npy' 26 | 27 | # Get measured periods 28 | Pobs_multi = gather_results(results_multi, 29 | pointing_indices=pointing_indices, 30 | ndays=ndays, 31 | gmags=gmags, 32 | template_indices=template_indices) 33 | Pobs_multi[np.isnan(Pobs_multi)] = 0 34 | 35 | Pobs_ssm = np.array([gather_results(results_ssm.format(band), 36 | pointing_indices=pointing_indices, 37 | ndays=ndays, 38 | gmags=gmags, 39 | template_indices=template_indices) 40 | for band in 'ugriz']) 41 | Pobs_ssm = Pobs_ssm[:, :, :, :, :, 0].transpose(1, 2, 3, 4, 0) 42 | Pobs_ssm[np.isnan(Pobs_ssm)] = 0 43 | 44 | # Get true periods 45 | rrlyrae = fetch_rrlyrae() 46 | Ptrue = np.reshape([rrlyrae.get_metadata(rrlyrae.ids[i])['P'] 47 | for i in template_indices.ravel()], 48 | template_indices.shape) 49 | 50 | # Check for matches 51 | dphimax = 0.37 52 | matches_multi = olusei_period_criterion(Pobs_multi, 53 | Ptrue.reshape(Ptrue.shape + (1,)), 54 | ndays.reshape(ndays.shape + (1,)), 55 | dphimax=dphimax) 56 | results_multi = np.any(matches_multi, -1).mean(-1).mean(-1) 57 | 58 | matches_ssm = olusei_period_criterion(Pobs_ssm, 59 | Ptrue.reshape(Ptrue.shape + (1,)), 60 | ndays.reshape(ndays.shape + (1,)), 61 | dphimax=dphimax) 62 | results_ssm = np.any(matches_ssm, -1).mean(-1).mean(-1) 63 | 64 | fig, ax = plt.subplots() 65 | for t, frac_multi, frac_ssm in reversed(list(zip(ndays.ravel(), 66 | results_multi.T, 67 | results_ssm.T))): 68 | line = ax.plot(gmags.ravel(), frac_multi, 69 | label='{0:.1f} years'.format(t / 365)) 70 | line = ax.plot(gmags.ravel(), frac_ssm, 71 | color=line[0].get_color(), linestyle='dashed') 72 | ax.fill_between(gmags.ravel(), frac_ssm, frac_multi, 73 | edgecolor='none', 74 | facecolor=line[0].get_color(), alpha=0.2) 75 | 76 | ax.add_artist(ax.legend(loc='lower left')) 77 | ref_lines = ax.plot([0], [0], '-k') + ax.plot([0], [0], '--k') 78 | ax.legend(ref_lines, ['multiband', 'supersmoother'], loc='lower center') 79 | 80 | ax.set(xlabel='g-band magnitude', 81 | ylabel='Fraction of Periods among Top-5', 82 | title='Multiband Improvement over SuperSmoother for LSST', 83 | xlim=(20, 24.5), ylim=(0, 1.05)) 84 | 85 | fig.savefig('fig09.pdf') 86 | plt.show() 87 | -------------------------------------------------------------------------------- /figures/fig_multiband_supersmoother.py: -------------------------------------------------------------------------------- 1 | """ 2 | Here we plot a typical approach to the multi-band periodogram: treating each 3 | band separately, and taking a majority vote between the bands. 4 | """ 5 | 6 | import sys 7 | import os 8 | sys.path.append(os.path.abspath('../..')) 9 | 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | 13 | # Use seaborn settings for plot styles 14 | import seaborn; seaborn.set() 15 | 16 | from gatspy.datasets import RRLyraeGenerated 17 | from gatspy.periodic import SuperSmoother 18 | 19 | 20 | # Choose a Sesar 2010 object to base our fits on 21 | lcid = 1019544 22 | rrlyrae = RRLyraeGenerated(lcid, random_state=0) 23 | print("Extinction A_r = {0:.4f}".format(rrlyrae.obsmeta['rExt'])) 24 | 25 | # Generate data in a 6-month observing season 26 | Nobs = 60 27 | rng = np.random.RandomState(0) 28 | 29 | nights = np.arange(180) 30 | rng.shuffle(nights) 31 | nights = nights[:Nobs] 32 | 33 | t = 57000 + nights + 0.05 * rng.randn(Nobs) 34 | dy = 0.06 + 0.01 * rng.randn(Nobs) 35 | mags = np.array([rrlyrae.generated(band, t, err=dy, corrected=False) 36 | for band in 'ugriz']) 37 | 38 | periods = np.linspace(0.2, 0.9, 1000) 39 | 40 | #---------------------------------------------------------------------- 41 | # First figure: 42 | # Compute the supersmoother periodogram in each band 43 | 44 | P = [SuperSmoother().fit(t, m, dy).periodogram(periods) for m in mags] 45 | 46 | fig, ax = plt.subplots(1, 2, figsize=(10, 4)) 47 | fig.subplots_adjust(left=0.07, right=0.95, wspace=0.1, bottom=0.15) 48 | 49 | for i, band in enumerate('ugriz'): 50 | ax[0].errorbar((t / rrlyrae.period) % 1, mags[i], dy, 51 | fmt='.', label=band) 52 | ax[0].set_ylim(18, 14.5) 53 | ax[0].legend(loc='upper left', fontsize=12, ncol=3) 54 | ax[0].set_title('Folded Data (P={0:.3f} days)'.format(rrlyrae.period)) 55 | ax[0].set_xlabel('phase') 56 | ax[0].set_ylabel('magnitude') 57 | 58 | for i, (Pi, band) in enumerate(zip(P, 'ugriz')): 59 | offset = 4 - i 60 | ax[1].plot(periods, Pi + offset, lw=1) 61 | ax[1].text(0.89, 0.7 + offset, band, fontsize=14, ha='right', va='top') 62 | ax[1].set_title('Periodogram in Each Band') 63 | ax[1].set_ylim(0, 5) 64 | ax[1].yaxis.set_major_formatter(plt.NullFormatter()) 65 | ax[1].set_xlabel('Period (days)') 66 | ax[1].set_ylabel('power + offset') 67 | 68 | #---------------------------------------------------------------------- 69 | # Second figure: 70 | # Use a truncated dataset 71 | 72 | # Alternate between the five bands. Because the times are randomized, 73 | # the filter orders will also be randomized. 74 | filts = np.take(list('ugriz'), np.arange(Nobs), mode='wrap') 75 | mags = mags[np.arange(Nobs) % 5, np.arange(Nobs)] 76 | 77 | masks = [(filts == band) for band in 'ugriz'] 78 | 79 | P = [SuperSmoother().fit(t[mask], mags[mask], 80 | dy[mask]).periodogram(periods) 81 | for mask in masks] 82 | 83 | fig, ax = plt.subplots(1, 2, figsize=(10, 4)) 84 | fig.subplots_adjust(left=0.07, right=0.95, wspace=0.1, bottom=0.15) 85 | 86 | for band, mask in zip('ugriz', masks): 87 | ax[0].errorbar(t[mask] / rrlyrae.period % 1, mags[mask], dy[mask], 88 | fmt='.', label=band) 89 | ax[0].set_ylim(18, 14.5) 90 | ax[0].legend(loc='upper left', fontsize=12, ncol=3) 91 | ax[0].set_title('Folded Data (P={0:.3f} days)'.format(rrlyrae.period)) 92 | ax[0].set_xlabel('phase') 93 | ax[0].set_ylabel('magnitude') 94 | 95 | for i, (Pi, band) in enumerate(zip(P, 'ugriz')): 96 | offset = 4 - i 97 | ax[1].plot(periods, Pi + offset, lw=1) 98 | ax[1].text(0.89, 0.7 + offset, band, fontsize=14, ha='right', va='top') 99 | ax[1].set_title('Periodogram in Each Band') 100 | ax[1].set_ylim(0, 5) 101 | ax[1].yaxis.set_major_formatter(plt.NullFormatter()) 102 | ax[1].set_xlabel('Period (days)') 103 | ax[1].set_ylabel('power + offset') 104 | 105 | 106 | plt.show() 107 | -------------------------------------------------------------------------------- /figures/fig_num_observations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import seaborn as sns 3 | import matplotlib.pyplot as plt 4 | 5 | from gatspy.datasets import fetch_rrlyrae 6 | 7 | 8 | rrlyrae = fetch_rrlyrae() 9 | rrlyrae_partial = fetch_rrlyrae(partial=True) 10 | 11 | data = [rrlyrae.get_lightcurve(lcid) 12 | for lcid in rrlyrae.ids] 13 | data_partial = [rrlyrae_partial.get_lightcurve(lcid) 14 | for lcid in rrlyrae.ids] 15 | 16 | tminmax = np.array([[t.min(), t.max()] for t,_,_,_ in data]) 17 | print(tminmax.min(0), tminmax.max(0)) 18 | 19 | counts = np.array([[np.sum(filts == f) for f in 'ugriz'] 20 | for t, y, dy, filts in data]) 21 | counts_partial = np.array([[np.sum(filts == f) for f in 'ugriz'] 22 | for t, y, dy, filts in data_partial]) 23 | 24 | print(np.mean(counts, 0), np.median(counts, 0)) 25 | print(np.mean(counts_partial, 0), np.median(counts_partial, 0)) 26 | 27 | 28 | fig, ax = plt.subplots(2) 29 | sns.violinplot(counts, ax=ax[0], names='ugriz') 30 | sns.violinplot(counts_partial, ax=ax[1], names='ugriz') 31 | 32 | ax[0].set_ylim(20, 100) 33 | ax[1].set_ylim(0, 20) 34 | 35 | plt.show() 36 | -------------------------------------------------------------------------------- /gh-publisher-scripts/README.md: -------------------------------------------------------------------------------- 1 | gh-publisher 2 | ============ 3 | 4 | This is a small package of software which will help you configure an 5 | automated build and publish process, using Travis CI and GitHub Pages. 6 | 7 | See https://github.com/ewanmellor/gh-publisher for more details. 8 | 9 | 10 | License 11 | ------- 12 | 13 | index.html, scale.fix.js, pygment_trac.css, styles.css: these are modified 14 | versions of https://github.com/orderedlist/minimal, which is licensed under a 15 | Creative Commons Attribution-ShareAlike 3.0 Unported License. 16 | http://creativecommons.org/licenses/by-sa/3.0/. 17 | 18 | The remaining files in this repository are by Ewan Mellor, and are dedicated 19 | to the public domain. 20 | To the extent possible under law, Ewan Mellor has waived all copyright and 21 | related or neighboring rights to this work. 22 | http://creativecommons.org/publicdomain/zero/1.0/. 23 | -------------------------------------------------------------------------------- /gh-publisher-scripts/build.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Use this file to configure your build. 3 | # 4 | # You have the following variables available: 5 | # 6 | # GH_PUBLISHER_PROJECT_DIR - The root of your project repository. 7 | # GH_PUBLISHER_SCRIPTS_DIR - The gh-publisher-scripts directory. 8 | # 9 | # The current working directory is $GH_PUBLISHER_PROJECT_DIR. 10 | # 11 | echo "$PWD" 12 | 13 | cd "$GH_PUBLISHER_PROJECT_DIR" 14 | cd writeup; make; cd .. 15 | cp writeup/paper.pdf ./ 16 | -------------------------------------------------------------------------------- /gh-publisher-scripts/copy.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Use this file to configure whichever artifacts you wish to publish. 3 | # 4 | # You have the following variables available: 5 | # 6 | # GH_PUBLISHER_PROJECT_DIR - The root of your project repository. 7 | # GH_PUBLISHER_PUBLISH_DIR - The destination for files to be published. 8 | # GH_PUBLISHER_SCRIPTS_DIR - The gh-publisher-scripts directory. 9 | # 10 | # The current working directory is $GH_PUBLISHER_PROJECT_DIR. 11 | # 12 | 13 | find . \( \ 14 | -name \*.html -o \ 15 | -name \*.css -o \ 16 | -name \*.js -o \ 17 | -name \*.gif -o \ 18 | -name \*.jpeg -o \ 19 | -name \*.jpg -o \ 20 | -name \*.png -o \ 21 | -name \*.pdf \ 22 | \) -print0 | 23 | rsync -av --files-from=- --from0 ./ "$GH_PUBLISHER_PUBLISH_DIR" 24 | -------------------------------------------------------------------------------- /gh-publisher-scripts/example.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | script: /bin/bash gh-publisher-scripts/gh-publisher.sh 5 | before_install: 6 | - yes "" | sudo apt-add-repository ppa:texlive-backports/ppa 7 | - sudo apt-get update -y 8 | - sudo apt-get install -y 9 | inkscape 10 | texlive-fonts-recommended 11 | texlive-latex-extra 12 | texlive-latex-recommended 13 | texlive-xetex 14 | -------------------------------------------------------------------------------- /gh-publisher-scripts/front-matter.yml: -------------------------------------------------------------------------------- 1 | # 2 | # This file controls the details on the published HTML page. 3 | # 4 | # All the values here are optional, but it will obviously look a lot 5 | # better if you fill them in. 6 | # 7 | # Remove the # from the front of each line, and then edit appropriately. 8 | # 9 | 10 | title: "Periodograms for Multiband Astronomical Time Series" 11 | 12 | abstract: "This site displays an automated build of the LaTeX source of the paper, which is hosted on GitHub. The arXiv url is http://arxiv.org/abs/1502.01344" 13 | 14 | # This controls which file is shown on the right-hand side of the published 15 | # HTML page. By default it will take the first PDF or HTML file that it 16 | # finds. If that doesn't work for you, you can specify the correct file here. 17 | iframe_src: paper.pdf 18 | 19 | # You can specify multiple authors, just follow the pattern below. 20 | # These fields can contain HTML tags. You probably want to put
21 | # in the address somewhere, indicating a line break. 22 | 23 | authors: 24 | - name: Jake VanderPlas 25 | address: eScience Institute, University of Washington 26 | - name: Zeljko Ivezic 27 | address: Astronomy department, University of Washington 28 | -------------------------------------------------------------------------------- /gh-publisher-scripts/gh-publisher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | function readlink_f() { 6 | if [ $(uname) = 'Darwin' ] 7 | then 8 | DIR=$(echo "${1%/*}") 9 | FILE=$(basename "$1") 10 | (cd "$DIR" && echo "$(pwd -P)/$FILE") 11 | else 12 | readlink -f "$1" 13 | fi 14 | } 15 | 16 | gh_token="${GH_TOKEN-}" 17 | git_name="${GIT_NAME-Automated build by gh-publisher}" 18 | git_email="${GIT_EMAIL-not.a.real.person@example.com}" 19 | git_remote="${GIT_REMOTE-origin}" 20 | git_publish_branch="${GIT_PUBLISH_BRANCH-gh-pages}" 21 | 22 | if [ -z "$gh_token" ] 23 | then 24 | echo "GH_TOKEN is not set. Cannot proceed." >&2 25 | echo "Please see the installation instructions." >&2 26 | exit 1 27 | fi 28 | 29 | scripts_dir=$(dirname $(readlink_f "$0")) 30 | scripts_dir_name=$(basename "$scripts_dir") 31 | project_dir=$(dirname "$scripts_dir") 32 | 33 | cd "$scripts_dir" 34 | git_url=$(git config "remote.${git_remote}.url" | 35 | sed -e 's,git://*,https://,' -e "s,https://,https://${gh_token}@,") 36 | git_rev=$(git rev-parse HEAD) 37 | github_username=$(echo "$git_url" | sed -e 's,^https://[^/]*/\([^/]*\)/.*$,\1,') 38 | github_repo=$(echo "$git_url" | sed -e 's,^https://[^/]*/[^/]*/\([^/.]*\).*$,\1,') 39 | 40 | tmpdir=$(mktemp -t publish.XXXXXX -d) 41 | (cd "$tmpdir" 42 | git clone "$git_url" "publish" 43 | cd publish 44 | git config user.name "$git_name" 45 | git config user.email "$git_email" 46 | if git branch -av | grep -q "remotes/origin/$git_publish_branch" 47 | then 48 | git checkout "$git_publish_branch" 49 | else 50 | git checkout --orphan "$git_publish_branch" 51 | git reset . 52 | git clean -dfx 53 | fi 54 | ) 55 | publish_dir="$tmpdir/publish" 56 | 57 | export GH_PUBLISHER_PROJECT_DIR="$project_dir" 58 | export GH_PUBLISHER_PUBLISH_DIR="$publish_dir" 59 | export GH_PUBLISHER_SCRIPTS_DIR="$scripts_dir" 60 | 61 | (cd "$project_dir" && /bin/bash "$scripts_dir/build.sh") 62 | (cd "$project_dir" && /bin/bash "$scripts_dir/copy.sh") 63 | 64 | published_scripts="$publish_dir/$scripts_dir_name" 65 | if [ -d "$published_scripts" ] 66 | then 67 | rsync -av "$published_scripts"/* "$publish_dir" 68 | rm -rf "$published_scripts" 69 | fi 70 | 71 | config_yml="$publish_dir/_config.yml" 72 | cp "$scripts_dir/front-matter.yml" "$config_yml" 73 | echo "github_username: $github_username" >>"$config_yml" 74 | echo "github_repo: $github_repo" >>"$config_yml" 75 | if ! grep -q "^iframe_src:" "$config_yml" 76 | then 77 | iframe_src=$( (cd "$publish_dir"; ls *.pdf || true) | sort | head -n1) 78 | if [ -z "$iframe_src" ] 79 | then 80 | iframe_src=$( (cd "$publish_dir"; ls *.html || true) | sort | head -n1) 81 | fi 82 | if [ -n "$iframe_src" ] 83 | then 84 | echo "iframe_src: $iframe_src" >>"$config_yml" 85 | fi 86 | fi 87 | if ! grep -q "^title:" "$config_yml" 88 | then 89 | echo "title: $github_repo" >>"$config_yml" 90 | fi 91 | 92 | (cd "$publish_dir" 93 | git add -A 94 | if ! git diff --quiet --staged 95 | then 96 | git commit -m "Built from revision $git_rev." 97 | git push -q origin "$git_publish_branch:$git_publish_branch" 98 | else 99 | echo "Nothing has changed! I hope that's what you expected." >&2 100 | fi 101 | ) 102 | -------------------------------------------------------------------------------- /gh-publisher-scripts/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ site.title }} 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 |
21 |
22 |

{{site.title }}

23 | {% for author in site.authors %} 24 | 25 |

{{ author.name }}
26 | {{ author.address }}

27 | 28 | {% endfor %} 29 | 30 | {% if site.abstract %} 31 |

{{ site.abstract }}

32 | {% endif %} 33 | 34 |

View the Project on GitHub {{ site.github_username }}/{{ site.github_repo }}

35 | 36 | 39 |
40 | {% if site.iframe_src %} 41 |
42 |