Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3ac5c66
Promote model-sample-absorption ADR to accepted
AndrewSazonov Jun 12, 2026
a87a685
Add model-sample-absorption implementation plan
AndrewSazonov Jun 12, 2026
c35475c
Add absorption switchable category package
AndrewSazonov Jun 12, 2026
4c5b3c4
Wire absorption category into powder experiment owner
AndrewSazonov Jun 12, 2026
616f86d
Add Hewat cylindrical absorption factor helper
AndrewSazonov Jun 12, 2026
d91bb05
Restrict cylinder-hewat absorption to constant wavelength
AndrewSazonov Jun 12, 2026
d49eae1
Apply absorption correction in cryspy and crysfml backends
AndrewSazonov Jun 12, 2026
f701b2f
Confirm absorption category round-trips in experiment CIF
AndrewSazonov Jun 12, 2026
3a431f5
Document sample-absorption parameters
AndrewSazonov Jun 12, 2026
7f68eef
Reach Phase 1 review gate
AndrewSazonov Jun 12, 2026
430184c
Export absorption metadata in IUCr powder report
AndrewSazonov Jun 12, 2026
3964d0e
Keep absorption type consistent when CIF tag is rejected
AndrewSazonov Jun 12, 2026
6e84f7f
Point plan ADR link at the accepted ADR
AndrewSazonov Jun 12, 2026
8e05f8d
Apply pixi run fix auto-fixes
AndrewSazonov Jun 12, 2026
a6bda97
Add unit tests for absorption category and helper
AndrewSazonov Jun 12, 2026
9139afb
Restore literal formula text in implementation plan
AndrewSazonov Jun 12, 2026
29e64b0
Test absorption application in cryspy and crysfml backends
AndrewSazonov Jun 12, 2026
b7f4b23
Model absorption in LaB6 verification page
AndrewSazonov Jun 12, 2026
9c08155
Assign exception message in crysfml absorption test
AndrewSazonov Jun 12, 2026
11e50c5
Document scoped outcome for absorption verification page
AndrewSazonov Jun 12, 2026
8374e7c
Add LaB6 no-FCJ verification page demonstrating absorption
AndrewSazonov Jun 12, 2026
3475025
Reference no-FCJ absorption demonstration page in plan
AndrewSazonov Jun 12, 2026
0f50238
Record linux-x86_64 tutorial benchmark run
AndrewSazonov Jun 12, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status

Proposed.
Accepted.

## Date

Expand Down Expand Up @@ -144,6 +144,18 @@ The owner exposes only `experiment.absorption` and a private
`_swap_absorption` hook (Family A: the hook **replaces the category
instance** when `type` changes), exactly as `extinction` does today.

**Owner scope — Bragg powder experiments only.** The category is
attached only when the experiment type is `sample_form = powder` **and**
`scattering_type = bragg` (the cryspy/crysfml Bragg backends). Total-
scattering / pdffit experiments **do not expose `experiment.absorption`
at all** — there is no `none` instance, no `_absorption.*` block, and
accessing the attribute raises as for any absent category. This mirrors
`extinction`, which is attached only for single-crystal experiments and
is simply absent otherwise; attachment is governed by the same
`Compatibility` gate, not by a runtime no-op. Consequently the `none`
type's calculator list is `cryspy, crysfml` (the Bragg backends) and
deliberately excludes `pdffit`.

### 2. Application point — a pointwise envelope on the calculated pattern

