From 150a7262e9dc31ca83270f100d43e348b99de6c6 Mon Sep 17 00:00:00 2001 From: Timo Reents Date: Fri, 25 Sep 2026 11:46:37 +0200 Subject: [PATCH 1/2] Fix `is_supercell` for left-handed input cells Left-handed input cells yield a negative volume ratio (determinant of the transformation matrix) and therefore caused a left-handed primitive cell to be flagged as a supercell. By only considering the magnitude, this problem is solved. --- seekpath/getpaths.py | 7 +++- seekpath/hpkot/__init__.py | 3 +- tests/test_paths_hpkot.py | 78 ++++++++++++++++++++++++++++++++------ 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/seekpath/getpaths.py b/seekpath/getpaths.py index 2f1d4c5..dc314de 100644 --- a/seekpath/getpaths.py +++ b/seekpath/getpaths.py @@ -168,7 +168,8 @@ def get_path( - ``volume_original_wrt_conv``: volume ratio of the user-provided cell with respect to the the crystallographic conventional cell - ``volume_original_wrt_prim``: volume ratio of the user-provided cell - with respect to the the crystalloraphic primitive cell + with respect to the the crystalloraphic primitive cell (both volume + ratios are negative if the user-provided cell is left-handed) :note: An :py:exc:`~seekpath.hpkot.EdgeCaseWarning` is issued for edge cases (e.g. if ``a==b==c`` for @@ -421,7 +422,9 @@ def get_path_orig_cell( recipe=recipe, ) - is_supercell = abs(res['volume_original_wrt_prim'] - 1) > 0.1 + # The volume ratio is negative for a left-handed input cell, so only its + # magnitude says whether the cell is a supercell + is_supercell = abs(abs(res['volume_original_wrt_prim']) - 1) > 0.1 if is_supercell: warnings.warn( diff --git a/seekpath/hpkot/__init__.py b/seekpath/hpkot/__init__.py index 708d393..9dd1705 100644 --- a/seekpath/hpkot/__init__.py +++ b/seekpath/hpkot/__init__.py @@ -121,7 +121,8 @@ def get_path( - ``volume_original_wrt_conv``: volume ratio of the user-provided cell with respect to the the crystallographic conventional cell - ``volume_original_wrt_prim``: volume ratio of the user-provided cell - with respect to the the crystallographic primitive cell + with respect to the the crystallographic primitive cell (both volume + ratios are negative if the user-provided cell is left-handed) :note: An :py:exc:`~seekpath.hpkot.EdgeCaseWarning` is issued for edge cases (e.g. if ``a==b==c`` for diff --git a/tests/test_paths_hpkot.py b/tests/test_paths_hpkot.py index 508d720..b21155c 100644 --- a/tests/test_paths_hpkot.py +++ b/tests/test_paths_hpkot.py @@ -1002,6 +1002,68 @@ def test_nonstandard_cubic_supercell(self): self.assertEqual(res['spacegroup_international'], 'Pm-3m') + def test_left_handed_fcc(self): + """ + Obtain the k-path for a left-handed primitive fcc cell, which is not + a supercell even though its volume ratio to the primitive cell is -1. + """ + import warnings + + from seekpath import SupercellWarning + + cell = [[-3.0, 0.0, 3.0], [0.0, 3.0, 3.0], [-3.0, 3.0, 0.0]] + positions = [[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]] + atomic_numbers = [1, 1] + + # An improper rotation makes the cell left-handed + s = np.sin(0.3) + c = np.cos(0.3) + R = np.array([[-1.0, 0.0, 0.0], [0.0, c, s], [0.0, -s, c]]) + + cell = cell @ R + system = (cell, positions, atomic_numbers) + self.assertLess(np.linalg.det(cell), 0) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + res = self.base_test(system) + self.assertEqual(res['is_supercell'], False) + + relevant_w = [_ for _ in w if issubclass(_.category, SupercellWarning)] + self.assertEqual(relevant_w, []) + + self.assertEqual(res['spacegroup_international'], 'Fd-3m') + + def test_left_handed_cubic_supercell(self): + """ + Obtain the k-path for a left-handed 2*1*1 supercell of a cubic system, + which must still be recognized as a supercell. + """ + import warnings + + from seekpath import SupercellWarning + + cell = [[8.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 4.0]] + positions = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0]] + atomic_numbers = [1, 1] + + T = np.array([[1, 0, 1], [0, 1, 2], [0, 0, -1]]) + + cell = T @ cell + positions = positions @ np.linalg.inv(T) + system = (cell, positions, atomic_numbers) + self.assertLess(np.linalg.det(cell), 0) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + res = self.base_test(system) + self.assertEqual(res['is_supercell'], True) + + relevant_w = [_ for _ in w if issubclass(_.category, SupercellWarning)] + self.assertEqual(len(relevant_w), 1) + + self.assertEqual(res['spacegroup_international'], 'Pm-3m') + def test_no_symmetrization(self): """ Test that symmetrization is not performed so that the k path is on @@ -1089,22 +1151,16 @@ def test_path(self): # pylint: disable=no-self-use res_standard = seekpath.get_explicit_k_path(system, recipe='hpkot') with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') res_original = seekpath.get_explicit_k_path_orig_cell( system, recipe='hpkot' ) - self.assertEqual(res_original['is_supercell'], True) + # The cell is left-handed (because of the improper rotation R), but + # it is a primitive cell, not a supercell + self.assertEqual(res_original['is_supercell'], False) - # Checks on issued warnings relevant_w = [_ for _ in w if issubclass(_.category, SupercellWarning)] - self.assertEqual( - len(relevant_w), - 1, - f'Wrong number of warnings issued! ({len(relevant_w)} instead of 1)', - ) - - check_string = 'The provided cell is a supercell: the returned' - if check_string is not None: - self.assertIn(check_string, str(relevant_w[0].message)) + self.assertEqual(relevant_w, []) self.assertEqual(res_original['path'], res_standard['path']) self.assertEqual(res_original['augmented_path'], res_standard['augmented_path']) From 78d45e2499d4216486b6cb9cc0c5823c1a89a919 Mon Sep 17 00:00:00 2001 From: Timo Reents Date: Fri, 25 Sep 2026 12:25:23 +0200 Subject: [PATCH 2/2] Clean-up --- seekpath/getpaths.py | 4 ++-- seekpath/hpkot/__init__.py | 4 ++-- tests/test_brillouinzone.py | 2 +- tests/test_paths_hpkot.py | 30 ------------------------------ 4 files changed, 5 insertions(+), 35 deletions(-) diff --git a/seekpath/getpaths.py b/seekpath/getpaths.py index dc314de..f9a8ebc 100644 --- a/seekpath/getpaths.py +++ b/seekpath/getpaths.py @@ -166,9 +166,9 @@ def get_path( (the determinant is integer and gives the ratio in volume between the conventional and primitive cells) - ``volume_original_wrt_conv``: volume ratio of the user-provided cell - with respect to the the crystallographic conventional cell + with respect to the crystallographic conventional cell - ``volume_original_wrt_prim``: volume ratio of the user-provided cell - with respect to the the crystalloraphic primitive cell (both volume + with respect to the crystallographic primitive cell (both volume ratios are negative if the user-provided cell is left-handed) :note: An :py:exc:`~seekpath.hpkot.EdgeCaseWarning` is issued for diff --git a/seekpath/hpkot/__init__.py b/seekpath/hpkot/__init__.py index 9dd1705..e6930c2 100644 --- a/seekpath/hpkot/__init__.py +++ b/seekpath/hpkot/__init__.py @@ -119,9 +119,9 @@ def get_path( - ``rotation_matrix``: rotation matrix in Cartesian space from the input cell to the standardized cell - ``volume_original_wrt_conv``: volume ratio of the user-provided cell - with respect to the the crystallographic conventional cell + with respect to the crystallographic conventional cell - ``volume_original_wrt_prim``: volume ratio of the user-provided cell - with respect to the the crystallographic primitive cell (both volume + with respect to the crystallographic primitive cell (both volume ratios are negative if the user-provided cell is left-handed) :note: An :py:exc:`~seekpath.hpkot.EdgeCaseWarning` is issued for diff --git a/tests/test_brillouinzone.py b/tests/test_brillouinzone.py index 89981dd..7af34d5 100644 --- a/tests/test_brillouinzone.py +++ b/tests/test_brillouinzone.py @@ -24,7 +24,7 @@ def is_same_face(f1, f2): f1 and f2 should be a list of 3d points (i.e., a list of lists). - It also checks if the the coordinates are shifted (i.e. the first one is + It also checks if the coordinates are shifted (i.e. the first one is now the second, the second the third etc.). Tries also points in reversed order. """ diff --git a/tests/test_paths_hpkot.py b/tests/test_paths_hpkot.py index b21155c..77f7463 100644 --- a/tests/test_paths_hpkot.py +++ b/tests/test_paths_hpkot.py @@ -1034,36 +1034,6 @@ def test_left_handed_fcc(self): self.assertEqual(res['spacegroup_international'], 'Fd-3m') - def test_left_handed_cubic_supercell(self): - """ - Obtain the k-path for a left-handed 2*1*1 supercell of a cubic system, - which must still be recognized as a supercell. - """ - import warnings - - from seekpath import SupercellWarning - - cell = [[8.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 4.0]] - positions = [[0.0, 0.0, 0.0], [0.5, 0.0, 0.0]] - atomic_numbers = [1, 1] - - T = np.array([[1, 0, 1], [0, 1, 2], [0, 0, -1]]) - - cell = T @ cell - positions = positions @ np.linalg.inv(T) - system = (cell, positions, atomic_numbers) - self.assertLess(np.linalg.det(cell), 0) - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - res = self.base_test(system) - self.assertEqual(res['is_supercell'], True) - - relevant_w = [_ for _ in w if issubclass(_.category, SupercellWarning)] - self.assertEqual(len(relevant_w), 1) - - self.assertEqual(res['spacegroup_international'], 'Pm-3m') - def test_no_symmetrization(self): """ Test that symmetrization is not performed so that the k path is on