{ "cells": [ { "cell_type": "markdown", "id": "89ae8342-d7e0-465e-885e-3e56082f8575", "metadata": {}, "source": [ "(example.e4cv.custom-class)=\n", "# Example 4-circle diffractometer custom Python class\n", "\n", "It's always possible to define your own subclass of\n", "{class}`~hklpy2.diffract.DiffractometerBase()` when you need more control than\n", "provided by {func}`~hklpy2.diffract.creator()`.\n", "\n", "Here's a brief example of a 4-circle diffractometer with a custom Python class.\n", "Add many additional axes, both in real (rotation angle) space and in reciprocal\n", "(pseudo) space." ] }, { "cell_type": "code", "execution_count": 1, "id": "b6f15454-7d0d-418a-bef4-89be51a26cf1", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "Fourc(prefix='gp:', name='fourc', settle_time=0.0, timeout=None, egu='', limits=(0, 0), source='computed', read_attrs=['h', 'h.readback', 'h.setpoint', 'k', 'k.readback', 'k.setpoint', 'l', 'l.readback', 'l.setpoint', 'ttheta', 'ttheta.user_readback', 'ttheta.user_setpoint', 'theta', 'theta.user_readback', 'theta.user_setpoint', 'chi', 'chi.user_readback', 'chi.user_setpoint', 'phi', 'phi.user_readback', 'phi.user_setpoint', 'h2', 'h2.readback', 'h2.setpoint', 'k2', 'k2.readback', 'k2.setpoint', 'l2', 'l2.readback', 'l2.setpoint', 'psi', 'energy'], configuration_attrs=['geometry', 'solver', 'wavelength', 'h', 'k', 'l', 'ttheta', 'ttheta.user_offset', 'ttheta.user_offset_dir', 'ttheta.velocity', 'ttheta.acceleration', 'ttheta.motor_egu', 'theta', 'theta.user_offset', 'theta.user_offset_dir', 'theta.velocity', 'theta.acceleration', 'theta.motor_egu', 'chi', 'chi.user_offset', 'chi.user_offset_dir', 'chi.velocity', 'chi.acceleration', 'chi.motor_egu', 'phi', 'phi.user_offset', 'phi.user_offset_dir', 'phi.velocity', 'phi.acceleration', 'phi.motor_egu', 'h2', 'k2', 'l2'], concurrent=True)" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import hklpy2\n", "from hklpy2.diffract import Hklpy2PseudoAxis\n", "from ophyd import Component as Cpt\n", "from ophyd import EpicsMotor\n", "from ophyd import Kind\n", "from ophyd import SoftPositioner\n", "\n", "NORMAL_HINTED = Kind.hinted | Kind.normal\n", "\n", "\n", "class Fourc(hklpy2.DiffractometerBase):\n", " \"\"\"Test case.\"\"\"\n", "\n", " # Pseudo-space axes, in order expected by hkl_soleil E4CV, engine=\"hkl\"\n", " h = Cpt(Hklpy2PseudoAxis, \"\", kind=NORMAL_HINTED) # noqa: E741\n", " k = Cpt(Hklpy2PseudoAxis, \"\", kind=NORMAL_HINTED) # noqa: E741\n", " l = Cpt(Hklpy2PseudoAxis, \"\", kind=NORMAL_HINTED) # noqa: E741\n", "\n", " # Real-space axes, in our own order..\n", " # Use different names than the solver for some axes\n", " ttheta = Cpt(EpicsMotor, \"m29\", kind=NORMAL_HINTED)\n", " theta = Cpt(EpicsMotor, \"m30\", kind=NORMAL_HINTED)\n", " chi = Cpt(EpicsMotor, \"m31\", kind=NORMAL_HINTED)\n", " phi = Cpt(EpicsMotor, \"m32\", kind=NORMAL_HINTED)\n", "\n", " # Pseudo-space extra axes used in a couple modes.\n", " h2 = Cpt(Hklpy2PseudoAxis, \"\", kind=NORMAL_HINTED) # noqa: E741\n", " k2 = Cpt(Hklpy2PseudoAxis, \"\", kind=NORMAL_HINTED) # noqa: E741\n", " l2 = Cpt(Hklpy2PseudoAxis, \"\", kind=NORMAL_HINTED) # noqa: E741\n", "\n", " # real-space extra axis used in a couple modes\n", " psi = Cpt(SoftPositioner, limits=(-170, 170), init_pos=0, kind=NORMAL_HINTED)\n", "\n", " # another Component, not used (yet)\n", " energy = Cpt(SoftPositioner, limits=(5, 35), init_pos=12.4, kind=NORMAL_HINTED)\n", "\n", " # Just the axes in expected order by hkl_soleil E4CV.\n", " _pseudo = \"h k l\".split()\n", " _real = \"theta chi phi ttheta\".split()\n", "\n", " def __init__(self, *args, **kwargs):\n", " super().__init__(\n", " *args,\n", " solver=\"hkl_soleil\",\n", " geometry=\"E4CV\",\n", " solver_kwargs=dict(engine=\"hkl\"),\n", " pseudos=\"h k l\".split(),\n", " reals=\"theta chi phi ttheta\".split(),\n", " **kwargs,\n", " )\n", "\n", "\n", "fourc = Fourc(\"gp:\", name=\"fourc\")\n", "fourc.wait_for_connection() # Recommended when connecting to control system.\n", "fourc" ] }, { "cell_type": "code", "execution_count": 2, "id": "321bed6a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Brief 'where' report:\n", "h=0, k=0, l=0\n", "wavelength=1.0\n", "theta=0, chi=0, phi=0, ttheta=0\n" ] } ], "source": [ "print(\"Brief 'where' report:\")\n", "fourc.wh()\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "cf49df56", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Full 'where' report:\n", "diffractometer='fourc'\n", "HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl', mode='bissector')\n", "Sample(name='sample', lattice=Lattice(a=1, system='cubic'))\n", "Orienting reflections: []\n", "U=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]\n", "UB=[[6.283185307179586, 0.0, 0.0], [0.0, 6.283185307179586, 0.0], [0.0, 0.0, 6.283185307179586]]\n", "constraint: -180.0 <= theta <= 180.0\n", "constraint: -180.0 <= chi <= 180.0\n", "constraint: -180.0 <= phi <= 180.0\n", "constraint: -180.0 <= ttheta <= 180.0\n", "h=0, k=0, l=0\n", "wavelength=1.0\n", "theta=0, chi=0, phi=0, ttheta=0\n" ] } ], "source": [ "print(\"Full 'where' report:\")\n", "fourc.wh(full=True)" ] }, { "cell_type": "markdown", "id": "0c4860b0", "metadata": {}, "source": [ "Use the lower level methods to compute `forward()` and `inverse()` transformations." ] }, { "cell_type": "code", "execution_count": 4, "id": "bc44c255", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "FourcRealPos(theta=29.999992418917, chi=1.3051987e-05, phi=89.999997321112, ttheta=59.999984837834)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fourc.forward(1, 0, 0) # Shows the default choice." ] }, { "cell_type": "code", "execution_count": 5, "id": "07e3c0fa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[FourcRealPos(theta=29.999992418917, chi=1.3051987e-05, phi=89.999997321112, ttheta=59.999984837834),\n", " FourcRealPos(theta=-29.999992418917, chi=-1.3051987e-05, phi=-90.000002678888, ttheta=-59.999984837834),\n", " FourcRealPos(theta=-150.000007581083, chi=-1.3051987e-05, phi=-90.000002678888, ttheta=59.999984837834),\n", " FourcRealPos(theta=29.999992418917, chi=179.999986948013, phi=-90.000002678888, ttheta=59.999984837834),\n", " FourcRealPos(theta=-29.999992418917, chi=-179.999986948013, phi=89.999997321112, ttheta=-59.999984837834),\n", " FourcRealPos(theta=-150.000007581083, chi=-179.999986948013, phi=89.999997321112, ttheta=59.999984837834)]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fourc.core.forward(dict(h=1, k=0, l=0)) # Shows ALL the possibilities." ] }, { "cell_type": "code", "execution_count": 6, "id": "ada211c3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'h': 1.000000229316, 'k': -2.28495e-07, 'l': -4.7217e-08}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fourc.core.inverse(dict(ttheta=60, theta=30, chi=0, phi=90))" ] } ], "metadata": { "kernelspec": { "display_name": "hklpy2", "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.13.2" } }, "nbformat": 4, "nbformat_minor": 5 }