Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/pymatgen/core/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,7 @@ def get_primitive_structure(
tolerance: float = 0.25,
use_site_props: bool = False,
constrain_latt: list | dict | None = None,
reduce: bool = True,
) -> Self | Structure:
"""Find a smaller unit cell than the input. Sometimes it doesn't
find the smallest possible one, so this method is recursively called
Expand All @@ -2577,6 +2578,8 @@ def get_primitive_structure(
preserve, e.g. ["alpha", "c"] or dict with the lattice
parameter names as keys and values we want the parameters to
be e.g. {"alpha": 90, "c": 2.5}.
reduce (bool): Whether get_reduced_structure is called prior to
checking lattice constraints and returning structure.

Returns:
The most primitive structure found.
Expand Down Expand Up @@ -2745,7 +2748,9 @@ def factors(n: int):
tolerance=tolerance,
use_site_props=use_site_props,
constrain_latt=constrain_latt,
).get_reduced_structure()
)
if reduce:
primitive = primitive.get_reduced_structure()
if not constrain_latt:
return primitive

Expand Down
59 changes: 43 additions & 16 deletions src/pymatgen/core/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ def __init__(
fractional_coords. Defaults to None for no properties.
energy (float): A value for the energy.
"""
self.oriented_unit_cell = oriented_unit_cell
self.miller_index = miller_index
self.shift = shift
self.reconstruction = reconstruction
Expand All @@ -154,6 +153,30 @@ def __init__(
lattice.gamma,
)

oriented_unit_cell = copy.deepcopy(oriented_unit_cell)
ouc_lattice = oriented_unit_cell.lattice
ouc_lattice = Lattice.from_parameters(
ouc_lattice.a,
ouc_lattice.b,
ouc_lattice.c,
ouc_lattice.alpha,
ouc_lattice.beta,
ouc_lattice.gamma,
)

self.oriented_unit_cell = Structure(
ouc_lattice,
oriented_unit_cell.species,
oriented_unit_cell.frac_coords,
charge=oriented_unit_cell.charge,
coords_are_cartesian=False,
site_properties=oriented_unit_cell.site_properties,
labels=oriented_unit_cell.labels,
properties=oriented_unit_cell.properties,
)
else:
self.oriented_unit_cell = oriented_unit_cell

super().__init__(
lattice,
species,
Expand Down Expand Up @@ -1152,31 +1175,35 @@ def get_slab(
if self.center_slab:
struct = center_slab(struct)

ouc = self.oriented_unit_cell.copy()

# Reduce to primitive cell
if self.primitive:
prim_slab = struct.get_primitive_structure(tolerance=tol)
struct = prim_slab
prim_slab = struct.get_primitive_structure(tolerance=tol, reduce=False)

if energy is not None:
energy *= prim_slab.volume / struct.volume

# Reorient the lattice to get the correctly reduced cell
ouc = self.oriented_unit_cell.copy()
if self.primitive:
# Find a reduced OUC
slab_l = struct.lattice
ouc = ouc.get_primitive_structure(
prim_slab_l = prim_slab.lattice
prim_ouc = ouc.get_primitive_structure(
constrain_latt={
"a": slab_l.a,
"b": slab_l.b,
"alpha": slab_l.alpha,
"beta": slab_l.beta,
"gamma": slab_l.gamma,
}
"a": prim_slab_l.a,
"b": prim_slab_l.b,
"alpha": prim_slab_l.alpha,
"beta": prim_slab_l.beta,
"gamma": prim_slab_l.gamma,
},
reduce=False,
)

# Ensure lattice a and b are consistent between the OUC and the Slab
ouc = ouc if (slab_l.a == ouc.lattice.a and slab_l.b == ouc.lattice.b) else self.oriented_unit_cell
# Ensure lattice a and b are consistent between the OUC and the slab
a_b_consistent = np.isclose(prim_slab_l.a, prim_ouc.lattice.a) and np.isclose(
prim_slab_l.b, prim_ouc.lattice.b
)
if a_b_consistent:
struct = prim_slab
ouc = prim_ouc

return Slab(
struct.lattice,
Expand Down
30 changes: 28 additions & 2 deletions tests/core/test_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,32 @@ def test_get_slab(self):
assert np.dot(a_vec, gen._normal) == approx(0)
assert np.dot(b_vec, gen._normal) == approx(0)

def test_get_slab_oriented_unit_cell_obeys_constrained_lattice(self):
bulk = Structure.from_spacegroup(
"Fm-3m",
Lattice.cubic(4.194),
["Mg", "O"],
[[0, 0, 0], [0.5, 0.5, 0.5]],
)
slab = SlabGenerator(
initial_structure=bulk,
miller_index=(1, 1, 1),
min_slab_size=10,
min_vacuum_size=20,
primitive=True,
lll_reduce=False,
center_slab=False,
max_normal_search=None,
).get_slab(shift=0)

slab_l = slab.lattice
ouc_l = slab.oriented_unit_cell.lattice
assert ouc_l.a == approx(slab_l.a)
assert ouc_l.b == approx(slab_l.b)
assert ouc_l.alpha == approx(slab_l.alpha)
assert ouc_l.beta == approx(slab_l.beta)
assert ouc_l.gamma == approx(slab_l.gamma)

def test_normal_search(self):
fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]])
for miller in [(1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)]:
Expand Down Expand Up @@ -630,8 +656,8 @@ def test_bonds_broken(self):
# expect 2 and 6 bonds broken so we check for this.
# Number of broken bonds are floats due to primitive
# flag check and subsequent transformation of slabs.
assert slabs[0].energy == approx(8.0)
assert slabs[1].energy == approx(24.0)
assert slabs[0].energy == approx(2.0)
assert slabs[1].energy == approx(6.0)


class TestReconstructionGenerator(MatSciTest):
Expand Down
Loading