Esempio n. 1
0
    def _resample(self, xgrid, ygrid, wgrid, x):

        lza = regrid.lanczos_forward_matrix(xgrid, x, a=self.lanczos_width).T

        y = np.matmul(ygrid, lza)
        w = invert_no_zero(np.matmul(invert_no_zero(wgrid), lza**2))

        return y, w
Esempio n. 2
0
    def process_finish(self):
        """Normalise the stack and return the result.

        Includes the sample variance over transits within the stack.

        Returns
        -------
        stack: draco.core.containers.TrackBeam
            Stacked transits.
        """
        # Divide by norm to get average transit
        inv_norm = invert_no_zero(self.norm)
        self.stack.beam[:] *= inv_norm
        self.stack.weight[:] = invert_no_zero(self.stack.weight[:]) * self.norm**2

        self.variance = self.variance * inv_norm - np.abs(self.stack.beam[:]) ** 2
        self.pseudo_variance = self.pseudo_variance * inv_norm - self.stack.beam[:] ** 2

        # Calculate the covariance between the real and imaginary component
        # from the accumulated variance and psuedo-variance
        self.stack.sample_variance[0] = 0.5 * (
            self.variance + self.pseudo_variance.real
        )
        self.stack.sample_variance[1] = 0.5 * self.pseudo_variance.imag
        self.stack.sample_variance[2] = 0.5 * (
            self.variance - self.pseudo_variance.real
        )

        # Create tag
        time_range = np.percentile(self.stack.attrs["transit_time"], [0, 100])
        self.stack.attrs["tag"] = "{}_{}_to_{}".format(
            self.stack.attrs["source_name"],
            ephem.unix_to_datetime(time_range[0]).strftime("%Y%m%dT%H%M%S"),
            ephem.unix_to_datetime(time_range[1]).strftime("%Y%m%dT%H%M%S"),
        )

        return self.stack
Esempio n. 3
0
    def process(self, track_in, gain):
        """Apply gain

        Parameters
        ----------
        track: draco.core.containers.TrackBeam
            Holography track to apply gains to. Will apply gains to
            track['beam'], expecting axes to be freq, pol, input, ha
        gain: np.array
            Gain to apply. Expected axes are freq, pol, and input

        Returns
        -------
        track: draco.core.containers.TrackBeam
            Holography track with gains applied.
        """

        if self.overwrite:
            track = track_in
        else:
            track = TrackBeam(
                axes_from=track_in,
                attrs_from=track_in,
                distributed=track_in.distributed,
                comm=track_in.comm,
            )
            track["beam"] = track_in["beam"][:]
            track["weight"] = track_in["weight"][:]

        track["beam"][:] *= gain.gain[:][:, np.newaxis, :, np.newaxis]
        track["weight"][:] *= invert_no_zero(np.abs(gain.gain[:]) ** 2)[
            :, np.newaxis, :, np.newaxis
        ]

        track["beam"][:] = np.where(np.isfinite(track["beam"][:]), track["beam"][:], 0)
        track["weight"][:] = np.where(
            np.isfinite(track["weight"][:]), track["weight"][:], 0
        )

        return track
