Energy and angular dispersive analysis

refnx able to deal with reflectivity from systems containing energy dispersive materials, i.e. those whose optical properties change as a function of wavelength. For neutrons this mainly corresponds to elements with strong absorption effects. The treatment below is also able to deal with energy dispersive X-ray measurements

import numpy as np
import matplotlib.pyplot as plt

from refnx.reflect import ReflectModelTL, ReflectModel, SLD, MaterialSLD
from refnx.util import q, xray_wavelength

Start off by creating a MaterialSLD. This is a variant of Scatterer, whose optical properties are controlled by a formula, mass density (g/cc), and whether the material is being used for neutron or X-ray calculation.

Here we’ll calculate the SLD of the material at two different wavelengths.

gdgao_disp = MaterialSLD("GdGa5O12", 7, probe='neutron') # can be 'x-ray'

gdgao_disp.wavelength = 2.8   # Angstrom
print(f"SLD: {complex(gdgao_disp)} at {gdgao_disp.wavelength} Angstrom")
gdgao_disp.wavelength = 18.
print(f"SLD: {complex(gdgao_disp)} at {gdgao_disp.wavelength} Angstrom")
SLD: (6.544686245235056+0.7318962182191739j) at 2.8 Angstrom
SLD: (6.471928664642279+0.648390380281317j) at 18.0 Angstrom

For comparison let’s create a non-dispersive version. By non-dispersive we mean that the optical properties don’t change as a function of wavelength

gdgao_nondisp = SLD(6.5 + 0.68j)

The MaterialSLD.density attribute can be allowed to vary during a fit.

print(gdgao_disp.parameters)
________________________________________________________________________________
Parameters:       ''       
<Parameter:   'density'   , value=7  (fixed) , bounds=[-inf, inf]>

In comparison, with the non-dispersive analogue one can allow the real and imaginary part of the SLD to vary.

print(gdgao_nondisp.parameters)
________________________________________________________________________________
Parameters:       ''       
<Parameter:   ' - sld'    , value=6.5  (fixed) , bounds=[-inf, inf]>
<Parameter:   ' - isld'   , value=0.68  (fixed) , bounds=[-inf, inf]>

Now we create two Structures that are ostensibly the same, but one has a dispersive material in it, the other a non-dispersive analogue.

air = SLD(0.0)
si = SLD(2.07)

s_disp = air | gdgao_disp(300, 5) | si(0, 3)
s_nondisp = air | gdgao_nondisp(300, 5) | si(0, 3)

Now we generate theta/wavelength arrays, with a corresponding Q value. Subsequently we create a ReflectModelTL and a ReflectModel. ReflectModelTL is a variant of ReflectModel. Instead of calculating reflectivity as a function of Q (a. la. ReflectModel), it calculates as a function of incident angle and wavelength

npnts = 201
theta = np.ones(npnts) * 0.65
wavelength = np.geomspace(2.8, 18, npnts)
qq = q(theta, wavelength)

model_disp = ReflectModelTL(s_disp)
model_nondisp = ReflectModel(s_nondisp)

Now let’s compare the reflectivity from the dispersive and non-dispersive analogues. The reflectivities are almost identical, the energy dispersive absorption effect has little effect in this case.

plt.plot(qq, model_nondisp(qq), label='nondisp')

# note how we provide theta and wavelength to the ReflectModelTL object.
plt.plot(qq, model_disp(np.c_[theta, wavelength]), label='disp')
plt.yscale('log')
plt.xscale('log')
plt.legend();
_images/fb7c047a039d92b1a8b99435e51e8072a7684587f29df259c5fe2fd7ecacdce7.png

To reassure ourselves let’s loko at the slab representation of the the dispersive Structure at two different wavelengths. We can see that the real and imaginary components of the SLD (second column) do change, just not by much.

print(s_disp.slabs(wavelength=2.8))
print()
print(s_disp.slabs(wavelength=18.0))
[[  0.           0.           0.           0.           0.        ]
 [300.           6.54468625   0.73189622   5.           0.        ]
 [  0.           2.07         0.           3.           0.        ]]

[[  0.           0.           0.           0.           0.        ]
 [300.           6.47192866   0.64839038   5.           0.        ]
 [  0.           2.07         0.           3.           0.        ]]

For comparison here is the non-dispersive system.

print(s_nondisp.slabs(wavelength=2.8))
print()
print(s_nondisp.slabs(wavelength=18.0))
[[  0.     0.     0.     0.     0.  ]
 [300.     6.5    0.68   5.     0.  ]
 [  0.     2.07   0.     3.     0.  ]]

[[  0.     0.     0.     0.     0.  ]
 [300.     6.5    0.68   5.     0.  ]
 [  0.     2.07   0.     3.     0.  ]]