Last modified: 27 Nov 2023

URL: https://cxc.cfa.harvard.edu/sherpa/threads/sample_flux/

Calculating Model Flux and Flux Uncertainty

Sherpa Threads (CIAO 4.16 Sherpa)


Overview

Synopsis:

This thread demonstrates the use of the sample_flux function for calculating the energy flux and associated flux uncertainty due to a Sherpa model, and any model sub-components, previously defined and fit to a data set.

Last Update: 27 Nov 2023 - reviewed for CIAO 4.16, no content change.


Contents


Getting Started

Please follow the Obtaining Data Used in Sherpa thread. In this example, we use the spectral data products for 3C 273, located in the sample_flux sub-directory.


Fitting a Model to the Spectrum

To begin, we load the spectrum and instrument response files for quasar 3C 273 using the load_pha command, and then fit it with a source model composed of the sum of an absorbed, non-thermal power-law model, and a thermal bremsstrahlung model component. We subtract off the background before fitting, and include only the 0.3–7.0 keV data range in our analysis.

sherpa> load_pha("3c273.pi")
WARNING: systematic errors were not found in file '3c273.pi'
statistical errors were found in file '3c273.pi' 
but not used; to use them, re-read with use_errors=True
read ARF file 3c273.arf
read RMF file 3c273.rmf
WARNING: systematic errors were not found in file '3c273_bg.pi'
statistical errors were found in file '3c273_bg.pi' 
but not used; to use them, re-read with use_errors=True
read background file 3c273_bg.pi
sherpa> notice(0.3, 7.0)
dataset 1: 0.00146:14.9504 -> 0.2482:9.8696 Energy (keV)

sherpa> subtract()
sherpa> set_stat("chi2datavar")
sherpa> set_method("simplex")
sherpa> set_source(xsphabs.gal * (xspowerlaw.p1 + xsbremss.kt))
sherpa> gal.nh = 0.07
sherpa> kt.kt = 0.5
sherpa> freeze(gal)
sherpa> fit()
Dataset               = 1
Method                = neldermead
Statistic             = chi2datavar
Initial fit statistic = 1.31212e+11
Final fit statistic   = 36.4503 at function evaluation 1435
Data points           = 44
Degrees of freedom    = 40
Probability [Q-value] = 0.630838
Reduced statistic     = 0.911256
Change in statistic   = 1.31212e+11
   p1.PhoIndex    2.09439     
   p1.norm        0.000213361 
   kt.kT          0.0526971   
   kt.norm        0.141333    

For reference, the best-fit looks like Figure 1:

sherpa> plot_fit(xlog=True, ylog=True)

Figure 1: Best fit model

[The model, drawn as an orange line, well fits the data (blue points).]
[Print media version: The model, drawn as an orange line, well fits the data (blue points).]

Figure 1: Best fit model

Having performed an initial fit to our data set—choosing χ2 statistics with variance calculated from the data, and the robust Neldermead-Simplex fit optimization—we are ready to run the sample_flux function to obtain an estimate of the energy flux for our source 3C 273, with the corresponding model parameters and associated flux uncertainty.


Sampling the Model Flux

The sample_flux function takes a number of arguments, the key ones being the model component(s) for which to calculate the associated flux and flux uncertainty, the number of flux simulations to run, and the range of data to consider in the analysis. The returned values include the flux from the full model expression used in the fitting (labeled "original model flux"), and the emission specific to the entered model sub-component(s) (labeled "model component flux").

In this thread example we consider the unabsorbed flux due to the sum of the power-law and bremsstrahlung model components (p1+kt), in the 0.3–7 keV energy range, using only ten simulations (in standard analysis, a larger number, e.g. 100 or more, should be used for a better sampling of the parameter distributions). We also set the Boolean 'correlated' parameter to 'True' to include correlations between the parameters in evaluating the uncertainty.

sherpa> component = p1 + kt
sherpa> print(component)
(xspowerlaw.p1 + xsbremss.kt)
   Param        Type          Value          Min          Max      Units
   -----        ----          -----          ---          ---      -----
   p1.PhoIndex  thawed      2.09439           -3           10           
   p1.norm      thawed  0.000213361            0        1e+24           
   kt.kT        thawed    0.0526971       0.0001          200        keV
   kt.norm      thawed     0.141333            0        1e+24           

sherpa> sample_flux(component, 0.3, 7 , num=10, correlated=True)
WARNING: hard minimum hit for parameter kt.norm
original model flux = 8.5631e-13, + 4.43236e-14, - 6.27143e-14
model component flux = 1.10448e-12, + 9.17731e-14, - 6.36545e-14

