Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
21 changes: 9 additions & 12 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__ = "06/10/2025"
__date__ = "09/01/2026"
__status__ = "production"

import os
Expand All @@ -49,6 +49,7 @@
import itertools
from math import sin, cos, sqrt, pi, ceil
from ..io.calibrant_config import CalibrantConfig, Miller, Reflection
from .space_groups import ReflectionCondition
from ..utils.decorators import deprecated

logger = logging.getLogger(__name__)
Expand All @@ -74,7 +75,9 @@ class Cell:
"P": "Primitive",
"I": "Body centered",
"F": "Face centered",
"C": "Side centered",
"A": "a-End centered",
"B": "b-End centered",
"C": "c-End centered",
"R": "Rhombohedral",
}

Expand All @@ -91,7 +94,7 @@ def __init__(
):
"""Constructor of the Cell class:

Crystalographic units are Angstrom for distances and degrees for angles !
Crystallographic units are Angstrom for distances and degrees for angles !

:param a,b,c: unit cell length in Angstrom
:param alpha, beta, gamma: unit cell angle in degrees
Expand Down Expand Up @@ -258,15 +261,9 @@ def type(self):
@type.setter
def type(self, lattice_type):
self._type = lattice_type if lattice_type in self.types else "P"
self.selection_rules = [lambda h, k, l: not (h == 0 and k == 0 and l == 0)] # noqa: E741
if self._type == "I":
self.selection_rules.append(lambda h, k, l: (h + k + l) % 2 == 0) # noqa: E741
if self._type == "F":
self.selection_rules.append(
lambda h, k, l: (h % 2 + k % 2 + l % 2) in (0, 3) # noqa: E741
)
if self._type == "R":
self.selection_rules.append(lambda h, k, l: ((h - k + l) % 3 == 0)) # noqa: E741
self.selection_rules = [ReflectionCondition.default]
if self._type != "P":
self.selection_rules.append(getattr(ReflectionCondition, f"type_{self._type}"))

get_type = deprecated(type.fset, reason="property", replacement="type", since_version="2025.07")
set_type = deprecated(type.fset, reason="property", replacement="type", since_version="2025.07")
Expand Down
53 changes: 52 additions & 1 deletion src/pyFAI/crystallography/space_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
__contact__ = "Jerome.Kieffer@ESRF.eu"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "08/10/2025"
__date__ = "09/01/2026"
__status__ = "production"


Expand All @@ -57,6 +57,57 @@ class ReflectionCondition:
Help is welcome to polish this class and fix the non-validated ones.
"""

@staticmethod
def default(h: int, k: int, l: int) -> bool: # noqa: E741
"""
Default selection rule: h=k=l=0 is forbidden
"""
return not (h == 0 and k == 0 and l == 0)

type_P = default

@staticmethod
def type_A(h: int, k: int, l: int) -> bool: # noqa: E741
"""
End-centered A type: h+l even
"""
return ((h + l) % 2 == 0)

@staticmethod
def type_B(h: int, k: int, l: int) -> bool: # noqa: E741
"""
End-centered B type: k+l even
"""
return ((k + l) % 2 == 0)
Copy link
Collaborator

@gudlot gudlot Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


@staticmethod
def type_C(h: int, k: int, l: int) -> bool: # noqa: E741
"""
End-centered C type: h+k even
"""
return ((h + k) % 2 == 0)

@staticmethod
def type_F(h: int, k: int, l: int) -> bool: # noqa: E741
"""
Face-centered type: h,k,l all even or all odd
"""
return (h % 2 + k % 2 + l % 2) in (0, 3)

@staticmethod
def type_I(h: int, k: int, l: int) -> bool: # noqa: E741
"""
Body-centered type: h+k+l even
"""
return (h + k + l) % 2 == 0

@staticmethod
def type_R(h: int, k: int, l: int) -> bool: # noqa: E741
"""
Rhombohedral type: h-k+l multiple of 3
"""
return ((h - k + l) % 3 == 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be more explicit as in -h+k+l=3n? International Tables for Crystallography, p 81. Figure 1.5.1.6. The obverse setting ... -h+k+l=3n. Or do you target the general reflection condition as h - k + l =3n (also in International Tables for Crystallography, p 81. Figure 1.5.1.6. )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it looks like the formula was wrong ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kif: While A and B were indeed mixed up, I don't tink h - k + l =3n is really wrong. There seems to be a difference between obverse and reverse setting, obverse seems to be standard now and since 1952, but the reverse is also used sometimes, and this is then described by h - k + l =3n.


@staticmethod
def group1_P1(h: int, k: int, l: int) -> bool: # noqa: E741
"""
Expand Down
16 changes: 14 additions & 2 deletions src/pyFAI/test/test_crystallography.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
__contact__ = "Jerome.Kieffer@ESRF.eu"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "09/07/2025"
__date__ = "09/01/2026"

import unittest
import numpy
import logging
from .utilstest import UtilsTest
from ..crystallography import resolution
from ..crystallography import resolution, Cell, ReflectionCondition

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -79,6 +79,18 @@ def test_langford(self):
self.assertTrue(numpy.allclose(c.sigma(numpy.linspace(0.1,1,10)), ref))
self.assertTrue(isinstance(c.fwhm(1), float))

def test_bug_2755(self):
"Missing default selection rule for C-type cells"
phase1 = Cell.monoclinic(3, 4, 5, 115, lattice_type='C')
res1 = len(phase1.calculate_dspacing(dmin=1))

phase2 = Cell.monoclinic(3, 4, 5, 115, lattice_type='P')
res0 = len(phase2.calculate_dspacing(dmin=1))
phase2.selection_rules.append(ReflectionCondition.group5_C2)
res2 = len(phase2.calculate_dspacing(dmin=1))
self.assertEqual(res1, res2)
self.assertGreater(res0, res2)


def suite():
testsuite = unittest.TestSuite()
Expand Down
Loading