{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Diffractometer Extra Motors and/or Pseudos\n", "\n", "Sometimes, it is desired to add additional ophyd components to\n", "a diffractometer object. Such components could include additional motor axes, azimuthal reference vectors, temperature, etc.\n", "\n", "**Objective**\n", "\n", "Add one or more real positioners to the standard positioners of the 2-circle\n", "diffractometer (*tth_tth*, *TH TTH Q* [geometry](../geometry_tables.rst)). Use\n", "simulated motors for the example (no EPICS required)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Standard 2-circle\n", "\n", "First, we start with the setup of a 2-circle ($\\theta:2\\theta$) diffractometer.\n", "\n", "axis | space\n", "--- | ---\n", "th | real\n", "tth | real\n", "q | pseudo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create the diffractometer object." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import hklpy2\n", "\n", "th2th = hklpy2.creator(name=\"th2th\", geometry=\"TH TTH Q\", solver=\"th_tth\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Show the diffractometer configuration and all the ophyd components." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "q=0\n", "wavelength=1.0\n", "th=0, tth=0\n", "th2th.component_names=('geometry', 'solver', 'wavelength', 'q', 'th', 'tth')\n" ] } ], "source": [ "th2th.wh()\n", "print(f\"{th2th.component_names=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Add additional positioner\n", "\n", "We can use the `hklpy2.creator()` for the additional positioner. Since we are not using the default `reals`, we'll provide a Python dictionary that defines each real axis, in order, and whether it uses a simulated motor or an EPICS PV. `None` means to use a simulated motor." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "q=0\n", "wavelength=1.0\n", "th=0, tth=0\n", "th2th.component_names=('geometry', 'solver', 'wavelength', 'q', 'th', 'tth', 'spinner')\n" ] } ], "source": [ "th2th = hklpy2.creator(\n", " name=\"th2th\",\n", " geometry=\"TH TTH Q\",\n", " solver=\"th_tth\",\n", " reals=dict(th=None, tth=None, spinner=None)\n", ")\n", "th2th.wh()\n", "print(f\"{th2th.component_names=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compare these results. The new result adds the `spinner` axis.\n", "\n", "Set (and show) the limits on the spinner:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(-10000, 10000)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "th2th.spinner._limits = -10_000, 10_000\n", "th2th.spinner.limits" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Can we add other pseudo axes?\n", "\n", "**Q**: With this capability to add additional real positioners, can we add axes\n", "to the pseudo positioners?\n", "\n", "**A**: Yes. See this example. It defines two pseudo axes: `Q` and `d`. As shown in `aliases`, the `Q` axis is mapped to the {ref}`solver ` `q` axis." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Q=0\n", "wavelength=1.0\n", "sample=0, detector=0\n" ] } ], "source": [ "th2th = hklpy2.creator(\n", " name=\"th2th\",\n", " geometry=\"TH TTH Q\",\n", " solver=\"th_tth\",\n", " pseudos=[\"Q\", \"d\"], # \n", " reals=dict(sample=None, detector=None, spinner=None),\n", " aliases=dict(pseudos=[\"Q\"], reals=[\"sample\", \"detector\"]),\n", ")\n", "th2th.wh()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Add additional Signals and Devices\n", "\n", "Finally, we add additional Signals and Component Devices as a demonstration.\n", "\n", "The {func}function]`~hklpy2.diffract.creator()` has its limits. The `creator()`\n", "relies on a {func}function]`~hklpy2.diffract.diffractometer_class_factory()`.\n", "Let's skip the factory function and show how to build a structure directly.\n", "\n", "Demonstrate a variety of additional components." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "q=0\n", "wavelength=1.0\n", "th=0, tth=0\n" ] }, { "data": { "text/plain": [ "OrderedDict([('th2tth_q', {'value': 0, 'timestamp': 1743011146.56252}),\n", " ('th2tth_q_setpoint',\n", " {'value': 0, 'timestamp': 1743011146.5625339}),\n", " ('th2tth_th', {'value': 0, 'timestamp': 1743011146.5971136}),\n", " ('th2tth_tth', {'value': 0, 'timestamp': 1743011146.5971181}),\n", " ('th2tth_spinner', {'value': 0, 'timestamp': 1743011146.5971215}),\n", " ('th2tth_atth', {'value': 0, 'timestamp': 1743011146.5971239}),\n", " ('th2tth_temperature',\n", " {'value': 25, 'timestamp': 1743011146.5629923}),\n", " ('th2tth_xy_x', {'value': 0, 'timestamp': 1743011146.5971382}),\n", " ('th2tth_xy_y', {'value': 0, 'timestamp': 1743011146.597141}),\n", " ('th2tth_xy_solenoid_lock',\n", " {'value': True, 'timestamp': 1743011146.563218})])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from hklpy2.diffract import Hklpy2PseudoAxis\n", "from ophyd import Component, Device, Signal, SoftPositioner\n", "from ophyd.signal import SignalRO\n", "\n", "\n", "class XYStage(Device):\n", " x = Component(SoftPositioner, kind=\"hinted\", limits=(-20, 105), init_pos=0)\n", " y = Component(SoftPositioner, kind=\"hinted\", limits=(-20, 105), init_pos=0)\n", " solenoid_lock = Component(Signal, value=True, kind=\"normal\")\n", "\n", "\n", "class MyTwoCircle(hklpy2.DiffractometerBase):\n", " _real = [\"th\", \"tth\"]\n", "\n", " q = Component(Hklpy2PseudoAxis, \"\", kind=\"hinted\")\n", " th = Component(\n", " SoftPositioner, kind=\"hinted\", limits=(-180, 180), egu=\"degrees\", init_pos=0\n", " )\n", " tth = Component(\n", " SoftPositioner, kind=\"hinted\", limits=(-180, 180), egu=\"degrees\", init_pos=0\n", " )\n", " spinner = Component(\n", " SoftPositioner,\n", " kind=\"hinted\",\n", " limits=(-10000, 10000),\n", " egu=\"rotations\",\n", " init_pos=0,\n", " )\n", " atth = Component(\n", " SoftPositioner, kind=\"hinted\", limits=(-180, 180), egu=\"degrees\", init_pos=0\n", " )\n", " temperature = Component(SignalRO, value=25, kind=\"normal\")\n", " xy = Component(XYStage, kind=\"normal\")\n", "\n", " def __init__(self, *args, **kwargs):\n", " super().__init__(\n", " *args,\n", " solver=\"th_tth\", # solver name\n", " geometry=\"TH TTH Q\", # solver geometry\n", " **kwargs,\n", " )\n", "\n", "\n", "th2tth = MyTwoCircle(name=\"th2tth\")\n", "th2tth.wh() # brief report of diffractometer position\n", "# th2th.summary() # show the full ophyd structure summary\n", "th2tth.read()" ] } ], "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": 4 }