Choose and Understand Basis Vectors#

A basis maps the three observable physical directions of the diffractometer — vertical, longitudinal, and transverse — to Cartesian unit vectors (x, y, z). The physics does not depend on which mapping you choose; the basis is purely a notational convention that determines the numerical representation of axis vectors, rotation matrices, and the UB matrix.

This page explains what a basis is, why it matters, how to choose one, and how to define a custom basis.


The three physical directions#

Every diffractometer has three mutually perpendicular directions that can be identified by looking at the instrument:

Direction

Definition

vertical

opposite to gravitational acceleration (upward)

longitudinal

in the horizontal plane, conventionally along the incident beam toward the equipment

transverse

orthogonal to both; positive sense completes a right-handed system (vertical x longitudinal)

These directions are physical observables — they do not depend on any coordinate convention. See Case Study: Describing a Diffractometer for the full derivation.


What a basis does#

A basis assigns each physical direction to a Cartesian axis. For example, the You (1999) convention assigns:

vertical     -> +x   (XHAT)
longitudinal -> +y   (YHAT)
transverse   -> +z   (ZHAT)

The Busing & Levy (1967) convention makes a different choice:

vertical     -> +z   (ZHAT)
longitudinal -> +y   (YHAT)
transverse   -> +x   (XHAT)

The NeXus convention makes a different choice:

vertical     -> +y   (YHAT)
longitudinal -> +z   (ZHAT)
transverse   -> +x   (XHAT)

All are valid right-handed systems. The same physical rotation (e.g. “right-handed about the vertical axis”) has different matrix representations in each basis, but the diffraction calculation gives the same motor angles regardless.


Requirements for a valid basis#

The package validates the basis at geometry construction time. A valid basis must satisfy:

  1. Exactly three vectors — one for each physical direction.

  2. Each vector is 3-dimensional and non-zero.

  3. Mutually orthogonal — every pair of vectors has zero dot product (within a tolerance of 1e-10).

Note

The package does not check right-handedness. It is the caller’s responsibility to ensure that vertical x longitudinal = transverse (i.e. the three vectors form a right-handed system). All pre-built geometries satisfy this constraint; custom geometries should verify it explicitly.


Pre-built conventions#

The package exports two basis constants used by the preset geometries:

Basis

vertical

longitudinal

transverse

Presets

BASIS_YOU

+x

+y

+z

psic, sixc, kappa6c, zaxis, s2d2, fivec

BASIS_BL

+z

+y

+x

fourcv, fourch, kappa4cv, kappa4ch

Other conventions exist in the broader community (e.g. NeXus, Hkl) — see Concepts for a tabulated comparison.


Defining a custom basis#

You may pass any valid basis dict to AdHocDiffractometer. The keys must be "vertical", "longitudinal", and "transverse"; the values are 3-element array-like objects:

import numpy as np
import ad_hoc_diffractometer as ahd
from ad_hoc_diffractometer.stage import Stage

# NeXus convention: vertical=+y, longitudinal=+z, transverse=+x
nexus_basis = {
    "vertical":     np.array([0.0, 1.0, 0.0]),
    "longitudinal": np.array([0.0, 0.0, 1.0]),
    "transverse":   np.array([1.0, 0.0, 0.0]),
}

# Use this basis with a custom geometry
g = ahd.AdHocDiffractometer(
    name="my_geometry",
    stages=[
        Stage("omega", np.array([0.0, 1.0, 0.0]), role="sample"),
        Stage("ttheta", np.array([0.0, 1.0, 0.0]), role="detector"),
    ],
    basis=nexus_basis,
    description="Minimal two-circle with NeXus basis",
)

The axis vectors on each Stage are expressed in the same Cartesian frame as the basis — so when you change the basis, the Stage axis vectors must change accordingly.


What changes when you change the basis#

Changing the basis changes the numerical representation of every vector quantity in the geometry:

  • Stage axis vectors (e.g. +XHAT vs +ZHAT for the vertical axis)

  • Rotation matrices

  • The B, U, and UB matrices

  • Q-vectors computed by angles_to_phi_vector()

What does not change:

  • Motor angles returned by forward() and accepted by inverse()

  • Bragg angles and d-spacings

  • Physical rotation directions (right-handed vs left-handed)

  • The number and type of solutions

The basis is a bookkeeping convention. Two geometries that describe the same physical diffractometer with different bases will produce identical motor angles for the same reflection.


Common mistakes#

Swapping vertical and transverse. If you accidentally assign vertical -> +z and transverse -> +z (or repeat any axis), the constructor will raise a ValueError because the vectors are not mutually orthogonal.

Left-handed system. If vertical x longitudinal points in the opposite direction from your transverse vector, the system is left-handed. The package does not detect this automatically — it will produce incorrect results silently. Always verify: np.cross(vertical, longitudinal) should equal transverse (not -transverse).

Mixing conventions. If you define stages using axis vectors from one convention but pass a basis dict from another, the geometry description will be internally inconsistent. The axis vectors and basis must use the same Cartesian frame.


See also#