{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Ex 2: Manipulating Spectra Part 1 - `Spectrum`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Notebook Initialization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "import sys\n",
    "sys.path.append(\"..\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%autoreload\n",
    "\n",
    "\n",
    "import matplotlib\n",
    "matplotlib.rc_file('matplotlibrc')\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import numpy as np\n",
    "from scipy.integrate import quad\n",
    "\n",
    "import darkhistory.physics as phys\n",
    "import darkhistory.spec.spectools as spectools\n",
    "from darkhistory.spec.spectrum import Spectrum"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DarkHistory Binning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "DarkHistory deals extensively with spectra, so it is important to understand how we generally deal with the problem of discretizing spectra in our code. \n",
    "\n",
    "Here are some general rules followed in DarkHistory when dealing with spectra: \n",
    "\n",
    "1. Spectra in DarkHistory should be viewed as a series of bins indexed by energies $E_i$, each containing some number of particles at that energy. \n",
    "\n",
    "\n",
    "2. Each bin has a *log bin width* ($\\Delta \\log E$), and two *bin boundaries*. We always use log-binning for all of the bins, with the bin boundaries taken to be the midpoint in log-space between two bin energies. The first and last bins are assumed to have their energies at the center of the bin in log-space. \n",
    "\n",
    "\n",
    "3. Consider a spectrum $dN/dE$ that is a function of the energy $E$. There are several ways in which we discretize this spectrum over some chosen energy abscissa $E_i$ (a vector of energy values over which we want to approximate the function):\n",
    "\n",
    "    a. The first is to simply assign the value of $dN/dE$ to each bin:\n",
    "    \n",
    "    $$ \\frac{dN}{dE} (E_i) \\approx \\mathbf{S}[E_i] $$ \n",
    "    \n",
    "    where $\\mathbf{S}$ is some vector of entries. This method is fast, but may fail to capture sharp features if the binning is insufficiently fine. \n",
    "\n",
    "    b. The second method is implemented numerically in [*spectools.discretize()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectools/darkhistory.spec.spectools.discretize.html). Given an abscissa $E_i$, total number and energy conservation is enforced, and a good approximation to the spectrum is returned. See the linked documentation for more details."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `Spectrum` Class - Introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Individual spectra are stored as [*Spectrum*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html) objects in DarkHistory. \n",
    "\n",
    "The main attributes are as follows: \n",
    "\n",
    "1. `eng`: the energy abscissa of the spectrum. \n",
    "2. `N` and `dNdE`: the number of particles in each bin, or the $dN/dE$ in each bin. \n",
    "\n",
    "The relationship between `N` and `dNdE` is given by `Spectrum.N == Spectrum.dNdE * Spectrum.eng * log_bin_width`. To calculate `log_bin_width`, the function [*spectools.get_log_bin_width()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectools/darkhistory.spec.spectools.get_log_bin_width.html) can be used with `spec.eng` as the argument. \n",
    "\n",
    "Other optional attributes include `rs` and `in_eng`, which contain the redshift or the injected energy of the particle that produced the spectrum, if these attributes are applicable. \n",
    "\n",
    "To initialize, simply define an abscissa, a spectrum over that abscissa, and then do the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the energy abscissa. \n",
    "eng = 10**((np.arange(120)-90)*(1/10))\n",
    "\n",
    "# Random spectrum\n",
    "random_spec_arr = 1e14/(np.exp((eng - 0.1)/0.1) + 1)\n",
    "\n",
    "random_spec = Spectrum(eng, random_spec_arr, spec_type = 'dNdE')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`spec_type = 'dNdE'` tells the constructor that you are giving it an array of $dN/dE$ values. Let's make a plot of this spectrum:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure()\n",
    "\n",
    "plt.loglog()\n",
    "\n",
    "random_plot, = plt.plot(\n",
    "    random_spec.eng, random_spec.eng**2*random_spec.dNdE\n",
    ")\n",
    "\n",
    "plt.title(r'Random Spectrum')\n",
    "plt.xlabel('Photon Energy [eV]')\n",
    "plt.ylabel('$E^2 \\, dn_\\gamma/dE$ [eV$^{-1}$ cm$^{-3}$]')\n",
    "plt.axis([1e-8, 1e4, 1e-10, 1e20])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The CMB Spectrum"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To create our first [*Spectrum*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html), we will use the function [*spectools.discretize()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectools/darkhistory.spec.spectools.discretize.html), and apply it to the CMB blackbody spectrum,\n",
    "\n",
    "$$ \\frac{dn_\\gamma}{dE} = \\frac{E^2}{\\pi^2 (\\hbar c)^3} \\frac{1}{e^{E/T_\\text{CMB}} - 1}$$\n",
    "\n",
    "The function [*physics.CMB_spec()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/physics/darkhistory.physics.CMB_spec.html) returns the value of $dn_\\gamma/dE$ for some photon energy $E$ and temperature $T$. This function can be passed to [*spectools.discretize()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectools/darkhistory.spec.spectools.discretize.html) to create a [*Spectrum*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html) object. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "T = 1.5 # The temperature in eV of the CMB.\n",
    "\n",
    "# discretize() takes the abscissa, the function to discretize, and\n",
    "# any other arguments that need to be passed to the function. \n",
    "discrete_CMB = spectools.discretize(eng, phys.CMB_spec, T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We should also set the `rs` attribute to the correct redshift. Here, we use [*physics.TCMB()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/physics/darkhistory.physics.TCMB.html), which takes in a redshift as an argument, to calculate what the redshift $1+z$ is at the temperature `T` that we selected earlier. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rs = T/phys.TCMB(1)\n",
    "\n",
    "discrete_CMB.rs = rs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's plot the spectrum contained in `discrete_CMB` as a check, and make sure that it agrees with [*physics.CMB_spec()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/physics/darkhistory.physics.CMB_spec.html). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure()\n",
    "\n",
    "plt.loglog()\n",
    "\n",
    "discrete_plot, = plt.plot(\n",
    "    discrete_CMB.eng, discrete_CMB.eng**2*discrete_CMB.dNdE, label='Discrete Spectrum'\n",
    ")\n",
    "analytic_plot, = plt.plot(\n",
    "    eng, eng**2*phys.CMB_spec(eng, T), 'o', marker='o', markersize='5', \n",
    "    markevery=3, markerfacecolor='w', label='Analytic Spectrum'\n",
    ")\n",
    "\n",
    "plt.legend(handles=[discrete_plot, analytic_plot])\n",
    "\n",
    "plt.title(r'CMB Spectrum, $T_\\mathrm{CMB} = $'+'{:3.2f}'.format(T)+' eV')\n",
    "plt.xlabel('Photon Energy [eV]')\n",
    "plt.ylabel('$E^2 \\, dn_\\gamma/dE$ [eV$^{-1}$ cm$^{-3}$]')\n",
    "plt.axis([1e-8, 1e4, 1e-10, 1e20])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Number and Energy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point, let's introduce two important methods for the `Spectrum` class: [*Spectrum.totN()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html?highlight=Spectrum#darkhistory.spec.spectrum.Spectrum.totN) and [*Spectrum.toteng()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html?highlight=Spectrum#darkhistory.spec.spectrum.Spectrum.toteng). These methods are used to obtain the total number of particles and total energy stored in the `Spectrum` object respectively. One can find the total amount of energy stored between two abscissa values or in a certain bin using this method, but for now we'll simply use them to find the total number of particles and total energy in the spectrum. \n",
    "\n",
    "Analytically, the total number density of particles in a blackbody at temperature $T$ is\n",
    "\n",
    "$$ n_\\gamma = \\frac{16 \\pi \\zeta(3)}{\\hbar^3 c^3} T^3 $$\n",
    "\n",
    "and the total energy density is\n",
    "\n",
    "$$ u_\\gamma = \\frac{\\pi^2}{15 c^3 \\hbar^3} T^4 $$\n",
    "\n",
    "where $T$ is expressed in eV. Let's check that we do recover these results in `discrete_CMB`. The physical constants can all be found in [*physics*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/darkhistory.physics.html). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.special import zeta\n",
    "\n",
    "n_gamma_analytic = 16*np.pi*zeta(3)/(phys.hbar**3 * phys.c**3 * (2*np.pi)**3) * T**3\n",
    "u_gamma_analytic = np.pi**2/(15 * phys.c**3 * phys.hbar**3) * T**4\n",
    "\n",
    "print('Number density (Analytic): ', n_gamma_analytic)\n",
    "print('Total number of photons in discrete_spec: ', discrete_CMB.totN())\n",
    "print('Ratio: ', discrete_CMB.totN()/n_gamma_analytic)\n",
    "print('Energy density (Analytic): ', u_gamma_analytic)\n",
    "print('Total energy of photons in discrete_spec: ', discrete_CMB.toteng())\n",
    "print('Ratio: ', discrete_CMB.toteng()/u_gamma_analytic)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because we used `spectools.discretize`, the total number and energy density enforced by the analytic expression for $dn_\\gamma/dE$ are fully preserved. Of course, the energy abscissa provided must span the part of the spectrum that contains the bulk of the energy. \n",
    "\n",
    "For convenience, the expression `n_gamma_analytic` and `u_gamma_analytic` can be obtained using [*phys.CMB_N_density()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/physics/darkhistory.physics.CMB_N_density.html) and [*phys.CMB_eng_density()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/physics/darkhistory.physics.CMB_eng_density.html) respectively. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`self.totN()` can also be used to find the total number of particles in various bins or combinations of bins. This is done by specifying the bin boundaries for the sets of desired combinations using the bound_arr keyword; e.g. `bound_arr=[a,b,c,d]` will produce three outputs, corresponding to the total number of particles in the bins between boundaries a-b, between boundaries b-c, and between boundaries c-d. For example, the code below returns the total number of particles in each of the first two bins. The bin boundaries do not need to be integers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Number of particles in first and second bin: ',\n",
    "      discrete_CMB.totN(bound_type='bin', bound_arr=np.array([0,1,2]))\n",
    "     )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One can also use `Spectrum.N` to get a list of number of particles in each bin. This is related to the `Spectrum` type, which we will come back to later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Number of particles in first and second bin: ', discrete_CMB.N[0:2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`self.totN()` can also return the total number of particles between different energy boundaries: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Number of particles between 0.2 and 0.45 eV, 0.45 eV and 0.6 eV: ', \n",
    "      discrete_CMB.totN(bound_type='eng', bound_arr=np.array([0.2, 0.45, 0.6]))\n",
    "     )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The function `self.toteng()` can be used in a similar manner. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Addition and Multiplication"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can add or subtract two `Spectrum` objects together (they must have the same `eng` and `spec_type` to do so), or add or subtract a scalar to a `Spectrum`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sum_of_BB_spec = discrete_CMB + random_spec\n",
    "\n",
    "plt.figure()\n",
    "\n",
    "plt.loglog()\n",
    "\n",
    "CMB_plot, = plt.plot(discrete_CMB.eng, discrete_CMB.eng**2*discrete_CMB.dNdE, ':', label='CMB')\n",
    "random_plot, = plt.plot(random_spec.eng, random_spec.eng**2*random_spec.dNdE, ':', label='Random')\n",
    "sum_plot, = plt.plot(sum_of_BB_spec.eng, sum_of_BB_spec.eng**2*sum_of_BB_spec.dNdE, label='Sum')\n",
    "\n",
    "plt.legend(handles=[CMB_plot, random_plot, sum_plot])\n",
    "\n",
    "plt.title(r'Adding Spectrum Objects')\n",
    "plt.xlabel('Photon Energy [eV]')\n",
    "plt.ylabel('$E^2 \\, dn_\\gamma/dE$ [eV$^{-1}$ cm$^{-3}$]')\n",
    "plt.axis([1e-8, 1e4, 1e-10, 1e20])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, you can multiply or divide `Spectrum` objects by another `Spectrum`, an array or a scalar. Suppose, for example, we wanted to find the average energy of particles in `discrete_CMB`. We could do"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = discrete_CMB * discrete_CMB.eng\n",
    "\n",
    "print('Mean energy in the CMB at {:3.2f}'.format(T)+' eV in units of k_B T: ', np.sum(a.N)/discrete_CMB.totN()/T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "which should be close to the theoretical value of $\\langle E \\rangle \\approx 2.70 k_B T$. Of course, we could simply have done"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\n",
    "    'Mean energy in the CMB at {:3.2f}'.format(T)+' eV in units of k_B T: ', \n",
    "    discrete_CMB.toteng()/discrete_CMB.totN()/T\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `Spectrum` - Rebinning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Changing the abscissa of a spectrum is frequently useful, for example in redshifting a spectrum, or in converting a spectrum of photoionizing photons into a spectrum of electrons freed from an atom. DarkHistory provides the method [*Spectrum.rebin()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html?highlight=Spectrum.rebin#darkhistory.spec.spectrum.Spectrum.rebin) for doing so in a manner that conserves total number and total energy, while attempting to preserve the shape of the spectrum. \n",
    "\n",
    "To see how this works, let's perform redshifting on our CMB spectrum. Let's first create a copy of `discrete_CMB`, called `redshifted_CMB`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "redshifted_CMB = Spectrum(discrete_CMB.eng, discrete_CMB.dNdE, spec_type='dNdE', rs=discrete_CMB.rs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first thing we'll do is to change the energy abscissa of `redshifted_CMB` to the final energy after redshifting to `final_rs`. This is done by the function [*Spectrum.shift_eng()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html?highlight=Spectrum.rebin#darkhistory.spec.spectrum.Spectrum.shift_eng), which not only changes the abscissa, but ensures that $dN/dE$ is correctly updated with the new bin widths. The argument passed to Spectrum.shift_eng() is the array of shifted energy abscissae."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_rs = rs/4\n",
    "\n",
    "redshifted_CMB.shift_eng(discrete_CMB.eng * final_rs / rs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point, the energy abscissa has been changed, but it is often the case that we want the final spectrum to have the same binning as the original. To do this, we use the function [*Spectrum.rebin()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html?highlight=Spectrum.rebin#darkhistory.spec.spectrum.Spectrum.rebin), which reassigns the particles in each bin of the original spectrum to the new one in a manner that conserves *total* number and energy, while attempting to preserve the spectral shape. The argument passed to Spectrum.rebin() is the array of desired new energy abscissae."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "redshifted_CMB.rebin(discrete_CMB.eng)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's make a plot for comparison! Don't forget that since we're actually storing number *densities* in these spectra, to compare before and after redshifting, we also have to include a factor of redshift$^3$. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure()\n",
    "\n",
    "plt.loglog()\n",
    "\n",
    "orig_plot, = plt.plot(discrete_CMB.eng, \n",
    "                      discrete_CMB.dNdE*discrete_CMB.eng**2, \n",
    "                      label=r'$T_\\mathrm{CMB} =$ '+'{:3.2f}'.format(T)+' eV')\n",
    "redshifted_plot, = plt.plot(redshifted_CMB.eng, \n",
    "                          redshifted_CMB.eng**2*redshifted_CMB.dNdE * (final_rs/rs)**3, \n",
    "                          label=r'$T_\\mathrm{CMB} =$ '+'{:3.2f}'.format(T * final_rs/rs)+' eV, Rebinned')\n",
    "\n",
    "\n",
    "plt.legend(handles=[orig_plot, redshifted_plot])\n",
    "\n",
    "plt.title('Redshifting CMB')\n",
    "plt.xlabel('Photon Energy [eV]')\n",
    "plt.ylabel(r'$E^2 \\, dn_\\gamma/dE$ [eV$^{-1}$ cm$^{-3}$]')\n",
    "plt.axis([1e-8, 1e4, 1e-10, 1e26])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Redshifting is also provided as a convenience function, [*Spectrum.redshift()*](https://darkhistory.readthedocs.io/en/latest/_autosummary/darkhistory/spec/spectrum/darkhistory.spec.spectrum.Spectrum.html?highlight=Spectrum.rebin#darkhistory.spec.spectrum.Spectrum.redshift). The input to Spectrum.redshift() is the new desired redshift (1+z), and the effect of calling the function is to update the Spectrum object to the desired redshift, properly redshifting the stored spectrum while maintaining the original energy abscissa."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Underflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's perform a really large redshift on yet another copy of `discrete_CMB`: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "large_redshift_CMB = Spectrum(discrete_CMB.eng, discrete_CMB.N, spec_type='N', rs=discrete_CMB.rs)\n",
    "\n",
    "final_rs_large = rs/1e8\n",
    "\n",
    "large_redshift_CMB.redshift(final_rs_large)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This spectrum looks like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure()\n",
    "\n",
    "plt.loglog()\n",
    "\n",
    "orig_spec, = plt.plot(discrete_CMB.eng, \n",
    "                      discrete_CMB.dNdE*discrete_CMB.eng**2, \n",
    "                      label=r'$T_\\mathrm{CMB} =$ '+'{:3.2f}'.format(T)+' eV')\n",
    "large_redshift_spec, = plt.plot(large_redshift_CMB.eng, \n",
    "                          large_redshift_CMB.dNdE*discrete_CMB.eng**2*(final_rs_large/rs)**3, \n",
    "                          label=r'$T_\\mathrm{CMB} =$ '+'{:2.2e}'.format(T * (final_rs_large/rs))+' eV')\n",
    "\n",
    "\n",
    "plt.legend(handles=[orig_spec, large_redshift_spec])\n",
    "\n",
    "plt.title('Redshifting CMB')\n",
    "plt.xlabel('Photon Energy [eV]')\n",
    "plt.ylabel(r'$E^2 \\, dn_\\gamma/dE$ [eV$^{-1}$ cm$^{-3}$]')\n",
    "plt.axis([1e-8, 1e4, 1e-30, 1e28])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because the spectrum has shifted so significantly to low energies, the photons that were in the lowest energy bins of `discrete_CMB` are in danger of being lost. However, number and energy conservation is always enforced when using `Spectrum.rebin()` or any function that calls this method, e.g. `Spectrum.redshift()` by assigning photons below the new energy abscissa to an underflow bin. `Spectrum.totN()` and `Spectrum.toteng()` automatically include these underflow photons. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Original total number of particles: ', discrete_CMB.totN())\n",
    "print('Redshifted total number of particles: ', large_redshift_CMB.totN())\n",
    "print('Total number of photons in underflow: ', large_redshift_CMB.underflow['N'])\n",
    "print('Ratio: ', discrete_CMB.totN()/large_redshift_CMB.totN())\n",
    "print('**********************************************************')\n",
    "print('Original total energy: ', discrete_CMB.toteng())\n",
    "print('Redshifted total energy: ', large_redshift_CMB.toteng())\n",
    "print('Ratio: ', discrete_CMB.toteng()/large_redshift_CMB.toteng())\n",
    "print('Total energy of photons in underflow: ', large_redshift_CMB.underflow['eng'])\n"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "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.12.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
