Fitting SANS data#

Previously, some small angle neutron scattering (SANS) data has been simulated and reduced, and can now be analysed with easyscience. Before the analysis can begin, it is necessary to load the experimental data and check that it looks reasonable. The data can be loaded with np.loadtxt as the data has been stored in a simple space-separated column file.

import numpy as np
from load import load

q, i, di = load('../4-reduction/sans_iofq_3pulses.dat')

With the data read in, we can produce a quick plot.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.errorbar(q, i, di, fmt='.')
ax.set(yscale='log', xlabel='$q$/Å^-1', ylabel='I(q)')
plt.show()
../_images/33466b88906b55d53641f1854efee5f0093970da24ebd654b4aa2e05567e681a.png

We now want to consider the mathematical model to be used in the analysis. There are SANS models a range of different systems, see for instance the models in SasView. However, initially, we will assume that our data has arisen from a spherical system.

The mathematical model for a sphere is

(3)#\[ I(q) = \frac{\text{scale}}{V} \bigg(\frac{3 V \Delta \rho [\sin{(qr)} - qr \cos{(qr)}]}{(qr)^3}\bigg)^2 + \text{bkg}, \]

where \(\text{scale}\) is a scale factor, \(V\) is the volume of the sphere, \(\Delta \rho\) is the difference between the solvent and particle scattering length density, \(r\) is the radius of the sphere, \(\text{bkg}\) is a uniform background, and \(q\) is the q-vector that the intensity is being calculated for.

Exercise 1: simplify the expression#

The mathematical model described in Eqn. (3) has five parameters. What simple mathematical simplification can be performed to reduce this to four?

Solution:

The volume of a sphere is related to the radius of the sphere as

(4)#\[ V = \frac{4}{3} \pi r^3. \]

Therefore, the parameter \(V\) can be replaced with Eqn. (4).

Exercise 2: write a function that computes for the form factor of a sphere#

Four parameters is a suitable number for modelling. Therefore, we should write a function that implements your reduced dimensionality version of Eqn. (3).

Hide code cell content
def sphere(q):
    """
    The function for the form factor of a sphere. 
    
    Parameters
    ----------
    q: 
        q-vectors to calculate for.
    
    Returns
    -------
    : 
        The modelled intensity.
    """
    qr = q * r.value
    V = 4 / 3 * np.pi * r.value ** 3
    return scale.value / V * (3 * V * delta_rho.value * (np.sin(qr) - qr * np.cos(qr)) / ((qr) ** 3)) ** 2 + bkg.value

Exercise 3: create fitting parameters#

Create four Parameter objects, for \(\text{scale}\), \(\Delta \rho\), \(r\), and \(\text{bkg}\).

Each should have an initial value and a uniform prior distribution based on the values given in Table 1, except for the \(\text{scale}\) which should be fixed to a value of 1.4 × 10-7.

Table 1 Parameter values for the spherical model.#

Parameter

Initial Value

Min

Max

\(\text{scale}\)

1.4 × 10-8

N/A

N/A

\(\Delta \rho\)

3

0

10

\(r\)

80

10

1000

\(\text{bkg}\)

3.0 × 10-3

1.0 × 10-3

1.0 × 10-2

Solution:

Hide code cell content
from easyscience.Objects.new_variable import Parameter

scale = Parameter(name='scale', value=1.4e-7, fixed=True)
delta_rho = Parameter(name='delta_rho', value=3, fixed=False, min=0, max=10)
r = Parameter(name='r', value=80, fixed=False, min=0, max=1000)
bkg = Parameter(name='bkg', value=0.01, fixed=False, min=0.001, max=0.1)

It is now possible to compare our model at the initial estimates to the simulated data.

fig, ax = plt.subplots()
ax.errorbar(q, i, di, fmt='.')
ax.plot(q, sphere(q), '-k', zorder=10)
ax.set(yscale='log', xlabel='$q$/Å^-1', ylabel='I(q)')
plt.show()
../_images/b6663cd1f8f8c300b08a9a17ea02760987113153ad2842620505c086166d08c0.png

Exercise 4: find maximum likelihood estimates#

Using easyscience, obtain maximum likelihood estimates for the four parameters of the model from comparison with the data.

Solution:

Hide code cell content
from easyscience.Objects.ObjectClasses import BaseObj
from easyscience.fitting import Fitter

params = BaseObj(name='params', delta_rho=delta_rho, r=r, bkg=bkg)
f = Fitter(params, sphere)

res = f.fit(x=q, y=i, weights=1/di)

We can then plot the model and the data together as before and print the values of the parameters along with their uncertainties.

fig, ax = plt.subplots()
ax.errorbar(q, i, di, fmt='.')
ax.plot(q, sphere(q), '-k', zorder=10)
ax.set(yscale='log', xlabel='$q$/Å^-1', ylabel='I(q)')
plt.show()
../_images/aae9ab8a7bddff2b32165e3f1f641f3590e88471734b3d9847a2a773af291b79.png
delta_rho, r, bkg
(<Parameter 'delta_rho': 3.5176 ± 0.0085, bounds=[0.0:10.0]>,
 <Parameter 'r': 90.5384 ± 0.1443, bounds=[0.0:1000.0]>,
 <Parameter 'bkg': 0.0193 ± 0.0003, bounds=[0.001:0.1]>)