How to Perform a Zone Scan#
A zone is a set of crystal lattice planes all parallel to a single line — the zone axis. Scanning along a zone axis constrains the diffractometer to a crystallographic plane in reciprocal space, which is a common technique for measuring diffuse scattering, structured diffuse scattering, or mapping regions of reciprocal space systematically.
See also
hklpy2.blocks.zone — API reference for all zone functions.
Zone Scan — worked demonstration notebook.
SPEC commands in hklpy2 — SPEC equivalents: cz, mz, pl, sz.
Prerequisites#
A diffractometer object with a sample, lattice, and computed UB matrix. See Tutorial: Your First Diffractometer and How to Compute and Set the UB Matrix.
A running bluesky
RunEngine(RE).At least one detector (or use
ophyd.sim.noisy_detfor testing).
The examples below use a simulated E4CV diffractometer with the Vibranium sample from the tutorial. Substitute your own diffractometer, sample, and hkl positions as needed.
import hklpy2
from ophyd.sim import noisy_det
import bluesky
import bluesky.plan_stubs as bps
RE = bluesky.RunEngine()
fourc = hklpy2.creator(name="fourc")
# ... define sample, add reflections, compute UB matrix ...
How to Move to a Position in the Zone#
Use move_zone() to move the diffractometer to
a single reciprocal-space position. This is the hklpy2 equivalent of
the SPEC mz command.
from hklpy2 import move_zone
# Move to (1, 0, 0)
RE(move_zone(fourc, (1, 0, 0)))
move_zone() calls
forward() internally and moves
all real axes to the computed positions.
How to Scan Along a Zone#
Use scan_zone() to scan from one reciprocal-space
position to another along the zone defined by their cross product. This is
the hklpy2 equivalent of the SPEC scanzone command.
from hklpy2 import scan_zone
# Scan from (1,0,0) to (0,1,0) in 11 steps, recording noisy_det
(uid,) = RE(scan_zone([noisy_det], fourc, (1, 0, 0), (0, 1, 0), 11))
The start and finish vectors define the zone axis implicitly via
their cross product. All intermediate points lie in the same zone.
Points where forward() finds no
valid solution are logged at DEBUG level and skipped — the run
continues with the remaining points.
Note
The num argument counts the total number of points including both
endpoints. Pass num=11 for 11 points (9 intermediate + 2 endpoints).
How to Inspect Zone Positions Before Scanning#
Use zone_series() to print a table of pseudos
and reals along the zone without running a scan. This is useful for
verifying that all positions are reachable before committing to a scan.
from hklpy2.blocks.zone import zone_series
zone_series(fourc, (1, 0, 0), (0, 1, 0), 11)
Example output (columns depend on your geometry’s axis names):
hkl_1=(1, 0, 0) hkl_2=(0, 1, 0) n=11
======= ======= ======= ========= ======= ======= =========
h k l omega chi phi tth
======= ======= ======= ========= ======= ======= =========
1.0000 0.0000 0.0000 ... ... ... ...
...
======= ======= ======= ========= ======= ======= =========
How to Define a Zone Axis Explicitly#
OrthonormalZone can be used directly when you
want to define the zone axis explicitly or inspect zone membership before
scanning.
From two vectors (cross product):
from hklpy2.blocks.zone import OrthonormalZone
# Zone axis = (1,0,0) × (0,1,0) = (0,0,1) — the [001] zone
zone = OrthonormalZone(b1=(1, 0, 0), b2=(0, 1, 0))
print(zone.axis) # array([0., 0., 1.])
Directly (SPEC sz equivalent):
zone = OrthonormalZone(axis=(0, 0, 1))
Check whether a vector is in the zone:
zone.in_zone((1, 1, 0)) # True — (110) is in the [001] zone
zone.in_zone((0, 0, 1)) # False — (001) is the zone axis itself
Common Pitfalls#
No forward solutions — if one or more points along the zone cannot be reached (constraints, geometry limits, or wavelength), those points are silently skipped by
scan_zone(). Usezone_series()first to verify coverage, and check constraints withforward()before running the scan.Parallel vectors — passing two parallel vectors as
b1andb2(e.g.(1,0,0)and(2,0,0)) raises aValueErrorbecause their cross product is zero and no zone axis can be defined.UB matrix not set — zone calculations use the sample’s UB matrix to transform between reciprocal space and real motor positions. Always compute the UB matrix before scanning. See How to Compute and Set the UB Matrix.