Sample orientation: export & restore diffractometer configuration#
A common user request is to preserve the orientation (UB) matrix and related terms used to compute it. Terms such as the list of orientation reflections, samples, and diffractometer geometry from their current session. The configuration should be saved somewhere so it is available, even after bluesky stops. Multiple sessions could (optionally) access the same configuration or use a different one.
It is a local choice how and where to save this configuration. The first step is to define a common structure to be exported and restored by hklpy. The structure would be stored in any of a variety of possibilities (some of these are provided by hklpy, the others are suggestions).
text file: stored in:
the local file directory
some other directory
network storage
NeXus HDF5 file: stored in the
pwd
or elsewhereEPICS Channel Access string waveform record
EPICS pvAccess structure
in-memory cache, such as:
database, such as:
Python object: (such as used in this documentation)
dictionary
JSON string
YAML string
A session could restore the configuration automatically from the chosen storage. Later, the session would export the configuration both automatically, and to a different location at the choice of the user.
Objective
Show how to export diffractometer configuration to a file and later restore it. Demonstrate with a 4-circle diffractometer (E4CV geometry).
Setup a (simulated) 4-circle diffractometer#
Load the support for a 4-circle diffractometer using the E4CV geometry. Since this is a simulation, we do not need an EPICS system with motors. The SimulatedE4CV structure (a Python class) provides all that is needed.
[1]:
from hkl import SimulatedE4CV
sim4c = SimulatedE4CV("", name="sim4c")
Print all the details about this sim4c
diffractometer we just created. Since this is a default configuration, no orientation reflections have been reported. The UB matrix is the default \((2\pi/1.54)\)U where U is the identity matrix and 1.54 is the unit cell length. The geometry defines the names of the rotational axes omega
, chi
, phi
, and tth
. This is the canonical order. The constraints are applied when computing values for the rotational axes from
reciprocal space coordinates.
[2]:
sim4c.pa()
===================== ====================================================================
term value
===================== ====================================================================
diffractometer sim4c
geometry E4CV
class SimulatedE4CV
energy (keV) 8.05092
wavelength (angstrom) 1.54000
calc engine hkl
mode bissector
positions ===== =======
name value
===== =======
omega 0.00000
chi 0.00000
phi 0.00000
tth 0.00000
===== =======
constraints ===== ========= ========== ===== ====
axis low_limit high_limit value fit
===== ========= ========== ===== ====
omega -180.0 180.0 0.0 True
chi -180.0 180.0 0.0 True
phi -180.0 180.0 0.0 True
tth -180.0 180.0 0.0 True
===== ========= ========== ===== ====
sample: main ================ ===================================================
term value
================ ===================================================
unit cell edges a=1.54, b=1.54, c=1.54
unit cell angles alpha=90.0, beta=90.0, gamma=90.0
[U] [[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
[UB] [[ 4.07999046e+00 -2.49827363e-16 -2.49827363e-16]
[ 0.00000000e+00 4.07999046e+00 -2.49827363e-16]
[ 0.00000000e+00 0.00000000e+00 4.07999046e+00]]
================ ===================================================
===================== ====================================================================
[2]:
<pyRestTable.rest_table.Table at 0x7f3b75440750>
What is described in a diffractometer configuration?#
The hkl.DiffractometerConfiguration
structure is used for export and restore. To show what an export contains (for this default diffractometer), we make a Python object specifically for the sim4c
diffractometer. In this example, the object is called agent
(you could pick some other name, such as config
).
Create agent
specifically for the sim4c
diffractometer to provide the methods in the next table.
Python |
description |
---|---|
|
describe a diffractometer’s configuration |
|
preview a saved configuration |
|
set a diffractometer’s configuration |
[3]:
from hkl import DiffractometerConfiguration
agent = DiffractometerConfiguration(sim4c)
Export#
export()
returns the current configuration of the diffractometer in any of several formats:
JSON string (default)
YAML string
Python dictionary
file (as defined by a Python pathlib object)
Note: Internally, the code uses the Python dictionary for the actual export and restore operations. The other formats derive from the Python dictionary. All information in the Python dictionary is first validated before it is restored to the diffractometer.
Export: JSON string (default) format#
Describe sim4c
using agent.export()
. The information content is similar to sim4c.pa()
, but in a format convenient for both saving (to a file or other structure) and for restoring.
The default format is a JSON string, formatted with line endings and indentation. (Alternatives include Python dictionary, YAML, and file.)
[4]:
print(agent.export())
{
"geometry": "E4CV",
"engine": "hkl",
"library": "gi.repository.Hkl",
"mode": "bissector",
"canonical_axes": [
"omega",
"chi",
"phi",
"tth"
],
"real_axes": [
"omega",
"chi",
"phi",
"tth"
],
"reciprocal_axes": [
"h",
"k",
"l"
],
"constraints": {
"omega": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
},
"chi": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
},
"phi": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
},
"tth": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
}
},
"samples": {
"main": {
"name": "main",
"lattice": {
"a": 1.54,
"b": 1.54,
"c": 1.54,
"alpha": 90.0,
"beta": 90.0,
"gamma": 90.0
},
"reflections": [],
"UB": [
[
4.079990459207523,
-2.4982736282101165e-16,
-2.4982736282101165e-16
],
[
0.0,
4.079990459207523,
-2.4982736282101165e-16
],
[
0.0,
0.0,
4.079990459207523
]
],
"U": [
[
1.0,
0.0,
0.0
],
[
0.0,
1.0,
0.0
],
[
0.0,
0.0,
1.0
]
]
}
},
"name": "sim4c",
"datetime": "2023-11-16 12:48:44.500249",
"wavelength_angstrom": 1.54,
"energy_keV": 8.050921974025975,
"hklpy_version": "1.0.5.dev207+g955ab4d",
"library_version": "v5.0.0.3001",
"python_class": "SimulatedE4CV",
"other": {}
}
Export: YAML string format#
View the same content in YAML format:
[5]:
print(agent.export("yaml"))
geometry: E4CV
engine: hkl
library: gi.repository.Hkl
mode: bissector
canonical_axes:
- omega
- chi
- phi
- tth
real_axes:
- omega
- chi
- phi
- tth
reciprocal_axes:
- h
- k
- l
constraints:
omega:
low_limit: -180.0
high_limit: 180.0
value: 0.0
fit: true
chi:
low_limit: -180.0
high_limit: 180.0
value: 0.0
fit: true
phi:
low_limit: -180.0
high_limit: 180.0
value: 0.0
fit: true
tth:
low_limit: -180.0
high_limit: 180.0
value: 0.0
fit: true
samples:
main:
name: main
lattice:
a: 1.54
b: 1.54
c: 1.54
alpha: 90.0
beta: 90.0
gamma: 90.0
reflections: []
UB:
- - 4.079990459207523
- -2.4982736282101165e-16
- -2.4982736282101165e-16
- - 0.0
- 4.079990459207523
- -2.4982736282101165e-16
- - 0.0
- 0.0
- 4.079990459207523
U:
- - 1.0
- 0.0
- 0.0
- - 0.0
- 1.0
- 0.0
- - 0.0
- 0.0
- 1.0
name: sim4c
datetime: '2023-11-16 12:48:44.571162'
wavelength_angstrom: 1.54
energy_keV: 8.050921974025975
hklpy_version: 1.0.5.dev207+g955ab4d
library_version: v5.0.0.3001
python_class: SimulatedE4CV
other: {}
Export: Python dictionary format#
Or as Python dictionary:
[6]:
agent.export("dict")
[6]:
{'geometry': 'E4CV',
'engine': 'hkl',
'library': 'gi.repository.Hkl',
'mode': 'bissector',
'canonical_axes': ['omega', 'chi', 'phi', 'tth'],
'real_axes': ['omega', 'chi', 'phi', 'tth'],
'reciprocal_axes': ['h', 'k', 'l'],
'constraints': {'omega': {'low_limit': -180.0,
'high_limit': 180.0,
'value': 0.0,
'fit': True},
'chi': {'low_limit': -180.0, 'high_limit': 180.0, 'value': 0.0, 'fit': True},
'phi': {'low_limit': -180.0, 'high_limit': 180.0, 'value': 0.0, 'fit': True},
'tth': {'low_limit': -180.0,
'high_limit': 180.0,
'value': 0.0,
'fit': True}},
'samples': {'main': {'name': 'main',
'lattice': {'a': 1.54,
'b': 1.54,
'c': 1.54,
'alpha': 90.0,
'beta': 90.0,
'gamma': 90.0},
'reflections': [],
'UB': [[4.079990459207523,
-2.4982736282101165e-16,
-2.4982736282101165e-16],
[0.0, 4.079990459207523, -2.4982736282101165e-16],
[0.0, 0.0, 4.079990459207523]],
'U': [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]}},
'name': 'sim4c',
'datetime': '2023-11-16 12:48:44.578586',
'wavelength_angstrom': 1.54,
'energy_keV': 8.050921974025975,
'hklpy_version': '1.0.5.dev207+g955ab4d',
'library_version': 'v5.0.0.3001',
'python_class': 'SimulatedE4CV',
'other': {}}
Export: Data file (text)#
Or as a data file. To save as a data file, we need the name (and directory path) of that file. Could be a local file such as sim4c-config.json
or a file in some other directory such as ~/.config/sim4c/autosave.json
. These are just ideas.
In any case, we describe the file name and path using Python’s pathlib support package.
For this example, we’ll use files created just for this notebook in a temporary directory using Python’s tempfile package. The entire directory will be deleted when the notebook exits. (The iterdir()
method prints the directory’s contents. It’s empty at the start.)
[7]:
import pathlib
import tempfile
temp_dir = tempfile.TemporaryDirectory()
config_path = pathlib.Path(temp_dir.name)
print(f"{config_path=}")
print(f"{list(config_path.iterdir())=}")
config_path=PosixPath('/tmp/tmpvrwz9f7z')
list(config_path.iterdir())=[]
Export the configuration to file sim4c.json
in the temporary directory. Show the file exists.
[8]:
config_file = config_path / "sim4c.json"
agent.export(config_file)
print(f"{list(config_path.iterdir())=}")
list(config_path.iterdir())=[PosixPath('/tmp/tmpvrwz9f7z/sim4c.json')]
Show the contents of the new configuration file.
[9]:
with open(config_file) as f:
print(f.read())
{
"geometry": "E4CV",
"engine": "hkl",
"library": "gi.repository.Hkl",
"mode": "bissector",
"canonical_axes": [
"omega",
"chi",
"phi",
"tth"
],
"real_axes": [
"omega",
"chi",
"phi",
"tth"
],
"reciprocal_axes": [
"h",
"k",
"l"
],
"constraints": {
"omega": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
},
"chi": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
},
"phi": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
},
"tth": {
"low_limit": -180.0,
"high_limit": 180.0,
"value": 0.0,
"fit": true
}
},
"samples": {
"main": {
"name": "main",
"lattice": {
"a": 1.54,
"b": 1.54,
"c": 1.54,
"alpha": 90.0,
"beta": 90.0,
"gamma": 90.0
},
"reflections": [],
"UB": [
[
4.079990459207523,
-2.4982736282101165e-16,
-2.4982736282101165e-16
],
[
0.0,
4.079990459207523,
-2.4982736282101165e-16
],
[
0.0,
0.0,
4.079990459207523
]
],
"U": [
[
1.0,
0.0,
0.0
],
[
0.0,
1.0,
0.0
],
[
0.0,
0.0,
1.0
]
]
}
},
"name": "sim4c",
"datetime": "2023-11-16 12:48:44.591289",
"wavelength_angstrom": 1.54,
"energy_keV": 8.050921974025975,
"hklpy_version": "1.0.5.dev207+g955ab4d",
"library_version": "v5.0.0.3001",
"python_class": "SimulatedE4CV",
"other": {}
}
Preview & Restore#
To demonstrate agent.preview()
and agent.restore()
, let’s compare the file we just wrote with a file found on the internet. The hklpy repository on GitHub has a compatible (same geometry, canonical motor names, and back-end computation library) file used for testing the hklpy source code.
We’ll use the requests package for the download. With requests, we can keep the structure in memory. (There is no need to save it to a local file.)
[10]:
import requests
r = requests.get(
"https://raw.githubusercontent.com/bluesky/hklpy/main/hkl/tests/data/e4c-config.json"
)
test_e4c_config = r.text
print(f"{test_e4c_config=}")
test_e4c_config='{\n "geometry": "E4CV",\n "engine": "hkl",\n "library": "gi.repository.Hkl",\n "mode": "bissector",\n "canonical_axes": [\n "omega",\n "chi",\n "phi",\n "tth"\n ],\n "real_axes": [\n "omega",\n "chi",\n "phi",\n "tth"\n ],\n "reciprocal_axes": [\n "h",\n "k",\n "l"\n ],\n "constraints": {\n "omega": {\n "low_limit": -100.0,\n "high_limit": 100.0,\n "value": 0.0,\n "fit": true\n },\n "chi": {\n "low_limit": -100.0,\n "high_limit": 100.0,\n "value": 0.0,\n "fit": true\n },\n "phi": {\n "low_limit": -100.0,\n "high_limit": 100.0,\n "value": 0.0,\n "fit": true\n },\n "tth": {\n "low_limit": -100.0,\n "high_limit": 100.0,\n "value": 0.0,\n "fit": true\n }\n },\n "samples": {\n "main": {\n "name": "main",\n "lattice": {\n "a": 1.54,\n "b": 1.54,\n "c": 1.54,\n "alpha": 90.0,\n "beta": 90.0,\n "gamma": 90.0\n },\n "reflections": [],\n "UB": [\n [\n 4.079990459207523,\n -2.4982736282101165e-16,\n -2.4982736282101165e-16\n ],\n [\n 0.0,\n 4.079990459207523,\n -2.4982736282101165e-16\n ],\n [\n 0.0,\n 0.0,\n 4.079990459207523\n ]\n ],\n "U": [\n [\n 1.0,\n 0.0,\n 0.0\n ],\n [\n 0.0,\n 1.0,\n 0.0\n ],\n [\n 0.0,\n 0.0,\n 1.0\n ]\n ]\n },\n "vibranium": {\n "name": "vibranium",\n "lattice": {\n "a": 6.283185307179586,\n "b": 6.283185307179586,\n "c": 6.283185307179586,\n "alpha": 90.0,\n "beta": 90.0,\n "gamma": 90.0\n },\n "reflections": [\n {\n "reflection": {\n "h": 4.0,\n "k": 0.0,\n "l": 0.0\n },\n "position": {\n "omega": -145.451,\n "chi": 0.0,\n "phi": 0.0,\n "tth": 69.0966\n },\n "wavelength": 1.54,\n "orientation_reflection": false,\n "flag": 1\n },\n {\n "reflection": {\n "h": 0.0,\n "k": 4.0,\n "l": 0.0\n },\n "position": {\n "omega": -145.451,\n "chi": 0.0,\n "phi": 90.0,\n "tth": 69.0966\n },\n "wavelength": 1.54,\n "orientation_reflection": true,\n "flag": 1\n },\n {\n "reflection": {\n "h": 0.0,\n "k": 0.0,\n "l": 4.0\n },\n "position": {\n "omega": -145.451,\n "chi": 90.0,\n "phi": 0.0,\n "tth": 69.0966\n },\n "wavelength": 1.54,\n "orientation_reflection": true,\n "flag": 1\n }\n ],\n "UB": [\n [\n 1.2217304763701863e-05,\n -0.9999999999253688,\n -1.7623547209572502e-15\n ],\n [\n -1.4926257043558267e-10,\n -1.4349296274686127e-42,\n -1.0000000000000002\n ],\n [\n 0.9999999999253688,\n 1.221730476364063e-05,\n -1.492626316575311e-10\n ]\n ],\n "U": [\n [\n 1.2217304763701863e-05,\n -0.9999999999253688,\n -1.8235863128158895e-15\n ],\n [\n -1.4926257043558267e-10,\n -9.139696455822134e-27,\n -1.0000000000000002\n ],\n [\n 0.9999999999253688,\n 1.2217304763701863e-05,\n -1.4926257042444305e-10\n ]\n ]\n }\n },\n "name": "e4c",\n "datetime": "2023-11-01 16:40",\n "wavelength_angstrom": 1.54,\n "energy_keV": 8.050921974025975,\n "hklpy_version": "1.0.5.dev122+g473f61f",\n "library_version": "v5.0.0.3001",\n "python_class": "SimulatedE4CV",\n "other": {}\n}\n'
Preview: JSON string#
The agent.preview(structure)
method shows the sample information (name, crystal lattice parameters, and number of orientation reflections) from the configuration structure
object, without loading it into the diffractometer.
agent.preview()
works with these types of structure
:
JSON string
YAML string
Python dictionary
file (pathlib object)
Here, test_e4c_config
is recognized as a JSON string.
[11]:
print(type(test_e4c_config))
print(agent.preview(test_e4c_config))
<class 'str'>
name: e4c
date: 2023-11-01 16:40
geometry: E4CV
Table of Samples
= ========= ========= ========= ========= ===== ==== ===== =====
# sample a b c alpha beta gamma #refl
= ========= ========= ========= ========= ===== ==== ===== =====
1 main 1.54 1.54 1.54 90.0 90.0 90.0 0
2 vibranium 6.2831853 6.2831853 6.2831853 90.0 90.0 90.0 3
= ========= ========= ========= ========= ===== ==== ===== =====
See that test_e4c_config
has an additional sample: vibranium
(looks cubic with a lattice constant \(a_o=2\pi\) which is right since vibranium is fictional). Three orientation reflections are provided. Next, show the additional details.
[12]:
# options to show constraints and list of reflections
print(agent.preview(test_e4c_config, show_constraints=True, show_reflections=True))
name: e4c
date: 2023-11-01 16:40
geometry: E4CV
Table of Samples
= ========= ========= ========= ========= ===== ==== ===== =====
# sample a b c alpha beta gamma #refl
= ========= ========= ========= ========= ===== ==== ===== =====
1 main 1.54 1.54 1.54 90.0 90.0 90.0 0
2 vibranium 6.2831853 6.2831853 6.2831853 90.0 90.0 90.0 3
= ========= ========= ========= ========= ===== ==== ===== =====
Table of Reflections for Sample: vibranium
= === === === ======== ==== ==== ======= ========== =======
# 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
= === === === ======== ==== ==== ======= ========== =======
Table of Axis Constraints
===== ========= ========== ===== ====
axis low_limit high_limit value fit?
===== ========= ========== ===== ====
omega -100.0 100.0 0.0 True
chi -100.0 100.0 0.0 True
phi -100.0 100.0 0.0 True
tth -100.0 100.0 0.0 True
===== ========= ========== ===== ====
Note that the high and low limits in the constraints are now \(\pm100\). In the default, they were \(\pm180\).
Restore: JSON string#
Let’s restore the test_e4c_config
configuration to our sim4c
diffractometer. This is possible because we have identical matches of the diffractometer and the configuration:
name of geometry
name of computation engine
name of back-end support library
names and exact order of reciprocal-space axes
names and exact order of canonical real-space axes
(Other validation steps are applied to make sure that the data in the configuration is specified correctly.)
Once restore is complete, print all the diffractometer settings, as before using sim4c.pa()
.
Note: The agent.restore()
method does not move any axes of the diffractometer. It only changes configuration settings in Python memory.
[13]:
agent.restore(test_e4c_config)
sim4c.pa()
===================== ===============================================================================
term value
===================== ===============================================================================
diffractometer sim4c
geometry E4CV
class SimulatedE4CV
energy (keV) 8.05092
wavelength (angstrom) 1.54000
calc engine hkl
mode bissector
positions ===== =======
name value
===== =======
omega 0.00000
chi 0.00000
phi 0.00000
tth 0.00000
===== =======
constraints ===== ========= ========== ===== ====
axis low_limit high_limit value fit
===== ========= ========== ===== ====
omega -100.0 100.0 0.0 True
chi -100.0 100.0 0.0 True
phi -100.0 100.0 0.0 True
tth -100.0 100.0 0.0 True
===== ========= ========== ===== ====
sample: vibranium ================= =============================================================
term value
================= =============================================================
unit cell edges a=6.283185307179586, b=6.283185307179586, c=6.283185307179586
unit cell angles alpha=90.0, beta=90.0, gamma=90.0
ref 1 (hkl) h=4.0, k=0.0, l=0.0
ref 1 positioners omega=-145.45100, chi=0.00000, phi=0.00000, tth=69.09660
ref 2 (hkl) h=0.0, k=4.0, l=0.0
ref 2 positioners omega=-145.45100, chi=0.00000, phi=90.00000, tth=69.09660
ref 3 (hkl) h=0.0, k=0.0, l=4.0
ref 3 positioners omega=-145.45100, chi=90.00000, phi=0.00000, tth=69.09660
[U] [[ 1.22173048e-05 -1.00000000e+00 -1.82358631e-15]
[-1.49262570e-10 -9.13969646e-27 -1.00000000e+00]
[ 1.00000000e+00 1.22173048e-05 -1.49262570e-10]]
[UB] [[ 1.22173048e-05 -1.00000000e+00 -1.76235472e-15]
[-1.49262570e-10 -1.43492963e-42 -1.00000000e+00]
[ 1.00000000e+00 1.22173048e-05 -1.49262632e-10]]
================= =============================================================
===================== ===============================================================================
[13]:
<pyRestTable.rest_table.Table at 0x7f3b646bb910>
Observe the list of three vibranium sample reflections. Two of them, as noted, have been used to compute the UB matrix.
Restore: file#
We can swap back to the default settings by restoring from the temporary configuration file we created above. This resets the constraints and removes the vibranium sample.
[14]:
agent.restore(config_file)
sim4c.pa()
===================== ====================================================================
term value
===================== ====================================================================
diffractometer sim4c
geometry E4CV
class SimulatedE4CV
energy (keV) 8.05092
wavelength (angstrom) 1.54000
calc engine hkl
mode bissector
positions ===== =======
name value
===== =======
omega 0.00000
chi 0.00000
phi 0.00000
tth 0.00000
===== =======
constraints ===== ========= ========== ===== ====
axis low_limit high_limit value fit
===== ========= ========== ===== ====
omega -180.0 180.0 0.0 True
chi -180.0 180.0 0.0 True
phi -180.0 180.0 0.0 True
tth -180.0 180.0 0.0 True
===== ========= ========== ===== ====
sample: main ================ ===================================================
term value
================ ===================================================
unit cell edges a=1.54, b=1.54, c=1.54
unit cell angles alpha=90.0, beta=90.0, gamma=90.0
[U] [[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
[UB] [[ 4.07999046e+00 -2.49827363e-16 -2.49827363e-16]
[ 0.00000000e+00 4.07999046e+00 -2.49827363e-16]
[ 0.00000000e+00 0.00000000e+00 4.07999046e+00]]
================ ===================================================
===================== ====================================================================
[14]:
<pyRestTable.rest_table.Table at 0x7f3b74433390>