Repository: RadioAstronomySoftwareGroup/pyuvdata

Issue:
Inflate by redundancy mapping
https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/a858a6c6663153345bbfecaa203f369ccda97647/pyuvdata/uvdata.py#L3825-L3826

It is not generally true that `Nblts == Nbls * Ntimes`. Currently `UVData.inflate_by_redundancy` makes this assumption when building the map from compressed to inflated data arrays.

To fix this we need to know which redundant groups have data available at each time. This may require for loops and other ugliness.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=h1) Report
> Merging [#614](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=desc) into [master](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/commit/78e6b49bf37fecdd7edc8bd4286d79e9cfbbd71d?src=pr&el=desc) will **decrease** coverage by `15.48%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/graphs/tree.svg?width=650&token=4QykcCeB1H&height=150&src=pr)](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           master     #614       +/-   ##
===========================================
- Coverage   98.86%   83.37%   -15.49%     
===========================================
  Files          20       20               
  Lines        8015     8019        +4     
===========================================
- Hits         7924     6686     -1238     
- Misses         91     1333     +1242
```


| [Impacted Files](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pyuvdata/uvdata.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXZkYXRhLnB5) | `95.81% <100%> (-4.19%)` | :arrow_down: |
| [pyuvdata/utils.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXRpbHMucHk=) | `99.15% <100%> (-0.85%)` | :arrow_down: |
| [pyuvdata/uvh5.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXZoNS5weQ==) | `0% <0%> (-100%)` | :arrow_down: |
| [pyuvdata/ms.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvbXMucHk=) | `0% <0%> (-89.68%)` | :arrow_down: |
| [pyuvdata/uvflag.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXZmbGFnLnB5) | `68.35% <0%> (-31.65%)` | :arrow_down: |
| [pyuvdata/uvbeam.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXZiZWFtLnB5) | `71.08% <0%> (-28.21%)` | :arrow_down: |
| [pyuvdata/beamfits.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvYmVhbWZpdHMucHk=) | `91.53% <0%> (-8.47%)` | :arrow_down: |
| [pyuvdata/version.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdmVyc2lvbi5weQ==) | `96.42% <0%> (-3.58%)` | :arrow_down: |
| [pyuvdata/cst\_beam.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree#diff-cHl1dmRhdGEvY3N0X2JlYW0ucHk=) | `98.17% <0%> (-1.83%)` | :arrow_down: |
| ... and [3 more](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=footer). Last update [78e6b49...12e0f77](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/614?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

bhazelton: @aelanman can you review this PR?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47b73d9f..0a434b8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,11 +4,13 @@ All notable changes to this project will be documented in this file.
 ## [Unreleased]
 
 ### Added
+- `conjugate_bls` option to `UVData.get_antenna_redundancies`
 - `UVData.conjugate_bls` method to conjugate baselines to get the desired baseline directions.
 - `UVData.reorder_blts` method to reorder the data along the blt axis (and optionally also conjugate baselines), and a new `blt_order` optional parameter on UVData objects to track the ordering (including through read/writes).
 - `lst_array` is now saved to UVFITS files (even though it's not a standard parameter) so that it doesn't have to be recalculated
 
 ### Fixed
+- Fixed a bug in `UVData.inflate_by_redundancy` when Nblts is not equal to Nbls * Ntimes.
 - Fixed a bug in UVData when reading in an FHD file with a single time integration.
 - Fixed a bug in how the longitudinal branch cut was handled in beam interpolation
 - Changed the way interpolation splines are saved in UVBeam to fix errors related to polarization selections.
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index ece69ac6..e1667f2c 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -952,11 +952,32 @@ UVData: Working with Redundant Baselines
 
 a) Finding Redundant Baselines
 ******************************
-pyuvdata.utils contains functions for finding redundant groups of baselines in an array, either by antenna positions or uvw coordinates. Baselines are considered redundant if they are within a specified tolerance distance (default is 1 meter).
-
-The ``get_baseline_redundancies`` function accepts an array of baseline indices and an array of baseline vectors (ie, uvw coordinates) as input, and finds redundancies among the vectors as given. If the ``with_conjugates`` option is selected, it will flip baselines such that ``u > 0``, or ``v > 0 if u = 0``, or ``w > 0 if u = v = 0``. In this case, a list of ``conjugates`` is returned as well, which contains indices for the baselines that were flipped for the redundant groups. In either mode of operation, this will only return baseline indices that are in the list passed in.
-
-The ``get_antenna_redundancies`` function accepts an array of antenna indices and an array of antenna positions as input, defines baseline vectors and indices under the positive-u condition described above, and runs ``get_baseline_redundancies`` to find redundant baselines. This is similar to running get_baseline_redundancies with the ``with_conjugates`` option, except that the baseline indices are returned such that none need to be flipped to be redundant. This is more like what's defined in the ``hera_cal`` package.
+:mod:`utils` contains functions for finding redundant groups of baselines in
+an array, either by antenna positions or uvw coordinates. Baselines are
+considered redundant if they are within a specified tolerance distance (default is 1 meter).
+
+The :func:`utils.get_baseline_redundancies` function accepts an array of baseline indices
+and an array of baseline vectors (ie, uvw coordinates) as input, and finds
+redundancies among the vectors as given. If the ``with_conjugates`` option is
+selected, it will flip baselines such that ``u > 0``, or ``v > 0 if u = 0``, or
+``w > 0 if u = v = 0``. In this case, a list of ``conjugates`` is returned as well,
+which contains indices for the baselines that were flipped for the redundant groups.
+In either mode of operation, this will only return baseline indices that are in the list passed in.
+
+The :func:`utils.get_antenna_redundancies` function accepts an array of
+antenna indices and an array of antenna positions as input, defines baseline vectors
+and indices under the positive-u condition described above, and runs
+:func:`utils.get_baseline_redundancies` to find redundant baselines.
+This is similar to running get_baseline_redundancies with the `with_conjugates`
+option, except that the baseline indices are returned such that none need to be
+flipped to be redundant. This is more like what's defined in the `hera_cal` package.
+
+There are also corresponding methods on :class:`~pyuvdata.UVData` with a few
+conveniences. :meth:`~pyuvdata.UVData.get_baseline_redundancies` passes the baselines
+that have data. :meth:`~pyuvdata.UVData.get_antenna_redundancies` has a convenience
+option `conjugate_bls`, which can be set to True to conjugate the UVData object
+to follow the positive-u condition so that the baselines on the object will match
+the baseline numbers returned by the method.
 
 ::
 
@@ -995,7 +1016,13 @@ The ``get_antenna_redundancies`` function accepts an array of antenna indices an
 
 b) Compressing/inflating on Redundant Baselines
 ***********************************************
-Since redundant baselines should have similar visibilities, some level of data compression can be achieved by only keeping one out of a set of redundant baselines. The function ``compress_by_redundancies`` will find groups of baselines that are redundant to a given tolerance, choose one baseline from each group, and use the ``select`` function to choose those baselines only. This action is (almost) inverted by the ``inflate_by_redundancies`` function, which finds all possible baselines from the antenna positions and fills in the full data array based on redundancy.
+Since redundant baselines should have similar visibilities, some level of data
+compression can be achieved by only keeping one out of a set of redundant baselines.
+The :meth:`~pyuvdata.UVData.compress_by_redundancy` method will find groups of baselines that are
+redundant to a given tolerance, choose one baseline from each group, and use the
+:meth:`~pyuvdata.UVData.select` method to choose those baselines only. This action is (almost)
+inverted by the :meth:`~pyuvdata.UVData.inflate_by_redundancy` method, which finds all possible
+baselines from the antenna positions and fills in the full data array based on redundancy.
 
 ::
 
@@ -1007,16 +1034,17 @@ Since redundant baselines should have similar visibilities, some level of data c
     >>> tol = 0.02   # In meters
 
     # Compression can be run in-place or return a separate UVData object.
-    >>> uv2 = uv0.compress_by_redundancy(tol=tol, inplace=False)
     >>> uv_backup = copy.deepcopy(uv0)
+    >>> uv2 = uv0.compress_by_redundancy(tol=tol, inplace=False)
     >>> uv0.compress_by_redundancy(tol=tol)
     >>> uv2 == uv0
     True
 
-    # Note -- Compressing and inflating changes the baseline order, so the inflated object may be different.
+    # Note -- Compressing and inflating changes the baseline order, reorder before comparing.
     >>> uv0.inflate_by_redundancy(tol=tol)
+    >>> uv_backup.reorder_blts()
     >>> np.all(uv0.baseline_array == uv_backup.baseline_array)
-    False
+    True
 
     >>> uv2.inflate_by_redundancy(tol=tol)
     >>> uv2 == uv0
diff --git a/pyuvdata/utils.py b/pyuvdata/utils.py
index 52d5c2f3..d04d6afa 100644
--- a/pyuvdata/utils.py
+++ b/pyuvdata/utils.py
@@ -956,24 +956,34 @@ def antnums_to_baseline(ant1, ant2, Nants_telescope, attempt256=False):
         return np.int64(baseline)
 
 
-def get_baseline_redundancies(baseline_inds, baseline_vecs, tol=1.0, with_conjugates=False):
+def get_baseline_redundancies(baselines, baseline_vecs, tol=1.0, with_conjugates=False):
     """
-    Return redundant baseline groups, and lists of corresponding lengths and vectors
+    Find redundant baseline groups
 
-    Args:
-        baseline_inds = Baseline indices.  (Nbls,), (int)
-        baseline_vecs = Baseline vectors in meters. (Nbls, 3), (float)
-        tol  = Absolute tolerance of redundancy, in meters. (float)
-        with_conjugates = Include baselines that are redundant when flipped.
-
-    Returns:
-        baseline_groups: list of lists of redundant baseline indices
-        vec_bin_centers: List of vectors describing redundant group centers
-        lengths: List of redundant group baseline lengths in meters
-        baseline_ind_conj: List of baselines that are redundant when reversed. (Only returned if with_conjugates is True)
+    Parameters
+    ----------
+    baselines : array_like of int
+        Baseline numbers, shape (Nbls,)
+    baseline_vecs : array_like of float
+        Baseline vectors in meters, shape shape (Nbls, 3)
+    tol : float
+        Absolute tolerance of redundancy, in meters.
+    with_conjugates : bool
+        Option to include baselines that are redundant when flipped.
 
+    Returns
+    -------
+    baseline_groups : list of lists of int
+        list of lists of redundant baseline numbers
+    vec_bin_centers : list of array_like of float
+        List of vectors describing redundant group centers
+    lengths : list of float
+        List of redundant group baseline lengths in meters
+    baseline_ind_conj : list of int
+        List of baselines that are redundant when reversed. Only returned if
+        with_conjugates is True
     """
-    Nbls = baseline_inds.shape[0]
+    Nbls = baselines.shape[0]
 
     if not baseline_vecs.shape == (Nbls, 3):
         raise ValueError("Baseline vectors must be shape (Nbls, 3)")
@@ -992,8 +1002,8 @@ def get_baseline_redundancies(baseline_inds, baseline_vecs, tol=1.0, with_conjug
                 conjugates.append(bl[0] < 0)
         conjugates = np.array(conjugates, dtype=bool)
         baseline_vecs[conjugates] *= (-1)
-        baseline_ind_conj = baseline_inds[conjugates]
-        bl_gps, vec_bin_centers, lens = get_baseline_redundancies(baseline_inds, baseline_vecs, tol=tol, with_conjugates=False)
+        baseline_ind_conj = baselines[conjugates]
+        bl_gps, vec_bin_centers, lens = get_baseline_redundancies(baselines, baseline_vecs, tol=tol, with_conjugates=False)
         return bl_gps, vec_bin_centers, lens, baseline_ind_conj
 
     # For each baseline, list all others that are within the tolerance distance.
@@ -1001,12 +1011,12 @@ def get_baseline_redundancies(baseline_inds, baseline_vecs, tol=1.0, with_conjug
     adj = {}   # Adjacency dictionary
 
     for bi, bv0 in enumerate(baseline_vecs):
-        key0 = baseline_inds[bi]
+        key0 = baselines[bi]
         adj[key0] = []
         for bj, bv1 in enumerate(baseline_vecs):
             dist = np.linalg.norm(bv1 - bv0)
             if dist < tol:
-                key1 = baseline_inds[bj]
+                key1 = baselines[bj]
                 adj[key0].append(key1)
 
     # The adjacency list defines a set of graph edges.
@@ -1036,7 +1046,7 @@ def get_baseline_redundancies(baseline_inds, baseline_vecs, tol=1.0, with_conjug
     N_unique = len(bl_gps)
     vec_bin_centers = np.zeros((N_unique, 3))
     for gi, gp in enumerate(bl_gps):
-        inds = [np.where(i == baseline_inds)[0] for i in gp]
+        inds = [np.where(i == baselines)[0] for i in gp]
         vec_bin_centers[gi] = np.mean(baseline_vecs[inds, :], axis=0)
 
     lens = np.sqrt(np.sum(vec_bin_centers**2, axis=1))
@@ -1046,22 +1056,33 @@ def get_baseline_redundancies(baseline_inds, baseline_vecs, tol=1.0, with_conjug
 
 def get_antenna_redundancies(antenna_numbers, antenna_positions, tol=1.0, include_autos=False):
     """
-    Construct baselines from antenna positions and get baseline redundancies.
+    Find redundant baseline groups based on antenna positions.
 
-    Args:
-        antenna_numbers:  Antenna numbers.   (float), shape (Nants,)
-        antenna_positions:  Antenna position vectors in meters. Cartesian frame  (float), shape (Nants, 3)
-        tol = (float)   Redundancy tolerance in meters. (float)
-        include_autos =  Include autocorrelations (bool). Default is false
+    Include all possible redundant baselines based on antenna positions.
 
-    Returns:
-        baseline_groups: list of lists of redundant baseline indices
-        vec_bin_centers: List of vectors describing redundant group centers
-        lengths: List of redundant group baseline lengths in meters
+    Parameters
+    ----------
+    antenna_numbers : array_like of int
+        Antenna numbers, shape (Nants,).
+    antenna_positions : array_like of float
+        Antenna position vectors in the ENU (topocentric) frame in meters, shape (Nants, 3).
+    tol : float
+        Redundancy tolerance in meters.
+    include_autos : bool
+        Option to include autocorrelations.
+
+    Returns
+    -------
+    baseline_groups : list of lists of int
+        list of lists of redundant baseline numbers
+    vec_bin_centers : list of array_like of float
+        List of vectors describing redundant group centers
+    lengths : list of float
+        List of redundant group baseline lengths in meters
     """
     Nants = antenna_numbers.shape[0]
 
-    bl_inds = []
+    bls = []
     bl_vecs = []
 
     for aj in range(Nants):
@@ -1078,10 +1099,10 @@ def get_antenna_redundancies(antenna_numbers, antenna_positions, tol=1.0, includ
                 bv *= (-1)
                 bidx = antnums_to_baseline(antj, anti, Nants)
             bl_vecs.append(bv)
-            bl_inds.append(bidx)
-    bl_inds = np.array(bl_inds)
+            bls.append(bidx)
+    bls = np.array(bls)
     bl_vecs = np.array(bl_vecs)
-    return get_baseline_redundancies(bl_inds, bl_vecs, tol=tol, with_conjugates=False)
+    return get_baseline_redundancies(bls, bl_vecs, tol=tol, with_conjugates=False)
 
 
 def _reraise_context(fmt, *args):
diff --git a/pyuvdata/uvdata.py b/pyuvdata/uvdata.py
index 63846527..0fad4f33 100644
--- a/pyuvdata/uvdata.py
+++ b/pyuvdata/uvdata.py
@@ -3783,25 +3783,36 @@ class UVData(UVBase):
         """
         return np.diff(np.sort(list(set(self.time_array))))[0] * 86400
 
-    def get_antenna_redundancies(self, tol=1.0, include_autos=True):
+    def get_antenna_redundancies(self, tol=1.0, include_autos=True,
+                                 conjugate_bls=False):
         """
         Convenience method for getting baseline redundancies to a given tolerance
         from antenna positions.
 
-        Note that the baseline numbers returned will only correspond with those
-        in the baseline_array under the u-positive convention. Use the conjugate_bls
-        method with convention='u>0' to flip all baselines in the UVData object
-        to follow this.
+        Returns all possible baselines (antenna pairs) not just those with data.
 
         Args:
             tol: Redundancy tolerance in meters (default 1m)
             include_autos: Include autocorrelations in the full redundancy list (default True)
+            conjugate_bls: Option to conjugate baselines on this object to the
+                'u>0' convention. Set this to True to ensure that the returned
+                baseline numbers will match the baseline numbers in the data
+                (if they exist in the data).
 
         Returns:
-            baseline_groups: list of lists of redundant baseline indices
+            baseline_groups: list of lists of redundant baseline numbers
             vec_bin_centers: List of vectors describing redundant group centers
             lengths: List of redundant group baseline lengths in meters
+
+        Notes
+        -----
+        Note that this method finds all possible redundant baselines in the 'u>0'
+        part of the uv plane. In order for the returned baseline numbers to match
+        baselines in this object, this method will conjugate baselines on this
+        object to the 'u>0' convention unless `no_conjugate` is set to True.
         """
+        if conjugate_bls:
+            self.conjugate_bls(convention='u>0')
         antpos, numbers = self.get_ENU_antpos(center=False)
         return uvutils.get_antenna_redundancies(numbers, antpos, tol=tol, include_autos=include_autos)
 
@@ -3813,7 +3824,7 @@ class UVData(UVBase):
             tol: Redundancy tolerance in meters (default 1m)
 
         Returns:
-            baseline_groups: list of lists of redundant baseline indices
+            baseline_groups: list of lists of redundant baseline numbers
             vec_bin_centers: List of vectors describing redundant group centers
             lengths: List of redundant group baseline lengths in meters
             baseline_ind_conj: List of baselines that are redundant when reversed. (Only returned if with_conjugates is True)
@@ -3821,9 +3832,9 @@ class UVData(UVBase):
         _, unique_inds = np.unique(self.baseline_array, return_index=True)
         unique_inds.sort()
         baseline_vecs = np.take(self.uvw_array, unique_inds, axis=0)
-        baseline_inds = np.take(self.baseline_array, unique_inds)
+        baselines = np.take(self.baseline_array, unique_inds)
 
-        return uvutils.get_baseline_redundancies(baseline_inds, baseline_vecs, tol=tol, with_conjugates=True)
+        return uvutils.get_baseline_redundancies(baselines, baseline_vecs, tol=tol, with_conjugates=True)
 
     def compress_by_redundancy(self, tol=1.0, inplace=True, metadata_only=False,
                                keep_all_metadata=True):
@@ -3847,63 +3858,77 @@ class UVData(UVBase):
         return self.select(bls=bl_ants, inplace=inplace, metadata_only=metadata_only,
                            keep_all_metadata=keep_all_metadata)
 
-    def inflate_by_redundancy(self, tol=1.0):
+    def inflate_by_redundancy(self, tol=1.0, blt_order='time', blt_minor_order=None):
         """
         Expand data to full size, copying data among redundant baselines.
 
+        Note that this method conjugates baselines to the 'u>0' convention in order
+        to inflate the redundancies.
+
         Args:
             tol = Redundancy tolerance in meters (default 1.0m)
+            blt_order : str
+                string specifying primary order along the blt axis (see `reorder_blts`)
+            blt_minor_order : str
+                string specifying minor order along the blt axis (see `reorder_blts`)
         """
 
-        # get_antenna_redundancies method gives baselines under the u-positive
-        # convention (u>0, v>0 if u==0, w>0 if u==v==0)
-        self.conjugate_bls(convention='u>0', use_enu=True)
-
-        red_gps, centers, lengths = self.get_antenna_redundancies(tol=tol)
+        red_gps, centers, lengths = self.get_antenna_redundancies(tol=tol,
+                                                                  conjugate_bls=True)
 
         # Stack redundant groups into one array.
         group_index, bl_array_full = zip(*[(i, bl) for i, gp in enumerate(red_gps) for bl in gp])
 
+        # TODO should be an assert that each baseline only ends up in one group
+
         # Map group index to blt indices in the compressed array.
         bl_array_comp = self.baseline_array
-        uniq_bl, bl_inv = np.unique(bl_array_comp, return_inverse=True)
+        uniq_bl = np.unique(bl_array_comp)
 
         group_blti = {}
+        Nblts_full = 0
         for i, gp in enumerate(red_gps):
             for bl in gp:
                 # First baseline in the group that is also in the compressed baseline array.
                 if bl in uniq_bl:
                     group_blti[i] = np.where(bl == bl_array_comp)[0]
+                    # add number of blts for this group
+                    Nblts_full += group_blti[i].size * len(gp)
                     break
 
-        Nbls_full = len(bl_array_full)
-        blt_map = np.zeros(Nbls_full * self.Ntimes, dtype=int)
-        missing = np.zeros(Nbls_full * self.Ntimes).astype(bool)
-        for i, gi in enumerate(group_index):
+        blt_map = np.zeros(Nblts_full, dtype=int)
+        full_baselines = np.zeros(Nblts_full, dtype=int)
+        missing = []
+        counter = 0
+        for bl, gi in zip(bl_array_full, group_index):
             try:
-                blt_map[i::Nbls_full] = group_blti[gi]
+                # this makes the time the fastest axis
+                blt_map[counter:counter + group_blti[gi].size] = group_blti[gi]
+                full_baselines[counter:counter + group_blti[gi].size] = bl
+                counter += group_blti[gi].size
             except KeyError:
-                missing[i::Nbls_full] = True
+                missing.append(bl)
                 pass
+
         if np.any(missing):
             warnings.warn("Missing some redundant groups. Filling in available data.")
-            blt_map = blt_map[~missing]
-            bl_array_full = np.array(bl_array_full)[~missing[:Nbls_full]]
 
         # blt_map is an index array mapping compressed blti indices to uncompressed
         self.data_array = self.data_array[blt_map, ...]
         self.nsample_array = self.nsample_array[blt_map, ...]
         self.flag_array = self.flag_array[blt_map, ...]
 
-        self.baseline_array = np.tile(bl_array_full, self.Ntimes)
-        self.ant_1_array, self.ant_2_array = self.baseline_to_antnums(self.baseline_array)
-        if np.any(missing):
-            self.Nants_data = np.unique(self.ant_1_array.tolist() + self.ant_2_array.tolist()).size
-        self.Nbls = len(bl_array_full)
-        self.Nblts = self.Nbls * self.Ntimes
         self.time_array = self.time_array[blt_map]
         self.lst_array = self.lst_array[blt_map]
         self.integration_time = self.integration_time[blt_map]
         self.uvw_array = self.uvw_array[blt_map, ...]
 
+        self.baseline_array = full_baselines
+        self.ant_1_array, self.ant_2_array = self.baseline_to_antnums(self.baseline_array)
+        self.Nants_data = np.unique(self.ant_1_array.tolist() + self.ant_2_array.tolist()).size
+        self.Nbls = np.unique(self.baseline_array).size
+        self.Nblts = Nblts_full
+
+        self.reorder_blts(order=blt_order, minor_order=blt_minor_order)
+
         self.check()


Test patch:
diff --git a/pyuvdata/tests/test_utils.py b/pyuvdata/tests/test_utils.py
index b59b3148..dfa736aa 100644
--- a/pyuvdata/tests/test_utils.py
+++ b/pyuvdata/tests/test_utils.py
@@ -465,8 +465,8 @@ def test_deprecated_funcs():
 
 def test_redundancy_finder():
     """
-        Check that get_baseline_redundancies and get_antenna_redundancies return consistent
-        redundant groups for a test file with the HERA19 layout.
+    Check that get_baseline_redundancies and get_antenna_redundancies return consistent
+    redundant groups for a test file with the HERA19 layout.
     """
     uvd = pyuvdata.UVData()
     uvd.read_uvfits(os.path.join(DATA_PATH, 'fewant_randsrc_airybeam_Nsrc100_10MHz.uvfits'))
@@ -481,38 +481,48 @@ def test_redundancy_finder():
 
     bl_positions = uvd.uvw_array
 
-    pytest.raises(ValueError, uvutils.get_baseline_redundancies, uvd.baseline_array, bl_positions[0:2, 0:1])
-    baseline_groups, vec_bin_centers, lens = uvutils.get_baseline_redundancies(uvd.baseline_array, bl_positions, tol=tol)
+    pytest.raises(ValueError, uvutils.get_baseline_redundancies,
+                  uvd.baseline_array, bl_positions[0:2, 0:1])
+    baseline_groups, vec_bin_centers, lens = uvutils.get_baseline_redundancies(
+        uvd.baseline_array, bl_positions, tol=tol)
 
-    baseline_groups, vec_bin_centers, lens = uvutils.get_baseline_redundancies(uvd.baseline_array, bl_positions, tol=tol)
+    baseline_groups, vec_bin_centers, lens = uvutils.get_baseline_redundancies(
+        uvd.baseline_array, bl_positions, tol=tol)
 
     for gi, gp in enumerate(baseline_groups):
         for bl in gp:
             bl_ind = np.where(uvd.baseline_array == bl)
             bl_vec = bl_positions[bl_ind]
-            assert np.allclose(np.sqrt(np.dot(bl_vec, vec_bin_centers[gi])), lens[gi], atol=tol)
+            assert np.allclose(np.sqrt(np.dot(bl_vec, vec_bin_centers[gi])),
+                               lens[gi], atol=tol)
 
-    # Shift the baselines around in a circle. Check that the same baselines are recovered to the corresponding tolerance increase.
-    # This moves one baseline at a time by a fixed displacement and checks that the redundant groups are the same.
+    # Shift the baselines around in a circle. Check that the same baselines are
+    # recovered to the corresponding tolerance increase.
+    # This moves one baseline at a time by a fixed displacement and checks that
+    # the redundant groups are the same.
 
     hightol = 0.25  # meters. Less than the smallest baseline in the file.
     Nbls = uvd.Nbls
     Nshifts = 5
     shift_angs = np.linspace(0, 2 * np.pi, Nshifts)
-    base_shifts = np.stack(((hightol - tol) * np.cos(shift_angs), (hightol - tol) * np.sin(shift_angs), np.zeros(Nshifts))).T
+    base_shifts = np.stack(((hightol - tol) * np.cos(shift_angs),
+                            (hightol - tol) * np.sin(shift_angs),
+                            np.zeros(Nshifts))).T
     for sh in base_shifts:
         for bi in range(Nbls):
             # Shift one baseline at a time.
             bl_positions_new = uvd.uvw_array
             bl_positions_new[bi] += sh
 
-            baseline_groups_new, vec_bin_centers, lens = uvutils.get_baseline_redundancies(uvd.baseline_array, bl_positions_new, tol=hightol)
+            baseline_groups_new, vec_bin_centers, lens = uvutils.get_baseline_redundancies(
+                uvd.baseline_array, bl_positions_new, tol=hightol)
 
             for gi, gp in enumerate(baseline_groups_new):
                 for bl in gp:
                     bl_ind = np.where(uvd.baseline_array == bl)
                     bl_vec = bl_positions[bl_ind]
-                    assert np.allclose(np.sqrt(np.abs(np.dot(bl_vec, vec_bin_centers[gi]))), lens[gi], atol=hightol)
+                    assert np.allclose(np.sqrt(np.abs(np.dot(bl_vec, vec_bin_centers[gi]))),
+                                       lens[gi], atol=hightol)
 
             # Compare baseline groups:
             a = [tuple(el) for el in baseline_groups]
@@ -521,22 +531,27 @@ def test_redundancy_finder():
 
     tol = 0.05
 
-    antpos, antnums = uvtest.checkWarnings(uvd.get_ENU_antpos, message=['The default for the `center`'],
+    antpos, antnums = uvtest.checkWarnings(uvd.get_ENU_antpos,
+                                           message=['The default for the `center`'],
                                            category=DeprecationWarning,
                                            nwarnings=1)
 
-    baseline_groups_ants, vec_bin_centers, lens = uvutils.get_antenna_redundancies(antnums, antpos, tol=tol, include_autos=False)
+    baseline_groups_ants, vec_bin_centers, lens = uvutils.get_antenna_redundancies(
+        antnums, antpos, tol=tol, include_autos=False)
     # Under these conditions, should see 19 redundant groups in the file.
     assert len(baseline_groups_ants) == 19
 
     # Check with conjugated baseline redundancies returned
     u16_0 = bl_positions[16, 0]
-    bl_positions[16, 0] = 0                 # Ensure at least one baseline has u==0 and v!=0 (for coverage of this case)
-    baseline_groups, vec_bin_centers, lens, conjugates = uvutils.get_baseline_redundancies(uvd.baseline_array, bl_positions, tol=tol, with_conjugates=True)
+    # Ensure at least one baseline has u==0 and v!=0 (for coverage of this case)
+    bl_positions[16, 0] = 0
+    baseline_groups, vec_bin_centers, lens, conjugates = uvutils.get_baseline_redundancies(
+        uvd.baseline_array, bl_positions, tol=tol, with_conjugates=True)
 
     # restore baseline (16,0) and repeat to get correct groups
     bl_positions[16, 0] = u16_0
-    baseline_groups, vec_bin_centers, lens, conjugates = uvutils.get_baseline_redundancies(uvd.baseline_array, bl_positions, tol=tol, with_conjugates=True)
+    baseline_groups, vec_bin_centers, lens, conjugates = uvutils.get_baseline_redundancies(
+        uvd.baseline_array, bl_positions, tol=tol, with_conjugates=True)
 
     # Should get the same groups as with the antenna method:
     baseline_groups_flipped = []
@@ -558,7 +573,8 @@ def test_redundancy_finder():
             bl_vec = bl_positions[bl_ind]
             if bl in conjugates:
                 bl_vec *= (-1)
-            assert np.isclose(np.sqrt(np.dot(bl_vec, vec_bin_centers[gi])), lens[gi], atol=tol)
+            assert np.isclose(np.sqrt(np.dot(bl_vec, vec_bin_centers[gi])),
+                              lens[gi], atol=tol)
 
 
 def test_redundancy_conjugates():
@@ -580,7 +596,8 @@ def test_redundancy_conjugates():
     for i, (u, v, w) in enumerate(bl_vecs):
         if (u < 0) or (v < 0 and u == 0) or (w < 0 and u == v == 0):
             expected_conjugates.append(bl_inds[i])
-    bl_gps, vecs, lens, conjugates = uvutils.get_baseline_redundancies(bl_inds, bl_vecs, tol=tol, with_conjugates=True)
+    bl_gps, vecs, lens, conjugates = uvutils.get_baseline_redundancies(
+        bl_inds, bl_vecs, tol=tol, with_conjugates=True)
 
     assert sorted(conjugates) == sorted(expected_conjugates)
 
@@ -594,7 +611,8 @@ def test_redundancy_finder_fully_redundant_array():
     tol = 1  # meters
     bl_positions = uvd.uvw_array
 
-    baseline_groups, vec_bin_centers, lens, conjugates = uvutils.get_baseline_redundancies(uvd.baseline_array, bl_positions, tol=tol, with_conjugates=True)
+    baseline_groups, vec_bin_centers, lens, conjugates = uvutils.get_baseline_redundancies(
+        uvd.baseline_array, bl_positions, tol=tol, with_conjugates=True)
 
     # Only 1 set of redundant baselines
     assert len(baseline_groups) == 1
diff --git a/pyuvdata/tests/test_uvdata.py b/pyuvdata/tests/test_uvdata.py
index c381ec5e..1a501ac2 100644
--- a/pyuvdata/tests/test_uvdata.py
+++ b/pyuvdata/tests/test_uvdata.py
@@ -3374,6 +3374,33 @@ def test_set_uvws_from_antenna_pos():
     assert np.isclose(max_diff, 0., atol=2)
 
 
+def test_get_antenna_redundancies():
+    uv0 = UVData()
+    uv0.read_uvfits(os.path.join(DATA_PATH, 'fewant_randsrc_airybeam_Nsrc100_10MHz.uvfits'))
+
+    red_gps, centers, lengths = uv0.get_antenna_redundancies(include_autos=False,
+                                                             conjugate_bls=True)
+
+    # assert all baselines are in the data (because it's conjugated to match)
+    for i, gp in enumerate(red_gps):
+        for bl in gp:
+            assert bl in uv0.baseline_array
+    old_bl_array = np.copy(uv0.baseline_array)
+
+    # conjugate data differently
+    uv0.conjugate_bls(convention='ant1<ant2')
+    new_red_gps, new_centers, new_lengths = uv0.get_antenna_redundancies(
+        include_autos=False)
+
+    # new and old baseline Numbers are not the same (different conjugation)
+    assert not np.allclose(uv0.baseline_array, old_bl_array)
+
+    # all redundancy info is the same
+    assert red_gps == new_red_gps
+    assert np.allclose(centers, new_centers)
+    assert np.allclose(lengths, new_lengths)
+
+
 def test_redundancy_contract_expand():
     # Test that a UVData object can be reduced to one baseline from each redundant group
     # and restored to its original form.
@@ -3384,10 +3411,7 @@ def test_redundancy_contract_expand():
     tol = 0.02   # Fails at lower precision because some baselines falling into multiple redundant groups
 
     # Assign identical data to each redundant group:
-    uvtest.checkWarnings(uv0.conjugate_bls, func_kwargs={'convention': 'u>0', 'use_enu': True},
-                         message=['The default for the `center`'],
-                         nwarnings=1, category=DeprecationWarning)
-    red_gps, centers, lengths = uv0.get_antenna_redundancies(tol=tol)
+    red_gps, centers, lengths = uv0.get_antenna_redundancies(tol=tol, conjugate_bls=True)
     for i, gp in enumerate(red_gps):
         for bl in gp:
             inds = np.where(bl == uv0.baseline_array)
@@ -3435,16 +3459,69 @@ def test_redundancy_contract_expand():
     assert uv2 == uv3
 
 
+def test_redundancy_contract_expand_nblts_not_nbls_times_ntimes():
+    uv0 = UVData()
+    testfile = os.path.join(DATA_PATH, 'day2_TDEM0003_10s_norx_1src_1spw.uvfits')
+    uvtest.checkWarnings(uv0.read_uvfits, [testfile], message='Telescope EVLA is not')
+
+    # check that Nblts != Nbls * Ntimes
+    assert uv0.Nblts != uv0.Nbls * uv0.Ntimes
+
+    tol = 1.0
+
+    # Assign identical data to each redundant group:
+    red_gps, centers, lengths = uv0.get_antenna_redundancies(tol=tol,
+                                                             conjugate_bls=True)
+    for i, gp in enumerate(red_gps):
+        for bl in gp:
+            inds = np.where(bl == uv0.baseline_array)
+            uv0.data_array[inds, ...] *= 0
+            uv0.data_array[inds, ...] += complex(i)
+
+    uv2 = uv0.compress_by_redundancy(tol=tol, inplace=False)
+
+    # check inflating gets back to the original
+    uvtest.checkWarnings(uv2.inflate_by_redundancy, {tol: tol},
+                         nwarnings=2, category=[DeprecationWarning, UserWarning],
+                         message=['The default for the `center` keyword has changed.',
+                                  'Missing some redundant groups. Filling in available data.'])
+
+    uv2.history = uv0.history
+    # Inflation changes the baseline ordering into the order of the redundant groups.
+    # reorder bls for comparison
+    uv0.reorder_blts()
+    uv2.reorder_blts()
+    uv2._uvw_array.tols = [0, tol]
+
+    blt_inds = []
+    missing_inds = []
+    for bl, t in zip(uv0.baseline_array, uv0.time_array):
+        antpair = uv2.baseline_to_antnums(bl)
+        if (bl, t) in zip(uv2.baseline_array, uv2.time_array):
+            this_ind = np.where((uv2.baseline_array == bl) & (uv2.time_array == t))[0]
+            blt_inds.append(this_ind[0])
+        else:
+            # this is missing because of the compress_by_redundancy step
+            missing_inds.append(np.where((uv0.baseline_array == bl) & (uv0.time_array == t))[0])
+
+    uv3 = uv2.select(blt_inds=blt_inds, inplace=False)
+
+    orig_inds_keep = list(np.arange(uv0.Nblts))
+    for ind in missing_inds:
+        orig_inds_keep.remove(ind)
+    uv1 = uv0.select(blt_inds=orig_inds_keep, inplace=False)
+
+    assert uv3 == uv1
+
+
 def test_compress_redundancy_metadata_only():
     uv0 = UVData()
     uv0.read_uvfits(os.path.join(DATA_PATH, 'fewant_randsrc_airybeam_Nsrc100_10MHz.uvfits'))
     tol = 0.01
 
     # Assign identical data to each redundant group:
-    uvtest.checkWarnings(uv0.conjugate_bls, func_kwargs={'convention': 'u>0', 'use_enu': True},
-                         message=['The default for the `center`'],
-                         nwarnings=1, category=DeprecationWarning)
-    red_gps, centers, lengths = uv0.get_antenna_redundancies(tol=tol)
+    red_gps, centers, lengths = uv0.get_antenna_redundancies(tol=tol,
+                                                             conjugate_bls=True)
     for i, gp in enumerate(red_gps):
         for bl in gp:
             inds = np.where(bl == uv0.baseline_array)