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:
Defining the
FourCircle
class using our local names (instead of the canonical E4CV names)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 rangekeep
theta
in the negative rangekeep
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…