Skip to content

Horizontal-bisecting modes cannot reach trigonal-rhombohedral (1, 1, 0) #285

@prjemian

Description

@prjemian

Observation

Three horizontal-bisecting modes return zero forward solutions for the
trigonal-rhombohedral reflection (1, 1, 0), even though their
libhkl-based peers (E4CH, K6C) solve it cleanly with a
finite chi / kappa rotation. Confirmed both on
ad_hoc_diffractometer 0.11.0 (the most recent PyPI release) and on
0.10.1, so this is a pre-existing gap rather than a v0.11.0
regression.

Geometry Mode Failure
fourch bisecting NoForwardSolutions for (1, 1, 0)
psic bisecting_vertical NoForwardSolutions for (1, 1, 0)
kappa6c bisecting_horizontal NoForwardSolutions for (1, 1, 0)

The libhkl reference solutions for the same setup are:

E4CH (bissector_horizontal):
  forward(1, 1, 0) -> omega=-5.48, chi=-50.85, phi=58.63, ttheta=-10.96
K6C (bissector_horizontal):
  forward(1, 1, 0) -> mu=5.48, komega=-54.17, kappa=-96.64, kphi=56.38,
                      ttheta=10.96, delta=0

Both libhkl solutions require a substantial out-of-plane sample
rotation (|chi| ~ 51 deg, |kappa| ~ 97 deg) — the ad_hoc
horizontal-bisecting solvers appear to confine their search to a
smaller angular sub-manifold and miss this branch.

Reproducer

Self-contained, library-only:

import math
import ad_hoc_diffractometer as ahd

# Trigonal-rhombohedral cell (single-parameter lattice, alpha != 90 deg)
RHO = dict(a=7.0, b=7.0, c=7.0, alpha=72, beta=72, gamma=72)
WL = 1.54

def bragg_theta(B, hkl, wl):
    import numpy as np
    q = B @ np.array(hkl, float)
    sintheta = wl * float(np.linalg.norm(q)) / (4 * math.pi)
    return math.degrees(math.asin(sintheta))

cases = [
    ("fourch", "bisecting"),
    ("psic",   "bisecting_vertical"),
    ("kappa6c", "bisecting_horizontal"),
]

for geom_name, mode_name in cases:
    g = ahd.make_geometry(geom_name)
    g.sample.lattice = ahd.Lattice(**RHO)
    g.wavelength = WL
    ahd.ub_identity(g.sample)
    g.mode_name = mode_name
    sols = g.forward(1, 1, 0)
    print(f"{geom_name:8s} / {mode_name:25s}  n_sols = {len(sols)}")

Output on v0.11.0 (and v0.10.1):

fourch   / bisecting                  n_sols = 0
psic     / bisecting_vertical         n_sols = 0
kappa6c  / bisecting_horizontal       n_sols = 0

Context

  • Discovered in downstream cross-validation work
    prjemian/hklpy2_solvers#69 (expanding the cross-validation
    sample set to all seven crystal systems).
  • The kappa6c bisecting_horizontal failure is structurally
    similar to the cubic (1, 0, 0) gap that survived the issue Audit order of rotations used in forward() / inverse() #280
    composition-order fix in v0.11.0 — both reflections require an
    out-of-plane sample rotation that the bisecting solver currently
    declines.
  • Downstream the three cases are tracked in
    tests/test_cross_validation.py::KNOWN_FORWARD_GAPS so the new
    trigonal_rhombohedral sample can land without regressing the
    suite; when the upstream fix lands those strict-xfails will surface
    as XPASS failures and be removed alongside the fix.

Suggested next step

The libhkl reference angles named above give the target (chi, phi)
and (komega, kappa, kphi) branches the ad_hoc solvers should also
find. Either the bisecting solver"s analytic branch enumeration
needs to admit |chi| > some_threshold solutions, or the search
needs an additional out-of-plane seed for cells with large
alpha = beta = gamma deviation from 90 deg.

Environment

  • ad_hoc_diffractometer 0.11.0 (and 0.10.1, same failures).
  • Python 3.14.4.
  • numpy 2.4.6.

Agent: OpenCode (argo/claudeopus47)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions