{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example of using PyImfit to Estimate B/T Uncertainties"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a Jupyter notebook demonstrating how to use PyImfit and bootstrap resampling to estimate uncertainties for derived quantities of fits, such as $B/T$ values.\n",
    "\n",
    "If you are seeing this as part of the readthedocs.org HTML documentation, you can retrieve the original .ipynb file\n",
    "[here](https://github.com/perwin/pyimfit/blob/master/docs/pyimfit_bootstrap_BtoT.ipynb).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "PyImfit will estimate uncertainties for individual model parameters from a fit (if you use the default Levenberg-Marquardt minimizer) -- e.g., position $X0,Y0$, position-angles, ellipticities, scale lengths, etc.. You can also estimate parameter uncertainties via bootstrap resampling, or by using an external Markov-Chain Monte Carlo algorithm (see [here](./pyimfit_emcee.html) for an example of the latter).\n",
    "\n",
    "Sometimes, you might also want to have some estimate of derived values based on a model, such as the total luminosity or the bulge/total ($B/T$) value (assuming you have some idea of which component in your model is the \"bulge\"). How do you determine the uncertainties for such quantities? This notebook shows a simple example of how one might do that, using PyImfit's bootstrap-resampling option.\n",
    "\n",
    "The basic idea is to generate a set of model-parameter vectors via, e.g., bootstrap resampling (or from an MCMC chain). You then compute the resulting derived quantity from those parameter values. In this particular case, we use the `pyimfit.Imfit` object containing the model to compute \"bulge\" and total flux values for each parameter vector, and then take the ratio to get $B/T$ values. By doing this for all the parameter vectors, you end up with a distribution for the derived quantity."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Preliminaries**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some initial setup for nice-looking plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Populating the interactive namespace from numpy and matplotlib\n"
     ]
    }
   ],
   "source": [
    "%pylab inline\n",
    "\n",
    "matplotlib.rcParams['figure.figsize'] = (8,6)\n",
    "matplotlib.rcParams['xtick.labelsize'] = 16\n",
    "matplotlib.rcParams['ytick.labelsize'] = 16\n",
    "matplotlib.rcParams['axes.labelsize'] = 20"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create an image-fitting model using PyImfit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Load the pymfit package; also load numpy and astropy.io.fits (so we can read FITS files):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pyimfit\n",
    "from astropy.io import fits"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Load the data image (here, an SDSS $r$-band image cutout of VCC 1512) and corresponding mask:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "imageFile = \"./pyimfit_bootstrap_BtoT_files/vcc1512rss_cutout.fits\"\n",
    "image_vcc1512 = fits.getdata(imageFile)\n",
    "maskFile = \"./pyimfit_bootstrap_BtoT_files/vcc1512rss_mask_cutout.fits\"\n",
    "mask_vcc1512 = fits.getdata(maskFile)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a ModelDescription instance based on an imfit configuration file (which specifies a Sersic + Exponential model):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "configFile = \"./pyimfit_bootstrap_BtoT_files/config_imfit_vcc1512.dat\"\n",
    "model_desc = pyimfit.ModelDescription.load(configFile)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ORIGINAL_SKY\t120.020408\n",
      "GAIN\t4.725000\n",
      "READNOISE\t4.300000\n",
      "X0\t\t60.0\n",
      "Y0\t\t73.0\n",
      "FUNCTION Sersic\n",
      "PA\t\t155.0\t\t90.0,180.0\n",
      "ell\t\t0.2\t\t0.0,0.5\n",
      "n\t\t2.05\t\t0.0,4.0\n",
      "I_e\t\t120.0\t\t0.0,10000.0\n",
      "r_e\t\t4.5\t\t0.0,20.0\n",
      "\n",
      "FUNCTION Exponential\n",
      "PA\t\t140.0\t\t90.0,180.0\n",
      "ell\t\t0.28\t\t0.0,0.8\n",
      "I_0\t\t70.0\t\t0.0,10000.0\n",
      "h\t\t20.0\t\t0.0,200.0\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(model_desc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create an Imfit instance containing the model, and add the image and mask data. Note that we are *not* doing PSF convolution, in order to save time (this is not meant to be a particular accurate model)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "imfit_fitter = pyimfit.Imfit(model_desc)\n",
    "imfit_fitter.loadData(image_vcc1512, mask=mask_vcc1512)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fit the model to the data (using the default Levenberg-Marquardt solver) and extract the best-fitting parameter values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = imfit_fitter.doFit(getSummary=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "            aic: 21156.824446201397\n",
      "            bic: 21242.642276390998\n",
      "   fitConverged: True\n",
      "        fitStat: 21134.809840392267\n",
      " fitStatReduced: 1.169219398118625\n",
      "          nIter: 10\n",
      "      paramErrs: array([0.01518161, 0.0167467 , 1.88166351, 0.00733777, 0.01613089,\n",
      "       1.9553319 , 0.05896027, 0.65080573, 0.00529781, 1.11196358,\n",
      "       0.18740197])\n",
      "         params: array([6.04336387e+01, 7.32059007e+01, 1.61799952e+02, 1.18947666e-01,\n",
      "       9.56352657e-01, 1.21814611e+02, 4.86558532e+00, 1.38986928e+02,\n",
      "       2.73912311e-01, 8.13853830e+01, 2.08521933e+01])\n",
      "     solverName: 'LM'\n"
     ]
    }
   ],
   "source": [
    "print(results)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Best-fitting parameter values:\n",
      "60.4336, 73.2059, 161.801, 0.118946, 0.956308, 121.821, 4.86538, 138.987, 0.273911, 81.389, 20.8517\n",
      "\n"
     ]
    }
   ],
   "source": [
    "p_bestfit = results.params\n",
    "\n",
    "print(\"Best-fitting parameter values:\")\n",
    "for i in range(len(p_bestfit) - 1):\n",
    "    print(\"{0:g}\".format(p_bestfit[i]), end=\", \")\n",
    "print(\"{0:g}\\n\".format(p_bestfit[-1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Run bootstrap-resampling to generate a set of parameter values (array of best-fit parameter vectors)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OK, now we're going to do some bootstrap resampling to build up a set of several hundred alternate \"best-fit\" parameter values.\n",
    "\n",
    "Note that you coul also generate a set of parameter vectors using MCMC; we're doing bootstrap resampling mainly because it's faster."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Run 500 iterations of bootstrap resamplng. More would be better; this is just to save time (takes about 1 minute on a 2017 MacBook Pro)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "bootstrap_params_array = imfit_fitter.runBootstrap(500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(500, 11)"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bootstrap_params_array.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use these parameter vectors to calculate range of B/T values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We define a function to calculate the $B/T$ value, given a parameter vector (for this model it's simple, \n",
    "but you might have a more complicated model where the first component isn't necessarily the \"bulge\")."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "def GetBtoT( fitter, params ):\n",
    "    \"\"\"\n",
    "    Get the B/T value for a model parameter vector (where \"bulge\" is the first component\n",
    "    in the model)\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    fitter : instance of PyImfit's Imfit class\n",
    "        The Imfit instance containing the model and data to be fit\n",
    "    params : 1D sequence of float\n",
    "        The parameter vector corresponding to the model\n",
    "    \n",
    "    Returns\n",
    "    -------\n",
    "    B/T : float\n",
    "    \"\"\"\n",
    "    total_flux, component_fluxes = fitter.getModelFluxes(params)\n",
    "    # here, we assume the first component in the model is the \"bulge\"\n",
    "    return component_fluxes[0] / total_flux\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The $B/T$ value for the best-fit model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.1557485598370547"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "GetBtoT(imfit_fitter, p_bestfit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now calculate the $B/T$ values for the bootstrap-generated set of parameter vectors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_param_vectors = params_array.shape[0]\n",
    "b2t_values = [GetBtoT(imfit_fitter, bootstrap_params_array[i]) for i in range(n_param_vectors)]\n",
    "b2t_values = np.array(b2t_values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we can analyze the vector of B/T values ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.15852423639167879"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.mean(b2t_values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A histogram of the $B/T$ values (vertical line = best-fit value):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.lines.Line2D at 0x12c671a10>"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 12152 characters]\n",
      "text/plain": [
       "<Figure size 576x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "hist(b2t_values, bins=np.arange(0.14,0.2,0.0025));xlabel(r\"$B/T$\");ylabel(r\"$N$\")\n",
    "axvline(GetBtoT(imfit_fitter, p_bestfit), color='k')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  },
  "toc": {
   "colors": {
    "hover_highlight": "#DAA520",
    "running_highlight": "#FF0000",
    "selected_highlight": "#FFD700"
   },
   "moveMenuLeft": true,
   "nav_menu": {
    "height": "99px",
    "width": "252px"
   },
   "navigate_menu": true,
   "number_sections": false,
   "sideBar": true,
   "threshold": "3",
   "toc_cell": false,
   "toc_section_display": "none",
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
