Coverage for ase / constraints / fixed_mode.py: 50.00%
30 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
1import numpy as np
3from ase.constraints.constraint import FixConstraint
6class FixedMode(FixConstraint):
7 """Constrain atoms to move along directions orthogonal to
8 a given mode only. Initialize with a mode, such as one produced by
9 ase.vibrations.Vibrations.get_mode()."""
11 def __init__(self, mode):
12 mode = np.asarray(mode)
13 self.mode = (mode / np.sqrt((mode**2).sum())).reshape(-1)
15 def get_removed_dof(self, atoms):
16 return len(atoms)
18 def adjust_positions(self, atoms, newpositions):
19 newpositions = newpositions.ravel()
20 oldpositions = atoms.positions.ravel()
21 step = newpositions - oldpositions
22 newpositions -= self.mode * np.dot(step, self.mode)
24 def adjust_forces(self, atoms, forces):
25 forces = forces.ravel()
26 forces -= self.mode * np.dot(forces, self.mode)
28 def index_shuffle(self, atoms, ind):
29 eps = 1e-12
30 mode = self.mode.reshape(-1, 3)
31 excluded = np.ones(len(mode), dtype=bool)
32 excluded[ind] = False
33 if (abs(mode[excluded]) > eps).any():
34 raise IndexError('All nonzero parts of mode not in slice')
35 self.mode = mode[ind].ravel()
37 def get_indices(self):
38 # This function will never properly work because it works on all
39 # atoms and it has no idea how to tell how many atoms it is
40 # attached to. If it is being used, surely the user knows
41 # everything is being constrained.
42 return []
44 def todict(self):
45 return {'name': 'FixedMode', 'kwargs': {'mode': self.mode.tolist()}}
47 def __repr__(self):
48 return f'FixedMode({self.mode.tolist()})'