Load and scale hitmaps and white noise covariance matrices
import logging as log

Simulations are executed at lower sampling frequency:

  • $20~Hz$ for SAT (production is $100~Hz$)
  • $200~Hz$ for LAT (production is $400~Hz$)

load_hitmap_wcov[source]

load_hitmap_wcov(config, site, channel, realization=0, raw_hitmap=False)

Load hitmaps and white noise covariance matrices for a channel

This loads the simulated hitmaps and white noise covariance matrices
and scales them properly to the experiment configuration and duration
as defined in the input config file.
Hitmaps assumes a sampling frequency of 100 Hz for SAT and 400 Hz for
LAT.

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
-------
hitmap : numpy array
    Hitmap for all channels all tubes combined
wcov : numpy array
    White noise covariance matrix, rows are:
    "II", "IQ", "IU", "QQ", "QU", "UU", units are K^2
hitmaps_naming = {
    "SAT": "telescope_all_time_all_hits.fits.gz",
    "LAT": "filtered_telescope_all_time_all_hmap.fits",
}
wcov_naming = {
    "SAT": "telescope_all_time_all_wcov.fits.gz",
    "LAT": "filtered_telescope_all_time_all_wcov.fits",
}


def load_hitmap_wcov(config, site, channel, realization=0, raw_hitmap=False):
    """Load hitmaps and white noise covariance matrices for a channel

    This loads the simulated hitmaps and white noise covariance matrices
    and scales them properly to the experiment configuration and duration
    as defined in the input config file.
    Hitmaps assumes a sampling frequency of 100 Hz for SAT and 400 Hz for
    LAT.

    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
    -------
    hitmap : numpy array
        Hitmap for all channels all tubes combined
    wcov : numpy array
        White noise covariance matrix, rows are:
        "II", "IQ", "IU", "QQ", "QU", "UU", units are K^2
    """

    # it is the same scaling for hitmap and white noise covariance matrix,
    # which is the same as noise except squared

    telescope = get_telescope(channel)
    tube = channel[:-1]

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

    map_filename = (
        Path(f"{realization:08d}")
        / f"{site.lower()}_noise_{telescope}_{channel_noP}_{hitmaps_naming[telescope]}"
    )
    wcov_filename = (
        Path(f"{realization:08d}")
        / f"{site.lower()}_noise_{telescope}_{channel_noP}_{wcov_naming[telescope]}"
    )
    log.info(f"Base folder: {base_folder}")
    log.info(f"Reading {map_filename}")
    hitmap = hp.read_map(Path(base_folder) / map_filename, 0, dtype=None, verbose=False)

    hitmap[hitmap == hp.UNSEEN] = 0
    if raw_hitmap:
        return hitmap
    wcov = hp.read_map(
        Path(base_folder) / wcov_filename, (0, 1, 2, 3, 4, 5), dtype=None, verbose=False
    )
    scaling = (
        365.25
        * get_observing_efficiency(
            config["experiment"]["observing_efficiency"], site, telescope, channel
        )
        * get_observing_efficiency(
            config["experiment"]["sensitivity_factor"], site, telescope, channel
        )
    ) / (10 * simulations_observing_efficiency[site.lower()][channel])
    # focal plane thinning factor of TOD simulations
    scaling *= get_thinfp(channel)
    scaling *= get_tube_years(config, site, channel) / simulated_tubes[tube]
    hitmap = np.round(hitmap * scaling).astype(np.int64)
    hitmap *= simulations_sampling_frequency_scaling[telescope]
    wcov /= scaling
    wcov[:, hitmap == 0] = hp.UNSEEN
    return hitmap, wcov
import toml

config = toml.load("s4_design.toml")

Estimate observation efficiency

In the Winter 2021 run, there is a significant efficiency loss in the turnaround of the telescopes. This is already taken into account in the overall observation efficiency so we want to calibrate it out. Best is to use the hitmaps themselves, and scale them up from whatever number of hits to the expected rate for 10 days at 100% efficiency for each channel.

Still we want to make sure they are within 10% of the scheduling efficiencies measured by TOAST:

