Set and compute \(\psi\) (psi)#

Show how to set and compute \(\psi\) with the E6C diffractometer geometry.

The angle \(\psi\) is a rotation around some virtual azimuthal axis as the diffractometer is oriented for a different axis.

This operation is a bit complicated. It requires two instances of the E6C diffractometer geometry, each with a different calculation engine. The "hkl" engine is used to work in reciprocal-space coordinates \(h, k, l\). The "psi" engine is used to work with the \(\psi\) rotation.

First, we’ll orient a crystalline sample with the "hkl" engine. Then, we’ll define the azimuthal reflection \(h_2, k_2, l_2\) and a \(\psi\) rotation around that azimuthal reflection. Next, we’ll position the diffractometer for the \(h, k, l\) reflection.

Then we copy sample, orientation, and position information from the "hkl" instance to the "psi" instance. After these steps, we can compare the computed psi value with the value we set previously.

SimulatedE6C#

[1]:
import hkl
e6c_hkl = hkl.SimulatedE6C("", name="e6c_hkl")

Show the different calculation engines available for the E6C geometry.

[2]:
e6c_hkl.calc.engines
[2]:
{'hkl': Engine(parameters=[], pseudo_axes=OrderedDict([('h', 0.0), ('k', 0.0), ('l', 0.0)]), mode='bissector_vertical', modes=['bissector_vertical', 'constant_omega_vertical', 'constant_chi_vertical', 'constant_phi_vertical', 'lifting_detector_phi', 'lifting_detector_omega', 'lifting_detector_mu', 'double_diffraction_vertical', 'bissector_horizontal', 'double_diffraction_horizontal', 'psi_constant_vertical', 'psi_constant_horizontal', 'constant_mu_horizontal'], units='user'),
 'psi': Engine(parameters=['h2', 'k2', 'l2'], pseudo_axes=OrderedDict([('psi', 0.0)]), mode='psi_vertical', modes=['psi_vertical'], units='user'),
 'q2': Engine(parameters=[], pseudo_axes=OrderedDict([('q', 0.0), ('alpha', 0.0)]), mode='q2', modes=['q2'], units='user'),
 'qper_qpar': Engine(parameters=['x', 'y', 'z'], pseudo_axes=OrderedDict([('qper', 0.0), ('qpar', 0.0)]), mode='qper_qpar', modes=['qper_qpar'], units='user'),
 'tth2': Engine(parameters=[], pseudo_axes=OrderedDict([('tth', 0.0), ('alpha', 0.0)]), mode='tth2', modes=['tth2'], units='user'),
 'incidence': Engine(parameters=['x', 'y', 'z'], pseudo_axes=OrderedDict([('incidence', 0.0), ('azimuth', 0.0)]), mode='incidence', modes=['incidence'], units='user'),
 'emergence': Engine(parameters=['x', 'y', 'z'], pseudo_axes=OrderedDict([('emergence', 0.0), ('azimuth', 0.0)]), mode='emergence', modes=['emergence'], units='user')}
  • The hkl engine has a "psi_constant_vertical" mode that can be used to calculate reals given some fixed parameters (UB, wavelength, \((hkl)\), \((hkl)_2\), \(\psi\))

  • The psi engine has a pseudo axis "psi" that can be used to calculate \(\psi\) given some fixed parameters (reals, UB, wavelength, \((hkl)\), \((hkl)_2\))

Define and orient a sample#

The sample for this notebook is crystalline vibranium, with a cubic lattice of exactly \(2\pi\). With it mounted on oru diffractometer, we have identified two reflections which define its orientation.

[3]:
a0 = 2 * 3.141592653589793
vibranium_lattice = hkl.Lattice(a=a0, b=a0, c=a0, alpha=90, beta=90, gamma=90)
sample = e6c_hkl.calc.new_sample("vibranium", lattice=vibranium_lattice)
sample.compute_UB(
    sample.add_reflection(4, 0, 0, [0, 29.35, 0, 50, 0, 58.71]),
    sample.add_reflection(0, 4, 0, [0, 29.35, 0, -40, 0, 58.71]),
)

[3]:
array([[ 7.65988346e-01, -6.42854457e-01, -7.53977616e-18],
       [ 0.00000000e+00,  0.00000000e+00, -1.00000000e+00],
       [ 6.42854457e-01,  7.65988346e-01, -8.62667415e-17]])

Move to the \((111)\) orientation#

[4]:
e6c_hkl.move(1, 1, 1)
e6c_hkl.engine.mode = "psi_constant_vertical"
print(f"{e6c_hkl.engine.parameters=}")
e6c_hkl.engine.parameters=['h2', 'k2', 'l2', 'psi']

