Quick Start#
This guide walks through the steps to build a four-circle diffractometer geometry by hand — without using the pre-built factory function. Building it step by step makes the design choices explicit and shows how every geometry in the package is constructed.
The concise factory-function equivalent appears at the end.
1. Choose a coordinate basis#
Any right-handed orthogonal mapping of the three physical directions (vertical, longitudinal, transverse) to Cartesian unit vectors (x, y, z) is a valid basis. The pre-built geometries use two common conventions, but you may supply any basis dict that satisfies those constraints:
Basis |
vertical |
longitudinal |
transverse |
Used by presets |
|---|---|---|---|---|
+x |
+y |
+z |
||
+z |
+y |
+x |
See Choose and Understand Basis Vectors for a full tutorial on choosing and understanding basis vectors.
For a standard four-circle diffractometer (Busing & Levy 1967) choose
BASIS_BL:
import numpy as np
import ad_hoc_diffractometer as ahd
BASIS = ahd.BASIS_BL
TRANSVERSE = BASIS["transverse"] # +x
LONGITUDINAL = BASIS["longitudinal"] # +y
VERTICAL = BASIS["vertical"] # +z
2. Define the stage stack#
A four-circle diffractometer has three sample stages and one detector stage.
Each Stage is described by:
name — motor name used in angle dictionaries and
forward()resultsaxis — rotation axis vector; a leading
−means left-handed rotationparent — the stage this one sits on (
None= directly on the floor)role —
"sample"or"detector"
The fourcv (vertical scattering plane, synchrotron) stack is:
stages = [
# Sample stack — base stage first
ahd.Stage("omega", -TRANSVERSE, parent=None, role="sample"),
ahd.Stage("chi", +LONGITUDINAL, parent="omega", role="sample"),
ahd.Stage("phi", -TRANSVERSE, parent="chi", role="sample"),
# Detector — independent of the sample stack
ahd.Stage("ttheta", -TRANSVERSE, parent=None, role="detector"),
]
omega and ttheta both rotate about the transverse axis, so their scattering
plane is vertical (the synchrotron convention). For the laboratory
(horizontal scattering plane) convention swap every TRANSVERSE for VERTICAL
and every LONGITUDINAL for TRANSVERSE — that is the fourch() geometry.
3. Define diffraction modes#
A ConstraintSet specifies which degrees of
freedom are constrained during a forward (hkl → motor angles) calculation.
Three constraint types cover most cases:
BisectConstraint— ties a sample stage to half the detector angle, placing the sample symmetrically in the beam.SampleConstraint— holds one sample stage at a fixed angle.DetectorConstraint— holds one detector stage at a fixed angle.
modes = {
"bisecting": ahd.ConstraintSet(
[ahd.BisectConstraint("omega", "ttheta")],
computed=["omega", "chi", "phi", "ttheta"],
),
"fixed_chi": ahd.ConstraintSet(
[ahd.SampleConstraint("chi", 90.0)],
computed=["omega", "phi", "ttheta"],
),
"fixed_phi": ahd.ConstraintSet(
[ahd.SampleConstraint("phi", 0.0)],
computed=["omega", "chi", "ttheta"],
),
}
4. Assemble the geometry#
Pass the stage list, basis, and modes to
AdHocDiffractometer:
g = ahd.AdHocDiffractometer(
name="my_fourcv",
stages=stages,
basis=BASIS,
description="Four-circle Eulerian, vertical scattering plane",
modes=modes,
default_mode="bisecting",
)
5. Set the wavelength and sample lattice#
g.wavelength = 1.5406 # Å (Cu Kα)
g.sample.lattice = ahd.Lattice(a=5.431) # cubic silicon
6. Inspect the geometry#
print(g.summary())
Example output:
Geometry: my_fourcv
Four-circle Eulerian, vertical scattering plane
Wavelength: 1.5406 Å Energy: 8.0478 keV
Mode: bisecting
Sample stages:
omega axis=-transverse angle= 0.000° limits=(-180.0, 180.0)
chi axis=+longitudinal angle= 0.000° limits=(-180.0, 180.0)
phi axis=-transverse angle= 0.000° limits=(-180.0, 180.0)
Detector stages:
ttheta axis=-transverse angle= 0.000° limits=(-180.0, 180.0)
7. Solve the forward problem#
Given a reflection (hkl), find the motor angles that satisfy Bragg’s law.
First orient the crystal (see Orient a Crystal), then call forward():
# Minimal orientation: identity U matrix (crystal axes || diffractometer axes)
ahd.ub_identity(g.sample)
# Solve for the (0, 0, 4) reflection
solutions = g.forward(0, 0, 4)
for sol in solutions:
print(sol)
Concise form — the factory function#
The code above is exactly what the built-in fourcv() factory does.
If you do not need to customise anything, use it directly:
import ad_hoc_diffractometer as ahd
g = ahd.presets.fourcv() # Busing & Levy (1967) four-circle, vertical plane
g.wavelength = 1.5406 # Å
g.sample.lattice = ahd.Lattice(a=5.431)
print(g.summary())
See fourcv — Eulerian Four-Circle (Synchrotron) for the full geometry reference, or fourch — Eulerian Four-Circle (Laboratory) for the horizontal-plane (laboratory) variant.
See also#
Build a Custom Diffractometer Geometry — how to build a geometry that is not one of the presets
Choose and Understand Basis Vectors — choosing and understanding basis vectors