Skip to content

check_cell_symmetry: the Le Bail cell / space-group gate (PR C) - #15

Merged
drthyang merged 2 commits into
mainfrom
claude/check-cell-symmetry
Sep 28, 2026
Merged

drthyang merged 2 commits into
mainfrom
claude/check-cell-symmetry

Conversation

@drthyang

Copy link
Copy Markdown
Owner

PR C of the LLM-usage plan. The Rietveld skill's Stage 0 gate becomes a computed check instead of a judgement stated in words.

The question

Before any structural refinement, the tool answers two questions:

  • Does the cell index every peak? If not, the cell or lattice is wrong, or a phase is missing.
  • Does the data respect the space group's absences? If not, the group is too symmetric: it assumes a centring or glide the crystal lacks.

A low Le Bail wR can't answer either, because free intensities will fit a wrong group happily.

How (core/workflow/cellSymmetryCheck.ts)

  1. A Le Bail fit refines the cell from peak positions alone, with free intensities (the existing leBailCellPrefit).
  2. The pattern is extracted with the allowed reflections only. What remains (the leftover), minus a running-median baseline, is read in σ, with a 5% model-error floor so peak-shape mismatch under strong peaks doesn't count.
  3. Absences: each forbidden family is tested at its own position. It's violated at ≥ 5σ, and untestable within 1.5 peak widths of an allowed reflection (or one from a named extra phase).
  4. Indexing: a leftover peak (an apex over a whole peak width) that sits near no reflection is unindexed.
  5. Peak shape: pseudo-Voigt for constant wavelength, since a Gaussian's missing tails read as shoulders. Gaussian for time of flight, with the width scaled by TOF.
  6. Window: only d ≥ 0.7 Å is read by default. Below that, reflections are too dense to judge, and the reflection count grows as 1/d³.
  7. Stated limits: the result's limits names what the check can't see: a cell that is too large (a supercell indexes anything), and a group with too few absences.

The MCP tool check_cell_symmetry wraps the check. It takes extraPhases, fitRange, dMin and significance. The skill's Stage 0 now calls it, and its "Dependency (MCP)" note is removed. LIMITATIONS and the generated AGENT_TOOLS table are updated.

Evidence (src/mcp/cellSymmetry.test.ts)

Synthetic data (pseudo-Voigt peaks with Caglioti widths, a sloping background, Poisson noise):

Case Result
CsCl in Pm-3m (correct) passes
CsCl declared Im-3m fails: 100 violated at 87σ, plus 210, 111, 300 and more (all h+k+l odd)
Tetragonal crystal declared cubic fails: 32 unindexed peaks
Two-phase data fails with the second phase unnamed; passes once it's passed as extraPhases

Real data:

Case Result
GaNb₄Se₈ at 28-ID, published F-43m (local data, skipped without data/) passes, with 143 absences tested
GaNb₄Se₈ declared Fd-3m the d-glide's forbidden 200 (16σ) and 420 (6σ) are caught
Mn₃Ga POWGEN 600 K (committed data) MnO (111) is unindexed alone and indexed once MnO is named; P6₃/mmc's absences hold

Full suite: 195 files, 1,483 tests pass. tsc -b and eslint . are clean.

Worth knowing

  • Unindexed lines on Mn₃Ga TOF. Even with MnO named, the check reports leftover lines 2–5% in d from any Mn₃Ga reflection, far beyond POWGEN's resolution. Two sit at the vanadium sample can's (110) and (200) positions (d = 2.151 and 1.523 Å), and one near Al (220). They look like real sample-environment lines, not false alarms. The test therefore asserts only the MnO behaviour, not a full pass.
  • Tuning changes, each driven by a case above:
    • pseudo-Voigt for constant wavelength (GaNb₄Se₈'s tails had read as three marginal 5σ "peaks");
    • the pattern's full d range for forbidden families (bcc's 100 lies beyond the largest allowed d);
    • an apex over a whole peak width (noise on an unfitted extra phase's flanks).

🤖 Generated with Claude Code

drthyang and others added 2 commits September 28, 2026 13:35
The Rietveld skill's Stage 0 gate, computed instead of narrated: before any
structural refinement, does the cell index every peak, and does the data
respect the space group's systematic absences?

core/workflow/cellSymmetryCheck.ts: a Le Bail fit refines the cell from peak
positions alone; the pattern is extracted with the ALLOWED reflections and
the leftover (minus a running-median baseline) is read in σ, with a 5 %
model-error floor so shape mismatch under strong peaks does not count.
- each forbidden family is tested at its position: violated at ≥ 5σ,
  untestable within 1.5 widths of an allowed reflection (or an extra phase's);
- leftover peaks (apex over a whole width) near no reflection are unindexed;
- pseudo-Voigt for constant wavelength (a Gaussian's missing tails read as
  shoulders on GaNb₄Se₈), Gaussian with TOF-scaled widths for time of flight;
- reads d ≥ 0.7 Å by default (dense below; also keeps Le Bail fast on TOF);
- `limits` states what it cannot see (a too-large cell, too few absences).

Evidence:
- synthetic (pseudo-Voigt Caglioti peaks, Poisson noise): correct CsCl
  passes; declared Im-3m → 100 violated at 87σ plus 210/111/300…; a
  tetragonal crystal declared cubic → 32 unindexed; a second phase's lines
  index once named;
- GaNb₄Se₈ 28-ID (local data): published F-43m passes (143 absences tested);
  declared Fd-3m → the d-glide's forbidden 200 (16σ) and 420 (6σ) caught;
- Mn₃Ga POWGEN 600 K (committed): MnO (111) unindexed alone, indexed with
  MnO named; P6₃/mmc absences hold.

The skill's Stage 0 now calls the tool (its "Dependency (MCP)" note is gone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lection

A skipped describe's body still runs when vitest collects it; on CI the
git-ignored data/ folder is absent and the top-level reads threw ENOENT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@drthyang
drthyang merged commit 98ddf79 into main Sep 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant