Diffractometer#
Diffractometers are built as a subclass of
DiffractometerBase()
, adding a variety of
positioners as ophyd Components. In an instance of that subclass, user
sets backend_solver
by calling
solver_factory()
. In this call, the user specifies the solver,
the geometry, and defines which Components (of the diffractometer) are to be used as
pseudos and reals. The backend implements
forward()
,
inverse()
, and all related support, for
only the pseudos and reals that are identified.
define subclass of
DiffractometerBase()
and create instancecreate instance of
Sample()
create instance of
Lattice()
create instance of
WavelengthBase()
subclasscreate instance of
SolverBase()
subclassset
wavelength
list available solvers: (
solvers()
)review saved orientation details
list available Solver
geometries
list a Solver geometry’s required
pseudo_axis_names
,real_axis_names
,extra_axis_names
,modes
create instance of
Reflection()
define or compute a \(UB\) matrix (
calculateOrientation()
)determine the diffractometer
position
save or restore orientation details
refine lattice parameters
Steps to define a diffractometer object#
Identify the geometry.
Find its Solver, geometry, and other parameters in Diffractometers.
Create a custom subclass for the diffractometer.
(optional) Identify the EPICS PVs for the real positioners.
(optional) Connect energy to the control system.
Define the diffractometer object using
hklpy2.creator()
.
A Diffractometer object#
name#
The name
of a DiffractometerBaseBase()
instance is
completely at the choice of the user and conveys no specific information to
the underlying Python support code.
One important convention is that the name given on the left side of the =
matches the name given by the name="..."
keyword, such as this example:
e4cv = hklpy2.creator(name="e4cv")
geometry#
The geometry describes the physical arrangement of real positioners, pseudo axes, and extra parameters that make up the diffractometer. The choices are limited to those geometries provided the chosen Solver.
core#
All operations are coordinated through Core Operations. This is fourc.core
.
wavelength (and energy)#
The diffractometer._source describes the radiation source using the
WavelengthBase
class. Wavelength is the
term common to both neutron and X-ray diffractometer users.
MonochromaticXrayWavelength
is the default.
This supports conversion between wavelength and X-ray photon energy.
Tip
Neutron users would make a similar class with different calculations between wavelength and energy.
Note
It is more common for X-ray users to describe the energy
of the incident radiation than its wavelength. The
MonochromaticXrayWavelength()
class allows the X-ray photon energy
to be expressed in any engineering units
that are convertible to the expected units (keV).
Note
The wavelength, commonly written as \(\lambda\), cannot be named in Python code as “lambda”. Python reserves lambda as a type of expression: reserved
sample#
The purpose of a diffractometer is to position a sample for scientific
measurements. The sample
attribute is an instance of
Sample
. Behind the scenes, the
Core
class maintains a dictionary of samples (keyed
by name
), each with its own Lattice
and
orientation Reflection
information.
lattice#
Crystal samples have Lattice
parameters defined by
unit cell lengths and angles. (Units here are angstroms and degrees.)
This table describes the lattice of crystalline Vibranium [1]:
sample |
a |
b |
c |
alpha |
beta |
gamma |
---|---|---|---|---|---|---|
vibranium |
\(2\pi\) |
\(2\pi\) |
\(2\pi\) |
90 |
90 |
90 |
orientation#
The UB matrix describes the forward()
and inverse()
transformations that allow
precise positioning of a crystalline sample’s atomic planes in the laboratory
reference system of the diffractometer. It is common to compute the UB matrix
from two orientation reflections using calc_UB()
.
orientation reflections#
An orientation reflection consists of a set of matching pseudos and reals at a specified wavelength. These values may be measured or computed.
There are several use cases for a set of reflections:
Computation of the $UB matrix (for 2 or more non-parallel reflections).
Documentation of observed (or theoretical) reflection settings.
Reference settings so as to re-position the diffractometer.
Define a crystallographic zone or axis to guide the diffractometer for measurements.
Here is an example of three orientation reflections for a sample of crystalline vibranium [1] as mounted on a diffractometer with E4CV geometry:
# |
h |
k |
l |
omega |
chi |
phi |
tth |
wavelength |
orient? |
---|---|---|---|---|---|---|---|---|---|
1 |
4.0 |
0.0 |
0.0 |
-145.451 |
0.0 |
0.0 |
69.0966 |
1.54 |
False |
2 |
0.0 |
4.0 |
0.0 |
-145.451 |
0.0 |
90.0 |
69.0966 |
1.54 |
True |
3 |
0.0 |
0.0 |
4.0 |
-145.451 |
90.0 |
0.0 |
69.0966 |
1.54 |
True |
mode#
The forward()
transformation can have many solutions. The
diffractometer is set to a mode (chosen from a list specified by the
diffractometer geometry) that controls how values for each of the real
positioners will be controlled. A mode can control relationships between
real positioners in addition to limiting the motion of a real positioner.
Further, a mode can specify an additional reflection which will be used to
determine the outcome of the forward()
transformation.
object |
meaning |
---|---|
|
mode selected now |
|
list of possible modes |
Here, DFRCT
is the diffractometer object (such as e4cv
above).
Parts of DiffractometerBase#
A DiffractometerBase
object has several parts:
The DiffractometerBase()
class should
be a thin interface. Most real diffractometer capability should be
provided in the Core()
class (or one of
its attributes, such as solver
and sample
)
Core-related methods and properties
|
Compute real-space coordinates from pseudos (hkl -> angles). |
|
Compute pseudo-space coordinates from reals (angles -> hkl). |
|
Pseudo motor position namedtuple |
Names of all the pseudo axes, in order of appearance. |
|
Names of all the real axes, in order of appearance. |
|
|
Concise report of the current diffractometer positions. |
Sample-related methods and properties
|
Add a new reflection with this geometry to the selected sample. |
|
Add a new sample. |
Current sample object. |
|
Dictionary of samples. |
Solver-related methods and properties
Name of backend Solver geometry. |
|
Name of backend Solver (library). |
|
Backend Solver library name. |
Related methods and properties from other classes
|
Designate attributes for use by the PseudoPositioner class. |
Ordered list of any extra axis names (such as x, y, z). |
|
Sample crystal lattice. |
|
Refine the lattice parameters from 3 or more reflections. |
|
Ordered dictionary of orientation reflections. |
|
|
Create an instance of the backend Solver library and geometry. |
Return the matrix, U, crystal orientation on the diffractometer. |
|
Return the crystal orientation matrix, UB. |
Use a Diffractometer with the bluesky RunEngine#
The positioners of a DiffractometerBase
object may be
used with the bluesky RunEngine
with any of the pre-assembled plans or
in custom plans of your own.
1from hklpy2.misc import ConfigurationRunWrapper 2 3fourc = hklpy2.creator(name="fourc") 4 5# Save configuration with every run 6crw = ConfigurationRunWrapper(fourc) 7RE.preprocessors.append(crw.wrapper) 8 9# steps not shown here: 10# define a sample & orientation reflections, and compute UB matrix 11 12# record the diffractometer metadata to a run 13RE(bp.count([fourc])) 14 15# relative *(h00)* scan 16RE(bp.rel_scan([scaler, fourc], fourc.h, -0.1, 0.1, 21)) 17 18# absolute *(0kl)* scan 19RE(bp.scan([scaler, fourc], fourc.k, 0.9, 1.1, fourc.l, 2, 3, 21)) 20 21# absolute ``chi`` scan 22RE(bp.scan([scaler, fourc], fourc.chi, 30, 60, 31))
Keep in mind these considerations:
Use the
hklpy2.misc.ConfigurationRunWrapper
to save configuration as part of every run. Here’s an example:1from hklpy2.misc import ConfigurationRunWrapper 2crw = ConfigurationRunWrapper(fourc) 3RE.preprocessors.append(crw.wrapper)
Don’t mix axis types (pseudos v. reals) in a scan. You can only scan with either pseudo axes (
h
,k
,l
,q
, …) or real axes (omega
,tth
,chi
, …) at one time. You cannot scan with both types (such ash
andtth
) in a single scan (because theforward()
andinverse()
methods cannot resolve). Example:1# Cannot scan both ``k`` and ``chi`` at the same time. 2# This will raise a `ValueError` exception. 3RE(bp.scan([scaler, fourc], fourc.k, 0.9, 1.1, fourc.chi, 2, 3, 21))
When scanning with pseudo axes (
h
,k
,l
,q
, …), first check that all steps in the scan can be computed successfully with theforward()
computation:fourc.forward(1.9, 0, 0)
Only restore orientation reflections from a matching diffractometer geometry (such as
E4CV
). Mismatch will trigger an exception.