Creation of a calibrant file
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.
In pyFAI, calibrant are defined in a bunch of files available from github. Those files are automatically installed with pyFAI and readily available from the programming interface, as described in the Calibrant tutorial.
This tutorials focuses on the content of the file and how to generate other calibrant files and how to exended existing ones.
Content of the file
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. 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.
One may think it would have been better/simpler to describe the cell of the material. 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. Neverthless this tutorial will be focused on how to generate calibrant files fron crystal structures.
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.
# Calibrant: Silver Behenate (AgBh)
# Pseudocrystal a=inf b=inf c=58.380
# Ref: doi:10.1107/S0021889899012388
5.83800000e+01 # (0,0,1)
2.91900000e+01 # (0,0,2)
1.94600000e+01 # (0,0,3)
1.45950000e+01 # (0,0,4)
1.16760000e+01 # (0,0,5)
9.73000000e+00 # (0,0,6)
8.34000000e+00 # (0,0,7)
7.29750000e+00 # (0,0,8)
6.48666667e+00 # (0,0,9)
(...)
The Cell class
To generate a calibrant file from a crystal structure, the easiest is to use the pyFAI.calibrant.Cell class.
[1]:
import pyFAI
print("using pyFAI version: ", pyFAI.version)
from pyFAI.calibrant import Cell
print(Cell.__doc__)
print(Cell.__init__.__doc__)
using pyFAI version: 0.20.0-beta1
This is a cell object, able to calculate the volume and d-spacing according to formula from:
http://geoweb3.princeton.edu/research/MineralPhy/xtalgeometry.pdf
Constructor of the Cell class:
Crystalographic units are Angstrom for distances and degrees for angles !
:param a,b,c: unit cell length in Angstrom
:param alpha, beta, gamma: unit cell angle in degrees
:param lattice: "cubic", "tetragonal", "hexagonal", "rhombohedral", "orthorhombic", "monoclinic", "triclinic"
:param lattice_type: P, I, F, C or R
The constructor of the class is used to build and well suited to triclinic crystal.
Specific constructors
[2]:
Po = Cell.cubic(3.359)
print(Po)
Primitive cubic cell a=3.3590 b=3.3590 c=3.3590 alpha=90.000 beta=90.000 gamma=90.000
[3]:
print(Po.volume)
37.899197279
[4]:
print("Po.d_spacing?")
print(Po.d_spacing.__doc__)
print("Po.save?")
print(Po.save.__doc__)
Po.d_spacing?
Calculate all d-spacing down to dmin
applies selection rules
:param dmin: minimum value of spacing requested
:return: dict d-spacing as string, list of tuple with Miller indices
preceded with the numerical value
Po.save?
Save informations about the cell in a d-spacing file, usable as Calibrant
:param name: name of the calibrant
:param doi: reference of the publication used to parametrize the cell
:param dmin: minimal d-spacing
:param dest_dir: name of the directory where to save the result
To generate a d-spacing file usable as calibrant, one simply has to call the save method of the Cell instance.
Nota: the “.D” suffix is automatically appended.
[5]:
Po.save("Po",doi="http://www.periodictable.com/Elements/084/data.html", dmin=1)
[6]:
with open("Po.D") as f:
for l in f:
print(l.strip())
# Calibrant: Po
# Primitive cubic cell a=3.3590 b=3.3590 c=3.3590 alpha=90.000 beta=90.000 gamma=90.000
# Ref: http://www.periodictable.com/Elements/084/data.html
3.35900000 # (1, 0, 0) 6
2.37517168 # (1, 1, 0) 12
1.93931955 # (1, 1, 1) 8
1.67950000 # (2, 0, 0) 6
1.50219047 # (2, 1, 0) 24
1.37130601 # (2, 1, 1) 24
1.18758584 # (2, 2, 0) 12
1.11966667 # (3, 0, 0) 30
1.06220907 # (3, 1, 0) 24
1.01277661 # (3, 1, 1) 24
Other Examples: LaB6
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 at a=4.156826.
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:
[7]:
LaB6 = Cell.cubic(4.156826)
%time d=LaB6.d_spacing(0.1)
print("Number of reflections: %s"%len(d))
CPU times: user 872 ms, sys: 4.06 ms, total: 876 ms
Wall time: 876 ms
Number of reflections: 1441
[8]:
LaB6.save("my_LaB6",doi="https://www-s.nist.gov/srmors/certificates/660C.pdf", dmin=0.1)
[9]:
#Print the 10 first lines:
with open("my_LaB6.D") as f:
for i, l in enumerate(f):
if i<10:
print(l.strip())
else:
break
# Calibrant: my_LaB6
# Primitive cubic cell a=4.1568 b=4.1568 c=4.1568 alpha=90.000 beta=90.000 gamma=90.000
# Ref: https://www-s.nist.gov/srmors/certificates/660C.pdf
4.15682600 # (1, 0, 0) 6
2.93931985 # (1, 1, 0) 12
2.39994461 # (1, 1, 1) 8
2.07841300 # (2, 0, 0) 6
1.85898910 # (2, 1, 0) 24
1.69701711 # (2, 1, 1) 24
1.46965993 # (2, 2, 0) 12
Other Examples: Silicon
Silicon is easy to obtain pure thanks to microelectronics industry. Its cell is a face centered cubic with a diamond like structure. The cell parameter is available from the NIST: a=5.431179 A.
Let’s compare the difference between the silicon structure and the equivalent FCC structure:
[10]:
Si = Cell.diamond(5.431179)
print(Si)
Face centered cubic cell a=5.4312 b=5.4312 c=5.4312 alpha=90.000 beta=90.000 gamma=90.000
[11]:
FCC = Cell.cubic(5.431179, "F")
print(FCC)
Face centered cubic cell a=5.4312 b=5.4312 c=5.4312 alpha=90.000 beta=90.000 gamma=90.000
Apparently, there is not difference. But to check it, let’s generate all lines down to 1A and compare them:
[12]:
sid = Si.d_spacing(1)
for key, val in sid.items():
print("%s: %s"%(sorted(val[1:][-1]),key))
[1, 1, 5]: 1.04523089e+00
[2, 2, 4]: 1.10863477e+00
[0, 0, 4]: 1.35779475e+00
[1, 3, 3]: 1.24599792e+00
[1, 1, 3]: 1.63756208e+00
[0, 2, 2]: 1.92021175e+00
[1, 1, 1]: 3.13569266e+00
[13]:
fccd = FCC.d_spacing(1)
for key, val in fccd.items():
print("%s: %s"%(sorted(val[1:][-1]),key))
[1, 1, 5]: 1.04523089e+00
[2, 2, 4]: 1.10863477e+00
[0, 2, 4]: 1.21444854e+00
[0, 0, 4]: 1.35779475e+00
[1, 3, 3]: 1.24599792e+00
[1, 1, 3]: 1.63756208e+00
[2, 2, 2]: 1.56784633e+00
[0, 2, 2]: 1.92021175e+00
[0, 0, 2]: 2.71558950e+00
[1, 1, 1]: 3.13569266e+00
So there are many more reflection in the FCC structure compared to the diamond-like structure: (4 2 0), (2 2 2) are extinct as h+k+l=4n and all even.
Selection rules
Cell object contain selection_rules which tells if a reflection is allowed or not. Those selection_rules can be introspected:
[14]:
print(Si.selection_rules)
print(FCC.selection_rules)
[<function Cell.set_type.<locals>.<lambda> at 0x7f4df4402700>, <function Cell.set_type.<locals>.<lambda> at 0x7f4df5c2d4c0>, <function Cell.diamond.<locals>.<lambda> at 0x7f4dcebd28b0>]
[<function Cell.set_type.<locals>.<lambda> at 0x7f4dcebd2af0>, <function Cell.set_type.<locals>.<lambda> at 0x7f4dcebd2dc0>]
The Si object has one additionnal selection rule which explains the difference: A specific rule telling that reflection with h+k+l=4n is forbidden when (h,k,l) are all even.
We will now have a look at the source code of those selection rules using the inspect module.
Nota: This is advanced Python hacking but useful for the demonstration
[15]:
import inspect
for rule in Si.selection_rules:
print(inspect.getsource(rule).strip())
self.selection_rules = [lambda h, k, l: not(h == 0 and k == 0 and l == 0)]
self.selection_rules.append(lambda h, k, l: (h % 2 + k % 2 + l % 2) in (0, 3))
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)))
Actually the last line correspond to an anonymous function (lambda) which implements this rule.
As we have seen previously one can simply adapt the Cell instance by simply appending extra selection rules to this list.
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.
Conclusion
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.