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]),