Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
4620fa8
Add space-group-database canonical-templates phase
AndrewSazonov Jun 2, 2026
74c410d
Refine canonical-templates phase to re-source from cryspy
AndrewSazonov Jun 2, 2026
940529e
Refine canonical-templates CT1 to fix generator and re-run
AndrewSazonov Jun 3, 2026
7c9f5d7
Settle canonical-templates phase on cctbx-free post-process
AndrewSazonov Jun 3, 2026
4811507
Canonicalize space-group coords_xyz templates
AndrewSazonov Jun 3, 2026
1a2a9e6
Assert canonical coords_xyz in packaged DB check
AndrewSazonov Jun 3, 2026
c4c7a7d
Record canonical coords_xyz invariant and provenance
AndrewSazonov Jun 3, 2026
62fd107
Reach canonical-templates Phase 1 review gate
AndrewSazonov Jun 3, 2026
176e6e6
Keep pattern-plot top and bottom panels at fixed height
AndrewSazonov Jun 3, 2026
7675de2
Test fixed pattern-plot panel heights across row layouts
AndrewSazonov Jun 3, 2026
bb817a5
Inline composite row-height max() and trim docstring
AndrewSazonov Jun 3, 2026
bec2f5e
Keep TeX report panels fixed when rows are hidden
AndrewSazonov Jun 3, 2026
da7cb4c
Store full canonical Wyckoff orbits via exact check
AndrewSazonov Jun 3, 2026
238cc34
Document two-stage canonical rebuild as invariant step
AndrewSazonov Jun 3, 2026
c27c23e
Build full Wyckoff orbits from cryspy primitive and centering
AndrewSazonov Jun 3, 2026
dcbd901
Fix plan references to obsolete generator coords source
AndrewSazonov Jun 3, 2026
92c4112
Validate canonical coords_xyz and coupled constraints
AndrewSazonov Jun 3, 2026
3053cca
Note plotting-module docstring fix for Phase 2 review
AndrewSazonov Jun 3, 2026
f7948c9
Mark Phase 2 verification complete
AndrewSazonov Jun 3, 2026
833df9a
Assert R-3m special positions stay on-site after fit
AndrewSazonov Jun 3, 2026
d4cb540
Confirm wyckoff letter detection ADR gate
AndrewSazonov Jun 3, 2026
16896f3
Add Wyckoff orbit detection to crystallography module
AndrewSazonov Jun 3, 2026
abed076
Record free-param-solving Wyckoff snap decision for P1.6
AndrewSazonov Jun 3, 2026
01ca0da
Add derived space group Wyckoff category
AndrewSazonov Jun 3, 2026
31d1278
Wire derived Wyckoff table into Structure
AndrewSazonov Jun 3, 2026
1bb7aea
Add read-only multiplicity to AtomSite
AndrewSazonov Jun 3, 2026
0f1db8e
Derive allowed Wyckoff letters from the space group
AndrewSazonov Jun 3, 2026
138c44d
Add Wyckoff coordinate snap helper to crystallography module
AndrewSazonov Jun 3, 2026
838d789
Detect and track Wyckoff letters in the update flow
AndrewSazonov Jun 3, 2026
582e12d
Read multiplicity from the model in the cryspy calculator
AndrewSazonov Jun 3, 2026
2819bb6
Serialize Wyckoff multiplicity and report Wyckoff table
AndrewSazonov Jun 3, 2026
3dcf0fa
Promote wyckoff-letter-detection ADR and close issue #51
AndrewSazonov Jun 3, 2026
8dd072c
Reach Phase 1 review gate
AndrewSazonov Jun 3, 2026
7cdb2d2
Preserve explicit Wyckoff letter until a later edit
AndrewSazonov Jun 3, 2026
f97f583
Build space-group Wyckoff id from multiplicity and letter
AndrewSazonov Jun 3, 2026
55a0656
Rebuild Wyckoff index and parent links on replace
AndrewSazonov Jun 3, 2026
68fb0d9
Reject item assignment and deletion on Wyckoff collection
AndrewSazonov Jun 3, 2026
32371a8
Resolve None-coord-code groups in Wyckoff constraint helper
AndrewSazonov Jun 3, 2026
91dd4d6
Sync top-level plan status with completed Phase 1
AndrewSazonov Jun 3, 2026
34d99f1
Raise ValueError for Wyckoff read-only mutation per ADR
AndrewSazonov Jun 3, 2026
7abbffe
Remove wyckoff_letter from tutorial examples
AndrewSazonov Jun 3, 2026
25b212b
Apply pixi run fix auto-fixes
AndrewSazonov Jun 3, 2026
7c901df
Resolve ruff lint findings in Wyckoff Phase 1 code
AndrewSazonov Jun 3, 2026
b170d0d
Update tests for Wyckoff detection behavior changes
AndrewSazonov Jun 3, 2026
7265ece
Add tests for derived space_group_wyckoff category
AndrewSazonov Jun 3, 2026
daded80
Add detection and lookup tests for Wyckoff crystallography
AndrewSazonov Jun 3, 2026
e59fb8f
Assert canonical Wyckoff representative templates in data
AndrewSazonov Jun 3, 2026
bd83195
Add atom-site Wyckoff detection behavior tests
AndrewSazonov Jun 3, 2026
692f170
Add tutorial-corpus Wyckoff detection regression
AndrewSazonov Jun 3, 2026
821e673
Satisfy pydoclint and ruff for Wyckoff Phase 2 tests
AndrewSazonov Jun 3, 2026
ddcd720
Fix ed-13 Si to the 8a diamond position
AndrewSazonov Jun 3, 2026
74f9354
Regenerate tutorial notebooks after Wyckoff letter removal
AndrewSazonov Jun 3, 2026
04a6721
Restore tutorial-corpus coverage with explicit ground-truth table
AndrewSazonov Jun 3, 2026
70c32c7
Match Fd-3m corpus ground truth to corrected ed-13 Si site
AndrewSazonov Jun 3, 2026
38b3c1c
Mark Phase 2 verification complete in plan
AndrewSazonov Jun 3, 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
96 changes: 77 additions & 19 deletions docs/dev/adrs/accepted/space-group-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Structure model.

