User guide: indexing reflections¶
New in version 1.3.0.
The topic of indexing reflections is a large one. The indexing functionality of
crystals
is intended to be general enough to be useable with x-ray,
electron, and neutron diffraction data. As such, all indexing routines presented
here expect that you have a list of peaks, already oriented in reciprocal space.
This way, the details of the scattering experiments are not important to crystals
.
General purpose direct indexing with DirAx¶
The first and simplest indexing routine, index_dirax()
, is based on the DirAx algorithm presented in:
For this example, we will need a list of reflections oriented in reciprocal space. Let’s start with a very simple example using the simplest crystal structure, cubic polonium:
>>> from crystals import Crystal
>>> import numpy as np
>>>
>>> indices = [ (0,0,0), (1,0,0), (0,1,0), (0,0,1) ]
>>>
>>> cryst = Crystal.from_database('Pu-epsilon') # cubic polonium
>>> peaks = [cryst.scattering_vector(hkl) for hkl in indices]
The list of peaks
are peak positions in three-dimensional reciprocal space. To index:
>>> from crystals import index_dirax
>>> lattice, hkls = index_dirax(peaks)
>>> lattice
< Lattice object with parameters 3.638Å, 3.638Å, 3.638Å, 90.00°, 90.00°, 90.00° >
>>> hkls.astype(int)
array([[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
Here, hkls
are the indices of the reflections in peaks
according to the indexing lattice, lattice
.
As expected, the Miller indices in hkls
match the ones we created in indices
.
Indexing a cubic lattice with perfect peak placement is pretty easy: we only needed four reflections. The DirAx algorithm is robust against missing reflections, but also alien reflections that do not belong there. In the next example, we will introduce 10% of non-fitting reflections:
>>> from crystals import Crystal, index_dirax
>>> import numpy as np
>>>
>>> cryst = Crystal.from_database('Pu-epsilon') # cubic polonium
>>> indices = list(cryst.bounded_reflections(3))
>>> num_aliens = len(indices) // 10 # 10% alien reflections
>>> aliens = [ np.random.random(size=(3,)) for _ in range(num_aliens) ]
>>>
>>> peaks = [cryst.scattering_vector(hkl) for hkl in indices + aliens]
>>> lattice, hkls = index_dirax(peaks)
>>> lattice
< Lattice object with parameters 3.638Å, 3.638Å, 3.638Å, 90.00°, 90.00°, 90.00° >
>>> hkls.round(decimals=3)
array([[-1. , -1. , -1. ],
[-1. , -1. , 0. ],
[-1. , -1. , 1. ],
[-1. , 0. , -1. ],
[-1. , 0. , 0. ],
[-1. , -0. , 1. ],
[-1. , 1. , -1. ],
[-1. , 1. , 0. ],
[-1. , 1. , 1. ],
[ 0. , -1. , -1. ],
[ 0. , -1. , 0. ],
[ 0. , -1. , 1. ],
[ 0. , 0. , -1. ],
[ 0. , 0. , 0. ],
[-0. , -0. , 1. ],
[-0. , 1. , -1. ],
[-0. , 1. , 0. ],
[-0. , 1. , 1. ],
[ 1. , -1. , -1. ],
[ 1. , -1. , 0. ],
[ 1. , -1. , 1. ],
[ 1. , 0. , -1. ],
[ 1. , 0. , 0. ],
[ 1. , -0. , 1. ],
[ 1. , 1. , -1. ],
[ 1. , 1. , 0. ],
[ 1. , 1. , 1. ],
[ 0.549, 0.715, 0.603],
[ 0.545, 0.424, 0.646]])
As you can see, the last indexed reflections in hkls
don’t have integer
Miller indices; those are the non-fitting (alien) reflections!
Of course, the DirAx algorithm is also resistant to noise. Here is an example where we add noise to our perfect peaks:
>>> from crystals import Crystal, Lattice, index_dirax
>>> import numpy as np
>>>
>>> cryst = Crystal.from_database("Pu-epsilon")
>>> indices = list(cryst.bounded_reflections(2))
>>>
>>> peaks = [
... cryst.scattering_vector(hkl) + np.random.normal(0, scale=0.01, size=(3,))
... for hkl in indices
... ]
>>> lattice, hkls = index_dirax(peaks)
>>> hkls.round(decimals=2)
array([[-0.98, -0. , -0. ],
[ 0. , -1. , 0.01],
[ 0. , -0. , -1. ],
[ 0. , 0.01, -0. ],
[ 0. , -0. , 0.99],
[ 0. , 1.01, -0. ],
[ 1. , -0.01, 0. ]])
Dealing with a restricted set of reflections¶
index_dirax()
can also deal with a restricted set of reflections. For example, in
electron diffraction measurements, it might occur that the only reflections measured are
of the form \(\{ (hk0) \}\). This happens, for example, when measuring diffraction from layered compounds
like graphite.
Let’s try to index without prior knowledge first:
>>> from crystals import Crystal, index_dirax
>>> import numpy as np
>>> from itertools import product
>>>
>>> cryst = Crystal.from_database('C') # graphite
>>> indices = product(
... [-2,-1,0,1,2], #-3 < h < 3
... [-2,-1,0,1,2], #-3 < k < 3
... [0] # l = 0
... )
>>> peaks = [cryst.scattering_vector(hkl) for hkl in indices]
>>> lattice, hkls = index_dirax(peaks)
Traceback (most recent call last):
crystals.indexing.common.IndexingError: No candidate lattice vectors could be determined.
As you can see, there not enough information to index. Let’s now use an initial guess:
>>> lattice, hkls = index_dirax(peaks, initial=cryst) # Lattice or Crystal works here
>>> lattice
< Lattice object with parameters 2.464Å, 2.464Å, 6.711Å, 90.00°, 90.00°, 120.00° >
Great!
Other indexing methods¶
More specialized and performant indexing methods are planned to be included in crystals
.
If there’s a particular method you would like to see included, please do not hesitate to
raise an issue!