Ejemplo n.º 1
0
def main():
    env = Environment.get()
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Test the TOAST runtime environment.",
        fromfile_prefix_chars="@")

    parser.add_argument(
        "--groupsize",
        required=False,
        type=int,
        default=0,
        help="size of processor groups used to distribute observations",
    )

    try:
        args = parser.parse_args()
    except SystemExit:
        return

    mpiworld, procs, rank = get_world()
    if rank == 0:
        print(env)
        log.info(
            "Numba threading layer set to '{}'".format(numba_threading_layer))
    if mpiworld is None:
        log.info("Running serially with one process")
    else:
        if rank == 0:
            log.info("Running with {} processes".format(procs))

    groupsize = args.groupsize
    if groupsize <= 0:
        groupsize = procs

    if rank == 0:
        log.info("Using group size of {} processes".format(groupsize))

    comm = Comm(world=mpiworld, groupsize=groupsize)

    log.info(
        "Process {}:  world rank {}, group {} of {}, group rank {}".format(
            rank, comm.world_rank, comm.group + 1, comm.ngroups,
            comm.group_rank))

    return
Ejemplo n.º 2
0
def parse_arguments(comm, procs):
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Simulate satellite boresight pointing and make a map.",
        fromfile_prefix_chars="@",
    )

    add_dist_args(parser)
    add_pointing_args(parser)
    add_tidas_args(parser)
    add_spt3g_args(parser)
    add_dipole_args(parser)
    add_pysm_args(parser)
    add_mc_args(parser)
    add_noise_args(parser)
    add_todsatellite_args(parser)

    parser.add_argument("--outdir",
                        required=False,
                        default="out",
                        help="Output directory")
    parser.add_argument(
        "--debug",
        required=False,
        default=False,
        action="store_true",
        help="Write diagnostics",
    )

    add_madam_args(parser)
    add_binner_args(parser)

    parser.add_argument(
        "--madam",
        required=False,
        action="store_true",
        help="Use libmadam for map-making",
        dest="use_madam",
    )
    parser.add_argument(
        "--no-madam",
        required=False,
        action="store_false",
        help="Do not use libmadam for map-making [default]",
        dest="use_madam",
    )
    parser.set_defaults(use_madam=False)

    parser.add_argument(
        "--focalplane",
        required=False,
        default=None,
        help="Pickle file containing a dictionary of detector properties.  "
        "The keys of this dict are the detector names, and each value is also "
        'a dictionary with keys "quat" (4 element ndarray), "fwhm" '
        '(float, arcmin), "fknee" (float, Hz), "alpha" (float), and "NET" '
        '(float).  For optional plotting, the key "color" can specify a '
        "valid matplotlib color string.",
    )

    parser.add_argument(
        "--gain",
        required=False,
        default=None,
        help="Calibrate the input timelines with a set of gains from a"
        "FITS file containing 3 extensions:"
        "HDU named DETECTORS : table with list of detector names in a column named DETECTORS"
        "HDU named TIME: table with common timestamps column named TIME"
        "HDU named GAINS: 2D image of floats with one row per detector and one column per value.",
    )

    try:
        args = parser.parse_args()
    except SystemExit:
        sys.exit()

    if comm.world_rank == 0:
        log.info("\n")
        log.info("All parameters:")
        for ag in vars(args):
            log.info("{} = {}".format(ag, getattr(args, ag)))
        log.info("\n")

    groupsize = args.group_size
    if groupsize is None or groupsize <= 0:
        groupsize = procs

    # This is the 2-level toast communicator.
    comm = Comm(groupsize=groupsize)

    return args, comm, groupsize
