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

BASIS_YOU

+x

+y

+z

psic(), sixc(), kappa6c(), zaxis(), s2d2(), fivec()

BASIS_BL

+z

+y

+x

fourcv(), fourch(), kappa4cv(), kappa4ch()

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() results

  • axis — rotation axis vector; a leading means left-handed rotation

  • parent — 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:

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#