Because A(θ) is a **slowly-varying smooth envelope** of sin²θ (its
Expand Down Expand Up @@ -182,10 +194,14 @@ category by registering a class (see Deferred Work).

Notes:

- `none` is the **default** (A ≡ 1). The category always exists for
powder so the user can discover and switch it on via
`show_supported()`; opting out is `type = 'none'`, not deleting the
category. (`scattering_type = 'total'` / pdffit is out of scope.)
- `none` is the **default** (A ≡ 1). The category exists for every
**Bragg powder** experiment (see §1 "Owner scope") so the user can
discover and switch it on via `show_supported()`; opting out is
`type = 'none'`, not deleting the category. Total-scattering / pdffit
experiments do not get the category at all (not even `none`), so the
`any` in the `none` row's beam-mode/sample-form columns is bounded by
that owner gate — it means "any Bragg-powder beam mode", not literally
every experiment type.
- **Why a single built type is correct now:** the FullProf example suite
uses only cylindrical absorption (§"Evidence from the FullProf example
suite"), and our sole verification reference is CW cylindrical (LaB₆,
Expand All @@ -201,10 +217,37 @@ Notes:
neutron vs X-ray. X-ray simply tends to larger μ; the same formula
applies. Both are supported.
- The future rows are designed (tags, parameters, CIF) but **not
built**. TOF in particular is λ-dependent and the slow-envelope
argument does not carry over unchanged (each detector bin mixes
wavelengths), so it needs its own application path; it is listed so
the taxonomy and CIF tags are settled once.
built**. They all share the stable category/swap contract; only the
TOF rows additionally need a new calculation path — see §3a.

### 3a. Stable category contract vs. per-mode calculation contract

These two contracts are intentionally separate, so the switchable
category can be extended without churn while the calculation layer grows
only as physics demands:

- **Category/swap contract — stable across _all_ types.** Every type
(CWL or TOF, Phase 1 or future) is a class registered on
`AbsorptionFactory`, selected through `experiment.absorption.type`,
swapped by the single `_swap_absorption` hook. Adding a type never
changes the category, the hook, the selector surface, or the CIF
identity tag. This is what the "no rework" claim refers to.
- **Calculation contract — per beam mode.** _How_ a type turns its
parameters into an applied correction is **not** uniform:
- **CWL types** (`cylinder-hewat`, `cylinder-lobanov`, `flat-plate`)
share the §2 helper `factor(two_theta, params) → A(2θ)`, applied as
a pointwise 2θ envelope. Adding a CWL form is just another branch in
that helper.
- **TOF types** (`tof-cylinder`, `tof-exponential`) are
wavelength-dependent: at fixed scattering angle a TOF bin mixes
wavelengths, so a pure A(2θ) envelope is wrong. They will need a
distinct `factor_tof(...)` application path keyed on λ (or d-spacing
/ TOF). Phase 1 does **not** build this path; it is added with the
first TOF type, behind the same category/swap contract.

So adding a TOF form leaves the category contract untouched (per the
first bullet) but **does** extend the calculation layer (a new
application path) — the Deferred Work note reflects exactly this split.

### 4. The μR parameter

Expand All @@ -215,6 +258,27 @@ FullProf's `muR` and CrysFML's `tmv`. It is a `Parameter`
separately is rejected (see Alternatives); they can be added later as
read-only provenance (Deferred Work).

**Out-of-range policy (boundary user input — no silent failure).** The
Hewat expansion is validated only to μR ≲ 1.5, yet `RangeValidator`
alone would silently evaluate it at any μR ≥ 0. Phase 1 therefore adds
an explicit, documented policy on `cylinder-hewat`:

- **Hard floor only in the validator:** `RangeValidator(ge=0.0)` — no
hard upper bound. A hard ceiling is rejected because legitimate real
examples reach μR = 1.28 (dy*, DyMnGe*; §"Evidence"), and a
characterised sample may sit slightly higher; erroring would block
valid use.
- **Warn above the validated ceiling:** when `mu_r` is set (or refined)
above **1.5**, the category emits a single `log.warning` stating that
μR exceeds the Hewat-validated range and that the (deferred)
`cylinder-lobanov` form should be used once available. The pattern is
still computed (extrapolated), so workflows do not break, but the user
is never silently handed an out-of-range result.
- This warn-not-fail choice is the boundary-input handling required by
[`AGENTS.md`](../../../../AGENTS.md) §Project Context, applied at the
public-API edge; the 1.5 threshold is a single named constant so the
future `cylinder-lobanov` type can raise/redirect coherently.

`flat-plate` uses `mu_t` (μ·thickness); the TOF exponential form uses
`coeff` and `exp` (`A = exp(−coeff·λ^exp)`).

Expand Down Expand Up @@ -350,9 +414,19 @@ backend round-trip.
## Deferred Work

All of these are designed into the taxonomy and CIF tags above but **not
built in Phase 1** — each is a new class registered on the same
`AbsorptionFactory`, gated by `Compatibility`/`CalculatorSupport`, with
no change to the category, the swap hook, or the application helper.
built in Phase 1**. Two contracts must be kept separate (see §3a "Stable
vs. calculation contract"):

- **Category/swap contract (stable for all future types):** each is a
new class registered on the same `AbsorptionFactory`, gated by
`Compatibility`/`CalculatorSupport`, with **no change to the category,
the `_swap_absorption` hook, or the selector surface**.
- **Calculation contract (per beam mode):** CWL types reuse the shared
2θ helper `factor(two_theta, params)`. **TOF types do not** — they
require a separate wavelength-aware application path (§3a), so adding
a TOF form _does_ extend the calculation layer even though it leaves
the category/swap contract untouched.

None appears in the FullProf example suite (only the cylinder does), so
none is urgent; each should land **with a verification dataset**, not on
spec alone.
Expand Down
1 change: 1 addition & 0 deletions docs/dev/adrs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ folders.
| Experiment model | Accepted | Automatic Line-Segment Background Estimation | Detects line-segment background control points from the measured pattern, peak-insensitive and editable. | [`background-auto-estimate.md`](accepted/background-auto-estimate.md) |
| Experiment model | Accepted | Calculation Without Measured Data | Adds a writable `data_range` category so a structure-only experiment is calculable and plottable without loaded data. | [`calculation-without-measured-data.md`](accepted/calculation-without-measured-data.md) |
| Experiment model | Accepted | Preferred-Orientation Category | Adds a per-phase March–Dollase preferred-orientation category for textured powder refinement on the CrysPy backend. | [`preferred-orientation-category.md`](accepted/preferred-orientation-category.md) |
| Experiment model | Accepted | Model Sample Absorption (Debye–Scherrer, μR) | Switchable `absorption` category applying a calculator-independent cylindrical Hewat A(θ) envelope for powder samples. | [`model-sample-absorption.md`](accepted/model-sample-absorption.md) |
| Factories | Accepted | Factory Contracts and Metadata | Standardizes factory construction, metadata, compatibility, and registration behavior. | [`factory-contracts.md`](accepted/factory-contracts.md) |
| Naming | Accepted | Factory Tag Naming | Defines canonical factory tag style and standard abbreviations. | [`factory-tag-naming.md`](accepted/factory-tag-naming.md) |
| Persistence | Accepted | Free-Flag CIF Encoding | Encodes fit free/fixed state through CIF uncertainty syntax instead of a separate free list. | [`free-flag-cif-encoding.md`](accepted/free-flag-cif-encoding.md) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
tutorial_name,elapsed_seconds,status
ed-1.py,29.439,ok
ed-2.py,33.644,ok
ed-3.py,59.676,ok
ed-4.py,10.615,ok
ed-5.py,110.183,ok
ed-6.py,203.854,ok
ed-7.py,426.070,ok
ed-8.py,431.961,ok
ed-9.py,24.443,ok
ed-10.py,108.541,ok
ed-11.py,26.034,ok
ed-12.py,22.227,ok
ed-13.py,51.865,ok
ed-14.py,41.654,ok
ed-15.py,73.293,ok
ed-16.py,228.713,ok
ed-17.py,209.935,ok
ed-18.py,14.420,ok
ed-20.py,58.465,ok
ed-21.py,273.340,ok
ed-22.py,196.221,ok
ed-23.py,59.115,ok
ed-24.py,9.213,ok
ed-25.py,72.704,ok
ed-26.py,88.104,ok
ed-27.py,9.413,ok
ed-28.py,9.813,ok
ed-29.py,8.237,ok
2 changes: 1 addition & 1 deletion docs/dev/issues/open.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ A(θ) = exp( -(1.7133 − 0.0368·sin²θ)·μR + (0.0927 + 0.375·sin²θ)·μR
A Lobanov–Alte-da-Veiga form covers `μR > 3`.

**Design:** captured in
[`adrs/suggestions/model-sample-absorption.md`](../adrs/suggestions/model-sample-absorption.md)
[`adrs/accepted/model-sample-absorption.md`](../adrs/accepted/model-sample-absorption.md)
— a switchable `experiment.absorption` category (mirroring `extinction`)
with a calculator-independent A(θ) envelope.

Expand Down
14 changes: 13 additions & 1 deletion docs/dev/package-structure/full.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
├── 📁 analysis
│ ├── 📁 calculators
│ │ ├── 📄 __init__.py
│ │ ├── 📄 absorption.py
│ │ ├── 📄 base.py
│ │ │ ├── 🏷️ class PowderReflnRecord
│ │ │ └── 🏷️ class CalculatorBase
Expand Down Expand Up @@ -274,6 +275,16 @@
├── 📁 datablocks
│ ├── 📁 experiment
│ │ ├── 📁 categories
│ │ │ ├── 📁 absorption
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 base.py
│ │ │ │ │ └── 🏷️ class AbsorptionBase
│ │ │ │ ├── 📄 cylinder_hewat.py
│ │ │ │ │ └── 🏷️ class CylinderHewatAbsorption
│ │ │ │ ├── 📄 factory.py
│ │ │ │ │ └── 🏷️ class AbsorptionFactory
│ │ │ │ └── 📄 none.py
│ │ │ │ └── 🏷️ class NoAbsorption
│ │ │ ├── 📁 background
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 base.py
Expand Down Expand Up @@ -450,7 +461,8 @@
│ │ │ │ ├── 🏷️ class BeamModeEnum
│ │ │ │ ├── 🏷️ class CalculatorEnum
│ │ │ │ ├── 🏷️ class PeakProfileTypeEnum
│ │ │ │ └── 🏷️ class ExtinctionModelEnum
│ │ │ │ ├── 🏷️ class ExtinctionModelEnum
│ │ │ │ └── 🏷️ class AbsorptionTypeEnum
│ │ │ ├── 📄 factory.py
│ │ │ │ └── 🏷️ class ExperimentFactory
│ │ │ └── 📄 total_pd.py
Expand Down
7 changes: 7 additions & 0 deletions docs/dev/package-structure/short.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
├── 📁 analysis
│ ├── 📁 calculators
│ │ ├── 📄 __init__.py
│ │ ├── 📄 absorption.py
│ │ ├── 📄 base.py
│ │ ├── 📄 crysfml.py
│ │ ├── 📄 cryspy.py
Expand Down Expand Up @@ -128,6 +129,12 @@
├── 📁 datablocks
│ ├── 📁 experiment
│ │ ├── 📁 categories
│ │ │ ├── 📁 absorption
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 base.py
│ │ │ │ ├── 📄 cylinder_hewat.py
│ │ │ │ ├── 📄 factory.py
│ │ │ │ └── 📄 none.py
│ │ │ ├── 📁 background
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 base.py
Expand Down
Loading
Loading