Skip to content
Merged
Show file tree
Hide file tree
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
384 changes: 384 additions & 0 deletions docs/architecture/ROADMAP.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions docs/architecture/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ experiment.data # CategoryCollection
# Type-switchable — recreates the underlying object
experiment.background_type = 'chebyshev' # triggers BackgroundFactory.create(...)
experiment.peak_profile_type = 'thompson-cox-hastings' # triggers PeakFactory.create(...)
experiment.extinction_type = 'shelx' # triggers ExtinctionFactory.create(...)
experiment.extinction_type = 'becker-coppens' # triggers ExtinctionFactory.create(...)
experiment.linked_crystal_type = 'default' # triggers LinkedCrystalFactory.create(...)
experiment.excluded_regions_type = 'default' # triggers ExcludedRegionsFactory.create(...)
experiment.linked_phases_type = 'default' # triggers LinkedPhasesFactory.create(...)
Expand Down Expand Up @@ -395,7 +395,7 @@ from .line_segment import LineSegmentBackground
| `PeakFactory` | Peak profiles | `CwlPseudoVoigt`, `TofPseudoVoigtIkedaCarpenter`, … |
| `InstrumentFactory` | Instruments | `CwlPdInstrument`, `TofPdInstrument`, … |
| `DataFactory` | Data collections | `PdCwlData`, `PdTofData`, `ReflnData`, `TotalData` |
| `ExtinctionFactory` | Extinction models | `ShelxExtinction` |
| `ExtinctionFactory` | Extinction models | `BeckerCoppensExtinction` |
| `LinkedCrystalFactory` | Linked-crystal refs | `LinkedCrystal` |
| `ExcludedRegionsFactory` | Excluded regions | `ExcludedRegions` |
| `LinkedPhasesFactory` | Linked phases | `LinkedPhases` |
Expand Down Expand Up @@ -480,9 +480,9 @@ Tags are the user-facing identifiers for selecting types. They must be:

**Extinction tags**

| Tag | Class |
| ------- | ----------------- |
| `shelx` | `ShelxExtinction` |
| Tag | Class |
| ---------------- | ------------------------- |
| `becker-coppens` | `BeckerCoppensExtinction` |

**Linked-crystal tags**

Expand Down Expand Up @@ -555,7 +555,7 @@ line-segment points.
| `TofPseudoVoigtIkedaCarpenter` | `PeakFactory` |
| `TofPseudoVoigtBackToBack` | `PeakFactory` |
| `TotalGaussianDampedSinc` | `PeakFactory` |
| `ShelxExtinction` | `ExtinctionFactory` |
| `BeckerCoppensExtinction` | `ExtinctionFactory` |
| `LinkedCrystal` | `LinkedCrystalFactory` |
| `Cell` | `CellFactory` |
| `SpaceGroup` | `SpaceGroupFactory` |
Expand Down
16 changes: 8 additions & 8 deletions docs/architecture/issues_closed.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ pattern. Each former single-file category is now a package with
class with `@register` + `type_info`), and `__init__.py` (re-exports
preserving import compatibility).

Experiment categories: `Extinction` → `ShelxExtinction` /
`ExtinctionFactory` (tag `shelx`), `LinkedCrystal` /
Experiment categories: `Extinction` → `BeckerCoppensExtinction` /
`ExtinctionFactory` (tag `becker-coppens`), `LinkedCrystal` /
`LinkedCrystalFactory` (tag `default`), `ExcludedRegions` /
`ExcludedRegionsFactory`, `LinkedPhases` / `LinkedPhasesFactory`,
`ExperimentType` / `ExperimentTypeFactory`.
Expand All @@ -105,12 +105,12 @@ Analysis categories: `Aliases` / `AliasesFactory`, `Constraints` /
`ConstraintsFactory`, `JointFitExperiments` /
`JointFitExperimentsFactory`.

