Diffractometer Configuration – Save and Restore#

This document shows examples that save and restore the configuration details of a diffractometer. The configuration can be saved to a file. Also, the configuration can be saved in a bluesky run (with the run’s metadata). The details include:

  • diffractometer name

  • axes

    • real-space axis names

    • mapping to canonical real-space axes known to the solver

    • reciprocal-space axis names

    • any extra axes, as used by the solver

  • sample(s)

    • lattice

    • orientation

    • reflections

  • constraints

  • solver

    • geometry

    • real-space axes

    • mode

    • version

  • _header (date & time, energy, wavelength, source type, version)

With these configuration details, it should be possible to recover a diffractometer orientation from a previous run or from file storage.

Files#

Create a simulated 4-circle diffractometer. Use the factory function. The default will use the E4CV geometry from the hkl_soleil solver.

[1]:
import hklpy2

sim4c = hklpy2.creator(name="sim4c")

Add a vibranium sample and describe its orientation.

[2]:
import math

sim4c.add_sample("vibranium", 2*math.pi, digits=3)
sim4c.add_reflection((1, 0, 0), (10, 0, 0, 20), name="r1")
sim4c.add_reflection((0, 1, 0), (10, -90, 0, 20), name="r2")
for r in sim4c.sample.reflections.order:
    print(f"{sim4c.sample.reflections[r]}")
sim4c.operator.calc_UB(*sim4c.sample.reflections.order)
Reflection(name='r1', geometry='E4CV', pseudos={'h': 1, 'k': 0, 'l': 0}, reals={'omega': 10, 'chi': 0, 'phi': 0, 'tth': 20}, wavelength=1.0, digits=4)
Reflection(name='r2', geometry='E4CV', pseudos={'h': 0, 'k': 1, 'l': 0}, reals={'omega': 10, 'chi': -90, 'phi': 0, 'tth': 20}, wavelength=1.0, digits=4)

Show details about the diffractometer configuration:

