{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Rename the axes of a 4-circle diffractometer\n", "\n", "Following the E4CV example, this example will repeat those same steps but using different names for the motor axes.\n", "\n", "E4CV name | local name\n", "--- | ---\n", "omega | theta\n", "chi | chi\n", "phi | phi\n", "tth | ttheta\n", "\n", "## How's it done?\n", "\n", "This change is made by:\n", "\n", "1. Defining the `FourCircle` class using our local names\n", " (instead of the canonical E4CV names)\n", "1. Writing a dictionary to the `fourc` object that maps\n", " the canonical E4CV names to our local names:\n", "\n", "```python\n", "fourc.calc.physical_axis_names = {\n", " # E4CV: local\n", " 'omega': 'theta',\n", " 'chi': 'chi',\n", " 'phi': 'phi',\n", " 'tth': 'ttheta',\n", " }\n", "```\n", "\n", "----\n", "\n", "Note: This example is available as a\n", "[Jupyter notebook](https://jupyter.org/) from the *hklpy* source\n", "code website: https://github.com/bluesky/hklpy/tree/main/docs/source/examples/notebooks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define _this_ diffractometer\n", "\n", "Following the E4CV example, create a `FourCircle` class,\n", "but use our own names for the motors.\n", "\n", "E4CV name | local name\n", "--- | ---\n", "omega | theta\n", "chi | chi\n", "phi | phi\n", "tth | ttheta\n", "\n", "Use the new motor names when constructing the `FourCircle` class. Otherwise, everything else in this step the same as in the E4CV example." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from hkl import E4CV, SimMixin\n", "from ophyd import SoftPositioner\n", "from ophyd import Component as Cpt\n", "\n", "class FourCircle(SimMixin, E4CV):\n", " \"\"\"\n", " Our 4-circle. Eulerian, vertical scattering orientation.\n", " \"\"\"\n", " # reciprocal axes defined in SimMixin\n", "\n", " # the motor axes are called: real in hklpy\n", " theta = Cpt(SoftPositioner, kind=\"hinted\", init_pos=0)\n", " chi = Cpt(SoftPositioner, kind=\"hinted\", init_pos=0)\n", " phi = Cpt(SoftPositioner, kind=\"hinted\", init_pos=0)\n", " ttheta = Cpt(SoftPositioner, kind=\"hinted\", init_pos=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create an instance of the diffractometer.\n", "\n", "As in the E4CV example, create an instance of the diffractometer. Note that the instance still has the canonical E4CV names, as reported by *libhkl*." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "motor names: ['omega', 'chi', 'phi', 'tth']\n" ] } ], "source": [ "fourc = FourCircle(\"\", name=\"fourc\")\n", "print(\"motor names:\", fourc.calc.physical_axis_names)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Switch to *our* motor names\n", "\n", "This is the magic step, *map* the canonical *libhkl* names onto\n", "the names we want to use. This is done using a Python dictionary. The keys are the canonical names, the value of key is the local name. *All* axes\n", "must be in the dictionary, even if the names remain the same." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "motor names: ['theta', 'chi', 'phi', 'ttheta']\n" ] } ], "source": [ "fourc.calc.physical_axis_names = {\n", " # E4CV: local\n", " 'omega': 'theta',\n", " 'chi': 'chi',\n", " 'phi': 'phi',\n", " 'tth': 'ttheta',\n", " }\n", "\n", "print(\"motor names:\", fourc.calc.physical_axis_names)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Add a sample with a crystal structure" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "HklSample(name='silicon', lattice=LatticeTuple(a=5.431020511, b=5.431020511, c=5.431020511, alpha=90.0, beta=90.0, gamma=90.0), ux=Parameter(name='None (internally: ux)', limits=(min=-180.0, max=180.0), value=0.0, fit=True, inverted=False, units='Degree'), uy=Parameter(name='None (internally: uy)', limits=(min=-180.0, max=180.0), value=0.0, fit=True, inverted=False, units='Degree'), uz=Parameter(name='None (internally: uz)', limits=(min=-180.0, max=180.0), value=0.0, fit=True, inverted=False, units='Degree'), U=array([[1., 0., 0.],\n", " [0., 1., 0.],\n", " [0., 0., 1.]]), UB=array([[ 1.15690694e+00, -7.08401189e-17, -7.08401189e-17],\n", " [ 0.00000000e+00, 1.15690694e+00, -7.08401189e-17],\n", " [ 0.00000000e+00, 0.00000000e+00, 1.15690694e+00]]), reflections=[])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from hkl import Lattice\n", "from hkl import SI_LATTICE_PARAMETER\n", "\n", "# add the sample to the calculation engine\n", "a0 = SI_LATTICE_PARAMETER\n", "fourc.calc.new_sample(\n", " \"silicon\",\n", " lattice=Lattice(a=a0, b=a0, c=a0, alpha=90, beta=90, gamma=90)\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup the **UB** orientation matrix using *hklpy*\n", "\n", "Define the crystal's orientation on the diffractometer using \n", "the 2-reflection method described by [Busing & Levy, Acta Cryst 22 (1967) 457](https://www.psi.ch/sites/default/files/import/sinq/zebra/PracticalsEN/1967-Busing-Levy-3-4-circle-Acta22.pdf).\n", "\n", "### Choose the same wavelength X-rays for both reflections" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "fourc.calc.wavelength = 1.54 # Angstrom (8.0509 keV)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Find the first reflection and identify its Miller indices: (_hkl_)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "r1 = fourc.calc.sample.add_reflection(\n", " 4, 0, 0,\n", " position=fourc.calc.Position(\n", " ttheta=69.0966,\n", " theta=-145.451,\n", " chi=0,\n", " phi=0,\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Find the second reflection" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "r2 = fourc.calc.sample.add_reflection(\n", " 0, 4, 0,\n", " position=fourc.calc.Position(\n", " ttheta=69.0966,\n", " theta=-145.451,\n", " chi=90,\n", " phi=0,\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Compute the **UB** orientation matrix\n", "\n", "The `compute_UB()` method always returns 1. Ignore it." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-1.41342846e-05, -1.41342846e-05, -1.15690694e+00],\n", " [ 0.00000000e+00, -1.15690694e+00, 1.41342846e-05],\n", " [-1.15690694e+00, 1.72682934e-10, 1.41342846e-05]])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fourc.calc.sample.compute_UB(r1, r2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Report what we have setup" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "==================== =========================================================================\n", "term value \n", "==================== =========================================================================\n", "energy, keV 8.050921974025975 \n", "wavelength, angstrom 1.54 \n", "position FourCirclePseudoPos(h=-0.0, k=0.0, l=0.0) \n", "sample name silicon \n", "[U] [[-1.22173048e-05 -1.22173048e-05 -1.00000000e+00] \n", " [ 0.00000000e+00 -1.00000000e+00 1.22173048e-05] \n", " [-1.00000000e+00 1.49262536e-10 1.22173048e-05]] \n", "[UB] [[-1.41342846e-05 -1.41342846e-05 -1.15690694e+00] \n", " [ 0.00000000e+00 -1.15690694e+00 1.41342846e-05] \n", " [-1.15690694e+00 1.72682934e-10 1.41342846e-05]] \n", "lattice [ 5.43102051 5.43102051 5.43102051 90. 90. 90. ]\n", "==================== =========================================================================\n", "\n", "sample\tHklSample(name='silicon', lattice=LatticeTuple(a=5.431020511, b=5.431020511, c=5.431020511, alpha=90.0, beta=90.0, gamma=90.0), ux=Parameter(name='None (internally: ux)', limits=(min=-180.0, max=180.0), value=-45.0, fit=True, inverted=False, units='Degree'), uy=Parameter(name='None (internally: uy)', limits=(min=-180.0, max=180.0), value=-89.99901005102187, fit=True, inverted=False, units='Degree'), uz=Parameter(name='None (internally: uz)', limits=(min=-180.0, max=180.0), value=135.00000000427607, fit=True, inverted=False, units='Degree'), U=array([[-1.22173048e-05, -1.22173048e-05, -1.00000000e+00],\n", " [ 0.00000000e+00, -1.00000000e+00, 1.22173048e-05],\n", " [-1.00000000e+00, 1.49262536e-10, 1.22173048e-05]]), UB=array([[-1.41342846e-05, -1.41342846e-05, -1.15690694e+00],\n", " [ 0.00000000e+00, -1.15690694e+00, 1.41342846e-05],\n", " [-1.15690694e+00, 1.72682934e-10, 1.41342846e-05]]), reflections=[(h=4.0, k=0.0, l=0.0), (h=0.0, k=4.0, l=0.0)], reflection_measured_angles=array([[0. , 1.57079633],\n", " [1.57079633, 0. ]]), reflection_theoretical_angles=array([[0. , 1.57079633],\n", " [1.57079633, 0. ]])))\n" ] } ], "source": [ "import pyRestTable\n", "\n", "tbl = pyRestTable.Table()\n", "tbl.labels = \"term value\".split()\n", "tbl.addRow((\"energy, keV\", fourc.calc.energy))\n", "tbl.addRow((\"wavelength, angstrom\", fourc.calc.wavelength))\n", "tbl.addRow((\"position\", fourc.position))\n", "tbl.addRow((\"sample name\", fourc.sample_name.get()))\n", "tbl.addRow((\"[U]\", fourc.U.get()))\n", "tbl.addRow((\"[UB]\", fourc.UB.get()))\n", "tbl.addRow((\"lattice\", fourc.lattice.get()))\n", "print(tbl)\n", "\n", "print(f\"sample\\t{fourc.calc.sample}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Check the orientation matrix\n", "\n", "Perform checks with _forward_ (hkl to angle) and\n", "_inverse_ (angle to hkl) computations to verify the diffractometer\n", "will move to the same positions where the reflections were identified.\n", "\n", "### Constrain the motors to limited ranges\n", "\n", "* allow for slight roundoff errors\n", "* keep `ttheta` in the positive range\n", "* keep `theta` in the negative range\n", "* keep `phi` fixed at zero\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "fourc.calc[\"ttheta\"].limits = (-0.001, 180)\n", "fourc.calc[\"theta\"].limits = (-180, 0.001)\n", "\n", "fourc.phi.move(0)\n", "fourc.engine.mode = \"constant_phi\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### (400) reflection\n", "#### Check the inverse calculation: (400)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(4 0 0) ? 4.00 0.00 0.00\n" ] } ], "source": [ "sol = fourc.inverse((-145.451, 0, 0, 69.0966))\n", "print(f\"(4 0 0) ? {sol.h:.2f} {sol.k:.2f} {sol.l:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Check the forward calculation: (400)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(400) : ttheta=69.0982 theta=-145.4502 chi=0.0000 phi=0.0000\n" ] } ], "source": [ "sol = fourc.forward((4, 0, 0))\n", "print(\n", " \"(400) :\", \n", " f\"ttheta={sol.ttheta:.4f}\", \n", " f\"theta={sol.theta:.4f}\", \n", " f\"chi={sol.chi:.4f}\", \n", " f\"phi={sol.phi:.4f}\"\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### (040) reflection\n", "#### Check another inverse calculation: (040)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0 4 0) ? 0.00 4.00 0.00\n" ] } ], "source": [ "sol = fourc.inverse((-145.451, 90, 0, 69.0966))\n", "print(f\"(0 4 0) ? {sol.h:.2f} {sol.k:.2f} {sol.l:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Continue the E4CV example on your own..." ] } ], "metadata": { "interpreter": { "hash": "cd09a60d4ca96784847e6d28c64916bc86a437fe6be574606d07ffca69ac8887" }, "kernelspec": { "display_name": "Python 3.9.12 ('training_2022')", "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.9.13" } }, "nbformat": 4, "nbformat_minor": 4 }