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

1import numpy as np 

2 

3from ase.constraints.constraint import FixConstraint 

4 

5 

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().""" 

10 

11 def __init__(self, mode): 

12 mode = np.asarray(mode) 

13 self.mode = (mode / np.sqrt((mode**2).sum())).reshape(-1) 

14 

15 def get_removed_dof(self, atoms): 

16 return len(atoms) 

17 

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) 

23 

24 def adjust_forces(self, atoms, forces): 

25 forces = forces.ravel() 

26 forces -= self.mode * np.dot(forces, self.mode) 

27 

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() 

36 

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 [] 

43 

44 def todict(self): 

45 return {'name': 'FixedMode', 'kwargs': {'mode': self.mode.tolist()}} 

46 

47 def __repr__(self): 

48 return f'FixedMode({self.mode.tolist()})'