Ejemplo n.º 3
0
def job_config(mpicomm, cases):
    env = Environment.get()
    log = Logger.get()

    class args:
        debug = False
        # TOD Ground options
        el_mod_step_deg = 0.0
        el_mod_rate_hz = 0.0
        el_mod_amplitude_deg = 1.0
        el_mod_sine = False
        el_nod_deg = False
        el_nod_every_scan = False
        start_with_el_nod = False
        end_with_el_nod = False
        scan_rate = 1.0
        scan_rate_el = 0.0
        scan_accel = 1.0
        scan_accel_el = 0.0
        scan_cosecant_modulate = False
        sun_angle_min = 30.0
        schedule = None  # required
        weather = "SIM"
        timezone = 0
        sample_rate = 100.0
        coord = "C"
        split_schedule = None
        sort_schedule = False
        hwp_rpm = 10.0
        hwp_step_deg = None
        hwp_step_time_s = None
        elevation_noise_a = 0.0
        elevation_noise_b = 0.0
        freq = "150"
        do_daymaps = False
        do_seasonmaps = False
        # Pointing options
        nside = 1024
        nside_submap = 16
        single_precision_pointing = False
        common_flag_mask = 1
        # Polyfilter options
        apply_polyfilter = False
        poly_order = 0
        # Ground filter options
        apply_groundfilter = False
        ground_order = 0
        # Atmosphere options
        simulate_atmosphere = False
        simulate_coarse_atmosphere = False
        focalplane_radius_deg = None
        atm_verbosity = 0
        atm_lmin_center = 0.01
        atm_lmin_sigma = 0.001
        atm_lmax_center = 10.0
        atm_lmax_sigma = 10.0
        atm_gain = 2.0e-5
        atm_gain_coarse = 8.0e-5
        atm_zatm = 40000.0
        atm_zmax = 200.0
        atm_xstep = 10.0
        atm_ystep = 10.0
        atm_zstep = 10.0
        atm_nelem_sim_max = 10000
        atm_wind_dist = 3000.0
        atm_z0_center = 2000.0
        atm_z0_sigma = 0.0
        atm_T0_center = 280.0
        atm_T0_sigma = 10.0
        atm_cache = None
        atm_apply_flags = False
        # Noise simulation options
        simulate_noise = False
        # Gain scrambler
        apply_gainscrambler = False
        gain_sigma = 0.01
        # Map maker
        mapmaker_prefix = "toast"
        mapmaker_mask = None
        mapmaker_weightmap = None
        mapmaker_iter_max = 20
        mapmaker_precond_width = 100
        mapmaker_prefilter_order = None
        mapmaker_baseline_length = 200.0
        mapmaker_noisefilter = False
        mapmaker_fourier2D_order = None
        mapmaker_fourier2D_subharmonics = None
        write_hits = True
        write_binmap = True
        write_wcov = False
        write_wcov_inv = False
        zip_maps = False
        # Monte Carlo
        MC_start = 0
        MC_count = 1
        # Sky signal
        input_map = None
        simulate_sky = True
        # Input dir
        auxdir = "toast_inputs"
        # Output
        outdir = "toast"
        tidas = None
        spt3g = None

    parser = argparse.ArgumentParser(
        description="Run a TOAST workflow scaled appropriately to the MPI communicator size and available memory.",
        fromfile_prefix_chars="@",
    )

    parser.add_argument(
        "--node_mem_gb",
        required=False,
        default=None,
        type=float,
        help="Use this much memory per node in GB",
    )

    parser.add_argument(
        "--dry_run",
        required=False,
        default=None,
        type=str,
        help="Comma-separated total_procs,node_procs to simulate.",
    )

    parser.parse_args(namespace=args)

    procs = 1
    rank = 0
    if mpicomm is not None:
        procs = mpicomm.size
        rank = mpicomm.rank

    avail_node_bytes = None
    procs_per_node = None

    if args.dry_run is not None:
        dryrun_total, dryrun_node = args.dry_run.split(",")
        dryrun_total = int(dryrun_total)
        dryrun_node = int(dryrun_node)
        if rank == 0:
            log.info(
                "DRY RUN simulating {} total processes with {} per node".format(
                    dryrun_total, dryrun_node
                )
            )
        procs_per_node = dryrun_node
        procs = dryrun_total
        # We are simulating the distribution
        avail_node_bytes = get_node_mem(mpicomm, 0)

    else:
        # Get information about the actual job size
        procs_per_node, avail_node_bytes = job_size(mpicomm)

    if rank == 0:
        log.info(
            "Minimum detected per-node memory available is {:0.2f} GB".format(
                avail_node_bytes / (1024 ** 3)
            )
        )

    if args.node_mem_gb is not None:
        avail_node_bytes = int((1024 ** 3) * args.node_mem_gb)
        if rank == 0:
            log.info(
                "Setting per-node available memory to {:0.2f} GB as requested".format(
                    avail_node_bytes / (1024 ** 3)
                )
            )

    # Based on the total number of processes and count per node, choose the number of
    # nodes in each observation and a focalplane such that every process has >= 4
    # detectors.

    n_nodes = procs // procs_per_node
    if rank == 0:
        log.info("Job has {} total nodes".format(n_nodes))

    if rank == 0:
        log.info("Examining {} possible cases to run:".format(len(cases)))

    selected_case = None
    selected_nodes = None
    n_detector = None
    time_samples = None
    group_procs = None
    group_nodes = None
    n_group = None
    group_time_samples = None

    for case_name, case_samples in cases.items():
        (
            case_n_detector,
            case_time_samples,
            case_group_procs,
            case_group_nodes,
            case_n_group,
            case_group_time_samples,
        ) = sample_distribution(
            rank, procs_per_node, avail_node_bytes, case_samples, args.sample_rate
        )

        case_min_nodes = case_n_group * case_group_nodes
        if rank == 0:
            log.info(
                "  {:8s}: requires {:d} nodes for {} MPI ranks and {:0.1f}GB per node".format(
                    case_name,
                    case_min_nodes,
                    procs_per_node,
                    avail_node_bytes / (1024 ** 3),
                )
            )

        if selected_nodes is None:
            if case_min_nodes <= n_nodes:
                # First case that fits in our job
                selected_case = case_name
                selected_nodes = case_min_nodes
                n_detector = case_n_detector
                time_samples = case_time_samples
                group_procs = case_group_procs
                group_nodes = case_group_nodes
                n_group = case_n_group
                group_time_samples = case_group_time_samples
        else:
            if (case_min_nodes <= n_nodes) and (case_min_nodes >= selected_nodes):
                # This case fits in our job and is larger than the current one
                selected_case = case_name
                selected_nodes = case_min_nodes
                n_detector = case_n_detector
                time_samples = case_time_samples
                group_procs = case_group_procs
                group_nodes = case_group_nodes
                n_group = case_n_group
                group_time_samples = case_group_time_samples

    if selected_case is None:
        msg = (
            "None of the available cases fit into aggregate memory.  Use a larger job."
        )
        if rank == 0:
            log.error(msg)
        raise RuntimeError(msg)
    else:
        if rank == 0:
            log.info("Selected case '{}'".format(selected_case))

    if rank == 0:
        log.info("Using groups of {} nodes".format(group_nodes))

    # Adjust number of groups

    if n_nodes % group_nodes != 0:
        msg = "Current number of nodes ({}) is not divisible by the required group size ({})".format(
            n_nodes, group_nodes
        )
        if rank == 0:
            log.error(msg)
        raise RuntimeError(msg)

    n_group = n_nodes // group_nodes
    group_time_samples = 1 + time_samples // n_group

    group_seconds = group_time_samples / args.sample_rate

    if args.simulate_atmosphere and args.weather is None:
        raise RuntimeError("Cannot simulate atmosphere without a TOAST weather file")

    comm = None
    if mpicomm is None or args.dry_run is not None:
        comm = Comm(world=None)
    else:
        comm = Comm(world=mpicomm, groupsize=group_procs)

    jobdate = datetime.now().strftime("%Y%m%d-%H:%M:%S")

    args.outdir += "_{:06d}_grp-{:04d}p-{:02d}n_{}".format(
        procs, group_procs, group_nodes, jobdate
    )
    args.auxdir = os.path.join(args.outdir, "inputs")

    if rank == 0:
        os.makedirs(args.outdir)
        os.makedirs(args.auxdir, exist_ok=True)

    if rank == 0:
        with open(os.path.join(args.outdir, "log"), "w") as f:
            f.write("Running at {}\n".format(jobdate))
            f.write("TOAST version = {}\n".format(env.version()))
            f.write("TOAST max threads = {}\n".format(env.max_threads()))
            f.write("MPI Processes = {}\n".format(procs))
            f.write("MPI Processes per node = {}\n".format(procs_per_node))
            f.write(
                "Memory per node = {:0.2f} GB\n".format(avail_node_bytes / (1024 ** 3))
            )
            f.write("Number of groups = {}\n".format(n_group))
            f.write("Group nodes = {}\n".format(group_nodes))
            f.write("Group MPI Processes = {}\n".format(group_procs))
            f.write("Case selected = {}\n".format(selected_case))
            f.write("Case number of detectors = {}\n".format(n_detector))
            f.write(
                "Case total samples = {}\n".format(
                    n_group * group_time_samples * n_detector
                )
            )
            f.write(
                "Case samples per group = {}\n".format(group_time_samples * n_detector)
            )
            f.write("Case data seconds per group = {}\n".format(group_seconds))
            f.write("Parameters:\n")
            for k, v in vars(args).items():
                if re.match(r"_.*", k) is None:
                    f.write("  {} = {}\n".format(k, v))

    args.schedule = os.path.join(args.auxdir, "schedule.txt")
    args.input_map = os.path.join(args.auxdir, "cmb.fits")

    return args, comm, n_nodes, n_detector, selected_case, group_seconds, n_group
