{ "cells": [ { "cell_type": "markdown", "id": "130a22bd", "metadata": { "nbsphinx": "hidden" }, "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": "a6c24bbd", "metadata": {}, "source": [ "# Crystal directions\n", "\n", "In this tutorial we will perform operations with and plot directions with resepct to the crystal reference frame using Miller indices with the [orix.vector.Miller](../reference/generated/orix.vector.Miller.rst) class.\n", "\n", "Many of the initial examples, explaining basic crystallographic computations with crystal lattice directions $[uvw]$ and crystal lattice planes $(hkl)$, are taken from the textbook *Introduction to Conventional Transmission Electron Microscopy* (De Graef (2003)). Some of the later examples are also inspired by MTEX' excellent documentation on [Miller indices](https://mtex-toolbox.github.io/CrystalDirections.html) and [operations with them](https://mtex-toolbox.github.io/CrystalOperations.html)." ] }, { "cell_type": "code", "execution_count": null, "id": "2b47ffdf", "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "from diffpy.structure import Lattice, Structure\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from orix.crystal_map import Phase\n", "from orix.quaternion import Orientation, Rotation, symmetry\n", "from orix.vector import Miller, Vector3d\n", "\n", "\n", "plt.rcParams.update(\n", " {\n", " \"figure.figsize\": (7, 7),\n", " \"font.size\": 20,\n", " \"axes.grid\": True,\n", " \"lines.markersize\": 10,\n", " \"lines.linewidth\": 2,\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "e7d884cf", "metadata": {}, "source": [ "To start with, let's create a tetragonal crystal with lattice parameters $a$ = $b$ = 0.5 nm and $c$ = 1 nm and symmetry elements described by point group *4*" ] }, { "cell_type": "code", "execution_count": null, "id": "91532311", "metadata": {}, "outputs": [], "source": [ "tetragonal = Phase(\n", " point_group=\"4\",\n", " structure=Structure(lattice=Lattice(0.5, 0.5, 1, 90, 90, 90)),\n", ")\n", "print(tetragonal)\n", "print(tetragonal.structure.lattice)" ] }, { "cell_type": "markdown", "id": "7e31de5d", "metadata": {}, "source": [ "Here, the [Phase](../reference/generated/orix.crystal_map.Phase.rst) class attaches a point group symmetry, [Symmetry](../reference/generated/orix.quaternion.Symmetry.rst), to a [Structure](https://www.diffpy.org/diffpy.structure/package.html#diffpy.structure.structure.Structure) containing a [Lattice](https://www.diffpy.org/diffpy.structure/mod_lattice.html#diffpy.structure.lattice.Lattice) (where the crystal axes are defined), and possibly some [Atom](https://www.diffpy.org/diffpy.structure/mod_atom.html#diffpy.structure.atom.Atom)s." ] }, { "cell_type": "markdown", "id": "2829c8ca-cfde-4bd0-b295-e46913442503", "metadata": {}, "source": [ "## Directions $[uvw]$\n", "\n", "A crystal lattice direction $\\mathbf{m} = u \\cdot \\mathbf{a} + v \\cdot \\mathbf{b} + w \\cdot \\mathbf{c}$ is a vector with coordinates $u, v, w$ with respect to the crystal axes $\\mathbf{a}, \\mathbf{b}, \\mathbf{c}$, and is denoted $[uvw]$. We can create a set of these Miller indices by passing a list/array/tuple to uvw in [Miller](../reference/generated/orix.vector.Miller.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "c7865433", "metadata": {}, "outputs": [], "source": [ "m1 = Miller(uvw=[[1, 2, 0], [3, 1, 1]], phase=tetragonal)\n", "m1.scatter(axes_labels=[\"x\", \"y\", None], c=[\"b\", \"r\"])\n", "m1" ] }, { "cell_type": "markdown", "id": "5dee5ca4", "metadata": {}, "source": [ "Here, we plotted the lattice directions in the stereographic projection using the [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst) plotting method, which also works for Miller.scatter() since the Miller class inherits most of the functionality in the Vector3d class.\n", "\n", "Let's compute the dot product in nanometres and the angle in degrees between the vectors" ] }, { "cell_type": "code", "execution_count": null, "id": "58fafae1", "metadata": {}, "outputs": [], "source": [ "m1[0].dot(m1[1])" ] }, { "cell_type": "code", "execution_count": null, "id": "377071d3", "metadata": {}, "outputs": [], "source": [ "m1[0].angle_with(m1[1], degrees=True)" ] }, { "cell_type": "markdown", "id": "0e05dc64", "metadata": {}, "source": [ "The length of a direct lattice vector is available via the [Miller.length](../reference/generated/orix.vector.Miller.length.rst) property, given in lattice parameter units (nm in this case)" ] }, { "cell_type": "code", "execution_count": null, "id": "bf0c0ee7", "metadata": {}, "outputs": [], "source": [ "Miller(uvw=[0, -0.5, 0.5], phase=tetragonal).length" ] }, { "cell_type": "markdown", "id": "212dde9a", "metadata": {}, "source": [ "## Planes $(hkl)$\n", "\n", "A crystal lattice plane $(hkl)$ is described by its normal vector $\\mathbf{n} = h\\cdot\\mathbf{a^*} + k\\cdot\\mathbf{b^*} + l\\cdot\\mathbf{c^*}$, where $\\mathbf{a^*}, \\mathbf{b^*}, \\mathbf{c^*}$ are the reciprocal crystal axes. As for crystal directions $[uvw]$, we can create a set of these Miller indices by passing hkl instead of uvw to [Miller](../reference/generated/orix.vector.Miller.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "d681d26c", "metadata": {}, "outputs": [], "source": [ "m2 = Miller(hkl=m1.uvw, phase=tetragonal)\n", "m2.scatter(c=[\"y\", \"g\"], marker=\"*\")\n", "m2" ] }, { "cell_type": "markdown", "id": "407ee88b", "metadata": {}, "source": [ "Let's compute the angle in degrees between the lattice plane normals" ] }, { "cell_type": "code", "execution_count": null, "id": "31c42ee7", "metadata": {}, "outputs": [], "source": [ "m2[0].angle_with(m2[1], degrees=True)" ] }, { "cell_type": "markdown", "id": "f6e45bf2", "metadata": {}, "source": [ "Note that the lattice plane normals $(hkl)$ are not always parallel to the lattice directions $[uvw]$, even if the indices are the same" ] }, { "cell_type": "code", "execution_count": null, "id": "a60c2918", "metadata": {}, "outputs": [], "source": [ "fig = m1.scatter(return_figure=True, c=[\"b\", \"r\"])\n", "m2.scatter(figure=fig, c=[\"y\", \"g\"], marker=\"*\")" ] }, { "cell_type": "markdown", "id": "6ff2f4e2", "metadata": {}, "source": [ "We can get the reciprocal components of the lattice vector [114] (i.e. which lattice plane the [114] direction is perpendicular to) by accessing [Miller.hkl](../reference/generated/orix.vector.Miller.hkl.rst) for a Miller object with crystal directions (or [Miller.uvw](../reference/generated/orix.vector.Miller.uvw.rst) for a Miller object with crystal plane normals)" ] }, { "cell_type": "code", "execution_count": null, "id": "ea017f82", "metadata": {}, "outputs": [], "source": [ "Miller(uvw=[1, 1, 4], phase=tetragonal).hkl" ] }, { "cell_type": "markdown", "id": "a0e03ff3", "metadata": {}, "source": [ "We see that the lattice vector [114] is perpendicular to the lattice plane (1 1 16).\n", "\n", "The length of reciprocal lattice vectors can also be accessed via the Miller.length property, and equals $\\left|\\mathbf{g}_{\\mathrm{hkl}}\\right| = \\frac{1}{d_{\\mathrm{hkl}}}$ in reciprocal lattice parameter units (nm^-1 in this case), meaning we can obtain the interplanar spacing $d_{\\mathrm{hkl}}$" ] }, { "cell_type": "code", "execution_count": null, "id": "dbf775b0", "metadata": {}, "outputs": [], "source": [ "m2.length" ] }, { "cell_type": "code", "execution_count": null, "id": "a826d6f6", "metadata": {}, "outputs": [], "source": [ "1 / m2.length" ] }, { "cell_type": "markdown", "id": "14fd8365-564c-4779-b2e2-43a5152616db", "metadata": {}, "source": [ "## Zone axes\n", "\n", "The cross product of the lattice vectors [110] and [111] in the tetragonal crystal, in direct space, is described by a vector expressed in reciprocal space, $(hkl)$" ] }, { "cell_type": "code", "execution_count": null, "id": "b2b3f2a5", "metadata": {}, "outputs": [], "source": [ "m3 = Miller(uvw=[[1, 1, 0], [1, 1, 1]], phase=tetragonal)\n", "m3perp = m3[0].cross(m3[1])\n", "m3perp" ] }, { "cell_type": "markdown", "id": "96055dc5", "metadata": {}, "source": [ "The exact \"indices\" are returned, however, we can get a new Miller instance with the indices rounded down or up to the *closest* smallest integer via the [Miller.round()](../reference/generated/orix.vector.Miller.round.rst) method" ] }, { "cell_type": "code", "execution_count": null, "id": "6279a24a", "metadata": {}, "outputs": [], "source": [ "m3perp.round()" ] }, { "cell_type": "markdown", "id": "f1825a11", "metadata": {}, "source": [ "The maximum index that Miller.round() might return can be set by passing the max_index parameter.\n", "\n", "We can plot these direct lattice vectors $[uvw]$ and the vectors normal to the cross product vector, i.e. normal to the reciprocal lattice vector $(hkl)$" ] }, { "cell_type": "code", "execution_count": null, "id": "721fbc40", "metadata": {}, "outputs": [], "source": [ "fig = m3.scatter(return_figure=True, c=\"r\")\n", "m3perp.draw_circle(figure=fig, color=\"b\", linestyle=\"--\")" ] }, { "cell_type": "markdown", "id": "fa1ab807", "metadata": {}, "source": [ "Likewise, the cross product of reciprocal lattice vectors (110) and (111) results in a direct lattice vector" ] }, { "cell_type": "code", "execution_count": null, "id": "2d06c437", "metadata": {}, "outputs": [], "source": [ "m4 = Miller(hkl=[[1, 1, 0], [1, 1, 1]], phase=tetragonal)\n", "m4perp = m4[0].cross(m4[1]).round()\n", "m4perp" ] }, { "cell_type": "code", "execution_count": null, "id": "9e21ab76", "metadata": {}, "outputs": [], "source": [ "fig = m4.scatter(return_figure=True, c=\"b\")\n", "m4perp.draw_circle(figure=fig, color=\"r\", linestyle=\"--\")" ] }, { "cell_type": "markdown", "id": "b9fb908e", "metadata": {}, "source": [ "## Trigonal and hexagonal index conventions\n", "\n", "Crystal lattice vectors and planes in lattices with trigonal and hexagonal crystal symmetry are typically expressed in Weber symbols $[UVTW]$ and Miller-Bravais indices $(hkil)$. The definition of $[UVTW]$ used in orix follows *Introduction to Conventional Transmission Electron Microscopy* (DeGraef, 2003)\n", "\n", "\\begin{align}\n", "U &= \\frac{2u - v}{3},\\\\\n", "V &= \\frac{2v - u}{3},\\\\\n", "T &= -\\frac{u + v}{3},\\\\\n", "W &= w.\n", "\\end{align}\n", "\n", "For a plane, the $h$, $k$ and $l$ indices are the same in $(hkl)$ and $(hkil)$, and $i = -(h + k)$.\n", "\n", "The first three Miller indices always sum up to zero, i.e.\n", "\n", "\\begin{align}\n", "U + V + T &= 0,\\\\\n", "h + k + i &= 0.\n", "\\end{align}" ] }, { "cell_type": "code", "execution_count": null, "id": "f772cb1f", "metadata": {}, "outputs": [], "source": [ "trigonal = Phase(\n", " point_group=\"321\",\n", " structure=Structure(lattice=Lattice(4.9, 4.9, 5.4, 90, 90, 120)),\n", ")\n", "trigonal" ] }, { "cell_type": "code", "execution_count": null, "id": "594b3c3f", "metadata": {}, "outputs": [], "source": [ "m5 = Miller(UVTW=[2, 1, -3, 1], phase=trigonal)\n", "m5" ] }, { "cell_type": "code", "execution_count": null, "id": "c1b370c2", "metadata": {}, "outputs": [], "source": [ "m6 = Miller(hkil=[1, 1, -2, 3], phase=trigonal)\n", "m6" ] }, { "cell_type": "code", "execution_count": null, "id": "02dd15f9", "metadata": {}, "outputs": [], "source": [ "fig = m5.scatter(return_figure=True, c=\"C0\", grid_resolution=(30, 30))\n", "m6.scatter(figure=fig, c=\"C1\")" ] }, { "cell_type": "markdown", "id": "2d558dc5", "metadata": {}, "source": [ "One can change the coordinate format of the Miller class, however this does not change the vector, since all vectors are stored with respect to the cartesian coordinate system internally" ] }, { "cell_type": "code", "execution_count": null, "id": "bbb6babd", "metadata": {}, "outputs": [], "source": [ "print(m6, \"\\n\\n\", m6.data)" ] }, { "cell_type": "code", "execution_count": null, "id": "4f90170d", "metadata": {}, "outputs": [], "source": [ "m6.coordinate_format = \"UVTW\"\n", "print(m6, \"\\n\\n\", m6.data)" ] }, { "cell_type": "markdown", "id": "15396d0c", "metadata": {}, "source": [ "Getting the closest integer indices, however, changes the vector" ] }, { "cell_type": "code", "execution_count": null, "id": "650b799d", "metadata": {}, "outputs": [], "source": [ "m6round = m6.round()\n", "print(m6round, \"\\n\\n\", m6round.data)" ] }, { "cell_type": "markdown", "id": "99a102ad", "metadata": {}, "source": [ "## Symmetrically equivalent directions and planes\n", "\n", "The point group symmetry elements of the crystal lattice can be applied to to describe symmetrically equivalent crystal directions and planes. This applies to crystals in all seven systems, but we'll use the cubic crystal as an example because of its high symmetry" ] }, { "cell_type": "code", "execution_count": null, "id": "eaa24f25", "metadata": {}, "outputs": [], "source": [ "cubic = Phase(point_group=\"m-3m\", structure=Structure())\n", "print(cubic, \"\\n\", cubic.structure.lattice.abcABG())" ] }, { "cell_type": "markdown", "id": "a42bb3de", "metadata": {}, "source": [ "The directions parallel to the crystal axes ($\\mathbf{a}$, $\\mathbf{b}$, $\\mathbf{c}$) given by $[100]$, $[\\bar{1}00]$, $[010]$, $[0\\bar{1}0]$, $[001]$, and $[00\\bar{1}]$ ($\\bar{1}$ means \"-1\") are symmetrically equivalent, and can be obtained using [Miller.symmetrise()](../reference/generated/orix.vector.Miller.symmetrise.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "6dee66d9", "metadata": {}, "outputs": [], "source": [ "m7 = Miller(uvw=[1, 0, 0], phase=cubic)\n", "m7.symmetrise(unique=True)" ] }, { "cell_type": "markdown", "id": "cd439e36", "metadata": {}, "source": [ "Without passing unique=True, since the cubic crystal symmetry is described by 48 symmetry operations (or elements), 48 directions would have been returned.\n", "\n", "These six directions, known as a family, may be expressed collectively as $\\left<100\\right>$, the brackets implying all six permutations or variants of 1, 0, 0. This particular family is said to have a multiplicity of 6" ] }, { "cell_type": "code", "execution_count": null, "id": "209131ba", "metadata": {}, "outputs": [], "source": [ "m7.multiplicity" ] }, { "cell_type": "code", "execution_count": null, "id": "8276d5d0", "metadata": {}, "outputs": [], "source": [ "m8 = Miller(uvw=[[1, 0, 0], [1, 1, 0], [1, 1, 1]], phase=cubic)\n", "m8" ] }, { "cell_type": "code", "execution_count": null, "id": "c490aa37", "metadata": {}, "outputs": [], "source": [ "m8.multiplicity" ] }, { "cell_type": "markdown", "id": "e85074c6", "metadata": {}, "source": [ "Let's plot the symmetrically equivalent directions from the direction families $\\left<100\\right>$, $\\left<110\\right>$, and $\\left<111\\right>$ impinging on the upper hemisphere. By also returning the indices of which family each symmetrically equivalent direction belongs to from Miller.symmetrise(), we can give a unique color per family" ] }, { "cell_type": "code", "execution_count": null, "id": "e66baa45", "metadata": {}, "outputs": [], "source": [ "m9, idx = m8.symmetrise(unique=True, return_index=True)\n", "\n", "fig = m9[idx == 0].scatter(c=\"C0\", return_figure=True)\n", "for i in range(1, m9.size):\n", " m9[idx == i].scatter(c=f\"C{i}\", figure=fig)" ] }, { "cell_type": "markdown", "id": "3b9e14f6", "metadata": {}, "source": [ "Similarly, symmetrically equivalent planes $(hkl)$ can be collectively expressed as planes of the form $\\{hkl\\}$" ] }, { "cell_type": "code", "execution_count": null, "id": "2213decb", "metadata": {}, "outputs": [], "source": [ "m10 = Miller(hkl=[[1, 0, 0], [1, 1, 0], [1, 1, 1]], phase=cubic)\n", "m10.multiplicity" ] }, { "cell_type": "code", "execution_count": null, "id": "22b968aa", "metadata": {}, "outputs": [], "source": [ "fig = m10[0].symmetrise(unique=True).scatter(c=\"C0\", return_figure=True)\n", "for i in range(1, m10.size):\n", " m10[i].symmetrise(unique=True).scatter(c=f\"C{i}\", figure=fig)" ] }, { "cell_type": "markdown", "id": "e37f191a", "metadata": {}, "source": [ "We computed the angles between directions and plane normals earlier in this tutorial. In general, [Miller.angle_with()](../reference/generated/orix.vector.Miller.angle_with.rst) does not consider symmetrically equivalent directions, unless use_symmetry=True is passed. Consider $(100)$ and $(\\bar{1}00)$ and $(111)$ and $(\\bar{1}11)$ in the stereographic plot above" ] }, { "cell_type": "code", "execution_count": null, "id": "55f44f37", "metadata": {}, "outputs": [], "source": [ "m11 = Miller(hkl=[[1, 0, 0], [1, 1, 1]], phase=cubic)\n", "m12 = Miller(hkl=[[-1, 0, 0], [-1, 1, 1]], phase=cubic)" ] }, { "cell_type": "code", "execution_count": null, "id": "e98a9e1d", "metadata": {}, "outputs": [], "source": [ "m11.angle_with(m12, degrees=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "6997dfff", "metadata": {}, "outputs": [], "source": [ "m11.angle_with(m12, use_symmetry=True, degrees=True)" ] }, { "cell_type": "markdown", "id": "48abd469-f493-4b10-8ca1-5d4e4261bd86", "metadata": {}, "source": [ "Thus, passing use_symmetry=True ensures that the smallest angles between m11 and the symmetrically equivalent directions to m12 are found.## Directions and planes in rotated crystals" ] }, { "cell_type": "markdown", "id": "f2551ec3-e0e9-4daa-97df-b62b67b2aa7a", "metadata": {}, "source": [ "## Directions and planes in rotated crystals\n", "\n", "Let's consider the orientation of a cubic crystal rotated $45^{\\circ}$ about the sample $\\mathbf{Z}$ axis. Orientations in orix are defined as rotations from the sample to the crystal (so-called \"lab2crystal\"), so we have to invert the orientation to get the vector expressed in the sample reference frame. Furthermore, as per the discussion by Rowenhorst et al. (2015), the axis-angle representation of a $45^{\\circ}$ rotation about the sample $\\mathbf{Z}$ is written as $(\\mathbf{\\hat{n}}, \\omega) = ([00\\bar{1},90^{\\circ})$" ] }, { "cell_type": "code", "execution_count": null, "id": "22bcb3d7", "metadata": {}, "outputs": [], "source": [ "o = Orientation.from_axes_angles(\n", " (0, 0, -1), 45, symmetry=cubic.point_group, degrees=True\n", ")\n", "o" ] }, { "cell_type": "markdown", "id": "3c5c6d4f", "metadata": {}, "source": [ "We can apply this orientation to a crystal direction $[uvw]$ to find this direction in this particular crystal with respect to the sample coordinate system" ] }, { "cell_type": "code", "execution_count": null, "id": "ce74eeea", "metadata": {}, "outputs": [], "source": [ "m12 = Miller(uvw=[1, 1, 1], phase=cubic)\n", "~o * m12" ] }, { "cell_type": "code", "execution_count": null, "id": "b673efce", "metadata": {}, "outputs": [], "source": [ "# [uvw] in unrotated crystal\n", "fig = m12.scatter(c=\"r\", return_figure=True, axes_labels=[\"X\", \"Y\", \"Z\"])\n", "\n", "# [uvw] in rotated crystal\n", "(~o * m12).scatter(figure=fig, c=\"b\", marker=\"x\")" ] }, { "cell_type": "markdown", "id": "9c3e33a8-10a4-45e9-8a6d-3b04ad92421c", "metadata": {}, "source": [ "We see that the $[111]$ vector in the crystal is aligned with the sample $\\mathbf{Y}$ direction.\n", "\n", "We can apply the crystal symmetry to obtain the coordinates with respect to the sample reference frame for all crystallographically equivalent directions" ] }, { "cell_type": "code", "execution_count": null, "id": "69c5fa0f", "metadata": {}, "outputs": [], "source": [ "(~o * m12.symmetrise(unique=True)).scatter(c=\"b\", marker=\"x\")" ] }, { "cell_type": "code", "execution_count": null, "id": "6617936e", "metadata": {}, "outputs": [], "source": [ "o2 = Orientation.from_euler(\n", " [10, 20, 30], symmetry=trigonal.point_group, degrees=True\n", ")\n", "o2" ] }, { "cell_type": "code", "execution_count": null, "id": "3f70a515", "metadata": {}, "outputs": [], "source": [ "m13 = Miller(hkil=[1, -1, 0, 0], phase=trigonal)\n", "m13" ] }, { "cell_type": "code", "execution_count": null, "id": "6f3cd576", "metadata": {}, "outputs": [], "source": [ "p = ~o2 * m13.symmetrise(unique=True)\n", "p.scatter(\n", " hemisphere=\"both\",\n", " grid_resolution=(30, 30),\n", " figure_kwargs=dict(figsize=(10, 5)),\n", " axes_labels=[\"X\", \"Y\", \"Z\"],\n", ")" ] }, { "cell_type": "markdown", "id": "27daff1b", "metadata": {}, "source": [ "The stereographic plots above are essentially the $\\{1\\bar{1}00\\}$ pole figure representation of the orientation $O_2$." ] }, { "cell_type": "markdown", "id": "b4b453f1", "metadata": {}, "source": [ "___" ] }, { "cell_type": "markdown", "id": "d2206e91", "metadata": {}, "source": [ "## A diamond [111] pole figure\n", "\n", "Let's make a pole figure in the [111] direction of the diamond structure, as seen in [this figure from Wikipedia](https://commons.wikimedia.org/wiki/File:DiamondPoleFigure111.png).\n", "\n", "The figure caption reads as follows\n", "\n", "> *The spots in the stereographic projection show the orientation of lattice planes with the 111 in the center. Only poles for a non-forbidden Bragg reflection are shown between Miller indices -10 <= (h,k,l) <= 10. The green spots contain Miller indices up to 3, for example 111, 113, 133, 200 etc in its fundamental order. Red are those raising to 5, ex. 115, 135, 335 etc, while blue are all remaining until 10, such as 119, 779, 10.10.00 etc.*" ] }, { "cell_type": "code", "execution_count": null, "id": "044a66ae", "metadata": {}, "outputs": [], "source": [ "diamond = Phase(space_group=227)\n", "md = Miller.from_highest_indices(phase=diamond, uvw=[10, 10, 10])\n", "md" ] }, { "cell_type": "markdown", "id": "d265c73e", "metadata": {}, "source": [ "Remove duplicates under symmetry using [Miller.unique()](../reference/generated/orix.vector.Miller.unique.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "3d8a5a21", "metadata": {}, "outputs": [], "source": [ "md2 = md.unique(use_symmetry=True)\n", "md2.size" ] }, { "cell_type": "markdown", "id": "e259e4b9", "metadata": {}, "source": [ "Symmetrise to get all symmetrically equivalent directions" ] }, { "cell_type": "code", "execution_count": null, "id": "3bfb0bab", "metadata": {}, "outputs": [], "source": [ "md3 = md2.symmetrise(unique=True)\n", "md3" ] }, { "cell_type": "markdown", "id": "32b6b396", "metadata": {}, "source": [ "Remove forbidden reflections in face-centered cubic structures (all hkl must be all even or all odd)" ] }, { "cell_type": "code", "execution_count": null, "id": "8cf5fca5", "metadata": {}, "outputs": [], "source": [ "selection = np.sum(np.mod(md3.hkl, 2), axis=1)\n", "allowed = np.array([i not in [1, 2] for i in selection], dtype=bool)\n", "md4 = md3[allowed]\n", "md4" ] }, { "cell_type": "markdown", "id": "8990f81f", "metadata": {}, "source": [ "Assign colors to each class of vectors as per the description on Wikipedia" ] }, { "cell_type": "code", "execution_count": null, "id": "5f34deaf", "metadata": {}, "outputs": [], "source": [ "uvw = np.abs(md4.uvw)\n", "green = np.all(uvw <= 3, axis=-1)\n", "red = np.any(uvw > 3, axis=-1) * np.all(uvw <= 5, axis=-1)\n", "blue = np.any(uvw > 5, axis=-1)\n", "rgb_mask = np.column_stack([red, green, blue])\n", "\n", "# Sanity check\n", "print(np.count_nonzero(rgb_mask) == md4.size)" ] }, { "cell_type": "markdown", "id": "31c237e0", "metadata": {}, "source": [ "Rotate directions so that [111] impinges the unit sphere in the north pole (out of plane direction)" ] }, { "cell_type": "code", "execution_count": null, "id": "7258abd9", "metadata": {}, "outputs": [], "source": [ "vz = Vector3d.zvector()\n", "v111 = Vector3d([1, 1, 1])\n", "r1 = Rotation.from_axes_angles(v111.cross(vz), v111.angle_with(vz).data)\n", "r2 = Rotation.from_axes_angles(vz, -15, degrees=True)\n", "md5 = r2 * r1 * md4" ] }, { "cell_type": "markdown", "id": "37614b89", "metadata": {}, "source": [ "Restrict to upper hemisphere and remove duplicates" ] }, { "cell_type": "code", "execution_count": null, "id": "aaf0c214", "metadata": {}, "outputs": [], "source": [ "is_upper = md5.z > 0\n", "md6 = md5[is_upper]\n", "rgb_mask2 = rgb_mask[is_upper]\n", "\n", "_, idx = md6.unit.unique(return_index=True)\n", "md7 = md6[idx]\n", "rgb_mask2 = rgb_mask2[idx]" ] }, { "cell_type": "markdown", "id": "eede6788", "metadata": {}, "source": [ "Finally, plot the vectors" ] }, { "cell_type": "code", "execution_count": null, "id": "554ec00e", "metadata": { "nbsphinx-thumbnail": { "tooltip": "Operating with vectors in the crystal and sample reference frames" }, "tags": [ "nbsphinx-thumbnail" ] }, "outputs": [], "source": [ "rgb = np.zeros_like(md7.uvw)\n", "rgb[rgb_mask2] = 1\n", "\n", "md7.scatter(c=rgb, s=10, grid=False, figure_kwargs=dict(figsize=(12, 12)))" ] } ], "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.10.8" } }, "nbformat": 4, "nbformat_minor": 5 }