Save and Restore a Diffractometer Configuration#
This guide shows how to save the complete state of a diffractometer to a file and restore it later. The state includes the geometry, wavelength, sample lattice, orienting reflections, UB matrix, and all other parameters.
What is serialized#
to_dict() captures the full diffractometer state:
Geometry name, description, and basis
Wavelength, source type
All motor stage names and angles
Active diffraction mode and cut points
All samples: lattice parameters, reflections (with angles and wavelength), orienting reflection designations (or0/or1), and UB matrix
Azimuthal reference, surface normal, inclination matrix
Detector geometry parameters (distance, tilt, offset)
The resulting dict contains only JSON-compatible types (str, float, int, list, dict, None) — it can be round-tripped through JSON or YAML without loss.
Basic round-trip#
import ad_hoc_diffractometer as ahd
# Set up a geometry
g = ahd.presets.fourcv()
g.wavelength = 1.5406 # Å (Cu Kα)
g.sample.lattice = ahd.Lattice(a=5.431) # cubic silicon
ahd.ub_identity(g.sample)
g.sample.reflections.add(
"r1",
hkl=(0, 0, 4),
angles={"omega": 34.56, "chi": 90.0, "phi": 0.0, "ttheta": 69.13},
)
g.sample.reflections.setor0("r1")
# Serialize to a dict
d = g.to_dict()
# Restore from the dict
g2 = ahd.AdHocDiffractometer.from_dict(d)
print(g2.name) # fourcv
print(g2.wavelength) # 1.5406
print(g2.sample.lattice.a) # 5.431
Save to a JSON file#
JSON is the simplest option — it requires only the Python standard library:
import json
# Save
with open("diffractometer.json", "w") as f:
json.dump(g.to_dict(), f, indent=2)
# Restore
with open("diffractometer.json") as f:
g2 = ahd.AdHocDiffractometer.from_dict(json.load(f))
Save to a YAML file#
YAML produces a more human-readable file. It requires pyyaml
(not installed automatically — run pip install pyyaml):
try:
import yaml
except ImportError:
raise ImportError("Install pyyaml: pip install pyyaml")
# Save
with open("diffractometer.yaml", "w") as f:
yaml.dump(g.to_dict(), f, default_flow_style=False, sort_keys=False)
# Restore
with open("diffractometer.yaml") as f:
g2 = ahd.AdHocDiffractometer.from_dict(yaml.safe_load(f))
Verify a round-trip#
Check that the restored geometry matches the original:
import numpy as np
assert g2.name == g.name
assert g2.wavelength == g.wavelength
assert np.isclose(g2.sample.lattice.a, g.sample.lattice.a)
assert list(g2.sample.reflections.keys()) == list(g.sample.reflections.keys())
assert np.allclose(g2.sample.UB, g.sample.UB)
print("Round-trip verified.")
Serialize individual objects#
All major classes support to_dict() / from_dict() independently:
from ad_hoc_diffractometer import Lattice, Sample
# Lattice
lat_dict = g.sample.lattice.to_dict()
lat2 = Lattice.from_dict(lat_dict)
# Sample (including reflections and UB)
sample_dict = g.sample.to_dict()
# sample2 = Sample.from_dict(sample_dict, parent=g) # requires parent geometry
ConstraintSet round-trip#
Diffraction modes (ConstraintSet) are
serialised as part of the geometry dict. They can also be inspected and
round-tripped independently:
import json
from ad_hoc_diffractometer import ConstraintSet, BisectConstraint
from ad_hoc_diffractometer import SampleConstraint, DetectorConstraint
from ad_hoc_diffractometer import REQUIRED
# A custom psic mode
cs = ConstraintSet(
[
BisectConstraint("eta", "delta"),
SampleConstraint("mu", 0.0),
DetectorConstraint("nu", 0.0),
],
computed=["eta", "chi", "phi", "delta"],
)
# Serialise
d = cs.to_dict()
print(json.dumps(d, indent=2))
# Restore
cs2 = ConstraintSet.from_dict(d)
assert cs2 == cs
# REQUIRED / OPTIONAL sentinels survive the round-trip
cs_extras = ConstraintSet(
[BisectConstraint("eta", "delta"),
SampleConstraint("mu", 0.0),
SampleConstraint("chi", 0.0)],
extras={"n_hat": REQUIRED, "psi": None},
)
d2 = cs_extras.to_dict()
cs_extras2 = ConstraintSet.from_dict(d2)
from ad_hoc_diffractometer import REQUIRED as REQ
assert cs_extras2.extras["n_hat"] is REQ # sentinel restored
The geometry-level round-trip preserves all modes, the active mode name, and cut points:
import ad_hoc_diffractometer as ahd
g = ahd.presets.psic()
g.mode_name = "bisecting_vertical"
d = g.to_dict()
g2 = ahd.AdHocDiffractometer.from_dict(d)
assert set(g2.modes.keys()) == set(g.modes.keys())
assert g2.mode_name == "bisecting_vertical"
Typical workflow#
A common pattern is to save the configuration after alignment and restore it at the start of the next session:
import json
import ad_hoc_diffractometer as ahd
SESSION_FILE = "session_config.json"
def save_session(geometry):
with open(SESSION_FILE, "w") as f:
json.dump(geometry.to_dict(), f, indent=2)
print(f"Session saved to {SESSION_FILE}")
def restore_session():
with open(SESSION_FILE) as f:
return ahd.AdHocDiffractometer.from_dict(json.load(f))
# End of session: save
g = ahd.presets.fourcv()
# ... do alignment ...
save_session(g)
# Start of next session: restore
g = restore_session()
ahd.pa(g) # verify the restored configuration