Usage details

Now that you have the basics let’s move on to some more complex usage of the fitter interface. First a quick preamble to do some imports and create our SherpaFitter object:

Parameter constraints

If you place any of the parameter constraints on the astropy models then they will be respected by the fitter. Let’s take a quick look at that. Firstly let’s make a compound model by adding two Gaussian1D instances:

double_gaussian = (Gaussian1D(amplitude=10, mean=-1.5, stddev=0.5) +
                   Gaussian1D(amplitude=3, mean=0.9, stddev=0.5))

Now we have the compound model lets add tie amplitude_1 (the amplitude of the right hand side Gaussian1D) to 1.2*amplitude_0 and while we’re at it let us generate some data, too. To do this we must first define the tiedfunc:

def tiedfunc(self): # a function used for tying amplitude_1
    return 1.2*self.amplitude_0

double_gaussian.amplitude_1.tied = tiedfunc

err = 0.8
step = 0.2
x = np.arange(-3, 3, step)
y = double_gaussian(x) + err * np.random.uniform(-1, 1, size=len(x))
yerrs = err * np.random.uniform(0.2, 1, size=len(x))
binsize=(step/2) * np.ones(x.shape)

Note

without astropy PR #5129 we need to do this double_gaussian.amplitude_1.value = double_gaussian.amplitude_1.tied(double_gaussian)

_images/example_plot_data2.png

Let’s add some more parameter constraints to the model and fit the data. We can print the sherpa models to check things are doing what they should.

fit_gg = double_gaussian.copy()
fit_gg.mean_0.value = -0.5
# sets the lower bound so we can force the parameter against it
fit_gg.mean_0.min = -1.25
fit_gg.mean_1.value = 0.8
fit_gg.stddev_0.value = 0.9
fit_gg.stddev_0.fixed = True

Fitting Config

An initialized SherpaFitter object has the opt_config property which holds the configuration details for the optimization routine. It’s docstring contains information about the the properties of the optimizer.

print(sfit.opt_config)
print(sfit.opt_config.__doc__)  # as help returns the help for the returned object
{'epsfcn': 1.1920928955078125e-07,
 'factor': 100.0,
'ftol': 1.1920928955078125e-07,
'gtol': 1.1920928955078125e-07,
'maxfev': None,
'verbose': 0,
'xtol': 1.1920928955078125e-07}


Levenberg-Marquardt optimization method.

The Levenberg-Marquardt method is an interface to the MINPACK
subroutine lmdif to find the local minimum of nonlinear least
squares functions of several variables by a modification of the
Levenberg-Marquardt algorithm [1]_.

Attributes
----------
ftol : number
   The function tolerance to terminate the search for the minimum;
   the default is sqrt(DBL_EPSILON) ~ 1.19209289551e-07, where
   DBL_EPSILON is the smallest number x such that `1.0 != 1.0 +
   x`. The conditions are satisfied when both the actual and
   predicted relative reductions in the sum of squares are, at
   most, ftol.

xtol : number
   The relative error desired in the approximate solution; default
   is sqrt( DBL_EPSILON ) ~ 1.19209289551e-07, where DBL_EPSILON
   is the smallest number x such that `1.0 != 1.0 + x`. The
   conditions are satisfied when the relative error between two
   consecutive iterates is, at most, `xtol`.

...

The parameters can be changed by

sfit.opt_config['ftol'] = 1e-5
print(sfit.opt_config)
{'epsfcn': 1.1920928955078125e-07,
 'factor': 100.0,
 'ftol': 1e-05,
 'gtol': 1.1920928955078125e-07,
 'maxfev': None,
 'verbose': 0,
 'xtol': 1.1920928955078125e-07}

Fitting this model is the same as earlier and we can also fit an unconstrained model for comparison:

fitted_gg = sfit(fit_gg,x, y, xbinsize=binsize, err=yerrs)

sfit2 = SherpaFitter(statistic='chi2', optimizer='levmar', estmethod='covariance')

free_gg = sfit2(double_gaussian.copy(), x, y, xbinsize=binsize, err=yerrs)
_images/example_plot_fitted2.png

The fitter keeps a copy of the converted model so we can use it to compare the constrained and unconstrained model setups:

Note

wrap\_.amplitude_1 should be linked, sherpa notation of astropy’s tied wrap\_.stddev_0 should be frozen, sherpa notation for fixed and finally wrap\_.mean_0‘s value should have moved to its minimum while fitting

“wrap_” is just perpended to the model name (we didn’t set one so it’s blank) on conversion to the sherpa Model.

