[1mdiff --git a/ase/neb.py b/ase/neb.py[m
[1mindex 940a8c7e1..69b4dbb6e 100644[m
[1m--- a/ase/neb.py[m
[1m+++ b/ase/neb.py[m
[36m@@ -49,12 +49,15 @@[m [mclass Spring:[m
[m
[m
class NEBState:[m
[31m- def __init__(self, neb, images, energies, precon):[m
[32m+[m[32m def __init__(self, neb, images, energies):[m
self.neb = neb[m
self.images = images[m
self.energies = energies[m
[31m- self.precon = precon[m
[31m- [m
[32m+[m
[32m+[m[32m @property[m
[32m+[m[32m def precon(self):[m
[32m+[m[32m return self.neb.precon[m
[32m+[m
def spring(self, i):[m
return Spring(self.images[i], self.images[i + 1],[m
self.energies[i], self.energies[i + 1],[m
[36m@@ -166,7 +169,7 @@[m [mclass FullSpringMethod(NEBMethod):[m
"""[m
Elastic band method. The full spring force is included.[m
"""[m
[31m- [m
[32m+[m
def get_tangent(self, state, spring1, spring2, i):[m
# Tangents are bisections of spring-directions[m
# (formula C8 of paper III)[m
[36m@@ -215,12 +218,12 @@[m [mclass BaseSplineMethod(NEBMethod):[m
[m
def add_image_force(self, state, tangential_force, tangent, imgforce,[m
spring1, spring2, i):[m
[31m- [m
[32m+[m
# update preconditioner and apply to image force[m
precon_imgforce, _ = state.precon[i].apply(imgforce.reshape(-1),[m
state.images[i])[m
imgforce[:] = precon_imgforce.reshape(-1, 3)[m
[31m- [m
[32m+[m
# project out tangential component (Eqs 6 and 7 in Paper IV)[m
imgforce -= tangential_force * tangent[m
[m
[36m@@ -240,7 +243,7 @@[m [mclass SplineMethod(BaseSplineMethod):[m
spring1, spring2, i):[m
super().add_image_force(state, tangential_force,[m
tangent, imgforce, spring1, spring2, i)[m
[31m- [m
[32m+[m
reaction_coordinate, _, x, dx, d2x_ds2 = state.spline[m
[m
# Definition following Eq. 9 in Paper IV[m
[36m@@ -259,10 +262,14 @@[m [mclass StringMethod(BaseSplineMethod):[m
def adjust_positions(self):[m
# fit cubic spline to positions, reinterpolate to equispace images[m
# note this use the preconditioned distance metric.[m
[31m- s, _, x, _, _ = self.neb.spline_fit()[m
[32m+[m[32m _, _, x, _, _ =[m
[32m+[m
[32m+[m[32m fit = self.neb.spline_fit()[m
[32m+[m
new_s = np.linspace(0.0, 1.0, self.neb.nimages)[m
[31m- new_positions = x(new_s[1:-1]).reshape(-1, 3)[m
[31m- self.neb.set_positions(new_positions)[m
[32m+[m[32m new_positions = fit.x(new_s[1:-1]).reshape(-1, 3)[m
[32m+[m[32m return new_positions[m
[32m+[m[32m #self.neb.set_positions(new_positions)[m
[m
[m
def get_neb_method(neb, method):[m
[36m@@ -284,7 +291,7 @@[m [mclass BaseNEB:[m
def __init__(self, images, k=0.1, climb=False, parallel=False,[m
remove_rotation_and_translation=False, world=None,[m
method='aseneb', allow_shared_calculator=False, precon=None):[m
[31m- [m
[32m+[m
self.images = images[m
self.climb = climb[m
self.parallel = parallel[m
[36m@@ -460,7 +467,7 @@[m [mclass BaseNEB:[m
self.energies = energies[m
self.real_forces = np.zeros((self.nimages, self.natoms, 3))[m
self.real_forces[1:-1] = forces[m
[31m- [m
[32m+[m
# if this is the first force call, we need to build the preconditioners[m
if self.precon is None or isinstance(self.precon, str):[m
self.precon = make_precon_images(self.precon, images)[m
[36m@@ -605,13 +612,13 @@[m [mclass BaseNEB:[m
raise ValueError(f'unknown norm {norm} in spline_fit()')[m
[m
s = d_P.cumsum() / d_P.sum() # Eq. A1 in paper IV[m
[31m- [m
[32m+[m
x_spline = CubicSpline(s, x, bc_type='not-a-knot')[m
dx_ds_spline = x_spline.derivative()[m
d2x_ds2_spline = x_spline.derivative(2)[m
[31m- [m
[32m+[m
return s, x, x_spline, dx_ds_spline, d2x_ds2_spline[m
[31m- [m
[32m+[m
def integrate_forces(self, spline_points=1000, bc_type='not-a-knot'):[m
"""[m
Use spline fit to integrate forces along MEP to approximate[m
[36m@@ -854,7 +861,7 @@[m [mclass NEBOptimizer(Optimizer):[m
C1=1e-2,[m
C2=2.0):[m
[m
[31m- Optimizer.__init__(self, atoms=neb, restart=restart, [m
[32m+[m[32m Optimizer.__init__(self, atoms=neb, restart=restart,[m
logfile=logfile, trajectory=trajectory,[m
master=master,[m
append_trajectory=append_trajectory,[m
[36m@@ -937,26 +944,33 @@[m [mclass NEBOptimizer(Optimizer):[m
except OptimizerConvergenceError:[m
return False[m
elif self.method == 'krylov':[m
[31m- res = root(self.force_function,[m
[31m- self.get_dofs(),[m
[31m- method='krylov',[m
[31m- options={'disp': True, 'fatol': fmax, 'maxiter': steps},[m
[31m- callback=self.callback)[m
[31m- if res.success:[m
[31m- self.set_dofs(res.x)[m
[31m- return True[m
[31m- else:[m
[31m- return False[m
[32m+[m[32m return self.thing2()[m
[32m+[m[32m else:[m
[32m+[m[32m return self.thing3()[m
[32m+[m
[32m+[m[32m def thing2(self):[m
[32m+[m[32m res = root(self.force_function,[m
[32m+[m[32m self.get_dofs(),[m
[32m+[m[32m method='krylov',[m
[32m+[m[32m options={'disp': True, 'fatol': fmax, 'maxiter': steps},[m
[32m+[m[32m callback=self.callback)[m
[32m+[m[32m if res.success:[m
[32m+[m[32m self.set_dofs(res.x)[m
[32m+[m[32m return True[m
else:[m
[31m- X = self.get_dofs()[m
[31m- for step in range(steps):[m
[31m- F = self.neb.force_function(X)[m
[31m- if self.neb.get_residual() <= fmax:[m
[31m- return True[m
[31m- X += self.alpha * F[m
[31m- self.callback(X)[m
return False[m
[31m- [m
[32m+[m
[32m+[m
[32m+[m[32m def thing3(self):[m
[32m+[m[32m X = self.get_dofs()[m
[32m+[m[32m for step in range(steps):[m
[32m+[m[32m F = self.neb.force_function(X)[m
[32m+[m[32m if self.neb.get_residual() <= fmax:[m
[32m+[m[32m return True[m
[32m+[m[32m X += self.alpha * F[m
[32m+[m[32m self.callback(X)[m
[32m+[m[32m return False[m
[32m+[m
[m
[m
class IDPP(Calculator):[m
[1mdiff --git a/ase/optimize/precon/precon.py b/ase/optimize/precon/precon.py[m
[1mindex 74d9927a5..b7b40e7b0 100644[m
[1m--- a/ase/optimize/precon/precon.py[m
[1m+++ b/ase/optimize/precon/precon.py[m
[36m@@ -22,7 +22,7 @@[m [mfrom ase.optimize.precon.neighbors import (get_neighbours,[m
try:[m
from pyamg import smoothed_aggregation_solver[m
have_pyamg = True[m
[31m- [m
[32m+[m
def create_pyamg_solver(P, max_levels=15):[m
return smoothed_aggregation_solver([m
P, B=None,[m
[36m@@ -40,7 +40,7 @@[m [mtry:[m
max_levels=max_levels,[m
max_coarse=300,[m
coarse_solver='pinv')[m
[31m- [m
[32m+[m
except ImportError:[m
have_pyamg = False[m
[m
[36m@@ -60,13 +60,13 @@[m [mclass Precon(ABC):[m
[m
Args:[m
atoms: the Atoms object used to create the preconditioner.[m
[31m- [m
[32m+[m
reinitialize: if True, parameters of the preconditioner[m
will be recalculated before the preconditioner matrix is[m
created. If False, they will be calculated only when they[m
do not currently have a value (ie, the first time this[m
function is called).[m
[31m- [m
[32m+[m
[m
Returns:[m
P: A sparse scipy csr_matrix. BE AWARE that using[m
[36m@@ -82,7 +82,7 @@[m [mclass Precon(ABC):[m
Return the result of applying P to a vector x[m
"""[m
...[m
[31m- [m
[32m+[m
def dot(self, x, y):[m
"""[m
Return the preconditioned dot product
[m
[36m@@ -90,13 +90,13 @@[m [mclass Precon(ABC):[m
Uses 128-bit floating point math for vector dot products[m
"""[m
return longsum(self.Pdot(x) * y)[m
[31m- [m
[32m+[m
def norm(self, x):[m
"""[m
Return the P-norm of x, where |x|_P = sqrt()[m
"""[m
return np.sqrt(self.dot(x, x))[m
[31m- [m
[32m+[m
def vdot(self, x, y):[m
return self.dot(x.reshape(-1),[m
y.reshape(-1))[m
[36m@@ -129,11 +129,11 @@[m [mclass Precon(ABC):[m
residual = np.linalg.norm(forces, np.inf)[m
precon_forces = self.solve(forces)[m
return precon_forces, residual[m
[31m- [m
[32m+[m
@abstractmethod[m
def copy(self):[m
...[m
[31m- [m
[32m+[m
@abstractmethod[m
def asarray(self):[m
"""[m
[36m@@ -259,11 +259,11 @@[m [mclass SparsePrecon(Precon):[m
raise ValueError('Dimension must be at least 1')[m
self.dim = dim[m
self.logfile = Logfile(logfile)[m
[31m- [m
[32m+[m
if rng is None:[m
rng = np.random.RandomState()[m
self.rng = rng[m
[31m- [m
[32m+[m
def copy(self):[m
return copy.deepcopy(self)[m
[m
[36m@@ -445,10 +445,10 @@[m [mclass SparsePrecon(Precon):[m
[m
self.P = None # force a rebuild with new mu (there may be fixed atoms)[m
return (self.mu, self.mu_c)[m
[31m- [m
[32m+[m
def asarray(self):[m
return np.array(self.P.todense())[m
[31m- [m
[32m+[m
def one_dim_to_ndim(self, csc_P, N):[m
"""Expand an N x N precon matrix to self.dim*N x self.dim * N[m
[m
[36m@@ -576,7 +576,7 @@[m [mclass SparseCoeffPrecon(SparsePrecon):[m
[m
self.P = self.one_dim_to_ndim(csc_P, N)[m
self.create_solver()[m
[31m- [m
[32m+[m
def make_precon(self, atoms, reinitialize=None):[m
if self.r_NN is None:[m
self.r_NN = estimate_nearest_neighbour_distance(atoms)[m
[36m@@ -632,13 +632,7 @@[m [mclass SparseCoeffPrecon(SparsePrecon):[m
...[m
[m
[m
[31m-class Pfrommer(Precon):[m
[31m- """Use initial guess for inverse Hessian from Pfrommer et al. as a[m
[31m- simple preconditioner[m
[31m-[m
[31m- J. Comput. Phys. vol 131 p233-240 (1997)[m
[31m- """[m
[31m-[m
[32m+[m[32mclass PfrommerParameters:[m
def __init__(self, bulk_modulus=500 * units.GPa, phonon_frequency=50 * THz,[m
apply_positions=True, apply_cell=True):[m
"""[m
[36m@@ -648,12 +642,18 @@[m [mclass Pfrommer(Precon):[m
self.phonon_frequency = phonon_frequency[m
self.apply_positions = apply_positions[m
self.apply_cell = apply_cell[m
[31m- self.H0 = None[m
[32m+[m
[32m+[m[32m def copy(self):[m
[32m+[m[32m return PfrommerParameters([m
[32m+[m[32m self.bulk_modulus,[m
[32m+[m[32m self.phonon_frequency,[m
[32m+[m[32m self.apply_positions,[m
[32m+[m[32m self.apply_cell)[m
[m
def make_precon(self, atoms, reinitialize=None):[m
[31m- if self.H0 is not None:[m
[32m+[m[32m #if self.H0 is not None:[m
# only build H0 on first call[m
[31m- return[m
[32m+[m[32m # return[m
[m
variable_cell = False[m
if isinstance(atoms, Filter):[m
[36m@@ -673,8 +673,27 @@[m [mclass Pfrommer(Precon):[m
coeff = 1.0 / (3 * self.bulk_modulus)[m
blocks.append(np.diag([coeff] * 9))[m
[m
[31m- self.H0 = sparse.block_diag(blocks, format='csr')[m
[31m- return[m
[32m+[m[32m H0 = sparse.block_diag(blocks, format='csr')[m
[32m+[m[32m return Pfrommer(H0, self)[m
[32m+[m
[32m+[m
[32m+[m[32mclass Pfrommer(Precon):[m
[32m+[m[32m """Use initial guess for inverse Hessian from Pfrommer et al. as a[m
[32m+[m[32m simple preconditioner[m
[32m+[m
[32m+[m[32m J. Comput. Phys. vol 131 p233-240 (1997)[m
[32m+[m[32m """[m
[32m+[m
[32m+[m[32m def __init__(self, bulk_modulus=500 * units.GPa, phonon_frequency=50 * THz,[m
[32m+[m[32m apply_positions=True, apply_cell=True):[m
[32m+[m[32m """[m
[32m+[m[32m Default bulk modulus is 500 GPa and default phonon frequency is 50 THz[m
[32m+[m[32m """[m
[32m+[m[32m #self.bulk_modulus = bulk_modulus[m
[32m+[m[32m #self.phonon_frequency = phonon_frequency[m
[32m+[m[32m #self.apply_positions = apply_positions[m
[32m+[m[32m #self.apply_cell = apply_cell[m
[32m+[m[32m self.H0 = H0[m
[m
def Pdot(self, x):[m
return self.H0.solve(x)[m
[36m@@ -682,13 +701,7 @@[m [mclass Pfrommer(Precon):[m
def solve(self, x):[m
y = self.H0.dot(x)[m
return y[m
[31m- [m
[31m- def copy(self):[m
[31m- return Pfrommer(self.bulk_modulus,[m
[31m- self.phonon_frequency,[m
[31m- self.apply_positions,[m
[31m- self.apply_cell)[m
[31m- [m
[32m+[m
def asarray(self):[m
return np.array(self.H0.todense())[m
[m
[36m@@ -709,7 +722,7 @@[m [mclass IdentityPrecon(Precon):[m
[m
def copy(self):[m
return IdentityPrecon()[m
[31m- [m
[32m+[m
def asarray(self):[m
return np.eye(3 * len(self.atoms))[m
[m
[36m@@ -784,8 +797,8 @@[m [mdef ijk_to_x(i, j, k):[m
3 * j, 3 * j + 1, 3 * j + 2,[m
3 * k, 3 * k + 1, 3 * k + 2][m
return x[m
[31m- [m
[31m- [m
[32m+[m
[32m+[m
def ijkl_to_x(i, j, k, l):[m
x = [3 * i, 3 * i + 1, 3 * i + 2,[m
3 * j, 3 * j + 1, 3 * j + 2,[m
[36m@@ -839,7 +852,7 @@[m [mclass FF(SparsePrecon):[m
self._make_sparse_precon(atoms, force_stab=self.force_stab)[m
self.logfile.write('--- Precon created in %s seconds ---\n'[m
% (time.time() - start_time))[m
[31m- [m
[32m+[m
def add_morse(self, morse, atoms, row, col, data, conn=None):[m
if self.hessian == 'reduced':[m
i, j, Hx = ff.get_morse_potential_reduced_hessian([m
[36m@@ -856,7 +869,7 @@[m [mclass FF(SparsePrecon):[m
if conn is not None:[m
conn[i, j] = True[m
conn[j, i] = True[m
[31m- [m
[32m+[m
def add_bond(self, bond, atoms, row, col, data, conn=None):[m
if self.hessian == 'reduced':[m
i, j, Hx = ff.get_bond_potential_reduced_hessian([m
[36m@@ -873,7 +886,7 @@[m [mclass FF(SparsePrecon):[m
if conn is not None:[m
conn[i, j] = True[m
conn[j, i] = True[m
[31m- [m
[32m+[m
def add_angle(self, angle, atoms, row, col, data, conn=None):[m
if self.hessian == 'reduced':[m
i, j, k, Hx = ff.get_angle_potential_reduced_hessian([m
[36m@@ -910,7 +923,7 @@[m [mclass FF(SparsePrecon):[m
j, k] = conn[j, l] = conn[k, l] = True[m
conn[j, i] = conn[k, i] = conn[l, i] = conn[[m
k, j] = conn[l, j] = conn[l, k] = True[m
[31m- [m
[32m+[m
def _make_sparse_precon(self, atoms, initial_assembly=False,[m
force_stab=False):[m
N = len(atoms)[m
[36m@@ -922,7 +935,7 @@[m [mclass FF(SparsePrecon):[m
if self.morses is not None:[m
for morse in self.morses:[m
self.add_morse(morse, atoms, row, col, data)[m
[31m- [m
[32m+[m
if self.bonds is not None:[m
for bond in self.bonds:[m
self.add_bond(bond, atoms, row, col, data)[m
[36m@@ -1174,10 +1187,10 @@[m [mdef make_precon(precon, atoms=None, **kwargs):[m
----------[m
precon - one of 'C1', 'Exp', 'Pfrommer', 'FF', 'Exp_FF', 'ID', None[m
or an instance of a subclass of `ase.optimize.precon.Precon`[m
[31m- [m
[32m+[m
atoms - ase.atoms.Atoms instance, optional[m
If present, build apreconditioner for this Atoms object[m
[31m- [m
[32m+[m
**kwargs - additional keyword arguments to pass to Precon constructor[m
[m
Returns[m
[1mdiff --git a/ase/utils/ff.py b/ase/utils/ff.py[m
[1mindex a5b823df0..5f4c5ac83 100644[m
[1m--- a/ase/utils/ff.py[m
[1m+++ b/ase/utils/ff.py[m
[36m@@ -26,6 +26,20 @@[m [mclass Morse:[m
self.r0 = r0[m
self.r = None[m
[m
[32m+[m[32m def get_bond_potential_value(self, atoms):[m
[32m+[m[32m i = self.atomi[m
[32m+[m[32m j = self.atomj[m
[32m+[m
[32m+[m[32m rij = rel_pos_pbc(atoms, i, j)[m
[32m+[m[32m dij = linalg.norm(rij)[m
[32m+[m
[32m+[m[32m v = 0.5*self.k*(dij-self.b0)**2[m
[32m+[m
[32m+[m[32m self.b = dij[m
[32m+[m
[32m+[m[32m return i, j, v[m
[32m+[m
[32m+[m
class Bond:[m
def __init__(self, atomi, atomj, k, b0, [m
alpha=None, rref=None):[m
[36m@@ -211,18 +225,6 @@[m [mdef get_morse_potential_reduced_hessian(atoms, morse):[m
[m
return i, j, Hx[m
[m
[31m-def get_bond_potential_value(atoms, bond):[m
[31m- i = bond.atomi[m
[31m- j = bond.atomj[m
[31m-[m
[31m- rij = rel_pos_pbc(atoms, i, j)[m
[31m- dij = linalg.norm(rij)[m
[31m-[m
[31m- v = 0.5*bond.k*(dij-bond.b0)**2[m
[31m-[m
[31m- bond.b = dij[m
[31m-[m
[31m- return i, j, v[m
[m
def get_bond_potential_gradient(atoms, bond):[m
i = bond.atomi[m