diff --git a/docs/architecture/ROADMAP.md b/docs/architecture/ROADMAP.md
new file mode 100644
index 000000000..1cf696a1e
--- /dev/null
+++ b/docs/architecture/ROADMAP.md
@@ -0,0 +1,384 @@
+# EasyDiffraction Development Roadmap
+
+LIB β Python library API
+APP β graphical application
+CLI β command-line interface
+
+Legend:
+
+- β
done
+- π§ work in progress
+- π planned
+ - π`highest` Urgent. Needs attention ASAP
+ - π`high` Should be prioritized soon
+ - π`medium` Normal/default priority
+ - π`low` Low importance
+ - π`lowest` Very low urgency
+- β not applicable / not implemented
+
+---
+
+# 1. Sample Model
+
+## 1.1 Crystal Structure Parameters
+
+### Space Group
+
+| Feature | LIB | APP |
+| ---------------------------------- | --- | --- |
+| Hermann-Mauguin space-group symbol | β
| β
|
+| Space group IT number | π | π |
+| IT coordinate system code | β
| β
|
+
+### Cell
+
+| Feature | LIB | APP |
+| ----------------- | --- | --- |
+| Lengths _a, b, c_ | β
| β
|
+| Angles _Ξ±, Ξ², Ξ³_ | β
| β
|
+
+### Atom Sites
+
+| Feature | LIB | APP |
+| ---------------------------------------------- | --- | --- |
+| Neutron scattering lengths (tabulated, CrysPy) | β
| β
|
+| X-ray scattering factors (tabulated, CrysPy) | β
| β
|
+| Custom neutron scattering length | π | π |
+| Fractional coordinates _x, y, z_ | β
| β
|
+| Occupancy | β
| β
|
+| Symmetry _wyckoff_letter_ | β
| β
|
+
+### Atomic Displacement Parameters (ADP)
+
+| Feature | LIB | APP |
+| ----------------------------------------------- | ----------- | --- |
+| Isotropic Biso | β
| π |
+| Isotropic Uiso | π | β
|
+| Anisotropic Bani _B11, B22, B33, B12, B13, B23_ | π`highest` | π |
+| Anisotropic Uani _U11, U22, U33, U12, U13, U23_ | π | π |
+
+---
+
+## 1.2 Magnetic Structure Parameters
+
+| Feature | LIB | APP |
+| ------------------------------------------------------- | --- | --- |
+| EPIC (Magnetic space groups, unpolarized and polarized) | π | π |
+
+---
+
+# 2. Experiment Model
+
+## 2.1 Powder Diffraction
+
+### Fitting Methods
+
+| Feature | LIB | APP |
+| ------------------------------------- | --- | --- |
+| Rietveld refinement (full pattern) | β
| β
|
+| Le Bail refinement (profile matching) | π | π |
+
+### Linked Phases
+
+| Feature | LIB | APP |
+| ------------ | --- | --- |
+| Scale factor | β
| β
|
+
+### Excluded Regions
+
+| Feature | LIB | APP |
+| ----------------------------------------- | --- | --- |
+| Multiple regions
_start/end positions_ | β
| π |
+
+---
+
+### Background
+
+| Feature | LIB | APP |
+| ------------------------------------------------- | --- | --- |
+| Line segments type
_x, y_ | β
| β
|
+| Chebyshev polynomial type
_order, coefficient_ | β
| π |
+
+### Preferred Orientation
+
+| Feature | LIB | APP |
+| ------------------------------------------ | -------- | --- |
+| Basic preferred orientation model (CrysPy) | π`high` | π |
+
+### Instrument β Constant Wavelength
+
+| Feature | LIB | APP |
+| -------------------------------------------------------- | --- | --- |
+| Wavelength | β
| β
|
+| Second wavelength | π§ | π |
+| 2ΞΈ offset | β
| β
|
+| Sample displacement correction (FullProf _SyCos, SySin_) | π§ | π |
+
+### Instrument β Time-of-Flight
+
+| Feature | LIB | APP |
+| ------------------------------------------------------------- | --- | --- |
+| 2ΞΈ bank | β
| β
|
+| d β TOF conversion
_reciprocal, offset, linear, quadratic_ | β
| β
|
+
+### Peak Profile β Constant Wavelength
+
+CrysPy: Pseudo-Voigt from FullProf. Empirical asymmetry, 4 params (p1,
+p2, p3, p4) from FullProf.
+
+| Feature | LIB | APP |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | --- |
+| Pseudo-Voigt + Empirical asymmetry
_Gaussian broadening U, V, W. Lorentzian broadening X, Y
Empirical asymmetry p1, p2, p3, p4_
(CrysPy) | β
| β
|
+| Thompson-Cox-Hastings Pseudo-Voigt + Finger-Cox-Jephcoat asymmetry
_Gaussian broadening U, V, W. Lorentzian broadening X, Y
Finger-Cox-Jephcoat asymmetry 1, 2_
(CrysFML) | β
/π | π |
+
+### Peak Profile β Time-of-Flight
+
+CrysPy peak_shape options:
+
+- "Gauss": Jorgensen (back-to-back exponentials β Gaussian)
+- "pseudo-Voigt": Jorgensen-Von Dreele (back-to-back exponentials β
+ pseudo-Voigt)
+- "type0m": Double back-to-back exponentials β pseudo-Voigt (Z-Rietveld
+ type 0m)
+
+| Feature | LIB | APP |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- |
+| Jorgensen (back-to-back exponentials β Gaussian)
_Gaussian broadening Οβ, Οβ, Οβ
Back-to-back exponential rise Ξ±β, Ξ±β. Back-to-back exponential decay Ξ²β, Ξ²β_
(CrysPy) | β
| β
|
+| Jorgensen-Von Dreele (back-to-back exponentials β pseudo-Voigt)
_Gaussian broadening Οβ, Οβ, Οβ. Lorentzian broadening Ξ³β, Ξ³β, Ξ³β
Back-to-back exponential rise Ξ±β, Ξ±β. Back-to-back exponential decay Ξ²β, Ξ²β_
(CrysPy) | π | β
|
+| Double back-to-back exponentials β pseudo-Voigt [Z-Rietveld type0m]
_Gaussian broadening Οβ, Οβ, Οβ. Lorentzian broadening Ξ³β, Ξ³β, Ξ³β
Rise Ξ±β, Ξ±β. Fast decay Ξ²ββ, Ξ²ββ. Slow decay Ξ²ββ. Switching rββ, rββ, rββ_
(CrysPy) | π | π |
+
+| TOF profile | TOF source | Performance |
+| ------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------- |
+| Jorgensen (back-to-back exponentials β Gaussian) | Simpler TOF profile, including reactor-source TOF implementations | Fast |
+| Jorgensen-Von Dreele (back-to-back exponentials β pseudo-Voigt) | Spallation-source TOF | Slower |
+| Double back-to-back exponentials β pseudo-Voigt (Z-Rietveld type0m) | Spallation-source TOF; more elaborate asymmetric profile | Slowest |
+
+---
+
+## 2.1.2 Total Scattering (Pair Distribution Function)
+
+### Peak Profile
+
+| Feature | LIB | APP |
+| ------------------------------------------------------------------------------------------------------ | --- | --- |
+| GaussianDampedSinc type
_cutoff q. broadening q. sharpening Ξ΄β, Ξ΄β
damping q, particle diameter_ | β
| π |
+
+---
+
+## 2.2 Single Crystal Diffraction
+
+### Extinction
+
+CrysPy's extinction is NOT Shelx-style. It's an analytical
+Becker-Coppens spherical model with Gauss or Lorentz mosaicity
+distribution
+
+| Feature | LIB | APP |
+| ----------------------------------------------------- | --- | --- |
+| Becker-Coppens, Gaussian model: _radius, mosaicity_ | β
| β
|
+| Becker-Coppens, Lorentzian model: _radius, mosaicity_ | β
| β
|
+| Anisotropic extinction correction | π | π |
+
+### Domains / Twinning
+
+| Feature | LIB | APP |
+| ------------------------------------------------- | --- | --- |
+| Twinning / domains for single-crystal diffraction | π | π |
+
+### Instrument β Constant Wavelength
+
+| Feature | LIB | APP |
+| --------------------- | --- | --- |
+| Wavelength | β
| β
|
+| Half wavelength (Ξ»/2) | π | π |
+
+### Instrument β Time-of-Flight
+
+| Feature | LIB | APP |
+| ------------------------------------ | --- | --- |
+| Individual wavelength per reflection | β
| π |
+
+## 2.3. Polarized Neutron Diffraction
+
+| Feature | LIB | APP |
+| ---------------------------------------------- | --- | --- |
+| EPIC (powders and single crystals, FR and SNP) | π | π |
+
+---
+
+# 3. Multi-Dataset Support
+
+| Feature | LIB | APP |
+| --------------------------------- | --- | --- |
+| Multiple structural data blocks | β
| β
|
+| Multiple experimental data blocks | β
| π |
+
+---
+
+# 4. Analysis (Fitting)
+
+### Refinement Algorithms
+
+| Feature | LIB | APP |
+| --------------------------------------------------------------- | --- | --- |
+| LevenbergβMarquardt (numerical derivatives)
LMFIT minimizer | β
| β
|
+| LevenbergβMarquardt (analytical derivatives)
LMFIT minimizer | π | π |
+| Derivative-free minimization
DFO-LS minimizer | β
| β
|
+| Bayesian analysis
BUMPS minimizer | π | π |
+
+### Fit Strategies
+
+| Feature | LIB | APP |
+| ---------------------------------------------------------------------------------------------------- | --- | --- |
+| Sequential fit of experimental data blocks | β
| π |
+| Joint fit of experimental data blocks within the same calculation engine | β
| π |
+| Joint fit of experimental data blocks using different calculation engines
(e.g. CrysPy + Pdffit2) | β
| π |
+| Custom weighting for joint fit: _weight per dataset_ | β
| π |
+
+### Live Fitting
+
+| Feature | LIB | APP |
+| ---------------------------------------------- | --- | --- |
+| Live fitting during real-time data acquisition | π | π |
+
+---
+
+# 5. Refinement Execution
+
+| Feature | LIB | APP | CLI |
+| --------------------------------------------- | --- | --- | --- |
+| Scripted refinement workflow | β
| β | β |
+| GUI-driven refinement workflow | β | β
| β |
+| Command-line refinement execution | β | β | β
|
+| Parameter modification | β
| β
| β |
+| Load individual structure or experiment files | β
| β
| β |
+| Project-based refinement | β
| β
| β
|
+| Save refinement results to project | β
| β
| β
|
+
+---
+
+# 6. Constraints
+
+| Feature | LIB | APP |
+| ------------------------------ | --- | ----- |
+| Automatic symmetry constraints | β
| β
/π |
+| User-defined constraints | β
| π |
+
+---
+
+# 7. Data Management
+
+### Data Loading
+
+| Feature | LIB | APP | CLI |
+| ------------------------------- | --- | --- | --- |
+| Load structure from CIF | β
| β
| β |
+| Load experiment data from ASCII | β
| β
| β |
+| Load experiment data from CIF | β
| β
| β |
+
+### Project Files
+
+| Feature | LIB | APP | CLI |
+| ---------------------- | --- | --- | --- |
+| Load project from disk | β
| β
| β
|
+| Save project to disk | β
| β
| β
|
+
+### SciCat Integration
+
+| Feature | LIB | APP | CLI |
+| ------------------------ | --- | --- | --- |
+| Load project from SciCat | π | π | β |
+| Save project to SciCat | π | π | β |
+
+### External Resources
+
+| Feature | LIB | APP | CLI |
+| --------------------------------- | --- | --- | --- |
+| List available tutorial notebooks | β | β | β
|
+| Download tutorial notebooks | β | β | β
|
+
+---
+
+# 8. Visualization
+
+## 8.1. Sample Model
+
+### Crystal Structure
+
+| Feature | LIB | APP |
+| ---------------------------------------- | --- | --- |
+| Visualize unit cell | π | β
|
+| Visualize multiple unit cells | π | π |
+| Visualize atom sites as spheres | π | β
|
+| Visualize atoms occupied same position | π | π |
+| Visualize bonds | π | π |
+| Visualize polyhedra | π | π |
+| Interactive mode
3D rotation, zooming | π | β
|
+| Orthogonal unit cell | π | β
|
+| Non-orthogonal unit cell | π | π |
+
+### Magnetic Structure
+
+| Feature | LIB | APP |
+| ------------------------------------ | --- | --- |
+| Visualize magnetic moments as arrows | π | π |
+
+## 8.2. Experiment
+
+### Powder Diffraction
+
+| Feature | LIB | APP | CLI |
+| ------------------------------------ | -------- | --- | --- |
+| Plot experimental curve | β
| β
| β
|
+| Plot calculated curve | β
| β
| β
|
+| Plot residual curve | β
| β
| β
|
+| Plot Bragg peaks | π`high` | β
| β |
+| Interactive mode
zooming, panning | β
| β
| β |
+
+### Single Crystal Diffraction
+
+| Feature | LIB | APP | CLI |
+| ------------------------------------ | --- | --- | --- |
+| Plot obs vs calc for reflections | β
| β
| β
|
+| Interactive mode
zooming, panning | β
| β
| β |
+
+## 8.3. Analysis
+
+| Feature | LIB | APP | CLI |
+| ---------------------------------------------------- | ----- | --- | --- |
+| Live update of plots on parameter change with slider | β | β
| β |
+| Live update of plots during refinement | β | β
| β |
+| Parameter evolution (sequential refinement) | β
/π | π | β |
+
+### Fitting
+
+| Feature | LIB | APP | CLI |
+| ----------------------------------------- | --- | --- | --- |
+| Live update of plots | β | β
| β |
+| Live update of fit quality (change in ΟΒ²) | β
| β
| β
|
+| Plot correlation between parameters | β
| π | β
|
+
+---
+
+# 9. User documentation
+
+| Feature | LIB | APP |
+| ----------------------------------- | --- | ----- |
+| New unified documentation structure | β
| π |
+| Introduction | β
| β
/π |
+| Installation and setup guide | β
| β
/π |
+| User guide | β
| β
/π |
+| Tutorials | β
| π |
+| API reference | β
| β |
+
+---
+
+# 10. Future Topics
+
+Here, we list features that are not sorted into the above categories,
+but are still on our radar for future development.
+
+- Restrains (soft constraints, e.g. bond lengths, angles)
+- Global optimization algorithms (e.g. simulated annealing)
+- Incommensurate structures
+- 2D Rietveld refinement
diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md
index 7faac40e2..52ae378fb 100644
--- a/docs/architecture/architecture.md
+++ b/docs/architecture/architecture.md
@@ -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(...)
@@ -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` |
@@ -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**
@@ -555,7 +555,7 @@ line-segment points.
| `TofPseudoVoigtIkedaCarpenter` | `PeakFactory` |
| `TofPseudoVoigtBackToBack` | `PeakFactory` |
| `TotalGaussianDampedSinc` | `PeakFactory` |
-| `ShelxExtinction` | `ExtinctionFactory` |
+| `BeckerCoppensExtinction` | `ExtinctionFactory` |
| `LinkedCrystal` | `LinkedCrystalFactory` |
| `Cell` | `CellFactory` |
| `SpaceGroup` | `SpaceGroupFactory` |
diff --git a/docs/architecture/issues_closed.md b/docs/architecture/issues_closed.md
index f95e3b89f..460fe0b09 100644
--- a/docs/architecture/issues_closed.md
+++ b/docs/architecture/issues_closed.md
@@ -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`.
@@ -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
diff --git a/docs/architecture/package-structure-full.md b/docs/architecture/package-structure-full.md
index 12b25e79a..7e879760c 100644
--- a/docs/architecture/package-structure-full.md
+++ b/docs/architecture/package-structure-full.md
@@ -179,8 +179,8 @@
β β β β βββ π __init__.py
β β β β βββ π factory.py
β β β β β βββ π·οΈ class ExtinctionFactory
-β β β β βββ π shelx.py
-β β β β βββ π·οΈ class ShelxExtinction
+β β β β βββ π becker_coppens.py
+β β β β βββ π·οΈ class BeckerCoppensExtinction
β β β βββ π instrument
β β β β βββ π __init__.py
β β β β βββ π base.py
diff --git a/docs/architecture/package-structure-short.md b/docs/architecture/package-structure-short.md
index 5bbc788ff..a26495b04 100644
--- a/docs/architecture/package-structure-short.md
+++ b/docs/architecture/package-structure-short.md
@@ -92,7 +92,7 @@
β β β βββ π extinction
β β β β βββ π __init__.py
β β β β βββ π factory.py
-β β β β βββ π shelx.py
+β β β β βββ π becker_coppens.py
β β β βββ π instrument
β β β β βββ π __init__.py
β β β β βββ π base.py
diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py
index adb726687..cc709a272 100644
--- a/src/easydiffraction/analysis/calculators/cryspy.py
+++ b/src/easydiffraction/analysis/calculators/cryspy.py
@@ -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:
diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py
index 3e6fa39a1..cc259fd8c 100644
--- a/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py
+++ b/src/easydiffraction/datablocks/experiment/categories/extinction/__init__.py
@@ -1,4 +1,6 @@
# SPDX-FileCopyrightText: 2026 EasyScience contributors
# 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,
+)
diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py b/src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py
similarity index 51%
rename from src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py
rename to src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py
index 42ffb8107..3170cb31d 100644
--- a/src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py
+++ b/src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py
@@ -1,6 +1,8 @@
# SPDX-FileCopyrightText: 2026 EasyScience contributors
# SPDX-License-Identifier: BSD-3-Clause
-"""Shelx-style isotropic extinction correction."""
+"""
+Becker-Coppens isotropic extinction correction for single crystals.
+"""
from __future__ import annotations
@@ -8,20 +10,33 @@
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}),
@@ -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'
@@ -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.
@@ -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.
diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py
index 608e25740..1ad27da9b 100644
--- a/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py
+++ b/src/easydiffraction/datablocks/experiment/categories/extinction/factory.py
@@ -13,5 +13,5 @@ class ExtinctionFactory(FactoryBase):
"""Create extinction correction models by tag."""
_default_rules: ClassVar[dict] = {
- frozenset(): 'shelx',
+ frozenset(): 'becker-coppens',
}
diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py
index 8ba045295..af5c0f468 100644
--- a/src/easydiffraction/datablocks/experiment/item/base.py
+++ b/src/easydiffraction/datablocks/experiment/item/base.py
@@ -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:
diff --git a/src/easydiffraction/datablocks/experiment/item/enums.py b/src/easydiffraction/datablocks/experiment/item/enums.py
index 1c73f5211..0155c5491 100644
--- a/src/easydiffraction/datablocks/experiment/item/enums.py
+++ b/src/easydiffraction/datablocks/experiment/item/enums.py
@@ -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
diff --git a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py
index dcee6f6e6..17ae1cfcc 100644
--- a/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py
+++ b/tests/unit/easydiffraction/datablocks/experiment/categories/test_extinction.py
@@ -3,26 +3,58 @@
def test_module_import():
- import easydiffraction.datablocks.experiment.categories.extinction.shelx as MUT
+ import easydiffraction.datablocks.experiment.categories.extinction.becker_coppens as MUT
- expected_module_name = 'easydiffraction.datablocks.experiment.categories.extinction.shelx'
+ expected_module_name = (
+ 'easydiffraction.datablocks.experiment.categories.extinction.becker_coppens'
+ )
actual_module_name = MUT.__name__
assert expected_module_name == actual_module_name
def test_extinction_defaults():
- from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
- ext = ShelxExtinction()
+ ext = BeckerCoppensExtinction()
+ assert ext.model.value == 'gauss'
assert ext.mosaicity.value == 1.0
assert ext.radius.value == 1.0
assert ext._identity.category_code == 'extinction'
+def test_extinction_model_setter():
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
+
+ ext = BeckerCoppensExtinction()
+
+ ext.model = 'lorentz'
+ assert ext.model.value == 'lorentz'
+
+ ext.model = 'gauss'
+ assert ext.model.value == 'gauss'
+
+
+def test_extinction_model_invalid():
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
+
+ ext = BeckerCoppensExtinction()
+
+ ext.model = 'invalid'
+ assert ext.model.value == 'gauss' # keeps previous value
+
+
def test_extinction_property_setters():
- from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
- ext = ShelxExtinction()
+ ext = BeckerCoppensExtinction()
ext.mosaicity = 0.5
assert ext.mosaicity.value == 0.5
@@ -32,9 +64,14 @@ def test_extinction_property_setters():
def test_extinction_cif_handler_names():
- from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
+
+ ext = BeckerCoppensExtinction()
- ext = ShelxExtinction()
+ model_cif_names = ext._model._cif_handler.names
+ assert '_extinction.model' in model_cif_names
mosaicity_cif_names = ext._mosaicity._cif_handler.names
assert '_extinction.mosaicity' in mosaicity_cif_names
@@ -44,10 +81,12 @@ def test_extinction_cif_handler_names():
def test_extinction_type_info():
- from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
- assert ShelxExtinction.type_info.tag == 'shelx'
- assert ShelxExtinction.type_info.description != ''
+ assert BeckerCoppensExtinction.type_info.tag == 'becker-coppens'
+ assert BeckerCoppensExtinction.type_info.description != ''
def test_extinction_factory_registration():
@@ -55,17 +94,19 @@ def test_extinction_factory_registration():
ExtinctionFactory,
)
- assert 'shelx' in ExtinctionFactory.supported_tags()
+ assert 'becker-coppens' in ExtinctionFactory.supported_tags()
def test_extinction_factory_create():
from easydiffraction.datablocks.experiment.categories.extinction.factory import (
ExtinctionFactory,
)
- from easydiffraction.datablocks.experiment.categories.extinction.shelx import ShelxExtinction
+ from easydiffraction.datablocks.experiment.categories.extinction.becker_coppens import (
+ BeckerCoppensExtinction,
+ )
- ext = ExtinctionFactory.create('shelx')
- assert isinstance(ext, ShelxExtinction)
+ ext = ExtinctionFactory.create('becker-coppens')
+ assert isinstance(ext, BeckerCoppensExtinction)
def test_extinction_factory_default_tag():
@@ -73,4 +114,4 @@ def test_extinction_factory_default_tag():
ExtinctionFactory,
)
- assert ExtinctionFactory.default_tag() == 'shelx'
+ assert ExtinctionFactory.default_tag() == 'becker-coppens'
diff --git a/tmp/unsorted/show_w505.py b/tmp/unsorted/show_w505.py
index 6e606803e..5f8e247f5 100644
--- a/tmp/unsorted/show_w505.py
+++ b/tmp/unsorted/show_w505.py
@@ -3,7 +3,7 @@
('src/easydiffraction/datablocks/experiment/categories/data/bragg_sc.py', [29,298,422]),
('src/easydiffraction/datablocks/experiment/categories/data/total_pd.py', [207]),
('src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py', [30]),
- ('src/easydiffraction/datablocks/experiment/categories/extinction/shelx.py', [20]),
+ ('src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py', [20]),
('src/easydiffraction/datablocks/experiment/item/base.py', [71,604]),
('src/easydiffraction/datablocks/experiment/item/factory.py', [78]),
('src/easydiffraction/datablocks/structure/item/base.py', [248]),