`ShelxExtinction` and `LinkedCrystal` get the full switchable-category
API on `ScExperimentBase` (`extinction_type`, `linked_crystal_type`
getter+setter, `show_supported_*_types()`, `show_current_*_type()`).
`ExcludedRegions` and `LinkedPhases` get the same API on
`PdExperimentBase`. `Cell`, `SpaceGroup`, and `AtomSites` get it on
`Structure`. `Aliases` and `Constraints` get it on `Analysis`.
`BeckerCoppensExtinction` and `LinkedCrystal` get the full
switchable-category API on `ScExperimentBase` (`extinction_type`,
`linked_crystal_type` getter+setter, `show_supported_*_types()`,
`show_current_*_type()`). `ExcludedRegions` and `LinkedPhases` get the
same API on `PdExperimentBase`. `Cell`, `SpaceGroup`, and `AtomSites`
get it on `Structure`. `Aliases` and `Constraints` get it on `Analysis`.
Architecture §3.3, §5.5, §5.7, §9.4, §9.5 updated. Copilot instructions
updated with universal switchable-category scope and architecture-first
workflow rule. Unit tests extended with factory tests for extinction and
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/package-structure-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 factory.py
│ │ │ │ │ └── 🏷️ class ExtinctionFactory
│ │ │ │ └── 📄 shelx.py
│ │ │ │ └── 🏷️ class ShelxExtinction
│ │ │ │ └── 📄 becker_coppens.py
│ │ │ │ └── 🏷️ class BeckerCoppensExtinction
│ │ │ ├── 📁 instrument
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 base.py
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/package-structure-short.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
│ │ │ ├── 📁 extinction
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 factory.py
│ │ │ │ └── 📄 shelx.py
│ │ │ │ └── 📄 becker_coppens.py
│ │ │ ├── 📁 instrument
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 base.py
Expand Down
2 changes: 1 addition & 1 deletion src/easydiffraction/analysis/calculators/cryspy.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ def _cif_extinction_section(
'mosaicity': '_extinction_mosaicity',
'radius': '_extinction_radius',
}
cif_lines.extend(('', '_extinction_model gauss'))
cif_lines.extend(('', f'_extinction_model {extinction.model.value}'))
for local_attr_name, engine_key_name in extinction_mapping.items():
attr_obj = getattr(extinction, local_attr_name)
if attr_obj is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# SPDX-FileCopyrightText: 2026 EasyScience contributors <https://github.com/easyscience>
# SPDX-License-Identifier: BSD-3-Clause

from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
BeckerCoppensExtinction,
)
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
# SPDX-FileCopyrightText: 2026 EasyScience contributors <https://github.com/easyscience>
# SPDX-License-Identifier: BSD-3-Clause
"""Shelx-style isotropic extinction correction."""
"""
Becker-Coppens isotropic extinction correction for single crystals.
"""

from __future__ import annotations

from easydiffraction.core.category import CategoryItem
from easydiffraction.core.metadata import Compatibility
from easydiffraction.core.metadata import TypeInfo
from easydiffraction.core.validation import AttributeSpec
from easydiffraction.core.validation import MembershipValidator
from easydiffraction.core.validation import RangeValidator
from easydiffraction.core.variable import Parameter
from easydiffraction.core.variable import StringDescriptor
from easydiffraction.datablocks.experiment.categories.extinction.factory import ExtinctionFactory
from easydiffraction.datablocks.experiment.item.enums import ExtinctionModelEnum
from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum
from easydiffraction.io.cif.handler import CifHandler


@ExtinctionFactory.register
class ShelxExtinction(CategoryItem):
"""Shelx-style extinction correction for single crystals."""
class BeckerCoppensExtinction(CategoryItem):
"""
Becker-Coppens spherical extinction correction for single crystals.

Combines primary and secondary extinction into a single correction
factor ``y = y_p * y_s``, following the Becker-Coppens formalism.
The mosaicity distribution for the secondary extinction can be
either Gaussian (``'gauss'``) or Lorentzian (``'lorentz'``).

Parameters are the crystal ``radius`` (in μm) and the ``mosaicity``
(in arc-minutes, as expected by CrysPy).
"""