Set azimuthal reflection \((110)\) and \(\psi=12\).

[5]:
e6c_hkl.engine._engine.parameters_values_set([1, 1, 0, 12], 1)
print(f"{e6c_hkl.engine._engine.parameters_values_get(1)=}")
e6c_hkl.engine._engine.parameters_values_get(1)=[1.0, 1.0, 0.0, 12.0]

Compute the real-axis motor values with the \((111)\) reflection oriented and \(\psi\) rotation around the azimuthal reflection.

[6]:
p_111 = e6c_hkl.forward(1, 1, 1)
print(f"{p_111=}")
p_111=PosCalcE6C(mu=0.0, omega=66.39160677520167, chi=-80.22618091661815, phi=-78.00866699932374, gamma=0.0, delta=24.50984451740515)

Move all reals to the \((111)\) reflection.

[7]:
for axis in p_111._fields:
    getattr(e6c_hkl, axis).move(getattr(p_111, axis))
print(f"{e6c_hkl.position=}")
print(f"{e6c_hkl.real_position=}")
print(f"{e6c_hkl.engine._engine.parameters_values_get(1)=}")
e6c_hkl.position=SimulatedE6CPseudoPos(h=0.9999999973844704, k=1.0000000120446895, l=0.9999999932554844)
e6c_hkl.real_position=SimulatedE6CRealPos(mu=0.0, omega=66.39160677520167, chi=-80.22618091661815, phi=-78.00866699932374, gamma=0.0, delta=24.50984451740515)
e6c_hkl.engine._engine.parameters_values_get(1)=[1.0, 1.0, 0.0, 12.0]

Calculate \(\psi\)#

[8]:
import hkl.diffract
from ophyd import Component as Cpt
from ophyd import PseudoSingle
from ophyd import SoftPositioner

class SimulatedPsiE6C(hkl.E6C):
    """SimulatedE6C: Eulerian 6-circle diffractometer, psi engine"""

    calc_class = hkl.calc.CalcE6C

    psi = Cpt(PseudoSingle, "", kind="hinted")

    mu = Cpt(SoftPositioner, limits=(-180, 180), init_pos=0, kind="normal")
    omega = Cpt(SoftPositioner, limits=(-180, 180), init_pos=0, kind="normal")
    chi = Cpt(SoftPositioner, limits=(-180, 180), init_pos=0, kind="normal")
    phi = Cpt(SoftPositioner, limits=(-180, 180), init_pos=0, kind="normal")
    gamma = Cpt(SoftPositioner, limits=(-180, 180), init_pos=0, kind="normal")
    delta = Cpt(SoftPositioner, limits=(-180, 180), init_pos=0, kind="normal")

    def __init__(self, prefix, **kwargs):
        super().__init__(prefix, engine="psi", **kwargs)

[9]:
e6c_psi = SimulatedPsiE6C("", name="e6c_psi")
print(f"{e6c_psi.engine.mode=}")
print(f"{e6c_psi.engine.parameters=}")
e6c_psi.engine.mode='psi_vertical'
e6c_psi.engine.parameters=['h2', 'k2', 'l2']

Same sample, lattice, and orientation. Same real-axis position.

[10]:
e6c_psi.calc.new_sample(e6c_hkl.calc.sample.name, lattice=e6c_hkl.calc.sample.lattice)
e6c_psi.calc.sample.UB = e6c_hkl.UB.get()
e6c_psi.engine._engine.parameters_values_set([1, 1, 0], 1)
for axis in p_111._fields:  # move all reals to the (111) reflection
    getattr(e6c_psi, axis).move(getattr(p_111, axis))
print(f"{e6c_psi.position=}")
print(f"{e6c_psi.real_position=}")
e6c_psi.wh()
e6c_psi.position=SimulatedPsiE6CPseudoPos(psi=12.000007801823596)
e6c_psi.real_position=SimulatedPsiE6CRealPos(mu=0.0, omega=66.39160677520167, chi=-80.22618091661815, phi=-78.00866699932374, gamma=0.0, delta=24.50984451740515)
===================== ================== =========
term                  value              axis_type
===================== ================== =========
diffractometer        e6c_psi
sample name           vibranium
energy (keV)          8.05092
wavelength (angstrom) 1.54000
calc engine           psi
mode                  psi_vertical
psi                   12.000007801823596 pseudo
mu                    0.0                real
omega                 66.39160677520167  real
chi                   -80.22618091661815 real
phi                   -78.00866699932374 real
gamma                 0.0                real
delta                 24.50984451740515  real
===================== ================== =========

[10]:
<pyRestTable.rest_table.Table at 0x7f4b67c60ad0>