A Quick Start Tutorial#
This tutorial provides a quick start guide to using ELISA to fit a spectral model to X-ray spectral data. The tutorial is divided into three sections:
Load Data and Define Spectral Model
Bayesian Fit
Maximum Likelihood Fit
The data used in this tutorial is from the Insight-HXMT observation of the Crab Nebula. The spectra are obtained by the Low Energy (LE), the Medium Energy (ME), and the High Energy (HE) telescopes. The spectral model used in this tutorial is a power-law model modified by a photoelectric absorption.
The tutorial demonstrates how to fit the spectral model to the data using both Bayesian and Maximum Likelihood methods.
Tip
By default, ELISA uses up to 4 CPU cores for parallel computation when available. To change the number of CPU cores in use, place the following at the beginning of your script:
import elisa
elisa.set_cpu_cores(8) # Set to 8 CPU cores for example
1. Load Data and Define Spectral Model#
from elisa import BayesFit, Data, MaxLikeFit
from elisa.models import PhAbs, PowerLaw
LE = Data(
erange=[1.5, 10],
specfile='data/P011160506306_LE_TOTAL.fits',
backfile='data/P011160506306_LE_BKG.fits',
respfile='data/P011160506306_LE_RSP.fits',
group='opt',
)
ME = Data(
erange=[10, 35],
specfile='data/P011160506306_ME_TOTAL.fits',
backfile='data/P011160506306_ME_BKG.fits',
respfile='data/P011160506306_ME_RSP.fits',
group='opt',
)
HE = Data(
erange=[28, 250],
specfile='data/P011160506306_HE_TOTAL.fits',
backfile='data/P011160506306_HE_BKG.fits',
respfile='data/P011160506306_HE_RSP.fits',
group='opt',
)
data = [LE, ME, HE]
model = PhAbs() * PowerLaw()
/home/docs/checkouts/readthedocs.org/user_builds/astro-elisa/conda/stable/lib/python3.12/site-packages/elisa/util/config.py:80: Warning: only 2 CPUs available, will use 1 CPUs
warnings.warn(msg, Warning)
/tmp/ipykernel_3302/1226189312.py:12: Warning: spectrum data/P011160506306_ME_BKG.fits has zero statistical errors, which may lead to wrong result under Gaussian statistics, consider grouping the spectrum
ME = Data(
/tmp/ipykernel_3302/1226189312.py:20: Warning: spectrum data/P011160506306_HE_BKG.fits has zero statistical errors, which may lead to wrong result under Gaussian statistics, consider grouping the spectrum
HE = Data(
2. Bayesian Fit#
Bayesian Fit
| Data | Model | Statistic |
|---|---|---|
| LE | PhAbs * PowerLaw | pgstat |
| ME | PhAbs * PowerLaw | pgstat |
| HE | PhAbs * PowerLaw | pgstat |
| No. | Component | Parameter | Value | Prior |
|---|---|---|---|---|
| 1 | PhAbs | nH | 1 | Uniform(0, 1e+06) |
| 2 | PowerLaw | alpha | 1.01 | Uniform(-3, 10) |
| 3 | PowerLaw | K | 1 | Uniform(1e-10, 1e+10) |
Run the No-U-Turn Sampler#
/home/docs/checkouts/readthedocs.org/user_builds/astro-elisa/conda/stable/lib/python3.12/site-packages/elisa/infer/fit.py:845: UserWarning: There are not enough devices to run parallel chains: expected 4 but got 1. Chains will be drawn sequentially. If you are running MCMC in CPU, consider using `numpyro.set_host_device_count(4)` at the beginning of your program. You can double-check how many devices are available in your system using `jax.local_device_count()`.
sampler = MCMC(
Posterior Result
Parameters
| Parameter | Mean | StdDev | Median | 68.3% Quantile | ESS | Rhat |
|---|---|---|---|---|---|---|
| PhAbs.nH | 0.36 | 0.0146 | 0.36 | [-0.0145, 0.0148] | 8279 | 1.00 |
| PowerLaw.alpha | 2.12 | 0.00352 | 2.12 | [-0.00351, 0.00354] | 9303 | 1.00 |
| PowerLaw.K | 8.94 | 0.0737 | 8.94 | [-0.0747, 0.0756] | 8478 | 1.00 |
Statistics
| Data | Statistic | Mean | Median | Channels |
|---|---|---|---|---|
| LE | pgstat | 95.56 | 95.07 | 80 |
| ME | pgstat | 8.01 | 7.74 | 16 |
| HE | pgstat | 43.90 | 43.56 | 25 |
| Total | stat/dof | 147.47/118 | 146.86/118 | 121 |
Information Criterion
| Method | Deviance | p |
|---|---|---|
| LOOIC | 150.04 ± 19.55 | 2.43 |
| WAIC | 150.01 ± 19.55 | 2.41 |
Pareto k Diagnostic
| Range | Flag | Count | Pct. |
|---|---|---|---|
| (-Inf, 0.70] | good | 121 | 100.0% |
| (0.70, 1] | bad | 0 | 0.0% |
| (1, Inf) | very bad | 0 | 0.0% |
Plot the Trace of Sampler#
fig = posterior.plot.plot_trace()
Plot of Spectral Fit and Residuals#
fig = posterior.plot()
Goodness of Fit: Quantile-Quantile Plot#
fig = posterior.plot.plot_qq(detrend=False)
Goodness of Fit: Probability Integral Transform ECDF Plot#
fig = posterior.plot.plot_pit(detrend=False)
Plot Marginal Posterior Distribution#
fig = posterior.plot.plot_corner()
Calculate Credible Intervals of Parameters#
ci = posterior.ci()
ci
CredibleInterval(median={'PhAbs.nH': 0.3599347091723838, 'PowerLaw.alpha': 2.11780831489098, 'PowerLaw.K': 8.935146713420071}, intervals={'PhAbs.nH': (0.34544273675717413, 0.37476861164766095), 'PowerLaw.alpha': (2.114306944251399, 2.121347555366423), 'PowerLaw.K': (8.860524875192109, 9.010709474604115)}, errors={'PhAbs.nH': (-0.014491972415209664, 0.014833902475277161), 'PowerLaw.alpha': (-0.003501370639581225, 0.003539240475443073), 'PowerLaw.K': (-0.0746218382279622, 0.07556276118404348)}, cl=np.float64(0.6826894921370859), method='ETI', dist={'PhAbs.nH': array([[0.35599069, 0.35933968, 0.36160596, ..., 0.3708304 , 0.35782115,
0.36095718],
[0.35528676, 0.34862422, 0.34748824, ..., 0.37397776, 0.37166695,
0.37245345],
[0.36833903, 0.36656733, 0.37639379, ..., 0.37693046, 0.37439426,
0.36380988],
[0.34456671, 0.36569141, 0.36952102, ..., 0.321445 , 0.32002548,
0.3235731 ]], shape=(4, 5000)), 'PowerLaw.alpha': array([[2.12135061, 2.11642621, 2.11667096, ..., 2.11951824, 2.11330331,
2.11869482],
[2.11575139, 2.1150868 , 2.11572526, ..., 2.11945612, 2.1180253 ,
2.12061482],
[2.1224765 , 2.12308707, 2.12107562, ..., 2.11869906, 2.11619376,
2.11957866],
[2.11512447, 2.12078232, 2.12024589, ..., 2.10856113, 2.10708222,
2.10780183]], shape=(4, 5000)), 'PowerLaw.K': array([[9.00119382, 8.90677693, 8.94729485, ..., 8.96129822, 8.85058949,
8.95147667],
[8.90703781, 8.86515411, 8.85707757, ..., 8.99782112, 8.97726857,
8.98169099],
[9.04213766, 9.03116423, 8.98838241, ..., 8.95963217, 8.95976431,
8.93037148],
[8.87988565, 9.01017246, 9.01839077, ..., 8.71542846, 8.71237327,
8.71145156]], shape=(4, 5000))})
ci.median
{'PhAbs.nH': 0.3599347091723838,
'PowerLaw.alpha': 2.11780831489098,
'PowerLaw.K': 8.935146713420071}
ci.errors
{'PhAbs.nH': (-0.014491972415209664, 0.014833902475277161),
'PowerLaw.alpha': (-0.003501370639581225, 0.003539240475443073),
'PowerLaw.K': (-0.0746218382279622, 0.07556276118404348)}
3. Maximum Likelihood Fit#
Maximum Likelihood Fit
| Data | Model | Statistic |
|---|---|---|
| LE | PhAbs * PowerLaw | pgstat |
| ME | PhAbs * PowerLaw | pgstat |
| HE | PhAbs * PowerLaw | pgstat |
| No. | Component | Parameter | Value | Bound |
|---|---|---|---|---|
| 1 | PhAbs | nH | 0.35 | (0, 1e+06) |
| 2 | PowerLaw | alpha | 1.01 | (-3, 10) |
| 3 | PowerLaw | K | 1 | (1e-10, 1e+10) |
Use Levenberg-Marquardt algorithm to find the MLE#
mle = fit2.mle(method='lm')
mle
MLE Result
Parameters
| Parameter | MLE | Error |
|---|---|---|
| PhAbs.nH | 0.36 | 0.01467 |
| PowerLaw.alpha | 2.118 | 0.00352 |
| PowerLaw.K | 8.935 | 0.0739 |
Fit Statistics
| Data | Statistic | Value | Channels |
|---|---|---|---|
| LE | pgstat | 93.61 | 80 |
| ME | pgstat | 7.60 | 16 |
| HE | pgstat | 43.26 | 25 |
| Total | stat/dof | 144.48/118 | 121 |
Information Criterion
| Method | Value |
|---|---|
| AIC | 150.68 |
| BIC | 158.87 |
Fit Status
| Migrad | |
|---|---|
| FCN = 144.5 (χ²/ndof = 1.2) | Nfcn = 90, Ngrad = 1 |
| EDM = 1.07e-11 (Goal: 0.0002) | time = 1.6 sec |
| Valid Minimum | Below EDM threshold (goal x 10) |
| No parameters at limit | Below call limit |
| Hesse ok | Covariance accurate |
Plot of Spectral Fit and Residuals#
fig = mle.plot()
Goodness of Fit: Quantile-Quantile Plot#
fig = mle.plot.plot_qq(detrend=False)
Goodness of Fit: Probability Integral Transform ECDF Plot#
fig = mle.plot.plot_pit(detrend=False)
Calculate Confidence Intervals of Parameters#
ci = mle.ci()
ci
ConfidenceInterval(mle={'PhAbs.nH': 0.3599707776186719, 'PowerLaw.alpha': 2.1178046343526487, 'PowerLaw.K': 8.934947339276833}, se={'PhAbs.nH': 0.014673851887025606, 'PowerLaw.alpha': 0.0035202870853913333, 'PowerLaw.K': 0.0739014710117776}, intervals={'PhAbs.nH': (0.3453066791681569, 0.3746544049684308), 'PowerLaw.alpha': (2.1142901302005304, 2.1213307196657176), 'PowerLaw.K': (8.86138804367345, 9.009193566664477)}, errors={'PhAbs.nH': (-0.014664098450515028, 0.014683627349758865), 'PowerLaw.alpha': (-0.003514504152118292, 0.0035260853130689718), 'PowerLaw.K': (-0.07355929560338303, 0.07424622738764342)}, cl=np.float64(0.6826894921370859), method='profile', status={'PhAbs.nH': {'valid': (True, True), 'at_limit': (False, False), 'at_max_fcn': (False, False), 'new_min': (False, False)}, 'PowerLaw.alpha': {'valid': (True, True), 'at_limit': (False, False), 'at_max_fcn': (False, False), 'new_min': (False, False)}, 'PowerLaw.K': {'valid': (True, True), 'at_limit': (False, False), 'at_max_fcn': (False, False), 'new_min': (False, False)}})
ci.mle
{'PhAbs.nH': 0.3599707776186719,
'PowerLaw.alpha': 2.1178046343526487,
'PowerLaw.K': 8.934947339276833}
ci.errors
{'PhAbs.nH': (-0.014664098450515028, 0.014683627349758865),
'PowerLaw.alpha': (-0.003514504152118292, 0.0035260853130689718),
'PowerLaw.K': (-0.07355929560338303, 0.07424622738764342)}