Ejemplo n.º 4
0
def parse_arguments(comm):
    timer = Timer()
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Simulate ground-based boresight pointing.  Simulate "
        "and map astrophysical signal.",
        fromfile_prefix_chars="@",
    )

    add_dist_args(parser)
    add_debug_args(parser)
    add_todground_args(parser)
    add_pointing_args(parser)
    add_polyfilter_args(parser)
    add_groundfilter_args(parser)
    add_gainscrambler_args(parser)
    add_noise_args(parser)
    add_sky_map_args(parser)
    add_tidas_args(parser)

    parser.add_argument("--outdir",
                        required=False,
                        default="out",
                        help="Output directory")

    add_madam_args(parser)
    add_binner_args(parser)

    parser.add_argument(
        "--madam",
        required=False,
        action="store_true",
        help="Use libmadam for map-making",
        dest="use_madam",
    )
    parser.add_argument(
        "--no-madam",
        required=False,
        action="store_false",
        help="Do not use libmadam for map-making [default]",
        dest="use_madam",
    )
    parser.set_defaults(use_madam=False)

    parser.add_argument(
        "--focalplane",
        required=False,
        default=None,
        help="Pickle file containing a dictionary of detector "
        "properties.  The keys of this dict are the detector "
        "names, and each value is also a dictionary with keys "
        '"quat" (4 element ndarray), "fwhm" (float, arcmin), '
        '"fknee" (float, Hz), "alpha" (float), and '
        '"NET" (float).  For optional plotting, the key "color"'
        " can specify a valid matplotlib color string.",
    )

    try:
        args = parser.parse_args()
    except SystemExit:
        sys.exit(0)

    if args.tidas is not None:
        if not tidas_available:
            raise RuntimeError("TIDAS not found- cannot export")

    if comm.comm_world is None or comm.world_rank == 0:
        log.info("All parameters:")
        for ag in vars(args):
            log.info("{} = {}".format(ag, getattr(args, ag)))

    if args.group_size:
        comm = Comm(groupsize=args.group_size)

    if comm.comm_world is None or comm.comm_world.rank == 0:
        os.makedirs(args.outdir, exist_ok=True)

    if comm.comm_world is None or comm.world_rank == 0:
        timer.report_clear("Parsed parameters")

    return args, comm
