factories#

Import: ad_hoc_diffractometer.factories

This module provides the geometry registry infrastructure: the @register_geometry decorator, discovery of third-party geometry plugins via entry points, and the list_geometries() / get_geometry() / make_geometry() lookup functions.

It also defines the shared basis dictionaries (BASIS_YOU, BASIS_BL) and the default kappa tilt angle (KAPPA_ALPHA_DEFAULT) used by the pre-built geometry functions in ad_hoc_diffractometer.presets.

Pre-built geometries#

The 10 pre-built geometry factory functions (psic, fourcv, fourch, sixc, kappa4cv, kappa4ch, kappa6c, zaxis, s2d2, fivec) live in ad_hoc_diffractometer.presets. Access them as:

import ad_hoc_diffractometer as ahd

g = ahd.presets.fourcv()

Extension via entry points#

Third-party packages can contribute additional geometry factories by declaring an entry point in the "ad_hoc_diffractometer.geometries" group in their pyproject.toml:

[project.entry-points."ad_hoc_diffractometer.geometries"]
my_geom = "my_package.module:my_factory_function"

The factory function must accept no required arguments (it may accept keyword arguments) and return an AdHocDiffractometer instance. Entry-point geometries are discovered and loaded automatically when list_geometries() or get_geometry() is first called; they do NOT need to call @register_geometry themselves.

Geometry names must be globally unique. If an entry-point name duplicates an already-registered name (whether a built-in or a previously loaded plugin), a ValueError is raised at discovery time. This prevents silent shadowing: an external package cannot overwrite fourcv, psic, or any other registered geometry.

Writing a custom geometry#

Each factory accepts an optional basis keyword argument (defaulting to the canonical convention for that geometry). Inside the factory, resolve physical-direction aliases locally:

from ad_hoc_diffractometer import AdHocDiffractometer, register_geometry
from ad_hoc_diffractometer.factories import BASIS_YOU
from ad_hoc_diffractometer.stage import Stage

@register_geometry
def my_geometry(basis=BASIS_YOU):
    VERTICAL     = basis["vertical"]
    TRANSVERSE   = basis["transverse"]
    LONGITUDINAL = basis["longitudinal"]
    stages = [
        Stage("omega", -TRANSVERSE,      role="sample"),
        Stage("chi",   +LONGITUDINAL, parent="omega", role="sample"),
        Stage("phi",   -TRANSVERSE,      parent="chi",   role="sample"),
        Stage("ttheta", -TRANSVERSE,     role="detector"),
    ]
    return AdHocDiffractometer(
        name="my_geometry", stages=stages, basis=basis,
    )

The two public basis dicts BASIS_YOU and BASIS_BL are available from this module. Pass basis=BASIS_BL for Busing & Levy convention geometries.

References

  • W.R. Busing & H.A. Levy, Acta Cryst. 22, 457-464 (1967)

  • H. You, J. Appl. Cryst. 32, 614-623 (1999) DOI:10.1107/S0021889899001223

  • ITC Vol. C, Sec. 2.2.6 (2006) DOI:10.1107/97809553602060000577

  • D.A. Walko, Ref. Module Mater. Sci. Mater. Eng. (2016)

Attributes#

BASIS_BL

Basis vector dictionary for the Busing & Levy (1967) coordinate convention.

BASIS_YOU

Basis vector dictionary for the You (1999) coordinate convention.

GEOMETRY_ENTRY_POINT_GROUP

Entry-point group name for geometry plugins.

KAPPA_ALPHA_DEFAULT

Default kappa tilt angle in degrees (Walko 2016; Enraf-Nonius; ITC Vol. C).

Functions#

_load_entry_point_geometries(→ None)

Discover and load geometry factories from installed entry points.

get_geometry(name)

Return the registered factory function for the named geometry.

list_geometries(→ dict[str, type])

Return a copy of the geometry registry as {name: factory_callable}.

make_geometry(name, **kwargs)

Instantiate a geometry by name, passing keyword arguments to its factory.

register_geometry(func)

Decorator that registers a geometry factory in _GEOMETRY_REGISTRY.

Module Contents#

ad_hoc_diffractometer.factories.BASIS_BL#

Basis vector dictionary for the Busing & Levy (1967) coordinate convention.

Maps physical direction names to Cartesian unit vectors:

  • "transverse" → +x

  • "longitudinal" → +y (along the beam)

  • "vertical" → +z (opposite to gravitational acceleration)

Used by fourcv, fourch, kappa4cv, and kappa4ch (in ad_hoc_diffractometer.presets).

ad_hoc_diffractometer.factories.BASIS_YOU#

Basis vector dictionary for the You (1999) coordinate convention.

Maps physical direction names to Cartesian unit vectors:

  • "vertical"XHAT (+x, opposite to gravitational acceleration)

  • "longitudinal"YHAT (+y, along the beam)

  • "transverse"ZHAT (+z, completes the right-handed system: vertical × longitudinal)

Default basis used by psic, sixc, kappa6c, zaxis, s2d2, and fivec (in ad_hoc_diffractometer.presets).

ad_hoc_diffractometer.factories.GEOMETRY_ENTRY_POINT_GROUP = 'ad_hoc_diffractometer.geometries'#

