def main():
    log = Logger.get()
    gt = GlobalTimers.get()
    gt.start("toast_planck_reduce (total)")

    mpiworld, procs, rank, comm = get_comm()

    # This is the 2-level toast communicator.  By default,
    # there is just one group which spans MPI_COMM_WORLD.
    comm = toast.Comm()

    if comm.comm_world.rank == 0:
        print(
            "Running with {} processes at {}".format(
                procs, str(datetime.datetime.now())
            )
        )

    parser = argparse.ArgumentParser(
        description="Simple on-the-fly signal convolution + MADAM Mapmaking",
        fromfile_prefix_chars="@",
    )
    parser.add_argument("--lmax", required=True, type=np.int, help="Simulation lmax")
    parser.add_argument(
        "--fwhm", required=True, type=np.float, help="Sky fwhm [arcmin] to deconvolve"
    )
    parser.add_argument("--beammmax", required=True, type=np.int, help="Beam mmax")
    parser.add_argument("--order", default=11, type=np.int, help="Iteration order")
    parser.add_argument(
        "--pxx",
        required=False,
        default=False,
        action="store_true",
        help="Beams are in Pxx frame, not Dxx",
    )
    parser.add_argument(
        "--normalize",
        required=False,
        default=False,
        action="store_true",
        help="Normalize the beams",
    )
    parser.add_argument(
        "--skyfile",
        required=True,
        help="Path to sky alm files. Tag DETECTOR will be "
        "replaced with detector name.",
    )
    parser.add_argument(
        "--remove_monopole",
        required=False,
        default=False,
        action="store_true",
        help="Remove the sky monopole before convolution",
    )
    parser.add_argument(
        "--remove_dipole",
        required=False,
        default=False,
        action="store_true",
        help="Remove the sky dipole before convolution",
    )
    parser.add_argument(
        "--beamfile",
        required=True,
        help="Path to beam alm files. Tag DETECTOR will be "
        "replaced with detector name.",
    )
    parser.add_argument("--rimo", required=True, help="RIMO file")
    parser.add_argument("--freq", required=True, type=np.int, help="Frequency")
    parser.add_argument(
        "--dets", required=False, default=None, help="Detector list (comma separated)"
    )
    parser.add_argument(
        "--effdir", required=True, help="Input Exchange Format File directory"
    )
    parser.add_argument(
        "--effdir_pntg",
        required=False,
        help="Input Exchange Format File directory " "for pointing",
    )
    parser.add_argument(
        "--effdir_out", required=False, help="Output directory for convolved TOD"
    )
    parser.add_argument(
        "--obtmask", required=False, default=1, type=np.int, help="OBT flag mask"
    )
    parser.add_argument(
        "--flagmask", required=False, default=1, type=np.int, help="Quality flag mask"
    )
    parser.add_argument("--ringdb", required=True, help="Ring DB file")
    parser.add_argument(
        "--odfirst", required=False, default=None, type=np.int, help="First OD to use"
    )
    parser.add_argument(
        "--odlast", required=False, default=None, type=np.int, help="Last OD to use"
    )
    parser.add_argument(
        "--ringfirst",
        required=False,
        default=None,
        type=np.int,
        help="First ring to use",
    )
    parser.add_argument(
        "--ringlast", required=False, default=None, type=np.int, help="Last ring to use"
    )
    parser.add_argument(
        "--obtfirst",
        required=False,
        default=None,
        type=np.float,
        help="First OBT to use",
    )
    parser.add_argument(
        "--obtlast", required=False, default=None, type=np.float, help="Last OBT to use"
    )
    parser.add_argument("--madam_prefix", required=False, help="map prefix")
    parser.add_argument(
        "--madampar", required=False, default=None, help="Madam parameter file"
    )
    parser.add_argument(
        "--obtmask_madam", required=False, type=np.int, help="OBT flag mask for Madam"
    )
    parser.add_argument(
        "--flagmask_madam",
        required=False,
        type=np.int,
        help="Quality flag mask for Madam",
    )
    parser.add_argument(
        "--skip_madam",
        required=False,
        default=False,
        action="store_true",
        help="Do not run Madam on the convolved timelines",
    )
    parser.add_argument("--out", required=False, default=".", help="Output directory")

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

    timer = Timer()
    timer.start()

    odrange = None
    if args.odfirst is not None and args.odlast is not None:
        odrange = (args.odfirst, args.odlast)

    ringrange = None
    if args.ringfirst is not None and args.ringlast is not None:
        ringrange = (args.ringfirst, args.ringlast)

    obtrange = None
    if args.obtfirst is not None and args.obtlast is not None:
        obtrange = (args.obtfirst, args.obtlast)

    detectors = None
    if args.dets is not None:
        detectors = re.split(",", args.dets)

    # This is the distributed data, consisting of one or
    # more observations, each distributed over a communicator.
    data = toast.Data(comm)

    # Ensure output directory exists

    if not os.path.isdir(args.out) and comm.comm_world.rank == 0:
        os.makedirs(args.out)

    # Read in madam parameter file

    # Allow more than one entry, gather into a list
    repeated_keys = ["detset", "detset_nopol", "survey"]
    pars = {}

    if comm.comm_world.rank == 0:
        pars["kfirst"] = False
        pars["temperature_only"] = True
        pars["base_first"] = 60.0
        pars["nside_map"] = 512
        pars["nside_cross"] = 512
        pars["nside_submap"] = 16
        pars["write_map"] = False
        pars["write_binmap"] = True
        pars["write_matrix"] = False
        pars["write_wcov"] = False
        pars["write_hits"] = True
        pars["kfilter"] = False
        pars["info"] = 3
        if args.madampar:
            pat = re.compile(r"\s*(\S+)\s*=\s*(\S+(\s+\S+)*)\s*")
            comment = re.compile(r"^#.*")
            with open(args.madampar, "r") as f:
                for line in f:
                    if not comment.match(line):
                        result = pat.match(line)
                        if result:
                            key, value = result.group(1), result.group(2)
                            if key in repeated_keys:
                                if key not in pars:
                                    pars[key] = []
                                pars[key].append(value)
                            else:
                                pars[key] = value
        # Command line parameters override the ones in the madam parameter file
        if "file_root" not in pars:
            pars["file_root"] = "madam"
        if args.madam_prefix is not None:
            pars["file_root"] = args.madam_prefix
        sfreq = "{:03}".format(args.freq)
        if sfreq not in pars["file_root"]:
            pars["file_root"] += "_" + sfreq
        try:
            fsample = {30: 32.51, 44: 46.55, 70: 78.77}[args.freq]
        except Exception:
            fsample = 180.3737
        pars["fsample"] = fsample
        pars["path_output"] = args.out

        print("All parameters:")
        print(args, flush=True)

    pars = comm.comm_world.bcast(pars, root=0)

    memreport("after parameters", MPI.COMM_WORLD)

    # madam only supports a single observation.  Normally
    # we would have multiple observations with some subset
    # assigned to each process group.

    # create the TOD for this observation

    tod = tp.Exchange(
        comm=comm.comm_group,
        detectors=detectors,
        ringdb=args.ringdb,
        effdir_in=args.effdir,
        effdir_pntg=args.effdir_pntg,
        obt_range=obtrange,
        ring_range=ringrange,
        od_range=odrange,
        freq=args.freq,
        RIMO=args.rimo,
        obtmask=args.obtmask,
        flagmask=args.flagmask,
        do_eff_cache=False,
    )

    # normally we would get the intervals from somewhere else, but since
    # the Exchange TOD already had to get that information, we can
    # get it from there.

    ob = {}
    ob["name"] = "mission"
    ob["id"] = 0
    ob["tod"] = tod
    ob["intervals"] = tod.valid_intervals
    ob["baselines"] = None
    ob["noise"] = tod.noise

    # Add the bare minimum focal plane information for the conviqt operator
    focalplane = {}
    for det in tod.detectors:
        if args.pxx:
            # Beam is in the polarization basis.
            # No extra rotations are needed
            psipol = tod.rimo[det].psi_pol
        else:
            # Beam is in the detector basis. Convolver needs to remove
            # the last rotation into the polarization sensitive frame.
            psipol = tod.rimo[det].psi_uv + tod.rimo[det].psi_pol
        focalplane[det] = {
            "pol_leakage" : tod.rimo[det].epsilon,
            "pol_angle_deg" : psipol,
        }
    ob["focalplane"] = focalplane

    data.obs.append(ob)

    comm.comm_world.barrier()
    if comm.comm_world.rank == 0:
        timer.report_clear("Metadata queries")

    loader = tp.OpInputPlanck(
        commonflags_name="common_flags", flags_name="flags", margin=0
    )

    loader.exec(data)

    comm.comm_world.barrier()
    if comm.comm_world.rank == 0:
        timer.report_clear("Data read and cache")
        tod.cache.report()

    memreport("after loading", mpiworld)

    # make a planck Healpix pointing matrix
    mode = "IQU"
    if pars["temperature_only"] == "T":
        mode = "I"
    nside = int(pars["nside_map"])
    pointing = tp.OpPointingPlanck(
        nside=nside,
        mode=mode,
        RIMO=tod.RIMO,
        margin=0,
        apply_flags=False,
        keep_vel=False,
        keep_pos=False,
        keep_phase=False,
        keep_quats=True,
    )
    pointing.exec(data)

    comm.comm_world.barrier()
    if comm.comm_world.rank == 0:
        timer.report_clear("Pointing Matrix took, mode = {}".format(mode))

    memreport("after pointing", mpiworld)

    # simulate the TOD by convolving the sky with the beams

    if comm.comm_world.rank == 0:
        print("Convolving TOD", flush=True)

    for pattern in args.beamfile.split(","):
        skyfiles = {}
        beamfiles = {}
        for det in tod.detectors:
            freq = "{:03}".format(tp.utilities.det2freq(det))
            if "LFI" in det:
                psmdet = "{}_{}".format(freq, det[3:])
                if det.endswith("M"):
                    arm = "y"
                else:
                    arm = "x"
                graspdet = "{}_{}_{}".format(freq[1:], det[3:5], arm)
            else:
                psmdet = det.replace("-", "_")
                graspdet = det
            skyfile = (
                args.skyfile.replace("FREQ", freq)
                .replace("PSMDETECTOR", psmdet)
                .replace("DETECTOR", det)
            )
            skyfiles[det] = skyfile
            beamfile = pattern.replace("GRASPDETECTOR", graspdet).replace(
                "DETECTOR", det
            )
            beamfiles[det] = beamfile
            if comm.comm_world.rank == 0:
                print("Convolving {} with {}".format(skyfile, beamfile), flush=True)

        conviqt = OpSimConviqt(
            comm.comm_world,
            skyfiles,
            beamfiles,
            lmax=args.lmax,
            beammmax=args.beammmax,
            pol=True,
            fwhm=args.fwhm,
            order=args.order,
            calibrate=True,
            dxx=True,
            out="conviqt_tod",
            apply_flags=False,
            remove_monopole=args.remove_monopole,
            remove_dipole=args.remove_dipole,
            verbosity=1,
            normalize_beam=args.normalize,
        )
        conviqt.exec(data)

    comm.comm_world.barrier()
    if comm.comm_world.rank == 0:
        timer.report_clear("Convolution")

    memreport("after conviqt", mpiworld)

    if args.effdir_out is not None:
        if comm.comm_world.rank == 0:
            print("Writing TOD", flush=True)

        tod.set_effdir_out(args.effdir_out, None)
        writer = tp.OpOutputPlanck(
            signal_name="conviqt_tod",
            flags_name="flags",
            commonflags_name="common_flags",
        )
        writer.exec(data)

        comm.comm_world.barrier()
        if comm.comm_world.rank == 0:
            timer.report_clear("Conviqt output")

        memreport("after writing", mpiworld)

    # for now, we pass in the noise weights from the RIMO.
    detweights = {}
    for d in tod.detectors:
        net = tod.rimo[d].net
        fsample = tod.rimo[d].fsample
        detweights[d] = 1.0 / (fsample * net * net)

    if not args.skip_madam:
        if comm.comm_world.rank == 0:
            print("Calling Madam", flush=True)

        try:
            if args.obtmask_madam is None:
                obtmask = args.obtmask
            else:
                obtmask = args.obtmask_madam
            if args.flagmask_madam is None:
                flagmask = args.flagmask
            else:
                flagmask = args.flagmask_madam
            madam = OpMadam(
                params=pars,
                detweights=detweights,
                name="conviqt_tod",
                flag_name="flags",
                purge=True,
                name_out="madam_tod",
                common_flag_mask=obtmask,
                flag_mask=flagmask,
            )
        except Exception as e:
            raise Exception(
                "{:4} : ERROR: failed to initialize Madam: {}".format(
                    comm.comm_world.rank, e
                )
            )
        madam.exec(data)

        comm.comm_world.barrier()
        if comm.comm_world.rank == 0:
            timer.report_clear("Madam took {:.3f} s")

        memreport("after madam", mpiworld)

    gt.stop_all()
    if mpiworld is not None:
        mpiworld.barrier()
    timer = Timer()
    timer.start()
    alltimers = gather_timers(comm=mpiworld)
    if comm.world_rank == 0:
        out = os.path.join(args.out, "timing")
        dump_timing(alltimers, out)
        timer.stop()
        timer.report("Gather and dump timing info")
    return