Ejemplo n.º 5
0
def parse_arguments(comm):
    timer = Timer()
    timer.start()
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Simulate ground-based boresight pointing.  Simulate "
        "atmosphere and make maps for some number of noise Monte Carlos.",
        fromfile_prefix_chars="@",
    )

    toast_tools.add_dist_args(parser)
    toast_tools.add_todground_args(parser)
    toast_tools.add_pointing_args(parser)
    toast_tools.add_polyfilter_args(parser)
    toast_tools.add_groundfilter_args(parser)
    toast_tools.add_atmosphere_args(parser)
    toast_tools.add_noise_args(parser)
    toast_tools.add_gainscrambler_args(parser)
    toast_tools.add_madam_args(parser)
    toast_tools.add_mapmaker_args(parser)
    toast_tools.add_filterbin_args(parser)
    toast_tools.add_sky_map_args(parser)
    toast_tools.add_sss_args(parser)
    toast_tools.add_tidas_args(parser)
    toast_tools.add_mc_args(parser)
    so_tools.add_corotator_args(parser)
    so_tools.add_time_constant_args(parser)
    so_tools.add_demodulation_args(parser)
    so_tools.add_h_n_args(parser)
    so_tools.add_crosslinking_args(parser)
    so_tools.add_cadence_map_args(parser)
    so_tools.add_hw_args(parser)
    so_tools.add_so_noise_args(parser)
    so_tools.add_pysm_args(parser)
    so_tools.add_export_args(parser)
    toast_tools.add_debug_args(parser)
    so_tools.add_import_args(parser)
    so_tools.add_sim_sso_args(parser)
    so_tools.add_flag_sso_args(parser)
    so_tools.add_sim_hwpss_args(parser)

    parser.add_argument(
        "--no-maps",
        required=False,
        default=False,
        action="store_true",
        help="Disable all mapmaking.",
    )

    parser.add_argument("--outdir",
                        required=False,
                        default="out",
                        help="Output directory")

    parser.add_argument(
        "--madam",
        required=False,
        action="store_true",
        help="Use libmadam for map-making",
        dest="use_madam",
    )
    parser.add_argument(
        "--no-madam",
        required=False,
        action="store_false",
        help="Do not use libmadam for map-making [default]",
        dest="use_madam",
    )
    parser.set_defaults(use_madam=True)

    try:
        args = parser.parse_args()
    except SystemExit as e:
        sys.exit()

    if len(args.bands.split(",")) != 1:
        # Multi frequency run.  We don't support multiple copies of
        # scanned signal.
        if args.input_map:
            raise RuntimeError(
                "Multiple frequencies are not supported when scanning from a map"
            )

    if args.weather is None:
        raise RuntimeError("You must provide a TOAST weather file")

    if comm.world_rank == 0:
        log.info("\n")
        log.info("All parameters:")
        for ag in vars(args):
            log.info("{} = {}".format(ag, getattr(args, ag)))
        log.info("\n")

    if args.group_size:
        comm = Comm(groupsize=args.group_size)

    if comm.world_rank == 0:
        if not os.path.isdir(args.outdir):
            try:
                os.makedirs(args.outdir)
            except FileExistsError:
                pass
        timer.report_clear("Parse arguments")

    return args, comm