[array([8.56310353e-13, 9.00633948e-13, 7.93596009e-13]),
 array([1.10448343e-12, 1.19625657e-12, 1.04082896e-12]),
 array([[8.61528748e-13, 2.01756352e+00, 2.10643412e-04, 3.65780232e-02,
         3.33851247e-01, 0.00000000e+00, 4.60084638e+01],
        [9.02872802e-13, 2.16299022e+00, 2.38209627e-04, 3.78749713e-02,
         3.27976429e-01, 0.00000000e+00, 4.45268040e+01],
        [8.31142211e-13, 2.05107293e+00, 2.09054170e-04, 7.10616928e-02,
         0.00000000e+00, 1.00000000e+00, 4.98398497e+01],
        [8.27532129e-13, 2.14380931e+00, 2.13218018e-04, 4.19131818e-02,
         2.84326776e-01, 0.00000000e+00, 3.95163959e+01],
        [9.35834548e-13, 2.06897590e+00, 2.20036716e-04, 5.37572402e-02,
         1.54653426e-01, 0.00000000e+00, 3.85096658e+01],
        [8.51091959e-13, 2.17013744e+00, 2.27731013e-04, 2.81137120e-02,
         4.69724438e-01, 0.00000000e+00, 4.74298747e+01],
        [7.88968356e-13, 2.13547205e+00, 2.07148130e-04, 3.15239734e-02,
         3.70428569e-01, 0.00000000e+00, 4.81048193e+01],
        [7.78234784e-13, 2.08483559e+00, 1.94263550e-04, 4.26333205e-02,
         2.40205530e-01, 0.00000000e+00, 4.43360584e+01],
        [7.89676944e-13, 2.11446969e+00, 2.05618790e-04, 6.40017978e-02,
         0.00000000e+00, 1.00000000e+00, 5.02555254e+01],
        [7.76753349e-13, 2.07777160e+00, 1.98309064e-04, 6.27607194e-02,
         0.00000000e+00, 1.00000000e+00, 5.20737745e+01],
        [8.84215690e-13, 2.07323435e+00, 2.15819285e-04, 5.29627345e-02,
         8.98713616e-02, 0.00000000e+00, 3.84211423e+01]])]

The screen output shows the 0.3–7 keV energy flux with upper and lower bounds in units of [ergs/s/cm2], for the full model fit to the data, followed by the flux for the specified model component, p1+kt. The flux is obtained as a median of all the flux values in the simulated parameter sets. The default upper and lower bounds are set at 68%, or 1σ, confidence, and may be changed via the 'confidence' parameter.

By default, sample_flux assumes the units of a standard X-ray data set (the 'Xrays' parameter is set to 'True'), returning the energy flux in [ergs/s/cm2]. If 'Xrays' is set to 'False' instead, then the units are determined by the type of input data. For example, for an optical spectrum entered in the units of [W/m2/Hz], defined in frequency (Hz), sample_flux will integrate the spectrum and give the flux in [W/m2].

The sample_flux function also returns a list of arrays. The first two arrays contain the flux with upper and lower bounds for the original model and the specified model component(s), respectively. The last array contains all the simulated parameters with a corresponding flux for the full model expression fit to the data.

To consider another example of the usage of the sample_flux function, we estimate the flux of an individual model component from our full model expression, the thermal bremsstrahlung model component 'kt', within the 0.5–2 keV range at 90% confidence. This time, we choose to use the default 'False' setting for the 'correlated' parameter, increase the number of simulations to 1000, and store the returned values in a variable called sample1.

sherpa> sample1 = sample_flux(kt, 0.5, 2, num=1000, confidence=90)
WARNING: hard minimum hit for parameter kt.norm
WARNING: Covariance failed for 'kt.norm', trying Confidence...
kt.norm lower bound:    -0.10618
kt.norm upper bound:    5.89866
original model flux = 3.906e-13, + 5.81815e-14, - 4.19351e-14
model component flux = 3.32545e-15, + 9.06334e-14, - 3.32291e-15

sherpa> len(sample1)
        3
sherpa> sample1[0]
        array([3.90600269e-13, 4.48781815e-13, 3.48665140e-13])
sherpa> sample1[1]
        array([3.32545258e-15, 9.39588448e-14, 2.54351740e-18])
sherpa> sample1[2].shape
        (1001, 7)

