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
20 changes: 17 additions & 3 deletions src/atomate2/common/schemas/elastic.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,15 +302,29 @@ def expand_strains(
strains will not contain the ones with other strain states. Also see:
`generate_elastic_deformations()`.
"""
if tol <= 0:
raise ValueError(f"tol must be positive, got {tol}")
for strain in strains:
applied_components = np.abs(strain.voigt)
if len(applied_components[applied_components > tol]) == 0:
raise ValueError("tol must be smaller than the applied strain magnitude")

def zero_numerical_components(strain: Strain) -> Strain:
voigt = np.asarray(strain.voigt).copy()
voigt[np.abs(voigt) <= tol] = 0
return Strain.from_voigt(voigt)

sga = SpacegroupAnalyzer(structure, symprec=symprec)
symm_ops = sga.get_symmetry_operations(cartesian=True)

full_strains = deepcopy(strains)
full_strains = [zero_numerical_components(strain) for strain in strains]
full_stresses = deepcopy(stresses)
full_uuids = deepcopy(uuids)
full_job_dirs = deepcopy(job_dirs)

mapping = TensorMapping(full_strains, [True for _ in full_strains])
# Preserve the original strains for identity comparisons so normalization does
# not change which symmetry-equivalent strains are accepted.
mapping = TensorMapping(deepcopy(strains), [True for _ in strains])
for idx, strain in enumerate(strains):
for symm_op in symm_ops:
rotated_strain = strain.transform(symm_op)
Expand All @@ -327,7 +341,7 @@ def expand_strains(
mapping[rotated_strain] = True

# expand the other properties
full_strains.append(rotated_strain)
full_strains.append(zero_numerical_components(rotated_strain))
full_stresses.append(stresses[idx].transform(symm_op))
full_uuids.append(uuids[idx])
full_job_dirs.append(job_dirs[idx])
Expand Down
53 changes: 52 additions & 1 deletion tests/common/jobs/test_elastic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import numpy as np
import pytest
from jobflow import run_locally
from pymatgen.analysis.elasticity import Stress
from pymatgen.analysis.elasticity import Strain, Stress
from pymatgen.core import Lattice, Structure
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

from atomate2 import SETTINGS
Expand Down Expand Up @@ -43,6 +44,56 @@ def test_reduce_expand_strains(clean_dir, symmetry_structure, conventional):
assert any(np.allclose(fs, rs) for rs in recovered_strains)


def test_expand_strains_zeroes_numerical_components():
"""Ensure symmetry residues use the same zero tolerance as expansion."""
base = Structure.from_spacegroup("Im-3m", Lattice.cubic(2.87), ["Fe"], [[0, 0, 0]])
lattice = np.asarray(base.lattice.matrix).copy()
lattice[0, 1] += 1e-3
lattice[1, 2] -= 7e-4
lattice[2, 0] += 3e-4
structure = Structure(Lattice(lattice), base.species, base.frac_coords)

strain = Strain.from_voigt([0.01, 0, 0, 0, 0, 0])
stresses = [Stress(np.zeros((3, 3)))]
expanded, _, _, _ = expand_strains(
structure,
[strain],
stresses=stresses,
uuids=["dummy"],
job_dirs=["dummy"],
symprec=0.1,
)

assert len(expanded) == 3
for expanded_strain in expanded:
components = np.abs(expanded_strain.voigt)
assert np.all((components == 0) | (components > 1e-3))


@pytest.mark.parametrize(
("tol", "strain_voigt"),
[
(-1e-3, [0.01, 0, 0, 0, 0, 0]),
(0, [0.01, 0, 0, 0, 0, 0]),
(1e-3, [5e-4, 0, 0, 0, 0, 0]),
],
)
def test_expand_strains_rejects_invalid_zero_tolerance(tol, strain_voigt):
structure = Structure(Lattice.cubic(2.87), ["Fe"], [[0, 0, 0]])
strain = Strain.from_voigt(strain_voigt)

with pytest.raises(ValueError, match="tol"):
expand_strains(
structure,
[strain],
stresses=[Stress(np.zeros((3, 3)))],
uuids=["dummy"],
job_dirs=["dummy"],
symprec=0.1,
tol=tol,
)


def _get_strains(structure, sym_reduce):
"""Get applied strains to deform the deformed structures."""

Expand Down