type_info = TypeInfo(
tag='shelx',
description='Shelx-style isotropic extinction correction',
tag='becker-coppens',
description='Becker-Coppens isotropic extinction correction',
)
compatibility = Compatibility(
sample_form=frozenset({SampleFormEnum.SINGLE_CRYSTAL}),
Expand All @@ -30,33 +45,37 @@ class ShelxExtinction(CategoryItem):
def __init__(self) -> None:
super().__init__()

self._model = StringDescriptor(
name='model',
description='Mosaicity distribution model (gauss or lorentz)',
value_spec=AttributeSpec(
default=ExtinctionModelEnum.default().value,
validator=MembershipValidator(
allowed=[member.value for member in ExtinctionModelEnum],
),
),
cif_handler=CifHandler(names=['_extinction.model']),
)

self._mosaicity = Parameter(
name='mosaicity',
description='Mosaicity value for extinction correction',
units='deg',
description='Mosaicity of the crystal',
units='arcmin',
value_spec=AttributeSpec(
default=1.0,
validator=RangeValidator(),
),
cif_handler=CifHandler(
names=[
'_extinction.mosaicity',
]
),
cif_handler=CifHandler(names=['_extinction.mosaicity']),
)
self._radius = Parameter(
name='radius',
description='Crystal radius for extinction correction',
description='Mean radius of the crystal',
units='μm',
value_spec=AttributeSpec(
default=1.0,
validator=RangeValidator(),
),
cif_handler=CifHandler(
names=[
'_extinction.radius',
]
),
cif_handler=CifHandler(names=['_extinction.radius']),
)

self._identity.category_code = 'extinction'
Expand All @@ -65,10 +84,25 @@ def __init__(self) -> None:
# Public properties
# ------------------------------------------------------------------

@property
def model(self) -> StringDescriptor:
"""
Mosaicity distribution model (``'gauss'`` or ``'lorentz'``).

Reading this property returns the underlying
``StringDescriptor`` object. Assigning to it updates the
descriptor value.
"""
return self._model

@model.setter
def model(self, value: str) -> None:
self._model.value = value

@property
def mosaicity(self) -> Parameter:
"""
Mosaicity value for extinction correction (deg).
Mosaicity of the crystal (arcmin).

Reading this property returns the underlying ``Parameter``
object. Assigning to it updates the parameter value.
Expand All @@ -82,7 +116,7 @@ def mosaicity(self, value: float) -> None:
@property
def radius(self) -> Parameter:
"""
Crystal radius for extinction correction (μm).
Mean radius of the crystal (μm).

Reading this property returns the underlying ``Parameter``
object. Assigning to it updates the parameter value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ class ExtinctionFactory(FactoryBase):
"""Create extinction correction models by tag."""

_default_rules: ClassVar[dict] = {
frozenset(): 'shelx',
frozenset(): 'becker-coppens',
}
2 changes: 1 addition & 1 deletion src/easydiffraction/datablocks/experiment/item/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def extinction_type(self, new_type: str) -> None:
Parameters
----------
new_type : str
Extinction tag (e.g. ``'shelx'``).
Extinction tag (e.g. ``'becker-coppens'``).
"""
supported_tags = ExtinctionFactory.supported_tags()
if new_type not in supported_tags:
Expand Down
34 changes: 34 additions & 0 deletions src/easydiffraction/datablocks/experiment/item/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,37 @@ def description(self) -> str: # noqa: PLR0911
if self is PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC:
return 'Gaussian-damped sinc profile for pair distribution function (PDF) analysis.'
return None


class ExtinctionModelEnum(StrEnum):
"""Mosaicity distribution model for Becker-Coppens extinction."""

GAUSS = 'gauss'
LORENTZ = 'lorentz'

@classmethod
def default(cls) -> 'ExtinctionModelEnum':
"""
Return the default extinction model (GAUSS).

Returns
-------
'ExtinctionModelEnum'
The default enum member.
"""
return cls.GAUSS

def description(self) -> str:
"""
Return a human-readable description of this extinction model.

Returns
-------
str
Description string for the current enum member.
"""
if self is ExtinctionModelEnum.GAUSS:
return 'Gaussian mosaicity distribution for extinction correction.'
if self is ExtinctionModelEnum.LORENTZ:
return 'Lorentzian mosaicity distribution for extinction correction.'
return None
Loading
Loading