Polarimetric Analysis Walkthrough#
contact: menglei.zhou@astro.uni-tuebingen.de, honghui.liu@uni-tuebingen.de
0. Prerequisites#
Installation of the IXPE calibration database.
$ cd $CALDB
$ wget https://heasarc.gsfc.nasa.gov/FTP/caldb/data/ixpe/gpd/goodfiles_ixpe_gpd_20250225.tar.gz
$ wget https://heasarc.gsfc.nasa.gov/FTP/caldb/data/ixpe/xrt/goodfiles_ixpe_xrt_20241028.tar.gz
Installation of
ixpeobssimpackage using pip:pip install ixpeobssim. Note: Since ixpeobssim includes instrumental response files updated only until 2024-07-01, we recommend using the IXPE tools provided in HEASoft when processing observations taken after 2025-01-01.
1. Recommended documents and papers to review before working with IXPE data:#
Introduction to IXPE observational data: the IXPE quick start guide
Theoretical background on X-ray polarization: Kislat et al. (2015)
Weighted polarimetric analysis: Di Marco et al. (2022)
IXPE background treatment: Di Marco et al. (2023)
Review of IXPE results on black hole binaries: Dovčiak et al. (2024)
Review of IXPE results on X-ray pulsars: Poutanen et al. (2024)
2. Some theoretic backgrounds:#
2.1. How do we detect X-ray polarization?#
The distribution of emitting angle of the photoelectrons follows:
where \(p_0\) is the true polarization fraction, \(\psi_0\) is the true polarization angle, and \(\mu\) is the (energy-dependent) modulation factor.
2.2. Stokes Parameters#
\(\delta\) is the lag of \(E_y\) behind \(E_x\).
For a linearly polarized light, \(\delta = 0\), \(U = 2 E_x E_y\), \(V = 0\). (See Kislat et al. 2015)
For a single event, \(E_x = |\overrightarrow{E}| \cdot \cos \chi\), \(E_y = |\overrightarrow{E}| \cdot \sin \chi\), where \(|\overrightarrow{E}|\) is the strength of the electrical field.
With some simple arithmetics, we find:
For an observation with multiple photon events, the Stokes parameters are additive and will follow normal distributions.
Here we introduce the normalized Stokes parameters:
\(\text{PD} = \sqrt{Q^2 + U^2}/{I} \, , \quad \text{PA} = 0.5 \times \arctan (U/Q) \, .\)
Instead of collecting the PAs of each photon, it is more accurate and convenient to record the Stokes parameters of each photon.
2.3. The event list file of IXPE#
3. Dealing with image and background#
3.1. Defining the source region and the background region using DS9#
Source region: a circular region with a radius of 80 arcsec.
Background region: an annular region with an inner radius of 120 arcsec and an outer radius of 240 arcsec.
3.2. Background rejection and subtraction#
Please refer to Di Marco et al. (2023) for further details.
Background rejection := applying selection criteria to determine whether a photon event in the event list is of background origin. See aledimarco/IXPE-background.
Background subtraction := incorporating background spectra in XSPEC, or subtracting the background Stokes parameters when using pcube or ixpepolarization.
$ python filter_background.py ixpe02004001_det1_evt2_v01.fits ../event_l1/ixpe02004001_det1_evt1_v01.fits.gz --output rej
$ python filter_background.py ixpe02004001_det1_evt2_v01.fits ../event_l1/ixpe02004001_det1_evt1_v01.fits.gz --output bkg
From this example, we see that even after background rejection, background subtraction can still be applied…
4. The model-independent method: pcube and ixpepolarization#
4.1. The pcube method provided by the ixpeobssim package#
First step: region filtering
$ xpselect --suffix src --regfile src.reg ixpe02004001_det1_evt2_v01.fits
$ xpselect --suffix src --regfile src.reg ixpe02004001_det2_evt2_v01.fits
$ xpselect --suffix src --regfile src.reg ixpe02004001_det3_evt2_v01.fits
$ xpselect --suffix bkg --regfile bkg.reg ixpe02004001_det1_evt2_v01.fits
$ xpselect --suffix bkg --regfile bkg.reg ixpe02004001_det2_evt2_v01.fits
$ xpselect --suffix bkg --regfile bkg.reg ixpe02004001_det3_evt2_v01.fits
and we now have region-filtered event list files for 3 DUs.
Please use ds9 to check the shape of the filtered event files!
run
xpbinto calculate the polarization degree (PD) and polarization angle (PA)
$ xpbin --algorithm PCUBE --irfname ixpe:obssim20230101_alpha075:v13 --acceptcorr True --ebins 1 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_src.fits
$ xpbin --algorithm PCUBE --irfname ixpe:obssim20230101_alpha075:v13 --acceptcorr True --ebins 1 --weights True --weightcol W_MOM ixpe02004001_det2_evt2_v01_src.fits
$ xpbin --algorithm PCUBE --irfname ixpe:obssim20230101_alpha075:v13 --acceptcorr True --ebins 1 --weights True --weightcol W_MOM ixpe02004001_det3_evt2_v01_src.fits
The argument of irfname:
if weights=False and greyfilter=False, irfname=ixpe:obssim20230101:v13;
if weights=True and greyfilter=False, irfname=ixpe:obssim20230101_alpha075:v13;
if weights=False and greyfilter=True, irfname=ixpe:obssim20230101_grey:v13;
if weights=True and greyfilter=True, irfname=ixpe:obssim20230101_alpha075_grey:v13;
Possible dates: 20211209, 20220702, 20230101, 20230702, 20240101, 20240701.
For example, if your observation was conducted between 2 February 2023 and 8 February 2023, the appropriate date for the instrumental response file is 20230101.
4.2. The ixpepolarization tool#
Please see https://heasarc.gsfc.nasa.gov/docs/software/lheasoft/help/ixpepolarization.py.html for further details.
$ ixpepolarization infile1=ixpe02004001_det1_evt2_v01.fits infile2=ixpe02004001_det2_evt2_v01.fits infile3=ixpe02004001_det3_evt2_v01.fits regfile=src.reg weight_scheme=neff outfile=- clobber=yes
$ ixpeplot_polarization infile=ixpepolarization_out.fits
5. The model-dependent method: extracting IXPE spectra and fitting in Xspec#
5.1. Extraction with Xselect (recommended for more accurate ARF computation, especially for observations after 2025-01-01)#
Use
Xselectto extract the spectra with region filter.
$ xselect
** XSELECT V2.5c **
> Enter session name >[xsel22118] Her X-1
Her_X-1:XRISM > read event ixpe02004001_det1_evt2_v01.fits.gz
> Enter the Event file dir >[./22118_tmp_numkrmf/] ./
Got new mission: IXPE
> Reset the mission ? >[yes] yes
Her_X-1:IXPE-DU1 > filter region "src.reg"
Her_X-1:IXPE-DU1 > extract SPEC stokes=NEFF
Her_X-1:IXPE-DU1 > save spec ixpe_det1_src_
Her_X-1:IXPE-DU1 > clear region
Her_X-1:IXPE-DU1 > filter region "bkg.reg"
Her_X-1:IXPE-DU1 > extract SPEC stokes=NEFF
Her_X-1:IXPE-DU1 > save spec ixpe_det1_bkg_
Her_X-1:IXPE-DU1 > exit
> Save this session? >[no] no
And use
ixpecalcarfto generate the appropriate instrumental effective area (assuming the source is on-axis)…
Please go to https://heasarc.gsfc.nasa.gov/docs/software/lheasoft/help/ixpecalcarf.py.html for more details.
#!/bin/bash
ixpecalcarf evtfile=ixpe02004001_det1_evt2_v01.fits attfiles=../hk/ixpe02004001_det1_att_v01.fits.gz arfout=ixpe_det1_src_II.arf specfile=none radius=1.3333333 weight=1 resptype=arf clobber=yes
ixpecalcarf evtfile=ixpe02004001_det1_evt2_v01.fits attfiles=../hk/ixpe02004001_det1_att_v01.fits.gz arfout=ixpe_det1_src_QU.mrf specfile=none radius=1.3333333 weight=1 resptype=mrf clobber=yes
ixpecalcarf evtfile=ixpe02004001_det2_evt2_v01.fits attfiles=../hk/ixpe02004001_det2_att_v01.fits.gz arfout=ixpe_det2_src_II.arf specfile=none radius=1.3333333 weight=1 resptype=arf clobber=yes
ixpecalcarf evtfile=ixpe02004001_det2_evt2_v01.fits attfiles=../hk/ixpe02004001_det2_att_v01.fits.gz arfout=ixpe_det2_src_QU.mrf specfile=none radius=1.3333333 weight=1 resptype=mrf clobber=yes
ixpecalcarf evtfile=ixpe02004001_det3_evt2_v01.fits attfiles=../hk/ixpe02004001_det3_att_v01.fits.gz arfout=ixpe_det3_src_II.arf specfile=none radius=1.3333333 weight=1 resptype=arf clobber=yes
ixpecalcarf evtfile=ixpe02004001_det3_evt2_v01.fits attfiles=../hk/ixpe02004001_det3_att_v01.fits.gz arfout=ixpe_det3_src_QU.mrf specfile=none radius=1.3333333 weight=1 resptype=mrf clobber=yes
Write the response file information into the spectrum headers.
Use the following code to check the correct response matrix file:
$ quzcif IXPE GPD DU1 - MATRIX 2023-02-02 12:00:00.0 -
$ quzcif IXPE GPD DU2 - MATRIX 2023-02-02 12:00:00.0 -
$ quzcif IXPE GPD DU3 - MATRIX 2023-02-02 12:00:00.0 -
ixpe_d1_20211209_01.rmf -> Unweighted method;
ixpe_d1_20211209_01_alpha075.rmf -> Weighted methods (NEFF / SIMPLE)
$ fthedit ixpe_det1_src_I.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d1_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det1_src_I.pha keyword=ANCRFILE operation=add value=" 'ixpe_det1_src_II.arf' " longstring=YES
$ fthedit ixpe_det1_src_Q.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d1_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det1_src_Q.pha keyword=ANCRFILE operation=add value=" 'ixpe_det1_src_QU.mrf' " longstring=YES
$ fthedit ixpe_det1_src_U.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d1_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det1_src_U.pha keyword=ANCRFILE operation=add value=" 'ixpe_det1_src_QU.mrf' " longstring=YES
$ fthedit ixpe_det2_src_I.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d2_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det2_src_I.pha keyword=ANCRFILE operation=add value=" 'ixpe_det2_src_II.arf' " longstring=YES
$ fthedit ixpe_det2_src_Q.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d2_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det2_src_Q.pha keyword=ANCRFILE operation=add value=" 'ixpe_det2_src_QU.mrf' " longstring=YES
$ fthedit ixpe_det2_src_U.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d2_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det2_src_U.pha keyword=ANCRFILE operation=add value=" 'ixpe_det2_src_QU.mrf' " longstring=YES
$ fthedit ixpe_det3_src_I.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d3_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det3_src_I.pha keyword=ANCRFILE operation=add value=" 'ixpe_det3_src_II.arf' " longstring=YES
$ fthedit ixpe_det3_src_Q.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d3_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det3_src_Q.pha keyword=ANCRFILE operation=add value=" 'ixpe_det3_src_QU.mrf' " longstring=YES
$ fthedit ixpe_det3_src_U.pha keyword=RESPFILE operation=add value=" '$CALDB/data/ixpe/gpd/cpf/rmf/ixpe_d3_20211209_alpha075_01.rmf' " longstring=YES
$ fthedit ixpe_det3_src_U.pha keyword=ANCRFILE operation=add value=" 'ixpe_det3_src_QU.mrf' " longstring=YES
Group the source spectra: the standard convention is to require at least 30 photons per bin for the I spectra. The Q/U spectra should follow the same grouping configuration.
See the python script below for an example.
import numpy as np
from astropy.io import fits as fits
from astropy.table import Table, Column
spec_type_list1 =['I', 'Q', 'U']
spec_type_list2 =['Q', 'U']
def group_photons(counts, min_count_value):
grouping_info = np.zeros_like(counts)
grouping_info[0] = 1
sum_counts = counts[0]
for ii in np.arange(1, len(counts)):
sum_counts += counts[ii]
if sum_counts >= min_count_value:
grouping_info[ii] = 1
sum_counts = 0
else:
grouping_info[ii] = -1
return grouping_info
for det_id in [1, 2, 3]:
spec_string = 'ixpe_det{0:}_src'.format(det_id)
### Create the new grouping column ###
pha1_spec_path = '{0:}_I.pha'.format(spec_string)
pha1_spec_path_new = '{0:}_I_grp.pha'.format(spec_string)
pha1_spec = fits.open(pha1_spec_path)
pha1_spec_counts = pha1_spec['SPECTRUM'].data['RATE']*pha1_spec['SPECTRUM'].header['EXPOSURE']
print(pha1_spec['SPECTRUM'].header['EXPOSURE'])
grouping_info = group_photons(pha1_spec_counts, 30.0)
spec_hdu = pha1_spec['SPECTRUM']
data = spec_hdu.data
table = Table(data)
group_column = Column(grouping_info, name='GROUPING', format='I')
table.add_column(group_column)
new_spec_hdu = fits.BinTableHDU(data=table, header=spec_hdu.header)
new_hdul = fits.HDUList([pha1_spec[0], new_spec_hdu])
new_hdul.writeto(pha1_spec_path_new, overwrite=True)
pha1_spec.close()
### Create the new grouping column END ###
grp_ref_spectrum_path = pha1_spec_path_new
for spec_type in spec_type_list2:
spectrum_path = '{0:}_{1:}.pha'.format(spec_string, spec_type)
spectrum_path_new = '{0:}_{1:}_grp.pha'.format(spec_string, spec_type)
grp_ref_spectrum = fits.open(grp_ref_spectrum_path)
spectrum = fits.open(spectrum_path)
grouping_info = grp_ref_spectrum['SPECTRUM'].data['GROUPING']
spectrum_hdu = spectrum['SPECTRUM']
data = spectrum_hdu.data
header = spectrum_hdu.header
table = Table(data)
grouping_column = Column(grouping_info, name='GROUPING')
table.add_column(grouping_column)
new_spec = fits.BinTableHDU(data=table, header=header)
new_hdul = fits.HDUList([spectrum[0], new_spec])
new_hdul.writeto(spectrum_path_new, overwrite=True)
spectrum.close()
grp_ref_spectrum.close()
5.2. Extraction with ixpeobssim (recommended for quick analyses and for observations prior to 2025-01-01)#
extract the source spectra for I, Q, U parameters for DU1:
$ xpbin --algorithm PHA1 --irfname ixpe:obssim20230101_alpha075:v13 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_src.fits
$ xpbin --algorithm PHA1Q --irfname ixpe:obssim20230101_alpha075:v13 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_src.fits
$ xpbin --algorithm PHA1U --irfname ixpe:obssim20230101_alpha075:v13 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_src.fits
… and background:
$ xpbin --algorithm PHA1 --irfname ixpe:obssim20230101_alpha075:v13 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_bkg.fits
$ xpbin --algorithm PHA1Q --irfname ixpe:obssim20230101_alpha075:v13 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_bkg.fits
$ xpbin --algorithm PHA1U --irfname ixpe:obssim20230101_alpha075:v13 --weights True --weightcol W_MOM ixpe02004001_det1_evt2_v01_bkg.fits
And do the same for DU2 and 3. At last, we should have 6 spectral files for each DU, i.e. the I Q U spectra of the source / background.
The .rmf files and .arf files are automatically assigned in the header of the spectra.
5.3. Fitting the IXPE spectra in Xspec#
XSPEC12>
data 1:1 ixpe_det1_src_I_grp.pha
data 1:2 ixpe_det1_src_Q_grp.pha
data 1:3 ixpe_det1_src_U_grp.pha
data 2:4 ixpe_det2_src_I_grp.pha
data 2:5 ixpe_det2_src_Q_grp.pha
data 2:6 ixpe_det2_src_U_grp.pha
data 3:7 ixpe_det3_src_I_grp.pha
data 3:8 ixpe_det3_src_Q_grp.pha
data 3:9 ixpe_det3_src_U_grp.pha
(background subtraction is optional, depending on the brightness of the source)
backgrnd 1 ixpe_det1_bkg_I.pha
backgrnd 2 ixpe_det1_bkg_Q.pha
backgrnd 3 ixpe_det1_bkg_U.pha
backgrnd 4 ixpe_det2_bkg_I.pha
backgrnd 5 ixpe_det2_bkg_Q.pha
backgrnd 6 ixpe_det2_bkg_U.pha
backgrnd 7 ixpe_det3_bkg_I.pha
backgrnd 8 ixpe_det3_bkg_Q.pha
backgrnd 9 ixpe_det3_bkg_U.pha
ignore 1-9: **-2.0 8.0-**
model constant*TBabs*(powerlaw*polconst + gaussian*polconst)