Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
5084800
Add test-suite-and-validation ADR suggestion
AndrewSazonov Jun 5, 2026
472a0dd
Update test-suite-and-validation ADR and index from review
AndrewSazonov Jun 5, 2026
c4d0532
Add test-suite-and-validation implementation plan
AndrewSazonov Jun 5, 2026
36609da
Make codecov patch informational and gate project at 80%
AndrewSazonov Jun 5, 2026
4db5ee9
Replace fast marker with pr and nightly test tiers
AndrewSazonov Jun 5, 2026
f1710a7
Select test tiers per trigger across all test layers
AndrewSazonov Jun 5, 2026
29701b7
Gate unit-test structure check on shared src tree walk
AndrewSazonov Jun 5, 2026
8f68124
Document strict test layer placement criteria
AndrewSazonov Jun 5, 2026
906f20d
Relocate network and engine tests to correct layers
AndrewSazonov Jun 5, 2026
6bfc73d
Document integration layer defaults to pr tier
AndrewSazonov Jun 5, 2026
31f8882
Add hypothesis deterministic profile and shared test fixtures
AndrewSazonov Jun 5, 2026
146ad57
Add property-based input-domain tests for validators
AndrewSazonov Jun 6, 2026
dc90a36
Raise coverage fail_under to 80 percent
AndrewSazonov Jun 6, 2026
aacc3e6
Add strict docs build and spell check on every push
AndrewSazonov Jun 6, 2026
60b7d8a
Add calculator support matrix query for verification
AndrewSazonov Jun 6, 2026
8cc7e2a
Add cross-engine verification comparison pages and script wiring
AndrewSazonov Jun 6, 2026
0c9ffb7
Add per-experiment performance benchmarks (nightly)
AndrewSazonov Jun 6, 2026
2dcb98d
Record cross-repo nightly harness and benchmarks as future work
AndrewSazonov Jun 6, 2026
d025df7
Promote test-suite-and-validation ADR to accepted
AndrewSazonov Jun 6, 2026
78450c4
Reach Phase 1 review gate
AndrewSazonov Jun 6, 2026
a2a23a3
Run pr tier on pull_request events, not only main branches
AndrewSazonov Jun 6, 2026
7ccd28f
Share excluded-dirs set between structure check and docs
AndrewSazonov Jun 6, 2026
5edcc7b
Add lychee link check to the docs gate
AndrewSazonov Jun 6, 2026
f9a1730
Run verification notebooks in notebook test and exec tasks
AndrewSazonov Jun 6, 2026
0262ed3
Assert explicit tolerances for all verification metrics
AndrewSazonov Jun 6, 2026
eb3aee0
Run full suite across all tiers in the nightly workflow
AndrewSazonov Jun 6, 2026
7d02a24
Scope verification to CWL powder now, defer rest to issue 115
AndrewSazonov Jun 6, 2026
64168ae
Lint and format verification pages like tutorials
AndrewSazonov Jun 6, 2026
81e521d
Apply pixi run fix auto-fixes and resolve lint findings
AndrewSazonov Jun 6, 2026
7ce29db
Tighten cross-engine verification tolerances from measurement
AndrewSazonov Jun 6, 2026
4f4de17
Render small canvas in raster tests to speed them up
AndrewSazonov Jun 6, 2026
83bd6a5
Add icon to the Verification docs section
AndrewSazonov Jun 6, 2026
ede35b7
Fix tabel typo in docs CSS comments
AndrewSazonov Jun 6, 2026
9bc03a2
Fix promoted ADR links to documentation-ci-build
AndrewSazonov Jun 6, 2026
54b6d48
Scope codespell and lychee to pass on real sources
AndrewSazonov Jun 6, 2026
bb2c967
Run docs checks (spell, link, strict build) with the checks
AndrewSazonov Jun 6, 2026
2dbb6f0
Reflow ADR prose after link fix
AndrewSazonov Jun 6, 2026
2f2a40d
Make check static-only and add full pre-PR all task
AndrewSazonov Jun 6, 2026
c1afbd2
Always build docs strictly; drop docs-build-strict
AndrewSazonov Jun 6, 2026
aee2a76
Gate docstring coverage as a check, not in coverage.yml
AndrewSazonov Jun 6, 2026
ba8008c
Add cross-engine verification helper module
AndrewSazonov Jun 6, 2026
fccca5b
Add pattern_comparison display method
AndrewSazonov Jun 6, 2026
3a1fbe5
Add FullProf cross-engine verification pages
AndrewSazonov Jun 6, 2026
0ee3430
Gate cryspy TOF Jorgensen-Von Dreele discrepancy
AndrewSazonov Jun 6, 2026
4cfc6d9
Exclude FullProf reference data from codespell
AndrewSazonov Jun 6, 2026
7ee58ce
Add CI-skip list for verification notebooks
AndrewSazonov Jun 6, 2026
7ed455a
Drop failing cryspy TOF gate; pages show the discrepancy
AndrewSazonov Jun 6, 2026
fd54a76
Group verification pages by experiment type
AndrewSazonov Jun 6, 2026
151977c
Fix comparison hover and residual labels to match legend
AndrewSazonov Jun 6, 2026
9107c48
Fix stale strict-gate notes on TOF verification pages
AndrewSazonov Jun 6, 2026
cfafd97
Validate comparison arrays against the experiment x grid
AndrewSazonov Jun 6, 2026
7d8054b
Strip trailing whitespace from si_tof.sub header
AndrewSazonov Jun 6, 2026
250d262
Wire relocated skip conftest to verification ci_skip
AndrewSazonov Jun 6, 2026
b15333f
Describe verification engines once, not per page
AndrewSazonov Jun 6, 2026
1878442
List peak profile and asymmetry per verification page
AndrewSazonov Jun 6, 2026
bf59273
Add skipped LaB6 SyCos/SySin verification skeleton
AndrewSazonov Jun 6, 2026
f153277
Add calculation-without-measured-data ADR suggestion
AndrewSazonov Jun 7, 2026
f68fa3e
Add reflection comparison plot and fix residual hover
AndrewSazonov Jun 7, 2026
f854a67
Add single-crystal verification helpers and parsers
AndrewSazonov Jun 7, 2026
be1e585
Restructure verification suite and relocate references
AndrewSazonov Jun 7, 2026
7b0aae7
Add FullProf reference comparison to LBCO page
AndrewSazonov Jun 7, 2026
ef00fe7
Add FullProf examples
AndrewSazonov Jun 7, 2026
f79c0e0
Remove empty notebook cell and trailing comment
AndrewSazonov Jun 7, 2026
9e39660
Update verification CI skip list
AndrewSazonov Jun 7, 2026
71c3b36
Reflow documentation and comments
AndrewSazonov Jun 7, 2026
7f17a0a
Merge branch 'develop' into more-validation-notebooks
AndrewSazonov Jun 7, 2026
d3d439e
Remove unused publication and rendering directories from package stru…
AndrewSazonov Jun 7, 2026
a36c9b8
Record lint-rule audit decisions as an accepted ADR
AndrewSazonov Jun 7, 2026
ebb2651
Remove implemented plans from docs/dev/plans
AndrewSazonov Jun 7, 2026
947b9c2
Fix ci_skip stem so asymmetry page is skipped in CI
AndrewSazonov Jun 8, 2026
f3f7436
Add SC verification tests and FullProf length validation
AndrewSazonov Jun 8, 2026
f2d750e
Fix engine order and drop empty cell in Si page
AndrewSazonov Jun 8, 2026
e45be3e
Clarify LaB6 verification page features are not yet applied
AndrewSazonov Jun 8, 2026
b8141e3
Add before/after refinement closeness helper
AndrewSazonov Jun 8, 2026
6378eaf
Investigate PbSO4 asymmetry disagreement by refinement
AndrewSazonov Jun 8, 2026
f3cfeaa
Add live-notebook Plotly loader implementation plan
AndrewSazonov Jun 8, 2026
8d2d06e
Vendor Plotly bundle and figure loader into the package
AndrewSazonov Jun 8, 2026
2fe65ea
Expose re-callable edFigures.activate for live notebooks
AndrewSazonov Jun 8, 2026
11fe671
Exclude vendored plot assets from prettier
AndrewSazonov Jun 8, 2026
05002e3
Add renderSpec direct-render entry to the figure loader
AndrewSazonov Jun 8, 2026
6a8a4c8
Render live figures through the self-hosted loader
AndrewSazonov Jun 8, 2026
1585cf9
Record live-notebook Plotly delivery options as issue 117
AndrewSazonov Jun 8, 2026
69678e8
Mark live-notebook Plotly loader Phase 1 complete
AndrewSazonov Jun 8, 2026
8065247
Trim top margin of live-notebook figures
AndrewSazonov Jun 8, 2026
ac61d83
Preload Plotly runtime on import for instant live plots
AndrewSazonov Jun 8, 2026
1a4e056
Revert "Preload Plotly runtime on import for instant live plots"
AndrewSazonov Jun 8, 2026
89bbd7e
Skip resize observer for live-notebook figures
AndrewSazonov Jun 8, 2026
049c39f
Reserve explicit figure height for JupyterLab windowing
AndrewSazonov Jun 8, 2026
7813d5f
Defer live figure render until the container has a size
AndrewSazonov Jun 8, 2026
0c9bf07
Revert VISA-specific live-figure tweaks, keep self-hosted runtime
AndrewSazonov Jun 8, 2026
a45549d
Record VISA Plotly empty-rows investigation as issue 118
AndrewSazonov Jun 8, 2026
71079d1
Compare verification patterns on absolute scale
AndrewSazonov Jun 8, 2026
cf35934
Plot verification comparisons at absolute scale
AndrewSazonov Jun 8, 2026
1036392
Update verification metric tests for absolute scale
AndrewSazonov Jun 8, 2026
8a7723d
Rework verification pages for absolute-scale comparison
AndrewSazonov Jun 8, 2026
25c4257
Add verification absolute-scale plan
AndrewSazonov Jun 8, 2026
c35282c
Add new validation example
AndrewSazonov Jun 8, 2026
ad6ae4d
Update LaB6 example
AndrewSazonov Jun 8, 2026
2fb5ca9
Build FullProf .sub grid from the intensity count
AndrewSazonov Jun 8, 2026
55cc1b3
Add Jorgensen TOF Si verification page
AndrewSazonov Jun 8, 2026
bcff16c
Make report_refinement_closeness display-only
AndrewSazonov Jun 8, 2026
125d67a
Update notebooks
AndrewSazonov Jun 8, 2026
4f17536
Update fullprof projects
AndrewSazonov Jun 8, 2026
9c230be
Add verification-exec task to run verification notebooks
AndrewSazonov Jun 8, 2026
9705a1d
xfail rather than skip CI-listed verification notebooks
AndrewSazonov Jun 8, 2026
696b764
Make verification pages consistent in structure
AndrewSazonov Jun 8, 2026
f1f3768
Refactor comments for clarity in structure definition
AndrewSazonov Jun 8, 2026
ab00f06
Reflow comments and docs to line-length limit
AndrewSazonov Jun 8, 2026
fd5c676
Guard FullProf profile loader against empty input
AndrewSazonov Jun 8, 2026
750e9a7
Remove implemented plans from docs/dev/plans
AndrewSazonov Jun 8, 2026
8a7c9cd
Align empty-input error message and test
AndrewSazonov Jun 8, 2026
747fc73
Guard empty profile body before genfromtxt
AndrewSazonov Jun 8, 2026
f6f878d
Make bundled_reference_dir test CWD-independent
AndrewSazonov Jun 8, 2026
7ec746f
Add issue 119: rename asym_empir and add FCJ model
AndrewSazonov Jun 8, 2026
9fb0ed2
Gate TOF scale-only verification pages as strict pass
AndrewSazonov Jun 8, 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
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ build/
# MkDocs
docs/site/