scheduling_efficiency = dict(
    pole=dict(SAT=0.642, LAT=0.754),
    chile=dict(SAT=0.988, LAT=1.042),
)
s4 = read_instrument_model()
tot_seconds = 10 * 24 * 60 * 60
sampling_frequency = dict(SAT=20, LAT=200)
observing_efficiency = dict(Pole={}, Chile={})
for row in s4:
    channel = row["band"]
    sites = ["Pole"] if row["telescope"] == "SAT" or ("P" in channel) else ["Chile"]
    for site in sites:
        print(channel, site)
        raw_hitmap = load_hitmap_wcov(config, site, channel, realization=0, raw_hitmap=True)
        observing_efficiency[site][channel] = raw_hitmap.sum() / (tot_seconds*sampling_frequency[row["telescope"]]*row["detectors_per_tube"]/ get_thinfp(channel))
ULFPL1 Pole
LFL1 Chile
LFPL1 Pole
LFL2 Chile
LFPL2 Pole
MFPL1 Pole
MFL1 Chile
MFL2 Chile
MFPL2 Pole
HFL1 Chile
HFPL1 Pole
HFL2 Chile
HFPL2 Pole
LFS1 Pole
LFS2 Pole
MFLS1 Pole
MFHS1 Pole
MFLS2 Pole
MFHS2 Pole
HFS1 Pole
HFS2 Pole
simulated_tubes
{'LFS': 1,
 'MFLS': 1,
 'MFHS': 1,
 'HFS': 1,
 'LFL': 8,
 'MFL': 54,
 'HFL': 23,
 'ULFPL': 4,
 'LFPL': 9,
 'MFPL': 54,
 'HFPL': 18}
scheduling_efficiency
{'pole': {'SAT': 0.642, 'LAT': 0.754}, 'chile': {'SAT': 0.988, 'LAT': 1.042}}
for site, channels in observing_efficiency.items():
    for channel in channels:
        observing_efficiency[site][channel] /= simulated_tubes[channel[:-1]]
for site, channels in observing_efficiency.items():
    for channel, efficiency in channels.items():
        telescope = get_telescope(channel)
        print(
f"{site} {channel} Efficiency from hitmap {efficiency:.2f}, scheduling efficiency {scheduling_efficiency[site.lower()][telescope]:.2f}, ratio {efficiency/scheduling_efficiency[site.lower()][telescope]:.1%}") 
Pole ULFPL1 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole LFPL1 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole LFPL2 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole MFPL1 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole MFPL2 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole HFPL1 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole HFPL2 Efficiency from hitmap 0.71, scheduling efficiency 0.75, ratio 94.1%
Pole LFS1 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole LFS2 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole MFLS1 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole MFHS1 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole MFLS2 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole MFHS2 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole HFS1 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Pole HFS2 Efficiency from hitmap 0.54, scheduling efficiency 0.64, ratio 84.3%
Chile LFL1 Efficiency from hitmap 1.04, scheduling efficiency 1.04, ratio 99.5%
Chile LFL2 Efficiency from hitmap 1.04, scheduling efficiency 1.04, ratio 99.5%
Chile MFL1 Efficiency from hitmap 1.04, scheduling efficiency 1.04, ratio 99.5%
Chile MFL2 Efficiency from hitmap 1.04, scheduling efficiency 1.04, ratio 99.5%
Chile HFL1 Efficiency from hitmap 1.04, scheduling efficiency 1.04, ratio 99.3%
Chile HFL2 Efficiency from hitmap 1.04, scheduling efficiency 1.04, ratio 99.3%
observing_efficiency
{'Pole': {'ULFPL1': 0.7094065625,
  'LFPL1': 0.7094065625,
  'LFPL2': 0.7094065625,
  'MFPL1': 0.7094065625,
  'MFPL2': 0.7094065625,
  'HFPL1': 0.7094065625,
  'HFPL2': 0.7094065625,
  'LFS1': 0.5410630787037037,
  'LFS2': 0.5410630787037037,
  'MFLS1': 0.5410630787037037,
  'MFHS1': 0.5410630787037037,
  'MFLS2': 0.5410630787037037,
  'MFHS2': 0.5410630787037037,
  'HFS1': 0.5410630787037037,
  'HFS2': 0.5410630787037037},
 'Chile': {'LFL1': 1.0367161458333334,
  'LFL2': 1.0367161458333334,
  'MFL1': 1.0367161458333334,
  'MFL2': 1.0367161458333334,
  'HFL1': 1.0350467156468064,
  'HFL2': 1.0350467156468064}}