> This ADR follows [`AGENTS.md`](../../../../AGENTS.md). It was a
> prerequisite for
> [`wyckoff-letter-detection.md`](../suggestions/wyckoff-letter-detection.md):
> Wyckoff detection can only resolve letters for space groups present in
> the bundled table, which this ADR's implementation completed for all
> 230 groups.
> [`wyckoff-letter-detection.md`](wyckoff-letter-detection.md): Wyckoff
> detection can only resolve letters for space groups present in the
> bundled table, which this ADR's implementation completed for all 230
> groups.

## Context

Expand Down Expand Up @@ -103,8 +103,7 @@ Coordinates and operators stay **strings** (e.g. `'(x,1/2,0)'`,
`sympify`) in `crystallography.py` and to keep the file JSON-native
(§2). Triclinic no-setting groups keep the `None` coordinate code, as
today (see the `''`→`None` normalisation in
[`wyckoff-letter-detection.md`](../suggestions/wyckoff-letter-detection.md)
§2).
[`wyckoff-letter-detection.md`](wyckoff-letter-detection.md) §2).

**Query surface preserved.** On disk the JSON is a list of setting
records, each carrying the canonical `IT_number` and
Expand Down Expand Up @@ -286,17 +285,46 @@ coordinate-system code": EasyDiffraction's `SpaceGroup` category uses
the empty string `''`, while the table key uses `None`. The database
keeps `(1, None)` and `(2, None)`; callers normalise `''` to `None` at
lookup boundaries, as specified in
[`wyckoff-letter-detection.md`](../suggestions/wyckoff-letter-detection.md).
This is the least surprising solution because it keeps "no setting"
distinct from any real coordinate-code string without inventing a
sentinel value.
[`wyckoff-letter-detection.md`](wyckoff-letter-detection.md). This is
the least surprising solution because it keeps "no setting" distinct
from any real coordinate-code string without inventing a sentinel value.

