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.

A Solver is not needed to:
A Solver is needed to:

Steps to define a diffractometer object#

  1. Identify the geometry.

  2. Find its Solver, geometry, and other parameters in Diffractometers.

  3. Create a custom subclass for the diffractometer.

  4. (optional) Identify the EPICS PVs for the real positioners.

  5. (optional) Connect energy to the control system.

  6. 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

DFRCT.core.solver.mode

mode selected now

DFRCT.core.solver.modes

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

forward(pseudos[, wavelength])

Compute real-space coordinates from pseudos (hkl -> angles).

inverse(reals[, wavelength])

Compute pseudo-space coordinates from reals (angles -> hkl).

position

Pseudo motor position namedtuple

pseudo_axis_names

Names of all the pseudo axes, in order of appearance.

real_axis_names

Names of all the real axes, in order of appearance.

wh([digits, full])

Concise report of the current diffractometer positions.

Sample-related methods and properties

add_reflection(pseudos[, reals, wavelength, ...])

Add a new reflection with this geometry to the selected sample.

add_sample(name, a[, b, c, alpha, beta, ...])

Add a new sample.

sample

Current sample object.

samples

Dictionary of samples.

Solver-related methods and properties

geometry

Name of backend Solver geometry.

solver

Name of backend Solver (library).

solver_name

Backend Solver library name.

Related methods and properties from other classes

assign_axes(pseudos, reals)

Designate attributes for use by the PseudoPositioner class.

extra_axis_names

Ordered list of any extra axis names (such as x, y, z).

lattice

Sample crystal lattice.

refine_lattice()

Refine the lattice parameters from 3 or more reflections.

reflections

Ordered dictionary of orientation reflections.

set_solver(name, geometry, **kwargs)

Create an instance of the backend Solver library and geometry.

U

Return the matrix, U, crystal orientation on the diffractometer.

UB

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:

  1. 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)
    
  2. 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 as h and tth) in a single scan (because the forward() and inverse() 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))
    
  3. When scanning with pseudo axes (h, k, l, q, …), first check that all steps in the scan can be computed successfully with the forward() computation:

    fourc.forward(1.9, 0, 0)
    
  4. Only restore orientation reflections from a matching diffractometer geometry (such as E4CV). Mismatch will trigger an exception.