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'))}