Load and scale the maps of the noise from the atmosphere
import toml
config = toml.load("s4_design.toml")
log.basicConfig(level=log.INFO)

get_telecope_years[source]

get_telecope_years(config, site, channel)

Compute the number of telescope/years in the CMB-S4 configuration

config_telescopes : dict
    CMB-S4 telescopes configuration,
    generally loaded from a TOML file
site : str
    'Pole' or 'Chile', case doesn't matter
channel : str
    Channel tag, e.g. 'MFHS1'
def get_telecope_years(config, site, channel):
    """Compute the number of telescope/years in the CMB-S4 configuration

    config_telescopes : dict
        CMB-S4 telescopes configuration,
        generally loaded from a TOML file
    site : str
        'Pole' or 'Chile', case doesn't matter
    channel : str
        Channel tag, e.g. 'MFHS1'
    """
    telescope_years = 0
    for telescope_name, telescope_config in config["telescopes"][
        get_telescope(channel)
    ].items():
        if telescope_config["site"].lower() == site.lower():
            has_band = telescope_config.get(channel[:-1], 0) > 0
            telescope_years += has_band * telescope_config.get(
                "years", config["experiment"]["total_experiment_length_years"]
            )
    return telescope_years
s4 = read_instrument_model()
!tail -30 s4_design.toml
Warning: Python module not loaded, you already have Python loaded via conda init
# we can also specify defaults for sites (notice capitalization) or telescopes
# observing_efficiency.Pole.SAT.default = 0.2
# observing_efficiency.Chile.default = 0.2

[telescopes]

    # Telescope names are not used within the software

    [telescopes.SAT]
    # Tubes for each SAT should be 3

    SAT1 = { LFS=0, MFLS= 3, MFHS=0, HFS=0, site="Pole", years=7 }
    SAT3 = { LFS=0, MFLS= 0, MFHS=3, HFS=0, site="Pole", years=7 }
    SAT5 = { LFS=1, MFLS= 0, MFHS=0, HFS=2, site="Pole", years=7 }

    SAT0 = { LFS=0, MFLS= 3, MFHS=0, HFS=0, site="Pole", years=7 }
    SAT2 = { LFS=0, MFLS= 0, MFHS=3, HFS=0, site="Pole", years=7 }
    SAT4 = { LFS=1, MFLS= 0, MFHS=0, HFS=2, site="Pole", years=7 }

    [telescopes.LAT]
    # Tubes for each LAT should be 19

    # Legacy survey LAT deployed in Chile

    LAT0 = { LFL=8, MFL=54, HFL=23, site="Chile", years=7 }
    LAT1 = { LFL=8, MFL=54, HFL=23, site="Chile", years=7 }

    # Delensing survey LAT deployed at Pole

    LAT2 = { ULFPL=4, LFPL=9, MFPL=54, HFPL=18, site="Pole", years=7 }

Compute telescope/years for the reference design

for site in ["Pole", "Chile"]:
    for channel in s4:
        telescope_years = get_telecope_years(config, site, channel["band"])
        print(site, channel["band"], telescope_years)
        telescope = get_telescope(channel["band"])
        if site == "Chile":
            if telescope == "SAT":
                assert telescope_years == 0, "All SAT at Pole"
            elif channel["band"].startswith("ULFL"):
                assert telescope_years == 0, "No ULFL in Chile"              
            else:
                if "P" in channel["band"]:
                    assert telescope_years == 0, "Pole only LAT"
                else:                   
                    assert telescope_years == 14, "2 LAT each band"
        if site == "Pole":
            if telescope == "SAT":
                assert telescope_years == 14, "2 SAT telescopes each band"
            else:
                if "P" in channel["band"]:
                    assert telescope_years == 7, "1 LAT with all 4 bands"
                else:
                    assert telescope_years == 0, "Chile only LAT"
    print(30 * "=")
Pole ULFPL1 7
Pole LFL1 0
Pole LFPL1 7
Pole LFL2 0
Pole LFPL2 7
Pole MFPL1 7
Pole MFL1 0
Pole MFL2 0
Pole MFPL2 7
Pole HFL1 0
Pole HFPL1 7
Pole HFL2 0
Pole HFPL2 7
Pole LFS1 14
Pole LFS2 14
Pole MFLS1 14
Pole MFHS1 14
Pole MFLS2 14
Pole MFHS2 14
Pole HFS1 14
Pole HFS2 14
==============================
Chile ULFPL1 0
Chile LFL1 14
Chile LFPL1 0
Chile LFL2 14
Chile LFPL2 0
Chile MFPL1 0
Chile MFL1 14
Chile MFL2 14
Chile MFPL2 0
Chile HFL1 14
Chile HFPL1 0
Chile HFL2 14
Chile HFPL2 0
Chile LFS1 0
Chile LFS2 0
Chile MFLS1 0
Chile MFHS1 0
Chile MFLS2 0
Chile MFHS2 0
Chile HFS1 0
Chile HFS2 0
==============================

