Comparison with Cotter

The visibility values produced by Birli are thoroughly tested against the equivalent values from Cotter on each commit. Any change which causes these tests to fail is rejected.

Because of a number of small differences between Cotter and Birli, these tests vary slightly in error tolerance, depending which corrections are applied in the test data.

  • Corrections in Birli are done using double precision float values, while Cotter only uses single precision.
    • Although this results in a slight speed decrease for these particular operations, Birli more than makes up for this in other areas and is significantly faster overall.
  • Birli uses a more accurate and higher resolution PFB gain curve which is provided at the highest frequency resolution that the MWAX correlator can use.
    • You can use the PFB gains from Cotter with the --pfb-gains cotter command line flag
  • Birli uses a more accurate (double precision) value for the array position.
    • You can use the --emulate-cotter flag to use the same array position value as Cotter.
  • Birli pays close attention to the timestamp values provided in the gpubox files and validates that these values match what is specified in the metafits.
    • Cotter only reads the first timestamp value of the first gpubox file, which can result in it processing corrupted visibility files without warning the user.
    • When Birli encounters a conflict between the raw files and the metafits, the user may be required to update the values in the metafits or raw files to resolve this conflict.
    • On the flip side, there are some cases where the legacy correlator could produce a file that can be preprocessed with Cotter but not Birli. 
    • Some examples of legacy correlator issues are documented here
  • Some Measurement Set and UVFits files created by Birli, Hyperdrive or Marlu can use the DUT1 timescale
    • The --ignore-dut1 flag can be used to emulate Cotter's behaviour and write timesteps the the UTC timescale.
    • it's best to read the timescale (DUT1 or UTC) specified in the file metadata. 

Testing your own observation

One of the most efficient ways to validate that Cotter and Birli produce the same visibilities is using taql, which can query a measurement significantly faster than Python.

This bash script preprocesses the same observation with Cotter and Birli, giving the best direct comparison assumes that the raw files for the observation are placed in ${outdir}/${obsid}/raw

export outdir="" # the root directory containing your observation data
export obsid="" # the specific observation to process
# preprocessor settings
export timeres_s=2
export freqres_khz=80
export edgewidth_khz=280
export metafits="${outdir}/${obsid}/raw/${obsid}.metafits"
export birli_ms="${outdir}/${obsid}/prep/birli_${obsid}_${timeres_s}s_${freqres_khz}kHz.ms"
birli \
        --avg-freq-res ${freqres_khz} --avg-time-res ${timeres_s} \
        --flag-edge-width ${edgewidth_khz} \
        --no-rfi \
        --emulate-cotter \
        --ignore-dut1 \
        --pfb-gains cotter \
        -M "${birli_ms}" \
        -m "${metafits}" \
        ${outdir}/${obsid}/raw/${obsid}_2*.fits
export cotter_ms="${outdir}/${obsid}/prep/cotter_${obsid}_${timeres_s}s_${freqres_khz}kHz.ms"
cotter \
        -freqres ${freqres_khz} -timeres ${timeres_s} \
        -edgewidth ${edgewidth_khz} \
        -o "${cotter_ms}" \
        -m "${metafits}" \
        -allowmissing \
        -noantennapruning \
        -noflagautos \
        -nostats \
        -norfi \
        ${outdir}/${obsid}/raw/${obsid}_2*.fits

Birli will print some helpful information about the start times provided in the raw correlator files, along with the processing time.

For a detailed explanation of the MWALib concepts of the difference between "Provided", "Common" and "Good" times, check out this wki.

    observation name:     Sun
    Array position:       { longitude: 116.6708°, latitude: -26.7033°, height: 377.827m }
    Phase centre:         (164.9390°, 6.2214°) => (10h59m45.3566s, 6d13m17.1999s)
    Pointing centre:      (161.4930°, 5.2610°) => (10h45m58.3127s, 5d15m39.4799s)
    Scheduled start:      2014-09-07 02:00:00.000 UTC, unix=1410055200.000, gps=1094090416.000, mjd=4916772000.000, lmst=132.7462°, lmst2k=132.5860°, lat2k=-26.6457°
    Scheduled end:        2014-09-07 02:04:00.000 UTC, unix=1410055440.000, gps=1094090656.000, mjd=4916772240.000, lmst=133.7489°, lmst2k=133.5882°, lat2k=-26.6447°
    Scheduled duration:   240.000s = 480 * 0.500s
    Quack duration:       2.500s =   5 * 0.500s
    Output duration:      237.000s = 474 * 0.500s
    Scheduled Bandwidth:  30.720MHz =  24 *  32 * 40.000kHz
    Output Bandwidth:     30.720MHz =       384 * 80.000kHz (2x)
    Timestep details (all=480, provided=474, common=472, good=471, select=474, flag=13):
             2014-09-07 UTC +  unix [s]        gps [s]         p  c  g  s  f
       ts0:      02:00:00.000  1410055200.000  1094090416.000              f
       ts1:      02:00:00.500  1410055200.500  1094090416.500              f
       ts2:      02:00:01.000  1410055201.000  1094090417.000              f
       ts3:      02:00:01.500  1410055201.500  1094090417.500              f
       ts4:      02:00:02.000  1410055202.000  1094090418.000  p  c     s  f
       ts5:      02:00:02.500  1410055202.500  1094090418.500  p  c  g  s  f
       ts6:      02:00:03.000  1410055203.000  1094090419.000  p  c  g  s  f
       ts7:      02:00:03.500  1410055203.500  1094090419.500  p  c  g  s  f
       ts8:      02:00:04.000  1410055204.000  1094090420.000  p  c  g  s  f
       ts9:      02:00:04.500  1410055204.500  1094090420.500  p  c  g  s
      ts10:      02:00:05.000  1410055205.000  1094090421.000  p  c  g  s
