Skip to content

Commit 0cb370f

Browse files
Zikkyingcursoragent
andcommitted
Ensure decohesive slabs stay right-handed for LAMMPS.
Swap a↔b when det([a,b,c]) ≤ 0 after slab construction so left-handed cells from odd axis permutations do not fail dpdata/LAMMPS conversion. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ccc9e1c commit 0cb370f

2 files changed

Lines changed: 75 additions & 11 deletions

File tree

apex/core/property/Decohesive.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,40 @@ def __gen_slab_pmg(
268268
self.is_flip = c_vec[2] < 0
269269
elong_scale = 1 + (abs(vacuum_size) / slab_height)
270270

271-
new_lattice = [a_vec, b_vec, elong_scale * c_vec]
271+
new_lattice = np.array([a_vec, b_vec, elong_scale * c_vec], dtype=float)
272272
new_frac_coords = []
273273
for frac in sorted_frac_coords:
274-
coord = frac.copy()
274+
coord = np.array(frac, dtype=float)
275275
coord[2] = coord[2] / elong_scale
276276
new_frac_coords.append(coord)
277277

278+
# LAMMPS requires a right-handed box: det([a, b, c]) > 0.
279+
# Slab construction (odd axis permutations / mirroring) can produce a
280+
# left-handed cell; swapping a↔b restores chirality without changing
281+
# the physical slab.
282+
new_lattice, new_frac_coords = _ensure_right_handed_cell(
283+
new_lattice, new_frac_coords
284+
)
285+
278286
return Structure(
279-
lattice=np.array(new_lattice), coords=new_frac_coords, species=sorted_species
287+
lattice=new_lattice, coords=new_frac_coords, species=sorted_species
288+
)
289+
290+
291+
def _ensure_right_handed_cell(lattice, frac_coords):
292+
"""Swap a↔b when det([a, b, c]) ≤ 0 so the cell is right-handed."""
293+
lattice = np.array(lattice, dtype=float)
294+
det = np.linalg.det(lattice)
295+
if det > 0:
296+
return lattice, [np.array(fc, dtype=float) for fc in frac_coords]
297+
if abs(det) <= np.finfo(float).eps:
298+
raise RuntimeError(
299+
"Generated slab has a singular lattice (det ≈ 0); cannot convert to LAMMPS"
280300
)
301+
lattice = lattice[[1, 0, 2]]
302+
fixed_frac = []
303+
for frac in frac_coords:
304+
coord = np.array(frac, dtype=float)
305+
coord[0], coord[1] = coord[1], coord[0]
306+
fixed_frac.append(coord)
307+
return lattice, fixed_frac

tests/test_decohesive.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,45 @@ def test_make_confs_with_c_vector_not_aligned_to_z(self):
128128
1.0,
129129
places=8,
130130
)
131-
131+
132+
def test_make_confs_bcc_110_is_finite_and_right_handed(self):
133+
bcc_mo = Structure(
134+
Lattice.cubic(3.16),
135+
["Mo", "Mo"],
136+
[[0, 0, 0], [0.5, 0.5, 0.5]],
137+
)
138+
bcc_mo.to(filename=os.path.join(self.equi_path, "CONTCAR"), fmt="poscar")
139+
parameter = {
140+
"type": "decohesive",
141+
"min_slab_size": 25,
142+
"max_vacuum_size": 2,
143+
"vacuum_size_step": 1,
144+
"miller_index": [1, 1, 0],
145+
"cal_type": "static",
146+
}
147+
148+
tasks = Decohesive(parameter).make_confs(self.target_path, self.equi_path)
149+
for task in tasks:
150+
slab = Structure.from_file(os.path.join(task, "POSCAR.tmp"))
151+
self.assertTrue(np.isfinite(slab.lattice.matrix).all())
152+
self.assertGreater(np.linalg.det(slab.lattice.matrix), 0.0)
153+
154+
def test_ensure_right_handed_cell_swaps_ab(self):
155+
from apex.core.property.Decohesive import _ensure_right_handed_cell
156+
157+
left_handed = np.array(
158+
[[0.0, 3.0, 0.0], [3.0, 0.0, 0.0], [0.0, 0.0, 3.0]], dtype=float
159+
)
160+
self.assertLess(np.linalg.det(left_handed), 0.0)
161+
fixed, frac = _ensure_right_handed_cell(
162+
left_handed, [np.array([0.1, 0.2, 0.3])]
163+
)
164+
self.assertGreater(np.linalg.det(fixed), 0.0)
165+
np.testing.assert_allclose(frac[0], [0.2, 0.1, 0.3])
166+
132167
def __gen_slab_pmg(self, structure: Structure,
133168
plane_miller, slab_size, vacuum_size) -> Structure:
169+
from apex.core.property.Decohesive import _ensure_right_handed_cell
134170

135171
# Generate slab via Pymatgen
136172
slabGen = SlabGenerator(structure, miller_index=plane_miller,
@@ -150,19 +186,20 @@ def __gen_slab_pmg(self, structure: Structure,
150186
sorted_species.append(species)
151187
# add vacuum layer to the slab with height unit of angstrom
152188
a_vec, b_vec, c_vec = slab.lattice.matrix
153-
slab_height = abs(c_vec[2])
189+
slab_height = np.linalg.norm(c_vec)
154190
elong_scale = 1 + (abs(vacuum_size) / slab_height)
155-
new_lattice = [a_vec, b_vec, elong_scale * c_vec]
191+
new_lattice = np.array([a_vec, b_vec, elong_scale * c_vec], dtype=float)
156192
new_frac_coords = []
157193
for frac in sorted_frac_coords:
158-
coord = frac.copy()
194+
coord = np.array(frac, dtype=float)
159195
coord[2] = coord[2] / elong_scale
160196
new_frac_coords.append(coord)
161-
slab_new = Structure(
162-
lattice=np.array(new_lattice), coords=new_frac_coords, species=sorted_species
197+
new_lattice, new_frac_coords = _ensure_right_handed_cell(
198+
new_lattice, new_frac_coords
199+
)
200+
return Structure(
201+
lattice=new_lattice, coords=new_frac_coords, species=sorted_species
163202
)
164-
165-
return slab_new
166203

167204
class TestDecohesiveReport(unittest.TestCase):
168205
def setUp(self):

0 commit comments

Comments
 (0)