|
1 | 1 | Tutorials |
2 | 2 | ========= |
3 | 3 |
|
4 | | -Examples usage of ``diffpy.cmi`` can be found at `this GitHub repo <https://github.com/diffpy/add2019-diffpy-cmi>`_. Tutorials coming soon... |
| 4 | +The atomic pair distribution function (PDF) provides a powerful route to understanding the local atomic structure of materials in real space. |
| 5 | +Despite its utility, PDF fitting and analysis can be challenging. |
| 6 | +The process of understand PDFs requires time, care, and the development of intuition to connect structural models to experimental data. |
| 7 | +Hopefully by the end of this tutorial series, PDF fitting will go from being a mysterious black box to a powerful tool in your structural analysis. |
| 8 | + |
| 9 | +Example usage of ``diffpy.cmi`` can be found at `this GitHub repo <https://github.com/diffpy/pdfttp_data>`_. |
| 10 | + |
| 11 | +.. _structural-parameters: |
| 12 | + |
| 13 | +Structural parameters |
| 14 | +--------------------- |
| 15 | + |
| 16 | +Before we start fitting PDFs, lets first understand the parameters that will be refined on and what they mean. |
| 17 | +It is important to understand these parameters as they will help you gain better insight into the fitting process and |
| 18 | +how to interpret the results. |
| 19 | + |
| 20 | +.. include:: ../snippets/structure_params.rst |
| 21 | + |
| 22 | +.. _dataset-parameters: |
| 23 | + |
| 24 | +Dataset parameters |
| 25 | +------------------ |
| 26 | + |
| 27 | +.. include:: ../snippets/data_params.rst |
| 28 | + |
| 29 | + |
| 30 | +Bulk Ni PDF fitting |
| 31 | +------------------- |
| 32 | +Although it is easier (and recommended) to fit PDFs of bulk homogeneous materials in `PDFgui <https://github.com/diffpy/diffpy.pdfgui>`_, |
| 33 | +fitting a bulk PDF in ``diffpy.cmi`` will provide you with a deeper insight into the fitting process. Additionally, this pedagogical |
| 34 | +example will help you build up confidence in using ``diffpy.cmi``. |
| 35 | + |
| 36 | +For bulk PDFs, we highly recommend using PDFgui. PDFgui is a great stepping stone before using ``diffpy.cmi``. |
| 37 | +If you don't know how to use PDFgui, please see this |
| 38 | +`PDFgui tutorial <https://www.diffpy.org/diffpy.pdfgui/tutorial.html#lesson-1-creating-simple-fit-of-ni-pdf>`_ |
| 39 | +to fit the same Ni PDF with PDFgui. |
| 40 | + |
| 41 | +.. admonition:: Data Download |
| 42 | + |
| 43 | + First, visit `this link <https://github.com/Billingegroup/pdfttp_data/raw/main/ch03NiModelling/data/Ni.cif>`_ and download the files for this tutorial: |
| 44 | + |
| 45 | + - ``Ni.cif`` |
| 46 | + - ``Ni.gr`` |
| 47 | + |
| 48 | +1. To start, we must first import all necessary modules and packages. |
| 49 | + |
| 50 | + .. code-block:: python |
| 51 | +
|
| 52 | + import matplotlib.pyplot as plt |
| 53 | + import numpy as np |
| 54 | +
|
| 55 | + from diffpy.utils.parsers.loaddata import loadData |
| 56 | + from diffpy.structure import loadStructure |
| 57 | + from diffpy.srfit.fitbase import FitContribution, FitRecipe |
| 58 | + from diffpy.srfit.fitbase import Profile |
| 59 | + from diffpy.srfit.pdf import PDFParser, PDFGenerator |
| 60 | + from diffpy.structure.parsers import getParser |
| 61 | + from diffpy.srfit.structure import constrainAsSpaceGroup |
| 62 | +
|
| 63 | +2. As a sanity check, lets load the cif and PDF data to see what they look like. |
| 64 | + |
| 65 | + .. code-block:: python |
| 66 | +
|
| 67 | + stru_path = "~/path/to/data/Ni.cif" |
| 68 | + data_path = "~/path/to/data/Ni.gr" |
| 69 | + # Load the structure and PDF data |
| 70 | + structure = loadStructure(stru_path) |
| 71 | + pdf_data = loadData(data_path) |
| 72 | + print("Ni cif file:") |
| 73 | + print(structure) |
| 74 | +
|
| 75 | + r = pdf_data[:, 0] |
| 76 | + g = pdf_data[:, 1] |
| 77 | + plt.plot(r, g, label="PDF Data") |
| 78 | + plt.xlabel("r (Angstrom)") |
| 79 | + plt.ylabel("G(r)") |
| 80 | + plt.title("Ni PDF Data") |
| 81 | + plt.legend() |
| 82 | + plt.show() |
| 83 | +
|
| 84 | +3. Once you see the data and have a feel for it, we can begin the fitting process. We will first define |
| 85 | + the :ref:`dataset-parameters` and :ref:`Structural parameters <structural-parameters>`. These values |
| 86 | + will be used to initialize our fit. Given this data, these values are reasonable starting points. |
| 87 | + |
| 88 | + .. code-block:: python |
| 89 | +
|
| 90 | + PDF_RMIN = 1.5 |
| 91 | + PDF_RMAX = 50 |
| 92 | + PDF_RSTEP = 0.01 |
| 93 | + QMAX = 25 |
| 94 | + QMIN = 0.1 |
| 95 | + CUBICLAT_I = 3.52 |
| 96 | + SCALE_I = 0.4 |
| 97 | + UISO_I = 0.005 |
| 98 | + DELTA2_I = 2 |
| 99 | + QDAMP_I = 0.04 |
| 100 | + QBROAD_I = 0.02 |
| 101 | +
|
| 102 | +4. Now, we will define a function called ``make_recipe``. |
| 103 | + This function will essentially tell the fit what to do. It contains all the necessary parameters and |
| 104 | + contributions for the PDF fitting process. Because looking at a function with a lot of code is scary, |
| 105 | + here is a gist of what the function does. |
| 106 | + |
| 107 | + |
| 108 | + .. image:: ../img/makerecipe_flow.png |
| 109 | + :alt: codecov-in-pr-comment |
| 110 | + :width: 210px |
| 111 | + :align: right |
| 112 | + |
| 113 | + - **Parses PDF data:** Given the PDF data file it extracts the Q_min and Q_max values. |
| 114 | + - **Parses cif file:** Given the cif file it extracts the structure and space group information. |
| 115 | + - **Simulates a PDF:** Given the structure, it computes a simulated PDF over predefined r-range. |
| 116 | + - **Creates a Fit Contribution:** This will hold the simulated PDF and the experimental PDF. |
| 117 | + - **Creates a Fit Recipe:** This is the main recipe that combines all contributions and parameters for the fit. |
| 118 | + - **Refines params based on Fit Recipe:** When the function is called, the parameters defined in the fit recipe are refined to best fit the data. |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + .. code-block:: python |
| 123 | +
|
| 124 | + def make_recipe(cif_path, dat_path): |
| 125 | +
|
| 126 | + # 1. Get structural info |
| 127 | + p_cif = getParser('cif') # Get the parser for CIF files |
| 128 | + stru = p_cif.parseFile(cif_path) # Using the parser, load the structure from the CIF |
| 129 | + sg = p_cif.spacegroup.short_name # Get the space group to constrain the fit later on |
| 130 | +
|
| 131 | + # 2. Get PDF data |
| 132 | + profile = Profile() # Create a Profile object to hold PDF data and metadata |
| 133 | + parser = PDFParser() # Create a PDFParser to get the appropriate Q_min and Q_max |
| 134 | + parser.parseFile(dat_path) # Parse the PDF data file |
| 135 | + profile.loadParsedData(parser) # Load the parsed PDF data into the profile |
| 136 | + profile.setCalculationRange( |
| 137 | + xmin=PDF_RMIN, xmax=PDF_RMAX, dx=PDF_RSTEP |
| 138 | + ) # Set the calculation range for the PDF fit |
| 139 | +
|
| 140 | + # 3. Create a PDFGenerator to generate a simulated PDF from the structure |
| 141 | + genpdf = PDFGenerator("generatedPDF") # Give the generator a name |
| 142 | + genpdf.setStructure(stru, periodic=True) # Give the generator the structure |
| 143 | +
|
| 144 | + # 4. Create a Fit Contribution object |
| 145 | + contribution = FitContribution("niPDFfit") # Give the contribution a name |
| 146 | + contribution.addProfileGenerator(genpdf) # Add the PDFGenerator to the contribution |
| 147 | +
|
| 148 | + # 5. Add the Profile to the contribution |
| 149 | + contribution.setProfile(profile, xname="r") # Add the Profile to the contribution |
| 150 | +
|
| 151 | + # 6. Set the equation used to combine the simulated PDF with the experimental PDF |
| 152 | + contribution.setEquation("s1*generatedPDF") # scaling factor for the simulated PDF |
| 153 | +
|
| 154 | + # 7. Create a Fit Recipe which turns our physics model into a mathematical recipe |
| 155 | + recipe = FitRecipe() |
| 156 | + recipe.addContribution(contribution) # Add the contribution to the recipe |
| 157 | +
|
| 158 | + # 8. Initialize the experimental parameters |
| 159 | + recipe.niPDFfit.generatedPDF.qdamp.value = QDAMP_I |
| 160 | + recipe.niPDFfit.generatedPDF.qbroad.value = QBROAD_I |
| 161 | + recipe.niPDFfit.generatedPDF.setQmin(QMIN) |
| 162 | + recipe.niPDFfit.generatedPDF.setQmax(QMAX) |
| 163 | +
|
| 164 | + # 9. Add scaling factor to the recipe |
| 165 | + recipe.addVar(contribution.s1, value=SCALE_I, tag="scale") |
| 166 | +
|
| 167 | + # 10. Add structural params to recipe, constraining them to the space group |
| 168 | + spacegroupparams = constrainAsSpaceGroup(genpdf.phase, sg) # Extract SG constraints |
| 169 | + for par in spacegroupparams.latpars: # Iterate over constrained lattice parameters |
| 170 | + recipe.addVar(par, |
| 171 | + value=CUBICLAT_I, |
| 172 | + name="fcc_lat", |
| 173 | + tag="lat") |
| 174 | + for par in spacegroupparams.adppars: # Iterate over constrained ADPs |
| 175 | + recipe.addVar(par, |
| 176 | + value=UISO_I, |
| 177 | + fixed=False, |
| 178 | + name="fcc_ADP", |
| 179 | + tag="adp") |
| 180 | + recipe.addVar(genpdf.delta2, |
| 181 | + name="Ni_Delta2", |
| 182 | + value=DELTA2_I, |
| 183 | + tag="d2") # Add delta2 parameter for PDF peak broadening |
| 184 | +
|
| 185 | + # 11. Add instrumental Qdamp and Qbroad parameters to the recipe |
| 186 | + recipe.addVar(genpdf.qdamp, |
| 187 | + fixed=False, |
| 188 | + name="Calib_Qdamp", |
| 189 | + value=QDAMP_I, |
| 190 | + tag="inst") |
| 191 | + recipe.addVar(genpdf.qbroad, |
| 192 | + fixed=False, |
| 193 | + name="Calib_Qbroad", |
| 194 | + value=QBROAD_I, |
| 195 | + tag="inst") |
| 196 | + return recipe |
| 197 | +
|
| 198 | +5. Now we can call ``make_recipe`` and plot the fit, experimental, and difference curves to evaluate how well the data fit. |
| 199 | + |
| 200 | + .. code-block:: python |
| 201 | +
|
| 202 | + recipe = make_recipe(cif_path, dat_path) # Call the function to create the fit recipe |
| 203 | + recipe() |
| 204 | + r = recipe.pdfcontrib.profile.x |
| 205 | + g = recipe.pdfcontrib.profile.y |
| 206 | + gcalc = recipe.pdfcontrib.profile.ycalc |
| 207 | + diffzero = -0.65 * max(g) * np.ones_like(g) # offset the difference curve |
| 208 | + diff = g - gcalc + diffzero |
| 209 | +
|
| 210 | + plt.figure(figsize=(15, 6)) |
| 211 | + plt.scatter(r, g, label="Experimental PDF") |
| 212 | + plt.plot(r, gcalc, label="Fitted PDF") |
| 213 | + plt.plot(r, diff, label="Difference") |
| 214 | +
|
| 215 | + plt.xlabel("r (Angstrom)") |
| 216 | + plt.ylabel("G(r)") |
| 217 | + plt.title("Ni PDF Fit") |
| 218 | + plt.legend() |
| 219 | + plt.show() |
| 220 | +
|
| 221 | + Once you run the above code, you should see a plot similar to the one below. |
| 222 | + |
| 223 | + .. image:: ../img/Ni_Fit.png |
| 224 | + :alt: codecov-in-pr-comment |
| 225 | + :width: 700px |
| 226 | + :align: center |
| 227 | + |
| 228 | +Congratulations! You have successfully fit a bulk Ni PDF using ``diffpy.cmi``. You are now one step closer to becoming a PDF fitting expert. |
0 commit comments