From e567b8bfa8f49bcafa83ec0f0f6a6beda799c832 Mon Sep 17 00:00:00 2001 From: Clemens Prescher Date: Tue, 21 May 2024 22:32:40 +0200 Subject: [PATCH] updated api quickstart to new core changes --- docs/api.ipynb | 293 ++++++++++++++++++++++++++++--------------------- 1 file changed, 167 insertions(+), 126 deletions(-) diff --git a/docs/api.ipynb b/docs/api.ipynb index 93179ff..1e1cd40 100644 --- a/docs/api.ipynb +++ b/docs/api.ipynb @@ -2,224 +2,272 @@ "cells": [ { "cell_type": "markdown", + "id": "3f5f2045f189c08b", + "metadata": { + "collapsed": false + }, "source": [ "# API - Quickstart\n", "\n", "This is a quickstart guide to using the API. For more detailed information, see the \n", "[API Documentation](apidoc/glassure.core.rst)." - ], - "metadata": { - "collapsed": false - }, - "id": "3f5f2045f189c08b" + ] }, { "cell_type": "code", "execution_count": null, + "id": "cb02bb0c43bfaef1", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt" - ], - "metadata": { - "collapsed": false - }, - "id": "cb02bb0c43bfaef1" + ] }, { "cell_type": "markdown", + "id": "2c63bb84da0c8cc6", + "metadata": { + "collapsed": false + }, "source": [ "\n", "## Loading a sample pattern and the background\n", "\n", "The basic class for handling x-y data in Glassure is the `Pattern` class. It can be used to load data from a file\n", "or to create a pattern from scratch." - ], - "metadata": { - "collapsed": false - }, - "id": "2c63bb84da0c8cc6" + ] }, { "cell_type": "code", "execution_count": null, + "id": "aa8ca6a1ce32e54e", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "from glassure.core import Pattern\n", "from tests import data_path\n", "pattern = Pattern.from_file(data_path('Mg2SiO4_ambient.xy'))\n", "bkg = Pattern.from_file(data_path('Mg2SiO4_ambient_bkg.xy'))" - ], - "metadata": { - "collapsed": false - }, - "id": "aa8ca6a1ce32e54e" + ] }, { "cell_type": "markdown", - "source": [ - "## Plotting the data\n", - "The `Pattern` objects can be easily plotted using matplotlib:" - ], + "id": "75c6c0d863095b7a", "metadata": { "collapsed": false }, - "id": "75c6c0d863095b7a" + "source": [ + "## Plotting the data\n", + "The `Pattern` objects can be easily plotted using matplotlib:" + ] }, { "cell_type": "code", "execution_count": null, + "id": "852fe6aaec504de1", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "plt.figure()\n", "plt.plot(pattern.x, pattern.y, label='pattern')\n", "plt.plot(bkg.x, bkg.y, label='background')\n", "plt.legend();" - ], - "metadata": { - "collapsed": false - }, - "id": "852fe6aaec504de1" + ] }, { "cell_type": "markdown", + "id": "a44e2137d921c69b", + "metadata": { + "collapsed": false + }, "source": [ "## Subtracting the background\n", "\n", "`Pattern` objects can be subtracted from each other. The result is a new `Pattern` object. Here we use this to \n", "subtract the background from the pattern. Before we scale the background pattern differently." - ], - "metadata": { - "collapsed": false - }, - "id": "a44e2137d921c69b" + ] }, { "cell_type": "code", "execution_count": null, + "id": "c6fd31f915839d6c", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "bkg.scaling = 1.1\n", "sample = pattern - bkg" - ], - "metadata": { - "collapsed": false - }, - "id": "c6fd31f915839d6c" + ] }, { "cell_type": "markdown", - "source": [ - "## Calculating the Structure Factor $S(Q)$\n", - "\n", - "The structure can now be calculated. For this we need the composition, which will be given as a dictionary, and a density estimate of the sample:" - ], + "id": "f69dac6a00ca7523", "metadata": { "collapsed": false }, - "id": "f69dac6a00ca7523" + "source": [ + "## Calculating the Structure Factor $S(Q)$\n", + "\n", + "The Faber Ziman structure factor can now be calculated. For this we need the composition, which will be given as a dictionary, and a density estimate of the sample:" + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [ - "composition = {'Mg': 2, 'Si': 1, 'O': 4}\n", - "density = 2.7 # g/cm^3" - ], + "id": "2b39a13ca449ea4f", "metadata": { "collapsed": false }, - "id": "2b39a13ca449ea4f" + "outputs": [], + "source": [ + "from glassure.core.utility import convert_density_to_atoms_per_cubic_angstrom\n", + "\n", + "composition = {'Mg': 2, 'Si': 1, 'O': 4}\n", + "density = 2.7 # g/cm^3\n", + "atomic_density = convert_density_to_atoms_per_cubic_angstrom(composition, density) # atoms/A^3" + ] }, { "cell_type": "markdown", + "id": "d4e36dbe", + "metadata": {}, "source": [ - "Further, we cannot use the whole pattern, because, at the largest Q values there might not be any elastic scattering left. \n", - "We limit the pattern to the range from 0 to 16 A$^{-1}$:" - ], - "metadata": { - "collapsed": false - }, - "id": "9d872aef2bbc82bf" + "The most important part for calculating S(Q) is the normalization of the collected intensity to atomic units. We will utilize a fit method here. With this method the normalization factor is estimated by a match of the sample pattern with the sum of the squared mean form factors of the sample and the incoherent scattering." + ] }, { "cell_type": "code", "execution_count": null, + "id": "eaee75a3", + "metadata": {}, "outputs": [], "source": [ - "from glassure.core.calc import calculate_sq\n", + "from glassure.core.utility import calculate_f_squared_mean, calculate_incoherent_scattering\n", + "from glassure.core.normalization import normalize_fit\n", + "\n", + "\n", "sample = sample.limit(0, 16)\n", - "sq = calculate_sq(sample, density, composition)\n" - ], + "\n", + "f_squared_mean = calculate_f_squared_mean(composition, sample.x)\n", + "incoherent_scattering = calculate_incoherent_scattering(composition, sample.x)\n", + "params, normalized_pattern = normalize_fit(sample, f_squared_mean, incoherent_scattering, q_cutoff=5)\n", + "\n", + "n = params['n'].value\n", + "\n", + "plt.figure()\n", + "plt.plot(sample.x, n * sample.y, label='sample normalized')\n", + "plt.plot(sample.x, incoherent_scattering, label='incoherent')\n", + "plt.plot(sample.x, f_squared_mean + incoherent_scattering, label=' + incoherent')\n", + "plt.xlabel('Q ($\\\\AA^{-1}$)'); plt.ylabel('Intensity (a.u.)')\n", + "plt.legend(); plt.tight_layout();" + ] + }, + { + "cell_type": "markdown", + "id": "9d872aef2bbc82bf", "metadata": { "collapsed": false }, - "id": "ffde56dbbba7dc2d" + "source": [ + "With the normalized pattern and incoherent subtracted pattern available we can now calculate the Structure factor: " + ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, + "id": "ffde56dbbba7dc2d", + "metadata": { + "collapsed": false + }, + "outputs": [], "source": [ - "## Calculating the pair distribution function $g(r)$\n", + "from glassure.core.transform import calculate_sq\n", + "from glassure.core.utility import calculate_f_mean_squared\n", + "f_mean_squared = calculate_f_mean_squared(composition, sample.x)\n", "\n", - "The main function for calculating the transforms are inside the `glassure.core.calc` module. The `calculate_fr` function calculates the Fourier transform of the structure factor and the `calculate_gr` function calculates the pair distribution function from the Fourier transform." - ], + "sq = calculate_sq(normalized_pattern, f_squared_mean, f_mean_squared)\n", + "\n", + "plt.figure()\n", + "plt.plot(sq.x, sq.y, label='S(Q)')\n", + "plt.xlabel('Q ($\\\\AA^{-1}$)'); plt.ylabel('S(Q) (a.u.)')\n", + "plt.legend(); plt.tight_layout();" + ] + }, + { + "cell_type": "markdown", + "id": "ac9abe5750845db3", "metadata": { "collapsed": false }, - "id": "ac9abe5750845db3" + "source": [ + "## Calculating the pair distribution function $g(r)$\n", + "\n", + "The main function for calculating the transforms are inside the `glassure.core.transform` module. The `calculate_fr` function calculates the Fourier transform of the structure factor and the `calculate_gr` function calculates the pair distribution function from the Fourier transform." + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [ - "from glassure.core.calc import calculate_fr, calculate_gr\n", - "fr = calculate_fr(sq)\n", - "gr = calculate_gr(fr, density, composition)" - ], + "id": "b66c6c2c80900054", "metadata": { "collapsed": false }, - "id": "b66c6c2c80900054" + "outputs": [], + "source": [ + "from glassure.core.transform import calculate_fr, calculate_gr\n", + "fr = calculate_fr(sq)\n", + "gr = calculate_gr(fr, atomic_density)" + ] }, { "cell_type": "markdown", - "source": [ - "## Plotting the results" - ], + "id": "9f6dd0157d2c205e", "metadata": { "collapsed": false }, - "id": "9f6dd0157d2c205e" + "source": [ + "## Plotting the results" + ] }, { "cell_type": "code", "execution_count": null, + "id": "cccefeeb7ca24317", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "plt.figure(figsize=(12, 4))\n", "plt.subplot(131)\n", "plt.plot(sq.x, sq.y)\n", - "plt.xlabel('Q [$\\AA^{-1}$]')\n", + "plt.xlabel('Q [$\\\\AA^{-1}$]')\n", "plt.ylabel('S(Q)')\n", "plt.subplot(132)\n", "plt.plot(fr.x, fr.y)\n", - "plt.xlabel('r [$\\AA$]')\n", + "plt.xlabel('r [$\\\\AA$]')\n", "plt.ylabel('F(r)')\n", "plt.subplot(133)\n", "plt.plot(gr.x, gr.y)\n", - "plt.xlabel('r [$\\AA$]')\n", + "plt.xlabel('r [$\\\\AA$]')\n", "plt.ylabel('g(r)')\n", "plt.tight_layout()\n" - ], - "metadata": { - "collapsed": false - }, - "id": "cccefeeb7ca24317" + ] }, { "cell_type": "markdown", + "id": "882b89d3b0c2fb3d", + "metadata": { + "collapsed": false + }, "source": [ "## Refining the calculations\n", "\n", @@ -230,8 +278,8 @@ "\n", "Several ways have been proposed in the past to improve the results:\n", "\n", - " - the background can be refined using a polynomial fit - this is done by PDFGetX3 by default - but works poorly for amorphous materials and liquids\n", - " - removing unphysical oscillations in the low r region of the g(r), by back transforming this area into S(Q), this has been described by [Kaplow et al., 1965](https://doi.org/10.1103/PhysRev.138.A1336), performing this multiple times results in a more physical result - this is the default method chosen for glassure - typically the iterations number should be between 3 and 5, if more are necessary, other factors should be considered\n", + "- the background can be refined using a polynomial fit - this is done by PDFGetX3 by default - but works poorly for amorphous materials and liquids\n", + "- removing unphysical oscillations in the low r region of the g(r), by back transforming this area into S(Q), this has been described by [Kaplow et al., 1965](https://doi.org/10.1103/PhysRev.138.A1336), performing this multiple times results in a more physical result - this is the default method chosen for glassure - typically the iterations number should be between 3 and 5, if more are necessary, other factors should be considered\n", "\n", "### Extending the Q-range to zero\n", "It is often difficult to obtain diffraction at very large Q as well as in the low-Q region. However, the Fourier transform is affected by a limited Q-range, and for reasonable results the pattern should be extended to q = 0. The theoretical value for a monatomic liquid S(Q=0) is:\n", @@ -252,22 +300,19 @@ "\n", "### Using a filter function to remove Fourier transform artifacts\n", "The Fourier transformation from S(Q) to F(r) will always result in some artifacts, due to the limited Q-range of the S(Q) data. To suppress these artifacts we can employ a filter function during the transformation such as the Lorch-Modification function ([Lorch, 1969](https://doi.org/10.1088/0022-3719/2/2/305))\n" - ], - "metadata": { - "collapsed": false - }, - "id": "882b89d3b0c2fb3d" + ] }, { "cell_type": "code", "execution_count": null, + "id": "5bb593df6d702fdb", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "from glassure.core.optimization import optimize_sq\n", - "from glassure.core.utility import calculate_s0, convert_density_to_atoms_per_cubic_angstrom, extrapolate_to_zero_linear\n", - "\n", - "# we need the atomic density for the optimize_sq function later\n", - "atomic_density = convert_density_to_atoms_per_cubic_angstrom(composition, density)\n", + "from glassure.core.utility import calculate_s0\n", "\n", "# we need to extend the sq to q=0, for simplicity we use a step function, but\n", "# other more potent methods are available in the utility module\n", @@ -277,33 +322,33 @@ " iterations=5,\n", " atomic_density=atomic_density)\n", "fr_opt = calculate_fr(sq_opt)\n", - "gr_opt = calculate_gr(fr_opt, density, composition)\n", + "gr_opt = calculate_gr(fr_opt, atomic_density)\n", "\n", "fr_opt_lorch = calculate_fr(sq_opt, use_modification_fcn=True)\n", - "gr_opt_lorch = calculate_gr(fr_opt_lorch, density, composition)" - ], - "metadata": { - "collapsed": false - }, - "id": "5bb593df6d702fdb" + "gr_opt_lorch = calculate_gr(fr_opt_lorch, atomic_density)" + ] }, { "cell_type": "code", "execution_count": null, + "id": "7be7e315f76f147d", + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "plt.figure(figsize=(12, 4))\n", "plt.subplot(131)\n", "plt.plot(sq.x, sq.y, label='original')\n", "plt.plot(sq_opt.x, sq_opt.y, label='optimized')\n", - "plt.xlabel('Q [$\\AA^{-1}$]')\n", + "plt.xlabel('Q [$\\\\AA^{-1}$]')\n", "plt.ylabel('S(Q)')\n", "plt.legend()\n", "plt.subplot(132)\n", "plt.plot(fr.x, fr.y, label='original')\n", "plt.plot(fr_opt.x, fr_opt.y, label='optimized')\n", "plt.plot(fr_opt_lorch.x, fr_opt_lorch.y, label='optimized + Lorch')\n", - "plt.xlabel('r [$\\AA$]')\n", + "plt.xlabel('r [$\\\\AA$]')\n", "plt.ylabel('F(r)')\n", "plt.legend()\n", "plt.subplot(133)\n", @@ -311,39 +356,35 @@ "plt.plot(gr_opt.x, gr_opt.y, label='optimized')\n", "plt.plot(gr_opt_lorch.x, gr_opt_lorch.y, label='optimized + Lorch')\n", "plt.ylim(-1, 3)\n", - "plt.xlabel('r [$\\AA$]')\n", + "plt.xlabel('r [$\\\\AA$]')\n", "plt.ylabel('g(r)')\n", "plt.legend()\n", "plt.tight_layout();" - ], - "metadata": { - "collapsed": false - }, - "id": "7be7e315f76f147d" + ] }, { "cell_type": "markdown", + "id": "64af6e04fd910a36", + "metadata": { + "collapsed": false + }, "source": [ "It can be clearly seen that the Kaplow-Optimization successfully removes the unphysical oscillations in the low r region. However, the r_cutoff for the back and forward transformation has to be chosen carefully. Furthermore, it is of large importance that the optimization procedure does not completely distort the original structure factor. If the optimized structure factor and the original unoptimized structure differ a lot, please consider other corrections to the data before proceeding with the PDF Analysis. This optimization procedure should only be used as a final step. \n", "\n", "The Lorch-modification function further smooths the g(r) and removes the artifacts from the Fourier transformation. However, it also removes information from the g(r) (loss of resolution). \n" - ], - "metadata": { - "collapsed": false - }, - "id": "64af6e04fd910a36" + ] }, { "cell_type": "markdown", + "id": "b1e90b8e835f7407", + "metadata": { + "collapsed": false + }, "source": [ "## Conclusion\n", "\n", "This notebook showed how to use the Glassure API to calculate the structure factor and the pair distribution function from collected total scattering XRD data. It also showed how to optimize the S(Q). For more information on the possibilities in the Glassure API, please see the [API Documentation](apidoc/glassure.core.rst)." - ], - "metadata": { - "collapsed": false - }, - "id": "b1e90b8e835f7407" + ] } ], "metadata": { @@ -355,14 +396,14 @@ "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.12.3" } }, "nbformat": 4,