Coverage for ase / utils / abc.py: 100.00%

23 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-04 10:20 +0000

1from abc import ABC, abstractmethod 

2 

3import numpy as np 

4 

5# Due to the high prevalence of cyclic imports surrounding ase.optimize, 

6# we define the Optimizable ABC here in utils. 

7# Can we find a better way? 

8 

9 

10class Optimizable(ABC): 

11 @abstractmethod 

12 def ndofs(self) -> int: 

13 """Return number of degrees of freedom.""" 

14 

15 @abstractmethod 

16 def get_x(self) -> np.ndarray: 

17 """Return current coordinates as a flat ndarray.""" 

18 

19 @abstractmethod 

20 def set_x(self, x: np.ndarray) -> None: 

21 """Set flat ndarray as current coordinates.""" 

22 

23 @abstractmethod 

24 def get_gradient(self): 

25 """Return gradient at current coordinates as flat ndarray.""" 

26 

27 @abstractmethod 

28 def get_value(self) -> float: 

29 """Return function value at current coordinates.""" 

30 

31 @abstractmethod 

32 def iterimages(self): 

33 """Yield domain objects that can be saved as trajectory. 

34 

35 For example this can yield Atoms objects if the optimizer 

36 has a trajectory that can write Atoms objects.""" 

37 

38 def converged(self, gradient: np.ndarray, fmax: float) -> bool: 

39 """Standard implementation of convergence criterion. 

40 

41 This assumes that forces are the actual (Nx3) forces. 

42 We can hopefully change this.""" 

43 assert gradient.ndim == 1 

44 return self.gradient_norm(gradient) < fmax 

45 

46 def gradient_norm(self, gradient): 

47 forces = gradient.reshape(-1, 3) # XXX Cartesian 

48 return np.linalg.norm(forces, axis=1).max() 

49 

50 def __ase_optimizable__(self) -> 'Optimizable': 

51 """Return self, being already an Optimizable.""" 

52 return self