Ejemplo n.º 6
0
def parse_arguments(comm):
    timer = Timer()
    timer.start()
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Simulate ground-based boresight pointing.  Simulate "
        "atmosphere and make maps for some number of noise Monte Carlos.",
        fromfile_prefix_chars="@",
    )

    add_dist_args(parser)
    add_debug_args(parser)
    add_todground_args(parser)
    add_pointing_args(parser)
    add_polyfilter_args(parser)
    add_groundfilter_args(parser)
    add_atmosphere_args(parser)
    add_noise_args(parser)
    add_gainscrambler_args(parser)
    add_madam_args(parser)
    add_sky_map_args(parser)
    add_pysm_args(parser)
    add_sss_args(parser)
    add_tidas_args(parser)
    add_spt3g_args(parser)
    add_mc_args(parser)

    parser.add_argument("--outdir",
                        required=False,
                        default="out",
                        help="Output directory")

    parser.add_argument(
        "--focalplane",
        required=False,
        default=None,
        help="Pickle file containing a dictionary of detector "
        "properties.  The keys of this dict are the detector "
        "names, and each value is also a dictionary with keys "
        '"quat" (4 element ndarray), "fwhm" (float, arcmin), '
        '"fknee" (float, Hz), "alpha" (float), and '
        '"NET" (float).',
    )
    parser.add_argument(
        "--freq",
        required=True,
        help="Comma-separated list of frequencies with identical focal planes."
        "  They override the bandpasses in the focalplane for the purpose of"
        " scaling the atmospheric signal but not for simulating the sky signal.",
    )

    try:
        args = parser.parse_args()
    except SystemExit:
        sys.exit(0)

    if args.tidas is not None:
        if not tidas_available:
            raise RuntimeError("TIDAS not found- cannot export")

    if args.spt3g is not None:
        if not spt3g_available:
            raise RuntimeError("SPT3G not found- cannot export")

    if len(args.freq.split(",")) != 1:
        # Multi frequency run.  We don't support multiple copies of
        # scanned signal.
        if args.input_map:
            raise RuntimeError(
                "Multiple frequencies are not supported when scanning from a map"
            )

    if args.simulate_atmosphere and args.weather is None:
        raise RuntimeError(
            "Cannot simulate atmosphere without a TOAST weather file")

    if comm.world_rank == 0:
        log.info("All parameters:")
        for ag in vars(args):
            log.info("{} = {}".format(ag, getattr(args, ag)))

    if args.group_size:
        comm = Comm(groupsize=args.group_size)

    if comm.world_rank == 0:
        os.makedirs(args.outdir, exist_ok=True)

    timer.stop()
    if comm.world_rank == 0:
        timer.report("Parsed parameters")

    return args, comm
