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
8 changes: 6 additions & 2 deletions deepmd/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,17 @@ def calculate(
# see https://gitlab.com/ase/ase/-/merge_requests/2485
self.results["free_energy"] = e[0][0]
self.results["forces"] = f[0]
self.results["virial"] = v[0].reshape(3, 3)
virial = v[0].reshape(3, 3)
self.results["virial"] = virial

# convert virial into stress for lattice relaxation
if cell is not None:
# the usual convention (tensile stress is positive)
# stress = -virial / volume
stress = -0.5 * (v[0].copy() + v[0].copy().T) / atoms.get_volume()
# ASE represents Cauchy stress as a symmetric tensor. Reshape the
# flat model output before transposing; ``.T`` on the original
# one-dimensional virial array would otherwise be a no-op.
stress = -0.5 * (virial + virial.T) / atoms.get_volume()
# Voigt notation
self.results["stress"] = stress.flat[[0, 4, 8, 5, 2, 1]]
elif "stress" in properties:
Expand Down
54 changes: 54 additions & 0 deletions source/tests/test_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Backend-independent regression tests for the ASE calculator adapter."""

from unittest.mock import (
Mock,
patch,
)

import numpy as np
from ase import (
Atoms,
)

from deepmd.calculator import (
DP,
)


def test_stress_symmetrizes_flat_virial() -> None:
"""Convert the model virial to ASE's symmetric Voigt stress convention.

Real DeePMD models normally produce an already-symmetric total virial, so
the existing integration tests could not detect that transposing its flat
nine-component representation was a no-op. An intentionally asymmetric
mock output makes each off-diagonal average independently observable.
"""
model = Mock()
model.get_type_map.return_value = ["H"]
model.get_ntypes.return_value = 1
virial = np.arange(1.0, 10.0).reshape(1, 9)
model.eval.return_value = (
np.array([[0.0]]),
np.zeros((1, 1, 3)),
virial,
)

with patch("deepmd.calculator.DeepPot", return_value=model):
calculator = DP("unused-model")

atoms = Atoms(
"H",
positions=[[0.0, 0.0, 0.0]],
cell=np.eye(3) * 2.0,
pbc=True,
calculator=calculator,
)

np.testing.assert_allclose(
atoms.get_stress(voigt=True),
-np.array([1.0, 5.0, 9.0, 7.0, 5.0, 3.0]) / atoms.get_volume(),
)
# Symmetrizing stress must not discard diagnostic information from the
# model: callers requesting the virial still receive the original tensor.
np.testing.assert_array_equal(calculator.results["virial"], virial.reshape(3, 3))
Loading