Entry-point group name for geometry plugins.

ad_hoc_diffractometer.factories.KAPPA_ALPHA_DEFAULT = 50.0#

Default kappa tilt angle in degrees (Walko 2016; Enraf-Nonius; ITC Vol. C).

ad_hoc_diffractometer.factories._load_entry_point_geometries() None[source]#

Import: ad_hoc_diffractometer.factories._load_entry_point_geometries

Discover and load geometry factories from installed entry points.

Scans the "ad_hoc_diffractometer.geometries" entry-point group for all installed packages (including this package itself) and adds any factories not already present in _GEOMETRY_REGISTRY.

This function is called automatically — and only once — by list_geometries() and get_geometry(). It is idempotent: repeated calls after the first are no-ops.

Notes

Built-in factories are registered via @register_geometry at import time, so they are always present even if entry-point discovery fails. Entry-point discovery supplements the registry with any third-party plugins that are installed but were not decorated with @register_geometry.

Each geometry name must be unique across all installed packages. If an entry-point name collides with an already-registered name (whether a built-in or a previously loaded plugin), a ValueError is raised identifying the conflicting name and its source. This prevents silent shadowing of built-in geometries and ambiguous duplicate registrations.

If loading a particular entry point raises an exception other than a name collision (e.g. the plugin package is broken or missing), that entry point is silently skipped so that the rest of the registry is unaffected.

Raises:

ValueError – If an entry-point name duplicates an already-registered geometry name.

ad_hoc_diffractometer.factories.get_geometry(name: str)[source]#

Import: ad_hoc_diffractometer.factories.get_geometry

Return the registered factory function for the named geometry.

This is the primitive lookup — it returns the callable factory, not an instance. Use make_geometry() if you want an AdHocDiffractometer instance directly.

Parameters:

name (str) – Name of the geometry, as registered by @register_geometry (e.g. ‘psic’, ‘fourcv’, ‘kappa4cv’).

Returns:

The factory function for the named geometry.

Return type:

callable

Raises:

ValueError – If no geometry with that name is registered, with a message listing the available names.

Examples

>>> from ad_hoc_diffractometer import get_geometry
>>> factory = get_geometry("psic")
>>> factory()
AdHocDiffractometer(name='psic', ...)
>>> get_geometry("kappa4cv")(alpha_deg=50)
AdHocDiffractometer(name='kappa4cv', ...)
ad_hoc_diffractometer.factories.list_geometries() dict[str, type][source]#

Import: ad_hoc_diffractometer.factories.list_geometries

Return a copy of the geometry registry as {name: factory_callable}.

Includes all built-in geometries (registered via @register_geometry at import time) plus any third-party geometry plugins installed as entry points in the "ad_hoc_diffractometer.geometries" group.

Entry-point discovery runs automatically the first time this function is called; subsequent calls use the already-populated registry.

Returns:

Keys are factory names (e.g. 'psic', 'fourcv'). Values are the callable factory functions.

Return type:

dict

Examples

>>> from ad_hoc_diffractometer import list_geometries
>>> sorted(list_geometries())
['fivec', 'fourch', 'fourcv', 'kappa4ch', 'kappa4cv', 'kappa6c',
 'psic', 's2d2', 'sixc', 'zaxis']
>>> list_geometries()['psic']()   # instantiate by name
AdHocDiffractometer(name='psic', ...)
ad_hoc_diffractometer.factories.make_geometry(name: str, **kwargs)[source]#

Import: ad_hoc_diffractometer.factories.make_geometry

Instantiate a geometry by name, passing keyword arguments to its factory.

Looks up the factory for the named geometry via get_geometry() and calls it with the supplied kwargs. This is the most convenient entry point for config-driven or programmatic geometry selection.

Parameters:
  • name (str) – Name of the geometry (e.g. ‘psic’, ‘fourcv’, ‘kappa4cv’).

  • **kwargs – Keyword arguments forwarded to the factory function. Most factories take no arguments; kappa factories accept alpha_deg.

Returns:

A fully configured diffractometer geometry instance.

Return type:

AdHocDiffractometer

Raises:

ValueError – If no geometry with that name is registered.

Examples

>>> from ad_hoc_diffractometer import make_geometry
>>> make_geometry("psic")
AdHocDiffractometer(name='psic', ...)
>>> make_geometry("kappa4cv", alpha_deg=50)
AdHocDiffractometer(name='kappa4cv', ...)
>>> make_geometry("kappa6c", alpha_deg=55)
AdHocDiffractometer(name='kappa6c', ...)
ad_hoc_diffractometer.factories.register_geometry(func)[source]#

Import: ad_hoc_diffractometer.factories.register_geometry

Decorator that registers a geometry factory in _GEOMETRY_REGISTRY.

The function is stored under its own __name__, so the registry key is always identical to the callable’s name. The function is returned unchanged; this decorator has no runtime effect on the factory itself.

Third-party packages do not need to use this decorator — they can instead declare an entry point in the "ad_hoc_diffractometer.geometries" group in their pyproject.toml and the factory will be discovered automatically.

Example

@register_geometry
def psic() -> AdHocDiffractometer:
    ...