Faraday rotation has been extensively used to probe the physical properties of astronomical objects such as the solar corona, H ii (star-forming) regions, and the interstellar, intergalactic, and intracluster mediums. However, if the signal is comprised of more than one radio source, then standard analysis using a single component can produce results that poorly reflect the underlining signals (Farnsworth et al, 2011).
The goal of this project was to classify Faraday sources as being simple or "complex" for the POSSUM (Polarisation Sky Survey of the Universe's Magnetism) survey, which is part of the Australian SKA Pathfinder (ASKAP). Shea Brown came up with the idea for the project, while Jacob Isbell, Daniel LaRocca, and I worked together on most of the programming and analysis. This repository is meant to provide an introduction with a streamlined code implementation.
The polarization that astronomers measure is often represented as a complex number, with
where
where
Faraday rotation is often parameterized by the rotation measure (RM), which measures the relationship between the wavelength and polarization angle
This linearity, however, is only applicable for simple cases and if there is more than one source the linear relationship will often break down. Two additional problems are the
where
In practice we only observe over a portion of the wavelength space. To generalize the relationship between the Faraday dispersion and polarization, we can introduce a window function
and our Fourier relationships hold but for the quantities
For POSSUM, this convolution results in a FWHM of
Finally, the Faraday dispersion is generally calculated using a discrete approximation
where
Complex sources can sometimes create issues, however, where the RM derived from RM synthesis can be well fit by a simple model that doesn't characterize the individual components nor their mean while also underestimating the uncertainty. Thus separating simple and complex sources in large automated surveys can be helpful for improving the accuracy of scientific studies.
For this project we constructed synthetic data designed to mimic observations taken by the POSSUM survey. We considered two cases, spectra consisting of a single Faraday source and those consisting of two Faraday sources. For the complex (two-component) case, we generate a polarization spectrum for each source and then add them together:
For each Polarization spectrum we then added random noise to the real and imaginary components, assuming the noise is independent of the frequency.
As an example, the following code snippet shows how to generate a noisy polarization spectrum for a single source using the ASKAP 12 coverage, which we show in the Figure below:
import matplotlib.pyplot as plt
import numpy as np
from possum.coverage import ASKAP12
from possum.polarization import createPolarization, addPolarizationNoise
np.random.seed(0)
nu = ASKAP12(); MHz=nu/1e6
p = createPolarization(nu=nu, chi=0, depth=20, amplitude=1)
p = addPolarizationNoise(polarization=p, sigma=0.2)
fig, ax = plt.subplots(figsize=(8,4))
ax.scatter(MHz, p.real, label='$Q$ (real)', s=5)
ax.scatter(MHz, p.imag, label='$U$ (imag)', s=5)
ax.legend(loc='lower right', frameon=False)
ax.set_xlabel(r'$\nu$ (MHz)')
ax.set_ylabel(r'$P_{\nu}$ (Jy/beam)')
fig.tight_layout()
fig.show()
While the polarization coverage has a wavelength gap around 1400 MHz, we can form a continuous sequence by transforming it into a Faraday spectrum as shown in the following code snippet and graph. Note that the amplitude of the signal peaks at the Faraday depth of the source
from possum.faraday import createFaraday
phi = np.linspace(-50,50,100)
f = createFaraday(nu=nu, polarization=p, phi=phi)
fig, ax = plt.subplots(figsize=(8,4))
ax.plot(phi, abs(f), label='abs')
ax.plot(phi, f.real, label='real')
ax.plot(phi, f.imag, label='imag')
ax.legend(loc='lower right', frameon=False)
ax.set_xlabel(r'$\phi$ (rad m$^{-2}$)')
ax.set_ylabel(r'$P_{\nu}$ (Jy/beam)')
fig.tight_layout()
fig.show()
To create a complex source, we can pass in an iterable for the polarization parameters, as illustrated in the following code block. Note that in this case the amplitude peaks are a bit offset from the location of the individual Faraday sources (represented by the dashed vertical lines) while also being shifted away from their mean value.
params = {'chi': [0,np.pi], 'depth': [-15,5], 'amplitude': [1,0.75]}
p = createPolarization(nu=nu, **params)
p = addPolarizationNoise(polarization=p, sigma=0.2)
f = createFaraday(nu=nu, polarization=p, phi=phi)
fig, ax = plt.subplots(figsize=(8,4))
ax.plot(phi, abs(f), label='abs')
ax.plot(phi, f.real, label='real')
ax.plot(phi, f.imag, label='imag')
ax.legend(loc='lower right', frameon=False)
ax.set_xlabel(r'$\phi$ (rad m$^{-2}$)')
ax.set_ylabel(r'$P_{\nu}$ (Jy/beam)')
ax.set_ylim(-1.15, 1.15)
ax.vlines(params['depth'], ymin=-1.15, ymax=1.15, ls='--', color='black', alpha=0.5)
fig.tight_layout()
fig.show()
In our previous examples we added white (Gaussian) noise to our polarization spectrum. In general one would expect the noise in a channel to be correlated with the noise of its neighboring channels. One way of incorporating this is to use a Gaussian process. In the example below we utilize the george package to add correlated noise to our polarization spectrum:
import george
import matplotlib.pyplot as plt
import numpy as np
from possum.coverage import ASKAP12
from possum.polarization import createPolarization
from possum.faraday import createFaraday
def addGaussianProcessNoise(
polarization:np.ndarray,
nu:np.ndarray,
kernel:callable,
):
gp = george.GP(kernel)
noise = gp.sample(nu) + 1j*gp.sample(nu)
return polarization + noise
np.random.seed(0)
nu = ASKAP12(); MHz = nu/1e6
p = createPolarization(nu=nu, chi=0, depth=20, amplitude=1)
gp = addGaussianProcessNoise(polarization=p, nu=MHz, kernel=0.1*george.kernels.ExpSquaredKernel(250))
phi = np.linspace(-50,50,100)
f = createFaraday(nu=nu, phi=phi, polarization=p)
f_gp = createFaraday(nu=nu, phi=phi, polarization=gp)
fig, ax = plt.subplots(nrows=2, figsize=(8,8))
ax[0].scatter(MHz, p.real, label='$Q$ (real)', s=2, color='tab:blue')
ax[0].scatter(MHz, p.imag, label='$U$ (imag)', s=2, color='tab:orange')
ax[0].scatter(MHz, gp.real, s=2, color='tab:blue', alpha=0.5)
ax[0].scatter(MHz, gp.imag, s=2, color='tab:orange', alpha=0.5)
ax[0].legend(loc='lower right', frameon=False)
ax[0].set_xlabel(r'$\nu$ (MHz)')
ax[0].set_ylabel(r'$P_{\nu}$ (Jy/beam)')
ax[1].plot(phi, abs(f), label='abs', color='tab:blue')
ax[1].plot(phi, abs(f_gp), color='tab:blue', alpha=0.5, linestyle='--')
ax[1].plot(phi, f.real, label='real', color='tab:orange')
ax[1].plot(phi, f_gp.real, color='tab:orange', alpha=0.5, linestyle='--')
ax[1].plot(phi, f.imag, label='imag', color='tab:green')
ax[1].plot(phi, f_gp.imag, color='tab:green', alpha=0.5, linestyle='--')
ax[1].legend(loc='lower right', frameon=False)
ax[1].set_xlabel(r'$\phi$ (rad m$^{-2}$)')
ax[1].set_ylabel(r'$P_{\nu}$ (Jy/beam)')
ax[1].set_ylim(-1.15, 1.15)
fig.tight_layout()
fig.show()
We'll stick with white noise in our examples, but include this example for those interested in better modeling the noise.
A demonstration of training our network is given in our Jupyter notebook. Here, we'll provide a brief exploratory analysis of the performance on complex sources after training a CNN for a little while.
We'll begin by plotting a comparison of network probablity against the noise level, amplitude ratio, offset in polarization angle, and offset in Faraday depth:
The strongest dependency appears to be the amplitude ratio, with the network starting to struggle to detect complex sources when the amplitude of the secondary component drops below 1/5 of the primary component. There is also a bit of a dependence on the offset in Faraday depth in that as the two sources become close together it is difficult to distinguish between the two components. This is not surprising, as the two sources effectively act as a single source in this regime.
Since the amplitude ratio and the offset in Faraday depth appear to be the two most important quantities, we can explore how the performance is correlated with both parameters by computing the average output probability in each spatial bin:
As expected, the network does a good job at classifying complex sources when their amplitudes are within an order of magnitude of each other and their Faraday depths are sufficiently offset from one another.