def main():
    log = Logger.get()
    gt = GlobalTimers.get()
    gt.start("toast_planck_reduce (total)")

    mpiworld, procs, rank, comm = get_comm()

    if comm.world_rank == 0:
        print("Running with {} processes at {}".format(
            procs, str(datetime.datetime.now())))

    parser = argparse.ArgumentParser(description='Simple MADAM Mapmaking',
                                     fromfile_prefix_chars='@')
    parser.add_argument('--skip_madam',
                        dest='skip_madam',
                        default=False,
                        action='store_true',
                        help='D not make maps with Madam.')
    parser.add_argument('--skip_noise',
                        dest='skip_noise',
                        default=False,
                        action='store_true',
                        help='Do not add simulated noise to the TOD.')
    parser.add_argument('--rimo', required=True, help='RIMO file')
    parser.add_argument('--freq', required=True, type=np.int, help='Frequency')
    parser.add_argument('--debug',
                        dest='debug',
                        default=False,
                        action='store_true',
                        help='Write data distribution info to file')
    parser.add_argument('--dets',
                        required=False,
                        default=None,
                        help='Detector list (comma separated)')
    parser.add_argument('--effdir',
                        required=True,
                        help='Input Exchange Format File directory')
    parser.add_argument('--effdir2',
                        required=False,
                        help='Additional input Exchange Format File directory')
    parser.add_argument('--effdir_pntg',
                        required=False,
                        help='Input Exchange Format File directory for '
                        'pointing')
    parser.add_argument('--effdir_fsl',
                        required=False,
                        help='Input Exchange Format File directory for '
                        'straylight')
    parser.add_argument('--obtmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='OBT flag mask')
    parser.add_argument('--flagmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='Quality flag mask')
    parser.add_argument('--pntflagmask',
                        required=False,
                        default=0,
                        type=np.int,
                        help='Which OBT flag bits to raise for HCM maneuvers')
    parser.add_argument('--bad_intervals',
                        required=False,
                        help='Path to bad interval file.')
    parser.add_argument('--ringdb', required=True, help='Ring DB file')
    parser.add_argument('--odfirst',
                        required=False,
                        default=None,
                        help='First OD to use')
    parser.add_argument('--odlast',
                        required=False,
                        default=None,
                        help='Last OD to use')
    parser.add_argument('--ringfirst',
                        required=False,
                        default=None,
                        help='First ring to use')
    parser.add_argument('--ringlast',
                        required=False,
                        default=None,
                        help='Last ring to use')
    parser.add_argument('--obtfirst',
                        required=False,
                        default=None,
                        help='First OBT to use')
    parser.add_argument('--obtlast',
                        required=False,
                        default=None,
                        help='Last OBT to use')
    parser.add_argument('--read_eff',
                        dest='read_eff',
                        default=False,
                        action='store_true',
                        help='Read and co-add the signal from effdir')
    parser.add_argument('--decalibrate',
                        required=False,
                        help='Path to calibration file to decalibrate with. '
                        'You can use python string formatting, assuming '
                        '.format(mc)')
    parser.add_argument('--calibrate',
                        required=False,
                        help='Path to calibration file to calibrate with. '
                        'You can use python string formatting, assuming '
                        '.format(mc)')
    parser.add_argument('--madampar',
                        required=False,
                        default=None,
                        help='Madam parameter file')
    parser.add_argument('--nside',
                        required=False,
                        default=None,
                        type=np.int,
                        help='Madam resolution')
    parser.add_argument('--out',
                        required=False,
                        default='.',
                        help='Output directory')
    parser.add_argument('--madam_prefix', required=False, help='map prefix')
    parser.add_argument('--make_rings',
                        dest='make_rings',
                        default=False,
                        action='store_true',
                        help='Compile ringsets.')
    parser.add_argument('--nside_ring',
                        required=False,
                        default=128,
                        type=np.int,
                        help='Ringset resolution')
    parser.add_argument('--ring_root',
                        required=False,
                        default='ringset',
                        help='Root filename for ringsets (setting to empty '
                        'disables ringset output).')
    parser.add_argument('--MC_start',
                        required=False,
                        default=0,
                        type=np.int,
                        help='First Monte Carlo noise realization')
    parser.add_argument('--MC_count',
                        required=False,
                        default=1,
                        type=np.int,
                        help='Number of Monte Carlo noise realizations')
    # noise parameters
    parser.add_argument('--noisefile',
                        required=False,
                        default='RIMO',
                        help='Path to noise PSD files for noise filter. '
                        'Tag DETECTOR will be replaced with detector name.')
    parser.add_argument('--noisefile_simu',
                        required=False,
                        default='RIMO',
                        help='Path to noise PSD files for noise simulation. '
                        'Tag DETECTOR will be replaced with detector name.')
    # Dipole parameters
    dipogroup = parser.add_mutually_exclusive_group()
    dipogroup.add_argument('--dipole',
                           dest='dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate dipole')
    dipogroup.add_argument('--solsys_dipole',
                           dest='solsys_dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate solar system dipole')
    dipogroup.add_argument('--orbital_dipole',
                           dest='orbital_dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate orbital dipole')
    dipo_parameters_group = parser.add_argument_group('dipole_parameters')
    dipo_parameters_group.add_argument(
        '--solsys_speed',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_speed"],
        help='Solar system speed wrt. CMB rest frame in km/s. Default is '
        'Planck 2015 best fit value')
    dipo_parameters_group.add_argument(
        '--solsys_glon',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_glon"],
        help='Solar system velocity direction longitude in degrees')
    dipo_parameters_group.add_argument(
        '--solsys_glat',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_glat"],
        help='Solar system velocity direction latitude in degrees')

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

    if comm.world_rank == 0:
        print('All parameters:')
        print(args, flush=True)

    if args.MC_count < 1:
        raise RuntimeError('MC_count = {} < 1. Nothing done.'
                           ''.format(args.MC_count))

    timer = Timer()
    timer.start()

    nrange = 1

    odranges = None
    if args.odfirst is not None and args.odlast is not None:
        odranges = []
        firsts = [int(i) for i in str(args.odfirst).split(',')]
        lasts = [int(i) for i in str(args.odlast).split(',')]
        for odfirst, odlast in zip(firsts, lasts):
            odranges.append((odfirst, odlast))
        nrange = len(odranges)

    ringranges = None
    if args.ringfirst is not None and args.ringlast is not None:
        ringranges = []
        firsts = [int(i) for i in str(args.ringfirst).split(',')]
        lasts = [int(i) for i in str(args.ringlast).split(',')]
        for ringfirst, ringlast in zip(firsts, lasts):
            ringranges.append((ringfirst, ringlast))
        nrange = len(ringranges)

    obtranges = None
    if args.obtfirst is not None and args.obtlast is not None:
        obtranges = []
        firsts = [float(i) for i in str(args.obtfirst).split(',')]
        lasts = [float(i) for i in str(args.obtlast).split(',')]
        for obtfirst, obtlast in zip(firsts, lasts):
            obtranges.append((obtfirst, obtlast))
        nrange = len(obtranges)

    if odranges is None:
        odranges = [None] * nrange

    if ringranges is None:
        ringranges = [None] * nrange

    if obtranges is None:
        obtranges = [None] * nrange

    detectors = None
    if args.dets is not None:
        detectors = re.split(',', args.dets)

    # create the TOD for this observation

    if args.noisefile != 'RIMO' or args.noisefile_simu != 'RIMO':
        do_eff_cache = True
    else:
        do_eff_cache = False

    tods = []

    for obtrange, ringrange, odrange in zip(obtranges, ringranges, odranges):
        # create the TOD for this observation
        tods.append(
            tp.Exchange(comm=comm.comm_group,
                        detectors=detectors,
                        ringdb=args.ringdb,
                        effdir_in=args.effdir,
                        extra_effdirs=[args.effdir2, args.effdir_fsl],
                        effdir_pntg=args.effdir_pntg,
                        obt_range=obtrange,
                        ring_range=ringrange,
                        od_range=odrange,
                        freq=args.freq,
                        RIMO=args.rimo,
                        obtmask=args.obtmask,
                        flagmask=args.flagmask,
                        pntflagmask=args.pntflagmask,
                        do_eff_cache=do_eff_cache))

    # Make output directory

    if not os.path.isdir(args.out) and comm.world_rank == 0:
        os.makedirs(args.out)

    # Read in madam parameter file
    # Allow more than one entry, gather into a list
    repeated_keys = ['detset', 'detset_nopol', 'survey']
    pars = {}

    if comm.world_rank == 0:
        pars['kfirst'] = False
        pars['temperature_only'] = True
        pars['base_first'] = 60.0
        pars['nside_submap'] = 16
        pars['write_map'] = False
        pars['write_binmap'] = True
        pars['write_matrix'] = False
        pars['write_wcov'] = False
        pars['write_hits'] = True
        pars['kfilter'] = False
        pars['info'] = 3
        if args.madampar:
            pat = re.compile(r'\s*(\S+)\s*=\s*(\S+(\s+\S+)*)\s*')
            comment = re.compile(r'^#.*')
            with open(args.madampar, 'r') as f:
                for line in f:
                    if not comment.match(line):
                        result = pat.match(line)
                        if result:
                            key, value = result.group(1), result.group(2)
                            if key in repeated_keys:
                                if key not in pars:
                                    pars[key] = []
                                pars[key].append(value)
                            else:
                                pars[key] = value
        # Command line parameters override the ones in the madam parameter file
        if 'file_root' not in pars:
            pars['file_root'] = 'madam'
        if args.madam_prefix is not None:
            pars['file_root'] = args.madam_prefix
        sfreq = '{:03}'.format(args.freq)
        if sfreq not in pars['file_root']:
            pars['file_root'] += '_' + sfreq
        try:
            fsample = {30: 32.51, 44: 46.55, 70: 78.77}[args.freq]
        except Exception:
            fsample = 180.3737
        pars['fsample'] = fsample
        pars['path_output'] = args.out

    pars = comm.comm_world.bcast(pars, root=0)

    madam_mcmode = True
    if 'nsubchunk' in pars and int(pars['nsubchunk']) > 1:
        madam_mcmode = False

    if args.noisefile != 'RIMO' or args.noisefile_simu != 'RIMO':
        # We split MPI_COMM_WORLD into single process groups, each of
        # which is assigned one or more observations (rings)
        comm = toast.Comm(groupsize=1)

    # This is the distributed data, consisting of one or
    # more observations, each distributed over a communicator.
    data = toast.Data(comm)

    for iobs, tod in enumerate(tods):
        if args.noisefile != 'RIMO' or args.noisefile_simu != 'RIMO':
            # Use a toast helper method to optimally distribute rings between
            # processes.
            dist = distribute_discrete(tod.ringsizes, comm.world_size)
            my_first_ring, my_n_ring = dist[comm.world_rank]

            for my_ring in range(my_first_ring, my_first_ring + my_n_ring):
                ringtod = tp.Exchange.from_tod(
                    tod,
                    my_ring,
                    comm.comm_group,
                    noisefile=args.noisefile,
                    noisefile_simu=args.noisefile_simu)
                ob = {}
                ob['name'] = 'ring{:05}'.format(ringtod.globalfirst_ring)
                ob['id'] = ringtod.globalfirst_ring
                ob['tod'] = ringtod
                ob['intervals'] = ringtod.valid_intervals
                ob['baselines'] = None
                ob['noise'] = ringtod.noise
                ob['noise_simu'] = ringtod.noise_simu
                data.obs.append(ob)
        else:
            ob = {}
            ob['name'] = 'observation{:04}'.format(iobs)
            ob['id'] = 0
            ob['tod'] = tod
            ob['intervals'] = tod.valid_intervals
            ob['baselines'] = None
            ob['noise'] = tod.noise
            ob['noise_simu'] = tod.noise

            data.obs.append(ob)

    rimo = tods[0].rimo

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Metadata queries")

    # Always read the signal and flags, even if the signal is later
    # overwritten.  There is no overhead for the signal because it is
    # interlaced with the flags.

    tod_name = 'signal'
    timestamps_name = 'timestamps'
    flags_name = 'flags'
    common_flags_name = 'common_flags'
    reader = tp.OpInputPlanck(signal_name=tod_name,
                              flags_name=flags_name,
                              timestamps_name=timestamps_name,
                              commonflags_name=common_flags_name)
    if comm.world_rank == 0:
        print('Reading input signal from {}'.format(args.effdir), flush=True)
    reader.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Read")

    # Clear the signal if we don't need it

    if not args.read_eff:
        eraser = tp.OpCacheMath(in1=tod_name,
                                in2=0,
                                multiply=True,
                                out=tod_name)
        if comm.world_rank == 0:
            print('Erasing TOD', flush=True)
        eraser.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Erase")

    # Optionally flag bad intervals

    if args.bad_intervals is not None:
        flagger = tp.OpBadIntervals(path=args.bad_intervals)
        flagger.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Apply {}".format(args.bad_intervals))

    # Now read an optional second TOD to add with the first

    if args.effdir2 is not None:
        # Read the extra TOD and add it to the first one
        reader = tp.OpInputPlanck(signal_name='tod2',
                                  flags_name=None,
                                  timestamps_name=None,
                                  commonflags_name=None,
                                  effdir=args.effdir2)
        if comm.world_rank == 0:
            print('Reading extra TOD from {}'.format(args.effdir2), flush=True)
        reader.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            print("Reading took {:.3f} s".format(elapsed), flush=True)

        adder = tp.OpCacheMath(in1=tod_name,
                               in2='signal2',
                               add=True,
                               out=tod_name)
        if comm.world_rank == 0:
            print('Adding TODs', flush=True)
        adder.exec(data)

        # Erase the extra cache object
        for ob in data.obs:
            tod = ob['tod']
            tod.cache.clear('signal2_.*')

    if args.effdir_fsl is not None:
        # Read the straylight signal into the tod cache under
        # "fsl_<detector>"
        reader = tp.OpInputPlanck(signal_name='fsl',
                                  flags_name=None,
                                  timestamps_name=None,
                                  commonflags_name=None,
                                  effdir=args.effdir_fsl)
        if comm.world_rank == 0:
            print('Reading straylight signal from {}'.format(args.effdir_fsl),
                  flush=True)
        reader.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Read FSL")
        do_fsl = True
    else:
        do_fsl = False

    # make a planck Healpix pointing matrix
    mode = 'IQU'
    if pars['temperature_only'] == 'T':
        mode = 'I'

    if args.nside is None:
        if 'nside_map' in pars:
            nside = int(pars['nside_map'])
        else:
            raise RuntimeError(
                'Nside must be set either in the Madam parameter file or on '
                'the command line')
    else:
        nside = args.nside
        pars['nside_map'] = nside
    if 'nside_cross' not in pars or pars['nside_cross'] > pars['nside_map']:
        pars['nside_cross'] = pars['nside_map']

    do_dipole = args.dipole or args.solsys_dipole or args.orbital_dipole

    pointing = tp.OpPointingPlanck(nside=nside,
                                   mode=mode,
                                   RIMO=rimo,
                                   margin=0,
                                   apply_flags=True,
                                   keep_vel=do_dipole,
                                   keep_pos=False,
                                   keep_phase=False,
                                   keep_quats=do_dipole)
    pointing.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Pointing Matrix")

    flags_name = 'flags'
    common_flags_name = 'common_flags'

    # for now, we pass in the noise weights from the RIMO.
    detweights = {}
    for d in tod.detectors:
        net = tod.rimo[d].net
        fsample = tod.rimo[d].fsample
        detweights[d] = 1.0 / (fsample * net * net)

    if args.debug:
        with open("debug_planck_exchange_madam.txt", "w") as f:
            data.info(f)

    if do_dipole:
        # Simulate the dipole
        if args.dipole:
            dipomode = 'total'
        elif args.solsys_dipole:
            dipomode = 'solsys'
        else:
            dipomode = 'orbital'
        dipo = tp.OpDipolePlanck(args.freq,
                                 solsys_speed=args.solsys_speed,
                                 solsys_glon=args.solsys_glon,
                                 solsys_glat=args.solsys_glat,
                                 mode=dipomode,
                                 output='dipole',
                                 keep_quats=False)
        dipo.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Dipole")

    # Loop over Monte Carlos

    madam = None

    for mc in range(args.MC_start, args.MC_start + args.MC_count):

        out = "{}/{:05d}".format(args.out, mc)
        if comm.world_rank == 0:
            if not os.path.isdir(out):
                os.makedirs(out)

        # clear all noise data from the cache, so that we can generate
        # new noise timestreams.

        for ob in data.obs:
            ob['tod'].cache.clear("noise_.*")
        tod_name = 'signal'

        if do_dipole:
            adder = tp.OpCacheMath(in1=tod_name,
                                   in2='dipole',
                                   add=True,
                                   out='noise')
            adder.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Add dipole".format(mc))
            tod_name = 'noise'

        # Simulate noise

        if not args.skip_noise:
            tod_name = 'noise'
            nse = toast.tod.OpSimNoise(out=tod_name,
                                       realization=mc,
                                       component=0,
                                       noise='noise_simu',
                                       rate=fsample)
            if comm.world_rank == 0:
                print('Simulating noise from {}'.format(args.noisefile_simu),
                      flush=True)
            nse.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Noise simulation".format(mc))

            # If we didn't add the dipole, we need to add the input
            # signal with the noise we just simulated

            if args.read_eff and not do_dipole:
                adder = tp.OpCacheMath(in1=tod_name,
                                       in2='signal',
                                       add=True,
                                       out=tod_name)
                adder.exec(data)
                if mpiworld is not None:
                    mpiworld.barrier()
                if comm.world_rank == 0:
                    timer.report_clear("MC {}:  Add input signal".format(mc))

        # Make rings

        if args.make_rings:
            ringmaker = tp.OpRingMaker(args.nside_ring,
                                       nside,
                                       signal=tod_name,
                                       fileroot=args.ring_root,
                                       out=out,
                                       commonmask=args.obtmask,
                                       detmask=args.flagmask)
            ringmaker.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Ringmaking".format(mc))

        # Apply calibration errors

        if args.decalibrate is not None:
            fn = args.decalibrate
            try:
                fn = fn.format(mc)
            except Exception:
                pass
            if comm.world_rank == 0:
                print('Decalibrating with {}'.format(fn), flush=True)
            decalibrator = tp.OpCalibPlanck(signal_in=tod_name,
                                            signal_out='noise',
                                            file_gain=fn,
                                            decalibrate=True)
            decalibrator.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Decalibrate".format(mc))
            tod_name = 'noise'

        if args.calibrate is not None:
            fn = args.calibrate
            try:
                fn = fn.format(mc)
            except Exception:
                pass
            if comm.world_rank == 0:
                print('Calibrating with {}'.format(fn), flush=True)
            calibrator = tp.OpCalibPlanck(signal_in=tod_name,
                                          signal_out='noise',
                                          file_gain=fn)
            calibrator.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Calibrate".format(mc))
            tod_name = 'noise'

        # Subtract the dipole and straylight

        if do_dipole:
            subtractor = tp.OpCacheMath(in1=tod_name,
                                        in2='dipole',
                                        subtract=True,
                                        out='noise')
            subtractor.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Subtract dipole".format(mc))
            tod_name = 'noise'

        if do_fsl:
            subtractor = tp.OpCacheMath(in1=tod_name,
                                        in2='fsl',
                                        subtract=True,
                                        out='noise')
            subtractor.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Subtract straylight".format(mc))
            tod_name = 'noise'

        # Make the map

        if not args.skip_madam:
            # Make maps
            if madam is None:
                try:
                    madam = toast.todmap.OpMadam(params=pars,
                                                 detweights=detweights,
                                                 purge_tod=True,
                                                 name=tod_name,
                                                 apply_flags=False,
                                                 name_out=None,
                                                 noise='noise',
                                                 mcmode=madam_mcmode,
                                                 translate_timestamps=False)
                except Exception as e:
                    raise Exception(
                        '{:4} : ERROR: failed to initialize Madam: '
                        '{}'.format(comm.world_rank, e))
            madam.params['path_output'] = out
            madam.exec(data)
            if mpiworld is not None:
                mpiworld.barrier()
            if comm.world_rank == 0:
                timer.report_clear("MC {}:  Mapmaking".format(mc))

    gt.stop_all()
    if mpiworld is not None:
        mpiworld.barrier()
    timer = Timer()
    timer.start()
    alltimers = gather_timers(comm=mpiworld)
    if comm.world_rank == 0:
        out = os.path.join(args.out, "timing")
        dump_timing(alltimers, out)
        timer.stop()
        timer.report("Gather and dump timing info")
    return
def main():
    timer0 = Timer()
    timer0.start()

    log = Logger.get()
    gt = GlobalTimers.get()
    gt.start("toast_planck_reduce (total)")

    mpiworld, procs, rank, comm = get_comm()
    memreport("At start of pipeline", mpiworld)

    if comm.world_rank == 0:
        print("Running with {} processes at {}".format(
            procs, str(datetime.datetime.now())))

    parser = argparse.ArgumentParser(description='Simple MADAM Mapmaking',
                                     fromfile_prefix_chars='@')
    parser.add_argument('--rimo', required=True, help='RIMO file')
    parser.add_argument('--freq', required=True, type=np.int, help='Frequency')
    parser.add_argument('--debug',
                        dest='debug',
                        default=False,
                        action='store_true',
                        help='Write data distribution info to file')
    parser.add_argument('--dets',
                        required=False,
                        default=None,
                        help='Detector list (comma separated)')
    parser.add_argument('--effdir',
                        required=True,
                        help='Input Exchange Format File directory')
    parser.add_argument('--effdir_pntg',
                        required=False,
                        help='Input Exchange Format File directory '
                        'for pointing')
    parser.add_argument('--coord',
                        default='G',
                        help='Coordinate system, "G", "E" or "C"')
    parser.add_argument('--obtmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='OBT flag mask')
    parser.add_argument('--flagmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='Quality flag mask')
    parser.add_argument('--pntflagmask',
                        required=False,
                        default=0,
                        type=np.int,
                        help='Pointing flag mask')
    parser.add_argument('--bad_intervals',
                        required=False,
                        help='Path to bad interval file.')
    parser.add_argument('--ringdb', required=True, help='Ring DB file')
    parser.add_argument('--odfirst',
                        required=False,
                        default=None,
                        type=np.int,
                        help='First OD to use')
    parser.add_argument('--odlast',
                        required=False,
                        default=None,
                        type=np.int,
                        help='Last OD to use')
    parser.add_argument('--ringfirst',
                        required=False,
                        default=None,
                        help='First ring to use (can be a list)')
    parser.add_argument('--ringlast',
                        required=False,
                        default=None,
                        help='Last ring to use (can be a list)')
    parser.add_argument('--obtfirst',
                        required=False,
                        default=None,
                        type=np.float,
                        help='First OBT to use')
    parser.add_argument('--obtlast',
                        required=False,
                        default=None,
                        type=np.float,
                        help='Last OBT to use')
    parser.add_argument('--out',
                        required=False,
                        default='.',
                        help='Output directory')
    parser.add_argument('--catalog', required=True, help='Target catalog file')
    parser.add_argument('--radius',
                        required=True,
                        type=np.float,
                        help='Search radius about the source [arc min]')
    parser.add_argument('--mask',
                        required=False,
                        help='Mask defining region of the sky to accept')
    parser.add_argument('--bg',
                        required=False,
                        help='Background map to subtract')
    parser.add_argument('--recalib_bg',
                        dest='recalib_bg',
                        default=False,
                        action='store_true',
                        help='Recalibrate bg map for each ring.')
    parser.add_argument('--full_rings',
                        dest='full_rings',
                        default=False,
                        action='store_true',
                        help='Extract impacted rings entirely.')
    # noise parameters
    parser.add_argument('--noisefile',
                        required=False,
                        default='RIMO',
                        help='Path to noise PSD files for noise filter. '
                        'Tag DETECTOR will be replaced with detector name.')
    # Dipole parameters
    dipogroup = parser.add_mutually_exclusive_group()
    dipogroup.add_argument('--dipole',
                           dest='dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate dipole')
    dipogroup.add_argument('--solsys_dipole',
                           dest='solsys_dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate solar system dipole')
    dipogroup.add_argument('--orbital_dipole',
                           dest='orbital_dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate orbital dipole')

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

    if comm.world_rank == 0:
        print('All parameters:')
        print(args, flush=True)

    data = create_observations(args, comm)
    rimo = data.obs[0]["tod"].rimo

    memreport("After create observations", mpiworld)

    # Read in the signal

    timer = Timer()
    timer.start()

    reader = tp.OpInputPlanck(signal_name='signal', flags_name='flags')
    if comm.world_rank == 0:
        print('Reading input signal from {}'.format(args.effdir), flush=True)
    reader.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Reading")
    tod_name = 'signal'
    flags_name = 'flags'

    memreport("After read", mpiworld)

    # Optionally flag bad intervals

    if args.bad_intervals is not None:
        flagger = tp.OpBadIntervals(path=args.bad_intervals)
        flagger.exec(data)
        if comm.world_rank == 0:
            timer.report_clear("Applying {}".format(args.bad_intervals))

    do_dipole = (args.dipole or args.solsys_dipole or args.orbital_dipole)

    # make a planck Healpix pointing matrix
    pointing = tp.OpPointingPlanck(nside=1024,
                                   mode='IQU',
                                   RIMO=rimo,
                                   margin=0,
                                   apply_flags=False,
                                   keep_vel=do_dipole,
                                   keep_pos=False,
                                   keep_phase=True,
                                   keep_quats=True)

    pointing.exec(data)

    memreport("After pointing", mpiworld)

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Pointing Matrix")

    # Optionally subtract the dipole

    if do_dipole:
        if args.dipole:
            dipomode = 'total'
        elif args.solsys_dipole:
            dipomode = 'solsys'
        else:
            dipomode = 'orbital'
        dipo = tp.OpDipolePlanck(args.freq,
                                 mode=dipomode,
                                 output='dipole',
                                 keep_quats=True)
        dipo.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Dipole")
        subtractor = tp.OpCacheMath(in1=tod_name,
                                    in2='dipole',
                                    subtract=True,
                                    out=tod_name)
        if comm.comm_world.rank == 0:
            print('Subtracting dipole', flush=True)
        subtractor.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Dipole subtraction")

        memreport("After dipole", mpiworld)

    extract = tp.OpExtractPlanck(rimo,
                                 args.catalog,
                                 args.radius,
                                 mpiworld,
                                 common_flag_mask=args.obtmask,
                                 flag_mask=args.flagmask,
                                 maskfile=args.mask,
                                 bg=args.bg,
                                 full_rings=args.full_rings,
                                 recalibrate_bg=args.recalib_bg,
                                 out=args.out)
    extract.exec(data)

    memreport("After extract", mpiworld)

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Extraction")

    gt.stop_all()
    if mpiworld is not None:
        mpiworld.barrier()
    timer = Timer()
    timer.start()
    alltimers = gather_timers(comm=mpiworld)
    if comm.world_rank == 0:
        out = os.path.join(args.out, "timing")
        dump_timing(alltimers, out)
        timer.report_clear("Gather and dump timing info")
        timer0.report_clear("Full pipeline")
    return
def main():
    log = Logger.get()
    gt = GlobalTimers.get()
    gt.start("toast_planck_reduce (total)")

    mpiworld, procs, rank, comm = get_comm()

    memreport("at beginning of main", mpiworld)

    # This is the 2-level toast communicator.  By default,
    # there is just one group which spans MPI_COMM_WORLD.
    comm = toast.Comm()

    if comm.world_rank == 0:
        print("Running with {} processes at {}".format(
            procs, str(datetime.datetime.now())))

    parser = argparse.ArgumentParser(description='Simple MADAM Mapmaking',
                                     fromfile_prefix_chars='@')
    parser.add_argument('--rimo', required=True, help='RIMO file')
    parser.add_argument('--freq', required=True, type=np.int, help='Frequency')
    parser.add_argument('--nside',
                        required=False,
                        type=np.int,
                        default=512,
                        help='Map resolution')
    parser.add_argument('--nside_cross',
                        required=False,
                        type=np.int,
                        default=512,
                        help='Destriping resolution')
    parser.add_argument('--debug',
                        dest='debug',
                        default=False,
                        action='store_true',
                        help='Write data distribution info to file')
    parser.add_argument('--dets',
                        required=False,
                        default=None,
                        help='Detector list (comma separated)')
    parser.add_argument('--effdir',
                        required=True,
                        help='Input Exchange Format File directory')
    parser.add_argument('--effdir_in_diode0',
                        required=False,
                        default=None,
                        help='Input Exchange Format File directory, '
                        'LFI diode 0')
    parser.add_argument('--effdir_in_diode1',
                        required=False,
                        default=None,
                        help='Input Exchange Format File directory, '
                        'LFI diode 1')
    parser.add_argument('--effdir_pntg',
                        required=False,
                        help='Input Exchange Format File directory '
                        'for pointing')
    parser.add_argument('--effdir_out',
                        required=False,
                        help='Output directory for destriped TOD')
    parser.add_argument('--effdir_out_diode0',
                        required=False,
                        help='Output directory for destriped TOD, LFI diode 0')
    parser.add_argument('--effdir_out_diode1',
                        required=False,
                        help='Output directory for destriped TOD, LFI diode 1')
    parser.add_argument('--obtmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='OBT flag mask')
    parser.add_argument('--flagmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='Quality flag mask')
    parser.add_argument('--pntflagmask',
                        required=False,
                        default=0,
                        type=np.int,
                        help='Pointing flag mask')
    parser.add_argument('--bad_intervals',
                        required=False,
                        help='Path to bad interval file.')
    parser.add_argument('--ringdb', required=True, help='Ring DB file')
    parser.add_argument('--odfirst',
                        required=False,
                        default=None,
                        type=np.int,
                        help='First OD to use')
    parser.add_argument('--odlast',
                        required=False,
                        default=None,
                        type=np.int,
                        help='Last OD to use')
    parser.add_argument('--ringfirst',
                        required=False,
                        default=None,
                        help='First ring to use (can be a list)')
    parser.add_argument('--ringlast',
                        required=False,
                        default=None,
                        help='Last ring to use (can be a list)')
    parser.add_argument('--obtfirst',
                        required=False,
                        default=None,
                        type=np.float,
                        help='First OBT to use')
    parser.add_argument('--obtlast',
                        required=False,
                        default=None,
                        type=np.float,
                        help='Last OBT to use')
    parser.add_argument('--madampar',
                        required=False,
                        default=None,
                        help='Madam parameter file')
    parser.add_argument('--out',
                        required=False,
                        default='.',
                        help='Output directory')
    parser.add_argument('--madam_prefix', required=False, help='map prefix')
    parser.add_argument('--split_mask',
                        required=False,
                        default=None,
                        help='Intensity mask, non-zero pixels are not split.')
    parser.add_argument('--save_leakage_matrices',
                        dest='save_leakage_matrices',
                        default=False,
                        action='store_true',
                        help='Compile and write out the leakage projection '
                        'matrices.')
    # noise parameters
    parser.add_argument('--noisefile',
                        required=False,
                        default='RIMO',
                        help='Path to noise PSD files for noise filter. '
                        'Tag DETECTOR will be replaced with detector name.')
    parser.add_argument('--static_noise',
                        dest='static_noise',
                        required=False,
                        default=False,
                        action='store_true',
                        help='Assume constant noise PSD')
    parser.add_argument('--filterfile',
                        required=False,
                        help='Extra filter file.')

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

    if comm.comm_world.rank == 0:
        print('All parameters:')
        print(args, flush=True)

    timer = Timer()
    timer.start()

    nrange = 1

    odranges = None
    if args.odfirst is not None and args.odlast is not None:
        odranges = []
        firsts = [int(i) for i in str(args.odfirst).split(',')]
        lasts = [int(i) for i in str(args.odlast).split(',')]
        for odfirst, odlast in zip(firsts, lasts):
            odranges.append((odfirst, odlast))
        nrange = len(odranges)

    ringranges = None
    if args.ringfirst is not None and args.ringlast is not None:
        ringranges = []
        firsts = [int(i) for i in str(args.ringfirst).split(',')]
        lasts = [int(i) for i in str(args.ringlast).split(',')]
        for ringfirst, ringlast in zip(firsts, lasts):
            ringranges.append((ringfirst, ringlast))
        nrange = len(ringranges)

    obtranges = None
    if args.obtfirst is not None and args.obtlast is not None:
        obtranges = []
        firsts = [float(i) for i in str(args.obtfirst).split(',')]
        lasts = [float(i) for i in str(args.obtlast).split(',')]
        for obtfirst, obtlast in zip(firsts, lasts):
            obtranges.append((obtfirst, obtlast))
        nrange = len(obtranges)

    if odranges is None:
        odranges = [None] * nrange

    if ringranges is None:
        ringranges = [None] * nrange

    if obtranges is None:
        obtranges = [None] * nrange

    detectors = None
    if args.dets is not None:
        detectors = re.split(',', args.dets)

    # create the TOD for this observation

    if args.noisefile != 'RIMO' and not args.static_noise:
        do_eff_cache = True
    else:
        do_eff_cache = False

    tods = []

    if args.static_noise:
        noisefile = args.noisefile
    else:
        noisefile = 'RIMO'

    for obtrange, ringrange, odrange in zip(obtranges, ringranges, odranges):
        tods.append(
            tp.Exchange(comm=comm.comm_group,
                        detectors=detectors,
                        ringdb=args.ringdb,
                        effdir_in=args.effdir,
                        effdir_in_diode0=args.effdir_in_diode0,
                        effdir_in_diode1=args.effdir_in_diode1,
                        effdir_pntg=args.effdir_pntg,
                        obt_range=obtrange,
                        ring_range=ringrange,
                        od_range=odrange,
                        freq=args.freq,
                        RIMO=args.rimo,
                        obtmask=args.obtmask,
                        flagmask=args.flagmask,
                        pntflagmask=args.pntflagmask,
                        do_eff_cache=do_eff_cache,
                        noisefile=noisefile))

    rimo = tods[0].rimo

    # Make output directory

    if not os.path.isdir(args.out) and comm.comm_world.rank == 0:
        os.makedirs(args.out)

    # Read in madam parameter file
    # Allow more than one entry, gather into a list
    repeated_keys = ['detset', 'detset_nopol', 'survey']
    pars = {}

    if comm.comm_world.rank == 0:
        pars['kfirst'] = False
        pars['temperature_only'] = True
        pars['base_first'] = 60.0
        pars['nside_map'] = args.nside
        pars['nside_cross'] = min(args.nside, args.nside_cross)
        pars['nside_submap'] = 16
        pars['write_map'] = False
        pars['write_binmap'] = True
        pars['write_matrix'] = False
        pars['write_wcov'] = False
        pars['write_hits'] = True
        pars['kfilter'] = False
        pars['info'] = 3
        pars['pixlim_map'] = 1e-3
        pars['pixlim_cross'] = 1e-3
        if args.madampar:
            pat = re.compile(r'\s*(\S+)\s*=\s*(\S+(\s+\S+)*)\s*')
            comment = re.compile(r'^#.*')
            with open(args.madampar, 'r') as f:
                for line in f:
                    if not comment.match(line):
                        result = pat.match(line)
                        if result:
                            key, value = result.group(1), result.group(2)
                            if key in repeated_keys:
                                if key not in pars:
                                    pars[key] = []
                                pars[key].append(value)
                            else:
                                pars[key] = value
        # Command line parameters override the ones in the madam parameter file
        if 'file_root' not in pars:
            pars['file_root'] = 'madam'
        if args.madam_prefix is not None:
            pars['file_root'] = args.madam_prefix
        sfreq = '{:03}'.format(args.freq)
        if sfreq not in pars['file_root']:
            pars['file_root'] += '_' + sfreq
        try:
            fsample = {30: 32.51, 44: 46.55, 70: 78.77}[args.freq]
        except Exception:
            fsample = 180.3737
        pars['fsample'] = fsample
        pars['path_output'] = args.out

        if args.save_leakage_matrices:
            pars['write_leakmatrix'] = True

    pars = comm.comm_world.bcast(pars, root=0)

    if args.noisefile != 'RIMO':
        # We split MPI_COMM_WORLD into single process groups, each of
        # which is assigned one or more observations (rings)
        comm = toast.Comm(groupsize=1)

    # This is the distributed data, consisting of one or
    # more observations, each distributed over a communicator.
    data = toast.Data(comm)

    for iobs, tod in enumerate(tods):
        if args.noisefile != 'RIMO' and not args.static_noise:
            # Use a toast helper method to optimally distribute rings between
            # processes.
            dist = toast.distribute_discrete(tod.ringsizes, comm.world_size)
            my_first_ring, my_n_ring = dist[comm.comm_world.rank]

            for my_ring in range(my_first_ring, my_first_ring + my_n_ring):
                ringtod = tp.Exchange.from_tod(tod,
                                               my_ring,
                                               comm.comm_group,
                                               noisefile=args.noisefile)
                ob = {}
                ob['name'] = 'ring{:05}'.format(ringtod.globalfirst_ring)
                ob['id'] = ringtod.globalfirst_ring
                ob['tod'] = ringtod
                ob['intervals'] = ringtod.valid_intervals
                ob['baselines'] = None
                ob['noise'] = ringtod.noise
                data.obs.append(ob)
        else:
            ob = {}
            ob['name'] = 'observation{:04}'.format(iobs)
            ob['id'] = 0
            ob['tod'] = tod
            ob['intervals'] = tod.valid_intervals
            ob['baselines'] = None
            ob['noise'] = tod.noise

            data.obs.append(ob)

    comm.comm_world.barrier()
    timer.stop()
    if comm.comm_world.rank == 0:
        timer.report("Metadata queries")

    if args.effdir_out is not None or (args.effdir_out_diode0 is not None
                                       and args.effdir_out_diode1 is not None):
        do_output = True
    else:
        do_output = False

    # Read in the signal

    timer.clear()
    timer.start()
    reader = tp.OpInputPlanck(signal_name='signal', flags_name='flags')
    if comm.comm_world.rank == 0:
        print('Reading input signal from {}'.format(args.effdir), flush=True)
    reader.exec(data)
    comm.comm_world.barrier()
    timer.stop()
    if comm.comm_world.rank == 0:
        timer.report("Read")
    tod_name = 'signal'
    flags_name = 'flags'

    # Optionally filter the signal

    apply_filter(args, data)

    # Optionally flag bad intervals

    if args.bad_intervals is not None:
        timer = Timer()
        timer.start()
        flagger = tp.OpBadIntervals(path=args.bad_intervals)
        flagger.exec(data)
        timer.stop()
        if comm.comm_world.rank == 0:
            timer.report("Apply {}".format(args.bad_intervals))

    # make a planck Healpix pointing matrix
    timer.clear()
    timer.start()
    mode = 'IQU'
    if pars['temperature_only'] == 'T':
        mode = 'I'
    nside = int(pars['nside_map'])
    pointing = tp.OpPointingPlanck(nside=nside,
                                   mode=mode,
                                   RIMO=rimo,
                                   margin=0,
                                   apply_flags=(not do_output),
                                   keep_vel=False,
                                   keep_pos=False,
                                   keep_phase=False,
                                   keep_quats=False)

    pointing.exec(data)

    comm.comm_world.barrier()
    timer.stop()
    if comm.comm_world.rank == 0:
        timer.report("Pointing Matrix, mode = {}".format(mode))

    for obs in data.obs:
        obs['tod'].purge_eff_cache()

    # for now, we pass in the noise weights from the RIMO.
    detweights = {}
    for d in tod.detectors:
        if d[-1] in '01' and d[-2] != '-':
            det = to_radiometer(d)
        else:
            det = d
        net = tod.rimo[det].net
        fsample = tod.rimo[det].fsample
        detweights[d] = 1.0 / (fsample * net * net)

    if do_output:
        name_out = 'madam_tod'
    else:
        name_out = None

    timer.clear()
    timer.start()
    try:
        madam = toast.todmap.OpMadam(name=tod_name,
                                     flag_name=flags_name,
                                     apply_flags=do_output,
                                     params=pars,
                                     detweights=detweights,
                                     purge=True,
                                     name_out=name_out,
                                     translate_timestamps=False)
    except Exception as e:
        raise Exception('{:4} : ERROR: failed to initialize Madam: {}'.format(
            comm.comm_world.rank, e))
    madam.exec(data)

    comm.comm_world.barrier()
    timer.stop()
    if comm.comm_world.rank == 0:
        timer.report("Madam")

    if do_output:
        timer = Timer()
        timer.start()
        writer = tp.OpOutputPlanck(signal_name='madam_tod',
                                   flags_name=None,
                                   commonflags_name=None,
                                   effdir_out=args.effdir_out,
                                   effdir_out_diode0=args.effdir_out_diode0,
                                   effdir_out_diode1=args.effdir_out_diode1)

        writer.exec(data)

        comm.comm_world.barrier()
        timer.stop()
        if comm.comm_world.rank == 0:
            timer.report("Madam output")

    memreport("at end of main", mpiworld)

    gt.stop_all()
    if mpiworld is not None:
        mpiworld.barrier()
    timer = Timer()
    timer.start()
    alltimers = gather_timers(comm=mpiworld)
    if comm.world_rank == 0:
        out = os.path.join(args.out, "timing")
        dump_timing(alltimers, out)
        timer.stop()
        timer.report("Gather and dump timing info")
    return
def main():
    log = Logger.get()
    gt = GlobalTimers.get()
    gt.start("toast_planck_reduce (total)")

    mpiworld, procs, rank, comm = get_comm()

    if comm.comm_world.rank == 0:
        print("Running with {} processes at {}".format(
            procs, str(datetime.datetime.now())))

    parser = argparse.ArgumentParser(description='Planck Ringset making',
                                     fromfile_prefix_chars='@')
    parser.add_argument('--rimo', required=True, help='RIMO file')
    parser.add_argument('--freq', required=True, type=np.int, help='Frequency')
    parser.add_argument('--dets',
                        required=False,
                        default=None,
                        help='Detector list (comma separated)')
    parser.add_argument('--effdir',
                        required=True,
                        help='Input Exchange Format File directory')
    parser.add_argument('--read_eff',
                        dest='read_eff',
                        default=False,
                        action='store_true',
                        help='Read and co-add the signal from effdir')
    parser.add_argument('--effdir_pntg',
                        required=False,
                        help='Input Exchange Format File directory for '
                        'pointing')
    parser.add_argument('--obtmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='OBT flag mask')
    parser.add_argument('--flagmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='Quality flag mask')
    parser.add_argument('--ringdb', required=True, help='Ring DB file')
    parser.add_argument('--odfirst',
                        required=False,
                        default=None,
                        help='First OD to use')
    parser.add_argument('--odlast',
                        required=False,
                        default=None,
                        help='Last OD to use')
    parser.add_argument('--ringfirst',
                        required=False,
                        default=None,
                        help='First ring to use')
    parser.add_argument('--ringlast',
                        required=False,
                        default=None,
                        help='Last ring to use')
    parser.add_argument('--obtfirst',
                        required=False,
                        default=None,
                        help='First OBT to use')
    parser.add_argument('--obtlast',
                        required=False,
                        default=None,
                        help='Last OBT to use')
    parser.add_argument('--out',
                        required=False,
                        default='.',
                        help='Output directory')
    dipogroup = parser.add_mutually_exclusive_group()
    dipogroup.add_argument('--dipole',
                           dest='dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate dipole')
    dipogroup.add_argument('--solsys_dipole',
                           dest='solsys_dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate solar system dipole')
    dipogroup.add_argument('--orbital_dipole',
                           dest='orbital_dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate orbital dipole')
    # dipole parameters
    dipo_parameters_group = parser.add_argument_group('dipole_parameters')
    dipo_parameters_group.add_argument(
        '--solsys_speed',
        dest='solsys_speed',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_speed"],
        help='Solar system speed wrt. CMB rest frame in km/s. '
        'Default is Planck 2015 best fit value')
    dipo_parameters_group.add_argument(
        '--solsys_glon',
        dest='solsys_glon',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_glon"],
        help='Solar system velocity direction longitude in degrees')
    dipo_parameters_group.add_argument(
        '--solsys_glat',
        dest='solsys_glat',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_glat"],
        help='Solar system velocity direction latitude in degrees')

    # libconviqt parameters
    parser.add_argument('--lmax',
                        required=False,
                        default=1024,
                        type=np.int,
                        help='Simulation lmax')
    parser.add_argument('--fwhm',
                        required=False,
                        default=0.0,
                        type=np.float,
                        help='Sky fwhm [arcmin] to deconvolve')
    parser.add_argument('--beammmax',
                        required=False,
                        default=None,
                        type=np.int,
                        help='Beam mmax')
    parser.add_argument('--order',
                        required=False,
                        default=11,
                        type=np.int,
                        help='Iteration order')
    parser.add_argument('--pxx',
                        required=False,
                        default=False,
                        action='store_true',
                        help='Beams are in Pxx frame, not Dxx')
    parser.add_argument('--skyfile',
                        required=False,
                        default=None,
                        help='Path to sky alm files. Tag DETECTOR will be '
                        'replaced with detector name.')
    parser.add_argument('--beamfile',
                        required=False,
                        default=None,
                        help='Path to beam alm files. Tag DETECTOR will be '
                        'replaced with detector name.')
    parser.add_argument('--nopol',
                        dest='nopol',
                        default=False,
                        action='store_true',
                        help='Sky and beam should be treated unpolarized')
    # noise simulation parameters
    parser.add_argument('--add_noise',
                        dest='add_noise',
                        default=False,
                        action='store_true',
                        help='Simulate noise')
    parser.add_argument('--noisefile',
                        required=False,
                        default='RIMO',
                        help='Path to noise PSD files for noise filter. '
                        'Tag DETECTOR will be replaced with detector name.')
    parser.add_argument('--noisefile_simu',
                        required=False,
                        default='RIMO',
                        help='Path to noise PSD files for noise simulation. '
                        'Tag DETECTOR will be replaced with detector name.')
    parser.add_argument('--mc',
                        required=False,
                        default=0,
                        type=np.int,
                        help='Noise realization')
    # ringset parameters
    parser.add_argument('--nside_ring',
                        required=False,
                        default=128,
                        type=np.int,
                        help='Ringset resolution')
    parser.add_argument('--ring_root',
                        required=False,
                        default='ringset',
                        help='Root filename for ringsets (setting to empty '
                        'disables ringset output).')

    args = parser.parse_args()

    if comm.world_rank == 0:
        print('All parameters:')
        print(args, flush=True)

    timer = Timer()
    timer.start()

    nrange = 1

    odranges = None
    if args.odfirst is not None and args.odlast is not None:
        odranges = []
        firsts = [int(i) for i in str(args.odfirst).split(',')]
        lasts = [int(i) for i in str(args.odlast).split(',')]
        for odfirst, odlast in zip(firsts, lasts):
            odranges.append((odfirst, odlast))
        nrange = len(odranges)

    ringranges = None
    if args.ringfirst is not None and args.ringlast is not None:
        ringranges = []
        firsts = [int(i) for i in str(args.ringfirst).split(',')]
        lasts = [int(i) for i in str(args.ringlast).split(',')]
        for ringfirst, ringlast in zip(firsts, lasts):
            ringranges.append((ringfirst, ringlast))
        nrange = len(ringranges)

    obtranges = None
    if args.obtfirst is not None and args.obtlast is not None:
        obtranges = []
        firsts = [float(i) for i in str(args.obtfirst).split(',')]
        lasts = [float(i) for i in str(args.obtlast).split(',')]
        for obtfirst, obtlast in zip(firsts, lasts):
            obtranges.append((obtfirst, obtlast))
        nrange = len(obtranges)

    if odranges is None:
        odranges = [None] * nrange

    if ringranges is None:
        ringranges = [None] * nrange

    if obtranges is None:
        obtranges = [None] * nrange

    detectors = None
    if args.dets is not None:
        detectors = re.split(',', args.dets)

    # Make output directory

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

    do_dipole = args.dipole or args.solsys_dipole or args.orbital_dipole
    do_convolve = args.skyfile is not None and args.beamfile is not None
    do_noise = args.add_noise
    if not do_noise and args.noisefile_simu != 'RIMO':
        raise RuntimeError('Did you mean to simulate noise? add_noise = {} '
                           'but noisefile_simu = {}'.format(
                               args.add_noise, args.noisefile_simu))

    if comm.world_rank == 0:
        print('read_eff = {}'.format(args.read_eff))
        print('do_dipole = {}'.format(do_dipole))
        print('solsys_speed = {}'.format(args.solsys_speed))
        print('solsys_glon = {}'.format(args.solsys_glon))
        print('solsys_glat = {}'.format(args.solsys_glat))
        print('do_convolve = {}'.format(do_convolve))
        print('do_noise = {}'.format(do_noise), flush=True)

    # create the TOD for the whole data span (loads ring database and caches
    # directory contents)

    if do_noise and args.noisefile_simu != 'RIMO':
        do_eff_cache = True
    else:
        do_eff_cache = False

    tods = []

    for obtrange, ringrange, odrange in zip(obtranges, ringranges, odranges):
        tods.append(
            tp.Exchange(comm=comm.comm_group,
                        detectors=detectors,
                        ringdb=args.ringdb,
                        effdir_in=args.effdir,
                        effdir_pntg=args.effdir_pntg,
                        obt_range=obtrange,
                        ring_range=ringrange,
                        od_range=odrange,
                        freq=args.freq,
                        RIMO=args.rimo,
                        obtmask=args.obtmask,
                        flagmask=args.flagmask,
                        do_eff_cache=do_eff_cache))

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Metadata queries")

    if args.noisefile != 'RIMO' or args.noisefile_simu != 'RIMO':
        # We split MPI_COMM_WORLD into single process groups, each of
        # which is assigned one or more observations (rings)
        comm = Comm(groupsize=1)

    # This is the distributed data, consisting of one or
    # more observations, each distributed over a communicator.
    data = Data(comm)

    for tod in tods:
        if args.noisefile != 'RIMO' or args.noisefile_simu != 'RIMO':
            # Use a toast helper method to optimally distribute rings between
            # processes.
            dist = distribute_discrete(tod.ringsizes, procs)
            my_first_ring, my_n_ring = dist[comm.world_rank]

            for my_ring in range(my_first_ring, my_first_ring + my_n_ring):
                ringtod = tp.Exchange.from_tod(
                    tod,
                    my_ring,
                    comm.comm_group,
                    noisefile=args.noisefile,
                    noisefile_simu=args.noisefile_simu)
                ob = {}
                ob['name'] = 'ring{:05}'.format(ringtod.globalfirst_ring)
                ob['id'] = ringtod.globalfirst_ring
                ob['tod'] = ringtod
                ob['intervals'] = ringtod.valid_intervals
                ob['baselines'] = None
                ob['noise'] = ringtod.noise
                ob['noise_simu'] = ringtod.noise_simu
                data.obs.append(ob)
        else:
            # This is the distributed data, consisting of one or
            # more observations, each distributed over a communicator.
            ob = {}
            ob['name'] = 'mission'
            ob['id'] = 0
            ob['tod'] = tod
            ob['intervals'] = tod.valid_intervals
            ob['baselines'] = None
            ob['noise'] = tod.noise

            data.obs.append(ob)

    rimo = tods[0].rimo
    fsample = rimo[detectors[0]].fsample

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Create observations")

    # make a planck Healpix pointing matrix
    mode = 'IQU'

    pointing = tp.OpPointingPlanck(nside=args.nside_ring,
                                   mode=mode,
                                   RIMO=rimo,
                                   margin=0,
                                   apply_flags=False,
                                   keep_vel=do_dipole,
                                   keep_pos=False,
                                   keep_phase=False,
                                   keep_quats=(do_dipole or do_convolve))

    pointing.exec(data)

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Pointing Matrix")

    # Always read the signal because we always need the flags

    reader = tp.OpInputPlanck(signal_name='tod')
    if comm.world_rank == 0:
        print('Reading input signal from {}'.format(args.effdir), flush=True)
    reader.exec(data)
    comm.comm_world.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Read")
    tod_name = 'tod'

    # Clear the signal if we don't need it

    if not args.read_eff:
        eraser = tp.OpCacheMath(in1='tod', in2=0, multiply=True, out='tod')
        if comm.comm_world.rank == 0:
            print('Erasing TOD', flush=True)
        eraser.exec(data)
        comm.comm_world.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Erase")

    if do_convolve:
        # simulate the TOD by convolving the sky with the beams
        detectordata = []
        for det in tod.detectors:
            skyfile = args.skyfile.replace('DETECTOR', det)
            beamfile = args.beamfile.replace('DETECTOR', det)
            epsilon = rimo[det].epsilon
            # Getting the right polarization angle can be a sensitive matter.
            # Dxx beams are always defined without psi_uv or psi_pol rotation
            # but some Pxx beams may require psi_pol to be removed and psi_uv
            # left in.
            if args.pxx:
                # Beam is in the polarization basis.
                # No extra rotations are needed
                psipol = np.radians(rimo[det].psi_pol)
            else:
                # Beam is in the detector basis. Convolver needs to remove
                # the last rotation into the polarization sensitive frame.
                psipol = np.radians(rimo[det].psi_uv + rimo[det].psi_pol)
            detectordata.append((det, skyfile, beamfile, epsilon, psipol))

        # always construct conviqt with dxx=True and modify the psipol
        # to produce the desired rotation.

        conviqt = tt.OpSimConviqt(args.lmax,
                                  args.beammmax,
                                  detectordata,
                                  pol=(not args.nopol),
                                  fwhm=args.fwhm,
                                  order=args.order,
                                  calibrate=True,
                                  dxx=True,
                                  out='tod',
                                  quat_name='quats',
                                  apply_flags=False)
        conviqt.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Convolution")
        tod_name = 'tod'

    if do_dipole:
        # Simulate the dipole
        if args.dipole:
            dipomode = 'total'
        elif args.solsys_dipole:
            dipomode = 'solsys'
        else:
            dipomode = 'orbital'
        dipo = tp.OpDipolePlanck(args.freq,
                                 solsys_speed=args.solsys_speed,
                                 solsys_glon=args.solsys_glon,
                                 solsys_glat=args.solsys_glat,
                                 mode=dipomode,
                                 output='tod',
                                 add_to_existing=args.read_eff)
        dipo.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Dipole")
        tod_name = 'tod'

    if do_noise:
        nse = tt.OpSimNoise(out='tod',
                            realization=args.mc,
                            component=0,
                            noise='noise_simu',
                            rate=fsample)
        nse.exec(data)
        if mpiworld is not None:
            mpiworld.barrier()
        if comm.world_rank == 0:
            timer.report_clear("Noise simulation")
        tod_name = 'tod'

    # for now, we pass in the noise weights from the RIMO.
    detweights = {}
    for d in tod.detectors:
        net = tod.rimo[d].net
        fsample = tod.rimo[d].fsample
        detweights[d] = 1.0 / (fsample * net * net)

    # Make rings

    ringmaker = tp.OpRingMaker(args.nside_ring,
                               args.nside_ring,
                               signal=tod_name,
                               fileroot=args.ring_root,
                               out=args.out,
                               detmask=args.flagmask,
                               commonmask=args.obtmask)
    ringmaker.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Ringmaking")

    gt.stop_all()
    if mpiworld is not None:
        mpiworld.barrier()
    timer = Timer()
    timer.start()
    alltimers = gather_timers(comm=mpiworld)
    if comm.world_rank == 0:
        out = os.path.join(args.out, "timing")
        dump_timing(alltimers, out)
        timer.stop()
        timer.report("Gather and dump timing info")
    return
def main():
    log = Logger.get()
    gt = GlobalTimers.get()
    gt.start("toast_planck_reduce (total)")

    mpiworld, procs, rank, comm = get_comm()

    if comm.world_rank == 0:
        print("Running with {} processes at {}".format(
            procs, str(datetime.datetime.now())))

    parser = argparse.ArgumentParser(description='Simple dipole pipeline',
                                     fromfile_prefix_chars='@')
    parser.add_argument('--rimo', required=True, help='RIMO file')
    parser.add_argument('--freq', required=True, type=np.int, help='Frequency')
    parser.add_argument('--dets',
                        required=False,
                        default=None,
                        help='Detector list (comma separated)')
    parser.add_argument('--effdir',
                        required=True,
                        help='Input Exchange Format File directory')
    parser.add_argument('--effdir_pntg',
                        required=False,
                        help='Input Exchange Format File directory for '
                        'pointing')
    parser.add_argument('--obtmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='OBT flag mask')
    parser.add_argument('--flagmask',
                        required=False,
                        default=1,
                        type=np.int,
                        help='Quality flag mask')
    parser.add_argument('--pntflagmask',
                        required=False,
                        default=0,
                        type=np.int,
                        help='Which OBT flag bits to raise for HCM maneuvers')
    parser.add_argument('--ringdb', required=True, help='Ring DB file')
    parser.add_argument('--odfirst',
                        required=False,
                        default=None,
                        help='First OD to use')
    parser.add_argument('--odlast',
                        required=False,
                        default=None,
                        help='Last OD to use')
    parser.add_argument('--ringfirst',
                        required=False,
                        default=None,
                        help='First ring to use')
    parser.add_argument('--ringlast',
                        required=False,
                        default=None,
                        help='Last ring to use')
    parser.add_argument('--obtfirst',
                        required=False,
                        default=None,
                        help='First OBT to use')
    parser.add_argument('--obtlast',
                        required=False,
                        default=None,
                        help='Last OBT to use')
    parser.add_argument('--out',
                        required=False,
                        default='.',
                        help='Output directory')
    # Dipole parameters
    dipogroup = parser.add_mutually_exclusive_group()
    dipogroup.add_argument('--dipole',
                           dest='dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate dipole')
    dipogroup.add_argument('--solsys-dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate solar system dipole')
    dipogroup.add_argument('--orbital-dipole',
                           required=False,
                           default=False,
                           action='store_true',
                           help='Simulate orbital dipole')
    dipo_parameters_group = parser.add_argument_group('dipole_parameters')
    dipo_parameters_group.add_argument(
        '--solsys_speed',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_speed"],
        help='Solar system speed wrt. CMB rest frame in km/s. Default is '
        'Planck 2015 best fit value')
    dipo_parameters_group.add_argument(
        '--solsys-glon',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_glon"],
        help='Solar system velocity direction longitude in degrees')
    dipo_parameters_group.add_argument(
        '--solsys-glat',
        required=False,
        type=np.float,
        default=DEFAULT_PARAMETERS["solsys_glat"],
        help='Solar system velocity direction latitude in degrees')

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

    if comm.world_rank == 0:
        print('All parameters:')
        print(args, flush=True)

    timer = Timer()
    timer.start()

    do_dipole = args.dipole or args.solsys_dipole or args.orbital_dipole
    if not do_dipole:
        raise RuntimeError(
            "You have to set dipole, solsys-dipole or orbital-dipole")

    nrange = 1

    odranges = None
    if args.odfirst is not None and args.odlast is not None:
        odranges = []
        firsts = [int(i) for i in str(args.odfirst).split(',')]
        lasts = [int(i) for i in str(args.odlast).split(',')]
        for odfirst, odlast in zip(firsts, lasts):
            odranges.append((odfirst, odlast))
        nrange = len(odranges)

    ringranges = None
    if args.ringfirst is not None and args.ringlast is not None:
        ringranges = []
        firsts = [int(i) for i in str(args.ringfirst).split(',')]
        lasts = [int(i) for i in str(args.ringlast).split(',')]
        for ringfirst, ringlast in zip(firsts, lasts):
            ringranges.append((ringfirst, ringlast))
        nrange = len(ringranges)

    obtranges = None
    if args.obtfirst is not None and args.obtlast is not None:
        obtranges = []
        firsts = [float(i) for i in str(args.obtfirst).split(',')]
        lasts = [float(i) for i in str(args.obtlast).split(',')]
        for obtfirst, obtlast in zip(firsts, lasts):
            obtranges.append((obtfirst, obtlast))
        nrange = len(obtranges)

    if odranges is None:
        odranges = [None] * nrange

    if ringranges is None:
        ringranges = [None] * nrange

    if obtranges is None:
        obtranges = [None] * nrange

    detectors = None
    if args.dets is not None:
        detectors = re.split(',', args.dets)

    # create the TOD for this observation

    tods = []

    for obtrange, ringrange, odrange in zip(obtranges, ringranges, odranges):
        # create the TOD for this observation
        tods.append(
            tp.Exchange(comm=comm.comm_group,
                        detectors=detectors,
                        ringdb=args.ringdb,
                        effdir_in=args.effdir,
                        effdir_pntg=args.effdir_pntg,
                        obt_range=obtrange,
                        ring_range=ringrange,
                        od_range=odrange,
                        freq=args.freq,
                        RIMO=args.rimo,
                        obtmask=args.obtmask,
                        flagmask=args.flagmask,
                        pntflagmask=args.pntflagmask,
                        do_eff_cache=False))

    # Make output directory

    if not os.path.isdir(args.out) and comm.world_rank == 0:
        os.makedirs(args.out)

    # This is the distributed data, consisting of one or
    # more observations, each distributed over a communicator.
    data = toast.Data(comm)

    for iobs, tod in enumerate(tods):
        ob = {}
        ob['name'] = 'observation{:04}'.format(iobs)
        ob['id'] = 0
        ob['tod'] = tod
        ob['intervals'] = tod.valid_intervals
        ob['baselines'] = None
        ob['noise'] = tod.noise
        ob['noise_simu'] = tod.noise

        data.obs.append(ob)

    rimo = tods[0].rimo

    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Metadata queries")

    # Always read the signal and flags, even if the signal is later
    # overwritten.  There is no overhead for the signal because it is
    # interlaced with the flags.

    tod_name = 'signal'
    timestamps_name = 'timestamps'
    flags_name = 'flags'
    common_flags_name = 'common_flags'
    reader = tp.OpInputPlanck(signal_name=tod_name,
                              flags_name=flags_name,
                              timestamps_name=timestamps_name,
                              commonflags_name=common_flags_name)
    if comm.world_rank == 0:
        print('Reading input signal from {}'.format(args.effdir), flush=True)
    reader.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Read")
    """
    # Clear the signal

    eraser = tp.OpCacheMath(in1=tod_name, in2=0, multiply=True,
                            out=tod_name)
    if comm.world_rank == 0:
        print('Erasing TOD', flush=True)
    eraser.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Erase")
    """

    # make a planck Healpix pointing matrix

    mode = 'IQU'
    nside = 512

    pointing = tp.OpPointingPlanck(nside=nside,
                                   mode=mode,
                                   RIMO=rimo,
                                   margin=0,
                                   apply_flags=True,
                                   keep_vel=do_dipole,
                                   keep_pos=False,
                                   keep_phase=False,
                                   keep_quats=do_dipole)
    pointing.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Pointing Matrix")

    flags_name = 'flags'
    common_flags_name = 'common_flags'

    # Simulate the dipole
    if args.dipole:
        dipomode = 'total'
    elif args.solsys_dipole:
        dipomode = 'solsys'
    else:
        dipomode = 'orbital'
    dipo = tp.OpDipolePlanck(args.freq,
                             solsys_speed=args.solsys_speed,
                             solsys_glon=args.solsys_glon,
                             solsys_glat=args.solsys_glat,
                             mode=dipomode,
                             output='dipole',
                             keep_quats=False,
                             npipe_mode=True)
    dipo.exec(data)
    dipo = tp.OpDipolePlanck(args.freq,
                             solsys_speed=args.solsys_speed,
                             solsys_glon=args.solsys_glon,
                             solsys_glat=args.solsys_glat,
                             mode=dipomode,
                             output='dipole4pi',
                             keep_quats=False,
                             npipe_mode=False,
                             lfi_mode=False)
    dipo.exec(data)
    if mpiworld is not None:
        mpiworld.barrier()
    if comm.world_rank == 0:
        timer.report_clear("Dipole")

    # Write out the values in ASCII
    for iobs, obs in enumerate(data.obs):
        tod = obs["tod"]
        times = tod.local_times()
        velocity = tod.local_velocity()
        for det in tod.local_dets:
            quat = tod.local_pointing(det)
            angles = np.vstack(qarray.to_angles(quat)).T
            signal = tod.local_signal(det)
            dipole = tod.local_signal(det, "dipole")
            dipole4pi = tod.local_signal(det, "dipole4pi")
            fname_out = os.path.join(
                args.out,
                "{}_dipole.{}.{}.{}.txt".format(dipomode, comm.world_rank,
                                                iobs, det))
            with open(fname_out, "w") as fout:
                for t, ang, vel, sig, dipo, dipo4pi in zip(
                        times, angles, velocity, signal, dipole, dipole4pi):
                    fout.write(
                        (10 * " {}" + "\n").format(t, *ang, *vel, sig, dipo,
                                                   dipo4pi))
            print("{} : Wrote {}".format(comm.world_rank, fname_out))

    if comm.world_rank == 0:
        timer.report_clear("Write dipole")

    gt.stop_all()
    if mpiworld is not None:
        mpiworld.barrier()
    timer = Timer()
    timer.start()
    alltimers = gather_timers(comm=mpiworld)
    if comm.world_rank == 0:
        out = os.path.join(args.out, "timing")
        dump_timing(alltimers, out)
        timer.stop()
        timer.report("Gather and dump timing info")
    return