# Generated docs-serving copy of the canonical Three.js (synced from
# src/ by `pixi run docs-sync-vendored-js`; the source of truth is src/)
# Generated docs-serving copies of the canonical JS runtimes and the
# shared figure loader (synced from src/ by `pixi run
# docs-sync-vendored-js`; the source of truth is src/)
docs/docs/assets/javascripts/vendor/threejs/
docs/docs/assets/javascripts/vendor/plotly/
docs/docs/assets/javascripts/ed-figures.js

# Jupyter Notebooks
.ipynb_checkpoints
Expand Down
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ node_modules

# Vendored snapshots
src/easydiffraction/display/structure/renderers/vendor/
src/easydiffraction/display/plotters/vendor/
src/easydiffraction/report/templates/html/vendor/
src/easydiffraction/report/templates/tex/styles/
src/easydiffraction/utils/_vendored/jupyter_dark_detect/

# Figure loader served verbatim (same as its docs/docs/assets/ origin)
src/easydiffraction/display/plotters/assets/

# Tox
.tox

Expand Down
79 changes: 79 additions & 0 deletions docs/dev/adrs/accepted/lint-rule-exceptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# ADR: Lint Rule Scope and Test-File Exceptions

## Status

Accepted.

## Date

2026-06-08

## Group

Quality.

## Context

A full audit of ruff's disabled rules (merged in #194) adopted a
low-risk subset and recorded which remaining suppressions are
deliberate. Three of those suppressions are standing policy rather than
temporary noise, and the audit flagged each as needing a recorded
decision because it either scopes or extends the
[`lint-complexity-thresholds.md`](lint-complexity-thresholds.md) ADR
(which treats PLR complexity limits as guardrails that must not be
silenced):

- The PLR complexity rules `PLR0913`, `PLR0914`, `PLR0915`, and
`PLR0917` are ignored for `tests/**`.
- `N812` (lowercase import alias) is ignored for `tests/**`.
- The flake8-builtins `A` family is not enabled at all, so CIF-aligned
field names such as `id` and `type` do not trip builtin-shadowing
rules.

The audit document itself was a one-time roadmap and is not retained;
this ADR records the durable decisions it surfaced.

## Decision

1. **Test-file complexity exception.** `PLR0913`, `PLR0914`, `PLR0915`,
and `PLR0917` stay ignored under `tests/**` via
`[tool.ruff.lint.per-file-ignores]`. This is a deliberate, scoped
exception to `lint-complexity-thresholds.md`: test bodies
legitimately accumulate many arguments, locals, and statements
(fixtures, parametrisation, arrange-act-assert) where the complexity
is not a maintainability signal. Production code under `src/**`
remains fully governed by `lint-complexity-thresholds.md` — the
guardrail is not relaxed there, and `# noqa` / threshold raises
remain disallowed in `src/**`.

