Fitting for a point source polarization with LeakageLib¶
Weights used:
Spatial
Spectral
This jupyter notebook fits for the polarization of a single simulated point source, with a photon and particle background. See other notebooks for how to use the more advanced weights.
[1]:
import leakagelib
>>> PyXSPEC is not installed, you will no be able to use it.
First, we need to load the data. See the next notebook for how to download and prepare real IXPE data. This notebook uses simulated data I made, which is located in <LEAKAGELIB>/docs/examples/data/ps. This notebook also comes downloaded with LeakageLib, located at <LEAKAGELIB>/docs/examples/fit-point-source.ipynb. If you run that notebook, the following load command should work.
[2]:
datas = leakagelib.IXPEData.load_all_detectors_with_path("data", "ps")
>>> Reading (in memory) /opt/homebrew/anaconda3/lib/python3.12/site-packages/ixpeobssim/caldb/ixpe/xrt/bcf/vign/ixpe_d1_obssim20240101_vign_v013.fits...
Alternatives to IXPEData.load_all_detectors_with_path
IXPEData.load_all_detectors. Searches all directories listed in the DATA_DIRECTORIES variable defined in settings.py for the given obs id, and load all detectors.IXPEData.load_all_detectors_with_path. Provide a path to the observation and the function will load all detectors. This will not use the DATA_DIRECTORIES variable.IXPEData. You must pass specific file names.
Next we center the data and cut to 2-8 keV.
[3]:
for data in datas:
data.iterative_centroid_center()
data.retain(data.evt_energies > 2)
data.retain(data.evt_energies < 8)
Alternatives to IXPEData.iterative_centroid_center
IXPEData.explicit_center: Center on a specific point, in pixels.IXPEData.iterative_centroid_center: Center by iteratively zooming in on the average event. This finds central PSs rather well.
Now we have to tell the fitter how we want to do the fit. All this information is stored in the FitSettings object. First, we tell it to only consider events within 280 arcseconds of the center. More distant events will automatically be removed from the data sets.
[4]:
settings = leakagelib.FitSettings(datas)
settings.apply_circular_roi(280)
6282 events were cut for being outside the region of interest.
Alternatives to FitSettings.apply_circular_roi
IXPEData.apply_circular_roi: Creates a circular ROI centered on the originIXPEData.apply_roi_cut: Adds a region file to the ROI (Or excludes it if you setexclude=True)IXPEData.apply_roi: Apply an arbitrary ROI, which you pass in as an image. The image must have the same pixel size and dimensions as the fit sources.
You might be curious how many events are left. You can check this here:
[5]:
sum([len(data) for data in datas])
[5]:
15894
For this simplified scenario there are two components: a point source and a photon background. To create the point source,
[6]:
settings.add_point_source() # Named "src" by default
settings.fix_flux("src", 1)
settings.set_initial_qu("src", (0.5,0))
Without loss of generality, we have defined the flux to be in units of the source flux.
You can use FitSettings.set_initial_qu to set the start point of the fitter. It’s important to set the start point as close to the true value as possible to avoid falling into the wrong maximum of the likelihood.
Now we create the background models. These background sources are assumed to be spatially uniform and already have spatial weights applied.
[7]:
settings.add_background() # Named "bkg" by default
settings.fix_qu("bkg", (0, 0)) # Assume an unpolarized background
To use energy weights, we must assign a spectrum. We create functions which take in the energy in keV and return the photon spectrum (dN/dE). Normalization doesn’t matter. The ARF and RMF are automatically included, but you can disable them with the use_arf and use_rmf arguments.
[8]:
settings.set_spectrum("bkg", lambda e: e**-2.5) # Gamma=2.5, unabsorbed powerlaw
settings.set_spectrum("src", lambda e: e**-1.5) # Gamma=1.5, unabsorbed powerlaw
>>> Reading (in memory) /opt/homebrew/anaconda3/lib/python3.12/site-packages/ixpeobssim/caldb/ixpe/gpd/cpf/arf/ixpe_d1_obssim20240101_v013.arf...
>>> Using cached xEffectiveArea object at /opt/homebrew/anaconda3/lib/python3.12/site-packages/ixpeobssim/caldb/ixpe/gpd/cpf/arf/ixpe_d1_obssim20240101_v013.arf...
We have assigned all the weights. Now we are ready to create the fitter…
[9]:
fitter = leakagelib.Fitter(settings)
Data set ps DU 1 had no exposure map loaded. Please load an exposure map if you are fitting to events in the vignetted portion.
… and run the fit
[10]:
result = fitter.fit()
result
[10]:
FitResult:
q (src) = 0.4697 +/- 0.0725
u (src) = -0.0260 +/- 0.0721
f (bkg) = 2.1813 +/- 0.0423
Polarization:
PD (src): 0.4704 +/- 0.0725
PA (src): -1.5836 deg +/- 4.3936
Likelihood 17886.60119278403, dof 15891
Optimization terminated successfully.
The true polarization of this simulated source was q = 0.5, u = 0.0. The fit result agrees.
Alternatives to Fitter.fit
Fitter.fit: Fit by numerically maximizing the likelihood and get Gaussian uncertainties by taking the Hessian and using Laplace’s approximation.IXPEData.fit_mcmc: Do a full MCMC fit.
You can access result.params and result.sigmas to get the best-fit values and uncertainties. Also result.cov to get the full covariance matrix. The covariance matrix entries are stored in the same order as the result.parameter_names array.
[11]:
print("===Results as human-readable dictionaries===")
print("Parameters", result.params)
print("Uncertainties", result.sigmas)
print()
print("===Results as arrays===")
print("Parameter names", result.parameter_names)
print("Best fit values", result.best_params)
print("Covariance", result.cov)
===Results as human-readable dictionaries===
Parameters {('q', 'src'): 0.46965223111815946, ('u', 'src'): -0.025987502310235725, ('f', 'bkg'): 2.1813398855067225}
Uncertainties {('q', 'src'): 0.07247836727152544, ('u', 'src'): 0.07213767036302343, ('f', 'bkg'): 0.0423476862090614}
===Results as arrays===
Parameter names [('q', 'src'), ('u', 'src'), ('f', 'bkg')]
Best fit values [ 0.46965223 -0.0259875 2.18133989]
Covariance [[ 5.25311372e-03 -1.73106799e-05 0.00000000e+00]
[-1.73106799e-05 5.20384349e-03 0.00000000e+00]
[ 0.00000000e+00 0.00000000e+00 1.79332653e-03]]