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
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 10:20 +0000
1from abc import ABC, abstractmethod
3import numpy as np
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?
10class Optimizable(ABC):
11 @abstractmethod
12 def ndofs(self) -> int:
13 """Return number of degrees of freedom."""
15 @abstractmethod
16 def get_x(self) -> np.ndarray:
17 """Return current coordinates as a flat ndarray."""
19 @abstractmethod
20 def set_x(self, x: np.ndarray) -> None:
21 """Set flat ndarray as current coordinates."""
23 @abstractmethod
24 def get_gradient(self):
25 """Return gradient at current coordinates as flat ndarray."""
27 @abstractmethod
28 def get_value(self) -> float:
29 """Return function value at current coordinates."""
31 @abstractmethod
32 def iterimages(self):
33 """Yield domain objects that can be saved as trajectory.
35 For example this can yield Atoms objects if the optimizer
36 has a trajectory that can write Atoms objects."""
38 def converged(self, gradient: np.ndarray, fmax: float) -> bool:
39 """Standard implementation of convergence criterion.
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
46 def gradient_norm(self, gradient):
47 forces = gradient.reshape(-1, 3) # XXX Cartesian
48 return np.linalg.norm(forces, axis=1).max()
50 def __ase_optimizable__(self) -> 'Optimizable':
51 """Return self, being already an Optimizable."""
52 return self