load_atmosphere[source]

load_atmosphere(config, site, channel, realization=0, raw=False)

Load foreground maps for a channel

Parameters
----------
config : dict
    CMB-S4 configuration,
    generally loaded from a TOML file
site : str
    'Pole' or 'Chile', case doesn't matter
channel : str
    Channel tag, e.g. 'MFHS1'
realization : int
    Choose one of the available 8 realizations

Returns
-------
output_map : numpy array
    Output map with all emissions combined, uses nan for missing pixels
def load_atmosphere(config, site, channel, realization=0, raw=False):
    """Load foreground maps for a channel

    Parameters
    ----------
    config : dict
        CMB-S4 configuration,
        generally loaded from a TOML file
    site : str
        'Pole' or 'Chile', case doesn't matter
    channel : str
        Channel tag, e.g. 'MFHS1'
    realization : int
        Choose one of the available 8 realizations

    Returns
    -------
    output_map : numpy array
        Output map with all emissions combined, uses nan for missing pixels
    """

    telescope = get_telescope(channel)
    channel_noP = channel.replace("P", "")

    map_filename = (
        Path(f"{realization:08d}")
        / f"{site.lower()}_atmosphere_{telescope}_{channel_noP}_{mapmaking_naming[telescope]}"
    )
    log.info(f"Reading {map_filename}")
    atmosphere_map = hp.read_map(
        Path(base_folder) / map_filename, (0, 1, 2), dtype=None, verbose=False
    )
    if raw:
        atmosphere_map[atmosphere_map == 0] = hp.UNSEEN
        return atmosphere_map

    atmosphere_map[atmosphere_map == hp.UNSEEN] = np.nan
    atmosphere_map[atmosphere_map == 0] = np.nan

    # input map is 10 days at 100% efficiency
    atmosphere_map *= np.sqrt(
        10
        * simulations_observing_efficiency[site.lower()][channel]
        / (
            365.25
            * get_observing_efficiency(
                config["experiment"]["observing_efficiency"], site, telescope, channel
            )
        )
    )
    atmosphere_map /= np.sqrt(get_telecope_years(config, site, channel))

    atmosphere_map[0] *= config["experiment"].get("atmosphere_scaling_T", 1)
    atmosphere_map[1:] *= config["experiment"].get("atmosphere_scaling_P", 1)
    atmosphere_map[1:] /= np.sqrt(get_thinfp(channel))

    return atmosphere_map

Run on a channel and plot results

Available atmosphere maps