Esempio n. 4
0
    def process(self, transit):
        """Perform the fit.

        Parameters
        ----------
        transit: TrackBeam
            Transit to be fit.

        Returns
        -------
        fit: TransitFitParams
            Fit parameters.
        """
        transit.redistribute("freq")

        # Set initial estimate of beam sigma
        local_slice = slice(
            transit.beam.local_offset[0],
            transit.beam.local_offset[0] + transit.beam.local_shape[0],
        )
        ninput = transit.beam.local_shape[2]
        freq = transit.freq[local_slice]
        sigma = (0.7 * SPEED_LIGHT / (CHIME_CYL_W * freq)) * (360.0 / np.pi)
        sigma = sigma[:, np.newaxis] * np.ones((1, ninput), dtype=sigma.dtype)

        # Find index into pol axis that yields copolar products
        pol_axis = list(transit.index_map["pol"])
        if "co" in pol_axis:
            copolar_slice = (slice(None), pol_axis.index("co"))
        else:
            this_pol = np.array(
                [
                    pol_axis.index("S")
                    if not ((ii // 256) % 2)
                    else pol_axis.index("E")
                    for ii in range(ninput)
                ]
            )
            copolar_slice = (slice(None), this_pol, np.arange(ninput))

        # Dereference datasets
        ha = transit.pix["phi"][:]

        vis = transit.beam[:].view(np.ndarray)
        vis = vis[copolar_slice]

        err = transit.weight[:].view(np.ndarray)
        err = np.sqrt(invert_no_zero(err[copolar_slice]))

        # Flag data that is outside the fit window set by nsigma config parameter
        if self.nsigma is not None:
            err *= (
                np.abs(ha[np.newaxis, np.newaxis, :])
                <= (self.nsigma * sigma[:, :, np.newaxis])
            ).astype(err.dtype)

        # Instantiate the model fitter
        model = self.ModelClass(**self.model_kwargs)

        # Fit the model
        model.fit(ha, vis, err, width=sigma, **self.fit_kwargs)

        # Pack into container
        fit = TransitFitParams(
            param=model.parameter_names,
            component=model.component,
            axes_from=transit,
            attrs_from=transit,
            distributed=transit.distributed,
            comm=transit.comm,
        )

        fit.add_dataset("chisq")
        fit.add_dataset("ndof")

        fit.redistribute("freq")

        # Transfer fit information to container attributes
        fit.attrs["model_kwargs"] = json.dumps(model.model_kwargs)
        fit.attrs["model_class"] = ".".join(
            [getattr(self.ModelClass, key) for key in ["__module__", "__name__"]]
        )

        # Save datasets
        fit.parameter[:] = model.param[:]
        fit.parameter_cov[:] = model.param_cov[:]
        fit.chisq[:] = model.chisq[:]
        fit.ndof[:] = model.ndof[:]

        return fit
Esempio n. 5
0
    def process(self, beam, data):
        """Stack

        Parameters
        ----------
        beam : TrackBeam
            The beam that will be stacked.
        data : VisContainer
            Must contain `prod` index map and `stack` reverse map
            that will be used to stack the beam.

        Returns
        -------
        stacked_beam: VisContainer
            The input `beam` stacked in the same manner as

        """
        # Distribute over frequencies
        data.redistribute("freq")
        beam.redistribute("freq")

        # Grab the stack specifications from the input sidereal stream
        prod = data.index_map["prod"]
        reverse_stack = data.reverse_map["stack"][:]

        input_flags = data.input_flags[:]
        if not np.any(input_flags):
            input_flags = np.ones_like(input_flags)

        # Create output container
        if isinstance(data, SiderealStream):
            OutputContainer = SiderealStream
            output_kwargs = {"ra": data.ra[:]}
        else:
            OutputContainer = TimeStream
            output_kwargs = {"time": data.time[:]}

        stacked_beam = OutputContainer(
            axes_from=data,
            attrs_from=beam,
            distributed=True,
            comm=data.comm,
            **output_kwargs
        )

        stacked_beam.vis[:] = 0.0
        stacked_beam.weight[:] = 0.0

        stacked_beam.attrs["tag"] = "_".join([beam.attrs["tag"], data.attrs["tag"]])

        # Dereference datasets
        bv = beam.beam[:].view(np.ndarray)
        bw = beam.weight[:].view(np.ndarray)

        ov = stacked_beam.vis[:]
        ow = stacked_beam.weight[:]

        pol_filter = {
            "X": "X",
            "Y": "Y",
            "E": "X",
            "S": "Y",
            "co": "co",
            "cross": "cross",
        }
        pol = [pol_filter.get(pp, None) for pp in self.telescope.polarisation]
        beam_pol = [pol_filter.get(pp, None) for pp in beam.index_map["pol"][:]]

        # Compute the fractional variance of the beam measurement
        frac_var = invert_no_zero(bw * np.abs(bv) ** 2)

        # Create counter to increment during the stacking.
        # This will be used to normalize at the end.
        counter = np.zeros_like(ow)

        # Construct stack
        for pp, (ss, conj) in enumerate(reverse_stack):

            aa, bb = prod[pp]
            if conj:
                aa, bb = bb, aa

            try:
                aa_pol, bb_pol = self._resolve_pol(pol[aa], pol[bb], beam_pol)
            except ValueError:
                continue

            cross = bv[:, aa_pol, aa, :] * bv[:, bb_pol, bb, :].conj()

            weight = (
                input_flags[np.newaxis, aa, :]
                * input_flags[np.newaxis, bb, :]
                * invert_no_zero(
                    np.abs(cross) ** 2
                    * (frac_var[:, aa_pol, aa, :] + frac_var[:, bb_pol, bb, :])
                )
            )

            if self.weight == "inverse_variance":
                wss = weight
            else:
                wss = (weight > 0.0).astype(np.float32)

            # Accumulate variances in quadrature.  Save in the weight dataset.
            ov[:, ss, :] += wss * cross
            ow[:, ss, :] += wss**2 * invert_no_zero(weight)

            # Increment counter
            counter[:, ss, :] += wss

        # Divide through by counter to get properly weighted visibility average
        ov[:] *= invert_no_zero(counter)
        ow[:] = counter**2 * invert_no_zero(ow[:])

        return stacked_beam
Esempio n. 6
0
    def process(self, transit):
        """Add a transit to the stack.

        Parameters
        ----------
        transit: draco.core.containers.TrackBeam
            A holography transit.
        """

        self.log.info("Weight is %s" % self.weight)

        if self.stack is None:
            self.log.info("Initializing transit stack.")
            self.stack = TrackBeam(
                axes_from=transit, distributed=transit.distributed, comm=transit.comm
            )

            self.stack.add_dataset("sample_variance")
            self.stack.add_dataset("nsample")
            self.stack.redistribute("freq")

            self.log.info("Adding %s to stack." % transit.attrs["tag"])

            # Copy over relevant attributes
            self.stack.attrs["filename"] = [transit.attrs["tag"]]
            self.stack.attrs["observation_id"] = [transit.attrs["observation_id"]]
            self.stack.attrs["transit_time"] = [transit.attrs["transit_time"]]
            self.stack.attrs["archivefiles"] = list(transit.attrs["archivefiles"])

            self.stack.attrs["dec"] = transit.attrs["dec"]
            self.stack.attrs["source_name"] = transit.attrs["source_name"]
            self.stack.attrs["icrs_ra"] = transit.attrs["icrs_ra"]
            self.stack.attrs["cirs_ra"] = transit.attrs["cirs_ra"]

            # Copy data for first transit
            flag = (transit.weight[:] > 0.0).astype(np.int)
            if self.weight == "inverse_variance":
                coeff = transit.weight[:]
            else:
                coeff = flag.astype(np.float32)

            self.stack.beam[:] = coeff * transit.beam[:]
            self.stack.weight[:] = (coeff**2) * invert_no_zero(transit.weight[:])
            self.stack.nsample[:] = flag.astype(np.int)

            self.variance = coeff * np.abs(transit.beam[:]) ** 2
            self.pseudo_variance = coeff * transit.beam[:] ** 2
            self.norm = coeff

        else:
            if list(transit.beam.shape) != list(self.stack.beam.shape):
                self.log.error(
                    "Transit has different shape than stack: {}, {}".format(
                        transit.beam.shape, self.stack.beam.shape
                    )
                    + " Skipping."
                )
                return None

            self.log.info("Adding %s to stack." % transit.attrs["tag"])

            self.stack.attrs["filename"].append(transit.attrs["tag"])
            self.stack.attrs["observation_id"].append(transit.attrs["observation_id"])
            self.stack.attrs["transit_time"].append(transit.attrs["transit_time"])
            self.stack.attrs["archivefiles"] += list(transit.attrs["archivefiles"])

            # Accumulate transit data
            flag = (transit.weight[:] > 0.0).astype(np.int)
            if self.weight == "inverse_variance":
                coeff = transit.weight[:]
            else:
                coeff = flag.astype(np.float32)

            self.stack.beam[:] += coeff * transit.beam[:]
            self.stack.weight[:] += (coeff**2) * invert_no_zero(transit.weight[:])
            self.stack.nsample[:] += flag

            self.variance += coeff * np.abs(transit.beam[:]) ** 2
            self.pseudo_variance += coeff * transit.beam[:] ** 2
            self.norm += coeff

        return None
Esempio n. 7
0
    def process(self, sstream):
        """Computes the ringmap.

        Parameters
        ----------
        sstream : containers.SiderealStream
            The input sidereal stream.

        Returns
        -------
        rm : containers.RingMap
        """

        # Redistribute over frequency
        sstream.redistribute("freq")
        nfreq = sstream.vis.local_shape[0]

        # Extract the right ascension (or calculate from timestamp)
        ra = sstream.ra if "ra" in sstream.index_map else ephemeris.lsa(sstream.time)
        nra = ra.size

        # Construct mapping from vis array to unpacked 2D grid
        nprod = sstream.prodstack.shape[0]
        pind = np.zeros(nprod, dtype=np.int)
        xind = np.zeros(nprod, dtype=np.int)
        ysep = np.zeros(nprod, dtype=np.float)

        for pp, (ii, jj) in enumerate(sstream.prodstack):

            if self.telescope.feedconj[ii, jj]:
                ii, jj = jj, ii

            fi = self.telescope.feeds[ii]
            fj = self.telescope.feeds[jj]

            pind[pp] = 2 * int(fi.pol == "S") + int(fj.pol == "S")
            xind[pp] = np.abs(fi.cyl - fj.cyl)

            # TODO: don't use this internal property. This can probably wait
            # until the RingMapMaker gets completely moved into draco
            ysep[pp] = fi._pos[1] - fj._pos[1]

        abs_ysep = np.abs(ysep)
        min_ysep, max_ysep = np.percentile(abs_ysep[abs_ysep > 0.0], [0, 100])

        yind = np.round(ysep / min_ysep).astype(np.int)

        grid_index = list(zip(pind, xind, yind))

        # Define several variables describing the baseline configuration.
        nfeed = int(np.round(max_ysep / min_ysep)) + 1
        nvis_1d = 2 * nfeed - 1
        ncyl = np.max(xind) + 1
        nbeam = 1 if self.single_beam else int(2 * ncyl - 1)

        # Define polarisation axis
        pol = np.array(["XX", "reXY", "imXY", "YY"])
        npol = len(pol)

        # Create empty array for output
        vis = np.zeros((nfreq, npol, nra, ncyl, nvis_1d), dtype=np.complex128)
        invvar = np.zeros((nfreq, npol, nra, ncyl, nvis_1d), dtype=np.float64)
        weight = np.zeros((nfreq, npol, nra, ncyl, nvis_1d), dtype=np.float64)

        # If natural or uniform weighting was chosen, then calculate the
        # redundancy of the collated visibilities.
        if self.weight != "inverse_variance":
            redundancy = tools.calculate_redundancy(
                sstream.input_flags[:],
                sstream.index_map["prod"][:],
                sstream.reverse_map["stack"]["stack"][:],
                sstream.vis.shape[1],
            )

            if self.weight == "uniform":
                redundancy = (redundancy > 0).astype(np.float32)

        # De-reference distributed arrays outside loop to save repeated MPI calls
        ssv = sstream.vis[:]
        ssw = sstream.weight[:]

        # Unpack visibilities into new array
        for vis_ind, (p_ind, x_ind, y_ind) in enumerate(grid_index):

            # Handle different options for weighting baselines
            if self.weight == "inverse_variance":
                w = ssw[:, vis_ind]
            else:
                w = (ssw[:, vis_ind] > 0.0).astype(np.float32)
                w *= redundancy[np.newaxis, vis_ind]

            if x_ind != 0 or not self.exclude_intracyl:
                vis[:, p_ind, :, x_ind, y_ind] = ssv[:, vis_ind]
                invvar[:, p_ind, :, x_ind, y_ind] = ssw[:, vis_ind]
                weight[:, p_ind, :, x_ind, y_ind] = w

        # Remove auto-correlations
        if not self.include_auto:
            weight[..., 0, 0] = 0.0
        # Autos get double-counted at the end
        weight[..., 0, 0] *= 0.5

        # Normalize the weighting function
        # Multiply by 2 here to count negative baselines
        norm = 2 * np.sum(weight, axis=(-2, -1))
        weight *= tools.invert_no_zero(norm)[..., np.newaxis, np.newaxis]

        # Construct phase array
        el = self.span * np.linspace(-1.0, 1.0, self.npix)

        vis_pos_1d = np.fft.fftfreq(nvis_1d, d=(1.0 / (nvis_1d * min_ysep)))

        # Create empty ring map
        rm = containers.RingMap(
            beam=nbeam, el=el, pol=pol, ra=ra, axes_from=sstream, attrs_from=sstream
        )
        # Add datasets
        rm.add_dataset("rms")
        rm.add_dataset("dirty_beam")

        # Make sure ring map is distributed over frequency
        rm.redistribute("freq")

        # Estimate rms noise in the ring map by propagating estimates
        # of the variance in the visibilities
        rm.rms[:] = np.sqrt(
            2 * np.sum(tools.invert_no_zero(invvar) * weight**2.0, axis=(-2, -1))
        ).transpose(1, 0, 2)

        # Dereference datasets
        rmm = rm.map[:]
        rmb = rm.dirty_beam[:]

        # Pre-allocate arrays that will be reused inside loop
        save_nb = 1 if self.single_beam else nbeam
        pa = np.zeros(vis_pos_1d.shape + el.shape, dtype=vis.dtype)
        bfm_y = np.zeros((npol, nra, ncyl, self.npix), dtype=vis.dtype)
        sb_y = np.zeros((npol, nra, ncyl, self.npix), dtype=vis.dtype)
        bfm = np.zeros((npol, nra, save_nb, self.npix), dtype=vis.dtype)
        sb = np.zeros((npol, nra, save_nb, self.npix), dtype=vis.dtype)

        # Loop over local frequencies and fill ring map
        for lfi, fi in sstream.vis[:].enumerate(0):

            # Get the current frequency and wavelength
            fr = sstream.freq[fi]

            wv = scipy.constants.c * 1e-6 / fr

            # Inverse discrete Fourier transform in y-direction
            pa[:] = np.exp(
                (-2.0j * np.pi / wv * vis_pos_1d[:, np.newaxis]) * el[np.newaxis, :]
            )

            # Perform inverse discrete fourier transform in y-direction
            # and inverse fast fourier transform in x-direction
            bfm_y[:] = np.matmul(weight[lfi] * vis[lfi], pa)
            sb_y[:] = np.matmul(weight[lfi], pa)
            if self.single_beam:
                # Only need the 0th term if the irfft, equivalent to adding in EW
                # direction
                bfm[:] = np.sum(bfm_y, axis=2)[:, :, np.newaxis, ...]
                sb[:] = np.sum(sb_y, axis=2)[:, :, np.newaxis, ...]
            else:
                bfm[:] = np.fft.ifft(bfm_y, nbeam, axis=2) * nbeam
                sb[:] = np.fft.ifft(sb_y, nbeam, axis=2) * nbeam

            # Save to container (shifting to the final axis ordering)
            # for co-pol we take twice the real part
            # to complete sum over negative baselines
            copol_ind = [0, 3]
            rmm[:, copol_ind, lfi] = 2 * bfm[copol_ind].real.transpose(2, 0, 1, 3)
            rmb[:, copol_ind, lfi] = 2 * sb[copol_ind].real.transpose(2, 0, 1, 3)
            # for cross-pol we save real and imaginary parts of complex map formed
            # by combining positive and negative baselines (which are in the other index)
            xpol_ind = [1, 2]
            rmm[:, xpol_ind, lfi] = (
                (bfm[xpol_ind[0]] + bfm[xpol_ind[1]].conj())
                .view("(2,)float")
                .transpose(1, 3, 0, 2)
            )
            rmb[:, xpol_ind, lfi] = (
                (sb[xpol_ind[0]] + sb[xpol_ind[1]].conj())
                .view("(2,)float")
                .transpose(1, 3, 0, 2)
            )

        return rm
    def process(self, tstream, inputmap):
        """Apply the decorrelation correction for a given source.

        Parameters
        ----------
        tstream : andata.CorrData
            timestream data
        inputmap : list of :class:`CorrInput`
            A list of describing the inputs as they are in the file, output from
            `tools.get_correlator_inputs()`.

        Returns
        -------
        tstream : andata.CorrData
            Returns the corrected timestream.
        """

        tstream.redistribute("freq")

        prod_map = tstream.prodstack
        src = ephemeris.source_dictionary[self.source]

        # Rotate the telescope
        tools.change_chime_location(rotation=self.telescope_rotation)

        # correct visibilities
        corr_vis = tools.decorrelation(
            tstream.vis[:],
            times=tstream.time,
            feeds=inputmap,
            src=src,
            prod_map=prod_map,
            wterm=self.wterm,
            inplace=self.overwrite,
        )

        # weights are inverse variance
        weight = np.sqrt(invert_no_zero(tstream.weight[:]))
        weight = tools.decorrelation(
            weight,
            times=tstream.time,
            feeds=inputmap,
            src=src,
            prod_map=prod_map,
            wterm=self.wterm,
            inplace=True,
        )
        weight = invert_no_zero(weight) ** 2

        # Return telescope to default rotation
        tools.change_chime_location(default=True)

        if self.overwrite:
            # visibilities were corrected in place
            tstream.weight[:] = weight
            return tstream
        else:
            tstream_corr = containers.empty_like(tstream)
            tstream_corr.vis[:] = corr_vis
            tstream_corr.weight[:] = weight
            return tstream_corr