Skip to content

structure.get_reduced_structure(reduction_algo="niggli") returns a reflected structure #4457

@vsimkus

Description

@vsimkus

Python version

Python 3.11.12

Pymatgen version

2025.2.18

Operating system version

MacOS 24.5.0

Current behavior

Hi, thanks for such a great package! I am having some trouble with the Niggli reduction, and unless I am misunderstanding something I think there is a bug in the final mapping.

The issue: Running structure.get_reduced_structure(reduction_algo="niggli") sometimes returns a reflected version of the original system. The core issue is in the final mapping at the following lines:

mapped = self.find_mapping(lattice, e, skip_rotation_matrix=True)
if mapped is not None:
if np.linalg.det(mapped[0].matrix) > 0:
return mapped[0] # type:ignore[return-value]
return type(self)(-mapped[0].matrix)

If you switchskip_rotation_matrix=False and check the determinant of the rotation matrix, you'll get -1, corresponding to a reflection matrix.

Let me know if I'm misunderstanding something and this is an intended behavior!

Below I've provided a snippet that shows the source of the issue: the find_mapping method returns a reflection mapping, even when the source and target lattices are the same!

Thanks again!

Expected Behavior

I would expect that find_mapping returns a rotated lattice but not a reflection. While a reflected system has the same energy, it's physical properties can be different.

Minimal example

import ase
import ase.io
import numpy as np
from pymatgen.io.ase import AseAtomsAdaptor


def get_atoms():
    atoms = ase.Atoms(
        positions=np.array(
            [
                [0.00000000e00, 0.00000000e00, 0.00000000e00],
                [2.32160500e00, -2.55395379e-10, 2.62836300e00],
                [-2.55396948e-10, 2.32160500e00, 2.62836300e00],
                [2.32160500e00, 2.32160500e00, -6.87146875e-17],
                [2.85942205e-10, -2.85941836e-10, 2.94272573e00],
                [-4.80245259e-10, 2.32160500e00, 3.14362728e-01],
                [2.32160500e00, 2.32160500e00, 2.31400027e00],
                [-3.05465285e-11, 2.32160500e00, 4.94236327e00],
            ]
        ),
        numbers=np.array([11, 11, 65, 65, 8, 8, 8, 8]),
        cell=[
            [4.643209998612975, 0.0, 0.0],
            [-1.0215833248797727e-09, 4.643209998612975, 0.0],
            [-2.3216049982849043, -2.3216049998172785, 5.2567259980108245],
        ],
        pbc=True,
    )
    return atoms


if __name__ == "__main__":
    atoms = get_atoms()
    ase.io.write("original.png", atoms)
    ase.io.write("original.cif", atoms)

    structure = AseAtomsAdaptor.get_structure(atoms)
    # structure = structure.get_reduced_structure(reduction_algo="niggli")
    tol = 1e-5
    e = tol * structure._lattice.volume ** (1 / 3)
    # Compute a lattice mapping between it and itself
    mapping, rotation_matrix, translation_vector = structure._lattice.find_mapping(
        structure._lattice, e, skip_rotation_matrix=False
    )
    print(np.linalg.det(rotation_matrix)).    # ----> prints -1, corresponding to a reflection!
    structure_mapped = type(structure)(
        mapping,
        structure.species_and_occu,
        structure.cart_coords,
        coords_are_cartesian=True,
        to_unit_cell=True,
        site_properties=structure.site_properties,
        labels=structure.labels,
        charge=structure._charge,
    )
    atoms_mapped = AseAtomsAdaptor.get_atoms(structure_mapped)
    ase.io.write("mapped.png", atoms_mapped)
    ase.io.write("mapped.cif", atoms_mapped)

Relevant files to reproduce this bug

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions