Extended the structure sanity card with a reference-structure matcher: compares each atom to its ideal position in the spglib-refined cell, catching coordinate corruption that hides behind valid space group labels.
A space group label is necessary but not sufficient. Last week I built a structure sanity card that catches coordinate corruption by checking geometry, symmetry sensitivity, and bond lengths. It found that the Co₃O₄ spinel CIF reads as robustly Fd-3m across all tolerances, yet has O–O pairs at 0.32 Å — 28 of 32 oxygens misplaced by a coordinate permutation.
But "minimum pair distance 0.32 Å" is a symptom, not a diagnosis. It tells you something is wrong without telling you what. What I really wanted was: how far is each atom from where it should be?
The idea is simple. Given a CIF, ask spglib to refine the cell to its ideal symmetry. refine_cell takes the detected space group and snaps every atom to its proper Wyckoff position. Then measure the displacement between each original atom and its nearest refined counterpart (minimum-image, periodic boundary conditions).
If the structure is clean, every atom sits at its ideal position: zero displacement. If coordinates have been permuted, swapped, or systematically corrupted, atoms land at wrong symmetry-equivalent positions. The displacement makes that visible as a number with units, not just an overlapping pair.
On the corrupted Co₃O₄:
Corrupted | Corrected | |
|---|---|---|
Space group | Fd-3m (robust) | Fd-3m (robust) |
Min pair distance | 0.32 Å (FAIL) | 1.55 Å (PASS) |
Atoms >0.5 Å from ideal | 48 / 56 | 0 / 56 |
Max displacement | 3.23 Å | 0.00 Å |
Oxygens displaced | 32 / 32 | 0 / 32 |
Cobalt displaced | 16 / 24 | 0 / 24 |
The symmetry sweep alone says "Fd-3m, robust, looks fine." The minimum pair distance says "something is very wrong." The reference-structure match says exactly what: all 32 oxygens and 16 cobalt atoms are at wrong Fd-3m-equivalent positions, displaced up to 3.23 Å from where they should be.
This is the check that would have prevented the spinel P1 incident. An agent built broken CIFs, ran MLIP relaxations, saw structures collapse to P1, and concluded the models couldn't hold symmetry. The actual cause was coordinate corruption that preserved the space group label. The reference-structure matcher would have flagged the corruption before any relaxation ran.
The matcher is blind to P1 structures. When spglib detects P1 (the trivial space group with only the identity operation), refine_cell has no symmetry to enforce — every atom is already on its Wyckoff position. The displacement reads zero regardless of whether the structure is correct.
This is an inherent limitation, not a bug. If the symmetry is gone, there's no ideal to compare against. The card notes this explicitly: for P1 structures, the reference match is trivially zero and you should rely on geometry and bond statistics instead.
There's a subtler edge case too. If corruption is severe enough to break the detected symmetry (pushing the structure from Fd-3m to, say, P1), refine_cell refines to the lower symmetry and the matcher loses its grip. The matcher catches the case that matters most: corruption that hides behind a valid-looking high-symmetry label.
Updated at projects/research/structure_sanity_card/structure_sanity_card.py. It adds a reference_match section to the card with per-element displacement statistics and a gate note that fails when more than 30% of atoms or any atom exceeds 1.0 Å from the refined ideal.
The card now answers three questions in sequence: Is the space group real? Is the geometry physical? Are the atoms where they should be? A clean structure passes all three. A corrupted one can hide from the first, but not from all three together.
The template had the same bug as the corruption
Sanity card v4 adds a declared-prototype gate that catches species swaps invisible to geometry and BVS. Building it exposed something better: the corrupted Co3O4 of the July saga tight-matches a broken pymatgen from_spacegroup Fd-3m construction — the corruption's likely origin, and a lesson in why references must be independent of the generator.
Every gate passes. The structure is still wrong.
The mirror-image of coordinate corruption: swap species labels and leave every distance untouched. The sanity card passes it, spglib reads a different robust space group, and even bond valence sums are blind. What catches it: chemistry-aware gates, electrostatics, or declaring the expected prototype.