# Choose the right symprec for pymatgen symmetry analysis

Resolve unexpected pymatgen space-group assignments (P1 everywhere, or pymatgen disagreeing with the Materials Project website): sweep SpacegroupAnalyzer symprec over 0.1/0.01/0.001, classify the pattern (distorted structure vs borderline tolerance vs non-monotonic spglib failure), and match the tolerance to the downstream consumer.

Exact reference: {"kind":"skill_version","skill_id":"skl_Ow_XgVO1dSwxKQga-QMc_g","version_id":"skv_mb0K-qPoO_4HhrCFjAta8w"}

Applicability: [{"constraint":">=2020.1","technology":"pymatgen","version_scheme":"semver"}]

# Choose the right symprec for pymatgen symmetry analysis

Use this when `SpacegroupAnalyzer` reports an unexpected space group —
typically P1 for a structure that should be symmetric, or a higher-symmetry
assignment (e.g. orthorhombic Cmcm) where you expected lower symmetry
(e.g. monoclinic P2_1/m). The space group is not a property of the
structure alone; it is a property of the structure *plus the tolerance*,
and the fix is to sweep the tolerance deliberately rather than accept the
default.

## 1. Know the two defaults that cause most confusion

- **pymatgen `SpacegroupAnalyzer` default: `symprec=0.01`,
  `angle_tolerance=5.0`.** Strict, intended for well-converged structures.
- **Materials Project database (emmet builders): `symprec=0.1`.** Loose,
  intended for structures straight out of DFT relaxation.

The same structure routinely gets different space groups under the two.
Documented case (matsci.org): mp-1271198 and mp-10021 analyze as Cmcm (63)
at `symprec=0.1` but as P2_1/m (11) at `symprec=0.01`. Neither is "the bug";
the database reports the loose-tolerance answer and pymatgen's default
gives the strict answer. When your local analysis disagrees with the MP
website, the first check is always the tolerance, not the structure.

```python
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

for tol in (0.1, 0.01, 0.001):
    sga = SpacegroupAnalyzer(struct, symprec=tol)
    print(tol, sga.get_space_group_symbol(), sga.get_space_group_number())
```

## 2. Classify the symptom from the tolerance sweep

Run the sweep above and read the pattern:

- **P1 (1) at every tolerance up to 0.1** — the structure is genuinely
  distorted, or atoms are on top of each other. Check
  `struct.is_valid(tol=0.5)` for overlapping sites and confirm fractional
  coords are wrapped (`to_unit_cell=True`). No tolerance increase fixes a
  bad structure; increasing `symprec` past ~0.1 to force a symmetry is
  fabricating it. (Note: a segfault on analyzer init with modern spglib,
  e.g. pymatgen 2025.1.24 + spglib 2.5.0, is a separate backend crash —
  reported upstream, and tolerance increases did not help, so treat it as
  a build issue rather than a tolerance problem.)
- **Higher symmetry at 0.1, lower at 0.01, same at 0.001** — borderline
  case: some atoms sit just past the strict tolerance. This is the common
  DFT-relaxed-structure regime. Match the tolerance to the *consumer* of the
  result: use `symprec=0.1` when comparing against MP database entries,
  `symprec=0.01` when the downstream code assumes a clean symmetry (k-point
  paths, symmetrized structures, Wyckoff assignments).
- **Symmetry jumps non-monotonically across tolerances** (e.g. a structure
  that is *more* symmetric at 0.01 than at 0.1, or a primitive-standard
  structure with atoms stacked on top of each other at 0.01) — this is the
  known Mg3Sb2 failure mode: `get_primitive_standard_structure()` with the
  default tolerance placed Sb atoms on top of Mg atoms, while 0.001 and 0.1
  were fine. Non-monotonic behavior means spglib is struggling with the
  cell choice at that tolerance; do not trust that structure. Rebuild from
  a tolerance that behaves monotonically and verify site distances.
- **`angle_tolerance` matters independently.** The default is 5.0 degrees.
  Monoclinic-vs-orthorhombic ambiguities (beta within a few degrees of 90)
  can flip on the angle tolerance alone. If the lattice angles are near a
  boundary, sweep `angle_tolerance` too, not just `symprec`.

## 3. Verify the answer, don't just read the symbol

A space group assignment is only as good as the structure it generates:

```python
sga = SpacegroupAnalyzer(struct, symprec=0.01)
symm = sga.get_symmetrized_structure()
std = sga.get_conventional_standard_structure()
# The symmetrized cell must contain the same atoms at sane distances:
assert symm.composition == struct.composition
assert std.is_valid(tol=0.5)
```

Also cross-check with `struct.get_space_group_info(symprec=...)`, which
returns the `(symbol, number)` tuple directly off the structure — if the
two entry points disagree at the same tolerance, something structural
(e.g. unwrapped coords, duplicate sites) is interfering, not the
tolerance.

## 4. Match tolerance to the downstream task

- **Comparing to MP data / site symmetry labels**: `symprec=0.1`
  (database convention).
- **Generating primitive/conventional cells for further calculation**:
  `symprec=0.01`, then run the verification in step 3. If verification
  fails, try 0.001 before 0.1 — tightening is safer than loosening for
  cell generation.
- **K-point meshes and symmetrization for analysis**: keep the same
  tolerance for the analyzer and for `get_ir_reciprocal_mesh`-style calls;
  mismatched tolerances between symmetry detection and mesh generation
  produce inconsistent k-point sets (fixed upstream in pymatgen PR #576,
  but the principle stands for any hand-rolled pipeline).
- **Structures mid-relaxation or from force fields**: these are the
  "loosely optimized" regime the old docs flag — start at 0.1 and tighten
  only if the result is suspiciously high-symmetry.

## Checklist for a wrong space group

1. Sweep `symprec` over 0.1 / 0.01 / 0.001 and record symbol + number.
2. Classify: P1 everywhere (bad structure), borderline split (match the
   consumer's convention), non-monotonic jumps (distrust that tolerance).
3. If comparing with the MP website, reproduce with `symprec=0.1`.
4. Verify the standardized cell: composition preserved, `is_valid(tol=0.5)`
   passes, no overlapping species.
5. Keep one tolerance for the whole downstream chain.


## Supporting basis and limitations

Built from the matsci.org thread on mp-1271198/mp-10021 (Cmcm at symprec=0.1 vs P2_1/m at 0.01; pymatgen default 0.01 vs emmet/MP 0.1), pymatgen GitHub issue #1543 (get_primitive_standard_structure placing Sb on Mg at symprec=0.01 for Mg3Sb2), issue #4263 (segfault with pymatgen 2025.1.24 + spglib 2.5.0), and PR #576 (symprec propagation to get_ir_reciprocal_mesh).

## Change and rationale

New skill: choose the right symprec for pymatgen symmetry analysis via a tolerance sweep and classification procedure.

Wrong space groups are a perennial forum/GitHub issue because the space group is a function of structure plus tolerance, and pymatgen's default symprec=0.01 differs from the MP database's symprec=0.1 convention. Agents accept the first answer or crank the tolerance to force a symmetry; a sweep-and-classify procedure picks the right one with evidence.
