Rename the axes of a 4-circle diffractometer#

Following the E4CV example, this example will repeat those same steps but using different names for the motor axes.

E4CV name

local name

omega

theta

chi

chi

phi

phi

tth

ttheta

How’s it done?#

This change is made by:

  1. Defining the FourCircle class using our local names (instead of the canonical E4CV names)

  2. Writing a dictionary to the fourc object that maps the canonical E4CV names to our local names:

fourc.calc.physical_axis_names = {
    # E4CV: local
    'omega': 'theta',
    'chi': 'chi',
    'phi': 'phi',
    'tth': 'ttheta',
    }

Note: This example is available as a Jupyter notebook from the hklpy source code website: bluesky/hklpy

Define this diffractometer#

Following the E4CV example, create a FourCircle class, but use our own names for the motors.

E4CV name

local name

omega

theta

chi

chi

phi

phi

tth

ttheta

Use the new motor names when constructing the FourCircle class. Otherwise, everything else in this step the same as in the E4CV example.

[1]:
from hkl import E4CV, SimMixin
from ophyd import SoftPositioner
from ophyd import Component as Cpt

class FourCircle(SimMixin, E4CV):
    """
    Our 4-circle.  Eulerian, vertical scattering orientation.
    """
    # reciprocal axes defined in SimMixin

    # the motor axes are called: real in hklpy
    theta = Cpt(SoftPositioner, kind="hinted", init_pos=0)
    chi = Cpt(SoftPositioner, kind="hinted", init_pos=0)
    phi = Cpt(SoftPositioner, kind="hinted", init_pos=0)
    ttheta = Cpt(SoftPositioner, kind="hinted", init_pos=0)

Create an instance of the diffractometer.#

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.

[2]:
fourc = FourCircle("", name="fourc")
print("motor names:", fourc.calc.physical_axis_names)
motor names: ['omega', 'chi', 'phi', 'tth']

Switch to our motor names#

This is the magic step, map the canonical libhkl names onto 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 must be in the dictionary, even if the names remain the same.

[3]:
fourc.calc.physical_axis_names = {
    # E4CV: local
    'omega': 'theta',
    'chi': 'chi',
    'phi': 'phi',
    'tth': 'ttheta',
    }

print("motor names:", fourc.calc.physical_axis_names)
motor names: ['theta', 'chi', 'phi', 'ttheta']

Add a sample with a crystal structure#

[4]:
from hkl import Lattice
from hkl import SI_LATTICE_PARAMETER

# add the sample to the calculation engine
a0 = SI_LATTICE_PARAMETER
fourc.calc.new_sample(
    "silicon",
    lattice=Lattice(a=a0, b=a0, c=a0, alpha=90, beta=90, gamma=90)
    )
[4]:
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.],
       [0., 1., 0.],
       [0., 0., 1.]]), UB=array([[ 1.15690694e+00, -7.08401189e-17, -7.08401189e-17],
       [ 0.00000000e+00,  1.15690694e+00, -7.08401189e-17],
       [ 0.00000000e+00,  0.00000000e+00,  1.15690694e+00]]), reflections=[])

Setup the UB orientation matrix using hklpy#

Define the crystal’s orientation on the diffractometer using the 2-reflection method described by Busing & Levy, Acta Cryst 22 (1967) 457.

Choose the same wavelength X-rays for both reflections#

[5]:
fourc.calc.wavelength = 1.54  # Angstrom (8.0509 keV)

Find the first reflection and identify its Miller indices: (hkl)#

[6]:
r1 = fourc.calc.sample.add_reflection(
    4, 0, 0,
    position=fourc.calc.Position(
        ttheta=69.0966,
        theta=-145.451,
        chi=0,
        phi=0,
    )
)

Find the second reflection#

[7]:
r2 = fourc.calc.sample.add_reflection(
    0, 4, 0,
    position=fourc.calc.Position(
        ttheta=69.0966,
        theta=-145.451,
        chi=90,
        phi=0,
    )
)

Compute the UB orientation matrix#

The compute_UB() method always returns 1. Ignore it.

