{ "cells": [ { "cell_type": "markdown", "id": "67195845-843f-4de5-b0ee-0addce775c64", "metadata": { "nbsphinx": "hidden", "tags": [] }, "source": [ "This notebook is part of the *orix* documentation https://orix.readthedocs.io. Links to the documentation won’t work from the notebook." ] }, { "cell_type": "markdown", "id": "f549c08a-bed7-4431-914c-b3edc609f476", "metadata": {}, "source": [ "# Crystal reference frame\n", "\n", "In this tutorial we will see how the crystal and sample reference frames are aligned in orix" ] }, { "cell_type": "code", "execution_count": null, "id": "d97dfe58-a726-4a19-b327-d5c3b6b15844", "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "from diffpy.structure import Atom, Lattice, Structure\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "from orix.crystal_map import Phase\n", "from orix.quaternion import Rotation\n", "from orix.vector import Miller\n", "\n", "\n", "plt.rcParams.update(\n", " {\n", " \"figure.figsize\": (10, 5),\n", " \"font.size\": 20,\n", " \"axes.grid\": True,\n", " \"lines.markersize\": 10,\n", " \"lines.linewidth\": 3,\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "a3e6c3aa-7cd7-4ed5-88cf-124b9b193532", "metadata": {}, "source": [ "## Alignment and the structure matrix\n", "\n", "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$).\n", "The reciprocal lattice has basis vectors given by\n", "\n", "$$\n", "\\mathbf{a^*} = \\frac{\\mathbf{b} \\times \\mathbf{c}}{\\mathbf{a} \\cdot (\\mathbf{b} \\times \\mathbf{c})},\\:\\:\n", "\\mathbf{b^*} = \\frac{\\mathbf{c} \\times \\mathbf{a}}{\\mathbf{a} \\cdot (\\mathbf{b} \\times \\mathbf{c})},\\:\\:\n", "\\mathbf{c^*} = \\frac{\\mathbf{a} \\times \\mathbf{b}}{\\mathbf{a} \\cdot (\\mathbf{b} \\times \\mathbf{c})},\n", "$$\n", "\n", "with reciprocal lattice parameters ($a^*$, $b^*$, $c^*$) and angles ($\\alpha^*$, $\\beta^*$, $\\gamma^*$).\n", "\n", "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\n", "\n", "$$\n", "\\mathbf{e_1} ||\\: \\frac{\\mathbf{a}}{a},\\:\\:\n", "\\mathbf{e_2} ||\\: \\mathbf{e_3} \\times \\mathbf{e_1},\\:\\:\n", "\\mathbf{e_3} ||\\: \\frac{\\mathbf{c^*}}{c^*}.\n", "$$\n", "\n", "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.\n", "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.\n", "\n", "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*).\n", "Given the chosen alignment of basis vectors with the Cartesian reference frame, $\\mathbf{A}$ is defined as\n", "\n", "\\begin{equation}\n", "\\mathbf{A} = \n", "\\begin{pmatrix}\n", "a & 0 & 0\\\\\n", "b\\cos\\gamma & b\\sin\\gamma & 0\\\\\n", "c\\cos\\beta & -c\\frac{(\\cos\\beta\\cos\\gamma - \\cos\\alpha)}{\\sin\\gamma} & \\frac{\\mathrm{V}}{ab\\sin\\gamma}\n", "\\end{pmatrix},\n", "\\end{equation}\n", "\n", "where $V$ is the volume of the unit cell.\n", "\n", "In orix, we use the [Lattice](https://www.diffpy.org/diffpy.structure/mod_lattice.html#diffpy.structure.lattice.Lattice) class in diffpy.structure to keep track of these properties internally.\n", "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})$" ] }, { "cell_type": "code", "execution_count": null, "id": "9f05c3e3-74fd-41ae-8660-ac997ac74d62", "metadata": {}, "outputs": [], "source": [ "lattice = Lattice(1.4, 1.4, 1.7, 90, 90, 120)\n", "lattice" ] }, { "cell_type": "markdown", "id": "932dcf90-8f5d-4e22-a121-d5676614b028", "metadata": {}, "source": [ "diffpy.structure stores the structure matrix in the `Lattice.base` property" ] }, { "cell_type": "code", "execution_count": null, "id": "20a8f43f-b2be-477c-900c-7771cf5c8a47", "metadata": {}, "outputs": [], "source": [ "lattice.base" ] }, { "cell_type": "markdown", "id": "bc67a5a6-3407-4bb6-b03a-58958eceb024", "metadata": {}, "source": [ "and we see that diffpy.structure does not use the orix alignment mentioned above, since $\\mathbf{e1} \\nparallel \\mathbf{a} / a$.\n", "Instead, we see that $\\mathbf{e3} \\parallel \\mathbf{c} / c$, which is in line with the alternative alignment mentioned above.\n", "\n", "Thus, the alignment is updated internally whenever a [Phase](../reference/generated/orix.crystal_map.Phase.rst) is created, a class which brings together this crystal lattice and a point group [Symmetry](../reference/generated/orix.quaternion.Symmetry.rst) $S$" ] }, { "cell_type": "code", "execution_count": null, "id": "0c95a661-e26d-4b07-9ac6-d4908f37305f", "metadata": {}, "outputs": [], "source": [ "phase = Phase(point_group=\"6/mmm\", structure=Structure(lattice=lattice))\n", "phase.structure.lattice.base" ] }, { "cell_type": "markdown", "id": "e35f63a1-1008-41e0-a2e7-fcc54695de98", "metadata": {}, "source": [ "
\n", "\n", "Warning\n", "\n", "Using another alignment than the one described above has undefined behaviour in orix.\n", "Therefore, it is important that the structure matrix of a `Phase` instance is not changed.\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "b4603803-0883-4b1e-b8c4-5c11eae7421e", "metadata": {}, "source": [ "
\n", "\n", "Note\n", " \n", "The lattice is included in a [Structure](https://www.diffpy.org/diffpy.structure/package.html#diffpy.structure.structure.Structure) because the latter class brings together a lattice and [atoms](https://www.diffpy.org/diffpy.structure/mod_atom.html#diffpy.structure.atom.Atom), which is useful when simulating diffraction.\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "c3b093cb-aad7-45e2-9148-d63d66eb389b", "metadata": {}, "source": [ "We can visualize the alignment of the direct and reciprocal lattice basis vectors with the Cartesian reference frame using the stereographic projection" ] }, { "cell_type": "code", "execution_count": null, "id": "55815802-4745-4329-80ff-4310ac3fd66e", "metadata": {}, "outputs": [], "source": [ "abc = Miller(uvw=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], phase=phase)\n", "abcr = Miller(hkl=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], phase=phase)" ] }, { "cell_type": "code", "execution_count": null, "id": "157d6a1d-81b9-4516-a479-7e293b250319", "metadata": {}, "outputs": [], "source": [ "fig = abc.scatter(\n", " c=[\"r\", \"g\", \"b\"],\n", " marker=\"o\",\n", " return_figure=True,\n", " axes_labels=[\"e1\", \"e2\"],\n", " hemisphere=\"both\",\n", ")\n", "abcr.scatter(c=[\"r\", \"g\", \"b\"], marker=\"x\", s=300, figure=fig)" ] }, { "cell_type": "markdown", "id": "96a9cde8-866e-4c22-be91-e27aa7e8265f", "metadata": {}, "source": [ "## Alignment of symmetry operations\n", "\n", "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](point_groups.ipynb) tutorial for all point groups supported in orix" ] }, { "cell_type": "code", "execution_count": null, "id": "26f0ba6c-d0bd-4222-ba17-17eaf42f98fd", "metadata": { "nbsphinx-thumbnail": { "tooltip": "Alignment of crystal reference frames and symmetry operations" }, "tags": [ "nbsphinx-thumbnail" ] }, "outputs": [], "source": [ "R = Rotation.from_axes_angles([0, 1, 0], -65, degrees=True)\n", "phase.point_group.plot(figure=fig, orientation=R)\n", "fig" ] }, { "cell_type": "markdown", "id": "fa96e593-2ac8-40eb-8806-d455cddf382f", "metadata": {}, "source": [ "## Converting crystal vectors\n", "\n", "The reference frame of the stereographic projection above is the Cartesian reference frame ($\\mathbf{e_1}, \\mathbf{e_2}, \\mathbf{e_3}$).\n", "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\n", "\n", "\\begin{align}\n", "(x, y, z) &= [uvw] \\cdot \\mathbf{A},\\\\\n", "(x, y, z) &= (hkl) \\cdot (\\mathbf{A}^{-1})^T.\n", "\\end{align}\n", "\n", "Let's compute the internal conversions directly and check for equality" ] }, { "cell_type": "code", "execution_count": null, "id": "5690186c-af02-4b14-8742-a7f98f66159c", "metadata": {}, "outputs": [], "source": [ "v1 = np.dot(abc.uvw, phase.structure.lattice.base)\n", "np.allclose(abc.data, v1)" ] }, { "cell_type": "code", "execution_count": null, "id": "c08f6f6c-e39d-4e25-b398-e99f37ee22ba", "metadata": {}, "outputs": [], "source": [ "v2 = np.dot(abcr.hkl, np.linalg.inv(phase.structure.lattice.base).T)\n", "np.allclose(abcr.data, v2.data)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.11.8" } }, "nbformat": 4, "nbformat_minor": 5 }