### 8. The database file is generated, not hand-edited

`space_groups.json.gz` is never edited by hand. Any correction flows
through the curation overrides and a regeneration run, keeping the file
and the documented decisions in sync.

### 9. Canonical ITA `coords_xyz` (no operator form)

Every Wyckoff `coords_xyz` template is stored in **canonical
International Tables parametric form** — each component a signed single
free variable (or an integer-coefficient combination such as `x-y`) plus
an optional rational constant, never a fractional coefficient on a
variable. cctbx's `unique_ops().as_xyz()` (the generator's raw output)
emits **operator form** (e.g. `1/2*x-1/2*y`) for coupled special
positions, which silently breaks
`crystallography._fract_constrained_flags` so a refined special-position
coordinate drifts off its symmetry site. A Wyckoff orbit is a list of
**distinct point-functions** (one per symmetry-equivalent site), and the
DB stores the **full** (centered) orbit — `coords_xyz` length equals the
ITA multiplicity. cryspy lists only the **primitive** orbit, so the full
orbit is built by **expanding** each cryspy primitive element over the
group's centering translations (the identity-rotation symops):
`full = {primitive element + centering vector}`, yielding exactly
`multiplicity` distinct canonical templates. Every replacement is
verified **exactly**: `len == multiplicity` and
`len(set) == multiplicity` (a proper full orbit with distinct elements —
no collapsed duplicates); no operator form and no fractional
coefficient; the representative's `_fract_constrained_flags` free-axis
count equals the manifold's rank (rejecting non-minimal spellings such
as `(x-y,-x+y,z)`); and parametrization-independent geometric
equivalence to the cctbx orbit (every element on a cctbx manifold, every
cctbx manifold covered). The no-operator-form and no-duplicate
invariants are enforced by the post-process before it writes, in the
unit tests, and (for operator form) by `tools/check_packaged_db.py`,
which rejects any operator-form template in the packaged wheel.

## Consequences

### Positive
Expand Down Expand Up @@ -346,9 +374,9 @@ and the documented decisions in sync.
early when `coord_code is None` and `_get_general_position_ops()`
indexes the raw key, so they need the `''`→`None` normalisation
defined in
[`wyckoff-letter-detection.md`](../suggestions/wyckoff-letter-detection.md)
§2 (which also updates these call sites). This ADR delivers the data;
that ADR delivers the `None`-code consumer handling.
[`wyckoff-letter-detection.md`](wyckoff-letter-detection.md) §2 (which
also updates these call sites). This ADR delivers the data; that ADR
delivers the `None`-code consumer handling.

## Alternatives Considered