filenames = !ls $base_folder/00000000/*atmo*
import os.path
for f in map(os.path.basename, filenames):
    print(f)
Warning: Python module not loaded, you already have Python loaded via conda init
chile_atmosphere_LAT_HFL1_filtered_telescope_all_time_all_bmap.fits
chile_atmosphere_LAT_HFL2_filtered_telescope_all_time_all_bmap.fits
chile_atmosphere_LAT_LFL1_filtered_telescope_all_time_all_bmap.fits
chile_atmosphere_LAT_LFL2_filtered_telescope_all_time_all_bmap.fits
chile_atmosphere_LAT_MFL1_filtered_telescope_all_time_all_bmap.fits
chile_atmosphere_LAT_MFL2_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_HFL1_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_HFL2_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_LFL1_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_LFL2_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_MFL1_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_MFL2_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_LAT_ULFL1_filtered_telescope_all_time_all_bmap.fits
pole_atmosphere_SAT_HFS1_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS1_thinfp16_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_thinfp16_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_thinfp16_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS1_thinfp1_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_thinfp1_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_thinfp1_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS1_thinfp2_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_thinfp2_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_thinfp2_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS1_thinfp32_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_thinfp32_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_thinfp32_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS1_thinfp4_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_thinfp4_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_thinfp4_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS1_thinfp8_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS1_thinfp8_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS1_thinfp8_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_HFS2_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_HFS2_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_HFS2_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_LFS1_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_LFS1_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_LFS1_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_LFS2_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_LFS2_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_LFS2_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_MFHS1_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_MFHS1_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_MFHS1_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_MFHS2_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_MFHS2_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_MFHS2_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_MFLS1_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_MFLS1_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_MFLS1_telescope_all_time_all_hits.fits.gz
pole_atmosphere_SAT_MFLS2_telescope_all_time_all_binned.fits.gz
pole_atmosphere_SAT_MFLS2_telescope_all_time_all_filtered.fits.gz
pole_atmosphere_SAT_MFLS2_telescope_all_time_all_hits.fits.gz
channel = "HFS1"
site = "Pole"
telescope = get_telescope(channel)
input_map = hp.read_map(
    base_folder + "/00000000/" + \
    f"{site.lower()}_atmosphere_{telescope}_{channel}_telescope_all_time_all_filtered.fits.gz"
, (0,1,2))
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/fitsfunc.py:369: UserWarning: If you are not specifying the input dtype and using the default np.float64 dtype of read_map(), please consider that it will change in a future version to None as to keep the same dtype of the input file: please explicitly set the dtype if it is important to you.
  "If you are not specifying the input dtype and using the default "
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/fitsfunc.py:391: UserWarning: NSIDE = 512
  warnings.warn("NSIDE = {0:d}".format(nside))
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/fitsfunc.py:400: UserWarning: ORDERING = NESTED in fits file
  warnings.warn("ORDERING = {0:s} in fits file".format(ordering))
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/fitsfunc.py:428: UserWarning: INDXSCHM = IMPLICIT
  warnings.warn("INDXSCHM = {0:s}".format(schm))
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/fitsfunc.py:486: UserWarning: Ordering converted to RING
  warnings.warn("Ordering converted to RING")
input_map[input_map == 0] = hp.UNSEEN
output_map = load_atmosphere(config, site, channel, realization=0)
INFO:root:Reading 00000000/pole_atmosphere_SAT_HFS1_telescope_all_time_all_filtered.fits.gz
np.nanmin(output_map), np.nanmax(output_map)
(-0.0018856889744025894, 0.0015042972206419924)
output_map[np.isnan(output_map)] = hp.UNSEEN
np.testing.assert_allclose(input_map == hp.UNSEEN, output_map == hp.UNSEEN)
channel
'HFS1'
observing_efficiency = get_observing_efficiency(
                config["experiment"]["observing_efficiency"], site, telescope, channel
            )
input_map[input_map == hp.UNSEEN] = np.nan
expected_map = input_map * np.sqrt(10 * simulations_observing_efficiency[site.lower()][channel] / (7 * 365.25 * observing_efficiency) / 2)
expected_map[1:] /= np.sqrt(get_thinfp(channel))
np.testing.assert_allclose(expected_map[np.logical_not(np.isnan(expected_map))],
    output_map[output_map != hp.UNSEEN], rtol=1e-6)
%matplotlib inline
hp.mollview(output_map[0], min=-1e-5, max=1e-5, unit="K", title="Atmosphere I")
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/projaxes.py:920: MatplotlibDeprecationWarning: You are modifying the state of a globally registered colormap. In future versions, you will not be able to modify a registered colormap in-place. To remove this warning, you can make a copy of the colormap first. cmap = copy.copy(mpl.cm.get_cmap("viridis"))
  newcm.set_over(newcm(1.0))
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/projaxes.py:921: MatplotlibDeprecationWarning: You are modifying the state of a globally registered colormap. In future versions, you will not be able to modify a registered colormap in-place. To remove this warning, you can make a copy of the colormap first. cmap = copy.copy(mpl.cm.get_cmap("viridis"))
  newcm.set_under(bgcolor)
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/projaxes.py:922: MatplotlibDeprecationWarning: You are modifying the state of a globally registered colormap. In future versions, you will not be able to modify a registered colormap in-place. To remove this warning, you can make a copy of the colormap first. cmap = copy.copy(mpl.cm.get_cmap("viridis"))
  newcm.set_bad(badcolor)
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/healpy/projaxes.py:211: MatplotlibDeprecationWarning: Passing parameters norm and vmin/vmax simultaneously is deprecated since 3.3 and will become an error two minor releases later. Please pass vmin/vmax directly to the norm when creating it.
  **kwds

There are no systematics in the half-wave plate or bandpass mismatch, so almost all the atmosphere signal is rejected in polarization.

hp.mollview(output_map[1], min=-1e-9, max=1e-9, unit="K", title="Atmosphere Q")
hp.mollview(output_map[2], min=-1e-9, max=1e-9, unit="K", title="Atmosphere U")

Test scaling

We want to be able to freely scale the atmosphere noise differently for Temperature and for Polarization

config["experiment"]["atmosphere_scaling_T"] = 3
config["experiment"]["atmosphere_scaling_P"] = 0.1
output_map = load_atmosphere(config, site, channel, realization=0)
INFO:root:Reading 00000000/pole_atmosphere_SAT_HFS1_telescope_all_time_all_filtered.fits.gz
output_map[np.isnan(output_map)] = hp.UNSEEN
np.testing.assert_allclose(config["experiment"]["atmosphere_scaling_T"] * expected_map[0, np.logical_not(np.isnan(expected_map[0]))],
    output_map[0][output_map[0] != hp.UNSEEN], rtol=1e-6)
np.testing.assert_allclose(config["experiment"]["atmosphere_scaling_P"]  * expected_map[1:][np.logical_not(np.isnan(expected_map[1:]))],
    output_map[1:][output_map[1:] != hp.UNSEEN], rtol=1e-6)