# 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.