The screen output from sample_flux shows the flux values and 90% bounds from both the full model and the single model component 'kt', along with messages from the covariance/confidence calculation of model parameter uncertainties.

The sample1 variable can be used to access the original and model component flux values—sample1[0] and sample1[1] respectively—and the flux, parameter values, and statistic for each simulation (sample1[2]). The following returns the fluxes and photon index values:

sherpa> fluxes = sample1[2][:, 0]
sherpa> phoindxs = sample1[2][:, 1]
sherpa> plot_scatter)(phoindxs, fluxes, xlabel=r'$\Gamma$', ylabel='flux')

The result of the scatter plot is Figure 2.

Figure 2: Scatter plot: photon-index versus flux

[The points are distributed mainly about 4e-13 (flux) and gamma=2.1, but with some scatter (mainly to higher flux)]
[Print media version: The points are distributed mainly about 4e-13 (flux) and gamma=2.1, but with some scatter (mainly to higher flux)]

Figure 2: Scatter plot: photon-index versus flux

sherpa> print(get_source())
(xsphabs.gal * (xspowerlaw.p1 + xsbremss.kt))
   Param        Type          Value          Min          Max      Units
   -----        ----          -----          ---          ---      -----
   gal.nH       frozen         0.07            0        1e+06 10^22 atoms / cm^2
   p1.PhoIndex  thawed      2.09439           -3           10           
   p1.norm      thawed  0.000213361            0        1e+24           
   kt.kT        thawed    0.0526971       0.0001          200        keV
   kt.norm      thawed     0.141333            0        1e+24           
sherpa> sample1[2][0]

array([3.62399310e-13, 2.18057882e+00, 2.03338150e-04, 4.70761003e-02,
       0.00000000e+00, 1.00000000e+00, 5.48493867e+01])

The first element of this array is the flux, and then the thawed parameter values in the same order as listed by the get_source command, with the last element being the statistic value.

The plot_trace function lets us look at trace plots (i.e. a value per iteration). For example, to look at how the fluxes and photon-indexes vary you could say (Figure 3):

sherpa> plt.clf()
sherpa> plt.subplot(2, 1, 1)
sherpa> plot_trace(fluxes, name='flux', clearwindow=False)
sherpa> plt.title('')
sherpa> plt.subplot(2, 1, 2)
sherpa> plot_trace(phoindxs, name=r'$\Gamma$', clearwindow=False)
sherpa> plt.title('')
sherpa> plt.subplots_adjust(hspace=0.05)

Figure 3: Trace plot: flux and photon-index

[The points show a "reasonable" distribution, although you can see the slightly-discrepant high-flux points seen in the scatter plot.]
[Print media version: The points show a "reasonable" distribution, although you can see the slightly-discrepant high-flux points seen in the scatter plot.]

Figure 3: Trace plot: flux and photon-index


Visualizing the Results of the Flux Simulations

We can visualize the distribution of model parameter values resulting from the sample_flux simulations, for a particular model component, using the Sherpa plot_pdf or plot_cdf functions. The plot_pdf function displays a binned probability density function (Figure 4), while plot_cdf shows the cumulative distribution function with lower, median, and upper quantiles (Figure 5).

Figure 4: Power-law photon index probability density

[Power-law photon index PDF plot]
[Print media version: Power-law photon index PDF plot]

Figure 4: Power-law photon index probability density

The probablity density for any of the parameters can be displayed using plot_pdf; in this case the p1.PhoIndex parameter:

sherpa> plot_pdf(sample1[2][:,1], xlabel="p1.PhoIndex", name="p1.PhoIndex")

Figure 5: Power-law photon index cumulative distribution

[Power-law photon index CDF plot]
[Print media version: Power-law photon index CDF plot]

Figure 5: Power-law photon index cumulative distribution

The cumulative distribution of the photon index is shown by plot_cdf; the median value is marked by the orange line and 68% quantiles with the blue ones.

sherpa> plot_cdf(sample1[2][:,1], xlabel="p1.PhoIndex", name="p1.PhoIndex")

Supplying the Scales for the Simulations

sample_flux assumes a multi-variate Gaussian distribution for the flux simulations, with the Gaussian scales for each parameter given by the covariance matrix (unless covariance fails, in which case confidence is used to calculate the sizes). Correlations are taken into account when the 'correlated' parameter is set to 'True' and the non-diagonal elements from the covariance matrix set the scales.

