How to Compute and Set the UB Matrix#
The \(UB\) orientation matrix encodes how the crystal is mounted on
the diffractometer and is required before any
forward() calculation can succeed. This guide covers the common tasks: computing \(UB\)
from reflections, setting it manually, inspecting and refining it, and
resetting it.
See also
Tutorial: Your First Diffractometer — step-by-step walkthrough of the full orientation workflow for a new user.
Constraints — filter forward()
solutions after the UB matrix is set.
Architecture & Design Decisions — explanation of the \(B\), \(U\), and \(UB\) matrices.
hkl_soleil UB matrix : calculate from 2 reflections. — executable notebook: compute
\(UB\) from two reflections using the hkl_soleil solver.
hkl_soleil UB matrix : Set directly — executable notebook: set \(UB\) directly from a known matrix.
Setup#
All examples use a simulated 4-circle diffractometer with silicon:
>>> import hklpy2
>>> from hklpy2.user import (
... add_sample, calc_UB, or_swap, pa,
... remove_reflection, set_diffractometer, setor,
... )
>>> fourc = hklpy2.creator(name="fourc", geometry="E4CV", solver="hkl_soleil")
>>> set_diffractometer(fourc)
>>> add_sample("silicon", a=hklpy2.SI_LATTICE_PARAMETER)
>>> fourc.beam.wavelength.put(1.54)
How do I add orientation reflections?#
Use setor() to record a reflection — the measured
motor angles for a known \((h, k, l)\) position:
>>> r1 = setor(4, 0, 0, tth=69.0966, omega=-145.451, chi=0, phi=0)
>>> r2 = setor(0, 4, 0, tth=69.0966, omega=-145.451, chi=90, phi=0)
setor() uses the diffractometer’s current wavelength
at the time it is called. To record a reflection measured at a different
wavelength, pass wavelength= explicitly:
>>> r3 = setor(0, 0, 4, tth=69.0966, omega=-145.451, chi=90, phi=90,
... wavelength=1.00)
For lower-level control, add_reflection() on
fourc.core accepts the same arguments and returns a
Reflection object directly.
How do I compute UB from two reflections?#
Call calc_UB() with the two reflection objects
returned by setor():
>>> calc_UB(r1, r2)
[[-1.4134285e-05, -1.4134285e-05, -1.156906937382],
[0.0, -1.156906937469, 1.4134285e-05],
[-1.156906937469, 1.73e-10, 1.4134285e-05]]
The matrix is stored on the sample and pushed to the solver automatically. You can also pass reflection names instead of objects:
>>> calc_UB(r1.name, r2.name)
Or call the equivalent method directly on Core:
>>> fourc.core.calc_UB(r1, r2)
How do I swap the two orienting reflections?#
or_swap() swaps the first two reflections in the
orientation list and recomputes \(UB\). This is equivalent to
SPEC’s or_swap command:
>>> or_swap()
The new \(UB\) matrix is returned and stored automatically.
How do I set UB manually from a known matrix?#
Assign directly to UB on the
sample. A plain nested Python list is sufficient — a numpy array is
accepted but not required:
>>> fourc.core.sample.UB = [
... [0.0, 0.0, -1.1569],
... [0.0, -1.1569, 0.0 ],
... [-1.1569, 0.0, 0.0 ],
... ]
The solver is updated automatically on the next
forward() or
inverse() call.
How do I inspect the current UB matrix?#
Call pa() to see the full diffractometer state
including the \(U\) and \(UB\) matrices:
>>> pa()
Or access the matrix directly:
>>> fourc.core.sample.UB
How do I remove a reflection?#
Use remove_reflection() with the reflection’s name:
>>> remove_reflection(r1.name)
To remove all reflections and reset the sample to defaults:
>>> fourc.core.reset_samples()
Note
reset_samples() removes all samples and
reflections and recreates a single default sample. Use
remove_reflection() if you only want to discard
one reflection while keeping others.
How do I refine the lattice parameters?#
If the solver supports it, refine_lattice()
uses three or more reflections to refine the lattice parameters:
>>> r3 = setor(0, 0, 4, tth=69.0966, omega=-145.451, chi=90, phi=90)
>>> fourc.core.refine_lattice(r1, r2, r3)
The refined Lattice is returned and
stored on the sample. Not all solvers implement lattice refinement —
check the solver documentation if this raises NotImplementedError.
How do I verify the UB matrix is correct?#
The standard check is to confirm that
inverse() at the reflection
angles recovers the expected \((h, k, l)\), and that
forward() at the expected
\((h, k, l)\) returns angles close to the measured ones:
>>> # inverse: angles → hkl
>>> fourc.inverse(dict(omega=-145.451, chi=0, phi=0, tth=69.0966))
Hklpy2DiffractometerPseudoPos(h=3.9999, k=0, l=0) # ≈ (4, 0, 0) ✓
>>> # forward: hkl → angles
>>> fourc.forward(4, 0, 0)
Hklpy2DiffractometerRealPos(omega=-34.5491, chi=0.0, phi=-110.9011, tth=69.0982)
Small differences (last decimal place) are normal floating-point rounding. Large differences indicate the orientation reflections were recorded incorrectly or the wrong geometry was selected.