This page was generated from doc/tutorials/crystal_reference_frame.ipynb. Interactive online version: Binder badge.

Crystal reference frame#

In this tutorial we will see how the crystal and sample reference frames are aligned in orix

[1]:
%matplotlib inline

from diffpy.structure import Atom, Lattice, Structure
import matplotlib.pyplot as plt
import numpy as np

from orix.crystal_map import Phase
from orix.quaternion import Rotation
from orix.vector import Miller


plt.rcParams.update(
    {
        "figure.figsize": (10, 5),
        "font.size": 20,
        "axes.grid": True,
        "lines.markersize": 10,
        "lines.linewidth": 3,
    }
)

Alignment and the structure matrix#

The direct Bravais lattice is characterized by basis vectors (\(\mathbf{a}, \mathbf{b}, \mathbf{c}\)) with unit cell edge lengths (\(a\), \(b\), \(c\)) and angles (\(\alpha\), \(\beta\), \(\gamma\)). The reciprocal lattice has basis vectors given by

\[\mathbf{a^*} = \frac{\mathbf{b} \times \mathbf{c}}{\mathbf{a} \cdot (\mathbf{b} \times \mathbf{c})},\:\: \mathbf{b^*} = \frac{\mathbf{c} \times \mathbf{a}}{\mathbf{a} \cdot (\mathbf{b} \times \mathbf{c})},\:\: \mathbf{c^*} = \frac{\mathbf{a} \times \mathbf{b}}{\mathbf{a} \cdot (\mathbf{b} \times \mathbf{c})},\]

with reciprocal lattice parameters (\(a^*\), \(b^*\), \(c^*\)) and angles (\(\alpha^*\), \(\beta^*\), \(\gamma^*\)).

Using these two crystallographic lattices, we can define a standard Cartesian (orthonormal) reference frame by the unit vectors (\(\mathbf{e_1}, \mathbf{e_2}, \mathbf{e_3}\)). In principle, the direct lattice reference frame can be oriented arbitrarily in the Cartesian reference frame. In orix we have chosen

\[\mathbf{e_1} ||\: \frac{\mathbf{a}}{a},\:\: \mathbf{e_2} ||\: \mathbf{e_3} \times \mathbf{e_1},\:\: \mathbf{e_3} ||\: \frac{\mathbf{c^*}}{c^*}.\]

This alignment is used for example in [Rowenhorst et al., 2015] and [De Graef, 2003], the latter which was the basis for the EMsoft Fortran suite of programs. Another common option is \(\mathbf{e_1} || \mathbf{a^*}/a^*\), \(\mathbf{e_2} || \mathbf{e_3} \times \mathbf{e_1}\), \(\mathbf{e_3} || \mathbf{c}/c\), which is used for example in [Britton et al., 2016] and the diffpy.structure Python package, which we’ll come back to.

In calculations, it is useful to describe the transformation of the Cartesian unit row vectors to the coordinates of the direct lattice vectors by the structure row matrix \(\mathbf{A}\) (also called the crystal base). Given the chosen alignment of basis vectors with the Cartesian reference frame, \(\mathbf{A}\) is defined as

\begin{equation} \mathbf{A} = \begin{pmatrix} a & 0 & 0\\ b\cos\gamma & b\sin\gamma & 0\\ c\cos\beta & -c\frac{(\cos\beta\cos\gamma - \cos\alpha)}{\sin\gamma} & \frac{\mathrm{V}}{ab\sin\gamma} \end{pmatrix}, \end{equation}

where \(V\) is the volume of the unit cell.

In orix, we use the Lattice class in diffpy.structure to keep track of these properties internally. Let’s create an hexagonal crystal with lattice parameters \((a, b, c)\) = (1.4, 1.4, 1.7) nm and (\(\alpha, \beta, \gamma\)) = \((90^{\circ}, 90^{\circ}, 120^{\circ})\)

[2]:
lattice = Lattice(1.4, 1.4, 1.7, 90, 90, 120)
lattice
[2]:
Lattice(a=1.4, b=1.4, c=1.7, alpha=90, beta=90, gamma=120)

diffpy.structure stores the structure matrix in the Lattice.base property

[3]:
lattice.base
[3]:
array([[ 1.21243557, -0.7       ,  0.        ],
       [ 0.        ,  1.4       ,  0.        ],
       [ 0.        ,  0.        ,  1.7       ]])

and we see that diffpy.structure does not use the orix alignment mentioned above, since \(\mathbf{e1} \nparallel \mathbf{a} / a\). Instead, we see that \(\mathbf{e3} \parallel \mathbf{c} / c\), which is in line with the alternative alignment mentioned above.

Thus, the alignment is updated internally whenever a Phase is created, a class which brings together this crystal lattice and a point group Symmetry \(S\)

[4]:
phase = Phase(point_group="6/mmm", structure=Structure(lattice=lattice))
phase.structure.lattice.base
[4]:
array([[ 1.4       ,  0.        ,  0.        ],
       [-0.7       ,  1.21243557,  0.        ],
       [ 0.        ,  0.        ,  1.7       ]])

Warning

Using another alignment than the one described above has undefined behaviour in orix. Therefore, it is important that the structure matrix of a Phase instance is not changed.

Note

The lattice is included in a Structure because the latter class brings together a lattice and atoms, which is useful when simulating diffraction.

We can visualize the alignment of the direct and reciprocal lattice basis vectors with the Cartesian reference frame using the stereographic projection

[5]:
abc = Miller(uvw=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], phase=phase)
abcr = Miller(hkl=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], phase=phase)
[6]:
fig = abc.scatter(
    c=["r", "g", "b"],
    marker="o",
    return_figure=True,
    axes_labels=["e1", "e2"],
    hemisphere="both",
)
abcr.scatter(c=["r", "g", "b"], marker="x", s=300, figure=fig)
../_images/tutorials_crystal_reference_frame_13_0.png

Alignment of symmetry operations#

To see which crystallographic axes the point group symmetry operations rotate about, we can add symmetry operations to the figure, like is done in the Visualizing point groups tutorial for all point groups supported in orix

[7]:
R = Rotation.from_axes_angles([0, 1, 0], -65, degrees=True)
phase.point_group.plot(figure=fig, orientation=R)
fig
[7]:
../_images/tutorials_crystal_reference_frame_15_0.png

Converting crystal vectors#

The reference frame of the stereographic projection above is the Cartesian reference frame (\(\mathbf{e_1}, \mathbf{e_2}, \mathbf{e_3}\)). The Cartesian coordinates \((\mathbf{x}, \mathbf{y}, \mathbf{z})\) of \((\mathbf{a}, \mathbf{b}, \mathbf{c})\) and \((\mathbf{a^*}, \mathbf{b^*}, \mathbf{c^*})\) were found using \(\mathbf{A}\) in the following conversions

\begin{align} (x, y, z) &= [uvw] \cdot \mathbf{A},\\ (x, y, z) &= (hkl) \cdot (\mathbf{A}^{-1})^T. \end{align}

Let’s compute the internal conversions directly and check for equality

[8]:
v1 = np.dot(abc.uvw, phase.structure.lattice.base)
np.allclose(abc.data, v1)
[8]:
True
[9]:
v2 = np.dot(abcr.hkl, np.linalg.inv(phase.structure.lattice.base).T)
np.allclose(abcr.data, v2.data)
[9]:
True