print("##Fit with constraints")
print(sfit._fitmodel.sherpa_model)
print("##Fit without constraints")
print(sfit2._fitmodel.sherpa_model)
##Fit with constraints

   Param        Type          Value          Min          Max      Units
   -----        ----          -----          ---          ---      -----
   wrap_.amplitude_0 thawed      5.58947 -3.40282e+38  3.40282e+38
   wrap_.mean_0 thawed        -1.25        -1.25  3.40282e+38
   wrap_.stddev_0 frozen          0.9 -3.40282e+38  3.40282e+38
   wrap_.amplitude_1 linked      6.70736 expr: (1.2 * wrap_.amplitude_0)
   wrap_.mean_1 thawed     0.869273 -3.40282e+38  3.40282e+38
   wrap_.stddev_1 thawed     0.447021 -3.40282e+38  3.40282e+38

##Fit without constraints

   Param        Type          Value          Min          Max      Units
   -----        ----          -----          ---          ---      -----
   wrap_.amplitude_0 thawed      6.95483 -3.40282e+38  3.40282e+38
   wrap_.mean_0 thawed     -1.59091 -3.40282e+38  3.40282e+38
   wrap_.stddev_0 thawed     0.545582 -3.40282e+38  3.40282e+38
   wrap_.amplitude_1 linked      8.34579 expr: (1.2 * wrap_.amplitude_0)
   wrap_.mean_1 thawed     0.785016 -3.40282e+38  3.40282e+38
   wrap_.stddev_1 thawed      0.46393 -3.40282e+38  3.40282e+38

Error Estimation Configuration

As with the optmethods before we are able to adjust the configuration of the estmethods. Some of the properties can be passed through est_errors as keyword arguments such as the sigma however for access to all options we have the est_config property.

print(sfit.est_config)
sfit.est_config['numcores'] = 5
sfit.est_config['max_rstat'] = 4
print(sfit.est_config)
{'eps': 0.01,
 'fast': False,
 'max_rstat': 3,
 'maxfits': 5,
 'maxiters': 200,
 'numcores': 8,
 'openinterval': False,
 'parallel': True,
 'remin': 0.01,
 'sigma': 1,
 'soft_limits': False,
 'tol': 0.2,
 'verbose': False}

{'eps': 0.01,
 'fast': False,
 'max_rstat': 3,
 'maxfits': 5,
 'maxiters': 200,
 'numcores': 5,
 'openinterval': False,
 'parallel': True,
 'remin': 0.01,
 'sigma': 1,
 'soft_limits': False,
 'tol': 0.2,
 'verbose': False}

Multiple models or multiple datasets

We have three scenarios we can handle:

  • Fitting N datasets with N models
  • Fitting a single dataset with N models
  • Fitting N datasets with a single model

If N > 1 for any of the scenarios then calling the fitter will return a list of models. Firstly we look at a single dataset with the two models as above. We quickly copy the two models above and supply them to the fitter as a list - hopefully we get the same result.

fit_gg = double_gaussian.copy()
fit_gg.mean_0.value = -0.5
fit_gg.mean_0.min = -1.25
fit_gg.mean_1.value = 0.8
fit_gg.stddev_0.value = 0.9
fit_gg.stddev_0.fixed = True

fm1, fm2 = sfit([fit_gg, double_gaussian.copy()], x, y, xbinsize=binsize, err=yerrs)
_images/example_plot_simul.png

We also can fit multiple datasets with a single model so let’s make a second dataset:

second_gg = double_gaussian.copy()
second_gg.mean_0 = -2
second_gg.mean_1 = 0.5
second_gg.amplitude_0 = 8
second_gg.amplitude_1 = 5
second_gg.stddev_0 = 0.4
second_gg.stddev_1 = 0.8

y2 = second_gg(x) + err * np.random.uniform(-1, 1, size=len(x))
y2errs = err * np.random.uniform(0.2, 1, size=len(x))

Here we supply lists for each of the data parameters. You can also use None for when you don’t have something like a missing binsizes - a lack of binsizes is a contrived example but a lack of y errors is not suitable for a chi:sup:2 fit and you don’t want to make a new fitter.

fit_gg = double_gaussian.copy()
fit_gg.mean_0 = -2.3
fit_gg.mean_1 = 0.7
fit_gg.amplitude_0 = 2
fit_gg.amplitude_1 = 3
fit_gg.stddev_0 = 0.3
fit_gg.stddev_1 = 0.5

fm1, fm2 = sfit(fit_gg, x=[x, x], y=[y, y2], xbinsize=[binsize, None], err=[yerrs, y2errs])
_images/example_plot_simul2.png

Background Data

It is also possible specify background data which is required for several of the fit statistics.

This is done by supplying a background array using the bkg keyword. If there is a scaling of the background relative to the source data then you can use the bkg_scale keyword

y[y<0]=0
cfit = SherpaFitter(statistic='cstat', optimizer='levmar', estmethod='covariance')
cfit(fit_gg, x=x, y=y, xbinsize=binsize, err=yerrs, bkg=y, bkg_scale=0.3)
_images/example_plot_bkg.png