UB matrix : Set directly#

\(UB\) is the 3x3 orientation matrix used to transform coordinates between reciprocal space directions (of the crystal lattice planes) and the rotational axes of the diffractometer.

It is possible to set \(UB\) directly, as shown below.

Alternatively, \(UB\) can be calculated from two non-parallel reflections, using the method of Busing and Levy (Acta Cryst 22 (1967) 457).

Quick example#

In hklpy2, the \(UB\) matrix is a property of the solver. For a diffractometer object named diffractometer, set \(UB\) such as:

diffractometer.operator.solver.UB = [
    [0.5, 6.24, -0.5],
    [-0.5, -0.5, 6.24],
    [-6.24, -0.5, -0.5],
]

Create a diffractometer object#

First, create a diffractometer object that uses the "hkl_soleil" solver with the "hkl" computation engine. This solver provides support for many diffractometer geometries. This example will use the simulated 4-circle geometry from the solver’s "E4CV".

[1]:
from hklpy2 import SimulatedE4CV

diffractometer = SimulatedE4CV(name="diffractometer")

Defaults#

The diffractometer object starts with a default sample. The structure is cubic (\(a=b=c\), 90 degree corners).

[2]:
diffractometer.sample
[2]:
Sample(name='sample', lattice=Lattice(a=1, system='cubic'))

This is the sample the solver will be using.

\(U\) is the orientation of the sample’s crystal lattice as mounted on the diffractometer sample holder. The default is to assume \(U=I\), where \(I\) is the 3x3 identity matrix.

\(U\) is provided by the solver, in this case "hkl_soleil". From the diffractometer object, the solver’s default sample \(U\) is accessed through a couple software layers:

[3]:
diffractometer.operator.solver.U
[3]:
[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]

The default \(UB\) (without knowing how the crystal planes are oriented with respect to the diffractometer) of this cubic crystal is \((2\pi/a) I\) where \(I\) is the 3x3 identity matrix and \(a\) is the lattice parameter. Like \(U\), this is provided by the solver, in this case "hkl_soleil".

[4]:
diffractometer.operator.solver.UB
[4]:
[[6.28318530718, -0.0, -0.0],
 [0.0, 6.28318530718, -0.0],
 [0.0, 0.0, 6.28318530718]]

\(UB\) is used to transform (hkl) to angles (method: forward()) and angles to (hkl) (method: inverse()). These transformations are fundamental to diffractometer operations, so they are provided to the diffractometer object directly.

Here, we compute the angles for the (111) orientation:

[5]:
diffractometer.forward(1, 1, 1)
[5]:
SimulatedE4CVRealPos(omega=60.000000002716, chi=35.264389682252, phi=44.999999999738, tth=120.000000005433)
[6]:
diffractometer.operator.solver.mode
[6]:
'bissector'

Now, convert (hkl) from these angles. Because we have truncated the numerical precision, we should not expect the precise values of (1.0, 1.0, 1.0).

[7]:
diffractometer.inverse(-60, -35, 45, -120)
[7]:
SimulatedE4CVPseudoPos(h=-1.003252265133, k=0.993463529784, l=-1.003252265133)

Set UB to new matrix#

The solver’s \(UB\) matrix can be re-defined by providing a new Python matrix. Here is a matrix for a cubic sample, oriented previously:

[[0.545455316412, -6.239788968842, -0.495930309978],
 [-0.547615630691, -0.543471652084, 6.235639164201],
 [-6.235463558747, -0.498103654451, -0.591011669061]]
[8]:
diffractometer.operator.solver.UB = [
    [0.545455316412, -6.239788968842, -0.495930309978],
    [-0.547615630691, -0.543471652084, 6.235639164201],
    [-6.235463558747, -0.498103654451, -0.591011669061]
]
diffractometer.operator.solver.UB
[8]:
[[0.5454561359, -6.239786441844, -0.49593056545],
 [-0.547617660448, -0.543473530637, 6.235639727849],
 [-6.235461050162, -0.498101063505, -0.591009918402]]
[9]:
diffractometer.operator.solver.U
[9]:
[[0.086812079408, -0.993092986426, -0.078930242493],
 [-0.087156096879, -0.086496568538, 0.992432445262],
 [-0.992404896021, -0.079275872384, -0.094063055512]]

Try it out#

First, compute \((hkl)\) from a set of reals.

[10]:
diffractometer.inverse(-60, -35, 45, -120)
[10]:
SimulatedE4CVPseudoPos(h=0.821953663396, k=0.989926119534, l=1.159500990198)

There can be more than one solution to the forward() transformation; many combinations of reals can be represented by the same set of pseudos. A solver’s geometry provides one or modes which provide additional constraints or relationships to limit the forward() computation.

The E4CV geometry’s "bissector" mode adds this relationship: tth = 2 * omega.

[11]:
diffractometer.operator.solver.mode
[11]:
'bissector'

Still, there can be more than one solution returned by the solver. In hklpy2, the default is to pick the first solution in the list returned from the solver, as shown next:

[12]:
diffractometer._forward_solution
[12]:
<function hklpy2.diffract.pick_first_item(now: tuple, solutions: list)>

Show the first computed solution in "bissector" mode for the \((1,1,1)\) reflection:

[13]:
diffractometer.forward(1, 1, 1)
[13]:
SimulatedE4CVRealPos(omega=-59.999921375969, chi=-28.211225311916, phi=40.202361982144, tth=-119.999842751938)