...
     ts474:      02:03:57.000  1410055437.000  1094090653.000  p  c  g  s
     ts475:      02:03:57.500  1410055437.500  1094090653.500  p  c  g  s
     ts476:      02:03:58.000  1410055438.000  1094090654.000  p        s  f
     ts477:      02:03:58.500  1410055438.500  1094090654.500  p        s  f
     ts478:      02:03:59.000  1410055439.000  1094090655.000              f
     ts479:      02:03:59.500  1410055439.500  1094090655.500              f

    Coarse channel details (metafits=24, provided=24, common=24, good=24, select=24, flag=0):
            gpu  corr  rec  cen [MHz]  p  c  g  s  f
      cc0:    1     0   57    72.9600  p  c  g  s
      cc1:    2     1   58    74.2400  p  c  g  s
      cc2:    3     2   59    75.5200  p  c  g  s
      cc3:    4     3   60    76.8000  p  c  g  s
      cc4:    5     4   61    78.0800  p  c  g  s
      cc5:    6     5   62    79.3600  p  c  g  s
      cc6:    7     6   63    80.6400  p  c  g  s
      cc7:    8     7   64    81.9200  p  c  g  s
      cc8:    9     8   65    83.2000  p  c  g  s
      cc9:   10     9   66    84.4800  p  c  g  s
     cc10:   11    10   67    85.7600  p  c  g  s
     cc11:   12    11   68    87.0400  p  c  g  s
     cc12:   13    12   69    88.3200  p  c  g  s
     cc13:   14    13   70    89.6000  p  c  g  s
     cc14:   15    14   71    90.8800  p  c  g  s
     cc15:   16    15   72    92.1600  p  c  g  s
     cc16:   17    16   73    93.4400  p  c  g  s
     cc17:   18    17   74    94.7200  p  c  g  s
     cc18:   19    18   75    96.0000  p  c  g  s
     cc19:   20    19   76    97.2800  p  c  g  s
     cc20:   21    20   77    98.5600  p  c  g  s
     cc21:   22    21   78    99.8400  p  c  g  s
     cc22:   23    22   79   101.1200  p  c  g  s
     cc23:   24    23   80   102.4000  p  c  g  s

    Antenna details (all=128, flag=8)
    Baseline Details (all=8256, auto=128, select=8256, flag=996):
    Estimated memory usage per timestep =              768ch *   8256bl * (32<Jones<f32>> + 4<f32> + 1<bool>) =    0.22 GiB
    Estimated memory selected           =   474ts *    768ch *   8256bl * (32<Jones<f32>> + 4<f32> + 1<bool>) =  103.56 GiB
    Estimated output size               =   474ts *    384ch *   8256bl * 4pol * (8<c32> + 4<f32> + 1<bool>) =   72.78 GiB
    Preprocessing Context:
    Will correct cable lengths.
    Will correct digital gains.
    Will correct coarse pfb passband gains.
    Will not flag with aoflagger
    Will correct geometry.

...
[2023-02-03T06:32:01Z INFO  birli] init duration: 81.309324237s
[2023-02-03T06:32:01Z INFO  birli] correct_passband duration: 20.408972661s
[2023-02-03T06:32:01Z INFO  birli] read duration: 14.37518451s
[2023-02-03T06:32:01Z INFO  birli] correct_geom duration: 3.533045269s
[2023-02-03T06:32:01Z INFO  birli] write duration: 526.83874591s
[2023-02-03T06:32:01Z INFO  birli] correct_cable duration: 13.050491358s
[2023-02-03T06:32:01Z INFO  birli] correct_digital duration: 9.208166293s
[2023-02-03T06:32:01Z INFO  birli] total duration: 668.723930238s

Cotter's output for the same task shows Birli taking a significant lead in processing time.

