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

zone in the Glossary.

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#

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(). Use zone_series() first to verify coverage, and check constraints with forward() before running the scan.

  • Parallel vectors — passing two parallel vectors as b1 and b2 (e.g. (1,0,0) and (2,0,0)) raises a ValueError because 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.