2. **Test-file import-alias exception.** `N812` stays ignored under
`tests/**` so tests may import a module-under-test with a lowercase
alias (the `MUT` / `mut` idiom) without renaming convention churn.

3. **CIF-aligned builtin names.** The flake8-builtins `A` family stays
disabled project-wide so categories can use the CIF-aligned field
names `id` and `type` (mandated by IUCr CIF tag alignment) without
builtin-shadowing warnings. Renaming these would break CIF
correspondence; the lint cost is not worth the divergence.

These exceptions are the complete set of standing PLR/naming/builtin
suppressions. Any further permanent suppression that conflicts with or
extends `lint-complexity-thresholds.md` needs its own recorded decision
before adoption.

## Consequences

### Positive

- The standing test-file and CIF-naming suppressions are documented
decisions rather than unexplained `pyproject.toml` entries.
- `lint-complexity-thresholds.md` keeps its full force over `src/**`;
the scope of the relaxation is explicit and bounded to `tests/**`.

### Trade-offs

- Test code can grow more complex without lint feedback; reviewers carry
that judgement instead of the linter.
- The `id` / `type` field names continue to shadow builtins by design,
so contributors must keep CIF-alignment context in mind when reading
those categories.
2 changes: 2 additions & 0 deletions docs/dev/adrs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ folders.
| Documentation | Suggestion | Documentation CI and Build Verification | Proposes strict MkDocs builds, API-derived docs, snippet smoke tests, link checks, and prose/spelling checks. | [`documentation-ci-build.md`](suggestions/documentation-ci-build.md) |
| Experiment model | Accepted | Immutable Experiment Type | Makes experiment type axes creation-time state rather than mutable runtime state. | [`immutable-experiment-type.md`](accepted/immutable-experiment-type.md) |
| 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 | Suggestion | 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`](suggestions/calculation-without-measured-data.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 All @@ -44,6 +45,7 @@ folders.
| Persistence | Accepted | IUCr CIF Tag Alignment | Aligns default CIF tags with IUCr dictionaries and adds a clean IUCr-aligned report export. | [`iucr-cif-tag-alignment.md`](accepted/iucr-cif-tag-alignment.md) |
| Persistence | Accepted | Python and CIF Category Correspondence | Compares current Python paths and CIF tags, then records scoped one-to-one mapping for project-level categories. | [`python-cif-category-correspondence.md`](accepted/python-cif-category-correspondence.md) |
| Quality | Accepted | Lint Complexity Thresholds | Treats ruff PLR complexity limits as design guardrails that should not be bypassed. | [`lint-complexity-thresholds.md`](accepted/lint-complexity-thresholds.md) |
| Quality | Accepted | Lint Rule Scope and Test-File Exceptions | Records the standing tests/\*\* PLR/N812 ignores and CIF-aligned `id`/`type` builtin exception from the lint audit. | [`lint-rule-exceptions.md`](accepted/lint-rule-exceptions.md) |
| Quality | Accepted | Test Strategy | Defines layered unit, functional, integration, script, and notebook testing. | [`test-strategy.md`](accepted/test-strategy.md) |
| Quality | Accepted | Test Suite and Validation Strategy | Strict test layers, cost tiers, coverage/codecov policy, cross-engine verification docs, and a nightly validation harness. | [`test-suite-and-validation.md`](accepted/test-suite-and-validation.md) |
| Structure model | Accepted | Type-Neutral ADP Parameters | Keeps ADP parameter object identities stable across B/U and iso/ani switches. | [`type-neutral-adp-parameters.md`](accepted/type-neutral-adp-parameters.md) |
Expand Down
183 changes: 183 additions & 0 deletions docs/dev/adrs/suggestions/calculation-without-measured-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# ADR: Calculation Without Measured Data

## Status

Proposed.

## Date

2026-06-06

## Group

Experiment model.

## Context

An experiment built with `ExperimentFactory.from_scratch(...)` has no
data points. The x-grid that every calculation runs on — the set of 2θ
or time-of-flight values — lives inside `experiment.data` and is only
ever populated by loading measured data
(`_create_items_set_xcoord_and_id`). With no grid, a calculation cannot
run:

- cryspy derives its scan range from `experiment.data.x.min()/max()`
(`_cif_range_section`), which raises
`ValueError: zero-size array to reduction operation minimum` on the
empty array.
- crysfml passes `experiment.data.x.tolist()` as the scan, i.e. an empty
scan.

So `project.display.pattern(expt_name=...)` fails for a structure that
has never been measured, even though everything needed to _calculate_ a
pattern (structure, instrument, peak shape, background) is present.

Scientists routinely want to **simulate** a pattern — or, for a single
crystal, per-reflection F² — from a structural model alone: choose a
range, calculate, and view the calculated curve with its background and
Bragg reflections, with no measurement to load or invent. This is also
how a saved project with no measured block should restore from the CLI.

Two existing decisions frame the solution:

- [Unified Pattern View](../accepted/pattern-display-unification.md)
already establishes that `pattern()` renders whatever the project
state supports. "Only calculated data is available" should simply be
one more supported state; today the display gates instead require
measured data before background or Bragg can appear.
- The IUCr powder and core dictionaries already model an evenly-spaced
scan **by range**: `_pd_meas.2theta_range_{min,max,inc}` and
`_pd_proc.2theta_range_{min,max,inc}` are defined to be used "in place
of the `2theta_scan` values" for constant-step data, and
`_refln.sin_theta_over_lambda` / `_refln.d_spacing` are
instrument-independent reciprocal coordinates shared by powder and
single-crystal data.

## Decision

Introduce a `data_range` category that defines the reciprocal-space
region (and, for powder, the profile step) to calculate over, and let
the calculation and display paths fall back to it whenever no measured
scan exists.

1. **New category, type-determined.** `data_range` is a flat sibling of
`data`, with per-type concrete classes created through a factory
(`CwlDataRange`, `TofDataRange`, `ScDataRange`) and exposed uniformly
as `experiment.data_range`. It is fixed by the experiment type, so it
has **no** `type` selector — the same treatment
[Switchable Category API](../accepted/switchable-category-api.md)
prescribes for fixed, single-type categories, and the same pattern
`instrument` already uses for its per-beam-mode classes
([Immutable Experiment Type](../accepted/immutable-experiment-type.md)).

2. **Stored truth is the natural input axis (writable).** The values a
user sets and that serialise to CIF are the experiment's own axis:
- CWL powder: `two_theta_{min,max,inc}`
- TOF powder: `time_of_flight_{min,max,inc}`
- Single crystal: `sin_theta_over_lambda_{min,max}` (no `inc`)

3. **sinθ/λ is the derived shared currency.** Every type also exposes
`sin_theta_over_lambda` and `d_spacing` (related by
`sinθ/λ = 1/(2·d)`, instrument-free), plus `x_{min,max,step}` aliases
onto the active axis (mirroring the existing plotting x-array alias).
Generic code — plotting and reflection generation — reads sinθ/λ. For
CWL and TOF the sinθ/λ and d views derive from the stored axis
through the instrument (λ for 2θ, DIFC/DIFA for TOF), so
**recalibration keeps the stored axis window fixed and re-derives
sinθ/λ**. For single crystal there is no measurement axis, so sinθ/λ
is itself the stored truth.

4. **Bounds bound generation; step is powder-only.** `min`/`max` (as
sinθ/λ) bound reflection generation — powder through the engine's
2θ/TOF range, single crystal through cryspy
`Crystal.calc_hkl(sthovl_max)`, which enumerates hkl with the space
group's systematic absences applied. `inc` is the profile point
spacing on the measurement axis and exists only for powder; a single
crystal has bounds but no step.

5. **Writable, guarded by measurement.** Following
[Guarded Public Properties](../accepted/guarded-public-properties.md),
the `data_range` axis attributes are writable public properties. The
setter raises when a measured scan is present, because then the range
is an _observed_ property of the data rather than an input; the
getter returns the measured-derived range in that case (subsuming
today's `experiment.measured_range`) and the stored or default range
otherwise. Loaders and project restore seed values through a private
`_set_`.

6. **Defaults authored in d-spacing.** Default ranges are stored in
d-spacing and projected onto each axis through the instrument, so a
`from_scratch` experiment is calculable with no manual setup and the
TOF default — meaningless in absolute µs without calibration — stays
well defined.

7. **No `simulate()` method.** Accessing or plotting `data` (powder) or
`refln` (single crystal) with no measured scan builds the grid or
reflection list from `data_range` and calculates on it. The grid is
model state, not a one-shot action, so it is expressed as a
serialisable category rather than a method call.

8. **Display extends the unified view.** Building on
[Unified Pattern View](../accepted/pattern-display-unification.md),
`background` and `bragg` become available with calculated-only data —
the measured-data requirement in their availability gates is dropped.
"No measurement" is represented as _absent_ intensities (not a
zero-filled array), so no phantom measured curve or residual is
drawn. A calc-only powder view is the calculated curve plus
background on the main panel and a Bragg row; a calc-only
single-crystal view shows per-reflection calculated intensities.

9. **CIF mapping.** CWL bounds reuse the standard
`_pd_meas.2theta_range_{min,max,inc}`. TOF, single-crystal, and the
sinθ/λ–d bounds have no standard range tag, so custom tags are chosen
in line with
[IUCr CIF Tag Alignment](../accepted/iucr-cif-tag-alignment.md) and
[Python and CIF Category Correspondence](../accepted/python-cif-category-correspondence.md).

## Consequences

- A structure-only experiment can be calculated and plotted with no data
loaded: set `data_range` (or accept the defaults), then call
`project.display.pattern(...)`. The original failure is resolved.
- One uniform `experiment.data_range` spans all experiment types;
generic display and calculator code read the shared sinθ/λ view and
need not branch on beam mode.
- `experiment.measured_range` is subsumed by the derived getter on
`data_range`.
- The project is in beta, so this adds the category with no
compatibility shim; tutorials and tests adopt it directly.
- New code spans a `data_range` category, factory, and per-type classes;
calc-on-access grid and reflection generation; single-crystal hkl
generation via `calc_hkl`; and the display-gate relaxation plus a
calc-only single-crystal view.

## Alternatives Considered

- **A single generic `data_range.min/max/inc`.** Rejected: a bare `min`
is not self-explaining, because even for CWL a range may be thought of
in 2θ, d-spacing, or sinθ/λ. Axis-named attributes that cross-convert
read better and still expose the shared sinθ/λ view for generic code.
- **A custom `pd_calc.2theta_range_*`.** Rejected: `pd_calc` has no
range in the dictionary (it is intensities, and reuses the meas/proc
point grid), so a custom calc range would lose interoperability with
no clear benefit over the standard `pd_meas` range plus the universal
`refln` reciprocal coordinates.
- **An input/output split** — a writable "requested range" input plus a
read-only derived output — mirroring
[Minimizer Input/Output Split](../accepted/minimizer-input-output-split.md).
Rejected as heavier than needed here; a single guarded writable
property covers both roles.
- **A `simulate(x_min, x_max, x_step)` method.** Rejected: the range is
persistent, restorable model state, which a method call is not.
- **A range read directly by the calculators, with no data points.**
Rejected: it is more invasive (both engines plus plotting) and breaks
the invariant that the data points define the grid.

## Deferred Work

- Single-crystal reflection-generation wiring (the cryspy `calc_hkl`
path and validation of absence handling); crysfml single-crystal
structure-factor support is expected soon and adopts the same path.
- Final custom CIF tag names for the TOF, sinθ/λ, and d-spacing bounds.
- Concrete default numeric ranges and steps per experiment type.
- The calc-only single-crystal plot specifics.
Loading
Loading