There is an option to supply the scales to sample_flux for the simulations via the 'scales' parameter, instead of recalculating them with covariance or confidence. If 'correlated=True', the covariance function must be run before sample_flux in order to set the scales. If parameters are uncorrelated ('correlation=False'), a list of scales corresponding to each parameter is needed.

Considering an example using correlated parameters with 'correlated=True', we run the Sherpa covariance command and obtain the resulting covariance matrix to set the scales for the sample_flux simulations.

sherpa> covar()
WARNING: hard minimum hit for parameter kt.norm
Dataset               = 1
Confidence Method     = covariance
Iterative Fit Method  = None
Fitting Method        = neldermead
Statistic             = chi2datavar
covariance 1-sigma (68.2689%) bounds:
   Param            Best-Fit  Lower Bound  Upper Bound
   -----            --------  -----------  -----------
   p1.PhoIndex       2.09439   -0.0630781    0.0630781
   p1.norm       0.000213361 -1.18751e-05  1.18751e-05
   kt.kT           0.0526971   -0.0137318    0.0137318
   kt.norm          0.141333        -----     0.183067

sherpa> matrix = get_covar_results().extra_output
sherpa> print(matrix)
[[ 3.97884279e-03  5.06247189e-07 -1.91149603e-04  2.04083462e-03]
 [ 5.06247189e-07  1.41018324e-10 -3.87275606e-08  4.19689773e-07]
 [-1.91149603e-04 -3.87275606e-08  1.88562723e-04 -2.44865204e-03]
 [ 2.04083462e-03  4.19689773e-07 -2.44865204e-03  3.35135463e-02]]

The scales for sample_flux may now be defined using the covariance results from get_covar_results, using the command directly, as shown below, or the 'matrix' variable defined above.

sherpa> sample2 = sample_flux(component, 0.5, 7, num=1000, correlated=True, scales=matrix)
original model flux = 7.60839e-13, + 3.22014e-14, - 3.27991e-14
model component flux = 8.59591e-13, + 3.49993e-14, - 3.61911e-14
sherpa> print(sample2[2][0:20, 1])
[2.12461006 2.10993261 2.06326981 2.09516266 2.01140388 2.13490478
 2.12486691 2.06181947 2.13028183 2.09363631 2.23718159 2.17696166
 2.10770759 2.18460791 2.11034962 2.15849421 2.04374209 2.16024991
 2.048527   2.13540422]

Figure 6: Comparison of the power-law distributions

[The distribution for the second analysis (using the covariance matrix) is slightly shifted compared to the un-correlated version.]
[Print media version: The distribution for the second analysis (using the covariance matrix) is slightly shifted compared to the un-correlated version.]

Figure 6: Comparison of the power-law distributions

Here we compare the power-law distribution from Figure 4 (blue line) to that obtained when using the covariance matrix (orange line):

sherpa> plot_pdf(sample1[2][:, 1])
sherpa> plot_pdf(sample2[2][:, 1], overplot=True)

Saving the Simulation Results

The arrays of model parameter values and associated fluxes from our two sample_flux runs may be written to file using the Sherpa save_arrays command, as shown below. Here, we write the power-law photon index and thermal bremsstrahlung temperature arrays from the first sample_flux run to the ASCII file sample1.dat, specifying data column names and overwrite the existing sample1.dat file.

sherpa> save_arrays("sample1.dat", [sample1[2][:,1], sample1[2][:,3]], ['Photon Index', 'Temperature'], clobber=True)

Scripting It

The file, fit.py , performs the primary commands used above. It can be executed by typing %fit -i fit.py on the Sherpa command line. The Sherpa script command may be used to save everything typed on the command line in a Sherpa session:

sherpa> script(filename="sherpa.log", clobber=False)

(Note that restoring a Sherpa session from such a file could be problematic since it may include syntax errors, unwanted fitting trials, et cetera.)


History

13 Dec 2012 original version
10 Dec 2013 Updated for CIAO 4.6: the return value from sample_flux now includes the statistic value of each simulation.
18 Mar 2015 Updated for CIAO 4.7, no content change.
12 Jul 2018 reviewed for CIAO 4.10, no content change.
12 Dec 2018 reviewed for CIAO 4.11, no content change.
13 Dec 2019 Updated for CIAO 4.12: use Matplotlib rather than ChIPS for plotting; added examples of plot_scatter and plot_trace.
24 Mar 2022 reviewed for CIAO 4.14, no content change.
06 Dec 2022 reviewed for CIAO 4.15, no content change.
27 Nov 2023 reviewed for CIAO 4.16, no content change.