[3]:
sim4c.configuration
[3]:
{'_header': {'datetime': '2025-02-18 15:22:12.551188',
  'energy_units': 'keV',
  'energy': 12.398419843856837,
  'hklpy2_version': '0.0.26.dev46+gcbd9032.d20250218',
  'python_class': 'Hklpy2Diffractometer',
  'source_type': 'X-ray',
  'wavelength_units': 'angstrom',
  'wavelength': 1.0},
 'name': 'sim4c',
 'axes': {'pseudo_axes': ['h', 'k', 'l'],
  'real_axes': ['omega', 'chi', 'phi', 'tth'],
  'axes_xref': {'h': 'h',
   'k': 'k',
   'l': 'l',
   'omega': 'omega',
   'chi': 'chi',
   'phi': 'phi',
   'tth': 'tth'},
  'extra_axes': {}},
 'sample_name': 'vibranium',
 'samples': {'sample': {'name': 'sample',
   'lattice': {'a': 1,
    'b': 1,
    'c': 1,
    'alpha': 90.0,
    'beta': 90.0,
    'gamma': 90.0},
   'reflections': {},
   'reflections_order': [],
   'U': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
   'UB': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
   'digits': 4},
  'vibranium': {'name': 'vibranium',
   'lattice': {'a': 6.283185307179586,
    'b': 6.283185307179586,
    'c': 6.283185307179586,
    'alpha': 90.0,
    'beta': 90.0,
    'gamma': 90.0},
   'reflections': {'r1': {'name': 'r1',
     'geometry': 'E4CV',
     'pseudos': {'h': 1, 'k': 0, 'l': 0},
     'reals': {'omega': 10, 'chi': 0, 'phi': 0, 'tth': 20},
     'wavelength': 1.0,
     'digits': 4},
    'r2': {'name': 'r2',
     'geometry': 'E4CV',
     'pseudos': {'h': 0, 'k': 1, 'l': 0},
     'reals': {'omega': 10, 'chi': -90, 'phi': 0, 'tth': 20},
     'wavelength': 1.0,
     'digits': 4}},
   'reflections_order': ['r1', 'r2'],
   'U': [[-0.0, -0.0, 1.0], [0.0, -1.0, -0.0], [1.0, -0.0, 0.0]],
   'UB': [[-0.0, -0.0, 1.0], [0.0, -1.0, 0.0], [1.0, -0.0, -0.0]],
   'digits': 3}},
 'constraints': {'omega': {'label': 'omega',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'chi': {'label': 'chi',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'phi': {'label': 'phi',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'tth': {'label': 'tth',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'}},
 'solver': {'name': 'hkl_soleil',
  'description': "HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl', mode='bissector')",
  'geometry': 'E4CV',
  'real_axes': ['omega', 'chi', 'phi', 'tth'],
  'version': '5.1.2',
  'engine': 'hkl'}}

Export (save) the configuration#

Save the sim4c configuration (with vibranium sample orientation) to a YAML file in the current working directory: dev_e4cv-vibranium.yml. (Here, we choose to identify file by including the diffractometer geometry as part of the file name. The dev_ prefix is an additional arbitrary choice, you could choose a completely different file name.)

[4]:
sim4c.operator.export("dev_e4cv-vibranium.yml", comment="example")

Let’s look at that file. Then, compare it with the sim4c.configuration reported above.

[5]:
%pycat dev_e4cv-vibranium.yml
#hklpy2 configuration file

_header:
  datetime: '2025-02-18 15:22:12.696168'
  energy_units: keV
  energy: 12.398419843856837
  hklpy2_version: 0.0.26.dev46+gcbd9032.d20250218
  python_class: Hklpy2Diffractometer
  source_type: X-ray
  wavelength_units: angstrom
  wavelength: 1.0
  file: dev_e4cv-vibranium.yml
  comment: example
name: sim4c
axes:
  pseudo_axes:
  - h
  - k
  - l
  real_axes:
  - omega
  - chi
  - phi
  - tth
  axes_xref:
    h: h
    k: k
    l: l
    omega: omega
    chi: chi
    phi: phi
    tth: tth
  extra_axes: {}
sample_name: vibranium
samples:
  sample:
    name: sample
    lattice:
      a: 1
      b: 1
      c: 1
      alpha: 90.0
      beta: 90.0
      gamma: 90.0
    reflections: {}
    reflections_order: []
    U: &id001
    - - 1
      - 0
      - 0
    - - 0
      - 1
      - 0
    - - 0
      - 0
      - 1
    UB: *id001
    digits: 4
  vibranium:
    name: vibranium
    lattice:
      a: 6.283185307179586
      b: 6.283185307179586
      c: 6.283185307179586
      alpha: 90.0
      beta: 90.0
      gamma: 90.0
    reflections:
      r1:
        name: r1
        geometry: E4CV
        pseudos:
          h: 1
          k: 0
          l: 0
        reals:
          omega: 10
          chi: 0
          phi: 0
          tth: 20
        wavelength: 1.0
        digits: 4
      r2:
        name: r2
        geometry: E4CV
        pseudos:
          h: 0
          k: 1
          l: 0
        reals:
          omega: 10
          chi: -90
          phi: 0
          tth: 20
        wavelength: 1.0
        digits: 4
    reflections_order:
    - r1
    - r2
    U:
    - - -0.0
      - -0.0
      - 1.0
    - - 0.0
      - -1.0
      - -0.0
    - - 1.0
      - -0.0
      - 0.0
    UB:
    - - -0.0
      - -0.0
      - 1.0
    - - 0.0
      - -1.0
      - 0.0
    - - 1.0
      - -0.0
      - -0.0
    digits: 3
constraints:
  omega:
    label: omega
    low_limit: -180.0
    high_limit: 180.0
    class: LimitsConstraint
  chi:
    label: chi
    low_limit: -180.0
    high_limit: 180.0
    class: LimitsConstraint
  phi:
    label: phi
    low_limit: -180.0
    high_limit: 180.0
    class: LimitsConstraint
  tth:
    label: tth
    low_limit: -180.0
    high_limit: 180.0
    class: LimitsConstraint
solver:
  name: hkl_soleil
  description: HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl',
    mode='bissector')
  geometry: E4CV
  real_axes:
  - omega
  - chi
  - phi
  - tth
  version: 5.1.2
  engine: hkl

Restore (load) Configuration#

To demonstrate restore, let’s start again with an unconfigured diffractometer. We’ll just recreate the sim4c object. There will only be the default sample.

[6]:
sim4c = hklpy2.creator(name="sim4c")
sim4c.samples
[6]:
{'sample': Sample(name='sample', lattice=Lattice(a=1, system='cubic'))}

Restore the configuration from the dev_e4cv-vibranium.yml file.

[7]:
sim4c.operator.restore("dev_e4cv-vibranium.yml", clear=True)

Show the samples again:

[8]:
sim4c.samples
[8]:
{'sample': Sample(name='sample', lattice=Lattice(a=1, system='cubic')),
 'vibranium': Sample(name='vibranium', lattice=Lattice(a=6.283, system='cubic'))}

Show the full configuration report again:

[9]:
sim4c.configuration
[9]:
{'_header': {'datetime': '2025-02-18 15:22:12.834557',
  'energy_units': 'keV',
  'energy': 12.398419843856837,
  'hklpy2_version': '0.0.26.dev46+gcbd9032.d20250218',
  'python_class': 'Hklpy2Diffractometer',
  'source_type': 'X-ray',
  'wavelength_units': 'angstrom',
  'wavelength': 1.0},
 'name': 'sim4c',
 'axes': {'pseudo_axes': ['h', 'k', 'l'],
  'real_axes': ['omega', 'chi', 'phi', 'tth'],
  'axes_xref': {'h': 'h',
   'k': 'k',
   'l': 'l',
   'omega': 'omega',
   'chi': 'chi',
   'phi': 'phi',
   'tth': 'tth'},
  'extra_axes': {}},
 'sample_name': 'vibranium',
 'samples': {'sample': {'name': 'sample',
   'lattice': {'a': 1,
    'b': 1,
    'c': 1,
    'alpha': 90.0,
    'beta': 90.0,
    'gamma': 90.0},
   'reflections': {},
   'reflections_order': [],
   'U': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
   'UB': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
   'digits': 4},
  'vibranium': {'name': 'vibranium',
   'lattice': {'a': 6.283185307179586,
    'b': 6.283185307179586,
    'c': 6.283185307179586,
    'alpha': 90.0,
    'beta': 90.0,
    'gamma': 90.0},
   'reflections': {'r1': {'name': 'r1',
     'geometry': 1.0,
     'pseudos': {'h': 1, 'k': 0, 'l': 0},
     'reals': {'omega': 10, 'chi': 0, 'phi': 0, 'tth': 20},
     'wavelength': 1.0,
     'digits': 4},
    'r2': {'name': 'r2',
     'geometry': 1.0,
     'pseudos': {'h': 0, 'k': 1, 'l': 0},
     'reals': {'omega': 10, 'chi': -90, 'phi': 0, 'tth': 20},
     'wavelength': 1.0,
     'digits': 4}},
   'reflections_order': ['r1', 'r2'],
   'U': [[-0.0, -0.0, 1.0], [0.0, -1.0, -0.0], [1.0, -0.0, 0.0]],
   'UB': [[-0.0, -0.0, 1.0], [0.0, -1.0, 0.0], [1.0, -0.0, -0.0]],
   'digits': 3}},
 'constraints': {'omega': {'label': 'omega',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'chi': {'label': 'chi',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'phi': {'label': 'phi',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'tth': {'label': 'tth',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'}},
 'solver': {'name': 'hkl_soleil',
  'description': "HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl', mode='bissector')",
  'geometry': 'E4CV',
  'real_axes': ['omega', 'chi', 'phi', 'tth'],
  'version': '5.1.2',
  'engine': 'hkl'}}

Run#

The configuration of one or more diffractometers can be saved to the metadata of each run. To save the configuration(s), an instance of the ConfigurationRunWrapper preprocessor is added to the RE.

This example uses one diffractometer, an Eulerian 4-circle named sim4c.

First, create a simulated 4-circle diffractometer. We’ll load the orientation from the dev_e4cv-vibranium.yml file we created above.

[10]:
import hklpy2

sim4c = hklpy2.creator(name="sim4c")
sim4c.operator.restore("dev_e4cv-vibranium.yml", clear=True)

Setup bluesky to run standard plans with the RunEngine. Also get noisy_det from the ophyd simulated detectors. To restore a diffractometer configuration from a previous run, we’ll need a catalog (cat) of runs.

[11]:
from bluesky import RunEngine, plans as bp
from ophyd.sim import noisy_det
import databroker

RE = RunEngine()
cat = databroker.temp().v2
RE.subscribe(cat.v1.insert)
[11]:
0

Save configuration with every run#

Create an instance of the ConfigurationRunWrapper, adding the sim4c diffractometer.

[12]:
from hklpy2 import ConfigurationRunWrapper

crw = ConfigurationRunWrapper(sim4c)

Add the crw instance to the list of preprocessors that are run by the RE. Each one examines the sequence of Msg objects internal to the RunEngine. The crw.wrapper method will add the sim4c diffractometer’s configuration details to every run’s metadata. (Later, we’ll show how to read and restore from that metadata.)

This configures RE to save sim4c configuration with every run.

[13]:
RE.preprocessors.append(crw.wrapper)

Let’s run a count plan using the noisy_det detector. Keep track of the run’s uid, we’ll use that later.

[14]:
uids = RE(bp.count([noisy_det]))

The sim4c configuration is saved as a dictionary with the other metadata under the "diffractometers" key. This is a dictionary, with a key for each diffractometer name to be reported. Let’s view the "diffractometers" dictionary. (Compare with similar views shown above.)

[15]:
run = cat.v2[uids[0]]
run.metadata["start"]["diffractometers"]
[15]:
{'sim4c': {'_header': {'datetime': '2025-02-18 15:22:13.941402',
   'energy_units': 'keV',
   'energy': 12.398419843856837,
   'hklpy2_version': '0.0.26.dev46+gcbd9032.d20250218',
   'python_class': 'Hklpy2Diffractometer',
   'source_type': 'X-ray',
   'wavelength_units': 'angstrom',
   'wavelength': 1.0},
  'name': 'sim4c',
  'axes': {'pseudo_axes': ['h', 'k', 'l'],
   'real_axes': ['omega', 'chi', 'phi', 'tth'],
   'axes_xref': {'h': 'h',
    'k': 'k',
    'l': 'l',
    'omega': 'omega',
    'chi': 'chi',
    'phi': 'phi',
    'tth': 'tth'},
   'extra_axes': {}},
  'sample_name': 'vibranium',
  'samples': {'sample': {'name': 'sample',
    'lattice': {'a': 1,
     'b': 1,
     'c': 1,
     'alpha': 90.0,
     'beta': 90.0,
     'gamma': 90.0},
    'reflections': {},
    'reflections_order': [],
    'U': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
    'UB': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
    'digits': 4},
   'vibranium': {'name': 'vibranium',
    'lattice': {'a': 6.283185307179586,
     'b': 6.283185307179586,
     'c': 6.283185307179586,
     'alpha': 90.0,
     'beta': 90.0,
     'gamma': 90.0},
    'reflections': {'r1': {'name': 'r1',
      'geometry': 1.0,
      'pseudos': {'h': 1, 'k': 0, 'l': 0},
      'reals': {'omega': 10, 'chi': 0, 'phi': 0, 'tth': 20},
      'wavelength': 1.0,
      'digits': 4},
     'r2': {'name': 'r2',
      'geometry': 1.0,
      'pseudos': {'h': 0, 'k': 1, 'l': 0},
      'reals': {'omega': 10, 'chi': -90, 'phi': 0, 'tth': 20},
      'wavelength': 1.0,
      'digits': 4}},
    'reflections_order': ['r1', 'r2'],
    'U': [[-0.0, -0.0, 1.0], [0.0, -1.0, -0.0], [1.0, -0.0, 0.0]],
    'UB': [[-0.0, -0.0, 1.0], [0.0, -1.0, 0.0], [1.0, -0.0, -0.0]],
    'digits': 3}},
  'constraints': {'omega': {'label': 'omega',
    'low_limit': -180.0,
    'high_limit': 180.0,
    'class': 'LimitsConstraint'},
   'chi': {'label': 'chi',
    'low_limit': -180.0,
    'high_limit': 180.0,
    'class': 'LimitsConstraint'},
   'phi': {'label': 'phi',
    'low_limit': -180.0,
    'high_limit': 180.0,
    'class': 'LimitsConstraint'},
   'tth': {'label': 'tth',
    'low_limit': -180.0,
    'high_limit': 180.0,
    'class': 'LimitsConstraint'}},
  'solver': {'name': 'hkl_soleil',
   'description': "HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl', mode='bissector')",
   'geometry': 'E4CV',
   'real_axes': ['omega', 'chi', 'phi', 'tth'],
   'version': '5.1.2',
   'engine': 'hkl'}}}

Restore from previous run#

To restore from a previous run, we must have a reference to the run (with a uid, scan_id, run object, …). In the steps below, we’ll use the run object from the previous steps.

[16]:
run
[16]:
BlueskyRun
  uid='c600dfdc-2e74-4776-a93c-ecc1e28e348f'
  exit_status='success'
  2025-02-18 15:22:13.944 -- 2025-02-18 15:22:13.969
  Streams:
    * primary

Get the configuration of the "sim4c" diffractometer.

[17]:
e4cv_configuration = run.metadata["start"]["diffractometers"]["sim4c"]

Let’s recreate sim4c to prove when we have recovered the configuration.

Note: This example uses the same name as before but that is not necessary. The configuration can be restored as long as the solver and geometry match.

[18]:
sim4c = hklpy2.creator(name="sim4c")
sim4c.samples
[18]:
{'sample': Sample(name='sample', lattice=Lattice(a=1, system='cubic'))}

Restore the configuration (from a previous run) to the sim4c diffractometer. Confirm by examining the sample dictionary.

[19]:
e4cv_configuration
[19]:
{'_header': {'datetime': '2025-02-18 15:22:13.941402',
  'energy_units': 'keV',
  'energy': 12.398419843856837,
  'hklpy2_version': '0.0.26.dev46+gcbd9032.d20250218',
  'python_class': 'Hklpy2Diffractometer',
  'source_type': 'X-ray',
  'wavelength_units': 'angstrom',
  'wavelength': 1.0},
 'name': 'sim4c',
 'axes': {'pseudo_axes': ['h', 'k', 'l'],
  'real_axes': ['omega', 'chi', 'phi', 'tth'],
  'axes_xref': {'h': 'h',
   'k': 'k',
   'l': 'l',
   'omega': 'omega',
   'chi': 'chi',
   'phi': 'phi',
   'tth': 'tth'},
  'extra_axes': {}},
 'sample_name': 'vibranium',
 'samples': {'sample': {'name': 'sample',
   'lattice': {'a': 1,
    'b': 1,
    'c': 1,
    'alpha': 90.0,
    'beta': 90.0,
    'gamma': 90.0},
   'reflections': {},
   'reflections_order': [],
   'U': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
   'UB': [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
   'digits': 4},
  'vibranium': {'name': 'vibranium',
   'lattice': {'a': 6.283185307179586,
    'b': 6.283185307179586,
    'c': 6.283185307179586,
    'alpha': 90.0,
    'beta': 90.0,
    'gamma': 90.0},
   'reflections': {'r1': {'name': 'r1',
     'geometry': 1.0,
     'pseudos': {'h': 1, 'k': 0, 'l': 0},
     'reals': {'omega': 10, 'chi': 0, 'phi': 0, 'tth': 20},
     'wavelength': 1.0,
     'digits': 4},
    'r2': {'name': 'r2',
     'geometry': 1.0,
     'pseudos': {'h': 0, 'k': 1, 'l': 0},
     'reals': {'omega': 10, 'chi': -90, 'phi': 0, 'tth': 20},
     'wavelength': 1.0,
     'digits': 4}},
   'reflections_order': ['r1', 'r2'],
   'U': [[-0.0, -0.0, 1.0], [0.0, -1.0, -0.0], [1.0, -0.0, 0.0]],
   'UB': [[-0.0, -0.0, 1.0], [0.0, -1.0, 0.0], [1.0, -0.0, -0.0]],
   'digits': 3}},
 'constraints': {'omega': {'label': 'omega',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'chi': {'label': 'chi',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'phi': {'label': 'phi',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'},
  'tth': {'label': 'tth',
   'low_limit': -180.0,
   'high_limit': 180.0,
   'class': 'LimitsConstraint'}},
 'solver': {'name': 'hkl_soleil',
  'description': "HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl', mode='bissector')",
  'geometry': 'E4CV',
  'real_axes': ['omega', 'chi', 'phi', 'tth'],
  'version': '5.1.2',
  'engine': 'hkl'}}
[20]:
sim4c.configuration = e4cv_configuration
sim4c.samples
[20]:
{'sample': Sample(name='sample', lattice=Lattice(a=1, system='cubic')),
 'vibranium': Sample(name='vibranium', lattice=Lattice(a=6.283, system='cubic'))}