ls /global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT*hmap.fits
Warning: Python module not loaded, you already have Python loaded via conda init
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_HFL1_filtered_telescope_all_time_all_hmap.fits
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_HFL2_filtered_telescope_all_time_all_hmap.fits
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_LFL1_filtered_telescope_all_time_all_hmap.fits
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_LFL2_filtered_telescope_all_time_all_hmap.fits
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_MFL1_filtered_telescope_all_time_all_hmap.fits
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_MFL2_filtered_telescope_all_time_all_hmap.fits
/global/cscratch1/sd/keskital/s4sim/reference_tool_round_2/out/00000000/pole_noise_LAT_ULFL1_filtered_telescope_all_time_all_hmap.fits

Verification of the input hitmap

filenames = !ls $base_folder/00000000/*noise*wcov*
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_noise_LAT_HFL1_filtered_telescope_all_time_all_wcov.fits
chile_noise_LAT_HFL1_filtered_telescope_all_time_all_wcov_inv.fits
chile_noise_LAT_HFL2_filtered_telescope_all_time_all_wcov.fits
chile_noise_LAT_HFL2_filtered_telescope_all_time_all_wcov_inv.fits
chile_noise_LAT_LFL1_filtered_telescope_all_time_all_wcov.fits
chile_noise_LAT_LFL1_filtered_telescope_all_time_all_wcov_inv.fits
chile_noise_LAT_LFL2_filtered_telescope_all_time_all_wcov.fits
chile_noise_LAT_LFL2_filtered_telescope_all_time_all_wcov_inv.fits
chile_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov.fits
chile_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov_inv.fits
chile_noise_LAT_MFL2_filtered_telescope_all_time_all_wcov.fits
chile_noise_LAT_MFL2_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_HFL1_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_HFL1_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_HFL2_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_HFL2_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_LFL1_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_LFL1_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_LFL2_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_LFL2_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_MFL2_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_MFL2_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_LAT_ULFL1_filtered_telescope_all_time_all_wcov.fits
pole_noise_LAT_ULFL1_filtered_telescope_all_time_all_wcov_inv.fits
pole_noise_SAT_HFS1_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_HFS1_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_HFS2_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_HFS2_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_LFS1_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_LFS1_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_LFS2_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_LFS2_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_MFHS1_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_MFHS1_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_MFHS2_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_MFHS2_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_MFLS1_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_MFLS1_telescope_all_time_all_wcov_inv.fits.gz
pole_noise_SAT_MFLS2_telescope_all_time_all_wcov.fits.gz
pole_noise_SAT_MFLS2_telescope_all_time_all_wcov_inv.fits.gz
pole_raster_1h_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov.fits
pole_raster_1h_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov_inv.fits
pole_raster_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov.fits
pole_raster_noise_LAT_MFL1_filtered_telescope_all_time_all_wcov_inv.fits
channel = "HFS1"
site = "Pole"
telescope = get_telescope(channel)
input_map = hp.read_map(
    base_folder + "/00000000/" + \
    f"{site.lower()}_noise_{telescope}_{channel}_telescope_all_time_all_hits.fits.gz"
, 0)
/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")
from astropy.io import fits
fits.open(base_folder + "/00000000/" + \
    f"{site.lower()}_noise_{telescope}_{channel}_telescope_all_time_all_wcov.fits.gz"
)[1].header
XTENSION= 'BINTABLE'           / binary table extension                         
BITPIX  =                    8 / array data type                                
NAXIS   =                    2 / number of array dimensions                     
NAXIS1  =                24576 / length of dimension 1                          
NAXIS2  =                 3072 / length of dimension 2                          
PCOUNT  =                    0 / number of group parameters                     
GCOUNT  =                    1 / number of groups                               
TFIELDS =                    6 / number of table fields                         
TTYPE1  = 'II      '                                                            
TFORM1  = '1024E   '                                                            
TTYPE2  = 'IQ      '                                                            
TFORM2  = '1024E   '                                                            
TTYPE3  = 'IU      '                                                            
TFORM3  = '1024E   '                                                            
TTYPE4  = 'QQ      '                                                            
TFORM4  = '1024E   '                                                            
TTYPE5  = 'QU      '                                                            
TFORM5  = '1024E   '                                                            
TTYPE6  = 'UU      '                                                            
TFORM6  = '1024E   '                                                            
PIXTYPE = 'HEALPIX '           / HEALPIX pixelisation                           
ORDERING= 'NESTED  '           / Pixel ordering scheme, either RING or NESTED   
EXTNAME = 'xtension'           / name of this binary table extension            
NSIDE   =                  512 / Resolution parameter of HEALPIX                
FIRSTPIX=                    0 / First pixel # (0 based)                        
LASTPIX =              3145727 / Last pixel # (0 based)                         
INDXSCHM= 'IMPLICIT'           / Indexing: IMPLICIT or EXPLICIT                 
OBJECT  = 'FULLSKY '           / Sky coverage, either FULLSKY or PARTIAL        
input_EE_wcov = hp.read_map(base_folder + "/00000000/" + \
    f"{site.lower()}_noise_{telescope}_{channel}_telescope_all_time_all_wcov.fits.gz"
, (3))
num_tubes = sum([
    telescope[channel[:-1]] for telescope in config["telescopes"][telescope].values()
])
sampling_rate = input_map.sum() 
sampling_rate /= 10 * 24 * 3600 * 0.5410630787037037
sampling_rate /= 8592 # HF detectors per tube
sampling_rate *= 8 # focal plane thinning factor
sampling_rate
20.0

Check we are within 5% of 20 Hz (used in sims for SAT, LAT used 200 Hz)

np.testing.assert_allclose(sampling_rate, 20, rtol=5/100)
import astropy.units as u
input_variance = input_EE_wcov * u.K**2 * (input_map  / (20 * u.Hz))
input_variance = input_variance[input_map > 0]
input_variance = input_variance[input_variance > 0]
import matplotlib.pyplot as plt
plt.hist(input_variance.value, bins=100, log=True);
input_NET = np.sqrt(np.median(input_variance))
input_NET.to(u.uK * u.s**(.5)) / np.sqrt(2)
$611.004 \; \mathrm{\mu K\,s^{1/2}}$
from astropy.tests.helper import assert_quantity_allclose
channel_NET = 609.1 * u.uK * u.s**(.5)

The channel NET is valid for temperature, when we solve for the $2$ polarization components we get a factor of $\sqrt{2}$.

assert_quantity_allclose(input_NET/np.sqrt(2), channel_NET, rtol=5/100) 
telescope
'SAT'

Verify the output hitmap

hitmap, wcov = load_hitmap_wcov(config, site, channel, realization=0)
sampling_rate = hitmap.sum() / (config["experiment"]["total_experiment_length_years"] \
                                * config["experiment"]["observing_efficiency"][site][telescope][channel] \
                                * config["experiment"]["sensitivity_factor"][site][telescope][channel])
sampling_rate /= 365.25 * 24 * 3600
sampling_rate /= 8592 # HF detectors per tube
sampling_rate /= num_tubes
sampling_rate
99.99999999611072
np.testing.assert_allclose(sampling_rate, 100, rtol=5/100)
np.testing.assert_allclose(input_map == 0, hitmap == 0)
%matplotlib inline
import matplotlib.pyplot as plt
log_hitmap = np.log10(hitmap)
/global/homes/z/zonca/condajupynersc/lib/python3.7/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in log10
  """Entry point for launching an IPython kernel.
log_hitmap[np.isinf(log_hitmap)] = hp.UNSEEN
hp.mollview(log_hitmap, unit="log10(samples)", min=3, max=8.5, title="Hitmap")
/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
for i,label in [(3, "EE"), (4, "EB"), (5, "BB")]:
    hp.mollview(wcov[i], unit="$K^2_{CMB}$", min=0, max=1e-11, title=f"{label} White noise covariance")
wcov[wcov == hp.UNSEEN] = 0
variance = wcov[3] * u.K**2 * (hitmap / (100 * u.Hz)) 
variance[variance != 0].min(), variance.max(), variance[variance != 0].mean()
(<Quantity 4.01461678e-07 K2 / Hz>,
 <Quantity 0.00029398 K2 / Hz>,
 <Quantity 8.90336291e-07 K2 / Hz>)
full_mission_NET = np.sqrt(np.median(variance[hitmap != 0]))
full_mission_NET.to(u.uK * u.s**(.5))/np.sqrt(2)
$610.02688 \; \mathrm{\mu K\,s^{1/2}}$
assert_quantity_allclose(full_mission_NET/np.sqrt(2), channel_NET, rtol=5/100)