Calculating Model Flux and Flux Uncertainty

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.

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")
statistical errors were found in file '3c273.pi'
but not used; to use them, re-read with use_errors=True
statistical errors were found in file '3c273_bg.pi'
but not used; to use them, re-read with use_errors=True
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
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)
```

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.

```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('')
```

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).

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
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]
```

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.