Coverage for ase / constraints / fix_scaled.py: 100.00%
26 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
1from __future__ import annotations
3import numpy as np
5from ase import Atoms
6from ase.constraints.constraint import IndexedConstraint
9class FixScaled(IndexedConstraint):
10 """Fix atoms in the directions of the unit vectors.
12 Parameters
13 ----------
14 a : Sequence[int]
15 Indices of atoms to be fixed.
16 mask : tuple[bool, bool, bool], default: (True, True, True)
17 Cell directions to be fixed. (False: unfixed, True: fixed)
18 """
20 def __init__(self, a, mask=(True, True, True), cell=None):
21 # XXX The unused cell keyword is there for compatibility
22 # with old trajectory files.
23 super().__init__(indices=a)
24 self.mask = np.asarray(mask, bool)
26 def get_removed_dof(self, atoms: Atoms):
27 return self.mask.sum() * len(self.index)
29 def adjust_positions(self, atoms: Atoms, new):
30 cell = atoms.cell
31 scaled_old = cell.scaled_positions(atoms.positions[self.index])
32 scaled_new = cell.scaled_positions(new[self.index])
33 scaled_new[:, self.mask] = scaled_old[:, self.mask]
34 new[self.index] = cell.cartesian_positions(scaled_new)
36 def adjust_forces(self, atoms: Atoms, forces):
37 # Forces are covariant to the coordinate transformation,
38 # use the inverse transformations
39 cell = atoms.cell
40 scaled_forces = cell.cartesian_positions(forces[self.index])
41 scaled_forces *= -(self.mask - 1)
42 forces[self.index] = cell.scaled_positions(scaled_forces)
44 def todict(self):
45 return {
46 'name': 'FixScaled',
47 'kwargs': {'a': self.index.tolist(), 'mask': self.mask.tolist()},
48 }
50 def __repr__(self):
51 name = type(self).__name__
52 return f'{name}(indices={self.index.tolist()}, {self.mask.tolist()})'