[8]:
fourc.calc.sample.compute_UB(r1, r2)
[8]:
array([[-1.41342846e-05, -1.41342846e-05, -1.15690694e+00],
       [ 0.00000000e+00, -1.15690694e+00,  1.41342846e-05],
       [-1.15690694e+00,  1.72682934e-10,  1.41342846e-05]])

Report what we have setup#

[9]:
import pyRestTable

tbl = pyRestTable.Table()
tbl.labels = "term value".split()
tbl.addRow(("energy, keV", fourc.calc.energy))
tbl.addRow(("wavelength, angstrom", fourc.calc.wavelength))
tbl.addRow(("position", fourc.position))
tbl.addRow(("sample name", fourc.sample_name.get()))
tbl.addRow(("[U]", fourc.U.get()))
tbl.addRow(("[UB]", fourc.UB.get()))
tbl.addRow(("lattice", fourc.lattice.get()))
print(tbl)

print(f"sample\t{fourc.calc.sample}")
==================== =========================================================================
term                 value
==================== =========================================================================
energy, keV          8.050921974025975
wavelength, angstrom 1.54
position             FourCirclePseudoPos(h=-0.0, k=0.0, l=0.0)
sample name          silicon
[U]                  [[-1.22173048e-05 -1.22173048e-05 -1.00000000e+00]
                      [ 0.00000000e+00 -1.00000000e+00  1.22173048e-05]
                      [-1.00000000e+00  1.49262536e-10  1.22173048e-05]]
[UB]                 [[-1.41342846e-05 -1.41342846e-05 -1.15690694e+00]
                      [ 0.00000000e+00 -1.15690694e+00  1.41342846e-05]
                      [-1.15690694e+00  1.72682934e-10  1.41342846e-05]]
lattice              [ 5.43102051  5.43102051  5.43102051 90.         90.         90.        ]
==================== =========================================================================

sample  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=-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],
       [ 0.00000000e+00, -1.00000000e+00,  1.22173048e-05],
       [-1.00000000e+00,  1.49262536e-10,  1.22173048e-05]]), UB=array([[-1.41342846e-05, -1.41342846e-05, -1.15690694e+00],
       [ 0.00000000e+00, -1.15690694e+00,  1.41342846e-05],
       [-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],
       [1.57079633, 0.        ]]), reflection_theoretical_angles=array([[0.        , 1.57079633],
       [1.57079633, 0.        ]])))

Check the orientation matrix#

Perform checks with forward (hkl to angle) and inverse (angle to hkl) computations to verify the diffractometer will move to the same positions where the reflections were identified.

Constrain the motors to limited ranges#

  • allow for slight roundoff errors

  • keep ttheta in the positive range

  • keep theta in the negative range

  • keep phi fixed at zero

[10]:
fourc.calc["ttheta"].limits = (-0.001, 180)
fourc.calc["theta"].limits = (-180, 0.001)

fourc.phi.move(0)
fourc.engine.mode = "constant_phi"

(400) reflection#

Check the inverse calculation: (400)#

[11]:
sol = fourc.inverse((-145.451, 0, 0, 69.0966))
print(f"(4 0 0) ? {sol.h:.2f} {sol.k:.2f} {sol.l:.2f}")
(4 0 0) ? 4.00 0.00 0.00

Check the forward calculation: (400)#

[12]:
sol = fourc.forward((4, 0, 0))
print(
    "(400) :",
    f"ttheta={sol.ttheta:.4f}",
    f"theta={sol.theta:.4f}",
    f"chi={sol.chi:.4f}",
    f"phi={sol.phi:.4f}"
    )
(400) : ttheta=69.0982 theta=-145.4502 chi=0.0000 phi=0.0000

(040) reflection#

Check another inverse calculation: (040)#

[13]:
sol = fourc.inverse((-145.451, 90, 0, 69.0966))
print(f"(0 4 0) ? {sol.h:.2f} {sol.k:.2f} {sol.l:.2f}")
(0 4 0) ? 0.00 4.00 0.00

Continue the E4CV example on your own…