Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions src/pyFAI/crystallography/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
__contact__ = "Jerome.Kieffer@ESRF.eu"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "19/05/2026"
__date__ = "12/06/2026"
__status__ = "production"

import os
Expand All @@ -62,6 +62,47 @@ def _pretty_length(a):
return f"{a:.5f}\N{Latin Capital Letter a with Ring Above}"


def _score(hkl):
"""helper function to `canonical_hkl`"""
hkl = tuple(x for x in hkl)
first_nonzero_neg = False
for x in hkl:
if x != 0:
first_nonzero_neg = x < 0
break
n_neg = sum(1 for x in hkl if x < 0)
abs_hkl = tuple(abs(x) for x in hkl)
neg_hkl = tuple(-x for x in hkl)
not_sorted = int(abs_hkl != tuple(sorted(abs_hkl, reverse=True)))
return (int(first_nonzero_neg), n_neg, not_sorted, *neg_hkl)


def canonical_hkl(equivalents: list):
"""Return the most representative Miller index from a list of
symmetry-equivalent reflections, following crystallographic conventions.

Selection priority (first differing criterion wins):

1. First non-zero index is positive — standard Friedel convention that
avoids listing both (hkl) and (-h-k-l) as separate entries.
2. Fewest negative indices — prefer all-positive when the point-group
symmetry allows it.
3. Non-increasing absolute values |h| >= |k| >= |l| — the conventional
ordered representation used in most structure databases.
4. Lexicographic tiebreak on (h, k, l), largest values first.

:param equivalents: iterable of Miller instances or 3-tuples of integers,
as returned by the values of ``Cell.calculate_dspacing``.
:return: the canonical Miller index (same type as the input elements).
:raises ValueError: if *equivalents* is empty.
"""

lst = list(equivalents)
if not lst:
raise ValueError("equivalents must not be empty")
return min(lst, key=_score)


class Cell:
"""
This is a cell object, able to calculate the volume and d-spacing according to formula from:
Expand Down Expand Up @@ -370,11 +411,10 @@ def build_calibrant_config(self, dmin=1.0):
dspacing = list(reflections.keys())
dspacing.sort(reverse=True)
for d in dspacing:
reflection = reflections[d]
config.reflections.append(Reflection(d, hkl=reflection[-1], multiplicity=len(reflection)))
family = reflections[d]
config.reflections.append(Reflection(d, hkl=canonical_hkl(family), multiplicity=len(reflections)))
return config


def save(self, name, long_name=None, doi=None, dmin=1.0, dest_dir=None):
"""Save information about the cell in a d-spacing file, usable as Calibrant

Expand Down
Loading