def test_compare_c_py_abeles(self): # test python and c are equivalent # but not the same file s = self.structure.slabs()[..., :4] # if not TEST_C_REFLECT: # return assert_(_reflect.__file__ != _creflect.__file__) calc1 = _reflect.abeles(self.qvals, s) calc2 = _creflect.abeles(self.qvals, s) assert_almost_equal(calc1, calc2) calc1 = _reflect.abeles(self.qvals, s, scale=2.) calc2 = _creflect.abeles(self.qvals, s, scale=2.) assert_almost_equal(calc1, calc2) calc1 = _reflect.abeles(self.qvals, s, scale=0.5, bkg=0.1) # threads = 1 is a non-threaded implementation calc2 = _creflect.abeles(self.qvals, s, scale=0.5, bkg=0.1, threads=1) # threads = 2 forces the calculation to go through multithreaded calcn, # even on single core processor calc3 = _creflect.abeles(self.qvals, s, scale=0.5, bkg=0.1, threads=2) assert_almost_equal(calc1, calc2) assert_almost_equal(calc1, calc3)
def _smeared_abeles_constant(q, w, resolution, threads=-1): """ Fast resolution smearing for constant dQ/Q. Parameters ---------- q: np.ndarray Q values to evaluate the reflectivity at w: np.ndarray Parameters for the reflectivity model resolution: float Percentage dq/q resolution. dq specified as FWHM of a resolution kernel. threads: int, optional Do you want to calculate in parallel? This option is only applicable if you are using the ``_creflect`` module. The option is ignored if using the pure python calculator, ``_reflect``. Returns ------- reflectivity: np.ndarray The resolution smeared reflectivity """ if resolution < 0.5: return refcalc.abeles(q, w, threads=threads) resolution /= 100 gaussnum = 51 gaussgpoint = (gaussnum - 1) / 2 def gauss(x, s): return 1. / s / np.sqrt(2 * np.pi) * np.exp(-0.5 * x**2 / s / s) lowq = np.min(q) highq = np.max(q) if lowq <= 0: lowq = 1e-6 start = np.log10(lowq) - 6 * resolution / _FWHM finish = np.log10(highq * (1 + 6 * resolution / _FWHM)) interpnum = np.round( np.abs(1 * (np.abs(start - finish)) / (1.7 * resolution / _FWHM / gaussgpoint))) xtemp = np.linspace(start, finish, int(interpnum)) xlin = np.power(10., xtemp) # resolution smear over [-4 sigma, 4 sigma] gauss_x = np.linspace(-1.7 * resolution, 1.7 * resolution, gaussnum) gauss_y = gauss(gauss_x, resolution / _FWHM) rvals = refcalc.abeles(xlin, w, threads=threads) smeared_rvals = np.convolve(rvals, gauss_y, mode='same') interpolator = InterpolatedUnivariateSpline(xlin, smeared_rvals) smeared_output = interpolator(q) smeared_output *= gauss_x[1] - gauss_x[0] return smeared_output
def test_c_abeles(self): # test reflectivity calculation with values generated from Motofit calc = _creflect.abeles(self.qvals, self.structure.slabs()[..., :4]) assert_almost_equal(calc, self.rvals) # test for non-contiguous Q values tempq = self.qvals[0::5] assert_(tempq.flags['C_CONTIGUOUS'] is False) calc = _creflect.abeles(tempq, self.structure.slabs()[..., :4]) assert_almost_equal(calc, self.rvals[0::5])
def test_compare_c_py_abeles0(self): # test two layer system if not TEST_C_REFLECT: return layer0 = np.array([[0, 2.07, 0.01, 3], [0, 6.36, 0.1, 3]]) calc1 = _reflect.abeles(self.qvals, layer0, scale=0.99, bkg=1e-8) calc2 = _creflect.abeles(self.qvals, layer0, scale=0.99, bkg=1e-8) assert_almost_equal(calc1, calc2) # test a negative background calc1 = _reflect.abeles(self.qvals, layer0, scale=0.99, bkg=-5e-7) calc2 = _creflect.abeles(self.qvals, layer0, scale=0.99, bkg=-5e-7) assert_almost_equal(calc1, calc2)
def reflectivity(self, q, threads=0): """ Calculate theoretical reflectivity of this structure Parameters ---------- q : array-like Q values (Angstrom**-1) for evaluation threads : int, optional Specifies the number of threads for parallel calculation. This option is only applicable if you are using the ``_creflect`` module. The option is ignored if using the pure python calculator, ``_reflect``. If `threads == 0` then all available processors are used. Notes ----- Normally the reflectivity will be calculated using the Nevot-Croce approximation for Gaussian roughness between different layers. However, if individual components have non-Gaussian roughness (e.g. Tanh), then the overall reflectivity and SLD profile are calculated by micro-slicing. Micro-slicing involves calculating the specific SLD profile, dividing it up into small-slabs, and calculating the reflectivity from those. This normally takes much longer than the Nevot-Croce approximation. To speed the calculation up the `Structure.contract` property can be used. """ return refcalc.abeles(q, self.slabs()[..., :4], threads=threads)
def _smearkernel(x, w, q, dq, threads): """ Adaptive Gaussian quadrature integration Parameters ---------- x : float Independent variable for integration. w : array-like The uniform slab model parameters in 'layer' form. q : float Nominal mean Q of normal distribution dq : float FWHM of a normal distribution. threads : int number of threads for parallel calculation Returns ------- reflectivity : float Model reflectivity multiplied by the probability density function evaluated at a given distance, x, away from the mean Q value. """ prefactor = 1 / np.sqrt(2 * np.pi) gauss = prefactor * np.exp(-0.5 * x * x) localq = q + x * dq / _FWHM return refcalc.abeles(localq, w, threads=threads) * gauss
def _smeared_abeles_fixed(qvals, w, dqvals, quad_order=17, threads=-1): """ Resolution smearing that uses fixed order Gaussian quadrature integration for the convolution. Parameters ---------- qvals : array-like The Q values for evaluation w : array-like The uniform slab model parameters in 'layer' form. dqvals : array-like dQ values corresponding to each value in `qvals`. Each dqval is the FWHM of a Gaussian approximation to the resolution kernel. quad-order : int, optional Specify the order of the Gaussian quadrature integration for the convolution. threads: int, optional Specifies the number of threads for parallel calculation. This option is only applicable if you are using the ``_creflect`` module. The option is ignored if using the pure python calculator, ``_reflect``. If `threads == -1` then all available processors are used. Returns ------- reflectivity : np.ndarray The smeared reflectivity """ # The fixed order quadrature does not use scipy.integrate.fixed_quad. # That library function does one point at a time, whereas in this function # the integration is vectorised # get the gauss-legendre weights and abscissae abscissa, weights = gauss_legendre(quad_order) # get the normal distribution at that point prefactor = 1. / np.sqrt(2 * np.pi) def gauss(x): return np.exp(-0.5 * x * x) gaussvals = prefactor * gauss(abscissa * _INTLIMIT) # integration between -3.5 and 3.5 sigma va = qvals - _INTLIMIT * dqvals / _FWHM vb = qvals + _INTLIMIT * dqvals / _FWHM va = va[:, np.newaxis] vb = vb[:, np.newaxis] qvals_for_res = ((np.atleast_2d(abscissa) * (vb - va) + vb + va) / 2.) smeared_rvals = refcalc.abeles(qvals_for_res, w, threads=threads) smeared_rvals = np.reshape(smeared_rvals, (qvals.size, abscissa.size)) smeared_rvals *= np.atleast_2d(gaussvals * weights) return np.sum(smeared_rvals, 1) * _INTLIMIT
def test_compare_c_py_abeles2(self): # test two layer system if not TEST_C_REFLECT: return layer2 = np.array([[0, 2.07, 0.01, 3], [10, 3.47, 0.01, 3], [100, 1.0, 0.01, 4], [0, 6.36, 0.1, 3]]) calc1 = _reflect.abeles(self.qvals, layer2, scale=0.99, bkg=1e-8) calc2 = _creflect.abeles(self.qvals, layer2, scale=0.99, bkg=1e-8) assert_almost_equal(calc1, calc2)
def test_c_abeles_reshape(self): # c reflectivity should be able to deal with multidimensional input if not TEST_C_REFLECT: return s = self.structure.slabs()[..., :4] reshaped_q = np.reshape(self.qvals, (2, 250)) reshaped_r = self.rvals.reshape(2, 250) calc = _creflect.abeles(reshaped_q, s) assert_equal(reshaped_r.shape, calc.shape) assert_almost_equal(reshaped_r, calc, 15)
def test_c_py_abeles_absorption(self): # https://github.com/andyfaff/refl1d_analysis/tree/master/notebooks q = np.linspace(0.008, 0.05, 500) depth = [0, 850, 0] rho = [2.067, 4.3, 6.] irho_zero = [0., 0.1, 0.] refnx_sigma = [np.nan, 35, 5.] w_zero = np.c_[depth, rho, irho_zero, refnx_sigma] py_abeles = _reflect.abeles(q, w_zero) c_abeles = _creflect.abeles(q, w_zero) assert_almost_equal(py_abeles, c_abeles)
def reflectivity(self, q, threads=0): """ Calculate theoretical reflectivity of this structure Parameters ---------- q : array-like Q values (Angstrom**-1) for evaluation threads : int, optional Specifies the number of threads for parallel calculation. This option is only applicable if you are using the ``_creflect`` module. The option is ignored if using the pure python calculator, ``_reflect``. If `threads == 0` then all available processors are used. """ return refcalc.abeles(q, self.slabs[..., :4], threads=threads)
def test_compare_refl1d(self): # refl1d calculated with: # from refl1d import abeles # x = np.linspace(0.005, 0.5, 1001) # z = abeles.refl(x / 2, # [0, 100, 200, 0], # [2.07, 3.45, 5., 6.], # irho=[0.0, 0.1, 0.01, 0], # sigma=[3, 1, 5, 0]) # a = z.real ** 2 + z.imag ** 2 layers = np.array([[0, 2.07, 0, 0], [100, 3.45, 0.1, 3], [200, 5.0, 0.01, 1], [0, 6., 0, 5]]) x = np.linspace(0.005, 0.5, 1001) calc1 = _reflect.abeles(x, layers) calc2 = _creflect.abeles(x, layers) refl1d = np.load(os.path.join(self.pth, 'refl1d.npy')) assert_almost_equal(calc1, refl1d) assert_almost_equal(calc2, refl1d)
def reflectivity(q, slabs, scale=1., bkg=0., dq=5., quad_order=17, threads=-1): r""" Abeles matrix formalism for calculating reflectivity from a stratified medium. Parameters ---------- q : np.ndarray The qvalues required for the calculation. :math:`Q=\frac{4Pi}{\lambda}\sin(\Omega)`. Units = Angstrom**-1 slabs : np.ndarray coefficients required for the calculation, has shape (2 + N, 4), where N is the number of layers - slabs[0, 0] ignored - slabs[N, 0] thickness of layer N - slabs[N+1, 0] ignored - slabs[0, 1] SLD_real of fronting (/1e-6 Angstrom**-2) - slabs[N, 1] SLD_real of layer N (/1e-6 Angstrom**-2) - slabs[-1, 1] SLD_real of backing (/1e-6 Angstrom**-2) - slabs[0, 2] SLD_imag of fronting (/1e-6 Angstrom**-2) - slabs[N, 2] iSLD_imag of layer N (/1e-6 Angstrom**-2) - slabs[-1, 2] iSLD_imag of backing (/1e-6 Angstrom**-2) - slabs[0, 3] ignored - slabs[N, 3] roughness between layer N-1/N - slabs[-1, 3] roughness between backing and layer N scale : float scale factor. All model values are multiplied by this value before the background is added bkg : float Q-independent constant background added to all model values. dq : float or np.ndarray, optional - `dq == 0` no resolution smearing is employed. - `dq` is a float a constant dQ/Q resolution smearing is employed. For 5% resolution smearing supply 5. - `dq` is the same shape as q the array contains the FWHM of a Gaussian approximated resolution kernel. Point by point resolution smearing is employed. Use this option if dQ/Q varies across your dataset. - `dq.ndim == q.ndim + 2` and `q.shape == dq[..., -3].shape` an individual resolution kernel is applied to each measurement point. This resolution kernel is a probability distribution function (PDF). `dqvals` will have the shape (qvals.shape, 2, M). There are `M` points in the kernel. `dq[:, 0, :]` holds the q values for the kernel, `dq[:, 1, :]` gives the corresponding probability. quad_order: int, optional the order of the Gaussian quadrature polynomial for doing the resolution smearing. default = 17. Don't choose less than 13. If quad_order == 'ultimate' then adaptive quadrature is used. Adaptive quadrature will always work, but takes a _long_ time (2 or 3 orders of magnitude longer). Fixed quadrature will always take a lot less time. BUT it won't necessarily work across all samples. For example, 13 points may be fine for a thin layer, but will be atrocious at describing a multilayer with bragg peaks. threads: int, optional Specifies the number of threads for parallel calculation. This option is only applicable if you are using the ``_creflect`` module. The option is ignored if using the pure python calculator, ``_reflect``. If `threads == -1` then all available processors are used. Example ------- >>> from refnx.reflect import reflectivity >>> q = np.linspace(0.01, 0.5, 1000) >>> slabs = np.array([[0, 2.07, 0, 0], ... [100, 3.47, 0, 3], ... [500, -0.5, 0.00001, 3], ... [0, 6.36, 0, 3]]) >>> print(reflectivity(q, slabs)) """ # constant dq/q smearing if isinstance(dq, numbers.Real) and float(dq) == 0: return refcalc.abeles(q, slabs, scale=scale, bkg=bkg, threads=threads) elif isinstance(dq, numbers.Real): dq = float(dq) return (scale * _smeared_abeles_constant(q, slabs, dq, threads=threads)) + bkg # point by point resolution smearing (each q point has different dq/q) if isinstance(dq, np.ndarray) and dq.size == q.size: dqvals_flat = dq.flatten() qvals_flat = q.flatten() # adaptive quadrature if quad_order == 'ultimate': smeared_rvals = (scale * _smeared_abeles_adaptive( qvals_flat, slabs, dqvals_flat, threads=threads) + bkg) return smeared_rvals.reshape(q.shape) # fixed order quadrature else: smeared_rvals = ( scale * _smeared_abeles_fixed(qvals_flat, slabs, dqvals_flat, quad_order=quad_order, threads=threads) + bkg) return np.reshape(smeared_rvals, q.shape) # resolution kernel smearing elif (isinstance(dq, np.ndarray) and dq.ndim == q.ndim + 2 and dq.shape[0:q.ndim] == q.shape): qvals_for_res = dq[:, 0, :] # work out the reflectivity at the kernel evaluation points smeared_rvals = refcalc.abeles(qvals_for_res, slabs, threads=threads) # multiply by probability smeared_rvals *= dq[:, 1, :] # now do simpson integration rvals = scipy.integrate.simps(smeared_rvals, x=dq[:, 0, :]) return scale * rvals + bkg return None
def test_c_abeles_multithreaded(self): _creflect.abeles(self.qvals, self.structure.slabs()[..., :4], threads=4)