Ejemplo n.º 7
0
def parse_arguments(comm):
    timer = Timer()
    timer.start()
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Simulate ground-based boresight pointing.  Simulate "
        "atmosphere and make maps for some number of noise Monte Carlos.",
        fromfile_prefix_chars="@",
    )

    toast_tools.add_dist_args(parser)
    toast_tools.add_todground_args(parser)
    toast_tools.add_pointing_args(parser)
    toast_tools.add_polyfilter_args(parser)
    toast_tools.add_polyfilter2D_args(parser)
    toast_tools.add_common_mode_filter_args(parser)
    toast_tools.add_groundfilter_args(parser)
    toast_tools.add_atmosphere_args(parser)
    toast_tools.add_noise_args(parser)
    toast_tools.add_gainscrambler_args(parser)
    toast_tools.add_madam_args(parser)
    toast_tools.add_filterbin_args(parser)
    toast_tools.add_sky_map_args(parser)
    toast_tools.add_sss_args(parser)
    toast_tools.add_tidas_args(parser)
    toast_tools.add_mc_args(parser)
    s4_tools.add_hw_args(parser)
    s4_tools.add_s4_noise_args(parser)
    s4_tools.add_pysm_args(parser)
    toast_tools.add_debug_args(parser)

    parser.add_argument(
        "--no-maps",
        required=False,
        default=False,
        action="store_true",
        help="Disable all mapmaking.",
    )

    parser.add_argument(
        "--skip-madam",
        required=False,
        default=False,
        action="store_true",
        help="Skip the first Madam call.",
    )

    parser.add_argument(
        "--pairdiff",
        required=False,
        default=False,
        action="store_true",
        help="Pair-difference TOD and pointing.",
    )

    parser.add_argument("--outdir",
                        required=False,
                        default="out",
                        help="Output directory")

    try:
        args = parser.parse_args()
    except SystemExit as e:
        sys.exit()

    if len(args.bands.split(",")) != 1:
        # Multi frequency run.  We don't support multiple copies of
        # scanned signal.
        if args.input_map:
            raise RuntimeError(
                "Multiple frequencies are not supported when scanning from a map"
            )

    if args.simulate_atmosphere and args.weather is None:
        raise RuntimeError(
            "Cannot simulate atmosphere without a TOAST weather file")

    if comm.world_rank == 0:
        log.info("\n")
        log.info("All parameters:")
        for ag in vars(args):
            log.info("{} = {}".format(ag, getattr(args, ag)))
        log.info("\n")

    if args.group_size:
        comm = Comm(groupsize=args.group_size)

    if comm.world_rank == 0:
        os.makedirs(args.outdir, exist_ok=True)
        timer.report_clear("Parse arguments")

    return args, comm