Expand Down Expand Up @@ -409,6 +437,32 @@ pixi exec --spec cctbx --spec gemmi --spec sympy --spec pyyaml \
--print-summary
```

**Canonical-`coords_xyz` correction (§9) — mandatory second stage.** The
rebuild has **two mandatory stages**, and the generator run above is
only the first. The generator emits cctbx operator-form `coords_xyz` for
coupled special positions and **cannot** emit canonical form itself:
cctbx always produces operator form, and the canonical ITA spelling
lives in cryspy's `wyckoff.dat`. The generator alone therefore does
**not** produce a shippable database. It **must** be followed by the
canonicalization post-process, which is the **invariant-enforcing step**
— it re-sources canonical ITA `coords_xyz` from cryspy's `wyckoff.dat`
and refuses to write unless every template is canonical (no operator
form, no fractional coefficient):

```bash
python tmp/space-groups/helper-tools/canonicalize_coords.py --write
```

It rebuilds each of the 288 coupled positions' **full** orbit by
**expanding** the cryspy primitive orbit over the group's centering
translations — `coords_xyz` length equals the multiplicity and every
element is distinct. It changes only `coords_xyz`, verifies every
replacement **exactly** (distinct full orbit, canonical spelling, a
`_fract_constrained_flags` rank check, and parametrization-independent
geometric equivalence to the cctbx orbit), and asserts no operator-form
and no duplicate template remains. The `space_groups.json.gz` SHA-256
below is **after** this correction.

Build environment:

- **cctbx** from conda-forge:
Expand All @@ -423,10 +477,14 @@ Build environment:

Generated and curation artifacts:

- `src/easydiffraction/crystallography/space_groups.json.gz`:
`30f0051c669712ab34d991e60223c5e29264fc033b2ab03392cc01465ceba926`
- `src/easydiffraction/crystallography/space_groups.json.gz` (after the
§9 canonical-`coords_xyz` correction):
`390f0e9d0ebe27a52ee5680a1bc686123ba84c8751302fed4dee4dfaf7edf7b4`
- `tmp/space-groups/helper-tools/generate_space_groups.py`:
`bf10dcfbcf9e60485037ddabc65425e61f746ad9649cd3ccc67376dd6aae241a`
`3aa5f03cd1a69bdfe0a280158c9343b65d5eaa4d75a6d58f2606fb5fbe3df83d`
- `tmp/space-groups/helper-tools/canonicalize_coords.py` (§9
canonical-`coords_xyz` post-process):
`8f2e94b130481d2a11de057fe200d8e5fd5d3d5eec7cdd39f5d4afd13f5cb8f2`
- `docs/dev/adrs/accepted/space-group-database/space_groups_overrides.yaml`:
`7077eec25d0f3b852dd7096a24dc7ac438467f9cb594f91a65ce10cda0e0722a`
- `tmp/space-groups/extracted-comparison/disagreements.md`:
Expand Down Expand Up @@ -511,8 +569,8 @@ respectively.

## Related ADRs

- [`wyckoff-letter-detection.md`](../suggestions/wyckoff-letter-detection.md)
— the dependent feature; its `''`→`None` coordinate-code normalisation
and its "unsupported group" handling both build on this database.
- [`wyckoff-letter-detection.md`](wyckoff-letter-detection.md) — the
dependent feature; its `''`→`None` coordinate-code normalisation and
its "unsupported group" handling both build on this database.
- [`iucr-cif-tag-alignment.md`](../accepted/iucr-cif-tag-alignment.md) —
consumes space-group and Wyckoff data on export.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR: Automatic Wyckoff Position Detection

**Status:** Proposed **Date:** 2026-06-01
**Status:** Accepted **Date:** 2026-06-01

## Group

Expand Down
2 changes: 1 addition & 1 deletion docs/dev/adrs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ folders.
| 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 | Test Strategy | Defines layered unit, functional, integration, script, and notebook testing. | [`test-strategy.md`](accepted/test-strategy.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) |
| Structure model | Suggestion | Automatic Wyckoff Position Detection | Detects Wyckoff letter, multiplicity, and site symmetry from space group and coordinates; calculators consume them. | [`wyckoff-letter-detection.md`](suggestions/wyckoff-letter-detection.md) |
| Structure model | Accepted | Automatic Wyckoff Position Detection | Detects Wyckoff letter, multiplicity, and site symmetry from space group and coordinates; calculators consume them. | [`wyckoff-letter-detection.md`](accepted/wyckoff-letter-detection.md) |
| Structure model | Accepted | Complete Space-Group Reference Database | One-time build of a complete space_groups.json.gz (all 230 groups) from cctbx, verified against multiple sources. | [`space-group-database.md`](accepted/space-group-database.md) |
| User-facing API | Accepted | Crystal Structure 3D Visualization | Adds a renderer-neutral scene model drawn by ASCII and interactive Three.js engines for viewing crystal structures. | [`crysview-structure-visualization.md`](accepted/crysview-structure-visualization.md) |
| User-facing API | Accepted | Display UX Facade | Defines `project.display` and `project.rendering` responsibilities and display method names. | [`display-ux.md`](accepted/display-ux.md) |
Expand Down
11 changes: 11 additions & 0 deletions docs/dev/issues/closed.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Issues that have been fully resolved. Kept for historical reference.

---

## 51. Access Space Group from `AtomSites` for Wyckoff Letters

Closed by the Wyckoff-letter-detection implementation. `AtomSite` now
derives its allowed Wyckoff letters from the parent structure's space
group (via `_resolve_structure_space_group`) instead of a hardcoded
list, and the missing-letter case is handled explicitly: untabulated
space groups leave the Wyckoff letter and multiplicity unset, while
tabulated groups detect and fill them during the update flow.

---

## 103. Make `_sync_engine_from_minimizer_category` Skip-Keys Declarative

Closed by the emcee minimizer implementation. Minimizer categories now
Expand Down
19 changes: 0 additions & 19 deletions docs/dev/issues/open.md
Original file line number Diff line number Diff line change
Expand Up @@ -996,24 +996,6 @@ generation.

---

## 51. 🟢 Access Space Group from `AtomSites` for Wyckoff Letters

**Type:** Design

`AtomSite` needs the current space group to determine allowed Wyckoff
letters but currently returns a hardcoded list. Also, a missing Wyckoff
letter case needs a decision.

**TODOs:**

- [default.py](src/easydiffraction/datablocks/structure/categories/atom_sites/default.py#L163)
- [default.py](src/easydiffraction/datablocks/structure/categories/atom_sites/default.py#L179)
- [default.py](src/easydiffraction/datablocks/structure/categories/atom_sites/default.py#L353)

**Depends on:** nothing.

---

## 52. 🟢 Rename Line-Segment Background `y` to `intensity`

**Type:** Naming
Expand Down Expand Up @@ -1951,7 +1933,6 @@ only render the index column when no explicit id column is present.
| 48 | Fix CrysPy TOF instrument default | 🟢 Low | Bug workaround |
| 49 | Automate space group CIF name variants | 🟢 Low | Maintainability |
| 50 | Clarify `Cell._update` minimizer param | 🟢 Low | Cleanup |
| 51 | Access space group for Wyckoff letters | 🟢 Low | Design |
| 52 | Rename line-segment `y` to `intensity` | 🟢 Low | Naming |
| 53 | Move `show()` to `CategoryCollection` | 🟢 Low | Maintainability |
| 54 | Add `point_id` to excluded regions | 🟢 Low | Completeness |
Expand Down
9 changes: 9 additions & 0 deletions docs/dev/package-structure/full.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@
│ │ ├── 🏷️ class TypeValidator
│ │ ├── 🏷️ class RangeValidator
│ │ ├── 🏷️ class MembershipValidator
│ │ ├── 🏷️ class PermissiveMembershipValidator
│ │ ├── 🏷️ class RegexValidator
│ │ └── 🏷️ class AttributeSpec
│ └── 📄 variable.py
Expand All @@ -262,6 +263,7 @@
├── 📁 crystallography
│ ├── 📄 __init__.py
│ ├── 📄 crystallography.py
│ │ └── 🏷️ class WyckoffPosition
│ └── 📄 space_groups.py
├── 📁 datablocks
│ ├── 📁 experiment
Expand Down Expand Up @@ -464,6 +466,13 @@
│ │ │ │ │ └── 🏷️ class SpaceGroup
│ │ │ │ └── 📄 factory.py
│ │ │ │ └── 🏷️ class SpaceGroupFactory
│ │ │ ├── 📁 space_group_wyckoff
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 default.py
│ │ │ │ │ ├── 🏷️ class SpaceGroupWyckoff
│ │ │ │ │ └── 🏷️ class SpaceGroupWyckoffCollection
│ │ │ │ └── 📄 factory.py
│ │ │ │ └── 🏷️ class SpaceGroupWyckoffFactory
│ │ │ └── 📄 __init__.py
│ │ ├── 📁 item
│ │ │ ├── 📄 __init__.py
Expand Down
4 changes: 4 additions & 0 deletions docs/dev/package-structure/short.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 default.py
│ │ │ │ └── 📄 factory.py
│ │ │ ├── 📁 space_group_wyckoff
│ │ │ │ ├── 📄 __init__.py
│ │ │ │ ├── 📄 default.py
│ │ │ │ └── 📄 factory.py
│ │ │ └── 📄 __init__.py
│ │ ├── 📁 item
│ │ │ ├── 📄 __init__.py
Expand Down
Loading
Loading