{ "cells": [ { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import pysm3\n", "import numpy as np\n", "import healpy as hp\n", "from astropy.tests.helper import assert_quantity_allclose\n", "import pysm3.units as u" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Investigate broken implementation of bandpass unit conversion in PySM 3\n", "- categories: [cmbs4, pysm, simonsobs]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Investigation of the broken implementation of the bandpass unit conversion function in PySM 3 and\n", "check of its impact on existing Simons Observatory and CMB-S4 simulated datasets.\n", "\n", "See the [related issue](https://github.com/healpy/pysm/issues/59) and the [pull request with the fix](https://github.com/healpy/pysm/issues/60). This affects all PySM 3 versions `<3.2.2`, it will be fixed in `3.3.0`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$ \\tilde{I} = \\int g(\\nu) I(\\nu)d\\nu$\n", "\n", "If we consider emission of the CMB in $K_{CMB}$ units, $I_{CMB}$ is not a function of $\\nu$:\n", "\n", "$ \\tilde{I}_{CMB}[K_{CMB}] = \\int g(\\nu) I_{CMB}[K_{CMB}] d\\nu = I_{CMB}[K_{CMB}] \\int g(\\nu) d\\nu = I_{CMB}[K_{CMB}]$ \n", "\n", "$ \\tilde{I}_{CMB}[K_{RJ}] = I_{CMB}[K_{CMB}] \\int g(\\nu) C_{K_{CMB}}^{K_{RJ}}(\\nu) d\\nu $\n", "\n", "However we assume that the bandpass of the instrument $g(\\nu)$ is given in power units, i.e. $Jy/sr$, the python `normalize_weights` functions does:\n", "\n", "$ g [K_{RJ}/K_{RJ}] = \\dfrac{C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}]}{\\int C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}] d(\\nu)} $\n", "\n", "CMB bandpass integrated converted to $K_{RJ}$.\n", "\n", "$ \\tilde{I}_{CMB}[K_{RJ}] = I_{CMB}[K_{CMB}] \\int g(\\nu)[K_{RJ}/K_{RJ}] C_{K_{CMB}}^{K_{RJ}}(\\nu) d\\nu = I_{CMB}[K_{CMB}] \\int \\dfrac {C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}]}{\\int C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}] d(\\nu)} C_{K_{CMB}}^{K_{RJ}}(\\nu) d\\nu $\n", "\n", "You can think the last equation as getting a value in $K_{CMB}$, turn it into $K_{RJ}$ and then to $Jy~sr^{-1}$, and do the relative weighting then." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However we assume that the bandpass of the instrument $g(\\nu)$ is given in power units, i.e. $Jy/sr$, the python `normalize_weights` functions does:\n", "\n", "$ g [K_{RJ}/K_{RJ}] = \\dfrac{C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}]}{\\int C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}] d(\\nu)} $\n", "\n", "## From $K_{CMB}$ to $K_{RJ}$:\n", "\n", "If the output is requested in $K_{CMB}$, we basically have to undo that steps, so:\n", "\n", "$ \\tilde{I}_{CMB}[K_{RJ}] = I_{CMB}[K_{CMB}] \\int \\dfrac{C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}]}{\\int C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}] d(\\nu)} C_{K_{CMB}}^{K_{RJ}}(\\nu) d\\nu $\n", "\n", "### `fixed_bandpass_unit_conversion`\n", "\n", "$ \\tilde{I}_{CMB}[K_{CMB}] = \\tilde{I}_{CMB}[K_{RJ}] \\dfrac{ \\int C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g(\\nu) d\\nu} { \\int C_{K_{CMB}}^{Jy~sr^{-1}}(\\nu) g(\\nu) d\\nu} $\n", "\n", "### `broken_bandpass_unit_conversion`\n", "\n", "\n", "$ \\tilde{I}_{CMB}[K_{CMB}] = \\tilde{I}_{CMB}[K_{RJ}] \\int C_{K_{RJ}}^{K_{CMB}}(\\nu) g_{RJ}(\\nu) d(\\nu) = \\tilde{I}_{CMB}[K_{RJ}] \\int C_{K_{RJ}}^{K_{CMB}}(\\nu) \\dfrac{C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}]}{\\int C_{K_{RJ}}^{Jy~sr^{-1}}(\\nu) g [Jy~sr^{-1} / Jy~sr^{-1}] d(\\nu)} d(\\nu)$" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [], "source": [ "from pysm3.utils import normalize_weights" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "def broken_bandpass_unit_conversion(freqs, weights, output_unit):\n", " \"\"\"Unit conversion from uK_RJ to output unit given a bandpass\n", "\n", " Parameters\n", " ----------\n", " freqs : astropy.units.Quantity\n", " Frequency array in a unit compatible with GHz\n", " \"\"\"\n", " freqs = check_freq_input(freqs)\n", " factors = (np.ones(len(freqs), dtype=np.float) * u.uK_RJ).to_value(\n", " output_unit, equivalencies=u.cmb_equivalencies(freqs * u.GHz)\n", " )\n", " if len(freqs) > 1:\n", " w = normalize_weights(freqs, weights)\n", " factor = np.trapz(factors * w, freqs)\n", " else:\n", " factor = factors[0]\n", " return factor * u.Unit(u.Unit(output_unit) / u.uK_RJ)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error: -0.00 %\n", "Error: -0.00 %\n" ] } ], "source": [ " nside = 32\n", " freqs = np.array([250, 300, 350]) * u.GHz\n", " weights = np.ones(len(freqs))\n", " sky = pysm3.Sky(nside=nside, preset_strings=[\"c2\"])\n", " CMB_rj_int = sky.get_emission(freqs, weights)\n", " CMB_thermo_int = CMB_rj_int*fixed_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " expected_map = pysm3.read_map(\n", " \"pysm_2/lensed_cmb.fits\", field=(0, 1), nside=nside, unit=u.uK_CMB\n", " )\n", " for pol in [0, 1]:\n", " #assert_quantity_allclose(expected_map[pol], CMB_thermo_int[pol], rtol=1e-4)\n", " print(\"Error: {:.2f} %\".format(100-np.median((CMB_thermo_int[pol]/expected_map[pol]).value)*100))" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "from pysm3.utils import check_freq_input" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "def fixed_bandpass_unit_conversion(freqs, weights, output_unit):\n", " \"\"\"Unit conversion from uK_RJ to output unit given a bandpass\n", " Parameters\n", " ----------\n", " freqs : astropy.units.Quantity\n", " Frequency array in a unit compatible with GHz\n", " \"\"\"\n", " freqs = check_freq_input(freqs)\n", " weights_to_rj = (weights * u.uK_RJ).to_value(\n", " (u.Jy / u.sr), equivalencies=u.cmb_equivalencies(freqs * u.GHz)\n", " )\n", " weights_to_out = (weights * output_unit).to_value(\n", " (u.Jy / u.sr), equivalencies=u.cmb_equivalencies(freqs * u.GHz)\n", " )\n", " if len(freqs) > 1:\n", " factor = np.trapz(weights_to_rj, freqs)/np.trapz(weights_to_out, freqs)\n", " else:\n", " factor = (1. * u.uK_RJ).to_value(\n", " output_unit, equivalencies=u.cmb_equivalencies(freqs * u.GHz)\n", " )\n", " return factor * u.Unit(u.Unit(output_unit) / u.uK_RJ)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit conversion error for 20% tophat bandpasses" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10 GHz, broken: 1.003 uK_CMB / uK_RJ, fixed: 1.003 uK_CMB / uK_RJ, error 0.00%\n", "20 GHz, broken: 1.011 uK_CMB / uK_RJ, fixed: 1.011 uK_CMB / uK_RJ, error 0.00%\n", "40 GHz, broken: 1.045 uK_CMB / uK_RJ, fixed: 1.045 uK_CMB / uK_RJ, error 0.01%\n", "70 GHz, broken: 1.143 uK_CMB / uK_RJ, fixed: 1.142 uK_CMB / uK_RJ, error 0.08%\n", "100 GHz, broken: 1.310 uK_CMB / uK_RJ, fixed: 1.306 uK_CMB / uK_RJ, error 0.32%\n", "150 GHz, broken: 1.808 uK_CMB / uK_RJ, fixed: 1.782 uK_CMB / uK_RJ, error 1.47%\n", "200 GHz, broken: 2.770 uK_CMB / uK_RJ, fixed: 2.661 uK_CMB / uK_RJ, error 4.06%\n", "250 GHz, broken: 4.627 uK_CMB / uK_RJ, fixed: 4.260 uK_CMB / uK_RJ, error 8.62%\n", "270 GHz, broken: 5.800 uK_CMB / uK_RJ, fixed: 5.221 uK_CMB / uK_RJ, error 11.10%\n", "300 GHz, broken: 8.297 uK_CMB / uK_RJ, fixed: 7.178 uK_CMB / uK_RJ, error 15.59%\n", "350 GHz, broken: 15.749 uK_CMB / uK_RJ, fixed: 12.560 uK_CMB / uK_RJ, error 25.39%\n", "400 GHz, broken: 31.306 uK_CMB / uK_RJ, fixed: 22.588 uK_CMB / uK_RJ, error 38.59%\n" ] } ], "source": [ "perc_error = {}\n", "for center_freq in np.array([10, 20, 40, 70, 100, 150, 200, 250, 270, 300, 350, 400])*u.GHz:\n", " freqs = np.linspace(center_freq*.8, center_freq*1.2, 10)\n", " weights = np.ones(10)\n", " rj_to_cmb = broken_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " fixed_rj_to_cmb = fixed_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " perc_error[center_freq] = (rj_to_cmb - fixed_rj_to_cmb) / fixed_rj_to_cmb * 100\n", " print(\"{: <3.0f}, broken: {:.3f}, fixed: {:.3f}, error {:.2f}%\".format(center_freq, rj_to_cmb, fixed_rj_to_cmb, perc_error[center_freq]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit conversion error for Simons Observatory channels" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "import h5py" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [], "source": [ "SO_chs = h5py.File(\"../mapsims/mapsims/data/simonsobs_instrument_parameters_2020.06.h5\", mode='r')" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "SO_chs[\"LT0_UHF1\"].keys()" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 84, "metadata": {}, "output_type": "execute_result" } ], "source": [ "SO_chs[\"LT0_UHF1\"].attrs.keys()" ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LA UHF1, 225.70 GHz, broken: 3.377 uK_CMB / uK_RJ, fixed: 3.293 uK_CMB / uK_RJ, error 2.54%\n", "LA UHF2, 285.40 GHz, broken: 6.166 uK_CMB / uK_RJ, fixed: 5.990 uK_CMB / uK_RJ, error 2.93%\n", "LA UHF1, 225.70 GHz, broken: 3.377 uK_CMB / uK_RJ, fixed: 3.293 uK_CMB / uK_RJ, error 2.54%\n", "LA UHF2, 285.40 GHz, broken: 6.166 uK_CMB / uK_RJ, fixed: 5.990 uK_CMB / uK_RJ, error 2.93%\n", "LA MFF1, 92.00 GHz, broken: 1.248 uK_CMB / uK_RJ, fixed: 1.247 uK_CMB / uK_RJ, error 0.12%\n", "LA MFF2, 147.50 GHz, broken: 1.729 uK_CMB / uK_RJ, fixed: 1.721 uK_CMB / uK_RJ, error 0.49%\n", "LA MFF1, 92.00 GHz, broken: 1.248 uK_CMB / uK_RJ, fixed: 1.247 uK_CMB / uK_RJ, error 0.12%\n", "LA MFF2, 147.50 GHz, broken: 1.729 uK_CMB / uK_RJ, fixed: 1.721 uK_CMB / uK_RJ, error 0.49%\n", "LA MFS1, 88.60 GHz, broken: 1.229 uK_CMB / uK_RJ, fixed: 1.228 uK_CMB / uK_RJ, error 0.11%\n", "LA MFS2, 146.50 GHz, broken: 1.720 uK_CMB / uK_RJ, fixed: 1.711 uK_CMB / uK_RJ, error 0.54%\n", "LA MFS1, 88.60 GHz, broken: 1.229 uK_CMB / uK_RJ, fixed: 1.228 uK_CMB / uK_RJ, error 0.11%\n", "LA MFS2, 146.50 GHz, broken: 1.720 uK_CMB / uK_RJ, fixed: 1.711 uK_CMB / uK_RJ, error 0.54%\n", "LA LF1 , 25.70 GHz, broken: 1.018 uK_CMB / uK_RJ, fixed: 1.018 uK_CMB / uK_RJ, error 0.00%\n", "LA LF2 , 38.90 GHz, broken: 1.043 uK_CMB / uK_RJ, fixed: 1.043 uK_CMB / uK_RJ, error 0.01%\n", "SA UHF1, 225.70 GHz, broken: 3.377 uK_CMB / uK_RJ, fixed: 3.293 uK_CMB / uK_RJ, error 2.54%\n", "SA UHF2, 285.40 GHz, broken: 6.166 uK_CMB / uK_RJ, fixed: 5.990 uK_CMB / uK_RJ, error 2.93%\n", "SA MFF1, 92.00 GHz, broken: 1.248 uK_CMB / uK_RJ, fixed: 1.247 uK_CMB / uK_RJ, error 0.12%\n", "SA MFF2, 147.50 GHz, broken: 1.729 uK_CMB / uK_RJ, fixed: 1.721 uK_CMB / uK_RJ, error 0.49%\n", "SA MFS1, 88.60 GHz, broken: 1.229 uK_CMB / uK_RJ, fixed: 1.228 uK_CMB / uK_RJ, error 0.11%\n", "SA MFS2, 146.50 GHz, broken: 1.720 uK_CMB / uK_RJ, fixed: 1.711 uK_CMB / uK_RJ, error 0.54%\n", "SA LF1 , 25.70 GHz, broken: 1.018 uK_CMB / uK_RJ, fixed: 1.018 uK_CMB / uK_RJ, error 0.00%\n", "SA LF2 , 38.90 GHz, broken: 1.043 uK_CMB / uK_RJ, fixed: 1.043 uK_CMB / uK_RJ, error 0.01%\n" ] } ], "source": [ "perc_error = {}\n", "for ch in SO_chs.values():\n", " freqs = ch[\"bandpass_frequency_GHz\"] * u.GHz\n", " weights = ch[\"bandpass_weight\"]\n", " rj_to_cmb = broken_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " fixed_rj_to_cmb = fixed_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " perc_error[center_freq] = (rj_to_cmb - fixed_rj_to_cmb) / fixed_rj_to_cmb * 100\n", " print(\"{} {:4s}, {:6.2f} GHz, broken: {:.3f}, fixed: {:.3f}, error {:.2f}%\".format(ch.attrs[\"telescope\"], ch.attrs[\"band\"], ch.attrs[\"center_frequency_GHz\"], rj_to_cmb, fixed_rj_to_cmb, perc_error[center_freq]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit conversion error for CMB-S4 channels" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [], "source": [ "S4_chs = h5py.File(\"../s4mapbasedsims/202006_foregrounds_extragalactic_cmb_tophat/cmbs4_tophat.h5\", mode='r')" ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LAT HFL1 , 225.00 GHz, broken: 3.364 uK_CMB / uK_RJ, fixed: 3.275 uK_CMB / uK_RJ, error 2.71%\n", "LAT HFL2 , 278.00 GHz, broken: 5.632 uK_CMB / uK_RJ, fixed: 5.523 uK_CMB / uK_RJ, error 1.98%\n", "SAT HFS1 , 220.00 GHz, broken: 3.163 uK_CMB / uK_RJ, fixed: 3.109 uK_CMB / uK_RJ, error 1.71%\n", "SAT HFS2 , 270.00 GHz, broken: 5.269 uK_CMB / uK_RJ, fixed: 5.099 uK_CMB / uK_RJ, error 3.34%\n", "LAT LFL1 , 27.00 GHz, broken: 1.019 uK_CMB / uK_RJ, fixed: 1.019 uK_CMB / uK_RJ, error 0.00%\n", "LAT LFL2 , 39.00 GHz, broken: 1.044 uK_CMB / uK_RJ, fixed: 1.044 uK_CMB / uK_RJ, error 0.01%\n", "SAT LFS1 , 30.00 GHz, broken: 1.024 uK_CMB / uK_RJ, fixed: 1.024 uK_CMB / uK_RJ, error 0.00%\n", "SAT LFS2 , 40.00 GHz, broken: 1.044 uK_CMB / uK_RJ, fixed: 1.044 uK_CMB / uK_RJ, error 0.01%\n", "SAT MFHS1, 95.00 GHz, broken: 1.263 uK_CMB / uK_RJ, fixed: 1.262 uK_CMB / uK_RJ, error 0.10%\n", "SAT MFHS2, 155.10 GHz, broken: 1.822 uK_CMB / uK_RJ, fixed: 1.813 uK_CMB / uK_RJ, error 0.51%\n", "LAT MFL1 , 93.00 GHz, broken: 1.262 uK_CMB / uK_RJ, fixed: 1.259 uK_CMB / uK_RJ, error 0.22%\n", "LAT MFL2 , 145.00 GHz, broken: 1.707 uK_CMB / uK_RJ, fixed: 1.697 uK_CMB / uK_RJ, error 0.62%\n", "SAT MFLS1, 85.00 GHz, broken: 1.207 uK_CMB / uK_RJ, fixed: 1.206 uK_CMB / uK_RJ, error 0.06%\n", "SAT MFLS2, 145.10 GHz, broken: 1.696 uK_CMB / uK_RJ, fixed: 1.690 uK_CMB / uK_RJ, error 0.40%\n", "LAT ULFL1, 20.00 GHz, broken: 1.011 uK_CMB / uK_RJ, fixed: 1.011 uK_CMB / uK_RJ, error 0.00%\n" ] } ], "source": [ "perc_error = {}\n", "for ch in S4_chs.values():\n", " freqs = ch[\"bandpass_frequency_GHz\"] * u.GHz\n", " weights = ch[\"bandpass_weight\"]\n", " rj_to_cmb = broken_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " fixed_rj_to_cmb = fixed_bandpass_unit_conversion(\n", " freqs, weights, u.uK_CMB\n", " )\n", " perc_error[center_freq] = (rj_to_cmb - fixed_rj_to_cmb) / fixed_rj_to_cmb * 100\n", " print(\"{} {:5s}, {:6.2f} GHz, broken: {:.3f}, fixed: {:.3f}, error {:.2f}%\".format(\n", " ch.attrs[\"telescope\"], ch.attrs[\"band\"], ch.attrs[\"center_frequency_GHz\"], rj_to_cmb, fixed_rj_to_cmb, perc_error[center_freq]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "healpyvenv", "language": "python", "name": "healpyvenv" }, "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.7.8" } }, "nbformat": 4, "nbformat_minor": 4 }