Ejemplo n.º 8
0
def parse_arguments(comm):
    timer = Timer()
    timer.start()
    log = Logger.get()

    parser = argparse.ArgumentParser(
        description="Simulate ground-based boresight pointing.  Simulate "
        "atmosphere and make maps for some number of noise Monte Carlos.",
        fromfile_prefix_chars="@",
    )

    toast_tools.add_dist_args(parser)
    toast_tools.add_todground_args(parser)
    toast_tools.add_pointing_args(parser)
    toast_tools.add_polyfilter_args(parser)
    toast_tools.add_groundfilter_args(parser)
    toast_tools.add_noise_args(parser)
    toast_tools.add_sky_map_args(parser)
    toast_tools.add_mc_args(parser)
    so_tools.add_hw_args(parser)
    so_tools.add_so_noise_args(parser)
    so_tools.add_pysm_args(parser)
    so_tools.add_export_args(parser)
    toast_tools.add_debug_args(parser)

    parser.add_argument("--outdir",
                        required=False,
                        default="out",
                        help="Output directory")

    parser.add_argument("--map-prefix",
                        required=False,
                        default="toast",
                        help="Output map prefix")

    parser.add_argument(
        "--madam",
        required=False,
        action="store_true",
        help="Use libmadam to bin the signal",
        dest="madam",
    )
    parser.add_argument(
        "--no-madam",
        required=False,
        action="store_false",
        help="Do not use libMadam to bin the signal",
        dest="madam",
    )
    parser.set_defaults(madam=False)

    parser.add_argument(
        "--madam-conserve-memory",
        required=False,
        action="store_true",
        help="Stage the Madam buffer packing",
        dest="madam_conserve_memory",
    )
    parser.add_argument(
        "--no-madam-conserve-memory",
        required=False,
        action="store_false",
        help="Do not stage the Madam buffer packing",
        dest="madam_conserve_memory",
    )
    parser.set_defaults(madam_conserve_memory=True)

    parser.add_argument(
        "--madam-allreduce",
        required=False,
        action="store_true",
        help="Use the allreduce communication pattern in Madam",
        dest="madam_allreduce",
    )
    parser.add_argument(
        "--no-madam-allreduce",
        required=False,
        action="store_false",
        help="Do not use the allreduce communication pattern in Madam",
        dest="madam_allreduce",
    )
    parser.set_defaults(madam_allreduce=False)

    parser.add_argument(
        "--madam-concatenate-messages",
        required=False,
        action="store_true",
        help="Use the alltoallv commucation pattern in Madam",
        dest="madam_concatenate_messages",
    )
    parser.add_argument(
        "--no-madam-concatenate-messages",
        required=False,
        action="store_false",
        help="Use the point-to-point communication pattern in Madam",
        dest="madam_concatenate_messages",
    )
    parser.set_defaults(madam_concatenate_messages=True)

    try:
        args = parser.parse_args()
    except SystemExit as e:
        sys.exit()

    if len(args.bands.split(",")) != 1:
        # Multi frequency run.  We don't support multiple copies of
        # scanned signal.
        if args.input_map:
            raise RuntimeError(
                "Multiple frequencies are not supported when scanning from a map"
            )

    if comm.world_rank == 0:
        log.info("\n")
        log.info("All parameters:")
        for ag in vars(args):
            log.info("{} = {}".format(ag, getattr(args, ag)))
        log.info("\n")

    if args.group_size:
        comm = Comm(groupsize=args.group_size)

    if comm.world_rank == 0:
        if not os.path.isdir(args.outdir):
            try:
                os.makedirs(args.outdir)
            except FileExistsError:
                pass
        timer.report_clear("Parse arguments")

    return args, comm