{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Creation of a calibrant file\n", "\n", "In this tutorial we will see how to generate a file describing a *reference material* used as calibrant to refine the geometry of the experimental setup, especially the position of the detector.\n", "\n", "In pyFAI, calibrant are defined in a bunch of files available from [github](https://github.com/silx-kit/pyFAI/tree/master/calibration). Those files are automatically installed with pyFAI and readily available from the programming interface, as described in the **Calibrant** tutorial.\n", "\n", "This tutorials focuses on the content of the file and how to generate other calibrant files and how to exended existing ones.\n", "\n", "## Content of the file\n", "\n", "Calibrant files from *pyFAI* are heavily inspired from the ones used in *Fit2D*: they simply store the *d-spacing* between Miller plans and are simply loaded using the *numpy.loadtxt* function. \n", "In pyFAI we have improved with plent of comment (using **#**) to provide in addition the familly of Miller plan associated and the multiplicity, which, while not proportionnal to *Fobs* is somewhat related.\n", "\n", "One may think it would have been better/simpler to describe the cell of the material. \n", "Actually some calibrant, especially the ones used in SAXS like silver behenate (AgBh), but not only, are not well crystallized compounds and providing the *d-spacing* allows to use them as calibrant.\n", "Neverthless this tutorial will be focused on how to generate calibrant files fron crystal structures.\n", "\n", "Finally the *calibrant file* has a few lines of headers containing the name, the crystal cell parameters (if available) and quite importantly a reference to the source of information like a publication to allow the re-generation of the file if needed. \n", "\n", " # Calibrant: Silver Behenate (AgBh)\n", " # Pseudocrystal a=inf b=inf c=58.380\n", " # Ref: doi:10.1107/S0021889899012388\n", " 5.83800000e+01 # (0,0,1)\n", " 2.91900000e+01 # (0,0,2)\n", " 1.94600000e+01 # (0,0,3)\n", " 1.45950000e+01 # (0,0,4)\n", " 1.16760000e+01 # (0,0,5)\n", " 9.73000000e+00 # (0,0,6)\n", " 8.34000000e+00 # (0,0,7)\n", " 7.29750000e+00 # (0,0,8)\n", " 6.48666667e+00 # (0,0,9)\n", " (...)\n", "\n", "## The Cell class \n", "\n", "To generate a *calibrant file* from a crystal structure, the easiest is to use the **pyFAI.calibrant.Cell** class." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "using pyFAI version: 0.20.0-beta1\n", "\n", " This is a cell object, able to calculate the volume and d-spacing according to formula from:\n", "\n", " http://geoweb3.princeton.edu/research/MineralPhy/xtalgeometry.pdf\n", " \n", "Constructor of the Cell class:\n", "\n", " Crystalographic units are Angstrom for distances and degrees for angles !\n", "\n", " :param a,b,c: unit cell length in Angstrom\n", " :param alpha, beta, gamma: unit cell angle in degrees\n", " :param lattice: \"cubic\", \"tetragonal\", \"hexagonal\", \"rhombohedral\", \"orthorhombic\", \"monoclinic\", \"triclinic\"\n", " :param lattice_type: P, I, F, C or R\n", " \n" ] } ], "source": [ "import pyFAI\n", "print(\"using pyFAI version: \", pyFAI.version)\n", "from pyFAI.calibrant import Cell\n", "print(Cell.__doc__)\n", "print(Cell.__init__.__doc__)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The constructor of the class is used to build and well suited to triclinic crystal. \n", "\n", "### Specific constructors ###\n", "\n", "Nevertheless, most used calibrants are of much higher symmetry, like cubic which takes only **one** parameter. \n", "Here is an example for defining [Polonium](http://www.periodictable.com/Elements/084/data.html) which is a simple cubic cell (Primitive) with a cell parameter of 335.9pm. This example was chosen as Polonium is apparently the only element with such primitive cubic packing." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Primitive cubic cell a=3.3590 b=3.3590 c=3.3590 alpha=90.000 beta=90.000 gamma=90.000\n" ] } ], "source": [ "Po = Cell.cubic(3.359)\n", "print(Po)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "37.899197279\n" ] } ], "source": [ "print(Po.volume)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Po.d_spacing?\n", "Calculate all d-spacing down to dmin\n", "\n", " applies selection rules\n", "\n", " :param dmin: minimum value of spacing requested\n", " :return: dict d-spacing as string, list of tuple with Miller indices\n", " preceded with the numerical value\n", " \n", "Po.save?\n", "Save informations about the cell in a d-spacing file, usable as Calibrant\n", "\n", " :param name: name of the calibrant\n", " :param doi: reference of the publication used to parametrize the cell\n", " :param dmin: minimal d-spacing\n", " :param dest_dir: name of the directory where to save the result\n", " \n" ] } ], "source": [ "print(\"Po.d_spacing?\")\n", "print(Po.d_spacing.__doc__)\n", "print(\"Po.save?\")\n", "print(Po.save.__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To generate a *d-spacing* file usable as calibrant, one simply has to call the *save* method of the *Cell* instance.\n", "\n", "**Nota:** the \".D\" suffix is automatically appended." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "Po.save(\"Po\",doi=\"http://www.periodictable.com/Elements/084/data.html\", dmin=1)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# Calibrant: Po\n", "# Primitive cubic cell a=3.3590 b=3.3590 c=3.3590 alpha=90.000 beta=90.000 gamma=90.000\n", "# Ref: http://www.periodictable.com/Elements/084/data.html\n", "3.35900000 # (1, 0, 0) 6\n", "2.37517168 # (1, 1, 0) 12\n", "1.93931955 # (1, 1, 1) 8\n", "1.67950000 # (2, 0, 0) 6\n", "1.50219047 # (2, 1, 0) 24\n", "1.37130601 # (2, 1, 1) 24\n", "1.18758584 # (2, 2, 0) 12\n", "1.11966667 # (3, 0, 0) 30\n", "1.06220907 # (3, 1, 0) 24\n", "1.01277661 # (3, 1, 1) 24\n" ] } ], "source": [ "with open(\"Po.D\") as f:\n", " for l in f:\n", " print(l.strip())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other Examples: LaB6 ###\n", "\n", "Lantanide Hexaboride, probably my favorite calibrant as it has a primitive cubic cell hence all reflections are valid and intense. The cell parameter is available from the [NIST](https://www-s.nist.gov/srmors/certificates/660C.pdf) at a=4.156826. \n", "\n", "The number of reflections in a file is controled by the *dmin* parameter. The lower it is, the more lines there are and the more time-consuming this will be:\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 872 ms, sys: 4.06 ms, total: 876 ms\n", "Wall time: 876 ms\n", "Number of reflections: 1441\n" ] } ], "source": [ "LaB6 = Cell.cubic(4.156826)\n", "%time d=LaB6.d_spacing(0.1)\n", "print(\"Number of reflections: %s\"%len(d))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "LaB6.save(\"my_LaB6\",doi=\"https://www-s.nist.gov/srmors/certificates/660C.pdf\", dmin=0.1)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# Calibrant: my_LaB6\n", "# Primitive cubic cell a=4.1568 b=4.1568 c=4.1568 alpha=90.000 beta=90.000 gamma=90.000\n", "# Ref: https://www-s.nist.gov/srmors/certificates/660C.pdf\n", "4.15682600 # (1, 0, 0) 6\n", "2.93931985 # (1, 1, 0) 12\n", "2.39994461 # (1, 1, 1) 8\n", "2.07841300 # (2, 0, 0) 6\n", "1.85898910 # (2, 1, 0) 24\n", "1.69701711 # (2, 1, 1) 24\n", "1.46965993 # (2, 2, 0) 12\n" ] } ], "source": [ "#Print the 10 first lines:\n", "with open(\"my_LaB6.D\") as f:\n", " for i, l in enumerate(f):\n", " if i<10:\n", " print(l.strip())\n", " else:\n", " break\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Other Examples: Silicon ###\n", "\n", "Silicon is easy to obtain **pure** thanks to microelectronics industry. \n", "Its cell is a face centered cubic with a diamond like structure. \n", "The cell parameter is available from the [NIST](https://www-s.nist.gov/srmors/certificates/640E.pdf): a=5.431179 A. \n", "\n", "Let's compare the difference between the silicon structure and the equivalent FCC structure:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Face centered cubic cell a=5.4312 b=5.4312 c=5.4312 alpha=90.000 beta=90.000 gamma=90.000\n" ] } ], "source": [ "Si = Cell.diamond(5.431179)\n", "print(Si)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Face centered cubic cell a=5.4312 b=5.4312 c=5.4312 alpha=90.000 beta=90.000 gamma=90.000\n" ] } ], "source": [ "FCC = Cell.cubic(5.431179, \"F\")\n", "print(FCC)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Apparently, there is not difference. \n", "But to check it, let's generate all lines down to 1A and compare them:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 1, 5]: 1.04523089e+00\n", "[2, 2, 4]: 1.10863477e+00\n", "[0, 0, 4]: 1.35779475e+00\n", "[1, 3, 3]: 1.24599792e+00\n", "[1, 1, 3]: 1.63756208e+00\n", "[0, 2, 2]: 1.92021175e+00\n", "[1, 1, 1]: 3.13569266e+00\n" ] } ], "source": [ "sid = Si.d_spacing(1)\n", "for key, val in sid.items():\n", " print(\"%s: %s\"%(sorted(val[1:][-1]),key))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 1, 5]: 1.04523089e+00\n", "[2, 2, 4]: 1.10863477e+00\n", "[0, 2, 4]: 1.21444854e+00\n", "[0, 0, 4]: 1.35779475e+00\n", "[1, 3, 3]: 1.24599792e+00\n", "[1, 1, 3]: 1.63756208e+00\n", "[2, 2, 2]: 1.56784633e+00\n", "[0, 2, 2]: 1.92021175e+00\n", "[0, 0, 2]: 2.71558950e+00\n", "[1, 1, 1]: 3.13569266e+00\n" ] } ], "source": [ "fccd = FCC.d_spacing(1)\n", "for key, val in fccd.items():\n", " print(\"%s: %s\"%(sorted(val[1:][-1]),key))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So there are many more reflection in the FCC structure compared to the diamond-like structure: (4 2 0), (2 2 2) are extinct\n", "as h+k+l=4n and all even. \n", "\n", "### Selection rules ###\n", "Cell object contain *selection_rules* which tells if a reflection is allowed or not.\n", "Those *selection_rules* can be introspected:\n", "\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[. at 0x7f4df4402700>, . at 0x7f4df5c2d4c0>, . at 0x7f4dcebd28b0>]\n", "[. at 0x7f4dcebd2af0>, . at 0x7f4dcebd2dc0>]\n" ] } ], "source": [ "print(Si.selection_rules)\n", "print(FCC.selection_rules)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The *Si* object has one additionnal selection rule which explains the difference:\n", "A specific rule telling that reflection with h+k+l=4n is forbidden when (h,k,l) are all even.\n", "\n", "We will now have a look at the source code of those selection rules using the *inspect* module.\n", "\n", "**Nota:** This is advanced Python hacking but useful for the demonstration" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "self.selection_rules = [lambda h, k, l: not(h == 0 and k == 0 and l == 0)]\n", "self.selection_rules.append(lambda h, k, l: (h % 2 + k % 2 + l % 2) in (0, 3))\n", "self.selection_rules.append(lambda h, k, l: not((h % 2 == 0) and (k % 2 == 0) and (l % 2 == 0) and ((h + k + l) % 4 != 0)))\n" ] } ], "source": [ "import inspect\n", "for rule in Si.selection_rules: \n", " print(inspect.getsource(rule).strip())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Actually the last line correspond to an anonymous function (lambda) which implements this rule.\n", "\n", "As we have seen previously one can simply adapt the Cell instance by simply appending extra selection rules to this list.\n", "\n", "A selection rule is a function taking 3 integers as input and returning *True* if the reflection is allowed and *False* when it is forbidden by symmetry. By this way one can simply adapt existing object to generate the right *calibrant file*.\n", "\n", "## Conclusion\n", "In this tutorial we have seen the structure of a *calibrant file*, how to generate crystal structure cell object to write such calibrant files automatically, including all metadata needed for redistribution. Most advanced programmers can now modify the selection rules to remove forbidden reflection for a given cell.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1" } }, "nbformat": 4, "nbformat_minor": 1 }