Running Cotter MWA preprocessing pipeline, version 4.6 (2022-01-20).
Flagging is performed by AOFlagger 3.2.1 (2022-05-19).
Input filenames succesfully parsed: using 96 files covering 4 timeranges from 24 GPU boxes.
Detected 503.7 GB of system memory.
Observation covers 72.32-103 MHz.
Output resolution: 0.5 s / 80 kHz (time avg: 1x, freq avg: 2x).
The first 8 samples (4 s), last 0 samples (0 s) and 7 edge channels will be flagged.
Using a-priori subband passband with 32 channels.
Using per-input subband gains. Average gains: 3.21436,2.90552,2.58398,2.29688,2.0874,1.97717,1.94177,1.93787,1.92065,1.88062,1.80994,1.74097,1.69104,1.67554,1.6897,1.71851,1.74182,1.7478,1.72314,1.68665,1.64905,1.62866,1.6272,1.64795
Subband order: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23
Observation's bandwidth is contiguous.
All 480 scans fit in memory; no partitioning necessary.
WARNING: start time according to raw files is 2014-09-07 02:00:02,
but meta files say 2014-09-07 02:00:00 !
Will use start time from raw file, which should be most accurate.
Warning: header specifies 480 scans, but there are only 472 in the data.
Last 8 scan(s) will be flagged.
Wall-clock time in reading: 00:04:42.520529 processing: 00:00:04.020476 writing: 00:25:38.408673

A python script can be used to display information about which timesteps are flagged by each preprocessor, which can vary slightly.

Some Measurement Set files created by Birli, Hyperdrive or Marlu can use the DUT1 timescale, so it's best to read the timescale specified in the column metadata. In this instance, the --ignore-dut1 flag was used to emulate Cotter's behaviour

for path in ['birli_1094090416_0.5s_80kHz.ms', 'cotter_1094090416_0.5s_80kHz.ms']:
    print(path)
    tb_main = table(os.path.join('/data/dev/1094090416/prep', path))
    timescale = tb_main.getcolkeyword('TIME', 'MEASINFO')['Ref'].lower() or 'utc'

    all_times = set()
    unflagged_times = set()
    for row_idx in range(tb_main.nrows()):
        time = Time(tb_main.getcell('TIME', row_idx) / 86400.0, format='mjd', scale=timescale)
        all_times.add(time.gps)
        if np.any(np.logical_not(tb_main.getcell('FLAG', row_idx))):
            unflagged_times.add(time.gps)
    print(f"number of times:  {len(all_times):12d}")
    print(f"number unflagged: {len(unflagged_times):12d}")
    print(f"first time:       {min(all_times):12.4f}")
    print(f"first unflagged:  {min(unflagged_times):12.4f}")
    print(f"last unflagged:   {max(unflagged_times):12.4f}")
    print(f"last time:        {max(all_times):12.4f}")

The results of running this command on both files show that Birli and Cotter flags start and end at different times (these are centroid times, where the Birli logs above are start times).

birli_1094090416_0.5s_80kHz.ms
Successful readonly open of default-locked table /data/dev/1094090416/prep/birli_1094090416_0.5s_80kHz.ms: 23 columns, 3913344 rows
number of times:           474
number unflagged:          467
first time:       1094090418.2500
first unflagged:  1094090420.7500
last unflagged:   1094090653.7500
last time:        1094090654.7500
cotter_1094090416_0.5s_80kHz.ms
Successful readonly open of default-locked table /data/dev/1094090416/prep/cotter_1094090416_0.5s_80kHz.ms: 23 columns, 3962880 rows
number of times:           480
number unflagged:          464
first time:       1094090418.2500
first unflagged:  1094090422.2500
last unflagged:   1094090653.7500
last time:        1094090657.7500

Birli flags all times that are within quack time (in this case, 2.5s) after the first provided timestamp, while Cotter flags more timesteps by default.


Taql can also be used to enumerate timestamps in both measurement set files

for proc in birli cotter; do export table="${proc}_${obsid}_${timeres_s}s_${freqres_khz}kHz.ms"; taql -o "${proc}_times.txt" -p "select unique TIME from $table"; done;


From these timestamps, we can construct a taql file to print a table of visibility magnitude values from a group of timesteps in the middle of the observation, looking at the band of frequencies bookended by the edge channels of the first coarse channel, showing only visibilities from the first (XX) polarization.

comparison.taql
SELECT
    TIME,
    mscal.ant1name() as ant1name,
    mscal.ant2name() as ant2name,
    STR(FLATTEN(abs(DATA[3:13, 0])),"%9.3f") as ch057XX
FROM $table
WHERE
    TIME>=MJD("07-Sep-2014/02:00:10.000")
    AND TIME<MJD("07-Sep-2014/02:00:50.000")
    AND mscal.ant1name() in ["Tile011","Tile021","Tile031"]
    AND mscal.ant2name() in ["Tile012","Tile022","Tile032"]

this taql file can be run on both measurement sets, and the results can be compared with your preferred diff viewer

for proc in birli cotter; do export table="${proc}_${obsid}_${timeres_s}s_${freqres_khz}kHz.ms"; taql -f comparison.taql -o ${proc}_ch057XX.txt -p; done;
diff -u birli_ch057XX.txt cotter_ch057XX.txt | diff-highlight

Although the visibilities are not identical because of the reasons mentioned above, there is negligible difference between the two.